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