← index #18638PR #18366
Related · high · value 0.080
QUERY · ISSUE

Segmentation Fault in mp_asm_base_get_cur_to_write_bytes (x64 native emitter)

openby oneafteropened 2026-01-05updated 2026-01-17
bugpy-core

Port, board and/or hardware

unix

MicroPython version

v1.27.0 and master-branch

Issue Report

Description

We discovered a Segmentation Fault vulnerability in MicroPython. The crash occurs within mp_asm_base_get_cur_to_write_bytes when compiling a specific Python script targeting the x64 native emitter.

The ASAN report indicates a READ memory access violation at an invalid address (0x0000bfff800a), suggesting corruption of the assembler state or an invalid pointer calculation during the code emission phase.

Environment

  • OS: Linux x86_64
  • Complier: gcc 11.5.0
  • Tools: AddressSanitizer
  • Affected Version: master branch
  • Build Configure:
make CFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0" \
       LDFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0" \
       CC=gcc STRIP= -j$(nproc)

Vulnerability Details

  • Target: MicroPython (Unix Port)
  • Vulnerability Type: Segmentation Fault (READ access violation)
  • Function: mp_asm_base_get_cur_to_write_bytes
  • Location: py/asmbase.c:70
  • Root Cause Analysis: The crash happens during the compilation phase (mp_compile), specifically when the native emitter is processing a return value (emit_native_return_value). The stack trace shows the flow:
#0 mp_asm_base_get_cur_to_write_bytes
#1 asm_x64_get_cur_to_write_bytes
#2 asm_x64_write_byte_2
#3 asm_x64_mov_mem64_to_r64
#4 emit_native_mov_reg_const

The function mp_asm_base_get_cur_to_write_bytes likely attempts to access the current code buffer pointer or size limit from the mp_asm_base_t structure. The invalid address 0x0000bfff800a suggests that the structure pointer itself is corrupted or one of its member pointers (like code_base) has been calculated incorrectly due to the malformed input script. This specifically affects the x64 Native/Viper code generation.

Reproduce

  1. Compile the micropython with gcc compiler and AddressSanitizer enabled
  2. Run the micropython with the POC input.

Proof of Concept:

def f():
    try:
        try:0
        finally:()
    except:()
@micropython.viper
def f():
    a**0
    try:0
    except:()
    a=0

ASAN report

==36122==ERROR: AddressSanitizer: SEGV on unknown address 0x0000bfff800a (pc 0x55fb9e917cc8 bp 0x000000000048 sp 0x7ffcb8c34f00 T0)
==36122==The signal is caused by a READ memory access.
    #0 0x55fb9e917cc8 in mp_asm_base_get_cur_to_write_bytes ../../py/asmbase.c:70
    #1 0x55fb9e917ea4 in asm_x64_get_cur_to_write_bytes ../../py/asmx64.c:125
    #2 0x55fb9e917ea4 in asm_x64_write_byte_2 ../../py/asmx64.c:136
    #3 0x55fb9e9184c7 in asm_x64_mov_mem64_to_r64 ../../py/asmx64.c:318
    #4 0x55fb9e9201fa in emit_native_mov_reg_const ../../py/emitnative.c:332
    #5 0x55fb9e9201fa in emit_native_return_value ../../py/emitnative.c:2904
    #6 0x55fb9e90f8a4 in compile_scope ../../py/compile.c:3190
    #7 0x55fb9e915184 in mp_compile_to_raw_code ../../py/compile.c:3598
    #8 0x55fb9e915184 in mp_compile ../../py/compile.c:3693
    #9 0x55fb9ea1d262 in parse_compile_execute ../../shared/runtime/pyexec.c:120
    #10 0x55fb9ea157e0 in do_file /src/repro/micropython/ports/unix/main.c:269
    #11 0x55fb9ea157e0 in main_ /src/repro/micropython/ports/unix/main.c:692
    #12 0x7fcf58a401c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9)
    #13 0x7fcf58a4028a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a)
    #14 0x55fb9e8f2fd4 in _start (/src/repro/micropython/ports/unix/build-standard/micropython+0x84fd4)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../../py/asmbase.c:70 in mp_asm_base_get_cur_to_write_bytes
==36122==ABORTING

What does this issue allow an attacker to do?

Denial of Service (DoS). By supplying a Python script that utilizes native code generation (e.g., via decorators or specific syntax), an attacker can crash the MicroPython compiler process. In a scenario where MicroPython allows users to upload and compile code (e.g., WebREPL or a multi-user environment), this leads to a service outage.

How does the attacker exploit this issue?

The attacker provides a malformed Python script designed to trigger edge cases in the x64 native emitter. The script likely contains a specific combination of operations inside a function decorated with @micropython.native or @micropython.viper. The stack trace (emit_native_return_value) suggests the issue is triggered when generating assembly code for a return statement, possibly involving a large constant or a complex memory reference that causes the assembler to calculate an invalid memory address.

Code of Conduct

Yes, I agree

CANDIDATE · PULL REQUEST

mpy-cross: Make the debug emitter work again.

mergedby agattiopened 2025-11-04updated 2025-11-27
py-core

Summary

This PR fixes a regression introduced in 1b92bda5b8e5e2db8e57c4b7134930e52bc5207a when adding RV64 support, in which a new architecture was added to mpy-cross but it had no matching native emitter entry point table entry.

The result was that the architecture emitter entry point would be correctly calculated according to the native architecture index, but if the emitters entry points table was not updated to match the new number of architectures an out of bound access may be performed.

Unfortunately adding RV64IMC shifted the debug emitter index further down the table, that wasn't updated to reflect the lack of an emitter for RV64. Adding a NULL entry there would cause a NULL pointer access as there was no need to perform any check about the emitter entry point function's validity until now.

Testing

Running mpy-cross -X emit=native -march=debug <file> on current master would crash with a segment violation on Unix/x64. With the changes of this PR mpy-cross wouldn't crash any more and the compiled module output was printed to STDOUT. In addition, attempting to compile native code for RV64 triggers an error letting the user know there's no emitter for the chosen architecture.

To prevent situations like this in the future, a new test workflow has been added to make sure the debug emitter doesn't crash and reports output that seems correct. The test simply runs the debug emitter for tests/basics/0prelim.py and checks whether the command doesn't report an error and that the output contains an ENTRY and an EXIT line.

I thought about reusing .github/workflows/mpy_format.yml but then the trigger patterns would need to be extended to also cover py/** and mpy-cross/** making those checks run on more commits than needed.

Trade-offs and Alternatives

This could be implemented almost entirely within mpy-cross except for a new NULL entry in the emitter entry points table, although it is not a generic solution. Being generic, however, doesn't lessen the footprint impact on MicroPython's core. If that's unacceptable I can re-implement this PR to leave the compiler alone and add the necessary checks exclusively inside mpy-cross.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied