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:
Serhiy Storchaka 2025-05-13 11:55:24 +03:00 committed by GitHub
parent 8cf4947b0f
commit c09cec5d69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 102 additions and 76 deletions

View file

@ -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
// 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(
"Audit hook failed for remote debugger script %s", path);
"Audit hook failed for remote debugger script %U", path);
return;
}
// Open the debugger script with the open code hook, and reopen the
// resulting file object to get a C FILE* object.
PyObject* fileobj = PyFile_OpenCode(path);
PyObject* fileobj = PyFile_OpenCodeObject(path);
if (!fileobj) {
PyErr_FormatUnraisable("Can't open debugger script %s", path);
PyErr_FormatUnraisable("Can't open debugger script %U", path);
return;
}
PyObject* source = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(read));
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));
if (!res) {
PyErr_FormatUnraisable("Error closing debugger script %s", path);
PyErr_FormatUnraisable("Error closing debugger script %U", path);
} else {
Py_DECREF(res);
}
@ -1249,7 +1249,7 @@ static inline void run_remote_debugger_script(const char *path)
if (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);
}
@ -1278,7 +1278,14 @@ int _PyRunRemoteDebugger(PyThreadState *tstate)
pathsz);
path[pathsz - 1] = '\0';
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);
}

View file

@ -2451,60 +2451,6 @@ sys_is_remote_debug_enabled_impl(PyObject *module)
#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]
sys.remote_exec
@ -2535,13 +2481,65 @@ static PyObject *
sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
/*[clinic end generated code: output=7d94c56afe4a52c0 input=39908ca2c5fe1eb0]*/
{
PyObject *ret = NULL;
PyObject *path;
if (PyUnicode_FSDecoder(script, &path)) {
ret = sys_remote_exec_unicode_path(module, pid, path);
Py_DECREF(path);
const char *debugger_script_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;
}