DF-4229 / model_uninit_error.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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | /* * DF-4229 harness: uninitialized `error` in cyapawrite() (sys/dev/smbus/cyapa/cyapa.c) * * Reproduces, in userspace, the EXACT control flow of cyapawrite() * (cyapa.c:785-1056) for the trigger reachable from userland: * * write(fd, buf, 0) -- zero-length write on an open /dev/cyapa* * * Trace on DragonFly (all sources cited): * sys/kern/sys_generic.c:506 dofilewrite() -> fo_write() with no * uio_resid==0 short-circuit * sys/kern/vfs_vnops.c vn_write() -> VOP_WRITE_FP() * sys/vfs/devfs/devfs_vnops.c:1366 devfs_write() -> dev_dwrite() * sys/kern/kern_device.c dev_dwrite() -> cyapawrite() * * In cyapawrite(): * cyapa.c:791 int error; <-- never initialized * cyapa.c:802 while ((n = fifo_space(&sc->wfifo)) > 0 && uio->uio_resid) * SKIPPED: resid == 0 <-- error never assigned * cyapa.c:815 while (... && error == 0) <-- uninit read (wfifo non-empty) * cyapa.c:1054 if (error == 0 && (...)) <-- uninit read (always) * cyapa.c:1056 return error; <-- stale stack slot returned * to userland as write()'s * result / errno * * The kernel function's stack frame occupies memory previously used by * arbitrary kernel code (previous syscall on the thread stack), so the * returned 32-bit value is stale kernel stack data (can contain kernel * pointers -> KASLR-relevant info leak). * * Demonstration method: poison the stack below the model function with a * known pattern, then call the model. A return of the pattern proves the * value flows from an uninitialized stack slot. Build with -O0 so `error` * gets a real stack slot (as it does in the kernel at -O2 where it is * likewise not initialized before the skip path). */ #include <stdio.h> #include <string.h> #include <stdint.h> #define CYAPA_BUFSIZE 128 #define CYAPA_BUFMASK (CYAPA_BUFSIZE - 1) struct cyapa_fifo { int rindex; int windex; char buf[CYAPA_BUFSIZE]; }; /* ---- stubs mirroring the invariants at the trigger point ---- */ static struct cyapa_fifo wfifo = { 5, 5, { 0 } }; /* empty wfifo */ static long uio_resid = 0; /* write(fd, buf, 0) */ static size_t fifo_space(struct cyapa_fifo *fifo) { size_t n; n = CYAPA_BUFSIZE - (fifo->windex & CYAPA_BUFMASK); if (n > (size_t)(CYAPA_BUFSIZE - (fifo->windex - fifo->rindex))) n = (size_t)(CYAPA_BUFSIZE - (fifo->windex - fifo->rindex)); return n; } static int fifo_ready(struct cyapa_fifo *fifo) { return ((fifo->windex - fifo->rindex) != 0); } static int uiomove_stub(void) { return 0; /* success */ } /* exact control-flow model of cyapawrite() cyapa.c:785-1056 */ static int cyapawrite_model(void) { int error; /* cyapa.c:791 */ int cmd_completed; size_t n; (void)(uintptr_t)&n; again: /* * Copy data from userland. */ while ((n = fifo_space(&wfifo)) > 0 && uio_resid) { /* cyapa.c:802 */ if (n > uio_resid) n = uio_resid; error = uiomove_stub(); /* cyapa.c:805 -- NOT reached */ if (error) break; break; /* resid==0: loop never runs */ } /* * Handle commands */ cmd_completed = (fifo_ready(&wfifo) != 0); /* cyapa.c:814 */ while (fifo_ready(&wfifo) && cmd_completed && error == 0) /* cyapa.c:815 */ break; /* cmd loop stubbed */ /* cyapa.c:1054 */ if (error == 0 && (cmd_completed || uio_resid)) goto again; return error; /* cyapa.c:1056 */ } /* * Smash a region of stack with the poison pattern; the callee's frame * (including `error`'s slot) is carved out of this dirty region. */ static void poison_stack(void) { volatile unsigned char junk[8192]; memset((void *)junk, 0xAB, sizeof(junk)); } int main(void) { int r; poison_stack(); r = cyapawrite_model(); printf("write(/dev/cyapa*, buf, 0) returned: 0x%08x (%d)\n", (unsigned)r, r); if (r == (int)0xABABABAB) { printf("RESULT: UNINITIALIZED STACK VALUE LEAKED " "(poison 0xABABABAB observed)\n"); return 0; } printf("RESULT: stale stack value observed (not the poison pattern " "this run; run again)\n"); return 1; } |