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
subprocess: add user, group and extra_groups parameters #80227
Comments
Currently when using python to automate system administration tasks, it is useful to drop privileges sometimes. Currently the only way to do this is via a preexec_fn, which has well-documented problems. It would be useful to be able to pass a user and groups arguments to subprocess.popen. |
Patrick, could you provide more background that would explain your choice of setreuid/setregid functions and the desired handling of supplementary groups? I'm not a security expert, so I may not have sufficient expertise to judge on that, but maybe my questions could be helpful for others:
The current workaround for the above is to call setgroups() only if geteuid() == 0, but I'm not sure it's a good one: Another solution would be to split groups into two arguments: gid and supplementary groups. This way users can explicitly tell us whether they want to switch supplementary groups or not. Overall, I'd really like to have security experts review this PR if possible. [1] http://man7.org/linux/man-pages/man2/setreuid.2.html |
Alexey, here are my responses to your points:
I am fine removing the workaround, and allowing it to fail with EPERM. Perhaps we could find another way around this for running the tests in an unprivileged environment, or just leave the test only running the EPERM case... I could change the API to have have group= and supp_groups= if you prefer. |
Thanks for your explanation. In case of a privileged process, the behavior of setreuid/setregid/setgroups does seem well-defined. But setuid/setgid change all ids (real, effective, saved) too in this case. Do you prefer setreuid/setregid because they provide stricter semantics in non-privileged processes compared to setuid/setgid? (The latter ones change the effective id only, potentially preserving the process ability to switch ids later).
Personally, I do prefer separate arguments because subprocess is a relatively low-level module and tends to expose underlying operations, so it seems logical to have a knob that maps to setgid() and another one that maps to setgroups(). @gregory.p.smith: what do you think about it? |
Yes, exactly. The stricter semantics provide stronger security guarantees. The idea is to run code in an unprivileged context in a way that the code has no way to regain privileges. |
I have updated the pull request to include 'group' and 'extra_groups' as separate parameters. |
I like the separate parameters. :) |
I'm curious to see what weird things the various buildbot platforms find for this one. crossing my fingers. |
Thanks for working on this. Least privilege is an important security consideration. The world will be a better place if we limit harm from bad or broken actors. |
The 'Debian root' buildbot exposed a unittest issue to deal with: https://buildbot.python.org/all/#/builders/27/builds/3702/steps/5/logs/stdio ====================================================================== Traceback (most recent call last):
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/test/test_subprocess.py", line 1761, in test_user
output = subprocess.check_output(
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 419, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 497, in run
with Popen(*popenargs, **kwargs) as process:
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 941, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 1797, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
PermissionError: [Errno 13] Permission denied: '/root/buildarea/3.x.angelico-debian-amd64/build/python' ====================================================================== Traceback (most recent call last):
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/test/test_subprocess.py", line 1761, in test_user
output = subprocess.check_output(
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 419, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 497, in run
with Popen(*popenargs, **kwargs) as process:
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 941, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 1797, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
PermissionError: [Errno 13] Permission denied: '/root/buildarea/3.x.angelico-debian-amd64/build/python' |
and some Fedora and RHEL bots are failing with: ====================================================================== Traceback (most recent call last):
File "/home/shager/cpython-buildarea/3.x.edelsohn-fedora-ppc64/build/Lib/test/test_subprocess.py", line 1840, in test_extra_groups
output = subprocess.check_output(
File "/home/shager/cpython-buildarea/3.x.edelsohn-fedora-ppc64/build/Lib/subprocess.py", line 419, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/home/shager/cpython-buildarea/3.x.edelsohn-fedora-ppc64/build/Lib/subprocess.py", line 497, in run
with Popen(*popenargs, **kwargs) as process:
File "/home/shager/cpython-buildarea/3.x.edelsohn-fedora-ppc64/build/Lib/subprocess.py", line 889, in __init__
gids.append(grp.getgrnam(extra_group).gr_gid)
KeyError: "getgrnam(): name not found: 'nogroup'" ====================================================================== Traceback (most recent call last):
File "/home/shager/cpython-buildarea/3.x.edelsohn-fedora-ppc64/build/Lib/test/test_subprocess.py", line 1800, in test_group
output = subprocess.check_output(
File "/home/shager/cpython-buildarea/3.x.edelsohn-fedora-ppc64/build/Lib/subprocess.py", line 419, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/home/shager/cpython-buildarea/3.x.edelsohn-fedora-ppc64/build/Lib/subprocess.py", line 497, in run
with Popen(*popenargs, **kwargs) as process:
File "/home/shager/cpython-buildarea/3.x.edelsohn-fedora-ppc64/build/Lib/subprocess.py", line 862, in __init__
gid = grp.getgrnam(group).gr_gid
KeyError: "getgrnam(): name not found: 'nogroup'" |
AIX has the same error as RHEL and Fedora https://buildbot.python.org/all/#/builders/161/builds/1615/steps/5/logs/stdio |
The test fails on Fedora: vstinner@apu$ ./python -m test test_subprocess -m test_extra_groups -m test_group -v Traceback (most recent call last):
File "/home/vstinner/python/master/Lib/test/test_subprocess.py", line 1840, in test_extra_groups
output = subprocess.check_output(
File "/home/vstinner/python/master/Lib/subprocess.py", line 419, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/home/vstinner/python/master/Lib/subprocess.py", line 497, in run
with Popen(*popenargs, **kwargs) as process:
File "/home/vstinner/python/master/Lib/subprocess.py", line 889, in __init__
gids.append(grp.getgrnam(extra_group).gr_gid)
KeyError: "getgrnam(): name not found: 'nogroup'" ====================================================================== Traceback (most recent call last):
File "/home/vstinner/python/master/Lib/test/test_subprocess.py", line 1800, in test_group
output = subprocess.check_output(
File "/home/vstinner/python/master/Lib/subprocess.py", line 419, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/home/vstinner/python/master/Lib/subprocess.py", line 497, in run
with Popen(*popenargs, **kwargs) as process:
File "/home/vstinner/python/master/Lib/subprocess.py", line 862, in __init__
gid = grp.getgrnam(group).gr_gid
KeyError: "getgrnam(): name not found: 'nogroup'" Ran 2 tests in 0.046s FAILED (errors=2) == Tests result: FAILURE == 1 test failed: Total duration: 175 ms |
Failures on Debian: ====================================================================== Traceback (most recent call last):
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/test/test_subprocess.py", line 1761, in test_user
output = subprocess.check_output(
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 419, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 497, in run
with Popen(*popenargs, **kwargs) as process:
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 941, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 1797, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
PermissionError: [Errno 13] Permission denied: '/root/buildarea/3.x.angelico-debian-amd64/build/python' ====================================================================== Traceback (most recent call last):
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/test/test_subprocess.py", line 1761, in test_user
output = subprocess.check_output(
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 419, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 497, in run
with Popen(*popenargs, **kwargs) as process:
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 941, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/root/buildarea/3.x.angelico-debian-amd64/build/Lib/subprocess.py", line 1797, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
PermissionError: [Errno 13] Permission denied: '/root/buildarea/3.x.angelico-debian-amd64/build/python' Ran 306 tests in 35.920s FAILED (errors=2, skipped=32) |
Hum, this change has a big (security?) issue: import subprocess
subprocess.Popen(["/usr/bin/id"], user=1000, group=1000).wait() gives: uid=1000(vstinner) gid=1000(vstinner) groupes=1000(vstinner),0(root) contexte=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 But: import subprocess
subprocess.Popen(["/usr/bin/id"], user=1000, group=1000, close_fds=False).wait() gives: uid=0(root) gid=0(root) groupes=0(root) contexte=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 => user and group arguments are ignored when using close_fds=False: when subprocess uses posix_spawn(). Note: test_subprocess test_group() is skipped on my Fedora 30. I wrote PR 16384 to fix the bug and enable test_group() on my Fedora (check also for "nobody" and "nfsnobody" groups). |
As mentioned in the code review for Victor's fix, it feels like the big conditional on the use of self._posix_spawn in Popen._execute_child should perhaps be pulled into _posix_spawn, or at least refactored so it's closer to _posix_spawn, with some explanation about what is or isn't supported by _posix_spawn and why. |
Maybe, yes. But having tests in the caller avoids to pass the very long list of arguments. Especially, it avoids to pass arguments which are not supposed by posix_spawn() :-) _execute_child() has 21 arguments... is that really reasonable? Maybe we should put these stuff into a temporary object? |
The posix_spawn() bug is now fixed, I close again the issue. Feel free to open a new issue if you want to follow-up on reworking the posix_spawn() code path ;-) |
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: