| Age | Commit message (Collapse) | Author |
|
That function has the following problems:
- Read/write handling scattered across different locations
E.g. At the beginning there is a dedicated hole read handling, but
later short read handling is at an if() branch.
- Modifying of @pos and @length parameter for short read
Although it's completely fine to modify those parameters as they are
passed by value, but it can still be confusing to read. As normally
we would assume @pos and @length to be the original range.
But for short IO handling we modify @pos/@length, and completely
ignore @written.
- Unnecessary split for ordered extent and changeset handling
Both OE and changeset are only for writes, but they are handled in two
different if (write) {} blocks.
Refactor the function so that:
- Handling of reads and writes are concentrated in their code block
Now the handling of reads are in its own small if () branch.
Leaving the more complex writes handling to take the remaining
function, and reduce the indent level.
This also removes all unnecessary "if (write)" checks.
- Do not modify @pos and @length
Let short IO handling to manually calculate the remaining range.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
This member records how many bytes are submitted for a direct
read/write, utilized by iomap_end() callback to handle short IO cases.
However iomap_end() callback is already providing an internally tracked
@written member, which is doing the same accounting and providing the
same value as btrfs_dio_data::submitted.
There is no need to duplicate the work, just remove btrfs_dio_data::submitted.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
Pull Btrfs Fixes 2: Electric Boogaloo from David Sterba:
"This brings back the fixup worker infrastructure.
It's a mechanism to detect pages/folios that are marked dirty without
filesystem knowledge and require COW fixup. The consequence of not
doing so is silent data loss.
The first patch covers the scenarios in detail, also reflecting folio
API port and subpage block size support added in recent years. The
original fixup worker was only for pages.
The patch is relatively big, half of the code is debugging and support
code, the rest is the core design around the detection and fix.
The second patch handles an unlikely case when there's work left
during unmount"
* tag 'for-7.2-rc6-fixup-worker-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
btrfs: flush the fixup workers during close_ctree
btrfs: trigger cow fixup via dirty_folio()
|
|
git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
Pull btrfs fixes from David Sterba:
- fix leak in encoded ioctl write
- disable large folios on systems with highmem
- disable block size > page size when there's no transparent hugepage
support (under experimental config)
- reject compressed inline extents without valid LZO headers
- properly initialize cached inode mapping (if block size > page size)
* tag 'for-7.2-rc6-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
btrfs: initialize inode mapping flags for cached inodes
btrfs: disable bs > ps support if no transparent hugepage support
btrfs: fix memory leak in btrfs_do_encoded_write()
btrfs: lzo: reject inline extents without valid headers
btrfs: disable large folios for systems with highmem
|
|
struct bvec_iter::bi_bvec_done is used an offset in the current bvec,
let's rename it accordingly for better clarity. I also plan to use it
for non-bvec based iteration in the future like dma-buf, so drop the
"bvec" part.
Suggested-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
Link: https://patch.msgid.link/4e4c21858705a200bd8848ffe4080522e3eb5c1c.1786018753.git.asml.silence@gmail.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
|
Reintroducing the COW fixup worker brought back the unmount race fixed
by commit 41fd1e94066a ("btrfs: wait for fixup workers before stopping
cleaner kthread during umount") without bringing back the fix.
A fixup work item queued by the final writeback pass can still be in flight
when close_ctree() stops the cleaner kthread and frees the fs roots.
While destroy_workqueue() drains the queue, that happens after the
cleaner thread was freed, so btrfs_add_delayed_iput() called from the
fixup worker is no longer safe (not to mention that we are already in
BTRFS_FS_STATE_NO_DELAYED_IPUT when it runs).
Therefore we need to bring back explicitly flushing the fixup workqueue
as in Filipe's original fix. The first flush will catch all the fixup
writeback queued during the final sync before umount, but some of that
might hit memory allocation errors and stay fixup in the blocks/folio,
leading any subsequent writeback triggered *inside* umount (e.g. reclaim
workers shutting down) to hit it and queue again. To fix that, and the
possibility of any really long-lived pinned folios getting marked, deny
queueing new fixup during umount. That allows us to flush twice (once
before doing a real writeback pass to get the actual data, second time
to clean up any rather unlikely stragglers right before declaring
BTRFS_FS_STATE_NO_DELAYED_IPUT) and be certain nothing got re-queued.
Reproduced by injecting a one-shot 30s sleep at the head of
btrfs_writepage_fixup_worker() on a KASAN kernel, running the normal
reproducing read dio workload before unmount and then observing:
BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x35/0x50
Read of size 1 at addr ffff88810b4b08f8 by task kworker/u32:5/219
Workqueue: btrfs-fixup btrfs_writepage_fixup_worker [btrfs]
Call Trace:
_raw_spin_lock_irqsave+0x35/0x50
try_to_wake_up+0xc0/0x18c0
btrfs_writepage_fixup_worker+0x7f3/0xf20 [btrfs]
...
Fixes: 4be9c7da6860 ("btrfs: trigger cow fixup via dirty_folio()")
Assisted-by: LLM (reproduction, analysis)
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Boris Burkov <boris@bur.io>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[BUG]
When running generic/795 with 8K block size, 4K page size, the test
always fails, triggering some ASSERT()s related to folio size:
795 (241074): drop_caches: 3
assertion failed: IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize), in extent_io.c:1404 (blocksize=8192 root=262 ino=258 start=16826368 end=16830463 mapping min order=0)
------------[ cut here ]------------
kernel BUG at extent_io.c:1404!
Oops: invalid opcode: 0000 [#1] SMP
CPU: 8 UID: 0 PID: 241105 Comm: fsstress Tainted: G OE 7.2.0-rc5-custom+ #442 PREEMPT(full) f4bfb352566f3949f29c233ce6f735050a03b245
Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022
RIP: 0010:assert_folio_range.cold+0x3d/0x3f [btrfs]
Call Trace:
<TASK>
btrfs_read_folio+0x9e/0x170 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3]
prepare_one_folio.constprop.0+0x104/0x2a0 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3]
btrfs_buffered_write+0x285/0xa50 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3]
btrfs_do_write_iter+0x1aa/0x210 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3]
iter_file_splice_write+0x31a/0x540
direct_splice_actor+0x53/0x170
splice_direct_to_actor+0xe9/0x240
do_splice_direct+0x76/0xb0
vfs_copy_file_range+0x1fd/0x630
__x64_sys_copy_file_range+0xf9/0x220
do_syscall_64+0xe1/0x790
entry_SYSCALL_64_after_hwframe+0x4b/0x53
</TASK>
---[ end trace 0000000000000000 ]---
The ASSERT() itself is added by a later patch.
The crash is triggered with that new debug patch, and without this fix.
[CAUSE]
In the above case, the start 16826368 is properly 8K aligned, but the
end (16830463 + 1) is not 8K aligned.
Furthermore the mapping's minimal folio order is 0, not the expected 1
for 8K block size with 4K page size.
So this means some inodes do not have btrfs_set_inode_mapping_order()
called on it.
The missing btrfs_set_inode_mapping_order() call happens for cached
inodes, through the following events:
- btrfs_create_new_inode() called for inode X
Which properly sets minimal folio order for the VFS inode.
- btrfs_update_inode() called for inode X
Which calls btrfs_delayed_update_inode() to create a delayed_node
into root->delayed_nodes xarray.
- Drop cache/memory pressure, evicting in-memory inode X
Which evicted the inode X, but delayed_node is still in
root->delayed_nodes for future reuse.
- btrfs_iget() for inode X called again
btrfs_iget()
|- btrfs_iget_locked()
| |- iget5_locked_rcu()
| Which creates a new vfs_inode for btrfs, whose mapping still
| has the minimal order as 0.
|
|- btrfs_read_locked_inode()
|- btrfs_fill_inode()
| |- btrfs_get_delayed_node()
| Which found out the previous node, and use that delayed
| node to initialize the new inode.
|
|- filled = true;
|- if (filled) goto cache_index;
Which skips the btrfs_update_inode_mapping_flags() and
btrfs_set_inode_mapping_order() calls.
So the inode still has minimal folio order set as 0, not
the required 1.
Thus later page cache read will get a folio whose size is smaller than
block size, as the mapping has its minimal folio order set as 0 not 1,
then trigger the ASSERT().
[FIX]
Move the btrfs_update_inode_mapping_flags() and
btrfs_set_inode_mapping_order() calls under cache_index label,
so that the mapping flags and minimal folio order is always set
no matter if we have a cached inode.
Assisted-by: LLM (analysis)
Fixes: ecde48a1a6b3 ("btrfs: expose per-inode stable writes flag")
Fixes: cc38d178ff33 ("btrfs: enable large data folio support under CONFIG_BTRFS_EXPERIMENTAL")
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
Btrfs relies on mapping_set_folio_order_range() to set the minimal
folio order for all its data inodes, but that function will be no-op if
transparent hugepage is not enabled.
Guard the bs > ps support behind CONFIG_TRANSPARENT_HUGEPAGE, just like
all other filesystems.
Fixes: 98077f7f2180 ("btrfs: enable experimental bs > ps support")
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
Local fuzzing of 6.12.94 has found the following memory leak:
Unreferenced object 0xffff888018050a80 (size 64):
comm "syz.0.17", pid 10297, jiffies 4294953601
hex dump (first 32 bytes):
00 10 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
10 0a 05 18 80 88 ff ff 10 0a 05 18 80 88 ff ff ................
backtrace (crc a8a6fc29):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
__kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358
kmalloc_noprof include/linux/slab.h:878 [inline]
extent_changeset_alloc fs/btrfs/extent_io.h:207 [inline]
qgroup_reserve_data+0x1c5/0x7d0 fs/btrfs/qgroup.c:4305
btrfs_qgroup_reserve_data+0x2e/0xb0 fs/btrfs/qgroup.c:4355
btrfs_do_encoded_write+0x92e/0x1040 fs/btrfs/inode.c:9746
btrfs_encoded_write fs/btrfs/file.c:1482 [inline]
btrfs_do_write_iter+0x280/0x610 fs/btrfs/file.c:1507
btrfs_ioctl_encoded_write+0x3d6/0x490 fs/btrfs/ioctl.c:4738
btrfs_ioctl+0x6f9/0xc90 fs/btrfs/ioctl.c:-1
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:906 [inline]
__se_sys_ioctl+0xf9/0x170 fs/ioctl.c:892
do_syscall_x64 arch/x86/entry/common.c:47 [inline]
do_syscall_64+0xbe/0x1a0 arch/x86/entry/common.c:78
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Unreferenced object 0xffff888018050a00 (size 64):
comm "syz.0.17", pid 10297, jiffies 4294953601
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 ff 0f 00 00 00 00 00 00 ................
90 0a 05 18 80 88 ff ff 90 0a 05 18 80 88 ff ff ................
backtrace (crc cb5c9580):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
__kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358
kmalloc_noprof include/linux/slab.h:878 [inline]
kzalloc_noprof include/linux/slab.h:1014 [inline]
ulist_prealloc+0x9c/0x110 fs/btrfs/ulist.c:114
extent_changeset_prealloc fs/btrfs/extent_io.h:217 [inline]
__set_extent_bit+0x16b/0x1a70 fs/btrfs/extent-io-tree.c:1086
set_record_extent_bits+0x50/0x90 fs/btrfs/extent-io-tree.c:1821
qgroup_reserve_data+0x274/0x7d0 fs/btrfs/qgroup.c:4312
btrfs_qgroup_reserve_data+0x2e/0xb0 fs/btrfs/qgroup.c:4355
btrfs_do_encoded_write+0x92e/0x1040 fs/btrfs/inode.c:9746
btrfs_encoded_write fs/btrfs/file.c:1482 [inline]
btrfs_do_write_iter+0x280/0x610 fs/btrfs/file.c:1507
btrfs_ioctl_encoded_write+0x3d6/0x490 fs/btrfs/ioctl.c:4738
btrfs_ioctl+0x6f9/0xc90 fs/btrfs/ioctl.c:-1
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:906 [inline]
__se_sys_ioctl+0xf9/0x170 fs/ioctl.c:892
do_syscall_x64 arch/x86/entry/common.c:47 [inline]
do_syscall_64+0xbe/0x1a0 arch/x86/entry/common.c:78
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Fix this by freeing an extent changeset before returning from
btrfs_do_encoded_write().
Fixes: 7c0c7269f7b5 ("btrfs: add BTRFS_IOC_ENCODED_WRITE")
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[BUG]
For a crafted btrfs image, the following KASAN can be triggered when
reading an inline lzo compressed file extent:
BUG: KASAN: slab-out-of-bounds in lzo_decompress+0x57d/0x700
Read of size 4 at addr ffff888006f2e644 by task btrfs_lzo_inlin/77
Call Trace:
<TASK>
dump_stack_lvl+0x5b/0x70
print_report+0xd1/0x610
kasan_report+0xe0/0x110
__asan_report_load_n_noabort+0x13/0x20
lzo_decompress+0x57d/0x700
btrfs_decompress+0x140/0x1c0
uncompress_inline+0x147/0x1b0
btrfs_get_extent+0xb23/0x10a0
btrfs_do_readpage.constprop.0+0x538/0x1ac0
btrfs_readahead+0x32f/0x5f0
read_pages+0x16f/0x850
page_cache_ra_unbounded+0x296/0x490
do_page_cache_ra+0xd9/0x130
page_cache_sync_ra+0x3ee/0x6f0
filemap_get_pages+0x306/0x15c0
filemap_read+0x329/0xd00
btrfs_file_read_iter+0x1f8/0x2b0
vfs_read+0x4ef/0x720
ksys_read+0xf8/0x1d0
__x64_sys_read+0x71/0xb0
x64_sys_call+0x1ab0/0x1b70
do_syscall_64+0x61/0x470
entry_SYSCALL_64_after_hwframe+0x4b/0x53
</TASK>
[CAUSE]
For an inline lzo compressed file extent, there should always be one lzo
header, recording the total length of the compressed data, followed by
one segment header, recording the compressed lzo payload.
But if a crafted inline lzo compressed file extent contains only an lzo
header, without the segment header or payload, lzo_decompress() will
still try to read the segment header, causing a read beyond the item
boundary.
Furthermore if the inline lzo compressed file extent is the first item
of the leaf, it will be at the extent buffer boundary. The above
out-of-boundary read will go beyond the extent buffer boundary,
triggering the above KASAN report.
[FIX]
Validate the total length of the inlined lzo compressed file extent, to
make sure there is at least one LZO header and one segment header, and a
non-zero payload.
Fixes: a6fa6fae40ec ("btrfs: Add lzo compression support")
Assisted-by: Codex:gpt-5.5
Signed-off-by: David Lee <david.lee@trailofbits.com>
[ Rework the commit message to remove slop ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[BUG]
There is a bug report that on 32bit systems (i686), btrfs crashes when
trying to do zstd compression:
BUG: unable to handle page fault for address: fffbc000
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
CPU: 0 UID: 0 PID: 61 Comm: kworker/u8:5 Tainted: G N 7.2.0-rc3-P3 #2 PREEMPTLAZY
Hardware name: LENOVO 2007F2G/2007F2G, BIOS 79ETE7WW (2.27 ) 03/21/2011
Workqueue: btrfs-delalloc btrfs_work_helper
EIP: ZSTD_compressStream2+0x221/0x5fc
Call Trace:
ZSTD_compressStream+0xd/0x48
zstd_compress_stream+0x8/0x10
zstd_compress_bio+0x20a/0x564
btrfs_compress_bio+0x94/0xc0
compress_file_range+0x20a/0x380
btrfs_work_helper+0xc1/0x1b4
process_scheduled_works+0x15f/0x204
worker_thread+0x10c/0x178
kthread+0xe1/0xe8
ret_from_fork+0x1d/0x14c
ret_from_fork_asm+0x12/0x18
entry_INT80_32+0xf0/0xf0
CR2: 00000000fffbc000
---[ end trace 0000000000000000 ]---
[CAUSE]
Inside zstd_compress_bio(), we assume the whole page cache folio can be
mapped in one go.
However that assumption is not true on systems with CONFIG_HIGHMEM, the
pages of the large folio can be in HIGHMEM, which needs to be mapped
before access.
Meanwhile zstd_compress_bio() only map the page of a large folio where
the start filepos is, the remaining pages are not mapped, and accessing
the remaining pages will trigger the above crash.
[FIX]
Do not enable large folios when the kernel has CONFIG_HIGHMEM enabled.
This is the same handling for bs > ps support.
Link: https://github.com/kdave/btrfs-progs/issues/1146
Reported-by: Erhard Furtner <erhard_f@mailbox.org>
Fixes: 9bce95edb1b4 ("btrfs: move large data folios out of experimental features")
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
Convert btrfs iomap_ops to the new ->iomap_next() callback. The callback is
generated with the DEFINE_IOMAP_ITER_NEXT_END() macro, which wraps the
iomap_iter_next() helper to finish the previous mapping if needed and
produce the next one. No functional changes are intended.
Acked-by: David Sterba <dsterba@suse.com>
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Link: https://patch.msgid.link/20260729192737.3190206-8-joannelkoong@gmail.com
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
|
|
The only time that 'false' is passed as the 'excl' arg to the ->create
inode_operation is in lookup_open() when ->atomic_open is not provided
by the parent directory.
*all* directory inode_operations which do not have ->atomic_open
completely ignore the 'excl' arg.
Therefore we don't need the 'excl' arg. Those few ->create operations
which pay attention to the arg are only ever called with a value of
'true'.
We remove that arg and change all ->create operations to behave as those
thhe arg were 'true'.
Signed-off-by: NeilBrown <neil@brown.name>
Link: https://patch.msgid.link/178290671516.27465.15984496764174914338@noble.neil.brown.name
Reviewed-by: Jori Koolstra <jkoolstra@xs4all.nl>
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
|
|
The problem scenario:
If we have a folio mmapped shared and then somebody does a dio read with
that folio as the read destination, then it is possible that the dio
will see a dirty destination page when it starts (and thus skip
dirtying and just GUP pin it) but then while it is doing the read, btrfs
finishes writing it back and by the endio, the folio is clean. In that
case, the dio read must re-dirty the folio with aops->dirty_folio():
btrfs_check_read_bio()
|- __iomap_dio_bio_end_io() from btrfs_bio_end_io()
|- bio_check_pages_dirty()
|- bio_dirty_fn()
|- bio_release_pages(bio, true)
|- __bio_release_pages(bio, mark_dirty == true)
|- folio_lock()
|- folio_mark_dirty()
|- aops->dirty_folio()
|- folio_unlock()
A data block normally moves through writeback as follows:
TASK
folio_lock
write clean -> dirty bit + delalloc
folio_unlock
WRITEBACK
for-each-dirty-folio:
folio_lock
run_delalloc delalloc consumed -> dirty bit + OE
submission dirty bit consumed -> writeback bit + OE
folio_unlock
ENDIO
endio OE bytes accounted
OE finish writeback -> clean; destroy OE
Three critical invariants that this path maintains are:
I1. Any dirty block is covered by delalloc xor an ordered extent
I2. Any dirty block covered by an OE will be submitted into that OE
I3. Any dirty block already submitted into an OE will not be submitted
again into the same OE.
These ensure that the block will be written exactly once. It is clear
that not reserving delalloc for the re-dirty case violates I1.
This situation, even without bs < folio_size, has long required btrfs to
fixup such dirty pages during writeback with an asynchronous worker that
is allowed to do this expensive work and writeback does not proceed for
a folio while it is doing this work.
Commit 247e743cbe6e ("Btrfs: Use async helpers to deal with pages that
have been improperly dirtied") introduced the COW fixup to catch exactly
this class at writeback, way back in 2008.
Since then, there have been many advances to prevent most of the causes
of such re-dirtying and we thought we could get away with removing the
annoying cow-fixup in the hope of simplifying writeback for large folio
support.
Commit b2a9f217ad3f ("btrfs: remove the COW fixup mechanism")
Commit 4927b141877c ("btrfs: remove folio ordered flag and subpage bitmap")
Since it turns out this assumption was incorrect, as evidenced by the
report and attendant reproducers, we must reintroduce the fixup concept.
This is of course critically further complicated by bs < folio_size. In
that case, rather than just a folio dirty bit, we have a bitmap for the
dirty blocks in the folio. And the (also broken) invariant is:
I4. folio dirty IFF at least one block bitmap dirty.
The original report of a stall on a misinterpreted empty bitmap is
exactly evidence of a violation of I4.
It is exactly because of bs < folio_size we don't want to simply revert the
removal patches. The original fixup was not properly bs < folio_size
aware, which motivated removal in the first place. So we wish to build a
bs < folio_size aware fixup.
One other important detail from the old design, any normal write that
happens after a re-dirty but before a fixup is racing with the cow fixup
to do the delalloc reservation, therefore it must cancel the fixup state.
If it arrives after the reservation exists, it will be a normal dirty
overwrite. This critically informs the design in a pretty clear way.
fixup requiring re-dirty has folio granularity, while cancellation has
delalloc (block) granularity so while we only ever produce fixup in
chunks of folios, we must be able to clear it in blocks. Therefore we
must track the blocks needing fixup at block granularity.
The obvious way to do this is with a new bitmap in btrfs_folio_state,
but it is desirable to avoid that if possible. Unfortunately, I don't
think it is possible and the reason is subtle and leans on a sort of
extreme reproducer, but I think can be explained relatively succinctly.
Consider a folio whose two halves will land in different ordered extents
(can be accomplished with tricks using nodatasum) and a dio read is
running with it as the shared mmap destination.
1. The front half:
a. folio comes clean on a normal write
b. dio read completes into the folio marking it fixup.
c. a write comes for the previous folio for a range extending into
this folio, this is a cancellation of the fixup which reserves
space.
d. writeback runs on the range *not* overlapping the folio. This half
remains dirty but is now covered by an OE and is awaiting
writeback running on its range to be submitted and finish the OE.
2. The back half:
a. the folio is part of an OE that gets far enough along to clear
writeback.
b. dio read completes into the folio marking it fixup.
After this, the folio's front half is dirty in the "normal" sense, it
needs to be submitted to the OE waiting for it. It's a cancelled fixup.
Meanwhile, the second half is a true fresh fixup. So at this point if we
run writeback on this folio, we genuinely can't know what to do without
block level information. If we submit it, we submit unreserved dirty
from the back half. If we don't, we will never finish the OE waiting for
it. So it's either a corruption or a deadlock.
Thus, the full high level design picture:
- btrfs_data_dirty_folio(): For out of band non-reserving dirties,
mark still-clean blocks inside EOF dirty and set their fixup bits
(the event carries no range, so every clean block is suspect).
Already-dirty blocks are covered or pending and are left alone.
- Writeback: skip fixup blocks and enqueue work for them
- writepage_fixup(): for each fixup block do the fixup reservation in a
worker, after which the blocks can be written back normally.
- Typical reserving write paths cancel fixup state for the ranges they
cover with btrfs_folio_cancel_fixup()
Link: https://lore.kernel.org/linux-btrfs/20260721191152.101118-1-borntraeger@linux.ibm.com/
Assisted-by: LLM
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Boris Burkov <boris@bur.io>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
Pull btrfs fixes from David Sterba:
"Zoned mode:
- fix assertion and handle case of finished zone and truncated extent
- fix zone metadata write pointer on actual zone reset
- fix deadlock caused metadata writeback and transaction commit
- fix return value reuse leading to confusion about chunk
reservations
raid56 scrub:
- fix tracking of sector checksums when there are not checksums found
- fix inverted logic when submitting parity read bio
mount/remount fixes:
- fix leaking 'remount in progress' state which can break other
operations to work (qgroup rescan, autodefrag, reclaim)
- adjust using global block reserve after read-only mount when using
rescue= option
- handle missing raid stripe tree when mounted with 'ignorebadroots'
Misc:
- fix -Wmaybe-uninitialized warning in GET_CSUMS ioctl"
* tag 'for-7.2-rc5-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
btrfs: raid56: fix scrub read assembly submitting no reads
btrfs: zoned: skip fully truncated ordered extents at zone finish
btrfs: initialize 'args' to avoid compiler warning in btrfs_ioctl_get_csums()
btrfs: zoned: fix missing chunk metadata reservation
btrfs: raid56: fix an incorrect csum skip during scrub
btrfs: report missing raid stripe tree root during lookup
btrfs: skip global block reserve accounting for rescue mounts
btrfs: zoned: reset meta_write_pointer on zone reset
btrfs: zoned: fix deadlock between metadata writeback and transaction commit
btrfs: fix leaking BTRFS_FS_STATE_REMOUNTING flag
|
|
git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs fixes from Christian Brauner:
- vfs: Preserve the ACL_DONT_CACHE state in forget_cached_acl().
ACL_DONT_CACHE is meant to be a permanent opt-out from ACL caching
which FUSE relies on for servers that don't negotiate FUSE_POSIX_ACL.
The helper replaced it with ACL_NOT_CACHED, silently re-enabling the
cache, and as fuse doesn't invalidate the cache for such servers a
properly timed get_acl() returned stale ACLs. Comes with a fuse
selftest reproducing this.
- pidfs:
- Preserve PIDFD_THREAD when a thread pidfd is reopened via
open_by_handle_at(). PIDFD_THREAD shares the O_EXCL bit which
do_dentry_open() strips after the flags have been validated, so
the reopened pidfd silently became a process pidfd. Comes with a
selftest.
- Add a pidfs_dentry_open() helper so the regular pidfd allocation
path and the file handle path share the code that forces O_RDWR
and reapplies the pidfd flags that do_dentry_open() strips.
- Handle FS_IOC32_GETVERSION in the compat ioctl path.
- Make pidfs_ino_lock static.
- iomap:
- Fix the block range calculation in ifs_clear_range_dirty() so a
partial clear doesn't drop the dirty state of blocks the range
only partially covers.
- Support invalidating partial folios so a partial truncate or hole
punch with blocksize < foliosize doesn't leave stale dirty bits
behind.
- Only set did_zero when iomap_zero_iter() actually zeroed
something.
- Guard ifs_set_range_dirty() and ifs_set_range_uptodate() against
zero-length ranges where the unsigned last-block calculation
underflows and bitmap_set() writes far beyond the ifs->state
allocation.
- Don't merge ioends with different io_private values as the merge
could leak or corrupt the private data of the individual ioends.
- exec:
- Raise bprm->have_execfd only once the binfmt_misc interpreter has
actually been opened. The flag was set as soon as a matching 'O'
or 'C' entry was found. If the interpreter open failed with
ENOEXEC the exec fell through to the next binary format with
have_execfd raised but no executable staged and begin_new_exec()
NULL derefed past the point of no return.
- Fix an unsigned loop counter wrap in transfer_args_to_stack() on
nommu. An overlong argument or environment string pushes bprm->p
below PAGE_SIZE, the stop index becomes zero, and the loop never
terminates, wrapping its counter and copying garbage from in
front of the page array into the new process stack.
- Make binfmt_elf_fdpic only honour the first PT_INTERP like
binfmt_elf does. Each additional PT_INTERP overwrote the previous
interpreter, leaking the name allocation and the interpreter file
reference together with the write denial open_exec() took,
leaving the file unwritable for as long as the system runs.
- overlayfs:
- Compare the full escaped xattr prefix including the trailing dot.
An xattr like "trusted.overlay.overlayfoo" was misclassified as
an escaped overlay xattr.
- Check read access to the copy_file_range() source with the
source's mounter credentials.
- super: Thawing a filesystem whose block device was frozen with
bdev_freeze() deadlocked. Dropping the last block layer freeze
reference from under s_umount ends up in fs_bdev_thaw() which
reacquires s_umount on the same task. Pin the superblock with an
active reference instead and call bdev_thaw() without holding
s_umount.
- procfs: Return EACCES instead of success when the ptrace access check
for namespace links fails.
- afs: Use afs_dir_get_block() rather than afs_dir_find_block() for
block 0 in afs_edit_dir_remove(), matching afs_edit_dir_add().
- Push the memcg gating of ->nr_cached_objects() down into the btrfs
and shmem callbacks instead of skipping every callback during
non-root memcg reclaim. The blanket check short-circuited XFS whose
inode reclaim hook is intentionally driven from per-memcg contexts to
free memcg-charged slab.
- eventpoll: Pin files while checking reverse paths.
Since struct file became SLAB_TYPESAFE_BY_RCU a concurrent close
could free and recycle the file under the check which then took and
dropped the f_lock of whatever live file now occupies that slot.
* tag 'vfs-7.2-rc5.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (24 commits)
super: fix emergency thaw deadlock on frozen block devices
pidfs: make pidfs_ino_lock static
eventpoll: pin files while checking reverse paths
fs: push nr_cached_objects memcg gating into individual filesystems
afs: Fix afs_edit_dir_remove() to get, not find, block 0
iomap: prevent ioend merge when io_private differs
iomap: add comments for ifs_clear/set_range_dirty()
iomap: fix out-of-bounds bitmap_set() with zero-length range
iomap: fix incorrect did_zero setting in iomap_zero_iter()
iomap: support invalidating partial folios
iomap: correct the range of a partial dirty clear
fs/super: fix emergency thaw double-unlock of s_umount
pidfs: handle FS_IOC32_GETVERSION in compat ioctl
ovl: check access to copy_file_range source with src mounter creds
proc: Fix broken error paths for namespace links
pidfs: add pidfs_dentry_open() helper
selftests/pidfd: check PIDFD_THREAD survives open_by_handle_at()
pidfs: preserve thread pidfds reopened by file handle
ovl: fix trusted xattr escape prefix matching
selftests/fuse: add ACL_DONT_CACHE regression test
...
|
|
Commit 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects
in memcg slab shrink") added a check in fs/super.c that skipped every
->nr_cached_objects() hook whenever the shrinker was invoked for a
non-root memcg, on the assumption that none of them honour sc->memcg.
That assumption is wrong for XFS, whose inode-reclaim hook is
intentionally driven from per-memcg contexts to free memcg-charged
slab. Encoding a blanket "never memcg-aware" policy in fs/super.c
short-circuits that path.
Push the check down into the callbacks whose counters really are
irrelevant to per-memcg reclaim - btrfs_nr_cached_objects() and
shmem_unused_huge_count() - and drop the fs/super.c gate. Each
filesystem can now lift the restriction independently if its counter
later grows memcg awareness, without touching fs/super.c.
Introduce mem_cgroup_shrink_is_root() in <linux/memcontrol.h> so the
callbacks don't open-code "sc->memcg is NULL or root".
Fixes: 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects in memcg slab shrink")
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Shakeel Butt <shakeel.butt@linux.dev>
Signed-off-by: Usama Arif <usama.arif@linux.dev>
Link: https://patch.msgid.link/20260715103516.2410175-1-usama.arif@linux.dev
Acked-by: David Sterba <dsterba@suse.com>
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
|
|
git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
Pull btrfs fixes from David Sterba:
"I'm catching up with the fix backlog in the development branch, so
here's a number of them and will probably send one more for this or
the next rc:
- relocation fixes:
- skip attempting compression on reloc inodes
- exclude inline extents from file extent offset checks
- fix minor memory leak after error when adding reloc root
- fix root cleanup after inserting and merging
- fix clearing folio tags after writeback
- clear logging flag of extent map before splitting
- fix unsigned 32/64 type conversions when accounting dirty metadata,
leading to continually exceeding threshold
- fix regression in 32bit compat ioctl for subvolume info
- fix type of SEARCH_TREE ioctl buffer in UAPI header
- fix expression in ASSERT expression which can be unconditionally
evaluated on some compilers
- only account delalloc bytes for regular inodes"
* tag 'for-7.2-rc4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
btrfs: fix GET_SUBVOL_INFO after compat refactor
btrfs: free mapping node on duplicate reloc root insert
btrfs: fix a regression where PAGECACHE_TAG_DIRTY is never cleared
btrfs: don't propagate EXTENT_FLAG_LOGGING to split extent maps
btrfs: fix u32 to s64 type conversion in dirty_metadata_bytes accounting
btrfs: fix NULL pointer deref during assertion in btrfs_backref_free_node()
btrfs: only account delalloc bytes for regular file inodes in btrfs_getattr()
btrfs: reject inline file extents item in get_new_location()
btrfs: do not try compression for data reloc inodes
btrfs: declare btrfs_ioctl_search_args_v2::buf as __u8
btrfs: fix reloc root cleanup in merge_reloc_roots()
btrfs: fix use-after-free on reloc root after error in insert_dirty_subvol()
|
|
Commit 5387bd958180 ("btrfs: raid56: remove sector_ptr structure")
converted the bio-list membership checks from sector pointers to
physical addresses. The two conversions in rmw_assemble_write_bios()
kept their polarity (skip the sector when it is NOT in the bio list,
i.e. when there is nothing to write), but scrub_assemble_read_bios()
has the opposite polarity -- skip the sector when it IS in the bio
list, because then there is nothing to read -- and the conversion
flipped it:
- sector = sector_in_rbio(rbio, stripe, sectornr, 1);
- if (sector)
+ paddr = sector_paddr_in_rbio(rbio, stripe, sectornr, 1);
+ if (paddr == INVALID_PADDR)
continue;
Since a parity-scrub rbio's bio list only holds the empty completion
bio, the result is that scrub_assemble_read_bios() submits no reads at
all. finish_parity_scrub() then compares the parity it computes from
the (cached, correct) data stripes against whatever happens to be in
the freshly allocated, uninitialized stripe pages:
- if the garbage differs from the computed parity, the sector is
"repaired" and written back -- accidentally producing the correct
on-disk result;
- if a recycled page happens to still hold the old (correct) parity
content, the sector is deemed clean, dropped from dbitmap, and the
actually-corrupt on-disk parity is left in place. (Scrub reports
no errors either way: there is no counter for P/Q corruption by
design, so the bug here is purely the failure to read and repair.)
The second case is intermittent because it depends on page-allocator
recycling. Observed with fstests btrfs/297 (raid5, 2 devices): the
corrupted P stripe intermittently stays corrupt after a scrub --
roughly 1/10 runs on x86-64 KVM and up to 7/8 on a UML build whose
timing favors page reuse.
Since the bio-list check can never be true for a parity-scrub rbio --
raid56_parity_alloc_scrub_rbio() adds a single empty completion bio
(asserting bi_size == 0), bio_paddrs[] is only populated by
index_rbio_pages() which is never called for BTRFS_RBIO_PARITY_SCRUB,
and rbio_can_merge() refuses to merge rbios of different operations --
remove the dead check entirely and assert the invariant instead, as
suggested by Qu Wenruo.
After this fix the injected corruption is read, detected and repaired
in every run (8/8 UML, 10/10 KVM), and the new assertion never fires
across the full fstests raid group.
Fixes: 5387bd958180 ("btrfs: raid56: remove sector_ptr structure")
CC: stable@vger.kernel.org # 7.1+
Suggested-by: Qu Wenruo <quwenruo.btrfs@gmx.com>
Assisted-by: Claude:claude-fable-5
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Mykola Lysenko <nickolay.lysenko@gmail.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
A fully truncated ordered extent (truncated_len == 0) wrote no data, so its
->csum_list is empty and btrfs_finish_ordered_zoned() trips:
assertion failed: !list_empty(&ordered->csum_list), in fs/btrfs/zoned.c:2141
Since commit 66ff4d366e7e a short or cancelled direct IO write finishes the
unsubmitted ordered extent as truncated with uptodate = true instead of
setting BTRFS_ORDERED_IOERR, so it now reaches btrfs_finish_ordered_zoned()
rather than being skipped by the IOERR check in btrfs_finish_ordered_io().
generic/208 hits this on a zoned filesystem.
Return early for these, like the BTRFS_ORDERED_PREALLOC case; there is no
zone append result to record and btrfs_finish_one_ordered() skips them too.
Fixes: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write")
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[COMPILER WARNING]
With GCC 11.5.0 and KASAN enabled on ARM, the following warning is
triggered during compiling:
In file included from ./include/asm-generic/rwonce.h:26,
from ./arch/arm64/include/asm/rwonce.h:81,
from ./include/linux/compiler.h:369,
from ./include/linux/array_size.h:5,
from ./include/linux/kernel.h:16,
from fs/btrfs/ioctl.c:6:
In function ‘instrument_copy_from_user_before’,
inlined from ‘_inline_copy_from_user’ at ./include/linux/uaccess.h:184:2,
inlined from ‘copy_from_user’ at ./include/linux/uaccess.h:222:9,
inlined from ‘btrfs_ioctl_get_csums.isra’ at fs/btrfs/ioctl.c:5220:6:
./include/linux/kasan-checks.h:38:27: warning: ‘args’ may be used uninitialized [-Wmaybe-uninitialized]
38 | #define kasan_check_write __kasan_check_write
./include/linux/instrumented.h:146:9: note: in expansion of macro ‘kasan_check_write’
146 | kasan_check_write(to, n);
| ^~~~~~~~~~~~~~~~~
fs/btrfs/ioctl.c: In function ‘btrfs_ioctl_get_csums.isra’:
./include/linux/kasan-checks.h:20:6: note: by argument 1 of type ‘const volatile void *’ to ‘__kasan_check_write’ declared here
20 | bool __kasan_check_write(const volatile void *p, unsigned int size);
| ^~~~~~~~~~~~~~~~~~~
fs/btrfs/ioctl.c:5201:43: note: ‘args’ declared here
5201 | struct btrfs_ioctl_get_csums_args args;
| ^~~~
[POSSIBLE FALSE ALERTS]
This seems to be a false alert from certain GCC versions.
The @args is immediately over-written by copy_from_user(), and there is
no code touching that @args until copy_from_user() finished correctly.
[WORKAROUND]
Initialize 'args' to zero, which suppresses the warning.
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
reserve_chunk_space() stores the return value of
btrfs_zoned_activate_one_bg() in ret. The helper can return 1 after
successfully activating a block group, but ret is later used to decide
whether to reserve metadata for chunk tree updates.
As a result, successful activation skips btrfs_block_rsv_add() and leaves
trans->chunk_bytes_reserved unchanged. Use a separate variable for the
activation result so positive success does not affect the later
reservation. Keep activation failures in ret instead of returning early so
the function uses the common tail path.
Fixes: b6a98021e401 ("btrfs: zoned: activate necessary block group")
CC: stable@vger.kernel.org
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Guanghui Yang <3497809730@qq.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
Commit 7425a2894019 ("btrfs: introduce btrfs_bio_for_each_block_all()
helper") uses the new helper to replace the nested loop inside
verify_bio_data_sectors(), which simplifies the code.
However that also changed the behavior of "continue" when a block has no
data checksum.
Previously the "continue" would skip the old for() loop, which would also
increase @total_sector_nr.
Now the "continue" will skip the new btrfs_bio_for_each_block_all()
loop, which doesn't update @total_sector_nr.
This means if we hit a block that has no data checksum, we will skip all
the remaining blocks no matter if they have data checksum.
As @total_sector_nr will never be updated, and that test_bit() will
always return false.
Fix it by increasing @total_sector_nr before calling "continue".
Fixes: 7425a2894019 ("btrfs: introduce btrfs_bio_for_each_block_all() helper")
Reviewed-by: Daniel Vacek <neelx@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
When rescue=ibadroots ignores a failure to load the raid stripe tree root,
fs_info->stripe_root remains NULL. After the rescue mount proceeds, reading
file data that requires the raid stripe tree reaches
btrfs_get_raid_extent_offset().
Currently btrfs_search_slot() handles the NULL root and returns -EINVAL.
This avoids a NULL pointer dereference, but provides no diagnostic and
incorrectly describes missing filesystem metadata as an invalid argument.
Check stripe_root before allocating a path, emit a rate-limited error with
the logical address, and return -EUCLEAN.
Lookups with a valid stripe root are unchanged.
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Dongjiang Zhu <zhudongjiang@fnnas.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[BUG]
Mounting with rescue=ibadroots after corrupting the block group tree
root triggers a NULL pointer dereference:
BUG: kernel NULL pointer dereference, address: 0000000000000100
RIP: 0010:btrfs_update_global_block_rsv+0x9d/0x1c0 [btrfs]
Call Trace:
fill_dummy_bgs+0xd4/0x120 [btrfs]
open_ctree+0xc6e/0x1ca0 [btrfs]
btrfs_get_tree+0x50d/0xa40 [btrfs]
The same crash occurs with a corrupted raid stripe tree root, via
btrfs_read_block_groups() instead of fill_dummy_bgs().
[CAUSE]
With rescue=ibadroots, btrfs_read_roots() allows the mount to continue
when either root cannot be read, leaving the corresponding root pointer
NULL while its on-disk feature bit remains set.
btrfs_update_global_block_rsv() then dereferences the missing root based
on the feature bit alone.
[FIX]
Rescue mounts are fully read-only and cannot start transactions, so the
global reserve is never consumed. Under btrfs_is_full_ro(), mark the
reserve as full and return before performing the accounting.
And since we need to check if the fs is mount fully RO, export
fs_is_full_ro() as btrfs_is_full_ro(), and move it to fs.h.
Fixes: 8dbfc14fc736 ("btrfs: account block group tree when calculating global reserve size")
Fixes: 515020900d44 ("btrfs: read raid stripe tree from disk")
Suggested-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Dongjiang Zhu <zhudongjiang@fnnas.com>
[ Squash the fs_is_full_ro() export commit into this one. ]
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
btrfs_reset_unused_block_groups() resets a block group's zone and sets
alloc_offset back to 0 so the space can be reused, but it leaves
meta_write_pointer pointing at the previous end of the zone.
Once the block group is reactivated and reused for metadata, newly
allocated tree blocks live before that stale write pointer.
btrfs_check_meta_write_pointer() then sees them behind the write pointer,
so they can never be written out in sequential order: the dirty extent
buffers are stranded and pin their btree_inode folios until unmount.
Reset meta_write_pointer back to the start of the block group for
metadata and system block groups.
Fixes: 453a73c3069a ("btrfs: zoned: reclaim unused zone by zone resetting")
Reviewed-by: Naohiro Aota <naohiro.aota@wdc.com>
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
When writing out metadata extent buffers in a zoned filesystem,
btree_writepages() holds fs_info->zoned_meta_io_lock across the whole
writeback loop, including the call to btrfs_check_meta_write_pointer() ->
check_bg_is_active().
For the tree-log block group, check_bg_is_active() may fail to activate
the zone and fall back to btrfs_zone_finish_one_bg() to free an active
zone. That path waits for the running transaction to commit while still
holding zoned_meta_io_lock, but the committer needs that same lock to
write out the tree extents, so the two tasks deadlock:
Task A (kworker, metadata writeback) Task B (fsstress, transaction commit)
------------------------------------ -------------------------------------
wb_workfn() btrfs_commit_transaction(T)
btree_writepages() btrfs_write_and_wait_transaction()
btrfs_zoned_meta_io_lock() btrfs_write_marked_extents()
btrfs_check_meta_write_pointer() btree_writepages()
check_bg_is_active() [treelog_bg] btrfs_zoned_meta_io_lock()
btrfs_zone_finish_one_bg() <blocks on zoned_meta_io_lock,
btrfs_zone_finish() held by Task A>
do_zone_finish()
btrfs_inc_block_group_ro()
btrfs_wait_for_commit()
<blocks waiting for commit
of transaction T, done by
Task B>
The sibling branch in check_bg_is_active() already drops zoned_meta_io_lock
around do_zone_finish() for this exact reason. Do the same in the tree-log
branch: release the lock around btrfs_zone_finish_one_bg() and re-acquire
it afterwards. The lock only protects fs_info->active_{meta,system}_bg,
which this branch does not touch, and ctx->zoned_bg keeps a reference to
the block group across the unlock, so nothing is lost while the lock
is dropped.
This hang occasionally reproduces with fstests generic/475 on a zoned
btrfs filesystem.
Fixes: 13bb483d32ab ("btrfs: zoned: activate metadata block group on write time")
Reviewed-by: Naohiro Aota <naohiro.aota@wdc.com>
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[BUG]
The following script can lead to unexpected qgroup rescan failure:
# mkfs.btrfs -f -O quota $dev
# mount $dev $mnt
# mount -o remount,rescue=ibadroots $mnt
^^^^^ This above command is expected to fail
# btrfs quota rescan -w $mnt
^^^^^ The above qgroup rescan is not expected to fail
# btrfs qgroup show $mnt
WARNING: qgroup data inconsistent, rescan recommended
Qgroupid Referenced Exclusive Path
-------- ---------- --------- ----
0/5 16.00KiB 16.00KiB <toplevel>
The above short script will be converted to a proper fstests case.
[CAUSE]
Inside btrfs_reconfigure(), if either btrfs_check_options() or
btrfs_check_features() failed, we will always have
BTRFS_FS_STATE_REMOUNTING set for the fs until the next successful
remount.
That BTRFS_FS_STATE_REMOUNTING flag will interrupt several operations,
including:
- Qgroup rescan
- Auto defrag
- Space reclaim
[FIX]
Change the error handling of btrfs_check_options() and
btrfs_check_features() to goto restore label.
Fixes: eddb1a433f26 ("btrfs: add reconfigure callback for fs_context")
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
Pull btrfs fixes from David Sterba:
- fix root structure leak after relocation error
- fix optimization when checksums are read from commit root, fall back
to checksum root during relocation
- in tree-checker, validate length of inode reference in items
- validate properties before setting them
- validate free space cache entries on load
- transaction abort fixes
- fix printing of internal trees as signed numbers
- add error messages after critical lzo compression errors
* tag 'for-7.2-rc3-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
btrfs: print-tree: print header owner as signed
btrfs: decentralize transaction aborts in create_reloc_root()
btrfs: tree-checker: validate INODE_REF's namelen
btrfs: lzo: add error message for invalid headers
btrfs: fallback to transaction csum tree on a commit root csum miss
btrfs: fix root leak if its reloc root is unexpected in merge_reloc_roots()
btrfs: reject free space cache with more entries than pages
btrfs: fix transaction abort logic in btrfs_fileattr_set()
btrfs: validate properties before setting them
|
|
btrfs_search_slot() returns a positive value when the search key does
not exactly match an item. This is expected here, since offset 0 is used
to find the first ROOT_BACKREF for the subvolume and the actual key has
the parent root ID as its offset.
Before the compat ioctl refactoring, the native handler still copied the
filled structure to userspace when the search returned 1. After the
lookup was moved to a shared helper, both native and compat callers
treat the positive return value as a failure and skip copy_to_user(),
leaving BTRFS_IOC_GET_SUBVOL_INFO unusable for non-top-level
subvolumes.
Reset ret after successfully validating and reading the ROOT_BACKREF so
the helper reports success and both callers copy the result to
userspace.
Fixes: 538e5bdbc899 ("btrfs: add 32-bit compat ioctl for BTRFS_IOC_GET_SUBVOL_INFO")
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Daan De Meyer <daan@amutable.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
__add_reloc_root() allocates a mapping_node before inserting it into
rc->reloc_root_tree. If rb_simple_insert() finds an existing entry, it
returns the existing rb_node and leaves the newly allocated node unlinked.
The error path then returns -EEXIST without freeing the new node. Since
the node was never inserted into reloc_root_tree, the later cleanup in
put_reloc_control() cannot find it either.
Free the newly allocated node before returning -EEXIST.
The callers currently assert that -EEXIST should not happen, so this is a
defensive cleanup for an unexpected duplicate insert path. If the path is
ever reached, the local allocation should still be released.
Fixes: 57a304cfd43b ("btrfs: do not panic in __add_reloc_root")
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Guanghui Yang <3497809730@qq.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[BUG]
The following script (already submitted as generic/798) will report
incorrect dirty page numbers, with 64K page size systems and 4K fs block
size:
# mkfs.btrfs -s 4k -f $dev
# mount $dev $mnt
# xfs_io -f -c "pwrite 0 64K" -c fsync -c "cachestat 0 64K" $mnt/foobar
Cached: 1, Dirty: 1, Writeback: 0, Evicted: 0, Recently Evicted: 0
Note that the dirtied page number is still 1.
[CAUSE]
The cachestat() goes through the XArray of the page cache, but
instead of checking each folio's flag, it uses the
PAGECACHE_TAG_DIRTY tag to report dirty pages.
Since commit 095be159f3eb ("btrfs: unify folio dirty flag clearing"),
btrfs replaced a folio_clear_dirty_for_io() call inside
extent_write_cache_pages() with folio_test_dirty().
This will cause the following call sequence for the folio at file offset
0:
extent_write_cache_pages()
|- folio_test_dirty()
| The folio is still dirty, continue to writeback.
|
|- extent_writepage()
|- extent_writepage_io()
|- submit_one_sector() for range [0, 4K)
| |- btrfs_folio_clear_dirty()
| |- btrfs_folio_set_writeback()
| |- folio_start_writeback()
| It's the first writeback block, we set the writeback
| flag for the folio.
| But the folio is still dirty, PAGECACHE_TAG_DIRTY is
| kept
|
|- submit_one_sector() for range [4K, 8K)
| |- btrfs_folio_clear_dirty()
| |- btrfs_folio_set_writeback()
| The folio already has writeback flag, no need to call
| folio_start_writeback()
|
| ...
|- submit_one_sector() for range [60K, 64K)
|- btrfs_folio_clear_dirty()
|- btrfs_folio_set_writeback()
The folio already has writeback flag, no need to call
folio_start_writeback()
So the PAGECACHE_TAG_DIRTY is never cleared.
Meanwhile for the old code, before that commit, the sequence looks
like:
extent_write_cache_pages()
|- folio_clear_dirty_for_io()
| The folio is still dirty, so continue to writeback.
| But the folio dirty flag is cleared now.
|
|- extent_writepage()
|- extent_writepage_io()
|- submit_one_sector() for range [0, 4K)
| |- btrfs_folio_clear_dirty()
| |- btrfs_folio_set_writeback()
| |- folio_start_writeback()
| |- xas_clear(PAGECACHE_TAG)
|
| It's the first writeback block, we set the writeback
| flag for the folio.
| And the folio is not dirty, PAGECACHE_TAG_DIRTY is
| cleared
|
|- submit_one_sector() for range [4K, 8K)
| |- btrfs_folio_clear_dirty()
| |- btrfs_folio_set_writeback()
| The folio already has writeback flag, no need to call
| folio_start_writeback()
|
| ...
|- submit_one_sector() for range [60K, 64K)
|- btrfs_folio_clear_dirty()
|- btrfs_folio_set_writeback()
The folio already has writeback flag, no need to call
folio_start_writeback()
Unlike the new code, old code will clear PAGECACHE_TAG_DIRTY for the
first writeback block.
There is a deeper problem, dirty and writeback folio flags are updated
at very different timing.
The dirty flag is only cleared when the last sub-folio block has dirty
flag cleared.
But the writeback flag is set when the first block starts writeback, and
later blocks that go through writeback will not call
folio_start_writeback() again.
If we rely on folio_start_writeback() to update the
PAGECACHE_TAG_DIRTY and PAGECACHE_TAG_TOWRITE, it will always be
incorrect in one way or another.
[FIX]
Do not let folio_start_writeback() do any PAGECACHE_TAG_TOWRITE
handling.
Instead, manually clear both PAGECACHE_TAG_TOWRITE and
PAGECACHE_TAG_DIRTY flags when the folio is no longer dirty during
btrfs_subpage_set_writeback().
However this is only a hot-fix, for the long term solution we will
follow iomap, by calling folio_start_writeback() immediately for the
whole folio, and folio_end_writeback() after all writeback finished
for the folio.
Fixes: 095be159f3eb ("btrfs: unify folio dirty flag clearing")
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
When btrfs_drop_extent_map_range() splits an extent map, the new split
maps inherit the original map's flags through a local 'flags' variable.
Commit f86f7a75e2fb ("btrfs: use the flags of an extent map to identify
the compression type") changed the EXTENT_FLAG_LOGGING clearing to
operate on em->flags instead of that local 'flags' copy, so a split of
an extent map that is currently being logged wrongly inherits
EXTENT_FLAG_LOGGING.
The flag is then never cleared on the split, and when it is freed while
still on the inode's modified_extents list (for example by the extent
map shrinker) it trips the WARN_ON(!list_empty(&em->list)) in
btrfs_free_extent_map() and leads to a use-after-free.
Clear EXTENT_FLAG_LOGGING from the local 'flags' copy used for the
splits and only clear EXTENT_FLAG_PINNED from em->flags, restoring the
behaviour prior to f86f7a75e2fb.
CC: Jeff Layton <jlayton@kernel.org>
Link: https://lore.kernel.org/all/20260629-btrfs-skip-logging-v1-1-4e3a28c1acaf@kernel.org/
Fixes: f86f7a75e2fb ("btrfs: use the flags of an extent map to identify the compression type")
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Leo Martins <loemra.dev@gmail.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
The percpu_counter dirty_metadata_bytes is updated by negating eb->len
and passing it to percpu_counter_add_batch(), whose amount parameter is
s64. Since commit 84cda1a6087d ("btrfs: cache folio size and shift in
extent_buffer"), eb->len is u32. The u32 result of -eb->len, when
widened to the s64 parameter, becomes a large positive value instead of
the intended negative value. For eb->len == 16384 the counter adds
+4294950912 instead of subtracting 16384.
The counter therefore grows on every metadata writeback instead of
shrinking by the extent buffer size, permanently exceeding
BTRFS_DIRTY_METADATA_THRESH and causing __btrfs_btree_balance_dirty()
to trigger balance_dirty_pages_ratelimited() unconditionally, adding
unnecessary writeback pressure.
Cast eb->len to s64 before negation at both call sites so the
subtraction is performed in signed 64-bit arithmetic.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Fixes: 84cda1a6087d ("btrfs: cache folio size and shift in extent_buffer")
Signed-off-by: Dave Chen <davechen@synology.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
In btrfs_backref_free_node() we have the following assertion:
ASSERT(node->eb == NULL, "node->eb->start=%llu", node->eb->start);
and a user reported the following crash:
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
CPU: 0 UID: 0 PID: 10422 Comm: syz.0.17 Not tainted 7.1.0-02765-g6b5a2b7d9bc1-dirty #44 PREEMPT(full)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
RIP: 0010:btrfs_backref_free_node fs/btrfs/backref.c:3057 [inline]
RIP: 0010:btrfs_backref_free_node+0xb9/0x200 fs/btrfs/backref.c:3051
Code: 00 fc ff (...)
RSP: 0018:ffa0000006b0f3c0 EFLAGS: 00010246
RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff840eb78b
RDX: 0000000000000000 RSI: ffffffff840eafa5 RDI: ff110000742ab768
RBP: ff110000742ab700 R08: 0000000000000000 R09: 0000000000000000
R10: ff110000742ab700 R11: 00000000000a81f9 R12: ff11000107a92020
R13: ff1100005c182ea8 R14: 0000000000000000 R15: dffffc0000000000
FS: 0000555575536500(0000) GS:ff11000183985000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fa3d0e9d580 CR3: 000000002232a000 CR4: 0000000000753ef0
PKRU: 00000000
Call Trace:
<TASK>
btrfs_backref_cleanup_node+0x27/0x30 fs/btrfs/backref.c:3133
relocate_tree_block fs/btrfs/relocation.c:2604 [inline]
relocate_tree_blocks+0x11b0/0x1a20 fs/btrfs/relocation.c:2707
relocate_block_group+0x499/0xf30 fs/btrfs/relocation.c:3635
do_nonremap_reloc fs/btrfs/relocation.c:5323 [inline]
btrfs_relocate_block_group+0x1749/0x5fb0 fs/btrfs/relocation.c:5490
btrfs_relocate_chunk+0x12b/0x950 fs/btrfs/volumes.c:3647
__btrfs_balance fs/btrfs/volumes.c:4586 [inline]
btrfs_balance+0x1c7f/0x55c0 fs/btrfs/volumes.c:4973
btrfs_ioctl_balance fs/btrfs/ioctl.c:3474 [inline]
btrfs_ioctl+0x38a4/0x5d20 fs/btrfs/ioctl.c:5570
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl fs/ioctl.c:583 [inline]
__x64_sys_ioctl+0x18f/0x210 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x11f/0x860 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fb38e3b56dd
Code: 02 b8 ff (...)
RSP: 002b:00007fff04115788 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007fb38f6b0020 RCX: 00007fb38e3b56dd
RDX: 00002000000003c0 RSI: 00000000c4009420 RDI: 0000000000000004
RBP: 00007fb38e451b48 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 0000000000000000 R14: 00007fb38f6b0020 R15: 00007fb38f6b002c
</TASK>
It seems that this happens on some systems for some reason, when the
ASSERT() macro calls the inline function verify_assert_printk_format()
to evaluate the format string and arguments, causing the NULL pointer
dereference on node->eb.
So change the assertion to check for a NULL node->eb before dereferencing
it. Also, while at it, make the assertion more useful by printing the
owner of the extent buffer as well as its level.
Reported-by: Yue Sun <samsun1006219@gmail.com>
Link: https://lore.kernel.org/linux-btrfs/20260626065542.38413-1-samsun1006219@gmail.com/
Fixes: c4e7778580d6 ("btrfs: use verbose assertions in backref.c")
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
btrfs_getattr() unconditionally reads BTRFS_I(inode)->new_delalloc_bytes
and adds it (sector-aligned) to stat->blocks for every inode type.
However, new_delalloc_bytes lives in a union with last_dir_index_offset:
union {
u64 new_delalloc_bytes; /* files only */
u64 last_dir_index_offset; /* directories only */
};
For a directory inode this memory holds last_dir_index_offset, which is
set during directory logging (e.g. flush_dir_items_batch()) to the
offset of the last logged BTRFS_DIR_INDEX_KEY. That offset grows with
the number of entries ever created in the directory (dir indexes are
monotonic and never reused), so it can be arbitrarily large.
As a result, after a directory has been logged (e.g. via an fsync that
triggers directory logging), btrfs_getattr() reports inflated st_blocks
for that directory. The inflation is purely in-core and disappears
after the inode is evicted and reloaded (btrfs_alloc_inode() zeroes the
union), e.g. after a remount.
Reproducer (on a btrfs filesystem):
D=/mnt/btrfs/d
mkdir -p $D
for i in $(seq 1 20000); do touch $D/f$i; done
sync # commit, push dir index high
touch $D/trigger # dirty the dir in a new transaction
xfs_io -c fsync $D # log the directory -> sets last_dir_index_offset
stat -c '%b' $D # st_blocks is now inflated (e.g. 40)
# umount + mount -> st_blocks drops back to the correct value
The evict path already knows this union is type-dependent and guards the
corresponding WARN_ON with !S_ISDIR() in btrfs_destroy_inode(); only
btrfs_getattr() was missing the equivalent check.
Only read new_delalloc_bytes for regular files, which are the only
inodes that ever set it.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Dave Chen <davechen@synology.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
Commit a6908f88c9da ("btrfs: validate data reloc tree file extent item
members") introduced extra checks on file extent items for data reloc
inodes, but it checked the file extent offset without checking if the file
extent is inlined.
This can lead to either false alerts (as the offset member is inside the
inlined data) or even reading beyond the item range.
This has already triggered a warning in a syzbot report.
Although the root fix is to avoid compression for data reloc inodes, for
the sake of consistency, reject inlined file extents first.
Fixes: a6908f88c9da ("btrfs: validate data reloc tree file extent item members")
CC: stable@vger.kernel.org
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[BUG]
There is a syzbot report that the check inside get_new_location()
triggered:
BTRFS info (device loop0): found 31 extents, stage: move data extents
BTRFS info (device loop0): leaf 8908800 gen 16 total ptrs 28 free space 1676 owner 18446744073709551607
item 0 key (256 INODE_ITEM 0) itemoff 3835 itemsize 160
inode generation 5 transid 0 size 0 nbytes 0
block group 0 mode 40755 links 1 uid 0 gid 0
rdev 0 sequence 0 flags 0x0
atime 1669132761.0
ctime 1669132761.0
mtime 1669132761.0
otime 0.0
item 1 key (256 INODE_REF 256) itemoff 3823 itemsize 12
index 0 name_len 2
item 2 key (258 INODE_ITEM 0) itemoff 3663 itemsize 160
inode generation 1 transid 16 size 733184 nbytes 106496
block group 0 mode 100600 links 0 uid 0 gid 0
rdev 0 sequence 24 flags 0x18
item 3 key (258 EXTENT_DATA 0) itemoff 3595 itemsize 68
generation 16 type 0
inline extent data size 47 ram_bytes 4096 compression 1
[...]
item 27 key (18446744073709551611 ORPHAN_ITEM 258) itemoff 2376 itemsize 0
BTRFS error (device loop0): unexpected non-zero offset in file extent item for data reloc inode 258 key offset 0 offset 9277520992061368337
------------[ cut here ]------------
btrfs_abort_should_print_stack(__error)
[CAUSE]
The above dump tree shows the first file extent item is inlined, which
should make no sense for data reloc inodes, as such inodes just
represent where the data extents are in the relocation destination chunk.
However the relocation path preallocates space for each block,
then dirties them, cluster by cluster.
It's possible to have a single block at the beginning of the block
group, and no other block in the same cluster.
So relocation will preallocate a file extent for that block and dirty
the first block. Then memory pressure forces the data reloc inode to be
written back, before any other blocks are dirtied/allocated.
Finally commit 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated
delalloc helper") changed the sequence of delalloc. Before that commit we
always tried NOCOW first, so that dirtied block would be written back into
the preallocated space, and appear as a regular extent.
But with that commit, we always try inline first, and since compression
is forced, we try compressing the first block, and then inline the
compressed data, resulting in the above inlined file extent in the data
reloc tree.
Then the check in get_new_location() will check the file offset, without
checking if the file extent is inlined or not, resulting in the above
failure.
[FIX]
Do not allow compression for data reloc inodes.
Since data reloc inode sizes are always block aligned, as long as we do
not compress, @data_len will always be at least one block, and
that will cause can_cow_file_range_inline() to return false, thus no
inlined extent will be created.
Reported-by: syzbot+d950c6ba09b79f6e1864@syzkaller.appspotmail.com
Link: https://lore.kernel.org/linux-btrfs/6a373dc5.764cf64f.168fbe.0001.GAE@google.com/
Fixes: 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper")
CC: stable@vger.kernel.org
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
If the root we got has zero root refs in its root item, we are resetting
the root's ->reloc_root without using barriers like we do everywhere else.
Sashiko complained about this while reviewing another patch, and it's
correct (see the Link tag below).
Also, we should not clear BTRFS_ROOT_DEAD_RELOC_TREE from the root unless
the root points to the reloc root we have.
Fix this by using clear_reloc_root(), which issues the memory barrier
after setting the root's ->reloc_root to NULL and before clearing the bit
BTRFS_ROOT_DEAD_RELOC_TREE from the root.
Link: https://sashiko.dev/#/patchset/cf84f1a217c719e25b6b69e4298dd7afd36c9427.1781194426.git.fdmanana%40suse.com
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
If during relocation we fail in insert_dirty_subvol() because
btrfs_update_reloc_root() returned an error, we will leave a root's
reloc_root field pointing to a reloc root that was freed instead of NULL,
resulting later in a use-after-free, or double free attempt during
unmount.
The sequence of steps is this:
1) During relocation the call to btrfs_update_reloc_root() in
insert_dirty_subvol() fails, so insert_dirty_subvol() returns the
error to merge_reloc_root() without adding the root to the list
rc->dirty_subvol_roots;
2) Then merge_reloc_root() aborts the current transaction because
insert_dirty_subvol() returned an error;
3) Up the call chain, merge_reloc_roots() gets the error, adds the
reloc root for root X to the local reloc_roots list and jumps to the
'out' label, where it calls free_reloc_roots() to free all the reloc
roots in the local reloc_roots list. This frees the reloc root for
root X;
4) We go up the call chain to relocate_block_group() which calls
clean_dirty_subvols() to go over dirty roots and set their
->reloc_root field to NULL, but root X is not in the dirty_subvol_roots
list, so its ->reloc_root still points to a reloc root;
5) Relocation finishes, with an error and a transaction abort, but the
->reloc_root field for root X still points to the reloc root that was
freed in step 3;
6) When unmounting the fs we end up calling:
btrfs_free_fs_roots()
btrfs_drop_and_free_fs_root()
--> calls btrfs_put_root() against root X's ->reloc_root
which is not NULL and points to the already freed
reloc root in step 4 above
Resulting in a use-after-free to a double free attempt.
Syzbot reported this with the following dmesg/syslog:
[ 106.004389][ T5339] BTRFS error (device loop0 state A): Transaction aborted (error -5)
[ 106.014266][ T5339] BTRFS: error (device loop0 state A) in merge_reloc_root:1655: errno=-5 IO failure
[ 106.021891][ T1061] BTRFS error (device loop0 state A): error while writing out transaction: -5
[ 106.026964][ T1061] BTRFS warning (device loop0 state A): Skipping commit of aborted transaction.
[ 106.033807][ T5340] BTRFS error (device loop0 state A): bdev /dev/loop0 errs: wr 3, rd 0, flush 0, corrupt 0, gen 0
[ 106.039265][ T1061] BTRFS: error (device loop0 state A) in cleanup_transaction:2067: errno=-5 IO failure
[ 106.044382][ T5339] BTRFS info (device loop0 state EA): forced readonly
[ 106.074329][ T5339] BTRFS: error (device loop0 state EA) in merge_reloc_roots:1887: errno=-5 IO failure
[ 106.081004][ T5356] BTRFS info (device loop0 state EA): scrub: started on devid 1
[ 106.085611][ T5339] BTRFS info (device loop0 state EA): balance: ended with status: -30
[ 106.089517][ T5356] BTRFS info (device loop0 state EA): scrub: not finished on devid 1 with status: -30
[ 106.662365][ T5338] BTRFS info (device loop0 state EA): last unmount of filesystem 3a375e4e-b156-4d76-a2ad-16e198ce1409
[ 106.682946][ T5338] ==================================================================
[ 106.686574][ T5338] BUG: KASAN: slab-use-after-free in btrfs_put_root+0x2f/0x250
[ 106.690090][ T5338] Write of size 4 at addr ffff88803f978630 by task syz.0.0/5338
[ 106.693173][ T5338]
[ 106.694279][ T5338] CPU: 0 UID: 0 PID: 5338 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
[ 106.694293][ T5338] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 106.694300][ T5338] Call Trace:
[ 106.694308][ T5338] <TASK>
[ 106.694314][ T5338] dump_stack_lvl+0xe8/0x150
[ 106.694331][ T5338] print_address_description+0x55/0x1e0
[ 106.694343][ T5338] ? btrfs_put_root+0x2f/0x250
[ 106.694358][ T5338] print_report+0x58/0x70
[ 106.694368][ T5338] kasan_report+0x117/0x150
[ 106.694384][ T5338] ? btrfs_put_root+0x2f/0x250
[ 106.694399][ T5338] kasan_check_range+0x264/0x2c0
[ 106.694416][ T5338] btrfs_put_root+0x2f/0x250
[ 106.694430][ T5338] btrfs_drop_and_free_fs_root+0x160/0x210
[ 106.694447][ T5338] btrfs_free_fs_roots+0x2f9/0x3c0
[ 106.694464][ T5338] ? __pfx_btrfs_free_fs_roots+0x10/0x10
[ 106.694479][ T5338] ? free_root_pointers+0x5bf/0x5f0
[ 106.694494][ T5338] close_ctree+0x798/0x12d0
[ 106.694511][ T5338] ? __pfx_close_ctree+0x10/0x10
[ 106.694526][ T5338] ? _raw_spin_unlock_irqrestore+0x74/0x80
[ 106.694599][ T5338] ? rcu_preempt_deferred_qs_irqrestore+0x906/0xbc0
[ 106.694620][ T5338] ? __rcu_read_unlock+0x83/0xe0
[ 106.694636][ T5338] ? btrfs_put_super+0x48/0x1c0
[ 106.694652][ T5338] ? __pfx_btrfs_put_super+0x10/0x10
[ 106.694667][ T5338] generic_shutdown_super+0x13d/0x2d0
[ 106.694682][ T5338] kill_anon_super+0x3b/0x70
[ 106.694695][ T5338] btrfs_kill_super+0x41/0x50
[ 106.694710][ T5338] deactivate_locked_super+0xbc/0x130
[ 106.694722][ T5338] cleanup_mnt+0x437/0x4d0
[ 106.694736][ T5338] ? _raw_spin_unlock_irq+0x23/0x50
[ 106.694752][ T5338] task_work_run+0x1d9/0x270
[ 106.694769][ T5338] ? __pfx_task_work_run+0x10/0x10
[ 106.694784][ T5338] ? do_raw_spin_unlock+0x4d/0x210
[ 106.694802][ T5338] do_exit+0x70f/0x22c0
[ 106.694817][ T5338] ? trace_irq_disable+0x3b/0x140
[ 106.694835][ T5338] ? __pfx_do_exit+0x10/0x10
[ 106.694848][ T5338] ? preempt_schedule_thunk+0x16/0x30
[ 106.694863][ T5338] ? preempt_schedule_common+0x82/0xd0
[ 106.694878][ T5338] ? preempt_schedule_thunk+0x16/0x30
[ 106.694892][ T5338] do_group_exit+0x21b/0x2d0
[ 106.694906][ T5338] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 106.694918][ T5338] __x64_sys_exit_group+0x3f/0x40
[ 106.694932][ T5338] x64_sys_call+0x221a/0x2240
[ 106.694944][ T5338] do_syscall_64+0x174/0x580
[ 106.694954][ T5338] ? clear_bhb_loop+0x40/0x90
[ 106.694967][ T5338] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 106.694978][ T5338] RIP: 0033:0x7f958ef9ce59
[ 106.694988][ T5338] Code: Unable to access opcode bytes at 0x7f958ef9ce2f.
[ 106.694994][ T5338] RSP: 002b:00007fffd4058318 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
[ 106.695008][ T5338] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f958ef9ce59
[ 106.695015][ T5338] RDX: 00007f958c3f8000 RSI: 0000000000000000 RDI: 0000000000000000
[ 106.695022][ T5338] RBP: 0000000000000003 R08: 0000000000000000 R09: 00007f958f1e73e0
[ 106.695028][ T5338] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
[ 106.695034][ T5338] R13: 00007f958f1e73e0 R14: 0000000000000003 R15: 00007fffd40583d0
[ 106.695046][ T5338] </TASK>
[ 106.695050][ T5338]
[ 106.821635][ T5338] Allocated by task 1061:
[ 106.823446][ T5338] kasan_save_track+0x3e/0x80
[ 106.825498][ T5338] __kasan_kmalloc+0x93/0xb0
[ 106.827381][ T5338] __kmalloc_cache_noprof+0x31c/0x660
[ 106.829525][ T5338] btrfs_alloc_root+0x75/0x930
[ 106.831458][ T5338] read_tree_root_path+0x127/0xb00
[ 106.833556][ T5338] btrfs_read_tree_root+0x34/0x60
[ 106.835553][ T5338] create_reloc_root+0x6b3/0xcb0
[ 106.837556][ T5338] btrfs_init_reloc_root+0x2ec/0x4b0
[ 106.839557][ T5338] record_root_in_trans+0x2ab/0x350
[ 106.841685][ T5338] btrfs_record_root_in_trans+0x15c/0x180
[ 106.844237][ T5338] start_transaction+0x39c/0x1820
[ 106.846638][ T5338] btrfs_finish_one_ordered+0x88e/0x2680
[ 106.849436][ T5338] btrfs_work_helper+0x37b/0xc20
[ 106.851549][ T5338] process_scheduled_works+0xb5d/0x1860
[ 106.853807][ T5338] worker_thread+0xa53/0xfc0
[ 106.855773][ T5338] kthread+0x389/0x470
[ 106.857548][ T5338] ret_from_fork+0x514/0xb70
[ 106.859493][ T5338] ret_from_fork_asm+0x1a/0x30
[ 106.861504][ T5338]
[ 106.862527][ T5338] Freed by task 5339:
[ 106.864224][ T5338] kasan_save_track+0x3e/0x80
[ 106.866180][ T5338] kasan_save_free_info+0x46/0x50
[ 106.868371][ T5338] __kasan_slab_free+0x5c/0x80
[ 106.870462][ T5338] kfree+0x1c5/0x640
[ 106.872180][ T5338] __del_reloc_root+0x341/0x3b0
[ 106.874290][ T5338] free_reloc_roots+0x5f/0x90
[ 106.876282][ T5338] merge_reloc_roots+0x73f/0x8a0
[ 106.878489][ T5338] relocate_block_group+0xbcc/0xe70
[ 106.880742][ T5338] do_nonremap_reloc+0xa8/0x5b0
[ 106.882885][ T5338] btrfs_relocate_block_group+0x7e6/0xc40
[ 106.885336][ T5338] btrfs_relocate_chunk+0x115/0x820
[ 106.887502][ T5338] __btrfs_balance+0x1db0/0x2ae0
[ 106.889543][ T5338] btrfs_balance+0xaf3/0x11b0
[ 106.891456][ T5338] btrfs_ioctl_balance+0x3d3/0x610
[ 106.893672][ T5338] __se_sys_ioctl+0xfc/0x170
[ 106.895530][ T5338] do_syscall_64+0x174/0x580
[ 106.897518][ T5338] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 106.900101][ T5338]
[ 106.901123][ T5338] The buggy address belongs to the object at ffff88803f978000
[ 106.901123][ T5338] which belongs to the cache kmalloc-4k of size 4096
[ 106.906907][ T5338] The buggy address is located 1584 bytes inside of
[ 106.906907][ T5338] freed 4096-byte region [ffff88803f978000, ffff88803f979000)
[ 106.912980][ T5338]
[ 106.914022][ T5338] The buggy address belongs to the physical page:
[ 106.916716][ T5338] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x3f978
[ 106.920390][ T5338] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[ 106.923834][ T5338] flags: 0x4fff00000000040(head|node=1|zone=1|lastcpupid=0x7ff)
[ 106.927104][ T5338] page_type: f5(slab)
[ 106.928898][ T5338] raw: 04fff00000000040 ffff88801ac42140 dead000000000122 0000000000000000
[ 106.932507][ T5338] raw: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
[ 106.936193][ T5338] head: 04fff00000000040 ffff88801ac42140 dead000000000122 0000000000000000
[ 106.939856][ T5338] head: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
[ 106.943601][ T5338] head: 04fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
[ 106.947268][ T5338] head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
[ 106.950988][ T5338] page dumped because: kasan: bad access detected
[ 106.953710][ T5338] page_owner tracks the page as allocated
[ 106.956198][ T5338] page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2820(GFP_ATOMIC|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 24, tgid 24 (kworker/u4:2), ts 105728970387, free_ts 29540875453
[ 106.964984][ T5338] post_alloc_hook+0x22d/0x280
[ 106.966956][ T5338] get_page_from_freelist+0x2593/0x2610
[ 106.969307][ T5338] __alloc_frozen_pages_noprof+0x18d/0x380
[ 106.971839][ T5338] allocate_slab+0x77/0x660
[ 106.973709][ T5338] refill_objects+0x339/0x3d0
[ 106.975696][ T5338] __pcs_replace_empty_main+0x321/0x720
[ 106.978136][ T5338] __kmalloc_node_track_caller_noprof+0x572/0x7b0
[ 106.981009][ T5338] __alloc_skb+0x2c1/0x7d0
[ 106.982983][ T5338] nsim_dev_trap_report_work+0x29a/0xb90
[ 106.985356][ T5338] process_scheduled_works+0xb5d/0x1860
[ 106.987710][ T5338] worker_thread+0xa53/0xfc0
[ 106.989847][ T5338] kthread+0x389/0x470
[ 106.991727][ T5338] ret_from_fork+0x514/0xb70
[ 106.993722][ T5338] ret_from_fork_asm+0x1a/0x30
[ 106.995900][ T5338] page last free pid 77 tgid 77 stack trace:
[ 106.998479][ T5338] __free_frozen_pages+0xc1c/0xd30
[ 107.000819][ T5338] vfree+0x1d1/0x2f0
[ 107.002631][ T5338] delayed_vfree_work+0x55/0x80
[ 107.004848][ T5338] process_scheduled_works+0xb5d/0x1860
[ 107.007366][ T5338] worker_thread+0xa53/0xfc0
[ 107.009388][ T5338] kthread+0x389/0x470
[ 107.011177][ T5338] ret_from_fork+0x514/0xb70
[ 107.013313][ T5338] ret_from_fork_asm+0x1a/0x30
[ 107.015454][ T5338]
[ 107.016460][ T5338] Memory state around the buggy address:
[ 107.019052][ T5338] ffff88803f978500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 107.022691][ T5338] ffff88803f978580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 107.026264][ T5338] >ffff88803f978600: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 107.029721][ T5338] ^
[ 107.032062][ T5338] ffff88803f978680: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 107.035547][ T5338] ffff88803f978700: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 107.038865][ T5338] ==================================================================
Fix this by resetting a root's ->reloc_root if we get an error while
trying to merge a reloc root.
Reported-by: syzbot+b3d472d13f9d7bf20669@syzkaller.appspotmail.com
Link: https://lore.kernel.org/linux-btrfs/6a1ebde9.c1435f33.112120.0176.GAE@google.com/
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
vfs_mkdir() now sets the S_IFDIR type bit in the mode it passes to
->mkdir(), so OR-ing S_IFDIR into the mode again in btrfs_mkdir() is
redundant. Drop it.
Assisted-by: LLM
Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
Link: https://patch.msgid.link/20260630105400.68459-7-jkoolstra@xs4all.nl
Reviewed-by: NeilBrown <neil@brown.name>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
|
|
When dumping a tree block, btrfs_header::owner is printed as
unsigned, which can result in numbers that are hard to read, e.g.:
BTRFS info (device loop0): leaf 8908800 gen 16 total ptrs 28 free space 1676 owner 18446744073709551607
For the above output, 18446744073709551607 is (s64)-9, the root id of data
reloc tree.
Despite those predefined root ids that are already negative, existing
subvolume trees will not have any negative values, as subvolume trees can
only utilize the lower 48 bits, so there will be no output change for
existing subvolumes, thus no extra confusion.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Sun YangKai <sunk67188@gmail.com>
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
Decentralize transaction aborts in create_reloc_root(), so that it is
obvious which call failed and what caused the transaction abort.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
[BUG]
A crafted btrfs image can trigger the following crash:
BUG: unable to handle page fault for address: ffffd1dc42884000
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
CPU: 9 UID: 0 PID: 1034 Comm: poc Not tainted 7.1.0-rc4-custom+ #383 PREEMPT(full) 46af0a92938a63be7132e0dfd71e62327c51d5c2
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022
RIP: 0010:memcpy+0xc/0x10
Call Trace:
<TASK>
read_extent_buffer+0xe4/0x100 [btrfs 3cf0785dd58fec8c5ff84633b772f17ce1f92a8f]
btrfs_get_name+0x15e/0x1e0 [btrfs 3cf0785dd58fec8c5ff84633b772f17ce1f92a8f]
reconnect_path+0x165/0x390
exportfs_decode_fh_raw+0x337/0x400
? drop_caches_sysctl_handler+0xb0/0xb0
</TASK>
---[ end trace 0000000000000000 ]---
RIP: 0010:memcpy+0xc/0x10
Kernel panic - not syncing: Fatal exception
[CAUSE]
TThe crafted image has the following corrupted INODE_REF item:
item 9 key (258 INODE_REF 257) itemoff 11544 itemsize 4106
index 2 namelen 4096 name: d\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
The itemsize matches the namelen, but the namelen is 4096, way larger
than normal name length limit (BTRFS_NAME_LEN, 255).
Meanwhile the memory of the @name is only 255 byte sized, this will cause
out-of-boundary access, and cause the above crash.
[FIX]
Add extra namelen verification for INODE_REF, just like what we have
done in ROOT_REF checks.
Now the crafted image can be rejected gracefully:
BTRFS critical (device dm-2): corrupt leaf: root=5 block=30572544 slot=14 ino=259, invalid inode ref name length, has 4096 expect [1, 255]
BTRFS error (device dm-2): read time tree block corruption detected on logical 30572544 mirror 2
Reported-by: Xiang Mei <xmei5@asu.edu>
Link: https://lore.kernel.org/linux-btrfs/aik0hEV6ehKx6Ldv@Air.local/
Acked-by: Weiming Shi <bestswngs@gmail.com>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
[ Rebase, add a Link: tag, add an simple cause analyze ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
Inside btrfs we always pair -EUCLEAN error with an error message to
indicate which data is corrupted.
However there are 3 cases inside lzo decompression where there is no
error message for corrupted headers.
Add those missing error messages to show exactly where the corruption
is.
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
We have been running with commit root csums enabled for some time and
have noticed a slight uptick in zero csum errors. Investigating those
revealed that they were same transaction reads of extents that were just
relocated, but the extent map generation was long ago.
It turns out that relocation intentionally does not update the extent
generation (replace_file_extents()), but must write a new csum since the
data has moved, so we must account for this with commit root csum reading.
Luckily this is a short lived condition: after the relocation transaction
the commit root will once again have the csum. So we can add a generic
fallback to the lookup to try again with the transaction csum root.
Fixes: f07b855c56b1 ("btrfs: try to search for data csums in commit root")
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Boris Burkov <boris@bur.io>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
If we have an unexpected reloc_root for our root, we jump to the out label
but never drop the reference we obtained for root, resulting in a leak.
Add a missing btrfs_put_root() call.
Fixes: 24213fa46c70 ("btrfs: do proper error handling in merge_reloc_roots")
Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
When loading a v1 free space cache, __load_free_space_cache() takes
num_entries and num_bitmaps straight from the on-disk
btrfs_free_space_header. That header is stored in the tree_root under a key
with type 0, which the tree-checker has no case for, so neither count is
validated before the load trusts it.
The load loops num_entries times and maps the next page whenever the current
one runs out, going through io_ctl_check_crc() -> io_ctl_map_page(), which
does io_ctl->pages[io_ctl->index++]. But pages[] is allocated in
io_ctl_init() from the cache inode's i_size, not from num_entries:
num_pages = DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE);
io_ctl->pages = kcalloc(num_pages, sizeof(struct page *), GFP_NOFS);
So if num_entries claims more records than the pages can hold, io_ctl->index
runs off the end of pages[]. The write side never hits this because
io_ctl_add_entry() and io_ctl_add_bitmap() both stop once
io_ctl->index >= io_ctl->num_pages; the read side just never had the same
check.
To trigger it, take a clean cache (num_entries = <N> here), set num_entries
in the header to 0x10000, and fix up the leaf checksum so it still passes
the tree-checker. The cache inode has i_size = 65536, so num_pages is 16 and
pages[] is a 16-pointer (kmalloc-128) array. The load now tries to read
65536 entries, io_ctl->index walks up to 16, and pages[16] is read past the
array:
BUG: KASAN: slab-out-of-bounds in io_ctl_check_crc (fs/btrfs/free-space-cache.c:420 fs/btrfs/free-space-cache.c:565)
Read of size 8 at addr ffff88800c833a80 by task kworker/u8:3/58
io_ctl_check_crc (fs/btrfs/free-space-cache.c:420 fs/btrfs/free-space-cache.c:565)
__load_free_space_cache (fs/btrfs/free-space-cache.c:655 fs/btrfs/free-space-cache.c:820)
load_free_space_cache (fs/btrfs/free-space-cache.c:1017)
caching_thread (fs/btrfs/block-group.c:880)
btrfs_work_helper (fs/btrfs/async-thread.c:312)
process_one_work
worker_thread
kthread
ret_from_fork
free-space-cache.c:420 is io_ctl_map_page(), inlined into io_ctl_check_crc()
at line 565, which is why that is the frame KASAN names. The out-of-bounds
slot is then treated as a struct page and handed to crc32c(), so the bad
read turns into a GP fault.
Add the missing check to io_ctl_check_crc(), which is where both the entry
loop and the bitmap loop end up. When num_entries is too large the load now
fails like any corrupt cache: __load_free_space_cache() drops it and rebuilds
the free space from the extent tree, so a valid cache is never rejected.
Reported-by: Weiming Shi <bestswngs@gmail.com>
Fixes: 5b0e95bf607d ("Btrfs: inline checksums into the disk free space cache")
Link: https://lore.kernel.org/linux-btrfs/CAPpSM+RMPByMCKXvM5QFKToxsyNccfuFLWMdD0mfd0wh2Ja62w@mail.gmail.com/
Assisted-by: Claude:claude-opus-4-8
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Xiang Mei <xmei5@asu.edu>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
There's no need to abort the transaction if we failed to set or delete a
property, as we haven't done any change. However we need to abort if we
set a property or delete a property and then fail to update the inode
item, as that would leave the inode's state in subvolume tree
inconsistent.
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|
|
We set the xattr and then attempt to apply the property. If the apply
fails we then attempt to delete the xattr to avoid an inconsistency.
However we don't verify if the deletion succeed, so if it fails we
leave an inconsistency between the state in the btree and the in-memory
inode.
Address this by validating first if we can apply the property, then set
the xattr, then apply the property, and this last step should not fail
since the validation succeeded before - assert that it does not fail but
leave code to attempt to delete the xattr if it happens, and then abort
the transaction only if the xattr delete failed.
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
|