From a808c4eca63dc9af38cea84773431aa81ac25307 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.47.3 From 761796579873fab2909190a8f474a1d6d6f0caee 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.47.3 From 82688e9427d796d1246b34f187e4c9dd26a57269 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 78ee39015e6..e1de0197175 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.47.3 From 3bec447359c569426b44ede3275d884d353b639a 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.47.3 From 5ed865769b84e8c133e957ef420b80d354b7e022 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; } - 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); + 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; + } - memcpy(ctdb->vnn_map->map, map->map, sizeof(uint32_t)*map->size); + talloc_free(ctdb->vnn_map); + ctdb->vnn_map = new; return 0; } -- 2.47.3 From 8a451568d04ed2f8758142498057a914bf41372f 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.47.3 From 67427ee4c2c5e40cb76a22d41f33dc929fce1a4e 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.47.3 From 00cb172350054bb9dd5d21bed961156db82c779e 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.47.3 From 03386441713a653a9de38245479f05ae2616a248 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.47.3 From 0dc078350f054a83bf4da889685c3dd0d9951774 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.47.3 From 05b51a72a8cfae65aedb828dc11a1ca3069ae997 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.47.3 From ff99b84b6594a1dc1f68dfd54bd30e5f1d229e5f 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.47.3 From b243090d096f58721dfd1db2b39b8463bb248486 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.47.3 From f004832f15e4352518086c88929aa98906134e7d 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.47.3 From 504da6f1f73103f5bc299b2d77b1cd0aea968b4d 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.47.3 From 90724dab81367d364bcbcf40665218d585234baf 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.47.3 From f235767036f4d9557e2f6c33cff9c8ee4dff81ee 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.47.3 From 1ab79135f5b803c99b06289378d090f69821f066 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.47.3 From 3d933500625e97b94831ff3378fdf83359e9b49f 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.47.3 From 5d09d812f2dd86ac94ca4185bd72dc03f82ef251 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.47.3 From 1b3a0a2e981d3a3c67b6bfb43561d7123584a193 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 --- source4/dsdb/samdb/ldb_modules/audit_log.c | 181 +----------------- source4/dsdb/samdb/ldb_modules/audit_util.c | 180 +++++++++++++++++ .../samdb/ldb_modules/tests/test_audit_log.c | 24 +-- 3 files changed, 193 insertions(+), 192 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/audit_log.c b/source4/dsdb/samdb/ldb_modules/audit_log.c index c944a84b360..3aad76a914a 100644 --- a/source4/dsdb/samdb/ldb_modules/audit_log.c +++ b/source4/dsdb/samdb/ldb_modules/audit_log.c @@ -867,185 +867,6 @@ static char *password_change_human_readable( TALLOC_FREE(ctx); return log_entry; } -/* - * @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 - * - */ -static char *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. - * - */ -static char *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 = log_attributes(ldb, - log_entry, - request->operation, - message); - log_entry = talloc_asprintf_append_buffer(log_entry, "]"); - } - TALLOC_FREE(ctx); - return log_entry; -} /* * @brief generate a human readable log entry detailing a replicated update @@ -1213,7 +1034,7 @@ static void log_standard_operation( if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT, OPERATION_LOG_LVL)) { char *entry = NULL; - entry = operation_human_readable( + entry = dsdb_audit_operation_human_readable( ctx, module, request, diff --git a/source4/dsdb/samdb/ldb_modules/audit_util.c b/source4/dsdb/samdb/ldb_modules/audit_util.c index 11e1b755616..33910d7c9a8 100644 --- a/source4/dsdb/samdb/ldb_modules/audit_util.c +++ b/source4/dsdb/samdb/ldb_modules/audit_util.c @@ -716,3 +716,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; +} diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c index 2862fa25f3f..6e7ceaf0cdc 100644 --- a/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c +++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c @@ -1615,7 +1615,7 @@ static void test_replicated_update_json(void **state) } /* - * minimal unit test of operation_human_readable, that ensures that all the + * minimal unit test of dsdb_audit_operation_human_readable, that ensures that all the * expected attributes and objects are in the json object. */ static void test_operation_hr_empty(void **state) @@ -1645,7 +1645,7 @@ static void test_operation_hr_empty(void **state) reply = talloc_zero(ctx, struct ldb_reply); reply->error = LDB_SUCCESS; - line = operation_human_readable(ctx, module, req, reply); + line = dsdb_audit_operation_human_readable(ctx, module, req, reply); assert_non_null(line); /* @@ -1740,7 +1740,7 @@ static void test_operation_hr(void **state) reply = talloc_zero(ctx, struct ldb_reply); reply->error = LDB_SUCCESS; - line = operation_human_readable(ctx, module, req, reply); + line = dsdb_audit_operation_human_readable(ctx, module, req, reply); assert_non_null(line); /* @@ -1851,7 +1851,7 @@ static void test_as_system_operation_hr(void **state) reply = talloc_zero(ctx, struct ldb_reply); reply->error = LDB_SUCCESS; - line = operation_human_readable(ctx, module, req, reply); + line = dsdb_audit_operation_human_readable(ctx, module, req, reply); assert_non_null(line); /* @@ -2162,7 +2162,7 @@ static void test_log_attributes(void **state) buf = talloc_zero(ctx, char); msg = talloc_zero(ctx, struct ldb_message); - str = log_attributes(ctx, buf, LDB_ADD, msg); + str = dsdb_audit_log_attributes(ctx, buf, LDB_ADD, msg); assert_string_equal("", str); TALLOC_FREE(str); @@ -2175,7 +2175,7 @@ static void test_log_attributes(void **state) msg = talloc_zero(ctx, struct ldb_message); ldb_msg_add_string(msg, "clearTextPassword", "secret"); - str = log_attributes(ctx, buf, LDB_ADD, msg); + str = dsdb_audit_log_attributes(ctx, buf, LDB_ADD, msg); assert_string_equal( "clearTextPassword [REDACTED SECRET ATTRIBUTE]", str); @@ -2185,7 +2185,7 @@ static void test_log_attributes(void **state) * action will be unknown as there are no ACL's set */ buf = talloc_zero(ctx, char); - str = log_attributes(ctx, buf, LDB_MODIFY, msg); + str = dsdb_audit_log_attributes(ctx, buf, LDB_MODIFY, msg); assert_string_equal( "unknown: clearTextPassword [REDACTED SECRET ATTRIBUTE]", str); @@ -2200,7 +2200,7 @@ static void test_log_attributes(void **state) msg = talloc_zero(ctx, struct ldb_message); ldb_msg_add_string(msg, "attribute", "value"); - str = log_attributes(ctx, buf, LDB_ADD, msg); + str = dsdb_audit_log_attributes(ctx, buf, LDB_ADD, msg); assert_string_equal( "attribute [value]", str); @@ -2216,7 +2216,7 @@ static void test_log_attributes(void **state) msg = talloc_zero(ctx, struct ldb_message); ldb_msg_add_string(msg, "attribute", "value"); - str = log_attributes(ctx, buf, LDB_MODIFY, msg); + str = dsdb_audit_log_attributes(ctx, buf, LDB_MODIFY, msg); assert_string_equal( "unknown: attribute [value]", str); @@ -2234,7 +2234,7 @@ static void test_log_attributes(void **state) ldb_msg_add_string(msg, "attribute02", "value02"); ldb_msg_add_string(msg, "attribute02", "value03"); - str = log_attributes(ctx, buf, LDB_MODIFY, msg); + str = dsdb_audit_log_attributes(ctx, buf, LDB_MODIFY, msg); assert_string_equal( "unknown: attribute01 [value01] " "unknown: attribute02 [value02] [value03]", @@ -2251,7 +2251,7 @@ static void test_log_attributes(void **state) msg = talloc_zero(ctx, struct ldb_message); ldb_msg_add_string(msg, "attribute", "value\n"); - str = log_attributes(ctx, buf, LDB_ADD, msg); + str = dsdb_audit_log_attributes(ctx, buf, LDB_ADD, msg); assert_string_equal("attribute {dmFsdWUK}", str); TALLOC_FREE(str); @@ -2268,7 +2268,7 @@ static void test_log_attributes(void **state) memset(lv, 'x', MAX_LENGTH+1); ldb_msg_add_string(msg, "attribute", lv); - str = log_attributes(ctx, buf, LDB_ADD, msg); + str = dsdb_audit_log_attributes(ctx, buf, LDB_ADD, msg); snprintf(ex, sizeof(ex), "attribute [%.*s...]", MAX_LENGTH, lv); assert_string_equal(ex, str); -- 2.47.3 From 75e3ded48f4bd0087abeb9d2403c6147097dfddc 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 364c118c70d..d41549b5631 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 06a6c350b3d..640bf250d4f 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.47.3 From 781ee8cce74af9d7c5627ba0035eb43a1aa7a2e3 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 7314e65778a..50b0adc7b8e 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" @@ -1453,6 +1454,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; @@ -1465,21 +1467,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); @@ -1501,6 +1513,7 @@ static NTSTATUS ldapsrv_CompareRequest(struct ldapsrv_call *call) } } +reply: compare = &compare_r->msg->r.CompareResponse; compare->dn = NULL; compare->resultcode = result; -- 2.47.3