⬢ DragonFlyBSD Kernel Audit
DF-2212 / df_ttm2212.c
← back to finding ↓ download raw
/*
 * df_ttm2212.c - DF-2212 verification harness (kernel-compiled verbatim rig)
 *
 * Finding: sys/dev/drm/ttm/ttm_module.c:98 - ttm_exit() ends with
 *   wait_event(exit_q, atomic_read(&device_released) == 1);
 * but the only compiled setter of device_released=1 lives in a #if 0 block
 * (ttm_module.c:48-52) and the init SUCCESS path explicitly zeroes it
 * (ttm_module.c:77). wait_event maps to an UNINTERRUPTIBLE tsleep loop
 * (sys/dev/drm/include/linux/wait.h:110-172: flags=0 -> no PCATCH ->
 * TASK_UNINTERRUPTIBLE). Nothing in sys/ sets device_released after a
 * successful ttm_init(), so the thread that runs ttm_exit() at module
 * unload - linker_file_sysuninit() called from linker_file_unload()
 * (sys/kern/kern_linker.c:535) in the kldunload(2) syscall thread -
 * sleeps forever and cannot be killed.
 *
 * RIG DESIGN (verbatim interposition, kernel-compiled):
 *   - <dev/drm/ttm/ttm_module.c>  : the REAL production file from the tree
 *     being built, included verbatim (sha256-gated by build.sh before and
 *     after; the harness must not modify it). module_init(ttm_init) fires
 *     at boot via SYSINIT (include/linux/module.h:58-59), which is the
 *     control: a successful ttm_init() leaves device_released == 0.
 *   - <dev/drm/linux_wait.c>      : the lkpi wait-queue support, verbatim;
 *     normally compiled only into drm.ko ('optional drm'), provides the
 *     prepare_to_wait()/__wait_event_prefix()/finish_wait()/
 *     autoremove_wake_function() symbols that wait_event calls.
 *   - drm_class_device_register()/drm_class_device_unregister() are
 *     provided here with EXACTLY the shipped semantics from
 *     sys/dev/drm/drm_sysfs.c:60-63 (return 0) and :73-75 (empty), plus
 *     observation counters - the only additions. dev_set_name() is already
 *     a static inline no-op returning 0 (include/linux/device.h:106-110).
 *
 * Test interface (root-only sysctls under hw.df_ttm2212):
 *   trigger=1  call the REAL ttm_exit() in the CALLING thread - exactly
 *              what the module-unload path does. Stock kernel: never
 *              returns; the sysctl(8) process becomes unkillable.
 *   cycle=N    run N x (ttm_init(); ttm_exit()) - post-fix sanity.
 *   status     dump rig state incl. device_released, thread wait state.
 *   kill9=1    marker correlated with `kill -9` from another process.
 *
 * DISCLOSED JIG: dfv_shutdown_safety_fn() would release the wait before a
 * module-style sysuninit walk - but DF never walks the *built-in*
 * sysuninit_set at shutdown (only kern_linker.c:188-231 walks it, for
 * loaded modules), so on this compiled-in rig it is inert; it exists so
 * the rig could never wedge a shutdown path even if that ever changed.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/proc.h>
#include <sys/callout.h>

/* ================= VERBATIM INTERPOSITION (sha256-gated) ============== */
#include <dev/drm/ttm/ttm_module.c>	/* THE production file            */
#include <dev/drm/linux_wait.c>		/* lkpi wait support, verbatim    */
#include <dev/drm/linux_sched.c>	/* lkpi sched support, verbatim:  *
					 * the lkpi `current' macro lazily*
					 * allocates curthread's task_str *
					 * via linux_task_alloc() (defined*
					 * here; M_DRM itself is core,    *
					 * kern_slaballoc.c:202)          */
/* ====================================================================== */

/*
 * Production-shape externs (identical semantics to drm_sysfs.c:60-75,
 * which the stock kernel does not compile - it is 'optional drm').
 */
static int dfv_register_calls;
static int dfv_unregister_calls;

int
drm_class_device_register(struct device *dev)
{
	atomic_add_int(&dfv_register_calls, 1);
	return (0);		/* == sys/dev/drm/drm_sysfs.c:60-63 */
}

void
drm_class_device_unregister(struct device *dev)
{
	atomic_add_int(&dfv_unregister_calls, 1);
	/* == sys/dev/drm/drm_sysfs.c:73-75: empty body */
}

/* ------------------------------ rig state ----------------------------- */
static thread_t	exit_td;	/* thread stuck in (or running) ttm_exit */
static int	exit_t0;	/* ticks at ttm_exit entry                */
static int	exit_done = 1;	/* ttm_exit returned?                     */
static int	exit_ms = -1;	/* measured ttm_exit duration, ms         */
static int	wdog_n;		/* watchdog samples taken                 */
static int	kill9_mark;	/* userland set this right before kill -9 */
static int	safety = 1;	/* documented inert jig knob              */
static struct callout wdog;

#define	WDOG_PERIOD	(5 * hz)
#define	WDOG_MAX	16

static int
sigkill_pending(thread_t td)
{
	struct lwp *lp;

	if (td == NULL)
		return (-1);
	lp = td->td_lwp;
	if (lp == NULL || lp->lwp_proc == NULL)
		return (-1);
	/* SIGKILL == 9 -> bit 0x100 in word 0 of the sigset */
	return ((lp->lwp_proc->p_siglist.__bits[0] & 0x100) ? 1 : 0);
}

/*
 * Watchdog: samples the blocked thread from a callout (any CPU) every
 * 5s, at most WDOG_MAX samples; each sample kprintf()s the thread wait
 * state so the serial console + dmesg carry the proof.
 */
static void
wdog_sample(void *arg)
{
	thread_t td = exit_td;
	int el;

	(void)arg;
	if (exit_done)
		return;			/* ttm_exit returned: stop chain */
	wdog_n++;
	el = (ticks - exit_t0) / hz;

	if (td != NULL) {
		kprintf("df_ttm2212: WATCHDOG sample %d/%d t+%ds: "
		    "exit-thread %p TDF_TSLEEPQ=%s wmesg=\"%s\" wchan=%p "
		    "device_released=%d sigkill_pending=%d kill9_marker=%s\n",
		    wdog_n, WDOG_MAX, el, td,
		    (td->td_flags & TDF_TSLEEPQ) ? "YES" : "no",
		    (td->td_wmesg != NULL) ? td->td_wmesg : "(null)",
		    __DECONST(void *, td->td_wchan),
		    atomic_read(&device_released),
		    sigkill_pending(td),
		    (kill9_mark != 0) ? "set" : "clear");
	} else {
		kprintf("df_ttm2212: WATCHDOG sample %d/%d t+%ds: "
		    "exit-thread (none)\n", wdog_n, WDOG_MAX, el);
	}

	if (wdog_n >= WDOG_MAX) {
		kprintf("df_ttm2212: WATCHDOG VERDICT after %d samples "
		    "(~%ds): ttm_exit() thread STILL blocked; "
		    "device_released=%d and no compiled code path in sys/ "
		    "can ever set it to 1 (only setter is #if 0'd out at "
		    "ttm_module.c:48-52) -> wait_event can never return; "
		    "sleep is uninterruptible (no PCATCH)\n",
		    wdog_n, el, atomic_read(&device_released));
		return;			/* stop sampling; thread stays stuck */
	}
	callout_reset(&wdog, WDOG_PERIOD, wdog_sample, NULL);
}

/* ------------------------------ sysctls ------------------------------- */

static int
sysctl_trigger(SYSCTL_HANDLER_ARGS)
{
	int val = 0;
	int error;
	int ms;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	kprintf("df_ttm2212: TRIGGER pid=%d (%s): calling the REAL "
	    "ttm_exit() (verbatim ttm_module.c:89-99) in THIS thread - "
	    "exactly what linker_file_sysuninit() (kern_linker.c:535) does "
	    "to the kldunload(2) thread of drm.ko\n",
	    (curproc != NULL) ? curproc->p_pid : -1,
	    ((curproc != NULL && curproc->p_comm != NULL) ?
		curproc->p_comm : "?"));
	kprintf("df_ttm2212: stack snapshot at entry to the wait path:\n");
	print_backtrace(-1);

	exit_td = curthread;
	exit_done = 0;
	exit_ms = -1;
	wdog_n = 0;
	exit_t0 = ticks;
	callout_init_mp(&wdog);
	callout_reset(&wdog, WDOG_PERIOD, wdog_sample, NULL);

	ttm_exit();		/* VERBATIM: never returns on stock code */

	ms = ((ticks - exit_t0) * 1000) / hz;
	exit_ms = ms;
	exit_done = 1;
	exit_td = NULL;
	kprintf("df_ttm2212: ttm_exit() RETURNED after %d ms "
	    "(device_released=%d) - fixed kernel behaviour\n",
	    ms, atomic_read(&device_released));
	return (0);
}

static int
sysctl_cycle(SYSCTL_HANDLER_ARGS)
{
	int val = 0;
	int error;
	int i;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val < 1 || val > 10000)
		return (EINVAL);

	for (i = 0; i < val; i++) {
		error = ttm_init();
		if (error != 0) {
			kprintf("df_ttm2212: CYCLE abort: ttm_init() failed "
			    "with %d at iteration %d\n", error, i);
			return (error);
		}
		ttm_exit();
	}
	kprintf("df_ttm2212: CYCLE: %d x (ttm_init(); ttm_exit()) "
	    "completed, every call returned\n", val);
	return (0);
}

static int
sysctl_status(SYSCTL_HANDLER_ARGS)
{
	char buf[320];
	thread_t td = exit_td;
	int onq = 0;
	const char *wm = "-";
	const void *wc = NULL;

	(void)oidp;
	(void)arg1;
	(void)arg2;
	if (td != NULL) {
		onq = (td->td_flags & TDF_TSLEEPQ) != 0;
		wm = (td->td_wmesg != NULL) ? td->td_wmesg : "(null)";
		wc = td->td_wchan;
	}
	ksnprintf(buf, sizeof(buf),
	    "register_calls=%d unregister_calls=%d device_released=%d "
	    "exit_done=%d exit_ms=%d wdog_samples=%d exit_thread=%p "
	    "tsleepq=%d wmesg=%s wchan=%p kill9_mark=%d\n",
	    dfv_register_calls, dfv_unregister_calls,
	    atomic_read(&device_released), exit_done, exit_ms, wdog_n,
	    td, onq, wm, wc, kill9_mark);
	return (SYSCTL_OUT(req, buf, strlen(buf)));
}

static int
sysctl_kill9(SYSCTL_HANDLER_ARGS)
{
	int val = 0;
	int error;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val != 0)
		kill9_mark = ticks;
	kprintf("df_ttm2212: KILL9 marker set (kill -9 about to be sent "
	    "to the blocked trigger process)\n");
	return (0);
}

SYSCTL_NODE(_hw, OID_AUTO, df_ttm2212, CTLFLAG_RW, 0,
    "DF-2212 ttm_exit deadlock verification rig");

SYSCTL_PROC(_hw_df_ttm2212, OID_AUTO, status,
    CTLTYPE_STRING | CTLFLAG_RD, NULL, 0, sysctl_status, "A",
    "rig status");
SYSCTL_PROC(_hw_df_ttm2212, OID_AUTO, trigger,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, sysctl_trigger, "I",
    "call the real ttm_exit() in this thread (kldunload-path simulation)");
SYSCTL_PROC(_hw_df_ttm2212, OID_AUTO, cycle,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, sysctl_cycle, "I",
    "run N x (ttm_init; ttm_exit)");
SYSCTL_PROC(_hw_df_ttm2212, OID_AUTO, kill9,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, sysctl_kill9, "I",
    "marker: SIGKILL about to be sent");
SYSCTL_INT(_hw_df_ttm2212, OID_AUTO, shutdown_safety, CTLFLAG_RW,
    &safety, 0,
    "documented inert jig knob (DF never walks built-in sysuninit_set)");

/* ---------------------- boot check / shutdown jig --------------------- */

static void
boot_check(void *arg)
{
	(void)arg;
	kprintf("df_ttm2212: BOOT CHECK: verbatim ttm_init() ran via "
	    "SYSINIT (module.h:58-59): register_calls=%d "
	    "device_released=%d (register>0 + value 0 => SUCCESS path "
	    "=> a later ttm_exit() can never pass its wait)\n",
	    dfv_register_calls, atomic_read(&device_released));
}
SYSINIT(dfv_boot_check, SI_SUB_DRIVERS, SI_ORDER_THIRD, boot_check, NULL);

static void
shutdown_safety_fn(void *arg)
{
	(void)arg;
	if (safety == 0)
		return;
	/* jig: release the exit wait so a module-style uninit walk of this
	 * image could never hang (inert on DF: built-ins are not walked) */
	atomic_set(&device_released, 1);
	wake_up_all(&exit_q);
}
SYSUNINIT(dfv_shutdown_safety, SI_SUB_DRIVERS, SI_ORDER_THIRD,
    shutdown_safety_fn, NULL);