mirror of
https://github.com/github/codeql.git
synced 2026-05-20 22:27:18 +02:00
404 lines
14 KiB
Plaintext
404 lines
14 KiB
Plaintext
/**
|
|
* @name Use of expired stack-address
|
|
* @description Accessing the stack-allocated memory of a function
|
|
* after it has returned can lead to memory corruption.
|
|
* @kind path-problem
|
|
* @problem.severity error
|
|
* @security-severity 9.3
|
|
* @precision high
|
|
* @id cpp/using-expired-stack-address
|
|
* @tags reliability
|
|
* security
|
|
* external/cwe/cwe-825
|
|
*/
|
|
|
|
import cpp
|
|
// We don't actually use the global value numbering library in this query, but without it we end up
|
|
// recomputing the IR.
|
|
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
|
import semmle.code.cpp.ir.IR
|
|
|
|
predicate instructionHasVariable(VariableAddressInstruction vai, StackVariable var, Function f) {
|
|
var = vai.getAstVariable() and
|
|
f = vai.getEnclosingFunction() and
|
|
// Pointer-to-member types aren't properly handled in the dbscheme.
|
|
not vai.getResultType() instanceof PointerToMemberType and
|
|
// Rule out FPs caused by extraction errors.
|
|
not any(ErrorExpr e).getEnclosingFunction() = f
|
|
}
|
|
|
|
/**
|
|
* Holds if `source` is the base address of an address computation whose
|
|
* result is stored in `address`.
|
|
*/
|
|
predicate stackPointerFlowsToUse(Instruction address, VariableAddressInstruction source) {
|
|
address = source and
|
|
instructionHasVariable(source, _, _)
|
|
or
|
|
stackPointerFlowsToUse(address.(CopyInstruction).getSourceValue(), source)
|
|
or
|
|
stackPointerFlowsToUse(address.(ConvertInstruction).getUnary(), source)
|
|
or
|
|
stackPointerFlowsToUse(address.(CheckedConvertOrNullInstruction).getUnary(), source)
|
|
or
|
|
stackPointerFlowsToUse(address.(InheritanceConversionInstruction).getUnary(), source)
|
|
or
|
|
stackPointerFlowsToUse(address.(FieldAddressInstruction).getObjectAddress(), source)
|
|
or
|
|
stackPointerFlowsToUse(address.(PointerOffsetInstruction).getLeft(), source)
|
|
}
|
|
|
|
/**
|
|
* A HashCons-like table for comparing addresses that are
|
|
* computed relative to some global variable.
|
|
*/
|
|
newtype TGlobalAddress =
|
|
TGlobalVariable(GlobalOrNamespaceVariable v) {
|
|
// Pointer-to-member types aren't properly handled in the dbscheme.
|
|
not v.getUnspecifiedType() instanceof PointerToMemberType
|
|
} or
|
|
TLoad(TGlobalAddress address) {
|
|
address = globalAddress(any(LoadInstruction load).getSourceAddress())
|
|
} or
|
|
TConversion(string kind, TGlobalAddress address, Type fromType, Type toType) {
|
|
kind = "unchecked" and
|
|
exists(ConvertInstruction convert |
|
|
uncheckedConversionTypes(convert, fromType, toType) and
|
|
address = globalAddress(convert.getUnary())
|
|
)
|
|
or
|
|
kind = "checked" and
|
|
exists(CheckedConvertOrNullInstruction convert |
|
|
checkedConversionTypes(convert, fromType, toType) and
|
|
address = globalAddress(convert.getUnary())
|
|
)
|
|
or
|
|
kind = "inheritance" and
|
|
exists(InheritanceConversionInstruction convert |
|
|
inheritanceConversionTypes(convert, fromType, toType) and
|
|
address = globalAddress(convert.getUnary())
|
|
)
|
|
} or
|
|
TFieldAddress(TGlobalAddress address, Field f) {
|
|
exists(FieldAddressInstruction fai |
|
|
fai.getField() = f and
|
|
address = globalAddress(fai.getObjectAddress())
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
predicate uncheckedConversionTypes(ConvertInstruction convert, Type fromType, Type toType) {
|
|
fromType = convert.getUnary().getResultType() and
|
|
toType = convert.getResultType()
|
|
}
|
|
|
|
pragma[noinline]
|
|
predicate checkedConversionTypes(CheckedConvertOrNullInstruction convert, Type fromType, Type toType) {
|
|
fromType = convert.getUnary().getResultType() and
|
|
toType = convert.getResultType()
|
|
}
|
|
|
|
pragma[noinline]
|
|
predicate inheritanceConversionTypes(
|
|
InheritanceConversionInstruction convert, Type fromType, Type toType
|
|
) {
|
|
fromType = convert.getUnary().getResultType() and
|
|
toType = convert.getResultType()
|
|
}
|
|
|
|
private signature class ConversionInstruction extends UnaryInstruction;
|
|
|
|
module Conversion<ConversionInstruction I> {
|
|
signature predicate hasTypes(I instr, Type fromType, Type toType);
|
|
|
|
module Using<hasTypes/3 project> {
|
|
pragma[nomagic]
|
|
predicate hasOperandAndTypes(I convert, Instruction unary, Type fromType, Type toType) {
|
|
project(convert, fromType, toType) and
|
|
unary = convert.getUnary()
|
|
}
|
|
}
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate hasObjectAndField(FieldAddressInstruction fai, Instruction object, Field f) {
|
|
fai.getObjectAddress() = object and
|
|
fai.getField() = f
|
|
}
|
|
|
|
/** Gets the HashCons value of an address computed by `instr`, if any. */
|
|
TGlobalAddress globalAddress(Instruction instr) {
|
|
result = TGlobalVariable(instr.(VariableAddressInstruction).getAstVariable())
|
|
or
|
|
not instr instanceof LoadInstruction and
|
|
result = globalAddress(instr.(CopyInstruction).getSourceValue())
|
|
or
|
|
exists(LoadInstruction load | instr = load |
|
|
result = TLoad(globalAddress(load.getSourceAddress()))
|
|
)
|
|
or
|
|
exists(Type fromType, Type toType, Instruction unary |
|
|
Conversion<ConvertInstruction>::Using<uncheckedConversionTypes/3>::hasOperandAndTypes(instr,
|
|
unary, fromType, toType) and
|
|
result = TConversion("unchecked", globalAddress(unary), fromType, toType)
|
|
)
|
|
or
|
|
exists(Type fromType, Type toType, Instruction unary |
|
|
Conversion<CheckedConvertOrNullInstruction>::Using<checkedConversionTypes/3>::hasOperandAndTypes(instr,
|
|
unary, fromType, toType) and
|
|
result = TConversion("checked", globalAddress(unary), fromType, toType)
|
|
)
|
|
or
|
|
exists(Type fromType, Type toType, Instruction unary |
|
|
Conversion<InheritanceConversionInstruction>::Using<inheritanceConversionTypes/3>::hasOperandAndTypes(instr,
|
|
unary, fromType, toType) and
|
|
result = TConversion("inheritance", globalAddress(unary), fromType, toType)
|
|
)
|
|
or
|
|
exists(FieldAddressInstruction fai, Instruction object, Field f | instr = fai |
|
|
hasObjectAndField(fai, object, f) and
|
|
result = TFieldAddress(globalAddress(object), f)
|
|
)
|
|
or
|
|
result = globalAddress(instr.(PointerOffsetInstruction).getLeft())
|
|
}
|
|
|
|
/**
|
|
* Gets a first `StoreInstruction` that writes to address `globalAddress` reachable
|
|
* from `block`.
|
|
*/
|
|
StoreInstruction getFirstStore(IRBlock block, TGlobalAddress globalAddress) {
|
|
1 = getStoreRank(result, block, globalAddress)
|
|
or
|
|
not exists(getStoreRank(_, block, globalAddress)) and
|
|
result = getFirstStore(block.getASuccessor(), globalAddress)
|
|
}
|
|
|
|
/**
|
|
* Gets the rank of `store` in block `block` (i.e., a rank of `1` means that it is the
|
|
* first `store` to write to `globalAddress`, a rank of `2` means it's the second, etc.)
|
|
*/
|
|
int getStoreRank(StoreInstruction store, IRBlock block, TGlobalAddress globalAddress) {
|
|
blockStoresToAddress(block, _, store, globalAddress) and
|
|
store =
|
|
rank[result](StoreInstruction anotherStore, int i |
|
|
blockStoresToAddress(_, i, anotherStore, globalAddress)
|
|
|
|
|
anotherStore order by i
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a next subsequent `StoreInstruction` to write to `globalAddress`
|
|
* after `store` has done so.
|
|
*/
|
|
StoreInstruction getANextStoreTo(StoreInstruction store, TGlobalAddress globalAddress) {
|
|
exists(IRBlock block, int rnk |
|
|
rnk = getStoreRank(store, block, globalAddress) and
|
|
rnk + 1 = getStoreRank(result, block, globalAddress)
|
|
)
|
|
or
|
|
exists(IRBlock block, int rnk, IRBlock succ |
|
|
rnk = getStoreRank(store, block, globalAddress) and
|
|
not rnk + 1 = getStoreRank(_, block, globalAddress) and
|
|
succ = block.getASuccessor() and
|
|
result = getFirstStore(succ, globalAddress)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `store` copies the address of `f`'s local variable `var`
|
|
* into the address `globalAddress`.
|
|
*/
|
|
predicate stackAddressEscapes(
|
|
StoreInstruction store, StackVariable var, TGlobalAddress globalAddress, Function f
|
|
) {
|
|
globalAddress = globalAddress(store.getDestinationAddress()) and
|
|
exists(VariableAddressInstruction vai |
|
|
instructionHasVariable(pragma[only_bind_into](vai), var, f) and
|
|
stackPointerFlowsToUse(store.getSourceValue(), vai)
|
|
) and
|
|
// Ensure there's no subsequent store that overrides the global address.
|
|
not exists(getANextStoreTo(store, globalAddress))
|
|
}
|
|
|
|
predicate blockStoresToAddress(
|
|
IRBlock block, int index, StoreInstruction store, TGlobalAddress globalAddress
|
|
) {
|
|
block.getInstruction(index) = store and
|
|
globalAddress = globalAddress(store.getDestinationAddress())
|
|
}
|
|
|
|
/**
|
|
* Holds if `globalAddress` evaluates to the address of `var` (which escaped through `store` before
|
|
* returning through `call`) when control reaches `block`.
|
|
*/
|
|
predicate globalAddressPointsToStack(
|
|
StoreInstruction store, StackVariable var, CallInstruction call, IRBlock block,
|
|
TGlobalAddress globalAddress, boolean isCallBlock, boolean isStoreBlock
|
|
) {
|
|
(
|
|
if blockStoresToAddress(block, _, _, globalAddress)
|
|
then isStoreBlock = true
|
|
else isStoreBlock = false
|
|
) and
|
|
(
|
|
isCallBlock = true and
|
|
exists(Function f |
|
|
stackAddressEscapes(store, var, globalAddress, f) and
|
|
call.getStaticCallTarget() = f and
|
|
call.getBlock() = block
|
|
)
|
|
or
|
|
isCallBlock = false and
|
|
step(store, var, call, globalAddress, _, block)
|
|
)
|
|
}
|
|
|
|
pragma[inline]
|
|
int getInstructionIndex(Instruction instr, IRBlock block) { block.getInstruction(result) = instr }
|
|
|
|
predicate step(
|
|
StoreInstruction store, StackVariable var, CallInstruction call, TGlobalAddress globalAddress,
|
|
IRBlock pred, IRBlock succ
|
|
) {
|
|
exists(boolean isCallBlock, boolean isStoreBlock |
|
|
// Only recurse if there is no store to `globalAddress` in `mid`.
|
|
globalAddressPointsToStack(store, var, call, pred, globalAddress, isCallBlock, isStoreBlock)
|
|
|
|
|
// Post domination ensures that `block` is always executed after `mid`
|
|
// Domination ensures that `mid` is always executed before `block`
|
|
isStoreBlock = false and
|
|
succ.immediatelyPostDominates(pred) and
|
|
pred.immediatelyDominates(succ)
|
|
or
|
|
exists(CallInstruction anotherCall, int anotherCallIndex |
|
|
anotherCall = pred.getInstruction(anotherCallIndex) and
|
|
succ.getFirstInstruction() instanceof EnterFunctionInstruction and
|
|
succ.getEnclosingFunction() = anotherCall.getStaticCallTarget() and
|
|
(if isCallBlock = true then getInstructionIndex(call, _) < anotherCallIndex else any()) and
|
|
(
|
|
if isStoreBlock = true
|
|
then
|
|
forex(int storeIndex | blockStoresToAddress(pred, storeIndex, _, globalAddress) |
|
|
anotherCallIndex < storeIndex
|
|
)
|
|
else any()
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
newtype TPathElement =
|
|
TStore(StoreInstruction store) { globalAddressPointsToStack(store, _, _, _, _, _, _) } or
|
|
TCall(CallInstruction call, IRBlock block) {
|
|
globalAddressPointsToStack(_, _, call, block, _, _, _)
|
|
} or
|
|
TMid(IRBlock block) { step(_, _, _, _, _, block) } or
|
|
TSink(LoadInstruction load, IRBlock block) {
|
|
exists(TGlobalAddress address |
|
|
globalAddressPointsToStack(_, _, _, block, address, _, _) and
|
|
block.getAnInstruction() = load and
|
|
globalAddress(load.getSourceAddress()) = address
|
|
)
|
|
}
|
|
|
|
class PathElement extends TPathElement {
|
|
StoreInstruction asStore() { this = TStore(result) }
|
|
|
|
CallInstruction asCall(IRBlock block) { this = TCall(result, block) }
|
|
|
|
predicate isCall(IRBlock block) { exists(this.asCall(block)) }
|
|
|
|
IRBlock asMid() { this = TMid(result) }
|
|
|
|
LoadInstruction asSink(IRBlock block) { this = TSink(result, block) }
|
|
|
|
predicate isSink(IRBlock block) { exists(this.asSink(block)) }
|
|
|
|
string toString() {
|
|
result =
|
|
[
|
|
this.asStore().toString(), this.asCall(_).toString(), this.asMid().toString(),
|
|
this.asSink(_).toString()
|
|
]
|
|
}
|
|
|
|
predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
this.asStore()
|
|
.getLocation()
|
|
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
or
|
|
this.asCall(_)
|
|
.getLocation()
|
|
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
or
|
|
this.asMid().getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
or
|
|
this.asSink(_)
|
|
.getLocation()
|
|
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
}
|
|
|
|
predicate isSink(LoadInstruction load, IRBlock block, int index, TGlobalAddress globalAddress) {
|
|
block.getInstruction(index) = load and
|
|
globalAddress(load.getSourceAddress()) = globalAddress
|
|
}
|
|
|
|
query predicate edges(PathElement pred, PathElement succ) {
|
|
// Store -> caller
|
|
globalAddressPointsToStack(pred.asStore(), _, succ.asCall(_), _, _, _, _)
|
|
or
|
|
// Call -> basic block
|
|
pred.isCall(succ.asMid())
|
|
or
|
|
// Special case for when the caller goes directly to the load with no steps
|
|
// across basic blocks (i.e., caller -> sink)
|
|
exists(IRBlock block |
|
|
pred.isCall(block) and
|
|
succ.isSink(block)
|
|
)
|
|
or
|
|
// Basic block -> basic block
|
|
step(_, _, _, _, pred.asMid(), succ.asMid())
|
|
or
|
|
// Basic block -> load
|
|
succ.isSink(pred.asMid())
|
|
}
|
|
|
|
from
|
|
StoreInstruction store, StackVariable var, LoadInstruction load, CallInstruction call,
|
|
IRBlock block, boolean isCallBlock, TGlobalAddress address, boolean isStoreBlock,
|
|
PathElement source, PathElement sink, int loadIndex
|
|
where
|
|
globalAddressPointsToStack(store, var, call, block, address, isCallBlock, isStoreBlock) and
|
|
isSink(load, block, loadIndex, address) and
|
|
(
|
|
// We know that we have a sequence:
|
|
// (1) store to `address` -> (2) return from `f` -> (3) load from `address`.
|
|
// But if (2) and (3) happen in the sam block we need to check the
|
|
// block indices to ensure that (3) happens after (2).
|
|
if isCallBlock = true
|
|
then
|
|
// If so, the load must happen after the call.
|
|
getInstructionIndex(call, _) < loadIndex
|
|
else any()
|
|
) and
|
|
(
|
|
// If there is a store to the address we need to make sure that the load we found was
|
|
// before that store (So that the load doesn't read an overwritten value).
|
|
if isStoreBlock = true
|
|
then
|
|
forex(int storeIndex | blockStoresToAddress(block, storeIndex, _, address) |
|
|
loadIndex < storeIndex
|
|
)
|
|
else any()
|
|
) and
|
|
source.asStore() = store and
|
|
sink.asSink(_) = load
|
|
select sink, source, sink, "Stack variable $@ escapes $@ and is used after it has expired.", var,
|
|
var.toString(), store, "here"
|