Version in base suite: 4.22.10+dfsg-0+deb13u1 Base version: samba_4.22.10+dfsg-0+deb13u1 Target version: samba_4.22.10+dfsg-0+deb13u2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/s/samba/samba_4.22.10+dfsg-0+deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/s/samba/samba_4.22.10+dfsg-0+deb13u2.dsc changelog | 28 patches/2026-jul-sec-update-bug-16039-v4-22-combined.patch | 1960 +++++++++++++ patches/series | 1 3 files changed, 1989 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpvx3tcz87/samba_4.22.10+dfsg-0+deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpvx3tcz87/samba_4.22.10+dfsg-0+deb13u2.dsc: no acceptable signature found diff -Nru samba-4.22.10+dfsg/debian/changelog samba-4.22.10+dfsg/debian/changelog --- samba-4.22.10+dfsg/debian/changelog 2026-05-26 12:46:55.000000000 +0000 +++ samba-4.22.10+dfsg/debian/changelog 2026-07-24 13:14:13.000000000 +0000 @@ -1,3 +1,31 @@ +samba (2:4.22.10+dfsg-0+deb13u2) trixie-security; urgency=medium + + * 2026-jul-sec-update-bug-16039-v4-22-combined.patch: + Jul-2026 samba security update addresses the following defects: + + CVE-2026-6949: https://bugzilla.samba.org/show_bug.cgi?id=16083 + TSIG packet with crafted name compression can crash internal DNS server + + CVE-2026-58224: https://bugzilla.samba.org/show_bug.cgi?id=16085 + CTDB: heap OOB read via unchecked packet length fields + + CVE-2026-58216: https://bugzilla.samba.org/show_bug.cgi?id=16087 + kpasswd service: 6-byte heap OOB read in packet parser + + CVE-2026-58218: https://bugzilla.samba.org/show_bug.cgi?id=16115 + DNS TKEY negotiation stores unauthenticated GSS contexts + in a fixed FIFO before authentication completes + + CVE-2026-58221: https://bugzilla.samba.org/show_bug.cgi?id=16147 + authenticated LDAP access to internal LDB special DNs + permits domain takeover + + CVE-2026-58222: https://bugzilla.samba.org/show_bug.cgi?id=16148 + LDAP Compare filter injection and trusted-request + confusion disclose protected attributes + + -- Michael Tokarev Fri, 24 Jul 2026 16:14:13 +0300 + samba (2:4.22.10+dfsg-0+deb13u1) trixie; urgency=medium * switch to actual upstream release for the May-2026 security fixes: diff -Nru samba-4.22.10+dfsg/debian/patches/2026-jul-sec-update-bug-16039-v4-22-combined.patch samba-4.22.10+dfsg/debian/patches/2026-jul-sec-update-bug-16039-v4-22-combined.patch --- samba-4.22.10+dfsg/debian/patches/2026-jul-sec-update-bug-16039-v4-22-combined.patch 1970-01-01 00:00:00.000000000 +0000 +++ samba-4.22.10+dfsg/debian/patches/2026-jul-sec-update-bug-16039-v4-22-combined.patch 2026-07-24 13:03:05.000000000 +0000 @@ -0,0 +1,1960 @@ +Date: Wed, 15 Jul 2026 18:42:00 +0000 +From: Björn Jacke +Subject: July 2026 samba security fixes for v4.22 +Origin: upstream, https://bugzilla.samba.org/show_bug.cgi?id=16039 +Forwarded: not-needed + +From ea7530366da502e0a116467e4565304603eba5b1 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 29 May 2026 12:43:13 +0200 +Subject: [PATCH 01/23] CVE-2026-6949: ndr_dns: let ndr_pull_dns_res_rec() + remember the start offset + +In order to verify TSIG signatures we need a reliable way to +truncate the original dns_name_packet buffer before the +last additional dns_res_rec. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16083 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + librpc/idl/dns.idl | 6 ++++++ + librpc/ndr/ndr_dns.c | 11 +++++++++++ + .../librpc/tests/dns-decode_dns_name_packet-hex.txt | 1 + + 3 files changed, 18 insertions(+) + +diff --git a/librpc/idl/dns.idl b/librpc/idl/dns.idl +index ec8668aa212..7cb316b2b12 100644 +--- a/librpc/idl/dns.idl ++++ b/librpc/idl/dns.idl +@@ -251,6 +251,12 @@ interface dns + } dns_rdata; + + typedef [flag(LIBNDR_PRINT_ARRAY_HEX|NDR_NOALIGN),nopush,nopull] struct { ++ /* ++ * This is the start offset of this ++ * dns_res_rec relative to the start of ++ * dns_name_packet buffer. ++ */ ++ [ignore] uint32 start_ndr_offset; + dns_string name; + dns_qtype rr_type; + dns_qclass rr_class; +diff --git a/librpc/ndr/ndr_dns.c b/librpc/ndr/ndr_dns.c +index 9cc54c1a972..f1de40fd7e8 100644 +--- a/librpc/ndr/ndr_dns.c ++++ b/librpc/ndr/ndr_dns.c +@@ -276,6 +276,17 @@ _PUBLIC_ enum ndr_err_code ndr_pull_dns_res_rec(struct ndr_pull *ndr, + ndr_set_flags(&ndr->flags, LIBNDR_PRINT_ARRAY_HEX | + LIBNDR_FLAG_NOALIGN); + if (ndr_flags & NDR_SCALARS) { ++ /* ++ * Remember the start offset in order ++ * to know how to truncate before the ++ * last dns_res_rec in order to do ++ * the correct TSIG calculation. ++ * ++ * Note that we just set LIBNDR_FLAG_NOALIGN ++ * above, so ndr_pull_align is a no-op. ++ */ ++ r->start_ndr_offset = ndr->offset; ++ + NDR_CHECK(ndr_pull_align(ndr, 4)); + NDR_CHECK(ndr_pull_dns_string(ndr, NDR_SCALARS, &r->name)); + NDR_CHECK(ndr_pull_dns_qtype(ndr, NDR_SCALARS, &r->rr_type)); +diff --git a/source4/librpc/tests/dns-decode_dns_name_packet-hex.txt b/source4/librpc/tests/dns-decode_dns_name_packet-hex.txt +index 84b55f2d362..ccc29c062f2 100644 +--- a/source4/librpc/tests/dns-decode_dns_name_packet-hex.txt ++++ b/source4/librpc/tests/dns-decode_dns_name_packet-hex.txt +@@ -21,6 +21,7 @@ pull returned Success + answers: ARRAY(0) + nsrecs: ARRAY(1) + nsrecs: struct dns_res_rec ++ start_ndr_offset : 0x00000027 (39) + name : 'cnamedotprefix0.samba2003.example.com' + rr_type : DNS_QTYPE_CNAME (0x5) + rr_class : DNS_QCLASS_IN (0x1) +-- +2.43.0 + + +From f7f4d096aedb236ccf026ed3c59c7538e5303830 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 29 May 2026 13:12:20 +0200 +Subject: [PATCH 02/23] CVE-2026-6949: s4:dns_server: correctly truncate the + buffer for TSIG verification + +Calculating the length of the TSIG additional dns_res_rec, +via ndr_push_dns_res_rec() is fragile and may generate +a buffer larger than the original dns_name_packet buffer. + +This could underflow the resulting packet_len, +to a very large value and buffer_len to a small value. + +Resulting in a memcpy() of a very large size into +a very small buffer. This most likely already +gets a segmentation fault when reading after the +in->data. + +This was reported by Arjun Basnet with Securin Labs. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16083 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source4/dns_server/dns_crypto.c | 44 ++++++++++++++++++++++++--------- + 1 file changed, 32 insertions(+), 12 deletions(-) + +diff --git a/source4/dns_server/dns_crypto.c b/source4/dns_server/dns_crypto.c +index d30e9715086..722213bfd0a 100644 +--- a/source4/dns_server/dns_crypto.c ++++ b/source4/dns_server/dns_crypto.c +@@ -36,6 +36,8 @@ static WERROR dns_copy_tsig(TALLOC_CTX *mem_ctx, + struct dns_res_rec *old, + struct dns_res_rec *new_rec) + { ++ new_rec->start_ndr_offset = 0; ++ + new_rec->name = talloc_strdup(mem_ctx, old->name); + W_ERROR_HAVE_NO_MEMORY(new_rec->name); + +@@ -101,7 +103,7 @@ WERROR dns_verify_tsig(struct dns_server *dns, + NTSTATUS status; + enum ndr_err_code ndr_err; + uint16_t i, arcount = 0; +- DATA_BLOB tsig_blob, fake_tsig_blob, sig; ++ DATA_BLOB fake_tsig_blob, sig; + uint8_t *buffer = NULL; + size_t buffer_len = 0, packet_len = 0; + struct dns_server_tkey *tkey = NULL; +@@ -131,6 +133,22 @@ WERROR dns_verify_tsig(struct dns_server *dns, + state->sign = true; + DBG_DEBUG("Got TSIG\n"); + ++ /* ++ * We need to keep the input packet exactly like we got it, ++ * but we need to cut off the tsig record. ++ * ++ * DNS packets can not be larger than UINT16_MAX, ++ * the size of the UDP payload is uint16_t and ++ * there's a uint16_t length header for TCP. ++ * ++ * And the start offset of the last ++ * additional record can't be larger than ++ * the whole packet. ++ */ ++ packet_len = packet->additional[i].start_ndr_offset; ++ SMB_ASSERT(in->length <= UINT16_MAX); ++ SMB_ASSERT(in->length > packet_len); ++ + state->tsig = talloc_zero(state->mem_ctx, struct dns_res_rec); + if (state->tsig == NULL) { + return WERR_NOT_ENOUGH_MEMORY; +@@ -205,14 +223,6 @@ WERROR dns_verify_tsig(struct dns_server *dns, + check_rec->other_size = 0; + check_rec->other_data = NULL; + +- ndr_err = ndr_push_struct_blob(&tsig_blob, mem_ctx, state->tsig, +- (ndr_push_flags_fn_t)ndr_push_dns_res_rec); +- if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { +- DEBUG(1, ("Failed to push packet: %s!\n", +- ndr_errstr(ndr_err))); +- return DNS_ERR(SERVER_FAILURE); +- } +- + ndr_err = ndr_push_struct_blob(&fake_tsig_blob, mem_ctx, check_rec, + (ndr_push_flags_fn_t)ndr_push_dns_fake_tsig_rec); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { +@@ -221,9 +231,19 @@ WERROR dns_verify_tsig(struct dns_server *dns, + return DNS_ERR(SERVER_FAILURE); + } + +- /* we need to work some magic here. we need to keep the input packet +- * exactly like we got it, but we need to cut off the tsig record */ +- packet_len = in->length - tsig_blob.length; ++ /* ++ * We already asserted packet_len < UINT16_MAX ++ * above, and struct ndr_push has alloc_size and ++ * offset as uint32_t, so it's really unlikely ++ * to overflow buffer_len. For SIZE_MAX == UINT32_MAX, ++ * the following check might be important, but ++ * just lets do it always. ++ */ ++ if (fake_tsig_blob.length > (SIZE_MAX - UINT16_MAX)) { ++ DBG_WARNING("fake_tsig_blob.length=%zu too large!\n", ++ fake_tsig_blob.length); ++ return DNS_ERR(SERVER_FAILURE); ++ } + buffer_len = packet_len + fake_tsig_blob.length; + buffer = talloc_zero_array(mem_ctx, uint8_t, buffer_len); + if (buffer == NULL) { +-- +2.43.0 + + +From f252a17bc8ae7568943b0f4cf526d1f8555d6429 Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Wed, 27 May 2026 18:59:24 +1000 +Subject: [PATCH 03/23] CVE-2026-58224: ctdb-daemon: Avoid out of bounds data access + +Protocol field lengths need to be validated to avoid attempts to +access memory beyond the end of the packet buffer. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Reported-by: Tristan Madani +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/server/ctdb_call.c | 38 ++++++++++++++++++++++++++++++++++++++ + ctdb/server/ctdb_client.c | 9 +++++++++ + ctdb/server/ctdb_control.c | 19 +++++++++++++++++++ + 3 files changed, 66 insertions(+) + +diff --git a/ctdb/server/ctdb_call.c b/ctdb/server/ctdb_call.c +index a51a92d3e45..cfeca78061c 100644 +--- a/ctdb/server/ctdb_call.c ++++ b/ctdb/server/ctdb_call.c +@@ -583,6 +583,9 @@ static int dmaster_defer_add(struct ctdb_db_context *ctdb_db, + void ctdb_request_dmaster(struct ctdb_context *ctdb, struct ctdb_req_header *hdr) + { + struct ctdb_req_dmaster_old *c = (struct ctdb_req_dmaster_old *)hdr; ++ size_t req_dmaster_header_len = offsetof(struct ctdb_req_dmaster_old, ++ data); ++ size_t buf_len = hdr->length; + TDB_DATA key, data, data2; + struct ctdb_ltdb_header header; + struct ctdb_db_context *ctdb_db; +@@ -590,6 +593,13 @@ void ctdb_request_dmaster(struct ctdb_context *ctdb, struct ctdb_req_header *hdr + size_t len; + int ret; + ++ if (buf_len < req_dmaster_header_len || ++ c->keylen > buf_len - req_dmaster_header_len || ++ c->datalen > buf_len - req_dmaster_header_len - c->keylen) { ++ DBG_WARNING("Invalid packet\n"); ++ return; ++ } ++ + key.dptr = c->data; + key.dsize = c->keylen; + data.dptr = c->data + c->keylen; +@@ -917,6 +927,8 @@ sort_keys: + void ctdb_request_call(struct ctdb_context *ctdb, struct ctdb_req_header *hdr) + { + struct ctdb_req_call_old *c = (struct ctdb_req_call_old *)hdr; ++ size_t req_call_header_len = offsetof(struct ctdb_req_call_old, data); ++ size_t buf_len = hdr->length; + TDB_DATA data; + struct ctdb_reply_call_old *r; + int ret, len; +@@ -925,6 +937,13 @@ void ctdb_request_call(struct ctdb_context *ctdb, struct ctdb_req_header *hdr) + struct ctdb_db_context *ctdb_db; + int tmp_count, bucket; + ++ if (buf_len < req_call_header_len || ++ c->keylen > buf_len - req_call_header_len || ++ c->calldatalen > buf_len - req_call_header_len - c->keylen) { ++ DBG_WARNING("Invalid packet\n"); ++ return; ++ } ++ + if (ctdb->methods == NULL) { + DEBUG(DEBUG_INFO,(__location__ " Failed ctdb_request_call. Transport is DOWN\n")); + return; +@@ -1199,8 +1218,17 @@ void ctdb_request_call(struct ctdb_context *ctdb, struct ctdb_req_header *hdr) + void ctdb_reply_call(struct ctdb_context *ctdb, struct ctdb_req_header *hdr) + { + struct ctdb_reply_call_old *c = (struct ctdb_reply_call_old *)hdr; ++ size_t reply_call_header_len = offsetof(struct ctdb_reply_call_old, ++ data); ++ size_t buf_len = hdr->length; + struct ctdb_call_state *state; + ++ if (buf_len < reply_call_header_len || ++ c->datalen > buf_len - reply_call_header_len) { ++ DBG_WARNING("Invalid packet\n"); ++ return; ++ } ++ + state = reqid_find(ctdb->idr, hdr->reqid, struct ctdb_call_state); + if (state == NULL) { + DEBUG(DEBUG_ERR, (__location__ " reqid %u not found\n", hdr->reqid)); +@@ -1296,12 +1324,22 @@ finished_ro: + void ctdb_reply_dmaster(struct ctdb_context *ctdb, struct ctdb_req_header *hdr) + { + struct ctdb_reply_dmaster_old *c = (struct ctdb_reply_dmaster_old *)hdr; ++ size_t reply_dmaster_header_len = offsetof(struct ctdb_reply_dmaster_old, ++ data); ++ size_t buf_len = hdr->length; + struct ctdb_db_context *ctdb_db; + TDB_DATA key, data; + uint32_t record_flags = 0; + size_t len; + int ret; + ++ if (buf_len < reply_dmaster_header_len || ++ c->keylen > buf_len - reply_dmaster_header_len || ++ c->datalen > buf_len - reply_dmaster_header_len - c->keylen) { ++ DBG_WARNING("Invalid packet\n"); ++ return; ++ } ++ + ctdb_db = find_ctdb_db(ctdb, c->db_id); + if (ctdb_db == NULL) { + DEBUG(DEBUG_ERR,("Unknown db_id 0x%x in ctdb_reply_dmaster\n", c->db_id)); +diff --git a/ctdb/server/ctdb_client.c b/ctdb/server/ctdb_client.c +index c9edb1d554c..84e8e3d2da6 100644 +--- a/ctdb/server/ctdb_client.c ++++ b/ctdb/server/ctdb_client.c +@@ -193,8 +193,17 @@ void ctdb_request_message(struct ctdb_context *ctdb, + struct ctdb_req_header *hdr) + { + struct ctdb_req_message_old *c = (struct ctdb_req_message_old *)hdr; ++ size_t req_message_header_len = offsetof(struct ctdb_req_message_old, ++ data); ++ size_t buf_len = hdr->length; + TDB_DATA data; + ++ if (buf_len < req_message_header_len || ++ c->datalen > buf_len - req_message_header_len) { ++ DBG_WARNING("Invalid packet\n"); ++ return; ++ } ++ + data.dsize = c->datalen; + data.dptr = talloc_memdup(c, &c->data[0], c->datalen); + if (data.dptr == NULL) { +diff --git a/ctdb/server/ctdb_control.c b/ctdb/server/ctdb_control.c +index a51795f340a..cfd80330c41 100644 +--- a/ctdb/server/ctdb_control.c ++++ b/ctdb/server/ctdb_control.c +@@ -933,11 +933,20 @@ void ctdb_request_control_reply(struct ctdb_context *ctdb, struct ctdb_req_contr + void ctdb_request_control(struct ctdb_context *ctdb, struct ctdb_req_header *hdr) + { + struct ctdb_req_control_old *c = (struct ctdb_req_control_old *)hdr; ++ size_t req_control_header_len = offsetof(struct ctdb_req_control_old, ++ data); ++ size_t buf_len = hdr->length; + TDB_DATA data, *outdata; + int32_t status; + bool async_reply = false; + const char *errormsg = NULL; + ++ if (buf_len < req_control_header_len || ++ c->datalen > buf_len - req_control_header_len) { ++ DBG_WARNING("Invalid packet\n"); ++ return; ++ } ++ + data.dptr = &c->data[0]; + data.dsize = c->datalen; + +@@ -957,10 +966,20 @@ void ctdb_request_control(struct ctdb_context *ctdb, struct ctdb_req_header *hdr + void ctdb_reply_control(struct ctdb_context *ctdb, struct ctdb_req_header *hdr) + { + struct ctdb_reply_control_old *c = (struct ctdb_reply_control_old *)hdr; ++ size_t reply_control_header_len = offsetof(struct ctdb_reply_control_old, ++ data); ++ size_t buf_len = hdr->length; + TDB_DATA data; + struct ctdb_control_state *state; + const char *errormsg = NULL; + ++ if (buf_len < reply_control_header_len || ++ c->datalen > buf_len - reply_control_header_len || ++ c->errorlen > buf_len - reply_control_header_len - c->datalen) { ++ DBG_WARNING("Invalid packet\n"); ++ return; ++ } ++ + state = reqid_find(ctdb->idr, hdr->reqid, struct ctdb_control_state); + if (state == NULL) { + DEBUG(DEBUG_ERR,("pnn %u Invalid reqid %u in ctdb_reply_control\n", +-- +2.43.0 + + +From 38a62cf5f72495b99f10b74339c6f895504469aa Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Sat, 30 May 2026 14:38:02 +1000 +Subject: [PATCH 04/23] CVE-2026-58224: ctdb-protocol: Avoid DoS memory allocation + +The pull loop already avoids out of bounds accesses beyond the end of +the buffer. However, it does not avoid a DoS memory allocation due to +an unreasonably large array size. + +Check that the number of specified array elements can be pulled from +buffer, which puts a reasonable upper bound on the subsequent memory +allocation. + +Use an initialised dummy variable to avoid static analysers +complaining about uninitialised variables being passed. Variable i +could be reused but that might be confusing, so leave any optimisation +to the compiler. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Reported-by: Martin Schwenke +Reported-by: Also Andrew Tridgell (issue 22) +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/protocol/protocol_types.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/ctdb/protocol/protocol_types.c b/ctdb/protocol/protocol_types.c +index 0eb1923207e..86e1bc3954e 100644 +--- a/ctdb/protocol/protocol_types.c ++++ b/ctdb/protocol/protocol_types.c +@@ -925,6 +925,7 @@ int ctdb_vnn_map_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + struct ctdb_vnn_map *val; + size_t offset = 0, np; + uint32_t i; ++ uint32_t dummy = 0; + int ret; + + val = talloc(mem_ctx, struct ctdb_vnn_map); +@@ -950,6 +951,11 @@ int ctdb_vnn_map_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->size * ctdb_uint32_len(&dummy) > buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->map = talloc_array(val, uint32_t, val->size); + if (val->map == NULL) { + ret = ENOMEM; +-- +2.43.0 + + +From 00dda8a31a045c242980c095d02f902d2ee3acc1 Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Sat, 30 May 2026 14:25:00 +1000 +Subject: [PATCH 05/23] CVE-2026-58224: ctdb-daemon: Avoid out-of-bounds data access + +Do not allow the VNN map's size to extend past the end of the buffer. +This is checked by switching to ctdb_vnn_map_pull(), which also +simplifies the code. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/server/ctdb_recover.c | 32 +++++++++++++++++++++----------- + 1 file changed, 21 insertions(+), 11 deletions(-) + +diff --git a/ctdb/server/ctdb_recover.c b/ctdb/server/ctdb_recover.c +index 18dc250f5ce..506b0989799 100644 +--- a/ctdb/server/ctdb_recover.c ++++ b/ctdb/server/ctdb_recover.c +@@ -36,6 +36,8 @@ + #include "ctdb_private.h" + #include "ctdb_client.h" + ++#include "protocol/protocol_private.h" ++ + #include "common/system.h" + #include "common/common.h" + #include "common/logging.h" +@@ -67,24 +69,32 @@ ctdb_control_getvnnmap(struct ctdb_context *ctdb, uint32_t opcode, TDB_DATA inda + int + ctdb_control_setvnnmap(struct ctdb_context *ctdb, uint32_t opcode, TDB_DATA indata, TDB_DATA *outdata) + { +- struct ctdb_vnn_map_wire *map = (struct ctdb_vnn_map_wire *)indata.dptr; ++ struct ctdb_vnn_map *new = NULL; ++ size_t npull = 0; ++ int ret = 0; + + if (ctdb->recovery_mode != CTDB_RECOVERY_ACTIVE) { + DEBUG(DEBUG_ERR, ("Attempt to set vnnmap when not in recovery\n")); + return -1; + } + ++ ret = ctdb_vnn_map_pull(indata.dptr, indata.dsize, ctdb, &new, &npull); ++ if (ret != 0) { ++ switch (ret) { ++ case ENOMEM: ++ DBG_ERR("Memory allocation error\n"); ++ break; ++ case EMSGSIZE: ++ DBG_ERR("Invalid packet\n"); ++ break; ++ default: ++ DBG_ERR("Unexpected error (%d)\n", ret); ++ } ++ return -1; ++ } ++ + talloc_free(ctdb->vnn_map); +- +- ctdb->vnn_map = talloc(ctdb, struct ctdb_vnn_map); +- CTDB_NO_MEMORY(ctdb, ctdb->vnn_map); +- +- ctdb->vnn_map->generation = map->generation; +- ctdb->vnn_map->size = map->size; +- ctdb->vnn_map->map = talloc_array(ctdb->vnn_map, uint32_t, map->size); +- CTDB_NO_MEMORY(ctdb, ctdb->vnn_map->map); +- +- memcpy(ctdb->vnn_map->map, map->map, sizeof(uint32_t)*map->size); ++ ctdb->vnn_map = new; + + return 0; + } +-- +2.43.0 + + +From 817f926ee93f5a5fc536e676551eb9fc3edb80e0 Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Sat, 30 May 2026 15:04:31 +1000 +Subject: [PATCH 06/23] CVE-2026-58224: ctdb-daemon: Avoid out of bounds data access + +If a NUL terminator doesn't appear in the buffer then the database +name is not a valid string. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/server/ctdb_ltdb_server.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/ctdb/server/ctdb_ltdb_server.c b/ctdb/server/ctdb_ltdb_server.c +index 8d03062b97a..2433b39b836 100644 +--- a/ctdb/server/ctdb_ltdb_server.c ++++ b/ctdb/server/ctdb_ltdb_server.c +@@ -1109,8 +1109,15 @@ int32_t ctdb_control_db_attach(struct ctdb_context *ctdb, + struct ctdb_db_context *db; + struct ctdb_node *node = ctdb->nodes[ctdb->pnn]; + struct ctdb_client *client = NULL; ++ char *t = NULL; + uint32_t opcode; + ++ t = memchr(indata.dptr, '\0', indata.dsize); ++ if (t == NULL) { ++ DBG_ERR("Invalid packet\n"); ++ return -1; ++ } ++ + if (ctdb->tunable.allow_client_db_attach == 0) { + DEBUG(DEBUG_ERR, ("DB Attach to database %s denied by tunable " + "AllowClientDBAccess == 0\n", db_name)); +-- +2.43.0 + + +From aebf88cc3f73574112a41b4aeaa6745bf735816a Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Sat, 30 May 2026 17:35:23 +1000 +Subject: [PATCH 07/23] CVE-2026-58224: ctdb-daemon: Avoid out of bounds data access + +The count can't exceed the recdata buffer size. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/server/ctdb_update_record.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/ctdb/server/ctdb_update_record.c b/ctdb/server/ctdb_update_record.c +index 405499c81e2..154b1427410 100644 +--- a/ctdb/server/ctdb_update_record.c ++++ b/ctdb/server/ctdb_update_record.c +@@ -318,6 +318,16 @@ int32_t ctdb_control_update_record(struct ctdb_context *ctdb, + struct childwrite_handle *handle; + struct ctdb_marshall_buffer *m = (struct ctdb_marshall_buffer *)recdata.dptr; + ++ if (recdata.dsize < offsetof(struct ctdb_marshall_buffer, data)) { ++ DBG_ERR("Invalid packet\n"); ++ return -1; ++ } ++ if (m->count > ++ recdata.dsize - offsetof(struct ctdb_marshall_buffer, data)) { ++ DBG_ERR("Invalid packet\n"); ++ return -1; ++ } ++ + if (ctdb->recovery_mode != CTDB_RECOVERY_NORMAL) { + DEBUG(DEBUG_INFO,("rejecting ctdb_control_update_record when recovery active\n")); + return -1; +-- +2.43.0 + + +From 424c42226fa2deac56f5df8fd9b114b6ab0b144e Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Sat, 30 May 2026 17:45:56 +1000 +Subject: [PATCH 08/23] CVE-2026-58224: ctdb-daemon: Avoid out of bounds data access + +The count can't exceed the indata buffer size. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/server/ctdb_recover.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/ctdb/server/ctdb_recover.c b/ctdb/server/ctdb_recover.c +index 506b0989799..b74dca58c8d 100644 +--- a/ctdb/server/ctdb_recover.c ++++ b/ctdb/server/ctdb_recover.c +@@ -1062,6 +1062,12 @@ int32_t ctdb_control_try_delete_records(struct ctdb_context *ctdb, TDB_DATA inda + return -1; + } + ++ if (reply->count > ++ indata.dsize - offsetof(struct ctdb_marshall_buffer, data)) { ++ DBG_ERR("Invalid packet\n"); ++ return -1; ++ } ++ + ctdb_db = find_ctdb_db(ctdb, reply->db_id); + if (!ctdb_db) { + DEBUG(DEBUG_ERR,(__location__ " Unknown db 0x%08x\n", reply->db_id)); +-- +2.43.0 + + +From ed3e723587920dc3c955f098c0e1a00f316835e4 Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Sun, 31 May 2026 15:11:39 +1000 +Subject: [PATCH 09/23] CVE-2026-58224: ctdb-daemon: Avoid out of bounds data access + +The first check is clearly needed because m->db_id is referenced. The +second check is handled by a similar update to +ctdb_control_update_record(), but repeat it in case something else +changes. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/server/ctdb_persistent.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/ctdb/server/ctdb_persistent.c b/ctdb/server/ctdb_persistent.c +index 26717441d17..1b0d1411553 100644 +--- a/ctdb/server/ctdb_persistent.c ++++ b/ctdb/server/ctdb_persistent.c +@@ -182,6 +182,16 @@ int32_t ctdb_control_trans3_commit(struct ctdb_context *ctdb, + struct ctdb_marshall_buffer *m = (struct ctdb_marshall_buffer *)recdata.dptr; + struct ctdb_db_context *ctdb_db; + ++ if (recdata.dsize < offsetof(struct ctdb_marshall_buffer, data)) { ++ DBG_ERR("Invalid packet\n"); ++ return -1; ++ } ++ if (m->count > ++ recdata.dsize - offsetof(struct ctdb_marshall_buffer, data)) { ++ DBG_ERR("Invalid packet\n"); ++ return -1; ++ } ++ + if (ctdb->recovery_mode != CTDB_RECOVERY_NORMAL) { + DEBUG(DEBUG_INFO,("rejecting ctdb_control_trans3_commit when recovery active\n")); + return -1; +-- +2.43.0 + + +From 6a6cfa25148a39d024f4e868029db420a36a2b61 Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Tue, 9 Jun 2026 10:41:25 +1000 +Subject: [PATCH 10/23] CVE-2026-58224: ctdb-daemon: Avoid out of bounds data access + +Instead of checking only that there is enough data for the length +field, check there is enough data for the entire header part of the +struct. After cross-checking overall lengths, ensure there is enough +data for the key/data in the data element. + +While here, modernise the DEBUG. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Reported-by: Andrew Tridgell (issue 13) +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/server/ctdb_traverse.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/ctdb/server/ctdb_traverse.c b/ctdb/server/ctdb_traverse.c +index 4865dcc94f0..094f824bf9d 100644 +--- a/ctdb/server/ctdb_traverse.c ++++ b/ctdb/server/ctdb_traverse.c +@@ -561,13 +561,17 @@ int32_t ctdb_control_traverse_all(struct ctdb_context *ctdb, TDB_DATA data, TDB_ + int32_t ctdb_control_traverse_data(struct ctdb_context *ctdb, TDB_DATA data, TDB_DATA *outdata) + { + struct ctdb_rec_data_old *d = (struct ctdb_rec_data_old *)data.dptr; ++ size_t rec_data_header_len = offsetof(struct ctdb_rec_data_old, data); + struct ctdb_traverse_all_handle *state; + TDB_DATA key; + ctdb_traverse_fn_t callback; + void *private_data; + +- if (data.dsize < sizeof(uint32_t) || data.dsize != d->length) { +- DEBUG(DEBUG_ERR,("Bad record size in ctdb_control_traverse_data\n")); ++ if (data.dsize < rec_data_header_len || ++ data.dsize != d->length || ++ d->keylen > d->length - rec_data_header_len || ++ d->datalen > d->length - rec_data_header_len - d->keylen) { ++ DBG_ERR("Invalid packet\n"); + return -1; + } + +-- +2.43.0 + + +From bbf7f1c64c31f50a4fceafee40315c2a6605e30d Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Tue, 9 Jun 2026 21:48:02 +1000 +Subject: [PATCH 11/23] CVE-2026-58224: ctdb-protocol: Avoid off-by-one error for bytes pulled + +As per the comment, if there is no NUL byte in the buffer then don't +count one in the number of bytes pulled. + +Note that this is unlikely to be a security issue because it would +take a protocol bug elsewhere to overrun the buffer. However, include +this fix here for posterity. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Reported-by: Andrew Tridgell (issue 16) +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/protocol/protocol_basic.c | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/ctdb/protocol/protocol_basic.c b/ctdb/protocol/protocol_basic.c +index 42f207790d0..9cc52def644 100644 +--- a/ctdb/protocol/protocol_basic.c ++++ b/ctdb/protocol/protocol_basic.c +@@ -247,7 +247,8 @@ void ctdb_string_push(const char **in, uint8_t *buf, size_t *npush) + int ctdb_string_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + const char **out, size_t *npull) + { +- const char *str; ++ const char *str = NULL; ++ size_t len = 0; + + if (buflen > UINT32_MAX) { + return EMSGSIZE; +@@ -265,7 +266,14 @@ int ctdb_string_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + } + + *out = str; +- *npull = ctdb_string_len(&str); ++ /* ++ * Avoid claiming to have consumed more than buflen. ++ * ctdb_string_len() returns buflen + 1 if there is no ++ * NUL-terminator within buflen, so no NUL was actually ++ * consumed (so no +1 needed). ++ */ ++ len = ctdb_string_len(&str); ++ *npull = MIN(buflen, len); + return 0; + } + +-- +2.43.0 + + +From 623cbadd46043ba6352d61a1fa9d8c17deb2b178 Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Tue, 9 Jun 2026 21:54:20 +1000 +Subject: [PATCH 12/23] CVE-2026-58224: ctdb-protocol: Always pull the specified number of + bytes + +The string should not contain a premature NUL terminator, which would +cause less than the specified number of bytes to be pulled. If it +does, consume the specified number of bytes anyway. The alternative +doesn't make sense. + +Note that this is unlikely to be a security issue, where trailing data +in the string field causes the buffer to be overrun. That would +require an additional protocol bug. However, include this fix here +for posterity. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/protocol/protocol_basic.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/ctdb/protocol/protocol_basic.c b/ctdb/protocol/protocol_basic.c +index 9cc52def644..750ea8f9fe7 100644 +--- a/ctdb/protocol/protocol_basic.c ++++ b/ctdb/protocol/protocol_basic.c +@@ -319,7 +319,8 @@ int ctdb_stringn_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + if (ret != 0) { + return ret; + } +- offset += np; ++ /* Always consume the specified number bytes */ ++ offset += u32; + + *npull = offset; + return 0; +-- +2.43.0 + + +From 0fa6bb4f3edfbe819ed15555c2c64fd66f3077ad Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Wed, 10 Jun 2026 12:15:17 +1000 +Subject: [PATCH 13/23] CVE-2026-58224: ctdb-protocol: Avoid DoS memory allocations + +The pull loop already avoids out of bounds accesses beyond the end of +the buffer. However, it does not avoid a DoS memory allocation due to +an unreasonably large array size. + +Check that the number of specified array elements can be pulled from +buffer, which puts a reasonable upper bound on the subsequent memory +allocation. + +Use an initialised dummy variable to avoid static analysers +complaining about uninitialised variables being passed. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Reported-by: Andrew Tridgell (issue 22) +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/protocol/protocol_types.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/ctdb/protocol/protocol_types.c b/ctdb/protocol/protocol_types.c +index 86e1bc3954e..2edf8228d18 100644 +--- a/ctdb/protocol/protocol_types.c ++++ b/ctdb/protocol/protocol_types.c +@@ -838,6 +838,7 @@ int ctdb_statistics_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + size_t *npull) + { + struct ctdb_statistics_list *val; ++ struct ctdb_statistics dummy = {}; + size_t offset = 0, np; + int ret, i; + +@@ -863,6 +864,11 @@ int ctdb_statistics_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->num * ctdb_statistics_len(&dummy) > buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->stats = talloc_array(val, struct ctdb_statistics, val->num); + if (val->stats == NULL) { + ret = ENOMEM; +@@ -1088,6 +1094,7 @@ int ctdb_dbid_map_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + struct ctdb_dbid_map **out, size_t *npull) + { + struct ctdb_dbid_map *val; ++ struct ctdb_dbid dummy = {}; + size_t offset = 0, np; + uint32_t i; + int ret; +@@ -1108,6 +1115,11 @@ int ctdb_dbid_map_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->num * ctdb_dbid_len(&dummy) > buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->dbs = talloc_array(val, struct ctdb_dbid, val->num); + if (val->dbs == NULL) { + ret = ENOMEM; +@@ -3905,6 +3917,7 @@ int ctdb_node_map_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + struct ctdb_node_map **out, size_t *npull) + { + struct ctdb_node_map *val; ++ struct ctdb_node_and_flags dummy = {}; + size_t offset = 0, np; + uint32_t i; + int ret; +@@ -3925,6 +3938,12 @@ int ctdb_node_map_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->num * ctdb_node_and_flags_len(&dummy) > ++ buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->node = talloc_array(val, struct ctdb_node_and_flags, val->num); + if (val->node == NULL) { + ret = ENOMEM; +-- +2.43.0 + + +From 0e50b8600d491fd4001c20aecccf3abe1ea199ac Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Wed, 10 Jun 2026 12:37:02 +1000 +Subject: [PATCH 14/23] CVE-2026-58224: ctdb-protocol: Avoid DoS memory allocations + +The pull loop already avoids out of bounds accesses beyond the end of +the buffer. However, it does not avoid a DoS memory allocation due to +an unreasonably large array size. + +Check that the number of specified array elements can be pulled from +buffer, which puts a reasonable upper bound on the subsequent memory +allocation. + +Use an initialised dummy variable to avoid static analysers +complaining about uninitialised variables being passed. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/protocol/protocol_types.c | 35 ++++++++++++++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + +diff --git a/ctdb/protocol/protocol_types.c b/ctdb/protocol/protocol_types.c +index 2edf8228d18..8cbdaee5106 100644 +--- a/ctdb/protocol/protocol_types.c ++++ b/ctdb/protocol/protocol_types.c +@@ -2380,6 +2380,7 @@ int ctdb_connection_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + struct ctdb_connection_list **out, size_t *npull) + { + struct ctdb_connection_list *val; ++ struct ctdb_connection dummy = {}; + size_t offset = 0, np; + uint32_t i; + int ret; +@@ -2400,6 +2401,11 @@ int ctdb_connection_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->num * ctdb_connection_len(&dummy) > buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->conn = talloc_array(val, struct ctdb_connection, val->num); + if (val->conn == NULL) { + ret = ENOMEM; +@@ -3417,6 +3423,7 @@ int ctdb_tickle_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + struct ctdb_tickle_list **out, size_t *npull) + { + struct ctdb_tickle_list *val; ++ struct ctdb_connection dummy = {}; + size_t offset = 0, np; + uint32_t i; + int ret; +@@ -3444,6 +3451,11 @@ int ctdb_tickle_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->num * ctdb_connection_len(&dummy) > buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->conn = talloc_array(val, struct ctdb_connection, val->num); + if (val->conn == NULL) { + ret = ENOMEM; +@@ -3762,6 +3774,7 @@ int ctdb_public_ip_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list **out, size_t *npull) + { + struct ctdb_public_ip_list *val; ++ struct ctdb_public_ip dummy = {}; + size_t offset = 0, np; + uint32_t i; + int ret; +@@ -3782,6 +3795,11 @@ int ctdb_public_ip_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->num * ctdb_public_ip_len(&dummy) > buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->ip = talloc_array(val, struct ctdb_public_ip, val->num); + if (val->ip == NULL) { + ret = ENOMEM; +@@ -4124,6 +4142,7 @@ int ctdb_script_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + struct ctdb_script_list **out, size_t *npull) + { + struct ctdb_script_list *val; ++ struct ctdb_script dummy = {}; + size_t offset = 0, np; + uint32_t i; + int ret; +@@ -4157,6 +4176,12 @@ int ctdb_script_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->num_scripts * ctdb_script_len(&dummy) ++ > buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->script = talloc_array(val, struct ctdb_script, val->num_scripts); + if (val->script == NULL) { + ret = ENOMEM; +@@ -4400,6 +4425,7 @@ int ctdb_iface_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + struct ctdb_iface_list **out, size_t *npull) + { + struct ctdb_iface_list *val; ++ struct ctdb_iface dummy = {}; + size_t offset = 0, np; + uint32_t i; + int ret; +@@ -4420,6 +4446,11 @@ int ctdb_iface_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + goto done; + } + ++ if ((uint64_t)val->num * ctdb_iface_len(&dummy) > buflen - offset) { ++ ret = EMSGSIZE; ++ goto fail; ++ } ++ + val->iface = talloc_array(val, struct ctdb_iface, val->num); + if (val->iface == NULL) { + ret = ENOMEM; +@@ -5347,6 +5378,10 @@ int ctdb_g_lock_list_pull(uint8_t *buf, size_t buflen, TALLOC_CTX *mem_ctx, + + val->num = buflen / ctdb_g_lock_len(&lock); + ++ /* ++ * No array count pre-check needed because val->num is ++ * calculated from buflen ++ */ + val->lock = talloc_array(val, struct ctdb_g_lock, val->num); + if (val->lock == NULL) { + ret = ENOMEM; +-- +2.43.0 + + +From 3f0c7161989b2adbbb00ce26838097e26ed6c09a Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Thu, 11 Jun 2026 11:25:52 +1000 +Subject: [PATCH 15/23] CVE-2026-58224: ctdb-common: Secure sock_daemon Unix domain sockets + +Currently, the mode of the socket depends on the creating process's +umask. This might allow unwanted access. + +It might be preferable to do this just for the eventd socket. +However, there is no useful place to hook this in outside of +sock_daemon. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/common/sock_daemon.c | 26 ++++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/ctdb/common/sock_daemon.c b/ctdb/common/sock_daemon.c +index e31a36445b5..ed0ccfd70ef 100644 +--- a/ctdb/common/sock_daemon.c ++++ b/ctdb/common/sock_daemon.c +@@ -204,6 +204,8 @@ static int sock_client_context_destructor( + static int socket_setup(const char *sockpath, bool remove_before_use) + { + struct sockaddr_un addr; ++ const char *t; ++ bool test_mode_enabled = false; + size_t len; + int ret, fd; + +@@ -240,6 +242,30 @@ static int socket_setup(const char *sockpath, bool remove_before_use) + return -1; + } + ++ t = getenv("CTDB_TEST_MODE"); ++ if (t != NULL) { ++ test_mode_enabled = true; ++ } ++ ++ if (!test_mode_enabled) { ++ /* Behaviour of fchown(2) is undefined on sockets */ ++ ret = chown(sockpath, geteuid(), getegid()); ++ if (ret != 0) { ++ D_ERR("Unable to secure (chown) socket '%s'\n", ++ sockpath); ++ close(fd); ++ return -1; ++ } ++ } ++ ++ /* Behaviour of fchmod(2) is undefined on sockets */ ++ ret = chmod(sockpath, 0700); ++ if (ret != 0) { ++ D_ERR("Unable to secure (chmod) socket '%s'\n", sockpath); ++ close(fd); ++ return -1; ++ } ++ + ret = listen(fd, 10); + if (ret != 0) { + D_ERR("socket listen failed - %s\n", sockpath); +-- +2.43.0 + + +From 6a35065be688fe449120d09045b90ad1ad681386 Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Wed, 10 Jun 2026 18:45:43 +1000 +Subject: [PATCH 16/23] CVE-2026-58224: ctdb-doc: Emphasise that the private network must be + private + +Note that the difference in the first couple of lines is leading +whitespace being switch to a TAB. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Tristan Madani +Reviewed-by: Stefan Metzmacher +--- + ctdb/doc/ctdb.7.xml | 27 +++++++++++++++++++-------- + 1 file changed, 19 insertions(+), 8 deletions(-) + +diff --git a/ctdb/doc/ctdb.7.xml b/ctdb/doc/ctdb.7.xml +index be2dd5e5b65..9087acf9870 100644 +--- a/ctdb/doc/ctdb.7.xml ++++ b/ctdb/doc/ctdb.7.xml +@@ -242,14 +242,25 @@ + services provided by the cluster. + + +- It is strongly recommended that the private addresses are +- configured on a private network that is separate from client +- networks. This is because the CTDB protocol is both +- unauthenticated and unencrypted. If clients share the private +- network then steps need to be taken to stop injection of +- packets to relevant ports on the private addresses. It is +- also likely that CTDB protocol traffic between nodes could +- leak sensitive information if it can be intercepted. ++ It is strongly recommended that the private addresses are ++ configured on a private network that is separate from client ++ networks. This is because the CTDB protocol is both ++ unauthenticated and unencrypted. If clients share ++ the private network then steps need to be taken to stop ++ injection of packets to relevant ports on the private ++ addresses. It is also likely that CTDB protocol traffic ++ between nodes could leak sensitive information if it can be ++ intercepted or manipulated. ++ ++ ++ In summary, the private network should not be accessible by ++ untrusted sources. We can't say "must not" because this can't ++ be enforced by CTDB, so we can only make a strong ++ recommendation. However, for this reason, future bugs in the ++ CTDB protocol used over the private network will be fixed as ++ regular bugs without following the Samba security process. ++ The same is true of local CTDB protocols used for Unix domain ++ socket and other communication. + + + +-- +2.43.0 + + +From 1ec95b73b235023221a4a18e888fa3286550faea Mon Sep 17 00:00:00 2001 +From: Martin Schwenke +Date: Tue, 30 Jun 2026 13:58:22 +1000 +Subject: [PATCH 17/23] CVE-2026-58224: ctdb-common: Add comments to ward off vulnerability + reports + +We can't deal with this in the current CTDB protocol without +disproportionate effort. So, document reality clearly in the code to +try to stop these from being reported. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16085 + +Signed-off-by: Martin Schwenke +Reviewed-by: Stefan Metzmacher +--- + ctdb/common/ctdb_io.c | 20 +++++++++++++++++++- + ctdb/common/pkt_read.c | 19 +++++++++++++++++++ + 2 files changed, 38 insertions(+), 1 deletion(-) + +diff --git a/ctdb/common/ctdb_io.c b/ctdb/common/ctdb_io.c +index 9ac9b84a7fb..346a7456f47 100644 +--- a/ctdb/common/ctdb_io.c ++++ b/ctdb/common/ctdb_io.c +@@ -115,7 +115,25 @@ static void queue_process(struct ctdb_queue *queue) + return; + } + +- /* Extract complete packet */ ++ /* ++ * Extract complete packet ++ * ++ * Yes, this allows an out-of-memory denial of service (DoS) ++ * attack if a number of packets are received with ++ * unreasonable packet sizes, noting that the maximum packet ++ * size is ~4GB. The CTDB protocol was not designed with a ++ * maximum packet size in mind, so CTDB may send very large ++ * valid packets. This means that imposing an arbitrary limit ++ * on incoming packets is not reasonable. Arguments that ++ * header fields, such as magic and/or version, should be ++ * validated before allocating a packet buffer are spurious ++ * from a security perspective because an attacker attempting ++ * DoS can send packets with valid headers. The private ++ * network and ctdbd socket should be secured against ++ * untrusted access, so reports that this possible DoS vector ++ * represents a security issue will be ignored. See the ++ * "Private addresses" section in ctdb(7) for more details. ++ */ + data = talloc_memdup(queue->data_pool, + queue->buffer.data + queue->buffer.offset, + pkt_size); +diff --git a/ctdb/common/pkt_read.c b/ctdb/common/pkt_read.c +index 212ace54bbd..8877f6aa368 100644 +--- a/ctdb/common/pkt_read.c ++++ b/ctdb/common/pkt_read.c +@@ -146,6 +146,25 @@ void pkt_read_handler(struct tevent_context *ev, struct tevent_fd *fde, + return; + } + ++ /* ++ * Allocate buffer for entire packet ++ * ++ * Yes, this allows an out-of-memory denial of service (DoS) ++ * attack if a number of packets are received with ++ * unreasonable packet sizes, noting that the maximum packet ++ * size is ~4GB. The CTDB protocol was not designed with a ++ * maximum packet size in mind, so CTDB may send very large ++ * valid packets. This means that imposing an arbitrary limit ++ * on incoming packets is not reasonable. Arguments that ++ * header fields, such as magic and/or version, should be ++ * validated before allocating a packet buffer are spurious ++ * from a security perspective because an attacker attempting ++ * DoS can send packets with valid headers. The private ++ * network and ctdbd socket should be secured against ++ * untrusted access, so reports that this possible DoS vector ++ * represents a security issue will be ignored. See the ++ * "Private addresses" section in ctdb(7) for more details. ++ */ + if (state->use_fixed) { + /* switch to dynamic buffer */ + tmp = talloc_array(state, uint8_t, state->total + more); +-- +2.43.0 + + +From ced0a00f97d9f165e6c7725f0d335b1a3411a478 Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Sun, 31 May 2026 12:48:11 +1200 +Subject: [PATCH 18/23] CVE-2026-58216: kdc:kpasswd: calculate correct size for password blob + +We were making the enc_data_blob 6 bytes too big. + +Its payload is an ASN.1 structure that knows its own size, so the +extra bytes are not usually read by Heimdal, but a crafted packet +could force them to be read. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16087 + +Reported-by: Tristan +Signed-off-by: Douglas Bagnall +Reviewed-by: Stefan Metzmacher +--- + source4/kdc/kpasswd-service.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/source4/kdc/kpasswd-service.c b/source4/kdc/kpasswd-service.c +index c671eb46d07..0284fb4f133 100644 +--- a/source4/kdc/kpasswd-service.c ++++ b/source4/kdc/kpasswd-service.c +@@ -137,7 +137,7 @@ kdc_code kpasswd_process(struct kdc_server *kdc, + + ap_req_blob = data_blob_const(&request->data[HEADER_LEN], ap_req_len); + +- enc_data_len = len - ap_req_len; ++ enc_data_len = len - (ap_req_len + HEADER_LEN); + enc_data_blob = data_blob_const(&request->data[HEADER_LEN + ap_req_len], + enc_data_len); + +-- +2.43.0 + + +From be4c33a1452aa2cd2fa6a420a1d443aef4233243 Mon Sep 17 00:00:00 2001 +From: Volker Lendecke +Date: Wed, 17 Jun 2026 08:24:47 +0200 +Subject: [PATCH 19/23] CVE-2026-58218: dns_server: Fix an error path memleak + +We talloc the new key off "dns->tkeys", which is long-lived. On any +error we never free'd that again. Probably not remotely triggerable, +this is only setting up the gensec context. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16115 + +Signed-off-by: Volker Lendecke +Reviewed-by: Douglas Bagnall +--- + source4/dns_server/dns_query.c | 25 ++++++++++++++----------- + 1 file changed, 14 insertions(+), 11 deletions(-) + +diff --git a/source4/dns_server/dns_query.c b/source4/dns_server/dns_query.c +index 1f46ee0aa19..ebc6a735a76 100644 +--- a/source4/dns_server/dns_query.c ++++ b/source4/dns_server/dns_query.c +@@ -675,18 +675,18 @@ static NTSTATUS create_tkey(struct dns_server *dns, + + k = talloc_zero(store, struct dns_server_tkey); + if (k == NULL) { +- return NT_STATUS_NO_MEMORY; ++ goto nomem; + } + + k->name = talloc_strdup(k, name); + + if (k->name == NULL) { +- return NT_STATUS_NO_MEMORY; ++ goto nomem; + } + + k->algorithm = talloc_strdup(k, algorithm); + if (k->algorithm == NULL) { +- return NT_STATUS_NO_MEMORY; ++ goto nomem; + } + + /* +@@ -704,8 +704,7 @@ static NTSTATUS create_tkey(struct dns_server *dns, + &k->gensec); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC server code: %s\n", nt_errstr(status))); +- *tkey = NULL; +- return status; ++ goto fail; + } + + gensec_want_feature(k->gensec, GENSEC_FEATURE_SIGN); +@@ -715,8 +714,7 @@ static NTSTATUS create_tkey(struct dns_server *dns, + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to set remote address into GENSEC: %s\n", + nt_errstr(status))); +- *tkey = NULL; +- return status; ++ goto fail; + } + + status = gensec_set_local_address(k->gensec, +@@ -724,8 +722,7 @@ static NTSTATUS create_tkey(struct dns_server *dns, + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to set local address into GENSEC: %s\n", + nt_errstr(status))); +- *tkey = NULL; +- return status; ++ goto fail; + } + + status = gensec_start_mech_by_oid(k->gensec, GENSEC_OID_SPNEGO); +@@ -733,8 +730,7 @@ static NTSTATUS create_tkey(struct dns_server *dns, + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC server code: %s\n", + nt_errstr(status))); +- *tkey = NULL; +- return status; ++ goto fail; + } + + TALLOC_FREE(store->tkeys[store->next_idx]); +@@ -745,6 +741,13 @@ static NTSTATUS create_tkey(struct dns_server *dns, + + *tkey = k; + return NT_STATUS_OK; ++ ++nomem: ++ status = NT_STATUS_NO_MEMORY; ++fail: ++ TALLOC_FREE(k); ++ *tkey = NULL; ++ return status; + } + + static NTSTATUS accept_gss_ticket(TALLOC_CTX *mem_ctx, +-- +2.43.0 + + +From d8752af75899caa621f71e6149024a2ed5d552b4 Mon Sep 17 00:00:00 2001 +From: Volker Lendecke +Date: Wed, 17 Jun 2026 08:58:40 +0200 +Subject: [PATCH 20/23] CVE-2026-58218: dns_server: Only add a tkey after successful + authentication + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16115 + +Signed-off-by: Volker Lendecke +Reviewed-by: Douglas Bagnall +--- + source4/dns_server/dns_query.c | 41 ++++++++++++++++++++++------------ + 1 file changed, 27 insertions(+), 14 deletions(-) + +diff --git a/source4/dns_server/dns_query.c b/source4/dns_server/dns_query.c +index ebc6a735a76..2998dbee060 100644 +--- a/source4/dns_server/dns_query.c ++++ b/source4/dns_server/dns_query.c +@@ -654,7 +654,8 @@ static WERROR handle_authoritative_recv(struct tevent_req *req) + return WERR_OK; + } + +-static NTSTATUS create_tkey(struct dns_server *dns, ++static NTSTATUS create_tkey(TALLOC_CTX *mem_ctx, ++ struct dns_server *dns, + const char* name, + const char* algorithm, + const struct tsocket_address *remote_address, +@@ -662,7 +663,6 @@ static NTSTATUS create_tkey(struct dns_server *dns, + struct dns_server_tkey **tkey) + { + NTSTATUS status; +- struct dns_server_tkey_store *store = dns->tkeys; + struct dns_server_tkey *k = NULL; + + if (strcmp(algorithm, "gss-tsig") == 0) { +@@ -673,7 +673,7 @@ static NTSTATUS create_tkey(struct dns_server *dns, + return NT_STATUS_ACCESS_DENIED; + } + +- k = talloc_zero(store, struct dns_server_tkey); ++ k = talloc_zero(mem_ctx, struct dns_server_tkey); + if (k == NULL) { + goto nomem; + } +@@ -733,12 +733,6 @@ static NTSTATUS create_tkey(struct dns_server *dns, + goto fail; + } + +- TALLOC_FREE(store->tkeys[store->next_idx]); +- +- store->tkeys[store->next_idx] = k; +- (store->next_idx)++; +- store->next_idx %= store->size; +- + *tkey = k; + return NT_STATUS_OK; + +@@ -750,6 +744,17 @@ fail: + return status; + } + ++static void add_tkey(struct dns_server_tkey_store *store, ++ struct dns_server_tkey **tkey) ++{ ++ TALLOC_FREE(store->tkeys[store->next_idx]); ++ ++ store->tkeys[store->next_idx] = talloc_move(store->tkeys, tkey); ++ ++ (store->next_idx)++; ++ store->next_idx %= store->size; ++} ++ + static NTSTATUS accept_gss_ticket(TALLOC_CTX *mem_ctx, + struct dns_server *dns, + struct dns_server_tkey *tkey, +@@ -863,6 +868,7 @@ static WERROR handle_tkey(struct dns_server *dns, + struct dns_server_tkey *tkey; + DATA_BLOB key; + DATA_BLOB reply; ++ bool created = false; + + tkey = dns_find_tkey(dns->tkeys, in->questions[0].name); + if (tkey != NULL && tkey->complete) { +@@ -873,15 +879,19 @@ static WERROR handle_tkey(struct dns_server *dns, + } + + if (tkey == NULL) { +- status = create_tkey(dns, in->questions[0].name, +- in_tkey->rdata.tkey_record.algorithm, +- state->remote_address, +- state->local_address, +- &tkey); ++ status = create_tkey( ++ mem_ctx, ++ dns, ++ in->questions[0].name, ++ in_tkey->rdata.tkey_record.algorithm, ++ state->remote_address, ++ state->local_address, ++ &tkey); + if (!NT_STATUS_IS_OK(status)) { + ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY; + return ntstatus_to_werror(status); + } ++ created = true; + } + + key.data = in_tkey->rdata.tkey_record.key_data; +@@ -906,6 +916,9 @@ static WERROR handle_tkey(struct dns_server *dns, + if (state->key_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } ++ if (created) { ++ add_tkey(dns->tkeys, &tkey); ++ } + } else { + DEBUG(1, ("GSS key negotiation returned %s\n", nt_errstr(status))); + ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY; +-- +2.43.0 + + +From 24f7eb574d2e9ea57746f9696491b50dc4ef5352 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Mon, 29 Jun 2026 19:09:28 +0200 +Subject: [PATCH 21/23] CVE-2026-58221: s4:dsdb: provide + dsdb_audit_{log_attributes,operation_human_readable}() functions + +They are useful outside of audit_log.c soon. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16147 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Volker Lendecke +Reviewed-by: Douglas Bagnall + +[backport: modified for v4.22, not removing the static versions used + in audit_log.c or adjusting the tests] +--- + source4/dsdb/samdb/ldb_modules/audit_util.c | 180 ++++++++++++++++++++ + 1 file changed, 180 insertions(+) + +diff --git a/source4/dsdb/samdb/ldb_modules/audit_util.c b/source4/dsdb/samdb/ldb_modules/audit_util.c +index 8fe7d8332d2..25f90ee3db3 100644 +--- a/source4/dsdb/samdb/ldb_modules/audit_util.c ++++ b/source4/dsdb/samdb/ldb_modules/audit_util.c +@@ -694,3 +694,183 @@ failure: + DBG_ERR("Unable to create ldb attributes JSON audit message\n"); + return attributes; + } ++ ++/* ++ * @brief Generate a human readable string, detailing attributes in a message ++ * ++ * For modify operations each attribute is prefixed with the action. ++ * Normal values are enclosed in [] ++ * Base64 values are enclosed in {} ++ * Truncated values are indicated by three trailing dots "..." ++ * ++ * @param[in] ldb the ldb_context ++ * @param[out] buffer the attributes will be appended to the buffer. ++ * assumed to have been allocated via talloc. ++ * @param[in] operation the operation type ++ * @param[in] message the message to process ++ * ++ * @return a pointer to buffer ++ * ++ */ ++char *dsdb_audit_log_attributes( ++ struct ldb_context *ldb, ++ char *buffer, ++ enum ldb_request_type operation, ++ const struct ldb_message *message) ++{ ++ size_t i, j; ++ for (i=0;inum_elements;i++) { ++ if (i > 0) { ++ buffer = talloc_asprintf_append_buffer(buffer, " "); ++ } ++ ++ if (message->elements[i].name == NULL) { ++ ldb_debug( ++ ldb, ++ LDB_DEBUG_ERROR, ++ "Error: Invalid element name (NULL) at " ++ "position %zu", i); ++ return NULL; ++ } ++ ++ if (operation == LDB_MODIFY) { ++ const char *action =NULL; ++ action = dsdb_audit_get_modification_action( ++ message->elements[i].flags); ++ buffer = talloc_asprintf_append_buffer( ++ buffer, ++ "%s: %s ", ++ action, ++ message->elements[i].name); ++ } else { ++ buffer = talloc_asprintf_append_buffer( ++ buffer, ++ "%s ", ++ message->elements[i].name); ++ } ++ ++ if (dsdb_audit_redact_attribute(message->elements[i].name)) { ++ /* ++ * Do not log the value of any secret or password ++ * attributes ++ */ ++ buffer = talloc_asprintf_append_buffer( ++ buffer, ++ "[REDACTED SECRET ATTRIBUTE]"); ++ continue; ++ } ++ ++ for (j=0;jelements[i].num_values;j++) { ++ struct ldb_val v; ++ bool use_b64_encode = false; ++ size_t length; ++ if (j > 0) { ++ buffer = talloc_asprintf_append_buffer( ++ buffer, ++ " "); ++ } ++ ++ v = message->elements[i].values[j]; ++ length = MIN(MAX_LENGTH, v.length); ++ use_b64_encode = ldb_should_b64_encode(ldb, &v); ++ if (use_b64_encode) { ++ const char *encoded = ldb_base64_encode( ++ buffer, ++ (char *)v.data, ++ length); ++ buffer = talloc_asprintf_append_buffer( ++ buffer, ++ "{%s%s}", ++ encoded, ++ (v.length > MAX_LENGTH ? "..." : "")); ++ } else { ++ buffer = talloc_asprintf_append_buffer( ++ buffer, ++ "[%*.*s%s]", ++ (int)length, ++ (int)length, ++ (char *)v.data, ++ (v.length > MAX_LENGTH ? "..." : "")); ++ } ++ } ++ } ++ return buffer; ++} ++ ++/* ++ * @brief generate a human readable log entry detailing an ldb operation. ++ * ++ * Generate a human readable log entry detailing an ldb operation. ++ * ++ * @param[in] mem_ctx the talloc context owning the returned string. ++ * @param[in] module the ldb module ++ * @param[in] request the request ++ * @param[in] reply the result of the operation ++ * ++ * @return the log entry. ++ * ++ */ ++char *dsdb_audit_operation_human_readable( ++ TALLOC_CTX *mem_ctx, ++ struct ldb_module *module, ++ const struct ldb_request *request, ++ const struct ldb_reply *reply) ++{ ++ struct ldb_context *ldb = NULL; ++ const char *remote_host = NULL; ++ const struct tsocket_address *remote = NULL; ++ const struct dom_sid *sid = NULL; ++ struct dom_sid_buf user_sid; ++ const char *timestamp = NULL; ++ const char *op_name = NULL; ++ char *log_entry = NULL; ++ const char *dn = NULL; ++ const char *new_dn = NULL; ++ const struct ldb_message *message = NULL; ++ ++ TALLOC_CTX *ctx = talloc_new(NULL); ++ ++ ldb = ldb_module_get_ctx(module); ++ ++ remote_host = dsdb_audit_get_remote_host(ldb, ctx); ++ remote = dsdb_audit_get_remote_address(ldb); ++ if (remote != NULL && dsdb_audit_is_system_session(module)) { ++ sid = dsdb_audit_get_actual_sid(ldb); ++ } else { ++ sid = dsdb_audit_get_user_sid(module); ++ } ++ timestamp = audit_get_timestamp(ctx); ++ op_name = dsdb_audit_get_operation_name(request); ++ dn = dsdb_audit_get_primary_dn(request); ++ new_dn = dsdb_audit_get_secondary_dn(request); ++ ++ message = dsdb_audit_get_message(request); ++ ++ log_entry = talloc_asprintf( ++ mem_ctx, ++ "[%s] at [%s] status [%s] " ++ "remote host [%s] SID [%s] DN [%s]", ++ op_name, ++ timestamp, ++ ldb_strerror(reply->error), ++ remote_host, ++ dom_sid_str_buf(sid, &user_sid), ++ dn); ++ if (new_dn != NULL) { ++ log_entry = talloc_asprintf_append_buffer( ++ log_entry, ++ " New DN [%s]", ++ new_dn); ++ } ++ if (message != NULL) { ++ log_entry = talloc_asprintf_append_buffer(log_entry, ++ " attributes ["); ++ log_entry = dsdb_audit_log_attributes(ldb, ++ log_entry, ++ request->operation, ++ message); ++ log_entry = talloc_asprintf_append_buffer(log_entry, "]"); ++ } ++ TALLOC_FREE(ctx); ++ return log_entry; ++} +-- +2.43.0 + + +From b5f78492628a06013354f76521cb153ab7d8e0d5 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Wed, 24 Jun 2026 14:01:32 +0200 +Subject: [PATCH 22/23] CVE-2026-58221: s4:dsdb: let rootdse_filter_operations() + reject untrusted operations on special DNs + +Without this authenticated (also non-admin) users write internal meta +data leading to admin privileges. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16147 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Volker Lendecke +Reviewed-by: Douglas Bagnall +--- + source4/dsdb/samdb/ldb_modules/rootdse.c | 55 ++++++++++++++++++- + .../samdb/ldb_modules/wscript_build_server | 2 +- + 2 files changed, 54 insertions(+), 3 deletions(-) + +diff --git a/source4/dsdb/samdb/ldb_modules/rootdse.c b/source4/dsdb/samdb/ldb_modules/rootdse.c +index 7541a2cf376..9cc8477b8f8 100644 +--- a/source4/dsdb/samdb/ldb_modules/rootdse.c ++++ b/source4/dsdb/samdb/ldb_modules/rootdse.c +@@ -28,6 +28,7 @@ + #include "dsdb/samdb/samdb.h" + #include "version.h" + #include "dsdb/samdb/ldb_modules/util.h" ++#include "dsdb/samdb/ldb_modules/audit_util_proto.h" + #include "libcli/security/security.h" + #include "librpc/ndr/libndr.h" + #include "auth/auth.h" +@@ -746,18 +747,68 @@ static int rootdse_filter_controls(struct ldb_module *module, struct ldb_request + return LDB_SUCCESS; + } + +-/* Ensure that anonymous users are not allowed to make anything other than rootDSE search operations */ +- ++/* ++ * Ensure that anonymous users are not allowed to make anything other than ++ * rootDSE search operations and special dns like @MODULES are not allowed ++ * over an untrusted connection. ++ */ + static int rootdse_filter_operations(struct ldb_module *module, struct ldb_request *req) + { + struct auth_session_info *session_info; + struct rootdse_private_data *priv = talloc_get_type(ldb_module_get_private(module), struct rootdse_private_data); + bool is_untrusted = ldb_req_is_untrusted(req); + bool is_anonymous = true; ++ struct ldb_dn *dn = NULL; ++ struct ldb_dn *dn2 = NULL; ++ + if (is_untrusted == false) { + return LDB_SUCCESS; + } + ++ switch (req->operation) { ++ case LDB_SEARCH: ++ dn = req->op.search.base; ++ break; ++ case LDB_ADD: ++ dn = req->op.add.message->dn; ++ break; ++ case LDB_MODIFY: ++ dn = req->op.mod.message->dn; ++ break; ++ case LDB_DELETE: ++ dn = req->op.del.dn; ++ break; ++ case LDB_RENAME: ++ dn = req->op.rename.olddn; ++ dn2 = req->op.rename.newdn; ++ break; ++ case LDB_EXTENDED: ++ break; ++ case LDB_REQ_REGISTER_CONTROL: ++ case LDB_REQ_REGISTER_PARTITION: ++ ldb_set_errstring(ldb_module_get_ctx(module), "Invalid OP"); ++ return LDB_ERR_OPERATIONS_ERROR; ++ } ++ ++ if (ldb_dn_is_special(dn)) { ++ struct ldb_reply reply = { .error = LDB_ERR_OPERATIONS_ERROR, }; ++ ++ D_ERR("CVE-2026-58221-ATTACK: %s\n", ++ dsdb_audit_operation_human_readable(req, module, req, &reply)); ++ ++ ldb_set_errstring(ldb_module_get_ctx(module), "Invalid DN"); ++ return LDB_ERR_OPERATIONS_ERROR; ++ } ++ if (ldb_dn_is_special(dn2)) { ++ struct ldb_reply reply = { .error = LDB_ERR_OPERATIONS_ERROR, }; ++ ++ D_ERR("CVE-2026-58221-ATTACK: %s\n", ++ dsdb_audit_operation_human_readable(req, module, req, &reply)); ++ ++ ldb_set_errstring(ldb_module_get_ctx(module), "Invalid DN"); ++ return LDB_ERR_OPERATIONS_ERROR; ++ } ++ + session_info = (struct auth_session_info *)ldb_get_opaque( + ldb_module_get_ctx(module), + DSDB_SESSION_INFO); +diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server +index 800ed2954c4..7582b64a938 100644 +--- a/source4/dsdb/samdb/ldb_modules/wscript_build_server ++++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server +@@ -185,7 +185,7 @@ bld.SAMBA_MODULE('ldb_rootdse', + init_function='ldb_rootdse_module_init', + module_init_name='ldb_init_module', + internal_module=False, +- deps='talloc samdb MESSAGING samba-security DSDB_MODULE_HELPERS RPC_NDR_IRPC' ++ deps='talloc samdb MESSAGING samba-security DSDB_MODULE_HELPERS DSDB_MODULE_HELPERS_AUDIT RPC_NDR_IRPC' + ) + + +-- +2.43.0 + + +From e3c15d9365fcec0389c9b3ac7bc38871854c64a7 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Wed, 24 Jun 2026 14:42:11 +0200 +Subject: [PATCH 23/23] CVE-2026-58222 s4:ldap_server: don't allow untrusted + compare requests for confidential attributes + +This means we apply acl checks against the search filter similar +to normal ldb searches. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16148 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source4/ldap_server/ldap_backend.c | 27 ++++++++++++++++++++------- + 1 file changed, 20 insertions(+), 7 deletions(-) + +diff --git a/source4/ldap_server/ldap_backend.c b/source4/ldap_server/ldap_backend.c +index 986bc1db941..4603935f0b0 100644 +--- a/source4/ldap_server/ldap_backend.c ++++ b/source4/ldap_server/ldap_backend.c +@@ -29,6 +29,7 @@ + #include "samba/service_stream.h" + #include "dsdb/gmsa/util.h" + #include "dsdb/samdb/samdb.h" ++#include "dsdb/common/util.h" + #include + #include + #include "ldb_wrap.h" +@@ -1449,6 +1450,7 @@ static NTSTATUS ldapsrv_CompareRequest(struct ldapsrv_call *call) + struct ldb_dn *dn; + const char *attrs[1]; + const char *errstr = NULL; ++ const char *value = NULL; + const char *filter = NULL; + int result = LDAP_SUCCESS; + int ldb_ret; +@@ -1461,21 +1463,31 @@ static NTSTATUS ldapsrv_CompareRequest(struct ldapsrv_call *call) + dn = ldb_dn_new(local_ctx, samdb, req->dn); + NT_STATUS_HAVE_NO_MEMORY(dn); + ++ compare_r = ldapsrv_init_reply(call, LDAP_TAG_CompareResponse); ++ NT_STATUS_HAVE_NO_MEMORY(compare_r); ++ + DBG_DEBUG("dn: [%s]\n", req->dn); +- filter = talloc_asprintf(local_ctx, "(%s=%*s)", req->attribute, +- (int)req->value.length, req->value.data); ++ ++ if (!ldb_valid_attr_name(req->attribute)) { ++ result = LDAP_INVALID_ATTRIBUTE_SYNTAX; ++ errstr = "Invalid Compare attribute name"; ++ goto reply; ++ } ++ value = ldb_binary_encode(local_ctx, req->value); ++ NT_STATUS_HAVE_NO_MEMORY(value); ++ ++ filter = talloc_asprintf(local_ctx, "%s=%s", req->attribute, value); + NT_STATUS_HAVE_NO_MEMORY(filter); + + DBG_DEBUG("attribute: [%s]\n", filter); + + attrs[0] = NULL; + +- compare_r = ldapsrv_init_reply(call, LDAP_TAG_CompareResponse); +- NT_STATUS_HAVE_NO_MEMORY(compare_r); +- + if (result == LDAP_SUCCESS) { +- ldb_ret = ldb_search(samdb, local_ctx, &res, +- dn, LDB_SCOPE_BASE, attrs, "%s", filter); ++ ldb_ret = dsdb_search(samdb, local_ctx, &res, ++ dn, LDB_SCOPE_BASE, attrs, ++ DSDB_MARK_REQ_UNTRUSTED, ++ "%s", filter); + if (ldb_ret != LDB_SUCCESS) { + result = map_ldb_error(local_ctx, ldb_ret, + ldb_errstring(samdb), &errstr); +@@ -1497,6 +1509,7 @@ static NTSTATUS ldapsrv_CompareRequest(struct ldapsrv_call *call) + } + } + ++reply: + compare = &compare_r->msg->r.CompareResponse; + compare->dn = NULL; + compare->resultcode = result; +-- +2.43.0 + diff -Nru samba-4.22.10+dfsg/debian/patches/series samba-4.22.10+dfsg/debian/patches/series --- samba-4.22.10+dfsg/debian/patches/series 2026-05-26 12:46:55.000000000 +0000 +++ samba-4.22.10+dfsg/debian/patches/series 2026-07-19 17:34:00.000000000 +0000 @@ -22,3 +22,4 @@ meaningful-error-if-no-python3-markdown.patch ctdb-use-run-instead-of-var-run.patch replace-xpg-strerror.patch +2026-jul-sec-update-bug-16039-v4-22-combined.patch