Skip to content

Commit fbe1aab

Browse files
committed
CTR55-CPP: improve end check logic for iterators
1 parent 795f67c commit fbe1aab

File tree

5 files changed

+78
-28
lines changed

5 files changed

+78
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- `CTR55-CPP` - `DoNotUseAnAdditiveOperatorOnAnIterator.ql`:
2+
- Address reported FP in #374. Improve logic on valid end checks on iterators.

cpp/cert/src/rules/CTR52-CPP/GuaranteeGenericCppLibraryFunctionsDoNotOverflow.ql

+1-10
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,7 @@ where
8080
iteratorCreationCall = outputContainer.getAnIteratorFunctionCall() and
8181
iteratorCreationCall = c.getOutputIteratorSource()
8282
|
83-
// Guarded by a bounds check that ensures our destination is larger than "some" value
84-
exists(
85-
GuardCondition guard, ContainerAccessWithoutRangeCheck::ContainerSizeCall sizeCall,
86-
boolean branch
87-
|
88-
globalValueNumber(sizeCall.getQualifier()) =
89-
globalValueNumber(iteratorCreationCall.getQualifier()) and
90-
guard.controls(c.getBasicBlock(), branch) and
91-
relOpWithSwapAndNegate(guard, sizeCall, _, Greater(), _, branch)
92-
)
83+
size_compare_bounds_checked(iteratorCreationCall, c)
9384
or
9485
// Container created with sufficient size for the input
9586
exists(ContainerAccessWithoutRangeCheck::ContainerConstructorCall outputIteratorConstructor |

cpp/cert/src/rules/CTR55-CPP/DoNotUseAnAdditiveOperatorOnAnIterator.ql

+36-15
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,44 @@ import cpp
1515
import codingstandards.cpp.cert
1616
import codingstandards.cpp.Iterators
1717

18-
from ContainerIteratorAccess it
18+
/**
19+
* any `.size()` check above our access
20+
*/
21+
predicate size_checked_above(ContainerIteratorAccess it, IteratorSource source) {
22+
exists(STLContainer c, FunctionCall guardCall |
23+
c.getACallToSize() = guardCall and
24+
guardCall = it.getAPredecessor*() and
25+
//make sure its the same container providing its size as giving the iterator
26+
globalValueNumber(guardCall.getQualifier()) = globalValueNumber(source.getQualifier()) and
27+
// and the size call we match must be after the assignment call
28+
source.getASuccessor*() = guardCall
29+
)
30+
}
31+
32+
/**
33+
* some loop check exists like: `iterator != end`
34+
* where a relevant`.end()` call flowed into end
35+
*/
36+
predicate valid_end_bound_check(ContainerIteratorAccess it, IteratorSource source) {
37+
exists(STLContainer c, Loop l, ContainerIteratorAccess otherAccess, IteratorSource end |
38+
end = c.getAnIteratorEndFunctionCall() and
39+
//flow exists between end() and the loop condition
40+
DataFlow::localFlow(DataFlow::exprNode(end), DataFlow::exprNode(l.getCondition().getAChild())) and
41+
l.getCondition().getAChild() = otherAccess and
42+
//make sure its the same iterator being checked as incremented
43+
otherAccess.getOwningContainer() = it.getOwningContainer() and
44+
//make sure its the same container providing its end as giving the iterator
45+
globalValueNumber(end.getQualifier()) = globalValueNumber(source.getQualifier())
46+
)
47+
}
48+
49+
from ContainerIteratorAccess it, IteratorSource source
1950
where
2051
not isExcluded(it, IteratorsPackage::doNotUseAnAdditiveOperatorOnAnIteratorQuery()) and
2152
it.isAdditiveOperation() and
2253
not exists(RangeBasedForStmt fs | fs.getUpdate().getAChild*() = it) and
23-
// we get the neraby assignment
24-
not exists(STLContainer c, FunctionCall nearbyAssigningIteratorCall, FunctionCall guardCall |
25-
nearbyAssigningIteratorCall = it.getANearbyAssigningIteratorCall() and
26-
// we look for calls to size or end
27-
(guardCall = c.getACallToSize() or guardCall = c.getAnIteratorEndFunctionCall()) and
28-
// such that the call to size is before this
29-
// access
30-
guardCall = it.getAPredecessor*() and
31-
// and it uses the same qualifier as the one we were just assigned
32-
nearbyAssigningIteratorCall.getQualifier().(VariableAccess).getTarget() =
33-
guardCall.getQualifier().(VariableAccess).getTarget() and
34-
// and the size call we match must be after the assignment call
35-
nearbyAssigningIteratorCall.getASuccessor*() = guardCall
36-
)
54+
source = it.getANearbyAssigningIteratorCall() and
55+
not size_compare_bounds_checked(source, it) and
56+
not valid_end_bound_check(it, source) and
57+
not size_checked_above(it, source)
3758
select it, "Increment of iterator may overflow since its bounds are not checked."

cpp/cert/test/rules/CTR55-CPP/test.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@ void f1(std::vector<int> &v) {
2626

2727
for (auto i = v.begin();; ++i) { // NON_COMPLIANT
2828
}
29+
}
30+
31+
void test_fp_reported_in_374(std::vector<int> &v) {
32+
{
33+
auto end = v.end();
34+
for (auto i = v.begin(); i != end; ++i) { // COMPLIANT
35+
}
36+
}
37+
38+
{
39+
auto end2 = v.end();
40+
end2++; // NON_COMPLIANT[FALSE_NEGATIVE]
41+
for (auto i = v.begin(); i != end2; ++i) { // NON_COMPLIANT[FALSE_NEGATIVE]
42+
}
43+
}
2944
}

cpp/common/src/codingstandards/cpp/Iterators.qll

+24-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import cpp
66
import codingstandards.cpp.dataflow.DataFlow
77
import codingstandards.cpp.dataflow.TaintTracking
88
import codingstandards.cpp.StdNamespace
9+
import codingstandards.cpp.rules.containeraccesswithoutrangecheck.ContainerAccessWithoutRangeCheck as ContainerAccessWithoutRangeCheck
10+
import semmle.code.cpp.controlflow.Guards
11+
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
12+
import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
913

1014
abstract class ContainerAccess extends VariableAccess {
1115
abstract Variable getOwningContainer();
@@ -63,9 +67,11 @@ class ContainerIteratorAccess extends ContainerAccess {
6367
)
6468
}
6569

66-
// get a function call to cbegin/begin that
67-
// assigns its value to the iterator represented by this
68-
// access
70+
/**
71+
* gets a function call to cbegin/begin that
72+
* assigns its value to the iterator represented by this
73+
* access
74+
*/
6975
FunctionCall getANearbyAssigningIteratorCall() {
7076
// the underlying container for this variable is one wherein
7177
// there is an assigned value of cbegin/cend
@@ -462,3 +468,18 @@ ControlFlowNode getANonInvalidatedSuccessor(ContainerInvalidationOperation op) {
462468
not result instanceof ContainerInvalidationOperation
463469
)
464470
}
471+
472+
/**
473+
* Guarded by a bounds check that ensures our destination is larger than "some" value
474+
*/
475+
predicate size_compare_bounds_checked(IteratorSource iteratorCreationCall, Expr guarded) {
476+
exists(
477+
GuardCondition guard, ContainerAccessWithoutRangeCheck::ContainerSizeCall sizeCall,
478+
boolean branch
479+
|
480+
globalValueNumber(sizeCall.getQualifier()) =
481+
globalValueNumber(iteratorCreationCall.getQualifier()) and
482+
guard.controls(guarded.getBasicBlock(), branch) and
483+
relOpWithSwapAndNegate(guard, sizeCall, _, Greater(), _, branch)
484+
)
485+
}

0 commit comments

Comments
 (0)