bpo-36085: Enable better DLL resolution on Windows (GH-12302)
This commit is contained in:
parent
32119e10b7
commit
2438cdf0e9
|
@ -1322,14 +1322,14 @@ There are several ways to load shared libraries into the Python process. One
|
|||
way is to instantiate one of the following classes:
|
||||
|
||||
|
||||
.. class:: CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
|
||||
.. class:: CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0)
|
||||
|
||||
Instances of this class represent loaded shared libraries. Functions in these
|
||||
libraries use the standard C calling convention, and are assumed to return
|
||||
:c:type:`int`.
|
||||
|
||||
|
||||
.. class:: OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
|
||||
.. class:: OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0)
|
||||
|
||||
Windows only: Instances of this class represent loaded shared libraries,
|
||||
functions in these libraries use the ``stdcall`` calling convention, and are
|
||||
|
@ -1342,7 +1342,7 @@ way is to instantiate one of the following classes:
|
|||
:exc:`WindowsError` used to be raised.
|
||||
|
||||
|
||||
.. class:: WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)
|
||||
.. class:: WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0)
|
||||
|
||||
Windows only: Instances of this class represent loaded shared libraries,
|
||||
functions in these libraries use the ``stdcall`` calling convention, and are
|
||||
|
@ -1394,6 +1394,17 @@ the Windows error code which is managed by the :func:`GetLastError` and
|
|||
:func:`ctypes.set_last_error` are used to request and change the ctypes private
|
||||
copy of the windows error code.
|
||||
|
||||
The *winmode* parameter is used on Windows to specify how the library is loaded
|
||||
(since *mode* is ignored). It takes any value that is valid for the Win32 API
|
||||
``LoadLibraryEx`` flags parameter. When omitted, the default is to use the flags
|
||||
that result in the most secure DLL load to avoiding issues such as DLL
|
||||
hijacking. Passing the full path to the DLL is the safest way to ensure the
|
||||
correct library and dependencies are loaded.
|
||||
|
||||
.. versionchanged:: 3.8
|
||||
Added *winmode* parameter.
|
||||
|
||||
|
||||
.. data:: RTLD_GLOBAL
|
||||
:noindex:
|
||||
|
||||
|
|
|
@ -3079,6 +3079,36 @@ to be ignored.
|
|||
:func:`signal.signal`.
|
||||
|
||||
|
||||
.. function:: add_dll_directory(path)
|
||||
|
||||
Add a path to the DLL search path.
|
||||
|
||||
This search path is used when resolving dependencies for imported
|
||||
extension modules (the module itself is resolved through sys.path),
|
||||
and also by :mod:`ctypes`.
|
||||
|
||||
Remove the directory by calling **close()** on the returned object
|
||||
or using it in a :keyword:`with` statement.
|
||||
|
||||
See the `Microsoft documentation
|
||||
<https://msdn.microsoft.com/44228cf2-6306-466c-8f16-f513cd3ba8b5>`_
|
||||
for more information about how DLLs are loaded.
|
||||
|
||||
.. availability:: Windows.
|
||||
|
||||
.. versionadded:: 3.8
|
||||
Previous versions of CPython would resolve DLLs using the default
|
||||
behavior for the current process. This led to inconsistencies,
|
||||
such as only sometimes searching :envvar:`PATH` or the current
|
||||
working directory, and OS functions such as ``AddDllDirectory``
|
||||
having no effect.
|
||||
|
||||
In 3.8, the two primary ways DLLs are loaded now explicitly
|
||||
override the process-wide behavior to ensure consistency. See the
|
||||
:ref:`porting notes <bpo-36085-whatsnew>` for information on
|
||||
updating libraries.
|
||||
|
||||
|
||||
.. function:: execl(path, arg0, arg1, ...)
|
||||
execle(path, arg0, arg1, ..., env)
|
||||
execlp(file, arg0, arg1, ...)
|
||||
|
|
|
@ -168,6 +168,16 @@ asyncio
|
|||
On Windows, the default event loop is now :class:`~asyncio.ProactorEventLoop`.
|
||||
|
||||
|
||||
ctypes
|
||||
------
|
||||
|
||||
On Windows, :class:`~ctypes.CDLL` and subclasses now accept a *winmode* parameter
|
||||
to specify flags for the underlying ``LoadLibraryEx`` call. The default flags are
|
||||
set to only load DLL dependencies from trusted locations, including the path
|
||||
where the DLL is stored (if a full or partial path is used to load the initial
|
||||
DLL) and paths added by :func:`~os.add_dll_directory`.
|
||||
|
||||
|
||||
gettext
|
||||
-------
|
||||
|
||||
|
@ -238,6 +248,13 @@ Added new function, :func:`math.prod`, as analogous function to :func:`sum`
|
|||
that returns the product of a 'start' value (default: 1) times an iterable of
|
||||
numbers. (Contributed by Pablo Galindo in :issue:`35606`)
|
||||
|
||||
os
|
||||
--
|
||||
|
||||
Added new function :func:`~os.add_dll_directory` on Windows for providing
|
||||
additional search paths for native dependencies when importing extension
|
||||
modules or loading DLLs using :mod:`ctypes`.
|
||||
|
||||
|
||||
os.path
|
||||
-------
|
||||
|
@ -727,6 +744,19 @@ Changes in the Python API
|
|||
environment variable and does not use :envvar:`HOME`, which is not normally
|
||||
set for regular user accounts.
|
||||
|
||||
.. _bpo-36085-whatsnew:
|
||||
|
||||
* DLL dependencies for extension modules and DLLs loaded with :mod:`ctypes` on
|
||||
Windows are now resolved more securely. Only the system paths, the directory
|
||||
containing the DLL or PYD file, and directories added with
|
||||
:func:`~os.add_dll_directory` are searched for load-time dependencies.
|
||||
Specifically, :envvar:`PATH` and the current working directory are no longer
|
||||
used, and modifications to these will no longer have any effect on normal DLL
|
||||
resolution. If your application relies on these mechanisms, you should check
|
||||
for :func:`~os.add_dll_directory` and if it exists, use it to add your DLLs
|
||||
directory while loading your library.
|
||||
(See :issue:`36085`.)
|
||||
|
||||
|
||||
Changes in the C API
|
||||
--------------------
|
||||
|
|
|
@ -326,7 +326,8 @@ class CDLL(object):
|
|||
|
||||
def __init__(self, name, mode=DEFAULT_MODE, handle=None,
|
||||
use_errno=False,
|
||||
use_last_error=False):
|
||||
use_last_error=False,
|
||||
winmode=None):
|
||||
self._name = name
|
||||
flags = self._func_flags_
|
||||
if use_errno:
|
||||
|
@ -341,6 +342,15 @@ class CDLL(object):
|
|||
"""
|
||||
if name and name.endswith(")") and ".a(" in name:
|
||||
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
|
||||
if _os.name == "nt":
|
||||
if winmode is not None:
|
||||
mode = winmode
|
||||
else:
|
||||
import nt
|
||||
mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
|
||||
if '/' in name or '\\' in name:
|
||||
self._name = nt._getfullpathname(self._name)
|
||||
mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
|
||||
|
||||
class _FuncPtr(_CFuncPtr):
|
||||
_flags_ = flags
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from ctypes import *
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import unittest
|
||||
import test.support
|
||||
from ctypes.util import find_library
|
||||
|
@ -112,5 +115,65 @@ class LoaderTest(unittest.TestCase):
|
|||
# This is the real test: call the function via 'call_function'
|
||||
self.assertEqual(0, call_function(proc, (None,)))
|
||||
|
||||
@unittest.skipUnless(os.name == "nt",
|
||||
'test specific to Windows')
|
||||
def test_load_dll_with_flags(self):
|
||||
_sqlite3 = test.support.import_module("_sqlite3")
|
||||
src = _sqlite3.__file__
|
||||
if src.lower().endswith("_d.pyd"):
|
||||
ext = "_d.dll"
|
||||
else:
|
||||
ext = ".dll"
|
||||
|
||||
with test.support.temp_dir() as tmp:
|
||||
# We copy two files and load _sqlite3.dll (formerly .pyd),
|
||||
# which has a dependency on sqlite3.dll. Then we test
|
||||
# loading it in subprocesses to avoid it starting in memory
|
||||
# for each test.
|
||||
target = os.path.join(tmp, "_sqlite3.dll")
|
||||
shutil.copy(src, target)
|
||||
shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext),
|
||||
os.path.join(tmp, "sqlite3" + ext))
|
||||
|
||||
def should_pass(command):
|
||||
with self.subTest(command):
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-c",
|
||||
"from ctypes import *; import nt;" + command],
|
||||
cwd=tmp
|
||||
)
|
||||
|
||||
def should_fail(command):
|
||||
with self.subTest(command):
|
||||
with self.assertRaises(subprocess.CalledProcessError):
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-c",
|
||||
"from ctypes import *; import nt;" + command],
|
||||
cwd=tmp, stderr=subprocess.STDOUT,
|
||||
)
|
||||
|
||||
# Default load should not find this in CWD
|
||||
should_fail("WinDLL('_sqlite3.dll')")
|
||||
|
||||
# Relative path (but not just filename) should succeed
|
||||
should_pass("WinDLL('./_sqlite3.dll')")
|
||||
|
||||
# Insecure load flags should succeed
|
||||
should_pass("WinDLL('_sqlite3.dll', winmode=0)")
|
||||
|
||||
# Full path load without DLL_LOAD_DIR shouldn't find dependency
|
||||
should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " +
|
||||
"winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)")
|
||||
|
||||
# Full path load with DLL_LOAD_DIR should succeed
|
||||
should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " +
|
||||
"winmode=nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)")
|
||||
|
||||
# User-specified directory should succeed
|
||||
should_pass("import os; p = os.add_dll_directory(os.getcwd());" +
|
||||
"WinDLL('_sqlite3.dll'); p.close()")
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
37
Lib/os.py
37
Lib/os.py
|
@ -1070,3 +1070,40 @@ class PathLike(abc.ABC):
|
|||
@classmethod
|
||||
def __subclasshook__(cls, subclass):
|
||||
return hasattr(subclass, '__fspath__')
|
||||
|
||||
|
||||
if name == 'nt':
|
||||
class _AddedDllDirectory:
|
||||
def __init__(self, path, cookie, remove_dll_directory):
|
||||
self.path = path
|
||||
self._cookie = cookie
|
||||
self._remove_dll_directory = remove_dll_directory
|
||||
def close(self):
|
||||
self._remove_dll_directory(self._cookie)
|
||||
self.path = None
|
||||
def __enter__(self):
|
||||
return self
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
def __repr__(self):
|
||||
if self.path:
|
||||
return "<AddedDllDirectory({!r})>".format(self.path)
|
||||
return "<AddedDllDirectory()>"
|
||||
|
||||
def add_dll_directory(path):
|
||||
"""Add a path to the DLL search path.
|
||||
|
||||
This search path is used when resolving dependencies for imported
|
||||
extension modules (the module itself is resolved through sys.path),
|
||||
and also by ctypes.
|
||||
|
||||
Remove the directory by calling close() on the returned object or
|
||||
using it in a with statement.
|
||||
"""
|
||||
import nt
|
||||
cookie = nt._add_dll_directory(path)
|
||||
return _AddedDllDirectory(
|
||||
path,
|
||||
cookie,
|
||||
nt._remove_dll_directory
|
||||
)
|
||||
|
|
|
@ -8,6 +8,8 @@ import os
|
|||
import platform
|
||||
import py_compile
|
||||
import random
|
||||
import shutil
|
||||
import subprocess
|
||||
import stat
|
||||
import sys
|
||||
import threading
|
||||
|
@ -17,6 +19,7 @@ import unittest.mock as mock
|
|||
import textwrap
|
||||
import errno
|
||||
import contextlib
|
||||
import glob
|
||||
|
||||
import test.support
|
||||
from test.support import (
|
||||
|
@ -460,6 +463,51 @@ class ImportTests(unittest.TestCase):
|
|||
finally:
|
||||
del sys.path[0]
|
||||
|
||||
@unittest.skipUnless(sys.platform == "win32", "Windows-specific")
|
||||
def test_dll_dependency_import(self):
|
||||
from _winapi import GetModuleFileName
|
||||
dllname = GetModuleFileName(sys.dllhandle)
|
||||
pydname = importlib.util.find_spec("_sqlite3").origin
|
||||
depname = os.path.join(
|
||||
os.path.dirname(pydname),
|
||||
"sqlite3{}.dll".format("_d" if "_d" in pydname else ""))
|
||||
|
||||
with test.support.temp_dir() as tmp:
|
||||
tmp2 = os.path.join(tmp, "DLLs")
|
||||
os.mkdir(tmp2)
|
||||
|
||||
pyexe = os.path.join(tmp, os.path.basename(sys.executable))
|
||||
shutil.copy(sys.executable, pyexe)
|
||||
shutil.copy(dllname, tmp)
|
||||
for f in glob.glob(os.path.join(sys.prefix, "vcruntime*.dll")):
|
||||
shutil.copy(f, tmp)
|
||||
|
||||
shutil.copy(pydname, tmp2)
|
||||
|
||||
env = None
|
||||
env = {k.upper(): os.environ[k] for k in os.environ}
|
||||
env["PYTHONPATH"] = tmp2 + ";" + os.path.dirname(os.__file__)
|
||||
|
||||
# Test 1: import with added DLL directory
|
||||
subprocess.check_call([
|
||||
pyexe, "-Sc", ";".join([
|
||||
"import os",
|
||||
"p = os.add_dll_directory({!r})".format(
|
||||
os.path.dirname(depname)),
|
||||
"import _sqlite3",
|
||||
"p.close"
|
||||
])],
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env,
|
||||
cwd=os.path.dirname(pyexe))
|
||||
|
||||
# Test 2: import with DLL adjacent to PYD
|
||||
shutil.copy(depname, tmp2)
|
||||
subprocess.check_call([pyexe, "-Sc", "import _sqlite3"],
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env,
|
||||
cwd=os.path.dirname(pyexe))
|
||||
|
||||
|
||||
@skip_if_dont_write_bytecode
|
||||
class FilePermissionTests(unittest.TestCase):
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Enable better DLL resolution on Windows by using safe DLL search paths and
|
||||
adding :func:`os.add_dll_directory`.
|
|
@ -1251,19 +1251,21 @@ static PyObject *format_error(PyObject *self, PyObject *args)
|
|||
}
|
||||
|
||||
static const char load_library_doc[] =
|
||||
"LoadLibrary(name) -> handle\n\
|
||||
"LoadLibrary(name, load_flags) -> handle\n\
|
||||
\n\
|
||||
Load an executable (usually a DLL), and return a handle to it.\n\
|
||||
The handle may be used to locate exported functions in this\n\
|
||||
module.\n";
|
||||
module. load_flags are as defined for LoadLibraryEx in the\n\
|
||||
Windows API.\n";
|
||||
static PyObject *load_library(PyObject *self, PyObject *args)
|
||||
{
|
||||
const WCHAR *name;
|
||||
PyObject *nameobj;
|
||||
PyObject *ignored;
|
||||
int load_flags = 0;
|
||||
HMODULE hMod;
|
||||
DWORD err;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "U|O:LoadLibrary", &nameobj, &ignored))
|
||||
if (!PyArg_ParseTuple(args, "U|i:LoadLibrary", &nameobj, &load_flags))
|
||||
return NULL;
|
||||
|
||||
name = _PyUnicode_AsUnicode(nameobj);
|
||||
|
@ -1271,11 +1273,22 @@ static PyObject *load_library(PyObject *self, PyObject *args)
|
|||
return NULL;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
hMod = LoadLibraryW(name);
|
||||
/* bpo-36085: Limit DLL search directories to avoid pre-loading
|
||||
* attacks and enable use of the AddDllDirectory function.
|
||||
*/
|
||||
hMod = LoadLibraryExW(name, NULL, (DWORD)load_flags);
|
||||
err = hMod ? 0 : GetLastError();
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (!hMod)
|
||||
return PyErr_SetFromWindowsErr(GetLastError());
|
||||
if (err == ERROR_MOD_NOT_FOUND) {
|
||||
PyErr_Format(PyExc_FileNotFoundError,
|
||||
("Could not find module '%.500S'. Try using "
|
||||
"the full path with constructor syntax."),
|
||||
nameobj);
|
||||
return NULL;
|
||||
} else if (err) {
|
||||
return PyErr_SetFromWindowsErr(err);
|
||||
}
|
||||
#ifdef _WIN64
|
||||
return PyLong_FromVoidPtr(hMod);
|
||||
#else
|
||||
|
@ -1291,15 +1304,18 @@ static PyObject *free_library(PyObject *self, PyObject *args)
|
|||
{
|
||||
void *hMod;
|
||||
BOOL result;
|
||||
DWORD err;
|
||||
if (!PyArg_ParseTuple(args, "O&:FreeLibrary", &_parse_voidp, &hMod))
|
||||
return NULL;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
result = FreeLibrary((HMODULE)hMod);
|
||||
err = result ? 0 : GetLastError();
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (!result)
|
||||
return PyErr_SetFromWindowsErr(GetLastError());
|
||||
if (!result) {
|
||||
return PyErr_SetFromWindowsErr(err);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
|
|
@ -7961,6 +7961,94 @@ exit:
|
|||
|
||||
#endif /* defined(HAVE_GETRANDOM_SYSCALL) */
|
||||
|
||||
#if defined(MS_WINDOWS)
|
||||
|
||||
PyDoc_STRVAR(os__add_dll_directory__doc__,
|
||||
"_add_dll_directory($module, /, path)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Add a path to the DLL search path.\n"
|
||||
"\n"
|
||||
"This search path is used when resolving dependencies for imported\n"
|
||||
"extension modules (the module itself is resolved through sys.path),\n"
|
||||
"and also by ctypes.\n"
|
||||
"\n"
|
||||
"Returns an opaque value that may be passed to os.remove_dll_directory\n"
|
||||
"to remove this directory from the search path.");
|
||||
|
||||
#define OS__ADD_DLL_DIRECTORY_METHODDEF \
|
||||
{"_add_dll_directory", (PyCFunction)(void(*)(void))os__add_dll_directory, METH_FASTCALL|METH_KEYWORDS, os__add_dll_directory__doc__},
|
||||
|
||||
static PyObject *
|
||||
os__add_dll_directory_impl(PyObject *module, path_t *path);
|
||||
|
||||
static PyObject *
|
||||
os__add_dll_directory(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
static const char * const _keywords[] = {"path", NULL};
|
||||
static _PyArg_Parser _parser = {NULL, _keywords, "_add_dll_directory", 0};
|
||||
PyObject *argsbuf[1];
|
||||
path_t path = PATH_T_INITIALIZE("_add_dll_directory", "path", 0, 0);
|
||||
|
||||
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
|
||||
if (!args) {
|
||||
goto exit;
|
||||
}
|
||||
if (!path_converter(args[0], &path)) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = os__add_dll_directory_impl(module, &path);
|
||||
|
||||
exit:
|
||||
/* Cleanup for path */
|
||||
path_cleanup(&path);
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* defined(MS_WINDOWS) */
|
||||
|
||||
#if defined(MS_WINDOWS)
|
||||
|
||||
PyDoc_STRVAR(os__remove_dll_directory__doc__,
|
||||
"_remove_dll_directory($module, /, cookie)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Removes a path from the DLL search path.\n"
|
||||
"\n"
|
||||
"The parameter is an opaque value that was returned from\n"
|
||||
"os.add_dll_directory. You can only remove directories that you added\n"
|
||||
"yourself.");
|
||||
|
||||
#define OS__REMOVE_DLL_DIRECTORY_METHODDEF \
|
||||
{"_remove_dll_directory", (PyCFunction)(void(*)(void))os__remove_dll_directory, METH_FASTCALL|METH_KEYWORDS, os__remove_dll_directory__doc__},
|
||||
|
||||
static PyObject *
|
||||
os__remove_dll_directory_impl(PyObject *module, PyObject *cookie);
|
||||
|
||||
static PyObject *
|
||||
os__remove_dll_directory(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
static const char * const _keywords[] = {"cookie", NULL};
|
||||
static _PyArg_Parser _parser = {NULL, _keywords, "_remove_dll_directory", 0};
|
||||
PyObject *argsbuf[1];
|
||||
PyObject *cookie;
|
||||
|
||||
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
|
||||
if (!args) {
|
||||
goto exit;
|
||||
}
|
||||
cookie = args[0];
|
||||
return_value = os__remove_dll_directory_impl(module, cookie);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* defined(MS_WINDOWS) */
|
||||
|
||||
#ifndef OS_TTYNAME_METHODDEF
|
||||
#define OS_TTYNAME_METHODDEF
|
||||
#endif /* !defined(OS_TTYNAME_METHODDEF) */
|
||||
|
@ -8480,4 +8568,12 @@ exit:
|
|||
#ifndef OS_GETRANDOM_METHODDEF
|
||||
#define OS_GETRANDOM_METHODDEF
|
||||
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
|
||||
/*[clinic end generated code: output=1a9c62f5841221ae input=a9049054013a1b77]*/
|
||||
|
||||
#ifndef OS__ADD_DLL_DIRECTORY_METHODDEF
|
||||
#define OS__ADD_DLL_DIRECTORY_METHODDEF
|
||||
#endif /* !defined(OS__ADD_DLL_DIRECTORY_METHODDEF) */
|
||||
|
||||
#ifndef OS__REMOVE_DLL_DIRECTORY_METHODDEF
|
||||
#define OS__REMOVE_DLL_DIRECTORY_METHODDEF
|
||||
#endif /* !defined(OS__REMOVE_DLL_DIRECTORY_METHODDEF) */
|
||||
/*[clinic end generated code: output=ab36ec0376a422ae input=a9049054013a1b77]*/
|
||||
|
|
|
@ -1442,17 +1442,23 @@ win32_error(const char* function, const char* filename)
|
|||
}
|
||||
|
||||
static PyObject *
|
||||
win32_error_object(const char* function, PyObject* filename)
|
||||
win32_error_object_err(const char* function, PyObject* filename, DWORD err)
|
||||
{
|
||||
/* XXX - see win32_error for comments on 'function' */
|
||||
errno = GetLastError();
|
||||
if (filename)
|
||||
return PyErr_SetExcFromWindowsErrWithFilenameObject(
|
||||
PyExc_OSError,
|
||||
errno,
|
||||
err,
|
||||
filename);
|
||||
else
|
||||
return PyErr_SetFromWindowsErr(errno);
|
||||
return PyErr_SetFromWindowsErr(err);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
win32_error_object(const char* function, PyObject* filename)
|
||||
{
|
||||
errno = GetLastError();
|
||||
return win32_error_object_err(function, filename, errno);
|
||||
}
|
||||
|
||||
#endif /* MS_WINDOWS */
|
||||
|
@ -13161,6 +13167,113 @@ error:
|
|||
}
|
||||
#endif /* HAVE_GETRANDOM_SYSCALL */
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
/* bpo-36085: Helper functions for managing DLL search directories
|
||||
* on win32
|
||||
*/
|
||||
|
||||
typedef DLL_DIRECTORY_COOKIE (WINAPI *PAddDllDirectory)(PCWSTR newDirectory);
|
||||
typedef BOOL (WINAPI *PRemoveDllDirectory)(DLL_DIRECTORY_COOKIE cookie);
|
||||
|
||||
/*[clinic input]
|
||||
os._add_dll_directory
|
||||
|
||||
path: path_t
|
||||
|
||||
Add a path to the DLL search path.
|
||||
|
||||
This search path is used when resolving dependencies for imported
|
||||
extension modules (the module itself is resolved through sys.path),
|
||||
and also by ctypes.
|
||||
|
||||
Returns an opaque value that may be passed to os.remove_dll_directory
|
||||
to remove this directory from the search path.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
os__add_dll_directory_impl(PyObject *module, path_t *path)
|
||||
/*[clinic end generated code: output=80b025daebb5d683 input=1de3e6c13a5808c8]*/
|
||||
{
|
||||
HMODULE hKernel32;
|
||||
PAddDllDirectory AddDllDirectory;
|
||||
DLL_DIRECTORY_COOKIE cookie = 0;
|
||||
DWORD err = 0;
|
||||
|
||||
/* For Windows 7, we have to load this. As this will be a fairly
|
||||
infrequent operation, just do it each time. Kernel32 is always
|
||||
loaded. */
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
if (!(hKernel32 = GetModuleHandleW(L"kernel32")) ||
|
||||
!(AddDllDirectory = (PAddDllDirectory)GetProcAddress(
|
||||
hKernel32, "AddDllDirectory")) ||
|
||||
!(cookie = (*AddDllDirectory)(path->wide))) {
|
||||
err = GetLastError();
|
||||
}
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (err) {
|
||||
return win32_error_object_err("add_dll_directory",
|
||||
path->object, err);
|
||||
}
|
||||
|
||||
return PyCapsule_New(cookie, "DLL directory cookie", NULL);
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
os._remove_dll_directory
|
||||
|
||||
cookie: object
|
||||
|
||||
Removes a path from the DLL search path.
|
||||
|
||||
The parameter is an opaque value that was returned from
|
||||
os.add_dll_directory. You can only remove directories that you added
|
||||
yourself.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
os__remove_dll_directory_impl(PyObject *module, PyObject *cookie)
|
||||
/*[clinic end generated code: output=594350433ae535bc input=c1d16a7e7d9dc5dc]*/
|
||||
{
|
||||
HMODULE hKernel32;
|
||||
PRemoveDllDirectory RemoveDllDirectory;
|
||||
DLL_DIRECTORY_COOKIE cookieValue;
|
||||
DWORD err = 0;
|
||||
|
||||
if (!PyCapsule_IsValid(cookie, "DLL directory cookie")) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Provided cookie was not returned from os.add_dll_directory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cookieValue = (DLL_DIRECTORY_COOKIE)PyCapsule_GetPointer(
|
||||
cookie, "DLL directory cookie");
|
||||
|
||||
/* For Windows 7, we have to load this. As this will be a fairly
|
||||
infrequent operation, just do it each time. Kernel32 is always
|
||||
loaded. */
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
if (!(hKernel32 = GetModuleHandleW(L"kernel32")) ||
|
||||
!(RemoveDllDirectory = (PRemoveDllDirectory)GetProcAddress(
|
||||
hKernel32, "RemoveDllDirectory")) ||
|
||||
!(*RemoveDllDirectory)(cookieValue)) {
|
||||
err = GetLastError();
|
||||
}
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (err) {
|
||||
return win32_error_object_err("remove_dll_directory",
|
||||
NULL, err);
|
||||
}
|
||||
|
||||
if (PyCapsule_SetName(cookie, NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static PyMethodDef posix_methods[] = {
|
||||
|
||||
|
@ -13349,6 +13462,10 @@ static PyMethodDef posix_methods[] = {
|
|||
OS_SCANDIR_METHODDEF
|
||||
OS_FSPATH_METHODDEF
|
||||
OS_GETRANDOM_METHODDEF
|
||||
#ifdef MS_WINDOWS
|
||||
OS__ADD_DLL_DIRECTORY_METHODDEF
|
||||
OS__REMOVE_DLL_DIRECTORY_METHODDEF
|
||||
#endif
|
||||
{NULL, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
@ -13826,6 +13943,14 @@ all_ins(PyObject *m)
|
|||
if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1;
|
||||
#endif
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
if (PyModule_AddIntConstant(m, "_LOAD_LIBRARY_SEARCH_DEFAULT_DIRS", LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)) return -1;
|
||||
if (PyModule_AddIntConstant(m, "_LOAD_LIBRARY_SEARCH_APPLICATION_DIR", LOAD_LIBRARY_SEARCH_APPLICATION_DIR)) return -1;
|
||||
if (PyModule_AddIntConstant(m, "_LOAD_LIBRARY_SEARCH_SYSTEM32", LOAD_LIBRARY_SEARCH_SYSTEM32)) return -1;
|
||||
if (PyModule_AddIntConstant(m, "_LOAD_LIBRARY_SEARCH_USER_DIRS", LOAD_LIBRARY_SEARCH_USER_DIRS)) return -1;
|
||||
if (PyModule_AddIntConstant(m, "_LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR", LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)) return -1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -215,12 +215,14 @@ dl_funcptr _PyImport_FindSharedFuncptrWindows(const char *prefix,
|
|||
#if HAVE_SXS
|
||||
cookie = _Py_ActivateActCtx();
|
||||
#endif
|
||||
/* We use LoadLibraryEx so Windows looks for dependent DLLs
|
||||
in directory of pathname first. */
|
||||
/* XXX This call doesn't exist in Windows CE */
|
||||
/* bpo-36085: We use LoadLibraryEx with restricted search paths
|
||||
to avoid DLL preloading attacks and enable use of the
|
||||
AddDllDirectory function. We add SEARCH_DLL_LOAD_DIR to
|
||||
ensure DLLs adjacent to the PYD are preferred. */
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
hDLL = LoadLibraryExW(wpathname, NULL,
|
||||
LOAD_WITH_ALTERED_SEARCH_PATH);
|
||||
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
|
||||
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
|
||||
Py_END_ALLOW_THREADS
|
||||
#if HAVE_SXS
|
||||
_Py_DeactivateActCtx(cookie);
|
||||
|
|
Loading…
Reference in New Issue