mirror of
				https://github.com/python/cpython.git
				synced 2025-10-28 20:25:04 +00:00 
			
		
		
		
	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
					
				
					 4 changed files with 102 additions and 76 deletions
				
			
		|  | @ -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 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None, prologu | ||||||
|                 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 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None, prologu | ||||||
| 
 | 
 | ||||||
|     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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Serhiy Storchaka
						Serhiy Storchaka