[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) {
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.