142 lines
3.8 KiB
Diff
142 lines
3.8 KiB
Diff
From 23fcb3340d033d9f081e21e6c12c2db7eaa541d3 Mon Sep 17 00:00:00 2001
|
|
From: Dave Chinner <dchinner@redhat.com>
|
|
Date: Thu, 21 Jun 2018 23:25:57 -0700
|
|
Subject: [PATCH] xfs: More robust inode extent count validation
|
|
|
|
When the inode is in extent format, it can't have more extents that
|
|
fit in the inode fork. We don't currenty check this, and so this
|
|
corruption goes unnoticed by the inode verifiers. This can lead to
|
|
crashes operating on invalid in-memory structures.
|
|
|
|
Attempts to access such a inode will now error out in the verifier
|
|
rather than allowing modification operations to proceed.
|
|
|
|
Reported-by: Wen Xu <wen.xu@gatech.edu>
|
|
Signed-off-by: Dave Chinner <dchinner@redhat.com>
|
|
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
|
|
[darrick: fix a typedef, add some braces and breaks to shut up compiler warnings]
|
|
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
|
|
---
|
|
fs/xfs/libxfs/xfs_format.h | 3 ++
|
|
fs/xfs/libxfs/xfs_inode_buf.c | 76 ++++++++++++++++++++++-------------
|
|
2 files changed, 50 insertions(+), 29 deletions(-)
|
|
|
|
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
|
|
index 1c5a8aaf2bfc..7b4a43deb83e 100644
|
|
--- a/fs/xfs/libxfs/xfs_format.h
|
|
+++ b/fs/xfs/libxfs/xfs_format.h
|
|
@@ -962,6 +962,9 @@ typedef enum xfs_dinode_fmt {
|
|
XFS_DFORK_DSIZE(dip, mp) : \
|
|
XFS_DFORK_ASIZE(dip, mp))
|
|
|
|
+#define XFS_DFORK_MAXEXT(dip, mp, w) \
|
|
+ (XFS_DFORK_SIZE(dip, mp, w) / sizeof(struct xfs_bmbt_rec))
|
|
+
|
|
/*
|
|
* Return pointers to the data or attribute forks.
|
|
*/
|
|
diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c
|
|
index d38d724534c4..33dc34655ac3 100644
|
|
--- a/fs/xfs/libxfs/xfs_inode_buf.c
|
|
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
|
|
@@ -374,6 +374,47 @@ xfs_log_dinode_to_disk(
|
|
}
|
|
}
|
|
|
|
+static xfs_failaddr_t
|
|
+xfs_dinode_verify_fork(
|
|
+ struct xfs_dinode *dip,
|
|
+ struct xfs_mount *mp,
|
|
+ int whichfork)
|
|
+{
|
|
+ uint32_t di_nextents = XFS_DFORK_NEXTENTS(dip, whichfork);
|
|
+
|
|
+ switch (XFS_DFORK_FORMAT(dip, whichfork)) {
|
|
+ case XFS_DINODE_FMT_LOCAL:
|
|
+ /*
|
|
+ * no local regular files yet
|
|
+ */
|
|
+ if (whichfork == XFS_DATA_FORK) {
|
|
+ if (S_ISREG(be16_to_cpu(dip->di_mode)))
|
|
+ return __this_address;
|
|
+ if (be64_to_cpu(dip->di_size) >
|
|
+ XFS_DFORK_SIZE(dip, mp, whichfork))
|
|
+ return __this_address;
|
|
+ }
|
|
+ if (di_nextents)
|
|
+ return __this_address;
|
|
+ break;
|
|
+ case XFS_DINODE_FMT_EXTENTS:
|
|
+ if (di_nextents > XFS_DFORK_MAXEXT(dip, mp, whichfork))
|
|
+ return __this_address;
|
|
+ break;
|
|
+ case XFS_DINODE_FMT_BTREE:
|
|
+ if (whichfork == XFS_ATTR_FORK) {
|
|
+ if (di_nextents > MAXAEXTNUM)
|
|
+ return __this_address;
|
|
+ } else if (di_nextents > MAXEXTNUM) {
|
|
+ return __this_address;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ return __this_address;
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
xfs_failaddr_t
|
|
xfs_dinode_verify(
|
|
struct xfs_mount *mp,
|
|
@@ -441,24 +482,9 @@ xfs_dinode_verify(
|
|
case S_IFREG:
|
|
case S_IFLNK:
|
|
case S_IFDIR:
|
|
- switch (dip->di_format) {
|
|
- case XFS_DINODE_FMT_LOCAL:
|
|
- /*
|
|
- * no local regular files yet
|
|
- */
|
|
- if (S_ISREG(mode))
|
|
- return __this_address;
|
|
- if (di_size > XFS_DFORK_DSIZE(dip, mp))
|
|
- return __this_address;
|
|
- if (dip->di_nextents)
|
|
- return __this_address;
|
|
- /* fall through */
|
|
- case XFS_DINODE_FMT_EXTENTS:
|
|
- case XFS_DINODE_FMT_BTREE:
|
|
- break;
|
|
- default:
|
|
- return __this_address;
|
|
- }
|
|
+ fa = xfs_dinode_verify_fork(dip, mp, XFS_DATA_FORK);
|
|
+ if (fa)
|
|
+ return fa;
|
|
break;
|
|
case 0:
|
|
/* Uninitialized inode ok. */
|
|
@@ -468,17 +494,9 @@ xfs_dinode_verify(
|
|
}
|
|
|
|
if (XFS_DFORK_Q(dip)) {
|
|
- switch (dip->di_aformat) {
|
|
- case XFS_DINODE_FMT_LOCAL:
|
|
- if (dip->di_anextents)
|
|
- return __this_address;
|
|
- /* fall through */
|
|
- case XFS_DINODE_FMT_EXTENTS:
|
|
- case XFS_DINODE_FMT_BTREE:
|
|
- break;
|
|
- default:
|
|
- return __this_address;
|
|
- }
|
|
+ fa = xfs_dinode_verify_fork(dip, mp, XFS_ATTR_FORK);
|
|
+ if (fa)
|
|
+ return fa;
|
|
} else {
|
|
/*
|
|
* If there is no fork offset, this may be a freshly-made inode
|
|
--
|
|
2.17.1
|
|
|