-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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-107913: Fix possible losses of OSError error codes #107930
gh-107913: Fix possible losses of OSError error codes #107930
Conversation
Functions like PyErr_SetFromErrno() and SetFromWindowsErr() should be called immediately after using the C API which sets errno or the Windows error code.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It only has effect if self->fd >= 0
.
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.
Ok, I see. This change is non-trivial, I preferred to double check.
PyErr_SetFromErrno(PyExc_OSError); | ||
ERR_clear_error(); |
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.
Is this function related to errno? Or is it specific to ssl and it leaves errno unchanged?
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 do not know, but it is better to be on safe side.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copying these 3 lines sounds overkill. The code would look better if you would just save/restore errno.
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.
Done.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
} | ||
iov_cleanup(iov, buf, cnt); |
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.
Would it be possible to leave it where it is and save/restore errno?
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.
Done.
Modules/posixmodule.c
Outdated
PyMem_Free(path); | ||
#else | ||
Py_DECREF(ub); | ||
#endif |
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 dislike the fact that you have to copy/paste the cleanup code. maybe save/restor errno?
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.
Done.
Modules/posixmodule.c
Outdated
PyMem_Free(path); | ||
return NULL; | ||
} | ||
PyMem_Free(path); |
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.
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.
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.
Done.
Python/fileutils.c
Outdated
|
||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dislike changes in this file :-( I prefer to exit ASAP on error.
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.
Done.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It only has effect if self->fd >= 0
.
PyErr_SetFromErrno(PyExc_OSError); | ||
ERR_clear_error(); |
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 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); | ||
} |
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.
Done.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
} | ||
iov_cleanup(iov, buf, cnt); |
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.
Done.
Modules/posixmodule.c
Outdated
PyMem_Free(path); | ||
#else | ||
Py_DECREF(ub); | ||
#endif |
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.
Done.
Modules/posixmodule.c
Outdated
PyMem_Free(path); | ||
return NULL; | ||
} | ||
PyMem_Free(path); |
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.
Done.
Python/fileutils.c
Outdated
|
||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
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, 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I see. This change is non-trivial, I preferred to double check.
@@ -4243,7 +4245,8 @@ _posix_listdir(path_t *path, PyObject *list) | |||
} | |||
|
|||
if (dirp == NULL) { | |||
list = path_error(path); | |||
path_error(path); | |||
list = 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.
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.