Repository navigation
Fix segfault when decompression fails while draining pending data - #13249
Conversation
The llhttp callbacks are declared except -1 but return -1 as a plain abort status with the exception already caught and stashed. That is fine when llhttp calls them, but feed_data also calls cb_on_body directly to drain pending decompressed data, and Cython takes the error path on -1 without checking PyErr_Occurred, crashing in __Pyx_AddTraceback on Python 3.12+. Declare the callbacks except? -1 so -1 without a pending exception is a legal value and handle the drain failure explicitly.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #13249 +/- ##
=======================================
Coverage 98.98% 98.98%
=======================================
Files 131 131
Lines 48993 49021 +28
Branches 2550 2551 +1
=======================================
+ Hits 48496 48524 +28
Misses 373 373
Partials 124 124
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Merging this PR will not alter performance
Comparing Footnotes
|
PR Review — Fix segfault when decompression fails while draining pending dataCorrect, minimal fix for a real 3.12+ segfault. Merge-ready.
🟢 Suggestions
1. Drain error path leaves self._payload set, unlike the llhttp error path
|
decline that. Too risky for this and should be kept narrow |
Backport to 3.14: 💚 backport PR created✅ Backport PR branch: Backported as #13272 🤖 @patchback |

What do these changes do?
The llhttp callbacks in the C parser are declared
except -1, which promises Cython that a -1 return always has an exception pending. The callbacks actually return -1 as a plain abort status after catching the error and stashing it for later delivery. That is fine when llhttp calls them, butfeed_dataalso callscb_on_bodydirectly to drain pending decompressed data; when the decompressor raises during that drain, Cython takes the error path with no exception set and crashes in__Pyx_AddTracebackon Python 3.12 and newer. This happens in the wild with brotlicffi 1.2, see python-hyper/brotlicffi#225 and cython/cython#7857.The callbacks are now declared
except? -1so Cython checksPyErr_Occurred()before taking the error path, and the drain call handles the failure explicitly; the error reaches the user through the payload asContentEncodingErrorlike other payload errors.Are there changes in behavior for the user?
A decompression failure while draining pending data now raises
ContentEncodingErrorinstead of crashing the interpreter.Is it a substantial burden for the maintainers to support this?
No, the exception declarations now match what the callbacks actually do.
Related issue number
Fixes #13203.
Checklist
CONTRIBUTORS.txtCHANGES/foldername it
<issue_or_pr_num>.<type>.rst(e.g.588.bugfix.rst)if you don't have an issue number, change it to the pull request
number after creating the PR
.bugfix: A bug fix for something the maintainers deemed animproper undesired behavior that got corrected to match
pre-agreed expectations.
.feature: A new behavior, public APIs. That sort of stuff..deprecation: A declaration of future API removals and breakingchanges in behavior.
.breaking: When something public is removed in a breaking way.Could be deprecated in an earlier release.
.doc: Notable updates to the documentation structure or buildprocess.
.packaging: Notes for downstreams about unobvious side effectsand tooling. Changes in the test invocation considerations and
runtime assumptions.
.contrib: Stuff that affects the contributor experience. e.g.Running tests, building the docs, setting up the development
environment.
.misc: Changes that are hard to assign to any of the abovecategories.
Make sure to use full sentences with correct case and punctuation,
for example:
Use the past tense or the present tense a non-imperative mood,
referring to what's changed compared to the last released version
of this project.
Test output
The new test reproduces the crash on the unfixed extension:
With the fix, on Python 3.14.2 with the C extension built:
Pure Python mode:
The regenerated call site in
_http_parser.cnow guards the error path:Drafted with Claude Code (Fable 5); reviewed by bdraco.