ANT-2026-S6CB3P3P · isc-projects/bind9

stack-buffer-overflow high

Severity Claude high · Security research firm high · Maintainer -

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Ada Logics.

ANT-2026-S6CB3P3P: 03-bind9-matchnsec3-stack-bof

In BIND's DNSSEC zone-verification path, match_nsec3() declares a stack buffer cbm[8244] and passes it to dns_nsec_compressbitmap() without a size argument. The compressor iterates up to 256 bitmap windows emitting up to 34 bytes each (2 header + 32 data), for a worst case of 8704 bytes — a 460-byte shortfall. An attacker supplying a DNSSEC-signed zone file for verification controls which RR types appear at the zone apex and thus which bitmap bytes are written; by placing one TYPE record per window at byte index 31, the attacker forces a 444-byte overflow with ~94% attacker-chosen content. The overflow overwrites the stack canary, six saved callee-save registers, the return address, and 368 bytes of the caller's frame. With the default -fstack-protector-strong build the canary check aborts the process (guaranteed DoS); without stack protector it is a control-flow-hijack write primitive.

Target

Project: bind9
Version: 9.21.21-dev (main branch, March 2026); any commit with cbm[8244] at zoneverify.c:~450
Location: lib/dns/zoneverify.c:468, match_nsec3()

Technical Details

ASAN: "stack-buffer-overflow ... WRITE of size 32 at 0x... in __interceptor_memmove ... [5472, 13716) 'cbm' (line 450) <== Memory access at offset 13716 overflows this variable" (13716 − 5472 = 8244, confirming the buffer size). The root cause is a sizing miscalculation: the author apparently knew the "compressed" NSEC3 wire format could exceed the 8192-byte raw bitmap (hence 8244 > 8192) but underestimated the growth — the per-window 2-byte framing header means dense bitmaps grow rather than shrink, and the true maximum is 256 × 34 = 8704 bytes. dns_nsec_compressbitmap() takes no output-buffer size and writes unconditionally; the bug is the caller's undersized buffer, not the writer.

Crash trace (truncated — full trace in attached crash.log):

==320560==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffb2503cb4 at pc 0x7f86d3e16c21 bp 0x7fffb2500670 sp 0x7fffb24ffe20
WRITE of size 32 at 0x7fffb2503cb4 thread T0
    #0 0x7f86d3e16c20 in __interceptor_memmove ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:810
    #1 0x7f86d3922c15 in memmove /usr/include/x86_64-linux-gnu/bits/string_fortified.h:36
    #2 0x7f86d3922c15 in dns_nsec_compressbitmap ../lib/dns/nsec.c:87
    #3 0x7f86d3be6ed3 in match_nsec3 ../lib/dns/zoneverify.c:468
    #4 0x7f86d3be6ed3 in verifynsec3 ../lib/dns/zoneverify.c:736
    #5 0x7f86d3be77e6 in verifynsec3s ../lib/dns/zoneverify.c:765
    #6 0x7f86d3be99fb in verifynode ../lib/dns/zoneverify.c:957
    #7 0x7f86d3bee323 in verify_nodes ../lib/dns/zoneverify.c:1761
    [... 6 more frames — full trace in crash.log]

Reproduction

  1. Construct an unsigned zone containing one TYPE RR per NSEC3 bitmap window (256 records), each chosen so the type's bit lands in byte index 31 of its window (e.g., TYPE(W*256+248) for window W)
  2. Sign the zone with NSEC3: dnssec-signzone -3 - -A -o example.com db.example.com
  3. Deliver the resulting db.example.com.signed to the target and cause dnssec-verify to process it
  4. verify_nodes → verifynode → verifynsec3s → verifynsec3 → match_nsec3 populates types[8192] with all 256 windows non-empty at octet 31
  5. match_nsec3 calls dns_nsec_compressbitmap(cbm, types, maxtype); the compressor emits 255×34 + 1×18 = 8688 bytes into the 8244-byte cbm[] stack buffer
  6. The 444-byte overflow overwrites the stack canary (rsp+0x2b48), six saved registers (rbx, rbp, r12–r15 at rsp+0x2b58), the return address (rsp+0x2b88), and 368 bytes of the caller's frame

[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-S6CB3P3P.


Reference: ANT-2026-S6CB3P3P
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure

SECURITY RESEARCH FIRM ANALYSIS

Triage and disclosure were performed by Ada Logics.

Verdict
true positive
Severity
high
UPSTREAM FIX

The change that resolved this finding.

diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h
index eda6214bb16..ebb1c65a438 100644
--- a/lib/dns/include/dns/nsec.h
+++ b/lib/dns/include/dns/nsec.h
@@ -21,7 +21,12 @@
 #include <dns/name.h>
 #include <dns/types.h>
 
-#define DNS_NSEC_BUFFERSIZE (DNS_NAME_MAXWIRE + 8192 + 512)
+/*
+ * max compressed bitmap size:
+ * 256 windows * (window number + window length + bitmap (max 256 bits))
+ */
+#define DNS_NSEC_MAXCBMSIZE (256 * ((256 / 8) + 2))
+#define DNS_NSEC_BUFFERSIZE (DNS_NAME_MAXWIRE + DNS_NSEC_MAXCBMSIZE)
 
 isc_result_t
 dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
diff --git a/lib/dns/zoneverify.c b/lib/dns/zoneverify.c
index 70209432a2c..70e21b4270d 100644
--- a/lib/dns/zoneverify.c
+++ b/lib/dns/zoneverify.c
@@ -447,7 +447,7 @@ match_nsec3(const vctx_t *vctx, const dns_name_t *name,
 	    const unsigned char types[8192], unsigned int maxtype,
 	    const unsigned char *rawhash, size_t rhsize,
 	    isc_result_t *vresult) {
-	unsigned char cbm[8244];
+	unsigned char cbm[DNS_NSEC_MAXCBMSIZE];
 	char namebuf[DNS_NAME_FORMATSIZE];
 	dns_rdata_nsec3_t nsec3;
 	isc_result_t result;

https://github.com/isc-projects/bind9/commit/e43e4bd20a99860e79294afbcc9b75f01ff28c26

TIMELINE

Dates from discovery through public reveal.

  1. 2026-03-26 Patch released
  2. 2026-03-31 Reported to tracker
  3. 2026-05-07 Sent to maintainer
  4. 2026-05-07 Maintainer acknowledged
  5. 2026-07-08 Publicly revealed
PROVENANCE

SHA-3-512 hash:

1a81a8c68555b513954cb7daffe6702584f613cf3878faeab4b7f4b4a9b5b80c6b93282ab289d3af24a84df046c42c0e673f533d97f7a509b4a54285a4b828ee

Committed 2026-05-07 07:53 PT

Revealed 2026-07-08 15:58 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-S6CB3P3P",
  "bug_class": "Stack Buffer Overflow",
  "claude_severity": "high",
  "commit_sha": null,
  "created_at": "2026-04-01T02:25:22+00:00",
  "description": "In BIND's DNSSEC zone-verification path, match_nsec3() declares a stack buffer cbm[8244] and passes it to dns_nsec_compressbitmap() without a size argument. The compressor iterates up to 256 bitmap windows emitting up to 34 bytes each (2 header + 32 data), for a worst case of 8704 bytes — a 460-byte shortfall. An attacker supplying a DNSSEC-signed zone file for verification controls which RR types appear at the zone apex and thus which bitmap bytes are written; by placing one TYPE<n> record per window at byte index 31, the attacker forces a 444-byte overflow with ~94% attacker-chosen content. The overflow overwrites the stack canary, six saved callee-save registers, the return address, and 368 bytes of the caller's frame. With the default -fstack-protector-strong build the canary check aborts the process (guaranteed DoS); without stack protector it is a control-flow-hijack write primitive.",
  "discovered_at": null,
  "location": "lib/dns/zoneverify.c:468, match_nsec3()",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "bind9",
  "reproduction": [
    "1. Construct an unsigned zone containing one TYPE<n> RR per NSEC3 bitmap window (256 records), each chosen so the type's bit lands in byte index 31 of its window (e.g., TYPE(W*256+248) for window W)",
    "2. Sign the zone with NSEC3: dnssec-signzone -3 - -A -o example.com db.example.com",
    "3. Deliver the resulting db.example.com.signed to the target and cause dnssec-verify to process it",
    "4. verify_nodes → verifynode → verifynsec3s → verifynsec3 → match_nsec3 populates types[8192] with all 256 windows non-empty at octet 31",
    "5. match_nsec3 calls dns_nsec_compressbitmap(cbm, types, maxtype); the compressor emits 255×34 + 1×18 = 8688 bytes into the 8244-byte cbm[] stack buffer",
    "6. The 444-byte overflow overwrites the stack canary (rsp+0x2b48), six saved registers (rbx, rbp, r12–r15 at rsp+0x2b58), the return address (rsp+0x2b88), and 368 bytes of the caller's frame"
  ],
  "technical_details": "ASAN: \"stack-buffer-overflow ... WRITE of size 32 at 0x... in __interceptor_memmove ... [5472, 13716) 'cbm' (line 450) <== Memory access at offset 13716 overflows this variable\" (13716 − 5472 = 8244, confirming the buffer size). The root cause is a sizing miscalculation: the author apparently knew the \"compressed\" NSEC3 wire format could exceed the 8192-byte raw bitmap (hence 8244 > 8192) but underestimated the growth — the per-window 2-byte framing header means dense bitmaps grow rather than shrink, and the true maximum is 256 × 34 = 8704 bytes. dns_nsec_compressbitmap() takes no output-buffer size and writes unconditionally; the bug is the caller's undersized buffer, not the writer.",
  "title": "03-bind9-matchnsec3-stack-bof",
  "vendor_severity": "high"
}