diff options
author | Patrick McLean <47801044+patrick-mclean@users.noreply.github.com> | 2019-09-12 10:15:44 -0700 |
---|---|---|
committer | Gregory P. Smith <greg@krypto.org> | 2019-09-12 18:15:44 +0100 |
commit | 2b2ead74382513d0bb9ef34504e283a71e6a706f (patch) | |
tree | 28a8a0f37d31dc7a674d2690085a2dcd8a629118 /Lib/subprocess.py | |
parent | 57b7dbc46e71269d855e644d30826d33eedee2a1 (diff) | |
download | cpython-2b2ead74382513d0bb9ef34504e283a71e6a706f.tar.gz cpython-2b2ead74382513d0bb9ef34504e283a71e6a706f.zip |
bpo-36046: Add user and group parameters to subprocess (GH-11950)
* subprocess: Add user, group and extra_groups paremeters to subprocess.Popen
This adds a `user` parameter to the Popen constructor that will call
setreuid() in the child before calling exec(). This allows processes
running as root to safely drop privileges before running the subprocess
without having to use a preexec_fn.
This also adds a `group` parameter that will call setregid() in
the child process before calling exec().
Finally an `extra_groups` parameter was added that will call
setgroups() to set the supplimental groups.
Diffstat (limited to 'Lib/subprocess.py')
-rw-r--r-- | Lib/subprocess.py | 105 |
1 files changed, 100 insertions, 5 deletions
diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 85b9ea07854..85e7969c092 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -53,6 +53,14 @@ import warnings import contextlib from time import monotonic as _time +try: + import pwd +except ImportError: + pwd = None +try: + import grp +except ImportError: + grp = None __all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput", "getoutput", "check_output", "run", "CalledProcessError", "DEVNULL", @@ -719,6 +727,12 @@ class Popen(object): start_new_session (POSIX only) + group (POSIX only) + + extra_groups (POSIX only) + + user (POSIX only) + pass_fds (POSIX only) encoding and errors: Text mode encoding and error handling to use for @@ -735,7 +749,8 @@ class Popen(object): shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, - pass_fds=(), *, encoding=None, errors=None, text=None): + pass_fds=(), *, user=None, group=None, extra_groups=None, + encoding=None, errors=None, text=None): """Create new Popen instance.""" _cleanup() # Held while anything is calling waitpid before returncode has been @@ -833,6 +848,78 @@ class Popen(object): else: line_buffering = False + gid = None + if group is not None: + if not hasattr(os, 'setregid'): + raise ValueError("The 'group' parameter is not supported on the " + "current platform") + + elif isinstance(group, str): + if grp is None: + raise ValueError("The group parameter cannot be a string " + "on systems without the grp module") + + gid = grp.getgrnam(group).gr_gid + elif isinstance(group, int): + gid = group + else: + raise TypeError("Group must be a string or an integer, not {}" + .format(type(group))) + + if gid < 0: + raise ValueError(f"Group ID cannot be negative, got {gid}") + + gids = None + if extra_groups is not None: + if not hasattr(os, 'setgroups'): + raise ValueError("The 'extra_groups' parameter is not " + "supported on the current platform") + + elif isinstance(extra_groups, str): + raise ValueError("Groups must be a list, not a string") + + gids = [] + for extra_group in extra_groups: + if isinstance(extra_group, str): + if grp is None: + raise ValueError("Items in extra_groups cannot be " + "strings on systems without the " + "grp module") + + gids.append(grp.getgrnam(extra_group).gr_gid) + elif isinstance(extra_group, int): + gids.append(extra_group) + else: + raise TypeError("Items in extra_groups must be a string " + "or integer, not {}" + .format(type(extra_group))) + + # make sure that the gids are all positive here so we can do less + # checking in the C code + for gid_check in gids: + if gid_check < 0: + raise ValueError(f"Group ID cannot be negative, got {gid_check}") + + uid = None + if user is not None: + if not hasattr(os, 'setreuid'): + raise ValueError("The 'user' parameter is not supported on " + "the current platform") + + elif isinstance(user, str): + if pwd is None: + raise ValueError("The user parameter cannot be a string " + "on systems without the pwd module") + + uid = pwd.getpwnam(user).pw_uid + elif isinstance(user, int): + uid = user + else: + raise TypeError("User must be a string or an integer") + + if uid < 0: + raise ValueError(f"User ID cannot be negative, got {uid}") + try: if p2cwrite != -1: self.stdin = io.open(p2cwrite, 'wb', bufsize) @@ -857,7 +944,9 @@ class Popen(object): p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, - restore_signals, start_new_session) + restore_signals, + gid, gids, uid, + start_new_session) except: # Cleanup if the child failed starting. for f in filter(None, (self.stdin, self.stdout, self.stderr)): @@ -1227,7 +1316,9 @@ class Popen(object): p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, - unused_restore_signals, unused_start_new_session): + unused_restore_signals, + unused_gid, unused_gids, unused_uid, + unused_start_new_session): """Execute program (MS Windows version)""" assert not pass_fds, "pass_fds not supported on Windows." @@ -1553,7 +1644,9 @@ class Popen(object): p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, - restore_signals, start_new_session): + restore_signals, + gid, gids, uid, + start_new_session): """Execute program (POSIX version)""" if isinstance(args, (str, bytes)): @@ -1641,7 +1734,9 @@ class Popen(object): p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, errpipe_read, errpipe_write, - restore_signals, start_new_session, preexec_fn) + restore_signals, start_new_session, + gid, gids, uid, + preexec_fn) self._child_created = True finally: # be sure the FD is closed no matter what |