| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
| } \ | ||
| } while (0) | ||
|
|
||
| #define CHECK_TUPLE_ITEMS(obj, n) do { \ |
There was a problem hiding this comment.
Truth be told, we reinvent similar check over and over:
cpython/Objects/memoryobject.c
Lines 217 to 222 in 9a75080
Lines 26 to 33 in 9a75080
Sometimes without a macro:
cpython/Modules/_io/stringio.c
Lines 922 to 930 in 9a75080
Lines 450 to 465 in 9a75080
Sorry, something went wrong.
| Py_ssize_t num_interpreters = PyList_GET_SIZE(stack_frames); | ||
| for (Py_ssize_t i = 0; i < num_interpreters; i++) { | ||
| PyObject *interp_info = PyList_GET_ITEM(stack_frames, i); | ||
| CHECK_TUPLE_ITEMS(interp_info, 2); |
There was a problem hiding this comment.
We validate as we go, so a bad thread in the middle leaves the earlier threads already written and total_samples bumped even though write_sample raised. Should we walk and validate the whole structure first so this is all-or-nothing? Otherwise the caller has no way to recover from the TypeError.
Sorry, something went wrong.
|
|
||
| PyObject *interp_id_obj = PyStructSequence_GET_ITEM(interp_info, 0); | ||
| PyObject *threads = PyStructSequence_GET_ITEM(interp_info, 1); | ||
| CHECK_LIST(threads); |
There was a problem hiding this comment.
This narrows the hole but does not close it. PyLong_AsLong(status_obj) goes through _PyNumber_Index, so an __index__ that mutates the same list re-enters us while we still hold a cached size and a borrowed thread_info:
threads = []
class Evil:
def __index__(self):
del threads[:]
return 0
threads.extend([(1, Evil(), []), (2, 0, [])])
_remote_debugging.BinaryWriter("/tmp/o.bin", 1000, 0, compression=0).write_sample([(0, threads)], 2000)This still segfaults on PyList_GET_ITEM(threads, 1) because del threads[:] frees ob_item. writer_intern_string has the same problem via PyObject_Hash. If we want to claim write_sample is safe against arbitrary input we need strong references to the containers (or to re-read the sizes), not only a type check up front. Happy to take this as a follow-up, but then let's not close the issue with this PR.
Sorry, something went wrong.
| int32_t end_column = LOCATION_NOT_AVAILABLE; | ||
|
|
||
| if (location != Py_None) { | ||
| CHECK_TUPLE_ITEMS(location, 4); |
There was a problem hiding this comment.
normalize_location() in Lib/profiling/sampling/collector.py accepts an int location (a bare lineno) and binary_collector.py hands stack_frames straight to write_sample, so this now raises where the other collectors work. Do we want a PyLong_Check(location) case here too, or is that branch in normalize_location() dead and we should drop it instead?
Sorry, something went wrong.
There was a problem hiding this comment.
@pablogsal I still need some time, but this is a dead path, excercised only by tests. To put aside this PR, do you want me to open a separate PR that removes these?
Sorry, something went wrong.
There was a problem hiding this comment.
Yes, please, a separate PR for the integer-location branches and their tests makes sense. Let's keep this one focused on container validation. Thanks!
Sorry, something went wrong.
|
|
||
| PyObject *interp_id_obj = PyStructSequence_GET_ITEM(interp_info, 0); | ||
| PyObject *threads = PyStructSequence_GET_ITEM(interp_info, 1); | ||
| CHECK_LIST(threads); |
There was a problem hiding this comment.
The __index__ repro above is still open: we iterate threads with a cached size and a borrowed thread_info while PyLong_AsLong(status_obj) can run arbitrary code. No rush, but can you tell me if you want to fix it here or in a separate issue? In the second case let's not close gh-157227 with this PR.
Sorry, something went wrong.
| filename, 1000, 0, compression=0 | ||
| ) as writer: | ||
| with self.assertRaisesRegex(TypeError, field): | ||
| writer.write_sample(sample, 2000) |
There was a problem hiding this comment.
Nit, feel free to ignore: if breaking the writer is intended, can we assert that finalize() raises ValueError after the TypeError?
Sorry, something went wrong.
|
|
||
| PyObject *interp_id_obj = PyStructSequence_GET_ITEM(interp_info, 0); | ||
| PyObject *threads = PyStructSequence_GET_ITEM(interp_info, 1); | ||
| CHECK_LIST(threads); |
There was a problem hiding this comment.
This still crashes when status.index clears threads: the next iteration uses the cached size and reads freed storage. We need mutation-safe traversal and a regression test.
Sorry, something went wrong.
| process_thread_sample(BinaryWriter *writer, PyObject *thread_info, | ||
| uint32_t interpreter_id, uint64_t timestamp_us) | ||
| { | ||
| CHECK_TUPLE_ITEMS(thread_info, 3); |
There was a problem hiding this comment.
A malformed second thread still counts the first and breaks the writer, so earlier valid samples cannot be finalized. Can we validate before committing the sample?
Sorry, something went wrong.
| int32_t end_column = LOCATION_NOT_AVAILABLE; | ||
|
|
||
| if (location != Py_None) { | ||
| CHECK_TUPLE_ITEMS(location, 4); |
There was a problem hiding this comment.
Yes, please, a separate PR for the integer-location branches and their tests makes sense. Let's keep this one focused on container validation. Thanks!
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
See #157227 for more details
Basically, BinaryWriter.write_sample() only checks whether stack_frames is a list, and does PyList_GET_ITEM() / PyStructSequence_GET_ITEM() all the way. The PR adds a check for these containers.
Now it's graceful:
maurycy@gimel cpython (fix-remote-debugging-writer-validation aec95c2*) % ./python.exe Python 3.16.0a0 (heads/fix-remote-debugging-writer-validation-dirty:aec95c21726, Sep 9 2026, 20:) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import _remote_debugging >>> _remote_debugging.BinaryWriter("/tmp/o.bin", 1000, 1000000).write_sample([42], 2000) Traceback (most recent call last): File "<python-input-1>", line 1, in <module> _remote_debugging.BinaryWriter("/tmp/o.bin", 1000, 1000000).write_sample([42], 2000) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ TypeError: interp_info must be a tuple of at least 2 items >>>instead of segmentation fault.