⬢ DragonFlyBSD Kernel Audit
DF-4952 / ffrec.c
← back to finding ↓ download raw
/*
 * ffrec.c — adversarial harness for the REAL sys/dev/sound/pcm/feeder_mixer.c
 * REC path (feed_mixer_rec), GLM 5.3 pass-2 follow-up to DF-4849.
 *
 * NEW SINK (not covered by DF-4849 :299-368, which is the PLAY path):
 *
 *   feeder_mixer.c:191-204
 *     b->rp = 0; b->rl = 0;
 *     cnt = sndbuf_getsize(b);
 *     maxfeed = SND_FXROUND(SND_FXDIV_MAX, sndbuf_getalign(b));
 *     do {
 *         cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf,
 *             min(cnt, maxfeed), c->bufhard);
 *         if (cnt != 0) {
 *             sndbuf_acquire(b, b->tmpbuf, cnt);   <-- only KASSERT-guarded
 *             cnt = sndbuf_getfree(b);
 *         }
 *     } while (cnt != 0);
 *
 * 1. The feeder directly below the REC mixer (feed_matrix / feed_format —
 *    same FXROUND over-return class proven on REAL code by DF-4846/DF-4847)
 *    returns count+delta for bad-residue requests.  count was
 *    min(bufsize, maxfeed) <= free == bufsize, so acquire(count+delta)
 *    inflates b->rl past b->bufsize (buffer.c:615-635, KASSERT compiled out
 *    on the production/no-INVARIANTS kernel).
 * 2. sndbuf_getfree(b) = bufsize - rl now WRAPS to ~0xFFFFxxxx.
 * 3. min(cnt, maxfeed) == maxfeed == SND_FXROUND(65536, align) ~= 65532 is
 *    then requested -- from a buffer whose tmpbuf is only
 *    round_page(bufsize) bytes (buffer.c:180-195).  chn_wrfeed (the PLAY
 *    caller) clamps want by sndbuf_getsize(); feed_mixer_rec does NOT.
 * 4. The converter (real feeder_matrix code here) computes
 *    src = dst + count - j (up to ~44KB past tmpbuf) and writes the whole
 *    converted block at dst: ~65KB into a ~20KB heap window --
 *    LINEAR HEAP OOB WRITE with converted capture data, repeated every
 *    loop pass while the (wrapped) free never drains.
 *
 * Chain modeled exactly as feeder_chain.c builds it for a REC vchan parent:
 * mixer(head) -> matrix (hw S16_LE 2ch -> soft S16_LE 6ch) -> root -> bufhard.
 * All real code: feeder_mixer.c + feeder_matrix.c + the real generated
 * snd_fxdiv_gen.h.  Only feed_root is stubbed (bounded hw budget, writes
 * where asked -- exactly what the real feed_root/sndbuf_dispose does).
 *
 * exit 42  == linear heap OOB write past tmpbuf proven
 * exit 0   == clean (fixed generator build)
 */
#include "fxshim.h"
#include <err.h>

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

/* real sound.h:206/217 values */
#define	AFMT_SIGNED	(AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE |	\
			 AFMT_S24_BE | AFMT_S16_LE | AFMT_S16_BE |	\
			 AFMT_S8)
#define	AFMT_PASSTHROUGH	AFMT_AC3

#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;
};

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

/* ---- faithful sndbuf_* (buffer.c:500-660), KASSERTs omitted exactly as
 *      they are compiled out on the production guest kernel ---- */
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 uint32_t sndbuf_getfree(struct snd_dbuf *b)
{
	return ((uint32_t)b->size - (uint32_t)b->rl);	/* wraps like kernel */
}
static inline uint32_t sndbuf_getready(struct snd_dbuf *b)
{
	return ((uint32_t)b->rl);
}
static inline uint8_t sndbuf_zerodata(uint32_t fmt)
{
	if (fmt & (AFMT_SIGNED | AFMT_PASSTHROUGH))
		return (0x00);
	else if (fmt & AFMT_MU_LAW)
		return (0x7f);
	else if (fmt & AFMT_A_LAW)
		return (0x55);
	return (0x80);
}

static uint64_t acq_over;	/* acquisitions with count > free */
static uint32_t acq_worst_over;

static inline int
sndbuf_acquire(struct snd_dbuf *b, uint8_t *from, unsigned int count)
{
	int l;
	uint32_t free_before = sndbuf_getfree(b);

	if (count > free_before) {
		acq_over++;
		acq_worst_over = (count - free_before > acq_worst_over) ?
		    count - free_before : acq_worst_over;
		printf("ACQUIRE-OVER: count=%u free=%u (rl=%d size=%u) "
		    "-- KASSERT compiled out, rl -> %d\n",
		    count, free_before, b->rl, b->size, b->rl + (int)count);
	}
	while (count > 0) {
		l = min(count, sndbuf_getsize(b) -
		    ((b->rp + b->rl) % sndbuf_getsize(b)));
		memcpy(sndbuf_getbuf(b) + ((b->rp + b->rl) % b->size), from, l);
		from += l;
		b->rl += l;
		count -= l;
	}
	return (0);
}

static inline int
sndbuf_dispose(struct snd_dbuf *b, uint8_t *to, unsigned int count)
{
	int l;

	while (count > 0) {
		l = min(count, b->size - b->rp);
		if (to != NULL)
			memcpy(to, b->buf + b->rp, l);
		b->rp = (b->rp + l) % b->size;
		b->rl -= l;
		count -= l;
	}
	return (0);
}

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

/* ---- the chain under test, all REAL code ---- */
#include "feeder_matrix.c"
#include "feeder_mixer.c"

/* ---- feed_root stub: bounded hw budget, writes exactly where asked ---- */
static struct pcm_feeder rootf;
static struct pcm_feeder matf_rec;	/* real matrix feeder instance */
static uint64_t hw_budget;		/* bytes of raw capture available */
static uint64_t root_max_write_off;	/* farthest byte written past tmpbuf */

/*
 * FEEDER dispatch: mixer's source == matrix feeder (real code);
 * matrix's source == root stub.
 */
uint32_t
fx_source_feed_impl(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
    uint32_t count, void *source)
{
	if (f == &rootf) {
		uint32_t give = min(count, (uint32_t)hw_budget);
		uintptr_t off;
		uint32_t i;

		hw_budget -= give;
		/* the real feed_root/sndbuf_dispose writes where it is told */
		for (i = 0; i < give; i++)
			b[i] = (uint8_t)(0xC3 ^ (i & 0x1f));
		off = (uintptr_t)b + give - fx_win_base;
		if (off > root_max_write_off)
			root_max_write_off = off;
		return (give);
	}
	if (f == &matf_rec)
		return (feed_matrix_feed(f, c, b, count, source));
	errx(1, "unexpected feeder %p", f);
}

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

static struct pcm_feeder mixf;
static struct pcm_channel parent;
static struct snd_dbuf softbuf, hardbuf;

static uint32_t tmpbuf_sz;		/* round_page(bufsize): kernel alloc */

/* full rear-guard scan (not just first 128 bytes like fx_win_check) */
static uint64_t rear_smashed;		/* smashed rear-guard byte count */
static uint64_t rear_farthest;

static int
scan_rear(uint8_t *win, uint32_t sz)
{
	unsigned char *base = win - FX_GUARD_FRONT;
	uint64_t i, first = 0, last = 0, n = 0;

	for (i = 0; i < FX_GUARD_REAR; i++) {
		if (base[sz + FX_GUARD_FRONT + i] != (uint8_t)0xCA) {
			if (n == 0)
				first = i;
			last = i;
			n++;
		}
	}
	if (n != 0) {
		rear_smashed = n;
		rear_farthest = last + 1;
		printf("REAR-GUARD SMASHED: window=%u first_oob=%llu "
		    "farthest=%llu smashed_bytes=%llu\n",
		    sz, (unsigned long long)first,
		    (unsigned long long)(last + 1),
		    (unsigned long long)n);
		(void)base;
		return (-1);
	}
	return (0);
}

int
main(void)
{
	uint32_t bufsize, maxfeed, sz, ret;
	int oob = 0;
	const char *where;

	/* mixer: REC parent fmt S16_LE 6ch (align 12) */
	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)
		errx(1, "mixer init failed");
	sz = feed_mixer_info_tab[FEEDMIXER_INFOIDX(mixf.data)].bps *
	    FEEDMIXER_CHANNELS(mixf.data);

	/* real matrix feeder: hw S16_LE 2ch -> soft S16_LE 6ch */
	memset(&matf_rec, 0, sizeof(matf_rec));
	matf_rec.desc = &matf_rec.desc_static;
	matf_rec.desc->in = SND_FORMAT(AFMT_S16_LE, 2, 0);
	matf_rec.desc->out = SND_FORMAT(AFMT_S16_LE, 6, 0);
	if (feed_matrix_init(&matf_rec) != 0)
		errx(1, "matrix init failed");
	matf_rec.source = &rootf;

	/* pick a real-kernel-formable bufsize (blkcnt>=2, blksz>=16, even)
	 * inside the generator's bad-residue window for divisor 12:
	 * SND_FXROUND(bufsize, 12) > bufsize == the entry over-return that
	 * DF-4846/DF-4847 prove feed_format/feed_matrix perform. */
	bufsize = 0;
	for (uint32_t v = 8194; v <= 65534; v += 2) {
		if (v / 2 < 16)
			continue;
		if (SND_FXROUND(v, sz) > v) {
			bufsize = v;
			break;
		}
	}
	if (bufsize == 0) {
		/* fixed-generator build: the entry-misround class is gone
		 * everywhere in [8194..65534] -- the REC wrap cannot be
		 * armed by any real feeder.  That IS the pass condition. */
		printf("ffrec: no bad-residue bufsize exists -- over-return "
		    "precondition eliminated; feed_mixer_rec cannot wrap\n");
		printf("ffrec: clean\n");
		return (0);
	}

	maxfeed = SND_FXROUND(SND_FXDIV_MAX, sz);
	tmpbuf_sz = (bufsize + 4095) & ~4095u;	/* round_page, buffer.c:184 */

	printf("ffrec: parent REC vchan fmt S16_LE 6ch sz=%u  "
	    "hw fmt S16_LE 2ch (matrix below mixer)\n", sz);
	printf("bufsoft: bufsize=%u (bad residue: FXROUND(%u,%u)=%u > %u)  "
	    "tmpbuf allocsize=%u (round_page)  maxfeed=%u  FXDIV_MAX=%u\n",
	    bufsize, bufsize, sz, (uint32_t)SND_FXROUND(bufsize, sz),
	    bufsize, tmpbuf_sz, maxfeed, (uint32_t)SND_FXDIV_MAX);

	/* bufsoft: ring + tmpbuf both kernel-sized */
	softbuf.buf = fx_win_alloc(bufsize);
	softbuf.tmpbuf = fx_win_alloc(tmpbuf_sz);
	softbuf.size = bufsize;
	softbuf.align = sz;
	softbuf.rp = 0;
	softbuf.rl = 0;
	hardbuf.size = 65536;

	fx_win_base = (uintptr_t)softbuf.tmpbuf;
	fx_win_limit = fx_win_base + tmpbuf_sz;
	fx_arena_base = fx_win_base - FX_GUARD_FRONT;
	fx_arena_limit = fx_win_base + tmpbuf_sz + FX_GUARD_REAR;

	memset(&parent, 0, sizeof(parent));
	parent.direction = PCMDIR_REC;
	parent.flags = CHN_F_HAS_VCHAN | CHN_F_RUNNING;
	parent.feeder = &mixf;
	mixf.source = &matf_rec;
	parent.bufsoft = &softbuf;
	parent.bufhard = &hardbuf;
	parent.children.busy.tqh_first = NULL;	/* children not needed:
						 * the OOB happens in the
						 * acquire loop itself */

	hw_budget = 65536;		/* one full hw buffer of capture data */

	printf("---- feed_mixer_rec (real feeder_mixer.c + real "
	    "feeder_matrix.c) ----\n");
	ret = feed_mixer_feed(&mixf, &parent, NULL, 0, NULL);

	printf("returned %d; bufsoft rl=%d size=%u free=0x%08x  "
	    "acquire-over count=%llu worst=%u\n",
	    ret, softbuf.rl, softbuf.size, sndbuf_getfree(&softbuf),
	    (unsigned long long)acq_over, acq_worst_over);
	printf("root max write offset past tmpbuf base: %llu "
	    "(window %u)\n",
	    (unsigned long long)root_max_write_off, tmpbuf_sz);

	/* OOB write proof: rear guard of the tmpbuf window */
	if (scan_rear(softbuf.tmpbuf, tmpbuf_sz) != 0) {
		oob = 1;
	} else {
		where = NULL;
		if (fx_win_check(softbuf.tmpbuf, tmpbuf_sz, &where) != 0) {
			printf("front/guard: %s\n", where);
			oob = 1;
		}
	}
	if (acq_over != 0 && !oob)
		printf("NOTE: rl inflated past bufsize but writes stayed in "
		    "alloc slack this run\n");

	fx_win_free(softbuf.tmpbuf);
	fx_win_free(softbuf.buf);

	printf("ffrec: %s  (exit 42 == linear heap OOB write past tmpbuf)\n",
	    oob ? "OOB PROVEN" : "clean");
	return (oob ? 42 : 0);
}