Skip to content

Commit 50c98f6

Browse files
committed
Merge branch 'tob/quantum-csharp' of github.com:trailofbits/codeql into tob/quantum-csharp
2 parents a7c5f7a + 46c037c commit 50c98f6

File tree

10 files changed

+269
-17
lines changed

10 files changed

+269
-17
lines changed

csharp/ql/lib/experimental/quantum/Language.qll

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
private import csharp as Language
2+
private import semmle.code.csharp.dataflow.DataFlow
23
private import codeql.quantum.experimental.Model
34

45
private class UnknownLocation extends Language::Location {
@@ -41,28 +42,142 @@ module CryptoInput implements InputSig<Language::Location> {
4142
// Instantiate the `CryptographyBase` module
4243
module Crypto = CryptographyBase<Language::Location, CryptoInput>;
4344

44-
module ArtifactFlowConfig implements Language::DataFlow::ConfigSig {
45-
predicate isSource(Language::DataFlow::Node source) {
45+
/**
46+
* An additional flow step in generic data-flow configurations.
47+
* Where a step is an edge between nodes `n1` and `n2`,
48+
* `this` = `n1` and `getOutput()` = `n2`.
49+
*
50+
* FOR INTERNAL MODELING USE ONLY.
51+
*/
52+
abstract class AdditionalFlowInputStep extends DataFlow::Node {
53+
abstract DataFlow::Node getOutput();
54+
55+
final DataFlow::Node getInput() { result = this }
56+
}
57+
58+
/**
59+
* Generic data source to node input configuration
60+
*/
61+
module GenericDataSourceFlowConfig implements DataFlow::ConfigSig {
62+
predicate isSource(DataFlow::Node source) {
63+
source = any(Crypto::GenericSourceInstance i).getOutputNode()
64+
}
65+
66+
predicate isSink(DataFlow::Node sink) {
67+
sink = any(Crypto::FlowAwareElement other).getInputNode()
68+
}
69+
70+
predicate isBarrierOut(DataFlow::Node node) {
71+
node = any(Crypto::FlowAwareElement element).getInputNode()
72+
}
73+
74+
predicate isBarrierIn(DataFlow::Node node) {
75+
node = any(Crypto::FlowAwareElement element).getOutputNode()
76+
}
77+
78+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
79+
node1.(AdditionalFlowInputStep).getOutput() = node2
80+
}
81+
}
82+
83+
module ArtifactFlowConfig implements DataFlow::ConfigSig {
84+
predicate isSource(DataFlow::Node source) {
4685
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
4786
}
4887

49-
predicate isSink(Language::DataFlow::Node sink) {
88+
predicate isSink(DataFlow::Node sink) {
5089
sink = any(Crypto::FlowAwareElement other).getInputNode()
5190
}
5291

53-
predicate isBarrierOut(Language::DataFlow::Node node) {
92+
predicate isBarrierOut(DataFlow::Node node) {
5493
node = any(Crypto::FlowAwareElement element).getInputNode()
5594
}
5695

57-
predicate isBarrierIn(Language::DataFlow::Node node) {
96+
predicate isBarrierIn(DataFlow::Node node) {
5897
node = any(Crypto::FlowAwareElement element).getOutputNode()
5998
}
6099

61-
predicate isAdditionalFlowStep(Language::DataFlow::Node node1, Language::DataFlow::Node node2) {
62-
none()
100+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
101+
node1.(AdditionalFlowInputStep).getOutput() = node2
102+
}
103+
}
104+
105+
module GenericDataSourceFlow = TaintTracking::Global<GenericDataSourceFlowConfig>;
106+
107+
module ArtifactFlow = DataFlow::Global<ArtifactFlowConfig>;
108+
109+
/**
110+
* A method access that returns random data or writes random data to an argument.
111+
*/
112+
abstract class RandomnessSource extends MethodCall {
113+
/**
114+
* Gets the expression representing the output of this source.
115+
*/
116+
abstract Expr getOutput();
117+
118+
/**
119+
* Gets the type of the source of randomness used by this call.
120+
*/
121+
Type getGenerator() { result = this.getQualifier().getType() }
122+
}
123+
124+
/**
125+
* A call to `System.Security.Cryptography.RandomNumberGenerator.GetBytes`.
126+
*/
127+
class SecureRandomnessSource extends RandomnessSource {
128+
SecureRandomnessSource() {
129+
this.getTarget()
130+
.hasFullyQualifiedName("System.Security.Cryptography", "RandomNumberGenerator", "GetBytes")
131+
}
132+
133+
override Expr getOutput() { result = this.getArgument(0) }
134+
}
135+
136+
/**
137+
* A call to `System.Random.NextBytes`.
138+
*/
139+
class InsecureRandomnessSource extends RandomnessSource {
140+
InsecureRandomnessSource() {
141+
this.getTarget().hasFullyQualifiedName("System", "Random", "NextBytes")
142+
}
143+
144+
override Expr getOutput() { result = this.getArgument(0) }
145+
}
146+
147+
/**
148+
* An instance of random number generation, modelled as the expression
149+
* tied to an output node (i.e., the RNG output)
150+
*/
151+
abstract class RandomnessInstance extends Crypto::RandomNumberGenerationInstance {
152+
override DataFlow::Node getOutputNode() { result.asExpr() = this }
153+
}
154+
155+
/**
156+
* An output instance from the system cryptographically secure RNG.
157+
*/
158+
class SecureRandomnessInstance extends RandomnessInstance {
159+
RandomnessSource source;
160+
161+
SecureRandomnessInstance() {
162+
source instanceof SecureRandomnessSource and
163+
this = source.getOutput()
63164
}
165+
166+
override string getGeneratorName() { result = source.getGenerator().getName() }
64167
}
65168

66-
module ArtifactFlow = Language::DataFlow::Global<ArtifactFlowConfig>;
169+
/**
170+
* An output instance from an insecure RNG.
171+
*/
172+
class InsecureRandomnessInstance extends RandomnessInstance {
173+
RandomnessSource source;
174+
175+
InsecureRandomnessInstance() {
176+
not source instanceof SecureRandomnessSource and
177+
this = source.getOutput()
178+
}
179+
180+
override string getGeneratorName() { result = source.getGenerator().getName() }
181+
}
67182

68183
import dotnet

csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
private import csharp
22
private import semmle.code.csharp.dataflow.DataFlow
3+
private import experimental.quantum.Language
34
private import OperationInstances
45
private import AlgorithmValueConsumers
56
private import Cryptography
@@ -51,15 +52,6 @@ module CreationToUseFlow<CreationCallSig Creation, UseCallSig Use> {
5152
sink.getNode().asExpr() = use.(QualifiableExpr).getQualifier() and
5253
CreationToUseFlow::flowPath(source, sink)
5354
}
54-
55-
// TODO: Remove this.
56-
Expr flowsTo(Expr expr) {
57-
exists(CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink |
58-
source.getNode().asExpr() = expr and
59-
sink.getNode().asExpr() = result and
60-
CreationToUseFlow::flowPath(source, sink)
61-
)
62-
}
6355
}
6456

6557
/**
@@ -271,3 +263,20 @@ module StreamFlow {
271263
StreamFlow::flowPath(source, sink)
272264
}
273265
}
266+
267+
/**
268+
* An additional flow step across property assignments used to track flow from
269+
* output artifacts to consumers.
270+
*
271+
* TODO: Figure out why this is needed.
272+
*/
273+
class PropertyWriteFlowStep extends AdditionalFlowInputStep {
274+
Assignment assignment;
275+
276+
PropertyWriteFlowStep() {
277+
this.asExpr() = assignment.getRValue() and
278+
assignment.getLValue() instanceof PropertyWrite
279+
}
280+
281+
override DataFlow::Node getOutput() { result.asExpr() = assignment.getLValue() }
282+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System;
2+
using System.IO;
3+
using System.Security.Cryptography;
4+
using System.Text;
5+
6+
namespace QuantumExamples.Cryptography
7+
{
8+
public class AesCfbExample
9+
{
10+
public static void RunExample()
11+
{
12+
const string originalMessage = "This is a secret message!";
13+
14+
byte[] key = GenerateRandomKey();
15+
byte[] iv = GenerateRandomIV();
16+
17+
byte[] encryptedData = EncryptStringWithCfb(originalMessage, key, iv);
18+
string decryptedMessage = DecryptStringWithCfb(encryptedData, key, iv);
19+
20+
bool isSuccessful = originalMessage == decryptedMessage;
21+
Console.WriteLine("Decryption successful: {0}", isSuccessful);
22+
}
23+
24+
private static byte[] EncryptStringWithCfb(string plainText, byte[] key, byte[] iv)
25+
{
26+
byte[] encrypted;
27+
28+
using (Aes aes = Aes.Create())
29+
{
30+
// Set the key and IV on the AES instance.
31+
aes.Key = key;
32+
aes.IV = iv;
33+
aes.Mode = CipherMode.CFB;
34+
aes.Padding = PaddingMode.None;
35+
36+
ICryptoTransform encryptor = aes.CreateEncryptor();
37+
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
38+
39+
// Create an empty memory stream and write the plaintext to the crypto stream.
40+
using (MemoryStream msEncrypt = new MemoryStream())
41+
{
42+
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
43+
{
44+
csEncrypt.Write(plainBytes, 0, plainBytes.Length);
45+
csEncrypt.FlushFinalBlock();
46+
}
47+
encrypted = msEncrypt.ToArray();
48+
}
49+
}
50+
return encrypted;
51+
}
52+
53+
private static string DecryptStringWithCfb(byte[] cipherText, byte[] key, byte[] iv)
54+
{
55+
string decrypted;
56+
57+
using (Aes aes = Aes.Create())
58+
{
59+
aes.Mode = CipherMode.CFB;
60+
aes.Padding = PaddingMode.None;
61+
62+
// Pass the key and IV to the decryptor directly.
63+
ICryptoTransform decryptor = aes.CreateDecryptor(key, iv);
64+
65+
// Pass the ciphertext to the memory stream directly.
66+
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
67+
{
68+
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
69+
{
70+
using (MemoryStream msPlain = new MemoryStream())
71+
{
72+
csDecrypt.CopyTo(msPlain);
73+
byte[] plainBytes = msPlain.ToArray();
74+
decrypted = Encoding.UTF8.GetString(plainBytes);
75+
}
76+
}
77+
}
78+
}
79+
return decrypted;
80+
}
81+
82+
private static byte[] GenerateRandomKey()
83+
{
84+
byte[] key = new byte[32]; // 256-bit key
85+
using (var rng = RandomNumberGenerator.Create())
86+
{
87+
rng.GetBytes(key);
88+
}
89+
return key;
90+
}
91+
92+
private static byte[] GenerateRandomIV()
93+
{
94+
byte[] iv = new byte[16]; // 128-bit IV
95+
using (var rng = RandomNumberGenerator.Create())
96+
{
97+
rng.GetBytes(iv);
98+
}
99+
return iv;
100+
}
101+
}
102+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration |
2+
| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import csharp
2+
import experimental.quantum.Language
3+
4+
from Crypto::CipherOperationNode op, Crypto::KeyArtifactNode k
5+
where op.getAKey() = k
6+
select op, k, k.getSourceNode()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration |
2+
| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import csharp
2+
import experimental.quantum.Language
3+
4+
from Crypto::CipherOperationNode op, Crypto::NonceArtifactNode n
5+
where op.getANonce() = n
6+
select op, n, n.getSourceNode()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:44:41:44:50 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:28:30:28:41 | KeyOperationAlgorithm | Encrypt |
2+
| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:73:49:73:65 | KeyOperationOutput | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:57:30:57:41 | KeyOperationAlgorithm | Decrypt |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import csharp
2+
import experimental.quantum.Language
3+
4+
from Crypto::CipherOperationNode n
5+
select n, n.getAnInputArtifact(), n.getAnOutputArtifact(), n.getAKey(), n.getANonce(),
6+
n.getAnAlgorithmOrGenericSource(), n.getKeyOperationSubtype()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
semmle-extractor-options: /nostdlib /noconfig
2+
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj

0 commit comments

Comments
 (0)