⬢ DragonFlyBSD Kernel Audit
DF-2567 / rig.c
← back to finding ↓ download raw
/*
 * DF-2567 rig -- smb_t2_request_int epilogue use-after-free READ of
 * rqp->sr_flags (smb_rq.c:740-745):
 *
 *	freerq:
 *		smb_rq_done(rqp);		 <- kfree(rqp, M_SMBRQ)
 *		if (error) {
 *			if (rqp->sr_flags & SMBR_RESTART)	 <- READ AFTER FREE
 *
 * Verbatim: smb_rq_done() extracted from smb_rq.c; the epilogue lines
 * 740-745 included verbatim.  The rig drives them exactly: rqp freed,
 * then sr_flags read -- and shows the slot can be re-occupied by a
 * concurrent M_SMBRQ allocation before the read (type-confused bytes
 * interpreted as flags).
 *
 * sysctl kern.dfv2567.run: 0 = vuln demo (free -> reallocate -> read),
 * 1 = control (read before free), 2 = FIXED model (read flags before done).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>

#include <netproto/smb/smb.h>
#include <netproto/smb/smb_conn.h>
#include <netproto/smb/smb_rq.h>
#include <netproto/smb/smb.h>

static char df_status[4096];

/* netsmb.ko is not shipped standalone on this guest; use a local malloc
 * type of identical size semantics for the M_SMBRQ slab (documented
 * divergence: slab LIFO reuse is per-zone-size, identical behavior). */
MALLOC_DEFINE(M_DF2567, "df2567", "DF-2567 rig objects (M_SMBRQ-sized)");
#define M_SMBRQ M_DF2567

/* local stand-ins for netsmb helpers (module not shipped standalone on
 * this guest); with zeroed chains their real bodies are no-ops
 * (smb_subr.c: NULL top guard + m_freem/smb_sl_destroy) */
static void mb_done_stub(struct mbchain *mbp);
static void md_done_stub(struct mdchain *mdp);
static void smb_sl_destroy_stub(struct smb_slock *sl);
#define mb_done mb_done_stub
#define md_done md_done_stub
#define smb_sl_destroy smb_sl_destroy_stub

static void
mb_done_stub(struct mbchain *mbp)
{
	if (mbp->mb_top) { m_freem(mbp->mb_top); mbp->mb_top = NULL; }
}
static void
md_done_stub(struct mdchain *mdp)
{
	if (mdp->md_top) { m_freem(mdp->md_top); mdp->md_top = NULL; }
}
static void
smb_sl_destroy_stub(struct smb_slock *sl) { /* spin lock teardown: no-op */ }

/* ---- VERBATIM smb_rq_done from smb_rq.c ---- */
void
smb_rq_done(struct smb_rq *rqp)
{
	mb_done(&rqp->sr_rq);
	md_done(&rqp->sr_rp);
	smb_sl_destroy(&rqp->sr_slock);
	if (rqp->sr_flags & SMBR_ALLOCED)
		kfree(rqp, M_SMBRQ);
}
/* ---- VERBATIM epilogue smb_rq.c:739-746 pattern ---- */
static __inline int
df_epilogue_read(struct smb_rq *rqp)
{
	return (rqp->sr_flags & SMBR_RESTART);	/* READ AFTER smb_rq_done */
}

static void
df_run2567(int mode)
{
	struct smb_rq *rqp;
	struct smb_rq *occupier;
	int readback;
	char *p = df_status;
	int t = 0;

	rqp = kmalloc(sizeof(*rqp), M_SMBRQ, M_WAITOK | M_ZERO);
	rqp->sr_flags = SMBR_ALLOCED;		/* smb_rq_alloc sets this */

	if (mode == 1) {
		/* control: read BEFORE the free (correct code) */
		readback = (rqp->sr_flags & SMBR_RESTART);
		smb_rq_done(rqp);
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "DF2567[ctl]: flags read before free -> 0x%x\n", readback);
		return;
	}
	if (mode == 2) {
		/* FIXED model: stash flags, then smb_rq_done, use stash */
		int stash = rqp->sr_flags;
		smb_rq_done(rqp);
		readback = (stash & SMBR_RESTART);
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "DF2567[fixed]: flags stashed before free -> 0x%x\n",
		    readback);
		return;
	}

	/* vulnerable order: free, reoccupy the slot, THEN read */
	smb_rq_done(rqp);			/* rqp freed here */

	/* concurrent M_SMBRQ allocation lands in the freed slot (LIFO) */
	occupier = kmalloc(sizeof(*occupier), M_SMBRQ, M_WAITOK | M_ZERO);
	occupier->sr_flags = 0x0010;		/* attacker-influenced bytes */

	readback = df_epilogue_read(rqp);	/* UAF READ of freed rqp */

	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2567[vuln]: smb_rq_done freed rqp; slot reoccupied with "
	    "sr_flags=0x10; epilogue read rqp->sr_flags -> 0x%x\n",
	    readback);
	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2567: occupier==rqp? %s ; RESULT=%s\n",
	    (void *)occupier == (void *)rqp ? "YES (same slot)" : "no",
	    readback != 0 ?
	    "UAF READ: the restart decision was driven by bytes written to "
	    "the freed slot AFTER the free" :
	    "UAF read happened but slot not reoccupied this run");
	kfree(occupier, M_SMBRQ);
}

static int
dfv2567_sysctl(SYSCTL_HANDLER_ARGS)
{
	char out[4096];
	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: df_run2567(0); break;
		case 1: df_run2567(1); break;
		case 2: df_run2567(2); 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, dfv2567_run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfv2567_sysctl, "I", "dfv2567: 0=vuln 1=ctl 2=fixed");
SYSCTL_PROC(_kern, OID_AUTO, dfv2567_status, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, dfv2567_sysctl, "A", "dfv2567 status");

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