Skip to content

Conversation

@fatelei
Copy link
Contributor

@fatelei fatelei commented Dec 28, 2025

the original code precomputed nblocks at the beginning of the function, but when a reentrant
writer cleared the array during the first callback, self->ob_item became NULL. The loop continued iterating based on the cached values and dereferenced the null pointer.

@picnixz
Copy link
Member

picnixz commented Dec 28, 2025

It is not the first time you are re-creating a PR because a force-push was wrong. Please, in the future, just don't force push. If you want to update your branch, hit the "update branch" button and pull your changes.

@picnixz picnixz changed the title gh-142884: Fix UAF in array.array.tofile with concurrent mutations gh-142884: Fix UAF in array.array.tofile with concurrent mutations Dec 28, 2025
@fatelei
Copy link
Contributor Author

fatelei commented Dec 28, 2025

It is not the first time you are re-creating a PR because a force-push was wrong. Please, in the future, just don't force push. If you want to update your branch, hit the "update branch" button and pull your changes.

after i rebase upstream/main,there are a lot of commits,so i close the old one,then create a new one

Copy link
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

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

In all your PRs, there are always unrelated changes. This makes me lose time in the reviews a lot so PLEASE, avoid this in the future:

  • Never remove comments unless they become outdated.
  • Do not change unrelated code or try to change code that is not directly used.

After a better look at the code, I'm sorry to tell you to use the while-loop approach because I didn't think about an array that could grow inside the writer (although it could give an infinite loop, I don't think it makes sense to prevent it). You'll need a test for that as well (independently of whether you use a for or a while loop approach).

@bedevere-app
Copy link

bedevere-app bot commented Dec 28, 2025

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 I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@picnixz
Copy link
Member

picnixz commented Dec 28, 2025

after i rebase upstream/main,there are a lot of commits,so i close the old one,then create a new one

Don't rebase upstream/main, just merge main into your branch.

@fatelei
Copy link
Contributor Author

fatelei commented Dec 28, 2025

after i rebase upstream/main,there are a lot of commits,so i close the old one,then create a new one

Don't rebase upstream/main, just merge main into your branch.

ok

Comment on lines +1370 to +1372
def test_tofile_concurrent_mutation(self):
# Keep this test in sync with the implementation in
# Modules/arraymodule.c:array_array_tofile_impl()
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def test_tofile_concurrent_mutation(self):
# Keep this test in sync with the implementation in
# Modules/arraymodule.c:array_array_tofile_impl()
def test_tofile_concurrent_mutation(self):
# Prevent crash when a writer concurrently mutates the array.
# See https://github.com/python/cpython/issues/142884.
# Keep 'BLOCKSIZE' in sync with the array.tofile() C implementation.

Comment on lines +1377 to +1382
cleared = False
def write(self, data):
if not self.cleared:
self.cleared = True
victim.clear()
return 0
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
cleared = False
def write(self, data):
if not self.cleared:
self.cleared = True
victim.clear()
return 0
def write(self, data):
victim.clear()
return 0

We actually don't care about calling clear() multiple times.

victim.clear()
return 0

victim.tofile(Writer())
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
victim.tofile(Writer())
victim.tofile(Writer()) # should not crash

}

if (total_size > max_items) {
return PyErr_NoMemory();
Copy link
Member

Choose a reason for hiding this comment

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

Why raise MemoryError? just break the loop. I think you can just do while (total_size <= max_items) as max_items doesn't change. This would simplify the loop.

Py_ssize_t offset = 0;
while (1) {
Py_ssize_t total_size = Py_SIZE(self);
if (self->ob_item == NULL || total_size == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather check nullity of ob_item at the end, or include it in the loop condition as well.

break;
}

Py_ssize_t size = current_nbytes - offset;
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer reverting this computation, that is, size = BLOCKSIZE and then cut it to what remains.

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.

2 participants