all modules: wrap Linux-only code in #ifdef __linux__ — full macOS build works

Every kernel-LPE module that uses Linux-only headers (splice, posix_fadvise,
linux/netlink.h, sys/ptrace.h, etc.) now follows the same #ifdef __linux__
pattern the new modules already used: Linux body in the ifdef, stub
detect/exploit/cleanup returning SKELETONKEY_PRECOND_FAIL on non-Linux,
platform-neutral rule strings + module struct + register fn left outside.

14 modules wrapped:
  dirty_pipe (already done above), af_packet, af_packet2,
  cgroup_release_agent, cls_route4, dirty_cow, fuse_legacy,
  netfilter_xtcompat, nf_tables, nft_fwd_dup, nft_payload,
  overlayfs, overlayfs_setuid, ptrace_traceme.

Several modules previously had ad-hoc partial stubs (af_packet2 faked
SIOCSIFFLAGS/MAP_LOCKED, netfilter_xtcompat faked sysv-msg syscalls,
the nft_* modules had 3 partial __linux__ islands each, fuse_legacy /
nf_tables had inner-only ifdef blocks) — all replaced with the uniform
outer-wrap shape from dirty_pipe / dirtydecrypt / fragnesia / pack2theroot.

Where a module includes core/kernel_range.h, core/finisher.h, or
core/offsets.h, those are now inside the ifdef block as well — silences
clangd's "unused-includes" LSP warning on macOS while keeping them
present for the real Linux build.

No exploit logic, constant, struct, shellcode byte, or rule string was
modified — only include placement and ifdef markers.

Build verification:
  macOS (local): make clean && make → Mach-O x86_64, 31 modules
                 registered, --scan reports each Linux-only module as
                 "Linux-only module — not applicable here".
  Linux (docker gcc:latest + libglib2.0-dev): make clean && make →
                 ELF 64-bit, 31 modules. Exploit code paths unchanged.
This commit is contained in:
2026-05-22 22:58:16 -04:00
parent 9a4cc91619
commit cdb8f5e8f9
14 changed files with 448 additions and 202 deletions
@@ -45,9 +45,6 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
@@ -55,13 +52,18 @@
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/wait.h>
#include <sys/socket.h>
#ifdef __linux__
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
@@ -72,52 +74,6 @@
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <poll.h>
#endif
/* ---------- macOS / non-linux build stubs ---------------------------
* Modules in SKELETONKEY are dev-built on macOS and run-built on Linux.
* Provide empty stubs so syntax checks pass without Linux headers.
* The exploit path is gated at runtime on the kernel version anyway,
* so the stubs are never reached on macOS targets. */
#ifndef __linux__
#define CLONE_NEWUSER 0x10000000
#define CLONE_NEWNET 0x40000000
#define ETH_P_ALL 0x0003
#define ETH_P_8021Q 0x8100
#define ETH_P_8021AD 0x88A8
#define ETH_P_IP 0x0800
#define ETH_ALEN 6
#define ETH_HLEN 14
#define VLAN_HLEN 4
#define IFF_UP 0x01
#define IFF_RUNNING 0x40
#define SIOCSIFFLAGS 0x8914
#define SIOCGIFINDEX 0x8933
#define SIOCGIFFLAGS 0x8913
#define SOL_PACKET 263
#define PACKET_RX_RING 5
#define PACKET_VERSION 10
#define PACKET_QDISC_BYPASS 20
#define TPACKET_V2 1
#define PACKET_HOST 0
struct sockaddr_ll { unsigned short sll_family; unsigned short sll_protocol; int sll_ifindex; int dummy; };
struct ifreq { char name[16]; union { int ifr_ifindex; short ifr_flags; } u; };
struct tpacket_req { unsigned int tp_block_size, tp_block_nr, tp_frame_size, tp_frame_nr; };
struct tpacket2_hdr { unsigned int tp_status, tp_len, tp_snaplen; unsigned short tp_mac, tp_net; };
struct pollfd { int fd; short events, revents; };
#define POLLIN 0x001
__attribute__((unused)) static int ioctl(int a, unsigned long b, ...) { (void)a; (void)b; errno=ENOSYS; return -1; }
__attribute__((unused)) static void *mmap(void *a, size_t b, int c, int d, int e, long f) { (void)a;(void)b;(void)c;(void)d;(void)e;(void)f; errno=ENOSYS; return (void*)-1; }
__attribute__((unused)) static int munmap(void *a, size_t b) { (void)a;(void)b; return -1; }
__attribute__((unused)) static int setsockopt(int a, int b, int c, const void *d, unsigned int e) { (void)a;(void)b;(void)c;(void)d;(void)e; errno=ENOSYS; return -1; }
__attribute__((unused)) static int poll(struct pollfd *a, unsigned long b, int c) { (void)a;(void)b;(void)c; errno=ENOSYS; return -1; }
__attribute__((unused)) static unsigned short htons(unsigned short x) { return x; }
#define MAP_SHARED 0x01
#define MAP_LOCKED 0x2000
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define MAP_FAILED ((void *)-1)
#endif
static const struct kernel_patched_from af_packet2_patched_branches[] = {
{4, 9, 235},
@@ -223,8 +179,6 @@ static skeletonkey_result_t af_packet2_detect(const struct skeletonkey_ctx *ctx)
* the primitive. It does not land cred overwrite.
*/
#ifdef __linux__
/* sendmmsg spray helper — best-effort skb groom. Adjacent kernel slab
* objects are sprayed so the OOB write lands on attacker bytes. */
static void af_packet2_skb_spray(int n_iters)
@@ -440,15 +394,6 @@ static int af_packet2_primitive_child(const struct skeletonkey_ctx *ctx)
return 0;
}
#else /* !__linux__: provide a stub for macOS sanity builds */
static int af_packet2_primitive_child(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] af_packet2: linux-only primitive — non-linux build\n");
return -1;
}
#endif
/* ---- Full-chain finisher (--full-chain, x86_64 only) ----------------
*
* Arb-write strategy (Or Cohen's sk_buff-data-pointer hijack):
@@ -490,7 +435,7 @@ struct afp2_arb_ctx {
int n_attempts; /* spray/fire rounds before giving up */
};
#if defined(__x86_64__) && defined(__linux__)
#if defined(__x86_64__)
static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vctx)
{
struct afp2_arb_ctx *c = (struct afp2_arb_ctx *)vctx;
@@ -508,9 +453,7 @@ static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vc
* frame would then write our payload (the modprobe_path string)
* into the forged ->data target. */
for (int i = 0; i < c->n_attempts; i++) {
#ifdef __linux__
af_packet2_skb_spray(8);
#endif
pid_t p = fork();
if (p < 0) return -1;
if (p == 0) {
@@ -535,9 +478,7 @@ static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vc
}
int st;
waitpid(p, &st, 0);
#ifdef __linux__
af_packet2_skb_spray(8);
#endif
}
/* LAST-RESORT depth: we have fired the trigger + spray but cannot
@@ -664,7 +605,7 @@ static skeletonkey_result_t af_packet2_exploit(const struct skeletonkey_ctx *ctx
" skeletonkey intentionally does not embed per-kernel offsets.\n");
}
if (ctx->full_chain) {
#if defined(__x86_64__) && defined(__linux__)
#if defined(__x86_64__)
/* --full-chain: resolve kernel offsets and run the Or-Cohen
* sk_buff-data-pointer hijack via the shared modprobe_path
* finisher. Per the verified-vs-claimed bar: if we can't
@@ -703,6 +644,29 @@ static skeletonkey_result_t af_packet2_exploit(const struct skeletonkey_ctx *ctx
}
}
#else /* !__linux__ */
/* Non-Linux dev builds: AF_PACKET + TPACKET_V2 + tpacket_rcv VLAN
* underflow are Linux-only kernel surface. Stub out cleanly so the
* module still registers and `--list` / `--detect-rules` work on
* macOS/BSD dev boxes — and so the top-level `make` actually completes
* there. */
static skeletonkey_result_t af_packet2_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] af_packet2: Linux-only module "
"(AF_PACKET TPACKET_V2 + user_ns) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t af_packet2_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] af_packet2: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
#endif /* __linux__ */
static const char af_packet2_auditd[] =
"# AF_PACKET VLAN LPE (CVE-2020-14386) — auditd detection rules\n"
"# Same syscall surface as CVE-2017-7308 — share the skeletonkey-af-packet\n"
@@ -60,17 +60,22 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#include <sys/socket.h>
@@ -858,6 +863,30 @@ static skeletonkey_result_t af_packet_exploit(const struct skeletonkey_ctx *ctx)
#endif
}
#else /* !__linux__ */
/* Non-Linux dev builds: AF_PACKET + unshare(CLONE_NEWUSER|CLONE_NEWNET)
* + TPACKET_V3 ring are Linux-only kernel surface; the TPACKET_V3
* integer-overflow primitive is structurally unreachable elsewhere.
* Stub out cleanly so the module still registers and `--list` /
* `--detect-rules` work on macOS/BSD dev boxes — and so the top-level
* `make` actually completes there. */
static skeletonkey_result_t af_packet_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] af_packet: Linux-only module "
"(AF_PACKET TPACKET_V3 + user_ns) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t af_packet_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] af_packet: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
#endif /* __linux__ */
static const char af_packet_auditd[] =
"# AF_PACKET TPACKET_V3 LPE (CVE-2017-7308) — auditd detection rules\n"
"# Flag AF_PACKET socket creation from non-root via userns.\n"
@@ -38,7 +38,6 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include <stdio.h>
#include <stdlib.h>
@@ -46,6 +45,10 @@
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
@@ -303,6 +306,34 @@ static skeletonkey_result_t cgroup_ra_cleanup(const struct skeletonkey_ctx *ctx)
return SKELETONKEY_OK;
}
#else /* !__linux__ */
/* Non-Linux dev builds: unshare(CLONE_NEWUSER|CLONE_NEWNS) + cgroup v1
* mount are Linux-only kernel surface; the release_agent primitive is
* structurally unreachable elsewhere. Stub out cleanly so the module
* still registers and `--list` / `--detect-rules` work on macOS/BSD
* dev boxes — and so the top-level `make` actually completes there. */
static skeletonkey_result_t cgroup_ra_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] cgroup_release_agent: Linux-only module "
"(user_ns + cgroup v1 release_agent) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t cgroup_ra_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] cgroup_release_agent: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t cgroup_ra_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
return SKELETONKEY_OK;
}
#endif /* __linux__ */
static const char cgroup_ra_auditd[] =
"# cgroup_release_agent (CVE-2022-0492) — auditd detection rules\n"
"# Flag unshare(NEWUSER|NEWNS) + mount(cgroup) + writes to release_agent.\n"
@@ -40,9 +40,6 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
@@ -50,6 +47,13 @@
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
@@ -412,8 +416,6 @@ static long slab_active_kmalloc_1k(void)
* Honest scope: this is structurally-fires-on-vuln + sentinel-arbitrated,
* not a deterministic R/W. Same shape and same depth as xtcompat. */
#ifdef __linux__
struct cls_route4_arb_ctx {
/* msg_msg queues kept hot inside the userns child. The arb-write
* sprays additional kaddr-tagged payloads into these and re-fires
@@ -544,8 +546,6 @@ static int cls4_arb_write(uintptr_t kaddr,
return 0;
}
#endif /* __linux__ */
/* ---- Exploit driver ----------------------------------------------- */
static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx)
@@ -565,11 +565,6 @@ static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx
return SKELETONKEY_PRECOND_FAIL;
}
#ifndef __linux__
fprintf(stderr, "[-] cls_route4: linux-only exploit; non-linux build\n");
(void)ctx;
return SKELETONKEY_PRECOND_FAIL;
#else
/* Full-chain pre-check: resolve offsets before forking. If
* modprobe_path can't be resolved, refuse early — no point doing
* the userns + tc + spray + trigger dance if we can't finish. */
@@ -782,7 +777,6 @@ static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx
}
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
}
/* ---- Cleanup ----------------------------------------------------- */
@@ -803,6 +797,34 @@ static skeletonkey_result_t cls_route4_cleanup(const struct skeletonkey_ctx *ctx
return SKELETONKEY_OK;
}
#else /* !__linux__ */
/* Non-Linux dev builds: cls_route4 / tc / netlink / msg_msg are
* Linux-only kernel surface; the route4 dead-UAF is structurally
* unreachable elsewhere. Stub out cleanly so the module still
* registers and `--list` / `--detect-rules` work on macOS/BSD dev
* boxes — and so the top-level `make` actually completes there. */
static skeletonkey_result_t cls_route4_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] cls_route4: Linux-only module "
"(net/sched cls_route4 + msg_msg) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] cls_route4: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t cls_route4_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
return SKELETONKEY_OK;
}
#endif /* __linux__ */
static const char cls_route4_auditd[] =
"# cls_route4 dead UAF (CVE-2022-2588) — auditd detection rules\n"
"# Flag tc filter operations with route4 classifier from non-root.\n"
@@ -43,15 +43,18 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include <stdint.h>
#include <stdatomic.h>
#include <fcntl.h>
#include <errno.h>
#include <pwd.h>
@@ -318,6 +321,34 @@ static skeletonkey_result_t dirty_cow_cleanup(const struct skeletonkey_ctx *ctx)
return SKELETONKEY_OK;
}
#else /* !__linux__ */
/* Non-Linux dev builds: the Dirty COW primitive (writer thread via
* /proc/self/mem + madvise(MADV_DONTNEED)) is Linux-only kernel
* surface. Stub out cleanly so the module still registers and
* `--list` / `--detect-rules` work on macOS/BSD dev boxes — and so
* the top-level `make` actually completes there. */
static skeletonkey_result_t dirty_cow_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] dirty_cow: Linux-only module "
"(/proc/self/mem + madvise race) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t dirty_cow_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] dirty_cow: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t dirty_cow_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
return SKELETONKEY_OK;
}
#endif /* __linux__ */
/* ---- Embedded detection rules ---- */
static const char dirty_cow_auditd[] =
@@ -32,7 +32,6 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
/* _GNU_SOURCE is passed via -D in the top-level Makefile; do not
* redefine here (warning: redefined). */
@@ -42,6 +41,10 @@
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h" /* used inside this block only */
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
@@ -407,6 +410,34 @@ static skeletonkey_result_t dirty_pipe_cleanup(const struct skeletonkey_ctx *ctx
return SKELETONKEY_OK;
}
#else /* !__linux__ */
/* Non-Linux dev builds: splice() / F_GETPIPE_SZ / posix_fadvise() are
* Linux-only kernel surface; the Dirty Pipe primitive is structurally
* unreachable elsewhere. Stub out cleanly so the module still
* registers and `--list` / `--detect-rules` work on macOS/BSD dev
* boxes — and so the top-level `make` actually completes there. */
static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] dirty_pipe: Linux-only module "
"(splice + PIPE_BUF_FLAG_CAN_MERGE) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t dirty_pipe_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] dirty_pipe: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t dirty_pipe_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
return SKELETONKEY_OK;
}
#endif /* __linux__ */
/* Embedded detection rules — keep the binary self-contained so
* `skeletonkey --detect-rules --format=auditd` works without a separate
* data-dir install. */
@@ -59,15 +59,20 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <fcntl.h>
#include <errno.h>
@@ -378,7 +383,6 @@ struct fuse_arb_ctx {
bool trigger_armed;
};
#ifdef __linux__
static int fuse_arb_write(uintptr_t kaddr, const void *buf, size_t len,
void *ctx_void)
{
@@ -504,15 +508,6 @@ static int fuse_arb_write(uintptr_t kaddr, const void *buf, size_t len,
(unsigned long)kaddr);
return 0;
}
#else
static int fuse_arb_write(uintptr_t kaddr, const void *buf, size_t len,
void *ctx_void)
{
(void)kaddr; (void)buf; (void)len; (void)ctx_void;
fprintf(stderr, "[-] fuse_arb_write: linux-only primitive\n");
return -1;
}
#endif /* __linux__ */
/* ------------------------------------------------------------------ */
/* exploit */
@@ -732,7 +727,6 @@ static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ct
* runs because the arb_write primitive re-fires the trigger and
* needs the live spray.
* --------------------------------------------------------------- */
#ifdef __linux__
if (ctx->full_chain) {
if (!ctx->json) {
fprintf(stderr, "[*] fuse_legacy: --full-chain requested — resolving "
@@ -792,7 +786,6 @@ static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ct
}
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
/* Clean up our IPC queues and mapping. The kernel slab state
* after the overflow may be unstable; we exit cleanly on success
@@ -826,6 +819,28 @@ static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ct
return SKELETONKEY_EXPLOIT_FAIL;
}
#else /* !__linux__ */
/* Non-Linux dev builds: fsopen/fsconfig + userns+mountns clone are
* Linux-only kernel surface. Stub out cleanly so the module still
* registers and `--list` / `--detect-rules` work on macOS/BSD dev
* boxes — and so the top-level `make` actually completes there. */
static skeletonkey_result_t fuse_legacy_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] fuse_legacy: Linux-only module "
"(fsopen + fsconfig + userns mount) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] fuse_legacy: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
#endif /* __linux__ */
/* ------------------------------------------------------------------ */
/* embedded detection rules */
/* ------------------------------------------------------------------ */
@@ -58,16 +58,20 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
@@ -76,8 +80,6 @@
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef __linux__
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/syscall.h>
@@ -91,31 +93,6 @@
#ifndef SOL_IP
#define SOL_IP 0
#endif
#endif
/* ---------- macOS / non-linux build stubs ---------------------------
* SKELETONKEY modules are dev-built on macOS (clangd / syntax check) and
* run-built on Linux. The Linux-only types and IPT_SO_SET_REPLACE
* constants are absent on Darwin; stub them so the .c file compiles
* cleanly under either toolchain. The actual exploit body is gated
* by `#ifdef __linux__` at runtime entry. */
#ifndef __linux__
#define CLONE_NEWUSER 0x10000000
#define CLONE_NEWNET 0x40000000
#define IPPROTO_RAW 255
#define SOL_IP 0
#define IPT_SO_SET_REPLACE 64
struct ipt_replace { char dummy; };
__attribute__((unused)) static int msgget(int a, int b) { (void)a;(void)b; errno=ENOSYS; return -1; }
__attribute__((unused)) static int msgsnd(int a, const void *b, size_t c, int d) { (void)a;(void)b;(void)c;(void)d; errno=ENOSYS; return -1; }
__attribute__((unused)) static ssize_t msgrcv(int a, void *b, size_t c, long d, int e) { (void)a;(void)b;(void)c;(void)d;(void)e; errno=ENOSYS; return -1; }
__attribute__((unused)) static int msgctl(int a, int b, void *c) { (void)a;(void)b;(void)c; errno=ENOSYS; return -1; }
#define IPC_PRIVATE 0
#define IPC_CREAT 01000
#define IPC_NOWAIT 04000
#define IPC_RMID 0
#define MSG_COPY 040000
#endif
/* ---- Kernel range ------------------------------------------------- */
@@ -202,8 +179,6 @@ static skeletonkey_result_t netfilter_xtcompat_detect(const struct skeletonkey_c
/* ---- Exploit: userns reach + trigger + groom ---------------------- */
#ifdef __linux__
/* Write uid_map and gid_map after unshare so we're root in userns.
* This is the standard setgroups=deny pattern; without it the uid_map
* write is rejected on modern kernels for unprivileged callers. */
@@ -471,8 +446,6 @@ static int xtcompat_fire_trigger(int *out_errno)
return 0;
}
#endif /* __linux__ — close original primitive block */
/* ---- Full-chain arb-write primitive --------------------------------
*
* Pattern (FALLBACK — see module top-comment): the xt_compat 4-byte OOB
@@ -509,8 +482,6 @@ static int xtcompat_fire_trigger(int *out_errno)
* patched kernel the trigger returns EINVAL on step 2 and arb_write
* returns -1 without ever queueing the follow-up. */
#ifdef __linux__
struct xtcompat_arb_ctx {
/* Spray queues kept hot across multiple arb_write calls. The
* msg_msg slots seeded here are what the finisher uses as
@@ -636,8 +607,6 @@ static int xtcompat_arb_write(uintptr_t kaddr,
return 0;
}
#endif /* __linux__ */
/* ---- Exploit driver ---------------------------------------------- */
static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_ctx *ctx)
@@ -661,11 +630,6 @@ static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_
return SKELETONKEY_PRECOND_FAIL;
}
#ifndef __linux__
fprintf(stderr, "[-] netfilter_xtcompat: linux-only exploit; non-linux build\n");
(void)ctx;
return SKELETONKEY_PRECOND_FAIL;
#else
/* Full-chain pre-check: resolve offsets before forking. If
* modprobe_path can't be resolved, refuse early with the manual-
* workflow help — no point doing the userns + spray + trigger
@@ -944,7 +908,6 @@ static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_
fprintf(stderr, "[-] netfilter_xtcompat: child exit %d unexpected\n", rc);
return SKELETONKEY_EXPLOIT_FAIL;
}
#endif /* __linux__ */
}
/* ---- Cleanup ----------------------------------------------------- */
@@ -963,6 +926,33 @@ static skeletonkey_result_t netfilter_xtcompat_cleanup(const struct skeletonkey_
return SKELETONKEY_OK;
}
#else /* !__linux__ */
/* Non-Linux dev builds: setsockopt(IPT_SO_SET_REPLACE) + nfnetlink +
* userns is Linux-only kernel surface. Stub out cleanly so the module
* still registers and `--list` / `--detect-rules` work on macOS/BSD
* dev boxes — and so the top-level `make` actually completes there. */
static skeletonkey_result_t netfilter_xtcompat_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] netfilter_xtcompat: Linux-only module "
"(xt_compat_target_to_user via SET_REPLACE) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] netfilter_xtcompat: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t netfilter_xtcompat_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
return SKELETONKEY_OK;
}
#endif /* __linux__ */
/* ---- Detection rules --------------------------------------------- */
static const char netfilter_xtcompat_auditd[] =
@@ -57,16 +57,20 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <sched.h>
#include <fcntl.h>
#include <errno.h>
@@ -618,7 +622,6 @@ static long slabinfo_active(const char *slab)
* Factored out so --full-chain can re-fire the trigger between
* msg_msg sprays without duplicating the batch-building logic.
* ------------------------------------------------------------------ */
#ifdef __linux__
static size_t build_trigger_batch(uint8_t *batch, size_t cap, uint32_t *seq)
{
(void)cap;
@@ -792,7 +795,6 @@ static int nft_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vct
usleep(20 * 1000);
return 0;
}
#endif /* __linux__ */
/* ------------------------------------------------------------------
* The exploit body.
@@ -825,7 +827,6 @@ static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx)
}
}
#ifdef __linux__
/* --- --full-chain path --------------------------------------- *
* Resolve offsets BEFORE doing anything destructive so we can
* refuse cleanly on hosts where we have no modprobe_path. We run
@@ -906,7 +907,6 @@ static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx)
close(sock);
return r;
}
#endif
/* --- primitive-only path: fork-isolated trigger -------------- *
* Fork: child enters userns+netns and fires the bug. If the
@@ -1070,6 +1070,28 @@ static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx)
return SKELETONKEY_EXPLOIT_FAIL;
}
#else /* !__linux__ */
/* Non-Linux dev builds: nfnetlink + nf_tables UAF + userns is
* Linux-only kernel surface. Stub out cleanly so the module still
* registers and `--list` / `--detect-rules` work on macOS/BSD dev
* boxes and so the top-level `make` actually completes there. */
static skeletonkey_result_t nf_tables_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] nf_tables: Linux-only module "
"(nft_verdict_init UAF via nfnetlink) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] nf_tables: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
#endif /* __linux__ */
/* ----- Embedded detection rules ----- */
static const char nf_tables_auditd[] =
@@ -43,16 +43,20 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <sched.h>
#include <fcntl.h>
#include <errno.h>
@@ -585,7 +589,6 @@ static int bring_lo_up(void)
return 0;
}
#ifdef __linux__
static size_t build_trigger_batch(uint8_t *batch, uint32_t *seq)
{
size_t off = 0;
@@ -596,7 +599,6 @@ static size_t build_trigger_batch(uint8_t *batch, uint32_t *seq)
put_batch_end(batch, &off, (*seq)++);
return off;
}
#endif
/* ------------------------------------------------------------------
* --full-chain arb-write context. The technique:
@@ -617,8 +619,6 @@ static size_t build_trigger_batch(uint8_t *batch, uint32_t *seq)
* mismatches as SKELETONKEY_EXPLOIT_FAIL rather than fake success.
* ------------------------------------------------------------------ */
#ifdef __linux__
#define SPRAY_QUEUES_ARB 32
struct fwd_arb_ctx {
@@ -721,8 +721,6 @@ static int nft_fwd_dup_arb_write(uintptr_t kaddr,
return 0;
}
#endif /* __linux__ */
/* ------------------------------------------------------------------
* Exploit driver.
* ------------------------------------------------------------------ */
@@ -748,11 +746,6 @@ static skeletonkey_result_t nft_fwd_dup_exploit(const struct skeletonkey_ctx *ct
return pre;
}
#ifndef __linux__
fprintf(stderr, "[-] nft_fwd_dup: linux-only exploit; non-linux build\n");
(void)ctx;
return SKELETONKEY_PRECOND_FAIL;
#else
if (!ctx->json) {
if (ctx->full_chain) {
fprintf(stderr, "[*] nft_fwd_dup: --full-chain — trigger + OOB-write "
@@ -946,7 +939,6 @@ static skeletonkey_result_t nft_fwd_dup_exploit(const struct skeletonkey_ctx *ct
fprintf(stderr, "[-] nft_fwd_dup: unexpected child rc=%d\n", rc);
}
return SKELETONKEY_EXPLOIT_FAIL;
#endif /* __linux__ */
}
/* ------------------------------------------------------------------
@@ -958,7 +950,6 @@ static skeletonkey_result_t nft_fwd_dup_cleanup(const struct skeletonkey_ctx *ct
if (!ctx->json) {
fprintf(stderr, "[*] nft_fwd_dup: cleaning up sysv queues + log\n");
}
#ifdef __linux__
/* Best-effort drain of any leftover msg queues with IPC_PRIVATE
* key owned by us. SysV doesn't enumerate by key, but msgctl
* IPC_STAT walks /proc/sysvipc/msg to find them. */
@@ -979,13 +970,38 @@ static skeletonkey_result_t nft_fwd_dup_cleanup(const struct skeletonkey_ctx *ct
}
fclose(f);
}
#endif
if (unlink("/tmp/skeletonkey-nft_fwd_dup.log") < 0 && errno != ENOENT) {
/* harmless */
}
return SKELETONKEY_OK;
}
#else /* !__linux__ */
/* Non-Linux dev builds: nf_tables / NETLINK_NETFILTER / SysV msg_msg
* groom all Linux-only kernel surface. Stub out so the module still
* registers and the top-level `make` completes on macOS/BSD dev boxes. */
static skeletonkey_result_t nft_fwd_dup_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] nft_fwd_dup: Linux-only module "
"(nf_tables HW-offload OOB) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t nft_fwd_dup_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] nft_fwd_dup: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t nft_fwd_dup_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
return SKELETONKEY_OK;
}
#endif /* __linux__ */
/* ------------------------------------------------------------------
* Embedded detection rules.
* ------------------------------------------------------------------ */
@@ -49,16 +49,20 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/offsets.h"
#include "../../core/finisher.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <sched.h>
#include <fcntl.h>
#include <errno.h>
@@ -71,13 +75,10 @@
#include <sys/mman.h>
#include <sys/syscall.h>
#include <arpa/inet.h>
#ifdef __linux__
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#endif
/* ------------------------------------------------------------------
* Kernel-range table
@@ -187,8 +188,6 @@ static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx
return SKELETONKEY_VULNERABLE;
}
#ifdef __linux__
/* ------------------------------------------------------------------
* userns + netns entry: become root in the new user_ns so subsequent
* netlink writes carry CAP_NET_ADMIN over our private net_ns.
@@ -801,8 +800,6 @@ static int nft_payload_arb_write(uintptr_t kaddr, const void *buf, size_t len,
return 0;
}
#endif /* __linux__ */
/* ------------------------------------------------------------------
* Exploit body.
* ------------------------------------------------------------------ */
@@ -838,11 +835,6 @@ static skeletonkey_result_t nft_payload_exploit(const struct skeletonkey_ctx *ct
}
}
#ifndef __linux__
(void)ctx;
fprintf(stderr, "[-] nft_payload: linux-only exploit; non-linux build\n");
return SKELETONKEY_PRECOND_FAIL;
#else
/* --- --full-chain path: resolve offsets in parent before doing
* anything destructive. */
if (ctx->full_chain) {
@@ -1074,7 +1066,6 @@ static skeletonkey_result_t nft_payload_exploit(const struct skeletonkey_ctx *ct
fprintf(stderr, "[-] nft_payload: unexpected child rc=%d\n", rc);
}
return SKELETONKEY_EXPLOIT_FAIL;
#endif /* __linux__ */
}
/* ------------------------------------------------------------------
@@ -1092,6 +1083,32 @@ static skeletonkey_result_t nft_payload_cleanup(const struct skeletonkey_ctx *ct
return SKELETONKEY_OK;
}
#else /* !__linux__ */
/* Non-Linux dev builds: nf_tables / NETLINK_NETFILTER / SysV msg_msg
* groom all Linux-only kernel surface. Stub out so the module still
* registers and the top-level `make` completes on macOS/BSD dev boxes. */
static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] nft_payload: Linux-only module "
"(nf_tables regset OOB) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t nft_payload_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] nft_payload: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t nft_payload_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
return SKELETONKEY_OK;
}
#endif /* __linux__ */
/* ------------------------------------------------------------------
* Detection rule corpus.
* ------------------------------------------------------------------ */
@@ -37,13 +37,16 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include <fcntl.h>
#include <sched.h>
#include <sys/mount.h>
@@ -446,6 +449,28 @@ fail_workdir:
return SKELETONKEY_EXPLOIT_FAIL;
}
#else /* !__linux__ */
/* Non-Linux dev builds: overlayfs / unshare(CLONE_NEWUSER|CLONE_NEWNS) /
* setxattr("security.capability") are all Linux-only. Stub out so the
* module still registers and the top-level `make` completes on
* macOS/BSD dev boxes. */
static skeletonkey_result_t overlayfs_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] overlayfs: Linux-only module "
"(Ubuntu userns-overlayfs) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t overlayfs_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] overlayfs: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
#endif /* __linux__ */
/* ----- Embedded detection rules ----- */
static const char overlayfs_auditd[] =
@@ -40,14 +40,17 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include <stdint.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
@@ -371,6 +374,32 @@ static skeletonkey_result_t overlayfs_setuid_cleanup(const struct skeletonkey_ct
return SKELETONKEY_OK;
}
#else /* !__linux__ */
/* Non-Linux dev builds: overlayfs copy-up / unshare(CLONE_NEWUSER|CLONE_NEWNS)
* / mount("overlay", ...) are Linux-only. Stub out so the module still
* registers and the top-level `make` completes on macOS/BSD dev boxes. */
static skeletonkey_result_t overlayfs_setuid_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] overlayfs_setuid: Linux-only module "
"(overlayfs setuid copy-up) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t overlayfs_setuid_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] overlayfs_setuid: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t overlayfs_setuid_cleanup(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
return SKELETONKEY_OK;
}
#endif /* __linux__ */
static const char overlayfs_setuid_auditd[] =
"# overlayfs setuid copy-up (CVE-2023-0386) — auditd detection rules\n"
"# Same surface as CVE-2021-3493; share the skeletonkey-overlayfs key.\n"
@@ -28,13 +28,16 @@
#include "skeletonkey_modules.h"
#include "../../core/registry.h"
#include "../../core/kernel_range.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
@@ -277,6 +280,27 @@ static skeletonkey_result_t ptrace_traceme_exploit(const struct skeletonkey_ctx
#endif
}
#else /* !__linux__ */
/* Non-Linux dev builds: PTRACE_TRACEME / PTRACE_ATTACH / user_regs_struct
* are Linux-only ABI surface. Stub out so the module still registers and
* the top-level `make` completes on macOS/BSD dev boxes. */
static skeletonkey_result_t ptrace_traceme_detect(const struct skeletonkey_ctx *ctx)
{
if (!ctx->json)
fprintf(stderr, "[i] ptrace_traceme: Linux-only module "
"(PTRACE_TRACEME cred-escalation) — not applicable here\n");
return SKELETONKEY_PRECOND_FAIL;
}
static skeletonkey_result_t ptrace_traceme_exploit(const struct skeletonkey_ctx *ctx)
{
(void)ctx;
fprintf(stderr, "[-] ptrace_traceme: Linux-only module — cannot run here\n");
return SKELETONKEY_PRECOND_FAIL;
}
#endif /* __linux__ */
static const char ptrace_traceme_auditd[] =
"# PTRACE_TRACEME LPE (CVE-2019-13272) — auditd detection rules\n"
"# Flag PTRACE_TRACEME (request 0) followed by parent execve of\n"