⬢ DragonFlyBSD Kernel Audit
DF-2423 / dfx2423.c
← back to finding ↓ download raw
/*
 * DF-2423 - verification harness (verbatim-interposition, kernel-compiled).
 *
 * Target: sys/dev/disk/ahci/ahci_pm.c ahci_pm_read (:976-978) and
 * ahci_pm_write (:1013-1015) call ahci_ata_get_xfer() and deref the
 * result (xa->fis at offsetof(struct ata_xfer, fis) == 0) without a
 * NULL check.  ahci_ata_get_xfer (ahci.c:3670-3689) returns NULL when
 * the per-port CCB free list (ap->ap_ccb_free) is empty, i.e. when all
 * sc_ncmds slots are in flight (NCQ I/O).
 *
 * Rig: this file is compiled INTO the kernel.  It #includes the
 * PRISTINE ahci_pm.c translation unit (sha256-gated by build.sh) and
 * drives the REAL ahci_pm_read/ahci_pm_write with a fabricated
 * ahci_port / ata_port / ahci_ccb pool state.  Because the stock
 * X86_64_GENERIC config builds the ahci driver into the kernel
 * (config line 65: "device ahci"), every symbol ahci_pm.c *defines*
 * is renamed with a dfx_ prefix via the preprocessor - the function
 * BODIES are compiled byte-verbatim, only the exported names change.
 * Only the three pool-path functions are interposed:
 *
 *   ahci_ata_get_xfer -> dfx_ahci_ata_get_xfer  (fabricated pool:
 *        returns NULL when dfx_pool_empty==1, faithfully modeling
 *        ahci_get_ccb()==NULL at ahci.c:3674-3678; otherwise performs
 *        the same bzero/at/fis->type preset as ahci.c:3684-3688)
 *   ahci_ata_cmd      -> dfx_ahci_ata_cmd       (fabricated completing
 *        device: fills rfis, sets ATA_S_COMPLETE, invokes the REAL
 *        static ahci_pm_dummy_done hook assigned by ahci_pm_read)
 *   ahci_ata_put_xfer -> dfx_ahci_ata_put_xfer  (records the put,
 *        sets state=ATA_S_PUT as ahci_put_ccb does, ahci.c:3317-3330)
 *
 * Every other external the TU references (ahci_pread, ahci_poll, ...)
 * links against the REAL in-kernel ahci driver; none of them are on
 * the exercised paths.
 *
 * Trigger (root): sysctl hw.dfx2423_phase=N
 *   1 = control:    pool has a free CCB; real pm_read + pm_write run
 *                   to completion against the fabricated device.
 *   2 = exhausted pool, real ahci_pm_read  -> expect Fatal trap 12,
 *                   fault VA 0x0 (load of xa->fis, offset 0), at
 *                   ahci_pm.c:978 (compiled as dfx_pm_read).
 *   3 = exhausted pool, real ahci_pm_write -> expect Fatal trap 12,
 *                   fault VA 0x0, at ahci_pm.c:1015 (dfx_pm_write).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysctl.h>

/* ---- interpose the CCB pool path (unit-under-test boundary) ---- */
#define ahci_ata_get_xfer	dfx_ahci_ata_get_xfer
#define ahci_ata_put_xfer	dfx_ahci_ata_put_xfer
#define ahci_ata_cmd		dfx_ahci_ata_cmd

/* ---- rename TU exports; bodies byte-verbatim ---- */
#define ahci_pm_port_init	dfx_pm_port_init
#define ahci_pm_port_probe	dfx_pm_port_probe
#define ahci_pm_identify	dfx_pm_identify
#define ahci_pm_hardreset	dfx_pm_hardreset
#define ahci_pm_softreset	dfx_pm_softreset
#define ahci_pm_phy_status	dfx_pm_phy_status
#define ahci_pm_check_good	dfx_pm_check_good
#define ahci_pm_read		dfx_pm_read	/* ahci_pm.c:970 */
#define ahci_pm_write		dfx_pm_write	/* ahci_pm.c:1007 */

#include "../../disk/ahci/ahci_pm.c"	/* PRISTINE TU, sha256-gated */

/* ================= fabrications (structs now defined) ================= */

static struct ata_fis_h2d dfx_h2d;	/* cmd FIS backing memory      */
static struct ahci_ccb	dfx_ccb;	/* the single fabricated CCB   */
static struct ata_port	dfx_at;		/* fake PM control target 15   */
static struct ahci_port	dfx_ap;		/* fake ahci_port              */

static int	dfx_pool_empty;		/* 1 = free list empty, as if all
					 * sc_ncmds slots are in flight */
static int	dfx_nget, dfx_nput, dfx_ncmd, dfx_ndone;

/*
 * Fabricated ahci_ata_get_xfer - mirrors ahci.c:3669-3689 exactly.
 */
struct ata_xfer *
dfx_ahci_ata_get_xfer(struct ahci_port *ap, struct ata_port *at)
{
	++dfx_nget;
	if (dfx_pool_empty) {
		/* ahci.c:3674-3678: ahci_get_ccb() found the free list
		 * empty and returned NULL -> get_xfer returns NULL. */
		kprintf("DFX2423: get_xfer: CCB pool EXHAUSTED -> NULL "
		    "(models ahci.c:3675-3678)\n");
		return (NULL);
	}
	/* ahci.c:3684-3688 */
	bzero(dfx_ccb.ccb_xa.fis, sizeof(*dfx_ccb.ccb_xa.fis));
	dfx_ccb.ccb_xa.at = at;
	dfx_ccb.ccb_xa.fis->type = ATA_FIS_TYPE_H2D;
	dfx_ccb.ccb_xa.state = ATA_S_SETUP;	/* as ahci_get_ccb ahci.c:3304 */
	return (&dfx_ccb.ccb_xa);
}

/*
 * Fabricated completing device - stands in for ahci_ata_cmd+chip.
 */
int
dfx_ahci_ata_cmd(struct ata_xfer *xa)
{
	++dfx_ncmd;
	/* fabricate the rfis the real code composes datap from */
	xa->rfis.sector_count = 0xAA;
	xa->rfis.lba_low	= 0xBB;
	xa->rfis.lba_mid	= 0xCC;
	xa->rfis.lba_high	= 0xDD;
	xa->state = ATA_S_COMPLETE;
	if (xa->complete) {
		++dfx_ndone;	/* the REAL static ahci_pm_dummy_done hook,
				 * assigned by ahci_pm_read (:985) /
				 * ahci_pm_write (:1026) */
		xa->complete(xa);
	}
	return (ATA_S_COMPLETE);
}

/*
 * Fabricated ahci_ata_put_xfer - as ahci_put_ccb (ahci.c:3317-3330).
 */
void
dfx_ahci_ata_put_xfer(struct ata_xfer *xa)
{
	++dfx_nput;
	if (xa->state != ATA_S_PUT)
		xa->state = ATA_S_PUT;
	++xa->serial;
}

static void
dfx_report_fis(const char *tag)
{
	kprintf("DFX2423: %s fis: type=%02x flags=%02x cmd=%02x feat=%02x "
	    "dev=%02x scnt=%02x lba=%02x,%02x,%02x ctl=%02x\n",
	    tag, dfx_h2d.type, dfx_h2d.flags, dfx_h2d.command,
	    dfx_h2d.features, dfx_h2d.device, dfx_h2d.sector_count,
	    dfx_h2d.lba_low, dfx_h2d.lba_mid, dfx_h2d.lba_high,
	    dfx_h2d.control);
}

/*
 * Phase 1 (control): a free CCB exists.  The real pm_read must:
 *   - build the H2D FIS in dfx_h2d (read PM reg, target 15):
 *       type=27 flags=9f cmd=e4(ATA_C_READ_PM) feat=20(EINFO)
 *       dev=4f(15|ATA_H2D_DEVICE_LBA) ctl=08
 *   - get ATA_S_COMPLETE from the fabricated device and compose
 *     datap = 0xAA | 0xBB<<8 | 0xCC<<16 | 0xDD<<24 = 0xddccbbaa
 * and the real pm_write (target 3, SCTL, 0xa1b2c3d4):
 *       cmd=e8 feat=02 dev=43 scnt=d4 lba=c3,b2,a1 ctl=08
 */
static void
dfx_phase_control(void)
{
	u_int32_t data;
	int rv;

	dfx_pool_empty = 0;
	dfx_nget = dfx_nput = dfx_ncmd = dfx_ndone = 0;
	kprintf("DFX2423: phase1 CONTROL: free CCB available\n");

	rv = dfx_pm_read(&dfx_ap, 15, SATA_PMREG_EINFO, &data);
	kprintf("DFX2423: pm_read(15, EINFO) rv=%d data=%08x "
	    "(expect rv=0 data=ddccbbaa)\n", rv, data);
	dfx_report_fis("read ");

	rv = dfx_pm_write(&dfx_ap, 3, SATA_PMREG_SCTL, 0xA1B2C3D4);
	kprintf("DFX2423: pm_write(3, SCTL, a1b2c3d4) rv=%d (expect 0)\n", rv);
	dfx_report_fis("write");

	kprintf("DFX2423: counters get=%d cmd=%d done=%d put=%d "
	    "(expect 2/2/2/2)\n", dfx_nget, dfx_ncmd, dfx_ndone, dfx_nput);
}

static int
dfx2423_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, phase, rv;
	u_int32_t data;

	phase = 0;
	if (req->newptr == NULL)
		return (0);
	error = SYSCTL_IN(req, &phase, sizeof(phase));
	if (error)
		return (error);

	switch (phase) {
	case 1:
		dfx_phase_control();
		break;
	case 2:
		kprintf("DFX2423: phase2: CCB pool EXHAUSTED, calling REAL "
		    "ahci_pm_read (ahci_pm.c:976) - expect NULL deref of "
		    "xa->fis at offset 0\n");
		dfx_pool_empty = 1;
		rv = dfx_pm_read(&dfx_ap, 15, SATA_PMREG_EINFO, &data);
		dfx_pool_empty = 0;
		kprintf("DFX2423: pm_read returned %d - NO CRASH\n", rv);
		break;
	case 3:
		kprintf("DFX2423: phase3: CCB pool EXHAUSTED, calling REAL "
		    "ahci_pm_write (ahci_pm.c:1013) - expect NULL deref of "
		    "xa->fis at offset 0\n");
		dfx_pool_empty = 1;
		rv = dfx_pm_write(&dfx_ap, 3, SATA_PMREG_SCTL, 0xA1B2C3D4);
		dfx_pool_empty = 0;
		kprintf("DFX2423: pm_write returned %d - NO CRASH\n", rv);
		break;
	default:
		return (EINVAL);
	}
	return (0);
}

SYSCTL_PROC(_hw, OID_AUTO, dfx2423_phase, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfx2423_sysctl, "I",
    "DF-2423: 1=control 2=exhausted-pool-read 3=exhausted-pool-write");

static void
dfx2423_init(void *arg __unused)
{
	strlcpy(dfx_ap.ap_name, "fakep0", sizeof(dfx_ap.ap_name));
	dfx_ap.ap_ata[15] = &dfx_at;			/* PM control target */
	dfx_ap.ap_pmcount = 4;

	strlcpy(dfx_at.at_name, "fakep0.15", sizeof(dfx_at.at_name));
	dfx_at.at_target = 15;
	dfx_at.at_probe = ATA_PROBE_GOOD;
	dfx_at.at_type = ATA_PORT_T_PM;

	dfx_ccb.ccb_xa.fis = &dfx_h2d;
	dfx_ccb.ccb_slot = 1;
	dfx_ccb.ccb_port = &dfx_ap;
	dfx_ccb.ccb_xa.state = ATA_S_PUT;

	kprintf("DFX2423: harness initialized (verbatim ahci_pm.c TU; "
	    "trigger: sysctl hw.dfx2423_phase=1|2|3)\n");
}
SYSINIT(dfx2423, SI_SUB_DRIVERS, SI_ORDER_ANY, dfx2423_init, NULL);