DragonFlyBSD Kernel Audit
DF-0829 / fix.diff
← back to finding ↓ download raw
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;