Skip to content

CHORE: load bundled Windows driver and auth DLLs from package-local directories - #735

Open
Gaurav Sharma (bewithgaurav) wants to merge 7 commits into
mainfrom
bewithgaurav/win-dll-search-hardening
Open

CHORE: load bundled Windows driver and auth DLLs from package-local directories#735
Gaurav Sharma (bewithgaurav) wants to merge 7 commits into
mainfrom
bewithgaurav/win-dll-search-hardening

Conversation

@bewithgaurav

@bewithgaurav Gaurav Sharma (bewithgaurav) commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Work Item / Issue Reference

AB#47762


Summary

On Windows the vendored ODBC driver (msodbcsql18.dll) and Entra auth DLL (mssql-auth.dll) are loaded with LoadLibraryW, so their dependencies are resolved via the default process search order, which also consults the current working directory and %PATH%. This makes dependency resolution dependent on ambient environment.

This change loads both DLLs with LoadLibraryExW using LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, which keeps the current working directory and %PATH% out of the search and confines dependency resolution to the driver's own directory, the application directory, and System32.

Changes

  • mssql_python/pybind/ddbc_bindings.cpp: LoadLibraryExW with constrained search flags on the driver and auth-DLL loads (replacing LoadLibraryW). The flag constants are #defined defensively in case the build SDK gates them behind an older _WIN32_WINNT.
  • tests/test_026_windows_dll_search.py: a source-contract regression test that fails if the loader reintroduces a bare LoadLibraryW call or drops the constrained-search flags. It is deliberately a source check, not a runtime one — the search restriction only manifests at DLL-resolution time on Windows and cannot be observed without dropping a file on disk, and a "does it still load" check would pass on the unhardened code too (any host with the dependency already in System32), so it would guard nothing.

Scope / notes

  • macOS and Linux are unaffected — the change is entirely within #ifdef _WIN32 blocks, and the shipped Unix libraries already carry @loader_path / $ORIGIN.
  • No global process state is mutated (SetDefaultDllDirectories / AddDllDirectory are intentionally not used), so a consuming application's own DLL resolution is unchanged. The bundled VC++ runtime already loads from the trusted copy next to the pybind extension (built /MD), so no extra search directory is needed.
  • Requires Windows 8+ (or Windows 7 + KB2533623), which the supported Python versions already imply.

Validation
This is a Windows-only native change and is validated by the ADO Windows build/test legs (x64/arm64/x86); it cannot be exercised on a non-Windows dev host.

…irectories

On Windows the vendored ODBC driver (msodbcsql18.dll) and Entra auth DLL
(mssql-auth.dll) were loaded with LoadLibraryW, so their dependencies -
including the bundled VC++ runtime - were resolved via the default process
search order, which also consults the current working directory and %PATH%.

Load them with LoadLibraryExW using
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, and
register the driver directory plus its co-located vcredist subfolder via
AddDllDirectory, so dependency resolution is confined to trusted,
package-local directories (System32, the application directory, and the
package's own folders). The vcredist subfolder is added explicitly because
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR only covers each DLL's own directory.

macOS and Linux are unaffected: the change is inside _WIN32 blocks, and the
shipped Unix libraries already carry @loader_path / $ORIGIN. Adds a
Windows-only regression test that plants a bogus msvcp140.dll on the CWD and
%PATH% and confirms the driver still loads from the package.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the pr-size: medium Moderate update size label Sep 1, 2026
Comment thread tests/test_026_windows_dll_search.py Fixed
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ibraryW

Replace the success-path load smoke test with a source-contract regression
guard. The previous test only confirmed the driver still loads, which also
passes on the unhardened code (any host with msvcp140.dll in System32), so it
guarded nothing. The new test fails if the loader reintroduces a bare
LoadLibraryW call or drops the constrained-search flags / vcredist
registration -- i.e. it fails on the pre-fix source and passes on the current
source. Deterministic and platform-independent (no DLL, no live driver).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bewithgaurav
Gaurav Sharma (bewithgaurav) marked this pull request as ready for review September 1, 2026 08:46
Copilot AI lite review requested due to automatic review settings September 1, 2026 08:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens Windows DLL loading for the bundled ODBC driver and Entra auth DLL by constraining dependency resolution to trusted, package-local directories, reducing exposure to CWD/%PATH% hijacking and nondeterministic loads.

Changes:

  • Switch Windows loads from LoadLibraryW to LoadLibraryExW with constrained search flags.
  • Register the driver directory and its vcredist subfolder as trusted DLL search directories (once per process).
  • Add a regression test (source-contract) and document the behavior in the changelog.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
mssql_python/pybind/ddbc_bindings.cpp Uses LoadLibraryExW with constrained search flags and registers package-local DLL directories via AddDllDirectory.
tests/test_026_windows_dll_search.py Adds a source-contract regression test to guard against reintroducing unconstrained LoadLibraryW usage.
CHANGELOG.md Notes the Windows DLL search hardening behavior change.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/test_026_windows_dll_search.py
Comment thread mssql_python/pybind/ddbc_bindings.cpp Outdated
Drop the AddDllDirectory registration (and its once_flag / <mutex>): the
bundled VC++ runtime is already loaded from the trusted copy next to the
pybind .pyd (built /MD) before the driver loads, so registering the vcredist
directory added a permanent process-global search-dir side effect for no
resolution benefit. The per-load LoadLibraryExW flags
(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR) are the
whole change: they keep the current directory and %PATH% out of the driver's
and auth DLL's dependency search without mutating any global process state.

Also revert the CHANGELOG entry and trim the regression test's assertions to
match (no more AddDllDirectory / vcredist checks).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added pr-size: small Minimal code update and removed pr-size: medium Moderate update size labels Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

100%


🎯 Overall Coverage

82%


📈 Total Lines Covered: 7801 out of 9475
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/pybind/ddbc_bindings.cpp (100%)

Summary

  • Total: 4 lines
  • Missing: 0 lines
  • Coverage: 100%

📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.logger_bridge.cpp: 58.9%
mssql_python.pybind.ddbc_bindings.h: 61.5%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 75.5%
mssql_python.__init__.py: 77.6%
mssql_python.row.py: 77.6%
mssql_python.pybind.connection.connection_pool.cpp: 81.6%
mssql_python.pybind.connection.connection.cpp: 84.4%
mssql_python.logging.py: 85.5%
mssql_python.connection.py: 85.9%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

Comment thread tests/test_026_windows_dll_search.py Outdated
The previous assertions checked that the search-flag names appeared anywhere
in the source, but they also appear in the #define block -- so gutting both
LoadLibraryExW calls' flags to 0 still passed. Match each LoadLibraryExW(...)
call and require both flags within that call's own argument list instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ening' into bewithgaurav/win-dll-search-hardening
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: small Minimal code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants