ths from ever being reached.

**g) `fs/hfs/mdb.c` - `is_hfs_cnid_counts_valid()`:** New helper
function that validates all three CNID-related counters.

**h) `fs/hfs/super.c` - `hfs_sync_fs()` and `flush_mdb()`:** Adds
`is_hfs_cnid_counts_valid()` calls before `hfs_mdb_commit()` as an extra
safety check.

### 3. THE BUG MECHANISM

Commit `a06ec283e125` (merged in v6.18) expanded `next_id`,
`file_count`, and `folder_count` from `u32` to `atomic64_t` for
atomicity, and added `BUG_ON()` to detect overflow past `U32_MAX`.
However, these values are read from the on-disk MDB at mount time via
`be32_to_cpu()` into `atomic64_t`. If the MDB is corrupted such that
these 32-bit on-disk values, when interpreted, lead to increments past
`U32_MAX` (the on-disk `drNxtCNID` could be `0xFFFFFFFF` or similar),
then creating a new file triggers `BUG_ON(next_id > U32_MAX)` at line
222 of `inode.c`.

The syzbot crash report confirms this exact scenario:
- **Crash site:** `kernel BUG at fs/hfs/inode.c:222` in
  `hfs_new_inode()`
- **Call path:** `openat() -> path_openat() -> lookup_open() ->
  hfs_create() -> hfs_new_inode()`
- **Trigger:** Opening a file for creation on a corrupted HFS filesystem
  image
- **Impact:** Instant kernel panic/oops (invalid opcode trap from
  BUG_ON)

### 4. DEPENDENCY ANALYSIS

**Critical dependency:** This fix ONLY applies to kernels containing
`a06ec283e125`, which first appeared in v6.18-rc1. The BUG_ON code does
not exist in v6.17 or earlier kernels. In those older kernels,
`next_id`, `file_count`, and `folder_count` are plain `u32` with simple
`++`/`--` operations and no overflow checks.

- **v6.18.y stable:** Has the BUG_ON (confirmed in v6.18.5). NEEDS this
  fix.
- **v6.12.y and older:** Does NOT have the BUG_ON. Does NOT need this
  fix.

### 5. SEVERITY AND IMPACT

- **Severity:** HIGH. This is a kernel BUG/panic triggered by corrupted
  filesystem data. Any user mounting a damaged or maliciously crafted
  HFS image can crash the kernel.
- **Attack surface:** HFS images can come from USB sticks, CD-ROMs, disk
  images, or network mounts. The crash is reachable from the `openat()`
  syscall (creating a file on a mounted corrupted HFS).
- **Security implications:** A crafted HFS image can crash the kernel,
  constituting a denial-of-service vulnerability. This is especially
  concerning for systems that auto-mount removable media.

### 6. RISK ASSESSMENT

- **Size:** ~130 lines changed across 5 files, all within `fs/hfs/`
- **Scope:** Entirely contained to the HFS filesystem subsystem
- **Pattern:** Well-understood "replace BUG_ON with error handling"
  pattern
- **Quality:** Reviewed by the HFS maintainer, tested by syzbot (patch
  testing passed), and went through 4 patch revisions (v1 through v4)
- **Risk:** LOW. The changes are:
  - Mount-time validation forces corrupted filesystems read-only (safe)
  - BUG_ON replaced with `return ERR_PTR(-ERANGE)` (graceful failure)
  - Callers updated to handle the new error convention
  - Atomic rollbacks on error (correct)

### 7. MINOR CONCERNS

- There's a minor whitespace issue (spaces vs tabs in `out_discard:`
  label indentation and `hfs_remove()`) - cosmetic only, no functional
  impact
- The `is_hfs_cnid_counts_valid()` calls in `hfs_sync_fs()` and
  `flush_mdb()` only print warnings but don't prevent `hfs_mdb_commit()`
  from running - however, the BUG_ONs in `hfs_mdb_commit()` are already
  removed, so this is a soft warning rather than crash prevention
- The commit has a typo "Singed-off-by" (should be "Signed-off-by") but
  this doesn't affect the code

### 8. CONCLUSION

This is a textbook stable backport candidate:
- Fixes a syzbot-reported, reproducible kernel BUG/panic
- The bug is triggered from userspace via a common syscall path
  (`openat()`)
- Corrupted filesystem images are a well-known attack vector
- The fix is contained, reviewed, and follows the standard "BUG_ON ->
  error handling" pattern
- It applies specifically to v6.18.y stable which contains the buggy
  BUG_ON code

The fix is small, surgical, and meets all stable kernel criteria.

**YES**

 fs/hfs/dir.c    | 15 +++++++++++----
 fs/hfs/hfs_fs.h |  1 +
 fs/hfs/inode.c  | 30 ++++++++++++++++++++++++------
 fs/hfs/mdb.c    | 31 +++++++++++++++++++++++++++----
 fs/hfs/super.c  |  3 +++
 5 files changed, 66 insertions(+), 14 deletions(-)

diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c
index 86a6b317b474a..0c615c078650c 100644
--- a/fs/hfs/dir.c
+++ b/fs/hfs/dir.c
@@ -196,8 +196,8 @@ static int hfs_create(struct mnt_idmap *idmap, struct inode *dir,
 	int res;
 
 	inode = hfs_new_inode(dir, &dentry->d_name, mode);
-	if (!inode)
-		return -ENOMEM;
+	if (IS_ERR(inode))
+		return PTR_ERR(inode);
 
 	res = hfs_cat_create(inode->i_ino, dir, &dentry->d_name, inode);
 	if (res) {
@@ -226,8 +226,8 @@ static struct dentry *hfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 	int res;
 
 	inode = hfs_new_inode(dir, &dentry->d_name, S_IFDIR | mode);
-	if (!inode)
-		return ERR_PTR(-ENOMEM);
+	if (IS_ERR(inode))
+		return ERR_CAST(inode);
 
 	res = hfs_cat_create(inode->i_ino, dir, &dentry->d_name, inode);
 	if (res) {
@@ -254,11 +254,18 @@ static struct dentry *hfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
  */
 static int hfs_remove(struct inode *dir, struct dentry *dentry)
 {
+	struct super_block *sb = dir->i_sb;
 	struct inode *inode = d_inode(dentry);
 	int res;
 
 	if (S_ISDIR(inode->i_mode) && inode->i_size != 2)
 		return -ENOTEMPTY;
+
+	if (unlikely(!is_hfs_cnid_counts_valid(sb))) {
+	    pr_err("cannot remove file/folder\n");
+	    return -ERANGE;
+	}
+
 	res = hfs_cat_delete(inode->i_ino, dir, &dentry->d_name);
 	if (res)
 		return res;
diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h
index e94dbc04a1e43..ac0e83f77a0f1 100644
--- a/fs/hfs/hfs_fs.h
+++ b/fs/hfs/hfs_fs.h
@@ -199,6 +199,7 @@ extern void hfs_delete_inode(struct inode *inode);
 extern const struct xattr_handler * const hfs_xattr_handlers[];
 
 /* mdb.c */
+extern bool is_hfs_cnid_counts_valid(struct super_block *sb);
 extern int hfs_mdb_get(struct super_block *sb);
 extern void hfs_mdb_commit(struct super_block *sb);
 extern void hfs_mdb_close(struct super_block *sb);
diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c
index 524db1389737d..878535db64d67 100644
--- a/fs/hfs/inode.c
+++ b/fs/hfs/inode.c
@@ -187,16 +187,23 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t
 	s64 next_id;
 	s64 file_count;
 	s64 folder_count;
+	int err = -ENOMEM;
 
 	if (!inode)
-		return NULL;
+		goto out_err;
+
+	err = -ERANGE;
 
 	mutex_init(&HFS_I(inode)->extents_lock);
 	INIT_LIST_HEAD(&HFS_I(inode)->open_dir_list);
 	spin_lock_init(&HFS_I(inode)->open_dir_lock);
 	hfs_cat_build_key(sb, (btree_key *)&HFS_I(inode)->cat_key, dir->i_ino, name);
 	next_id = atomic64_inc_return(&HFS_SB(sb)->next_id);
-	BUG_ON(next_id > U32_MAX);
+	if (next_id > U32_MAX) {
+		atomic64_dec(&HFS_SB(sb)->next_id);
+		pr_err("cannot create new inode: next CNID exceeds limit\n");
+		goto out_discard;
+	}
 	inode->i_ino = (u32)next_id;
 	inode->i_mode = mode;
 	inode->i_uid = current_fsuid();
@@ -210,7 +217,11 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t
 	if (S_ISDIR(mode)) {
 		inode->i_size = 2;
 		folder_count = atomic64_inc_return(&HFS_SB(sb)->folder_count);
-		BUG_ON(folder_count > U32_MAX);
+		if (folder_count> U32_MAX) {
+			atomic64_dec(&HFS_SB(sb)->folder_count);
+			pr_err("cannot create new inode: folder count exceeds limit\n");
+			goto out_discard;
+		}
 		if (dir->i_ino == HFS_ROOT_CNID)
 			HFS_SB(sb)->root_dirs++;
 		inode->i_op = &hfs_dir_inode_operations;
@@ -220,7 +231,11 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t
 	} else if (S_ISREG(mode)) {
 		HFS_I(inode)->clump_blocks = HFS_SB(sb)->clumpablks;
 		file_count = atomic64_inc_return(&HFS_SB(sb)->file_count);
-		BUG_ON(file_count > U32_MAX);
+		if (file_count > U32_MAX) {
+			atomic64_dec(&HFS_SB(sb)->file_count);
+			pr_err("cannot create new inode: file count exceeds limit\n");
+			goto out_discard;
+		}
 		if (dir->i_ino == HFS_ROOT_CNID)
 			HFS_SB(sb)->root_files++;
 		inode->i_op = &hfs_file_inode_operations;
@@ -244,6 +259,11 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t
 	hfs_mark_mdb_dirty(sb);
 
 	return inode;
+
+	out_discard:
+		iput(inode);
+	out_err:
+		return ERR_PTR(err);
 }
 
 void hfs_delete_inode(struct inode *inode)
@@ -252,7 +272,6 @@ void hfs_delete_inode(struct inode *inode)
 
 	hfs_dbg("ino %lu\n", inode->i_ino);
 	if (S_ISDIR(inode->i_mode)) {
-		BUG_ON(atomic64_read(&HFS_SB(sb)->folder_count) > U32_MAX);
 		atomic64_dec(&HFS_SB(sb)->folder_count);
 		if (HFS_I(inode)->cat_key.ParID == cpu_to_be32(HFS_ROOT_CNID))
 			HFS_SB(sb)->root_dirs--;
@@ -261,7 +280,6 @@ void hfs_delete_inode(struct inode *inode)
 		return;
 	}
 
-	BUG_ON(atomic64_read(&HFS_SB(sb)->file_count) > U32_MAX);
 	atomic64_dec(&HFS_SB(sb)->file_count);
 	if (HFS_I(inode)->cat_key.ParID == cpu_to_be32(HFS_ROOT_CNID))
 		HFS_SB(sb)->root_files--;
diff --git a/fs/hfs/mdb.c b/fs/hfs/mdb.c
index 53f3fae602179..e0150945cf13b 100644
--- a/fs/hfs/mdb.c
+++ b/fs/hfs/mdb.c
@@ -64,6 +64,27 @@ static int hfs_get_last_session(struct super_block *sb,
 	return 0;
 }
 
+bool is_hfs_cnid_counts_valid(struct super_block *sb)
+{
+	struct hfs_sb_info *sbi = HFS_SB(sb);
+	bool corrupted = false;
+
+	if (unlikely(atomic64_read(&sbi->next_id) > U32_MAX)) {
+		pr_warn("next CNID exceeds limit\n");
+		corrupted = true;
+	}
+	if (unlikely(atomic64_read(&sbi->file_count) > U32_MAX)) {
+		pr_warn("file count exceeds limit\n");
+		corrupted = true;
+	}
+	if (unlikely(atomic64_read(&sbi->folder_count) > U32_MAX)) {
+		pr_warn("folder count exceeds limit\n");
+		corrupted = true;
+	}
+
+	return !corrupted;
+}
+
 /*
  * hfs_mdb_get()
  *
@@ -156,6 +177,11 @@ int hfs_mdb_get(struct super_block *sb)
 	atomic64_set(&HFS_SB(sb)->file_count, be32_to_cpu(mdb->drFilCnt));
 	atomic64_set(&HFS_SB(sb)->folder_count, be32_to_cpu(mdb->drDirCnt));
 
+	if (!is_hfs_cnid_counts_valid(sb)) {
+		pr_warn("filesystem possibly corrupted, running fsck.hfs is recommended. Mounting read-only.\n");
+		sb->s_flags |= SB_RDONLY;
+	}
+
 	/* TRY to get the alternate (backup) MDB. */
 	sect = part_start + part_size - 2;
 	bh = sb_bread512(sb, sect, mdb2);
@@ -209,7 +235,7 @@ int hfs_mdb_get(struct super_block *sb)
 
 	attrib = mdb->drAtrb;
 	if (!(attrib & cpu_to_be16(HFS_SB_ATTRIB_UNMNT))) {
-		pr_warn("filesystem was not cleanly unmounted, running fsck.hfs is recommended.  mounting read-only.\n");
+		pr_warn("filesystem was not cleanly unmounted, running fsck.hfs is recommended.	Mounting read-only.\n");
 		sb->s_flags |= SB_RDONLY;
 	}
 	if ((attrib & cpu_to_be16(HFS_SB_ATTRIB_SLOCK))) {
@@ -273,15 +299,12 @@ void hfs_mdb_commit(struct super_block *sb)
 		/* These parameters may have been modified, so write them back */
 		mdb->drLsMod = hfs_mtime();
 		mdb->drFreeBks = cpu_to_be16(HFS_SB(sb)->free_ablocks);
-		BUG_ON(atomic64_read(&HFS_SB(sb)->next_id) > U32_MAX);
 		mdb->drNxtCNID =
 			cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->next_id));
 		mdb->drNmFls = cpu_to_be16(HFS_SB(sb)->root_files);
 		mdb->drNmRtDirs = cpu_to_be16(HFS_SB(sb)->root_dirs);
-		BUG_ON(atomic64_read(&HFS_SB(sb)->file_count) > U32_MAX);
 		mdb->drFilCnt =
 			cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->file_count));
-		BUG_ON(atomic64_read(&HFS_SB(sb)->folder_count) > U32_MAX);
 		mdb->drDirCnt =
 			cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->folder_count));
 
diff --git a/fs/hfs/super.c b/fs/hfs/super.c
index 47f50fa555a45..70e118c27e200 100644
--- a/fs/hfs/super.c
+++ b/fs/hfs/super.c
@@ -34,6 +34,7 @@ MODULE_LICENSE("GPL");
 
 static int hfs_sync_fs(struct super_block *sb, int wait)
 {
+	is_hfs_cnid_counts_valid(sb);
 	hfs_mdb_commit(sb);
 	return 0;
 }
@@ -65,6 +66,8 @@ static void flush_mdb(struct work_struct *work)
 	sbi->work_queued = 0;
 	spin_unlock(&sbi->work_lock);
 
+	is_hfs_cnid_counts_valid(sb);
+
 	hfs_mdb_commit(sb);
 }
 
-- 
2.51.0[PATCH AUTOSEL 6.19] hfs: Replace BUG_ON with error handling for CNID count checksSasha Levin <sashal@kernel.org> undefinedpatches@lists.linux.dev, stable@vger.kernel.org undefined undefined undefined undefined undefined undefined undefined