mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
Merge pull request #1054 from raulgarciamsft/users/raulga/ICryptoTransformLambda
2n part of ICryptoTransform.
This commit is contained in:
36
csharp/ql/src/Likely Bugs/ICryptoTransform.qll
Normal file
36
csharp/ql/src/Likely Bugs/ICryptoTransform.qll
Normal file
@@ -0,0 +1,36 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.dataflow.DataFlow
|
||||
|
||||
import csharp
|
||||
class ImplementsICryptoTransform extends Class {
|
||||
ImplementsICryptoTransform() {
|
||||
this.getABaseType*().hasQualifiedName("System.Security.Cryptography", "ICryptoTransform")
|
||||
}
|
||||
}
|
||||
|
||||
predicate usesICryptoTransformType( ValueOrRefType t ) {
|
||||
exists( ImplementsICryptoTransform ict |
|
||||
ict = t
|
||||
or usesICryptoTransformType( t.getAChild() )
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasICryptoTransformMember( Class c) {
|
||||
c.getAField().getType() instanceof UsesICryptoTransform
|
||||
}
|
||||
|
||||
class UsesICryptoTransform extends Class {
|
||||
UsesICryptoTransform() {
|
||||
usesICryptoTransformType(this) or hasICryptoTransformMember(this)
|
||||
}
|
||||
}
|
||||
|
||||
class LambdaCapturingICryptoTransformSource extends DataFlow::Node {
|
||||
LambdaCapturingICryptoTransformSource() {
|
||||
exists( LambdaExpr l, LocalScopeVariable lsvar, UsesICryptoTransform ict |
|
||||
l = this.asExpr() |
|
||||
ict = lsvar.getType()
|
||||
and lsvar.getACapturingCallable() = l
|
||||
)
|
||||
}
|
||||
}
|
||||
28
csharp/ql/src/Likely Bugs/ParallelSink.qll
Normal file
28
csharp/ql/src/Likely Bugs/ParallelSink.qll
Normal file
@@ -0,0 +1,28 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.dataflow.DataFlow
|
||||
|
||||
abstract class ParallelSink extends DataFlow::Node {
|
||||
}
|
||||
|
||||
class LambdaParallelSink extends ParallelSink {
|
||||
LambdaParallelSink() {
|
||||
exists( Class c, Method m, MethodCall mc, Expr e |
|
||||
e = this.asExpr() |
|
||||
c.getABaseType*().hasQualifiedName("System.Threading.Tasks", "Parallel")
|
||||
and c.getAMethod() = m
|
||||
and m.getName() = "Invoke"
|
||||
and m.getACall() = mc
|
||||
and mc.getAnArgument() = e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadStartParallelSink extends ParallelSink {
|
||||
ThreadStartParallelSink() {
|
||||
exists( DelegateCreation dc, Expr e |
|
||||
e = this.asExpr() |
|
||||
dc.getArgument() = e
|
||||
and dc.getType().getName().matches("%Start")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
public static void RunThreadUnSafeICryptoTransformLambdaBad()
|
||||
{
|
||||
const int threadCount = 4;
|
||||
// This local variable for a hash object is going to be shared across multiple threads
|
||||
var sha1 = SHA1.Create();
|
||||
var b = new Barrier(threadCount);
|
||||
Action start = () => {
|
||||
b.SignalAndWait();
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
var pwd = Guid.NewGuid().ToString();
|
||||
var bytes = Encoding.UTF8.GetBytes(pwd);
|
||||
// This call may fail, or return incorrect results
|
||||
sha1.ComputeHash(bytes);
|
||||
}
|
||||
};
|
||||
var threads = Enumerable.Range(0, threadCount)
|
||||
.Select(_ => new ThreadStart(start))
|
||||
.Select(x => new Thread(x))
|
||||
.ToList();
|
||||
foreach (var t in threads) t.Start();
|
||||
foreach (var t in threads) t.Join();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
public static void RunThreadUnSafeICryptoTransformLambdaFixed()
|
||||
{
|
||||
const int threadCount = 4;
|
||||
var b = new Barrier(threadCount);
|
||||
Action start = () => {
|
||||
b.SignalAndWait();
|
||||
// The hash object is no longer shared
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
var sha1 = SHA1.Create();
|
||||
var pwd = Guid.NewGuid().ToString();
|
||||
var bytes = Encoding.UTF8.GetBytes(pwd);
|
||||
sha1.ComputeHash(bytes);
|
||||
}
|
||||
};
|
||||
var threads = Enumerable.Range(0, threadCount)
|
||||
.Select(_ => new ThreadStart(start))
|
||||
.Select(x => new Thread(x))
|
||||
.ToList();
|
||||
foreach (var t in threads) t.Start();
|
||||
foreach (var t in threads) t.Join();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<include src="ThreadUnsafeICryptoTransform.qhelp" />
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<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>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>This example demonstrates the dangers of using a shared <code>System.Security.Cryptography.ICryptoTransform</code> in a way that generates incorrect results or may raise an exception.</p>
|
||||
<sample src="ThreadUnSafeICryptoTransformLambdaBad.cs" />
|
||||
|
||||
<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>
|
||||
<sample src="ThreadUnSafeICryptoTransformLambdaGood.cs" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Microsoft documentation, <a href="https://docs.microsoft.com/en-us/dotnet/api/system.threadstaticattribute?view=netframework-4.7.2">ThreadStaticAttribute Class</a>.
|
||||
</li>
|
||||
<li>
|
||||
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>.
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @name Potential usage of an object implementing ICryptoTransform class in a way that would be unsafe for concurrent threads.
|
||||
* @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,
|
||||
* and used in what seems to be a thread initialization method.
|
||||
* Using an instance of this class in concurrent threads is dangerous as it may not only result in an error,
|
||||
* but under some circumstances may also result in incorrect results.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cs/thread-unsafe-icryptotransform-captured-in-lambda
|
||||
* @tags concurrency
|
||||
* security
|
||||
* external/cwe/cwe-362
|
||||
*/
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.dataflow.DataFlow
|
||||
import ParallelSink
|
||||
import ICryptoTransform
|
||||
|
||||
class NotThreadSafeCryptoUsageIntoParallelInvokeConfig extends TaintTracking::Configuration {
|
||||
NotThreadSafeCryptoUsageIntoParallelInvokeConfig() { this = "NotThreadSafeCryptoUsageIntoParallelInvokeConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof LambdaCapturingICryptoTransformSource
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink instanceof ParallelSink
|
||||
}
|
||||
}
|
||||
|
||||
from Expr e, string m, LambdaExpr l, NotThreadSafeCryptoUsageIntoParallelInvokeConfig config
|
||||
where
|
||||
config.hasFlow(DataFlow::exprNode(l), DataFlow::exprNode(e))
|
||||
and m = "A $@ 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."
|
||||
select e, m, l, "lambda expression"
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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 /r:System.Threading.Tasks.Parallel.dll
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
class DirectUsagePositiveCase
|
||||
{
|
||||
public static void Run(int max)
|
||||
{
|
||||
const int threadCount = 4;
|
||||
|
||||
// This variable is used in multiple threads
|
||||
var sha1 = SHA1.Create();
|
||||
Action start = () => {
|
||||
for (int i = 0; i < max; i++)
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
sha1.ComputeHash(bytes);
|
||||
}
|
||||
};
|
||||
|
||||
// BUG expected
|
||||
var threads = Enumerable.Range(0, threadCount)
|
||||
.Select(_ => new ThreadStart(start))
|
||||
.Select(x => new Thread(x))
|
||||
.ToList();
|
||||
foreach (var t in threads) t.Start();
|
||||
foreach (var t in threads) t.Join();
|
||||
}
|
||||
}
|
||||
|
||||
class DirectUsageNegativeCase
|
||||
{
|
||||
public static void Run(int max)
|
||||
{
|
||||
const int threadCount = 4;
|
||||
Action start = () => {
|
||||
for (int i = 0; i < max; i++)
|
||||
{
|
||||
var sha1 = SHA1.Create();
|
||||
var bytes = new byte[4];
|
||||
sha1.ComputeHash(bytes);
|
||||
}
|
||||
};
|
||||
var threads = Enumerable.Range(0, threadCount)
|
||||
.Select(_ => new ThreadStart(start))
|
||||
.Select(x => new Thread(x))
|
||||
.ToList();
|
||||
foreach (var t in threads) t.Start();
|
||||
foreach (var t in threads) t.Join();
|
||||
}
|
||||
}
|
||||
|
||||
public class Nest01
|
||||
{
|
||||
private readonly SHA256 _sha;
|
||||
|
||||
public Nest01()
|
||||
{
|
||||
_sha = SHA256.Create();
|
||||
}
|
||||
|
||||
public byte[] ComputeHash(byte[] bytes)
|
||||
{
|
||||
return _sha.ComputeHash(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
class IndirectUsagePositiveCase
|
||||
{
|
||||
public static void Run(int max)
|
||||
{
|
||||
const int threadCount = 4;
|
||||
// This variable is used in multiple threads
|
||||
var sha1 = new Nest01();
|
||||
|
||||
// BUG expected
|
||||
Action start = () => {
|
||||
for (int i = 0; i < max; i++)
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
sha1.ComputeHash(bytes);
|
||||
}
|
||||
};
|
||||
var threads = Enumerable.Range(0, threadCount)
|
||||
.Select(_ => new ThreadStart(start))
|
||||
.Select(x => new Thread(x))
|
||||
.ToList();
|
||||
foreach (var t in threads) t.Start();
|
||||
foreach (var t in threads) t.Join();
|
||||
}
|
||||
}
|
||||
|
||||
class IndirectUsageNegativeCase
|
||||
{
|
||||
public static void Run(int max)
|
||||
{
|
||||
const int threadCount = 4;
|
||||
Action start = () => {
|
||||
for (int i = 0; i < max; i++)
|
||||
{
|
||||
var sha1 = new Nest01();
|
||||
var bytes = new byte[4];
|
||||
sha1.ComputeHash(bytes);
|
||||
}
|
||||
};
|
||||
var threads = Enumerable.Range(0, threadCount)
|
||||
.Select(_ => new ThreadStart(start))
|
||||
.Select(x => new Thread(x))
|
||||
.ToList();
|
||||
foreach (var t in threads) t.Start();
|
||||
foreach (var t in threads) t.Join();
|
||||
}
|
||||
}
|
||||
|
||||
class LambdaNotStart
|
||||
{
|
||||
public static void Run()
|
||||
{
|
||||
var sha1 = SHA1.Create();
|
||||
|
||||
Func<string> myFunc = () =>
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
return Convert.ToBase64String(sha1.ComputeHash(bytes));
|
||||
};
|
||||
|
||||
var d = myFunc.DynamicInvoke();
|
||||
}
|
||||
}
|
||||
|
||||
class ParallelInvoke
|
||||
{
|
||||
public static void Run()
|
||||
{
|
||||
var sha1 = SHA1.Create();
|
||||
|
||||
try
|
||||
{
|
||||
Parallel.Invoke(() =>
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
Convert.ToBase64String(sha1.ComputeHash(bytes));
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
Convert.ToBase64String(sha1.ComputeHash(bytes));
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
Console.WriteLine("An action has thrown an exception. THIS WAS UNEXPECTED.\n{0}", e.InnerException.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
| ThreadUnsafeICryptoTransformLambda.cs:27:62:27:66 | access to local variable start | A $@ 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. | ThreadUnsafeICryptoTransformLambda.cs:17:24:23:9 | (...) => ... | lambda expression |
|
||||
| ThreadUnsafeICryptoTransformLambda.cs:89:62:89:66 | access to local variable start | A $@ 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. | ThreadUnsafeICryptoTransformLambda.cs:81:24:87:9 | (...) => ... | lambda expression |
|
||||
| ThreadUnsafeICryptoTransformLambda.cs:143:29:147:17 | (...) => ... | A $@ 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. | ThreadUnsafeICryptoTransformLambda.cs:143:29:147:17 | (...) => ... | lambda expression |
|
||||
| ThreadUnsafeICryptoTransformLambda.cs:148:17:152:17 | (...) => ... | A $@ 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. | ThreadUnsafeICryptoTransformLambda.cs:148:17:152:17 | (...) => ... | lambda expression |
|
||||
@@ -0,0 +1 @@
|
||||
Likely Bugs/ThreadUnsafeICryptoTransformLambda.ql
|
||||
Reference in New Issue
Block a user