samba/SOURCES/samba-4.19-redhat.patch
2026-06-02 23:30:52 -04:00

17966 lines
562 KiB
Diff

From 3c29fc78029e1274f931e171c9e04c19ad0182c1 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Thu, 17 Aug 2023 01:05:54 +0300
Subject: [PATCH 001/122] gp: Support more global trust directories
In addition to the SUSE global trust directory, add support for RHEL and
Debian-based distributions (including Ubuntu).
To determine the correct directory to use, we iterate over the variants
and stop at the first which is a directory.
In case none is found, fallback to the first option which will produce a
warning as it did previously.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit a1b285e485c0b5a8747499bdbbb9f3f4fc025b2f)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 312c8ddf467..1b90ab46e90 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -45,10 +45,12 @@ cert_wrap = b"""
-----BEGIN CERTIFICATE-----
%s
-----END CERTIFICATE-----"""
-global_trust_dir = '/etc/pki/trust/anchors'
endpoint_re = '(https|HTTPS)://(?P<server>[a-zA-Z0-9.-]+)/ADPolicyProvider' + \
'_CEP_(?P<auth>[a-zA-Z]+)/service.svc/CEP'
+global_trust_dirs = ['/etc/pki/trust/anchors', # SUSE
+ '/etc/pki/ca-trust/source/anchors', # RHEL/Fedora
+ '/usr/local/share/ca-certificates'] # Debian/Ubuntu
def octet_string_to_objectGUID(data):
"""Convert an octet string to an objectGUID."""
@@ -249,12 +251,20 @@ def getca(ca, url, trust_dir):
return root_certs
+def find_global_trust_dir():
+ """Return the global trust dir using known paths from various Linux distros."""
+ for trust_dir in global_trust_dirs:
+ if os.path.isdir(trust_dir):
+ return trust_dir
+ return global_trust_dirs[0]
+
def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
"""Install the root certificate chain."""
data = dict({'files': [], 'templates': []}, **ca)
url = 'http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?' % ca['hostname']
root_certs = getca(ca, url, trust_dir)
data['files'].extend(root_certs)
+ global_trust_dir = find_global_trust_dir()
for src in root_certs:
# Symlink the certs to global trust dir
dst = os.path.join(global_trust_dir, os.path.basename(src))
--
2.53.0
From 063606e8ec83a58972df47eb561ab267f8937ba4 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Thu, 17 Aug 2023 01:09:28 +0300
Subject: [PATCH 002/122] gp: Support update-ca-trust helper
This is used on RHEL/Fedora instead of update-ca-certificates. They
behave similarly so it's enough to change the command name.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit fa80d1d86439749c44e60cf9075e84dc9ed3c268)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 1b90ab46e90..cefdafa21b2 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -258,6 +258,10 @@ def find_global_trust_dir():
return trust_dir
return global_trust_dirs[0]
+def update_ca_command():
+ """Return the command to update the CA trust store."""
+ return which('update-ca-certificates') or which('update-ca-trust')
+
def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
"""Install the root certificate chain."""
data = dict({'files': [], 'templates': []}, **ca)
@@ -283,7 +287,7 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
# already exists. Ignore the FileExistsError. Preserve the
# existing symlink in the unapply data.
data['files'].append(dst)
- update = which('update-ca-certificates')
+ update = update_ca_command()
if update is not None:
Popen([update]).wait()
# Setup Certificate Auto Enrollment
--
2.53.0
From 3b548bf280ca59ef12a7af10a9131813067a850a Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Fri, 11 Aug 2023 18:46:42 +0300
Subject: [PATCH 003/122] gp: Change root cert extension suffix
On Ubuntu, certificates must end in '.crt' in order to be considered by
the `update-ca-certificates` helper.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit bce3a89204545dcab5fb39a712590f6e166f997b)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index cefdafa21b2..c562722906b 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -241,7 +241,8 @@ def getca(ca, url, trust_dir):
certs = load_der_pkcs7_certificates(r.content)
for i in range(0, len(certs)):
cert = certs[i].public_bytes(Encoding.PEM)
- dest = '%s.%d' % (root_cert, i)
+ filename, extension = root_cert.rsplit('.', 1)
+ dest = '%s.%d.%s' % (filename, i, extension)
with open(dest, 'wb') as w:
w.write(cert)
root_certs.append(dest)
--
2.53.0
From 7592ed5032836dc43f657f66607a0a4661edcdb4 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Fri, 18 Aug 2023 17:06:43 +0300
Subject: [PATCH 004/122] gp: Test with binary content for certificate data
This fails all GPO-related tests that call `gpupdate --rsop`.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit 1ef722cf66f9ec99f52939f1cfca031c5fe1ad70)
---
python/samba/tests/gpo.py | 8 ++++----
selftest/knownfail.d/gpo | 13 +++++++++++++
2 files changed, 17 insertions(+), 4 deletions(-)
create mode 100644 selftest/knownfail.d/gpo
diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
index e4b75cc62a4..963f873f755 100644
--- a/python/samba/tests/gpo.py
+++ b/python/samba/tests/gpo.py
@@ -6783,14 +6783,14 @@ class GPOTests(tests.TestCase):
ldb.add({'dn': certa_dn,
'objectClass': 'certificationAuthority',
'authorityRevocationList': ['XXX'],
- 'cACertificate': 'XXX',
+ 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
'certificateRevocationList': ['XXX'],
})
# Write the dummy pKIEnrollmentService
enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
ldb.add({'dn': enroll_dn,
'objectClass': 'pKIEnrollmentService',
- 'cACertificate': 'XXXX',
+ 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
'certificateTemplates': ['Machine'],
'dNSHostName': hostname,
})
@@ -7201,14 +7201,14 @@ class GPOTests(tests.TestCase):
ldb.add({'dn': certa_dn,
'objectClass': 'certificationAuthority',
'authorityRevocationList': ['XXX'],
- 'cACertificate': 'XXX',
+ 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
'certificateRevocationList': ['XXX'],
})
# Write the dummy pKIEnrollmentService
enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
ldb.add({'dn': enroll_dn,
'objectClass': 'pKIEnrollmentService',
- 'cACertificate': 'XXXX',
+ 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
'certificateTemplates': ['Machine'],
'dNSHostName': hostname,
})
diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo
new file mode 100644
index 00000000000..0aad59607c2
--- /dev/null
+++ b/selftest/knownfail.d/gpo
@@ -0,0 +1,13 @@
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_user_centrify_crontab_ext
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_user_scripts_ext
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_rsop
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_access
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_files
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_issue
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_motd
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_openssh
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_startup_scripts
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_sudoers
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_symlink
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_advanced_gp_cert_auto_enroll_ext
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext
--
2.53.0
From 7f7b235bda9e85c5ea330e52e734d1113a884571 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Wed, 16 Aug 2023 12:20:11 +0300
Subject: [PATCH 005/122] gp: Convert CA certificates to base64
I don't know whether this applies universally, but in our case the
contents of `es['cACertificate'][0]` are binary, so cleanly converting
to a string fails with the following:
'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte
We found a fix to be encoding the certificate to base64 when
constructing the CA list.
Section 4.4.5.2 of MS-CAESO also suggests that the content of
`cACertificate` is binary (OCTET string).
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit 157335ee93eb866f9b6a47486a5668d6e76aced5)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 5 ++---
selftest/knownfail.d/gpo | 13 -------------
2 files changed, 2 insertions(+), 16 deletions(-)
delete mode 100644 selftest/knownfail.d/gpo
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index c562722906b..c8b5368c16a 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -158,7 +158,7 @@ def fetch_certification_authorities(ldb):
for es in res:
data = { 'name': get_string(es['cn'][0]),
'hostname': get_string(es['dNSHostName'][0]),
- 'cACertificate': get_string(es['cACertificate'][0])
+ 'cACertificate': get_string(base64.b64encode(es['cACertificate'][0]))
}
result.append(data)
return result
@@ -176,8 +176,7 @@ def fetch_template_attrs(ldb, name, attrs=None):
return {'msPKI-Minimal-Key-Size': ['2048']}
def format_root_cert(cert):
- cert = base64.b64encode(cert.encode())
- return cert_wrap % re.sub(b"(.{64})", b"\\1\n", cert, 0, re.DOTALL)
+ return cert_wrap % re.sub(b"(.{64})", b"\\1\n", cert.encode(), 0, re.DOTALL)
def find_cepces_submit():
certmonger_dirs = [os.environ.get("PATH"), '/usr/lib/certmonger',
diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo
deleted file mode 100644
index 0aad59607c2..00000000000
--- a/selftest/knownfail.d/gpo
+++ /dev/null
@@ -1,13 +0,0 @@
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_user_centrify_crontab_ext
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_user_scripts_ext
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_rsop
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_access
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_files
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_issue
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_motd
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_openssh
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_startup_scripts
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_sudoers
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_vgp_symlink
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_advanced_gp_cert_auto_enroll_ext
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext
--
2.53.0
From 49cc74015a603e80048a38fe635cd1ac28938ee4 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Fri, 18 Aug 2023 17:16:23 +0300
Subject: [PATCH 006/122] gp: Test adding new cert templates enforces changes
Ensure that cepces-submit reporting additional templates and re-applying
will enforce the updated policy.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit 2d6943a864405f324c467e8c3464c31ac08457b0)
---
python/samba/tests/bin/cepces-submit | 3 +-
python/samba/tests/gpo.py | 48 ++++++++++++++++++++++++++++
selftest/knownfail.d/gpo | 2 ++
3 files changed, 52 insertions(+), 1 deletion(-)
create mode 100644 selftest/knownfail.d/gpo
diff --git a/python/samba/tests/bin/cepces-submit b/python/samba/tests/bin/cepces-submit
index 668682a9f58..de63164692b 100755
--- a/python/samba/tests/bin/cepces-submit
+++ b/python/samba/tests/bin/cepces-submit
@@ -14,4 +14,5 @@ if __name__ == "__main__":
assert opts.auth == 'Kerberos'
if 'CERTMONGER_OPERATION' in os.environ and \
os.environ['CERTMONGER_OPERATION'] == 'GET-SUPPORTED-TEMPLATES':
- print('Machine') # Report a Machine template
+ templates = os.environ.get('CEPCES_SUBMIT_SUPPORTED_TEMPLATES', 'Machine').split(',')
+ print('\n'.join(templates)) # Report the requested templates
diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
index 963f873f755..e75c411bde7 100644
--- a/python/samba/tests/gpo.py
+++ b/python/samba/tests/gpo.py
@@ -6812,6 +6812,23 @@ class GPOTests(tests.TestCase):
self.assertTrue(os.path.exists(machine_crt),
'Machine key was not generated')
+ # Subsequent apply should react to new certificate templates
+ os.environ['CEPCES_SUBMIT_SUPPORTED_TEMPLATES'] = 'Machine,Workstation'
+ self.addCleanup(os.environ.pop, 'CEPCES_SUBMIT_SUPPORTED_TEMPLATES')
+ ext.process_group_policy([], gpos, dname, dname)
+ self.assertTrue(os.path.exists(ca_crt),
+ 'Root CA certificate was not requested')
+ self.assertTrue(os.path.exists(machine_crt),
+ 'Machine certificate was not requested')
+ self.assertTrue(os.path.exists(machine_crt),
+ 'Machine key was not generated')
+ workstation_crt = os.path.join(dname, '%s.Workstation.crt' % ca_cn)
+ self.assertTrue(os.path.exists(workstation_crt),
+ 'Workstation certificate was not requested')
+ workstation_key = os.path.join(dname, '%s.Workstation.key' % ca_cn)
+ self.assertTrue(os.path.exists(workstation_crt),
+ 'Workstation key was not generated')
+
# Verify RSOP does not fail
ext.rsop([g for g in gpos if g.name == guid][0])
@@ -6829,11 +6846,17 @@ class GPOTests(tests.TestCase):
'Machine certificate was not removed')
self.assertFalse(os.path.exists(machine_crt),
'Machine key was not removed')
+ self.assertFalse(os.path.exists(workstation_crt),
+ 'Workstation certificate was not removed')
+ self.assertFalse(os.path.exists(workstation_crt),
+ 'Workstation key was not removed')
out, _ = Popen(['getcert', 'list-cas'], stdout=PIPE).communicate()
self.assertNotIn(get_bytes(ca_cn), out, 'CA was not removed')
out, _ = Popen(['getcert', 'list'], stdout=PIPE).communicate()
self.assertNotIn(b'Machine', out,
'Machine certificate not removed')
+ self.assertNotIn(b'Workstation', out,
+ 'Workstation certificate not removed')
# Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
ldb.delete(certa_dn)
@@ -7233,6 +7256,25 @@ class GPOTests(tests.TestCase):
self.assertTrue(os.path.exists(machine_crt),
'Machine key was not generated')
+ # Subsequent apply should react to new certificate templates
+ os.environ['CEPCES_SUBMIT_SUPPORTED_TEMPLATES'] = 'Machine,Workstation'
+ self.addCleanup(os.environ.pop, 'CEPCES_SUBMIT_SUPPORTED_TEMPLATES')
+ ext.process_group_policy([], gpos, dname, dname)
+ for ca in ca_list:
+ self.assertTrue(os.path.exists(ca_crt),
+ 'Root CA certificate was not requested')
+ self.assertTrue(os.path.exists(machine_crt),
+ 'Machine certificate was not requested')
+ self.assertTrue(os.path.exists(machine_crt),
+ 'Machine key was not generated')
+
+ workstation_crt = os.path.join(dname, '%s.Workstation.crt' % ca)
+ self.assertTrue(os.path.exists(workstation_crt),
+ 'Workstation certificate was not requested')
+ workstation_key = os.path.join(dname, '%s.Workstation.key' % ca)
+ self.assertTrue(os.path.exists(workstation_crt),
+ 'Workstation key was not generated')
+
# Verify RSOP does not fail
ext.rsop([g for g in gpos if g.name == guid][0])
@@ -7250,12 +7292,18 @@ class GPOTests(tests.TestCase):
'Machine certificate was not removed')
self.assertFalse(os.path.exists(machine_crt),
'Machine key was not removed')
+ self.assertFalse(os.path.exists(workstation_crt),
+ 'Workstation certificate was not removed')
+ self.assertFalse(os.path.exists(workstation_crt),
+ 'Workstation key was not removed')
out, _ = Popen(['getcert', 'list-cas'], stdout=PIPE).communicate()
for ca in ca_list:
self.assertNotIn(get_bytes(ca), out, 'CA was not removed')
out, _ = Popen(['getcert', 'list'], stdout=PIPE).communicate()
self.assertNotIn(b'Machine', out,
'Machine certificate not removed')
+ self.assertNotIn(b'Workstation', out,
+ 'Workstation certificate not removed')
# Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
ldb.delete(certa_dn)
diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo
new file mode 100644
index 00000000000..4edc1dce730
--- /dev/null
+++ b/selftest/knownfail.d/gpo
@@ -0,0 +1,2 @@
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_advanced_gp_cert_auto_enroll_ext
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext
--
2.53.0
From 4c0906bd79f030e591701234bc54bc749a42d686 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Wed, 16 Aug 2023 12:37:17 +0300
Subject: [PATCH 007/122] gp: Template changes should invalidate cache
If certificate templates are added or removed, the autoenroll extension
should react to this and reapply the policy. Previously this wasn't
taken into account.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit 2a6ae997f2464b12b72b5314fa80d9784fb0f6c1)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 15 ++++++++++-----
selftest/knownfail.d/gpo | 2 --
2 files changed, 10 insertions(+), 7 deletions(-)
delete mode 100644 selftest/knownfail.d/gpo
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index c8b5368c16a..8233713e8ad 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -262,6 +262,11 @@ def update_ca_command():
"""Return the command to update the CA trust store."""
return which('update-ca-certificates') or which('update-ca-trust')
+def changed(new_data, old_data):
+ """Return True if any key present in both dicts has changed."""
+ return any((new_data[k] != old_data[k] if k in old_data else False) \
+ for k in new_data.keys())
+
def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
"""Install the root certificate chain."""
data = dict({'files': [], 'templates': []}, **ca)
@@ -351,12 +356,12 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier):
# If the policy has changed, unapply, then apply new policy
old_val = self.cache_get_attribute_value(guid, attribute)
old_data = json.loads(old_val) if old_val is not None else {}
- if all([(ca[k] == old_data[k] if k in old_data else False) \
- for k in ca.keys()]) or \
- self.cache_get_apply_state() == GPOSTATE.ENFORCE:
+ templates = ['%s.%s' % (ca['name'], t.decode()) for t in get_supported_templates(ca['hostname'])]
+ new_data = { 'templates': templates, **ca }
+ if changed(new_data, old_data) or self.cache_get_apply_state() == GPOSTATE.ENFORCE:
self.unapply(guid, attribute, old_val)
- # If policy is already applied, skip application
- if old_val is not None and \
+ # If policy is already applied and unchanged, skip application
+ if old_val is not None and not changed(new_data, old_data) and \
self.cache_get_apply_state() != GPOSTATE.ENFORCE:
return
diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo
deleted file mode 100644
index 4edc1dce730..00000000000
--- a/selftest/knownfail.d/gpo
+++ /dev/null
@@ -1,2 +0,0 @@
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_advanced_gp_cert_auto_enroll_ext
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext
--
2.53.0
From e61f30dc2518d5a1c239f090baea4a309307f3f8 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Fri, 18 Aug 2023 17:26:59 +0300
Subject: [PATCH 008/122] gp: Test disabled enrollment unapplies policy
For this we need to stage a Registry.pol file with certificate
autoenrollment enabled, but with checkboxes unticked.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit ee814f7707a8ddef2657212cd6d31799501b7bb3)
---
python/samba/tests/gpo.py | 54 +++++++++++++++++++++++++++++++++++++++
selftest/knownfail.d/gpo | 1 +
2 files changed, 55 insertions(+)
create mode 100644 selftest/knownfail.d/gpo
diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
index e75c411bde7..580f3568de8 100644
--- a/python/samba/tests/gpo.py
+++ b/python/samba/tests/gpo.py
@@ -281,6 +281,28 @@ b"""
</PolFile>
"""
+auto_enroll_unchecked_reg_pol = \
+b"""
+<?xml version="1.0" encoding="utf-8"?>
+<PolFile num_entries="3" signature="PReg" version="1">
+ <Entry type="4" type_name="REG_DWORD">
+ <Key>Software\Policies\Microsoft\Cryptography\AutoEnrollment</Key>
+ <ValueName>AEPolicy</ValueName>
+ <Value>0</Value>
+ </Entry>
+ <Entry type="4" type_name="REG_DWORD">
+ <Key>Software\Policies\Microsoft\Cryptography\AutoEnrollment</Key>
+ <ValueName>OfflineExpirationPercent</ValueName>
+ <Value>10</Value>
+ </Entry>
+ <Entry type="1" type_name="REG_SZ">
+ <Key>Software\Policies\Microsoft\Cryptography\AutoEnrollment</Key>
+ <ValueName>OfflineExpirationStoreNames</ValueName>
+ <Value>MY</Value>
+ </Entry>
+</PolFile>
+"""
+
advanced_enroll_reg_pol = \
b"""
<?xml version="1.0" encoding="utf-8"?>
@@ -6836,6 +6858,38 @@ class GPOTests(tests.TestCase):
ret = rsop(self.lp)
self.assertEqual(ret, 0, 'gpupdate --rsop failed!')
+ # Remove policy by staging pol file with auto-enroll unchecked
+ parser.load_xml(etree.fromstring(auto_enroll_unchecked_reg_pol.strip()))
+ ret = stage_file(reg_pol, ndr_pack(parser.pol_file))
+ self.assertTrue(ret, 'Could not create the target %s' % reg_pol)
+ ext.process_group_policy([], gpos, dname, dname)
+ self.assertFalse(os.path.exists(ca_crt),
+ 'Root CA certificate was not removed')
+ self.assertFalse(os.path.exists(machine_crt),
+ 'Machine certificate was not removed')
+ self.assertFalse(os.path.exists(machine_crt),
+ 'Machine key was not removed')
+ self.assertFalse(os.path.exists(workstation_crt),
+ 'Workstation certificate was not removed')
+ self.assertFalse(os.path.exists(workstation_crt),
+ 'Workstation key was not removed')
+
+ # Reapply policy by staging the enabled pol file
+ parser.load_xml(etree.fromstring(auto_enroll_reg_pol.strip()))
+ ret = stage_file(reg_pol, ndr_pack(parser.pol_file))
+ self.assertTrue(ret, 'Could not create the target %s' % reg_pol)
+ ext.process_group_policy([], gpos, dname, dname)
+ self.assertTrue(os.path.exists(ca_crt),
+ 'Root CA certificate was not requested')
+ self.assertTrue(os.path.exists(machine_crt),
+ 'Machine certificate was not requested')
+ self.assertTrue(os.path.exists(machine_crt),
+ 'Machine key was not generated')
+ self.assertTrue(os.path.exists(workstation_crt),
+ 'Workstation certificate was not requested')
+ self.assertTrue(os.path.exists(workstation_crt),
+ 'Workstation key was not generated')
+
# Remove policy
gp_db = store.get_gplog(machine_creds.get_username())
del_gpos = get_deleted_gpos_list(gp_db, [])
diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo
new file mode 100644
index 00000000000..83bc9f0ac1f
--- /dev/null
+++ b/selftest/knownfail.d/gpo
@@ -0,0 +1 @@
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext
--
2.53.0
From 7757b9b48546d71e19798d1260da97780caa99c3 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Wed, 16 Aug 2023 12:33:59 +0300
Subject: [PATCH 009/122] gp: Send list of keys instead of dict to remove
`cache_get_all_attribute_values` returns a dict whereas we need to pass
a list of keys to `remove`. These will be interpolated in the gpdb search.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: David Mulder <dmulder@samba.org>
Autobuild-User(master): Andrew Bartlett <abartlet@samba.org>
Autobuild-Date(master): Mon Aug 28 03:01:22 UTC 2023 on atb-devel-224
(cherry picked from commit 7dc181757c76b881ceaf1915ebb0bfbcf5aca83a)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 2 +-
selftest/knownfail.d/gpo | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
delete mode 100644 selftest/knownfail.d/gpo
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 8233713e8ad..64c35782ae8 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -415,7 +415,7 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier):
# remove any existing policy
ca_attrs = \
self.cache_get_all_attribute_values(gpo.name)
- self.clean(gpo.name, remove=ca_attrs)
+ self.clean(gpo.name, remove=list(ca_attrs.keys()))
def __read_cep_data(self, guid, ldb, end_point_information,
trust_dir, private_dir):
diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo
deleted file mode 100644
index 83bc9f0ac1f..00000000000
--- a/selftest/knownfail.d/gpo
+++ /dev/null
@@ -1 +0,0 @@
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext
--
2.53.0
From 4e9b2e6409c5764ec0e66cc6c90b08e70f702e7c Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Tue, 9 Jan 2024 08:50:01 +0100
Subject: [PATCH 010/122] python:gp: Print a nice message if cepces-submit
can't be found
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15552
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: David Mulder <dmulder@samba.org>
(cherry picked from commit 8eb42425a8eb1b30ca0e94dfc01d8175ae5cde4b)
Autobuild-User(v4-19-test): Jule Anger <janger@samba.org>
Autobuild-Date(v4-19-test): Mon Jan 15 11:11:31 UTC 2024 on atb-devel-224
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 24 ++++++++++++----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 64c35782ae8..08d1a7348cd 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -185,17 +185,19 @@ def find_cepces_submit():
def get_supported_templates(server):
cepces_submit = find_cepces_submit()
- if os.path.exists(cepces_submit):
- env = os.environ
- env['CERTMONGER_OPERATION'] = 'GET-SUPPORTED-TEMPLATES'
- p = Popen([cepces_submit, '--server=%s' % server, '--auth=Kerberos'],
- env=env, stdout=PIPE, stderr=PIPE)
- out, err = p.communicate()
- if p.returncode != 0:
- data = { 'Error': err.decode() }
- log.error('Failed to fetch the list of supported templates.', data)
- return out.strip().split()
- return []
+ if not cepces_submit or not os.path.exists(cepces_submit):
+ log.error('Failed to find cepces-submit')
+ return []
+
+ env = os.environ
+ env['CERTMONGER_OPERATION'] = 'GET-SUPPORTED-TEMPLATES'
+ p = Popen([cepces_submit, '--server=%s' % server, '--auth=Kerberos'],
+ env=env, stdout=PIPE, stderr=PIPE)
+ out, err = p.communicate()
+ if p.returncode != 0:
+ data = {'Error': err.decode()}
+ log.error('Failed to fetch the list of supported templates.', data)
+ return out.strip().split()
def getca(ca, url, trust_dir):
--
2.53.0
From fb3aefff51c02cf8ba3f8dfeb7d3f971e8d4902a Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Mon, 8 Jan 2024 18:05:08 +0200
Subject: [PATCH 011/122] gpo: Test certificate policy without NDES
As of 8231eaf856b, the NDES feature is no longer required on Windows, as
cert auto-enroll can use the certificate from the LDAP request.
However, 157335ee93e changed the implementation to convert the LDAP
certificate to base64 due to it failing to cleanly convert to a string.
Because of insufficient test coverage I missed handling the part where
NDES is disabled or not reachable and the LDAP certificate was imported.
The call to load_der_x509_certificate now fails with an error because it
expects binary data, yet it receives a base64 encoded string.
This adds a test to confirm the issue.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15557
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(cherry picked from commit 0d1ff69936f18ea729fc11fbbb1569a833302572)
---
python/samba/tests/gpo.py | 126 ++++++++++++++++++++++++++++++++++++--
selftest/knownfail.d/gpo | 1 +
2 files changed, 121 insertions(+), 6 deletions(-)
create mode 100644 selftest/knownfail.d/gpo
diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
index 580f3568de8..a78af17dba4 100644
--- a/python/samba/tests/gpo.py
+++ b/python/samba/tests/gpo.py
@@ -102,17 +102,21 @@ def dummy_certificate():
# Dummy requests structure for Certificate Auto Enrollment
class dummy_requests(object):
- @staticmethod
- def get(url=None, params=None):
+ class exceptions(object):
+ ConnectionError = Exception
+
+ def __init__(self, want_exception=False):
+ self.want_exception = want_exception
+
+ def get(self, url=None, params=None):
+ if self.want_exception:
+ raise self.exceptions.ConnectionError
+
dummy = requests.Response()
dummy._content = dummy_certificate()
dummy.headers = {'Content-Type': 'application/x-x509-ca-cert'}
return dummy
- class exceptions(object):
- ConnectionError = Exception
-cae.requests = dummy_requests
-
realm = os.environ.get('REALM')
policies = realm + '/POLICIES'
realm = realm.lower()
@@ -6764,6 +6768,114 @@ class GPOTests(tests.TestCase):
# Unstage the Registry.pol file
unstage_file(reg_pol)
+ def test_gp_cert_auto_enroll_ext_without_ndes(self):
+ local_path = self.lp.cache_path('gpo_cache')
+ guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
+ reg_pol = os.path.join(local_path, policies, guid,
+ 'MACHINE/REGISTRY.POL')
+ cache_dir = self.lp.get('cache directory')
+ store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
+
+ machine_creds = Credentials()
+ machine_creds.guess(self.lp)
+ machine_creds.set_machine_account()
+
+ # Initialize the group policy extension
+ cae.requests = dummy_requests(want_exception=True)
+ ext = cae.gp_cert_auto_enroll_ext(self.lp, machine_creds,
+ machine_creds.get_username(), store)
+
+ gpos = get_gpo_list(self.server, machine_creds, self.lp,
+ machine_creds.get_username())
+
+ # Stage the Registry.pol file with test data
+ parser = GPPolParser()
+ parser.load_xml(etree.fromstring(auto_enroll_reg_pol.strip()))
+ ret = stage_file(reg_pol, ndr_pack(parser.pol_file))
+ self.assertTrue(ret, 'Could not create the target %s' % reg_pol)
+
+ # Write the dummy CA entry, Enrollment Services, and Templates Entries
+ admin_creds = Credentials()
+ admin_creds.set_username(os.environ.get('DC_USERNAME'))
+ admin_creds.set_password(os.environ.get('DC_PASSWORD'))
+ admin_creds.set_realm(os.environ.get('REALM'))
+ hostname = get_dc_hostname(machine_creds, self.lp)
+ url = 'ldap://%s' % hostname
+ ldb = Ldb(url=url, session_info=system_session(),
+ lp=self.lp, credentials=admin_creds)
+ # Write the dummy CA
+ confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn
+ ca_cn = '%s-CA' % hostname.replace('.', '-')
+ certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn)
+ ldb.add({'dn': certa_dn,
+ 'objectClass': 'certificationAuthority',
+ 'authorityRevocationList': ['XXX'],
+ 'cACertificate': dummy_certificate(),
+ 'certificateRevocationList': ['XXX'],
+ })
+ # Write the dummy pKIEnrollmentService
+ enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
+ ldb.add({'dn': enroll_dn,
+ 'objectClass': 'pKIEnrollmentService',
+ 'cACertificate': dummy_certificate(),
+ 'certificateTemplates': ['Machine'],
+ 'dNSHostName': hostname,
+ })
+ # Write the dummy pKICertificateTemplate
+ template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn
+ ldb.add({'dn': template_dn,
+ 'objectClass': 'pKICertificateTemplate',
+ })
+
+ with TemporaryDirectory() as dname:
+ try:
+ ext.process_group_policy([], gpos, dname, dname)
+ except Exception as e:
+ self.fail(str(e))
+
+ ca_crt = os.path.join(dname, '%s.crt' % ca_cn)
+ self.assertTrue(os.path.exists(ca_crt),
+ 'Root CA certificate was not requested')
+ machine_crt = os.path.join(dname, '%s.Machine.crt' % ca_cn)
+ self.assertTrue(os.path.exists(machine_crt),
+ 'Machine certificate was not requested')
+ machine_key = os.path.join(dname, '%s.Machine.key' % ca_cn)
+ self.assertTrue(os.path.exists(machine_key),
+ 'Machine key was not generated')
+
+ # Verify RSOP does not fail
+ ext.rsop([g for g in gpos if g.name == guid][0])
+
+ # Check that a call to gpupdate --rsop also succeeds
+ ret = rsop(self.lp)
+ self.assertEqual(ret, 0, 'gpupdate --rsop failed!')
+
+ # Remove policy
+ gp_db = store.get_gplog(machine_creds.get_username())
+ del_gpos = get_deleted_gpos_list(gp_db, [])
+ ext.process_group_policy(del_gpos, [], dname)
+ self.assertFalse(os.path.exists(ca_crt),
+ 'Root CA certificate was not removed')
+ self.assertFalse(os.path.exists(machine_crt),
+ 'Machine certificate was not removed')
+ self.assertFalse(os.path.exists(machine_key),
+ 'Machine key was not removed')
+ out, _ = Popen(['getcert', 'list-cas'], stdout=PIPE).communicate()
+ self.assertNotIn(get_bytes(ca_cn), out, 'CA was not removed')
+ out, _ = Popen(['getcert', 'list'], stdout=PIPE).communicate()
+ self.assertNotIn(b'Machine', out,
+ 'Machine certificate not removed')
+ self.assertNotIn(b'Workstation', out,
+ 'Workstation certificate not removed')
+
+ # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
+ ldb.delete(certa_dn)
+ ldb.delete(enroll_dn)
+ ldb.delete(template_dn)
+
+ # Unstage the Registry.pol file
+ unstage_file(reg_pol)
+
def test_gp_cert_auto_enroll_ext(self):
local_path = self.lp.cache_path('gpo_cache')
guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}'
@@ -6777,6 +6889,7 @@ class GPOTests(tests.TestCase):
machine_creds.set_machine_account()
# Initialize the group policy extension
+ cae.requests = dummy_requests()
ext = cae.gp_cert_auto_enroll_ext(self.lp, machine_creds,
machine_creds.get_username(), store)
@@ -7241,6 +7354,7 @@ class GPOTests(tests.TestCase):
machine_creds.set_machine_account()
# Initialize the group policy extension
+ cae.requests = dummy_requests()
ext = cae.gp_cert_auto_enroll_ext(self.lp, machine_creds,
machine_creds.get_username(), store)
diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo
new file mode 100644
index 00000000000..f1e590bc7d8
--- /dev/null
+++ b/selftest/knownfail.d/gpo
@@ -0,0 +1 @@
+^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext_without_ndes
--
2.53.0
From 1a9af36177c7491687c75df151474bb10285f00e Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Thu, 18 Jan 2024 20:23:24 +0200
Subject: [PATCH 012/122] gpo: Decode base64 root cert before importing
The reasoning behind this is described in the previous commit message,
but essentially this should either be wrapped in certificate blocks and
imported as PEM, or converted back to binary and imported as DER.
I've opted for the latter since it's how it used to work before it
regressed in 157335ee93e.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15557
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(cherry picked from commit 3f3ddfa699a33c2c8a59f7fb9ee044bb2a6e0e06)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 5 +++--
selftest/knownfail.d/gpo | 1 -
2 files changed, 3 insertions(+), 3 deletions(-)
delete mode 100644 selftest/knownfail.d/gpo
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 08d1a7348cd..cd5e54f1110 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -217,10 +217,11 @@ def getca(ca, url, trust_dir):
' installed or not configured.')
if 'cACertificate' in ca:
log.warn('Installing the server certificate only.')
+ der_certificate = base64.b64decode(ca['cACertificate'])
try:
- cert = load_der_x509_certificate(ca['cACertificate'])
+ cert = load_der_x509_certificate(der_certificate)
except TypeError:
- cert = load_der_x509_certificate(ca['cACertificate'],
+ cert = load_der_x509_certificate(der_certificate,
default_backend())
cert_data = cert.public_bytes(Encoding.PEM)
with open(root_cert, 'wb') as w:
diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo
deleted file mode 100644
index f1e590bc7d8..00000000000
--- a/selftest/knownfail.d/gpo
+++ /dev/null
@@ -1 +0,0 @@
-^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext_without_ndes
--
2.53.0
From f5fc88f9ae255f4dc135580f0fa4a02f5addc390 Mon Sep 17 00:00:00 2001
From: Gabriel Nagy <gabriel.nagy@canonical.com>
Date: Fri, 19 Jan 2024 11:36:19 +0200
Subject: [PATCH 013/122] gpo: Do not get templates list on first run
This is a visual fix and has no impact on functionality apart from
cleaner log messages.
The point of this is to get the list of supported templates in order to
compute a diff between the current applied templates and the updated
list, so we are able to unapply and reapply the policy in case there are
differences.
However this code path is executed on first applies as well, at which
point the root CA is not yet set up. This causes the
`get_supported_templates` call to fail, which is not a hard failure but
still pollutes the logs. In this case it's safe to avoid executing the
command as the policy will be applied regardless.
Signed-off-by: Gabriel Nagy <gabriel.nagy@canonical.com>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Mon Jan 22 16:48:57 UTC 2024 on atb-devel-224
(cherry picked from commit 8579340fc540633c13c017d896034904a8dbd55c)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index cd5e54f1110..559c903e1a2 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -359,7 +359,8 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier):
# If the policy has changed, unapply, then apply new policy
old_val = self.cache_get_attribute_value(guid, attribute)
old_data = json.loads(old_val) if old_val is not None else {}
- templates = ['%s.%s' % (ca['name'], t.decode()) for t in get_supported_templates(ca['hostname'])]
+ templates = ['%s.%s' % (ca['name'], t.decode()) for t in get_supported_templates(ca['hostname'])] \
+ if old_val is not None else []
new_data = { 'templates': templates, **ca }
if changed(new_data, old_data) or self.cache_get_apply_state() == GPOSTATE.ENFORCE:
self.unapply(guid, attribute, old_val)
--
2.53.0
From e8a6219181f2af87813b53fd09684650c1aa6f90 Mon Sep 17 00:00:00 2001
From: David Mulder <dmulder@samba.org>
Date: Fri, 5 Jan 2024 08:47:07 -0700
Subject: [PATCH 014/122] gp: Skip site GP list if no site is found
[MS-GPOL] 3.2.5.1.4 Site Search says if the site
search returns ERROR_NO_SITENAME, the GP site
search should be skipped.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15548
Signed-off-by: David Mulder <dmulder@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Tue Jan 23 11:20:35 UTC 2024 on atb-devel-224
(cherry picked from commit f05b61b4991e7f51bd184d76a79f8b50114a0ff3)
---
python/samba/gp/gpclass.py | 30 ++++++++++++++++++------------
1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/python/samba/gp/gpclass.py b/python/samba/gp/gpclass.py
index 617ef79350c..babd8f90748 100644
--- a/python/samba/gp/gpclass.py
+++ b/python/samba/gp/gpclass.py
@@ -866,19 +866,25 @@ def get_gpo_list(dc_hostname, creds, lp, username):
# (S)ite
if gpo_list_machine:
- site_dn = site_dn_for_machine(samdb, dc_hostname, lp, creds, username)
-
try:
- log.debug("get_gpo_list: query SITE: [%s] for GPOs" % site_dn)
- gp_link = get_gpo_link(samdb, site_dn)
- except ldb.LdbError as e:
- (enum, estr) = e.args
- log.debug(estr)
- else:
- add_gplink_to_gpo_list(samdb, gpo_list, forced_gpo_list,
- site_dn, gp_link,
- gpo.GP_LINK_SITE,
- add_only_forced_gpos, token)
+ site_dn = site_dn_for_machine(samdb, dc_hostname, lp, creds, username)
+
+ try:
+ log.debug("get_gpo_list: query SITE: [%s] for GPOs" % site_dn)
+ gp_link = get_gpo_link(samdb, site_dn)
+ except ldb.LdbError as e:
+ (enum, estr) = e.args
+ log.debug(estr)
+ else:
+ add_gplink_to_gpo_list(samdb, gpo_list, forced_gpo_list,
+ site_dn, gp_link,
+ gpo.GP_LINK_SITE,
+ add_only_forced_gpos, token)
+ except ldb.LdbError:
+ # [MS-GPOL] 3.2.5.1.4 Site Search: If the method returns
+ # ERROR_NO_SITENAME, the remainder of this message MUST be skipped
+ # and the protocol sequence MUST continue at GPO Search
+ pass
# (L)ocal
gpo_list.insert(0, gpo.GROUP_POLICY_OBJECT("Local Policy",
--
2.53.0
From d0d1a890d6f2466691fa4ee663232ee0bd1c3776 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Mon, 22 Jan 2024 14:14:30 +0100
Subject: [PATCH 015/122] python:gp: Avoid path check for cepces-submit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
find_cepces_submit() uses which(), which returns None if not found.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15559
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Pavel Filipenský <pfilipensky@samba.org>
(cherry picked from commit 6a9630eff624643fd725219775784e68d967d04c)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 559c903e1a2..7325d5132cf 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -185,7 +185,7 @@ def find_cepces_submit():
def get_supported_templates(server):
cepces_submit = find_cepces_submit()
- if not cepces_submit or not os.path.exists(cepces_submit):
+ if not cepces_submit:
log.error('Failed to find cepces-submit')
return []
@@ -301,7 +301,7 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
# Setup Certificate Auto Enrollment
getcert = which('getcert')
cepces_submit = find_cepces_submit()
- if getcert is not None and os.path.exists(cepces_submit):
+ if getcert is not None and cepces_submit is not None:
p = Popen([getcert, 'add-ca', '-c', ca['name'], '-e',
'%s --server=%s --auth=%s' % (cepces_submit,
ca['hostname'], auth)],
--
2.53.0
From 7f6c9a4945635c6eb8ada2255bd0febbf0f4e540 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Mon, 22 Jan 2024 14:07:47 +0100
Subject: [PATCH 016/122] python:gp: Improve logging for certificate enrollment
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15559
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Pavel Filipenský <pfilipensky@samba.org>
(cherry picked from commit 6d5507e05050690cd4c56f3f97f5fb7de0338b87)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 7325d5132cf..a25a9678587 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -274,6 +274,9 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
"""Install the root certificate chain."""
data = dict({'files': [], 'templates': []}, **ca)
url = 'http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?' % ca['hostname']
+
+ log.info("Try to get root or server certificates")
+
root_certs = getca(ca, url, trust_dir)
data['files'].extend(root_certs)
global_trust_dir = find_global_trust_dir()
@@ -283,6 +286,7 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
try:
os.symlink(src, dst)
data['files'].append(dst)
+ log.info("Created symlink: %s -> %s" % (src, dst))
except PermissionError:
log.warn('Failed to symlink root certificate to the'
' admin trust anchors')
@@ -295,9 +299,14 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
# already exists. Ignore the FileExistsError. Preserve the
# existing symlink in the unapply data.
data['files'].append(dst)
+
update = update_ca_command()
+ log.info("Running %s" % (update))
if update is not None:
- Popen([update]).wait()
+ ret = Popen([update]).wait()
+ if ret != 0:
+ log.error('Failed to run %s' % (update))
+
# Setup Certificate Auto Enrollment
getcert = which('getcert')
cepces_submit = find_cepces_submit()
--
2.53.0
From 5321d5b5bd24d7659743576f2e12a7dc0a93a828 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Mon, 22 Jan 2024 15:04:36 +0100
Subject: [PATCH 017/122] python:gp: Do not print an error, if CA already
exists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We will get an exit status for duplicate in future:
https://www.pagure.io/certmonger/issue/269
We can't really fix that right now, as older version of certmonger
don't support the `-v` option.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15559
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Pavel Filipenský <pfilipensky@samba.org>
(cherry picked from commit 728757cd1ff0465967fcbda100254c9312e87c93)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index a25a9678587..0b23cd688db 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -318,8 +318,12 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
out, err = p.communicate()
log.debug(out.decode())
if p.returncode != 0:
- data = { 'Error': err.decode(), 'CA': ca['name'] }
- log.error('Failed to add Certificate Authority', data)
+ if p.returncode == 2:
+ log.info('The CA [%s] already exists' % ca['name'])
+ else:
+ data = {'Error': err.decode(), 'CA': ca['name']}
+ log.error('Failed to add Certificate Authority', data)
+
supported_templates = get_supported_templates(ca['hostname'])
for template in supported_templates:
attrs = fetch_template_attrs(ldb, template)
--
2.53.0
From 6a7a8a4090b8cdb8e71f4ad590260ceeda253ce2 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Mon, 22 Jan 2024 15:05:02 +0100
Subject: [PATCH 018/122] python:gp: Do not print an error if template already
exists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We will get an exit status for duplicate in future:
https://www.pagure.io/certmonger/issue/269
We can't really fix that right now, as older version of certmonger
don't support the `-v` option.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15559
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Pavel Filipenský <pfilipensky@samba.org>
(cherry picked from commit 98dc44286ea102ef7701ccdea26bbde32b523a7e)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 0b23cd688db..db681cb6f69 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -338,8 +338,12 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
out, err = p.communicate()
log.debug(out.decode())
if p.returncode != 0:
- data = { 'Error': err.decode(), 'Certificate': nickname }
- log.error('Failed to request certificate', data)
+ if p.returncode == 2:
+ log.info('The template [%s] already exists' % (nickname))
+ else:
+ data = {'Error': err.decode(), 'Certificate': nickname}
+ log.error('Failed to request certificate', data)
+
data['files'].extend([keyfile, certfile])
data['templates'].append(nickname)
if update is not None:
--
2.53.0
From 43dc3d5d833bc1db885eb45402decd3225a7c946 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Mon, 22 Jan 2024 15:05:24 +0100
Subject: [PATCH 019/122] python:gp: Log an error if update fails
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15559
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Pavel Filipenský <pfilipensky@samba.org>
(cherry picked from commit 367756b85a9ac8daaac2326392bcd1373feed3b7)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index db681cb6f69..c8ad2039dc6 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -347,7 +347,9 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
data['files'].extend([keyfile, certfile])
data['templates'].append(nickname)
if update is not None:
- Popen([update]).wait()
+ ret = Popen([update]).wait()
+ if ret != 0:
+ log.error('Failed to run %s' % (update))
else:
log.warn('certmonger and cepces must be installed for ' +
'certificate auto enrollment to work')
--
2.53.0
From d8276d6a098d10f405b8f24c4dfb82af4496607c Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Mon, 22 Jan 2024 15:46:24 +0100
Subject: [PATCH 020/122] python:gp: Improve working of log messages to avoid
confusion
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We should not use the word "Failed". We are totally fine if we can't
connect to NDES in the meantime. This logs:
Try to get root or server certificates.
Unable to install root certificates (requires NDES).
Installing the server certificate only.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15559
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: David Mulder <dmulder@samba.org>
Reviewed-by: Pavel Filipenský <pfilipensky@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Mon Jan 29 10:37:29 UTC 2024 on atb-devel-224
(cherry picked from commit 1f823424418e814d9dc0785658e2a7d92643dab2)
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index c8ad2039dc6..2b7f7d22c2b 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -209,12 +209,10 @@ def getca(ca, url, trust_dir):
r = requests.get(url=url, params={'operation': 'GetCACert',
'message': 'CAIdentifier'})
except requests.exceptions.ConnectionError:
- log.warn('Failed to establish a new connection')
+ log.warn('Could not connect to Network Device Enrollment Service.')
r = None
if r is None or r.content == b'' or r.headers['Content-Type'] == 'text/html':
- log.warn('Failed to fetch the root certificate chain.')
- log.warn('The Network Device Enrollment Service is either not' +
- ' installed or not configured.')
+ log.warn('Unable to fetch root certificates (requires NDES).')
if 'cACertificate' in ca:
log.warn('Installing the server certificate only.')
der_certificate = base64.b64decode(ca['cACertificate'])
--
2.53.0
From 585357bf0d8889747a2769c2451ee34766087d95 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Mon, 29 Jan 2024 17:46:30 +0100
Subject: [PATCH 021/122] python:gp: Fix logging with gp
This allows enable INFO level logging with: `samba-gpupdate -d3`
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15558
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Tue Jan 30 07:18:05 UTC 2024 on atb-devel-224
(cherry picked from commit 145194071b10c4c1857f28fe79c57fd63ffab889)
---
python/samba/gp/util/logging.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/python/samba/gp/util/logging.py b/python/samba/gp/util/logging.py
index a74a8707d50..c3de32825db 100644
--- a/python/samba/gp/util/logging.py
+++ b/python/samba/gp/util/logging.py
@@ -24,9 +24,10 @@ import gettext
import random
import sys
-logger = logging.getLogger()
+logger = logging.getLogger("gp")
+
+
def logger_init(name, log_level):
- logger = logging.getLogger(name)
logger.addHandler(logging.StreamHandler(sys.stdout))
logger.setLevel(logging.CRITICAL)
if log_level == 1:
--
2.53.0
From 14ceb0b5f2f954bbabdaf78b8185fc515e3c8294 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Wed, 13 Mar 2024 13:55:41 +0100
Subject: [PATCH 022/122] docs-xml: Add parameter all_groupmem to idmap_ad
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15605
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(cherry picked from commit a485d9de2f2d6a9815dcac6addb988a8987e111c)
---
docs-xml/manpages/idmap_ad.8.xml | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/docs-xml/manpages/idmap_ad.8.xml b/docs-xml/manpages/idmap_ad.8.xml
index b364bbfa231..de6d36afe95 100644
--- a/docs-xml/manpages/idmap_ad.8.xml
+++ b/docs-xml/manpages/idmap_ad.8.xml
@@ -100,6 +100,16 @@
</listitem>
</varlistentry>
<varlistentry>
+ <term>all_groupmem = yes/no</term>
+ <listitem><para>
+ If set to <parameter>yes</parameter> winbind will retrieve all
+ group members for getgrnam(3), getgrgid(3) and getgrent(3) calls,
+ including those with missing uidNumber.
+ </para>
+ <para>Default: no</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
<term>deny ous</term>
<listitem><para>This parameter is a list of OUs from
which objects will not be mapped via the ad idmap
--
2.53.0
From ac4184c8c3220263cb6f1a46a012533ed1c4e047 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Tue, 12 Mar 2024 13:20:24 +0100
Subject: [PATCH 023/122] s3:winbindd: Improve performance of lookup_groupmem()
in idmap_ad
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The LDAP query of lookup_groupmem() returns all group members from AD
even those with missing uidNumber. Such group members are useless in
UNIX environment for idmap_ad backend since there is no uid mapping.
'test_user' is member of group "Domanin Users" with 200K members,
only 20K members have set uidNumber.
Without this fix:
$ time id test_user
real 1m5.946s
user 0m0.019s
sys 0m0.012s
With this fix:
$ time id test_user
real 0m3.544s
user 0m0.004s
sys 0m0.007s
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15605
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(cherry picked from commit 5d475d26a3d545f04791a04e85a06b8b192e3fcf)
---
source3/winbindd/winbindd_ads.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/source3/winbindd/winbindd_ads.c b/source3/winbindd/winbindd_ads.c
index d7a665abbc6..e625aa6473f 100644
--- a/source3/winbindd/winbindd_ads.c
+++ b/source3/winbindd/winbindd_ads.c
@@ -1037,7 +1037,7 @@ static NTSTATUS lookup_useraliases(struct winbindd_domain *domain,
}
static NTSTATUS add_primary_group_members(
- ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, uint32_t rid,
+ ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, uint32_t rid, const char *domname,
char ***all_members, size_t *num_all_members)
{
char *filter;
@@ -1049,10 +1049,13 @@ static NTSTATUS add_primary_group_members(
char **members;
size_t num_members;
ads_control args;
+ bool all_groupmem = idmap_config_bool(domname, "all_groupmem", false);
filter = talloc_asprintf(
- mem_ctx, "(&(objectCategory=user)(primaryGroupID=%u))",
- (unsigned)rid);
+ mem_ctx,
+ "(&(objectCategory=user)(primaryGroupID=%u)%s)",
+ (unsigned)rid,
+ all_groupmem ? "" : "(uidNumber=*)(!(uidNumber=0))");
if (filter == NULL) {
goto done;
}
@@ -1204,7 +1207,7 @@ static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
DEBUG(10, ("ads lookup_groupmem: got %d sids via extended dn call\n", (int)num_members));
- status = add_primary_group_members(ads, mem_ctx, rid,
+ status = add_primary_group_members(ads, mem_ctx, rid, domain->name,
&members, &num_members);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(10, ("%s: add_primary_group_members failed: %s\n",
--
2.53.0
From d0e2002efcc37055b35c351a6b936e6ab89fad32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Mon, 25 Mar 2024 22:38:18 +0100
Subject: [PATCH 024/122] selftest: Add "winbind expand groups = 1" to
setup_ad_member_idmap_ad
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15605
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(backported from commit 2dab3a331b5511b4f2253f2b3b4513db7e52ea9a)
---
selftest/target/Samba3.pm | 1 +
1 file changed, 1 insertion(+)
diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index 44ac4a5901a..606c65f8ab1 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -1412,6 +1412,7 @@ sub setup_ad_member_idmap_ad
idmap config $dcvars->{TRUST_DOMAIN} : backend = ad
idmap config $dcvars->{TRUST_DOMAIN} : range = 2000000-2999999
gensec_gssapi:requested_life_time = 5
+ winbind expand groups = 1
";
my $ret = $self->provision(
--
2.53.0
From 9625b6aed981aa4e70fe11d9d1acdb54db7591a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Thu, 14 Mar 2024 15:24:21 +0100
Subject: [PATCH 025/122] tests: Add a test for "all_groups=no" to
test_idmap_ad.sh
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15605
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
Autobuild-User(master): Pavel Filipensky <pfilipensky@samba.org>
Autobuild-Date(master): Tue Apr 2 13:25:39 UTC 2024 on atb-devel-224
(cherry picked from commit f8b72aa1f72881989990fabc9f4888968bb81967)
---
nsswitch/tests/test_idmap_ad.sh | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/nsswitch/tests/test_idmap_ad.sh b/nsswitch/tests/test_idmap_ad.sh
index 7ae112ada71..1d4bd395ba9 100755
--- a/nsswitch/tests/test_idmap_ad.sh
+++ b/nsswitch/tests/test_idmap_ad.sh
@@ -94,6 +94,14 @@ gidNumber: 2000001
unixHomeDirectory: /home/forbidden
loginShell: /bin/tcsh
gecos: User in forbidden OU
+
+dn: CN=no_posix_id,CN=Users,$BASE_DN
+changetype: add
+objectClass: user
+samaccountName: no_posix_id
+unixHomeDirectory: /home/no_posix_id
+loginShell: /bin/sh
+gecos: User without uidNumber and gidNumber
EOF
#
@@ -171,6 +179,17 @@ then
failed=$(($failed + 1))
fi
+#
+# Test 6: Make sure that with the default "all_groups=no"
+# the group "domain users" will not show user "no_posix_id"
+# but will show "SAMBA2008R2/administrator"
+#
+
+dom_users="$DOMAIN/domain users" # Extra step to make sure that all is one word
+out="$($wbinfo --group-info "$dom_users")"
+testit_grep_count "no_posix_id1" "no_posix_id" 0 echo "$out" || failed=$(expr $failed + 1)
+testit_grep "no_posix_id2" "SAMBA2008R2/administrator" echo "$out" || failed=$(expr $failed + 1)
+
#
# Trusted domain test 1: Test uid of Administrator, should be 2500000
#
@@ -241,6 +260,9 @@ gidNumber: 2000002
dn: cn=forbidden,ou=sub,$BASE_DN
changetype: delete
+dn: CN=no_posix_id,CN=Users,$BASE_DN
+changetype: delete
+
dn: ou=sub,$BASE_DN
changetype: delete
EOF
--
2.53.0
From e5890e63c35a4a5af29ae16e6dd734c4a3a304cc Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Tue, 28 May 2024 13:51:53 +0200
Subject: [PATCH 026/122] s3:libads: Allow get_kdc_ip_string() to lookup the
KDCs IP
Remove the requirement to provide an IP address. We should look up the
IP of the KDC and use it for the specified realm/workgroup.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15653
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
(cherry picked from commit 28aa0b815baf4668e3df01d52597c40fd430e2fb)
---
source3/libads/kerberos.c | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/source3/libads/kerberos.c b/source3/libads/kerberos.c
index 50f4a6de3c6..ddf97c11973 100644
--- a/source3/libads/kerberos.c
+++ b/source3/libads/kerberos.c
@@ -437,23 +437,23 @@ static char *get_kdc_ip_string(char *mem_ctx,
char *kdc_str = NULL;
char *canon_sockaddr = NULL;
- SMB_ASSERT(pss != NULL);
-
- canon_sockaddr = print_canonical_sockaddr_with_port(frame, pss);
- if (canon_sockaddr == NULL) {
- goto out;
- }
+ if (pss != NULL) {
+ canon_sockaddr = print_canonical_sockaddr_with_port(frame, pss);
+ if (canon_sockaddr == NULL) {
+ goto out;
+ }
- kdc_str = talloc_asprintf(frame,
- "\t\tkdc = %s\n",
- canon_sockaddr);
- if (kdc_str == NULL) {
- goto out;
- }
+ kdc_str = talloc_asprintf(frame,
+ "\t\tkdc = %s\n",
+ canon_sockaddr);
+ if (kdc_str == NULL) {
+ goto out;
+ }
- ok = sockaddr_storage_to_samba_sockaddr(&sa, pss);
- if (!ok) {
- goto out;
+ ok = sockaddr_storage_to_samba_sockaddr(&sa, pss);
+ if (!ok) {
+ goto out;
+ }
}
/*
--
2.53.0
From 96a1ecd8db249fa03db60259cf76fdef9c1bd749 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Tue, 28 May 2024 13:53:51 +0200
Subject: [PATCH 027/122] s3:libads: Do not fail if we don't get an IP passed
down
The IP should be optional and we should look it up if not provided.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15653
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
(cherry picked from commit 9dcc52d2a57314ec9ddaae82b3c49da051d1f1d2)
---
source3/libads/kerberos.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/source3/libads/kerberos.c b/source3/libads/kerberos.c
index ddf97c11973..f74d8eb567c 100644
--- a/source3/libads/kerberos.c
+++ b/source3/libads/kerberos.c
@@ -704,7 +704,7 @@ bool create_local_private_krb5_conf_for_domain(const char *realm,
return false;
}
- if (domain == NULL || pss == NULL) {
+ if (domain == NULL) {
return false;
}
--
2.53.0
From 4934642b7a7d92c6d81ba25ef6e4b66e3805f708 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Tue, 28 May 2024 13:54:24 +0200
Subject: [PATCH 028/122] s3:winbind: Fix idmap_ad creating an invalid local
krb5.conf
In case of a trusted domain, we are providing the realm of the primary
trust but specify the KDC IP of the trusted domain. This leads to
Kerberos ticket requests to the trusted domain KDC which doesn't know
about the machine account. However we need a ticket from our primary
trust KDC.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15653
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
(backported from commit 8989aa47b7493e6b7978c2efc4a40c781e9a2aee)
---
source3/winbindd/idmap_ad.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/source3/winbindd/idmap_ad.c b/source3/winbindd/idmap_ad.c
index 5c9fe07db95..b8002825161 100644
--- a/source3/winbindd/idmap_ad.c
+++ b/source3/winbindd/idmap_ad.c
@@ -320,7 +320,10 @@ static NTSTATUS idmap_ad_get_tldap_ctx(TALLOC_CTX *mem_ctx,
struct tldap_context **pld)
{
struct netr_DsRGetDCNameInfo *dcinfo;
- struct sockaddr_storage dcaddr;
+ struct sockaddr_storage dcaddr = {
+ .ss_family = AF_UNSPEC,
+ };
+ struct sockaddr_storage *pdcaddr = NULL;
struct cli_credentials *creds;
struct loadparm_context *lp_ctx;
struct tldap_context *ld;
@@ -362,9 +365,13 @@ static NTSTATUS idmap_ad_get_tldap_ctx(TALLOC_CTX *mem_ctx,
* create_local_private_krb5_conf_for_domain() can deal with
* sitename==NULL
*/
+ if (strequal(domname, lp_realm()) || strequal(domname, lp_workgroup()))
+ {
+ pdcaddr = &dcaddr;
+ }
ok = create_local_private_krb5_conf_for_domain(
- lp_realm(), lp_workgroup(), sitename, &dcaddr);
+ lp_realm(), lp_workgroup(), sitename, pdcaddr);
TALLOC_FREE(sitename);
if (!ok) {
DBG_DEBUG("Could not create private krb5.conf\n");
--
2.53.0
From cccc902c64c93db317bf4707d0af5e56b2887286 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Mon, 22 Jul 2024 12:26:55 +0200
Subject: [PATCH 029/122] s3:notifyd: Use a watcher per db record
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This fixes a O(n²) performance regression in notifyd. The problem was
that we had a watcher per notify instance. This changes the code to have
a watcher per notify db entry.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14430
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Tue Oct 1 14:22:43 UTC 2024 on atb-devel-224
(cherry picked from commit af011b987a4ad0d3753d83cc0b8d97ad64ba874a)
---
source3/smbd/notifyd/notifyd.c | 214 ++++++++++++++++++-------
source3/smbd/notifyd/notifyd_db.c | 5 +-
source3/smbd/notifyd/notifyd_entry.c | 51 ++++--
source3/smbd/notifyd/notifyd_private.h | 46 ++++--
4 files changed, 228 insertions(+), 88 deletions(-)
diff --git a/source3/smbd/notifyd/notifyd.c b/source3/smbd/notifyd/notifyd.c
index ca303bd4d51..b368b8390fa 100644
--- a/source3/smbd/notifyd/notifyd.c
+++ b/source3/smbd/notifyd/notifyd.c
@@ -337,6 +337,7 @@ static bool notifyd_apply_rec_change(
struct messaging_context *msg_ctx)
{
struct db_record *rec = NULL;
+ struct notifyd_watcher watcher = {};
struct notifyd_instance *instances = NULL;
size_t num_instances;
size_t i;
@@ -344,6 +345,7 @@ static bool notifyd_apply_rec_change(
TDB_DATA value;
NTSTATUS status;
bool ok = false;
+ bool new_watcher = false;
if (pathlen == 0) {
DBG_WARNING("pathlen==0\n");
@@ -374,8 +376,12 @@ static bool notifyd_apply_rec_change(
value = dbwrap_record_get_value(rec);
if (value.dsize != 0) {
- if (!notifyd_parse_entry(value.dptr, value.dsize, NULL,
- &num_instances)) {
+ ok = notifyd_parse_entry(value.dptr,
+ value.dsize,
+ &watcher,
+ NULL,
+ &num_instances);
+ if (!ok) {
goto fail;
}
}
@@ -390,8 +396,22 @@ static bool notifyd_apply_rec_change(
goto fail;
}
- if (value.dsize != 0) {
- memcpy(instances, value.dptr, value.dsize);
+ if (num_instances > 0) {
+ struct notifyd_instance *tmp = NULL;
+ size_t num_tmp = 0;
+
+ ok = notifyd_parse_entry(value.dptr,
+ value.dsize,
+ NULL,
+ &tmp,
+ &num_tmp);
+ if (!ok) {
+ goto fail;
+ }
+
+ memcpy(instances,
+ tmp,
+ sizeof(struct notifyd_instance) * num_tmp);
}
for (i=0; i<num_instances; i++) {
@@ -414,41 +434,106 @@ static bool notifyd_apply_rec_change(
*instance = (struct notifyd_instance) {
.client = *client,
.instance = *chg,
- .internal_filter = chg->filter,
- .internal_subdir_filter = chg->subdir_filter
};
num_instances += 1;
}
- if ((instance->instance.filter != 0) ||
- (instance->instance.subdir_filter != 0)) {
- int ret;
+ /*
+ * Calculate an intersection of the instances filters for the watcher.
+ */
+ if (instance->instance.filter > 0) {
+ uint32_t filter = instance->instance.filter;
+
+ if ((watcher.filter & filter) != filter) {
+ watcher.filter |= filter;
+
+ new_watcher = true;
+ }
+ }
+
+ /*
+ * Calculate an intersection of the instances subdir_filters for the
+ * watcher.
+ */
+ if (instance->instance.subdir_filter > 0) {
+ uint32_t subdir_filter = instance->instance.subdir_filter;
- TALLOC_FREE(instance->sys_watch);
+ if ((watcher.subdir_filter & subdir_filter) != subdir_filter) {
+ watcher.subdir_filter |= subdir_filter;
- ret = sys_notify_watch(entries, sys_notify_ctx, path,
- &instance->internal_filter,
- &instance->internal_subdir_filter,
- notifyd_sys_callback, msg_ctx,
- &instance->sys_watch);
- if (ret != 0) {
- DBG_WARNING("sys_notify_watch for [%s] returned %s\n",
- path, strerror(errno));
+ new_watcher = true;
}
}
if ((instance->instance.filter == 0) &&
(instance->instance.subdir_filter == 0)) {
+ uint32_t tmp_filter = 0;
+ uint32_t tmp_subdir_filter = 0;
+
/* This is a delete request */
- TALLOC_FREE(instance->sys_watch);
*instance = instances[num_instances-1];
num_instances -= 1;
+
+ for (i = 0; i < num_instances; i++) {
+ struct notifyd_instance *tmp = &instances[i];
+
+ tmp_filter |= tmp->instance.filter;
+ tmp_subdir_filter |= tmp->instance.subdir_filter;
+ }
+
+ /*
+ * If the filter has changed, register a new watcher with the
+ * changed filter.
+ */
+ if (watcher.filter != tmp_filter ||
+ watcher.subdir_filter != tmp_subdir_filter)
+ {
+ watcher.filter = tmp_filter;
+ watcher.subdir_filter = tmp_subdir_filter;
+
+ new_watcher = true;
+ }
+ }
+
+ if (new_watcher) {
+ /*
+ * In case we removed all notify instances, we want to remove
+ * the watcher. We won't register a new one, if no filters are
+ * set anymore.
+ */
+
+ TALLOC_FREE(watcher.sys_watch);
+
+ watcher.sys_filter = watcher.filter;
+ watcher.sys_subdir_filter = watcher.subdir_filter;
+
+ /*
+ * Only register a watcher if we have filter.
+ */
+ if (watcher.filter != 0 || watcher.subdir_filter != 0) {
+ int ret = sys_notify_watch(entries,
+ sys_notify_ctx,
+ path,
+ &watcher.sys_filter,
+ &watcher.sys_subdir_filter,
+ notifyd_sys_callback,
+ msg_ctx,
+ &watcher.sys_watch);
+ if (ret != 0) {
+ DBG_WARNING("sys_notify_watch for [%s] "
+ "returned %s\n",
+ path,
+ strerror(errno));
+ }
+ }
}
DBG_DEBUG("%s has %zu instances\n", path, num_instances);
if (num_instances == 0) {
+ TALLOC_FREE(watcher.sys_watch);
+
status = dbwrap_record_delete(rec);
if (!NT_STATUS_IS_OK(status)) {
DBG_WARNING("dbwrap_record_delete returned %s\n",
@@ -456,13 +541,21 @@ static bool notifyd_apply_rec_change(
goto fail;
}
} else {
- value = make_tdb_data(
- (uint8_t *)instances,
- sizeof(struct notifyd_instance) * num_instances);
+ struct TDB_DATA iov[2] = {
+ {
+ .dptr = (uint8_t *)&watcher,
+ .dsize = sizeof(struct notifyd_watcher),
+ },
+ {
+ .dptr = (uint8_t *)instances,
+ .dsize = sizeof(struct notifyd_instance) *
+ num_instances,
+ },
+ };
- status = dbwrap_record_store(rec, value, 0);
+ status = dbwrap_record_storev(rec, iov, ARRAY_SIZE(iov), 0);
if (!NT_STATUS_IS_OK(status)) {
- DBG_WARNING("dbwrap_record_store returned %s\n",
+ DBG_WARNING("dbwrap_record_storev returned %s\n",
nt_errstr(status));
goto fail;
}
@@ -706,12 +799,18 @@ static void notifyd_trigger_parser(TDB_DATA key, TDB_DATA data,
.when = tstate->msg->when };
struct iovec iov[2];
size_t path_len = key.dsize;
+ struct notifyd_watcher watcher = {};
struct notifyd_instance *instances = NULL;
size_t num_instances = 0;
size_t i;
+ bool ok;
- if (!notifyd_parse_entry(data.dptr, data.dsize, &instances,
- &num_instances)) {
+ ok = notifyd_parse_entry(data.dptr,
+ data.dsize,
+ &watcher,
+ &instances,
+ &num_instances);
+ if (!ok) {
DBG_DEBUG("Could not parse notifyd_entry\n");
return;
}
@@ -734,9 +833,11 @@ static void notifyd_trigger_parser(TDB_DATA key, TDB_DATA data,
if (tstate->covered_by_sys_notify) {
if (tstate->recursive) {
- i_filter = instance->internal_subdir_filter;
+ i_filter = watcher.sys_subdir_filter &
+ instance->instance.subdir_filter;
} else {
- i_filter = instance->internal_filter;
+ i_filter = watcher.sys_filter &
+ instance->instance.filter;
}
} else {
if (tstate->recursive) {
@@ -1142,46 +1243,39 @@ static int notifyd_add_proxy_syswatches(struct db_record *rec,
struct db_context *db = dbwrap_record_get_db(rec);
TDB_DATA key = dbwrap_record_get_key(rec);
TDB_DATA value = dbwrap_record_get_value(rec);
- struct notifyd_instance *instances = NULL;
- size_t num_instances = 0;
- size_t i;
+ struct notifyd_watcher watcher = {};
char path[key.dsize+1];
bool ok;
+ int ret;
memcpy(path, key.dptr, key.dsize);
path[key.dsize] = '\0';
- ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
- &num_instances);
+ /* This is a remote database, we just need the watcher. */
+ ok = notifyd_parse_entry(value.dptr, value.dsize, &watcher, NULL, NULL);
if (!ok) {
DBG_WARNING("Could not parse notifyd entry for %s\n", path);
return 0;
}
- for (i=0; i<num_instances; i++) {
- struct notifyd_instance *instance = &instances[i];
- uint32_t filter = instance->instance.filter;
- uint32_t subdir_filter = instance->instance.subdir_filter;
- int ret;
+ watcher.sys_watch = NULL;
+ watcher.sys_filter = watcher.filter;
+ watcher.sys_subdir_filter = watcher.subdir_filter;
- /*
- * This is a remote database. Pointers that we were
- * given don't make sense locally. Initialize to NULL
- * in case sys_notify_watch fails.
- */
- instances[i].sys_watch = NULL;
-
- ret = state->sys_notify_watch(
- db, state->sys_notify_ctx, path,
- &filter, &subdir_filter,
- notifyd_sys_callback, state->msg_ctx,
- &instance->sys_watch);
- if (ret != 0) {
- DBG_WARNING("inotify_watch returned %s\n",
- strerror(errno));
- }
+ ret = state->sys_notify_watch(db,
+ state->sys_notify_ctx,
+ path,
+ &watcher.filter,
+ &watcher.subdir_filter,
+ notifyd_sys_callback,
+ state->msg_ctx,
+ &watcher.sys_watch);
+ if (ret != 0) {
+ DBG_WARNING("inotify_watch returned %s\n", strerror(errno));
}
+ memcpy(value.dptr, &watcher, sizeof(struct notifyd_watcher));
+
return 0;
}
@@ -1189,21 +1283,17 @@ static int notifyd_db_del_syswatches(struct db_record *rec, void *private_data)
{
TDB_DATA key = dbwrap_record_get_key(rec);
TDB_DATA value = dbwrap_record_get_value(rec);
- struct notifyd_instance *instances = NULL;
- size_t num_instances = 0;
- size_t i;
+ struct notifyd_watcher watcher = {};
bool ok;
- ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
- &num_instances);
+ ok = notifyd_parse_entry(value.dptr, value.dsize, &watcher, NULL, NULL);
if (!ok) {
DBG_WARNING("Could not parse notifyd entry for %.*s\n",
(int)key.dsize, (char *)key.dptr);
return 0;
}
- for (i=0; i<num_instances; i++) {
- TALLOC_FREE(instances[i].sys_watch);
- }
+ TALLOC_FREE(watcher.sys_watch);
+
return 0;
}
diff --git a/source3/smbd/notifyd/notifyd_db.c b/source3/smbd/notifyd/notifyd_db.c
index 18228619e9a..7dc3cd58081 100644
--- a/source3/smbd/notifyd/notifyd_db.c
+++ b/source3/smbd/notifyd/notifyd_db.c
@@ -40,7 +40,10 @@ static bool notifyd_parse_db_parser(TDB_DATA key, TDB_DATA value,
memcpy(path, key.dptr, key.dsize);
path[key.dsize] = 0;
- ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
+ ok = notifyd_parse_entry(value.dptr,
+ value.dsize,
+ NULL,
+ &instances,
&num_instances);
if (!ok) {
DBG_DEBUG("Could not parse entry for path %s\n", path);
diff --git a/source3/smbd/notifyd/notifyd_entry.c b/source3/smbd/notifyd/notifyd_entry.c
index 539010de03a..f3b0e908136 100644
--- a/source3/smbd/notifyd/notifyd_entry.c
+++ b/source3/smbd/notifyd/notifyd_entry.c
@@ -21,22 +21,51 @@
* Parse an entry in the notifyd_context->entries database
*/
-bool notifyd_parse_entry(
- uint8_t *buf,
- size_t buflen,
- struct notifyd_instance **instances,
- size_t *num_instances)
+/**
+ * @brief Parse a notifyd database entry.
+ *
+ * The memory we pass down needs to be aligned. If it isn't aligned we can run
+ * into obscure errors as we just point into the data buffer.
+ *
+ * @param data The data to parse
+ * @param data_len The length of the data to parse
+ * @param watcher A pointer to store the watcher data or NULL.
+ * @param instances A pointer to store the array of notify instances or NULL.
+ * @param pnum_instances The number of elements in the array. If you just want
+ * the number of elements pass NULL for the watcher and instances pointers.
+ *
+ * @return true on success, false if an error occurred.
+ */
+bool notifyd_parse_entry(uint8_t *data,
+ size_t data_len,
+ struct notifyd_watcher *watcher,
+ struct notifyd_instance **instances,
+ size_t *pnum_instances)
{
- if ((buflen % sizeof(struct notifyd_instance)) != 0) {
- DBG_WARNING("invalid buffer size: %zu\n", buflen);
+ size_t ilen;
+
+ if (data_len < sizeof(struct notifyd_watcher)) {
return false;
}
- if (instances != NULL) {
- *instances = (struct notifyd_instance *)buf;
+ if (watcher != NULL) {
+ *watcher = *((struct notifyd_watcher *)(uintptr_t)data);
}
- if (num_instances != NULL) {
- *num_instances = buflen / sizeof(struct notifyd_instance);
+
+ ilen = data_len - sizeof(struct notifyd_watcher);
+ if ((ilen % sizeof(struct notifyd_instance)) != 0) {
+ return false;
+ }
+
+ if (pnum_instances != NULL) {
+ *pnum_instances = ilen / sizeof(struct notifyd_instance);
}
+ if (instances != NULL) {
+ /* The (uintptr_t) cast removes a warning from -Wcast-align. */
+ *instances =
+ (struct notifyd_instance *)(uintptr_t)
+ (data + sizeof(struct notifyd_watcher));
+ }
+
return true;
}
diff --git a/source3/smbd/notifyd/notifyd_private.h b/source3/smbd/notifyd/notifyd_private.h
index 36c08f47c54..db8e6e1c005 100644
--- a/source3/smbd/notifyd/notifyd_private.h
+++ b/source3/smbd/notifyd/notifyd_private.h
@@ -20,30 +20,48 @@
#include "lib/util/server_id.h"
#include "notifyd.h"
+
/*
- * notifyd's representation of a notify instance
+ * Representation of a watcher for a path
+ *
+ * This will be stored in the db.
*/
-struct notifyd_instance {
- struct server_id client;
- struct notify_instance instance;
-
- void *sys_watch; /* inotify/fam/etc handle */
+struct notifyd_watcher {
+ /*
+ * This is an intersections of the filter the watcher is listening for.
+ */
+ uint32_t filter;
+ uint32_t subdir_filter;
/*
- * Filters after sys_watch took responsibility of some bits
+ * Those are inout variables passed to the sys_watcher. The sys_watcher
+ * will remove the bits it can't handle.
*/
- uint32_t internal_filter;
- uint32_t internal_subdir_filter;
+ uint32_t sys_filter;
+ uint32_t sys_subdir_filter;
+
+ /* The handle for inotify/fam etc. */
+ void *sys_watch;
+};
+
+/*
+ * Representation of a notifyd instance
+ *
+ * This will be stored in the db.
+ */
+struct notifyd_instance {
+ struct server_id client;
+ struct notify_instance instance;
};
/*
* Parse an entry in the notifyd_context->entries database
*/
-bool notifyd_parse_entry(
- uint8_t *buf,
- size_t buflen,
- struct notifyd_instance **instances,
- size_t *num_instances);
+bool notifyd_parse_entry(uint8_t *data,
+ size_t data_len,
+ struct notifyd_watcher *watcher,
+ struct notifyd_instance **instances,
+ size_t *num_instances);
#endif
--
2.53.0
From b04cb93ee52aac0ce7213d0581d69e852df52d4a Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Mon, 5 Feb 2024 15:03:48 +0100
Subject: [PATCH 030/122] smbd: simplify handling of failing fstat() after
unlinking file
close_remove_share_mode() already called vfs_stat_fsp(), so we can skip the
fstat() triggered in fd_close() by fsp->fsp_flags.fstat_before_close being true.
This avoids getting an EACCESS error when doing an fstat() on the removed file
which seems to happen with some FUSE filesystems.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15527
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
(cherry picked from commit 6e6324cff29089a636823786183222a73fe7cb28)
---
source3/smbd/close.c | 1 +
source3/smbd/open.c | 15 +--------------
2 files changed, 2 insertions(+), 14 deletions(-)
diff --git a/source3/smbd/close.c b/source3/smbd/close.c
index af5e78daa10..e16cb2d3485 100644
--- a/source3/smbd/close.c
+++ b/source3/smbd/close.c
@@ -603,6 +603,7 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
*/
fsp->fsp_flags.delete_on_close = false;
+ fsp->fsp_flags.fstat_before_close = false;
lck_state.reset_delete_on_close = true;
done:
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 3581c4b9173..93c12e00eb0 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -997,20 +997,7 @@ NTSTATUS fd_close(files_struct *fsp)
if (fsp->fsp_flags.fstat_before_close) {
status = vfs_stat_fsp(fsp);
if (!NT_STATUS_IS_OK(status)) {
- /*
- * If this is a stream and delete-on-close was set, the
- * backing object (an xattr from streams_xattr) might
- * already be deleted so fstat() fails with
- * NT_STATUS_NOT_FOUND. So if fsp refers to a stream we
- * ignore the error and only bail for normal files where
- * an fstat() should still work. NB. We cannot use
- * fsp_is_alternate_stream(fsp) for this as the base_fsp
- * has already been closed at this point and so the value
- * fsp_is_alternate_stream() checks for is already NULL.
- */
- if (fsp->fsp_name->stream_name == NULL) {
- return status;
- }
+ return status;
}
}
--
2.53.0
From 29f0c0fb2f1cb0cfc4c615d31e82048b46a2cb0d Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power@suse.com>
Date: Tue, 20 Feb 2024 09:26:29 +0000
Subject: [PATCH 031/122] s3/smbd: If we fail to close file_handle ensure we
should reset the fd
if fsp_flags.fstat_before_close == true then close_file_smb will call
vfs_stat which can fail. If it does fail then the fd associated
with the file handle will still be set (and we will hit an assert
is the file handle destructor) when calling file_free.
We need to set fd to -1 to avoid that. To achieve that we capture and
return the vfs_stat_fsp failure status while still processing the rest
of the fd_close logic.
[2024/02/20 09:23:48.454671, 0, pid=9744] ../../source3/smbd/smb2_close.c:226(smbd_smb2_close)
smbd_smb2_close: close_file[]: NT_STATUS_ACCESS_DENIED
[2024/02/20 09:23:48.454757, 0, pid=9744] ../../source3/smbd/fd_handle.c:40(fd_handle_destructor)
PANIC: assert failed at ../../source3/smbd/fd_handle.c(40): (fh->fd == -1) || (fh->fd == AT_FDCWD)
[2024/02/20 09:23:48.454781, 0, pid=9744] ../../lib/util/fault.c:178(smb_panic_log)
===============================================================
[2024/02/20 09:23:48.454804, 0, pid=9744] ../../lib/util/fault.c:185(smb_panic_log)
INTERNAL ERROR: assert failed: (fh->fd == -1) || (fh->fd == AT_FDCWD) in smbd (smbd[192.168.10) (client [192.168.100.15]) pid 9744 (4.21.0pre1-DEVELOPERBUILD)
[2024/02/20 09:23:48.454844, 0, pid=9744] ../../lib/util/fault.c:190(smb_panic_log)
If you are running a recent Samba version, and if you think this problem is not yet fixed in the latest versions, please consider reporting this bug, see https://wiki.samba.org/index.php/Bug_Reporting
[2024/02/20 09:23:48.454869, 0, pid=9744] ../../lib/util/fault.c:191(smb_panic_log)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15527
Signed-off-by: Noel Power <noel.power@suse.com>
Reviewed-by: Jeremy Allison <jra@samba.org>
Autobuild-User(master): Noel Power <npower@samba.org>
Autobuild-Date(master): Wed Mar 13 10:34:45 UTC 2024 on atb-devel-224
(cherry picked from commit 6ee3f809a54d7b833ff798e68a93ada00a215d4d)
---
source3/smbd/open.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 93c12e00eb0..74be444fef5 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -987,7 +987,7 @@ NTSTATUS fd_openat(const struct files_struct *dirfsp,
NTSTATUS fd_close(files_struct *fsp)
{
- NTSTATUS status;
+ NTSTATUS stat_status = NT_STATUS_OK;
int ret;
if (fsp == fsp->conn->cwd_fsp) {
@@ -995,10 +995,12 @@ NTSTATUS fd_close(files_struct *fsp)
}
if (fsp->fsp_flags.fstat_before_close) {
- status = vfs_stat_fsp(fsp);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
+ /*
+ * capture status, if failure
+ * continue close processing
+ * and return status
+ */
+ stat_status = vfs_stat_fsp(fsp);
}
if (fsp->dptr) {
@@ -1020,7 +1022,7 @@ NTSTATUS fd_close(files_struct *fsp)
if (ret == -1) {
return map_nt_error_from_unix(errno);
}
- return NT_STATUS_OK;
+ return stat_status;
}
/****************************************************************************
--
2.53.0
From ed138c4d679e8291de18162e1cac65cc9da33b4d Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra@samba.org>
Date: Wed, 15 Jan 2025 10:21:19 -0800
Subject: [PATCH 032/122] auth: Add missing talloc_free() in error code path.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15782
Signed-off-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
Autobuild-User(master): Günther Deschner <gd@samba.org>
Autobuild-Date(master): Thu Jan 16 14:32:39 UTC 2025 on atb-devel-224
(cherry picked from commit c514ce8dcadcbbf0d86f3038d2be0f9253a76b75)
---
auth/kerberos/kerberos_pac.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/auth/kerberos/kerberos_pac.c b/auth/kerberos/kerberos_pac.c
index b914075d85c..196654b36bd 100644
--- a/auth/kerberos/kerberos_pac.c
+++ b/auth/kerberos/kerberos_pac.c
@@ -351,6 +351,7 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
if (ret) {
DEBUG(5, ("PAC Decode: Failed to verify the service "
"signature: %s\n", error_message(ret)));
+ talloc_free(tmp_ctx);
return NT_STATUS_ACCESS_DENIED;
}
--
2.53.0
From f8a7d7a3e8c3be3c7742c874239766b34c25ef3e Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra@samba.org>
Date: Thu, 16 Jan 2025 16:12:31 -0800
Subject: [PATCH 033/122] auth: Cleanup exit code paths in
kerberos_decode_pac().
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
One more memory leak missed and now fixed. tmp_ctx
must be freed once the pac data is talloc_move'd.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15782
Signed-off-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
Reviewed-by: Christian Ambach <ambi@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
Autobuild-User(master): Günther Deschner <gd@samba.org>
Autobuild-Date(master): Fri Jan 17 12:01:47 UTC 2025 on atb-devel-224
(cherry picked from commit f9eb0b248da0689c82656f3e482161c45749afb6)
---
auth/kerberos/kerberos_pac.c | 88 ++++++++++++++++++------------------
1 file changed, 43 insertions(+), 45 deletions(-)
diff --git a/auth/kerberos/kerberos_pac.c b/auth/kerberos/kerberos_pac.c
index 196654b36bd..abb096bde1b 100644
--- a/auth/kerberos/kerberos_pac.c
+++ b/auth/kerberos/kerberos_pac.c
@@ -128,7 +128,7 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
time_t tgs_authtime,
struct PAC_DATA **pac_data_out)
{
- NTSTATUS status;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
enum ndr_err_code ndr_err;
krb5_error_code ret;
DATA_BLOB modified_pac_blob;
@@ -164,8 +164,8 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
kdc_sig_wipe = talloc(tmp_ctx, struct PAC_SIGNATURE_DATA);
srv_sig_wipe = talloc(tmp_ctx, struct PAC_SIGNATURE_DATA);
if (!pac_data_raw || !pac_data || !kdc_sig_wipe || !srv_sig_wipe) {
- talloc_free(tmp_ctx);
- return NT_STATUS_NO_MEMORY;
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
}
ndr_err = ndr_pull_struct_blob(&pac_data_blob, pac_data, pac_data,
@@ -174,15 +174,14 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
status = ndr_map_error2ntstatus(ndr_err);
DEBUG(0,("can't parse the PAC: %s\n",
nt_errstr(status)));
- talloc_free(tmp_ctx);
- return status;
+ goto out;
}
if (pac_data->num_buffers < 4) {
/* we need logon_ingo, service_key and kdc_key */
DEBUG(0,("less than 4 PAC buffers\n"));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
ndr_err = ndr_pull_struct_blob(
@@ -192,15 +191,14 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
status = ndr_map_error2ntstatus(ndr_err);
DEBUG(0,("can't parse the PAC: %s\n",
nt_errstr(status)));
- talloc_free(tmp_ctx);
- return status;
+ goto out;
}
if (pac_data_raw->num_buffers < 4) {
/* we need logon_ingo, service_key and kdc_key */
DEBUG(0,("less than 4 PAC buffers\n"));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
if (pac_data->num_buffers != pac_data_raw->num_buffers) {
@@ -208,8 +206,8 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
DEBUG(0, ("misparse! PAC_DATA has %d buffers while "
"PAC_DATA_RAW has %d\n", pac_data->num_buffers,
pac_data_raw->num_buffers));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
for (i=0; i < pac_data->num_buffers; i++) {
@@ -220,8 +218,8 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
DEBUG(0, ("misparse! PAC_DATA buffer %d has type "
"%d while PAC_DATA_RAW has %d\n", i,
data_buf->type, raw_buf->type));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
switch (data_buf->type) {
case PAC_TYPE_LOGON_INFO:
@@ -254,26 +252,26 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
if (!logon_info) {
DEBUG(0,("PAC no logon_info\n"));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
if (!logon_name) {
DEBUG(0,("PAC no logon_name\n"));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
if (!srv_sig_ptr || !srv_sig_blob) {
DEBUG(0,("PAC no srv_key\n"));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
if (!kdc_sig_ptr || !kdc_sig_blob) {
DEBUG(0,("PAC no kdc_key\n"));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
/* Find and zero out the signatures,
@@ -288,8 +286,7 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
status = ndr_map_error2ntstatus(ndr_err);
DEBUG(0,("can't parse the KDC signature: %s\n",
nt_errstr(status)));
- talloc_free(tmp_ctx);
- return status;
+ goto out;
}
ndr_err = ndr_pull_struct_blob(
@@ -299,8 +296,7 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
status = ndr_map_error2ntstatus(ndr_err);
DEBUG(0,("can't parse the SRV signature: %s\n",
nt_errstr(status)));
- talloc_free(tmp_ctx);
- return status;
+ goto out;
}
/* Now zero the decoded structure */
@@ -317,8 +313,7 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
status = ndr_map_error2ntstatus(ndr_err);
DEBUG(0,("can't repack the KDC signature: %s\n",
nt_errstr(status)));
- talloc_free(tmp_ctx);
- return status;
+ goto out;
}
ndr_err = ndr_push_struct_blob(
srv_sig_blob, pac_data_raw, srv_sig_wipe,
@@ -327,8 +322,7 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
status = ndr_map_error2ntstatus(ndr_err);
DEBUG(0,("can't repack the SRV signature: %s\n",
nt_errstr(status)));
- talloc_free(tmp_ctx);
- return status;
+ goto out;
}
/* push out the whole structure, but now with zero'ed signatures */
@@ -339,8 +333,7 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
status = ndr_map_error2ntstatus(ndr_err);
DEBUG(0,("can't repack the RAW PAC: %s\n",
nt_errstr(status)));
- talloc_free(tmp_ctx);
- return status;
+ goto out;
}
if (service_keyblock) {
@@ -351,8 +344,8 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
if (ret) {
DEBUG(5, ("PAC Decode: Failed to verify the service "
"signature: %s\n", error_message(ret)));
- talloc_free(tmp_ctx);
- return NT_STATUS_ACCESS_DENIED;
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
}
if (krbtgt_keyblock) {
@@ -362,8 +355,8 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
if (ret) {
DEBUG(1, ("PAC Decode: Failed to verify the KDC signature: %s\n",
smb_get_krb5_error_message(context, ret, tmp_ctx)));
- talloc_free(tmp_ctx);
- return NT_STATUS_ACCESS_DENIED;
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
}
}
}
@@ -379,8 +372,8 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
nt_time_string(tmp_ctx, logon_name->logon_time)));
DEBUG(2, ("PAC Decode: Ticket: %s\n",
nt_time_string(tmp_ctx, tgs_authtime_nttime)));
- talloc_free(tmp_ctx);
- return NT_STATUS_ACCESS_DENIED;
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
}
}
@@ -392,8 +385,8 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
if (ret) {
DEBUG(2, ("Could not unparse name from ticket to match with name from PAC: [%s]:%s\n",
logon_name->account_name, error_message(ret)));
- talloc_free(tmp_ctx);
- return NT_STATUS_INVALID_PARAMETER;
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
}
bool_ret = strcmp(client_principal_string, logon_name->account_name) == 0;
@@ -404,8 +397,8 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
logon_name->account_name,
client_principal_string));
SAFE_FREE(client_principal_string);
- talloc_free(tmp_ctx);
- return NT_STATUS_ACCESS_DENIED;
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
}
SAFE_FREE(client_principal_string);
@@ -426,10 +419,15 @@ NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
}
if (pac_data_out) {
- *pac_data_out = talloc_steal(mem_ctx, pac_data);
+ *pac_data_out = talloc_move(mem_ctx, &pac_data);
}
- return NT_STATUS_OK;
+ status = NT_STATUS_OK;
+
+ out:
+
+ TALLOC_FREE(tmp_ctx);
+ return status;
}
NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx,
--
2.53.0
From 9fd06d5c331f5babaf417cc7339d12854a79fe4b Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 15 Feb 2024 17:29:46 +0100
Subject: [PATCH 034/122] s3:libsmb/dsgetdcname: use
NETLOGON_NT_VERSION_AVOID_NT4EMUL
In 2024 we always want an active directory response...
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15620
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
(cherry picked from commit 2b66663c75cdb3bc1b6bc5b1736dd9d35b094b42)
---
source3/libsmb/dsgetdcname.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/source3/libsmb/dsgetdcname.c b/source3/libsmb/dsgetdcname.c
index 280ccd585b0..6fcaa26810c 100644
--- a/source3/libsmb/dsgetdcname.c
+++ b/source3/libsmb/dsgetdcname.c
@@ -930,6 +930,11 @@ static NTSTATUS process_dc_netbios(TALLOC_CTX *mem_ctx,
name_type = NBT_NAME_PDC;
}
+ /*
+ * It's 2024 we always want an AD style response!
+ */
+ nt_version |= NETLOGON_NT_VERSION_AVOID_NT4EMUL;
+
nt_version |= map_ds_flags_to_nt_version(flags);
snprintf(my_acct_name,
--
2.53.0
From 58e28d056f2df0906ee77ccfb9b56e8a764b38b4 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Tue, 7 May 2024 14:53:24 +0000
Subject: [PATCH 035/122] s3:libsmb: allow store_cldap_reply() to work with a
ipv6 response
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15642
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Autobuild-User(master): Andrew Bartlett <abartlet@samba.org>
Autobuild-Date(master): Fri May 10 01:35:18 UTC 2024 on atb-devel-224
(cherry picked from commit 712ffbffc03c7dcd551c1e22815ebe7c0b9b45d2)
---
source3/libsmb/dsgetdcname.c | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/source3/libsmb/dsgetdcname.c b/source3/libsmb/dsgetdcname.c
index 6fcaa26810c..da173e7bbb0 100644
--- a/source3/libsmb/dsgetdcname.c
+++ b/source3/libsmb/dsgetdcname.c
@@ -196,7 +196,29 @@ static NTSTATUS store_cldap_reply(TALLOC_CTX *mem_ctx,
/* FIXME */
r->sockaddr_size = 0x10; /* the w32 winsock addr size */
r->sockaddr.sockaddr_family = 2; /* AF_INET */
- r->sockaddr.pdc_ip = talloc_strdup(mem_ctx, addr);
+ if (is_ipaddress_v4(addr)) {
+ r->sockaddr.pdc_ip = talloc_strdup(mem_ctx, addr);
+ if (r->sockaddr.pdc_ip == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ /*
+ * ndr_push_NETLOGON_SAM_LOGON_RESPONSE_EX will
+ * fail with an ipv6 address.
+ *
+ * This matches windows behaviour in the CLDAP
+ * response when NETLOGON_NT_VERSION_5EX_WITH_IP
+ * is used.
+ *
+ * Windows returns the ipv4 address of the ipv6
+ * server interface and falls back to 127.0.0.1
+ * if there's no ipv4 address.
+ */
+ r->sockaddr.pdc_ip = talloc_strdup(mem_ctx, "127.0.0.1");
+ if (r->sockaddr.pdc_ip == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
ndr_err = ndr_push_struct_blob(&blob, mem_ctx, r,
(ndr_push_flags_fn_t)ndr_push_NETLOGON_SAM_LOGON_RESPONSE_EX);
--
2.53.0
From e4d5269b2359c670acdf0cba81248f148ae68c17 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 11 Oct 2024 13:32:22 +0000
Subject: [PATCH 036/122] s3:libsmb: let discover_dc_netbios() return
DOMAIN_CONTROLLER_NOT_FOUND
We may get NT_STATUS_NOT_FOUND when the name can't be resolved
and NT_STATUS_INVALID_ADDRESS if the system doesn't have ipv4
addresses...
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(cherry picked from commit e47ce1d10b13d8ef165c70984e6e490f4c2a64c2)
---
source3/libsmb/dsgetdcname.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/source3/libsmb/dsgetdcname.c b/source3/libsmb/dsgetdcname.c
index da173e7bbb0..8278959dd7d 100644
--- a/source3/libsmb/dsgetdcname.c
+++ b/source3/libsmb/dsgetdcname.c
@@ -483,7 +483,19 @@ static NTSTATUS discover_dc_netbios(TALLOC_CTX *mem_ctx,
&count,
resolve_order);
if (!NT_STATUS_IS_OK(status)) {
- DEBUG(10,("discover_dc_netbios: failed to find DC\n"));
+ NTSTATUS raw_status = status;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_ADDRESS)) {
+ status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ DBG_DEBUG("failed to find DC for %s: %s => %s\n",
+ domain_name,
+ nt_errstr(raw_status),
+ nt_errstr(status));
return status;
}
--
2.53.0
From d90d2b0e985913247f43192cb94eec0efb3e9046 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnther=20Deschner?= <gd@samba.org>
Date: Wed, 2 Jul 2025 21:59:48 +0200
Subject: [PATCH 037/122] s3-winbindd: Fix internal winbind dsgetdcname calls
w.r.t. domain name
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
when winbind calls to dsgetdcname internally, make sure to
prefer the DNS domain name if we have it. Makes DNS lookups much more
likely to succeed.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15876
Guenther
Signed-off-by: Guenther Deschner <gd@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
Autobuild-User(master): Ralph Böhme <slow@samba.org>
Autobuild-Date(master): Mon Jul 7 10:44:37 UTC 2025 on atb-devel-224
(cherry picked from commit 2560c9b3224816ffd371a62103f65b3aca301ad5)
---
source3/winbindd/wb_queryuser.c | 17 +++++++++++++----
source3/winbindd/wb_sids2xids.c | 17 +++++++++++++----
source3/winbindd/wb_xids2sids.c | 12 +++++++++---
source3/winbindd/winbindd_dual.c | 6 +++++-
source3/winbindd/winbindd_proto.h | 1 +
source3/winbindd/winbindd_util.c | 19 +++++++++++++++++++
6 files changed, 60 insertions(+), 12 deletions(-)
diff --git a/source3/winbindd/wb_queryuser.c b/source3/winbindd/wb_queryuser.c
index c2758f1b76a..db8e946ba71 100644
--- a/source3/winbindd/wb_queryuser.c
+++ b/source3/winbindd/wb_queryuser.c
@@ -289,10 +289,19 @@ static void wb_queryuser_done(struct tevent_req *subreq)
if (NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) &&
!state->tried_dclookup) {
- D_DEBUG("GetNssInfo got DOMAIN_CONTROLLER_NOT_FOUND, calling wb_dsgetdcname_send()\n");
- subreq = wb_dsgetdcname_send(
- state, state->ev, state->info->domain_name, NULL, NULL,
- DS_RETURN_DNS_NAME);
+ const char *domain_name = find_dns_domain_name(
+ state->info->domain_name);
+
+ D_DEBUG("GetNssInfo got DOMAIN_CONTROLLER_NOT_FOUND, calling "
+ "wb_dsgetdcname_send(%s)\n",
+ domain_name);
+
+ subreq = wb_dsgetdcname_send(state,
+ state->ev,
+ domain_name,
+ NULL,
+ NULL,
+ DS_RETURN_DNS_NAME);
if (tevent_req_nomem(subreq, req)) {
return;
}
diff --git a/source3/winbindd/wb_sids2xids.c b/source3/winbindd/wb_sids2xids.c
index f0f6c23fc20..03e5e7e0258 100644
--- a/source3/winbindd/wb_sids2xids.c
+++ b/source3/winbindd/wb_sids2xids.c
@@ -612,13 +612,22 @@ static void wb_sids2xids_done(struct tevent_req *subreq)
!state->tried_dclookup) {
struct lsa_DomainInfo *d;
+ const char *domain_name = NULL;
- D_DEBUG("Domain controller not found. Calling wb_dsgetdcname_send() to get it.\n");
d = &state->idmap_doms.domains[state->dom_index];
- subreq = wb_dsgetdcname_send(
- state, state->ev, d->name.string, NULL, NULL,
- DS_RETURN_DNS_NAME);
+ domain_name = find_dns_domain_name(d->name.string);
+
+ D_DEBUG("Domain controller not found. Calling "
+ "wb_dsgetdcname_send(%s) to get it.\n",
+ domain_name);
+
+ subreq = wb_dsgetdcname_send(state,
+ state->ev,
+ domain_name,
+ NULL,
+ NULL,
+ DS_RETURN_DNS_NAME);
if (tevent_req_nomem(subreq, req)) {
return;
}
diff --git a/source3/winbindd/wb_xids2sids.c b/source3/winbindd/wb_xids2sids.c
index 86bd7f9deab..6fcf524d94f 100644
--- a/source3/winbindd/wb_xids2sids.c
+++ b/source3/winbindd/wb_xids2sids.c
@@ -143,9 +143,15 @@ static void wb_xids2sids_dom_done(struct tevent_req *subreq)
if (NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) &&
!state->tried_dclookup) {
- subreq = wb_dsgetdcname_send(
- state, state->ev, state->dom_map->name, NULL, NULL,
- DS_RETURN_DNS_NAME);
+ const char *domain_name = find_dns_domain_name(
+ state->dom_map->name);
+
+ subreq = wb_dsgetdcname_send(state,
+ state->ev,
+ domain_name,
+ NULL,
+ NULL,
+ DS_RETURN_DNS_NAME);
if (tevent_req_nomem(subreq, req)) {
return;
}
diff --git a/source3/winbindd/winbindd_dual.c b/source3/winbindd/winbindd_dual.c
index 36562ab10b8..02a10e41537 100644
--- a/source3/winbindd/winbindd_dual.c
+++ b/source3/winbindd/winbindd_dual.c
@@ -532,6 +532,7 @@ static void wb_domain_request_trigger(struct tevent_req *req,
struct wb_domain_request_state *state = tevent_req_data(
req, struct wb_domain_request_state);
struct winbindd_domain *domain = state->domain;
+ const char *domain_name = NULL;
struct tevent_req *subreq = NULL;
size_t shortest_queue_length;
@@ -604,8 +605,11 @@ static void wb_domain_request_trigger(struct tevent_req *req,
* which is indicated by DS_RETURN_DNS_NAME.
* For NT4 domains we still get the netbios name.
*/
+
+ domain_name = find_dns_domain_name(state->domain->name);
+
subreq = wb_dsgetdcname_send(state, state->ev,
- state->domain->name,
+ domain_name,
NULL, /* domain_guid */
NULL, /* site_name */
DS_RETURN_DNS_NAME); /* flags */
diff --git a/source3/winbindd/winbindd_proto.h b/source3/winbindd/winbindd_proto.h
index 9b10f2c061a..4f7dc8a15d6 100644
--- a/source3/winbindd/winbindd_proto.h
+++ b/source3/winbindd/winbindd_proto.h
@@ -567,6 +567,7 @@ bool parse_sidlist(TALLOC_CTX *mem_ctx, const char *sidstr,
struct dom_sid **sids, uint32_t *num_sids);
bool parse_xidlist(TALLOC_CTX *mem_ctx, const char *xidstr,
struct unixid **pxids, uint32_t *pnum_xids);
+const char *find_dns_domain_name(const char *domain_name);
/* The following definitions come from winbindd/winbindd_wins.c */
diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c
index fe93528787d..eca4116d0c8 100644
--- a/source3/winbindd/winbindd_util.c
+++ b/source3/winbindd/winbindd_util.c
@@ -2181,3 +2181,22 @@ fail:
TALLOC_FREE(xids);
return false;
}
+
+/**
+ * Helper to extract the DNS Domain Name from a struct winbindd_domain
+ */
+const char *find_dns_domain_name(const char *domain_name)
+{
+ struct winbindd_domain *wbdom = NULL;
+
+ wbdom = find_domain_from_name(domain_name);
+ if (wbdom == NULL) {
+ return domain_name;
+ }
+
+ if (wbdom->active_directory && wbdom->alt_name != NULL) {
+ return wbdom->alt_name;
+ }
+
+ return wbdom->name;
+}
--
2.53.0
From 7da6072ce95bca445368f6d0453247c8f92fcdf2 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 9 May 2025 09:38:41 +0200
Subject: [PATCH 038/122] s3:winbindd: avoid using any netlogon call to get a
dc name
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15876
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(backported from commit f86a4bf6848ade2db7229d182576db3320c3ece7)
---
source3/winbindd/winbindd_cm.c | 145 ---------------------------
source3/winbindd/winbindd_dual_srv.c | 105 +------------------
2 files changed, 5 insertions(+), 245 deletions(-)
diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
index 2ebfb0f6dd8..195259daa43 100644
--- a/source3/winbindd/winbindd_cm.c
+++ b/source3/winbindd/winbindd_cm.c
@@ -475,135 +475,6 @@ static bool cm_is_ipc_credentials(struct cli_credentials *creds)
return ret;
}
-static bool get_dc_name_via_netlogon(struct winbindd_domain *domain,
- fstring dcname,
- struct sockaddr_storage *dc_ss,
- uint32_t request_flags)
-{
- struct winbindd_domain *our_domain = NULL;
- struct rpc_pipe_client *netlogon_pipe = NULL;
- NTSTATUS result;
- WERROR werr;
- TALLOC_CTX *mem_ctx;
- unsigned int orig_timeout;
- const char *tmp = NULL;
- const char *p;
- struct dcerpc_binding_handle *b;
-
- /* Hmmmm. We can only open one connection to the NETLOGON pipe at the
- * moment.... */
-
- if (IS_DC) {
- return False;
- }
-
- if (domain->primary) {
- return False;
- }
-
- our_domain = find_our_domain();
-
- if ((mem_ctx = talloc_init("get_dc_name_via_netlogon")) == NULL) {
- return False;
- }
-
- result = cm_connect_netlogon(our_domain, &netlogon_pipe);
- if (!NT_STATUS_IS_OK(result)) {
- talloc_destroy(mem_ctx);
- return False;
- }
-
- b = netlogon_pipe->binding_handle;
-
- /* This call can take a long time - allow the server to time out.
- 35 seconds should do it. */
-
- orig_timeout = rpccli_set_timeout(netlogon_pipe, 35000);
-
- if (our_domain->active_directory) {
- struct netr_DsRGetDCNameInfo *domain_info = NULL;
-
- /*
- * TODO request flags are not respected in the server
- * (and in some cases, like REQUIRE_PDC, causes an error)
- */
- result = dcerpc_netr_DsRGetDCName(b,
- mem_ctx,
- our_domain->dcname,
- domain->name,
- NULL,
- NULL,
- request_flags|DS_RETURN_DNS_NAME,
- &domain_info,
- &werr);
- if (NT_STATUS_IS_OK(result) && W_ERROR_IS_OK(werr)) {
- tmp = talloc_strdup(
- mem_ctx, domain_info->dc_unc);
- if (tmp == NULL) {
- DEBUG(0, ("talloc_strdup failed\n"));
- talloc_destroy(mem_ctx);
- return false;
- }
- if (domain->alt_name == NULL) {
- domain->alt_name = talloc_strdup(domain,
- domain_info->domain_name);
- if (domain->alt_name == NULL) {
- DEBUG(0, ("talloc_strdup failed\n"));
- talloc_destroy(mem_ctx);
- return false;
- }
- }
- if (domain->forest_name == NULL) {
- domain->forest_name = talloc_strdup(domain,
- domain_info->forest_name);
- if (domain->forest_name == NULL) {
- DEBUG(0, ("talloc_strdup failed\n"));
- talloc_destroy(mem_ctx);
- return false;
- }
- }
- }
- } else {
- result = dcerpc_netr_GetAnyDCName(b, mem_ctx,
- our_domain->dcname,
- domain->name,
- &tmp,
- &werr);
- }
-
- /* And restore our original timeout. */
- rpccli_set_timeout(netlogon_pipe, orig_timeout);
-
- if (!NT_STATUS_IS_OK(result)) {
- DEBUG(10,("dcerpc_netr_GetAnyDCName failed: %s\n",
- nt_errstr(result)));
- talloc_destroy(mem_ctx);
- return false;
- }
-
- if (!W_ERROR_IS_OK(werr)) {
- DEBUG(10,("dcerpc_netr_GetAnyDCName failed: %s\n",
- win_errstr(werr)));
- talloc_destroy(mem_ctx);
- return false;
- }
-
- /* dcerpc_netr_GetAnyDCName gives us a name with \\ */
- p = strip_hostname(tmp);
-
- fstrcpy(dcname, p);
-
- talloc_destroy(mem_ctx);
-
- DEBUG(10,("dcerpc_netr_GetAnyDCName returned %s\n", dcname));
-
- if (!resolve_name(dcname, dc_ss, 0x20, true)) {
- return False;
- }
-
- return True;
-}
-
/**
* Helper function to assemble trust password and account name
*/
@@ -1279,24 +1150,8 @@ static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
struct samba_sockaddr *sa_list = NULL;
size_t salist_size = 0;
size_t i;
- bool is_our_domain;
enum security_types sec = (enum security_types)lp_security();
- is_our_domain = strequal(domain->name, lp_workgroup());
-
- /* If not our domain, get the preferred DC, by asking our primary DC */
- if ( !is_our_domain
- && get_dc_name_via_netlogon(domain, dcname, &ss, request_flags)
- && add_one_dc_unique(mem_ctx, domain->name, dcname, &ss, dcs,
- num_dcs) )
- {
- char addr[INET6_ADDRSTRLEN];
- print_sockaddr(addr, sizeof(addr), &ss);
- DEBUG(10, ("Retrieved DC %s at %s via netlogon\n",
- dcname, addr));
- return True;
- }
-
if ((sec == SEC_ADS) && (domain->alt_name != NULL)) {
char *sitename = NULL;
diff --git a/source3/winbindd/winbindd_dual_srv.c b/source3/winbindd/winbindd_dual_srv.c
index f0fd18a8fa6..47c68257b12 100644
--- a/source3/winbindd/winbindd_dual_srv.c
+++ b/source3/winbindd/winbindd_dual_srv.c
@@ -662,106 +662,11 @@ NTSTATUS _wbint_QueryUserRidList(struct pipes_struct *p,
NTSTATUS _wbint_DsGetDcName(struct pipes_struct *p, struct wbint_DsGetDcName *r)
{
- struct winbindd_domain *domain = wb_child_domain();
- struct rpc_pipe_client *netlogon_pipe;
- struct netr_DsRGetDCNameInfo *dc_info;
- NTSTATUS status;
- WERROR werr;
- unsigned int orig_timeout;
- struct dcerpc_binding_handle *b;
- bool retry = false;
- bool try_dsrgetdcname = false;
-
- if (domain == NULL) {
- return dsgetdcname(p->mem_ctx, global_messaging_context(),
- r->in.domain_name, r->in.domain_guid,
- r->in.site_name ? r->in.site_name : "",
- r->in.flags,
- r->out.dc_info);
- }
-
- if (domain->active_directory) {
- try_dsrgetdcname = true;
- }
-
-reconnect:
- status = cm_connect_netlogon(domain, &netlogon_pipe);
-
- reset_cm_connection_on_error(domain, NULL, status);
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(10, ("Can't contact the NETLOGON pipe\n"));
- return status;
- }
-
- b = netlogon_pipe->binding_handle;
-
- /* This call can take a long time - allow the server to time out.
- 35 seconds should do it. */
-
- orig_timeout = rpccli_set_timeout(netlogon_pipe, 35000);
-
- if (try_dsrgetdcname) {
- status = dcerpc_netr_DsRGetDCName(b,
- p->mem_ctx, domain->dcname,
- r->in.domain_name, NULL, r->in.domain_guid,
- r->in.flags, r->out.dc_info, &werr);
- if (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(werr)) {
- goto done;
- }
- if (!retry &&
- reset_cm_connection_on_error(domain, NULL, status))
- {
- retry = true;
- goto reconnect;
- }
- try_dsrgetdcname = false;
- retry = false;
- }
-
- /*
- * Fallback to less capable methods
- */
-
- dc_info = talloc_zero(r->out.dc_info, struct netr_DsRGetDCNameInfo);
- if (dc_info == NULL) {
- status = NT_STATUS_NO_MEMORY;
- goto done;
- }
-
- if (r->in.flags & DS_PDC_REQUIRED) {
- status = dcerpc_netr_GetDcName(b,
- p->mem_ctx, domain->dcname,
- r->in.domain_name, &dc_info->dc_unc, &werr);
- } else {
- status = dcerpc_netr_GetAnyDCName(b,
- p->mem_ctx, domain->dcname,
- r->in.domain_name, &dc_info->dc_unc, &werr);
- }
-
- if (!retry && reset_cm_connection_on_error(domain, b, status)) {
- retry = true;
- goto reconnect;
- }
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(10, ("dcerpc_netr_Get[Any]DCName failed: %s\n",
- nt_errstr(status)));
- goto done;
- }
- if (!W_ERROR_IS_OK(werr)) {
- DEBUG(10, ("dcerpc_netr_Get[Any]DCName failed: %s\n",
- win_errstr(werr)));
- status = werror_to_ntstatus(werr);
- goto done;
- }
-
- *r->out.dc_info = dc_info;
- status = NT_STATUS_OK;
-
-done:
- /* And restore our original timeout. */
- rpccli_set_timeout(netlogon_pipe, orig_timeout);
-
- return status;
+ return dsgetdcname(p->mem_ctx, global_messaging_context(),
+ r->in.domain_name, r->in.domain_guid,
+ r->in.site_name ? r->in.site_name : "",
+ r->in.flags,
+ r->out.dc_info);
}
NTSTATUS _wbint_LookupRids(struct pipes_struct *p, struct wbint_LookupRids *r)
--
2.53.0
From ad54ceadacfbcf0d9c96ad773e50db96003e2c08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Wed, 23 Jul 2025 15:09:21 +0200
Subject: [PATCH 039/122] s3:winbindd: Resolve dc name using CLDAP also for
ROLE_IPA_DC
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
server role ROLE_IPA_DC (introduced in e2d5b4d) needs special handling
in dcip_check_name(). We should resolve the DC name using:
- CLDAP in dcip_check_name_ads()
instead of:
- NETBIOS in nbt_getdc() that fails if Windows is not providing netbios.
The impacted environment has:
domain->alt_name = example.com
domain->active_directory = 1
security = USER
server role = ROLE_IPA_DC
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15891
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Signed-off-by: Andreas Schneider <asn@samba.org>
Pair-programmed-with: Andreas Schneider <asn@samba.org>
Reviewed-by: Alexander Bokovoy <ab@samba.org>
(cherry picked from commit 4921c3304e5e0480e5bb80a757b3f04b3b92c3b1)
(cherry picked from commit fe8eafc289dfbb6f2b6c706f2a8a68186807d4f8)
---
source3/winbindd/winbindd_cm.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
index 195259daa43..86dbf68f033 100644
--- a/source3/winbindd/winbindd_cm.c
+++ b/source3/winbindd/winbindd_cm.c
@@ -1075,7 +1075,9 @@ static bool dcip_check_name(TALLOC_CTX *mem_ctx,
if ((lp_security() == SEC_ADS) && (domain->alt_name != NULL)) {
is_ad_domain = true;
- } else if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ } else if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC ||
+ lp_server_role() == ROLE_IPA_DC)
+ {
is_ad_domain = domain->active_directory;
}
--
2.53.0
From b73efffbb02903427af2c2cc57171d4848ca11f8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Mon, 4 Aug 2025 08:35:29 +0200
Subject: [PATCH 040/122] docs-xml: Make smb.conf 'server role' value
consistent with ROLE_IPA_DC in libparam
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15891
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Reviewed-by: Alexander Bokovoy <ab@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(cherry picked from commit d88268102ade07fab345e04109818d97d8843a14)
(cherry picked from commit d14fa6eb96a9f296d386ff4864e4f016440f2ac8)
---
docs-xml/smbdotconf/security/serverrole.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs-xml/smbdotconf/security/serverrole.xml b/docs-xml/smbdotconf/security/serverrole.xml
index 4ea4e4751ee..40244e125ce 100644
--- a/docs-xml/smbdotconf/security/serverrole.xml
+++ b/docs-xml/smbdotconf/security/serverrole.xml
@@ -78,7 +78,7 @@
url="http://wiki.samba.org/index.php/Samba4/HOWTO">Samba4
HOWTO</ulink></para>
- <para><anchor id="IPA-DC"/><emphasis>SERVER ROLE = IPA DOMAIN CONTROLLER</emphasis></para>
+ <para><anchor id="IPA-DC"/><emphasis>SERVER ROLE = IPA PRIMARY DOMAIN CONTROLLER</emphasis></para>
<para>This mode of operation runs Samba in a hybrid mode for IPA
domain controller, providing forest trust to Active Directory.
--
2.53.0
From 832a4e31630fd441f8ab4325439f90d561cb8fa4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Mon, 4 Aug 2025 23:26:02 +0200
Subject: [PATCH 041/122] s3:netlogon: IPA DC is the PDC as well - allow
ROLE_IPA_DC in _netr_DsRGetForestTrustInformation()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15891
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Reviewed-by: Alexander Bokovoy <ab@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(cherry picked from commit 1dbafcc4e4ff8f39af5ca737b30e9821413dd1f2)
(cherry picked from commit 00adb3104e745babb2c330fa9c9e324805395edb)
---
source3/rpc_server/netlogon/srv_netlog_nt.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/source3/rpc_server/netlogon/srv_netlog_nt.c b/source3/rpc_server/netlogon/srv_netlog_nt.c
index c5a4b0ef30c..7957d3ab34d 100644
--- a/source3/rpc_server/netlogon/srv_netlog_nt.c
+++ b/source3/rpc_server/netlogon/srv_netlog_nt.c
@@ -2613,7 +2613,10 @@ WERROR _netr_DsRGetForestTrustInformation(struct pipes_struct *p,
return WERR_INVALID_FLAGS;
}
- if ((r->in.flags & DS_GFTI_UPDATE_TDO) && (lp_server_role() != ROLE_DOMAIN_PDC)) {
+ if ((r->in.flags & DS_GFTI_UPDATE_TDO) &&
+ (lp_server_role() != ROLE_DOMAIN_PDC) &&
+ (lp_server_role() != ROLE_IPA_DC))
+ {
p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
return WERR_NERR_NOTPRIMARY;
}
--
2.53.0
From 8d5638581dfc539c8524d7a507e8cc8977e827a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Mon, 4 Aug 2025 23:28:24 +0200
Subject: [PATCH 042/122] s3:utils: Allow ROLE_IPA_DC to allow to use Kerberos
in gensec
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15891
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Reviewed-by: Alexander Bokovoy <ab@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Tue Aug 5 14:51:51 UTC 2025 on atb-devel-224
(cherry picked from commit a4dff82e45308db3ccabac2a55c03d52f04d7b4d)
Autobuild-User(v4-22-test): Jule Anger <janger@samba.org>
Autobuild-Date(v4-22-test): Mon Aug 11 07:53:47 UTC 2025 on atb-devel-224
(cherry picked from commit 3364797676624aa9367076a69b2daf73870429ba)
---
source3/utils/ntlm_auth.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/source3/utils/ntlm_auth.c b/source3/utils/ntlm_auth.c
index cff3c53845f..2968ca47734 100644
--- a/source3/utils/ntlm_auth.c
+++ b/source3/utils/ntlm_auth.c
@@ -1341,7 +1341,11 @@ static NTSTATUS ntlm_auth_prepare_gensec_server(TALLOC_CTX *mem_ctx,
cli_credentials_set_conf(server_credentials, lp_ctx);
- if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC || lp_security() == SEC_ADS || USE_KERBEROS_KEYTAB) {
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC ||
+ lp_server_role() == ROLE_IPA_DC ||
+ lp_security() == SEC_ADS ||
+ USE_KERBEROS_KEYTAB)
+ {
cli_credentials_set_kerberos_state(server_credentials,
CRED_USE_KERBEROS_DESIRED,
CRED_SPECIFIED);
--
2.53.0
From 3ef02a381cdc83549506e159ebc457730c06c547 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 22 Jul 2025 19:22:31 +0200
Subject: [PATCH 043/122] libads: fix get_kdc_ip_string()
Correctly handle the interaction between optionally passed in DC via
pss and DC lookup.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15876
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit 23f100f67c0586a940e91e9e1e6f42b804401322)
---
source3/libads/kerberos.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/source3/libads/kerberos.c b/source3/libads/kerberos.c
index f74d8eb567c..f324321c87b 100644
--- a/source3/libads/kerberos.c
+++ b/source3/libads/kerberos.c
@@ -523,10 +523,12 @@ static char *get_kdc_ip_string(char *mem_ctx,
DBG_DEBUG("%zu additional KDCs to test\n", num_dcs);
if (num_dcs == 0) {
/*
- * We do not have additional KDCs, but we have the one passed
- * in via `pss`. So just use that one and leave.
+ * We do not have additional KDCs, but if we have one passed
+ * in via `pss` just use that one, otherwise fail
*/
- result = talloc_move(mem_ctx, &kdc_str);
+ if (pss != NULL) {
+ result = talloc_move(mem_ctx, &kdc_str);
+ }
goto out;
}
@@ -567,6 +569,9 @@ static char *get_kdc_ip_string(char *mem_ctx,
if (!NT_STATUS_IS_OK(status)) {
DEBUG(10,("get_kdc_ip_string: cldap_multi_netlogon failed: "
"%s\n", nt_errstr(status)));
+ if (pss != NULL) {
+ result = talloc_move(mem_ctx, &kdc_str);
+ }
goto out;
}
--
2.53.0
From b0dbc167f85deabff2af5b18bc201e8db0d3b97d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 22 Jul 2025 19:16:14 +0200
Subject: [PATCH 044/122] winbindd: use find_domain_from_name_noinit() in
find_dns_domain_name()
Avoid triggering a connection to a DC of a trusted domain.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15876
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit 9ad2e59a464bb472da2071c61a254547b6497625)
---
source3/winbindd/winbindd_util.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c
index eca4116d0c8..3a7a9114988 100644
--- a/source3/winbindd/winbindd_util.c
+++ b/source3/winbindd/winbindd_util.c
@@ -2189,7 +2189,7 @@ const char *find_dns_domain_name(const char *domain_name)
{
struct winbindd_domain *wbdom = NULL;
- wbdom = find_domain_from_name(domain_name);
+ wbdom = find_domain_from_name_noinit(domain_name);
if (wbdom == NULL) {
return domain_name;
}
--
2.53.0
From 1961f54ce07f7dc3cfcae5c00b96b39109f08b3a Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 19 Dec 2023 11:11:55 +0100
Subject: [PATCH 045/122] vfs_default: allow disabling /proc/fds and
RESOLVE_NO_SYMLINK at compile time
This will be used in CI to have a gitlab runner without all modern Linux
features we make use of as part of path processing:
- O_PATH
- openat2() with RESOLVE_NO_SYMLINKS
- somehow safely reopen an O_PATH file handle
That gives what a classix UNIX like AIX or Solaris offers feature wise.
Other OSes support other combinations of those features, but we leave the
exersize of possibly adding more runners supporting those combinations to the
reader.
The following list shows which features are available and used by Samba on a few
OSes:
| O_PATH | RESOLVE_NO_SYMLINKS | Safe reopen | CI covered
--------|----------------|---------------------|----------------------------
| Supported Used | Supported Used | Supported Used |
============================================================================
Linux | + + | + + | + + | +
FreeBSD | + + | + [1] - | + [2] - | -
AIX | - - | - - | - - | +
[1] via open() flag O_RESOLVE_BENEATH
[2] via open() flag O_EMPTY_PATH
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>
(cherry picked from commit 5c2f96442a25a1725809a28b3719afbc0bd01830)
---
source3/modules/vfs_default.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/source3/modules/vfs_default.c b/source3/modules/vfs_default.c
index 1d4b9b1a840..8d78831492f 100644
--- a/source3/modules/vfs_default.c
+++ b/source3/modules/vfs_default.c
@@ -52,6 +52,9 @@ static int vfswrap_connect(vfs_handle_struct *handle, const char *service, const
bool bval;
handle->conn->have_proc_fds = sys_have_proc_fds();
+#ifdef DISABLE_PROC_FDS
+ handle->conn->have_proc_fds = false;
+#endif
/*
* assume the kernel will support openat2(),
@@ -70,6 +73,9 @@ static int vfswrap_connect(vfs_handle_struct *handle, const char *service, const
handle->conn->open_how_resolve |=
VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS;
}
+#ifdef DISABLE_VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS
+ handle->conn->open_how_resolve &= ~VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS;
+#endif
return 0; /* Return >= 0 for success */
}
--
2.53.0
From 26de62a2a968dd5b73af296251b26112cdd533e5 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 19 Dec 2023 11:12:49 +0100
Subject: [PATCH 046/122] CI: disable /proc/fds and RESOLVE_NO_SYMLINK in
samba-no-opath-build runner
This is a more sensible combination of missing Linux specific features:
- O_PATH
- openat2() with RESOLVE_NO_SYMLINKS
- somehow safely reopen an O_PATH file handle
Currently only O_PATH is disabled for these jobs, but that doesn't really match
and know OS.
The following list shows which features are available and used by Samba on a few
OSes:
| O_PATH | RESOLVE_NO_SYMLINKS | Safe reopen | CI covered
--------|----------------|---------------------|----------------------------
| Supported Used | Supported Used | Supported Used |
============================================================================
Linux | + + | + + | + + | +
FreeBSD | + + | + [1] - | + [2] - | -
AIX | - - | - - | - - | +
So by also disabling RESOLVE_NO_SYMLINKS and Safe Reopen, we cover classic UNIX
systems like AIX.
[1] via open() flag O_RESOLVE_BENEATH
[2] via open() flag O_EMPTY_PATH
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>
(cherry picked from commit 62cbe145c7e500c4759ed2005c78bd5056c87f43)
---
script/autobuild.py | 2 +-
selftest/skip.opath-required | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/script/autobuild.py b/script/autobuild.py
index e074c39d3c0..85043032d73 100755
--- a/script/autobuild.py
+++ b/script/autobuild.py
@@ -296,7 +296,7 @@ tasks = {
"samba-no-opath-build": {
"git-clone-required": True,
"sequence": [
- ("configure", "ADDITIONAL_CFLAGS='-DDISABLE_OPATH=1' ./configure.developer --without-ad-dc " + samba_configure_params),
+ ("configure", "ADDITIONAL_CFLAGS='-DDISABLE_OPATH=1 -DDISABLE_VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS=1 -DDISABLE_PROC_FDS=1' ./configure.developer --without-ad-dc " + samba_configure_params),
("make", "make -j"),
("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
("chmod-R-a-w", "chmod -R a-w ."),
diff --git a/selftest/skip.opath-required b/selftest/skip.opath-required
index c3a13f5ec6e..67764a0b027 100644
--- a/selftest/skip.opath-required
+++ b/selftest/skip.opath-required
@@ -14,3 +14,9 @@
# available this works fine. So for now restrict testing posix
# extensions to environments where we have O_PATH around
^samba.tests.smb1posix
+
+# These don't work without /proc/fd support
+^samba3.blackbox.test_symlink_traversal.*\(fileserver\)
+^samba3.blackbox.shadow_copy_torture.*\(fileserver\)
+^samba3.blackbox.virus_scanner.*\(fileserver:local\)
+^samba3.blackbox.shadow_copy2.*\(fileserver.*\)
--
2.53.0
From 2c27aae5a4c8d7368dc142fb2be36919296d2a02 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 2 Jan 2024 12:49:14 +0100
Subject: [PATCH 047/122] smbd: pass symlink target path to
safe_symlink_target_path()
Moves processing the symlink error response to the caller
filename_convert_dirfsp(). Prepares for using this in
non_widelink_open(), where it will replace symlink_target_below_conn()
with the same functionality.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>
(back-ported from commit 0515dded4ddb49e5570ae7df51126af1a2d643de)
---
source3/include/proto.h | 5 +++
source3/smbd/filename.c | 72 +++++++++++++++++++----------------------
2 files changed, 38 insertions(+), 39 deletions(-)
diff --git a/source3/include/proto.h b/source3/include/proto.h
index 8eed81d8f2e..13240033bf1 100644
--- a/source3/include/proto.h
+++ b/source3/include/proto.h
@@ -719,6 +719,11 @@ struct smb_filename *synthetic_smb_fname(TALLOC_CTX *mem_ctx,
const SMB_STRUCT_STAT *psbuf,
NTTIME twrp,
uint32_t flags);
+NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
+ const char *connectpath,
+ const char *target,
+ size_t unparsed,
+ char **_relative);
NTSTATUS filename_convert_dirfsp(
TALLOC_CTX *ctx,
connection_struct *conn,
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c
index 8693dcf1153..45fb90381e2 100644
--- a/source3/smbd/filename.c
+++ b/source3/smbd/filename.c
@@ -942,44 +942,34 @@ static char *symlink_target_path(
return ret;
}
-static NTSTATUS safe_symlink_target_path(
- TALLOC_CTX *mem_ctx,
- const char *connectpath,
- const char *name_in,
- const char *substitute,
- size_t unparsed,
- char **_name_out)
+NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
+ const char *connectpath,
+ const char *target,
+ size_t unparsed,
+ char **_relative)
{
- char *target = NULL;
char *abs_target = NULL;
char *abs_target_canon = NULL;
const char *relative = NULL;
- char *name_out = NULL;
- NTSTATUS status = NT_STATUS_NO_MEMORY;
bool in_share;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
- target = symlink_target_path(mem_ctx, name_in, substitute, unparsed);
- if (target == NULL) {
- goto fail;
- }
-
- DBG_DEBUG("name_in: %s, substitute: %s, unparsed: %zu, target=%s\n",
- name_in,
- substitute,
- unparsed,
- target);
+ DBG_DEBUG("connectpath [%s] target [%s] unparsed [%zu]\n",
+ connectpath, target, unparsed);
if (target[0] == '/') {
- abs_target = target;
+ abs_target = talloc_strdup(mem_ctx, target);
} else {
- abs_target = talloc_asprintf(
- target, "%s/%s", connectpath, target);
- if (abs_target == NULL) {
- goto fail;
- }
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ connectpath,
+ target);
+ }
+ if (abs_target == NULL) {
+ goto fail;
}
- abs_target_canon = canonicalize_absolute_path(target, abs_target);
+ abs_target_canon = canonicalize_absolute_path(abs_target, abs_target);
if (abs_target_canon == NULL) {
goto fail;
}
@@ -994,15 +984,14 @@ static NTSTATUS safe_symlink_target_path(
goto fail;
}
- name_out = talloc_strdup(mem_ctx, relative);
- if (name_out == NULL) {
+ *_relative = talloc_strdup(mem_ctx, relative);
+ if (*_relative == NULL) {
goto fail;
}
status = NT_STATUS_OK;
- *_name_out = name_out;
fail:
- TALLOC_FREE(target);
+ TALLOC_FREE(abs_target);
return status;
}
@@ -1438,6 +1427,7 @@ NTSTATUS filename_convert_dirfsp(
size_t unparsed = 0;
NTSTATUS status;
char *target = NULL;
+ char *safe_target = NULL;
size_t symlink_redirects = 0;
next:
@@ -1476,17 +1466,21 @@ next:
* resolve all symlinks locally.
*/
- status = safe_symlink_target_path(
- mem_ctx,
- conn->connectpath,
- name_in,
- substitute,
- unparsed,
- &target);
+ target = symlink_target_path(mem_ctx, name_in, substitute, unparsed);
+ if (target == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = safe_symlink_target_path(mem_ctx,
+ conn->connectpath,
+ target,
+ unparsed,
+ &safe_target);
+ TALLOC_FREE(target);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
- name_in = target;
+ name_in = safe_target;
symlink_redirects += 1;
--
2.53.0
From 99d7e841d4e18f760c137530bbed0dea6115311a Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 2 Jan 2024 13:25:25 +0100
Subject: [PATCH 048/122] smbd: add a directory argument to
safe_symlink_target_path()
Existing caller passes NULL, no change in behaviour. Prepares for
replacing symlink_target_below_conn() in open.c.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>
(cherry picked from commit fc80c72d658a41fe4d93b24b793b52c91b350175)
---
source3/include/proto.h | 1 +
source3/smbd/filename.c | 15 ++++++++++++++-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/source3/include/proto.h b/source3/include/proto.h
index 13240033bf1..15c5839caf8 100644
--- a/source3/include/proto.h
+++ b/source3/include/proto.h
@@ -721,6 +721,7 @@ struct smb_filename *synthetic_smb_fname(TALLOC_CTX *mem_ctx,
uint32_t flags);
NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
const char *connectpath,
+ const char *dir,
const char *target,
size_t unparsed,
char **_relative);
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c
index 45fb90381e2..55a49e0ba93 100644
--- a/source3/smbd/filename.c
+++ b/source3/smbd/filename.c
@@ -944,6 +944,7 @@ static char *symlink_target_path(
NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
const char *connectpath,
+ const char *dir,
const char *target,
size_t unparsed,
char **_relative)
@@ -959,10 +960,21 @@ NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
if (target[0] == '/') {
abs_target = talloc_strdup(mem_ctx, target);
- } else {
+ } else if (dir == NULL) {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ connectpath,
+ target);
+ } else if (dir[0] == '/') {
abs_target = talloc_asprintf(mem_ctx,
"%s/%s",
+ dir,
+ target);
+ } else {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
connectpath,
+ dir,
target);
}
if (abs_target == NULL) {
@@ -1473,6 +1485,7 @@ next:
status = safe_symlink_target_path(mem_ctx,
conn->connectpath,
+ NULL,
target,
unparsed,
&safe_target);
--
2.53.0
From 5041a6fa5cdfd21bf697249d900ea5c107d355a2 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 2 Jan 2024 14:34:26 +0100
Subject: [PATCH 049/122] smbd: use safe_symlink_target_path() in
symlink_target_below_conn()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>
(cherry picked from commit 1965fc77b3852a0593e13897af08f5304a1ce3a2)
---
selftest/skip.opath-required | 2 -
source3/smbd/open.c | 73 +++++++-----------------------------
2 files changed, 14 insertions(+), 61 deletions(-)
diff --git a/selftest/skip.opath-required b/selftest/skip.opath-required
index 67764a0b027..9c6ba481cdf 100644
--- a/selftest/skip.opath-required
+++ b/selftest/skip.opath-required
@@ -16,7 +16,5 @@
^samba.tests.smb1posix
# These don't work without /proc/fd support
-^samba3.blackbox.test_symlink_traversal.*\(fileserver\)
^samba3.blackbox.shadow_copy_torture.*\(fileserver\)
^samba3.blackbox.virus_scanner.*\(fileserver:local\)
-^samba3.blackbox.shadow_copy2.*\(fileserver.*\)
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 74be444fef5..6582bd60245 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -571,7 +571,6 @@ out:
static NTSTATUS symlink_target_below_conn(
TALLOC_CTX *mem_ctx,
const char *connection_path,
- size_t connection_path_len,
struct files_struct *fsp,
struct files_struct *dirfsp,
struct smb_filename *symlink_name,
@@ -579,9 +578,7 @@ static NTSTATUS symlink_target_below_conn(
{
char *target = NULL;
char *absolute = NULL;
- const char *relative = NULL;
NTSTATUS status;
- bool ok;
if (fsp_get_pathref_fd(fsp) != -1) {
/*
@@ -594,69 +591,28 @@ static NTSTATUS symlink_target_below_conn(
talloc_tos(), dirfsp, symlink_name, &target);
}
+ status = safe_symlink_target_path(talloc_tos(),
+ connection_path,
+ dirfsp->fsp_name->base_name,
+ target,
+ 0,
+ &absolute);
if (!NT_STATUS_IS_OK(status)) {
- DBG_DEBUG("readlink_talloc failed: %s\n", nt_errstr(status));
+ DBG_DEBUG("safe_symlink_target_path() failed: %s\n",
+ nt_errstr(status));
return status;
}
- if (target[0] != '/') {
- char *tmp = talloc_asprintf(
- talloc_tos(),
- "%s/%s/%s",
- connection_path,
- dirfsp->fsp_name->base_name,
- target);
-
- TALLOC_FREE(target);
-
- if (tmp == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
- target = tmp;
- }
-
- DBG_DEBUG("redirecting to %s\n", target);
-
- absolute = canonicalize_absolute_path(talloc_tos(), target);
- TALLOC_FREE(target);
-
- if (absolute == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
-
- /*
- * We're doing the "below connection_path" here because it's
- * cheap. It might be that we get a symlink out of the share,
- * pointing to yet another symlink getting us back into the
- * share. If we need that, we would have to remove the check
- * here.
- */
- ok = subdir_of(
- connection_path,
- connection_path_len,
- absolute,
- &relative);
- if (!ok) {
- DBG_NOTICE("Bad access attempt: %s is a symlink "
- "outside the share path\n"
- "conn_rootdir =%s\n"
- "resolved_name=%s\n",
- symlink_name->base_name,
- connection_path,
- absolute);
- TALLOC_FREE(absolute);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
- }
-
- if (relative[0] == '\0') {
+ if (absolute[0] == '\0') {
/*
* special case symlink to share root: "." is our
* share root filename
*/
- absolute[0] = '.';
- absolute[1] = '\0';
- } else {
- memmove(absolute, relative, strlen(relative)+1);
+ TALLOC_FREE(absolute);
+ absolute = talloc_strdup(talloc_tos(), ".");
+ if (absolute == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
}
*_target = absolute;
@@ -834,7 +790,6 @@ again:
status = symlink_target_below_conn(
talloc_tos(),
connpath,
- connpath_len,
fsp,
discard_const_p(files_struct, dirfsp),
smb_fname_rel,
--
2.53.0
From f2fc99f0c7d441115a486413f345c0226a00b38b Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Mon, 18 Dec 2023 12:35:58 +0100
Subject: [PATCH 050/122] smbd: use dirfsp and atname in open_directory()
On systems without /proc/fd support this avoid the expensive chdir()
logic in non_widelink_open(). open_file_ntcreate() already passes
dirfsp and atname to reopen_from_fsp(), it was just missed in the
conversion.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549
Reviewed-by: Volker Lendecke <vl@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Autobuild-User(master): Volker Lendecke <vl@samba.org>
Autobuild-Date(master): Mon Jan 22 12:00:56 UTC 2024 on atb-devel-224
(cherry picked from commit 2713023250f15cf9971d88620cab9dd4afd0dc73)
Autobuild-User(v4-19-test): Jule Anger <janger@samba.org>
Autobuild-Date(v4-19-test): Mon Jan 29 11:59:41 UTC 2024 on atb-devel-224
---
source3/smbd/open.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 6582bd60245..b9849f82396 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -4865,8 +4865,8 @@ static NTSTATUS open_directory(connection_struct *conn,
if (access_mask & need_fd_access) {
status = reopen_from_fsp(
- fsp->conn->cwd_fsp,
- fsp->fsp_name,
+ parent_dir_fname->fsp,
+ smb_fname_atname,
fsp,
O_RDONLY | O_DIRECTORY,
0,
--
2.53.0
From 7d102268ebbebf6fc723a43485a82f72069d00ee Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl@samba.org>
Date: Fri, 16 Dec 2022 16:35:00 +0100
Subject: [PATCH 051/122] smbd: Return open_symlink_err from
filename_convert_dirfsp_nosymlink()
Don't lose information returned from openat_pathref_fsp_nosymlink()
Signed-off-by: Volker Lendecke <vl@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(backported from commit c81d1d3fe4e3aeb2761dc539e8fb87d2ad862e5f)
Related to: https://bugzilla.samba.org/show_bug.cgi?id=15549
---
source3/smbd/filename.c | 77 +++++++++++++++++------------------------
1 file changed, 32 insertions(+), 45 deletions(-)
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c
index 55a49e0ba93..9fd85af992a 100644
--- a/source3/smbd/filename.c
+++ b/source3/smbd/filename.c
@@ -944,37 +944,35 @@ static char *symlink_target_path(
NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
const char *connectpath,
- const char *dir,
- const char *target,
+ const char *name_in,
+ const char *substitute,
size_t unparsed,
char **_relative)
{
+ char *target = NULL;
char *abs_target = NULL;
char *abs_target_canon = NULL;
const char *relative = NULL;
bool in_share;
NTSTATUS status = NT_STATUS_NO_MEMORY;
+ target = symlink_target_path(mem_ctx,
+ name_in,
+ substitute,
+ unparsed);
+ if (target == NULL) {
+ goto fail;
+ }
+
DBG_DEBUG("connectpath [%s] target [%s] unparsed [%zu]\n",
connectpath, target, unparsed);
if (target[0] == '/') {
- abs_target = talloc_strdup(mem_ctx, target);
- } else if (dir == NULL) {
- abs_target = talloc_asprintf(mem_ctx,
- "%s/%s",
- connectpath,
- target);
- } else if (dir[0] == '/') {
- abs_target = talloc_asprintf(mem_ctx,
- "%s/%s",
- dir,
- target);
+ abs_target = target;
} else {
- abs_target = talloc_asprintf(mem_ctx,
- "%s/%s/%s",
+ abs_target = talloc_asprintf(target,
+ "%s/%s",
connectpath,
- dir,
target);
}
if (abs_target == NULL) {
@@ -1019,8 +1017,7 @@ static NTSTATUS filename_convert_dirfsp_nosymlink(
NTTIME twrp,
struct files_struct **_dirfsp,
struct smb_filename **_smb_fname,
- char **_substitute,
- size_t *_unparsed)
+ struct open_symlink_err **_symlink_err)
{
struct smb_filename *smb_dirname = NULL;
struct smb_filename *smb_fname_rel = NULL;
@@ -1142,11 +1139,8 @@ static NTSTATUS filename_convert_dirfsp_nosymlink(
SMB_ASSERT(name_in_len >= dirname_len);
- *_substitute = talloc_move(
- mem_ctx,
- &symlink_err->reparse->substitute_name);
- *_unparsed = symlink_err->unparsed +
- (name_in_len - dirname_len);
+ symlink_err->unparsed += (name_in_len - dirname_len);
+ *_symlink_err = symlink_err;
goto fail;
}
@@ -1435,10 +1429,9 @@ NTSTATUS filename_convert_dirfsp(
struct files_struct **_dirfsp,
struct smb_filename **_smb_fname)
{
- char *substitute = NULL;
- size_t unparsed = 0;
+ struct open_symlink_err *symlink_err = NULL;
NTSTATUS status;
- char *target = NULL;
+ char *substitute = NULL;
char *safe_target = NULL;
size_t symlink_redirects = 0;
@@ -1447,16 +1440,14 @@ next:
return NT_STATUS_OBJECT_PATH_NOT_FOUND;
}
- status = filename_convert_dirfsp_nosymlink(
- mem_ctx,
- conn,
- name_in,
- ucf_flags,
- twrp,
- _dirfsp,
- _smb_fname,
- &substitute,
- &unparsed);
+ status = filename_convert_dirfsp_nosymlink(mem_ctx,
+ conn,
+ name_in,
+ ucf_flags,
+ twrp,
+ _dirfsp,
+ _smb_fname,
+ &symlink_err);
if (!NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
return status;
@@ -1477,19 +1468,15 @@ next:
* UCF_POSIX_PATHNAMES set to cause the client to
* resolve all symlinks locally.
*/
-
- target = symlink_target_path(mem_ctx, name_in, substitute, unparsed);
- if (target == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
+ substitute = symlink_err->reparse->substitute_name;
status = safe_symlink_target_path(mem_ctx,
conn->connectpath,
- NULL,
- target,
- unparsed,
+ name_in,
+ substitute,
+ symlink_err->unparsed,
&safe_target);
- TALLOC_FREE(target);
+ TALLOC_FREE(symlink_err);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
--
2.53.0
From edaabc3d53fddd9e2fa6168c8bf01ebfbf229657 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 25 Apr 2024 15:24:57 +0200
Subject: [PATCH 052/122] s3/lib: add next helper variable in server_id_watch_*
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit d76edcd48437715c7541b5b1e6a56245c25f460b)
---
source3/lib/server_id_watch.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/source3/lib/server_id_watch.c b/source3/lib/server_id_watch.c
index f0189e0e896..50b35f27b3e 100644
--- a/source3/lib/server_id_watch.c
+++ b/source3/lib/server_id_watch.c
@@ -27,6 +27,7 @@
struct server_id_watch_state {
struct tevent_context *ev;
struct server_id pid;
+ struct timeval start;
};
static void server_id_watch_waited(struct tevent_req *subreq);
@@ -37,6 +38,7 @@ struct tevent_req *server_id_watch_send(TALLOC_CTX *mem_ctx,
{
struct tevent_req *req, *subreq;
struct server_id_watch_state *state;
+ struct timeval next;
req = tevent_req_create(mem_ctx, &state, struct server_id_watch_state);
if (req == NULL) {
@@ -44,14 +46,15 @@ struct tevent_req *server_id_watch_send(TALLOC_CTX *mem_ctx,
}
state->ev = ev;
state->pid = pid;
+ state->start = tevent_timeval_current();
if (!serverid_exists(&state->pid)) {
tevent_req_done(req);
return tevent_req_post(req, ev);
}
- subreq = tevent_wakeup_send(
- state, ev, tevent_timeval_current_ofs(0, 500000));
+ next = tevent_timeval_add(&state->start, 0, 500000);
+ subreq = tevent_wakeup_send(state, ev, next);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
@@ -66,6 +69,8 @@ static void server_id_watch_waited(struct tevent_req *subreq)
subreq, struct tevent_req);
struct server_id_watch_state *state = tevent_req_data(
req, struct server_id_watch_state);
+ struct timeval now;
+ struct timeval next;
bool ok;
ok = tevent_wakeup_recv(subreq);
@@ -80,8 +85,9 @@ static void server_id_watch_waited(struct tevent_req *subreq)
return;
}
- subreq = tevent_wakeup_send(
- state, state->ev, tevent_timeval_current_ofs(0, 500000));
+ now = tevent_timeval_current();
+ next = tevent_timeval_add(&now, 0, 500000);
+ subreq = tevent_wakeup_send(state, state->ev, next);
if (tevent_req_nomem(subreq, req)) {
return;
}
--
2.53.0
From c25f1811c2ccaa2d5cc8005597fb9979aa1102ee Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 4 Apr 2024 12:31:05 +0200
Subject: [PATCH 053/122] s3/lib: add option "serverid watch:debug = yes" to
print kernel stack of hanging process
We only do if sys_have_proc_fds() returns true, so it's most likely
linux...
Enabled by default with log level 10...
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit 5c57e840527432c4b1a7ec94894939022a9e9622)
---
source3/lib/server_id_watch.c | 62 +++++++++++++++++++++++++++++++++--
1 file changed, 59 insertions(+), 3 deletions(-)
diff --git a/source3/lib/server_id_watch.c b/source3/lib/server_id_watch.c
index 50b35f27b3e..c372ec8c431 100644
--- a/source3/lib/server_id_watch.c
+++ b/source3/lib/server_id_watch.c
@@ -17,17 +17,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "replace.h"
-#include <tevent.h>
-#include <talloc.h>
+#include "includes.h"
#include "serverid.h"
#include "server_id_watch.h"
+#include "lib/util/server_id.h"
#include "lib/util/tevent_unix.h"
struct server_id_watch_state {
struct tevent_context *ev;
struct server_id pid;
struct timeval start;
+ struct timeval warn;
+ bool debug;
};
static void server_id_watch_waited(struct tevent_req *subreq);
@@ -47,6 +48,12 @@ struct tevent_req *server_id_watch_send(TALLOC_CTX *mem_ctx,
state->ev = ev;
state->pid = pid;
state->start = tevent_timeval_current();
+ state->warn = tevent_timeval_add(&state->start, 10, 0);
+
+ state->debug = lp_parm_bool(GLOBAL_SECTION_SNUM,
+ "serverid watch",
+ "debug",
+ CHECK_DEBUGLVL(DBGLVL_DEBUG));
if (!serverid_exists(&state->pid)) {
tevent_req_done(req);
@@ -86,6 +93,55 @@ static void server_id_watch_waited(struct tevent_req *subreq)
}
now = tevent_timeval_current();
+
+ if (!state->debug) {
+ goto next;
+ }
+
+ if (timeval_compare(&state->warn, &now) == -1) {
+ double duration = timeval_elapsed2(&state->start, &now);
+ char proc_path[64] = { 0, };
+ char *kstack = NULL;
+ struct server_id_buf buf;
+ const char *pid = server_id_str_buf(state->pid, &buf);
+ int ret;
+
+ state->warn = tevent_timeval_add(&now, 10, 0);
+
+ if (!procid_is_local(&state->pid) || !sys_have_proc_fds()) {
+ DBG_ERR("Process %s hanging for %f seconds?\n",
+ pid, duration);
+ goto next;
+ }
+
+ ret = snprintf(proc_path,
+ ARRAY_SIZE(proc_path),
+ "/proc/%" PRIu64 "/stack",
+ state->pid.pid);
+ if (ret < 0) {
+ DBG_ERR("Process %s hanging for %f seconds?\n"
+ "snprintf failed\n",
+ pid, duration);
+ goto next;
+ }
+
+ become_root();
+ kstack = file_load(proc_path, NULL, 0, state);
+ unbecome_root();
+ if (kstack == NULL) {
+ DBG_ERR("Process %s hanging for %f seconds?\n"
+ "file_load [%s] failed\n",
+ pid, duration, proc_path);
+ goto next;
+ }
+
+ DBG_ERR("Process %s hanging for %f seconds?\n"
+ "%s:\n%s",
+ pid, duration, proc_path, kstack);
+ TALLOC_FREE(kstack);
+ }
+
+next:
next = tevent_timeval_add(&now, 0, 500000);
subreq = tevent_wakeup_send(state, state->ev, next);
if (tevent_req_nomem(subreq, req)) {
--
2.53.0
From 23dbf8f0317810d65e716a3c9b947c7a6549cb46 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 25 Apr 2024 15:17:08 +0200
Subject: [PATCH 054/122] s3/lib: add option "serverid watch:debug script"
This takes just PID and NODE:PID on a cluster.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit 7add7dbf1aee13b4d9ab70d1a5312c8ff30d9e00)
---
source3/lib/server_id_watch.c | 52 +++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/source3/lib/server_id_watch.c b/source3/lib/server_id_watch.c
index c372ec8c431..8ddf9c6b1c8 100644
--- a/source3/lib/server_id_watch.c
+++ b/source3/lib/server_id_watch.c
@@ -100,6 +100,7 @@ static void server_id_watch_waited(struct tevent_req *subreq)
if (timeval_compare(&state->warn, &now) == -1) {
double duration = timeval_elapsed2(&state->start, &now);
+ const char *cmd = NULL;
char proc_path[64] = { 0, };
char *kstack = NULL;
struct server_id_buf buf;
@@ -108,6 +109,57 @@ static void server_id_watch_waited(struct tevent_req *subreq)
state->warn = tevent_timeval_add(&now, 10, 0);
+ cmd = lp_parm_const_string(GLOBAL_SECTION_SNUM,
+ "serverid watch",
+ "debug script",
+ NULL);
+ if (cmd != NULL) {
+ char *cmdstr = NULL;
+ char *output = NULL;
+ int fd;
+
+ /*
+ * Note in a cluster setup pid will be
+ * a NOTE:PID like '1:3978365'
+ *
+ * Without clustering it is just '3978365'
+ */
+ cmdstr = talloc_asprintf(state, "%s %s", cmd, pid);
+ if (cmdstr == NULL) {
+ DBG_ERR("Process %s hanging for %f seconds?\n"
+ "talloc_asprintf failed\n",
+ pid, duration);
+ goto next;
+ }
+
+ become_root();
+ ret = smbrun(cmdstr, &fd, NULL);
+ unbecome_root();
+ if (ret != 0) {
+ DBG_ERR("Process %s hanging for %f seconds?\n"
+ "smbrun('%s') failed\n",
+ pid, duration, cmdstr);
+ TALLOC_FREE(cmdstr);
+ goto next;
+ }
+
+ output = fd_load(fd, NULL, 0, state);
+ close(fd);
+ if (output == NULL) {
+ DBG_ERR("Process %s hanging for %f seconds?\n"
+ "fd_load() of smbrun('%s') failed\n",
+ pid, duration, cmdstr);
+ TALLOC_FREE(cmdstr);
+ goto next;
+ }
+ DBG_ERR("Process %s hanging for %f seconds?\n"
+ "%s returned:\n%s",
+ pid, duration, cmdstr, output);
+ TALLOC_FREE(cmdstr);
+ TALLOC_FREE(output);
+ goto next;
+ }
+
if (!procid_is_local(&state->pid) || !sys_have_proc_fds()) {
DBG_ERR("Process %s hanging for %f seconds?\n",
pid, duration);
--
2.53.0
From 59975168627e4bfbd2e75a611cb8cb13019a7df3 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Fri, 5 Apr 2024 12:15:28 +0200
Subject: [PATCH 055/122] smbd: log share_mode_watch_recv() errors as errors
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit b45e78871aadca6ae33475bee890736838f44219)
---
source3/smbd/open.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index b9849f82396..da129119c7f 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -2969,8 +2969,9 @@ static void defer_open_done(struct tevent_req *req)
status = share_mode_watch_recv(req, NULL, NULL);
TALLOC_FREE(req);
if (!NT_STATUS_IS_OK(status)) {
- DEBUG(5, ("dbwrap_watched_watch_recv returned %s\n",
- nt_errstr(status)));
+ DBG_ERR("share_mode_watch_recv() returned %s, "
+ "rescheduling mid %" PRIu64 "\n",
+ nt_errstr(status), state->mid);
/*
* Even if it failed, retry anyway. TODO: We need a way to
* tell a re-scheduled open about that error.
--
2.53.0
From e619b72fe1b9c36963c452c1d102009b28e8e289 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 4 Apr 2024 19:18:19 +0200
Subject: [PATCH 056/122] smbd: add option "smbd lease break:debug hung procs"
By enabling this a process sending a lease break message to another process
holding a lease will start watching that process and if that process didn't
process the lease break within 10 seconds (cf server_id_watch_waited()), we log
a kernel stack backtrace of that process.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit d8613d7ee23c4e990285a387eb9ac2eeefff9749)
---
source3/smbd/open.c | 110 ++++++++++++++++++++++++++++++++++++++++----
1 file changed, 102 insertions(+), 8 deletions(-)
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index da129119c7f..4cc5190f690 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -38,6 +38,7 @@
#include "serverid.h"
#include "messages.h"
#include "source3/lib/dbwrap/dbwrap_watch.h"
+#include "source3/lib/server_id_watch.h"
#include "locking/leases_db.h"
#include "librpc/gen_ndr/ndr_leases_db.h"
#include "lib/util/time_basic.h"
@@ -2472,6 +2473,10 @@ static int map_lease_type_to_oplock(uint32_t lease_type)
return result;
}
+struct blocker_debug_state {
+ size_t num_blockers;
+};
+
struct delay_for_oplock_state {
struct files_struct *fsp;
const struct smb2_lease *lease;
@@ -2483,8 +2488,22 @@ struct delay_for_oplock_state {
bool have_other_lease;
uint32_t total_lease_types;
bool delay;
+ struct blocker_debug_state *blocker_debug_state;
};
+static int blocker_debug_state_destructor(struct blocker_debug_state *state)
+{
+ if (state->num_blockers == 0) {
+ return 0;
+ }
+
+ DBG_DEBUG("blocker_debug_state [%p] num_blockers [%zu]\n",
+ state, state->num_blockers);
+ return 0;
+}
+
+static void delay_for_oplock_fn_watch_done(struct tevent_req *subreq);
+
static bool delay_for_oplock_fn(
struct share_mode_entry *e,
bool *modified,
@@ -2497,6 +2516,8 @@ static bool delay_for_oplock_fn(
uint32_t e_lease_type = SMB2_LEASE_NONE;
uint32_t break_to;
bool lease_is_breaking = false;
+ struct tevent_req *subreq = NULL;
+ struct server_id_buf idbuf = {};
if (e_is_lease) {
NTSTATUS status;
@@ -2636,9 +2657,56 @@ static bool delay_for_oplock_fn(
state->delay = true;
}
+ if (!state->delay) {
+ return false;
+ }
+
+ if (state->blocker_debug_state == NULL) {
+ return false;
+ }
+
+ subreq = server_id_watch_send(state->blocker_debug_state,
+ fsp->conn->sconn->ev_ctx,
+ e->pid);
+ if (subreq == NULL) {
+ DBG_ERR("server_id_watch_send(%s) returned NULL\n",
+ server_id_str_buf(e->pid, &idbuf));
+ return false;
+ }
+
+ tevent_req_set_callback(subreq,
+ delay_for_oplock_fn_watch_done,
+ state->blocker_debug_state);
+
+ state->blocker_debug_state->num_blockers++;
+
+ DBG_DEBUG("Starting to watch pid [%s] state [%p] num_blockers [%zu]\n",
+ server_id_str_buf(e->pid, &idbuf),
+ state->blocker_debug_state,
+ state->blocker_debug_state->num_blockers);
+
return false;
};
+static void delay_for_oplock_fn_watch_done(struct tevent_req *subreq)
+{
+ struct blocker_debug_state *blocker_debug_state = tevent_req_callback_data(
+ subreq, struct blocker_debug_state);
+ struct server_id pid = {};
+ struct server_id_buf idbuf = {};
+ int ret;
+
+ ret = server_id_watch_recv(subreq, &pid);
+ if (ret != 0) {
+ DBG_ERR("server_id_watch_recv failed %s\n", strerror(ret));
+ return;
+ }
+
+ DBG_DEBUG("state [%p] server_id_watch_recv() returned pid [%s] exited\n",
+ blocker_debug_state,
+ server_id_str_buf(pid, &idbuf));
+}
+
static NTSTATUS delay_for_oplock(files_struct *fsp,
int oplock_request,
const struct smb2_lease *lease,
@@ -2647,7 +2715,8 @@ static NTSTATUS delay_for_oplock(files_struct *fsp,
uint32_t create_disposition,
bool first_open_attempt,
int *poplock_type,
- uint32_t *pgranted)
+ uint32_t *pgranted,
+ struct blocker_debug_state **blocker_debug_state)
{
struct delay_for_oplock_state state = {
.fsp = fsp,
@@ -2693,6 +2762,22 @@ static NTSTATUS delay_for_oplock(files_struct *fsp,
goto grant;
}
+ if (lp_parm_bool(GLOBAL_SECTION_SNUM,
+ "smbd lease break",
+ "debug hung procs",
+ false))
+ {
+ state.blocker_debug_state = talloc_zero(fsp,
+ struct blocker_debug_state);
+ if (state.blocker_debug_state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_steal(talloc_tos(), state.blocker_debug_state);
+
+ talloc_set_destructor(state.blocker_debug_state,
+ blocker_debug_state_destructor);
+ }
+
state.delay_mask = have_sharing_violation ?
SMB2_LEASE_HANDLE : SMB2_LEASE_WRITE;
@@ -2714,6 +2799,7 @@ static NTSTATUS delay_for_oplock(files_struct *fsp,
}
if (state.delay) {
+ *blocker_debug_state = state.blocker_debug_state;
return NT_STATUS_RETRY;
}
@@ -2827,7 +2913,8 @@ static NTSTATUS handle_share_mode_lease(
const struct smb2_lease *lease,
bool first_open_attempt,
int *poplock_type,
- uint32_t *pgranted)
+ uint32_t *pgranted,
+ struct blocker_debug_state **blocker_debug_state)
{
bool sharing_violation = false;
NTSTATUS status;
@@ -2868,7 +2955,8 @@ static NTSTATUS handle_share_mode_lease(
create_disposition,
first_open_attempt,
poplock_type,
- pgranted);
+ pgranted,
+ blocker_debug_state);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
@@ -2903,7 +2991,8 @@ static void defer_open_done(struct tevent_req *req);
static void defer_open(struct share_mode_lock *lck,
struct timeval timeout,
struct smb_request *req,
- struct file_id id)
+ struct file_id id,
+ struct blocker_debug_state **blocker_debug_state)
{
struct deferred_open_record *open_rec = NULL;
struct timeval abs_timeout;
@@ -2947,6 +3036,8 @@ static void defer_open(struct share_mode_lock *lck,
}
tevent_req_set_callback(watch_req, defer_open_done, watch_state);
+ talloc_move(watch_req, blocker_debug_state);
+
ok = tevent_req_set_endtime(watch_req, req->sconn->ev_ctx, abs_timeout);
if (!ok) {
exit_server("tevent_req_set_endtime failed");
@@ -3229,7 +3320,8 @@ static bool open_match_attributes(connection_struct *conn,
static void schedule_defer_open(struct share_mode_lock *lck,
struct file_id id,
- struct smb_request *req)
+ struct smb_request *req,
+ struct blocker_debug_state **blocker_debug_state)
{
/* This is a relative time, added to the absolute
request_time value to get the absolute timeout time.
@@ -3253,7 +3345,7 @@ static void schedule_defer_open(struct share_mode_lock *lck,
return;
}
- defer_open(lck, timeout, req, id);
+ defer_open(lck, timeout, req, id, blocker_debug_state);
}
/****************************************************************************
@@ -3315,6 +3407,7 @@ static NTSTATUS check_and_store_share_mode(
int oplock_type = NO_OPLOCK;
uint32_t granted_lease = 0;
const struct smb2_lease_key *lease_key = NULL;
+ struct blocker_debug_state *blocker_debug_state = NULL;
bool delete_on_close;
bool ok;
@@ -3337,9 +3430,10 @@ static NTSTATUS check_and_store_share_mode(
lease,
first_open_attempt,
&oplock_type,
- &granted_lease);
+ &granted_lease,
+ &blocker_debug_state);
if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
- schedule_defer_open(lck, fsp->file_id, req);
+ schedule_defer_open(lck, fsp->file_id, req, &blocker_debug_state);
return NT_STATUS_SHARING_VIOLATION;
}
if (!NT_STATUS_IS_OK(status)) {
--
2.53.0
From e6a0d821ba28839728371ca94bb364dd6865b5dd Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Wed, 20 Mar 2024 14:27:27 +0100
Subject: [PATCH 057/122] smbd: move trace_state variable behind tv variable
Next commit adds timestamp variables to trace_state that want to be initialized
with the current time, so moving behind tv we can then just reuse tv for that.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit 679e12aee2f0c283a6f9b9c6008c549a6ca9633e)
---
source3/smbd/smb2_process.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/source3/smbd/smb2_process.c b/source3/smbd/smb2_process.c
index fbbe4ef3992..188eaa14839 100644
--- a/source3/smbd/smb2_process.c
+++ b/source3/smbd/smb2_process.c
@@ -1783,10 +1783,6 @@ void smbd_process(struct tevent_context *ev_ctx,
int sock_fd,
bool interactive)
{
- struct smbd_tevent_trace_state trace_state = {
- .ev = ev_ctx,
- .frame = talloc_stackframe(),
- };
const struct loadparm_substitution *lp_sub =
loadparm_s3_global_substitution();
struct smbXsrv_client *client = NULL;
@@ -1797,6 +1793,10 @@ void smbd_process(struct tevent_context *ev_ctx,
int ret;
NTSTATUS status;
struct timeval tv = timeval_current();
+ struct smbd_tevent_trace_state trace_state = {
+ .ev = ev_ctx,
+ .frame = talloc_stackframe(),
+ };
NTTIME now = timeval_to_nttime(&tv);
char *chroot_dir = NULL;
int rc;
--
2.53.0
From 15276d7645255ddddf2a3bf6b7a429e3d40ec9b7 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Wed, 20 Mar 2024 14:28:43 +0100
Subject: [PATCH 058/122] smbd: add option "smbd:debug events" for tevent
handling duration threshold warnings
Can be used to enable printing an error message if tevent event handlers ran
longer then three seconds. Also logs a message with a loglevel of 3 if there
were no events at hall.
Enabled by default with 'log level = 10' or
'smbd profiling level = on'...
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit 90d776cb18395ed804f0ab4fd13ef571fc0ad827)
---
source3/smbd/smb2_process.c | 64 +++++++++++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)
diff --git a/source3/smbd/smb2_process.c b/source3/smbd/smb2_process.c
index 188eaa14839..dbe91132f7f 100644
--- a/source3/smbd/smb2_process.c
+++ b/source3/smbd/smb2_process.c
@@ -1692,8 +1692,36 @@ struct smbd_tevent_trace_state {
struct tevent_context *ev;
TALLOC_CTX *frame;
SMBPROFILE_BASIC_ASYNC_STATE(profile_idle);
+ struct timeval before_wait_tv;
+ struct timeval after_wait_tv;
};
+static inline void smbd_tevent_trace_callback_before_wait(
+ struct smbd_tevent_trace_state *state)
+{
+ struct timeval now = timeval_current();
+ struct timeval diff;
+
+ diff = tevent_timeval_until(&state->after_wait_tv, &now);
+ if (diff.tv_sec > 3) {
+ DBG_ERR("Handling event took %ld seconds!\n", (long)diff.tv_sec);
+ }
+ state->before_wait_tv = now;
+}
+
+static inline void smbd_tevent_trace_callback_after_wait(
+ struct smbd_tevent_trace_state *state)
+{
+ struct timeval now = timeval_current();
+ struct timeval diff;
+
+ diff = tevent_timeval_until(&state->before_wait_tv, &now);
+ if (diff.tv_sec > 30) {
+ DBG_NOTICE("No event for %ld seconds!\n", (long)diff.tv_sec);
+ }
+ state->after_wait_tv = now;
+}
+
static inline void smbd_tevent_trace_callback_before_loop_once(
struct smbd_tevent_trace_state *state)
{
@@ -1729,6 +1757,30 @@ static void smbd_tevent_trace_callback(enum tevent_trace_point point,
errno = 0;
}
+static void smbd_tevent_trace_callback_debug(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct smbd_tevent_trace_state *state =
+ (struct smbd_tevent_trace_state *)private_data;
+
+ switch (point) {
+ case TEVENT_TRACE_BEFORE_WAIT:
+ smbd_tevent_trace_callback_before_wait(state);
+ break;
+ case TEVENT_TRACE_AFTER_WAIT:
+ smbd_tevent_trace_callback_after_wait(state);
+ break;
+ case TEVENT_TRACE_BEFORE_LOOP_ONCE:
+ smbd_tevent_trace_callback_before_loop_once(state);
+ break;
+ case TEVENT_TRACE_AFTER_LOOP_ONCE:
+ smbd_tevent_trace_callback_after_loop_once(state);
+ break;
+ }
+
+ errno = 0;
+}
+
static void smbd_tevent_trace_callback_profile(enum tevent_trace_point point,
void *private_data)
{
@@ -1737,6 +1789,7 @@ static void smbd_tevent_trace_callback_profile(enum tevent_trace_point point,
switch (point) {
case TEVENT_TRACE_BEFORE_WAIT:
+ smbd_tevent_trace_callback_before_wait(state);
if (!smbprofile_dump_pending()) {
/*
* If there's no dump pending
@@ -1749,6 +1802,7 @@ static void smbd_tevent_trace_callback_profile(enum tevent_trace_point point,
SMBPROFILE_BASIC_ASYNC_START(idle, profile_p, state->profile_idle);
break;
case TEVENT_TRACE_AFTER_WAIT:
+ smbd_tevent_trace_callback_after_wait(state);
SMBPROFILE_BASIC_ASYNC_END(state->profile_idle);
if (!smbprofile_dump_pending()) {
/*
@@ -1796,7 +1850,13 @@ void smbd_process(struct tevent_context *ev_ctx,
struct smbd_tevent_trace_state trace_state = {
.ev = ev_ctx,
.frame = talloc_stackframe(),
+ .before_wait_tv = tv,
+ .after_wait_tv = tv,
};
+ bool debug = lp_parm_bool(GLOBAL_SECTION_SNUM,
+ "smbd",
+ "debug events",
+ CHECK_DEBUGLVL(DBGLVL_DEBUG));
NTTIME now = timeval_to_nttime(&tv);
char *chroot_dir = NULL;
int rc;
@@ -2041,6 +2101,10 @@ void smbd_process(struct tevent_context *ev_ctx,
tevent_set_trace_callback(ev_ctx,
smbd_tevent_trace_callback_profile,
&trace_state);
+ } else if (debug) {
+ tevent_set_trace_callback(ev_ctx,
+ smbd_tevent_trace_callback_debug,
+ &trace_state);
} else {
tevent_set_trace_callback(ev_ctx,
smbd_tevent_trace_callback,
--
2.53.0
From 4631b9d60a874db10dbdd52406d0094a7dbd1356 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 26 Aug 2024 14:11:02 +0200
Subject: [PATCH 059/122] vfs_error_inject: add 'error_inject:durable_reconnect
= st_ex_nlink'
This allows to simulate durable reconnect failures because the stat
information of the file changed.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit 692ed832dfff61ad1c9b646b5c8d6f85f25efb99)
---
source3/modules/vfs_error_inject.c | 76 ++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/source3/modules/vfs_error_inject.c b/source3/modules/vfs_error_inject.c
index 529504fd8d5..dcf0de0a2d9 100644
--- a/source3/modules/vfs_error_inject.c
+++ b/source3/modules/vfs_error_inject.c
@@ -19,6 +19,7 @@
#include "includes.h"
#include "smbd/smbd.h"
+#include "librpc/gen_ndr/ndr_open_files.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_VFS
@@ -204,11 +205,86 @@ static int vfs_error_inject_unlinkat(struct vfs_handle_struct *handle,
return -1;
}
+static NTSTATUS vfs_error_inject_durable_reconnect(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie)
+{
+ const char *vfs_func = "durable_reconnect";
+ const char *err_str = NULL;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+ DATA_BLOB modified_cookie = data_blob_null;
+
+ err_str = lp_parm_const_string(SNUM(handle->conn),
+ "error_inject",
+ vfs_func,
+ NULL);
+ if (err_str == NULL) {
+ return SMB_VFS_NEXT_DURABLE_RECONNECT(handle,
+ smb1req,
+ op,
+ old_cookie,
+ mem_ctx,
+ fsp,
+ new_cookie);
+ }
+
+ ndr_err = ndr_pull_struct_blob(&old_cookie, talloc_tos(), &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (strequal(err_str, "st_ex_nlink")) {
+ cookie.stat_info.st_ex_nlink += 1;
+ } else {
+ DBG_ERR("Unknown error inject %s requested "
+ "for vfs function %s\n", err_str, vfs_func);
+ return SMB_VFS_NEXT_DURABLE_RECONNECT(handle,
+ smb1req,
+ op,
+ old_cookie,
+ mem_ctx,
+ fsp,
+ new_cookie);
+ }
+
+ ndr_err = ndr_push_struct_blob(&modified_cookie, talloc_tos(), &cookie,
+ (ndr_push_flags_fn_t)ndr_push_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ return SMB_VFS_NEXT_DURABLE_RECONNECT(handle,
+ smb1req,
+ op,
+ modified_cookie,
+ mem_ctx,
+ fsp,
+ new_cookie);
+}
+
static struct vfs_fn_pointers vfs_error_inject_fns = {
.chdir_fn = vfs_error_inject_chdir,
.pwrite_fn = vfs_error_inject_pwrite,
.openat_fn = vfs_error_inject_openat,
.unlinkat_fn = vfs_error_inject_unlinkat,
+ .durable_reconnect_fn = vfs_error_inject_durable_reconnect,
};
static_decl_vfs;
--
2.53.0
From c8e88652163cc56b1f9fb0926a140c81e6b7ec94 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 26 Aug 2024 14:42:02 +0200
Subject: [PATCH 060/122] s4:torture/smb2: add
smb2.durable-v2-regressions.durable_v2_reconnect_bug15624
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit ef4ef04e7f83b1029446ff8b5fc5fdf4ab33edbd)
---
selftest/skip | 1 +
source4/torture/smb2/durable_v2_open.c | 118 +++++++++++++++++++++++++
source4/torture/smb2/smb2.c | 2 +
3 files changed, 121 insertions(+)
diff --git a/selftest/skip b/selftest/skip
index e808367c00d..056c54ea287 100644
--- a/selftest/skip
+++ b/selftest/skip
@@ -149,3 +149,4 @@ bench # don't run benchmarks in our selftest
^samba.tests.reparsepoints.*
^samba.tests.smb2symlink.*
^samba3.blackbox.open-eintr.*
+smb2.durable-v2-regressions # Only used in blackbox tests
diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c
index 9b9af11124c..7447dd287a4 100644
--- a/source4/torture/smb2/durable_v2_open.c
+++ b/source4/torture/smb2/durable_v2_open.c
@@ -2355,6 +2355,112 @@ done:
return ret;
}
+/**
+ * basic test for doing a durable open
+ * tcp disconnect, reconnect, do a durable reopen (succeeds)
+ */
+static bool test_durable_v2_reconnect_bug15624(struct torture_context *tctx,
+ struct smb2_tree *tree,
+ struct smb2_tree *tree2)
+{
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ char fname[256];
+ struct smb2_handle _h;
+ struct smb2_handle *h = NULL;
+ struct smb2_create io;
+ struct GUID create_guid = GUID_random();
+ struct smbcli_options options;
+ uint64_t previous_session_id;
+ uint8_t b = 0;
+ bool ret = true;
+ bool ok;
+
+ if (!torture_setting_bool(tctx, "bug15624", false)) {
+ torture_comment(tctx,
+ "share requires:\n"
+ "'vfs objects = error_inject'\n"
+ "'error_inject:durable_reconnect=st_ex_nlink'\n"
+ "test requires:\n"
+ "'--option=torture:bug15624=yes'\n");
+ torture_skip(tctx, "'--option=torture:bug15624=yes' missing");
+ }
+
+ options = tree->session->transport->options;
+ previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname,
+ sizeof(fname),
+ "durable_v2_reconnect_bug15624_%s.dat",
+ generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree, fname);
+
+ smb2_oplock_create_share(&io, fname,
+ smb2_util_share_access(""),
+ smb2_util_oplock_level("b"));
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid;
+ io.in.timeout = 0;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ _h = io.out.file.handle;
+ h = &_h;
+ CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+ CHECK_VAL(io.out.durable_open_v2, true);
+
+ status = smb2_util_write(tree, *h, &b, 0, 1);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ /* disconnect, leaving the durable open */
+ TALLOC_FREE(tree);
+ h = NULL;
+
+ ok = torture_smb2_connection_ext(tctx, previous_session_id,
+ &options, &tree);
+ torture_assert_goto(tctx, ok, ret, done, "couldn't reconnect, bailing\n");
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = &_h;
+ io.in.create_guid = create_guid;
+
+ /*
+ * This assumes 'error_inject:durable_reconnect = st_ex_nlink'
+ * will cause the durable reconnect to fail...
+ * in order to have a regression test for the dead lock.
+ */
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+ /*
+ * With the regression this will fail with
+ * a timeout...
+ */
+ status = smb2_util_unlink(tree2, fname);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+done:
+ if (h != NULL) {
+ smb2_util_close(tree, *h);
+ }
+ TALLOC_FREE(tree);
+
+ smb2_util_unlink(tree2, fname);
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
struct torture_suite *torture_smb2_durable_v2_delay_init(TALLOC_CTX *ctx)
{
struct torture_suite *suite =
@@ -2369,3 +2475,15 @@ struct torture_suite *torture_smb2_durable_v2_delay_init(TALLOC_CTX *ctx)
return suite;
}
+
+struct torture_suite *torture_smb2_durable_v2_regressions_init(TALLOC_CTX *ctx)
+{
+ struct torture_suite *suite =
+ torture_suite_create(ctx, "durable-v2-regressions");
+
+ torture_suite_add_2smb2_test(suite,
+ "durable_v2_reconnect_bug15624",
+ test_durable_v2_reconnect_bug15624);
+
+ return suite;
+}
diff --git a/source4/torture/smb2/smb2.c b/source4/torture/smb2/smb2.c
index 5b6477e47bc..9cf7f5da78b 100644
--- a/source4/torture/smb2/smb2.c
+++ b/source4/torture/smb2/smb2.c
@@ -170,6 +170,8 @@ NTSTATUS torture_smb2_init(TALLOC_CTX *ctx)
torture_smb2_durable_v2_open_init(suite));
torture_suite_add_suite(suite,
torture_smb2_durable_v2_delay_init(suite));
+ torture_suite_add_suite(suite,
+ torture_smb2_durable_v2_regressions_init(suite));
torture_suite_add_suite(suite, torture_smb2_dir_init(suite));
torture_suite_add_suite(suite, torture_smb2_lease_init(suite));
torture_suite_add_suite(suite, torture_smb2_compound_init(suite));
--
2.53.0
From 56a3aaf95c44052b19b61115686c71d5b7dbab4a Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 26 Aug 2024 14:42:12 +0200
Subject: [PATCH 061/122] s3:tests: let test_durable_handle_reconnect.sh run
smb2.durable-v2-regressions.durable_v2_reconnect_bug15624
This demonstrates the dead lock after a durable reconnect failed
because the stat info changed, the file can't be accessed anymore
as we leak the incomplete share mode entry in a still running
process.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit 14875448ca06a3a28800343a3a326f1a66bccec0)
---
.../samba3.blackbox.durable_v2_delay | 1 +
.../tests/test_durable_handle_reconnect.sh | 18 ++++++++++++++++++
2 files changed, 19 insertions(+)
create mode 100644 selftest/knownfail.d/samba3.blackbox.durable_v2_delay
diff --git a/selftest/knownfail.d/samba3.blackbox.durable_v2_delay b/selftest/knownfail.d/samba3.blackbox.durable_v2_delay
new file mode 100644
index 00000000000..88e29960797
--- /dev/null
+++ b/selftest/knownfail.d/samba3.blackbox.durable_v2_delay
@@ -0,0 +1 @@
+^samba3.blackbox.durable_v2_delay.durable-v2-regressions.durable_v2_reconnect_bug15624
diff --git a/source3/script/tests/test_durable_handle_reconnect.sh b/source3/script/tests/test_durable_handle_reconnect.sh
index 0ab32974824..fd5c156956f 100755
--- a/source3/script/tests/test_durable_handle_reconnect.sh
+++ b/source3/script/tests/test_durable_handle_reconnect.sh
@@ -33,4 +33,22 @@ testit "durable_v2_delay.durable_v2_reconnect_delay_msec" $VALGRIND \
rm $delay_inject_conf
+error_inject_conf=$(dirname $SMB_CONF_PATH)/error_inject.conf
+
+cat > $error_inject_conf << _EOF
+ kernel share modes = no
+ kernel oplocks = no
+ posix locking = no
+ error_inject:durable_reconnect = st_ex_nlink
+_EOF
+
+testit "durable-v2-regressions.durable_v2_reconnect_bug15624" \
+ $VALGRIND $BINDIR/smbtorture //$SERVER_IP/error_inject \
+ -U$USERNAME%$PASSWORD \
+ --option=torture:bug15624=yes \
+ smb2.durable-v2-regressions.durable_v2_reconnect_bug15624 ||
+ failed=$(expr $failed + 1)
+
+rm $error_inject_conf
+
testok $0 $failed
--
2.53.0
From d8f01885145ecfce15f2507fdcc625442db1738c Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 9 Apr 2024 14:52:44 +0200
Subject: [PATCH 062/122] smbd: consolidate DH reconnect failure code
No change in behaviour, except that we now
also call fd_close() if vfs_default_durable_cookie()
failed.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
(cherry picked from commit a91457f97c98fcec1ed062514c364271af1df669)
---
source3/smbd/durable.c | 142 ++++++++++++++---------------------------
1 file changed, 47 insertions(+), 95 deletions(-)
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
index b21c223b2e4..50075ddd3f7 100644
--- a/source3/smbd/durable.c
+++ b/source3/smbd/durable.c
@@ -624,22 +624,22 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
ok = share_mode_forall_entries(lck, durable_reconnect_fn, &e);
if (!ok) {
DBG_WARNING("share_mode_forall_entries failed\n");
- TALLOC_FREE(lck);
- return NT_STATUS_INTERNAL_DB_ERROR;
+ status = NT_STATUS_INTERNAL_DB_ERROR;
+ goto fail;
}
if (e.pid.pid == 0) {
DBG_WARNING("Did not find a unique valid share mode entry\n");
- TALLOC_FREE(lck);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
}
if (!server_id_is_disconnected(&e.pid)) {
DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
"reconnect for handle that was not marked "
"disconnected (e.g. smbd or cluster node died)\n"));
- TALLOC_FREE(lck);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
}
if (e.share_file_id != op->global->open_persistent_id) {
@@ -648,8 +648,8 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
"(e.g. another client had opened the file)\n",
e.share_file_id,
op->global->open_persistent_id);
- TALLOC_FREE(lck);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
}
if ((e.access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) &&
@@ -658,8 +658,8 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
"share[%s] is not writeable anymore\n",
lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
- TALLOC_FREE(lck);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
}
/*
@@ -670,8 +670,7 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("vfs_default_durable_reconnect: failed to create "
"new fsp: %s\n", nt_errstr(status)));
- TALLOC_FREE(lck);
- return status;
+ goto fail;
}
fh_set_private_options(fsp->fh, e.private_options);
@@ -714,9 +713,8 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
*/
if (!GUID_equal(fsp_client_guid(fsp),
&e.client_guid)) {
- TALLOC_FREE(lck);
- file_free(smb1req, fsp);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
}
status = leases_db_get(
@@ -730,9 +728,7 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
&lease_version, /* lease_version */
&epoch); /* epoch */
if (!NT_STATUS_IS_OK(status)) {
- TALLOC_FREE(lck);
- file_free(smb1req, fsp);
- return status;
+ goto fail;
}
fsp->lease = find_fsp_lease(
@@ -742,9 +738,8 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
lease_version,
epoch);
if (fsp->lease == NULL) {
- TALLOC_FREE(lck);
- file_free(smb1req, fsp);
- return NT_STATUS_NO_MEMORY;
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
}
}
@@ -760,12 +755,10 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
status = fsp_set_smb_fname(fsp, smb_fname);
if (!NT_STATUS_IS_OK(status)) {
- TALLOC_FREE(lck);
- file_free(smb1req, fsp);
DEBUG(0, ("vfs_default_durable_reconnect: "
"fsp_set_smb_fname failed: %s\n",
nt_errstr(status)));
- return status;
+ goto fail;
}
op->compat = fsp;
@@ -780,11 +773,8 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
fh_get_gen_id(fsp->fh));
if (!ok) {
DBG_DEBUG("Could not set new share_mode_entry values\n");
- TALLOC_FREE(lck);
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return NT_STATUS_INTERNAL_ERROR;
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
}
ok = brl_reconnect_disconnected(fsp);
@@ -793,11 +783,7 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
DEBUG(1, ("vfs_default_durable_reconnect: "
"failed to reopen brlocks: %s\n",
nt_errstr(status)));
- TALLOC_FREE(lck);
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return status;
+ goto fail;
}
/*
@@ -813,13 +799,9 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
if (!NT_STATUS_IS_OK(status)) {
- TALLOC_FREE(lck);
DEBUG(1, ("vfs_default_durable_reconnect: failed to open "
"file: %s\n", nt_errstr(status)));
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return status;
+ goto fail;
}
/*
@@ -833,48 +815,22 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
if (ret == -1) {
- NTSTATUS close_status;
status = map_nt_error_from_unix_common(errno);
DEBUG(1, ("Unable to fstat stream: %s => %s\n",
smb_fname_str_dbg(smb_fname),
nt_errstr(status)));
- close_status = fd_close(fsp);
- if (!NT_STATUS_IS_OK(close_status)) {
- DBG_ERR("fd_close failed (%s) - leaking file "
- "descriptor\n", nt_errstr(close_status));
- }
- TALLOC_FREE(lck);
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return status;
+ goto fail;
}
if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
- NTSTATUS close_status = fd_close(fsp);
- if (!NT_STATUS_IS_OK(close_status)) {
- DBG_ERR("fd_close failed (%s) - leaking file "
- "descriptor\n", nt_errstr(close_status));
- }
- TALLOC_FREE(lck);
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
}
file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
if (!file_id_equal(&cookie.id, &file_id)) {
- NTSTATUS close_status = fd_close(fsp);
- if (!NT_STATUS_IS_OK(close_status)) {
- DBG_ERR("fd_close failed (%s) - leaking file "
- "descriptor\n", nt_errstr(close_status));
- }
- TALLOC_FREE(lck);
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
}
(void)fdos_mode(fsp);
@@ -883,42 +839,21 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
&fsp->fsp_name->st,
fsp_str_dbg(fsp));
if (!ok) {
- NTSTATUS close_status = fd_close(fsp);
- if (!NT_STATUS_IS_OK(close_status)) {
- DBG_ERR("fd_close failed (%s) - leaking file "
- "descriptor\n", nt_errstr(close_status));
- }
- TALLOC_FREE(lck);
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
}
status = set_file_oplock(fsp);
if (!NT_STATUS_IS_OK(status)) {
- NTSTATUS close_status = fd_close(fsp);
- if (!NT_STATUS_IS_OK(close_status)) {
- DBG_ERR("fd_close failed (%s) - leaking file "
- "descriptor\n", nt_errstr(close_status));
- }
- TALLOC_FREE(lck);
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return status;
+ goto fail;
}
status = vfs_default_durable_cookie(fsp, mem_ctx, &new_cookie_blob);
if (!NT_STATUS_IS_OK(status)) {
- TALLOC_FREE(lck);
DEBUG(1, ("vfs_default_durable_reconnect: "
"vfs_default_durable_cookie - %s\n",
nt_errstr(status)));
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
- return status;
+ goto fail;
}
smb1req->chain_fsp = fsp;
@@ -935,4 +870,21 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
*new_cookie = new_cookie_blob;
return NT_STATUS_OK;
+
+fail:
+ if (fsp != NULL && fsp_get_pathref_fd(fsp) != -1) {
+ NTSTATUS close_status;
+ close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s), leaking fd\n",
+ nt_errstr(close_status));
+ }
+ }
+ TALLOC_FREE(lck);
+ if (fsp != NULL) {
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ }
+ return status;
}
--
2.53.0
From b248ddd3dd7193ba44c9ad86488dd180a25e3774 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 9 Apr 2024 14:53:32 +0200
Subject: [PATCH 063/122] smbd: remove just created sharemode entry in the
error codepaths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Without this we leave stale sharemode entries around that can lead to all sorts
of havoc.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Guenther Deschner <gd@samba.org>
Autobuild-User(master): Günther Deschner <gd@samba.org>
Autobuild-Date(master): Thu Sep 19 19:36:19 UTC 2024 on atb-devel-224
(cherry picked from commit 2ff3b9bc0d254a63a913ff9084de3d794fee27d0)
---
selftest/knownfail.d/samba3.blackbox.durable_v2_delay | 1 -
source3/smbd/durable.c | 8 ++++++++
2 files changed, 8 insertions(+), 1 deletion(-)
delete mode 100644 selftest/knownfail.d/samba3.blackbox.durable_v2_delay
diff --git a/selftest/knownfail.d/samba3.blackbox.durable_v2_delay b/selftest/knownfail.d/samba3.blackbox.durable_v2_delay
deleted file mode 100644
index 88e29960797..00000000000
--- a/selftest/knownfail.d/samba3.blackbox.durable_v2_delay
+++ /dev/null
@@ -1 +0,0 @@
-^samba3.blackbox.durable_v2_delay.durable-v2-regressions.durable_v2_reconnect_bug15624
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
index 50075ddd3f7..98d0d403e30 100644
--- a/source3/smbd/durable.c
+++ b/source3/smbd/durable.c
@@ -538,6 +538,7 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
enum ndr_err_code ndr_err;
struct vfs_default_durable_cookie cookie;
DATA_BLOB new_cookie_blob = data_blob_null;
+ bool have_share_mode_entry = false;
*result = NULL;
*new_cookie = data_blob_null;
@@ -776,6 +777,7 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
status = NT_STATUS_INTERNAL_ERROR;
goto fail;
}
+ have_share_mode_entry = true;
ok = brl_reconnect_disconnected(fsp);
if (!ok) {
@@ -872,6 +874,12 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
return NT_STATUS_OK;
fail:
+ if (fsp != NULL && have_share_mode_entry) {
+ /*
+ * Something is screwed up, delete the sharemode entry.
+ */
+ del_share_mode(lck, fsp);
+ }
if (fsp != NULL && fsp_get_pathref_fd(fsp) != -1) {
NTSTATUS close_status;
close_status = fd_close(fsp);
--
2.53.0
From 67ff429e41004899e514d893e80332de79ca2bab Mon Sep 17 00:00:00 2001
From: Earl Chew <earl_chew@yahoo.com>
Date: Sun, 17 Dec 2023 08:37:33 -0800
Subject: [PATCH 064/122] Augment library_flags() to return libraries
Extend library_flags() to return the libraries provided by
pkg-config --libs.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15623
Signed-off-by: Earl Chew <earl_chew@yahoo.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
(cherry picked from commit 363c33185779141fdfbda695997d548939a0251f)
---
buildtools/wafsamba/samba3.py | 8 ++++----
buildtools/wafsamba/samba_autoconf.py | 22 +++++++++++++---------
buildtools/wafsamba/samba_deps.py | 5 ++---
lib/util/charset/wscript_configure | 2 +-
4 files changed, 20 insertions(+), 17 deletions(-)
diff --git a/buildtools/wafsamba/samba3.py b/buildtools/wafsamba/samba3.py
index 227ee27705d..ba0783f0d22 100644
--- a/buildtools/wafsamba/samba3.py
+++ b/buildtools/wafsamba/samba3.py
@@ -45,25 +45,25 @@ def s3_fix_kwargs(bld, kwargs):
'../bin/default/third_party/heimdal/lib/asn1' ]
if bld.CONFIG_SET('USING_SYSTEM_TDB'):
- (tdb_includes, tdb_ldflags, tdb_cpppath) = library_flags(bld, 'tdb')
+ (tdb_includes, tdb_ldflags, tdb_cpppath, tdb_libs) = library_flags(bld, 'tdb')
extra_includes += tdb_cpppath
else:
extra_includes += [ '../lib/tdb/include' ]
if bld.CONFIG_SET('USING_SYSTEM_TEVENT'):
- (tevent_includes, tevent_ldflags, tevent_cpppath) = library_flags(bld, 'tevent')
+ (tevent_includes, tevent_ldflags, tevent_cpppath, tevent_libs) = library_flags(bld, 'tevent')
extra_includes += tevent_cpppath
else:
extra_includes += [ '../lib/tevent' ]
if bld.CONFIG_SET('USING_SYSTEM_TALLOC'):
- (talloc_includes, talloc_ldflags, talloc_cpppath) = library_flags(bld, 'talloc')
+ (talloc_includes, talloc_ldflags, talloc_cpppath, talloc_libs) = library_flags(bld, 'talloc')
extra_includes += talloc_cpppath
else:
extra_includes += [ '../lib/talloc' ]
if bld.CONFIG_SET('USING_SYSTEM_POPT'):
- (popt_includes, popt_ldflags, popt_cpppath) = library_flags(bld, 'popt')
+ (popt_includes, popt_ldflags, popt_cpppath, popt_libs) = library_flags(bld, 'popt')
extra_includes += popt_cpppath
else:
extra_includes += [ '../lib/popt' ]
diff --git a/buildtools/wafsamba/samba_autoconf.py b/buildtools/wafsamba/samba_autoconf.py
index 34fd5fab2c0..d3b6503c5ca 100644
--- a/buildtools/wafsamba/samba_autoconf.py
+++ b/buildtools/wafsamba/samba_autoconf.py
@@ -91,7 +91,7 @@ def CHECK_HEADER(conf, h, add_headers=False, lib=None):
conf.env.hlist.append(h)
return True
- (ccflags, ldflags, cpppath) = library_flags(conf, lib)
+ (ccflags, ldflags, cpppath, libs) = library_flags(conf, lib)
hdrs = hlist_to_string(conf, headers=h)
if lib is None:
@@ -435,7 +435,7 @@ def CHECK_CODE(conf, code, define,
uselib = TO_LIST(lib)
- (ccflags, ldflags, cpppath) = library_flags(conf, uselib)
+ (ccflags, ldflags, cpppath, libs) = library_flags(conf, uselib)
includes = TO_LIST(includes)
includes.extend(cpppath)
@@ -569,21 +569,24 @@ Build.BuildContext.CONFIG_SET = CONFIG_SET
Build.BuildContext.CONFIG_GET = CONFIG_GET
-def library_flags(self, libs):
+def library_flags(self, library):
'''work out flags from pkg_config'''
ccflags = []
ldflags = []
cpppath = []
- for lib in TO_LIST(libs):
+ libs = []
+ for lib in TO_LIST(library):
# note that we do not add the -I and -L in here, as that is added by the waf
# core. Adding it here would just change the order that it is put on the link line
# which can cause system paths to be added before internal libraries
extra_ccflags = TO_LIST(getattr(self.env, 'CFLAGS_%s' % lib.upper(), []))
extra_ldflags = TO_LIST(getattr(self.env, 'LDFLAGS_%s' % lib.upper(), []))
extra_cpppath = TO_LIST(getattr(self.env, 'CPPPATH_%s' % lib.upper(), []))
+ extra_libs = TO_LIST(getattr(self.env, 'LIB_%s' % lib.upper(), []))
ccflags.extend(extra_ccflags)
ldflags.extend(extra_ldflags)
cpppath.extend(extra_cpppath)
+ libs.extend(extra_libs)
extra_cpppath = TO_LIST(getattr(self.env, 'INCLUDES_%s' % lib.upper(), []))
cpppath.extend(extra_cpppath)
@@ -593,11 +596,12 @@ def library_flags(self, libs):
ccflags = unique_list(ccflags)
ldflags = unique_list(ldflags)
cpppath = unique_list(cpppath)
- return (ccflags, ldflags, cpppath)
+ libs = unique_list(libs)
+ return (ccflags, ldflags, cpppath, libs)
@conf
-def CHECK_LIB(conf, libs, mandatory=False, empty_decl=True, set_target=True, shlib=False):
+def CHECK_LIB(conf, library, mandatory=False, empty_decl=True, set_target=True, shlib=False):
'''check if a set of libraries exist as system libraries
returns the sublist of libs that do exist as a syslib or []
@@ -611,13 +615,13 @@ int foo()
}
'''
ret = []
- liblist = TO_LIST(libs)
- for lib in liblist[:]:
+ liblist = TO_LIST(library)
+ for lib in liblist:
if GET_TARGET_TYPE(conf, lib) == 'SYSLIB':
ret.append(lib)
continue
- (ccflags, ldflags, cpppath) = library_flags(conf, lib)
+ (ccflags, ldflags, cpppath, libs) = library_flags(conf, lib)
if shlib:
res = conf.check(features='c cshlib', fragment=fragment, lib=lib, uselib_store=lib, cflags=ccflags, ldflags=ldflags, uselib=lib.upper(), mandatory=False)
else:
diff --git a/buildtools/wafsamba/samba_deps.py b/buildtools/wafsamba/samba_deps.py
index 66adf40307e..5b428295b86 100644
--- a/buildtools/wafsamba/samba_deps.py
+++ b/buildtools/wafsamba/samba_deps.py
@@ -83,9 +83,8 @@ def build_dependencies(self):
self.add_objects = list(self.final_objects)
# extra link flags from pkg_config
- libs = self.final_syslibs.copy()
-
- (cflags, ldflags, cpppath) = library_flags(self, list(libs))
+ (cflags, ldflags, cpppath, libs) = library_flags(
+ self, list(self.final_syslibs.copy()))
new_ldflags = getattr(self, 'samba_ldflags', [])[:]
new_ldflags.extend(ldflags)
self.ldflags = new_ldflags
diff --git a/lib/util/charset/wscript_configure b/lib/util/charset/wscript_configure
index 9c27fc664f0..58858f69b31 100644
--- a/lib/util/charset/wscript_configure
+++ b/lib/util/charset/wscript_configure
@@ -8,7 +8,7 @@
# managed to link when specifying -liconv a executable even if there is no
# libiconv.so or libiconv.a
-conf.CHECK_LIB(libs="iconv", shlib=True)
+conf.CHECK_LIB("iconv", shlib=True)
#HP-UX can use libiconv as an add-on package, which has #define iconv_open libiconv_open
if (conf.CHECK_FUNCS_IN('iconv_open', 'iconv', checklibc=False, headers='iconv.h') or
--
2.53.0
From a4f79d7fb725fab47bda53b9482c1ee301a8393a Mon Sep 17 00:00:00 2001
From: Earl Chew <earl_chew@yahoo.com>
Date: Sat, 16 Dec 2023 17:47:09 -0800
Subject: [PATCH 065/122] Improve CHECK_LIB interaction with CHECK_PKG
When checking for shared libraries, only name the target library
if it was not previously discoverd by pkg-config --libs and now
available from uselib_store. This avoids using both sources of
information which results in the library being named twice on
the command line.
Once the library is confirmed by CHECK_LIB, append the library if
not already present, to avoid dropping libraries that were
previously discovered by CHECK_PKG.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15623
Signed-off-by: Earl Chew <earl_chew@yahoo.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
(cherry picked from commit 0c983bd0095d4fb20ef8b42f5efb740393073862)
---
buildtools/wafsamba/samba_autoconf.py | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/buildtools/wafsamba/samba_autoconf.py b/buildtools/wafsamba/samba_autoconf.py
index d3b6503c5ca..b1d2f761095 100644
--- a/buildtools/wafsamba/samba_autoconf.py
+++ b/buildtools/wafsamba/samba_autoconf.py
@@ -623,7 +623,12 @@ int foo()
(ccflags, ldflags, cpppath, libs) = library_flags(conf, lib)
if shlib:
- res = conf.check(features='c cshlib', fragment=fragment, lib=lib, uselib_store=lib, cflags=ccflags, ldflags=ldflags, uselib=lib.upper(), mandatory=False)
+ # Avoid repeating the library if it is already named by
+ # pkg-config --libs.
+ kw = {}
+ if lib not in libs:
+ kw['lib'] = lib
+ res = conf.check(features='c cshlib', fragment=fragment, uselib_store=lib, cflags=ccflags, ldflags=ldflags, uselib=lib.upper(), mandatory=False, **kw)
else:
res = conf.check(lib=lib, uselib_store=lib, cflags=ccflags, ldflags=ldflags, uselib=lib.upper(), mandatory=False)
@@ -637,7 +642,10 @@ int foo()
SET_TARGET_TYPE(conf, lib, 'EMPTY')
else:
conf.define('HAVE_LIB%s' % lib.upper().replace('-','_').replace('.','_'), 1)
- conf.env['LIB_' + lib.upper()] = lib
+ # To avoid losing information from pkg-config, append the library
+ # only it is not already present.
+ if lib not in libs:
+ conf.env.append_value('LIB_' + lib.upper(), lib)
if set_target:
conf.SET_TARGET_TYPE(lib, 'SYSLIB')
ret.append(lib)
--
2.53.0
From 2b4f5a62eac69e12ecd9a1e3919ea4a8b3d40820 Mon Sep 17 00:00:00 2001
From: Earl Chew <earl_chew@yahoo.com>
Date: Sat, 16 Dec 2023 08:48:36 -0800
Subject: [PATCH 066/122] Combine ICU libraries icu-i18n and icu-uc into a
single dependency
Rather than probing for icu-i18n, icu-uc, and icudata libraries
separately, only probe for icu-i18n, and icu-uc, as direct dependencies
This avoids overlinking with icudata, and allows the package
to build even when ICU is not installed as a system library.
RN: Only use icu-i18n and icu-uc to express ICU dependency
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15623
Signed-off-by: Earl Chew <earl_chew@yahoo.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
(cherry picked from commit 05807488fd340751ee976c5f8a367013ff94843e)
---
lib/util/charset/iconv.c | 8 ++++----
lib/util/charset/wscript_build | 3 ++-
lib/util/charset/wscript_configure | 17 +++++++----------
3 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/lib/util/charset/iconv.c b/lib/util/charset/iconv.c
index 30e705ee119..3234f92bc55 100644
--- a/lib/util/charset/iconv.c
+++ b/lib/util/charset/iconv.c
@@ -26,7 +26,7 @@
#include "lib/util/charset/charset.h"
#include "lib/util/charset/charset_proto.h"
-#ifdef HAVE_ICU_I18N
+#ifdef HAVE_ICUI18N
#include <unicode/ustring.h>
#include <unicode/utrans.h>
#endif
@@ -168,7 +168,7 @@ static size_t sys_iconv(void *cd,
}
#endif
-#ifdef HAVE_ICU_I18N
+#ifdef HAVE_ICUI18N
static size_t sys_uconv(void *cd,
const char **inbuf,
size_t *inbytesleft,
@@ -334,7 +334,7 @@ static bool is_utf16(const char *name)
static int smb_iconv_t_destructor(smb_iconv_t hwd)
{
-#ifdef HAVE_ICU_I18N
+#ifdef HAVE_ICUI18N
/*
* This has to come first, as the cd_direct member won't be an iconv
* handle and must not be passed to iconv_close().
@@ -418,7 +418,7 @@ _PUBLIC_ smb_iconv_t smb_iconv_open_ex(TALLOC_CTX *mem_ctx, const char *tocode,
}
#endif
-#ifdef HAVE_ICU_I18N
+#ifdef HAVE_ICUI18N
if (strcasecmp(fromcode, "UTF8-NFD") == 0 &&
strcasecmp(tocode, "UTF8-NFC") == 0)
{
diff --git a/lib/util/charset/wscript_build b/lib/util/charset/wscript_build
index c69a17170ad..3af90a0ad57 100644
--- a/lib/util/charset/wscript_build
+++ b/lib/util/charset/wscript_build
@@ -6,7 +6,8 @@ bld.SAMBA_SUBSYSTEM('ICONV_WRAPPER',
weird.c
charset_macosxfs.c
''',
- public_deps='iconv replace talloc ' + bld.env['icu-libs'])
+ deps=bld.env['icu-libs'],
+ public_deps='iconv replace talloc')
bld.SAMBA_SUBSYSTEM('charset',
public_headers='charset.h',
diff --git a/lib/util/charset/wscript_configure b/lib/util/charset/wscript_configure
index 58858f69b31..c49b55a4fd4 100644
--- a/lib/util/charset/wscript_configure
+++ b/lib/util/charset/wscript_configure
@@ -37,15 +37,12 @@ conf.CHECK_CODE('''
lib='iconv',
headers='errno.h iconv.h')
-if conf.CHECK_CFG(package='icu-i18n',
+if conf.CHECK_CFG(package='icu-i18n icu-uc',
args='--cflags --libs',
- msg='Checking for icu-i18n',
- uselib_store='ICU_I18N'):
- for lib in conf.env['LIB_ICU_I18N']:
- conf.CHECK_LIB(lib, shlib=True, mandatory=True)
- conf.env['icu-libs'] = ' '.join(conf.env['LIB_ICU_I18N'])
- if not conf.CHECK_HEADERS('unicode/ustring.h'):
- conf.fatal('Found libicu, but unicode/ustring.h is missing')
+ msg='Checking for icu-i18n icu-uc',
+ uselib_store='ICUI18N'):
+ conf.env['icu-libs'] = 'icui18n'
+ conf.CHECK_LIB(conf.env['icu-libs'], shlib=True, mandatory=True)
+ if not conf.CHECK_HEADERS('unicode/ustring.h', lib='icui18n'):
+ conf.fatal('Found icui18n, but unicode/ustring.h is missing')
conf.DEFINE('HAVE_UTF8_NORMALISATION', 1)
-else:
- conf.env['icu-libs'] = ''
--
2.53.0
From 8e5968634b263c20ad71c75e839abb217614b567 Mon Sep 17 00:00:00 2001
From: Earl Chew <earl_chew@yahoo.com>
Date: Fri, 10 May 2024 19:46:28 -0700
Subject: [PATCH 067/122] Restore empty string default for conf.env['icu-libs']
The reworked ICU libraries configuration code used [] as
default for conf.env['icu-libs']. This breaks dependency analysis
in samba_deps.py because SAMBA_SUBSYSTEM() expects deps to be
a string.
Signed-off-by: Earl Chew <earl_chew@yahoo.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Volker Lendecke <vl@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Tue May 14 14:44:06 UTC 2024 on atb-devel-224
(cherry picked from commit 68a1200f66e9008ca0a739b37b48c49453ca9d83)
---
lib/util/charset/wscript_configure | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lib/util/charset/wscript_configure b/lib/util/charset/wscript_configure
index c49b55a4fd4..adae44eab5e 100644
--- a/lib/util/charset/wscript_configure
+++ b/lib/util/charset/wscript_configure
@@ -46,3 +46,5 @@ if conf.CHECK_CFG(package='icu-i18n icu-uc',
if not conf.CHECK_HEADERS('unicode/ustring.h', lib='icui18n'):
conf.fatal('Found icui18n, but unicode/ustring.h is missing')
conf.DEFINE('HAVE_UTF8_NORMALISATION', 1)
+else:
+ conf.env['icu-libs'] = ''
--
2.53.0
From 88a29be0ed6cf611eb812c0729d2ee61be07a3a3 Mon Sep 17 00:00:00 2001
From: Earl Chew <earl_chew@yahoo.com>
Date: Fri, 27 Sep 2024 06:50:31 -0700
Subject: [PATCH 068/122] Describe implication of upstream ICU-22610
Add commentary to link commit 86c7688 (MR !3447) to the upstream
fix for ICU-22610 in case there is subsequent breakage.
Signed-off-by: Earl Chew <earl_chew@yahoo.com>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Autobuild-User(master): Andrew Bartlett <abartlet@samba.org>
Autobuild-Date(master): Fri Nov 8 00:20:38 UTC 2024 on atb-devel-224
(cherry picked from commit 1655413f1246147db9b34d4684a64dac49cf5f0c)
---
lib/util/charset/wscript_configure | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/util/charset/wscript_configure b/lib/util/charset/wscript_configure
index adae44eab5e..451f7f7bca3 100644
--- a/lib/util/charset/wscript_configure
+++ b/lib/util/charset/wscript_configure
@@ -37,6 +37,10 @@ conf.CHECK_CODE('''
lib='iconv',
headers='errno.h iconv.h')
+# Since commit 86c7688 (MR !3447), the required ICU libraries are discovered
+# as a single group. This had the benefit of working around ICU-22610, and also
+# works with the fix that was merged to ICU main in commit 199bc827.
+
if conf.CHECK_CFG(package='icu-i18n icu-uc',
args='--cflags --libs',
msg='Checking for icu-i18n icu-uc',
--
2.53.0
From 72c6766af2ac55854b816147a277404d98b1de9a Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 8 Jan 2026 11:55:18 +0100
Subject: [PATCH 069/122] smbd: add a directory argument to
safe_symlink_target_path()
Existing caller passes NULL, no change in behaviour. Prepares for
replacing symlink_target_below_conn() in open.c.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>
(backported from commit fc80c72d658a41fe4d93b24b793b52c91b350175)
Backport changes:
- The v4-19 branch has a different safe_symlink_target_path() signature
that takes (name_in, substitute) instead of (dir, target), due to
commit 7d102268ebb being backported out of order.
- Adapted the function to use the new (dir, target) signature matching
proto.h, which already had the correct declaration.
- Updated filename_convert_dirfsp() to call symlink_target_path() first
to compute the target path, then pass dir=NULL to safe_symlink_target_path().
- This fixes symlink resolution for relative symlinks in subdirectories
(e.g., subdir/link -> ../file) by correctly building the absolute path
as connectpath/dir/target instead of just connectpath/target.
---
source3/smbd/filename.c | 52 +++++++++++++++++++++++++++--------------
1 file changed, 34 insertions(+), 18 deletions(-)
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c
index 9fd85af992a..f6e9ed6aae0 100644
--- a/source3/smbd/filename.c
+++ b/source3/smbd/filename.c
@@ -944,35 +944,40 @@ static char *symlink_target_path(
NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
const char *connectpath,
- const char *name_in,
- const char *substitute,
+ const char *dir,
+ const char *target,
size_t unparsed,
char **_relative)
{
- char *target = NULL;
char *abs_target = NULL;
char *abs_target_canon = NULL;
const char *relative = NULL;
bool in_share;
NTSTATUS status = NT_STATUS_NO_MEMORY;
- target = symlink_target_path(mem_ctx,
- name_in,
- substitute,
- unparsed);
- if (target == NULL) {
- goto fail;
- }
-
- DBG_DEBUG("connectpath [%s] target [%s] unparsed [%zu]\n",
- connectpath, target, unparsed);
+ DBG_DEBUG("connectpath [%s] dir [%s] target [%s] unparsed [%zu]\n",
+ connectpath,
+ dir != NULL ? dir : "",
+ target,
+ unparsed);
if (target[0] == '/') {
- abs_target = target;
- } else {
- abs_target = talloc_asprintf(target,
+ abs_target = talloc_strdup(mem_ctx, target);
+ } else if (dir == NULL) {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ connectpath,
+ target);
+ } else if (dir[0] == '/') {
+ abs_target = talloc_asprintf(mem_ctx,
"%s/%s",
+ dir,
+ target);
+ } else {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
connectpath,
+ dir,
target);
}
if (abs_target == NULL) {
@@ -1432,6 +1437,7 @@ NTSTATUS filename_convert_dirfsp(
struct open_symlink_err *symlink_err = NULL;
NTSTATUS status;
char *substitute = NULL;
+ char *target = NULL;
char *safe_target = NULL;
size_t symlink_redirects = 0;
@@ -1470,13 +1476,23 @@ next:
*/
substitute = symlink_err->reparse->substitute_name;
+ target = symlink_target_path(mem_ctx,
+ name_in,
+ substitute,
+ symlink_err->unparsed);
+ if (target == NULL) {
+ TALLOC_FREE(symlink_err);
+ return NT_STATUS_NO_MEMORY;
+ }
+
status = safe_symlink_target_path(mem_ctx,
conn->connectpath,
- name_in,
- substitute,
+ NULL,
+ target,
symlink_err->unparsed,
&safe_target);
TALLOC_FREE(symlink_err);
+ TALLOC_FREE(target);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
--
2.53.0
From 9b8c2d3abe56b53b4ac7dfb6af927a889580ae7f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= <pfilipensky@samba.org>
Date: Mon, 19 Jan 2026 14:33:52 +0100
Subject: [PATCH 070/122] s3:libads: Reset ads->config.flags in
ads_disconnect()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This is doing the same thing in ads_disconnect() as commit
a26f535 Clear previous CLDAP ping flags when reusing the ADS_STRUCT
did in ads_current_time()
In this case we:
1) found cached ADS_STRUCT which already has ads->config.flags set:
lookup_groupmem()
ads_cached_connection()
ads_cached_connection_reuse()
2) started search which immediately timeouts (the cached conn. was dead)
ads_do_search_retry_internal()
ldap_search_with_timeout() - IO_TIMEOUT
3) Retry loop finds a new DC and tries to connect
ads_do_search_retry_internal()
ads_disconnect()
ads_find_dc()
ads_try_connect()
netlogon_pings()
check_cldap_reply_required_flags()
4) check_cldap_reply_required_flags() fails since ads->config.flags
(stored possibly long time ago) contain:
NBT_SERVER_CLOSEST 0x00000080
which is misinterpreted as:
DS_PDC_REQUIRED 0x00000080
the newly found DC is not PDC (we asked for DS_ONLY_LDAP_NEEDED)
and since previous DC had NBT_SERVER_CLOSEST we want DS_PDC_REQUIRED
and fail.
We should anyway avoid mixing independent namespaces NBT_* and DS_*
in the same flag.
Next commit will do that.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15972
Signed-off-by: Pavel Filipenský <pfilipensky@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
(cherry picked from commit 9f3a35991feb01a8d2c2b69fa0b914bbc637a809)
---
source3/libads/ldap.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c
index cc00753ff74..625377fa2cc 100644
--- a/source3/libads/ldap.c
+++ b/source3/libads/ldap.c
@@ -1068,6 +1068,7 @@ void ads_disconnect(ADS_STRUCT *ads)
if (ads->ldap_wrap_data.mem_ctx) {
talloc_free(ads->ldap_wrap_data.mem_ctx);
}
+ ads->config.flags = 0;
ads_zero_ldap(ads);
ZERO_STRUCT(ads->ldap_wrap_data);
}
--
2.53.0
From 1e1c43cc946f1947835570907064b4ee3aaa3ee7 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 30 Aug 2024 14:22:24 +0200
Subject: [PATCH 071/122] s4:torture/smb2: improve error handling in
durable_open.c
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit e65e1326a0214a7dfff75ea1e528e82c8fc64517)
---
source4/torture/smb2/durable_open.c | 39 ++++++++---------------------
1 file changed, 11 insertions(+), 28 deletions(-)
diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c
index f56c55811ac..39b6efad567 100644
--- a/source4/torture/smb2/durable_open.c
+++ b/source4/torture/smb2/durable_open.c
@@ -28,34 +28,17 @@
#include "torture/smb2/proto.h"
#include "../libcli/smb/smbXcli_base.h"
-#define CHECK_VAL(v, correct) do { \
- if ((v) != (correct)) { \
- torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should be 0x%llx\n", \
- __location__, #v, (unsigned long long)v, (unsigned long long)correct); \
- ret = false; \
- }} while (0)
-
-#define CHECK_NOT_VAL(v, incorrect) do { \
- if ((v) == (incorrect)) { \
- torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should not be 0x%llx\n", \
- __location__, #v, (unsigned long long)v, (unsigned long long)incorrect); \
- ret = false; \
- }} while (0)
-
-#define CHECK_NOT_NULL(p) do { \
- if ((p) == NULL) { \
- torture_result(tctx, TORTURE_FAIL, "(%s): %s is NULL but it should not be.\n", \
- __location__, #p); \
- ret = false; \
- }} while (0)
-
-#define CHECK_STATUS(status, correct) do { \
- if (!NT_STATUS_EQUAL(status, correct)) { \
- torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
- nt_errstr(status), nt_errstr(correct)); \
- ret = false; \
- goto done; \
- }} while (0)
+#define CHECK_VAL(v, correct) \
+ torture_assert_u64_equal_goto(tctx, v, correct, ret, done, __location__)
+
+#define CHECK_NOT_VAL(v, incorrect) \
+ torture_assert_u64_not_equal_goto(tctx, v, incorrect, ret, done, __location__)
+
+#define CHECK_NOT_NULL(p) \
+ torture_assert_not_null_goto(tctx, p, ret, done, __location__)
+
+#define CHECK_STATUS(status, correct) \
+ torture_assert_ntstatus_equal_goto(tctx, status, correct, ret, done, __location__)
#define CHECK_CREATED(__io, __created, __attribute) \
do { \
--
2.53.0
From 9149916376570a97ee0d94e9ed64751796da5053 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 30 Aug 2024 14:22:24 +0200
Subject: [PATCH 072/122] s4:torture/smb2: improve error handling in
durable_v2_open.c
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit 9b2417c2f04857709c25e3665cd783a68edf0cf2)
---
source4/torture/smb2/durable_v2_open.c | 19 +++++--------------
1 file changed, 5 insertions(+), 14 deletions(-)
diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c
index 7447dd287a4..7eed4327b52 100644
--- a/source4/torture/smb2/durable_v2_open.c
+++ b/source4/torture/smb2/durable_v2_open.c
@@ -27,20 +27,11 @@
#include "torture/smb2/proto.h"
#include "librpc/ndr/libndr.h"
-#define CHECK_VAL(v, correct) do { \
- if ((v) != (correct)) { \
- torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \
- __location__, #v, (int)v, (int)correct); \
- ret = false; \
- }} while (0)
-
-#define CHECK_STATUS(status, correct) do { \
- if (!NT_STATUS_EQUAL(status, correct)) { \
- torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
- nt_errstr(status), nt_errstr(correct)); \
- ret = false; \
- goto done; \
- }} while (0)
+#define CHECK_VAL(v, correct) \
+ torture_assert_u64_equal_goto(tctx, v, correct, ret, done, __location__)
+
+#define CHECK_STATUS(status, correct) \
+ torture_assert_ntstatus_equal_goto(tctx, status, correct, ret, done, __location__)
#define CHECK_CREATED(__io, __created, __attribute) \
do { \
--
2.53.0
From 986cc4d97eba6d3807170f66f6b3664f395a6e3e Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 30 Aug 2024 17:38:02 +0200
Subject: [PATCH 073/122] s4:torture/smb2: add smb2.durable-open.lock-noW-lease
This demonstrates that a W lease is required for a
durable handle to be durable when it has byte range locks.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit 1cc1586d84a65046ab7804f17297c6964bb76c23)
---
selftest/knownfail.d/smb2.durable.lock | 1 +
source4/torture/smb2/durable_open.c | 97 +++++++++++++++++++++++++-
2 files changed, 97 insertions(+), 1 deletion(-)
create mode 100644 selftest/knownfail.d/smb2.durable.lock
diff --git a/selftest/knownfail.d/smb2.durable.lock b/selftest/knownfail.d/smb2.durable.lock
new file mode 100644
index 00000000000..3e3bd80ba46
--- /dev/null
+++ b/selftest/knownfail.d/smb2.durable.lock
@@ -0,0 +1 @@
+^samba3.smb2.durable-open.lock-noW-lease
diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c
index 39b6efad567..cc78c0aff66 100644
--- a/source4/torture/smb2/durable_open.c
+++ b/source4/torture/smb2/durable_open.c
@@ -2151,7 +2151,7 @@ static bool test_durable_open_lock_oplock(struct torture_context *tctx,
}
/*
- Open, take BRL, disconnect, reconnect.
+ Open(RWH), take BRL, disconnect, reconnect.
*/
static bool test_durable_open_lock_lease(struct torture_context *tctx,
struct smb2_tree *tree)
@@ -2249,6 +2249,100 @@ static bool test_durable_open_lock_lease(struct torture_context *tctx,
return ret;
}
+/*
+ Open(RH), take BRL, disconnect, fails reconnect without W LEASE
+*/
+static bool test_durable_open_lock_noW_lease(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ struct smb2_create io;
+ struct smb2_lease ls;
+ struct smb2_handle h = {{0}};
+ struct smb2_lock lck;
+ struct smb2_lock_element el[2];
+ NTSTATUS status;
+ char fname[256];
+ bool ret = true;
+ uint64_t lease;
+ uint32_t caps;
+ struct smbcli_options options;
+
+ options = tree->session->transport->options;
+
+ caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ /*
+ * Choose a random name and random lease in case the state is left a
+ * little funky.
+ */
+ lease = random();
+ snprintf(fname, 256, "durable_open_lease_noW_lock_%s.dat", generate_random_str(tctx, 8));
+
+ /* Clean slate */
+ smb2_util_unlink(tree, fname);
+
+ /* Create with lease */
+
+ smb2_lease_create(&io, &ls, false /* dir */, fname, lease,
+ smb2_util_lease_state("RH"));
+ io.in.durable_open = true;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h = io.out.file.handle;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+
+ CHECK_VAL(io.out.durable_open, true);
+ CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
+ CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
+ CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
+ CHECK_VAL(io.out.lease_response.lease_state,
+ SMB2_LEASE_READ|SMB2_LEASE_HANDLE);
+
+ ZERO_STRUCT(lck);
+ ZERO_STRUCT(el);
+ lck.in.locks = el;
+ lck.in.lock_count = 0x0001;
+ lck.in.lock_sequence = 0x00000000;
+ lck.in.file.handle = h;
+ el[0].offset = 0;
+ el[0].length = 1;
+ el[0].reserved = 0x00000000;
+ el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
+ status = smb2_lock(tree, &lck);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ /* Disconnect/Reconnect. */
+ talloc_free(tree);
+ tree = NULL;
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_handle = &h;
+ io.in.lease_request = &ls;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ h = io.out.file.handle;
+
+ done:
+ smb2_util_close(tree, h);
+ smb2_util_unlink(tree, fname);
+ talloc_free(tree);
+
+ return ret;
+}
+
/**
* Open with a RH lease, disconnect, open in another tree, reconnect.
*
@@ -2823,6 +2917,7 @@ struct torture_suite *torture_smb2_durable_open_init(TALLOC_CTX *ctx)
torture_suite_add_2smb2_test(suite, "lease", test_durable_open_lease);
torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_open_lock_oplock);
torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_open_lock_lease);
+ torture_suite_add_1smb2_test(suite, "lock-noW-lease", test_durable_open_lock_noW_lease);
torture_suite_add_2smb2_test(suite, "open2-lease",
test_durable_open_open2_lease);
torture_suite_add_2smb2_test(suite, "open2-oplock",
--
2.53.0
From cdffe7d7a0b6919fe405fbf4b536a6f33d33363f Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 30 Aug 2024 17:38:02 +0200
Subject: [PATCH 074/122] s4:torture/smb2: add
smb2.durable-v2-open.lock-{oplock,lease,noW-lease}
This demonstrates that a W lease is required for a
durable handle to be durable when it has byte range locks.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit 8884d617310b47375e38c0386433c5e183703454)
---
selftest/knownfail.d/smb2.durable.lock | 1 +
source4/torture/smb2/durable_v2_open.c | 336 +++++++++++++++++++++++++
2 files changed, 337 insertions(+)
diff --git a/selftest/knownfail.d/smb2.durable.lock b/selftest/knownfail.d/smb2.durable.lock
index 3e3bd80ba46..16273fb4ad4 100644
--- a/selftest/knownfail.d/smb2.durable.lock
+++ b/selftest/knownfail.d/smb2.durable.lock
@@ -1 +1,2 @@
^samba3.smb2.durable-open.lock-noW-lease
+^samba3.smb2.durable-v2-open.lock-noW-lease
diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c
index 7eed4327b52..685ef80c0cc 100644
--- a/source4/torture/smb2/durable_v2_open.c
+++ b/source4/torture/smb2/durable_v2_open.c
@@ -41,6 +41,30 @@
CHECK_VAL((__io)->out.reserved2, 0); \
} while(0)
+#define CHECK_LEASE_V2(__io, __state, __oplevel, __key, __flags, __parent, __epoch) \
+ do { \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_version, 2); \
+ if (__oplevel) { \
+ CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], (__key)); \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], ~(__key)); \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_state, smb2_util_lease_state(__state)); \
+ } else { \
+ CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], 0); \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], 0); \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_state, 0); \
+ } \
+ \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_flags, __flags); \
+ if (__flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET) { \
+ CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[0], (__parent)); \
+ CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[1], ~(__parent)); \
+ } \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_duration, 0); \
+ CHECK_VAL((__io)->out.lease_response_v2.lease_epoch, (__epoch)); \
+ } while(0)
+
static struct {
int count;
struct smb2_close cl;
@@ -1731,6 +1755,315 @@ done:
return ret;
}
+/*
+ Open(BATCH), take BRL, disconnect, reconnect.
+*/
+static bool test_durable_v2_open_lock_oplock(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ struct smb2_create io;
+ struct GUID create_guid = GUID_random();
+ struct smb2_handle h = {{0}};
+ struct smb2_lock lck;
+ struct smb2_lock_element el[2];
+ NTSTATUS status;
+ char fname[256];
+ bool ret = true;
+ struct smbcli_options options;
+
+ options = tree->session->transport->options;
+
+ snprintf(fname, 256, "durable_v2_open_lock_oplock_%s.dat", generate_random_str(tctx, 8));
+
+ /* Clean slate */
+ smb2_util_unlink(tree, fname);
+
+ /* Create with lease */
+
+ smb2_oplock_create_share(&io, fname,
+ smb2_util_share_access(""),
+ smb2_util_oplock_level("b"));
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h = io.out.file.handle;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+
+ ZERO_STRUCT(lck);
+ ZERO_STRUCT(el);
+ lck.in.locks = el;
+ lck.in.lock_count = 0x0001;
+ lck.in.lock_sequence = 0x00000000;
+ lck.in.file.handle = h;
+ el[0].offset = 0;
+ el[0].length = 1;
+ el[0].reserved = 0x00000000;
+ el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
+ status = smb2_lock(tree, &lck);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ /* Disconnect/Reconnect. */
+ talloc_free(tree);
+ tree = NULL;
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = &h;
+ io.in.create_guid = create_guid;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h = io.out.file.handle;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+
+ lck.in.file.handle = h;
+ el[0].flags = SMB2_LOCK_FLAG_UNLOCK;
+ status = smb2_lock(tree, &lck);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ done:
+ smb2_util_close(tree, h);
+ smb2_util_unlink(tree, fname);
+ talloc_free(tree);
+
+ return ret;
+}
+
+/*
+ Open(RWH), take BRL, disconnect, reconnect.
+*/
+static bool test_durable_v2_open_lock_lease(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ struct smb2_create io;
+ struct smb2_lease ls;
+ struct GUID create_guid = GUID_random();
+ struct smb2_handle h = {{0}};
+ struct smb2_lock lck;
+ struct smb2_lock_element el[2];
+ NTSTATUS status;
+ char fname[256];
+ bool ret = true;
+ uint64_t lease;
+ uint32_t caps;
+ struct smbcli_options options;
+
+ options = tree->session->transport->options;
+
+ caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ /*
+ * Choose a random name and random lease in case the state is left a
+ * little funky.
+ */
+ lease = random();
+ snprintf(fname, 256, "durable_v2_open_lock_lease_%s.dat", generate_random_str(tctx, 8));
+
+ /* Clean slate */
+ smb2_util_unlink(tree, fname);
+
+ /* Create with lease */
+
+ smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
+ lease, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h = io.out.file.handle;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ ls.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RWH", true, lease,
+ 0, 0, ls.lease_epoch);
+
+ ZERO_STRUCT(lck);
+ ZERO_STRUCT(el);
+ lck.in.locks = el;
+ lck.in.lock_count = 0x0001;
+ lck.in.lock_sequence = 0x00000000;
+ lck.in.file.handle = h;
+ el[0].offset = 0;
+ el[0].length = 1;
+ el[0].reserved = 0x00000000;
+ el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
+ status = smb2_lock(tree, &lck);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ /* Disconnect/Reconnect. */
+ talloc_free(tree);
+ tree = NULL;
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = &h;
+ io.in.create_guid = create_guid;
+ io.in.lease_request_v2 = &ls;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h = io.out.file.handle;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RWH", true, lease,
+ 0, 0, ls.lease_epoch);
+
+ lck.in.file.handle = h;
+ el[0].flags = SMB2_LOCK_FLAG_UNLOCK;
+ status = smb2_lock(tree, &lck);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ done:
+ smb2_util_close(tree, h);
+ smb2_util_unlink(tree, fname);
+ talloc_free(tree);
+
+ return ret;
+}
+
+/*
+ Open(RH), take BRL, disconnect, fails reconnect without W LEASE
+*/
+static bool test_durable_v2_open_lock_noW_lease(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ struct smb2_create io;
+ struct smb2_lease ls;
+ struct GUID create_guid = GUID_random();
+ struct smb2_handle h = {{0}};
+ struct smb2_lock lck;
+ struct smb2_lock_element el[2];
+ NTSTATUS status;
+ char fname[256];
+ bool ret = true;
+ uint64_t lease;
+ uint32_t caps;
+ struct smbcli_options options;
+
+ options = tree->session->transport->options;
+
+ caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ /*
+ * Choose a random name and random lease in case the state is left a
+ * little funky.
+ */
+ lease = random();
+ snprintf(fname, 256, "durable_v2_open_lock_noW_lease_%s.dat", generate_random_str(tctx, 8));
+
+ /* Clean slate */
+ smb2_util_unlink(tree, fname);
+
+ /* Create with lease */
+
+ smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
+ lease, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h = io.out.file.handle;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ ls.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease,
+ 0, 0, ls.lease_epoch);
+
+ ZERO_STRUCT(lck);
+ ZERO_STRUCT(el);
+ lck.in.locks = el;
+ lck.in.lock_count = 0x0001;
+ lck.in.lock_sequence = 0x00000000;
+ lck.in.file.handle = h;
+ el[0].offset = 0;
+ el[0].length = 1;
+ el[0].reserved = 0x00000000;
+ el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
+ status = smb2_lock(tree, &lck);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ /* Disconnect/Reconnect. */
+ talloc_free(tree);
+ tree = NULL;
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = &h;
+ io.in.create_guid = create_guid;
+ io.in.lease_request_v2 = &ls;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+ done:
+ smb2_util_close(tree, h);
+ smb2_util_unlink(tree, fname);
+ talloc_free(tree);
+
+ return ret;
+}
+
/**
* Test durable request / reconnect with AppInstanceId
*/
@@ -2157,6 +2490,9 @@ struct torture_suite *torture_smb2_durable_v2_open_init(TALLOC_CTX *ctx)
torture_suite_add_1smb2_test(suite, "reopen2-lease", test_durable_v2_open_reopen2_lease);
torture_suite_add_1smb2_test(suite, "reopen2-lease-v2", test_durable_v2_open_reopen2_lease_v2);
torture_suite_add_1smb2_test(suite, "durable-v2-setinfo", test_durable_v2_setinfo);
+ torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_v2_open_lock_oplock);
+ torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_v2_open_lock_lease);
+ torture_suite_add_1smb2_test(suite, "lock-noW-lease", test_durable_v2_open_lock_noW_lease);
torture_suite_add_2smb2_test(suite, "app-instance", test_durable_v2_open_app_instance);
torture_suite_add_1smb2_test(suite, "persistent-open-oplock", test_persistent_open_oplock);
torture_suite_add_1smb2_test(suite, "persistent-open-lease", test_persistent_open_lease);
--
2.53.0
From d770866bbdd9ba70f896eb3b75bba6baa01fdf33 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 30 Aug 2024 18:10:16 +0200
Subject: [PATCH 075/122] s3:smbd: only store durable handles with byte range
locks when having WRITE lease
This simplifies the reconnect assumptions, when we want to allow
more than one durable handle on a file for multiple clients with
READ+HANDLE leases.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit 0893ae88180137d44f17196234f657d362543ff5)
---
selftest/knownfail.d/smb2.durable.lock | 2 --
source3/smbd/durable.c | 6 ++++++
2 files changed, 6 insertions(+), 2 deletions(-)
delete mode 100644 selftest/knownfail.d/smb2.durable.lock
diff --git a/selftest/knownfail.d/smb2.durable.lock b/selftest/knownfail.d/smb2.durable.lock
deleted file mode 100644
index 16273fb4ad4..00000000000
--- a/selftest/knownfail.d/smb2.durable.lock
+++ /dev/null
@@ -1,2 +0,0 @@
-^samba3.smb2.durable-open.lock-noW-lease
-^samba3.smb2.durable-v2-open.lock-noW-lease
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
index 98d0d403e30..b7fa53e7555 100644
--- a/source3/smbd/durable.c
+++ b/source3/smbd/durable.c
@@ -173,6 +173,12 @@ NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
return NT_STATUS_NOT_SUPPORTED;
}
+ if (fsp->current_lock_count != 0 &&
+ (fsp_lease_type(fsp) & SMB2_LEASE_WRITE) == 0)
+ {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
/*
* For now let it be simple and do not keep
* delete on close files durable open
--
2.53.0
From 14ec1065ac307f5c59e292ac8f805104f9c10884 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Wed, 28 Aug 2024 16:48:27 +0200
Subject: [PATCH 076/122] s4:torture/smb2: add
smb2.durable-v2-open.{[non]stat[RH]-and,two-same,two-different}-lease
These show that it's possible to have durable handles in addition
of stat opens, as well as multiple durable opens with RH leases.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit 77c7741f39a0a9789bede7c4722bd3f35d4af3fd)
---
.../knownfail.d/smb2.durable-v2-open.bug15649 | 2 +
.../knownfail.d/smb2.durable-v2-open.bug15651 | 3 +
source4/torture/smb2/durable_v2_open.c | 784 ++++++++++++++++++
3 files changed, 789 insertions(+)
create mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15649
create mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15651
diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15649 b/selftest/knownfail.d/smb2.durable-v2-open.bug15649
new file mode 100644
index 00000000000..748b6c3150e
--- /dev/null
+++ b/selftest/knownfail.d/smb2.durable-v2-open.bug15649
@@ -0,0 +1,2 @@
+^samba3.smb2.durable-v2-open.stat-and-lease
+^samba3.smb2.durable-v2-open.nonstat-and-lease
diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15651 b/selftest/knownfail.d/smb2.durable-v2-open.bug15651
new file mode 100644
index 00000000000..1bb0a70d9a0
--- /dev/null
+++ b/selftest/knownfail.d/smb2.durable-v2-open.bug15651
@@ -0,0 +1,3 @@
+^samba3.smb2.durable-v2-open.statRH-and-lease
+^samba3.smb2.durable-v2-open.two-same-lease
+^samba3.smb2.durable-v2-open.two-different-lease
diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c
index 685ef80c0cc..e86b1955092 100644
--- a/source4/torture/smb2/durable_v2_open.c
+++ b/source4/torture/smb2/durable_v2_open.c
@@ -26,6 +26,7 @@
#include "torture/torture.h"
#include "torture/smb2/proto.h"
#include "librpc/ndr/libndr.h"
+#include "lease_break_handler.h"
#define CHECK_VAL(v, correct) \
torture_assert_u64_equal_goto(tctx, v, correct, ret, done, __location__)
@@ -2064,6 +2065,784 @@ static bool test_durable_v2_open_lock_noW_lease(struct torture_context *tctx,
return ret;
}
+/**
+ * 1. stat open (without lease) => h1
+ * 2. durable open with RWH => h2
+ * 3. disconnect
+ * 4. reconnect
+ * 5. durable reconnect RWH => h2
+ */
+static bool test_durable_v2_open_stat_and_lease(struct torture_context *tctx,
+ struct smb2_tree *tree1)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls;
+ uint64_t lease_key;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ smb2_generic_create(&io, NULL, false /* dir */, fname,
+ FILE_OPEN_IF, 0, 0, 0);
+ io.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
+ io.in.desired_access |= SEC_FILE_WRITE_ATTRIBUTE;
+ io.in.desired_access |= SEC_STD_SYNCHRONIZE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE);
+
+ lease_key = random();
+ smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
+ lease_key, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+ h1 = NULL;
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h2;
+ io.in.create_guid = create_guid2;
+ io.in.lease_request_v2 = &ls;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ status = smb2_util_close(tree1, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+
+ if (tree1 != NULL && h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+ if (tree1 != NULL && h2 != NULL) {
+ smb2_util_close(tree1, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. non stat open (without a lease) => h1
+ * 2. durable open with RWH => h2 => RH
+ * 3. disconnect
+ * 4. reconnect
+ * 5. durable reconnect RH => h2
+ */
+static bool test_durable_v2_open_nonstat_and_lease(struct torture_context *tctx,
+ struct smb2_tree *tree1)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls;
+ uint64_t lease_key;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ smb2_generic_create(&io, NULL, false /* dir */, fname,
+ FILE_OPEN_IF, 0, 0, 0);
+ io.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
+ io.in.desired_access |= SEC_FILE_WRITE_ATTRIBUTE;
+ io.in.desired_access |= SEC_STD_SYNCHRONIZE;
+ /*
+ * SEC_STD_READ_CONTROL means we no longer
+ * have a stat open that would allow a RWH lease
+ */
+ io.in.desired_access |= SEC_STD_READ_CONTROL;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE);
+
+ lease_key = random();
+ smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
+ lease_key, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+ h1 = NULL;
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h2;
+ io.in.create_guid = create_guid2;
+ io.in.lease_request_v2 = &ls;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ status = smb2_util_close(tree1, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+
+ if (tree1 != NULL && h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+ if (tree1 != NULL && h2 != NULL) {
+ smb2_util_close(tree1, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. stat open with RH lease => h1
+ * 2. durable open with RWH => h2 => RH
+ * 3. disconnect
+ * 4. reconnect
+ * 5. durable reconnect RH => h2
+ */
+static bool test_durable_v2_open_statRH_and_lease(struct torture_context *tctx,
+ struct smb2_tree *tree1)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls;
+ uint64_t lease_key;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ smb2_generic_create(&io, NULL, false /* dir */, fname,
+ FILE_OPEN_IF, 0, 0, 0);
+ lease_key = random();
+ smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
+ lease_key, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
+ ls.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ lease_key = random();
+ smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
+ lease_key, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+ h1 = NULL;
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h2;
+ io.in.create_guid = create_guid2;
+ io.in.lease_request_v2 = &ls;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ status = smb2_util_close(tree1, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+
+ if (tree1 != NULL && h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+ if (tree1 != NULL && h2 != NULL) {
+ smb2_util_close(tree1, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1(RWH) => h1
+ * 2. durable open with L1(RWH) => h2
+ * 3. disconnect
+ * 4. reconnect
+ * 5. durable reconnect L1(RWH) => h1
+ * 6. durable reconnect L1(RWH) => h2
+ */
+static bool test_durable_v2_open_two_same_lease(struct torture_context *tctx,
+ struct smb2_tree *tree1)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1 = GUID_random();
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls;
+ uint64_t lease_key;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key = random();
+ smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
+ lease_key, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
+ lease_key, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1;
+ io.in.create_guid = create_guid1;
+ io.in.lease_request_v2 = &ls;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h2;
+ io.in.create_guid = create_guid2;
+ io.in.lease_request_v2 = &ls;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key,
+ 0, 0, ls.lease_epoch);
+
+ status = smb2_util_close(tree1, *h1);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1 = NULL;
+
+ status = smb2_util_close(tree1, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+
+ if (tree1 != NULL && h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+ if (tree1 != NULL && h2 != NULL) {
+ smb2_util_close(tree1, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1(RH) => h1
+ * 2. durable open with L2(RH) => h2
+ * 3. disconnect
+ * 4. reconnect
+ * 5. durable reconnect L1(RH) => h1
+ * 6. durable reconnect L2(RH) => h2
+ */
+static bool test_durable_v2_open_two_different_leases(struct torture_context *tctx,
+ struct smb2_tree *tree1)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1 = GUID_random();
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls1;
+ uint64_t lease_key1;
+ struct smb2_lease ls2;
+ uint64_t lease_key2;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1 = random();
+ smb2_lease_v2_create(&io, &ls1, false /* dir */, fname,
+ lease_key1, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1,
+ 0, 0, ls1.lease_epoch);
+
+ lease_key2 = random();
+ smb2_lease_v2_create(&io, &ls2, false /* dir */, fname,
+ lease_key2, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls2.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key2,
+ 0, 0, ls2.lease_epoch);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1;
+ io.in.create_guid = create_guid1;
+ io.in.lease_request_v2 = &ls1;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1,
+ 0, 0, ls1.lease_epoch);
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h2;
+ io.in.create_guid = create_guid2;
+ io.in.lease_request_v2 = &ls2;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key2,
+ 0, 0, ls2.lease_epoch);
+
+ status = smb2_util_close(tree1, *h1);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1 = NULL;
+
+ status = smb2_util_close(tree1, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+
+ if (tree1 != NULL && h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+ if (tree1 != NULL && h2 != NULL) {
+ smb2_util_close(tree1, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
/**
* Test durable request / reconnect with AppInstanceId
*/
@@ -2493,6 +3272,11 @@ struct torture_suite *torture_smb2_durable_v2_open_init(TALLOC_CTX *ctx)
torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_v2_open_lock_oplock);
torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_v2_open_lock_lease);
torture_suite_add_1smb2_test(suite, "lock-noW-lease", test_durable_v2_open_lock_noW_lease);
+ torture_suite_add_1smb2_test(suite, "stat-and-lease", test_durable_v2_open_stat_and_lease);
+ torture_suite_add_1smb2_test(suite, "nonstat-and-lease", test_durable_v2_open_nonstat_and_lease);
+ torture_suite_add_1smb2_test(suite, "statRH-and-lease", test_durable_v2_open_statRH_and_lease);
+ torture_suite_add_1smb2_test(suite, "two-same-lease", test_durable_v2_open_two_same_lease);
+ torture_suite_add_1smb2_test(suite, "two-different-lease", test_durable_v2_open_two_different_leases);
torture_suite_add_2smb2_test(suite, "app-instance", test_durable_v2_open_app_instance);
torture_suite_add_1smb2_test(suite, "persistent-open-oplock", test_persistent_open_oplock);
torture_suite_add_1smb2_test(suite, "persistent-open-lease", test_persistent_open_lease);
--
2.53.0
From 7c395c25b4e2d0365ef8c99404e5e9e88f0adf2d Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Wed, 4 Sep 2024 18:18:43 +0200
Subject: [PATCH 077/122] s4:torture/smb2: add
smb2.durable-v2-open.{keep,purge}-disconnected-* tests
These demonstrate which durables handles are kept and which are purged
because of various opens, writes or renames.
smb2.durable-v2-open.keep-disconnected-rh-with-stat-open
smb2.durable-v2-open.keep-disconnected-rh-with-rh-open
smb2.durable-v2-open.keep-disconnected-rh-with-rwh-open
smb2.durable-v2-open.keep-disconnected-rwh-with-stat-open
smb2.durable-v2-open.purge-disconnected-rwh-with-rwh-open
smb2.durable-v2-open.purge-disconnected-rwh-with-rh-open
smb2.durable-v2-open.purge-disconnected-rh-with-share-none-open
smb2.durable-v2-open.purge-disconnected-rh-with-write
smb2.durable-v2-open.purge-disconnected-rh-with-rename
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15708
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit 9e98cd5c7a180521026b0d73a330bdaf2c8af73a)
---
.../knownfail.d/smb2.durable-v2-open.bug15651 | 2 +
.../knownfail.d/smb2.durable-v2-open.bug15708 | 7 +
source4/torture/smb2/durable_v2_open.c | 1851 +++++++++++++++++
3 files changed, 1860 insertions(+)
create mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15708
diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15651 b/selftest/knownfail.d/smb2.durable-v2-open.bug15651
index 1bb0a70d9a0..1702a3a6580 100644
--- a/selftest/knownfail.d/smb2.durable-v2-open.bug15651
+++ b/selftest/knownfail.d/smb2.durable-v2-open.bug15651
@@ -1,3 +1,5 @@
^samba3.smb2.durable-v2-open.statRH-and-lease
^samba3.smb2.durable-v2-open.two-same-lease
^samba3.smb2.durable-v2-open.two-different-lease
+^samba3.smb2.durable-v2-open.keep-disconnected-rh-with-stat-open
+^samba3.smb2.durable-v2-open.keep-disconnected-rwh-with-stat-open
diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15708 b/selftest/knownfail.d/smb2.durable-v2-open.bug15708
new file mode 100644
index 00000000000..3a6380c6d65
--- /dev/null
+++ b/selftest/knownfail.d/smb2.durable-v2-open.bug15708
@@ -0,0 +1,7 @@
+#
+# https://bugzilla.samba.org/show_bug.cgi?id=15708 is not fixed
+# yet, it requires some complex changes within handle_share_mode_lease()
+# merging logic of open_mode_check() and delay_for_oplock()...
+#
+^samba3.smb2.durable-v2-open.keep-disconnected-rh-with-rh-open
+^samba3.smb2.durable-v2-open.keep-disconnected-rh-with-rwh-open
diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c
index e86b1955092..104796e76ad 100644
--- a/source4/torture/smb2/durable_v2_open.c
+++ b/source4/torture/smb2/durable_v2_open.c
@@ -2843,6 +2843,1848 @@ done:
return ret;
}
+/**
+ * 1. durable open with L1A(RH) on tree1 => h1a
+ * 1. durable open with L1B(RH) on tree1 => h1b
+ * 2. disconnect tree1
+ * 3. stat open on tree2 => h2
+ * 4. reconnect tree1
+ * 5. durable reconnect L1A(RH) => h1a
+ * 6. durable reconnect L1B(RH) => h1a
+ */
+static bool test_durable_v2_open_keep_disconnected_rh_with_stat_open(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1a;
+ struct smb2_handle *h1a = NULL;
+ struct smb2_handle _h1b;
+ struct smb2_handle *h1b = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1a = GUID_random();
+ struct GUID create_guid1b = GUID_random();
+ struct smb2_lease ls1a;
+ uint64_t lease_key1a;
+ struct smb2_lease ls1b;
+ uint64_t lease_key1b;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1a = random();
+ smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname,
+ lease_key1a, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1a;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1a.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ lease_key1b = random();
+ smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname,
+ lease_key1b, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1b;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1b.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ CHECK_NO_BREAK(tctx);
+
+ smb2_generic_create(&io, NULL, false /* dir */, fname,
+ FILE_OPEN_IF, 0, 0, 0);
+ io.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false);
+ CHECK_VAL(io.out.persistent_open, false);
+
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1a;
+ io.in.create_guid = create_guid1a;
+ io.in.lease_request_v2 = &ls1a;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1b;
+ io.in.create_guid = create_guid1b;
+ io.in.lease_request_v2 = &ls1b;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ status = smb2_util_close(tree1, *h1a);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1a = NULL;
+
+ status = smb2_util_close(tree1, *h1b);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1b = NULL;
+
+ status = smb2_util_close(tree2, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1a != NULL) {
+ smb2_util_close(tree1, *h1a);
+ }
+ if (tree1 != NULL && h1b != NULL) {
+ smb2_util_close(tree1, *h1b);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1A(RH) on tree1 => h1a
+ * 1. durable open with L1B(RH) on tree1 => h1b
+ * 2. disconnect tree1
+ * 3. durable open with L2(RH) on tree2 => h2
+ * 4. reconnect tree1
+ * 5. durable reconnect L1A(RH) => h1a
+ * 6. durable reconnect L1B(RH) => h1a
+ */
+static bool test_durable_v2_open_keep_disconnected_rh_with_rh_open(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1a;
+ struct smb2_handle *h1a = NULL;
+ struct smb2_handle _h1b;
+ struct smb2_handle *h1b = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1a = GUID_random();
+ struct GUID create_guid1b = GUID_random();
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls1a;
+ uint64_t lease_key1a;
+ struct smb2_lease ls1b;
+ uint64_t lease_key1b;
+ struct smb2_lease ls2;
+ uint64_t lease_key2;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1a = random();
+ smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname,
+ lease_key1a, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1a;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1a.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ lease_key1b = random();
+ smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname,
+ lease_key1b, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1b;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1b.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ CHECK_NO_BREAK(tctx);
+
+ lease_key2 = random();
+ smb2_lease_v2_create(&io, &ls2, false /* dir */, fname,
+ lease_key2, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls2.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key2,
+ 0, 0, ls2.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1a;
+ io.in.create_guid = create_guid1a;
+ io.in.lease_request_v2 = &ls1a;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1b;
+ io.in.create_guid = create_guid1b;
+ io.in.lease_request_v2 = &ls1b;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ status = smb2_util_close(tree1, *h1a);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1a = NULL;
+
+ status = smb2_util_close(tree1, *h1b);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1b = NULL;
+
+ status = smb2_util_close(tree2, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1a != NULL) {
+ smb2_util_close(tree1, *h1a);
+ }
+ if (tree1 != NULL && h1b != NULL) {
+ smb2_util_close(tree1, *h1b);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1A(RH) on tree1 => h1a
+ * 1. durable open with L1B(RH) on tree1 => h1b
+ * 2. disconnect tree1
+ * 3. durable open with L2(RWH) on tree2 => h2 => RH
+ * 4. reconnect tree1
+ * 5. durable reconnect L1A(RH) => h1a
+ * 6. durable reconnect L1B(RH) => h1a
+ */
+static bool test_durable_v2_open_keep_disconnected_rh_with_rwh_open(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1a;
+ struct smb2_handle *h1a = NULL;
+ struct smb2_handle _h1b;
+ struct smb2_handle *h1b = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1a = GUID_random();
+ struct GUID create_guid1b = GUID_random();
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls1a;
+ uint64_t lease_key1a;
+ struct smb2_lease ls1b;
+ uint64_t lease_key1b;
+ struct smb2_lease ls2;
+ uint64_t lease_key2;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1a = random();
+ smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname,
+ lease_key1a, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1a;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1a.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ lease_key1b = random();
+ smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname,
+ lease_key1b, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1b;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1b.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ CHECK_NO_BREAK(tctx);
+
+ lease_key2 = random();
+ smb2_lease_v2_create(&io, &ls2, false /* dir */, fname,
+ lease_key2, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls2.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key2,
+ 0, 0, ls2.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1a;
+ io.in.create_guid = create_guid1a;
+ io.in.lease_request_v2 = &ls1a;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1b;
+ io.in.create_guid = create_guid1b;
+ io.in.lease_request_v2 = &ls1b;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ status = smb2_util_close(tree1, *h1a);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1a = NULL;
+
+ status = smb2_util_close(tree1, *h1b);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1b = NULL;
+
+ status = smb2_util_close(tree2, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1a != NULL) {
+ smb2_util_close(tree1, *h1a);
+ }
+ if (tree1 != NULL && h1b != NULL) {
+ smb2_util_close(tree1, *h1b);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1(RWH) on tree1 => h1
+ * 2. disconnect tree1
+ * 3. stat open on tree2 => h2
+ * 4. reconnect tree1
+ * 5. durable reconnect L1(RWH) => h1
+ */
+static bool test_durable_v2_open_keep_disconnected_rwh_with_stat_open(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1 = GUID_random();
+ struct smb2_lease ls1;
+ uint64_t lease_key1;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1 = random();
+ smb2_lease_v2_create(&io, &ls1, false /* dir */, fname,
+ lease_key1, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key1,
+ 0, 0, ls1.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ CHECK_NO_BREAK(tctx);
+
+ smb2_generic_create(&io, NULL, false /* dir */, fname,
+ FILE_OPEN_IF, 0, 0, 0);
+ io.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false);
+ CHECK_VAL(io.out.persistent_open, false);
+
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1;
+ io.in.create_guid = create_guid1;
+ io.in.lease_request_v2 = &ls1;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key1,
+ 0, 0, ls1.lease_epoch);
+
+ status = smb2_util_close(tree1, *h1);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1 = NULL;
+
+ status = smb2_util_close(tree2, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1(RWH) on tree1 => h1
+ * 2. disconnect tree1
+ * 3. durable open with L2(RWH) on tree2 => h2
+ * 4. reconnect tree1
+ * 5. durable reconnect L1(RH) => h1 => not found
+ */
+static bool test_durable_v2_open_purge_disconnected_rwh_with_rwh_open(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1 = GUID_random();
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls1;
+ uint64_t lease_key1;
+ struct smb2_lease ls2;
+ uint64_t lease_key2;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1 = random();
+ smb2_lease_v2_create(&io, &ls1, false /* dir */, fname,
+ lease_key1, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key1,
+ 0, 0, ls1.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ CHECK_NO_BREAK(tctx);
+
+ lease_key2 = random();
+ smb2_lease_v2_create(&io, &ls2, false /* dir */, fname,
+ lease_key2, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls2.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key2,
+ 0, 0, ls2.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1;
+ io.in.create_guid = create_guid1;
+ io.in.lease_request_v2 = &ls1;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+ ls1.lease_state = smb2_util_lease_state("RH");
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+ status = smb2_util_close(tree2, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1(RWH) on tree1 => h1
+ * 2. disconnect tree1
+ * 3. durable open with L2(RH) on tree2 => h2
+ * 4. reconnect tree1
+ * 5. durable reconnect L1(RH) => h1 => not found
+ */
+static bool test_durable_v2_open_purge_disconnected_rwh_with_rh_open(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1 = GUID_random();
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls1;
+ uint64_t lease_key1;
+ struct smb2_lease ls2;
+ uint64_t lease_key2;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1 = random();
+ smb2_lease_v2_create(&io, &ls1, false /* dir */, fname,
+ lease_key1, 0, /* parent lease key */
+ smb2_util_lease_state("RWH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RWH", true, lease_key1,
+ 0, 0, ls1.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ CHECK_NO_BREAK(tctx);
+
+ lease_key2 = random();
+ smb2_lease_v2_create(&io, &ls2, false /* dir */, fname,
+ lease_key2, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls2.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key2,
+ 0, 0, ls2.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1;
+ io.in.create_guid = create_guid1;
+ io.in.lease_request_v2 = &ls1;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+ ls1.lease_state = smb2_util_lease_state("RH");
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+ status = smb2_util_close(tree2, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1A(RH) on tree1 => h1a
+ * 2. durable open with L1B(RH) on tree1 => h1b
+ * 3. disconnect tree1
+ * 4. open with SHARE_NONE on tree2 => h2
+ * 5. reconnect tree1
+ * 6. durable reconnect L1A(RH) => not found
+ * 7. durable reconnect L1B(RH) => not found
+ */
+static bool test_durable_v2_open_purge_disconnected_rh_with_share_none_open(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1a;
+ struct smb2_handle *h1a = NULL;
+ struct smb2_handle _h1b;
+ struct smb2_handle *h1b = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1a = GUID_random();
+ struct GUID create_guid1b = GUID_random();
+ struct smb2_lease ls1a;
+ uint64_t lease_key1a;
+ struct smb2_lease ls1b;
+ uint64_t lease_key1b;
+ bool ret = true;
+ struct smbcli_options options1;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options1 = tree1->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1a = random();
+ smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname,
+ lease_key1a, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1a;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1a.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ lease_key1b = random();
+ smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname,
+ lease_key1b, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1b;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1b.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree1);
+
+ CHECK_NO_BREAK(tctx);
+
+ smb2_generic_create_share(&io, &ls1a, false /* dir */, fname,
+ NTCREATEX_DISP_OPEN_IF,
+ FILE_SHARE_NONE,
+ SMB2_OPLOCK_LEVEL_NONE, 0, 0);
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, false);
+ CHECK_VAL(io.out.persistent_open, false);
+
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1a;
+ io.in.create_guid = create_guid1a;
+ io.in.lease_request_v2 = &ls1a;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ h1a = NULL;
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h1b;
+ io.in.create_guid = create_guid1b;
+ io.in.lease_request_v2 = &ls1b;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ h1b = NULL;
+
+ status = smb2_util_close(tree2, *h2);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h2 = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1a != NULL) {
+ smb2_util_close(tree1, *h1a);
+ }
+ if (tree1 != NULL && h1b != NULL) {
+ smb2_util_close(tree1, *h1b);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1A(RH) on tree1 => h1a
+ * 2. durable open with L1B(RH) on tree1 => h1b
+ * 3. durable open with L2(RH) on tree2 => h2
+ * 4. disconnect tree2
+ * 5.1 write to h1a
+ * 5.2 lease break to NONE for L1B (ack requested, but ignored)
+ * 6. reconnect tree2
+ * 7. durable reconnect L2(RH) => h2 => not found
+ * 8. close h1a
+ * 9. durable open with L1A(RWH) on tree1 => h1a only RH
+ */
+static bool test_durable_v2_open_purge_disconnected_rh_with_write(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[256];
+ struct smb2_handle dh;
+ struct smb2_handle _h1a;
+ struct smb2_handle *h1a = NULL;
+ struct smb2_handle _h1b;
+ struct smb2_handle *h1b = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1a = GUID_random();
+ struct GUID create_guid1b = GUID_random();
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls1a;
+ uint64_t lease_key1a;
+ struct smb2_lease ls1b;
+ uint64_t lease_key1b;
+ struct smb2_lease ls2;
+ uint64_t lease_key2;
+ struct smb2_write wrt;
+ bool ret = true;
+ struct smbcli_options options2;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options2 = tree2->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 256, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+
+ smb2_util_unlink(tree1, fname);
+
+ lease_key1a = random();
+ smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname,
+ lease_key1a, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1a;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1a.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ lease_key1b = random();
+ smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname,
+ lease_key1b, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1b;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1b.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ lease_key2 = random();
+ smb2_lease_v2_create(&io, &ls2, false /* dir */, fname,
+ lease_key2, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls2.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key2,
+ 0, 0, ls2.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree2);
+
+ CHECK_NO_BREAK(tctx);
+ lease_break_info.lease_skip_ack = true;
+
+ ZERO_STRUCT(wrt);
+ wrt.in.file.handle = *h1a;
+ wrt.in.offset = 0;
+ wrt.in.data = data_blob_string_const("data");
+ status = smb2_write(tree1, &wrt);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ ls1b.lease_epoch += 1;
+ CHECK_BREAK_INFO_V2(tree1->session->transport,
+ "RH", "", lease_key1b, ls1b.lease_epoch);
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options2, &tree2)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h2;
+ io.in.create_guid = create_guid2;
+ io.in.lease_request_v2 = &ls2;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ h2 = NULL;
+
+ status = smb2_util_close(tree1, *h1a);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1a = NULL;
+
+ /*
+ * Now there's only lease_key2 with state NONE
+ *
+ * And that means an additional open still
+ * only gets RH...
+ */
+ smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname,
+ lease_key1a, 0, /* parent lease key */
+ smb2_util_lease_state("RHW"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1a;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED);
+ CHECK_VAL(io.out.size, wrt.in.data.length);
+ CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1a.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ status = smb2_util_close(tree1, *h1a);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1a = NULL;
+
+ status = smb2_util_close(tree1, *h1b);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1b = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1a != NULL) {
+ smb2_util_close(tree1, *h1a);
+ }
+ if (tree1 != NULL && h1b != NULL) {
+ smb2_util_close(tree1, *h1b);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * 1. durable open with L1A(RH) on tree1 => h1a
+ * 2. durable open with L1B(RH) on tree1 => h1b
+ * 3. durable open with L2(RH) on tree2 => h2
+ * 4. disconnect tree2
+ * 5.1 rename h1a
+ * 5.2 lease break to R for L1B (ack requested, and required)
+ * 6. reconnect tree2
+ * 7. durable reconnect L2(RH) => h2 => not found
+ */
+static bool test_durable_v2_open_purge_disconnected_rh_with_rename(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ NTSTATUS status;
+ char fname[128];
+ char fname_renamed[140];
+ struct smb2_handle dh;
+ struct smb2_handle _h1a;
+ struct smb2_handle *h1a = NULL;
+ struct smb2_handle _h1b;
+ struct smb2_handle *h1b = NULL;
+ struct smb2_handle _h2;
+ struct smb2_handle *h2 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid1a = GUID_random();
+ struct GUID create_guid1b = GUID_random();
+ struct GUID create_guid2 = GUID_random();
+ struct smb2_lease ls1a;
+ uint64_t lease_key1a;
+ struct smb2_lease ls1b;
+ uint64_t lease_key1b;
+ struct smb2_lease ls2;
+ uint64_t lease_key2;
+ union smb_setfileinfo sinfo;
+ bool ret = true;
+ struct smbcli_options options2;
+ uint32_t caps;
+
+ caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+ if (!(caps & SMB2_CAP_LEASING)) {
+ torture_skip(tctx, "leases are not supported");
+ }
+
+ options2 = tree2->session->transport->options;
+
+ tree1->session->transport->lease.handler = torture_lease_handler;
+ tree1->session->transport->lease.private_data = tree1;
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+
+ smb2_deltree(tree1, __func__);
+ status = torture_smb2_testdir(tree1, __func__, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed\n");
+ smb2_util_close(tree1, dh);
+
+ /* Choose a random name in case the state is left a little funky. */
+ snprintf(fname, 128, "%s\\file_%s.dat",
+ __func__, generate_random_str(tctx, 8));
+ snprintf(fname_renamed, 140, "%s.renamed", fname);
+
+ smb2_util_unlink(tree1, fname);
+ smb2_util_unlink(tree1, fname_renamed);
+
+ lease_key1a = random();
+ smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname,
+ lease_key1a, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1a;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1a = io.out.file.handle;
+ h1a = &_h1a;
+ CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1a.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1a,
+ 0, 0, ls1a.lease_epoch);
+
+ lease_key1b = random();
+ smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname,
+ lease_key1b, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid1b;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1b = io.out.file.handle;
+ h1b = &_h1b;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls1b.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key1b,
+ 0, 0, ls1b.lease_epoch);
+
+ lease_key2 = random();
+ smb2_lease_v2_create(&io, &ls2, false /* dir */, fname,
+ lease_key2, 0, /* parent lease key */
+ smb2_util_lease_state("RH"), 0 /* lease epoch */);
+ io.in.durable_open = false;
+ io.in.durable_open_v2 = true;
+ io.in.persistent_open = false;
+ io.in.create_guid = create_guid2;
+ io.in.timeout = UINT32_MAX;
+
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h2 = io.out.file.handle;
+ h2 = &_h2;
+ CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+ CHECK_VAL(io.out.durable_open, false);
+ CHECK_VAL(io.out.durable_open_v2, true);
+ CHECK_VAL(io.out.persistent_open, false);
+ CHECK_VAL(io.out.timeout, 300*1000);
+ ls2.lease_epoch += 1;
+ CHECK_LEASE_V2(&io, "RH", true, lease_key2,
+ 0, 0, ls2.lease_epoch);
+
+ CHECK_NO_BREAK(tctx);
+
+ /* disconnect, reconnect and then do durable reopen */
+ TALLOC_FREE(tree2);
+
+ CHECK_NO_BREAK(tctx);
+
+ ZERO_STRUCT(sinfo);
+ sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
+ sinfo.rename_information.in.file.handle = *h1a;
+ sinfo.rename_information.in.overwrite = 0;
+ sinfo.rename_information.in.root_fid = 0;
+ sinfo.rename_information.in.new_name = fname_renamed;
+ status = smb2_setinfo_file(tree1, &sinfo);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ ls1b.lease_epoch += 1;
+ CHECK_BREAK_INFO_V2(tree1->session->transport,
+ "RH", "R", lease_key1b, ls1b.lease_epoch);
+ torture_reset_lease_break_info(tctx, &lease_break_info);
+ CHECK_NO_BREAK(tctx);
+
+ if (!torture_smb2_connection_ext(tctx, 0, &options2, &tree2)) {
+ torture_warning(tctx, "couldn't reconnect, bailing\n");
+ ret = false;
+ goto done;
+ }
+
+ tree2->session->transport->lease.handler = torture_lease_handler;
+ tree2->session->transport->lease.private_data = tree2;
+
+ ZERO_STRUCT(io);
+ io.in.fname = fname;
+ io.in.durable_open_v2 = false;
+ io.in.durable_handle_v2 = h2;
+ io.in.create_guid = create_guid2;
+ io.in.lease_request_v2 = &ls2;
+ io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+
+ status = smb2_create(tree2, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ h2 = NULL;
+
+ status = smb2_util_close(tree1, *h1a);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1a = NULL;
+
+ status = smb2_util_close(tree1, *h1b);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ h1b = NULL;
+
+ CHECK_NO_BREAK(tctx);
+
+done:
+ if (tree1 != NULL) {
+ smb2_keepalive(tree1->session->transport);
+ }
+ if (tree2 != NULL) {
+ smb2_keepalive(tree2->session->transport);
+ }
+ if (tree1 != NULL && h1a != NULL) {
+ smb2_util_close(tree1, *h1a);
+ }
+ if (tree1 != NULL && h1b != NULL) {
+ smb2_util_close(tree1, *h1b);
+ }
+ if (tree2 != NULL && h2 != NULL) {
+ smb2_util_close(tree2, *h2);
+ }
+
+ if (tree1 != NULL) {
+ smb2_util_unlink(tree1, fname);
+ smb2_util_unlink(tree1, fname_renamed);
+ smb2_deltree(tree1, __func__);
+
+ TALLOC_FREE(tree1);
+ }
+
+ TALLOC_FREE(tree2);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
/**
* Test durable request / reconnect with AppInstanceId
*/
@@ -3277,6 +5119,15 @@ struct torture_suite *torture_smb2_durable_v2_open_init(TALLOC_CTX *ctx)
torture_suite_add_1smb2_test(suite, "statRH-and-lease", test_durable_v2_open_statRH_and_lease);
torture_suite_add_1smb2_test(suite, "two-same-lease", test_durable_v2_open_two_same_lease);
torture_suite_add_1smb2_test(suite, "two-different-lease", test_durable_v2_open_two_different_leases);
+ torture_suite_add_2smb2_test(suite, "keep-disconnected-rh-with-stat-open", test_durable_v2_open_keep_disconnected_rh_with_stat_open);
+ torture_suite_add_2smb2_test(suite, "keep-disconnected-rh-with-rh-open", test_durable_v2_open_keep_disconnected_rh_with_rh_open);
+ torture_suite_add_2smb2_test(suite, "keep-disconnected-rh-with-rwh-open", test_durable_v2_open_keep_disconnected_rh_with_rwh_open);
+ torture_suite_add_2smb2_test(suite, "keep-disconnected-rwh-with-stat-open", test_durable_v2_open_keep_disconnected_rwh_with_stat_open);
+ torture_suite_add_2smb2_test(suite, "purge-disconnected-rwh-with-rwh-open", test_durable_v2_open_purge_disconnected_rwh_with_rwh_open);
+ torture_suite_add_2smb2_test(suite, "purge-disconnected-rwh-with-rh-open", test_durable_v2_open_purge_disconnected_rwh_with_rh_open);
+ torture_suite_add_2smb2_test(suite, "purge-disconnected-rh-with-share-none-open", test_durable_v2_open_purge_disconnected_rh_with_share_none_open);
+ torture_suite_add_2smb2_test(suite, "purge-disconnected-rh-with-write", test_durable_v2_open_purge_disconnected_rh_with_write);
+ torture_suite_add_2smb2_test(suite, "purge-disconnected-rh-with-rename", test_durable_v2_open_purge_disconnected_rh_with_rename);
torture_suite_add_2smb2_test(suite, "app-instance", test_durable_v2_open_app_instance);
torture_suite_add_1smb2_test(suite, "persistent-open-oplock", test_persistent_open_oplock);
torture_suite_add_1smb2_test(suite, "persistent-open-lease", test_persistent_open_lease);
--
2.53.0
From 9b8a74aac964841fce8d032487678513a438274a Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 29 Aug 2024 20:20:23 +0200
Subject: [PATCH 078/122] s3:smbd: let durable_reconnect_fn already check for a
disconnected handle with the correct file_id
We'll soon allow more than one disconnected durable handle, so
we need to find the correct one instead of assuming only a single
one.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit 2869bd1a507e7376f0bb0ec68ed4e045b043cfdb)
---
source3/smbd/durable.c | 29 +++++++++++++++++++++--------
1 file changed, 21 insertions(+), 8 deletions(-)
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
index b7fa53e7555..bf3cbed0a2c 100644
--- a/source3/smbd/durable.c
+++ b/source3/smbd/durable.c
@@ -506,19 +506,33 @@ static bool vfs_default_durable_reconnect_check_stat(
return true;
}
+struct durable_reconnect_state {
+ struct smbXsrv_open *op;
+ struct share_mode_entry *e;
+};
+
static bool durable_reconnect_fn(
struct share_mode_entry *e,
bool *modified,
void *private_data)
{
- struct share_mode_entry *dst_e = private_data;
+ struct durable_reconnect_state *state = private_data;
+ uint64_t id = state->op->global->open_persistent_id;
+
+ if (e->share_file_id != id) {
+ return false; /* Look at potential other entries */
+ }
- if (dst_e->pid.pid != 0) {
+ if (!server_id_is_disconnected(&e->pid)) {
+ return false; /* Look at potential other entries */
+ }
+
+ if (state->e->share_file_id == id) {
DBG_INFO("Found more than one entry, invalidating previous\n");
- dst_e->pid.pid = 0;
+ *state->e = (struct share_mode_entry) { .pid = { .pid = 0, }};
return true; /* end the loop through share mode entries */
}
- *dst_e = *e;
+ *state->e = *e;
return false; /* Look at potential other entries */
}
@@ -533,7 +547,8 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
const struct loadparm_substitution *lp_sub =
loadparm_s3_global_substitution();
struct share_mode_lock *lck;
- struct share_mode_entry e;
+ struct share_mode_entry e = { .pid = { .pid = 0, }};
+ struct durable_reconnect_state rstate = { .op = op, .e = &e, };
struct files_struct *fsp = NULL;
NTSTATUS status;
bool ok;
@@ -626,9 +641,7 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
- e = (struct share_mode_entry) { .pid.pid = 0 };
-
- ok = share_mode_forall_entries(lck, durable_reconnect_fn, &e);
+ ok = share_mode_forall_entries(lck, durable_reconnect_fn, &rstate);
if (!ok) {
DBG_WARNING("share_mode_forall_entries failed\n");
status = NT_STATUS_INTERNAL_DB_ERROR;
--
2.53.0
From 2aa438545f3d31dfb2be6c054e3dcbd2c4707caa Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 29 Aug 2024 18:43:14 +0200
Subject: [PATCH 079/122] s3:smbd: allow reset_share_mode_entry() to handle
more than one durable handle
This means that multiple durable handles with RH leases can
co-exist now... Before only the last remaining durable handle
was able to pass the SMB_VFS_DURABLE_DISCONNECT() step.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit b1e5f5d8d2852b66ca4c858d14d367ffe228a88d)
---
.../knownfail.d/smb2.durable-v2-open.bug15651 | 5 -
source3/locking/share_mode_lock.c | 315 ++++++++++++++++--
2 files changed, 293 insertions(+), 27 deletions(-)
delete mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15651
diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15651 b/selftest/knownfail.d/smb2.durable-v2-open.bug15651
deleted file mode 100644
index 1702a3a6580..00000000000
--- a/selftest/knownfail.d/smb2.durable-v2-open.bug15651
+++ /dev/null
@@ -1,5 +0,0 @@
-^samba3.smb2.durable-v2-open.statRH-and-lease
-^samba3.smb2.durable-v2-open.two-same-lease
-^samba3.smb2.durable-v2-open.two-different-lease
-^samba3.smb2.durable-v2-open.keep-disconnected-rh-with-stat-open
-^samba3.smb2.durable-v2-open.keep-disconnected-rwh-with-stat-open
diff --git a/source3/locking/share_mode_lock.c b/source3/locking/share_mode_lock.c
index 3fc7d56562a..4bbccdcd3bd 100644
--- a/source3/locking/share_mode_lock.c
+++ b/source3/locking/share_mode_lock.c
@@ -2703,16 +2703,25 @@ bool reset_share_mode_entry(
struct share_mode_data *d = NULL;
TDB_DATA key = locking_key(&id);
struct locking_tdb_data *ltdb = NULL;
- struct share_mode_entry e;
+ struct share_mode_entry e = { .pid.pid = 0 };
struct share_mode_entry_buf e_buf;
+ size_t old_idx;
+ size_t new_idx;
+ bool found;
NTSTATUS status;
- int cmp;
bool ret = false;
bool ok;
+ struct file_id_buf id_buf;
+ struct server_id_buf pid_buf1;
+ struct server_id_buf pid_buf2;
+ size_t low_idx1, low_idx2, low_num;
+ size_t mid_idx1, mid_idx2, mid_num;
+ size_t high_idx1, high_idx2, high_num;
+ TDB_DATA dbufs[4];
+ size_t num_dbufs = 0;
status = share_mode_lock_access_private_data(lck, &d);
if (!NT_STATUS_IS_OK(status)) {
- struct file_id_buf id_buf;
/* Any error recovery possible here ? */
DBG_ERR("share_mode_lock_access_private_data() failed for "
"%s - %s\n",
@@ -2728,29 +2737,54 @@ bool reset_share_mode_entry(
return false;
}
- if (ltdb->num_share_entries != 1) {
- DBG_DEBUG("num_share_modes=%zu\n", ltdb->num_share_entries);
- goto done;
- }
+ DBG_DEBUG("%s - num_share_modes=%zu\n",
+ file_id_str_buf(id, &id_buf),
+ ltdb->num_share_entries);
- ok = share_mode_entry_get(ltdb->share_entries, &e);
- if (!ok) {
- DBG_WARNING("share_mode_entry_get failed\n");
+ new_idx = share_mode_entry_find(
+ ltdb->share_entries,
+ ltdb->num_share_entries,
+ new_pid,
+ new_share_file_id,
+ &e,
+ &found);
+ if (found) {
+ DBG_ERR("%s - num_share_modes=%zu "
+ "found NEW[%s][%"PRIu64"]\n",
+ file_id_str_buf(id, &id_buf),
+ ltdb->num_share_entries,
+ server_id_str_buf(new_pid, &pid_buf2),
+ new_share_file_id);
goto done;
}
- cmp = share_mode_entry_cmp(
- old_pid, old_share_file_id, e.pid, e.share_file_id);
- if (cmp != 0) {
- struct server_id_buf tmp1, tmp2;
- DBG_WARNING("Expected pid=%s, file_id=%"PRIu64", "
- "got pid=%s, file_id=%"PRIu64"\n",
- server_id_str_buf(old_pid, &tmp1),
- old_share_file_id,
- server_id_str_buf(e.pid, &tmp2),
- e.share_file_id);
+ old_idx = share_mode_entry_find(
+ ltdb->share_entries,
+ ltdb->num_share_entries,
+ old_pid,
+ old_share_file_id,
+ &e,
+ &found);
+ if (!found) {
+ DBG_WARNING("%s - num_share_modes=%zu "
+ "OLD[%s][%"PRIu64"] not found\n",
+ file_id_str_buf(id, &id_buf),
+ ltdb->num_share_entries,
+ server_id_str_buf(old_pid, &pid_buf1),
+ old_share_file_id);
goto done;
}
+ DBG_DEBUG("%s - num_share_modes=%zu "
+ "OLD[%s][%"PRIu64"] => idx=%zu "
+ "NEW[%s][%"PRIu64"] => idx=%zu\n",
+ file_id_str_buf(id, &id_buf),
+ ltdb->num_share_entries,
+ server_id_str_buf(old_pid, &pid_buf1),
+ old_share_file_id,
+ old_idx,
+ server_id_str_buf(new_pid, &pid_buf2),
+ new_share_file_id,
+ new_idx);
e.pid = new_pid;
if (new_mid != UINT64_MAX) {
@@ -2764,11 +2798,248 @@ bool reset_share_mode_entry(
goto done;
}
- ltdb->share_entries = e_buf.buf;
+ /*
+ * The logic to remove the existing
+ * entry and add the new one at the
+ * same time is a bit complex because
+ * we need to keep the entries sorted.
+ *
+ * The following examples should catch
+ * the corner cases and show that
+ * the {low,mid,high}_{idx1,num} are
+ * correctly calculated and the new
+ * entry is put before or after the mid
+ * elements...
+ *
+ * 1.
+ * 0
+ * 1
+ * 2 <- old_idx
+ * new_idx -> 3
+ * 3
+ * 4
+ *
+ * low_idx1 = 0;
+ * low_idx2 = MIN(old_idx, new_idx); => 2
+ * low_num = low_idx2 - low_idx1; => 2
+ *
+ * if (new < old) => new; => no
+ *
+ * mid_idx1 = MIN(old_idx+1, new_idx); => 3
+ * mid_idx2 = MAX(old_idx, new_idx); => 3
+ * mid_num = mid_idx2 - mid_idx1; => 0
+ *
+ * if (new >= old) => new; => yes
+ *
+ * high_idx1 = MAX(old_idx+1, new_idx); => 3
+ * high_idx2 = num_share_entries; => 5
+ * high_num = high_idx2 - high_idx1 = 2
+ *
+ * 2.
+ * 0
+ * 1
+ * new_idx -> 2
+ * 2 <- old_idx
+ * 3
+ * 4
+ *
+ * low_idx1 = 0;
+ * low_idx2 = MIN(old_idx, new_idx); => 2
+ * low_num = low_idx2 - low_idx1; => 2
+ *
+ * if (new < old) => new; => no
+ *
+ * mid_idx1 = MIN(old_idx+1, new_idx); => 2
+ * mid_idx2 = MAX(old_idx, new_idx); => 2
+ * mid_num = mid_idx2 - mid_idx1; => 0
+ *
+ * if (new >= old) => new; => yes
+ *
+ * high_idx1 = MAX(old_idx+1, new_idx); => 3
+ * high_idx2 = num_share_entries; => 5
+ * high_num = high_idx2 - high_idx1 = 2
+ *
+ * 3.
+ * 0
+ * 1 <- old_idx
+ * 2
+ * new_idx -> 3
+ * 3
+ * 4
+ *
+ * low_idx1 = 0;
+ * low_idx2 = MIN(old_idx, new_idx); => 1
+ * low_num = low_idx2 - low_idx1; => 1
+ *
+ * if (new < old) => new; => no
+ *
+ * mid_idx1 = MIN(old_idx+1, new_idx); => 2
+ * mid_idx2 = MAX(old_idx, new_idx); => 3
+ * mid_num = mid_idx2 - mid_idx1; => 1
+ *
+ * if (new >= old) => new; => yes
+ *
+ * high_idx1 = MAX(old_idx+1, new_idx); => 3
+ * high_idx2 = num_share_entries; => 5
+ * high_num = high_idx2 - high_idx1 = 2
+ *
+ * 4.
+ * 0
+ * new_idx -> 1
+ * 1
+ * 2
+ * 3 <- old_idx
+ * 4
+ *
+ * low_idx1 = 0;
+ * low_idx2 = MIN(old_idx, new_idx); => 1
+ * low_num = low_idx2 - low_idx1; => 1
+ *
+ * if (new < old) => new; => yes
+ *
+ * mid_idx1 = MIN(old_idx+1, new_idx); => 1
+ * mid_idx2 = MAX(old_idx, new_idx); => 3
+ * mid_num = mid_idx2 - mid_idx1; => 2
+ *
+ * if (new >= old) => new; => no
+ *
+ * high_idx1 = MAX(old_idx+1, new_idx); => 4
+ * high_idx2 = num_share_entries; => 5
+ * high_num = high_idx2 - high_idx1 = 1
+ *
+ * 5.
+ * new_idx -> 0
+ * 0
+ * 1
+ * 2
+ * 3
+ * 4 <- old_idx
+ *
+ * low_idx1 = 0;
+ * low_idx2 = MIN(old_idx, new_idx); => 0
+ * low_num = low_idx2 - low_idx1; => 0
+ *
+ * if (new < old) => new; => yes
+ *
+ * mid_idx1 = MIN(old_idx+1, new_idx); => 0
+ * mid_idx2 = MAX(old_idx, new_idx); => 4
+ * mid_num = mid_idx2 - mid_idx1; => 4
+ *
+ * if (new >= old) => new; => no
+ *
+ * high_idx1 = MAX(old_idx+1, new_idx); => 5
+ * high_idx2 = num_share_entries; => 5
+ * high_num = high_idx2 - high_idx1 = 0
+ *
+ * 6.
+ * new_idx -> 0
+ * 0 <- old_idx
+ *
+ * low_idx1 = 0;
+ * low_idx2 = MIN(old_idx, new_idx); => 0
+ * low_num = low_idx2 - low_idx1; => 0
+ *
+ * if (new < old) => new; => no
+ *
+ * mid_idx1 = MIN(old_idx+1, new_idx); => 0
+ * mid_idx2 = MAX(old_idx, new_idx); => 0
+ * mid_num = mid_idx2 - mid_idx1; => 0
+ *
+ * if (new >= old) => new; => yes
+ *
+ * high_idx1 = MAX(old_idx+1, new_idx); => 1
+ * high_idx2 = num_share_entries; => 1
+ * high_num = high_idx2 - high_idx1 = 0
+ *
+ * 7.
+ * 0 <- old_idx
+ * new_idx -> 1
+ *
+ * low_idx1 = 0;
+ * low_idx2 = MIN(old_idx, new_idx); => 0
+ * low_num = low_idx2 - low_idx1; => 0
+ *
+ * if (new < old) => new; => no
+ *
+ * mid_idx1 = MIN(old_idx+1, new_idx); => 1
+ * mid_idx2 = MAX(old_idx, new_idx); => 1
+ * mid_num = mid_idx2 - mid_idx1; => 0
+ *
+ * if (new >= old) => new; => yes
+ *
+ * high_idx1 = MAX(old_idx+1, new_idx); => 1
+ * high_idx2 = num_share_entries; => 1
+ * high_num = high_idx2 - high_idx1 = 0
+ */
+ low_idx1 = 0;
+ low_idx2 = MIN(old_idx, new_idx);
+ low_num = low_idx2 - low_idx1;
+ mid_idx1 = MIN(old_idx+1, new_idx);
+ mid_idx2 = MAX(old_idx, new_idx);
+ mid_num = mid_idx2 - mid_idx1;
+ high_idx1 = MAX(old_idx+1, new_idx);
+ high_idx2 = ltdb->num_share_entries;
+ high_num = high_idx2 - high_idx1;
+
+ if (low_num != 0) {
+ dbufs[num_dbufs] = (TDB_DATA) {
+ .dptr = discard_const_p(uint8_t, ltdb->share_entries) +
+ low_idx1 * SHARE_MODE_ENTRY_SIZE,
+ .dsize = low_num * SHARE_MODE_ENTRY_SIZE,
+ };
+ num_dbufs += 1;
+ }
+
+ if (new_idx < old_idx) {
+ dbufs[num_dbufs] = (TDB_DATA) {
+ .dptr = e_buf.buf, .dsize = SHARE_MODE_ENTRY_SIZE,
+ };
+ num_dbufs += 1;
+ }
+
+ if (mid_num != 0) {
+ dbufs[num_dbufs] = (TDB_DATA) {
+ .dptr = discard_const_p(uint8_t, ltdb->share_entries) +
+ mid_idx1 * SHARE_MODE_ENTRY_SIZE,
+ .dsize = mid_num * SHARE_MODE_ENTRY_SIZE,
+ };
+ num_dbufs += 1;
+ }
+
+ if (new_idx >= old_idx) {
+ dbufs[num_dbufs] = (TDB_DATA) {
+ .dptr = e_buf.buf, .dsize = SHARE_MODE_ENTRY_SIZE,
+ };
+ num_dbufs += 1;
+ }
+
+ if (high_num != 0) {
+ dbufs[num_dbufs] = (TDB_DATA) {
+ .dptr = discard_const_p(uint8_t, ltdb->share_entries) +
+ high_idx1 * SHARE_MODE_ENTRY_SIZE,
+ .dsize = high_num * SHARE_MODE_ENTRY_SIZE,
+ };
+ num_dbufs += 1;
+ }
+ {
+ size_t i;
+ for (i=0; i<num_dbufs; i++) {
+ DBG_DEBUG("dbufs[%zu]=(%p, %zu)\n",
+ i,
+ dbufs[i].dptr,
+ dbufs[i].dsize);
+ }
+ }
+
+ /*
+ * We completely rewrite the entries...
+ */
+ ltdb->share_entries = NULL;
+ ltdb->num_share_entries = 0;
d->modified = true;
- status = share_mode_data_ltdb_store(d, key, ltdb, NULL, 0);
+ status = share_mode_data_ltdb_store(d, key, ltdb, dbufs, num_dbufs);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("share_mode_data_ltdb_store failed: %s\n",
nt_errstr(status));
--
2.53.0
From 2eca89f51181b452cc86277f45db234e8b00db74 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 30 Aug 2024 14:16:12 +0200
Subject: [PATCH 080/122] s3:smbd: avoid false positives for got_oplock and
have_other_lease in delay_for_oplock_fn
stat opens should not cause a oplock/lease downgrade if
they don't have a lease attached to itself.
Note that opens broken to NONE still count if they are
non-stat opens...
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
Autobuild-User(master): Stefan Metzmacher <metze@samba.org>
Autobuild-Date(master): Thu Oct 10 13:59:18 UTC 2024 on atb-devel-224
(cherry picked from commit dd5b9e08c7a98c54b62d3b097c75faa09cd17da7)
Autobuild-User(v4-21-test): Jule Anger <janger@samba.org>
Autobuild-Date(v4-21-test): Mon Oct 14 11:09:14 UTC 2024 on atb-devel-224
---
selftest/knownfail | 1 -
.../knownfail.d/smb2.durable-v2-open.bug15649 | 2 --
source3/smbd/open.c | 26 ++++++++++++++-----
3 files changed, 20 insertions(+), 9 deletions(-)
delete mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15649
diff --git a/selftest/knownfail b/selftest/knownfail
index 4e34effbbd1..2da5cbd6dfd 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -219,7 +219,6 @@
^samba3.smb2.compound.interim2 # wrong return code (STATUS_CANCELLED)
^samba3.smb2.compound.aio.interim2 # wrong return code (STATUS_CANCELLED)
^samba3.smb2.lock.*replay_broken_windows # This tests the windows behaviour
-^samba3.smb2.lease.statopen3
^samba3.smb2.lease.unlink # we currently do not downgrade RH lease to R after unlink
^samba4.smb2.ioctl.compress_notsup.*\(ad_dc_ntvfs\)
^samba3.raw.session.*reauth2 # maybe fix this?
diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15649 b/selftest/knownfail.d/smb2.durable-v2-open.bug15649
deleted file mode 100644
index 748b6c3150e..00000000000
--- a/selftest/knownfail.d/smb2.durable-v2-open.bug15649
+++ /dev/null
@@ -1,2 +0,0 @@
-^samba3.smb2.durable-v2-open.stat-and-lease
-^samba3.smb2.durable-v2-open.nonstat-and-lease
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 4cc5190f690..2ccccb9eb75 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -2485,7 +2485,7 @@ struct delay_for_oplock_state {
bool first_open_attempt;
bool got_handle_lease;
bool got_oplock;
- bool have_other_lease;
+ bool disallow_write_lease;
uint32_t total_lease_types;
bool delay;
struct blocker_debug_state *blocker_debug_state;
@@ -2593,15 +2593,27 @@ static bool delay_for_oplock_fn(
}
if (!state->got_oplock &&
+ (e->op_type != NO_OPLOCK) &&
(e->op_type != LEASE_OPLOCK) &&
!share_entry_stale_pid(e)) {
state->got_oplock = true;
}
- if (!state->have_other_lease &&
+ /*
+ * Two things prevent a write lease
+ * to be granted:
+ *
+ * 1. Any oplock or lease (even broken to NONE)
+ * 2. An open with an access mask other than
+ * FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES
+ * or SYNCHRONIZE_ACCESS
+ */
+ if (!state->disallow_write_lease &&
+ (e->op_type != NO_OPLOCK || !is_oplock_stat_open(e->access_mask)) &&
!is_same_lease(fsp, e, lease) &&
- !share_entry_stale_pid(e)) {
- state->have_other_lease = true;
+ !share_entry_stale_pid(e))
+ {
+ state->disallow_write_lease = true;
}
if (e_is_lease && is_lease_stat_open(fsp->access_mask)) {
@@ -2835,9 +2847,11 @@ grant:
granted &= ~SMB2_LEASE_READ;
}
- if (state.have_other_lease) {
+ if (state.disallow_write_lease) {
/*
- * Can grant only one writer
+ * Can grant only a write lease
+ * if there are no other leases
+ * and no other non-stat opens.
*/
granted &= ~SMB2_LEASE_WRITE;
}
--
2.53.0
From 11fc816defc6e5d381c8ce8834dbf03fac4a2378 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 9 Jan 2025 08:57:17 +0100
Subject: [PATCH 081/122] dbwrap: check for option "tdb_hash_size:DBNAME.tdb"
in db_open()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 7eb135c42d530a16e80e165d9e8e99d920797f12)
---
source3/lib/dbwrap/dbwrap_open.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/source3/lib/dbwrap/dbwrap_open.c b/source3/lib/dbwrap/dbwrap_open.c
index 52c8a94aeff..91556f22819 100644
--- a/source3/lib/dbwrap/dbwrap_open.c
+++ b/source3/lib/dbwrap/dbwrap_open.c
@@ -80,6 +80,11 @@ struct db_context *db_open(TALLOC_CTX *mem_ctx,
base = name;
}
+ hash_size = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "tdb_hash_size",
+ base,
+ hash_size);
+
if (tdb_flags & TDB_CLEAR_IF_FIRST) {
bool try_readonly = false;
--
2.53.0
From 8d960286aa04b28b2e97fe7bfddb487c3694bef6 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 9 Jan 2025 12:27:43 +0100
Subject: [PATCH 082/122] smbtorture: add test "open-brlock-deadlock"
smbtorture reproducer for bug 15767. As it needs a very specific setup that
can't easily be done in selftest, the test is only executed when manually called
with
--option=torture:open_brlock_deadlock_timemout=SEC
To prepare the setup for the test set:
tdb_hash_size:locking.tdb = 1
tdb_hash_size:brlock.tdb = 1
and remove both tdb from disk which is needed so the TDBs get recreated with the
new hash_size.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 7c60498cee7dca5770d4d1f623c472d585ae9cae)
---
source4/torture/smb2/lock.c | 283 ++++++++++++++++++++++++++++++++++++
1 file changed, 283 insertions(+)
diff --git a/source4/torture/smb2/lock.c b/source4/torture/smb2/lock.c
index eac0d557fc3..e5cf61a471a 100644
--- a/source4/torture/smb2/lock.c
+++ b/source4/torture/smb2/lock.c
@@ -3465,6 +3465,288 @@ done:
return ret;
}
+struct open_brlock_deadlock_state {
+ bool stop;
+ bool ok;
+ struct torture_context *tctx;
+ struct smb2_tree *tree1;
+ struct smb2_tree *tree2;
+ struct smb2_create cr;
+ struct smb2_request *cr_req;
+ struct smb2_close cl;
+ struct smb2_request *cl_req;
+
+ struct smb2_lock_element el;
+ struct smb2_lock lock;
+ struct smb2_request *lock_req;
+};
+
+static void test_open_brlock_deadlock_loop_opened(struct smb2_request *req);
+
+static void test_open_brlock_deadlock_loop_open(
+ struct open_brlock_deadlock_state *state)
+{
+ if (state->stop) {
+ return;
+ }
+
+ state->cr_req = smb2_create_send(state->tree1, &state->cr);
+ torture_assert_goto(state->tctx, state->cr_req != NULL, state->ok, failed,
+ "smb2_create_send failed\n");
+
+ state->cr_req->async.fn = test_open_brlock_deadlock_loop_opened;
+ state->cr_req->async.private_data = state;
+ return;
+failed:
+ state->stop = true;
+}
+
+static void test_open_brlock_deadlock_loop_close(
+ struct open_brlock_deadlock_state *state);
+
+static void test_open_brlock_deadlock_loop_opened(struct smb2_request *req)
+{
+ struct open_brlock_deadlock_state *state = req->async.private_data;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ status = smb2_create_recv(req, frame, &state->cr);
+ torture_assert_ntstatus_ok_goto(state->tctx, status,
+ state->ok, failed, __location__);
+ state->cr_req = NULL;
+
+ TALLOC_FREE(frame);
+ test_open_brlock_deadlock_loop_close(state);
+ return;
+
+failed:
+ state->stop = true;
+ TALLOC_FREE(frame);
+}
+
+static void test_open_brlock_deadlock_loop_closed(struct smb2_request *req);
+
+static void test_open_brlock_deadlock_loop_close(
+ struct open_brlock_deadlock_state *state)
+{
+ if (state->stop) {
+ return;
+ }
+
+ state->cl.in.file = state->cr.out.file;
+ state->cl_req = smb2_close_send(state->tree1, &state->cl);
+ torture_assert_goto(state->tctx, state->cl_req != NULL, state->ok, failed,
+ "smb2_create_send failed\n");
+
+ state->cl_req->async.fn = test_open_brlock_deadlock_loop_closed;
+ state->cl_req->async.private_data = state;
+ return;
+failed:
+ state->stop = true;
+}
+
+static void test_open_brlock_deadlock_loop_closed(struct smb2_request *req)
+{
+ struct open_brlock_deadlock_state *state = req->async.private_data;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ status = smb2_close_recv(req, &state->cl);
+ torture_assert_ntstatus_ok_goto(state->tctx, status,
+ state->ok, failed, __location__);
+ state->cl_req = NULL;
+
+ TALLOC_FREE(frame);
+ test_open_brlock_deadlock_loop_open(state);
+ return;
+
+failed:
+ state->stop = true;
+ TALLOC_FREE(frame);
+}
+
+static void test_open_brlock_deadlock_loop_locked(struct smb2_request *req);
+
+static void test_open_brlock_deadlock_loop_lock(
+ struct open_brlock_deadlock_state *state)
+{
+ if (state->stop) {
+ return;
+ }
+
+ state->el.flags = SMB2_LOCK_FLAG_EXCLUSIVE;
+
+ state->lock_req = smb2_lock_send(state->tree2, &state->lock);
+ torture_assert_goto(state->tctx, state->lock_req != NULL,
+ state->ok, failed,
+ "smb2_create_send failed\n");
+
+ state->lock_req->async.fn = test_open_brlock_deadlock_loop_locked;
+ state->lock_req->async.private_data = state;
+ return;
+failed:
+ state->stop = true;
+}
+
+static void test_open_brlock_deadlock_loop_unlock(
+ struct open_brlock_deadlock_state *state);
+
+static void test_open_brlock_deadlock_loop_locked(struct smb2_request *req)
+{
+ struct open_brlock_deadlock_state *state = req->async.private_data;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ status = smb2_lock_recv(req, &state->lock);
+ torture_assert_ntstatus_ok_goto(state->tctx, status,
+ state->ok, failed, __location__);
+ state->lock_req = NULL;
+
+ TALLOC_FREE(frame);
+ test_open_brlock_deadlock_loop_unlock(state);
+ return;
+
+failed:
+ state->stop = true;
+ TALLOC_FREE(frame);
+}
+
+static void test_open_brlock_deadlock_loop_unlocked(struct smb2_request *req);
+
+static void test_open_brlock_deadlock_loop_unlock(
+ struct open_brlock_deadlock_state *state)
+{
+ if (state->stop) {
+ return;
+ }
+
+ state->el.flags = SMB2_LOCK_FLAG_UNLOCK;
+
+ state->lock_req = smb2_lock_send(state->tree2, &state->lock);
+ torture_assert_goto(state->tctx, state->lock_req != NULL,
+ state->ok, failed,
+ "smb2_create_send failed\n");
+
+ state->lock_req->async.fn = test_open_brlock_deadlock_loop_unlocked;
+ state->lock_req->async.private_data = state;
+ return;
+failed:
+ state->stop = true;
+}
+
+static void test_open_brlock_deadlock_loop_unlocked(struct smb2_request *req)
+{
+ struct open_brlock_deadlock_state *state = req->async.private_data;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ status = smb2_lock_recv(req, &state->lock);
+ torture_assert_ntstatus_ok_goto(state->tctx, status,
+ state->ok, failed, __location__);
+ state->lock_req = NULL;
+
+ TALLOC_FREE(frame);
+ test_open_brlock_deadlock_loop_lock(state);
+ return;
+
+failed:
+ state->stop = true;
+ TALLOC_FREE(frame);
+}
+
+
+static void test_open_brlock_deadlock_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct open_brlock_deadlock_state *state = private_data;
+ state->stop = true;
+}
+
+static bool test_open_brlock_deadlock(struct torture_context *tctx,
+ struct smb2_tree *tree1,
+ struct smb2_tree *tree2)
+{
+ int timeout_sec = torture_setting_int(tctx, "open_brlock_deadlock_timemout", 0);
+ const char *fname1 = BASEDIR "\\test_open_brlock_deadlock1.txt";
+ const char *fname2 = BASEDIR "\\test_open_brlock_deadlock2.txt";
+ struct open_brlock_deadlock_state state;
+ struct smb2_handle h = {};
+ uint8_t buf[200];
+ struct tevent_timer *te = NULL;
+ NTSTATUS status;
+ bool ret = true;
+
+ if (timeout_sec == 0) {
+ torture_skip_goto(tctx, done, "Test skipped, pass '--option=torture:open_brlock_deadlock_timemout=SEC' to run\n");
+ }
+
+ state = (struct open_brlock_deadlock_state) {
+ .tctx = tctx,
+ .tree1 = tree1,
+ .tree2 = tree2,
+ };
+
+ ret = smb2_util_setup_dir(tctx, tree1, BASEDIR);
+ torture_assert_goto(tctx, ret, ret, done,
+ "smb2_util_setup_dir failed");
+
+ status = torture_smb2_testfile(tree1, fname1, &h);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testfile failed");
+ status = smb2_util_close(tree1, h);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_util_close failed");
+
+ status = torture_smb2_testfile(tree2, fname2, &h);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testfile failed");
+
+ status = smb2_util_write(tree2, h, buf, 0, ARRAY_SIZE(buf));
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_util_write failed");
+
+ state.cr = (struct smb2_create) {
+ .in.desired_access = SEC_FILE_READ_DATA,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+ .in.create_disposition = NTCREATEX_DISP_OPEN,
+ .in.fname = fname1,
+ };
+
+ state.el = (struct smb2_lock_element) {
+ .length = 1,
+ .offset = 0,
+ };
+ state.lock = (struct smb2_lock) {
+ .in.locks = &state.el,
+ .in.lock_count = 1,
+ .in.file.handle = h,
+ };
+
+ te = tevent_add_timer(tctx->ev,
+ tctx,
+ timeval_current_ofs(timeout_sec, 0),
+ test_open_brlock_deadlock_timeout,
+ &state);
+ torture_assert_goto(tctx, te != NULL, ret, done, __location__);
+
+ test_open_brlock_deadlock_loop_open(&state);
+ test_open_brlock_deadlock_loop_lock(&state);
+
+ while (!state.stop) {
+ int rc = tevent_loop_once(tctx->ev);
+ torture_assert_int_equal(tctx, rc, 0, "tevent_loop_once");
+ }
+
+done:
+ if (!smb2_util_handle_empty(h)) {
+ smb2_util_close(tree2, h);
+ }
+ smb2_deltree(tree1, BASEDIR);
+ return ret;
+}
+
/* basic testing of SMB2 locking
*/
struct torture_suite *torture_smb2_lock_init(TALLOC_CTX *ctx)
@@ -3506,6 +3788,7 @@ struct torture_suite *torture_smb2_lock_init(TALLOC_CTX *ctx)
torture_suite_add_1smb2_test(suite, "replay_smb3_specification_multi",
test_replay_smb3_specification_multi);
torture_suite_add_1smb2_test(suite, "ctdb-delrec-deadlock", test_deadlock);
+ torture_suite_add_2smb2_test(suite, "open-brlock-deadlock", test_open_brlock_deadlock);
suite->description = talloc_strdup(suite, "SMB2-LOCK tests");
--
2.53.0
From 241beffc5dee532a6f9a013d3f2d60e5dc7fb472 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 6 Jan 2025 15:59:27 +0100
Subject: [PATCH 083/122] s3/brlock: split out brl_get_locks_readonly_parse()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Pair-Programmed-With: Ralph Boehme <slow@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 94e7cbcc32b73e4d56e7209e04d22d4270a6eb5b)
---
source3/locking/brlock.c | 43 ++++++++++++++++++++++++++--------------
1 file changed, 28 insertions(+), 15 deletions(-)
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index 905da049c58..a1713418d65 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -1859,29 +1859,18 @@ static void brl_get_locks_readonly_parser(TDB_DATA key, TDB_DATA data,
*state->br_lock = br_lck;
}
-struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp)
+static struct byte_range_lock *brl_get_locks_readonly_parse(TALLOC_CTX *mem_ctx,
+ files_struct *fsp)
{
struct byte_range_lock *br_lock = NULL;
struct brl_get_locks_readonly_state state;
NTSTATUS status;
- DEBUG(10, ("seqnum=%d, fsp->brlock_seqnum=%d\n",
- dbwrap_get_seqnum(brlock_db), fsp->brlock_seqnum));
-
- if ((fsp->brlock_rec != NULL)
- && (dbwrap_get_seqnum(brlock_db) == fsp->brlock_seqnum)) {
- /*
- * We have cached the brlock_rec and the database did not
- * change.
- */
- return fsp->brlock_rec;
- }
-
/*
* Parse the record fresh from the database
*/
- state.mem_ctx = fsp;
+ state.mem_ctx = mem_ctx;
state.br_lock = &br_lock;
status = dbwrap_parse_record(
@@ -1894,7 +1883,7 @@ struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp)
/*
* No locks on this file. Return an empty br_lock.
*/
- br_lock = talloc_zero(fsp, struct byte_range_lock);
+ br_lock = talloc_zero(mem_ctx, struct byte_range_lock);
if (br_lock == NULL) {
return NULL;
}
@@ -1912,6 +1901,30 @@ struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp)
br_lock->modified = false;
br_lock->record = NULL;
+ return br_lock;
+}
+
+struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp)
+{
+ struct byte_range_lock *br_lock = NULL;
+
+ DEBUG(10, ("seqnum=%d, fsp->brlock_seqnum=%d\n",
+ dbwrap_get_seqnum(brlock_db), fsp->brlock_seqnum));
+
+ if ((fsp->brlock_rec != NULL)
+ && (dbwrap_get_seqnum(brlock_db) == fsp->brlock_seqnum)) {
+ /*
+ * We have cached the brlock_rec and the database did not
+ * change.
+ */
+ return fsp->brlock_rec;
+ }
+
+ br_lock = brl_get_locks_readonly_parse(fsp, fsp);
+ if (br_lock == NULL) {
+ return NULL;
+ }
+
/*
* Cache the brlock struct, invalidated when the dbwrap_seqnum
* changes. See beginning of this routine.
--
2.53.0
From e0acfc837633b184e933a2a60cdf294229767f9a Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 6 Jan 2025 17:07:11 +0100
Subject: [PATCH 084/122] s3/brlock: add brl_req_set()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit c9c04c7d75dee0c3e6e843b581624a3852042057)
---
source3/locking/brlock.c | 9 +++++++++
source3/locking/proto.h | 3 +++
2 files changed, 12 insertions(+)
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index a1713418d65..9629c68c3c8 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -86,6 +86,15 @@ struct files_struct *brl_fsp(struct byte_range_lock *brl)
return brl->fsp;
}
+
+void brl_req_set(struct byte_range_lock *br_lck,
+ TALLOC_CTX *req_mem_ctx,
+ const struct GUID *req_guid)
+{
+ br_lck->req_mem_ctx = req_mem_ctx;
+ br_lck->req_guid = req_guid;
+}
+
TALLOC_CTX *brl_req_mem_ctx(const struct byte_range_lock *brl)
{
if (brl->req_mem_ctx == NULL) {
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index 7fc177d7aa6..3413596baed 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -32,6 +32,9 @@ void brl_shutdown(void);
unsigned int brl_num_locks(const struct byte_range_lock *brl);
struct files_struct *brl_fsp(struct byte_range_lock *brl);
+void brl_req_set(struct byte_range_lock *br_lck,
+ TALLOC_CTX *req_mem_ctx,
+ const struct GUID *req_guid);
TALLOC_CTX *brl_req_mem_ctx(const struct byte_range_lock *brl);
const struct GUID *brl_req_guid(const struct byte_range_lock *brl);
--
2.53.0
From 862bec505a66f8c9958f96411fa2f05db8059ed1 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Sat, 1 Feb 2025 10:37:40 +0100
Subject: [PATCH 085/122] s3/brlock: add share_mode_do_locked_brl()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit e17fb732c89f8b34de00904383044de3c4f85bd0)
---
source3/locking/brlock.c | 103 +++++++++++++++++++++++++++++++++++++++
source3/locking/proto.h | 8 +++
2 files changed, 111 insertions(+)
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index 9629c68c3c8..cf639451e99 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -34,6 +34,7 @@
#include "serverid.h"
#include "messages.h"
#include "util_tdb.h"
+#include "source3/locking/share_mode_lock.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_LOCKING
@@ -2018,3 +2019,105 @@ done:
talloc_free(frame);
return ret;
}
+
+struct share_mode_do_locked_brl_state {
+ share_mode_do_locked_brl_fn_t cb;
+ void *cb_data;
+ struct files_struct *fsp;
+ NTSTATUS status;
+};
+
+static void share_mode_do_locked_brl_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct share_mode_do_locked_brl_state *state = private_data;
+ struct byte_range_lock *br_lck = NULL;
+ TDB_DATA key = make_tdb_data((uint8_t *)&state->fsp->file_id,
+ sizeof(state->fsp->file_id));
+
+ if (lp_locking(state->fsp->conn->params) &&
+ state->fsp->fsp_flags.can_lock)
+ {
+ br_lck = brl_get_locks_readonly_parse(talloc_tos(),
+ state->fsp);
+ if (br_lck == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+ }
+
+ state->cb(lck, br_lck, state->cb_data);
+
+ if (br_lck == NULL || !br_lck->modified) {
+ TALLOC_FREE(br_lck);
+ return;
+ }
+
+ br_lck->record = dbwrap_fetch_locked(brlock_db, br_lck, key);
+ if (br_lck->record == NULL) {
+ DBG_ERR("Could not lock byte range lock entry for '%s'\n",
+ fsp_str_dbg(state->fsp));
+ TALLOC_FREE(br_lck);
+ state->status = NT_STATUS_INTERNAL_DB_ERROR;
+ return;
+ }
+
+ byte_range_lock_flush(br_lck);
+ share_mode_wakeup_waiters(br_lck->fsp->file_id);
+ TALLOC_FREE(br_lck);
+}
+
+/*
+ * Run cb with a glock'ed locking.tdb record, providing both a share_mode_lock
+ * and a br_lck object. An initial read-only, but upgradable, br_lck object is
+ * fetched from brlock.tdb while holding the glock on the locking.tdb record.
+ *
+ * This function only ever hold one low-level TDB chainlock at a time on either
+ * locking.tdb or brlock.tdb, so it can't run afoul any lock order violations.
+ *
+ * Note that br_lck argument in the callback might be NULL in case lp_locking()
+ * is disabled, the fsp doesn't allow locking or is a directory, so either
+ * the caller or the callback have to check for this.
+ */
+NTSTATUS share_mode_do_locked_brl(files_struct *fsp,
+ share_mode_do_locked_brl_fn_t cb,
+ void *cb_data)
+{
+ static bool recursion_guard;
+ TALLOC_CTX *frame = NULL;
+ struct share_mode_do_locked_brl_state state = {
+ .fsp = fsp,
+ .cb = cb,
+ .cb_data = cb_data,
+ };
+ NTSTATUS status;
+
+ SMB_ASSERT(!recursion_guard);
+
+ /* silently return ok on print files as we don't do locking there */
+ if (fsp->print_file) {
+ return NT_STATUS_OK;
+ }
+
+ frame = talloc_stackframe();
+
+ recursion_guard = true;
+ status = share_mode_do_locked_vfs_allowed(
+ fsp->file_id,
+ share_mode_do_locked_brl_fn,
+ &state);
+ recursion_guard = false;
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_do_locked_vfs_allowed() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ TALLOC_FREE(frame);
+ return state.status;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index 3413596baed..c9d769ba53f 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -87,6 +87,14 @@ struct byte_range_lock *brl_get_locks_for_locking(TALLOC_CTX *mem_ctx,
files_struct *fsp,
TALLOC_CTX *req_mem_ctx,
const struct GUID *req_guid);
+struct share_mode_lock;
+typedef void (*share_mode_do_locked_brl_fn_t)(
+ struct share_mode_lock *lck,
+ struct byte_range_lock *br_lck, /* br_lck can be NULL */
+ void *private_data);
+NTSTATUS share_mode_do_locked_brl(files_struct *fsp,
+ share_mode_do_locked_brl_fn_t fn,
+ void *private_data);
struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx,
files_struct *fsp);
struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp);
--
2.53.0
From d18f6942b66eb7166f8929e9b475d0acbd721cb0 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Wed, 8 Jan 2025 15:43:04 +0100
Subject: [PATCH 086/122] s3/brlock: don't increment current_lock_count if
do_lock_fn() failed
Also only assign psmblctx and pblocker_pid if the lock request failed.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 3a0c6e99de4377f44bc29766b6ceb79040caed9f)
---
source3/locking/locking.c | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index 6ee5987ffda..d796e6ffb7b 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -340,19 +340,21 @@ NTSTATUS do_lock(files_struct *fsp,
nt_errstr(status));
return status;
}
-
- if (psmblctx != NULL) {
- *psmblctx = state.blocker_smblctx;
- }
- if (pblocker_pid != NULL) {
- *pblocker_pid = state.blocker_pid;
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_DEBUG("do_lock_fn returned %s\n",
+ nt_errstr(state.status));
+ if (psmblctx != NULL) {
+ *psmblctx = state.blocker_smblctx;
+ }
+ if (pblocker_pid != NULL) {
+ *pblocker_pid = state.blocker_pid;
+ }
+ return state.status;
}
- DBG_DEBUG("returning status=%s\n", nt_errstr(state.status));
-
increment_current_lock_count(fsp, lock_flav);
- return state.status;
+ return NT_STATUS_OK;
}
/****************************************************************************
--
2.53.0
From 4a6c36f1d31556e1abcdbf090f76d4c49b261e92 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Wed, 29 Jan 2025 06:13:29 +0100
Subject: [PATCH 087/122] s3/locking: add brl_set_modified()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 2772f147c9b13cd2160181c4f7905b54ab765054)
---
source3/locking/brlock.c | 5 +++++
source3/locking/proto.h | 1 +
2 files changed, 6 insertions(+)
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index cf639451e99..e0d18800cf1 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -2121,3 +2121,8 @@ NTSTATUS share_mode_do_locked_brl(files_struct *fsp,
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
+
+void brl_set_modified(struct byte_range_lock *br_lck, bool modified)
+{
+ br_lck->modified = modified;
+}
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index c9d769ba53f..c74539c8161 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -99,6 +99,7 @@ struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx,
files_struct *fsp);
struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp);
bool brl_cleanup_disconnected(struct file_id fid, uint64_t open_persistent_id);
+void brl_set_modified(struct byte_range_lock *br_lck, bool modified);
/* The following definitions come from locking/locking.c */
--
2.53.0
From 3362898470c1efa07bb9e05f6663a42fef53c784 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Wed, 29 Jan 2025 06:13:44 +0100
Subject: [PATCH 088/122] smbd: use share_mode_do_locked_brl()
Fix a deadlock that can happen if two clients happen to open and byte-range-lock
two different files whos record in locking.tdb and brlock.tdb happen to sit on
the same hashchain.
The deadlock was introduced by commit
680c7907325b433856ac1dd916ab63e671fbe4ab. Before, we used share_mode_do_locked()
in do_lock() which meant we acquired a chainlock on locking.tdb before getting a
chainlock on brlock.tdb via brl_get_locks_for_locking(), so the TDB chainlock
order invariant was always uphold.
The following race between specific client requests lead to the deadlock.
Client A) issues a byte-range-lock request on a file:
A1) glock locking.tdb (via _share_mode_do_locked_vfs_allowed())
A2) chainlock brlock.tdb (via brl_lock())
A3) attempt to chainlock locking.tdb (via share_mode_g_lock_dump())
[1]
Client B) opens a different (!) file:
B1) glock and chainlock locking.tdb (via _share_mode_entry_prepare_lock())
B2) attempt to chainlock brlock.tdb (via file_has_brlocks())
[2]
The glock from A1 is per record and hence doesn't synchronize with the glock
from B1 as it is for a different file and hence a different record, subsequently
A2 and A3 violate the lock order constraint
To avoid the chainlock lock order violation in the second client we modify the
br-lock code to not take the brlock.tdb chainlock from step A2 via
br_get_locks() for the whole time we process the request. Instead we just fetch
the br-locks via br_get_locks_readonly(), so when running into
contend_level2_oplocks_begin_default() to check for leases and looking into
locking.tdb we don't hold a brlock.tdb chainlock.
Or im simpler terms, we only ever take at most one low-level TDB chainlock at a
time:
Byte-range-lock code calls share_mode_do_locked_brl(..., cb_fn, ...):
1) chainlock locking.tdb
2) glock locking.tdb (via share_mode_do_locked_vfs_allowed())
3) chainunlock locking.tdb
4) share_mode_do_locked_brl_fn() -> brl_get_locks_readonly_parse():
a) chainlock brlock.tdb
b) parse record and store in-memory copy
c) chainunlock brlock.tdb
5) run cb_fn()
6) chainlock brlock.tdb:
a) br_lck->record = dbwrap_fetch_locked(brlock_db, ...)
b) store modifed br_lck from 5) via byte_range_lock_flush()
7) chainunlock brlock.tdb
8) chainlock locking.tdb
9) gunlock locking.tdb
10) chainunlock locking.tdb
All access to brlock.tdb is synchronized correctly via glocks on the locking.tdb
record of the file (step 3)), so operations still appear atomic to clients.
As a result of using share_mode_do_locked_brl(), the functions do_[un]lock() ->
brl_[un]lock() now loop over the same br_lck object in memory, avoiding
repeatedly fetching and storing the locks per loop.
[1]
Full SBT:
#0 0x00007fffa0cecbb0 in __pthread_mutex_lock_full () from /lib64/glibc-hwcaps/power9/libpthread-2.28.so
#1 0x00007fffa0a73cf8 in chain_mutex_lock (m=<optimized out>, m@entry=0x7fff9ae071b0, waitflag=<optimized out>, waitflag@entry=true) at ../../lib/tdb/common/mutex.c:182
#2 0x00007fffa0a7432c in tdb_mutex_lock (tdb=0x1543ba120, rw=<optimized out>, off=<optimized out>, len=<optimized out>, waitflag=<optimized out>, pret=0x7fffd7df3858) at ../../lib/tdb/common/mutex.c:234
#3 0x00007fffa0a6812c in fcntl_lock (waitflag=<optimized out>, len=1, off=376608, rw=0, tdb=0x1543ba120) at ../../lib/tdb/common/lock.c:200
#4 tdb_brlock (tdb=0x1543ba120, rw_type=<optimized out>, offset=<optimized out>, len=1, flags=<optimized out>) at ../../lib/tdb/common/lock.c:200
#5 0x00007fffa0a68af8 in tdb_nest_lock (flags=<optimized out>, ltype=0, offset=<optimized out>, tdb=0x1543ba120) at ../../lib/tdb/common/lock.c:390
#6 tdb_nest_lock (tdb=0x1543ba120, offset=<optimized out>, ltype=<optimized out>, flags=<optimized out>) at ../../lib/tdb/common/lock.c:336
#7 0x00007fffa0a69088 in tdb_lock_list (tdb=0x1543ba120, list=<optimized out>, ltype=<optimized out>, waitflag=<optimized out>) at ../../lib/tdb/common/lock.c:482
#8 0x00007fffa0a69198 in tdb_lock (tdb=0x1543ba120, list=<optimized out>, ltype=<optimized out>) at ../../lib/tdb/common/lock.c:500
#9 0x00007fffa0a64b50 in tdb_find_lock_hash (tdb=<optimized out>, tdb@entry=0x1543ba120, key=..., hash=<optimized out>, locktype=<optimized out>, locktype@entry=0, rec=<optimized out>, rec@entry=0x7fffd7df3ab0) at ../../lib/tdb/common/tdb.c:165
#10 0x00007fffa0a64ed0 in tdb_parse_record (tdb=0x1543ba120, key=..., parser=0x7fffa0e74470 <db_ctdb_ltdb_parser>, private_data=0x7fffd7df3b18) at ../../lib/tdb/common/tdb.c:329
#11 0x00007fffa0e74cbc in db_ctdb_ltdb_parse (db=<optimized out>, private_data=0x7fffd7df3b70, parser=0x7fffa0e76470 <db_ctdb_parse_record_parser_nonpersistent>, key=...) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:170
#12 db_ctdb_try_parse_local_record (ctx=ctx@entry=0x1543d4580, key=..., state=state@entry=0x7fffd7df3b70) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:1385
#13 0x00007fffa0e76024 in db_ctdb_parse_record (db=<optimized out>, key=..., parser=0x7fffa1313910 <dbwrap_watched_parse_record_parser>, private_data=0x7fffd7df3c08) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:1425
#14 0x00007fffa0884760 in dbwrap_parse_record (db=<optimized out>, key=..., parser=<optimized out>, private_data=<optimized out>) at ../../lib/dbwrap/dbwrap.c:454
#15 0x00007fffa1313ab4 in dbwrap_watched_parse_record (db=0x1543a7160, key=..., parser=0x7fffa13187d0 <g_lock_dump_fn>, private_data=0x7fffd7df3ce8) at ../../source3/lib/dbwrap/dbwrap_watch.c:783
#16 0x00007fffa0884760 in dbwrap_parse_record (db=<optimized out>, key=..., parser=<optimized out>, private_data=<optimized out>) at ../../lib/dbwrap/dbwrap.c:454
#17 0x00007fffa131c004 in g_lock_dump (ctx=<error reading variable: value has been optimized out>, key=..., fn=0x7fffa14f3d70 <fsp_update_share_mode_flags_fn>, private_data=0x7fffd7df3dd8) at ../../source3/lib/g_lock.c:1653
#18 0x00007fffa14f434c in share_mode_g_lock_dump (key=..., fn=0x7fffa14f3d70 <fsp_update_share_mode_flags_fn>, private_data=0x7fffd7df3dd8) at ../../source3/locking/share_mode_lock.c:96
#19 0x00007fffa14f8d44 in fsp_update_share_mode_flags (fsp=0x15433c550) at ../../source3/locking/share_mode_lock.c:1181
#20 file_has_read_lease (fsp=0x15433c550) at ../../source3/locking/share_mode_lock.c:1207
#21 0x00007fffa15ccc98 in contend_level2_oplocks_begin_default (type=<optimized out>, fsp=0x15433c550) at ../../source3/smbd/smb2_oplock.c:1282
#22 smbd_contend_level2_oplocks_begin (fsp=0x15433c550, type=<optimized out>) at ../../source3/smbd/smb2_oplock.c:1338
#23 0x00007fffa0dd0b54 in contend_level2_oplocks_begin (fsp=<optimized out>, type=<optimized out>) at ../../source3/lib/smbd_shim.c:72
#24 0x00007fffa14ecfd0 in brl_lock_windows_default (br_lck=0x154421330, plock=0x7fffd7df4250) at ../../source3/locking/brlock.c:457
#25 0x00007fffa150b70c in vfswrap_brl_lock_windows (handle=<optimized out>, br_lck=<optimized out>, plock=<optimized out>) at ../../source3/modules/vfs_default.c:3424
#26 0x00007fffa1561910 in smb_vfs_call_brl_lock_windows (handle=<optimized out>, br_lck=<optimized out>, plock=<optimized out>) at ../../source3/smbd/vfs.c:2686
#27 0x00007fff9c0a7350 in smb_time_audit_brl_lock_windows (handle=<optimized out>, br_lck=0x154421330, plock=0x7fffd7df4250) at ../../source3/modules/vfs_time_audit.c:1740
#28 0x00007fffa1561910 in smb_vfs_call_brl_lock_windows (handle=<optimized out>, br_lck=<optimized out>, plock=<optimized out>) at ../../source3/smbd/vfs.c:2686
#29 0x00007fffa14ed410 in brl_lock (br_lck=0x154421330, smblctx=3102281601, pid=..., start=0, size=18446744073709551615, lock_type=<optimized out>, lock_flav=WINDOWS_LOCK, blocker_pid=0x7fffd7df4540, psmblctx=0x7fffd7df4558) at ../../source3/locking/brlock.c:1004
#30 0x00007fffa14e7b18 in do_lock_fn (lck=<optimized out>, private_data=0x7fffd7df4508) at ../../source3/locking/locking.c:271
#31 0x00007fffa14fcd94 in _share_mode_do_locked_vfs_allowed (id=..., fn=0x7fffa14e7a60 <do_lock_fn>, private_data=0x7fffd7df4508, location=<optimized out>) at ../../source3/locking/share_mode_lock.c:2927
#32 0x00007fffa14e918c in do_lock (fsp=0x15433c550, req_mem_ctx=<optimized out>, req_guid=<optimized out>, smblctx=<optimized out>, count=18446744073709551615, offset=0, lock_type=<optimized out>, lock_flav=<optimized out>, pblocker_pid=0x7fffd7df46f0,
psmblctx=0x7fffd7df46d8) at ../../source3/locking/locking.c:335
#33 0x00007fffa155381c in smbd_do_locks_try (fsp=0x15433c550, num_locks=<optimized out>, locks=0x1543bc310, blocker_idx=0x7fffd7df46d6, blocking_pid=0x7fffd7df46f0, blocking_smblctx=0x7fffd7df46d8) at ../../source3/smbd/blocking.c:46
#34 0x00007fffa159dc90 in smbd_smb2_lock_try (req=req@entry=0x1543bc080) at ../../source3/smbd/smb2_lock.c:590
#35 0x00007fffa159ee8c in smbd_smb2_lock_send (in_locks=<optimized out>, in_lock_count=1, in_lock_sequence=<optimized out>, fsp=0x15433c550, smb2req=0x1543532e0, ev=0x154328120, mem_ctx=0x1543532e0) at ../../source3/smbd/smb2_lock.c:488
#36 smbd_smb2_request_process_lock (req=0x1543532e0) at ../../source3/smbd/smb2_lock.c:150
#37 0x00007fffa158a368 in smbd_smb2_request_dispatch (req=0x1543532e0) at ../../source3/smbd/smb2_server.c:3515
#38 0x00007fffa158c540 in smbd_smb2_io_handler (fde_flags=<optimized out>, xconn=0x154313f30) at ../../source3/smbd/smb2_server.c:5112
#39 smbd_smb2_connection_handler (ev=<optimized out>, fde=<optimized out>, flags=<optimized out>, private_data=<optimized out>) at ../../source3/smbd/smb2_server.c:5150
#40 0x00007fffa1198b2c in tevent_common_invoke_fd_handler (fde=0x1543670f0, flags=<optimized out>, removed=0x0) at ../../lib/tevent/tevent_fd.c:158
#41 0x00007fffa11a2b9c in epoll_event_loop (tvalp=0x7fffd7df4b28, epoll_ev=0x1543b4e80) at ../../lib/tevent/tevent_epoll.c:730
#42 epoll_event_loop_once (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent_epoll.c:946
#43 0x00007fffa11a0090 in std_event_loop_once (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent_standard.c:110
#44 0x00007fffa119744c in _tevent_loop_once (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent.c:823
#45 0x00007fffa1197884 in tevent_common_loop_wait (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent.c:950
#46 0x00007fffa119ffc0 in std_event_loop_wait (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent_standard.c:141
#47 0x00007fffa1197978 in _tevent_loop_wait (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent.c:971
#48 0x00007fffa15737fc in smbd_process (ev_ctx=0x154328120, msg_ctx=<optimized out>, sock_fd=<optimized out>, interactive=<optimized out>) at ../../source3/smbd/smb2_process.c:2158
#49 0x000000011db5c554 in smbd_accept_connection (ev=0x154328120, fde=<optimized out>, flags=<optimized out>, private_data=<optimized out>) at ../../source3/smbd/server.c:1150
#50 0x00007fffa1198b2c in tevent_common_invoke_fd_handler (fde=0x1543ac2d0, flags=<optimized out>, removed=0x0) at ../../lib/tevent/tevent_fd.c:158
#51 0x00007fffa11a2b9c in epoll_event_loop (tvalp=0x7fffd7df4f98, epoll_ev=0x154328350) at ../../lib/tevent/tevent_epoll.c:730
#52 epoll_event_loop_once (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent_epoll.c:946
#53 0x00007fffa11a0090 in std_event_loop_once (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent_standard.c:110
#54 0x00007fffa119744c in _tevent_loop_once (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent.c:823
#55 0x00007fffa1197884 in tevent_common_loop_wait (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent.c:950
#56 0x00007fffa119ffc0 in std_event_loop_wait (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent_standard.c:141
#57 0x00007fffa1197978 in _tevent_loop_wait (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent.c:971
#58 0x000000011db58c54 in smbd_parent_loop (parent=<optimized out>, ev_ctx=0x154328120) at ../../source3/smbd/server.c:1499
#59 main (argc=<optimized out>, argv=<optimized out>) at ../../source3/smbd/server.c:2258
[2]
Full SBT:
#0 0x00007fffa0cecbb0 in __pthread_mutex_lock_full () from /lib64/glibc-hwcaps/power9/libpthread-2.28.so
#1 0x00007fffa0a73cf8 in chain_mutex_lock (m=<optimized out>, m@entry=0x7fff9b3a71b0, waitflag=<optimized out>, waitflag@entry=true) at ../../lib/tdb/common/mutex.c:182
#2 0x00007fffa0a7432c in tdb_mutex_lock (tdb=0x1543c6900, rw=<optimized out>, off=<optimized out>, len=<optimized out>, waitflag=<optimized out>, pret=0x7fffd7df2e28) at ../../lib/tdb/common/mutex.c:234
#3 0x00007fffa0a6812c in fcntl_lock (waitflag=<optimized out>, len=1, off=376608, rw=0, tdb=0x1543c6900) at ../../lib/tdb/common/lock.c:200
#4 tdb_brlock (tdb=0x1543c6900, rw_type=<optimized out>, offset=<optimized out>, len=1, flags=<optimized out>) at ../../lib/tdb/common/lock.c:200
#5 0x00007fffa0a68af8 in tdb_nest_lock (flags=<optimized out>, ltype=0, offset=<optimized out>, tdb=0x1543c6900) at ../../lib/tdb/common/lock.c:390
#6 tdb_nest_lock (tdb=0x1543c6900, offset=<optimized out>, ltype=<optimized out>, flags=<optimized out>) at ../../lib/tdb/common/lock.c:336
#7 0x00007fffa0a69088 in tdb_lock_list (tdb=0x1543c6900, list=<optimized out>, ltype=<optimized out>, waitflag=<optimized out>) at ../../lib/tdb/common/lock.c:482
#8 0x00007fffa0a69198 in tdb_lock (tdb=0x1543c6900, list=<optimized out>, ltype=<optimized out>) at ../../lib/tdb/common/lock.c:500
#9 0x00007fffa0a64b50 in tdb_find_lock_hash (tdb=<optimized out>, tdb@entry=0x1543c6900, key=..., hash=<optimized out>, locktype=<optimized out>, locktype@entry=0, rec=<optimized out>, rec@entry=0x7fffd7df3080) at ../../lib/tdb/common/tdb.c:165
#10 0x00007fffa0a64ed0 in tdb_parse_record (tdb=0x1543c6900, key=..., parser=0x7fffa0e74470 <db_ctdb_ltdb_parser>, private_data=0x7fffd7df30e8) at ../../lib/tdb/common/tdb.c:329
#11 0x00007fffa0e74cbc in db_ctdb_ltdb_parse (db=<optimized out>, private_data=0x7fffd7df3140, parser=0x7fffa0e76470 <db_ctdb_parse_record_parser_nonpersistent>, key=...) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:170
#12 db_ctdb_try_parse_local_record (ctx=ctx@entry=0x154328fc0, key=..., state=state@entry=0x7fffd7df3140) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:1385
#13 0x00007fffa0e76024 in db_ctdb_parse_record (db=<optimized out>, key=..., parser=0x7fffa14ec820 <brl_get_locks_readonly_parser>, private_data=0x7fffd7df3218) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:1425
#14 0x00007fffa0884760 in dbwrap_parse_record (db=<optimized out>, key=..., parser=<optimized out>, private_data=<optimized out>) at ../../lib/dbwrap/dbwrap.c:454
#15 0x00007fffa14ef5bc in brl_get_locks_readonly (fsp=0x1543d01e0) at ../../source3/locking/brlock.c:1884
#16 0x00007fffa1546968 in file_has_brlocks (fsp=0x1543d01e0) at ../../source3/smbd/open.c:2232
#17 delay_for_oplock (pgranted=<synthetic pointer>, poplock_type=<synthetic pointer>, first_open_attempt=<optimized out>, create_disposition=1, have_sharing_violation=false, lck=0x7fffd7df3ce8, lease=0x0, oplock_request=0, fsp=0x1543d01e0) at ../../source3/smbd/open.c:2749
#18 handle_share_mode_lease (pgranted=<synthetic pointer>, poplock_type=<synthetic pointer>, first_open_attempt=<optimized out>, lease=0x0, oplock_request=0, share_access=7, access_mask=131201, create_disposition=1, lck=0x7fffd7df3ce8, fsp=0x1543d01e0) at ../../source3/smbd/open.c:2865
#19 check_and_store_share_mode (first_open_attempt=<optimized out>, lease=0x0, oplock_request=0, share_access=7, access_mask=131201, create_disposition=1, lck=0x7fffd7df3ce8, req=0x154414800, fsp=0x1543d01e0) at ../../source3/smbd/open.c:3333
#20 open_ntcreate_lock_add_entry (lck=0x7fffd7df3ce8, keep_locked=0x7fffd7df3ad0, private_data=0x7fffd7df3cc8) at ../../source3/smbd/open.c:3688
#21 0x00007fffa14f6248 in share_mode_entry_prepare_lock_fn (glck=0x7fffd7df35b8, cb_private=0x7fffd7df3a88) at ../../source3/locking/share_mode_lock.c:2978
#22 0x00007fffa1317680 in g_lock_lock_cb_run_and_store (cb_state=cb_state@entry=0x7fffd7df35b8) at ../../source3/lib/g_lock.c:597
#23 0x00007fffa1319df8 in g_lock_lock_simple_fn (rec=0x7fffd7df3798, value=..., private_data=0x7fffd7df39a0) at ../../source3/lib/g_lock.c:1212
#24 0x00007fffa13160e0 in dbwrap_watched_do_locked_fn (backend_rec=<optimized out>, backend_value=..., private_data=0x7fffd7df3768) at ../../source3/lib/dbwrap/dbwrap_watch.c:458
#25 0x00007fffa0884e48 in dbwrap_do_locked (db=<optimized out>, key=..., fn=0x7fffa1316080 <dbwrap_watched_do_locked_fn>, private_data=0x7fffd7df3768) at ../../lib/dbwrap/dbwrap.c:602
#26 0x00007fffa1315274 in dbwrap_watched_do_locked (db=0x1543a7160, key=..., fn=0x7fffa1319ca0 <g_lock_lock_simple_fn>, private_data=0x7fffd7df39a0) at ../../source3/lib/dbwrap/dbwrap_watch.c:480
#27 0x00007fffa0884d60 in dbwrap_do_locked (db=<optimized out>, key=..., fn=<optimized out>, private_data=<optimized out>) at ../../lib/dbwrap/dbwrap.c:582
#28 0x00007fffa131b458 in g_lock_lock (ctx=0x1543cc630, key=..., type=<optimized out>, timeout=..., cb_fn=0x7fffa14f6190 <share_mode_entry_prepare_lock_fn>, cb_private=0x7fffd7df3a88) at ../../source3/lib/g_lock.c:1267
#29 0x00007fffa14fd060 in _share_mode_entry_prepare_lock (prepare_state=0x7fffd7df3cc8, id=..., servicepath=<optimized out>, smb_fname=<optimized out>, old_write_time=<optimized out>, fn=<optimized out>, private_data=0x7fffd7df3cc8, location=0x7fffa165b880 "../../source3/smbd/open.c:4292") at ../../source3/locking/share_mode_lock.c:3033
#30 0x00007fffa15491e0 in open_file_ntcreate (conn=conn@entry=0x154382050, req=req@entry=0x154414800, access_mask=<optimized out>, access_mask@entry=131201, share_access=share_access@entry=7, create_disposition=create_disposition@entry=1, create_options=create_options@entry=0, new_dos_attributes=<optimized out>, new_dos_attributes@entry=128, oplock_request=oplock_request@entry=0, lease=<optimized out>, lease@entry=0x0, private_flags=<optimized out>, private_flags@entry=0, parent_dir_fname=<optimized out>, smb_fname_atname=<optimized out>, pinfo=<optimized out>, pinfo@entry=0x7fffd7df3f1c, fsp=<optimized out>, fsp@entry=0x1543d01e0) at ../../source3/smbd/open.c:4286
#31 0x00007fffa154b94c in create_file_unixpath (conn=conn@entry=0x154382050, req=req@entry=0x154414800, dirfsp=dirfsp@entry=0x15439a7f0, smb_fname=smb_fname@entry=0x154416300, access_mask=access_mask@entry=131201, share_access=share_access@entry=7, create_disposition=create_disposition@entry=1, create_options=create_options@entry=0, file_attributes=file_attributes@entry=128, oplock_request=<optimized out>, oplock_request@entry=0, lease=<optimized out>, lease@entry=0x0, allocation_size=allocation_size@entry=0, private_flags=private_flags@entry=0, sd=sd@entry=0x0, ea_list=ea_list@entry=0x0, result=result@entry=0x7fffd7df4168, pinfo=pinfo@entry=0x7fffd7df4160) at ../../source3/smbd/open.c:6290
#32 0x00007fffa154dfac in create_file_default (conn=0x154382050, req=0x154414800, dirfsp=0x15439a7f0, smb_fname=0x154416300, access_mask=<optimized out>, share_access=<optimized out>, create_disposition=<optimized out>, create_options=<optimized out>, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/smbd/open.c:6609
#33 0x00007fffa150972c in vfswrap_create_file (handle=<optimized out>, req=<optimized out>, dirfsp=<optimized out>, smb_fname=<optimized out>, access_mask=<optimized out>, share_access=<optimized out>, create_disposition=<optimized out>, create_options=<optimized out>, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/modules/vfs_default.c:776
#34 0x00007fffa1559cbc in smb_vfs_call_create_file (handle=<optimized out>, req=<optimized out>, dirfsp=<optimized out>, smb_fname=<optimized out>, access_mask=<optimized out>, share_access=<optimized out>, create_disposition=<optimized out>, create_options=<optimized out>, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/smbd/vfs.c:1560
#35 0x00007fff9c0a9ec4 in smb_time_audit_create_file (handle=0x154426820, req=0x154414800, dirfsp=0x15439a7f0, fname=0x154416300, access_mask=<optimized out>, share_access=<optimized out>, create_disposition=<optimized out>, create_options=<optimized out>, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result_fsp=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/modules/vfs_time_audit.c:634
#36 0x00007fffa1559cbc in smb_vfs_call_create_file (handle=<optimized out>, req=<optimized out>, dirfsp=<optimized out>, smb_fname=<optimized out>, access_mask=<optimized out>, share_access=<optimized out>, create_disposition=<optimized out>, create_options=<optimized out>, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/smbd/vfs.c:1560
#37 0x00007fffa1597aa8 in smbd_smb2_create_send (in_context_blobs=..., in_name=0x154413ca0, in_create_options=<optimized out>, in_create_disposition=<optimized out>, in_share_access=<optimized out>, in_file_attributes=<optimized out>, in_desired_access=<optimized out>, in_impersonation_level=<optimized out>, in_oplock_level=<optimized out>, smb2req=0x154413770, ev=0x154328120, mem_ctx=0x154413770) at ../../source3/smbd/smb2_create.c:1115
#38 smbd_smb2_request_process_create (smb2req=0x154413770) at ../../source3/smbd/smb2_create.c:291
#39 0x00007fffa158a628 in smbd_smb2_request_dispatch (req=0x154413770) at ../../source3/smbd/smb2_server.c:3485
#40 0x00007fffa158c540 in smbd_smb2_io_handler (fde_flags=<optimized out>, xconn=0x154313f30) at ../../source3/smbd/smb2_server.c:5112
#41 smbd_smb2_connection_handler (ev=<optimized out>, fde=<optimized out>, flags=<optimized out>, private_data=<optimized out>) at ../../source3/smbd/smb2_server.c:5150
#42 0x00007fffa1198b2c in tevent_common_invoke_fd_handler (fde=0x15435add0, flags=<optimized out>, removed=0x0) at ../../lib/tevent/tevent_fd.c:158
#43 0x00007fffa11a2b9c in epoll_event_loop (tvalp=0x7fffd7df4b28, epoll_ev=0x1543b4e80) at ../../lib/tevent/tevent_epoll.c:730
#44 epoll_event_loop_once (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent_epoll.c:946
#45 0x00007fffa11a0090 in std_event_loop_once (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent_standard.c:110
#46 0x00007fffa119744c in _tevent_loop_once (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent.c:823
#47 0x00007fffa1197884 in tevent_common_loop_wait (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent.c:950
#48 0x00007fffa119ffc0 in std_event_loop_wait (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent_standard.c:141
#49 0x00007fffa1197978 in _tevent_loop_wait (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent.c:971
#50 0x00007fffa15737fc in smbd_process (ev_ctx=0x154328120, msg_ctx=<optimized out>, sock_fd=<optimized out>, interactive=<optimized out>) at ../../source3/smbd/smb2_process.c:2158
#51 0x000000011db5c554 in smbd_accept_connection (ev=0x154328120, fde=<optimized out>, flags=<optimized out>, private_data=<optimized out>) at ../../source3/smbd/server.c:1150
#52 0x00007fffa1198b2c in tevent_common_invoke_fd_handler (fde=0x1543ac2d0, flags=<optimized out>, removed=0x0) at ../../lib/tevent/tevent_fd.c:158
#53 0x00007fffa11a2b9c in epoll_event_loop (tvalp=0x7fffd7df4f98, epoll_ev=0x154328350) at ../../lib/tevent/tevent_epoll.c:730
#54 epoll_event_loop_once (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent_epoll.c:946
#55 0x00007fffa11a0090 in std_event_loop_once (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent_standard.c:110
#56 0x00007fffa119744c in _tevent_loop_once (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent.c:823
#57 0x00007fffa1197884 in tevent_common_loop_wait (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent.c:950
#58 0x00007fffa119ffc0 in std_event_loop_wait (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent_standard.c:141
#59 0x00007fffa1197978 in _tevent_loop_wait (ev=<optimized out>, location=<optimized out>) at ../../lib/tevent/tevent.c:971
#60 0x000000011db58c54 in smbd_parent_loop (parent=<optimized out>, ev_ctx=0x154328120) at ../../source3/smbd/server.c:1499
#61 main (argc=<optimized out>, argv=<optimized out>) at ../../source3/smbd/server.c:2258
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
(backported from commit 2eef298ff4c5baf15c7d29c65fb021dbed5b0a93)
[slow@samba.org: changed argument of share_mode_watch_send()]
[slow@samba.org: small context change in vfs_fruit]
---
source3/locking/locking.c | 109 +++++--------------
source3/locking/proto.h | 4 +-
source3/modules/vfs_fruit.c | 91 +++++++++++-----
source3/smbd/blocking.c | 202 +++++++++++++++++++-----------------
source3/smbd/proto.h | 19 ++--
source3/smbd/smb2_lock.c | 77 +++++++++-----
source3/smbd/smb2_reply.c | 53 ++++++----
7 files changed, 293 insertions(+), 262 deletions(-)
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index d796e6ffb7b..25b3bdcf7f7 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -237,52 +237,7 @@ static void decrement_current_lock_count(files_struct *fsp,
Utility function called by locking requests.
****************************************************************************/
-struct do_lock_state {
- struct files_struct *fsp;
- TALLOC_CTX *req_mem_ctx;
- const struct GUID *req_guid;
- uint64_t smblctx;
- uint64_t count;
- uint64_t offset;
- enum brl_type lock_type;
- enum brl_flavour lock_flav;
-
- struct server_id blocker_pid;
- uint64_t blocker_smblctx;
- NTSTATUS status;
-};
-
-static void do_lock_fn(
- struct share_mode_lock *lck,
- void *private_data)
-{
- struct do_lock_state *state = private_data;
- struct byte_range_lock *br_lck = NULL;
-
- br_lck = brl_get_locks_for_locking(talloc_tos(),
- state->fsp,
- state->req_mem_ctx,
- state->req_guid);
- if (br_lck == NULL) {
- state->status = NT_STATUS_NO_MEMORY;
- return;
- }
-
- state->status = brl_lock(
- br_lck,
- state->smblctx,
- messaging_server_id(state->fsp->conn->sconn->msg_ctx),
- state->offset,
- state->count,
- state->lock_type,
- state->lock_flav,
- &state->blocker_pid,
- &state->blocker_smblctx);
-
- TALLOC_FREE(br_lck);
-}
-
-NTSTATUS do_lock(files_struct *fsp,
+NTSTATUS do_lock(struct byte_range_lock *br_lck,
TALLOC_CTX *req_mem_ctx,
const struct GUID *req_guid,
uint64_t smblctx,
@@ -293,22 +248,13 @@ NTSTATUS do_lock(files_struct *fsp,
struct server_id *pblocker_pid,
uint64_t *psmblctx)
{
- struct do_lock_state state = {
- .fsp = fsp,
- .req_mem_ctx = req_mem_ctx,
- .req_guid = req_guid,
- .smblctx = smblctx,
- .count = count,
- .offset = offset,
- .lock_type = lock_type,
- .lock_flav = lock_flav,
- };
+ files_struct *fsp = brl_fsp(br_lck);
+ struct server_id blocker_pid;
+ uint64_t blocker_smblctx;
NTSTATUS status;
- /* silently return ok on print files as we don't do locking there */
- if (fsp->print_file) {
- return NT_STATUS_OK;
- }
+ SMB_ASSERT(req_mem_ctx != NULL);
+ SMB_ASSERT(req_guid != NULL);
if (!fsp->fsp_flags.can_lock) {
if (fsp->fsp_flags.is_directory) {
@@ -332,25 +278,27 @@ NTSTATUS do_lock(files_struct *fsp,
fsp_fnum_dbg(fsp),
fsp_str_dbg(fsp));
- status = share_mode_do_locked_vfs_allowed(fsp->file_id,
- do_lock_fn,
- &state);
- if (!NT_STATUS_IS_OK(status)) {
- DBG_DEBUG("share_mode_do_locked returned %s\n",
- nt_errstr(status));
- return status;
- }
- if (!NT_STATUS_IS_OK(state.status)) {
- DBG_DEBUG("do_lock_fn returned %s\n",
- nt_errstr(state.status));
+ brl_req_set(br_lck, req_mem_ctx, req_guid);
+ status = brl_lock(br_lck,
+ smblctx,
+ messaging_server_id(fsp->conn->sconn->msg_ctx),
+ offset,
+ count,
+ lock_type,
+ lock_flav,
+ &blocker_pid,
+ &blocker_smblctx);
+ brl_req_set(br_lck, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("brl_lock failed: %s\n", nt_errstr(status));
if (psmblctx != NULL) {
- *psmblctx = state.blocker_smblctx;
+ *psmblctx = blocker_smblctx;
}
if (pblocker_pid != NULL) {
- *pblocker_pid = state.blocker_pid;
+ *pblocker_pid = blocker_pid;
}
- return state.status;
- }
+ return status;
+ }
increment_current_lock_count(fsp, lock_flav);
@@ -361,14 +309,14 @@ NTSTATUS do_lock(files_struct *fsp,
Utility function called by unlocking requests.
****************************************************************************/
-NTSTATUS do_unlock(files_struct *fsp,
+NTSTATUS do_unlock(struct byte_range_lock *br_lck,
uint64_t smblctx,
uint64_t count,
uint64_t offset,
enum brl_flavour lock_flav)
{
+ files_struct *fsp = brl_fsp(br_lck);
bool ok = False;
- struct byte_range_lock *br_lck = NULL;
if (!fsp->fsp_flags.can_lock) {
return fsp->fsp_flags.is_directory ?
@@ -387,11 +335,6 @@ NTSTATUS do_unlock(files_struct *fsp,
fsp_fnum_dbg(fsp),
fsp_str_dbg(fsp));
- br_lck = brl_get_locks(talloc_tos(), fsp);
- if (!br_lck) {
- return NT_STATUS_NO_MEMORY;
- }
-
ok = brl_unlock(br_lck,
smblctx,
messaging_server_id(fsp->conn->sconn->msg_ctx),
@@ -399,8 +342,6 @@ NTSTATUS do_unlock(files_struct *fsp,
count,
lock_flav);
- TALLOC_FREE(br_lck);
-
if (!ok) {
DEBUG(10,("do_unlock: returning ERRlock.\n" ));
return NT_STATUS_RANGE_NOT_LOCKED;
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index c74539c8161..e332abf34ec 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -120,7 +120,7 @@ NTSTATUS query_lock(files_struct *fsp,
uint64_t *poffset,
enum brl_type *plock_type,
enum brl_flavour lock_flav);
-NTSTATUS do_lock(files_struct *fsp,
+NTSTATUS do_lock(struct byte_range_lock *br_lck,
TALLOC_CTX *req_mem_ctx,
const struct GUID *req_guid,
uint64_t smblctx,
@@ -130,7 +130,7 @@ NTSTATUS do_lock(files_struct *fsp,
enum brl_flavour lock_flav,
struct server_id *pblocker_pid,
uint64_t *psmblctx);
-NTSTATUS do_unlock(files_struct *fsp,
+NTSTATUS do_unlock(struct byte_range_lock *br_lck,
uint64_t smblctx,
uint64_t count,
uint64_t offset,
diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 49a1723864e..caaf1f73856 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -622,11 +622,21 @@ static bool test_netatalk_lock(files_struct *fsp, off_t in_offset)
return false;
}
-static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
- files_struct *fsp,
- uint32_t access_mask,
- uint32_t share_mode)
+struct check_access_state {
+ NTSTATUS status;
+ files_struct *fsp;
+ uint32_t access_mask;
+ uint32_t share_mode;
+};
+
+static void fruit_check_access(struct share_mode_lock *lck,
+ struct byte_range_lock *br_lck,
+ void *private_data)
{
+ struct check_access_state *state = private_data;
+ files_struct *fsp = state->fsp;
+ uint32_t access_mask = state->access_mask;
+ uint32_t share_mode = state->share_mode;
NTSTATUS status = NT_STATUS_OK;
off_t off;
bool share_for_read = (share_mode & FILE_SHARE_READ);
@@ -640,6 +650,14 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
/* FIXME: hardcoded data fork, add resource fork */
enum apple_fork fork_type = APPLE_FORK_DATA;
+ /*
+ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so
+ * br_lck has to be there!
+ */
+ SMB_ASSERT(br_lck != NULL);
+
+ state->status = NT_STATUS_OK;
+
DBG_DEBUG("fruit_check_access: %s, am: %s/%s, sm: 0x%x\n",
fsp_str_dbg(fsp),
access_mask & FILE_READ_DATA ? "READ" :"-",
@@ -647,7 +665,7 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
share_mode);
if (fsp_get_io_fd(fsp) == -1) {
- return NT_STATUS_OK;
+ return;
}
/* Read NetATalk opens and deny modes on the file. */
@@ -670,22 +688,26 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
/* If there are any conflicts - sharing violation. */
if ((access_mask & FILE_READ_DATA) &&
netatalk_already_open_with_deny_read) {
- return NT_STATUS_SHARING_VIOLATION;
+ state->status = NT_STATUS_SHARING_VIOLATION;
+ return;
}
if (!share_for_read &&
netatalk_already_open_for_reading) {
- return NT_STATUS_SHARING_VIOLATION;
+ state->status = NT_STATUS_SHARING_VIOLATION;
+ return;
}
if ((access_mask & FILE_WRITE_DATA) &&
netatalk_already_open_with_deny_write) {
- return NT_STATUS_SHARING_VIOLATION;
+ state->status = NT_STATUS_SHARING_VIOLATION;
+ return;
}
if (!share_for_write &&
netatalk_already_open_for_writing) {
- return NT_STATUS_SHARING_VIOLATION;
+ state->status = NT_STATUS_SHARING_VIOLATION;
+ return;
}
if (!(access_mask & FILE_READ_DATA)) {
@@ -693,15 +715,16 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
* Nothing we can do here, we need read access
* to set locks.
*/
- return NT_STATUS_OK;
+ return;
}
/* Set NetAtalk locks matching our access */
if (access_mask & FILE_READ_DATA) {
off = access_to_netatalk_brl(fork_type, FILE_READ_DATA);
req_guid.time_hi_and_version = __LINE__;
+
status = do_lock(
- fsp,
+ br_lck,
talloc_tos(),
&req_guid,
fsp->op->global->open_persistent_id,
@@ -711,17 +734,18 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
POSIX_LOCK,
NULL,
NULL);
-
if (!NT_STATUS_IS_OK(status)) {
- return status;
+ state->status = status;
+ return;
}
}
if (!share_for_read) {
off = denymode_to_netatalk_brl(fork_type, DENY_READ);
req_guid.time_hi_and_version = __LINE__;
+
status = do_lock(
- fsp,
+ br_lck,
talloc_tos(),
&req_guid,
fsp->op->global->open_persistent_id,
@@ -731,17 +755,18 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
POSIX_LOCK,
NULL,
NULL);
-
if (!NT_STATUS_IS_OK(status)) {
- return status;
+ state->status = status;
+ return;
}
}
if (access_mask & FILE_WRITE_DATA) {
off = access_to_netatalk_brl(fork_type, FILE_WRITE_DATA);
req_guid.time_hi_and_version = __LINE__;
+
status = do_lock(
- fsp,
+ br_lck,
talloc_tos(),
&req_guid,
fsp->op->global->open_persistent_id,
@@ -751,17 +776,18 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
POSIX_LOCK,
NULL,
NULL);
-
if (!NT_STATUS_IS_OK(status)) {
- return status;
+ state->status = status;
+ return;
}
}
if (!share_for_write) {
off = denymode_to_netatalk_brl(fork_type, DENY_WRITE);
req_guid.time_hi_and_version = __LINE__;
+
status = do_lock(
- fsp,
+ br_lck,
talloc_tos(),
&req_guid,
fsp->op->global->open_persistent_id,
@@ -771,13 +797,11 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
POSIX_LOCK,
NULL,
NULL);
-
if (!NT_STATUS_IS_OK(status)) {
- return status;
+ state->status = status;
+ return;
}
}
-
- return NT_STATUS_OK;
}
static NTSTATUS check_aapl(vfs_handle_struct *handle,
@@ -4346,16 +4370,27 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
}
if ((config->locking == FRUIT_LOCKING_NETATALK) &&
+ lp_locking(fsp->conn->params) &&
+ fsp->fsp_flags.can_lock &&
(fsp->op != NULL) &&
!fsp->fsp_flags.is_pathref)
{
- status = fruit_check_access(
- handle, *result,
- access_mask,
- share_access);
+ struct check_access_state state = (struct check_access_state) {
+ .fsp = fsp,
+ .access_mask = access_mask,
+ .share_mode = share_access,
+ };
+
+ status = share_mode_do_locked_brl(fsp,
+ fruit_check_access,
+ &state);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
+ if (!NT_STATUS_IS_OK(state.status)) {
+ status = state.status;
+ goto fail;
+ }
}
return status;
diff --git a/source3/smbd/blocking.c b/source3/smbd/blocking.c
index 8b41288bfbf..c9d9aff98b7 100644
--- a/source3/smbd/blocking.c
+++ b/source3/smbd/blocking.c
@@ -29,31 +29,27 @@
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_LOCKING
-NTSTATUS smbd_do_locks_try(
- struct files_struct *fsp,
- uint16_t num_locks,
- struct smbd_lock_element *locks,
- uint16_t *blocker_idx,
- struct server_id *blocking_pid,
- uint64_t *blocking_smblctx)
+NTSTATUS smbd_do_locks_try(struct byte_range_lock *br_lck,
+ struct smbd_do_locks_state *state)
{
- NTSTATUS status = NT_STATUS_OK;
+ bool unlock_ok;
uint16_t i;
+ NTSTATUS status = NT_STATUS_OK;
- for (i=0; i<num_locks; i++) {
- struct smbd_lock_element *e = &locks[i];
+ for (i = 0; i < state->num_locks; i++) {
+ struct smbd_lock_element *e = &state->locks[i];
status = do_lock(
- fsp,
- locks, /* req_mem_ctx */
+ br_lck,
+ state->locks, /* req_mem_ctx */
&e->req_guid,
e->smblctx,
e->count,
e->offset,
e->brltype,
e->lock_flav,
- blocking_pid,
- blocking_smblctx);
+ &state->blocking_pid,
+ &state->blocking_smblctx);
if (!NT_STATUS_IS_OK(status)) {
break;
}
@@ -63,18 +59,35 @@ NTSTATUS smbd_do_locks_try(
return NT_STATUS_OK;
}
- *blocker_idx = i;
+ state->blocker_idx = i;
+ unlock_ok = true;
/*
* Undo the locks we successfully got
*/
for (i = i-1; i != UINT16_MAX; i--) {
- struct smbd_lock_element *e = &locks[i];
- do_unlock(fsp,
- e->smblctx,
- e->count,
- e->offset,
- e->lock_flav);
+ struct smbd_lock_element *e = &state->locks[i];
+ NTSTATUS ulstatus;
+
+ ulstatus = do_unlock(br_lck,
+ e->smblctx,
+ e->count,
+ e->offset,
+ e->lock_flav);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_DEBUG("Failed to undo lock flavour %s lock "
+ "type %s start=%"PRIu64" len=%"PRIu64" "
+ "requested for file [%s]\n",
+ lock_flav_name(e->lock_flav),
+ lock_type_name(e->brltype),
+ e->offset,
+ e->count,
+ fsp_str_dbg(brl_fsp(br_lck)));
+ unlock_ok = false;
+ }
+ }
+ if (unlock_ok) {
+ brl_set_modified(br_lck, false);
}
return status;
@@ -118,13 +131,6 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req);
static void smbd_smb1_do_locks_retry(struct tevent_req *subreq);
static void smbd_smb1_blocked_locks_cleanup(
struct tevent_req *req, enum tevent_req_state req_state);
-static NTSTATUS smbd_smb1_do_locks_check(
- struct files_struct *fsp,
- uint16_t num_locks,
- struct smbd_lock_element *locks,
- uint16_t *blocker_idx,
- struct server_id *blocking_pid,
- uint64_t *blocking_smblctx);
static void smbd_smb1_do_locks_setup_timeout(
struct smbd_smb1_do_locks_state *state,
@@ -378,18 +384,35 @@ static NTSTATUS smbd_smb1_do_locks_check_blocked(
return NT_STATUS_OK;
}
-static NTSTATUS smbd_smb1_do_locks_check(
- struct files_struct *fsp,
- uint16_t num_locks,
- struct smbd_lock_element *locks,
- uint16_t *blocker_idx,
- struct server_id *blocking_pid,
- uint64_t *blocking_smblctx)
+static void smbd_smb1_do_locks_try_fn(struct share_mode_lock *lck,
+ struct byte_range_lock *br_lck,
+ void *private_data)
{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ struct smbd_do_locks_state brl_state;
+ struct files_struct *fsp = state->fsp;
struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
size_t num_blocked = talloc_array_length(blocked);
- NTSTATUS status;
+ struct timeval endtime = { 0 };
+ struct tevent_req *subreq = NULL;
size_t bi;
+ NTSTATUS status;
+ bool ok;
+ bool expired;
+
+ /*
+ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so
+ * br_lck has to be there!
+ */
+ SMB_ASSERT(br_lck != NULL);
+
+ brl_state = (struct smbd_do_locks_state) {
+ .num_locks = state->num_locks,
+ .locks = state->locks,
+ };
/*
* We check the pending/blocked requests
@@ -404,8 +427,8 @@ static NTSTATUS smbd_smb1_do_locks_check(
tevent_req_data(blocked[bi],
struct smbd_smb1_do_locks_state);
- if (blocked_state->locks == locks) {
- SMB_ASSERT(blocked_state->num_locks == num_locks);
+ if (blocked_state->locks == state->locks) {
+ SMB_ASSERT(blocked_state->num_locks == state->num_locks);
/*
* We found ourself...
@@ -416,61 +439,24 @@ static NTSTATUS smbd_smb1_do_locks_check(
status = smbd_smb1_do_locks_check_blocked(
blocked_state->num_locks,
blocked_state->locks,
- num_locks,
- locks,
- blocker_idx,
- blocking_smblctx);
+ state->num_locks,
+ state->locks,
+ &brl_state.blocker_idx,
+ &brl_state.blocking_smblctx);
if (!NT_STATUS_IS_OK(status)) {
- *blocking_pid = messaging_server_id(
- fsp->conn->sconn->msg_ctx);
- return status;
+ brl_state.blocking_pid = messaging_server_id(
+ fsp->conn->sconn->msg_ctx);
+ goto check_retry;
}
}
- status = smbd_do_locks_try(
- fsp,
- num_locks,
- locks,
- blocker_idx,
- blocking_pid,
- blocking_smblctx);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
-
- return NT_STATUS_OK;
-}
-
-static void smbd_smb1_do_locks_try(struct tevent_req *req)
-{
- struct smbd_smb1_do_locks_state *state = tevent_req_data(
- req, struct smbd_smb1_do_locks_state);
- struct files_struct *fsp = state->fsp;
- struct share_mode_lock *lck;
- struct timeval endtime = { 0 };
- struct server_id blocking_pid = { 0 };
- uint64_t blocking_smblctx = 0;
- struct tevent_req *subreq = NULL;
- NTSTATUS status;
- bool ok;
- bool expired;
-
- lck = get_existing_share_mode_lock(state, fsp->file_id);
- if (tevent_req_nomem(lck, req)) {
- DBG_DEBUG("Could not get share mode lock\n");
- return;
- }
-
- status = smbd_smb1_do_locks_check(
- fsp,
- state->num_locks,
- state->locks,
- &state->blocker,
- &blocking_pid,
- &blocking_smblctx);
+ status = smbd_do_locks_try(br_lck, &brl_state);
if (NT_STATUS_IS_OK(status)) {
goto done;
}
+
+ state->blocker = brl_state.blocker_idx;
+
if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
/*
* We got NT_STATUS_RETRY,
@@ -494,8 +480,8 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req)
* locking.tdb may cause retries.
*/
- if (blocking_smblctx != UINT64_MAX) {
- SMB_ASSERT(blocking_smblctx == 0);
+ if (brl_state.blocking_smblctx != UINT64_MAX) {
+ SMB_ASSERT(brl_state.blocking_smblctx == 0);
goto setup_retry;
}
@@ -516,6 +502,8 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req)
endtime = timeval_current_ofs_msec(state->retry_msecs);
goto setup_retry;
}
+
+check_retry:
if (!ERROR_WAS_LOCK_DENIED(status)) {
goto done;
}
@@ -529,7 +517,7 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req)
smbd_smb1_do_locks_setup_timeout(state, &state->locks[state->blocker]);
DBG_DEBUG("timeout=%"PRIu32", blocking_smblctx=%"PRIu64"\n",
state->timeout,
- blocking_smblctx);
+ brl_state.blocking_smblctx);
/*
* The client specified timeout expired
@@ -554,7 +542,7 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req)
endtime = state->endtime;
- if (blocking_smblctx == UINT64_MAX) {
+ if (brl_state.blocking_smblctx == UINT64_MAX) {
struct timeval tmp;
smbd_smb1_do_locks_update_polling_msecs(state);
@@ -568,11 +556,11 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req)
setup_retry:
subreq = share_mode_watch_send(
- state, state->ev, lck, blocking_pid);
+ state, state->ev, lck, brl_state.blocking_pid);
if (tevent_req_nomem(subreq, req)) {
+ status = NT_STATUS_NO_MEMORY;
goto done;
}
- TALLOC_FREE(lck);
tevent_req_set_callback(subreq, smbd_smb1_do_locks_retry, req);
if (timeval_is_zero(&endtime)) {
@@ -586,10 +574,38 @@ setup_retry:
}
return;
done:
- TALLOC_FREE(lck);
smbd_smb1_brl_finish_by_req(req, status);
}
+static void smbd_smb1_do_locks_try(struct tevent_req *req)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ NTSTATUS status;
+
+ if (!state->fsp->fsp_flags.can_lock) {
+ if (state->fsp->fsp_flags.is_directory) {
+ return smbd_smb1_brl_finish_by_req(req,
+ NT_STATUS_INVALID_DEVICE_REQUEST);
+ }
+ return smbd_smb1_brl_finish_by_req(req,
+ NT_STATUS_INVALID_HANDLE);
+ }
+
+ if (!lp_locking(state->fsp->conn->params)) {
+ return smbd_smb1_brl_finish_by_req(req, NT_STATUS_OK);
+ }
+
+ status = share_mode_do_locked_brl(state->fsp,
+ smbd_smb1_do_locks_try_fn,
+ req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_smb1_brl_finish_by_req(req, status);
+ return;
+ }
+ return;
+}
+
static void smbd_smb1_do_locks_retry(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 78e1b48be09..b8bb8eb293c 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -86,13 +86,18 @@ ssize_t pwrite_fsync_recv(struct tevent_req *req, int *perr);
/* The following definitions come from smbd/blocking.c */
-NTSTATUS smbd_do_locks_try(
- struct files_struct *fsp,
- uint16_t num_locks,
- struct smbd_lock_element *locks,
- uint16_t *blocker_idx,
- struct server_id *blocking_pid,
- uint64_t *blocking_smblctx);
+struct smbd_do_locks_state {
+ uint16_t num_locks;
+ struct smbd_lock_element *locks;
+ NTSTATUS status;
+ uint16_t blocker_idx;
+ struct server_id blocking_pid;
+ uint64_t blocking_smblctx;
+};
+
+NTSTATUS smbd_do_locks_try(struct byte_range_lock *br_lck,
+ struct smbd_do_locks_state *state);
+
struct tevent_req *smbd_smb1_do_locks_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
diff --git a/source3/smbd/smb2_lock.c b/source3/smbd/smb2_lock.c
index c9d810f71ba..14f912bf2da 100644
--- a/source3/smbd/smb2_lock.c
+++ b/source3/smbd/smb2_lock.c
@@ -569,33 +569,32 @@ static void smbd_smb2_lock_update_polling_msecs(
state->polling_msecs += v_min;
}
-static void smbd_smb2_lock_try(struct tevent_req *req)
+static void smbd_do_locks_try_fn(struct share_mode_lock *lck,
+ struct byte_range_lock *br_lck,
+ void *private_data)
{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
struct smbd_smb2_lock_state *state = tevent_req_data(
req, struct smbd_smb2_lock_state);
- struct share_mode_lock *lck = NULL;
- uint16_t blocker_idx;
- struct server_id blocking_pid = { 0 };
- uint64_t blocking_smblctx;
- NTSTATUS status;
+ struct smbd_do_locks_state brl_state;
struct tevent_req *subreq = NULL;
struct timeval endtime = { 0 };
+ NTSTATUS status;
- lck = get_existing_share_mode_lock(
- talloc_tos(), state->fsp->file_id);
- if (tevent_req_nomem(lck, req)) {
- return;
- }
+ /*
+ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so
+ * br_lck has to be there!
+ */
+ SMB_ASSERT(br_lck != NULL);
- status = smbd_do_locks_try(
- state->fsp,
- state->lock_count,
- state->locks,
- &blocker_idx,
- &blocking_pid,
- &blocking_smblctx);
+ brl_state = (struct smbd_do_locks_state) {
+ .num_locks = state->lock_count,
+ .locks = state->locks,
+ };
+
+ status = smbd_do_locks_try(br_lck, &brl_state);
if (NT_STATUS_IS_OK(status)) {
- TALLOC_FREE(lck);
tevent_req_done(req);
return;
}
@@ -622,8 +621,8 @@ static void smbd_smb2_lock_try(struct tevent_req *req)
* locking.tdb may cause retries.
*/
- if (blocking_smblctx != UINT64_MAX) {
- SMB_ASSERT(blocking_smblctx == 0);
+ if (brl_state.blocking_smblctx != UINT64_MAX) {
+ SMB_ASSERT(brl_state.blocking_smblctx == 0);
goto setup_retry;
}
@@ -658,7 +657,6 @@ static void smbd_smb2_lock_try(struct tevent_req *req)
status = NT_STATUS_LOCK_NOT_GRANTED;
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_LOCK_NOT_GRANTED)) {
- TALLOC_FREE(lck);
tevent_req_nterror(req, status);
return;
}
@@ -670,12 +668,11 @@ static void smbd_smb2_lock_try(struct tevent_req *req)
state->retry_msecs = 0;
if (!state->blocking) {
- TALLOC_FREE(lck);
tevent_req_nterror(req, status);
return;
}
- if (blocking_smblctx == UINT64_MAX) {
+ if (brl_state.blocking_smblctx == UINT64_MAX) {
smbd_smb2_lock_update_polling_msecs(state);
DBG_DEBUG("Blocked on a posix lock. Retry in %"PRIu32" msecs\n",
@@ -688,8 +685,7 @@ setup_retry:
DBG_DEBUG("Watching share mode lock\n");
subreq = share_mode_watch_send(
- state, state->ev, lck, blocking_pid);
- TALLOC_FREE(lck);
+ state, state->ev, lck, brl_state.blocking_pid);
if (tevent_req_nomem(subreq, req)) {
return;
}
@@ -708,6 +704,35 @@ setup_retry:
}
}
+static void smbd_smb2_lock_try(struct tevent_req *req)
+{
+ struct smbd_smb2_lock_state *state = tevent_req_data(
+ req, struct smbd_smb2_lock_state);
+ NTSTATUS status;
+
+ if (!state->fsp->fsp_flags.can_lock) {
+ if (state->fsp->fsp_flags.is_directory) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_DEVICE_REQUEST);
+ return;
+ }
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (!lp_locking(state->fsp->conn->params)) {
+ return tevent_req_done(req);
+ }
+
+ status = share_mode_do_locked_brl(state->fsp,
+ smbd_do_locks_try_fn,
+ req);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+}
+
static void smbd_smb2_lock_retry(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
diff --git a/source3/smbd/smb2_reply.c b/source3/smbd/smb2_reply.c
index dfcd05d2cae..c782a624d7f 100644
--- a/source3/smbd/smb2_reply.c
+++ b/source3/smbd/smb2_reply.c
@@ -2113,23 +2113,22 @@ uint64_t get_lock_offset(const uint8_t *data, int data_offset,
return offset;
}
-struct smbd_do_unlocking_state {
- struct files_struct *fsp;
- uint16_t num_ulocks;
- struct smbd_lock_element *ulocks;
- NTSTATUS status;
-};
-
-static void smbd_do_unlocking_fn(
- struct share_mode_lock *lck,
- void *private_data)
+static void smbd_do_unlocking_fn(struct share_mode_lock *lck,
+ struct byte_range_lock *br_lck,
+ void *private_data)
{
- struct smbd_do_unlocking_state *state = private_data;
- struct files_struct *fsp = state->fsp;
+ struct smbd_do_locks_state *state = private_data;
+ struct files_struct *fsp = brl_fsp(br_lck);
uint16_t i;
- for (i = 0; i < state->num_ulocks; i++) {
- struct smbd_lock_element *e = &state->ulocks[i];
+ /*
+ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so
+ * br_lck has to be there!
+ */
+ SMB_ASSERT(br_lck != NULL);
+
+ for (i = 0; i < state->num_locks; i++) {
+ struct smbd_lock_element *e = &state->locks[i];
DBG_DEBUG("unlock start=%"PRIu64", len=%"PRIu64" for "
"pid %"PRIu64", file %s\n",
@@ -2145,7 +2144,7 @@ static void smbd_do_unlocking_fn(
}
state->status = do_unlock(
- fsp, e->smblctx, e->count, e->offset, e->lock_flav);
+ br_lck, e->smblctx, e->count, e->offset, e->lock_flav);
DBG_DEBUG("do_unlock returned %s\n",
nt_errstr(state->status));
@@ -2163,20 +2162,30 @@ NTSTATUS smbd_do_unlocking(struct smb_request *req,
uint16_t num_ulocks,
struct smbd_lock_element *ulocks)
{
- struct smbd_do_unlocking_state state = {
- .fsp = fsp,
- .num_ulocks = num_ulocks,
- .ulocks = ulocks,
+ struct smbd_do_locks_state state = {
+ .num_locks = num_ulocks,
+ .locks = ulocks,
};
NTSTATUS status;
DBG_NOTICE("%s num_ulocks=%"PRIu16"\n", fsp_fnum_dbg(fsp), num_ulocks);
- status = share_mode_do_locked_vfs_allowed(
- fsp->file_id, smbd_do_unlocking_fn, &state);
+ if (!fsp->fsp_flags.can_lock) {
+ if (fsp->fsp_flags.is_directory) {
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!lp_locking(fsp->conn->params)) {
+ return NT_STATUS_OK;
+ }
+ status = share_mode_do_locked_brl(fsp,
+ smbd_do_unlocking_fn,
+ &state);
if (!NT_STATUS_IS_OK(status)) {
- DBG_DEBUG("share_mode_do_locked_vfs_allowed failed: %s\n",
+ DBG_DEBUG("share_mode_do_locked_brl failed: %s\n",
nt_errstr(status));
return status;
}
--
2.53.0
From e067ece27012e10a5a49cf0f757fcfb37bc97450 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Wed, 8 Jan 2025 12:51:37 +0100
Subject: [PATCH 089/122] s3/brlock: remove brl_get_locks_for_locking()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Pair-Programmed-With: Ralph Boehme <slow@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 0c4c430c50e15d591a0d871a5f3e59e8be0d0a83)
---
source3/locking/brlock.c | 19 -------------------
source3/locking/proto.h | 4 ----
2 files changed, 23 deletions(-)
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index e0d18800cf1..592792c5303 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -1824,25 +1824,6 @@ struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, files_struct *fsp)
return br_lck;
}
-struct byte_range_lock *brl_get_locks_for_locking(TALLOC_CTX *mem_ctx,
- files_struct *fsp,
- TALLOC_CTX *req_mem_ctx,
- const struct GUID *req_guid)
-{
- struct byte_range_lock *br_lck = NULL;
-
- br_lck = brl_get_locks(mem_ctx, fsp);
- if (br_lck == NULL) {
- return NULL;
- }
- SMB_ASSERT(req_mem_ctx != NULL);
- br_lck->req_mem_ctx = req_mem_ctx;
- SMB_ASSERT(req_guid != NULL);
- br_lck->req_guid = req_guid;
-
- return br_lck;
-}
-
struct brl_get_locks_readonly_state {
TALLOC_CTX *mem_ctx;
struct byte_range_lock **br_lock;
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index e332abf34ec..44b43c1b1e2 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -83,10 +83,6 @@ int brl_forall(void (*fn)(struct file_id id, struct server_id pid,
br_off start, br_off size,
void *private_data),
void *private_data);
-struct byte_range_lock *brl_get_locks_for_locking(TALLOC_CTX *mem_ctx,
- files_struct *fsp,
- TALLOC_CTX *req_mem_ctx,
- const struct GUID *req_guid);
struct share_mode_lock;
typedef void (*share_mode_do_locked_brl_fn_t)(
struct share_mode_lock *lck,
--
2.53.0
From 79331346ad6305470c67710e7a6189442ca491f1 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Mon, 27 Jan 2025 15:22:26 +0100
Subject: [PATCH 090/122] smbd: call locking_close_file() while still holding a
glock on the locking.tdb record
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 4d680b6c17ee7674b9686aec2b69038f89e1989a)
---
source3/smbd/close.c | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/source3/smbd/close.c b/source3/smbd/close.c
index e16cb2d3485..f36e699c6ea 100644
--- a/source3/smbd/close.c
+++ b/source3/smbd/close.c
@@ -303,6 +303,17 @@ static void close_share_mode_lock_prepare(struct share_mode_lock *lck,
*/
*keep_locked = false;
+ if (fsp->current_lock_count > 0) {
+ /*
+ * Remove the byte-range locks under the glock
+ */
+ *keep_locked = true;
+ }
+
+ if (fh_get_refcount(fsp->fh) > 1) {
+ return;
+ }
+
if (fsp->oplock_type != NO_OPLOCK) {
ok = remove_share_oplock(lck, fsp);
if (!ok) {
@@ -453,6 +464,12 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
return status;
}
+ locking_close_file(fsp, close_type);
+
+ if (fh_get_refcount(fsp->fh) > 1) {
+ goto done;
+ }
+
/* Remove the oplock before potentially deleting the file. */
if (fsp->oplock_type != NO_OPLOCK) {
release_file_oplock(fsp);
@@ -901,13 +918,8 @@ static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp,
the same handle we only have one share mode. Ensure we only remove
the share mode on the last close. */
- if (fh_get_refcount(fsp->fh) == 1) {
- /* Should we return on error here... ? */
- tmp = close_remove_share_mode(fsp, close_type);
- status = ntstatus_keeperror(status, tmp);
- }
-
- locking_close_file(fsp, close_type);
+ tmp = close_remove_share_mode(fsp, close_type);
+ status = ntstatus_keeperror(status, tmp);
/*
* Ensure pending modtime is set before closing underlying fd.
--
2.53.0
From 02708477b64206765976cb03e70c95a4b4daf894 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 30 Jan 2025 17:35:26 +0100
Subject: [PATCH 091/122] s3/locking: prepare brl_locktest() for upgradable
read-only locks
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 8f9387ceb5c94c7db92ab342e33c64b858c301b1)
---
source3/locking/brlock.c | 5 +++--
source3/locking/locking.c | 4 ++--
source3/locking/proto.h | 3 ++-
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index 592792c5303..08500c4d1ac 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -1283,7 +1283,8 @@ bool brl_unlock(struct byte_range_lock *br_lck,
****************************************************************************/
bool brl_locktest(struct byte_range_lock *br_lck,
- const struct lock_struct *rw_probe)
+ const struct lock_struct *rw_probe,
+ bool upgradable)
{
bool ret = True;
unsigned int i;
@@ -1296,7 +1297,7 @@ bool brl_locktest(struct byte_range_lock *br_lck,
* Our own locks don't conflict.
*/
if (brl_conflict_other(&locks[i], rw_probe)) {
- if (br_lck->record == NULL) {
+ if (!upgradable) {
/* readonly */
return false;
}
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index 25b3bdcf7f7..d8e70a479ad 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -143,7 +143,7 @@ bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock)
if (!br_lck) {
return true;
}
- ret = brl_locktest(br_lck, plock);
+ ret = brl_locktest(br_lck, plock, false);
if (!ret) {
/*
@@ -154,7 +154,7 @@ bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock)
if (br_lck == NULL) {
return true;
}
- ret = brl_locktest(br_lck, plock);
+ ret = brl_locktest(br_lck, plock, true);
TALLOC_FREE(br_lck);
}
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index 44b43c1b1e2..44808171f1a 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -66,7 +66,8 @@ bool brl_unlock(struct byte_range_lock *br_lck,
bool brl_unlock_windows_default(struct byte_range_lock *br_lck,
const struct lock_struct *plock);
bool brl_locktest(struct byte_range_lock *br_lck,
- const struct lock_struct *rw_probe);
+ const struct lock_struct *rw_probe,
+ bool upgradable);
NTSTATUS brl_lockquery(struct byte_range_lock *br_lck,
uint64_t *psmblctx,
struct server_id pid,
--
2.53.0
From 0bf638a66c95a1f8304cb3efa284f2303445f2c0 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Wed, 2 Apr 2025 12:43:15 +0200
Subject: [PATCH 092/122] smbd: check can_lock in strict_lock_check_default()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 678f28c1af7c160ffdcb0e4baa0a7d4b9906f2e5)
---
source3/locking/locking.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index d8e70a479ad..9bb0696946e 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -115,7 +115,10 @@ bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock)
return True;
}
- if (!lp_locking(fsp->conn->params) || !strict_locking) {
+ if (!lp_locking(fsp->conn->params) ||
+ !strict_locking ||
+ !fsp->fsp_flags.can_lock)
+ {
return True;
}
--
2.53.0
From 936a9736ba92cf9827591a2d8279ec7953717323 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 30 Jan 2025 07:40:32 +0100
Subject: [PATCH 093/122] smbd: use share_mode_do_locked_brl() in
strict_lock_check_default()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit 56bb20c87a733ab8f7efedd881ea0ecaf51b2ba8)
---
source3/locking/locking.c | 43 +++++++++++++++++++++++++++++++++------
1 file changed, 37 insertions(+), 6 deletions(-)
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index 9bb0696946e..400b4722379 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -105,10 +105,32 @@ void init_strict_lock_struct(files_struct *fsp,
plock->lock_flav = lp_posix_cifsu_locktype(fsp);
}
+struct strict_lock_check_state {
+ bool ret;
+ files_struct *fsp;
+ struct lock_struct *plock;
+};
+
+static void strict_lock_check_default_fn(struct share_mode_lock *lck,
+ struct byte_range_lock *br_lck,
+ void *private_data)
+{
+ struct strict_lock_check_state *state = private_data;
+
+ /*
+ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so
+ * br_lck has to be there!
+ */
+ SMB_ASSERT(br_lck != NULL);
+
+ state->ret = brl_locktest(br_lck, state->plock, true);
+}
+
bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock)
{
struct byte_range_lock *br_lck;
int strict_locking = lp_strict_locking(fsp->conn->params);
+ NTSTATUS status;
bool ret = False;
if (plock->size == 0) {
@@ -147,18 +169,27 @@ bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock)
return true;
}
ret = brl_locktest(br_lck, plock, false);
-
if (!ret) {
/*
* We got a lock conflict. Retry with rw locks to enable
* autocleanup. This is the slow path anyway.
*/
- br_lck = brl_get_locks(talloc_tos(), fsp);
- if (br_lck == NULL) {
- return true;
+
+ struct strict_lock_check_state state =
+ (struct strict_lock_check_state) {
+ .fsp = fsp,
+ .plock = plock,
+ };
+
+ status = share_mode_do_locked_brl(fsp,
+ strict_lock_check_default_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_do_locked_brl [%s] failed: %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ state.ret = false;
}
- ret = brl_locktest(br_lck, plock, true);
- TALLOC_FREE(br_lck);
+ ret = state.ret;
}
DEBUG(10, ("strict_lock_default: flavour = %s brl start=%ju "
--
2.53.0
From 66794d5d5c2b930d6afde8103484333f53b2e68c Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 28 Jan 2025 11:19:05 +0100
Subject: [PATCH 094/122] smbd: use share_mode_do_locked_brl() in
vfs_default_durable_disconnect()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(backported from commit 393379fc9c726eb781fd1bfb3a70ea2802739aff)
[slow@samba.org: conflict due to removed delayed write time handling]
---
source3/locking/brlock.c | 14 +----
source3/locking/proto.h | 9 ++-
source3/smbd/durable.c | 117 ++++++++++++++++++++++++---------------
3 files changed, 82 insertions(+), 58 deletions(-)
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index 08500c4d1ac..b4c8501b4de 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -1459,14 +1459,14 @@ void brl_close_fnum(struct byte_range_lock *br_lck)
}
}
-bool brl_mark_disconnected(struct files_struct *fsp)
+bool brl_mark_disconnected(struct files_struct *fsp,
+ struct byte_range_lock *br_lck)
{
uint32_t tid = fsp->conn->cnum;
uint64_t smblctx;
uint64_t fnum = fsp->fnum;
unsigned int i;
struct server_id self = messaging_server_id(fsp->conn->sconn->msg_ctx);
- struct byte_range_lock *br_lck = NULL;
if (fsp->op == NULL) {
return false;
@@ -1482,11 +1482,6 @@ bool brl_mark_disconnected(struct files_struct *fsp)
return true;
}
- br_lck = brl_get_locks(talloc_tos(), fsp);
- if (br_lck == NULL) {
- return false;
- }
-
for (i=0; i < br_lck->num_locks; i++) {
struct lock_struct *lock = &br_lck->lock_data[i];
@@ -1496,22 +1491,18 @@ bool brl_mark_disconnected(struct files_struct *fsp)
*/
if (lock->context.smblctx != smblctx) {
- TALLOC_FREE(br_lck);
return false;
}
if (lock->context.tid != tid) {
- TALLOC_FREE(br_lck);
return false;
}
if (!server_id_equal(&lock->context.pid, &self)) {
- TALLOC_FREE(br_lck);
return false;
}
if (lock->fnum != fnum) {
- TALLOC_FREE(br_lck);
return false;
}
@@ -1521,7 +1512,6 @@ bool brl_mark_disconnected(struct files_struct *fsp)
}
br_lck->modified = true;
- TALLOC_FREE(br_lck);
return true;
}
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index 44808171f1a..d3b4e02bc26 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -75,7 +75,14 @@ NTSTATUS brl_lockquery(struct byte_range_lock *br_lck,
br_off *psize,
enum brl_type *plock_type,
enum brl_flavour lock_flav);
-bool brl_mark_disconnected(struct files_struct *fsp);
+
+struct brl_connectstate {
+ bool ok;
+ struct files_struct *fsp;
+};
+
+bool brl_mark_disconnected(struct files_struct *fsp,
+ struct byte_range_lock *br_lck);
bool brl_reconnect_disconnected(struct files_struct *fsp);
void brl_close_fnum(struct byte_range_lock *br_lck);
int brl_forall(void (*fn)(struct file_id id, struct server_id pid,
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
index bf3cbed0a2c..1506d37208a 100644
--- a/source3/smbd/durable.c
+++ b/source3/smbd/durable.c
@@ -133,6 +133,63 @@ NTSTATUS vfs_default_durable_cookie(struct files_struct *fsp,
return NT_STATUS_OK;
}
+struct durable_disconnect_state {
+ NTSTATUS status;
+ struct files_struct *fsp;
+};
+
+static void default_durable_disconnect_fn(struct share_mode_lock *lck,
+ struct byte_range_lock *br_lck,
+ void *private_data)
+{
+ struct durable_disconnect_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+ struct smb_file_time ft;
+ bool ok;
+
+ /* Ensure any pending write time updates are done. */
+ if (fsp->update_write_time_event) {
+ fsp_flush_write_time_update(fsp);
+ }
+
+
+ init_smb_file_time(&ft);
+
+ if (fsp->fsp_flags.write_time_forced) {
+ NTTIME mtime = share_mode_changed_write_time(lck);
+ ft.mtime = nt_time_to_full_timespec(mtime);
+ } else if (fsp->fsp_flags.update_write_time_on_close) {
+ if (is_omit_timespec(&fsp->close_write_time)) {
+ ft.mtime = timespec_current();
+ } else {
+ ft.mtime = fsp->close_write_time;
+ }
+ }
+
+ if (!is_omit_timespec(&ft.mtime)) {
+ round_timespec(fsp->conn->ts_res, &ft.mtime);
+ file_ntimes(fsp->conn, fsp, &ft);
+ }
+
+ ok = mark_share_mode_disconnected(lck, fsp);
+ if (!ok) {
+ state->status = NT_STATUS_UNSUCCESSFUL;
+ return;
+ }
+
+ if (br_lck == NULL) {
+ state->status = NT_STATUS_OK;
+ return;
+ }
+
+ ok = brl_mark_disconnected(fsp, br_lck);
+ if (!ok) {
+ state->status = NT_STATUS_UNSUCCESSFUL;
+ return;
+ }
+ state->status = NT_STATUS_OK;
+}
+
NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
const DATA_BLOB old_cookie,
TALLOC_CTX *mem_ctx,
@@ -143,8 +200,7 @@ NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
enum ndr_err_code ndr_err;
struct vfs_default_durable_cookie cookie;
DATA_BLOB new_cookie_blob = data_blob_null;
- struct share_mode_lock *lck;
- bool ok;
+ struct durable_disconnect_state state;
*new_cookie = data_blob_null;
@@ -198,52 +254,23 @@ NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
return NT_STATUS_NOT_SUPPORTED;
}
- /* Ensure any pending write time updates are done. */
- if (fsp->update_write_time_event) {
- fsp_flush_write_time_update(fsp);
- }
+ state = (struct durable_disconnect_state) {
+ .fsp = fsp,
+ };
- /*
- * The above checks are done in mark_share_mode_disconnected() too
- * but we want to avoid getting the lock if possible
- */
- lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
- if (lck != NULL) {
- struct smb_file_time ft;
-
- init_smb_file_time(&ft);
-
- if (fsp->fsp_flags.write_time_forced) {
- NTTIME mtime = share_mode_changed_write_time(lck);
- ft.mtime = nt_time_to_full_timespec(mtime);
- } else if (fsp->fsp_flags.update_write_time_on_close) {
- if (is_omit_timespec(&fsp->close_write_time)) {
- ft.mtime = timespec_current();
- } else {
- ft.mtime = fsp->close_write_time;
- }
- }
-
- if (!is_omit_timespec(&ft.mtime)) {
- round_timespec(conn->ts_res, &ft.mtime);
- file_ntimes(conn, fsp, &ft);
- }
-
- ok = mark_share_mode_disconnected(lck, fsp);
- if (!ok) {
- TALLOC_FREE(lck);
- }
- }
- if (lck != NULL) {
- ok = brl_mark_disconnected(fsp);
- if (!ok) {
- TALLOC_FREE(lck);
- }
+ status = share_mode_do_locked_brl(fsp,
+ default_durable_disconnect_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_do_locked_brl [%s] failed: %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ return status;
}
- if (lck == NULL) {
- return NT_STATUS_NOT_SUPPORTED;
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_ERR("default_durable_disconnect_fn [%s] failed: %s\n",
+ fsp_str_dbg(fsp), nt_errstr(state.status));
+ return state.status;
}
- TALLOC_FREE(lck);
status = vfs_stat_fsp(fsp);
if (!NT_STATUS_IS_OK(status)) {
--
2.53.0
From 70a466aad41c97cdedb01d0a501d3a34c6fe1eb3 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Wed, 2 Apr 2025 14:52:03 +0200
Subject: [PATCH 095/122] smbd: use share_mode_do_locked_brl() in
vfs_default_durable_reconnect()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(backported from commit dc03a06ffcc79d0818ae4a36fe3f2df705144138)
[slow@samba.org: conflict due to removed delayed write time handling]
[slow@samba.org: conflict due to filename_convert_dirfsp_rel()]
---
source3/locking/brlock.c | 15 +-
source3/locking/proto.h | 3 +-
source3/smbd/durable.c | 439 ++++++++++++++++++++-------------------
3 files changed, 234 insertions(+), 223 deletions(-)
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index b4c8501b4de..117b3edf05f 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -1515,14 +1515,14 @@ bool brl_mark_disconnected(struct files_struct *fsp,
return true;
}
-bool brl_reconnect_disconnected(struct files_struct *fsp)
+bool brl_reconnect_disconnected(struct files_struct *fsp,
+ struct byte_range_lock *br_lck)
{
uint32_t tid = fsp->conn->cnum;
uint64_t smblctx;
uint64_t fnum = fsp->fnum;
unsigned int i;
struct server_id self = messaging_server_id(fsp->conn->sconn->msg_ctx);
- struct byte_range_lock *br_lck = NULL;
if (fsp->op == NULL) {
return false;
@@ -1540,13 +1540,7 @@ bool brl_reconnect_disconnected(struct files_struct *fsp)
* them instead.
*/
- br_lck = brl_get_locks(talloc_tos(), fsp);
- if (br_lck == NULL) {
- return false;
- }
-
if (br_lck->num_locks == 0) {
- TALLOC_FREE(br_lck);
return true;
}
@@ -1559,22 +1553,18 @@ bool brl_reconnect_disconnected(struct files_struct *fsp)
*/
if (lock->context.smblctx != smblctx) {
- TALLOC_FREE(br_lck);
return false;
}
if (lock->context.tid != TID_FIELD_INVALID) {
- TALLOC_FREE(br_lck);
return false;
}
if (!server_id_is_disconnected(&lock->context.pid)) {
- TALLOC_FREE(br_lck);
return false;
}
if (lock->fnum != FNUM_FIELD_INVALID) {
- TALLOC_FREE(br_lck);
return false;
}
@@ -1585,7 +1575,6 @@ bool brl_reconnect_disconnected(struct files_struct *fsp)
fsp->current_lock_count = br_lck->num_locks;
br_lck->modified = true;
- TALLOC_FREE(br_lck);
return true;
}
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index d3b4e02bc26..37d382833fc 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -83,7 +83,8 @@ struct brl_connectstate {
bool brl_mark_disconnected(struct files_struct *fsp,
struct byte_range_lock *br_lck);
-bool brl_reconnect_disconnected(struct files_struct *fsp);
+bool brl_reconnect_disconnected(struct files_struct *fsp,
+ struct byte_range_lock *br_lck);
void brl_close_fnum(struct byte_range_lock *br_lck);
int brl_forall(void (*fn)(struct file_id id, struct server_id pid,
enum brl_type lock_type,
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
index 1506d37208a..34218d1c596 100644
--- a/source3/smbd/durable.c
+++ b/source3/smbd/durable.c
@@ -563,121 +563,42 @@ static bool durable_reconnect_fn(
return false; /* Look at potential other entries */
}
-NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
- struct smb_request *smb1req,
- struct smbXsrv_open *op,
- const DATA_BLOB old_cookie,
- TALLOC_CTX *mem_ctx,
- files_struct **result,
- DATA_BLOB *new_cookie)
+struct vfs_default_durable_reconnect_state {
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx;
+ struct smb_request *smb1req;
+ struct smbXsrv_open *op;
+ struct vfs_default_durable_cookie cookie;
+ struct files_struct *fsp;
+ DATA_BLOB new_cookie_blob;
+};
+
+static void vfs_default_durable_reconnect_fn(struct share_mode_lock *lck,
+ struct byte_range_lock *br_lck,
+ void *private_data)
{
+ struct vfs_default_durable_reconnect_state *state = private_data;
const struct loadparm_substitution *lp_sub =
loadparm_s3_global_substitution();
- struct share_mode_lock *lck;
+ struct files_struct *fsp = state->fsp;
struct share_mode_entry e = { .pid = { .pid = 0, }};
- struct durable_reconnect_state rstate = { .op = op, .e = &e, };
- struct files_struct *fsp = NULL;
- NTSTATUS status;
- bool ok;
- int ret;
+ struct durable_reconnect_state rstate = { .op = state->op, .e = &e, };
struct vfs_open_how how = { .flags = 0, };
struct file_id file_id;
- struct smb_filename *smb_fname = NULL;
- enum ndr_err_code ndr_err;
- struct vfs_default_durable_cookie cookie;
- DATA_BLOB new_cookie_blob = data_blob_null;
bool have_share_mode_entry = false;
-
- *result = NULL;
- *new_cookie = data_blob_null;
-
- if (!lp_durable_handles(SNUM(conn))) {
- return NT_STATUS_NOT_SUPPORTED;
- }
-
- /*
- * the checks for kernel oplocks
- * and similar things are done
- * in the vfs_default_durable_cookie()
- * call below.
- */
-
- ndr_err = ndr_pull_struct_blob_all(
- &old_cookie,
- talloc_tos(),
- &cookie,
- (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
- if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
- status = ndr_map_error2ntstatus(ndr_err);
- return status;
- }
-
- if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
- return NT_STATUS_INVALID_PARAMETER;
- }
-
- if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
- return NT_STATUS_INVALID_PARAMETER;
- }
-
- if (!cookie.allow_reconnect) {
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
- }
-
- if (strcmp(cookie.servicepath, conn->connectpath) != 0) {
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
- }
-
- /* Create an smb_filename with stream_name == NULL. */
- smb_fname = synthetic_smb_fname(talloc_tos(),
- cookie.base_name,
- NULL,
- NULL,
- 0,
- 0);
- if (smb_fname == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
-
- ret = SMB_VFS_LSTAT(conn, smb_fname);
- if (ret == -1) {
- status = map_nt_error_from_unix_common(errno);
- DEBUG(1, ("Unable to lstat stream: %s => %s\n",
- smb_fname_str_dbg(smb_fname),
- nt_errstr(status)));
- return status;
- }
-
- if (!S_ISREG(smb_fname->st.st_ex_mode)) {
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
- }
-
- file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
- if (!file_id_equal(&cookie.id, &file_id)) {
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
- }
-
- /*
- * 1. check entry in locking.tdb
- */
-
- lck = get_existing_share_mode_lock(mem_ctx, file_id);
- if (lck == NULL) {
- DEBUG(5, ("vfs_default_durable_reconnect: share-mode lock "
- "not obtained from db\n"));
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
- }
+ int ret;
+ bool ok;
ok = share_mode_forall_entries(lck, durable_reconnect_fn, &rstate);
if (!ok) {
DBG_WARNING("share_mode_forall_entries failed\n");
- status = NT_STATUS_INTERNAL_DB_ERROR;
+ state->status = NT_STATUS_INTERNAL_DB_ERROR;
goto fail;
}
if (e.pid.pid == 0) {
DBG_WARNING("Did not find a unique valid share mode entry\n");
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
@@ -685,69 +606,36 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
"reconnect for handle that was not marked "
"disconnected (e.g. smbd or cluster node died)\n"));
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
- if (e.share_file_id != op->global->open_persistent_id) {
+ if (e.share_file_id != state->op->global->open_persistent_id) {
DBG_INFO("denying durable "
"share_file_id changed %"PRIu64" != %"PRIu64" "
"(e.g. another client had opened the file)\n",
e.share_file_id,
- op->global->open_persistent_id);
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ state->op->global->open_persistent_id);
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
if ((e.access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) &&
- !CAN_WRITE(conn))
+ !CAN_WRITE(fsp->conn))
{
DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
"share[%s] is not writeable anymore\n",
- lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
- goto fail;
- }
-
- /*
- * 2. proceed with opening file
- */
-
- status = fsp_new(conn, conn, &fsp);
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(0, ("vfs_default_durable_reconnect: failed to create "
- "new fsp: %s\n", nt_errstr(status)));
+ lp_servicename(talloc_tos(), lp_sub, SNUM(fsp->conn))));
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
fh_set_private_options(fsp->fh, e.private_options);
- fsp->file_id = file_id;
- fsp->file_pid = smb1req->smbpid;
- fsp->vuid = smb1req->vuid;
fsp->open_time = e.time;
fsp->access_mask = e.access_mask;
fsp->fsp_flags.can_read = ((fsp->access_mask & FILE_READ_DATA) != 0);
fsp->fsp_flags.can_write = ((fsp->access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) != 0);
- fsp->fnum = op->local_id;
- fsp_set_gen_id(fsp);
- /*
- * TODO:
- * Do we need to store the modified flag in the DB?
- */
- fsp->fsp_flags.modified = false;
- /*
- * no durables for directories
- */
- fsp->fsp_flags.is_directory = false;
- /*
- * For normal files, can_lock == !is_directory
- */
- fsp->fsp_flags.can_lock = true;
- /*
- * We do not support aio write behind for smb2
- */
- fsp->fsp_flags.aio_write_behind = false;
fsp->oplock_type = e.op_type;
if (fsp->oplock_type == LEASE_OPLOCK) {
@@ -760,21 +648,21 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
*/
if (!GUID_equal(fsp_client_guid(fsp),
&e.client_guid)) {
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
- status = leases_db_get(
+ state->status = leases_db_get(
&e.client_guid,
&e.lease_key,
- &file_id,
+ &fsp->file_id,
&current_state, /* current_state */
NULL, /* breaking */
NULL, /* breaking_to_requested */
NULL, /* breaking_to_required */
&lease_version, /* lease_version */
&epoch); /* epoch */
- if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_IS_OK(state->status)) {
goto fail;
}
@@ -785,53 +673,46 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
lease_version,
epoch);
if (fsp->lease == NULL) {
- status = NT_STATUS_NO_MEMORY;
+ state->status = NT_STATUS_NO_MEMORY;
goto fail;
}
}
- fsp->initial_allocation_size = cookie.initial_allocation_size;
- fh_set_position_information(fsp->fh, cookie.position_information);
+ fsp->initial_allocation_size = state->cookie.initial_allocation_size;
+ fh_set_position_information(fsp->fh, state->cookie.position_information);
fsp->fsp_flags.update_write_time_triggered =
- cookie.update_write_time_triggered;
+ state->cookie.update_write_time_triggered;
fsp->fsp_flags.update_write_time_on_close =
- cookie.update_write_time_on_close;
- fsp->fsp_flags.write_time_forced = cookie.write_time_forced;
+ state->cookie.update_write_time_on_close;
+ fsp->fsp_flags.write_time_forced = state->cookie.write_time_forced;
fsp->close_write_time = nt_time_to_full_timespec(
- cookie.close_write_time);
+ state->cookie.close_write_time);
- status = fsp_set_smb_fname(fsp, smb_fname);
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(0, ("vfs_default_durable_reconnect: "
- "fsp_set_smb_fname failed: %s\n",
- nt_errstr(status)));
- goto fail;
- }
-
- op->compat = fsp;
- fsp->op = op;
+ state->op->compat = fsp;
+ fsp->op = state->op;
ok = reset_share_mode_entry(
lck,
e.pid,
e.share_file_id,
- messaging_server_id(conn->sconn->msg_ctx),
- smb1req->mid,
+ messaging_server_id(fsp->conn->sconn->msg_ctx),
+ state->smb1req->mid,
fh_get_gen_id(fsp->fh));
if (!ok) {
DBG_DEBUG("Could not set new share_mode_entry values\n");
- status = NT_STATUS_INTERNAL_ERROR;
+ state->status = NT_STATUS_INTERNAL_ERROR;
goto fail;
}
have_share_mode_entry = true;
- ok = brl_reconnect_disconnected(fsp);
- if (!ok) {
- status = NT_STATUS_INTERNAL_ERROR;
- DEBUG(1, ("vfs_default_durable_reconnect: "
- "failed to reopen brlocks: %s\n",
- nt_errstr(status)));
- goto fail;
+ if (br_lck != NULL) {
+ ok = brl_reconnect_disconnected(fsp, br_lck);
+ if (!ok) {
+ state->status = NT_STATUS_INTERNAL_ERROR;
+ DBG_ERR("failed to reopen brlocks: %s\n",
+ nt_errstr(state->status));
+ goto fail;
+ }
}
/*
@@ -845,10 +726,9 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
how.flags = O_RDONLY;
}
- status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(1, ("vfs_default_durable_reconnect: failed to open "
- "file: %s\n", nt_errstr(status)));
+ state->status = fd_openat(fsp->conn->cwd_fsp, fsp->fsp_name, fsp, &how);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_ERR("failed to open file: %s\n", nt_errstr(state->status));
goto fail;
}
@@ -863,70 +743,66 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
if (ret == -1) {
- status = map_nt_error_from_unix_common(errno);
- DEBUG(1, ("Unable to fstat stream: %s => %s\n",
- smb_fname_str_dbg(smb_fname),
- nt_errstr(status)));
+ state->status = map_nt_error_from_unix_common(errno);
+ DBG_ERR("Unable to fstat stream: %s => %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(state->status));
goto fail;
}
if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
- file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
- if (!file_id_equal(&cookie.id, &file_id)) {
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ file_id = vfs_file_id_from_sbuf(fsp->conn, &fsp->fsp_name->st);
+ if (!file_id_equal(&state->cookie.id, &file_id)) {
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
(void)fdos_mode(fsp);
- ok = vfs_default_durable_reconnect_check_stat(&cookie.stat_info,
+ ok = vfs_default_durable_reconnect_check_stat(&state->cookie.stat_info,
&fsp->fsp_name->st,
fsp_str_dbg(fsp));
if (!ok) {
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
goto fail;
}
- status = set_file_oplock(fsp);
- if (!NT_STATUS_IS_OK(status)) {
+ state->status = set_file_oplock(fsp);
+ if (!NT_STATUS_IS_OK(state->status)) {
goto fail;
}
- status = vfs_default_durable_cookie(fsp, mem_ctx, &new_cookie_blob);
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(1, ("vfs_default_durable_reconnect: "
- "vfs_default_durable_cookie - %s\n",
- nt_errstr(status)));
+ state->status = vfs_default_durable_cookie(fsp,
+ state->mem_ctx,
+ &state->new_cookie_blob);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_ERR("vfs_default_durable_cookie - %s\n",
+ nt_errstr(state->status));
goto fail;
}
- smb1req->chain_fsp = fsp;
- smb1req->smb2req->compat_chain_fsp = fsp;
-
- DEBUG(10, ("vfs_default_durable_reconnect: opened file '%s'\n",
- fsp_str_dbg(fsp)));
+ state->smb1req->chain_fsp = fsp;
+ state->smb1req->smb2req->compat_chain_fsp = fsp;
- TALLOC_FREE(lck);
+ DBG_DEBUG("opened file '%s'\n", fsp_str_dbg(fsp));
fsp->fsp_flags.is_fsa = true;
- *result = fsp;
- *new_cookie = new_cookie_blob;
-
- return NT_STATUS_OK;
+ state->status = NT_STATUS_OK;
+ return;
fail:
- if (fsp != NULL && have_share_mode_entry) {
+ if (have_share_mode_entry) {
/*
* Something is screwed up, delete the sharemode entry.
*/
del_share_mode(lck, fsp);
}
- if (fsp != NULL && fsp_get_pathref_fd(fsp) != -1) {
+ if (fsp_get_pathref_fd(fsp) != -1) {
NTSTATUS close_status;
close_status = fd_close(fsp);
if (!NT_STATUS_IS_OK(close_status)) {
@@ -934,11 +810,156 @@ fail:
nt_errstr(close_status));
}
}
- TALLOC_FREE(lck);
- if (fsp != NULL) {
- op->compat = NULL;
- fsp->op = NULL;
- file_free(smb1req, fsp);
+ state->op->compat = NULL;
+ fsp->op = NULL;
+}
+
+NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ files_struct **result,
+ DATA_BLOB *new_cookie)
+{
+ struct vfs_default_durable_reconnect_state state;
+ struct smb_filename *smb_fname = NULL;
+ struct file_id file_id;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ int ret;
+
+ *result = NULL;
+ *new_cookie = data_blob_null;
+
+ if (!lp_durable_handles(SNUM(conn))) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ state = (struct vfs_default_durable_reconnect_state) {
+ .mem_ctx = mem_ctx,
+ .smb1req = smb1req,
+ .op = op,
+ };
+
+ /*
+ * the checks for kernel oplocks
+ * and similar things are done
+ * in the vfs_default_durable_cookie()
+ * call below.
+ */
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &old_cookie,
+ talloc_tos(),
+ &state.cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
}
- return status;
+
+ if (strcmp(state.cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (state.cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!state.cookie.allow_reconnect) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (strcmp(state.cookie.servicepath, conn->connectpath) != 0) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* Create an smb_filename with stream_name == NULL. */
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ state.cookie.base_name,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_LSTAT(conn, smb_fname);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("Unable to lstat stream: %s => %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ return status;
+ }
+ if (!S_ISREG(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ if (!file_id_equal(&state.cookie.id, &file_id)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = fsp_new(conn, conn, &state.fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to create new fsp: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ state.fsp->file_id = file_id;
+ state.fsp->file_pid = smb1req->smbpid;
+ state.fsp->vuid = smb1req->vuid;
+ state.fsp->fnum = op->local_id;
+ fsp_set_gen_id(state.fsp);
+
+ status = fsp_set_smb_fname(state.fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("fsp_set_smb_fname failed: %s\n",
+ nt_errstr(status));
+ file_free(smb1req, state.fsp);
+ return status;
+ }
+
+ /*
+ * TODO:
+ * Do we need to store the modified flag in the DB?
+ */
+ state.fsp->fsp_flags.modified = false;
+ /*
+ * no durables for directories
+ */
+ state.fsp->fsp_flags.is_directory = false;
+ /*
+ * For normal files, can_lock == !is_directory
+ */
+ state.fsp->fsp_flags.can_lock = true;
+ /*
+ * We do not support aio write behind for smb2
+ */
+ state.fsp->fsp_flags.aio_write_behind = false;
+
+ status = share_mode_do_locked_brl(state.fsp,
+ vfs_default_durable_reconnect_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_do_locked_brl [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status));
+ file_free(smb1req, state.fsp);
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_ERR("default_durable_reconnect_fn [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(state.status));
+ file_free(smb1req, state.fsp);
+ return state.status;
+ }
+
+ *result = state.fsp;
+ *new_cookie = state.new_cookie_blob;
+
+ return NT_STATUS_OK;
}
--
2.53.0
From 05df077f6fd9402f2513a9c9a9d55cfbb40971cf Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Tue, 28 Jan 2025 14:48:39 +0100
Subject: [PATCH 096/122] s3:rpc_server/srvsvc: use brl_get_locks_readonly()
instead of brl_get_locks()
No need to keep the record locked longer then needed.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit c36cc2b6720a2cfe54ce52a500dc499418e27e34)
---
source3/rpc_server/srvsvc/srv_srvsvc_nt.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
index d6e7bed5949..c07eefdfaad 100644
--- a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
+++ b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
@@ -191,17 +191,23 @@ static WERROR net_enum_files(TALLOC_CTX *ctx,
/* need to count the number of locks on a file */
for (i=0; i<(*ctr3)->count; i++) {
- struct files_struct fsp = { .file_id = f_enum_cnt.fids[i], };
+ struct files_struct *fsp = NULL;
struct byte_range_lock *brl = NULL;
- brl = brl_get_locks(ctx, &fsp);
+ fsp = talloc_zero(talloc_tos(), struct files_struct);
+ if (fsp == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ fsp->file_id = f_enum_cnt.fids[i];
+
+ brl = brl_get_locks_readonly(fsp);
if (brl == NULL) {
continue;
}
(*ctr3)->array[i].num_locks = brl_num_locks(brl);
-
TALLOC_FREE(brl);
+ TALLOC_FREE(fsp);
}
return WERR_OK;
--
2.53.0
From e40fd11548bda2fe4cf178b639f56dd7652675ee Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@samba.org>
Date: Tue, 24 Mar 2026 15:00:21 +0100
Subject: [PATCH 097/122] wafsamba: Add -D_FORTIFY_SOURCE=3 when stack
protector is enabled
The capability check in SAMBA_CONFIG_H() already tests that the compiler
accepts both -Wp,-D_FORTIFY_SOURCE and the stack protector flag
together, but only the stack protector flag was added to EXTRA_CFLAGS on
success.
The glibc normally silently downgrades to the supported level if the on
specified is not supported.
Note that -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 only sets it if not
already defined.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16040
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Anoop C S <anoopcs@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Fri Mar 27 08:33:09 UTC 2026 on atb-devel-224
---
buildtools/wafsamba/samba_autoconf.py | 7 ++++++-
script/autobuild.py | 3 ++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/buildtools/wafsamba/samba_autoconf.py b/buildtools/wafsamba/samba_autoconf.py
index b1d2f761095..d0c9bf110ff 100644
--- a/buildtools/wafsamba/samba_autoconf.py
+++ b/buildtools/wafsamba/samba_autoconf.py
@@ -733,11 +733,16 @@ def SAMBA_CONFIG_H(conf, path=None):
}
''',
execute=0,
- cflags=[ '-Werror', '-Wp,-D_FORTIFY_SOURCE=2', stack_protect_flag],
+ cflags=[
+ '-Werror',
+ '-Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3',
+ stack_protect_flag
+ ],
mandatory=False,
msg='Checking if compiler accepts %s' % (stack_protect_flag))
if flag_supported:
conf.ADD_CFLAGS('%s' % (stack_protect_flag))
+ conf.ADD_CFLAGS('-Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3')
break
flag_supported = conf.check(fragment='''
diff --git a/script/autobuild.py b/script/autobuild.py
index 85043032d73..938c7a97fdd 100755
--- a/script/autobuild.py
+++ b/script/autobuild.py
@@ -171,6 +171,7 @@ samba_libs_configure_libs = samba_libs_configure_base + " --bundled-libraries=cm
samba_libs_configure_bundled_libs = " --bundled-libraries=!talloc,!pytalloc-util,!tdb,!pytdb,!ldb,!pyldb,!pyldb-util,!tevent,!pytevent,!popt"
samba_libs_configure_samba = samba_libs_configure_base + samba_libs_configure_bundled_libs
+samba_o3_cflags = "-O3"
def format_option(name, value=None):
"""Format option as str list."""
@@ -808,7 +809,7 @@ tasks = {
"samba-o3": {
"sequence": [
("random-sleep", random_sleep(300, 900)),
- ("configure", "ADDITIONAL_CFLAGS='-O3 -Wp,-D_FORTIFY_SOURCE=2' ./configure.developer --abi-check-disable" + samba_configure_params),
+ ("configure", "ADDITIONAL_CFLAGS='" + samba_o3_cflags + "' ./configure.developer --abi-check-disable" + samba_configure_params),
("make", "make -j"),
("test", make_test(cmd='make test', TESTS="--exclude=selftest/slow-none", include_envs=["none"])),
("quicktest", make_test(cmd='make quicktest', include_envs=["ad_dc", "ad_dc_smb1", "ad_dc_smb1_done"])),
--
2.53.0
From e916e33421ea017ebd106d418dfd1a3a4f0fe896 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Date: Fri, 27 Feb 2026 11:30:40 +1300
Subject: [PATCH 098/122] CVE-2026-3012: gpo tests: fix test cleanup
These tests are going to fail soon but as currently written they do
not clean up after themselves, erroring instead of failing and causing
cascading errors in subsequent tests. For now we don't care to make
the other tests less fragile.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
---
python/samba/tests/gpo.py | 42 +++++++++++++++++++++++----------------
1 file changed, 25 insertions(+), 17 deletions(-)
diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
index a78af17dba4..067b92728d4 100644
--- a/python/samba/tests/gpo.py
+++ b/python/samba/tests/gpo.py
@@ -6807,6 +6807,7 @@ class GPOTests(tests.TestCase):
confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn
ca_cn = '%s-CA' % hostname.replace('.', '-')
certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn)
+ self.addCleanup(ldb.delete, certa_dn)
ldb.add({'dn': certa_dn,
'objectClass': 'certificationAuthority',
'authorityRevocationList': ['XXX'],
@@ -6815,6 +6816,7 @@ class GPOTests(tests.TestCase):
})
# Write the dummy pKIEnrollmentService
enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
+ self.addCleanup(ldb.delete, enroll_dn)
ldb.add({'dn': enroll_dn,
'objectClass': 'pKIEnrollmentService',
'cACertificate': dummy_certificate(),
@@ -6823,6 +6825,7 @@ class GPOTests(tests.TestCase):
})
# Write the dummy pKICertificateTemplate
template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn
+ self.addCleanup(ldb.delete, template_dn)
ldb.add({'dn': template_dn,
'objectClass': 'pKICertificateTemplate',
})
@@ -6868,11 +6871,6 @@ class GPOTests(tests.TestCase):
self.assertNotIn(b'Workstation', out,
'Workstation certificate not removed')
- # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
- ldb.delete(certa_dn)
- ldb.delete(enroll_dn)
- ldb.delete(template_dn)
-
# Unstage the Registry.pol file
unstage_file(reg_pol)
@@ -6883,6 +6881,7 @@ class GPOTests(tests.TestCase):
'MACHINE/REGISTRY.POL')
cache_dir = self.lp.get('cache directory')
store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
+ self.addCleanup(store.log.close)
machine_creds = Credentials()
machine_creds.guess(self.lp)
@@ -6915,6 +6914,7 @@ class GPOTests(tests.TestCase):
confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn
ca_cn = '%s-CA' % hostname.replace('.', '-')
certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn)
+ self.addCleanup(ldb.delete, certa_dn)
ldb.add({'dn': certa_dn,
'objectClass': 'certificationAuthority',
'authorityRevocationList': ['XXX'],
@@ -6923,6 +6923,7 @@ class GPOTests(tests.TestCase):
})
# Write the dummy pKIEnrollmentService
enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
+ self.addCleanup(ldb.delete, enroll_dn)
ldb.add({'dn': enroll_dn,
'objectClass': 'pKIEnrollmentService',
'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
@@ -6931,12 +6932,16 @@ class GPOTests(tests.TestCase):
})
# Write the dummy pKICertificateTemplate
template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn
+ self.addCleanup(ldb.delete, template_dn)
ldb.add({'dn': template_dn,
'objectClass': 'pKICertificateTemplate',
})
with TemporaryDirectory() as dname:
- ext.process_group_policy([], gpos, dname, dname)
+ try:
+ ext.process_group_policy([], gpos, dname, dname)
+ except Exception as e:
+ self.fail(f"process_group_policy() raised {e}")
ca_crt = os.path.join(dname, '%s.crt' % ca_cn)
self.assertTrue(os.path.exists(ca_crt),
'Root CA certificate was not requested')
@@ -7025,11 +7030,6 @@ class GPOTests(tests.TestCase):
self.assertNotIn(b'Workstation', out,
'Workstation certificate not removed')
- # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
- ldb.delete(certa_dn)
- ldb.delete(enroll_dn)
- ldb.delete(template_dn)
-
# Unstage the Registry.pol file
unstage_file(reg_pol)
@@ -7348,6 +7348,7 @@ class GPOTests(tests.TestCase):
'MACHINE/REGISTRY.POL')
cache_dir = self.lp.get('cache directory')
store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
+ self.addCleanup(store.log.close)
machine_creds = Credentials()
machine_creds.guess(self.lp)
@@ -7389,6 +7390,8 @@ class GPOTests(tests.TestCase):
confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn
ca_cn = '%s-CA' % hostname.replace('.', '-')
certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn)
+ self.addCleanup(ldb.delete, certa_dn)
+
ldb.add({'dn': certa_dn,
'objectClass': 'certificationAuthority',
'authorityRevocationList': ['XXX'],
@@ -7397,6 +7400,7 @@ class GPOTests(tests.TestCase):
})
# Write the dummy pKIEnrollmentService
enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
+ self.addCleanup(ldb.delete, enroll_dn)
ldb.add({'dn': enroll_dn,
'objectClass': 'pKIEnrollmentService',
'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
@@ -7405,12 +7409,21 @@ class GPOTests(tests.TestCase):
})
# Write the dummy pKICertificateTemplate
template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn
+ try:
+ ldb.delete(template_dn)
+ except _ldb.LdbError:
+ pass
+
+ self.addCleanup(ldb.delete, template_dn)
ldb.add({'dn': template_dn,
'objectClass': 'pKICertificateTemplate',
})
with TemporaryDirectory() as dname:
- ext.process_group_policy([], gpos, dname, dname)
+ try:
+ ext.process_group_policy([], gpos, dname, dname)
+ except Exception as e:
+ self.fail(f"process_group_policy() raised {e}")
ca_list = [ca_cn, 'example0-com-CA', 'example1-com-CA',
'example2-com-CA']
for ca in ca_list:
@@ -7473,11 +7486,6 @@ class GPOTests(tests.TestCase):
self.assertNotIn(b'Workstation', out,
'Workstation certificate not removed')
- # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
- ldb.delete(certa_dn)
- ldb.delete(enroll_dn)
- ldb.delete(template_dn)
-
# Unstage the Registry.pol file
unstage_file(reg_pol)
--
2.53.0
From 5edabac806ad07c2b77a725fe8fccb9155897d7e Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Date: Mon, 23 Feb 2026 11:01:57 +1300
Subject: [PATCH 099/122] CVE-2026-3012: do not fetch certificate over http
In the case where a certificate was found via HTTP, it was trusted
without verification and put in the global CA store.
There is no means to check the certificate other than by comparing it
to certificates we may have gathered via LDAP, but in that case there
is no advantage over just using the LDAP-derived certificates.
Using the LDAP certificates was already the fallback case if HTTP
failed, so we just make it the default.
The HTTP fetch depends on the NDES service, which is a variant of
Simple Certificate Enrolment Protocol (SCEP, RFC8894), but in fact
Samba implements none of that protocol other than the HTTP fetch. SCEP
is for clients that are not true domain members. Domain members can
access to certificates over LDAP. This patch is not reducing SCEP
client support because Samba never had it.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003
Reported-by: Arad Inbar, DREAM Security Research Team
Reported-by: Nir Somech, DREAM Security Research Team
Reported-by: Ben Grinberg, DREAM Security Research Team
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 54 ++++------------------
selftest/knownfail.d/gpo-auto-enrol | 2 +
2 files changed, 11 insertions(+), 45 deletions(-)
create mode 100644 selftest/knownfail.d/gpo-auto-enrol
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index 2b7f7d22c2b..f1c55a0dbf1 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -16,7 +16,6 @@
import os
import operator
-import requests
from samba.gp.gpclass import gp_pol_ext, gp_applier, GPOSTATE
from samba import Ldb
from ldb import SCOPE_SUBTREE, SCOPE_BASE
@@ -200,58 +199,24 @@ def get_supported_templates(server):
return out.strip().split()
-def getca(ca, url, trust_dir):
- """Fetch Certificate Chain from the CA."""
+def getca(ca, trust_dir):
+ """Fetch a certificate from LDAP."""
root_cert = os.path.join(trust_dir, '%s.crt' % ca['name'])
root_certs = []
-
- try:
- r = requests.get(url=url, params={'operation': 'GetCACert',
- 'message': 'CAIdentifier'})
- except requests.exceptions.ConnectionError:
- log.warn('Could not connect to Network Device Enrollment Service.')
- r = None
- if r is None or r.content == b'' or r.headers['Content-Type'] == 'text/html':
- log.warn('Unable to fetch root certificates (requires NDES).')
- if 'cACertificate' in ca:
- log.warn('Installing the server certificate only.')
- der_certificate = base64.b64decode(ca['cACertificate'])
- try:
- cert = load_der_x509_certificate(der_certificate)
- except TypeError:
- cert = load_der_x509_certificate(der_certificate,
- default_backend())
- cert_data = cert.public_bytes(Encoding.PEM)
- with open(root_cert, 'wb') as w:
- w.write(cert_data)
- root_certs.append(root_cert)
- return root_certs
-
- if r.headers['Content-Type'] == 'application/x-x509-ca-cert':
- # Older versions of load_der_x509_certificate require a backend param
+ if 'cACertificate' in ca:
+ log.warn('Installing the server certificate only.')
+ der_certificate = base64.b64decode(ca['cACertificate'])
try:
- cert = load_der_x509_certificate(r.content)
+ cert = load_der_x509_certificate(der_certificate)
except TypeError:
- cert = load_der_x509_certificate(r.content, default_backend())
+ cert = load_der_x509_certificate(der_certificate,
+ default_backend())
cert_data = cert.public_bytes(Encoding.PEM)
with open(root_cert, 'wb') as w:
w.write(cert_data)
root_certs.append(root_cert)
- elif r.headers['Content-Type'] == 'application/x-x509-ca-ra-cert':
- certs = load_der_pkcs7_certificates(r.content)
- for i in range(0, len(certs)):
- cert = certs[i].public_bytes(Encoding.PEM)
- filename, extension = root_cert.rsplit('.', 1)
- dest = '%s.%d.%s' % (filename, i, extension)
- with open(dest, 'wb') as w:
- w.write(cert)
- root_certs.append(dest)
- else:
- log.warn('getca: Wrong (or missing) MIME content type')
-
return root_certs
-
def find_global_trust_dir():
"""Return the global trust dir using known paths from various Linux distros."""
for trust_dir in global_trust_dirs:
@@ -271,11 +236,10 @@ def changed(new_data, old_data):
def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
"""Install the root certificate chain."""
data = dict({'files': [], 'templates': []}, **ca)
- url = 'http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?' % ca['hostname']
log.info("Try to get root or server certificates")
- root_certs = getca(ca, url, trust_dir)
+ root_certs = getca(ca, trust_dir)
data['files'].extend(root_certs)
global_trust_dir = find_global_trust_dir()
for src in root_certs:
diff --git a/selftest/knownfail.d/gpo-auto-enrol b/selftest/knownfail.d/gpo-auto-enrol
new file mode 100644
index 00000000000..4bf4b8e3c72
--- /dev/null
+++ b/selftest/knownfail.d/gpo-auto-enrol
@@ -0,0 +1,2 @@
+^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\)
+^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\)
--
2.53.0
From b5f1e5a313c5cd2dd4b7c8f21dff0272abc9b904 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Date: Thu, 26 Feb 2026 14:21:01 +1300
Subject: [PATCH 100/122] CVE-2026-3012: gp_auto_enrol: skip CAs not found in
LDAP
If a certificate is mentioned in a GPO but is not present as a
cACertificate attribute on a pKIEnrollmentService object, we have no way
of obtaining it, so we might as well forget it.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
---
python/samba/gp/gp_cert_auto_enroll_ext.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
index f1c55a0dbf1..c2cc73c80f0 100644
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -457,11 +457,21 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier):
# This is a basic configuration.
cas = fetch_certification_authorities(ldb)
for _ca in cas:
+ if 'cACertificate' not in _ca:
+ log.warning(f"ignoring CA '{_ca['name']}' with no "
+ "cACertificate in LDAP.")
+ continue
+
self.apply(guid, _ca, cert_enroll, _ca, ldb, trust_dir,
private_dir)
ca_names.append(_ca['name'])
# If EndPoint.URI starts with "HTTPS//":
elif ca['URL'].lower().startswith('https://'):
+ if 'cACertificate' not in ca:
+ log.warning(f"ignoring CA '{ca['name']}' "
+ f"({ca['URL']}) with no "
+ "cACertificate in LDAP.")
+ continue
self.apply(guid, ca, cert_enroll, ca, ldb, trust_dir,
private_dir, auth=ca['auth'])
ca_names.append(ca['name'])
--
2.53.0
From c9dee6ec8532681652de09f1e411cc25c6cb886f Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Date: Fri, 27 Feb 2026 14:46:04 +1300
Subject: [PATCH 101/122] CVE-2026-3012: gpo tests should use real certificates
Or at least, more real than a short arbitrary byte string, so that
the certificates can be parsed.
This shows that certificate enrolment works via LDAP in the situations
where we would have fetched them via HTTP.
This does not fix the advanced_gp_cert_auto_enroll_ext test which
wants to install certificates it has no access too. This will not be
fixed in the security release.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
---
python/samba/tests/gpo.py | 8 ++++----
selftest/knownfail.d/gpo-auto-enrol | 1 -
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
index 067b92728d4..cbf53b88235 100644
--- a/python/samba/tests/gpo.py
+++ b/python/samba/tests/gpo.py
@@ -6918,7 +6918,7 @@ class GPOTests(tests.TestCase):
ldb.add({'dn': certa_dn,
'objectClass': 'certificationAuthority',
'authorityRevocationList': ['XXX'],
- 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
+ 'cACertificate': dummy_certificate(),
'certificateRevocationList': ['XXX'],
})
# Write the dummy pKIEnrollmentService
@@ -6926,7 +6926,7 @@ class GPOTests(tests.TestCase):
self.addCleanup(ldb.delete, enroll_dn)
ldb.add({'dn': enroll_dn,
'objectClass': 'pKIEnrollmentService',
- 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
+ 'cACertificate': dummy_certificate(),
'certificateTemplates': ['Machine'],
'dNSHostName': hostname,
})
@@ -7395,7 +7395,7 @@ class GPOTests(tests.TestCase):
ldb.add({'dn': certa_dn,
'objectClass': 'certificationAuthority',
'authorityRevocationList': ['XXX'],
- 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
+ 'cACertificate': dummy_certificate(),
'certificateRevocationList': ['XXX'],
})
# Write the dummy pKIEnrollmentService
@@ -7403,7 +7403,7 @@ class GPOTests(tests.TestCase):
self.addCleanup(ldb.delete, enroll_dn)
ldb.add({'dn': enroll_dn,
'objectClass': 'pKIEnrollmentService',
- 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
+ 'cACertificate': dummy_certificate(),
'certificateTemplates': ['Machine'],
'dNSHostName': hostname,
})
diff --git a/selftest/knownfail.d/gpo-auto-enrol b/selftest/knownfail.d/gpo-auto-enrol
index 4bf4b8e3c72..4b787a5ac86 100644
--- a/selftest/knownfail.d/gpo-auto-enrol
+++ b/selftest/knownfail.d/gpo-auto-enrol
@@ -1,2 +1 @@
^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\)
-^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\)
--
2.53.0
From 272d18c47aa7c8e1c4a5d7690f8cd2c17c0d4161 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 23 Apr 2026 18:20:15 +0200
Subject: [PATCH 102/122] CVE-2026-4480/CVE-2026-4408: lib/util: inline
string_sub2() into string_sub() the only caller
This will simplify further changes.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/substitute.c | 20 ++------------------
1 file changed, 2 insertions(+), 18 deletions(-)
diff --git a/lib/util/substitute.c b/lib/util/substitute.c
index b7b5588da86..26362ca77b2 100644
--- a/lib/util/substitute.c
+++ b/lib/util/substitute.c
@@ -47,10 +47,9 @@
use of len==0 which was for no length checks to be done.
**/
-static void string_sub2(char *s,const char *pattern, const char *insert, size_t len,
- bool remove_unsafe_characters, bool replace_once,
- bool allow_trailing_dollar)
+void string_sub(char *s, const char *pattern, const char *insert, size_t len)
{
+ bool remove_unsafe_characters = true;
char *p;
size_t ls, lp, li, i;
@@ -79,13 +78,6 @@ static void string_sub2(char *s,const char *pattern, const char *insert, size_t
for (i=0;i<li;i++) {
switch (insert[i]) {
case '$':
- /* allow a trailing $
- * (as in machine accounts) */
- if (allow_trailing_dollar && (i == li - 1 )) {
- p[i] = insert[i];
- break;
- }
- FALL_THROUGH;
case '`':
case '"':
case '\'':
@@ -107,17 +99,9 @@ static void string_sub2(char *s,const char *pattern, const char *insert, size_t
}
s = p + li;
ls = ls + li - lp;
-
- if (replace_once)
- break;
}
}
-void string_sub(char *s,const char *pattern, const char *insert, size_t len)
-{
- string_sub2( s, pattern, insert, len, true, false, false );
-}
-
/**
Similar to string_sub() but allows for any character to be substituted.
Use with caution!
--
2.53.0
From 4dd5b25a69b7b6ae9576e0da1514aa6bd9b6504d Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 23 Apr 2026 18:20:15 +0200
Subject: [PATCH 103/122] CVE-2026-4480/CVE-2026-4408: lib/util: remove unused
talloc_strdup(insert) from talloc_string_sub2()
The insert string is not modified, so we do not need to copy it.
This will simplify further changes.
Review with: git show --patience
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/substitute.c | 57 +++++++++++++++++++------------------------
1 file changed, 25 insertions(+), 32 deletions(-)
diff --git a/lib/util/substitute.c b/lib/util/substitute.c
index 26362ca77b2..4a0c58ab3a7 100644
--- a/lib/util/substitute.c
+++ b/lib/util/substitute.c
@@ -157,7 +157,7 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
bool replace_once,
bool allow_trailing_dollar)
{
- char *p, *in;
+ char *p;
char *s;
char *string;
ssize_t ls,lp,li,ld, i;
@@ -175,22 +175,32 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
s = string;
- in = talloc_strdup(mem_ctx, insert);
- if (!in) {
- DEBUG(0, ("talloc_string_sub2: ENOMEM\n"));
- talloc_free(string);
- return NULL;
- }
ls = (ssize_t)strlen(s);
lp = (ssize_t)strlen(pattern);
li = (ssize_t)strlen(insert);
ld = li - lp;
- for (i=0;i<li;i++) {
- switch (in[i]) {
+ while ((p = strstr_m(s,pattern))) {
+ if (ld > 0) {
+ int offset = PTR_DIFF(s,string);
+ string = (char *)talloc_realloc_size(mem_ctx, string,
+ ls + ld + 1);
+ if (!string) {
+ DEBUG(0, ("talloc_string_sub: out of "
+ "memory!\n"));
+ return NULL;
+ }
+ p = string + offset + (p - s);
+ }
+ if (li != lp) {
+ memmove(p+li,p+lp,strlen(p+lp)+1);
+ }
+ for (i=0; i<li; i++) {
+ switch (insert[i]) {
case '$':
- /* allow a trailing $
- * (as in machine accounts) */
+ /*
+ * allow a trailing $ (as in machine accounts)
+ */
if (allow_trailing_dollar && (i == li - 1 )) {
break;
}
@@ -204,34 +214,18 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
case '\r':
case '\n':
if (remove_unsafe_characters) {
- in[i] = '_';
- break;
+ p[i] = '_';
+ continue;
}
FALL_THROUGH;
default:
/* ok */
break;
- }
- }
-
- while ((p = strstr_m(s,pattern))) {
- if (ld > 0) {
- int offset = PTR_DIFF(s,string);
- string = (char *)talloc_realloc_size(mem_ctx, string,
- ls + ld + 1);
- if (!string) {
- DEBUG(0, ("talloc_string_sub: out of "
- "memory!\n"));
- TALLOC_FREE(in);
- return NULL;
}
- p = string + offset + (p - s);
- }
- if (li != lp) {
- memmove(p+li,p+lp,strlen(p+lp)+1);
+
+ p[i] = insert[i];
}
- memcpy(p, in, li);
s = p + li;
ls += ld;
@@ -239,7 +233,6 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
break;
}
}
- TALLOC_FREE(in);
return string;
}
--
2.53.0
From aa8022cabec1ca534b88272c6f4ba2ae22e87c99 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 23 Apr 2026 18:20:15 +0200
Subject: [PATCH 104/122] CVE-2026-4480/CVE-2026-4408: lib/util: factor out a
mask_unsafe_character() helper function
This moves the logic into a single place and
makes if more flexible to be used with more
values than STRING_SUB_UNSAFE_CHARACTERS.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/substitute.c | 109 +++++++++++++++++++++---------------------
lib/util/substitute.h | 6 ++-
2 files changed, 60 insertions(+), 55 deletions(-)
diff --git a/lib/util/substitute.c b/lib/util/substitute.c
index 4a0c58ab3a7..b9fe32e993e 100644
--- a/lib/util/substitute.c
+++ b/lib/util/substitute.c
@@ -35,6 +35,33 @@
* @brief Substitute utilities.
**/
+static inline
+char mask_unsafe_character(char in,
+ bool is_last,
+ bool allow_trailing_dollar,
+ const char *unsafe_characters,
+ char safe_out)
+{
+ const char *unsafe = NULL;
+
+ if (unsafe_characters == NULL) {
+ return in;
+ }
+
+ /* allow a trailing $ (as in machine accounts) */
+ if (allow_trailing_dollar && is_last && in == '$') {
+ return in;
+ }
+
+ unsafe = strchr(unsafe_characters, in);
+ if (unsafe != NULL) {
+ return safe_out;
+ }
+
+ /* ok */
+ return in;
+}
+
/**
Substitute a string for a pattern in another string. Make sure there is
enough room!
@@ -42,14 +69,16 @@
This routine looks for pattern in s and replaces it with
insert. It may do multiple replacements or just one.
- Any of " ; ' $ or ` in the insert string are replaced with _
+ Any of STRING_SUB_UNSAFE_CHARACTERS in the insert string are replaced with _
+
if len==0 then the string cannot be extended. This is different from the old
use of len==0 which was for no length checks to be done.
**/
void string_sub(char *s, const char *pattern, const char *insert, size_t len)
{
- bool remove_unsafe_characters = true;
+ const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
+ char safe_character = '_';
char *p;
size_t ls, lp, li, i;
@@ -76,26 +105,18 @@ void string_sub(char *s, const char *pattern, const char *insert, size_t len)
memmove(p+li,p+lp,strlen(p+lp)+1);
}
for (i=0;i<li;i++) {
- switch (insert[i]) {
- case '$':
- case '`':
- case '"':
- case '\'':
- case ';':
- case '%':
- case '\r':
- case '\n':
- if ( remove_unsafe_characters ) {
- p[i] = '_';
- /* yes this break should be here
- * since we want to fall throw if
- * not replacing unsafe chars */
- break;
- }
- FALL_THROUGH;
- default:
- p[i] = insert[i];
- }
+ /*
+ * Without allow_trailing_dollar we don't
+ * need to calculate is_last...
+ */
+ const bool is_last = false;
+ const bool allow_trailing_dollar = false;
+
+ p[i] = mask_unsafe_character(insert[i],
+ is_last,
+ allow_trailing_dollar,
+ unsafe_characters,
+ safe_character);
}
s = p + li;
ls = ls + li - lp;
@@ -157,9 +178,11 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
bool replace_once,
bool allow_trailing_dollar)
{
- char *p;
- char *s;
- char *string;
+ const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
+ const char safe_character = '_';
+ char *p = NULL,
+ char *s = NULL;
+ char *string = NULL;
ssize_t ls,lp,li,ld, i;
if (!insert || !pattern || !*pattern || !src) {
@@ -195,36 +218,14 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
if (li != lp) {
memmove(p+li,p+lp,strlen(p+lp)+1);
}
- for (i=0; i<li; i++) {
- switch (insert[i]) {
- case '$':
- /*
- * allow a trailing $ (as in machine accounts)
- */
- if (allow_trailing_dollar && (i == li - 1 )) {
- break;
- }
-
- FALL_THROUGH;
- case '`':
- case '"':
- case '\'':
- case ';':
- case '%':
- case '\r':
- case '\n':
- if (remove_unsafe_characters) {
- p[i] = '_';
- continue;
- }
-
- FALL_THROUGH;
- default:
- /* ok */
- break;
- }
+ for (i=0; i < li; i++) {
+ bool is_last = (i == li - 1);
- p[i] = insert[i];
+ p[i] = mask_unsafe_character(insert[i],
+ is_last,
+ allow_trailing_dollar,
+ unsafe_characters,
+ safe_character);
}
s = p + li;
ls += ld;
diff --git a/lib/util/substitute.h b/lib/util/substitute.h
index 3134cfcdea5..e1a82859dac 100644
--- a/lib/util/substitute.h
+++ b/lib/util/substitute.h
@@ -26,6 +26,8 @@
#include <talloc.h>
+#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%\r\n"
+
/**
Substitute a string for a pattern in another string. Make sure there is
enough room!
@@ -33,7 +35,9 @@
This routine looks for pattern in s and replaces it with
insert. It may do multiple replacements.
- Any of " ; ' $ or ` in the insert string are replaced with _
+ Any of STRING_SUB_UNSAFE_CHARACTERS (see above) in the
+ insert string are replaced with _
+
if len==0 then the string cannot be extended. This is different from the old
use of len==0 which was for no length checks to be done.
**/
--
2.53.0
From 7d209a9a91c0c94b5d0069a83252df686752807d Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 30 Apr 2026 14:48:26 +0200
Subject: [PATCH 105/122] CVE-2026-4480/CVE-2026-4408: lib/util: split out
realloc_string_sub_raw()
This will allow realloc_string_sub2() to use it in order
to have the logic in one place only.
And it will also allow adjacted callers to be
more flexible.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/substitute.c | 85 ++++++++++++++++++++++++++++++-------------
lib/util/substitute.h | 18 +++++++++
2 files changed, 78 insertions(+), 25 deletions(-)
diff --git a/lib/util/substitute.c b/lib/util/substitute.c
index b9fe32e993e..465aea86605 100644
--- a/lib/util/substitute.c
+++ b/lib/util/substitute.c
@@ -171,32 +171,24 @@ _PUBLIC_ void all_string_sub(char *s,const char *pattern,const char *insert, siz
* talloc version of string_sub2.
*/
-char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
- const char *pattern,
- const char *insert,
- bool remove_unsafe_characters,
- bool replace_once,
- bool allow_trailing_dollar)
+bool realloc_string_sub_raw(char **_string,
+ const char *pattern,
+ const char *insert,
+ bool replace_once,
+ bool allow_trailing_dollar,
+ const char *unsafe_characters,
+ char safe_character)
{
- const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
- const char safe_character = '_';
- char *p = NULL,
+ char *p = NULL;
char *s = NULL;
char *string = NULL;
ssize_t ls,lp,li,ld, i;
- if (!insert || !pattern || !*pattern || !src) {
- return NULL;
- }
-
- string = talloc_strdup(mem_ctx, src);
- if (string == NULL) {
- DEBUG(0, ("talloc_string_sub2: "
- "talloc_strdup failed\n"));
- return NULL;
+ if (!insert || !pattern || !*pattern || !_string|| !*_string) {
+ return false;
}
- s = string;
+ s = string = *_string;
ls = (ssize_t)strlen(s);
lp = (ssize_t)strlen(pattern);
@@ -205,14 +197,13 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
while ((p = strstr_m(s,pattern))) {
if (ld > 0) {
- int offset = PTR_DIFF(s,string);
- string = (char *)talloc_realloc_size(mem_ctx, string,
- ls + ld + 1);
+ ptrdiff_t offset = PTR_DIFF(s,string);
+ string = talloc_realloc(NULL, string, char, ls + ld + 1);
if (!string) {
- DEBUG(0, ("talloc_string_sub: out of "
- "memory!\n"));
- return NULL;
+ DBG_ERR("out of memory(realloc)!\n");
+ return false;
}
+ *_string = string;
p = string + offset + (p - s);
}
if (li != lp) {
@@ -234,6 +225,50 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
break;
}
}
+ return true;
+}
+
+char *talloc_string_sub2(TALLOC_CTX *mem_ctx,
+ const char *src,
+ const char *pattern,
+ const char *insert,
+ bool remove_unsafe_characters,
+ bool replace_once,
+ bool allow_trailing_dollar)
+{
+ const char *unsafe_characters = NULL;
+ char safe_character = '\0';
+ char *string = NULL;
+ bool ok;
+
+ if (!insert || !pattern || !*pattern || !src) {
+ return NULL;
+ }
+
+ if (remove_unsafe_characters) {
+ unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
+ safe_character = '_';
+ }
+
+ string = talloc_strdup(mem_ctx, src);
+ if (string == NULL) {
+ DBG_ERR("out of memory, talloc_strdup(src)!\n");
+ return NULL;
+ }
+
+ ok = realloc_string_sub_raw(&string,
+ pattern,
+ insert,
+ replace_once,
+ allow_trailing_dollar,
+ unsafe_characters,
+ safe_character);
+ if (!ok) {
+ TALLOC_FREE(string);
+ DBG_ERR("out of memory, realloc_string_sub_raw()!\n");
+ return NULL;
+ }
+
return string;
}
diff --git a/lib/util/substitute.h b/lib/util/substitute.h
index e1a82859dac..041a649fd18 100644
--- a/lib/util/substitute.h
+++ b/lib/util/substitute.h
@@ -51,6 +51,24 @@ void string_sub(char *s,const char *pattern, const char *insert, size_t len);
**/
void all_string_sub(char *s,const char *pattern,const char *insert, size_t len);
+/*
+ * If unsafe_characters is NULL all characters are allowed,
+ * if unsafe_characters is not NULL all characters caught
+ * by iscntrl() are also replaced by safe_character.
+ *
+ * *_string might be reallocated!
+ *
+ * On error *_string may still be reallocated and
+ * may contain partial replacements.
+ */
+bool realloc_string_sub_raw(char **_string,
+ const char *pattern,
+ const char *insert,
+ bool replace_once,
+ bool allow_trailing_dollar,
+ const char *unsafe_characters,
+ char safe_character);
+
char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src,
const char *pattern,
const char *insert,
--
2.53.0
From 362a51adc666ae43b7e4bb24c38a59a2de0493ba Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Wed, 6 May 2026 17:23:39 +0200
Subject: [PATCH 106/122] CVE-2026-4480/CVE-2026-4408: s3:lib: fix potential
memory leak in talloc_sub_basic()
This makes the code easier to understand...
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source3/lib/substitute.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/source3/lib/substitute.c b/source3/lib/substitute.c
index 40eb15aee04..5121fcaac1c 100644
--- a/source3/lib/substitute.c
+++ b/source3/lib/substitute.c
@@ -317,6 +317,7 @@ char *talloc_sub_basic(TALLOC_CTX *mem_ctx,
}
tmp_ctx = talloc_stackframe();
+ a_string = talloc_steal(tmp_ctx, a_string);
for (s = a_string; (p = strchr_m(s, '%')); s = a_string + (p - b)) {
@@ -478,6 +479,7 @@ error:
TALLOC_FREE(a_string);
done:
+ a_string = talloc_steal(mem_ctx, a_string);
TALLOC_FREE(tmp_ctx);
return a_string;
}
--
2.53.0
From 203717ae88b5a51de3542366dc0ea375cbd7dcf9 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 23 Apr 2026 21:11:27 +0200
Subject: [PATCH 107/122] CVE-2026-4480/CVE-2026-4408: s3:lib: let
realloc_string_sub2() use realloc_string_sub_raw()
We don't need this logic more than once!
But we leave the strange calling convention of
realloc_string_sub2(), where the caller it
not allowed to use the passed pointer when
NULL is returned...
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source3/lib/substitute_generic.c | 81 ++++++++++----------------------
1 file changed, 24 insertions(+), 57 deletions(-)
diff --git a/source3/lib/substitute_generic.c b/source3/lib/substitute_generic.c
index 26c5ee761f8..e0639f04eb8 100644
--- a/source3/lib/substitute_generic.c
+++ b/source3/lib/substitute_generic.c
@@ -37,71 +37,38 @@ char *realloc_string_sub2(char *string,
bool remove_unsafe_characters,
bool allow_trailing_dollar)
{
- char *p, *in;
- char *s;
- ssize_t ls,lp,li,ld, i;
+ const char *unsafe_characters = NULL;
+ char safe_character = '\0';
+ bool ok;
if (!insert || !pattern || !*pattern || !string || !*string)
return NULL;
- s = string;
+ if (remove_unsafe_characters) {
+ unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
+ safe_character = '_';
+ }
- in = talloc_strdup(talloc_tos(), insert);
- if (!in) {
- DEBUG(0, ("realloc_string_sub: out of memory!\n"));
+ ok = realloc_string_sub_raw(&string,
+ pattern,
+ insert,
+ false, /* replace_once */
+ allow_trailing_dollar,
+ unsafe_characters,
+ safe_character);
+ if (!ok) {
+ DBG_ERR("out of memory, realloc_string_sub_raw()!\n");
+ /*
+ * The calling convention of realloc_string_sub2()
+ * is very strange regarding stale string pointers.
+ *
+ * It is assumed the given string was allocated
+ * on talloc_tos(), so we just don't touch
+ * it at all here...
+ */
return NULL;
}
- ls = (ssize_t)strlen(s);
- lp = (ssize_t)strlen(pattern);
- li = (ssize_t)strlen(insert);
- ld = li - lp;
- for (i=0;i<li;i++) {
- switch (in[i]) {
- case '$':
- /* allow a trailing $
- * (as in machine accounts) */
- if (allow_trailing_dollar && (i == li - 1 )) {
- break;
- }
- FALL_THROUGH;
- case '`':
- case '"':
- case '\'':
- case ';':
- case '%':
- case '\r':
- case '\n':
- if ( remove_unsafe_characters ) {
- in[i] = '_';
- break;
- }
- FALL_THROUGH;
- default:
- /* ok */
- break;
- }
- }
- while ((p = strstr_m(s,pattern))) {
- if (ld > 0) {
- int offset = PTR_DIFF(s,string);
- string = talloc_realloc(NULL, string, char, ls + ld + 1);
- if (!string) {
- DEBUG(0, ("realloc_string_sub: "
- "out of memory!\n"));
- talloc_free(in);
- return NULL;
- }
- p = string + offset + (p - s);
- }
- if (li != lp) {
- memmove(p+li,p+lp,strlen(p+lp)+1);
- }
- memcpy(p, in, li);
- s = p + li;
- ls += ld;
- }
- talloc_free(in);
return string;
}
--
2.53.0
From 92fa7a66d53334c1d27c46e3425a5a99d49395d5 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 23 Apr 2026 18:21:08 +0200
Subject: [PATCH 108/122] CVE-2026-4480/CVE-2026-4408: lib/util: let
mask_unsafe_character() check all control characters
There's no reason to mask only \r and \n.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/substitute.c | 8 +++++++-
lib/util/substitute.h | 6 +++---
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/lib/util/substitute.c b/lib/util/substitute.c
index 465aea86605..30989927da7 100644
--- a/lib/util/substitute.c
+++ b/lib/util/substitute.c
@@ -22,6 +22,7 @@
*/
#include "replace.h"
+#include "system/locale.h"
#include "debug.h"
#ifndef SAMBA_UTIL_CORE_ONLY
#include "charset/charset.h"
@@ -53,6 +54,10 @@ char mask_unsafe_character(char in,
return in;
}
+ if (iscntrl(in)) {
+ return safe_out;
+ }
+
unsafe = strchr(unsafe_characters, in);
if (unsafe != NULL) {
return safe_out;
@@ -69,7 +74,8 @@ char mask_unsafe_character(char in,
This routine looks for pattern in s and replaces it with
insert. It may do multiple replacements or just one.
- Any of STRING_SUB_UNSAFE_CHARACTERS in the insert string are replaced with _
+ Any of STRING_SUB_UNSAFE_CHARACTERS and any character
+ caught by calling iscntrl() in the insert string are replaced with _
if len==0 then the string cannot be extended. This is different from the old
use of len==0 which was for no length checks to be done.
diff --git a/lib/util/substitute.h b/lib/util/substitute.h
index 041a649fd18..b183d864671 100644
--- a/lib/util/substitute.h
+++ b/lib/util/substitute.h
@@ -26,7 +26,7 @@
#include <talloc.h>
-#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%\r\n"
+#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%"
/**
Substitute a string for a pattern in another string. Make sure there is
@@ -35,8 +35,8 @@
This routine looks for pattern in s and replaces it with
insert. It may do multiple replacements.
- Any of STRING_SUB_UNSAFE_CHARACTERS (see above) in the
- insert string are replaced with _
+ Any of STRING_SUB_UNSAFE_CHARACTERS (see above) and any character
+ caught by calling iscntrl() in the insert string are replaced with _
if len==0 then the string cannot be extended. This is different from the old
use of len==0 which was for no length checks to be done.
--
2.53.0
From 31f45a68954ee6597b520faa70dbd8c7f4169c67 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 23 Apr 2026 18:21:08 +0200
Subject: [PATCH 109/122] CVE-2026-4480/CVE-2026-4408: lib/util: add more
unsafe characters to STRING_SUB_UNSAFE_CHARACTERS
|&<> are unsafe characters for shell processing.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/substitute.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/util/substitute.h b/lib/util/substitute.h
index b183d864671..41f56c73ba2 100644
--- a/lib/util/substitute.h
+++ b/lib/util/substitute.h
@@ -26,7 +26,7 @@
#include <talloc.h>
-#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%"
+#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%|&<>"
/**
Substitute a string for a pattern in another string. Make sure there is
--
2.53.0
From 076a15e3f20c8bca24bc92960770737b5b5d55cd Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 8 May 2026 22:33:32 +0200
Subject: [PATCH 110/122] CVE-2026-4480/CVE-2026-4408: lib/util: let
log_escape() make use of iscntrl()
using iscntrl() also handles 0x7F (DEL).
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/util_str_escape.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/util/util_str_escape.c b/lib/util/util_str_escape.c
index ea0fcc2f905..908f70fa47a 100644
--- a/lib/util/util_str_escape.c
+++ b/lib/util/util_str_escape.c
@@ -18,6 +18,7 @@
*/
#include "replace.h"
+#include "system/locale.h"
#include "lib/util/debug.h"
#include "lib/util/util_str_escape.h"
@@ -28,7 +29,7 @@
*/
static size_t encoded_length(char c)
{
- if (c != '\\' && c > 0x1F) {
+ if (c != '\\' && !iscntrl(c)) {
return 1;
} else {
switch (c) {
@@ -79,7 +80,7 @@ char *log_escape(TALLOC_CTX *frame, const char *in)
c = in;
e = encoded;
while (*c) {
- if (*c != '\\' && *c > 0x1F) {
+ if (*c != '\\' && !iscntrl((unsigned char)(*c))) {
*e++ = *c++;
} else {
switch (*c) {
--
2.53.0
From 4384eeb867ddb35d217b7f6232b89e7c66966fb8 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 7 May 2026 18:10:50 +0200
Subject: [PATCH 111/122] CVE-2026-4480/CVE-2026-4408: lib/util: add
talloc_string_sub_{mixed_quoting,unsafe}() helpers
This is the basic helper function for the security problems.
talloc_string_sub_mixed_quoting() checks for strange quoting
in smb.conf options.
And talloc_string_sub_unsafe() tries to autodetect how the unsafe
(client controlled value) and masked and single quote it,
as a fallback for strange quoting a fixed fallback string
is used and the caller should warn the admin and give
hints how to fix the configuration.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Pair-Programmed-With: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/substitute.c | 260 ++++++++++++++++++++++++++++++++++++++++++
lib/util/substitute.h | 17 +++
2 files changed, 277 insertions(+)
diff --git a/lib/util/substitute.c b/lib/util/substitute.c
index 30989927da7..406d8424be1 100644
--- a/lib/util/substitute.c
+++ b/lib/util/substitute.c
@@ -25,6 +25,8 @@
#include "system/locale.h"
#include "debug.h"
#ifndef SAMBA_UTIL_CORE_ONLY
+#include "lib/util/fault.h"
+#include "lib/util/talloc_stack.h"
#include "charset/charset.h"
#else
#include "charset_compat.h"
@@ -297,3 +299,261 @@ char *talloc_all_string_sub(TALLOC_CTX *ctx,
return talloc_string_sub2(ctx, src, pattern, insert,
false, false, false);
}
+
+#ifndef SAMBA_UTIL_CORE_ONLY
+
+bool talloc_string_sub_mixed_quoting(const char *full_cmd, char variable_char)
+{
+ /*
+ * Try to make sure talloc_string_sub_unsafe()
+ * won't return NULL, instead talloc_stackframe_pool()
+ * would panic
+ */
+ size_t cmd_len = full_cmd != NULL ? strlen(full_cmd) : 0;
+ size_t pool_size = 512 + cmd_len;
+ TALLOC_CTX *frame = talloc_stackframe_pool(pool_size);
+ char *cmd = NULL;
+ bool modified = false;
+ bool masked = false;
+ bool mixed_fallback = false;
+
+ cmd = talloc_string_sub_unsafe(frame,
+ full_cmd,
+ variable_char,
+ "U", /* unsafe_value */
+ "'\"%", /* unsafe_characters */
+ '_', /* safe_character */
+ "F", /* fallback_value */
+ &modified,
+ &masked,
+ &mixed_fallback);
+ if (cmd == NULL) {
+ mixed_fallback = false;
+ }
+ TALLOC_FREE(frame);
+ return mixed_fallback;
+}
+
+char *talloc_string_sub_unsafe(TALLOC_CTX *mem_ctx,
+ const char *orig_cmd,
+ char variable_char,
+ const char *unsafe_value,
+ const char *unsafe_characters,
+ char safe_character,
+ const char *fallback_value,
+ bool *_modified,
+ bool *_masked,
+ bool *_mixed_fallback)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char variable[3] =
+ { '%', variable_char, '\0' };
+ const char variable_s_quoted[5] =
+ { '\'', '%', variable_char, '\'', '\0' };
+ const char variable_d_quoted[5] =
+ { '"', '%', variable_char, '"', '\0' };
+ char *cmd = NULL;
+ char *masked_value = NULL;
+ char *quoted_value = NULL;
+ bool has_s_quotes;
+ bool has_d_quotes;
+ bool has_variable;
+ bool has_variable_s_quoted;
+ bool has_variable_d_quoted;
+ bool modified = false;
+ bool masked = false;
+ bool mixed_fallback = false;
+ bool ok;
+
+ /*
+ * The unsafe_characters argument should contain
+ * single and double quotes.
+ * Otherwise We can't safely handle this.
+ */
+ SMB_ASSERT(unsafe_characters != NULL);
+ SMB_ASSERT(strchr(unsafe_characters, '\'') != NULL);
+ SMB_ASSERT(strchr(unsafe_characters, '"') != NULL);
+ SMB_ASSERT(strchr(unsafe_characters, '%') != NULL);
+
+ cmd = talloc_strdup(mem_ctx, orig_cmd);
+ if (cmd == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ cmd = talloc_steal(frame, cmd);
+
+ has_variable = strstr(orig_cmd, variable) != NULL;
+ if (!has_variable) {
+ /*
+ * Nothing to do...
+ */
+ goto done;
+ }
+ modified = true;
+
+ /*
+ * Replace all unsafe characters as well as control
+ * characters.
+ *
+ * Note that we start with masked_value = "%u"
+ * and then replace "%u" with unsafe_value,
+ * as a result we have a masked version of
+ * unsafe_value.
+ *
+ * And don't allow option injected like
+ *
+ * '-h value'
+ * '--help value'
+ *
+ */
+ masked_value = talloc_strdup(frame, variable);
+ if (masked_value == NULL) {
+ goto nomem;
+ }
+ ok = realloc_string_sub_raw(&masked_value,
+ variable,
+ unsafe_value,
+ false, /* replace_once */
+ false, /* allow_trailing_dollar */
+ unsafe_characters,
+ safe_character);
+ if (!ok) {
+ goto nomem;
+ }
+ if (masked_value[0] == '-') {
+ masked_value[0] = safe_character;
+ }
+ masked = strcmp(masked_value, unsafe_value) != 0;
+
+retry:
+
+ has_s_quotes = strchr(cmd, '\'') != NULL;
+ has_d_quotes = strchr(cmd, '"') != NULL;
+ has_variable = strstr(cmd, variable) != NULL;
+ has_variable_s_quoted = strstr(cmd, variable_s_quoted) != NULL;
+ has_variable_d_quoted = strstr(cmd, variable_d_quoted) != NULL;
+
+ if (has_variable_s_quoted) {
+ /*
+ * In smb.conf we have something like
+ *
+ * some script = /usr/bin/script '%u'
+ *
+ * It is safe to replace '%u' (or '%J' etc, depending
+ * on variable_char) with '<masked_value>' if
+ * masked_value does not contain single quotes. We
+ * have checked that.
+ */
+
+ if (quoted_value == NULL) {
+ quoted_value = talloc_asprintf(frame, "'%s'",
+ masked_value);
+ if (quoted_value == NULL) {
+ goto nomem;
+ }
+ }
+
+ ok = realloc_string_sub_raw(&cmd,
+ variable_s_quoted,
+ quoted_value,
+ false, /* replace_once */
+ false, /* allow_trailing_dollar */
+ NULL, /* unsafe_characters */
+ '\0'); /* safe_character */
+ if (!ok) {
+ goto nomem;
+ }
+
+ goto retry;
+ }
+
+ if (has_variable_d_quoted && !has_s_quotes) {
+ /*
+ * replace the "%u"
+ *
+ * some script = /usr/bin/script "%u"
+ *
+ * with '%u' and try the '%u' -> 'variable' substitution
+ * again.
+ */
+
+ ok = realloc_string_sub_raw(&cmd,
+ variable_d_quoted,
+ variable_s_quoted,
+ false, /* replace_once */
+ false, /* allow_trailing_dollar */
+ NULL, /* unsafe_characters */
+ '\0'); /* safe_character */
+ if (!ok) {
+ goto nomem;
+ }
+
+ goto retry;
+ }
+
+ if (has_variable && !has_s_quotes && !has_d_quotes) {
+ /*
+ * In this case:
+ *
+ * some script = /usr/bin/script %u
+ *
+ * we can safely substitute %u -> '%u' and try the
+ * single quote test again.
+ */
+
+ ok = realloc_string_sub_raw(&cmd,
+ variable,
+ variable_s_quoted,
+ false, /* replace_once */
+ false, /* allow_trailing_dollar */
+ NULL, /* unsafe_characters */
+ '\0'); /* safe_character */
+ if (!ok) {
+ goto nomem;
+ }
+
+ goto retry;
+ }
+
+ if (has_variable) {
+ /*
+ * There are single or double quotes, but not tightly
+ * bound around a %u.
+ *
+ * Or there's a mix of single and double quotes.
+ *
+ * We just use a generic fallback value.
+ * and let the caller warn about this
+ * and give the admin a hind to fix the smb.conf
+ * option.
+ */
+ mixed_fallback = true;
+
+ ok = realloc_string_sub_raw(&cmd,
+ variable,
+ fallback_value,
+ false, /* replace_once */
+ false, /* allow_trailing_dollar */
+ NULL, /* unsafe_characters */
+ '\0'); /* safe_character */
+ if (!ok) {
+ goto nomem;
+ }
+ }
+
+done:
+ *_modified = modified;
+ *_masked = masked;
+ *_mixed_fallback = mixed_fallback;
+ cmd = talloc_steal(mem_ctx, cmd);
+ TALLOC_FREE(frame);
+ return cmd;
+
+nomem:
+ *_modified = false;
+ *_masked = false;
+ *_mixed_fallback = false;
+ TALLOC_FREE(frame);
+ return NULL;
+}
+#endif /* ! SAMBA_UTIL_CORE_ONLY */
diff --git a/lib/util/substitute.h b/lib/util/substitute.h
index 41f56c73ba2..b8205055da1 100644
--- a/lib/util/substitute.h
+++ b/lib/util/substitute.h
@@ -83,4 +83,21 @@ char *talloc_all_string_sub(TALLOC_CTX *ctx,
const char *src,
const char *pattern,
const char *insert);
+
+#ifndef SAMBA_UTIL_CORE_ONLY
+bool talloc_string_sub_mixed_quoting(const char *full_cmd, char variable_char);
+
+char *talloc_string_sub_unsafe(TALLOC_CTX *mem_ctx,
+ const char *orig_cmd,
+ char variable_char,
+ const char *unsafe_value,
+ const char *unsafe_characters,
+ char safe_character,
+ const char *fallback_value,
+ bool *_modified,
+ bool *_masked,
+ bool *_mixed_fallback);
+
+#endif /* ! SAMBA_UTIL_CORE_ONLY */
+
#endif /* _SAMBA_SUBSTITUTE_H_ */
--
2.53.0
From d4588ae23b22671f3960c396644941d11f538778 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Date: Sat, 9 May 2026 22:02:47 +1200
Subject: [PATCH 112/122] CVE-2026-4480/CVE-2026-4408: lib/util: add
test_string_sub unittests
This demonstrates the logic of talloc_string_sub_{mixed_quoting,unsafe}()
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
lib/util/tests/test_string_sub.c | 1044 ++++++++++++++++++++++++++++++
lib/util/wscript_build | 6 +
selftest/tests.py | 2 +
3 files changed, 1052 insertions(+)
create mode 100644 lib/util/tests/test_string_sub.c
diff --git a/lib/util/tests/test_string_sub.c b/lib/util/tests/test_string_sub.c
new file mode 100644
index 00000000000..da97c1c936c
--- /dev/null
+++ b/lib/util/tests/test_string_sub.c
@@ -0,0 +1,1044 @@
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <sys/stat.h>
+#include "replace.h"
+#include <cmocka.h>
+#include "talloc.h"
+
+#include "../substitute.h"
+
+/* set _DEBUG_VERBOSE to print more. */
+#define _DEBUG_VERBOSE
+
+#ifdef _DEBUG_VERBOSE
+#define debug_message(...) print_message(__VA_ARGS__)
+#else
+#define debug_message(...) /* debug_message */
+#endif
+
+
+static int setup_talloc_context(void **state)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ *state = mem_ctx;
+ return 0;
+}
+
+static int teardown_talloc_context(void **state)
+{
+ TALLOC_CTX *mem_ctx = *state;
+ TALLOC_FREE(mem_ctx);
+ return 0;
+}
+
+struct cmd_expansion {
+ const char *lp_cmd;
+ const char *username;
+ const char *result_cmd;
+ bool modified;
+ bool masked;
+ bool mixed_fallback;
+};
+
+static void _test_talloc_string_sub_unsafe(void **state,
+ struct cmd_expansion expansions[],
+ size_t n_expansions,
+ const char *unsafe_characters)
+{
+ TALLOC_CTX *mem_ctx = *state;
+ size_t i;
+
+ for (i = 0; i < n_expansions; i++) {
+ struct cmd_expansion t = expansions[i];
+ char *result_cmd = NULL;
+ bool masked;
+ bool mixed_fallback;
+ bool modified;
+ bool flags_correct;
+ bool mixed;
+ int cmp;
+
+ mixed = talloc_string_sub_mixed_quoting(t.lp_cmd, 'u');
+
+ result_cmd = talloc_string_sub_unsafe(mem_ctx,
+ t.lp_cmd,
+ 'u',
+ t.username,
+ unsafe_characters,
+ '_',
+ "FallbackUsername",
+ &modified,
+ &masked,
+ &mixed_fallback);
+ assert_ptr_not_equal(result_cmd, NULL);
+ assert_ptr_not_equal(t.result_cmd, NULL);
+
+ cmp = strcmp(t.result_cmd, result_cmd);
+ flags_correct = (modified == t.modified &&
+ masked == t.masked &&
+ mixed_fallback == t.mixed_fallback);
+
+ if (cmp == 0) {
+ debug_message("[%zu] «%s» «%s» -> «%s»; AS EXPECTED\n",
+ i, t.lp_cmd,
+ t.username,
+ result_cmd);
+ } else {
+ debug_message("[%zu] «%s» «%s»; "
+ "expected [%zu] «%s» got [%zu] «%s»\033[1;31m BAD! \033[0m\n",
+ i, t.lp_cmd,
+ t.username,
+ strlen(t.result_cmd), t.result_cmd,
+ strlen(result_cmd), result_cmd);
+ }
+ assert_int_equal(cmp, 0);
+ if (!flags_correct) {
+ debug_message("[%zu] ", i);
+#define _FLAG(x) debug_message((t. x == x) ? "%s: %s √; ": \
+ "%s \033[1;31m expected %s \033[0m; ", \
+ #x, t.x ? "true": "false");
+ _FLAG(modified);
+ _FLAG(masked);
+ _FLAG(mixed_fallback);
+ debug_message("\n");
+ }
+ assert_int_equal(flags_correct, true);
+ if (mixed_fallback != mixed) {
+ debug_message("[%zu] %s mixed \033[1;31m expected %s \033[0m; ",
+ i, t.lp_cmd,
+ mixed_fallback ? "true": "false");
+ }
+ assert_int_equal(mixed_fallback, mixed);
+#undef _FLAG
+ }
+ debug_message("ALL correct\n");
+}
+
+static void test_talloc_string_sub_unsafe(void **state)
+{
+ const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
+
+ static struct cmd_expansion expansions[] = {
+ {
+ "/bin/echo \"bob'",
+ "bob",
+ "/bin/echo \"bob'",
+ false,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "bob",
+ "/bin/echo 'bob'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "bob",
+ "/bin/echo 'bob'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "bob'",
+ "/bin/echo 'bob_'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "bob'''",
+ "/bin/echo 'bob___'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "bob\'",
+ "/bin/echo 'bob_'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo '%u",
+ "bob bob bob",
+ "/bin/echo 'FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo \"%u\"",
+ " ",
+ "/bin/echo ' '",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob",
+ "/bin/echo \"--uu=FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob !0",
+ "/bin/echo \"--uu=FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo %u",
+ "!0",
+ "/bin/echo '!0'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob \\",
+ "/bin/echo \"--uu=FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "bob >> x",
+ "/bin/echo --uu='bob __ x'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo '--uu=%u\"",
+ "bob",
+ "/bin/echo '--uu=FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "bob",
+ "/bin/echo --uu='bob'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo --uu'=%u'",
+ "bob",
+ "/bin/echo --uu'=FallbackUsername'",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu'=%u'",
+ "`ls`",
+ "/bin/echo --uu'=FallbackUsername'",
+ true,
+ true,
+ true,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "u%u%u%u%u",
+ "/bin/echo --uu='u_u_u_u_u'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "$(ls)",
+ "/bin/echo --uu='_(ls)'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "`ls`",
+ "/bin/echo --uu='_ls_'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo --uu='1' %u",
+ "`ls`",
+ "/bin/echo --uu='1' FallbackUsername",
+ true,
+ true,
+ true,
+ },
+ {
+ "/bin/echo --uu=\"'%u'\"",
+ "bob",
+ "/bin/echo --uu=\"'bob'\"",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo --uu='%u' --yy='%u' '%u' %u",
+ "bob",
+ "/bin/echo --uu='bob' --yy='bob' 'bob' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu=%u%u%u'' %user 50%u",
+ "bob",
+ "/bin/echo --uu=FallbackUsernameFallbackUsernameFallbackUsername'' FallbackUsernameser 50FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo %u",
+ "!!",
+ "/bin/echo '!!'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ ">xxx",
+ "/bin/echo '_xxx'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "3",
+ "/bin/echo '3'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "3$",
+ "/bin/echo '3_'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "comp$",
+ "/bin/echo 'comp_'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "3$3",
+ "/bin/echo '3_3'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "q $3",
+ "/bin/echo 'q _3'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo '%u",
+ "q $3",
+ "/bin/echo 'FallbackUsername",
+ true,
+ true,
+ true,
+ },
+ {
+ "/bin/echo -s '%u' %u",
+ "āāā",
+ "/bin/echo -s 'āāā' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo -s '%u' %u",
+ "-āāā",
+ "/bin/echo -s '_āāā' FallbackUsername",
+ true,
+ true,
+ true,
+ },
+ {
+ "/bin/echo -s %u",
+ "āāā",
+ "/bin/echo -s 'āāā'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo -s %u",
+ "a -a",
+ "/bin/echo -s 'a -a'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo -s=%u %u",
+ "ā -a",
+ "/bin/echo -s='ā -a' 'ā -a'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo -s=\"%u %u\"",
+ "ā -a",
+ "/bin/echo -s=\"FallbackUsername FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo -m='fridge' %u",
+ "ā -ß",
+ "/bin/echo -m='fridge' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo -m='fridge' %u",
+ "-ā -a",
+ "/bin/echo -m='fridge' FallbackUsername",
+ true,
+ true,
+ true,
+ },
+ {
+ "/bin/echo %u",
+ "-n",
+ "/bin/echo '_n'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "o'clock",
+ "/bin/echo 'o_clock'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo \"bob'",
+ "bob",
+ "/bin/echo \"bob'",
+ false,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"%u\"",
+ "%u",
+ "/bin/echo '_u'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo \"$(ls)\"",
+ "%u",
+ "/bin/echo \"$(ls)\"",
+ false,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "\\",
+ "/bin/echo '\\'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "\\",
+ "/bin/echo '\\'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"%u\"",
+ "\\",
+ "/bin/echo '\\'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"%u\" %u",
+ "\\",
+ "/bin/echo '\\' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo '%u' \"%u\" %u",
+ "\\",
+ "/bin/echo '\\' \"FallbackUsername\" FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo '%u' \"%u\"",
+ "bob",
+ "/bin/echo 'bob' \"FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ };
+
+ _test_talloc_string_sub_unsafe(state,
+ expansions,
+ ARRAY_SIZE(expansions),
+ unsafe_characters);
+}
+
+static void test_talloc_string_sub_unsafe_minimal_unsafe_chars(void **state)
+{
+ const char *unsafe_characters = "\"'%";
+
+ static struct cmd_expansion expansions[] = {
+ {
+ "/bin/echo \"bob'",
+ "bob",
+ "/bin/echo \"bob'",
+ false,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "bob",
+ "/bin/echo 'bob'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "bob",
+ "/bin/echo 'bob'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "bob'",
+ "/bin/echo 'bob_'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "bob'''",
+ "/bin/echo 'bob___'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "bob\'",
+ "/bin/echo 'bob_'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo '%u",
+ "bob bob bob",
+ "/bin/echo 'FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo \"%u\"",
+ " ",
+ "/bin/echo ' '",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob",
+ "/bin/echo \"--uu=FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob !0",
+ "/bin/echo \"--uu=FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo %u",
+ "!0",
+ "/bin/echo '!0'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob \\",
+ "/bin/echo \"--uu=FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "bob >> x",
+ "/bin/echo --uu='bob >> x'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '--uu=%u\"",
+ "bob",
+ "/bin/echo '--uu=FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "bob",
+ "/bin/echo --uu='bob'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo --uu'=%u'",
+ "bob",
+ "/bin/echo --uu'=FallbackUsername'",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu'=%u'",
+ "`ls`",
+ "/bin/echo --uu'=FallbackUsername'",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "u%u%u%u%u",
+ "/bin/echo --uu='u_u_u_u_u'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "$(ls)",
+ "/bin/echo --uu='$(ls)'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "`ls`",
+ "/bin/echo --uu='`ls`'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo --uu='1' %u",
+ "`ls`",
+ "/bin/echo --uu='1' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu=\"'%u'\"",
+ "bob",
+ "/bin/echo --uu=\"'bob'\"",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo --uu='%u' --yy='%u' '%u' %u",
+ "bob",
+ "/bin/echo --uu='bob' --yy='bob' 'bob' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo --uu=%u%u%u'' %user 50%u",
+ "bob",
+ "/bin/echo --uu=FallbackUsernameFallbackUsernameFallbackUsername'' FallbackUsernameser 50FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo %u",
+ "!!",
+ "/bin/echo '!!'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ ">xxx",
+ "/bin/echo '>xxx'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "3",
+ "/bin/echo '3'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "3$",
+ "/bin/echo '3$'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "comp$",
+ "/bin/echo 'comp$'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "3$3",
+ "/bin/echo '3$3'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "q $3",
+ "/bin/echo 'q $3'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u",
+ "q $3",
+ "/bin/echo 'FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo -s '%u' %u",
+ "āāā",
+ "/bin/echo -s 'āāā' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo -s '%u' %u",
+ "-āāā",
+ "/bin/echo -s '_āāā' FallbackUsername",
+ true,
+ true,
+ true,
+ },
+ {
+ "/bin/echo -s %u",
+ "āāā",
+ "/bin/echo -s 'āāā'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo -s %u",
+ "a -a",
+ "/bin/echo -s 'a -a'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo -s=%u %u",
+ "ā -a",
+ "/bin/echo -s='ā -a' 'ā -a'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo -s=\"%u %u\"",
+ "ā -a",
+ "/bin/echo -s=\"FallbackUsername FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo -m='fridge' %u",
+ "ā -ß",
+ "/bin/echo -m='fridge' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo -m='fridge' %u",
+ "-ā -a",
+ "/bin/echo -m='fridge' FallbackUsername",
+ true,
+ true,
+ true,
+ },
+ {
+ "/bin/echo %u",
+ "-n",
+ "/bin/echo '_n'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "o'clock",
+ "/bin/echo 'o_clock'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo \"bob'",
+ "bob",
+ "/bin/echo \"bob'",
+ false,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"%u\"",
+ "%u",
+ "/bin/echo '_u'",
+ true,
+ true,
+ false,
+ },
+ {
+ "/bin/echo \"$(ls)\"",
+ "%u",
+ "/bin/echo \"$(ls)\"",
+ false,
+ false,
+ false,
+ },
+ {
+ "/bin/echo %u",
+ "\\",
+ "/bin/echo '\\'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo '%u'",
+ "\\",
+ "/bin/echo '\\'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"%u\"",
+ "\\",
+ "/bin/echo '\\'",
+ true,
+ false,
+ false,
+ },
+ {
+ "/bin/echo \"%u\" %u",
+ "\\",
+ "/bin/echo '\\' FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo '%u' \"%u\" %u",
+ "\\",
+ "/bin/echo '\\' \"FallbackUsername\" FallbackUsername",
+ true,
+ false,
+ true,
+ },
+ {
+ "/bin/echo '%u' \"%u\"",
+ "bob",
+ "/bin/echo 'bob' \"FallbackUsername\"",
+ true,
+ false,
+ true,
+ },
+ };
+
+ _test_talloc_string_sub_unsafe(state,
+ expansions,
+ ARRAY_SIZE(expansions),
+ unsafe_characters);
+}
+
+static void test_talloc_string_sub_unsafe_all_mixes(void **state)
+{
+ const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
+ size_t i;
+
+ for (i = 0; i < 32; i++) {
+ char in[100] = { 0, };
+ char out[100] = { 0, };
+ struct cmd_expansion expansions[] = {
+ {
+ in,
+ "bob",
+ out,
+ true,
+ false,
+ false,
+ },
+ };
+ bool vsq = i & 1;
+ bool vdq = i & 2;
+ bool v = i & 4;
+ bool sq = i & 8;
+ bool dq = i & 16;
+ char *inp = in;
+ char *outp = out;
+ if (vsq) {
+ inp = stpcpy(inp, "'%u' ");
+ outp = stpcpy(outp, "'bob' ");
+ debug_message("vsq ");
+ }
+ if (vdq) {
+ inp = stpcpy(inp, "\"%u\" ");
+ outp = stpcpy(outp, (vsq || sq) ? "\"FallbackUsername\" " : "'bob' ");
+ debug_message("vdq ");
+ if (vsq || sq) {
+ expansions[0].mixed_fallback = true;
+ }
+ }
+ if (v) {
+ inp = stpcpy(inp, "%u ");
+ outp = stpcpy(outp, (vsq || vdq || sq || dq) ? "FallbackUsername " : "'bob' ");
+ debug_message("v ");
+ if (vsq || vdq || sq || dq) {
+ expansions[0].mixed_fallback = true;
+ }
+ }
+ if (sq) {
+ inp = stpcpy(inp, "' ");
+ outp = stpcpy(outp, "' ");
+ debug_message("sq ");
+ }
+ if (dq) {
+ inp = stpcpy(inp, "\" ");
+ outp = stpcpy(outp, "\" ");
+ debug_message("dq ");
+ }
+ debug_message("(i: %zu)\n", i);
+ *inp = '\0';
+ *outp = '\0';
+ expansions[0].modified = strcmp(in, out) != 0;
+
+ _test_talloc_string_sub_unsafe(state,
+ expansions,
+ ARRAY_SIZE(expansions),
+ unsafe_characters);
+ }
+}
+
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_talloc_string_sub_unsafe),
+ cmocka_unit_test(test_talloc_string_sub_unsafe_minimal_unsafe_chars),
+ cmocka_unit_test(test_talloc_string_sub_unsafe_all_mixes),
+ };
+ if (!isatty(1)) {
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ }
+ return cmocka_run_group_tests(tests,
+ setup_talloc_context,
+ teardown_talloc_context);
+}
diff --git a/lib/util/wscript_build b/lib/util/wscript_build
index b4fcfeaba07..900751adc7f 100644
--- a/lib/util/wscript_build
+++ b/lib/util/wscript_build
@@ -413,3 +413,9 @@ else:
deps='cmocka replace talloc stable_sort',
local_include=False,
for_selftest=True)
+
+ bld.SAMBA3_BINARY('test_string_sub',
+ source='tests/test_string_sub.c',
+ deps='''cmocka replace talloc samba-util
+ ''',
+ for_selftest=True)
diff --git a/selftest/tests.py b/selftest/tests.py
index 2cafe2faa4e..3869d9894ee 100644
--- a/selftest/tests.py
+++ b/selftest/tests.py
@@ -439,6 +439,8 @@ plantestsuite("samba.unittests.sys_rw", "none",
[os.path.join(bindir(), "default/lib/util/test_sys_rw")])
plantestsuite("samba.unittests.stable_sort", "none",
[os.path.join(bindir(), "default/lib/util/test_stable_sort")])
+plantestsuite("samba.unittests.test_string_sub", "none",
+ [os.path.join(bindir(), "test_string_sub")])
plantestsuite("samba.unittests.ntlm_check", "none",
[os.path.join(bindir(), "default/libcli/auth/test_ntlm_check")])
plantestsuite("samba.unittests.gnutls", "none",
--
2.53.0
From f1430e0324e53727c964baf46bb2bacaf5bd6699 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Sun, 15 Mar 2026 19:15:14 +0100
Subject: [PATCH 113/122] CVE-2026-4480: s3:printing: mask and/or single quote
jobname passed as %J to "print command"
Fix an unauthenticated remote code execution vulnerability with
printing set to anything *but* cups and iprint, for example "lprng",
so that "print command" is executed upon job submission. If the
client-controlled job name is handed to the "print command" via %J,
rpcd_spoolssd passes this to the shell without escaping critical
characters.
Using single quotes (directly) around %J, '%J' would avoid the
problem, we now try to autodetect if we can use '%J' implicitly
or we fallback to a fixed "__CVE-2026-4480_FallbackJobname__"
string instead of the client provided jobname.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source3/printing/print_generic.c | 117 ++++++++++++++++++++++++++-----
1 file changed, 99 insertions(+), 18 deletions(-)
diff --git a/source3/printing/print_generic.c b/source3/printing/print_generic.c
index 8798a4cf34a..c3a6f8b1506 100644
--- a/source3/printing/print_generic.c
+++ b/source3/printing/print_generic.c
@@ -1,23 +1,24 @@
-/*
+/*
Unix SMB/CIFS implementation.
printing command routines
Copyright (C) Andrew Tridgell 1992-2000
-
+
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
-
+
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
-
+
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "includes.h"
+#include "lib/util/util_str_escape.h"
#include "printing.h"
#include "smbd/proto.h"
#include "source3/lib/substitute.h"
@@ -124,7 +125,7 @@ static int generic_job_pause(int snum, struct printjob *pjob)
const struct loadparm_substitution *lp_sub =
loadparm_s3_global_substitution();
fstring jobstr;
-
+
/* need to pause the spooled entry */
fstr_sprintf(jobstr, "%d", pjob->sysjob);
return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
@@ -206,6 +207,52 @@ static int generic_queue_get(const char *printer_name,
return qcount;
}
+static const char *replace_print_cmd_J(TALLOC_CTX *mem_ctx,
+ const char *orig_cmd,
+ const char *unsafe_jobname,
+ const char *fallback_jobname)
+{
+ char *cmd = NULL;
+ bool modified = false;
+ bool masked = false;
+ bool mixed_fallback = false;
+
+ /*
+ * This replaces unsafe characters with '_'.
+ * We also mask forward and backslash here.
+ *
+ * Then it replaces %J with an single quoted
+ * version of the masked jobname or it falls
+ * back to fallback_jobname is the print command
+ * uses strange mixed quoting.
+ */
+
+#define JOBNAME_UNSAFE_CHARACTERS \
+ STRING_SUB_UNSAFE_CHARACTERS "/\\"
+
+ cmd = talloc_string_sub_unsafe(mem_ctx,
+ orig_cmd,
+ 'J',
+ unsafe_jobname,
+ JOBNAME_UNSAFE_CHARACTERS,
+ '_',
+ fallback_jobname,
+ &modified,
+ &masked,
+ &mixed_fallback);
+ if (cmd == NULL) {
+ return NULL;
+ }
+
+ /*
+ * The caller already checked talloc_string_sub_mixed_quoting()
+ * and warned the admin, so we don't check mixed_fallback
+ * here
+ */
+
+ return cmd;
+}
+
/****************************************************************************
Submit a file for printing - called from print_job_end()
****************************************************************************/
@@ -221,11 +268,12 @@ static int generic_job_submit(int snum, struct printjob *pjob,
char *print_directory = NULL;
char *wd = NULL;
char *p = NULL;
- char *jobname = NULL;
+ const char *print_cmd = NULL;
TALLOC_CTX *ctx = talloc_tos();
fstring job_page_count, job_size;
print_queue_struct *q;
print_status_struct status;
+ const char *jobname = "No Document Name";
/* we print from the directory path to give the best chance of
parsing the lpq output */
@@ -254,24 +302,48 @@ static int generic_job_submit(int snum, struct printjob *pjob,
return -1;
}
- jobname = talloc_strdup(ctx, pjob->jobname);
- if (!jobname) {
- ret = -1;
- goto out;
+ if (pjob->jobname[0] != '\0') {
+ jobname = pjob->jobname;
}
- jobname = talloc_string_sub(ctx, jobname, "'", "_");
- if (!jobname) {
- ret = -1;
- goto out;
+
+ print_cmd = lp_print_command(snum);
+ if (print_cmd != NULL) {
+ const char *invalid_jobname = "__CVE-2026-4480_FallbackJobname__";
+
+ if (talloc_string_sub_mixed_quoting(print_cmd, 'J')) {
+ /*
+ * The admin used a strange mixture of
+ * single and double quotes, fallback
+ * to InvalidDocumentName and warn about
+ * it, so that the admin can adjust to
+ * the use single quotes directly around %J,
+ * e.g. '%J'.
+ */
+ jobname = invalid_jobname;
+ D_WARNING("CVE-2026-4480: printer %s "
+ "strange quoting in 'print command', "
+ "falling back to jobname=%s, "
+ "use testparm to fix the configuration\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ invalid_jobname);
+ }
+
+ print_cmd = replace_print_cmd_J(ctx,
+ print_cmd,
+ jobname,
+ invalid_jobname);
+ if (!print_cmd) {
+ ret = -1;
+ goto out;
+ }
}
fstr_sprintf(job_page_count, "%d", pjob->page_count);
fstr_sprintf(job_size, "%zu", pjob->size);
/* send it to the system spooler */
ret = print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
- lp_print_command(snum), NULL,
+ print_cmd, NULL,
"%s", p,
- "%J", jobname,
"%f", p,
"%z", job_size,
"%c", job_page_count,
@@ -292,9 +364,14 @@ static int generic_job_submit(int snum, struct printjob *pjob,
int i;
for (i = 0; i < ret; i++) {
if (strcmp(q[i].fs_file, p) == 0) {
+ char *le_jobname =
+ log_escape(talloc_tos(), jobname);
+
pjob->sysjob = q[i].sysjob;
DEBUG(5, ("new job %u (%s) matches sysjob %d\n",
- pjob->jobid, jobname, pjob->sysjob));
+ pjob->jobid, le_jobname, pjob->sysjob));
+
+ TALLOC_FREE(le_jobname);
break;
}
}
@@ -302,8 +379,12 @@ static int generic_job_submit(int snum, struct printjob *pjob,
ret = 0;
}
if (pjob->sysjob == -1) {
+ char *le_jobname = log_escape(talloc_tos(), jobname);
+
DEBUG(2, ("failed to get sysjob for job %u (%s), tracking as "
- "Unix job\n", pjob->jobid, jobname));
+ "Unix job\n", pjob->jobid, le_jobname));
+
+ TALLOC_FREE(le_jobname);
}
--
2.53.0
From 97cef37691467fc2e910339ed2af49f7531691ac Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 8 May 2026 23:27:35 +0200
Subject: [PATCH 114/122] CVE-2026-4480: s3:testparm: warn about 'print
command' %J usage
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source3/utils/testparm.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/source3/utils/testparm.c b/source3/utils/testparm.c
index 96e05a57f47..16ac47f9ff4 100644
--- a/source3/utils/testparm.c
+++ b/source3/utils/testparm.c
@@ -796,6 +796,14 @@ static void do_per_share_checks(int s)
"parameter is ignored when using CUPS libraries.\n\n",
lp_servicename(talloc_tos(), lp_sub, s));
}
+ if (talloc_string_sub_mixed_quoting(lp_print_command(s), 'J')) {
+ fprintf(stderr,
+ "WARNING: Service %s defines a 'print command' "
+ "with mixed quoting and %%J.\n"
+ "CVE-2026-4480 changed the way %%J substitution works.\n"
+ "You should use single quotes (directly) around '%%J'.\n\n",
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
vfs_objects = lp_vfs_objects(s);
if (vfs_objects && str_list_check(vfs_objects, "fruit")) {
--
2.53.0
From a8fc9494b7633950c7e027332479e16a59cc646a Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 11 May 2026 14:11:34 +0200
Subject: [PATCH 115/122] CVE-2026-4480: docs-xml/smbdotconf: clarify '%J' in
'print command'
Admins should use '%J'.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
docs-xml/smbdotconf/printing/printcommand.xml | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/docs-xml/smbdotconf/printing/printcommand.xml b/docs-xml/smbdotconf/printing/printcommand.xml
index c84e45f404d..d708287932a 100644
--- a/docs-xml/smbdotconf/printing/printcommand.xml
+++ b/docs-xml/smbdotconf/printing/printcommand.xml
@@ -21,8 +21,11 @@
<para>%p - the appropriate printer
name</para>
- <para>%J - the job
- name as transmitted by the client.</para>
+ <para>%J - the job name as transmitted by the client,
+ but with dangerous characters being replaced by _.
+ You should use single quotes (directly) around %J, e.g. '%J',
+ see CVE-2026-4480 for more details.
+ </para>
<para>%c - The number of printed pages
of the spooled job (if known).</para>
--
2.53.0
From e8a0c5e718caff4b9792aa4758646dcff5cf3fe1 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Thu, 23 Apr 2026 18:56:21 +0200
Subject: [PATCH 116/122] CVE-2026-4408: lib/util: introduce
strstr_for_invalid_account_characters()
This splits out the logic from samaccountname_bad_chars_check()
in source4/dsdb/samdb/ldb_modules/samldb.c, this will be used
in other places soon.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
lib/util/samba_util.h | 9 +++++++++
lib/util/util_str.c | 38 ++++++++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/lib/util/samba_util.h b/lib/util/samba_util.h
index e672b4b00b2..954110983fe 100644
--- a/lib/util/samba_util.h
+++ b/lib/util/samba_util.h
@@ -300,6 +300,15 @@ _PUBLIC_ bool set_boolean(const char *boolean_string, bool *boolean);
*/
_PUBLIC_ bool conv_str_bool(const char * str, bool * val);
+/**
+ * Returns a pointer to the first invalid character in name.
+ *
+ * Passing a NULL pointer as name is not allowed!
+ *
+ * This returns NULL for a valid account name.
+ **/
+_PUBLIC_ const char *strstr_for_invalid_account_characters(const char *name);
+
/**
* Convert a size specification like 16K into an integral number of bytes.
**/
diff --git a/lib/util/util_str.c b/lib/util/util_str.c
index 7c1d15dbeb0..c4eda4f49f3 100644
--- a/lib/util/util_str.c
+++ b/lib/util/util_str.c
@@ -305,3 +305,41 @@ _PUBLIC_ bool set_boolean(const char *boolean_string, bool *boolean)
}
return false;
}
+
+_PUBLIC_ const char *strstr_for_invalid_account_characters(const char *name)
+{
+ /*
+ * Return a pointer to the first invalid character in the
+ * sAMAccountName, or NULL if the whole name is valid.
+ *
+ * The rules here are based on
+ *
+ * https://social.technet.microsoft.com/wiki/contents/articles/11216.active-directory-requirements-for-creating-objects.aspx
+ */
+ size_t i;
+
+ for (i = 0; name[i] != '\0'; i++) {
+ uint8_t c = name[i];
+ const char *p = NULL;
+
+ if (iscntrl(c)) {
+ return &name[i];
+ }
+
+ p = strchr("\"[]:;|=+*?<>/\\,", c);
+ if (p != NULL) {
+ return &name[i];
+ }
+ }
+
+ if (i == 0) {
+ return &name[i];
+ }
+
+ if (name[i - 1] == '.') {
+ i -= 1;
+ return &name[i];
+ }
+
+ return NULL;
+}
--
2.53.0
From d37f3cb93d96ec3c92f1d9f8ddf16093b4c18420 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 11 May 2026 20:21:36 +0200
Subject: [PATCH 117/122] CVE-2026-4408: s3:samr-server: only allow
_samr_ValidatePassword as DC
This is only supported with 'rpc start on demand helpers = no',
as it needs ncacn_ip_tcp, but we better also restrict it to DCs.
Maybe only FreeIPA needs it as NT4 didn't support ncacn_ip_tcp.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source3/rpc_server/samr/srv_samr_nt.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/source3/rpc_server/samr/srv_samr_nt.c b/source3/rpc_server/samr/srv_samr_nt.c
index 2cc168926a7..26d8e408d38 100644
--- a/source3/rpc_server/samr/srv_samr_nt.c
+++ b/source3/rpc_server/samr/srv_samr_nt.c
@@ -7499,6 +7499,14 @@ NTSTATUS _samr_ValidatePassword(struct pipes_struct *p,
return NT_STATUS_ACCESS_DENIED;
}
+ if (lp_server_role() <= ROLE_DOMAIN_MEMBER) {
+ /*
+ * We only want this on DCs
+ */
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
if (r->in.level < 1 || r->in.level > 3) {
return NT_STATUS_INVALID_INFO_CLASS;
}
--
2.53.0
From d44e016983db65d430dfa0556755fba32e498dd1 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Wed, 18 Mar 2026 12:24:47 +0100
Subject: [PATCH 118/122] CVE-2026-4408: s3:samr-server: deny, mask and/or
single quote username to 'check password script'
We pass this on to the check password script, prevent remote command
execution.
We now try to autodetect if we could implicitly use '%u' for the
replacement and fallback to a fixed fallback username.
Admins should make use of SAMBA_CPS_ACCOUNT_NAME
instead of passing '%u' to 'check password script'
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Pair-Programmed-With: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source3/rpc_server/samr/srv_samr_chgpasswd.c | 110 +++++++++++++++++--
1 file changed, 101 insertions(+), 9 deletions(-)
diff --git a/source3/rpc_server/samr/srv_samr_chgpasswd.c b/source3/rpc_server/samr/srv_samr_chgpasswd.c
index 3373ffa76f6..0a1edb88712 100644
--- a/source3/rpc_server/samr/srv_samr_chgpasswd.c
+++ b/source3/rpc_server/samr/srv_samr_chgpasswd.c
@@ -54,6 +54,7 @@
#include "passdb.h"
#include "auth.h"
#include "lib/util/sys_rw.h"
+#include "lib/util/util_str_escape.h"
#include "librpc/rpc/dcerpc_samr.h"
#include "lib/crypto/gnutls_helpers.h"
@@ -1008,27 +1009,118 @@ static bool check_passwd_history(struct samu *sampass, const char *plaintext)
/***********************************************************
************************************************************/
+static NTSTATUS check_password_complexity_internal(TALLOC_CTX *tosctx,
+ const char *orig_cmd,
+ const char *username,
+ char **cmd_out)
+{
+ const char *fallback_username = "__CVE-2026-4408_FallbackUsername__";
+ const char *inv = NULL;
+ char *cmd = NULL;
+ bool modified = false;
+ bool masked = false;
+ bool mixed_fallback = false;
+
+ *cmd_out = NULL;
+
+ if (username == NULL) {
+ return NT_STATUS_INVALID_USER_PRINCIPAL_NAME;
+ }
+
+ /*
+ * This catches invalid characters in account names
+ * which might be problematic passing to a shell script.
+ */
+ inv = strstr_for_invalid_account_characters(username);
+ if (inv != NULL) {
+ char *le_username = log_escape(tosctx, username);
+
+ DBG_WARNING("username '%s' has invalid or dangerous characters\n",
+ le_username);
+
+ TALLOC_FREE(le_username);
+
+ return NT_STATUS_INVALID_USER_PRINCIPAL_NAME;
+ }
+
+ /*
+ * This masks the remaining unsafe characters which
+ * are not already caught by strstr_for_invalid_account_characters()
+ * with '_'.
+ *
+ * Then it replaces %u with an single quoted
+ * and/or shell escaped version of the masked username.
+ */
+ cmd = talloc_string_sub_unsafe(tosctx,
+ orig_cmd,
+ 'u',
+ username,
+ STRING_SUB_UNSAFE_CHARACTERS,
+ '_',
+ fallback_username,
+ &modified,
+ &masked,
+ &mixed_fallback);
+ if (cmd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Now warn about unexpected values
+ */
+
+ if (mixed_fallback) {
+ D_WARNING("CVE-2026-4408: "
+ "strange quoting in 'check password script', "
+ "falling back to replace %%u with %s, "
+ "use testparm to fix the configuration\n",
+ fallback_username);
+ D_WARNING("CVE-2026-4408: "
+ "You should use '%%u', or SAMBA_CPS_ACCOUNT_NAME "
+ "inside of 'check password script'.\n");
+ } else if (masked) {
+ char *le_username = log_escape(tosctx, username);
+
+ D_WARNING("CVE-2026-4408: "
+ "replaced %%u with masked value instead of: %s\n",
+ le_username);
+ D_WARNING("CVE-2026-4408: "
+ "You should use SAMBA_CPS_ACCOUNT_NAME inside "
+ "'check password script' instead of %%u.\n");
+
+ TALLOC_FREE(le_username);
+ }
+
+ *cmd_out = cmd;
+ return NT_STATUS_OK;
+}
+
+
NTSTATUS check_password_complexity(const char *username,
const char *fullname,
const char *password,
enum samPwdChangeReason *samr_reject_reason)
{
+ int check_ret;
+ NTSTATUS status;
TALLOC_CTX *tosctx = talloc_tos();
const struct loadparm_substitution *lp_sub =
loadparm_s3_global_substitution();
- int check_ret;
- char *cmd;
+ const char *orig_cmd = NULL;
+ char *cmd = NULL;
- /* Use external script to check password complexity */
- if ((lp_check_password_script(tosctx, lp_sub) == NULL)
- || (*(lp_check_password_script(tosctx, lp_sub)) == '\0')){
+ orig_cmd = lp_check_password_script(tosctx, lp_sub);
+ if (orig_cmd == NULL || orig_cmd[0] == '\0') {
return NT_STATUS_OK;
}
- cmd = talloc_string_sub(tosctx, lp_check_password_script(tosctx, lp_sub), "%u",
- username);
- if (!cmd) {
- return NT_STATUS_PASSWORD_RESTRICTION;
+ /* note we don't use 'fullname' or 'password' here */
+ status = check_password_complexity_internal(tosctx,
+ orig_cmd,
+ username,
+ &cmd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
}
check_ret = setenv("SAMBA_CPS_ACCOUNT_NAME", username, 1);
--
2.53.0
From 778f3b61db7e4c2b617a97b1dfd463cdcf597148 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Date: Sat, 2 May 2026 22:12:38 +1200
Subject: [PATCH 119/122] CVE-2026-4408: s3:samr-server: make
check_password_complexity_internal() non-static, for easier testing
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source3/rpc_server/samr/srv_samr_chgpasswd.c | 8 ++++----
source3/rpc_server/samr/srv_samr_util.h | 5 +++++
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/source3/rpc_server/samr/srv_samr_chgpasswd.c b/source3/rpc_server/samr/srv_samr_chgpasswd.c
index 0a1edb88712..62536ed7531 100644
--- a/source3/rpc_server/samr/srv_samr_chgpasswd.c
+++ b/source3/rpc_server/samr/srv_samr_chgpasswd.c
@@ -1009,10 +1009,10 @@ static bool check_passwd_history(struct samu *sampass, const char *plaintext)
/***********************************************************
************************************************************/
-static NTSTATUS check_password_complexity_internal(TALLOC_CTX *tosctx,
- const char *orig_cmd,
- const char *username,
- char **cmd_out)
+NTSTATUS check_password_complexity_internal(TALLOC_CTX *tosctx,
+ const char *orig_cmd,
+ const char *username,
+ char **cmd_out)
{
const char *fallback_username = "__CVE-2026-4408_FallbackUsername__";
const char *inv = NULL;
diff --git a/source3/rpc_server/samr/srv_samr_util.h b/source3/rpc_server/samr/srv_samr_util.h
index 5e839ac77c0..a3a22012858 100644
--- a/source3/rpc_server/samr/srv_samr_util.h
+++ b/source3/rpc_server/samr/srv_samr_util.h
@@ -79,6 +79,11 @@ NTSTATUS pass_oem_change(char *user, const char *rhost,
uchar password_encrypted_with_nt_hash[516],
const uchar old_nt_hash_encrypted[16],
enum samPwdChangeReason *reject_reason);
+
+NTSTATUS check_password_complexity_internal(TALLOC_CTX *mem_ctx,
+ const char *_orig_cmd,
+ const char *username,
+ char **cmd_out);
NTSTATUS check_password_complexity(const char *username,
const char *fullname,
const char *password,
--
2.53.0
From 179cacf4d661e83a402c98c1c5b3b321a9a52786 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Date: Sat, 2 May 2026 22:14:43 +1200
Subject: [PATCH 120/122] CVE-2026-4408: s3:torture: tests for password
complexity scripts
This tries to demonstrate the new logic for %u in
'check password script'.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
selftest/tests.py | 2 +
source3/torture/test_rpc_samr.c | 358 ++++++++++++++++++++++++++++++++
source3/torture/wscript_build | 6 +
3 files changed, 366 insertions(+)
create mode 100644 source3/torture/test_rpc_samr.c
diff --git a/selftest/tests.py b/selftest/tests.py
index 3869d9894ee..3b48d262f82 100644
--- a/selftest/tests.py
+++ b/selftest/tests.py
@@ -455,6 +455,8 @@ plantestsuite("samba.unittests.test_oLschema2ldif", "none",
[os.path.join(bindir(), "default/source4/utils/oLschema2ldif/test_oLschema2ldif")])
plantestsuite("samba.unittests.auth.sam", "none",
[os.path.join(bindir(), "test_auth_sam")])
+plantestsuite("samba.unittests.test_rpc_samr", "none",
+ [os.path.join(bindir(), "test_rpc_samr")])
if have_heimdal_support and not using_system_gssapi:
plantestsuite("samba.unittests.auth.heimdal_gensec_unwrap_des", "none",
[valgrindify(os.path.join(bindir(), "test_heimdal_gensec_unwrap_des"))])
diff --git a/source3/torture/test_rpc_samr.c b/source3/torture/test_rpc_samr.c
new file mode 100644
index 00000000000..8d4f3985246
--- /dev/null
+++ b/source3/torture/test_rpc_samr.c
@@ -0,0 +1,358 @@
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <sys/stat.h>
+#include <cmocka.h>
+#include "includes.h"
+#include "talloc.h"
+#include "libcli/util/ntstatus.h"
+#include "../librpc/gen_ndr/samr.h"
+#include "rpc_server/samr/srv_samr_util.h"
+
+/* set SAMR_DEBUG_VERBOSE to true to print more. */
+#define SAMR_DEBUG_VERBOSE true
+
+#if SAMR_DEBUG_VERBOSE
+#define debug_message(...) print_message(__VA_ARGS__)
+#else
+#define debug_message(...) /* debug_message */
+#endif
+
+static int setup_talloc_context(void **state)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ *state = mem_ctx;
+ return 0;
+}
+
+static int teardown_talloc_context(void **state)
+{
+ TALLOC_CTX *mem_ctx = *state;
+ TALLOC_FREE(mem_ctx);
+ return 0;
+}
+
+struct cmd_expansion {
+ const char *lp_cmd;
+ const char *username;
+ const char *result_cmd;
+ NTSTATUS result_code;
+};
+
+static struct cmd_expansion expansions[] = {
+ {
+ "/bin/echo '%u'",
+ "bob",
+ "/bin/echo 'bob'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "bob",
+ "/bin/echo 'bob'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "bob'",
+ "/bin/echo 'bob_'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "bob\'",
+ "/bin/echo 'bob_'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "bob'''",
+ "/bin/echo 'bob___'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "bob*",
+ NULL,
+ NT_STATUS_INVALID_USER_PRINCIPAL_NAME
+ },
+ {
+ "/bin/echo %u",
+ "bob\"",
+ NULL,
+ NT_STATUS_INVALID_USER_PRINCIPAL_NAME
+ },
+ {
+ "/bin/echo '%u",
+ "bob bob bob",
+ "/bin/echo '__CVE-2026-4408_FallbackUsername__",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo \"%u\"",
+ " ",
+ "/bin/echo ' '",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob",
+ "/bin/echo \"--uu=__CVE-2026-4408_FallbackUsername__\"",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob !0",
+ "/bin/echo \"--uu=__CVE-2026-4408_FallbackUsername__\"",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "!0",
+ "/bin/echo '!0'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo \"--uu=%u\"",
+ "bob \\",
+ NULL,
+ NT_STATUS_INVALID_USER_PRINCIPAL_NAME
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "bob >> x",
+ NULL,
+ NT_STATUS_INVALID_USER_PRINCIPAL_NAME
+ },
+ {
+ "/bin/echo '--uu=%u\"",
+ "bob",
+ "/bin/echo '--uu=__CVE-2026-4408_FallbackUsername__\"",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "bob",
+ "/bin/echo --uu='bob'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo --uu'=%u'",
+ "bob",
+ "/bin/echo --uu'=__CVE-2026-4408_FallbackUsername__'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo --uu'=%u'",
+ "`ls`",
+ "/bin/echo --uu'=__CVE-2026-4408_FallbackUsername__'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo --uu'=%u'",
+ "$(ls)",
+ "/bin/echo --uu'=__CVE-2026-4408_FallbackUsername__'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo --uu='%u'",
+ "$(ls)",
+ "/bin/echo --uu='_(ls)'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo --uu=\"'%u'\"",
+ "bob",
+ "/bin/echo --uu=\"'bob'\"",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo --uu='%u' --yy='%u' '%u' %u",
+ "bob",
+ "/bin/echo --uu='bob' --yy='bob' 'bob' __CVE-2026-4408_FallbackUsername__",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo --uu=%u%u'' %user 50%u",
+ "bob",
+ "/bin/echo --uu=__CVE-2026-4408_FallbackUsername____CVE-2026-4408_FallbackUsername__'' __CVE-2026-4408_FallbackUsername__ser 50__CVE-2026-4408_FallbackUsername__",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "!!",
+ "/bin/echo '!!'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ ">xxx",
+ NULL,
+ NT_STATUS_INVALID_USER_PRINCIPAL_NAME
+ },
+ {
+ "/bin/echo %u",
+ "\\",
+ NULL,
+ NT_STATUS_INVALID_USER_PRINCIPAL_NAME
+ },
+ {
+ "/bin/echo %u",
+ "3",
+ "/bin/echo '3'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo '%u'",
+ "3$",
+ "/bin/echo '3_'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo '%u'",
+ "comp$",
+ "/bin/echo 'comp_'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo '%u'",
+ "3$3",
+ "/bin/echo '3_3'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo '%u'",
+ "q $3",
+ "/bin/echo 'q _3'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo -s '%u' %u",
+ "āāā",
+ "/bin/echo -s 'āāā' __CVE-2026-4408_FallbackUsername__",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo -s '%u' %u",
+ "-āāā",
+ "/bin/echo -s '_āāā' __CVE-2026-4408_FallbackUsername__",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo -s %u",
+ "āāā",
+ "/bin/echo -s 'āāā'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo -s %u",
+ "a -a",
+ "/bin/echo -s 'a -a'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo -s=%u %u",
+ "ā -a",
+ "/bin/echo -s='ā -a' 'ā -a'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo -s=\"%u %u\"",
+ "ā -a",
+ "/bin/echo -s=\"__CVE-2026-4408_FallbackUsername__ __CVE-2026-4408_FallbackUsername__\"",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo -m='fridge' %u",
+ "ā -x -ß",
+ "/bin/echo -m='fridge' __CVE-2026-4408_FallbackUsername__",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo -m='fridge' %u",
+ "-ā -a",
+ "/bin/echo -m='fridge' __CVE-2026-4408_FallbackUsername__",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "-n",
+ "/bin/echo '_n'",
+ NT_STATUS_OK
+ },
+ {
+ "/bin/echo %u",
+ "o'clock",
+ "/bin/echo 'o_clock'",
+ NT_STATUS_OK
+ },
+};
+
+static void test_expansions(void **state)
+{
+ TALLOC_CTX *mem_ctx = *state;
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(expansions); i++) {
+ struct cmd_expansion t = expansions[i];
+ char *result_cmd = NULL;
+ NTSTATUS status;
+
+ status = check_password_complexity_internal(mem_ctx,
+ t.lp_cmd,
+ t.username,
+ &result_cmd);
+ if (NT_STATUS_IS_OK(t.result_code) && NT_STATUS_IS_OK(status)) {
+ int cmp;
+
+ cmp = strcmp(t.result_cmd, result_cmd);
+ if (cmp == 0) {
+ debug_message("[%zu] «%s» «%s» -> «%s», nstatus %s; AS EXPECTED\n",
+ i, t.lp_cmd,
+ t.username,
+ result_cmd,
+ nt_errstr(status));
+ } else {
+ debug_message("[%zu] «%s» «%s», nstatus %s; "
+ "expected «%s» got «%s»\033[1;31m BAD! \033[0m\n",
+ i, t.lp_cmd,
+ t.username,
+ nt_errstr(status),
+ t.result_cmd,
+ result_cmd);
+ }
+ assert_int_equal(cmp, 0);
+ } else if (NT_STATUS_EQUAL(status, t.result_code)) {
+ debug_message("[%zu] «%s» «%s», nstatus %s FAILED AS EXPECTED\n",
+ i, t.lp_cmd,
+ t.username,
+ nt_errstr(status));
+ } else {
+ debug_message("[%zu] «%s» «%s» -> «%s», nstatus %s; "
+ "EXPECTED result «%s» ntstatus %s; \033[1;31m BAD! \033[0m\n",
+ i, t.lp_cmd,
+ t.username,
+ result_cmd,
+ nt_errstr(status),
+ t.result_cmd,
+ nt_errstr(t.result_code));
+ assert_int_equal(true, false);
+ }
+ }
+ debug_message("ALL correct\n");
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_expansions),
+ };
+ if (!isatty(1)) {
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ }
+ return cmocka_run_group_tests(tests,
+ setup_talloc_context,
+ teardown_talloc_context);
+}
diff --git a/source3/torture/wscript_build b/source3/torture/wscript_build
index 1d2520099e3..d04008b3df1 100644
--- a/source3/torture/wscript_build
+++ b/source3/torture/wscript_build
@@ -133,3 +133,9 @@ bld.SAMBA3_BINARY('vfstest',
SMBREADLINE
''',
for_selftest=True)
+
+bld.SAMBA3_BINARY('test_rpc_samr',
+ source='test_rpc_samr.c',
+ deps='''RPC_SERVICE cmocka
+ ''',
+ for_selftest=True)
--
2.53.0
From f13ff6d7d0fafa1ca02e1d024d00f4b35cb5743e Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 8 May 2026 23:27:35 +0200
Subject: [PATCH 121/122] CVE-2026-4408: s3:testparm: warn about 'check
password script' %u usage
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
source3/utils/testparm.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/source3/utils/testparm.c b/source3/utils/testparm.c
index 16ac47f9ff4..d49e8bcfc64 100644
--- a/source3/utils/testparm.c
+++ b/source3/utils/testparm.c
@@ -276,6 +276,7 @@ static int do_global_checks(void)
const char *socket_options;
const struct loadparm_substitution *lp_sub =
loadparm_s3_global_substitution();
+ const char *check_pw_script = NULL;
fprintf(stderr, "\n");
@@ -699,6 +700,17 @@ static int do_global_checks(void)
"CVE-2022-37966\n\n");
}
+ check_pw_script = lp_check_password_script(talloc_tos(), lp_sub);
+ if (talloc_string_sub_mixed_quoting(check_pw_script, 'u')) {
+ fprintf(stderr,
+ "WARNING: You are using 'check password script' "
+ "with mixed quoting and %%u.\n"
+ "CVE-2026-4408 changed the way %%u substitution works. \n"
+ "You should use the SAMBA_CPS_ACCOUNT_NAME "
+ "environment variable exported to the script, or\n"
+ "at least use single quotes (directly) around '%%u'.\n\n");
+ }
+
return ret;
}
--
2.53.0
From 4c48c55544768d5bc5c168c326c8bdbc7ce0de95 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 11 May 2026 13:52:52 +0200
Subject: [PATCH 122/122] CVE-2026-4408: docs-xml/smbdotconf: clarify '%u' in
'check password script'
Admins should use SAMBA_CPS_ACCOUNT_NAME.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
docs-xml/smbdotconf/security/checkpasswordscript.xml | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/docs-xml/smbdotconf/security/checkpasswordscript.xml b/docs-xml/smbdotconf/security/checkpasswordscript.xml
index 18aa2c6d290..dd162d89f08 100644
--- a/docs-xml/smbdotconf/security/checkpasswordscript.xml
+++ b/docs-xml/smbdotconf/security/checkpasswordscript.xml
@@ -20,8 +20,8 @@
<itemizedlist>
<listitem><para>
- SAMBA_CPS_ACCOUNT_NAME is always present and contains the sAMAccountName of user,
- the is the same as the %u substitutions in the none AD DC case.
+ SAMBA_CPS_ACCOUNT_NAME is always present and contains the sAMAccountName of user.
+ It is the same as the '%u' substitutions in the non AD DC case.
</para></listitem>
<listitem><para>
@@ -33,6 +33,12 @@
</para></listitem>
</itemizedlist>
+ <para>Even on a non AD DC SAMBA_CPS_ACCOUNT_NAME is the preferred way to access the
+ account name, as it contains the raw value provided by the client. If that's not
+ possible you should use single quotes (directly) around %u, e.g. /path/to/somescript '%u',
+ see CVE-2026-4408 for more details.
+ </para>
+
<para>Note: In the example directory is a sample program called <command moreinfo="none">crackcheck</command>
that uses cracklib to check the password quality.</para>
--
2.53.0