⬢ DragonFlyBSD Kernel Audit
DF-4546 / lm_detach_nullwrite.c
← back to finding ↓ download raw
/*
 * DF-4546 — lm78.c lm_detach on a zero-sensor attach (W83791SD)
 * =================================================================
 * Lifted-copy harness (macro-level; the in-kernel path needs an LM78-class
 * chip on the ISA bus, absent on the audit guest).
 *
 * Faithful replica of:
 *   - struct ksensordev (sys/sys/sensors.h:156-166) in a zeroed softc
 *   - the DragonFly TAILQ macros verbatim from sys/sys/queue.h:646-662
 *   - sensordev_install / sensordev_deinstall list logic
 *     (sys/kern/kern_sensors.c:77-109 / 153-176 -- sysctl parts dropped)
 *   - the driver flow: wb_match() W83791SD case (lm78.c:590-592) which sets
 *     NO sensor table -> numsensors == 0 -> lm_attach() early return
 *     (lm78.c:427-429, sensordev never installed) -> lm_detach()
 *     (lm78.c:445-458) which UNCONDITIONALLY calls sensordev_deinstall()
 *
 * TAILQ_REMOVE(&sensordev_list, &zeroed->sensordev, list) on a never
 * inserted (zeroed) node executes, per sys/sys/queue.h:646-662:
 *     tqe_next == NULL  ->  head->tqh_last = NULL   (global list corrupted)
 *     *(tqe_prev == NULL) = tqe_next                (write to address 0)
 * i.e. a NULL-pointer WRITE plus corruption of the global sensordev_list
 * head -- in the kernel this is an immediate panic (and any later
 * TAILQ_INSERT_TAIL dereferences the NULL tqh_last).
 *
 * Control: a normal chip (numsensors > 0) installs the device and detaches
 * cleanly.
 *
 * Build: cc -O2 -Wall -o lm_detach_nullwrite lm_detach_nullwrite.c
 * Run:   ./lm_detach_nullwrite
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

#define SENSOR_MAX_TYPES	18		/* sys/sys/sensors.h enum */

#define TAILQ_HEAD(name, type)						\
struct name {								\
	struct type *tqh_first;	/* first element */			\
	struct type **tqh_last;	/* addr of last next element */		\
}

#define TAILQ_ENTRY(type)						\
struct {								\
	struct type *tqe_next;	/* next element */			\
	struct type **tqe_prev;	/* address of previous next element */	\
}

/* sys/sys/queue.h:646-662 -- verbatim (QMD_* debug hooks are no-ops) */
#define	TAILQ_REMOVE(head, elm, field) do {				\
	if ((TAILQ_NEXT((elm), field)) != NULL)				\
		TAILQ_NEXT((elm), field)->field.tqe_prev = 		\
		    (elm)->field.tqe_prev;				\
	else {								\
		(head)->tqh_last = (elm)->field.tqe_prev;		\
	}								\
	*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field);		\
} while (0)

#define	TAILQ_INSERT_TAIL(head, elm, field) do {			\
	(elm)->field.tqe_next = NULL;					\
	(elm)->field.tqe_prev = (head)->tqh_last;			\
	*(head)->tqh_last = (elm);					\
	(head)->tqh_last = &(elm)->field.tqe_next;			\
} while (0)

#define	TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)

/* sys/sys/sensors.h:156-166 */
struct ksensordev {
	TAILQ_ENTRY(ksensordev)	list;
	int num;
	char xname[16];
	int maxnumt[SENSOR_MAX_TYPES];
	int sensors_count;
	struct { void *slh_first; } sensors_list;
	void *oid;
	void *clist;
};

TAILQ_HEAD(sensordev_list, ksensordev);

/* the global from kern_sensors.c:39-40 */
static struct sensordev_list sensordev_list =
    { NULL, (struct ksensordev **)&sensordev_list.tqh_first };

static volatile unsigned long fault_addr;

static void sigsegv(int sig, siginfo_t *si, void *uc)
{
	char msg[96];
	int len;
	(void)sig; (void)uc;
	fault_addr = (unsigned long)si->si_addr;
	len = snprintf(msg, sizeof(msg),
	    "  >>> SIGSEGV: kernel-equivalent NULL-pointer WRITE at 0x%lx\n",
	    fault_addr);
	write(1, msg, len);
	_exit(1);
}

/* kern_sensors.c:77-109 (sysctl parts elided) */
static void sensordev_install(struct ksensordev *sensdev)
{
	sensdev->num = 0;
	TAILQ_INSERT_TAIL(&sensordev_list, sensdev, list);
}

/* kern_sensors.c:153-176 (sysctl parts elided) */
static void sensordev_deinstall(struct ksensordev *sensdev)
{
	TAILQ_REMOVE(&sensordev_list, sensdev, list);
}

/* lm_softc (lm78var.h:139-156) -- only the fields that matter */
struct lm_softc {
	struct ksensordev sensordev;
	unsigned numsensors;
};

/* lm78.c:445-458 (+ fix.diff guard) */
static int lm_detach_patched;

static int lm_detach(struct lm_softc *sc)
{
	int i;

	if (lm_detach_patched && sc->numsensors == 0)
		return (0);			/* fix.diff: never installed */

	/* Remove sensors */
	sensordev_deinstall(&sc->sensordev);		/* unconditional! */
	for (i = 0; i < (int)sc->numsensors; i++)
		;						/* ... */

	return 0;
}

/* lm78.c:422-443 */
static void lm_attach(struct lm_softc *sc)
{
	unsigned i;

	if (sc->numsensors == 0)		/* W83791SD path: bail here */
		return;				/* sensordev NEVER installed */

	for (i = 0; i < sc->numsensors; ++i)
		;
	sensordev_install(&sc->sensordev);
}

static struct sigaction sa;

static int run_case(const char *name, unsigned numsensors, int expect_fault)
{
	struct lm_softc *sc;
	int pid, status, ret;

	printf("=== %s (numsensors=%u) ===\n", name, numsensors);

	if ((pid = fork()) < 0) { perror("fork"); exit(2); }
	if (pid == 0) {
		sigaction(SIGSEGV, &sa, NULL);
		lm_detach_patched = (strstr(name, "PATCHED") != NULL);
		sc = calloc(1, sizeof(*sc));	/* newbus zeroes the softc */
		sc->numsensors = numsensors;
		lm_attach(sc);			/* probe picked the chip    */
		printf("  attach done (sensordev %sinstalled), detaching...\n",
		    numsensors ? "" : "never ");
		lm_detach(sc);
		printf("  detach returned cleanly\n");
		_exit(0);
	}
	waitpid(pid, &status, 0);
	ret = WEXITSTATUS(status);
	printf("  %s\n\n", ret == 1 ? "NULL-WRITE FAULT REPRODUCED" :
	    (expect_fault ? "NOT REPRODUCED" : "OK (clean, as expected)"));
	return ret == 1;
}

int main(void)
{
	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = sigsegv;
	sa.sa_flags = SA_SIGINFO;
	sigfillset(&sa.sa_mask);

	printf("DF-4546 lifted-copy harness: lm_detach -> sensordev_deinstall "
	    "of never-installed ksensordev (lm78.c:445-458 + "
	    "sys/sys/queue.h:646)\n\n");

	/* control: real chip with sensors (e.g. W83792D, 19 sensors) */
	run_case("CONTROL normal chip", 19, 0);
	/* bug: wb_match() W83791SD sets no sensor table (lm78.c:590-592) */
	int f = run_case("BUG W83791SD zero-sensor attach", 0, 1);
	/* patched: same zero-sensor attach, guarded detach -> clean */
	run_case("PATCHED W83791SD guarded detach", 0, 0);

	printf("%s: zero-sensor attach + detach writes through NULL and "
	    "corrupts sensordev_list.tqh_last\n",
	    f ? "REPRODUCED" : "NOT REPRODUCED");
	return f ? 0 : 1;
}