Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commiteb4c0f0

Browse files
authored
Advance column index to avoid double clean. (#2825)
1 parent62af3b5 commiteb4c0f0

File tree

5 files changed

+243
-7
lines changed

5 files changed

+243
-7
lines changed

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6388,16 +6388,36 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md,
63886388
{
63896389
if (stateObj is not null)
63906390
{
6391-
// call to decrypt column keys has failed. The data wont be decrypted.
6392-
// Not setting the value to false, forces the driver to look for column value.
6393-
// Packet received from Key Vault will throws invalid token header.
6394-
if (stateObj.HasPendingData)
6391+
// Throwing an exception here circumvents the normal pending data checks and cleanup processes,
6392+
// so we need to ensure the appropriate state. Increment the _nextColumnDataToRead index because
6393+
// we already read the encrypted column data; Otherwise we'll double count and attempt to drain a
6394+
// corresponding number of bytes a second time. We don't want the rest of the pending data to
6395+
// interfere with future operations, so we must drain it. Set HasPendingData to false to indicate
6396+
// that we successfully drained the data.
6397+
6398+
// The SqlDataReader also maintains a state called dataReady. We need to set that to false if we've
6399+
// drained the data off the connection. Otherwise, a consumer that catches the exception may
6400+
// continue to use the reader and will timeout waiting to read data that doesn't exist.
6401+
6402+
// Order matters here. Must increment column before draining data.
6403+
// Update state objects after draining data.
6404+
6405+
6406+
6407+
if (stateObj._readerState != null)
63956408
{
6396-
// Drain the pending data now if setting the HasPendingData to false.
6397-
// SqlDataReader.TryCloseInternal can not drain if HasPendingData = false.
6398-
DrainData(stateObj);
6409+
stateObj._readerState._nextColumnDataToRead++;
63996410
}
6411+
6412+
DrainData(stateObj);
6413+
6414+
if (stateObj._readerState != null)
6415+
{
6416+
stateObj._readerState._dataReady = false;
6417+
}
6418+
64006419
stateObj.HasPendingData = false;
6420+
64016421
}
64026422
throw SQL.ColumnDecryptionFailed(columnName, null, e);
64036423
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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+
usingSystem;
6+
usingSystem.Collections;
7+
usingSystem.Collections.Generic;
8+
usingMicrosoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup;
9+
usingMicrosoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals;
10+
usingXunit;
11+
12+
namespaceMicrosoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
13+
{
14+
publicsealedclassColumnDecryptErrorTests:IClassFixture<SQLSetupStrategyAzureKeyVault>,IDisposable
15+
{
16+
privateSQLSetupStrategyAzureKeyVaultfixture;
17+
18+
privatereadonlystringtableName;
19+
20+
publicColumnDecryptErrorTests(SQLSetupStrategyAzureKeyVaultcontext)
21+
{
22+
fixture=context;
23+
tableName=fixture.ColumnDecryptErrorTestTable.Name;
24+
}
25+
26+
/*
27+
* This test ensures that column decryption errors and connection pooling play nicely together.
28+
* When a decryption error is encountered, we expect the connection to be drained of data and
29+
* properly reset before being returned to the pool. If this doesn't happen, then random bytes
30+
* may be left in the connection's state. These can interfere with the next operation that utilizes
31+
* the connection.
32+
*
33+
* We test that state is properly reset by triggering the same error condition twice. Routing column key discovery
34+
* away from AKV toward a dummy key store achieves this. Each connection pulls from a pool of max
35+
* size one to ensure we are using the same internal connection/socket both times. We expect to
36+
* receive the "Failed to decrypt column" exception twice. If the state were not cleaned properly,
37+
* the second error would be different because the TDS stream would be unintelligible.
38+
*
39+
* Finally, we assert that restoring the connection to AKV allows a successful query.
40+
*/
41+
[ConditionalTheory(typeof(DataTestUtility),nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore),nameof(DataTestUtility.IsAKVSetupAvailable))]
42+
[ClassData(typeof(TestQueries))]
43+
publicvoidTestCleanConnectionAfterDecryptFail(stringconnString,stringselectQuery,inttotalColumnsInSelect,string[]types)
44+
{
45+
// Arrange
46+
Assert.False(string.IsNullOrWhiteSpace(selectQuery),"FAILED: select query should not be null or empty.");
47+
Assert.True(totalColumnsInSelect<=3,"FAILED: totalColumnsInSelect should <= 3.");
48+
49+
using(SqlConnectionsqlConnection=newSqlConnection(connString))
50+
{
51+
sqlConnection.Open();
52+
53+
Table.DeleteData(tableName,sqlConnection);
54+
55+
Customercustomer=newCustomer(
56+
45,
57+
"Microsoft",
58+
"Corporation");
59+
60+
DatabaseHelper.InsertCustomerData(sqlConnection,null,tableName,customer);
61+
}
62+
63+
64+
// Act - Trigger a column decrypt error on the connection
65+
Dictionary<String,SqlColumnEncryptionKeyStoreProvider>keyStoreProviders=new()
66+
{
67+
{"AZURE_KEY_VAULT",newDummyKeyStoreProvider()}
68+
};
69+
70+
StringpoolEnabledConnString=newSqlConnectionStringBuilder(connString){Pooling=true,MaxPoolSize=1}.ToString();
71+
72+
using(SqlConnectionsqlConnection=newSqlConnection(poolEnabledConnString))
73+
{
74+
sqlConnection.Open();
75+
sqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(keyStoreProviders);
76+
77+
usingSqlCommandsqlCommand=newSqlCommand(string.Format(selectQuery,tableName),
78+
sqlConnection,null,SqlCommandColumnEncryptionSetting.Enabled);
79+
80+
usingSqlDataReadersqlDataReader=sqlCommand.ExecuteReader();
81+
82+
Assert.True(sqlDataReader.HasRows,"FAILED: Select statement did not return any rows.");
83+
84+
while(sqlDataReader.Read())
85+
{
86+
varerror=Assert.Throws<SqlException>(()=>DatabaseHelper.CompareResults(sqlDataReader,types,totalColumnsInSelect));
87+
Assert.Contains("Failed to decrypt column",error.Message);
88+
}
89+
}
90+
91+
92+
// Assert
93+
using(SqlConnectionsqlConnection=newSqlConnection(poolEnabledConnString))
94+
{
95+
sqlConnection.Open();
96+
sqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(keyStoreProviders);
97+
98+
usingSqlCommandsqlCommand=newSqlCommand(string.Format(selectQuery,tableName),
99+
sqlConnection,null,SqlCommandColumnEncryptionSetting.Enabled);
100+
usingSqlDataReadersqlDataReader=sqlCommand.ExecuteReader();
101+
102+
Assert.True(sqlDataReader.HasRows,"FAILED: Select statement did not return any rows.");
103+
104+
while(sqlDataReader.Read())
105+
{
106+
varerror=Assert.Throws<SqlException>(()=>DatabaseHelper.CompareResults(sqlDataReader,types,totalColumnsInSelect));
107+
Assert.Contains("Failed to decrypt column",error.Message);
108+
}
109+
}
110+
111+
using(SqlConnectionsqlConnection=newSqlConnection(poolEnabledConnString))
112+
{
113+
sqlConnection.Open();
114+
115+
usingSqlCommandsqlCommand=newSqlCommand(string.Format(selectQuery,tableName),
116+
sqlConnection,null,SqlCommandColumnEncryptionSetting.Enabled);
117+
usingSqlDataReadersqlDataReader=sqlCommand.ExecuteReader();
118+
119+
Assert.True(sqlDataReader.HasRows,"FAILED: Select statement did not return any rows.");
120+
121+
while(sqlDataReader.Read())
122+
{
123+
DatabaseHelper.CompareResults(sqlDataReader,types,totalColumnsInSelect);
124+
}
125+
}
126+
}
127+
128+
129+
publicvoidDispose()
130+
{
131+
foreach(stringconnStrAEinDataTestUtility.AEConnStringsSetup)
132+
{
133+
using(SqlConnectionsqlConnection=newSqlConnection(connStrAE))
134+
{
135+
sqlConnection.Open();
136+
Table.DeleteData(fixture.ColumnDecryptErrorTestTable.Name,sqlConnection);
137+
}
138+
}
139+
}
140+
141+
privatesealedclassDummyKeyStoreProvider:SqlColumnEncryptionKeyStoreProvider
142+
{
143+
publicoverridebyte[]DecryptColumnEncryptionKey(stringmasterKeyPath,stringencryptionAlgorithm,byte[]encryptedColumnEncryptionKey)
144+
{
145+
// Must be 32 to match the key length expected for the 'AEAD_AES_256_CBC_HMAC_SHA256' algorithm
146+
returnnewbyte[32];
147+
}
148+
149+
publicoverridebyte[]EncryptColumnEncryptionKey(stringmasterKeyPath,stringencryptionAlgorithm,byte[]columnEncryptionKey)
150+
{
151+
returnnewbyte[32];
152+
}
153+
}
154+
}
155+
156+
publicclassTestQueries:IEnumerable<object[]>
157+
{
158+
publicIEnumerator<object[]>GetEnumerator()
159+
{
160+
foreach(stringconnStrAEinDataTestUtility.AEConnStrings)
161+
{
162+
yieldreturnnewobject[]{connStrAE,@"select CustomerId, FirstName, LastName from [{0}] ",3,newstring[]{@"int",@"string",@"string"}};
163+
yieldreturnnewobject[]{connStrAE,@"select CustomerId, FirstName from [{0}] ",2,newstring[]{@"int",@"string"}};
164+
yieldreturnnewobject[]{connStrAE,@"select LastName from [{0}] ",1,newstring[]{@"string"}};
165+
}
166+
}
167+
IEnumeratorIEnumerable.GetEnumerator()=>GetEnumerator();
168+
}
169+
}
170+

‎src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class SQLSetupStrategy : IDisposable
2121
publicTableApiTestTable{get;privateset;}
2222
publicTableBulkCopyAEErrorMessageTestTable{get;privateset;}
2323
publicTableBulkCopyAETestTable{get;privateset;}
24+
publicTableColumnDecryptErrorTestTable{get;privateset;}
2425
publicTableSqlParameterPropertiesTable{get;privateset;}
2526
publicTableDateOnlyTestTable{get;privateset;}
2627
publicTableEnd2EndSmokeTable{get;privateset;}
@@ -127,6 +128,9 @@ protected List<Table> CreateTables(IList<ColumnEncryptionKey> columnEncryptionKe
127128
BulkCopyAETestTable=newBulkCopyAETestTable(GenerateUniqueName("BulkCopyAETestTable"),columnEncryptionKeys[0],columnEncryptionKeys[1]);
128129
tables.Add(BulkCopyAETestTable);
129130

131+
ColumnDecryptErrorTestTable=newColumnDecryptErrorTestTable(GenerateUniqueName("ColumnDecryptErrorTestTable"),columnEncryptionKeys[0],columnEncryptionKeys[1]);
132+
tables.Add(ColumnDecryptErrorTestTable);
133+
130134
SqlParameterPropertiesTable=newSqlParameterPropertiesTable(GenerateUniqueName("SqlParameterPropertiesTable"));
131135
tables.Add(SqlParameterPropertiesTable);
132136

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
namespaceMicrosoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup
6+
{
7+
publicclassColumnDecryptErrorTestTable:Table
8+
{
9+
privateconststringColumnEncryptionAlgorithmName=@"AEAD_AES_256_CBC_HMAC_SHA_256";
10+
publicColumnEncryptionKeycolumnEncryptionKey1;
11+
publicColumnEncryptionKeycolumnEncryptionKey2;
12+
privatebooluseDeterministicEncryption;
13+
14+
publicColumnDecryptErrorTestTable(stringtableName,ColumnEncryptionKeycolumnEncryptionKey1,ColumnEncryptionKeycolumnEncryptionKey2,booluseDeterministicEncryption=false):base(tableName)
15+
{
16+
this.columnEncryptionKey1=columnEncryptionKey1;
17+
this.columnEncryptionKey2=columnEncryptionKey2;
18+
this.useDeterministicEncryption=useDeterministicEncryption;
19+
}
20+
21+
publicoverridevoidCreate(SqlConnectionsqlConnection)
22+
{
23+
stringencryptionType=useDeterministicEncryption?"DETERMINISTIC":DataTestUtility.EnclaveEnabled?"RANDOMIZED":"DETERMINISTIC";
24+
stringsql=
25+
$@"CREATE TABLE [dbo].[{Name}]
26+
(
27+
[CustomerId] [int] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey1.Name}], ENCRYPTION_TYPE ={encryptionType}, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
28+
[FirstName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey2.Name}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
29+
[LastName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey2.Name}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}')
30+
)";
31+
32+
using(SqlCommandcommand=sqlConnection.CreateCommand())
33+
{
34+
command.CommandText=sql;
35+
command.ExecuteNonQuery();
36+
}
37+
}
38+
39+
}
40+
}

‎src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<CompileInclude="AlwaysEncrypted\TestFixtures\Setup\BulkCopyAETestTable.cs" />
5858
<CompileInclude="AlwaysEncrypted\TestFixtures\Setup\BulkCopyAEErrorMessageTestTable.cs" />
5959
<CompileInclude="AlwaysEncrypted\TestFixtures\Setup\BulkCopyTruncationTables.cs" />
60+
<CompileInclude="AlwaysEncrypted\TestFixtures\Setup\ColumnDecryptErrorTestTable.cs" />
6061
<CompileInclude="AlwaysEncrypted\TestFixtures\Setup\DateOnlyTestTable.cs" />
6162
<CompileInclude="AlwaysEncrypted\TestFixtures\Setup\SqlNullValuesTable.cs" />
6263
<CompileInclude="AlwaysEncrypted\TestFixtures\Setup\SqlParameterPropertiesTable.cs" />
@@ -73,6 +74,7 @@
7374
</ItemGroup>
7475
<ItemGroupCondition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0')) AND ('$(TestSet)' == '' OR '$(TestSet)' == 'AE')">
7576
<CompileInclude="AlwaysEncrypted\DateOnlyReadTests.cs" />
77+
<CompileInclude="AlwaysEncrypted\ColumnDecryptErrorTests.cs" />
7678
</ItemGroup>
7779
<ItemGroupCondition="'$(TestSet)' == '' OR '$(TestSet)' == '1'">
7880
<CompileInclude="SQL\AsyncTest\AsyncTimeoutTest.cs" />

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp