-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
gh-143544: Fix possible use-after-free in the JSON decoder when JSONDecodeError disappears during raising it #143561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-143544: Fix possible use-after-free in the JSON decoder when JSONDecodeError disappears during raising it #143561
Conversation
|
skip news |
|
No, this affects user-facing APIs; please add a news entry. |
ZeroIntensity
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Please avoid making unnecessary code changes. They make it significantly more difficult to review.
- This is doing too much. The much simpler and more correct fix would be to simply move the
Py_DECREFcall on line 426 to after theif (exc)block. - Please add a test case and news entry.
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
|
Thanks for the review. I’ve updated the patch to keep the fix minimal, added a regression test |
Misc/NEWS.d/next/Library/2026-01-08-19-38-02.gh-issue-143544.4jRCBJ.rst
Outdated
Show resolved
Hide resolved
Modules/_json.c
Outdated
|
|
||
| PyObject *JSONDecodeError = | ||
| PyImport_ImportModuleAttr(&_Py_STR(json_decoder), &_Py_ID(JSONDecodeError)); | ||
| PyImport_ImportModuleAttr(&_Py_STR(json_decoder), &_Py_ID(JSONDecodeError)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again please don't make unrelated formatting changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change.
Modules/_json.c
Outdated
| if (exc) { | ||
| PyObject *exc = PyObject_CallFunction(JSONDecodeError, "zOn", msg, s, end); | ||
| if (exc != NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. The only change we need is the movement of the Py_DECREF call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change, only move Py_DECREF(JSONDecodeError);.
|
Thanks for the clarification. I’ve updated the test to only assert that the |
|
The NEWS entry was removed per discussion above. Could someone please add the “skip news” label? |
|
I have made the requested changes; please review again. |
|
Thanks for making the requested changes! @ZeroIntensity: please review the changes made to this pull request. |
Modules/_json.c
Outdated
|
|
||
| PyObject *JSONDecodeError = | ||
| PyImport_ImportModuleAttr(&_Py_STR(json_decoder), &_Py_ID(JSONDecodeError)); | ||
| PyImport_ImportModuleAttr(&_Py_STR(json_decoder), &_Py_ID(JSONDecodeError)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change.
Modules/_json.c
Outdated
| if (exc) { | ||
| PyObject *exc = PyObject_CallFunction(JSONDecodeError, "zOn", msg, s, end); | ||
| if (exc != NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change, only move Py_DECREF(JSONDecodeError);.
Lib/test/test_json/test_fail.py
Outdated
|
|
||
| def test_reentrant_jsondecodeerror_does_not_crash(self): | ||
| # gh-143544 | ||
| import json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this import at the module top level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved import json to the module top level as requested. Thanks.
Modules/_json.c
Outdated
| Py_DECREF(exc); | ||
| } | ||
|
|
||
| /* Move DECREF after PyErr_SetObject */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that this comment is useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the comment as suggested.
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no good place for such test, but I think test_speedups is less wrong than test_fail.
But I suspect that it may be impossible to write a test without raising SystemError for the fixed code, which is only slightly better than a crash. If this is so, we will give up and accept a fix without unit test.
Lib/test/test_json/test_fail.py
Outdated
| raise self | ||
|
|
||
| hook = Trigger() | ||
| hook = Trigger("boom") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, hook should be not an instance of exception, but an exception class itself.
So, you need to define __new__() to trigger execution when it is called, not __call__().
Lib/test/test_json/test_fail.py
Outdated
| self.assertEqual(sys.getrefcount(hook), 3) | ||
| del hook | ||
|
|
||
| support.gc_collect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not have effect here, because there are references like json.decoder.JSONDecodeError.
But calling it after del json.decoder.JSONDecodeError in __new__() will also not help, because we still have a reference as a method parameter. So, it is useless.
Maybe we could not create a good test for this issue.
|
Thanks for clarifying. I agree that reliably testing this without triggering a SystemError may not be feasible. |
|
If this is not feasible, drop the tests. Thank you for your PR. |
|
I’ve dropped the test as suggested since it doesn’t seem feasible to reliably reproduce this case without introducing a SystemError. Could you please re-review and let me know if anything else is needed? |
vstinner
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. I'm fine with no adding a test for this tricky bug.
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
|
Thanks @VanshAgarwal24036 for the PR, and @vstinner for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14. |
|
GH-143733 is a backport of this pull request to the 3.14 branch. |
… JSONDecodeError disappears during raising it (pythonGH-143561) (cherry picked from commit c3157480601499565fd42a8afbdb0207328ac484) Co-authored-by: VanshAgarwal24036 <[email protected]> Co-authored-by: Bénédikt Tran <[email protected]>
|
GH-143734 is a backport of this pull request to the 3.13 branch. |
… JSONDecodeError disappears during raising it (pythonGH-143561) (cherry picked from commit c315748) Co-authored-by: VanshAgarwal24036 <[email protected]> Co-authored-by: Bénédikt Tran <[email protected]>
|
Merged, thanks for the fix @VanshAgarwal24036. |
…n JSONDecodeError disappears during raising it (GH-143561) (#143734) gh-143544: Fix possible use-after-free in the JSON decoder when JSONDecodeError disappears during raising it (GH-143561) (cherry picked from commit c315748) Co-authored-by: VanshAgarwal24036 <[email protected]> Co-authored-by: Bénédikt Tran <[email protected]>
…n JSONDecodeError disappears during raising it (GH-143561) (#143733) gh-143544: Fix possible use-after-free in the JSON decoder when JSONDecodeError disappears during raising it (GH-143561) (cherry picked from commit c315748) Co-authored-by: VanshAgarwal24036 <[email protected]> Co-authored-by: Bénédikt Tran <[email protected]>
|
Thanks a lot for the review and merge! I really appreciate it. |
… JSONDecodeError disappears during raising it (python#143561) Co-authored-by: Bénédikt Tran <[email protected]>
Summary
When JSONDecodeError is user-replaced and re-enters during JSON parsing,
_raise_errmsg could reuse a freed exception type, leading to a
use-after-free.
This change holds a strong reference across the call and validates the
exception type before setting it, falling back safely when needed.
Issue
_json.raise_errmsgvia re-entrantJSONDecodeErrorhook #143544