[PATCH v2 1/2] ocfs2: validate inline xattr metadata in ocfs2_xattr_ibody_get

Zhang Cen <[email protected]> Tue, 26 May 2026 11:40:39 +0800
Newsgroups dev.linux.lists.ocfs2-devel
Message-ID <[email protected]>
ocfs2_xattr_ibody_get() derives xs->header from
di->i_xattr_inline_size and then immediately hands that header to
ocfs2_xattr_find_entry(). If a corrupted inode advertises a zero,
too-small, or too-large inline xattr size, the get path can place the
header at the end of the inode block or outside it before reading
xh_count and walking entries.

Reuse the inline xattr size and xh_count validation that the list path
already relies on before parsing the in-inode header on the get path.
Reject corrupted inline metadata with -EFSCORRUPTED instead of walking
past the inline area.

A crafted image can otherwise trigger:
BUG: KASAN: use-after-free in ocfs2_xattr_find_entry+0x5a/0x170

Fixes: cf1d6c763fbc ("ocfs2: Add extended attribute support")
Assisted-by: Codex:gpt-5.5
Signed-off-by: Zhang Cen <[email protected]>
---
 fs/ocfs2/xattr.c | 51 ++++++++++++++++++++++++++++++------------------
 1 file changed, 32 insertions(+), 19 deletions(-)

diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 86cfd4c2adf9..eabfaa02d8b8 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -967,24 +967,17 @@ int ocfs2_has_inline_xattr_value_outside(struct inode *inode,
 	return 0;
 }
 
-static int ocfs2_xattr_ibody_list(struct inode *inode,
-				  struct ocfs2_dinode *di,
-				  char *buffer,
-				  size_t buffer_size)
+static int ocfs2_xattr_check_inline_xh(struct inode *inode,
+				       struct ocfs2_dinode *di,
+				       struct ocfs2_xattr_header **header)
 {
-	struct ocfs2_xattr_header *header = NULL;
-	struct ocfs2_inode_info *oi = OCFS2_I(inode);
-	int ret = 0;
-	u16 xattr_count;
+	struct ocfs2_xattr_header *xh;
 	size_t max_entries;
 	u16 inline_size;
-
-	if (!(oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL))
-		return ret;
+	u16 xattr_count;
 
 	inline_size = le16_to_cpu(di->i_xattr_inline_size);
 
-	/* Validate inline size is reasonable */
 	if (inline_size > inode->i_sb->s_blocksize ||
 	    inline_size < sizeof(struct ocfs2_xattr_header)) {
 		ocfs2_error(inode->i_sb,
@@ -994,12 +987,11 @@ static int ocfs2_xattr_ibody_list(struct inode *inode,
 		return -EFSCORRUPTED;
 	}
 
-	header = (struct ocfs2_xattr_header *)
-		 ((void *)di + inode->i_sb->s_blocksize - inline_size);
-
-	xattr_count = le16_to_cpu(header->xh_count);
+	xh = (struct ocfs2_xattr_header *)
+		((void *)di + inode->i_sb->s_blocksize - inline_size);
+	xattr_count = le16_to_cpu(xh->xh_count);
 	max_entries = (inline_size - sizeof(struct ocfs2_xattr_header)) /
-		       sizeof(struct ocfs2_xattr_entry);
+		      sizeof(struct ocfs2_xattr_entry);
 
 	if (xattr_count > max_entries) {
 		ocfs2_error(inode->i_sb,
@@ -1009,6 +1001,26 @@ static int ocfs2_xattr_ibody_list(struct inode *inode,
 		return -EFSCORRUPTED;
 	}
 
+	*header = xh;
+	return 0;
+}
+
+static int ocfs2_xattr_ibody_list(struct inode *inode,
+				  struct ocfs2_dinode *di,
+				  char *buffer,
+				  size_t buffer_size)
+{
+	struct ocfs2_xattr_header *header = NULL;
+	struct ocfs2_inode_info *oi = OCFS2_I(inode);
+	int ret = 0;
+
+	if (!(oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL))
+		return ret;
+
+	ret = ocfs2_xattr_check_inline_xh(inode, di, &header);
+	if (ret)
+		return ret;
+
 	ret = ocfs2_xattr_list_entries(inode, header, buffer, buffer_size);
 
 	return ret;
@@ -1200,8 +1212,9 @@ static int ocfs2_xattr_ibody_get(struct inode *inode,
 		return -ENODATA;
 
 	xs->end = (void *)di + inode->i_sb->s_blocksize;
-	xs->header = (struct ocfs2_xattr_header *)
-			(xs->end - le16_to_cpu(di->i_xattr_inline_size));
+	ret = ocfs2_xattr_check_inline_xh(inode, di, &xs->header);
+	if (ret)
+		return ret;
 	xs->base = (void *)xs->header;
 	xs->here = xs->header->xh_entries;
 
-- 
2.43.0