gh-107913: Fix possible losses of OSError error codes#107930
gh-107913: Fix possible losses of OSError error codes#107930serhiy-storchaka merged 4 commits intopython:mainfrom
Conversation
Functions like PyErr_SetFromErrno() and SetFromWindowsErr() should be called immediately after using the C API which sets errno or the Windows error code.
vstinner
left a comment
There was a problem hiding this comment.
Maybe it would be just easier/safer to save the errno and then pass it to the function raising OSError, rather than relying on the magic errno variable.
When it's about exchanging two lines, it's fine. But when you move large cleanup code just to preserve errno, IMO it's not a good tradeoff. I prefer code readability and code easy to follow, to read/audit. If you care about the original errno value, just save it and add an API which takes an error code. It's weird that Python has such API for Windows, but not for Unix :-)
This pattern } while (n < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); is kind of fragile if you can about the original errno.
| if (self->fd < 0) { | ||
| PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj); | ||
| goto error; | ||
| } |
There was a problem hiding this comment.
fd_is_own = 1; changes the code in the error label, it's unclear to me if your change is correct or not.
There was a problem hiding this comment.
It only has effect if self->fd >= 0.
There was a problem hiding this comment.
Ok, I see. This change is non-trivial, I preferred to double check.
| else if (errno != 0) { | ||
| ERR_clear_error(); | ||
| PyErr_SetFromErrno(PyExc_OSError); | ||
| ERR_clear_error(); |
There was a problem hiding this comment.
Is this function related to errno? Or is it specific to ssl and it leaves errno unchanged?
There was a problem hiding this comment.
I do not know, but it is better to be on safe side.
There was a problem hiding this comment.
Ok, I'm fine with this change.
Modules/mmapmodule.c
Outdated
| PyErr_SetFromErrno(PyExc_OSError); | ||
| if (devzero != -1) { | ||
| close(devzero); | ||
| } |
There was a problem hiding this comment.
Copying these 3 lines sounds overkill. The code would look better if you would just save/restore errno.
Modules/posixmodule.c
Outdated
| Py_DECREF(list); | ||
| list = path_error(path); | ||
| path_error(path); | ||
| Py_SETREF(list, NULL); |
There was a problem hiding this comment.
Py_CLEAR() can be used. Same remark below.
Oh wow, list = path_error(path); code is wild! But it works :-D
There was a problem hiding this comment.
Yes, for some reasons I used code like Py_SETREF(list, NULL) in few places when introduced this macro (Py_CLEAR() has an additional check and Py_SETREF() clearer expressed the intention). But in this case I think we can ignore the difference and simply use Py_CLEAR().
Done.
Modules/posixmodule.c
Outdated
| return -1; | ||
| n = -1; | ||
| } | ||
| iov_cleanup(iov, buf, cnt); |
There was a problem hiding this comment.
Would it be possible to leave it where it is and save/restore errno?
Modules/posixmodule.c
Outdated
| PyMem_Free(path); | ||
| #else | ||
| Py_DECREF(ub); | ||
| #endif |
There was a problem hiding this comment.
i dislike the fact that you have to copy/paste the cleanup code. maybe save/restor errno?
Modules/posixmodule.c
Outdated
| PyMem_Free(path); | ||
| return NULL; | ||
| } | ||
| PyMem_Free(path); |
There was a problem hiding this comment.
For me, it's easier to reason about PyMem_Free() when it's closer to where it's used, I would prefer to keep it where it is. In the previous code, for me, the scope of "path" is more obvious to me, thanks to the grouped lines.
Python/fileutils.c
Outdated
| #endif | ||
|
|
||
| if (set_inheritable(fileno(f), 0, 1, NULL) < 0) { | ||
| if (f != NULL && set_inheritable(fileno(f), 0, 1, NULL) < 0) { |
There was a problem hiding this comment.
I dislike changes in this file :-( I prefer to exit ASAP on error.
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Thank you for your review comments, Victor. I hope that I addressed them all.
| if (self->fd < 0) { | ||
| PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj); | ||
| goto error; | ||
| } |
There was a problem hiding this comment.
It only has effect if self->fd >= 0.
| else if (errno != 0) { | ||
| ERR_clear_error(); | ||
| PyErr_SetFromErrno(PyExc_OSError); | ||
| ERR_clear_error(); |
There was a problem hiding this comment.
I do not know, but it is better to be on safe side.
Modules/mmapmodule.c
Outdated
| PyErr_SetFromErrno(PyExc_OSError); | ||
| if (devzero != -1) { | ||
| close(devzero); | ||
| } |
Modules/posixmodule.c
Outdated
| Py_DECREF(list); | ||
| list = path_error(path); | ||
| path_error(path); | ||
| Py_SETREF(list, NULL); |
There was a problem hiding this comment.
Yes, for some reasons I used code like Py_SETREF(list, NULL) in few places when introduced this macro (Py_CLEAR() has an additional check and Py_SETREF() clearer expressed the intention). But in this case I think we can ignore the difference and simply use Py_CLEAR().
Done.
Modules/posixmodule.c
Outdated
| return -1; | ||
| n = -1; | ||
| } | ||
| iov_cleanup(iov, buf, cnt); |
Modules/posixmodule.c
Outdated
| PyMem_Free(path); | ||
| #else | ||
| Py_DECREF(ub); | ||
| #endif |
Modules/posixmodule.c
Outdated
| PyMem_Free(path); | ||
| return NULL; | ||
| } | ||
| PyMem_Free(path); |
Python/fileutils.c
Outdated
| #endif | ||
|
|
||
| if (set_inheritable(fileno(f), 0, 1, NULL) < 0) { | ||
| if (f != NULL && set_inheritable(fileno(f), 0, 1, NULL) < 0) { |
vstinner
left a comment
There was a problem hiding this comment.
LGTM! Thanks, it's a nice fix.
I'm concerned about the complex async_err/EINTR code path, but this one is complicated and can deserve its own separated PR.
| if (self->fd < 0) { | ||
| PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj); | ||
| goto error; | ||
| } |
There was a problem hiding this comment.
Ok, I see. This change is non-trivial, I preferred to double check.
| if (dirp == NULL) { | ||
| list = path_error(path); | ||
| path_error(path); | ||
| list = NULL; |
There was a problem hiding this comment.
This list parameter is like really surprising. Would you mind to take the opportunity to convert it to a regular local variable (and maybe always initialize it to NULL at the beginning)?
It seems like in the case, the caller could pass their own list.
I have a personal preference to code like this for the code below:
list = PyList_New(0);
if (list == NULL) {
goto exit;
}
|
Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.11, 3.12. |
|
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
|
GH-108523 is a backport of this pull request to the 3.12 branch. |
…ythonGH-107930) Functions like PyErr_SetFromErrno() and SetFromWindowsErr() should be called immediately after using the C API which sets errno or the Windows error code.. (cherry picked from commit 2b15536) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
|
GH-108524 is a backport of this pull request to the 3.11 branch. |
|
…) (#108523) gh-107913: Fix possible losses of OSError error codes (GH-107930) Functions like PyErr_SetFromErrno() and SetFromWindowsErr() should be called immediately after using the C API which sets errno or the Windows error code. (cherry picked from commit 2b15536) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Functions like PyErr_SetFromErrno() and SetFromWindowsErr() should be called immediately after using the C API which sets errno or the Windows error code.