Repository navigation
fix(tds): read past the cancelled response for the Attention acknowlement - #20
Closed
CommonwealthPasta wants to merge 169 commits into
Closed
CommonwealthPasta wants to merge 169 commits into
CommonwealthPasta wants to merge 169 commits into
Conversation
They expect AuthMethod::Integrated and AuthMethod::Windows, which exist on Windows only with `winauth`, so the unit tests failed to compile without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`BytesMut::extend` copied the payload into the read buffer a byte at a time; `extend_from_slice` copies it in one go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`split_to` made a second handle to the buffer, bumping its refcount or promoting it to shared, only to copy from it and drop it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`BytesMut::extend` copied the payload into the frame a byte at a time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a packet already in the frame buffer, such as the Attention a dropped `cancel_query` leaves queued, the new packet's length was written into the first packet's header instead of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`BytesMut::reserve` takes the bytes wanted beyond those already held, so a partly received packet reserved room for nearly twice its length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each call copied the header into a new `BytesMut` and decoded it, even while the packet was still incomplete, then decoded it again. The length is now read in place, checked first, and the header decoded once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`read_exact` is safe here: `Connection::poll_read` spans packet boundaries itself. Buffers still grow in 64 KiB steps, so an untrusted length cannot force one huge allocation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its own byte-wise `read_bytes` gives way to the PLP one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the code page leaves the bytes as they are (ASCII, or a UTF-8 collation), they become the `String` without a copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UTF-8 length is counted first, and ASCII-only values are copied a byte per code unit without decoding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They were read a byte (ntext: a code unit) at a time; they are now read whole and decoded with the varchar and nvarchar decoders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`as_chunks::<2>` yields `[u8; 2]` arrays, so no slice indexing is left to bounds-check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A token's bytes stay buffered until it ends, up to 1 MiB, so a cancel after a stream or `cancel_query` dropped mid-token no longer parses from its middle. Past 1 MiB the cancel fails and poisons the connection. Audit: P2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Skipping it lost its ENVCHANGEs, so after a `BEGIN TRAN` or `COMMIT` in it the server rejected the next request (3989) or every later one (3971). A response out of step with its tokens is still skipped. Audit: P2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After an encode error, such as a TVP value too long for its column, the next request waited for a response to the request never sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty result set after the first vanished and shifted later ones down an index. Every COLMETADATA now opens a result set. Audit: P4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a DONE with DONE_COUNT and a non-SELECT CurCmd is counted, as in TdsParser.TryProcessDone; SELECTs and variable assignments were too. Audit: P5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.2.6.8 is the SSPI message, and the packet type is 14 (0x0E), not 0x14. The compatibility table also lists `save`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It sent `IsolationLevel::Unspecified`, which keeps the session's level (MS-TDS 2.2.6.9), so a transaction after a SERIALIZABLE one was SERIALIZABLE too. SqlClient's BeginTransaction asks for READ COMMITTED. Audit: P6 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MS-TDS 2.2.7.18 makes it a LONG; `RETURN -1` came back as 4294967295. `CommandItem::ReturnStatus`, `as_return_status` and `return_code` are now `i32`. Audit: P7 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only its first ERROR survived. `Error::Server` still carries it; the new `TokenError::other_errors` and `TokenError::info_messages` return the response's other ERROR and INFO tokens, up to 1000 of each. Audit: P8 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The context kept its default, 7.4, so DONE, ERROR and INFO tokens were never read in their 7.1 layouts. The docs no longer claim 7.1 support: every request carries ALL_HEADERS, which 7.2 introduced. Audit: P9 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ignoring them would change what the connection does: for example MultipleActiveResultSets=true, Failover Partner or an Entra ID Authentication; from_ado_string's docs list them all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SPN is derived from the server name and port, so a given one was ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It replaces the connection-string crate: a bare \ and non-ASCII values parse and }} in braces is one }. Values are built in zeroizing buffers sized up front, so no freed block holds part of a password. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Properties are stored by mssql-jdbc's name, as mssql-jdbc stores them: serverName=a;server=b connects to b, and server= overrides the URL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
serverName= connected to host "" and serverName=h\ asked the SQL Browser for instance "". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The connection GUID, the activity GUID and its sequence, not 20 bytes; a TRACEID of another length is skipped, as SqlClient skips it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 0x80 bit failed the prelogin as an invalid ENCRYPTION value; with it, no encryption ends the connection, since the certificate needs TLS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MSSQLSvc/<host>:<instance>, as SqlClient uses when it has no port; the SPN carried the SQL Browser's own port 1434. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strict runs TLS straight over TCP, so the TLS 1.2 cap, there for TDS 7.x's PRELOGIN-framed handshake, no longer applies to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rows of up to 256 columns no longer allocate a Vec for the bitmap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
put_b_varchar and put_us_varchar check the UTF-16 length against the u8 or u16 prefix; RPC names and TVP type names use them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
256 overflowed the u8 length: a debug-build panic, 0 in release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The database, schema and type names (u8) and the assembly-qualified name (u16) in bulk and TVP metadata wrapped instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The database name and owner (u8) and collection name (u16) in TVP metadata wrapped instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
256 wrapped the B_VARCHAR length to 0 ahead of all 256 units. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65,534 bytes overflowed the option offsets (a debug-build panic), 70,000 wrapped INSTOPT's length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An AAD token of 2^31 UTF-16 units wrapped Length, FeatureDataLen and the token's own length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XmlData and nvarchar(max) now share put_plp_utf16, which checks the length before writing; XmlData's u32 byte count wrapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its length was cast with `as u32`, which wrapped from 4 GiB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its length was cast with `as u32`, which wrapped from 4 GiB; the check now comes before the text pointer is written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its u32 byte count overflowed from 2^31 UTF-16 units; the length is now checked before anything is written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its length was cast with `as u32`, which wrapped from 4 GiB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As SqlBulkCopy does; it was an error. The value goes in one PLP chunk, as the column is declared nvarchar(max). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
datetimeoffset stores whole minutes, and +01:00:30 was cut to +01:00, its local time 30 s off; a chrono offset with seconds is already an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Encoding one as a parameter now fails with a Conversion error instead of sending a value the server rejects (8023). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rounding carried it into the next day: 24:00:00.5 was stored as 00:00:00.5 the day after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A chrono leap second counted on into the next second (12:34:60.5 was sent as 12:35:00.5) or past midnight; it now converts to a marker the encoder rejects, as ToSql cannot fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Integrated authentication uses a non-blank Server SPN / ServerSPN (ADO.NET) or serverSpn (JDBC) as given, as SqlClient does; with integrated security it was an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes clippy::useless_vec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Title:
fix(tds): read past the cancelled response for the Attention acknowledgementDisclosure
Full transparency, this changeset was authored by Claude Opus 5.5. I instructed it to prove the bug existed before bothering to patch it. I am a professional software developer by trade and consume this library through the TDSQL library. I would loved to have authored this fix myself, but I have too many other projects to work on. I apologize! Feel free to reject as necessary.
Summary
After sending an Attention,
cancel_queryread only to the end of the current TDS message. SQL Server can send the acknowledgement (a DONE token withDONE_ATTN) as a message of its own, after the cancelled response (MS-TDS 3.2.5.9, 4.19.2).cancel_querythen failed and left the acknowledgement unread, so the connection desynced:cancel_requestnow drains message by message until the acknowledgement arrives, discarding the cancelled response, including any server error in it.Related issues
None.
Type of change
Checklist
devbranch (feature work lands ondev, is promoted toqa, thenmain).cargo fmt --allhas been run and the code is formatted.cargo clippy --all-targets --all-features -- -D warningspasses with no warnings. (--all-featuresdoesn't compile onmainbecause the TLS backends are mutually exclusive.--all-targets --features=allis clean.)cargo testpasses. Integration tests against a live SQL Server (viaTIBERIUS_TEST_CONNECTION_STRING/docker compose) were run if the change affects runtime behaviour.cargo deny checkis green (advisories, licenses, bans, sources).CHANGELOG.mdhas been updated with a user-facing entry (unless this change is not user-visible).Testing
tests/cancel.rs, run on tokio and smol:cancel_mid_streamcancels a batch mid-SELECT, then checks that the rest of the batch never ran and that the next query reads its own result.cancel_after_a_server_errorchecks the next query's result after cancelling a response that contains a server error.Err(crate::Error::Server(_))arm removed.RUSTFLAGS=-Dwarnings:docker/.cargo testwith each of--features=all,--no-default-features, and--no-default-featurespluschrono,time,rustlsorvendored-openssl.cargo fmt --all --check,cargo clippy --all-targets --features=all -- -D warningsandcargo deny check advisories bans sources licensesare all clean.Additional notes
cancel_querywait indefinitely. Before, it errored at the end of the first message. The server is required to acknowledge (MS-TDS 3.3.5.9).connection.rs,client.rsandheader.rs, left for a separate change.