[PATCH] ocfs2: validate inline xattr header before listxattr walks it

ZhengYuan Huang <[email protected]>
Newsgroups dev.linux.lists.ocfs2-devel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
[BUG]
listxattr() can walk off the end of a dinode block when the inode-body
xattr header is corrupted:

BUG: KASAN: use-after-free in ocfs2_xattr_list_entry+0x1bd/0x370 fs/ocfs2/xattr.c:918
Read of size 13 at addr ffff88800ab5c0c0 by task syz.0.1231/3756

Call Trace:
 ...
 ocfs2_xattr_list_entry+0x1bd/0x370 fs/ocfs2/xattr.c:918
 ocfs2_xattr_list_entries+0x1e1/0x320 fs/ocfs2/xattr.c:938
 ocfs2_xattr_ibody_list fs/ocfs2/xattr.c:982 [inline]
 ocfs2_listxattr+0x4fb/0x980 fs/ocfs2/xattr.c:1044
 vfs_listxattr+0xb4/0x120 fs/xattr.c:493
 listxattr+0x76/0x170 fs/xattr.c:924
 filename_listxattr fs/xattr.c:958 [inline]
 path_listxattrat+0x137/0x320 fs/xattr.c:988
 __do_sys_listxattr fs/xattr.c:1001 [inline]
 ...

[CAUSE]
ocfs2_xattr_ibody_list() computes the inline xattr header from
di->i_xattr_inline_size and passes it straight to
ocfs2_xattr_list_entries(). If corruption inflates xh_count, the list
walk steps past the inline xattr area and eventually past the end of the
4K dinode block. The xe_type load in ocfs2_xattr_get_type() then reads
poisoned memory.

[FIX]
Validate di->i_xattr_inline_size before locating the header, then bound
xh_count by the number of ocfs2_xattr_entry records that fit inside the
claimed inline area. Reject corrupted metadata with ocfs2_error() and
-EFSCORRUPTED instead of iterating past the dinode block.

Signed-off-by: ZhengYuan Huang <[email protected]>
---
 fs/ocfs2/xattr.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@
 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;
+	u16 xattr_count;
+	size_t max_entries;
+	u16 inline_size;
 
 	if (!(oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL))
 		return ret;
 
+	inline_size = le16_to_cpu(di->i_xattr_inline_size);
+
+	if (inline_size > inode->i_sb->s_blocksize ||
+	    inline_size < sizeof(struct ocfs2_xattr_header)) {
+		ocfs2_error(inode->i_sb,
+			    "Invalid xattr inline size %u in inode %llu\n",
+			    inline_size,
+			    (unsigned long long)OCFS2_I(inode)->ip_blkno);
+		return -EFSCORRUPTED;
+	}
+
 	header = (struct ocfs2_xattr_header *)
 		 ((void *)di + inode->i_sb->s_blocksize -
-		 le16_to_cpu(di->i_xattr_inline_size));
+		 inline_size);
+
+	xattr_count = le16_to_cpu(header->xh_count);
+	max_entries = (inline_size - sizeof(struct ocfs2_xattr_header)) /
+		      sizeof(struct ocfs2_xattr_entry);
+	if (xattr_count > max_entries) {
+		ocfs2_error(inode->i_sb,
+			    "Invalid xattr entry count %u (max %zu) in inode %llu\n",
+			    xattr_count, max_entries,
+			    (unsigned long long)OCFS2_I(inode)->ip_blkno);
+		return -EFSCORRUPTED;
+	}
 
 	ret = ocfs2_xattr_list_entries(inode, header, buffer, buffer_size);
 
 	return ret;
 }
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.