mirror of
https://github.com/github/codeql.git
synced 2026-05-03 12:45:27 +02:00
Merge branch 'main' into use-range-analysis-in-buffer-write
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user