{"uuid": "94c058ed-5994-41b2-895c-33c4a5b653f6", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-43500", "type": "seen", "source": "https://gist.github.com/artem-panchenko/0fab615ba3e9a7b471b659bef4472281", "content": "// Dirty Frag CVE reachability + diagnostic check (C port of dirtyfrag_check.py).\n//\n// Determines whether this container can reach either of the two CVEs in the\n// Dirty Frag chain and reports which kernel/sandbox layer is blocking.\n// Read-only; no exploit primitive is run.\n//\n// CVEs covered:\n//   CVE-2026-43500 - RxRPC Page-Cache Write\n//     NVD:      https://nvd.nist.gov/vuln/detail/CVE-2026-43500\n//     Write-up: https://github.com/V4bel/dirtyfrag\n//\n//   CVE-2026-43284 - xfrm-ESP Page-Cache Write\n//     NVD:      https://nvd.nist.gov/vuln/detail/CVE-2026-43284\n//     Write-up: https://github.com/V4bel/dirtyfrag\n//\n// Build:   gcc -O0 -Wall -o /tmp/dirtyfrag_check dirtyfrag_check.c\n// Run:     /tmp/dirtyfrag_check\n\n#define _GNU_SOURCE\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n#ifndef AF_RXRPC\n#define AF_RXRPC 33\n#endif\n#ifndef NETLINK_XFRM\n#define NETLINK_XFRM 6\n#endif\n#ifndef CLONE_NEWUSER\n#define CLONE_NEWUSER 0x10000000\n#endif\n#ifndef CLONE_NEWNET\n#define CLONE_NEWNET  0x40000000\n#endif\n#ifndef CLONE_NEWNS\n#define CLONE_NEWNS   0x00020000\n#endif\n#ifndef CLONE_NEWPID\n#define CLONE_NEWPID  0x20000000\n#endif\n\n// ---------- helpers ----------\n\n// Read a small file's contents into buf (NUL-terminated, trailing whitespace\n// stripped). Returns buf. On error, fills buf with \"\".\nstatic const char *cat(const char *path, char *buf, size_t buflen) {\n    int fd = open(path, O_RDONLY);\n    if (fd &lt; 0) {\n        snprintf(buf, buflen, \"\", strerror(errno));\n        return buf;\n    }\n    ssize_t n = read(fd, buf, buflen - 1);\n    close(fd);\n    if (n &lt; 0) {\n        snprintf(buf, buflen, \"\", strerror(errno));\n        return buf;\n    }\n    buf[n] = 0;\n    while (n &gt; 0 &amp;&amp; (buf[n - 1] == '\\n' || buf[n - 1] == ' ' || buf[n - 1] == '\\t')) {\n        buf[--n] = 0;\n    }\n    return buf;\n}\n\n// Pull a \"Name: value\" field out of /proc/self/status. Returns NULL if absent.\nstatic const char *proc_status_field(const char *name, char *out, size_t outlen) {\n    FILE *fp = fopen(\"/proc/self/status\", \"r\");\n    if (!fp) { out[0] = 0; return NULL; }\n    char line[512];\n    size_t namelen = strlen(name);\n    while (fgets(line, sizeof(line), fp)) {\n        if (strncmp(line, name, namelen) == 0 &amp;&amp; line[namelen] == ':') {\n            const char *v = line + namelen + 1;\n            while (*v == ' ' || *v == '\\t') v++;\n            size_t L = strlen(v);\n            while (L &amp;&amp; (v[L-1] == '\\n' || v[L-1] == ' ')) L--;\n            if (L &gt;= outlen) L = outlen - 1;\n            memcpy(out, v, L);\n            out[L] = 0;\n            fclose(fp);\n            return out;\n        }\n    }\n    fclose(fp);\n    out[0] = 0;\n    return NULL;\n}\n\n// True if a kernel module is loaded on the node.\n// Checks /sys/module// first, then /proc/modules with exact-prefix match.\nstatic bool module_loaded(const char *name) {\n    char path[256];\n    snprintf(path, sizeof(path), \"/sys/module/%s\", name);\n    struct stat st;\n    if (stat(path, &amp;st) == 0) return true;\n\n    FILE *fp = fopen(\"/proc/modules\", \"r\");\n    if (!fp) return false;\n    char line[512];\n    size_t namelen = strlen(name);\n    bool found = false;\n    while (fgets(line, sizeof(line), fp)) {\n        if (strncmp(line, name, namelen) == 0 &amp;&amp; line[namelen] == ' ') {\n            found = true;\n            break;\n        }\n    }\n    fclose(fp);\n    return found;\n}\n\n// Fork a child, attempt unshare(flags), report back to the parent.\n// Returns NULL on success, otherwise strerror() of the errno from unshare().\nstatic const char *try_unshare(int flags) {\n    pid_t pid = fork();\n    if (pid &lt; 0) return strerror(errno);\n    if (pid == 0) {\n        int rc = unshare(flags);\n        _exit(rc == 0 ? 0 : (errno &amp; 0xff));\n    }\n    int st;\n    if (waitpid(pid, &amp;st, 0) &lt; 0) return strerror(errno);\n    if (!WIFEXITED(st)) return \"child died abnormally\";\n    int code = WEXITSTATUS(st);\n    return code == 0 ? NULL : strerror(code);\n}\n\nstatic void section(const char *title) {\n    putchar('\\n');\n    for (int i = 0; i &lt; 72; i++) putchar('=');\n    printf(\"\\n%s\\n\", title);\n    for (int i = 0; i &lt; 72; i++) putchar('=');\n    putchar('\\n');\n}\n\n// Tiny convenience: returns \"true\" / \"false\" string for printing.\nstatic const char *tf(bool v) { return v ? \"true\" : \"false\"; }\n\n// ---------- CVE-2026-43500 RxRPC check ----------\n\ntypedef struct {\n    const char *verdict;   // \"EXPLOITABLE\" or \"NOT EXPLOITABLE\"\n    const char *cve;       // \"CVE-2026-43500 RxRPC\"\n    char        why[256];  // one-line reason\n} result_t;\n\nstatic result_t check_rxrpc(void) {\n    section(\"CVE-2026-43500 - RxRPC Page-Cache Write\");\n    puts(\"  Family:    Dirty Frag chain\");\n    puts(\"  NVD:       https://nvd.nist.gov/vuln/detail/CVE-2026-43500\");\n    puts(\"  Write-up:  https://github.com/V4bel/dirtyfrag\");\n    puts(\"  Trigger:   socket(AF_RXRPC, ...) - no caps, no namespace needed\");\n    puts(\"  Distinct:  Reaches the bug WITHOUT requiring CAP_NET_ADMIN or a\");\n    puts(\"             user namespace - so seccomp+caps alone do not stop it.\");\n    puts(\"             Mitigation must be node-level (rxrpc.ko unload/blacklist).\");\n    putchar('\\n');\n\n    bool reachable = false;\n    int s = socket(AF_RXRPC, SOCK_DGRAM, 0);\n    if (s &gt;= 0) {\n        close(s);\n        reachable = true;\n        puts(\"  Reachability: REACHABLE - exploitable from this container\");\n    } else {\n        printf(\"  Reachability: blocked (%s, errno %d)\\n\", strerror(errno), errno);\n    }\n\n    char protos[8192], initstate[64];\n    cat(\"/proc/net/protocols\", protos, sizeof(protos));\n    bool rxrpc_in_protos = strstr(protos, \"RXRPC\") != NULL;\n    bool rxrpc_mod = module_loaded(\"rxrpc\");\n    cat(\"/sys/module/rxrpc/initstate\", initstate, sizeof(initstate));\n\n    putchar('\\n');\n    puts(\"  Why (kernel module presence):\");\n    printf(\"    AF_RXRPC in /proc/net/protocols: %s\\n\", tf(rxrpc_in_protos));\n    printf(\"    rxrpc.ko loaded:                 %s\\n\", tf(rxrpc_mod));\n    printf(\"    /sys/module/rxrpc/initstate:     %s\\n\", initstate);\n\n    result_t r = { .cve = \"CVE-2026-43500 RxRPC\" };\n    if (reachable) {\n        r.verdict = \"EXPLOITABLE\";\n        snprintf(r.why, sizeof(r.why),\n                 \"AF_RXRPC socket reachable - bug primitive available\");\n    } else {\n        r.verdict = \"NOT EXPLOITABLE\";\n        snprintf(r.why, sizeof(r.why),\n                 \"rxrpc kernel module not loaded on node\");\n    }\n    return r;\n}\n\n// ---------- CVE-2026-43284 xfrm-ESP check ----------\n\nstatic result_t check_xfrm(void) {\n    section(\"CVE-2026-43284 - xfrm-ESP Page-Cache Write\");\n    puts(\"  Family:    Dirty Frag chain (patched mainline 2026-05-08)\");\n    puts(\"  NVD:       https://nvd.nist.gov/vuln/detail/CVE-2026-43284\");\n    puts(\"  Write-up:  https://github.com/V4bel/dirtyfrag\");\n    puts(\"  Trigger:   XFRM_MSG_NEWSA over NETLINK_XFRM - requires CAP_NET_ADMIN\");\n    puts(\"             PoC escape: unshare(CLONE_NEWUSER|CLONE_NEWNET) to acquire\");\n    puts(\"             CAP_NET_ADMIN inside a fresh user namespace.\");\n    puts(\"  Distinct:  Two-step attack. Standard hardening (drop ALL caps +\");\n    puts(\"             RuntimeDefault seccomp denying CLONE_NEWUSER) blocks both\");\n    puts(\"             rungs of the ladder.\");\n    putchar('\\n');\n\n    // Module presence: SA registration over NETLINK_XFRM returns EPROTONOSUPPORT\n    // if neither esp4 nor esp6 is loaded, regardless of how reachable the\n    // netlink socket is. Module autoload from inside an unprivileged userns\n    // is blocked, so userspace can't fix this.\n    bool esp4 = module_loaded(\"esp4\");\n    bool esp6 = module_loaded(\"esp6\");\n    bool esp_present = esp4 || esp6;\n\n    const char *unshare_err = try_unshare(CLONE_NEWUSER | CLONE_NEWNET);\n    bool netlink_ok = false;\n    const char *netlink_err = NULL;\n    if (unshare_err == NULL) {\n        int s = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);\n        if (s &gt;= 0) { close(s); netlink_ok = true; }\n        else        { netlink_err = strerror(errno); }\n    }\n\n    bool reachable = netlink_ok &amp;&amp; esp_present;\n\n    if (reachable) {\n        puts(\"  Reachability: REACHABLE - SA registration would succeed\");\n    } else if (!esp_present) {\n        puts(\"  Reachability: blocked - esp4.ko/esp6.ko not loaded on node\");\n        puts(\"                (NETLINK_XFRM socket may be reachable but SA\");\n        puts(\"                 registration returns EPROTONOSUPPORT)\");\n    } else if (unshare_err != NULL) {\n        printf(\"  Reachability: blocked at unshare(CLONE_NEWUSER|CLONE_NEWNET) - %s\\n\",\n               unshare_err);\n    } else {\n        printf(\"  Reachability: userns OK but NETLINK_XFRM blocked (%s)\\n\", netlink_err);\n    }\n\n    char max_un[64], capeff[64], seccomp[64], nnp[64];\n    char unprivuserns[64], lsm[256], uidmap[256];\n    cat(\"/proc/sys/user/max_user_namespaces\", max_un, sizeof(max_un));\n    cat(\"/proc/sys/kernel/unprivileged_userns_clone\", unprivuserns, sizeof(unprivuserns));\n    cat(\"/proc/self/attr/current\", lsm, sizeof(lsm));\n    cat(\"/proc/self/uid_map\", uidmap, sizeof(uidmap));\n    proc_status_field(\"CapEff\",  capeff,  sizeof(capeff));\n    proc_status_field(\"Seccomp\", seccomp, sizeof(seccomp));\n    proc_status_field(\"NoNewPrivs\", nnp, sizeof(nnp));\n\n    putchar('\\n');\n    puts(\"  Why (which layer blocks):\");\n    printf(\"    esp4.ko loaded on node:                        %s\\n\", tf(esp4));\n    printf(\"    esp6.ko loaded on node:                        %s\\n\", tf(esp6));\n    printf(\"    Seccomp:                                       %s  (2 = filter active)\\n\", seccomp);\n    printf(\"    NoNewPrivs:                                    %s\\n\", nnp);\n    printf(\"    CapEff:                                        %s  (0 = no caps)\\n\", capeff);\n    printf(\"    /proc/sys/user/max_user_namespaces:            %s\\n\", max_un);\n    printf(\"    /proc/sys/kernel/unprivileged_userns_clone:    %s\\n\", unprivuserns);\n    printf(\"    AppArmor/SELinux (/proc/self/attr/current):    %s\\n\", lsm);\n    printf(\"    uid_map (in initial userns?):                  %s\\n\", uidmap);\n\n    putchar('\\n');\n    puts(\"  Per-flag unshare (distinguishes cap check from seccomp):\");\n    struct { int flag; const char *label; } flags[] = {\n        { CLONE_NEWUSER, \"CLONE_NEWUSER\" },\n        { CLONE_NEWNET,  \"CLONE_NEWNET\"  },\n        { CLONE_NEWNS,   \"CLONE_NEWNS\"   },\n        { CLONE_NEWPID,  \"CLONE_NEWPID\"  },\n    };\n    for (size_t i = 0; i &lt; sizeof(flags)/sizeof(flags[0]); i++) {\n        const char *e = try_unshare(flags[i].flag);\n        printf(\"    unshare(%-14s): %s\\n\", flags[i].label, e ? e : \"OK\");\n    }\n\n    result_t r = { .cve = \"CVE-2026-43284 xfrm-ESP\" };\n    if (reachable) {\n        r.verdict = \"EXPLOITABLE\";\n        snprintf(r.why, sizeof(r.why),\n                 \"userns + NETLINK_XFRM reachable AND esp module loaded\");\n    } else if (!esp_present) {\n        r.verdict = \"NOT EXPLOITABLE\";\n        snprintf(r.why, sizeof(r.why),\n                 \"neither esp4 nor esp6 module loaded on node \"\n                 \"(no ESP handler registered)\");\n    } else if (unshare_err == NULL &amp;&amp; netlink_err != NULL) {\n        r.verdict = \"NOT EXPLOITABLE\";\n        snprintf(r.why, sizeof(r.why),\n                 \"userns ok but NETLINK_XFRM socket blocked\");\n    } else {\n        r.verdict = \"NOT EXPLOITABLE\";\n        long max_un_n = strtol(max_un, NULL, 10);\n        if (max_un_n == 0) {\n            snprintf(r.why, sizeof(r.why),\n                     \"kernel sysctl forbids user namespace creation\");\n        } else {\n            snprintf(r.why, sizeof(r.why),\n                     \"seccomp denies user namespace creation, caps dropped\");\n        }\n    }\n    return r;\n}\n\n// ---------- main ----------\n\nint main(void) {\n    puts(\"Dirty Frag CVE reachability + diagnostic check\");\n    puts(\"Run from inside the container under test. Read-only, non-destructive.\");\n\n    result_t rxrpc = check_rxrpc();\n    result_t xfrm  = check_xfrm();\n\n    section(\"Summary\");\n    printf(\"  %s - %s - %s\\n\", rxrpc.verdict, rxrpc.cve, rxrpc.why);\n    printf(\"  %s - %s - %s\\n\", xfrm.verdict,  xfrm.cve,  xfrm.why);\n\n    putchar('\\n');\n    puts(\"------ copy-paste summary ------\");\n    printf(\"%s - %s - %s\\n\", rxrpc.verdict, rxrpc.cve, rxrpc.why);\n    printf(\"%s - %s - %s\\n\", xfrm.verdict,  xfrm.cve,  xfrm.why);\n    puts(\"--------------------------------\");\n\n    return 0;\n}", "creation_timestamp": "2026-05-09T17:43:07.000000Z"}