Skip to content

Commit f3751ef

Browse files
authored
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.
1 parent 8177404 commit f3751ef

File tree

7 files changed

+57
-19
lines changed

7 files changed

+57
-19
lines changed

Doc/library/subprocess.rst

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,9 @@ functions.
339339
stderr=None, preexec_fn=None, close_fds=True, shell=False, \
340340
cwd=None, env=None, universal_newlines=None, \
341341
startupinfo=None, creationflags=0, restore_signals=True, \
342-
start_new_session=False, pass_fds=(), *, group=None, \
343-
extra_groups=None, user=None, encoding=None, errors=None, \
344-
text=None)
342+
start_new_session=False, pass_fds=(), \*, group=None, \
343+
extra_groups=None, user=None, umask=-1, \
344+
encoding=None, errors=None, text=None)
345345

346346
Execute a child program in a new process. On POSIX, the class uses
347347
:meth:`os.execvp`-like behavior to execute the child program. On Windows,
@@ -572,6 +572,12 @@ functions.
572572
.. availability:: POSIX
573573
.. versionadded:: 3.9
574574

575+
If *umask* is not negative, the umask() system call will be made in the
576+
child process prior to the execution of the subprocess.
577+
578+
.. availability:: POSIX
579+
.. versionadded:: 3.9
580+
575581
If *env* is not ``None``, it must be a mapping that defines the environment
576582
variables for the new process; these are used instead of the default
577583
behavior of inheriting the current process' environment.

Lib/multiprocessing/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ def spawnv_passfds(path, args, passfds):
429429
return _posixsubprocess.fork_exec(
430430
args, [os.fsencode(path)], True, passfds, None, None,
431431
-1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
432-
False, False, None, None, None, None)
432+
False, False, None, None, None, -1, None)
433433
finally:
434434
os.close(errpipe_read)
435435
os.close(errpipe_write)

Lib/subprocess.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,8 @@ class Popen(object):
733733
734734
user (POSIX only)
735735
736+
umask (POSIX only)
737+
736738
pass_fds (POSIX only)
737739
738740
encoding and errors: Text mode encoding and error handling to use for
@@ -750,7 +752,7 @@ def __init__(self, args, bufsize=-1, executable=None,
750752
startupinfo=None, creationflags=0,
751753
restore_signals=True, start_new_session=False,
752754
pass_fds=(), *, user=None, group=None, extra_groups=None,
753-
encoding=None, errors=None, text=None):
755+
encoding=None, errors=None, text=None, umask=-1):
754756
"""Create new Popen instance."""
755757
_cleanup()
756758
# Held while anything is calling waitpid before returncode has been
@@ -945,7 +947,7 @@ def __init__(self, args, bufsize=-1, executable=None,
945947
c2pread, c2pwrite,
946948
errread, errwrite,
947949
restore_signals,
948-
gid, gids, uid,
950+
gid, gids, uid, umask,
949951
start_new_session)
950952
except:
951953
# Cleanup if the child failed starting.
@@ -1318,6 +1320,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
13181320
errread, errwrite,
13191321
unused_restore_signals,
13201322
unused_gid, unused_gids, unused_uid,
1323+
unused_umask,
13211324
unused_start_new_session):
13221325
"""Execute program (MS Windows version)"""
13231326

@@ -1645,7 +1648,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
16451648
c2pread, c2pwrite,
16461649
errread, errwrite,
16471650
restore_signals,
1648-
gid, gids, uid,
1651+
gid, gids, uid, umask,
16491652
start_new_session):
16501653
"""Execute program (POSIX version)"""
16511654

@@ -1684,7 +1687,8 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
16841687
and not start_new_session
16851688
and gid is None
16861689
and gids is None
1687-
and uid is None):
1690+
and uid is None
1691+
and umask < 0):
16881692
self._posix_spawn(args, executable, env, restore_signals,
16891693
p2cread, p2cwrite,
16901694
c2pread, c2pwrite,
@@ -1738,7 +1742,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
17381742
errread, errwrite,
17391743
errpipe_read, errpipe_write,
17401744
restore_signals, start_new_session,
1741-
gid, gids, uid,
1745+
gid, gids, uid, umask,
17421746
preexec_fn)
17431747
self._child_created = True
17441748
finally:

Lib/test/test_capi.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,15 @@ class Z(object):
9797
def __len__(self):
9898
return 1
9999
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
100-
1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
100+
1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
101101
# Issue #15736: overflow in _PySequence_BytesToCharpArray()
102102
class Z(object):
103103
def __len__(self):
104104
return sys.maxsize
105105
def __getitem__(self, i):
106106
return b'x'
107107
self.assertRaises(MemoryError, _posixsubprocess.fork_exec,
108-
1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
108+
1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
109109

110110
@unittest.skipUnless(_posixsubprocess, '_posixsubprocess required for this test.')
111111
def test_subprocess_fork_exec(self):
@@ -115,7 +115,7 @@ def __len__(self):
115115

116116
# Issue #15738: crash in subprocess_fork_exec()
117117
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
118-
Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
118+
Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
119119

120120
@unittest.skipIf(MISSING_C_DOCSTRINGS,
121121
"Signature information for builtins requires docstrings")

Lib/test/test_subprocess.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,6 +1894,28 @@ def test_extra_groups_error(self):
18941894
with self.assertRaises(ValueError):
18951895
subprocess.check_call([sys.executable, "-c", "pass"], extra_groups=[])
18961896

1897+
@unittest.skipIf(mswindows or not hasattr(os, 'umask'),
1898+
'POSIX umask() is not available.')
1899+
def test_umask(self):
1900+
tmpdir = None
1901+
try:
1902+
tmpdir = tempfile.mkdtemp()
1903+
name = os.path.join(tmpdir, "beans")
1904+
# We set an unusual umask in the child so as a unique mode
1905+
# for us to test the child's touched file for.
1906+
subprocess.check_call(
1907+
[sys.executable, "-c", f"open({name!r}, 'w')"], # touch
1908+
umask=0o053)
1909+
# Ignore execute permissions entirely in our test,
1910+
# filesystems could be mounted to ignore or force that.
1911+
st_mode = os.stat(name).st_mode & 0o666
1912+
expected_mode = 0o624
1913+
self.assertEqual(expected_mode, st_mode,
1914+
msg=f'{oct(expected_mode)} != {oct(st_mode)}')
1915+
finally:
1916+
if tmpdir is not None:
1917+
shutil.rmtree(tmpdir)
1918+
18971919
def test_run_abort(self):
18981920
# returncode handles signal termination
18991921
with support.SuppressCrashReport():
@@ -2950,7 +2972,7 @@ def test_fork_exec(self):
29502972
-1, -1, -1, -1,
29512973
1, 2, 3, 4,
29522974
True, True,
2953-
False, [], 0,
2975+
False, [], 0, -1,
29542976
func)
29552977
# Attempt to prevent
29562978
# "TypeError: fork_exec() takes exactly N arguments (M given)"
@@ -2999,7 +3021,7 @@ def __int__(self):
29993021
-1, -1, -1, -1,
30003022
1, 2, 3, 4,
30013023
True, True,
3002-
None, None, None,
3024+
None, None, None, -1,
30033025
None)
30043026
self.assertIn('fds_to_keep', str(c.exception))
30053027
finally:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added support for setting the umask in the child process to the subprocess
2+
module on POSIX systems.

Modules/_posixsubprocess.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#ifdef HAVE_SYS_TYPES_H
99
#include <sys/types.h>
1010
#endif
11-
#if defined(HAVE_SYS_STAT_H) && defined(__FreeBSD__)
11+
#if defined(HAVE_SYS_STAT_H)
1212
#include <sys/stat.h>
1313
#endif
1414
#ifdef HAVE_SYS_SYSCALL_H
@@ -428,7 +428,7 @@ child_exec(char *const exec_array[],
428428
int call_setsid,
429429
int call_setgid, gid_t gid,
430430
int call_setgroups, size_t groups_size, const gid_t *groups,
431-
int call_setuid, uid_t uid,
431+
int call_setuid, uid_t uid, int child_umask,
432432
PyObject *py_fds_to_keep,
433433
PyObject *preexec_fn,
434434
PyObject *preexec_fn_args_tuple)
@@ -498,6 +498,9 @@ child_exec(char *const exec_array[],
498498
if (cwd)
499499
POSIX_CALL(chdir(cwd));
500500

501+
if (child_umask >= 0)
502+
umask(child_umask); /* umask() always succeeds. */
503+
501504
if (restore_signals)
502505
_Py_RestoreSignals();
503506

@@ -609,6 +612,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
609612
int call_setgid = 0, call_setgroups = 0, call_setuid = 0;
610613
uid_t uid;
611614
gid_t gid, *groups = NULL;
615+
int child_umask;
612616
PyObject *cwd_obj, *cwd_obj2;
613617
const char *cwd;
614618
pid_t pid;
@@ -619,14 +623,14 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
619623
int saved_errno = 0;
620624

621625
if (!PyArg_ParseTuple(
622-
args, "OOpO!OOiiiiiiiiiiOOOO:fork_exec",
626+
args, "OOpO!OOiiiiiiiiiiOOOiO:fork_exec",
623627
&process_args, &executable_list,
624628
&close_fds, &PyTuple_Type, &py_fds_to_keep,
625629
&cwd_obj, &env_list,
626630
&p2cread, &p2cwrite, &c2pread, &c2pwrite,
627631
&errread, &errwrite, &errpipe_read, &errpipe_write,
628632
&restore_signals, &call_setsid,
629-
&gid_object, &groups_list, &uid_object,
633+
&gid_object, &groups_list, &uid_object, &child_umask,
630634
&preexec_fn))
631635
return NULL;
632636

@@ -843,7 +847,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
843847
errread, errwrite, errpipe_read, errpipe_write,
844848
close_fds, restore_signals, call_setsid,
845849
call_setgid, gid, call_setgroups, num_groups, groups,
846-
call_setuid, uid,
850+
call_setuid, uid, child_umask,
847851
py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
848852
_exit(255);
849853
return NULL; /* Dead code to avoid a potential compiler warning. */

0 commit comments

Comments
 (0)