Linux filesystem development
 help / color / mirror / Atom feed
From: ThangNN99 <ngocthang2710.1999@gmail.com>
To: Viacheslav Dubeyko <slava@dubeyko.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] hfsplus: fix recursive tree_lock in hfsplus_file_extend()
Date: Tue,  8 Sep 2026 00:15:43 +0700	[thread overview]
Message-ID: <20260907171543.8175-1-ngocthang2710.1999@gmail.com> (raw)
In-Reply-To: <56524bbf4d0a08e932f9cea18861538151a89534.camel@dubeyko.com>

Hi Slava,

Thanks for looking at this.

> Could you please explain the use-case or workload that is trying to
> claim more blocks that fork can include for Extents Overflow file?

It is not a normal workload -- it requires a corrupted/adversarial
on-disk volume, e.g. a loop-mounted image (removable media, a
downloaded .img/.dmg, or a fuzzer). Per Apple's TN1150 ("HFS Plus
Volume Format"):

  "The extents overflow file also stores additional extents for the
  special files except for the extents overflow file itself."

So by design the extents overflow file must always be fully described
by the eight extents in its own fork record; it can never legitimately
need an overflow extent of its own. The syzbot reproducer mounts an
image whose volume header sets the Extents File fork's total block
count higher than what its eight direct extents describe, which puts
hip->alloc_blocks != hip->first_blocks for HFSPLUS_EXT_CNID -- a state
the volume header alone can force without the extents tree itself
being touched. hfs_btree_open() doesn't currently validate this fork
against the invariant above.

Once mounted, a plain pwritev2() to a regular file (call it FILE_A)
that already has extents cached from a previous lookup is enough to
hit it:

> Could you please share the call trace for the issue?

  pwritev2
   -> hfsplus_get_block(FILE_A)
   -> hfsplus_file_extend(FILE_A)
   -> hfsplus_ext_read_extent(FILE_A) -> hfs_find_init(ext_tree)   [tree_lock acquired]
   -> __hfsplus_ext_cache_extent(FILE_A): FILE_A's cached extent is dirty
   -> __hfsplus_ext_write_extent(FILE_A): needs to insert a new record
   -> hfs_bmap_reserve(ext_tree): ext_tree itself is out of free nodes
   -> hfsplus_file_extend(ext_tree->inode)          <- now growing the tree's own file
   -> hfsplus_ext_read_extent(ext_tree->inode) -> hfs_find_init(ext_tree)  [tree_lock again -> deadlock]

Full syzbot lockdep report for reference:

WARNING: possible recursive locking detected
6.16.0-rc7-syzkaller-00120-g5f33ebd2018c #0 Not tainted
--------------------------------------------
syz-executor310/5840 is trying to acquire lock:
ffff88807fe920b0 (&tree->tree_lock/1){+.+.}-{4:4}, at: hfsplus_find_init+0x15a/0x1d0 fs/hfsplus/bfind.c:28

but task is already holding lock:
ffff88807fe920b0 (&tree->tree_lock/1){+.+.}-{4:4}, at: hfsplus_find_init+0x15a/0x1d0 fs/hfsplus/bfind.c:28

5 locks held by syz-executor310/5840:
 #0: sb_writers#8, at: vfs_writev+0x288/0x960 fs/read_write.c:1055
 #1: &sb->s_type->i_mutex_key#14, at: generic_file_write_iter+0xe3/0x540 mm/filemap.c:4252
 #2: &hip->extents_lock, at: hfsplus_file_extend+0x1fc/0x1990 fs/hfsplus/extents.c:458
 #3: &tree->tree_lock/1, at: hfsplus_find_init+0x15a/0x1d0 fs/hfsplus/bfind.c:28
 #4: &HFSPLUS_I(inode)->extents_lock, at: hfsplus_file_extend+0x1fc/0x1990 fs/hfsplus/extents.c:458

Call Trace:
 hfsplus_find_init fs/hfsplus/bfind.c:28
 hfsplus_ext_read_extent fs/hfsplus/extents.c:216 [inline]
 hfsplus_file_extend+0x416/0x1990 fs/hfsplus/extents.c:462
 hfsplus_bmap_reserve+0x122/0x500 fs/hfsplus/btree.c:358
 __hfsplus_ext_write_extent+0x28d/0x5b0 fs/hfsplus/extents.c:104
 __hfsplus_ext_cache_extent+0x89/0xe30 fs/hfsplus/extents.c:186
 hfsplus_ext_read_extent fs/hfsplus/extents.c:218 [inline]
 hfsplus_file_extend+0x444/0x1990 fs/hfsplus/extents.c:462
 hfsplus_get_block+0x411/0x1530 fs/hfsplus/extents.c:245
 __block_write_begin_int+0x6b2/0x1900 fs/buffer.c:2151
 block_write_begin fs/buffer.c:2262 [inline]
 cont_write_begin+0x789/0xb50 fs/buffer.c:2601
 hfsplus_write_begin+0x66/0xb0 fs/hfsplus/inode.c:46
 generic_perform_write+0x2c4/0x910 mm/filemap.c:4112
 generic_file_write_iter+0x10f/0x540 mm/filemap.c:4255
 do_iter_readv_writev+0x56b/0x7f0 fs/read_write.c:-1
 vfs_writev+0x31a/0x960 fs/read_write.c:1057
 do_pwritev fs/read_write.c:1153 [inline]
 __se_sys_pwritev2+0x179/0x290 fs/read_write.c:1202

(full report: https://syzkaller.appspot.com/text?tag=CrashReport&x=172748a2580000)

hfsplus_get_block() already refuses HFSPLUS_EXT_CNID for the lookup
direction (extents.c:261: "if (inode->i_ino == HFSPLUS_EXT_CNID)
return -EIO;"). This patch adds the same refusal on the grow path in
hfsplus_file_extend(), which is the one hfs_bmap_reserve() can reach
with tree_lock already held. It doesn't fix the underlying corruption,
just stops it from self-deadlocking the tree_lock; a hfs_btree_open()
check that rejects such a volume outright at mount time would close
the hole earlier and I'm happy to send that as a follow-up if you'd
rather validate it there instead.

I have not personally observed the crash on current mainline: I
rebuilt the syzbot C reproducer, and hfs_btree_open(HFSPLUS_CAT_CNID)
now rejects the image at mount (silently) -- it's a 2022-era fuzzed
image and mainline has since gained catalog b-tree validation
(node-size sanity check, record-offset table validation, etc.) that
this image no longer passes. The analysis above is derived from source
plus the syzbot-provided trace, not from a reproduced local crash.

Thanks,
Thang

  reply	other threads:[~2026-09-07 17:15 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-06 15:49 [PATCH] hfsplus: fix recursive tree_lock in hfsplus_file_extend() ThangNN99
2026-09-07 17:05 ` Viacheslav Dubeyko
2026-09-07 17:15   ` ThangNN99 [this message]
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-12 13:24                         ` [PATCH v4 2/2] hfsplus: validate b-tree fork extents at mount time Nguyen Ngoc Thang

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=20260907171543.8175-1-ngocthang2710.1999@gmail.com \
    --to=ngocthang2710.1999@gmail.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=slava@dubeyko.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