Skip to content

py/objarray: Guard array allocation size against overflow.#19222

Open
kbhetrr wants to merge 1 commit into
micropython:masterfrom
kbhetrr:fix/array-len-overflow-18620
Open

py/objarray: Guard array allocation size against overflow.#19222
kbhetrr wants to merge 1 commit into
micropython:masterfrom
kbhetrr:fix/array-len-overflow-18620

Conversation

@kbhetrr
Copy link
Copy Markdown

@kbhetrr kbhetrr commented May 14, 2026

Fixes #18620.

Summary

array_new() allocates the backing buffer for array.array() as typecode_size * n bytes, where n comes from the initializer's __len__. With a large enough n the multiplication overflows size_t, so the allocation is sized for a small (or zero-byte) buffer while o->len still records the original huge value. The subsequent fill loop in array_construct() then writes through a NULL or undersized items pointer.

This PR adds an overflow check before allocation and raises MemoryError via the existing m_malloc_fail() path when the requested array would exceed SIZE_MAX bytes.

Reproducer

from array import array


class Boom:
    def __len__(self):
        return 1 << 61

    def __iter__(self):
        for _ in range(4):
            yield 1.23


array("d", Boom())

Before the patch, on a 64-bit Unix port build:

  • standard build: segmentation fault (exit 139), writing to address 0 in mp_binary_set_val_array().
  • ASan/UBSan build: null pointer store reported in py/binary.c.

After the patch the same script raises MemoryError cleanly.

Tests

A regression test is added at tests/basics/array_construct_len_overflow_intbig.py. The _intbig suffix gates it on builds with long-int support, since the literal 1 << 61 is not representable as a small int on 32-bit small-int-only configurations.

make -C mpy-cross -j$(nproc)
make -C ports/unix submodules
make -C ports/unix -j$(nproc)
env MICROPY_MICROPYTHON=../ports/unix/build-standard/micropython \
    python3 tests/run-tests.py -d basics

Result: 545 tests pass, 30 skipped (no new failures or regressions). Targeted check:

basics/array_construct.py                       pass
basics/array_construct_len_overflow_intbig.py   pass
float/array_construct.py                        pass

An ASan/UBSan Unix build was also used to verify the original reproducer no longer crashes.

Code formatting

  • C: tools/codeformat.py -c -f py/objarray.c with uncrustify v0.72.
  • Python: ruff check and ruff format --check on the new test file (both clean).

Notes

  • The check is placed in array_new() rather than array_construct() so it also covers bytearray(<huge int>) and any other call site that funnels through array_new().
  • Long-int representation of the test's __len__ value on 32-bit configs would already break the existing MP_OBJ_SMALL_INT_VALUE(len_in) assumption in array_construct(), which is a separate latent issue; the regression test deliberately scopes itself to bignum-capable builds via the _intbig suffix.

When constructing array.array() from an object whose __len__ returns
a very large value, the backing-buffer size computation
typecode_size * n could wrap around size_t and produce a small or
zero-sized allocation while o->len remained the original huge value.
Subsequent writes during construction then dereferenced a NULL or
undersized items pointer, crashing the VM.

Reproducer (Unix port, 64-bit):

    from array import array

    class Boom:
        def __len__(self):
            return 1 << 61
        def __iter__(self):
            for _ in range(4):
                yield 1.23

    array("d", Boom())

Without this change the Unix port crashes with a write to address
zero in mp_binary_set_val_array(); an ASan/UBSan build reports a
null pointer store in py/binary.c.

Check the multiplication in array_new() before allocating and raise
MemoryError via the existing m_malloc_fail() path when the requested
array would exceed SIZE_MAX bytes.

Fixes micropython#18620.

Signed-off-by: Kyounghwan Kim <kimkh7534@pnucslab.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.47%. Comparing base (200db69) to head (9dba6c0).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #19222   +/-   ##
=======================================
  Coverage   98.47%   98.47%           
=======================================
  Files         176      176           
  Lines       22845    22847    +2     
=======================================
+ Hits        22497    22499    +2     
  Misses        348      348           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

Code size report:

Reference:  tests/run-tests.py: Skip string_escape.py as an mpy if no unicode. [200db69]
Comparison: py/objarray: Guard array allocation size against overflow. [merge of 9dba6c0]
  mpy-cross:   +16 +0.004% 
   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:   +16 +0.002% standard
      stm32:   +24 +0.006% PYBV10
      esp32:   -76 -0.004% ESP32_GENERIC
     mimxrt:   +24 +0.006% TEENSY40
        rp2:   +56 +0.006% RPI_PICO_W
       samd:   +24 +0.009% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:   +10 +0.002% VIRT_RV32

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.

Integer overflow in array constructor via len*itemsize multiplication

1 participant