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/thermal/testing/command.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/thermal/testing/command.c')
| -rw-r--r-- | drivers/thermal/testing/command.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/drivers/thermal/testing/command.c b/drivers/thermal/testing/command.c new file mode 100644 index 000000000..90e06c593 --- /dev/null +++ b/drivers/thermal/testing/command.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2024, Intel Corporation + * + * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> + * + * Thermal subsystem testing facility. + * + * This facility allows the thermal core functionality to be exercised in a + * controlled way in order to verify its behavior. + * + * It resides in the "thermal-testing" directory under the debugfs root and + * starts with a single file called "command" which can be written a string + * representing a thermal testing facility command. + * + * The currently supported commands are listed in the tt_commands enum below. + * + * The "addtz" command causes a new test thermal zone template to be created, + * for example: + * + * # echo addtz > /sys/kernel/debug/thermal-testing/command + * + * That template will be represented as a subdirectory in the "thermal-testing" + * directory, for example + * + * # ls /sys/kernel/debug/thermal-testing/ + * command tz0 + * + * The thermal zone template can be populated with trip points with the help of + * the "tzaddtrip" command, for example: + * + * # echo tzaddtrip:0 > /sys/kernel/debug/thermal-testing/command + * + * which causes a trip point template to be added to the test thermal zone + * template 0 (represented by the tz0 subdirectory in "thermal-testing"). + * + * # ls /sys/kernel/debug/thermal-testing/tz0 + * init_temp temp trip_0_temp trip_0_hyst + * + * The temperature of a trip point template is initially THERMAL_TEMP_INVALID + * and its hysteresis is initially 0. They can be adjusted by writing to the + * "trip_x_temp" and "trip_x_hyst" files correspoinding to that trip point + * template, respectively. + * + * The initial temperature of a thermal zone based on a template can be set by + * writing to the "init_temp" file in its directory under "thermal-testing", for + * example: + * + * echo 50000 > /sys/kernel/debug/thermal-testing/tz0/init_temp + * + * When ready, "tzreg" command can be used for registering and enabling a + * thermal zone based on a given template with the thermal core, for example + * + * # echo tzreg:0 > /sys/kernel/debug/thermal-testing/command + * + * In this case, test thermal zone template 0 is used for registering a new + * thermal zone and the set of trip point templates associated with it is used + * for populating the new thermal zone's trip points table. The type of the new + * thermal zone is "test_tz". + * + * The temperature and hysteresis of all of the trip points in that new thermal + * zone are adjustable via sysfs, so they can be updated at any time. + * + * The current temperature of the new thermal zone can be set by writing to the + * "temp" file in the corresponding thermal zone template's directory under + * "thermal-testing", for example + * + * echo 10000 > /sys/kernel/debug/thermal-testing/tz0/temp + * + * which will also trigger a temperature update for this zone in the thermal + * core, including checking its trip points, sending notifications to user space + * if any of them have been crossed and so on. + * + * When it is not needed any more, a test thermal zone template can be deleted + * with the help of the "deltz" command, for example + * + * # echo deltz:0 > /sys/kernel/debug/thermal-testing/command + * + * which will also unregister the thermal zone based on it, if present. + */ + +#define pr_fmt(fmt) "thermal-testing: " fmt + +#include <linux/debugfs.h> +#include <linux/module.h> + +#include "thermal_testing.h" + +struct workqueue_struct *tt_wq __ro_after_init; + +struct dentry *d_testing __ro_after_init; +static struct dentry *d_command __ro_after_init; + +#define TT_COMMAND_SIZE 16 + +enum tt_commands { + TT_CMD_ADDTZ, + TT_CMD_DELTZ, + TT_CMD_TZADDTRIP, + TT_CMD_TZREG, + TT_CMD_TZUNREG, +}; + +static const char *tt_command_strings[] = { + [TT_CMD_ADDTZ] = "addtz", + [TT_CMD_DELTZ] = "deltz", + [TT_CMD_TZADDTRIP] = "tzaddtrip", + [TT_CMD_TZREG] = "tzreg", + [TT_CMD_TZUNREG] = "tzunreg", +}; + +static int tt_command_exec(int index, const char *arg) +{ + int ret; + + switch (index) { + case TT_CMD_ADDTZ: + ret = tt_add_tz(); + break; + + case TT_CMD_DELTZ: + if (!arg || !*arg) + return -EINVAL; + + ret = tt_del_tz(arg); + break; + + case TT_CMD_TZADDTRIP: + if (!arg || !*arg) + return -EINVAL; + + ret = tt_zone_add_trip(arg); + break; + + case TT_CMD_TZREG: + if (!arg || !*arg) + return -EINVAL; + + ret = tt_zone_reg(arg); + break; + + case TT_CMD_TZUNREG: + if (!arg || !*arg) + return -EINVAL; + + ret = tt_zone_unreg(arg); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static ssize_t tt_command_process(char *s) +{ + char *arg; + int i; + + strim(s); + + arg = strchr(s, ':'); + if (arg) { + *arg = '\0'; + arg++; + } + + for (i = 0; i < ARRAY_SIZE(tt_command_strings); i++) { + if (!strcmp(s, tt_command_strings[i])) + return tt_command_exec(i, arg); + } + + return -EINVAL; +} + +static ssize_t tt_command_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[TT_COMMAND_SIZE]; + ssize_t ret; + + if (*ppos) + return -EINVAL; + + if (count > TT_COMMAND_SIZE - 1) + return -E2BIG; + + if (copy_from_user(buf, user_buf, count)) + return -EFAULT; + buf[count] = '\0'; + + ret = tt_command_process(buf); + if (ret) + return ret; + + return count; +} + +static const struct file_operations tt_command_fops = { + .write = tt_command_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static int __init thermal_testing_init(void) +{ + int error; + + tt_wq = alloc_workqueue("thermal_testing", WQ_UNBOUND, 0); + if (!tt_wq) + return -ENOMEM; + + d_testing = debugfs_create_dir("thermal-testing", NULL); + if (IS_ERR(d_testing)) { + error = PTR_ERR(d_testing); + goto destroy_wq; + } + + d_command = debugfs_create_file("command", 0200, d_testing, NULL, &tt_command_fops); + if (IS_ERR(d_command)) { + error = PTR_ERR(d_command); + goto remove_d_testing; + } + + return 0; + +remove_d_testing: + debugfs_remove(d_testing); +destroy_wq: + destroy_workqueue(tt_wq); + return error; +} +module_init(thermal_testing_init); + +static void __exit thermal_testing_exit(void) +{ + /* First, prevent new commands from being entered. */ + debugfs_remove(d_command); + /* Flush commands in progress (if any). */ + flush_workqueue(tt_wq); + destroy_workqueue(tt_wq); + /* Remove the directory structure and clean up. */ + debugfs_remove(d_testing); + tt_zone_cleanup(); +} +module_exit(thermal_testing_exit); + +MODULE_DESCRIPTION("Thermal core testing facility"); +MODULE_LICENSE("GPL v2"); |
