Skip to content

gh-152107: Fix crash when creating a dict item iterator under MemoryError#152110

Open
zangjiucheng wants to merge 3 commits into
python:mainfrom
zangjiucheng:gh-152107
Open

gh-152107: Fix crash when creating a dict item iterator under MemoryError#152110
zangjiucheng wants to merge 3 commits into
python:mainfrom
zangjiucheng:gh-152107

Conversation

@zangjiucheng

@zangjiucheng zangjiucheng commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Fix: guard the untrack with _PyObject_GC_IS_TRACKED() so a never-tracked
iterator is freed cleanly.

Reproducer (debug build, _strptime drains the size-2 tuple freelist so the
di_result allocation actually fails):

import _strptime
from _testcapi import set_nomemory
for start in range(60):
    set_nomemory(start)
    try:
        _strptime._strptime("", "")
    except BaseException:
        pass

Found via OOM-injection fuzzing (OOM-0006 in #151763).

🤖 Generated with Claude Code

Comment thread Lib/test/test_dict.py Outdated
@support.cpython_only
def test_item_iterator_no_memory(self):
# gh-152107: dictiter_new() for a dict item-iterator allocates
# di_result via _PyTuple_FromPairSteal() before _PyObject_GC_TRACK().

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 suggest to remove all the technical details from the comments, they will change at some point.

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.

Thank you. yep. I wasn't sure earlier whether I should keep the behaviour explanation.

Will address in next commit

Comment thread Objects/dictobject.c Outdated
dictiterobject *di = (dictiterobject *)self;
/* bpo-31095: UnTrack is needed before calling any callbacks */
_PyObject_GC_UNTRACK(di);
/* bpo-31095: UnTrack is needed before calling any callbacks.

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.

Please, do not change the existing comment, add a new one.

Comment thread Lib/test/test_dict.py Outdated
for start in range(60):
set_nomemory(start)
try:
_strptime._strptime("", "")

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 am not sure that this test is correct: _strptime can switch from dict api to something else, test will pass, but it won't check the same problem.

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.

Good catch. I will rewrite the test to drive the failing path directly via iter(d.items()) instead of relying on _strptime's internals, which prevent _strtime implement changes.

Comment thread Lib/test/test_dict.py Outdated
import _strptime
from _testcapi import set_nomemory
for start in range(60):
set_nomemory(start)

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.

shouldn't we clear the memory hooks here on each iteration?

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.

I will add remove_mem_hooks() in a finally on every iteration, and move the whole thing into a subprocess via assert_python_ok so the OOM injection can't leak into the rest of the suite.

@sergey-miryanov

Copy link
Copy Markdown
Contributor

Since having di->di_dict == NULL and di->di_result == NULL is a valid dictiter state, we can move _PyObject_GC_TRACK earlier in dictiter_new().

@zangjiucheng

Copy link
Copy Markdown
Contributor Author

Since having di->di_dict == NULL and di->di_result == NULL is a valid dictiter state, we can move _PyObject_GC_TRACK earlier in dictiter_new().

That's a good point. The fix is identical, but we can make it early to ensure the dictiter valid state is met.

@zangjiucheng

Copy link
Copy Markdown
Contributor Author

Hello everyone, thank you for your suggestions!! I’ve addressed all the issues based on your feedback.

Sorry, I was running a Claude in my workspace to do some auto scan, which caused some confusion in the commit history, so I made a forced commit to resolve the issue :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants