mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
C++: Apply my own review comments from #2218
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
bool not_in_range(T *ptr, size_t a) {
|
||||
return ptr + a < ptr; // BAD
|
||||
bool not_in_range(T *ptr, T *ptr_end, size_t a) {
|
||||
return ptr + a >= ptr_end || ptr + a < ptr; // BAD
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
bool in_range(T *ptr, T *ptr_end, size_t a) {
|
||||
return a < ptr_end - ptr; // GOOD
|
||||
bool not_in_range(T *ptr, T *ptr_end, size_t a) {
|
||||
return a >= ptr_end - ptr; // GOOD
|
||||
}
|
||||
@@ -4,16 +4,18 @@
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
When checking for out-of-range pointer values, we might write tests like
|
||||
<code>p + a < p</code> and check if the value "wraps around".
|
||||
Such a test is wrong in that it relies on the overflow of <code>p + a</code>,
|
||||
which has undefined behavior. In fact, many optimizing compilers will remove
|
||||
<code>p + a < p</code> altogether and replace it with the value <code>0</code>
|
||||
(<code>false</code>). Conversely, should <code>p + a</code> <i>not</i> overflow,
|
||||
the programmer may erroneously assume that the memory location <code>p + a</code>
|
||||
is accessible to the program, even though it may be inaccessible (protected by
|
||||
the operating system) or nonexistent; writing to such memory, if allowed, can
|
||||
lead to memory corruption.
|
||||
The expression <code>ptr + a < ptr</code> is equivalent to <code>a <
|
||||
0</code>, and an optimizing compiler is likely to make that replacement,
|
||||
thereby removing a range check that might have been necessary for security.
|
||||
If `a` is known to be non-negative, the compiler can even replace <code>ptr +
|
||||
a < ptr</code> with <code>false</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The reason is that pointer arithmetic overflow in C/C++ is undefined
|
||||
behavior. The optimizing compiler can assume that the program has no
|
||||
undefined behavior, which means that adding a positive number to `ptr` cannot
|
||||
produce a pointer less than `ptr`.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
@@ -21,30 +23,42 @@ lead to memory corruption.
|
||||
To check whether an index <code>a</code> is less than the length of an array,
|
||||
simply compare these two numbers as unsigned integers: <code>a < ARRAY_LENGTH</code>.
|
||||
If the length of the array is defined as the difference between two pointers
|
||||
<code>p</code> and <code>p_end</code>, write <code>a < p_end - p</code>.
|
||||
<code>ptr</code> and <code>p_end</code>, write <code>a < p_end - ptr</code>.
|
||||
If a is <code>signed</code>, cast it to <code>unsigned</code>
|
||||
in order to guard against negative <code>a</code>. For example, write
|
||||
<code>(size_t)a < p_end - p</code>.</p>
|
||||
<code>(size_t)a < p_end - ptr</code>.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In the first example, an index <code>a</code>is being added to a pointer <code>p</code>
|
||||
to an array with elements of type <code>T</code>. Since we are not provided a
|
||||
separate pointer pointing to the end of the array, we fall back
|
||||
on a check for address "wrap-around" to see if <code>p + a</code> points at
|
||||
valid memory. This scheme does not work, unfortunately, since the
|
||||
value of <code>p + a</code> is undefined if it points at invalid memory (for
|
||||
example, outside our array). Even if <code>p + a</code> were to point to
|
||||
some accessible memory location, it would almost certainly lie
|
||||
<i>outside</i> the bounds of the array.
|
||||
An invalid check for pointer overflow is most often seen as part of checking
|
||||
whether a number <code>a</code> is too large by checking first if adding the
|
||||
number to <code>ptr</code> goes past the end of an allocation and then
|
||||
checking if adding it to <code>ptr</code> creates a pointer so large that it
|
||||
overflows and wraps around.
|
||||
</p>
|
||||
|
||||
<sample src="PointerWrapAround-bad.cpp" />
|
||||
|
||||
<p>
|
||||
The next example shows how to properly check for an out-of-range pointer.
|
||||
In order to do so, we need to obtain the value <code>ptr_end</code>
|
||||
representing the end of the array (or the address immediately past
|
||||
the end). We can then express the condition <code>p + a < p_end</code> as a
|
||||
difference of two pointers, even if <code>p + a</code> happens to be undefined.
|
||||
In both of these checks, the operations are performed in the wrong order.
|
||||
First, an expression that may lead to undefined behavior is evaluated
|
||||
(<code>ptr + a</code>), and then the result is checked for being in range.
|
||||
But once undefined behavior has happened in the pointer addition, it cannot
|
||||
be recovered from: it's too late to perform the range check after a possible
|
||||
pointer overflow.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
While it's not the subject of this query, the expression <code>ptr + a <
|
||||
ptr_end</code> is also an invalid range check. It's undefined behavor in
|
||||
C/C++ to create a pointer that points more than one past the end of an
|
||||
allocation.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The next example shows how to portably check whether a number is outside the
|
||||
range of an allocation between <code>ptr</code> and <code>ptr_end</code>.
|
||||
</p>
|
||||
<sample src="PointerWrapAround-good.cpp" />
|
||||
</example>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/**
|
||||
* @name Reliance on pointer wrap-around
|
||||
* @description Testing for out-of-range pointers by adding a value to a pointer
|
||||
* @description Adding a value to a pointer
|
||||
* to see if it "wraps around" is dangerous because it relies
|
||||
* on undefined behavior and may lead to attempted use of
|
||||
* nonexistent or inaccessible memory locations.
|
||||
* on undefined behavior and may lead to memory corruption.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id cpp/pointer-overflow-check
|
||||
* @id cpp/pointer-wrap-around
|
||||
* @tags reliability
|
||||
* security
|
||||
*/
|
||||
@@ -15,22 +14,18 @@
|
||||
import cpp
|
||||
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
class AssertMacro extends Macro {
|
||||
AssertMacro() {
|
||||
getName() = "assert" or
|
||||
getName() = "_ASSERT" or
|
||||
getName() = "_ASSERTE" or
|
||||
getName() = "_ASSERT_EXPR"
|
||||
}
|
||||
}
|
||||
|
||||
from RelationalOperation ro, PointerAddExpr add, Expr expr1, Expr expr2
|
||||
where
|
||||
ro.getAnOperand() = add and
|
||||
add.getAnOperand() = expr1 and
|
||||
ro.getAnOperand() = expr2 and
|
||||
globalValueNumber(expr1) = globalValueNumber(expr2) and
|
||||
// Exclude macros except for assert macros.
|
||||
// TODO: port that location-based macro check we have in another query. Then
|
||||
// we don't need to special-case on names.
|
||||
not exists(MacroInvocation mi |
|
||||
mi.getAnAffectedElement() = add and not mi.getMacro() instanceof AssertMacro
|
||||
mi.getAnAffectedElement() = add and
|
||||
not mi.getMacroName().toLowerCase().matches("%assert%")
|
||||
)
|
||||
select ro, "Pointer out-of-range check relying on pointer overflow is undefined."
|
||||
// TODO: Add a check for -fno-strict-overflow and -fwrapv-pointer
|
||||
select ro, "Range check relying on pointer overflow."
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
| test.cpp:6:12:6:33 | ... < ... | Pointer out-of-range check relying on pointer overflow is undefined. |
|
||||
| test.cpp:32:12:32:24 | ... < ... | Pointer out-of-range check relying on pointer overflow is undefined. |
|
||||
| test.cpp:6:12:6:33 | ... < ... | Range check relying on pointer overflow. |
|
||||
| test.cpp:33:9:33:21 | ... < ... | Range check relying on pointer overflow. |
|
||||
|
||||
@@ -21,17 +21,22 @@ struct Q {
|
||||
|
||||
void foo(int untrusted_int) {
|
||||
Q q;
|
||||
if (q.begin() + untrusted_int > q.end() || // GOOD
|
||||
if (q.begin() + untrusted_int > q.end() || // GOOD (for the purpose of this test)
|
||||
q.begin() + untrusted_int < q.begin()) // BAD [NOT DETECTED]
|
||||
throw q;
|
||||
}
|
||||
|
||||
typedef unsigned long long size_t;
|
||||
typedef unsigned long size_t;
|
||||
|
||||
bool not_in_range(Q *ptr, size_t a) {
|
||||
return ptr + a < ptr; // BAD
|
||||
bool not_in_range_bad(Q *ptr, Q *ptr_end, size_t a) {
|
||||
return ptr + a >= ptr_end || // GOOD (for the purpose of this test)
|
||||
ptr + a < ptr; // BAD
|
||||
}
|
||||
|
||||
bool not_in_range_good(Q *ptr, Q *ptr_end, size_t a) {
|
||||
return a >= ptr_end - ptr; // GOOD
|
||||
}
|
||||
|
||||
bool in_range(Q *ptr, Q *ptr_end, size_t a) {
|
||||
return a < ptr_end - ptr; // GOOD
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user