Commit 23fcb334 authored by Dave Chinner's avatar Dave Chinner Committed by Darrick J. Wong

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: default avatarWen Xu <wen.xu@gatech.edu>
Signed-off-by: default avatarDave Chinner <dchinner@redhat.com>
Reviewed-by: default avatarDarrick J. Wong <darrick.wong@oracle.com>
[darrick: fix a typedef, add some braces and breaks to shut up compiler warnings]
Signed-off-by: default avatarDarrick J. Wong <darrick.wong@oracle.com>
parent e2ac8363
...@@ -962,6 +962,9 @@ typedef enum xfs_dinode_fmt { ...@@ -962,6 +962,9 @@ typedef enum xfs_dinode_fmt {
XFS_DFORK_DSIZE(dip, mp) : \ XFS_DFORK_DSIZE(dip, mp) : \
XFS_DFORK_ASIZE(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. * Return pointers to the data or attribute forks.
*/ */
......
...@@ -374,6 +374,47 @@ xfs_log_dinode_to_disk( ...@@ -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_failaddr_t
xfs_dinode_verify( xfs_dinode_verify(
struct xfs_mount *mp, struct xfs_mount *mp,
...@@ -441,24 +482,9 @@ xfs_dinode_verify( ...@@ -441,24 +482,9 @@ xfs_dinode_verify(
case S_IFREG: case S_IFREG:
case S_IFLNK: case S_IFLNK:
case S_IFDIR: case S_IFDIR:
switch (dip->di_format) { fa = xfs_dinode_verify_fork(dip, mp, XFS_DATA_FORK);
case XFS_DINODE_FMT_LOCAL: if (fa)
/* return fa;
* 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;
}
break; break;
case 0: case 0:
/* Uninitialized inode ok. */ /* Uninitialized inode ok. */
...@@ -468,17 +494,9 @@ xfs_dinode_verify( ...@@ -468,17 +494,9 @@ xfs_dinode_verify(
} }
if (XFS_DFORK_Q(dip)) { if (XFS_DFORK_Q(dip)) {
switch (dip->di_aformat) { fa = xfs_dinode_verify_fork(dip, mp, XFS_ATTR_FORK);
case XFS_DINODE_FMT_LOCAL: if (fa)
if (dip->di_anextents) return fa;
return __this_address;
/* fall through */
case XFS_DINODE_FMT_EXTENTS:
case XFS_DINODE_FMT_BTREE:
break;
default:
return __this_address;
}
} else { } else {
/* /*
* If there is no fork offset, this may be a freshly-made inode * If there is no fork offset, this may be a freshly-made inode
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment