From cc18e91d1b9640faa406937ce604866c8e53752f Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 8 Apr 2026 11:49:41 +0100 Subject: [PATCH] gh-148252: Fix stack depth calculation in binary reader on 32-bit platforms Compute ``final_depth`` in ``decode_stack_pop_push()`` and ``decode_stack_suffix()`` using ``uint64_t`` before validating it. On 32-bit builds, using ``size_t`` arithmetic for ``keep + push`` can wrap for large input values, causing the later bounds check to validate the wrong final depth. Using a widened type keeps the validation aligned with the actual result. --- ...-04-08-14-25-47.gh-issue-148252.IEp9Rt.rst | 3 ++ Modules/_remote_debugging/binary_io_reader.c | 32 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-04-08-14-25-47.gh-issue-148252.IEp9Rt.rst diff --git a/Misc/NEWS.d/next/Security/2026-04-08-14-25-47.gh-issue-148252.IEp9Rt.rst b/Misc/NEWS.d/next/Security/2026-04-08-14-25-47.gh-issue-148252.IEp9Rt.rst new file mode 100644 index 00000000000000..adc2c9287149db --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-04-08-14-25-47.gh-issue-148252.IEp9Rt.rst @@ -0,0 +1,3 @@ +Fixed stack depth calculation in :mod:`!_remote_debugging` when decoding +certain ``.pyb`` inputs on 32-bit builds. Issue originally identified and +diagnosed by Tristan Madani (@TristanInSec on GitHub). diff --git a/Modules/_remote_debugging/binary_io_reader.c b/Modules/_remote_debugging/binary_io_reader.c index 616213541e12e1..aca93e9cb1a30e 100644 --- a/Modules/_remote_debugging/binary_io_reader.c +++ b/Modules/_remote_debugging/binary_io_reader.c @@ -601,6 +601,20 @@ reader_get_or_create_thread_state(BinaryReader *reader, uint64_t thread_id, * STACK DECODING HELPERS * ============================================================================ */ +/* Validate that final_depth fits in the stack buffer. + * Uses uint64_t to prevent overflow on 32-bit platforms. */ +static inline int +validate_stack_depth(ReaderThreadState *ts, uint64_t final_depth) +{ + if (final_depth > ts->current_stack_capacity) { + PyErr_Format(PyExc_ValueError, + "Final stack depth %llu exceeds capacity %zu", + (unsigned long long)final_depth, ts->current_stack_capacity); + return -1; + } + return 0; +} + /* Decode a full stack from sample data. * Updates ts->current_stack and ts->current_stack_depth. * Returns 0 on success, -1 on error (bounds violation). */ @@ -658,12 +672,9 @@ decode_stack_suffix(ReaderThreadState *ts, const uint8_t *data, return -1; } - /* Validate final depth doesn't exceed capacity */ - size_t final_depth = (size_t)shared + new_count; - if (final_depth > ts->current_stack_capacity) { - PyErr_Format(PyExc_ValueError, - "Final stack depth %zu exceeds capacity %zu", - final_depth, ts->current_stack_capacity); + /* Use uint64_t to prevent overflow on 32-bit platforms */ + uint64_t final_depth = (uint64_t)shared + new_count; + if (validate_stack_depth(ts, final_depth) < 0) { return -1; } @@ -713,12 +724,9 @@ decode_stack_pop_push(ReaderThreadState *ts, const uint8_t *data, } size_t keep = (ts->current_stack_depth > pop) ? ts->current_stack_depth - pop : 0; - /* Validate final depth doesn't exceed capacity */ - size_t final_depth = keep + push; - if (final_depth > ts->current_stack_capacity) { - PyErr_Format(PyExc_ValueError, - "Final stack depth %zu exceeds capacity %zu", - final_depth, ts->current_stack_capacity); + /* Use uint64_t to prevent overflow on 32-bit platforms */ + uint64_t final_depth = (uint64_t)keep + push; + if (validate_stack_depth(ts, final_depth) < 0) { return -1; }