* [PATCH 0/4] btrfs-progs: convert: fix the invalid regular extents for symbol links
@ 2024-09-03 7:28 Qu Wenruo
2024-09-03 7:28 ` [PATCH 1/4] btrfs-progs: convert: fix inline extent size for symbol link Qu Wenruo
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Qu Wenruo @ 2024-09-03 7:28 UTC (permalink / raw)
To: linux-btrfs
Test case btrfs/012 fails randomly after the rework to use fsstress to
populate the fs.
It turns out that, if fsstress creates a symbol link whose target is
4095 bytes (at the max size limit), btrfs-convert will create a regular
extent instead of the expected inline one.
This regular extent for symbol link inodes will be rejected by kernel,
resulting test case failure.
The reason that btrfs-convert created such regular extent is,
btrfs-convert accidentally added one byte for the terminating NUL,
enlarge the should-be inlined file extent to be a regular one.
The first patch will fix the bug.
Then two patches to enahnce btrfs-check to detect the error (regular and
lowmem mode)
Eventually a dedicated test case for btrfs-convert, so in the future we
won't cause the problem again.
Qu Wenruo (4):
btrfs-progs: convert: fix inline extent size for symbol link
btrfs-progs: check/original: detect invalid file extent items for
symbol links
btrfs-progs: check/lowmem: detect invalid file extents for symbol
links
btrfs-progs: convert-tests: add a test case to verify large symbol
link handling
check/main.c | 7 +++
check/mode-lowmem.c | 44 +++++++++++++++++++
convert/source-ext2.c | 12 +++--
convert/source-reiserfs.c | 4 +-
kernel-shared/file-item.c | 3 ++
.../027-large-symbol-link/test.sh | 33 ++++++++++++++
6 files changed, 98 insertions(+), 5 deletions(-)
create mode 100755 tests/convert-tests/027-large-symbol-link/test.sh
--
2.46.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/4] btrfs-progs: convert: fix inline extent size for symbol link
2024-09-03 7:28 [PATCH 0/4] btrfs-progs: convert: fix the invalid regular extents for symbol links Qu Wenruo
@ 2024-09-03 7:28 ` Qu Wenruo
2024-09-03 7:29 ` [PATCH 2/4] btrfs-progs: check/original: detect invalid file extent items for symbol links Qu Wenruo
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2024-09-03 7:28 UTC (permalink / raw)
To: linux-btrfs
[BUG]
Sometimes test case btrfs/012 fails randomly, with the failure to read a
softlink:
QA output created by 012
Checking converted btrfs against the original one:
-OK
+readlink: Structure needs cleaning
Checking saved ext2 image against the original one:
OK
Furthermore, this will trigger a kernel error message:
BTRFS critical (device dm-2): regular/prealloc extent found for non-regular inode 133081
[CAUSE]
For that specific inode 133081, the tree dump looks like this:
item 127 key (133081 INODE_ITEM 0) itemoff 40984 itemsize 160
generation 1 transid 1 size 4095 nbytes 4096
block group 0 mode 120777 links 1 uid 0 gid 0 rdev 0
sequence 0 flags 0x0(none)
item 128 key (133081 INODE_REF 133080) itemoff 40972 itemsize 12
index 2 namelen 2 name: l3
item 129 key (133081 EXTENT_DATA 0) itemoff 40919 itemsize 53
generation 4 type 1 (regular)
extent data disk byte 2147483648 nr 38080512
extent data offset 37974016 nr 4096 ram 38080512
extent compression 0 (none)
Note that, the soft link inode size is 4095, just one by smaller than
the sector size, but its nbytes is 4096, larger than the upper limit of
inline extent.
Thus it results the creation of a regular extent, but for btrfs we do
not accept a soft link with a regular/preallocated extent, thus kernel
rejects such read and failed the readlink call.
The root cause is in the convert code, where for soft links we always
create a data extent with its size + 1, causing the above problem.
I guess the original code is to handle the terminating NUL, but in btrfs
we never need to bother terminating NUL for inline extents.
Thus this pitfall in btrfs-convert leads to the above invalid data
extent and fail the test case.
[FIX]
Fix the corner case by removing the terminating NUL when inserting an
inline extent.
Furthermore, to prevent such problem from happening again, do extra
UASSERT() for btrfs_insert_inline_extent() to detect such problem.
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
convert/source-ext2.c | 12 +++++++++---
convert/source-reiserfs.c | 4 ++--
kernel-shared/file-item.c | 3 +++
3 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/convert/source-ext2.c b/convert/source-ext2.c
index acba5db7ee81..dfad64a6d226 100644
--- a/convert/source-ext2.c
+++ b/convert/source-ext2.c
@@ -476,8 +476,14 @@ static int ext2_create_symlink(struct btrfs_trans_handle *trans,
int ret;
char *pathname;
u64 inode_size = btrfs_stack_inode_size(btrfs_inode);
+
if (ext2fs_inode_data_blocks2(ext2_fs, ext2_inode)) {
- btrfs_set_stack_inode_size(btrfs_inode, inode_size + 1);
+ if (inode_size >= trans->fs_info->sectorsize) {
+ error("inode size too large for symbol link, has %llu expect [1, %u]",
+ inode_size, trans->fs_info->sectorsize - 1);
+ return -EUCLEAN;
+ }
+ btrfs_set_stack_inode_size(btrfs_inode, inode_size);
ret = ext2_create_file_extents(trans, root, objectid,
btrfs_inode, ext2_fs, ext2_ino,
CONVERT_FLAG_DATACSUM |
@@ -489,8 +495,8 @@ static int ext2_create_symlink(struct btrfs_trans_handle *trans,
pathname = (char *)&(ext2_inode->i_block[0]);
BUG_ON(pathname[inode_size] != 0);
ret = btrfs_insert_inline_extent(trans, root, objectid, 0,
- pathname, inode_size + 1);
- btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size + 1);
+ pathname, inode_size);
+ btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size);
return ret;
}
diff --git a/convert/source-reiserfs.c b/convert/source-reiserfs.c
index 3edc72ed08a5..e2f07bfda188 100644
--- a/convert/source-reiserfs.c
+++ b/convert/source-reiserfs.c
@@ -538,8 +538,8 @@ static int reiserfs_copy_symlink(struct btrfs_trans_handle *trans,
len = get_ih_item_len(tp_item_head(&path));
ret = btrfs_insert_inline_extent(trans, root, objectid, 0,
- symlink, len + 1);
- btrfs_set_stack_inode_nbytes(btrfs_inode, len + 1);
+ symlink, len);
+ btrfs_set_stack_inode_nbytes(btrfs_inode, len);
fail:
pathrelse(&path);
return ret;
diff --git a/kernel-shared/file-item.c b/kernel-shared/file-item.c
index d2da56e1f523..22fa1b03bed7 100644
--- a/kernel-shared/file-item.c
+++ b/kernel-shared/file-item.c
@@ -26,6 +26,7 @@
#include "kernel-shared/extent_io.h"
#include "kernel-shared/uapi/btrfs.h"
#include "common/internal.h"
+#include "common/messages.h"
#define MAX_CSUM_ITEMS(r, size) ((((BTRFS_LEAF_DATA_SIZE(r->fs_info) - \
sizeof(struct btrfs_item) * 2) / \
@@ -97,6 +98,8 @@ int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans,
int err = 0;
int ret;
+ UASSERT(size < trans->fs_info->sectorsize);
+
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
--
2.46.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 2/4] btrfs-progs: check/original: detect invalid file extent items for symbol links
2024-09-03 7:28 [PATCH 0/4] btrfs-progs: convert: fix the invalid regular extents for symbol links Qu Wenruo
2024-09-03 7:28 ` [PATCH 1/4] btrfs-progs: convert: fix inline extent size for symbol link Qu Wenruo
@ 2024-09-03 7:29 ` Qu Wenruo
2024-09-03 7:29 ` [PATCH 3/4] btrfs-progs: check/lowmem: detect invalid file extents " Qu Wenruo
2024-09-03 7:29 ` [PATCH 4/4] btrfs-progs: convert-tests: add a test case to verify large symbol link handling Qu Wenruo
3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2024-09-03 7:29 UTC (permalink / raw)
To: linux-btrfs
[BUG]
There is a recent bug that btrfs/012 fails and kernel rejects to read a
symbol link which is backed by a regular extent.
Furthremore in that case, "btrfs check" doesn't detect such problem at
all.
[CAUSE]
For symbol links, we only allow inline file extents, and this means we
should only have a symbol link target which is smaller than 4K.
But btrfs check doesn't handle symbol link inodes any differently, thus
it doesn't check if the file extents are inlined or not, nor reporting
this problem as an error.
[FIX]
When processing data extents, if we find the owning inode is a symbol
link, and the file extent is regular/preallocated, mark the inode with
I_ERR_FILE_EXTENT_TOO_LARGE error.
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
check/main.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/check/main.c b/check/main.c
index 205bbb4a3c73..93da49b70fc4 100644
--- a/check/main.c
+++ b/check/main.c
@@ -1745,6 +1745,13 @@ static int process_file_extent(struct btrfs_root *root,
rec->errors |= I_ERR_BAD_FILE_EXTENT;
if (disk_bytenr > 0)
rec->found_size += num_bytes;
+ /*
+ * Symbol link should only have inlined extent.
+ * A regular extent means it's already too large to
+ * be inlined.
+ */
+ if (S_ISLNK(rec->imode))
+ rec->errors |= I_ERR_FILE_EXTENT_TOO_LARGE;
} else {
rec->errors |= I_ERR_BAD_FILE_EXTENT;
}
--
2.46.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 3/4] btrfs-progs: check/lowmem: detect invalid file extents for symbol links
2024-09-03 7:28 [PATCH 0/4] btrfs-progs: convert: fix the invalid regular extents for symbol links Qu Wenruo
2024-09-03 7:28 ` [PATCH 1/4] btrfs-progs: convert: fix inline extent size for symbol link Qu Wenruo
2024-09-03 7:29 ` [PATCH 2/4] btrfs-progs: check/original: detect invalid file extent items for symbol links Qu Wenruo
@ 2024-09-03 7:29 ` Qu Wenruo
2024-09-03 7:29 ` [PATCH 4/4] btrfs-progs: convert-tests: add a test case to verify large symbol link handling Qu Wenruo
3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2024-09-03 7:29 UTC (permalink / raw)
To: linux-btrfs
[BUG]
There is a recent bug that btrfs/012 fails and kernel rejects to read a
symbol link which is backed by a regular extent.
Furthremore in that case, "btrfs check --mode=lowmem" doesn't detect such
problem at all.
[CAUSE]
For symbol links, we only allow inline extents, and this means we should
only have a symbol link target which is smaller than 4K.
But lowmem mode btrfs check doesn't handle symbol link inodes any
differently, thus it doesn't check if the file extents are inlined or not,
nor reporting this problem as an error.
[FIX]
When processing data extents, if we find the owning inode is a symbol
link, and the file extent is regular/preallocated, report an error for
the bad file extent item.
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
check/mode-lowmem.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c
index a9908eaf629d..a5e49f03355d 100644
--- a/check/mode-lowmem.c
+++ b/check/mode-lowmem.c
@@ -3351,6 +3351,31 @@ out_no_release:
return err;
}
+static int grab_inode_item(struct btrfs_root *root,
+ u64 ino, struct btrfs_inode_item *ret_ii)
+{
+ struct btrfs_path path = { 0 };
+ struct btrfs_key key = {
+ .objectid = ino,
+ .type = BTRFS_INODE_ITEM_KEY,
+ .offset = 0
+ };
+ int ret;
+
+ ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+ if (ret > 0)
+ ret = -ENOENT;
+ if (ret < 0)
+ goto out;
+
+ read_extent_buffer(path.nodes[0], ret_ii,
+ btrfs_item_ptr_offset(path.nodes[0], path.slots[0]),
+ sizeof(*ret_ii));
+out:
+ btrfs_release_path(&path);
+ return ret;
+}
+
/*
* Check EXTENT_DATA item, mainly for its dbackref in extent tree
*
@@ -3371,6 +3396,7 @@ static int check_extent_data_item(struct btrfs_root *root,
struct btrfs_extent_item *ei;
struct btrfs_extent_inline_ref *iref;
struct btrfs_extent_data_ref *dref;
+ struct btrfs_inode_item inode_item;
u64 owner;
u64 disk_bytenr;
u64 disk_num_bytes;
@@ -3400,6 +3426,24 @@ static int check_extent_data_item(struct btrfs_root *root,
extent_num_bytes = btrfs_file_extent_num_bytes(eb, fi);
offset = btrfs_file_extent_offset(eb, fi);
+ /*
+ * There is a regular/preallocated data extent. Make sure the owning
+ * inode is not a symbol link.
+ * As symbol links can only have inline data extents.
+ */
+ ret = grab_inode_item(root, fi_key.objectid, &inode_item);
+ if (ret < 0) {
+ errno = -ret;
+ error("failed to grab the inode item for inode %llu: %m",
+ fi_key.objectid);
+ err |= INODE_ITEM_MISSING;
+ }
+ if (S_ISLNK(inode_item.mode)) {
+ error("symbol link at root %lld ino %llu has regular/preallocated extents",
+ root->root_key.objectid, fi_key.objectid);
+ err |= FILE_EXTENT_ERROR;
+ }
+
/* Check unaligned disk_bytenr, disk_num_bytes and num_bytes */
if (!IS_ALIGNED(disk_bytenr, gfs_info->sectorsize)) {
error(
--
2.46.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 4/4] btrfs-progs: convert-tests: add a test case to verify large symbol link handling
2024-09-03 7:28 [PATCH 0/4] btrfs-progs: convert: fix the invalid regular extents for symbol links Qu Wenruo
` (2 preceding siblings ...)
2024-09-03 7:29 ` [PATCH 3/4] btrfs-progs: check/lowmem: detect invalid file extents " Qu Wenruo
@ 2024-09-03 7:29 ` Qu Wenruo
3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2024-09-03 7:29 UTC (permalink / raw)
To: linux-btrfs
The new test case will:
- Create a symbol which contains a 4095 bytes sized target on ext4
- Convert the ext4 to btrfs
- Make sure we can still read the symbol link
For unpatched btrfs-convert, the resulted symbol link will be rejected
by kernel and fail.
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
.../027-large-symbol-link/test.sh | 33 +++++++++++++++++++
1 file changed, 33 insertions(+)
create mode 100755 tests/convert-tests/027-large-symbol-link/test.sh
diff --git a/tests/convert-tests/027-large-symbol-link/test.sh b/tests/convert-tests/027-large-symbol-link/test.sh
new file mode 100755
index 000000000000..85f7329a86d2
--- /dev/null
+++ b/tests/convert-tests/027-large-symbol-link/test.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+# Make sure btrfs-convert can handle a symbol link which is 4095 bytes large
+
+source "$TEST_TOP/common" || exit
+source "$TEST_TOP/common.convert" || exit
+
+setup_root_helper
+prepare_test_dev 1G
+check_global_prereq mkfs.ext4
+
+link_target=""
+
+# Soft link in btrfs can only have inlined data extent.
+# So here we create a symbol link whose target is 4095 bytes length.
+for ((i = 0; i < 4095; i++)); do
+ link_target+="b"
+done
+
+convert_test_prep_fs ext4 mke2fs -t ext4 -b 4096
+run_check $SUDO_HELPER ln -s "$link_target" "$TEST_MNT/symbol_link"
+run_check_umount_test_dev
+
+# For unpatched btrfs-convert, it will always append one byte to the
+# link target, causing above 4095 target to be 4096, exactly one sector,
+# resulting a regular file extent.
+convert_test_do_convert
+
+run_check_mount_test_dev
+# If the unpatched btrfs-convert created a regular extent, and the kernel is
+# newer enough, such readlink will be rejected by kernel.
+run_check $SUDO_HELPER readlink "$TEST_MNT/symbol_link"
+run_check_umount_test_dev
+
--
2.46.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2024-09-03 7:29 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-09-03 7:28 [PATCH 0/4] btrfs-progs: convert: fix the invalid regular extents for symbol links Qu Wenruo
2024-09-03 7:28 ` [PATCH 1/4] btrfs-progs: convert: fix inline extent size for symbol link Qu Wenruo
2024-09-03 7:29 ` [PATCH 2/4] btrfs-progs: check/original: detect invalid file extent items for symbol links Qu Wenruo
2024-09-03 7:29 ` [PATCH 3/4] btrfs-progs: check/lowmem: detect invalid file extents " Qu Wenruo
2024-09-03 7:29 ` [PATCH 4/4] btrfs-progs: convert-tests: add a test case to verify large symbol link handling Qu Wenruo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).