From: Hui Peng <benquike@gmail.com>
To: David Sterba <dsterba@suse.com>
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH 1/3] affs: reject out-of-range blocks in affs_free_block()
Date: Sat, 19 Sep 2026 18:09:55 +0000 [thread overview]
Message-ID: <20260919180958.1362943-2-benquike@gmail.com> (raw)
In-Reply-To: <20260919180958.1362943-1-benquike@gmail.com>
affs_free_block() accepts any block number below or equal to
s_partition_size, then subtracts s_reserved from it:
if (block > sbi->s_partition_size)
goto err_range;
blk = block - sbi->s_reserved;
bmap = blk / sbi->s_bmap_bits;
bit = blk % sbi->s_bmap_bits;
bm = &sbi->s_bitmap[bmap];
Both bounds are wrong.
block is a u32 taken straight from the on-disk file header or extension
block, and nothing rejects a value below s_reserved. For block = 1 with
s_reserved = 2 the subtraction underflows to 0xffffffff, so with a 512
byte block size (s_bmap_bits = 512 * 8 - 32 = 4064) the index becomes
0xffffffff / 4064 = 1056832. sizeof(struct affs_bm_info) is 8, so
&sbi->s_bitmap[bmap] lands roughly 8.45 MB past an allocation that is
only a handful of entries long.
The upper bound is also off by one: s_partition_size is a block count,
so the last valid block is s_partition_size - 1, and a block equal to
s_partition_size is accepted today. Because s_bmap_count is
ceil((s_partition_size - s_reserved) / s_bmap_bits), that block yields
bmap == s_bmap_count exactly whenever the partition divides evenly into
bitmap blocks - a one element overrun of the same array.
Mounting a crafted AFFS image whose file header references a block below
s_reserved and truncating the file reproduces the underflow variant:
==================================================================
BUG: KASAN: slab-use-after-free in affs_free_block+0x5d4/0x670
Read of size 4 at addr ffff8881068d4c00 by task init/172
CPU: 2 UID: 0 PID: 172 Comm: init Not tainted 7.3.0-rc3 #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996)
Call Trace:
<TASK>
dump_stack_lvl+0x70/0xa0
print_report+0x153/0x4c6
kasan_report+0xf1/0x120
affs_free_block+0x5d4/0x670
affs_truncate+0x635/0x1520
affs_setattr+0x367/0x470
notify_change+0x941/0x1050
do_truncate+0x1ba/0x210
vfs_truncate+0x305/0x490
ksys_truncate+0xd9/0x160
__x64_sys_truncate+0x59/0x80
do_syscall_64+0xda/0x4b0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
==================================================================
KASAN calls it a use-after-free only because the wild address happened
to land inside an unrelated slab object that had already been freed;
the allocation and free stacks in the full report belong to a boot time
kobject_uevent_env() allocation. It is an out-of-bounds read, not a
temporal bug, and where it lands depends on the heap layout.
AFFS already has a helper that encodes the valid range, and it has done
so since the beginning of git history:
static inline bool affs_validblock(struct super_block *sb, int block)
{
return(block >= AFFS_SB(sb)->s_reserved &&
block < AFFS_SB(sb)->s_partition_size);
}
affs_bread(), affs_getblk(), affs_getzeroblk() and affs_getemptyblk()
all gate on it, so a block that affs_free_block() accepts today is one
that AFFS has always refused to read. Use the same helper here rather
than open coding a third variant of the test.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
The last valid block is still freeable: affs_init_bitmap() explicitly
marks every bit mapping to a block >= s_partition_size as allocated in
the final bitmap block, so s_partition_size - 1 is the highest block the
allocator can hand out and affs_validblock() accepts it.
I have left out two further checks that I had initially written, because
both are unreachable once this patch is applied and I did not want to
mix speculative hardening into a fix with a reproducer:
- a `bmap >= sbi->s_bmap_count` test after the division. Given
s_reserved <= block < s_partition_size we have blk <= N-1 where
N = s_partition_size - s_reserved, and s_bmap_count = ceil(N /
s_bmap_bits) = floor((N-1) / s_bmap_bits) + 1, so bmap is always
<= s_bmap_count - 1.
- an early return when sbi->s_bitmap is NULL or sbi->s_bmap_bits is 0,
guarding the division. affs_init_bitmap() only leaves those unset on
paths that force SB_RDONLY (including the ro->rw reconfigure path),
and a read-only superblock cannot reach affs_truncate().
Happy to add either if you would prefer the belt and braces.
Not Cc'd to stable and posted in the open: per
Documentation/process/threat-model.rst, "bugs triggered by mounting a
corrupted or maliciously crafted file system image" are regular bugs
rather than vulnerabilities, because mounting is privileged. Say the
word if you would like it tagged for stable anyway.
Found with a QEMU/KASAN reproducer built around a crafted 4 KB AFFS
image; reproduced in six independent runs.
diff --git a/fs/affs/bitmap.c b/fs/affs/bitmap.c
--- a/fs/affs/bitmap.c
+++ b/fs/affs/bitmap.c
@@ -46,7 +46,7 @@
pr_debug("%s(%u)\n", __func__, block);
- if (block > sbi->s_partition_size)
+ if (!affs_validblock(sb, block))
goto err_range;
blk = block - sbi->s_reserved;
--
2.43.0
next prev parent reply other threads:[~2026-09-19 18:10 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-19 18:09 [PATCH 0/3] affs: fix out-of-bounds bitmap access on crafted images Hui Peng
2026-09-19 18:09 ` Hui Peng [this message]
2026-09-19 18:09 ` [PATCH 2/3] affs: check affs_bread() return value in affs_truncate() Hui Peng
2026-09-19 18:09 ` [PATCH 3/3] affs: validate the allocation goal in affs_alloc_block() Hui Peng
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260919180958.1362943-2-benquike@gmail.com \
--to=benquike@gmail.com \
--cc=dsterba@suse.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®