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/firmware/meson/Kconfig | 11 ++ drivers/firmware/meson/Makefile | 2 + drivers/firmware/meson/meson_sm.c | 367 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 drivers/firmware/meson/Kconfig create mode 100644 drivers/firmware/meson/Makefile create mode 100644 drivers/firmware/meson/meson_sm.c (limited to 'drivers/firmware/meson') diff --git a/drivers/firmware/meson/Kconfig b/drivers/firmware/meson/Kconfig new file mode 100644 index 000000000..179f5d46d --- /dev/null +++ b/drivers/firmware/meson/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Amlogic Secure Monitor driver +# +config MESON_SM + tristate "Amlogic Secure Monitor driver" + depends on ARCH_MESON || COMPILE_TEST + default ARCH_MESON + depends on ARM64_4K_PAGES + help + Say y here to enable the Amlogic secure monitor driver diff --git a/drivers/firmware/meson/Makefile b/drivers/firmware/meson/Makefile new file mode 100644 index 000000000..c6c09483b --- /dev/null +++ b/drivers/firmware/meson/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_MESON_SM) += meson_sm.o diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c new file mode 100644 index 000000000..ab9751a59 --- /dev/null +++ b/drivers/firmware/meson/meson_sm.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Amlogic Secure Monitor driver + * + * Copyright (C) 2016 Endless Mobile, Inc. + * Author: Carlo Caione + */ + +#define pr_fmt(fmt) "meson-sm: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include + +#include + +struct meson_sm_cmd { + unsigned int index; + u32 smc_id; +}; +#define CMD(d, s) { .index = (d), .smc_id = (s), } + +struct meson_sm_chip { + unsigned int shmem_size; + u32 cmd_shmem_in_base; + u32 cmd_shmem_out_base; + struct meson_sm_cmd cmd[]; +}; + +static const struct meson_sm_chip gxbb_chip = { + .shmem_size = SZ_4K, + .cmd_shmem_in_base = 0x82000020, + .cmd_shmem_out_base = 0x82000021, + .cmd = { + CMD(SM_EFUSE_READ, 0x82000030), + CMD(SM_EFUSE_WRITE, 0x82000031), + CMD(SM_EFUSE_USER_MAX, 0x82000033), + CMD(SM_GET_CHIP_ID, 0x82000044), + CMD(SM_THERMAL_CALIB_READ, 0x82000047), + CMD(SM_A1_PWRC_SET, 0x82000093), + CMD(SM_A1_PWRC_GET, 0x82000095), + { /* sentinel */ }, + }, +}; + +struct meson_sm_firmware { + const struct meson_sm_chip *chip; + void __iomem *sm_shmem_in_base; + void __iomem *sm_shmem_out_base; +}; + +static u32 meson_sm_get_cmd(const struct meson_sm_chip *chip, + unsigned int cmd_index) +{ + const struct meson_sm_cmd *cmd = chip->cmd; + + while (cmd->smc_id && cmd->index != cmd_index) + cmd++; + + return cmd->smc_id; +} + +static s32 __meson_sm_call(u32 cmd, u32 arg0, u32 arg1, u32 arg2, + u32 arg3, u32 arg4) +{ + struct arm_smccc_res res; + + arm_smccc_smc(cmd, arg0, arg1, arg2, arg3, arg4, 0, 0, &res); + return res.a0; +} + +static void __iomem *meson_sm_map_shmem(u32 cmd_shmem, unsigned int size) +{ + u32 sm_phy_base; + + sm_phy_base = __meson_sm_call(cmd_shmem, 0, 0, 0, 0, 0); + if (!sm_phy_base) + return NULL; + + return ioremap_cache(sm_phy_base, size); +} + +/** + * meson_sm_call - generic SMC32 call to the secure-monitor + * + * @fw: Pointer to secure-monitor firmware + * @cmd_index: Index of the SMC32 function ID + * @ret: Returned value + * @arg0: SMC32 Argument 0 + * @arg1: SMC32 Argument 1 + * @arg2: SMC32 Argument 2 + * @arg3: SMC32 Argument 3 + * @arg4: SMC32 Argument 4 + * + * Return: 0 on success, a negative value on error + */ +int meson_sm_call(struct meson_sm_firmware *fw, unsigned int cmd_index, + s32 *ret, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4) +{ + u32 cmd; + s32 lret; + + if (!fw->chip) + return -ENOENT; + + cmd = meson_sm_get_cmd(fw->chip, cmd_index); + if (!cmd) + return -EINVAL; + + lret = __meson_sm_call(cmd, arg0, arg1, arg2, arg3, arg4); + + if (ret) + *ret = lret; + + return 0; +} +EXPORT_SYMBOL(meson_sm_call); + +/** + * meson_sm_call_read - retrieve data from secure-monitor + * + * @fw: Pointer to secure-monitor firmware + * @buffer: Buffer to store the retrieved data + * @bsize: Size of the buffer + * @cmd_index: Index of the SMC32 function ID + * @arg0: SMC32 Argument 0 + * @arg1: SMC32 Argument 1 + * @arg2: SMC32 Argument 2 + * @arg3: SMC32 Argument 3 + * @arg4: SMC32 Argument 4 + * + * Return: size of read data on success, a negative value on error + * When 0 is returned there is no guarantee about the amount of + * data read and bsize bytes are copied in buffer. + */ +int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, + unsigned int bsize, unsigned int cmd_index, u32 arg0, + u32 arg1, u32 arg2, u32 arg3, u32 arg4) +{ + s32 size; + int ret; + + if (!fw->chip) + return -ENOENT; + + if (!fw->chip->cmd_shmem_out_base) + return -EINVAL; + + if (bsize > fw->chip->shmem_size) + return -EINVAL; + + if (meson_sm_call(fw, cmd_index, &size, arg0, arg1, arg2, arg3, arg4) < 0) + return -EINVAL; + + if (size < 0 || size > bsize) + return -EINVAL; + + ret = size; + + /* In some cases (for example GET_CHIP_ID command), + * SMC doesn't return the number of bytes read, even + * though the bytes were actually read into sm_shmem_out. + * So this check is needed. + */ + if (!size) + size = bsize; + + if (buffer) + memcpy(buffer, fw->sm_shmem_out_base, size); + + return ret; +} +EXPORT_SYMBOL(meson_sm_call_read); + +/** + * meson_sm_call_write - send data to secure-monitor + * + * @fw: Pointer to secure-monitor firmware + * @buffer: Buffer containing data to send + * @size: Size of the data to send + * @cmd_index: Index of the SMC32 function ID + * @arg0: SMC32 Argument 0 + * @arg1: SMC32 Argument 1 + * @arg2: SMC32 Argument 2 + * @arg3: SMC32 Argument 3 + * @arg4: SMC32 Argument 4 + * + * Return: size of sent data on success, a negative value on error + */ +int meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer, + unsigned int size, unsigned int cmd_index, u32 arg0, + u32 arg1, u32 arg2, u32 arg3, u32 arg4) +{ + s32 written; + + if (!fw->chip) + return -ENOENT; + + if (size > fw->chip->shmem_size) + return -EINVAL; + + if (!fw->chip->cmd_shmem_in_base) + return -EINVAL; + + memcpy(fw->sm_shmem_in_base, buffer, size); + + if (meson_sm_call(fw, cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0) + return -EINVAL; + + if (written <= 0 || written > size) + return -EINVAL; + + return written; +} +EXPORT_SYMBOL(meson_sm_call_write); + +/** + * meson_sm_get - get pointer to meson_sm_firmware structure. + * + * @sm_node: Pointer to the secure-monitor Device Tree node. + * + * Return: NULL is the secure-monitor device is not ready. + */ +struct meson_sm_firmware *meson_sm_get(struct device_node *sm_node) +{ + struct platform_device *pdev = of_find_device_by_node(sm_node); + struct meson_sm_firmware *fw; + + if (!pdev) + return NULL; + + fw = platform_get_drvdata(pdev); + + put_device(&pdev->dev); + + return fw; +} +EXPORT_SYMBOL_GPL(meson_sm_get); + +/** + * meson_sm_get_thermal_calib - Read thermal sensor calibration data. + * @fw: Pointer to secure-monitor firmware. + * @trim_info: Pointer to store the returned calibration data. + * @tsensor_id: Sensor index to identify which sensor's calibration data + * to retrieve + * + * Return: 0 on success, negative error code on failure. + */ +int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info, + u32 tsensor_id) +{ + return meson_sm_call(fw, SM_THERMAL_CALIB_READ, trim_info, tsensor_id, + 0, 0, 0, 0); +} +EXPORT_SYMBOL_GPL(meson_sm_get_thermal_calib); + +#define SM_CHIP_ID_LENGTH 119 +#define SM_CHIP_ID_OFFSET 4 +#define SM_CHIP_ID_SIZE 12 + +static ssize_t serial_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct meson_sm_firmware *fw; + uint8_t *id_buf; + int ret; + + fw = platform_get_drvdata(pdev); + + id_buf = kmalloc(SM_CHIP_ID_LENGTH, GFP_KERNEL); + if (!id_buf) + return -ENOMEM; + + ret = meson_sm_call_read(fw, id_buf, SM_CHIP_ID_LENGTH, SM_GET_CHIP_ID, + 0, 0, 0, 0, 0); + if (ret < 0) { + kfree(id_buf); + return ret; + } + + ret = sprintf(buf, "%12phN\n", &id_buf[SM_CHIP_ID_OFFSET]); + + kfree(id_buf); + + return ret; +} + +static DEVICE_ATTR_RO(serial); + +static struct attribute *meson_sm_sysfs_attrs[] = { + &dev_attr_serial.attr, + NULL, +}; +ATTRIBUTE_GROUPS(meson_sm_sysfs); + +static const struct of_device_id meson_sm_ids[] = { + { .compatible = "amlogic,meson-gxbb-sm", .data = &gxbb_chip }, + { /* sentinel */ }, +}; + +static int __init meson_sm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct meson_sm_chip *chip; + struct meson_sm_firmware *fw; + + fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL); + if (!fw) + return -ENOMEM; + + chip = device_get_match_data(dev); + if (!chip) + return -EINVAL; + + if (chip->cmd_shmem_in_base) { + fw->sm_shmem_in_base = meson_sm_map_shmem(chip->cmd_shmem_in_base, + chip->shmem_size); + if (WARN_ON(!fw->sm_shmem_in_base)) + goto out; + } + + if (chip->cmd_shmem_out_base) { + fw->sm_shmem_out_base = meson_sm_map_shmem(chip->cmd_shmem_out_base, + chip->shmem_size); + if (WARN_ON(!fw->sm_shmem_out_base)) + goto unmap_in_base; + } + + fw->chip = chip; + + platform_set_drvdata(pdev, fw); + + if (devm_of_platform_populate(dev)) + goto unmap_out_base; + + pr_info("secure-monitor enabled\n"); + + return 0; + +unmap_out_base: + iounmap(fw->sm_shmem_out_base); +unmap_in_base: + iounmap(fw->sm_shmem_in_base); +out: + return -EINVAL; +} + +static struct platform_driver meson_sm_driver = { + .driver = { + .name = "meson-sm", + .of_match_table = of_match_ptr(meson_sm_ids), + .dev_groups = meson_sm_sysfs_groups, + }, +}; +module_platform_driver_probe(meson_sm_driver, meson_sm_probe); +MODULE_DESCRIPTION("Amlogic Secure Monitor driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.3.1