[3.12] gh-104522: Fix OSError raised when run a subprocess (GH-114195) (#114219)

gh-104522: Fix OSError raised when run a subprocess (GH-114195)

Only set filename to cwd if it was caused by failed chdir(cwd).

_fork_exec() now returns "noexec:chdir" for failed chdir(cwd).

(cherry picked from commit e2c097ebde)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Robert O'Shea <PurityLake@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2024-01-18 02:19:11 +01:00 committed by GitHub
parent 2c9cf64a3f
commit f8fc8534c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 29 additions and 18 deletions

View file

@ -1938,16 +1938,21 @@ class Popen:
SubprocessError) SubprocessError)
if issubclass(child_exception_type, OSError) and hex_errno: if issubclass(child_exception_type, OSError) and hex_errno:
errno_num = int(hex_errno, 16) errno_num = int(hex_errno, 16)
child_exec_never_called = (err_msg == "noexec") if err_msg == "noexec:chdir":
if child_exec_never_called:
err_msg = "" err_msg = ""
# The error must be from chdir(cwd). # The error must be from chdir(cwd).
err_filename = cwd err_filename = cwd
elif err_msg == "noexec":
err_msg = ""
err_filename = None
else: else:
err_filename = orig_executable err_filename = orig_executable
if errno_num != 0: if errno_num != 0:
err_msg = os.strerror(errno_num) err_msg = os.strerror(errno_num)
if err_filename is not None:
raise child_exception_type(errno_num, err_msg, err_filename) raise child_exception_type(errno_num, err_msg, err_filename)
else:
raise child_exception_type(errno_num, err_msg)
raise child_exception_type(err_msg) raise child_exception_type(err_msg)

View file

@ -2032,11 +2032,12 @@ class POSIXProcessTestCase(BaseTestCase):
"import os; print(os.getuid())"], "import os; print(os.getuid())"],
user=user, user=user,
close_fds=close_fds) close_fds=close_fds)
except PermissionError: # (EACCES, EPERM) except PermissionError as e: # (EACCES, EPERM)
pass self.assertIsNone(e.filename)
except OSError as e: except OSError as e:
if e.errno not in (errno.EACCES, errno.EPERM): if e.errno not in (errno.EACCES, errno.EPERM):
raise raise
self.assertIsNone(e.filename)
else: else:
if isinstance(user, str): if isinstance(user, str):
user_uid = pwd.getpwnam(user).pw_uid user_uid = pwd.getpwnam(user).pw_uid
@ -2080,8 +2081,8 @@ class POSIXProcessTestCase(BaseTestCase):
"import os; print(os.getgid())"], "import os; print(os.getgid())"],
group=group, group=group,
close_fds=close_fds) close_fds=close_fds)
except PermissionError: # (EACCES, EPERM) except PermissionError as e: # (EACCES, EPERM)
pass self.assertIsNone(e.filename)
else: else:
if isinstance(group, str): if isinstance(group, str):
group_gid = grp.getgrnam(group).gr_gid group_gid = grp.getgrnam(group).gr_gid
@ -2129,7 +2130,8 @@ class POSIXProcessTestCase(BaseTestCase):
[sys.executable, "-c", [sys.executable, "-c",
"import os, sys, json; json.dump(os.getgroups(), sys.stdout)"], "import os, sys, json; json.dump(os.getgroups(), sys.stdout)"],
extra_groups=group_list) extra_groups=group_list)
except PermissionError: except PermissionError as e:
self.assertIsNone(e.filename)
self.skipTest("setgroup() EPERM; this test may require root.") self.skipTest("setgroup() EPERM; this test may require root.")
else: else:
parent_groups = os.getgroups() parent_groups = os.getgroups()

View file

@ -0,0 +1,3 @@
:exc:`OSError` raised when run a subprocess now only has *filename*
attribute set to *cwd* if the error was caused by a failed attempt to change
the current directory.

View file

@ -589,9 +589,10 @@ child_exec(char *const exec_array[],
PyObject *preexec_fn, PyObject *preexec_fn,
PyObject *preexec_fn_args_tuple) PyObject *preexec_fn_args_tuple)
{ {
int i, saved_errno, reached_preexec = 0; int i, saved_errno;
PyObject *result; PyObject *result;
const char* err_msg = ""; /* Indicate to the parent that the error happened before exec(). */
const char *err_msg = "noexec";
/* Buffer large enough to hold a hex integer. We can't malloc. */ /* Buffer large enough to hold a hex integer. We can't malloc. */
char hex_errno[sizeof(saved_errno)*2+1]; char hex_errno[sizeof(saved_errno)*2+1];
@ -651,8 +652,12 @@ child_exec(char *const exec_array[],
/* We no longer manually close p2cread, c2pwrite, and errwrite here as /* We no longer manually close p2cread, c2pwrite, and errwrite here as
* _close_open_fds takes care when it is not already non-inheritable. */ * _close_open_fds takes care when it is not already non-inheritable. */
if (cwd) if (cwd) {
POSIX_CALL(chdir(cwd)); if (chdir(cwd) == -1) {
err_msg = "noexec:chdir";
goto error;
}
}
if (child_umask >= 0) if (child_umask >= 0)
umask(child_umask); /* umask() always succeeds. */ umask(child_umask); /* umask() always succeeds. */
@ -699,7 +704,7 @@ child_exec(char *const exec_array[],
#endif /* HAVE_SETREUID */ #endif /* HAVE_SETREUID */
reached_preexec = 1; err_msg = "";
if (preexec_fn != Py_None && preexec_fn_args_tuple) { if (preexec_fn != Py_None && preexec_fn_args_tuple) {
/* This is where the user has asked us to deadlock their program. */ /* This is where the user has asked us to deadlock their program. */
result = PyObject_Call(preexec_fn, preexec_fn_args_tuple, NULL); result = PyObject_Call(preexec_fn, preexec_fn_args_tuple, NULL);
@ -757,16 +762,12 @@ error:
} }
_Py_write_noraise(errpipe_write, cur, hex_errno + sizeof(hex_errno) - cur); _Py_write_noraise(errpipe_write, cur, hex_errno + sizeof(hex_errno) - cur);
_Py_write_noraise(errpipe_write, ":", 1); _Py_write_noraise(errpipe_write, ":", 1);
if (!reached_preexec) {
/* Indicate to the parent that the error happened before exec(). */
_Py_write_noraise(errpipe_write, "noexec", 6);
}
/* We can't call strerror(saved_errno). It is not async signal safe. /* We can't call strerror(saved_errno). It is not async signal safe.
* The parent process will look the error message up. */ * The parent process will look the error message up. */
} else { } else {
_Py_write_noraise(errpipe_write, "SubprocessError:0:", 18); _Py_write_noraise(errpipe_write, "SubprocessError:0:", 18);
_Py_write_noraise(errpipe_write, err_msg, strlen(err_msg));
} }
_Py_write_noraise(errpipe_write, err_msg, strlen(err_msg));
} }