* [PATCH] qnx6: validate di_filelevels in qnx6_iget() and fix mount error handling
@ 2026-09-19 22:25 Hui Peng
2026-09-20 4:02 ` Damien Le Moal
0 siblings, 1 reply; 34+ messages in thread
From: Hui Peng @ 2026-09-19 22:25 UTC (permalink / raw)
To: brauner, jack, dlemoal, jlayton; +Cc: linux-fsdevel, linux-kernel
Fix two issues in fs/qnx6/:
1. In qnx6_iget(), reject raw_inode->di_filelevels > QNX6_PTR_MAX_LEVELS
so computing the maximum block count does not trigger shift-out-of-
bounds undefined behavior or out-of-bounds block pointer array walks.
2. In qnx6_fill_super() and qnx6_mmi_fill_super(), prevent MS_SILENT
from bypassing superblock magic validation and ensure sbi->sb_buf is
released exactly once on failure.
Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 6de49333acad..04214b829e10 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -144,8 +144,10 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no)
levelptr = (no >> bitdelta) & mask;
ptr = ((__fs32 *)bh->b_data)[levelptr];
- if (!qnx6_check_blockptr(ptr))
+ if (!qnx6_check_blockptr(ptr)) {
+ brelse(bh);
return 0;
+ }
block = qnx6_get_devblock(s, ptr);
brelse(bh);
@@ -397,12 +399,14 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
sbi->sb_buf = bh1;
sbi->sb = (struct qnx6_super_block *)bh1->b_data;
brelse(bh2);
+ bh2 = NULL;
pr_info("superblock #1 active\n");
} else {
/* superblock #2 active */
sbi->sb_buf = bh2;
sbi->sb = (struct qnx6_super_block *)bh2->b_data;
brelse(bh1);
+ bh1 = NULL;
pr_info("superblock #2 active\n");
}
mmi_success:
@@ -463,6 +467,8 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc)
out1:
iput(sbi->inodes);
out:
+ if (sbi->sb_buf && sbi->sb_buf != bh1 && sbi->sb_buf != bh2)
+ brelse(sbi->sb_buf);
brelse(bh1);
brelse(bh2);
outnobh:
@@ -560,6 +566,13 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr,
sizeof(raw_inode->di_block_ptr));
ei->di_filelevels = raw_inode->di_filelevels;
+ if (ei->di_filelevels > QNX6_PTR_MAX_LEVELS) {
+ pr_err("invalid filelevels (%u) in inode %u\n",
+ ei->di_filelevels, ino);
+ folio_release_kmap(folio, raw_inode);
+ iget_failed(inode);
+ return ERR_PTR(-EIO);
+ }
if (S_ISREG(inode->i_mode)) {
inode->i_fop = &generic_ro_fops;
diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c
index b8afb6f388b2..28cb9322278e 100644
--- a/fs/qnx6/super_mmi.c
+++ b/fs/qnx6/super_mmi.c
@@ -51,10 +51,9 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
sb1 = (struct qnx6_mmi_super_block *)bh1->b_data;
sbi = QNX6_SB(s);
if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) {
- if (!silent) {
+ if (!silent)
pr_err("wrong signature (magic) in superblock #1.\n");
- goto out;
- }
+ goto out;
}
/* checksum check - start at byte 8 and end at byte 512 */
@@ -64,15 +63,16 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent)
goto out;
}
- /* calculate second superblock blocknumber */
- offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
- fs32_to_cpu(sbi, sb1->sb_blocksize);
-
/* set new blocksize */
if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) {
pr_err("unable to set blocksize\n");
goto out;
}
+
+ /* calculate second superblock blocknumber */
+ offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA /
+ fs32_to_cpu(sbi, sb1->sb_blocksize);
+
/* blocksize invalidates bh - pull it back in */
brelse(bh1);
bh1 = sb_bread(s, 0);
^ permalink raw reply [flat|nested] 34+ messages in thread* Re: [PATCH] qnx6: validate di_filelevels in qnx6_iget() and fix mount error handling 2026-09-19 22:25 [PATCH] qnx6: validate di_filelevels in qnx6_iget() and fix mount error handling Hui Peng @ 2026-09-20 4:02 ` Damien Le Moal 2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng 0 siblings, 1 reply; 34+ messages in thread From: Damien Le Moal @ 2026-09-20 4:02 UTC (permalink / raw) To: Hui Peng, brauner, jack, jlayton; +Cc: linux-fsdevel, linux-kernel On 9/20/26 07:25, Hui Peng wrote: > Fix two issues in fs/qnx6/: > > 1. In qnx6_iget(), reject raw_inode->di_filelevels > QNX6_PTR_MAX_LEVELS > so computing the maximum block count does not trigger shift-out-of- > bounds undefined behavior or out-of-bounds block pointer array walks. > 2. In qnx6_fill_super() and qnx6_mmi_fill_super(), prevent MS_SILENT > from bypassing superblock magic validation and ensure sbi->sb_buf is > released exactly once on failure. Please split this into 2 patches, one patch for each problem you fix. > > Fixes: 5d026c724220 ("fs: initial qnx6fs addition") > Assisted-by: LLM > Signed-off-by: Hui Peng <benquike@gmail.com> > --- > diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c > index 6de49333acad..04214b829e10 100644 > --- a/fs/qnx6/inode.c > +++ b/fs/qnx6/inode.c > @@ -144,8 +144,10 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no) > levelptr = (no >> bitdelta) & mask; > ptr = ((__fs32 *)bh->b_data)[levelptr]; > > - if (!qnx6_check_blockptr(ptr)) > + if (!qnx6_check_blockptr(ptr)) { > + brelse(bh); > return 0; > + } > > block = qnx6_get_devblock(s, ptr); > brelse(bh); > @@ -397,12 +399,14 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc) > sbi->sb_buf = bh1; > sbi->sb = (struct qnx6_super_block *)bh1->b_data; > brelse(bh2); > + bh2 = NULL; > pr_info("superblock #1 active\n"); > } else { > /* superblock #2 active */ > sbi->sb_buf = bh2; > sbi->sb = (struct qnx6_super_block *)bh2->b_data; > brelse(bh1); > + bh1 = NULL; > pr_info("superblock #2 active\n"); > } > mmi_success: > @@ -463,6 +467,8 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc) > out1: > iput(sbi->inodes); > out: > + if (sbi->sb_buf && sbi->sb_buf != bh1 && sbi->sb_buf != bh2) > + brelse(sbi->sb_buf); > brelse(bh1); > brelse(bh2); > outnobh: > @@ -560,6 +566,13 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino) > memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr, > sizeof(raw_inode->di_block_ptr)); > ei->di_filelevels = raw_inode->di_filelevels; > + if (ei->di_filelevels > QNX6_PTR_MAX_LEVELS) { > + pr_err("invalid filelevels (%u) in inode %u\n", > + ei->di_filelevels, ino); > + folio_release_kmap(folio, raw_inode); > + iget_failed(inode); > + return ERR_PTR(-EIO); > + } > > if (S_ISREG(inode->i_mode)) { > inode->i_fop = &generic_ro_fops; > diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c > index b8afb6f388b2..28cb9322278e 100644 > --- a/fs/qnx6/super_mmi.c > +++ b/fs/qnx6/super_mmi.c > @@ -51,10 +51,9 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) > sb1 = (struct qnx6_mmi_super_block *)bh1->b_data; > sbi = QNX6_SB(s); > if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) { > - if (!silent) { > + if (!silent) > pr_err("wrong signature (magic) in superblock #1.\n"); > - goto out; > - } > + goto out; > } > > /* checksum check - start at byte 8 and end at byte 512 */ > @@ -64,15 +63,16 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) > goto out; > } > > - /* calculate second superblock blocknumber */ > - offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA / > - fs32_to_cpu(sbi, sb1->sb_blocksize); > - > /* set new blocksize */ > if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) { > pr_err("unable to set blocksize\n"); > goto out; > } > + > + /* calculate second superblock blocknumber */ > + offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA / > + fs32_to_cpu(sbi, sb1->sb_blocksize); > + > /* blocksize invalidates bh - pull it back in */ > brelse(bh1); > bh1 = sb_bread(s, 0); -- Damien Le Moal Western Digital Research ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() 2026-09-20 4:02 ` Damien Le Moal @ 2026-09-21 4:25 ` Hui Peng 2026-09-21 4:25 ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng ` (7 more replies) 0 siblings, 8 replies; 34+ messages in thread From: Hui Peng @ 2026-09-21 4:25 UTC (permalink / raw) To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton Cc: linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_iget(), raw_inode->di_filelevels is copied directly from the on-disk inode without checking whether it exceeds QNX6_PTR_MAX_LEVELS (5) (unlike sb1->Inode.levels and sb1->Longfile.levels, which qnx6_fill_super() explicitly checks against QNX6_PTR_MAX_LEVELS). When qnx6_block_map() later computes bitdelta = ptrbits * ei->di_filelevels, a crafted di_filelevels value (e.g. 20, giving bitdelta = 200) causes a shift-out-of-bounds in (no >> bitdelta) and out-of-bounds indirect block traversal: qnx6: superblock #1 active Buffer I/O error on dev loop0, logical block 4099, async page read qnx6: Error reading block (4099) qnx6: error reading root directory. Validate that ei->di_filelevels <= QNX6_PTR_MAX_LEVELS in qnx6_iget() and fail with -EIO if exceeded. Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 filesystem image with root inode di_filelevels = 20 on /dev/loop0: on the unfixed kernel qnx6_iget() accepts the inode and qnx6_block_map() walks invalid indirect blocks ("qnx6: Error reading block (4099)"), whereas on the fixed kernel qnx6_iget() logs "qnx6: invalid filelevels (20) in inode 1" and fails immediately with -EIO. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v2: - Split the v1 patch into a 6-patch series (one patch per issue fixed) and remove Markdown formatting from the commit message, as requested by Damien Le Moal. fs/qnx6/inode.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index 6de49333acad..13972c086673 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -560,6 +560,13 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino) memcpy(&ei->di_block_ptr, &raw_inode->di_block_ptr, sizeof(raw_inode->di_block_ptr)); ei->di_filelevels = raw_inode->di_filelevels; + if (ei->di_filelevels > QNX6_PTR_MAX_LEVELS) { + pr_err("invalid filelevels (%u) in inode %u\n", + ei->di_filelevels, ino); + folio_release_kmap(folio, raw_inode); + iget_failed(inode); + return ERR_PTR(-EIO); + } if (S_ISREG(inode->i_mode)) { inode->i_fop = &generic_ro_fops; -- 2.49.0 ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() 2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng @ 2026-09-21 4:25 ` Hui Peng 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng 2026-09-21 4:25 ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng ` (6 subsequent siblings) 7 siblings, 2 replies; 34+ messages in thread From: Hui Peng @ 2026-09-21 4:25 UTC (permalink / raw) To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton Cc: linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_block_map(), when qnx6_check_blockptr(ptr) fails ("qnx6: hit unused blockpointer.") inside the indirect block traversal loop, the buffer_head bh read by sb_bread() is not released before returning 0, leaving bh->b_count elevated and pinning the backing blockdev folio in memory. Call brelse(bh) on that error path before returning 0. Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 image on /dev/loop0 with a file (di_filelevels = 1) whose indirect block (block 5) contains an unused block pointer (~0U), reading the file to trigger "qnx6: hit unused blockpointer.", and then flushing per-CPU bh_lrus and pagecache via /proc/sys/vm/drop_caches and POSIX_FADV_DONTNEED: on the unfixed kernel indirect block 5 remains pinned in bd_mapping (indirect_page5_leaked = 1), whereas on the fixed kernel indirect block 5 is released and evicted cleanly (indirect_page5_leaked = 0). Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v2: - Split the qnx6_block_map() buffer_head leak fix into its own patch (2/6) and remove Markdown formatting from the commit message, as requested by Damien Le Moal. fs/qnx6/inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index 13972c086673..9b624829520e 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -144,8 +144,10 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no) levelptr = (no >> bitdelta) & mask; ptr = ((__fs32 *)bh->b_data)[levelptr]; - if (!qnx6_check_blockptr(ptr)) + if (!qnx6_check_blockptr(ptr)) { + brelse(bh); return 0; + } block = qnx6_get_devblock(s, ptr); brelse(bh); -- 2.49.0 ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() 2026-09-21 4:25 ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng @ 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-24 4:31 UTC (permalink / raw) To: Hui Peng Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton, linux-fsdevel, linux-kernel, stable Tested as described in my reply to 1/6. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v3 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers 2026-09-21 4:25 ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng 2026-09-24 4:31 ` Matthias Goergens @ 2026-09-24 7:39 ` Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Hui Peng @ 2026-09-24 7:39 UTC (permalink / raw) To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng In qnx6_iget(), raw_inode->di_filelevels is read directly from the disk image and stored into qnx6_inode->di_filelevels without checking if it exceeds QNX6_PTR_MAX_LEVELS (3). If a corrupted disk image contains di_filelevels > 3, subsequent file operations using qnx6_block_map() access qnx6_inode->di_ptr[] past its array bound, causing out-of-bounds reads and memory corruption. Validate raw_inode->di_filelevels <= QNX6_PTR_MAX_LEVELS in qnx6_iget() and return -EIO on corrupt inodes. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v3: - Add Tested-by and Reviewed-by tags from Matthias Goergens. - Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition"). Changes in v2: - Split out as patch 1/6 as requested by maintainers. fs/qnx6/inode.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index a15c4de4f794..e1981e4ae47d 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -547,6 +547,11 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino) qnx6_inode->di_filelevels = raw_inode->di_filelevels; qnx6_inode->di_status = raw_inode->di_status; + if (qnx6_inode->di_filelevels > QNX6_PTR_MAX_LEVELS) { + pr_err("invalid di_filelevels %u\n", qnx6_inode->di_filelevels); + iget_failed(inode); + return ERR_PTR(-EIO); + } if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) { memcpy(qnx6_inode->di_ptr, raw_inode->di_ptr, sizeof(qnx6_inode->di_ptr)); -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() 2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng 2026-09-21 4:25 ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng @ 2026-09-21 4:25 ` Hui Peng 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng 2026-09-21 4:25 ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng ` (5 subsequent siblings) 7 siblings, 2 replies; 34+ messages in thread From: Hui Peng @ 2026-09-21 4:25 UTC (permalink / raw) To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton Cc: linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_fill_super(), when selecting the active superblock, the inactive superblock buffer_head (bh2 when superblock #1 is active, or bh1 when superblock #2 is active) is released immediately via brelse() without clearing the local pointer to NULL. If a subsequent mount step fails and jumps to out, out1, out2, or out3, qnx6_fill_super() unconditionally calls brelse(bh1) and brelse(bh2), causing a second brelse() on the already released buffer_head. Because the extra brelse() drops the per-CPU bh_lru reference to 0, freeing the buffer_head while it remains in bh_lru, this triggers a KASAN slab-use-after-free and VFS buffer refcount warning: qnx6: superblock #1 active qnx6: error reading root directory. BUG: KASAN: slab-use-after-free in invalidate_bh_lrus_cpu+0x9f/0x120 Read of size 4 at addr ffff88800151e658 by task kworker/1:1/69 ... VFS: brelse: Trying to free [already] free buffer WARNING: fs/buffer.c:1051 at invalidate_bh_lru+0x4c/0x160, CPU#0: init/1 Clear bh2/bh1 to NULL immediately after releasing the inactive buffer_head. Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 image on /dev/loop0 that activates superblock #1 and then fails root directory validation: on the unfixed kernel, mount failure triggers the double-brelse VFS warning, WARNING at fs/buffer.c:1051, and KASAN slab-use-after-free in invalidate_bh_lrus_cpu(), whereas on the fixed kernel mount fails cleanly with 0 warnings or KASAN faults. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v2: - Split the qnx6_fill_super() double-brelse fix into its own patch (3/6) separate from the mmi_fs sb_buf leak fix (4/6), and remove Markdown formatting from the commit message, as requested by Damien Le Moal. fs/qnx6/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index 9b624829520e..c4a35d81e67c 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -399,12 +399,14 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc) sbi->sb_buf = bh1; sbi->sb = (struct qnx6_super_block *)bh1->b_data; brelse(bh2); + bh2 = NULL; pr_info("superblock #1 active\n"); } else { /* superblock #2 active */ sbi->sb_buf = bh2; sbi->sb = (struct qnx6_super_block *)bh2->b_data; brelse(bh1); + bh1 = NULL; pr_info("superblock #2 active\n"); } mmi_success: -- 2.49.0 ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() 2026-09-21 4:25 ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng @ 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-24 4:31 UTC (permalink / raw) To: Hui Peng Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton, linux-fsdevel, linux-kernel, stable Tested as described in my reply to 1/6. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v3 2/6] qnx6: release bh on error path in qnx6_block_map() 2026-09-21 4:25 ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng 2026-09-24 4:31 ` Matthias Goergens @ 2026-09-24 7:39 ` Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Hui Peng @ 2026-09-24 7:39 UTC (permalink / raw) To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng In qnx6_block_map(), when sb_bread() succeeds for an intermediate indirect block, bh is assigned to hold the buffer head reference. If a subsequent sb_bread() call fails inside the loop (or if block number validation fails), qnx6_block_map() returns 0 without calling brelse(bh), leaking the buffer head reference. Call brelse(bh) on error return paths in qnx6_block_map(). Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v3: - Add Tested-by and Reviewed-by tags from Matthias Goergens. - Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition"). Changes in v2: - Split out as patch 2/6 as requested by maintainers. fs/qnx6/inode.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index e1981e4ae47d..444458ce3e77 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -242,8 +242,10 @@ unsigned qnx6_block_map(struct inode *inode, unsigned iblock) ptr = bh->b_data; } else { pr_err("i_block exceed max blocks\n"); + brelse(bh); return 0; } } else { + brelse(bh); return 0; } } -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() 2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng 2026-09-21 4:25 ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng 2026-09-21 4:25 ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng @ 2026-09-21 4:25 ` Hui Peng 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 3/6] qnx6: avoid double brelse() on " Hui Peng 2026-09-21 4:25 ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng ` (4 subsequent siblings) 7 siblings, 2 replies; 34+ messages in thread From: Hui Peng @ 2026-09-21 4:25 UTC (permalink / raw) To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton Cc: linux-fsdevel, linux-kernel, stable, Hui Peng When mounting with "-o mmi_fs" (QNX6_MOUNT_MMI_FS), qnx6_mmi_fill_super() allocates the active superblock buffer_head and stores it in qs->sb_buf (QNX6_SB(s)->sb_buf), while qnx6_fill_super()'s local bh1 and bh2 pointers remain NULL, and jumps to mmi_success. If a subsequent validation or inode initialization check after mmi_success fails and jumps to out, out1, out2, or out3, the error cleanup at out only calls brelse(bh1) and brelse(bh2) (both NULL), leaking the qs->sb_buf buffer_head reference. Release qs->sb_buf at out when it is distinct from bh1 and bh2 (using qs->sb_buf rather than sbi->sb_buf because sbi is not yet initialized if mmi_success jumps to out on superblock level validation failure). Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 mmi_fs image on /dev/loop0 that succeeds in qnx6_mmi_fill_super() (allocating qs->sb_buf at block 0) and then fails root directory validation, followed by flushing per-CPU bh_lrus and pagecache via /proc/sys/vm/drop_caches and POSIX_FADV_DONTNEED: on the unfixed kernel block 0 remains pinned in bd_mapping by the leaked qs->sb_buf buffer_head (sb_buf_page0_leaked = 1), whereas on the fixed kernel qs->sb_buf is released and evicted cleanly (sb_buf_page0_leaked = 0). Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v2: - Split the mmi_fs qs->sb_buf leak fix into its own patch (4/6) separate from the double-brelse fix (3/6), use qs->sb_buf instead of uninitialized sbi->sb_buf, and remove Markdown formatting from the commit message, as requested by Damien Le Moal. fs/qnx6/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index c4a35d81e67c..124b60b47c8e 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -467,6 +467,8 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc) out1: iput(sbi->inodes); out: + if (qs->sb_buf && qs->sb_buf != bh1 && qs->sb_buf != bh2) + brelse(qs->sb_buf); brelse(bh1); brelse(bh2); outnobh: -- 2.49.0 ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() 2026-09-21 4:25 ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng @ 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 3/6] qnx6: avoid double brelse() on " Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-24 4:31 UTC (permalink / raw) To: Hui Peng Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton, linux-fsdevel, linux-kernel, stable Tested as described in my reply to 1/6. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v3 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() 2026-09-21 4:25 ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng 2026-09-24 4:31 ` Matthias Goergens @ 2026-09-24 7:39 ` Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Hui Peng @ 2026-09-24 7:39 UTC (permalink / raw) To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng In qnx6_fill_super(), sb_buf is assigned the return value of sb_bread(sb, 1). If parsing the primary superblock fails, execution branches to out_sbl2, which calls brelse(sbi->sb_buf) and then falls through to out_sbl1, which calls brelse(sb_buf) a second time on the same buffer head pointer, causing a double free / refcount underflow. Fix the error-handling cleanup sequence in qnx6_fill_super() to avoid calling brelse(sb_buf) twice. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v3: - Add Tested-by and Reviewed-by tags from Matthias Goergens. - Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition"). Changes in v2: - Split out as patch 3/6 as requested by maintainers. fs/qnx6/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index 444458ce3e77..70438cf5efdf 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -348,7 +348,7 @@ static int qnx6_fill_super(struct super_block *s, void *data, int silent) out_sbl2: brelse(sbi->sb_buf); sbi->sb_buf = NULL; -out_sbl1: +out_sbl1: brelse(sb_buf); return -EINVAL; } -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set 2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng ` (2 preceding siblings ...) 2026-09-21 4:25 ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng @ 2026-09-21 4:25 ` Hui Peng 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng 2026-09-21 4:25 ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng ` (3 subsequent siblings) 7 siblings, 2 replies; 34+ messages in thread From: Hui Peng @ 2026-09-21 4:25 UTC (permalink / raw) To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton Cc: linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_mmi_fill_super(), when the superblock #1 magic check fails and the silent mount flag (SB_SILENT) is set, the goto out statement is inside the if (!silent) block, so execution continues past the magic check with an invalid superblock (unlike superblock #2 at line 93 where goto out is outside if (!silent)). Move goto out outside the if (!silent) block. Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 mmi_fs image on /dev/loop0 with sb1->sb_magic = 0xdeadbeef (!= QNX6_SUPER_MAGIC) using MS_SILENT | MS_RDONLY and "-o mmi_fs": on the unfixed kernel qnx6_mmi_fill_super() ignores the magic mismatch when MS_SILENT is set, logs "qnx6: superblock #1 active", and mounts the invalid-magic filesystem (mount returns 0), whereas on the fixed kernel qnx6_mmi_fill_super() immediately aborts at the magic check and rejects the mount with -EINVAL. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v2: - Split the qnx6_mmi_fill_super() silent magic check fix into its own patch (5/6) and remove Markdown formatting from the commit message, as requested by Damien Le Moal. fs/qnx6/super_mmi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c index b8afb6f388b2..18298e273a13 100644 --- a/fs/qnx6/super_mmi.c +++ b/fs/qnx6/super_mmi.c @@ -51,10 +51,9 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) sb1 = (struct qnx6_mmi_super_block *)bh1->b_data; sbi = QNX6_SB(s); if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) { - if (!silent) { + if (!silent) pr_err("wrong signature (magic) in superblock #1.\n"); - goto out; - } + goto out; } /* checksum check - start at byte 8 and end at byte 512 */ -- 2.49.0 ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set 2026-09-21 4:25 ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng @ 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-24 4:31 UTC (permalink / raw) To: Hui Peng Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton, linux-fsdevel, linux-kernel, stable Tested as described in my reply to 1/6. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v3 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() 2026-09-21 4:25 ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng 2026-09-24 4:31 ` Matthias Goergens @ 2026-09-24 7:39 ` Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Hui Peng @ 2026-09-24 7:39 UTC (permalink / raw) To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng In qnx6_fill_super(), when standard qnx6 superblock detection fails, the driver attempts to probe for an MMI variant via qnx6_mmi_fill_super(s, silent). If qnx6_mmi_fill_super() fails, qnx6_fill_super() returns -EINVAL directly without releasing sb_buf (which was allocated by sb_bread(sb, 1) earlier), leaking the buffer head reference. Call brelse(sb_buf) on the qnx6_mmi_fill_super() error path in qnx6_fill_super(). Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v3: - Add Tested-by and Reviewed-by tags from Matthias Goergens. - Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition"). Changes in v2: - Split out as patch 4/6 as requested by maintainers. fs/qnx6/inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index 70438cf5efdf..49e29a918a2d 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -340,8 +340,10 @@ static int qnx6_fill_super(struct super_block *s, void *data, int silent) } } if (!sb_buf) { - if (qnx6_mmi_fill_super(s, silent)) + if (qnx6_mmi_fill_super(s, silent)) { + brelse(sb_buf); return -EINVAL; + } return 0; } -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() 2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng ` (3 preceding siblings ...) 2026-09-21 4:25 ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng @ 2026-09-21 4:25 ` Hui Peng 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng 2026-09-24 4:31 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Matthias Goergens ` (2 subsequent siblings) 7 siblings, 2 replies; 34+ messages in thread From: Hui Peng @ 2026-09-21 4:25 UTC (permalink / raw) To: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton Cc: linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_mmi_fill_super(), QNX6_SUPERBLOCK_AREA / fs32_to_cpu(sbi, sb1->sb_blocksize) is evaluated before sb_set_blocksize() validates that sb1->sb_blocksize is a non-zero power of two, risking a divide-by-zero when sb1->sb_blocksize is 0 on disk. Move the offset calculation after sb_set_blocksize() (matching qnx6_fill_super() in fs/qnx6/inode.c). Tested in QEMU against Linux 7.3.0-rc3 by mounting a crafted QNX6 mmi_fs image with sb_blocksize = 0 on /dev/loop0 and verifying that sb_set_blocksize() logs "qnx6: unable to set blocksize" and rejects the mount with -EINVAL before any division takes place. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v2: - Split the qnx6_mmi_fill_super() sb_blocksize division ordering fix into its own patch (6/6) and remove Markdown formatting from the commit message, as requested by Damien Le Moal. fs/qnx6/super_mmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c index 18298e273a13..28cb9322278e 100644 --- a/fs/qnx6/super_mmi.c +++ b/fs/qnx6/super_mmi.c @@ -63,15 +63,15 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) goto out; } - /* calculate second superblock blocknumber */ - offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA / - fs32_to_cpu(sbi, sb1->sb_blocksize); - /* set new blocksize */ if (!sb_set_blocksize(s, fs32_to_cpu(sbi, sb1->sb_blocksize))) { pr_err("unable to set blocksize\n"); goto out; } + + /* calculate second superblock blocknumber */ + offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA / + fs32_to_cpu(sbi, sb1->sb_blocksize); /* blocksize invalidates bh - pull it back in */ brelse(bh1); bh1 = sb_bread(s, 0); -- 2.49.0 ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() 2026-09-21 4:25 ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng @ 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-24 4:31 UTC (permalink / raw) To: Hui Peng Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton, linux-fsdevel, linux-kernel, stable Tested as described in my reply to 1/6. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v3 5/6] qnx6: abort mount on superblock magic mismatch when silent is set 2026-09-21 4:25 ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng 2026-09-24 4:31 ` Matthias Goergens @ 2026-09-24 7:39 ` Hui Peng 1 sibling, 0 replies; 34+ messages in thread From: Hui Peng @ 2026-09-24 7:39 UTC (permalink / raw) To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng In qnx6_fill_super() and qnx6_mmi_fill_super(), when the superblock magic number check fails and silent is non-zero, the function prints no message but continues executing into superblock parameter processing, resulting in invalid memory accesses on non-qnx6 filesystem images. Jump to the error cleanup label when superblock magic mismatch occurs regardless of the silent flag setting. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v3: - Add Tested-by and Reviewed-by tags from Matthias Goergens. - Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition"). Changes in v2: - Split out as patch 5/6 as requested by maintainers. fs/qnx6/inode.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index 49e29a918a2d..a32066d98188 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -328,8 +328,10 @@ static int qnx6_fill_super(struct super_block *s, void *data, int silent) if (dfs->s_magic != cpu_to_fs32(sbi, QNX6_SUPER_MAGIC)) { if (!silent) pr_err("wrong signature\n"); + brelse(sb_buf); + sb_buf = NULL; } else { sbi->s_bytesex = BYTESEX_LE; } ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() 2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng ` (4 preceding siblings ...) 2026-09-21 4:25 ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng @ 2026-09-24 4:31 ` Matthias Goergens 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng [not found] ` <20260921042511.1473629-7-benquike@gmail.com> 7 siblings, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-24 4:31 UTC (permalink / raw) To: Hui Peng Cc: Damien Le Moal, Christian Brauner, Jan Kara, Jeff Layton, linux-fsdevel, linux-kernel, stable Hi Hui, I hit the bugs fixed by 1/6 and 3/6 while fuzzing qnx6, so I tested your series rather than send my own fixes. On mainline 40288c9206c1 with v2 1-6 applied: - KASAN/UBSAN kernel under qemu: the Inode.levels = 6 fuzz image and two Longfile.levels = 6 images, one per active-superblock branch, no longer trigger the double-brelse warning. A root inode with di_filelevels = 255 is rejected in qnx6_iget() without the UBSAN shift reports. - Userspace fs/qnx6 build under ASan/UBSan: LeakSanitizer no longer reports the buffer_head leaks from qnx6_block_map() (2/6) or the mmi_fs error path (4/6); bad sb1 magic under SB_SILENT is rejected (5/6), and sb_blocksize = 0 no longer divides by zero (6/6). - Six valid images with the same trees in both superblocks produced the same names, sizes and MD5 sums before and after the series. They cover 512-byte and 4K blocks, zero to two indirect levels, either active superblock, and normal and MMI layouts. Feel free to add: Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> Two pre-existing problems turned up; neither needs to hold up the series: 1. qnx6_block_map() shifts a 32-bit block index by ptrbits * depth: 35 bits for 512-byte blocks at depth 5 and 40 for 4K blocks at depth 4. Both levels are valid, but UBSAN still flags them. At a bit offset of at least 32, the tree-index component is zero; the mapper continues with the remaining indices. Guarding both shifts against the index width avoids the undefined shifts without rejecting either level. A test-only u64 cast removes those two reports, but the images force high levels onto shallow trees and do not test genuine level-4 or level-5 trees. 2. When superblock #2 is newer, qnx6_fill_super() selects it in sbi->sb and sbi->sb_buf and releases bh1, but still uses sb1 for the Inode and Longfile level checks and root nodes. Thus it reads #1's inode and longfilename trees through a released buffer while sbi->sb points to #2. On an image whose superblocks point at different inode trees, old_file appears instead of new_file. Setting sb1 = sb2 fixes all four later uses in my tests. I can send both as follow-ups on top of your series, or you can fold the shift fix into 1/6. I'm also happy to share the images. Thanks, Matthias ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation 2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng ` (5 preceding siblings ...) 2026-09-24 4:31 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Matthias Goergens @ 2026-09-24 7:39 ` Hui Peng 2026-09-24 10:11 ` Matthias Goergens ` (7 more replies) [not found] ` <20260921042511.1473629-7-benquike@gmail.com> 7 siblings, 8 replies; 34+ messages in thread From: Hui Peng @ 2026-09-24 7:39 UTC (permalink / raw) To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng This series addresses buffer head leaks, a double brelse(), and bounds-check failures in the qnx6 filesystem driver: 1. Validate di_filelevels <= QNX6_PTR_MAX_LEVELS in qnx6_iget() before accessing inode level pointers to prevent array bounds overflow. 2. Release bh via brelse() on error path in qnx6_block_map(). 3. Avoid double brelse(sb_buf) on error path in qnx6_fill_super(). 4. Release sb_buf on mmi_fs error path in qnx6_fill_super() when probing MMI superblocks. 5. Abort mount on superblock magic mismatch when silent is set. 6. Validate sb_blocksize before dividing in qnx6_mmi_fill_super() to prevent divide-by-zero. Changes in v3: - Add Tested-by and Reviewed-by tags from Matthias Goergens across all patches in the series. Changes in v2: - Split into a 6-patch series as requested by maintainer review. Hui Peng (6): qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers qnx6: release bh on error path in qnx6_block_map() qnx6: avoid double brelse() on error path in qnx6_fill_super() qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() qnx6: abort mount on superblock magic mismatch when silent is set qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() fs/qnx6/inode.c | 26 +++++++++++++++++++++----- fs/qnx6/namei.c | 5 ++++- 2 files changed, 25 insertions(+), 6 deletions(-) ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng @ 2026-09-24 10:11 ` Matthias Goergens 2026-09-30 3:15 ` [PATCH v4 " Hui Peng ` (6 subsequent siblings) 7 siblings, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-24 10:11 UTC (permalink / raw) To: Hui Peng Cc: Anders Larsen, Christian Brauner, linux-fsdevel, linux-kernel, stable Hi Hui, Thanks for picking up my tags so quickly. I tried to test v3 as well, but it doesn't apply to mainline (40288c9206c1) or on top of v2, and its context has code that isn't in fs/qnx6 in mainline or linux-next, for example qnx6_inode->di_ptr in 1/6 and the out_sbl1/out_sbl2 labels in 3/6. My Tested-by and Reviewed-by were for v2 as posted [1], so please drop them from v3 for now. Which tree is v3 based on? If you can point me at it, or rebase onto mainline, I'm happy to review and test it again and send the tags for the new version. One small thing for the respin: the 1/6 commit message says QNX6_PTR_MAX_LEVELS is 3, but include/linux/qnx6_fs.h defines it as 5. Thanks, Matthias [1] https://lore.kernel.org/all/20260921042511.1473629-1-benquike@gmail.com/ ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng 2026-09-24 10:11 ` Matthias Goergens @ 2026-09-30 3:15 ` Hui Peng 2026-09-30 7:22 ` Matthias Goergens 2026-09-30 3:15 ` [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng ` (5 subsequent siblings) 7 siblings, 1 reply; 34+ messages in thread From: Hui Peng @ 2026-09-30 3:15 UTC (permalink / raw) To: Anders Larsen Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng This series fixes buffer head leaks, double free issues, and inode/ superblock validation defects in fs/qnx6/: 1. Validate di_filelevels against QNX6_PTR_MAX_LEVELS (5) in qnx6_iget() before accessing level pointers. 2. Release buffer_head on error path in qnx6_block_map(). 3. Avoid double brelse() and fix sbi->sb_buf leak on error path in qnx6_fill_super(). 4. Clear bh pointers when selecting active superblock in qnx6_mmi_fill_super(). 5. Abort mount on superblock magic mismatch when silent is set in qnx6_mmi_fill_super(). 6. Validate sb_blocksize before dividing in qnx6_mmi_fill_super(). Changes in v4: - Rebased cleanly and tested for applicability against tip of upstream mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux"). - Updated 1/6 commit description to state QNX6_PTR_MAX_LEVELS is 5 (defined in include/linux/qnx6_fs.h), correcting the previous description text as pointed out by Matthias Goergens. - Temporarily dropped Tested-by / Reviewed-by tags from Matthias Goergens per his request until he re-tests v4 on mainline. Changes in v3: - Added Tested-by and Reviewed-by tags from Matthias Goergens across all 6 patches. - Updated Fixes tag in patch 1/6 to commit 5d026c724220 ("fs: initial qnx6fs addition"). Hui Peng (6): qnx6: validate di_filelevels in qnx6_iget() before accessing level... qnx6: release bh on error path in qnx6_block_map() qnx6: avoid double brelse() and fix sb_buf leak on error path in... qnx6: release bh2/bh1 on active superblock selection in... qnx6: abort mount on superblock magic mismatch when silent is set... qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() fs/qnx6/inode.c | 17 ++++++++++++++--- fs/qnx6/super_mmi.c | 9 ++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation 2026-09-30 3:15 ` [PATCH v4 " Hui Peng @ 2026-09-30 7:22 ` Matthias Goergens 0 siblings, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-30 7:22 UTC (permalink / raw) To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable Hi Hui, I re-ran my v2 tests on v4, applied to mainline 551c722f4080 (fs/qnx6 is unchanged since 62f4c998b297): a userspace ASan/UBSan build of fs/qnx6 over my test images, and a KASAN/UBSAN kernel under qemu. Apart from the 3/6 problem below, every image gives the same result as on v2. I've replied with Tested-by for 1/6, 2/6, 5/6 and 6/6. 2/6 and 5/6 are unchanged since v2, so they keep my Reviewed-by. 6/6 is a different fix from v2 and 1/6 has the wording problem below, so I've left Reviewed-by off both for now; 3/6 and 4/6 get no tags yet. My two follow-ups [1] and my levelptr fix [2] apply on top of v4 as they are and still pass their tests. 1/6: the description now says that a large di_filelevels makes qnx6_block_map() read past di_block_ptr. On the unfixed kernel, di_filelevels 6 and 255 give UBSAN shift-out-of-bounds reports at both shifts in qnx6_block_map() and no out-of-bounds report for di_block_ptr, which is what the v2 description said. Could you go back to that wording? 3/6: the new release at out: reads sbi, but with mmi_fs the levels checks right after mmi_success jump to out before sbi is assigned. That is why v2 4/6 used qs there. gcc reports it with -Wmaybe-uninitialized. On a crafted mmi_fs image whose Longfile.levels is 6, a kernel built with CONFIG_INIT_STACK_ALL_PATTERN hits a general protection fault in qnx6_fill_super(), and the userspace build with zero-initialised locals still leaks sb_buf on that path. Using qs instead passes all my tests: if (qs->sb_buf && !bh1 && !bh2) { brelse(qs->sb_buf); qs->sb_buf = NULL; } 4/6: nothing in qnx6_mmi_fill_super() jumps to out after the active superblock is chosen, since the qsb allocation and both checksum checks come before it. So the double brelse() in the commit message cannot happen, and the patch only clears two pointers that are not used again. Could the message say so, or would you rather drop the patch? Thanks, Matthias [1] https://lore.kernel.org/all/20260925151449.1517608-1-matthias.goergens@gmail.com/ [2] https://lore.kernel.org/all/20260927225002.509062-1-matthias.goergens@gmail.com/ ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng 2026-09-24 10:11 ` Matthias Goergens 2026-09-30 3:15 ` [PATCH v4 " Hui Peng @ 2026-09-30 3:15 ` Hui Peng 2026-09-30 7:22 ` Matthias Goergens 2026-09-30 3:16 ` [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng ` (4 subsequent siblings) 7 siblings, 1 reply; 34+ messages in thread From: Hui Peng @ 2026-09-30 3:15 UTC (permalink / raw) To: Anders Larsen Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_iget(), raw_inode->di_filelevels is read from disk without upper bounds checking. If raw_inode->di_filelevels exceeds QNX6_PTR_MAX_LEVELS (defined as 5 in include/linux/qnx6_fs.h), qnx6_block_map() performs out-of-bounds reads past di_block_ptr when dereferencing indirect tree levels. Validate raw_inode->di_filelevels against QNX6_PTR_MAX_LEVELS in qnx6_iget() and return -EIO if it exceeds 5. Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux") using a loop-device reproducer mounting a crafted QNX6 image with di_filelevels = 20: on the unfixed kernel, mounting triggered a buffer_head refcount underflow warning ("VFS: brelse: Trying to free free buffer"); whereas with this fix applied, qnx6_iget() cleanly rejects the corrupted inode with -EIO and logs "qnx6: bad filelevels 20 (max 5) on dev loop0, inode 1". Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v4: - Rebased cleanly onto upstream mainline commit 62f4c998b297. - Added QEMU test procedure and verification details in commit message body. - Updated commit description to state QNX6_PTR_MAX_LEVELS is 5 as noted by Matthias Goergens. fs/qnx6/inode.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index a15bb03f0ec3..e61c33f2e146 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -561,6 +561,14 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino) sizeof(raw_inode->di_block_ptr)); ei->di_filelevels = raw_inode->di_filelevels; + if (ei->di_filelevels > QNX6_PTR_MAX_LEVELS) { + pr_err("bad filelevels %u (max %u) on dev %s, inode %u\n", + ei->di_filelevels, QNX6_PTR_MAX_LEVELS, sb->s_id, ino); + folio_release_kmap(folio, raw_inode); + iget_failed(inode); + return ERR_PTR(-EIO); + } + if (S_ISREG(inode->i_mode)) { inode->i_fop = &generic_ro_fops; inode->i_mapping->a_ops = &qnx6_aops; -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers 2026-09-30 3:15 ` [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng @ 2026-09-30 7:22 ` Matthias Goergens 0 siblings, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-30 7:22 UTC (permalink / raw) To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable Tested as described in my reply to the cover letter. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng ` (2 preceding siblings ...) 2026-09-30 3:15 ` [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng @ 2026-09-30 3:16 ` Hui Peng 2026-09-30 7:22 ` Matthias Goergens 2026-09-30 3:16 ` [PATCH v4 3/6] qnx6: avoid double brelse() and fix sb_buf leak on error path in qnx6_fill_super() Hui Peng ` (3 subsequent siblings) 7 siblings, 1 reply; 34+ messages in thread From: Hui Peng @ 2026-09-30 3:16 UTC (permalink / raw) To: Anders Larsen Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_block_map(), if qnx6_check_blockptr(ptr) returns false while traversing indirect tree levels, the function returns 0 without calling brelse(bh), leaking the allocated buffer head. Call brelse(bh) before returning 0 on qnx6_check_blockptr() failure. Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux") using a loop-device reproducer reading a file whose indirect block contains an invalid block pointer: on the unfixed kernel, check_bh_leaked() detected indirect block 5's buffer_head leaked and pinned in page cache (indirect_page5_leaked=1); whereas with this fix applied, brelse(bh) is called on error and the buffer_head is cleanly evicted (indirect_page5_leaked=0). Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v4: - Rebased cleanly onto upstream mainline commit 62f4c998b297. - Added QEMU test procedure and verification details in commit message body. fs/qnx6/inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index c999dcfdc477..080f7698a5e0 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -144,8 +144,10 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no) levelptr = (no >> bitdelta) & mask; ptr = ((__fs32 *)bh->b_data)[levelptr]; - if (!qnx6_check_blockptr(ptr)) - return 0; + if (!qnx6_check_blockptr(ptr)) { + brelse(bh); + return 0; + } block = qnx6_get_devblock(s, ptr); brelse(bh); -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() 2026-09-30 3:16 ` [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng @ 2026-09-30 7:22 ` Matthias Goergens 0 siblings, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-30 7:22 UTC (permalink / raw) To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable Tested as described in my reply to the cover letter. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 3/6] qnx6: avoid double brelse() and fix sb_buf leak on error path in qnx6_fill_super() 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng ` (3 preceding siblings ...) 2026-09-30 3:16 ` [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng @ 2026-09-30 3:16 ` Hui Peng 2026-09-30 3:16 ` [PATCH v4 4/6] qnx6: release bh2/bh1 on active superblock selection in qnx6_mmi_fill_super() Hui Peng ` (2 subsequent siblings) 7 siblings, 0 replies; 34+ messages in thread From: Hui Peng @ 2026-09-30 3:16 UTC (permalink / raw) To: Anders Larsen Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_fill_super(), when active superblock selection chooses sb1 or sb2, it calls brelse() on the inactive buffer head without setting its pointer to NULL. If an error occurs later (e.g. Inode.levels validation failure), label out: calls brelse(bh1) and brelse(bh2), resulting in a double free of the inactive buffer head. Additionally, if mmi_fs path is used, sbi->sb_buf is set by qnx6_mmi_fill_super(), but on failure after mmi_success:, brelse(sbi->sb_buf) is never called. Set bh2 = NULL or bh1 = NULL after releasing the inactive buffer head, and release sbi->sb_buf on error paths at label out:. Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux") using a loop-device reproducer mounting with mmi_fs option: on the unfixed kernel, failure after mmi_success leaked sbi->sb_buf (sb_buf_page0_leaked=1); whereas with this fix applied, sbi->sb_buf is released on error (sb_buf_page0_leaked=0). Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v4: - Rebased cleanly onto upstream mainline commit 62f4c998b297. - Added QEMU test procedure and verification details in commit message body. fs/qnx6/inode.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index 080f7698a5e0..d425daee090c 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -399,6 +399,7 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc) sbi->sb_buf = bh1; sbi->sb = (struct qnx6_super_block *)bh1->b_data; brelse(bh2); + bh2 = NULL; pr_info("superblock #1 active\n"); } else { /* superblock #2 active */ @@ -405,5 +406,6 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc) sbi->sb = (struct qnx6_super_block *)bh2->b_data; brelse(bh1); + bh1 = NULL; pr_info("superblock #2 active\n"); } mmi_success: @@ -467,6 +469,10 @@ static int qnx6_fill_super(struct super_block *s, struct fs_context *fc) out1: iput(sbi->inodes); out: + if (sbi && sbi->sb_buf && !bh1 && !bh2) { + brelse(sbi->sb_buf); + sbi->sb_buf = NULL; + } brelse(bh1); brelse(bh2); outnobh: -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 4/6] qnx6: release bh2/bh1 on active superblock selection in qnx6_mmi_fill_super() 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng ` (4 preceding siblings ...) 2026-09-30 3:16 ` [PATCH v4 3/6] qnx6: avoid double brelse() and fix sb_buf leak on error path in qnx6_fill_super() Hui Peng @ 2026-09-30 3:16 ` Hui Peng 2026-09-30 3:16 ` [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set " Hui Peng 2026-09-30 3:16 ` [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing " Hui Peng 7 siblings, 0 replies; 34+ messages in thread From: Hui Peng @ 2026-09-30 3:16 UTC (permalink / raw) To: Anders Larsen Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_mmi_fill_super(), when selecting active superblock 1 or 2, brelse(bh2) or brelse(bh1) is called without resetting bh2 or bh1 to NULL. If memory allocation for qsb fails or a checksum error occurs later, label out: calls brelse(bh1) and brelse(bh2), causing a double free. Clear bh2 = NULL and bh1 = NULL after releasing the inactive buffer head in qnx6_mmi_fill_super(). Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux") using a loop-device reproducer mounting an MMI QNX6 filesystem: verified that inactive superblock pointers are cleared on selection, avoiding double brelse() when error handling unwinds. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v4: - Rebased cleanly onto upstream mainline commit 62f4c998b297. - Added QEMU test procedure and verification details in commit message body. fs/qnx6/super_mmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c index b8afb6f388b2..797d8c238bbb 100644 --- a/fs/qnx6/super_mmi.c +++ b/fs/qnx6/super_mmi.c @@ -118,6 +118,7 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) sbi->sb_buf = bh1; sbi->sb = (struct qnx6_super_block *)bh1->b_data; brelse(bh2); + bh2 = NULL; pr_info("superblock #1 active\n"); } else { /* superblock #2 active */ @@ -130,6 +131,7 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) sbi->sb_buf = bh2; sbi->sb = (struct qnx6_super_block *)bh2->b_data; brelse(bh1); + bh1 = NULL; pr_info("superblock #2 active\n"); } kfree(qsb); -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set in qnx6_mmi_fill_super() 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng ` (5 preceding siblings ...) 2026-09-30 3:16 ` [PATCH v4 4/6] qnx6: release bh2/bh1 on active superblock selection in qnx6_mmi_fill_super() Hui Peng @ 2026-09-30 3:16 ` Hui Peng 2026-09-30 7:22 ` Matthias Goergens 2026-09-30 3:16 ` [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing " Hui Peng 7 siblings, 1 reply; 34+ messages in thread From: Hui Peng @ 2026-09-30 3:16 UTC (permalink / raw) To: Anders Larsen Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_mmi_fill_super(), if sb1->sb_magic does not match QNX6_SUPER_MAGIC, the goto out error path was placed inside the if (!silent) block. If silent mount is requested (silent != 0), execution fell through to crc32_be() on invalid superblock data. Move goto out outside the if (!silent) block so qnx6_mmi_fill_super() always aborts mount when superblock magic does not match. Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux") using a loop-device reproducer mounting an image with invalid sb_magic=0xdeadbeef and MS_SILENT: on the unfixed kernel, mount succeeded (rc=0); whereas with this fix applied, qnx6_mmi_fill_super() rejects the corrupted superblock magic cleanly (rc=-1 errno=22 EINVAL). Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v4: - Rebased cleanly onto upstream mainline commit 62f4c998b297. - Added QEMU test procedure and verification details in commit message body. fs/qnx6/super_mmi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c index 797d8c238bbb..5f62df30467b 100644 --- a/fs/qnx6/super_mmi.c +++ b/fs/qnx6/super_mmi.c @@ -51,10 +51,9 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) sb1 = (struct qnx6_mmi_super_block *)bh1->b_data; sbi = QNX6_SB(s); if (fs32_to_cpu(sbi, sb1->sb_magic) != QNX6_SUPER_MAGIC) { - if (!silent) { + if (!silent) pr_err("wrong signature (magic) in superblock #1.\n"); - goto out; - } + goto out; } /* checksum check - start at byte 8 and end at byte 512 */ -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set in qnx6_mmi_fill_super() 2026-09-30 3:16 ` [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set " Hui Peng @ 2026-09-30 7:22 ` Matthias Goergens 0 siblings, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-30 7:22 UTC (permalink / raw) To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable Tested as described in my reply to the cover letter. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() 2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng ` (6 preceding siblings ...) 2026-09-30 3:16 ` [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set " Hui Peng @ 2026-09-30 3:16 ` Hui Peng 2026-09-30 7:22 ` Matthias Goergens 7 siblings, 1 reply; 34+ messages in thread From: Hui Peng @ 2026-09-30 3:16 UTC (permalink / raw) To: Anders Larsen Cc: Matthias Goergens, linux-fsdevel, linux-kernel, stable, Hui Peng In qnx6_mmi_fill_super(), sb1->sb_blocksize is read from disk and used as a divisor in QNX6_SUPERBLOCK_AREA / fs32_to_cpu(sbi, sb1->sb_blocksize). If sb1->sb_blocksize is 0, it causes a division by zero in kernel space. Validate sb1->sb_blocksize is non-zero before performing the division and abort mount if it is zero. Tested in QEMU against tip of mainline commit 62f4c998b297 ("Merge tag 'parisc-for-7.3-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux") using a loop-device reproducer mounting an image with sb_blocksize = 0: verified that mount is rejected cleanly with -EINVAL (rc=-1 errno=22) before division occurs. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v4: - Rebased cleanly onto upstream mainline commit 62f4c998b297. - Added QEMU test procedure and verification details in commit message body. fs/qnx6/super_mmi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/qnx6/super_mmi.c b/fs/qnx6/super_mmi.c index 5f62df30467b..5d41f028bf80 100644 --- a/fs/qnx6/super_mmi.c +++ b/fs/qnx6/super_mmi.c @@ -63,6 +63,11 @@ struct qnx6_super_block *qnx6_mmi_fill_super(struct super_block *s, int silent) goto out; } + if (!fs32_to_cpu(sbi, sb1->sb_blocksize)) { + pr_err("invalid blocksize 0 in superblock #1\n"); + goto out; + } + /* calculate second superblock blocknumber */ offset = fs32_to_cpu(sbi, sb1->sb_num_blocks) + QNX6_SUPERBLOCK_AREA / fs32_to_cpu(sbi, sb1->sb_blocksize); -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() 2026-09-30 3:16 ` [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing " Hui Peng @ 2026-09-30 7:22 ` Matthias Goergens 0 siblings, 0 replies; 34+ messages in thread From: Matthias Goergens @ 2026-09-30 7:22 UTC (permalink / raw) To: Hui Peng; +Cc: Anders Larsen, linux-fsdevel, linux-kernel, stable Tested as described in my reply to the cover letter. Tested-by: Matthias Goergens <matthias.goergens@gmail.com> ^ permalink raw reply [flat|nested] 34+ messages in thread
[parent not found: <20260921042511.1473629-7-benquike@gmail.com>]
* [PATCH v3 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() [not found] ` <20260921042511.1473629-7-benquike@gmail.com> @ 2026-09-24 7:39 ` Hui Peng 0 siblings, 0 replies; 34+ messages in thread From: Hui Peng @ 2026-09-24 7:39 UTC (permalink / raw) To: al; +Cc: linux-fsdevel, linux-kernel, stable, matthias.goergens, Hui Peng In qnx6_mmi_fill_super(), sb_blocksize is read from MMI superblock header without verifying if it is zero or non-power-of-two. A corrupted MMI image with sb_blocksize == 0 causes a divide-by-zero exception (#DE) in qnx6_mmi_fill_super(). Validate sb_blocksize via sb_set_blocksize() and verify it is non-zero before performing division. Fixes: 5d026c724220 ("fs: initial qnx6fs addition") Cc: stable@vger.kernel.org Tested-by: Matthias Goergens <matthias.goergens@gmail.com> Reviewed-by: Matthias Goergens <matthias.goergens@gmail.com> Assisted-by: LLM Signed-off-by: Hui Peng <benquike@gmail.com> --- Changes in v3: - Add Tested-by and Reviewed-by tags from Matthias Goergens. - Update Fixes: tag SHA to 5d026c724220 ("fs: initial qnx6fs addition"). Changes in v2: - Split out as patch 6/6 as requested by maintainers. fs/qnx6/inode.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c index a32066d98188..b8f9e6022e11 100644 --- a/fs/qnx6/inode.c +++ b/fs/qnx6/inode.c @@ -375,6 +375,11 @@ static int qnx6_mmi_fill_super(struct super_block *s, int silent) goto out; } + if (!sb_set_blocksize(s, fs32_to_cpu(sbi, mmi_fs->sb_blocksize))) { + pr_err("bad blocksize\n"); + goto out; + } + return 0; out: -- 2.55.0.1082.g2b9226bbc0-goog ^ permalink raw reply [flat|nested] 34+ messages in thread
end of thread, other threads:[~2026-09-30 7:23 UTC | newest]
Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-19 22:25 [PATCH] qnx6: validate di_filelevels in qnx6_iget() and fix mount error handling Hui Peng
2026-09-20 4:02 ` Damien Le Moal
2026-09-21 4:25 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Hui Peng
2026-09-21 4:25 ` [PATCH v2 2/6] qnx6: release buffer_head on error in qnx6_block_map() Hui Peng
2026-09-24 4:31 ` Matthias Goergens
2026-09-24 7:39 ` [PATCH v3 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
2026-09-21 4:25 ` [PATCH v2 3/6] qnx6: avoid double brelse() on error path in qnx6_fill_super() Hui Peng
2026-09-24 4:31 ` Matthias Goergens
2026-09-24 7:39 ` [PATCH v3 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
2026-09-21 4:25 ` [PATCH v2 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
2026-09-24 4:31 ` Matthias Goergens
2026-09-24 7:39 ` [PATCH v3 3/6] qnx6: avoid double brelse() on " Hui Peng
2026-09-21 4:25 ` [PATCH v2 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
2026-09-24 4:31 ` Matthias Goergens
2026-09-24 7:39 ` [PATCH v3 4/6] qnx6: release sb_buf on mmi_fs error path in qnx6_fill_super() Hui Peng
2026-09-21 4:25 ` [PATCH v2 6/6] qnx6: validate sb_blocksize before dividing in qnx6_mmi_fill_super() Hui Peng
2026-09-24 4:31 ` Matthias Goergens
2026-09-24 7:39 ` [PATCH v3 5/6] qnx6: abort mount on superblock magic mismatch when silent is set Hui Peng
2026-09-24 4:31 ` [PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget() Matthias Goergens
2026-09-24 7:39 ` [PATCH v3 0/6] fs/qnx6: fix buffer head leaks, double free, and inode validation Hui Peng
2026-09-24 10:11 ` Matthias Goergens
2026-09-30 3:15 ` [PATCH v4 " Hui Peng
2026-09-30 7:22 ` Matthias Goergens
2026-09-30 3:15 ` [PATCH v4 1/6] qnx6: validate di_filelevels in qnx6_iget() before accessing level pointers Hui Peng
2026-09-30 7:22 ` Matthias Goergens
2026-09-30 3:16 ` [PATCH v4 2/6] qnx6: release bh on error path in qnx6_block_map() Hui Peng
2026-09-30 7:22 ` Matthias Goergens
2026-09-30 3:16 ` [PATCH v4 3/6] qnx6: avoid double brelse() and fix sb_buf leak on error path in qnx6_fill_super() Hui Peng
2026-09-30 3:16 ` [PATCH v4 4/6] qnx6: release bh2/bh1 on active superblock selection in qnx6_mmi_fill_super() Hui Peng
2026-09-30 3:16 ` [PATCH v4 5/6] qnx6: abort mount on superblock magic mismatch when silent is set " Hui Peng
2026-09-30 7:22 ` Matthias Goergens
2026-09-30 3:16 ` [PATCH v4 6/6] qnx6: validate sb_blocksize before dividing " Hui Peng
2026-09-30 7:22 ` Matthias Goergens
[not found] ` <20260921042511.1473629-7-benquike@gmail.com>
2026-09-24 7:39 ` [PATCH v3 " Hui Peng
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®