Skip to content

Commit 0e9d36b

Browse files
authored
Merge pull request #6335 from geoffw0/toctou2
C++: Improvements to the cpp/toctou-race-condition query
2 parents 74f1992 + a4c137f commit 0e9d36b

File tree

5 files changed

+87
-59
lines changed

5 files changed

+87
-59
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Improvements have been made to the `cpp/toctou-race-condition` query, both to find more correct results and fewer false positive results.

cpp/ql/src/Security/CWE/CWE-367/TOCTOUFilesystemRace.ql

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,29 @@ import semmle.code.cpp.controlflow.Guards
2626
*/
2727
FunctionCall filenameOperation(Expr path) {
2828
exists(string name | name = result.getTarget().getName() |
29-
(
30-
name = "remove" or
31-
name = "unlink" or
32-
name = "rmdir" or
33-
name = "rename" or
34-
name = "chmod" or
35-
name = "chown" or
36-
name = "fopen" or
37-
name = "open" or
38-
name = "freopen" or
39-
name = "_open" or
40-
name = "_wopen" or
41-
name = "_wfopen"
42-
) and
29+
name =
30+
[
31+
"remove", "unlink", "rmdir", "rename", "fopen", "open", "freopen", "_open", "_wopen",
32+
"_wfopen", "_fsopen", "_wfsopen"
33+
] and
4334
result.getArgument(0) = path
4435
or
45-
(
46-
name = "fopen_s" or
47-
name = "wfopen_s"
48-
) and
36+
name = ["fopen_s", "wfopen_s", "rename"] and
4937
result.getArgument(1) = path
5038
)
39+
or
40+
result = sensitiveFilenameOperation(path)
41+
}
42+
43+
/**
44+
* An operation on a filename that is likely to modify the security properties
45+
* of the corresponding file and may return an indication of success.
46+
*/
47+
FunctionCall sensitiveFilenameOperation(Expr path) {
48+
exists(string name | name = result.getTarget().getName() |
49+
name = ["chmod", "chown"] and
50+
result.getArgument(0) = path
51+
)
5152
}
5253

5354
/**
@@ -56,11 +57,7 @@ FunctionCall filenameOperation(Expr path) {
5657
*/
5758
FunctionCall accessCheck(Expr path) {
5859
exists(string name | name = result.getTarget().getName() |
59-
name = "access" or
60-
name = "_access" or
61-
name = "_waccess" or
62-
name = "_access_s" or
63-
name = "_waccess_s"
60+
name = ["access", "_access", "_waccess", "_access_s", "_waccess_s"]
6461
) and
6562
path = result.getArgument(0)
6663
}
@@ -72,9 +69,7 @@ FunctionCall accessCheck(Expr path) {
7269
*/
7370
FunctionCall stat(Expr path, Expr buf) {
7471
exists(string name | name = result.getTarget().getName() |
75-
name = "stat" or
76-
name = "lstat" or
77-
name = "fstat" or
72+
name = ["stat", "lstat", "fstat"] or
7873
name.matches("\\_stat%") or
7974
name.matches("\\_wstat%")
8075
) and
@@ -98,29 +93,36 @@ from Expr check, Expr checkPath, FunctionCall use, Expr usePath
9893
where
9994
// `check` looks like a check on a filename
10095
(
101-
// either:
102-
// an access check
103-
check = accessCheck(checkPath)
104-
or
105-
// a stat
106-
check = stat(checkPath, _)
96+
(
97+
// either:
98+
// an access check
99+
check = accessCheck(checkPath)
100+
or
101+
// a stat
102+
check = stat(checkPath, _)
103+
or
104+
// access to a member variable on the stat buf
105+
// (morally, this should be a use-use pair, but it seems unlikely
106+
// that this variable will get reused in practice)
107+
exists(Expr call, Expr e, Variable v |
108+
call = stat(checkPath, e) and
109+
e.getAChild*().(VariableAccess).getTarget() = v and
110+
check.(VariableAccess).getTarget() = v and
111+
not e.getAChild*() = check // the call that writes to the pointer is not where the pointer is checked.
112+
)
113+
) and
114+
// `op` looks like an operation on a filename
115+
use = filenameOperation(usePath)
107116
or
108117
// another filename operation (null pointers can indicate errors)
109-
check = filenameOperation(checkPath)
110-
or
111-
// access to a member variable on the stat buf
112-
// (morally, this should be a use-use pair, but it seems unlikely
113-
// that this variable will get reused in practice)
114-
exists(Variable buf | exists(stat(checkPath, buf.getAnAccess())) |
115-
check.(VariableAccess).getQualifier() = buf.getAnAccess()
116-
)
118+
check = filenameOperation(checkPath) and
119+
// `op` looks like a sensitive operation on a filename
120+
use = sensitiveFilenameOperation(usePath)
117121
) and
118122
// `checkPath` and `usePath` refer to the same SSA variable
119123
exists(SsaDefinition def, StackVariable v |
120124
def.getAUse(v) = checkPath and def.getAUse(v) = usePath
121125
) and
122-
// `op` looks like an operation on a filename
123-
use = filenameOperation(usePath) and
124126
// the return value of `check` is used (possibly with one step of
125127
// variable indirection) in a guard which controls `use`
126128
exists(GuardCondition guard | referenceTo(check, guard.getAChild*()) |
Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
| test2.cpp:39:7:39:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:39:13:39:16 | path | filename | test2.cpp:34:6:34:10 | call to fopen | checked |
2-
| test2.cpp:52:7:52:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:52:13:52:16 | path | filename | test2.cpp:52:7:52:11 | call to fopen | checked |
31
| test2.cpp:69:7:69:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:69:13:69:16 | path | filename | test2.cpp:67:6:67:9 | call to stat | checked |
4-
| test2.cpp:98:7:98:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:98:13:98:16 | path | filename | test2.cpp:96:15:96:17 | foo | checked |
2+
| test2.cpp:83:7:83:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:83:13:83:16 | path | filename | test2.cpp:81:6:81:8 | buf | checked |
3+
| test2.cpp:98:7:98:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:98:13:98:16 | path | filename | test2.cpp:96:6:96:12 | buf_ptr | checked |
4+
| test2.cpp:115:7:115:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:115:13:115:16 | path | filename | test2.cpp:113:22:113:24 | buf | checked |
5+
| test2.cpp:130:7:130:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:130:13:130:16 | path | filename | test2.cpp:128:21:128:27 | buf_ptr | checked |
56
| test2.cpp:157:7:157:10 | call to open | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:157:12:157:15 | path | filename | test2.cpp:155:6:155:9 | call to stat | checked |
67
| test2.cpp:170:7:170:10 | call to open | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:170:12:170:15 | path | filename | test2.cpp:168:6:168:10 | call to lstat | checked |
78
| test2.cpp:245:3:245:7 | call to chmod | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:245:9:245:12 | path | filename | test2.cpp:238:6:238:10 | call to fopen | checked |
8-
| test2.cpp:255:3:255:8 | call to remove | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:255:10:255:14 | path1 | filename | test2.cpp:253:6:253:11 | call to rename | checked |
99
| test2.cpp:277:7:277:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:277:13:277:16 | path | filename | test2.cpp:275:6:275:11 | call to access | checked |
1010
| test2.cpp:303:7:303:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:303:13:303:16 | path | filename | test2.cpp:301:7:301:12 | call to access | checked |
1111
| test2.cpp:317:7:317:11 | call to fopen | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:317:13:317:16 | path | filename | test2.cpp:313:6:313:11 | call to access | checked |
12-
| test.cpp:21:3:21:8 | call to remove | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test.cpp:21:10:21:14 | file1 | filename | test.cpp:19:7:19:12 | call to rename | checked |
13-
| test.cpp:35:3:35:8 | call to remove | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test.cpp:35:10:35:14 | file1 | filename | test.cpp:32:7:32:12 | call to rename | checked |
14-
| test.cpp:49:3:49:8 | call to remove | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test.cpp:49:10:49:14 | file1 | filename | test.cpp:47:7:47:12 | call to rename | checked |
12+
| test2.cpp:348:3:348:7 | call to chmod | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:348:9:348:12 | path | filename | test2.cpp:341:6:341:10 | call to fopen | checked |
13+
| test2.cpp:356:3:356:7 | call to chmod | The $@ being operated upon was previously $@, but the underlying file may have been changed since then. | test2.cpp:356:9:356:13 | path2 | filename | test2.cpp:354:7:354:12 | call to rename | checked |

cpp/ql/test/query-tests/Security/CWE/CWE-367/semmle/test.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void test1()
1818
create(file1);
1919
if (!rename(file1, file2))
2020
{
21-
remove(file1); // DUBIOUS (bad but perhaps not exploitable) [REPORTED]
21+
remove(file1); // DUBIOUS (bad but perhaps not exploitable)
2222
}
2323
}
2424

@@ -32,7 +32,7 @@ void test2()
3232
if (!rename(file1, file2))
3333
{
3434
file1.set("d.txt");
35-
remove(file1); // GOOD [FALSE POSITIVE]
35+
remove(file1); // GOOD
3636
}
3737
}
3838

@@ -46,6 +46,6 @@ void test3()
4646
create(file1);
4747
if (!rename(file1, file2))
4848
{
49-
remove(file1); // DUBIOUS (bad but perhaps not exploitable) [REPORTED]
49+
remove(file1); // DUBIOUS (bad but perhaps not exploitable)
5050
}
5151
}

cpp/ql/test/query-tests/Security/CWE/CWE-367/semmle/test2.cpp

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ void test1_1(const char *path)
3636
if (f == NULL)
3737
{
3838
// retry
39-
f = fopen(path, "r"); // GOOD (this is just trying again) [FALSE POSITIVE]
39+
f = fopen(path, "r"); // GOOD (this is just trying again)
4040
}
4141

4242
// ...
@@ -49,7 +49,7 @@ void test1_2(const char *path)
4949
// try until we succeed
5050
while (f == NULL)
5151
{
52-
f = fopen(path, "r"); // GOOD (this is just trying again) [FALSE POSITIVE]
52+
f = fopen(path, "r"); // GOOD (this is just trying again)
5353

5454
// ...
5555
}
@@ -80,7 +80,7 @@ void test2_2(const char *path)
8080
stat(path, &buf);
8181
if (buf.foo > 0)
8282
{
83-
f = fopen(path, "r"); // BAD [NOT DETECTED]
83+
f = fopen(path, "r"); // BAD
8484
}
8585

8686
// ...
@@ -112,7 +112,7 @@ void test2_4(const char *path)
112112
stat(path, &buf);
113113
if (stat_condition(&buf))
114114
{
115-
f = fopen(path, "r"); // BAD [NOT DETECTED]
115+
f = fopen(path, "r"); // BAD
116116
}
117117

118118
// ...
@@ -127,7 +127,7 @@ void test2_5(const char *path)
127127
stat(path, buf_ptr);
128128
if (stat_condition(buf_ptr))
129129
{
130-
f = fopen(path, "r"); // BAD [NOT DETECTED]
130+
f = fopen(path, "r"); // BAD
131131
}
132132

133133
// ...
@@ -242,7 +242,7 @@ void test4_1(const char *path)
242242

243243
fclose(f);
244244

245-
chmod(path, 0); // DUBIOUS (bad but perhaps not exploitable) [REPORTED]
245+
chmod(path, 0); // BAD
246246
}
247247
}
248248

@@ -252,7 +252,7 @@ void test5_1(const char *path1, const char *path2)
252252
{
253253
if (rename(path1, path2))
254254
{
255-
remove(path1); // DUBIOUS (bad but perhaps not exploitable) [REPORTED]
255+
remove(path1); // DUBIOUS (bad but perhaps not exploitable)
256256
}
257257
}
258258

@@ -331,3 +331,28 @@ void test6_5(const char *path1, const char *path2)
331331
// ...
332332
}
333333
}
334+
335+
// --- open / rename -> chmod ---
336+
337+
void test7_1(const char *path)
338+
{
339+
FILE *f;
340+
341+
f = fopen(path, "wt");
342+
if (f != 0)
343+
{
344+
// ...
345+
346+
fclose(f);
347+
348+
chmod(path, 1234); // BAD
349+
}
350+
}
351+
352+
void test7_1(const char *path1, const char *path2)
353+
{
354+
if (!rename(path1, path2))
355+
{
356+
chmod(path2, 1234); // BAD
357+
}
358+
}

0 commit comments

Comments
 (0)