mirror of
https://github.com/github/codeql.git
synced 2025-12-21 03:06:31 +01:00
304 lines
12 KiB
Plaintext
304 lines
12 KiB
Plaintext
/**
|
|
* Provides utilities for determining which expressions contain
|
|
* stack addresses.
|
|
*/
|
|
|
|
import cpp
|
|
import semmle.code.cpp.controlflow.SSA
|
|
|
|
/**
|
|
* A stack address flows to `use`.
|
|
* The simplest case is when `use` is the expression `&var`, but
|
|
* assignments are also handled. For example:
|
|
*
|
|
* x = &var;
|
|
* y = x;
|
|
* ...y... // use of &var
|
|
*
|
|
* `useType` is the type of data which we believe was allocated on the
|
|
* stack. It is particularly important when dealing with pointers. Consider
|
|
* this example:
|
|
*
|
|
* int x[10];
|
|
* int *y = new int[10];
|
|
* ... = &x[1];
|
|
* ... = &y[1];
|
|
*
|
|
* In this example, x and y are both stack variables. But &x[1] is a
|
|
* pointer to the stack and &y[1] is a pointer to the heap. The difference
|
|
* is that the type of x is int[10], but the type of y is int*. This
|
|
* information is stored in `useType`.
|
|
*
|
|
* `source` is the origin of the stack address. It is only used to improve
|
|
* the quality of the error messages.
|
|
*
|
|
* `isLocal` is true if the stack address came from the current
|
|
* function. It is false if the stack address arrived via a function
|
|
* parameter. This information is only used to improve the quality of the
|
|
* error messages.
|
|
*/
|
|
predicate stackPointerFlowsToUse(Expr use, Type useType, Expr source, boolean isLocal) {
|
|
// Arrays in C are convertible to pointers. For example, if the type of x
|
|
// is int[10] then it is convertible to int*. The same rule applies to
|
|
// multidimensional arrays: if the type of y is int[2][3][4], then it is
|
|
// convertible to int(*)[3][4]. This conversion is what happens under the
|
|
// hood when we index an array. For example, x[5] is equivalent to
|
|
// *(x+5), so x is first converted to a pointer and then the pointer
|
|
// arithmetic is applied to the pointer.
|
|
exists(ArrayType arrayType |
|
|
stackReferenceFlowsToUse(use, arrayType, source, isLocal) and
|
|
useType = getExprPtrType(use.getConversion()).getBaseType()
|
|
)
|
|
or
|
|
// Address of: &x
|
|
stackReferenceFlowsToUse(use.(AddressOfExpr).getOperand(), useType, source, isLocal)
|
|
or
|
|
// Pointer subtract: if p is a pointer to a stack address, then p-1 is
|
|
// too.
|
|
stackPointerFlowsToUse(use.(PointerSubExpr).getLeftOperand(), useType, source, isLocal)
|
|
or
|
|
// Pointer add: if p is a pointer to a stack address, then p+1 is too.
|
|
stackPointerFlowsToUse(use.(PointerAddExpr).getAnOperand(), useType, source, isLocal)
|
|
or
|
|
// Indirect use of a stack address.
|
|
exists(SsaDefinition def, StackVariable var |
|
|
stackPointerFlowsToDef(def, var, useType, source, isLocal) and
|
|
use = def.getAUse(var)
|
|
)
|
|
or
|
|
// Use of a stack address which arrived via one of the function's
|
|
// parameters. We do not use the original pointer source here because it
|
|
// could lead to an extremely large number of results if the function has
|
|
// many callers. Instead we set `source` to the use of the parameter.
|
|
exists(SsaDefinition def, Parameter param |
|
|
pointerParamFlowsToDef(def, param, useType) and
|
|
use = def.getAUse(param) and
|
|
source = use and
|
|
isLocal = false
|
|
)
|
|
or
|
|
// Similar to the parameter case above. If a member function is called
|
|
// on an object which is allocated on the stack, then the "this" pointer
|
|
// contains a stack address.
|
|
exists(ThisExpr thisExpr |
|
|
use = thisExpr and
|
|
memberFcnMightRunOnStack(thisExpr.getEnclosingFunction(), useType) and
|
|
source = use and
|
|
isLocal = false
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Helper function for stackPointerFlowsToUse. Gets the type of a pointer
|
|
* expression.
|
|
*/
|
|
cached
|
|
private PointerType getExprPtrType(Expr use) { result = use.getUnspecifiedType() }
|
|
|
|
/**
|
|
* Holds if `use` has type `useType` and `source` is an access to a stack variable
|
|
* that flows to `use`. `isLocal` is `true` if `use` is accessed via a parameter, and
|
|
* `false` otherwise.
|
|
*/
|
|
predicate stackReferenceFlowsToUse(Expr use, Type useType, Expr source, boolean isLocal) {
|
|
// Stack variables
|
|
exists(StackVariable var |
|
|
use = source and
|
|
source = var.getAnAccess() and
|
|
isLocal = true and
|
|
// If the type of the variable is a reference type, such as int&, then
|
|
// we need to look at its definition to determine whether it contains a
|
|
// stack reference. This is handled by stackReferenceFlowsToDef, below.
|
|
not isReferenceVariable(var) and
|
|
// There is a subtlety relating to ArrayType which is hidden by the
|
|
// simplicity of the line below. Consider this example:
|
|
//
|
|
// int arraytest(int p[3][4][5]) {
|
|
// int x[3][4][5] = { ... };
|
|
// return x[1][2][3] + p[1][2][3];
|
|
// }
|
|
//
|
|
// The types of `x` and `p` are the same, but `x` is stack allocated
|
|
// and `p` is not. So we want `useType` to be an ArrayType for `x` and
|
|
// a PointerType for `p`. Luckily, this conversion happens
|
|
// automatically when the variable is used. So we get the correct type
|
|
// provided that we get it from `use` rather than from `var`.
|
|
useType = use.getUnspecifiedType()
|
|
)
|
|
or
|
|
// Accessing the field of a class, struct, or union.
|
|
exists(FieldAccess access, Class classType |
|
|
use = access and useType = access.getUnspecifiedType()
|
|
|
|
|
// Handle both x.f and x->f:
|
|
stackReferenceFlowsToUse(access.getQualifier(), classType, source, isLocal) or
|
|
stackPointerFlowsToUse(access.getQualifier(), classType, source, isLocal)
|
|
)
|
|
or
|
|
// Array indexing: x[i].
|
|
// Note that array types are converted to pointers in
|
|
// stackPointerFlowsToUse.
|
|
stackPointerFlowsToUse(use.(ArrayExpr).getArrayBase(), useType, source, isLocal)
|
|
or
|
|
// Pointer dereference: *x
|
|
stackPointerFlowsToUse(use.(PointerDereferenceExpr).getOperand(), useType, source, isLocal)
|
|
or
|
|
// Indirect use of a stack reference, via a reference variable.
|
|
exists(SsaDefinition def, StackVariable var |
|
|
stackReferenceFlowsToDef(def, var, useType, source, isLocal) and
|
|
use = def.getAUse(var)
|
|
)
|
|
or
|
|
// Use of a stack reference which arrived via one of the function's
|
|
// parameters. We do not use the original reference source here because
|
|
// it could lead to an extremely large number of results if the function
|
|
// has many callers. Instead we set `source` to the use of the parameter.
|
|
exists(SsaDefinition def, Parameter param |
|
|
referenceParamFlowsToDef(def, param, useType) and
|
|
use = def.getAUse(param) and
|
|
source = use and
|
|
isLocal = false
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Helper predicate for stackPointerFlowsToUse. Tracks the flow of stack
|
|
* addresses through SSA definitions.
|
|
*/
|
|
predicate stackPointerFlowsToDef(
|
|
SsaDefinition def, StackVariable var, Type useType, Expr source, boolean isLocal
|
|
) {
|
|
stackPointerFlowsToUse(def.getDefiningValue(var), useType, source, isLocal)
|
|
or
|
|
// Increment/decrement operators.
|
|
exists(VariableAccess access |
|
|
access = def.(CrementOperation).getOperand() and
|
|
var = access.getTarget() and
|
|
stackPointerFlowsToUse(access, useType, source, isLocal)
|
|
)
|
|
or
|
|
// Transitive closure over phi definitions.
|
|
stackPointerFlowsToDef(def.getAPhiInput(var), var, useType, source, isLocal)
|
|
}
|
|
|
|
/**
|
|
* Helper predicate for stackPointerFlowsToUse. Tracks the flow of stack
|
|
* references through SSA definitions. This predicate is almost identical
|
|
* to stackPointerFlowsToDef, except it handles references types, such as
|
|
* int&, rather than pointers.
|
|
*/
|
|
predicate stackReferenceFlowsToDef(
|
|
SsaDefinition def, StackVariable var, Type useType, Expr source, boolean isLocal
|
|
) {
|
|
// Check that the type of the variable is a reference type and delegate
|
|
// the rest of the work to stackReferenceFlowsToDef_Impl.
|
|
isReferenceVariable(var) and
|
|
stackReferenceFlowsToDef_Impl(def, var, useType, source, isLocal)
|
|
}
|
|
|
|
/**
|
|
* stackReferenceFlowsToDef delegates most of the work to this
|
|
* predicate.
|
|
*/
|
|
predicate stackReferenceFlowsToDef_Impl(
|
|
SsaDefinition def, StackVariable var, Type useType, Expr source, boolean isLocal
|
|
) {
|
|
stackReferenceFlowsToUse(def.getDefiningValue(var), useType, source, isLocal)
|
|
or
|
|
// Increment/decrement operators.
|
|
exists(VariableAccess access |
|
|
access = def.(CrementOperation).getOperand() and
|
|
var = access.getTarget() and
|
|
stackReferenceFlowsToUse(access, useType, source, isLocal)
|
|
)
|
|
or
|
|
// Transitive closure over phi definitions.
|
|
stackReferenceFlowsToDef(def.getAPhiInput(var), var, useType, source, isLocal)
|
|
}
|
|
|
|
/** The type of the variable is a reference type, such as int&. */
|
|
predicate isReferenceVariable(StackVariable var) {
|
|
var.getUnspecifiedType() instanceof ReferenceType
|
|
}
|
|
|
|
/**
|
|
* Helper predicate for stackPointerFlowsToUse. Tracks the flow of stack
|
|
* addresses which arrived through one of the function's parameters. This
|
|
* predicate is very similar to stackPointerFlowsToDef but they cannot be
|
|
* merged, because we cannot identify a sensible source expression here.
|
|
*/
|
|
predicate pointerParamFlowsToDef(SsaDefinition def, Parameter param, Type useType) {
|
|
// Only include a parameter definition if we can find a call site which
|
|
// might pass it a stack address.
|
|
exists(FunctionCall call |
|
|
call.getTarget() = param.getFunction() and
|
|
stackPointerFlowsToUse(call.getArgument(param.getIndex()), useType, _, _) and
|
|
def.definedByParameter(param)
|
|
)
|
|
or
|
|
// Transitive closure over phi definitions.
|
|
pointerParamFlowsToDef(def.getAPhiInput(param), param, useType)
|
|
}
|
|
|
|
/**
|
|
* Helper predicate for stackPointerFlowsToUse. Tracks the flow of stack
|
|
* addresses which arrived through one of the function's parameters. This
|
|
* predicate is very similar to stackPointerFlowsToDef but they cannot be
|
|
* merged, because we cannot identify a sensible source expression here.
|
|
*/
|
|
predicate referenceParamFlowsToDef(SsaDefinition def, Parameter param, Type useType) {
|
|
// Only include a parameter definition if we can find a call site which
|
|
// might pass it a stack reference.
|
|
exists(FunctionCall call |
|
|
call.getTarget() = param.getFunction() and
|
|
stackReferenceFlowsToUse(call.getArgument(param.getIndex()), useType, _, _) and
|
|
def.definedByParameter(param)
|
|
)
|
|
or
|
|
// Transitive closure over phi definitions.
|
|
referenceParamFlowsToDef(def.getAPhiInput(param), param, useType)
|
|
}
|
|
|
|
/**
|
|
* Holds if this member function might be called on an object which
|
|
* is allocated on the stack.
|
|
*/
|
|
predicate memberFcnMightRunOnStack(MemberFunction fcn, Type useType) {
|
|
exists(FunctionCall call | call.getTarget() = fcn |
|
|
// Call of the form `x.f()` where `x` is allocated on the stack.
|
|
stackReferenceFlowsToUse(call.getQualifier(), useType, _, _)
|
|
or
|
|
// Call of the form `x->f()` where `x` is allocated on the stack.
|
|
stackPointerFlowsToUse(call.getQualifier(), useType, _, _)
|
|
)
|
|
or
|
|
// Constructor calls need to be treated as a special case, because
|
|
// `call.getQualifier()` is empty.
|
|
constructorMightRunOnStack(fcn) and
|
|
useType = fcn.getDeclaringType().getUnspecifiedType()
|
|
}
|
|
|
|
/**
|
|
* Helper predicate for memberFcnMightRunOnStack. Function calls
|
|
* to constructors need to be treated as a special case, because
|
|
* `call.getQualifier()` is empty. Instead, we need to check whether
|
|
* the constructor is called from an initializer. There are several
|
|
* kinds of initializers to consider.
|
|
*/
|
|
predicate constructorMightRunOnStack(Constructor constructor) {
|
|
exists(ConstructorCall call | call.getTarget() = constructor |
|
|
// Call to a constructor from a stack variable's initializer.
|
|
exists(StackVariable var | var.getInitializer().getExpr() = call)
|
|
or
|
|
// Call to a constructor from another constructor which might
|
|
// also run on the stack.
|
|
constructorMightRunOnStack(call.getEnclosingFunction()) and
|
|
(
|
|
call instanceof ConstructorDirectInit or
|
|
call instanceof ConstructorVirtualInit or
|
|
call instanceof ConstructorDelegationInit or
|
|
exists(ConstructorFieldInit init | init.getExpr() = call)
|
|
)
|
|
)
|
|
}
|