Merge branch 'master' into rdmarsh/cpp/hasGlobalOrStdName

This commit is contained in:
Robert Marsh
2019-11-08 11:33:35 -08:00
1110 changed files with 55404 additions and 17972 deletions

View File

@@ -5,7 +5,7 @@
* @id cpp/comparison-with-wider-type
* @kind problem
* @problem.severity warning
* @precision medium
* @precision high
* @tags reliability
* security
* external/cwe/cwe-190

View File

@@ -0,0 +1,59 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>A common pattern is to initialize a local variable by calling another function (an
"initialization" function) with the address of the local variable as a pointer argument. That
function is then responsible for writing to the memory location referenced by the pointer.</p>
<p>In some cases, the called function may not always write to the memory pointed to by the
pointer argument. In such cases, the function will typically return a "status" code, informing the
caller as to whether the initialization succeeded or not. If the caller does not check the status
code before reading the local variable, it may read unitialized memory, which can result in
unexpected behavior.</p>
</overview>
<recommendation>
<p>When using a initialization function that does not guarantee to initialize the memory pointed to
by the passed pointer, and returns a status code to indicate whether such initialization occurred,
the status code should be checked before reading from the local variable.</p>
</recommendation>
<example>
<p>In this hypothetical example we have code for managing a series of devices. The code
includes a <code>DeviceConfig</code> struct that can represent properties about each device.
The <code>initDeviceConfig</code> function can be called to initialize one of these structures, by
providing a "device number", which can be used to look up the appropriate properties in some data
store. If an invalid device number is provided, the function returns a status code of
<code>-1</code>, and does not initialize the provided pointer.</p>
<p>In the first code sample below, the <code>notify</code> function calls the
<code>initDeviceConfig</code> function with a pointer to the local variable <code>config</code>,
which is then subsequently accessed to fetch properties of the device. However, the code does not
check the return value from the function call to <code>initDeviceConfig</code>. If the
device number passed to the <code>notify</code> function was invalid, the
<code>initDeviceConfig</code> function will leave the <code>config</code> variable uninitialized,
which will result in the <code>notify</code> function accessing uninitialized memory.</p>
<sample src="ConditionallyUninitializedVariableBad.c" />
<p>To fix this, the code needs to check that the return value of the call to
<code>initDeviceConfig</code> is zero. If that is true, then the calling code can safely assume
that the local variable has been initialized.</p>
<sample src="ConditionallyUninitializedVariableGood.c" />
</example>
<references>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/Uninitialized_variable">Uninitialized variable</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,33 @@
/**
* @name Conditionally uninitialized variable
* @description When an initialization function is used to initialize a local variable, but the
* returned status code is not checked, the variable may be left in an uninitialized
* state, and reading the variable may result in undefined behavior.
* @kind problem
* @problem.severity warning
* @opaque-id SM02313
* @id cpp/conditionally-uninitialized-variable
* @tags security
* external/cwe/cwe-457
*/
import cpp
import semmle.code.cpp.controlflow.SSA
private import UninitializedVariables
from
ConditionallyInitializedVariable v, ConditionalInitializationFunction f,
ConditionalInitializationCall call, string defined, Evidence e
where
exists(v.getARiskyAccess(f, call, e)) and
(
if e = DefinitionInSnapshot()
then defined = ""
else
if e = SuggestiveSALAnnotation()
then defined = "externally defined (SAL) "
else defined = "externally defined (CSV) "
)
select call,
"The status of this call to " + defined +
"$@ is not checked, potentially leaving $@ uninitialized.", f, f.getName(), v, v.getName()

View File

@@ -0,0 +1,25 @@
struct DeviceConfig {
bool isEnabled;
int channel;
};
int initDeviceConfig(DeviceConfig *ref, int deviceNumber) {
if (deviceNumber >= getMaxDevices()) {
// No device with that number, return -1 to indicate failure
return -1;
}
// Device with that number, fetch parameters and initialize struct
ref->isEnabled = fetchIsDeviceEnabled(deviceNumber);
ref->channel = fetchDeviceChannel(deviceNumber);
// Return 0 to indicate success
return 0;
}
int notify(int deviceNumber) {
DeviceConfig config;
initDeviceConfig(&config, deviceNumber);
// BAD: Using config without checking the status code that is returned
if (config.isEnabled) {
notifyChannel(config.channel);
}
}

View File

@@ -0,0 +1,27 @@
struct DeviceConfig {
bool isEnabled;
int channel;
};
int initDeviceConfig(DeviceConfig *ref, int deviceNumber) {
if (deviceNumber >= getMaxDevices()) {
// No device with that number, return -1 to indicate failure
return -1;
}
// Device with that number, fetch parameters and initialize struct
ref->isEnabled = fetchIsDeviceEnabled(deviceNumber);
ref->channel = fetchDeviceChannel(deviceNumber);
// Return 0 to indicate success
return 0;
}
void notify(int deviceNumber) {
DeviceConfig config;
int statusCode = initDeviceConfig(&config, deviceNumber);
if (statusCode == 0) {
// GOOD: Status code returned by initialization function is checked, so this is safe
if (config.isEnabled) {
notifyChannel(config.channel);
}
}
}

View File

@@ -0,0 +1,705 @@
/**
* Provides classes and predicates for identifying functions that initialize their arguments.
*/
import cpp
import external.ExternalArtifact
private import semmle.code.cpp.dispatch.VirtualDispatch
import semmle.code.cpp.NestedFields
import Microsoft.SAL
import semmle.code.cpp.controlflow.Guards
/** A context under which a function may be called. */
private newtype TContext =
/** No specific call context. */
NoContext() or
/**
* The call context is that the given other parameter is null.
*
* This context is created for all parameters that are null checked in the body of the function.
*/
ParamNull(Parameter p) { p = any(ParameterNullCheck pnc).getParameter() } or
/**
* The call context is that the given other parameter is not null.
*
* This context is created for all parameters that are null checked in the body of the function.
*/
ParamNotNull(Parameter p) { p = any(ParameterNullCheck pnc).getParameter() }
/**
* A context under which a function may be called.
*
* Some functions may conditionally initialize a parameter depending on the value of another
* parameter. Consider:
* ```
* int MyInitFunction(int* paramToBeInitialized, int* paramToCheck) {
* if (!paramToCheck) {
* // fail!
* return -1;
* }
* paramToBeInitialized = 0;
* }
* ```
* In this case, whether `paramToBeInitialized` is initialized when this function call completes
* depends on whether `paramToCheck` is or is not null. A call-context insensitive analysis will
* determine that any call to this function may leave the parameter uninitialized, even if the
* argument to paramToCheck is guaranteed to be non-null (`&foo`, for example).
*
* This class models call contexts that can be considered when calculating whether a given parameter
* initializes or not. The supported contexts are:
* - `ParamNull(otherParam)` - the given `otherParam` is considered to be null. Applies when
* exactly one parameter other than this one is null checked.
* - `ParamNotNull(otherParam)` - the given `otherParam` is considered to be not null. Applies when
* exactly one parameter other than this one is null checked.
* - `NoContext()` - applies in all other circumstances.
*/
class Context extends TContext {
string toString() {
this = NoContext() and result = "NoContext"
or
this = ParamNull(any(Parameter p | result = "ParamNull(" + p.getName() + ")"))
or
this = ParamNotNull(any(Parameter p | result = "ParamNotNull(" + p.getName() + ")"))
}
}
/**
* A check against a parameter.
*/
abstract class ParameterCheck extends Expr {
/**
* Gets a successor of this check that should be ignored for the given context.
*/
abstract ControlFlowNode getIgnoredSuccessorForContext(Context c);
}
/** A null-check expression on a parameter. */
class ParameterNullCheck extends ParameterCheck {
Parameter p;
ControlFlowNode nullSuccessor;
ControlFlowNode notNullSuccessor;
ParameterNullCheck() {
this.isCondition() and
p.getFunction() instanceof InitializationFunction and
p.getType().getUnspecifiedType() instanceof PointerType and
exists(VariableAccess va | va = p.getAnAccess() |
nullSuccessor = getATrueSuccessor() and
notNullSuccessor = getAFalseSuccessor() and
(
va = this.(NotExpr).getOperand() or
va = any(EQExpr eq | eq = this and eq.getAnOperand().getValue() = "0").getAnOperand() or
va = getAssertedFalseCondition(this) or
va = any(NEExpr eq |
eq = getAssertedFalseCondition(this) and eq.getAnOperand().getValue() = "0"
).getAnOperand()
)
or
nullSuccessor = getAFalseSuccessor() and
notNullSuccessor = getATrueSuccessor() and
(
va = this or
va = any(NEExpr eq | eq = this and eq.getAnOperand().getValue() = "0").getAnOperand() or
va = any(EQExpr eq |
eq = getAssertedFalseCondition(this) and eq.getAnOperand().getValue() = "0"
).getAnOperand()
)
)
}
/** The parameter being null-checked. */
Parameter getParameter() { result = p }
override ControlFlowNode getIgnoredSuccessorForContext(Context c) {
c = ParamNull(p) and result = notNullSuccessor
or
c = ParamNotNull(p) and result = nullSuccessor
}
/** The successor at which the parameter is confirmed to be null. */
ControlFlowNode getNullSuccessor() { result = nullSuccessor }
/** The successor at which the parameter is confirmed to be not-null. */
ControlFlowNode getNotNullSuccessor() { result = notNullSuccessor }
}
/**
* An entry in a CSV file in cond-init that contains externally defined functions that are
* conditional initializers. These files are typically produced by running the
* ConditionallyInitializedFunction companion query.
*/
class ValidatedExternalCondInitFunction extends ExternalData {
ValidatedExternalCondInitFunction() { this.getDataPath().matches("%cond-init%.csv") }
predicate isExternallyVerified(Function f, int param) {
functionSignature(f, getField(1), getField(2)) and param = getFieldAsInt(3)
}
}
/**
* The type of evidence used to determine whether a function initializes a parameter.
*/
newtype Evidence =
/**
* The function is defined in the snapshot, and the CFG has been analyzed to determine that the
* parameter is not initialized on at least one path to the exit.
*/
DefinitionInSnapshot() or
/**
* The function is externally defined, but the parameter has an `_out` SAL annotation which
* suggests that it is initialized in the function.
*/
SuggestiveSALAnnotation() or
/**
* We have been given a CSV file which indicates this parameter is conditionally initialized.
*/
ExternalEvidence()
/**
* A call to an function which initializes one or more of its parameters.
*/
class InitializationFunctionCall extends FunctionCall {
Expr initializedArgument;
InitializationFunctionCall() { initializedArgument = getAnInitializedArgument(this) }
/** Gets a parameter that is initialized by this call. */
Parameter getAnInitParameter() { result.getAnAccess() = initializedArgument }
}
/**
* A variable access which is dereferenced then assigned to.
*/
private predicate isPointerDereferenceAssignmentTarget(VariableAccess target) {
target.getParent().(PointerDereferenceExpr) = any(Assignment e).getLValue()
}
/**
* A function which initializes one or more of its parameters.
*/
class InitializationFunction extends Function {
int i;
Evidence evidence;
InitializationFunction() {
evidence = DefinitionInSnapshot() and
(
// Assignment by pointer dereferencing the parameter
isPointerDereferenceAssignmentTarget(this.getParameter(i).getAnAccess()) or
// Field wise assignment to the parameter
any(Assignment e).getLValue() = getAFieldAccess(this.getParameter(i)) or
i = this
.(MemberFunction)
.getAnOverridingFunction+()
.(InitializationFunction)
.initializedParameter() or
getParameter(i) = any(InitializationFunctionCall c).getAnInitParameter()
)
or
// If we have no definition, we look at SAL annotations
not this.isDefined() and
this.getParameter(i).(SALParameter).isOut() and
evidence = SuggestiveSALAnnotation()
or
// We have some external information that this function conditionally initializes
not this.isDefined() and
any(ValidatedExternalCondInitFunction vc).isExternallyVerified(this, i) and
evidence = ExternalEvidence()
}
/** Gets a parameter index which is initialized by this function. */
int initializedParameter() { result = i }
/** Gets a `ControlFlowNode` which assigns a new value to the parameter with the given index. */
ControlFlowNode paramReassignment(int index) {
index = i and
(
result = this.getParameter(i).getAnAccess() and
(
result = any(Assignment a).getLValue().(PointerDereferenceExpr).getOperand()
or
// Field wise assignment to the parameter
result = any(Assignment a).getLValue().(FieldAccess).getQualifier()
or
// Assignment to a nested field of the parameter
result = any(Assignment a).getLValue().(NestedFieldAccess).getUltimateQualifier()
or
result = getAnInitializedArgument(any(Call c))
or
exists(IfStmt check | result = check.getCondition().getAChild*() |
paramReassignmentCondition(check)
)
)
or
result = any(AssumeExpr e |
e.getEnclosingFunction() = this and e.getAChild().(Literal).getValue() = "0"
)
)
}
/**
* Helper predicate: holds if the `if` statement `check` contains a
* reassignment to the `i`th parameter within its `then` statement.
*/
pragma[noinline]
private predicate paramReassignmentCondition(IfStmt check) {
this.paramReassignment(i).getEnclosingStmt().getParentStmt*() = check.getThen()
}
/** Holds if `n` can be reached without the parameter at `index` being reassigned. */
predicate paramNotReassignedAt(ControlFlowNode n, int index, Context c) {
c = getAContext(index) and
(
not exists(this.getEntryPoint()) and index = i and n = this
or
n = this.getEntryPoint() and index = i
or
exists(ControlFlowNode mid | paramNotReassignedAt(mid, index, c) |
n = mid.getASuccessor() and
not n = paramReassignment(index) and
/*
* Ignore successor edges where the parameter is null, because it is then confirmed to be
* initialized.
*/
not exists(ParameterNullCheck nullCheck |
nullCheck = mid and
nullCheck = getANullCheck(index) and
n = nullCheck.getNullSuccessor()
) and
/*
* Ignore successor edges which are excluded by the given context
*/
not exists(ParameterCheck paramCheck | paramCheck = mid |
n = paramCheck.getIgnoredSuccessorForContext(c)
)
)
)
}
/** Gets a null-check on the parameter at `index`. */
private ParameterNullCheck getANullCheck(int index) {
getParameter(index) = result.getParameter()
}
/** Gets a parameter which is not at the given index. */
private Parameter getOtherParameter(int index) {
index = i and
result = getAParameter() and
not result.getIndex() = index
}
/**
* Gets a call `Context` that is applicable when considering whether parameter at the `index` can
* be conditionally initialized.
*/
Context getAContext(int index) {
index = i and
/*
* If there is one and only one other parameter which is null checked in the body of the method,
* then we have two contexts to consider - that the other param is null, or that the other param
* is not null.
*/
if
strictcount(Parameter p |
exists(Context c | c = ParamNull(p) or c = ParamNotNull(p)) and
p = getOtherParameter(index)
) = 1
then
exists(Parameter p | p = getOtherParameter(index) |
result = ParamNull(p) or result = ParamNotNull(p)
)
else
// Otherwise, only consider NoContext.
result = NoContext()
}
/**
* Holds if this function should be whitelisted - that is, not considered as conditionally
* initializing its parameters.
*/
predicate whitelisted() {
exists(string name | this.hasName(name) |
// Return value is not a success code but the output functions never fail.
name.matches("_Interlocked%")
or
// Functions that never fail, according to MSDN.
name = "QueryPerformanceCounter"
or
name = "QueryPerformanceFrequency"
or
// Functions that never fail post-Vista, according to MSDN.
name = "InitializeCriticalSectionAndSpinCount"
or
// `rand_s` writes 0 to a non-null argument if it fails, according to MSDN.
name = "rand_s"
or
// IntersectRect initializes the argument regardless of whether the input intersects
name = "IntersectRect"
or
name = "SetRect"
or
name = "UnionRect"
or
// These functions appears to have an incorrect CFG, which leads to false positives
name = "PhysicalToLogicalDPIPoint"
or
name = "LogicalToPhysicalDPIPoint"
or
// Sets NtProductType to default on error
name = "RtlGetNtProductType"
or
// Our CFG is not sophisticated enough to detect that the argument is always initialized
name = "StringCchLengthA"
or
// All paths init the argument, and always returns SUCCESS.
name = "RtlUnicodeToMultiByteSize"
or
// All paths init the argument, and always returns SUCCESS.
name = "RtlMultiByteToUnicodeSize"
or
// All paths init the argument, and always returns SUCCESS.
name = "RtlUnicodeToMultiByteN"
or
// Always initializes argument
name = "RtlGetFirstRange"
or
// Destination range is zeroed out on failure, assuming first two parameters are valid
name = "memcpy_s"
or
// This zeroes the memory unconditionally
name = "SeCreateAccessState"
)
}
}
/**
* A function which initializes one or more of its parameters, but not on all paths.
*/
class ConditionalInitializationFunction extends InitializationFunction {
Context c;
ConditionalInitializationFunction() {
c = this.getAContext(i) and
not this.whitelisted() and
exists(Type status | status = this.getType().getUnspecifiedType() |
status instanceof IntegralType or
status instanceof Enum
) and
not this.getType().getName().toLowerCase() = "size_t" and
(
/*
* If there is no definition, consider this to be conditionally initializing (based on either
* SAL or external data).
*/
not evidence = DefinitionInSnapshot()
or
/*
* If this function is defined in this snapshot, then it conditionally initializes if there
* is at least one path through the function which doesn't initialize the parameter.
*
* Explicitly ignore pure virtual functions.
*/
this.isDefined() and
this.paramNotReassignedAt(this, i, c) and
not this instanceof PureVirtualFunction
)
}
/** Gets the evidence associated with the given parameter. */
Evidence getEvidence(int param) {
/*
* Note: due to the way the predicate dispatch interacts with fields, this needs to be
* implemented on this class, not `InitializationFunction`. If implemented on the latter it
* can return evidence that does not result in conditional initialization.
*/
param = i and evidence = result
}
/** Gets the index of a parameter which is conditionally initialized. */
int conditionallyInitializedParameter(Context context) { result = i and context = c }
}
/**
* More elaborate tracking, flagging cases where the status is checked after
* the potentially uninitialized variable has been used, and ignoring cases
* where the status is not checked but there is no use of the potentially
* uninitialized variable, may be obtained via `getARiskyAccess`.
*/
class ConditionalInitializationCall extends FunctionCall {
ConditionalInitializationFunction target;
ConditionalInitializationCall() { target = getTarget(this) }
/** Gets the argument passed for the given parameter to this call. */
Expr getArgumentForParameter(Parameter p) {
p = getTarget().getAParameter() and
result = getArgument(p.getIndex())
}
/**
* Gets an argument conditionally initialized by this call.
*/
Expr getAConditionallyInitializedArgument(ConditionalInitializationFunction condTarget, Evidence e) {
condTarget = target and
exists(Context context |
result = getAConditionallyInitializedArgument(this, condTarget, context, e)
|
context = NoContext()
or
exists(Parameter otherP, Expr otherArg |
context = ParamNotNull(otherP) or
context = ParamNull(otherP)
|
otherArg = getArgumentForParameter(otherP) and
(otherArg instanceof AddressOfExpr implies context = ParamNotNull(otherP)) and
(otherArg.getType() instanceof ArrayType implies context = ParamNotNull(otherP)) and
(otherArg.getValue() = "0" implies context = ParamNull(otherP))
)
)
}
VariableAccess getAConditionallyInitializedVariable() {
not result.getTarget().getAnAssignedValue().getASuccessor+() = result and
// Should not be assigned field-wise prior to the call.
not exists(Assignment a, FieldAccess fa |
fa.getQualifier() = result.getTarget().getAnAccess() and
a.getLValue() = fa and
fa.getASuccessor+() = result
) and
result = this
.getArgument(getTarget(this)
.(ConditionalInitializationFunction)
.conditionallyInitializedParameter(_))
.(AddressOfExpr)
.getOperand()
}
Variable getStatusVariable() {
exists(AssignExpr a | a.getLValue() = result.getAnAccess() | a.getRValue() = this)
or
result.getInitializer().getExpr() = this
}
Expr getSuccessCheck() {
exists(this.getAFalseSuccessor()) and result = this
or
result = this.getParent() and
(
result instanceof NotExpr or
result.(EQExpr).getAnOperand().getValue() = "0" or
result.(GEExpr).getLesserOperand().getValue() = "0"
)
}
Expr getFailureCheck() {
result = this.getParent() and
(
result instanceof NotExpr or
result.(NEExpr).getAnOperand().getValue() = "0" or
result.(LTExpr).getLesserOperand().getValue() = "0"
)
}
private predicate inCheckedContext() {
exists(Call parent | this = parent.getAnArgument() |
parent.getTarget() instanceof Operator or
parent.getTarget().hasName("VerifyOkCatastrophic")
)
}
ControlFlowNode uncheckedReaches(LocalVariable var) {
(
not exists(var.getInitializer()) and
var = this.getAConditionallyInitializedVariable().getTarget() and
if exists(this.getFailureCheck())
then result = this.getFailureCheck().getATrueSuccessor()
else
if exists(this.getSuccessCheck())
then result = this.getSuccessCheck().getAFalseSuccessor()
else (
result = this.getASuccessor() and not this.inCheckedContext()
)
)
or
exists(ControlFlowNode mid | mid = uncheckedReaches(var) |
not mid = getStatusVariable().getAnAccess() and
not mid = var.getAnAccess() and
not exists(VariableAccess write | result = write and write = var.getAnAccess() |
write = any(AssignExpr a).getLValue() or
write = any(AddressOfExpr a).getOperand()
) and
result = mid.getASuccessor()
)
}
VariableAccess getARiskyRead(Function f) {
f = this.getTarget() and
exists(this.getFile().getRelativePath()) and
result = this.uncheckedReaches(result.getTarget()) and
not this.(GuardCondition).controls(result.getBasicBlock(), _)
}
}
/**
* Gets the position of an argument to the call which is initialized by the call.
*/
pragma[nomagic]
int initializedArgument(Call call) {
exists(InitializationFunction target |
target = getTarget(call) and
result = target.initializedParameter()
)
}
/**
* Gets an argument which is initialized by the call.
*/
Expr getAnInitializedArgument(Call call) { result = call.getArgument(initializedArgument(call)) }
/**
* Gets the position of an argument to the call to the target which is conditionally initialized by
* the call, under the given context and evidence.
*/
pragma[nomagic]
int conditionallyInitializedArgument(
Call call, ConditionalInitializationFunction target, Context c, Evidence e
) {
target = getTarget(call) and
c = target.getAContext(result) and
e = target.getEvidence(result) and
result = target.conditionallyInitializedParameter(c)
}
/**
* Gets an argument which is conditionally initialized by the call to the given target under the given context and evidence.
*/
Expr getAConditionallyInitializedArgument(
Call call, ConditionalInitializationFunction target, Context c, Evidence e
) {
result = call.getArgument(conditionallyInitializedArgument(call, target, c, e))
}
/**
* Gets the type signature for the functions parameters.
*/
string typeSig(Function f) {
result = concat(int i, Type pt |
pt = f.getParameter(i).getType()
|
pt.getUnspecifiedType().toString(), "," order by i
)
}
/**
* Holds where qualifiedName and typeSig make up the signature for the function.
*/
predicate functionSignature(Function f, string qualifiedName, string typeSig) {
qualifiedName = f.getQualifiedName() and
typeSig = typeSig(f)
}
/**
* Gets a possible definition for the undefined function by matching the undefined function name
* and parameter arity with a defined function.
*
* This is useful for identifying call to target dependencies across libraries, where the libraries
* are never statically linked together.
*/
Function getAPossibleDefinition(Function undefinedFunction) {
not undefinedFunction.isDefined() and
exists(string qn, string typeSig |
functionSignature(undefinedFunction, qn, typeSig) and functionSignature(result, qn, typeSig)
) and
result.isDefined()
}
/**
* Helper predicate for `getTarget`, that computes possible targets of a `Call`.
*
* If there is at least one defined target after performing some simple virtual dispatch
* resolution, then the result is all the defined targets.
*/
private Function getTarget1(Call c) {
result = VirtualDispatch::getAViableTarget(c) and
result.isDefined()
}
/**
* Helper predicate for `getTarget`, that computes possible targets of a `Call`.
*
* If we can use the heuristic matching of functions to find definitions for some of the viable
* targets, return those.
*/
private Function getTarget2(Call c) {
not exists(getTarget1(c)) and
result = getAPossibleDefinition(VirtualDispatch::getAViableTarget(c))
}
/**
* Helper predicate for `getTarget`, that computes possible targets of a `Call`.
*
* Otherwise, the result is the undefined `Function` instances.
*/
private Function getTarget3(Call c) {
not exists(getTarget1(c)) and
not exists(getTarget2(c)) and
result = VirtualDispatch::getAViableTarget(c)
}
/**
* Gets a possible target for the `Call`, using the name and parameter matching if we did not associate
* this call with a specific definition at link or compile time, and performing simple virtual
* dispatch resolution.
*/
Function getTarget(Call c) {
result = getTarget1(c) or
result = getTarget2(c) or
result = getTarget3(c)
}
/**
* Get an access of a field on `Variable` v.
*/
FieldAccess getAFieldAccess(Variable v) {
exists(VariableAccess va, Expr qualifierExpr |
// Find an access of the variable, or an AddressOfExpr that has the access
va = v.getAnAccess() and
(
qualifierExpr = va or
qualifierExpr.(AddressOfExpr).getOperand() = va
)
|
// Direct field access
qualifierExpr = result.getQualifier()
or
// Nested field access
qualifierExpr = result.(NestedFieldAccess).getUltimateQualifier()
)
}
/**
* Gets a condition which is asserted to be false by the given `ne` expression, according to this pattern:
* ```
* int a = !!result;
* if (!a) { // <- ne
* ....
* }
* ```
*/
Expr getAssertedFalseCondition(NotExpr ne) {
exists(LocalVariable v |
result = v.getInitializer().getExpr().(NotExpr).getOperand().(NotExpr).getOperand() and
ne.getOperand() = v.getAnAccess() and
nonAssignedVariable(v)
// and not passed by val?
)
}
pragma[noinline]
private predicate nonAssignedVariable(Variable v) { not exists(v.getAnAssignment()) }

View File

@@ -0,0 +1,190 @@
/**
* A module for identifying conditionally initialized variables.
*/
import cpp
import InitializationFunctions
// Optimised reachability predicates
private predicate reaches(ControlFlowNode a, ControlFlowNode b) = fastTC(successor/2)(a, b)
private predicate successor(ControlFlowNode a, ControlFlowNode b) { b = a.getASuccessor() }
class WhitelistedCallsConfig extends string {
WhitelistedCallsConfig() { this = "config" }
abstract predicate isWhitelisted(Call c);
}
abstract class WhitelistedCall extends Call {
override Function getTarget() { none() }
}
private predicate hasConditionalInitialization(
ConditionalInitializationFunction f, ConditionalInitializationCall call, LocalVariable v,
VariableAccess initAccess, Evidence e
) {
// Ignore whitelisted calls
not call instanceof WhitelistedCall and
f = getTarget(call) and
initAccess = v.getAnAccess() and
initAccess = call.getAConditionallyInitializedArgument(f, e).(AddressOfExpr).getOperand()
}
/**
* A variable that can be conditionally initialized by a call.
*/
class ConditionallyInitializedVariable extends LocalVariable {
ConditionalInitializationCall call;
ConditionalInitializationFunction f;
VariableAccess initAccess;
Evidence e;
ConditionallyInitializedVariable() {
// Find a call that conditionally initializes this variable
hasConditionalInitialization(f, call, this, initAccess, e) and
// Ignore cases where the variable is assigned prior to the call
not reaches(getAnAssignedValue(), initAccess) and
// Ignore cases where the variable is assigned field-wise prior to the call.
not exists(FieldAccess fa |
exists(Assignment a |
fa = getAFieldAccess(this) and
a.getLValue() = fa
)
|
reaches(fa, initAccess)
) and
// Ignore cases where the variable is assigned by a prior call to an initialization function
not exists(Call c |
getAnAccess() = getAnInitializedArgument(c).(AddressOfExpr).getOperand() and
reaches(c, initAccess)
) and
/*
* Static local variables with constant initializers do not have the initializer expr as part of
* the CFG, but should always be considered as initialized, so exclude them.
*/
not exists(getInitializer().getExpr())
}
/**
* Gets an access of the variable `v` which is not used as an lvalue, and not used as an argument
* to an initialization function.
*/
private VariableAccess getAReadAccess() {
result = this.getAnAccess() and
// Not used as an lvalue
not result = any(AssignExpr a).getLValue() and
// Not passed to another initialization function
not exists(Call c, int j | j = c.getTarget().(InitializationFunction).initializedParameter() |
result = c.getArgument(j).(AddressOfExpr).getOperand()
) and
// Not a pointless read
not result = any(ExprStmt es).getExpr()
}
/**
* Gets a read access of variable `v` that occurs after the `initializingCall`.
*/
private VariableAccess getAReadAccessAfterCall(ConditionalInitializationCall initializingCall) {
// Variable associated with this particular call
call = initializingCall and
// Access is a meaningful read access
result = getAReadAccess() and
// Which occurs after the call
reaches(call, result) and
/*
* Ignore risky accesses which are arguments to calls which also include another parameter to
* the original call. This is an attempt to eliminate results where the "status" can be checked
* through another parameter that assigned as part of the original call.
*/
not exists(Call c |
c.getAnArgument() = result or
c.getAnArgument().(AddressOfExpr).getOperand() = result
|
exists(LocalVariable lv |
call.getAnArgument().(AddressOfExpr).getOperand() = lv.getAnAccess() and
not lv = this
|
c.getAnArgument() = lv.getAnAccess()
)
)
}
/**
* Gets an access to the variable that is risky because the variable may not be initialized after
* the `call`, and the status of the call is never checked.
*/
VariableAccess getARiskyAccessWithNoStatusCheck(
ConditionalInitializationFunction initializingFunction,
ConditionalInitializationCall initializingCall, Evidence evidence
) {
// Variable associated with this particular call
call = initializingCall and
initializingFunction = f and
e = evidence and
result = getAReadAccessAfterCall(initializingCall) and
(
// Access is risky because status return code ignored completely
call instanceof ExprInVoidContext
or
// Access is risky because status return code ignored completely
exists(LocalVariable status | call = status.getAnAssignedValue() |
not exists(status.getAnAccess())
)
)
}
/**
* Gets an access to the variable that is risky because the variable may not be initialized after
* the `call`, and the status of the call is only checked after the risky access.
*/
VariableAccess getARiskyAccessBeforeStatusCheck(
ConditionalInitializationFunction initializingFunction,
ConditionalInitializationCall initializingCall, Evidence evidence
) {
// Variable associated with this particular call
call = initializingCall and
initializingFunction = f and
e = evidence and
result = getAReadAccessAfterCall(initializingCall) and
exists(LocalVariable status, Assignment a |
a.getRValue() = call and
call = status.getAnAssignedValue() and
// There exists a check of the status code
definitionUsePair(status, a, _) and
// And the check of the status code does not occur before the risky access
not exists(VariableAccess statusAccess |
definitionUsePair(status, a, statusAccess) and
reaches(statusAccess, result)
) and
// Ignore cases where the assignment to the status code is used directly
a instanceof ExprInVoidContext and
/*
* Ignore risky accesses which are arguments to calls which also include the status code.
* If both the risky value and status code are passed to a different function, that
* function is responsible for checking the status code.
*/
not exists(Call c |
c.getAnArgument() = result or
c.getAnArgument().(AddressOfExpr).getOperand() = result
|
definitionUsePair(status, a, c.getAnArgument())
)
)
}
/**
* Gets an access to the variable that is risky because the variable may not be initialized after
* the `call`.
*/
VariableAccess getARiskyAccess(
ConditionalInitializationFunction initializingFunction,
ConditionalInitializationCall initializingCall, Evidence evidence
) {
result = getARiskyAccessBeforeStatusCheck(initializingFunction, initializingCall, evidence) or
result = getARiskyAccessWithNoStatusCheck(initializingFunction, initializingCall, evidence)
}
}