[PATCH v2 1/6] qnx6: validate di_filelevels in qnx6_iget()

From: Hui Peng

Date: Mon Sep 21 2026 - 00:27:22 EST


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@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@xxxxxxxxx>
---
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