2018-07-09 16:42:46 +00:00
|
|
|
From 7d71a671a2d900606d3a62ed5976d3b0feada3a6 Mon Sep 17 00:00:00 2001
|
|
|
|
From: Dave Chinner <dchinner@redhat.com>
|
|
|
|
Date: Tue, 5 Jun 2018 10:06:44 -0700
|
|
|
|
Subject: [PATCH] xfs: verify extent size hint is valid in inode verifier
|
|
|
|
|
|
|
|
There are rules for vald extent size hints. We enforce them when
|
|
|
|
applications set them, but fuzzers violate those rules and that
|
|
|
|
screws us over.
|
|
|
|
|
|
|
|
This results in alignment assertion failures when setting up
|
|
|
|
allocations such as this in direct IO:
|
|
|
|
|
|
|
|
XFS: Assertion failed: ap->length, file: fs/xfs/libxfs/xfs_bmap.c, line: 3432
|
|
|
|
....
|
|
|
|
Call Trace:
|
|
|
|
xfs_bmap_btalloc+0x415/0x910
|
|
|
|
xfs_bmapi_write+0x71c/0x12e0
|
|
|
|
xfs_iomap_write_direct+0x2a9/0x420
|
|
|
|
xfs_file_iomap_begin+0x4dc/0xa70
|
|
|
|
iomap_apply+0x43/0x100
|
|
|
|
iomap_file_buffered_write+0x62/0x90
|
|
|
|
xfs_file_buffered_aio_write+0xba/0x300
|
|
|
|
__vfs_write+0xd5/0x150
|
|
|
|
vfs_write+0xb6/0x180
|
|
|
|
ksys_write+0x45/0xa0
|
|
|
|
do_syscall_64+0x5a/0x180
|
|
|
|
entry_SYSCALL_64_after_hwframe+0x49/0xbe
|
|
|
|
|
|
|
|
And from xfs_db:
|
|
|
|
|
|
|
|
core.extsize = 10380288
|
|
|
|
|
|
|
|
Which is not an integer multiple of the block size, and so violates
|
|
|
|
Rule #7 for setting extent size hints. Validate extent size hint
|
|
|
|
rules in the inode verifier to catch this.
|
|
|
|
|
|
|
|
Signed-off-by: Dave Chinner <dchinner@redhat.com>
|
|
|
|
Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com>
|
|
|
|
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
|
|
|
|
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
|
|
|
|
Signed-off-by: Jeremy Cline <jcline@redhat.com>
|
|
|
|
---
|
|
|
|
fs/xfs/libxfs/xfs_inode_buf.c | 9 ++++++++-
|
|
|
|
1 file changed, 8 insertions(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c
|
|
|
|
index 1201107eabc6..1fe18555b451 100644
|
|
|
|
--- a/fs/xfs/libxfs/xfs_inode_buf.c
|
|
|
|
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
|
|
|
|
@@ -397,6 +397,7 @@ xfs_dinode_verify(
|
|
|
|
xfs_ino_t ino,
|
|
|
|
struct xfs_dinode *dip)
|
|
|
|
{
|
|
|
|
+ xfs_failaddr_t fa;
|
|
|
|
uint16_t mode;
|
|
|
|
uint16_t flags;
|
|
|
|
uint64_t flags2;
|
|
|
|
@@ -513,6 +514,12 @@ xfs_dinode_verify(
|
|
|
|
return __this_address;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ /* extent size hint validation */
|
|
|
|
+ fa = xfs_inode_validate_extsize(mp, be32_to_cpu(dip->di_extsize),
|
|
|
|
+ mode, flags);
|
|
|
|
+ if (fa)
|
|
|
|
+ return fa;
|
|
|
|
+
|
|
|
|
/* only version 3 or greater inodes are extensively verified here */
|
|
|
|
if (dip->di_version < 3)
|
|
|
|
return NULL;
|
|
|
|
@@ -521,7 +528,7 @@ xfs_dinode_verify(
|
|
|
|
|
|
|
|
/* don't allow reflink/cowextsize if we don't have reflink */
|
|
|
|
if ((flags2 & (XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE)) &&
|
|
|
|
- !xfs_sb_version_hasreflink(&mp->m_sb))
|
|
|
|
+ !xfs_sb_version_hasreflink(&mp->m_sb))
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
/* only regular files get reflink */
|
|
|
|
--
|
|
|
|
2.17.1
|
|
|
|
|
2018-07-05 14:31:36 +00:00
|
|
|
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
|
|
|
|
|