diff mbox series

[v2] btrfs: new test for logical inode resolution panic

Message ID 4d2045a13be9bb2931c4755aa4b558c60f698f78.1671481303.git.boris@bur.io (mailing list archive)
State New, archived
Headers show
Series [v2] btrfs: new test for logical inode resolution panic | expand

Commit Message

Boris Burkov Dec. 19, 2022, 8:25 p.m. UTC
If we create a file that has an inline extent followed by a prealloc
extent, then attempt to use the logical to inode ioctl on the prealloc
extent, but in the overwritten range, backref resolution will process
the inline extent. Depending on the leaf eb layout, this can panic.
Add a new test for this condition. In the long run, we can add spew when
we read out-of-bounds fields of inline extent items and simplify this
test to look for dmesg warnings rather than trying to force a fairly
fragile panic (dependent on non-standardized details of leaf layout).

The test causes a kernel panic unless:
btrfs: fix logical_ino ioctl panic
is applied to the kernel.

Signed-off-by: Boris Burkov <boris@bur.io>
---
Changes for V2:
- move to btrfs/299
- change to 64k extent buffers
- improve comments
- cut down on unneeded fsyncs
- various cleanups to requires/includes

 tests/btrfs/299     | 103 ++++++++++++++++++++++++++++++++++++++++++++
 tests/btrfs/299.out |   2 +
 2 files changed, 105 insertions(+)
 create mode 100755 tests/btrfs/299
 create mode 100644 tests/btrfs/299.out

Comments

Filipe Manana Dec. 20, 2022, 11:33 a.m. UTC | #1
On Mon, Dec 19, 2022 at 8:28 PM Boris Burkov <boris@bur.io> wrote:
>
> If we create a file that has an inline extent followed by a prealloc
> extent, then attempt to use the logical to inode ioctl on the prealloc
> extent, but in the overwritten range, backref resolution will process
> the inline extent. Depending on the leaf eb layout, this can panic.
> Add a new test for this condition. In the long run, we can add spew when
> we read out-of-bounds fields of inline extent items and simplify this
> test to look for dmesg warnings rather than trying to force a fairly
> fragile panic (dependent on non-standardized details of leaf layout).
>
> The test causes a kernel panic unless:
> btrfs: fix logical_ino ioctl panic
> is applied to the kernel.
>
> Signed-off-by: Boris Burkov <boris@bur.io>

Reviewed-by: Filipe Manana <fdmanana@suse.com>

Looks good, thanks.

> ---
> Changes for V2:
> - move to btrfs/299
> - change to 64k extent buffers
> - improve comments
> - cut down on unneeded fsyncs
> - various cleanups to requires/includes
>
>  tests/btrfs/299     | 103 ++++++++++++++++++++++++++++++++++++++++++++
>  tests/btrfs/299.out |   2 +
>  2 files changed, 105 insertions(+)
>  create mode 100755 tests/btrfs/299
>  create mode 100644 tests/btrfs/299.out
>
> diff --git a/tests/btrfs/299 b/tests/btrfs/299
> new file mode 100755
> index 00000000..42a08317
> --- /dev/null
> +++ b/tests/btrfs/299
> @@ -0,0 +1,103 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2022 Meta Platforms, Inc.  All Rights Reserved.
> +#
> +# FS QA Test 299
> +#
> +# Given a file with extents:
> +# [0 : 4096) (inline)
> +# [4096 : N] (prealloc)
> +# if a user uses the ioctl BTRFS_IOC_LOGICAL_INO[_V2] asking for the file of the
> +# non-inline extent, it results in reading the offset field of the inline
> +# extent, which is meaningless (it is full of user data..). If we are
> +# particularly lucky, it can be past the end of the extent buffer, resulting in
> +# a crash. This test creates that circumstance and asserts that logical inode
> +# resolution is still successful.
> +#
> +. ./common/preamble
> +_begin_fstest auto quick preallocrw
> +
> +# real QA test starts here
> +# Modify as appropriate.
> +_supported_fs btrfs
> +_require_scratch
> +_require_xfs_io_command "falloc" "-k"
> +_require_btrfs_command inspect-internal dump-tree
> +_require_btrfs_command inspect-internal logical-resolve
> +_fixed_by_kernel_commit xxxxxxxx "btrfs: fix logical_ino ioctl panic"
> +
> +dump_tree() {
> +       $BTRFS_UTIL_PROG inspect-internal dump-tree $SCRATCH_DEV
> +}
> +
> +get_extent_data() {
> +       local ino=$1
> +       dump_tree $SCRATCH_DEV | grep -A4 "($ino EXTENT_DATA "
> +}
> +
> +get_prealloc_offset() {
> +       local ino=$1
> +       get_extent_data $ino | grep "disk byte" | $AWK_PROG '{print $5}'
> +}
> +
> +# This test needs to create conditions s.t. the special inode's inline extent
> +# is the first item in a leaf. Therefore, fix a leaf size and add
> +# items that are otherwise not necessary to reproduce the inline-prealloc
> +# condition to get to such a state.
> +#
> +# Roughly, the idea for getting the right item fill is to:
> +# 1. create extra inline items to cause leaf splitting.
> +# 2. put the target item in the middle so it is likely to catch the split
> +# 3. add an extra variable inline item to tweak any final adjustments
> +#
> +# It took a bit of trial and error to hit working counts of inline items, since
> +# it also had to account for dir and index items all going to the front.
> +
> +# use a 64k nodesize so that an fs with 64k pages and no subpage sector size
> +# support will correctly reproduce the problem.
> +_scratch_mkfs "--nodesize 64k" >> $seqres.full || _fail "mkfs failed"
> +_scratch_mount
> +
> +f=$SCRATCH_MNT/f
> +# write extra files before the evil file to use up the leaf and
> +# help trick leaf balancing
> +for i in {1..41}; do
> +       $XFS_IO_PROG -fc "pwrite -q 0 1024" $f.inl.$i
> +done
> +
> +# write a variable inline file to help pad the preceeding leaf
> +$XFS_IO_PROG -fc "pwrite -q 0 1" $f.inl-var.$i
> +
> +# falloc the evil file whose inline extent will start a leaf
> +$XFS_IO_PROG -fc "falloc -k 0 1m" $f.evil
> +$XFS_IO_PROG -fc fsync $f.evil
> +
> +# write extra files after the evil file to use up the leaf and
> +# help trick leaf balancing
> +for i in {1..42}; do
> +       $XFS_IO_PROG -fc "pwrite -q 0 1024" $f.inl.2.$i
> +done
> +
> +# grab the prealloc offset from dump tree while it's still the only
> +# extent data item for the inode
> +ino=$(stat -c '%i' $f.evil)
> +logical=$(get_prealloc_offset $ino)
> +
> +# do the "small write; fsync; small write" pattern which reproduces the desired
> +# item pattern of an inline extent followed by a preallocated extent. The 23
> +# size is somewhat arbitrary, but ensures that the offset field is past the eb
> +# when we are item 0 (borrowed from the actual crash this reproduces).
> +$XFS_IO_PROG -fc "pwrite -q 0 23" $f.evil
> +$XFS_IO_PROG -fc fsync $f.evil
> +$XFS_IO_PROG -fc "pwrite -q 0 23" $f.evil
> +
> +# ensure we have all the extent_data items for when we do logical to inode
> +# resolution
> +sync
> +
> +# trigger the backref walk which accesses the bad inline extent
> +btrfs inspect-internal logical-resolve $logical $SCRATCH_MNT
> +
> +echo "Silence is golden"
> +status=0
> +exit
> diff --git a/tests/btrfs/299.out b/tests/btrfs/299.out
> new file mode 100644
> index 00000000..0fcc0304
> --- /dev/null
> +++ b/tests/btrfs/299.out
> @@ -0,0 +1,2 @@
> +QA output created by 299
> +Silence is golden
> --
> 2.38.1
>
diff mbox series

Patch

diff --git a/tests/btrfs/299 b/tests/btrfs/299
new file mode 100755
index 00000000..42a08317
--- /dev/null
+++ b/tests/btrfs/299
@@ -0,0 +1,103 @@ 
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2022 Meta Platforms, Inc.  All Rights Reserved.
+#
+# FS QA Test 299
+#
+# Given a file with extents:
+# [0 : 4096) (inline)
+# [4096 : N] (prealloc)
+# if a user uses the ioctl BTRFS_IOC_LOGICAL_INO[_V2] asking for the file of the
+# non-inline extent, it results in reading the offset field of the inline
+# extent, which is meaningless (it is full of user data..). If we are
+# particularly lucky, it can be past the end of the extent buffer, resulting in
+# a crash. This test creates that circumstance and asserts that logical inode
+# resolution is still successful.
+#
+. ./common/preamble
+_begin_fstest auto quick preallocrw
+
+# real QA test starts here
+# Modify as appropriate.
+_supported_fs btrfs
+_require_scratch
+_require_xfs_io_command "falloc" "-k"
+_require_btrfs_command inspect-internal dump-tree
+_require_btrfs_command inspect-internal logical-resolve
+_fixed_by_kernel_commit xxxxxxxx "btrfs: fix logical_ino ioctl panic"
+
+dump_tree() {
+	$BTRFS_UTIL_PROG inspect-internal dump-tree $SCRATCH_DEV
+}
+
+get_extent_data() {
+	local ino=$1
+	dump_tree $SCRATCH_DEV | grep -A4 "($ino EXTENT_DATA "
+}
+
+get_prealloc_offset() {
+	local ino=$1
+	get_extent_data $ino | grep "disk byte" | $AWK_PROG '{print $5}'
+}
+
+# This test needs to create conditions s.t. the special inode's inline extent
+# is the first item in a leaf. Therefore, fix a leaf size and add
+# items that are otherwise not necessary to reproduce the inline-prealloc
+# condition to get to such a state.
+#
+# Roughly, the idea for getting the right item fill is to:
+# 1. create extra inline items to cause leaf splitting.
+# 2. put the target item in the middle so it is likely to catch the split
+# 3. add an extra variable inline item to tweak any final adjustments
+#
+# It took a bit of trial and error to hit working counts of inline items, since
+# it also had to account for dir and index items all going to the front.
+
+# use a 64k nodesize so that an fs with 64k pages and no subpage sector size
+# support will correctly reproduce the problem.
+_scratch_mkfs "--nodesize 64k" >> $seqres.full || _fail "mkfs failed"
+_scratch_mount
+
+f=$SCRATCH_MNT/f
+# write extra files before the evil file to use up the leaf and
+# help trick leaf balancing
+for i in {1..41}; do
+	$XFS_IO_PROG -fc "pwrite -q 0 1024" $f.inl.$i
+done
+
+# write a variable inline file to help pad the preceeding leaf
+$XFS_IO_PROG -fc "pwrite -q 0 1" $f.inl-var.$i
+
+# falloc the evil file whose inline extent will start a leaf
+$XFS_IO_PROG -fc "falloc -k 0 1m" $f.evil
+$XFS_IO_PROG -fc fsync $f.evil
+
+# write extra files after the evil file to use up the leaf and
+# help trick leaf balancing
+for i in {1..42}; do
+	$XFS_IO_PROG -fc "pwrite -q 0 1024" $f.inl.2.$i
+done
+
+# grab the prealloc offset from dump tree while it's still the only
+# extent data item for the inode
+ino=$(stat -c '%i' $f.evil)
+logical=$(get_prealloc_offset $ino)
+
+# do the "small write; fsync; small write" pattern which reproduces the desired
+# item pattern of an inline extent followed by a preallocated extent. The 23
+# size is somewhat arbitrary, but ensures that the offset field is past the eb
+# when we are item 0 (borrowed from the actual crash this reproduces).
+$XFS_IO_PROG -fc "pwrite -q 0 23" $f.evil
+$XFS_IO_PROG -fc fsync $f.evil
+$XFS_IO_PROG -fc "pwrite -q 0 23" $f.evil
+
+# ensure we have all the extent_data items for when we do logical to inode
+# resolution
+sync
+
+# trigger the backref walk which accesses the bad inline extent
+btrfs inspect-internal logical-resolve $logical $SCRATCH_MNT
+
+echo "Silence is golden"
+status=0
+exit
diff --git a/tests/btrfs/299.out b/tests/btrfs/299.out
new file mode 100644
index 00000000..0fcc0304
--- /dev/null
+++ b/tests/btrfs/299.out
@@ -0,0 +1,2 @@ 
+QA output created by 299
+Silence is golden