diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/comedi/comedi_pcmcia.c | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
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) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/comedi/comedi_pcmcia.c')
| -rw-r--r-- | drivers/comedi/comedi_pcmcia.c | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/drivers/comedi/comedi_pcmcia.c b/drivers/comedi/comedi_pcmcia.c new file mode 100644 index 000000000..17962bf66 --- /dev/null +++ b/drivers/comedi/comedi_pcmcia.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi_pcmcia.c + * Comedi PCMCIA driver specific functions. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/comedi/comedi_pcmcia.h> + +/** + * comedi_to_pcmcia_dev() - Return PCMCIA 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 pcmcia_device. + * + * Return: Attached PCMCIA device if @dev->hw_dev is non-%NULL. + * Return %NULL if @dev->hw_dev is %NULL. + */ +struct pcmcia_device *comedi_to_pcmcia_dev(struct comedi_device *dev) +{ + return dev->hw_dev ? to_pcmcia_dev(dev->hw_dev) : NULL; +} +EXPORT_SYMBOL_GPL(comedi_to_pcmcia_dev); + +static int comedi_pcmcia_conf_check(struct pcmcia_device *link, + void *priv_data) +{ + if (link->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(link); +} + +/** + * comedi_pcmcia_enable() - Request the regions and enable the PCMCIA device + * @dev: COMEDI device. + * @conf_check: Optional callback to check each configuration option of the + * PCMCIA device and request I/O regions. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a a + * &struct device embedded in a &struct pcmcia_device. The comedi PCMCIA + * driver needs to set the 'config_flags' member in the &struct pcmcia_device, + * as appropriate for that driver, before calling this function in order to + * allow pcmcia_loop_config() to do its internal autoconfiguration. + * + * If @conf_check is %NULL it is set to a default function. If is + * passed to pcmcia_loop_config() and should return %0 if the configuration + * is valid and I/O regions requested successfully, otherwise it should return + * a negative error value. The default function returns -%EINVAL if the + * 'config_index' member is %0, otherwise it calls pcmcia_request_io() and + * returns the result. + * + * If the above configuration check passes, pcmcia_enable_device() is called + * to set up and activate the PCMCIA device. + * + * If this function returns an error, comedi_pcmcia_disable() should be called + * to release requested resources. + * + * Return: + * 0 on success, + * -%ENODEV id @dev->hw_dev is %NULL, + * a negative error number from pcmcia_loop_config() if it fails, + * or a negative error number from pcmcia_enable_device() if it fails. + */ +int comedi_pcmcia_enable(struct comedi_device *dev, + int (*conf_check)(struct pcmcia_device *p_dev, + void *priv_data)) +{ + struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + int ret; + + if (!link) + return -ENODEV; + + if (!conf_check) + conf_check = comedi_pcmcia_conf_check; + + ret = pcmcia_loop_config(link, conf_check, NULL); + if (ret) + return ret; + + return pcmcia_enable_device(link); +} +EXPORT_SYMBOL_GPL(comedi_pcmcia_enable); + +/** + * comedi_pcmcia_disable() - Disable the PCMCIA device and release the regions + * @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 pcmcia_device. Call + * pcmcia_disable_device() to disable and clean up the PCMCIA device. + */ +void comedi_pcmcia_disable(struct comedi_device *dev) +{ + struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + + if (link) + pcmcia_disable_device(link); +} +EXPORT_SYMBOL_GPL(comedi_pcmcia_disable); + +/** + * comedi_pcmcia_auto_config() - Configure/probe a PCMCIA COMEDI device + * @link: PCMCIA device. + * @driver: Registered COMEDI driver. + * + * Typically called from the pcmcia_driver (*probe) function. Auto-configure + * a COMEDI device, using a pointer to the &struct device embedded in *@link + * as the hardware device. The @driver's "auto_attach" handler may call + * comedi_to_pcmcia_dev() on the passed in COMEDI device to recover @link. + * + * Return: The result of calling comedi_auto_config() (0 on success, or a + * negative error number on failure). + */ +int comedi_pcmcia_auto_config(struct pcmcia_device *link, + struct comedi_driver *driver) +{ + return comedi_auto_config(&link->dev, driver, 0); +} +EXPORT_SYMBOL_GPL(comedi_pcmcia_auto_config); + +/** + * comedi_pcmcia_auto_unconfig() - Unconfigure/remove a PCMCIA COMEDI device + * @link: PCMCIA device. + * + * Typically called from the pcmcia_driver (*remove) function. + * Auto-unconfigure a COMEDI device attached to this PCMCIA device, using a + * pointer to the &struct device embedded in *@link 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_pcmcia_auto_unconfig(struct pcmcia_device *link) +{ + comedi_auto_unconfig(&link->dev); +} +EXPORT_SYMBOL_GPL(comedi_pcmcia_auto_unconfig); + +/** + * comedi_pcmcia_driver_register() - Register a PCMCIA COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @pcmcia_driver: PCMCIA driver to be registered. + * + * This function is used for the module_init() of PCMCIA COMEDI driver modules + * to register the COMEDI driver and the PCMCIA driver. Do not call it + * directly, use the module_comedi_pcmcia_driver() helper macro instead. + * + * Return: 0 on success, or a negative error number on failure. + */ +int comedi_pcmcia_driver_register(struct comedi_driver *comedi_driver, + struct pcmcia_driver *pcmcia_driver) +{ + int ret; + + ret = comedi_driver_register(comedi_driver); + if (ret < 0) + return ret; + + ret = pcmcia_register_driver(pcmcia_driver); + if (ret < 0) { + comedi_driver_unregister(comedi_driver); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_register); + +/** + * comedi_pcmcia_driver_unregister() - Unregister a PCMCIA COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @pcmcia_driver: PCMCIA driver to be registered. + * + * This function is called from the module_exit() of PCMCIA COMEDI driver + * modules to unregister the PCMCIA driver and the COMEDI driver. Do not call + * it directly, use the module_comedi_pcmcia_driver() helper macro instead. + */ +void comedi_pcmcia_driver_unregister(struct comedi_driver *comedi_driver, + struct pcmcia_driver *pcmcia_driver) +{ + pcmcia_unregister_driver(pcmcia_driver); + comedi_driver_unregister(comedi_driver); +} +EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_unregister); + +MODULE_AUTHOR("https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi PCMCIA interface module"); +MODULE_LICENSE("GPL"); |
