-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
POpen does not close fds when fds have been inherited from a process with a higher resource limit #65817
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
The sysconf(_SC_OPEN_MAX) approach to closing fds is kind of flawed. It is kind of hacky and slow (see http://bugs.python.org/issue1663329). Moreover, this approach is incorrect as fds can be inherited from previous processes that have had higher resource limits. This is especially important because this is a possible security problem. I would recommend using the closefrom system call on BSDs or the /dev/fd directory on BSDs and /proc/self/fd on Linux (remember not to close fds as you read directory entries from the fd directory as that gives weird results because you're concurrently reading and modifying the entries in the directory at the same time). A C program that illustrates the problem of inheriting fds past lowered resource limits is shown below. #include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
int main() {
struct rlimit const limit = {
.rlim_cur = 0,
.rlim_max = 0
};
if (-1 == setrlimit(RLIMIT_NOFILE, &limit)) {
fprintf(stderr, "error: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
} |
Okay here's a stub patch that address FreeBSD, NetBSD and Linux. I'm not sure how to address the other platforms. |
Oh right! I forgot a possible problem with my proposed patch. It is incompatible with Valgrind (see issue https://bugs.kde.org/show_bug.cgi?id=331311). Either this patch won't be applied, Valgrind compatibility is judged not essential or the Valgrind developers will start emulating /proc/self/fd. |
Are you aware that the subprocess module does use /proc/self/fd in Python 3.2 and later? The fd closing is not done from Python code. See Modules/_posixsubprocess.c - http://hg.python.org/cpython/file/53fa2c9523d4/Modules/_posixsubprocess.c |
regardless, the current C code for this does limit itself to the sysconf(_SC_OPEN_MAX) max_fd from module import time when closing fds found in /proc/self/fd so this code does still have a bug in that fds higher than that will remain unclosed (at which point your valgrind issue would come into play unless we can detect we are running under valgrind and alter our behavior to obey the max in that case). |
There appear to be a two bugs here, depending on which platform subprocess is being used on.
It should not pay attention to end_fd at all. It knows the list of actual open fds and should use that. If possible, consider detecting and avoiding closing valgrind fds; but that is a special case for a valgrind bug and likely not worth it.
The sysconf("SC_OPEN_MAX") value is only saved at module import time but may be changed up or down at runtime by the process by using the setrlimit(RLIMIT_NOFILE, ...) libc call. what sysconf returns is the same as the current rlim_cur setting. (at least on Linux where this code path wouldn't actually be taken because #1 is available). possible solution: call getrlimit(RLIMIT_NOFILE) and use rlim_max instead of sysconf("SC_OPEN_MAX") at module import time. Neither of these are likely scenarios so I wouldn't consider this a high priority to fix but it should be done. Most code never ever touches its os resource limits. getrlimit and setrlimit are not exposed in the os module; you must use ctypes or an extension module to call them from Python: import ctypes
class StructRLimit(ctypes.Structure):
_fields_ = [('rlim_cur', ctypes.c_ulong), ('rlim_max', ctypes.c_ulong)]
libc = ctypes.cdll.LoadLibrary('libc.so.6')
RLIMIT_NOFILE = 7 # Linux
limits = StructRLimit()
assert libc.getrlimit(RLIMIT_NOFILE, ctypes.byref(limits)) == 0
print(limits.rlim_cur, limits.rlim_max)
limits.rlim_cur = limits.rlim_max
assert libc.setrlimit(RLIMIT_NOFILE, ctypes.byref(limits)) == 0 |
I agree that this is not a likely scenario but I can think of one Thanks for correcting me on the location of the fd closing code. Some Strangely, there seems to be a _close_fds method in the Python The bug I mentioned earlier about concurrently modifing the fd dir and There doesn't seem to be any point to caching max_fd in a variable on Anyways, the code should be refactored to not use max_fd on the Thank you for your thoughts. Also, should I keep discussion of some of |
There is >>> import resource
>>> resource.getrlimit(resource.RLIMIT_NOFILE)
(1024, 4096) |
I found another problem with _close_open_fd_range_safe. POSIX leaves |
Here's a patch with a unittest that reproduces the problem with fixes to stop using any end_fds. The max fd is only ever used in the absolute fallback situation where no way to get a list of open fd's is available. In that case it is obtained from sysconf() at the time it is needed rather than module load time as sysconf() is async-signal-safe. I'm not worried about calling close() an additional time on EINTR in the single threaded child process prior to exec(). The most that will happen is one extra call with a different error if the fd is in a bad state from the previous one. That is better than any chance of one being left open. |
Thank you for the very quick patch Gregory P. Smith. It's fair enough if you don't bother to fix the EINTR issue. One small note:
That's a typo right? Shouldn't it be may instead of mail? |
New changeset 5453b9c59cd7 by Gregory P. Smith in branch '3.4': New changeset 012329c8c4ec by Gregory P. Smith in branch 'default': |
Backported to subprocess32 in https://code.google.com/p/python-subprocess32/source/detail?r=1c27bfe7e98f78e6aaa746b5c0a4d902a956e2a5 |
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: