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; 8+ 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] 8+ 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; 8+ 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] 8+ 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
                       ` (4 more replies)
  0 siblings, 5 replies; 8+ 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] 8+ 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-21  4:25     ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng
                       ` (3 subsequent siblings)
  4 siblings, 0 replies; 8+ 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] 8+ 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-21  4:25     ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs " Hui Peng
                       ` (2 subsequent siblings)
  4 siblings, 0 replies; 8+ 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] 8+ 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-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     ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng
  4 siblings, 0 replies; 8+ 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] 8+ 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 " Hui Peng
@ 2026-09-21  4:25     ` Hui Peng
  2026-09-21  4:25     ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng
  4 siblings, 0 replies; 8+ 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] 8+ 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
  4 siblings, 0 replies; 8+ 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] 8+ messages in thread

end of thread, other threads:[~2026-09-21  4:25 UTC | newest]

Thread overview: 8+ 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-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     ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs " 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-21  4:25     ` [PATCH v2 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®