-
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 13 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,118 @@ | ||
/** | ||
* @name Missing return-value check for a scanf-like function | ||
* @description TODO | ||
* @kind problem | ||
* @problem.severity warning | ||
* @security-severity TODO | ||
* @precision TODO | ||
* @id cpp/missing-check-scanf | ||
* @tags security | ||
*/ | ||
|
||
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 | ||
|
||
/** An expression appearing as an output argument to a `scanf`-like call */ | ||
class ScanfOutput extends Expr { | ||
ScanfFunctionCall call; | ||
|
||
int varargIndex; | ||
|
||
ScanfOutput() { | ||
this = call.getArgument(call.getTarget().getNumberOfParameters() + varargIndex) and | ||
varargIndex >= 0 | ||
} | ||
|
||
ScanfFunctionCall getCall() { result = call } | ||
|
||
/** | ||
* Any subsequent use of this argument should be surrounded by a | ||
* check ensuring that the `scanf`-like function has returned a value | ||
* equal to at least `getMinimumGuardConstant()`. | ||
*/ | ||
int getMinimumGuardConstant() { | ||
result = | ||
varargIndex + 1 - | ||
count(ScanfFormatLiteral f, int n | | ||
n <= varargIndex and f.getUse() = call and f.parseConvSpec(n, _, _, _, "n") | ||
) | ||
} | ||
|
||
predicate hasGuardedAccess(Access e, boolean isGuarded) { | ||
e = getAnAccess() and | ||
|
||
if | ||
exists(int value, int minGuard | minGuard = getMinimumGuardConstant() | | ||
|
||
e.getBasicBlock() = blockGuardedBy(value, "==", call) and minGuard <= value | ||
or | ||
e.getBasicBlock() = blockGuardedBy(value, "<", call) and minGuard - 1 <= value | ||
or | ||
e.getBasicBlock() = blockGuardedBy(value, "<=", call) and minGuard <= value | ||
) | ||
then isGuarded = true | ||
else isGuarded = false | ||
} | ||
|
||
/** | ||
* Get a subsequent access of the same underlying storage, | ||
* but before it gets reset or reused in another `scanf` call. | ||
*/ | ||
Access getAnAccess() { | ||
exists(Instruction j | result = j.getAst() | | ||
j = getASubsequentSameValuedInstruction() and | ||
|
||
forall(Instruction k | | ||
k = [getAResetInstruction(), getAReuseInstruction()] implies j.getASuccessor+() = k | ||
|
||
) | ||
) | ||
} | ||
|
||
private Instruction getAResetInstruction() { | ||
result = getASubsequentSameValuedInstruction() and | ||
|
||
result = any(StoreInstruction s).getDestinationAddress() | ||
} | ||
|
||
private Instruction getAReuseInstruction() { | ||
result = getASubsequentSameValuedInstruction() and | ||
|
||
exists(Expr e | result.getAst() = e | | ||
e instanceof ScanfOutput | ||
or | ||
e.getParent().(AddressOfExpr) instanceof ScanfOutput | ||
) | ||
} | ||
|
||
private Instruction getASubsequentSameValuedInstruction() { | ||
exists(Instruction i | | ||
i.getUnconvertedResultExpression() = this and | ||
result = valueNumber(i).getAnInstruction() and | ||
i.getASuccessor+() = result and | ||
forall(Expr child | child = this.getAChild*() implies child != result.getAst()) | ||
) | ||
} | ||
} | ||
|
||
/** Returns a block guarded by the assertion of $value $op $call */ | ||
BasicBlock blockGuardedBy(int value, string op, ScanfFunctionCall call) { | ||
exists(GuardCondition g, Expr left, Expr right | | ||
right = g.getAChild() and | ||
value = left.getValue().toInt() and | ||
DataFlow::localExprFlow(call, right) | ||
| | ||
g.ensuresEq(left, right, 0, result, true) and op = "==" | ||
or | ||
g.ensuresLt(left, right, 0, result, true) and op = "<" | ||
or | ||
g.ensuresLt(left, right, 1, result, true) and op = "<=" | ||
) | ||
} | ||
|
||
from ScanfOutput output, ScanfFunctionCall call, ScanfFunction fun, Access access | ||
where | ||
call.getTarget() = fun and | ||
output.getCall() = call and | ||
output.hasGuardedAccess(access, false) | ||
select access, | ||
"$@ is read here, but may not have been written. " + | ||
"It should be guarded by a check that $@() returns at least " + output.getMinimumGuardConstant() | ||
+ ".", access, access.toString(), call, fun.getName() |
1 change: 1 addition & 0 deletions
1
cpp/ql/test/query-tests/Critical/MissingCheckScanf/.clang-format
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 @@ | ||
DisableFormat: true |
16 changes: 16 additions & 0 deletions
16
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,16 @@ | ||
| test.cpp:30:7:30:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:30:7:30:7 | i | i | test.cpp:29:3:29:7 | call to scanf | scanf | | ||
| test.cpp:46:7:46:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:46:7:46:7 | i | i | test.cpp:45:3:45:7 | call to scanf | scanf | | ||
| test.cpp:63:7:63:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:63:7:63:7 | i | i | test.cpp:62:3:62:7 | call to scanf | scanf | | ||
| test.cpp:77:7:77:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:77:7:77:7 | i | i | test.cpp:76:3:76:8 | call to fscanf | fscanf | | ||
| test.cpp:84:7:84:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:84:7:84:7 | i | i | test.cpp:83:3:83:8 | call to sscanf | sscanf | | ||
| test.cpp:133:8:133:8 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:133:8:133:8 | i | i | test.cpp:131:7:131:11 | call to scanf | scanf | | ||
| test.cpp:142:8:142:8 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:142:8:142:8 | i | i | test.cpp:140:7:140:11 | call to scanf | scanf | | ||
| test.cpp:174:8:174:8 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:174:8:174:8 | i | i | test.cpp:173:7:173:11 | call to scanf | scanf | | ||
| test.cpp:193:8:193:8 | j | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 2. | test.cpp:193:8:193:8 | j | j | test.cpp:190:7:190:11 | call to scanf | scanf | | ||
| test.cpp:214:7:214:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:214:7:214:7 | i | i | test.cpp:213:3:213:7 | call to scanf | scanf | | ||
| test.cpp:222:7:222:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:222:7:222:7 | i | i | test.cpp:221:3:221:7 | call to scanf | scanf | | ||
| test.cpp:230:7:230:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:230:7:230:7 | i | i | test.cpp:229:3:229:7 | call to scanf | scanf | | ||
| test.cpp:242:7:242:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:242:7:242:7 | i | i | test.cpp:241:3:241:7 | call to scanf | scanf | | ||
| test.cpp:252:8:252:12 | ptr_i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:252:8:252:12 | ptr_i | ptr_i | test.cpp:251:3:251:7 | call to scanf | scanf | | ||
| test.cpp:260:7:260:7 | i | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:260:7:260:7 | i | i | test.cpp:259:3:259:7 | call to scanf | scanf | | ||
| test.cpp:354:25:354:25 | u | $@ is read here, but may not have been written. It should be guarded by a check that $@() returns at least 1. | test.cpp:354:25:354:25 | u | u | test.cpp:353:6:353:11 | call to sscanf | sscanf | |
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 |
Oops, something went wrong.
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.