Skip to content

fix: bound TDS response parser allocations against malformed streams - #421

Open
Saurabh Singh (saurabh500) wants to merge 63 commits into
mainfrom
saurabh500-bound-tds-response-allocations
Open

Saurabh Singh (saurabh500) wants to merge 63 commits into
mainfrom
saurabh500-bound-tds-response-allocations

Conversation

@saurabh500

@saurabh500 Saurabh Singh (saurabh500) commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes #420. Originally stacked on #419 (the Layer 1 fuzz harness); this PR now targets main. Part of the fuzz-coverage work tracked in #418.

Several TDS response parsers allocated buffers or slices from attacker-controlled length/count prefixes before reading or validating the corresponding data. Repeated metadata could multiply otherwise bounded per-column reservations into gigabytes. Malformed sql_variant sizes could also let a fixed-width read consume bytes belonging to the next value. The affected malformed lengths/counts now fail through badStreamPanic as StreamError, allowing the driver to retire the connection instead of returning it to the pool.

Allocation and boundary checks

Parser Change
parseFedAuthInfo Cap the token at 1 MiB; require option headers to fit; reject offset/length overflow using uint64 arithmetic before slicing.
parseColMetadata72 Start with at most 64 columns of capacity and append only after parsing each column.
readTypeInfo / readVarLen Do not allocate column-value buffers while parsing metadata, including fixed-, byte-, short-length and UDT types.
Fixed-/byte-/short-length value readers Allocate reusable buffers for actual value lengths, not metadata maxima; reject lengths beyond the declaration. Keep binary row copies independent of buffer reuse.
readCekTable Append entries only after parsing them, avoiding upfront allocation from the table count.
readCekTableEntry Enforce SQL Server's documented maximum of two encrypted values per CEK before allocating the value slice. Read all eight metadata-version bytes across packet boundaries and widen UTF-16 lengths before multiplication.
readLongLenType Preserve NULL sentinels, reject negative lengths, and grow TEXT/NTEXT/IMAGE buffers from received bytes with a bounded initial capacity.
readPLPType / readPLPBytes Reserve nothing from the advertised total length. Grow as chunks arrive and check each chunk against the remaining 2 GiB-minus-one per-value payload allowance, including unknown-length values. Empty PLP values remain non-nil without retaining a reservation.
readVariantTypeWithEncoding Reject negative or oversized data lengths, invalid property counts, invalid fixed/decimal/scaled-time payload widths, undersized headers and unsupported types. Reject odd UTF-16 payload lengths as StreamError before reading properties or data.

The CEK limit follows ALTER COLUMN ENCRYPTION KEY: two encrypted values support master-key rotation. PLP retains the existing treatment of total lengths as hints, consistent with the MS-TDS data-stream description, while limiting the actual accumulated payload. Variant property counts follow the MS-TDS sql_variant layout; decimal/numeric permits all four documented payload widths, including shorter representations of higher-precision values.

There are no exported API changes or changes to authentication, TLS, or certificate settings. Valid values, including empty binary/text variants and NULL variants, retain their existing behavior.

Scope of memory protection: This fixes allocations driven by unchecked prefixes and metadata-only reservations, not a general per-response memory quota. Fully received CEK entries and large legitimate values still require storage proportional to their data. A bounded review probe with the maximum advertised CEK table count allocated 56 bytes when no entries were supplied, about 1.46 MB after receiving one 395 KB maximum-field entry, and 2.90 MB after two. This confirms incremental consumption, not a cumulative hard limit; a genuinely huge supplied table can still use substantial memory. No new arbitrary CEK-table budget was added in response to that review suggestion.

Regression coverage

  • token_alloc_regression_test.go: FEDAUTHINFO underflow, oversized/count/offset-overflow cases, malformed/truncated LOB lengths, sql_variant allocation lengths, COLMETADATA allocation measurement, and complete malformed responses.
  • token_cek_regression_test.go: maximum CEK counts, repeated allocation attempts, valid rotation, every truncated-entry prefix, UTF-16 boundaries, packet seams, ordinal lookup, and connection retirement.
  • types_alloc_regression_test.go: metadata creates no value buffers; safely scaled a5/fffe metadata reproduces the allocation amplification; repeated fixed/byte/short reads preserve values and allocate only for actual lengths; decrypted buffers, invalid lengths, exact maximum PLP hints, many empty PLP columns, cumulative known/unknown-length limits, truncation and packet seams are exercised.
  • types_variant_test.go: covers fixed-width overreads with adjacent sentinels, property counts, decimal widths, time scales, truncated/invalid headers and unsupported types. Covers malformed and valid ROW, NBCROW and RETURNVALUE paths, repeated valid values and packet seams. Unicode additions verify odd lengths 1/3/5 fail before properties or payload are read and retire the connection; empty and even-length values preserve it.
  • Cumulative PLP tests use the production chunk-reading loop with a small per-value limit, avoiding multi-gigabyte test allocations. Two consecutive values exercise the correct per-value lifetime.
  • Mutation checks verify the assertions: small eager metadata/empty-PLP reservations, allocation to metadata maxima, and a per-chunk-only PLP limit all fail. A one-byte variant overread fails with the length checks still present, as does disabling packet fragmentation. Changing the new malformed Unicode fixtures to even lengths also fails the tests, confirming they depend on the intended malformed input.
  • FuzzProcessSingleResponse has permanent malformed metadata/PLP/CEK/variant seeds, including both odd-width Unicode variant types, and runs every input with Always Encrypted disabled and enabled.
  • The earlier empty IMAGE review claim was refuted against the unchanged long-length reader: io.CopyN dispatches to bytes.Buffer.ReadFrom, which allocates before observing zero-length input. No IMAGE-specific change was made.

Validation on c1af349

  • go build ./... passes; changed files are gofmt-clean.
  • Focused variant, Unicode, CEK and response-seed regressions pass. The Unicode/variant suite also passes on Windows 386; other runs used Windows amd64.
  • Earlier focused runs covered 99% of readVariantTypeWithEncoding except its existing unreachable terminal panic, and 100% of the CEK parsers and lazy-buffer/PLP-byte helpers. The new Unicode rejection and healthy paths are both exercised by explicit assertions.
  • go test -count=1 -timeout 30m ./... passes in every package except two pre-existing Windows certificate-provisioning failures in aecmk/localcert: TestLoadWindowsCertStoreCertificate and TestEncryptDecryptEncryptionKeyRoundTrip fail while creating their certificate, before TDS parsing. No tests were skipped or weakened to hide these failures.
  • go vet ./... repeats integratedauth/winsspi/winsspi.go:24:36: possible misuse of unsafe.Pointer. The identical finding was reproduced with Go 1.26.5 in an isolated, unmodified main snapshot during an earlier merge; that file remains unchanged. No unrelated authentication code or vet settings were changed.
  • Live SQL Server/Azure integration endpoints are not configured locally. These shared parser changes affect Windows and other platforms; live-server coverage remains with CI, including AppVeyor for Windows.
  • Earlier reader benchmark comparisons showed no added per-operation allocations and median timing changes below the existing CI threshold. No benchmark thresholds or exclusions were changed.
  • go test -run=^$ -fuzz=^FuzzProcessSingleResponse$ -fuzztime=180s . completes with no crash or worker OOM/EOF:
fuzz: elapsed: 3m28s, execs: 9621982 (28527/sec), new interesting: 34 (total: 1338)
fuzz: elapsed: 3m29s, execs: 9621982 (0/sec), new interesting: 34 (total: 1338)
PASS
ok  github.com/microsoft/go-mssqldb  209.720s

Upstream base updates

The branch includes main's v1.11.1 release update (#456) and #469, which intentionally reverts #410 and restores the known query-draining issue #407 for separate reassessment. This PR preserves that upstream decision rather than reinstating response-cleanup work. The base updates themselves did not change the allocation protections.

Saurabh Singh (SQL Drivers) and others added 2 commits August 14, 2026 11:14
Add token_fuzz_test.go with reusable helpers to frame arbitrary token
streams into one or more packReply TDS packets (with configurable
fragmentation), build a tdsSession reading from the framed bytes, and run
processSingleResponse while fully draining its buffered token channel.

Add FuzzProcessSingleResponse seeded with valid synthetic responses
(DONE, COLMETADATA/ROW, NBCROW, multiple result sets, ERROR, INFO,
RETURNSTATUS, DONEPROC/DONEINPROC, ENVCHANGE, TABNAME/COLINFO/ORDER) and
malformed inputs (unknown token, truncated token, trailing garbage). Add
TestProcessSingleResponsePacketBoundary asserting the parser's token
sequence is independent of packet fragmentation for the valid seeds.

Refs #418

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

Several TDS token parsers allocated buffers and slices sized directly from
attacker-controlled length prefixes before validating them against the
available data, so a malformed server response could drive an unbounded
allocation and OOM the client (a DoS). The stream recover() does not catch
OOM because it is uncontrolled allocation, not a panic.

Bound the wire-derived allocation sizes and reject underflows via
badStreamPanic so a malformed stream fails cleanly as an error token:

- parseFedAuthInfo: cap the token size and reject an option count that
  cannot fit within it (fixes the EE 00*8 ~4GB underflow repro).
- readLongLenType: reject negative/oversized TEXT/NTEXT/IMAGE lengths.
- readVariantTypeWithEncoding: reject underflowed/oversized data lengths.

Add regression unit tests plus permanent fuzz seeds for the crafted inputs.

Fixes #420

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@codecov-commenter

Codecov Comments Bot (codecov-commenter) commented Aug 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.70%. Comparing base (60674a5) to head (bfee5b4).

Files with missing lines Patch % Lines
token.go 94.59% 2 Missing ⚠️
types.go 98.43% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #421      +/-   ##
==========================================
+ Coverage   82.13%   82.70%   +0.57%     
==========================================
  Files          35       35              
  Lines        7065     7148      +83     
==========================================
+ Hits         5803     5912     +109     
+ Misses        995      974      -21     
+ Partials      267      262       -5     
Files with missing lines Coverage Δ
token.go 73.05% <94.59%> (+1.42%) ⬆️
types.go 84.47% <98.43%> (+2.17%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 14, 2026 21:47
- Correct frameReplyPackets doc: frag is a target fragment count (1..8),
  streams exceeding the per-packet limit split into additional packets.
- Fix stale doneBody helper comment (was buildDone).
- Set a spec-faithful Length field on ERROR/INFO seed tokens.
- Restrict the packet-boundary determinism test to known-valid seeds by
  splitting seeds into validResponseSeeds/malformedResponseSeeds.
- Skip per-token fmt.Sprintf allocations in the fuzz hot path via a
  collect flag on drainSingleResponse.

Refs #418

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c3e268ca-c6f4-4edb-826c-3a4018cf939c

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the TDS response parser against OOM/DoS scenarios by bounding allocations that are derived from server-controlled length prefixes, and adds targeted regression tests + fuzz seeds to keep these cases pinned.

Changes:

  • Add size/count validation in parseFedAuthInfo to prevent underflow/oversized allocations.
  • Add validation for TEXT/NTEXT/IMAGE and sql_variant length-derived allocations in types.go.
  • Update the end-to-end fuzz target documentation and add permanent regression seeds plus a dedicated regression test file.

Reviewed changes

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

File Description
types.go Adds bounds/underflow checks for long-length types and sql_variant before allocating.
token.go Caps FEDAUTHINFO token size and validates option count fits within advertised token size before allocating.
token_fuzz_test.go Updates fuzz-target note and adds regression seeds for previously OOM-inducing malformed streams.
token_alloc_regression_test.go Adds direct + end-to-end regression tests for malformed-length allocation issues.
Suppressed comments (2)

token.go:528

  • Same issue as above: badStreamPanicf produces a plain error, so this malformed-stream case won’t be classified as StreamError and may not poison the connection for pooling/retry logic. Use badStreamPanic(fmt.Errorf(...)) to ensure StreamError.
	if uint64(count)*9+uint64(offset) > uint64(size) {
		badStreamPanicf("federated authentication info advertised %d options that do not fit in %d bytes", count, size)
	}

types.go:669

  • Same StreamError classification concern: badStreamPanicf panics with a plain error, so malformed sql_variant data may not mark the connection as bad. Use badStreamPanic(fmt.Errorf(...)) so callers see a StreamError.
	// size-2-propbytes is the trailing data length and is used below as an
	// allocation size. It is derived from an attacker-controlled size prefix,
	// so reject an underflowed (negative) or implausibly large value before any
	// make() to avoid an OOM DoS (issue #420).
	if datalen := size - 2 - propbytes; datalen < 0 || int64(datalen) > _MAX_PLP_LEN {
		badStreamPanicf("sql_variant data length %d is invalid", datalen)
	}

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

Comment thread token.go
Comment thread types.go Outdated
Convert the issue #420 allocation guards from badStreamPanicf (plain
error) to badStreamPanic(fmt.Errorf(...)) so a malformed stream surfaces
as a StreamError. Conn.checkBadConn only marks the connection bad for
StreamError, so a plain-error panic would leave the poisoned connection
in the pool. Also assert the guards panic StreamError in the regression
tests.

Addresses Copilot review feedback on PR #421.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67
Copilot AI review requested due to automatic review settings August 14, 2026 21:55
…ness' into saurabh500-bound-tds-response-allocations

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

token_fuzz_test.go:287

  • This NOTE claims parseColMetadata72’s column-count allocation is “now bounded”, but this PR doesn’t change parseColMetadata72 (it still allocates make([]columnStruct, count) from the wire uint16). Consider tightening the wording to only describe the allocation sites actually bounded by this PR to avoid misleading future readers.
			single, _, ok := drainSingleResponse(seed, 0, true) // one packet
			if !ok {
				t.Fatal("failed to frame seed as a single packet")
			}
			for _, frag := range []byte{1, 3, 7, 255} {

token_alloc_regression_test.go:42

  • The StreamError assertion is correct for the new allocation guards added for issue #420, but the comment currently reads like a general guarantee for all malformed-stream failures. Consider narrowing it to the specific intent of these tests (verifying the new guards panic StreamError so checkBadConn drops the connection).
// assertStreamError fails unless err is a StreamError. The allocation guards
// must panic StreamError (not a plain error) so Conn.checkBadConn marks the
// connection bad and drops it from the pool on a malformed stream.

token_alloc_regression_test.go:25

  • The comment for recoverErr is a bit misleading: not all malformed-stream panics come from badStreamPanic (StreamError), and this helper captures panics directly rather than via processSingleResponse. Tweaking the wording here will make it clearer what’s being asserted in these tests.

This issue also appears on line 40 of the same file.

// recoverErr runs fn and returns any panic value coerced to an error. Token
// parsers signal a malformed stream by panicking (badStreamPanic) which
// processSingleResponse recovers into an error token.

Comment thread types.go
Copilot AI review requested due to automatic review settings August 14, 2026 22:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

types.go:667

  • readVariantTypeWithEncoding still allows an attacker-controlled size to be as large as ~2GiB (since the guard uses _MAX_PLP_LEN). For sql_variant, SQL Server values have a much smaller maximum storage size (8016 bytes), so a malformed stream can still trigger a multi‑GiB make([]byte, size-2-propbytes) allocation before ReadFull fails, leading to OOM.
	// size-2-propbytes is the trailing data length and is used below as an
	// allocation size. It is derived from an attacker-controlled size prefix,
	// so reject an underflowed (negative) or implausibly large value before any
	// make() to avoid an OOM DoS (issue #420).
	if datalen := size - 2 - propbytes; datalen < 0 || int64(datalen) > _MAX_PLP_LEN {

A sql_variant is capped at 8016 bytes on SQL Server and is never a (max)/
LOB type, so bounding its data length at _MAX_PLP_LEN (~2 GiB) still let an
attacker-controlled size prefix drive a multi-gigabyte make(). Introduce
_MAX_VARIANT_LEN and reject any larger length, and cover the oversize case
in the regression test.

Addresses Copilot review feedback on PR #421 (issue #420).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67
Copilot AI review requested due to automatic review settings August 14, 2026 22:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

types.go:589

  • The guard covers both negative lengths and lengths exceeding the LOB maximum, but the error text currently reads as though every failure is an “exceeds the maximum” case. For negative sizes this is misleading and makes diagnosing malformed streams harder. Consider a message that states the value is invalid/out of range while still mentioning the maximum LOB size.
	// The advertised size is attacker-controlled; reject anything a real server
	// cannot produce before using it as an allocation size (OOM DoS, issue #420).
	if size < 0 || int64(size) > _MAX_PLP_LEN {
		badStreamPanic(fmt.Errorf("TEXT/NTEXT/IMAGE length %d exceeds the maximum LOB size of %d bytes", size, int64(_MAX_PLP_LEN)))
	}

@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

Benchmark Results (main vs PR)

Click to expand benchstat output
goos: linux
goarch: amd64
pkg: github.com/microsoft/go-mssqldb
cpu: AMD EPYC 9V45 96-Core Processor                
                         │ bench_old.txt │             bench_new.txt             │
                         │    sec/op     │    sec/op      vs base                │
TdsBuffer_Write_Small-4               6.442n ±  3%    6.606n ±  3%        ~ (p=0.353 n=10)
TdsBuffer_Write_Medium-4              13.35n ±  0%    13.52n ±  2%   +1.27% (p=0.000 n=10)
TdsBuffer_Write_Large-4               73.57n ±  1%    73.50n ±  3%        ~ (p=0.810 n=10)
TdsBuffer_WriteByte-4                 174.2n ±  3%    175.8n ±  3%        ~ (p=0.971 n=10)
TdsBuffer_Read_Small-4                5.031n ±  3%    4.966n ±  3%        ~ (p=0.699 n=10)
TdsBuffer_ReadByte-4                  129.3n ±  3%    126.6n ±  4%        ~ (p=0.041 n=10)
TdsBuffer_Uint16-4                    68.32n ±  1%    65.36n ±  4%   -4.33% (p=0.000 n=10)
TdsBuffer_Uint32-4                    79.85n ±  4%    76.38n ±  4%   -4.34% (p=0.000 n=10)
TdsBuffer_Uint64-4                    79.53n ±  3%    77.04n ±  2%        ~ (p=0.011 n=10)
TdsBuffer_BeginFinishPacket-4         3.577n ±  1%    3.581n ±  3%        ~ (p=0.684 n=10)
ReadBVarCharOrPanicWideChars-4        80.01n ±  1%    80.42n ±  1%        ~ (p=0.435 n=10)
ReadBVarCharOrPanicOnly1WideChar-4    102.1n ±  2%    104.3n ±  4%        ~ (p=0.018 n=10)
BulkMakeParam_Int64-4                 12.62n ±  1%    13.24n ±  4%   +4.83% (p=0.000 n=10)
BulkMakeParam_Int32-4                 11.75n ±  1%    11.89n ±  1%   +1.19% (p=0.007 n=10)
BulkMakeParam_Float64-4               12.76n ±  1%    12.52n ±  2%   -1.84% (p=0.005 n=10)
BulkMakeParam_String_NVarChar-4       130.9n ±  2%    130.2n ±  1%        ~ (p=0.352 n=10)
BulkMakeParam_String_VarChar-4        20.86n ±  1%    20.51n ±  1%   -1.65% (p=0.000 n=10)
BulkMakeParam_DateTime-4              44.98n ±  0%    45.49n ±  2%   +1.13% (p=0.004 n=10)
BulkMakeParam_DateTime2-4             47.88n ±  0%    47.88n ±  1%        ~ (p=0.755 n=10)
BulkMakeParam_Bool-4                  12.38n ±  1%    12.32n ±  1%        ~ (p=0.158 n=10)
BulkMakeParam_MultiColumn-4           108.6n ± 16%    109.6n ±  3%        ~ (p=0.853 n=10)
ConvertAssign_StringToString-4        8.972n ±  5%    8.876n ±  2%        ~ (p=0.436 n=10)
ConvertAssign_StringToBytes-4         23.95n ±  3%    24.08n ±  2%        ~ (p=0.812 n=10)
ConvertAssign_BytesToString-4         30.88n ±  2%    31.60n ±  6%        ~ (p=0.029 n=10)
ConvertAssign_BytesToBytes-4          31.14n ±  6%    31.93n ±  3%        ~ (p=0.448 n=10)
ConvertAssign_Int64ToInt64-4          20.55n ±  1%    20.39n ±  1%        ~ (p=0.066 n=10)
ConvertAssign_Int64ToString-4         25.79n ±  2%    26.28n ±  2%        ~ (p=0.015 n=10)
ConvertAssign_Float64ToFloat64-4      20.73n ±  3%    20.89n ±  5%        ~ (p=0.210 n=10)
ConvertAssign_TimeToTime-4            22.29n ±  3%    22.43n ±  2%        ~ (p=0.210 n=10)
ConvertAssign_TimeToString-4          65.57n ±  1%    64.74n ±  1%        ~ (p=0.014 n=10)
ConvertAssign_NilToBytes-4            9.989n ±  4%   10.000n ±  3%        ~ (p=0.305 n=10)
ConvertAssign_Int64ToInterface-4      8.926n ±  4%    8.998n ±  2%        ~ (p=0.631 n=10)
ConvertAssign_StringToRawBytes-4      25.27n ±  2%    25.38n ±  2%        ~ (p=0.754 n=10)
ConvertAssign_BoolToBool-4            7.947n ±  2%    7.883n ±  1%        ~ (p=0.529 n=10)
RoundTrip_ConnectDisconnect-4         49.49m ±  1%    49.21m ±  1%        ~ (p=0.035 n=10)
RoundTrip_Select1-4                   159.0µ ±  2%    156.4µ ±  2%        ~ (p=0.123 n=10)
RoundTrip_ParamQuery-4                125.7µ ±  1%    125.3µ ±  2%        ~ (p=0.579 n=10)
RoundTrip_MultiRow-4                  350.8µ ±  1%    352.4µ ±  1%        ~ (p=0.393 n=10)
RoundTrip_LargeResultSet-4            2.009m ±  1%    2.019m ±  3%   +0.48% (p=0.005 n=10)
RoundTrip_ExecInsert-4                135.1µ ±  6%    134.1µ ±  1%        ~ (p=0.123 n=10)
RoundTrip_StoredProc-4                131.1µ ±  1%    132.7µ ±  1%   +1.19% (p=0.005 n=10)
RoundTrip_Transaction-4               354.9µ ±  0%    363.5µ ±  2%   +2.41% (p=0.000 n=10)
RoundTrip_BulkInsert/Rows_100-4       2.094m ± 26%    2.126m ±  1%        ~ (p=0.075 n=10)
RoundTrip_BulkInsert/Rows_1000-4      3.050m ±  1%    3.089m ±  2%   +1.28% (p=0.003 n=10)
RoundTrip_ConcurrentQueries-4         75.74µ ±  2%    78.69µ ±  4%   +3.90% (p=0.007 n=10)
RoundTrip_MixedTypes-4                208.7µ ±  2%    213.1µ ±  2%        ~ (p=0.029 n=10)
RoundTrip_LargePayload-4              260.9µ ±  4%    267.3µ ±  2%        ~ (p=0.105 n=10)
RoundTrip_PreparedStmt-4              125.3µ ±  2%    125.9µ ±  1%        ~ (p=0.247 n=10)
RoundTrip_MessageQuery-4              269.0µ ±  5%    273.2µ ±  2%        ~ (p=0.063 n=10)
WriteTypeInfo_Int8-4                  25.13n ±  2%    24.95n ±  2%        ~ (p=0.255 n=10)
WriteTypeInfo_NVarChar-4              58.53n ±  1%    57.96n ±  1%        ~ (p=0.061 n=10)
WriteTypeInfo_NVarCharMax-4           58.29n ±  2%    58.17n ±  1%        ~ (p=0.184 n=10)
WriteByteLenType-4                    15.88n ±  1%    15.85n ±  2%        ~ (p=0.868 n=10)
WriteShortLenType-4                   18.62n ±  3%    18.37n ±  1%   -1.34% (p=0.006 n=10)
WritePLPType_Short-4                  46.18n ±  2%    46.27n ±  1%        ~ (p=0.986 n=10)
SendRpc_SingleIntParam-4              7.246µ ±  1%    7.371µ ±  2%   +1.73% (p=0.003 n=10)
Str2ucs2_Short-4                      44.08n ±  2%    44.72n ±  1%        ~ (p=0.011 n=10)
Str2ucs2_Medium-4                     97.50n ±  0%   101.55n ±  1%   +4.15% (p=0.000 n=10)
Str2ucs2_Long-4                       380.5n ±  3%    401.0n ±  2%   +5.37% (p=0.000 n=10)
Ucs22str_ASCII-4                      30.39n ±  5%    32.56n ±  2%   +7.16% (p=0.001 n=10)
Ucs22str_Unicode-4                    106.7n ±  3%    114.4n ±  5%   +7.17% (p=0.000 n=10)
Ucs22str_LongASCII-4                  60.61n ±  6%    64.75n ±  3%   +6.85% (p=0.003 n=10)
ManglePassword_Short-4                51.44n ±  1%    55.66n ±  1%   +8.21% (p=0.000 n=10)
ManglePassword_Long-4                 200.7n ±  5%    220.3n ±  4%   +9.74% (p=0.000 n=10)
SendLogin-4                           655.4n ±  1%    705.4n ±  4%   +7.61% (p=0.000 n=10)
WritePrelogin-4                       339.8n ±  1%    352.7n ±  6%   +3.80% (p=0.000 n=10)
Ucs22strAscii-4                       7.197n ±  1%    7.478n ±  1%   +3.90% (p=0.000 n=10)
Ucs22strMediumAscii-4                 11.90n ±  3%    12.50n ±  6%   +5.04% (p=0.000 n=10)
Ucs22strLongAscii-4                   27.56n ±  1%    29.44n ±  3%   +6.80% (p=0.000 n=10)
Ucs22strLongerAscii-4                 216.0n ±  1%    232.9n ±  2%   +7.80% (p=0.000 n=10)
Ucs22strTrailingUnicode-4             78.34n ±  3%    81.06n ±  1%   +3.47% (p=0.007 n=10)
Ucs22strLongEmojis-4                  113.9n ±  2%    120.4n ±  3%   +5.66% (p=0.000 n=10)
ParseDone-4                           4.879n ±  2%    4.968n ±  2%        ~ (p=0.143 n=10)
ParseDoneInProc-4                     4.889n ±  3%    4.927n ±  3%        ~ (p=0.631 n=10)
ParseReturnStatus-4                   1.294n ±  3%    1.443n ±  5%  +11.51% (p=0.000 n=10)
ParseOrder-4                          14.52n ±  6%    16.08n ±  4%  +10.78% (p=0.001 n=10)
ParseError72-4                        93.41n ±  1%   103.70n ±  1%  +11.02% (p=0.000 n=10)
ParseInfo-4                           99.77n ±  1%   112.05n ±  1%  +12.31% (p=0.000 n=10)
ParseLoginAck-4                       34.67n ±  1%    39.81n ±  2%  +14.83% (p=0.001 n=10)
ParseFeatureExtAck_Empty-4            19.68n ± 13%    22.28n ±  1%  +13.24% (p=0.004 n=10)
ParseTabName-4                        30.35n ±  6%    33.38n ±  1%   +9.98% (p=0.001 n=10)
ParseColInfo-4                        30.26n ±  2%    33.63n ±  3%  +11.14% (p=0.000 n=10)
ReadFixedType_Int64-4                 14.07n ±  3%    15.46n ±  1%   +9.84% (p=0.000 n=10)
ReadFixedType_Int32-4                 8.341n ±  3%    9.237n ±  5%  +10.75% (p=0.000 n=10)
ReadFixedType_Float64-4               13.86n ±  1%    15.55n ±  1%  +12.15% (p=0.000 n=10)
ReadFixedType_DateTime-4              29.41n ±  2%    33.64n ±  3%  +14.40% (p=0.000 n=10)
ReadByteLenType_IntN_8-4              14.43n ±  1%    16.13n ±  3%  +11.78% (p=0.000 n=10)
ReadByteLenType_IntN_4-4              14.26n ±  1%    15.90n ±  6%  +11.50% (p=0.000 n=10)
ReadByteLenType_FloatN_8-4            14.32n ±  1%    15.62n ±  4%   +9.04% (p=0.000 n=10)
ReadByteLenType_BitN-4                8.889n ±  0%    8.966n ± 12%        ~ (p=0.210 n=10)
ReadByteLenType_Null-4                3.046n ±  3%    3.181n ±  2%   +4.41% (p=0.001 n=10)
ReadShortLenType_NVarChar_Short-4     25.49n ±  4%    28.53n ±  2%  +11.93% (p=0.000 n=10)
ReadShortLenType_NVarChar_Medium-4    45.40n ±  1%    50.85n ±  3%  +12.02% (p=0.000 n=10)
ReadShortLenType_VarBinary-4          31.98n ±  1%    35.76n ±  4%  +11.80% (p=0.000 n=10)
ReadByteLenType_VarChar-4             32.11n ± 12%    32.89n ±  3%        ~ (p=0.353 n=10)
DecodeDateTime-4                      21.43n ±  6%    22.51n ±  3%        ~ (p=0.011 n=10)
DecodeDateTim4-4                      19.34n ±  5%    20.62n ±  8%   +6.59% (p=0.002 n=10)
EncodeDateTime-4                      37.53n ±  0%    40.38n ±  5%   +7.61% (p=0.000 n=10)
geomean                               136.4n          141.1n         +3.45%

               │ bench_old.txt │            bench_new.txt            │
               │      B/s      │     B/s       vs base               │
TdsBuffer_Write_Small-4     9.252Gi ± 3%   9.024Gi ± 3%       ~ (p=0.353 n=10)
TdsBuffer_Write_Medium-4    71.42Gi ± 0%   70.54Gi ± 2%  -1.24% (p=0.000 n=10)
TdsBuffer_Write_Large-4     103.7Gi ± 1%   103.8Gi ± 3%       ~ (p=0.796 n=10)
TdsBuffer_Read_Small-4      11.85Gi ± 3%   12.00Gi ± 3%       ~ (p=0.739 n=10)
Str2ucs2_Short-4            129.8Mi ± 2%   128.0Mi ± 1%       ~ (p=0.011 n=10)
Str2ucs2_Medium-4           264.1Mi ± 0%   253.6Mi ± 0%  -3.96% (p=0.000 n=10)
Str2ucs2_Long-4             250.7Mi ± 3%   237.8Mi ± 2%  -5.11% (p=0.000 n=10)
Ucs22str_ASCII-4            2.207Gi ± 5%   2.059Gi ± 2%  -6.69% (p=0.001 n=10)
Ucs22str_Unicode-4          429.1Mi ± 3%   400.3Mi ± 5%  -6.71% (p=0.000 n=10)
Ucs22str_LongASCII-4        3.903Gi ± 6%   3.653Gi ± 3%  -6.41% (p=0.003 n=10)
geomean                     2.735Gi        2.645Gi       -3.30%

                         │ bench_old.txt  │             bench_new.txt              │
                         │      B/op      │     B/op      vs base                  │
TdsBuffer_Write_Small-4                0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_Write_Medium-4               0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_Write_Large-4                0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_WriteByte-4                  0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_Read_Small-4                 0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_ReadByte-4                   0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_Uint16-4                     0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_Uint32-4                     0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_Uint64-4                     0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
TdsBuffer_BeginFinishPacket-4          0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadBVarCharOrPanicWideChars-4         72.00 ± 0%       72.00 ± 0%        ~ (p=1.000 n=10) ¹
ReadBVarCharOrPanicOnly1WideChar-4     97.00 ± 0%       97.00 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_Int64-4                  8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_Int32-4                  4.000 ± 0%       4.000 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_Float64-4                8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_String_NVarChar-4        304.0 ± 0%       304.0 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_String_VarChar-4         48.00 ± 0%       48.00 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_DateTime-4               32.00 ± 0%       32.00 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_DateTime2-4              32.00 ± 0%       32.00 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_Bool-4                   1.000 ± 0%       1.000 ± 0%        ~ (p=1.000 n=10) ¹
BulkMakeParam_MultiColumn-4            72.00 ± 0%       72.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_StringToString-4         16.00 ± 0%       16.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_StringToBytes-4          72.00 ± 0%       72.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_BytesToString-4          88.00 ± 0%       88.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_BytesToBytes-4           96.00 ± 0%       96.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToInt64-4           8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToString-4          32.00 ± 0%       32.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_Float64ToFloat64-4       8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_TimeToTime-4             48.00 ± 0%       48.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_TimeToString-4           72.00 ± 0%       72.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_NilToBytes-4             24.00 ± 0%       24.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToInterface-4       16.00 ± 0%       16.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_StringToRawBytes-4       72.00 ± 0%       72.00 ± 0%        ~ (p=1.000 n=10) ¹
ConvertAssign_BoolToBool-4             1.000 ± 0%       1.000 ± 0%        ~ (p=1.000 n=10) ¹
RoundTrip_ConnectDisconnect-4        160.4Ki ± 2%     159.1Ki ± 1%        ~ (p=0.118 n=10)
RoundTrip_Select1-4                  2.602Ki ± 0%     2.602Ki ± 0%        ~ (p=1.000 n=10) ¹
RoundTrip_ParamQuery-4               5.782Ki ± 0%     5.782Ki ± 0%        ~ (p=1.000 n=10) ¹
RoundTrip_MultiRow-4                 37.17Ki ± 0%     37.32Ki ± 0%   +0.42% (p=0.000 n=10)
RoundTrip_LargeResultSet-4           277.3Ki ± 0%     277.1Ki ± 0%   -0.08% (p=0.000 n=10)
RoundTrip_ExecInsert-4               6.110Ki ± 0%     6.110Ki ± 0%        ~ (p=1.000 n=10)
RoundTrip_StoredProc-4               5.399Ki ± 0%     5.399Ki ± 0%        ~ (p=1.000 n=10) ¹
RoundTrip_Transaction-4              7.452Ki ± 0%     7.451Ki ± 0%        ~ (p=0.357 n=10)
RoundTrip_BulkInsert/Rows_100-4      72.43Ki ± 0%     72.20Ki ± 0%   -0.31% (p=0.000 n=10)
RoundTrip_BulkInsert/Rows_1000-4     583.8Ki ± 0%     583.6Ki ± 0%   -0.03% (p=0.000 n=10)
RoundTrip_ConcurrentQueries-4        2.633Ki ± 0%     2.634Ki ± 0%        ~ (p=0.669 n=10)
RoundTrip_MixedTypes-4               6.642Ki ± 0%     6.337Ki ± 0%   -4.59% (p=0.000 n=10)
RoundTrip_LargePayload-4             134.7Ki ± 0%     150.3Ki ± 0%  +11.57% (p=0.000 n=10)
RoundTrip_PreparedStmt-4             5.454Ki ± 0%     5.438Ki ± 0%   -0.29% (p=0.000 n=10)
RoundTrip_MessageQuery-4             13.76Ki ± 0%     13.61Ki ± 0%   -1.08% (p=0.000 n=10)
WriteTypeInfo_Int8-4                   2.000 ± 0%       2.000 ± 0%        ~ (p=1.000 n=10) ¹
WriteTypeInfo_NVarChar-4               8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
WriteTypeInfo_NVarCharMax-4            8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
WriteByteLenType-4                     1.000 ± 0%       1.000 ± 0%        ~ (p=1.000 n=10) ¹
WriteShortLenType-4                    2.000 ± 0%       2.000 ± 0%        ~ (p=1.000 n=10) ¹
WritePLPType_Short-4                   16.00 ± 0%       16.00 ± 0%        ~ (p=1.000 n=10) ¹
SendRpc_SingleIntParam-4             64.21Ki ± 0%     64.21Ki ± 0%        ~ (p=0.057 n=10)
Str2ucs2_Short-4                       56.00 ± 0%       56.00 ± 0%        ~ (p=1.000 n=10) ¹
Str2ucs2_Medium-4                      152.0 ± 0%       152.0 ± 0%        ~ (p=1.000 n=10) ¹
Str2ucs2_Long-4                        856.0 ± 0%       856.0 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22str_ASCII-4                       64.00 ± 0%       64.00 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22str_Unicode-4                     72.00 ± 0%       72.00 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22str_LongASCII-4                   144.0 ± 0%       144.0 ± 0%        ~ (p=1.000 n=10) ¹
ManglePassword_Short-4                 56.00 ± 0%       56.00 ± 0%        ~ (p=1.000 n=10) ¹
ManglePassword_Long-4                  408.0 ± 0%       408.0 ± 0%        ~ (p=1.000 n=10) ¹
SendLogin-4                            448.0 ± 0%       448.0 ± 0%        ~ (p=1.000 n=10) ¹
WritePrelogin-4                        50.00 ± 0%       50.00 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22strAscii-4                        3.000 ± 0%       3.000 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22strMediumAscii-4                  16.00 ± 0%       16.00 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22strLongAscii-4                    64.00 ± 0%       64.00 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22strLongerAscii-4                  640.0 ± 0%       640.0 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22strTrailingUnicode-4              48.00 ± 0%       48.00 ± 0%        ~ (p=1.000 n=10) ¹
Ucs22strLongEmojis-4                   128.0 ± 0%       128.0 ± 0%        ~ (p=1.000 n=10) ¹
ParseDone-4                            0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ParseDoneInProc-4                      0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ParseReturnStatus-4                    0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ParseOrder-4                           8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
ParseError72-4                         128.0 ± 0%       128.0 ± 0%        ~ (p=1.000 n=10) ¹
ParseInfo-4                            160.0 ± 0%       160.0 ± 0%        ~ (p=1.000 n=10) ¹
ParseLoginAck-4                        88.00 ± 0%       88.00 ± 0%        ~ (p=1.000 n=10) ¹
ParseFeatureExtAck_Empty-4             48.00 ± 0%       48.00 ± 0%        ~ (p=1.000 n=10) ¹
ParseTabName-4                         24.00 ± 0%       24.00 ± 0%        ~ (p=1.000 n=10) ¹
ParseColInfo-4                         24.00 ± 0%       24.00 ± 0%        ~ (p=1.000 n=10) ¹
ReadFixedType_Int64-4                  8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadFixedType_Int32-4                  0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadFixedType_Float64-4                8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadFixedType_DateTime-4               24.00 ± 0%       24.00 ± 0%        ~ (p=1.000 n=10) ¹
ReadByteLenType_IntN_8-4               8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadByteLenType_IntN_4-4               8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadByteLenType_FloatN_8-4             8.000 ± 0%       8.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadByteLenType_BitN-4                 0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadByteLenType_Null-4                 0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ReadShortLenType_NVarChar_Short-4      21.00 ± 0%       21.00 ± 0%        ~ (p=1.000 n=10) ¹
ReadShortLenType_NVarChar_Medium-4     80.00 ± 0%       80.00 ± 0%        ~ (p=1.000 n=10) ¹
ReadShortLenType_VarBinary-4           88.00 ± 0%       88.00 ± 0%        ~ (p=1.000 n=10) ¹
ReadByteLenType_VarChar-4              32.00 ± 0%       32.00 ± 0%        ~ (p=1.000 n=10) ¹
DecodeDateTime-4                       24.00 ± 0%       24.00 ± 0%        ~ (p=1.000 n=10) ¹
DecodeDateTim4-4                       24.00 ± 0%       24.00 ± 0%        ~ (p=1.000 n=10) ¹
EncodeDateTime-4                       32.00 ± 0%       32.00 ± 0%        ~ (p=1.000 n=10) ¹
geomean                                           ²                  +0.04%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                         │ bench_old.txt │            bench_new.txt             │
                         │   allocs/op   │  allocs/op   vs base                 │
TdsBuffer_Write_Small-4               0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Write_Medium-4              0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Write_Large-4               0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_WriteByte-4                 0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Read_Small-4                0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_ReadByte-4                  0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint16-4                    0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint32-4                    0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_Uint64-4                    0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
TdsBuffer_BeginFinishPacket-4         0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadBVarCharOrPanicWideChars-4        4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadBVarCharOrPanicOnly1WideChar-4    4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Int64-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Int32-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Float64-4               1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_String_NVarChar-4       3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_String_VarChar-4        1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_DateTime-4              2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_DateTime2-4             2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_Bool-4                  1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
BulkMakeParam_MultiColumn-4           5.000 ± 0%      5.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToString-4        1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToBytes-4         2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BytesToString-4         3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BytesToBytes-4          3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToInt64-4          1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToString-4         2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Float64ToFloat64-4      1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_TimeToTime-4            2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_TimeToString-4          3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_NilToBytes-4            1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_Int64ToInterface-4      1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_StringToRawBytes-4      2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ConvertAssign_BoolToBool-4            1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_ConnectDisconnect-4         745.5 ± 0%      745.0 ± 0%       ~ (p=0.102 n=10)
RoundTrip_Select1-4                   47.00 ± 0%      47.00 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_ParamQuery-4                116.0 ± 0%      116.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_MultiRow-4                 1.354k ± 0%     1.358k ± 0%  +0.30% (p=0.000 n=10)
RoundTrip_LargeResultSet-4           10.05k ± 0%     10.05k ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_ExecInsert-4                102.0 ± 0%      102.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_StoredProc-4                94.00 ± 0%      94.00 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_Transaction-4               138.0 ± 0%      138.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_BulkInsert/Rows_100-4      2.111k ± 0%     2.107k ± 0%  -0.21% (p=0.000 n=10)
RoundTrip_BulkInsert/Rows_1000-4     21.45k ± 0%     21.45k ± 0%  -0.02% (p=0.000 n=10)
RoundTrip_ConcurrentQueries-4         47.00 ± 0%      47.00 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_MixedTypes-4                72.00 ± 0%      71.00 ± 0%  -1.39% (p=0.000 n=10)
RoundTrip_LargePayload-4              110.0 ± 0%      115.0 ± 0%  +4.55% (p=0.000 n=10)
RoundTrip_PreparedStmt-4              103.0 ± 0%      103.0 ± 0%       ~ (p=1.000 n=10) ¹
RoundTrip_MessageQuery-4              215.0 ± 0%      217.0 ± 0%  +0.93% (p=0.000 n=10)
WriteTypeInfo_Int8-4                  2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteTypeInfo_NVarChar-4              4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteTypeInfo_NVarCharMax-4           4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteByteLenType-4                    1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
WriteShortLenType-4                   1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
WritePLPType_Short-4                  3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
SendRpc_SingleIntParam-4              17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=10) ¹
Str2ucs2_Short-4                      3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
Str2ucs2_Medium-4                     3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
Str2ucs2_Long-4                       4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_ASCII-4                      2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_Unicode-4                    3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22str_LongASCII-4                  2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ManglePassword_Short-4                3.000 ± 0%      3.000 ± 0%       ~ (p=1.000 n=10) ¹
ManglePassword_Long-4                 4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=10) ¹
SendLogin-4                           15.00 ± 0%      15.00 ± 0%       ~ (p=1.000 n=10) ¹
WritePrelogin-4                       12.00 ± 0%      12.00 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strAscii-4                       1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strMediumAscii-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongAscii-4                   1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongerAscii-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strTrailingUnicode-4             2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
Ucs22strLongEmojis-4                  2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseDone-4                           0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseDoneInProc-4                     0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseReturnStatus-4                   0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseOrder-4                          1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseError72-4                        7.000 ± 0%      7.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseInfo-4                           7.000 ± 0%      7.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseLoginAck-4                       2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseFeatureExtAck_Empty-4            1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseTabName-4                        1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ParseColInfo-4                        1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Int64-4                 1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Int32-4                 0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_Float64-4               1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadFixedType_DateTime-4              1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_IntN_8-4              1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_IntN_4-4              1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_FloatN_8-4            1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_BitN-4                0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_Null-4                0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_NVarChar_Short-4     2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_NVarChar_Medium-4    2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadShortLenType_VarBinary-4          2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
ReadByteLenType_VarChar-4             2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
DecodeDateTime-4                      1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
DecodeDateTim4-4                      1.000 ± 0%      1.000 ± 0%       ~ (p=1.000 n=10) ¹
EncodeDateTime-4                      2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                          ²                +0.04%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/microsoft/go-mssqldb/msdsn
             │ bench_old.txt │            bench_new.txt            │
             │    sec/op     │   sec/op     vs base                │
Parse_URL-4                1.968µ ± 1%   2.056µ ± 2%   +4.45% (p=0.000 n=10)
Parse_URL_Azure-4          2.390µ ± 2%   2.460µ ± 6%   +2.91% (p=0.002 n=10)
Parse_ADO-4                2.373µ ± 1%   2.479µ ± 4%   +4.47% (p=0.000 n=10)
Parse_URL_Minimal-4        1.302µ ± 3%   1.408µ ± 3%   +8.10% (p=0.000 n=10)
Parse_URL_ManyParams-4     3.601µ ± 0%   4.008µ ± 3%  +11.32% (p=0.000 n=10)
geomean                    2.207µ        2.344µ        +6.20%

             │ bench_old.txt │             bench_new.txt             │
             │     B/op      │     B/op      vs base                 │
Parse_URL-4               2.047Ki ± 0%   2.047Ki ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_Azure-4         2.680Ki ± 0%   2.680Ki ± 0%       ~ (p=1.000 n=10) ¹
Parse_ADO-4               2.500Ki ± 0%   2.500Ki ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_Minimal-4       1.625Ki ± 0%   1.625Ki ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_ManyParams-4    4.695Ki ± 0%   4.695Ki ± 0%       ~ (p=1.000 n=10) ¹
geomean                   2.535Ki        2.535Ki       +0.00%
¹ all samples are equal

             │ bench_old.txt │            bench_new.txt            │
             │   allocs/op   │ allocs/op   vs base                 │
Parse_URL-4                 22.00 ± 0%   22.00 ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_Azure-4           26.00 ± 0%   26.00 ± 0%       ~ (p=1.000 n=10) ¹
Parse_ADO-4                 40.00 ± 0%   40.00 ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_Minimal-4         16.00 ± 0%   16.00 ± 0%       ~ (p=1.000 n=10) ¹
Parse_URL_ManyParams-4      41.00 ± 0%   41.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                     27.24        27.24       +0.00%
¹ all samples are equal

Generated by CI — commit 60232fd

Saurabh Singh (SQL Drivers) and others added 4 commits August 16, 2026 14:14
Address Copilot review suggestions on the TDS fuzz harness:
- Add TestProcessSingleResponseMalformedSeeds so a regression that silently
  accepts an unknown or truncated token is caught, instead of relying only on
  the fuzz target (which checks for panics) and the boundary test (which
  excludes malformed seeds).
- Assert in TestProcessSingleResponsePacketBoundary that valid seeds never
  produce an error token, so a seed that fails identically for every
  fragmentation cannot pass the determinism comparison unnoticed.

Refs #418

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c3e268ca-c6f4-4edb-826c-3a4018cf939c
Address Copilot review suggestions on the TDS fuzz harness:
- frameReplyPackets now takes an explicit per-packet payload size instead of
  a fixed 1..8 fragment count, so callers can place a packet seam at any byte
  offset. The fuzz body derives the chunk size from the fuzzed byte, and
  TestProcessSingleResponsePacketBoundary now enumerates every byte boundary
  for each valid seed rather than four fixed splits.
- Give the ERROR seed's terminating DONE the doneError status bit so it is a
  protocol-faithful error response per MS-TDS rather than an orphaned error.

Refs #418

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c3e268ca-c6f4-4edb-826c-3a4018cf939c
Integrate upstream response-draining and connection-cleanup changes from #410 while preserving all TDS allocation fixes and regression coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67
Copilot AI review requested due to automatic review settings September 11, 2026 06:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Broad, security-sensitive parser changes require final human review.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@muskan124947

Copy link
Copy Markdown
Collaborator

No new findings at 0f46c75.

Integrate #469's upstream revert of #410 without changing the TDS allocation fixes. The resulting source tree matches the previously validated b8067c0 tree.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67
Copilot AI review requested due to automatic review settings September 23, 2026 02:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Security-sensitive parser changes require final human review.

Review effort: Lite
Findings: None

Integrate the upstream release version, manifest, and changelog from #456 without changing the TDS allocation fixes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The fixed-width sql_variant payload size remains insufficiently validated, allowing malformed data to desynchronize parsing.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)

Comment thread types.go Outdated
Reject invalid property counts, fixed and scale-dependent payload widths, decimal lengths, and undersized headers as StreamError before reading outside a variant. Preserve valid values across consecutive reads and packet boundaries, and add ROW, NBCROW, return-value and fuzz regressions.

Fixes #420

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67
Comment thread types.go Outdated
Comment thread types_variant_test.go Outdated
Address QF1003 in the new date/time variant width checks and their regression fixtures without changing behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved result-column, CEK allocation, and malformed UTF-16 handling issues remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)
Resolved since last review (1)

Comment thread token.go
Comment thread types.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Odd-width Unicode variant payloads can leave malformed response bytes available for connection reuse.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)

Reject incomplete UTF-16 code units before reading variant properties or payloads so malformed results retire the connection. Cover both Unicode types, empty and even-length values, consecutive reads, fragmented responses, and permanent fuzz seeds.

Fixes #420

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The changes require final human review because they are too complex or risky for automated approval.

Review effort: Lite
Findings: None

Resolved since last review (2)

Integrate the upstream yes/no boolean connection-string support from #468, including its tests and documentation, without changing the TDS allocation and variant fixes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

All reviewed changes have adequate regression and fuzz coverage with no unresolved blocking issues.

Review effort: Lite
Findings: None

Integrate the upstream release version, manifest, and changelog from #470 without changing the TDS allocation and variant fixes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The changes span multiple protocol parsers and require final human review.

Review effort: Lite
Findings: None

@muskan124947

Copy link
Copy Markdown
Collaborator

Automated review deferred: benchmarks failed. The regression check reported EncodeDateTime-4 67.95n ± 0% → 80.50n ± 1% +18.45% (p=0.000 n=10), which exceeds the workflow threshold of >15% at p<0.01, and the step exited with Statistically significant regression detected (>15%, p<0.01). Job log.

No code review was performed. Please investigate the failure. This sweep will reconsider the PR only after a new commit, provided the Ready-For-Maintainer-Review label remains applied.

Integrate the upstream Authentication keyword and ADO.NET value aliases from #368, including regression tests, without changing the TDS allocation and variant fixes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Preserve non-nil empty-slice semantics for valid zero-length UDT values and add regression coverage.

Review effort: Lite
Findings: None

@saurabh500

Copy link
Copy Markdown
Collaborator Author

Refuted as a current behavior regression: readVarLen always selects readPLPType for UDTs; readPLPBytes starts with a non-nil empty slice and decodeUdt returns it unchanged, so the short-length reader's buffer is not involved.

Preserve non-nil empty-slice semantics for valid zero-length UDT values and add regression coverage.

An isolated test through the metadata-selected reader confirmed known-/unknown-length empty values stay non-nil, NULL stays nil, and non-empty values decode correctly across two consecutive passes and single-packet/1-byte/3-byte fragmentation, without changing production code.

Integrate upstream regression and compatibility review guidance from #471. Driver code, tests, and the TDS allocation fixes remain unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8081545c-165e-47b4-b155-797228e2ee67

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The security-sensitive parser changes are broad and require final human review.

Review effort: Lite
Findings: None

@muskan124947

Copy link
Copy Markdown
Collaborator

No new code findings at bfee5b4.

Reviewed token.go and types.go in full at the head SHA, against base 60674a5, after reading the existing inline comments, review bodies (including the Suppressed comments blocks) and top-level comments. Everything I could reach was either already raised and addressed, or correct as written. This means no new demonstrated findings — not proof that every compatibility case is excluded.

Checked and found sound: the FEDAUTHINFO bounds now hold on both ends (4 + 9*count <= size keeps size-offset non-negative, and dataOffset >= offset plus the uint64 sum keep data[dataOffset-offset : +dataLength] inside the buffer with no uint32 wrap); the sql_variant property/payload widths match the MS-TDS layout for every accepted type, including the four decimal widths and the scale-derived time widths via calcTimeSize; readPLPBytes cannot underflow maxLen - buf.Len() because each chunk is checked against the remaining allowance first, and bytes.NewBuffer([]byte{}) keeps a zero-length PLP value non-nil and distinct from NULL; the parseColMetadata72 value-then-append rewrite is semantically equivalent, since column.cryptoMeta was zero-valued at the readTypeInfo call in the original too, and parseCryptoMetadata only takes &cekTable.entries[ordinal] after readCekTable has finished appending, so no pointer is invalidated by a regrow.

The one area of the diff nobody had commented on is getBuffer/growBuffer. Tracing it through the Always Encrypted path, decryptColumn sets typeInfo.Buffer = d and r.rsize = len(d), so size == len(ti.Buffer) <= ti.Size and getBuffer returns the same d[:size] the old ti.Buffer[:size] produced; the binary readers still copy before returning, and the growth path only replaces the backing array, which is strictly safer than the previous fixed buffer. TestValueReaders_LazyBufferReuse, _RejectOversizedValues and _DecryptedBuffers already pin that behavior.

Verification gap, for the record: no Go toolchain was available on the sweep host, so none of this was executed — it is source reasoning plus the green CI matrix (all 12 build legs, benchmarks, AppVeyor, go-mssqldb Tests, CodeQL, golangci-lint) and Codecov's 97.6% patch coverage at this SHA. The live-server and Windows-native evidence remains CI's.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unbounded allocations from attacker-controlled length prefixes in TDS response parsing (OOM DoS)

5 participants