mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "jiale yao" <19888972804@163.com>
To: "Rafael J. Wysocki" <rafael@kernel.org>,
	"Len Brown" <lenb@kernel.org>,
	linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re:[PATCH] ACPI: processor: validate CPU mapping entries
Date: Thu, 29 Oct 2026 13:17:35 +0800 (CST)	[thread overview]
Message-ID: <6ef8af71.406c.1a1861797a5.Coremail.19888972804@163.com> (raw)
In-Reply-To: <20261016141440.2794000-1-yaojiale02@163.com>

At 2026-10-16 22:14:40, "Jiale Yao" <yaojiale02@163.com> wrote:
>CPU physical ID lookup walks MADT subtables directly and also accepts
>records returned by _MAT. Both paths can pass a truncated typed record
>to a CPU-specific mapper, and the MADT walker can be stalled or moved
>outside the table by an invalid length.
>
>Validate generic record bounds and the minimum size required by each
>supported CPU record before dispatching it on both provider paths.

Test Report

I built malformed ACPI inputs for both provider paths that this patch
validates and ran them against an unpatched and a patched kernel.  Both
issues reproduce without the patch and disappear with it.  Details and
logs below.

I hope this is useful for the series.

Summary
=======

Two independent inputs, one per provider path:

  1. _MAT path (map_mat_entry): a processor device whose _MAT returns a
     2-byte Buffer.  map_mat_entry() only checks
     obj->buffer.length >= sizeof(struct acpi_subtable_header) and then
     dispatches on the subtable type; map_gicc_mpidr() reads
     gicc->flags at record offset 12, which is 4 bytes past the end of
     the ACPICA result allocation.  KASAN reports
     "slab-out-of-bounds in acpi_get_phys_id".

  2. MADT path (map_madt_entry): a MADT subtable with length 0.  The
     walker does "entry += header->length" without validating the
     length, so it never advances; the boot thread spins in
     map_madt_entry() and userspace is never reached.

With the patch applied neither input produces a report; the guest
boots normally in both cases.



Test environment
================

  kernel     Linux 7.3.0-rc4, ARCH=loongarch, defconfig with
             CONFIG_KASAN=y, CONFIG_ACPI=y, CONFIG_ACPI_PROCESSOR=y,
             CONFIG_ACPI_TABLE_UPGRADE=y, built with clang 18.1.8
             (LLVM=1, ld.lld)
  machine    qemu-system-loongarch64 11.0.92
             -M virt -cpu max -m 2G -smp 2
             -bios edk2-loongarch64-code.fd
  guest      a freestanding init that mounts sysfs, dumps the ACPI
             tables and powers the machine off
  cmdline    console=ttyS0,115200 earlycon=uart8250,mmio,0x1fe001e0
             acpi=force nokaslr

The firmware is required: QEMU's direct -kernel boot for loongarch
passes no RSDP, so ACPI (and therefore processor_core.c) is only
reachable when a firmware installs the tables.  EDK2 does that from
QEMU's fw_cfg blobs.

Both inputs ride on the documented ACPI table override interface:

  * The SSDT is placed in the initrd as kernel/firmware/acpi/SSDT (an
    uncompressed cpio; find_cpio_data() does not decompress).
    acpi_table_initrd_scan() installs it before acpi_load_tables(), so
    it is part of the ACPI namespace.

  * The MADT is placed in the initrd as kernel/firmware/acpi/MADT and
    replaces the firmware's MADT through acpi_table_upgrade()
    (signature/oem_id/oem_table_id match, oem_revision increased).

In both cases the ACPI namespace scan then enumerates the processor
device and calls acpi_processor_get_info() -> acpi_get_phys_id().


Input 1: _MAT returns a truncated record
========================================

SSDT (86 bytes, hand-written AML):

    Scope (\_SB) {
        Device (CPU9) {
            Name (_HID, "ACPI0007")
            Name (_UID, 9)
            Method (_MAT, 0) {
                Return (Buffer (2) { 0x0B, 0x02 })
            }
        }
    }

  - type 0x0B is ACPI_MADT_TYPE_GENERIC_INTERRUPT (GICC), whose first
    typed access in map_gicc_mpidr() is gicc->flags at offset 12;
  - length 2 is the smallest buffer map_mat_entry() accepts;
  - _UID 9 does not collide with the real CPUs (0/1).

Kernel log, without the patch:

    BUG: KASAN: slab-out-of-bounds in acpi_get_phys_id+0x2bc/0x380
    Read of size 4 at addr 900000000e36bfe4 by task swapper/0/1
    ...
     acpi_get_phys_id+0x2b8/0x380
     acpi_processor_add+0x3ec/0x980
     acpi_bus_attach+0x370/0x480
     acpi_bus_scan+0x7c/0x2a0
     acpi_scan_init+0x104/0x25c
     acpi_init+0xf8/0x118
    ...
    Allocated by task 1:
     __kasan_kmalloc
     __kmalloc_noprof
     acpi_ut_initialize_buffer+0x90/0x100
     acpi_evaluate_object+0x524/0x5e0
     acpi_get_phys_id+0x78/0x380
     acpi_processor_add+0x3ec/0x980
    ...
    The buggy address belongs to the cache kmalloc-32 of size 32
    The buggy address is located 4 bytes to the right of
    allocated 32-byte region

The allocation is the ACPI_ALLOCATE_BUFFER result: sizeof(union
acpi_object) rounded up plus one native word of buffer data.  The
2-byte buffer sits at the start of the data area, so the 4-byte read at
data + 12 lands just past the 32-byte object.

One note on observability: a 3-byte LOCAL_APIC _MAT buffer does not
trip KASAN, because map_lapic_id()'s first access is at offset 4 and
that still falls inside the same allocation.  The GICC type is used
only to make the out-of-bounds access visible to KASAN; the defect is
the same for every type.

Kernel log, with the patch applied: no report of any kind, the scan
completes and the guest boots to userspace and powers off.

Input 2: MADT subtable with length 0
====================================

The firmware MADT plus:

    7f 00    type 0x7f, length 0
    7f 02    two more bytes, so the loop condition reaches the record

The SSDT above is used without _MAT, so acpi_get_phys_id() falls back
to map_madt_entry() for _UID 9, which matches no CORE_PIC entry and
therefore walks the whole table.

Kernel log, without the patch (with a temporary print added to the loop
for this test only; the print is rate-limited to three lines):

    POC-WALK: zero-length subtable at 0xffff8000820200c3 (type 127): entry += 0 -> spin

After that line the loop spins in entry += header->length, "Run /init"
is never printed and the machine never reaches userspace.  The same
result was observed without the temporary print (the boot simply stops
right after the ACPI scan).

With the patch applied, the same input boots normally: the walker stops
at the invalid record because the loop condition now calls
madt_cpu_entry_is_valid().

Related observation: with the truncated record placed at the end of a
page-aligned MADT (3-byte LOCAL_APIC), the read does not fault on this
machine.  LoongArch's early ACPI mapping is the identity/direct map and
the table lives in RAM, so the out-of-bounds read is silent here.  The
"length > end - start" check is what keeps the walker inside the table;
that part was verified by code inspection, not by a fault.

      parent reply	other threads:[~2026-10-29  5:17 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-16 14:14 [PATCH] " Jiale Yao
2026-10-17  7:33 ` jiale yao
2026-10-29  5:17 ` jiale yao [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=6ef8af71.406c.1a1861797a5.Coremail.19888972804@163.com \
    --to=19888972804@163.com \
    --cc=lenb@kernel.org \
    --cc=linux-acpi@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=rafael@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®