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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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
       [not found]     ` <20260921042511.1473629-7-benquike@gmail.com>
  7 siblings, 1 reply; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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
  0 siblings, 0 replies; 22+ 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] 22+ messages in thread

end of thread, other threads:[~2026-09-24 10:11 UTC | newest]

Thread overview: 22+ 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
     [not found]     ` <20260921042511.1473629-7-benquike@gmail.com>
2026-09-24  7:39       ` [PATCH v3 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() 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®