Skip to content

Commit e0430a7

Browse files
committed
Merge branch 'tob/quantum-csharp' of github.com:trailofbits/codeql into tob/quantum-csharp
2 parents a3e93ce + c7caf97 commit e0430a7

4 files changed

Lines changed: 196 additions & 40 deletions

File tree

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

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof
7272

7373
class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SymmetricAlgorithmCreation
7474
{
75+
SymmetricAlgorithmConsumer consumer;
76+
77+
SymmetricAlgorithmInstance() { consumer = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) }
78+
7579
override string getRawAlgorithmName() { result = super.getSymmetricAlgorithm().getName() }
7680

7781
override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
@@ -80,13 +84,33 @@ class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance i
8084
else result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::OtherSymmetricCipherType())
8185
}
8286

83-
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
87+
// The cipher mode is set by assigning it to the `Mode` property of the
88+
// symmetric algorithm.
89+
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() {
90+
result.(CipherModeLiteralInstance).getConsumer() = this.getCipherModeAlgorithmValueConsumer()
91+
}
8492

85-
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
93+
// The padding mode is set by assigning it to the `Padding` property of the
94+
// symmetric algorithm.
95+
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() {
96+
result.(PaddingModeLiteralInstance).getConsumer() = this.getPaddingAlgorithmValueConsumer()
97+
}
98+
99+
Crypto::AlgorithmValueConsumer getPaddingAlgorithmValueConsumer() {
100+
result = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) and
101+
result instanceof PaddingPropertyWrite
102+
}
103+
104+
Crypto::AlgorithmValueConsumer getCipherModeAlgorithmValueConsumer() {
105+
result = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) and
106+
result instanceof CipherModePropertyWrite
107+
}
86108

87109
override int getKeySizeFixed() { none() }
88110

89111
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }
112+
113+
Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
90114
}
91115

92116
/**
@@ -98,7 +122,7 @@ class PaddingModeLiteralInstance extends Crypto::PaddingAlgorithmInstance instan
98122

99123
PaddingModeLiteralInstance() {
100124
this = any(PaddingMode mode).getAnAccess() and
101-
consumer = PaddingModeLiteralFlow::getConsumer(this, _, _)
125+
consumer = ModeLiteralFlow::getConsumer(this, _, _)
102126
}
103127

104128
override string getRawPaddingAlgorithmName() { result = super.getTarget().getName() }
@@ -112,6 +136,29 @@ class PaddingModeLiteralInstance extends Crypto::PaddingAlgorithmInstance instan
112136
Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
113137
}
114138

139+
/**
140+
* A padding mode literal, such as `PaddingMode.PKCS7`.
141+
*/
142+
class CipherModeLiteralInstance extends Crypto::ModeOfOperationAlgorithmInstance instanceof MemberConstantAccess
143+
{
144+
Crypto::AlgorithmValueConsumer consumer;
145+
146+
CipherModeLiteralInstance() {
147+
this = any(CipherMode mode).getAnAccess() and
148+
consumer = ModeLiteralFlow::getConsumer(this, _, _)
149+
}
150+
151+
override string getRawModeAlgorithmName() { result = super.getTarget().getName() }
152+
153+
override Crypto::TBlockCipherModeOfOperationType getModeType() {
154+
if exists(modeNameToType(this.getRawModeAlgorithmName()))
155+
then result = modeNameToType(this.getRawModeAlgorithmName())
156+
else result = Crypto::OtherMode()
157+
}
158+
159+
Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
160+
}
161+
115162
private Crypto::KeyOpAlg::Algorithm symmetricAlgorithmNameToType(string algorithmName) {
116163
algorithmName = "Aes" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES())
117164
or
@@ -133,3 +180,13 @@ private Crypto::TPaddingType paddingNameToType(string paddingName) {
133180
or
134181
paddingName = "PKCS7" and result = Crypto::PKCS7()
135182
}
183+
184+
private Crypto::TBlockCipherModeOfOperationType modeNameToType(string modeName) {
185+
modeName = "CBC" and result = Crypto::CBC()
186+
or
187+
modeName = "CFB" and result = Crypto::CFB()
188+
or
189+
modeName = "ECB" and result = Crypto::ECB()
190+
or
191+
modeName = "OFB" and result = Crypto::OFB()
192+
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,32 @@ class PaddingPropertyWrite extends Crypto::AlgorithmValueConsumer instanceof Sym
4040
result.(PaddingModeLiteralInstance).getConsumer() = this
4141
}
4242
}
43+
44+
/**
45+
* A write access to the `Mode` property of a `SymmetricAlgorithm` instance.
46+
*/
47+
class CipherModePropertyWrite extends Crypto::AlgorithmValueConsumer instanceof SymmetricAlgorithmUse
48+
{
49+
CipherModePropertyWrite() { super.isModeConsumer() }
50+
51+
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
52+
53+
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
54+
result.(CipherModeLiteralInstance).getConsumer() = this
55+
}
56+
}
57+
58+
/**
59+
* A call to a `SymmetricAlgorithm.CreateEncryptor` or `SymmetricAlgorithm.CreateDecryptor`
60+
* method that returns a `CryptoTransform` instance.
61+
*/
62+
class SymmetricAlgorithmConsumer extends Crypto::AlgorithmValueConsumer instanceof CryptoTransformCreation
63+
{
64+
override Crypto::ConsumerInputDataFlowNode getInputNode() {
65+
result.asExpr() = super.getQualifier()
66+
}
67+
68+
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
69+
result.(SymmetricAlgorithmInstance).getConsumer() = this
70+
}
71+
}

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

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ module CreationToUseFlow<CreationCallSig Creation, UseCallSig Use> {
3131
Use use, CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink
3232
) {
3333
source.getNode().asExpr() = result and
34-
sink.getNode().asExpr() = use.(MethodCall).getQualifier() and
34+
sink.getNode().asExpr() = use.(QualifiableExpr).getQualifier() and
3535
CreationToUseFlow::flowPath(source, sink)
3636
}
3737

3838
Use getUseFromCreation(
3939
Creation creation, CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink
4040
) {
4141
source.getNode().asExpr() = creation and
42-
sink.getNode().asExpr() = result.(MethodCall).getQualifier() and
42+
sink.getNode().asExpr() = result.(QualifiableExpr).getQualifier() and
4343
CreationToUseFlow::flowPath(source, sink)
4444
}
4545

@@ -145,7 +145,9 @@ module CryptoTransformFlow {
145145
/**
146146
* A flow analysis module that tracks the flow from a `PaddingMode` member
147147
* access (e.g. `PaddingMode.PKCS7`) to a `Padding` property write on a
148-
* `SymmetricAlgorithm` instance.
148+
* `SymmetricAlgorithm` instance, or from a `CipherMode` member access
149+
* (e.g. `CipherMode.CBC`) to a `Mode` property write on a `SymmetricAlgorithm`
150+
* instance.
149151
*
150152
* Example:
151153
* ```
@@ -154,13 +156,18 @@ module CryptoTransformFlow {
154156
* ...
155157
* ```
156158
*/
157-
module PaddingModeLiteralFlow {
158-
private module PaddingModeLiteralConfig implements DataFlow::ConfigSig {
159+
module ModeLiteralFlow {
160+
private module ModeLiteralConfig implements DataFlow::ConfigSig {
159161
predicate isSource(DataFlow::Node source) {
160162
source.asExpr() = any(PaddingMode mode).getAnAccess()
163+
or
164+
source.asExpr() = any(CipherMode mode).getAnAccess()
161165
}
162166

163-
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof PaddingPropertyWrite }
167+
predicate isSink(DataFlow::Node sink) {
168+
sink.asExpr() instanceof PaddingPropertyWrite or
169+
sink.asExpr() instanceof CipherModePropertyWrite
170+
}
164171

165172
// TODO: Figure out why this is needed.
166173
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
@@ -171,51 +178,96 @@ module PaddingModeLiteralFlow {
171178
}
172179
}
173180

174-
private module PaddingModeLiteralFlow = DataFlow::Global<PaddingModeLiteralConfig>;
181+
private module ModeLiteralFlow = DataFlow::Global<ModeLiteralConfig>;
175182

176183
SymmetricAlgorithmUse getConsumer(
177-
Expr mode, PaddingModeLiteralFlow::PathNode source, PaddingModeLiteralFlow::PathNode sink
184+
Expr mode, ModeLiteralFlow::PathNode source, ModeLiteralFlow::PathNode sink
178185
) {
179186
source.getNode().asExpr() = mode and
180187
sink.getNode().asExpr() = result and
181-
PaddingModeLiteralFlow::flowPath(source, sink)
188+
ModeLiteralFlow::flowPath(source, sink)
182189
}
183190
}
184191

185192
/**
186-
* A flow analysis module that tracks the flow from a `MemoryStream` object
187-
* creation to the `stream` argument passed to a `CryptoStream` constructor
188-
* call.
193+
* A flow analysis module that tracks the flow from an arbitrary `Stream` object
194+
* creation to the creation of a second `Stream` object wrapping the first one.
189195
*
190-
* TODO: This should probably be made generic over multiple stream types.
196+
* This is useful for tracking the flow of data from a buffer passed to a
197+
* `MemoryStream` to a `CryptoStream` wrapping the original `MemoryStream`. It
198+
* can also be used to track dataflow from a `Stream` object to a call to
199+
* `ToArray()` on the stream, or a wrapped stream.
191200
*/
192-
module MemoryStreamFlow {
193-
private class MemoryStreamCreation extends ObjectCreation {
194-
MemoryStreamCreation() {
195-
this.getObjectType().hasFullyQualifiedName("System.IO", "MemoryStream")
201+
module StreamFlow {
202+
private class Stream extends Class {
203+
Stream() { this.getABaseType().hasFullyQualifiedName("System.IO", "Stream") }
204+
}
205+
206+
/**
207+
* A `Stream` object creation.
208+
*/
209+
private class StreamCreation extends ObjectCreation {
210+
StreamCreation() { this.getObjectType() instanceof Stream }
211+
212+
Expr getInputArg() {
213+
result = this.getAnArgument() and
214+
result.getType().hasFullyQualifiedName("System", "Byte[]")
215+
}
216+
217+
Expr getStreamArg() {
218+
result = this.getAnArgument() and
219+
result.getType() instanceof Stream
220+
}
221+
}
222+
223+
private class StreamUse extends MethodCall {
224+
StreamUse() {
225+
this.getQualifier().getType() instanceof Stream and
226+
this.getTarget().hasName("ToArray")
196227
}
197228

198-
Expr getBufferArg() { result = this.getArgument(0) }
229+
Expr getOutput() { result = this }
199230
}
200231

201-
// (Note that we cannot use `CreationToUseFlow` here, because the use is not a
202-
// `QualifiableExpr`.)
203-
private module MemoryStreamConfig implements DataFlow::ConfigSig {
204-
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof MemoryStreamCreation }
232+
private module StreamConfig implements DataFlow::ConfigSig {
233+
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof StreamCreation }
205234

206235
predicate isSink(DataFlow::Node sink) {
207-
exists(CryptoStreamCreation creation | sink.asExpr() = creation.getStreamArg())
236+
sink.asExpr() instanceof StreamCreation
237+
or
238+
exists(StreamUse use | sink.asExpr() = use.getQualifier())
239+
}
240+
241+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
242+
// Allow flow from one stream wrapped by a second stream.
243+
exists(StreamCreation creation |
244+
node1.asExpr() = creation.getStreamArg() and
245+
node2.asExpr() = creation
246+
)
247+
or
248+
exists(MethodCall copy |
249+
node1.asExpr() = copy.getQualifier() and
250+
node2.asExpr() = copy.getAnArgument() and
251+
copy.getTarget().hasName("CopyTo")
252+
)
208253
}
209254
}
210255

211-
private module MemoryStreamFlow = DataFlow::Global<MemoryStreamConfig>;
256+
private module StreamFlow = DataFlow::Global<StreamConfig>;
212257

213-
MemoryStreamCreation getCreationFromUse(
214-
CryptoStreamCreation creation, MemoryStreamFlow::PathNode source,
215-
MemoryStreamFlow::PathNode sink
258+
StreamCreation getWrappedStream(
259+
StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink
216260
) {
217261
source.getNode().asExpr() = result and
218-
sink.getNode().asExpr() = creation.getStreamArg() and
219-
MemoryStreamFlow::flowPath(source, sink)
262+
sink.getNode().asExpr() = stream and
263+
StreamFlow::flowPath(source, sink)
264+
}
265+
266+
StreamUse getStreamUse(
267+
StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink
268+
) {
269+
source.getNode().asExpr() = stream and
270+
sink.getNode().asExpr() = result.getQualifier() and
271+
StreamFlow::flowPath(source, sink)
220272
}
221273
}

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class SymmetricAlgorithmUse extends QualifiableExpr {
8888
SymmetricAlgorithmUse() {
8989
this.getQualifier().getType() instanceof SymmetricAlgorithm and
9090
this.getQualifiedDeclaration()
91-
.hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding"])
91+
.hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding", "Mode"])
9292
}
9393

9494
Expr getSymmetricAlgorithm() { result = this.getQualifier() }
@@ -111,6 +111,11 @@ class SymmetricAlgorithmUse extends QualifiableExpr {
111111
predicate isPaddingConsumer() {
112112
this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Padding"
113113
}
114+
115+
// The cipher mode may be set by assigning it to the `Mode` property of the symmetric algorithm.
116+
predicate isModeConsumer() {
117+
this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Mode"
118+
}
114119
}
115120

116121
module SymmetricAlgorithmFlow =
@@ -162,6 +167,12 @@ class PaddingMode extends MemberConstant {
162167
}
163168
}
164169

170+
class CipherMode extends MemberConstant {
171+
CipherMode() {
172+
this.getDeclaringType().hasFullyQualifiedName("System.Security.Cryptography", "CipherMode")
173+
}
174+
}
175+
165176
class CryptoStreamCreation extends ObjectCreation {
166177
CryptoStreamCreation() { this.getObjectType() instanceof CryptoStream }
167178

@@ -211,7 +222,7 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc
211222
(transform.isEncryptor() or transform.isDecryptor())
212223
}
213224

214-
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { none() }
225+
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = transform }
215226

216227
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
217228
if transform.isEncryptor()
@@ -246,14 +257,21 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc
246257
)
247258
}
248259

249-
// Inputs to the operation can be passed either through the stream argument
250-
// when the `CryptoStream` is created, or through calls to
251-
// `CryptoStream.Write()`. If the input is passed through the stream argument,
252-
// it is wrapped using a `MemoryStream` object.
260+
// Inputs can be passed either through the `stream` argument when the
261+
// `CryptoStream` is created, or through calls to `Write()` on the
262+
// `CryptoStream` object.
253263
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
254-
result.asExpr() = MemoryStreamFlow::getCreationFromUse(this, _, _).getBufferArg() or
264+
result.asExpr() = StreamFlow::getWrappedStream(this, _, _).getInputArg() or
255265
result.asExpr() = CryptoStreamFlow::getUseFromCreation(this, _, _).getInputArg()
256266
}
257267

258-
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { none() }
268+
// The output is obtained by calling `ToArray()` on a `Stream` either wrapped
269+
// by the `CryptoStream` object, or copied from the `CryptoStream` object.
270+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
271+
// We perform backwards dataflow to identify stream objects that are wrapped
272+
// by the `CryptoStream` object, and then we look for calls to `ToArray()`
273+
// on those streams.
274+
result.asExpr() =
275+
StreamFlow::getStreamUse(any(StreamFlow::getWrappedStream(this, _, _)), _, _).getOutput()
276+
}
259277
}

0 commit comments

Comments
 (0)