Commit 4a7fe7e3 authored by Steve Dower's avatar Steve Dower

Issue #23955: Add pyvenv.cfg option to suppress registry/environment lookup...

Issue #23955: Add pyvenv.cfg option to suppress registry/environment lookup for generating sys.path.
Also cleans up and secures getpathp.c
parent d9ef74e3
...@@ -661,6 +661,17 @@ This is how :data:`sys.path` is populated on Windows: ...@@ -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 the environment, and no registry entries can be found, a default path with
relative entries is used (e.g. ``.\Lib;.\plat-win``, etc). 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: The end result of all this is:
* When running :file:`python.exe`, or any other .exe in the main Python * When running :file:`python.exe`, or any other .exe in the main Python
...@@ -672,13 +683,17 @@ The end result of all this is: ...@@ -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 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. 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 * If Python can't find its home and there are no registry value (frozen .exe,
very strange installation setup) you get a path with some default, but some very strange installation setup) you get a path with some default, but
relative, paths. relative, paths.
For those who want to bundle Python into their application or distribution, the For those who want to bundle Python into their application or distribution, the
following advice will prevent conflicts with other installations: 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 * If you are loading :file:`python3.dll` or :file:`python35.dll` in your own
executable, explicitly call :c:func:`Py_SetPath` or (at least) executable, explicitly call :c:func:`Py_SetPath` or (at least)
:c:func:`Py_SetProgramName` before :c:func:`Py_Initialize`. :c:func:`Py_SetProgramName` before :c:func:`Py_Initialize`.
...@@ -688,7 +703,7 @@ following advice will prevent conflicts with other installations: ...@@ -688,7 +703,7 @@ following advice will prevent conflicts with other installations:
* If you cannot use the previous suggestions (for example, you are a * If you cannot use the previous suggestions (for example, you are a
distribution that allows people to run :file:`python.exe` directly), ensure 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.) (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 These will ensure that the files in a system-wide installation will not take
......
...@@ -10,6 +10,9 @@ Release date: 2015-05-24 ...@@ -10,6 +10,9 @@ Release date: 2015-05-24
Core and Builtins 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 - Issue #24257: Fixed system error in the comparison of faked
types.SimpleNamespace. types.SimpleNamespace.
......
...@@ -113,7 +113,10 @@ is_sep(wchar_t ch) /* determine if "ch" is a separator character */ ...@@ -113,7 +113,10 @@ is_sep(wchar_t ch) /* determine if "ch" is a separator character */
static void static void
reduce(wchar_t *dir) 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])) while (i > 0 && !is_sep(dir[i]))
--i; --i;
dir[i] = '\0'; dir[i] = '\0';
...@@ -130,16 +133,23 @@ exists(wchar_t *filename) ...@@ -130,16 +133,23 @@ exists(wchar_t *filename)
may extend 'filename' by one character. may extend 'filename' by one character.
*/ */
static int 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)) if (exists(filename))
return 1; return 1;
/* Check for the compiled version of prefix. */ /* Check for the compiled version of prefix. */
if (wcslen(filename) < MAXPATHLEN) { n = wcsnlen_s(filename, MAXPATHLEN+1);
wcscat(filename, Py_OptimizeFlag ? L"o" : L"c"); if (n < MAXPATHLEN) {
if (exists(filename)) int exist = 0;
return 1; 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; return 0;
} }
...@@ -154,23 +164,23 @@ ismodule(wchar_t *filename) /* Is module -- check for .pyc/.pyo too */ ...@@ -154,23 +164,23 @@ ismodule(wchar_t *filename) /* Is module -- check for .pyc/.pyo too */
stuff as fits will be appended. stuff as fits will be appended.
*/ */
static void static void
join(wchar_t *buffer, wchar_t *stuff) join(wchar_t *buffer, const wchar_t *stuff)
{ {
size_t n, k; size_t n;
if (is_sep(stuff[0])) if (is_sep(stuff[0]) ||
n = 0; (wcsnlen_s(stuff, 4) >= 3 && stuff[1] == ':' && is_sep(stuff[2]))) {
else { if (wcscpy_s(buffer, MAXPATHLEN+1, stuff) != 0)
n = wcslen(buffer); Py_FatalError("buffer overflow in getpathp.c's join()");
if (n > 0 && !is_sep(buffer[n-1]) && n < MAXPATHLEN) return;
buffer[n++] = SEP; }
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) if (wcscat_s(buffer, MAXPATHLEN+1, stuff) != 0)
Py_FatalError("buffer overflow in getpathp.c's joinpath()"); Py_FatalError("buffer overflow in getpathp.c's join()");
k = wcslen(stuff);
if (n + k > MAXPATHLEN)
k = MAXPATHLEN - n;
wcsncpy(buffer+n, stuff, k);
buffer[n+k] = '\0';
} }
/* gotlandmark only called by search_for_prefix, which ensures /* gotlandmark only called by search_for_prefix, which ensures
...@@ -181,11 +191,10 @@ static int ...@@ -181,11 +191,10 @@ static int
gotlandmark(wchar_t *landmark) gotlandmark(wchar_t *landmark)
{ {
int ok; int ok;
Py_ssize_t n; Py_ssize_t n = wcsnlen_s(prefix, MAXPATHLEN);
n = wcslen(prefix);
join(prefix, landmark); join(prefix, landmark);
ok = ismodule(prefix); ok = ismodule(prefix, FALSE);
prefix[n] = '\0'; prefix[n] = '\0';
return ok; return ok;
} }
...@@ -196,7 +205,7 @@ static int ...@@ -196,7 +205,7 @@ static int
search_for_prefix(wchar_t *argv0_path, wchar_t *landmark) search_for_prefix(wchar_t *argv0_path, wchar_t *landmark)
{ {
/* Search from argv0_path, until landmark is found */ /* Search from argv0_path, until landmark is found */
wcscpy(prefix, argv0_path); wcscpy_s(prefix, MAXPATHLEN + 1, argv0_path);
do { do {
if (gotlandmark(landmark)) if (gotlandmark(landmark))
return 1; return 1;
...@@ -236,7 +245,7 @@ getpythonregpath(HKEY keyBase, int skipcore) ...@@ -236,7 +245,7 @@ getpythonregpath(HKEY keyBase, int skipcore)
WCHAR *dataBuf = NULL; WCHAR *dataBuf = NULL;
static const WCHAR keyPrefix[] = L"Software\\Python\\PythonCore\\"; static const WCHAR keyPrefix[] = L"Software\\Python\\PythonCore\\";
static const WCHAR keySuffix[] = L"\\PythonPath"; static const WCHAR keySuffix[] = L"\\PythonPath";
size_t versionLen; size_t versionLen, keyBufLen;
DWORD index; DWORD index;
WCHAR *keyBuf = NULL; WCHAR *keyBuf = NULL;
WCHAR *keyBufPtr; WCHAR *keyBufPtr;
...@@ -245,12 +254,13 @@ getpythonregpath(HKEY keyBase, int skipcore) ...@@ -245,12 +254,13 @@ getpythonregpath(HKEY keyBase, int skipcore)
/* Tried to use sysget("winver") but here is too early :-( */ /* Tried to use sysget("winver") but here is too early :-( */
versionLen = strlen(PyWin_DLLVersionString); versionLen = strlen(PyWin_DLLVersionString);
/* Space for all the chars, plus one \0 */ /* Space for all the chars, plus one \0 */
keyBuf = keyBufPtr = PyMem_RawMalloc(sizeof(keyPrefix) + keyBufLen = sizeof(keyPrefix) +
sizeof(WCHAR)*(versionLen-1) + sizeof(WCHAR)*(versionLen-1) +
sizeof(keySuffix)); sizeof(keySuffix);
keyBuf = keyBufPtr = PyMem_RawMalloc(keyBufLen);
if (keyBuf==NULL) goto done; 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; keyBufPtr += Py_ARRAY_LENGTH(keyPrefix) - 1;
mbstowcs(keyBufPtr, PyWin_DLLVersionString, versionLen); mbstowcs(keyBufPtr, PyWin_DLLVersionString, versionLen);
keyBufPtr += versionLen; keyBufPtr += versionLen;
...@@ -484,7 +494,7 @@ calculate_path(void) ...@@ -484,7 +494,7 @@ calculate_path(void)
wchar_t *machinepath = NULL; wchar_t *machinepath = NULL;
wchar_t *userpath = NULL; wchar_t *userpath = NULL;
wchar_t zip_path[MAXPATHLEN+1]; wchar_t zip_path[MAXPATHLEN+1];
size_t len; int applocal = 0;
if (!Py_IgnoreEnvironmentFlag) { if (!Py_IgnoreEnvironmentFlag) {
envpath = _wgetenv(L"PYTHONPATH"); envpath = _wgetenv(L"PYTHONPATH");
...@@ -502,7 +512,7 @@ calculate_path(void) ...@@ -502,7 +512,7 @@ calculate_path(void)
get_progpath(); get_progpath();
/* progpath guaranteed \0 terminated in MAXPATH+1 bytes. */ /* progpath guaranteed \0 terminated in MAXPATH+1 bytes. */
wcscpy(argv0_path, progpath); wcscpy_s(argv0_path, MAXPATHLEN+1, progpath);
reduce(argv0_path); reduce(argv0_path);
/* Search for an environment configuration file, first in the /* Search for an environment configuration file, first in the
...@@ -511,27 +521,39 @@ calculate_path(void) ...@@ -511,27 +521,39 @@ calculate_path(void)
*/ */
{ {
wchar_t envbuffer[MAXPATHLEN+1];
wchar_t tmpbuffer[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; FILE * env_file = NULL;
wcscpy(tmpbuffer, argv0_path); wcscpy_s(envbuffer, MAXPATHLEN+1, argv0_path);
join(tmpbuffer, env_cfg); join(envbuffer, env_cfg);
env_file = _Py_wfopen(tmpbuffer, L"r"); env_file = _Py_wfopen(envbuffer, L"r");
if (env_file == NULL) { if (env_file == NULL) {
errno = 0; errno = 0;
reduce(tmpbuffer); reduce(envbuffer);
reduce(tmpbuffer); reduce(envbuffer);
join(tmpbuffer, env_cfg); join(envbuffer, env_cfg);
env_file = _Py_wfopen(tmpbuffer, L"r"); env_file = _Py_wfopen(envbuffer, L"r");
if (env_file == NULL) { if (env_file == NULL) {
errno = 0; errno = 0;
} }
} }
if (env_file != NULL) { 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 */ /* Look for a 'home' variable and set argv0_path to it, if found */
if (find_env_config_value(env_file, L"home", tmpbuffer)) { if (find_env_config_value(env_file, L"home", tmpbuffer)) {
wcscpy(argv0_path, tmpbuffer); wcscpy_s(argv0_path, MAXPATHLEN+1, tmpbuffer);
} }
fclose(env_file); fclose(env_file);
env_file = NULL; env_file = NULL;
...@@ -545,33 +567,30 @@ calculate_path(void) ...@@ -545,33 +567,30 @@ calculate_path(void)
pythonhome = NULL; pythonhome = NULL;
} }
else else
wcsncpy(prefix, pythonhome, MAXPATHLEN); wcscpy_s(prefix, MAXPATHLEN+1, pythonhome);
if (envpath && *envpath == '\0') if (envpath && *envpath == '\0')
envpath = NULL; envpath = NULL;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
/* Calculate zip archive path */ /* Calculate zip archive path from DLL or exe path */
if (dllpath[0]) /* use name of python DLL */ if (wcscpy_s(zip_path, MAXPATHLEN+1, dllpath[0] ? dllpath : progpath))
wcsncpy(zip_path, dllpath, MAXPATHLEN); /* exceeded buffer length - ignore zip_path */
else /* use name of executable program */ zip_path[0] = '\0';
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';
}
else { 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; skiphome = pythonhome==NULL ? 0 : 1;
#ifdef Py_ENABLE_SHARED #ifdef Py_ENABLE_SHARED
machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome); if (!applocal) {
userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome); machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome);
userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome);
}
#endif #endif
/* We only use the default relative PYTHONPATH if we havent /* We only use the default relative PYTHONPATH if we havent
anything better to use! */ anything better to use! */
...@@ -590,6 +609,7 @@ calculate_path(void) ...@@ -590,6 +609,7 @@ calculate_path(void)
Extra rules: Extra rules:
- If PYTHONHOME is set (in any way) item (3) is ignored. - If PYTHONHOME is set (in any way) item (3) is ignored.
- If registry values are used, (4) and (5) are 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 */ /* Calculate size of return buffer */
...@@ -600,21 +620,21 @@ calculate_path(void) ...@@ -600,21 +620,21 @@ calculate_path(void)
if (*p == DELIM) if (*p == DELIM)
bufsz++; /* number of DELIM plus one */ bufsz++; /* number of DELIM plus one */
} }
bufsz *= wcslen(pythonhome); bufsz *= wcsnlen_s(pythonhome, MAXPATHLEN+1);
} }
else else
bufsz = 0; bufsz = 0;
bufsz += wcslen(PYTHONPATH) + 1; bufsz += wcsnlen_s(PYTHONPATH, MAXPATHLEN+1) + 1;
bufsz += wcslen(argv0_path) + 1; bufsz += wcsnlen_s(argv0_path, MAXPATHLEN+1) + 1;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (userpath) if (!applocal && userpath)
bufsz += wcslen(userpath) + 1; bufsz += wcsnlen_s(userpath, MAXPATHLEN+1) + 1;
if (machinepath) if (!applocal && machinepath)
bufsz += wcslen(machinepath) + 1; bufsz += wcsnlen_s(machinepath, MAXPATHLEN+1) + 1;
bufsz += wcslen(zip_path) + 1; bufsz += wcsnlen_s(zip_path, MAXPATHLEN+1) + 1;
#endif #endif
if (envpath != NULL) if (envpath != NULL)
bufsz += wcslen(envpath) + 1; bufsz += wcsnlen_s(envpath, MAXPATHLEN+1) + 1;
module_search_path = buf = PyMem_RawMalloc(bufsz*sizeof(wchar_t)); module_search_path = buf = PyMem_RawMalloc(bufsz*sizeof(wchar_t));
if (buf == NULL) { if (buf == NULL) {
...@@ -636,38 +656,45 @@ calculate_path(void) ...@@ -636,38 +656,45 @@ calculate_path(void)
} }
if (envpath) { 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 = wcschr(buf, L'\0');
*buf++ = DELIM; *buf++ = DELIM;
} }
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (zip_path[0]) { 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 = wcschr(buf, L'\0');
*buf++ = DELIM; *buf++ = DELIM;
} }
if (userpath) { 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 = wcschr(buf, L'\0');
*buf++ = DELIM; *buf++ = DELIM;
PyMem_RawFree(userpath); PyMem_RawFree(userpath);
} }
if (machinepath) { 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 = wcschr(buf, L'\0');
*buf++ = DELIM; *buf++ = DELIM;
PyMem_RawFree(machinepath); PyMem_RawFree(machinepath);
} }
if (pythonhome == NULL) { if (pythonhome == NULL) {
if (!skipdefault) { 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 = wcschr(buf, L'\0');
*buf++ = DELIM;
} }
} }
#else #else
if (pythonhome == NULL) { if (pythonhome == NULL) {
wcscpy(buf, PYTHONPATH); wcscpy(buf, PYTHONPATH);
buf = wcschr(buf, L'\0'); buf = wcschr(buf, L'\0');
*buf++ = DELIM;
} }
#endif /* MS_WINDOWS */ #endif /* MS_WINDOWS */
else { else {
...@@ -681,25 +708,26 @@ calculate_path(void) ...@@ -681,25 +708,26 @@ calculate_path(void)
else else
n = q-p; n = q-p;
if (p[0] == '.' && is_sep(p[1])) { 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'); buf = wcschr(buf, L'\0');
p++; p++;
n--; n--;
} }
wcsncpy(buf, p, n); wcsncpy(buf, p, n);
buf += n; buf += n;
*buf++ = DELIM;
if (q == NULL) if (q == NULL)
break; break;
*buf++ = DELIM;
p = q+1; p = q+1;
} }
} }
if (argv0_path) { if (argv0_path) {
*buf++ = DELIM;
wcscpy(buf, argv0_path); wcscpy(buf, argv0_path);
buf = wcschr(buf, L'\0'); 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 /* Now to pull one last hack/trick. If sys.prefix is
empty, then try and find it somewhere on the paths empty, then try and find it somewhere on the paths
we calculated. We scan backwards, as our general policy we calculated. We scan backwards, as our general policy
......
...@@ -149,6 +149,9 @@ def main(): ...@@ -149,6 +149,9 @@ def main():
copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c)) copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c))
print('Copied {} files'.format(copied)) 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)) total = copy_to_layout(out, rglob(temp, '*', None))
print('Wrote {} files to {}'.format(total, out)) print('Wrote {} files to {}'.format(total, out))
finally: finally:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment