DF-4322 / dsp_race.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 115 | /* * dsp_race.c -- DF-4322 evidence: shared `static struct cdev *dev` in * dsp_clone() (sys/dev/sound/pcm/dsp.c:2381) is a single global handed * across concurrently-running devfs clone handlers. devfs_clone() drops * devfs_lock around the handler (devfs_core.c), so two opens on two * different pcm units run dsp_clone() simultaneously; the global `dev` * written by one card's handler can be read by the other's, and the wrong * card's clone cdev is handed back to devfs for the open. * * UNPRIVILEGED. Needs two registered pcm units (here: pcm0=ES1370, * pcm1=intel-hda). * * Method: hammer open("/dev/dsp0") and open("/dev/dsp1") in parallel * threads; after each successful open, fstat() the fd and check the * unit byte of the minor (PCMUNIT = minor & 0xff). If an fd obtained * via /dev/dspN carries the other unit's minor, the clone handler raced * us onto the other card's cdev. On mismatch, confirm with * SNDCTL_AUDIOINFO (dev=-1) which prints the backing channel's devnode. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/soundcard.h> #define THREADS_PER_CARD 6 #define RUN_SECONDS 180 static volatile int stop = 0; static volatile int mismatches = 0; static unsigned long total_iters = 0, total_ebusy = 0, total_eagain = 0; static pthread_mutex_t mx = PTHREAD_MUTEX_INITIALIZER; static void confirm_and_report(int fd, int opened_unit, int got_unit) { oss_audioinfo ai; memset(&ai, 0, sizeof(ai)); ai.dev = -1; if (ioctl(fd, SNDCTL_AUDIOINFO, &ai) == 0) printf("[!] RACE HIT: opened /dev/dsp%d, fd is clone of card %d " "(devnode %s, name %s)\n", opened_unit, got_unit, ai.devnode, ai.name); else printf("[!] RACE HIT: opened /dev/dsp%d, fd minor unit=%d " "(audioinfo failed: %s)\n", opened_unit, got_unit, strerror(errno)); } static void * hammer(void *arg) { int unit = (int)(intptr_t)arg; char path[32]; struct stat st; int fd, got; unsigned long it = 0, eb = 0; snprintf(path, sizeof(path), "/dev/dsp%d", unit); while (!stop) { fd = open(path, O_WRONLY); if (fd < 0) { if (errno == EBUSY) eb++; continue; } it++; if (fstat(fd, &st) == 0) { got = minor(st.st_rdev) & 0xff; if (got != unit) { pthread_mutex_lock(&mx); mismatches++; confirm_and_report(fd, unit, got); pthread_mutex_unlock(&mx); } } close(fd); } pthread_mutex_lock(&mx); total_iters += it; total_ebusy += eb; pthread_mutex_unlock(&mx); return (NULL); } int main(void) { pthread_t th[2 * THREADS_PER_CARD]; int i, n = 0; printf("dsp_race: %d threads/card, %d s -- open /dev/dsp0 vs /dev/dsp1\n", THREADS_PER_CARD, RUN_SECONDS); for (i = 0; i < THREADS_PER_CARD; i++) { pthread_create(&th[n++], NULL, hammer, (void *)(intptr_t)0); pthread_create(&th[n++], NULL, hammer, (void *)(intptr_t)1); } sleep(RUN_SECONDS); stop = 1; for (i = 0; i < n; i++) pthread_join(th[i], NULL); printf("dsp_race: %lu open cycles (%lu EBUSY), %d cross-card mismatches\n", total_iters, total_ebusy, mismatches); return (mismatches > 0) ? 1 : 0; } |