[PATCH] ACPI: osl: use vsnprintf() in acpi_os_vprintf()
Vlatko Kosturjak <[email protected]>
| Newsgroups | org.kernel.vger.linux-acpi |
|---|---|
| Message-ID | <CAPAw8HEMdsNN6WhizHkv2Orp2pkQ2-g009XCGFnKnhXoRhcKZQ@mail.gmail.com> |
acpi_os_vprintf() formats every ACPICA diagnostic into a 512-byte static
buffer with an unbounded vsprintf():
static char buffer[512];
vsprintf(buffer, fmt, args);
ACPICA interpolates table-derived namespace paths into its messages, so the
length is driven by table content. A malformed ACPI table can therefore
produce a diagnostic longer than the buffer and overwrite whatever follows
it.
From the System.map of an x86_64_defconfig build, acpi_ioremap_lock sits 624
bytes into that buffer - 112 bytes past its end - so bytes 624..631 of an
over-long message land on the mutex's owner field, which __mutex_lock() then
dereferences.
The overwritten bytes are attacker-influenced but not attacker-controlled:
they are printable ASCII from a NUL-terminated string, so the resulting
pointer is always non-canonical and always faults. This is a reliable denial
of service, not a demonstrated pointer-control primitive.
Because acpi_tb_load_namespace() parses the DSDT and every SSDT during
acpi_init(), a table supplied by firmware triggers this in PID 1 inside
do_one_initcall(), panicking the kernel before userspace exists.
Bound the write with vsnprintf(). Truncating a diagnostic is strictly better
than corrupting adjacent kernel objects.
Tested on pristine Linux 7.2.2 - tag: v7.2.2., Stock x86_64_defconfig
(no KASAN, no debug options), table supplied by firmware via
"qemu -acpitable":
[ 0.022259] ACPI: SSDT 0x000000003FFE1C52 000640 (v01 _ASUS_
Notebook 00000005 MSFT 0100000D)
[ 0.353684] Oops: general protection fault, probably for
non-canonical address
0x41435f2e345f2e64: 0000 [#1] SMP NOPTI
[ 0.353727] RIP: 0010:__mutex_lock.constprop.0+0xbd/0xa40
[ 0.353727] RAX: 41435f2e345f2e30 RBX: 00000000fed00000
[ 0.353727] R15: ffffffff8c248530
[ 0.353727] Call Trace:
[ 0.353727] acpi_os_map_iomem
[ 0.353727] acpi_ex_system_memory_space_handler
[ 0.353727] acpi_ev_address_space_dispatch
[ 0.353727] acpi_ex_access_region
[ 0.353727] acpi_ex_read_data_from_field
[ 0.353727] acpi_ex_resolve_node_to_value
[ 0.353727] acpi_ds_evaluate_name_path
[ 0.353727] acpi_ps_parse_loop
[ 0.353727] acpi_ps_execute_method
[ 0.354729] Kernel panic - not syncing: Attempted to kill init!
exitcode=0x00000009
RAX is the corrupted owner; the faulting address is RAX + 0x34.
R15 matches acpi_ioremap_lock. On a KASAN build the overflow is
reported directly as:
BUG: KASAN: global-out-of-bounds in string.
Signed-off-by: Vlatko Kosturjak <[email protected]>
---
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c
index ed2162a97..5b7aefb48 100644
--- a/drivers/acpi/osl.c
+++ b/drivers/acpi/osl.c
@@ -159,7 +159,7 @@ void __printf(1, 0) acpi_os_vprintf(const char
*fmt, va_list args)
{
static char buffer[512];
- vsprintf(buffer, fmt, args);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
#ifdef ENABLE_DEBUGGER
if (acpi_in_debugger) {