[PATCH RFC] hfs/hfsplus: fix lockdep false positive for extents_lock

0 views
Skip to first unread message

syzbot

unread,
May 9, 2026, 1:50:39 AM (7 days ago) May 9
to syzkaller-upst...@googlegroups.com, syz...@lists.linux.dev
hfs/hfsplus: fix lockdep false positive for extents_lock

syzkaller reported a possible recursive locking deadlock on
&HFSPLUS_I(inode)->extents_lock. This is a lockdep false positive.

In HFS and HFS+, extents_lock is used to protect the extent allocation
information of an inode. The problem arises because the lock is
initialized uniformly for all inodes, including both regular files and
special internal system files (like the Allocation file, Extents file,
Catalog file, etc.). Because of this, lockdep assigns the exact same
lock class to the extents_lock of all inodes.

When a regular file is truncated or extended, its extents_lock is
acquired. During this process, the filesystem might need to allocate or
free blocks, which requires reading or updating the Allocation file (or
other system files). This triggers a read on the Allocation file's
mapping, which in turn acquires the Allocation file's extents_lock.
Lockdep sees the same lock class being acquired twice in a nested
fashion and flags it as a recursive locking deadlock.

The exact same issue also exists in the older HFS filesystem (fs/hfs/),
where operations on a regular file can trigger updates to the Extents
or Catalog B-trees, leading to a similar nested acquisition of
extents_lock.

To fix this false positive, assign distinct lock classes to the
extents_lock of special system files using lockdep_set_class().
For HFS+, add lock class keys for the Extents, Catalog, Allocation,
Attributes, and Startup files, and apply them in hfsplus_iget().
For HFS, add lock class keys for the Extents and Catalog B-tree files,
and apply them in hfs_btree_open().

Assisted-by: Gemini:gemini-3.1-pro-preview
To: <fran...@vivo.com>
To: <glau...@physik.fu-berlin.de>
To: <linux-...@vger.kernel.org>
To: <sl...@dubeyko.com>
Cc: <linux-...@vger.kernel.org>

---
diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c
index 2eb37a2f6..fc262d703 100644
--- a/fs/hfs/btree.c
+++ b/fs/hfs/btree.c
@@ -12,9 +12,17 @@
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/log2.h>
+#include <linux/lockdep.h>

+#include <linux/lockdep.h>
#include "btree.h"

+static struct lock_class_key hfs_ext_extents_key;
+static struct lock_class_key hfs_cat_extents_key;
+
+static struct lock_class_key hfs_ext_extents_key;
+static struct lock_class_key hfs_cat_extents_key;
+
/* Get a reference to a B*Tree and do some initial checks */
struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp keycmp)
{
@@ -49,6 +57,10 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
mutex_init(&HFS_I(tree->inode)->extents_lock);
switch (id) {
case HFS_EXT_CNID:
+ lockdep_set_class(&HFS_I(tree->inode)->extents_lock,
+ &hfs_ext_extents_key);
+ lockdep_set_class(&HFS_I(tree->inode)->extents_lock,
+ &hfs_ext_extents_key);
hfs_inode_read_fork(tree->inode, mdb->drXTExtRec, mdb->drXTFlSize,
mdb->drXTFlSize, be32_to_cpu(mdb->drXTClpSiz));
if (HFS_I(tree->inode)->alloc_blocks >
@@ -61,6 +73,10 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
tree->inode->i_mapping->a_ops = &hfs_btree_aops;
break;
case HFS_CAT_CNID:
+ lockdep_set_class(&HFS_I(tree->inode)->extents_lock,
+ &hfs_cat_extents_key);
+ lockdep_set_class(&HFS_I(tree->inode)->extents_lock,
+ &hfs_cat_extents_key);
hfs_inode_read_fork(tree->inode, mdb->drCTExtRec, mdb->drCTFlSize,
mdb->drCTFlSize, be32_to_cpu(mdb->drCTClpSiz));

diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c
index 40a0feda7..e1345a6fe 100644
--- a/fs/hfsplus/super.c
+++ b/fs/hfsplus/super.c
@@ -18,13 +18,35 @@
#include <linux/slab.h>
#include <linux/vfs.h>
#include <linux/nls.h>
+#include <linux/lockdep.h>

static struct inode *hfsplus_alloc_inode(struct super_block *sb);
static void hfsplus_free_inode(struct inode *inode);

+#include <linux/lockdep.h>
+
+#include <linux/lockdep.h>
#include "hfsplus_fs.h"
+
+static struct lock_class_key hfsplus_ext_extents_key;
+static struct lock_class_key hfsplus_cat_extents_key;
+static struct lock_class_key hfsplus_alloc_extents_key;
+static struct lock_class_key hfsplus_attr_extents_key;
+static struct lock_class_key hfsplus_start_extents_key;
#include "xattr.h"

+static struct lock_class_key hfsplus_ext_extents_key;
+static struct lock_class_key hfsplus_cat_extents_key;
+static struct lock_class_key hfsplus_alloc_extents_key;
+static struct lock_class_key hfsplus_attr_extents_key;
+static struct lock_class_key hfsplus_start_extents_key;
+
+static struct lock_class_key hfsplus_ext_extents_key;
+static struct lock_class_key hfsplus_cat_extents_key;
+static struct lock_class_key hfsplus_alloc_extents_key;
+static struct lock_class_key hfsplus_attr_extents_key;
+static struct lock_class_key hfsplus_start_extents_key;
+
static int hfsplus_system_read_inode(struct inode *inode)
{
struct hfsplus_vh *vhdr = HFSPLUS_SB(inode->i_sb)->s_vhdr;
@@ -84,6 +106,51 @@ struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino)
memset(HFSPLUS_I(inode)->cached_extents, 0, sizeof(hfsplus_extent_rec));
HFSPLUS_I(inode)->extent_state = 0;
mutex_init(&HFSPLUS_I(inode)->extents_lock);
+ switch (ino) {
+ case HFSPLUS_EXT_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_ext_extents_key);
+ break;
+ case HFSPLUS_CAT_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_cat_extents_key);
+ break;
+ case HFSPLUS_ALLOC_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_alloc_extents_key);
+ break;
+ case HFSPLUS_ATTR_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_attr_extents_key);
+ break;
+ case HFSPLUS_START_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_start_extents_key);
+ break;
+ }
+ /* Set lockdep classes for system files to avoid false positives */
+ switch (ino) {
+ case HFSPLUS_EXT_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_ext_extents_key);
+ break;
+ case HFSPLUS_CAT_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_cat_extents_key);
+ break;
+ case HFSPLUS_ALLOC_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_alloc_extents_key);
+ break;
+ case HFSPLUS_ATTR_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_attr_extents_key);
+ break;
+ case HFSPLUS_START_CNID:
+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_start_extents_key);
+ break;
+ }
HFSPLUS_I(inode)->rsrc_inode = NULL;
HFSPLUS_I(inode)->create_date = 0;
HFSPLUS_I(inode)->linkid = 0;


base-commit: 7fd2df204f342fc17d1a0bfcd474b24232fb0f32
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to send it to the mailing list.
Reply with '#syz reject' to reject it.

See for more information.

Aleksandr Nogikh

unread,
May 11, 2026, 5:32:37 PM (4 days ago) May 11
to syzbot, syzkaller-upst...@googlegroups.com, syz...@lists.linux.dev
Note the duplicate lines in the patch.

On Sat, May 9, 2026 at 7:50 AM 'syzbot' via
syzkaller-upstream-moderation
> --
> You received this message because you are subscribed to the Google Groups "syzkaller-upstream-moderation" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to syzkaller-upstream-m...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/syzkaller-upstream-moderation/0bbb511f-6f0a-48b1-b3a4-e55eb907a1b4%40mail.kernel.org.

syzbot

unread,
May 12, 2026, 5:19:29 AM (4 days ago) May 12
to syzkaller-upst...@googlegroups.com, syz...@lists.linux.dev
Fixes: 895c23f8c39c0c8d7b536bb2566d4aa968d78be2 ("hfsplus: convert the extents_lock in a mutex")
Assisted-by: Gemini:gemini-3.1-pro-preview Gemini:gemini-3-flash-preview
Reported-by: syzbot+b7ef7c...@syzkaller.appspotmail.com
Link: https://syzkaller.appspot.com/bug?extid=b7ef7c0c8d8098686ae2
Link: https://syzkaller.appspot.com/ai_job?id=8c05b019-0a6c-4a92-8e06-c7a648023928
v2:
- Removed duplicate lines (includes, lock class key declarations, and switch statements).

v1:
https://lore.kernel.org/all/0bbb511f-6f0a-48b1...@mail.kernel.org/T/
---
diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c
index 2eb37a2f6..9182ca939 100644
--- a/fs/hfs/btree.c
+++ b/fs/hfs/btree.c
@@ -12,9 +12,13 @@
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/log2.h>
+#include <linux/lockdep.h>

#include "btree.h"

+static struct lock_class_key hfs_ext_extents_key;
+static struct lock_class_key hfs_cat_extents_key;
+
/* Get a reference to a B*Tree and do some initial checks */
struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp keycmp)
{
@@ -49,6 +53,8 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
mutex_init(&HFS_I(tree->inode)->extents_lock);
switch (id) {
case HFS_EXT_CNID:
+ lockdep_set_class(&HFS_I(tree->inode)->extents_lock,
+ &hfs_ext_extents_key);
hfs_inode_read_fork(tree->inode, mdb->drXTExtRec, mdb->drXTFlSize,
mdb->drXTFlSize, be32_to_cpu(mdb->drXTClpSiz));
if (HFS_I(tree->inode)->alloc_blocks >
@@ -61,6 +67,8 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
tree->inode->i_mapping->a_ops = &hfs_btree_aops;
break;
case HFS_CAT_CNID:
+ lockdep_set_class(&HFS_I(tree->inode)->extents_lock,
+ &hfs_cat_extents_key);
hfs_inode_read_fork(tree->inode, mdb->drCTExtRec, mdb->drCTFlSize,
mdb->drCTFlSize, be32_to_cpu(mdb->drCTClpSiz));

diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c
index 40a0feda7..17147d5f5 100644
--- a/fs/hfsplus/super.c
+++ b/fs/hfsplus/super.c
@@ -18,6 +18,7 @@
#include <linux/slab.h>
#include <linux/vfs.h>
#include <linux/nls.h>
+#include <linux/lockdep.h>

static struct inode *hfsplus_alloc_inode(struct super_block *sb);
static void hfsplus_free_inode(struct inode *inode);
@@ -25,6 +26,12 @@ static void hfsplus_free_inode(struct inode *inode);
#include "hfsplus_fs.h"
#include "xattr.h"

+static struct lock_class_key hfsplus_ext_extents_key;
+static struct lock_class_key hfsplus_cat_extents_key;
+static struct lock_class_key hfsplus_alloc_extents_key;
+static struct lock_class_key hfsplus_attr_extents_key;
+static struct lock_class_key hfsplus_start_extents_key;
+
static int hfsplus_system_read_inode(struct inode *inode)
{
struct hfsplus_vh *vhdr = HFSPLUS_SB(inode->i_sb)->s_vhdr;
@@ -84,6 +91,29 @@ struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino)
memset(HFSPLUS_I(inode)->cached_extents, 0, sizeof(hfsplus_extent_rec));
HFSPLUS_I(inode)->extent_state = 0;
mutex_init(&HFSPLUS_I(inode)->extents_lock);

Aleksandr Nogikh

unread,
May 12, 2026, 6:48:28 AM (4 days ago) May 12
to syzbot, syzkaller-upst...@googlegroups.com, syz...@lists.linux.dev
Have a look if you can further minimize the patch.

On Tue, May 12, 2026 at 11:19 AM 'syzbot' via
syzkaller-upstream-moderation
> --
> You received this message because you are subscribed to the Google Groups "syzkaller-upstream-moderation" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to syzkaller-upstream-m...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/syzkaller-upstream-moderation/c672c95e-82f0-4c76-9ba0-09e5133639cc%40mail.kernel.org.

syzbot

unread,
May 12, 2026, 6:34:24 PM (3 days ago) May 12
to syzkaller-upst...@googlegroups.com, syz...@lists.linux.dev
syzkaller reported a possible recursive locking deadlock on
&HFSPLUS_I(inode)->extents_lock. This is a lockdep false positive.

In HFS and HFS+, extents_lock is used to protect the extent allocation
information of an inode. The problem arises because the lock is
initialized uniformly for all inodes, including both regular files and
special internal system files (like the Allocation file, Extents file,
Catalog file, etc.). Because of this, lockdep assigns the exact same
lock class to the extents_lock of all inodes.

When a regular file is truncated or extended, its extents_lock is
acquired. During this process, the filesystem might need to allocate or
free blocks, which requires reading or updating the Allocation file (or
other system files). This triggers a read on the Allocation file's
mapping, which in turn acquires the Allocation file's extents_lock.
Lockdep sees the same lock class being acquired twice in a nested
fashion and flags it as a recursive locking deadlock.

The exact same issue also exists in the older HFS filesystem (fs/hfs/),
where operations on a regular file can trigger updates to the Extents or
Catalog B-trees, leading to a similar nested acquisition of
extents_lock.

To fix this false positive, assign distinct lock classes to the
extents_lock of special system files using lockdep_set_class(). For
HFS+, add an array of lock class keys for system files, and apply them
in hfsplus_system_read_inode(). For HFS, add an array of lock class keys
for B-tree files, and apply them in hfs_btree_open().

Fixes: 895c23f8c39c0c8d7b536bb2566d4aa968d78be2 ("hfsplus: convert the extents_lock in a mutex")
Assisted-by: Gemini:gemini-3.1-pro-preview Gemini:gemini-3-flash-preview
Reported-by: syzbot+b7ef7c...@syzkaller.appspotmail.com
Link: https://syzkaller.appspot.com/bug?extid=b7ef7c0c8d8098686ae2
Link: https://syzkaller.appspot.com/ai_job?id=05a9fad5-be33-46b2-888a-68617780e581
v3:
- Minimized the patch by using arrays of lock class keys indexed by inode ID instead of individual variables and switch statements.
- Moved the lockdep_set_class() call for HFS+ from hfsplus_iget() to hfsplus_system_read_inode().

v2:
- Removed duplicate lines (includes, lock class key declarations, and switch statements).
https://lore.kernel.org/all/c672c95e-82f0-4c76...@mail.kernel.org/T/

v1:
https://lore.kernel.org/all/0bbb511f-6f0a-48b1...@mail.kernel.org/T/
---
diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c
index 2eb37a2f6..34d582a3e 100644
--- a/fs/hfs/btree.c
+++ b/fs/hfs/btree.c
@@ -12,9 +12,12 @@
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/log2.h>
+#include <linux/lockdep.h>

#include "btree.h"

+static struct lock_class_key hfs_btree_extents_key[HFS_FIRSTUSER_CNID];
+
/* Get a reference to a B*Tree and do some initial checks */
struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp keycmp)
{
@@ -47,6 +50,8 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
struct hfs_mdb *mdb = HFS_SB(sb)->mdb;
HFS_I(tree->inode)->flags = 0;
mutex_init(&HFS_I(tree->inode)->extents_lock);
+ lockdep_set_class(&HFS_I(tree->inode)->extents_lock,
+ &hfs_btree_extents_key[id]);
switch (id) {
case HFS_EXT_CNID:
hfs_inode_read_fork(tree->inode, mdb->drXTExtRec, mdb->drXTFlSize,
diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c
index 40a0feda7..7be84552a 100644
--- a/fs/hfsplus/super.c
+++ b/fs/hfsplus/super.c
@@ -18,6 +18,7 @@
#include <linux/slab.h>
#include <linux/vfs.h>
#include <linux/nls.h>
+#include <linux/lockdep.h>

static struct inode *hfsplus_alloc_inode(struct super_block *sb);
static void hfsplus_free_inode(struct inode *inode);
@@ -25,10 +26,15 @@ static void hfsplus_free_inode(struct inode *inode);
#include "hfsplus_fs.h"
#include "xattr.h"

+static struct lock_class_key hfsplus_sys_extents_key[HFSPLUS_FIRSTUSER_CNID];
+
static int hfsplus_system_read_inode(struct inode *inode)
{
struct hfsplus_vh *vhdr = HFSPLUS_SB(inode->i_sb)->s_vhdr;

+ lockdep_set_class(&HFSPLUS_I(inode)->extents_lock,
+ &hfsplus_sys_extents_key[inode->i_ino]);
+
switch (inode->i_ino) {
case HFSPLUS_EXT_CNID:
hfsplus_inode_read_fork(inode, &vhdr->ext_file);
Reply all
Reply to author
Forward
0 new messages