Skip to content

Commit 7e1120c

Browse files
authored
Select secure version of System.Text.RegularExpressions (#375)
* Select secure version of System.Text.RegularExpressions * Add npm version workaround because latest Azure DevOps build agents have version with bug in unzip code * Try to handle failure to kill func host more gracefully * Add retries on access denied failures
1 parent 1293e35 commit 7e1120c

5 files changed

Lines changed: 110 additions & 31 deletions

File tree

Solutions/Corvus.Testing.AzureFunctions.SpecFlow.Demo/packages.lock.json

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
},
3535
"Castle.Core": {
3636
"type": "Transitive",
37-
"resolved": "5.1.0",
38-
"contentHash": "31UJpTHOiWq95CDOHazE3Ub/hE/PydNWsJMwnEVTqFFP4WhAugwpaVGxzOxKgNeSUUeqS2W6lxV+q7u1pAOfXg==",
37+
"resolved": "5.1.1",
38+
"contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==",
3939
"dependencies": {
4040
"System.Diagnostics.EventLog": "6.0.0"
4141
}
@@ -239,8 +239,8 @@
239239
},
240240
"Microsoft.NETCore.Targets": {
241241
"type": "Transitive",
242-
"resolved": "1.1.0",
243-
"contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
242+
"resolved": "1.1.3",
243+
"contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ=="
244244
},
245245
"Microsoft.SourceLink.Common": {
246246
"type": "Transitive",
@@ -295,10 +295,10 @@
295295
},
296296
"Moq": {
297297
"type": "Transitive",
298-
"resolved": "4.18.3",
299-
"contentHash": "nmV2lludVOFmVi+Vtq9twX1/SDiEVyYDURzxW39gUBqjyoXmdyNwJSeOfSCJoJTXDXBVfFNfEljB5UWGj/cKnQ==",
298+
"resolved": "4.18.4",
299+
"contentHash": "IOo+W51+7Afnb0noltJrKxPBSfsgMzTKCw+Re5AMx8l/vBbAbMDOynLik4+lBYIWDJSO0uV7Zdqt7cNb6RZZ+A==",
300300
"dependencies": {
301-
"Castle.Core": "5.1.0"
301+
"Castle.Core": "5.1.1"
302302
}
303303
},
304304
"NETStandard.Library": {
@@ -1011,11 +1011,11 @@
10111011
},
10121012
"System.Runtime": {
10131013
"type": "Transitive",
1014-
"resolved": "4.3.0",
1015-
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
1014+
"resolved": "4.3.1",
1015+
"contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==",
10161016
"dependencies": {
1017-
"Microsoft.NETCore.Platforms": "1.1.0",
1018-
"Microsoft.NETCore.Targets": "1.1.0"
1017+
"Microsoft.NETCore.Platforms": "1.1.1",
1018+
"Microsoft.NETCore.Targets": "1.1.3"
10191019
}
10201020
},
10211021
"System.Runtime.CompilerServices.Unsafe": {
@@ -1302,10 +1302,10 @@
13021302
},
13031303
"System.Text.RegularExpressions": {
13041304
"type": "Transitive",
1305-
"resolved": "4.3.0",
1306-
"contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==",
1305+
"resolved": "4.3.1",
1306+
"contentHash": "N0kNRrWe4+nXOWlpLT4LAY5brb8caNFlUuIRpraCVMDLYutKkol1aV079rQjLuSxKMJT2SpBQsYX9xbcTMmzwg==",
13071307
"dependencies": {
1308-
"System.Runtime": "4.3.0"
1308+
"System.Runtime": "4.3.1"
13091309
}
13101310
},
13111311
"System.Threading": {
@@ -1448,8 +1448,9 @@
14481448
"dependencies": {
14491449
"Corvus.Testing.AzureFunctions.SpecFlow": "[1.0.0, )",
14501450
"Microsoft.NET.Test.Sdk": "[17.4.0, )",
1451-
"Moq": "[4.18.3, )",
1451+
"Moq": "[4.18.4, )",
14521452
"SpecFlow.NUnit.Runners": "[3.9.74, )",
1453+
"System.Text.RegularExpressions": "[4.3.1, )",
14531454
"coverlet.msbuild": "[3.2.0, )"
14541455
}
14551456
},

Solutions/Corvus.Testing.AzureFunctions.SpecFlow.NUnit/Corvus.Testing.AzureFunctions.SpecFlow.NUnit.csproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,30 @@
3333
<!-- ensure the 'build' assets are not private -->
3434
<PrivateAssets>contentfiles; analyzers</PrivateAssets>
3535
</PackageReference>
36+
37+
<!--
38+
We have an indirect dependency on System.Text.RegularExpressions, and by default applications
39+
will end up with the 4.3.0 version, for which a security advisory exists. The issue is around
40+
a potential denial of service attack, and since this only affects test projects (not production
41+
code) this doesn't appear to be a realistic security flaw. However, some code analyzers will
42+
flag this, so we are upgrading the reference to prevent warnings.
43+
44+
The dependency comes from NUnit:
45+
46+
Corvus.Testing.AzureFunctions.SpecFlow.NUnit
47+
SpecFlow.NUnit.Runners 3.9.74
48+
NUnit3TestAdapter 3.17.0
49+
System.Xml.XmlDocument
50+
System.Xml.ReaderWriter
51+
System.Text.RegularExpressions
52+
53+
It's possible that this problem will go away with NUnit3TestAdapter 4, because that doesn't
54+
appear to have this dependency. Currently, SpecFlow.NUnit.Runners depends on the older
55+
version (and as of 2023/04/20, previews for SpecFlow.NUnit.Runners v4 continue to do that)
56+
but if at some point we are able to move to NUnit3TestAdapter 4, we should look at
57+
removing this dependency, because we shouldn't need it any more.
58+
-->
59+
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
3660
</ItemGroup>
3761

3862
<ItemGroup>

Solutions/Corvus.Testing.AzureFunctions/Corvus/Testing/AzureFunctions/FunctionsController.cs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ namespace Corvus.Testing.AzureFunctions
66
{
77
using System;
88
using System.Collections.Generic;
9+
using System.ComponentModel;
910
using System.Diagnostics;
1011
using System.IO;
1112
using System.Linq;
1213
using System.Management;
1314
using System.Net.NetworkInformation;
15+
using System.Threading;
1416
using System.Threading.Tasks;
1517

1618
using Corvus.Testing.AzureFunctions.Internal;
@@ -33,6 +35,9 @@ namespace Corvus.Testing.AzureFunctions
3335
public sealed class FunctionsController
3436
{
3537
private const long StartupTimeout = 60;
38+
#pragma warning disable SA1310 // Field names should not contain underscore - this is the proper name for this symbol
39+
private const int E_ACCESSDENIED = unchecked((int)0x80070005);
40+
#pragma warning restore SA1310
3641

3742
private readonly List<FunctionOutputBufferHandler> output = new();
3843
private readonly object sync = new();
@@ -136,6 +141,18 @@ public IEnumerable<IProcessOutput> GetFunctionsOutput()
136141
/// <summary>
137142
/// Tear down the running functions instances, forcibly killing the process where required.
138143
/// </summary>
144+
/// <remarks>
145+
/// <para>
146+
/// This is a best-effort approach, because in some environments (e.g., on some build
147+
/// agents) we get Access Denied errors when trying to kill the host forcibly. We will
148+
/// retry up to three times when that happens because sometimes race conditions can cause
149+
/// spurious access denied failures. But we will eventually give up. We don't throw an
150+
/// exception in this case because there may be situations in which tests can proceed,
151+
/// but in most cases this is likely to result in the next test failing, because it will
152+
/// try to run the functions host again with the same port number settings, and that will
153+
/// fail because the port is already in use by the process we were unable to terminate.
154+
/// </para>
155+
/// </remarks>
139156
public void TeardownFunctions()
140157
{
141158
var aggregate = new List<Exception>();
@@ -145,7 +162,10 @@ public void TeardownFunctions()
145162
{
146163
KillProcessAndChildren(outputHandler.Process.Id);
147164

148-
outputHandler.Process.WaitForExit();
165+
if (!outputHandler.Process.WaitForExit(10000))
166+
{
167+
Console.Error.WriteLine("Unable to shut down functions host");
168+
}
149169
}
150170
catch (Exception e)
151171
{
@@ -178,7 +198,7 @@ private static async Task<string> GetToolPath()
178198

179199
string toolPath = Path.Combine(
180200
toolsFolder,
181-
"func");
201+
Environment.OSVersion.Platform == PlatformID.Win32NT ? "func.exe" : "func");
182202

183203
Console.WriteLine($"\tToolsPath: {toolPath}");
184204
return toolPath;
@@ -205,15 +225,42 @@ private static void KillProcessAndChildren(int pid)
205225
}
206226
}
207227

208-
try
209-
{
210-
var proc = Process.GetProcessById(pid);
211-
proc.Kill();
212-
}
213-
catch (ArgumentException)
228+
bool failedDueToAccessDenied;
229+
int accessDenyRetryCount = 0;
230+
const int MaxAccessDeniedRetries = 3;
231+
232+
do
214233
{
215-
// Process already exited.
234+
failedDueToAccessDenied = false;
235+
236+
Process proc;
237+
try
238+
{
239+
proc = Process.GetProcessById(pid);
240+
}
241+
catch (ArgumentException)
242+
{
243+
// Process already exited.
244+
return;
245+
}
246+
247+
try
248+
{
249+
proc.Kill();
250+
}
251+
catch (Win32Exception x)
252+
when (x.ErrorCode == E_ACCESSDENIED)
253+
{
254+
Console.Error.WriteLine($"Access denied when trying to kill process id {pid}, '{proc.ProcessName}'");
255+
failedDueToAccessDenied = true;
256+
}
257+
258+
if (failedDueToAccessDenied && accessDenyRetryCount < MaxAccessDeniedRetries)
259+
{
260+
Thread.Sleep(100);
261+
}
216262
}
263+
while (failedDueToAccessDenied && accessDenyRetryCount++ < MaxAccessDeniedRetries);
217264
}
218265

219266
/// <summary>

Solutions/Corvus.Testing.SpecFlow.Specs/packages.lock.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
},
3535
"Castle.Core": {
3636
"type": "Transitive",
37-
"resolved": "5.1.0",
38-
"contentHash": "31UJpTHOiWq95CDOHazE3Ub/hE/PydNWsJMwnEVTqFFP4WhAugwpaVGxzOxKgNeSUUeqS2W6lxV+q7u1pAOfXg==",
37+
"resolved": "5.1.1",
38+
"contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==",
3939
"dependencies": {
4040
"System.Diagnostics.EventLog": "6.0.0"
4141
}
@@ -212,10 +212,10 @@
212212
},
213213
"Moq": {
214214
"type": "Transitive",
215-
"resolved": "4.18.3",
216-
"contentHash": "nmV2lludVOFmVi+Vtq9twX1/SDiEVyYDURzxW39gUBqjyoXmdyNwJSeOfSCJoJTXDXBVfFNfEljB5UWGj/cKnQ==",
215+
"resolved": "4.18.4",
216+
"contentHash": "IOo+W51+7Afnb0noltJrKxPBSfsgMzTKCw+Re5AMx8l/vBbAbMDOynLik4+lBYIWDJSO0uV7Zdqt7cNb6RZZ+A==",
217217
"dependencies": {
218-
"Castle.Core": "5.1.0"
218+
"Castle.Core": "5.1.1"
219219
}
220220
},
221221
"NETStandard.Library": {
@@ -1346,7 +1346,7 @@
13461346
"dependencies": {
13471347
"Corvus.Testing.SpecFlow": "[1.0.0, )",
13481348
"Microsoft.NET.Test.Sdk": "[17.4.0, )",
1349-
"Moq": "[4.18.3, )",
1349+
"Moq": "[4.18.4, )",
13501350
"SpecFlow.NUnit.Runners": "[3.9.74, )",
13511351
"coverlet.msbuild": "[3.2.0, )"
13521352
}

azure-pipelines.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,17 @@ jobs:
2323
service_connection_github: $(Endjin_Service_Connection_GitHub)
2424
solution_to_build: $(Endjin_Solution_To_Build)
2525
postCustomEnvironmentVariables:
26+
- task: NodeTool@0
27+
displayName: 'Temporary downgrade of NPM to work around bug' # https://github.com/actions/runner-images/issues/7467#issuecomment-1518007292 - we can remove this once https://github.com/Azure/azure-functions-core-tools/pull/3338 drops, whenever azure-functions-core-tools >v4.0.5095 drops
28+
inputs:
29+
versionSpec: '18.15'
2630
- task: Npm@1
2731
displayName: 'Install Latest Azure Functions V4 Runtime'
2832
inputs:
2933
command: custom
3034
verbose: false
31-
customCommand: 'install -g azure-functions-core-tools@ --unsafe-perm true'
35+
customCommand: 'install -g azure-functions-core-tools@ --unsafe-perm true --verbose'
36+
- powershell: |
37+
gci -r C:\npm\prefix\node_modules\azure-functions-core-tools
38+
displayName: 'Show Azure Functions V4 Runtime folder'
3239
netSdkVersion: '6.x'

0 commit comments

Comments
 (0)