Skip to content

Commit 0ef17ba

Browse files
authored
Merge pull request #19607 from trailofbits/openssl-base-classes
Quantum: Add base classes for OpenSSL EVP methods
2 parents 8fe2699 + 60d9b6e commit 0ef17ba

File tree

8 files changed

+230
-203
lines changed

8 files changed

+230
-203
lines changed

cpp/ql/lib/experimental/quantum/OpenSSL/Operations/ECKeyGenOperation.qll

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,15 @@ private import OpenSSLOperationBase
44
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
55
private import semmle.code.cpp.dataflow.new.DataFlow
66

7-
private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig {
8-
predicate isSource(DataFlow::Node source) {
9-
exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source)
10-
}
11-
12-
predicate isSink(DataFlow::Node sink) {
13-
exists(ECKeyGenOperation c | c.getAlgorithmArg() = sink.asExpr())
14-
}
15-
}
16-
17-
private module AlgGetterToAlgConsumerFlow = DataFlow::Global<AlgGetterToAlgConsumerConfig>;
18-
197
class ECKeyGenOperation extends OpenSSLOperation, Crypto::KeyGenerationOperationInstance {
208
ECKeyGenOperation() { this.(Call).getTarget().getName() = "EC_KEY_generate_key" }
219

22-
override Expr getOutputArg() {
23-
result = this.(Call) // return value of call
24-
}
25-
26-
Expr getAlgorithmArg() { result = this.(Call).getArgument(0) }
27-
28-
override Expr getInputArg() {
29-
// there is no 'input', in the sense that no data is being manipulated by the operation.
30-
// There is an input of an algorithm, but that is not the intention of the operation input arg.
31-
none()
32-
}
10+
override Expr getAlgorithmArg() { result = this.(Call).getArgument(0) }
3311

3412
override Crypto::KeyArtifactType getOutputKeyType() { result = Crypto::TAsymmetricKeyType() }
3513

3614
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() {
37-
result = this.getOutputNode()
38-
}
39-
40-
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
41-
AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
42-
DataFlow::exprNode(this.getAlgorithmArg()))
15+
result.asExpr() = this.(Call).getArgument(0)
4316
}
4417

4518
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {

cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherInitializer.qll

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
private import experimental.quantum.Language
77
private import experimental.quantum.OpenSSL.CtxFlow as CTXFlow
8+
private import OpenSSLOperationBase
89

910
module EncValToInitEncArgConfig implements DataFlow::ConfigSig {
1011
predicate isSource(DataFlow::Node source) { source.asExpr().getValue().toInt() in [0, 1] }
@@ -34,19 +35,12 @@ Crypto::KeyOperationSubtype intToCipherOperationSubtype(int i) {
3435
}
3536

3637
// TODO: need to add key consumer
37-
abstract class EVP_Cipher_Initializer extends Call {
38-
Expr getContextArg() { result = this.(Call).getArgument(0) }
38+
abstract class EVP_Cipher_Initializer extends EVPInitialize {
39+
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
3940

40-
Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
41-
42-
abstract Expr getKeyArg();
43-
44-
abstract Expr getIVArg();
45-
46-
// abstract Crypto::CipherOperationSubtype getCipherOperationSubtype();
4741
abstract Expr getOperationSubtypeArg();
4842

49-
Crypto::KeyOperationSubtype getCipherOperationSubtype() {
43+
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
5044
if this.(Call).getTarget().getName().toLowerCase().matches("%encrypt%")
5145
then result instanceof Crypto::TEncryptMode
5246
else

cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPCipherOperation.qll

Lines changed: 25 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,23 @@ private import EVPCipherInitializer
44
private import OpenSSLOperationBase
55
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
66

7-
private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig {
8-
predicate isSource(DataFlow::Node source) {
9-
exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source)
7+
class EVP_Cipher_Update_Call extends EVPUpdate {
8+
EVP_Cipher_Update_Call() {
9+
this.(Call).getTarget().getName() in [
10+
"EVP_EncryptUpdate", "EVP_DecryptUpdate", "EVP_CipherUpdate"
11+
]
1012
}
1113

12-
predicate isSink(DataFlow::Node sink) {
13-
exists(EVP_Cipher_Operation c | c.getAlgorithmArg() = sink.asExpr())
14-
}
15-
}
14+
override Expr getInputArg() { result = this.(Call).getArgument(3) }
1615

17-
private module AlgGetterToAlgConsumerFlow = DataFlow::Global<AlgGetterToAlgConsumerConfig>;
16+
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
17+
}
1818

19-
// import experimental.quantum.OpenSSL.AlgorithmValueConsumers.AlgorithmValueConsumers
20-
// import OpenSSLOperation
21-
// class EVPCipherOutput extends CipherOutputArtifact {
22-
// EVPCipherOutput() { exists(EVP_Cipher_Operation op | op.getOutputArg() = this) }
23-
// override DataFlow::Node getOutputNode() { result.asDefiningArgument() = this }
24-
// }
25-
//
2619
/**
2720
* see: https://docs.openssl.org/master/man3/EVP_EncryptInit/#synopsis
2821
* Base configuration for all EVP cipher operations.
29-
* NOTE: cannot extend instance of OpenSSLOperation, as we need to override
30-
* elements of OpenSSLOperation (i.e., we are creating an instance)
3122
*/
32-
abstract class EVP_Cipher_Operation extends OpenSSLOperation, Crypto::KeyOperationInstance {
33-
Expr getContextArg() { result = this.(Call).getArgument(0) }
34-
35-
Expr getAlgorithmArg() { this.getInitCall().getAlgorithmArg() = result }
36-
23+
abstract class EVP_Cipher_Operation extends EVPOperation, Crypto::KeyOperationInstance {
3724
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
3825

3926
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
@@ -43,81 +30,48 @@ abstract class EVP_Cipher_Operation extends OpenSSLOperation, Crypto::KeyOperati
4330
result instanceof Crypto::TDecryptMode and
4431
this.(Call).getTarget().getName().toLowerCase().matches("%decrypt%")
4532
or
46-
result = this.getInitCall().getCipherOperationSubtype() and
33+
result = this.getInitCall().getKeyOperationSubtype() and
4734
this.(Call).getTarget().getName().toLowerCase().matches("%cipher%")
4835
}
4936

50-
EVP_Cipher_Initializer getInitCall() {
51-
CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg())
52-
}
53-
5437
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
5538
this.getInitCall().getIVArg() = result.asExpr()
5639
}
5740

58-
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
59-
6041
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
6142
this.getInitCall().getKeyArg() = result.asExpr()
43+
// todo: or track to the EVP_PKEY_CTX_new
6244
}
6345

64-
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
46+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
47+
result = EVPOperation.super.getOutputArtifact()
48+
}
6549

66-
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
67-
AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
68-
DataFlow::exprNode(this.getInitCall().getAlgorithmArg()))
50+
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
51+
result = EVPOperation.super.getInputConsumer()
6952
}
7053
}
7154

72-
class EVP_Cipher_Call extends EVP_Cipher_Operation {
55+
class EVP_Cipher_Call extends EVPOperation, EVP_Cipher_Operation {
7356
EVP_Cipher_Call() { this.(Call).getTarget().getName() = "EVP_Cipher" }
7457

7558
override Expr getInputArg() { result = this.(Call).getArgument(2) }
7659
}
7760

78-
// NOTE: not modeled as cipher operations, these are intermediate calls
79-
class EVP_Cipher_Update_Call extends Call {
80-
EVP_Cipher_Update_Call() {
81-
this.(Call).getTarget().getName() in [
82-
"EVP_EncryptUpdate", "EVP_DecryptUpdate", "EVP_CipherUpdate"
83-
]
84-
}
85-
86-
Expr getInputArg() { result = this.(Call).getArgument(3) }
87-
88-
DataFlow::Node getInputNode() { result.asExpr() = this.getInputArg() }
89-
90-
Expr getContextArg() { result = this.(Call).getArgument(0) }
91-
}
92-
93-
class EVP_Cipher_Final_Call extends EVP_Cipher_Operation {
61+
class EVP_Cipher_Final_Call extends EVPFinal, EVP_Cipher_Operation {
9462
EVP_Cipher_Final_Call() {
9563
this.(Call).getTarget().getName() in [
9664
"EVP_EncryptFinal_ex", "EVP_DecryptFinal_ex", "EVP_CipherFinal_ex", "EVP_EncryptFinal",
9765
"EVP_DecryptFinal", "EVP_CipherFinal"
9866
]
9967
}
10068

101-
EVP_Cipher_Update_Call getUpdateCalls() {
102-
CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg())
103-
}
104-
105-
override Expr getInputArg() { result = this.getUpdateCalls().getInputArg() }
106-
107-
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
108-
}
109-
110-
class EVP_PKEY_Operation extends EVP_Cipher_Operation {
111-
EVP_PKEY_Operation() {
112-
this.(Call).getTarget().getName() in ["EVP_PKEY_decrypt", "EVP_PKEY_encrypt"]
69+
/**
70+
* Output is both from update calls and from the final call.
71+
*/
72+
override Expr getOutputArg() {
73+
result = EVPFinal.super.getOutputArg()
74+
or
75+
result = EVP_Cipher_Operation.super.getOutputArg()
11376
}
114-
115-
override Expr getInputArg() { result = this.(Call).getArgument(3) }
116-
// TODO: how PKEY is initialized is different that symmetric cipher
117-
// Consider making an entirely new class for this and specializing
118-
// the get init call
119-
}
120-
121-
class EVPCipherInputArgument extends Expr {
122-
EVPCipherInputArgument() { exists(EVP_Cipher_Operation op | op.getInputArg() = this) }
12377
}

cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashInitializer.qll

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import cpp
2+
private import OpenSSLOperationBase
23

3-
abstract class EVP_Hash_Initializer extends Call {
4-
Expr getContextArg() { result = this.(Call).getArgument(0) }
5-
6-
abstract Expr getAlgorithmArg();
7-
}
4+
abstract class EVP_Hash_Initializer extends EVPInitialize { }
85

96
class EVP_DigestInit_Variant_Calls extends EVP_Hash_Initializer {
107
EVP_DigestInit_Variant_Calls() {

cpp/ql/lib/experimental/quantum/OpenSSL/Operations/EVPHashOperation.qll

Lines changed: 30 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,118 +8,78 @@ private import OpenSSLOperationBase
88
private import EVPHashInitializer
99
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
1010

11-
// import EVPHashConsumers
12-
abstract class EVP_Hash_Operation extends OpenSSLOperation, Crypto::HashOperationInstance {
13-
Expr getContextArg() { result = this.(Call).getArgument(0) }
11+
class EVP_Digest_Update_Call extends EVPUpdate {
12+
EVP_Digest_Update_Call() { this.(Call).getTarget().getName() = "EVP_DigestUpdate" }
1413

15-
Expr getAlgorithmArg() { result = this.getInitCall().getAlgorithmArg() }
16-
17-
EVP_Hash_Initializer getInitCall() {
18-
CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg())
19-
}
20-
21-
/**
22-
* By default, the algorithm value comes from the init call.
23-
* There are variants where this isn't true, in which case the
24-
* subclass should override this method.
25-
*/
26-
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
27-
AlgGetterToAlgConsumerFlow::flow(result.(OpenSSLAlgorithmValueConsumer).getResultNode(),
28-
DataFlow::exprNode(this.getAlgorithmArg()))
29-
}
30-
}
31-
32-
private module AlgGetterToAlgConsumerConfig implements DataFlow::ConfigSig {
33-
predicate isSource(DataFlow::Node source) {
34-
exists(OpenSSLAlgorithmValueConsumer c | c.getResultNode() = source)
35-
}
36-
37-
predicate isSink(DataFlow::Node sink) {
38-
exists(EVP_Hash_Operation c | c.getAlgorithmArg() = sink.asExpr())
39-
}
14+
override Expr getInputArg() { result = this.(Call).getArgument(1) }
4015
}
4116

42-
private module AlgGetterToAlgConsumerFlow = DataFlow::Global<AlgGetterToAlgConsumerConfig>;
43-
4417
//https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
45-
class EVP_Q_Digest_Operation extends EVP_Hash_Operation {
18+
class EVP_Q_Digest_Operation extends EVPOperation, Crypto::HashOperationInstance {
4619
EVP_Q_Digest_Operation() { this.(Call).getTarget().getName() = "EVP_Q_digest" }
4720

48-
//override Crypto::AlgorithmConsumer getAlgorithmConsumer() { }
21+
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
22+
4923
override EVP_Hash_Initializer getInitCall() {
5024
// This variant of digest does not use an init
5125
// and even if it were used, the init would be ignored/undefined
5226
none()
5327
}
5428

55-
override Expr getOutputArg() { result = this.(Call).getArgument(5) }
56-
5729
override Expr getInputArg() { result = this.(Call).getArgument(3) }
5830

59-
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
60-
61-
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
31+
override Expr getOutputArg() { result = this.(Call).getArgument(5) }
6232

63-
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
64-
// The operation is a direct algorithm consumer
65-
// NOTE: the operation itself is already modeld as a value consumer, so we can
66-
// simply return 'this', see modeled hash algorithm consuers for EVP_Q_Digest
67-
this = result
33+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
34+
result = EVPOperation.super.getOutputArtifact()
6835
}
6936

70-
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
37+
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
38+
result = EVPOperation.super.getInputConsumer()
39+
}
7140
}
7241

73-
class EVP_Digest_Operation extends EVP_Hash_Operation {
42+
class EVP_Digest_Operation extends EVPOperation, Crypto::HashOperationInstance {
7443
EVP_Digest_Operation() { this.(Call).getTarget().getName() = "EVP_Digest" }
7544

7645
// There is no context argument for this function
7746
override Expr getContextArg() { none() }
7847

48+
override Expr getAlgorithmArg() { result = this.(Call).getArgument(4) }
49+
7950
override EVP_Hash_Initializer getInitCall() {
8051
// This variant of digest does not use an init
8152
// and even if it were used, the init would be ignored/undefined
8253
none()
8354
}
8455

85-
override Expr getAlgorithmArg() { result = this.(Call).getArgument(4) }
86-
87-
override Expr getOutputArg() { result = this.(Call).getArgument(2) }
88-
8956
override Expr getInputArg() { result = this.(Call).getArgument(0) }
9057

91-
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
92-
93-
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
94-
}
95-
96-
// NOTE: not modeled as hash operations, these are intermediate calls
97-
class EVP_Digest_Update_Call extends Call {
98-
EVP_Digest_Update_Call() { this.(Call).getTarget().getName() in ["EVP_DigestUpdate"] }
99-
100-
Expr getInputArg() { result = this.(Call).getArgument(1) }
58+
override Expr getOutputArg() { result = this.(Call).getArgument(2) }
10159

102-
DataFlow::Node getInputNode() { result.asExpr() = this.getInputArg() }
60+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
61+
result = EVPOperation.super.getOutputArtifact()
62+
}
10363

104-
Expr getContextArg() { result = this.(Call).getArgument(0) }
64+
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
65+
result = EVPOperation.super.getInputConsumer()
66+
}
10567
}
10668

107-
class EVP_Digest_Final_Call extends EVP_Hash_Operation {
69+
class EVP_Digest_Final_Call extends EVPFinal, Crypto::HashOperationInstance {
10870
EVP_Digest_Final_Call() {
10971
this.(Call).getTarget().getName() in [
11072
"EVP_DigestFinal", "EVP_DigestFinal_ex", "EVP_DigestFinalXOF"
11173
]
11274
}
11375

114-
EVP_Digest_Update_Call getUpdateCalls() {
115-
CTXFlow::ctxArgFlowsToCtxArg(result.getContextArg(), this.getContextArg())
116-
}
117-
118-
override Expr getInputArg() { result = this.getUpdateCalls().getInputArg() }
119-
120-
override Crypto::ConsumerInputDataFlowNode getInputConsumer() { result = this.getInputNode() }
121-
12276
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
12377

124-
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result = this.getOutputNode() }
78+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
79+
result = EVPFinal.super.getOutputArtifact()
80+
}
81+
82+
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
83+
result = EVPFinal.super.getInputConsumer()
84+
}
12585
}

0 commit comments

Comments
 (0)