* [PATCH] ext4: xattr: fix size_t underflow in ext4_xattr_set_entry
@ 2026-03-17 10:28 ZhengYuan Huang
0 siblings, 0 replies; only message in thread
From: ZhengYuan Huang @ 2026-03-17 10:28 UTC (permalink / raw)
To: tytso, adilger.kernel
Cc: linux-ext4, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
ZhengYuan Huang, stable
[BUG]
KASAN reports an out-of-bounds read with an astronomically large access
size when ext4_xattr_set_entry() deletes a corrupted ibody xattr entry:
BUG: KASAN: out-of-bounds in ext4_xattr_set_entry+0x11a3/0x1f40 fs/ext4/xattr.c:1756
Read of size 18446744073709551600 at addr ffff8880179737cc
Call Trace:
...
ext4_xattr_set_entry+0x11a3/0x1f40 fs/ext4/xattr.c:1756
ext4_xattr_ibody_set+0x396/0x5a0 fs/ext4/xattr.c:2268
ext4_destroy_inline_data_nolock+0x25e/0x560 fs/ext4/inline.c:463
ext4_convert_inline_data_nolock+0x186/0xa80 fs/ext4/inline.c:1105
ext4_try_add_inline_entry+0x58e/0x960 fs/ext4/inline.c:1224
ext4_add_entry+0x6d2/0xce0 fs/ext4/namei.c:2389
ext4_add_nondir+0x9c/0x280 fs/ext4/namei.c:2784
ext4_create+0x380/0x500 fs/ext4/namei.c:2830
...
The access size 18446744073709551600 equals (size_t)(-16), the result of
a pointer arithmetic underflow.
The bug is reproducible on next-20260313 with our dynamic metadata
fuzzing tool that corrupts ext4 metadata at runtime.
[CAUSE]
When deleting an ibody xattr entry (i->value == NULL), the code computes
the memmove length as:
(void *)last - (void *)here + sizeof(__u32)
where `last` is first the result of walking the xattr entry list to its
terminating IS_LAST_ENTRY, and then stepped back by EXT4_XATTR_LEN(name_len):
last = ENTRY((void *)last - size);
Consider a corrupted ibody xattr list where a spurious IS_LAST_ENTRY (four
zero bytes) has been planted before the real target entry (e.g.,
system.data used for inline data). When ext4_xattr_ibody_find() calls
xattr_find_entry() to locate the entry, the walk stops at the spurious
terminator. xattr_find_entry() returns -ENODATA and sets s->here to that
terminator position; s->not_found is set to -ENODATA accordingly.
Back in ext4_xattr_set_entry(), the entry-walking for loop starts from
s->first and also stops at the same spurious IS_LAST_ENTRY, so
last == here after the loop. Because the "remove old value" block is
guarded by (!s->not_found), it is skipped. The delete-name path
(if (!i->value)) is not guarded by s->not_found at all, so it runs
unconditionally. It then executes:
last = ENTRY((void *)last - size); /* last = here - 20 */
which places last before here. The subsequent memmove() computes its
length as:
(here - 20) - here + sizeof(__u32) = -16
which wraps to (size_t)(-16) = 18446744073709551600 as an unsigned type,
causing memmove() to attempt an enormous read.
The load-time check_xattrs() call also stops at the first IS_LAST_ENTRY
it encounters. A spurious terminator inserted before the real entries
silently truncates the range that check_xattrs() examines, so entries
beyond it are never validated at load time.
[FIX]
After adjusting `last`, check that it has not moved before `here`. If it
has, the xattr entry list is corrupted; reject it with -EFSCORRUPTED.
After this fix, the code will detect the corrupted entry and reject it with
-EFSCORRUPTED, preventing the out-of-bounds access.
Cc: stable@vger.kernel.org
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
It might also be worth considering whether check_xattrs() needs a similar fix.
I'm not deeply familiar with the ext4 codebase, so I'd appreciate any guidance
from the maintainers and would be happy to update and resend the patch if needed.
---
fs/ext4/xattr.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
index ce7253b3f549..b8f1102972f0 100644
--- a/fs/ext4/xattr.c
+++ b/fs/ext4/xattr.c
@@ -1753,6 +1753,11 @@ static int ext4_xattr_set_entry(struct ext4_xattr_info *i,
size_t size = EXT4_XATTR_LEN(name_len);
last = ENTRY((void *)last - size);
+ if ((void *)last < (void *)here) {
+ EXT4_ERROR_INODE(inode, "corrupted xattr entries: last before here");
+ ret = -EFSCORRUPTED;
+ goto out;
+ }
memmove(here, (void *)here + size,
(void *)last - (void *)here + sizeof(__u32));
memset(last, 0, size);
--
2.43.0
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-03-17 10:28 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-17 10:28 [PATCH] ext4: xattr: fix size_t underflow in ext4_xattr_set_entry ZhengYuan Huang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox