modules: migrate remaining 22 modules to ctx->host fingerprint
Completes the host-fingerprint refactor that started in c00c3b4. Every
module now consults the shared ctx->host (populated once at startup
by core/host.c) instead of re-doing uname / geteuid / /etc/os-release
parsing / fork+unshare(CLONE_NEWUSER) probes per detect().
Migrations applied per module (mechanical, no exploit logic touched):
1. #include "../../core/host.h" inside each module's #ifdef __linux__.
2. kernel_version_current(&v) -> ctx->host->kernel (with the
v -> v-> arrow-vs-dot fix for all later usage). Drops ~20 redundant
uname() calls across the corpus.
3. geteuid() == 0 (the 'already root, nothing to escalate' gate) ->
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
This is the key change that lets the unit test suite construct
non-root fingerprints regardless of the test process's actual euid.
4. Per-detect fork+unshare(CLONE_NEWUSER) probe helpers (named
can_unshare_userns / can_unshare_userns_mount across the corpus)
are removed wholesale; their call sites now consult
ctx->host->unprivileged_userns_allowed, which was probed once at
startup. Removes ~10 per-scan fork()s.
Modules touched by this commit (22):
Batch A (7): dirty_pipe, dirty_cow, ptrace_traceme, pwnkit,
cgroup_release_agent, overlayfs_setuid, and entrybleed
(no migration target — KPTI gate stays as direct sysfs
read; documented as 'no applicable pattern').
Batch B (7): nf_tables, cls_route4, netfilter_xtcompat, af_packet,
af_packet2, af_unix_gc, fuse_legacy.
Batch C (8): stackrot, nft_set_uaf, nft_fwd_dup, nft_payload,
sudo_samedit, sequoia, sudoedit_editor, vmwgfx.
Combined with the 4 modules already migrated (dirtydecrypt, fragnesia,
pack2theroot, overlayfs) and the 5-module copy_fail_family bridge,
the entire registered corpus now goes through ctx->host. The 4
'fork+unshare per detect()' helpers that existed across nf_tables,
cls_route4, netfilter_xtcompat, af_packet, af_packet2, fuse_legacy,
nft_set_uaf, nft_fwd_dup, nft_payload, sequoia,
cgroup_release_agent, and overlayfs_setuid are now gone — replaced by
the single startup probe in core/host.c.
Verification:
- Linux (docker gcc:latest + libglib2.0-dev): full clean build links
31 modules; tests/test_detect.c: 8/8 pass.
- macOS (local): full clean build links 31 modules (Mach-O, 172KB);
test suite reports skipped as designed on non-Linux.
Subsequent commits can add more EXPECT_DETECT cases in
tests/test_detect.c — the host-fingerprint paths in every module are
now uniformly testable via synthetic struct skeletonkey_host instances.
This commit is contained in:
@@ -56,6 +56,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
|
||||||
@@ -91,53 +92,44 @@ static const struct kernel_range af_packet2_range = {
|
|||||||
sizeof(af_packet2_patched_branches[0]),
|
sizeof(af_packet2_patched_branches[0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
static int can_unshare_userns(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static skeletonkey_result_t af_packet2_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t af_packet2_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] af_packet2: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] af_packet2: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug introduced in 4.6 (tpacket_rcv VLAN path). Pre-4.6 immune. */
|
/* Bug introduced in 4.6 (tpacket_rcv VLAN path). Pre-4.6 immune. */
|
||||||
if (v.major < 4 || (v.major == 4 && v.minor < 6)) {
|
if (v->major < 4 || (v->major == 4 && v->minor < 6)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] af_packet2: kernel %s predates the bug (introduced in 4.6)\n",
|
fprintf(stderr, "[+] af_packet2: kernel %s predates the bug (introduced in 4.6)\n",
|
||||||
v.release);
|
v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&af_packet2_range, &v);
|
bool patched = kernel_range_is_patched(&af_packet2_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] af_packet2: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] af_packet2: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns();
|
bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false;
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] af_packet2: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[i] af_packet2: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] af_packet2: user_ns+net_ns clone: %s\n",
|
fprintf(stderr, "[i] af_packet2: user_ns+net_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" : "could not test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] af_packet2: user_ns denied → unprivileged exploit unreachable\n");
|
fprintf(stderr, "[+] af_packet2: user_ns denied → unprivileged exploit unreachable\n");
|
||||||
}
|
}
|
||||||
@@ -513,8 +505,11 @@ static skeletonkey_result_t af_packet2_exploit(const struct skeletonkey_ctx *ctx
|
|||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2. Refuse if already root. */
|
/* 2. Refuse if already root. Consult ctx->host first so unit tests
|
||||||
if (geteuid() == 0) {
|
* can construct a non-root fingerprint regardless of the test
|
||||||
|
* process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] af_packet2: already running as root — nothing to escalate\n");
|
fprintf(stderr, "[i] af_packet2: already running as root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
|
||||||
@@ -111,44 +112,35 @@ static const struct kernel_range af_packet_range = {
|
|||||||
sizeof(af_packet_patched_branches[0]),
|
sizeof(af_packet_patched_branches[0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
static int can_unshare_userns(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static skeletonkey_result_t af_packet_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t af_packet_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] af_packet: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] af_packet: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&af_packet_range, &v);
|
bool patched = kernel_range_is_patched(&af_packet_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] af_packet: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] af_packet: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns();
|
bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false;
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] af_packet: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[i] af_packet: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] af_packet: user_ns+net_ns clone (CAP_NET_RAW gate): %s\n",
|
fprintf(stderr, "[i] af_packet: user_ns+net_ns clone (CAP_NET_RAW gate): %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" : "could not test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] af_packet: user_ns denied → "
|
fprintf(stderr, "[+] af_packet: user_ns denied → "
|
||||||
"unprivileged exploit unreachable\n");
|
"unprivileged exploit unreachable\n");
|
||||||
@@ -723,8 +715,11 @@ static skeletonkey_result_t af_packet_exploit(const struct skeletonkey_ctx *ctx)
|
|||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2. Refuse if already root. */
|
/* 2. Refuse if already root. Consult ctx->host first so unit tests
|
||||||
if (geteuid() == 0) {
|
* can construct a non-root fingerprint regardless of the test
|
||||||
|
* process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] af_packet: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] af_packet: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
@@ -732,16 +727,19 @@ static skeletonkey_result_t af_packet_exploit(const struct skeletonkey_ctx *ctx)
|
|||||||
/* 3. Resolve offsets for THIS kernel. If we don't have them, bail
|
/* 3. Resolve offsets for THIS kernel. If we don't have them, bail
|
||||||
* early — the kernel-write walk needs them. The integrator can
|
* early — the kernel-write walk needs them. The integrator can
|
||||||
* extend known_offsets[] for new distro builds. */
|
* extend known_offsets[] for new distro builds. */
|
||||||
struct kernel_version v;
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
if (!kernel_version_current(&v)) {
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] af_packet: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
struct af_packet_offsets off;
|
struct af_packet_offsets off;
|
||||||
if (!resolve_offsets(&off, &v)) {
|
if (!resolve_offsets(&off, v)) {
|
||||||
fprintf(stderr, "[-] af_packet: no offset table for kernel %s\n"
|
fprintf(stderr, "[-] af_packet: no offset table for kernel %s\n"
|
||||||
" set SKELETONKEY_AFPACKET_OFFSETS=<task_cred>:<cred_uid>:<cred_size>\n"
|
" set SKELETONKEY_AFPACKET_OFFSETS=<task_cred>:<cred_uid>:<cred_size>\n"
|
||||||
" (hex). Known table covers Ubuntu 16.04 (4.4) and 18.04 (4.15).\n",
|
" (hex). Known table covers Ubuntu 16.04 (4.4) and 18.04 (4.15).\n",
|
||||||
v.release);
|
v->release);
|
||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
}
|
}
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
#include "skeletonkey_modules.h"
|
#include "skeletonkey_modules.h"
|
||||||
#include "../../core/registry.h"
|
#include "../../core/registry.h"
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
|
||||||
@@ -129,9 +130,14 @@ static bool can_create_af_unix(void)
|
|||||||
|
|
||||||
static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] af_unix_gc: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] af_unix_gc: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,10 +145,10 @@ static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx)
|
|||||||
* the dawn of time. ANY kernel below the fix is vulnerable. The
|
* the dawn of time. ANY kernel below the fix is vulnerable. The
|
||||||
* kernel_range walker handles "older than every entry" correctly
|
* kernel_range walker handles "older than every entry" correctly
|
||||||
* (returns false → not patched → vulnerable). */
|
* (returns false → not patched → vulnerable). */
|
||||||
bool patched = kernel_range_is_patched(&af_unix_gc_range, &v);
|
bool patched = kernel_range_is_patched(&af_unix_gc_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] af_unix_gc: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] af_unix_gc: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
@@ -157,7 +163,7 @@ static skeletonkey_result_t af_unix_gc_detect(const struct skeletonkey_ctx *ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[!] af_unix_gc: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[!] af_unix_gc: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] af_unix_gc: bug is reachable as PLAIN UNPRIVILEGED USER\n"
|
fprintf(stderr, "[i] af_unix_gc: bug is reachable as PLAIN UNPRIVILEGED USER\n"
|
||||||
" (no userns / no CAP_* required — AF_UNIX is universally\n"
|
" (no userns / no CAP_* required — AF_UNIX is universally\n"
|
||||||
" creatable). The race window is microseconds wide and\n"
|
" creatable). The race window is microseconds wide and\n"
|
||||||
@@ -549,7 +555,8 @@ static skeletonkey_result_t af_unix_gc_exploit_linux(const struct skeletonkey_ct
|
|||||||
fprintf(stderr, "[-] af_unix_gc: detect() says not vulnerable; refusing\n");
|
fprintf(stderr, "[-] af_unix_gc: detect() says not vulnerable; refusing\n");
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] af_unix_gc: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] af_unix_gc: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
@@ -74,44 +75,40 @@ static const struct kernel_range cgroup_ra_range = {
|
|||||||
sizeof(cgroup_ra_patched_branches[0]),
|
sizeof(cgroup_ra_patched_branches[0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
static int can_unshare_userns_mount(void)
|
/* The unprivileged-userns precondition is now read from the shared
|
||||||
{
|
* host fingerprint (ctx->host->unprivileged_userns_allowed), which
|
||||||
pid_t pid = fork();
|
* probes once at startup via core/host.c. The previous per-detect
|
||||||
if (pid < 0) return -1;
|
* fork-probe helper was removed. */
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static skeletonkey_result_t cgroup_ra_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t cgroup_ra_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] cgroup_release_agent: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] cgroup_release_agent: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&cgroup_ra_range, &v);
|
bool patched = kernel_range_is_patched(&cgroup_ra_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] cgroup_release_agent: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] cgroup_release_agent: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns_mount();
|
bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false;
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] cgroup_release_agent: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[i] cgroup_release_agent: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] cgroup_release_agent: user_ns+mount_ns clone: %s\n",
|
fprintf(stderr, "[i] cgroup_release_agent: user_ns+mount_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" : "could not test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] cgroup_release_agent: user_ns denied → unprivileged exploit unreachable\n");
|
fprintf(stderr, "[+] cgroup_release_agent: user_ns denied → unprivileged exploit unreachable\n");
|
||||||
}
|
}
|
||||||
@@ -157,7 +154,10 @@ static skeletonkey_result_t cgroup_ra_exploit(const struct skeletonkey_ctx *ctx)
|
|||||||
fprintf(stderr, "[-] cgroup_release_agent: detect() says not vulnerable; refusing\n");
|
fprintf(stderr, "[-] cgroup_release_agent: detect() says not vulnerable; refusing\n");
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
/* Consult ctx->host->is_root so unit tests can construct a
|
||||||
|
* non-root fingerprint regardless of the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] cgroup_release_agent: already root\n");
|
fprintf(stderr, "[i] cgroup_release_agent: already root\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
|
||||||
@@ -97,55 +98,46 @@ static bool cls_route4_module_available(void)
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int can_unshare_userns(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static skeletonkey_result_t cls_route4_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t cls_route4_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] cls_route4: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] cls_route4: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug-introduction predates anything we'd reasonably scan; if the
|
/* Bug-introduction predates anything we'd reasonably scan; if the
|
||||||
* kernel is below the oldest LTS we model (5.4), still report
|
* kernel is below the oldest LTS we model (5.4), still report
|
||||||
* vulnerable. */
|
* vulnerable. */
|
||||||
bool patched = kernel_range_is_patched(&cls_route4_range, &v);
|
bool patched = kernel_range_is_patched(&cls_route4_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] cls_route4: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] cls_route4: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Module + userns preconditions. */
|
/* Module + userns preconditions. */
|
||||||
bool nft_loaded = cls_route4_module_available();
|
bool nft_loaded = cls_route4_module_available();
|
||||||
int userns_ok = can_unshare_userns();
|
bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false;
|
||||||
|
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] cls_route4: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[i] cls_route4: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] cls_route4: cls_route4 module currently loaded: %s\n",
|
fprintf(stderr, "[i] cls_route4: cls_route4 module currently loaded: %s\n",
|
||||||
nft_loaded ? "yes" : "no (may autoload)");
|
nft_loaded ? "yes" : "no (may autoload)");
|
||||||
fprintf(stderr, "[i] cls_route4: unprivileged user_ns + net_ns clone: %s\n",
|
fprintf(stderr, "[i] cls_route4: unprivileged user_ns + net_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" : "could not test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If userns is locked down, unprivileged-LPE path is closed.
|
/* If userns is locked down, unprivileged-LPE path is closed.
|
||||||
* Kernel still needs patching though — report PRECOND_FAIL so the
|
* Kernel still needs patching though — report PRECOND_FAIL so the
|
||||||
* verdict isn't "VULNERABLE" but the issue isn't masked. */
|
* verdict isn't "VULNERABLE" but the issue isn't masked. */
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] cls_route4: user_ns denied → unprivileged exploit unreachable\n");
|
fprintf(stderr, "[+] cls_route4: user_ns denied → unprivileged exploit unreachable\n");
|
||||||
}
|
}
|
||||||
@@ -555,7 +547,8 @@ static skeletonkey_result_t cls_route4_exploit(const struct skeletonkey_ctx *ctx
|
|||||||
fprintf(stderr, "[-] cls_route4: detect() says not vulnerable; refusing\n");
|
fprintf(stderr, "[-] cls_route4: detect() says not vulnerable; refusing\n");
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] cls_route4: already root\n");
|
fprintf(stderr, "[i] cls_route4: already root\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -231,22 +232,27 @@ static void revert_passwd_page_cache(void)
|
|||||||
|
|
||||||
static skeletonkey_result_t dirty_cow_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t dirty_cow_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] dirty_cow: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] dirty_cow: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&dirty_cow_range, &v);
|
bool patched = kernel_range_is_patched(&dirty_cow_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] dirty_cow: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] dirty_cow: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[!] dirty_cow: kernel %s is in the vulnerable range\n",
|
fprintf(stderr, "[!] dirty_cow: kernel %s is in the vulnerable range\n",
|
||||||
v.release);
|
v->release);
|
||||||
fprintf(stderr, "[i] dirty_cow: --exploit will race a write to "
|
fprintf(stderr, "[i] dirty_cow: --exploit will race a write to "
|
||||||
"/etc/passwd via /proc/self/mem\n");
|
"/etc/passwd via /proc/self/mem\n");
|
||||||
}
|
}
|
||||||
@@ -261,7 +267,10 @@ static skeletonkey_result_t dirty_cow_exploit(const struct skeletonkey_ctx *ctx)
|
|||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (geteuid() == 0) {
|
/* Consult ctx->host->is_root so unit tests can construct a
|
||||||
|
* non-root fingerprint regardless of the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] dirty_cow: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] dirty_cow: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h" /* used inside this block only */
|
#include "../../core/kernel_range.h" /* used inside this block only */
|
||||||
|
#include "../../core/host.h"
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -257,22 +258,27 @@ static int dirty_pipe_active_probe(void)
|
|||||||
|
|
||||||
static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] dirty_pipe: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] dirty_pipe: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug introduced in 5.8. */
|
/* Bug introduced in 5.8. */
|
||||||
if (v.major < 5 || (v.major == 5 && v.minor < 8)) {
|
if (v->major < 5 || (v->major == 5 && v->minor < 8)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] dirty_pipe: kernel %s predates the bug (introduced in 5.8)\n",
|
fprintf(stderr, "[i] dirty_pipe: kernel %s predates the bug (introduced in 5.8)\n",
|
||||||
v.release);
|
v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched_by_version = kernel_range_is_patched(&dirty_pipe_range, &v);
|
bool patched_by_version = kernel_range_is_patched(&dirty_pipe_range, v);
|
||||||
|
|
||||||
/* Active probe overrides version-only verdict when requested.
|
/* Active probe overrides version-only verdict when requested.
|
||||||
* The version check is necessary-but-not-sufficient: distros
|
* The version check is necessary-but-not-sufficient: distros
|
||||||
@@ -287,7 +293,7 @@ static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx)
|
|||||||
if (probe == 1) {
|
if (probe == 1) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[!] dirty_pipe: ACTIVE PROBE CONFIRMED — primitive lands "
|
fprintf(stderr, "[!] dirty_pipe: ACTIVE PROBE CONFIRMED — primitive lands "
|
||||||
"(version %s)\n", v.release);
|
"(version %s)\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_VULNERABLE;
|
return SKELETONKEY_VULNERABLE;
|
||||||
}
|
}
|
||||||
@@ -310,14 +316,14 @@ static skeletonkey_result_t dirty_pipe_detect(const struct skeletonkey_ctx *ctx)
|
|||||||
if (patched_by_version) {
|
if (patched_by_version) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] dirty_pipe: kernel %s is patched (version-only check; "
|
fprintf(stderr, "[+] dirty_pipe: kernel %s is patched (version-only check; "
|
||||||
"use --active to confirm empirically)\n", v.release);
|
"use --active to confirm empirically)\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[!] dirty_pipe: kernel %s appears VULNERABLE (version-only check)\n"
|
fprintf(stderr, "[!] dirty_pipe: kernel %s appears VULNERABLE (version-only check)\n"
|
||||||
" Confirm empirically: re-run with --scan --active\n",
|
" Confirm empirically: re-run with --scan --active\n",
|
||||||
v.release);
|
v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_VULNERABLE;
|
return SKELETONKEY_VULNERABLE;
|
||||||
}
|
}
|
||||||
@@ -331,17 +337,20 @@ static skeletonkey_result_t dirty_pipe_exploit(const struct skeletonkey_ctx *ctx
|
|||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Resolve current user. */
|
/* Resolve current user. Consult ctx->host->is_root for the
|
||||||
|
* already-root short-circuit so unit tests can construct a
|
||||||
|
* non-root fingerprint regardless of the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
|
fprintf(stderr, "[i] dirty_pipe: already running as root — nothing to escalate\n");
|
||||||
|
return SKELETONKEY_OK;
|
||||||
|
}
|
||||||
uid_t euid = geteuid();
|
uid_t euid = geteuid();
|
||||||
struct passwd *pw = getpwuid(euid);
|
struct passwd *pw = getpwuid(euid);
|
||||||
if (!pw) {
|
if (!pw) {
|
||||||
fprintf(stderr, "[-] dirty_pipe: getpwuid(%d) failed: %s\n", euid, strerror(errno));
|
fprintf(stderr, "[-] dirty_pipe: getpwuid(%d) failed: %s\n", euid, strerror(errno));
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
if (euid == 0) {
|
|
||||||
fprintf(stderr, "[i] dirty_pipe: already running as root — nothing to escalate\n");
|
|
||||||
return SKELETONKEY_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find the UID field. Need a 4-digit-or-similar UID we can replace
|
/* Find the UID field. Need a 4-digit-or-similar UID we can replace
|
||||||
* with "0000" of identical width. Refuse if the user's UID width
|
* with "0000" of identical width. Refuse if the user's UID width
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
|
||||||
@@ -158,57 +159,53 @@ static const struct kernel_range fuse_legacy_range = {
|
|||||||
sizeof(fuse_legacy_patched_branches[0]),
|
sizeof(fuse_legacy_patched_branches[0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
static int can_unshare_userns_mount(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* detect */
|
/* detect */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
static skeletonkey_result_t fuse_legacy_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t fuse_legacy_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] fuse_legacy: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] fuse_legacy: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug introduced in 5.1 (when legacy_parse_param landed). Pre-5.1
|
/* Bug introduced in 5.1 (when legacy_parse_param landed). Pre-5.1
|
||||||
* kernels predate the code path entirely. */
|
* kernels predate the code path entirely. */
|
||||||
if (v.major < 5 || (v.major == 5 && v.minor < 1)) {
|
if (v->major < 5 || (v->major == 5 && v->minor < 1)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] fuse_legacy: kernel %s predates the bug introduction\n",
|
fprintf(stderr, "[+] fuse_legacy: kernel %s predates the bug introduction\n",
|
||||||
v.release);
|
v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&fuse_legacy_range, &v);
|
bool patched = kernel_range_is_patched(&fuse_legacy_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] fuse_legacy: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] fuse_legacy: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns_mount();
|
/* user_ns availability comes from the shared host fingerprint. The
|
||||||
|
* fingerprint's probe uses CLONE_NEWUSER alone; this module also
|
||||||
|
* needs CLONE_NEWNS, but the kernel gates both on the same userns
|
||||||
|
* sysctls (kernel.unprivileged_userns_clone / AppArmor restriction),
|
||||||
|
* so the userns probe is a sound proxy. */
|
||||||
|
bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false;
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] fuse_legacy: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[i] fuse_legacy: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] fuse_legacy: user_ns+mount_ns clone (CAP_SYS_ADMIN gate): %s\n",
|
fprintf(stderr, "[i] fuse_legacy: user_ns+mount_ns clone (CAP_SYS_ADMIN gate): %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" : "could not test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] fuse_legacy: user_ns denied → "
|
fprintf(stderr, "[+] fuse_legacy: user_ns denied → "
|
||||||
"unprivileged exploit unreachable\n");
|
"unprivileged exploit unreachable\n");
|
||||||
@@ -521,8 +518,11 @@ static skeletonkey_result_t fuse_legacy_exploit(const struct skeletonkey_ctx *ct
|
|||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (R2) Refuse if already root — no LPE work to do. */
|
/* (R2) Refuse if already root — no LPE work to do. Consult
|
||||||
if (geteuid() == 0) {
|
* ctx->host first so unit tests can construct a non-root
|
||||||
|
* fingerprint regardless of the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] fuse_legacy: already root; nothing to escalate\n");
|
fprintf(stderr, "[i] fuse_legacy: already root; nothing to escalate\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
|
||||||
@@ -116,53 +117,44 @@ static const struct kernel_range netfilter_xtcompat_range = {
|
|||||||
|
|
||||||
/* ---- Detect ------------------------------------------------------- */
|
/* ---- Detect ------------------------------------------------------- */
|
||||||
|
|
||||||
static int can_unshare_userns(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static skeletonkey_result_t netfilter_xtcompat_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t netfilter_xtcompat_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] netfilter_xtcompat: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] netfilter_xtcompat: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.major < 2 || (v.major == 2 && v.minor < 6)) {
|
if (v->major < 2 || (v->major == 2 && v->minor < 6)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s predates the bug introduction\n",
|
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s predates the bug introduction\n",
|
||||||
v.release);
|
v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&netfilter_xtcompat_range, &v);
|
bool patched = kernel_range_is_patched(&netfilter_xtcompat_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns();
|
bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false;
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] netfilter_xtcompat: kernel %s in vulnerable range "
|
fprintf(stderr, "[i] netfilter_xtcompat: kernel %s in vulnerable range "
|
||||||
"(bug existed since 2.6.19, 2006)\n", v.release);
|
"(bug existed since 2.6.19, 2006)\n", v->release);
|
||||||
fprintf(stderr, "[i] netfilter_xtcompat: user_ns+net_ns clone: %s\n",
|
fprintf(stderr, "[i] netfilter_xtcompat: user_ns+net_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" : "could not test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] netfilter_xtcompat: user_ns denied → "
|
fprintf(stderr, "[+] netfilter_xtcompat: user_ns denied → "
|
||||||
"unprivileged exploit path unreachable\n");
|
"unprivileged exploit path unreachable\n");
|
||||||
@@ -613,7 +605,10 @@ static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_
|
|||||||
{
|
{
|
||||||
/* 1. Refuse-gate: re-confirm vulnerability through detect(). */
|
/* 1. Refuse-gate: re-confirm vulnerability through detect(). */
|
||||||
skeletonkey_result_t pre = netfilter_xtcompat_detect(ctx);
|
skeletonkey_result_t pre = netfilter_xtcompat_detect(ctx);
|
||||||
if (pre == SKELETONKEY_OK && geteuid() == 0) {
|
/* Consult ctx->host first so unit tests can construct a non-root
|
||||||
|
* fingerprint regardless of the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (pre == SKELETONKEY_OK && is_root) {
|
||||||
fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
@@ -621,7 +616,7 @@ static skeletonkey_result_t netfilter_xtcompat_exploit(const struct skeletonkey_
|
|||||||
fprintf(stderr, "[-] netfilter_xtcompat: detect() says not vulnerable; refusing\n");
|
fprintf(stderr, "[-] netfilter_xtcompat: detect() says not vulnerable; refusing\n");
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] netfilter_xtcompat: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
|
||||||
@@ -112,19 +113,6 @@ static const struct kernel_range nf_tables_range = {
|
|||||||
* Preconditions probe
|
* Preconditions probe
|
||||||
* ------------------------------------------------------------------ */
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
static int can_unshare_userns(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool nf_tables_loaded(void)
|
static bool nf_tables_loaded(void)
|
||||||
{
|
{
|
||||||
FILE *f = fopen("/proc/modules", "r");
|
FILE *f = fopen("/proc/modules", "r");
|
||||||
@@ -140,44 +128,47 @@ static bool nf_tables_loaded(void)
|
|||||||
|
|
||||||
static skeletonkey_result_t nf_tables_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t nf_tables_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] nf_tables: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] nf_tables: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug introduced in 5.14. Anything below predates it. */
|
/* Bug introduced in 5.14. Anything below predates it. */
|
||||||
if (v.major < 5 || (v.major == 5 && v.minor < 14)) {
|
if (v->major < 5 || (v->major == 5 && v->minor < 14)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] nf_tables: kernel %s predates the bug "
|
fprintf(stderr, "[i] nf_tables: kernel %s predates the bug "
|
||||||
"(introduced in 5.14)\n", v.release);
|
"(introduced in 5.14)\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&nf_tables_range, &v);
|
bool patched = kernel_range_is_patched(&nf_tables_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] nf_tables: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] nf_tables: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns();
|
bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false;
|
||||||
bool nft_loaded = nf_tables_loaded();
|
bool nft_loaded = nf_tables_loaded();
|
||||||
|
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] nf_tables: kernel %s is in the vulnerable range\n",
|
fprintf(stderr, "[i] nf_tables: kernel %s is in the vulnerable range\n",
|
||||||
v.release);
|
v->release);
|
||||||
fprintf(stderr, "[i] nf_tables: unprivileged user_ns clone: %s\n",
|
fprintf(stderr, "[i] nf_tables: unprivileged user_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" :
|
|
||||||
"could not test");
|
|
||||||
fprintf(stderr, "[i] nf_tables: nf_tables module currently loaded: %s\n",
|
fprintf(stderr, "[i] nf_tables: nf_tables module currently loaded: %s\n",
|
||||||
nft_loaded ? "yes" : "no (will autoload on first nft use)");
|
nft_loaded ? "yes" : "no (will autoload on first nft use)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] nf_tables: kernel vulnerable but user_ns clone "
|
fprintf(stderr, "[+] nf_tables: kernel vulnerable but user_ns clone "
|
||||||
"denied → unprivileged exploit unreachable\n");
|
"denied → unprivileged exploit unreachable\n");
|
||||||
@@ -809,8 +800,11 @@ static skeletonkey_result_t nf_tables_exploit(const struct skeletonkey_ctx *ctx)
|
|||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gate 2: already root? Nothing to escalate. */
|
/* Gate 2: already root? Nothing to escalate. Consult ctx->host first
|
||||||
if (geteuid() == 0) {
|
* so unit tests can construct a non-root fingerprint regardless of
|
||||||
|
* the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
if (!ctx->json)
|
if (!ctx->json)
|
||||||
fprintf(stderr, "[i] nf_tables: already running as root\n");
|
fprintf(stderr, "[i] nf_tables: already running as root\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
@@ -103,19 +104,6 @@ static const struct kernel_range nft_fwd_dup_range = {
|
|||||||
* Probes.
|
* Probes.
|
||||||
* ------------------------------------------------------------------ */
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
static int can_unshare_userns(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool nf_tables_loaded(void)
|
static bool nf_tables_loaded(void)
|
||||||
{
|
{
|
||||||
FILE *f = fopen("/proc/modules", "r");
|
FILE *f = fopen("/proc/modules", "r");
|
||||||
@@ -131,45 +119,43 @@ static bool nf_tables_loaded(void)
|
|||||||
|
|
||||||
static skeletonkey_result_t nft_fwd_dup_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t nft_fwd_dup_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
if (!kernel_version_current(&v)) {
|
if (!v || v->major == 0) {
|
||||||
fprintf(stderr, "[!] nft_fwd_dup: could not parse kernel version\n");
|
if (!ctx->json) fprintf(stderr, "[!] nft_fwd_dup: host fingerprint missing kernel version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The offload code path only exists from 5.4 onward. Anything
|
/* The offload code path only exists from 5.4 onward. Anything
|
||||||
* older predates the bug. */
|
* older predates the bug. */
|
||||||
if (v.major < 5 || (v.major == 5 && v.minor < 4)) {
|
if (v->major < 5 || (v->major == 5 && v->minor < 4)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] nft_fwd_dup: kernel %s predates the bug "
|
fprintf(stderr, "[i] nft_fwd_dup: kernel %s predates the bug "
|
||||||
"(nft offload hook introduced in 5.4)\n", v.release);
|
"(nft offload hook introduced in 5.4)\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&nft_fwd_dup_range, &v);
|
bool patched = kernel_range_is_patched(&nft_fwd_dup_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] nft_fwd_dup: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] nft_fwd_dup: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns();
|
bool userns_ok = ctx->host->unprivileged_userns_allowed;
|
||||||
bool nft_loaded = nf_tables_loaded();
|
bool nft_loaded = nf_tables_loaded();
|
||||||
|
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] nft_fwd_dup: kernel %s is in the vulnerable range\n",
|
fprintf(stderr, "[i] nft_fwd_dup: kernel %s is in the vulnerable range\n",
|
||||||
v.release);
|
v->release);
|
||||||
fprintf(stderr, "[i] nft_fwd_dup: unprivileged user_ns+net_ns clone: %s\n",
|
fprintf(stderr, "[i] nft_fwd_dup: unprivileged user_ns+net_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" :
|
|
||||||
"could not test");
|
|
||||||
fprintf(stderr, "[i] nft_fwd_dup: nf_tables module currently loaded: %s\n",
|
fprintf(stderr, "[i] nft_fwd_dup: nf_tables module currently loaded: %s\n",
|
||||||
nft_loaded ? "yes" : "no (will autoload)");
|
nft_loaded ? "yes" : "no (will autoload)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] nft_fwd_dup: kernel vulnerable but user_ns clone "
|
fprintf(stderr, "[+] nft_fwd_dup: kernel vulnerable but user_ns clone "
|
||||||
"denied → unprivileged path unreachable\n");
|
"denied → unprivileged path unreachable\n");
|
||||||
@@ -733,7 +719,8 @@ static skeletonkey_result_t nft_fwd_dup_exploit(const struct skeletonkey_ctx *ct
|
|||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
}
|
}
|
||||||
/* Gate 1: already root? */
|
/* Gate 1: already root? */
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
if (!ctx->json)
|
if (!ctx->json)
|
||||||
fprintf(stderr, "[i] nft_fwd_dup: already running as root\n");
|
fprintf(stderr, "[i] nft_fwd_dup: already running as root\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
@@ -104,19 +105,6 @@ static const struct kernel_range nft_payload_range = {
|
|||||||
* Preconditions probe
|
* Preconditions probe
|
||||||
* ------------------------------------------------------------------ */
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
static int can_unshare_userns(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool nf_tables_loaded(void)
|
static bool nf_tables_loaded(void)
|
||||||
{
|
{
|
||||||
FILE *f = fopen("/proc/modules", "r");
|
FILE *f = fopen("/proc/modules", "r");
|
||||||
@@ -132,46 +120,44 @@ static bool nf_tables_loaded(void)
|
|||||||
|
|
||||||
static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t nft_payload_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
if (!kernel_version_current(&v)) {
|
if (!v || v->major == 0) {
|
||||||
fprintf(stderr, "[!] nft_payload: could not parse kernel version\n");
|
if (!ctx->json) fprintf(stderr, "[!] nft_payload: host fingerprint missing kernel version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug introduced with the set-payload extension in 5.4. Anything
|
/* Bug introduced with the set-payload extension in 5.4. Anything
|
||||||
* below 5.4 predates the affected codepath entirely. */
|
* below 5.4 predates the affected codepath entirely. */
|
||||||
if (v.major < 5 || (v.major == 5 && v.minor < 4)) {
|
if (v->major < 5 || (v->major == 5 && v->minor < 4)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] nft_payload: kernel %s predates the bug "
|
fprintf(stderr, "[i] nft_payload: kernel %s predates the bug "
|
||||||
"(set-payload extension landed in 5.4)\n",
|
"(set-payload extension landed in 5.4)\n",
|
||||||
v.release);
|
v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&nft_payload_range, &v);
|
bool patched = kernel_range_is_patched(&nft_payload_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] nft_payload: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] nft_payload: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns();
|
bool userns_ok = ctx->host->unprivileged_userns_allowed;
|
||||||
bool nft_loaded = nf_tables_loaded();
|
bool nft_loaded = nf_tables_loaded();
|
||||||
|
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] nft_payload: kernel %s is in the vulnerable range\n",
|
fprintf(stderr, "[i] nft_payload: kernel %s is in the vulnerable range\n",
|
||||||
v.release);
|
v->release);
|
||||||
fprintf(stderr, "[i] nft_payload: unprivileged user_ns clone: %s\n",
|
fprintf(stderr, "[i] nft_payload: unprivileged user_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" :
|
|
||||||
"could not test");
|
|
||||||
fprintf(stderr, "[i] nft_payload: nf_tables module currently loaded: %s\n",
|
fprintf(stderr, "[i] nft_payload: nf_tables module currently loaded: %s\n",
|
||||||
nft_loaded ? "yes" : "no (will autoload on first nft use)");
|
nft_loaded ? "yes" : "no (will autoload on first nft use)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] nft_payload: kernel vulnerable but user_ns "
|
fprintf(stderr, "[+] nft_payload: kernel vulnerable but user_ns "
|
||||||
"clone denied → unprivileged exploit unreachable\n");
|
"clone denied → unprivileged exploit unreachable\n");
|
||||||
@@ -811,7 +797,8 @@ static skeletonkey_result_t nft_payload_exploit(const struct skeletonkey_ctx *ct
|
|||||||
"exploit code can crash the kernel\n");
|
"exploit code can crash the kernel\n");
|
||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
if (!ctx->json)
|
if (!ctx->json)
|
||||||
fprintf(stderr, "[i] nft_payload: already running as root\n");
|
fprintf(stderr, "[i] nft_payload: already running as root\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#include "skeletonkey_modules.h"
|
#include "skeletonkey_modules.h"
|
||||||
#include "../../core/registry.h"
|
#include "../../core/registry.h"
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -115,19 +116,6 @@ static const struct kernel_range nft_set_uaf_range = {
|
|||||||
* ------------------------------------------------------------------ */
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
static int can_unshare_userns(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool nf_tables_loaded(void)
|
static bool nf_tables_loaded(void)
|
||||||
{
|
{
|
||||||
FILE *f = fopen("/proc/modules", "r");
|
FILE *f = fopen("/proc/modules", "r");
|
||||||
@@ -148,45 +136,43 @@ static skeletonkey_result_t nft_set_uaf_detect(const struct skeletonkey_ctx *ctx
|
|||||||
(void)ctx;
|
(void)ctx;
|
||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
#else
|
#else
|
||||||
struct kernel_version v;
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
if (!kernel_version_current(&v)) {
|
if (!v || v->major == 0) {
|
||||||
fprintf(stderr, "[!] nft_set_uaf: could not parse kernel version\n");
|
if (!ctx->json) fprintf(stderr, "[!] nft_set_uaf: host fingerprint missing kernel version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug introduced in 5.1 (anonymous-set support). Anything below
|
/* Bug introduced in 5.1 (anonymous-set support). Anything below
|
||||||
* predates it — report OK (not vulnerable to *this* CVE). */
|
* predates it — report OK (not vulnerable to *this* CVE). */
|
||||||
if (v.major < 5 || (v.major == 5 && v.minor < 1)) {
|
if (v->major < 5 || (v->major == 5 && v->minor < 1)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] nft_set_uaf: kernel %s predates the bug "
|
fprintf(stderr, "[i] nft_set_uaf: kernel %s predates the bug "
|
||||||
"(anonymous-set support landed in 5.1)\n", v.release);
|
"(anonymous-set support landed in 5.1)\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&nft_set_uaf_range, &v);
|
bool patched = kernel_range_is_patched(&nft_set_uaf_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] nft_set_uaf: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] nft_set_uaf: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns();
|
bool userns_ok = ctx->host->unprivileged_userns_allowed;
|
||||||
bool nft_loaded = nf_tables_loaded();
|
bool nft_loaded = nf_tables_loaded();
|
||||||
|
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] nft_set_uaf: kernel %s is in the vulnerable range\n",
|
fprintf(stderr, "[i] nft_set_uaf: kernel %s is in the vulnerable range\n",
|
||||||
v.release);
|
v->release);
|
||||||
fprintf(stderr, "[i] nft_set_uaf: unprivileged user_ns clone: %s\n",
|
fprintf(stderr, "[i] nft_set_uaf: unprivileged user_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" :
|
|
||||||
"could not test");
|
|
||||||
fprintf(stderr, "[i] nft_set_uaf: nf_tables module currently loaded: %s\n",
|
fprintf(stderr, "[i] nft_set_uaf: nf_tables module currently loaded: %s\n",
|
||||||
nft_loaded ? "yes" : "no (will autoload on first nft use)");
|
nft_loaded ? "yes" : "no (will autoload on first nft use)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] nft_set_uaf: kernel vulnerable but user_ns clone "
|
fprintf(stderr, "[+] nft_set_uaf: kernel vulnerable but user_ns clone "
|
||||||
"denied → unprivileged exploit unreachable\n");
|
"denied → unprivileged exploit unreachable\n");
|
||||||
@@ -762,7 +748,8 @@ static skeletonkey_result_t nft_set_uaf_exploit(const struct skeletonkey_ctx *ct
|
|||||||
fprintf(stderr, "[-] nft_set_uaf: refusing without --i-know gate\n");
|
fprintf(stderr, "[-] nft_set_uaf: refusing without --i-know gate\n");
|
||||||
return SKELETONKEY_EXPLOIT_FAIL;
|
return SKELETONKEY_EXPLOIT_FAIL;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
if (!ctx->json)
|
if (!ctx->json)
|
||||||
fprintf(stderr, "[i] nft_set_uaf: already running as root\n");
|
fprintf(stderr, "[i] nft_set_uaf: already running as root\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -71,18 +72,10 @@ static const struct kernel_range overlayfs_setuid_range = {
|
|||||||
sizeof(overlayfs_setuid_patched_branches[0]),
|
sizeof(overlayfs_setuid_patched_branches[0]),
|
||||||
};
|
};
|
||||||
|
|
||||||
static int can_unshare_userns_mount(void)
|
/* The unprivileged-userns precondition is now read from the shared
|
||||||
{
|
* host fingerprint (ctx->host->unprivileged_userns_allowed), which
|
||||||
pid_t pid = fork();
|
* probes once at startup via core/host.c. The previous per-detect
|
||||||
if (pid < 0) return -1;
|
* fork-probe helper was removed. */
|
||||||
if (pid == 0) {
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == 0) _exit(0);
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *find_setuid_in_lower(void)
|
static const char *find_setuid_in_lower(void)
|
||||||
{
|
{
|
||||||
@@ -101,39 +94,43 @@ static const char *find_setuid_in_lower(void)
|
|||||||
|
|
||||||
static skeletonkey_result_t overlayfs_setuid_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t overlayfs_setuid_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] overlayfs_setuid: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] overlayfs_setuid: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug introduced in 5.11 when ovl copy-up was generalized.
|
/* Bug introduced in 5.11 when ovl copy-up was generalized.
|
||||||
* Pre-5.11 immune via a different code path. */
|
* Pre-5.11 immune via a different code path. */
|
||||||
if (v.major < 5 || (v.major == 5 && v.minor < 11)) {
|
if (v->major < 5 || (v->major == 5 && v->minor < 11)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] overlayfs_setuid: kernel %s predates the bug "
|
fprintf(stderr, "[+] overlayfs_setuid: kernel %s predates the bug "
|
||||||
"(introduced in 5.11)\n", v.release);
|
"(introduced in 5.11)\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&overlayfs_setuid_range, &v);
|
bool patched = kernel_range_is_patched(&overlayfs_setuid_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] overlayfs_setuid: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] overlayfs_setuid: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns_mount();
|
bool userns_ok = ctx->host ? ctx->host->unprivileged_userns_allowed : false;
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] overlayfs_setuid: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[i] overlayfs_setuid: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] overlayfs_setuid: user_ns+mount_ns clone: %s\n",
|
fprintf(stderr, "[i] overlayfs_setuid: user_ns+mount_ns clone: %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" : "could not test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] overlayfs_setuid: user_ns denied → unprivileged exploit unreachable\n");
|
fprintf(stderr, "[+] overlayfs_setuid: user_ns denied → unprivileged exploit unreachable\n");
|
||||||
}
|
}
|
||||||
@@ -200,7 +197,10 @@ static skeletonkey_result_t overlayfs_setuid_exploit(const struct skeletonkey_ct
|
|||||||
fprintf(stderr, "[-] overlayfs_setuid: detect() says not vulnerable; refusing\n");
|
fprintf(stderr, "[-] overlayfs_setuid: detect() says not vulnerable; refusing\n");
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
/* Consult ctx->host->is_root so unit tests can construct a
|
||||||
|
* non-root fingerprint regardless of the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] overlayfs_setuid: already root\n");
|
fprintf(stderr, "[i] overlayfs_setuid: already root\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
@@ -66,32 +67,37 @@ static const struct kernel_range ptrace_traceme_range = {
|
|||||||
|
|
||||||
static skeletonkey_result_t ptrace_traceme_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t ptrace_traceme_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
/* Consult the shared host fingerprint instead of calling
|
||||||
if (!kernel_version_current(&v)) {
|
* kernel_version_current() ourselves — populated once at startup
|
||||||
fprintf(stderr, "[!] ptrace_traceme: could not parse kernel version\n");
|
* and identical across every module's detect(). */
|
||||||
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
|
if (!v || v->major == 0) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[!] ptrace_traceme: host fingerprint missing kernel "
|
||||||
|
"version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug existed since ptrace's inception (early 2.x); anything
|
/* Bug existed since ptrace's inception (early 2.x); anything
|
||||||
* pre-LTS-backport is vulnerable. Anything < 4.4 in our range
|
* pre-LTS-backport is vulnerable. Anything < 4.4 in our range
|
||||||
* model defaults to vulnerable since no entry covers it. */
|
* model defaults to vulnerable since no entry covers it. */
|
||||||
if (v.major < 4 || (v.major == 4 && v.minor < 4)) {
|
if (v->major < 4 || (v->major == 4 && v->minor < 4)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[!] ptrace_traceme: ancient kernel %s — assume VULNERABLE\n",
|
fprintf(stderr, "[!] ptrace_traceme: ancient kernel %s — assume VULNERABLE\n",
|
||||||
v.release);
|
v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_VULNERABLE;
|
return SKELETONKEY_VULNERABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&ptrace_traceme_range, &v);
|
bool patched = kernel_range_is_patched(&ptrace_traceme_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] ptrace_traceme: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] ptrace_traceme: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[!] ptrace_traceme: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[!] ptrace_traceme: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] ptrace_traceme: no exotic preconditions — works on default config "
|
fprintf(stderr, "[i] ptrace_traceme: no exotic preconditions — works on default config "
|
||||||
"(no user_ns required)\n");
|
"(no user_ns required)\n");
|
||||||
}
|
}
|
||||||
@@ -186,7 +192,10 @@ static skeletonkey_result_t ptrace_traceme_exploit(const struct skeletonkey_ctx
|
|||||||
fprintf(stderr, "[-] ptrace_traceme: detect() says not vulnerable; refusing\n");
|
fprintf(stderr, "[-] ptrace_traceme: detect() says not vulnerable; refusing\n");
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
/* Consult ctx->host->is_root so unit tests can construct a
|
||||||
|
* non-root fingerprint regardless of the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] ptrace_traceme: already root\n");
|
fprintf(stderr, "[i] ptrace_traceme: already root\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include "skeletonkey_modules.h"
|
#include "skeletonkey_modules.h"
|
||||||
#include "../../core/registry.h"
|
#include "../../core/registry.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -215,7 +216,10 @@ static skeletonkey_result_t pwnkit_exploit(const struct skeletonkey_ctx *ctx)
|
|||||||
const char *pkexec = find_pkexec();
|
const char *pkexec = find_pkexec();
|
||||||
if (!pkexec) return SKELETONKEY_PRECOND_FAIL;
|
if (!pkexec) return SKELETONKEY_PRECOND_FAIL;
|
||||||
|
|
||||||
if (geteuid() == 0) {
|
/* Consult ctx->host->is_root so unit tests can construct a
|
||||||
|
* non-root fingerprint regardless of the test process's real euid. */
|
||||||
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] pwnkit: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] pwnkit: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -166,25 +167,6 @@ static bool write_file(const char *path, const char *s)
|
|||||||
return n == (ssize_t)strlen(s);
|
return n == (ssize_t)strlen(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Probe: can this user unshare(CLONE_NEWUSER|CLONE_NEWNS) and get
|
|
||||||
* CAP_SYS_ADMIN-in-userns? We need this for the bind-mount step. The
|
|
||||||
* deeply-nested mkdir works without it, but the trigger needs the
|
|
||||||
* extra mountinfo entry to push the rendered string past INT_MAX. */
|
|
||||||
static int can_unshare_userns_mount(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid < 0) return -1;
|
|
||||||
if (pid == 0) {
|
|
||||||
#ifdef __linux__
|
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == 0) _exit(0);
|
|
||||||
#endif
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
int status = 0;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
static bool enter_userns_root(void)
|
static bool enter_userns_root(void)
|
||||||
{
|
{
|
||||||
@@ -215,31 +197,30 @@ static bool enter_userns_root(void)
|
|||||||
|
|
||||||
static skeletonkey_result_t sequoia_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t sequoia_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
if (!kernel_version_current(&v)) {
|
if (!v || v->major == 0) {
|
||||||
fprintf(stderr, "[!] sequoia: could not parse kernel version\n");
|
if (!ctx->json) fprintf(stderr, "[!] sequoia: host fingerprint missing kernel version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The bug predates every kernel we'd run on, so there's no
|
/* The bug predates every kernel we'd run on, so there's no
|
||||||
* "pre-introduction" cutoff; only patched-or-not matters. */
|
* "pre-introduction" cutoff; only patched-or-not matters. */
|
||||||
bool patched = kernel_range_is_patched(&sequoia_range, &v);
|
bool patched = kernel_range_is_patched(&sequoia_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] sequoia: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] sequoia: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int userns_ok = can_unshare_userns_mount();
|
bool userns_ok = ctx->host->unprivileged_userns_allowed;
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] sequoia: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[i] sequoia: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] sequoia: user_ns+mount_ns clone (CAP_SYS_ADMIN gate): %s\n",
|
fprintf(stderr, "[i] sequoia: user_ns+mount_ns clone (CAP_SYS_ADMIN gate): %s\n",
|
||||||
userns_ok == 1 ? "ALLOWED" :
|
userns_ok ? "ALLOWED" : "DENIED");
|
||||||
userns_ok == 0 ? "DENIED" : "could not test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userns_ok == 0) {
|
if (!userns_ok) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] sequoia: user_ns denied → unprivileged "
|
fprintf(stderr, "[+] sequoia: user_ns denied → unprivileged "
|
||||||
"exploit unreachable via bind-mount path\n");
|
"exploit unreachable via bind-mount path\n");
|
||||||
@@ -408,7 +389,8 @@ static skeletonkey_result_t sequoia_exploit_linux(const struct skeletonkey_ctx *
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* (R1) refuse if already root. */
|
/* (R1) refuse if already root. */
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] sequoia: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] sequoia: already root — nothing to escalate\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@
|
|||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -150,31 +151,31 @@ static bool maple_tree_variant_present(const struct kernel_version *v)
|
|||||||
|
|
||||||
static skeletonkey_result_t stackrot_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t stackrot_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
if (!kernel_version_current(&v)) {
|
if (!v || v->major == 0) {
|
||||||
fprintf(stderr, "[!] stackrot: could not parse kernel version\n");
|
if (!ctx->json) fprintf(stderr, "[!] stackrot: host fingerprint missing kernel version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug introduced in 6.1 (when maple tree landed). Pre-6.1 kernels
|
/* Bug introduced in 6.1 (when maple tree landed). Pre-6.1 kernels
|
||||||
* use rbtree-based VMAs and don't have this bug. */
|
* use rbtree-based VMAs and don't have this bug. */
|
||||||
if (v.major < 6 || (v.major == 6 && v.minor < 1)) {
|
if (v->major < 6 || (v->major == 6 && v->minor < 1)) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] stackrot: kernel %s predates maple-tree VMA code (introduced in 6.1)\n",
|
fprintf(stderr, "[+] stackrot: kernel %s predates maple-tree VMA code (introduced in 6.1)\n",
|
||||||
v.release);
|
v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&stackrot_range, &v);
|
bool patched = kernel_range_is_patched(&stackrot_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] stackrot: kernel %s is patched\n", v.release);
|
fprintf(stderr, "[+] stackrot: kernel %s is patched\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[!] stackrot: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[!] stackrot: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] stackrot: mm-class bug — affects default-config kernels; "
|
fprintf(stderr, "[i] stackrot: mm-class bug — affects default-config kernels; "
|
||||||
"no exotic preconditions\n");
|
"no exotic preconditions\n");
|
||||||
}
|
}
|
||||||
@@ -631,7 +632,8 @@ static skeletonkey_result_t stackrot_exploit_linux(const struct skeletonkey_ctx
|
|||||||
fprintf(stderr, "[-] stackrot: detect() says not vulnerable; refusing\n");
|
fprintf(stderr, "[-] stackrot: detect() says not vulnerable; refusing\n");
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] stackrot: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] stackrot: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
@@ -641,8 +643,8 @@ static skeletonkey_result_t stackrot_exploit_linux(const struct skeletonkey_ctx
|
|||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
if (!kernel_version_current(&v) || !maple_tree_variant_present(&v)) {
|
if (!v || v->major == 0 || !maple_tree_variant_present(v)) {
|
||||||
fprintf(stderr, "[-] stackrot: maple-tree variant not detectable\n");
|
fprintf(stderr, "[-] stackrot: maple-tree variant not detectable\n");
|
||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
#include "skeletonkey_modules.h"
|
#include "skeletonkey_modules.h"
|
||||||
#include "../../core/registry.h"
|
#include "../../core/registry.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -246,7 +247,8 @@ static skeletonkey_result_t sudo_samedit_exploit(const struct skeletonkey_ctx *c
|
|||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] sudo_samedit: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] sudo_samedit: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include "skeletonkey_modules.h"
|
#include "skeletonkey_modules.h"
|
||||||
#include "../../core/registry.h"
|
#include "../../core/registry.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -331,7 +332,8 @@ static skeletonkey_result_t sudoedit_editor_exploit(const struct skeletonkey_ctx
|
|||||||
fprintf(stderr, "[-] sudoedit_editor: refusing exploit — pass --i-know to authorize\n");
|
fprintf(stderr, "[-] sudoedit_editor: refusing exploit — pass --i-know to authorize\n");
|
||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] sudoedit_editor: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] sudoedit_editor: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "../../core/kernel_range.h"
|
#include "../../core/kernel_range.h"
|
||||||
#include "../../core/offsets.h"
|
#include "../../core/offsets.h"
|
||||||
#include "../../core/finisher.h"
|
#include "../../core/finisher.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -219,26 +220,26 @@ static char *probe_drm_version_name(const char *cardpath)
|
|||||||
|
|
||||||
static skeletonkey_result_t vmwgfx_detect(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t vmwgfx_detect(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct kernel_version v;
|
const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL;
|
||||||
if (!kernel_version_current(&v)) {
|
if (!v || v->major == 0) {
|
||||||
fprintf(stderr, "[!] vmwgfx: could not parse kernel version\n");
|
if (!ctx->json) fprintf(stderr, "[!] vmwgfx: host fingerprint missing kernel version — bailing\n");
|
||||||
return SKELETONKEY_TEST_ERROR;
|
return SKELETONKEY_TEST_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool patched = kernel_range_is_patched(&vmwgfx_range, &v);
|
bool patched = kernel_range_is_patched(&vmwgfx_range, v);
|
||||||
if (patched) {
|
if (patched) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] vmwgfx: kernel %s is patched (>= 6.3-rc6 / "
|
fprintf(stderr, "[+] vmwgfx: kernel %s is patched (>= 6.3-rc6 / "
|
||||||
"6.2.10 / 6.1.23)\n", v.release);
|
"6.2.10 / 6.1.23)\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pre-vmwgfx kernels (no driver shipped) — extremely unlikely but
|
/* Pre-vmwgfx kernels (no driver shipped) — extremely unlikely but
|
||||||
* report PRECOND_FAIL rather than VULNERABLE. */
|
* report PRECOND_FAIL rather than VULNERABLE. */
|
||||||
if (v.major < 4) {
|
if (v->major < 4) {
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[+] vmwgfx: kernel %s predates vmwgfx driver\n", v.release);
|
fprintf(stderr, "[+] vmwgfx: kernel %s predates vmwgfx driver\n", v->release);
|
||||||
}
|
}
|
||||||
return SKELETONKEY_PRECOND_FAIL;
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
}
|
}
|
||||||
@@ -247,7 +248,7 @@ static skeletonkey_result_t vmwgfx_detect(const struct skeletonkey_ctx *ctx)
|
|||||||
char vendor[128] = {0};
|
char vendor[128] = {0};
|
||||||
bool vmware = host_is_vmware_guest(vendor, sizeof vendor);
|
bool vmware = host_is_vmware_guest(vendor, sizeof vendor);
|
||||||
if (!ctx->json) {
|
if (!ctx->json) {
|
||||||
fprintf(stderr, "[i] vmwgfx: kernel %s in vulnerable range\n", v.release);
|
fprintf(stderr, "[i] vmwgfx: kernel %s in vulnerable range\n", v->release);
|
||||||
fprintf(stderr, "[i] vmwgfx: dmi sys_vendor = \"%s\"\n",
|
fprintf(stderr, "[i] vmwgfx: dmi sys_vendor = \"%s\"\n",
|
||||||
vendor[0] ? vendor : "(unreadable)");
|
vendor[0] ? vendor : "(unreadable)");
|
||||||
}
|
}
|
||||||
@@ -520,7 +521,8 @@ static skeletonkey_result_t vmwgfx_exploit_linux(const struct skeletonkey_ctx *c
|
|||||||
fprintf(stderr, "[-] vmwgfx: detect() says not vulnerable; refusing\n");
|
fprintf(stderr, "[-] vmwgfx: detect() says not vulnerable; refusing\n");
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
if (geteuid() == 0) {
|
bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0);
|
||||||
|
if (is_root) {
|
||||||
fprintf(stderr, "[i] vmwgfx: already root — nothing to escalate\n");
|
fprintf(stderr, "[i] vmwgfx: already root — nothing to escalate\n");
|
||||||
return SKELETONKEY_OK;
|
return SKELETONKEY_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user