Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 3 May 2019 21:54:15 +0000 (UTC)
From:      Kirk McKusick <mckusick@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r347066 - in head: sbin/fsck_ffs sys/ufs/ufs
Message-ID:  <201905032154.x43LsFae008760@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: mckusick
Date: Fri May  3 21:54:14 2019
New Revision: 347066
URL: https://svnweb.freebsd.org/changeset/base/347066

Log:
  This update eliminates a kernel stack disclosure bug in UFS/FFS
  directory entries that is caused by uninitialized directory entry
  padding written to the disk. It can be viewed by any user with read
  access to that directory. Up to 3 bytes of kernel stack are disclosed
  per file entry, depending on the the amount of padding the kernel
  needs to pad out the entry to a 32 bit boundry. The offset in the
  kernel stack that is disclosed is a function of the filename size.
  Furthermore, if the user can create files in a directory, this 3
  byte window can be expanded 3 bytes at a time to a 254 byte window
  with 75% of the data in that window exposed. The additional exposure
  is done by removing the entry, creating a new entry with a 4-byte
  longer name, extracting 3 more bytes by reading the directory, and
  repeating until a 252 byte name is created.
  
  This exploit works in part because the area of the kernel stack
  that is being disclosed is in an area that typically doesn't change
  that often (perhaps a few times a second on a lightly loaded system),
  and these file creates and unlinks themselves don't overwrite the
  area of kernel stack being disclosed.
  
  It appears that this bug originated with the creation of the Fast
  File System in 4.1b-BSD (Circa 1982, more than 36 years ago!), and
  is likely present in every Unix or Unix-like system that uses
  UFS/FFS. Amazingly, nobody noticed until now.
  
  This update also adds the -z flag to fsck_ffs to have it scrub
  the leaked information in the name padding of existing directories.
  It only needs to be run once on each UFS/FFS filesystem after a
  patched kernel is installed and running.
  
  Submitted by: David G. Lawrence <dg@dglawrence.com>
  Reviewed by:  kib
  MFC after:    1 week

Modified:
  head/sbin/fsck_ffs/dir.c
  head/sbin/fsck_ffs/fsck.h
  head/sbin/fsck_ffs/fsck_ffs.8
  head/sbin/fsck_ffs/globs.c
  head/sbin/fsck_ffs/main.c
  head/sys/ufs/ufs/ufs_lookup.c

Modified: head/sbin/fsck_ffs/dir.c
==============================================================================
--- head/sbin/fsck_ffs/dir.c	Fri May  3 21:48:42 2019	(r347065)
+++ head/sbin/fsck_ffs/dir.c	Fri May  3 21:54:14 2019	(r347066)
@@ -147,14 +147,23 @@ fsck_readdir(struct inodesc *idesc)
 	struct direct *dp, *ndp;
 	struct bufarea *bp;
 	long size, blksiz, fix, dploc;
+	int dc;
 
 	blksiz = idesc->id_numfrags * sblock.fs_fsize;
 	bp = getdirblk(idesc->id_blkno, blksiz);
 	if (idesc->id_loc % DIRBLKSIZ == 0 && idesc->id_filesize > 0 &&
 	    idesc->id_loc < blksiz) {
 		dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
-		if (dircheck(idesc, dp))
+		if ((dc = dircheck(idesc, dp)) > 0) {
+			if (dc == 2) {
+				/*
+				 * dircheck() cleared unused directory space.
+				 * Mark the buffer as dirty to write it out.
+				 */
+				dirty(bp);
+			}
 			goto dpok;
+		}
 		if (idesc->id_fix == IGNORE)
 			return (0);
 		fix = dofix(idesc, "DIRECTORY CORRUPTED");
@@ -181,19 +190,26 @@ dpok:
 	if ((idesc->id_loc % DIRBLKSIZ) == 0)
 		return (dp);
 	ndp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
-	if (idesc->id_loc < blksiz && idesc->id_filesize > 0 &&
-	    dircheck(idesc, ndp) == 0) {
-		size = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ);
-		idesc->id_loc += size;
-		idesc->id_filesize -= size;
-		if (idesc->id_fix == IGNORE)
-			return (0);
-		fix = dofix(idesc, "DIRECTORY CORRUPTED");
-		bp = getdirblk(idesc->id_blkno, blksiz);
-		dp = (struct direct *)(bp->b_un.b_buf + dploc);
-		dp->d_reclen += size;
-		if (fix)
+	if (idesc->id_loc < blksiz && idesc->id_filesize > 0) {
+		if ((dc = dircheck(idesc, ndp)) == 0) {
+			size = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ);
+			idesc->id_loc += size;
+			idesc->id_filesize -= size;
+			if (idesc->id_fix == IGNORE)
+				return (0);
+			fix = dofix(idesc, "DIRECTORY CORRUPTED");
+			bp = getdirblk(idesc->id_blkno, blksiz);
+			dp = (struct direct *)(bp->b_un.b_buf + dploc);
+			dp->d_reclen += size;
+			if (fix)
+				dirty(bp);
+		} else if (dc == 2) {
+			/*
+			 * dircheck() cleared unused directory space.
+			 * Mark the buffer as dirty to write it out.
+			 */
 			dirty(bp);
+		}
 	}
 	return (dp);
 }
@@ -201,6 +217,11 @@ dpok:
 /*
  * Verify that a directory entry is valid.
  * This is a superset of the checks made in the kernel.
+ * Also optionally clears padding and unused directory space.
+ *
+ * Returns 0 if the entry is bad, 1 if the entry is good and no changes
+ * were made, and 2 if the entry is good but modified to clear out padding
+ * and unused space and needs to be written back to disk.
  */
 static int
 dircheck(struct inodesc *idesc, struct direct *dp)
@@ -209,15 +230,39 @@ dircheck(struct inodesc *idesc, struct direct *dp)
 	char *cp;
 	u_char type;
 	u_int8_t namlen;
-	int spaceleft;
+	int spaceleft, modified, unused;
 
+	modified = 0;
 	spaceleft = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ);
 	if (dp->d_reclen == 0 ||
 	    dp->d_reclen > spaceleft ||
-	    (dp->d_reclen & 0x3) != 0)
+	    (dp->d_reclen & (DIR_ROUNDUP - 1)) != 0)
 		goto bad;
-	if (dp->d_ino == 0)
-		return (1);
+	if (dp->d_ino == 0) {
+		/*
+		 * Special case of an unused directory entry. Normally
+		 * the kernel would coalesce unused space with the previous
+		 * entry by extending its d_reclen, but there are situations
+		 * (e.g. fsck) where that doesn't occur.
+		 * If we're clearing out directory cruft (-z flag), then make
+		 * sure this entry gets fully cleared as well.
+		 */
+		if (zflag && fswritefd >= 0) {
+			if (dp->d_type != 0) {
+				dp->d_type = 0;
+				modified = 1;
+			}
+			if (dp->d_namlen != 0) {
+				dp->d_namlen = 0;
+				modified = 1;
+			}
+			if (dp->d_name[0] != '\0') {
+				dp->d_name[0] = '\0';
+				modified = 1;
+			}
+		}
+		goto good;
+	}
 	size = DIRSIZ(0, dp);
 	namlen = dp->d_namlen;
 	type = dp->d_type;
@@ -231,7 +276,37 @@ dircheck(struct inodesc *idesc, struct direct *dp)
 			goto bad;
 	if (*cp != '\0')
 		goto bad;
+
+good:
+	if (zflag && fswritefd >= 0) {
+		/*
+		 * Clear unused directory entry space, including the d_name
+		 * padding.
+		 */
+		/* First figure the number of pad bytes. */
+		unused = roundup2(namlen + 1, DIR_ROUNDUP) - (namlen + 1);
+
+		/* Add in the free space to the end of the record. */
+		unused += dp->d_reclen - DIRSIZ(0, dp);
+
+		/*
+		 * Now clear out the unused space, keeping track if we actually
+		 * changed anything.
+		 */
+		for (cp = &dp->d_name[namlen + 1]; unused > 0; unused--, cp++) {
+			if (*cp != '\0') {
+				*cp = '\0';
+				modified = 1;
+			}
+		}
+		
+		if (modified) {
+			return 2;
+		}
+	}
+
 	return (1);
+
 bad:
 	if (debug)
 		printf("Bad dir: ino %d reclen %d namlen %d type %d name %s\n",

Modified: head/sbin/fsck_ffs/fsck.h
==============================================================================
--- head/sbin/fsck_ffs/fsck.h	Fri May  3 21:48:42 2019	(r347065)
+++ head/sbin/fsck_ffs/fsck.h	Fri May  3 21:54:14 2019	(r347066)
@@ -315,6 +315,7 @@ extern off_t	bflag;			/* location of alternate super b
 extern int	debug;			/* output debugging info */
 extern int	Eflag;			/* delete empty data blocks */
 extern int	Zflag;			/* zero empty data blocks */
+extern int	zflag;			/* zero unused directory space */
 extern int	inoopt;			/* trim out unused inodes */
 extern char	ckclean;		/* only do work if not cleanly unmounted */
 extern int	cvtlevel;		/* convert to newer file system format */

Modified: head/sbin/fsck_ffs/fsck_ffs.8
==============================================================================
--- head/sbin/fsck_ffs/fsck_ffs.8	Fri May  3 21:48:42 2019	(r347065)
+++ head/sbin/fsck_ffs/fsck_ffs.8	Fri May  3 21:54:14 2019	(r347066)
@@ -29,7 +29,7 @@
 .\"	@(#)fsck.8	8.4 (Berkeley) 5/9/95
 .\" $FreeBSD$
 .\"
-.Dd January 13, 2018
+.Dd May 3, 2019
 .Dt FSCK_FFS 8
 .Os
 .Sh NAME
@@ -38,7 +38,7 @@
 .Nd file system consistency check and interactive repair
 .Sh SYNOPSIS
 .Nm
-.Op Fl BCdEFfnpRrSyZ
+.Op Fl BCdEFfnpRrSyZz
 .Op Fl b Ar block
 .Op Fl c Ar level
 .Op Fl m Ar mode
@@ -301,6 +301,9 @@ If both
 and
 .Fl Z
 are specified, blocks are first zeroed and then erased.
+.It Fl z
+Clear unused directory space.
+The cleared space includes deleted file names and name padding.
 .El
 .Pp
 Inconsistencies checked are as follows:

Modified: head/sbin/fsck_ffs/globs.c
==============================================================================
--- head/sbin/fsck_ffs/globs.c	Fri May  3 21:48:42 2019	(r347065)
+++ head/sbin/fsck_ffs/globs.c	Fri May  3 21:54:14 2019	(r347066)
@@ -85,6 +85,7 @@ off_t	bflag;			/* location of alternate super block */
 int	debug;			/* output debugging info */
 int	Eflag;			/* delete empty data blocks */
 int	Zflag;			/* zero empty data blocks */
+int	zflag;			/* zero unused directory space */
 int	inoopt;			/* trim out unused inodes */
 char	ckclean;		/* only do work if not cleanly unmounted */
 int	cvtlevel;		/* convert to newer file system format */

Modified: head/sbin/fsck_ffs/main.c
==============================================================================
--- head/sbin/fsck_ffs/main.c	Fri May  3 21:48:42 2019	(r347065)
+++ head/sbin/fsck_ffs/main.c	Fri May  3 21:54:14 2019	(r347066)
@@ -89,7 +89,7 @@ main(int argc, char *argv[])
 	sync();
 	skipclean = 1;
 	inoopt = 0;
-	while ((ch = getopt(argc, argv, "b:Bc:CdEfFm:npRrSyZ")) != -1) {
+	while ((ch = getopt(argc, argv, "b:Bc:CdEfFm:npRrSyZz")) != -1) {
 		switch (ch) {
 		case 'b':
 			skipclean = 0;
@@ -164,6 +164,10 @@ main(int argc, char *argv[])
 
 		case 'Z':
 			Zflag++;
+			break;
+
+		case 'z':
+			zflag++;
 			break;
 
 		default:

Modified: head/sys/ufs/ufs/ufs_lookup.c
==============================================================================
--- head/sys/ufs/ufs/ufs_lookup.c	Fri May  3 21:48:42 2019	(r347065)
+++ head/sys/ufs/ufs/ufs_lookup.c	Fri May  3 21:54:14 2019	(r347066)
@@ -825,14 +825,21 @@ ufs_makedirentry(ip, cnp, newdirp)
 	struct componentname *cnp;
 	struct direct *newdirp;
 {
+	u_int namelen;
 
-#ifdef INVARIANTS
-	if ((cnp->cn_flags & SAVENAME) == 0)
-		panic("ufs_makedirentry: missing name");
-#endif
+	namelen = (unsigned)cnp->cn_namelen;
+	KASSERT((cnp->cn_flags & SAVENAME) != 0,
+		("ufs_makedirentry: missing name"));
+	KASSERT(namelen <= UFS_MAXNAMLEN,
+		("ufs_makedirentry: name too long"));
 	newdirp->d_ino = ip->i_number;
-	newdirp->d_namlen = cnp->cn_namelen;
-	bcopy(cnp->cn_nameptr, newdirp->d_name, (unsigned)cnp->cn_namelen + 1);
+	newdirp->d_namlen = namelen;
+
+	/* Zero out after-name padding */
+	*(u_int32_t *)(&newdirp->d_name[namelen & ~(DIR_ROUNDUP - 1)]) = 0;
+
+	bcopy(cnp->cn_nameptr, newdirp->d_name, namelen);
+
 	if (ITOV(ip)->v_mount->mnt_maxsymlinklen > 0)
 		newdirp->d_type = IFTODT(ip->i_mode);
 	else {



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201905032154.x43LsFae008760>