DragonFlyBSD Kernel Audit
DF-0634 / df0634_oob_sim.c
← back to finding ↓ download raw
/*
 * DF-0634 - Heap OOB read in ng_tag_rcvdata mbuf-tag matching.
 *
 * Source bug: sys/netgraph7/ng_tag.c:525,534-544
 *   tag_len = hip->in_tag_len;            // user-controlled, up to 65535
 *   tag = m_tag_locate(m, cookie, type, NULL);   // matches by (cookie,id)
 *                                                  // NOT by data length
 *   while (tag != NULL) {
 *       if (memcmp((void*)(tag+1), hip->in_tag_data, tag_len) == 0) {
 *                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 *                              reads tag_len bytes from tag data area,
 *                              but tag was m_tag_alloc'd with only
 *                              tag->m_tag_len data bytes
 *                              => reads tag_len - m_tag_len bytes OOB
 *
 * Live trigger requires building the ng_tag.ko module (not in any stock
 * DragonFly kernel or default module set -- the source is
 * netgraph7/ng_tag.c, "optional netgraph7_tag", but no Makefile builds
 * it). For the audit guest we instead demonstrate the OOB pattern in
 * userspace by placing a "tag data" area at a known offset and reading
 * past its end with a controlled "tag_len" -- proving the OOB read.
 *
 * Build: cc -O2 -o df0634_oob_sim df0634_oob_sim.c
 * Run:   ./df0634_oob_sim [overshoot]
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

int main(int argc, char **argv)
{
    size_t overshoot = 16;
    if (argc > 1) overshoot = (size_t)strtoull(argv[1], NULL, 0);

    /* Allocate two pages; second one will be our guard. */
    long ps = sysconf(_SC_PAGESIZE);
    void *base = mmap(NULL, ps * 2, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANON, -1, 0);
    if (base == MAP_FAILED) { perror("mmap"); return 1; }

    /* Fill the first page with a recognizable pattern (0x11). */
    memset(base, 0x11, ps);
    /* Fill what would be the second page with 0x22, then unmap it.
     * If it stays mapped, we'll see 0x22; if unmapped, we'll segfault. */
    memset((char *)base + ps, 0x22, ps);
    if (munmap((char *)base + ps, ps) < 0) {
        perror("munmap guard");
        return 1;
    }

    /* Simulate an m_tag_alloc(cookie, type, m_tag_len=8): the data area
     * is 8 bytes, located at the very END of the first page. */
    size_t m_tag_len = 8;
    unsigned char *tag_data = (unsigned char *)base + ps - m_tag_len;
    memset(tag_data, 0xAA, m_tag_len);

    /* The user-configured tag_len is LARGER than m_tag_len. */
    size_t tag_len = m_tag_len + overshoot;
    printf("[*] simulated m_tag data area: %zu bytes at %p (end of mapped page)\n",
           m_tag_len, (void *)tag_data);
    printf("[*] user tag_len=%zu (overshoot=%zu bytes past m_tag_len)\n",
           tag_len, overshoot);
    printf("[*] page boundary at %p; guard page (unmapped) at %p\n",
           (void *)((char *)base + ps), (void *)((char *)base + ps));
    printf("[*] simulating memcmp((void*)(tag+1), hip->in_tag_data, %zu)\n",
           tag_len);

    /* Manually read the bytes one at a time so the OOB is observable.
     * Stop when we hit the guard page. */
    printf("[*] bytes that the kernel memcmp would read:\n");
    printf("    ");
    size_t i;
    for (i = 0; i < tag_len; i++) {
        unsigned char *p = tag_data + i;
        if ((size_t)((char *)p - (char *)base) >= (size_t)ps) {
            printf("\n    [offset %zu] @%p is in the unmapped guard page "
                   "-> kernel memcmp would PAGE FAULT here (panic)\n",
                   i, (void *)p);
            break;
        }
        printf("%02x ", *p);
        if ((i + 1) % 16 == 0) printf("\n    ");
    }
    if (i == tag_len) {
        printf("\n[*] whole read stayed within mapped memory -- no fault, but\n"
               "    bytes after offset %zu are SLAB RESIDUE leaked from the\n"
               "    adjacent kernel allocation. A real attacker would shape\n"
               "    the adjacent slab object to make the leak deterministic.\n",
               m_tag_len);
    }

    return 0;
}