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/cdx/cdx_msi.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/cdx/cdx_msi.c')
| -rw-r--r-- | drivers/cdx/cdx_msi.c | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/drivers/cdx/cdx_msi.c b/drivers/cdx/cdx_msi.c new file mode 100644 index 000000000..c8d832b0b --- /dev/null +++ b/drivers/cdx/cdx_msi.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD CDX bus driver MSI support + * + * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. + */ + +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/msi.h> +#include <linux/cdx/cdx_bus.h> + +#include "cdx.h" + +static void cdx_msi_write_msg(struct irq_data *irq_data, struct msi_msg *msg) +{ + struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); + struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev); + + /* We would not operate on msg here rather we wait for irq_bus_sync_unlock() + * to be called from preemptible task context. + */ + msi_desc->msg = *msg; + cdx_dev->msi_write_pending = true; +} + +static void cdx_msi_write_irq_lock(struct irq_data *irq_data) +{ + struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); + struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev); + + mutex_lock(&cdx_dev->irqchip_lock); +} + +static void cdx_msi_write_irq_unlock(struct irq_data *irq_data) +{ + struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); + struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev); + struct cdx_controller *cdx = cdx_dev->cdx; + struct cdx_device_config dev_config; + + if (!cdx_dev->msi_write_pending) { + mutex_unlock(&cdx_dev->irqchip_lock); + return; + } + + cdx_dev->msi_write_pending = false; + mutex_unlock(&cdx_dev->irqchip_lock); + + dev_config.msi.msi_index = msi_desc->msi_index; + dev_config.msi.data = msi_desc->msg.data; + dev_config.msi.addr = ((u64)(msi_desc->msg.address_hi) << 32) | msi_desc->msg.address_lo; + + /* + * dev_configure() is a controller callback which can interact with + * Firmware or other entities, and can sleep, so invoke this function + * outside of the mutex held region. + */ + dev_config.type = CDX_DEV_MSI_CONF; + if (cdx->ops->dev_configure) + cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num, &dev_config); +} + +int cdx_enable_msi(struct cdx_device *cdx_dev) +{ + struct cdx_controller *cdx = cdx_dev->cdx; + struct cdx_device_config dev_config; + + dev_config.type = CDX_DEV_MSI_ENABLE; + dev_config.msi_enable = true; + if (cdx->ops->dev_configure) { + return cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num, + &dev_config); + } + + return -EOPNOTSUPP; +} +EXPORT_SYMBOL_GPL(cdx_enable_msi); + +void cdx_disable_msi(struct cdx_device *cdx_dev) +{ + struct cdx_controller *cdx = cdx_dev->cdx; + struct cdx_device_config dev_config; + + dev_config.type = CDX_DEV_MSI_ENABLE; + dev_config.msi_enable = false; + if (cdx->ops->dev_configure) + cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num, &dev_config); +} +EXPORT_SYMBOL_GPL(cdx_disable_msi); + +static struct irq_chip cdx_msi_irq_chip = { + .name = "CDX-MSI", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = msi_domain_set_affinity, + .irq_write_msi_msg = cdx_msi_write_msg, + .irq_bus_lock = cdx_msi_write_irq_lock, + .irq_bus_sync_unlock = cdx_msi_write_irq_unlock +}; + +/* Convert an msi_desc to a unique identifier within the domain. */ +static irq_hw_number_t cdx_domain_calc_hwirq(struct cdx_device *dev, + struct msi_desc *desc) +{ + return ((irq_hw_number_t)dev->msi_dev_id << 10) | desc->msi_index; +} + +static void cdx_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) +{ + arg->desc = desc; + arg->hwirq = cdx_domain_calc_hwirq(to_cdx_device(desc->dev), desc); +} + +static int cdx_msi_prepare(struct irq_domain *msi_domain, + struct device *dev, + int nvec, msi_alloc_info_t *info) +{ + struct of_phandle_args msi_spec = {}; + struct cdx_device *cdx_dev = to_cdx_device(dev); + struct device *parent = cdx_dev->cdx->dev; + struct msi_domain_info *msi_info; + int ret; + + /* Retrieve device ID from requestor ID using parent device */ + ret = of_map_msi_id(parent->of_node, cdx_dev->msi_dev_id, NULL, &msi_spec); + if (ret) { + dev_err(dev, "of_map_msi_id failed for MSI: %d\n", ret); + return ret; + } + of_node_put(msi_spec.np); + +#ifdef GENERIC_MSI_DOMAIN_OPS + /* Set the device Id to be passed to the GIC-ITS */ + info->scratchpad[0].ul = msi_spec.args[0]; +#endif + + msi_info = msi_get_domain_info(msi_domain->parent); + + return msi_info->ops->msi_prepare(msi_domain->parent, dev, nvec, info); +} + +static struct msi_domain_ops cdx_msi_ops = { + .msi_prepare = cdx_msi_prepare, + .set_desc = cdx_msi_set_desc +}; + +static struct msi_domain_info cdx_msi_domain_info = { + .ops = &cdx_msi_ops, + .chip = &cdx_msi_irq_chip, + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS +}; + +struct irq_domain *cdx_msi_domain_init(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct fwnode_handle *fwnode_handle; + struct irq_domain *cdx_msi_domain; + struct device_node *parent_node; + struct irq_domain *parent; + + fwnode_handle = of_fwnode_handle(np); + + parent_node = of_parse_phandle(np, "msi-map", 1); + if (!parent_node) { + dev_err(dev, "msi-map not present on cdx controller\n"); + return NULL; + } + + parent = irq_find_matching_fwnode(of_fwnode_handle(parent_node), DOMAIN_BUS_NEXUS); + of_node_put(parent_node); + if (!parent || !msi_get_domain_info(parent)) { + dev_err(dev, "unable to locate ITS domain\n"); + return NULL; + } + + cdx_msi_domain = msi_create_irq_domain(fwnode_handle, &cdx_msi_domain_info, parent); + if (!cdx_msi_domain) { + dev_err(dev, "unable to create CDX-MSI domain\n"); + return NULL; + } + + dev_dbg(dev, "CDX-MSI domain created\n"); + + return cdx_msi_domain; +} +EXPORT_SYMBOL_NS_GPL(cdx_msi_domain_init, "CDX_BUS_CONTROLLER"); |
