mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
Merge pull request #2151 from raulgarciamsft/users/raul/oss
Users/raul/oss
This commit is contained in:
@@ -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) }
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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(), _)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
25
cpp/ql/src/Microsoft/SAL/IgnoreReturnValueSAL.ql
Normal file
25
cpp/ql/src/Microsoft/SAL/IgnoreReturnValueSAL.ql
Normal 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()
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
690
cpp/ql/src/Security/CWE/CWE-457/InitializationFunctions.qll
Normal file
690
cpp/ql/src/Security/CWE/CWE-457/InitializationFunctions.qll
Normal 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()) }
|
||||
190
cpp/ql/src/Security/CWE/CWE-457/UninitializedVariables.qll
Normal file
190
cpp/ql/src/Security/CWE/CWE-457/UninitializedVariables.qll
Normal 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)
|
||||
}
|
||||
}
|
||||
41
cpp/ql/src/semmle/code/cpp/NestedFields.qll
Normal file
41
cpp/ql/src/semmle/code/cpp/NestedFields.qll
Normal 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 }
|
||||
}
|
||||
83
cpp/ql/src/semmle/code/cpp/dispatch/VirtualDispatch.qll
Normal file
83
cpp/ql/src/semmle/code/cpp/dispatch/VirtualDispatch.qll
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
45
csharp/ql/src/Security Features/CWE-091/XMLInjection.qhelp
Normal file
45
csharp/ql/src/Security Features/CWE-091/XMLInjection.qhelp
Normal 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</name></employee><employee><name>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>
|
||||
53
csharp/ql/src/Security Features/CWE-091/XMLInjection.ql
Normal file
53
csharp/ql/src/Security Features/CWE-091/XMLInjection.ql
Normal 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"
|
||||
21
csharp/ql/src/Security Features/CWE-091/XMLInjectionBad.cs
Normal file
21
csharp/ql/src/Security Features/CWE-091/XMLInjectionBad.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
csharp/ql/src/Security Features/CWE-091/XMLInjectionGood.cs
Normal file
25
csharp/ql/src/Security Features/CWE-091/XMLInjectionGood.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
// ...
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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<int></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>
|
||||
@@ -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."
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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ñ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>
|
||||
@@ -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."
|
||||
@@ -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()))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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ñ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>
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
105
csharp/ql/src/semmle/code/csharp/serialization/Serialization.qll
Normal file
105
csharp/ql/src/semmle/code/csharp/serialization/Serialization.qll
Normal 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%")
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security Features/CWE-091/XMLInjection.ql
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security Features/CWE-114/AssemblyPathInjection.ql
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security Features/CWE-321/HardcodedEncryptionKey.ql
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Security Features/CWE-091/XMLInjection.ql
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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. |
|
||||
@@ -0,0 +1 @@
|
||||
Security Features/CWE-502/DeserializedDelegate.ql
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// semmle-extractor-options: /r:System.Runtime.Serialization.Formatters.dll /r:System.IO.FileSystem.dll /r:System.Linq.Expressions.dll
|
||||
@@ -0,0 +1 @@
|
||||
// semmle-extractor-options: /r:System.Runtime.Extensions.dll /r:System.IO.FileSystem.dll
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. |
|
||||
@@ -0,0 +1 @@
|
||||
Security Features/CWE-502/UnsafeDeserialization.ql
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// semmle-extractor-options: /r:System.Runtime.Extensions.dll /r:System.IO.FileSystem.dll
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security Features/CWE-502/UnsafeDeserializationUntrustedInput.ql
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user