Skip to content

Commit 7408726

Browse files
authored
Merge pull request #2312 from jbj/pointer-wraparound-query
C++: New query: Pointer overflow check
2 parents d5edb65 + c7176e5 commit 7408726

File tree

15 files changed

+219
-10
lines changed

15 files changed

+219
-10
lines changed

change-notes/1.23/analysis-cpp.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ The following changes in version 1.23 affect C/C++ analysis in all applications.
99
| **Query** | **Tags** | **Purpose** |
1010
|-----------------------------|-----------|--------------------------------------------------------------------|
1111
| Hard-coded Japanese era start date (`cpp/japanese-era/exact-era-date`) | reliability, japanese-era | This query is a combination of two old queries that were identical in purpose but separate as an implementation detail. This new query replaces Hard-coded Japanese era start date in call (`cpp/japanese-era/constructor-or-method-with-exact-era-date`) and Hard-coded Japanese era start date in struct (`cpp/japanese-era/struct-with-exact-era-date`). |
12-
| Signed overflow check (`cpp/signed-overflow-check`) | correctness, reliability | Finds overflow checks that rely on signed integer addition to overflow, which has undefined behavior. Example: `a + b < a`. |
12+
| Signed overflow check (`cpp/signed-overflow-check`) | correctness, security | Finds overflow checks that rely on signed integer addition to overflow, which has undefined behavior. Example: `a + b < a`. |
13+
| Pointer overflow check (`cpp/pointer-overflow-check`) | correctness, security | Finds overflow checks that rely on pointer addition to overflow, which has undefined behavior. Example: `ptr + a < ptr`. |
1314

1415
## Changes to existing queries
1516

cpp/ql/src/Likely Bugs/Arithmetic/PointlessSelfComparison.ql

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,13 @@
1313

1414
import cpp
1515
import PointlessSelfComparison
16+
import semmle.code.cpp.commons.Exclusions
1617

1718
from ComparisonOperation cmp
1819
where
1920
pointlessSelfComparison(cmp) and
2021
not nanTest(cmp) and
2122
not overflowTest(cmp) and
2223
not cmp.isFromTemplateInstantiation(_) and
23-
not exists(MacroInvocation mi |
24-
// cmp is in mi
25-
mi.getAnExpandedElement() = cmp and
26-
// and cmp was apparently not passed in as a macro parameter
27-
cmp.getLocation().getStartLine() = mi.getLocation().getStartLine() and
28-
cmp.getLocation().getStartColumn() = mi.getLocation().getStartColumn()
29-
)
24+
not isFromMacroDefinition(cmp)
3025
select cmp, "Self comparison."

cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/**
2-
* @name Undefined result of signed test for overflow
2+
* @name Signed overflow check
33
* @description Testing for overflow by adding a value to a variable
44
* to see if it "wraps around" works only for
55
* unsigned integer values.
66
* @kind problem
77
* @problem.severity warning
88
* @precision high
99
* @id cpp/signed-overflow-check
10-
* @tags reliability
10+
* @tags correctness
1111
* security
1212
*/
1313

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
bool not_in_range(T *ptr, T *ptr_end, size_t a) {
2+
return ptr + a >= ptr_end || ptr + a < ptr; // BAD
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
bool not_in_range(T *ptr, T *ptr_end, size_t a) {
2+
return a >= ptr_end - ptr; // GOOD
3+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
The expression <code>ptr + a &lt; ptr</code> is equivalent to <code>a &lt;
8+
0</code>, and an optimizing compiler is likely to make that replacement,
9+
thereby removing a range check that might have been necessary for security.
10+
If <code>a</code> is known to be non-negative, the compiler can even replace <code>ptr +
11+
a &lt; ptr</code> with <code>false</code>.
12+
</p>
13+
14+
<p>
15+
The reason is that pointer arithmetic overflow in C/C++ is undefined
16+
behavior. The optimizing compiler can assume that the program has no
17+
undefined behavior, which means that adding a positive number to <code>ptr</code> cannot
18+
produce a pointer less than <code>ptr</code>.
19+
</p>
20+
</overview>
21+
<recommendation>
22+
<p>
23+
To check whether an index <code>a</code> is less than the length of an array,
24+
simply compare these two numbers as unsigned integers: <code>a &lt; ARRAY_LENGTH</code>.
25+
If the length of the array is defined as the difference between two pointers
26+
<code>ptr</code> and <code>p_end</code>, write <code>a &lt; p_end - ptr</code>.
27+
If a is <code>signed</code>, cast it to <code>unsigned</code>
28+
in order to guard against negative <code>a</code>. For example, write
29+
<code>(size_t)a &lt; p_end - ptr</code>.
30+
</p>
31+
</recommendation>
32+
<example>
33+
<p>
34+
An invalid check for pointer overflow is most often seen as part of checking
35+
whether a number <code>a</code> is too large by checking first if adding the
36+
number to <code>ptr</code> goes past the end of an allocation and then
37+
checking if adding it to <code>ptr</code> creates a pointer so large that it
38+
overflows and wraps around.
39+
</p>
40+
41+
<sample src="PointerOverflow-bad.cpp" />
42+
43+
<p>
44+
In both of these checks, the operations are performed in the wrong order.
45+
First, an expression that may cause undefined behavior is evaluated
46+
(<code>ptr + a</code>), and then the result is checked for being in range.
47+
But once undefined behavior has happened in the pointer addition, it cannot
48+
be recovered from: it's too late to perform the range check after a possible
49+
pointer overflow.
50+
</p>
51+
52+
<p>
53+
While it's not the subject of this query, the expression <code>ptr + a &lt;
54+
ptr_end</code> is also an invalid range check. It's undefined behavor in
55+
C/C++ to create a pointer that points more than one past the end of an
56+
allocation.
57+
</p>
58+
59+
<p>
60+
The next example shows how to portably check whether an unsigned number is outside the
61+
range of an allocation between <code>ptr</code> and <code>ptr_end</code>.
62+
</p>
63+
<sample src="PointerOverflow-good.cpp" />
64+
</example>
65+
<references>
66+
<li>Embedded in Academia: <a href="https://blog.regehr.org/archives/1395">Pointer Overflow Checking</a>.</li>
67+
<li>LWN: <a href="https://lwn.net/Articles/278137/">GCC and pointer overflows</a>.</li>
68+
</references>
69+
</qhelp>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @name Pointer overflow check
3+
* @description Adding a value to a pointer to check if it overflows relies
4+
* on undefined behavior and may lead to memory corruption.
5+
* @kind problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id cpp/pointer-overflow-check
9+
* @tags reliability
10+
* security
11+
*/
12+
13+
import cpp
14+
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
15+
private import semmle.code.cpp.commons.Exclusions
16+
17+
from RelationalOperation ro, PointerAddExpr add, Expr expr1, Expr expr2
18+
where
19+
ro.getAnOperand() = add and
20+
add.getAnOperand() = expr1 and
21+
ro.getAnOperand() = expr2 and
22+
globalValueNumber(expr1) = globalValueNumber(expr2) and
23+
// Exclude macros but not their arguments
24+
not isFromMacroDefinition(ro) and
25+
// There must be a compilation of this file without a flag that makes pointer
26+
// overflow well defined.
27+
exists(Compilation c | c.getAFileCompiled() = ro.getFile() |
28+
not c.getAnArgument() = "-fwrapv-pointer" and
29+
not c.getAnArgument() = "-fno-strict-overflow"
30+
)
31+
select ro, "Range check relying on pointer overflow."

cpp/ql/src/semmle/code/cpp/commons/Exclusions.qll

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,26 @@ predicate functionContainsPreprocCode(Function f) {
7979
pbdStartLine >= fBlockStartLine
8080
)
8181
}
82+
83+
/**
84+
* Holds if `e` is completely or partially from a macro definition, as opposed
85+
* to being passed in as an argument.
86+
*
87+
* In the following example, the call to `f` is from a macro definition,
88+
* while `y`, `+`, `1`, and `;` are not. This assumes that no identifier apart
89+
* from `M` refers to a macro.
90+
* ```
91+
* #define M(x) f(x)
92+
* ...
93+
* M(y + 1);
94+
* ```
95+
*/
96+
predicate isFromMacroDefinition(Element e) {
97+
exists(MacroInvocation mi |
98+
// e is in mi
99+
mi.getAnExpandedElement() = e and
100+
// and e was apparently not passed in as a macro parameter
101+
e.getLocation().getStartLine() = mi.getLocation().getStartLine() and
102+
e.getLocation().getStartColumn() = mi.getLocation().getStartColumn()
103+
)
104+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// This is the example from the QLDoc of `isFromMacroDefinition`.
2+
3+
void f(int);
4+
5+
#define M(x) f(x)
6+
7+
void useM(int y) {
8+
M(y + 1);
9+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| exclusions.cpp:8:3:8:10 | call to f |

0 commit comments

Comments
 (0)