Skip to content

events-arch-arm: check the payload length in the ampere, hisilicon and jaguarmicro decoders - #265

Open
agenticode wants to merge 3 commits into
mchehab:masterfrom
agenticode:arm-decoder-payload-length
Open

agenticode wants to merge 3 commits into
mchehab:masterfrom
agenticode:arm-decoder-payload-length

Conversation

@agenticode

Copy link
Copy Markdown

Three ARM vendor decoders cast event->error to a fixed-size struct without looking at event->length, and two of them walk a register array whose length also comes from the firmware payload. A short CPER record makes them read past the end of the trace ring buffer page.

154bdc1 ("rasdaemon: fix several security issues") fixed this class in non-standard-yitian.c and made ras_arm_event_handler() reject oem_len > len, so the length handed to the decoders is correct now - these three just never look at it. non-standard-ampereone.c has had the check since it was added.

The worst one is decode_amp_arm_vendor_data(): it is registered with .midr = -1, so it is the fallback for every ARM CPU that has no exact match, not only Ampere parts. An Ampere Altra reports MIDR 0x413fd0c1 and has no exact handler, so that is the path it takes.

Reproducer

x86_64, Fedora 44, kernel 6.19.10, ASan build, events generated from a small out-of-tree module firing the real tracepoints.

ras:arm_event, vendor data shorter than struct amp_payload0_type_sec - dies after 149 of 4000 events, 3 runs out of 3:

==6878==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7d1fe90ae104
READ of size 8 at 0x7d1fe90ae104 thread T0
    #0 decode_amp_payload0_err_regs ../events-arch-arm/non-standard-ampere.c:657
    #1 decode_amp_arm_vendor_data   ../events-arch-arm/non-standard-ampere.c:1016
    #2 ras_arm_vendor_data_decode   ../events-arch-arm/ras-arm-vendor-data.c:75
    #3 ras_arm_event_handler        ../events-arch-arm/ras-arm-handler.c:579
    #5 parse_ras_data               ../core/ras-events.c:560
    #6 read_ras_event_all_cpus      ../core/ras-events.c:797

0x7d1fe90ae104 is located 4 bytes after 4096-byte region [0x7d1fe90ad100,0x7d1fe90ae100)
allocated by thread T0 here:
    #1 read_ras_event_all_cpus ../core/ras-events.c:665

The region is the ring buffer page. Without ASan the decode usually stays inside the page, so instead of crashing it decodes the next event's bytes and inserts them into the database.

Same on aarch64 (Fedora 44, gcc 16.2, ASan), this time deterministic: the registered decoders are called directly through the HAVE_UNITTEST hooks with a 1-byte payload, one fork per decoder, so every one of the 14 gets a verdict.

arm_event vendor data, midr 0x413fd0c1 (Altra), 0x480fd010 (Kunpeng 920), 0x611f0000:
  all three -> heap-buffer-overflow, READ of size 1,
  decode_amp_payload0_err_regs ../events-arch-arm/non-standard-ampere.c:618

non_standard_event, 1-byte payload:
  e8ed898d (ampere)         -> heap-buffer-overflow, decode_amp_payload0_err_regs
  c8b328a8 (hisilicon)      -> heap-buffer-overflow, READ of size 1,
                               decode_hisi_common_section_hdr ...hisilicon.c:265
  5 jaguarmicro GUIDs       -> heap-buffer-overflow, READ of size 4,
                               decode_jm_common_sec_head ...jaguarmicro.c:478
  ampereone, nvidia x2, yitian -> already rejected, "truncated payload"

With the patches applied every one of those returns -1 with a "truncated payload" line instead, and the x86_64 soak keeps logging.

Testing

aarch64, Fedora 44:

  • gcc 16.2.1 and clang 22.1.8: no build warnings
  • meson test: 9/9 with both compilers
  • python3 tests/run.py: 140 tests, rc=0
  • scripts/checkpatch.pl --no-tree --strict: 0 errors, 0 warnings, 0 checks on all three patches

x86_64, Fedora 44, 8 vCPU: same, meson test 9/9 with gcc and clang, tests/run.py 140 tests.

non-standard-hisi_hip08.c has the same bug - the aarch64 sweep kills it in decode_hip08_oem_type1_error (:684), decode_hip08_oem_type2_error (:850) and decode_hip08_pcie_local_error (:995). It is the same fix, but it is not in this series; I can send it as a follow-up.

decode_amp_oem_type_error() picks a payload type from event->error[0]
and casts the buffer to the matching struct without looking at
event->length. decode_amp_arm_vendor_data() is worse: it ignores its
length argument entirely and casts the buffer to a 48-byte
amp_payload0_type_sec.

That one is registered with .midr = -1, so it is the fallback decoder
for every ARM CPU with no exact match, not only Ampere parts.

Feeding ras:arm_event records whose vendor data is shorter than the
struct kills the daemon:

  AddressSanitizer: heap-buffer-overflow
  READ of size 8
    #0 decode_amp_payload0_err_regs non-standard-ampere.c:657
    mchehab#1 decode_amp_arm_vendor_data   non-standard-ampere.c:1016
    mchehab#2 ras_arm_vendor_data_decode   ras-arm-vendor-data.c:75
    mchehab#3 ras_arm_event_handler        ras-arm-handler.c:579
  0x7d1fe90ae104 is located 4 bytes after 4096-byte region

The object it runs off is the trace ring buffer page itself, so without
ASan this silently decodes whatever follows it and writes that to the
database.

non-standard-ampereone.c already checks this the same way.

Signed-off-by: Jongwon Lee <ai@linux.com>
decode_hisi_common_section() casts event->error to
hisi_common_error_section without checking event->length, then dumps
err->reg_array up to err->reg_array_size, which comes straight from the
firmware payload. Both read past the end of a short record.

A ras:non_standard_event with a 1-byte payload on the common section
GUID gives:

  AddressSanitizer: heap-buffer-overflow
  READ of size 4
    #0 decode_hisi_common_section non-standard-hisilicon.c:331

Check the length before the cast and clamp the register dump to the
bytes that were actually delivered.

Signed-off-by: Jongwon Lee <ai@linux.com>
decode_jm_oem_type_error() casts event->error to one of five payload
structs without checking event->length, and decode_jm_common_sec_tail()
loops over err->reg_array up to the firmware-supplied
err->reg_array_size.

A ras:non_standard_event with a 1-byte payload on three of the
JaguarMicro section GUIDs gives:

  AddressSanitizer: heap-buffer-overflow
  READ of size 4
    #0 decode_jm_common_sec_tail non-standard-jaguarmicro.c:574

Check the length before each cast and pass the remaining byte count down
to the tail decoder so the register dump stops at the end of the record.

Signed-off-by: Jongwon Lee <ai@linux.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant