diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll b/cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll index 07f6203f910..1100a28ea1f 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll @@ -26,7 +26,7 @@ module ProductFlow { ) { isSourcePair(source1.getNode(), source2.getNode()) and isSinkPair(sink1.getNode(), sink2.getNode()) and - reachablePair(this, source1, source2, sink1, sink2) + reachablePair2(this, source1, source2, sink1, sink2) } } @@ -52,6 +52,10 @@ module ProductFlow { override predicate isSink(DataFlow::Node sink) { exists(Configuration conf | conf.isSinkPair(_, sink)) } + + override int explorationLimit() { + result = 10 + } } predicate reachablePair1( diff --git a/cpp/ql/src/experimental/Likely Bugs/ArrayAccessProductFlow.ql b/cpp/ql/src/experimental/Likely Bugs/ArrayAccessProductFlow.ql new file mode 100644 index 00000000000..8296f73de22 --- /dev/null +++ b/cpp/ql/src/experimental/Likely Bugs/ArrayAccessProductFlow.ql @@ -0,0 +1,49 @@ +import cpp +import experimental.semmle.code.cpp.dataflow.ProductFlow +import experimental.semmle.code.cpp.semantic.analysis.RangeAnalysis +import experimental.semmle.code.cpp.rangeanalysis.Bound +import experimental.semmle.code.cpp.semantic.SemanticExprSpecific +import semmle.code.cpp.ir.IR +import semmle.code.cpp.valuenumbering.GlobalValueNumbering +import semmle.code.cpp.models.interfaces.Allocation + +predicate bounded(Instruction i, Bound b, int delta, boolean upper) { + // TODO: reason + semBounded(getSemanticExpr(i), b, delta, upper, _) +} + +class ArraySizeConfiguration extends ProductFlow::Configuration { + ArraySizeConfiguration() { this = "ArraySizeConfiguration" } + + override predicate isSourcePair(DataFlow::Node source1, DataFlow::Node source2) { + exists(GVN sizeGVN | + source1.asConvertedExpr().(AllocationExpr).getSizeExpr() = sizeGVN.getAnExpr() and + source2.asConvertedExpr() = sizeGVN.getAnExpr() + ) + } + + override predicate isSinkPair(DataFlow::Node sink1, DataFlow::Node sink2) { + exists(PointerAddInstruction pai, Instruction index, Bound b, int delta | + pai.getRight() = index and + pai.getLeft() = sink1.asInstruction() and + bounded(index, b, delta, true) and + sink2.asInstruction() = b.getInstruction()) + } +} + +predicate hasFlow1(DataFlow::PathNode source, DataFlow::PathNode sink) { + any(ProductFlow::Conf1 conf).hasFlowPath(source, sink) +} + +predicate hasFlow2(DataFlow2::PathNode source, DataFlow2::PathNode sink) { + any(ProductFlow::Conf2 conf).hasFlowPath(source, sink) + } + + + predicate hasPartialFlow2(DataFlow2::PartialPathNode source, DataFlow2::PartialPathNode sink) { + any(ProductFlow::Conf2 conf).hasPartialFlow(source, sink, _) + } + +from ArraySizeConfiguration conf, DataFlow::PathNode source1, DataFlow2::PathNode source2, DataFlow::PathNode sink1, DataFlow2::PathNode sink2 +where conf.hasFlowPath(source1, source2, sink1, sink2) +select source1, source2, sink1, sink2 \ No newline at end of file diff --git a/cpp/ql/src/experimental/Likely Bugs/OverrunWriteProductFlow.ql b/cpp/ql/src/experimental/Likely Bugs/OverrunWriteProductFlow.ql new file mode 100644 index 00000000000..bfa77a673d6 --- /dev/null +++ b/cpp/ql/src/experimental/Likely Bugs/OverrunWriteProductFlow.ql @@ -0,0 +1,31 @@ +import cpp +import experimental.semmle.code.cpp.dataflow.ProductFlow +import semmle.code.cpp.ir.IR +import semmle.code.cpp.valuenumbering.GlobalValueNumbering +import semmle.code.cpp.models.interfaces.Allocation +import semmle.code.cpp.models.interfaces.ArrayFunction + +class StringSizeConfiguration extends ProductFlow::Configuration { + StringSizeConfiguration() { this = "StringSizeConfiguration" } + + override predicate isSourcePair(DataFlow::Node bufSource, DataFlow::Node sizeSource) { + exists( + GVN sizeGVN // TODO: use-use flow instead of GVN + | + bufSource.asConvertedExpr().(AllocationExpr).getSizeExpr() = sizeGVN.getAnExpr() and + sizeSource.asConvertedExpr() = sizeGVN.getAnExpr() + ) + } + + override predicate isSinkPair(DataFlow::Node bufSink, DataFlow::Node sizeSink) { + exists(CallInstruction c, int bufIndex, int sizeIndex | + c.getStaticCallTarget().(ArrayFunction).hasArrayWithVariableSize(bufIndex, sizeIndex) and + c.getArgument(bufIndex) = bufSink.asInstruction() and + c.getArgument(sizeIndex) = sizeSink.asInstruction() + ) + } +} + +from StringSizeConfiguration conf, DataFlow::PathNode source1, DataFlow2::PathNode source2, DataFlow::PathNode sink1, DataFlow2::PathNode sink2 +where conf.hasFlowPath(source1, source2, sink1, sink2) +select source1, source2, sink1, sink2 diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.expected new file mode 100644 index 00000000000..56ae4e59a9a --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.expected @@ -0,0 +1,2 @@ +| test.cpp:19:19:19:24 | call to malloc | test.cpp:18:17:18:20 | size | test.cpp:26:18:26:23 | string | test.cpp:26:31:26:39 | (size_t)... | +| test.cpp:19:19:19:24 | call to malloc | test.cpp:18:17:18:20 | size | test.cpp:30:18:30:23 | string | test.cpp:30:31:30:39 | (size_t)... | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.qlref new file mode 100644 index 00000000000..21ced45de5d --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.qlref @@ -0,0 +1 @@ +experimental/Likely Bugs/OverrunWriteProductFlow.ql \ No newline at end of file diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/test.cpp new file mode 100644 index 00000000000..670b43f672e --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/test.cpp @@ -0,0 +1,37 @@ + +typedef unsigned long long size_t; +int sprintf(char *s, const char *format, ...); +int snprintf(char *s, size_t n, const char *format, ...); +int scanf(const char *format, ...); +int sscanf(const char *s, const char *format, ...); +char *malloc(size_t size); +char *strncpy(char *dst, const char *src, size_t n); + +typedef struct +{ + char *string; + int size; +} string_t; + +string_t *mk_string_t(int size) { + string_t *str = (string_t *) malloc(sizeof(string_t)); + str->size = size; + str->string = malloc(size); + return str; +} + +void test1(int size, char *buf) { + string_t *str = mk_string_t(size); + + strncpy(str->string, buf, str->size); +} + +void strncpy_wrapper(string_t *str, char *buf) { + strncpy(str->string, buf, str->size); +} + +void test2(int size, char *buf) { + string_t *str = mk_string_t(size); + strncpy_wrapper(str, buf); +} + diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.qlref new file mode 100644 index 00000000000..8186dd0721b --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.qlref @@ -0,0 +1 @@ +experimental/Likely Bugs/ArrayAccessProductFlow.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/test.cpp new file mode 100644 index 00000000000..9031a62bacf --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/test.cpp @@ -0,0 +1,99 @@ +char *malloc(int size); + +void test1(int size) { + char *arr = malloc(size); + for (int i = 0; i < size; i++) { + arr[i] = 0; + } + + for (int i = 0; i <= size; i++) { + arr[i] = i; + } +} + +typedef struct { + int size; + char *p; +} array_t; + +array_t mk_array(int size) { + array_t arr; + arr.size = size; + arr.p = malloc(size); + + return arr; +} + +void test2(int size) { + array_t arr = mk_array(size); + + for (int i = 0; i < arr.size; i++) { + arr.p[i] = 0; + } + + for (int i = 0; i <= arr.size; i++) { + arr.p[i] = i; + } +} + +void test3_callee(array_t arr) { + for (int i = 0; i < arr.size; i++) { + arr.p[i] = 0; + } + + for (int i = 0; i <= arr.size; i++) { + arr.p[i] = i; + } +} + +void test3(int size) { + test3_callee(mk_array(size)); +} + +void test4(int size) { + array_t arr; + arr.size = size; + arr.p = malloc(size); + + for (int i = 0; i < arr.size; i++) { + arr.p[i] = 0; + } + + for (int i = 0; i <= arr.size; i++) { + arr.p[i] = i; + } +} + +array_t *mk_array_p(int size) { + array_t *arr = (array_t*) malloc(sizeof(array_t)); + arr->size = size; + arr->p = malloc(size); + + return arr; +} + +void test5(int size) { + array_t *arr = mk_array_p(size); + + for (int i = 0; i < arr->size; i++) { + arr->p[i] = 0; + } + + for (int i = 0; i <= arr->size; i++) { + arr->p[i] = i; + } +} + +void test6_callee(array_t *arr) { + for (int i = 0; i < arr->size; i++) { + arr->p[i] = 0; + } + + for (int i = 0; i <= arr->size; i++) { + arr->p[i] = i; + } +} + +void test6(int size) { + test6_callee(mk_array_p(size)); +} \ No newline at end of file