⬢ DragonFlyBSD Kernel Audit
DF-5419 / harness.c
← back to finding ↓ download raw
/*
 * ppbharness — KLD harness for sys/bus/ppbus/ppb_1284.c (DragonFly 6.5-DEVEL)
 *
 * Evidence class: "KLD-harness-with-production-code" (no parallel port in
 * QEMU).  The module compiles the *unmodified* production source files
 * ppb_1284.c and ppb_base.c out of /usr/src/sys/bus/ppbus and drives them
 * against a fully scripted (malicious) IEEE1284 peer implemented as a fake
 * ppbus controller ("fakeppc") on nexus.  The 1284 state machine runs on a
 * "ppbshim" child device whose softc is a real struct ppb_data, exactly as
 * the ppbus driver would provide, so DEVTOSOFTC()/ppb_1284_get_state()/
 * ppb_peripheral_*()/ppb_1284_*() all execute their production code paths.
 *
 * Tests (all output prefixed "PPBHX:"):
 *
 *  DF-5418  ppb_peripheral_negotiate() ppb_1284.c:201-206 spin off-by-one
 *           T1  host accepts BYTE mode but answers the nBUSY poll exactly
 *               on iteration #10  -> post-decrement leaves spin==0 ->
 *               "!spin" is TRUE  -> spurious ENODEV despite success.
 *           T2  identical handshake, host answers on iteration #5 ->
 *               spin==5 -> "!spin" FALSE -> negotiation succeeds (ret 0).
 *               T1 vs T2: one poll of timing flips success/failure.
 *           T3  host stays silent the whole 10-iteration window (timeout):
 *               post-decrement leaves spin==-1 -> "!spin" FALSE ->
 *               the intended timeout branch is DEAD; code falls through,
 *               reads the (scripted 0x55) data register and reports
 *               EINVAL / PPB_MODE_UNSUPPORTED instead of ENODEV timeout.
 *
 *  DF-5419  ppb_1284_read_id() ppb_1284.c:588-606 early returns never set
 *           *read.  T4: peer fails Event 2 (not IEEE1284) -> negotiate
 *           fails -> read_id returns without touching *read; sentinel
 *           0x5A5A5A5A survives in the caller's stack variable.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/systm.h>
#include <sys/types.h>

#include <machine/clock.h>

#include <bus/ppbus/ppbconf.h>
#include <bus/ppbus/ppb_1284.h>

#include "ppbus_if.h"

/* ------------------------------------------------------------------ */
/* scripted malicious peer                                            */
/* ------------------------------------------------------------------ */

static char	hx_script[64];
static int	hx_script_len, hx_script_pos;
static char	hx_default_status;
static int	hx_cycle;			/* alternate 0xC0/0x40 default */
static int	hx_tog;
static char	hx_data;			/* PPB_RDTR answer */
static int	hx_rstr_calls, hx_wctr_calls, hx_wdtr_calls;

static void
hx_reset_peer(const char *script, int len, char defstat, int cycle, char data)
{
	int i;

	for (i = 0; i < 64; i++)
		hx_script[i] = 0;
	for (i = 0; i < len && i < 64; i++)
		hx_script[i] = script[i];
	hx_script_len = len;
	hx_script_pos = 0;
	hx_default_status = defstat;
	hx_cycle = cycle;
	hx_tog = 0;
	hx_data = data;
	hx_rstr_calls = 0;
	hx_wctr_calls = 0;
	hx_wdtr_calls = 0;
}

static char
hx_next_status(void)
{
	if (hx_script_pos < hx_script_len)
		return (hx_script[hx_script_pos++]);
	if (hx_cycle) {
		hx_tog ^= 1;
		return (hx_tog ? (char)0xC0 : (char)0x40);
	}
	return (hx_default_status);
}

/* ------------------------------------------------------------------ */
/* fake ppbus controller (parent): implements the ppbus INTERFACE      */
/* ------------------------------------------------------------------ */

static u_char
fakeppc_io(device_t dev, int opcode, u_char *addr, int cnt, u_char byte)
{
	switch (opcode) {
	case PPB_RSTR:
		hx_rstr_calls++;
		return ((u_char)hx_next_status());
	case PPB_RDTR:
		return ((u_char)hx_data);
	case PPB_WDTR:
		hx_wdtr_calls++;
		break;
	case PPB_WCTR:
		hx_wctr_calls++;
		break;
	default:
		break;
	}
	return (0);
}

static int
fakeppc_setmode(device_t dev, int mode)
{
	/* pretend the chipset accepted every mode */
	return (0);
}

static void
fakeppc_identify(driver_t *drv, device_t parent)
{
	if (device_find_child(parent, "fakeppc", 0) == NULL)
		BUS_ADD_CHILD(parent, parent, 0, "fakeppc", 0);
}

static int
fakeppc_probe(device_t dev)
{
	device_set_desc(dev, "scripted IEEE1284 peer (harness)");
	return (0);
}

static int
fakeppc_attach(device_t dev)
{
	/*
	 * NB: plain device_add_child(), NOT BUS_ADD_CHILD(): DF's
	 * bus_generic_add_child() forwards to dev->parent (nexus), which
	 * would create the child on the wrong bus.  fakeppc prints its
	 * children via the ivars-safe bus_generic_print_child().
	 */
	if (device_add_child(dev, "ppbshim", 0) == NULL)
		return (ENOMEM);
	return (bus_generic_attach(dev));
}

static int
fakeppc_detach(device_t dev)
{
	bus_generic_detach(dev);
	device_delete_children(dev);
	return (0);
}

static device_method_t fakeppc_methods[] = {
	DEVMETHOD(device_identify,	fakeppc_identify),
	DEVMETHOD(device_probe,		fakeppc_probe),
	DEVMETHOD(device_attach,	fakeppc_attach),
	DEVMETHOD(device_detach,	fakeppc_detach),

	/* ppbus interface */
	DEVMETHOD(ppbus_io,		fakeppc_io),
	DEVMETHOD(ppbus_setmode,	fakeppc_setmode),

	DEVMETHOD_END
};

static driver_t fakeppc_driver = {
	"fakeppc",
	fakeppc_methods,
	0,
};

static devclass_t fakeppc_devclass;

DRIVER_MODULE(fakeppc, nexus, fakeppc_driver, fakeppc_devclass, NULL, NULL);

/* ------------------------------------------------------------------ */
/* ppbshim: child whose softc is a real struct ppb_data               */
/* ------------------------------------------------------------------ */

static device_t	hx_bus;

static int
ppbshim_probe(device_t dev)
{
	device_set_desc(dev, "ppb_1284 state machine target (harness)");
	return (0);
}

static struct ppb_data *
hx_ppb(void)
{
	return ((struct ppb_data *)device_get_softc(hx_bus));
}

static void
hx_fresh_state(void)
{
	struct ppb_data *ppb = hx_ppb();

	ppb->state = PPB_FORWARD_IDLE;
	ppb->error = PPB_NO_ERROR;
	ppb->mode = PPB_COMPATIBLE;
}

static void
run_df5418_tests(void)
{
	char script[64];
	struct ppb_data *ppb = hx_ppb();
	int ret, i;

	kprintf("PPBHX: --- DF-5418 ppb_peripheral_negotiate spin off-by-one ---\n");

	/*
	 * T1: host lowers nBUSY exactly on loop iteration #10.
	 * Evals 1..9 read 0xC0 (nBUSY high); eval #10 reads 0x40 -> loop
	 * exits with spin==0.  SELECT recheck reads 0x50 (SELECT set).
	 * Data lines carry 0x01 == BYTE_1284_NORMAL (host accepts BYTE).
	 */
	for (i = 0; i < 9; i++)
		script[i] = (char)0xC0;
	script[9] = (char)0x40;			/* poll #10: nBUSY drops   */
	script[10] = (char)0x50;		/* SELECT recheck: SELECT  */
	hx_reset_peer(script, 11, (char)0x50, 1, (char)0x01);
	hx_fresh_state();
	ret = ppb_peripheral_negotiate(hx_bus, PPB_BYTE, 0);
	kprintf("PPBHX: DF-5418 T1 host-answers-poll-10: ret=%d state=%d "
		"error=%d rstr=%d (correct: ret=0/state=PPB_PERIPHERAL_IDLE; "
		"buggy: ret=ENODEV(%d))\n",
		ret, ppb->state, ppb->error, hx_rstr_calls, ENODEV);

	/*
	 * T2: identical, host answers on iteration #5 -> spin==5.
	 */
	for (i = 0; i < 4; i++)
		script[i] = (char)0xC0;
	script[4] = (char)0x40;
	script[5] = (char)0x50;
	hx_reset_peer(script, 6, (char)0x50, 1, (char)0x01);
	hx_fresh_state();
	ret = ppb_peripheral_negotiate(hx_bus, PPB_BYTE, 0);
	kprintf("PPBHX: DF-5418 T2 host-answers-poll-5 : ret=%d state=%d "
		"error=%d rstr=%d (control: expect ret=0, state=%d)\n",
		ret, ppb->state, ppb->error, hx_rstr_calls,
		PPB_PERIPHERAL_IDLE);

	/*
	 * T3: host silent the whole window (nBUSY never drops), SELECT high
	 * at the recheck, data lines float at 0x55.  Correct: timeout ->
	 * ENODEV.  Buggy: "!spin" dead (spin==-1) -> falls through, matches
	 * neither request mode -> EINVAL + PPB_MODE_UNSUPPORTED.
	 */
	for (i = 0; i < 10; i++)
		script[i] = (char)0xC0;
	script[10] = (char)0xD0;		/* SELECT|nBUSY|nACK set */
	hx_reset_peer(script, 11, (char)0xD0, 1, (char)0x55);
	hx_fresh_state();
	ret = ppb_peripheral_negotiate(hx_bus, PPB_BYTE, 0);
	kprintf("PPBHX: DF-5418 T3 host-silent-timeout : ret=%d state=%d "
		"error=%d (correct: ret=ENODEV(%d); buggy: ret=EINVAL(%d) + "
		"error=PPB_MODE_UNSUPPORTED(%d))\n",
		ret, ppb->state, ppb->error, ENODEV, EINVAL,
		PPB_MODE_UNSUPPORTED);
}

static void
run_df5419_test(void)
{
	char buf[32];
	struct ppb_data *ppb = hx_ppb();
	int ret;
	int read_val = 0x5A5A5A5A;		/* poison sentinel */

	kprintf("PPBHX: --- DF-5419 ppb_1284_read_id *read uninitialized ---\n");

	/*
	 * Peer that never presents PERROR|SELECT|nFAULT with nACK low:
	 * Event 2 of ppb_1284_negotiate() times out -> ENODEV ->
	 * read_id returns at ppb_1284.c:600-601 without ever writing *read.
	 */
	hx_reset_peer(NULL, 0, (char)0x00, 0, (char)0x00);
	hx_fresh_state();
	ret = ppb_1284_read_id(hx_bus, PPB_NIBBLE, buf, sizeof(buf),
			       &read_val);
	kprintf("PPBHX: DF-5419 T4 read_id-negotiate-fail: ret=%d "
		"read=0x%08x state=%d error=%d (correct: read=0; buggy: "
		"read=sentinel 0x5A5A5A5A untouched)\n",
		ret, (unsigned int)read_val, ppb->state, ppb->error);
}

static int
ppbshim_attach(device_t dev)
{
	hx_bus = dev;

	kprintf("PPBHX: harness start (production ppb_1284.c + ppb_base.c)\n");
	run_df5418_tests();
	run_df5419_test();
	kprintf("PPBHX: harness done\n");

	return (0);
}

static int
ppbshim_detach(device_t dev)
{
	return (0);
}

static device_method_t ppbshim_methods[] = {
	DEVMETHOD(device_probe,		ppbshim_probe),
	DEVMETHOD(device_attach,	ppbshim_attach),
	DEVMETHOD(device_detach,	ppbshim_detach),
	DEVMETHOD_END
};

static driver_t ppbshim_driver = {
	"ppbshim",
	ppbshim_methods,
	sizeof(struct ppb_data),
};

static devclass_t ppbshim_devclass;

DRIVER_MODULE(ppbshim, fakeppc, ppbshim_driver, ppbshim_devclass, NULL, NULL);