[PATCH] f2fs: bound the SIT/NAT bitmaps by the checkpoint pack
From: Kaixuan Li
Date: Sat Sep 19 2026 - 09:09:59 EST
sit_ver_bitmap_bytesize and nat_ver_bitmap_bytesize are used as memcpy
lengths into and out of sbi->ckpt, which is sized from cp_payload alone,
and nothing compares either length against that allocation.
sanity_check_ckpt() has checked these two fields since c77ec61ca0a4, which
added them after a crafted image overran build_sit_info(). That check ties
each length to a segment count; it never compares either against the
allocation, which is the half this adds.
Mounting over-reads in build_sit_info() and build_node_manager(), and the
first checkpoint writes the same lengths back through the same pointers,
so the over-read's contents reach the on-disk checkpoint. An unmount is
enough; the filesystem does not have to be used.
Bound both ends the way __bitmap_ptr() lays them out. The three branches
differ, so a single sum of the two lengths would still admit a small
overflow when cp_payload is non-zero.
Fixes: c77ec61ca0a4 ("f2fs: fix to do sanity check with {sit,nat}_ver_bitmap_bytesize")
Signed-off-by: Kaixuan Li <kaixuanli0131@xxxxxxxxx>
---
Reproduced on v7.2.4 x86_64 with CONFIG_KASAN=y. Two crafted images from a
plain mkfs.f2fs 512G volume, with segments taken off the SSA area so
main_blkaddr does not move:
cp_payload 0, segment_count_sit 20 -> 220
sbi->ckpt 4096 bytes, SIT bitmap at +192 for 7040, NAT at
+7232 for 3200, ending 6336 past the allocation
cp_payload 2, segment_count_sit 20 -> 520
sbi->ckpt 12288 bytes, SIT bitmap at +4096 for 16640,
ending 8448 past
case kernel result
crafted cp_payload=2 stock mounts and unmounts
crafted cp_payload=0 stock mounts and unmounts
pristine mkfs.f2fs stock mounts
crafted cp_payload=2 patched rejected
crafted cp_payload=0 patched rejected
pristine mkfs.f2fs patched mounts
with the patched kernel naming the numbers:
Bitmaps do not fit the checkpoint pack: sit ends at 20736, nat at 3392, pack is 12288 bytes
Bitmaps do not fit the checkpoint pack: sit ends at 7232, nat at 10432, pack is 4096 bytes
Setting sit_ver_bitmap_bytesize to 7040 while leaving segment_count_sit at
20 gives "Wrong bitmap size: sit: 7040, nat:3200" and the mount fails, so
the field is read and the only thing checked against it is the segment
count.
KASAN does not flag the copy: with cp_payload 0 the allocation requests
exactly 4096, so there is no in-object redzone, and at 12288 it went
unreported too. What the cp_payload 2 image does produce, on one of three
runs with a freshly built image each time, is the corruption landing at
unmount:
BUG: KASAN: slab-use-after-free in kthread_stop+0x23/0x390
Write of size 4 at addr ffff88800a329fa8 by task init/1
Call Trace:
kasan_check_range+0x39/0x1c0
kthread_stop+0x23/0x390
f2fs_destroy_segment_manager+0x176/0x9b0
f2fs_put_super+0x635/0x10e0
generic_shutdown_super+0x13e/0x440
kill_f2fs_super+0x24e/0x500
Which neighbour it lands on varies.
Mounting a crafted image is not a Linux kernel vulnerability
(Documentation/process/threat-model.rst), and I am not reporting it as
one.
---
fs/f2fs/super.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -4180,8 +4180,9 @@ int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi)
block_t user_block_count, valid_user_blocks;
block_t avail_node_count, valid_node_count;
unsigned int nat_blocks, nat_bits_bytes, nat_bits_blocks;
unsigned int sit_blk_cnt;
+ unsigned long long ckpt_size, sit_end, nat_end;
int i, j;
total = le32_to_cpu(raw_super->segment_count);
fsmeta = le32_to_cpu(raw_super->segment_count_ckpt);
@@ -4308,8 +4309,31 @@ skip_cross:
cp_pack_start_sum);
return 1;
}
+ /*
+ * Nothing above ties the bitmap lengths to sbi->ckpt, which is
+ * (1 + cp_payload) blocks. Bound both ends the way __bitmap_ptr()
+ * lays them out.
+ */
+ ckpt_size = (unsigned long long)(cp_payload + 1) * F2FS_BLKSIZE;
+ if (__is_set_ckpt_flags(ckpt, CP_LARGE_NAT_BITMAP_FLAG)) {
+ nat_end = CP_MIN_CHKSUM_OFFSET + sizeof(__le32) + nat_bitmap_size;
+ sit_end = nat_end + sit_bitmap_size;
+ } else if (cp_payload > 0) {
+ nat_end = CP_MIN_CHKSUM_OFFSET + nat_bitmap_size;
+ sit_end = F2FS_BLKSIZE + sit_bitmap_size;
+ } else {
+ sit_end = CP_MIN_CHKSUM_OFFSET + sit_bitmap_size;
+ nat_end = sit_end + nat_bitmap_size;
+ }
+
+ if (sit_end > ckpt_size || nat_end > ckpt_size) {
+ f2fs_err(sbi, "Bitmaps do not fit the checkpoint pack: sit ends at %llu, nat at %llu, pack is %llu bytes",
+ sit_end, nat_end, ckpt_size);
+ return 1;
+ }
+
if (__is_set_ckpt_flags(ckpt, CP_LARGE_NAT_BITMAP_FLAG) &&
le32_to_cpu(ckpt->checksum_offset) != CP_MIN_CHKSUM_OFFSET) {
f2fs_warn(sbi, "using deprecated layout of large_nat_bitmap, "
"please run fsck v1.13.0 or higher to repair, chksum_offset: %u, "