public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
From: ZhengYuan Huang <gality369@gmail.com>
To: tytso@mit.edu, adilger.kernel@dilger.ca
Cc: linux-ext4@vger.kernel.org, linux-kernel@vger.kernel.org,
	baijiaju1990@gmail.com, r33s3n6@gmail.com, zzzccc427@gmail.com,
	ZhengYuan Huang <gality369@gmail.com>,
	stable@vger.kernel.org
Subject: [PATCH] ext4: xattr: fix size_t underflow in ext4_xattr_set_entry
Date: Tue, 17 Mar 2026 18:28:10 +0800	[thread overview]
Message-ID: <20260317102810.2984100-1-gality369@gmail.com> (raw)

[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

                 reply	other threads:[~2026-03-17 10:28 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20260317102810.2984100-1-gality369@gmail.com \
    --to=gality369@gmail.com \
    --cc=adilger.kernel@dilger.ca \
    --cc=baijiaju1990@gmail.com \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=r33s3n6@gmail.com \
    --cc=stable@vger.kernel.org \
    --cc=tytso@mit.edu \
    --cc=zzzccc427@gmail.com \
    /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