Version in base suite: 4.17.12+dfsg-0+deb12u2 Base version: samba_4.17.12+dfsg-0+deb12u2 Target version: samba_4.17.12+dfsg-0+deb12u3 Base file: /srv/ftp-master.debian.org/ftp/pool/main/s/samba/samba_4.17.12+dfsg-0+deb12u2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/s/samba/samba_4.17.12+dfsg-0+deb12u3.dsc changelog | 9 patches/CVE-2018-14628/01-python-descriptor-add-get_deletedobjects_descriptor.patch | 43 + patches/CVE-2018-14628/02-python-provision-make-DELETEDOBJECTS_DESCRIPTOR-availab.patch | 91 +++ patches/CVE-2018-14628/03-s4-setup-set-the-correct-nTSecurityDescriptor-on-the-CN.patch | 67 ++ patches/CVE-2018-14628/04-s4-dsdb-remove-unused-code-in-dirsync_filter_entry.patch | 99 +++ patches/CVE-2018-14628/05-dbchecker-use-get_deletedobjects_descriptor-for-missing.patch | 66 ++ patches/CVE-2018-14628/06-python-descriptor-let-samba-tool-dbch.patch | 138 ++++ patches/CVE-2025-10230/s4-tests-check-that-wins-hook-sanitizes-names.patch | 286 ++++++++++ patches/CVE-2025-10230/s4-wins-restrict-names-fed-to-shell.patch | 71 ++ patches/CVE-2025-9640/Add-torture-test-for-inserting-hole-in-stream.patch | 286 ++++++++++ patches/CVE-2025-9640/s3-modules-vfs_streams_xattr-fix-unitialized-write.patch | 44 + patches/series | 14 12 files changed, 1214 insertions(+) diff -Nru samba-4.17.12+dfsg/debian/changelog samba-4.17.12+dfsg/debian/changelog --- samba-4.17.12+dfsg/debian/changelog 2025-07-11 08:21:51.000000000 +0000 +++ samba-4.17.12+dfsg/debian/changelog 2025-11-30 08:35:04.000000000 +0000 @@ -1,3 +1,12 @@ +samba (2:4.17.12+dfsg-0+deb12u3) bookworm; urgency=medium + + * CVE-2018-14628: Unprivileged read of deleted object tombstones + in AD LDAP server. Closes: #1034803 + * CVE-2025-10230: Command injection via WINS server hook script + * CVE-2025-9640: Uninitialized memory disclosure via vfs_streams_xattr + + -- Michael Tokarev Sun, 30 Nov 2025 11:35:04 +0300 + samba (2:4.17.12+dfsg-0+deb12u2) bookworm; urgency=medium [ Salvatore Bonaccorso ] diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/01-python-descriptor-add-get_deletedobjects_descriptor.patch samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/01-python-descriptor-add-get_deletedobjects_descriptor.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/01-python-descriptor-add-get_deletedobjects_descriptor.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/01-python-descriptor-add-get_deletedobjects_descriptor.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,43 @@ +From: Stefan Metzmacher +Date: Fri, 29 Jan 2016 23:30:59 +0100 +Subject: CVE-2018-14628: python:descriptor: add get_deletedobjects_descriptor() + +samba-tool drs clone-dc-database was quite useful to find +the true value of nTSecurityDescriptor of the CN=Delete Objects +containers. + +Only the auto inherited SACL is available via a ldap search. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=13595 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Andrew Bartlett +(cherry picked from commit 3be190dcf7153e479383f7f3d29ddca43fe121b8) +--- + python/samba/descriptor.py | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/python/samba/descriptor.py b/python/samba/descriptor.py +index ac4c7e3273d..08c7518f56a 100644 +--- a/python/samba/descriptor.py ++++ b/python/samba/descriptor.py +@@ -52,6 +52,16 @@ def get_empty_descriptor(domain_sid, name_map={}): + # "get_schema_descriptor" is located in "schema.py" + + ++def get_deletedobjects_descriptor(domain_sid, name_map=None): ++ if name_map is None: ++ name_map = {} ++ ++ sddl = "O:SYG:SYD:PAI" \ ++ "(A;;RPWPCCDCLCRCWOWDSDSW;;;SY)" \ ++ "(A;;RPLC;;;BA)" ++ return sddl2binary(sddl, domain_sid, name_map) ++ ++ + def get_config_descriptor(domain_sid, name_map={}): + sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ + "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \ +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/02-python-provision-make-DELETEDOBJECTS_DESCRIPTOR-availab.patch samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/02-python-provision-make-DELETEDOBJECTS_DESCRIPTOR-availab.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/02-python-provision-make-DELETEDOBJECTS_DESCRIPTOR-availab.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/02-python-provision-make-DELETEDOBJECTS_DESCRIPTOR-availab.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,91 @@ +From: Stefan Metzmacher +Date: Fri, 29 Jan 2016 23:33:37 +0100 +Subject: CVE-2018-14628: python:provision: make DELETEDOBJECTS_DESCRIPTOR + available in the ldif files + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=13595 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Andrew Bartlett +(cherry picked from commit 0c329a0fda37d87ed737e4b579b6d04ec907604c) +--- + python/samba/provision/__init__.py | 5 +++++ + python/samba/provision/sambadns.py | 4 ++++ + 2 files changed, 9 insertions(+) + +diff --git a/python/samba/provision/__init__.py b/python/samba/provision/__init__.py +index ff9b8fac916..f7d7468e4fa 100644 +--- a/python/samba/provision/__init__.py ++++ b/python/samba/provision/__init__.py +@@ -78,6 +78,7 @@ from samba.provision.backend import ( + LDBBackend, + ) + from samba.descriptor import ( ++ get_deletedobjects_descriptor, + get_empty_descriptor, + get_config_descriptor, + get_config_partitions_descriptor, +@@ -1441,6 +1442,8 @@ def fill_samdb(samdb, lp, names, logger, policyguid, + msg["subRefs"] = ldb.MessageElement(names.configdn, ldb.FLAG_MOD_ADD, + "subRefs") + ++ deletedobjects_descr = b64encode(get_deletedobjects_descriptor(names.domainsid)).decode('utf8') ++ + samdb.invocation_id = invocationid + + # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it +@@ -1472,6 +1475,7 @@ def fill_samdb(samdb, lp, names, logger, policyguid, + "FOREST_FUNCTIONALITY": str(forestFunctionality), + "DOMAIN_FUNCTIONALITY": str(domainFunctionality), + "NTDSQUOTAS_DESCRIPTOR": ntdsquotas_descr, ++ "DELETEDOBJECTS_DESCRIPTOR": deletedobjects_descr, + "LOSTANDFOUND_DESCRIPTOR": protected1wd_descr, + "SERVICES_DESCRIPTOR": protected1_descr, + "PHYSICALLOCATIONS_DESCRIPTOR": protected1wd_descr, +@@ -1536,6 +1540,7 @@ def fill_samdb(samdb, lp, names, logger, policyguid, + "RIDAVAILABLESTART": str(next_rid + 600), + "POLICYGUID_DC": policyguid_dc, + "INFRASTRUCTURE_DESCRIPTOR": infrastructure_desc, ++ "DELETEDOBJECTS_DESCRIPTOR": deletedobjects_descr, + "LOSTANDFOUND_DESCRIPTOR": lostandfound_desc, + "SYSTEM_DESCRIPTOR": system_desc, + "BUILTIN_DESCRIPTOR": builtin_desc, +diff --git a/python/samba/provision/sambadns.py b/python/samba/provision/sambadns.py +index 9184711a764..d057b7830ad 100644 +--- a/python/samba/provision/sambadns.py ++++ b/python/samba/provision/sambadns.py +@@ -42,6 +42,7 @@ from samba.dsdb import ( + DS_GUID_USERS_CONTAINER + ) + from samba.descriptor import ( ++ get_deletedobjects_descriptor, + get_domain_descriptor, + get_domain_delete_protected1_descriptor, + get_domain_delete_protected2_descriptor, +@@ -256,6 +257,7 @@ def setup_dns_partitions(samdb, domainsid, domaindn, forestdn, configdn, + domainzone_dn = "DC=DomainDnsZones,%s" % domaindn + forestzone_dn = "DC=ForestDnsZones,%s" % forestdn + descriptor = get_dns_partition_descriptor(domainsid) ++ deletedobjects_desc = get_deletedobjects_descriptor(domainsid) + + setup_add_ldif(samdb, setup_path("provision_dnszones_partitions.ldif"), { + "ZONE_DN": domainzone_dn, +@@ -278,6 +280,7 @@ def setup_dns_partitions(samdb, domainsid, domaindn, forestdn, configdn, + "ZONE_DNS": domainzone_dns, + "CONFIGDN": configdn, + "SERVERDN": serverdn, ++ "DELETEDOBJECTS_DESCRIPTOR": b64encode(deletedobjects_desc).decode('utf8'), + "LOSTANDFOUND_DESCRIPTOR": b64encode(protected2_desc).decode('utf8'), + "INFRASTRUCTURE_DESCRIPTOR": b64encode(protected1_desc).decode('utf8'), + }) +@@ -297,6 +300,7 @@ def setup_dns_partitions(samdb, domainsid, domaindn, forestdn, configdn, + "ZONE_DNS": forestzone_dns, + "CONFIGDN": configdn, + "SERVERDN": serverdn, ++ "DELETEDOBJECTS_DESCRIPTOR": b64encode(deletedobjects_desc).decode('utf8'), + "LOSTANDFOUND_DESCRIPTOR": b64encode(protected2_desc).decode('utf8'), + "INFRASTRUCTURE_DESCRIPTOR": b64encode(protected1_desc).decode('utf8'), + }) +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/03-s4-setup-set-the-correct-nTSecurityDescriptor-on-the-CN.patch samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/03-s4-setup-set-the-correct-nTSecurityDescriptor-on-the-CN.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/03-s4-setup-set-the-correct-nTSecurityDescriptor-on-the-CN.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/03-s4-setup-set-the-correct-nTSecurityDescriptor-on-the-CN.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,67 @@ +From: Stefan Metzmacher +Date: Fri, 29 Jan 2016 23:34:15 +0100 +Subject: CVE-2018-14628: s4:setup: set the correct nTSecurityDescriptor on the + CN=Deleted Objects container + +This revealed a bug in our dirsync code, so we mark +test_search_with_dirsync_deleted_objects as knownfail. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=13595 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Andrew Bartlett +(cherry picked from commit 7f8b15faa76d05023c987fac2c4c31f9ac61bb47) +--- + selftest/knownfail.d/samba4.ldap.confidential_attr | 1 + + source4/setup/provision.ldif | 1 + + source4/setup/provision_configuration.ldif | 1 + + source4/setup/provision_dnszones_add.ldif | 1 + + 4 files changed, 4 insertions(+) + create mode 100644 selftest/knownfail.d/samba4.ldap.confidential_attr + +diff --git a/selftest/knownfail.d/samba4.ldap.confidential_attr b/selftest/knownfail.d/samba4.ldap.confidential_attr +new file mode 100644 +index 00000000000..46a75ce928b +--- /dev/null ++++ b/selftest/knownfail.d/samba4.ldap.confidential_attr +@@ -0,0 +1 @@ ++^samba4.ldap.confidential_attr.python.*.__main__.*.test_search_with_dirsync_deleted_objects +diff --git a/source4/setup/provision.ldif b/source4/setup/provision.ldif +index 5d9eba49f86..7f966fd57f8 100644 +--- a/source4/setup/provision.ldif ++++ b/source4/setup/provision.ldif +@@ -34,6 +34,7 @@ isDeleted: TRUE + isCriticalSystemObject: TRUE + showInAdvancedViewOnly: TRUE + systemFlags: -1946157056 ++nTSecurityDescriptor:: ${DELETEDOBJECTS_DESCRIPTOR} + + # Computers located in "provision_computers*.ldif" + # Users/Groups located in "provision_users*.ldif" +diff --git a/source4/setup/provision_configuration.ldif b/source4/setup/provision_configuration.ldif +index 53c9c8536de..8fcbddbdae4 100644 +--- a/source4/setup/provision_configuration.ldif ++++ b/source4/setup/provision_configuration.ldif +@@ -14,6 +14,7 @@ description: Container for deleted objects + isDeleted: TRUE + isCriticalSystemObject: TRUE + systemFlags: -1946157056 ++nTSecurityDescriptor:: ${DELETEDOBJECTS_DESCRIPTOR} + + # Extended rights + +diff --git a/source4/setup/provision_dnszones_add.ldif b/source4/setup/provision_dnszones_add.ldif +index 860aa4b72b3..a2d6b6bab8f 100644 +--- a/source4/setup/provision_dnszones_add.ldif ++++ b/source4/setup/provision_dnszones_add.ldif +@@ -8,6 +8,7 @@ description: Deleted objects + isDeleted: TRUE + isCriticalSystemObject: TRUE + systemFlags: -1946157056 ++nTSecurityDescriptor:: ${DELETEDOBJECTS_DESCRIPTOR} + + dn: CN=LostAndFound,${ZONE_DN} + objectClass: top +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/04-s4-dsdb-remove-unused-code-in-dirsync_filter_entry.patch samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/04-s4-dsdb-remove-unused-code-in-dirsync_filter_entry.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/04-s4-dsdb-remove-unused-code-in-dirsync_filter_entry.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/04-s4-dsdb-remove-unused-code-in-dirsync_filter_entry.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,99 @@ +From: Stefan Metzmacher +Date: Mon, 26 Jun 2023 15:14:24 +0200 +Subject: CVE-2018-14628: s4:dsdb: remove unused code in dirsync_filter_entry() + +This makes the next change easier to understand. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=13595 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Andrew Bartlett +(cherry picked from commit 498542be0bbf4f26558573c1f87b77b8e3509371) +--- + source4/dsdb/samdb/ldb_modules/dirsync.c | 53 +++--------------------- + 1 file changed, 5 insertions(+), 48 deletions(-) + +diff --git a/source4/dsdb/samdb/ldb_modules/dirsync.c b/source4/dsdb/samdb/ldb_modules/dirsync.c +index fbb75790095..124cff25e39 100644 +--- a/source4/dsdb/samdb/ldb_modules/dirsync.c ++++ b/source4/dsdb/samdb/ldb_modules/dirsync.c +@@ -151,10 +151,6 @@ static int dirsync_filter_entry(struct ldb_request *req, + * list only the attribute that have been modified since last interogation + * + */ +- newmsg = ldb_msg_new(dsc->req); +- if (newmsg == NULL) { +- return ldb_oom(ldb); +- } + for (i = msg->num_elements - 1; i >= 0; i--) { + if (ldb_attr_cmp(msg->elements[i].name, "uSNChanged") == 0) { + int error = 0; +@@ -201,11 +197,6 @@ static int dirsync_filter_entry(struct ldb_request *req, + */ + return LDB_SUCCESS; + } +- newmsg->dn = ldb_dn_new(newmsg, ldb, ""); +- if (newmsg->dn == NULL) { +- return ldb_oom(ldb); +- } +- + el = ldb_msg_find_element(msg, "objectGUID"); + if ( el != NULL) { + guidfound = true; +@@ -216,48 +207,14 @@ static int dirsync_filter_entry(struct ldb_request *req, + * well will uncomment the code bellow + */ + SMB_ASSERT(guidfound == true); +- /* +- if (guidfound == false) { +- struct GUID guid; +- struct ldb_val *new_val; +- DATA_BLOB guid_blob; +- +- tmp[0] = '\0'; +- txt = strrchr(txt, ':'); +- if (txt == NULL) { +- return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); +- } +- txt++; +- +- status = GUID_from_string(txt, &guid); +- if (!NT_STATUS_IS_OK(status)) { +- return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); +- } +- +- status = GUID_to_ndr_blob(&guid, msg, &guid_blob); +- if (!NT_STATUS_IS_OK(status)) { +- return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); +- } +- +- new_val = talloc(msg, struct ldb_val); +- if (new_val == NULL) { +- return ldb_oom(ldb); +- } +- new_val->data = talloc_steal(new_val, guid_blob.data); +- new_val->length = guid_blob.length; +- if (ldb_msg_add_value(msg, "objectGUID", new_val, NULL) != 0) { +- return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); +- } +- } +- */ +- ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD); +- talloc_steal(newmsg->elements, el->name); +- talloc_steal(newmsg->elements, el->values); +- +- talloc_steal(newmsg->elements, msg); + return ldb_module_send_entry(dsc->req, msg, controls); + } + ++ newmsg = ldb_msg_new(dsc->req); ++ if (newmsg == NULL) { ++ return ldb_oom(ldb); ++ } ++ + ndr_err = ndr_pull_struct_blob(replMetaData, dsc, &rmd, + (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/05-dbchecker-use-get_deletedobjects_descriptor-for-missing.patch samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/05-dbchecker-use-get_deletedobjects_descriptor-for-missing.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/05-dbchecker-use-get_deletedobjects_descriptor-for-missing.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/05-dbchecker-use-get_deletedobjects_descriptor-for-missing.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,66 @@ +From: Stefan Metzmacher +Date: Wed, 7 Jun 2023 18:18:58 +0200 +Subject: CVE-2018-14628: dbchecker: use get_deletedobjects_descriptor for + missing deleted objects container + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=13595 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Andrew Bartlett +(cherry picked from commit 70586061128f90afa33f25e104d4570a1cf778db) +--- + python/samba/dbchecker.py | 17 ++++++++++++++--- + 1 file changed, 14 insertions(+), 3 deletions(-) + +diff --git a/python/samba/dbchecker.py b/python/samba/dbchecker.py +index 449b0a7d985..e124b1a0d67 100644 +--- a/python/samba/dbchecker.py ++++ b/python/samba/dbchecker.py +@@ -20,7 +20,7 @@ + import ldb + import samba + import time +-from base64 import b64decode ++from base64 import b64decode, b64encode + from samba import dsdb + from samba import common + from samba.dcerpc import misc +@@ -29,7 +29,11 @@ from samba.ndr import ndr_unpack, ndr_pack + from samba.dcerpc import drsblobs + from samba.samdb import dsdb_Dn + from samba.dcerpc import security +-from samba.descriptor import get_wellknown_sds, get_diff_sds ++from samba.descriptor import ( ++ get_wellknown_sds, ++ get_deletedobjects_descriptor, ++ get_diff_sds ++) + from samba.auth import system_session, admin_session + from samba.netcmd import CommandError + from samba.netcmd.fsmo import get_fsmo_roleowner +@@ -341,6 +345,12 @@ class dbcheck(object): + listwko.append('%s:%s' % (wko_prefix, dn)) + guid_suffix = "" + ++ ++ domain_sid = security.dom_sid(self.samdb.get_domain_sid()) ++ sec_desc = get_deletedobjects_descriptor(domain_sid, ++ name_map=self.name_map) ++ sec_desc_b64 = b64encode(sec_desc).decode('utf8') ++ + # Insert a brand new Deleted Objects container + self.samdb.add_ldif("""dn: %s + objectClass: top +@@ -349,7 +359,8 @@ description: Container for deleted objects + isDeleted: TRUE + isCriticalSystemObject: TRUE + showInAdvancedViewOnly: TRUE +-systemFlags: -1946157056%s""" % (dn, guid_suffix), ++nTSecurityDescriptor:: %s ++systemFlags: -1946157056%s""" % (dn, sec_desc_b64, guid_suffix), + controls=["relax:0", "provision:0"]) + + delta = ldb.Message() +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/06-python-descriptor-let-samba-tool-dbch.patch samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/06-python-descriptor-let-samba-tool-dbch.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/06-python-descriptor-let-samba-tool-dbch.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2018-14628/06-python-descriptor-let-samba-tool-dbch.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,138 @@ +From: Stefan Metzmacher +Date: Fri, 29 Jan 2016 23:35:31 +0100 +Subject: CVE-2018-14628: python:descriptor: let samba-tool dbcheck fix + the nTSecurityDescriptor on CN=Deleted Objects containers + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=13595 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Andrew Bartlett +(cherry picked from commit 97e4aab1a6e2feda7c6c6fdeaa7c3e1818c55566) +--- + python/samba/dbchecker.py | 10 ++++++++-- + python/samba/descriptor.py | 15 ++++++++++++++- + testprogs/blackbox/dbcheck-links.sh | 12 ++++++++++++ + 3 files changed, 34 insertions(+), 3 deletions(-) + +diff --git a/python/samba/dbchecker.py b/python/samba/dbchecker.py +index e124b1a0d67..28d99c01d04 100644 +--- a/python/samba/dbchecker.py ++++ b/python/samba/dbchecker.py +@@ -2444,7 +2444,7 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) + error_count += 1 + continue + +- if self.reset_well_known_acls: ++ if dn == deleted_objects_dn or self.reset_well_known_acls: + try: + well_known_sd = self.get_wellknown_sd(dn) + except KeyError: +@@ -2453,7 +2453,13 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) + current_sd = ndr_unpack(security.descriptor, + obj[attrname][0]) + +- diff = get_diff_sds(well_known_sd, current_sd, security.dom_sid(self.samdb.get_domain_sid())) ++ ignoreAdditionalACEs = False ++ if not self.reset_well_known_acls: ++ ignoreAdditionalACEs = True ++ ++ diff = get_diff_sds(well_known_sd, current_sd, ++ security.dom_sid(self.samdb.get_domain_sid()), ++ ignoreAdditionalACEs=ignoreAdditionalACEs) + if diff != "": + self.err_wrong_default_sd(dn, well_known_sd, diff) + error_count += 1 +diff --git a/python/samba/descriptor.py b/python/samba/descriptor.py +index 08c7518f56a..34877fa4814 100644 +--- a/python/samba/descriptor.py ++++ b/python/samba/descriptor.py +@@ -417,6 +417,7 @@ def get_wellknown_sds(samdb): + # Then subcontainers + subcontainers = [ + (ldb.Dn(samdb, "%s" % str(samdb.domain_dn())), get_domain_descriptor), ++ (ldb.Dn(samdb, "CN=Deleted Objects,%s" % str(samdb.domain_dn())), get_deletedobjects_descriptor), + (ldb.Dn(samdb, "CN=LostAndFound,%s" % str(samdb.domain_dn())), get_domain_delete_protected2_descriptor), + (ldb.Dn(samdb, "CN=System,%s" % str(samdb.domain_dn())), get_domain_delete_protected1_descriptor), + (ldb.Dn(samdb, "CN=Infrastructure,%s" % str(samdb.domain_dn())), get_domain_infrastructure_descriptor), +@@ -427,6 +428,7 @@ def get_wellknown_sds(samdb): + (ldb.Dn(samdb, "CN=MicrosoftDNS,CN=System,%s" % str(samdb.domain_dn())), get_dns_domain_microsoft_dns_descriptor), + + (ldb.Dn(samdb, "%s" % str(samdb.get_config_basedn())), get_config_descriptor), ++ (ldb.Dn(samdb, "CN=Deleted Objects,%s" % str(samdb.get_config_basedn())), get_deletedobjects_descriptor), + (ldb.Dn(samdb, "CN=NTDS Quotas,%s" % str(samdb.get_config_basedn())), get_config_ntds_quotas_descriptor), + (ldb.Dn(samdb, "CN=LostAndFoundConfig,%s" % str(samdb.get_config_basedn())), get_config_delete_protected1wd_descriptor), + (ldb.Dn(samdb, "CN=Services,%s" % str(samdb.get_config_basedn())), get_config_delete_protected1_descriptor), +@@ -451,6 +453,9 @@ def get_wellknown_sds(samdb): + if ldb.Dn(samdb, nc.decode('utf8')) == dnsforestdn: + c = (ldb.Dn(samdb, "%s" % str(dnsforestdn)), get_dns_partition_descriptor) + subcontainers.append(c) ++ c = (ldb.Dn(samdb, "CN=Deleted Objects,%s" % str(dnsforestdn)), ++ get_deletedobjects_descriptor) ++ subcontainers.append(c) + c = (ldb.Dn(samdb, "CN=Infrastructure,%s" % str(dnsforestdn)), + get_domain_delete_protected1_descriptor) + subcontainers.append(c) +@@ -466,6 +471,9 @@ def get_wellknown_sds(samdb): + if ldb.Dn(samdb, nc.decode('utf8')) == dnsdomaindn: + c = (ldb.Dn(samdb, "%s" % str(dnsdomaindn)), get_dns_partition_descriptor) + subcontainers.append(c) ++ c = (ldb.Dn(samdb, "CN=Deleted Objects,%s" % str(dnsdomaindn)), ++ get_deletedobjects_descriptor) ++ subcontainers.append(c) + c = (ldb.Dn(samdb, "CN=Infrastructure,%s" % str(dnsdomaindn)), + get_domain_delete_protected1_descriptor) + subcontainers.append(c) +@@ -558,7 +566,8 @@ def get_clean_sd(sd): + return sd_clean + + +-def get_diff_sds(refsd, cursd, domainsid, checkSacl=True): ++def get_diff_sds(refsd, cursd, domainsid, checkSacl=True, ++ ignoreAdditionalACEs=False): + """Get the difference between 2 sd + + This function split the textual representation of ACL into smaller +@@ -613,6 +622,10 @@ def get_diff_sds(refsd, cursd, domainsid, checkSacl=True): + h_ref.remove(k) + + if len(h_cur) + len(h_ref) > 0: ++ if txt == "" and len(h_ref) == 0: ++ if ignoreAdditionalACEs: ++ return "" ++ + txt = "%s\tPart %s is different between reference" \ + " and current here is the detail:\n" % (txt, part) + +diff --git a/testprogs/blackbox/dbcheck-links.sh b/testprogs/blackbox/dbcheck-links.sh +index 29fb5b85abc..a91ed00fb0f 100755 +--- a/testprogs/blackbox/dbcheck-links.sh ++++ b/testprogs/blackbox/dbcheck-links.sh +@@ -59,6 +59,16 @@ dbcheck() + fi + } + ++dbcheck_acl_reset() ++{ ++ $PYTHON $BINDIR/samba-tool dbcheck -H tdb://$PREFIX_ABS/${RELEASE}/private/sam.ldb --cross-ncs --fix --yes --attrs=nTSecurityDescriptor ++} ++ ++dbcheck_acl_clean() ++{ ++ $PYTHON $BINDIR/samba-tool dbcheck -H tdb://$PREFIX_ABS/${RELEASE}/private/sam.ldb --cross-ncs --attrs=nTSecurityDescriptor ++} ++ + dbcheck_dangling() + { + dbcheck "" "1" "--selftest-check-expired-tombstones" +@@ -925,6 +935,8 @@ EOF + remove_directory $PREFIX_ABS/${RELEASE} + + testit $RELEASE undump || failed=$(expr $failed + 1) ++testit_expect_failure "dbcheck_acl_reset" dbcheck_acl_reset || failed=$(expr $failed + 1) ++testit "dbcheck_acl_clean" dbcheck_acl_clean || failed=$(expr $failed + 1) + testit "add_two_more_users" add_two_more_users || failed=$(expr $failed + 1) + testit "add_four_more_links" add_four_more_links || failed=$(expr $failed + 1) + testit "remove_one_link" remove_one_link || failed=$(expr $failed + 1) +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2025-10230/s4-tests-check-that-wins-hook-sanitizes-names.patch samba-4.17.12+dfsg/debian/patches/CVE-2025-10230/s4-tests-check-that-wins-hook-sanitizes-names.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2025-10230/s4-tests-check-that-wins-hook-sanitizes-names.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2025-10230/s4-tests-check-that-wins-hook-sanitizes-names.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,286 @@ +From: Douglas Bagnall +Date: Tue, 9 Sep 2025 13:36:16 +1200 +Subject: CVE-2025-10230: s4/tests: check that wins hook sanitizes names + +An smb.conf can contain a 'wins hook' parameter, which names a script +to run when a WINS name is changed. The man page says + + The second argument is the NetBIOS name. If the name is not a + legal name then the wins hook is not called. Legal names contain + only letters, digits, hyphens, underscores and periods. + +but it turns out the legality check is not performed if the WINS +server in question is the source4 nbt one. It is not expected that +people will run this server, but they can. This is bad because the +name is passed unescaped into a shell command line, allowing command +injection. + +For this test we don't care whether the WINS server is returning an +error code, just whether it is running the wins hook. The tests show +it often runs the hook it shouldn't, though some characters are +incidentally blocked because the name has to fit in a DN before it +gets to the hook, and DNs have a few syntactic restrictions (e.g., +blocking '<', '>', and ';'). + +The source3 WINS server that is used by Samba when not run as a DC is +not affected and not here tested. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15903 + +Signed-off-by: Douglas Bagnall +Reviewed-by: Gary Lockyer +--- + python/samba/tests/usage.py | 2 + + .../samba4.nbt.wins.wins_bad_names | 1 + + selftest/target/Samba4.pm | 1 + + source4/torture/nbt/wins.c | 136 +++++++++++++++++- + testprogs/blackbox/wins_hook_test | 15 ++ + 5 files changed, 152 insertions(+), 3 deletions(-) + create mode 100644 selftest/knownfail.d/samba4.nbt.wins.wins_bad_names + create mode 100755 testprogs/blackbox/wins_hook_test + +diff --git a/python/samba/tests/usage.py b/python/samba/tests/usage.py +index 0286be15ec3..3dead1958a4 100644 +--- a/python/samba/tests/usage.py ++++ b/python/samba/tests/usage.py +@@ -75,6 +75,7 @@ EXCLUDE_USAGE = { + 'lib/ldb/tests/python/api.py', + 'source4/selftest/tests.py', + 'buildtools/bin/waf', ++ 'testprogs/blackbox/wins_hook_test', + 'selftest/tap2subunit', + 'script/show_test_time', + 'source4/scripting/bin/subunitrun', +@@ -121,6 +122,7 @@ EXCLUDE_HELP = { + 'selftest/tap2subunit', + 'wintest/test-s3.py', + 'wintest/test-s4-howto.py', ++ 'testprogs/blackbox/wins_hook_test', + } + + +diff --git a/selftest/knownfail.d/samba4.nbt.wins.wins_bad_names b/selftest/knownfail.d/samba4.nbt.wins.wins_bad_names +new file mode 100644 +index 00000000000..52388ce5749 +--- /dev/null ++++ b/selftest/knownfail.d/samba4.nbt.wins.wins_bad_names +@@ -0,0 +1 @@ ++samba4.nbt.wins.wins_bad_names +diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm +index 7033146f46a..8d93b50b1ee 100755 +--- a/selftest/target/Samba4.pm ++++ b/selftest/target/Samba4.pm +@@ -1613,6 +1613,7 @@ sub provision_ad_dc_ntvfs($$$) + ldap server require strong auth = allow_sasl_over_tls + raw NTLMv2 auth = yes + lsa over netlogon = yes ++ wins hook = $ENV{SRCDIR_ABS}/testprogs/blackbox/wins_hook_test + rpc server port = 1027 + auth event notification = true + dsdb event notification = true +diff --git a/source4/torture/nbt/wins.c b/source4/torture/nbt/wins.c +index 8c847b5ac50..7d7321752d6 100644 +--- a/source4/torture/nbt/wins.c ++++ b/source4/torture/nbt/wins.c +@@ -31,6 +31,10 @@ + #include "torture/nbt/proto.h" + #include "param/param.h" + ++/* rcode used when you don't want to check the rcode */ ++#define WINS_TEST_RCODE_WE_DONT_CARE 255 ++ ++ + #define CHECK_VALUE(tctx, v, correct) \ + torture_assert_int_equal(tctx, v, correct, "Incorrect value") + +@@ -137,7 +141,9 @@ static bool nbt_test_wins_name(struct torture_context *tctx, const char *address + address)); + + CHECK_STRING(tctx, io.out.wins_server, address); +- CHECK_VALUE(tctx, io.out.rcode, 0); ++ if (register_rcode != WINS_TEST_RCODE_WE_DONT_CARE) { ++ CHECK_VALUE(tctx, io.out.rcode, 0); ++ } + + torture_comment(tctx, "register the name correct address\n"); + name_register.in.name = *name; +@@ -185,7 +191,9 @@ static bool nbt_test_wins_name(struct torture_context *tctx, const char *address + talloc_asprintf(tctx, "Bad response from %s for name register\n", + address)); + +- CHECK_VALUE(tctx, name_register.out.rcode, 0); ++ if (register_rcode != WINS_TEST_RCODE_WE_DONT_CARE) { ++ CHECK_VALUE(tctx, name_register.out.rcode, 0); ++ } + CHECK_STRING(tctx, name_register.out.reply_addr, myaddress); + } + +@@ -203,7 +211,9 @@ static bool nbt_test_wins_name(struct torture_context *tctx, const char *address + torture_assert_ntstatus_ok(tctx, status, talloc_asprintf(tctx, "Bad response from %s for name register", address)); + + CHECK_STRING(tctx, io.out.wins_server, address); +- CHECK_VALUE(tctx, io.out.rcode, register_rcode); ++ if (register_rcode != WINS_TEST_RCODE_WE_DONT_CARE) { ++ CHECK_VALUE(tctx, io.out.rcode, register_rcode); ++ } + + if (register_rcode != NBT_RCODE_OK) { + return true; +@@ -532,6 +542,124 @@ static bool nbt_test_wins(struct torture_context *tctx) + return ret; + } + ++/* ++ * Test that the WINS server does not call 'wins hook' when the name ++ * contains dodgy characters. ++ */ ++static bool nbt_test_wins_bad_names(struct torture_context *tctx) ++{ ++ const char *address = NULL; ++ const char *wins_hook_file = NULL; ++ bool ret = true; ++ int err; ++ bool ok; ++ struct nbt_name name = {}; ++ size_t i, j; ++ FILE *fh = NULL; ++ ++ struct { ++ const char *name; ++ bool should_succeed; ++ } test_cases[] = { ++ {"NORMAL", true}, ++ {"|look|", false}, ++ {"look&true", false}, ++ {"look\\;false", false}, ++ {"&ls>foo", false}, /* already fails due to DN syntax */ ++ {"has spaces", false}, ++ {"hyphen-dot.0", true}, ++ }; ++ ++ wins_hook_file = talloc_asprintf(tctx, "%s/wins_hook_writes_here", ++ getenv("SELFTEST_TMPDIR")); ++ ++ if (!torture_nbt_get_name(tctx, &name, &address)) { ++ return false; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(test_cases); i++) { ++ err = unlink(wins_hook_file); ++ if (err != 0 && errno != ENOENT) { ++ /* we expect ENOENT, but nothing else */ ++ torture_comment(tctx, ++ "unlink %zu of '%s' failed\n", ++ i, wins_hook_file); ++ } ++ ++ name.name = test_cases[i].name; ++ name.type = NBT_NAME_CLIENT; ++ ok = nbt_test_wins_name(tctx, address, ++ &name, ++ NBT_NODE_H, ++ true, ++ WINS_TEST_RCODE_WE_DONT_CARE ++ ); ++ if (ok == false) { ++ /* ++ * This happens when the name interferes with ++ * the DN syntax when it is put in winsdb. ++ * ++ * The wins hook will not be reached. ++ */ ++ torture_comment(tctx, "tests for '%s' failed\n", ++ name.name); ++ } ++ ++ /* ++ * poll for the file being created by the wins hook. ++ */ ++ for (j = 0; j < 10; j++) { ++ usleep(200000); ++ fh = fopen(wins_hook_file, "r"); ++ if (fh != NULL) { ++ break; ++ } ++ } ++ ++ if (fh == NULL) { ++ if (errno == ENOENT) { ++ if (test_cases[i].should_succeed) { ++ torture_comment( ++ tctx, ++ "wins hook for '%s' failed\n", ++ test_cases[i].name); ++ ret = false; ++ } ++ } else { ++ torture_comment( ++ tctx, ++ "wins hook for '%s' unexpectedly failed with %d\n", ++ test_cases[i].name, ++ errno); ++ ret = false; ++ } ++ } else { ++ char readback[17] = {0}; ++ size_t n = fread(readback, 1, 16, fh); ++ torture_comment(tctx, ++ "wins hook wrote '%s' read '%.*s'\n", ++ test_cases[i].name, ++ (int)n, readback); ++ ++ if (! test_cases[i].should_succeed) { ++ torture_comment(tctx, ++ "wins hook for '%s' should fail\n", ++ test_cases[i].name); ++ ret = false; ++ } ++ fclose(fh); ++ } ++ } ++ err = unlink(wins_hook_file); ++ if (err != 0 && errno != ENOENT) { ++ torture_comment(tctx, "final unlink of '%s' failed\n", ++ wins_hook_file); ++ } ++ torture_assert(tctx, ret, "wins hook failure\n"); ++ return ret; ++} ++ ++ + /* + test WINS operations + */ +@@ -540,6 +668,8 @@ struct torture_suite *torture_nbt_wins(TALLOC_CTX *mem_ctx) + struct torture_suite *suite = torture_suite_create(mem_ctx, "wins"); + + torture_suite_add_simple_test(suite, "wins", nbt_test_wins); ++ torture_suite_add_simple_test(suite, "wins_bad_names", ++ nbt_test_wins_bad_names); + + return suite; + } +diff --git a/testprogs/blackbox/wins_hook_test b/testprogs/blackbox/wins_hook_test +new file mode 100755 +index 00000000000..f15379c28ca +--- /dev/null ++++ b/testprogs/blackbox/wins_hook_test +@@ -0,0 +1,15 @@ ++#!/usr/bin/python3 ++ ++import os ++import sys ++ ++filename = f"{os.environ['SELFTEST_TMPDIR']}/wins_hook_writes_here" ++ ++f = open(filename, 'wb') ++ ++# Some names may truncate argv (e.g. '&'), for which we leave the file ++# empty. ++if len(sys.argv) > 2: ++ f.write(os.fsencode(sys.argv[2])) ++ ++f.close() +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2025-10230/s4-wins-restrict-names-fed-to-shell.patch samba-4.17.12+dfsg/debian/patches/CVE-2025-10230/s4-wins-restrict-names-fed-to-shell.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2025-10230/s4-wins-restrict-names-fed-to-shell.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2025-10230/s4-wins-restrict-names-fed-to-shell.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,71 @@ +From: Douglas Bagnall +Date: Wed, 3 Sep 2025 14:20:24 +1200 +Subject: CVE-2025-10230: s4:wins: restrict names fed to shell + +If the "wins hook" smb.conf parameter is set, the WINS server will +attempt to execute that value in a shell command line when a client +asks to modify a name. The WINS system is a trusting one, and clients +can claim any NETBIOS name they wish. + +With the source3 nmbd WINS server (since the 1999 commit now called +3db52feb1f3b2c07ce0b06ad4a7099fa6efe3fc7) the wins hook will not be +run for names that contain shell metacharacters. This restriction has +not been present on the source4 nbt WINS server, which is the WINS +server that will be used in the event that an Active Directory Domain +Controller is also running WINS. + +This allowed an unauthenticated client to execute arbitrary commands +on the server. + +This commit brings the nmbd check into the nbt WINS server, so that +the wins hook will only be run for names that contain only letters, +digits, hyphens, underscores and periods. This matches the behaviour +described in the smb.conf man page. + +The source3 nmbd WINS server has another layer of protection, in that +it uses the smb_run() exec wrapper that tries to escape arguments. We +don't do that here. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15903 + +Signed-off-by: Douglas Bagnall +Reviewed-by: Gary Lockyer +--- + selftest/knownfail.d/samba4.nbt.wins.wins_bad_names | 1 - + source4/nbt_server/wins/wins_hook.c | 9 +++++++++ + 2 files changed, 9 insertions(+), 1 deletion(-) + delete mode 100644 selftest/knownfail.d/samba4.nbt.wins.wins_bad_names + +diff --git a/selftest/knownfail.d/samba4.nbt.wins.wins_bad_names b/selftest/knownfail.d/samba4.nbt.wins.wins_bad_names +deleted file mode 100644 +index 52388ce5749..00000000000 +--- a/selftest/knownfail.d/samba4.nbt.wins.wins_bad_names ++++ /dev/null +@@ -1 +0,0 @@ +-samba4.nbt.wins.wins_bad_names +diff --git a/source4/nbt_server/wins/wins_hook.c b/source4/nbt_server/wins/wins_hook.c +index 1af471b15bc..442141fecdd 100644 +--- a/source4/nbt_server/wins/wins_hook.c ++++ b/source4/nbt_server/wins/wins_hook.c +@@ -43,9 +43,18 @@ void wins_hook(struct winsdb_handle *h, const struct winsdb_record *rec, + int child; + char *cmd = NULL; + TALLOC_CTX *tmp_mem = NULL; ++ const char *p = NULL; + + if (!wins_hook_script || !wins_hook_script[0]) return; + ++ for (p = rec->name->name; *p; p++) { ++ if (!(isalnum((int)*p) || strchr_m("._-", *p))) { ++ DBG_ERR("not calling wins hook for invalid name %s\n", ++ rec->name->name); ++ return; ++ } ++ } ++ + tmp_mem = talloc_new(h); + if (!tmp_mem) goto failed; + +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2025-9640/Add-torture-test-for-inserting-hole-in-stream.patch samba-4.17.12+dfsg/debian/patches/CVE-2025-9640/Add-torture-test-for-inserting-hole-in-stream.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2025-9640/Add-torture-test-for-inserting-hole-in-stream.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2025-9640/Add-torture-test-for-inserting-hole-in-stream.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,286 @@ +From: Andrew Walker +Date: Thu, 28 Aug 2025 19:39:34 +0000 +Subject: CVE-2025-9640: Add torture test for inserting hole in stream + +This commit adds an smb torture test for inserting a hole into +an alternate data stream and then verifying that hole contains +null bytes. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15885 + +Signed-off-by: Andrew Walker +Reviewed-by: Volker Lendecke +--- + source3/selftest/tests.py | 3 + + source4/torture/vfs/streams_xattr.c | 211 ++++++++++++++++++++++++++++ + source4/torture/vfs/vfs.c | 1 + + source4/torture/wscript_build | 2 +- + 4 files changed, 216 insertions(+), 1 deletion(-) + create mode 100644 source4/torture/vfs/streams_xattr.c + +diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py +index e93365e3db5..6d7c41c37d1 100755 +--- a/source3/selftest/tests.py ++++ b/source3/selftest/tests.py +@@ -919,6 +919,7 @@ nbt = ["nbt.dgram"] + vfs = [ + "vfs.fruit", + "vfs.acl_xattr", ++ "vfs.streams_xattr", + "vfs.fruit_netatalk", + "vfs.fruit_file_id", + "vfs.fruit_timemachine", +@@ -1107,6 +1108,8 @@ for t in tests: + plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD') + elif t == "vfs.acl_xattr": + plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD') ++ elif t == "vfs.streams_xattr": ++ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_wo_fruit -U$USERNAME%$PASSWORD') + elif t == "smb2.compound_find": + plansmbtorture4testsuite(t, "fileserver", '//$SERVER/compound_find -U$USERNAME%$PASSWORD') + plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD') +diff --git a/source4/torture/vfs/streams_xattr.c b/source4/torture/vfs/streams_xattr.c +new file mode 100644 +index 00000000000..0eb83e092e7 +--- /dev/null ++++ b/source4/torture/vfs/streams_xattr.c +@@ -0,0 +1,211 @@ ++/* ++ Unix SMB/CIFS implementation. ++ ++ Copyright (C) Andrew Walker (2025) ++ ++ 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 . ++*/ ++ ++#include "includes.h" ++#include "lib/cmdline/cmdline.h" ++#include "libcli/smb2/smb2.h" ++#include "libcli/smb2/smb2_calls.h" ++#include "libcli/smb/smbXcli_base.h" ++#include "torture/torture.h" ++#include "torture/vfs/proto.h" ++#include "libcli/resolve/resolve.h" ++#include "torture/util.h" ++#include "torture/smb2/proto.h" ++#include "lib/param/param.h" ++ ++#define BASEDIR "smb2-testads" ++ ++ ++static bool get_stream_handle(struct torture_context *tctx, ++ struct smb2_tree *tree, ++ const char *dname, ++ const char *fname, ++ const char *sname, ++ struct smb2_handle *hdl_in) ++{ ++ bool ret = true; ++ NTSTATUS status; ++ struct smb2_handle fhandle = {{0}}; ++ struct smb2_handle dhandle = {{0}}; ++ ++ torture_comment(tctx, "Create dir\n"); ++ ++ status = torture_smb2_testdir(tree, dname, &dhandle); ++ torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir\n"); ++ ++ torture_comment(tctx, "Create file\n"); ++ ++ status = torture_smb2_testfile(tree, fname, &fhandle); ++ torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile\n"); ++ ++ status = torture_smb2_testfile(tree, sname, hdl_in); ++ torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testfile\n"); ++ ++done: ++ if (!smb2_util_handle_empty(fhandle)) { ++ smb2_util_close(tree, fhandle); ++ } ++ if (!smb2_util_handle_empty(dhandle)) { ++ smb2_util_close(tree, dhandle); ++ } ++ return ret; ++} ++ ++static bool read_stream(struct torture_context *tctx, ++ TALLOC_CTX *mem_ctx, ++ struct smb2_tree *tree, ++ struct smb2_handle *stream_hdl, ++ off_t read_offset, ++ size_t read_count, ++ char **data_out, ++ size_t *data_out_sz) ++{ ++ NTSTATUS status; ++ struct smb2_read r; ++ bool ret = true; ++ ++ ZERO_STRUCT(r); ++ r.in.file.handle = *stream_hdl; ++ r.in.length = read_count; ++ r.in.offset = read_offset; ++ ++ status = smb2_read(tree, mem_ctx, &r); ++ torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "stream read\n"); ++ ++ *data_out = (char *)r.out.data.data; ++ *data_out_sz = r.out.data.length; ++ ++done: ++ return ret; ++} ++ ++ ++#define WRITE_PAYLOAD "canary" ++#define ADS_LEN 1024 ++#define ADS_OFF_TAIL ADS_LEN - sizeof(WRITE_PAYLOAD) ++ ++static bool test_streams_pwrite_hole(struct torture_context *tctx, ++ struct smb2_tree *tree) ++{ ++ NTSTATUS status; ++ bool ok; ++ bool ret = true; ++ const char *dname = BASEDIR "\\testdir"; ++ const char *fname = BASEDIR "\\testdir\\testfile"; ++ const char *sname = BASEDIR "\\testdir\\testfile:test_stream"; ++ const char *canary = "canary"; ++ struct smb2_handle shandle = {{0}}; ++ TALLOC_CTX *tmp_ctx = NULL; ++ char *data = NULL; ++ size_t data_sz, i; ++ ++ ok = smb2_util_setup_dir(tctx, tree, BASEDIR); ++ torture_assert_goto(tctx, ok == true, ret, done, "Unable to setup testdir\n"); ++ ++ tmp_ctx = talloc_new(tree); ++ torture_assert_goto(tctx, tmp_ctx != NULL, ret, done, "Memory failure\n"); ++ ++ ok = get_stream_handle(tctx, tree, dname, fname, sname, &shandle); ++ if (!ok) { ++ // torture assert already set ++ goto done; ++ } ++ ++ /* ++ * We're going to write a string at the beginning at the ADS, then write the same ++ * string at a later offset, introducing a hole in the file ++ */ ++ torture_comment(tctx, "writing at varying offsets to create hole\n"); ++ status = smb2_util_write(tree, shandle, WRITE_PAYLOAD, 0, sizeof(WRITE_PAYLOAD)); ++ if (!NT_STATUS_IS_OK(status)) { ++ torture_comment(tctx, "Failed to write %zu bytes to " ++ "stream at offset 0\n", sizeof(canary)); ++ return false; ++ } ++ ++ status = smb2_util_write(tree, shandle, WRITE_PAYLOAD, ADS_OFF_TAIL, sizeof(WRITE_PAYLOAD)); ++ if (!NT_STATUS_IS_OK(status)) { ++ torture_comment(tctx, "Failed to write %zu bytes to " ++ "stream at offset 1018\n", sizeof(canary)); ++ return false; ++ } ++ ++ /* Now we'll read the stream contents */ ++ torture_comment(tctx, "Read stream data\n"); ++ ok = read_stream(tctx, tmp_ctx, tree, &shandle, 0, ADS_LEN, &data, &data_sz); ++ if (!ok) { ++ // torture assert already set ++ goto done; ++ } ++ ++ torture_assert_goto(tctx, data_sz == ADS_LEN, ret, done, "Short read on ADS\n"); ++ ++ /* Make sure our strings actually got written */ ++ if (strncmp(data, WRITE_PAYLOAD, sizeof(WRITE_PAYLOAD)) != 0) { ++ torture_result(tctx, TORTURE_FAIL, ++ "Payload write at beginning of file failed"); ++ ret = false; ++ goto done; ++ } ++ ++ if (strncmp(data + ADS_OFF_TAIL, WRITE_PAYLOAD, sizeof(WRITE_PAYLOAD)) != 0) { ++ torture_result(tctx, TORTURE_FAIL, ++ "Payload write at end of file failed"); ++ ret = false; ++ goto done; ++ } ++ ++ /* Now we'll check that the hole is full of null bytes */ ++ for (i = sizeof(WRITE_PAYLOAD); i < ADS_OFF_TAIL; i++) { ++ if (data[i] != '\0') { ++ torture_comment(tctx, "idx: %zu, got 0x%02x when expected 0x00\n", ++ i, (uint8_t)data[i]); ++ torture_result(tctx, TORTURE_FAIL, ++ "0x%08x: unexpected non-null byte in ADS read\n", ++ data[i]); ++ ret = false; ++ goto done; ++ } ++ } ++ ++done: ++ talloc_free(tmp_ctx); ++ ++ if (!smb2_util_handle_empty(shandle)) { ++ smb2_util_close(tree, shandle); ++ } ++ ++ smb2_deltree(tree, BASEDIR); ++ ++ return ret; ++} ++ ++/* ++ basic testing of vfs_streams_xattr ++*/ ++struct torture_suite *torture_vfs_streams_xattr(TALLOC_CTX *ctx) ++{ ++ struct torture_suite *suite = torture_suite_create(ctx, "streams_xattr"); ++ ++ torture_suite_add_1smb2_test(suite, "streams-pwrite-hole", test_streams_pwrite_hole); ++ ++ suite->description = talloc_strdup(suite, "vfs_streams_xattr tests"); ++ ++ return suite; ++} +diff --git a/source4/torture/vfs/vfs.c b/source4/torture/vfs/vfs.c +index 69da13f6d28..28e0b16ed95 100644 +--- a/source4/torture/vfs/vfs.c ++++ b/source4/torture/vfs/vfs.c +@@ -115,6 +115,7 @@ NTSTATUS torture_vfs_init(TALLOC_CTX *ctx) + torture_suite_add_suite(suite, torture_vfs_fruit_timemachine(suite)); + torture_suite_add_suite(suite, torture_vfs_fruit_conversion(suite)); + torture_suite_add_suite(suite, torture_vfs_fruit_unfruit(suite)); ++ torture_suite_add_suite(suite, torture_vfs_streams_xattr(suite)); + + torture_register_suite(ctx, suite); + +diff --git a/source4/torture/wscript_build b/source4/torture/wscript_build +index 6bfc6aeae65..57e006dcaf8 100644 +--- a/source4/torture/wscript_build ++++ b/source4/torture/wscript_build +@@ -305,7 +305,7 @@ bld.SAMBA_MODULE('TORTURE_NTP', + ) + + bld.SAMBA_MODULE('TORTURE_VFS', +- source='vfs/vfs.c vfs/fruit.c vfs/acl_xattr.c', ++ source='vfs/vfs.c vfs/fruit.c vfs/acl_xattr.c vfs/streams_xattr.c', + subsystem='smbtorture', + deps='LIBCLI_SMB TORTURE_UTIL smbclient-raw TORTURE_RAW', + internal_module=True, +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/CVE-2025-9640/s3-modules-vfs_streams_xattr-fix-unitialized-write.patch samba-4.17.12+dfsg/debian/patches/CVE-2025-9640/s3-modules-vfs_streams_xattr-fix-unitialized-write.patch --- samba-4.17.12+dfsg/debian/patches/CVE-2025-9640/s3-modules-vfs_streams_xattr-fix-unitialized-write.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/CVE-2025-9640/s3-modules-vfs_streams_xattr-fix-unitialized-write.patch 2025-11-30 08:35:04.000000000 +0000 @@ -0,0 +1,44 @@ +From: Andrew Walker +Date: Thu, 28 Aug 2025 19:36:19 +0000 +Subject: CVE-2025-9640: s3/modules/vfs_streams_xattr fix unitialized write + +This commit fixes a situation in which vfs_streams_xattr could +write unitialized memory into alternate data streams if the +user writes to an offset that is beyond the current end of file +to insert a hole in it. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15885 + +Signed-off-by: Andrew Walker +Reviewed-by: Volker Lendecke +--- + source3/modules/vfs_streams_xattr.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/source3/modules/vfs_streams_xattr.c b/source3/modules/vfs_streams_xattr.c +index f3371ca9b7e..5a419dbb705 100644 +--- a/source3/modules/vfs_streams_xattr.c ++++ b/source3/modules/vfs_streams_xattr.c +@@ -963,15 +963,18 @@ static ssize_t streams_xattr_pwrite(vfs_handle_struct *handle, + + if ((offset + n) > ea.value.length-1) { + uint8_t *tmp; ++ size_t new_sz = offset + n + 1; + + tmp = talloc_realloc(talloc_tos(), ea.value.data, uint8_t, +- offset + n + 1); ++ new_sz); + + if (tmp == NULL) { + TALLOC_FREE(ea.value.data); + errno = ENOMEM; + return -1; + } ++ ++ memset(tmp + ea.value.length, 0, new_sz - ea.value.length); + ea.value.data = tmp; + ea.value.length = offset + n + 1; + ea.value.data[offset+n] = 0; +-- +2.47.3 + diff -Nru samba-4.17.12+dfsg/debian/patches/series samba-4.17.12+dfsg/debian/patches/series --- samba-4.17.12+dfsg/debian/patches/series 2025-07-10 13:02:07.000000000 +0000 +++ samba-4.17.12+dfsg/debian/patches/series 2025-11-30 08:35:04.000000000 +0000 @@ -30,3 +30,17 @@ s3-winbindd-use-better-debug-messages-than-talloc_st.patch s3-winbindd-avoid-using-any-netlogon-call-to-get-a-d.patch s3-winbindd-Fix-internal-winbind-dsgetdcname-calls-w.patch +# CVE-2018-14628: Unprivileged read of deleted object tombstones in AD LDAP server +# https://gitlab.com/samba-team/lts-community/samba/-/merge_requests/3 +CVE-2018-14628/01-python-descriptor-add-get_deletedobjects_descriptor.patch +CVE-2018-14628/02-python-provision-make-DELETEDOBJECTS_DESCRIPTOR-availab.patch +CVE-2018-14628/03-s4-setup-set-the-correct-nTSecurityDescriptor-on-the-CN.patch +CVE-2018-14628/04-s4-dsdb-remove-unused-code-in-dirsync_filter_entry.patch +CVE-2018-14628/05-dbchecker-use-get_deletedobjects_descriptor-for-missing.patch +CVE-2018-14628/06-python-descriptor-let-samba-tool-dbch.patch +# CVE-2025-10230: Command injection via WINS server hook script +CVE-2025-10230/s4-tests-check-that-wins-hook-sanitizes-names.patch +CVE-2025-10230/s4-wins-restrict-names-fed-to-shell.patch +# CVE-2025-9640: Uninitialized memory disclosure via vfs_streams_xattr +CVE-2025-9640/Add-torture-test-for-inserting-hole-in-stream.patch +CVE-2025-9640/s3-modules-vfs_streams_xattr-fix-unitialized-write.patch