The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Ian Bridges <icb@fastmail.org>
To: Mark Fasheh <mark@fasheh.com>, Joel Becker <jlbec@evilplan.org>,
	Joseph Qi <joseph.qi@linux.alibaba.com>,
	ocfs2-devel@lists.linux.dev, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] ocfs2: fix missing metadata reservation for large xattrs
Date: Thu, 23 Jul 2026 10:46:04 -0500	[thread overview]
Message-ID: <amI3PMgVtPU_DVVT@dev> (raw)
In-Reply-To: <ahrxN7dbUaOqX9vT@dev>

On Sat, May 30, 2026 at 09:16:28AM -0500, Ian Bridges wrote:
> [BUG]
> lsetxattr() panics the kernel when setting a large xattr value on a
> fragmented filesystem where the file already has an external xattr
> block.
> 
> [CAUSE]
> ocfs2_calc_xattr_set_need() never reserves metadata blocks for a new
> xattr value's extent tree when the file already has an external xattr
> block. The not_found path leaves meta_add at zero, so meta_ac is NULL
> when ocfs2_xattr_extend_allocation() runs.
> 
> A new value root has room for a single extent record. On a fragmented
> filesystem, the allocator cannot satisfy the xattr value in one
> contiguous run, so each non-contiguous run requires its own extent
> record. When the value root's extent list is full and meta_ac is NULL,
> ocfs2_add_clusters_in_btree() returns RESTART_META, and
> ocfs2_xattr_extend_allocation() hits BUG_ON(why == RESTART_META).
> 
> [FIX]
> The case where no xattr block exists yet already calls
> ocfs2_extend_meta_needed(&def_xv.xv.xr_list) to reserve value tree
> metadata. Add the same reservation to the case where an xattr block
> already exists, making the two cases consistent.
> 
> Replace the BUG_ON with a -ENOSPC return so that if RESTART_META is
> returned despite the reservation, the error propagates to userspace
> instead of panicking the kernel.
> 
> Fixes: a78f9f466894 ("ocfs2: make xattr extension work with new local alloc reservation.")
> Reported-by: syzbot+e538032956b1157914a3@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=e538032956b1157914a3
> Signed-off-by: Ian Bridges <icb@fastmail.org>
> ---
> This patch contains a proposed fix for a crash reported by syzbot
> in ocfs2_xattr_value_truncate().
> 
> This is my first attempt at submitting a patch to the Linux kernel,
> so all feedback is appreciated.
> 
> The file names and offsets in this description are from commit
> 7cb1c5b32a2bfde961fff8d5204526b609bcb30a from this repo:
> git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
> 
> I also have a small test harness that reproduces the original panic,
> which I can make available as well.
> 
> The Bug
> 
> When a user sets an extended attribute (xattr) that requires more
> than 80 bytes of storage (OCFS2_XATTR_INLINE_SIZE,
> fs/ocfs2/xattr.c:80) on a file, OCFS2 adds a name-root
> (ocfs2_xattr_value_root) pair to the file's xattr storage area —
> either the inline area at the tail of the inode block or the
> external xattr block, depending on which has space. The xattr value
> is then stored in separate clusters on disk. The value root is the
> entry point for looking up which clusters hold the value.
> 
> Each new root is cloned from def_xv (fs/ocfs2/xattr.c:90). The
> def_xv template has its l_count member explicitly initialized to 1
> (fs/ocfs2/xattr.c:91). l_count = 1 means the embedded
> ocfs2_extent_list has room for exactly one extent record.
> l_tree_depth is implicitly set to 0.
> 
> OCFS2 will attempt to find a single run of contiguous free clusters
> to store the xattr value. If it cannot find such a run because the
> disk is fragmented, OCFS2 will store the xattr value across multiple
> non-contiguous runs of clusters.
> 
> The ocfs2_xattr_value_root embedded in the xattr entry is the root
> of a B-tree that tracks extent records. Each non-contiguous run of
> clusters requires its own extent record in this tree. Since l_count
> is 1, the root can only hold a single extent record initially. If
> the xattr value requires more than one extent record, the tree must
> grow, which requires allocating a new metadata block.
> 
> Before opening a transaction to allocate clusters for the xattr
> value, ocfs2_xattr_set() calls ocfs2_init_xattr_set_ctxt()
> (fs/ocfs2/xattr.c:3280), which calls ocfs2_calc_xattr_set_need()
> (fs/ocfs2/xattr.c:3068) to pre-calculate the number of clusters
> and metadata blocks the operation will need.
> 
> The root cause of the bug is a missing reservation in
> ocfs2_calc_xattr_set_need(). When adding a new large-value xattr to
> a file that already has an external xattr block, the function never
> adds anything to meta_add for the value tree, leaving it at 0. Here
> is a breakdown:
> 
>  0. ocfs2_calc_xattr_set_need() is called from
>     ocfs2_init_xattr_set_ctxt() (fs/ocfs2/xattr.c:3296).
>  1. The meta_add local is initialized to 0.
>  2. Because we are adding a new xattr, xis->not_found and
>     xbs->not_found are both -ENODATA. This means execution is
>     transferred to the meta_guess label (fs/ocfs2/xattr.c:3212)
>     with meta_add still set to 0.
>  3. The reproducer code only sets a few xattrs. The xattrs fill the
>     inode inline area and spill into the external xattr block, but
>     not enough to cause the block to be indexed. Since the block is
>     not indexed, we skip the incrementing of meta_add under the
>     meta_guess label (fs/ocfs2/xattr.c:3235), and meta_add remains
>     0.
>  4. The value of meta_add (still 0) is returned to
>     ocfs2_init_xattr_set_ctxt() through the meta_need parameter
>     (fs/ocfs2/xattr.c:3296).
>  5. extra_meta is 0 because the file is not a refcounted inode, so
>     meta_add in ocfs2_init_xattr_set_ctxt() remains 0
>     (fs/ocfs2/xattr.c:3303).
>  6. Because meta_add is 0, ocfs2_init_xattr_set_ctxt() skips the
>     metadata block reservation code
>     (ocfs2_reserve_new_metadata_blocks()) and meta_ac remains 0
>     (fs/ocfs2/xattr.c:3307).
>  7. Eventually, we end up in ocfs2_xattr_extend_allocation()
>     (fs/ocfs2/xattr.c:699) with the 0 meta_ac value having been
>     propagated into ctxt->meta_ac.
>  8. Due to disk fragmentation (which we purposefully cause in the
>     reproducer code), the xattr value we set must be split into two
>     non-contiguous clusters. This causes us to pass through the
>     allocation loop in ocfs2_xattr_extend_allocation() twice.
>  9. When ocfs2_add_clusters_in_btree() is called during the first
>     pass through the loop (fs/ocfs2/xattr.c:723), the root has one
>     free extent slot (l_count (1) - l_next_free_rec (0) = 1). The
>     extent record for the first cluster is inserted into that slot,
>     and l_next_free_rec is incremented to 1.
>  10. Since the entire xattr value did not fit in the first cluster,
>      why is set to RESTART_TRANS. This triggers another pass through
>      the allocation loop.
>  11. During the second pass through the loop, ctxt->meta_ac is
>      still 0. Now that there are no more free slots in the root's
>      ocfs2_extent_list (l_count (1) - l_next_free_rec (1) = 0),
>      ocfs2_add_clusters_in_btree() returns RESTART_META in why
>      (fs/ocfs2/alloc.c:4832).
>  12. We then hit the BUG_ON assertion (fs/ocfs2/xattr.c:747) and
>      panic.
> 
> The Proposed Fix
> 
> The proposed fix has two parts.
> 
> The first part adds the missing reservation in
> ocfs2_calc_xattr_set_need(). This change is derived from a similar
> pattern (fs/ocfs2/xattr.c:3260), which handles the case where no
> xattr block exists yet. This ensures meta_ac is not 0 when
> ocfs2_xattr_extend_allocation() runs.
> 
> The second part replaces the BUG_ON(why == RESTART_META) assertion
> (fs/ocfs2/xattr.c:747) with a -ENOSPC return. If RESTART_META is
> returned, the loop breaks and propagates -ENOSPC to userspace
> instead of panicking the kernel.
> 
>  fs/ocfs2/xattr.c | 18 ++++++++++++------
>  1 file changed, 12 insertions(+), 6 deletions(-)
> 
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index 86cfd4c2adf9..7eb8cce433de 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -740,12 +740,10 @@ static int ocfs2_xattr_extend_allocation(struct inode *inode,
>  					 prev_clusters;
>  
>  		if (why != RESTART_NONE && clusters_to_add) {
> -			/*
> -			 * We can only fail in case the alloc file doesn't give
> -			 * up enough clusters.
> -			 */
> -			BUG_ON(why == RESTART_META);
> -
> +			if (why == RESTART_META) {
> +				status = -ENOSPC;
> +				break;
> +			}
>  			credits = ocfs2_calc_extend_credits(inode->i_sb,
>  							    &vb->vb_xv->xr_list);
>  			status = ocfs2_extend_trans(handle, credits);
> @@ -3241,6 +3239,14 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode,
>  		} else
>  			credits += OCFS2_SUBALLOC_ALLOC + 1;
>  
> +		/*
> +		 * Reserve metadata for the new xattr's value extent tree.
> +		 * The not_found path above adds credits for this tree but
> +		 * omits meta_add, leaving meta_ac NULL for large values.
> +		 */
> +		if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE)
> +			meta_add += ocfs2_extend_meta_needed(&def_xv.xv.xr_list);
> +
>  		/*
>  		 * This cluster will be used either for new bucket or for
>  		 * new xattr block.
> -- 
> 2.47.3
>

Gentle ping. This fixes a syzbot reported kernel panic and has had no
response since May. The patch still applies cleanly to current mainline
and to mm-nonmm-unstable. Happy to resend if that is preferable.

Thanks,
Ian

  reply	other threads:[~2026-07-23 15:46 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-30 14:16 [PATCH] ocfs2: fix missing metadata reservation for large xattrs Ian Bridges
2026-07-23 15:46 ` Ian Bridges [this message]
2026-07-24  1:32   ` Joseph Qi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=amI3PMgVtPU_DVVT@dev \
    --to=icb@fastmail.org \
    --cc=jlbec@evilplan.org \
    --cc=joseph.qi@linux.alibaba.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mark@fasheh.com \
    --cc=ocfs2-devel@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox