Writing an invalid value such as 0 to /sys/module/sg/parameters/scatter_elem_sz causes a shift-out-of-bounds bug during subsequent device operations. When opening a device via sg_open(), the driver calls sg_build_indirect() to allocate scatter-gather buffers. In sg_build_indirect(), the code checks if the local variable num is less than PAGE_SIZE and updates the global scatter_elem_sz and scatter_elem_sz_prev variables, but fails to update num. As a result, num remains 0 and is passed to order = get_order(num). Calling get_order(0) is undefined and underflows to 52 on 64-bit systems, causing ret_sz = 1 << (PAGE_SHIFT + order) to compute 1 << 64. Shifting a 32-bit int by 64 bits triggers a UBSAN shift-out-of-bounds warning: UBSAN: shift-out-of-bounds in drivers/scsi/sg.c:1887:13 shift exponent 64 is too large for 32-bit type 'int' Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 ubsan_epilogue+0xa/0x30 lib/ubsan.c:233 __ubsan_handle_shift_out_of_bounds+0x36d/0x400 lib/ubsan.c:494 sg_build_indirect+0x559/0x870 drivers/scsi/sg.c:1887 sg_build_reserve drivers/scsi/sg.c:1997 [inline] sg_add_sfp drivers/scsi/sg.c:2177 [inline] sg_open+0x1128/0x18a0 drivers/scsi/sg.c:349 chrdev_open+0x4d9/0x600 fs/char_dev.c:411 do_dentry_open+0x816/0x1380 fs/open.c:996 vfs_open+0x3b/0x340 fs/open.c:1101 Additionally, excessively large values written to scatter_elem_sz can cause order to exceed MAX_PAGE_ORDER, which can overflow signed 32-bit arithmetic and trigger warnings in the page allocator. Mutating global variables scatter_elem_sz and scatter_elem_sz_prev inside the I/O path without locking also introduces data races between concurrent device accesses. Fix this by replacing module_param_named() with module_param_cb() for scatter_elem_sz, similar to def_reserved_size. The setter callback scatter_elem_sz_set() validates that the input value is within [PAGE_SIZE, PAGE_SIZE << MAX_PAGE_ORDER], rejecting invalid values with -ERANGE or -EINVAL at the sysfs boundary. Remove scatter_elem_sz_prev and the runtime parameter modification from sg_build_indirect(), compute the allocation order directly from scatter_elem_sz, and simplify the per-element size calculation using min(rem_sz, ret_sz). Fixes: 10db10d144c0 ("sg: convert the indirect IO path to use the block layer") Assisted-by: Gemini:gemini-3.8-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+270f1c719ee7baab9941@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=270f1c719ee7baab9941 Link: https://syzkaller.appspot.com/ai_job?id=4512b081-e607-45b8-8a53-4eefe3737482 To: "James E.J. Bottomley" To: "Doug Gilbert" To: To: "Martin K. Petersen" To: "FUJITA Tomonori" Cc: --- diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c index 5408f002e..4c82fc00d 100644 --- a/drivers/scsi/sg.c +++ b/drivers/scsi/sg.c @@ -92,7 +92,6 @@ static int def_reserved_size = SG_DEF_RESERVED_SIZE; static int sg_allow_dio = SG_ALLOW_DIO_DEF; static int scatter_elem_sz = SG_SCATTER_SZ; -static int scatter_elem_sz_prev = SG_SCATTER_SZ; #define SG_SECTOR_SZ 512 @@ -1621,9 +1620,33 @@ sg_remove_device(struct device *cl_dev) kref_put(&sdp->d_ref, sg_device_destroy); } -module_param_named(scatter_elem_sz, scatter_elem_sz, int, S_IRUGO | S_IWUSR); module_param_named(allow_dio, sg_allow_dio, int, S_IRUGO | S_IWUSR); +static int scatter_elem_sz_set(const char *val, const struct kernel_param *kp) +{ + int size, ret; + + if (!val) + return -EINVAL; + + ret = kstrtoint(val, 0, &size); + if (ret) + return ret; + + if (size < PAGE_SIZE || size > (PAGE_SIZE << MAX_PAGE_ORDER)) + return -ERANGE; + + scatter_elem_sz = size; + return 0; +} + +static const struct kernel_param_ops scatter_elem_sz_ops = { + .set = scatter_elem_sz_set, + .get = param_get_int, +}; + +module_param_cb(scatter_elem_sz, &scatter_elem_sz_ops, &scatter_elem_sz, 0644); + static int def_reserved_size_set(const char *val, const struct kernel_param *kp) { int size, ret; @@ -1667,10 +1690,8 @@ init_sg(void) { int rc; - if (scatter_elem_sz < PAGE_SIZE) { + if (scatter_elem_sz < PAGE_SIZE) scatter_elem_sz = PAGE_SIZE; - scatter_elem_sz_prev = scatter_elem_sz; - } rc = register_chrdev_region(MKDEV(SCSI_GENERIC_MAJOR, 0), SG_MAX_DEVS, "sg"); @@ -1873,36 +1894,19 @@ sg_build_indirect(Sg_scatter_hold * schp, Sg_fd * sfp, int buff_size) if (mx_sc_elems < 0) return mx_sc_elems; /* most likely -ENOMEM */ - num = scatter_elem_sz; - if (unlikely(num != scatter_elem_sz_prev)) { - if (num < PAGE_SIZE) { - scatter_elem_sz = PAGE_SIZE; - scatter_elem_sz_prev = PAGE_SIZE; - } else - scatter_elem_sz_prev = num; - } - - order = get_order(num); + order = get_order(scatter_elem_sz); retry: ret_sz = 1 << (PAGE_SHIFT + order); for (k = 0, rem_sz = blk_size; rem_sz > 0 && k < mx_sc_elems; k++, rem_sz -= ret_sz) { - num = (rem_sz > scatter_elem_sz_prev) ? - scatter_elem_sz_prev : rem_sz; + num = min(rem_sz, ret_sz); schp->pages[k] = alloc_pages(gfp_mask, order); if (!schp->pages[k]) goto out; - if (num == scatter_elem_sz_prev) { - if (unlikely(ret_sz > scatter_elem_sz_prev)) { - scatter_elem_sz = ret_sz; - scatter_elem_sz_prev = ret_sz; - } - } - SCSI_LOG_TIMEOUT(5, sg_printk(KERN_INFO, sfp->parentdp, "sg_build_indirect: k=%d, num=%d, ret_sz=%d\n", k, num, ret_sz)); base-commit: df2908090cda368b01ff43709f51890076c56157 -- This is an AI-generated patch subject to moderation. Reply with '#syz upstream' to Sign-off the patch as a human author and send it to the upstream kernel mailing lists. Reply with '#syz reject' to reject it ('#syz unreject' to undo). See https://goo.gle/syzbot-ai-patches for information about AI-generated patches. You can comment on the patch as usual, syzbot will try to address the comments and send a new version of the patch if necessary. syzbot engineers can be reached at syzkaller@googlegroups.com.