DF-0634 / df0634_oob_sim.c
/* * 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; } |