Merge pull request #2151 from raulgarciamsft/users/raul/oss

Users/raul/oss
This commit is contained in:
Tom Hvitved
2019-10-24 19:35:40 +02:00
committed by GitHub
81 changed files with 3469 additions and 43 deletions

View File

@@ -0,0 +1,174 @@
import cpp
import semmle.code.cpp.dataflow.TaintTracking
private import semmle.code.cpp.dataflow.RecursionPrevention
/**
* A buffer which includes an allocation size.
*/
abstract class BufferWithSize extends DataFlow::Node {
abstract Expr getSizeExpr();
BufferAccess getAnAccess() {
any(BufferWithSizeConfig bsc).hasFlow(this, DataFlow::exprNode(result.getPointer()))
}
}
/** An allocation function. */
abstract class Alloc extends Function { }
/**
* Allocation functions identified by the QL for C/C++ standard library.
*/
class DefaultAlloc extends Alloc {
DefaultAlloc() { allocationFunction(this) }
}
/** A buffer created through a call to an allocation function. */
class AllocBuffer extends BufferWithSize {
FunctionCall call;
AllocBuffer() {
asExpr() = call and
call.getTarget() instanceof Alloc
}
override Expr getSizeExpr() { result = call.getArgument(0) }
}
/**
* Find accesses of buffers for which we have a size expression.
*/
private class BufferWithSizeConfig extends TaintTracking::Configuration {
BufferWithSizeConfig() { this = "BufferWithSize" }
override predicate isSource(DataFlow::Node n) { n = any(BufferWithSize b) }
override predicate isSink(DataFlow::Node n) { n.asExpr() = any(BufferAccess ae).getPointer() }
override predicate isSanitizer(DataFlow::Node s) {
s = any(BufferWithSize b) and
s.asExpr().getControlFlowScope() instanceof Alloc
}
}
/**
* An access (read or write) to a buffer, provided as a pair of
* a pointer to the buffer and the length of data to be read or written.
* Extend this class to support different kinds of buffer access.
*/
abstract class BufferAccess extends Locatable {
/** Gets the pointer to the buffer being accessed. */
abstract Expr getPointer();
/** Gets the length of the data being read or written by this buffer access. */
abstract Expr getAccessedLength();
}
/**
* A buffer access through an array expression.
*/
class ArrayBufferAccess extends BufferAccess, ArrayExpr {
override Expr getPointer() { result = this.getArrayBase() }
override Expr getAccessedLength() { result = this.getArrayOffset() }
}
/**
* A buffer access through an overloaded array expression.
*/
class OverloadedArrayBufferAccess extends BufferAccess, OverloadedArrayExpr {
override Expr getPointer() { result = this.getQualifier() }
override Expr getAccessedLength() { result = this.getAnArgument() }
}
/**
* A buffer access through pointer arithmetic.
*/
class PointerArithmeticAccess extends BufferAccess, Expr {
PointerArithmeticOperation p;
PointerArithmeticAccess() {
this = p and
p.getAnOperand().getType().getUnspecifiedType() instanceof IntegralType and
not p.getParent() instanceof ComparisonOperation
}
override Expr getPointer() {
result = p.getAnOperand() and
result.getType().getUnspecifiedType() instanceof PointerType
}
override Expr getAccessedLength() {
result = p.getAnOperand() and
result.getType().getUnspecifiedType() instanceof IntegralType
}
}
/**
* A pair of buffer accesses through a call to memcpy.
*/
class MemCpy extends BufferAccess, FunctionCall {
MemCpy() { getTarget().hasName("memcpy") }
override Expr getPointer() {
result = getArgument(0) or
result = getArgument(1)
}
override Expr getAccessedLength() { result = getArgument(2) }
}
class StrncpySizeExpr extends BufferAccess, FunctionCall {
StrncpySizeExpr() { getTarget().hasName("strncpy") }
override Expr getPointer() {
result = getArgument(0) or
result = getArgument(1)
}
override Expr getAccessedLength() { result = getArgument(2) }
}
class RecvSizeExpr extends BufferAccess, FunctionCall {
RecvSizeExpr() { getTarget().hasName("recv") }
override Expr getPointer() { result = getArgument(1) }
override Expr getAccessedLength() { result = getArgument(2) }
}
class SendSizeExpr extends BufferAccess, FunctionCall {
SendSizeExpr() { getTarget().hasName("send") }
override Expr getPointer() { result = getArgument(1) }
override Expr getAccessedLength() { result = getArgument(2) }
}
class SnprintfSizeExpr extends BufferAccess, FunctionCall {
SnprintfSizeExpr() { getTarget().hasName("snprintf") }
override Expr getPointer() { result = getArgument(0) }
override Expr getAccessedLength() { result = getArgument(1) }
}
class MemcmpSizeExpr extends BufferAccess, FunctionCall {
MemcmpSizeExpr() { getTarget().hasName("Memcmp") }
override Expr getPointer() {
result = getArgument(0) or
result = getArgument(1)
}
override Expr getAccessedLength() { result = getArgument(2) }
}
class MallocSizeExpr extends BufferAccess, FunctionCall {
MallocSizeExpr() { getTarget().hasName("malloc") }
override Expr getPointer() { none() }
override Expr getAccessedLength() { result = getArgument(1) }
}

View File

@@ -0,0 +1,6 @@
int get_number_from_network();
int process_network(int[] buff, int buffSize) {
int i = ntohl(get_number_from_network());
return buff[i];
}

View File

@@ -0,0 +1,10 @@
uint32_t get_number_from_network();
int process_network(int[] buff, uint32_t buffSize) {
uint32_t i = ntohl(get_number_from_network());
if (i < buffSize) {
return buff[i];
} else {
return -1;
}
}

View File

@@ -0,0 +1,33 @@
import cpp
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.controlflow.Guards
import BufferAccess
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
class NetworkFunctionCall extends FunctionCall {
NetworkFunctionCall() {
getTarget().hasName("ntohd") or
getTarget().hasName("ntohf") or
getTarget().hasName("ntohl") or
getTarget().hasName("ntohll") or
getTarget().hasName("ntohs")
}
}
class NetworkToBufferSizeConfiguration extends DataFlow::Configuration {
NetworkToBufferSizeConfiguration() { this = "NetworkToBufferSizeConfiguration" }
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof NetworkFunctionCall }
override predicate isSink(DataFlow::Node node) {
node.asExpr() = any(BufferAccess ba).getAccessedLength()
}
override predicate isBarrier(DataFlow::Node node) {
exists(GuardCondition gc, GVN gvn |
gc.getAChild*() = gvn.getAnExpr() and
globalValueNumber(node.asExpr()) = gvn and
gc.controls(node.asExpr().getBasicBlock(), _)
)
}
}

View File

@@ -0,0 +1,54 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Data received over a network connection may be received in a different byte order than
the byte order used by the local host, making the data difficult to process. To address this,
data received over the wire is usually converted to host byte order by a call to a network-to-host
byte order function, such as <code>ntohl</code>.
</p>
<p>
The use of a network-to-host byte order function is therefore a good indicator that the returned
value is unvalidated data retrieved from the network, and should not be used without further
validation. In particular, the returned value should not be used as an array index or array length
value without validation, which may result in a buffer overflow vulnerability.
</p>
</overview>
<recommendation>
<p>
Validate data returned by network-to-host byte order functions before use and especially before
using the value as an array index or bound.
</p>
</recommendation>
<example>
<p>In the example below, network data is retrieved and passed to <code>ntohl</code> to convert
it to host byte order. The data is then used as an index in an array access expression. However,
there is no validation that the data returned by <code>ntohl</code> is within the bounds of the array,
which could lead to reading outside the bounds of the buffer.
</p>
<sample src="NtohlArrayBad.cpp" />
<p>In the corrected example, the returned data is validated against the known size of the buffer,
before being used as an array index.</p>
<sample src="NtohlArrayGood.cpp" />
</example>
<references>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-ntohl">
ntohl - winsock reference
</a>
</li>
<li>
<a href="https://linux.die.net/man/3/ntohl">
ntohl - Linux man page
</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,17 @@
/**
* @id cpp/network-to-host-function-as-array-bound
* @name Untrusted network-to-host usage
* @description Using the result of a network-to-host byte order function, such as ntohl, as an
* array bound or length value without checking it may result in buffer overflows or
* other vulnerabilties.
* @kind problem
* @problem.severity error
*/
import cpp
import NtohlArrayNoBound
import semmle.code.cpp.dataflow.DataFlow
from NetworkToBufferSizeConfiguration bufConfig, DataFlow::Node source, DataFlow::Node sink
where bufConfig.hasFlow(source, sink)
select sink, "Unchecked use of data from network function $@", source, source.toString()

View File

@@ -2,29 +2,45 @@ import cpp
class SALMacro extends Macro {
SALMacro() {
this.getFile().getBaseName() = "sal.h" or
this.getFile().getBaseName() = "specstrings_strict.h" or
this.getFile().getBaseName() = "specstrings.h"
exists(string filename | filename = this.getFile().getBaseName() |
filename = "sal.h" or
filename = "specstrings_strict.h" or
filename = "specstrings.h" or
filename = "w32p.h" or
filename = "minwindef.h"
) and
(
// Dialect for Windows 8 and above
this.getName().matches("\\_%\\_")
or
// Dialect for Windows 7
this.getName().matches("\\_\\_%")
)
}
}
pragma[noinline]
predicate isTopLevelMacroAccess(MacroAccess ma) { not exists(ma.getParentInvocation()) }
class SALAnnotation extends MacroInvocation {
SALAnnotation() {
this.getMacro() instanceof SALMacro and
not exists(this.getParentInvocation())
isTopLevelMacroAccess(this)
}
/** Returns the `Declaration` annotated by `this`. */
Declaration getDeclaration() { annotatesAt(this, result.getADeclarationEntry(), _, _) }
Declaration getDeclaration() {
annotatesAt(this, result.getADeclarationEntry(), _, _) and
not result instanceof Type // exclude typedefs
}
/** Returns the `DeclarationEntry` annotated by `this`. */
DeclarationEntry getDeclarationEntry() { annotatesAt(this, result, _, _) }
DeclarationEntry getDeclarationEntry() {
annotatesAt(this, result, _, _) and
not result instanceof TypeDeclarationEntry // exclude typedefs
}
}
/*
* Particular SAL annotations of interest
*/
class SALCheckReturn extends SALAnnotation {
SALCheckReturn() {
exists(SALMacro m | m = this.getMacro() |
@@ -39,8 +55,8 @@ class SALNotNull extends SALAnnotation {
exists(SALMacro m | m = this.getMacro() |
not m.getName().matches("%\\_opt\\_%") and
(
m.getName().matches("\\_In%") or
m.getName().matches("\\_Out%") or
m.getName().matches("_In%") or
m.getName().matches("_Out%") or
m.getName() = "_Ret_notnull_"
)
) and
@@ -63,42 +79,124 @@ class SALMaybeNull extends SALAnnotation {
}
}
/*
* Implementation details
///////////////////////////////////////////////////////////////////////////////
// Implementation details
/**
* Holds if `a` annotates the declaration entry `d` and
* its start position is the `idx`th position in `file` that holds a SAL element.
*/
private predicate annotatesAt(SALAnnotation a, DeclarationEntry e, File file, int idx) {
a = salElementAt(file, idx) and
(
// Base case: `a` right before `e`
e = salElementAt(file, idx + 1)
or
// Recursive case: `a` right before some annotation on `e`
annotatesAt(_, e, file, idx + 1)
)
}
library class SALElement extends Element {
SALElement() {
this instanceof DeclarationEntry or
this instanceof SALAnnotation
}
}
/** Gets the `idx`th `SALElement` in `file`. */
private SALElement salElementAt(File file, int idx) {
interestingLoc(file, result, interestingStartPos(file, idx))
predicate annotatesAt(SALAnnotation a, DeclarationEntry d, File file, int idx) {
annotatesAtPosition(a.(SALElement).getStartPosition(), d, file, idx)
}
/**
* Holds if an SALElement element at character `result` comes at
* position `idx` in `file`.
* Holds if `pos` is the `idx`th position in `file` that holds a SAL element,
* which annotates the declaration entry `d` (by occurring before it without
* any other declaration entries in between).
*/
private int interestingStartPos(File file, int idx) {
result = rank[idx](int otherStart | interestingLoc(file, _, otherStart))
// For performance reasons, do not mention the annotation itself here,
// but compute with positions instead. This performs better on databases
// with many annotations at the same position.
private predicate annotatesAtPosition(SALPosition pos, DeclarationEntry d, File file, int idx) {
pos = salRelevantPositionAt(file, idx) and
salAnnotationPos(pos) and
(
// Base case: `pos` right before `d`
d.(SALElement).getStartPosition() = salRelevantPositionAt(file, idx + 1)
or
// Recursive case: `pos` right before some annotation on `d`
annotatesAtPosition(_, d, file, idx + 1)
)
}
/** Holds if `element` in `file` is at character `startPos`. */
private predicate interestingLoc(File file, SALElement element, int startPos) {
element.getLocation().charLoc(file, startPos, _)
/**
* A parameter annotated by one or more SAL annotations.
*/
class SALParameter extends Parameter {
/** One of this parameter's annotations. */
SALAnnotation a;
SALParameter() { annotatesAt(a, this.getADeclarationEntry(), _, _) }
predicate isIn() { a.getMacroName().toLowerCase().matches("%\\_in%") }
predicate isOut() { a.getMacroName().toLowerCase().matches("%\\_out%") }
predicate isInOut() { a.getMacroName().toLowerCase().matches("%\\_inout%") }
}
/**
* A SAL element, i.e. a SAL annotation or a declaration entry
* that may have SAL annotations.
*/
library class SALElement extends Element {
SALElement() {
containsSALAnnotation(this.(DeclarationEntry).getFile()) or
this instanceof SALAnnotation
}
predicate hasStartPosition(File file, int line, int col) {
exists(Location loc | loc = this.getLocation() |
file = loc.getFile() and
line = loc.getStartLine() and
col = loc.getStartColumn()
)
}
predicate hasEndPosition(File file, int line, int col) {
exists(Location loc |
loc = this.(FunctionDeclarationEntry).getBlock().getLocation()
or
this = any(VariableDeclarationEntry vde |
vde.isDefinition() and
loc = vde.getVariable().getInitializer().getLocation()
)
|
file = loc.getFile() and
line = loc.getEndLine() and
col = loc.getEndColumn()
)
}
SALPosition getStartPosition() {
exists(File file, int line, int col |
this.hasStartPosition(file, line, col) and
result = MkSALPosition(file, line, col)
)
}
}
/** Holds if `file` contains a SAL annotation. */
pragma[noinline]
private predicate containsSALAnnotation(File file) { any(SALAnnotation a).getFile() = file }
/**
* A source-file position of a `SALElement`. Unlike location, this denotes a
* point in the file rather than a range.
*/
private newtype SALPosition =
MkSALPosition(File file, int line, int col) {
exists(SALElement e |
e.hasStartPosition(file, line, col)
or
e.hasEndPosition(file, line, col)
)
}
/** Holds if `pos` is the start position of a SAL annotation. */
pragma[noinline]
private predicate salAnnotationPos(SALPosition pos) {
any(SALAnnotation a).(SALElement).getStartPosition() = pos
}
/**
* Gets the `idx`th position in `file` that holds a SAL element,
* ordering positions lexicographically by their start line and start column.
*/
private SALPosition salRelevantPositionAt(File file, int idx) {
result = rank[idx](SALPosition pos, int line, int col |
pos = MkSALPosition(file, line, col)
|
pos order by line, col
)
}

View File

@@ -0,0 +1,25 @@
/**
* @name SAL requires inspecting return value
* @description When a return value is discarded even though the SAL annotation
* requires inspecting it, a recoverable error may turn into a
* whole-program crash.
* @kind problem
* @problem.severity warning
* @tags reliability
* external/cwe/cwe-573
* external/cwe/cwe-252
* @opaque-id SM02344
* @microsoft.severity Important
* @id cpp/ignorereturnvaluesal
*/
import Microsoft.SAL
from Function f, FunctionCall call
where
call.getTarget() = f and
call instanceof ExprInVoidContext and
any(SALCheckReturn a).getDeclaration() = f and
not any(Options o).okToIgnoreReturnValue(call)
select call, "Return value of $@ discarded although a SAL annotation " + "requires inspecting it.",
f, f.getName()

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 would 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 behaviour.
* @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,690 @@
/**
* 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()
}
/**
* 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) {
if VirtualDispatch::getAViableTarget(c).isDefined()
then
/*
* If there is at least one defined target after performing some simple virtual dispatch
* resolution, then the result is all the defined targets.
*/
result = VirtualDispatch::getAViableTarget(c) and
result.isDefined()
else
if exists(getAPossibleDefinition(VirtualDispatch::getAViableTarget(c)))
then
/*
* If we can use the heuristic matching of functions to find definitions for some of the viable
* targets, return those.
*/
result = getAPossibleDefinition(VirtualDispatch::getAViableTarget(c))
else
// Otherwise, the result is the undefined `Function` instances
result = VirtualDispatch::getAViableTarget(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)
}
}

View File

@@ -0,0 +1,41 @@
import cpp
/**
* Gets a `Field` that is nested within the given `Struct`.
*
* This identifies `Field`s which are located in the same memory
*/
private Field getANestedField(Struct s) {
result = s.getAField()
or
exists(NestedStruct ns |
s = ns.getDeclaringType() and
result = getANestedField(ns)
)
}
/**
* Unwraps a series of field accesses to determine the inner-most qualifier.
*/
private Expr getUltimateQualifier(FieldAccess fa) {
exists(Expr qualifier | qualifier = fa.getQualifier() |
result = getUltimateQualifier(qualifier)
or
not qualifier instanceof FieldAccess and result = qualifier
)
}
/**
* Accesses to nested fields.
*/
class NestedFieldAccess extends FieldAccess {
Expr ultimateQualifier;
NestedFieldAccess() {
ultimateQualifier = getUltimateQualifier(this) and
getTarget() = getANestedField(ultimateQualifier.getType().stripType())
}
/** Gets the ultimate qualifier of this nested field access. */
Expr getUltimateQualifier() { result = ultimateQualifier }
}

View File

@@ -0,0 +1,83 @@
import cpp
/**
* A module for performing simple virtual dispatch analysis.
*/
module VirtualDispatch {
/**
* Gets a possible implementation target when the given function is the static target of a virtual call.
*/
private MemberFunction getAPossibleImplementation(MemberFunction staticTarget) {
/*
* `IUnknown` is a COM interface with many sub-types, and many overrides (tens of thousands on
* some databases), so we ignore any member functions defined within that interface.
*/
not staticTarget.getDeclaringType().hasName("IUnknown") and
result = staticTarget.getAnOverridingFunction*()
}
/** Gets the static type of the qualifier expression for the given call. */
private Class getCallQualifierType(FunctionCall c) {
result = c.getQualifier().getType().stripType() and
/*
* `IUnknown` is a COM interface with many sub-types (tens of thousands on some databases), so
* we ignore any cases where the qualifier type is that interface.
*/
not result.hasName("IUnknown")
}
/**
* Gets a viable target for the given function call.
*
* If `c` is a virtual call, then we will perform a simple virtual dispatch analysis to return
* the `Function` instances which might be a viable target, based on an analysis of the declared
* type of the qualifier expression.
*
* (This analysis is imprecise: it looks for subtypes of the declared type of the qualifier expression
* and the possible implementations of `c.getTarget()` that are declared or inherited by those subtypes.
* This does not account for virtual inheritance and the ways this affects dispatch.)
*
* If `c` is not a virtual call, the result will be `c.getTarget()`.
*/
Function getAViableTarget(Call c) {
exists(Function staticTarget | staticTarget = c.getTarget() |
if c.(FunctionCall).isVirtual() and staticTarget instanceof MemberFunction
then
exists(Class qualifierType, Class qualifierSubType |
result = getAPossibleImplementation(staticTarget) and
qualifierType = getCallQualifierType(c) and
qualifierType = qualifierSubType.getABaseClass*() and
mayInherit(qualifierSubType, result) and
not cannotInherit(qualifierSubType, result)
)
else result = staticTarget
)
}
/** Holds if `f` is declared in `c` or a transitive base class of `c`. */
private predicate mayInherit(Class c, MemberFunction f) {
f.getDeclaringType() = c.getABaseClass*()
}
/**
* Holds if `c` cannot inherit the member function `f`,
* i.e. `c` or one of its supertypes overrides `f`.
*/
private predicate cannotInherit(Class c, MemberFunction f) {
exists(Class overridingType, MemberFunction override |
cannotInheritHelper(c, f, overridingType, override) and
override.overrides+(f)
)
}
pragma[noinline]
private predicate cannotInheritHelper(
Class c, MemberFunction f, Class overridingType, MemberFunction override
) {
c.getABaseClass*() = overridingType and
override.getDeclaringType() = overridingType and
overridingType.getABaseClass+() = f.getDeclaringType()
}
}

View File

@@ -0,0 +1,20 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
A write that looks like it may be bypassing runtime checks.
</p>
</overview>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Data_Validation">Data Validation</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,35 @@
/**
* @name Serialization check bypass
* @description A write that looks like it may be bypassing runtime checks.
* @kind problem
* @id cs/serialization-check-bypass
* @problem.severity warning
* @tags security
* external/cwe/cwe-20
*/
/*
* consider: @precision medium
*/
import semmle.code.csharp.serialization.Serialization
/**
* The result is a write to the field `f`, assigning it the value
* of variable `v` which was checked by the condition `check`.
*/
Expr checkedWrite(Field f, Variable v, IfStmt check) {
result = v.getAnAccess() and
result = f.getAnAssignedValue() and
check.getCondition() = v.getAnAccess().getParent*() and
result.getAControlFlowNode() = check.getAControlFlowNode().getASuccessor*()
}
from BinarySerializableType t, Field f, IfStmt check, Expr write, Expr unsafeWrite
where
f = t.getASerializedField() and
write = checkedWrite(f, t.getAConstructor().getAParameter(), check) and
unsafeWrite = f.getAnAssignedValue() and
t.getADeserializationCallback() = unsafeWrite.getEnclosingCallable() and
not t.getADeserializationCallback().calls*(checkedWrite(f, _, _).getEnclosingCallable())
select unsafeWrite, "This write to $@ may be circumventing a $@.", f, f.toString(), check, "check"

View File

@@ -0,0 +1,45 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
The APIs provided by the .NET libraries for XML manipulation allow the insertion of "raw" text at
a specified point in an XML document. If user input is passed to this API, it could allow a
malicious user to add extra content that could corrupt or supersede existing content, or enable
unintended additional functionality.
</p>
</overview>
<recommendation>
<p>
Avoid using the <code>WriteRaw</code> method on <code>System.Xml.XmlWriter</code> with user input.
If possible, use the high-level APIs to write new XML elements to a document, as these automatically
escape user content. If that is not possible, then user input should be escaped before being
included in a string that will be used with the <code>WriteRaw</code> API.
</p>
</recommendation>
<example>
<p>In this example, user input is provided describing the name of an employee to add to an XML
document representing a set of names. The <code>WriteRaw</code> API is used to write the new
employee record to the XML file.</p>
<sample src="XMLInjectionBad.cs" />
<p>However, if a malicious user were to provide the content
<code>Bobby Pages&lt;/name&gt;&lt;/employee&gt;&lt;employee&gt;&lt;name&gt;Hacker1</code>, they
would be able to add an extra entry into the XML file.
</p>
<p>The corrected version demonstrates two ways to avoid this issue. The first is to escape user
input before passing it to the <code>WriteRaw</code> API, which prevents a malicious user from
closing or opening XML tags. The second approach uses the high level XML API to add XML elements,
which ensures the content is appropriately escaped.</p>
<sample src="XMLInjectionGood.cs" />
</example>
<references>
<li>
<a href="http://projects.webappsec.org/w/page/13247004/XML%20Injection">XML Injection</a> (The Web Application Security Consortium).
</li>
<li>
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.xml.xmlwriter.writeraw?view=netframework-4.8">WriteRaw</a> (Microsoft documentation).
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,53 @@
/**
* @name XML injection
* @description Building an XML document from user-controlled sources is vulnerable to insertion of
* malicious code by the user.
* @kind problem
* @id cs/xml-injection
* @problem.severity error
* @tags security
* external/cwe/cwe-091
*/
/*
* consider: @precision high
*/
import csharp
import semmle.code.csharp.dataflow.flowsources.Remote
import semmle.code.csharp.frameworks.system.Xml
/**
* A taint-tracking configuration for untrusted user input used in XML.
*/
class TaintTrackingConfiguration extends TaintTracking::Configuration {
TaintTrackingConfiguration() { this = "XMLInjection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(MethodCall mc |
mc.getTarget().hasName("WriteRaw") and
mc.getTarget().getDeclaringType().getABaseType*().hasQualifiedName("System.Xml.XmlWriter")
|
mc.getArgument(0) = sink.asExpr()
)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(MethodCall mc |
mc.getTarget().hasName("Escape") and
mc
.getTarget()
.getDeclaringType()
.getABaseType*()
.hasQualifiedName("System.Security.SecurityElement")
|
mc = node.asExpr()
)
}
}
from TaintTrackingConfiguration c, DataFlow::Node source, DataFlow::Node sink
where c.hasFlow(source, sink)
select sink, "$@ flows to here and is inserted as XML.", source, "User-provided value"

View File

@@ -0,0 +1,21 @@
using System;
using System.Security;
using System.Web;
using System.Xml;
public class XMLInjectionHandler : IHttpHandler {
public void ProcessRequest(HttpContext ctx) {
string employeeName = ctx.Request.QueryString["employeeName"];
using (XmlWriter writer = XmlWriter.Create("employees.xml"))
{
writer.WriteStartDocument();
// BAD: Insert user input directly into XML
writer.WriteRaw("<employee><name>" + employeeName + "</name></employee>");
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Security;
using System.Web;
using System.Xml;
public class XMLInjectionHandler : IHttpHandler {
public void ProcessRequest(HttpContext ctx) {
string employeeName = ctx.Request.QueryString["employeeName"];
using (XmlWriter writer = XmlWriter.Create("employees.xml"))
{
writer.WriteStartDocument();
// GOOD: Escape user input before inserting into string
writer.WriteRaw("<employee><name>" + SecurityElement.Escape(employeeName) + "</name></employee>");
// GOOD: Use standard API, which automatically encodes values
writer.WriteStartElement("Employee");
writer.WriteElementString("Name", employeeName);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
C# supports runtime loading of assemblies by path through the use of the
<code>System.Reflection.Assembly</code> API. If an external user can influence the path used to
load an assembly, then the application can potentially be tricked into loading an assembly which
was not intended to be loaded, and executing arbitrary code.
</p>
</overview>
<recommendation>
<p>
Avoid loading assemblies based on user provided input. If this is not possible, ensure that the path
is validated before being used with <code>Assembly</code>. For example, compare the provided input
against a whitelist of known safe assemblies, or confirm that path is restricted to a single
directory which only contains safe assemblies.
</p>
</recommendation>
<example>
<p>In this example, user input is provided describing the path to an assembly, which is loaded
without validation. This is problematic because it allows the user to load any assembly installed
on the system, and is particularly problematic if an attacker can upload a custom DLL elsewhere on
the system.</p>
<sample src="AssemblyPathInjectionBad.cs" />
<p>In the corrected version, user input is validated against one of two options, and the assembly
is only loaded if the user input matches one of those options.</p>
<sample src="AssemblyPathInjectionGood.cs" />
</example>
<references>
<li>
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly?view=netframework-4.8">System.Reflection.Assembly</a> (Microsoft documentation).
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,66 @@
/**
* @name Assembly path injection
* @description Loading a .NET assembly based on a path constructed from user-controlled sources
* may allow a malicious user to load code which modifies the program in unintended
* ways.
* @kind problem
* @id cs/assembly-path-injection
* @problem.severity error
* @tags security
* external/cwe/cwe-114
*/
/*
* consider: @precision high
*/
import csharp
import semmle.code.csharp.dataflow.flowsources.Remote
class MainMethod extends Method {
MainMethod() {
this.hasName("Main") and
this.isStatic() and
(this.getReturnType() instanceof VoidType or this.getReturnType() instanceof IntType) and
if this.getNumberOfParameters() = 1
then this.getParameter(0).getType().(ArrayType).getElementType() instanceof StringType
else this.getNumberOfParameters() = 0
}
}
/**
* A taint-tracking configuration for untrusted user input used to load a DLL.
*/
class TaintTrackingConfiguration extends TaintTracking::Configuration {
TaintTrackingConfiguration() { this = "DLLInjection" }
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource or
source.asExpr() = any(MainMethod main).getParameter(0).getAnAccess()
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodCall mc, string name, int arg |
mc.getTarget().getName().matches(name) and
mc
.getTarget()
.getDeclaringType()
.getABaseType*()
.hasQualifiedName("System.Reflection.Assembly") and
mc.getArgument(arg) = sink.asExpr()
|
name = "LoadFrom" and arg = 0 and mc.getNumberOfArguments() = [1 .. 2]
or
name = "LoadFile" and arg = 0
or
name = "LoadWithPartialName" and arg = 0
or
name = "UnsafeLoadFrom" and arg = 0
)
}
}
from TaintTrackingConfiguration c, DataFlow::Node source, DataFlow::Node sink
where c.hasFlow(source, sink)
select sink, "$@ flows to here and is used as the path to dynamically load an assembly.", source,
"User-provided value"

View File

@@ -0,0 +1,18 @@
using System;
using System.Web;
using System.Reflection;
public class AssemblyPathInjectionHandler : IHttpHandler {
public void ProcessRequest(HttpContext ctx) {
string assemblyPath = ctx.Request.QueryString["assemblyPath"];
// BAD: Load assembly based on user input
var badAssembly = Assembly.LoadFile(assemblyPath);
// Method called on loaded assembly. If the user can control the loaded assembly, then this
// could result in a remote code execution vulnerability
MethodInfo m = badAssembly.GetType("Config").GetMethod("GetCustomPath");
Object customPath = m.Invoke(null, null);
// ...
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Web;
using System.Reflection;
public class AssemblyPathInjectionHandler : IHttpHandler {
public void ProcessRequest(HttpContext ctx) {
string configType = ctx.Request.QueryString["configType"];
if (configType.equals("configType1") || configType.equals("configType2")) {
// GOOD: Loaded assembly is one of the two known safe options
var safeAssembly = Assembly.LoadFile(@"C:\SafeLibraries\" + configType + ".dll");
// Code execution is limited to one of two known and vetted assemblies
MethodInfo m = safeAssembly.GetType("Config").GetMethod("GetCustomPath");
Object customPath = m.Invoke(null, null);
// ...
}
}
}

View File

@@ -0,0 +1,39 @@
/**
* @name Do not use hard-coded encryption keys.
* @description The .Key property or rgbKey parameter of a SymmetricAlgorithm should never be a hardcoded value.
* @kind problem
* @id cs/hardcoded-key
* @problem.severity error
* @tags security
*/
/*
* consider: @precision high
*/
import csharp
import semmle.code.csharp.security.cryptography.EncryptionKeyDataFlow::EncryptionKeyDataFlow
/**
* The creation of a literal byte array.
*/
class ByteArrayLiteralSource extends KeySource {
ByteArrayLiteralSource() {
this.asExpr() = any(ArrayCreation ac |
ac.getArrayType().getElementType() instanceof ByteType and
ac.hasInitializer()
)
}
}
/**
* Any string literal as a source
*/
class StringLiteralSource extends KeySource {
StringLiteralSource() { this.asExpr() instanceof StringLiteral }
}
from SymmetricKeyTaintTrackingConfiguration keyFlow, KeySource src, SymmetricEncryptionKeySink sink
where keyFlow.hasFlow(src, sink)
select sink, "Hard-coded symmetric $@ is used in symmetric algorithm in " + sink.getDescription(),
src, "key"

View File

@@ -0,0 +1,8 @@
using System.Security.Cryptography;
var b = new AesCryptoServiceProvider()
{
// BAD: explicit key assignment, hard-coded value
Key = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
};

View File

@@ -0,0 +1,22 @@
/**
* @name Hard-coded symmetric encryption key
* @description The .Key property or rgbKey parameter of a SymmetricAlgorithm should never be a hardcoded value.
* @kind path-problem
* @id cs/hard-coded-symmetric-encryption-key
* @problem.severity error
* @tags security
*/
/*
* consider: @precision high
*/
import csharp
import semmle.code.csharp.security.cryptography.HardcodedSymmetricEncryptionKey::HardcodedSymmetricEncryptionKey
import DataFlow::PathGraph
from TaintTrackingConfiguration c, DataFlow::PathNode source, DataFlow::PathNode sink
where c.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Hard-coded symmetric $@ is used in symmetric algorithm in " +
sink.getNode().(Sink).getDescription() + ".", source.getNode(), "key"

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Finds uses of insecure SQL Connections string by not enabling the <code>Encrypt</code> option.</p>
<p>
SQL Server connections where the client is not enforcing the encryption in transit are susceptible to multiple attacks, including a man-in-the-middle, that would potentially compromise the user credentials and/or the TDS session.
</p>
</overview>
<recommendation>
<p>Ensure that the client code enforces the <code>Encrypt</code> option by setting it to <code>true</code> in the connection string.</p>
</recommendation>
<example>
<p>The following example shows a SQL connection string that is not explicitly enabling the <code>Encrypt</code> setting to force encryption.</p>
<sample src="InsecureSQLConnectionBad.cs" />
<p>
The following example shows a SQL connection string that is explicitly enabling the <code>Encrypt</code> setting to force encryption in transit.
</p>
<sample src="InsecureSQLConnectionGood.cs" />
</example>
<references>
<li>
<a href="https://blogs.msdn.microsoft.com/sql_protocols/2009/10/19/selectively-using-secure-connection-to-sql-server/">Selectively using secure connection to SQL Server</a>
</li>
<li>
<a href="https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.connectionstring(v=vs.110).aspx">Net SqlClient (ADO .Net)</a>
</li>
<li><a href="https://msdn.microsoft.com/en-us/library/ms130822.aspx">SQL native driver (SNAC)</a>
</li>
<li>
<a href="https://msdn.microsoft.com/en-us/library/ms378988(v=sql.110).aspx">JDBC driver</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,47 @@
/**
* @name Insecure SQL connection
* @description TODO.
* @kind path-problem
* @id cs/insecure-sql-connection
* @problem.severity error
* @tags security
* external/cwe/cwe-327
*/
/*
* consider: @precision high
*/
import csharp
import DataFlow::PathGraph
/**
* A `DataFlow::Configuration` for tracking `Strings passed to SqlConnectionStringBuilder` instances.
*/
class TaintTrackingConfiguration extends TaintTracking::Configuration {
TaintTrackingConfiguration() { this = "TaintTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
exists(string s | s = source.asExpr().(StringLiteral).getValue().toLowerCase() |
s.matches("%encrypt=false%")
or
not s.matches("%encrypt=%")
)
}
override predicate isSink(DataFlow::Node sink) {
exists(ObjectCreation oc |
oc.getRuntimeArgument(0) = sink.asExpr() and
(
oc.getType().getName() = "SqlConnectionStringBuilder"
or
oc.getType().getName() = "SqlConnection"
)
)
}
}
from TaintTrackingConfiguration c, DataFlow::PathNode source, DataFlow::PathNode sink
where c.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows to here and does not specify `Encrypt=True`.",
source.getNode(), "Connection string"

View File

@@ -0,0 +1,7 @@
using System.Data.SqlClient;
// BAD, Encrypt not specified
string connectString =
"Server=1.2.3.4;Database=Anything;Integrated Security=true;";
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString);
var conn = new SqlConnection(builder.ConnectionString);

View File

@@ -0,0 +1,6 @@
using System.Data.SqlClient;
string connectString =
"Server=1.2.3.4;Database=Anything;Integrated Security=true;;Encrypt=true;";
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString);
var conn = new SqlConnection(builder.ConnectionString);

View File

@@ -0,0 +1,35 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Deserializing a delegate object may result in remote code execution, when an
attacker can control the serialized data.</p>
</overview>
<recommendation>
<p>Avoid deserializing delegate objects, if possible, or make sure that the serialized
data cannot be controlled by an attacker.</p>
</recommendation>
<example>
<p>In this example, a file stream is deserialized to a <code>Func&lt;int&gt;</code>
object, using a <code>BinaryFormatter</code>. The file stream is a parameter of a public
method, so depending on the calls to <code>InvokeSerialized</code>, this may or may not
pose a security problem.</p>
<sample src="DeserializedDelegateBad.cs" />
</example>
<references>
<li>
Microsoft:
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter">BinaryFormatter Class</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,25 @@
/**
* @name Deserialized delegate
* @description Deserializing a delegate allows for remote code execution when an
* attacker can control the serialized data.
* @kind problem
* @id cs/deserialized-delegate
* @problem.severity warning
* @tags security
* external/cwe/cwe-502
*/
/*
* consider: @precision high
*/
import csharp
import semmle.code.csharp.frameworks.system.linq.Expressions
import semmle.code.csharp.serialization.Deserializers
from Call deserialization, Cast cast
where
deserialization.getTarget() instanceof UnsafeDeserializer and
cast.getExpr() = deserialization and
cast.getTargetType() instanceof SystemLinqExpressions::DelegateExtType
select deserialization, "Deserialization of delegate type."

View File

@@ -0,0 +1,14 @@
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
class Bad
{
public static int InvokeSerialized(FileStream fs)
{
var formatter = new BinaryFormatter();
// BAD
var f = (Func<int>)formatter.Deserialize(fs);
return f();
}
}

View File

@@ -0,0 +1,38 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Deserializing an object from untrusted input may result in security problems, such
as denial-of-service or remote code execution.</p>
</overview>
<recommendation>
<p>Avoid using an unsafe deserialization framework.</p>
</recommendation>
<example>
<p>In this example, a string is deserialized using a
<code>JavaScriptSerializer</code> with a simple type resolver. Using a type resolver
means that arbitrary code may be executed.</p>
<sample src="UnsafeDeserializationBad.cs" />
<p>To fix this specific vulnerability, we avoid using a type resolver. In other cases,
it may be necessary to use a different deserialization framework.</p>
<sample src="UnsafeDeserializationGood.cs" />
</example>
<references>
<li>
Mu&ntilde;oz, Alvaro and Mirosh, Oleksandr:
<a href="https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf">JSON Attacks</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name Unsafe deserializer
* @description Calling an unsafe deserializer with data controlled by an attacker
* can lead to denial of service and other security problems.
* @kind problem
* @id cs/unsafe-deserialization
* @problem.severity warning
* @tags security
* external/cwe/cwe-502
*/
/*
* consider: @precision low
*/
import csharp
import UnsafeDeserialization::UnsafeDeserialization
from Call deserializeCall, Sink sink
where deserializeCall.getAnArgument() = sink.asExpr()
select deserializeCall,
"Unsafe deserializer is used. Make sure the value being deserialized comes from a trusted source."

View File

@@ -0,0 +1,83 @@
/**
* Provides a taint-tracking configuration for reasoning about uncontrolled data
* in calls to unsafe deserializers (XML, JSON, XAML).
*/
import csharp
module UnsafeDeserialization {
private import semmle.code.csharp.dataflow.flowsources.Remote
private import semmle.code.csharp.dataflow.flowsources.Remote
private import semmle.code.csharp.serialization.Deserializers
/**
* A data flow source for unsafe deserialization vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for unsafe deserialization vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for unsafe deserialization vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A taint-tracking configuration for reasoning about unsafe deserialization.
*/
class TaintTrackingConfig extends TaintTracking::Configuration {
TaintTrackingConfig() { this = "UnsafeDeserialization" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
}
class RemoteSource extends Source {
RemoteSource() { this instanceof RemoteFlowSource }
}
/** A call to an unsafe deserializer. */
class UnsafeDeserializerSink extends Sink {
UnsafeDeserializerSink() {
exists(Call c |
this.asExpr() = c.getAnArgument() and
c.getTarget() instanceof UnsafeDeserializer
)
}
}
private class JavaScriptSerializerClass extends Class {
JavaScriptSerializerClass() {
this.hasQualifiedName("System.Web.Script.Serialization.JavaScriptSerializer")
}
}
/**
* An unsafe use of a JavaScript deserializer. That is, a use with a custom type-resolver
* (constructor parameter).
*/
class JavaScriptSerializerSink extends Sink {
JavaScriptSerializerSink() {
exists(ObjectCreation oc |
oc.getTarget().getDeclaringType() instanceof JavaScriptSerializerClass and
oc.getTarget().getNumberOfParameters() > 0 and
exists(MethodCall mc, Method m |
m = mc.getTarget() and
m.getDeclaringType() instanceof JavaScriptSerializerClass and
(
m.hasName("Deserialize") or
m.hasName("DeserializeObject")
) and
this.asExpr() = mc.getAnArgument() and
DataFlow::localFlow(DataFlow::exprNode(oc), DataFlow::exprNode(mc.getQualifier()))
)
)
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Web.Script.Serialization;
class Bad
{
public static object Deserialize(string s)
{
JavaScriptSerializer sr = new JavaScriptSerializer(new SimpleTypeResolver());
// BAD
return sr.DeserializeObject(s);
}
}

View File

@@ -0,0 +1,11 @@
using System.Web.Script.Serialization;
class Good
{
public static object Deserialize(string s)
{
// GOOD
JavaScriptSerializer sr = new JavaScriptSerializer();
return sr.DeserializeObject(s);
}
}

View File

@@ -0,0 +1,39 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Deserializing an object from untrusted input may result in security problems, such
as denial-of-service or remote code execution.</p>
</overview>
<recommendation>
<p>Avoid deserializing objects from an untrusted source, and if not possible, make sure
to use a safe deserialization framework.</p>
</recommendation>
<example>
<p>In this example, text from an HTML text box is deserialized using a
<code>JavaScriptSerializer</code> with a simple type resolver. Using a type resolver
means that arbitrary code may be executed.</p>
<sample src="UnsafeDeserializationUntrustedInputBad.cs" />
<p>To fix this specific vulnerability, we avoid using a type resolver. In other cases,
it may be necessary to use a different deserialization framework.</p>
<sample src="UnsafeDeserializationUntrustedInputGood.cs" />
</example>
<references>
<li>
Mu&ntilde;oz, Alvaro and Mirosh, Oleksandr:
<a href="https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf">JSON Attacks</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Deserialization of untrusted data
* @description Calling an unsafe deserializer with data controlled by an attacker
* can lead to denial of service and other security problems.
* @kind path-problem
* @id cs/unsafe-deserialization-untrusted-input
* @problem.severity error
* @tags security
* external/cwe/cwe-502
*/
/*
* consider: @precision high
*/
import csharp
import UnsafeDeserialization::UnsafeDeserialization
import DataFlow::PathGraph
from TaintTrackingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows to unsafe deserializer.", source.getNode(),
"User-provided data"

View File

@@ -0,0 +1,12 @@
using System.Web.UI.WebControls;
using System.Web.Script.Serialization;
class Bad
{
public static object Deserialize(TextBox textBox)
{
JavaScriptSerializer sr = new JavaScriptSerializer(new SimpleTypeResolver());
// BAD
return sr.DeserializeObject(textBox.Text);
}
}

View File

@@ -0,0 +1,12 @@
using System.Web.UI.WebControls;
using System.Web.Script.Serialization;
class Good
{
public static object Deserialize(TextBox textBox)
{
JavaScriptSerializer sr = new JavaScriptSerializer();
// GOOD
return sr.DeserializeObject(textBox.Text);
}
}

View File

@@ -0,0 +1,137 @@
/**
* Provides classes for working with symmetric cryptography algorithms.
*/
import csharp
/**
* Holds if the object creation `oc` is the creation of the reference type with the specified `qualifiedName`, or a class derived from
* the class with the specified `qualifiedName`.
*/
private predicate isCreatingObject(ObjectCreation oc, string qualifiedName) {
exists(RefType t | t = oc.getType() | t.getBaseClass*().hasQualifiedName(qualifiedName))
}
/**
* Holds if the method call `mc` is returning the reference type with the specified `qualifiedName`.
* and the target of the method call is a library method.
*/
private predicate isReturningObject(MethodCall mc, string qualifiedName) {
mc.getTarget().fromLibrary() and
exists(RefType t | t = mc.getType() | t.hasQualifiedName(qualifiedName))
}
/**
* Holds if the method call `mc` is a call on the library method target with the specified `qualifiedName` and `methodName`, and an argument at
* index `argumentIndex` has the specified value `argumentValue` (case-insensitive).
*/
bindingset[argumentValue]
private predicate isMethodCalledWithArg(
MethodCall mc, string qualifiedName, string methodName, int argumentIndex, string argumentValue
) {
mc.getTarget().fromLibrary() and
mc.getTarget().hasQualifiedName(qualifiedName, methodName) and
mc.getArgument(argumentIndex).getValue().toUpperCase() = argumentValue.toUpperCase()
}
/**
* The class `System.Security.Cryptography.SymmetricAlgorithm` or any sub class of this class.
*/
class SymmetricAlgorithm extends Class {
SymmetricAlgorithm() {
this.getABaseType*().hasQualifiedName("System.Security.Cryptography", "SymmetricAlgorithm")
}
/** Gets the `IV` property. */
Property getIVProperty() { result = this.getProperty("IV") }
/** Gets the 'Key' property. */
Property getKeyProperty() { result = this.getProperty("Key") }
/** Gets a method call that constructs a symmetric encryptor. */
MethodCall getASymmetricEncryptor() { result.getTarget() = this.getAMethod("CreateEncryptor") }
/** Gets a method call that constructs a symmetric decryptor. */
MethodCall getASymmetricDecryptor() { result.getTarget() = this.getAMethod("CreateDecryptor") }
}
/**
* Holds if the expression 'e' creates DES symmetric algorithm.
* Note: not all of the class names are supported on all platforms.
*/
predicate isCreatingDES(Expr e) {
isCreatingObject(e, "System.Security.Cryptography.DES") or
isReturningObject(e, "System.Security.Cryptography.DES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0, "DES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"System.Security.Cryptography.DES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0, "DES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"System.Security.Cryptography.DES")
}
/**
* Holds if the expression 'e' creates Triple DES symmetric algorithm.
* Note: not all of the class names are supported on all platforms.
*/
predicate isCreatingTripleDES(Expr e) {
isCreatingObject(e, "System.Security.Cryptography.TripleDES") or
isReturningObject(e, "System.Security.Cryptography.TripleDES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"TripleDES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0, "3DES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"Triple DES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"System.Security.Cryptography.TripleDES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"TripleDES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0, "3DES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"Triple DES") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"System.Security.Cryptography.TripleDES")
}
/**
* Holds if the expression 'e' creates RC2 symmetric algorithm.
* Note: not all of the class names are supported on all platforms.
*/
predicate isCreatingRC2(Expr e) {
isCreatingObject(e, "System.Security.Cryptography.RC2") or
isReturningObject(e, "System.Security.Cryptography.RC2") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0, "RC2") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"System.Security.Cryptography.RC2") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0, "RC2") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"System.Security.Cryptography.RC2")
}
/**
* Holds if the expression 'e' creates Rijndael symmetric algorithm.
*/
predicate isCreatingRijndael(Expr e) {
isCreatingObject(e, "System.Security.Cryptography.Rijndael") or
isReturningObject(e, "System.Security.Cryptography.Rijndael") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"Rijndael") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"RijndaelManaged") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"System.Security.Cryptography.Rijndael") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"System.Security.Cryptography.RijndaelManaged") or
isMethodCalledWithArg(e, "System.Security.Cryptography.SymmetricAlgorithm", "Create", 0,
"System.Security.Cryptography.SymmetricAlgorithm") or // this creates Rijndael
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"Rijndael") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"System.Security.Cryptography.Rijndael") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"RijndaelManaged") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"System.Security.Cryptography.RijndaelManaged") or
isMethodCalledWithArg(e, "System.Security.Cryptography.CryptoConfig", "CreateFromName", 0,
"System.Security.Cryptography.SymmetricAlgorithm") // this creates Rijndael
}

View File

@@ -0,0 +1,83 @@
/**
* This module has classes and data flow configuration for working with symmetric encryption keys data flow.
*/
import csharp
module EncryptionKeyDataFlow {
private import semmle.code.csharp.frameworks.system.security.cryptography.SymmetricAlgorithm
/** Array of type Byte */
class ByteArray extends ArrayType {
ByteArray() { getElementType() instanceof ByteType }
}
/** Abstract class for all sources of keys */
abstract class KeySource extends DataFlow::Node { }
/**
* A symmetric encryption sink is abstract base class for all ways to set a key for symmetric encryption.
*/
abstract class SymmetricEncryptionKeySink extends DataFlow::Node {
/** override to create a meaningful description of the sink */
abstract string getDescription();
}
/**
* A sanitizer for symmetric encryption key. If present, for example, key is properly constructed or retrieved from secret storage.
*/
abstract class KeySanitizer extends DataFlow::ExprNode { }
/**
* Symmetric Algorithm, 'Key' property assigned a value
*/
class SymmetricEncryptionKeyPropertySink extends SymmetricEncryptionKeySink {
SymmetricEncryptionKeyPropertySink() {
exists(SymmetricAlgorithm ag | asExpr() = ag.getKeyProperty().getAnAssignedValue())
}
override string getDescription() { result = "Key property assignment" }
}
/**
* Symmetric Algorithm, CreateEncryptor method, rgbKey parameter
*/
class SymmetricEncryptionCreateEncryptorSink extends SymmetricEncryptionKeySink {
SymmetricEncryptionCreateEncryptorSink() {
exists(SymmetricAlgorithm ag, MethodCall mc | mc = ag.getASymmetricEncryptor() |
asExpr() = mc.getArgumentForName("rgbKey")
)
}
override string getDescription() { result = "Encryptor(rgbKey, IV)" }
}
/**
* Symmetric Algorithm, CreateDecryptor method, rgbKey parameter
*/
class SymmetricEncryptionCreateDecryptorSink extends SymmetricEncryptionKeySink {
SymmetricEncryptionCreateDecryptorSink() {
exists(SymmetricAlgorithm ag, MethodCall mc | mc = ag.getASymmetricDecryptor() |
asExpr() = mc.getArgumentForName("rgbKey")
)
}
override string getDescription() { result = "Decryptor(rgbKey, IV)" }
}
/**
* Symmetric Key Data Flow configuration.
*/
class SymmetricKeyTaintTrackingConfiguration extends TaintTracking::Configuration {
SymmetricKeyTaintTrackingConfiguration() { this = "SymmetricKeyTaintTracking" }
/** Holds if the node is a key source. */
override predicate isSource(DataFlow::Node src) { src instanceof KeySource }
/** Holds if the node is a symmetric encryption key sink. */
override predicate isSink(DataFlow::Node sink) { sink instanceof SymmetricEncryptionKeySink }
/** Holds if the node is a key sanitizer. */
override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof KeySanitizer }
}
}

View File

@@ -0,0 +1,112 @@
/**
* Provides a taint-tracking configuration for reasoning about hard-coded
* symmetric encryption keys.
*/
import csharp
module HardcodedSymmetricEncryptionKey {
private import semmle.code.csharp.frameworks.system.security.cryptography.SymmetricAlgorithm
/** A data flow source for hard-coded symmetric encryption keys. */
abstract class Source extends DataFlow::Node { }
/** A data flow sink for hard-coded symmetric encryption keys. */
abstract class Sink extends DataFlow::ExprNode {
/** Gets a description of this sink. */
abstract string getDescription();
}
/** A sanitizer for hard-coded symmetric encryption keys. */
abstract class Sanitizer extends DataFlow::ExprNode { }
private class ByteArrayType extends ArrayType {
ByteArrayType() { getElementType() instanceof ByteType }
}
private class ByteArrayLiteralSource extends Source {
ByteArrayLiteralSource() {
this.asExpr() = any(ArrayCreation ac |
ac.getArrayType() instanceof ByteArrayType and
ac.hasInitializer()
)
}
}
private class StringLiteralSource extends Source {
StringLiteralSource() { this.asExpr() instanceof StringLiteral }
}
private class SymmetricEncryptionKeyPropertySink extends Sink {
SymmetricEncryptionKeyPropertySink() {
this.asExpr() = any(SymmetricAlgorithm sa).getKeyProperty().getAnAssignedValue()
}
override string getDescription() { result = "'Key' property assignment" }
}
private class SymmetricEncryptionCreateEncryptorSink extends Sink {
SymmetricEncryptionCreateEncryptorSink() {
exists(SymmetricAlgorithm ag, MethodCall mc | mc = ag.getASymmetricEncryptor() |
asExpr() = mc.getArgumentForName("rgbKey")
)
}
override string getDescription() { result = "Encryptor(rgbKey, IV)" }
}
private class SymmetricEncryptionCreateDecryptorSink extends Sink {
SymmetricEncryptionCreateDecryptorSink() {
exists(SymmetricAlgorithm ag, MethodCall mc | mc = ag.getASymmetricDecryptor() |
asExpr() = mc.getArgumentForName("rgbKey")
)
}
override string getDescription() { result = "Decryptor(rgbKey, IV)" }
}
private class CreateSymmetricKeySink extends Sink {
CreateSymmetricKeySink() {
exists(MethodCall mc, Method m |
mc.getTarget() = m and
m
.hasQualifiedName("Windows.Security.Cryptography.Core.SymmetricKeyAlgorithmProvider",
"CreateSymmetricKey") and
this.asExpr() = mc.getArgumentForName("keyMaterial")
)
}
override string getDescription() { result = "CreateSymmetricKey(IBuffer keyMaterial)" }
}
private class CryptographicBuffer extends Class {
CryptographicBuffer() {
this.hasQualifiedName("Windows.Security.Cryptography", "CryptographicBuffer")
}
}
/**
* A taint-tracking configuration for uncontrolled data in path expression vulnerabilities.
*/
class TaintTrackingConfiguration extends TaintTracking::Configuration {
TaintTrackingConfiguration() { this = "HardcodedSymmetricEncryptionKey" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
/**
* Since `CryptographicBuffer` uses native code inside, taint tracking doesn't pass through it.
* Need to create an additional custom step.
*/
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(MethodCall mc, CryptographicBuffer c |
pred.asExpr() = mc.getAnArgument() and
mc.getTarget() = c.getAMethod() and
succ.asExpr() = mc
)
}
}
}

View File

@@ -0,0 +1,65 @@
/**
* Provides a library of known unsafe deserializers.
* See https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf.
*/
import csharp
/** An unsafe deserializer. */
abstract class UnsafeDeserializer extends Callable { }
class SystemDeserializer extends UnsafeDeserializer {
SystemDeserializer() {
this
.hasQualifiedName("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter",
"Deserialize")
or
this
.hasQualifiedName("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter",
"UnsafeDeserialize")
or
this
.hasQualifiedName("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter",
"UnsafeDeserializeMethodResponse")
or
this
.hasQualifiedName("System.Runtime.Deserialization.Formatters.Soap.SoapFormatter",
"Deserialize")
or
this.hasQualifiedName("System.Web.UI.ObjectStateFormatter", "Deserialize")
or
this.hasQualifiedName("System.Runtime.Serialization.NetDataContractSerializer", "Deserialize")
or
this.hasQualifiedName("System.Runtime.Serialization.NetDataContractSerializer", "ReadObject")
or
this.hasQualifiedName("System.Web.UI.LosFormatter", "Deserialize")
or
this.hasQualifiedName("System.Workflow.ComponentModel.Activity", "Load")
or
this.hasQualifiedName("System.Resources.ResourceReader", "ResourceReader")
or
this.hasQualifiedName("System.Messaging", "BinaryMessageFormatter")
or
this.hasQualifiedName("System.Windows.Markup.XamlReader", "Parse")
or
this.hasQualifiedName("System.Windows.Markup.XamlReader", "Load")
or
this.hasQualifiedName("System.Windows.Markup.XamlReader", "LoadAsync")
}
}
class MicrosoftDeserializer extends UnsafeDeserializer {
MicrosoftDeserializer() {
this.hasQualifiedName("Microsoft.Web.Design.Remote.ProxyObject", "DecodeValue")
}
}
class WrapperDeserializer extends UnsafeDeserializer {
WrapperDeserializer() {
exists(Call call |
call.getEnclosingCallable() = this and
call.getAnArgument() instanceof ParameterAccess and
call.getTarget() instanceof UnsafeDeserializer
)
}
}

View File

@@ -0,0 +1,105 @@
import csharp
class SerializationConstructor extends Constructor {
SerializationConstructor() {
this.getNumberOfParameters() = 2 and
this.getParameter(0).getType().hasName("SerializationInfo") and
this.getParameter(1).getType().hasName("StreamingContext")
}
}
/**
* A serializable type, using any of the built-in .NET serialization mechanisms.
*/
abstract class SerializableType extends ValueOrRefType {
/**
* A method or constructor that should always be considered as potentially
* called on a deserialized object. The precise details depend on the
* deserialization mechanism.
*/
abstract Callable getADeserializationCallback();
/**
* A field whose value is restored during a deserialization, rendering it
* potentially untrusted.
*/
abstract Field getASerializedField();
/**
* Get a callback that is automatically executed (without user code
* interaction) when an object instance is deserialized. This includes
* deserialization callbacks as well as cleanup/dispose calls.
*/
Callable getAnAutomaticCallback() {
result = this.getADeserializationCallback() or
result.(Destructor).getDeclaringType() = this or
result = any(Method m |
m.getDeclaringType() = this and
m.hasName("Dispose") and
(
m.getNumberOfParameters() = 0
or
m.getNumberOfParameters() = 1 and m.getParameter(0).getType() instanceof BoolType
)
)
}
}
/**
* To be serializable by the `BinaryFormatter`, a class must have the `Serializable`
* attribute.
*/
class BinarySerializableType extends SerializableType {
BinarySerializableType() { this.getAnAttribute().getType().hasName("SerializableAttribute") }
/**
* In addition to the defaults, a `BinarySerializer` will call any method annotated
* with an `OnDeserialized` or `OnDeserializing` attribute, as well as an
* `OnDeserialization` method.
*/
override Callable getADeserializationCallback() {
result.(SerializationConstructor).getDeclaringType() = this
or
result = this.getAMethod() and
(
result.(Attributable).getAnAttribute().getType().hasName("OnDeserializedAttribute") or
result.(Attributable).getAnAttribute().getType().hasName("OnDeserializingAttribute") or
result.hasName("OnDeserialization")
)
}
override Field getASerializedField() {
result.getDeclaringType() = this and
not result.getAnAttribute().getType().hasName("NonSerializedAttribute") and
not result.isStatic()
}
}
/**
* If a class annotated with the `Serializable` attribute also implements `ISerializable`,
* then it is serialized and deserialized in a special way.
*/
class CustomBinarySerializableType extends BinarySerializableType {
CustomBinarySerializableType() { this.getABaseType*().hasName("ISerializable") }
/**
* For custom deserialization, the `BinarySerializer` will call the serialization constructor.
*/
override Callable getADeserializationCallback() {
result = super.getADeserializationCallback() or
result.(SerializationConstructor).getDeclaringType() = this
}
}
class DangerousCallable extends Callable {
DangerousCallable() {
//files
this.(Method).getQualifiedName().matches("System.IO.File.Write%") or
this.(Method).getQualifiedName().matches("System.IO.File.%Copy%") or
this.(Method).getQualifiedName().matches("System.IO.File.%Move%") or
this.(Method).getQualifiedName().matches("System.IO.File.%Append%") or
this.(Method).getQualifiedName().matches("System.IO.%.%Delete%") or
//assembly
this.(Method).getQualifiedName().matches("System.Reflection.Assembly.%Load%")
}
}

View File

@@ -0,0 +1,37 @@
// semmle-extractor-options: /r:System.Private.Xml.dll /r:System.Xml.dll /r:System.Xml.ReaderWriter.dll /r:System.Runtime.Extensions.dll /r:System.Collections.Specialized.dll ${testdir}/../../../../resources/stubs/System.Web.cs
using System;
using System.Security;
using System.Web;
using System.Xml;
public class XMLInjectionHandler : IHttpHandler {
public void ProcessRequest(HttpContext ctx) {
string employeeName = ctx.Request.QueryString["employeeName"];
using (XmlWriter writer = XmlWriter.Create("employees.xml"))
{
writer.WriteStartDocument();
// BAD: Insert user input directly into XML
writer.WriteRaw("<employee><name>" + employeeName + "</name></employee>");
// GOOD: Escape user input before inserting into string
writer.WriteRaw("<employee><name>" + SecurityElement.Escape(employeeName) + "</name></employee>");
// GOOD: Use standard API, which automatically encodes values
writer.WriteStartElement("Employee");
writer.WriteElementString("Name", employeeName);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
public bool IsReusable {
get {
return true;
}
}
}

View File

@@ -0,0 +1 @@
| Test.cs:17:25:17:80 | ... + ... | $@ flows to here and is inserted as XML. | Test.cs:10:27:10:49 | access to property QueryString | User-provided value |

View File

@@ -0,0 +1 @@
Security Features/CWE-091/XMLInjection.ql

View File

@@ -0,0 +1 @@
| Test.cs:12:36:12:46 | access to local variable libraryName | $@ flows to here and is used as the path to dynamically load an assembly. | Test.cs:9:26:9:48 | access to property QueryString | User-provided value |

View File

@@ -0,0 +1 @@
Security Features/CWE-114/AssemblyPathInjection.ql

View File

@@ -0,0 +1,23 @@
// semmle-extractor-options: /r:System.Collections.Specialized.dll ${testdir}/../../../../resources/stubs/System.Web.cs
using System;
using System.Web;
using System.Reflection;
public class DLLInjectionHandler : IHttpHandler {
public void ProcessRequest(HttpContext ctx) {
string libraryName = ctx.Request.QueryString["libraryName"];
// BAD: Load DLL based on user input
var badDLL = Assembly.LoadFile(libraryName);
// GOOD: Load DLL using fixed string
var goodDLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");
}
public bool IsReusable {
get {
return true;
}
}
}

View File

@@ -0,0 +1,117 @@
// semmle-extractor-options: /r:System.IO.FileSystem.dll /r:System.Security.Cryptography.Primitives.dll /r:System.Security.Cryptography.Csp.dll /r:System.Security.Cryptography.Algorithms.dll
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
namespace HardcodedSymmetricEncryptionKey
{
class Program
{
static void Main(string[] args)
{
var a = new AesCryptoServiceProvider();
// BAD: explicit key assignment, hard-coded value
a.Key = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 };
var b = new AesCryptoServiceProvider()
{
// BAD: explicit key assignment, hard-coded value
Key = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
};
var c = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 };
var d = c;
var byteArrayFromString = Encoding.UTF8.GetBytes("Hello, world: here is a very bad way to create a key");
// BAD: key assignment via variable, from hard-coded value
a.Key = d;
// GOOD (not really, but better than hard coding)
a.Key = File.ReadAllBytes("secret.key");
var cp = CreateProvider(d);
var iv = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 };
// BAD: hard-coded key passed to Encrypt [NOT DETECTED]
var ct = Encrypt("Test string here", c, iv);
// BAD: hard-coded key converted from string and passed to Encrypt [NOT DETECTED]
var ct1 = Encrypt("Test string here", byteArrayFromString, iv);
// GOOD (this function hashes password)
var de = DecryptWithPassword(ct, c, iv);
// BAD [NOT DETECTED]
CreateCryptographicKey(null, byteArrayFromString);
// GOOD
CreateCryptographicKey(null, File.ReadAllBytes("secret.key"));
}
public static string DecryptWithPassword(byte[] cipherText, byte[] password, byte[] IV)
{
byte[] rawPlaintext;
var salt = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 };
using (Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(password, salt, 100))
{
using (Aes aes = new AesManaged())
{
using (MemoryStream ms = new MemoryStream())
{
// GOOD: for this test, the flow through Rfc2898DeriveBytes should be considered GOOD.
// GOOD: Use of hard-coded password for Rfc2898DeriveBytes is found by Semmle CWE-798: HardcodedCredentials.ql
using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(k1.GetBytes(16), IV), CryptoStreamMode.Write))
{
cs.Write(cipherText, 0, cipherText.Length);
}
rawPlaintext = ms.ToArray();
}
return Encoding.Unicode.GetString(rawPlaintext);
}
}
}
static SymmetricAlgorithm CreateProvider(byte[] key)
{
return new AesManaged()
{
// BAD: assignment from parameter
Key = key
};
}
public static byte[] Encrypt(string plaintext, byte[] key, byte[] IV)
{
byte[] rawPlaintext = Encoding.Unicode.GetBytes("Test string here");
byte[] cipherText = null;
using (Aes aes = new AesManaged())
{
using (MemoryStream ms = new MemoryStream())
{
// BAD: flow of hardcoded key to CreateEncryptor constructor
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(key, IV), CryptoStreamMode.Write))
{
cs.Write(rawPlaintext, 0, rawPlaintext.Length);
}
cipherText = ms.ToArray();
}
return cipherText;
}
}
static CryptographicKey CreateCryptographicKey(SymmetricKeyAlgorithmProvider p, byte[] bytes)
{
var buffer = CryptographicBuffer.CreateFromByteArray(bytes);
return p.CreateSymmetricKey(buffer);
}
}
}

View File

@@ -0,0 +1,6 @@
| HardcodedSymmetricEncryptionKey.cs:18:21:18:97 | array creation of type Byte[] | Hard-coded symmetric $@ is used in symmetric algorithm in Key property assignment | HardcodedSymmetricEncryptionKey.cs:18:21:18:97 | array creation of type Byte[] | key |
| HardcodedSymmetricEncryptionKey.cs:23:23:23:99 | array creation of type Byte[] | Hard-coded symmetric $@ is used in symmetric algorithm in Key property assignment | HardcodedSymmetricEncryptionKey.cs:23:23:23:99 | array creation of type Byte[] | key |
| HardcodedSymmetricEncryptionKey.cs:32:21:32:21 | access to local variable d | Hard-coded symmetric $@ is used in symmetric algorithm in Key property assignment | HardcodedSymmetricEncryptionKey.cs:26:21:26:97 | array creation of type Byte[] | key |
| HardcodedSymmetricEncryptionKey.cs:86:23:86:25 | access to parameter key | Hard-coded symmetric $@ is used in symmetric algorithm in Key property assignment | HardcodedSymmetricEncryptionKey.cs:26:21:26:97 | array creation of type Byte[] | key |
| HardcodedSymmetricEncryptionKey.cs:99:87:99:89 | access to parameter key | Hard-coded symmetric $@ is used in symmetric algorithm in Encryptor(rgbKey, IV) | HardcodedSymmetricEncryptionKey.cs:26:21:26:97 | array creation of type Byte[] | key |
| HardcodedSymmetricEncryptionKey.cs:99:87:99:89 | access to parameter key | Hard-coded symmetric $@ is used in symmetric algorithm in Encryptor(rgbKey, IV) | HardcodedSymmetricEncryptionKey.cs:29:62:29:115 | "Hello, world: here is a very bad way to create a key" | key |

View File

@@ -0,0 +1 @@
Security Features/CWE-321/HardcodedEncryptionKey.ql

View File

@@ -0,0 +1,22 @@
namespace Windows.Security.Cryptography
{
public static class CryptographicBuffer
{
public static Windows.Storage.Streams.IBuffer CreateFromByteArray(byte[] value) => throw null;
}
}
namespace Windows.Storage.Streams
{
public interface IBuffer { }
}
namespace Windows.Security.Cryptography.Core
{
public sealed class CryptographicKey { }
public sealed class SymmetricKeyAlgorithmProvider
{
public CryptographicKey CreateSymmetricKey(Windows.Storage.Streams.IBuffer keyMaterial) => throw null;
}
}

View File

@@ -0,0 +1,59 @@
using System.Data.SqlClient;
namespace InsecureSQLConnection
{
public class Class1
{
public void StringInConstructor()
{
SqlConnection conn = new SqlConnection("Encrypt=true");
}
public void StringInProperty()
{
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "Encrypt=true";
}
public void StringInBuilder()
{
SqlConnectionStringBuilder conBuilder = new SqlConnectionStringBuilder();
conBuilder.Encrypt = true;
SqlConnection conn = new SqlConnection(conBuilder.ToString());
}
public void StringInBuilderProperty()
{
SqlConnectionStringBuilder conBuilder = new SqlConnectionStringBuilder();
conBuilder.Encrypt = true;
SqlConnection conn = new SqlConnection();
conn.ConnectionString = conBuilder.ToString();
}
public void TriggerThis()
{
// BAD, Encrypt not specified [NOT DETECTED]
SqlConnection conn = new SqlConnection("Server=myServerName\\myInstanceName;Database=myDataBase;User Id=myUsername;");
}
void Test4()
{
string connectString =
"Server=1.2.3.4;Database=Anything;UID=ab;Pwd=cd";
// BAD, Encrypt not specified [NOT DETECTED]
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString);
var conn = new SqlConnection(builder.ConnectionString);
}
void Test5()
{
string connectString =
"Server=1.2.3.4;Database=Anything;UID=ab;Pwd=cd;Encrypt=false";
// BAD, Encrypt set to false [NOT DETECTED]
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString);
var conn = new SqlConnection(builder.ConnectionString);
}
}
}

View File

@@ -0,0 +1 @@
Security Features/CWE-091/XMLInjection.ql

View File

@@ -0,0 +1,71 @@
// This file contains auto-generated code.
namespace System
{
namespace Data
{
// Generated from `System.Data.IDbConnection` in `System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089`
public interface IDbConnection : System.IDisposable
{
string ConnectionString { get; set; }
}
namespace Common
{
// Generated from `System.Data.Common.DbConnectionStringBuilder` in `System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089`
public class DbConnectionStringBuilder : System.Collections.IEnumerable, System.Collections.IDictionary, System.Collections.ICollection
{
System.Collections.IDictionaryEnumerator System.Collections.IDictionary.GetEnumerator() => throw null;
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null;
bool System.Collections.ICollection.IsSynchronized { get => throw null; }
bool System.Collections.IDictionary.Contains(object keyword) => throw null;
object System.Collections.ICollection.SyncRoot { get => throw null; }
public object this[object keyword] { get => throw null; set => throw null; }
public bool IsReadOnly { get => throw null; }
public override string ToString() => throw null;
public string ConnectionString { get => throw null; set => throw null; }
public virtual System.Collections.ICollection Keys { get => throw null; }
public virtual System.Collections.ICollection Values { get => throw null; }
public virtual bool IsFixedSize { get => throw null; }
public virtual int Count { get => throw null; }
public virtual void Clear() => throw null;
void System.Collections.ICollection.CopyTo(System.Array array, int index) => throw null;
void System.Collections.IDictionary.Add(object keyword, object value) => throw null;
void System.Collections.IDictionary.Remove(object keyword) => throw null;
public void Dispose() => throw null;
}
// Generated from `System.Data.Common.DbConnection` in `System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089`
abstract public class DbConnection : System.IDisposable, System.Data.IDbConnection
{
public abstract string ConnectionString { get; set; }
public void Dispose() => throw null;
}
}
namespace SqlClient
{
// Generated from `System.Data.SqlClient.SqlConnectionStringBuilder` in `System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089`
public class SqlConnectionStringBuilder : System.Data.Common.DbConnectionStringBuilder
{
public SqlConnectionStringBuilder() => throw null;
public SqlConnectionStringBuilder(string connectionString) => throw null;
public bool Encrypt { get => throw null; set => throw null; }
public override System.Collections.ICollection Keys { get => throw null; }
public override System.Collections.ICollection Values { get => throw null; }
public override bool IsFixedSize { get => throw null; }
public override void Clear() => throw null;
}
// Generated from `System.Data.SqlClient.SqlConnection` in `System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089`
public class SqlConnection : System.Data.Common.DbConnection, System.ICloneable
{
object System.ICloneable.Clone() => throw null;
public SqlConnection() => throw null;
public SqlConnection(string connectionString) => throw null;
public override string ConnectionString { get => throw null; set => throw null; }
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Linq.Expressions;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
class DeserializedDelegate
{
delegate int D();
public static void M(FileStream fs)
{
var formatter = new BinaryFormatter();
// BAD
var a = (Func<int>)formatter.Deserialize(fs);
// BAD
var b = (Expression<Func<int>>)formatter.Deserialize(fs);
// BAD
var c = (D)formatter.Deserialize(fs);
// GOOD
var d = (int)formatter.Deserialize(fs);
}
}

View File

@@ -0,0 +1,4 @@
| DeserializedDelegate.cs:14:28:14:52 | call to method Deserialize | Deserialization of delegate type. |
| DeserializedDelegate.cs:16:40:16:64 | call to method Deserialize | Deserialization of delegate type. |
| DeserializedDelegate.cs:18:20:18:44 | call to method Deserialize | Deserialization of delegate type. |
| DeserializedDelegateBad.cs:11:28:11:52 | call to method Deserialize | Deserialization of delegate type. |

View File

@@ -0,0 +1 @@
Security Features/CWE-502/DeserializedDelegate.ql

View File

@@ -0,0 +1,14 @@
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
class Bad
{
public static int InvokeSerialized(FileStream fs)
{
var formatter = new BinaryFormatter();
// BAD
var f = (Func<int>)formatter.Deserialize(fs);
return f();
}
}

View File

@@ -0,0 +1 @@
// semmle-extractor-options: /r:System.Runtime.Serialization.Formatters.dll /r:System.IO.FileSystem.dll /r:System.Linq.Expressions.dll

View File

@@ -0,0 +1 @@
// semmle-extractor-options: /r:System.Runtime.Extensions.dll /r:System.IO.FileSystem.dll

View File

@@ -0,0 +1,34 @@
// This file contains auto-generated code.
// original-extractor-options: /r:System.Runtime.Extensions.dll /r:System.IO.FileSystem.dll
namespace System
{
namespace Web
{
namespace Script
{
namespace Serialization
{
// Generated from `System.Web.Script.Serialization.JavaScriptSerializer` in `System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`
public class JavaScriptSerializer
{
public JavaScriptSerializer() => throw null;
public JavaScriptSerializer(System.Web.Script.Serialization.JavaScriptTypeResolver resolver) => throw null;
public object DeserializeObject(string input) => throw null;
}
// Generated from `System.Web.Script.Serialization.JavaScriptTypeResolver` in `System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`
abstract public class JavaScriptTypeResolver
{
}
// Generated from `System.Web.Script.Serialization.SimpleTypeResolver` in `System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`
public class SimpleTypeResolver : System.Web.Script.Serialization.JavaScriptTypeResolver
{
public SimpleTypeResolver() => throw null;
}
}
}
}
}

View File

@@ -0,0 +1 @@
| UnsafeDeserializationBad.cs:9:16:9:38 | call to method DeserializeObject | Unsafe deserializer is used. Make sure the value being deserialized comes from a trusted source. |

View File

@@ -0,0 +1 @@
Security Features/CWE-502/UnsafeDeserialization.ql

View File

@@ -0,0 +1,11 @@
using System.Web.Script.Serialization;
class Bad
{
public static object Deserialize(string s)
{
JavaScriptSerializer sr = new JavaScriptSerializer(new SimpleTypeResolver());
// BAD
return sr.DeserializeObject(s);
}
}

View File

@@ -0,0 +1,11 @@
using System.Web.Script.Serialization;
class Good
{
public static object Deserialize(string s)
{
// GOOD
JavaScriptSerializer sr = new JavaScriptSerializer();
return sr.DeserializeObject(s);
}
}

View File

@@ -0,0 +1 @@
// semmle-extractor-options: /r:System.Runtime.Extensions.dll /r:System.IO.FileSystem.dll

View File

@@ -0,0 +1,45 @@
// This file contains auto-generated code.
// original-extractor-options: /r:System.Runtime.Extensions.dll /r:System.IO.FileSystem.dll
namespace System
{
namespace Web
{
namespace UI
{
namespace WebControls
{
public class TextBox
{
public string Text { get; set; }
}
}
}
namespace Script
{
namespace Serialization
{
// Generated from `System.Web.Script.Serialization.JavaScriptSerializer` in `System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`
public class JavaScriptSerializer
{
public JavaScriptSerializer() => throw null;
public JavaScriptSerializer(System.Web.Script.Serialization.JavaScriptTypeResolver resolver) => throw null;
public object DeserializeObject(string input) => throw null;
}
// Generated from `System.Web.Script.Serialization.JavaScriptTypeResolver` in `System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`
abstract public class JavaScriptTypeResolver
{
}
// Generated from `System.Web.Script.Serialization.SimpleTypeResolver` in `System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`
public class SimpleTypeResolver : System.Web.Script.Serialization.JavaScriptTypeResolver
{
public SimpleTypeResolver() => throw null;
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
edges
| UnsafeDeserializationUntrustedInputBad.cs:10:37:10:43 | access to parameter textBox | UnsafeDeserializationUntrustedInputBad.cs:10:37:10:48 | access to property Text |
nodes
| UnsafeDeserializationUntrustedInputBad.cs:10:37:10:43 | access to parameter textBox | semmle.label | access to parameter textBox |
| UnsafeDeserializationUntrustedInputBad.cs:10:37:10:48 | access to property Text | semmle.label | access to property Text |
#select
| UnsafeDeserializationUntrustedInputBad.cs:10:37:10:48 | access to property Text | UnsafeDeserializationUntrustedInputBad.cs:10:37:10:43 | access to parameter textBox | UnsafeDeserializationUntrustedInputBad.cs:10:37:10:48 | access to property Text | $@ flows to unsafe deserializer. | UnsafeDeserializationUntrustedInputBad.cs:10:37:10:43 | access to parameter textBox | User-provided data |

View File

@@ -0,0 +1 @@
Security Features/CWE-502/UnsafeDeserializationUntrustedInput.ql

View File

@@ -0,0 +1,12 @@
using System.Web.UI.WebControls;
using System.Web.Script.Serialization;
class Bad
{
public static object Deserialize(TextBox textBox)
{
JavaScriptSerializer sr = new JavaScriptSerializer(new SimpleTypeResolver());
// BAD
return sr.DeserializeObject(textBox.Text);
}
}

View File

@@ -0,0 +1,12 @@
using System.Web.UI.WebControls;
using System.Web.Script.Serialization;
class Good
{
public static object Deserialize(TextBox textBox)
{
JavaScriptSerializer sr = new JavaScriptSerializer();
// GOOD
return sr.DeserializeObject(textBox.Text);
}
}