* [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®