mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Viacheslav Dubeyko <slava@dubeyko.com>
To: Nguyen Ngoc Thang <ngocthang2710.1999@gmail.com>
Cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>,
	Yangtao Li <frank.li@vivo.com>,
	linux-fsdevel@vger.kernel.org,  linux-kernel@vger.kernel.org,
	 syzbot+f8ce6c197125ab9d72ce@syzkaller.appspotmail.com
Subject: Re: [PATCH v4 1/2] hfsplus: fix recursive tree_lock in hfsplus_file_extend()
Date: Mon, 14 Sep 2026 12:26:03 -0700	[thread overview]
Message-ID: <d66b55454add2f2a48a6dabdb4569051bebb5d42.camel@dubeyko.com> (raw)
In-Reply-To: <20260912132407.16856-2-ngocthang2710.1999@gmail.com>

On Sat, 2026-09-12 at 20:24 +0700, Nguyen Ngoc Thang wrote:
> hfs_bmap_reserve() calls hfsplus_file_extend() on tree->inode with
> tree->tree_lock already held. For the extents overflow B-tree's own
> inode, growing it can call hfsplus_ext_read_extent() ->
> hfs_find_init()
> on that same tree, taking tree_lock a second time (lockdep: "possible
> recursive locking ... &tree->tree_lock/1"). This happens two ways:
> 
>  - the fork already claims more blocks than its eight extents
>    describe (a corrupted on-disk fork), so hfsplus_ext_read_extent()
>    is called immediately to look up the rest; or
>  - the fork's eight extents get exhausted during this call, and
>    inserting a new overflow extent record for the file would need
>    the same lookup.
> 
> Per the HFS+ format the extents overflow file is fully described by
> its eight fork extents and can never legitimately have overflow
> extents of its own, so both cases mean it cannot grow any further.
> 
> Move the check into hfsplus_ext_read_extent() itself, the one place
> that actually re-enters hfs_find_init(), rather than duplicating it
> at
> each caller, and report -ENOSPC.
> 
> For the second case, don't allocate blocks on the chance the fork
> still has room and undo it if not: hfsplus_ext_fork_full() tests the
> fork first. If it does have a free extent slot, any free space works,
> same as before. If it's already full, the only way to grow is a
> contiguous extension of the last extent, so only search for free
> space starting exactly at the block right after it, and fail with
> -ENOSPC immediately if that block isn't free -- nothing gets
> allocated in that case, so there's nothing to undo. The prior
> allocate-then-free-on-failure code stays at the insert_extent label
> as a backstop, in case this reasoning has a gap.
> 
> Reported-by: syzbot+f8ce6c197125ab9d72ce@syzkaller.appspotmail.com
> Signed-off-by: Nguyen Ngoc Thang <ngocthang2710.1999@gmail.com>
> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
> ---
>  fs/hfsplus/extents.c | 59 +++++++++++++++++++++++++++++++++++++++++-
> --
>  1 file changed, 55 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c
> index eb7c11524d18..236f2d9a7a2d 100644
> --- a/fs/hfsplus/extents.c
> +++ b/fs/hfsplus/extents.c
> @@ -84,6 +84,17 @@ static u32 hfsplus_ext_lastblock(struct
> hfsplus_extent *ext)
>  	return be32_to_cpu(ext->start_block) + be32_to_cpu(ext-
> >block_count);
>  }
>  
> +/* True if all eight extents of a fork are in use (no free slot
> left) */
> +static bool hfsplus_ext_fork_full(struct hfsplus_extent *ext)

static inline?

> +{
> +	int i;
> +
> +	for (i = 0; i < 8; ext++, i++)

I am introducing the special constant for the 8 extents of the fork in
HFS+ iomap patchset. How can we handle this? Because I would like to
see the named constant instead of hardcoded value.


> +		
> if (!ext->block_count)
> +			return false;
> +	return true;
> +}
> +
>  static int __hfsplus_ext_write_extent(struct inode *inode,
>  		struct hfs_find_data *fd)
>  {
> @@ -217,6 +228,15 @@ static int hfsplus_ext_read_extent(struct inode
> *inode, u32 block)
>  	    block < hip->cached_start + hip->cached_blocks)
>  		return 0;
>  
> +	/*
> +	 * The extents overflow file is fully described by its own
> fork
> +	 * extents; looking up an overflow extent for it would re-
> enter
> +	 * hfs_find_init() on the extents tree, whose tree_lock may
> already
> +	 * be held by the caller.
> +	 */

The comment is not fully correct. We should not be here for the case of
Extents Overflow file because there is no forks other than in
superblock. It's not about the lock issue. We simply should not be here
at all.

> +	if (inode->i_ino == HFSPLUS_EXT_CNID)

Maybe, we need to introduce something like is_extents_btree() method?
What do you think?

> +		return -ENOSPC;
> +
>  	res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd);
>  	if (!res) {
>  		res = __hfsplus_ext_cache_extent(&fd, inode, block);
> @@ -465,13 +485,30 @@ int hfsplus_file_extend(struct inode *inode,
> bool zeroout)
>  	}
>  
>  	len = hip->clump_blocks;
> -	start = hfsplus_block_allocate(sb, sbi->total_blocks, goal,
> &len);
> -	if (start >= sbi->total_blocks) {
> -		start = hfsplus_block_allocate(sb, goal, 0, &len);
> -		if (start >= goal) {
> +	if (inode->i_ino == HFSPLUS_EXT_CNID &&
> +	    hip->alloc_blocks == hip->first_blocks &&
> +	    hfsplus_ext_fork_full(hip->first_extents)) {

It looks like complicated condition and it deserves a static inline
function, from my point of view.

> +		/*
> +		 * No free slot is left in the fork, and the extents
> overflow
> +		 * file can't record an overflow extent of its own:
> the only
> +		 * way to grow it is a contiguous extension of the
> last
> +		 * extent, so only accept free space starting
> exactly at
> +		 * goal instead of allocating anywhere and having to
> undo it.
> +		 */

Maybe, instead of this long comment we need to introduce a dedicated
method for processing Extents Overflow file allocation case?

> +		start = hfsplus_block_allocate(sb, goal + 1, goal,
> &len);

Maybe, I am missing something here. But goal + 1 sounds like we request
to allocate only one block. Is it correct? If yes, why only one block?
Usually, we need to try to allocate a clumpSize.

> +		if (start != goal) {
>  			res = -ENOSPC;
>  			goto out;
>  		}
> +	} else {
> +		start = hfsplus_block_allocate(sb, sbi-
> >total_blocks, goal, &len);
> +		if (start >= sbi->total_blocks) {
> +			start = hfsplus_block_allocate(sb, goal, 0,
> &len);
> +			if (start >= goal) {
> +				res = -ENOSPC;
> +				goto out;
> +			}
> +		}
>  	}
>  
>  	if (zeroout) {
> @@ -526,6 +563,20 @@ int hfsplus_file_extend(struct inode *inode,
> bool zeroout)
>  	return res;
>  
>  insert_extent:
> +	/*
> +	 * The fork-full precheck above keeps the extents overflow
> file's
> +	 * own inode from ever landing here with blocks already
> allocated;
> +	 * this is a backstop, so still free what was allocated
> rather
> +	 * than leak it.
> +	 */
> +	if (inode->i_ino == HFSPLUS_EXT_CNID) {
> +		if (hfsplus_block_free(sb, start, len))

Can we be here at all? If start != goal, then we cannot allocate at
all. And we can be here only if we have empty slot it the fork. Am I
right?

Additional comment:

checkpatch.pl --strict flags one alignment style issue:
fs/hfsplus/extents.c:575: pr_err("can't free extent: start %u, count
%u\n",
                      start, len);
continuation should align with the open paren — cosmetic only

Thanks,
Slava.

> +			pr_err("can't free extent: start %u, count
> %u\n",
> +				start, len);
> +		res = -ENOSPC;
> +		goto out;
> +	}
> +
>  	hfs_dbg("insert new extent\n");
>  	res = hfsplus_ext_write_extent_locked(inode);
>  	if (res)

  reply	other threads:[~2026-09-14 19:26 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-06 15:49 [PATCH] " ThangNN99
2026-09-07 17:05 ` Viacheslav Dubeyko
2026-09-07 17:15   ` ThangNN99
2026-09-07 17:26     ` Viacheslav Dubeyko
2026-09-08 11:54       ` ThangNN99
2026-09-08 17:39         ` Viacheslav Dubeyko
2026-09-09 16:20           ` Nguyen Ngoc Thang
2026-09-09 18:36             ` Viacheslav Dubeyko
2026-09-10 16:01               ` Nguyen Ngoc Thang
2026-09-10 19:20                 ` Viacheslav Dubeyko
2026-09-11 11:46                   ` Nguyen Ngoc Thang
2026-09-11 18:26                     ` Viacheslav Dubeyko
2026-09-12 13:24                       ` [PATCH v4 0/2] hfsplus: fix the extents overflow file recursive tree_lock and its root cause Nguyen Ngoc Thang
2026-09-12 13:24                         ` [PATCH v4 1/2] hfsplus: fix recursive tree_lock in hfsplus_file_extend() Nguyen Ngoc Thang
2026-09-14 19:26                           ` Viacheslav Dubeyko [this message]
2026-09-15 14:07                             ` Nguyen Ngoc Thang
2026-09-15 23:43                               ` Viacheslav Dubeyko
2026-09-12 13:24                         ` [PATCH v4 2/2] hfsplus: validate b-tree fork extents at mount time Nguyen Ngoc Thang
2026-09-14 20:12                           ` Viacheslav Dubeyko
2026-09-15 14:15                             ` Nguyen Ngoc Thang
2026-09-15 23:48                               ` Viacheslav Dubeyko
2026-09-16 17:15                                 ` Viacheslav Dubeyko

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=d66b55454add2f2a48a6dabdb4569051bebb5d42.camel@dubeyko.com \
    --to=slava@dubeyko.com \
    --cc=frank.li@vivo.com \
    --cc=glaubitz@physik.fu-berlin.de \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ngocthang2710.1999@gmail.com \
    --cc=syzbot+f8ce6c197125ab9d72ce@syzkaller.appspotmail.com \
    /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

all inboxes | Powered by JetHome®