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/keyboard/spear-keyboard.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/keyboard/spear-keyboard.c')
| -rw-r--r-- | drivers/input/keyboard/spear-keyboard.c | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/drivers/input/keyboard/spear-keyboard.c b/drivers/input/keyboard/spear-keyboard.c new file mode 100644 index 000000000..53f3ac64c --- /dev/null +++ b/drivers/input/keyboard/spear-keyboard.c @@ -0,0 +1,327 @@ +/* + * SPEAr Keyboard Driver + * Based on omap-keypad driver + * + * Copyright (C) 2010 ST Microelectronics + * Rajeev Kumar <rajeevkumar.linux@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/input/matrix_keypad.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> + +/* Keyboard Registers */ +#define MODE_CTL_REG 0x00 +#define STATUS_REG 0x0C +#define DATA_REG 0x10 +#define INTR_MASK 0x54 + +/* Register Values */ +#define NUM_ROWS 16 +#define NUM_COLS 16 +#define MODE_CTL_PCLK_FREQ_SHIFT 9 +#define MODE_CTL_PCLK_FREQ_MSK 0x7F + +#define MODE_CTL_KEYBOARD (0x2 << 0) +#define MODE_CTL_SCAN_RATE_10 (0x0 << 2) +#define MODE_CTL_SCAN_RATE_20 (0x1 << 2) +#define MODE_CTL_SCAN_RATE_40 (0x2 << 2) +#define MODE_CTL_SCAN_RATE_80 (0x3 << 2) +#define MODE_CTL_KEYNUM_SHIFT 6 +#define MODE_CTL_START_SCAN (0x1 << 8) + +#define STATUS_DATA_AVAIL (0x1 << 1) + +#define DATA_ROW_MASK 0xF0 +#define DATA_COLUMN_MASK 0x0F + +#define ROW_SHIFT 4 + +struct spear_kbd { + struct input_dev *input; + void __iomem *io_base; + struct clk *clk; + unsigned int irq; + u32 mode; + u32 suspended_rate; + u32 mode_ctl_reg; + unsigned short last_key; + unsigned short keycodes[NUM_ROWS * NUM_COLS]; + bool irq_wake_enabled; +}; + +static irqreturn_t spear_kbd_interrupt(int irq, void *dev_id) +{ + struct spear_kbd *kbd = dev_id; + struct input_dev *input = kbd->input; + unsigned int key; + u32 sts, val; + + sts = readl_relaxed(kbd->io_base + STATUS_REG); + if (!(sts & STATUS_DATA_AVAIL)) + return IRQ_NONE; + + if (kbd->last_key != KEY_RESERVED) { + input_report_key(input, kbd->last_key, 0); + kbd->last_key = KEY_RESERVED; + } + + /* following reads active (row, col) pair */ + val = readl_relaxed(kbd->io_base + DATA_REG) & + (DATA_ROW_MASK | DATA_COLUMN_MASK); + key = kbd->keycodes[val]; + + input_event(input, EV_MSC, MSC_SCAN, val); + input_report_key(input, key, 1); + input_sync(input); + + kbd->last_key = key; + + /* clear interrupt */ + writel_relaxed(0, kbd->io_base + STATUS_REG); + + return IRQ_HANDLED; +} + +static int spear_kbd_open(struct input_dev *dev) +{ + struct spear_kbd *kbd = input_get_drvdata(dev); + int error; + u32 val; + + kbd->last_key = KEY_RESERVED; + + error = clk_enable(kbd->clk); + if (error) + return error; + + /* keyboard rate to be programmed is input clock (in MHz) - 1 */ + val = clk_get_rate(kbd->clk) / 1000000 - 1; + val = (val & MODE_CTL_PCLK_FREQ_MSK) << MODE_CTL_PCLK_FREQ_SHIFT; + + /* program keyboard */ + val = MODE_CTL_SCAN_RATE_80 | MODE_CTL_KEYBOARD | val | + (kbd->mode << MODE_CTL_KEYNUM_SHIFT); + writel_relaxed(val, kbd->io_base + MODE_CTL_REG); + writel_relaxed(1, kbd->io_base + STATUS_REG); + + /* start key scan */ + val = readl_relaxed(kbd->io_base + MODE_CTL_REG); + val |= MODE_CTL_START_SCAN; + writel_relaxed(val, kbd->io_base + MODE_CTL_REG); + + return 0; +} + +static void spear_kbd_close(struct input_dev *dev) +{ + struct spear_kbd *kbd = input_get_drvdata(dev); + u32 val; + + /* stop key scan */ + val = readl_relaxed(kbd->io_base + MODE_CTL_REG); + val &= ~MODE_CTL_START_SCAN; + writel_relaxed(val, kbd->io_base + MODE_CTL_REG); + + clk_disable(kbd->clk); + + kbd->last_key = KEY_RESERVED; +} + +static int spear_kbd_probe(struct platform_device *pdev) +{ + struct spear_kbd *kbd; + struct input_dev *input_dev; + int irq; + int error; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + kbd = devm_kzalloc(&pdev->dev, sizeof(*kbd), GFP_KERNEL); + if (!kbd) { + dev_err(&pdev->dev, "not enough memory for driver data\n"); + return -ENOMEM; + } + + error = device_property_read_u32(&pdev->dev, "st,mode", &kbd->mode); + if (error) { + dev_err(&pdev->dev, "Invalid or missing mode\n"); + return error; + } + + device_property_read_u32(&pdev->dev, "suspended_rate", &kbd->suspended_rate); + + input_dev = devm_input_allocate_device(&pdev->dev); + if (!input_dev) { + dev_err(&pdev->dev, "unable to allocate input device\n"); + return -ENOMEM; + } + + kbd->input = input_dev; + kbd->irq = irq; + + kbd->io_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); + if (IS_ERR(kbd->io_base)) + return PTR_ERR(kbd->io_base); + + kbd->clk = devm_clk_get_prepared(&pdev->dev, NULL); + if (IS_ERR(kbd->clk)) + return PTR_ERR(kbd->clk); + + input_dev->name = "Spear Keyboard"; + input_dev->phys = "keyboard/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->open = spear_kbd_open; + input_dev->close = spear_kbd_close; + + error = matrix_keypad_build_keymap(NULL, NULL, NUM_ROWS, NUM_COLS, + kbd->keycodes, input_dev); + if (error) { + dev_err(&pdev->dev, "Failed to build keymap\n"); + return error; + } + + if (device_property_read_bool(&pdev->dev, "autorepeat")) + __set_bit(EV_REP, input_dev->evbit); + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + input_set_drvdata(input_dev, kbd); + + error = devm_request_irq(&pdev->dev, irq, spear_kbd_interrupt, 0, + "keyboard", kbd); + if (error) { + dev_err(&pdev->dev, "request_irq failed\n"); + return error; + } + + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, "Unable to register keyboard device\n"); + return error; + } + + device_init_wakeup(&pdev->dev, 1); + platform_set_drvdata(pdev, kbd); + + return 0; +} + +static int spear_kbd_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spear_kbd *kbd = platform_get_drvdata(pdev); + struct input_dev *input_dev = kbd->input; + unsigned int rate = 0, mode_ctl_reg, val; + + guard(mutex)(&input_dev->mutex); + + /* explicitly enable clock as we may program device */ + clk_enable(kbd->clk); + + mode_ctl_reg = readl_relaxed(kbd->io_base + MODE_CTL_REG); + + if (device_may_wakeup(&pdev->dev)) { + if (!enable_irq_wake(kbd->irq)) + kbd->irq_wake_enabled = true; + + /* + * reprogram the keyboard operating frequency as on some + * platform it may change during system suspended + */ + if (kbd->suspended_rate) + rate = kbd->suspended_rate / 1000000 - 1; + else + rate = clk_get_rate(kbd->clk) / 1000000 - 1; + + val = mode_ctl_reg & + ~(MODE_CTL_PCLK_FREQ_MSK << MODE_CTL_PCLK_FREQ_SHIFT); + val |= (rate & MODE_CTL_PCLK_FREQ_MSK) + << MODE_CTL_PCLK_FREQ_SHIFT; + writel_relaxed(val, kbd->io_base + MODE_CTL_REG); + + } else { + if (input_device_enabled(input_dev)) { + writel_relaxed(mode_ctl_reg & ~MODE_CTL_START_SCAN, + kbd->io_base + MODE_CTL_REG); + clk_disable(kbd->clk); + } + } + + /* store current configuration */ + if (input_device_enabled(input_dev)) + kbd->mode_ctl_reg = mode_ctl_reg; + + /* restore previous clk state */ + clk_disable(kbd->clk); + + return 0; +} + +static int spear_kbd_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spear_kbd *kbd = platform_get_drvdata(pdev); + struct input_dev *input_dev = kbd->input; + + guard(mutex)(&input_dev->mutex); + + if (device_may_wakeup(&pdev->dev)) { + if (kbd->irq_wake_enabled) { + kbd->irq_wake_enabled = false; + disable_irq_wake(kbd->irq); + } + } else { + if (input_device_enabled(input_dev)) + clk_enable(kbd->clk); + } + + /* restore current configuration */ + if (input_device_enabled(input_dev)) + writel_relaxed(kbd->mode_ctl_reg, kbd->io_base + MODE_CTL_REG); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(spear_kbd_pm_ops, + spear_kbd_suspend, spear_kbd_resume); + +#ifdef CONFIG_OF +static const struct of_device_id spear_kbd_id_table[] = { + { .compatible = "st,spear300-kbd" }, + {} +}; +MODULE_DEVICE_TABLE(of, spear_kbd_id_table); +#endif + +static struct platform_driver spear_kbd_driver = { + .probe = spear_kbd_probe, + .driver = { + .name = "keyboard", + .pm = pm_sleep_ptr(&spear_kbd_pm_ops), + .of_match_table = of_match_ptr(spear_kbd_id_table), + }, +}; +module_platform_driver(spear_kbd_driver); + +MODULE_AUTHOR("Rajeev Kumar"); +MODULE_DESCRIPTION("SPEAr Keyboard Driver"); +MODULE_LICENSE("GPL"); |
