DragonFlyBSD Kernel Audit
DF-0927 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/hpfs/hpfs_lookup.c b/sys/vfs/hpfs/hpfs_lookup.c
--- a/sys/vfs/hpfs/hpfs_lookup.c
+++ b/sys/vfs/hpfs/hpfs_lookup.c
@@ -46,6 +46,8 @@
 
 int	hpfs_removedirent (struct hpfsmount *, lsn_t, char *, int, int *);
 
+#define HPFS_DIRDEPTH_MAX 64	/* B-tree depth sanity bound */
+
 /*
  * This routine traverse the b+ tree representing directory
  * looking for file named 'name'. Returns buf struct and hpfsdirent
@@ -65,21 +67,39 @@
 	struct dirblk *dp;
 	struct hpfsdirent *dep;
 	lsn_t lsn;
-	int error, res;
+	caddr_t dlimit;
+	int error, res, depth;
 
 	dprintf(("hpfs_genlookupbyname(0x%x, %s (%d)): \n", 
 		dhp->h_no, name, namelen));
 
 	lsn = ((alleaf_t *)dhp->h_fn.fn_abd)->al_lsn;
+
+	depth = 0;
 dive:
+	if (depth++ > HPFS_DIRDEPTH_MAX) {	/* defeat DE_DOWN cycles */
+		kprintf("hpfs_genlookupbyname: too deep at lsn 0x%x\n", lsn);
+		return (EINVAL);
+	}
 	error = hpfs_breaddirblk (hpmp, lsn, &bp);
 	if (error)
 		return (error);
 
 	dp = (struct dirblk *) bp->b_data;
 	dep = D_DIRENT(dp);
+	dlimit = (caddr_t)dp + D_BSIZE;		/* hard bound: bread'd size */
 
 	while(!(dep->de_flag & DE_END)) {
+		/* Dirent header + advance must lie entirely in the buffer.
+		 * de_reclen must be >= the fixed header size (this also
+		 * forbids de_reclen==0, which would otherwise spin forever),
+		 * and large enough to hold de_namelen bytes. */
+		if ((caddr_t)dep + sizeof(struct hpfsdirent) > dlimit ||
+		    dep->de_reclen < sizeof(struct hpfsdirent) ||
+		    (caddr_t)dep + dep->de_reclen > dlimit ||
+		    dep->de_namelen > dep->de_reclen - sizeof(struct hpfsdirent) + 1)
+			goto bad;
+
 		dprintf(("no: 0x%x, size: %d, name: %2d:%.*s, flag: 0x%x\n",
 			dep->de_fnode, dep->de_size, dep->de_namelen,
 			dep->de_namelen, dep->de_name, dep->de_flag));
@@ -96,6 +116,11 @@
 		dep = (hpfsdirent_t *)(((caddr_t)dep) + dep->de_reclen);
 	}
 
+	/* re-validate the terminator dirent before reading DE_DOWN/DE_DOWNLSN */
+	if ((caddr_t)dep + sizeof(struct hpfsdirent) > dlimit ||
+	    dep->de_reclen < sizeof(struct hpfsdirent))
+		goto bad;
+
 	if (dep->de_flag & DE_DOWN) {
 		lsn = DE_DOWNLSN(dep);
 		brelse(bp);
@@ -105,6 +130,11 @@
 	brelse(bp);
 
 	return (ENOENT);
+
+ bad:
+	kprintf("hpfs_genlookupbyname: corrupt dirblk at lsn 0x%x\n", lsn);
+	brelse(bp);
+	return (EINVAL);
 }
 
 int
diff --git a/sys/vfs/hpfs/hpfs_subr.c b/sys/vfs/hpfs/hpfs_subr.c
--- a/sys/vfs/hpfs/hpfs_subr.c
+++ b/sys/vfs/hpfs/hpfs_subr.c
@@ -44,6 +44,8 @@
 #include "hpfsmount.h"
 #include "hpfs_subr.h"
 
+#define HPFS_DIRDEPTH_MAX 64	/* B-tree depth sanity bound */
+
 u_long
 hpfs_checksum(
 	u_int8_t *object,
@@ -529,6 +531,7 @@
 	struct dirblk *dp;
 	struct hpfsdirent *dep;
 	lsn_t lsn, olsn;
+	caddr_t dlimit;
 	int level, error;
 
 	dprintf(("hpfs_validatetimes(0x%x): [parent: 0x%x] ",
@@ -551,6 +554,11 @@
 
 dive:
 	dprintf(("[dive 0x%x] ", lsn));
+	if (level > HPFS_DIRDEPTH_MAX) {
+		kprintf("hpfs_validateparent: too deep at lsn 0x%x\n", lsn);
+		error = EINVAL;
+		goto failed;
+	}
 	if (bp != NULL)
 		brelse(bp);
 	error = bread(dhp->h_devvp, dbtodoff(lsn), D_BSIZE, &bp);
@@ -565,6 +573,7 @@
 	}
 
 	dep = D_DIRENT(dp);
+	dlimit = (caddr_t)dp + D_BSIZE;
 
 	if (olsn) {
 		dprintf(("[restore 0x%x] ", olsn));
@@ -572,10 +581,18 @@
 		while(!(dep->de_flag & DE_END) ) {
 			if((dep->de_flag & DE_DOWN) &&
 			   (olsn == DE_DOWNLSN(dep)))
-					 break;
+				 break;
+			if ((caddr_t)dep + sizeof(struct hpfsdirent) > dlimit ||
+			    dep->de_reclen < sizeof(struct hpfsdirent) ||
+			    (caddr_t)dep + dep->de_reclen > dlimit) {
+				kprintf("hpfs_validateparent: corrupt dirblk\n");
+				error = EINVAL;
+				goto failed;
+			}
 			dep = (hpfsdirent_t *)((caddr_t)dep + dep->de_reclen);
 		}
 
+
 		if((dep->de_flag & DE_DOWN) && (olsn == DE_DOWNLSN(dep))) {
 			if (dep->de_flag & DE_END)
 				goto blockdone;
@@ -585,6 +602,13 @@
 				goto readdone;
 			}
 
+			if ((caddr_t)dep + sizeof(struct hpfsdirent) > dlimit ||
+			    dep->de_reclen < sizeof(struct hpfsdirent) ||
+			    (caddr_t)dep + dep->de_reclen > dlimit) {
+				kprintf("hpfs_validateparent: corrupt dirblk\n");
+				error = EINVAL;
+				goto failed;
+			}
 			dep = (hpfsdirent_t *)((caddr_t)dep + dep->de_reclen);
 		} else {
 			kprintf("hpfs_validatetimes: ERROR! oLSN not found\n");
@@ -607,6 +631,13 @@
 			goto readdone;
 		}
 
+		if ((caddr_t)dep + sizeof(struct hpfsdirent) > dlimit ||
+		    dep->de_reclen < sizeof(struct hpfsdirent) ||
+		    (caddr_t)dep + dep->de_reclen > dlimit) {
+			kprintf("hpfs_validateparent: corrupt dirblk\n");
+			error = EINVAL;
+			goto failed;
+		}
 		dep = (hpfsdirent_t *)((caddr_t)dep + dep->de_reclen);
 	}