From f7f4d096aedb236ccf026ed3c59c7538e5303830 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
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 <metze@samba.org>
Reviewed-by: Douglas Bagnall <dbagnall@samba.org>
---
 source4/dns_server/dns_crypto.c | 44 ++++++++++++++++++++++++---------
 1 file changed, 32 insertions(+), 12 deletions(-)

--- a/source4/dns_server/dns_crypto.c
+++ b/source4/dns_server/dns_crypto.c
@@ -35,6 +35,8 @@ static WERROR dns_copy_tsig(TALLOC_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);
 
@@ -100,7 +102,7 @@ WERROR dns_verify_tsig(struct dns_server
 	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;
@@ -130,6 +132,22 @@ WERROR dns_verify_tsig(struct dns_server
 	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;
@@ -194,14 +212,6 @@ WERROR dns_verify_tsig(struct dns_server
 	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)) {
@@ -210,9 +220,19 @@ WERROR dns_verify_tsig(struct dns_server
 		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) {
