Commit f3751efb authored by Gregory P. Smith's avatar Gregory P. Smith Committed by GitHub

bpo-38417: Add umask support to subprocess (GH-16726)

On POSIX systems, allow the umask to be set in the child process before we exec.
parent 8177404d
...@@ -339,9 +339,9 @@ functions. ...@@ -339,9 +339,9 @@ functions.
stderr=None, preexec_fn=None, close_fds=True, shell=False, \ stderr=None, preexec_fn=None, close_fds=True, shell=False, \
cwd=None, env=None, universal_newlines=None, \ cwd=None, env=None, universal_newlines=None, \
startupinfo=None, creationflags=0, restore_signals=True, \ startupinfo=None, creationflags=0, restore_signals=True, \
start_new_session=False, pass_fds=(), *, group=None, \ start_new_session=False, pass_fds=(), \*, group=None, \
extra_groups=None, user=None, encoding=None, errors=None, \ extra_groups=None, user=None, umask=-1, \
text=None) encoding=None, errors=None, text=None)
Execute a child program in a new process. On POSIX, the class uses Execute a child program in a new process. On POSIX, the class uses
:meth:`os.execvp`-like behavior to execute the child program. On Windows, :meth:`os.execvp`-like behavior to execute the child program. On Windows,
...@@ -572,6 +572,12 @@ functions. ...@@ -572,6 +572,12 @@ functions.
.. availability:: POSIX .. availability:: POSIX
.. versionadded:: 3.9 .. versionadded:: 3.9
If *umask* is not negative, the umask() system call will be made in the
child process prior to the execution of the subprocess.
.. availability:: POSIX
.. versionadded:: 3.9
If *env* is not ``None``, it must be a mapping that defines the environment If *env* is not ``None``, it must be a mapping that defines the environment
variables for the new process; these are used instead of the default variables for the new process; these are used instead of the default
behavior of inheriting the current process' environment. behavior of inheriting the current process' environment.
......
...@@ -429,7 +429,7 @@ def spawnv_passfds(path, args, passfds): ...@@ -429,7 +429,7 @@ def spawnv_passfds(path, args, passfds):
return _posixsubprocess.fork_exec( return _posixsubprocess.fork_exec(
args, [os.fsencode(path)], True, passfds, None, None, args, [os.fsencode(path)], True, passfds, None, None,
-1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write, -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
False, False, None, None, None, None) False, False, None, None, None, -1, None)
finally: finally:
os.close(errpipe_read) os.close(errpipe_read)
os.close(errpipe_write) os.close(errpipe_write)
......
...@@ -733,6 +733,8 @@ class Popen(object): ...@@ -733,6 +733,8 @@ class Popen(object):
user (POSIX only) user (POSIX only)
umask (POSIX only)
pass_fds (POSIX only) pass_fds (POSIX only)
encoding and errors: Text mode encoding and error handling to use for encoding and errors: Text mode encoding and error handling to use for
...@@ -750,7 +752,7 @@ class Popen(object): ...@@ -750,7 +752,7 @@ class Popen(object):
startupinfo=None, creationflags=0, startupinfo=None, creationflags=0,
restore_signals=True, start_new_session=False, restore_signals=True, start_new_session=False,
pass_fds=(), *, user=None, group=None, extra_groups=None, pass_fds=(), *, user=None, group=None, extra_groups=None,
encoding=None, errors=None, text=None): encoding=None, errors=None, text=None, umask=-1):
"""Create new Popen instance.""" """Create new Popen instance."""
_cleanup() _cleanup()
# Held while anything is calling waitpid before returncode has been # Held while anything is calling waitpid before returncode has been
...@@ -945,7 +947,7 @@ class Popen(object): ...@@ -945,7 +947,7 @@ class Popen(object):
c2pread, c2pwrite, c2pread, c2pwrite,
errread, errwrite, errread, errwrite,
restore_signals, restore_signals,
gid, gids, uid, gid, gids, uid, umask,
start_new_session) start_new_session)
except: except:
# Cleanup if the child failed starting. # Cleanup if the child failed starting.
...@@ -1318,6 +1320,7 @@ class Popen(object): ...@@ -1318,6 +1320,7 @@ class Popen(object):
errread, errwrite, errread, errwrite,
unused_restore_signals, unused_restore_signals,
unused_gid, unused_gids, unused_uid, unused_gid, unused_gids, unused_uid,
unused_umask,
unused_start_new_session): unused_start_new_session):
"""Execute program (MS Windows version)""" """Execute program (MS Windows version)"""
...@@ -1645,7 +1648,7 @@ class Popen(object): ...@@ -1645,7 +1648,7 @@ class Popen(object):
c2pread, c2pwrite, c2pread, c2pwrite,
errread, errwrite, errread, errwrite,
restore_signals, restore_signals,
gid, gids, uid, gid, gids, uid, umask,
start_new_session): start_new_session):
"""Execute program (POSIX version)""" """Execute program (POSIX version)"""
...@@ -1684,7 +1687,8 @@ class Popen(object): ...@@ -1684,7 +1687,8 @@ class Popen(object):
and not start_new_session and not start_new_session
and gid is None and gid is None
and gids is None and gids is None
and uid is None): and uid is None
and umask < 0):
self._posix_spawn(args, executable, env, restore_signals, self._posix_spawn(args, executable, env, restore_signals,
p2cread, p2cwrite, p2cread, p2cwrite,
c2pread, c2pwrite, c2pread, c2pwrite,
...@@ -1738,7 +1742,7 @@ class Popen(object): ...@@ -1738,7 +1742,7 @@ class Popen(object):
errread, errwrite, errread, errwrite,
errpipe_read, errpipe_write, errpipe_read, errpipe_write,
restore_signals, start_new_session, restore_signals, start_new_session,
gid, gids, uid, gid, gids, uid, umask,
preexec_fn) preexec_fn)
self._child_created = True self._child_created = True
finally: finally:
......
...@@ -97,7 +97,7 @@ class CAPITest(unittest.TestCase): ...@@ -97,7 +97,7 @@ class CAPITest(unittest.TestCase):
def __len__(self): def __len__(self):
return 1 return 1
self.assertRaises(TypeError, _posixsubprocess.fork_exec, self.assertRaises(TypeError, _posixsubprocess.fork_exec,
1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
# Issue #15736: overflow in _PySequence_BytesToCharpArray() # Issue #15736: overflow in _PySequence_BytesToCharpArray()
class Z(object): class Z(object):
def __len__(self): def __len__(self):
...@@ -105,7 +105,7 @@ class CAPITest(unittest.TestCase): ...@@ -105,7 +105,7 @@ class CAPITest(unittest.TestCase):
def __getitem__(self, i): def __getitem__(self, i):
return b'x' return b'x'
self.assertRaises(MemoryError, _posixsubprocess.fork_exec, self.assertRaises(MemoryError, _posixsubprocess.fork_exec,
1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
@unittest.skipUnless(_posixsubprocess, '_posixsubprocess required for this test.') @unittest.skipUnless(_posixsubprocess, '_posixsubprocess required for this test.')
def test_subprocess_fork_exec(self): def test_subprocess_fork_exec(self):
...@@ -115,7 +115,7 @@ class CAPITest(unittest.TestCase): ...@@ -115,7 +115,7 @@ class CAPITest(unittest.TestCase):
# Issue #15738: crash in subprocess_fork_exec() # Issue #15738: crash in subprocess_fork_exec()
self.assertRaises(TypeError, _posixsubprocess.fork_exec, self.assertRaises(TypeError, _posixsubprocess.fork_exec,
Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
@unittest.skipIf(MISSING_C_DOCSTRINGS, @unittest.skipIf(MISSING_C_DOCSTRINGS,
"Signature information for builtins requires docstrings") "Signature information for builtins requires docstrings")
......
...@@ -1894,6 +1894,28 @@ class POSIXProcessTestCase(BaseTestCase): ...@@ -1894,6 +1894,28 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
subprocess.check_call([sys.executable, "-c", "pass"], extra_groups=[]) subprocess.check_call([sys.executable, "-c", "pass"], extra_groups=[])
@unittest.skipIf(mswindows or not hasattr(os, 'umask'),
'POSIX umask() is not available.')
def test_umask(self):
tmpdir = None
try:
tmpdir = tempfile.mkdtemp()
name = os.path.join(tmpdir, "beans")
# We set an unusual umask in the child so as a unique mode
# for us to test the child's touched file for.
subprocess.check_call(
[sys.executable, "-c", f"open({name!r}, 'w')"], # touch
umask=0o053)
# Ignore execute permissions entirely in our test,
# filesystems could be mounted to ignore or force that.
st_mode = os.stat(name).st_mode & 0o666
expected_mode = 0o624
self.assertEqual(expected_mode, st_mode,
msg=f'{oct(expected_mode)} != {oct(st_mode)}')
finally:
if tmpdir is not None:
shutil.rmtree(tmpdir)
def test_run_abort(self): def test_run_abort(self):
# returncode handles signal termination # returncode handles signal termination
with support.SuppressCrashReport(): with support.SuppressCrashReport():
...@@ -2950,7 +2972,7 @@ class POSIXProcessTestCase(BaseTestCase): ...@@ -2950,7 +2972,7 @@ class POSIXProcessTestCase(BaseTestCase):
-1, -1, -1, -1, -1, -1, -1, -1,
1, 2, 3, 4, 1, 2, 3, 4,
True, True, True, True,
False, [], 0, False, [], 0, -1,
func) func)
# Attempt to prevent # Attempt to prevent
# "TypeError: fork_exec() takes exactly N arguments (M given)" # "TypeError: fork_exec() takes exactly N arguments (M given)"
...@@ -2999,7 +3021,7 @@ class POSIXProcessTestCase(BaseTestCase): ...@@ -2999,7 +3021,7 @@ class POSIXProcessTestCase(BaseTestCase):
-1, -1, -1, -1, -1, -1, -1, -1,
1, 2, 3, 4, 1, 2, 3, 4,
True, True, True, True,
None, None, None, None, None, None, -1,
None) None)
self.assertIn('fds_to_keep', str(c.exception)) self.assertIn('fds_to_keep', str(c.exception))
finally: finally:
......
Added support for setting the umask in the child process to the subprocess
module on POSIX systems.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#ifdef HAVE_SYS_TYPES_H #ifdef HAVE_SYS_TYPES_H
#include <sys/types.h> #include <sys/types.h>
#endif #endif
#if defined(HAVE_SYS_STAT_H) && defined(__FreeBSD__) #if defined(HAVE_SYS_STAT_H)
#include <sys/stat.h> #include <sys/stat.h>
#endif #endif
#ifdef HAVE_SYS_SYSCALL_H #ifdef HAVE_SYS_SYSCALL_H
...@@ -428,7 +428,7 @@ child_exec(char *const exec_array[], ...@@ -428,7 +428,7 @@ child_exec(char *const exec_array[],
int call_setsid, int call_setsid,
int call_setgid, gid_t gid, int call_setgid, gid_t gid,
int call_setgroups, size_t groups_size, const gid_t *groups, int call_setgroups, size_t groups_size, const gid_t *groups,
int call_setuid, uid_t uid, int call_setuid, uid_t uid, int child_umask,
PyObject *py_fds_to_keep, PyObject *py_fds_to_keep,
PyObject *preexec_fn, PyObject *preexec_fn,
PyObject *preexec_fn_args_tuple) PyObject *preexec_fn_args_tuple)
...@@ -498,6 +498,9 @@ child_exec(char *const exec_array[], ...@@ -498,6 +498,9 @@ child_exec(char *const exec_array[],
if (cwd) if (cwd)
POSIX_CALL(chdir(cwd)); POSIX_CALL(chdir(cwd));
if (child_umask >= 0)
umask(child_umask); /* umask() always succeeds. */
if (restore_signals) if (restore_signals)
_Py_RestoreSignals(); _Py_RestoreSignals();
...@@ -609,6 +612,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args) ...@@ -609,6 +612,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
int call_setgid = 0, call_setgroups = 0, call_setuid = 0; int call_setgid = 0, call_setgroups = 0, call_setuid = 0;
uid_t uid; uid_t uid;
gid_t gid, *groups = NULL; gid_t gid, *groups = NULL;
int child_umask;
PyObject *cwd_obj, *cwd_obj2; PyObject *cwd_obj, *cwd_obj2;
const char *cwd; const char *cwd;
pid_t pid; pid_t pid;
...@@ -619,14 +623,14 @@ subprocess_fork_exec(PyObject* self, PyObject *args) ...@@ -619,14 +623,14 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
int saved_errno = 0; int saved_errno = 0;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "OOpO!OOiiiiiiiiiiOOOO:fork_exec", args, "OOpO!OOiiiiiiiiiiOOOiO:fork_exec",
&process_args, &executable_list, &process_args, &executable_list,
&close_fds, &PyTuple_Type, &py_fds_to_keep, &close_fds, &PyTuple_Type, &py_fds_to_keep,
&cwd_obj, &env_list, &cwd_obj, &env_list,
&p2cread, &p2cwrite, &c2pread, &c2pwrite, &p2cread, &p2cwrite, &c2pread, &c2pwrite,
&errread, &errwrite, &errpipe_read, &errpipe_write, &errread, &errwrite, &errpipe_read, &errpipe_write,
&restore_signals, &call_setsid, &restore_signals, &call_setsid,
&gid_object, &groups_list, &uid_object, &gid_object, &groups_list, &uid_object, &child_umask,
&preexec_fn)) &preexec_fn))
return NULL; return NULL;
...@@ -843,7 +847,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args) ...@@ -843,7 +847,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
errread, errwrite, errpipe_read, errpipe_write, errread, errwrite, errpipe_read, errpipe_write,
close_fds, restore_signals, call_setsid, close_fds, restore_signals, call_setsid,
call_setgid, gid, call_setgroups, num_groups, groups, call_setgid, gid, call_setgroups, num_groups, groups,
call_setuid, uid, call_setuid, uid, child_umask,
py_fds_to_keep, preexec_fn, preexec_fn_args_tuple); py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
_exit(255); _exit(255);
return NULL; /* Dead code to avoid a potential compiler warning. */ return NULL; /* Dead code to avoid a potential compiler warning. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment