Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bd08c64

Browse files
committed
C++: Apply my own review comments from #2218
1 parent 18cc539 commit bd08c64

6 files changed

Lines changed: 66 additions & 52 deletions

File tree

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
bool not_in_range(T *ptr, size_t a) {
2-
return ptr + a < ptr; // BAD
1+
bool not_in_range(T *ptr, T *ptr_end, size_t a) {
2+
return ptr + a >= ptr_end || ptr + a < ptr; // BAD
33
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
bool in_range(T *ptr, T *ptr_end, size_t a) {
2-
return a < ptr_end - ptr; // GOOD
1+
bool not_in_range(T *ptr, T *ptr_end, size_t a) {
2+
return a >= ptr_end - ptr; // GOOD
33
}

cpp/ql/src/Likely Bugs/Memory Management/PointerWrapAround.qhelp

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,61 @@
44
<qhelp>
55
<overview>
66
<p>
7-
When checking for out-of-range pointer values, we might write tests like
8-
<code>p + a &lt; p</code> and check if the value "wraps around".
9-
Such a test is wrong in that it relies on the overflow of <code>p + a</code>,
10-
which has undefined behavior. In fact, many optimizing compilers will remove
11-
<code>p + a &lt; p</code> altogether and replace it with the value <code>0</code>
12-
(<code>false</code>). Conversely, should <code>p + a</code> <i>not</i> overflow,
13-
the programmer may erroneously assume that the memory location <code>p + a</code>
14-
is accessible to the program, even though it may be inaccessible (protected by
15-
the operating system) or nonexistent; writing to such memory, if allowed, can
16-
lead to memory corruption.
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 `a` 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 `ptr` cannot
18+
produce a pointer less than `ptr`.
1719
</p>
1820
</overview>
1921
<recommendation>
2022
<p>
2123
To check whether an index <code>a</code> is less than the length of an array,
2224
simply compare these two numbers as unsigned integers: <code>a &lt; ARRAY_LENGTH</code>.
2325
If the length of the array is defined as the difference between two pointers
24-
<code>p</code> and <code>p_end</code>, write <code>a &lt; p_end - p</code>.
26+
<code>ptr</code> and <code>p_end</code>, write <code>a &lt; p_end - ptr</code>.
2527
If a is <code>signed</code>, cast it to <code>unsigned</code>
2628
in order to guard against negative <code>a</code>. For example, write
27-
<code>(size_t)a &lt; p_end - p</code>.</p>
29+
<code>(size_t)a &lt; p_end - ptr</code>.
30+
</p>
2831
</recommendation>
2932
<example>
3033
<p>
31-
In the first example, an index <code>a</code>is being added to a pointer <code>p</code>
32-
to an array with elements of type <code>T</code>. Since we are not provided a
33-
separate pointer pointing to the end of the array, we fall back
34-
on a check for address "wrap-around" to see if <code>p + a</code> points at
35-
valid memory. This scheme does not work, unfortunately, since the
36-
value of <code>p + a</code> is undefined if it points at invalid memory (for
37-
example, outside our array). Even if <code>p + a</code> were to point to
38-
some accessible memory location, it would almost certainly lie
39-
<i>outside</i> the bounds of the array.
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.
4039
</p>
40+
4141
<sample src="PointerWrapAround-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 lead to 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+
4259
<p>
43-
The next example shows how to properly check for an out-of-range pointer.
44-
In order to do so, we need to obtain the value <code>ptr_end</code>
45-
representing the end of the array (or the address immediately past
46-
the end). We can then express the condition <code>p + a &lt; p_end</code> as a
47-
difference of two pointers, even if <code>p + a</code> happens to be undefined.
60+
The next example shows how to portably check whether a number is outside the
61+
range of an allocation between <code>ptr</code> and <code>ptr_end</code>.
4862
</p>
4963
<sample src="PointerWrapAround-good.cpp" />
5064
</example>
Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,31 @@
11
/**
22
* @name Reliance on pointer wrap-around
3-
* @description Testing for out-of-range pointers by adding a value to a pointer
3+
* @description Adding a value to a pointer
44
* to see if it "wraps around" is dangerous because it relies
5-
* on undefined behavior and may lead to attempted use of
6-
* nonexistent or inaccessible memory locations.
5+
* on undefined behavior and may lead to memory corruption.
76
* @kind problem
87
* @problem.severity error
98
* @precision high
10-
* @id cpp/pointer-overflow-check
9+
* @id cpp/pointer-wrap-around
1110
* @tags reliability
1211
* security
1312
*/
1413

1514
import cpp
1615
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
1716

18-
class AssertMacro extends Macro {
19-
AssertMacro() {
20-
getName() = "assert" or
21-
getName() = "_ASSERT" or
22-
getName() = "_ASSERTE" or
23-
getName() = "_ASSERT_EXPR"
24-
}
25-
}
26-
2717
from RelationalOperation ro, PointerAddExpr add, Expr expr1, Expr expr2
2818
where
2919
ro.getAnOperand() = add and
3020
add.getAnOperand() = expr1 and
3121
ro.getAnOperand() = expr2 and
3222
globalValueNumber(expr1) = globalValueNumber(expr2) and
23+
// Exclude macros except for assert macros.
24+
// TODO: port that location-based macro check we have in another query. Then
25+
// we don't need to special-case on names.
3326
not exists(MacroInvocation mi |
34-
mi.getAnAffectedElement() = add and not mi.getMacro() instanceof AssertMacro
27+
mi.getAnAffectedElement() = add and
28+
not mi.getMacroName().toLowerCase().matches("%assert%")
3529
)
36-
select ro, "Pointer out-of-range check relying on pointer overflow is undefined."
30+
// TODO: Add a check for -fno-strict-overflow and -fwrapv-pointer
31+
select ro, "Range check relying on pointer overflow."
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
| test.cpp:6:12:6:33 | ... < ... | Pointer out-of-range check relying on pointer overflow is undefined. |
2-
| test.cpp:32:12:32:24 | ... < ... | Pointer out-of-range check relying on pointer overflow is undefined. |
1+
| test.cpp:6:12:6:33 | ... < ... | Range check relying on pointer overflow. |
2+
| test.cpp:33:9:33:21 | ... < ... | Range check relying on pointer overflow. |

cpp/ql/test/query-tests/Likely Bugs/Memory Management/PointerOverflowCheck/test.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,22 @@ struct Q {
2121

2222
void foo(int untrusted_int) {
2323
Q q;
24-
if (q.begin() + untrusted_int > q.end() || // GOOD
24+
if (q.begin() + untrusted_int > q.end() || // GOOD (for the purpose of this test)
2525
q.begin() + untrusted_int < q.begin()) // BAD [NOT DETECTED]
2626
throw q;
2727
}
2828

29-
typedef unsigned long long size_t;
29+
typedef unsigned long size_t;
3030

31-
bool not_in_range(Q *ptr, size_t a) {
32-
return ptr + a < ptr; // BAD
31+
bool not_in_range_bad(Q *ptr, Q *ptr_end, size_t a) {
32+
return ptr + a >= ptr_end || // GOOD (for the purpose of this test)
33+
ptr + a < ptr; // BAD
34+
}
35+
36+
bool not_in_range_good(Q *ptr, Q *ptr_end, size_t a) {
37+
return a >= ptr_end - ptr; // GOOD
3338
}
3439

3540
bool in_range(Q *ptr, Q *ptr_end, size_t a) {
3641
return a < ptr_end - ptr; // GOOD
37-
}
42+
}

0 commit comments

Comments
 (0)