โฌข DragonFlyBSD Kernel Audit
DF-4846 / ffmix.c
โ† back to finding โ†“ download raw
/*
 * ffmix.c โ€” adversarial harness for the REAL sys/dev/sound/pcm/feeder_mixer.c
 * (GLM 5.3 pass-2, FXROUND follow-up; DF-4849).
 *
 * M1  :299 entry misround: count = SND_FXROUND(count_in, sz) > count_in for
 *     bad-residue free-space values inside the claimed-exact domain =>
 *     children fill count > free bytes into b and the mixer returns
 *     rcnt > count_in โ€” in-kernel sndbuf_acquire() then overflows the ring
 *     (buffer.c:615-636, only KASSERT-protected in production).
 * M2  :339-364 return misround + :362 unsigned underflow: a child feeder
 *     that returns slightly more than asked (proven possible by the
 *     fxfmt/ffvol harnesses: feeder_format returns count+delta on entry
 *     misrounds) makes rcnt = SND_FXROUND(ret, sz) > count, so
 *     mcnt = count - rcnt wraps to ~0xFFFFxxxx and :368 issues
 *     memset(b + rcnt, zerodata, ~4GiB) โ€” catastrophic heap overwrite,
 *     caught here by a memset interceptor (exit code 42 = bomb detected).
 */
#include "fxshim.h"
#include <err.h>

#define min(a, b)	(((a) < (b)) ? (a) : (b))
#define AFMT_PASSTHROUGH	AFMT_AC3

/* ---- fake channel / buffer machinery (channel.h + buffer.h shapes) ---- */
#define PCMDIR_PLAY	1
#define PCMDIR_REC	2
#define CHN_F_MMAP		0x00000001
#define CHN_F_CLOSING		0x00000002
#define CHN_F_DIRTY		0x00000004
#define CHN_F_EXCLUSIVE		0x00000008
#define CHN_F_VIRTUAL		0x00000010
#define CHN_F_HAS_VCHAN		0x00000020
#define CHN_F_PASSTHROUGH	0x00000040
#define CHN_F_STOPPED		0x00000080
#define CHN_F_RUNNING		0x00000100

#define CHN_LOCK(ch)		do { } while (0)
#define CHN_UNLOCK(ch)		do { } while (0)
#define CHN_STOPPED(ch)		(((ch)->flags & CHN_F_STOPPED) != 0)

struct pcm_channel {
	int direction;
	int flags;
	int feedcount;
	int xruns;
	uint32_t format;
	struct pcm_feeder *feeder;
	struct snd_dbuf *bufsoft, *bufhard;
	struct {
		struct {
			struct pcm_channel *tqh_first;
		} busy;
	} children;
	struct pcm_channel *next_busy;	/* fake list link */
};

struct snd_dbuf {
	uint8_t *buf;
	uint8_t *tmpbuf;
	uint32_t size;
	uint32_t align;
	int rp, rl;
};

static inline uint32_t
sndbuf_getsize(struct snd_dbuf *b) { return (b->size); }
static inline uint32_t
sndbuf_getalign(struct snd_dbuf *b) { return (b->align); }
static inline uint8_t *
sndbuf_getbuf(struct snd_dbuf *b) { return (b->buf); }
static inline int
sndbuf_getfree(struct snd_dbuf *b) { return ((int)(b->size)); }
static inline int
sndbuf_getready(struct snd_dbuf *b) { return ((int)(b->size)); }
static inline uint8_t
sndbuf_zerodata(uint32_t fmt) { (void)fmt; return (0); }
static inline int
sndbuf_acquire(struct snd_dbuf *b, uint8_t *from, unsigned int count)
{
	(void)b; (void)from; (void)count;
	return (0);
}
static inline int
sndbuf_dispose(struct snd_dbuf *b, uint8_t *to, unsigned int count)
{
	(void)b; (void)to; (void)count;
	return (0);
}

#define CHN_FOREACH(var, parent, field)					\
	for ((var) = (parent)->children.busy.tqh_first; (var) != NULL;	\
	     (var) = (var)->next_busy)

/* ---- per-child source control ---- */
struct childctl {
	uint32_t give;		/* fixed return: 0 = fill all asked */
	uint32_t pad;		/* extra bytes past asked (overshoot return) */
};
static struct childctl cctl[8];
static int nchildren;
static uint8_t *mixwin;
static uint32_t last_count;

/* ---- memset bomb interceptor (defined before the file under test) ---- */
#define memset fx_memset
static void *fx_memset(void *p, int c, size_t n);

#include "feeder_mixer.c"

static void *
fx_memset(void *p, int c, size_t n)
{
	if (n > (1u << 20)) {
		/* do NOT execute the multi-gigabyte memset; proof recorded */
		printf("MEMSET-BOMB: memset(b + %ld, 0x%02x, %llu) "
		    "[mixer count window was %u => mcnt underflow]\n",
		    (long)((uintptr_t)p - (uintptr_t)mixwin), c,
		    (unsigned long long)n, last_count);
		fflush(stdout);
		fflush(stderr);
		_exit(42);
	}
	return (__builtin_memset(p, c, n));
}

static struct pcm_feeder mixf;
static struct pcm_channel parent, child[8];
static struct pcm_feeder childf[8];
static struct snd_dbuf srcbuf;
static uint8_t srcbuf_storage[1 << 17];

static uint32_t
child_give(int idx, uint32_t asked)
{
	struct childctl *cc = &cctl[idx];
	uint32_t give = (cc->give != 0) ? cc->give : asked;

	return (give + cc->pad);
}

/* per-child feed stub: tagged by feeder desc idx */
uint32_t
fx_source_feed_impl(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
    uint32_t count, void *source)
{
	uint32_t give, i;

	(void)c; (void)source;
	fx_audit_asks++;
	if ((uint64_t)count > fx_audit_max_ask)
		fx_audit_max_ask = count;
	give = child_give(f->desc->idx, count);
	if (fx_in_range(b, give)) {
		for (i = 0; i < give; i++)
			b[i] = (uint8_t)(0x5A ^ (i & 0x1f));
		if ((uintptr_t)b + give - fx_win_base > fx_audit_max_off)
			fx_audit_max_off = (uintptr_t)b + give - fx_win_base;
	}
	fx_audit_given += give;
	return (give);
}

int
fx_feeder_set(struct pcm_feeder *fe, int what, int value)
{
	return (feed_mixer_set(fe, what, value));
}

int
main(void)
{
	uint32_t sz, count_in, ret, found;
	int t;
	uint32_t prng = 0x27d4eb2fu;
	uint64_t fails = 0, checks = 0;

	__builtin_memset(&mixf, 0, sizeof(mixf));
	mixf.desc = &mixf.desc_static;
	mixf.desc->in = mixf.desc->out = SND_FORMAT(AFMT_S16_LE, 6, 0);
	if (feed_mixer_init(&mixf) != 0) {
		printf("mixer init failed\n");
		return (1);
	}
	sz = feed_mixer_info_tab[FEEDMIXER_INFOIDX(mixf.data)].bps *
	    FEEDMIXER_CHANNELS(mixf.data);
	printf("mixer: parent fmt S16_LE 6ch, sz=%u\n", sz);

	srcbuf.buf = srcbuf_storage;
	srcbuf.size = sizeof(srcbuf_storage);
	srcbuf.align = sz;

	__builtin_memset(&parent, 0, sizeof(parent));
	parent.direction = PCMDIR_PLAY;
	parent.flags = CHN_F_HAS_VCHAN;

	nchildren = 2;
	for (t = 0; t < nchildren; t++) {
		__builtin_memset(&child[t], 0, sizeof(child[t]));
		child[t].direction = PCMDIR_PLAY;
		child[t].flags = CHN_F_VIRTUAL | CHN_F_RUNNING;
		child[t].format = SND_FORMAT(AFMT_S16_LE, 6, 0);
		__builtin_memset(&childf[t], 0, sizeof(childf[t]));
		childf[t].desc = &childf[t].desc_static;
		childf[t].desc->idx = t;
		child[t].feeder = &childf[t];
		child[t].next_busy = (t + 1 < nchildren) ? &child[t + 1] : NULL;
	}
	parent.children.busy.tqh_first = &child[0];

	/* ---- M1: free-space values where FXROUND(count_in, sz) > count_in ---- */
	found = 0;
	for (count_in = 1; count_in <= 65536 && found < 500; count_in++) {
		if (SND_FXROUND(count_in, sz) <= count_in)
			continue;
		found++;
		mixwin = fx_win_alloc(1 << 17);
		fx_win_base = (uintptr_t)mixwin;
		fx_win_limit = (uintptr_t)mixwin + (1 << 17);
		fx_arena_base = fx_win_base;
		fx_arena_limit = fx_win_base + (1 << 18);
		fx_audit_max_ask = 0;
		for (t = 0; t < nchildren; t++) {
			cctl[t].give = 0;
			cctl[t].pad = 0;
		}
		last_count = SND_FXROUND(count_in, sz);
		ret = feed_mixer_feed(&mixf, &parent, mixwin, count_in, &srcbuf);
		checks++;
		if (ret > count_in) {
			if (fails < 10)
				printf("FAIL[M1 ring overflow] count_in(free)=%u "
				    "FXROUND=%u rcnt=%u  (+%u bytes past free)\n",
				    count_in, SND_FXROUND(count_in, sz), ret,
				    ret - count_in);
			fails++;
		}
		fx_win_free(mixwin);
	}
	printf("M1 done: %llu checks, %llu fails (rcnt > free, ring overflow)\n",
	    (unsigned long long)checks, (unsigned long long)fails);

	/* ---- M2: child overshoot return => mcnt underflow => memset bomb ---- */
	{
		/* feeder_format observed deltas: entry FXROUND(count, oalign)
		 * can return count+8 at oalign=12 (e.g. 65548 -> 65556) */
		uint32_t overs = 8;
		count_in = 65536;
		mixwin = fx_win_alloc(1 << 17);
		fx_win_base = (uintptr_t)mixwin;
		fx_win_limit = (uintptr_t)mixwin + (1 << 17);
		fx_arena_base = fx_win_base;
		fx_arena_limit = fx_win_base + (1 << 18);
		for (t = 0; t < nchildren; t++) {
			cctl[t].give = 0;
			cctl[t].pad = (t == 0) ? overs : 0;
		}
		last_count = SND_FXROUND(count_in, sz);
		printf("M2: child0 (feeder_format-style) returns count+%u; "
		    "FXROUND(%u+%u, %u) = %u > count %u => mcnt = %u - %u\n",
		    overs, last_count, overs, sz,
		    SND_FXROUND(last_count + overs, sz), last_count,
		    last_count, SND_FXROUND(last_count + overs, sz));
		fflush(stdout);
		ret = feed_mixer_feed(&mixf, &parent, mixwin, count_in, &srcbuf);
		/* reaching here means NO bomb: rcnt stayed within the window */
		printf("M2: no memset bomb (rcnt=%u, window=%u) -> %s\n",
		    ret, last_count, ret <= last_count ? "PASS" : "FAIL");
		if (ret > last_count)
			fails++;
		fx_win_free(mixwin);
	}

	/* soak: random residues with well-behaved children */
	for (t = 0; t < 5000; t++) {
		prng ^= prng << 13; prng ^= prng >> 17; prng ^= prng << 5;
		count_in = 4096 + (prng % 61441);
		mixwin = fx_win_alloc(1 << 17);
		fx_win_base = (uintptr_t)mixwin;
		fx_win_limit = (uintptr_t)mixwin + (1 << 17);
		fx_arena_base = fx_win_base;
		fx_arena_limit = fx_win_base + (1 << 18);
		for (int u = 0; u < nchildren; u++) {
			cctl[u].give = 0;
			cctl[u].pad = 0;
		}
		last_count = SND_FXROUND(count_in, sz);
		ret = feed_mixer_feed(&mixf, &parent, mixwin, count_in, &srcbuf);
		checks++;
		if (ret > count_in) {
			if (fails < 10)
				printf("FAIL[soak] count_in=%u rcnt=%u\n",
				    count_in, ret);
			fails++;
		}
		fx_win_free(mixwin);
	}

	printf("ffmix: checks=%llu failures=%llu -> %s "
	    "(exit 42 == memset bomb)\n",
	    (unsigned long long)checks, (unsigned long long)fails,
	    fails ? "FAIL" : "ALL PASS");
	return (fails != 0);
}