[PATCH 2/2] qnx6: avoid undefined shifts in qnx6_block_map()

From: Matthias Goergens

Date: Fri Sep 25 2026 - 11:17:25 EST


qnx6_block_map() takes each level's index from the 32-bit block number
by shifting it right by ptrbits * depth. QNX6_PTR_MAX_LEVELS allows
five levels, and ptrbits is 7 for 512 byte blocks and 10 for 4K
blocks, so a depth-5 tree with 512 byte blocks shifts by 35 and a
depth-4 tree with 4K blocks by 40. Such trees are deeper than any
32-bit block number needs, but the driver accepts them, and a shift by
32 or more is undefined. UBSAN reports shift-out-of-bounds, and on
x86, which masks the shift count, files in such trees read back with
the wrong contents; at depth 5 with 512 byte blocks UBSAN also reports
index 16 out of range for di_block_ptr[].

The index bits at those offsets are zero for any 32-bit block number,
so use zero for them explicitly and walk the remaining levels as
before. Casting the block number to u64 would not be enough: with 64K
blocks, which sb_set_blocksize() accepts on 64K-page kernels, ptrbits
is 14 and the shift reaches 70.

Fixes: 5d026c724220 ("fs: initial qnx6fs addition")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Matthias Goergens <matthias.goergens@xxxxxxxxx>
---
fs/qnx6/inode.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 0dfe8a3dab83..fd61cf27b81d 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -124,8 +124,13 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no)
int depth = ei->di_filelevels;
int i;

+ /*
+ * For valid levels bitdelta can reach the width of no (e.g. 35 for
+ * 512 byte blocks at depth 5). Index bits beyond no are zero, and
+ * shifting by that much would be undefined.
+ */
bitdelta = ptrbits * depth;
- levelptr = no >> bitdelta;
+ levelptr = bitdelta < BITS_PER_TYPE(no) ? no >> bitdelta : 0;

if (levelptr > QNX6_NO_DIRECT_POINTERS - 1) {
pr_err("Requested file block number (%u) too big.", no);
@@ -141,7 +146,8 @@ static unsigned qnx6_block_map(struct inode *inode, unsigned no)
return 0;
}
bitdelta -= ptrbits;
- levelptr = (no >> bitdelta) & mask;
+ levelptr = bitdelta < BITS_PER_TYPE(no) ?
+ (no >> bitdelta) & mask : 0;
ptr = ((__fs32 *)bh->b_data)[levelptr];

if (!qnx6_check_blockptr(ptr)) {
--
2.55.0