Skip to content

Commit b66b878

Browse files
committed
Merge branch 'main' into unitialized-local-as-path
2 parents bd0969b + 35a309f commit b66b878

File tree

25 files changed

+1397
-26
lines changed

25 files changed

+1397
-26
lines changed

cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -440,17 +440,19 @@ private class IteratorAssignmentMemberOperatorModel extends IteratorAssignmentMe
440440
* A `begin` or `end` member function, or a related member function, that
441441
* returns an iterator.
442442
*/
443-
private class BeginOrEndFunction extends MemberFunction, TaintFunction, GetIteratorFunction,
444-
AliasFunction, SideEffectFunction
445-
{
443+
class BeginOrEndFunction extends MemberFunction {
446444
BeginOrEndFunction() {
447445
this.hasName([
448446
"begin", "cbegin", "rbegin", "crbegin", "end", "cend", "rend", "crend", "before_begin",
449447
"cbefore_begin"
450448
]) and
451449
this.getType().getUnspecifiedType() instanceof Iterator
452450
}
451+
}
453452

453+
private class BeginOrEndFunctionModels extends BeginOrEndFunction, TaintFunction,
454+
GetIteratorFunction, AliasFunction, SideEffectFunction
455+
{
454456
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
455457
input.isQualifierObject() and
456458
output.isReturnValue()

cpp/ql/lib/semmle/code/cpp/models/implementations/StdContainer.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,15 @@ private class StdSequenceContainerAssign extends TaintFunction {
253253
/**
254254
* The standard container functions `at` and `operator[]`.
255255
*/
256-
private class StdSequenceContainerAt extends TaintFunction {
256+
class StdSequenceContainerAt extends MemberFunction {
257257
StdSequenceContainerAt() {
258258
this.getClassAndName(["at", "operator[]"]) instanceof Array or
259259
this.getClassAndName(["at", "operator[]"]) instanceof Deque or
260260
this.getClassAndName(["at", "operator[]"]) instanceof Vector
261261
}
262+
}
262263

264+
private class StdSequenceContainerAtModel extends StdSequenceContainerAt, TaintFunction {
263265
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
264266
// flow from qualifier to referenced return value
265267
input.isQualifierObject() and

cpp/ql/lib/semmle/code/cpp/models/implementations/StdMap.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,11 @@ private class StdMapMerge extends TaintFunction {
129129
/**
130130
* The standard map functions `at` and `operator[]`.
131131
*/
132-
private class StdMapAt extends TaintFunction {
132+
class StdMapAt extends MemberFunction {
133133
StdMapAt() { this.getClassAndName(["at", "operator[]"]) instanceof MapOrUnorderedMap }
134+
}
134135

136+
private class StdMapAtModels extends StdMapAt, TaintFunction {
135137
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
136138
// flow from qualifier to referenced return value
137139
input.isQualifierObject() and
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Using an iterator owned by a container after the lifetime of the container has expired can lead to undefined behavior.
8+
This is because the iterator may be invalidated when the container is destroyed, and dereferencing an invalidated iterator is undefined behavior.
9+
These problems can be hard to spot due to C++'s complex rules for temporary object lifetimes and their extensions.
10+
</p>
11+
12+
</overview>
13+
<recommendation>
14+
15+
<p>
16+
Never create an iterator to a temporary container when the iterator is expected to be used after the container's lifetime has expired.
17+
</p>
18+
19+
</recommendation>
20+
<example>
21+
<p>
22+
23+
</p>
24+
25+
<p>
26+
The rules for lifetime extension ensures that the code in <code>lifetime_of_temp_extended</code> is well-defined. This is because the
27+
lifetime of the temporary container returned by <code>get_vector</code> is extended to the end of the loop. However, prior to C++23,
28+
the lifetime extension rules do not ensure that the container returned by <code>get_vector</code> is extended in <code>lifetime_of_temp_not_extended</code>.
29+
This is because the temporary container is not bound to a rvalue reference.
30+
</p>
31+
<sample src="IteratorToExpiredContainerExtendedLifetime.cpp" />
32+
33+
</example>
34+
<references>
35+
36+
<li>CERT C Coding Standard:
37+
<a href="https://wiki.sei.cmu.edu/confluence/display/c/MEM30-C.+Do+not+access+freed+memory">MEM30-C. Do not access freed memory</a>.</li>
38+
<li>
39+
OWASP:
40+
<a href="https://owasp.org/www-community/vulnerabilities/Using_freed_memory">Using freed memory</a>.
41+
</li>
42+
<li>
43+
<a href="https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Lifetime.pdf">Lifetime safety: Preventing common dangling</a>
44+
</li>
45+
<li>
46+
<a href="https://en.cppreference.com/w/cpp/container">Containers library</a>
47+
</li>
48+
<li>
49+
<a href="https://en.cppreference.com/w/cpp/language/range-for">Range-based for loop (since C++11)</a>
50+
</li>
51+
52+
</references>
53+
</qhelp>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* @name Iterator to expired container
3+
* @description Using an iterator owned by a container whose lifetime has expired may lead to unexpected behavior.
4+
* @kind problem
5+
* @precision high
6+
* @id cpp/iterator-to-expired-container
7+
* @problem.severity warning
8+
* @tags reliability
9+
* security
10+
* external/cwe/cwe-416
11+
* external/cwe/cwe-664
12+
*/
13+
14+
// IMPORTANT: This query does not currently find anything since it relies on extractor and analysis improvements that hasn't yet been released
15+
import cpp
16+
import semmle.code.cpp.ir.IR
17+
import semmle.code.cpp.dataflow.new.DataFlow
18+
import semmle.code.cpp.models.implementations.StdContainer
19+
import semmle.code.cpp.models.implementations.StdMap
20+
import semmle.code.cpp.models.implementations.Iterator
21+
22+
/**
23+
* A configuration to track flow from a temporary variable to the qualifier of
24+
* a destructor call
25+
*/
26+
module TempToDestructorConfig implements DataFlow::ConfigSig {
27+
predicate isSource(DataFlow::Node source) {
28+
source.asInstruction().(VariableAddressInstruction).getIRVariable() instanceof IRTempVariable
29+
}
30+
31+
predicate isSink(DataFlow::Node sink) {
32+
sink.asOperand().(ThisArgumentOperand).getCall().getStaticCallTarget() instanceof Destructor
33+
}
34+
}
35+
36+
module TempToDestructorFlow = DataFlow::Global<TempToDestructorConfig>;
37+
38+
/**
39+
* Gets a `DataFlow::Node` that represents a temporary that will be destroyed
40+
* by a call to a destructor, or a `DataFlow::Node` that will transitively be
41+
* destroyed by a call to a destructor.
42+
*
43+
* For the latter case, consider something like:
44+
* ```
45+
* std::vector<std::vector<int>> get_2d_vector();
46+
* auto& v = get_2d_vector()[0];
47+
* ```
48+
* Given the above, this predicate returns the node corresponding
49+
* to `get_2d_vector()[0]` since the temporary `get_2d_vector()` gets
50+
* destroyed by a call to `std::vector<std::vector<int>>::~vector`,
51+
* and thus the result of `get_2d_vector()[0]` is also an invalid reference.
52+
*/
53+
DataFlow::Node getADestroyedNode() {
54+
exists(TempToDestructorFlow::PathNode destroyedTemp | destroyedTemp.isSource() |
55+
result = destroyedTemp.getNode()
56+
or
57+
exists(CallInstruction call |
58+
result.asInstruction() = call and
59+
DataFlow::localFlow(destroyedTemp.getNode(),
60+
DataFlow::operandNode(call.getThisArgumentOperand()))
61+
|
62+
call.getStaticCallTarget() instanceof StdSequenceContainerAt or
63+
call.getStaticCallTarget() instanceof StdMapAt
64+
)
65+
)
66+
}
67+
68+
predicate isSinkImpl(DataFlow::Node sink, FunctionCall fc) {
69+
exists(CallInstruction call |
70+
call = sink.asOperand().(ThisArgumentOperand).getCall() and
71+
fc = call.getUnconvertedResultExpression() and
72+
call.getStaticCallTarget() instanceof BeginOrEndFunction
73+
)
74+
}
75+
76+
/**
77+
* Flow from any destroyed object to the qualifier of a `begin` or `end` call
78+
*/
79+
module DestroyedToBeginConfig implements DataFlow::ConfigSig {
80+
predicate isSource(DataFlow::Node source) { source = getADestroyedNode() }
81+
82+
predicate isSink(DataFlow::Node sink) { isSinkImpl(sink, _) }
83+
84+
DataFlow::FlowFeature getAFeature() {
85+
// By blocking argument-to-parameter flow we ensure that we don't enter a
86+
// function body where the temporary outlives anything inside the function.
87+
// This prevents false positives in cases like:
88+
// ```cpp
89+
// void foo(const std::vector<int>& v) {
90+
// for(auto x : v) { ... } // this is fine since v outlives the loop
91+
// }
92+
// ...
93+
// foo(create_temporary())
94+
// ```
95+
result instanceof DataFlow::FeatureHasSinkCallContext
96+
}
97+
}
98+
99+
module DestroyedToBeginFlow = DataFlow::Global<DestroyedToBeginConfig>;
100+
101+
from DataFlow::Node source, DataFlow::Node sink, FunctionCall beginOrEnd
102+
where DestroyedToBeginFlow::flow(source, sink) and isSinkImpl(sink, beginOrEnd)
103+
select source, "This object is destroyed before $@ is called.", beginOrEnd, beginOrEnd.toString()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <vector>
2+
3+
std::vector<int> get_vector();
4+
5+
void use(int);
6+
7+
void lifetime_of_temp_extended() {
8+
for(auto x : get_vector()) {
9+
use(x); // GOOD: The lifetime of the vector returned by `get_vector()` is extended until the end of the loop.
10+
}
11+
}
12+
13+
// Writes the the values of `v` to an external log and returns it unchanged.
14+
const std::vector<int>& log_and_return_argument(const std::vector<int>& v);
15+
16+
void lifetime_of_temp_not_extended() {
17+
for(auto x : log_and_return_argument(get_vector())) {
18+
use(x); // BAD: The lifetime of the vector returned by `get_vector()` is not extended, and the behavior is undefined.
19+
}
20+
}

cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.expected

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-416/IteratorToExpiredContainer.ql

0 commit comments

Comments
 (0)