From 5b56bbaf3f4971d38549c1970b68a6a5a697fe41 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Fri, 2 Oct 2026 20:51:13 +0000 Subject: [PATCH 1/2] Add content from: Dnsmasq DNS Remote Heap Buffer Overflow (CVE-2026-2291) --- .../libc-heap/heap-overflow.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/binary-exploitation/libc-heap/heap-overflow.md b/src/binary-exploitation/libc-heap/heap-overflow.md index 923de826a57..527059308c6 100644 --- a/src/binary-exploitation/libc-heap/heap-overflow.md +++ b/src/binary-exploitation/libc-heap/heap-overflow.md @@ -113,6 +113,25 @@ The implementation also performed `destination = stored_pointer - packet_length` > [!NOTE] > If the corrupted process cannot safely continue, execute persistence or cleanup logic **before** the inevitable fault. In the NetScaler chain, shellcode replaced fatal-signal handlers with `SIG_IGN` via FreeBSD `sigaction`, preventing the handlers from asking the supervisor to reboot the appliance. The supervisor then respawned only the packet engine, preserving files written before the crash. This behavior is supervisor-specific and should be verified by tracing the process-to-watchdog notification path.[[10]](#references) +### Length-expanding decoders and application free-list poisoning + +Size buffers for the **largest internal representation**, not only for the wire input. Dnsmasq provides a useful example: its DNS-name decoder escaped bytes such as `0x00`, `0x2e`, and `0x01`, so one wire byte could require two bytes in the internal C string. The cache selected its 1,025-byte `bigname` allocation whenever the decoded name exceeded the 49-byte small-name threshold, but it did not check the escaped length against the large allocation before `strcpy()`. The upstream fix changed the storage to `(2 * MAXDNAME) + 1`, accounting for worst-case expansion and the terminator.[[12]](#references)[[13]](#references) + +This bug class is easy to miss during review because the parser can enforce the protocol's wire-length limit while a later **escaping, canonicalization, normalization, or transcoding pass** produces a larger object. Audit the length after every representation change and compare that result with the actual destination capacity.[[12]](#references)[[13]](#references) + +#### Turning an overflow into an application-pool arbitrary write + +The demonstrated Dnsmasq chain poisons an **application-managed** singly linked free list rather than glibc metadata. This distinction matters: the affected objects remain in Dnsmasq's `big_free` pool instead of being passed to `free()`, so libc heap-integrity checks never inspect the corrupted link. The published remote path used a resolver configured with an attacker-controlled upstream DNS server, which returned the crafted replies below.[[13]](#references) + +1. **Groom with parser objects.** Return a long CNAME chain whose decoded targets exceed the small-name threshold. Each target consumes one large-name object. Use enough records to drain inconvenient holes and make the final objects adjacent. +2. **Recycle the layout.** Select a processing path that does not retain the answer. In the demonstrated response, the `CD` bit prevents caching and returns the objects to `big_free` in a predictable order. +3. **Corrupt the next free object.** Reuse one adjacent object for an expanded name and overflow into the free neighbor. On the 32-bit target, the first four bytes of that neighbor are its `next` pointer. Replace them with the desired write address. +4. **Advance the poisoned list.** The first allocation removes the corrupted entry and copies its forged `next` value into the free-list head. The following allocation therefore treats the chosen address as a valid large-name buffer. +5. **Use the normal copy as the write.** A second attacker-controlled name reaches `strcpy(destination, name)`, giving a constrained write-what-where primitive. The terminating NUL and the decoder's escaping rules are part of the write constraints. +6. **Hijack a later indirect call.** The published x86/musl example overwrote the `VDSO_CGT32_SYM` function pointer in `ld.so`; a later packet caused `EIP = 0x61616161`. Reliable code execution still required an information leak or equivalent ASLR bypass to calculate the library base.[[13]](#references) + +When reproducing this pattern, break on both the pool pop and the vulnerable copy. Confirm the exact free-list order, whether the list is LIFO, which transformed bytes survive in the copied string, and whether the forged address must satisfy alignment or object-header checks. Do not assume allocator hardening protects an object pool implemented by the application.[[13]](#references) + ### Real-World Example: CVE-2025-40597 – Misusing `__sprintf_chk` In SonicWall SMA100 firmware 10.2.1.15 the reverse-proxy module `mod_httprp.so` allocates an **0x80-byte** heap chunk and then concatenates several strings into it with `__sprintf_chk`:[[1]](#references) @@ -159,5 +178,7 @@ Practical exploitation would require **heap grooming** to place a controllable o - [9] [MITRE CWE-122 – Heap-based Buffer Overflow](https://cwe.mitre.org/data/definitions/122.html) - [10] [watchTowr Labs – You’re Back In The Room: Citrix NetScaler pre-authentication RCE](https://labs.watchtowr.com/youre-back-in-the-room-citrix-netscaler-pre-auth-rce-cve-2026-8452/) - [11] [Citrix Security Bulletin CTX696604](https://support.citrix.com/support-home/kbsearch/article?articleNumber=CTX696604) +- [12] [Dnsmasq upstream patch for CVE-2026-2291](https://thekelleys.org.uk/dnsmasq/CVE/CVE-2026-2291.diff) +- [13] [Exodus Intelligence – Dnsmasq DNS Remote Heap Buffer Overflow](https://blog.exodusintel.com/2026/07/20/dnsmasq-dns-remote-heap-buffer-overflow/) {{#include ../../banners/hacktricks-training.md}} From 9d8ec2c06096738bb6a5e1fd33bcc19e7a08d833 Mon Sep 17 00:00:00 2001 From: SirBroccoli Date: Mon, 12 Oct 2026 00:51:27 +0200 Subject: [PATCH 2/2] docs: repair PR placement and formatting after editorial review --- .../libc-heap/heap-overflow.md | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/binary-exploitation/libc-heap/heap-overflow.md b/src/binary-exploitation/libc-heap/heap-overflow.md index 527059308c6..24fa6e0c353 100644 --- a/src/binary-exploitation/libc-heap/heap-overflow.md +++ b/src/binary-exploitation/libc-heap/heap-overflow.md @@ -115,22 +115,18 @@ The implementation also performed `destination = stored_pointer - packet_length` ### Length-expanding decoders and application free-list poisoning -Size buffers for the **largest internal representation**, not only for the wire input. Dnsmasq provides a useful example: its DNS-name decoder escaped bytes such as `0x00`, `0x2e`, and `0x01`, so one wire byte could require two bytes in the internal C string. The cache selected its 1,025-byte `bigname` allocation whenever the decoded name exceeded the 49-byte small-name threshold, but it did not check the escaped length against the large allocation before `strcpy()`. The upstream fix changed the storage to `(2 * MAXDNAME) + 1`, accounting for worst-case expansion and the terminator.[[12]](#references)[[13]](#references) +Size buffers for the **largest internal representation**, not only for the wire input. Dnsmasq's DNS-name decoder escapes `0x00`, `0x2e`, and `0x01`, so a wire byte can require two bytes in the internal C string. The cache selected a 1,025-byte `bigname` allocation when the decoded name exceeded the 49-byte small-name limit, but it did not check the escaped length before `strcpy()`. The upstream fix changed this storage to `(2 * MAXDNAME) + 1`, allowing for worst-case expansion and the terminator.[[12]](#references)[[13]](#references) -This bug class is easy to miss during review because the parser can enforce the protocol's wire-length limit while a later **escaping, canonicalization, normalization, or transcoding pass** produces a larger object. Audit the length after every representation change and compare that result with the actual destination capacity.[[12]](#references)[[13]](#references) +A protocol parser can therefore enforce its wire-length limit while a later decoding step still creates an oversized object. Validate the length after each representation change and compare that value with the destination capacity.[[12]](#references)[[13]](#references) -#### Turning an overflow into an application-pool arbitrary write +#### Turning an application free list into an arbitrary write -The demonstrated Dnsmasq chain poisons an **application-managed** singly linked free list rather than glibc metadata. This distinction matters: the affected objects remain in Dnsmasq's `big_free` pool instead of being passed to `free()`, so libc heap-integrity checks never inspect the corrupted link. The published remote path used a resolver configured with an attacker-controlled upstream DNS server, which returned the crafted replies below.[[13]](#references) +The Dnsmasq example poisons an **application-managed** singly linked free list rather than glibc metadata. The affected objects remain in the `big_free` pool instead of being passed to `free()`, so libc integrity checks and safe-linking protections described for [tcache poisoning](tcache-bin-attack.md) do not inspect the corrupted link. The demonstrated remote path requires a resolver configured with an attacker-controlled upstream DNS server.[[13]](#references) -1. **Groom with parser objects.** Return a long CNAME chain whose decoded targets exceed the small-name threshold. Each target consumes one large-name object. Use enough records to drain inconvenient holes and make the final objects adjacent. -2. **Recycle the layout.** Select a processing path that does not retain the answer. In the demonstrated response, the `CD` bit prevents caching and returns the objects to `big_free` in a predictable order. -3. **Corrupt the next free object.** Reuse one adjacent object for an expanded name and overflow into the free neighbor. On the 32-bit target, the first four bytes of that neighbor are its `next` pointer. Replace them with the desired write address. -4. **Advance the poisoned list.** The first allocation removes the corrupted entry and copies its forged `next` value into the free-list head. The following allocation therefore treats the chosen address as a valid large-name buffer. -5. **Use the normal copy as the write.** A second attacker-controlled name reaches `strcpy(destination, name)`, giving a constrained write-what-where primitive. The terminating NUL and the decoder's escaping rules are part of the write constraints. -6. **Hijack a later indirect call.** The published x86/musl example overwrote the `VDSO_CGT32_SYM` function pointer in `ld.so`; a later packet caused `EIP = 0x61616161`. Reliable code execution still required an information leak or equivalent ASLR bypass to calculate the library base.[[13]](#references) - -When reproducing this pattern, break on both the pool pop and the vulnerable copy. Confirm the exact free-list order, whether the list is LIFO, which transformed bytes survive in the copied string, and whether the forged address must satisfy alignment or object-header checks. Do not assume allocator hardening protects an object pool implemented by the application.[[13]](#references) +1. A long CNAME chain consumes large-name objects. A response that is not retained in the cache returns those objects to `big_free`; the published example used the `CD` bit to obtain this layout. +2. An expanded name reuses an adjacent object and overflows into the next free object's link. On the demonstrated 32-bit target, this overwrites the first four bytes with the intended write address. +3. The next allocation removes that entry and promotes its forged link to the free-list head. A second allocation then treats the chosen address as a large-name buffer, and the normal `strcpy()` becomes a constrained write-what-where operation. The terminating NUL and decoder escaping constrain the bytes that can be written. +4. The published x86/musl example targeted the `VDSO_CGT32_SYM` function pointer in `ld.so`. Triggering the later indirect call gave instruction-pointer control, but reliable code execution still required an information leak or another ASLR bypass.[[13]](#references) ### Real-World Example: CVE-2025-40597 – Misusing `__sprintf_chk`