Skip to content

Commit 328d919

Browse files
jackhaxclaude
andcommitted
gh-146266: Add bounds validation for _Py_DebugOffsets offset fields
validate_debug_offsets() only checked the cookie and version but did not validate that individual offset fields are within their declared struct sizes. If a target process has corrupted _Py_DebugOffsets (e.g. from memory corruption or a misbehaving extension), the GET_MEMBER macro performs unchecked pointer arithmetic with the invalid offsets, causing RemoteUnwinder to crash with SIGSEGV. Add per-field bounds checking: each offset must be less than the .size of its containing sub-struct. Invalid offsets now raise RuntimeError with a descriptive message instead of crashing the debugger process. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 83360b5 commit 328d919

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed

Modules/_remote_debugging/module.c

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,128 @@ validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets)
240240
return -1;
241241
}
242242

243+
// Validate that all offset fields are within their declared struct sizes.
244+
// Each sub-struct has a .size field representing the target struct's total
245+
// size; every other field is an offset that must be strictly less than size.
246+
// Without this check, corrupted offsets cause out-of-bounds reads (SIGSEGV).
247+
248+
#define _CHECK_OFFSET(section, field) \
249+
do { \
250+
if (debug_offsets->section.field >= debug_offsets->section.size) { \
251+
PyErr_Format(PyExc_RuntimeError, \
252+
"debug_offsets." #section "." #field " (%" PRIu64 ") " \
253+
"exceeds " #section ".size (%" PRIu64 ")", \
254+
debug_offsets->section.field, \
255+
debug_offsets->section.size); \
256+
return -1; \
257+
} \
258+
} while (0)
259+
260+
// runtime_state
261+
_CHECK_OFFSET(runtime_state, finalizing);
262+
_CHECK_OFFSET(runtime_state, interpreters_head);
263+
264+
// interpreter_state
265+
_CHECK_OFFSET(interpreter_state, id);
266+
_CHECK_OFFSET(interpreter_state, next);
267+
_CHECK_OFFSET(interpreter_state, threads_head);
268+
_CHECK_OFFSET(interpreter_state, threads_main);
269+
_CHECK_OFFSET(interpreter_state, gc);
270+
_CHECK_OFFSET(interpreter_state, imports_modules);
271+
_CHECK_OFFSET(interpreter_state, sysdict);
272+
_CHECK_OFFSET(interpreter_state, builtins);
273+
_CHECK_OFFSET(interpreter_state, ceval_gil);
274+
_CHECK_OFFSET(interpreter_state, gil_runtime_state);
275+
_CHECK_OFFSET(interpreter_state, gil_runtime_state_locked);
276+
_CHECK_OFFSET(interpreter_state, gil_runtime_state_holder);
277+
_CHECK_OFFSET(interpreter_state, code_object_generation);
278+
279+
// thread_state
280+
_CHECK_OFFSET(thread_state, prev);
281+
_CHECK_OFFSET(thread_state, next);
282+
_CHECK_OFFSET(thread_state, interp);
283+
_CHECK_OFFSET(thread_state, current_frame);
284+
_CHECK_OFFSET(thread_state, base_frame);
285+
_CHECK_OFFSET(thread_state, last_profiled_frame);
286+
_CHECK_OFFSET(thread_state, thread_id);
287+
_CHECK_OFFSET(thread_state, native_thread_id);
288+
_CHECK_OFFSET(thread_state, datastack_chunk);
289+
_CHECK_OFFSET(thread_state, status);
290+
_CHECK_OFFSET(thread_state, holds_gil);
291+
_CHECK_OFFSET(thread_state, gil_requested);
292+
_CHECK_OFFSET(thread_state, current_exception);
293+
_CHECK_OFFSET(thread_state, exc_state);
294+
295+
// interpreter_frame
296+
_CHECK_OFFSET(interpreter_frame, previous);
297+
_CHECK_OFFSET(interpreter_frame, executable);
298+
_CHECK_OFFSET(interpreter_frame, instr_ptr);
299+
_CHECK_OFFSET(interpreter_frame, localsplus);
300+
_CHECK_OFFSET(interpreter_frame, owner);
301+
_CHECK_OFFSET(interpreter_frame, stackpointer);
302+
303+
// code_object
304+
_CHECK_OFFSET(code_object, filename);
305+
_CHECK_OFFSET(code_object, name);
306+
_CHECK_OFFSET(code_object, qualname);
307+
_CHECK_OFFSET(code_object, linetable);
308+
_CHECK_OFFSET(code_object, firstlineno);
309+
_CHECK_OFFSET(code_object, argcount);
310+
_CHECK_OFFSET(code_object, localsplusnames);
311+
_CHECK_OFFSET(code_object, localspluskinds);
312+
_CHECK_OFFSET(code_object, co_code_adaptive);
313+
314+
// pyobject
315+
_CHECK_OFFSET(pyobject, ob_type);
316+
317+
// type_object
318+
_CHECK_OFFSET(type_object, tp_name);
319+
_CHECK_OFFSET(type_object, tp_repr);
320+
_CHECK_OFFSET(type_object, tp_flags);
321+
322+
// tuple_object
323+
_CHECK_OFFSET(tuple_object, ob_item);
324+
_CHECK_OFFSET(tuple_object, ob_size);
325+
326+
// list_object
327+
_CHECK_OFFSET(list_object, ob_item);
328+
_CHECK_OFFSET(list_object, ob_size);
329+
330+
// set_object
331+
_CHECK_OFFSET(set_object, used);
332+
_CHECK_OFFSET(set_object, table);
333+
_CHECK_OFFSET(set_object, mask);
334+
335+
// dict_object
336+
_CHECK_OFFSET(dict_object, ma_keys);
337+
_CHECK_OFFSET(dict_object, ma_values);
338+
339+
// float_object
340+
_CHECK_OFFSET(float_object, ob_fval);
341+
342+
// long_object
343+
_CHECK_OFFSET(long_object, lv_tag);
344+
_CHECK_OFFSET(long_object, ob_digit);
345+
346+
// bytes_object
347+
_CHECK_OFFSET(bytes_object, ob_size);
348+
_CHECK_OFFSET(bytes_object, ob_sval);
349+
350+
// unicode_object
351+
_CHECK_OFFSET(unicode_object, state);
352+
_CHECK_OFFSET(unicode_object, length);
353+
354+
// gc
355+
_CHECK_OFFSET(gc, collecting);
356+
_CHECK_OFFSET(gc, frame);
357+
358+
// gen_object
359+
_CHECK_OFFSET(gen_object, gi_name);
360+
_CHECK_OFFSET(gen_object, gi_iframe);
361+
_CHECK_OFFSET(gen_object, gi_frame_state);
362+
363+
#undef _CHECK_OFFSET
364+
243365
return 0;
244366
}
245367

0 commit comments

Comments
 (0)