diff options
author | Steve Dower <steve.dower@microsoft.com> | 2015-05-22 22:10:10 (GMT) |
---|---|---|
committer | Steve Dower <steve.dower@microsoft.com> | 2015-05-22 22:10:10 (GMT) |
commit | 4a7fe7e3975062a939a4e7242f2a12b172befd8a (patch) | |
tree | 7d7eeee5aa9b075cd6838691e2f25b6c3a795b9c | |
parent | d9ef74e3ddffc9327a3b225734cdc5a5028e8d0b (diff) | |
download | cpython-4a7fe7e3975062a939a4e7242f2a12b172befd8a.zip cpython-4a7fe7e3975062a939a4e7242f2a12b172befd8a.tar.gz cpython-4a7fe7e3975062a939a4e7242f2a12b172befd8a.tar.bz2 |
Issue #23955: Add pyvenv.cfg option to suppress registry/environment lookup for generating sys.path.
Also cleans up and secures getpathp.c
-rw-r--r-- | Doc/using/windows.rst | 21 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | PC/getpathp.c | 178 | ||||
-rw-r--r-- | Tools/msi/make_zip.py | 3 |
4 files changed, 127 insertions, 78 deletions
diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index 999ec1f..8e3c110 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -661,6 +661,17 @@ This is how :data:`sys.path` is populated on Windows: the environment, and no registry entries can be found, a default path with relative entries is used (e.g. ``.\Lib;.\plat-win``, etc). +If a ``pyvenv.cfg`` file is found alongside the main executable or in the +directory one level above the executable, the following variations apply: + +* If ``home`` is an absolute path and :envvar:`PYTHONHOME` is not set, this + path is used instead of the path to the main executable when deducing the + home location. + +* If ``applocal`` is set to true, the ``home`` property or the main executable + is always used as the home path, and all environment variables or registry + values affecting the path are ignored. The landmark file is not checked. + The end result of all this is: * When running :file:`python.exe`, or any other .exe in the main Python @@ -672,13 +683,17 @@ The end result of all this is: etc), the "Python Home" will not be deduced, so the core path from the registry is used. Other "application paths" in the registry are always read. -* If Python can't find its home and there is no registry (eg, frozen .exe, some - very strange installation setup) you get a path with some default, but +* If Python can't find its home and there are no registry value (frozen .exe, + some very strange installation setup) you get a path with some default, but relative, paths. For those who want to bundle Python into their application or distribution, the following advice will prevent conflicts with other installations: +* Include a ``pyvenv.cfg`` file alongside your executable containing + ``applocal = true``. This will ensure that your own directory will be used to + resolve paths even if you have included the standard library in a ZIP file. + * If you are loading :file:`python3.dll` or :file:`python35.dll` in your own executable, explicitly call :c:func:`Py_SetPath` or (at least) :c:func:`Py_SetProgramName` before :c:func:`Py_Initialize`. @@ -688,7 +703,7 @@ following advice will prevent conflicts with other installations: * If you cannot use the previous suggestions (for example, you are a distribution that allows people to run :file:`python.exe` directly), ensure - that the landmark file (:file:`Lib\\os.py`) exists in your bundled library. + that the landmark file (:file:`Lib\\os.py`) exists in your install directory. (Note that it will not be detected inside a ZIP file.) These will ensure that the files in a system-wide installation will not take @@ -10,6 +10,9 @@ Release date: 2015-05-24 Core and Builtins ----------------- +- Issue #23955: Add pyvenv.cfg option to suppress registry/environment + lookup for generating sys.path on Windows. + - Issue #24257: Fixed system error in the comparison of faked types.SimpleNamespace. diff --git a/PC/getpathp.c b/PC/getpathp.c index d4f4574..08b65c5 100644 --- a/PC/getpathp.c +++ b/PC/getpathp.c @@ -113,7 +113,10 @@ is_sep(wchar_t ch) /* determine if "ch" is a separator character */ static void reduce(wchar_t *dir) { - size_t i = wcslen(dir); + size_t i = wcsnlen_s(dir, MAXPATHLEN+1); + if (i >= MAXPATHLEN+1) + Py_FatalError("buffer overflow in getpathp.c's reduce()"); + while (i > 0 && !is_sep(dir[i])) --i; dir[i] = '\0'; @@ -130,16 +133,23 @@ exists(wchar_t *filename) may extend 'filename' by one character. */ static int -ismodule(wchar_t *filename) /* Is module -- check for .pyc/.pyo too */ +ismodule(wchar_t *filename, int update_filename) /* Is module -- check for .pyc/.pyo too */ { + int n; + if (exists(filename)) return 1; /* Check for the compiled version of prefix. */ - if (wcslen(filename) < MAXPATHLEN) { - wcscat(filename, Py_OptimizeFlag ? L"o" : L"c"); - if (exists(filename)) - return 1; + n = wcsnlen_s(filename, MAXPATHLEN+1); + if (n < MAXPATHLEN) { + int exist = 0; + filename[n] = Py_OptimizeFlag ? L'o' : L'c'; + filename[n + 1] = L'\0'; + exist = exists(filename); + if (!update_filename) + filename[n] = L'\0'; + return exist; } return 0; } @@ -154,23 +164,23 @@ ismodule(wchar_t *filename) /* Is module -- check for .pyc/.pyo too */ stuff as fits will be appended. */ static void -join(wchar_t *buffer, wchar_t *stuff) +join(wchar_t *buffer, const wchar_t *stuff) { - size_t n, k; - if (is_sep(stuff[0])) - n = 0; - else { - n = wcslen(buffer); - if (n > 0 && !is_sep(buffer[n-1]) && n < MAXPATHLEN) - buffer[n++] = SEP; + size_t n; + if (is_sep(stuff[0]) || + (wcsnlen_s(stuff, 4) >= 3 && stuff[1] == ':' && is_sep(stuff[2]))) { + if (wcscpy_s(buffer, MAXPATHLEN+1, stuff) != 0) + Py_FatalError("buffer overflow in getpathp.c's join()"); + return; + } + + n = wcsnlen_s(buffer, MAXPATHLEN+1); + if (n > 0 && !is_sep(buffer[n - 1]) && n < MAXPATHLEN) { + buffer[n] = SEP; + buffer[n + 1] = '\0'; } - if (n > MAXPATHLEN) - Py_FatalError("buffer overflow in getpathp.c's joinpath()"); - k = wcslen(stuff); - if (n + k > MAXPATHLEN) - k = MAXPATHLEN - n; - wcsncpy(buffer+n, stuff, k); - buffer[n+k] = '\0'; + if (wcscat_s(buffer, MAXPATHLEN+1, stuff) != 0) + Py_FatalError("buffer overflow in getpathp.c's join()"); } /* gotlandmark only called by search_for_prefix, which ensures @@ -181,11 +191,10 @@ static int gotlandmark(wchar_t *landmark) { int ok; - Py_ssize_t n; + Py_ssize_t n = wcsnlen_s(prefix, MAXPATHLEN); - n = wcslen(prefix); join(prefix, landmark); - ok = ismodule(prefix); + ok = ismodule(prefix, FALSE); prefix[n] = '\0'; return ok; } @@ -196,7 +205,7 @@ static int search_for_prefix(wchar_t *argv0_path, wchar_t *landmark) { /* Search from argv0_path, until landmark is found */ - wcscpy(prefix, argv0_path); + wcscpy_s(prefix, MAXPATHLEN + 1, argv0_path); do { if (gotlandmark(landmark)) return 1; @@ -236,7 +245,7 @@ getpythonregpath(HKEY keyBase, int skipcore) WCHAR *dataBuf = NULL; static const WCHAR keyPrefix[] = L"Software\\Python\\PythonCore\\"; static const WCHAR keySuffix[] = L"\\PythonPath"; - size_t versionLen; + size_t versionLen, keyBufLen; DWORD index; WCHAR *keyBuf = NULL; WCHAR *keyBufPtr; @@ -245,12 +254,13 @@ getpythonregpath(HKEY keyBase, int skipcore) /* Tried to use sysget("winver") but here is too early :-( */ versionLen = strlen(PyWin_DLLVersionString); /* Space for all the chars, plus one \0 */ - keyBuf = keyBufPtr = PyMem_RawMalloc(sizeof(keyPrefix) + - sizeof(WCHAR)*(versionLen-1) + - sizeof(keySuffix)); + keyBufLen = sizeof(keyPrefix) + + sizeof(WCHAR)*(versionLen-1) + + sizeof(keySuffix); + keyBuf = keyBufPtr = PyMem_RawMalloc(keyBufLen); if (keyBuf==NULL) goto done; - memcpy(keyBufPtr, keyPrefix, sizeof(keyPrefix)-sizeof(WCHAR)); + memcpy_s(keyBufPtr, keyBufLen, keyPrefix, sizeof(keyPrefix)-sizeof(WCHAR)); keyBufPtr += Py_ARRAY_LENGTH(keyPrefix) - 1; mbstowcs(keyBufPtr, PyWin_DLLVersionString, versionLen); keyBufPtr += versionLen; @@ -484,7 +494,7 @@ calculate_path(void) wchar_t *machinepath = NULL; wchar_t *userpath = NULL; wchar_t zip_path[MAXPATHLEN+1]; - size_t len; + int applocal = 0; if (!Py_IgnoreEnvironmentFlag) { envpath = _wgetenv(L"PYTHONPATH"); @@ -502,7 +512,7 @@ calculate_path(void) get_progpath(); /* progpath guaranteed \0 terminated in MAXPATH+1 bytes. */ - wcscpy(argv0_path, progpath); + wcscpy_s(argv0_path, MAXPATHLEN+1, progpath); reduce(argv0_path); /* Search for an environment configuration file, first in the @@ -511,27 +521,39 @@ calculate_path(void) */ { + wchar_t envbuffer[MAXPATHLEN+1]; wchar_t tmpbuffer[MAXPATHLEN+1]; - wchar_t *env_cfg = L"pyvenv.cfg"; + const wchar_t *env_cfg = L"pyvenv.cfg"; FILE * env_file = NULL; - wcscpy(tmpbuffer, argv0_path); - join(tmpbuffer, env_cfg); - env_file = _Py_wfopen(tmpbuffer, L"r"); + wcscpy_s(envbuffer, MAXPATHLEN+1, argv0_path); + join(envbuffer, env_cfg); + env_file = _Py_wfopen(envbuffer, L"r"); if (env_file == NULL) { errno = 0; - reduce(tmpbuffer); - reduce(tmpbuffer); - join(tmpbuffer, env_cfg); - env_file = _Py_wfopen(tmpbuffer, L"r"); + reduce(envbuffer); + reduce(envbuffer); + join(envbuffer, env_cfg); + env_file = _Py_wfopen(envbuffer, L"r"); if (env_file == NULL) { errno = 0; } } if (env_file != NULL) { + /* Look for an 'applocal' variable and, if true, ignore all registry + * keys and environment variables, but retain the default paths + * (DLLs, Lib) and the zip file. Setting pythonhome here suppresses + * the search for LANDMARK below and overrides %PYTHONHOME%. + */ + if (find_env_config_value(env_file, L"applocal", tmpbuffer) && + (applocal = (wcsicmp(tmpbuffer, L"true") == 0))) { + envpath = NULL; + pythonhome = argv0_path; + } + /* Look for a 'home' variable and set argv0_path to it, if found */ if (find_env_config_value(env_file, L"home", tmpbuffer)) { - wcscpy(argv0_path, tmpbuffer); + wcscpy_s(argv0_path, MAXPATHLEN+1, tmpbuffer); } fclose(env_file); env_file = NULL; @@ -545,33 +567,30 @@ calculate_path(void) pythonhome = NULL; } else - wcsncpy(prefix, pythonhome, MAXPATHLEN); + wcscpy_s(prefix, MAXPATHLEN+1, pythonhome); if (envpath && *envpath == '\0') envpath = NULL; #ifdef MS_WINDOWS - /* Calculate zip archive path */ - if (dllpath[0]) /* use name of python DLL */ - wcsncpy(zip_path, dllpath, MAXPATHLEN); - else /* use name of executable program */ - wcsncpy(zip_path, progpath, MAXPATHLEN); - zip_path[MAXPATHLEN] = '\0'; - len = wcslen(zip_path); - if (len > 4) { - zip_path[len-3] = 'z'; /* change ending to "zip" */ - zip_path[len-2] = 'i'; - zip_path[len-1] = 'p'; - } + /* Calculate zip archive path from DLL or exe path */ + if (wcscpy_s(zip_path, MAXPATHLEN+1, dllpath[0] ? dllpath : progpath)) + /* exceeded buffer length - ignore zip_path */ + zip_path[0] = '\0'; else { - zip_path[0] = 0; + wchar_t *dot = wcsrchr(zip_path, '.'); + if (!dot || wcscpy_s(dot, MAXPATHLEN+1 - (dot - zip_path), L".zip")) + /* exceeded buffer length - ignore zip_path */ + zip_path[0] = L'\0'; } skiphome = pythonhome==NULL ? 0 : 1; #ifdef Py_ENABLE_SHARED - machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome); - userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome); + if (!applocal) { + machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome); + userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome); + } #endif /* We only use the default relative PYTHONPATH if we havent anything better to use! */ @@ -590,6 +609,7 @@ calculate_path(void) Extra rules: - If PYTHONHOME is set (in any way) item (3) is ignored. - If registry values are used, (4) and (5) are ignored. + - If applocal is set, (1), (3), and registry values are ignored */ /* Calculate size of return buffer */ @@ -600,21 +620,21 @@ calculate_path(void) if (*p == DELIM) bufsz++; /* number of DELIM plus one */ } - bufsz *= wcslen(pythonhome); + bufsz *= wcsnlen_s(pythonhome, MAXPATHLEN+1); } else bufsz = 0; - bufsz += wcslen(PYTHONPATH) + 1; - bufsz += wcslen(argv0_path) + 1; + bufsz += wcsnlen_s(PYTHONPATH, MAXPATHLEN+1) + 1; + bufsz += wcsnlen_s(argv0_path, MAXPATHLEN+1) + 1; #ifdef MS_WINDOWS - if (userpath) - bufsz += wcslen(userpath) + 1; - if (machinepath) - bufsz += wcslen(machinepath) + 1; - bufsz += wcslen(zip_path) + 1; + if (!applocal && userpath) + bufsz += wcsnlen_s(userpath, MAXPATHLEN+1) + 1; + if (!applocal && machinepath) + bufsz += wcsnlen_s(machinepath, MAXPATHLEN+1) + 1; + bufsz += wcsnlen_s(zip_path, MAXPATHLEN+1) + 1; #endif if (envpath != NULL) - bufsz += wcslen(envpath) + 1; + bufsz += wcsnlen_s(envpath, MAXPATHLEN+1) + 1; module_search_path = buf = PyMem_RawMalloc(bufsz*sizeof(wchar_t)); if (buf == NULL) { @@ -636,38 +656,45 @@ calculate_path(void) } if (envpath) { - wcscpy(buf, envpath); + if (wcscpy_s(buf, bufsz - (buf - module_search_path), envpath)) + Py_FatalError("buffer overflow in getpathp.c's calculate_path()"); buf = wcschr(buf, L'\0'); *buf++ = DELIM; } #ifdef MS_WINDOWS if (zip_path[0]) { - wcscpy(buf, zip_path); + if (wcscpy_s(buf, bufsz - (buf - module_search_path), zip_path)) + Py_FatalError("buffer overflow in getpathp.c's calculate_path()"); buf = wcschr(buf, L'\0'); *buf++ = DELIM; } if (userpath) { - wcscpy(buf, userpath); + if (wcscpy_s(buf, bufsz - (buf - module_search_path), userpath)) + Py_FatalError("buffer overflow in getpathp.c's calculate_path()"); buf = wcschr(buf, L'\0'); *buf++ = DELIM; PyMem_RawFree(userpath); } if (machinepath) { - wcscpy(buf, machinepath); + if (wcscpy_s(buf, bufsz - (buf - module_search_path), machinepath)) + Py_FatalError("buffer overflow in getpathp.c's calculate_path()"); buf = wcschr(buf, L'\0'); *buf++ = DELIM; PyMem_RawFree(machinepath); } if (pythonhome == NULL) { if (!skipdefault) { - wcscpy(buf, PYTHONPATH); + if (wcscpy_s(buf, bufsz - (buf - module_search_path), PYTHONPATH)) + Py_FatalError("buffer overflow in getpathp.c's calculate_path()"); buf = wcschr(buf, L'\0'); + *buf++ = DELIM; } } #else if (pythonhome == NULL) { wcscpy(buf, PYTHONPATH); buf = wcschr(buf, L'\0'); + *buf++ = DELIM; } #endif /* MS_WINDOWS */ else { @@ -681,25 +708,26 @@ calculate_path(void) else n = q-p; if (p[0] == '.' && is_sep(p[1])) { - wcscpy(buf, pythonhome); + if (wcscpy_s(buf, bufsz - (buf - module_search_path), pythonhome)) + Py_FatalError("buffer overflow in getpathp.c's calculate_path()"); buf = wcschr(buf, L'\0'); p++; n--; } wcsncpy(buf, p, n); buf += n; + *buf++ = DELIM; if (q == NULL) break; - *buf++ = DELIM; p = q+1; } } if (argv0_path) { - *buf++ = DELIM; wcscpy(buf, argv0_path); buf = wcschr(buf, L'\0'); + *buf++ = DELIM; } - *buf = L'\0'; + *(buf - 1) = L'\0'; /* Now to pull one last hack/trick. If sys.prefix is empty, then try and find it somewhere on the paths we calculated. We scan backwards, as our general policy diff --git a/Tools/msi/make_zip.py b/Tools/msi/make_zip.py index 72ec280..fcfcd56 100644 --- a/Tools/msi/make_zip.py +++ b/Tools/msi/make_zip.py @@ -149,6 +149,9 @@ def main(): copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c)) print('Copied {} files'.format(copied)) + with open(str(temp / 'pyvenv.cfg'), 'w') as f: + print('applocal = true', file=f) + total = copy_to_layout(out, rglob(temp, '*', None)) print('Wrote {} files to {}'.format(total, out)) finally: |