-
Notifications
You must be signed in to change notification settings - Fork 1.7k
C++: Missing return-value check for scanf-like functions #1076 #10033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 5 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
94c43c0
Update .gitignore for .vscode/*.log temporaries
d10c 76ef779
C++: Add test and placeholder query.
geoffw0 c62ae3b
C++: First working. We now prefer flagging the cases where the variab…
geoffw0 3e71ef2
Add more MissingCheckScanf test cases
d10c c7db7ec
WIP: ScanfOutput.getAnAccess() returns all immediate use()s.
d10c a7f46e8
WIP: Full-featured query
d10c 28331a5
WIP: More test cases, including False Positives
d10c b380e9a
.clang-format: do not autoformat test.cpp
d10c 9b4b7cb
WIP: Eliminated the _scanf_l false positive
d10c ea033f5
WIP: Fix the %n-related False Positive
d10c db04f77
WIP: Add test for loop-related False Positive
d10c f7800f2
WIP: Fix loop-related False Positive
d10c 0132865
WIP: Added a test case exercising the last uwsgi FP
d10c 230f411
WIP: use explicit `this.` in method calls
d10c 63a64ee
WIP: Fix bad join order.
d10c 362980b
WIP: Found another false positive via opencv/opencv
d10c 0af6160
WIP: Fixed the loop-related FP !!
d10c File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/** | ||
* @name TODO | ||
* @description TODO | ||
* @kind problem | ||
* @problem.severity warning | ||
* @security-severity TODO | ||
* @precision TODO | ||
* @id cpp/missing-check-scanf | ||
* @tags TODO | ||
*/ | ||
|
||
import cpp | ||
import semmle.code.cpp.commons.Scanf | ||
import semmle.code.cpp.controlflow.Guards | ||
import semmle.code.cpp.dataflow.DataFlow | ||
import semmle.code.cpp.ir.IR | ||
import semmle.code.cpp.ir.ValueNumbering | ||
|
||
// DataFlow::localFlow(DataFlow::parameterNode(src), DataFlow::exprNode(snk)) | ||
/* | ||
* Find: | ||
* - all scanf calls | ||
* => ScanfFunctionCall | ||
* | ||
* - relate the value number of each out-arg to: | ||
* 1. the return value of the scanf call, and | ||
* 2. the arg number, post-format, of the out-arg. | ||
* => scanfOutput/3 | ||
* | ||
* - Find all accesses to variables with the same | ||
* value number, and if applicable, the guard condition | ||
* of one of their enclosing blocks that ensures | ||
* that the scanf return value is at least the | ||
* associated arg-number | ||
* | ||
* - Combine into data flow tracking from out-arg to use, | ||
* with usage in _other_ scanf calls as barrier. | ||
*/ | ||
|
||
class ScanfOutput extends Expr { | ||
ScanfFunctionCall call; | ||
|
||
int argNum; | ||
|
||
|
||
ScanfOutput() { | ||
this = call.getArgument(call.getFormatParameterIndex() + argNum) and | ||
d10c marked this conversation as resolved.
Show resolved
Hide resolved
|
||
argNum >= 1 | ||
} | ||
|
||
Access getAnAccess() { | ||
exists(Instruction j | | ||
j = getNextInstruction() and | ||
forall(Instruction k | k = getAReset() implies j.getASuccessor+() = k) and | ||
forall(Instruction k | k = getAReuse() implies j.getASuccessor+() = k) | ||
| | ||
result = j.getAst() | ||
) | ||
} | ||
|
||
private Instruction getAReset() { | ||
result = getNextInstruction() and | ||
result = any(StoreInstruction s).getDestinationAddress() | ||
} | ||
|
||
private Instruction getAReuse() { | ||
result = getNextInstruction() and | ||
exists(Expr e | result.getAst() = e | | ||
e instanceof ScanfOutput | ||
or | ||
e.getParent().(AddressOfExpr) instanceof ScanfOutput | ||
) | ||
} | ||
|
||
private Instruction getNextInstruction() { | ||
d10c marked this conversation as resolved.
Show resolved
Hide resolved
|
||
exists(Instruction i | | ||
i.getUnconvertedResultExpression() = this and | ||
result = valueNumber(i).getAnInstruction() and | ||
i.getASuccessor+() = result | ||
) | ||
} | ||
} | ||
|
||
//select any(CallInstruction i | i.getStaticCallTarget() instanceof ScanfFunction).toString() | ||
from ScanfFunctionCall fc | ||
where fc instanceof ExprInVoidContext | ||
select fc, "This is a call to scanf." |
10 changes: 10 additions & 0 deletions
10
cpp/ql/test/query-tests/Critical/MissingCheckScanf/MissingCheckScanf.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
| test.cpp:23:3:23:7 | call to scanf | This is a call to scanf. | | ||
| test.cpp:39:3:39:7 | call to scanf | This is a call to scanf. | | ||
| test.cpp:56:3:56:7 | call to scanf | This is a call to scanf. | | ||
| test.cpp:70:3:70:8 | call to fscanf | This is a call to scanf. | | ||
| test.cpp:77:3:77:8 | call to sscanf | This is a call to scanf. | | ||
| test.cpp:178:3:178:7 | call to scanf | This is a call to scanf. | | ||
| test.cpp:186:3:186:7 | call to scanf | This is a call to scanf. | | ||
| test.cpp:194:3:194:7 | call to scanf | This is a call to scanf. | | ||
| test.cpp:206:3:206:7 | call to scanf | This is a call to scanf. | | ||
| test.cpp:216:3:216:7 | call to scanf | This is a call to scanf. | |
1 change: 1 addition & 0 deletions
1
cpp/ql/test/query-tests/Critical/MissingCheckScanf/MissingCheckScanf.qlref
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Critical/MissingCheckScanf.ql |
287 changes: 287 additions & 0 deletions
287
cpp/ql/test/query-tests/Critical/MissingCheckScanf/test.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,287 @@ | ||
typedef struct | ||
{ | ||
} FILE; | ||
|
||
int scanf(const char *format, ...); | ||
int fscanf(FILE *stream, const char *format, ...); | ||
int sscanf(const char *s, const char *format, ...); | ||
|
||
void use(int i); | ||
|
||
void set_by_ref(int &i); | ||
void set_by_ptr(int *i); | ||
bool maybe(); | ||
|
||
FILE *get_a_stream(); | ||
const char *get_a_string(); | ||
|
||
int main() | ||
{ | ||
// --- simple cases --- | ||
|
||
{ | ||
int i; | ||
|
||
scanf("%d", &i); // BAD: may not have written `i` | ||
use(i); | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
if (scanf("%d", &i) == 1) // GOOD: checks return value | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
int i = 0; | ||
|
||
scanf("%d", &i); // BAD. Design choice: already initialized variables shouldn't make a difference. | ||
use(i); | ||
} | ||
|
||
{ | ||
int i; | ||
use(i); | ||
|
||
if (scanf("%d", &i) == 1) // GOOD: only care about uses after scanf call | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
int i; // Reused variable | ||
|
||
scanf("%d", &i); // BAD | ||
use(i); | ||
|
||
if (scanf("%d", &i) == 1) // GOOD | ||
{ | ||
use(i); | ||
} | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// --- different scanf functions --- | ||
|
||
{ | ||
int i; | ||
|
||
fscanf(get_a_stream(), "%d", &i); // BAD: may not have written `i` | ||
use(i); | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
sscanf(get_a_string(), "%d", &i); // BAD: may not have written `i` | ||
use(i); | ||
} | ||
|
||
// --- different ways of checking --- | ||
|
||
{ | ||
int i; | ||
|
||
if (scanf("%d", &i) >= 1) // GOOD | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
if (scanf("%d", &i) == 1) // GOOD | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
if (scanf("%d", &i) != 0) // BAD: scanf can return -1 [NOT DETECTED] | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
if (scanf("%d", &i) == 0) // BAD: checks return value incorrectly [NOT DETECTED] | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
int r; | ||
int i; | ||
|
||
r = scanf("%d", &i); // GOOD | ||
|
||
if (r >= 1) | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
bool b; | ||
int i; | ||
|
||
b = scanf("%d", &i); // BAD [NOT DETECTED]: scanf can return EOF (boolifies true) | ||
d10c marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (b >= 1) | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
bool b; | ||
int i; | ||
|
||
b = scanf("%d", &i); // BAD [NOT DETECTED] | ||
|
||
use(i); | ||
} | ||
|
||
{ | ||
int i, j; | ||
|
||
if (scanf("%d %d", &i) >= 2) // GOOD: `j` is not a scanf arg, so out of scope of MissingCheckScanf | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
use(i); | ||
use(j); | ||
} | ||
} | ||
|
||
{ | ||
int i, j; | ||
|
||
if (scanf("%d %d", &i, &j) >= 1) // BAD: checks return value incorrectly [NOT DETECTED] | ||
{ | ||
use(i); | ||
use(j); | ||
} | ||
} | ||
|
||
// --- different initialization --- | ||
|
||
{ | ||
int i; | ||
i = 0; | ||
|
||
scanf("%d", &i); // BAD | ||
use(i); | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
set_by_ref(i); | ||
scanf("%d", &i); // BAD | ||
use(i); | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
set_by_ptr(&i); | ||
scanf("%d", &i); // BAD | ||
use(i); | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
if (maybe()) | ||
{ | ||
i = 0; | ||
} | ||
|
||
scanf("%d", &i); // BAD: `i` may not have been initialized | ||
use(i); | ||
} | ||
|
||
// --- different use --- | ||
|
||
{ | ||
int i; | ||
int *ptr_i = &i; | ||
|
||
scanf("%d", &i); // BAD: may not have written `i` | ||
use(*ptr_i); | ||
} | ||
|
||
{ | ||
int i; | ||
int *ptr_i = &i; | ||
|
||
scanf("%d", ptr_i); // BAD: may not have written `*ptr_i` | ||
use(i); | ||
} | ||
|
||
{ | ||
int i; | ||
scanf("%d", &i); | ||
i = 42; | ||
use(i); // GOOD [FALSE POSITIVE] | ||
} | ||
|
||
// --- weird formatting strings --- | ||
|
||
{ | ||
int i; | ||
|
||
if (scanf("%n %d", &i) >= 1) // GOOD (`%n` does not consume input) | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
if (scanf("%% %d", &i) >= 1) // GOOD (`%%` does not consume input) | ||
{ | ||
use(i); | ||
} | ||
} | ||
|
||
{ | ||
int i; | ||
|
||
if (scanf("%*d %d", &i) >= 1) // GOOD (`%*d` does not consume input) | ||
{ | ||
use(i); | ||
} | ||
} | ||
} | ||
|
||
// Non-local cases: | ||
|
||
bool my_scan_int(int &i) | ||
{ | ||
return scanf("%d", &i) == 1; // GOOD | ||
} | ||
|
||
void my_scan_int_test() | ||
{ | ||
int i; | ||
|
||
use(i); // GOOD: used before scanf | ||
|
||
my_scan_int(i); // BAD [NOT DETECTED] | ||
use(i); | ||
|
||
if (my_scan_int(i)) // GOOD | ||
{ | ||
use(i); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.