[PATCH] ocfs2: validate local xattr value bounds on lookup
ZhengYuan Huang <[email protected]>
| Newsgroups | dev.linux.lists.ocfs2-devel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
[BUG] A corrupted local xattr entry can crash ACL lookup during open. BUG: KASAN: use-after-free in ocfs2_xattr_block_get fs/ocfs2/xattr.c:1261 [inline] BUG: KASAN: use-after-free in ocfs2_xattr_get_nolock+0x8cb/0x1110 fs/ocfs2/xattr.c:1312 Read of size 252 at addr ffff88801e7dbfb8 by task syz.0.1/347 Call Trace: ... ocfs2_xattr_block_get fs/ocfs2/xattr.c:1261 [inline] ocfs2_xattr_get_nolock+0x8cb/0x1110 fs/ocfs2/xattr.c:1312 ocfs2_get_acl_nolock+0xb1/0x3d0 fs/ocfs2/acl.c:137 ocfs2_iop_get_acl+0x197/0x260 fs/ocfs2/acl.c:313 __get_acl+0x33a/0x480 fs/posix_acl.c:159 get_inode_acl+0x2b/0x40 fs/posix_acl.c:184 check_acl fs/namei.c:333 [inline] acl_permission_check fs/namei.c:419 [inline] generic_permission+0x509/0x670 fs/namei.c:472 ocfs2_permission+0xeb/0x1a0 fs/ocfs2/file.c:1366 do_inode_permission fs/namei.c:526 [inline] inode_permission+0x317/0x5a0 fs/namei.c:593 may_open+0x10e/0x350 fs/namei.c:3565 do_open fs/namei.c:3973 [inline] path_openat+0x500/0x2ce0 fs/namei.c:4134 do_filp_open+0x1f6/0x430 fs/namei.c:4161 do_sys_openat2+0x117/0x1c0 fs/open.c:1437 do_sys_open fs/open.c:1452 [inline] __do_sys_open fs/open.c:1460 [inline] __se_sys_open fs/open.c:1456 [inline] __x64_sys_open+0x12d/0x1e0 fs/open.c:1456 ... [CAUSE] The lookup helpers only validate the entry position and the name bytes. Once a name matches, ocfs2_xattr_block_get() trusts xe_value_size and memcpy()s that many bytes from inline xattr storage. A local xattr entry is only valid when its inline value both fits the local size limit and stays inside the current xattr block or bucket block. That invariant is never checked for matched local entries, so corrupted metadata can inflate xe_value_size and drive memcpy() past the end of the source block. [FIX] Validate matched local xattr entries before returning them from ocfs2_xattr_find_entry() and ocfs2_find_xe_in_bucket(). Reject entries whose inline value exceeds the local limit or crosses the current block boundary, and route the failure through normal OCFS2 corruption handling. Signed-off-by: ZhengYuan Huang <[email protected]> --- diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -237,6 +237,29 @@ return namevalue_size(xe->xe_name_len, value_len); } +static int ocfs2_validate_local_xe(struct inode *inode, + struct ocfs2_xattr_entry *xe, + void *base, + void *end) +{ + char *nameval = base + le16_to_cpu(xe->xe_name_offset); + size_t name_len = OCFS2_XATTR_SIZE(xe->xe_name_len); + u64 value_size = le64_to_cpu(xe->xe_value_size); + + if (value_size > OCFS2_XATTR_INLINE_SIZE) + return ocfs2_error(inode->i_sb, + "Inode %llu has invalid local xattr value size %llu\n", + (unsigned long long)OCFS2_I(inode)->ip_blkno, + (unsigned long long)value_size); + + if (nameval + name_len + value_size > (char *)end) + return ocfs2_error(inode->i_sb, + "Inode %llu has corrupt local xattr value bounds\n", + (unsigned long long)OCFS2_I(inode)->ip_blkno); + + return 0; +} + static int ocfs2_xattr_bucket_get_name_value(struct super_block *sb, struct ocfs2_xattr_header *xh, @@ -1098,7 +1121,7 @@ { struct ocfs2_xattr_entry *entry; size_t name_len; - int i, name_offset, cmp = 1; + int i, name_offset, cmp = 1, ret; if (name == NULL) return -EINVAL; @@ -1121,6 +1144,11 @@ return -EFSCORRUPTED; } cmp = memcmp(name, (xs->base + name_offset), name_len); + if (!cmp && ocfs2_xattr_is_local(entry)) { + ret = ocfs2_validate_local_xe(inode, entry, xs->base, xs->end); + if (ret) + return ret; + } } if (cmp == 0) break; @@ -3790,6 +3818,8 @@ struct ocfs2_xattr_header *xh = bucket_xh(bucket); size_t name_len = strlen(name); struct ocfs2_xattr_entry *xe = NULL; + char *block; + char *block_end; char *xe_name; /* @@ -3821,8 +3851,15 @@ } - xe_name = bucket_block(bucket, block_off) + new_offset; + block = bucket_block(bucket, block_off); + block_end = block + inode->i_sb->s_blocksize; + xe_name = block + new_offset; if (!memcmp(name, xe_name, name_len)) { + if (ocfs2_xattr_is_local(xe)) { + ret = ocfs2_validate_local_xe(inode, xe, block, block_end); + if (ret) + break; + } *xe_index = i; *found = 1; ret = 0; -- 2.43.0