public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
* [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