Merge pull request #10562 from rdmarsh2/rdmarsh2/cpp/field-off-by-one

C++: prototype for off-by-one in array-typed field
This commit is contained in:
Mathias Vorreiter Pedersen
2022-10-06 11:04:12 +01:00
committed by GitHub
6 changed files with 271 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
#define MAX_SIZE 1024
struct FixedArray {
int buf[MAX_SIZE];
};
int main(){
FixedArray arr;
for(int i = 0; i <= MAX_SIZE; i++) {
arr.buf[i] = 0; // BAD
}
for(int i = 0; i < MAX_SIZE; i++) {
arr.buf[i] = 0; // GOOD
}
}

View File

@@ -0,0 +1,29 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>The program performs an out-of-bounds read or write operation. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.</p>
</overview>
<recommendation>
<p>Ensure that pointer dereferences are properly guarded to ensure that they cannot be used to read or write past the end of the allocation.</p>
</recommendation>
<example>
<p>The first example uses a for loop which is improperly bounded by a non-strict less-than operation and will write one position past the end of the array. The second example bounds the for loop properly with a strict less-than operation.</p>
<sample src="ConstantSizeArrayOffByOne.cpp" />
</example>
<references>
<li>CERT C Coding Standard:
<a href="https://wiki.sei.cmu.edu/confluence/display/c/ARR30-C.+Do+not+form+or+use+out-of-bounds+pointers+or+array+subscripts">ARR30-C. Do not form or use out-of-bounds pointers or array subscripts</a>.</li>
<li>
OWASP:
<a href="https://owasp.org/www-community/vulnerabilities/Buffer_Overflow">Buffer Overflow</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,107 @@
/**
* @name Constant array overflow
* @description Dereferencing a pointer that points past a statically-sized array is undefined behavior
* and may lead to security vulnerabilities
* @kind path-problem
* @problem.severity error
* @id cpp/constant-array-overflow
* @tags reliability
* security
*/
import experimental.semmle.code.cpp.semantic.analysis.RangeAnalysis
import experimental.semmle.code.cpp.semantic.SemanticBound
import experimental.semmle.code.cpp.semantic.SemanticExprSpecific
import semmle.code.cpp.ir.IR
import experimental.semmle.code.cpp.ir.dataflow.DataFlow
import experimental.semmle.code.cpp.ir.dataflow.DataFlow2
import DataFlow2::PathGraph
pragma[nomagic]
Instruction getABoundIn(SemBound b, IRFunction func) {
result = b.getExpr(0) and
result.getEnclosingIRFunction() = func
}
/**
* Holds if `i <= b + delta`.
*/
pragma[nomagic]
predicate bounded(Instruction i, Instruction b, int delta) {
exists(SemBound bound, IRFunction func |
semBounded(getSemanticExpr(i), bound, delta, true, _) and
b = getABoundIn(bound, func) and
i.getEnclosingIRFunction() = func
)
}
class FieldAddressToPointerArithmeticConf extends DataFlow::Configuration {
FieldAddressToPointerArithmeticConf() { this = "FieldAddressToPointerArithmeticConf" }
override predicate isSource(DataFlow::Node source) { isFieldAddressSource(_, source) }
override predicate isSink(DataFlow::Node sink) {
exists(PointerAddInstruction pai | pai.getLeft() = sink.asInstruction())
}
}
predicate isFieldAddressSource(Field f, DataFlow::Node source) {
source.asInstruction().(FieldAddressInstruction).getField() = f
}
/**
* Holds if `sink` is a sink for `InvalidPointerToDerefConf` and `i` is a `StoreInstruction` that
* writes to an address that non-strictly upper-bounds `sink`, or `i` is a `LoadInstruction` that
* reads from an address that non-strictly upper-bounds `sink`.
*/
predicate isInvalidPointerDerefSink(DataFlow::Node sink, Instruction i, string operation) {
exists(AddressOperand addr, int delta |
bounded(addr.getDef(), sink.asInstruction(), delta) and
delta >= 0 and
i.getAnOperand() = addr
|
i instanceof StoreInstruction and
operation = "write"
or
i instanceof LoadInstruction and
operation = "read"
)
}
predicate isConstantSizeOverflowSource(Field f, PointerAddInstruction pai, int delta) {
exists(
int size, int bound, FieldAddressToPointerArithmeticConf conf, DataFlow::Node source,
DataFlow::InstructionNode sink
|
conf.hasFlow(source, sink) and
isFieldAddressSource(f, source) and
pai.getLeft() = sink.asInstruction() and
f.getUnspecifiedType().(ArrayType).getArraySize() = size and
semBounded(getSemanticExpr(pai.getRight()), any(SemZeroBound b), bound, true, _) and
delta = bound - size and
delta >= 0 and
size != 0 and
size != 1
)
}
class PointerArithmeticToDerefConf extends DataFlow2::Configuration {
PointerArithmeticToDerefConf() { this = "PointerArithmeticToDerefConf" }
override predicate isSource(DataFlow::Node source) {
isConstantSizeOverflowSource(_, source.asInstruction(), _)
}
override predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _) }
}
from
Field f, DataFlow2::PathNode source, DataFlow2::PathNode sink, Instruction deref,
PointerArithmeticToDerefConf conf, string operation, int delta
where
conf.hasFlowPath(source, sink) and
isInvalidPointerDerefSink(sink.getNode(), deref, operation) and
isConstantSizeOverflowSource(f, source.getNode().asInstruction(), delta)
select source, source, sink,
"This pointer arithmetic may have an off-by-" + (delta + 1) +
" error allowing it to overrun $@ at this $@.", f, f.getName(), deref, operation