From eae696d6f24379e29f534f8a87d3f565789a7d1e Mon Sep 17 00:00:00 2001 From: Sungwoo Kim Date: Sat, 7 Mar 2026 19:13:58 -0500 Subject: [PATCH] Fix null-ptr-deref in bio_integrity_map_user() A malicious user program can request large user-memory pinning via ioctl with a large metadata_len. However, this does not guarantee that all requested memory will be pinned. Pinning may partially succeed and return the number of bytes that were actually pinned, which may not match the requested size. In this case, only the addresses of the pinned pages are valid. The current implementation does not handle partial pinning and incorrectly assumes that all pages in the range [0, nr_vecs) are valid. This can lead to a null-pointer dereference because pages[n] may refer to an unpinned memory range. To fix this, add a check to verify that all requested pages are successfully pinned. Pinning all pages is required to copy user data. KASAN splat: Syzkaller hit 'general protection fault in bio_integrity_map_user' bug. nvme nvme0: Command: 80f60320000000000300000000c9ffffb38ab5410000000070693aa0ffffffffb00e619dffffffff80f6032000000000b38ab5410000000070fc38a0ffffffff nvme nvme0: Command: 80f60320000000000300000000c9ffffb38ab5410000000070693aa0ffffffffb00e619dffffffff80f6032000000000b38ab5410000000070fc38a0ffffffff nvme nvme0: 2/0/0 default/read/poll queues Oops: general protection fault, probably for non-canonical address 0xdffffc0000000001: 0000 [#1] PREEMPT SMP KASAN PTI KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f] CPU: 0 UID: 0 PID: 280 Comm: syz-executor294 Not tainted 6.11.0-dirty #6 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014 RIP: 0010:_compound_head home/wukong/fuzznvme/linux/./include/linux/page-flags.h:240 [inline] RIP: 0010:bvec_from_pages home/wukong/fuzznvme/linux/block/bio-integrity.c:290 [inline] RIP: 0010:bio_integrity_map_user+0x5a3/0x11e0 home/wukong/fuzznvme/linux/block/bio-integrity.c:345 Code: 4c 89 e0 48 c1 e8 03 80 3c 30 00 0f 85 4b 0a 00 00 48 be 00 00 00 00 00 fc ff df 49 8b 1c 24 48 8d 7b 08 48 89 f8 48 c1 e8 03 <80> 3c 30 00 0f 85 35 0a 00 00 48 8b 43 08 31 ff 49 89 c5 48 89 44 RSP: 0018:ffffc900010cf4f0 EFLAGS: 00010202 RAX: 0000000000000001 RBX: 0000000000000000 RCX: 000000000000f761 RDX: ffff888006cae600 RSI: dffffc0000000000 RDI: 0000000000000008 RBP: ffffc900010cf7d0 R08: ffff888006cae600 R09: ffffed1000e0db95 R10: ffff88800706dcaf R11: ffff888006a31a00 R12: ffff888006a31a08 R13: 0000000000000740 R14: ffff888006a31a00 R15: 0000000000000001 FS: 0000555587e483c0(0000) GS:ffff88806ce00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000000002002f8c0 CR3: 000000000719a000 CR4: 00000000000006f0 Call Trace: nvme_map_user_request+0x4b6/0x5e0 home/wukong/fuzznvme/linux/drivers/nvme/host/ioctl.c:149 nvme_submit_user_cmd+0x2e8/0x3c0 home/wukong/fuzznvme/linux/drivers/nvme/host/ioctl.c:185 nvme_user_cmd.constprop.0+0x35b/0x540 home/wukong/fuzznvme/linux/drivers/nvme/host/ioctl.c:325 nvme_ns_ioctl+0x11e/0x1c0 home/wukong/fuzznvme/linux/drivers/nvme/host/ioctl.c:570 nvme_ioctl+0x147/0x1d0 home/wukong/fuzznvme/linux/drivers/nvme/host/ioctl.c:605 blkdev_ioctl+0x28c/0x6c0 home/wukong/fuzznvme/linux/block/ioctl.c:676 vfs_ioctl home/wukong/fuzznvme/linux/fs/ioctl.c:51 [inline] __do_sys_ioctl home/wukong/fuzznvme/linux/fs/ioctl.c:907 [inline] __se_sys_ioctl home/wukong/fuzznvme/linux/fs/ioctl.c:893 [inline] __x64_sys_ioctl+0x1bc/0x230 home/wukong/fuzznvme/linux/fs/ioctl.c:893 x64_sys_call+0x1209/0x20d0 home/wukong/fuzznvme/linux/./arch/x86/include/generated/asm/syscalls_64.h:17 do_syscall_x64 home/wukong/fuzznvme/linux/arch/x86/entry/common.c:52 [inline] do_syscall_64+0x6f/0x110 home/wukong/fuzznvme/linux/arch/x86/entry/common.c:83 entry_SYSCALL_64_after_hwframe+0x76/0x7e RIP: 0033:0x7f5b3d8e98bd Code: c3 e8 a7 1f 00 00 0f 1f 80 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007fffd1c0e988 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00000000000f4240 RCX: 00007f5b3d8e98bd RDX: 000000002003f680 RSI: 00000000c0484e43 RDI: 0000000000000003 RBP: 0000000000000000 R08: 00007f5b3d93eb4d R09: 00007f5b3d93eb4d R10: 00007f5b3d93eb4d R11: 0000000000000246 R12: 0000000000000001 R13: 00007fffd1c0ebe8 R14: 00007fffd1c0e9b0 R15: 00007fffd1c0e9a0 Modules linked in: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000001: 0000 [#2] PREEMPT SMP KASAN PTI Fixes: 492c5d455969 (block: bio-integrity: directly map user buffers) Acked-by: Chao Shi Acked-by: Weidong Zhu Acked-by: Dave Tian Signed-off-by: Sungwoo Kim --- block/bio-integrity.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/block/bio-integrity.c b/block/bio-integrity.c index 20f5d301d32d..8de950c63dc3 100644 --- a/block/bio-integrity.c +++ b/block/bio-integrity.c @@ -338,6 +338,23 @@ int bio_integrity_map_user(struct bio *bio, struct iov_iter *iter) extraction_flags, &offset); if (unlikely(ret < 0)) goto free_bvec; + if (unlikely(ret != bytes)) { + /* + * Not all pages could be pinned. This can happen when + * pin_user_pages_fast() returns fewer pages than requested. + * All pages must be pinned to copy user data, so unpin + * whatever we got and fail. + */ + int npinned = DIV_ROUND_UP(offset + ret, PAGE_SIZE); + int i; + + for (i = 0; i < npinned; i++) + unpin_user_page(pages[i]); + if (pages != stack_pages) + kvfree(pages); + ret = -EFAULT; + goto free_bvec; + } nr_bvecs = bvec_from_pages(bvec, pages, nr_vecs, bytes, offset, &is_p2p);