Skip to content

CHORE: add mock TDS FedAuth regression test + CI install for deferred access-token UAF (#594/#596)#652

Draft
saurabh500 wants to merge 7 commits into
mainfrom
dev/saurabh/mock-tds-fedauth-test
Draft

CHORE: add mock TDS FedAuth regression test + CI install for deferred access-token UAF (#594/#596)#652
saurabh500 wants to merge 7 commits into
mainfrom
dev/saurabh/mock-tds-fedauth-test

Conversation

@saurabh500

@saurabh500 saurabh500 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

GitHub Issue: #594 (fixed by #596)


Summary

Adds tests/test_024_mock_tds_fedauth.py, an integration test that uses the mssql-mock-tds in-process TDS server to guard the deferred connect-attribute use-after-free fixed in #596 (issue #594), and wires the mock server install into the PR-validation pipeline so the test actually runs (instead of skipping) on every test leg.

Why

SQL_COPT_SS_ACCESS_TOKEN (1256) is a deferred ODBC connect attribute: the MS ODBC driver stashes the caller's pointer at SQLSetConnectAttr time and only dereferences it later, during SQLDriverConnect, to build the FedAuth Login7 packet. PR #568 briefly copied the value into a stack-local buffer that was freed before that deferred read — a use-after-free that variously produced SIGBUS, a server reset, or "Authentication token is missing in the federated authentication message." PR #596 fixed it by storing the value in Connection-owned member buffers.

This regression is invisible to ordinary unit tests because it only manifests once a real driver actually transmits the token to a server. The mock TDS server gives us exactly that: it records the access token it received in the Login7 FedAuth feature.

What the test does

For each case it sets attrs_before={SQL_COPT_SS_ACCESS_TOKEN: token_struct}, lets the real ODBC driver transmit the token to the mock server, and asserts the server captured the exact token. If the deferred buffer is ever corrupted again, the captured token won't match (or won't arrive) and the test fails.

  • test_access_token_round_trips_to_server — direct guard: exact byte-for-byte round-trip.
  • test_unique_access_token_transmitted_exactly — random unguessable token rules out any cached/constant fallback.
  • test_distinct_tokens_on_sequential_connects — two sequential connects with different tokens, exercising the per-attribute owned buffers from FIX: Store deferred connect-attribute values in member buffers (#594) #596.

CI integration

Adds a reusable install step (eng/pipelines/steps/install-mock-tds.yml, backed by install-mock-tds.{sh,ps1} and pinned via eng/versions/mssql-mock-tds.version), mirroring the existing install-mssql-py-core pattern. It installs mssql-mock-tds + cryptography from the public mssql-rs_Public feed and is referenced from every test leg (Windows, macOS, and all Linux containers incl. Alpine/musl and ARM64). The scripts are best-effort: if a compatible wheel is ever missing or the sandbox feed is unavailable, they emit a pipeline warning and exit 0, so the leg stays green and the test simply skips there.

Notes

  • Opt-in / skips cleanly when mssql-mock-tds or cryptography aren't installed, so it won't break CI legs that don't install the mock. The mock package currently lives on the mssql-rs_Public sandbox feed (from draft Rename mock TDS Python package + add sandbox publish pipeline (test-only) mssql-rs#80):
    pip install --index-url https://pkgs.dev.azure.com/sqlclientdrivers/public/_packaging/mssql-rs_Public/pypi/simple/ mssql-mock-tds
    
  • Generates a throwaway TLS identity in a temp dir (the mock requires TLS for FedAuth) — no certs/artifacts land in the repo.
  • The driver's connect still times out (the mock doesn't complete the full handshake); that is expected and intentionally not asserted on — only the captured token matters, and it has already been transmitted by then. A short login timeout keeps the tests fast (~13s total).

Validation

Built the ddbc_bindings extension locally from this branch's source and ran the suite: 3 passed.

Note

Draft pending the mssql-mock-tds package being published to a stable feed (currently sandbox-only via microsoft/mssql-rs#80).

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

…UAF (#594/#596)

Adds tests/test_024_mock_tds_fedauth.py which uses the mssql-mock-tds in-process TDS server to assert that SQL_COPT_SS_ACCESS_TOKEN round-trips byte-for-byte through the ODBC driver to the server's Login7 FedAuth feature.

This guards the deferred connect-attribute use-after-free fixed in PR #596: a corrupted deferred buffer would surface as a missing/garbage token captured by the mock, failing the test. Skips cleanly when mssql-mock-tds or cryptography are unavailable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread tests/test_024_mock_tds_fedauth.py Dismissed
Comment thread tests/test_024_mock_tds_fedauth.py Fixed
saurabh500 and others added 3 commits June 29, 2026 21:58
…fedauth test runs (not skips)

Adds a reusable install step (eng/pipelines/steps/install-mock-tds.yml) backed by
install-mock-tds.{sh,ps1}, mirroring the existing install-mssql-py-core pattern.
The step installs mssql-mock-tds (pinned via eng/versions/mssql-mock-tds.version)
plus cryptography from the public mssql-rs_Public PyPI feed, then verifies import.

It is referenced from every test leg (Windows, macOS, and all Linux containers).
The scripts are best-effort: on a platform with no compatible wheel (e.g.
Alpine/musl) or if the sandbox feed is unavailable, they emit an Azure DevOps
pipeline warning and exit 0, so the leg stays green and test_024_mock_tds_fedauth
simply skips there instead of failing the build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pine wheels)

The new dev build ships musllinux_1_2 wheels (x86_64 + aarch64), so the Alpine
test legs now install the mock server and run test_024_mock_tds_fedauth instead
of skipping. Updated the stale comments that called out musl as an unsupported
platform; the install scripts stay best-effort in case the sandbox feed is ever
unavailable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread tests/test_024_mock_tds_fedauth.py Dismissed
@saurabh500 saurabh500 changed the title test: add mock TDS FedAuth regression test for deferred access-token UAF (#594/#596) CHORE: add mock TDS FedAuth regression test + CI install for deferred access-token UAF (#594/#596) Jun 30, 2026
@github-actions github-actions Bot added the pr-size: medium Moderate update size label Jun 30, 2026
The mock server resolves its TLS identity by trying valid_cert.pem + key.pem
first (via create_test_identity, OpenSSL, non-Windows only) and only then falls
back to identity.pfx, which its Python binding loads with an EMPTY password.

macOS' Security framework rejects a password-less PKCS#12 with 'The user name
or passphrase you entered is not correct.', and we can't change the empty
password the binding uses -- so the .pfx path can never work on macOS.

Fix: write the PEM pair on non-Windows (Linux + macOS), where create_test_identity
re-packs them into a 3DES PKCS#12 with a non-empty password that macOS accepts;
keep emitting identity.pfx on Windows, where OpenSSL isn't bundled and Schannel
loads a password-less PKCS#12 fine.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added pr-size: large Substantial code update and removed pr-size: medium Moderate update size labels Jun 30, 2026
saurabh500 and others added 2 commits June 30, 2026 14:31
…t test

The mock server stores connections in a HashMap keyed by client socket address,
so get_last_access_token() (which returns values().last()) reflects arbitrary
hash ordering rather than connect order. The Windows SQL2022 leg flaked because
'last' resolved to the first connect's token.

Assert instead that both distinct tokens were received byte-for-byte (order
independent) and that two connections were recorded. That is the actual property
guarding the per-attribute owned buffers from #596 -- two connects must not
clobber each other's token. The single-connection tests keep using
get_last_access_token(), which is deterministic with one stored entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…test_024

The new mssql-mock-tds build handles the TransactionManager (0x0E) request
that Python DB-API drivers send after FedAuth login, so mssql_python now
completes the connect instead of hanging on Linux CI (microsoft/mssql-rs#86).

The mock records the FedAuth token only when the client connection actually
closes. mssql_python auto-enables ODBC connection pooling on the first
connect(), so conn.close() returned the socket to the pool and the mock never
observed the close -- leaving has_received_token empty and failing all three
tests. Add an autouse fixture that disables pooling (a process-global ODBC
setting) for the duration of these tests and restores the prior configuration
afterwards, so each connection is torn down promptly and its token recorded.

Also refresh the now-stale comments that claimed the mock never completes the
handshake.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: large Substantial code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants