ACPI: sysfs: Map only the requested range in acpi_data_show()

acpi_data_show() maps the whole data region on every read(), although
it copies at most count bytes and sysfs passes at most PAGE_SIZE bytes
per call. acpi_os_unmap_iomem() defers the iounmap until after an RCU
grace period, so a process reading one of these files in a loop keeps
many full-size mappings in flight.

Map only the range being read. acpi_os_map_iomem() rounds the mapping
to page boundaries, so each mapping is now at most two pages, whatever
the size of the region.

A malformed table can describe a region that wraps around the end of
the physical address space. acpi_os_map_iomem() doesn't check for that,
and its lookup of existing mappings then matches an unrelated mapping,
with or without this change. Fail reads of such a region before mapping
anything.

Signed-off-by: Jamie Nguyen <jamien@nvidia.com>
Link: https://patch.msgid.link/20261013180728.957618-1-jamien@nvidia.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
Jamie Nguyen 2026-10-13 11:07:28 -07:00 committed by Rafael J. Wysocki
parent 68a385dbe0
commit 4d23d84ef2

View File

@ -420,6 +420,7 @@ static ssize_t acpi_data_show(struct file *filp, struct kobject *kobj,
struct acpi_data_attr *data_attr;
void __iomem *base;
ssize_t size;
u64 end;
data_attr = container_of(bin_attr, struct acpi_data_attr, attr);
size = data_attr->attr.size;
@ -433,13 +434,17 @@ static ssize_t acpi_data_show(struct file *filp, struct kobject *kobj,
if (count > size - offset)
count = size - offset;
base = acpi_os_map_iomem(data_attr->addr, size);
/* acpi_os_map_iomem() can't handle a range that wraps around */
if (check_add_overflow(data_attr->addr, size, &end))
return -EINVAL;
base = acpi_os_map_iomem(data_attr->addr + offset, count);
if (!base)
return -ENOMEM;
memcpy_fromio(buf, base + offset, count);
memcpy_fromio(buf, base, count);
acpi_os_unmap_iomem(base, size);
acpi_os_unmap_iomem(base, count);
return count;
}