-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
Py_BuildValue may leak 'N' arguments on PyTuple_New failure #70356
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
Comments
Expected behavior: Actual behavior: Example: PyBuildValue("iN", 0, obj);
Many other leaks are possible, as long as at least one allocation occurs prior to the processing of the N arguments. |
Current code already decref obj if creating previous argument is failed. But a leak if PyTuple_New() fails is possible. Proposed patch fixes this leak and adds a test for the case when creating previous argument is failed. Testing the failure of PyTuple_New() is problematic. |
It looks like this patch is against the "default" cpython (3.x) branch. The 2.7 branch is missing the if(itemfailed) code in do_mktuple whose else clause was modified by the patch. It looks like some of this needs to be backported to 2.7? |
Left a review. Squidevil: You say if do_mkvalue() fails, the N object is not released. But looking at the code, I think it gets stored in the tuple, and then the tuple is released: <https://hg.python.org/cpython/file/v3.5.1/Python/modsupport.c#l182\>. So the only leak I can see is if the tuple construction fails. Serhiy’s patch fixes do_mktuple(), but I think the same problem would affect do_mkdict() and do_mklist() as well. |
Martin Panter: You're right. The DECREF on v when itemfailed will decref the N object and prevent the leak. My original analysis was wrong on that count. You're right, do_mklist and do_mkdict (in 2.7.11 at least) have similar problems, bailing after list or dict creation failure, without continuing to process the rest of the items. |
Rather rare reference leak is not the worst bug here. Following example const char *s = ...;
PyObject *b = PyBytes_New(...);
return PyBuildValue("(Ns)s", b, s, PyBytes_AS_STRING(b)); works if s is correct UTF-8 encoded string. But if it is not correct UTF-8 encoded string, the first "s" is failed and the inner tuple is deallocated together with the borrowed reference to b. The second "s" then reads freed memory. |
Previous attempt to fix reference leaks was in bpo-984722. Proposed patch uses different approach. It correctly fixes reference leaks after error in processing dict items (like "{()s(())N"), fixes leaksafter memory errors on creating tuple, list or dict, fixes bpo-20024 for lists and dicts, fixes use-after-free issue described in msg259242. But the patch has a disadvantage in comparison with current code. If user converter ("O&") accepts Python objects and steal its reference (as "N"), this reference will be leaked. This is a price for avoiding potential use-after-free issues. Current stdlib code doesn't use converters that steal Python object reference. May be we should document this limitation. |
I was going to suggest just documenting your use-after-free case as a limitation of the N format. I doubt there is much demand to give away a borrowed reference with N _and_ rely on that borrowed reference staying alive for other parameters. It seems a lot of churn just to avoid a theoretical problem. |
Following patch is written in the style of current code for tuples. |
Patch 3 looks good to me |
Thank you Martin. |
I'm not happy with pybuildvalue_leak3.patch. For failed keys it saves values with the same key (None). This means that old value can be deallocated before the end of building all dict. Following patch collects all values after error in a tuple. This not only fixes the issue with building dict, but makes the code for building tuple, list and dict cleaner. It no longer contains the code for processing after error, it is moved in separate function. |
Is it really a problem if the old value is deallocated? It sounds like a similar case to <https://bugs.python.org/issue26168#msg259242\>, and would only be a problem if you passed a borrowed reference, and relied on the reference staying alive for another argument. I do like the separate do_ignore() function in patch 4, but I don’t think it is worthwhile allocating a temporary tuple to save values in. The allocation can fail. Also, I understand do_mktuple() etc are recursive, so nested borrowed references would still be released before the outer do_ignore() function releases the outer tuple. |
Currently do_mktuple(), do_mklist() and do_mkdict() save references in a collection. I think that there are reasons to do this, and third-party code can be broken if just deallocate references. pybuildvalue_leak4.patch just implements this strategy more reliably. It guaranties (unless tuple allocation fails) that borrowed references in the same container or upper levels are kept until all content of the container is proceeded. The more perfect solution is to allocate one list on error, save all references to it and deallocate it at the end of PyBuildValue(). But this leads to more complicated code. |
Yeah okay, I agree that the code was already saving the references in a container, so it makes sense to keep doing that. |
New changeset 4e605f809551 by Serhiy Storchaka in branch '3.5': New changeset 0285173d81b4 by Serhiy Storchaka in branch '2.7': New changeset 03b32f854680 by Serhiy Storchaka in branch 'default': |
Thank you for your review Martin. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: