Skip to content

Commit c5a2169

Browse files
[6.1] Add TDS token data length bounds checks (#4340) (#4359)
1 parent b448b72 commit c5a2169

10 files changed

Lines changed: 1367 additions & 16 deletions

File tree

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,6 +2799,12 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
27992799
{
28002800
len = bLen;
28012801
}
2802+
2803+
if (len < 0 || len > data.Length - i)
2804+
{
2805+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, len);
2806+
}
2807+
28022808
byte[] stateData = new byte[len];
28032809
Buffer.BlockCopy(data, i, stateData, 0, len);
28042810
i += len;

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2906,6 +2906,10 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb
29062906
// new value has 4 byte length
29072907
return result;
29082908
}
2909+
if (env._newLength < 0 || env._newLength > TdsEnums.MaxPromoteTransactionLength)
2910+
{
2911+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, env._newLength);
2912+
}
29092913
// read new value with 4 byte length
29102914
env._newBinValue = new byte[env._newLength];
29112915
result = stateObj.TryReadByteArray(env._newBinValue, env._newLength);
@@ -3300,10 +3304,15 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj
33003304
{
33013305
return result;
33023306
}
3303-
byte[] data = new byte[dataLen];
3304-
if (dataLen > 0)
3307+
if (dataLen > (uint)TdsEnums.MaxTokenDataLength)
3308+
{
3309+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, dataLen);
3310+
}
3311+
int dataLength = (int)dataLen;
3312+
byte[] data = new byte[dataLength];
3313+
if (dataLength > 0)
33053314
{
3306-
result = stateObj.TryReadByteArray(data, checked((int)dataLen));
3315+
result = stateObj.TryReadByteArray(data, dataLength);
33073316
if (result != TdsOperationStatus.Done)
33083317
{
33093318
return result;
@@ -3623,6 +3632,10 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj,
36233632
{
36243633
throw SQL.ParsingError();
36253634
}
3635+
if (length > TdsEnums.MaxTokenDataLength)
3636+
{
3637+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length);
3638+
}
36263639
uint seqNum;
36273640
TdsOperationStatus result = stateObj.TryReadUInt32(out seqNum);
36283641
if (result != TdsOperationStatus.Done)
@@ -3672,6 +3685,10 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj,
36723685
return result;
36733686
}
36743687
}
3688+
if (stateLen < 0 || stateLen > TdsEnums.MaxTokenDataLength)
3689+
{
3690+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, stateLen);
3691+
}
36753692
byte[] buffer = null;
36763693
lock (sdata._delta)
36773694
{
@@ -3888,6 +3905,10 @@ private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj,
38883905
SqlClientEventSource.Log.TryTraceEvent("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream length too short for CountOfInfoIDs.");
38893906
throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForCountOfInfoIds, tokenLen);
38903907
}
3908+
if (tokenLen > TdsEnums.MaxTokenDataLength)
3909+
{
3910+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, tokenLen);
3911+
}
38913912

38923913
// read how many FedAuthInfo options there are
38933914
uint optionsCount;
@@ -4361,14 +4382,20 @@ internal TdsOperationStatus TryProcessReturnValue(int length,
43614382
}
43624383

43634384
// always read as sql types
4364-
Debug.Assert(valLen < (ulong)(int.MaxValue), "ProcessReturnValue received data size > 2Gb");
4365-
4366-
int intlen = valLen > (ulong)(int.MaxValue) ? int.MaxValue : (int)valLen;
4385+
int intlen;
43674386

43684387
if (rec.metaType.IsPlp)
43694388
{
43704389
intlen = int.MaxValue; // If plp data, read it all
43714390
}
4391+
else if (valLen > (ulong)int.MaxValue)
4392+
{
4393+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, unchecked((int)valLen));
4394+
}
4395+
else
4396+
{
4397+
intlen = (int)valLen;
4398+
}
43724399

43734400
if (rec.type == SqlDbTypeExtensions.Vector)
43744401
{
@@ -6548,7 +6575,14 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
65486575

65496576
private TdsOperationStatus TryReadSqlDateTime(SqlBuffer value, byte tdsType, int length, byte scale, TdsParserStateObject stateObj)
65506577
{
6551-
Span<byte> datetimeBuffer = ((uint)length <= 16) ? stackalloc byte[16] : new byte[length];
6578+
// DateTimeOffset is the largest datetime type at 10 bytes (5 time + 3 date + 2 offset).
6579+
// Reject anything larger to prevent heap allocation from spoofed metadata.
6580+
if (length < 0 || length > TdsEnums.MaxDateTimeLength)
6581+
{
6582+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length);
6583+
}
6584+
6585+
Span<byte> datetimeBuffer = stackalloc byte[TdsEnums.MaxDateTimeLength];
65526586
TdsOperationStatus result;
65536587

65546588
result = stateObj.TryReadByteArray(datetimeBuffer, length);
@@ -6801,7 +6835,10 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp
68016835
case TdsEnums.SQLVECTOR:
68026836
{
68036837
// Note: Better not come here with plp data!!
6804-
Debug.Assert(length <= TdsEnums.MAXSIZE);
6838+
if (length < 0 || length > TdsEnums.MAXSIZE)
6839+
{
6840+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length);
6841+
}
68056842
byte[] b = new byte[length];
68066843
result = stateObj.TryReadByteArray(b, length);
68076844
if (result != TdsOperationStatus.Done)

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2836,6 +2836,12 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
28362836
{
28372837
len = bLen;
28382838
}
2839+
2840+
if (len < 0 || len > data.Length - i)
2841+
{
2842+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, len);
2843+
}
2844+
28392845
byte[] stateData = new byte[len];
28402846
Buffer.BlockCopy(data, i, stateData, 0, len);
28412847
i += len;

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2965,6 +2965,10 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb
29652965
// new value has 4 byte length
29662966
return result;
29672967
}
2968+
if (env._newLength < 0 || env._newLength > TdsEnums.MaxPromoteTransactionLength)
2969+
{
2970+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, env._newLength);
2971+
}
29682972
// read new value with 4 byte length
29692973
env._newBinValue = new byte[env._newLength];
29702974
result = stateObj.TryReadByteArray(env._newBinValue, env._newLength);
@@ -3359,10 +3363,15 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj
33593363
{
33603364
return result;
33613365
}
3362-
byte[] data = new byte[dataLen];
3363-
if (dataLen > 0)
3366+
if (dataLen > (uint)TdsEnums.MaxTokenDataLength)
3367+
{
3368+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, dataLen);
3369+
}
3370+
int dataLength = (int)dataLen;
3371+
byte[] data = new byte[dataLength];
3372+
if (dataLength > 0)
33643373
{
3365-
result = stateObj.TryReadByteArray(data, checked((int)dataLen));
3374+
result = stateObj.TryReadByteArray(data, dataLength);
33663375
if (result != TdsOperationStatus.Done)
33673376
{
33683377
return result;
@@ -3682,6 +3691,10 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj,
36823691
{
36833692
throw SQL.ParsingErrorLength(ParsingErrorState.SessionStateLengthTooShort, length);
36843693
}
3694+
if (length > TdsEnums.MaxTokenDataLength)
3695+
{
3696+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length);
3697+
}
36853698
uint seqNum;
36863699
TdsOperationStatus result = stateObj.TryReadUInt32(out seqNum);
36873700
if (result != TdsOperationStatus.Done)
@@ -3731,6 +3744,10 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj,
37313744
return result;
37323745
}
37333746
}
3747+
if (stateLen < 0 || stateLen > TdsEnums.MaxTokenDataLength)
3748+
{
3749+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, stateLen);
3750+
}
37343751
byte[] buffer = null;
37353752
lock (sdata._delta)
37363753
{
@@ -3947,6 +3964,10 @@ private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj,
39473964
SqlClientEventSource.Log.TryTraceEvent("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream length too short for CountOfInfoIDs.");
39483965
throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForCountOfInfoIds, tokenLen);
39493966
}
3967+
if (tokenLen > TdsEnums.MaxTokenDataLength)
3968+
{
3969+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, tokenLen);
3970+
}
39503971

39513972
// read how many FedAuthInfo options there are
39523973
uint optionsCount;
@@ -4421,14 +4442,20 @@ internal TdsOperationStatus TryProcessReturnValue(int length,
44214442
}
44224443

44234444
// always read as sql types
4424-
Debug.Assert(valLen < (ulong)(int.MaxValue), "ProcessReturnValue received data size > 2Gb");
4425-
4426-
int intlen = valLen > (ulong)(int.MaxValue) ? int.MaxValue : (int)valLen;
4445+
int intlen;
44274446

44284447
if (rec.metaType.IsPlp)
44294448
{
44304449
intlen = int.MaxValue; // If plp data, read it all
44314450
}
4451+
else if (valLen > (ulong)int.MaxValue)
4452+
{
4453+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, unchecked((int)valLen));
4454+
}
4455+
else
4456+
{
4457+
intlen = (int)valLen;
4458+
}
44324459

44334460
if (rec.type == SqlDbTypeExtensions.Vector)
44344461
{
@@ -6736,7 +6763,14 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value,
67366763

67376764
private TdsOperationStatus TryReadSqlDateTime(SqlBuffer value, byte tdsType, int length, byte scale, TdsParserStateObject stateObj)
67386765
{
6739-
Span<byte> datetimeBuffer = ((uint)length <= 16) ? stackalloc byte[16] : new byte[length];
6766+
// DateTimeOffset is the largest datetime type at 10 bytes (5 time + 3 date + 2 offset).
6767+
// Reject anything larger to prevent heap allocation from spoofed metadata.
6768+
if (length < 0 || length > TdsEnums.MaxDateTimeLength)
6769+
{
6770+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length);
6771+
}
6772+
6773+
Span<byte> datetimeBuffer = stackalloc byte[TdsEnums.MaxDateTimeLength];
67406774

67416775
TdsOperationStatus result = stateObj.TryReadByteArray(datetimeBuffer, length);
67426776
if (result != TdsOperationStatus.Done)
@@ -6993,7 +7027,10 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp
69937027
case TdsEnums.SQLVECTOR:
69947028
{
69957029
// Note: Better not come here with plp data!!
6996-
Debug.Assert(length <= TdsEnums.MAXSIZE);
7030+
if (length < 0 || length > TdsEnums.MAXSIZE)
7031+
{
7032+
throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length);
7033+
}
69977034
byte[] b = new byte[length];
69987035
result = stateObj.TryReadByteArray(b, length);
69997036
if (result != TdsOperationStatus.Done)

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,10 @@ internal static Exception ParsingErrorLength(ParsingErrorState state, int length
920920
{
921921
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ParsingErrorLength, ((int)state).ToString(CultureInfo.InvariantCulture), length));
922922
}
923+
internal static Exception ParsingErrorLength(ParsingErrorState state, uint length)
924+
{
925+
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ParsingErrorLength, ((int)state).ToString(CultureInfo.InvariantCulture), length));
926+
}
923927
internal static Exception ParsingErrorStatus(ParsingErrorState state, int status)
924928
{
925929
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ParsingErrorStatus, ((int)state).ToString(CultureInfo.InvariantCulture), status));

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@ internal static class TdsEnums
8888
public const int MAX_PACKET_SIZE = 32768;
8989
public const int MAX_SERVER_USER_NAME = 256; // obtained from luxor
9090

91+
// Maximum allowed data length for token payloads (feature ext ack,
92+
// session state, fedauth info). Prevents a malicious server from causing
93+
// unbounded memory allocation via spoofed token length fields.
94+
internal const int MaxTokenDataLength = 1 << 20; // 1 MB
95+
96+
// Maximum allowed data length for a DTC promote transaction propagation token.
97+
internal const int MaxPromoteTransactionLength = 1 << 16; // 64 KB
98+
99+
// Maximum valid wire size for datetime types (DateTimeOffset = 5 time + 3 date + 2 offset).
100+
internal const int MaxDateTimeLength = 10;
101+
91102
// Severity 0 - 10 indicates informational (non-error) messages
92103
// Severity 11 - 16 indicates errors that can be corrected by user (syntax errors, etc...)
93104
// Severity 17 - 19 indicates failure due to insufficient resources in the server

0 commit comments

Comments
 (0)