CodeQL之CWE-401(3)

在第一部分中,我们将CWE-401简单分为两类,第一类是对于分配的内存,不存在路径对它进行了释放,第二类是有的路径释放了,而有的路径忘了释放,现在我们对第二类进行解决

第二类示例如下

1
2
3
4
5
6
7
8
9
int* f() {
try {
int *buff = malloc(SIZE*sizeof(int));
do_stuff(buff);
return buff;
} catch (int do_stuff_exception) {
return NULL; //returns NULL on error, but does not free memory
}
}

总体思路#

还是从RSA_newRSA_free的示例出发,按照上篇博客中的MemoryMayNotBeFreed画瓢就行,有所区别的是,在这里想将它扩展成函数未配对使用的情况,即malloc却没有freefopen却没有fclose

STEP1 定义基本函数#

首先写上对配对函数名称检查的函数

1
2
3
predicate checkNameOpen(Function func) { func.getName().matches("RSA_new") }

predicate checkNameClose(Function func) {func.getName().matches("RSA_free")}

借鉴样板中的直接调用和间接调用(大部分应该都不会选择用间接调用,但是在linux等项目中十分普遍),我们直接抄录直接调用和间接调用判断如下:

1
2
3
4
5
6
7
8
9
/**
* 'call' is either a direct call to f, or a possible call to f
* via a function pointer.
*/
predicate mayCallFunction(Expr call, Function f) {
call.(FunctionCall).getTarget() = f or
call.(VariableCall).getVariable().getAnAssignedValue().getAChild*().(FunctionAccess).getTarget() =
f
}

同样,样版中的赋值给全局变量或者类的域内变量也值得我们直接抄写一番

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
predicate assignedToFieldOrGlobal(StackVariable v, Expr e) {
// assigned to anything except a StackVariable
// (typically a field or global, but for example also *ptr = v)
e.(Assignment).getRValue() = v.getAnAccess() and
not e.(Assignment).getLValue().(VariableAccess).getTarget() instanceof StackVariable
or
exists(Expr midExpr, Function mid, int arg |
// indirect assignment
e.(FunctionCall).getArgument(arg) = v.getAnAccess() and
mayCallFunction(e, mid) and
midExpr.getEnclosingFunction() = mid and
assignedToFieldOrGlobal(mid.getParameter(arg), midExpr)
)
or
// assigned to a field via constructor field initializer
e.(ConstructorFieldInit).getExpr() = v.getAnAccess()
}

STEP2 定义匹配函数调用#

在这一步我们还是为后面做铺垫,首先需要我们定义是否调用了匹配函数,用于source、sink和barrier点的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
predicate openCall(Expr e) {
// direct alloc call
checkNameOpen(e.(FunctionCall).getTarget())
// We are only interested in alloc calls that are
// actually freed somehow, as MemoryNeverFreed
// will catch those that aren't.
or
exists(ReturnStmt rtn |
// indirect alloc call
mayCallFunction(e, rtn.getEnclosingFunction()) and
(
// return alloc
openCall(rtn.getExpr())
or
// return variable assigned with alloc
exists(Variable v |
v = rtn.getExpr().(VariableAccess).getTarget() and
openCall(v.getAnAssignedValue()) and
not assignedToFieldOrGlobal(v, _)
)
)
)
}

openCall是判断表达式是不是调用了Open类的函数,或者说它调用了函数func,函数func里调用了Open类的函数,然后将返回值传递了出来。

1
2
3
4
5
6
7
8
9
10
11
12
predicate closeCall(ControlFlowNode n, Variable v) {
exists(int arg | n.(Call).getArgument(arg) = v.getAnAccess() and
checkNameClose(n.(Call).getTarget()))
or
exists(FunctionCall midcall, Function mid, int arg |
// indirect free call
n.(Call).getArgument(arg) = v.getAnAccess() and
mayCallFunction(n, mid) and
midcall.getEnclosingFunction() = mid and
closeCall(midcall, mid.getParameter(arg))
)
}

closeCall判断是否调用了Close类函数,或者调用了函数func,传入了参数,并在func里对该参数调用了Close类函数

由于我们现在只考虑函数失去配对的情况,所以不考虑样本中realloc的一类情况。

STEP3 套套套#

对于其他的部分,我们只要修改名称为自己喜欢的就行啦,直接套用即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import cpp
import semmle.code.cpp.controlflow.Guards
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.controlflow.StackVariableReachability

predicate checkNameOpen(Function func) { func.getName().matches("RSA_new") }

predicate checkNameClose(Function func) { func.getName().matches("RSA_free") }

/**
* 'call' is either a direct call to f, or a possible call to f
* via a function pointer.
*/
predicate mayCallFunction(Expr call, Function f) {
call.(FunctionCall).getTarget() = f or
call.(VariableCall).getVariable().getAnAssignedValue().getAChild*().(FunctionAccess).getTarget() =
f
}

predicate assignedToFieldOrGlobal(StackVariable v, Expr e) {
// assigned to anything except a StackVariable
// (typically a field or global, but for example also *ptr = v)
e.(Assignment).getRValue() = v.getAnAccess() and
not e.(Assignment).getLValue().(VariableAccess).getTarget() instanceof StackVariable
or
exists(Expr midExpr, Function mid, int arg |
// indirect assignment
e.(FunctionCall).getArgument(arg) = v.getAnAccess() and
mayCallFunction(e, mid) and
midExpr.getEnclosingFunction() = mid and
assignedToFieldOrGlobal(mid.getParameter(arg), midExpr)
)
or
// assigned to a field via constructor field initializer
e.(ConstructorFieldInit).getExpr() = v.getAnAccess()
}

predicate openCall(Expr e) {
// direct alloc call
checkNameOpen(e.(FunctionCall).getTarget())
or
// We are only interested in alloc calls that are
// actually freed somehow, as MemoryNeverFreed
// will catch those that aren't.
exists(ReturnStmt rtn |
// indirect alloc call
mayCallFunction(e, rtn.getEnclosingFunction()) and
(
// return alloc
openCall(rtn.getExpr())
or
// return variable assigned with alloc
exists(Variable v |
v = rtn.getExpr().(VariableAccess).getTarget() and
openCall(v.getAnAssignedValue()) and
not assignedToFieldOrGlobal(v, _)
)
)
)
}

predicate closeCall(ControlFlowNode n, Variable v) {
exists(int arg | n.(Call).getArgument(arg) = v.getAnAccess() and
checkNameClose(n.(Call).getTarget()))
or
exists(FunctionCall midcall, Function mid, int arg |
// indirect free call
n.(Call).getArgument(arg) = v.getAnAccess() and
mayCallFunction(n, mid) and
midcall.getEnclosingFunction() = mid and
closeCall(midcall, mid.getParameter(arg))
)
}

predicate openDefinition(StackVariable v, ControlFlowNode def) {
exists(Expr expr | exprDefinition(v, def, expr) and openCall(expr))
}


class OpenVariableReachability extends StackVariableReachabilityWithReassignment {
OpenVariableReachability() { this = "OpenVariableReachability" }

override predicate isSourceActual(ControlFlowNode node, StackVariable v) {
openDefinition(v, node)
}

override predicate isSinkActual(ControlFlowNode node, StackVariable v) {
// node may be used in allocationReaches
exists(node.(AnalysedExpr).getNullSuccessor(v)) or
closeCall(node, v) or
assignedToFieldOrGlobal(v, node) or
// node may be used directly in query
v.getFunction() = node.(ReturnStmt).getEnclosingFunction()
}

override predicate isBarrier(ControlFlowNode node, StackVariable v) { definitionBarrier(v, node) }
}

/**
* The value from allocation `def` is still held in Variable `v` upon entering `node`.
*/
predicate openVariableReaches(StackVariable v, ControlFlowNode def, ControlFlowNode node) {
exists(OpenVariableReachability r |
// reachability
r.reachesTo(def, _, node, v)
or
// accept def node itself
r.isSource(def, v) and
node = def
)
}

class OpenReachability extends StackVariableReachabilityExt {
OpenReachability() { this = "OpenReachability" }

override predicate isSource(ControlFlowNode node, StackVariable v) {
openDefinition(v, node)
}

override predicate isSink(ControlFlowNode node, StackVariable v) {
v.getFunction() = node.(ReturnStmt).getEnclosingFunction()
}

override predicate isBarrier(
ControlFlowNode source, ControlFlowNode node, ControlFlowNode next, StackVariable v
) {
isSource(source, v) and
next = node.getASuccessor() and
// the memory (stored in any variable `v0`) allocated at `source` is freed or
// assigned to a global at node, or NULL checked on the edge node -> next.
exists(StackVariable v0 | openVariableReaches(v0, source, node) |
node.(AnalysedExpr).getNullSuccessor(v0) = next or
closeCall(node, v0) or
assignedToFieldOrGlobal(v0, node)
)
}
}

predicate openReaches(ControlFlowNode def, ControlFlowNode node) {
exists(OpenReachability r | r.reaches(def, _, node))
}

from ControlFlowNode def, ReturnStmt ret
where
openReaches(def, ret) and
not exists(StackVariable v |
openVariableReaches(v, def, ret) and
ret.getAChild*() = v.getAnAccess()
)
select def, ret,def.getLocation()

STEP4 查询结果#

最后的结果如下

image-20201116190204544

可以看到找到了两个结果,当然这两个结果是在一个函数内,只是返回路径不同,所以报了两个结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static EVP_PKEY *get_test_pkey(void)
{
static unsigned char n[] =
"\x00\xAA\x36\xAB\xCE\x88\xAC\xFD\xFF\x55\x52\x3C\x7F\xC4\x52\x3F"
"\x90\xEF\xA0\x0D\xF3\x77\x4A\x25\x9F\x2E\x62\xB4\xC5\xD9\x9C\xB5"
"\xAD\xB3\x00\xA0\x28\x5E\x53\x01\x93\x0E\x0C\x70\xFB\x68\x76\x93"
"\x9C\xE6\x16\xCE\x62\x4A\x11\xE0\x08\x6D\x34\x1E\xBC\xAC\xA0\xA1"
"\xF5";
static unsigned char e[] = "\x11";

RSA *rsa = RSA_new();
EVP_PKEY *pk = EVP_PKEY_new();

if (rsa == NULL || pk == NULL || !EVP_PKEY_assign_RSA(pk, rsa)) {
RSA_free(rsa);
EVP_PKEY_free(pk);
return NULL;
}

if (!RSA_set0_key(rsa, BN_bin2bn(n, sizeof(n)-1, NULL),
BN_bin2bn(e, sizeof(e)-1, NULL), NULL)) {
EVP_PKEY_free(pk);
return NULL;
}

return pk;
}

可以看到在第23行和第26行的return语句前,都没有对rsa进行释放,不过这同样还是在测试的代码里,没有什么价值(但是不妨碍我们提交issue,手动狗头)

https://github.com/openssl/openssl/issues/13420,很开心得到了开发者的回应,但是很明显傻逼的我没有看到函数EVP_PKEY_assign_RSA其实是把rsa赋值给了pk,所以在freepk的时候,其实也是释放了rsa的空间(提之前我还用valgrind测了没啥问题,我还以为是valgrind出错了,md)。

总结#

通过上面的惨痛经历,可以看到虽然确实存在函数失配的问题,但对于上面的误报,脚本目前还不能解决,因此还需要更近一步的改进。

评论