-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
TOCTOUFilesystemRace.ql
171 lines (157 loc) · 5.52 KB
/
TOCTOUFilesystemRace.ql
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* @name Time-of-check time-of-use filesystem race condition
* @description Separately checking the state of a file before operating
* on it may allow an attacker to modify the file between
* the two operations.
* @kind problem
* @problem.severity warning
* @security-severity 7.7
* @precision high
* @id cpp/toctou-race-condition
* @tags security
* external/cwe/cwe-367
*/
import cpp
import semmle.code.cpp.controlflow.Guards
/**
* An operation on a filename that is likely to modify the corresponding file
* and may return an indication of success.
*
* Note: we're not interested in operations where the file is specified by a
* descriptor, rather than a filename, as they are better behaved. We are
* interested in functions that take a filename and return a file descriptor,
* however.
*/
FunctionCall filenameOperation(Expr path) {
exists(string name | name = result.getTarget().getName() |
name =
[
"remove", "unlink", "rmdir", "rename", "fopen", "open", "freopen", "_open", "_wopen",
"_wfopen", "_fsopen", "_wfsopen"
] and
result.getArgument(0) = path
or
name = ["fopen_s", "wfopen_s", "rename"] and
result.getArgument(1) = path
)
or
result = sensitiveFilenameOperation(path)
}
/**
* An operation on a filename that is likely to modify the security properties
* of the corresponding file and may return an indication of success.
*/
FunctionCall sensitiveFilenameOperation(Expr path) {
exists(string name | name = result.getTarget().getName() |
name = ["chmod", "chown"] and
result.getArgument(0) = path
)
}
/**
* An operation on a filename that returns information in the return value but
* does not modify the corresponding file. For example, `access`.
*/
FunctionCall accessCheck(Expr path) {
exists(string name | name = result.getTarget().getName() |
name = ["access", "_access", "_waccess", "_access_s", "_waccess_s"]
) and
path = result.getArgument(0)
}
/**
* An operation on a filename that returns information via a pointer argument
* and any return value, but does not modify the corresponding file. For
* example, `stat`.
*/
FunctionCall stat(Expr path, Expr buf) {
exists(string name | name = result.getTarget().getName() |
name = ["stat", "lstat", "fstat"] or
name.matches("\\_stat%") or
name.matches("\\_wstat%")
) and
path = result.getArgument(0) and
buf = result.getArgument(1)
}
/**
* Holds if `use` refers to `source`, either by being the same or by
* one step of variable indirection.
*/
predicate referenceTo(Expr source, Expr use) {
source = use
or
exists(SsaDefinition def, StackVariable v |
def.getAnUltimateDefiningValue(v) = source and def.getAUse(v) = use
)
}
pragma[noinline]
predicate statCallWithPointer(Expr checkPath, Expr call, Expr e, Variable v) {
call = stat(checkPath, e) and
e.getAChild*().(VariableAccess).getTarget() = v
}
predicate checksPath(Expr check, Expr checkPath) {
// either:
// an access check
check = accessCheck(checkPath)
or
// a stat
check = stat(checkPath, _)
or
// access to a member variable on the stat buf
// (morally, this should be a use-use pair, but it seems unlikely
// that this variable will get reused in practice)
exists(Expr e, Variable v |
statCallWithPointer(checkPath, _, e, v) and
check.(VariableAccess).getTarget() = v and
not e.getAChild*() = check // the call that writes to the pointer is not where the pointer is checked.
)
}
pragma[nomagic]
predicate checkPathControlsUse(Expr check, Expr checkPath, Expr use) {
exists(GuardCondition guard | referenceTo(check, guard.getAChild*()) |
guard.controls(use.getBasicBlock(), _)
) and
checksPath(pragma[only_bind_into](check), checkPath)
}
pragma[nomagic]
predicate fileNameOperationControlsUse(Expr check, Expr checkPath, Expr use) {
exists(GuardCondition guard | referenceTo(check, guard.getAChild*()) |
guard.controls(use.getBasicBlock(), _)
) and
pragma[only_bind_into](check) = filenameOperation(checkPath)
}
predicate checkUse(Expr check, Expr checkPath, FunctionCall use, Expr usePath) {
// `check` is part of a guard that controls `use`
checkPathControlsUse(check, checkPath, use) and
// `check` looks like a check on a filename
checksPath(check, checkPath) and
// `op` looks like an operation on a filename
use = filenameOperation(usePath)
or
// `check` is part of a guard that controls `use`
fileNameOperationControlsUse(check, checkPath, use) and
// another filename operation (null pointers can indicate errors)
check = filenameOperation(checkPath) and
// `op` looks like a sensitive operation on a filename
use = sensitiveFilenameOperation(usePath)
}
pragma[noinline]
predicate isCheckedPath(
Expr check, SsaDefinition def, StackVariable v, FunctionCall use, Expr usePath, Expr checkPath
) {
checkUse(check, checkPath, use, usePath) and
def.getAUse(v) = checkPath
}
pragma[noinline]
predicate isUsedPath(
Expr check, SsaDefinition def, StackVariable v, FunctionCall use, Expr usePath, Expr checkPath
) {
checkUse(check, checkPath, use, usePath) and
def.getAUse(v) = usePath
}
from Expr check, Expr checkPath, FunctionCall use, Expr usePath, SsaDefinition def, StackVariable v
where
// `checkPath` and `usePath` refer to the same SSA variable
isCheckedPath(check, def, v, use, usePath, checkPath) and
isUsedPath(check, def, v, use, usePath, checkPath)
select use,
"The $@ being operated upon was previously $@, but the underlying file may have been changed since then.",
usePath, "filename", check, "checked"