copy_fail_family: bridge-level userns gate + 4 new tests (33 total)
The 4 dirty_frag siblings + the GCM variant all gate on unprivileged user-namespace creation (the XFRM-ESP / AF_RXRPC paths are unreachable without it). The inner DIRTYFAIL detect functions already check this, but the check happened deep inside the legacy code — invisible to the test harness, and the bridge wrappers would delegate first and only short-circuit afterwards. Move the check up to the bridge: a single cff_check_userns() helper inspects ctx->host->unprivileged_userns_allowed and returns PRECOND_FAIL (with a host-fingerprint-annotated message) BEFORE calling the inner detect. The inner check stays in place as belt- and-suspenders. copy_fail itself uses AF_ALG (no userns needed) and bypasses the gate — its inner detect still confirms the primitive empirically via the active probe. modules/copy_fail_family/skeletonkey_modules.c: - #include "../../core/host.h" alongside the existing includes. - new static cff_check_userns(modname, ctx) helper. - copy_fail_gcm_detect_wrap, dirty_frag_esp_detect_wrap, dirty_frag_esp6_detect_wrap, dirty_frag_rxrpc_detect_wrap all call cff_check_userns before delegating. - copy_fail_detect_wrap is intentionally untouched. tests/test_detect.c: 4 new EXPECT_DETECT cases assert that all 4 gated bridge wrappers return PRECOND_FAIL when unprivileged_userns_allowed=false, using the existing h_kernel_5_14_no_userns fingerprint. 29 → 33 tests, all pass on Linux.
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
#include "skeletonkey_modules.h"
|
#include "skeletonkey_modules.h"
|
||||||
#include "../../core/registry.h"
|
#include "../../core/registry.h"
|
||||||
|
#include "../../core/host.h"
|
||||||
|
|
||||||
#include "src/common.h"
|
#include "src/common.h"
|
||||||
#include "src/copyfail.h"
|
#include "src/copyfail.h"
|
||||||
@@ -43,6 +44,29 @@ static void apply_ctx(const struct skeletonkey_ctx *ctx)
|
|||||||
* it's a debug knob; default stays off. */
|
* it's a debug knob; default stays off. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bridge-level userns precondition. The 4 dirty_frag siblings + the
|
||||||
|
* GCM variant all reach the bug via XFRM-ESP / AF_RXRPC paths gated on
|
||||||
|
* unprivileged user-namespace creation (the inner DIRTYFAIL detect
|
||||||
|
* checks for it too, but doing it here gives the dispatcher one
|
||||||
|
* testable point per module and short-circuits the heavier
|
||||||
|
* inner-detect work when the gate is closed). copy_fail itself uses
|
||||||
|
* AF_ALG which doesn't strictly need userns, so it bypasses this
|
||||||
|
* gate — its inner detect still confirms the primitive empirically. */
|
||||||
|
static skeletonkey_result_t cff_check_userns(const char *modname,
|
||||||
|
const struct skeletonkey_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (ctx->host && !ctx->host->unprivileged_userns_allowed) {
|
||||||
|
if (!ctx->json)
|
||||||
|
fprintf(stderr, "[i] %s: unprivileged user namespaces are "
|
||||||
|
"disabled (host fingerprint) — XFRM/RxRPC variant "
|
||||||
|
"unreachable here%s\n", modname,
|
||||||
|
ctx->host->apparmor_restrict_userns
|
||||||
|
? "; AppArmor restriction is on" : "");
|
||||||
|
return SKELETONKEY_PRECOND_FAIL;
|
||||||
|
}
|
||||||
|
return SKELETONKEY_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----- Family-wide --mitigate / --cleanup -----
|
/* ----- Family-wide --mitigate / --cleanup -----
|
||||||
*
|
*
|
||||||
* The family-wide mitigation (blacklist algif_aead + esp4 + esp6 + rxrpc,
|
* The family-wide mitigation (blacklist algif_aead + esp4 + esp6 + rxrpc,
|
||||||
@@ -154,6 +178,8 @@ const struct skeletonkey_module copy_fail_module = {
|
|||||||
static skeletonkey_result_t copy_fail_gcm_detect_wrap(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t copy_fail_gcm_detect_wrap(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
apply_ctx(ctx);
|
apply_ctx(ctx);
|
||||||
|
skeletonkey_result_t pre = cff_check_userns("copy_fail_gcm", ctx);
|
||||||
|
if (pre != SKELETONKEY_OK) return pre;
|
||||||
return (skeletonkey_result_t)copyfail_gcm_detect();
|
return (skeletonkey_result_t)copyfail_gcm_detect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +210,8 @@ const struct skeletonkey_module copy_fail_gcm_module = {
|
|||||||
static skeletonkey_result_t dirty_frag_esp_detect_wrap(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t dirty_frag_esp_detect_wrap(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
apply_ctx(ctx);
|
apply_ctx(ctx);
|
||||||
|
skeletonkey_result_t pre = cff_check_userns("dirty_frag_esp", ctx);
|
||||||
|
if (pre != SKELETONKEY_OK) return pre;
|
||||||
return (skeletonkey_result_t)dirtyfrag_esp_detect();
|
return (skeletonkey_result_t)dirtyfrag_esp_detect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +242,8 @@ const struct skeletonkey_module dirty_frag_esp_module = {
|
|||||||
static skeletonkey_result_t dirty_frag_esp6_detect_wrap(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t dirty_frag_esp6_detect_wrap(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
apply_ctx(ctx);
|
apply_ctx(ctx);
|
||||||
|
skeletonkey_result_t pre = cff_check_userns("dirty_frag_esp6", ctx);
|
||||||
|
if (pre != SKELETONKEY_OK) return pre;
|
||||||
return (skeletonkey_result_t)dirtyfrag_esp6_detect();
|
return (skeletonkey_result_t)dirtyfrag_esp6_detect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +274,8 @@ const struct skeletonkey_module dirty_frag_esp6_module = {
|
|||||||
static skeletonkey_result_t dirty_frag_rxrpc_detect_wrap(const struct skeletonkey_ctx *ctx)
|
static skeletonkey_result_t dirty_frag_rxrpc_detect_wrap(const struct skeletonkey_ctx *ctx)
|
||||||
{
|
{
|
||||||
apply_ctx(ctx);
|
apply_ctx(ctx);
|
||||||
|
skeletonkey_result_t pre = cff_check_userns("dirty_frag_rxrpc", ctx);
|
||||||
|
if (pre != SKELETONKEY_OK) return pre;
|
||||||
return (skeletonkey_result_t)dirtyfrag_rxrpc_detect();
|
return (skeletonkey_result_t)dirtyfrag_rxrpc_detect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ extern const struct skeletonkey_module nft_payload_module;
|
|||||||
extern const struct skeletonkey_module stackrot_module;
|
extern const struct skeletonkey_module stackrot_module;
|
||||||
extern const struct skeletonkey_module sequoia_module;
|
extern const struct skeletonkey_module sequoia_module;
|
||||||
extern const struct skeletonkey_module vmwgfx_module;
|
extern const struct skeletonkey_module vmwgfx_module;
|
||||||
|
extern const struct skeletonkey_module copy_fail_gcm_module;
|
||||||
|
extern const struct skeletonkey_module dirty_frag_esp_module;
|
||||||
|
extern const struct skeletonkey_module dirty_frag_esp6_module;
|
||||||
|
extern const struct skeletonkey_module dirty_frag_rxrpc_module;
|
||||||
|
|
||||||
static int g_pass = 0;
|
static int g_pass = 0;
|
||||||
static int g_fail = 0;
|
static int g_fail = 0;
|
||||||
@@ -337,6 +341,30 @@ static void run_all(void)
|
|||||||
|
|
||||||
run_one("stackrot: kernel 4.4 predates 6.1 → OK",
|
run_one("stackrot: kernel 4.4 predates 6.1 → OK",
|
||||||
&stackrot_module, &h_kernel_4_4, SKELETONKEY_OK);
|
&stackrot_module, &h_kernel_4_4, SKELETONKEY_OK);
|
||||||
|
|
||||||
|
/* ── copy_fail_family bridge userns gate ─────────────────────
|
||||||
|
* The 4 dirty_frag siblings + the GCM variant all reach the
|
||||||
|
* bug via XFRM-ESP / AF_RXRPC paths gated on unprivileged
|
||||||
|
* user-namespace creation. Bridge-layer precondition fires
|
||||||
|
* before delegating to the inner DIRTYFAIL detect. copy_fail
|
||||||
|
* itself uses AF_ALG (no userns needed) and bypasses the
|
||||||
|
* gate — its detect would proceed to the inner active probe. */
|
||||||
|
|
||||||
|
run_one("copy_fail_gcm: userns_allowed=false → PRECOND_FAIL",
|
||||||
|
©_fail_gcm_module, &h_kernel_5_14_no_userns,
|
||||||
|
SKELETONKEY_PRECOND_FAIL);
|
||||||
|
|
||||||
|
run_one("dirty_frag_esp: userns_allowed=false → PRECOND_FAIL",
|
||||||
|
&dirty_frag_esp_module, &h_kernel_5_14_no_userns,
|
||||||
|
SKELETONKEY_PRECOND_FAIL);
|
||||||
|
|
||||||
|
run_one("dirty_frag_esp6: userns_allowed=false → PRECOND_FAIL",
|
||||||
|
&dirty_frag_esp6_module, &h_kernel_5_14_no_userns,
|
||||||
|
SKELETONKEY_PRECOND_FAIL);
|
||||||
|
|
||||||
|
run_one("dirty_frag_rxrpc: userns_allowed=false → PRECOND_FAIL",
|
||||||
|
&dirty_frag_rxrpc_module, &h_kernel_5_14_no_userns,
|
||||||
|
SKELETONKEY_PRECOND_FAIL);
|
||||||
#else
|
#else
|
||||||
fprintf(stderr, "[i] non-Linux platform: detect() bodies are stubbed; "
|
fprintf(stderr, "[i] non-Linux platform: detect() bodies are stubbed; "
|
||||||
"tests skipped (would tautologically pass).\n");
|
"tests skipped (would tautologically pass).\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user