[PATCH 1/3] resize2fs: out-of-bounds read/write in xattr entry scanning

Josh Hunt <[email protected]> Thu, 30 Jul 2026 20:36:59 -0700
Newsgroups org.kernel.vger.linux-ext4
Message-ID <[email protected]>
From: Kit Knox <[email protected]>

The fix_ea_entries() function iterates over extended attribute entries
using the loop condition "while (entry < end && !EXT2_EXT_IS_LAST_ENTRY(entry))".
This check is insufficient because:

  1. It does not verify the full 16-byte ext2_ext_attr_entry structure fits
     within the buffer before accessing its fields. With fewer than 16 bytes
     remaining, evaluating EXT2_EXT_IS_LAST_ENTRY() reads past the buffer.

  2. A crafted e_name_len value can cause EXT2_EXT_ATTR_NEXT() to advance
     past the buffer boundary, and subsequent iterations read/write beyond
     allocated memory.

  3. When entry->e_value_inum > last_ino, resize2fs writes to entry->e_value_inum,
     potentially corrupting heap memory.

Additionally, fix_ea_ibody_entries() does not validate i_extra_isize before
using it to compute xattr offsets. A malformed inode with an invalid
i_extra_isize value (unaligned or too large) causes out-of-bounds reads
when accessing the ea_magic field.

Signed-off-by: Kit Knox <[email protected]>
---
 resize/resize2fs.c | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/resize/resize2fs.c b/resize/resize2fs.c
index c8964af5..dfefca1d 100644
--- a/resize/resize2fs.c
+++ b/resize/resize2fs.c
@@ -2045,7 +2045,16 @@ static int fix_ea_entries(ext2_extent imap, struct ext2_ext_attr_entry *entry,
 	int modified = 0;
 	ext2_ino_t new_ino;
 
-	while (entry < end && !EXT2_EXT_IS_LAST_ENTRY(entry)) {
+	/*
+	 * Iterate over xattr entries with proper bounds checking.
+	 * We must verify that the entire entry structure fits within
+	 * the buffer before accessing any fields, as a malformed
+	 * e_name_len could cause EXT2_EXT_ATTR_NEXT() to advance
+	 * past the buffer boundary.
+	 */
+	while ((char *)entry + sizeof(struct ext2_ext_attr_entry) <= (char *)end &&
+	       (char *)EXT2_EXT_ATTR_NEXT(entry) <= (char *)end &&
+	       !EXT2_EXT_IS_LAST_ENTRY(entry)) {
 		if (entry->e_value_inum > last_ino) {
 			new_ino = ext2fs_extent_translate(imap,
 							  entry->e_value_inum);
@@ -2063,10 +2072,20 @@ static int fix_ea_ibody_entries(ext2_extent imap,
 {
 	struct ext2_ext_attr_entry *start, *end;
 	__u32 *ea_magic;
+	int max_extra_isize;
 
 	if (inode->i_extra_isize == 0)
 		return 0;
 
+	/*
+	 * Validate i_extra_isize before using it to compute offsets.
+	 * It must be 4-byte aligned and fit within the inode.
+	 */
+	max_extra_isize = inode_size - EXT2_GOOD_OLD_INODE_SIZE;
+	if ((inode->i_extra_isize & 3) ||
+	    inode->i_extra_isize > max_extra_isize)
+		return 0;
+
 	ea_magic = (__u32 *)((char *)inode + EXT2_GOOD_OLD_INODE_SIZE +
 				inode->i_extra_isize);
 	if (*ea_magic != EXT2_EXT_ATTR_MAGIC)
-- 
2.34.1