Re: [PATCH 0/2] ocfs2: validate xattr metadata bounds

Cen Zhang <[email protected]> Tue, 26 May 2026 11:17:37 +0800
Newsgroups dev.linux.lists.ocfs2-devel
Message-ID <CAB7XQsEjwRQgoxGjfEt2DJCDzPJip5zWv=f3aVzLqaRgw=0yvw@mail.gmail.com>
Hi,

Sorry, I noticed that the patch series I sent has some formatting
issues. This is my first time sending a patch series, and I made a
mistake.

Please disregard this version. I will fix the formatting and resend it
properly.

Sorry for the noise.

Best regards,
Zhang Cen

Zhang Cen <[email protected]> 于2026年5月26日周二 11:04写道:
>
> Hi,
>
> This small series rejects corrupted OCFS2 xattr metadata before the
> getxattr and listxattr paths walk filesystem-controlled inline, external
> block, or indexed bucket storage.
>
> Patch 1 validates the inline xattr header placement and entry count
> before ocfs2_xattr_ibody_get() parses it. Patch 2 builds on that helper
> and validates listxattr entry counts and name ranges against the storage
> that backs each walk.
>
> Both issues were reproduced with crafted OCFS2 images under KASAN.
>
> Zhang Cen (2):
>   ocfs2: validate inline xattr metadata in ocfs2_xattr_ibody_get
>   ocfs2: validate listxattr entry bounds
>
>  fs/ocfs2/xattr.c | 122 +++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 101 insertions(+), 21 deletions(-)
>
> --
> 2.43.0
>
> From 89ea62caaac9486558bf9cb933975d276a3e67c8 Mon Sep 17 00:00:00 2001
> From: Zhang Cen <[email protected]>
> Date: Sun, 24 May 2026 14:34:49 +0800
> Subject: [PATCH 1/2] ocfs2: validate inline xattr metadata in
>  ocfs2_xattr_ibody_get
>
> 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
>
>
> From 1ff5a1fde2b2ed5cfacf6c2205bb2f139795aa68 Mon Sep 17 00:00:00 2001
> From: Zhang Cen <[email protected]>
> Date: Tue, 26 May 2026 10:56:57 +0800
> Subject: [PATCH 2/2] ocfs2: validate listxattr entry bounds
>
> 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