Files
codeql/cpp/ql/lib/semmle/code/cpp/dataflow/StackAddress.qll
2022-10-14 10:14:52 +02:00

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)
)
)
}