bpo-36085: Enable better DLL resolution on Windows (GH-12302)

This commit is contained in:
Steve Dower 2019-03-29 16:37:16 -07:00 committed by GitHub
parent 32119e10b7
commit 2438cdf0e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 492 additions and 22 deletions

View file

@ -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:

View file

@ -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, ...)

View file

@ -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
--------------------

View file

@ -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 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None,
"""
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

View file

@ -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 @@ def test_1703286_B(self):
# 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()

View file

@ -1070,3 +1070,40 @@ def __fspath__(self):
@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
)

View file

@ -8,6 +8,8 @@
import platform
import py_compile
import random
import shutil
import subprocess
import stat
import sys
import threading
@ -17,6 +19,7 @@
import textwrap
import errno
import contextlib
import glob
import test.support
from test.support import (
@ -460,6 +463,51 @@ def run():
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):

View file

@ -0,0 +1,2 @@
Enable better DLL resolution on Windows by using safe DLL search paths and
adding :func:`os.add_dll_directory`.

View file

@ -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;
}

View file

@ -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]*/

View file

@ -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 @@ os_getrandom_impl(PyObject *module, Py_ssize_t size, int flags)
}
#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;
}

View file

@ -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);