Merge branch 'main' into use-range-analysis-in-buffer-write

This commit is contained in:
Mathias Vorreiter Pedersen
2021-11-08 08:22:49 +00:00
68 changed files with 2937 additions and 2070 deletions

View File

@@ -3,11 +3,14 @@ private import semmle.code.cpp.models.interfaces.ArrayFunction
private import semmle.code.cpp.models.implementations.Strcat
import semmle.code.cpp.dataflow.DataFlow
private predicate mayAddNullTerminatorHelper(Expr e, VariableAccess va, Expr e0) {
exists(StackVariable v0, Expr val |
exprDefinition(v0, e, val) and
val.getAChild*() = va and
mayAddNullTerminator(e0, v0.getAnAccess())
/**
* Holds if the expression `e` assigns something including `va` to a
* stack variable `v0`.
*/
private predicate mayAddNullTerminatorHelper(Expr e, VariableAccess va, StackVariable v0) {
exists(Expr val |
exprDefinition(v0, e, val) and // `e` is `v0 := val`
val.getAChild*() = va
)
}
@@ -25,8 +28,8 @@ private predicate controlFlowNodeSuccessorTransitive(ControlFlowNode n1, Control
}
/**
* Holds if the expression `e` may add a null terminator to the string in
* variable `v`.
* Holds if the expression `e` may add a null terminator to the string
* accessed by `va`.
*/
predicate mayAddNullTerminator(Expr e, VariableAccess va) {
// Assignment: dereferencing or array access
@@ -43,8 +46,9 @@ predicate mayAddNullTerminator(Expr e, VariableAccess va) {
)
or
// Assignment to another stack variable
exists(Expr e0 |
mayAddNullTerminatorHelper(pragma[only_bind_into](e), va, pragma[only_bind_into](e0)) and
exists(StackVariable v0, Expr e0 |
mayAddNullTerminatorHelper(e, va, v0) and
mayAddNullTerminator(pragma[only_bind_into](e0), pragma[only_bind_into](v0.getAnAccess())) and
controlFlowNodeSuccessorTransitive(e, e0)
)
or

View File

@@ -474,6 +474,24 @@ module TaintedWithPath {
}
}
/**
* INTERNAL: Do not use.
*/
module Private {
/** Gets a predecessor `PathNode` of `pathNode`, if any. */
PathNode getAPredecessor(PathNode pathNode) { edges(result, pathNode) }
/** Gets the element that `pathNode` wraps, if any. */
Element getElementFromPathNode(PathNode pathNode) {
exists(DataFlow::Node node | node = pathNode.(WrapPathNode).inner().getNode() |
result = node.asExpr() or
result = node.asParameter()
)
or
result = pathNode.(EndpointPathNode).inner()
}
}
private class WrapPathNode extends PathNode, TWrapPathNode {
DataFlow3::PathNode inner() { this = TWrapPathNode(result) }

View File

@@ -4,8 +4,8 @@ using SinkFunction = void (*)(int);
void notSink(int notSinkParam);
void callsSink(int sinkParam) {
sink(sinkParam); // $ ast,ir=31:28 ast,ir=32:31 ast,ir=34:22 MISSING: ast,ir=28
void callsSink(int sinkParam) { // $ ir-path=31:28 ir-path=32:31 ir-path=34:22
sink(sinkParam); // $ ir-sink=31:28 ir-sink=32:31 ir-sink=34:22 ast=31:28 ast=32:31 ast=34:22 MISSING: ast,ir=28
}
struct {
@@ -25,13 +25,13 @@ void assignGlobals() {
};
void testStruct() {
globalStruct.sinkPtr(atoi(getenv("TAINTED"))); // $ MISSING: ast,ir
globalStruct.sinkPtr(atoi(getenv("TAINTED"))); // $ ir MISSING: ast
globalStruct.notSinkPtr(atoi(getenv("TAINTED"))); // clean
globalUnion.sinkPtr(atoi(getenv("TAINTED"))); // $ ast,ir
globalUnion.notSinkPtr(atoi(getenv("TAINTED"))); // $ ast,ir
globalUnion.sinkPtr(atoi(getenv("TAINTED"))); // $ ast ir-path
globalUnion.notSinkPtr(atoi(getenv("TAINTED"))); // $ ast ir-path
globalSinkPtr(atoi(getenv("TAINTED"))); // $ ast,ir
globalSinkPtr(atoi(getenv("TAINTED"))); // $ ast ir-path
}
class B {
@@ -48,19 +48,19 @@ class D2 : public D1 {
class D3 : public D2 {
public:
void f(const char* p) override {
sink(p); // $ ast,ir=58:10 ast,ir=60:17 ast,ir=61:28 ast,ir=62:29 ast,ir=63:33 SPURIOUS: ast,ir=73:30
void f(const char* p) override { // $ ir-path=58:10 ir-path=60:17 ir-path=61:28 ir-path=62:29 ir-path=63:33 ir-path=73:30
sink(p); // $ ir-sink=58:10 ir-sink=60:17 ir-sink=61:28 ir-sink=62:29 ir-sink=63:33 ast=58:10 ast=60:17 ast=61:28 ast=62:29 ast=63:33 SPURIOUS: ast=73:30 ir-sink=73:30
}
};
void test_dynamic_cast() {
B* b = new D3();
b->f(getenv("VAR")); // $ ast,ir
b->f(getenv("VAR")); // $ ast ir-path
((D2*)b)->f(getenv("VAR")); // $ ast,ir
static_cast<D2*>(b)->f(getenv("VAR")); // $ ast,ir
dynamic_cast<D2*>(b)->f(getenv("VAR")); // $ ast,ir
reinterpret_cast<D2*>(b)->f(getenv("VAR")); // $ ast,ir
((D2*)b)->f(getenv("VAR")); // $ ast ir-path
static_cast<D2*>(b)->f(getenv("VAR")); // $ ast ir-path
dynamic_cast<D2*>(b)->f(getenv("VAR")); // $ ast ir-path
reinterpret_cast<D2*>(b)->f(getenv("VAR")); // $ ast ir-path
B* b2 = new D2();
b2->f(getenv("VAR"));
@@ -70,5 +70,5 @@ void test_dynamic_cast() {
dynamic_cast<D2*>(b2)->f(getenv("VAR"));
reinterpret_cast<D2*>(b2)->f(getenv("VAR"));
dynamic_cast<D3*>(b2)->f(getenv("VAR")); // $ SPURIOUS: ast,ir
dynamic_cast<D3*>(b2)->f(getenv("VAR")); // $ SPURIOUS: ast ir-path
}

View File

@@ -7,9 +7,10 @@ import cpp
import semmle.code.cpp.security.TaintTrackingImpl as ASTTaintTracking
import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IRDefaultTaintTracking
import IRDefaultTaintTracking::TaintedWithPath as TaintedWithPath
import TaintedWithPath::Private
import TestUtilities.InlineExpectationsTest
predicate isSink(Element sink) {
predicate isSinkArgument(Element sink) {
exists(FunctionCall call |
call.getTarget().getName() = "sink" and
sink = call.getAnArgument()
@@ -19,31 +20,34 @@ predicate isSink(Element sink) {
predicate astTaint(Expr source, Element sink) { ASTTaintTracking::tainted(source, sink) }
class SourceConfiguration extends TaintedWithPath::TaintTrackingConfiguration {
override predicate isSink(Element e) { any() }
override predicate isSink(Element e) { isSinkArgument(e) }
}
predicate irTaint(Expr source, Element sink) {
TaintedWithPath::taintedWithPath(source, sink, _, _)
predicate irTaint(Element source, Element sink, string tag) {
exists(TaintedWithPath::PathNode sinkNode, TaintedWithPath::PathNode predNode |
TaintedWithPath::taintedWithPath(source, _, _, sinkNode) and
predNode = getAPredecessor*(sinkNode) and
sink = getElementFromPathNode(predNode) and
// Make sure the path is actually reachable from this predecessor.
// Otherwise, we could pick `predNode` to be b when `source` is
// `source1` in this dataflow graph:
// source1 ---> a ---> c ---> sinkNode
// ^
// source2 ---> b --/
source = getElementFromPathNode(getAPredecessor*(predNode)) and
if sinkNode = predNode then tag = "ir-sink" else tag = "ir-path"
)
}
class IRDefaultTaintTrackingTest extends InlineExpectationsTest {
IRDefaultTaintTrackingTest() { this = "IRDefaultTaintTrackingTest" }
override string getARelevantTag() { result = "ir" }
override string getARelevantTag() { result = ["ir-path", "ir-sink"] }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Expr source, Element tainted, int n |
tag = "ir" and
irTaint(source, tainted) and
(
isSink(tainted)
or
exists(Element sink |
isSink(sink) and
irTaint(tainted, sink)
)
) and
n = strictcount(Expr otherSource | irTaint(otherSource, tainted)) and
exists(Element source, Element tainted, int n |
irTaint(source, tainted, tag) and
n = strictcount(Element otherSource | irTaint(otherSource, tainted, _)) and
(
n = 1 and value = ""
or
@@ -70,10 +74,10 @@ class ASTTaintTrackingTest extends InlineExpectationsTest {
tag = "ast" and
astTaint(source, tainted) and
(
isSink(tainted)
isSinkArgument(tainted)
or
exists(Element sink |
isSink(sink) and
isSinkArgument(sink) and
astTaint(tainted, sink)
)
) and

View File

@@ -13,8 +13,8 @@ struct S {
}
};
void calls_sink_with_argv(const char* a) {
sink(a); // $ ast,ir=96:26 ast,ir=98:18
void calls_sink_with_argv(const char* a) { // $ ir-path=96:26 ir-path=98:18
sink(a); // $ ast=96:26 ast=98:18 ir-sink=96:26 ir-sink=98:18
}
extern int i;
@@ -26,8 +26,8 @@ public:
class DerivedCallsSink : public BaseWithPureVirtual {
public:
void f(const char* p) override {
sink(p); // $ ir ast=108:10 SPURIOUS: ast=111:10
void f(const char* p) override { // $ ir-path
sink(p); // $ ir-sink ast=108:10 SPURIOUS: ast=111:10
}
};
@@ -38,8 +38,8 @@ public:
class DerivedCallsSinkDiamond1 : virtual public BaseWithPureVirtual {
public:
void f(const char* p) override {
sink(p); // $ ast,ir
void f(const char* p) override { // $ ir-path
sink(p); // $ ast ir-sink
}
};
@@ -49,7 +49,7 @@ public:
};
class DerivesMultiple : public DerivedCallsSinkDiamond1, public DerivedDoesNotCallSinkDiamond2 {
void f(const char* p) override {
void f(const char* p) override { // $ ir-path
DerivedCallsSinkDiamond1::f(p);
}
};
@@ -57,15 +57,15 @@ class DerivesMultiple : public DerivedCallsSinkDiamond1, public DerivedDoesNotCa
template<typename T>
class CRTP {
public:
void f(const char* p) {
void f(const char* p) { // $ ir-path
static_cast<T*>(this)->g(p);
}
};
class CRTPCallsSink : public CRTP<CRTPCallsSink> {
public:
void g(const char* p) {
sink(p); // $ ast,ir
void g(const char* p) { // $ ir-path
sink(p); // $ ast ir-sink
}
};
@@ -78,8 +78,8 @@ class Derived2 : public Derived1 {
class Derived3 : public Derived2 {
public:
void f(const char* p) override {
sink(p); // $ ast,ir=124:19 ast,ir=126:43 ast,ir=128:44
void f(const char* p) override { // $ ir-path=124:19 ir-path=126:43 ir-path=128:44
sink(p); // $ ast,ir-sink=124:19 ast,ir-sink=126:43 ast,ir-sink=128:44
}
};
@@ -89,41 +89,41 @@ class CRTPDoesNotCallSink : public CRTP<CRTPDoesNotCallSink> {
};
int main(int argc, char *argv[]) {
sink(argv[0]); // $ ast,ir
sink(argv[0]); // $ ast,ir-path,ir-sink
sink(reinterpret_cast<int>(argv)); // $ ast,ir
sink(reinterpret_cast<int>(argv)); // $ ast,ir-sink
calls_sink_with_argv(argv[1]); // $ ast,ir
calls_sink_with_argv(argv[1]); // $ ast,ir-path
char*** p = &argv; // $ ast,ir
char*** p = &argv; // $ ast,ir-path
sink(*p[0]); // $ ast,ir
sink(*p[0]); // $ ast,ir-sink
calls_sink_with_argv(*p[i]); // $ MISSING: ast,ir
calls_sink_with_argv(*p[i]); // $ MISSING: ast,ir-path
sink(*(argv + 1)); // $ ast,ir
sink(*(argv + 1)); // $ ast,ir-path ir-sink
BaseWithPureVirtual* b = new DerivedCallsSink;
b->f(argv[1]); // $ ast,ir
b->f(argv[1]); // $ ast,ir-path
b = new DerivedDoesNotCallSink;
b->f(argv[0]); // $ SPURIOUS: ast
BaseWithPureVirtual* b2 = new DerivesMultiple;
b2->f(argv[i]); // $ ast,ir
b2->f(argv[i]); // $ ast,ir-path
CRTP<CRTPDoesNotCallSink> crtp_not_call_sink;
crtp_not_call_sink.f(argv[0]); // clean
CRTP<CRTPCallsSink> crtp_calls_sink;
crtp_calls_sink.f(argv[0]); // $ ast,ir
crtp_calls_sink.f(argv[0]); // $ ast,ir-path
Derived1* calls_sink = new Derived3;
calls_sink->f(argv[1]); // $ ast,ir
calls_sink->f(argv[1]); // $ ast,ir-path
static_cast<Derived2*>(calls_sink)->f(argv[1]); // $ ast,ir
static_cast<Derived2*>(calls_sink)->f(argv[1]); // $ ast,ir-path
dynamic_cast<Derived2*>(calls_sink)->f(argv[1]); // $ ast,ir
dynamic_cast<Derived2*>(calls_sink)->f(argv[1]); // $ ast,ir-path
}