Skip to content

Commit e9b96ad

Browse files
committed
C++: Exclude results formatted with a character other than %s.
1 parent f8fed26 commit e9b96ad

File tree

4 files changed

+38
-9
lines changed

4 files changed

+38
-9
lines changed

cpp/ql/src/Security/CWE/CWE-311/CleartextFileWrite.ql

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,29 @@ predicate filenameOperation(FunctionCall op, Expr path) {
2525
name =
2626
[
2727
"remove", "unlink", "rmdir", "rename", "fopen", "open", "freopen", "_open", "_wopen",
28-
"_wfopen", "_fsopen", "_wfsopen", "chmod", "chown", "stat", "lstat", "fstat", "access", "_access", "_waccess", "_access_s", "_waccess_s"
28+
"_wfopen", "_fsopen", "_wfsopen", "chmod", "chown", "stat", "lstat", "fstat", "access",
29+
"_access", "_waccess", "_access_s", "_waccess_s"
2930
] and
30-
path = op.getArgument(0)
31+
path = op.getArgument(0)
3132
or
3233
name = ["fopen_s", "wfopen_s", "rename"] and
3334
path = op.getArgument(1)
3435
)
3536
}
3637

37-
predicate isFileName(GVN gvn)
38-
{
38+
predicate isFileName(GVN gvn) {
3939
exists(FunctionCall op, Expr path |
4040
filenameOperation(op, path) and
4141
gvn = globalValueNumber(path)
4242
)
4343
}
4444

45-
from FileWrite w, SensitiveExpr source, Expr dest
45+
from FileWrite w, SensitiveExpr source, Expr mid, Expr dest
4646
where
47-
DataFlow::localFlow(DataFlow::exprNode(source), DataFlow::exprNode(w.getASource())) and
47+
DataFlow::localFlow(DataFlow::exprNode(source), DataFlow::exprNode(mid)) and
48+
mid = w.getASource() and
4849
dest = w.getDest() and
49-
not isFileName(globalValueNumber(source)) // file names are not passwords
50+
not isFileName(globalValueNumber(source)) and // file names are not passwords
51+
not exists(string convChar | convChar = w.getSourceConvChar(mid) | not convChar = ["s", "S"]) // ignore things written with other conversion characters
5052
select w, "This write into file '" + dest.toString() + "' may contain unencrypted data from $@",
5153
source, "this source."

cpp/ql/src/semmle/code/cpp/security/FileWrite.qll

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ class FileWrite extends Expr {
1919
* Gets the expression for the object being written to.
2020
*/
2121
Expr getDest() { fileWrite(this, _, result) }
22+
23+
/**
24+
* Gets the conversion character for this write, if it exists and is known. For example in the following code the write of `value1` has conversion character `"s"`, whereas the write of `value2` has no conversion specifier.
25+
* ```
26+
* fprintf(file, "%s", value1);
27+
* stream << value2;
28+
* ```
29+
*/
30+
string getSourceConvChar(Expr source) { fileWriteWithConvChar(this, source, result) }
2231
}
2332

2433
/**
@@ -150,3 +159,22 @@ private predicate fileWrite(Call write, Expr source, Expr dest) {
150159
// file stream using '<<', 'put' or 'write'
151160
fileStreamChain(write, source, dest)
152161
}
162+
163+
/**
164+
* Whether the function call is a write to a file from 'source' with
165+
* conversion character 'conv'. Does not hold if there isn't a conversion
166+
* character, or if it is unknown (for example the format string is not a
167+
* constant).
168+
*/
169+
private predicate fileWriteWithConvChar(
170+
FormattingFunctionCall ffc, Expr source, string conv
171+
) {
172+
// fprintf
173+
exists(FormattingFunction f, int n |
174+
f = ffc.getTarget() and
175+
source = ffc.getFormatArgument(n)
176+
|
177+
exists(f.getOutputParameterIndex(true)) and
178+
conv = ffc.(FormattingFunctionCall).getFormat().(FormatLiteral).getConversionChar(n)
179+
)
180+
}

cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextFileWrite.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
| test2.cpp:55:2:55:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:55:40:55:51 | widepassword | this source. |
66
| test2.cpp:57:2:57:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:57:39:57:49 | call to getPassword | this source. |
77
| test2.cpp:65:3:65:9 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:62:18:62:25 | password | this source. |
8-
| test2.cpp:79:2:79:8 | call to fprintf | This write into file 'log' may contain unencrypted data from $@ | test2.cpp:79:36:79:43 | password | this source. |
98
| test.cpp:45:3:45:7 | call to fputs | This write into file 'file' may contain unencrypted data from $@ | test.cpp:45:9:45:19 | thePassword | this source. |
109
| test.cpp:70:35:70:35 | call to operator<< | This write into file 'mystream' may contain unencrypted data from $@ | test.cpp:70:38:70:48 | thePassword | this source. |
1110
| test.cpp:73:37:73:41 | call to write | This write into file 'mystream' may contain unencrypted data from $@ | test.cpp:73:43:73:53 | thePassword | this source. |

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void tests(FILE *log, myStruct &s)
7676
fprintf(log, "buf = %s\n", buf); // GOOD
7777
}
7878

79-
fprintf(log, "password = %p\n", s.password); // GOOD [FALSE POSITIVE]
79+
fprintf(log, "password = %p\n", s.password); // GOOD
8080

8181
{
8282
if (fopen(s.passwd_config2, "rt") == 0)

0 commit comments

Comments
 (0)