Skip to content

Commit ae6adc9

Browse files
authored
gh-146308: Fix error handling issues in _remote_debugging module (#146309)
1 parent b4e5bc2 commit ae6adc9

File tree

10 files changed

+85
-19
lines changed

10 files changed

+85
-19
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fixed multiple error handling issues in the :mod:`!_remote_debugging` module
2+
including a double-free in code object caching, memory leaks on allocation
3+
failure, missing exception checks in binary format varint decoding, reference
4+
leaks on error paths in frame chain processing, and inconsistent thread status
5+
error reporting across platforms. Patch by Pablo Galindo.

Modules/_remote_debugging/_remote_debugging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ typedef struct {
308308
#endif
309309
#ifdef __APPLE__
310310
uint64_t thread_id_offset;
311+
int thread_id_offset_initialized;
311312
#endif
312313
#ifdef MS_WINDOWS
313314
PVOID win_process_buffer;

Modules/_remote_debugging/asyncio.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,9 @@ process_running_task_chain(
940940
PyObject *coro_chain = PyStructSequence_GET_ITEM(task_info, 2);
941941
assert(coro_chain != NULL);
942942
if (PyList_GET_SIZE(coro_chain) != 1) {
943+
PyErr_Format(PyExc_RuntimeError,
944+
"Expected single-item coro chain, got %zd items",
945+
PyList_GET_SIZE(coro_chain));
943946
set_exception_cause(unwinder, PyExc_RuntimeError, "Coro chain is not a single item");
944947
return -1;
945948
}

Modules/_remote_debugging/binary_io.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,8 @@ decode_varint_u32(const uint8_t *data, size_t *offset, size_t max_size)
415415
{
416416
size_t saved_offset = *offset;
417417
uint64_t value = decode_varint_u64(data, offset, max_size);
418-
if (PyErr_Occurred()) {
419-
return 0;
418+
if (*offset == saved_offset) {
419+
return 0; /* decode_varint_u64 already set PyErr */
420420
}
421421
if (UNLIKELY(value > UINT32_MAX)) {
422422
*offset = saved_offset;
@@ -430,9 +430,10 @@ decode_varint_u32(const uint8_t *data, size_t *offset, size_t max_size)
430430
static inline int32_t
431431
decode_varint_i32(const uint8_t *data, size_t *offset, size_t max_size)
432432
{
433+
size_t saved_offset = *offset;
433434
uint32_t zigzag = decode_varint_u32(data, offset, max_size);
434-
if (PyErr_Occurred()) {
435-
return 0;
435+
if (*offset == saved_offset) {
436+
return 0; /* decode_varint_u32 already set PyErr */
436437
}
437438
return (int32_t)((zigzag >> 1) ^ -(int32_t)(zigzag & 1));
438439
}

Modules/_remote_debugging/binary_io_reader.c

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -571,15 +571,16 @@ reader_get_or_create_thread_state(BinaryReader *reader, uint64_t thread_id,
571571
return NULL;
572572
}
573573
} else if (reader->thread_state_count >= reader->thread_state_capacity) {
574-
reader->thread_states = grow_array(reader->thread_states,
575-
&reader->thread_state_capacity,
576-
sizeof(ReaderThreadState));
577-
if (!reader->thread_states) {
574+
ReaderThreadState *new_states = grow_array(reader->thread_states,
575+
&reader->thread_state_capacity,
576+
sizeof(ReaderThreadState));
577+
if (!new_states) {
578578
return NULL;
579579
}
580+
reader->thread_states = new_states;
580581
}
581582

582-
ReaderThreadState *ts = &reader->thread_states[reader->thread_state_count++];
583+
ReaderThreadState *ts = &reader->thread_states[reader->thread_state_count];
583584
memset(ts, 0, sizeof(ReaderThreadState));
584585
ts->thread_id = thread_id;
585586
ts->interpreter_id = interpreter_id;
@@ -590,6 +591,9 @@ reader_get_or_create_thread_state(BinaryReader *reader, uint64_t thread_id,
590591
PyErr_NoMemory();
591592
return NULL;
592593
}
594+
// Increment count only after successful allocation to avoid
595+
// leaving a half-initialized entry visible to future lookups
596+
reader->thread_state_count++;
593597
return ts;
594598
}
595599

@@ -604,7 +608,11 @@ static inline int
604608
decode_stack_full(ReaderThreadState *ts, const uint8_t *data,
605609
size_t *offset, size_t max_size)
606610
{
611+
size_t prev_offset = *offset;
607612
uint32_t depth = decode_varint_u32(data, offset, max_size);
613+
if (*offset == prev_offset) {
614+
return -1;
615+
}
608616

609617
/* Validate depth against capacity to prevent buffer overflow */
610618
if (depth > ts->current_stack_capacity) {
@@ -615,7 +623,11 @@ decode_stack_full(ReaderThreadState *ts, const uint8_t *data,
615623

616624
ts->current_stack_depth = depth;
617625
for (uint32_t i = 0; i < depth; i++) {
626+
size_t prev_offset = *offset;
618627
ts->current_stack[i] = decode_varint_u32(data, offset, max_size);
628+
if (*offset == prev_offset) {
629+
return -1;
630+
}
619631
}
620632
return 0;
621633
}
@@ -627,8 +639,16 @@ static inline int
627639
decode_stack_suffix(ReaderThreadState *ts, const uint8_t *data,
628640
size_t *offset, size_t max_size)
629641
{
642+
size_t prev_offset = *offset;
630643
uint32_t shared = decode_varint_u32(data, offset, max_size);
644+
if (*offset == prev_offset) {
645+
return -1;
646+
}
647+
prev_offset = *offset;
631648
uint32_t new_count = decode_varint_u32(data, offset, max_size);
649+
if (*offset == prev_offset) {
650+
return -1;
651+
}
632652

633653
/* Validate shared doesn't exceed current stack depth */
634654
if (shared > ts->current_stack_depth) {
@@ -664,7 +684,11 @@ decode_stack_suffix(ReaderThreadState *ts, const uint8_t *data,
664684
}
665685

666686
for (uint32_t i = 0; i < new_count; i++) {
687+
size_t prev_offset = *offset;
667688
ts->current_stack[i] = decode_varint_u32(data, offset, max_size);
689+
if (*offset == prev_offset) {
690+
return -1;
691+
}
668692
}
669693
ts->current_stack_depth = final_depth;
670694
return 0;
@@ -677,8 +701,16 @@ static inline int
677701
decode_stack_pop_push(ReaderThreadState *ts, const uint8_t *data,
678702
size_t *offset, size_t max_size)
679703
{
704+
size_t prev_offset = *offset;
680705
uint32_t pop = decode_varint_u32(data, offset, max_size);
706+
if (*offset == prev_offset) {
707+
return -1;
708+
}
709+
prev_offset = *offset;
681710
uint32_t push = decode_varint_u32(data, offset, max_size);
711+
if (*offset == prev_offset) {
712+
return -1;
713+
}
682714
size_t keep = (ts->current_stack_depth > pop) ? ts->current_stack_depth - pop : 0;
683715

684716
/* Validate final depth doesn't exceed capacity */
@@ -699,7 +731,12 @@ decode_stack_pop_push(ReaderThreadState *ts, const uint8_t *data,
699731
}
700732

701733
for (uint32_t i = 0; i < push; i++) {
734+
size_t prev_offset = *offset;
702735
ts->current_stack[i] = decode_varint_u32(data, offset, max_size);
736+
/* If offset didn't advance, varint decoding failed */
737+
if (*offset == prev_offset) {
738+
return -1;
739+
}
703740
}
704741
ts->current_stack_depth = final_depth;
705742
return 0;
@@ -1222,6 +1259,9 @@ binary_reader_close(BinaryReader *reader)
12221259
reader->mapped_data = NULL; /* Prevent use-after-free */
12231260
reader->mapped_size = 0;
12241261
}
1262+
/* Clear sample_data which may point into the now-unmapped region */
1263+
reader->sample_data = NULL;
1264+
reader->sample_data_size = 0;
12251265
if (reader->fd >= 0) {
12261266
close(reader->fd);
12271267
reader->fd = -1; /* Mark as closed */

Modules/_remote_debugging/code_objects.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ cache_tlbc_array(RemoteUnwinderObject *unwinder, uintptr_t code_addr, uintptr_t
110110
void *key = (void *)code_addr;
111111
if (_Py_hashtable_set(unwinder->tlbc_cache, key, entry) < 0) {
112112
tlbc_cache_entry_destroy(entry);
113+
PyErr_NoMemory();
113114
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to store TLBC entry in cache");
114115
return 0; // Cache error
115116
}
@@ -408,7 +409,14 @@ parse_code_object(RemoteUnwinderObject *unwinder,
408409
meta->addr_code_adaptive = real_address + (uintptr_t)unwinder->debug_offsets.code_object.co_code_adaptive;
409410

410411
if (unwinder && unwinder->code_object_cache && _Py_hashtable_set(unwinder->code_object_cache, key, meta) < 0) {
412+
// Ownership of func/file/linetable was transferred to meta,
413+
// so NULL them before destroying meta to prevent double-free
414+
// in the error label's Py_XDECREF calls.
415+
func = NULL;
416+
file = NULL;
417+
linetable = NULL;
411418
cached_code_metadata_destroy(meta);
419+
PyErr_NoMemory();
412420
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to cache code metadata");
413421
goto error;
414422
}

Modules/_remote_debugging/frames.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,12 @@ process_frame_chain(
348348
PyObject *extra_frame_info = make_frame_info(
349349
unwinder, _Py_LATIN1_CHR('~'), Py_None, extra_frame, Py_None);
350350
if (extra_frame_info == NULL) {
351+
Py_XDECREF(frame);
351352
return -1;
352353
}
353354
if (PyList_Append(ctx->frame_info, extra_frame_info) < 0) {
354355
Py_DECREF(extra_frame_info);
356+
Py_XDECREF(frame);
355357
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append extra frame");
356358
return -1;
357359
}

Modules/_remote_debugging/module.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
420420

421421
#if defined(__APPLE__)
422422
self->thread_id_offset = 0;
423+
self->thread_id_offset_initialized = 0;
423424
#endif
424425

425426
#ifdef MS_WINDOWS

Modules/_remote_debugging/object_reading.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ read_py_long(
196196

197197
// Validate size: reject garbage (negative or unreasonably large)
198198
if (size < 0 || size > MAX_LONG_DIGITS) {
199+
PyErr_Format(PyExc_RuntimeError,
200+
"Invalid PyLong digit count: %zd (expected 0-%d)", size, MAX_LONG_DIGITS);
199201
set_exception_cause(unwinder, PyExc_RuntimeError,
200202
"Invalid PyLong size (corrupted remote memory)");
201203
return -1;

Modules/_remote_debugging/threads.c

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,11 @@ find_running_frame(
157157
int
158158
get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread_id) {
159159
#if defined(__APPLE__) && TARGET_OS_OSX
160-
if (unwinder->thread_id_offset == 0) {
160+
if (!unwinder->thread_id_offset_initialized) {
161161
uint64_t *tids = (uint64_t *)PyMem_Malloc(MAX_NATIVE_THREADS * sizeof(uint64_t));
162162
if (!tids) {
163-
PyErr_NoMemory();
164-
return -1;
163+
// Non-fatal: thread status is best-effort
164+
return THREAD_STATE_UNKNOWN;
165165
}
166166
int n = proc_pidinfo(unwinder->handle.pid, PROC_PIDLISTTHREADS, 0, tids, MAX_NATIVE_THREADS * sizeof(uint64_t)) / sizeof(uint64_t);
167167
if (n <= 0) {
@@ -176,6 +176,7 @@ get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread
176176
}
177177
}
178178
unwinder->thread_id_offset = min_offset;
179+
unwinder->thread_id_offset_initialized = 1;
179180
PyMem_Free(tids);
180181
}
181182
struct proc_threadinfo ti;
@@ -239,20 +240,21 @@ get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread
239240
unwinder->win_process_buffer_size = n;
240241
PVOID new_buffer = PyMem_Realloc(unwinder->win_process_buffer, n);
241242
if (!new_buffer) {
242-
return -1;
243+
// Match Linux/macOS: degrade gracefully on alloc failure
244+
return THREAD_STATE_UNKNOWN;
243245
}
244246
unwinder->win_process_buffer = new_buffer;
245247
return get_thread_status(unwinder, tid, pthread_id);
246248
}
247249
if (status != STATUS_SUCCESS) {
248-
return -1;
250+
return THREAD_STATE_UNKNOWN;
249251
}
250252

251253
SYSTEM_PROCESS_INFORMATION *pi = (SYSTEM_PROCESS_INFORMATION *)unwinder->win_process_buffer;
252254
while ((ULONG)(ULONG_PTR)pi->UniqueProcessId != unwinder->handle.pid) {
253255
if (pi->NextEntryOffset == 0) {
254-
// We didn't find the process
255-
return -1;
256+
// Process not found (may have exited)
257+
return THREAD_STATE_UNKNOWN;
256258
}
257259
pi = (SYSTEM_PROCESS_INFORMATION *)(((BYTE *)pi) + pi->NextEntryOffset);
258260
}
@@ -264,7 +266,8 @@ get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread
264266
}
265267
}
266268

267-
return -1;
269+
// Thread not found (may have exited)
270+
return THREAD_STATE_UNKNOWN;
268271
#else
269272
return THREAD_STATE_UNKNOWN;
270273
#endif
@@ -385,12 +388,12 @@ unwind_stack_for_thread(
385388
long pthread_id = GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.thread_id);
386389

387390
// Optimization: only check CPU status if needed by mode because it's expensive
388-
int cpu_status = -1;
391+
int cpu_status = THREAD_STATE_UNKNOWN;
389392
if (unwinder->mode == PROFILING_MODE_CPU || unwinder->mode == PROFILING_MODE_ALL) {
390393
cpu_status = get_thread_status(unwinder, tid, pthread_id);
391394
}
392395

393-
if (cpu_status == -1) {
396+
if (cpu_status == THREAD_STATE_UNKNOWN) {
394397
status_flags |= THREAD_STATUS_UNKNOWN;
395398
} else if (cpu_status == THREAD_STATE_RUNNING) {
396399
status_flags |= THREAD_STATUS_ON_CPU;

0 commit comments

Comments
 (0)