[PATCH X] xfs: More robust inode extent count validation
Tim Gardner
tim.gardner at canonical.com
Wed Mar 10 21:18:36 UTC 2021
From: Dave Chinner <dchinner at redhat.com>
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 at gatech.edu>
Signed-off-by: Dave Chinner <dchinner at redhat.com>
Reviewed-by: Darrick J. Wong <darrick.wong at oracle.com>
[darrick: fix a typedef, add some braces and breaks to shut up compiler warnings]
Signed-off-by: Darrick J. Wong <darrick.wong at oracle.com>
(backported from commit 23fcb3340d033d9f081e21e6c12c2db7eaa541d3)
Signed-off-by: Tim Gardner <tim.gardner at canonical.com>
---
[ Conflicts:
fs/xfs/libxfs/xfs_inode_buf.c
Return false if xfs_dinode_verify_fork() is non-zero
Backport xfs_failaddr_t and __this_address ]
fs/xfs/libxfs/xfs_format.h | 3 ++
fs/xfs/libxfs/xfs_inode_buf.c | 73 ++++++++++++++++++++++-------------
fs/xfs/libxfs/xfs_types.h | 7 ++++
fs/xfs/xfs_linux.h | 7 ++++
4 files changed, 63 insertions(+), 27 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index e2536bb1c760..e632f0a59060 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -949,6 +949,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 a03b9c02d09c..d6eee3591e3f 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.c
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
@@ -290,12 +290,54 @@ xfs_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;
+}
+
static bool
xfs_dinode_verify(
struct xfs_mount *mp,
xfs_ino_t ino,
struct xfs_dinode *dip)
{
+ xfs_failaddr_t fa;
uint16_t mode;
uint16_t flags;
uint64_t di_size;
@@ -340,24 +382,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 false;
- if (di_size > XFS_DFORK_DSIZE(dip, mp))
- return false;
- if (dip->di_nextents)
- return false;
- /* fall through */
- case XFS_DINODE_FMT_EXTENTS:
- case XFS_DINODE_FMT_BTREE:
- break;
- default:
+ fa = xfs_dinode_verify_fork(dip, mp, XFS_DATA_FORK);
+ if (fa)
return false;
- }
break;
case 0:
/* Uninitialized inode ok. */
@@ -367,17 +394,9 @@ xfs_dinode_verify(
}
if (XFS_DFORK_Q(dip)) {
- switch (dip->di_aformat) {
- case XFS_DINODE_FMT_LOCAL:
- if (dip->di_anextents)
- return false;
- /* fall through */
- case XFS_DINODE_FMT_EXTENTS:
- case XFS_DINODE_FMT_BTREE:
- break;
- default:
+ fa = xfs_dinode_verify_fork(dip, mp, XFS_ATTR_FORK);
+ if (fa)
return false;
- }
} else {
/*
* If there is no fork offset, this may be a freshly-made inode
diff --git a/fs/xfs/libxfs/xfs_types.h b/fs/xfs/libxfs/xfs_types.h
index b79dc66b2ecd..60bd4ddc91d7 100644
--- a/fs/xfs/libxfs/xfs_types.h
+++ b/fs/xfs/libxfs/xfs_types.h
@@ -47,6 +47,13 @@ typedef __uint64_t xfs_filblks_t; /* number of blocks in a file */
typedef __int64_t xfs_srtblock_t; /* signed version of xfs_rtblock_t */
typedef __int64_t xfs_sfiloff_t; /* signed block number in a file */
+
+/*
+ * New verifiers will return the instruction address of the failing check.
+ * NULL means everything is ok.
+ */
+typedef void * xfs_failaddr_t;
+
/*
* Null values for the types.
*/
diff --git a/fs/xfs/xfs_linux.h b/fs/xfs/xfs_linux.h
index 201aae0b2662..71d5bc764a29 100644
--- a/fs/xfs/xfs_linux.h
+++ b/fs/xfs/xfs_linux.h
@@ -148,6 +148,13 @@ typedef __u32 xfs_nlink_t;
#define SYNCHRONIZE() barrier()
#define __return_address __builtin_return_address(0)
+/*
+ * Return the address of a label. Use barrier() so that the optimizer
+ * won't reorder code to refactor the error jumpouts into a single
+ * return, which throws off the reported address.
+ */
+#define __this_address ({ __label__ __here; __here: barrier(); &&__here; })
+
#define XFS_PROJID_DEFAULT 0
#define MAXPATHLEN 1024
--
2.17.1
More information about the kernel-team
mailing list