Skip to content

Commit 2e0c337

Browse files
2n part of ICryptoTransform.
Detecting potential unsafe usage (object shared across multiple threads) on variables captured by Lambda
1 parent 54493eb commit 2e0c337

8 files changed

Lines changed: 285 additions & 0 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
/.vs/ql/v15/Browse.VC.db
1414
/.vs/ProjectSettings.json
1515

16+
/.vs/ql_ICryptoTransformLmbda/v15/Browse.VC.opendb
17+
/.vs/ql_ICryptoTransformLmbda/v15/Browse.VC.db
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
public static void RunThreadUnSafeICryptoTransformLambdaBad()
2+
{
3+
const int threadCount = 4;
4+
// This local variable for a hash object is going to be shared across multiple threads
5+
var sha1 = SHA1.Create();
6+
var b = new Barrier(threadCount);
7+
Action start = () => {
8+
b.SignalAndWait();
9+
for (int i = 0; i < 1000; i++)
10+
{
11+
var pwd = Guid.NewGuid().ToString();
12+
var bytes = Encoding.UTF8.GetBytes(pwd);
13+
// This call may fail, or return incorrect results
14+
sha1.ComputeHash(bytes);
15+
}
16+
};
17+
var threads = Enumerable.Range(0, threadCount)
18+
.Select(_ => new ThreadStart(start))
19+
.Select(x => new Thread(x))
20+
.ToList();
21+
foreach (var t in threads) t.Start();
22+
foreach (var t in threads) t.Join();
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
public static void RunThreadUnSafeICryptoTransformLambdaFixed()
2+
{
3+
const int threadCount = 4;
4+
var b = new Barrier(threadCount);
5+
Action start = () => {
6+
b.SignalAndWait();
7+
for (int i = 0; i < 1000; i++)
8+
{
9+
// The hash object is no longer shared
10+
var sha1 = SHA1.Create();
11+
var pwd = Guid.NewGuid().ToString();
12+
var bytes = Encoding.UTF8.GetBytes(pwd);
13+
sha1.ComputeHash(bytes);
14+
}
15+
};
16+
var threads = Enumerable.Range(0, threadCount)
17+
.Select(_ => new ThreadStart(start))
18+
.Select(x => new Thread(x))
19+
.ToList();
20+
foreach (var t in threads) t.Start();
21+
foreach (var t in threads) t.Join();
22+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<include src="ThreadUnsafeICryptoTransform.qhelp" />
7+
8+
</overview>
9+
<recommendation>
10+
<p>Create new instances of the object that implements or has a field of type <code>System.Security.Cryptography.ICryptoTransform</code> to avoid sharing it accross multiple threads.</p>
11+
12+
</recommendation>
13+
<example>
14+
<p>This example demonstrates the dangers of using a static <code>System.Security.Cryptography.ICryptoTransform</code> in a way that generates incorrect results or may raise an exception.</p>
15+
<sample src="ThreadUnSafeICryptoTransformLambdaBad.cs" />
16+
17+
<p>A simple fix is to change the local variable <code>sha1</code> being captured by the lambda to be a local variable within the lambda.</p>
18+
<sample src="ThreadUnSafeICryptoTransformLambdaGood.cs" />
19+
</example>
20+
21+
<references>
22+
<li>
23+
Microsoft documentation, <a href="https://docs.microsoft.com/en-us/dotnet/api/system.threadstaticattribute?view=netframework-4.7.2">ThreadStaticAttribute Class</a>.
24+
</li>
25+
<li>
26+
Stack Overflow, <a href="https://stackoverflow.com/questions/26592596/why-does-sha1-computehash-fail-under-high-load-with-many-threads">Why does SHA1.ComputeHash fail under high load with many threads?</a>.
27+
</li>
28+
</references>
29+
30+
</qhelp>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* @name Potential usage of a n object implementing ICryptoTransform class in a way that would be unsafe for concurrent threads.
3+
* @description An instance of a class that either implements or has a field of type System.Security.Cryptography.ICryptoTransform is being captured by a lambda,
4+
* and used in what seems to be a thread initialization method.
5+
* Using this an instance of this class in concurrent threads is dangerous as it may not only result in an error,
6+
* but under some circumstances may also result in incorrect results.
7+
* @kind problem
8+
* @problem.severity warning
9+
* @precision medium
10+
* @id cs/thread-unsafe-icryptotransform-captured-in-lambda
11+
* @tags concurrency
12+
* security
13+
* external/cwe/cwe-362
14+
*/
15+
16+
import csharp
17+
import semmle.code.csharp.dataflow.DataFlow
18+
19+
class ICryptoTransform extends Class {
20+
ICryptoTransform() {
21+
this.getABaseType*().hasQualifiedName("System.Security.Cryptography", "ICryptoTransform")
22+
}
23+
}
24+
25+
predicate usesICryptoTransformType( Type t ) {
26+
exists( ICryptoTransform ict |
27+
ict = t
28+
or usesICryptoTransformType( t.getAChild() )
29+
or usesICryptoTransformType( t.(Class).getAMember() )
30+
)
31+
}
32+
33+
predicate hasICryptoTransformMember( Class c) {
34+
exists( Field f |
35+
f = c.getAMember()
36+
and (
37+
exists( ICryptoTransform ict | ict = f.getType() )
38+
or hasICryptoTransformMember(f.getType())
39+
or usesICryptoTransformType(f.getType())
40+
)
41+
)
42+
}
43+
44+
class UsesICryptoTransform extends Class {
45+
UsesICryptoTransform() {
46+
usesICryptoTransformType(this) or hasICryptoTransformMember(this)
47+
}
48+
}
49+
50+
class NotThreadSafeCryptoUsageIntoStartingCallingConfig extends TaintTracking::Configuration {
51+
NotThreadSafeCryptoUsageIntoStartingCallingConfig() { this = "NotThreadSafeCryptoUsageIntoStartingCallingConfig" }
52+
53+
override predicate isSource(DataFlow::Node source) {
54+
exists( LambdaExpr l, LocalScopeVariable lsvar, UsesICryptoTransform ict |
55+
l = source.asExpr() |
56+
ict = lsvar.getType()
57+
and lsvar.getACapturingCallable() = l
58+
)
59+
}
60+
61+
override predicate isSink(DataFlow::Node sink) {
62+
exists( DelegateCreation dc, Expr e |
63+
e = sink.asExpr() |
64+
dc.getArgument() = e
65+
and dc.getType().getName().matches("%Start")
66+
)
67+
}
68+
}
69+
70+
from NotThreadSafeCryptoUsageIntoStartingCallingConfig config, LambdaExpr l, Expr e
71+
where config.hasFlow(DataFlow::exprNode(l), DataFlow::exprNode(e))
72+
select e, "A Lambda expression at " + l.getLocation() + " seems to be used to start a new thread is capturing a local variable that either implements 'System.Security.Cryptography.ICryptoTransform' or has a field of this type."
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// semmle-extractor-options: /r:System.Security.Cryptography.Csp.dll /r:System.Security.Cryptography.Algorithms.dll /r:System.Security.Cryptography.Primitives.dll /r:System.Threading.Tasks.dll /r:System.Threading.Thread.dll /r:System.Linq.dll /r:System.Collections.dll
2+
using System;
3+
using System.Linq;
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using System.Security.Cryptography;
8+
9+
class DirectUsagePositiveCase
10+
{
11+
public static void Run(int max)
12+
{
13+
const int threadCount = 4;
14+
15+
// This variable is used in multiple threads
16+
var sha1 = SHA1.Create();
17+
Action start = () => {
18+
for (int i = 0; i < max; i++)
19+
{
20+
var bytes = new byte[4];
21+
sha1.ComputeHash(bytes);
22+
}
23+
};
24+
25+
// BUG expected
26+
var threads = Enumerable.Range(0, threadCount)
27+
.Select(_ => new ThreadStart(start))
28+
.Select(x => new Thread(x))
29+
.ToList();
30+
foreach (var t in threads) t.Start();
31+
foreach (var t in threads) t.Join();
32+
}
33+
}
34+
35+
class DirectUsageNegativeCase
36+
{
37+
public static void Run(int max)
38+
{
39+
const int threadCount = 4;
40+
Action start = () => {
41+
for (int i = 0; i < max; i++)
42+
{
43+
var sha1 = SHA1.Create();
44+
var bytes = new byte[4];
45+
sha1.ComputeHash(bytes);
46+
}
47+
};
48+
var threads = Enumerable.Range(0, threadCount)
49+
.Select(_ => new ThreadStart(start))
50+
.Select(x => new Thread(x))
51+
.ToList();
52+
foreach (var t in threads) t.Start();
53+
foreach (var t in threads) t.Join();
54+
}
55+
}
56+
57+
public class Nest01
58+
{
59+
private readonly SHA256 _sha;
60+
61+
public Nest01()
62+
{
63+
_sha = SHA256.Create();
64+
}
65+
66+
public byte[] ComputeHash(byte[] bytes)
67+
{
68+
return _sha.ComputeHash(bytes);
69+
}
70+
}
71+
72+
class IndirectUsagePositiveCase
73+
{
74+
public static void Run(int max)
75+
{
76+
const int threadCount = 4;
77+
// This variable is used in multiple threads
78+
var sha1 = new Nest01();
79+
80+
// BUG expected
81+
Action start = () => {
82+
for (int i = 0; i < max; i++)
83+
{
84+
var bytes = new byte[4];
85+
sha1.ComputeHash(bytes);
86+
}
87+
};
88+
var threads = Enumerable.Range(0, threadCount)
89+
.Select(_ => new ThreadStart(start))
90+
.Select(x => new Thread(x))
91+
.ToList();
92+
foreach (var t in threads) t.Start();
93+
foreach (var t in threads) t.Join();
94+
}
95+
}
96+
97+
class indirectUsageNegativeCase
98+
{
99+
public static void Run(int max)
100+
{
101+
const int threadCount = 4;
102+
Action start = () => {
103+
for (int i = 0; i < max; i++)
104+
{
105+
var sha1 = new Nest01();
106+
var bytes = new byte[4];
107+
sha1.ComputeHash(bytes);
108+
}
109+
};
110+
var threads = Enumerable.Range(0, threadCount)
111+
.Select(_ => new ThreadStart(start))
112+
.Select(x => new Thread(x))
113+
.ToList();
114+
foreach (var t in threads) t.Start();
115+
foreach (var t in threads) t.Join();
116+
}
117+
}
118+
119+
class LambdaNotStart
120+
{
121+
public static void Run()
122+
{
123+
var sha1 = SHA1.Create();
124+
125+
Func<string> myFunc = () =>
126+
{
127+
var bytes = new byte[4];
128+
return Convert.ToBase64String(sha1.ComputeHash(bytes));
129+
};
130+
131+
var d = myFunc.DynamicInvoke();
132+
}
133+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| ThreadUnsafeICryptoTransformLambda.cs:27:62:27:66 | access to local variable start | A Lambda expression at ThreadUnsafeICryptoTransformLambda.cs:17:24:23:9 seems to be used to start a new thread is capturing a local variable that either implements 'System.Security.Cryptography.ICryptoTransform' or has a field of this type. |
2+
| ThreadUnsafeICryptoTransformLambda.cs:89:62:89:66 | access to local variable start | A Lambda expression at ThreadUnsafeICryptoTransformLambda.cs:81:24:87:9 seems to be used to start a new thread is capturing a local variable that either implements 'System.Security.Cryptography.ICryptoTransform' or has a field of this type. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Likely Bugs/ThreadUnsafeICryptoTransformLambda.ql

0 commit comments

Comments
 (0)