Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions change_notes/2024-03-22-fix-fp-ctr55-cpp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- `CTR55-CPP` - `DoNotUseAnAdditiveOperatorOnAnIterator.ql`:
- Address reported FP in #374. Improve logic on valid end checks and size checks on iterators.
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,7 @@ where
iteratorCreationCall = outputContainer.getAnIteratorFunctionCall() and
iteratorCreationCall = c.getOutputIteratorSource()
|
// Guarded by a bounds check that ensures our destination is larger than "some" value
exists(
GuardCondition guard, ContainerAccessWithoutRangeCheck::ContainerSizeCall sizeCall,
boolean branch
|
globalValueNumber(sizeCall.getQualifier()) =
globalValueNumber(iteratorCreationCall.getQualifier()) and
guard.controls(c.getBasicBlock(), branch) and
relOpWithSwapAndNegate(guard, sizeCall, _, Greater(), _, branch)
)
sizeCompareBoundsChecked(iteratorCreationCall, c)
or
// Container created with sufficient size for the input
exists(ContainerAccessWithoutRangeCheck::ContainerConstructorCall outputIteratorConstructor |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,65 @@
import cpp
import codingstandards.cpp.cert
import codingstandards.cpp.Iterators
import semmle.code.cpp.controlflow.Dominance

from ContainerIteratorAccess it
/**
* something like:
* `end = begin() + size()`
*/
Comment thread
knewbury01 marked this conversation as resolved.
Expr calculatedEndCheck(AdditiveOperatorFunctionCall calc) {
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
exists(
ContainerAccessWithoutRangeCheck::ContainerSizeCall size,
ContainerAccessWithoutRangeCheck::ContainerBeginCall begin
|
calc.getTarget().hasName("operator+") and
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
DataFlow::localFlow(DataFlow::exprNode(size), DataFlow::exprNode(calc.getAChild*())) and
DataFlow::localFlow(DataFlow::exprNode(begin), DataFlow::exprNode(calc.getAChild*())) and
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
//make sure its the same container providing its size as giving the begin
globalValueNumber(begin.getQualifier()) = globalValueNumber(size.getQualifier()) and
result = begin.getQualifier()
)
}

Expr validEndCheck(FunctionCall end) {
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
end instanceof ContainerAccessWithoutRangeCheck::ContainerEndCall and
result = end.getQualifier()
or
result = calculatedEndCheck(end)
}

/**
* some guard exists like: `iterator != end`
* where a relevant`.end()` call flowed into end
*/
predicate validEndBoundCheck(ContainerIteratorAccess it, IteratorSource source) {
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
exists(
FunctionCall end, BasicBlock b, GuardCondition l, ContainerIteratorAccess otherAccess,
Expr qualifierToCheck
|
//sufficient end guard
qualifierToCheck = validEndCheck(end) and
//guard controls the access
l.controls(b, _) and
b.contains(it) and
//guard is comprised of end check and an iterator access
DataFlow::localFlow(DataFlow::exprNode(end), DataFlow::exprNode(l.getChild(_))) and
l.getChild(_) = otherAccess and
//make sure its the same iterator being checked in the guard as accessed
otherAccess.getOwningContainer() = it.getOwningContainer() and
Comment thread
rvermeulen marked this conversation as resolved.
Outdated
//if its the end call itself (or its parts), make sure its the same container providing its end as giving the iterator
globalValueNumber(qualifierToCheck) = globalValueNumber(source.getQualifier()) and
// and the guard call we match must be after the assignment call (to avoid valid guards protecting new iterator accesses further down)
source.getASuccessor*() = l
)
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
}

from ContainerIteratorAccess it, IteratorSource source
where
not isExcluded(it, IteratorsPackage::doNotUseAnAdditiveOperatorOnAnIteratorQuery()) and
it.isAdditiveOperation() and
not exists(RangeBasedForStmt fs | fs.getUpdate().getAChild*() = it) and
// we get the neraby assignment
not exists(STLContainer c, FunctionCall nearbyAssigningIteratorCall, FunctionCall guardCall |
nearbyAssigningIteratorCall = it.getANearbyAssigningIteratorCall() and
// we look for calls to size or end
(guardCall = c.getACallToSize() or guardCall = c.getAnIteratorEndFunctionCall()) and
// such that the call to size is before this
// access
guardCall = it.getAPredecessor*() and
// and it uses the same qualifier as the one we were just assigned
nearbyAssigningIteratorCall.getQualifier().(VariableAccess).getTarget() =
guardCall.getQualifier().(VariableAccess).getTarget() and
// and the size call we match must be after the assignment call
nearbyAssigningIteratorCall.getASuccessor*() = guardCall
)
source = it.getANearbyAssigningIteratorCall() and
not validEndBoundCheck(it, source) and
not sizeCompareBoundsChecked(source, it)
Comment thread
knewbury01 marked this conversation as resolved.
Outdated
select it, "Increment of iterator may overflow since its bounds are not checked."
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
| test.cpp:8:7:8:7 | i | Increment of iterator may overflow since its bounds are not checked. |
| test.cpp:9:9:9:9 | i | Increment of iterator may overflow since its bounds are not checked. |
| test.cpp:10:9:10:9 | i | Increment of iterator may overflow since its bounds are not checked. |
| test.cpp:27:31:27:31 | i | Increment of iterator may overflow since its bounds are not checked. |
| test.cpp:22:18:22:18 | i | Increment of iterator may overflow since its bounds are not checked. |
| test.cpp:28:31:28:31 | i | Increment of iterator may overflow since its bounds are not checked. |
| test.cpp:41:5:41:8 | end2 | Increment of iterator may overflow since its bounds are not checked. |
20 changes: 18 additions & 2 deletions cpp/cert/test/rules/CTR55-CPP/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,26 @@ void f1(std::vector<int> &v) {
}
for (auto i = v.begin(),
l = (i + std::min(static_cast<std::vector<int>::size_type>(10),
v.size()));
i != l; ++i) { // COMPLIANT
v.size())); // NON_COMPLIANT - technically in the
// calculation
i != l; ++i) { // COMPLIANT
}

for (auto i = v.begin();; ++i) { // NON_COMPLIANT
}
}

void test_fp_reported_in_374(std::vector<int> &v) {
{
auto end = v.end();
for (auto i = v.begin(); i != end; ++i) { // COMPLIANT
}
}

{
auto end2 = v.end();
end2++; // NON_COMPLIANT
for (auto i = v.begin(); i != end2; ++i) { // NON_COMPLIANT[FALSE_NEGATIVE]
Comment thread
rvermeulen marked this conversation as resolved.
Outdated
}
}
}
27 changes: 24 additions & 3 deletions cpp/common/src/codingstandards/cpp/Iterators.qll
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import cpp
import codingstandards.cpp.dataflow.DataFlow
import codingstandards.cpp.dataflow.TaintTracking
import codingstandards.cpp.StdNamespace
import codingstandards.cpp.rules.containeraccesswithoutrangecheck.ContainerAccessWithoutRangeCheck as ContainerAccessWithoutRangeCheck
import semmle.code.cpp.controlflow.Guards
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils

abstract class ContainerAccess extends VariableAccess {
abstract Variable getOwningContainer();
Expand Down Expand Up @@ -63,9 +67,11 @@ class ContainerIteratorAccess extends ContainerAccess {
)
}

// get a function call to cbegin/begin that
// assigns its value to the iterator represented by this
// access
/**
* gets a function call to cbegin/begin that
* assigns its value to the iterator represented by this
* access
*/
FunctionCall getANearbyAssigningIteratorCall() {
// the underlying container for this variable is one wherein
// there is an assigned value of cbegin/cend
Expand Down Expand Up @@ -462,3 +468,18 @@ ControlFlowNode getANonInvalidatedSuccessor(ContainerInvalidationOperation op) {
not result instanceof ContainerInvalidationOperation
)
}

/**
* Guarded by a bounds check that ensures our destination is larger than "some" value
*/
predicate sizeCompareBoundsChecked(IteratorSource iteratorCreationCall, Expr guarded) {
exists(
GuardCondition guard, ContainerAccessWithoutRangeCheck::ContainerSizeCall sizeCall,
boolean branch
|
globalValueNumber(sizeCall.getQualifier()) =
globalValueNumber(iteratorCreationCall.getQualifier()) and
guard.controls(guarded.getBasicBlock(), branch) and
relOpWithSwapAndNegate(guard, sizeCall, _, Greater(), _, branch)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ class ContainerEmptyCall extends FunctionCall {
}
}

/**
* A call to either `begin()` on a container.
*/
class ContainerBeginCall extends FunctionCall {
ContainerBeginCall() {
getTarget().getDeclaringType() instanceof ContainerType and
getTarget().getName() = "begin"
}
}

/**
* A call to either `end()` on a container.
*/
class ContainerEndCall extends FunctionCall {
ContainerEndCall() {
getTarget().getDeclaringType() instanceof ContainerType and
getTarget().getName() = "end"
}
}

/**
* A call to either `size()` or `length()` on a container.
*/
Expand Down