mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] qnx6: validate di_filelevels in qnx6_iget() and fix mount error handling
@ 2026-09-19 22:25 Hui Peng
  2026-09-20  4:02 ` Damien Le Moal
  0 siblings, 1 reply; 34+ messages in thread
From: Hui Peng @ 2026-09-19 22:25 UTC (permalink / raw)
  To: brauner, jack, dlemoal, jlayton; +Cc: linux-fsdevel, linux-kernel

Fix two issues in fs/qnx6/:

1. In qnx6_iget(), reject raw_inode->di_filelevels > QNX6_PTR_MAX_LEVELS
   so computing the maximum block count does not trigger shift-out-of-
   bounds undefined behavior or out-of-bounds block pointer array walks.
2. In qnx6_fill_super() and qnx6_mmi_fill_super(), prevent MS_SILENT
   from bypassing superblock magic validation and ensure sbi->sb_buf is
   released exactly once on failure.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 6de49333acad..04214b829e10 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -144,8 +144,10 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no)
 		levelptr = (no >> bitdelta) & mask;
 		ptr = ((__fs32 *)bh->b_data)[levelptr];
 
-		if (!qnx6_check_blockptr(ptr))
+		if (!qnx6_check_blockptr(ptr)) {
+			brelse(bh);
 			return 0;
+		}
 
 		block = qnx6_get_devblock(s, ptr);
 		brelse(bh);
@@ -397,12 +399,14 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
 		sbi->sb_buf = bh1;
 		sbi->sb = (struct qnx6_super_block *)bh1->b_data;
 		brelse(bh2);
+		bh2 = NULL;
 		pr_info("superblock #1 active\n");
 	} else {
 		/* superblock #2 active */
 		sbi->sb_buf = bh2;
 		sbi->sb = (struct qnx6_super_block *)bh2->b_data;
 		brelse(bh1);
+		bh1 = NULL;
 		pr_info("superblock #2 active\n");
 	}
 mmi_success:
@@ -463,6 +467,8 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
 out1:
 	iput(sbi->inodes);
 out:
+	if (sbi->sb_buf && sbi->sb_buf != bh1 && sbi->sb_buf != bh2)
+		brelse(sbi->sb_buf);
 	brelse(bh1);
 	brelse(bh2);
 outnobh:
@@ -560,6 +566,13 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
 	memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr,
 				sizeof(raw_inode->di_block_ptr));
 	ei->di_filelevels = raw_inode->di_filelevels;
+	if (ei->di_filelevels > QNX6_PTR_MAX_LEVELS) {
+		pr_err("invalid filelevels (%u) in inode %u\n",
+		       ei->di_filelevels, ino);
+		folio_release_kmap(folio, raw_inode);
+		iget_failed(inode);
+		return ERR_PTR(-EIO);
+	}
 
 	if (S_ISREG(inode->i_mode)) {
 		inode->i_fop = &generic_ro_fops;
diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
index b8afb6f388b2..28cb9322278e 100644
--- a/fs/qnx6/super_mmi.c
+++ b/fs/qnx6/super_mmi.c
@@ -51,10 +51,9 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
 	sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
 	sbi = QNX6_SB(s);
 	if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) {
-		if (!silent) {
+		if (!silent)
 			pr_err("wrong signature (magic) in superblock #1.\n");
-			goto out;
-		}
+		goto out;
 	}
 
 	/* checksum check - start at byte 8 and end at byte 512 */
@@ -64,15 +63,16 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
 		goto out;
 	}
 
-	/* calculate second superblock blocknumber */
-	offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
-					fs32_to_cpu(sbi, sb1->sb_blocksize);
-
 	/* set new blocksize */
 	if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) {
 		pr_err("unable to set blocksize\n");
 		goto out;
 	}
+
+	/* calculate second superblock blocknumber */
+	offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
+					fs32_to_cpu(sbi, sb1->sb_blocksize);
+
 	/* blocksize invalidates bh - pull it back in */
 	brelse(bh1);
 	bh1 = sb_bread(s, 0);

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH] qnx6: validate di_filelevels in qnx6_iget() and fix mount error handling
  2026-09-19 22:25 [PATCH] qnx6: validate di_filelevels in qnx6_iget() and fix mount error handling Hui Peng
@ 2026-09-20  4:02 ` Damien Le Moal
  2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
  0 siblings, 1 reply; 34+ messages in thread
From: Damien Le Moal @ 2026-09-20  4:02 UTC (permalink / raw)
  To: Hui Peng, brauner, jack, jlayton; +Cc: linux-fsdevel, linux-kernel

On 9/20/26 07:25, Hui Peng wrote:
> Fix two issues in fs/qnx6/:
> 
> 1. In qnx6_iget(), reject raw_inode->di_filelevels > QNX6_PTR_MAX_LEVELS
>    so computing the maximum block count does not trigger shift-out-of-
>    bounds undefined behavior or out-of-bounds block pointer array walks.
> 2. In qnx6_fill_super() and qnx6_mmi_fill_super(), prevent MS_SILENT
>    from bypassing superblock magic validation and ensure sbi->sb_buf is
>    released exactly once on failure.

Please split this into 2 patches, one patch for each problem you fix.

> 
> Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
> Assisted-by: LLM
> Signed-off-by: Hui Peng <benquike@gmail.com>
> ---
> diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
> index 6de49333acad..04214b829e10 100644
> --- a/fs/qnx6/inode.c
> +++ b/fs/qnx6/inode.c
> @@ -144,8 +144,10 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no)
>  		levelptr = (no >> bitdelta) & mask;
>  		ptr = ((__fs32 *)bh->b_data)[levelptr];
>  
> -		if (!qnx6_check_blockptr(ptr))
> +		if (!qnx6_check_blockptr(ptr)) {
> +			brelse(bh);
>  			return 0;
> +		}
>  
>  		block = qnx6_get_devblock(s, ptr);
>  		brelse(bh);
> @@ -397,12 +399,14 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
>  		sbi->sb_buf = bh1;
>  		sbi->sb = (struct qnx6_super_block *)bh1->b_data;
>  		brelse(bh2);
> +		bh2 = NULL;
>  		pr_info("superblock #1 active\n");
>  	} else {
>  		/* superblock #2 active */
>  		sbi->sb_buf = bh2;
>  		sbi->sb = (struct qnx6_super_block *)bh2->b_data;
>  		brelse(bh1);
> +		bh1 = NULL;
>  		pr_info("superblock #2 active\n");
>  	}
>  mmi_success:
> @@ -463,6 +467,8 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
>  out1:
>  	iput(sbi->inodes);
>  out:
> +	if (sbi->sb_buf && sbi->sb_buf != bh1 && sbi->sb_buf != bh2)
> +		brelse(sbi->sb_buf);
>  	brelse(bh1);
>  	brelse(bh2);
>  outnobh:
> @@ -560,6 +566,13 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
>  	memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr,
>  				sizeof(raw_inode->di_block_ptr));
>  	ei->di_filelevels = raw_inode->di_filelevels;
> +	if (ei->di_filelevels > QNX6_PTR_MAX_LEVELS) {
> +		pr_err("invalid filelevels (%u) in inode %u\n",
> +		       ei->di_filelevels, ino);
> +		folio_release_kmap(folio, raw_inode);
> +		iget_failed(inode);
> +		return ERR_PTR(-EIO);
> +	}
>  
>  	if (S_ISREG(inode->i_mode)) {
>  		inode->i_fop = &generic_ro_fops;
> diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
> index b8afb6f388b2..28cb9322278e 100644
> --- a/fs/qnx6/super_mmi.c
> +++ b/fs/qnx6/super_mmi.c
> @@ -51,10 +51,9 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
>  	sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
>  	sbi = QNX6_SB(s);
>  	if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) {
> -		if (!silent) {
> +		if (!silent)
>  			pr_err("wrong signature (magic) in superblock #1.\n");
> -			goto out;
> -		}
> +		goto out;
>  	}
>  
>  	/* checksum check - start at byte 8 and end at byte 512 */
> @@ -64,15 +63,16 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
>  		goto out;
>  	}
>  
> -	/* calculate second superblock blocknumber */
> -	offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
> -					fs32_to_cpu(sbi, sb1->sb_blocksize);
> -
>  	/* set new blocksize */
>  	if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) {
>  		pr_err("unable to set blocksize\n");
>  		goto out;
>  	}
> +
> +	/* calculate second superblock blocknumber */
> +	offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
> +					fs32_to_cpu(sbi, sb1->sb_blocksize);
> +
>  	/* blocksize invalidates bh - pull it back in */
>  	brelse(bh1);
>  	bh1 = sb_bread(s, 0);


-- 
Damien Le Moal
Western Digital Research

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget()
  2026-09-20  4:02 ` Damien Le Moal
@ 2026-09-21  4:25   ` Hui Peng
  2026-09-21  4:25     ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng
                       ` (7 more replies)
  0 siblings, 8 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-21  4:25 UTC (permalink / raw)
  To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton
  Cc: linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_iget(), raw_inode->di_filelevels is copied directly from the
on-disk inode without checking whether it exceeds QNX6_PTR_MAX_LEVELS (5)
(unlike sb1->Inode.levels and sb1->Longfile.levels, which qnx6_fill_super()
explicitly checks against QNX6_PTR_MAX_LEVELS). When qnx6_block_map()
later computes bitdelta = ptrbits * ei->di_filelevels, a crafted
di_filelevels value (e.g. 20, giving bitdelta = 200) causes a
shift-out-of-bounds in (no >> bitdelta) and out-of-bounds indirect block
traversal:

  qnx6: superblock #1 active
  Buffer I/O error on dev loop0, logical block 4099, async page read
  qnx6: Error reading block (4099)
  qnx6: error reading root directory.

Validate that ei->di_filelevels <= QNX6_PTR_MAX_LEVELS in qnx6_iget() and
fail with -EIO if exceeded.

Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6
filesystem image with root inode di_filelevels = 20 on /dev/loop0: on the
unfixed kernel qnx6_iget() accepts the inode and qnx6_block_map() walks
invalid indirect blocks ("qnx6: Error reading block (4099)"), whereas on
the fixed kernel qnx6_iget() logs "qnx6: invalid filelevels (20) in inode
1" and fails immediately with -EIO.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split the v1 patch into a 6-patch series (one patch per issue fixed) and
  remove Markdown formatting from the commit message, as requested by
  Damien Le Moal.

 fs/qnx6/inode.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 6de49333acad..13972c086673 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -560,6 +560,13 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
 	memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr,
 				sizeof(raw_inode->di_block_ptr));
 	ei->di_filelevels = raw_inode->di_filelevels;
+	if (ei->di_filelevels > QNX6_PTR_MAX_LEVELS) {
+		pr_err("invalid filelevels (%u) in inode %u\n",
+		       ei->di_filelevels, ino);
+		folio_release_kmap(folio, raw_inode);
+		iget_failed(inode);
+		return ERR_PTR(-EIO);
+	}
 
 	if (S_ISREG(inode->i_mode)) {
 		inode->i_fop = &generic_ro_fops;
-- 
2.49.0

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map()
  2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
@ 2026-09-21  4:25     ` Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
  2026-09-21  4:25     ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng
                       ` (6 subsequent siblings)
  7 siblings, 2 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-21  4:25 UTC (permalink / raw)
  To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton
  Cc: linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_block_map(), when qnx6_check_blockptr(ptr) fails ("qnx6: hit
unused blockpointer.") inside the indirect block traversal loop, the
buffer_head bh read by sb_bread() is not released before returning 0,
leaving bh->b_count elevated and pinning the backing blockdev folio in
memory. Call brelse(bh) on that error path before returning 0.

Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 image on
/dev/loop0 with a file (di_filelevels = 1) whose indirect block (block 5)
contains an unused block pointer (~0U), reading the file to trigger
"qnx6: hit unused blockpointer.", and then flushing per-CPU bh_lrus and
pagecache via /proc/sys/vm/drop_caches and POSIX_FADV_DONTNEED: on the
unfixed kernel indirect block 5 remains pinned in bd_mapping
(indirect_page5_leaked = 1), whereas on the fixed kernel indirect block 5
is released and evicted cleanly (indirect_page5_leaked = 0).

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split the qnx6_block_map() buffer_head leak fix into its own patch (2/6)
  and remove Markdown formatting from the commit message, as requested by
  Damien Le Moal.

 fs/qnx6/inode.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 13972c086673..9b624829520e 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -144,8 +144,10 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no)
 		levelptr = (no >> bitdelta) & mask;
 		ptr = ((__fs32 *)bh->b_data)[levelptr];
 
-		if (!qnx6_check_blockptr(ptr))
+		if (!qnx6_check_blockptr(ptr)) {
+			brelse(bh);
 			return 0;
+		}
 
 		block = qnx6_get_devblock(s, ptr);
 		brelse(bh);
-- 
2.49.0

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super()
  2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
  2026-09-21  4:25     ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng
@ 2026-09-21  4:25     ` Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
  2026-09-21  4:25     ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
                       ` (5 subsequent siblings)
  7 siblings, 2 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-21  4:25 UTC (permalink / raw)
  To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton
  Cc: linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_fill_super(), when selecting the active superblock, the inactive
superblock buffer_head (bh2 when superblock #1 is active, or bh1 when
superblock #2 is active) is released immediately via brelse() without
clearing the local pointer to NULL. If a subsequent mount step fails and
jumps to out, out1, out2, or out3, qnx6_fill_super() unconditionally calls
brelse(bh1) and brelse(bh2), causing a second brelse() on the already
released buffer_head. Because the extra brelse() drops the per-CPU bh_lru
reference to 0, freeing the buffer_head while it remains in bh_lru, this
triggers a KASAN slab-use-after-free and VFS buffer refcount warning:

  qnx6: superblock #1 active
  qnx6: error reading root directory.
  BUG: KASAN: slab-use-after-free in invalidate_bh_lrus_cpu+0x9f/0x120
  Read of size 4 at addr ffff88800151e658 by task kworker/1:1/69
  ...
  VFS: brelse: Trying to free [already] free buffer
  WARNING: fs/buffer.c:1051 at invalidate_bh_lru+0x4c/0x160, CPU#0: init/1

Clear bh2/bh1 to NULL immediately after releasing the inactive buffer_head.

Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 image on
/dev/loop0 that activates superblock #1 and then fails root directory
validation: on the unfixed kernel, mount failure triggers the double-brelse
VFS warning, WARNING at fs/buffer.c:1051, and KASAN slab-use-after-free in
invalidate_bh_lrus_cpu(), whereas on the fixed kernel mount fails cleanly
with 0 warnings or KASAN faults.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split the qnx6_fill_super() double-brelse fix into its own patch (3/6)
  separate from the mmi_fs sb_buf leak fix (4/6), and remove Markdown
  formatting from the commit message, as requested by Damien Le Moal.

 fs/qnx6/inode.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 9b624829520e..c4a35d81e67c 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -399,12 +399,14 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
 		sbi->sb_buf = bh1;
 		sbi->sb = (struct qnx6_super_block *)bh1->b_data;
 		brelse(bh2);
+		bh2 = NULL;
 		pr_info("superblock #1 active\n");
 	} else {
 		/* superblock #2 active */
 		sbi->sb_buf = bh2;
 		sbi->sb = (struct qnx6_super_block *)bh2->b_data;
 		brelse(bh1);
+		bh1 = NULL;
 		pr_info("superblock #2 active\n");
 	}
 mmi_success:
-- 
2.49.0

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super()
  2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
  2026-09-21  4:25     ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng
  2026-09-21  4:25     ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng
@ 2026-09-21  4:25     ` Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 3/6] qnx6: avoid double brelse() on " Hui Peng
  2026-09-21  4:25     ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
                       ` (4 subsequent siblings)
  7 siblings, 2 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-21  4:25 UTC (permalink / raw)
  To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton
  Cc: linux-fsdevel, linux-kernel, stable, Hui Peng

When mounting with "-o mmi_fs" (QNX6_MOUNT_MMI_FS), qnx6_mmi_fill_super()
allocates the active superblock buffer_head and stores it in qs->sb_buf
(QNX6_SB(s)->sb_buf), while qnx6_fill_super()'s local bh1 and bh2 pointers
remain NULL, and jumps to mmi_success. If a subsequent validation or inode
initialization check after mmi_success fails and jumps to out, out1, out2,
or out3, the error cleanup at out only calls brelse(bh1) and brelse(bh2)
(both NULL), leaking the qs->sb_buf buffer_head reference.

Release qs->sb_buf at out when it is distinct from bh1 and bh2 (using
qs->sb_buf rather than sbi->sb_buf because sbi is not yet initialized if
mmi_success jumps to out on superblock level validation failure).

Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 mmi_fs
image on /dev/loop0 that succeeds in qnx6_mmi_fill_super() (allocating
qs->sb_buf at block 0) and then fails root directory validation, followed
by flushing per-CPU bh_lrus and pagecache via /proc/sys/vm/drop_caches and
POSIX_FADV_DONTNEED: on the unfixed kernel block 0 remains pinned in
bd_mapping by the leaked qs->sb_buf buffer_head (sb_buf_page0_leaked = 1),
whereas on the fixed kernel qs->sb_buf is released and evicted cleanly
(sb_buf_page0_leaked = 0).

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split the mmi_fs qs->sb_buf leak fix into its own patch (4/6) separate
  from the double-brelse fix (3/6), use qs->sb_buf instead of
  uninitialized sbi->sb_buf, and remove Markdown formatting from the
  commit message, as requested by Damien Le Moal.

 fs/qnx6/inode.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index c4a35d81e67c..124b60b47c8e 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -467,6 +467,8 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
 out1:
 	iput(sbi->inodes);
 out:
+	if (qs->sb_buf && qs->sb_buf != bh1 && qs->sb_buf != bh2)
+		brelse(qs->sb_buf);
 	brelse(bh1);
 	brelse(bh2);
 outnobh:
-- 
2.49.0

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set
  2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
                       ` (2 preceding siblings ...)
  2026-09-21  4:25     ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
@ 2026-09-21  4:25     ` Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
  2026-09-21  4:25     ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng
                       ` (3 subsequent siblings)
  7 siblings, 2 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-21  4:25 UTC (permalink / raw)
  To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton
  Cc: linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_mmi_fill_super(), when the superblock #1 magic check fails and the
silent mount flag (SB_SILENT) is set, the goto out statement is inside the
if (!silent) block, so execution continues past the magic check with an
invalid superblock (unlike superblock #2 at line 93 where goto out is
outside if (!silent)). Move goto out outside the if (!silent) block.

Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 mmi_fs
image on /dev/loop0 with sb1->sb_magic = 0xdeadbeef (!= QNX6_SUPER_MAGIC)
using MS_SILENT | MS_RDONLY and "-o mmi_fs": on the unfixed kernel
qnx6_mmi_fill_super() ignores the magic mismatch when MS_SILENT is set,
logs "qnx6: superblock #1 active", and mounts the invalid-magic filesystem
(mount returns 0), whereas on the fixed kernel qnx6_mmi_fill_super()
immediately aborts at the magic check and rejects the mount with -EINVAL.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split the qnx6_mmi_fill_super() silent magic check fix into its own
  patch (5/6) and remove Markdown formatting from the commit message, as
  requested by Damien Le Moal.

 fs/qnx6/super_mmi.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
index b8afb6f388b2..18298e273a13 100644
--- a/fs/qnx6/super_mmi.c
+++ b/fs/qnx6/super_mmi.c
@@ -51,10 +51,9 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
 	sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
 	sbi = QNX6_SB(s);
 	if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) {
-		if (!silent) {
+		if (!silent)
 			pr_err("wrong signature (magic) in superblock #1.\n");
-			goto out;
-		}
+		goto out;
 	}
 
 	/* checksum check - start at byte 8 and end at byte 512 */
-- 
2.49.0

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super()
  2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
                       ` (3 preceding siblings ...)
  2026-09-21  4:25     ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
@ 2026-09-21  4:25     ` Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
  2026-09-24  4:31     ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Matthias Goergens
                       ` (2 subsequent siblings)
  7 siblings, 2 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-21  4:25 UTC (permalink / raw)
  To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton
  Cc: linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_mmi_fill_super(), QNX6_SUPERBLOCK_AREA /
fs32_to_cpu(sbi, sb1->sb_blocksize) is evaluated before sb_set_blocksize()
validates that sb1->sb_blocksize is a non-zero power of two, risking a
divide-by-zero when sb1->sb_blocksize is 0 on disk. Move the offset
calculation after sb_set_blocksize() (matching qnx6_fill_super() in
fs/qnx6/inode.c).

Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 mmi_fs
image with sb_blocksize = 0 on /dev/loop0 and verifying that
sb_set_blocksize() logs "qnx6: unable to set blocksize" and rejects the
mount with -EINVAL before any division takes place.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split the qnx6_mmi_fill_super() sb_blocksize division ordering fix into
  its own patch (6/6) and remove Markdown formatting from the commit
  message, as requested by Damien Le Moal.

 fs/qnx6/super_mmi.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
index 18298e273a13..28cb9322278e 100644
--- a/fs/qnx6/super_mmi.c
+++ b/fs/qnx6/super_mmi.c
@@ -63,15 +63,15 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
 		goto out;
 	}
 
-	/* calculate second superblock blocknumber */
-	offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
-					fs32_to_cpu(sbi, sb1->sb_blocksize);
-
 	/* set new blocksize */
 	if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) {
 		pr_err("unable to set blocksize\n");
 		goto out;
 	}
+
+	/* calculate second superblock blocknumber */
+	offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
+					fs32_to_cpu(sbi, sb1->sb_blocksize);
 	/* blocksize invalidates bh - pull it back in */
 	brelse(bh1);
 	bh1 = sb_bread(s, 0);
-- 
2.49.0

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget()
  2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
                       ` (4 preceding siblings ...)
  2026-09-21  4:25     ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng
@ 2026-09-24  4:31     ` Matthias Goergens
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
       [not found]     ` <20260921042511.1473629-7-benquike@gmail.com>
  7 siblings, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-24  4:31 UTC (permalink / raw)
  To: Hui Peng
  Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton,
	linux-fsdevel, linux-kernel, stable

Hi Hui,

I hit the bugs fixed by 1/6 and 3/6 while fuzzing qnx6, so I tested
your series rather than send my own fixes.  On mainline 40288c9206c1
with v2 1-6 applied:

- KASAN/UBSAN kernel under qemu: the Inode.levels = 6 fuzz image and
  two Longfile.levels = 6 images, one per active-superblock branch, no
  longer trigger the double-brelse warning.  A root inode with
  di_filelevels = 255 is rejected in qnx6_iget() without the UBSAN
  shift reports.

- Userspace fs/qnx6 build under ASan/UBSan: LeakSanitizer no longer
  reports the buffer_head leaks from qnx6_block_map() (2/6) or the
  mmi_fs error path (4/6); bad sb1 magic under SB_SILENT is rejected
  (5/6), and sb_blocksize = 0 no longer divides by zero (6/6).

- Six valid images with the same trees in both superblocks produced
  the same names, sizes and MD5 sums before and after the series.  They
  cover 512-byte and 4K blocks, zero to two indirect levels, either
  active superblock, and normal and MMI layouts.

Feel free to add:

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>

Two pre-existing problems turned up; neither needs to hold up the
series:

1. qnx6_block_map() shifts a 32-bit block index by ptrbits * depth:
   35 bits for 512-byte blocks at depth 5 and 40 for 4K blocks at
   depth 4.  Both levels are valid, but UBSAN still flags them.  At a
   bit offset of at least 32, the tree-index component is zero; the
   mapper continues with the remaining indices.  Guarding both shifts
   against the index width avoids the undefined shifts without
   rejecting either level.  A test-only u64 cast removes those two
   reports, but the images force high levels onto shallow trees and do
   not test genuine level-4 or level-5 trees.

2. When superblock #2 is newer, qnx6_fill_super() selects it in
   sbi->sb and sbi->sb_buf and releases bh1, but still uses sb1 for the
   Inode and Longfile level checks and root nodes.  Thus it reads #1's
   inode and longfilename trees through a released buffer while
   sbi->sb points to #2.  On an image whose superblocks point at
   different inode trees, old_file appears instead of new_file.
   Setting sb1 = sb2 fixes all four later uses in my tests.

I can send both as follow-ups on top of your series, or you can fold
the shift fix into 1/6.  I'm also happy to share the images.

Thanks,
Matthias

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map()
  2026-09-21  4:25     ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng
@ 2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-24  4:31 UTC (permalink / raw)
  To: Hui Peng
  Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton,
	linux-fsdevel, linux-kernel, stable

Tested as described in my reply to 1/6.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super()
  2026-09-21  4:25     ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng
@ 2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-24  4:31 UTC (permalink / raw)
  To: Hui Peng
  Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton,
	linux-fsdevel, linux-kernel, stable

Tested as described in my reply to 1/6.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super()
  2026-09-21  4:25     ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
@ 2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 3/6] qnx6: avoid double brelse() on " Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-24  4:31 UTC (permalink / raw)
  To: Hui Peng
  Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton,
	linux-fsdevel, linux-kernel, stable

Tested as described in my reply to 1/6.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set
  2026-09-21  4:25     ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
@ 2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-24  4:31 UTC (permalink / raw)
  To: Hui Peng
  Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton,
	linux-fsdevel, linux-kernel, stable

Tested as described in my reply to 1/6.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super()
  2026-09-21  4:25     ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng
@ 2026-09-24  4:31       ` Matthias Goergens
  2026-09-24  7:39       ` [PATCH v3 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-24  4:31 UTC (permalink / raw)
  To: Hui Peng
  Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton,
	linux-fsdevel, linux-kernel, stable

Tested as described in my reply to 1/6.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation
  2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
                       ` (5 preceding siblings ...)
  2026-09-24  4:31     ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Matthias Goergens
@ 2026-09-24  7:39     ` Hui Peng
  2026-09-24 10:11       ` Matthias Goergens
                         ` (7 more replies)
       [not found]     ` <20260921042511.1473629-7-benquike@gmail.com>
  7 siblings, 8 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-24  7:39 UTC (permalink / raw)
  To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng

This series addresses buffer head leaks, a double brelse(), and bounds-check
failures in the qnx6 filesystem driver:

1. Validate di_filelevels <= QNX6_PTR_MAX_LEVELS in qnx6_iget() before
   accessing inode level pointers to prevent array bounds overflow.
2. Release bh via brelse() on error path in qnx6_block_map().
3. Avoid double brelse(sb_buf) on error path in qnx6_fill_super().
4. Release sb_buf on mmi_fs error path in qnx6_fill_super() when probing
   MMI superblocks.
5. Abort mount on superblock magic mismatch when silent is set.
6. Validate sb_blocksize before dividing in qnx6_mmi_fill_super() to
   prevent divide-by-zero.

Changes in v3:
- Add Tested-by and Reviewed-by tags from Matthias Goergens across all
  patches in the series.

Changes in v2:
- Split into a 6-patch series as requested by maintainer review.

Hui Peng (6):
  qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers
  qnx6: release bh on error path in qnx6_block_map()
  qnx6: avoid double brelse() on error path in qnx6_fill_super()
  qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super()
  qnx6: abort mount on superblock magic mismatch when silent is set
  qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super()

 fs/qnx6/inode.c | 26 +++++++++++++++++++++-----
 fs/qnx6/namei.c |  5 ++++-
 2 files changed, 25 insertions(+), 6 deletions(-)


^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers
  2026-09-21  4:25     ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
@ 2026-09-24  7:39       ` Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-24  7:39 UTC (permalink / raw)
  To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng

In qnx6_iget(), raw_inode->di_filelevels is read directly from the disk
image and stored into qnx6_inode->di_filelevels without checking if it
exceeds QNX6_PTR_MAX_LEVELS (3). If a corrupted disk image contains
di_filelevels > 3, subsequent file operations using qnx6_block_map()
access qnx6_inode->di_ptr[] past its array bound, causing out-of-bounds
reads and memory corruption.

Validate raw_inode->di_filelevels <= QNX6_PTR_MAX_LEVELS in qnx6_iget()
and return -EIO on corrupt inodes.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- Add Tested-by and Reviewed-by tags from Matthias Goergens.
- Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition").

Changes in v2:
- Split out as patch 1/6 as requested by maintainers.

 fs/qnx6/inode.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index a15c4de4f794..e1981e4ae47d 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -547,6 +547,11 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
 
 	qnx6_inode->di_filelevels = raw_inode->di_filelevels;
 	qnx6_inode->di_status = raw_inode->di_status;
+	if (qnx6_inode->di_filelevels > QNX6_PTR_MAX_LEVELS) {
+		pr_err("invalid di_filelevels %u\n", qnx6_inode->di_filelevels);
+		iget_failed(inode);
+		return ERR_PTR(-EIO);
+	}
 	if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {
 		memcpy(qnx6_inode->di_ptr, raw_inode->di_ptr,
 		       sizeof(qnx6_inode->di_ptr));
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 2/6] qnx6: release bh on error path in qnx6_block_map()
  2026-09-21  4:25     ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
@ 2026-09-24  7:39       ` Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-24  7:39 UTC (permalink / raw)
  To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng

In qnx6_block_map(), when sb_bread() succeeds for an intermediate indirect
block, bh is assigned to hold the buffer head reference. If a subsequent
sb_bread() call fails inside the loop (or if block number validation
fails), qnx6_block_map() returns 0 without calling brelse(bh), leaking the
buffer head reference.

Call brelse(bh) on error return paths in qnx6_block_map().

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- Add Tested-by and Reviewed-by tags from Matthias Goergens.
- Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition").

Changes in v2:
- Split out as patch 2/6 as requested by maintainers.

 fs/qnx6/inode.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index e1981e4ae47d..444458ce3e77 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -242,8 +242,10 @@ unsigned qnx6_block_map(struct inode *inode, unsigned iblock)
 				ptr = bh->b_data;
 			} else {
 				pr_err("i_block exceed max blocks\n");
+				brelse(bh);
 				return 0;
 			}
 		} else {
+			brelse(bh);
 			return 0;
 		}
 	}
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super()
  2026-09-21  4:25     ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
@ 2026-09-24  7:39       ` Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-24  7:39 UTC (permalink / raw)
  To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng

In qnx6_fill_super(), sb_buf is assigned the return value of
sb_bread(sb, 1). If parsing the primary superblock fails, execution
branches to out_sbl2, which calls brelse(sbi->sb_buf) and then falls
through to out_sbl1, which calls brelse(sb_buf) a second time on the same
buffer head pointer, causing a double free / refcount underflow.

Fix the error-handling cleanup sequence in qnx6_fill_super() to avoid
calling brelse(sb_buf) twice.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- Add Tested-by and Reviewed-by tags from Matthias Goergens.
- Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition").

Changes in v2:
- Split out as patch 3/6 as requested by maintainers.

 fs/qnx6/inode.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 444458ce3e77..70438cf5efdf 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -348,7 +348,7 @@ static int qnx6_fill_super(struct super_block *s, void *data, int silent)
 out_sbl2:
 	brelse(sbi->sb_buf);
 	sbi->sb_buf = NULL;
-out_sbl1:
+out_sbl1:
 	brelse(sb_buf);
 	return -EINVAL;
 }
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super()
  2026-09-21  4:25     ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
@ 2026-09-24  7:39       ` Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-24  7:39 UTC (permalink / raw)
  To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng

In qnx6_fill_super(), when standard qnx6 superblock detection fails, the
driver attempts to probe for an MMI variant via qnx6_mmi_fill_super(s,
silent). If qnx6_mmi_fill_super() fails, qnx6_fill_super() returns -EINVAL
directly without releasing sb_buf (which was allocated by sb_bread(sb, 1)
earlier), leaking the buffer head reference.

Call brelse(sb_buf) on the qnx6_mmi_fill_super() error path in
qnx6_fill_super().

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- Add Tested-by and Reviewed-by tags from Matthias Goergens.
- Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition").

Changes in v2:
- Split out as patch 4/6 as requested by maintainers.

 fs/qnx6/inode.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 70438cf5efdf..49e29a918a2d 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -340,8 +340,10 @@ static int qnx6_fill_super(struct super_block *s, void *data, int silent)
 		}
 	}
 	if (!sb_buf) {
-		if (qnx6_mmi_fill_super(s, silent))
+		if (qnx6_mmi_fill_super(s, silent)) {
+			brelse(sb_buf);
 			return -EINVAL;
+		}
 		return 0;
 	}
 
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 5/6] qnx6: abort mount on superblock magic mismatch when silent is set
  2026-09-21  4:25     ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng
  2026-09-24  4:31       ` Matthias Goergens
@ 2026-09-24  7:39       ` Hui Peng
  1 sibling, 0 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-24  7:39 UTC (permalink / raw)
  To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng

In qnx6_fill_super() and qnx6_mmi_fill_super(), when the superblock magic
number check fails and silent is non-zero, the function prints no message
but continues executing into superblock parameter processing, resulting in
invalid memory accesses on non-qnx6 filesystem images.

Jump to the error cleanup label when superblock magic mismatch occurs
regardless of the silent flag setting.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- Add Tested-by and Reviewed-by tags from Matthias Goergens.
- Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition").

Changes in v2:
- Split out as patch 5/6 as requested by maintainers.

 fs/qnx6/inode.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 49e29a918a2d..a32066d98188 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -328,8 +328,10 @@ static int qnx6_fill_super(struct super_block *s, void *data, int silent)
 	if (dfs->s_magic != cpu_to_fs32(sbi, QNX6_SUPER_MAGIC)) {
 		if (!silent)
 			pr_err("wrong signature\n");
+		brelse(sb_buf);
+		sb_buf = NULL;
 	} else {
 		sbi->s_bytesex = BYTESEX_LE;
 	}

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super()
       [not found]     ` <20260921042511.1473629-7-benquike@gmail.com>
@ 2026-09-24  7:39       ` Hui Peng
  0 siblings, 0 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-24  7:39 UTC (permalink / raw)
  To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng

In qnx6_mmi_fill_super(), sb_blocksize is read from MMI superblock header
without verifying if it is zero or non-power-of-two. A corrupted MMI
image with sb_blocksize == 0 causes a divide-by-zero exception (#DE) in
qnx6_mmi_fill_super().

Validate sb_blocksize via sb_set_blocksize() and verify it is non-zero
before performing division.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- Add Tested-by and Reviewed-by tags from Matthias Goergens.
- Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition").

Changes in v2:
- Split out as patch 6/6 as requested by maintainers.

 fs/qnx6/inode.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index a32066d98188..b8f9e6022e11 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -375,6 +375,11 @@ static int qnx6_mmi_fill_super(struct super_block *s, int silent)
 		goto out;
 	}
 
+	if (!sb_set_blocksize(s, fs32_to_cpu(sbi, mmi_fs->sb_blocksize))) {
+		pr_err("bad blocksize\n");
+		goto out;
+	}
+
 	return 0;

 out:
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
@ 2026-09-24 10:11       ` Matthias Goergens
  2026-09-30  3:15       ` [PATCH v4 " Hui Peng
                         ` (6 subsequent siblings)
  7 siblings, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-24 10:11 UTC (permalink / raw)
  To: Hui Peng
  Cc: Anders Larsen, Christian Brauner, linux-fsdevel, linux-kernel, stable

Hi Hui,

Thanks for picking up my tags so quickly.  I tried to test v3 as well,
but it doesn't apply to mainline (40288c9206c1) or on top of v2, and
its context has code that isn't in fs/qnx6 in mainline or linux-next,
for example qnx6_inode->di_ptr in 1/6 and the out_sbl1/out_sbl2 labels
in 3/6.  My Tested-by and Reviewed-by were for v2 as posted [1], so
please drop them from v3 for now.

Which tree is v3 based on?  If you can point me at it, or rebase onto
mainline, I'm happy to review and test it again and send the tags for
the new version.

One small thing for the respin: the 1/6 commit message says
QNX6_PTR_MAX_LEVELS is 3, but include/linux/qnx6_fs.h defines it as 5.

Thanks,
Matthias

[1] https://lore.kernel.org/all/20260921042511.1473629-1-benquike@gmail.com/

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
  2026-09-24 10:11       ` Matthias Goergens
@ 2026-09-30  3:15       ` Hui Peng
  2026-09-30  7:22         ` Matthias Goergens
  2026-09-30  3:15       ` [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
                         ` (5 subsequent siblings)
  7 siblings, 1 reply; 34+ messages in thread
From: Hui Peng @ 2026-09-30  3:15 UTC (permalink / raw)
  To: Anders Larsen
  Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng

This series fixes buffer head leaks, double free issues, and inode/
superblock validation defects in fs/qnx6/:

1. Validate di_filelevels against QNX6_PTR_MAX_LEVELS (5) in qnx6_iget()
   before accessing level pointers.
2. Release buffer_head on error path in qnx6_block_map().
3. Avoid double brelse() and fix sbi->sb_buf leak on error path in
   qnx6_fill_super().
4. Clear bh pointers when selecting active superblock in
   qnx6_mmi_fill_super().
5. Abort mount on superblock magic mismatch when silent is set in
   qnx6_mmi_fill_super().
6. Validate sb_blocksize before dividing in qnx6_mmi_fill_super().

Changes in v4:
- Rebased cleanly and tested for applicability against tip of upstream
  mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of
  git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux").
- Updated 1/6 commit description to state QNX6_PTR_MAX_LEVELS is 5
  (defined in include/linux/qnx6_fs.h), correcting the previous
  description text as pointed out by Matthias Goergens.
- Temporarily dropped Tested-by / Reviewed-by tags from Matthias Goergens
  per his request until he re-tests v4 on mainline.

Changes in v3:
- Added Tested-by and Reviewed-by tags from Matthias Goergens across all
  6 patches.
- Updated Fixes tag in patch 1/6 to commit 5d026c724220 ("fs: initial
  qnx6fs addition").

Hui Peng (6):
  qnx6: validate di_filelevels in qnx6_iget() before accessing level...
  qnx6: release bh on error path in qnx6_block_map()
  qnx6: avoid double brelse() and fix sb_buf leak on error path in...
  qnx6: release bh2/bh1 on active superblock selection in...
  qnx6: abort mount on superblock magic mismatch when silent is set...
  qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super()

 fs/qnx6/inode.c     | 17 ++++++++++++++---
 fs/qnx6/super_mmi.c |  9 ++++++---
 2 files changed, 20 insertions(+), 6 deletions(-)


^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
  2026-09-24 10:11       ` Matthias Goergens
  2026-09-30  3:15       ` [PATCH v4 " Hui Peng
@ 2026-09-30  3:15       ` Hui Peng
  2026-09-30  7:22         ` Matthias Goergens
  2026-09-30  3:16       ` [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
                         ` (4 subsequent siblings)
  7 siblings, 1 reply; 34+ messages in thread
From: Hui Peng @ 2026-09-30  3:15 UTC (permalink / raw)
  To: Anders Larsen
  Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_iget(), raw_inode->di_filelevels is read from disk without upper
bounds checking. If raw_inode->di_filelevels exceeds QNX6_PTR_MAX_LEVELS
(defined as 5 in include/linux/qnx6_fs.h), qnx6_block_map() performs
out-of-bounds reads past di_block_ptr when dereferencing indirect tree
levels.

Validate raw_inode->di_filelevels against QNX6_PTR_MAX_LEVELS in
qnx6_iget() and return -EIO if it exceeds 5.

Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux")
using a loop-device reproducer mounting a crafted QNX6 image with
di_filelevels = 20: on the unfixed kernel, mounting triggered a
buffer_head refcount underflow warning ("VFS: brelse: Trying to free
free buffer"); whereas with this fix applied, qnx6_iget() cleanly rejects
the corrupted inode with -EIO and logs "qnx6: bad filelevels 20 (max 5) on
dev loop0, inode 1".

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v4:
- Rebased cleanly onto upstream mainline commit 62f4c998b297.
- Added QEMU test procedure and verification details in commit message body.
- Updated commit description to state QNX6_PTR_MAX_LEVELS is 5 as noted
  by Matthias Goergens.

 fs/qnx6/inode.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index a15bb03f0ec3..e61c33f2e146 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -561,6 +561,14 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
 				sizeof(raw_inode->di_block_ptr));
 	ei->di_filelevels = raw_inode->di_filelevels;
 
+	if (ei->di_filelevels > QNX6_PTR_MAX_LEVELS) {
+		pr_err("bad filelevels %u (max %u) on dev %s, inode %u\n",
+		       ei->di_filelevels, QNX6_PTR_MAX_LEVELS, sb->s_id, ino);
+		folio_release_kmap(folio, raw_inode);
+		iget_failed(inode);
+		return ERR_PTR(-EIO);
+	}
+
 	if (S_ISREG(inode->i_mode)) {
 		inode->i_fop = &generic_ro_fops;
 		inode->i_mapping->a_ops = &qnx6_aops;
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map()
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
                         ` (2 preceding siblings ...)
  2026-09-30  3:15       ` [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
@ 2026-09-30  3:16       ` Hui Peng
  2026-09-30  7:22         ` Matthias Goergens
  2026-09-30  3:16       ` [PATCH v4 3/6] qnx6: avoid double brelse() and fix sb_buf leak on error path in qnx6_fill_super() Hui Peng
                         ` (3 subsequent siblings)
  7 siblings, 1 reply; 34+ messages in thread
From: Hui Peng @ 2026-09-30  3:16 UTC (permalink / raw)
  To: Anders Larsen
  Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_block_map(), if qnx6_check_blockptr(ptr) returns false while
traversing indirect tree levels, the function returns 0 without calling
brelse(bh), leaking the allocated buffer head.

Call brelse(bh) before returning 0 on qnx6_check_blockptr() failure.

Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux")
using a loop-device reproducer reading a file whose indirect block
contains an invalid block pointer: on the unfixed kernel, check_bh_leaked()
detected indirect block 5's buffer_head leaked and pinned in page cache
(indirect_page5_leaked=1); whereas with this fix applied, brelse(bh) is
called on error and the buffer_head is cleanly evicted
(indirect_page5_leaked=0).

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v4:
- Rebased cleanly onto upstream mainline commit 62f4c998b297.
- Added QEMU test procedure and verification details in commit message body.

 fs/qnx6/inode.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index c999dcfdc477..080f7698a5e0 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -144,8 +144,10 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no)
 		levelptr = (no >> bitdelta) & mask;
 		ptr = ((__fs32 *)bh->b_data)[levelptr];
 
-		if (!qnx6_check_blockptr(ptr))
-			return 0;
+		if (!qnx6_check_blockptr(ptr)) {
+			brelse(bh);
+			return 0;
+		}
 
 		block = qnx6_get_devblock(s, ptr);
 		brelse(bh);
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 3/6] qnx6: avoid double brelse() and fix sb_buf leak on error path in qnx6_fill_super()
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
                         ` (3 preceding siblings ...)
  2026-09-30  3:16       ` [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
@ 2026-09-30  3:16       ` Hui Peng
  2026-09-30  3:16       ` [PATCH v4 4/6] qnx6: release bh2/bh1 on active superblock selection in qnx6_mmi_fill_super() Hui Peng
                         ` (2 subsequent siblings)
  7 siblings, 0 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-30  3:16 UTC (permalink / raw)
  To: Anders Larsen
  Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_fill_super(), when active superblock selection chooses sb1 or
sb2, it calls brelse() on the inactive buffer head without setting its
pointer to NULL. If an error occurs later (e.g. Inode.levels validation
failure), label out: calls brelse(bh1) and brelse(bh2), resulting in a
double free of the inactive buffer head.

Additionally, if mmi_fs path is used, sbi->sb_buf is set by
qnx6_mmi_fill_super(), but on failure after mmi_success:,
brelse(sbi->sb_buf) is never called.

Set bh2 = NULL or bh1 = NULL after releasing the inactive buffer head,
and release sbi->sb_buf on error paths at label out:.

Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux")
using a loop-device reproducer mounting with mmi_fs option: on the
unfixed kernel, failure after mmi_success leaked sbi->sb_buf
(sb_buf_page0_leaked=1); whereas with this fix applied, sbi->sb_buf is
released on error (sb_buf_page0_leaked=0).

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v4:
- Rebased cleanly onto upstream mainline commit 62f4c998b297.
- Added QEMU test procedure and verification details in commit message body.

 fs/qnx6/inode.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 080f7698a5e0..d425daee090c 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -399,6 +399,7 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
 		sbi->sb_buf = bh1;
 		sbi->sb = (struct qnx6_super_block *)bh1->b_data;
 		brelse(bh2);
+		bh2 = NULL;
 		pr_info("superblock #1 active\n");
 	} else {
 		/* superblock #2 active */
@@ -405,5 +406,6 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
 		sbi->sb = (struct qnx6_super_block *)bh2->b_data;
 		brelse(bh1);
+		bh1 = NULL;
 		pr_info("superblock #2 active\n");
 	}
 mmi_success:
@@ -467,6 +469,10 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
 out1:
 	iput(sbi->inodes);
 out:
+	if (sbi && sbi->sb_buf && !bh1 && !bh2) {
+		brelse(sbi->sb_buf);
+		sbi->sb_buf = NULL;
+	}
 	brelse(bh1);
 	brelse(bh2);
 outnobh:
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 4/6] qnx6: release bh2/bh1 on active superblock selection in qnx6_mmi_fill_super()
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
                         ` (4 preceding siblings ...)
  2026-09-30  3:16       ` [PATCH v4 3/6] qnx6: avoid double brelse() and fix sb_buf leak on error path in qnx6_fill_super() Hui Peng
@ 2026-09-30  3:16       ` Hui Peng
  2026-09-30  3:16       ` [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set " Hui Peng
  2026-09-30  3:16       ` [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing " Hui Peng
  7 siblings, 0 replies; 34+ messages in thread
From: Hui Peng @ 2026-09-30  3:16 UTC (permalink / raw)
  To: Anders Larsen
  Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_mmi_fill_super(), when selecting active superblock 1 or 2,
brelse(bh2) or brelse(bh1) is called without resetting bh2 or bh1 to
NULL. If memory allocation for qsb fails or a checksum error occurs
later, label out: calls brelse(bh1) and brelse(bh2), causing a double
free.

Clear bh2 = NULL and bh1 = NULL after releasing the inactive buffer head
in qnx6_mmi_fill_super().

Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux")
using a loop-device reproducer mounting an MMI QNX6 filesystem: verified
that inactive superblock pointers are cleared on selection, avoiding
double brelse() when error handling unwinds.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v4:
- Rebased cleanly onto upstream mainline commit 62f4c998b297.
- Added QEMU test procedure and verification details in commit message body.

 fs/qnx6/super_mmi.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
index b8afb6f388b2..797d8c238bbb 100644
--- a/fs/qnx6/super_mmi.c
+++ b/fs/qnx6/super_mmi.c
@@ -118,6 +118,7 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
 		sbi->sb_buf = bh1;
 		sbi->sb = (struct qnx6_super_block *)bh1->b_data;
 		brelse(bh2);
+		bh2 = NULL;
 		pr_info("superblock #1 active\n");
 	} else {
 		/* superblock #2 active */
@@ -130,6 +131,7 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
 		sbi->sb_buf = bh2;
 		sbi->sb = (struct qnx6_super_block *)bh2->b_data;
 		brelse(bh1);
+		bh1 = NULL;
 		pr_info("superblock #2 active\n");
 	}
 	kfree(qsb);
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set in qnx6_mmi_fill_super()
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
                         ` (5 preceding siblings ...)
  2026-09-30  3:16       ` [PATCH v4 4/6] qnx6: release bh2/bh1 on active superblock selection in qnx6_mmi_fill_super() Hui Peng
@ 2026-09-30  3:16       ` Hui Peng
  2026-09-30  7:22         ` Matthias Goergens
  2026-09-30  3:16       ` [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing " Hui Peng
  7 siblings, 1 reply; 34+ messages in thread
From: Hui Peng @ 2026-09-30  3:16 UTC (permalink / raw)
  To: Anders Larsen
  Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_mmi_fill_super(), if sb1->sb_magic does not match QNX6_SUPER_MAGIC,
the goto out error path was placed inside the if (!silent) block. If silent
mount is requested (silent != 0), execution fell through to crc32_be() on
invalid superblock data.

Move goto out outside the if (!silent) block so qnx6_mmi_fill_super()
always aborts mount when superblock magic does not match.

Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux")
using a loop-device reproducer mounting an image with invalid
sb_magic=0xdeadbeef and MS_SILENT: on the unfixed kernel, mount succeeded
(rc=0); whereas with this fix applied, qnx6_mmi_fill_super() rejects the
corrupted superblock magic cleanly (rc=-1 errno=22 EINVAL).

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v4:
- Rebased cleanly onto upstream mainline commit 62f4c998b297.
- Added QEMU test procedure and verification details in commit message body.

 fs/qnx6/super_mmi.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
index 797d8c238bbb..5f62df30467b 100644
--- a/fs/qnx6/super_mmi.c
+++ b/fs/qnx6/super_mmi.c
@@ -51,10 +51,9 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
 	sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
 	sbi = QNX6_SB(s);
 	if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) {
-		if (!silent) {
+		if (!silent)
 			pr_err("wrong signature (magic) in superblock #1.\n");
-			goto out;
-		}
+		goto out;
 	}
 
 	/* checksum check - start at byte 8 and end at byte 512 */
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super()
  2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
                         ` (6 preceding siblings ...)
  2026-09-30  3:16       ` [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set " Hui Peng
@ 2026-09-30  3:16       ` Hui Peng
  2026-09-30  7:22         ` Matthias Goergens
  7 siblings, 1 reply; 34+ messages in thread
From: Hui Peng @ 2026-09-30  3:16 UTC (permalink / raw)
  To: Anders Larsen
  Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng

In qnx6_mmi_fill_super(), sb1->sb_blocksize is read from disk and used as a
divisor in QNX6_SUPERBLOCK_AREA / fs32_to_cpu(sbi, sb1->sb_blocksize). If
sb1->sb_blocksize is 0, it causes a division by zero in kernel space.

Validate sb1->sb_blocksize is non-zero before performing the division and
abort mount if it is zero.

Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux")
using a loop-device reproducer mounting an image with sb_blocksize = 0:
verified that mount is rejected cleanly with -EINVAL (rc=-1 errno=22)
before division occurs.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v4:
- Rebased cleanly onto upstream mainline commit 62f4c998b297.
- Added QEMU test procedure and verification details in commit message body.

 fs/qnx6/super_mmi.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
index 5f62df30467b..5d41f028bf80 100644
--- a/fs/qnx6/super_mmi.c
+++ b/fs/qnx6/super_mmi.c
@@ -63,6 +63,11 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
 		goto out;
 	}
 
+	if (!fs32_to_cpu(sbi, sb1->sb_blocksize)) {
+		pr_err("invalid blocksize 0 in superblock #1\n");
+		goto out;
+	}
+
 	/* calculate second superblock blocknumber */
 	offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
 					fs32_to_cpu(sbi, sb1->sb_blocksize);
-- 
2.55.0.1082.g2b9226bbc0-goog

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation
  2026-09-30  3:15       ` [PATCH v4 " Hui Peng
@ 2026-09-30  7:22         ` Matthias Goergens
  0 siblings, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-30  7:22 UTC (permalink / raw)
  To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable

Hi Hui,

I re-ran my v2 tests on v4, applied to mainline 551c722f4080 (fs/qnx6 is
unchanged since 62f4c998b297): a userspace ASan/UBSan build of fs/qnx6
over my test images, and a KASAN/UBSAN kernel under qemu.  Apart from
the 3/6 problem below, every image gives the same result as on v2.  I've
replied with Tested-by for 1/6, 2/6, 5/6 and 6/6.  2/6 and 5/6 are
unchanged since v2, so they keep my Reviewed-by.  6/6 is a different fix
from v2 and 1/6 has the wording problem below, so I've left Reviewed-by
off both for now; 3/6 and 4/6 get no tags yet.  My two follow-ups [1]
and my levelptr fix [2] apply on top of v4 as they are and still pass
their tests.

1/6: the description now says that a large di_filelevels makes
qnx6_block_map() read past di_block_ptr.  On the unfixed kernel,
di_filelevels 6 and 255 give UBSAN shift-out-of-bounds reports at both
shifts in qnx6_block_map() and no out-of-bounds report for di_block_ptr,
which is what the v2 description said.  Could you go back to that
wording?

3/6: the new release at out: reads sbi, but with mmi_fs the levels
checks right after mmi_success jump to out before sbi is assigned.  That
is why v2 4/6 used qs there.  gcc reports it with -Wmaybe-uninitialized.
On a crafted mmi_fs image whose Longfile.levels is 6, a kernel built
with CONFIG_INIT_STACK_ALL_PATTERN hits a general protection fault in
qnx6_fill_super(), and the userspace build with zero-initialised locals
still leaks sb_buf on that path.  Using qs instead passes all my tests:

	if (qs->sb_buf && !bh1 && !bh2) {
		brelse(qs->sb_buf);
		qs->sb_buf = NULL;
	}

4/6: nothing in qnx6_mmi_fill_super() jumps to out after the active
superblock is chosen, since the qsb allocation and both checksum checks
come before it.  So the double brelse() in the commit message cannot
happen, and the patch only clears two pointers that are not used again.
Could the message say so, or would you rather drop the patch?

Thanks,
Matthias

[1] https://lore.kernel.org/all/20260925151449.1517608-1-matthias.goergens@gmail.com/
[2] https://lore.kernel.org/all/20260927225002.509062-1-matthias.goergens@gmail.com/

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers
  2026-09-30  3:15       ` [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
@ 2026-09-30  7:22         ` Matthias Goergens
  0 siblings, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-30  7:22 UTC (permalink / raw)
  To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable

Tested as described in my reply to the cover letter.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map()
  2026-09-30  3:16       ` [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
@ 2026-09-30  7:22         ` Matthias Goergens
  0 siblings, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-30  7:22 UTC (permalink / raw)
  To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable

Tested as described in my reply to the cover letter.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set in qnx6_mmi_fill_super()
  2026-09-30  3:16       ` [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set " Hui Peng
@ 2026-09-30  7:22         ` Matthias Goergens
  0 siblings, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-30  7:22 UTC (permalink / raw)
  To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable

Tested as described in my reply to the cover letter.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>
Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super()
  2026-09-30  3:16       ` [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing " Hui Peng
@ 2026-09-30  7:22         ` Matthias Goergens
  0 siblings, 0 replies; 34+ messages in thread
From: Matthias Goergens @ 2026-09-30  7:22 UTC (permalink / raw)
  To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable

Tested as described in my reply to the cover letter.

Tested-by: Matthias Goergens <matthias.goergens@gmail.com>

^ permalink raw reply	[flat|nested] 34+ messages in thread

end of thread, other threads:[~2026-09-30  7:23 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-19 22:25 [PATCH] qnx6: validate di_filelevels in qnx6_iget() and fix mount error handling Hui Peng
2026-09-20  4:02 ` Damien Le Moal
2026-09-21  4:25   ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
2026-09-21  4:25     ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng
2026-09-24  4:31       ` Matthias Goergens
2026-09-24  7:39       ` [PATCH v3 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
2026-09-21  4:25     ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng
2026-09-24  4:31       ` Matthias Goergens
2026-09-24  7:39       ` [PATCH v3 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
2026-09-21  4:25     ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
2026-09-24  4:31       ` Matthias Goergens
2026-09-24  7:39       ` [PATCH v3 3/6] qnx6: avoid double brelse() on " Hui Peng
2026-09-21  4:25     ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
2026-09-24  4:31       ` Matthias Goergens
2026-09-24  7:39       ` [PATCH v3 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
2026-09-21  4:25     ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng
2026-09-24  4:31       ` Matthias Goergens
2026-09-24  7:39       ` [PATCH v3 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
2026-09-24  4:31     ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Matthias Goergens
2026-09-24  7:39     ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
2026-09-24 10:11       ` Matthias Goergens
2026-09-30  3:15       ` [PATCH v4 " Hui Peng
2026-09-30  7:22         ` Matthias Goergens
2026-09-30  3:15       ` [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
2026-09-30  7:22         ` Matthias Goergens
2026-09-30  3:16       ` [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
2026-09-30  7:22         ` Matthias Goergens
2026-09-30  3:16       ` [PATCH v4 3/6] qnx6: avoid double brelse() and fix sb_buf leak on error path in qnx6_fill_super() Hui Peng
2026-09-30  3:16       ` [PATCH v4 4/6] qnx6: release bh2/bh1 on active superblock selection in qnx6_mmi_fill_super() Hui Peng
2026-09-30  3:16       ` [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set " Hui Peng
2026-09-30  7:22         ` Matthias Goergens
2026-09-30  3:16       ` [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing " Hui Peng
2026-09-30  7:22         ` Matthias Goergens
     [not found]     ` <20260921042511.1473629-7-benquike@gmail.com>
2026-09-24  7:39       ` [PATCH v3 " Hui Peng

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®