Buffer Under-read

Description

Buffer Under-read occurs when a program reads data from memory locations before the beginning of an intended buffer. This typically happens when a pointer or array index is decremented below zero, pointer arithmetic produces a negative offset, or a negative index is used to access an array. Unlike buffer overflows, under-reads primarily cause information disclosure by exposing data from memory preceding the target buffer. This can include sensitive information from other variables, heap metadata, or previously freed memory contents.

Risk

Buffer under-reads pose confidentiality risks by potentially exposing sensitive data stored in memory locations before the target buffer. While typically less severe than buffer overflows, under-reads can leak cryptographic keys, passwords, or other secrets stored in adjacent memory. In some cases, under-reads can cause crashes when accessing unmapped memory regions, leading to denial of service. The vulnerability may also be combined with other weaknesses to achieve more severe exploitation outcomes.

Solution

Validate all array indices and pointer offsets before use, checking both upper and lower bounds. Use unsigned integer types for array indices where negative values are logically invalid. Implement bounds-checking wrapper functions for buffer access. Use memory-safe languages or C++ containers that prevent out-of-bounds access. Enable AddressSanitizer during development to detect under-reads. Review pointer arithmetic operations, especially those involving subtraction or decrement operations.

Common Consequences

ImpactDetails
ConfidentialityScope: Information Disclosure

Reading memory before buffer start can expose sensitive data from other variables or data structures.
AvailabilityScope: Availability

Accessing unmapped memory pages causes segmentation faults and crashes.
IntegrityScope: Information Leak

Leaked information may assist attackers in bypassing security mechanisms or exploiting other vulnerabilities.

Example Code + Solution Code

Vulnerable Code

#include <string.h>
#include <stdlib.h>

// VULNERABLE: Negative index under-read
int get_previous_value(int *array, int current_index) {
    // No check for negative result
    return array[current_index - 1];  // Under-read if current_index == 0
}

// VULNERABLE: Pointer decrement under-read
char *find_start_of_line(char *buffer, char *current_pos) {
    // Decrement without checking buffer boundary
    while (*current_pos != '\n') {
        current_pos--;  // May go before buffer start
    }
    return current_pos + 1;
}

// VULNERABLE: Negative offset from configuration
void read_at_offset(char *buffer, int offset) {
    // User-controlled offset could be negative
    char value = buffer[offset];  // Under-read if offset < 0
    process(value);
}

Fixed Code

#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

// SAFE: Bounds check before access
bool get_previous_value_safe(int *array, size_t array_size,
                             size_t current_index, int *out_value) {
    // Check that there IS a previous element
    if (current_index == 0 || current_index > array_size) {
        return false;  // No valid previous element
    }
    *out_value = array[current_index - 1];
    return true;
}

// SAFE: Check buffer boundaries
char *find_start_of_line_safe(char *buffer_start, char *buffer_end,
                              char *current_pos) {
    // Validate current_pos is within buffer
    if (current_pos < buffer_start || current_pos >= buffer_end) {
        return NULL;
    }

    // Stop at buffer start
    while (current_pos > buffer_start && *current_pos != '\n') {
        current_pos--;
    }

    // Return position after newline (or buffer start)
    return (current_pos == buffer_start) ? current_pos : current_pos + 1;
}

// SAFE: Validate offset is non-negative
bool read_at_offset_safe(const char *buffer, size_t buffer_size,
                         size_t offset, char *out_value) {
    // size_t is unsigned, but check upper bound too
    if (offset >= buffer_size) {
        return false;
    }
    *out_value = buffer[offset];
    return true;
}

Exploited in the Wild

c-ares DNS Library (Multiple Systems, 2024)

CVE-2024-25629 is a buffer under-read vulnerability in the c-ares C library for asynchronous DNS requests. A NULL character at the start of a new line in configuration files (/etc/resolv.conf, /etc/nsswitch.conf) could cause reads before the buffer start, leading to crashes. The vulnerability affected IBM Storage Ceph, Node.js, and many Linux distributions.

Multiple Vendor Products (Various, Ongoing)

Buffer under-read vulnerabilities continue to be discovered in various products including media codecs, network protocol parsers, and file format handlers, typically resulting in information disclosure or denial of service.


Tools to test/exploit

  • AddressSanitizer — detects buffer under-reads at runtime with precise error reporting.

  • Valgrind — memory error detector that identifies reads before allocated memory.

  • AFL++ — fuzzer effective at discovering boundary condition bugs.


CVE Examples

  • CVE-2024-25629 — c-ares buffer under-read causing denial of service.

  • CVE-2023-52160 — wpa_supplicant buffer under-read in EAP-TLS processing.

  • CVE-2021-3449 — OpenSSL NULL pointer dereference from under-read condition.


References

  1. MITRE. "CWE-127: Buffer Under-read." https://cwe.mitre.org/data/definitions/127.html

  2. CERT. "ARR30-C. Do not form or use out-of-bounds pointers or array subscripts." https://wiki.sei.cmu.edu/confluence/display/c/ARR30-C