The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH 0/5] ocfs2: validate inline xattr header consumers
@ 2026-05-08  8:59 ZhengYuan Huang
  2026-05-08  8:59 ` [PATCH 1/5] ocfs2: validate inline xattr header before ibody lookups ZhengYuan Huang
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: ZhengYuan Huang @ 2026-05-08  8:59 UTC (permalink / raw)
  To: mark, jlbec, joseph.qi
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	ZhengYuan Huang

Corrupt i_xattr_inline_size can move the computed inode-body xattr header
outside the dinode block. Several OCFS2 paths then trust xh_count or
xattr entry geometry from that unchecked header.

The reported KASAN splat hits the ibody lookup path:

  BUG: KASAN: use-after-free in ocfs2_xattr_find_entry+0x37b/0x3a0
  ocfs2_xattr_ibody_get()
  ocfs2_xattr_get_nolock()
  ocfs2_calc_xattr_init()

The same unchecked header derivation also exists in the outside-value
probe, ibody remove, inline refcount attach, and inline reflink paths.

This series factors the existing ibody list validation into a shared
helper and then converts the remaining inline-header consumers one at a
time.

Patch layout:

1. validate ibody get/find and reuse the helper in ibody list
2. validate the outside-value probe
3. validate ibody remove
4. validate inline refcount attach
5. validate inline reflink

ZhengYuan Huang (5):
  ocfs2: validate inline xattr header before ibody lookups
  ocfs2: validate inline xattr header before checking outside values
  ocfs2: validate inline xattr header before ibody remove
  ocfs2: validate inline xattr header before inline refcount attach
  ocfs2: validate inline xattr header before reflinking inline xattrs

 fs/ocfs2/xattr.c | 123 ++++++++++++++++++++++++++++-------------------
 1 file changed, 73 insertions(+), 50 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/5] ocfs2: validate inline xattr header before ibody lookups
  2026-05-08  8:59 [PATCH 0/5] ocfs2: validate inline xattr header consumers ZhengYuan Huang
@ 2026-05-08  8:59 ` ZhengYuan Huang
  2026-05-11  6:26   ` Joseph Qi
  2026-05-08  8:59 ` [PATCH 2/5] ocfs2: validate inline xattr header before checking outside values ZhengYuan Huang
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: ZhengYuan Huang @ 2026-05-08  8:59 UTC (permalink / raw)
  To: mark, jlbec, joseph.qi
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	ZhengYuan Huang

[BUG]
mknodat() can read past the end of a dinode block when ACL inheritance
walks a corrupted inode-body xattr header. Another report shows the same
unchecked lookup later faulting in the VFS open path after create
returns a garbage status.

KASAN: use-after-free in
ocfs2_xattr_find_entry+0x37b/0x3a0 fs/ocfs2/xattr.c:1078
Read of size 2 at addr ffff88801c520300 by task syz.0.10/360

Trace:
 ...
 ocfs2_xattr_find_entry+0x37b/0x3a0 fs/ocfs2/xattr.c:1078
 ocfs2_xattr_ibody_get fs/ocfs2/xattr.c:1178 [inline]
 ocfs2_xattr_get_nolock+0x2ee/0x1110 fs/ocfs2/xattr.c:1309
 ocfs2_calc_xattr_init+0x716/0xac0 fs/ocfs2/xattr.c:628
 ocfs2_mknod+0x935/0x2400 fs/ocfs2/namei.c:333
 ocfs2_create+0x158/0x390 fs/ocfs2/namei.c:676
 vfs_create fs/namei.c:3493 [inline]
 vfs_create+0x445/0x6f0 fs/namei.c:3477
 do_mknodat+0x2d8/0x5e0 fs/namei.c:4372
 __do_sys_mknodat fs/namei.c:4400 [inline]
 __se_sys_mknodat fs/namei.c:4397 [inline]
 __x64_sys_mknodat+0xb6/0xf0 fs/namei.c:4397
 ...

Another report:
 BUG: unable to handle page fault for address: fffffbfff3e40ec0
 RIP: 0010:__d_entry_type include/linux/dcache.h:414 [inline]
 RIP: 0010:d_can_lookup include/linux/dcache.h:429 [inline]
 RIP: 0010:d_is_dir include/linux/dcache.h:439 [inline]
 RIP: 0010:path_openat+0xe2f/0x2ce0 fs/namei.c:4134

Trace:
 ...
 do_filp_open+0x1f6/0x430 fs/namei.c:4161
 do_sys_openat2+0x117/0x1c0 fs/open.c:1437
 __x64_sys_openat+0x15b/0x220 fs/open.c:1463
 ...

[CAUSE]
ocfs2_xattr_ibody_list() already validates the inline xattr size and
entry count, but ocfs2_xattr_ibody_get() and ocfs2_xattr_ibody_find()
still derive the inline header directly from di->i_xattr_inline_size and
then trust xh_count. A corrupted inline size or entry count can therefore
move the computed header outside the dinode block before get/find start
walking it. That can either make ocfs2_xattr_find_entry() dereference
xs->header->xh_count outside the block or make ocfs2_xattr_get_nolock()
bubble a garbage status back through ocfs2_calc_xattr_init() into the
create/open path.

[FIX]
Factor the existing ibody header geometry checks into a shared helper.
Use it in ocfs2_xattr_ibody_get() and ocfs2_xattr_ibody_find(), and have
ocfs2_xattr_ibody_list() reuse the same helper instead of open-coding
the validation. Reject corrupt ibody metadata with -EFSCORRUPTED before
the lookup path can walk bogus xattr geometry or return a garbage status.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
 fs/ocfs2/xattr.c | 82 +++++++++++++++++++++++++++---------------------
 1 file changed, 47 insertions(+), 35 deletions(-)

diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 86cfd4c2adf9..3a5a17cdcf7e 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -950,6 +950,41 @@ static int ocfs2_xattr_list_entries(struct inode *inode,
 	return result;
 }
 
+static int ocfs2_xattr_ibody_lookup_header(struct inode *inode,
+					   struct ocfs2_dinode *di,
+					   struct ocfs2_xattr_header **header)
+{
+	u16 xattr_count;
+	size_t max_entries;
+	u16 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 - 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,
+			    "xattr entry count %u exceeds maximum %zu in inode %llu\n",
+			    xattr_count, max_entries,
+			    (unsigned long long)OCFS2_I(inode)->ip_blkno);
+		return -EFSCORRUPTED;
+	}
+
+	return 0;
+}
+
 int ocfs2_has_inline_xattr_value_outside(struct inode *inode,
 					 struct ocfs2_dinode *di)
 {
@@ -975,39 +1010,13 @@ 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 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);
-
-	/* 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,
-			    "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 - 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,
-			    "xattr entry count %u exceeds maximum %zu in inode %llu\n",
-			    xattr_count, max_entries,
-			    (unsigned long long)OCFS2_I(inode)->ip_blkno);
-		return -EFSCORRUPTED;
-	}
+	ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
+	if (ret)
+		return ret;
 
 	ret = ocfs2_xattr_list_entries(inode, header, buffer, buffer_size);
 
@@ -1200,8 +1209,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_ibody_lookup_header(inode, di, &xs->header);
+	if (ret)
+		return ret;
 	xs->base = (void *)xs->header;
 	xs->here = xs->header->xh_entries;
 
@@ -2726,12 +2736,14 @@ static int ocfs2_xattr_ibody_find(struct inode *inode,
 
 	xs->xattr_bh = xs->inode_bh;
 	xs->end = (void *)di + inode->i_sb->s_blocksize;
-	if (oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL)
-		xs->header = (struct ocfs2_xattr_header *)
-			(xs->end - le16_to_cpu(di->i_xattr_inline_size));
-	else
+	if (oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL) {
+		ret = ocfs2_xattr_ibody_lookup_header(inode, di, &xs->header);
+		if (ret)
+			return ret;
+	} else {
 		xs->header = (struct ocfs2_xattr_header *)
 			(xs->end - OCFS2_SB(inode->i_sb)->s_xattr_inline_size);
+	}
 	xs->base = (void *)xs->header;
 	xs->here = xs->header->xh_entries;
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/5] ocfs2: validate inline xattr header before checking outside values
  2026-05-08  8:59 [PATCH 0/5] ocfs2: validate inline xattr header consumers ZhengYuan Huang
  2026-05-08  8:59 ` [PATCH 1/5] ocfs2: validate inline xattr header before ibody lookups ZhengYuan Huang
@ 2026-05-08  8:59 ` ZhengYuan Huang
  2026-05-11  6:30   ` Joseph Qi
  2026-05-08  8:59 ` [PATCH 3/5] ocfs2: validate inline xattr header before ibody remove ZhengYuan Huang
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: ZhengYuan Huang @ 2026-05-08  8:59 UTC (permalink / raw)
  To: mark, jlbec, joseph.qi
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	ZhengYuan Huang

[BUG]
A corrupt inline xattr header can make
ocfs2_has_inline_xattr_value_outside() walk xh_count from an unchecked
header while refcount-tree teardown decides whether inline xattrs still
point outside the inode body.

[CAUSE]
ocfs2_has_inline_xattr_value_outside() still computed the inline header
directly from di->i_xattr_inline_size and immediately iterated xh_count.
That is the same unchecked metadata boundary as the ibody lookup bug.

[FIX]
Reuse the shared inline-header helper before iterating xh_count. Because
this helper returns a boolean-style answer to its caller, treat a corrupt
header conservatively as "has outside values" instead of walking it.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
 fs/ocfs2/xattr.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 3a5a17cdcf7e..05f6f0a886cf 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -989,11 +989,12 @@ int ocfs2_has_inline_xattr_value_outside(struct inode *inode,
 					 struct ocfs2_dinode *di)
 {
 	struct ocfs2_xattr_header *xh;
+	int ret;
 	int i;
 
-	xh = (struct ocfs2_xattr_header *)
-		 ((void *)di + inode->i_sb->s_blocksize -
-		 le16_to_cpu(di->i_xattr_inline_size));
+	ret = ocfs2_xattr_ibody_lookup_header(inode, di, &xh);
+	if (ret)
+		return 1;
 
 	for (i = 0; i < le16_to_cpu(xh->xh_count); i++)
 		if (!ocfs2_xattr_is_local(&xh->xh_entries[i]))
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/5] ocfs2: validate inline xattr header before ibody remove
  2026-05-08  8:59 [PATCH 0/5] ocfs2: validate inline xattr header consumers ZhengYuan Huang
  2026-05-08  8:59 ` [PATCH 1/5] ocfs2: validate inline xattr header before ibody lookups ZhengYuan Huang
  2026-05-08  8:59 ` [PATCH 2/5] ocfs2: validate inline xattr header before checking outside values ZhengYuan Huang
@ 2026-05-08  8:59 ` ZhengYuan Huang
  2026-05-11  6:32   ` Joseph Qi
  2026-05-08  8:59 ` [PATCH 4/5] ocfs2: validate inline xattr header before inline refcount attach ZhengYuan Huang
  2026-05-08  8:59 ` [PATCH 5/5] ocfs2: validate inline xattr header before reflinking inline xattrs ZhengYuan Huang
  4 siblings, 1 reply; 11+ messages in thread
From: ZhengYuan Huang @ 2026-05-08  8:59 UTC (permalink / raw)
  To: mark, jlbec, joseph.qi
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	ZhengYuan Huang

[BUG]
A corrupt inline xattr header can make ocfs2_xattr_ibody_remove() pass an
unchecked header into ocfs2_remove_value_outside() during inode xattr
teardown.

[CAUSE]
ocfs2_xattr_ibody_remove() still rebuilt the ibody xattr header directly
from di->i_xattr_inline_size and then handed it to code that iterates
xh_count and entry geometry.

[FIX]
Validate the inline xattr header with the shared helper before handing it
to the outside-value removal path, and propagate -EFSCORRUPTED on bad
metadata instead of traversing the unchecked header.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
 fs/ocfs2/xattr.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 05f6f0a886cf..bbb25a01b097 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -2476,9 +2476,9 @@ static int ocfs2_xattr_ibody_remove(struct inode *inode,
 		.vb_access = ocfs2_journal_access_di,
 	};
 
-	header = (struct ocfs2_xattr_header *)
-		 ((void *)di + inode->i_sb->s_blocksize -
-		 le16_to_cpu(di->i_xattr_inline_size));
+	ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
+	if (ret)
+		return ret;
 
 	ret = ocfs2_remove_value_outside(inode, &vb, header,
 					 ref_ci, ref_root_bh);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/5] ocfs2: validate inline xattr header before inline refcount attach
  2026-05-08  8:59 [PATCH 0/5] ocfs2: validate inline xattr header consumers ZhengYuan Huang
                   ` (2 preceding siblings ...)
  2026-05-08  8:59 ` [PATCH 3/5] ocfs2: validate inline xattr header before ibody remove ZhengYuan Huang
@ 2026-05-08  8:59 ` ZhengYuan Huang
  2026-05-11  6:33   ` Joseph Qi
  2026-05-08  8:59 ` [PATCH 5/5] ocfs2: validate inline xattr header before reflinking inline xattrs ZhengYuan Huang
  4 siblings, 1 reply; 11+ messages in thread
From: ZhengYuan Huang @ 2026-05-08  8:59 UTC (permalink / raw)
  To: mark, jlbec, joseph.qi
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	ZhengYuan Huang

[BUG]
A corrupt inline xattr header can make ocfs2_xattr_inline_attach_refcount()
feed an unchecked header into the refcount-attachment walk for inline
xattr values.

[CAUSE]
The inline refcount-attach path still derived the header directly from
di->i_xattr_inline_size and then passed it to code that iterates xh_count
and xattr entries.

[FIX]
Use the shared ibody header helper before attaching refcounts to inline
xattr values so corrupt header geometry is rejected with -EFSCORRUPTED
instead of being traversed.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
 fs/ocfs2/xattr.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index bbb25a01b097..4877406a83ce 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -6016,14 +6016,17 @@ static int ocfs2_xattr_inline_attach_refcount(struct inode *inode,
 				struct ocfs2_cached_dealloc_ctxt *dealloc)
 {
 	struct ocfs2_dinode *di = (struct ocfs2_dinode *)fe_bh->b_data;
-	struct ocfs2_xattr_header *header = (struct ocfs2_xattr_header *)
-				(fe_bh->b_data + inode->i_sb->s_blocksize -
-				le16_to_cpu(di->i_xattr_inline_size));
+	struct ocfs2_xattr_header *header;
+	int ret;
 	struct ocfs2_xattr_value_buf vb = {
 		.vb_bh = fe_bh,
 		.vb_access = ocfs2_journal_access_di,
 	};
 
+	ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
+	if (ret)
+		return ret;
+
 	return ocfs2_xattr_attach_refcount_normal(inode, &vb, header,
 						  ref_ci, ref_root_bh, dealloc);
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 5/5] ocfs2: validate inline xattr header before reflinking inline xattrs
  2026-05-08  8:59 [PATCH 0/5] ocfs2: validate inline xattr header consumers ZhengYuan Huang
                   ` (3 preceding siblings ...)
  2026-05-08  8:59 ` [PATCH 4/5] ocfs2: validate inline xattr header before inline refcount attach ZhengYuan Huang
@ 2026-05-08  8:59 ` ZhengYuan Huang
  2026-05-11  6:35   ` Joseph Qi
  4 siblings, 1 reply; 11+ messages in thread
From: ZhengYuan Huang @ 2026-05-08  8:59 UTC (permalink / raw)
  To: mark, jlbec, joseph.qi
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	ZhengYuan Huang

[BUG]
A corrupt inline xattr header can make ocfs2_reflink_xattr_inline() lock,
copy, and reflink xattr state from an unchecked ibody xattr header.

[CAUSE]
The inline reflink path still trusted di->i_xattr_inline_size to compute
header_off, xh, and new_xh before handing the source header to the reflink
allocator and copy logic.

[FIX]
Validate the source inode's inline xattr header with the shared helper
first, then derive the reflink copy offsets from the validated inline
size/header. This keeps the reflink path from traversing corrupt ibody
xattr geometry.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
 fs/ocfs2/xattr.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 4877406a83ce..fcddd3c13acd 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -6511,12 +6511,10 @@ static int ocfs2_reflink_xattr_inline(struct ocfs2_xattr_reflink *args)
 	handle_t *handle;
 	struct ocfs2_super *osb = OCFS2_SB(args->old_inode->i_sb);
 	struct ocfs2_dinode *di = (struct ocfs2_dinode *)args->old_bh->b_data;
-	int inline_size = le16_to_cpu(di->i_xattr_inline_size);
-	int header_off = osb->sb->s_blocksize - inline_size;
-	struct ocfs2_xattr_header *xh = (struct ocfs2_xattr_header *)
-					(args->old_bh->b_data + header_off);
-	struct ocfs2_xattr_header *new_xh = (struct ocfs2_xattr_header *)
-					(args->new_bh->b_data + header_off);
+	int inline_size;
+	int header_off;
+	struct ocfs2_xattr_header *xh;
+	struct ocfs2_xattr_header *new_xh;
 	struct ocfs2_alloc_context *meta_ac = NULL;
 	struct ocfs2_inode_info *new_oi;
 	struct ocfs2_dinode *new_di;
@@ -6525,6 +6523,15 @@ static int ocfs2_reflink_xattr_inline(struct ocfs2_xattr_reflink *args)
 		.vb_access = ocfs2_journal_access_di,
 	};
 
+	ret = ocfs2_xattr_ibody_lookup_header(args->old_inode, di, &xh);
+	if (ret)
+		goto out;
+
+	inline_size = le16_to_cpu(di->i_xattr_inline_size);
+	header_off = osb->sb->s_blocksize - inline_size;
+	new_xh = (struct ocfs2_xattr_header *)
+		(args->new_bh->b_data + header_off);
+
 	ret = ocfs2_reflink_lock_xattr_allocators(osb, xh, args->ref_root_bh,
 						  &credits, &meta_ac);
 	if (ret) {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/5] ocfs2: validate inline xattr header before ibody lookups
  2026-05-08  8:59 ` [PATCH 1/5] ocfs2: validate inline xattr header before ibody lookups ZhengYuan Huang
@ 2026-05-11  6:26   ` Joseph Qi
  0 siblings, 0 replies; 11+ messages in thread
From: Joseph Qi @ 2026-05-11  6:26 UTC (permalink / raw)
  To: ZhengYuan Huang, akpm
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	Mark Fasheh, Joel Becker



On 5/8/26 4:59 PM, ZhengYuan Huang wrote:
> [BUG]
> mknodat() can read past the end of a dinode block when ACL inheritance
> walks a corrupted inode-body xattr header. Another report shows the same
> unchecked lookup later faulting in the VFS open path after create
> returns a garbage status.
> 
> KASAN: use-after-free in
> ocfs2_xattr_find_entry+0x37b/0x3a0 fs/ocfs2/xattr.c:1078
> Read of size 2 at addr ffff88801c520300 by task syz.0.10/360
> 
> Trace:
>  ...
>  ocfs2_xattr_find_entry+0x37b/0x3a0 fs/ocfs2/xattr.c:1078
>  ocfs2_xattr_ibody_get fs/ocfs2/xattr.c:1178 [inline]
>  ocfs2_xattr_get_nolock+0x2ee/0x1110 fs/ocfs2/xattr.c:1309
>  ocfs2_calc_xattr_init+0x716/0xac0 fs/ocfs2/xattr.c:628
>  ocfs2_mknod+0x935/0x2400 fs/ocfs2/namei.c:333
>  ocfs2_create+0x158/0x390 fs/ocfs2/namei.c:676
>  vfs_create fs/namei.c:3493 [inline]
>  vfs_create+0x445/0x6f0 fs/namei.c:3477
>  do_mknodat+0x2d8/0x5e0 fs/namei.c:4372
>  __do_sys_mknodat fs/namei.c:4400 [inline]
>  __se_sys_mknodat fs/namei.c:4397 [inline]
>  __x64_sys_mknodat+0xb6/0xf0 fs/namei.c:4397
>  ...
> 
> Another report:
>  BUG: unable to handle page fault for address: fffffbfff3e40ec0
>  RIP: 0010:__d_entry_type include/linux/dcache.h:414 [inline]
>  RIP: 0010:d_can_lookup include/linux/dcache.h:429 [inline]
>  RIP: 0010:d_is_dir include/linux/dcache.h:439 [inline]
>  RIP: 0010:path_openat+0xe2f/0x2ce0 fs/namei.c:4134
> 
> Trace:
>  ...
>  do_filp_open+0x1f6/0x430 fs/namei.c:4161
>  do_sys_openat2+0x117/0x1c0 fs/open.c:1437
>  __x64_sys_openat+0x15b/0x220 fs/open.c:1463
>  ...
> 
> [CAUSE]
> ocfs2_xattr_ibody_list() already validates the inline xattr size and
> entry count, but ocfs2_xattr_ibody_get() and ocfs2_xattr_ibody_find()
> still derive the inline header directly from di->i_xattr_inline_size and
> then trust xh_count. A corrupted inline size or entry count can therefore
> move the computed header outside the dinode block before get/find start
> walking it. That can either make ocfs2_xattr_find_entry() dereference
> xs->header->xh_count outside the block or make ocfs2_xattr_get_nolock()
> bubble a garbage status back through ocfs2_calc_xattr_init() into the
> create/open path.
> 
> [FIX]
> Factor the existing ibody header geometry checks into a shared helper.
> Use it in ocfs2_xattr_ibody_get() and ocfs2_xattr_ibody_find(), and have
> ocfs2_xattr_ibody_list() reuse the same helper instead of open-coding
> the validation. Reject corrupt ibody metadata with -EFSCORRUPTED before
> the lookup path can walk bogus xattr geometry or return a garbage status.
> 
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>

Looks fine.
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>

> ---
>  fs/ocfs2/xattr.c | 82 +++++++++++++++++++++++++++---------------------
>  1 file changed, 47 insertions(+), 35 deletions(-)
> 
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index 86cfd4c2adf9..3a5a17cdcf7e 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -950,6 +950,41 @@ static int ocfs2_xattr_list_entries(struct inode *inode,
>  	return result;
>  }
>  
> +static int ocfs2_xattr_ibody_lookup_header(struct inode *inode,
> +					   struct ocfs2_dinode *di,
> +					   struct ocfs2_xattr_header **header)
> +{
> +	u16 xattr_count;
> +	size_t max_entries;
> +	u16 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 - 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,
> +			    "xattr entry count %u exceeds maximum %zu in inode %llu\n",
> +			    xattr_count, max_entries,
> +			    (unsigned long long)OCFS2_I(inode)->ip_blkno);
> +		return -EFSCORRUPTED;
> +	}
> +
> +	return 0;
> +}
> +
>  int ocfs2_has_inline_xattr_value_outside(struct inode *inode,
>  					 struct ocfs2_dinode *di)
>  {
> @@ -975,39 +1010,13 @@ 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 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);
> -
> -	/* 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,
> -			    "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 - 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,
> -			    "xattr entry count %u exceeds maximum %zu in inode %llu\n",
> -			    xattr_count, max_entries,
> -			    (unsigned long long)OCFS2_I(inode)->ip_blkno);
> -		return -EFSCORRUPTED;
> -	}
> +	ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
> +	if (ret)
> +		return ret;
>  
>  	ret = ocfs2_xattr_list_entries(inode, header, buffer, buffer_size);
>  
> @@ -1200,8 +1209,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_ibody_lookup_header(inode, di, &xs->header);
> +	if (ret)
> +		return ret;
>  	xs->base = (void *)xs->header;
>  	xs->here = xs->header->xh_entries;
>  
> @@ -2726,12 +2736,14 @@ static int ocfs2_xattr_ibody_find(struct inode *inode,
>  
>  	xs->xattr_bh = xs->inode_bh;
>  	xs->end = (void *)di + inode->i_sb->s_blocksize;
> -	if (oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL)
> -		xs->header = (struct ocfs2_xattr_header *)
> -			(xs->end - le16_to_cpu(di->i_xattr_inline_size));
> -	else
> +	if (oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL) {
> +		ret = ocfs2_xattr_ibody_lookup_header(inode, di, &xs->header);
> +		if (ret)
> +			return ret;
> +	} else {
>  		xs->header = (struct ocfs2_xattr_header *)
>  			(xs->end - OCFS2_SB(inode->i_sb)->s_xattr_inline_size);
> +	}
>  	xs->base = (void *)xs->header;
>  	xs->here = xs->header->xh_entries;
>  


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 2/5] ocfs2: validate inline xattr header before checking outside values
  2026-05-08  8:59 ` [PATCH 2/5] ocfs2: validate inline xattr header before checking outside values ZhengYuan Huang
@ 2026-05-11  6:30   ` Joseph Qi
  0 siblings, 0 replies; 11+ messages in thread
From: Joseph Qi @ 2026-05-11  6:30 UTC (permalink / raw)
  To: ZhengYuan Huang, akpm
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	Mark Fasheh, Joel Becker



On 5/8/26 4:59 PM, ZhengYuan Huang wrote:
> [BUG]
> A corrupt inline xattr header can make
> ocfs2_has_inline_xattr_value_outside() walk xh_count from an unchecked
> header while refcount-tree teardown decides whether inline xattrs still
> point outside the inode body.
> 
> [CAUSE]
> ocfs2_has_inline_xattr_value_outside() still computed the inline header
> directly from di->i_xattr_inline_size and immediately iterated xh_count.
> That is the same unchecked metadata boundary as the ibody lookup bug.
> 
> [FIX]
> Reuse the shared inline-header helper before iterating xh_count. Because
> this helper returns a boolean-style answer to its caller, treat a corrupt
> header conservatively as "has outside values" instead of walking it.
> 
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>

Looks fine.
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> ---
>  fs/ocfs2/xattr.c | 7 ++++---
>  1 file changed, 4 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index 3a5a17cdcf7e..05f6f0a886cf 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -989,11 +989,12 @@ int ocfs2_has_inline_xattr_value_outside(struct inode *inode,
>  					 struct ocfs2_dinode *di)
>  {
>  	struct ocfs2_xattr_header *xh;
> +	int ret;
>  	int i;
>  
> -	xh = (struct ocfs2_xattr_header *)
> -		 ((void *)di + inode->i_sb->s_blocksize -
> -		 le16_to_cpu(di->i_xattr_inline_size));
> +	ret = ocfs2_xattr_ibody_lookup_header(inode, di, &xh);
> +	if (ret)
> +		return 1;
>  
>  	for (i = 0; i < le16_to_cpu(xh->xh_count); i++)
>  		if (!ocfs2_xattr_is_local(&xh->xh_entries[i]))


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 3/5] ocfs2: validate inline xattr header before ibody remove
  2026-05-08  8:59 ` [PATCH 3/5] ocfs2: validate inline xattr header before ibody remove ZhengYuan Huang
@ 2026-05-11  6:32   ` Joseph Qi
  0 siblings, 0 replies; 11+ messages in thread
From: Joseph Qi @ 2026-05-11  6:32 UTC (permalink / raw)
  To: ZhengYuan Huang, akpm
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	Mark Fasheh, Joel Becker



On 5/8/26 4:59 PM, ZhengYuan Huang wrote:
> [BUG]
> A corrupt inline xattr header can make ocfs2_xattr_ibody_remove() pass an
> unchecked header into ocfs2_remove_value_outside() during inode xattr
> teardown.
> 
> [CAUSE]
> ocfs2_xattr_ibody_remove() still rebuilt the ibody xattr header directly
> from di->i_xattr_inline_size and then handed it to code that iterates
> xh_count and entry geometry.
> 
> [FIX]
> Validate the inline xattr header with the shared helper before handing it
> to the outside-value removal path, and propagate -EFSCORRUPTED on bad
> metadata instead of traversing the unchecked header.
> 
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>

Looks fine.
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> ---
>  fs/ocfs2/xattr.c | 6 +++---
>  1 file changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index 05f6f0a886cf..bbb25a01b097 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -2476,9 +2476,9 @@ static int ocfs2_xattr_ibody_remove(struct inode *inode,
>  		.vb_access = ocfs2_journal_access_di,
>  	};
>  
> -	header = (struct ocfs2_xattr_header *)
> -		 ((void *)di + inode->i_sb->s_blocksize -
> -		 le16_to_cpu(di->i_xattr_inline_size));
> +	ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
> +	if (ret)
> +		return ret;
>  
>  	ret = ocfs2_remove_value_outside(inode, &vb, header,
>  					 ref_ci, ref_root_bh);


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 4/5] ocfs2: validate inline xattr header before inline refcount attach
  2026-05-08  8:59 ` [PATCH 4/5] ocfs2: validate inline xattr header before inline refcount attach ZhengYuan Huang
@ 2026-05-11  6:33   ` Joseph Qi
  0 siblings, 0 replies; 11+ messages in thread
From: Joseph Qi @ 2026-05-11  6:33 UTC (permalink / raw)
  To: ZhengYuan Huang, akpm
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	Mark Fasheh, Joel Becker



On 5/8/26 4:59 PM, ZhengYuan Huang wrote:
> [BUG]
> A corrupt inline xattr header can make ocfs2_xattr_inline_attach_refcount()
> feed an unchecked header into the refcount-attachment walk for inline
> xattr values.
> 
> [CAUSE]
> The inline refcount-attach path still derived the header directly from
> di->i_xattr_inline_size and then passed it to code that iterates xh_count
> and xattr entries.
> 
> [FIX]
> Use the shared ibody header helper before attaching refcounts to inline
> xattr values so corrupt header geometry is rejected with -EFSCORRUPTED
> instead of being traversed.
> 
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>

Looks fine.
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> ---
>  fs/ocfs2/xattr.c | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index bbb25a01b097..4877406a83ce 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -6016,14 +6016,17 @@ static int ocfs2_xattr_inline_attach_refcount(struct inode *inode,
>  				struct ocfs2_cached_dealloc_ctxt *dealloc)
>  {
>  	struct ocfs2_dinode *di = (struct ocfs2_dinode *)fe_bh->b_data;
> -	struct ocfs2_xattr_header *header = (struct ocfs2_xattr_header *)
> -				(fe_bh->b_data + inode->i_sb->s_blocksize -
> -				le16_to_cpu(di->i_xattr_inline_size));
> +	struct ocfs2_xattr_header *header;
> +	int ret;
>  	struct ocfs2_xattr_value_buf vb = {
>  		.vb_bh = fe_bh,
>  		.vb_access = ocfs2_journal_access_di,
>  	};
>  
> +	ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
> +	if (ret)
> +		return ret;
> +
>  	return ocfs2_xattr_attach_refcount_normal(inode, &vb, header,
>  						  ref_ci, ref_root_bh, dealloc);
>  }


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 5/5] ocfs2: validate inline xattr header before reflinking inline xattrs
  2026-05-08  8:59 ` [PATCH 5/5] ocfs2: validate inline xattr header before reflinking inline xattrs ZhengYuan Huang
@ 2026-05-11  6:35   ` Joseph Qi
  0 siblings, 0 replies; 11+ messages in thread
From: Joseph Qi @ 2026-05-11  6:35 UTC (permalink / raw)
  To: ZhengYuan Huang, akpm
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	Mark Fasheh, Joel Becker



On 5/8/26 4:59 PM, ZhengYuan Huang wrote:
> [BUG]
> A corrupt inline xattr header can make ocfs2_reflink_xattr_inline() lock,
> copy, and reflink xattr state from an unchecked ibody xattr header.
> 
> [CAUSE]
> The inline reflink path still trusted di->i_xattr_inline_size to compute
> header_off, xh, and new_xh before handing the source header to the reflink
> allocator and copy logic.
> 
> [FIX]
> Validate the source inode's inline xattr header with the shared helper
> first, then derive the reflink copy offsets from the validated inline
> size/header. This keeps the reflink path from traversing corrupt ibody
> xattr geometry.
> 
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>

Looks fine.
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>

> ---
>  fs/ocfs2/xattr.c | 19 +++++++++++++------
>  1 file changed, 13 insertions(+), 6 deletions(-)
> 
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index 4877406a83ce..fcddd3c13acd 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -6511,12 +6511,10 @@ static int ocfs2_reflink_xattr_inline(struct ocfs2_xattr_reflink *args)
>  	handle_t *handle;
>  	struct ocfs2_super *osb = OCFS2_SB(args->old_inode->i_sb);
>  	struct ocfs2_dinode *di = (struct ocfs2_dinode *)args->old_bh->b_data;
> -	int inline_size = le16_to_cpu(di->i_xattr_inline_size);
> -	int header_off = osb->sb->s_blocksize - inline_size;
> -	struct ocfs2_xattr_header *xh = (struct ocfs2_xattr_header *)
> -					(args->old_bh->b_data + header_off);
> -	struct ocfs2_xattr_header *new_xh = (struct ocfs2_xattr_header *)
> -					(args->new_bh->b_data + header_off);
> +	int inline_size;
> +	int header_off;
> +	struct ocfs2_xattr_header *xh;
> +	struct ocfs2_xattr_header *new_xh;
>  	struct ocfs2_alloc_context *meta_ac = NULL;
>  	struct ocfs2_inode_info *new_oi;
>  	struct ocfs2_dinode *new_di;
> @@ -6525,6 +6523,15 @@ static int ocfs2_reflink_xattr_inline(struct ocfs2_xattr_reflink *args)
>  		.vb_access = ocfs2_journal_access_di,
>  	};
>  
> +	ret = ocfs2_xattr_ibody_lookup_header(args->old_inode, di, &xh);
> +	if (ret)
> +		goto out;
> +
> +	inline_size = le16_to_cpu(di->i_xattr_inline_size);
> +	header_off = osb->sb->s_blocksize - inline_size;
> +	new_xh = (struct ocfs2_xattr_header *)
> +		(args->new_bh->b_data + header_off);
> +
>  	ret = ocfs2_reflink_lock_xattr_allocators(osb, xh, args->ref_root_bh,
>  						  &credits, &meta_ac);
>  	if (ret) {


^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-05-11  6:35 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-08  8:59 [PATCH 0/5] ocfs2: validate inline xattr header consumers ZhengYuan Huang
2026-05-08  8:59 ` [PATCH 1/5] ocfs2: validate inline xattr header before ibody lookups ZhengYuan Huang
2026-05-11  6:26   ` Joseph Qi
2026-05-08  8:59 ` [PATCH 2/5] ocfs2: validate inline xattr header before checking outside values ZhengYuan Huang
2026-05-11  6:30   ` Joseph Qi
2026-05-08  8:59 ` [PATCH 3/5] ocfs2: validate inline xattr header before ibody remove ZhengYuan Huang
2026-05-11  6:32   ` Joseph Qi
2026-05-08  8:59 ` [PATCH 4/5] ocfs2: validate inline xattr header before inline refcount attach ZhengYuan Huang
2026-05-11  6:33   ` Joseph Qi
2026-05-08  8:59 ` [PATCH 5/5] ocfs2: validate inline xattr header before reflinking inline xattrs ZhengYuan Huang
2026-05-11  6:35   ` Joseph Qi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox