Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a news entry here either (I think we've decided that before too, I think it was on Jelle's PR), additionally it describes a lot of internal things (please see the devguide for more information about what we want in these entries).

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix a segfault in ``_interpreters.capture_exception()`` under memory
pressure. When ``_PyXI_NewExcInfo()`` failed to allocate, the cleanup path
called ``_PyXI_FreeExcInfo(NULL)``, which then dereferenced offset 0 in
``_excinfo_clear_type()``. ``_PyXI_FreeExcInfo()`` now accepts ``NULL`` like
the surrounding ``PyMem_RawFree`` idiom. Patch by Amrutha Modela.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is too technical. Users reading the changelog will have no idea what this means. Let's just say something like "Fix crash when _interpreters.capture_memory runs out of memory."

5 changes: 5 additions & 0 deletions Python/crossinterp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,11 @@ _PyXI_NewExcInfo(PyObject *exc)
void
_PyXI_FreeExcInfo(_PyXI_excinfo *info)
{
if (info == NULL) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to not allow NULL values without a specific need. It might hide errors.

Please, take a look at #152013 You can copy my solution from there. I missed that you had a PR already and created my own one. But, I closed it, so you can contribute :)

This is not the only problem in the same function. _PyXI_NewExcInfo can also crash.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sobolevn!! Really appreciated you closing #152013 and sharing your diff, especially the catch about _PyXI_NewExcInfo also being able to crash. The final fix is much better for addressing both bugs in one go. :)

// Matches the PyMem_RawFree(NULL) idiom: callers may pass NULL when
// _PyXI_NewExcInfo() failed (e.g. under OOM) before any allocation.
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but I don't think this is the right approach. Functions that do nothing under NULL don't make good APIs. To quote the Zen: "Errors should never pass silently".

To me, free() accepting NULL has always felt like a weird half-baked solution to error handling that can't be removed for the sake of backward compatibility. Rant aside, there are some real API-design consequences to this:

  1. A NULL pointer usually means there's some sort of underlying logic bug, and/or that something else has gone unchecked. Crashing on NULL forces the developer to double-check their error handling logic.
  2. This is inconsistent with the rest of the _PyXI_* API. I can't find anything else in _PyXI that has a no-op case when passed NULL.
  3. I'd argue that this encourages sloppy habit. _PyXI_FreeExcInfo accepting NULL makes it feel safer than it actually is. You still need to ensure that the memory is initialized and that it's called exactly once for every call to _PyXI_NewExcInfo.
  4. This one is nitpicky and might be optimized away, but every call to _PyXI_FreeExcInfo now has a performance tax of a NULL check. It's better to structure the code so that _PyXI_FreeExcInfo isn't called at all on an allocation failure, so a separate check isn't needed in the first place.

Please just add a NULL check at the call site, and perhaps add an info != NULL assertion here. It's hard to come up with concrete examples against this pattern when there's only a single use of the function, but I hope you respect my arguments as a maintainer.

_PyXI_excinfo_clear(info);
PyMem_RawFree(info);
}
Expand Down
Loading