Skip to content

Android SslStream splits messages over 2048 bytes in multiple TLS records (breaks MS-TDS protocol) #95295

@awakecoding

Description

@awakecoding

Description

The Android SslStream implementation using the Android SSLEngine APIs splits application data over 2048 bytes in multiple TLS records, something which normally isn't important, except for SQL Server which expects TDS RPC requests (query messages) to be sent in a single TLS record, breaking Microsoft.Data.SqlClient on Android. This issue only affects SslStream Android implementation, all other platforms resize internal buffers to send larger TLS records that can be safely fragmented at the transport level instead.

Reproduction Steps

Write a simple Android project that makes a TLS connection to a service and send a message of size larger than 2048 bytes - if it is encoded in multiple TLS records, the issue is successfully reproduced. As for reproducing the issue in a context where it matters enough to cause SQL Server to disconnect the client, a test project is available in the original SqlClient issue. From there, all you need is to connect to SQL Server with TLS configured and enforced and make a query using a string longer than 956 characters (UTF-16 doubles the byte size + header overhead), resulting in a TDS RPC request longer than 2048 bytes over the wire. Upon receiving the message split in two TLS records, SQL Server drops the connection:

image

Expected behavior

SslStream.Write should send input buffers in the smallest number of output TLS records by default, resizing internal buffers accordingly, and taking into account the maximum TLS record data size (16384). This means that calling SslStream.Write with a buffer of 10000 bytes should result in a single TLS record even if internal buffer sizes were initialized to a smaller size. As long as we try sending the input buffers as complete TLS records instead of splitting them, edge cases like MS-TDS RPC requests sent to SQL Server should work.

Actual behavior

SslStream.Write with input buffers larger than 2048 bytes are automatically split and sent in multiple TLS records, breaking long SQL queries made with SqlClient on Android.

Regression?

This used to work in the old Xamarin.Android runtime prior to .NET Core (yes, the ancient one!), but since we've been waiting for .NET 6,7,8 for other SslStream-related Android issues to be fixed, we only noticed this particular regression after finally switching to .NET 8.

Known Workarounds

Increasing the internal initial buffer size from 2048 to 4096 is sufficient to work around the issue, and we have managed to inject a patched System.Net.Security.dll in our builds as a temporary solution. However, it's far from ideal and an upstream fix is needed in the .NET runtime, even if it means just increasing the initial buffer size upstream as a quick fix.

# https://github.com/dotnet/SqlClient/issues/2141
$SystemNetSecurityDllZipUrl = "https://github.com/dotnet/SqlClient/files/13468922/System.Net.Security.dll.zip"
Invoke-WebRequest -Uri $SystemNetSecurityDllZipUrl -OutFile "System.Net.Security.dll.zip"
Expand-Archive "System.Net.Security.dll.zip" -DestinationPath .
$AndroidRuntimePrefix = "C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Runtime.Mono.android"
Get-Item "${AndroidRuntimePrefix}-*\8.0.0\runtimes\android-*\lib\net8.0\System.Net.Security.dll" | % {
  $BackupFile = ($_.FullName + ".bak")
  Move-Item $_ $BackupFile -Force
  Copy-Item "System.Net.Security.dll" $_ -Force
  @($_, $BackupFile) | % { Get-FileHash $_ } | Format-List
}

Configuration

.NET 8 for Android - both x64 and ARM64. The same code using SqlClient on all other platforms (iOS, Windows, macOS, Linux) is unaffected by this issue.

Other information

A lot of additional information is present in the original SqlClient issue.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions