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.