Skip to content

Commit 2786f4b

Browse files
authored
Merge pull request #561 from knewbury01/knewbury01/fix-374
CTR55-CPP: address 374
2 parents 48fabdf + cee9f01 commit 2786f4b

File tree

7 files changed

+163
-31
lines changed

7 files changed

+163
-31
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 and size 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+
sizeCompareBoundsChecked(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

+72-15
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,81 @@
1414
import cpp
1515
import codingstandards.cpp.cert
1616
import codingstandards.cpp.Iterators
17+
import semmle.code.cpp.controlflow.Dominance
1718

18-
from ContainerIteratorAccess it
19+
/**
20+
* Models a call to an iterator's `operator+`
21+
*/
22+
class AdditionOperatorFunctionCall extends AdditiveOperatorFunctionCall {
23+
AdditionOperatorFunctionCall() { this.getTarget().hasName("operator+") }
24+
}
25+
26+
/**
27+
* There exists a calculation for the reference one passed the end of some container
28+
* An example derivation is:
29+
* `end = begin() + size()`
30+
*/
31+
Expr getDerivedReferenceToOnePassedTheEndElement(Expr containerReference) {
32+
exists(
33+
ContainerAccessWithoutRangeCheck::ContainerSizeCall size,
34+
ContainerAccessWithoutRangeCheck::ContainerBeginCall begin, AdditionOperatorFunctionCall calc
35+
|
36+
result = calc
37+
|
38+
DataFlow::localFlow(DataFlow::exprNode(size), DataFlow::exprNode(calc.getAChild+())) and
39+
DataFlow::localFlow(DataFlow::exprNode(begin), DataFlow::exprNode(calc.getAChild+())) and
40+
//make sure its the same container providing its size as giving the begin
41+
globalValueNumber(begin.getQualifier()) = globalValueNumber(size.getQualifier()) and
42+
containerReference = begin.getQualifier()
43+
)
44+
}
45+
46+
/**
47+
* a wrapper predicate for a couple of types of permitted end bounds checks
48+
*/
49+
Expr getReferenceToOnePassedTheEndElement(Expr containerReference) {
50+
//a container end access - v.end()
51+
result instanceof ContainerAccessWithoutRangeCheck::ContainerEndCall and
52+
containerReference = result.(FunctionCall).getQualifier()
53+
or
54+
result = getDerivedReferenceToOnePassedTheEndElement(containerReference)
55+
}
56+
57+
/**
58+
* some guard exists like: `iterator != end`
59+
* where a relevant`.end()` call flowed into end
60+
*/
61+
predicate isUpperBoundEndCheckedIteratorAccess(IteratorSource source, ContainerIteratorAccess it) {
62+
exists(
63+
Expr referenceToOnePassedTheEndElement, BasicBlock basicBlockOfIteratorAccess,
64+
GuardCondition upperBoundCheck, ContainerIteratorAccess checkedIteratorAccess,
65+
Expr containerReferenceFromEndGuard
66+
|
67+
//sufficient end guard
68+
referenceToOnePassedTheEndElement =
69+
getReferenceToOnePassedTheEndElement(containerReferenceFromEndGuard) and
70+
//guard controls the access
71+
upperBoundCheck.controls(basicBlockOfIteratorAccess, _) and
72+
basicBlockOfIteratorAccess.contains(it) and
73+
//guard is comprised of end check and an iterator access
74+
DataFlow::localFlow(DataFlow::exprNode(referenceToOnePassedTheEndElement),
75+
DataFlow::exprNode(upperBoundCheck.getChild(_))) and
76+
upperBoundCheck.getChild(_) = checkedIteratorAccess and
77+
//make sure its the same iterator being checked in the guard as accessed
78+
checkedIteratorAccess.getOwningContainer() = it.getOwningContainer() and
79+
//if its the end call itself (or its parts), make sure its the same container providing its end as giving the iterator
80+
globalValueNumber(containerReferenceFromEndGuard) = globalValueNumber(source.getQualifier()) and
81+
// and the guard call we match must be after the assignment call (to avoid valid guards protecting new iterator accesses further down)
82+
source.getASuccessor*() = upperBoundCheck
83+
)
84+
}
85+
86+
from ContainerIteratorAccess it, IteratorSource source
1987
where
2088
not isExcluded(it, IteratorsPackage::doNotUseAnAdditiveOperatorOnAnIteratorQuery()) and
2189
it.isAdditiveOperation() and
2290
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-
)
91+
source = it.getANearbyAssigningIteratorCall() and
92+
not isUpperBoundEndCheckedIteratorAccess(source, it) and
93+
not sizeCompareBoundsChecked(source, it)
3794
select it, "Increment of iterator may overflow since its bounds are not checked."
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
| test.cpp:8:7:8:7 | i | Increment of iterator may overflow since its bounds are not checked. |
22
| test.cpp:9:9:9:9 | i | Increment of iterator may overflow since its bounds are not checked. |
33
| test.cpp:10:9:10:9 | i | Increment of iterator may overflow since its bounds are not checked. |
4-
| test.cpp:27:31:27:31 | i | Increment of iterator may overflow since its bounds are not checked. |
4+
| test.cpp:22:18:22:18 | i | Increment of iterator may overflow since its bounds are not checked. |
5+
| test.cpp:28:31:28:31 | i | Increment of iterator may overflow since its bounds are not checked. |
6+
| test.cpp:41:5:41:8 | end2 | Increment of iterator may overflow since its bounds are not checked. |
7+
| test.cpp:53:42:53:42 | i | Increment of iterator may overflow since its bounds are not checked. |
8+
| test.cpp:64:15:64:15 | i | Increment of iterator may overflow since its bounds are not checked. |

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

+39-2
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,47 @@ void f1(std::vector<int> &v) {
2020
}
2121
for (auto i = v.begin(),
2222
l = (i + std::min(static_cast<std::vector<int>::size_type>(10),
23-
v.size()));
24-
i != l; ++i) { // COMPLIANT
23+
v.size())); // NON_COMPLIANT - technically in the
24+
// calculation
25+
i != l; ++i) { // COMPLIANT
2526
}
2627

2728
for (auto i = v.begin();; ++i) { // NON_COMPLIANT
2829
}
30+
}
31+
32+
void test_fp_reported_in_374(std::vector<int> &v) {
33+
{
34+
auto end = v.end();
35+
for (auto i = v.begin(); i != end; ++i) { // COMPLIANT
36+
}
37+
}
38+
39+
{
40+
auto end2 = v.end();
41+
end2++; // NON_COMPLIANT
42+
for (auto i = v.begin(); i != end2;
43+
++i) { // NON_COMPLIANT[FALSE_NEGATIVE] - case of invalidations to
44+
// check before use expected to be less frequent, can model in
45+
// future if need be
46+
}
47+
}
48+
}
49+
50+
void test(std::vector<int> &v, std::vector<int> &v2) {
51+
{
52+
auto end = v2.end();
53+
for (auto i = v.begin(); i != end; ++i) { // NON_COMPLIANT - wrong check
54+
}
55+
}
56+
}
57+
58+
void test2(std::vector<int> &v) {
59+
auto i = v.begin();
60+
while (1) {
61+
auto i2 = ((i != v.end()) != 0);
62+
if (!i2)
63+
break;
64+
(void)((++i)); // COMPLIANT[FALSE_POSITIVE]
65+
}
2966
}

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 sizeCompareBoundsChecked(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+
}

cpp/common/src/codingstandards/cpp/rules/containeraccesswithoutrangecheck/ContainerAccessWithoutRangeCheck.qll

+20
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ class ContainerEmptyCall extends FunctionCall {
8585
}
8686
}
8787

88+
/**
89+
* A call to either `begin()` on a container.
90+
*/
91+
class ContainerBeginCall extends FunctionCall {
92+
ContainerBeginCall() {
93+
getTarget().getDeclaringType() instanceof ContainerType and
94+
getTarget().getName() = "begin"
95+
}
96+
}
97+
98+
/**
99+
* A call to either `end()` on a container.
100+
*/
101+
class ContainerEndCall extends FunctionCall {
102+
ContainerEndCall() {
103+
getTarget().getDeclaringType() instanceof ContainerType and
104+
getTarget().getName() = "end"
105+
}
106+
}
107+
88108
/**
89109
* A call to either `size()` or `length()` on a container.
90110
*/

0 commit comments

Comments
 (0)