Skip to content

Commit e5a9b35

Browse files
bpo-32457: Improves handling of denormalized executable path when launching Python (GH-5756)
(cherry picked from commit 48e8c82) Co-authored-by: Steve Dower <[email protected]>
1 parent 01dd52f commit e5a9b35

File tree

3 files changed

+54
-54
lines changed

3 files changed

+54
-54
lines changed

Lib/test/test_cmd_line.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,17 @@ def test_pythondevmode_env(self):
701701
self.assertEqual(proc.stdout.rstrip(), 'True')
702702
self.assertEqual(proc.returncode, 0, proc)
703703

704+
@unittest.skipUnless(sys.platform == 'win32',
705+
'bpo-32457 only applies on Windows')
706+
def test_argv0_normalization(self):
707+
args = sys.executable, '-c', 'print(0)'
708+
prefix, exe = os.path.split(sys.executable)
709+
executable = prefix + '\\.\\.\\.\\' + exe
710+
711+
proc = subprocess.run(args, stdout=subprocess.PIPE,
712+
executable=executable)
713+
self.assertEqual(proc.returncode, 0, proc)
714+
self.assertEqual(proc.stdout.strip(), b'0')
704715

705716
@unittest.skipIf(interpreter_requires_environment(),
706717
'Cannot run -I tests when PYTHON env vars are required.')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improves handling of denormalized executable path when launching Python.

PC/getpathp.c

Lines changed: 42 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,41 @@ join(wchar_t *buffer, const wchar_t *stuff)
266266
}
267267
}
268268

269+
static int _PathCchCanonicalizeEx_Initialized = 0;
270+
typedef HRESULT(__stdcall *PPathCchCanonicalizeEx) (PWSTR pszPathOut, size_t cchPathOut,
271+
PCWSTR pszPathIn, unsigned long dwFlags);
272+
static PPathCchCanonicalizeEx _PathCchCanonicalizeEx;
273+
274+
static _PyInitError canonicalize(wchar_t *buffer, const wchar_t *path)
275+
{
276+
if (buffer == NULL) {
277+
return _Py_INIT_NO_MEMORY();
278+
}
279+
280+
if (_PathCchCanonicalizeEx_Initialized == 0) {
281+
HMODULE pathapi = LoadLibraryW(L"api-ms-win-core-path-l1-1-0.dll");
282+
if (pathapi) {
283+
_PathCchCanonicalizeEx = (PPathCchCanonicalizeEx)GetProcAddress(pathapi, "PathCchCanonicalizeEx");
284+
}
285+
else {
286+
_PathCchCanonicalizeEx = NULL;
287+
}
288+
_PathCchCanonicalizeEx_Initialized = 1;
289+
}
290+
291+
if (_PathCchCanonicalizeEx) {
292+
if (FAILED(_PathCchCanonicalizeEx(buffer, MAXPATHLEN + 1, path, 0))) {
293+
return _Py_INIT_ERR("buffer overflow in getpathp.c's canonicalize()");
294+
}
295+
}
296+
else {
297+
if (!PathCanonicalizeW(buffer, path)) {
298+
return _Py_INIT_ERR("buffer overflow in getpathp.c's canonicalize()");
299+
}
300+
}
301+
return _Py_INIT_OK();
302+
}
303+
269304

270305
/* gotlandmark only called by search_for_prefix, which ensures
271306
'prefix' is null terminated in bounds. join() ensures
@@ -504,63 +539,16 @@ get_program_full_path(const _PyCoreConfig *core_config,
504539
wchar_t program_full_path[MAXPATHLEN+1];
505540
memset(program_full_path, 0, sizeof(program_full_path));
506541

507-
if (GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
508-
goto done;
509-
}
510-
511-
/* If there is no slash in the argv0 path, then we have to
512-
* assume python is on the user's $PATH, since there's no
513-
* other way to find a directory to start the search from. If
514-
* $PATH isn't exported, you lose.
515-
*/
516-
#ifdef ALTSEP
517-
if (wcschr(core_config->program_name, SEP) ||
518-
wcschr(core_config->program_name, ALTSEP))
519-
#else
520-
if (wcschr(core_config->program_name, SEP))
521-
#endif
522-
{
523-
wcsncpy(program_full_path, core_config->program_name, MAXPATHLEN);
542+
if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
543+
/* GetModuleFileName should never fail when passed NULL */
544+
return _Py_INIT_ERR("Cannot determine program path");
524545
}
525-
else if (calculate->path_env) {
526-
const wchar_t *path = calculate->path_env;
527-
while (1) {
528-
const wchar_t *delim = wcschr(path, DELIM);
529-
530-
if (delim) {
531-
size_t len = delim - path;
532-
/* ensure we can't overwrite buffer */
533-
len = min(MAXPATHLEN,len);
534-
wcsncpy(program_full_path, path, len);
535-
program_full_path[len] = '\0';
536-
}
537-
else {
538-
wcsncpy(program_full_path, path, MAXPATHLEN);
539-
}
540-
541-
/* join() is safe for MAXPATHLEN+1 size buffer */
542-
join(program_full_path, core_config->program_name);
543-
if (exists(program_full_path)) {
544-
break;
545-
}
546546

547-
if (!delim) {
548-
program_full_path[0] = '\0';
549-
break;
550-
}
551-
path = delim + 1;
552-
}
553-
}
554-
else {
555-
program_full_path[0] = '\0';
556-
}
547+
config->program_full_path = PyMem_RawMalloc(
548+
sizeof(wchar_t) * (MAXPATHLEN + 1));
557549

558-
done:
559-
config->program_full_path = _PyMem_RawWcsdup(program_full_path);
560-
if (config->program_full_path == NULL) {
561-
return _Py_INIT_NO_MEMORY();
562-
}
563-
return _Py_INIT_OK();
550+
return canonicalize(config->program_full_path,
551+
program_full_path);
564552
}
565553

566554

0 commit comments

Comments
 (0)