Skip to content

Commit d9b8058

Browse files
authored
Merge pull request #31 from trailofbits/alan/enhance-crypto-queries
Improve and extend C++ openssl crypto queries
2 parents ae97761 + d919310 commit d9b8058

File tree

11 files changed

+312
-25
lines changed

11 files changed

+312
-25
lines changed

cpp/lib/trailofbits/crypto/openssl/bn.qll

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import trailofbits.crypto.common
44
// BIGNUM *BN_new(void);
55
class BN_new extends CustomAllocator {
66
BN_new() {
7-
this.getQualifiedName() = "BN_new" and (
7+
this.getQualifiedName() = "BN_new" and
8+
(
89
dealloc instanceof BN_free or
910
dealloc instanceof BN_clear_free
1011
)
@@ -14,7 +15,8 @@ class BN_new extends CustomAllocator {
1415
// BIGNUM *BN_secure_new(void);
1516
class BN_secure_new extends CustomAllocator {
1617
BN_secure_new() {
17-
this.getQualifiedName() = "BN_secure_new" and (
18+
this.getQualifiedName() = "BN_secure_new" and
19+
(
1820
dealloc instanceof BN_free or
1921
dealloc instanceof BN_clear_free
2022
)
@@ -23,24 +25,16 @@ class BN_secure_new extends CustomAllocator {
2325

2426
// void BN_free(BIGNUM *a);
2527
class BN_free extends CustomDeallocator {
26-
BN_free() {
27-
this.getQualifiedName() = "BN_free"
28-
}
28+
BN_free() { this.getQualifiedName() = "BN_free" }
2929

30-
override int getPointer() {
31-
result = 0
32-
}
30+
override int getPointer() { result = 0 }
3331
}
3432

3533
// void BN_clear_free(BIGNUM *a);
3634
class BN_clear_free extends CustomDeallocator {
37-
BN_clear_free() {
38-
this.getQualifiedName() = "BN_clear_free"
39-
}
35+
BN_clear_free() { this.getQualifiedName() = "BN_clear_free" }
4036

41-
override int getPointer() {
42-
result = 0
43-
}
37+
override int getPointer() { result = 0 }
4438
}
4539

4640
// void BN_clear(BIGNUM *a);
@@ -50,18 +44,63 @@ class BN_clear extends FunctionCall {
5044
Expr getBignum() { result = this.getArgument(0) }
5145
}
5246

53-
// int BN_rand(BIGNUM *rnd, int bits, int top, int bottom);
47+
// int BN_rand(BIGNUM *rnd, int bits, int top, int bottom); (and variants)
48+
/// Reference: https://docs.openssl.org/master/man3/BN_rand/#synopsis
5449
class BN_rand extends FunctionCall {
55-
BN_rand() { this.getTarget().getName() = "BN_rand" }
56-
57-
Expr getBignum() {
58-
result = this.getArgument(0)
50+
BN_rand() {
51+
this.getTarget().getName().matches("BN\\_rand%") or
52+
this.getTarget().getName().matches("BN\\_priv\\_rand%") or
53+
this.getTarget().getName().matches("BN\\_pseudo\\_rand%")
5954
}
55+
56+
Expr getBignum() { result = this.getArgument(0) }
6057
}
6158

6259
class BIGNUM extends FunctionCall {
63-
BIGNUM () {
60+
BIGNUM() {
6461
this.getTarget() instanceof BN_new or
6562
this.getTarget() instanceof BN_secure_new
6663
}
6764
}
65+
66+
// BN_CTX *BN_CTX_new(void);
67+
class BN_CTX_new extends CustomAllocator {
68+
BN_CTX_new() {
69+
this.getQualifiedName().matches("BN\\_CTX_new%")
70+
or
71+
this.getQualifiedName().matches("BN\\_CTX\\_secure\\_new%") and
72+
dealloc instanceof BN_CTX_free
73+
}
74+
}
75+
76+
// void BN_CTX_free(BN_CTX *c);
77+
class BN_CTX_free extends CustomDeallocator {
78+
BN_CTX_free() { this.getQualifiedName() = "BN_CTX_free" }
79+
80+
override int getPointer() { result = 0 }
81+
}
82+
83+
// void BN_CTX_start(BN_CTX *ctx);
84+
class BN_CTX_start extends FunctionCall {
85+
BN_CTX_start() { this.getTarget().getName() = "BN_CTX_start" }
86+
87+
Expr getContext() { result = this.getArgument(0) }
88+
}
89+
90+
// void BN_CTX_end(BN_CTX *ctx);
91+
class BN_CTX_end extends FunctionCall {
92+
BN_CTX_end() { this.getTarget().getName() = "BN_CTX_end" }
93+
94+
Expr getContext() { result = this.getArgument(0) }
95+
}
96+
97+
// BIGNUM *BN_CTX_get(BN_CTX *ctx);
98+
class BN_CTX_get extends FunctionCall {
99+
BN_CTX_get() { this.getTarget().getName() = "BN_CTX_get" }
100+
101+
Expr getContext() { result = this.getArgument(0) }
102+
}
103+
104+
class BN_CTX extends FunctionCall {
105+
BN_CTX() { this.getTarget() instanceof BN_CTX_new }
106+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @name BN_CTX_free called before BN_CTX_end
3+
* @id tob/cpp/bn-ctx-free-before-end
4+
* @description Detects BN_CTX_free called before BN_CTX_end, which violates the required lifecycle
5+
* @kind path-problem
6+
* @tags correctness crypto
7+
* @problem.severity error
8+
* @precision medium
9+
* @group cryptography
10+
*/
11+
12+
import cpp
13+
import trailofbits.crypto.libraries
14+
import semmle.code.cpp.dataflow.new.DataFlow
15+
import semmle.code.cpp.controlflow.ControlFlowGraph
16+
17+
/**
18+
* Tracks BN_CTX from BN_CTX_start to BN_CTX_free without going through BN_CTX_end
19+
*/
20+
module BnCtxFreeBeforeEndConfig implements DataFlow::ConfigSig {
21+
predicate isSource(DataFlow::Node source) {
22+
exists(BN_CTX_start start | source.asExpr() = start.getContext())
23+
}
24+
25+
predicate isSink(DataFlow::Node sink) {
26+
exists(CustomDeallocatorCall free |
27+
free.getTarget() instanceof BN_CTX_free and
28+
sink.asExpr() = free.getPointer()
29+
)
30+
}
31+
32+
predicate isBarrier(DataFlow::Node node) {
33+
// If the context flows through BN_CTX_end, it's properly handled
34+
exists(BN_CTX_end end | node.asExpr() = end.getContext())
35+
}
36+
}
37+
38+
module BnCtxFreeBeforeEndFlow = DataFlow::Global<BnCtxFreeBeforeEndConfig>;
39+
40+
import BnCtxFreeBeforeEndFlow::PathGraph
41+
42+
from
43+
BnCtxFreeBeforeEndFlow::PathNode source, BnCtxFreeBeforeEndFlow::PathNode sink,
44+
BN_CTX_start start, CustomDeallocatorCall free
45+
where
46+
BnCtxFreeBeforeEndFlow::flowPath(source, sink) and
47+
source.getNode().asExpr() = start.getContext() and
48+
sink.getNode().asExpr() = free.getPointer() and
49+
free.getTarget() instanceof BN_CTX_free
50+
select free, source, sink,
51+
"BN_CTX_free called at line " + free.getLocation().getStartLine().toString() +
52+
" before BN_CTX_end after BN_CTX_start at line " + start.getLocation().getStartLine().toString()

cpp/src/crypto/MissingZeroization.ql

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,24 @@ import cpp
1313
import trailofbits.crypto.libraries
1414
import semmle.code.cpp.dataflow.new.DataFlow
1515

16-
// TODO: Handle `BN_clear_free` as well.
1716
predicate isCleared(Expr bignum) {
1817
exists(BN_clear clear |
19-
DataFlow::localFlow(DataFlow::exprNode(bignum), DataFlow::exprNode(clear.getArgument(0)))
18+
DataFlow::localFlow(DataFlow::exprNode(bignum), DataFlow::exprNode(clear.getBignum()))
19+
)
20+
or
21+
exists(CustomDeallocatorCall clear_free |
22+
clear_free.getTarget() instanceof BN_clear_free and
23+
DataFlow::localFlow(DataFlow::exprNode(bignum), DataFlow::exprNode(clear_free.getPointer()))
2024
)
2125
}
2226

23-
// TODO: Add support for remaining OpenSSL PRNG functions.
2427
predicate isRandom(Expr bignum) {
2528
exists(BN_rand rand |
26-
DataFlow::localFlow(DataFlow::exprNode(bignum), DataFlow::exprNode(rand.getArgument(0)))
29+
DataFlow::localFlow(DataFlow::exprNode(bignum), DataFlow::exprNode(rand.getBignum()))
2730
)
2831
}
2932

3033
from BIGNUM bignum
3134
where isRandom(bignum) and not isCleared(bignum)
32-
select bignum.getLocation(), "Bignum is initialized with random data but is not zeroized before it goes out of scope"
35+
select bignum.getLocation(),
36+
"Bignum is initialized with random data but is not zeroized before it goes out of scope"

cpp/src/crypto/UnbalancedBnCtx.ql

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @name Unbalanced BN_CTX_start and BN_CTX_end pair
3+
* @id tob/cpp/missing-bn-ctx-end
4+
* @description Detects if one call in the BN_CTX_start/BN_CTX_end pair is missing
5+
* @kind problem
6+
* @tags correctness crypto
7+
* @problem.severity warning
8+
* @precision medium
9+
* @group cryptography
10+
*/
11+
12+
import cpp
13+
import trailofbits.crypto.libraries
14+
import semmle.code.cpp.dataflow.new.DataFlow
15+
import semmle.code.cpp.controlflow.Guards
16+
17+
predicate hasMatchingEnd(BN_CTX_start start) {
18+
exists(BN_CTX_end end, Function f |
19+
start.getEnclosingFunction() = f and
20+
end.getEnclosingFunction() = f and
21+
DataFlow::localFlow(DataFlow::exprNode(start.getContext()), DataFlow::exprNode(end.getContext()))
22+
)
23+
}
24+
25+
predicate hasMatchingStart(BN_CTX_end end) {
26+
exists(BN_CTX_start start, Function f |
27+
end.getEnclosingFunction() = f and
28+
start.getEnclosingFunction() = f and
29+
DataFlow::localFlow(DataFlow::exprNode(start.getContext()), DataFlow::exprNode(end.getContext()))
30+
)
31+
}
32+
33+
from FunctionCall call, string message
34+
where
35+
(
36+
call instanceof BN_CTX_start and
37+
not hasMatchingEnd(call) and
38+
message = "BN_CTX_start called without corresponding BN_CTX_end"
39+
) or
40+
(
41+
call instanceof BN_CTX_end and
42+
not hasMatchingStart(call) and
43+
message = "BN_CTX_end called without corresponding BN_CTX_start"
44+
)
45+
select call.getLocation(), message

cpp/test/include/openssl/bn.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,32 @@ int BN_rand(BIGNUM *rnd, int bits, int top, int bottom) {
3232
return 1;
3333
}
3434

35+
// BN_CTX functions
36+
#define BN_CTX int
37+
38+
BN_CTX *BN_CTX_new(void) {
39+
static BN_CTX ctx = 1;
40+
return &ctx;
41+
}
42+
43+
BN_CTX *BN_CTX_secure_new(void) {
44+
static BN_CTX ctx = 1;
45+
return &ctx;
46+
}
47+
48+
void BN_CTX_free(BN_CTX *c) {
49+
}
50+
51+
void BN_CTX_start(BN_CTX *ctx) {
52+
}
53+
54+
void BN_CTX_end(BN_CTX *ctx) {
55+
}
56+
57+
BIGNUM *BN_CTX_get(BN_CTX *ctx) {
58+
return &BN;
59+
}
60+
3561
# ifdef __cplusplus
3662
}
3763
#endif
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
edges
2+
| test.c:13:16:13:18 | ctx | test.c:15:15:15:17 | ctx | provenance | |
3+
| test.c:34:16:34:18 | ctx | test.c:40:15:40:17 | ctx | provenance | |
4+
nodes
5+
| test.c:13:16:13:18 | ctx | semmle.label | ctx |
6+
| test.c:15:15:15:17 | ctx | semmle.label | ctx |
7+
| test.c:34:16:34:18 | ctx | semmle.label | ctx |
8+
| test.c:40:15:40:17 | ctx | semmle.label | ctx |
9+
subpaths
10+
#select
11+
| test.c:15:3:15:13 | call to BN_CTX_free | test.c:13:16:13:18 | ctx | test.c:15:15:15:17 | ctx | BN_CTX_free called at line 15 before BN_CTX_end after BN_CTX_start at line 13 |
12+
| test.c:40:3:40:13 | call to BN_CTX_free | test.c:34:16:34:18 | ctx | test.c:40:15:40:17 | ctx | BN_CTX_free called at line 40 before BN_CTX_end after BN_CTX_start at line 34 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
crypto/BnCtxFreeBeforeEnd.ql
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include "../../../include/openssl/bn.h"
2+
3+
void good_proper_lifecycle() {
4+
BN_CTX *ctx = BN_CTX_new();
5+
BN_CTX_start(ctx);
6+
BIGNUM *a = BN_CTX_get(ctx);
7+
BN_CTX_end(ctx); // Properly call end before free
8+
BN_CTX_free(ctx);
9+
}
10+
11+
void bad_free_before_end() {
12+
BN_CTX *ctx = BN_CTX_new();
13+
BN_CTX_start(ctx);
14+
BIGNUM *a = BN_CTX_get(ctx);
15+
BN_CTX_free(ctx); // BUG: free before end
16+
}
17+
18+
void good_mult_ctx() {
19+
BN_CTX *ctx = BN_CTX_new();
20+
21+
BN_CTX_start(ctx);
22+
BIGNUM *a = BN_CTX_get(ctx);
23+
BN_CTX_end(ctx);
24+
25+
BN_CTX_start(ctx);
26+
BIGNUM *b = BN_CTX_get(ctx);
27+
BN_CTX_end(ctx);
28+
29+
BN_CTX_free(ctx);
30+
}
31+
32+
void bad_conditional_end(int condition) {
33+
BN_CTX *ctx = BN_CTX_new();
34+
BN_CTX_start(ctx);
35+
BIGNUM *a = BN_CTX_get(ctx);
36+
37+
if (condition) {
38+
BN_CTX_end(ctx);
39+
}
40+
BN_CTX_free(ctx); // BUG: May free before end if condition is false
41+
}
42+
43+
int main(void) {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| test.c:13:3:13:14 | test.c:13:3:13:14 | BN_CTX_start called without corresponding BN_CTX_end |
2+
| test.c:21:3:21:12 | test.c:21:3:21:12 | BN_CTX_end called without corresponding BN_CTX_start |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
crypto/UnbalancedBnCtx.ql

0 commit comments

Comments
 (0)