gh-133886: Fix sys.remote_exec() for non-UTF-8 paths (GH-133887)
It now supports non-ASCII paths in non-UTF-8 locales and non-UTF-8 paths in UTF-8 locales.
This commit is contained in:
parent
8cf4947b0f
commit
c09cec5d69
@ -1976,12 +1976,13 @@ class TestRemoteExec(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
test.support.reap_children()
|
test.support.reap_children()
|
||||||
|
|
||||||
def _run_remote_exec_test(self, script_code, python_args=None, env=None, prologue=''):
|
def _run_remote_exec_test(self, script_code, python_args=None, env=None,
|
||||||
|
prologue='',
|
||||||
|
script_path=os_helper.TESTFN + '_remote.py'):
|
||||||
# Create the script that will be remotely executed
|
# Create the script that will be remotely executed
|
||||||
script = os_helper.TESTFN + '_remote.py'
|
self.addCleanup(os_helper.unlink, script_path)
|
||||||
self.addCleanup(os_helper.unlink, script)
|
|
||||||
|
|
||||||
with open(script, 'w') as f:
|
with open(script_path, 'w') as f:
|
||||||
f.write(script_code)
|
f.write(script_code)
|
||||||
|
|
||||||
# Create and run the target process
|
# Create and run the target process
|
||||||
@ -2050,7 +2051,7 @@ sock.close()
|
|||||||
self.assertEqual(response, b"ready")
|
self.assertEqual(response, b"ready")
|
||||||
|
|
||||||
# Try remote exec on the target process
|
# Try remote exec on the target process
|
||||||
sys.remote_exec(proc.pid, script)
|
sys.remote_exec(proc.pid, script_path)
|
||||||
|
|
||||||
# Signal script to continue
|
# Signal script to continue
|
||||||
client_socket.sendall(b"continue")
|
client_socket.sendall(b"continue")
|
||||||
@ -2073,14 +2074,32 @@ sock.close()
|
|||||||
|
|
||||||
def test_remote_exec(self):
|
def test_remote_exec(self):
|
||||||
"""Test basic remote exec functionality"""
|
"""Test basic remote exec functionality"""
|
||||||
script = '''
|
script = 'print("Remote script executed successfully!")'
|
||||||
print("Remote script executed successfully!")
|
|
||||||
'''
|
|
||||||
returncode, stdout, stderr = self._run_remote_exec_test(script)
|
returncode, stdout, stderr = self._run_remote_exec_test(script)
|
||||||
# self.assertEqual(returncode, 0)
|
# self.assertEqual(returncode, 0)
|
||||||
self.assertIn(b"Remote script executed successfully!", stdout)
|
self.assertIn(b"Remote script executed successfully!", stdout)
|
||||||
self.assertEqual(stderr, b"")
|
self.assertEqual(stderr, b"")
|
||||||
|
|
||||||
|
def test_remote_exec_bytes(self):
|
||||||
|
script = 'print("Remote script executed successfully!")'
|
||||||
|
script_path = os.fsencode(os_helper.TESTFN) + b'_bytes_remote.py'
|
||||||
|
returncode, stdout, stderr = self._run_remote_exec_test(script,
|
||||||
|
script_path=script_path)
|
||||||
|
self.assertIn(b"Remote script executed successfully!", stdout)
|
||||||
|
self.assertEqual(stderr, b"")
|
||||||
|
|
||||||
|
@unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, 'requires undecodable path')
|
||||||
|
@unittest.skipIf(sys.platform == 'darwin',
|
||||||
|
'undecodable paths are not supported on macOS')
|
||||||
|
def test_remote_exec_undecodable(self):
|
||||||
|
script = 'print("Remote script executed successfully!")'
|
||||||
|
script_path = os_helper.TESTFN_UNDECODABLE + b'_undecodable_remote.py'
|
||||||
|
for script_path in [script_path, os.fsdecode(script_path)]:
|
||||||
|
returncode, stdout, stderr = self._run_remote_exec_test(script,
|
||||||
|
script_path=script_path)
|
||||||
|
self.assertIn(b"Remote script executed successfully!", stdout)
|
||||||
|
self.assertEqual(stderr, b"")
|
||||||
|
|
||||||
def test_remote_exec_with_self_process(self):
|
def test_remote_exec_with_self_process(self):
|
||||||
"""Test remote exec with the target process being the same as the test process"""
|
"""Test remote exec with the target process being the same as the test process"""
|
||||||
|
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
Fix :func:`sys.remote_exec` for non-ASCII paths in non-UTF-8 locales and
|
||||||
|
non-UTF-8 paths in UTF-8 locales.
|
@ -1218,30 +1218,30 @@ static inline int run_remote_debugger_source(PyObject *source)
|
|||||||
|
|
||||||
// Note that this function is inline to avoid creating a PLT entry
|
// Note that this function is inline to avoid creating a PLT entry
|
||||||
// that would be an easy target for a ROP gadget.
|
// that would be an easy target for a ROP gadget.
|
||||||
static inline void run_remote_debugger_script(const char *path)
|
static inline void run_remote_debugger_script(PyObject *path)
|
||||||
{
|
{
|
||||||
if (0 != PySys_Audit("remote_debugger_script", "s", path)) {
|
if (0 != PySys_Audit("remote_debugger_script", "O", path)) {
|
||||||
PyErr_FormatUnraisable(
|
PyErr_FormatUnraisable(
|
||||||
"Audit hook failed for remote debugger script %s", path);
|
"Audit hook failed for remote debugger script %U", path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the debugger script with the open code hook, and reopen the
|
// Open the debugger script with the open code hook, and reopen the
|
||||||
// resulting file object to get a C FILE* object.
|
// resulting file object to get a C FILE* object.
|
||||||
PyObject* fileobj = PyFile_OpenCode(path);
|
PyObject* fileobj = PyFile_OpenCodeObject(path);
|
||||||
if (!fileobj) {
|
if (!fileobj) {
|
||||||
PyErr_FormatUnraisable("Can't open debugger script %s", path);
|
PyErr_FormatUnraisable("Can't open debugger script %U", path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* source = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(read));
|
PyObject* source = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(read));
|
||||||
if (!source) {
|
if (!source) {
|
||||||
PyErr_FormatUnraisable("Error reading debugger script %s", path);
|
PyErr_FormatUnraisable("Error reading debugger script %U", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* res = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(close));
|
PyObject* res = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(close));
|
||||||
if (!res) {
|
if (!res) {
|
||||||
PyErr_FormatUnraisable("Error closing debugger script %s", path);
|
PyErr_FormatUnraisable("Error closing debugger script %U", path);
|
||||||
} else {
|
} else {
|
||||||
Py_DECREF(res);
|
Py_DECREF(res);
|
||||||
}
|
}
|
||||||
@ -1249,7 +1249,7 @@ static inline void run_remote_debugger_script(const char *path)
|
|||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
if (0 != run_remote_debugger_source(source)) {
|
if (0 != run_remote_debugger_source(source)) {
|
||||||
PyErr_FormatUnraisable("Error executing debugger script %s", path);
|
PyErr_FormatUnraisable("Error executing debugger script %U", path);
|
||||||
}
|
}
|
||||||
Py_DECREF(source);
|
Py_DECREF(source);
|
||||||
}
|
}
|
||||||
@ -1278,7 +1278,14 @@ int _PyRunRemoteDebugger(PyThreadState *tstate)
|
|||||||
pathsz);
|
pathsz);
|
||||||
path[pathsz - 1] = '\0';
|
path[pathsz - 1] = '\0';
|
||||||
if (*path) {
|
if (*path) {
|
||||||
run_remote_debugger_script(path);
|
PyObject *path_obj = PyUnicode_DecodeFSDefault(path);
|
||||||
|
if (path_obj == NULL) {
|
||||||
|
PyErr_FormatUnraisable("Can't decode debugger script");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
run_remote_debugger_script(path_obj);
|
||||||
|
Py_DECREF(path_obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PyMem_Free(path);
|
PyMem_Free(path);
|
||||||
}
|
}
|
||||||
|
@ -2451,60 +2451,6 @@ sys_is_remote_debug_enabled_impl(PyObject *module)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
sys_remote_exec_unicode_path(PyObject *module, int pid, PyObject *script)
|
|
||||||
{
|
|
||||||
const char *debugger_script_path = PyUnicode_AsUTF8(script);
|
|
||||||
if (debugger_script_path == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef MS_WINDOWS
|
|
||||||
// Use UTF-16 (wide char) version of the path for permission checks
|
|
||||||
wchar_t *debugger_script_path_w = PyUnicode_AsWideCharString(script, NULL);
|
|
||||||
if (debugger_script_path_w == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check file attributes using wide character version (W) instead of ANSI (A)
|
|
||||||
DWORD attr = GetFileAttributesW(debugger_script_path_w);
|
|
||||||
PyMem_Free(debugger_script_path_w);
|
|
||||||
if (attr == INVALID_FILE_ATTRIBUTES) {
|
|
||||||
DWORD err = GetLastError();
|
|
||||||
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
|
|
||||||
PyErr_SetString(PyExc_FileNotFoundError, "Script file does not exist");
|
|
||||||
}
|
|
||||||
else if (err == ERROR_ACCESS_DENIED) {
|
|
||||||
PyErr_SetString(PyExc_PermissionError, "Script file cannot be read");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
PyErr_SetFromWindowsErr(0);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (access(debugger_script_path, F_OK | R_OK) != 0) {
|
|
||||||
switch (errno) {
|
|
||||||
case ENOENT:
|
|
||||||
PyErr_SetString(PyExc_FileNotFoundError, "Script file does not exist");
|
|
||||||
break;
|
|
||||||
case EACCES:
|
|
||||||
PyErr_SetString(PyExc_PermissionError, "Script file cannot be read");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
PyErr_SetFromErrno(PyExc_OSError);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (_PySysRemoteDebug_SendExec(pid, 0, debugger_script_path) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
sys.remote_exec
|
sys.remote_exec
|
||||||
|
|
||||||
@ -2535,13 +2481,65 @@ static PyObject *
|
|||||||
sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
|
sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
|
||||||
/*[clinic end generated code: output=7d94c56afe4a52c0 input=39908ca2c5fe1eb0]*/
|
/*[clinic end generated code: output=7d94c56afe4a52c0 input=39908ca2c5fe1eb0]*/
|
||||||
{
|
{
|
||||||
PyObject *ret = NULL;
|
|
||||||
PyObject *path;
|
PyObject *path;
|
||||||
if (PyUnicode_FSDecoder(script, &path)) {
|
const char *debugger_script_path;
|
||||||
ret = sys_remote_exec_unicode_path(module, pid, path);
|
|
||||||
Py_DECREF(path);
|
if (PyUnicode_FSConverter(script, &path) < 0) {
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
return ret;
|
debugger_script_path = PyBytes_AS_STRING(path);
|
||||||
|
#ifdef MS_WINDOWS
|
||||||
|
PyObject *unicode_path;
|
||||||
|
if (PyUnicode_FSDecoder(path, &unicode_path) < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
// Use UTF-16 (wide char) version of the path for permission checks
|
||||||
|
wchar_t *debugger_script_path_w = PyUnicode_AsWideCharString(unicode_path, NULL);
|
||||||
|
Py_DECREF(unicode_path);
|
||||||
|
if (debugger_script_path_w == NULL) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
DWORD attr = GetFileAttributesW(debugger_script_path_w);
|
||||||
|
if (attr == INVALID_FILE_ATTRIBUTES) {
|
||||||
|
DWORD err = GetLastError();
|
||||||
|
PyMem_Free(debugger_script_path_w);
|
||||||
|
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
|
||||||
|
PyErr_SetString(PyExc_FileNotFoundError, "Script file does not exist");
|
||||||
|
}
|
||||||
|
else if (err == ERROR_ACCESS_DENIED) {
|
||||||
|
PyErr_SetString(PyExc_PermissionError, "Script file cannot be read");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetFromWindowsErr(err);
|
||||||
|
}
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
PyMem_Free(debugger_script_path_w);
|
||||||
|
#else // MS_WINDOWS
|
||||||
|
if (access(debugger_script_path, F_OK | R_OK) != 0) {
|
||||||
|
switch (errno) {
|
||||||
|
case ENOENT:
|
||||||
|
PyErr_SetString(PyExc_FileNotFoundError, "Script file does not exist");
|
||||||
|
break;
|
||||||
|
case EACCES:
|
||||||
|
PyErr_SetString(PyExc_PermissionError, "Script file cannot be read");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
|
}
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
#endif // MS_WINDOWS
|
||||||
|
if (_PySysRemoteDebug_SendExec(pid, 0, debugger_script_path) < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(path);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
|
||||||
|
error:
|
||||||
|
Py_DECREF(path);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user