⬢ DragonFlyBSD Kernel Audit
DF-5195 / deadlock_probe.c
← back to finding ↓ download raw
/*
 * deadlock_probe.c -- DF-5195 clean reproduction (root, vidconsole boot)
 *
 * Parent: arms the snake saver, waits for it to draw, then forks a child
 * that issues CONS_SET (mode change) while the console is blanked.
 * The child is expected to HANG in the ioctl (kernel deadlock:
 * wait_scrn_saver_stop sleeps holding vga_token while the wake path
 * stop_scrn_saver->restore_scrn_saver_mode(TRUE)->set_mode needs it).
 * Parent kills the child after 10s, then demonstrates the permanent
 * aftermath: scrn_blanked stuck at 1, module unload EBUSY forever.
 */
#include <sys/ioctl.h>
#include <sys/consio.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

static int rdcells(void)
{
	unsigned char b[8192];
	int fd = open("/dev/mem", O_RDONLY);
	int c = 0;
	if (fd < 0) return -1;
	pread(fd, b, 8192, 0xB8000);
	close(fd);
	for (int j = 0; j < 8192; j += 2)
		if (b[j+1] == 0x07 && b[j] != 0x20 && b[j])
			c++;
	return c;
}

int main(void)
{
	int fd, v = 1, mode = 32 /* M_VGA_C80x30 */, status, i;
	pid_t pid;

	fd = open("/dev/ttyv0", O_WRONLY);
	if (fd < 0) { perror("ttyv0"); return 1; }

	/* arm the saver: 2s blank time */
	v = 2;
	if (ioctl(fd, CONS_BLANKTIME, &v)) { perror("BLANKTIME"); return 1; }

	printf("[1] waiting for snake to draw (VRAM cells)...\n");
	for (i = 0; i < 50; i++) {
		usleep(200000);
		v = rdcells();
		if (v > 5) break;
	}
	printf("[2] snake cells=%d (saver active, screen blanked)\n", v);
	if (v <= 5) { printf("[-] saver did not engage\n"); return 2; }

	pid = fork();
	if (pid == 0) {
		int m = mode;
		printf("[3] child: CONS_SET(80x30) while blanked...\n");
		fflush(stdout);
		errno = 0;
		v = ioctl(fd, CONS_SET, &m);
		printf("[?] child: CONS_SET returned rc=%d errno=%d "
		       "(UNEXPECTED - should hang)\n", v, errno);
		_exit(0);
	}
	/* parent: give the child 10s to (not) finish */
	for (i = 0; i < 100; i++) {
		usleep(100000);
		if (waitpid(pid, &status, WNOHANG) == pid)
			break;
	}
	if (i >= 100) {
		printf("[4] child still blocked after 10s in CONS_SET: "
		       "DEADLOCK REPRODUCED (wchan 'scrsav')\n");
		kill(pid, SIGKILL);
		waitpid(pid, &status, 0);
		printf("[5] child SIGKILLed (tsleep is PCATCH-killable)\n");
	} else {
		printf("[4] child finished (no hang?!)\n");
	}

	/* permanent aftermath */
	sleep(2);
	printf("[6] post-kill: VRAM cells=%d; sleeping 10s to give the "
	       "kernel every chance to recover...\n", rdcells());
	sleep(10);
	printf("[7] after 12s: VRAM cells=%d; now 'kldunload snake_saver' "
	       "(expect EBUSY/hang) and ps:\n", rdcells());
	fflush(stdout);
	system("timeout 8 kldunload snake_saver; echo UNLOAD_RC=$?");
	system("ps -o pid,stat,wchan,args -p $$ 2>/dev/null | head -2");
	printf("[8] check kernel state with /tmp/kread (scrn_blanked)\n");
	fflush(stdout);
	return 0;
}