From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/comedi/comedi_usb.c | 139 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 drivers/comedi/comedi_usb.c (limited to 'drivers/comedi/comedi_usb.c') diff --git a/drivers/comedi/comedi_usb.c b/drivers/comedi/comedi_usb.c new file mode 100644 index 000000000..75d65171c --- /dev/null +++ b/drivers/comedi/comedi_usb.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi_usb.c + * Comedi USB driver specific functions. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-2000 David A. Schleef + */ + +#include +#include + +/** + * comedi_to_usb_interface() - Return USB interface attached to COMEDI device + * @dev: COMEDI device. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct usb_interface. + * + * Return: Attached USB interface if @dev->hw_dev is non-%NULL. + * Return %NULL if @dev->hw_dev is %NULL. + */ +struct usb_interface *comedi_to_usb_interface(struct comedi_device *dev) +{ + return dev->hw_dev ? to_usb_interface(dev->hw_dev) : NULL; +} +EXPORT_SYMBOL_GPL(comedi_to_usb_interface); + +/** + * comedi_to_usb_dev() - Return USB device attached to COMEDI device + * @dev: COMEDI device. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct usb_interface. + * + * Return: USB device to which the USB interface belongs if @dev->hw_dev is + * non-%NULL. Return %NULL if @dev->hw_dev is %NULL. + */ +struct usb_device *comedi_to_usb_dev(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + + return intf ? interface_to_usbdev(intf) : NULL; +} +EXPORT_SYMBOL_GPL(comedi_to_usb_dev); + +/** + * comedi_usb_auto_config() - Configure/probe a USB COMEDI driver + * @intf: USB interface. + * @driver: Registered COMEDI driver. + * @context: Driver specific data, passed to comedi_auto_config(). + * + * Typically called from the usb_driver (*probe) function. Auto-configure a + * COMEDI device, using a pointer to the &struct device embedded in *@intf as + * the hardware device. The @context value gets passed through to @driver's + * "auto_attach" handler. The "auto_attach" handler may call + * comedi_to_usb_interface() on the passed in COMEDI device to recover @intf. + * + * Return: The result of calling comedi_auto_config() (%0 on success, or + * a negative error number on failure). + */ +int comedi_usb_auto_config(struct usb_interface *intf, + struct comedi_driver *driver, + unsigned long context) +{ + return comedi_auto_config(&intf->dev, driver, context); +} +EXPORT_SYMBOL_GPL(comedi_usb_auto_config); + +/** + * comedi_usb_auto_unconfig() - Unconfigure/disconnect a USB COMEDI device + * @intf: USB interface. + * + * Typically called from the usb_driver (*disconnect) function. + * Auto-unconfigure a COMEDI device attached to this USB interface, using a + * pointer to the &struct device embedded in *@intf as the hardware device. + * The COMEDI driver's "detach" handler will be called during unconfiguration + * of the COMEDI device. + * + * Note that the COMEDI device may have already been unconfigured using the + * %COMEDI_DEVCONFIG ioctl, in which case this attempt to unconfigure it + * again should be ignored. + */ +void comedi_usb_auto_unconfig(struct usb_interface *intf) +{ + comedi_auto_unconfig(&intf->dev); +} +EXPORT_SYMBOL_GPL(comedi_usb_auto_unconfig); + +/** + * comedi_usb_driver_register() - Register a USB COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @usb_driver: USB driver to be registered. + * + * This function is called from the module_init() of USB COMEDI driver modules + * to register the COMEDI driver and the USB driver. Do not call it directly, + * use the module_comedi_usb_driver() helper macro instead. + * + * Return: %0 on success, or a negative error number on failure. + */ +int comedi_usb_driver_register(struct comedi_driver *comedi_driver, + struct usb_driver *usb_driver) +{ + int ret; + + ret = comedi_driver_register(comedi_driver); + if (ret < 0) + return ret; + + ret = usb_register(usb_driver); + if (ret < 0) { + comedi_driver_unregister(comedi_driver); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(comedi_usb_driver_register); + +/** + * comedi_usb_driver_unregister() - Unregister a USB COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @usb_driver: USB driver to be registered. + * + * This function is called from the module_exit() of USB COMEDI driver modules + * to unregister the USB driver and the COMEDI driver. Do not call it + * directly, use the module_comedi_usb_driver() helper macro instead. + */ +void comedi_usb_driver_unregister(struct comedi_driver *comedi_driver, + struct usb_driver *usb_driver) +{ + usb_deregister(usb_driver); + comedi_driver_unregister(comedi_driver); +} +EXPORT_SYMBOL_GPL(comedi_usb_driver_unregister); + +MODULE_AUTHOR("https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi USB interface module"); +MODULE_LICENSE("GPL"); -- cgit v1.3.1