Skip to content

Commit c7caf97

Browse files
committed
Extended CryptoStreamKeyOperationInstance
- Added support to get input consumers and output artifacts - Added padding and cipher mode algorithm instances, as well as dataflow to link these to `CryptoStream` key operations
1 parent b1bbd97 commit c7caf97

File tree

4 files changed

+196
-40
lines changed

4 files changed

+196
-40
lines changed

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

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

4545
class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SymmetricAlgorithmCreation
4646
{
47+
SymmetricAlgorithmConsumer consumer;
48+
49+
SymmetricAlgorithmInstance() { consumer = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) }
50+
4751
override string getRawAlgorithmName() { result = super.getSymmetricAlgorithm().getName() }
4852

4953
override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
@@ -52,13 +56,33 @@ class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance i
5256
else result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::OtherSymmetricCipherType())
5357
}
5458

55-
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
59+
// The cipher mode is set by assigning it to the `Mode` property of the
60+
// symmetric algorithm.
61+
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() {
62+
result.(CipherModeLiteralInstance).getConsumer() = this.getCipherModeAlgorithmValueConsumer()
63+
}
64+
65+
// The padding mode is set by assigning it to the `Padding` property of the
66+
// symmetric algorithm.
67+
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() {
68+
result.(PaddingModeLiteralInstance).getConsumer() = this.getPaddingAlgorithmValueConsumer()
69+
}
70+
71+
Crypto::AlgorithmValueConsumer getPaddingAlgorithmValueConsumer() {
72+
result = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) and
73+
result instanceof PaddingPropertyWrite
74+
}
5675

57-
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
76+
Crypto::AlgorithmValueConsumer getCipherModeAlgorithmValueConsumer() {
77+
result = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) and
78+
result instanceof CipherModePropertyWrite
79+
}
5880

5981
override int getKeySizeFixed() { none() }
6082

6183
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }
84+
85+
Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
6286
}
6387

6488
/**
@@ -70,7 +94,7 @@ class PaddingModeLiteralInstance extends Crypto::PaddingAlgorithmInstance instan
7094

7195
PaddingModeLiteralInstance() {
7296
this = any(PaddingMode mode).getAnAccess() and
73-
consumer = PaddingModeLiteralFlow::getConsumer(this, _, _)
97+
consumer = ModeLiteralFlow::getConsumer(this, _, _)
7498
}
7599

76100
override string getRawPaddingAlgorithmName() { result = super.getTarget().getName() }
@@ -84,6 +108,29 @@ class PaddingModeLiteralInstance extends Crypto::PaddingAlgorithmInstance instan
84108
Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
85109
}
86110

111+
/**
112+
* A padding mode literal, such as `PaddingMode.PKCS7`.
113+
*/
114+
class CipherModeLiteralInstance extends Crypto::ModeOfOperationAlgorithmInstance instanceof MemberConstantAccess
115+
{
116+
Crypto::AlgorithmValueConsumer consumer;
117+
118+
CipherModeLiteralInstance() {
119+
this = any(CipherMode mode).getAnAccess() and
120+
consumer = ModeLiteralFlow::getConsumer(this, _, _)
121+
}
122+
123+
override string getRawModeAlgorithmName() { result = super.getTarget().getName() }
124+
125+
override Crypto::TBlockCipherModeOfOperationType getModeType() {
126+
if exists(modeNameToType(this.getRawModeAlgorithmName()))
127+
then result = modeNameToType(this.getRawModeAlgorithmName())
128+
else result = Crypto::OtherMode()
129+
}
130+
131+
Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
132+
}
133+
87134
private Crypto::KeyOpAlg::Algorithm symmetricAlgorithmNameToType(string algorithmName) {
88135
algorithmName = "Aes" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES())
89136
or
@@ -105,3 +152,13 @@ private Crypto::TPaddingType paddingNameToType(string paddingName) {
105152
or
106153
paddingName = "PKCS7" and result = Crypto::PKCS7()
107154
}
155+
156+
private Crypto::TBlockCipherModeOfOperationType modeNameToType(string modeName) {
157+
modeName = "CBC" and result = Crypto::CBC()
158+
or
159+
modeName = "CFB" and result = Crypto::CFB()
160+
or
161+
modeName = "ECB" and result = Crypto::ECB()
162+
or
163+
modeName = "OFB" and result = Crypto::OFB()
164+
}

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

@@ -143,7 +143,9 @@ module CryptoTransformFlow {
143143
/**
144144
* A flow analysis module that tracks the flow from a `PaddingMode` member
145145
* access (e.g. `PaddingMode.PKCS7`) to a `Padding` property write on a
146-
* `SymmetricAlgorithm` instance.
146+
* `SymmetricAlgorithm` instance, or from a `CipherMode` member access
147+
* (e.g. `CipherMode.CBC`) to a `Mode` property write on a `SymmetricAlgorithm`
148+
* instance.
147149
*
148150
* Example:
149151
* ```
@@ -152,13 +154,18 @@ module CryptoTransformFlow {
152154
* ...
153155
* ```
154156
*/
155-
module PaddingModeLiteralFlow {
156-
private module PaddingModeLiteralConfig implements DataFlow::ConfigSig {
157+
module ModeLiteralFlow {
158+
private module ModeLiteralConfig implements DataFlow::ConfigSig {
157159
predicate isSource(DataFlow::Node source) {
158160
source.asExpr() = any(PaddingMode mode).getAnAccess()
161+
or
162+
source.asExpr() = any(CipherMode mode).getAnAccess()
159163
}
160164

161-
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof PaddingPropertyWrite }
165+
predicate isSink(DataFlow::Node sink) {
166+
sink.asExpr() instanceof PaddingPropertyWrite or
167+
sink.asExpr() instanceof CipherModePropertyWrite
168+
}
162169

163170
// TODO: Figure out why this is needed.
164171
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
@@ -169,51 +176,96 @@ module PaddingModeLiteralFlow {
169176
}
170177
}
171178

172-
private module PaddingModeLiteralFlow = DataFlow::Global<PaddingModeLiteralConfig>;
179+
private module ModeLiteralFlow = DataFlow::Global<ModeLiteralConfig>;
173180

174181
SymmetricAlgorithmUse getConsumer(
175-
Expr mode, PaddingModeLiteralFlow::PathNode source, PaddingModeLiteralFlow::PathNode sink
182+
Expr mode, ModeLiteralFlow::PathNode source, ModeLiteralFlow::PathNode sink
176183
) {
177184
source.getNode().asExpr() = mode and
178185
sink.getNode().asExpr() = result and
179-
PaddingModeLiteralFlow::flowPath(source, sink)
186+
ModeLiteralFlow::flowPath(source, sink)
180187
}
181188
}
182189

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

196-
Expr getBufferArg() { result = this.getArgument(0) }
227+
Expr getOutput() { result = this }
197228
}
198229

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

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

209-
private module MemoryStreamFlow = DataFlow::Global<MemoryStreamConfig>;
254+
private module StreamFlow = DataFlow::Global<StreamConfig>;
210255

211-
MemoryStreamCreation getCreationFromUse(
212-
CryptoStreamCreation creation, MemoryStreamFlow::PathNode source,
213-
MemoryStreamFlow::PathNode sink
256+
StreamCreation getWrappedStream(
257+
StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink
214258
) {
215259
source.getNode().asExpr() = result and
216-
sink.getNode().asExpr() = creation.getStreamArg() and
217-
MemoryStreamFlow::flowPath(source, sink)
260+
sink.getNode().asExpr() = stream and
261+
StreamFlow::flowPath(source, sink)
262+
}
263+
264+
StreamUse getStreamUse(
265+
StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink
266+
) {
267+
source.getNode().asExpr() = stream and
268+
sink.getNode().asExpr() = result.getQualifier() and
269+
StreamFlow::flowPath(source, sink)
218270
}
219271
}

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class SymmetricAlgorithmUse extends QualifiableExpr {
7474
SymmetricAlgorithmUse() {
7575
this.getQualifier().getType() instanceof SymmetricAlgorithm and
7676
this.getQualifiedDeclaration()
77-
.hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding"])
77+
.hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding", "Mode"])
7878
}
7979

8080
Expr getSymmetricAlgorithm() { result = this.getQualifier() }
@@ -97,6 +97,11 @@ class SymmetricAlgorithmUse extends QualifiableExpr {
9797
predicate isPaddingConsumer() {
9898
this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Padding"
9999
}
100+
101+
// The cipher mode may be set by assigning it to the `Mode` property of the symmetric algorithm.
102+
predicate isModeConsumer() {
103+
this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Mode"
104+
}
100105
}
101106

102107
module SymmetricAlgorithmFlow =
@@ -148,6 +153,12 @@ class PaddingMode extends MemberConstant {
148153
}
149154
}
150155

156+
class CipherMode extends MemberConstant {
157+
CipherMode() {
158+
this.getDeclaringType().hasFullyQualifiedName("System.Security.Cryptography", "CipherMode")
159+
}
160+
}
161+
151162
class CryptoStreamCreation extends ObjectCreation {
152163
CryptoStreamCreation() { this.getObjectType() instanceof CryptoStream }
153164

@@ -197,7 +208,7 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc
197208
(transform.isEncryptor() or transform.isDecryptor())
198209
}
199210

200-
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { none() }
211+
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = transform }
201212

202213
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
203214
if transform.isEncryptor()
@@ -232,14 +243,21 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc
232243
)
233244
}
234245

235-
// Inputs to the operation can be passed either through the stream argument
236-
// when the `CryptoStream` is created, or through calls to
237-
// `CryptoStream.Write()`. If the input is passed through the stream argument,
238-
// it is wrapped using a `MemoryStream` object.
246+
// Inputs can be passed either through the `stream` argument when the
247+
// `CryptoStream` is created, or through calls to `Write()` on the
248+
// `CryptoStream` object.
239249
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
240-
result.asExpr() = MemoryStreamFlow::getCreationFromUse(this, _, _).getBufferArg() or
250+
result.asExpr() = StreamFlow::getWrappedStream(this, _, _).getInputArg() or
241251
result.asExpr() = CryptoStreamFlow::getUseFromCreation(this, _, _).getInputArg()
242252
}
243253

244-
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { none() }
254+
// The output is obtained by calling `ToArray()` on a `Stream` either wrapped
255+
// by the `CryptoStream` object, or copied from the `CryptoStream` object.
256+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
257+
// We perform backwards dataflow to identify stream objects that are wrapped
258+
// by the `CryptoStream` object, and then we look for calls to `ToArray()`
259+
// on those streams.
260+
result.asExpr() =
261+
StreamFlow::getStreamUse(any(StreamFlow::getWrappedStream(this, _, _)), _, _).getOutput()
262+
}
245263
}

0 commit comments

Comments
 (0)