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:
|
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
|
Instances of this class represent loaded shared libraries. Functions in these
|
||||||
libraries use the standard C calling convention, and are assumed to return
|
libraries use the standard C calling convention, and are assumed to return
|
||||||
:c:type:`int`.
|
: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,
|
Windows only: Instances of this class represent loaded shared libraries,
|
||||||
functions in these libraries use the ``stdcall`` calling convention, and are
|
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.
|
: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,
|
Windows only: Instances of this class represent loaded shared libraries,
|
||||||
functions in these libraries use the ``stdcall`` calling convention, and are
|
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
|
:func:`ctypes.set_last_error` are used to request and change the ctypes private
|
||||||
copy of the windows error code.
|
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
|
.. data:: RTLD_GLOBAL
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
|
|
|
@ -3079,6 +3079,36 @@ to be ignored.
|
||||||
:func:`signal.signal`.
|
: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, ...)
|
.. function:: execl(path, arg0, arg1, ...)
|
||||||
execle(path, arg0, arg1, ..., env)
|
execle(path, arg0, arg1, ..., env)
|
||||||
execlp(file, arg0, arg1, ...)
|
execlp(file, arg0, arg1, ...)
|
||||||
|
|
|
@ -168,6 +168,16 @@ asyncio
|
||||||
On Windows, the default event loop is now :class:`~asyncio.ProactorEventLoop`.
|
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
|
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
|
that returns the product of a 'start' value (default: 1) times an iterable of
|
||||||
numbers. (Contributed by Pablo Galindo in :issue:`35606`)
|
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
|
os.path
|
||||||
-------
|
-------
|
||||||
|
@ -727,6 +744,19 @@ Changes in the Python API
|
||||||
environment variable and does not use :envvar:`HOME`, which is not normally
|
environment variable and does not use :envvar:`HOME`, which is not normally
|
||||||
set for regular user accounts.
|
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
|
Changes in the C API
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
@ -326,7 +326,8 @@ class CDLL(object):
|
||||||
|
|
||||||
def __init__(self, name, mode=DEFAULT_MODE, handle=None,
|
def __init__(self, name, mode=DEFAULT_MODE, handle=None,
|
||||||
use_errno=False,
|
use_errno=False,
|
||||||
use_last_error=False):
|
use_last_error=False,
|
||||||
|
winmode=None):
|
||||||
self._name = name
|
self._name = name
|
||||||
flags = self._func_flags_
|
flags = self._func_flags_
|
||||||
if use_errno:
|
if use_errno:
|
||||||
|
@ -341,6 +342,15 @@ class CDLL(object):
|
||||||
"""
|
"""
|
||||||
if name and name.endswith(")") and ".a(" in name:
|
if name and name.endswith(")") and ".a(" in name:
|
||||||
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
|
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):
|
class _FuncPtr(_CFuncPtr):
|
||||||
_flags_ = flags
|
_flags_ = flags
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
from ctypes import *
|
from ctypes import *
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import sysconfig
|
||||||
import unittest
|
import unittest
|
||||||
import test.support
|
import test.support
|
||||||
from ctypes.util import find_library
|
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'
|
# This is the real test: call the function via 'call_function'
|
||||||
self.assertEqual(0, call_function(proc, (None,)))
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
37
Lib/os.py
37
Lib/os.py
|
@ -1070,3 +1070,40 @@ class PathLike(abc.ABC):
|
||||||
@classmethod
|
@classmethod
|
||||||
def __subclasshook__(cls, subclass):
|
def __subclasshook__(cls, subclass):
|
||||||
return hasattr(subclass, '__fspath__')
|
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 platform
|
||||||
import py_compile
|
import py_compile
|
||||||
import random
|
import random
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
@ -17,6 +19,7 @@ import unittest.mock as mock
|
||||||
import textwrap
|
import textwrap
|
||||||
import errno
|
import errno
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import glob
|
||||||
|
|
||||||
import test.support
|
import test.support
|
||||||
from test.support import (
|
from test.support import (
|
||||||
|
@ -460,6 +463,51 @@ class ImportTests(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
del sys.path[0]
|
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
|
@skip_if_dont_write_bytecode
|
||||||
class FilePermissionTests(unittest.TestCase):
|
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[] =
|
static const char load_library_doc[] =
|
||||||
"LoadLibrary(name) -> handle\n\
|
"LoadLibrary(name, load_flags) -> handle\n\
|
||||||
\n\
|
\n\
|
||||||
Load an executable (usually a DLL), and return a handle to it.\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\
|
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)
|
static PyObject *load_library(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
const WCHAR *name;
|
const WCHAR *name;
|
||||||
PyObject *nameobj;
|
PyObject *nameobj;
|
||||||
PyObject *ignored;
|
int load_flags = 0;
|
||||||
HMODULE hMod;
|
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;
|
return NULL;
|
||||||
|
|
||||||
name = _PyUnicode_AsUnicode(nameobj);
|
name = _PyUnicode_AsUnicode(nameobj);
|
||||||
|
@ -1271,11 +1273,22 @@ static PyObject *load_library(PyObject *self, PyObject *args)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
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
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
if (!hMod)
|
if (err == ERROR_MOD_NOT_FOUND) {
|
||||||
return PyErr_SetFromWindowsErr(GetLastError());
|
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
|
#ifdef _WIN64
|
||||||
return PyLong_FromVoidPtr(hMod);
|
return PyLong_FromVoidPtr(hMod);
|
||||||
#else
|
#else
|
||||||
|
@ -1291,15 +1304,18 @@ static PyObject *free_library(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
void *hMod;
|
void *hMod;
|
||||||
BOOL result;
|
BOOL result;
|
||||||
|
DWORD err;
|
||||||
if (!PyArg_ParseTuple(args, "O&:FreeLibrary", &_parse_voidp, &hMod))
|
if (!PyArg_ParseTuple(args, "O&:FreeLibrary", &_parse_voidp, &hMod))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
result = FreeLibrary((HMODULE)hMod);
|
result = FreeLibrary((HMODULE)hMod);
|
||||||
|
err = result ? 0 : GetLastError();
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
if (!result)
|
if (!result) {
|
||||||
return PyErr_SetFromWindowsErr(GetLastError());
|
return PyErr_SetFromWindowsErr(err);
|
||||||
|
}
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7961,6 +7961,94 @@ exit:
|
||||||
|
|
||||||
#endif /* defined(HAVE_GETRANDOM_SYSCALL) */
|
#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
|
#ifndef OS_TTYNAME_METHODDEF
|
||||||
#define OS_TTYNAME_METHODDEF
|
#define OS_TTYNAME_METHODDEF
|
||||||
#endif /* !defined(OS_TTYNAME_METHODDEF) */
|
#endif /* !defined(OS_TTYNAME_METHODDEF) */
|
||||||
|
@ -8480,4 +8568,12 @@ exit:
|
||||||
#ifndef OS_GETRANDOM_METHODDEF
|
#ifndef OS_GETRANDOM_METHODDEF
|
||||||
#define OS_GETRANDOM_METHODDEF
|
#define OS_GETRANDOM_METHODDEF
|
||||||
#endif /* !defined(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 *
|
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' */
|
/* XXX - see win32_error for comments on 'function' */
|
||||||
errno = GetLastError();
|
|
||||||
if (filename)
|
if (filename)
|
||||||
return PyErr_SetExcFromWindowsErrWithFilenameObject(
|
return PyErr_SetExcFromWindowsErrWithFilenameObject(
|
||||||
PyExc_OSError,
|
PyExc_OSError,
|
||||||
errno,
|
err,
|
||||||
filename);
|
filename);
|
||||||
else
|
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 */
|
#endif /* MS_WINDOWS */
|
||||||
|
@ -13161,6 +13167,113 @@ error:
|
||||||
}
|
}
|
||||||
#endif /* HAVE_GETRANDOM_SYSCALL */
|
#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[] = {
|
static PyMethodDef posix_methods[] = {
|
||||||
|
|
||||||
|
@ -13349,6 +13462,10 @@ static PyMethodDef posix_methods[] = {
|
||||||
OS_SCANDIR_METHODDEF
|
OS_SCANDIR_METHODDEF
|
||||||
OS_FSPATH_METHODDEF
|
OS_FSPATH_METHODDEF
|
||||||
OS_GETRANDOM_METHODDEF
|
OS_GETRANDOM_METHODDEF
|
||||||
|
#ifdef MS_WINDOWS
|
||||||
|
OS__ADD_DLL_DIRECTORY_METHODDEF
|
||||||
|
OS__REMOVE_DLL_DIRECTORY_METHODDEF
|
||||||
|
#endif
|
||||||
{NULL, NULL} /* Sentinel */
|
{NULL, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13826,6 +13943,14 @@ all_ins(PyObject *m)
|
||||||
if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1;
|
if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1;
|
||||||
#endif
|
#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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -215,12 +215,14 @@ dl_funcptr _PyImport_FindSharedFuncptrWindows(const char *prefix,
|
||||||
#if HAVE_SXS
|
#if HAVE_SXS
|
||||||
cookie = _Py_ActivateActCtx();
|
cookie = _Py_ActivateActCtx();
|
||||||
#endif
|
#endif
|
||||||
/* We use LoadLibraryEx so Windows looks for dependent DLLs
|
/* bpo-36085: We use LoadLibraryEx with restricted search paths
|
||||||
in directory of pathname first. */
|
to avoid DLL preloading attacks and enable use of the
|
||||||
/* XXX This call doesn't exist in Windows CE */
|
AddDllDirectory function. We add SEARCH_DLL_LOAD_DIR to
|
||||||
|
ensure DLLs adjacent to the PYD are preferred. */
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
hDLL = LoadLibraryExW(wpathname, NULL,
|
hDLL = LoadLibraryExW(wpathname, NULL,
|
||||||
LOAD_WITH_ALTERED_SEARCH_PATH);
|
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
|
||||||
|
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
#if HAVE_SXS
|
#if HAVE_SXS
|
||||||
_Py_DeactivateActCtx(cookie);
|
_Py_DeactivateActCtx(cookie);
|
||||||
|
|
Loading…
Reference in New Issue