[PATCH v2 2/2] ocfs2: validate listxattr entry bounds
Zhang Cen <[email protected]> Tue, 26 May 2026 11:40:40 +0800
| Newsgroups | dev.linux.lists.ocfs2-devel |
|---|---|
| Message-ID | <[email protected]> |
listxattr() trusts xh_count and the on-disk name offsets in the xattr
storage it walks. A corrupted inline xattr area, non-indexed xattr block
or indexed bucket can therefore push the walk past the entry array or
make it read name bytes from outside the local xattr storage.
Validate the entry count against the storage that actually backs the
walk before iterating it, and reject names that extend past that local
storage. For non-indexed external xattr blocks, use the bytes from
xb_attrs.xb_header to the end of the block, so the count check matches
the real xattr storage. For indexed buckets, validate xh_count against
the full 4K bucket storage and reject names that select an invalid
bucket block or cross the end of that block before bucket_block() is
used.
This makes listxattr() fail with -EFSCORRUPTED instead of walking
corrupted xattr metadata.
Validation reproduced this kernel report:
KASAN use-after-free in ocfs2_xattr_list_entries+0xd7/0x190
RIP: 0033:0x42086b
Read of size 1
Call trace:
dump_stack_lvl+0x66/0xa0 (?:?)
print_report+0xce/0x630 (?:?)
ocfs2_xattr_list_entries+0xd7/0x190 (fs/ocfs2/xattr.c:937)
srso_alias_return_thunk+0x5/0xfbef5 (?:?)
__virt_addr_valid+0x19f/0x330 (?:?)
kasan_report+0xe0/0x110 (?:?)
ocfs2_listxattr+0x3f6/0x610 (fs/ocfs2/xattr.c:1050)
vfs_listxattr+0x4c/0xa0 (?:?)
listxattr+0x90/0xe0 (?:?)
path_listxattrat+0xed/0x220 (?:?)
do_user_addr_fault+0x65a/0x890 (?:?)
do_syscall_64+0x115/0x6a0 (arch/x86/entry/syscall_64.c:87)
entry_SYSCALL_64_after_hwframe+0x77/0x7f (?:?)
Fixes: cf1d6c763fbc ("ocfs2: Add extended attribute support")
Fixes: 0c044f0b24b9 ("ocfs2: Add xattr bucket iteration for large numbers of EAs")
Assisted-by: Codex:gpt-5.5
Signed-off-by: Zhang Cen <[email protected]>
---
fs/ocfs2/xattr.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 69 insertions(+), 2 deletions(-)
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index eabfaa02d8b8..66f5f9da2b3c 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -925,14 +925,54 @@ static int ocfs2_xattr_list_entry(struct super_block *sb,
return 0;
}
+static int ocfs2_xattr_list_corrupted(struct inode *inode)
+{
+ ocfs2_error(inode->i_sb, "corrupted xattr entries in inode %llu",
+ (unsigned long long)OCFS2_I(inode)->ip_blkno);
+ return -EFSCORRUPTED;
+}
+
+static int ocfs2_validate_xattr_list_entries(struct inode *inode,
+ struct ocfs2_xattr_header *header,
+ size_t storage_size)
+{
+ u16 xattr_count = le16_to_cpu(header->xh_count);
+ size_t max_entries;
+ int i;
+
+ if (storage_size < sizeof(*header))
+ return ocfs2_xattr_list_corrupted(inode);
+
+ max_entries = (storage_size - sizeof(*header)) /
+ sizeof(struct ocfs2_xattr_entry);
+ if (xattr_count > max_entries)
+ return ocfs2_xattr_list_corrupted(inode);
+
+ for (i = 0; i < xattr_count; i++) {
+ struct ocfs2_xattr_entry *entry = &header->xh_entries[i];
+ size_t name_offset = le16_to_cpu(entry->xe_name_offset);
+
+ if (name_offset > storage_size ||
+ entry->xe_name_len > storage_size - name_offset)
+ return ocfs2_xattr_list_corrupted(inode);
+ }
+
+ return 0;
+}
+
static int ocfs2_xattr_list_entries(struct inode *inode,
struct ocfs2_xattr_header *header,
+ size_t storage_size,
char *buffer, size_t buffer_size)
{
size_t result = 0;
int i, type, ret;
const char *name;
+ ret = ocfs2_validate_xattr_list_entries(inode, header, storage_size);
+ if (ret)
+ return ret;
+
for (i = 0 ; i < le16_to_cpu(header->xh_count); i++) {
struct ocfs2_xattr_entry *entry = &header->xh_entries[i];
type = ocfs2_xattr_get_type(entry);
@@ -1013,15 +1053,18 @@ static int ocfs2_xattr_ibody_list(struct inode *inode,
struct ocfs2_xattr_header *header = NULL;
struct ocfs2_inode_info *oi = OCFS2_I(inode);
int ret = 0;
+ u16 inline_size;
if (!(oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL))
return ret;
+ inline_size = le16_to_cpu(di->i_xattr_inline_size);
ret = ocfs2_xattr_check_inline_xh(inode, di, &header);
if (ret)
return ret;
- ret = ocfs2_xattr_list_entries(inode, header, buffer, buffer_size);
+ ret = ocfs2_xattr_list_entries(inode, header, inline_size,
+ buffer, buffer_size);
return ret;
}
@@ -1033,6 +1076,7 @@ static int ocfs2_xattr_block_list(struct inode *inode,
{
struct buffer_head *blk_bh = NULL;
struct ocfs2_xattr_block *xb;
+ size_t storage_size;
int ret = 0;
if (!di->i_xattr_loc)
@@ -1048,7 +1092,10 @@ static int ocfs2_xattr_block_list(struct inode *inode,
xb = (struct ocfs2_xattr_block *)blk_bh->b_data;
if (!(le16_to_cpu(xb->xb_flags) & OCFS2_XATTR_INDEXED)) {
struct ocfs2_xattr_header *header = &xb->xb_attrs.xb_header;
+ storage_size = blk_bh->b_size -
+ offsetof(struct ocfs2_xattr_block, xb_attrs.xb_header);
ret = ocfs2_xattr_list_entries(inode, header,
+ storage_size,
buffer, buffer_size);
} else
ret = ocfs2_xattr_tree_list_index_block(inode, blk_bh,
@@ -4096,8 +4143,28 @@ static int ocfs2_list_xattr_bucket(struct inode *inode,
struct ocfs2_xattr_tree_list *xl = (struct ocfs2_xattr_tree_list *)para;
int i, block_off, new_offset;
const char *name;
+ size_t blocksize = inode->i_sb->s_blocksize;
+ u16 xattr_count = le16_to_cpu(bucket_xh(bucket)->xh_count);
+ size_t max_entries;
+
+ max_entries = (OCFS2_XATTR_BUCKET_SIZE -
+ sizeof(struct ocfs2_xattr_header)) /
+ sizeof(struct ocfs2_xattr_entry);
+ if (xattr_count > max_entries)
+ return ocfs2_xattr_list_corrupted(inode);
+
+ for (i = 0; i < xattr_count; i++) {
+ struct ocfs2_xattr_entry *entry = &bucket_xh(bucket)->xh_entries[i];
+ size_t name_offset = le16_to_cpu(entry->xe_name_offset);
+
+ block_off = name_offset >> inode->i_sb->s_blocksize_bits;
+ new_offset = name_offset % blocksize;
+ if (block_off >= bucket->bu_blocks ||
+ entry->xe_name_len > blocksize - new_offset)
+ return ocfs2_xattr_list_corrupted(inode);
+ }
- for (i = 0 ; i < le16_to_cpu(bucket_xh(bucket)->xh_count); i++) {
+ for (i = 0 ; i < xattr_count; i++) {
struct ocfs2_xattr_entry *entry = &bucket_xh(bucket)->xh_entries[i];
type = ocfs2_xattr_get_type(entry);
--
2.43.0