xfs: fix up xfs_swap_extent_forks inline extent handling
commit
4dfce57db6354603641132fac3c887614e3ebe81 upstream.
There have been several reports over the years of NULL pointer
dereferences in xfs_trans_log_inode during xfs_fsr processes,
when the process is doing an fput and tearing down extents
on the temporary inode, something like:
BUG: unable to handle kernel NULL pointer dereference at
0000000000000018
PID: 29439 TASK:
ffff880550584fa0 CPU: 6 COMMAND: "xfs_fsr"
[exception RIP: xfs_trans_log_inode+0x10]
#9 [
ffff8800a57bbbe0] xfs_bunmapi at
ffffffffa037398e [xfs]
#10 [
ffff8800a57bbce8] xfs_itruncate_extents at
ffffffffa0391b29 [xfs]
#11 [
ffff8800a57bbd88] xfs_inactive_truncate at
ffffffffa0391d0c [xfs]
#12 [
ffff8800a57bbdb8] xfs_inactive at
ffffffffa0392508 [xfs]
#13 [
ffff8800a57bbdd8] xfs_fs_evict_inode at
ffffffffa035907e [xfs]
#14 [
ffff8800a57bbe00] evict at
ffffffff811e1b67
#15 [
ffff8800a57bbe28] iput at
ffffffff811e23a5
#16 [
ffff8800a57bbe58] dentry_kill at
ffffffff811dcfc8
#17 [
ffff8800a57bbe88] dput at
ffffffff811dd06c
#18 [
ffff8800a57bbea8] __fput at
ffffffff811c823b
#19 [
ffff8800a57bbef0] ____fput at
ffffffff811c846e
#20 [
ffff8800a57bbf00] task_work_run at
ffffffff81093b27
#21 [
ffff8800a57bbf30] do_notify_resume at
ffffffff81013b0c
#22 [
ffff8800a57bbf50] int_signal at
ffffffff8161405d
As it turns out, this is because the i_itemp pointer, along
with the d_ops pointer, has been overwritten with zeros
when we tear down the extents during truncate. When the in-core
inode fork on the temporary inode used by xfs_fsr was originally
set up during the extent swap, we mistakenly looked at di_nextents
to determine whether all extents fit inline, but this misses extents
generated by speculative preallocation; we should be using if_bytes
instead.
This mistake corrupts the in-memory inode, and code in
xfs_iext_remove_inline eventually gets bad inputs, causing
it to memmove and memset incorrect ranges; this became apparent
because the two values in ifp->if_u2.if_inline_ext[1] contained
what should have been in d_ops and i_itemp; they were memmoved due
to incorrect array indexing and then the original locations
were zeroed with memset, again due to an array overrun.
Fix this by properly using i_df.if_bytes to determine the number
of extents, not di_nextents.
Thanks to dchinner for looking at this with me and spotting the
root cause.
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>