DF-0829 / fix.diff
diff --git a/sys/vfs/hpfs/hpfs_vnops.c b/sys/vfs/hpfs/hpfs_vnops.c index cfb3ade..bca0410 100644 --- a/sys/vfs/hpfs/hpfs_vnops.c +++ b/sys/vfs/hpfs/hpfs_vnops.c @@ -141,9 +141,29 @@ hpfs_ioctl(struct vop_ioctl_args *ap) hp->h_no, ap->a_command, ap->a_data, ap->a_fflag); switch (ap->a_command) { + /* + * DF-0829: hp->h_fn.fn_ealen is an unvalidated u16 straight off disk + * (hpfs_vfsops.c bcopy's the fnode verbatim). The EA iteration loops + * below walk (struct ea *)eap across fn_int using a running `passed` + * byte counter, advancing by sizeof(struct ea) + eap->ea_namelen + 1 + + * eap->ea_vallen per EA -- also attacker-controlled. Without bounding + * both the outer walk and the per-EA name/value span against + * sizeof(fn_int), a crafted image walks past fn_int into the adjacent + * struct hpfsnode fields (h_vp, h_devvp, ...) and, for HPFSIOCRDEA, + * copyout()s up to ~64 KB of that kernel heap to userspace. We clamp + * the outer limit to sizeof(fn_int) and stop the moment the next EA's + * header or name/value would not fit -- so every byte the loop reads + * (and every byte copyout() forwards) is provably inside fn_int. + */ +#define HPFS_EA_LIMIT(hp) (sizeof((hp)->h_fn.fn_int)) +#define HPFS_EA_FITS(hp, passed, eap, lim) \ + ((passed) + sizeof(struct ea) <= (lim) && \ + (size_t)(eap)->ea_namelen + 1 + (size_t)(eap)->ea_vallen <= \ + (lim) - (passed) - sizeof(struct ea)) case HPFSIOCGEANUM: { u_long eanum; u_long passed; + u_long ealimit = HPFS_EA_LIMIT(hp); struct ea *eap; eanum = 0; @@ -152,7 +172,9 @@ hpfs_ioctl(struct vop_ioctl_args *ap) eap = (struct ea *)&(hp->h_fn.fn_int); passed = 0; - while (passed < hp->h_fn.fn_ealen) { + while (passed < hp->h_fn.fn_ealen && + passed < ealimit && + HPFS_EA_FITS(hp, passed, eap, ealimit)) { kprintf("EAname: %s\n", EA_NAME(eap)); @@ -176,6 +198,7 @@ hpfs_ioctl(struct vop_ioctl_args *ap) case HPFSIOCGEASZ: { u_long eanum; u_long passed; + u_long ealimit = HPFS_EA_LIMIT(hp); struct ea *eap; kprintf("EA%ld\n", *(u_long *)ap->a_data); @@ -186,7 +209,9 @@ hpfs_ioctl(struct vop_ioctl_args *ap) passed = 0; error = ENOENT; - while (passed < hp->h_fn.fn_ealen) { + while (passed < hp->h_fn.fn_ealen && + passed < ealimit && + HPFS_EA_FITS(hp, passed, eap, ealimit)) { kprintf("EAname: %s\n", EA_NAME(eap)); if (eanum == *(u_long *)ap->a_data) { @@ -213,6 +238,7 @@ hpfs_ioctl(struct vop_ioctl_args *ap) case HPFSIOCRDEA: { u_long eanum; u_long passed; + u_long ealimit = HPFS_EA_LIMIT(hp); struct hpfs_rdea *rdeap; struct ea *eap; @@ -225,7 +251,9 @@ hpfs_ioctl(struct vop_ioctl_args *ap) passed = 0; error = ENOENT; - while (passed < hp->h_fn.fn_ealen) { + while (passed < hp->h_fn.fn_ealen && + passed < ealimit && + HPFS_EA_FITS(hp, passed, eap, ealimit)) { kprintf("EAname: %s\n", EA_NAME(eap)); if (eanum == rdeap->ea_no) { @@ -249,6 +277,8 @@ hpfs_ioctl(struct vop_ioctl_args *ap) break; } +#undef HPFS_EA_LIMIT +#undef HPFS_EA_FITS default: error = EOPNOTSUPP; break; |