diff mbox series

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

Message ID 20200901165710.87830-3-william.gray@canonical.com
State New
Headers show
Series 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 | 47 +++++++++++++++++++++++++++++++++-
 1 file changed, 46 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 0defbd02f62d..6c08fa1785dd 100644
--- a/fs/xfs/libxfs/xfs_inode_fork.c
+++ b/fs/xfs/libxfs/xfs_inode_fork.c
@@ -165,6 +165,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;
@@ -188,8 +198,33 @@  xfs_iformat_fork(
 	if (error) {
 		return error;
 	}
-	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);
@@ -210,6 +245,16 @@  xfs_iformat_fork(
 			return -EFSCORRUPTED;
 		}
 
+		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);
+			return -EFSCORRUPTED;
+		}
+
 		error = xfs_iformat_local(ip, dip, XFS_ATTR_FORK, size);
 		break;
 	case XFS_DINODE_FMT_EXTENTS: