diff mbox series

[SRU,BIONIC,CVE-2018-10322,1/1] xfs: enhance dinode verifier

Message ID 20200901165710.87830-2-william.gray@canonical.com
State New
Headers show
Series [SRU,BIONIC,CVE-2018-10322,1/1] xfs: enhance dinode verifier | expand

Commit Message

William Breathitt Gray Sept. 1, 2020, 4:57 p.m. UTC
From: Eric Sandeen <sandeen@sandeen.net>

Add several more validations to xfs_dinode_verify:

- For LOCAL data fork formats, di_nextents must be 0.
- For LOCAL attr fork formats, di_anextents must be 0.
- For inodes with no attr fork offset,
  - format must be XFS_DINODE_FMT_EXTENTS if set at all
  - di_anextents must be 0.

Thanks to dchinner for pointing out a couple related checks I had
forgotten to add.

Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=199377
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>

CVE-2018-10322

(backported from commit b42db0860e13067fcc7cbfba3966c9e652668bbc)
[ vilhelmgray: adapted for affected code in xfs_inode_fork.c ]
Signed-off-by: William Breathitt Gray <william.gray@canonical.com>
---
 fs/xfs/libxfs/xfs_inode_fork.c | 48 +++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/fs/xfs/libxfs/xfs_inode_fork.c b/fs/xfs/libxfs/xfs_inode_fork.c
index c79a1616b79d..f1f92d1d39ff 100644
--- a/fs/xfs/libxfs/xfs_inode_fork.c
+++ b/fs/xfs/libxfs/xfs_inode_fork.c
@@ -160,6 +160,16 @@  xfs_iformat_fork(
 				return -EFSCORRUPTED;
 			}
 
+			if (dip->di_nextents) {
+				xfs_warn(ip->i_mount,
+			"corrupt inode %Lu (di_nextents must be 0 for local inode).",
+					(unsigned long long) ip->i_ino);
+				XFS_CORRUPTION_ERROR("xfs_iformat(9)",
+						     XFS_ERRLEVEL_LOW,
+						     ip->i_mount, dip);
+				return -EFSCORRUPTED;
+			}
+
 			size = (int)di_size;
 			error = xfs_iformat_local(ip, dip, XFS_DATA_FORK, size);
 			break;
@@ -197,8 +207,33 @@  xfs_iformat_fork(
 		xfs_ifork_init_cow(ip);
 	}
 
-	if (!XFS_DFORK_Q(dip))
+	if (!XFS_DFORK_Q(dip)) {
+		/*
+		 * If there is no fork offset, this may be a freshly-made inode
+		 * in a new disk cluster, in which case di_aformat is zeroed.
+		 * Otherwise, such an inode must be in EXTENTS format; this goes
+		 * for freed inodes as well.
+		 */
+		switch (dip->di_aformat) {
+		case 0:
+		case XFS_DINODE_FMT_EXTENTS:
+			break;
+		default:
+			XFS_ERROR_REPORT("xfs_iformat(10)", XFS_ERRLEVEL_LOW, ip->i_mount);
+			return -EFSCORRUPTED;
+		}
+		if (dip->di_anextents) {
+			xfs_warn(ip->i_mount,
+		"corrupt inode %Lu (di_anextents must be 0 for freshly-made inode).",
+				(unsigned long long) ip->i_ino);
+			XFS_CORRUPTION_ERROR("xfs_iformat(11)",
+					     XFS_ERRLEVEL_LOW,
+					     ip->i_mount, dip);
+			return -EFSCORRUPTED;
+		}
+
 		return 0;
+	}
 
 	ASSERT(ip->i_afp == NULL);
 	ip->i_afp = kmem_zone_zalloc(xfs_ifork_zone, KM_SLEEP | KM_NOFS);
@@ -220,6 +255,17 @@  xfs_iformat_fork(
 			break;
 		}
 
+		if (dip->di_anextents) {
+			xfs_warn(ip->i_mount,
+				"corrupt inode %Lu (di_anextents must be 0 for local inode).",
+				(unsigned long long) ip->i_ino);
+			XFS_CORRUPTION_ERROR("xfs_iformat(12)",
+					     XFS_ERRLEVEL_LOW,
+					     ip->i_mount, dip);
+			error = -EFSCORRUPTED;
+			break;
+		}
+
 		error = xfs_iformat_local(ip, dip, XFS_ATTR_FORK, size);
 		break;
 	case XFS_DINODE_FMT_EXTENTS: