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/input/joystick/pxrc.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/input/joystick/pxrc.c')
| -rw-r--r-- | drivers/input/joystick/pxrc.c | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/drivers/input/joystick/pxrc.c b/drivers/input/joystick/pxrc.c new file mode 100644 index 000000000..52d9eab66 --- /dev/null +++ b/drivers/input/joystick/pxrc.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Phoenix RC Flight Controller Adapter + * + * Copyright (C) 2018 Marcus Folkesson <marcus.folkesson@gmail.com> + */ + +#include <linux/cleanup.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <linux/usb.h> +#include <linux/usb/input.h> + +#define PXRC_VENDOR_ID 0x1781 +#define PXRC_PRODUCT_ID 0x0898 + +struct pxrc { + struct input_dev *input; + struct usb_interface *intf; + struct urb *urb; + struct mutex pm_mutex; + bool is_open; + char phys[64]; +}; + +static void pxrc_usb_irq(struct urb *urb) +{ + struct pxrc *pxrc = urb->context; + u8 *data = urb->transfer_buffer; + int error; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ETIME: + /* this urb is timing out */ + dev_dbg(&pxrc->intf->dev, + "%s - urb timed out - was the device unplugged?\n", + __func__); + return; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -EPIPE: + /* this urb is terminated, clean up */ + dev_dbg(&pxrc->intf->dev, "%s - urb shutting down with status: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(&pxrc->intf->dev, "%s - nonzero urb status received: %d\n", + __func__, urb->status); + goto exit; + } + + if (urb->actual_length == 8) { + input_report_abs(pxrc->input, ABS_X, data[0]); + input_report_abs(pxrc->input, ABS_Y, data[2]); + input_report_abs(pxrc->input, ABS_RX, data[3]); + input_report_abs(pxrc->input, ABS_RY, data[4]); + input_report_abs(pxrc->input, ABS_RUDDER, data[5]); + input_report_abs(pxrc->input, ABS_THROTTLE, data[6]); + input_report_abs(pxrc->input, ABS_MISC, data[7]); + + input_report_key(pxrc->input, BTN_A, data[1]); + } + +exit: + /* Resubmit to fetch new fresh URBs */ + error = usb_submit_urb(urb, GFP_ATOMIC); + if (error && error != -EPERM) + dev_err(&pxrc->intf->dev, + "%s - usb_submit_urb failed with result: %d", + __func__, error); +} + +static int pxrc_open(struct input_dev *input) +{ + struct pxrc *pxrc = input_get_drvdata(input); + int error; + + guard(mutex)(&pxrc->pm_mutex); + error = usb_submit_urb(pxrc->urb, GFP_KERNEL); + if (error) { + dev_err(&pxrc->intf->dev, + "%s - usb_submit_urb failed, error: %d\n", + __func__, error); + return -EIO; + } + + pxrc->is_open = true; + return 0; +} + +static void pxrc_close(struct input_dev *input) +{ + struct pxrc *pxrc = input_get_drvdata(input); + + guard(mutex)(&pxrc->pm_mutex); + usb_kill_urb(pxrc->urb); + pxrc->is_open = false; +} + +static void pxrc_free_urb(void *_pxrc) +{ + struct pxrc *pxrc = _pxrc; + + usb_free_urb(pxrc->urb); +} + +static int pxrc_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct pxrc *pxrc; + struct usb_endpoint_descriptor *epirq; + size_t xfer_size; + void *xfer_buf; + int error; + + /* + * Locate the endpoint information. This device only has an + * interrupt endpoint. + */ + error = usb_find_common_endpoints(intf->cur_altsetting, + NULL, NULL, &epirq, NULL); + if (error) { + dev_err(&intf->dev, "Could not find endpoint\n"); + return error; + } + + pxrc = devm_kzalloc(&intf->dev, sizeof(*pxrc), GFP_KERNEL); + if (!pxrc) + return -ENOMEM; + + mutex_init(&pxrc->pm_mutex); + pxrc->intf = intf; + + usb_set_intfdata(pxrc->intf, pxrc); + + xfer_size = usb_endpoint_maxp(epirq); + xfer_buf = devm_kmalloc(&intf->dev, xfer_size, GFP_KERNEL); + if (!xfer_buf) + return -ENOMEM; + + pxrc->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pxrc->urb) + return -ENOMEM; + + error = devm_add_action_or_reset(&intf->dev, pxrc_free_urb, pxrc); + if (error) + return error; + + usb_fill_int_urb(pxrc->urb, udev, + usb_rcvintpipe(udev, epirq->bEndpointAddress), + xfer_buf, xfer_size, pxrc_usb_irq, pxrc, 1); + + pxrc->input = devm_input_allocate_device(&intf->dev); + if (!pxrc->input) { + dev_err(&intf->dev, "couldn't allocate input device\n"); + return -ENOMEM; + } + + pxrc->input->name = "PXRC Flight Controller Adapter"; + + usb_make_path(udev, pxrc->phys, sizeof(pxrc->phys)); + strlcat(pxrc->phys, "/input0", sizeof(pxrc->phys)); + pxrc->input->phys = pxrc->phys; + + usb_to_input_id(udev, &pxrc->input->id); + + pxrc->input->open = pxrc_open; + pxrc->input->close = pxrc_close; + + input_set_capability(pxrc->input, EV_KEY, BTN_A); + input_set_abs_params(pxrc->input, ABS_X, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_RX, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_RY, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_RUDDER, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_THROTTLE, 0, 255, 0, 0); + input_set_abs_params(pxrc->input, ABS_MISC, 0, 255, 0, 0); + + input_set_drvdata(pxrc->input, pxrc); + + error = input_register_device(pxrc->input); + if (error) + return error; + + return 0; +} + +static void pxrc_disconnect(struct usb_interface *intf) +{ + /* All driver resources are devm-managed. */ +} + +static int pxrc_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct pxrc *pxrc = usb_get_intfdata(intf); + + guard(mutex)(&pxrc->pm_mutex); + if (pxrc->is_open) + usb_kill_urb(pxrc->urb); + + return 0; +} + +static int pxrc_resume(struct usb_interface *intf) +{ + struct pxrc *pxrc = usb_get_intfdata(intf); + + guard(mutex)(&pxrc->pm_mutex); + if (pxrc->is_open && usb_submit_urb(pxrc->urb, GFP_KERNEL) < 0) + return -EIO; + + return 0; +} + +static int pxrc_pre_reset(struct usb_interface *intf) +{ + struct pxrc *pxrc = usb_get_intfdata(intf); + + mutex_lock(&pxrc->pm_mutex); + usb_kill_urb(pxrc->urb); + return 0; +} + +static int pxrc_post_reset(struct usb_interface *intf) +{ + struct pxrc *pxrc = usb_get_intfdata(intf); + int retval = 0; + + if (pxrc->is_open && usb_submit_urb(pxrc->urb, GFP_KERNEL) < 0) + retval = -EIO; + + mutex_unlock(&pxrc->pm_mutex); + + return retval; +} + +static int pxrc_reset_resume(struct usb_interface *intf) +{ + return pxrc_resume(intf); +} + +static const struct usb_device_id pxrc_table[] = { + { USB_DEVICE(PXRC_VENDOR_ID, PXRC_PRODUCT_ID) }, + { } +}; +MODULE_DEVICE_TABLE(usb, pxrc_table); + +static struct usb_driver pxrc_driver = { + .name = "pxrc", + .probe = pxrc_probe, + .disconnect = pxrc_disconnect, + .id_table = pxrc_table, + .suspend = pxrc_suspend, + .resume = pxrc_resume, + .pre_reset = pxrc_pre_reset, + .post_reset = pxrc_post_reset, + .reset_resume = pxrc_reset_resume, +}; + +module_usb_driver(pxrc_driver); + +MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>"); +MODULE_DESCRIPTION("PhoenixRC Flight Controller Adapter"); +MODULE_LICENSE("GPL v2"); |
