Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f0a7a6c

Browse filesBrowse files
[3.14] gh-133886: Fix sys.remote_exec() for non-UTF-8 paths (GH-133887) (GH-133963)
It now supports non-ASCII paths in non-UTF-8 locales and non-UTF-8 paths in UTF-8 locales. (cherry picked from commit c09cec5) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 198dc8d commit f0a7a6c
Copy full SHA for f0a7a6c

File tree

4 files changed

+97
-71
lines changed
Filter options

4 files changed

+97
-71
lines changed

‎Lib/test/test_sys.py

Copy file name to clipboardExpand all lines: Lib/test/test_sys.py
+27-8Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,12 +1976,13 @@ class TestRemoteExec(unittest.TestCase):
19761976
def tearDown(self):
19771977
test.support.reap_children()
19781978

1979-
def _run_remote_exec_test(self, script_code, python_args=None, env=None, prologue=''):
1979+
def _run_remote_exec_test(self, script_code, python_args=None, env=None,
1980+
prologue='',
1981+
script_path=os_helper.TESTFN + '_remote.py'):
19801982
# Create the script that will be remotely executed
1981-
script = os_helper.TESTFN + '_remote.py'
1982-
self.addCleanup(os_helper.unlink, script)
1983+
self.addCleanup(os_helper.unlink, script_path)
19831984

1984-
with open(script, 'w') as f:
1985+
with open(script_path, 'w') as f:
19851986
f.write(script_code)
19861987

19871988
# Create and run the target process
@@ -2050,7 +2051,7 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None, prologu
20502051
self.assertEqual(response, b"ready")
20512052

20522053
# Try remote exec on the target process
2053-
sys.remote_exec(proc.pid, script)
2054+
sys.remote_exec(proc.pid, script_path)
20542055

20552056
# Signal script to continue
20562057
client_socket.sendall(b"continue")
@@ -2073,14 +2074,32 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None, prologu
20732074

20742075
def test_remote_exec(self):
20752076
"""Test basic remote exec functionality"""
2076-
script = '''
2077-
print("Remote script executed successfully!")
2078-
'''
2077+
script = 'print("Remote script executed successfully!")'
20792078
returncode, stdout, stderr = self._run_remote_exec_test(script)
20802079
# self.assertEqual(returncode, 0)
20812080
self.assertIn(b"Remote script executed successfully!", stdout)
20822081
self.assertEqual(stderr, b"")
20832082

2083+
def test_remote_exec_bytes(self):
2084+
script = 'print("Remote script executed successfully!")'
2085+
script_path = os.fsencode(os_helper.TESTFN) + b'_bytes_remote.py'
2086+
returncode, stdout, stderr = self._run_remote_exec_test(script,
2087+
script_path=script_path)
2088+
self.assertIn(b"Remote script executed successfully!", stdout)
2089+
self.assertEqual(stderr, b"")
2090+
2091+
@unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, 'requires undecodable path')
2092+
@unittest.skipIf(sys.platform == 'darwin',
2093+
'undecodable paths are not supported on macOS')
2094+
def test_remote_exec_undecodable(self):
2095+
script = 'print("Remote script executed successfully!")'
2096+
script_path = os_helper.TESTFN_UNDECODABLE + b'_undecodable_remote.py'
2097+
for script_path in [script_path, os.fsdecode(script_path)]:
2098+
returncode, stdout, stderr = self._run_remote_exec_test(script,
2099+
script_path=script_path)
2100+
self.assertIn(b"Remote script executed successfully!", stdout)
2101+
self.assertEqual(stderr, b"")
2102+
20842103
def test_remote_exec_with_self_process(self):
20852104
"""Test remote exec with the target process being the same as the test process"""
20862105

+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :func:`sys.remote_exec` for non-ASCII paths in non-UTF-8 locales and
2+
non-UTF-8 paths in UTF-8 locales.

‎Python/ceval_gil.c

Copy file name to clipboardExpand all lines: Python/ceval_gil.c
+16-9Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,38 +1218,38 @@ static inline int run_remote_debugger_source(PyObject *source)
12181218

12191219
// Note that this function is inline to avoid creating a PLT entry
12201220
// that would be an easy target for a ROP gadget.
1221-
static inline void run_remote_debugger_script(const char *path)
1221+
static inline void run_remote_debugger_script(PyObject *path)
12221222
{
1223-
if (0 != PySys_Audit("remote_debugger_script", "s", path)) {
1223+
if (0 != PySys_Audit("remote_debugger_script", "O", path)) {
12241224
PyErr_FormatUnraisable(
1225-
"Audit hook failed for remote debugger script %s", path);
1225+
"Audit hook failed for remote debugger script %U", path);
12261226
return;
12271227
}
12281228

12291229
// Open the debugger script with the open code hook, and reopen the
12301230
// resulting file object to get a C FILE* object.
1231-
PyObject* fileobj = PyFile_OpenCode(path);
1231+
PyObject* fileobj = PyFile_OpenCodeObject(path);
12321232
if (!fileobj) {
1233-
PyErr_FormatUnraisable("Can't open debugger script %s", path);
1233+
PyErr_FormatUnraisable("Can't open debugger script %U", path);
12341234
return;
12351235
}
12361236

12371237
PyObject* source = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(read));
12381238
if (!source) {
1239-
PyErr_FormatUnraisable("Error reading debugger script %s", path);
1239+
PyErr_FormatUnraisable("Error reading debugger script %U", path);
12401240
}
12411241

12421242
PyObject* res = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(close));
12431243
if (!res) {
1244-
PyErr_FormatUnraisable("Error closing debugger script %s", path);
1244+
PyErr_FormatUnraisable("Error closing debugger script %U", path);
12451245
} else {
12461246
Py_DECREF(res);
12471247
}
12481248
Py_DECREF(fileobj);
12491249

12501250
if (source) {
12511251
if (0 != run_remote_debugger_source(source)) {
1252-
PyErr_FormatUnraisable("Error executing debugger script %s", path);
1252+
PyErr_FormatUnraisable("Error executing debugger script %U", path);
12531253
}
12541254
Py_DECREF(source);
12551255
}
@@ -1278,7 +1278,14 @@ int _PyRunRemoteDebugger(PyThreadState *tstate)
12781278
pathsz);
12791279
path[pathsz - 1] = '\0';
12801280
if (*path) {
1281-
run_remote_debugger_script(path);
1281+
PyObject *path_obj = PyUnicode_DecodeFSDefault(path);
1282+
if (path_obj == NULL) {
1283+
PyErr_FormatUnraisable("Can't decode debugger script");
1284+
}
1285+
else {
1286+
run_remote_debugger_script(path_obj);
1287+
Py_DECREF(path_obj);
1288+
}
12821289
}
12831290
PyMem_Free(path);
12841291
}

‎Python/sysmodule.c

Copy file name to clipboardExpand all lines: Python/sysmodule.c
+52-54Lines changed: 52 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,38 +2451,71 @@ sys_is_remote_debug_enabled_impl(PyObject *module)
24512451
#endif
24522452
}
24532453

2454+
/*[clinic input]
2455+
sys.remote_exec
2456+
2457+
pid: int
2458+
script: object
2459+
2460+
Executes a file containing Python code in a given remote Python process.
2461+
2462+
This function returns immediately, and the code will be executed by the
2463+
target process's main thread at the next available opportunity, similarly
2464+
to how signals are handled. There is no interface to determine when the
2465+
code has been executed. The caller is responsible for making sure that
2466+
the file still exists whenever the remote process tries to read it and that
2467+
it hasn't been overwritten.
2468+
2469+
The remote process must be running a CPython interpreter of the same major
2470+
and minor version as the local process. If either the local or remote
2471+
interpreter is pre-release (alpha, beta, or release candidate) then the
2472+
local and remote interpreters must be the same exact version.
2473+
2474+
Args:
2475+
pid (int): The process ID of the target Python process.
2476+
script (str|bytes): The path to a file containing
2477+
the Python code to be executed.
2478+
[clinic start generated code]*/
2479+
24542480
static PyObject *
2455-
sys_remote_exec_unicode_path(PyObject *module, int pid, PyObject *script)
2481+
sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
2482+
/*[clinic end generated code: output=7d94c56afe4a52c0 input=39908ca2c5fe1eb0]*/
24562483
{
2457-
const char *debugger_script_path = PyUnicode_AsUTF8(script);
2458-
if (debugger_script_path == NULL) {
2484+
PyObject *path;
2485+
const char *debugger_script_path;
2486+
2487+
if (PyUnicode_FSConverter(script, &path) < 0) {
24592488
return NULL;
24602489
}
2461-
2490+
debugger_script_path = PyBytes_AS_STRING(path);
24622491
#ifdef MS_WINDOWS
2492+
PyObject *unicode_path;
2493+
if (PyUnicode_FSDecoder(path, &unicode_path) < 0) {
2494+
goto error;
2495+
}
24632496
// Use UTF-16 (wide char) version of the path for permission checks
2464-
wchar_t *debugger_script_path_w = PyUnicode_AsWideCharString(script, NULL);
2497+
wchar_t *debugger_script_path_w = PyUnicode_AsWideCharString(unicode_path, NULL);
2498+
Py_DECREF(unicode_path);
24652499
if (debugger_script_path_w == NULL) {
2466-
return NULL;
2500+
goto error;
24672501
}
2468-
2469-
// Check file attributes using wide character version (W) instead of ANSI (A)
24702502
DWORD attr = GetFileAttributesW(debugger_script_path_w);
2471-
PyMem_Free(debugger_script_path_w);
24722503
if (attr == INVALID_FILE_ATTRIBUTES) {
24732504
DWORD err = GetLastError();
2505+
PyMem_Free(debugger_script_path_w);
24742506
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
24752507
PyErr_SetString(PyExc_FileNotFoundError, "Script file does not exist");
24762508
}
24772509
else if (err == ERROR_ACCESS_DENIED) {
24782510
PyErr_SetString(PyExc_PermissionError, "Script file cannot be read");
24792511
}
24802512
else {
2481-
PyErr_SetFromWindowsErr(0);
2513+
PyErr_SetFromWindowsErr(err);
24822514
}
2483-
return NULL;
2515+
goto error;
24842516
}
2485-
#else
2517+
PyMem_Free(debugger_script_path_w);
2518+
#else // MS_WINDOWS
24862519
if (access(debugger_script_path, F_OK | R_OK) != 0) {
24872520
switch (errno) {
24882521
case ENOENT:
@@ -2494,54 +2527,19 @@ sys_remote_exec_unicode_path(PyObject *module, int pid, PyObject *script)
24942527
default:
24952528
PyErr_SetFromErrno(PyExc_OSError);
24962529
}
2497-
return NULL;
2530+
goto error;
24982531
}
2499-
#endif
2500-
2532+
#endif // MS_WINDOWS
25012533
if (_PySysRemoteDebug_SendExec(pid, 0, debugger_script_path) < 0) {
2502-
return NULL;
2534+
goto error;
25032535
}
25042536

2537+
Py_DECREF(path);
25052538
Py_RETURN_NONE;
2506-
}
2507-
2508-
/*[clinic input]
2509-
sys.remote_exec
2510-
2511-
pid: int
2512-
script: object
2513-
2514-
Executes a file containing Python code in a given remote Python process.
2515-
2516-
This function returns immediately, and the code will be executed by the
2517-
target process's main thread at the next available opportunity, similarly
2518-
to how signals are handled. There is no interface to determine when the
2519-
code has been executed. The caller is responsible for making sure that
2520-
the file still exists whenever the remote process tries to read it and that
2521-
it hasn't been overwritten.
25222539

2523-
The remote process must be running a CPython interpreter of the same major
2524-
and minor version as the local process. If either the local or remote
2525-
interpreter is pre-release (alpha, beta, or release candidate) then the
2526-
local and remote interpreters must be the same exact version.
2527-
2528-
Args:
2529-
pid (int): The process ID of the target Python process.
2530-
script (str|bytes): The path to a file containing
2531-
the Python code to be executed.
2532-
[clinic start generated code]*/
2533-
2534-
static PyObject *
2535-
sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
2536-
/*[clinic end generated code: output=7d94c56afe4a52c0 input=39908ca2c5fe1eb0]*/
2537-
{
2538-
PyObject *ret = NULL;
2539-
PyObject *path;
2540-
if (PyUnicode_FSDecoder(script, &path)) {
2541-
ret = sys_remote_exec_unicode_path(module, pid, path);
2542-
Py_DECREF(path);
2543-
}
2544-
return ret;
2540+
error:
2541+
Py_DECREF(path);
2542+
return NULL;
25452543
}
25462544

25472545

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.