Skip to content

[3.5] bpo-30065: Fixed arguments validation in _posixsubprocess.fork_… #1190

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

Merged
merged 1 commit into from
Apr 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/multiprocessing/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ def _close_stdin():

def spawnv_passfds(path, args, passfds):
import _posixsubprocess
passfds = sorted(passfds)
passfds = tuple(sorted(map(int, passfds)))
errpipe_read, errpipe_write = os.pipe()
try:
return _posixsubprocess.fork_exec(
Expand Down
3 changes: 2 additions & 1 deletion Lib/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -1214,7 +1214,8 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
fds_to_keep.add(errpipe_write)
self.pid = _posixsubprocess.fork_exec(
args, executable_list,
close_fds, sorted(fds_to_keep), cwd, env_list,
close_fds, tuple(sorted(map(int, fds_to_keep))),
cwd, env_list,
p2cread, p2cwrite, c2pread, c2pwrite,
errread, errwrite,
errpipe_read, errpipe_write,
Expand Down
6 changes: 3 additions & 3 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ class Z(object):
def __len__(self):
return 1
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
1,Z(),3,[1, 2],5,6,7,8,9,10,11,12,13,14,15,16,17)
1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17)
# Issue #15736: overflow in _PySequence_BytesToCharpArray()
class Z(object):
def __len__(self):
return sys.maxsize
def __getitem__(self, i):
return b'x'
self.assertRaises(MemoryError, _posixsubprocess.fork_exec,
1,Z(),3,[1, 2],5,6,7,8,9,10,11,12,13,14,15,16,17)
1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17)

@unittest.skipUnless(_posixsubprocess, '_posixsubprocess required for this test.')
def test_subprocess_fork_exec(self):
Expand All @@ -114,7 +114,7 @@ def __len__(self):

# Issue #15738: crash in subprocess_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)
Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17)

@unittest.skipIf(MISSING_C_DOCSTRINGS,
"Signature information for builtins requires docstrings")
Expand Down
13 changes: 12 additions & 1 deletion Lib/test/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -2371,7 +2371,7 @@ def test_fork_exec(self):
with self.assertRaises(TypeError):
_posixsubprocess.fork_exec(
args, exe_list,
True, [], cwd, env_list,
True, (), cwd, env_list,
-1, -1, -1, -1,
1, 2, 3, 4,
True, True, func)
Expand All @@ -2383,6 +2383,16 @@ def test_fork_exec(self):
def test_fork_exec_sorted_fd_sanity_check(self):
# Issue #23564: sanity check the fork_exec() fds_to_keep sanity check.
import _posixsubprocess
class BadInt:
first = True
def __init__(self, value):
self.value = value
def __int__(self):
if self.first:
self.first = False
return self.value
raise ValueError

gc_enabled = gc.isenabled()
try:
gc.enable()
Expand All @@ -2393,6 +2403,7 @@ def test_fork_exec_sorted_fd_sanity_check(self):
(18, 23, 42, 2**63), # Out of range.
(5, 4), # Not sorted.
(6, 7, 7, 8), # Duplicate.
(BadInt(1), BadInt(2)),
):
with self.assertRaises(
ValueError,
Expand Down
43 changes: 23 additions & 20 deletions Modules/_posixsubprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,17 @@ _is_fdescfs_mounted_on_dev_fd(void)
static int
_sanity_check_python_fd_sequence(PyObject *fd_sequence)
{
Py_ssize_t seq_idx, seq_len = PySequence_Length(fd_sequence);
Py_ssize_t seq_idx;
long prev_fd = -1;
for (seq_idx = 0; seq_idx < seq_len; ++seq_idx) {
PyObject* py_fd = PySequence_Fast_GET_ITEM(fd_sequence, seq_idx);
long iter_fd = PyLong_AsLong(py_fd);
for (seq_idx = 0; seq_idx < PyTuple_GET_SIZE(fd_sequence); ++seq_idx) {
PyObject* py_fd = PyTuple_GET_ITEM(fd_sequence, seq_idx);
long iter_fd;
if (!PyLong_Check(py_fd)) {
return 1;
}
iter_fd = PyLong_AsLong(py_fd);
if (iter_fd < 0 || iter_fd <= prev_fd || iter_fd > INT_MAX) {
/* Negative, overflow, not a Long, unsorted, too big for a fd. */
/* Negative, overflow, unsorted, too big for a fd. */
return 1;
}
prev_fd = iter_fd;
Expand All @@ -133,13 +137,12 @@ _is_fd_in_sorted_fd_sequence(int fd, PyObject *fd_sequence)
{
/* Binary search. */
Py_ssize_t search_min = 0;
Py_ssize_t search_max = PySequence_Length(fd_sequence) - 1;
Py_ssize_t search_max = PyTuple_GET_SIZE(fd_sequence) - 1;
if (search_max < 0)
return 0;
do {
long middle = (search_min + search_max) / 2;
long middle_fd = PyLong_AsLong(
PySequence_Fast_GET_ITEM(fd_sequence, middle));
long middle_fd = PyLong_AsLong(PyTuple_GET_ITEM(fd_sequence, middle));
if (fd == middle_fd)
return 1;
if (fd > middle_fd)
Expand All @@ -155,9 +158,9 @@ make_inheritable(PyObject *py_fds_to_keep, int errpipe_write)
{
Py_ssize_t i, len;

len = PySequence_Length(py_fds_to_keep);
len = PyTuple_GET_SIZE(py_fds_to_keep);
for (i = 0; i < len; ++i) {
PyObject* fdobj = PySequence_Fast_GET_ITEM(py_fds_to_keep, i);
PyObject* fdobj = PyTuple_GET_ITEM(py_fds_to_keep, i);
long fd = PyLong_AsLong(fdobj);
assert(!PyErr_Occurred());
assert(0 <= fd && fd <= INT_MAX);
Expand Down Expand Up @@ -214,14 +217,13 @@ static void
_close_fds_by_brute_force(long start_fd, PyObject *py_fds_to_keep)
{
long end_fd = safe_get_max_fd();
Py_ssize_t num_fds_to_keep = PySequence_Length(py_fds_to_keep);
Py_ssize_t num_fds_to_keep = PyTuple_GET_SIZE(py_fds_to_keep);
Py_ssize_t keep_seq_idx;
int fd_num;
/* As py_fds_to_keep is sorted we can loop through the list closing
* fds inbetween any in the keep list falling within our range. */
for (keep_seq_idx = 0; keep_seq_idx < num_fds_to_keep; ++keep_seq_idx) {
PyObject* py_keep_fd = PySequence_Fast_GET_ITEM(py_fds_to_keep,
keep_seq_idx);
PyObject* py_keep_fd = PyTuple_GET_ITEM(py_fds_to_keep, keep_seq_idx);
int keep_fd = PyLong_AsLong(py_keep_fd);
if (keep_fd < start_fd)
continue;
Expand Down Expand Up @@ -307,7 +309,7 @@ _close_open_fds_safe(int start_fd, PyObject* py_fds_to_keep)


/* Close all open file descriptors from start_fd and higher.
* Do not close any in the sorted py_fds_to_keep list.
* Do not close any in the sorted py_fds_to_keep tuple.
*
* This function violates the strict use of async signal safe functions. :(
* It calls opendir(), readdir() and closedir(). Of these, the one most
Expand Down Expand Up @@ -563,8 +565,9 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
#endif

if (!PyArg_ParseTuple(
args, "OOpOOOiiiiiiiiiiO:fork_exec",
&process_args, &executable_list, &close_fds, &py_fds_to_keep,
args, "OOpO!OOiiiiiiiiiiO:fork_exec",
&process_args, &executable_list,
&close_fds, &PyTuple_Type, &py_fds_to_keep,
&cwd_obj, &env_list,
&p2cread, &p2cwrite, &c2pread, &c2pwrite,
&errread, &errwrite, &errpipe_read, &errpipe_write,
Expand All @@ -575,10 +578,6 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
PyErr_SetString(PyExc_ValueError, "errpipe_write must be >= 3");
return NULL;
}
if (PySequence_Length(py_fds_to_keep) < 0) {
PyErr_SetString(PyExc_ValueError, "cannot get length of fds_to_keep");
return NULL;
}
if (_sanity_check_python_fd_sequence(py_fds_to_keep)) {
PyErr_SetString(PyExc_ValueError, "bad value(s) in fds_to_keep");
return NULL;
Expand Down Expand Up @@ -632,6 +631,10 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
goto cleanup;
for (arg_num = 0; arg_num < num_args; ++arg_num) {
PyObject *borrowed_arg, *converted_arg;
if (PySequence_Fast_GET_SIZE(fast_args) != num_args) {
PyErr_SetString(PyExc_RuntimeError, "args changed during iteration");
goto cleanup;
}
borrowed_arg = PySequence_Fast_GET_ITEM(fast_args, arg_num);
if (PyUnicode_FSConverter(borrowed_arg, &converted_arg) == 0)
goto cleanup;
Expand Down