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/acpi/device_sysfs.c | 651 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 651 insertions(+) create mode 100644 drivers/acpi/device_sysfs.c (limited to 'drivers/acpi/device_sysfs.c') diff --git a/drivers/acpi/device_sysfs.c b/drivers/acpi/device_sysfs.c new file mode 100644 index 000000000..c5434b998 --- /dev/null +++ b/drivers/acpi/device_sysfs.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/acpi/device_sysfs.c - ACPI device sysfs attributes and modalias. + * + * Copyright (C) 2015, Intel Corp. + * Author: Mika Westerberg + * Author: Rafael J. Wysocki + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include + +#include "internal.h" + +static ssize_t acpi_object_path(acpi_handle handle, char *buf) +{ + struct acpi_buffer path = {ACPI_ALLOCATE_BUFFER, NULL}; + int result; + + result = acpi_get_name(handle, ACPI_FULL_PATHNAME, &path); + if (result) + return result; + + result = sysfs_emit(buf, "%s\n", (char *)path.pointer); + kfree(path.pointer); + return result; +} + +struct acpi_data_node_attr { + struct attribute attr; + ssize_t (*show)(struct acpi_data_node *, char *); + ssize_t (*store)(struct acpi_data_node *, const char *, size_t count); +}; + +#define DATA_NODE_ATTR(_name) \ + static struct acpi_data_node_attr data_node_##_name = \ + __ATTR(_name, 0444, data_node_show_##_name, NULL) + +static ssize_t data_node_show_path(struct acpi_data_node *dn, char *buf) +{ + return dn->handle ? acpi_object_path(dn->handle, buf) : 0; +} + +DATA_NODE_ATTR(path); + +static struct attribute *acpi_data_node_default_attrs[] = { + &data_node_path.attr, + NULL +}; +ATTRIBUTE_GROUPS(acpi_data_node_default); + +#define to_data_node(k) container_of(k, struct acpi_data_node, kobj) +#define to_attr(a) container_of(a, struct acpi_data_node_attr, attr) + +static ssize_t acpi_data_node_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct acpi_data_node *dn = to_data_node(kobj); + struct acpi_data_node_attr *dn_attr = to_attr(attr); + + return dn_attr->show ? dn_attr->show(dn, buf) : -ENXIO; +} + +static const struct sysfs_ops acpi_data_node_sysfs_ops = { + .show = acpi_data_node_attr_show, +}; + +static void acpi_data_node_release(struct kobject *kobj) +{ + struct acpi_data_node *dn = to_data_node(kobj); + + complete(&dn->kobj_done); +} + +static const struct kobj_type acpi_data_node_ktype = { + .sysfs_ops = &acpi_data_node_sysfs_ops, + .default_groups = acpi_data_node_default_groups, + .release = acpi_data_node_release, +}; + +static void acpi_expose_nondev_subnodes(struct kobject *kobj, + struct acpi_device_data *data) +{ + struct list_head *list = &data->subnodes; + struct acpi_data_node *dn; + + if (list_empty(list)) + return; + + list_for_each_entry(dn, list, sibling) { + int ret; + + init_completion(&dn->kobj_done); + ret = kobject_init_and_add(&dn->kobj, &acpi_data_node_ktype, + kobj, "%s", dn->name); + if (!ret) + acpi_expose_nondev_subnodes(&dn->kobj, &dn->data); + else if (dn->handle) + acpi_handle_err(dn->handle, "Failed to expose (%d)\n", ret); + } +} + +static void acpi_hide_nondev_subnodes(struct acpi_device_data *data) +{ + struct list_head *list = &data->subnodes; + struct acpi_data_node *dn; + + if (list_empty(list)) + return; + + list_for_each_entry_reverse(dn, list, sibling) { + acpi_hide_nondev_subnodes(&dn->data); + kobject_put(&dn->kobj); + } +} + +/** + * create_pnp_modalias - Create hid/cid(s) string for modalias and uevent + * @acpi_dev: ACPI device object. + * @modalias: Buffer to print into. + * @size: Size of the buffer. + * + * Creates hid/cid(s) string needed for modalias and uevent + * e.g. on a device with hid:IBM0001 and cid:ACPI0001 you get: + * char *modalias: "acpi:IBM0001:ACPI0001" + * Return: 0: no _HID and no _CID + * -EINVAL: output error + * -ENOMEM: output is truncated + */ +static int create_pnp_modalias(const struct acpi_device *acpi_dev, char *modalias, + int size) +{ + int len; + int count; + struct acpi_hardware_id *id; + + /* Avoid unnecessarily loading modules for non present devices. */ + if (!acpi_device_is_present(acpi_dev)) + return 0; + + /* + * Since we skip ACPI_DT_NAMESPACE_HID from the modalias below, 0 should + * be returned if ACPI_DT_NAMESPACE_HID is the only ACPI/PNP ID in the + * device's list. + */ + count = 0; + list_for_each_entry(id, &acpi_dev->pnp.ids, list) + if (strcmp(id->id, ACPI_DT_NAMESPACE_HID)) + count++; + + if (!count) + return 0; + + len = snprintf(modalias, size, "acpi:"); + if (len >= size) + return -ENOMEM; + + size -= len; + + list_for_each_entry(id, &acpi_dev->pnp.ids, list) { + if (!strcmp(id->id, ACPI_DT_NAMESPACE_HID)) + continue; + + count = snprintf(&modalias[len], size, "%s:", id->id); + + if (count >= size) + return -ENOMEM; + + len += count; + size -= count; + } + + return len; +} + +/** + * create_of_modalias - Creates DT compatible string for modalias and uevent + * @acpi_dev: ACPI device object. + * @modalias: Buffer to print into. + * @size: Size of the buffer. + * + * Expose DT compatible modalias as of:NnameTCcompatible. This function should + * only be called for devices having ACPI_DT_NAMESPACE_HID in their list of + * ACPI/PNP IDs. + */ +static int create_of_modalias(const struct acpi_device *acpi_dev, char *modalias, + int size) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + const union acpi_object *of_compatible, *obj; + acpi_status status; + int len, count; + int i, nval; + char *c; + + status = acpi_get_name(acpi_dev->handle, ACPI_SINGLE_NAME, &buf); + if (ACPI_FAILURE(status)) + return -ENODEV; + + /* DT strings are all in lower case */ + for (c = buf.pointer; *c != '\0'; c++) + *c = tolower(*c); + + len = snprintf(modalias, size, "of:N%sT", (char *)buf.pointer); + ACPI_FREE(buf.pointer); + + if (len >= size) + return -ENOMEM; + + size -= len; + + of_compatible = acpi_dev->data.of_compatible; + if (of_compatible->type == ACPI_TYPE_PACKAGE) { + nval = of_compatible->package.count; + obj = of_compatible->package.elements; + } else { /* Must be ACPI_TYPE_STRING. */ + nval = 1; + obj = of_compatible; + } + for (i = 0; i < nval; i++, obj++) { + count = snprintf(&modalias[len], size, "C%s", + obj->string.pointer); + + if (count >= size) + return -ENOMEM; + + len += count; + size -= count; + } + + return len; +} + +int __acpi_device_uevent_modalias(const struct acpi_device *adev, + struct kobj_uevent_env *env) +{ + int len; + + if (!adev) + return -ENODEV; + + if (list_empty(&adev->pnp.ids)) + return 0; + + if (add_uevent_var(env, "MODALIAS=")) + return -ENOMEM; + + if (adev->data.of_compatible) + len = create_of_modalias(adev, &env->buf[env->buflen - 1], + sizeof(env->buf) - env->buflen); + else + len = create_pnp_modalias(adev, &env->buf[env->buflen - 1], + sizeof(env->buf) - env->buflen); + if (len < 0) + return len; + + env->buflen += len; + + return 0; +} + +/** + * acpi_device_uevent_modalias - uevent modalias for ACPI-enumerated devices. + * @dev: Struct device to get ACPI device node. + * @env: Environment variables of the kobject uevent. + * + * Create the uevent modalias field for ACPI-enumerated devices. + * + * Because other buses do not support ACPI HIDs & CIDs, e.g. for a device with + * hid:IBM0001 and cid:ACPI0001 you get: "acpi:IBM0001:ACPI0001". + */ +int acpi_device_uevent_modalias(const struct device *dev, struct kobj_uevent_env *env) +{ + return __acpi_device_uevent_modalias(acpi_companion_match(dev), env); +} +EXPORT_SYMBOL_GPL(acpi_device_uevent_modalias); + +static int __acpi_device_modalias(const struct acpi_device *adev, char *buf, int size) +{ + int len, count; + + if (!adev) + return -ENODEV; + + if (list_empty(&adev->pnp.ids)) + return 0; + + len = create_pnp_modalias(adev, buf, size - 1); + if (len < 0) { + return len; + } else if (len > 0) { + buf[len++] = '\n'; + size -= len; + } + if (!adev->data.of_compatible) + return len; + + count = create_of_modalias(adev, buf + len, size - 1); + if (count < 0) { + return count; + } else if (count > 0) { + len += count; + buf[len++] = '\n'; + } + + return len; +} + +/** + * acpi_device_modalias - modalias sysfs attribute for ACPI-enumerated devices. + * @dev: Struct device to get ACPI device node. + * @buf: The buffer to save pnp_modalias and of_modalias. + * @size: Size of buffer. + * + * Create the modalias sysfs attribute for ACPI-enumerated devices. + * + * Because other buses do not support ACPI HIDs & CIDs, e.g. for a device with + * hid:IBM0001 and cid:ACPI0001 you get: "acpi:IBM0001:ACPI0001". + */ +int acpi_device_modalias(struct device *dev, char *buf, int size) +{ + return __acpi_device_modalias(acpi_companion_match(dev), buf, size); +} +EXPORT_SYMBOL_GPL(acpi_device_modalias); + +static ssize_t +modalias_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return __acpi_device_modalias(to_acpi_device(dev), buf, 1024); +} +static DEVICE_ATTR_RO(modalias); + +static ssize_t real_power_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_device *adev = to_acpi_device(dev); + int state; + int ret; + + ret = acpi_device_get_power(adev, &state); + if (ret) + return ret; + + return sysfs_emit(buf, "%s\n", acpi_power_state_string(state)); +} + +static DEVICE_ATTR_RO(real_power_state); + +static ssize_t power_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_device *adev = to_acpi_device(dev); + + return sysfs_emit(buf, "%s\n", acpi_power_state_string(adev->power.state)); +} + +static DEVICE_ATTR_RO(power_state); + +static ssize_t +eject_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi_device = to_acpi_device(d); + acpi_object_type not_used; + acpi_status status; + + if (!count || buf[0] != '1') + return -EINVAL; + + if ((!acpi_device->handler || !acpi_device->handler->hotplug.enabled) + && !d->driver) + return -ENODEV; + + status = acpi_get_type(acpi_device->handle, ¬_used); + if (ACPI_FAILURE(status) || !acpi_device->flags.ejectable) + return -ENODEV; + + acpi_dev_get(acpi_device); + status = acpi_hotplug_schedule(acpi_device, ACPI_OST_EC_OSPM_EJECT); + if (ACPI_SUCCESS(status)) + return count; + + acpi_dev_put(acpi_device); + acpi_evaluate_ost(acpi_device->handle, ACPI_OST_EC_OSPM_EJECT, + ACPI_OST_SC_NON_SPECIFIC_FAILURE, NULL); + return status == AE_NO_MEMORY ? -ENOMEM : -EAGAIN; +} + +static DEVICE_ATTR_WO(eject); + +static ssize_t +hid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + + return sysfs_emit(buf, "%s\n", acpi_device_hid(acpi_dev)); +} +static DEVICE_ATTR_RO(hid); + +static ssize_t cid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_device_info *info = NULL; + ssize_t len = 0; + + acpi_get_object_info(acpi_dev->handle, &info); + if (!info) + return 0; + + if (info->valid & ACPI_VALID_CID) { + struct acpi_pnp_device_id_list *cid_list = &info->compatible_id_list; + int i; + + for (i = 0; i < cid_list->count - 1; i++) + len += sysfs_emit_at(buf, len, "%s,", cid_list->ids[i].string); + + len += sysfs_emit_at(buf, len, "%s\n", cid_list->ids[i].string); + } + + kfree(info); + + return len; +} +static DEVICE_ATTR_RO(cid); + +static ssize_t uid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + + return sysfs_emit(buf, "%s\n", acpi_device_uid(acpi_dev)); +} +static DEVICE_ATTR_RO(uid); + +static ssize_t adr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + + if (acpi_dev->pnp.bus_address > U32_MAX) + return sysfs_emit(buf, "0x%016llx\n", acpi_dev->pnp.bus_address); + else + return sysfs_emit(buf, "0x%08llx\n", acpi_dev->pnp.bus_address); +} +static DEVICE_ATTR_RO(adr); + +static ssize_t path_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + + return acpi_object_path(acpi_dev->handle, buf); +} +static DEVICE_ATTR_RO(path); + +/* sysfs file that shows description text from the ACPI _STR method */ +static ssize_t description_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *str_obj; + acpi_status status; + int result; + + status = acpi_evaluate_object_typed(acpi_dev->handle, "_STR", + NULL, &buffer, + ACPI_TYPE_BUFFER); + if (ACPI_FAILURE(status)) + return -EIO; + + str_obj = buffer.pointer; + + /* + * The _STR object contains a Unicode identifier for a device. + * We need to convert to utf-8 so it can be displayed. + */ + result = utf16s_to_utf8s( + (wchar_t *)str_obj->buffer.pointer, + str_obj->buffer.length, + UTF16_LITTLE_ENDIAN, buf, + PAGE_SIZE - 1); + + buf[result++] = '\n'; + + ACPI_FREE(str_obj); + + return result; +} +static DEVICE_ATTR_RO(description); + +static ssize_t +sun_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + acpi_status status; + unsigned long long sun; + + status = acpi_evaluate_integer(acpi_dev->handle, "_SUN", NULL, &sun); + if (ACPI_FAILURE(status)) + return -EIO; + + return sysfs_emit(buf, "%llu\n", sun); +} +static DEVICE_ATTR_RO(sun); + +static ssize_t +hrv_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + acpi_status status; + unsigned long long hrv; + + status = acpi_evaluate_integer(acpi_dev->handle, "_HRV", NULL, &hrv); + if (ACPI_FAILURE(status)) + return -EIO; + + return sysfs_emit(buf, "%llu\n", hrv); +} +static DEVICE_ATTR_RO(hrv); + +static ssize_t status_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + acpi_status status; + unsigned long long sta; + + status = acpi_evaluate_integer(acpi_dev->handle, "_STA", NULL, &sta); + if (ACPI_FAILURE(status)) + return -EIO; + + return sysfs_emit(buf, "%llu\n", sta); +} +static DEVICE_ATTR_RO(status); + +static struct attribute *acpi_attrs[] = { + &dev_attr_path.attr, + &dev_attr_hid.attr, + &dev_attr_cid.attr, + &dev_attr_modalias.attr, + &dev_attr_description.attr, + &dev_attr_adr.attr, + &dev_attr_uid.attr, + &dev_attr_sun.attr, + &dev_attr_hrv.attr, + &dev_attr_status.attr, + &dev_attr_eject.attr, + &dev_attr_power_state.attr, + &dev_attr_real_power_state.attr, + NULL +}; + +static bool acpi_show_attr(struct acpi_device *dev, const struct device_attribute *attr) +{ + /* + * Devices gotten from FADT don't have a "path" attribute + */ + if (attr == &dev_attr_path) + return dev->handle; + + if (attr == &dev_attr_hid || attr == &dev_attr_modalias) + return !list_empty(&dev->pnp.ids); + + if (attr == &dev_attr_description) + return acpi_has_method(dev->handle, "_STR"); + + if (attr == &dev_attr_adr) + return dev->pnp.type.bus_address; + + if (attr == &dev_attr_uid) + return acpi_device_uid(dev); + + if (attr == &dev_attr_sun) + return acpi_has_method(dev->handle, "_SUN"); + + if (attr == &dev_attr_hrv) + return acpi_has_method(dev->handle, "_HRV"); + + if (attr == &dev_attr_status) + return acpi_has_method(dev->handle, "_STA"); + + if (attr == &dev_attr_cid) + return acpi_has_method(dev->handle, "_CID"); + + /* + * If device has _EJ0, 'eject' file is created that is used to trigger + * hot-removal function from userland. + */ + if (attr == &dev_attr_eject) + return acpi_has_method(dev->handle, "_EJ0"); + + if (attr == &dev_attr_power_state) + return dev->flags.power_manageable; + + if (attr == &dev_attr_real_power_state) + return dev->flags.power_manageable && dev->power.flags.power_resources; + + dev_warn_once(&dev->dev, "Unexpected attribute: %s\n", attr->attr.name); + return false; +} + +static umode_t acpi_attr_is_visible(struct kobject *kobj, + struct attribute *attr, + int attrno) +{ + struct acpi_device *dev = to_acpi_device(kobj_to_dev(kobj)); + + if (acpi_show_attr(dev, container_of(attr, struct device_attribute, attr))) + return attr->mode; + else + return 0; +} + +static const struct attribute_group acpi_group = { + .attrs = acpi_attrs, + .is_visible = acpi_attr_is_visible, +}; + +const struct attribute_group *acpi_groups[] = { + &acpi_group, + NULL +}; + +/** + * acpi_device_setup_files - Create sysfs attributes of an ACPI device. + * @dev: ACPI device object. + */ +void acpi_device_setup_files(struct acpi_device *dev) +{ + acpi_expose_nondev_subnodes(&dev->dev.kobj, &dev->data); +} + +/** + * acpi_device_remove_files - Remove sysfs attributes of an ACPI device. + * @dev: ACPI device object. + */ +void acpi_device_remove_files(struct acpi_device *dev) +{ + acpi_hide_nondev_subnodes(&dev->data); +} -- cgit v1.3.1