Skip to content

Commit 7ebb96d

Browse files
Fix unsafe cast in SqlException for SerializationEntry.Value (#527)
1 parent 47f918a commit 7ebb96d

File tree

4 files changed

+113
-11
lines changed

4 files changed

+113
-11
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ private SqlException(SerializationInfo si, StreamingContext sc) : base(si, sc)
3636
HResult = SqlExceptionHResult;
3737
foreach (SerializationEntry siEntry in si)
3838
{
39-
if ("ClientConnectionId" == siEntry.Name)
39+
if (nameof(ClientConnectionId) == siEntry.Name)
4040
{
41-
_clientConnectionId = (Guid)siEntry.Value;
41+
_clientConnectionId = (Guid)si.GetValue(nameof(ClientConnectionId), typeof(Guid));
4242
break;
4343
}
4444
}
@@ -49,7 +49,7 @@ public override void GetObjectData(SerializationInfo si, StreamingContext contex
4949
{
5050
base.GetObjectData(si, context);
5151
si.AddValue("Errors", null); // Not specifying type to enable serialization of null value of non-serializable type
52-
si.AddValue("ClientConnectionId", _clientConnectionId, typeof(Guid));
52+
si.AddValue("ClientConnectionId", _clientConnectionId, typeof(object));
5353

5454
// Writing sqlerrors to base exception data table
5555
for (int i = 0; i < Errors.Count; i++)

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,31 @@ private SqlException(SerializationInfo si, StreamingContext sc) : base(si, sc)
3838
HResult = HResults.SqlException;
3939
foreach (SerializationEntry siEntry in si)
4040
{
41-
if ("ClientConnectionId" == siEntry.Name)
41+
if (nameof(ClientConnectionId) == siEntry.Name)
4242
{
43-
_clientConnectionId = (Guid)siEntry.Value;
43+
_clientConnectionId = (Guid)si.GetValue(nameof(ClientConnectionId), typeof(Guid));
4444
break;
4545
}
4646
}
47-
4847
}
4948

5049
/// <include file='..\..\..\..\..\..\..\doc\snippets\Microsoft.Data.SqlClient\SqlException.xml' path='docs/members[@name="SqlException"]/GetObjectData/*' />
5150
override public void GetObjectData(SerializationInfo si, StreamingContext context)
5251
{
53-
if (null == si)
52+
base.GetObjectData(si, context);
53+
si.AddValue("Errors", null); // Not specifying type to enable serialization of null value of non-serializable type
54+
si.AddValue("ClientConnectionId", _clientConnectionId, typeof(object));
55+
56+
// Writing sqlerrors to base exception data table
57+
for (int i = 0; i < Errors.Count; i++)
5458
{
55-
throw new ArgumentNullException("si");
59+
string key = "SqlError " + (i + 1);
60+
if (Data.Contains(key))
61+
{
62+
Data.Remove(key);
63+
}
64+
Data.Add(key, Errors[i].ToString());
5665
}
57-
si.AddValue("Errors", _errors, typeof(SqlErrorCollection));
58-
si.AddValue("ClientConnectionId", _clientConnectionId, typeof(Guid));
59-
base.GetObjectData(si, context);
6066
}
6167

6268
/// <include file='..\..\..\..\..\..\..\doc\snippets\Microsoft.Data.SqlClient\SqlException.xml' path='docs/members[@name="SqlException"]/Errors/*' />

src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<Compile Include="BaseProviderAsyncTest\MockDataReader.cs" />
3737
<Compile Include="SqlCredentialTest.cs" />
3838
<Compile Include="SqlDataRecordTest.cs" />
39+
<Compile Include="SqlExceptionTest.cs" />
3940
<Compile Include="SqlParameterTest.cs" />
4041
<Compile Include="SqlClientFactoryTest.cs" />
4142
<Compile Include="SqlErrorCollectionTest.cs" />
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using Newtonsoft.Json;
7+
using Xunit;
8+
9+
namespace Microsoft.Data.SqlClient.Tests
10+
{
11+
public class SqlExceptionTest
12+
{
13+
private const string badServer = "92B96911A0BD43E8ADA4451031F7E7CF";
14+
15+
[Fact]
16+
[ActiveIssue("12161", TestPlatforms.AnyUnix)]
17+
public void SerializationTest()
18+
{
19+
SqlException e = CreateException();
20+
string json = JsonConvert.SerializeObject(e);
21+
22+
var settings = new JsonSerializerSettings()
23+
{
24+
TypeNameHandling = TypeNameHandling.All,
25+
};
26+
27+
// TODO: Deserialization fails on Unix with "Member 'ClassName' was not found."
28+
var sqlEx = JsonConvert.DeserializeObject<SqlException>(json, settings);
29+
30+
Assert.Equal(e.ClientConnectionId, sqlEx.ClientConnectionId);
31+
Assert.Equal(e.StackTrace, sqlEx.StackTrace);
32+
}
33+
34+
[Fact]
35+
public void JSONSerializationTest()
36+
{
37+
string clientConnectionId = "90cdab4d-2145-4c24-a354-c8ccff903542";
38+
string json = @"{""ClassName"":""Microsoft.Data.SqlClient.SqlException"","
39+
+ @"""Message"":""A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server)"","
40+
+ @"""Data"":{""HelpLink.ProdName"":""Microsoft SQL Server"","
41+
+ @"""HelpLink.EvtSrc"":""MSSQLServer"","
42+
+ @"""HelpLink.EvtID"":""0"","
43+
+ @"""HelpLink.BaseHelpUrl"":""http://go.microsoft.com/fwlink"","
44+
+ @"""HelpLink.LinkId"":""20476"","
45+
+ @"""SqlError 1"":""Microsoft.Data.SqlClient.SqlError: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server)"","
46+
+ @"""$type"":""System.Collections.ListDictionaryInternal, System.Private.CoreLib""},"
47+
+ @"""InnerException"":null,"
48+
+ @"""HelpURL"":null,"
49+
+ @"""StackTraceString"":"" at Microsoft.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken)\\n at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)\\n at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)\\n at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)\\n at System.Data.ProviderBase.DbConnectionPool.WaitForPendingOpen()\\n"","
50+
+ @"""RemoteStackTraceString"":null,"
51+
+ @"""RemoteStackIndex"":0,"
52+
+ @"""ExceptionMethod"":null,"
53+
+ @"""HResult"":-2146232060,"
54+
+ @"""Source"":""Core .Net SqlClient Data Provider"","
55+
+ @"""WatsonBuckets"":null,"
56+
+ @"""Errors"":null,"
57+
+ @"""ClientConnectionId"":""90cdab4d-2145-4c24-a354-c8ccff903542""}";
58+
59+
var settings = new JsonSerializerSettings()
60+
{
61+
TypeNameHandling = TypeNameHandling.All,
62+
};
63+
64+
var sqlEx = JsonConvert.DeserializeObject<SqlException>(json, settings);
65+
Assert.IsType<SqlException>(sqlEx);
66+
Assert.Equal(clientConnectionId, sqlEx.ClientConnectionId.ToString());
67+
}
68+
69+
private static SqlException CreateException()
70+
{
71+
var builder = new SqlConnectionStringBuilder()
72+
{
73+
DataSource = badServer,
74+
ConnectTimeout = 1,
75+
Pooling = false
76+
};
77+
78+
using (var connection = new SqlConnection(builder.ConnectionString))
79+
{
80+
try
81+
{
82+
connection.Open();
83+
}
84+
catch (SqlException ex)
85+
{
86+
Assert.NotNull(ex.Errors);
87+
Assert.Single(ex.Errors);
88+
89+
return ex;
90+
}
91+
}
92+
throw new InvalidOperationException("SqlException should have been returned.");
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)