diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c97b2d96286..5324ac8f301 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,14 +14,16 @@ If you have an idea for a query that you would like to share with other CodeQL u 1. **Directory structure** - There are six language-specific query directories in this repository: + There are eight language-specific query directories in this repository: * C/C++: `cpp/ql/src` * C#: `csharp/ql/src` - * Java: `java/ql/src` + * Go: `go/ql/src` + * Java/Kotlin: `java/ql/src` * JavaScript: `javascript/ql/src` * Python: `python/ql/src` * Ruby: `ruby/ql/src` + * Swift: `swift/ql/src` Each language-specific directory contains further subdirectories that group queries based on their `@tags` or purpose. - Experimental queries and libraries are stored in the `experimental` subdirectory within each language-specific directory in the [CodeQL repository](https://github.com/github/codeql). For example, experimental Java queries and libraries are stored in `java/ql/src/experimental` and any corresponding tests in `java/ql/test/experimental`. diff --git a/cpp/ql/lib/CHANGELOG.md b/cpp/ql/lib/CHANGELOG.md index 8f6d36edc61..162ce6d354b 100644 --- a/cpp/ql/lib/CHANGELOG.md +++ b/cpp/ql/lib/CHANGELOG.md @@ -1,3 +1,23 @@ +## 0.8.1 + +### Deprecated APIs + +* The library `semmle.code.cpp.dataflow.DataFlow` has been deprecated. Please use `semmle.code.cpp.dataflow.new.DataFlow` instead. + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* The `IRGuards` library has improved handling of pointer addition and subtraction operations. + ## 0.8.0 ### New Features diff --git a/cpp/ql/lib/change-notes/released/0.8.1.md b/cpp/ql/lib/change-notes/released/0.8.1.md new file mode 100644 index 00000000000..7c7c9b0109b --- /dev/null +++ b/cpp/ql/lib/change-notes/released/0.8.1.md @@ -0,0 +1,19 @@ +## 0.8.1 + +### Deprecated APIs + +* The library `semmle.code.cpp.dataflow.DataFlow` has been deprecated. Please use `semmle.code.cpp.dataflow.new.DataFlow` instead. + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* The `IRGuards` library has improved handling of pointer addition and subtraction operations. diff --git a/cpp/ql/lib/codeql-pack.release.yml b/cpp/ql/lib/codeql-pack.release.yml index 37eab3197dc..2f693f95ba6 100644 --- a/cpp/ql/lib/codeql-pack.release.yml +++ b/cpp/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.8.0 +lastReleaseVersion: 0.8.1 diff --git a/cpp/ql/lib/qlpack.yml b/cpp/ql/lib/qlpack.yml index 9bc445e8fdc..e0aca5679e3 100644 --- a/cpp/ql/lib/qlpack.yml +++ b/cpp/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/cpp-all -version: 0.8.0 +version: 0.8.1 groups: cpp dbscheme: semmlecode.cpp.dbscheme extractor: cpp diff --git a/cpp/ql/lib/semmle/code/cpp/PrintAST.qll b/cpp/ql/lib/semmle/code/cpp/PrintAST.qll index 11e1791ba60..b552c6eeb38 100644 --- a/cpp/ql/lib/semmle/code/cpp/PrintAST.qll +++ b/cpp/ql/lib/semmle/code/cpp/PrintAST.qll @@ -741,6 +741,8 @@ private predicate namedExprChildPredicates(Expr expr, Element ele, string pred) or expr.(VariableAccess).getQualifier() = ele and pred = "getQualifier()" or + expr.(FunctionAccess).getQualifier() = ele and pred = "getQualifier()" + or exists(Field f | expr.(ClassAggregateLiteral).getAFieldExpr(f) = ele and pred = "getAFieldExpr(" + f.toString() + ")" diff --git a/cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll b/cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll index d12eaa4e238..49b7e0c8b6d 100644 --- a/cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll +++ b/cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll @@ -627,6 +627,20 @@ private predicate sub_lt( x = int_value(rhs.getRight()) and k = c - x ) + or + exists(PointerSubInstruction lhs, int c, int x | + compares_lt(cmp, lhs.getAUse(), right, c, isLt, testIsTrue) and + left = lhs.getLeftOperand() and + x = int_value(lhs.getRight()) and + k = c + x + ) + or + exists(PointerSubInstruction rhs, int c, int x | + compares_lt(cmp, left, rhs.getAUse(), c, isLt, testIsTrue) and + right = rhs.getLeftOperand() and + x = int_value(rhs.getRight()) and + k = c - x + ) } // left + x < right + c => left < right + (c-x) @@ -653,6 +667,26 @@ private predicate add_lt( ) and k = c + x ) + or + exists(PointerAddInstruction lhs, int c, int x | + compares_lt(cmp, lhs.getAUse(), right, c, isLt, testIsTrue) and + ( + left = lhs.getLeftOperand() and x = int_value(lhs.getRight()) + or + left = lhs.getRightOperand() and x = int_value(lhs.getLeft()) + ) and + k = c - x + ) + or + exists(PointerAddInstruction rhs, int c, int x | + compares_lt(cmp, left, rhs.getAUse(), c, isLt, testIsTrue) and + ( + right = rhs.getLeftOperand() and x = int_value(rhs.getRight()) + or + right = rhs.getRightOperand() and x = int_value(rhs.getLeft()) + ) and + k = c + x + ) } // left - x == right + c => left == right + (c+x) @@ -673,6 +707,20 @@ private predicate sub_eq( x = int_value(rhs.getRight()) and k = c - x ) + or + exists(PointerSubInstruction lhs, int c, int x | + compares_eq(cmp, lhs.getAUse(), right, c, areEqual, testIsTrue) and + left = lhs.getLeftOperand() and + x = int_value(lhs.getRight()) and + k = c + x + ) + or + exists(PointerSubInstruction rhs, int c, int x | + compares_eq(cmp, left, rhs.getAUse(), c, areEqual, testIsTrue) and + right = rhs.getLeftOperand() and + x = int_value(rhs.getRight()) and + k = c - x + ) } // left + x == right + c => left == right + (c-x) @@ -699,6 +747,26 @@ private predicate add_eq( ) and k = c + x ) + or + exists(PointerAddInstruction lhs, int c, int x | + compares_eq(cmp, lhs.getAUse(), right, c, areEqual, testIsTrue) and + ( + left = lhs.getLeftOperand() and x = int_value(lhs.getRight()) + or + left = lhs.getRightOperand() and x = int_value(lhs.getLeft()) + ) and + k = c - x + ) + or + exists(PointerAddInstruction rhs, int c, int x | + compares_eq(cmp, left, rhs.getAUse(), c, areEqual, testIsTrue) and + ( + right = rhs.getLeftOperand() and x = int_value(rhs.getRight()) + or + right = rhs.getRightOperand() and x = int_value(rhs.getLeft()) + ) and + k = c + x + ) } /** The int value of integer constant expression. */ diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow.qll index 24fdb60eff1..56e636eab23 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow.qll @@ -20,10 +20,12 @@ import cpp /** + * DEPRECATED: Use `semmle.code.cpp.dataflow.new.DataFlow` instead. + * * Provides classes for performing local (intra-procedural) and * global (inter-procedural) data flow analyses. */ -module DataFlow { +deprecated module DataFlow { import semmle.code.cpp.dataflow.internal.DataFlow import semmle.code.cpp.dataflow.internal.DataFlowImpl1 } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow2.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow2.qll index 6d9c38af545..19ffa16b76c 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow2.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow2.qll @@ -12,9 +12,11 @@ import cpp /** + * DEPRECATED: Use `semmle.code.cpp.dataflow.new.DataFlow2` instead. + * * Provides classes for performing local (intra-procedural) and * global (inter-procedural) data flow analyses. */ -module DataFlow2 { +deprecated module DataFlow2 { import semmle.code.cpp.dataflow.internal.DataFlowImpl2 } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow3.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow3.qll index c4e7a4906fc..554b2e155b4 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow3.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow3.qll @@ -12,9 +12,11 @@ import cpp /** + * DEPRECATED: Use `semmle.code.cpp.dataflow.new.DataFlow3` instead. + * * Provides classes for performing local (intra-procedural) and * global (inter-procedural) data flow analyses. */ -module DataFlow3 { +deprecated module DataFlow3 { import semmle.code.cpp.dataflow.internal.DataFlowImpl3 } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow4.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow4.qll index 8287ecf70fe..fdd4e8ab7ae 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow4.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/DataFlow4.qll @@ -12,9 +12,11 @@ import cpp /** + * DEPRECATED: Use `semmle.code.cpp.dataflow.new.DataFlow4` instead. + * * Provides classes for performing local (intra-procedural) and * global (inter-procedural) data flow analyses. */ -module DataFlow4 { +deprecated module DataFlow4 { import semmle.code.cpp.dataflow.internal.DataFlowImpl4 } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking.qll index 57d93068a4c..fcee9801e78 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking.qll @@ -19,10 +19,12 @@ import semmle.code.cpp.dataflow.DataFlow import semmle.code.cpp.dataflow.DataFlow2 /** + * DEPRECATED: Use `semmle.code.cpp.dataflow.new.TaintTracking` instead. + * * Provides classes for performing local (intra-procedural) and * global (inter-procedural) taint-tracking analyses. */ -module TaintTracking { +deprecated module TaintTracking { import semmle.code.cpp.dataflow.internal.tainttracking1.TaintTracking import semmle.code.cpp.dataflow.internal.tainttracking1.TaintTrackingImpl } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking2.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking2.qll index a8f92ee1041..dce00316cbb 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking2.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/TaintTracking2.qll @@ -12,9 +12,11 @@ */ /** + * DEPRECATED: Use `semmle.code.cpp.dataflow.new.TaintTracking2` instead. + * * Provides classes for performing local (intra-procedural) and * global (inter-procedural) taint-tracking analyses. */ -module TaintTracking2 { +deprecated module TaintTracking2 { import semmle.code.cpp.dataflow.internal.tainttracking2.TaintTrackingImpl } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll index f34554d6eea..03975c6a54a 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll @@ -46,6 +46,14 @@ signature module ConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or isAdditionalFlowStep(_, node) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -114,7 +122,7 @@ signature module StateConfigSig { * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state); + default predicate isBarrier(Node node, FlowState state) { none() } /** Holds if data flow into `node` is prohibited. */ default predicate isBarrierIn(Node node) { none() } @@ -131,7 +139,9 @@ signature module StateConfigSig { * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + default predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } /** * Holds if an arbitrary number of implicit read steps of content `c` may be @@ -139,6 +149,17 @@ signature module StateConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or + isAdditionalFlowStep(_, node) or + isAdditionalFlowStep(node, _, _, _) or + isAdditionalFlowStep(_, _, node, _) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 410543e0fc9..29561b0f0a6 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -66,6 +66,12 @@ signature module FullStateConfigSig { */ predicate allowImplicitRead(Node node, ContentSet c); + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + predicate neverSkip(Node node); + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -254,6 +260,11 @@ module Impl { not fullBarrier(node2) } + pragma[nomagic] + private predicate isUnreachableInCall1(NodeEx n, LocalCallContextSpecificCall cc) { + isUnreachableInCallCached(n.asNode(), cc.getCall()) + } + /** * Holds if data can flow in one local step from `node1` to `node2`. */ @@ -2019,7 +2030,8 @@ module Impl { castNode(this.asNode()) or clearsContentCached(this.asNode(), _) or expectsContentCached(this.asNode(), _) or - neverSkipInPathGraph(this.asNode()) + neverSkipInPathGraph(this.asNode()) or + Config::neverSkip(this.asNode()) } } @@ -2108,7 +2120,7 @@ module Impl { NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, LocalCallContext cc ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCall1(node2, cc) and ( localFlowEntry(node1, pragma[only_bind_into](state)) and ( @@ -2123,7 +2135,7 @@ module Impl { ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) + not isUnreachableInCall1(node1, cc) or exists(NodeEx mid | localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and @@ -2160,10 +2172,8 @@ module Impl { preservesValue = false and t = node2.getDataFlowType() and callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) + not isUnreachableInCall1(node1, callContext) and + not isUnreachableInCall1(node2, callContext) } } @@ -2703,7 +2713,7 @@ module Impl { ParamNodeEx getParamNode() { result = p } - override string toString() { result = p + ": " + ap } + override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn @@ -2755,12 +2765,21 @@ module Impl { ) } + private predicate forceUnfold(AccessPathApprox apa) { + forceHighPrecision(apa.getHead()) + or + exists(Content c2 | + apa = TConsCons(_, _, c2, _) and + forceHighPrecision(c2) + ) + } + /** * Holds with `unfold = false` if a precise head-tail representation of `apa` is * expected to be expensive. Holds with `unfold = true` otherwise. */ private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { - if forceHighPrecision(apa.getHead()) + if forceUnfold(apa) then unfold = true else exists(int aps, int nodes, int apLimit, int tupleLimit | @@ -3089,6 +3108,12 @@ module Impl { result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } + private string ppSummaryCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">" + } + /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() } @@ -3097,7 +3122,9 @@ module Impl { * representation of the call context. */ string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + result = + this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + + this.ppSummaryCtx() } /** diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/exprs/Access.qll b/cpp/ql/lib/semmle/code/cpp/exprs/Access.qll index 04e7376b11d..c88395f18e9 100644 --- a/cpp/ql/lib/semmle/code/cpp/exprs/Access.qll +++ b/cpp/ql/lib/semmle/code/cpp/exprs/Access.qll @@ -368,6 +368,11 @@ class FunctionAccess extends Access, @routineexpr { /** Gets the accessed function. */ override Function getTarget() { funbind(underlyingElement(this), unresolveElement(result)) } + /** + * Gets the expression generating the function being accessed. + */ + Expr getQualifier() { this.getChild(-1) = result } + /** Gets a textual representation of this function access. */ override string toString() { if exists(this.getTarget()) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll index f34554d6eea..03975c6a54a 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll @@ -46,6 +46,14 @@ signature module ConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or isAdditionalFlowStep(_, node) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -114,7 +122,7 @@ signature module StateConfigSig { * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state); + default predicate isBarrier(Node node, FlowState state) { none() } /** Holds if data flow into `node` is prohibited. */ default predicate isBarrierIn(Node node) { none() } @@ -131,7 +139,9 @@ signature module StateConfigSig { * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + default predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } /** * Holds if an arbitrary number of implicit read steps of content `c` may be @@ -139,6 +149,17 @@ signature module StateConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or + isAdditionalFlowStep(_, node) or + isAdditionalFlowStep(node, _, _, _) or + isAdditionalFlowStep(_, _, node, _) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 410543e0fc9..29561b0f0a6 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -66,6 +66,12 @@ signature module FullStateConfigSig { */ predicate allowImplicitRead(Node node, ContentSet c); + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + predicate neverSkip(Node node); + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -254,6 +260,11 @@ module Impl { not fullBarrier(node2) } + pragma[nomagic] + private predicate isUnreachableInCall1(NodeEx n, LocalCallContextSpecificCall cc) { + isUnreachableInCallCached(n.asNode(), cc.getCall()) + } + /** * Holds if data can flow in one local step from `node1` to `node2`. */ @@ -2019,7 +2030,8 @@ module Impl { castNode(this.asNode()) or clearsContentCached(this.asNode(), _) or expectsContentCached(this.asNode(), _) or - neverSkipInPathGraph(this.asNode()) + neverSkipInPathGraph(this.asNode()) or + Config::neverSkip(this.asNode()) } } @@ -2108,7 +2120,7 @@ module Impl { NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, LocalCallContext cc ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCall1(node2, cc) and ( localFlowEntry(node1, pragma[only_bind_into](state)) and ( @@ -2123,7 +2135,7 @@ module Impl { ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) + not isUnreachableInCall1(node1, cc) or exists(NodeEx mid | localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and @@ -2160,10 +2172,8 @@ module Impl { preservesValue = false and t = node2.getDataFlowType() and callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) + not isUnreachableInCall1(node1, callContext) and + not isUnreachableInCall1(node2, callContext) } } @@ -2703,7 +2713,7 @@ module Impl { ParamNodeEx getParamNode() { result = p } - override string toString() { result = p + ": " + ap } + override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn @@ -2755,12 +2765,21 @@ module Impl { ) } + private predicate forceUnfold(AccessPathApprox apa) { + forceHighPrecision(apa.getHead()) + or + exists(Content c2 | + apa = TConsCons(_, _, c2, _) and + forceHighPrecision(c2) + ) + } + /** * Holds with `unfold = false` if a precise head-tail representation of `apa` is * expected to be expensive. Holds with `unfold = true` otherwise. */ private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { - if forceHighPrecision(apa.getHead()) + if forceUnfold(apa) then unfold = true else exists(int aps, int nodes, int apLimit, int tupleLimit | @@ -3089,6 +3108,12 @@ module Impl { result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } + private string ppSummaryCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">" + } + /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() } @@ -3097,7 +3122,9 @@ module Impl { * representation of the call context. */ string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + result = + this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + + this.ppSummaryCtx() } /** diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index be70086a93a..b0de9745816 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DefaultTaintTrackingImpl.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DefaultTaintTrackingImpl.qll index 960b373b4fa..ee70380224c 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DefaultTaintTrackingImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DefaultTaintTrackingImpl.qll @@ -448,6 +448,8 @@ module TaintedWithPath { } predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) } + + predicate neverSkip(Node node) { none() } } private module AdjustedFlow = TaintTracking::Global; diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/ProductFlow.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/ProductFlow.qll index ee34b9932b4..c5d8eae5799 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/ProductFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/ProductFlow.qll @@ -297,6 +297,22 @@ module ProductFlow { reachable(source1, source2, sink1, sink2) } + /** Holds if data can flow from `(source1, source2)` to `(sink1, sink2)`. */ + predicate flow( + DataFlow::Node source1, DataFlow::Node source2, DataFlow::Node sink1, DataFlow::Node sink2 + ) { + exists( + Flow1::PathNode pSource1, Flow2::PathNode pSource2, Flow1::PathNode pSink1, + Flow2::PathNode pSink2 + | + pSource1.getNode() = source1 and + pSource2.getNode() = source2 and + pSink1.getNode() = sink1 and + pSink2.getNode() = sink2 and + flowPath(pSource1, pSource2, pSink1, pSink2) + ) + } + private module Config1 implements DataFlow::StateConfigSig { class FlowState = FlowState1; diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll index 5832aa9f928..4f93f5d3702 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll @@ -991,9 +991,19 @@ class TranslatedStructuredBindingVariableAccess extends TranslatedNonConstantExp class TranslatedFunctionAccess extends TranslatedNonConstantExpr { override FunctionAccess expr; - override TranslatedElement getChild(int id) { none() } + override TranslatedElement getChild(int id) { + id = 0 and result = this.getQualifier() // Might not exist + } - override Instruction getFirstInstruction() { result = this.getInstruction(OnlyInstructionTag()) } + final TranslatedExpr getQualifier() { + result = getTranslatedExpr(expr.getQualifier().getFullyConverted()) + } + + override Instruction getFirstInstruction() { + if exists(this.getQualifier()) + then result = this.getQualifier().getFirstInstruction() + else result = this.getInstruction(OnlyInstructionTag()) + } override Instruction getResult() { result = this.getInstruction(OnlyInstructionTag()) } @@ -1014,7 +1024,9 @@ class TranslatedFunctionAccess extends TranslatedNonConstantExpr { result = expr.getTarget() } - override Instruction getChildSuccessor(TranslatedElement child) { none() } + override Instruction getChildSuccessor(TranslatedElement child) { + child = this.getQualifier() and result = this.getInstruction(OnlyInstructionTag()) + } } /** diff --git a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/SemanticExprSpecific.qll b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/SemanticExprSpecific.qll index 6ac6081c558..a92361ece17 100644 --- a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/SemanticExprSpecific.qll +++ b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/SemanticExprSpecific.qll @@ -188,6 +188,9 @@ module SemanticExprConfig { none() } + /** Holds if no range analysis should be performed on the phi edges in `f`. */ + private predicate excludeFunction(Cpp::Function f) { count(f.getEntryPoint()) > 1 } + SemType getUnknownExprType(Expr expr) { result = getSemanticType(expr.getResultIRType()) } class BasicBlock = IR::IRBlock; @@ -270,7 +273,13 @@ module SemanticExprConfig { getSemanticExpr(v.asInstruction()) = sourceExpr } - predicate phi(SsaVariable v) { v.asInstruction() instanceof IR::PhiInstruction } + predicate phi(SsaVariable v) { + exists(IR::PhiInstruction phi, Cpp::Function f | + phi = v.asInstruction() and + f = phi.getEnclosingFunction() and + not excludeFunction(f) + ) + } SsaVariable getAPhiInput(SsaVariable v) { exists(IR::PhiInstruction instr | v.asInstruction() = instr | diff --git a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/AllocationToInvalidPointer.qll b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/AllocationToInvalidPointer.qll new file mode 100644 index 00000000000..61821ee5bca --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/AllocationToInvalidPointer.qll @@ -0,0 +1,256 @@ +/** + * This file provides the first phase of the `cpp/invalid-pointer-deref` query that identifies flow + * from an allocation to a pointer-arithmetic instruction that constructs a pointer that is out of bounds. + */ + +private import cpp +private import semmle.code.cpp.ir.dataflow.internal.ProductFlow +private import semmle.code.cpp.ir.ValueNumbering +private import semmle.code.cpp.controlflow.IRGuards +private import codeql.util.Unit +private import RangeAnalysisUtil + +private VariableAccess getAVariableAccess(Expr e) { e.getAChild*() = result } + +/** + * Holds if the `(n, state)` pair represents the source of flow for the size + * expression associated with `alloc`. + */ +predicate hasSize(HeuristicAllocationExpr alloc, DataFlow::Node n, int state) { + exists(VariableAccess va, Expr size, int delta | + size = alloc.getSizeExpr() and + // Get the unique variable in a size expression like `x` in `malloc(x + 1)`. + va = unique( | | getAVariableAccess(size)) and + // Compute `delta` as the constant difference between `x` and `x + 1`. + bounded1(any(Instruction instr | instr.getUnconvertedResultExpression() = size), + any(LoadInstruction load | load.getUnconvertedResultExpression() = va), delta) and + n.asConvertedExpr() = va.getFullyConverted() and + state = delta + ) +} + +/** + * A module that encapsulates a barrier guard to remove false positives from flow like: + * ```cpp + * char *p = new char[size]; + * // ... + * unsigned n = size; + * // ... + * if(n < size) { + * use(*p[n]); + * } + * ``` + * In this case, the sink pair identified by the product flow library (without any additional barriers) + * would be `(p, n)` (where `n` is the `n` in `p[n]`), because there exists a pointer-arithmetic + * instruction `pai` such that: + * 1. The left-hand of `pai` flows from the allocation, and + * 2. The right-hand of `pai` is non-strictly upper bounded by `n` (where `n` is the `n` in `p[n]`) + * but because there's a strict comparison that compares `n` against the size of the allocation this + * snippet is fine. + */ +module Barrier2 { + private class FlowState2 = int; + + private module BarrierConfig2 implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // The sources is the same as in the sources for the second + // projection in the `AllocToInvalidPointerConfig` module. + hasSize(_, source, _) + } + + additional predicate isSink( + DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, FlowState2 state, + boolean testIsTrue + ) { + // The sink is any "large" side of a relational comparison. + g.comparesLt(left.asOperand(), right.asOperand(), state, true, testIsTrue) + } + + predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) } + } + + private import DataFlow::Global + + private FlowState2 getAFlowStateForNode(DataFlow::Node node) { + exists(DataFlow::Node source | + flow(source, node) and + hasSize(_, source, result) + ) + } + + private predicate operandGuardChecks( + IRGuardCondition g, Operand left, Operand right, FlowState2 state, boolean edge + ) { + exists(DataFlow::Node nLeft, DataFlow::Node nRight, FlowState2 state0 | + nRight.asOperand() = right and + nLeft.asOperand() = left and + BarrierConfig2::isSink(nLeft, nRight, g, state0, edge) and + state = getAFlowStateForNode(nRight) and + state0 <= state + ) + } + + /** + * Gets an instruction that is guarded by a guard condition which ensures that + * the value of the instruction is upper-bounded by size of some allocation. + */ + Instruction getABarrierInstruction(FlowState2 state) { + exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge | + use = value.getAUse() and + operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _, + pragma[only_bind_into](state), pragma[only_bind_into](edge)) and + result = value.getAnInstruction() and + g.controls(result.getBlock(), edge) + ) + } + + /** + * Gets a `DataFlow::Node` that is guarded by a guard condition which ensures that + * the value of the node is upper-bounded by size of some allocation. + */ + DataFlow::Node getABarrierNode(FlowState2 state) { + result.asOperand() = getABarrierInstruction(state).getAUse() + } + + /** + * Gets the block of a node that is guarded (see `getABarrierInstruction` or + * `getABarrierNode` for the definition of what it means to be guarded). + */ + IRBlock getABarrierBlock(FlowState2 state) { + result.getAnInstruction() = getABarrierInstruction(state) + } +} + +private module InterestingPointerAddInstruction { + private module PointerAddInstructionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // The sources is the same as in the sources for the second + // projection in the `AllocToInvalidPointerConfig` module. + hasSize(source.asConvertedExpr(), _, _) + } + + predicate isSink(DataFlow::Node sink) { + sink.asInstruction() = any(PointerAddInstruction pai).getLeft() + } + } + + private import DataFlow::Global + + /** + * Holds if `pai` is a pointer-arithmetic instruction such that the + * result of an allocation flows to the left-hand side of `pai`. + * + * This predicate is used to reduce the set of tuples in `isSinkPair`. + */ + predicate isInteresting(PointerAddInstruction pai) { + exists(DataFlow::Node n | + n.asInstruction() = pai.getLeft() and + flowTo(n) + ) + } +} + +/** + * A product-flow configuration for flow from an (allocation, size) pair to a + * pointer-arithmetic operation that is non-strictly upper-bounded by `allocation + size`. + * + * The goal of this query is to find patterns such as: + * ```cpp + * 1. char* begin = (char*)malloc(size); + * 2. char* end = begin + size; + * 3. for(int *p = begin; p <= end; p++) { + * 4. use(*p); + * 5. } + * ``` + * + * We do this by splitting the task up into two configurations: + * 1. `AllocToInvalidPointerConfig` find flow from `malloc(size)` to `begin + size`, and + * 2. `InvalidPointerToDerefConfig` finds flow from `begin + size` to an `end` (on line 3). + * + * Finally, the range-analysis library will find a load from (or store to) an address that + * is non-strictly upper-bounded by `end` (which in this case is `*p`). + */ +private module Config implements ProductFlow::StateConfigSig { + class FlowState1 = Unit; + + class FlowState2 = int; + + predicate isSourcePair( + DataFlow::Node source1, FlowState1 state1, DataFlow::Node source2, FlowState2 state2 + ) { + // In the case of an allocation like + // ```cpp + // malloc(size + 1); + // ``` + // we use `state2` to remember that there was an offset (in this case an offset of `1`) added + // to the size of the allocation. This state is then checked in `isSinkPair`. + exists(state1) and + hasSize(source1.asConvertedExpr(), source2, state2) + } + + predicate isSinkPair( + DataFlow::Node sink1, FlowState1 state1, DataFlow::Node sink2, FlowState2 state2 + ) { + exists(state1) and + // We check that the delta computed by the range analysis matches the + // state value that we set in `isSourcePair`. + pointerAddInstructionHasBounds0(_, sink1, sink2, state2) + } + + predicate isBarrier2(DataFlow::Node node, FlowState2 state) { + node = Barrier2::getABarrierNode(state) + } + + predicate isBarrierIn1(DataFlow::Node node) { isSourcePair(node, _, _, _) } + + predicate isBarrierOut2(DataFlow::Node node) { + node = any(DataFlow::SsaPhiNode phi).getAnInput(true) + } +} + +private module AllocToInvalidPointerFlow = ProductFlow::GlobalWithState; + +/** + * Holds if `pai` is non-strictly upper bounded by `sink2 + delta` and `sink1` is the + * left operand of the pointer-arithmetic operation. + * + * For example in, + * ```cpp + * char* end = p + (size + 1); + * ``` + * We will have: + * - `pai` is `p + (size + 1)`, + * - `sink1` is `p` + * - `sink2` is `size` + * - `delta` is `1`. + */ +pragma[nomagic] +private predicate pointerAddInstructionHasBounds0( + PointerAddInstruction pai, DataFlow::Node sink1, DataFlow::Node sink2, int delta +) { + InterestingPointerAddInstruction::isInteresting(pragma[only_bind_into](pai)) and + exists(Instruction right, Instruction instr2 | + pai.getRight() = right and + pai.getLeft() = sink1.asInstruction() and + instr2 = sink2.asInstruction() and + // pai.getRight() <= sink2 + delta + bounded1(right, instr2, delta) and + not right = Barrier2::getABarrierInstruction(delta) and + not instr2 = Barrier2::getABarrierInstruction(delta) + ) +} + +/** + * Holds if `allocation` flows to `sink1` and `sink1` represents the left-hand + * side of the pointer-arithmetic instruction `pai`, and the right-hand side of `pai` + * is non-strictly upper bounded by the size of `alllocation` + `delta`. + */ +pragma[nomagic] +predicate pointerAddInstructionHasBounds( + DataFlow::Node allocation, PointerAddInstruction pai, DataFlow::Node sink1, int delta +) { + exists(DataFlow::Node sink2 | + AllocToInvalidPointerFlow::flow(allocation, _, sink1, sink2) and + pointerAddInstructionHasBounds0(pai, sink1, sink2, delta) + ) +} diff --git a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/InvalidPointerToDereference.qll b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/InvalidPointerToDereference.qll new file mode 100644 index 00000000000..3eb0a2da670 --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/InvalidPointerToDereference.qll @@ -0,0 +1,196 @@ +/** + * This file provides the second phase of the `cpp/invalid-pointer-deref` query that identifies flow + * from the out-of-bounds pointer identified by the `AllocationToInvalidPointer.qll` library to + * a dereference of the out-of-bounds pointer. + */ + +private import cpp +private import semmle.code.cpp.dataflow.new.DataFlow +private import semmle.code.cpp.ir.ValueNumbering +private import semmle.code.cpp.controlflow.IRGuards +private import AllocationToInvalidPointer as AllocToInvalidPointer +private import RangeAnalysisUtil + +private module InvalidPointerToDerefBarrier { + private module BarrierConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // The sources is the same as in the sources for `InvalidPointerToDerefConfig`. + invalidPointerToDerefSource(_, _, source, _) + } + + additional predicate isSink( + DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, int state, boolean testIsTrue + ) { + // The sink is any "large" side of a relational comparison. + g.comparesLt(left.asOperand(), right.asOperand(), state, true, testIsTrue) + } + + predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) } + } + + private module BarrierFlow = DataFlow::Global; + + private int getInvalidPointerToDerefSourceDelta(DataFlow::Node node) { + exists(DataFlow::Node source | + BarrierFlow::flow(source, node) and + invalidPointerToDerefSource(_, _, source, result) + ) + } + + private predicate operandGuardChecks( + IRGuardCondition g, Operand left, Operand right, int state, boolean edge + ) { + exists(DataFlow::Node nLeft, DataFlow::Node nRight, int state0 | + nRight.asOperand() = right and + nLeft.asOperand() = left and + BarrierConfig::isSink(nLeft, nRight, g, state0, edge) and + state = getInvalidPointerToDerefSourceDelta(nRight) and + state0 <= state + ) + } + + Instruction getABarrierInstruction(int state) { + exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge | + use = value.getAUse() and + operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _, state, + pragma[only_bind_into](edge)) and + result = value.getAnInstruction() and + g.controls(result.getBlock(), edge) + ) + } + + DataFlow::Node getABarrierNode() { result.asOperand() = getABarrierInstruction(_).getAUse() } + + pragma[nomagic] + IRBlock getABarrierBlock(int state) { result.getAnInstruction() = getABarrierInstruction(state) } +} + +/** + * A configuration to track flow from a pointer-arithmetic operation found + * by `AllocToInvalidPointerConfig` to a dereference of the pointer. + */ +private module InvalidPointerToDerefConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { invalidPointerToDerefSource(_, _, source, _) } + + pragma[inline] + predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _, _) } + + predicate isBarrier(DataFlow::Node node) { + node = any(DataFlow::SsaPhiNode phi | not phi.isPhiRead()).getAnInput(true) + or + node = InvalidPointerToDerefBarrier::getABarrierNode() + } +} + +private import DataFlow::Global + +/** + * Holds if `source1` is dataflow node that represents an allocation that flows to the + * left-hand side of the pointer-arithmetic `pai`, and `derefSource` is a dataflow node with + * a pointer-value that is non-strictly upper bounded by `pai + delta`. + * + * For example, if `pai` is a pointer-arithmetic operation `p + size` in an expression such + * as `(p + size) + 1` and `derefSource` is the node representing `(p + size) + 1`. In this + * case `delta` is 1. + */ +private predicate invalidPointerToDerefSource( + DataFlow::Node source1, PointerArithmeticInstruction pai, DataFlow::Node derefSource, int delta +) { + exists(int delta0 | + // Note that `delta` is not necessarily equal to `delta0`: + // `delta0` is the constant offset added to the size of the allocation, and + // delta is the constant difference between the pointer-arithmetic instruction + // and the instruction computing the address for which we will search for a dereference. + AllocToInvalidPointer::pointerAddInstructionHasBounds(source1, pai, _, delta0) and + bounded2(derefSource.asInstruction(), pai, delta) and + delta >= 0 and + // TODO: This condition will go away once #13725 is merged, and then we can make `Barrier2` + // private to `AllocationToInvalidPointer.qll`. + not derefSource.getBasicBlock() = AllocToInvalidPointer::Barrier2::getABarrierBlock(delta0) + ) +} + +/** + * Holds if `sink` is a sink for `InvalidPointerToDerefConfig` and `i` is a `StoreInstruction` that + * writes to an address that non-strictly upper-bounds `sink`, or `i` is a `LoadInstruction` that + * reads from an address that non-strictly upper-bounds `sink`. + */ +pragma[inline] +private predicate isInvalidPointerDerefSink( + DataFlow::Node sink, Instruction i, string operation, int delta +) { + exists(AddressOperand addr, Instruction s, IRBlock b | + s = sink.asInstruction() and + bounded(addr.getDef(), s, delta) and + delta >= 0 and + i.getAnOperand() = addr and + b = i.getBlock() and + not b = InvalidPointerToDerefBarrier::getABarrierBlock(delta) + | + i instanceof StoreInstruction and + operation = "write" + or + i instanceof LoadInstruction and + operation = "read" + ) +} + +/** + * Yields any instruction that is control-flow reachable from `instr`. + */ +bindingset[instr, result] +pragma[inline_late] +private Instruction getASuccessor(Instruction instr) { + exists(IRBlock b, int instrIndex, int resultIndex | + b.getInstruction(instrIndex) = instr and + b.getInstruction(resultIndex) = result + | + resultIndex >= instrIndex + ) + or + instr.getBlock().getASuccessor+() = result.getBlock() +} + +private predicate paiForDereferenceSink(PointerArithmeticInstruction pai, DataFlow::Node derefSink) { + exists(DataFlow::Node derefSource | + invalidPointerToDerefSource(_, pai, derefSource, _) and + flow(derefSource, derefSink) + ) +} + +/** + * Holds if `derefSink` is a dataflow node that represents an out-of-bounds address that is about to + * be dereferenced by `operation` (which is either a `StoreInstruction` or `LoadInstruction`), and + * `pai` is the pointer-arithmetic operation that caused the `derefSink` to be out-of-bounds. + */ +private predicate derefSinkToOperation( + DataFlow::Node derefSink, PointerArithmeticInstruction pai, DataFlow::Node operation, + string description, int delta +) { + exists(Instruction i | + paiForDereferenceSink(pai, pragma[only_bind_into](derefSink)) and + isInvalidPointerDerefSink(derefSink, i, description, delta) and + i = getASuccessor(derefSink.asInstruction()) and + operation.asInstruction() = i + ) +} + +/** + * Holds if `allocation` is the result of an allocation that flows to the left-hand side of `pai`, and where + * the right-hand side of `pai` is an offset such that the result of `pai` points to an out-of-bounds pointer. + * + * Furthermore, `derefSource` is at least as large as `pai` and flows to `derefSink` before being dereferenced + * by `operation` (which is either a `StoreInstruction` or `LoadInstruction`). The result is that `operation` + * dereferences a pointer that's "off by `delta`" number of elements. + */ +predicate operationIsOffBy( + DataFlow::Node allocation, PointerArithmeticInstruction pai, DataFlow::Node derefSource, + DataFlow::Node derefSink, string description, DataFlow::Node operation, int delta +) { + exists(int deltaDerefSourceAndPai, int deltaDerefSinkAndDerefAddress | + invalidPointerToDerefSource(allocation, pai, derefSource, deltaDerefSourceAndPai) and + flow(derefSource, derefSink) and + derefSinkToOperation(derefSink, pai, operation, description, deltaDerefSinkAndDerefAddress) and + delta = deltaDerefSourceAndPai + deltaDerefSinkAndDerefAddress + ) +} diff --git a/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/RangeAnalysisUtil.qll b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/RangeAnalysisUtil.qll new file mode 100644 index 00000000000..12bb50321fa --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/security/InvalidPointerDereference/RangeAnalysisUtil.qll @@ -0,0 +1,48 @@ +/** + * This file contains the range-analysis specific parts of the `cpp/invalid-pointer-deref` query + * that is used by both `AllocationToInvalidPointer.qll` and `InvalidPointerToDereference.qll`. + */ + +private import cpp +private import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.RangeAnalysis +private import semmle.code.cpp.rangeanalysis.new.internal.semantic.SemanticExprSpecific +private import semmle.code.cpp.ir.IR + +pragma[nomagic] +private Instruction getABoundIn(SemBound b, IRFunction func) { + getSemanticExpr(result) = b.getExpr(0) and + result.getEnclosingIRFunction() = func +} + +/** + * Holds if `i <= b + delta`. + */ +pragma[inline] +private predicate boundedImpl(Instruction i, Instruction b, int delta) { + exists(SemBound bound, IRFunction func | + semBounded(getSemanticExpr(i), bound, delta, true, _) and + b = getABoundIn(bound, func) and + i.getEnclosingIRFunction() = func + ) +} + +/** + * Holds if `i <= b + delta`. + * + * This predicate enforces a join-order that ensures that `i` has already been bound. + */ +bindingset[i] +pragma[inline_late] +predicate bounded1(Instruction i, Instruction b, int delta) { boundedImpl(i, b, delta) } + +/** + * Holds if `i <= b + delta`. + * + * This predicate enforces a join-order that ensures that `b` has already been bound. + */ +bindingset[b] +pragma[inline_late] +predicate bounded2(Instruction i, Instruction b, int delta) { boundedImpl(i, b, delta) } + +/** Holds if `i <= b + delta`. */ +predicate bounded = boundedImpl/3; diff --git a/cpp/ql/src/CHANGELOG.md b/cpp/ql/src/CHANGELOG.md index 5b7104d2331..3527a0fc497 100644 --- a/cpp/ql/src/CHANGELOG.md +++ b/cpp/ql/src/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.7.1 + +### Minor Analysis Improvements + +* The `cpp/uninitialized-local` query now excludes uninitialized uses that are explicitly cast to void and are expression statements. As a result, the query will report less false positives. + ## 0.7.0 ### Minor Analysis Improvements diff --git a/cpp/ql/src/Critical/FlowAfterFree.qll b/cpp/ql/src/Critical/FlowAfterFree.qll index 0e04b294d70..7705e8841d4 100644 --- a/cpp/ql/src/Critical/FlowAfterFree.qll +++ b/cpp/ql/src/Critical/FlowAfterFree.qll @@ -88,14 +88,6 @@ module FlowFromFree { e = any(StoreInstruction store).getDestinationAddress().getUnconvertedResultExpression() ) } - - predicate isBarrier(DataFlow::Node n, FlowState state) { none() } - - predicate isAdditionalFlowStep( - DataFlow::Node n1, FlowState state1, DataFlow::Node n2, FlowState state2 - ) { - none() - } } import DataFlow::GlobalWithState diff --git a/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql b/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql index 79a5d530c21..7dda356353e 100644 --- a/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql +++ b/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql @@ -44,14 +44,6 @@ module CastToPointerArithFlowConfig implements DataFlow::StateConfigSig { ) and getFullyConvertedType(node) = state } - - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - - predicate isAdditionalFlowStep( - DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 - ) { - none() - } } /** diff --git a/cpp/ql/src/Likely Bugs/Memory Management/UninitializedLocal.ql b/cpp/ql/src/Likely Bugs/Memory Management/UninitializedLocal.ql index baa98bdfb2f..0fbd1707db0 100644 --- a/cpp/ql/src/Likely Bugs/Memory Management/UninitializedLocal.ql +++ b/cpp/ql/src/Likely Bugs/Memory Management/UninitializedLocal.ql @@ -72,6 +72,11 @@ VariableAccess commonException() { or result.getParent() instanceof BuiltInOperation or + // Ignore any uninitialized use that is explicitly cast to void and + // is an expression statement. + result.getActualType() instanceof VoidType and + result.getParent() instanceof ExprStmt + or // Finally, exclude functions that contain assembly blocks. It's // anyone's guess what happens in those. containsInlineAssembly(result.getEnclosingFunction()) diff --git a/cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql b/cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql index cc4b748e5dc..0686c4a707c 100644 --- a/cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql +++ b/cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql @@ -134,8 +134,6 @@ module ExecTaintConfig implements DataFlow::StateConfigSig { predicate isBarrier(DataFlow::Node node) { isBarrierImpl(node) } - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - predicate isBarrierOut(DataFlow::Node node) { isSink(node, _) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers } diff --git a/cpp/ql/src/Security/CWE/CWE-119/OverrunWriteProductFlow.ql b/cpp/ql/src/Security/CWE/CWE-119/OverrunWriteProductFlow.ql index baba3a033db..510b7e8b6c4 100644 --- a/cpp/ql/src/Security/CWE/CWE-119/OverrunWriteProductFlow.ql +++ b/cpp/ql/src/Security/CWE/CWE-119/OverrunWriteProductFlow.ql @@ -118,8 +118,6 @@ module ValidState { state = [false, true] } - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - predicate isAdditionalFlowStep( DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 ) { diff --git a/cpp/ql/src/Security/CWE/CWE-611/XXE.ql b/cpp/ql/src/Security/CWE/CWE-611/XXE.ql index 6b73e50c963..50cd63acb40 100644 --- a/cpp/ql/src/Security/CWE/CWE-611/XXE.ql +++ b/cpp/ql/src/Security/CWE/CWE-611/XXE.ql @@ -43,6 +43,8 @@ module XxeConfig implements DataFlow::StateConfigSig { // flowstate value. node.asIndirectExpr().(XxeFlowStateTransformer).transform(flowstate) != flowstate } + + predicate neverSkip(DataFlow::Node node) { none() } } module XxeFlow = DataFlow::GlobalWithState; diff --git a/cpp/ql/src/change-notes/released/0.7.1.md b/cpp/ql/src/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..ac119d48baa --- /dev/null +++ b/cpp/ql/src/change-notes/released/0.7.1.md @@ -0,0 +1,5 @@ +## 0.7.1 + +### Minor Analysis Improvements + +* The `cpp/uninitialized-local` query now excludes uninitialized uses that are explicitly cast to void and are expression statements. As a result, the query will report less false positives. diff --git a/cpp/ql/src/codeql-pack.release.yml b/cpp/ql/src/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/cpp/ql/src/codeql-pack.release.yml +++ b/cpp/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-193/ConstantSizeArrayOffByOne.ql b/cpp/ql/src/experimental/Security/CWE/CWE-193/ConstantSizeArrayOffByOne.ql index 42623d37328..c38a012b27b 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-193/ConstantSizeArrayOffByOne.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-193/ConstantSizeArrayOffByOne.ql @@ -168,8 +168,6 @@ module ArrayAddressToDerefConfig implements DataFlow::StateConfigSig { ) } - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - predicate isBarrierIn(DataFlow::Node node) { isSource(node, _) } predicate isBarrierOut(DataFlow::Node node) { isSink(node, _) } diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql b/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql index dbbbc5e787f..93cac5939d4 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql @@ -16,561 +16,92 @@ */ import cpp -import semmle.code.cpp.ir.dataflow.internal.ProductFlow -import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.RangeAnalysis -import semmle.code.cpp.rangeanalysis.new.internal.semantic.SemanticExprSpecific -import semmle.code.cpp.ir.ValueNumbering -import semmle.code.cpp.controlflow.IRGuards +import semmle.code.cpp.dataflow.new.DataFlow import semmle.code.cpp.ir.IR -import codeql.util.Unit - -pragma[nomagic] -Instruction getABoundIn(SemBound b, IRFunction func) { - getSemanticExpr(result) = b.getExpr(0) and - result.getEnclosingIRFunction() = func -} +import FinalFlow::PathGraph +import semmle.code.cpp.security.InvalidPointerDereference.AllocationToInvalidPointer +import semmle.code.cpp.security.InvalidPointerDereference.InvalidPointerToDereference /** - * Holds if `i <= b + delta`. + * A configuration that represents the full dataflow path all the way from + * the allocation to the dereference. We need this final dataflow traversal + * to ensure that the transition from the sink in `AllocToInvalidPointerConfig` + * to the source in `InvalidPointerToDerefFlow` did not make us construct an + * infeasible path (which can happen since the transition from one configuration + * to the next does not preserve information about call contexts). */ -pragma[inline] -predicate boundedImpl(Instruction i, Instruction b, int delta) { - exists(SemBound bound, IRFunction func | - semBounded(getSemanticExpr(i), bound, delta, true, _) and - b = getABoundIn(bound, func) and - i.getEnclosingIRFunction() = func - ) -} - -bindingset[i] -pragma[inline_late] -predicate bounded1(Instruction i, Instruction b, int delta) { boundedImpl(i, b, delta) } - -bindingset[b] -pragma[inline_late] -predicate bounded2(Instruction i, Instruction b, int delta) { boundedImpl(i, b, delta) } - -VariableAccess getAVariableAccess(Expr e) { e.getAChild*() = result } - -/** - * Holds if `(n, state)` pair represents the source of flow for the size - * expression associated with `alloc`. - */ -predicate hasSize(HeuristicAllocationExpr alloc, DataFlow::Node n, int state) { - exists(VariableAccess va, Expr size, int delta | - size = alloc.getSizeExpr() and - // Get the unique variable in a size expression like `x` in `malloc(x + 1)`. - va = unique( | | getAVariableAccess(size)) and - // Compute `delta` as the constant difference between `x` and `x + 1`. - bounded1(any(Instruction instr | instr.getUnconvertedResultExpression() = size), - any(LoadInstruction load | load.getUnconvertedResultExpression() = va), delta) and - n.asConvertedExpr() = va.getFullyConverted() and - state = delta - ) -} - -/** - * A module that encapsulates a barrier guard to remove false positives from flow like: - * ```cpp - * char *p = new char[size]; - * // ... - * unsigned n = size; - * // ... - * if(n < size) { - * use(*p[n]); - * } - * ``` - * In this case, the sink pair identified by the product flow library (without any additional barriers) - * would be `(p, n)` (where `n` is the `n` in `p[n]`), because there exists a pointer-arithmetic - * instruction `pai` such that: - * 1. The left-hand of `pai` flows from the allocation, and - * 2. The right-hand of `pai` is non-strictly upper bounded by `n` (where `n` is the `n` in `p[n]`) - * but because there's a strict comparison that compares `n` against the size of the allocation this - * snippet is fine. - */ -module Barrier2 { - private class FlowState2 = AllocToInvalidPointerConfig::FlowState2; - - private module BarrierConfig2 implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - // The sources is the same as in the sources for the second - // projection in the `AllocToInvalidPointerConfig` module. - hasSize(_, source, _) +module FinalConfig implements DataFlow::StateConfigSig { + newtype FlowState = + additional TInitial() or + additional TPointerArith(PointerArithmeticInstruction pai) { + operationIsOffBy(_, pai, _, _, _, _, _) } - additional predicate isSink( - DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, FlowState2 state, - boolean testIsTrue - ) { - // The sink is any "large" side of a relational comparison. - g.comparesLt(left.asOperand(), right.asOperand(), state, true, testIsTrue) - } - - predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) } + predicate isSource(DataFlow::Node source, FlowState state) { + state = TInitial() and + operationIsOffBy(source, _, _, _, _, _, _) } - private import DataFlow::Global - - private FlowState2 getAFlowStateForNode(DataFlow::Node node) { - exists(DataFlow::Node source | - flow(source, node) and - hasSize(_, source, result) + predicate isSink(DataFlow::Node sink, FlowState state) { + exists(PointerArithmeticInstruction pai | + operationIsOffBy(_, pai, _, _, _, sink, _) and + state = TPointerArith(pai) ) } - private predicate operandGuardChecks( - IRGuardCondition g, Operand left, Operand right, FlowState2 state, boolean edge + predicate isAdditionalFlowStep( + DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 ) { - exists(DataFlow::Node nLeft, DataFlow::Node nRight, FlowState2 state0 | - nRight.asOperand() = right and - nLeft.asOperand() = left and - BarrierConfig2::isSink(nLeft, nRight, g, state0, edge) and - state = getAFlowStateForNode(nRight) and - state0 <= state + // A step from the left-hand side of a pointer-arithmetic operation that has been + // identified as creating an out-of-bounds pointer to the result of the pointer-arithmetic + // operation. + exists(PointerArithmeticInstruction pai | + pointerAddInstructionHasBounds(_, pai, node1, _) and + operationIsOffBy(_, pai, node2, _, _, _, _) and + state1 = TInitial() and + state2 = TPointerArith(pai) ) - } - - Instruction getABarrierInstruction(FlowState2 state) { - exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge | - use = value.getAUse() and - operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _, - pragma[only_bind_into](state), pragma[only_bind_into](edge)) and - result = value.getAnInstruction() and - g.controls(result.getBlock(), edge) - ) - } - - DataFlow::Node getABarrierNode(FlowState2 state) { - result.asOperand() = getABarrierInstruction(state).getAUse() - } - - IRBlock getABarrierBlock(FlowState2 state) { - result.getAnInstruction() = getABarrierInstruction(state) - } -} - -/** - * A product-flow configuration for flow from an (allocation, size) pair to a - * pointer-arithmetic operation that is non-strictly upper-bounded by `allocation + size`. - * - * The goal of this query is to find patterns such as: - * ```cpp - * 1. char* begin = (char*)malloc(size); - * 2. char* end = begin + size; - * 3. for(int *p = begin; p <= end; p++) { - * 4. use(*p); - * 5. } - * ``` - * - * We do this by splitting the task up into two configurations: - * 1. `AllocToInvalidPointerConfig` find flow from `malloc(size)` to `begin + size`, and - * 2. `InvalidPointerToDerefConfig` finds flow from `begin + size` to an `end` (on line 3). - * - * Finally, the range-analysis library will find a load from (or store to) an address that - * is non-strictly upper-bounded by `end` (which in this case is `*p`). - */ -module AllocToInvalidPointerConfig implements ProductFlow::StateConfigSig { - class FlowState1 = Unit; - - class FlowState2 = int; - - predicate isSourcePair( - DataFlow::Node source1, FlowState1 state1, DataFlow::Node source2, FlowState2 state2 - ) { - // In the case of an allocation like - // ```cpp - // malloc(size + 1); - // ``` - // we use `state2` to remember that there was an offset (in this case an offset of `1`) added - // to the size of the allocation. This state is then checked in `isSinkPair`. - exists(state1) and - hasSize(source1.asConvertedExpr(), source2, state2) - } - - predicate isSinkPair( - DataFlow::Node sink1, FlowState1 state1, DataFlow::Node sink2, FlowState2 state2 - ) { - exists(state1) and - // We check that the delta computed by the range analysis matches the - // state value that we set in `isSourcePair`. - isSinkImpl(_, sink1, sink2, state2) - } - - predicate isBarrier2(DataFlow::Node node, FlowState2 state) { - node = Barrier2::getABarrierNode(state) - } - - predicate isBarrierIn1(DataFlow::Node node) { isSourcePair(node, _, _, _) } - - predicate isBarrierOut2(DataFlow::Node node) { - node = any(DataFlow::SsaPhiNode phi).getAnInput(true) - } -} - -module AllocToInvalidPointerFlow = ProductFlow::GlobalWithState; - -/** - * Holds if `pai` is non-strictly upper bounded by `sink2 + delta` and `sink1` is the - * left operand of the pointer-arithmetic operation. - * - * For example in, - * ```cpp - * char* end = p + (size + 1); - * ``` - * We will have: - * - `pai` is `p + (size + 1)`, - * - `sink1` is `p` - * - `sink2` is `size` - * - `delta` is `1`. - */ -pragma[nomagic] -predicate pointerAddInstructionHasBounds( - PointerAddInstruction pai, DataFlow::Node sink1, DataFlow::Node sink2, int delta -) { - InterestingPointerAddInstruction::isInteresting(pragma[only_bind_into](pai)) and - exists(Instruction right, Instruction instr2 | - pai.getRight() = right and - pai.getLeft() = sink1.asInstruction() and - instr2 = sink2.asInstruction() and - bounded1(right, instr2, delta) and - not right = Barrier2::getABarrierInstruction(delta) and - not instr2 = Barrier2::getABarrierInstruction(delta) - ) -} - -module InterestingPointerAddInstruction { - private module PointerAddInstructionConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - // The sources is the same as in the sources for the second - // projection in the `AllocToInvalidPointerConfig` module. - hasSize(source.asConvertedExpr(), _, _) - } - - predicate isSink(DataFlow::Node sink) { - sink.asInstruction() = any(PointerAddInstruction pai).getLeft() - } - } - - private import DataFlow::Global - - predicate isInteresting(PointerAddInstruction pai) { - exists(DataFlow::Node n | - n.asInstruction() = pai.getLeft() and - flowTo(n) - ) - } -} - -/** - * Holds if `pai` is non-strictly upper bounded by `sink2 + delta` and `sink1` is the - * left operand of the pointer-arithmetic operation. - * - * See `pointerAddInstructionHasBounds` for an example. - */ -predicate isSinkImpl( - PointerAddInstruction pai, DataFlow::Node sink1, DataFlow::Node sink2, int delta -) { - pointerAddInstructionHasBounds(pai, sink1, sink2, delta) -} - -/** - * Yields any instruction that is control-flow reachable from `instr`. - */ -bindingset[instr, result] -pragma[inline_late] -Instruction getASuccessor(Instruction instr) { - exists(IRBlock b, int instrIndex, int resultIndex | - result.getBlock() = b and - instr.getBlock() = b and - b.getInstruction(instrIndex) = instr and - b.getInstruction(resultIndex) = result - | - resultIndex >= instrIndex - ) - or - instr.getBlock().getASuccessor+() = result.getBlock() -} - -/** - * Holds if `sink` is a sink for `InvalidPointerToDerefConfig` and `i` is a `StoreInstruction` that - * writes to an address that non-strictly upper-bounds `sink`, or `i` is a `LoadInstruction` that - * reads from an address that non-strictly upper-bounds `sink`. - */ -pragma[inline] -predicate isInvalidPointerDerefSink(DataFlow::Node sink, Instruction i, string operation, int delta) { - exists(AddressOperand addr, Instruction s, IRBlock b | - s = sink.asInstruction() and - boundedImpl(addr.getDef(), s, delta) and - delta >= 0 and - i.getAnOperand() = addr and - b = i.getBlock() and - not b = InvalidPointerToDerefBarrier::getABarrierBlock(delta) - | - i instanceof StoreInstruction and - operation = "write" or - i instanceof LoadInstruction and - operation = "read" - ) + // A step from an out-of-bounds address to the operation (which is either a `StoreInstruction` + // or a `LoadInstruction`) that dereferences the address. + // This step exists purely for aesthetic reasons: we want the alert to be placed at the operation + // that causes the dereference, and not at the address that flows into the operation. + state1 = state2 and + exists(PointerArithmeticInstruction pai | + state1 = TPointerArith(pai) and + operationIsOffBy(_, pai, _, node1, _, node2, _) + ) + } } -module InvalidPointerToDerefBarrier { - private module BarrierConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - // The sources is the same as in the sources for `InvalidPointerToDerefConfig`. - invalidPointerToDerefSource(_, source, _) - } - - additional predicate isSink( - DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, int state, boolean testIsTrue - ) { - // The sink is any "large" side of a relational comparison. - g.comparesLt(left.asOperand(), right.asOperand(), state, true, testIsTrue) - } - - predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) } - } - - private import DataFlow::Global - - private int getInvalidPointerToDerefSourceDelta(DataFlow::Node node) { - exists(DataFlow::Node source | - flow(source, node) and - invalidPointerToDerefSource(_, source, result) - ) - } - - private predicate operandGuardChecks( - IRGuardCondition g, Operand left, Operand right, int state, boolean edge - ) { - exists(DataFlow::Node nLeft, DataFlow::Node nRight, int state0 | - nRight.asOperand() = right and - nLeft.asOperand() = left and - BarrierConfig::isSink(nLeft, nRight, g, state0, edge) and - state = getInvalidPointerToDerefSourceDelta(nRight) and - state0 <= state - ) - } - - Instruction getABarrierInstruction(int state) { - exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge | - use = value.getAUse() and - operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _, state, - pragma[only_bind_into](edge)) and - result = value.getAnInstruction() and - g.controls(result.getBlock(), edge) - ) - } - - DataFlow::Node getABarrierNode() { result.asOperand() = getABarrierInstruction(_).getAUse() } - - pragma[nomagic] - IRBlock getABarrierBlock(int state) { result.getAnInstruction() = getABarrierInstruction(state) } -} +module FinalFlow = DataFlow::GlobalWithState; /** - * A configuration to track flow from a pointer-arithmetic operation found - * by `AllocToInvalidPointerConfig` to a dereference of the pointer. - */ -module InvalidPointerToDerefConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { invalidPointerToDerefSource(_, source, _) } - - pragma[inline] - predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _, _) } - - predicate isBarrier(DataFlow::Node node) { - node = any(DataFlow::SsaPhiNode phi | not phi.isPhiRead()).getAnInput(true) - or - node = InvalidPointerToDerefBarrier::getABarrierNode() - } -} - -module InvalidPointerToDerefFlow = DataFlow::Global; - -/** - * Holds if `pai` is a pointer-arithmetic operation and `source` is a dataflow node with a - * pointer-value that is non-strictly upper bounded by `pai + delta`. + * Holds if `source` is an allocation that flows into the left-hand side of `pai`, which produces an out-of-bounds + * pointer that flows into an address that is dereferenced by `sink` (which is either a `LoadInstruction` or a + * `StoreInstruction`). The end result is that `sink` writes to an address that is off-by-`delta` from the end of + * the allocation. The string `operation` describes whether the `sink` is a load or a store (which is then used + * to produce the alert message). * - * For example, if `pai` is a pointer-arithmetic operation `p + size` in an expression such - * as `(p + size) + 1` and `source` is the node representing `(p + size) + 1`. In this - * case `delta` is 1. + * Note that multiple `delta`s can exist for a given `(source, pai, sink)` triplet. */ -predicate invalidPointerToDerefSource( - PointerArithmeticInstruction pai, DataFlow::Node source, int delta -) { - exists( - AllocToInvalidPointerFlow::PathNode1 p1, AllocToInvalidPointerFlow::PathNode2 p2, - DataFlow::Node sink1, DataFlow::Node sink2, int delta0 - | - pragma[only_bind_out](p1.getNode()) = sink1 and - pragma[only_bind_out](p2.getNode()) = sink2 and - AllocToInvalidPointerFlow::flowPath(_, _, pragma[only_bind_into](p1), pragma[only_bind_into](p2)) and - // Note that `delta` is not necessarily equal to `delta0`: - // `delta0` is the constant offset added to the size of the allocation, and - // delta is the constant difference between the pointer-arithmetic instruction - // and the instruction computing the address for which we will search for a dereference. - isSinkImpl(pai, sink1, sink2, delta0) and - bounded2(source.asInstruction(), pai, delta) and - delta >= 0 and - not source.getBasicBlock() = Barrier2::getABarrierBlock(delta0) - ) -} - -newtype TMergedPathNode = - // The path nodes computed by the first projection of `AllocToInvalidPointerConfig` - TPathNode1(AllocToInvalidPointerFlow::PathNode1 p) or - // The path nodes computed by `InvalidPointerToDerefConfig` - TPathNode3(InvalidPointerToDerefFlow::PathNode p) or - // The read/write that uses the invalid pointer identified by `InvalidPointerToDerefConfig`. - // This one is needed because the sink identified by `InvalidPointerToDerefConfig` is the - // pointer, but we want to raise an alert at the dereference. - TPathNodeSink(Instruction i) { - exists(DataFlow::Node n | - InvalidPointerToDerefFlow::flowTo(pragma[only_bind_into](n)) and - isInvalidPointerDerefSink(n, i, _, _) and - i = getASuccessor(n.asInstruction()) - ) - } - -class MergedPathNode extends TMergedPathNode { - string toString() { none() } - - final AllocToInvalidPointerFlow::PathNode1 asPathNode1() { this = TPathNode1(result) } - - final InvalidPointerToDerefFlow::PathNode asPathNode3() { this = TPathNode3(result) } - - final Instruction asSinkNode() { this = TPathNodeSink(result) } - - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - none() - } -} - -class PathNode1 extends MergedPathNode, TPathNode1 { - override string toString() { - exists(AllocToInvalidPointerFlow::PathNode1 p | - this = TPathNode1(p) and - result = p.toString() - ) - } - - override predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.asPathNode1().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -class PathNode3 extends MergedPathNode, TPathNode3 { - override string toString() { - exists(InvalidPointerToDerefFlow::PathNode p | - this = TPathNode3(p) and - result = p.toString() - ) - } - - override predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.asPathNode3().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -class PathSinkNode extends MergedPathNode, TPathNodeSink { - override string toString() { - exists(Instruction i | - this = TPathNodeSink(i) and - result = i.toString() - ) - } - - override predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.asSinkNode() - .getLocation() - .hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -query predicate edges(MergedPathNode node1, MergedPathNode node2) { - node1.asPathNode1().getASuccessor() = node2.asPathNode1() - or - joinOn1(_, node1.asPathNode1(), node2.asPathNode3()) - or - node1.asPathNode3().getASuccessor() = node2.asPathNode3() - or - joinOn2(node1.asPathNode3(), node2.asSinkNode(), _, _) -} - -query predicate nodes(MergedPathNode n, string key, string val) { - AllocToInvalidPointerFlow::PathGraph1::nodes(n.asPathNode1(), key, val) - or - InvalidPointerToDerefFlow::PathGraph::nodes(n.asPathNode3(), key, val) - or - key = "semmle.label" and val = n.asSinkNode().toString() -} - -query predicate subpaths( - MergedPathNode arg, MergedPathNode par, MergedPathNode ret, MergedPathNode out -) { - AllocToInvalidPointerFlow::PathGraph1::subpaths(arg.asPathNode1(), par.asPathNode1(), - ret.asPathNode1(), out.asPathNode1()) - or - InvalidPointerToDerefFlow::PathGraph::subpaths(arg.asPathNode3(), par.asPathNode3(), - ret.asPathNode3(), out.asPathNode3()) -} - -/** - * Holds if `p1` is a sink of `AllocToInvalidPointerConfig` and `p2` is a source - * of `InvalidPointerToDerefConfig`, and they are connected through `pai`. - */ -predicate joinOn1( - PointerArithmeticInstruction pai, AllocToInvalidPointerFlow::PathNode1 p1, - InvalidPointerToDerefFlow::PathNode p2 -) { - isSinkImpl(pai, p1.getNode(), _, _) and - invalidPointerToDerefSource(pai, p2.getNode(), _) -} - -/** - * Holds if `p1` is a sink of `InvalidPointerToDerefConfig` and `i` is the instruction - * that dereferences `p1`. The string `operation` describes whether the `i` is - * a `StoreInstruction` or `LoadInstruction`. - */ -pragma[inline] -predicate joinOn2(InvalidPointerToDerefFlow::PathNode p1, Instruction i, string operation, int delta) { - isInvalidPointerDerefSink(p1.getNode(), i, operation, delta) -} - predicate hasFlowPath( - MergedPathNode source1, MergedPathNode sink, InvalidPointerToDerefFlow::PathNode source3, - PointerArithmeticInstruction pai, string operation, int delta + FinalFlow::PathNode source, FinalFlow::PathNode sink, PointerArithmeticInstruction pai, + string operation, int delta ) { - exists(InvalidPointerToDerefFlow::PathNode sink3, AllocToInvalidPointerFlow::PathNode1 sink1 | - AllocToInvalidPointerFlow::flowPath(source1.asPathNode1(), _, sink1, _) and - joinOn1(pai, sink1, source3) and - InvalidPointerToDerefFlow::flowPath(source3, sink3) and - joinOn2(sink3, sink.asSinkNode(), operation, delta) - ) + FinalFlow::flowPath(source, sink) and + operationIsOffBy(source.getNode(), pai, _, _, operation, sink.getNode(), delta) and + sink.getState() = FinalConfig::TPointerArith(pai) } from - MergedPathNode source, MergedPathNode sink, int k, string kstr, PointerArithmeticInstruction pai, - string operation, Expr offset, DataFlow::Node n + FinalFlow::PathNode source, FinalFlow::PathNode sink, int k, string kstr, + PointerArithmeticInstruction pai, string operation, Expr offset, DataFlow::Node n where - k = - min(int k2, int k3, InvalidPointerToDerefFlow::PathNode source3 | - hasFlowPath(source, sink, source3, pai, operation, k3) and - invalidPointerToDerefSource(pai, source3.getNode(), k2) - | - k2 + k3 - ) and + k = min(int cand | hasFlowPath(source, sink, pai, operation, cand)) and offset = pai.getRight().getUnconvertedResultExpression() and - n = source.asPathNode1().getNode() and + n = source.getNode() and if k = 0 then kstr = "" else kstr = " + " + k -select sink, source, sink, +select sink.getNode(), source, sink, "This " + operation + " might be out of bounds, as the pointer might be equal to $@ + $@" + kstr + ".", n, n.toString(), offset, offset.toString() diff --git a/cpp/ql/src/qlpack.yml b/cpp/ql/src/qlpack.yml index b400a52bb10..c2818af35c3 100644 --- a/cpp/ql/src/qlpack.yml +++ b/cpp/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/cpp-queries -version: 0.7.0 +version: 0.7.1 groups: - cpp - queries diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected index 9c2cc36448e..f8b8bab0e4f 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected @@ -55,6 +55,26 @@ edges | test.cpp:286:19:286:25 | buffer2 | test.cpp:286:19:286:25 | buffer2 | | test.cpp:289:19:289:25 | buffer3 | test.cpp:277:35:277:35 | p | | test.cpp:289:19:289:25 | buffer3 | test.cpp:289:19:289:25 | buffer3 | +| test.cpp:292:25:292:27 | arr | test.cpp:299:16:299:21 | access to array | +| test.cpp:292:25:292:27 | arr | test.cpp:299:16:299:21 | access to array | +| test.cpp:306:20:306:23 | arr1 | test.cpp:292:25:292:27 | arr | +| test.cpp:306:20:306:23 | arr1 | test.cpp:306:20:306:23 | arr1 | +| test.cpp:309:20:309:23 | arr2 | test.cpp:292:25:292:27 | arr | +| test.cpp:309:20:309:23 | arr2 | test.cpp:309:20:309:23 | arr2 | +| test.cpp:319:19:319:22 | temp | test.cpp:319:19:319:27 | ... + ... | +| test.cpp:319:19:319:22 | temp | test.cpp:324:23:324:32 | ... + ... | +| test.cpp:319:19:319:27 | ... + ... | test.cpp:325:24:325:26 | end | +| test.cpp:322:19:322:22 | temp | test.cpp:322:19:322:27 | ... + ... | +| test.cpp:322:19:322:22 | temp | test.cpp:324:23:324:32 | ... + ... | +| test.cpp:322:19:322:27 | ... + ... | test.cpp:325:24:325:26 | end | +| test.cpp:324:23:324:26 | temp | test.cpp:324:23:324:32 | ... + ... | +| test.cpp:324:23:324:32 | ... + ... | test.cpp:325:15:325:19 | temp2 | +| test.cpp:351:9:351:11 | arr | test.cpp:351:9:351:14 | access to array | +| test.cpp:351:9:351:11 | arr | test.cpp:351:18:351:25 | access to array | +| test.cpp:351:18:351:20 | arr | test.cpp:351:9:351:14 | access to array | +| test.cpp:351:18:351:20 | arr | test.cpp:351:18:351:25 | access to array | +| test.cpp:351:29:351:31 | arr | test.cpp:351:9:351:14 | access to array | +| test.cpp:351:29:351:31 | arr | test.cpp:351:18:351:25 | access to array | nodes | test.cpp:34:5:34:24 | access to array | semmle.label | access to array | | test.cpp:34:10:34:12 | buf | semmle.label | buf | @@ -131,6 +151,27 @@ nodes | test.cpp:286:19:286:25 | buffer2 | semmle.label | buffer2 | | test.cpp:289:19:289:25 | buffer3 | semmle.label | buffer3 | | test.cpp:289:19:289:25 | buffer3 | semmle.label | buffer3 | +| test.cpp:292:25:292:27 | arr | semmle.label | arr | +| test.cpp:292:25:292:27 | arr | semmle.label | arr | +| test.cpp:299:16:299:21 | access to array | semmle.label | access to array | +| test.cpp:306:20:306:23 | arr1 | semmle.label | arr1 | +| test.cpp:306:20:306:23 | arr1 | semmle.label | arr1 | +| test.cpp:309:20:309:23 | arr2 | semmle.label | arr2 | +| test.cpp:309:20:309:23 | arr2 | semmle.label | arr2 | +| test.cpp:319:19:319:22 | temp | semmle.label | temp | +| test.cpp:319:19:319:27 | ... + ... | semmle.label | ... + ... | +| test.cpp:322:19:322:22 | temp | semmle.label | temp | +| test.cpp:322:19:322:27 | ... + ... | semmle.label | ... + ... | +| test.cpp:324:23:324:26 | temp | semmle.label | temp | +| test.cpp:324:23:324:32 | ... + ... | semmle.label | ... + ... | +| test.cpp:325:15:325:19 | temp2 | semmle.label | temp2 | +| test.cpp:325:24:325:26 | end | semmle.label | end | +| test.cpp:325:24:325:26 | end | semmle.label | end | +| test.cpp:351:9:351:11 | arr | semmle.label | arr | +| test.cpp:351:9:351:14 | access to array | semmle.label | access to array | +| test.cpp:351:18:351:20 | arr | semmle.label | arr | +| test.cpp:351:18:351:25 | access to array | semmle.label | access to array | +| test.cpp:351:29:351:31 | arr | semmle.label | arr | subpaths #select | test.cpp:35:5:35:22 | PointerAdd: access to array | test.cpp:35:10:35:12 | buf | test.cpp:35:5:35:22 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:35:5:35:26 | Store: ... = ... | write | @@ -149,3 +190,10 @@ subpaths | test.cpp:221:5:221:11 | PointerAdd: access to array | test.cpp:218:23:218:28 | buffer | test.cpp:221:5:221:11 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:217:19:217:24 | buffer | buffer | test.cpp:221:5:221:15 | Store: ... = ... | write | | test.cpp:232:5:232:10 | PointerAdd: access to array | test.cpp:229:25:229:29 | array | test.cpp:232:5:232:10 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:228:10:228:14 | array | array | test.cpp:232:5:232:19 | Store: ... = ... | write | | test.cpp:261:27:261:30 | PointerAdd: access to array | test.cpp:286:19:286:25 | buffer2 | test.cpp:261:27:261:30 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:285:19:285:25 | buffer2 | buffer2 | test.cpp:261:27:261:30 | Load: access to array | read | +| test.cpp:299:16:299:21 | PointerAdd: access to array | test.cpp:309:20:309:23 | arr2 | test.cpp:299:16:299:21 | access to array | This pointer arithmetic may have an off-by-1014 error allowing it to overrun $@ at this $@. | test.cpp:308:9:308:12 | arr2 | arr2 | test.cpp:299:16:299:21 | Load: access to array | read | +| test.cpp:322:19:322:27 | PointerAdd: ... + ... | test.cpp:322:19:322:22 | temp | test.cpp:325:24:325:26 | end | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:314:10:314:13 | temp | temp | test.cpp:330:13:330:24 | Store: ... = ... | write | +| test.cpp:322:19:322:27 | PointerAdd: ... + ... | test.cpp:322:19:322:22 | temp | test.cpp:325:24:325:26 | end | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:314:10:314:13 | temp | temp | test.cpp:331:13:331:24 | Store: ... = ... | write | +| test.cpp:322:19:322:27 | PointerAdd: ... + ... | test.cpp:322:19:322:22 | temp | test.cpp:325:24:325:26 | end | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:314:10:314:13 | temp | temp | test.cpp:333:13:333:24 | Store: ... = ... | write | +| test.cpp:351:18:351:25 | PointerAdd: access to array | test.cpp:351:9:351:11 | arr | test.cpp:351:18:351:25 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:348:9:348:11 | arr | arr | test.cpp:351:18:351:25 | Load: access to array | read | +| test.cpp:351:18:351:25 | PointerAdd: access to array | test.cpp:351:18:351:20 | arr | test.cpp:351:18:351:25 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:348:9:348:11 | arr | arr | test.cpp:351:18:351:25 | Load: access to array | read | +| test.cpp:351:18:351:25 | PointerAdd: access to array | test.cpp:351:29:351:31 | arr | test.cpp:351:18:351:25 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:348:9:348:11 | arr | arr | test.cpp:351:18:351:25 | Load: access to array | read | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp index 22f63ec3713..2d3945e48db 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp @@ -288,3 +288,67 @@ void test_call_use2() { unsigned char buffer3[3]; call_call_use(buffer3,3); } + +int guardingCallee(int *arr, int size) { + if (size > MAX_SIZE) { + return -1; + } + + int sum; + for (int i = 0; i < size; i++) { + sum += arr[i]; // GOOD [FALSE POSITIVE] - guarded by size + } + return sum; +} + +int guardingCaller() { + int arr1[MAX_SIZE]; + guardingCallee(arr1, MAX_SIZE); + + int arr2[10]; + guardingCallee(arr2, 10); +} + +// simplified md5 padding +void correlatedCondition(int num) { + char temp[64]; + + char *end; + if(num < 64) { + if (num < 56) { + end = temp + 56; + } + else if (num < 64) { + end = temp + 64; // GOOD [FALSE POSITVE] + } + char *temp2 = temp + num; + while(temp2 != end) { + *temp2 = 0; + temp2++; + } + if(num < 56) { + temp2[0] = 0; + temp2[1] = 0; + // ... + temp2[7] = 0; + } + } +} + +int positiveRange(int x) { + if (x < 40) { + return -1; + } + if (x > 1024) { + return -1; + } + + int offset = (unsigned char)(x + 7)/8; + + int arr[128]; + + for(int i=127-offset; i>= 0; i--) { + arr[i] = arr[i+1] + arr[i+offset]; // GOOD [FALSE POSITIVE] + } + return arr[0]; +} diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/use.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/AllocationToInvalidPointer.expected similarity index 100% rename from ruby/ql/test/library-tests/dataflow/api-graphs/use.expected rename to cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/AllocationToInvalidPointer.expected diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/AllocationToInvalidPointer.ql b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/AllocationToInvalidPointer.ql new file mode 100644 index 00000000000..50baab4bfa7 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/AllocationToInvalidPointer.ql @@ -0,0 +1,29 @@ +import cpp +import semmle.code.cpp.security.InvalidPointerDereference.AllocationToInvalidPointer +import TestUtilities.InlineExpectationsTest +import semmle.code.cpp.ir.IR +import semmle.code.cpp.dataflow.new.DataFlow + +module AllocationToInvalidPointerTest implements TestSig { + string getARelevantTag() { result = "alloc" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(DataFlow::Node allocation, PointerAddInstruction pai, int delta | + pointerAddInstructionHasBounds(allocation, pai, _, delta) and + location = pai.getLocation() and + element = pai.toString() and + tag = "alloc" + | + delta > 0 and + value = "L" + allocation.getLocation().getStartLine().toString() + "+" + delta.toString() + or + delta = 0 and + value = "L" + allocation.getLocation().getStartLine().toString() + or + delta < 0 and + value = "L" + allocation.getLocation().getStartLine().toString() + "-" + (-delta).toString() + ) + } +} + +import MakeTest diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected index 32b068daf81..881a1d8383d 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected @@ -1,1571 +1,407 @@ edges -| test.cpp:4:15:4:20 | call to malloc | test.cpp:5:15:5:15 | p | -| test.cpp:5:15:5:15 | p | test.cpp:5:15:5:22 | ... + ... | -| test.cpp:5:15:5:15 | p | test.cpp:5:15:5:22 | ... + ... | -| test.cpp:5:15:5:15 | p | test.cpp:5:15:5:22 | ... + ... | -| test.cpp:5:15:5:15 | p | test.cpp:5:15:5:22 | ... + ... | -| test.cpp:5:15:5:15 | p | test.cpp:6:15:6:15 | q | -| test.cpp:5:15:5:15 | p | test.cpp:6:15:6:15 | q | -| test.cpp:5:15:5:15 | p | test.cpp:7:16:7:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:7:16:7:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:8:16:8:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:8:16:8:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:8:16:8:20 | ... + ... | -| test.cpp:5:15:5:15 | p | test.cpp:9:16:9:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:9:16:9:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:10:16:10:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:10:16:10:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:11:16:11:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:11:16:11:16 | q | -| test.cpp:5:15:5:15 | p | test.cpp:12:16:12:16 | q | +| test.cpp:4:15:4:20 | call to malloc | test.cpp:5:15:5:22 | ... + ... | +| test.cpp:4:15:4:20 | call to malloc | test.cpp:5:15:5:22 | ... + ... | +| test.cpp:4:15:4:20 | call to malloc | test.cpp:6:14:6:15 | * ... | +| test.cpp:4:15:4:20 | call to malloc | test.cpp:6:14:6:15 | * ... | +| test.cpp:4:15:4:20 | call to malloc | test.cpp:8:14:8:21 | * ... | | test.cpp:5:15:5:22 | ... + ... | test.cpp:5:15:5:22 | ... + ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:5:15:5:22 | ... + ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:15:6:15 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:15:6:15 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:15:6:15 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:15:6:15 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:7:16:7:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:7:16:7:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:7:16:7:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:7:16:7:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:16:8:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:16:8:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:16:8:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:16:8:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:9:16:9:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:9:16:9:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:9:16:9:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:9:16:9:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:10:16:10:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:10:16:10:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:10:16:10:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:10:16:10:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:11:16:11:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:11:16:11:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:11:16:11:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:11:16:11:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:12:16:12:16 | q | -| test.cpp:5:15:5:22 | ... + ... | test.cpp:12:16:12:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:6:15:6:15 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:6:15:6:15 | q | test.cpp:7:16:7:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:7:16:7:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:6:15:6:15 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:6:15:6:15 | q | test.cpp:8:16:8:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:8:16:8:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:9:16:9:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:9:16:9:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:10:16:10:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:10:16:10:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:11:16:11:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:11:16:11:16 | q | -| test.cpp:6:15:6:15 | q | test.cpp:12:16:12:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:7:16:7:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:7:16:7:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:7:16:7:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:7:16:7:16 | q | test.cpp:8:16:8:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:8:16:8:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:9:16:9:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:9:16:9:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:10:16:10:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:10:16:10:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:11:16:11:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:11:16:11:16 | q | -| test.cpp:7:16:7:16 | q | test.cpp:12:16:12:16 | q | -| test.cpp:8:16:8:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:8:16:8:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:8:16:8:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:8:16:8:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:8:16:8:16 | q | test.cpp:9:16:9:16 | q | -| test.cpp:8:16:8:16 | q | test.cpp:9:16:9:16 | q | -| test.cpp:8:16:8:16 | q | test.cpp:10:16:10:16 | q | -| test.cpp:8:16:8:16 | q | test.cpp:10:16:10:16 | q | -| test.cpp:8:16:8:16 | q | test.cpp:11:16:11:16 | q | -| test.cpp:8:16:8:16 | q | test.cpp:11:16:11:16 | q | -| test.cpp:8:16:8:16 | q | test.cpp:12:16:12:16 | q | -| test.cpp:8:16:8:20 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:9:16:9:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:9:16:9:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:9:16:9:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:9:16:9:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:9:16:9:16 | q | test.cpp:10:16:10:16 | q | -| test.cpp:9:16:9:16 | q | test.cpp:10:16:10:16 | q | -| test.cpp:9:16:9:16 | q | test.cpp:11:16:11:16 | q | -| test.cpp:9:16:9:16 | q | test.cpp:11:16:11:16 | q | -| test.cpp:9:16:9:16 | q | test.cpp:12:16:12:16 | q | -| test.cpp:10:16:10:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:10:16:10:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:10:16:10:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:10:16:10:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:10:16:10:16 | q | test.cpp:11:16:11:16 | q | -| test.cpp:10:16:10:16 | q | test.cpp:11:16:11:16 | q | -| test.cpp:10:16:10:16 | q | test.cpp:12:16:12:16 | q | -| test.cpp:11:16:11:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:11:16:11:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:11:16:11:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:11:16:11:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:11:16:11:16 | q | test.cpp:12:16:12:16 | q | -| test.cpp:12:16:12:16 | q | test.cpp:6:14:6:15 | Load: * ... | -| test.cpp:12:16:12:16 | q | test.cpp:8:14:8:21 | Load: * ... | -| test.cpp:16:15:16:20 | call to malloc | test.cpp:17:15:17:15 | p | -| test.cpp:17:15:17:15 | p | test.cpp:17:15:17:22 | ... + ... | -| test.cpp:17:15:17:15 | p | test.cpp:20:16:20:20 | ... + ... | -| test.cpp:17:15:17:22 | ... + ... | test.cpp:20:14:20:21 | Load: * ... | -| test.cpp:20:16:20:20 | ... + ... | test.cpp:20:14:20:21 | Load: * ... | -| test.cpp:28:15:28:20 | call to malloc | test.cpp:29:15:29:15 | p | -| test.cpp:29:15:29:15 | p | test.cpp:29:15:29:28 | ... + ... | -| test.cpp:29:15:29:15 | p | test.cpp:29:15:29:28 | ... + ... | -| test.cpp:29:15:29:15 | p | test.cpp:29:15:29:28 | ... + ... | -| test.cpp:29:15:29:15 | p | test.cpp:29:15:29:28 | ... + ... | -| test.cpp:29:15:29:15 | p | test.cpp:30:15:30:15 | q | -| test.cpp:29:15:29:15 | p | test.cpp:30:15:30:15 | q | -| test.cpp:29:15:29:15 | p | test.cpp:31:16:31:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:31:16:31:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:32:16:32:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:32:16:32:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:32:16:32:20 | ... + ... | -| test.cpp:29:15:29:15 | p | test.cpp:33:16:33:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:33:16:33:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:34:16:34:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:34:16:34:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:35:16:35:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:35:16:35:16 | q | -| test.cpp:29:15:29:15 | p | test.cpp:36:16:36:16 | q | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:14:8:21 | * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:14:8:21 | * ... | +| test.cpp:6:14:6:15 | * ... | test.cpp:8:14:8:21 | * ... | +| test.cpp:16:15:16:20 | call to malloc | test.cpp:20:14:20:21 | * ... | +| test.cpp:28:15:28:20 | call to malloc | test.cpp:29:15:29:28 | ... + ... | +| test.cpp:28:15:28:20 | call to malloc | test.cpp:29:15:29:28 | ... + ... | +| test.cpp:28:15:28:20 | call to malloc | test.cpp:30:14:30:15 | * ... | +| test.cpp:28:15:28:20 | call to malloc | test.cpp:30:14:30:15 | * ... | +| test.cpp:28:15:28:20 | call to malloc | test.cpp:32:14:32:21 | * ... | | test.cpp:29:15:29:28 | ... + ... | test.cpp:29:15:29:28 | ... + ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:29:15:29:28 | ... + ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:15:30:15 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:15:30:15 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:15:30:15 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:15:30:15 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:31:16:31:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:31:16:31:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:31:16:31:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:31:16:31:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:16:32:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:16:32:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:16:32:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:16:32:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:33:16:33:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:33:16:33:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:33:16:33:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:33:16:33:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:34:16:34:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:34:16:34:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:34:16:34:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:34:16:34:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:35:16:35:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:35:16:35:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:35:16:35:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:35:16:35:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:36:16:36:16 | q | -| test.cpp:29:15:29:28 | ... + ... | test.cpp:36:16:36:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:30:15:30:15 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:30:15:30:15 | q | test.cpp:31:16:31:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:31:16:31:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:30:15:30:15 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:30:15:30:15 | q | test.cpp:32:16:32:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:32:16:32:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:33:16:33:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:33:16:33:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:34:16:34:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:34:16:34:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:35:16:35:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:35:16:35:16 | q | -| test.cpp:30:15:30:15 | q | test.cpp:36:16:36:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:31:16:31:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:31:16:31:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:31:16:31:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:31:16:31:16 | q | test.cpp:32:16:32:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:32:16:32:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:33:16:33:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:33:16:33:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:34:16:34:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:34:16:34:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:35:16:35:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:35:16:35:16 | q | -| test.cpp:31:16:31:16 | q | test.cpp:36:16:36:16 | q | -| test.cpp:32:16:32:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:32:16:32:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:32:16:32:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:32:16:32:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:32:16:32:16 | q | test.cpp:33:16:33:16 | q | -| test.cpp:32:16:32:16 | q | test.cpp:33:16:33:16 | q | -| test.cpp:32:16:32:16 | q | test.cpp:34:16:34:16 | q | -| test.cpp:32:16:32:16 | q | test.cpp:34:16:34:16 | q | -| test.cpp:32:16:32:16 | q | test.cpp:35:16:35:16 | q | -| test.cpp:32:16:32:16 | q | test.cpp:35:16:35:16 | q | -| test.cpp:32:16:32:16 | q | test.cpp:36:16:36:16 | q | -| test.cpp:32:16:32:20 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:33:16:33:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:33:16:33:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:33:16:33:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:33:16:33:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:33:16:33:16 | q | test.cpp:34:16:34:16 | q | -| test.cpp:33:16:33:16 | q | test.cpp:34:16:34:16 | q | -| test.cpp:33:16:33:16 | q | test.cpp:35:16:35:16 | q | -| test.cpp:33:16:33:16 | q | test.cpp:35:16:35:16 | q | -| test.cpp:33:16:33:16 | q | test.cpp:36:16:36:16 | q | -| test.cpp:34:16:34:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:34:16:34:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:34:16:34:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:34:16:34:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:34:16:34:16 | q | test.cpp:35:16:35:16 | q | -| test.cpp:34:16:34:16 | q | test.cpp:35:16:35:16 | q | -| test.cpp:34:16:34:16 | q | test.cpp:36:16:36:16 | q | -| test.cpp:35:16:35:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:35:16:35:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:35:16:35:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:35:16:35:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:35:16:35:16 | q | test.cpp:36:16:36:16 | q | -| test.cpp:36:16:36:16 | q | test.cpp:30:14:30:15 | Load: * ... | -| test.cpp:36:16:36:16 | q | test.cpp:32:14:32:21 | Load: * ... | -| test.cpp:40:15:40:20 | call to malloc | test.cpp:41:15:41:15 | p | -| test.cpp:41:15:41:15 | p | test.cpp:41:15:41:28 | ... + ... | -| test.cpp:41:15:41:15 | p | test.cpp:41:15:41:28 | ... + ... | -| test.cpp:41:15:41:15 | p | test.cpp:41:15:41:28 | ... + ... | -| test.cpp:41:15:41:15 | p | test.cpp:41:15:41:28 | ... + ... | -| test.cpp:41:15:41:15 | p | test.cpp:42:15:42:15 | q | -| test.cpp:41:15:41:15 | p | test.cpp:42:15:42:15 | q | -| test.cpp:41:15:41:15 | p | test.cpp:43:16:43:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:43:16:43:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:44:16:44:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:44:16:44:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:44:16:44:20 | ... + ... | -| test.cpp:41:15:41:15 | p | test.cpp:45:16:45:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:45:16:45:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:46:16:46:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:46:16:46:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:47:16:47:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:47:16:47:16 | q | -| test.cpp:41:15:41:15 | p | test.cpp:48:16:48:16 | q | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:14:32:21 | * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:14:32:21 | * ... | +| test.cpp:30:14:30:15 | * ... | test.cpp:32:14:32:21 | * ... | +| test.cpp:40:15:40:20 | call to malloc | test.cpp:41:15:41:28 | ... + ... | +| test.cpp:40:15:40:20 | call to malloc | test.cpp:41:15:41:28 | ... + ... | +| test.cpp:40:15:40:20 | call to malloc | test.cpp:42:14:42:15 | * ... | +| test.cpp:40:15:40:20 | call to malloc | test.cpp:42:14:42:15 | * ... | +| test.cpp:40:15:40:20 | call to malloc | test.cpp:44:14:44:21 | * ... | | test.cpp:41:15:41:28 | ... + ... | test.cpp:41:15:41:28 | ... + ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:41:15:41:28 | ... + ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:15:42:15 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:15:42:15 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:15:42:15 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:15:42:15 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:43:16:43:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:43:16:43:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:43:16:43:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:43:16:43:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:16:44:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:16:44:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:16:44:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:16:44:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:45:16:45:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:45:16:45:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:45:16:45:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:45:16:45:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:46:16:46:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:46:16:46:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:46:16:46:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:46:16:46:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:47:16:47:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:47:16:47:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:47:16:47:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:47:16:47:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:48:16:48:16 | q | -| test.cpp:41:15:41:28 | ... + ... | test.cpp:48:16:48:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:42:15:42:15 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:42:15:42:15 | q | test.cpp:43:16:43:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:43:16:43:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:42:15:42:15 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:42:15:42:15 | q | test.cpp:44:16:44:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:44:16:44:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:45:16:45:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:45:16:45:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:46:16:46:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:46:16:46:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:47:16:47:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:47:16:47:16 | q | -| test.cpp:42:15:42:15 | q | test.cpp:48:16:48:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:43:16:43:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:43:16:43:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:43:16:43:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:43:16:43:16 | q | test.cpp:44:16:44:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:44:16:44:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:45:16:45:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:45:16:45:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:46:16:46:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:46:16:46:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:47:16:47:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:47:16:47:16 | q | -| test.cpp:43:16:43:16 | q | test.cpp:48:16:48:16 | q | -| test.cpp:44:16:44:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:44:16:44:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:44:16:44:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:44:16:44:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:44:16:44:16 | q | test.cpp:45:16:45:16 | q | -| test.cpp:44:16:44:16 | q | test.cpp:45:16:45:16 | q | -| test.cpp:44:16:44:16 | q | test.cpp:46:16:46:16 | q | -| test.cpp:44:16:44:16 | q | test.cpp:46:16:46:16 | q | -| test.cpp:44:16:44:16 | q | test.cpp:47:16:47:16 | q | -| test.cpp:44:16:44:16 | q | test.cpp:47:16:47:16 | q | -| test.cpp:44:16:44:16 | q | test.cpp:48:16:48:16 | q | -| test.cpp:44:16:44:20 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:45:16:45:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:45:16:45:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:45:16:45:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:45:16:45:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:45:16:45:16 | q | test.cpp:46:16:46:16 | q | -| test.cpp:45:16:45:16 | q | test.cpp:46:16:46:16 | q | -| test.cpp:45:16:45:16 | q | test.cpp:47:16:47:16 | q | -| test.cpp:45:16:45:16 | q | test.cpp:47:16:47:16 | q | -| test.cpp:45:16:45:16 | q | test.cpp:48:16:48:16 | q | -| test.cpp:46:16:46:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:46:16:46:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:46:16:46:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:46:16:46:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:46:16:46:16 | q | test.cpp:47:16:47:16 | q | -| test.cpp:46:16:46:16 | q | test.cpp:47:16:47:16 | q | -| test.cpp:46:16:46:16 | q | test.cpp:48:16:48:16 | q | -| test.cpp:47:16:47:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:47:16:47:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:47:16:47:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:47:16:47:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:47:16:47:16 | q | test.cpp:48:16:48:16 | q | -| test.cpp:48:16:48:16 | q | test.cpp:42:14:42:15 | Load: * ... | -| test.cpp:48:16:48:16 | q | test.cpp:44:14:44:21 | Load: * ... | -| test.cpp:51:7:51:14 | mk_array indirection | test.cpp:60:19:60:26 | call to mk_array | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:14:44:21 | * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:14:44:21 | * ... | +| test.cpp:42:14:42:15 | * ... | test.cpp:44:14:44:21 | * ... | | test.cpp:51:33:51:35 | end | test.cpp:60:34:60:37 | mk_array output argument | -| test.cpp:52:19:52:24 | call to malloc | test.cpp:51:7:51:14 | mk_array indirection | -| test.cpp:52:19:52:24 | call to malloc | test.cpp:53:12:53:16 | begin | +| test.cpp:52:19:52:24 | call to malloc | test.cpp:53:5:53:23 | ... = ... | +| test.cpp:52:19:52:24 | call to malloc | test.cpp:53:12:53:23 | ... + ... | | test.cpp:53:5:53:23 | ... = ... | test.cpp:51:33:51:35 | end | -| test.cpp:53:12:53:16 | begin | test.cpp:53:5:53:23 | ... = ... | -| test.cpp:53:12:53:16 | begin | test.cpp:53:12:53:23 | ... + ... | -| test.cpp:53:12:53:23 | ... + ... | test.cpp:51:33:51:35 | end | -| test.cpp:60:19:60:26 | call to mk_array | test.cpp:62:39:62:39 | p | -| test.cpp:60:19:60:26 | call to mk_array | test.cpp:66:39:66:39 | p | -| test.cpp:60:19:60:26 | call to mk_array | test.cpp:70:38:70:38 | p | -| test.cpp:60:34:60:37 | mk_array output argument | test.cpp:62:32:62:34 | end | -| test.cpp:60:34:60:37 | mk_array output argument | test.cpp:66:32:66:34 | end | -| test.cpp:60:34:60:37 | mk_array output argument | test.cpp:70:31:70:33 | end | -| test.cpp:62:32:62:34 | end | test.cpp:67:9:67:14 | Store: ... = ... | -| test.cpp:66:32:66:34 | end | test.cpp:67:9:67:14 | Store: ... = ... | -| test.cpp:70:31:70:33 | end | test.cpp:67:9:67:14 | Store: ... = ... | -| test.cpp:80:9:80:16 | mk_array indirection [begin] | test.cpp:89:19:89:26 | call to mk_array [begin] | -| test.cpp:80:9:80:16 | mk_array indirection [begin] | test.cpp:119:18:119:25 | call to mk_array [begin] | +| test.cpp:53:12:53:23 | ... + ... | test.cpp:53:5:53:23 | ... = ... | +| test.cpp:60:34:60:37 | mk_array output argument | test.cpp:67:9:67:14 | ... = ... | | test.cpp:80:9:80:16 | mk_array indirection [end] | test.cpp:89:19:89:26 | call to mk_array [end] | | test.cpp:80:9:80:16 | mk_array indirection [end] | test.cpp:119:18:119:25 | call to mk_array [end] | | test.cpp:82:5:82:28 | ... = ... | test.cpp:82:9:82:13 | arr indirection [post update] [begin] | -| test.cpp:82:9:82:13 | arr indirection [post update] [begin] | test.cpp:83:5:83:7 | arr indirection [begin] | | test.cpp:82:9:82:13 | arr indirection [post update] [begin] | test.cpp:83:15:83:17 | arr indirection [begin] | | test.cpp:82:17:82:22 | call to malloc | test.cpp:82:5:82:28 | ... = ... | -| test.cpp:83:5:83:7 | arr indirection [begin] | test.cpp:80:9:80:16 | mk_array indirection [begin] | | test.cpp:83:5:83:30 | ... = ... | test.cpp:83:9:83:11 | arr indirection [post update] [end] | | test.cpp:83:9:83:11 | arr indirection [post update] [end] | test.cpp:80:9:80:16 | mk_array indirection [end] | | test.cpp:83:15:83:17 | arr indirection [begin] | test.cpp:83:19:83:23 | begin indirection | | test.cpp:83:15:83:30 | ... + ... | test.cpp:83:5:83:30 | ... = ... | -| test.cpp:83:19:83:23 | begin | test.cpp:83:5:83:30 | ... = ... | -| test.cpp:83:19:83:23 | begin | test.cpp:83:15:83:30 | ... + ... | -| test.cpp:83:19:83:23 | begin indirection | test.cpp:83:19:83:23 | begin | -| test.cpp:89:19:89:26 | call to mk_array [begin] | test.cpp:91:20:91:22 | arr indirection [begin] | -| test.cpp:89:19:89:26 | call to mk_array [begin] | test.cpp:95:20:95:22 | arr indirection [begin] | -| test.cpp:89:19:89:26 | call to mk_array [begin] | test.cpp:99:20:99:22 | arr indirection [begin] | +| test.cpp:83:19:83:23 | begin indirection | test.cpp:83:5:83:30 | ... = ... | +| test.cpp:83:19:83:23 | begin indirection | test.cpp:83:15:83:30 | ... + ... | | test.cpp:89:19:89:26 | call to mk_array [end] | test.cpp:91:36:91:38 | arr indirection [end] | | test.cpp:89:19:89:26 | call to mk_array [end] | test.cpp:95:36:95:38 | arr indirection [end] | -| test.cpp:89:19:89:26 | call to mk_array [end] | test.cpp:99:35:99:37 | arr indirection [end] | -| test.cpp:91:20:91:22 | arr indirection [begin] | test.cpp:91:24:91:28 | begin | -| test.cpp:91:20:91:22 | arr indirection [begin] | test.cpp:91:24:91:28 | begin indirection | -| test.cpp:91:24:91:28 | begin | test.cpp:91:47:91:47 | p | -| test.cpp:91:24:91:28 | begin indirection | test.cpp:91:47:91:47 | p | -| test.cpp:91:36:91:38 | arr indirection [end] | test.cpp:91:40:91:42 | end | | test.cpp:91:36:91:38 | arr indirection [end] | test.cpp:91:40:91:42 | end indirection | -| test.cpp:91:40:91:42 | end | test.cpp:96:9:96:14 | Store: ... = ... | -| test.cpp:91:40:91:42 | end indirection | test.cpp:91:40:91:42 | end | -| test.cpp:95:20:95:22 | arr indirection [begin] | test.cpp:95:24:95:28 | begin | -| test.cpp:95:20:95:22 | arr indirection [begin] | test.cpp:95:24:95:28 | begin indirection | -| test.cpp:95:24:95:28 | begin | test.cpp:95:47:95:47 | p | -| test.cpp:95:24:95:28 | begin indirection | test.cpp:95:47:95:47 | p | -| test.cpp:95:36:95:38 | arr indirection [end] | test.cpp:95:40:95:42 | end | +| test.cpp:91:36:91:38 | arr indirection [end] | test.cpp:96:9:96:14 | ... = ... | +| test.cpp:91:40:91:42 | end indirection | test.cpp:96:9:96:14 | ... = ... | | test.cpp:95:36:95:38 | arr indirection [end] | test.cpp:95:40:95:42 | end indirection | -| test.cpp:95:40:95:42 | end | test.cpp:96:9:96:14 | Store: ... = ... | -| test.cpp:95:40:95:42 | end indirection | test.cpp:95:40:95:42 | end | -| test.cpp:99:20:99:22 | arr indirection [begin] | test.cpp:99:24:99:28 | begin | -| test.cpp:99:20:99:22 | arr indirection [begin] | test.cpp:99:24:99:28 | begin indirection | -| test.cpp:99:24:99:28 | begin | test.cpp:99:46:99:46 | p | -| test.cpp:99:24:99:28 | begin indirection | test.cpp:99:46:99:46 | p | -| test.cpp:99:35:99:37 | arr indirection [end] | test.cpp:99:39:99:41 | end | -| test.cpp:99:35:99:37 | arr indirection [end] | test.cpp:99:39:99:41 | end indirection | -| test.cpp:99:39:99:41 | end | test.cpp:96:9:96:14 | Store: ... = ... | -| test.cpp:99:39:99:41 | end indirection | test.cpp:99:39:99:41 | end | -| test.cpp:104:27:104:29 | arr [begin] | test.cpp:105:20:105:22 | arr indirection [begin] | -| test.cpp:104:27:104:29 | arr [begin] | test.cpp:109:20:109:22 | arr indirection [begin] | -| test.cpp:104:27:104:29 | arr [begin] | test.cpp:113:20:113:22 | arr indirection [begin] | +| test.cpp:95:36:95:38 | arr indirection [end] | test.cpp:96:9:96:14 | ... = ... | +| test.cpp:95:40:95:42 | end indirection | test.cpp:96:9:96:14 | ... = ... | | test.cpp:104:27:104:29 | arr [end] | test.cpp:105:36:105:38 | arr indirection [end] | | test.cpp:104:27:104:29 | arr [end] | test.cpp:109:36:109:38 | arr indirection [end] | -| test.cpp:104:27:104:29 | arr [end] | test.cpp:113:35:113:37 | arr indirection [end] | -| test.cpp:105:20:105:22 | arr indirection [begin] | test.cpp:105:24:105:28 | begin | -| test.cpp:105:20:105:22 | arr indirection [begin] | test.cpp:105:24:105:28 | begin indirection | -| test.cpp:105:24:105:28 | begin | test.cpp:105:47:105:47 | p | -| test.cpp:105:24:105:28 | begin indirection | test.cpp:105:47:105:47 | p | -| test.cpp:105:36:105:38 | arr indirection [end] | test.cpp:105:40:105:42 | end | | test.cpp:105:36:105:38 | arr indirection [end] | test.cpp:105:40:105:42 | end indirection | -| test.cpp:105:40:105:42 | end | test.cpp:110:9:110:14 | Store: ... = ... | -| test.cpp:105:40:105:42 | end indirection | test.cpp:105:40:105:42 | end | -| test.cpp:109:20:109:22 | arr indirection [begin] | test.cpp:109:24:109:28 | begin | -| test.cpp:109:20:109:22 | arr indirection [begin] | test.cpp:109:24:109:28 | begin indirection | -| test.cpp:109:24:109:28 | begin | test.cpp:109:47:109:47 | p | -| test.cpp:109:24:109:28 | begin indirection | test.cpp:109:47:109:47 | p | -| test.cpp:109:36:109:38 | arr indirection [end] | test.cpp:109:40:109:42 | end | +| test.cpp:105:36:105:38 | arr indirection [end] | test.cpp:110:9:110:14 | ... = ... | +| test.cpp:105:40:105:42 | end indirection | test.cpp:110:9:110:14 | ... = ... | | test.cpp:109:36:109:38 | arr indirection [end] | test.cpp:109:40:109:42 | end indirection | -| test.cpp:109:40:109:42 | end | test.cpp:110:9:110:14 | Store: ... = ... | -| test.cpp:109:40:109:42 | end indirection | test.cpp:109:40:109:42 | end | -| test.cpp:113:20:113:22 | arr indirection [begin] | test.cpp:113:24:113:28 | begin | -| test.cpp:113:20:113:22 | arr indirection [begin] | test.cpp:113:24:113:28 | begin indirection | -| test.cpp:113:24:113:28 | begin | test.cpp:113:46:113:46 | p | -| test.cpp:113:24:113:28 | begin indirection | test.cpp:113:46:113:46 | p | -| test.cpp:113:35:113:37 | arr indirection [end] | test.cpp:113:39:113:41 | end | -| test.cpp:113:35:113:37 | arr indirection [end] | test.cpp:113:39:113:41 | end indirection | -| test.cpp:113:39:113:41 | end | test.cpp:110:9:110:14 | Store: ... = ... | -| test.cpp:113:39:113:41 | end indirection | test.cpp:113:39:113:41 | end | -| test.cpp:119:18:119:25 | call to mk_array [begin] | test.cpp:104:27:104:29 | arr [begin] | +| test.cpp:109:36:109:38 | arr indirection [end] | test.cpp:110:9:110:14 | ... = ... | +| test.cpp:109:40:109:42 | end indirection | test.cpp:110:9:110:14 | ... = ... | | test.cpp:119:18:119:25 | call to mk_array [end] | test.cpp:104:27:104:29 | arr [end] | -| test.cpp:124:15:124:20 | call to malloc | test.cpp:125:5:125:17 | ... = ... | -| test.cpp:124:15:124:20 | call to malloc | test.cpp:126:15:126:15 | p | -| test.cpp:125:5:125:17 | ... = ... | test.cpp:125:9:125:13 | arr indirection [post update] [begin] | -| test.cpp:125:9:125:13 | arr indirection [post update] [begin] | test.cpp:126:5:126:7 | arr indirection [begin] | -| test.cpp:126:5:126:7 | arr indirection [begin] | test.cpp:129:11:129:13 | arr indirection [begin] | -| test.cpp:126:5:126:7 | arr indirection [begin] | test.cpp:133:11:133:13 | arr indirection [begin] | -| test.cpp:126:5:126:7 | arr indirection [begin] | test.cpp:137:11:137:13 | arr indirection [begin] | -| test.cpp:129:11:129:13 | arr indirection [begin] | test.cpp:129:15:129:19 | begin indirection | -| test.cpp:129:15:129:19 | begin indirection | test.cpp:129:15:129:19 | begin | -| test.cpp:133:11:133:13 | arr indirection [begin] | test.cpp:133:15:133:19 | begin indirection | -| test.cpp:133:15:133:19 | begin indirection | test.cpp:133:15:133:19 | begin | -| test.cpp:137:11:137:13 | arr indirection [begin] | test.cpp:137:15:137:19 | begin indirection | -| test.cpp:137:15:137:19 | begin indirection | test.cpp:137:15:137:19 | begin | -| test.cpp:141:10:141:19 | mk_array_p indirection [begin] | test.cpp:150:20:150:29 | call to mk_array_p indirection [begin] | -| test.cpp:141:10:141:19 | mk_array_p indirection [begin] | test.cpp:180:19:180:28 | call to mk_array_p indirection [begin] | | test.cpp:141:10:141:19 | mk_array_p indirection [end] | test.cpp:150:20:150:29 | call to mk_array_p indirection [end] | | test.cpp:141:10:141:19 | mk_array_p indirection [end] | test.cpp:180:19:180:28 | call to mk_array_p indirection [end] | | test.cpp:143:5:143:29 | ... = ... | test.cpp:143:10:143:14 | arr indirection [post update] [begin] | -| test.cpp:143:10:143:14 | arr indirection [post update] [begin] | test.cpp:144:5:144:7 | arr indirection [begin] | | test.cpp:143:10:143:14 | arr indirection [post update] [begin] | test.cpp:144:16:144:18 | arr indirection [begin] | | test.cpp:143:18:143:23 | call to malloc | test.cpp:143:5:143:29 | ... = ... | -| test.cpp:144:5:144:7 | arr indirection [begin] | test.cpp:141:10:141:19 | mk_array_p indirection [begin] | | test.cpp:144:5:144:32 | ... = ... | test.cpp:144:10:144:12 | arr indirection [post update] [end] | | test.cpp:144:10:144:12 | arr indirection [post update] [end] | test.cpp:141:10:141:19 | mk_array_p indirection [end] | | test.cpp:144:16:144:18 | arr indirection [begin] | test.cpp:144:21:144:25 | begin indirection | | test.cpp:144:16:144:32 | ... + ... | test.cpp:144:5:144:32 | ... = ... | -| test.cpp:144:21:144:25 | begin | test.cpp:144:5:144:32 | ... = ... | -| test.cpp:144:21:144:25 | begin | test.cpp:144:16:144:32 | ... + ... | -| test.cpp:144:21:144:25 | begin indirection | test.cpp:144:21:144:25 | begin | -| test.cpp:150:20:150:29 | call to mk_array_p indirection [begin] | test.cpp:152:20:152:22 | arr indirection [begin] | -| test.cpp:150:20:150:29 | call to mk_array_p indirection [begin] | test.cpp:156:20:156:22 | arr indirection [begin] | -| test.cpp:150:20:150:29 | call to mk_array_p indirection [begin] | test.cpp:160:20:160:22 | arr indirection [begin] | +| test.cpp:144:21:144:25 | begin indirection | test.cpp:144:5:144:32 | ... = ... | +| test.cpp:144:21:144:25 | begin indirection | test.cpp:144:16:144:32 | ... + ... | | test.cpp:150:20:150:29 | call to mk_array_p indirection [end] | test.cpp:156:37:156:39 | arr indirection [end] | -| test.cpp:152:20:152:22 | arr indirection [begin] | test.cpp:152:25:152:29 | begin | -| test.cpp:152:20:152:22 | arr indirection [begin] | test.cpp:152:25:152:29 | begin indirection | -| test.cpp:152:25:152:29 | begin | test.cpp:152:49:152:49 | p | -| test.cpp:152:25:152:29 | begin indirection | test.cpp:152:49:152:49 | p | -| test.cpp:156:20:156:22 | arr indirection [begin] | test.cpp:156:25:156:29 | begin | -| test.cpp:156:20:156:22 | arr indirection [begin] | test.cpp:156:25:156:29 | begin indirection | -| test.cpp:156:25:156:29 | begin | test.cpp:156:49:156:49 | p | -| test.cpp:156:25:156:29 | begin indirection | test.cpp:156:49:156:49 | p | -| test.cpp:156:37:156:39 | arr indirection [end] | test.cpp:156:42:156:44 | end | | test.cpp:156:37:156:39 | arr indirection [end] | test.cpp:156:42:156:44 | end indirection | -| test.cpp:156:42:156:44 | end | test.cpp:157:9:157:14 | Store: ... = ... | -| test.cpp:156:42:156:44 | end indirection | test.cpp:156:42:156:44 | end | -| test.cpp:160:20:160:22 | arr indirection [begin] | test.cpp:160:25:160:29 | begin | -| test.cpp:160:20:160:22 | arr indirection [begin] | test.cpp:160:25:160:29 | begin indirection | -| test.cpp:160:25:160:29 | begin | test.cpp:160:48:160:48 | p | -| test.cpp:160:25:160:29 | begin indirection | test.cpp:160:48:160:48 | p | -| test.cpp:165:29:165:31 | arr indirection [begin] | test.cpp:166:20:166:22 | arr indirection [begin] | -| test.cpp:165:29:165:31 | arr indirection [begin] | test.cpp:170:20:170:22 | arr indirection [begin] | -| test.cpp:165:29:165:31 | arr indirection [begin] | test.cpp:174:20:174:22 | arr indirection [begin] | +| test.cpp:156:37:156:39 | arr indirection [end] | test.cpp:157:9:157:14 | ... = ... | +| test.cpp:156:42:156:44 | end indirection | test.cpp:157:9:157:14 | ... = ... | | test.cpp:165:29:165:31 | arr indirection [end] | test.cpp:166:37:166:39 | arr indirection [end] | | test.cpp:165:29:165:31 | arr indirection [end] | test.cpp:170:37:170:39 | arr indirection [end] | -| test.cpp:165:29:165:31 | arr indirection [end] | test.cpp:174:36:174:38 | arr indirection [end] | -| test.cpp:166:20:166:22 | arr indirection [begin] | test.cpp:166:25:166:29 | begin | -| test.cpp:166:20:166:22 | arr indirection [begin] | test.cpp:166:25:166:29 | begin indirection | -| test.cpp:166:25:166:29 | begin | test.cpp:166:49:166:49 | p | -| test.cpp:166:25:166:29 | begin indirection | test.cpp:166:49:166:49 | p | -| test.cpp:166:37:166:39 | arr indirection [end] | test.cpp:166:42:166:44 | end | | test.cpp:166:37:166:39 | arr indirection [end] | test.cpp:166:42:166:44 | end indirection | -| test.cpp:166:42:166:44 | end | test.cpp:171:9:171:14 | Store: ... = ... | -| test.cpp:166:42:166:44 | end indirection | test.cpp:166:42:166:44 | end | -| test.cpp:170:20:170:22 | arr indirection [begin] | test.cpp:170:25:170:29 | begin | -| test.cpp:170:20:170:22 | arr indirection [begin] | test.cpp:170:25:170:29 | begin indirection | -| test.cpp:170:25:170:29 | begin | test.cpp:170:49:170:49 | p | -| test.cpp:170:25:170:29 | begin indirection | test.cpp:170:49:170:49 | p | -| test.cpp:170:37:170:39 | arr indirection [end] | test.cpp:170:42:170:44 | end | +| test.cpp:166:37:166:39 | arr indirection [end] | test.cpp:171:9:171:14 | ... = ... | +| test.cpp:166:42:166:44 | end indirection | test.cpp:171:9:171:14 | ... = ... | | test.cpp:170:37:170:39 | arr indirection [end] | test.cpp:170:42:170:44 | end indirection | -| test.cpp:170:42:170:44 | end | test.cpp:171:9:171:14 | Store: ... = ... | -| test.cpp:170:42:170:44 | end indirection | test.cpp:170:42:170:44 | end | -| test.cpp:174:20:174:22 | arr indirection [begin] | test.cpp:174:25:174:29 | begin | -| test.cpp:174:20:174:22 | arr indirection [begin] | test.cpp:174:25:174:29 | begin indirection | -| test.cpp:174:25:174:29 | begin | test.cpp:174:48:174:48 | p | -| test.cpp:174:25:174:29 | begin indirection | test.cpp:174:48:174:48 | p | -| test.cpp:174:36:174:38 | arr indirection [end] | test.cpp:174:41:174:43 | end | -| test.cpp:174:36:174:38 | arr indirection [end] | test.cpp:174:41:174:43 | end indirection | -| test.cpp:174:41:174:43 | end | test.cpp:171:9:171:14 | Store: ... = ... | -| test.cpp:174:41:174:43 | end indirection | test.cpp:174:41:174:43 | end | -| test.cpp:180:19:180:28 | call to mk_array_p indirection [begin] | test.cpp:165:29:165:31 | arr indirection [begin] | +| test.cpp:170:37:170:39 | arr indirection [end] | test.cpp:171:9:171:14 | ... = ... | +| test.cpp:170:42:170:44 | end indirection | test.cpp:171:9:171:14 | ... = ... | | test.cpp:180:19:180:28 | call to mk_array_p indirection [end] | test.cpp:165:29:165:31 | arr indirection [end] | -| test.cpp:188:15:188:20 | call to malloc | test.cpp:189:15:189:15 | p | -| test.cpp:194:23:194:28 | call to malloc | test.cpp:195:17:195:17 | p | -| test.cpp:194:23:194:28 | call to malloc | test.cpp:197:8:197:8 | p | -| test.cpp:194:23:194:28 | call to malloc | test.cpp:201:5:201:5 | p | -| test.cpp:195:17:195:17 | p | test.cpp:195:17:195:23 | ... + ... | -| test.cpp:195:17:195:17 | p | test.cpp:195:17:195:23 | ... + ... | -| test.cpp:195:17:195:17 | p | test.cpp:195:17:195:23 | ... + ... | -| test.cpp:195:17:195:17 | p | test.cpp:195:17:195:23 | ... + ... | -| test.cpp:195:17:195:17 | p | test.cpp:197:20:197:22 | end | -| test.cpp:195:17:195:17 | p | test.cpp:201:5:201:12 | access to array | +| test.cpp:194:23:194:28 | call to malloc | test.cpp:195:17:195:23 | ... + ... | +| test.cpp:194:23:194:28 | call to malloc | test.cpp:195:17:195:23 | ... + ... | +| test.cpp:194:23:194:28 | call to malloc | test.cpp:201:5:201:19 | ... = ... | | test.cpp:195:17:195:23 | ... + ... | test.cpp:195:17:195:23 | ... + ... | -| test.cpp:195:17:195:23 | ... + ... | test.cpp:195:17:195:23 | ... + ... | -| test.cpp:195:17:195:23 | ... + ... | test.cpp:197:20:197:22 | end | -| test.cpp:195:17:195:23 | ... + ... | test.cpp:197:20:197:22 | end | -| test.cpp:195:17:195:23 | ... + ... | test.cpp:201:5:201:19 | Store: ... = ... | -| test.cpp:195:17:195:23 | ... + ... | test.cpp:201:5:201:19 | Store: ... = ... | -| test.cpp:195:17:195:23 | ... + ... | test.cpp:201:5:201:19 | Store: ... = ... | -| test.cpp:195:17:195:23 | ... + ... | test.cpp:201:5:201:19 | Store: ... = ... | -| test.cpp:197:20:197:22 | end | test.cpp:201:5:201:19 | Store: ... = ... | -| test.cpp:201:5:201:12 | access to array | test.cpp:201:5:201:19 | Store: ... = ... | -| test.cpp:205:23:205:28 | call to malloc | test.cpp:206:17:206:17 | p | -| test.cpp:205:23:205:28 | call to malloc | test.cpp:208:15:208:15 | p | -| test.cpp:206:17:206:17 | p | test.cpp:206:17:206:23 | ... + ... | -| test.cpp:206:17:206:17 | p | test.cpp:206:17:206:23 | ... + ... | -| test.cpp:206:17:206:17 | p | test.cpp:206:17:206:23 | ... + ... | -| test.cpp:206:17:206:17 | p | test.cpp:206:17:206:23 | ... + ... | -| test.cpp:206:17:206:17 | p | test.cpp:209:12:209:14 | end | -| test.cpp:206:17:206:17 | p | test.cpp:213:5:213:6 | * ... | -| test.cpp:206:17:206:17 | p | test.cpp:213:6:213:6 | q | -| test.cpp:206:17:206:17 | p | test.cpp:213:6:213:6 | q | +| test.cpp:195:17:195:23 | ... + ... | test.cpp:201:5:201:19 | ... = ... | +| test.cpp:195:17:195:23 | ... + ... | test.cpp:201:5:201:19 | ... = ... | +| test.cpp:205:23:205:28 | call to malloc | test.cpp:206:17:206:23 | ... + ... | +| test.cpp:205:23:205:28 | call to malloc | test.cpp:206:17:206:23 | ... + ... | +| test.cpp:205:23:205:28 | call to malloc | test.cpp:213:5:213:13 | ... = ... | | test.cpp:206:17:206:23 | ... + ... | test.cpp:206:17:206:23 | ... + ... | -| test.cpp:206:17:206:23 | ... + ... | test.cpp:206:17:206:23 | ... + ... | -| test.cpp:206:17:206:23 | ... + ... | test.cpp:209:12:209:14 | end | -| test.cpp:206:17:206:23 | ... + ... | test.cpp:209:12:209:14 | end | -| test.cpp:206:17:206:23 | ... + ... | test.cpp:213:5:213:13 | Store: ... = ... | -| test.cpp:206:17:206:23 | ... + ... | test.cpp:213:5:213:13 | Store: ... = ... | -| test.cpp:206:17:206:23 | ... + ... | test.cpp:213:5:213:13 | Store: ... = ... | -| test.cpp:206:17:206:23 | ... + ... | test.cpp:213:5:213:13 | Store: ... = ... | -| test.cpp:209:12:209:14 | end | test.cpp:213:5:213:13 | Store: ... = ... | -| test.cpp:213:5:213:6 | * ... | test.cpp:213:5:213:13 | Store: ... = ... | -| test.cpp:213:6:213:6 | q | test.cpp:213:5:213:6 | * ... | -| test.cpp:213:6:213:6 | q | test.cpp:213:5:213:13 | Store: ... = ... | -| test.cpp:213:6:213:6 | q | test.cpp:213:5:213:13 | Store: ... = ... | -| test.cpp:221:17:221:22 | call to malloc | test.cpp:222:5:222:5 | p | -| test.cpp:231:18:231:30 | new[] | test.cpp:232:3:232:9 | newname | -| test.cpp:232:3:232:9 | newname | test.cpp:232:3:232:16 | access to array | -| test.cpp:232:3:232:16 | access to array | test.cpp:232:3:232:20 | Store: ... = ... | -| test.cpp:238:20:238:32 | new[] | test.cpp:239:5:239:11 | newname | -| test.cpp:239:5:239:11 | newname | test.cpp:239:5:239:18 | access to array | -| test.cpp:239:5:239:18 | access to array | test.cpp:239:5:239:22 | Store: ... = ... | -| test.cpp:248:24:248:30 | call to realloc | test.cpp:249:9:249:9 | p | -| test.cpp:248:24:248:30 | call to realloc | test.cpp:250:22:250:22 | p | -| test.cpp:248:24:248:30 | call to realloc | test.cpp:254:9:254:9 | p | -| test.cpp:254:9:254:9 | p | test.cpp:254:9:254:12 | access to array | -| test.cpp:254:9:254:12 | access to array | test.cpp:254:9:254:16 | Store: ... = ... | -| test.cpp:260:13:260:24 | new[] | test.cpp:261:14:261:15 | xs | -| test.cpp:261:14:261:15 | xs | test.cpp:261:14:261:21 | ... + ... | -| test.cpp:261:14:261:15 | xs | test.cpp:261:14:261:21 | ... + ... | -| test.cpp:261:14:261:15 | xs | test.cpp:261:14:261:21 | ... + ... | -| test.cpp:261:14:261:15 | xs | test.cpp:261:14:261:21 | ... + ... | -| test.cpp:261:14:261:15 | xs | test.cpp:262:26:262:28 | end | -| test.cpp:261:14:261:15 | xs | test.cpp:262:26:262:28 | end | -| test.cpp:261:14:261:15 | xs | test.cpp:262:31:262:31 | x | -| test.cpp:261:14:261:15 | xs | test.cpp:264:14:264:14 | x | -| test.cpp:261:14:261:15 | xs | test.cpp:264:14:264:14 | x | +| test.cpp:206:17:206:23 | ... + ... | test.cpp:213:5:213:13 | ... = ... | +| test.cpp:206:17:206:23 | ... + ... | test.cpp:213:5:213:13 | ... = ... | +| test.cpp:231:18:231:30 | new[] | test.cpp:232:3:232:20 | ... = ... | +| test.cpp:238:20:238:32 | new[] | test.cpp:239:5:239:22 | ... = ... | +| test.cpp:248:24:248:30 | call to realloc | test.cpp:254:9:254:16 | ... = ... | +| test.cpp:260:13:260:24 | new[] | test.cpp:261:14:261:21 | ... + ... | +| test.cpp:260:13:260:24 | new[] | test.cpp:261:14:261:21 | ... + ... | +| test.cpp:260:13:260:24 | new[] | test.cpp:264:13:264:14 | * ... | +| test.cpp:260:13:260:24 | new[] | test.cpp:264:13:264:14 | * ... | | test.cpp:261:14:261:21 | ... + ... | test.cpp:261:14:261:21 | ... + ... | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:261:14:261:21 | ... + ... | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:262:26:262:28 | end | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:262:26:262:28 | end | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:262:26:262:28 | end | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:262:26:262:28 | end | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:261:14:261:21 | ... + ... | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:262:26:262:28 | end | test.cpp:262:26:262:28 | end | -| test.cpp:262:26:262:28 | end | test.cpp:262:26:262:28 | end | -| test.cpp:262:26:262:28 | end | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:262:26:262:28 | end | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:262:31:262:31 | x | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:264:14:264:14 | x | test.cpp:262:31:262:31 | x | -| test.cpp:264:14:264:14 | x | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:264:14:264:14 | x | test.cpp:264:13:264:14 | Load: * ... | -| test.cpp:270:13:270:24 | new[] | test.cpp:271:14:271:15 | xs | -| test.cpp:270:13:270:24 | new[] | test.cpp:272:31:272:31 | x | -| test.cpp:271:14:271:15 | xs | test.cpp:271:14:271:21 | ... + ... | -| test.cpp:271:14:271:15 | xs | test.cpp:271:14:271:21 | ... + ... | -| test.cpp:271:14:271:15 | xs | test.cpp:271:14:271:21 | ... + ... | -| test.cpp:271:14:271:15 | xs | test.cpp:271:14:271:21 | ... + ... | -| test.cpp:271:14:271:15 | xs | test.cpp:272:26:272:28 | end | -| test.cpp:271:14:271:15 | xs | test.cpp:272:26:272:28 | end | -| test.cpp:271:14:271:15 | xs | test.cpp:272:31:272:31 | x | -| test.cpp:271:14:271:15 | xs | test.cpp:274:5:274:6 | * ... | -| test.cpp:271:14:271:15 | xs | test.cpp:274:6:274:6 | x | -| test.cpp:271:14:271:15 | xs | test.cpp:274:6:274:6 | x | +| test.cpp:261:14:261:21 | ... + ... | test.cpp:264:13:264:14 | * ... | +| test.cpp:261:14:261:21 | ... + ... | test.cpp:264:13:264:14 | * ... | +| test.cpp:261:14:261:21 | ... + ... | test.cpp:264:13:264:14 | * ... | +| test.cpp:261:14:261:21 | ... + ... | test.cpp:264:13:264:14 | * ... | +| test.cpp:264:13:264:14 | * ... | test.cpp:264:13:264:14 | * ... | +| test.cpp:264:13:264:14 | * ... | test.cpp:264:13:264:14 | * ... | +| test.cpp:270:13:270:24 | new[] | test.cpp:271:14:271:21 | ... + ... | +| test.cpp:270:13:270:24 | new[] | test.cpp:271:14:271:21 | ... + ... | +| test.cpp:270:13:270:24 | new[] | test.cpp:274:5:274:10 | ... = ... | | test.cpp:271:14:271:21 | ... + ... | test.cpp:271:14:271:21 | ... + ... | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:271:14:271:21 | ... + ... | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:272:26:272:28 | end | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:272:26:272:28 | end | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:272:26:272:28 | end | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:272:26:272:28 | end | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:272:26:272:28 | end | test.cpp:272:26:272:28 | end | -| test.cpp:272:26:272:28 | end | test.cpp:272:26:272:28 | end | -| test.cpp:272:26:272:28 | end | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:272:26:272:28 | end | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:272:31:272:31 | x | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:274:5:274:6 | * ... | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:274:6:274:6 | x | test.cpp:272:31:272:31 | x | -| test.cpp:274:6:274:6 | x | test.cpp:274:5:274:6 | * ... | -| test.cpp:274:6:274:6 | x | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:274:6:274:6 | x | test.cpp:274:5:274:10 | Store: ... = ... | -| test.cpp:280:13:280:24 | new[] | test.cpp:281:14:281:15 | xs | -| test.cpp:290:13:290:24 | new[] | test.cpp:291:14:291:15 | xs | -| test.cpp:290:13:290:24 | new[] | test.cpp:292:30:292:30 | x | -| test.cpp:304:15:304:26 | new[] | test.cpp:307:5:307:6 | xs | -| test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:6 | xs | -| test.cpp:308:5:308:6 | xs | test.cpp:308:5:308:11 | access to array | -| test.cpp:308:5:308:11 | access to array | test.cpp:308:5:308:29 | Store: ... = ... | -| test.cpp:313:14:313:27 | new[] | test.cpp:314:15:314:16 | xs | -| test.cpp:325:14:325:27 | new[] | test.cpp:326:15:326:16 | xs | -| test.cpp:326:15:326:16 | xs | test.cpp:326:15:326:23 | ... + ... | -| test.cpp:326:15:326:16 | xs | test.cpp:326:15:326:23 | ... + ... | -| test.cpp:326:15:326:16 | xs | test.cpp:338:8:338:15 | * ... | -| test.cpp:326:15:326:16 | xs | test.cpp:341:8:341:17 | * ... | -| test.cpp:326:15:326:23 | ... + ... | test.cpp:342:8:342:17 | * ... | -| test.cpp:326:15:326:23 | ... + ... | test.cpp:342:8:342:17 | * ... | -| test.cpp:338:8:338:15 | * ... | test.cpp:342:8:342:17 | * ... | -| test.cpp:341:8:341:17 | * ... | test.cpp:342:8:342:17 | * ... | -| test.cpp:347:14:347:27 | new[] | test.cpp:348:15:348:16 | xs | -| test.cpp:355:14:355:27 | new[] | test.cpp:356:15:356:16 | xs | -| test.cpp:356:15:356:16 | xs | test.cpp:356:15:356:23 | ... + ... | -| test.cpp:356:15:356:16 | xs | test.cpp:356:15:356:23 | ... + ... | -| test.cpp:356:15:356:16 | xs | test.cpp:356:15:356:23 | ... + ... | -| test.cpp:356:15:356:16 | xs | test.cpp:356:15:356:23 | ... + ... | -| test.cpp:356:15:356:16 | xs | test.cpp:357:24:357:26 | end | -| test.cpp:356:15:356:16 | xs | test.cpp:357:24:357:30 | ... + ... | -| test.cpp:356:15:356:16 | xs | test.cpp:357:24:357:30 | ... + ... | -| test.cpp:356:15:356:16 | xs | test.cpp:357:24:357:30 | ... + ... | -| test.cpp:356:15:356:16 | xs | test.cpp:357:24:357:30 | ... + ... | -| test.cpp:356:15:356:16 | xs | test.cpp:358:15:358:26 | end_plus_one | -| test.cpp:356:15:356:16 | xs | test.cpp:358:15:358:26 | end_plus_one | -| test.cpp:356:15:356:16 | xs | test.cpp:359:16:359:27 | end_plus_one | -| test.cpp:356:15:356:16 | xs | test.cpp:359:16:359:31 | ... + ... | +| test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | ... = ... | +| test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | ... = ... | +| test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:29 | ... = ... | +| test.cpp:355:14:355:27 | new[] | test.cpp:356:15:356:23 | ... + ... | +| test.cpp:355:14:355:27 | new[] | test.cpp:356:15:356:23 | ... + ... | +| test.cpp:355:14:355:27 | new[] | test.cpp:357:24:357:30 | ... + ... | +| test.cpp:355:14:355:27 | new[] | test.cpp:357:24:357:30 | ... + ... | +| test.cpp:355:14:355:27 | new[] | test.cpp:358:14:358:26 | * ... | +| test.cpp:355:14:355:27 | new[] | test.cpp:359:14:359:32 | * ... | | test.cpp:356:15:356:23 | ... + ... | test.cpp:356:15:356:23 | ... + ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:356:15:356:23 | ... + ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:357:24:357:26 | end | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:357:24:357:26 | end | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:356:15:356:23 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:357:24:357:26 | end | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:357:24:357:26 | end | test.cpp:359:14:359:32 | Load: * ... | +| test.cpp:356:15:356:23 | ... + ... | test.cpp:358:14:358:26 | * ... | +| test.cpp:356:15:356:23 | ... + ... | test.cpp:358:14:358:26 | * ... | +| test.cpp:356:15:356:23 | ... + ... | test.cpp:359:14:359:32 | * ... | +| test.cpp:356:15:356:23 | ... + ... | test.cpp:359:14:359:32 | * ... | | test.cpp:357:24:357:30 | ... + ... | test.cpp:357:24:357:30 | ... + ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:357:24:357:30 | ... + ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:15:358:26 | end_plus_one | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:15:358:26 | end_plus_one | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:15:358:26 | end_plus_one | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:15:358:26 | end_plus_one | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:359:16:359:27 | end_plus_one | -| test.cpp:357:24:357:30 | ... + ... | test.cpp:359:16:359:27 | end_plus_one | -| test.cpp:358:15:358:26 | end_plus_one | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:358:15:358:26 | end_plus_one | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:358:15:358:26 | end_plus_one | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:358:15:358:26 | end_plus_one | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:358:15:358:26 | end_plus_one | test.cpp:359:16:359:27 | end_plus_one | -| test.cpp:359:16:359:27 | end_plus_one | test.cpp:358:14:358:26 | Load: * ... | -| test.cpp:359:16:359:27 | end_plus_one | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:359:16:359:31 | ... + ... | test.cpp:359:14:359:32 | Load: * ... | -| test.cpp:363:14:363:27 | new[] | test.cpp:365:15:365:15 | p | -| test.cpp:377:14:377:27 | new[] | test.cpp:378:15:378:16 | xs | -| test.cpp:378:15:378:16 | xs | test.cpp:378:15:378:23 | ... + ... | -| test.cpp:378:15:378:16 | xs | test.cpp:378:15:378:23 | ... + ... | -| test.cpp:378:15:378:16 | xs | test.cpp:378:15:378:23 | ... + ... | -| test.cpp:378:15:378:16 | xs | test.cpp:378:15:378:23 | ... + ... | -| test.cpp:378:15:378:16 | xs | test.cpp:381:5:381:7 | end | -| test.cpp:378:15:378:16 | xs | test.cpp:381:5:381:9 | ... ++ | -| test.cpp:378:15:378:16 | xs | test.cpp:381:5:381:9 | ... ++ | -| test.cpp:378:15:378:16 | xs | test.cpp:384:14:384:16 | end | +| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:14:358:26 | * ... | +| test.cpp:357:24:357:30 | ... + ... | test.cpp:358:14:358:26 | * ... | +| test.cpp:357:24:357:30 | ... + ... | test.cpp:359:14:359:32 | * ... | +| test.cpp:357:24:357:30 | ... + ... | test.cpp:359:14:359:32 | * ... | +| test.cpp:377:14:377:27 | new[] | test.cpp:378:15:378:23 | ... + ... | +| test.cpp:377:14:377:27 | new[] | test.cpp:378:15:378:23 | ... + ... | +| test.cpp:377:14:377:27 | new[] | test.cpp:381:5:381:9 | ... ++ | +| test.cpp:377:14:377:27 | new[] | test.cpp:381:5:381:9 | ... ++ | +| test.cpp:377:14:377:27 | new[] | test.cpp:384:13:384:16 | * ... | | test.cpp:378:15:378:23 | ... + ... | test.cpp:378:15:378:23 | ... + ... | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:378:15:378:23 | ... + ... | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:381:5:381:7 | end | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:381:5:381:7 | end | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:384:13:384:16 | Load: * ... | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:384:13:384:16 | Load: * ... | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:384:13:384:16 | Load: * ... | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:384:13:384:16 | Load: * ... | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:384:14:384:16 | end | -| test.cpp:378:15:378:23 | ... + ... | test.cpp:384:14:384:16 | end | -| test.cpp:381:5:381:7 | end | test.cpp:384:13:384:16 | Load: * ... | -| test.cpp:381:5:381:9 | ... ++ | test.cpp:384:14:384:16 | end | -| test.cpp:381:5:381:9 | ... ++ | test.cpp:384:14:384:16 | end | -| test.cpp:384:14:384:16 | end | test.cpp:384:13:384:16 | Load: * ... | -| test.cpp:388:14:388:27 | new[] | test.cpp:389:16:389:17 | xs | -| test.cpp:388:14:388:27 | new[] | test.cpp:392:3:392:4 | xs | -| test.cpp:399:14:399:27 | new[] | test.cpp:400:16:400:17 | xs | -| test.cpp:399:14:399:27 | new[] | test.cpp:402:5:402:6 | xs | -| test.cpp:410:14:410:27 | new[] | test.cpp:411:16:411:17 | xs | -| test.cpp:410:14:410:27 | new[] | test.cpp:413:5:413:6 | xs | +| test.cpp:378:15:378:23 | ... + ... | test.cpp:384:13:384:16 | * ... | +| test.cpp:378:15:378:23 | ... + ... | test.cpp:384:13:384:16 | * ... | +| test.cpp:381:5:381:9 | ... ++ | test.cpp:381:5:381:9 | ... ++ | +| test.cpp:381:5:381:9 | ... ++ | test.cpp:384:13:384:16 | * ... | +| test.cpp:410:14:410:27 | new[] | test.cpp:411:15:411:23 | & ... | +| test.cpp:410:14:410:27 | new[] | test.cpp:411:15:411:23 | & ... | +| test.cpp:410:14:410:27 | new[] | test.cpp:413:5:413:8 | ... ++ | +| test.cpp:410:14:410:27 | new[] | test.cpp:413:5:413:8 | ... ++ | +| test.cpp:410:14:410:27 | new[] | test.cpp:415:7:415:15 | ... = ... | | test.cpp:411:15:411:23 | & ... | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:15:411:23 | & ... | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:15:411:23 | & ... | test.cpp:412:12:412:14 | end | -| test.cpp:411:15:411:23 | & ... | test.cpp:412:12:412:14 | end | -| test.cpp:411:15:411:23 | & ... | test.cpp:412:12:412:14 | end | -| test.cpp:411:15:411:23 | & ... | test.cpp:412:12:412:14 | end | -| test.cpp:411:15:411:23 | & ... | test.cpp:414:14:414:16 | end | -| test.cpp:411:15:411:23 | & ... | test.cpp:414:14:414:16 | end | -| test.cpp:411:15:411:23 | & ... | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:411:15:411:23 | & ... | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:411:15:411:23 | & ... | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:411:15:411:23 | & ... | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:411:16:411:17 | xs | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:16:411:17 | xs | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:16:411:17 | xs | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:16:411:17 | xs | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:16:411:17 | xs | test.cpp:411:16:411:23 | access to array | -| test.cpp:411:16:411:17 | xs | test.cpp:411:16:411:23 | access to array | -| test.cpp:411:16:411:17 | xs | test.cpp:412:12:412:14 | end | -| test.cpp:411:16:411:17 | xs | test.cpp:412:12:412:14 | end | -| test.cpp:411:16:411:17 | xs | test.cpp:413:5:413:8 | ... ++ | -| test.cpp:411:16:411:17 | xs | test.cpp:413:5:413:8 | ... ++ | -| test.cpp:411:16:411:17 | xs | test.cpp:413:5:413:8 | ... ++ | -| test.cpp:411:16:411:17 | xs | test.cpp:413:5:413:8 | ... ++ | -| test.cpp:411:16:411:17 | xs | test.cpp:414:9:414:10 | xs | -| test.cpp:411:16:411:17 | xs | test.cpp:414:14:414:16 | end | -| test.cpp:411:16:411:17 | xs | test.cpp:415:7:415:11 | access to array | -| test.cpp:411:16:411:23 | access to array | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:16:411:23 | access to array | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:16:411:23 | access to array | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:16:411:23 | access to array | test.cpp:411:15:411:23 | & ... | -| test.cpp:411:16:411:23 | access to array | test.cpp:412:12:412:14 | end | -| test.cpp:411:16:411:23 | access to array | test.cpp:412:12:412:14 | end | -| test.cpp:411:16:411:23 | access to array | test.cpp:414:14:414:16 | end | -| test.cpp:411:16:411:23 | access to array | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:411:16:411:23 | access to array | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:412:12:412:14 | end | test.cpp:414:14:414:16 | end | -| test.cpp:412:12:412:14 | end | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:412:12:412:14 | end | test.cpp:415:7:415:15 | Store: ... = ... | +| test.cpp:411:15:411:23 | & ... | test.cpp:415:7:415:15 | ... = ... | +| test.cpp:411:15:411:23 | & ... | test.cpp:415:7:415:15 | ... = ... | | test.cpp:413:5:413:8 | ... ++ | test.cpp:413:5:413:8 | ... ++ | -| test.cpp:413:5:413:8 | ... ++ | test.cpp:413:5:413:8 | ... ++ | -| test.cpp:413:5:413:8 | ... ++ | test.cpp:414:9:414:10 | xs | -| test.cpp:413:5:413:8 | ... ++ | test.cpp:414:9:414:10 | xs | -| test.cpp:413:5:413:8 | ... ++ | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:413:5:413:8 | ... ++ | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:413:5:413:8 | ... ++ | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:413:5:413:8 | ... ++ | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:414:9:414:10 | xs | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:414:14:414:16 | end | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:415:7:415:11 | access to array | test.cpp:415:7:415:15 | Store: ... = ... | -| test.cpp:421:14:421:27 | new[] | test.cpp:422:16:422:17 | xs | -| test.cpp:421:14:421:27 | new[] | test.cpp:424:5:424:6 | xs | +| test.cpp:413:5:413:8 | ... ++ | test.cpp:415:7:415:15 | ... = ... | +| test.cpp:413:5:413:8 | ... ++ | test.cpp:415:7:415:15 | ... = ... | +| test.cpp:421:14:421:27 | new[] | test.cpp:422:15:422:23 | & ... | +| test.cpp:421:14:421:27 | new[] | test.cpp:422:15:422:23 | & ... | +| test.cpp:421:14:421:27 | new[] | test.cpp:424:5:424:8 | ... ++ | +| test.cpp:421:14:421:27 | new[] | test.cpp:424:5:424:8 | ... ++ | +| test.cpp:421:14:421:27 | new[] | test.cpp:426:7:426:15 | ... = ... | | test.cpp:422:15:422:23 | & ... | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:15:422:23 | & ... | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:15:422:23 | & ... | test.cpp:423:12:423:14 | end | -| test.cpp:422:15:422:23 | & ... | test.cpp:423:12:423:14 | end | -| test.cpp:422:15:422:23 | & ... | test.cpp:423:12:423:14 | end | -| test.cpp:422:15:422:23 | & ... | test.cpp:423:12:423:14 | end | -| test.cpp:422:15:422:23 | & ... | test.cpp:425:18:425:20 | end | -| test.cpp:422:15:422:23 | & ... | test.cpp:425:18:425:20 | end | -| test.cpp:422:15:422:23 | & ... | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:422:15:422:23 | & ... | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:422:15:422:23 | & ... | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:422:15:422:23 | & ... | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:422:16:422:17 | xs | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:16:422:17 | xs | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:16:422:17 | xs | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:16:422:17 | xs | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:16:422:17 | xs | test.cpp:422:16:422:23 | access to array | -| test.cpp:422:16:422:17 | xs | test.cpp:422:16:422:23 | access to array | -| test.cpp:422:16:422:17 | xs | test.cpp:423:12:423:14 | end | -| test.cpp:422:16:422:17 | xs | test.cpp:423:12:423:14 | end | -| test.cpp:422:16:422:17 | xs | test.cpp:424:5:424:8 | ... ++ | -| test.cpp:422:16:422:17 | xs | test.cpp:424:5:424:8 | ... ++ | -| test.cpp:422:16:422:17 | xs | test.cpp:424:5:424:8 | ... ++ | -| test.cpp:422:16:422:17 | xs | test.cpp:424:5:424:8 | ... ++ | -| test.cpp:422:16:422:17 | xs | test.cpp:425:9:425:10 | xs | -| test.cpp:422:16:422:17 | xs | test.cpp:425:9:425:10 | xs | -| test.cpp:422:16:422:17 | xs | test.cpp:425:18:425:20 | end | -| test.cpp:422:16:422:17 | xs | test.cpp:426:7:426:8 | xs | -| test.cpp:422:16:422:17 | xs | test.cpp:426:7:426:11 | access to array | -| test.cpp:422:16:422:23 | access to array | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:16:422:23 | access to array | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:16:422:23 | access to array | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:16:422:23 | access to array | test.cpp:422:15:422:23 | & ... | -| test.cpp:422:16:422:23 | access to array | test.cpp:423:12:423:14 | end | -| test.cpp:422:16:422:23 | access to array | test.cpp:423:12:423:14 | end | -| test.cpp:422:16:422:23 | access to array | test.cpp:425:18:425:20 | end | -| test.cpp:422:16:422:23 | access to array | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:422:16:422:23 | access to array | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:423:12:423:14 | end | test.cpp:425:18:425:20 | end | -| test.cpp:423:12:423:14 | end | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:423:12:423:14 | end | test.cpp:426:7:426:15 | Store: ... = ... | +| test.cpp:422:15:422:23 | & ... | test.cpp:426:7:426:15 | ... = ... | +| test.cpp:422:15:422:23 | & ... | test.cpp:426:7:426:15 | ... = ... | | test.cpp:424:5:424:8 | ... ++ | test.cpp:424:5:424:8 | ... ++ | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:424:5:424:8 | ... ++ | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:425:9:425:10 | xs | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:425:9:425:10 | xs | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:425:9:425:10 | xs | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:425:9:425:10 | xs | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:426:7:426:8 | xs | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:426:7:426:8 | xs | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:424:5:424:8 | ... ++ | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:425:9:425:10 | xs | test.cpp:426:7:426:8 | xs | -| test.cpp:425:9:425:10 | xs | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:425:9:425:10 | xs | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:425:18:425:20 | end | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:426:7:426:8 | xs | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:426:7:426:11 | access to array | test.cpp:426:7:426:15 | Store: ... = ... | -| test.cpp:432:14:432:27 | new[] | test.cpp:433:16:433:17 | xs | -| test.cpp:432:14:432:27 | new[] | test.cpp:436:5:436:6 | xs | +| test.cpp:424:5:424:8 | ... ++ | test.cpp:426:7:426:15 | ... = ... | +| test.cpp:424:5:424:8 | ... ++ | test.cpp:426:7:426:15 | ... = ... | +| test.cpp:432:14:432:27 | new[] | test.cpp:433:15:433:23 | & ... | +| test.cpp:432:14:432:27 | new[] | test.cpp:433:15:433:23 | & ... | +| test.cpp:432:14:432:27 | new[] | test.cpp:436:5:436:8 | ... ++ | +| test.cpp:432:14:432:27 | new[] | test.cpp:436:5:436:8 | ... ++ | +| test.cpp:432:14:432:27 | new[] | test.cpp:438:7:438:15 | ... = ... | | test.cpp:433:15:433:23 | & ... | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:15:433:23 | & ... | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:15:433:23 | & ... | test.cpp:434:12:434:14 | end | -| test.cpp:433:15:433:23 | & ... | test.cpp:434:12:434:14 | end | -| test.cpp:433:15:433:23 | & ... | test.cpp:434:12:434:14 | end | -| test.cpp:433:15:433:23 | & ... | test.cpp:434:12:434:14 | end | -| test.cpp:433:15:433:23 | & ... | test.cpp:435:5:435:7 | end | -| test.cpp:433:15:433:23 | & ... | test.cpp:435:5:435:7 | end | -| test.cpp:433:15:433:23 | & ... | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:433:15:433:23 | & ... | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:433:15:433:23 | & ... | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:433:15:433:23 | & ... | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:433:16:433:17 | xs | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:16:433:17 | xs | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:16:433:17 | xs | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:16:433:17 | xs | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:16:433:17 | xs | test.cpp:433:16:433:23 | access to array | -| test.cpp:433:16:433:17 | xs | test.cpp:433:16:433:23 | access to array | -| test.cpp:433:16:433:17 | xs | test.cpp:434:12:434:14 | end | -| test.cpp:433:16:433:17 | xs | test.cpp:434:12:434:14 | end | -| test.cpp:433:16:433:17 | xs | test.cpp:435:5:435:7 | end | -| test.cpp:433:16:433:17 | xs | test.cpp:436:5:436:8 | ... ++ | -| test.cpp:433:16:433:17 | xs | test.cpp:436:5:436:8 | ... ++ | -| test.cpp:433:16:433:17 | xs | test.cpp:436:5:436:8 | ... ++ | -| test.cpp:433:16:433:17 | xs | test.cpp:436:5:436:8 | ... ++ | -| test.cpp:433:16:433:17 | xs | test.cpp:437:9:437:10 | xs | -| test.cpp:433:16:433:17 | xs | test.cpp:438:7:438:11 | access to array | -| test.cpp:433:16:433:23 | access to array | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:16:433:23 | access to array | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:16:433:23 | access to array | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:16:433:23 | access to array | test.cpp:433:15:433:23 | & ... | -| test.cpp:433:16:433:23 | access to array | test.cpp:434:12:434:14 | end | -| test.cpp:433:16:433:23 | access to array | test.cpp:434:12:434:14 | end | -| test.cpp:433:16:433:23 | access to array | test.cpp:435:5:435:7 | end | -| test.cpp:433:16:433:23 | access to array | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:433:16:433:23 | access to array | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:434:12:434:14 | end | test.cpp:435:5:435:7 | end | -| test.cpp:434:12:434:14 | end | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:434:12:434:14 | end | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:435:5:435:7 | end | test.cpp:438:7:438:15 | Store: ... = ... | +| test.cpp:433:15:433:23 | & ... | test.cpp:438:7:438:15 | ... = ... | +| test.cpp:433:15:433:23 | & ... | test.cpp:438:7:438:15 | ... = ... | | test.cpp:436:5:436:8 | ... ++ | test.cpp:436:5:436:8 | ... ++ | -| test.cpp:436:5:436:8 | ... ++ | test.cpp:436:5:436:8 | ... ++ | -| test.cpp:436:5:436:8 | ... ++ | test.cpp:437:9:437:10 | xs | -| test.cpp:436:5:436:8 | ... ++ | test.cpp:437:9:437:10 | xs | -| test.cpp:436:5:436:8 | ... ++ | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:436:5:436:8 | ... ++ | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:436:5:436:8 | ... ++ | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:436:5:436:8 | ... ++ | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:437:9:437:10 | xs | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:438:7:438:11 | access to array | test.cpp:438:7:438:15 | Store: ... = ... | -| test.cpp:444:14:444:27 | new[] | test.cpp:445:16:445:17 | xs | -| test.cpp:444:14:444:27 | new[] | test.cpp:448:5:448:6 | xs | +| test.cpp:436:5:436:8 | ... ++ | test.cpp:438:7:438:15 | ... = ... | +| test.cpp:436:5:436:8 | ... ++ | test.cpp:438:7:438:15 | ... = ... | +| test.cpp:444:14:444:27 | new[] | test.cpp:445:15:445:23 | & ... | +| test.cpp:444:14:444:27 | new[] | test.cpp:445:15:445:23 | & ... | +| test.cpp:444:14:444:27 | new[] | test.cpp:448:5:448:8 | ... ++ | +| test.cpp:444:14:444:27 | new[] | test.cpp:448:5:448:8 | ... ++ | +| test.cpp:444:14:444:27 | new[] | test.cpp:450:7:450:15 | ... = ... | | test.cpp:445:15:445:23 | & ... | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:15:445:23 | & ... | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:15:445:23 | & ... | test.cpp:446:3:446:5 | end | -| test.cpp:445:15:445:23 | & ... | test.cpp:446:3:446:5 | end | -| test.cpp:445:15:445:23 | & ... | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:445:15:445:23 | & ... | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:445:15:445:23 | & ... | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:445:15:445:23 | & ... | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:445:16:445:17 | xs | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:16:445:17 | xs | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:16:445:17 | xs | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:16:445:17 | xs | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:16:445:17 | xs | test.cpp:445:16:445:23 | access to array | -| test.cpp:445:16:445:17 | xs | test.cpp:445:16:445:23 | access to array | -| test.cpp:445:16:445:17 | xs | test.cpp:446:3:446:5 | end | -| test.cpp:445:16:445:17 | xs | test.cpp:448:5:448:8 | ... ++ | -| test.cpp:445:16:445:17 | xs | test.cpp:448:5:448:8 | ... ++ | -| test.cpp:445:16:445:17 | xs | test.cpp:448:5:448:8 | ... ++ | -| test.cpp:445:16:445:17 | xs | test.cpp:448:5:448:8 | ... ++ | -| test.cpp:445:16:445:17 | xs | test.cpp:449:9:449:10 | xs | -| test.cpp:445:16:445:17 | xs | test.cpp:450:7:450:11 | access to array | -| test.cpp:445:16:445:23 | access to array | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:16:445:23 | access to array | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:16:445:23 | access to array | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:16:445:23 | access to array | test.cpp:445:15:445:23 | & ... | -| test.cpp:445:16:445:23 | access to array | test.cpp:446:3:446:5 | end | -| test.cpp:445:16:445:23 | access to array | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:445:16:445:23 | access to array | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:446:3:446:5 | end | test.cpp:450:7:450:15 | Store: ... = ... | +| test.cpp:445:15:445:23 | & ... | test.cpp:450:7:450:15 | ... = ... | +| test.cpp:445:15:445:23 | & ... | test.cpp:450:7:450:15 | ... = ... | | test.cpp:448:5:448:8 | ... ++ | test.cpp:448:5:448:8 | ... ++ | -| test.cpp:448:5:448:8 | ... ++ | test.cpp:448:5:448:8 | ... ++ | -| test.cpp:448:5:448:8 | ... ++ | test.cpp:449:9:449:10 | xs | -| test.cpp:448:5:448:8 | ... ++ | test.cpp:449:9:449:10 | xs | -| test.cpp:448:5:448:8 | ... ++ | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:448:5:448:8 | ... ++ | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:448:5:448:8 | ... ++ | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:448:5:448:8 | ... ++ | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:449:9:449:10 | xs | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:450:7:450:11 | access to array | test.cpp:450:7:450:15 | Store: ... = ... | -| test.cpp:456:14:456:31 | new[] | test.cpp:457:16:457:17 | xs | -| test.cpp:456:14:456:31 | new[] | test.cpp:460:5:460:6 | xs | -| test.cpp:468:14:468:27 | new[] | test.cpp:469:16:469:17 | xs | -| test.cpp:468:14:468:27 | new[] | test.cpp:472:5:472:6 | xs | -| test.cpp:480:14:480:27 | new[] | test.cpp:481:16:481:17 | xs | -| test.cpp:480:14:480:27 | new[] | test.cpp:484:5:484:6 | xs | +| test.cpp:448:5:448:8 | ... ++ | test.cpp:450:7:450:15 | ... = ... | +| test.cpp:448:5:448:8 | ... ++ | test.cpp:450:7:450:15 | ... = ... | +| test.cpp:480:14:480:27 | new[] | test.cpp:481:15:481:23 | & ... | +| test.cpp:480:14:480:27 | new[] | test.cpp:481:15:481:23 | & ... | +| test.cpp:480:14:480:27 | new[] | test.cpp:484:5:484:8 | ... ++ | +| test.cpp:480:14:480:27 | new[] | test.cpp:484:5:484:8 | ... ++ | +| test.cpp:480:14:480:27 | new[] | test.cpp:486:7:486:15 | ... = ... | | test.cpp:481:15:481:23 | & ... | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:15:481:23 | & ... | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:15:481:23 | & ... | test.cpp:482:3:482:5 | end | -| test.cpp:481:15:481:23 | & ... | test.cpp:482:3:482:5 | end | -| test.cpp:481:15:481:23 | & ... | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:481:15:481:23 | & ... | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:481:15:481:23 | & ... | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:481:15:481:23 | & ... | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:481:16:481:17 | xs | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:16:481:17 | xs | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:16:481:17 | xs | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:16:481:17 | xs | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:16:481:17 | xs | test.cpp:481:16:481:23 | access to array | -| test.cpp:481:16:481:17 | xs | test.cpp:481:16:481:23 | access to array | -| test.cpp:481:16:481:17 | xs | test.cpp:482:3:482:5 | end | -| test.cpp:481:16:481:17 | xs | test.cpp:484:5:484:8 | ... ++ | -| test.cpp:481:16:481:17 | xs | test.cpp:484:5:484:8 | ... ++ | -| test.cpp:481:16:481:17 | xs | test.cpp:484:5:484:8 | ... ++ | -| test.cpp:481:16:481:17 | xs | test.cpp:484:5:484:8 | ... ++ | -| test.cpp:481:16:481:17 | xs | test.cpp:485:9:485:10 | xs | -| test.cpp:481:16:481:17 | xs | test.cpp:486:7:486:11 | access to array | -| test.cpp:481:16:481:23 | access to array | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:16:481:23 | access to array | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:16:481:23 | access to array | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:16:481:23 | access to array | test.cpp:481:15:481:23 | & ... | -| test.cpp:481:16:481:23 | access to array | test.cpp:482:3:482:5 | end | -| test.cpp:481:16:481:23 | access to array | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:481:16:481:23 | access to array | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:482:3:482:5 | end | test.cpp:486:7:486:15 | Store: ... = ... | +| test.cpp:481:15:481:23 | & ... | test.cpp:486:7:486:15 | ... = ... | +| test.cpp:481:15:481:23 | & ... | test.cpp:486:7:486:15 | ... = ... | | test.cpp:484:5:484:8 | ... ++ | test.cpp:484:5:484:8 | ... ++ | -| test.cpp:484:5:484:8 | ... ++ | test.cpp:484:5:484:8 | ... ++ | -| test.cpp:484:5:484:8 | ... ++ | test.cpp:485:9:485:10 | xs | -| test.cpp:484:5:484:8 | ... ++ | test.cpp:485:9:485:10 | xs | -| test.cpp:484:5:484:8 | ... ++ | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:484:5:484:8 | ... ++ | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:484:5:484:8 | ... ++ | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:484:5:484:8 | ... ++ | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:485:9:485:10 | xs | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:486:7:486:11 | access to array | test.cpp:486:7:486:15 | Store: ... = ... | -| test.cpp:499:3:499:25 | ... = ... | test.cpp:499:7:499:8 | val indirection [post update] [xs] | -| test.cpp:499:7:499:8 | val indirection [post update] [xs] | test.cpp:500:3:500:5 | val indirection [xs] | -| test.cpp:499:12:499:25 | new[] | test.cpp:499:3:499:25 | ... = ... | -| test.cpp:500:3:500:5 | val indirection [xs] | test.cpp:500:7:500:8 | xs indirection | -| test.cpp:500:7:500:8 | xs indirection | test.cpp:500:7:500:8 | xs | -| test.cpp:510:16:510:33 | new[] | test.cpp:512:7:512:8 | xs | -| test.cpp:520:14:520:27 | new[] | test.cpp:526:5:526:6 | xs | -| test.cpp:532:14:532:27 | new[] | test.cpp:537:5:537:6 | xs | -| test.cpp:543:14:543:27 | new[] | test.cpp:548:5:548:6 | xs | -| test.cpp:548:5:548:6 | xs | test.cpp:548:5:548:15 | access to array | -| test.cpp:548:5:548:15 | access to array | test.cpp:548:5:548:19 | Store: ... = ... | -| test.cpp:554:14:554:27 | new[] | test.cpp:559:5:559:6 | xs | -| test.cpp:559:5:559:6 | xs | test.cpp:559:5:559:15 | access to array | -| test.cpp:559:5:559:15 | access to array | test.cpp:559:5:559:19 | Store: ... = ... | -| test.cpp:565:14:565:27 | new[] | test.cpp:570:5:570:6 | xs | -| test.cpp:576:14:576:27 | new[] | test.cpp:581:5:581:6 | xs | -| test.cpp:587:14:587:31 | new[] | test.cpp:592:5:592:6 | xs | -| test.cpp:598:14:598:31 | new[] | test.cpp:603:5:603:6 | xs | -| test.cpp:609:14:609:31 | new[] | test.cpp:614:5:614:6 | xs | -| test.cpp:620:14:620:31 | new[] | test.cpp:625:5:625:6 | xs | -| test.cpp:631:14:631:31 | new[] | test.cpp:636:5:636:6 | xs | -| test.cpp:642:14:642:31 | new[] | test.cpp:647:5:647:6 | xs | -| test.cpp:647:5:647:6 | xs | test.cpp:647:5:647:15 | access to array | -| test.cpp:647:5:647:15 | access to array | test.cpp:647:5:647:19 | Store: ... = ... | -| test.cpp:652:14:652:27 | new[] | test.cpp:653:16:653:17 | xs | -| test.cpp:652:14:652:27 | new[] | test.cpp:656:3:656:4 | xs | -| test.cpp:653:16:653:17 | xs | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:653:16:653:17 | xs | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:653:16:653:17 | xs | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:653:16:653:17 | xs | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:653:16:653:17 | xs | test.cpp:657:7:657:8 | xs | +| test.cpp:484:5:484:8 | ... ++ | test.cpp:486:7:486:15 | ... = ... | +| test.cpp:484:5:484:8 | ... ++ | test.cpp:486:7:486:15 | ... = ... | +| test.cpp:543:14:543:27 | new[] | test.cpp:548:5:548:19 | ... = ... | +| test.cpp:554:14:554:27 | new[] | test.cpp:559:5:559:19 | ... = ... | +| test.cpp:642:14:642:31 | new[] | test.cpp:647:5:647:19 | ... = ... | +| test.cpp:652:14:652:27 | new[] | test.cpp:656:3:656:6 | ... ++ | +| test.cpp:652:14:652:27 | new[] | test.cpp:656:3:656:6 | ... ++ | +| test.cpp:652:14:652:27 | new[] | test.cpp:662:3:662:11 | ... = ... | | test.cpp:656:3:656:6 | ... ++ | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:656:3:656:6 | ... ++ | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:657:7:657:8 | xs | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:657:7:657:8 | xs | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | Store: ... = ... | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | Store: ... = ... | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | Store: ... = ... | -| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | Store: ... = ... | -| test.cpp:657:7:657:8 | xs | test.cpp:662:3:662:11 | Store: ... = ... | -| test.cpp:667:14:667:31 | new[] | test.cpp:675:7:675:8 | xs | -| test.cpp:675:7:675:8 | xs | test.cpp:675:7:675:19 | access to array | -| test.cpp:675:7:675:19 | access to array | test.cpp:675:7:675:23 | Store: ... = ... | +| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | ... = ... | +| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | ... = ... | +| test.cpp:667:14:667:31 | new[] | test.cpp:675:7:675:23 | ... = ... | nodes | test.cpp:4:15:4:20 | call to malloc | semmle.label | call to malloc | -| test.cpp:5:15:5:15 | p | semmle.label | p | | test.cpp:5:15:5:22 | ... + ... | semmle.label | ... + ... | | test.cpp:5:15:5:22 | ... + ... | semmle.label | ... + ... | -| test.cpp:5:15:5:22 | ... + ... | semmle.label | ... + ... | -| test.cpp:5:15:5:22 | ... + ... | semmle.label | ... + ... | -| test.cpp:6:14:6:15 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:6:15:6:15 | q | semmle.label | q | -| test.cpp:6:15:6:15 | q | semmle.label | q | -| test.cpp:7:16:7:16 | q | semmle.label | q | -| test.cpp:7:16:7:16 | q | semmle.label | q | -| test.cpp:8:14:8:21 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:8:16:8:16 | q | semmle.label | q | -| test.cpp:8:16:8:16 | q | semmle.label | q | -| test.cpp:8:16:8:20 | ... + ... | semmle.label | ... + ... | -| test.cpp:9:16:9:16 | q | semmle.label | q | -| test.cpp:9:16:9:16 | q | semmle.label | q | -| test.cpp:10:16:10:16 | q | semmle.label | q | -| test.cpp:10:16:10:16 | q | semmle.label | q | -| test.cpp:11:16:11:16 | q | semmle.label | q | -| test.cpp:11:16:11:16 | q | semmle.label | q | -| test.cpp:12:16:12:16 | q | semmle.label | q | +| test.cpp:6:14:6:15 | * ... | semmle.label | * ... | +| test.cpp:6:14:6:15 | * ... | semmle.label | * ... | +| test.cpp:8:14:8:21 | * ... | semmle.label | * ... | | test.cpp:16:15:16:20 | call to malloc | semmle.label | call to malloc | -| test.cpp:17:15:17:15 | p | semmle.label | p | -| test.cpp:17:15:17:22 | ... + ... | semmle.label | ... + ... | -| test.cpp:20:14:20:21 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:20:16:20:20 | ... + ... | semmle.label | ... + ... | +| test.cpp:20:14:20:21 | * ... | semmle.label | * ... | | test.cpp:28:15:28:20 | call to malloc | semmle.label | call to malloc | -| test.cpp:29:15:29:15 | p | semmle.label | p | | test.cpp:29:15:29:28 | ... + ... | semmle.label | ... + ... | | test.cpp:29:15:29:28 | ... + ... | semmle.label | ... + ... | -| test.cpp:29:15:29:28 | ... + ... | semmle.label | ... + ... | -| test.cpp:29:15:29:28 | ... + ... | semmle.label | ... + ... | -| test.cpp:30:14:30:15 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:30:15:30:15 | q | semmle.label | q | -| test.cpp:30:15:30:15 | q | semmle.label | q | -| test.cpp:31:16:31:16 | q | semmle.label | q | -| test.cpp:31:16:31:16 | q | semmle.label | q | -| test.cpp:32:14:32:21 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:32:16:32:16 | q | semmle.label | q | -| test.cpp:32:16:32:16 | q | semmle.label | q | -| test.cpp:32:16:32:20 | ... + ... | semmle.label | ... + ... | -| test.cpp:33:16:33:16 | q | semmle.label | q | -| test.cpp:33:16:33:16 | q | semmle.label | q | -| test.cpp:34:16:34:16 | q | semmle.label | q | -| test.cpp:34:16:34:16 | q | semmle.label | q | -| test.cpp:35:16:35:16 | q | semmle.label | q | -| test.cpp:35:16:35:16 | q | semmle.label | q | -| test.cpp:36:16:36:16 | q | semmle.label | q | +| test.cpp:30:14:30:15 | * ... | semmle.label | * ... | +| test.cpp:30:14:30:15 | * ... | semmle.label | * ... | +| test.cpp:32:14:32:21 | * ... | semmle.label | * ... | | test.cpp:40:15:40:20 | call to malloc | semmle.label | call to malloc | -| test.cpp:41:15:41:15 | p | semmle.label | p | | test.cpp:41:15:41:28 | ... + ... | semmle.label | ... + ... | | test.cpp:41:15:41:28 | ... + ... | semmle.label | ... + ... | -| test.cpp:41:15:41:28 | ... + ... | semmle.label | ... + ... | -| test.cpp:41:15:41:28 | ... + ... | semmle.label | ... + ... | -| test.cpp:42:14:42:15 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:42:15:42:15 | q | semmle.label | q | -| test.cpp:42:15:42:15 | q | semmle.label | q | -| test.cpp:43:16:43:16 | q | semmle.label | q | -| test.cpp:43:16:43:16 | q | semmle.label | q | -| test.cpp:44:14:44:21 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:44:16:44:16 | q | semmle.label | q | -| test.cpp:44:16:44:16 | q | semmle.label | q | -| test.cpp:44:16:44:20 | ... + ... | semmle.label | ... + ... | -| test.cpp:45:16:45:16 | q | semmle.label | q | -| test.cpp:45:16:45:16 | q | semmle.label | q | -| test.cpp:46:16:46:16 | q | semmle.label | q | -| test.cpp:46:16:46:16 | q | semmle.label | q | -| test.cpp:47:16:47:16 | q | semmle.label | q | -| test.cpp:47:16:47:16 | q | semmle.label | q | -| test.cpp:48:16:48:16 | q | semmle.label | q | -| test.cpp:51:7:51:14 | mk_array indirection | semmle.label | mk_array indirection | +| test.cpp:42:14:42:15 | * ... | semmle.label | * ... | +| test.cpp:42:14:42:15 | * ... | semmle.label | * ... | +| test.cpp:44:14:44:21 | * ... | semmle.label | * ... | | test.cpp:51:33:51:35 | end | semmle.label | end | | test.cpp:52:19:52:24 | call to malloc | semmle.label | call to malloc | | test.cpp:53:5:53:23 | ... = ... | semmle.label | ... = ... | -| test.cpp:53:12:53:16 | begin | semmle.label | begin | | test.cpp:53:12:53:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:60:19:60:26 | call to mk_array | semmle.label | call to mk_array | | test.cpp:60:34:60:37 | mk_array output argument | semmle.label | mk_array output argument | -| test.cpp:62:32:62:34 | end | semmle.label | end | -| test.cpp:62:39:62:39 | p | semmle.label | p | -| test.cpp:66:32:66:34 | end | semmle.label | end | -| test.cpp:66:39:66:39 | p | semmle.label | p | -| test.cpp:67:9:67:14 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:70:31:70:33 | end | semmle.label | end | -| test.cpp:70:38:70:38 | p | semmle.label | p | -| test.cpp:80:9:80:16 | mk_array indirection [begin] | semmle.label | mk_array indirection [begin] | +| test.cpp:67:9:67:14 | ... = ... | semmle.label | ... = ... | | test.cpp:80:9:80:16 | mk_array indirection [end] | semmle.label | mk_array indirection [end] | | test.cpp:82:5:82:28 | ... = ... | semmle.label | ... = ... | | test.cpp:82:9:82:13 | arr indirection [post update] [begin] | semmle.label | arr indirection [post update] [begin] | | test.cpp:82:17:82:22 | call to malloc | semmle.label | call to malloc | -| test.cpp:83:5:83:7 | arr indirection [begin] | semmle.label | arr indirection [begin] | | test.cpp:83:5:83:30 | ... = ... | semmle.label | ... = ... | | test.cpp:83:9:83:11 | arr indirection [post update] [end] | semmle.label | arr indirection [post update] [end] | | test.cpp:83:15:83:17 | arr indirection [begin] | semmle.label | arr indirection [begin] | | test.cpp:83:15:83:30 | ... + ... | semmle.label | ... + ... | -| test.cpp:83:19:83:23 | begin | semmle.label | begin | | test.cpp:83:19:83:23 | begin indirection | semmle.label | begin indirection | -| test.cpp:89:19:89:26 | call to mk_array [begin] | semmle.label | call to mk_array [begin] | | test.cpp:89:19:89:26 | call to mk_array [end] | semmle.label | call to mk_array [end] | -| test.cpp:91:20:91:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:91:24:91:28 | begin | semmle.label | begin | -| test.cpp:91:24:91:28 | begin indirection | semmle.label | begin indirection | | test.cpp:91:36:91:38 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:91:40:91:42 | end | semmle.label | end | | test.cpp:91:40:91:42 | end indirection | semmle.label | end indirection | -| test.cpp:91:47:91:47 | p | semmle.label | p | -| test.cpp:95:20:95:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:95:24:95:28 | begin | semmle.label | begin | -| test.cpp:95:24:95:28 | begin indirection | semmle.label | begin indirection | | test.cpp:95:36:95:38 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:95:40:95:42 | end | semmle.label | end | | test.cpp:95:40:95:42 | end indirection | semmle.label | end indirection | -| test.cpp:95:47:95:47 | p | semmle.label | p | -| test.cpp:96:9:96:14 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:99:20:99:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:99:24:99:28 | begin | semmle.label | begin | -| test.cpp:99:24:99:28 | begin indirection | semmle.label | begin indirection | -| test.cpp:99:35:99:37 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:99:39:99:41 | end | semmle.label | end | -| test.cpp:99:39:99:41 | end indirection | semmle.label | end indirection | -| test.cpp:99:46:99:46 | p | semmle.label | p | -| test.cpp:104:27:104:29 | arr [begin] | semmle.label | arr [begin] | +| test.cpp:96:9:96:14 | ... = ... | semmle.label | ... = ... | | test.cpp:104:27:104:29 | arr [end] | semmle.label | arr [end] | -| test.cpp:105:20:105:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:105:24:105:28 | begin | semmle.label | begin | -| test.cpp:105:24:105:28 | begin indirection | semmle.label | begin indirection | | test.cpp:105:36:105:38 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:105:40:105:42 | end | semmle.label | end | | test.cpp:105:40:105:42 | end indirection | semmle.label | end indirection | -| test.cpp:105:47:105:47 | p | semmle.label | p | -| test.cpp:109:20:109:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:109:24:109:28 | begin | semmle.label | begin | -| test.cpp:109:24:109:28 | begin indirection | semmle.label | begin indirection | | test.cpp:109:36:109:38 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:109:40:109:42 | end | semmle.label | end | | test.cpp:109:40:109:42 | end indirection | semmle.label | end indirection | -| test.cpp:109:47:109:47 | p | semmle.label | p | -| test.cpp:110:9:110:14 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:113:20:113:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:113:24:113:28 | begin | semmle.label | begin | -| test.cpp:113:24:113:28 | begin indirection | semmle.label | begin indirection | -| test.cpp:113:35:113:37 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:113:39:113:41 | end | semmle.label | end | -| test.cpp:113:39:113:41 | end indirection | semmle.label | end indirection | -| test.cpp:113:46:113:46 | p | semmle.label | p | -| test.cpp:119:18:119:25 | call to mk_array [begin] | semmle.label | call to mk_array [begin] | +| test.cpp:110:9:110:14 | ... = ... | semmle.label | ... = ... | | test.cpp:119:18:119:25 | call to mk_array [end] | semmle.label | call to mk_array [end] | -| test.cpp:124:15:124:20 | call to malloc | semmle.label | call to malloc | -| test.cpp:125:5:125:17 | ... = ... | semmle.label | ... = ... | -| test.cpp:125:9:125:13 | arr indirection [post update] [begin] | semmle.label | arr indirection [post update] [begin] | -| test.cpp:126:5:126:7 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:126:15:126:15 | p | semmle.label | p | -| test.cpp:129:11:129:13 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:129:15:129:19 | begin | semmle.label | begin | -| test.cpp:129:15:129:19 | begin indirection | semmle.label | begin indirection | -| test.cpp:133:11:133:13 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:133:15:133:19 | begin | semmle.label | begin | -| test.cpp:133:15:133:19 | begin indirection | semmle.label | begin indirection | -| test.cpp:137:11:137:13 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:137:15:137:19 | begin | semmle.label | begin | -| test.cpp:137:15:137:19 | begin indirection | semmle.label | begin indirection | -| test.cpp:141:10:141:19 | mk_array_p indirection [begin] | semmle.label | mk_array_p indirection [begin] | | test.cpp:141:10:141:19 | mk_array_p indirection [end] | semmle.label | mk_array_p indirection [end] | | test.cpp:143:5:143:29 | ... = ... | semmle.label | ... = ... | | test.cpp:143:10:143:14 | arr indirection [post update] [begin] | semmle.label | arr indirection [post update] [begin] | | test.cpp:143:18:143:23 | call to malloc | semmle.label | call to malloc | -| test.cpp:144:5:144:7 | arr indirection [begin] | semmle.label | arr indirection [begin] | | test.cpp:144:5:144:32 | ... = ... | semmle.label | ... = ... | | test.cpp:144:10:144:12 | arr indirection [post update] [end] | semmle.label | arr indirection [post update] [end] | | test.cpp:144:16:144:18 | arr indirection [begin] | semmle.label | arr indirection [begin] | | test.cpp:144:16:144:32 | ... + ... | semmle.label | ... + ... | -| test.cpp:144:21:144:25 | begin | semmle.label | begin | | test.cpp:144:21:144:25 | begin indirection | semmle.label | begin indirection | -| test.cpp:150:20:150:29 | call to mk_array_p indirection [begin] | semmle.label | call to mk_array_p indirection [begin] | | test.cpp:150:20:150:29 | call to mk_array_p indirection [end] | semmle.label | call to mk_array_p indirection [end] | -| test.cpp:152:20:152:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:152:25:152:29 | begin | semmle.label | begin | -| test.cpp:152:25:152:29 | begin indirection | semmle.label | begin indirection | -| test.cpp:152:49:152:49 | p | semmle.label | p | -| test.cpp:156:20:156:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:156:25:156:29 | begin | semmle.label | begin | -| test.cpp:156:25:156:29 | begin indirection | semmle.label | begin indirection | | test.cpp:156:37:156:39 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:156:42:156:44 | end | semmle.label | end | | test.cpp:156:42:156:44 | end indirection | semmle.label | end indirection | -| test.cpp:156:49:156:49 | p | semmle.label | p | -| test.cpp:157:9:157:14 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:160:20:160:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:160:25:160:29 | begin | semmle.label | begin | -| test.cpp:160:25:160:29 | begin indirection | semmle.label | begin indirection | -| test.cpp:160:48:160:48 | p | semmle.label | p | -| test.cpp:165:29:165:31 | arr indirection [begin] | semmle.label | arr indirection [begin] | +| test.cpp:157:9:157:14 | ... = ... | semmle.label | ... = ... | | test.cpp:165:29:165:31 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:166:20:166:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:166:25:166:29 | begin | semmle.label | begin | -| test.cpp:166:25:166:29 | begin indirection | semmle.label | begin indirection | | test.cpp:166:37:166:39 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:166:42:166:44 | end | semmle.label | end | | test.cpp:166:42:166:44 | end indirection | semmle.label | end indirection | -| test.cpp:166:49:166:49 | p | semmle.label | p | -| test.cpp:170:20:170:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:170:25:170:29 | begin | semmle.label | begin | -| test.cpp:170:25:170:29 | begin indirection | semmle.label | begin indirection | | test.cpp:170:37:170:39 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:170:42:170:44 | end | semmle.label | end | | test.cpp:170:42:170:44 | end indirection | semmle.label | end indirection | -| test.cpp:170:49:170:49 | p | semmle.label | p | -| test.cpp:171:9:171:14 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:174:20:174:22 | arr indirection [begin] | semmle.label | arr indirection [begin] | -| test.cpp:174:25:174:29 | begin | semmle.label | begin | -| test.cpp:174:25:174:29 | begin indirection | semmle.label | begin indirection | -| test.cpp:174:36:174:38 | arr indirection [end] | semmle.label | arr indirection [end] | -| test.cpp:174:41:174:43 | end | semmle.label | end | -| test.cpp:174:41:174:43 | end indirection | semmle.label | end indirection | -| test.cpp:174:48:174:48 | p | semmle.label | p | -| test.cpp:180:19:180:28 | call to mk_array_p indirection [begin] | semmle.label | call to mk_array_p indirection [begin] | +| test.cpp:171:9:171:14 | ... = ... | semmle.label | ... = ... | | test.cpp:180:19:180:28 | call to mk_array_p indirection [end] | semmle.label | call to mk_array_p indirection [end] | -| test.cpp:188:15:188:20 | call to malloc | semmle.label | call to malloc | -| test.cpp:189:15:189:15 | p | semmle.label | p | | test.cpp:194:23:194:28 | call to malloc | semmle.label | call to malloc | -| test.cpp:195:17:195:17 | p | semmle.label | p | | test.cpp:195:17:195:23 | ... + ... | semmle.label | ... + ... | | test.cpp:195:17:195:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:195:17:195:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:195:17:195:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:197:8:197:8 | p | semmle.label | p | -| test.cpp:197:20:197:22 | end | semmle.label | end | -| test.cpp:201:5:201:5 | p | semmle.label | p | -| test.cpp:201:5:201:12 | access to array | semmle.label | access to array | -| test.cpp:201:5:201:19 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:201:5:201:19 | ... = ... | semmle.label | ... = ... | | test.cpp:205:23:205:28 | call to malloc | semmle.label | call to malloc | -| test.cpp:206:17:206:17 | p | semmle.label | p | | test.cpp:206:17:206:23 | ... + ... | semmle.label | ... + ... | | test.cpp:206:17:206:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:206:17:206:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:206:17:206:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:208:15:208:15 | p | semmle.label | p | -| test.cpp:209:12:209:14 | end | semmle.label | end | -| test.cpp:213:5:213:6 | * ... | semmle.label | * ... | -| test.cpp:213:5:213:13 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:213:6:213:6 | q | semmle.label | q | -| test.cpp:213:6:213:6 | q | semmle.label | q | -| test.cpp:221:17:221:22 | call to malloc | semmle.label | call to malloc | -| test.cpp:222:5:222:5 | p | semmle.label | p | +| test.cpp:213:5:213:13 | ... = ... | semmle.label | ... = ... | | test.cpp:231:18:231:30 | new[] | semmle.label | new[] | -| test.cpp:232:3:232:9 | newname | semmle.label | newname | -| test.cpp:232:3:232:16 | access to array | semmle.label | access to array | -| test.cpp:232:3:232:20 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:232:3:232:20 | ... = ... | semmle.label | ... = ... | | test.cpp:238:20:238:32 | new[] | semmle.label | new[] | -| test.cpp:239:5:239:11 | newname | semmle.label | newname | -| test.cpp:239:5:239:18 | access to array | semmle.label | access to array | -| test.cpp:239:5:239:22 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:239:5:239:22 | ... = ... | semmle.label | ... = ... | | test.cpp:248:24:248:30 | call to realloc | semmle.label | call to realloc | -| test.cpp:249:9:249:9 | p | semmle.label | p | -| test.cpp:250:22:250:22 | p | semmle.label | p | -| test.cpp:254:9:254:9 | p | semmle.label | p | -| test.cpp:254:9:254:12 | access to array | semmle.label | access to array | -| test.cpp:254:9:254:16 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:254:9:254:16 | ... = ... | semmle.label | ... = ... | | test.cpp:260:13:260:24 | new[] | semmle.label | new[] | -| test.cpp:261:14:261:15 | xs | semmle.label | xs | | test.cpp:261:14:261:21 | ... + ... | semmle.label | ... + ... | | test.cpp:261:14:261:21 | ... + ... | semmle.label | ... + ... | -| test.cpp:261:14:261:21 | ... + ... | semmle.label | ... + ... | -| test.cpp:261:14:261:21 | ... + ... | semmle.label | ... + ... | -| test.cpp:262:26:262:28 | end | semmle.label | end | -| test.cpp:262:26:262:28 | end | semmle.label | end | -| test.cpp:262:31:262:31 | x | semmle.label | x | -| test.cpp:264:13:264:14 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:264:14:264:14 | x | semmle.label | x | -| test.cpp:264:14:264:14 | x | semmle.label | x | +| test.cpp:264:13:264:14 | * ... | semmle.label | * ... | +| test.cpp:264:13:264:14 | * ... | semmle.label | * ... | | test.cpp:270:13:270:24 | new[] | semmle.label | new[] | -| test.cpp:271:14:271:15 | xs | semmle.label | xs | | test.cpp:271:14:271:21 | ... + ... | semmle.label | ... + ... | | test.cpp:271:14:271:21 | ... + ... | semmle.label | ... + ... | -| test.cpp:271:14:271:21 | ... + ... | semmle.label | ... + ... | -| test.cpp:271:14:271:21 | ... + ... | semmle.label | ... + ... | -| test.cpp:272:26:272:28 | end | semmle.label | end | -| test.cpp:272:26:272:28 | end | semmle.label | end | -| test.cpp:272:31:272:31 | x | semmle.label | x | -| test.cpp:272:31:272:31 | x | semmle.label | x | -| test.cpp:274:5:274:6 | * ... | semmle.label | * ... | -| test.cpp:274:5:274:10 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:274:6:274:6 | x | semmle.label | x | -| test.cpp:274:6:274:6 | x | semmle.label | x | -| test.cpp:280:13:280:24 | new[] | semmle.label | new[] | -| test.cpp:281:14:281:15 | xs | semmle.label | xs | -| test.cpp:290:13:290:24 | new[] | semmle.label | new[] | -| test.cpp:291:14:291:15 | xs | semmle.label | xs | -| test.cpp:292:30:292:30 | x | semmle.label | x | +| test.cpp:274:5:274:10 | ... = ... | semmle.label | ... = ... | | test.cpp:304:15:304:26 | new[] | semmle.label | new[] | -| test.cpp:307:5:307:6 | xs | semmle.label | xs | -| test.cpp:308:5:308:6 | xs | semmle.label | xs | -| test.cpp:308:5:308:11 | access to array | semmle.label | access to array | -| test.cpp:308:5:308:29 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:313:14:313:27 | new[] | semmle.label | new[] | -| test.cpp:314:15:314:16 | xs | semmle.label | xs | -| test.cpp:325:14:325:27 | new[] | semmle.label | new[] | -| test.cpp:326:15:326:16 | xs | semmle.label | xs | -| test.cpp:326:15:326:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:326:15:326:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:338:8:338:15 | * ... | semmle.label | * ... | -| test.cpp:341:8:341:17 | * ... | semmle.label | * ... | -| test.cpp:342:8:342:17 | * ... | semmle.label | * ... | -| test.cpp:347:14:347:27 | new[] | semmle.label | new[] | -| test.cpp:348:15:348:16 | xs | semmle.label | xs | +| test.cpp:308:5:308:29 | ... = ... | semmle.label | ... = ... | | test.cpp:355:14:355:27 | new[] | semmle.label | new[] | -| test.cpp:356:15:356:16 | xs | semmle.label | xs | | test.cpp:356:15:356:23 | ... + ... | semmle.label | ... + ... | | test.cpp:356:15:356:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:356:15:356:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:356:15:356:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:357:24:357:26 | end | semmle.label | end | | test.cpp:357:24:357:30 | ... + ... | semmle.label | ... + ... | | test.cpp:357:24:357:30 | ... + ... | semmle.label | ... + ... | -| test.cpp:357:24:357:30 | ... + ... | semmle.label | ... + ... | -| test.cpp:357:24:357:30 | ... + ... | semmle.label | ... + ... | -| test.cpp:358:14:358:26 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:358:15:358:26 | end_plus_one | semmle.label | end_plus_one | -| test.cpp:358:15:358:26 | end_plus_one | semmle.label | end_plus_one | -| test.cpp:359:14:359:32 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:359:16:359:27 | end_plus_one | semmle.label | end_plus_one | -| test.cpp:359:16:359:31 | ... + ... | semmle.label | ... + ... | -| test.cpp:363:14:363:27 | new[] | semmle.label | new[] | -| test.cpp:365:15:365:15 | p | semmle.label | p | +| test.cpp:358:14:358:26 | * ... | semmle.label | * ... | +| test.cpp:359:14:359:32 | * ... | semmle.label | * ... | | test.cpp:377:14:377:27 | new[] | semmle.label | new[] | -| test.cpp:378:15:378:16 | xs | semmle.label | xs | | test.cpp:378:15:378:23 | ... + ... | semmle.label | ... + ... | | test.cpp:378:15:378:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:378:15:378:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:378:15:378:23 | ... + ... | semmle.label | ... + ... | -| test.cpp:381:5:381:7 | end | semmle.label | end | | test.cpp:381:5:381:9 | ... ++ | semmle.label | ... ++ | | test.cpp:381:5:381:9 | ... ++ | semmle.label | ... ++ | -| test.cpp:384:13:384:16 | Load: * ... | semmle.label | Load: * ... | -| test.cpp:384:14:384:16 | end | semmle.label | end | -| test.cpp:388:14:388:27 | new[] | semmle.label | new[] | -| test.cpp:389:16:389:17 | xs | semmle.label | xs | -| test.cpp:392:3:392:4 | xs | semmle.label | xs | -| test.cpp:399:14:399:27 | new[] | semmle.label | new[] | -| test.cpp:400:16:400:17 | xs | semmle.label | xs | -| test.cpp:402:5:402:6 | xs | semmle.label | xs | +| test.cpp:384:13:384:16 | * ... | semmle.label | * ... | | test.cpp:410:14:410:27 | new[] | semmle.label | new[] | | test.cpp:411:15:411:23 | & ... | semmle.label | & ... | | test.cpp:411:15:411:23 | & ... | semmle.label | & ... | -| test.cpp:411:15:411:23 | & ... | semmle.label | & ... | -| test.cpp:411:15:411:23 | & ... | semmle.label | & ... | -| test.cpp:411:16:411:17 | xs | semmle.label | xs | -| test.cpp:411:16:411:23 | access to array | semmle.label | access to array | -| test.cpp:411:16:411:23 | access to array | semmle.label | access to array | -| test.cpp:412:12:412:14 | end | semmle.label | end | -| test.cpp:412:12:412:14 | end | semmle.label | end | -| test.cpp:413:5:413:6 | xs | semmle.label | xs | | test.cpp:413:5:413:8 | ... ++ | semmle.label | ... ++ | | test.cpp:413:5:413:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:413:5:413:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:413:5:413:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:414:9:414:10 | xs | semmle.label | xs | -| test.cpp:414:14:414:16 | end | semmle.label | end | -| test.cpp:415:7:415:11 | access to array | semmle.label | access to array | -| test.cpp:415:7:415:15 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:415:7:415:15 | ... = ... | semmle.label | ... = ... | | test.cpp:421:14:421:27 | new[] | semmle.label | new[] | | test.cpp:422:15:422:23 | & ... | semmle.label | & ... | | test.cpp:422:15:422:23 | & ... | semmle.label | & ... | -| test.cpp:422:15:422:23 | & ... | semmle.label | & ... | -| test.cpp:422:15:422:23 | & ... | semmle.label | & ... | -| test.cpp:422:16:422:17 | xs | semmle.label | xs | -| test.cpp:422:16:422:23 | access to array | semmle.label | access to array | -| test.cpp:422:16:422:23 | access to array | semmle.label | access to array | -| test.cpp:423:12:423:14 | end | semmle.label | end | -| test.cpp:423:12:423:14 | end | semmle.label | end | -| test.cpp:424:5:424:6 | xs | semmle.label | xs | | test.cpp:424:5:424:8 | ... ++ | semmle.label | ... ++ | | test.cpp:424:5:424:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:424:5:424:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:424:5:424:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:425:9:425:10 | xs | semmle.label | xs | -| test.cpp:425:9:425:10 | xs | semmle.label | xs | -| test.cpp:425:18:425:20 | end | semmle.label | end | -| test.cpp:426:7:426:8 | xs | semmle.label | xs | -| test.cpp:426:7:426:11 | access to array | semmle.label | access to array | -| test.cpp:426:7:426:15 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:426:7:426:15 | ... = ... | semmle.label | ... = ... | | test.cpp:432:14:432:27 | new[] | semmle.label | new[] | | test.cpp:433:15:433:23 | & ... | semmle.label | & ... | | test.cpp:433:15:433:23 | & ... | semmle.label | & ... | -| test.cpp:433:15:433:23 | & ... | semmle.label | & ... | -| test.cpp:433:15:433:23 | & ... | semmle.label | & ... | -| test.cpp:433:16:433:17 | xs | semmle.label | xs | -| test.cpp:433:16:433:23 | access to array | semmle.label | access to array | -| test.cpp:433:16:433:23 | access to array | semmle.label | access to array | -| test.cpp:434:12:434:14 | end | semmle.label | end | -| test.cpp:434:12:434:14 | end | semmle.label | end | -| test.cpp:435:5:435:7 | end | semmle.label | end | -| test.cpp:436:5:436:6 | xs | semmle.label | xs | | test.cpp:436:5:436:8 | ... ++ | semmle.label | ... ++ | | test.cpp:436:5:436:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:436:5:436:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:436:5:436:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:437:9:437:10 | xs | semmle.label | xs | -| test.cpp:438:7:438:11 | access to array | semmle.label | access to array | -| test.cpp:438:7:438:15 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:438:7:438:15 | ... = ... | semmle.label | ... = ... | | test.cpp:444:14:444:27 | new[] | semmle.label | new[] | | test.cpp:445:15:445:23 | & ... | semmle.label | & ... | | test.cpp:445:15:445:23 | & ... | semmle.label | & ... | -| test.cpp:445:15:445:23 | & ... | semmle.label | & ... | -| test.cpp:445:15:445:23 | & ... | semmle.label | & ... | -| test.cpp:445:16:445:17 | xs | semmle.label | xs | -| test.cpp:445:16:445:23 | access to array | semmle.label | access to array | -| test.cpp:445:16:445:23 | access to array | semmle.label | access to array | -| test.cpp:446:3:446:5 | end | semmle.label | end | -| test.cpp:448:5:448:6 | xs | semmle.label | xs | | test.cpp:448:5:448:8 | ... ++ | semmle.label | ... ++ | | test.cpp:448:5:448:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:448:5:448:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:448:5:448:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:449:9:449:10 | xs | semmle.label | xs | -| test.cpp:450:7:450:11 | access to array | semmle.label | access to array | -| test.cpp:450:7:450:15 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:456:14:456:31 | new[] | semmle.label | new[] | -| test.cpp:457:16:457:17 | xs | semmle.label | xs | -| test.cpp:460:5:460:6 | xs | semmle.label | xs | -| test.cpp:468:14:468:27 | new[] | semmle.label | new[] | -| test.cpp:469:16:469:17 | xs | semmle.label | xs | -| test.cpp:472:5:472:6 | xs | semmle.label | xs | +| test.cpp:450:7:450:15 | ... = ... | semmle.label | ... = ... | | test.cpp:480:14:480:27 | new[] | semmle.label | new[] | | test.cpp:481:15:481:23 | & ... | semmle.label | & ... | | test.cpp:481:15:481:23 | & ... | semmle.label | & ... | -| test.cpp:481:15:481:23 | & ... | semmle.label | & ... | -| test.cpp:481:15:481:23 | & ... | semmle.label | & ... | -| test.cpp:481:16:481:17 | xs | semmle.label | xs | -| test.cpp:481:16:481:23 | access to array | semmle.label | access to array | -| test.cpp:481:16:481:23 | access to array | semmle.label | access to array | -| test.cpp:482:3:482:5 | end | semmle.label | end | -| test.cpp:484:5:484:6 | xs | semmle.label | xs | | test.cpp:484:5:484:8 | ... ++ | semmle.label | ... ++ | | test.cpp:484:5:484:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:484:5:484:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:484:5:484:8 | ... ++ | semmle.label | ... ++ | -| test.cpp:485:9:485:10 | xs | semmle.label | xs | -| test.cpp:486:7:486:11 | access to array | semmle.label | access to array | -| test.cpp:486:7:486:15 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:499:3:499:25 | ... = ... | semmle.label | ... = ... | -| test.cpp:499:7:499:8 | val indirection [post update] [xs] | semmle.label | val indirection [post update] [xs] | -| test.cpp:499:12:499:25 | new[] | semmle.label | new[] | -| test.cpp:500:3:500:5 | val indirection [xs] | semmle.label | val indirection [xs] | -| test.cpp:500:7:500:8 | xs | semmle.label | xs | -| test.cpp:500:7:500:8 | xs indirection | semmle.label | xs indirection | -| test.cpp:510:16:510:33 | new[] | semmle.label | new[] | -| test.cpp:512:7:512:8 | xs | semmle.label | xs | -| test.cpp:520:14:520:27 | new[] | semmle.label | new[] | -| test.cpp:526:5:526:6 | xs | semmle.label | xs | -| test.cpp:532:14:532:27 | new[] | semmle.label | new[] | -| test.cpp:537:5:537:6 | xs | semmle.label | xs | +| test.cpp:486:7:486:15 | ... = ... | semmle.label | ... = ... | | test.cpp:543:14:543:27 | new[] | semmle.label | new[] | -| test.cpp:548:5:548:6 | xs | semmle.label | xs | -| test.cpp:548:5:548:15 | access to array | semmle.label | access to array | -| test.cpp:548:5:548:19 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:548:5:548:19 | ... = ... | semmle.label | ... = ... | | test.cpp:554:14:554:27 | new[] | semmle.label | new[] | -| test.cpp:559:5:559:6 | xs | semmle.label | xs | -| test.cpp:559:5:559:15 | access to array | semmle.label | access to array | -| test.cpp:559:5:559:19 | Store: ... = ... | semmle.label | Store: ... = ... | -| test.cpp:565:14:565:27 | new[] | semmle.label | new[] | -| test.cpp:570:5:570:6 | xs | semmle.label | xs | -| test.cpp:576:14:576:27 | new[] | semmle.label | new[] | -| test.cpp:581:5:581:6 | xs | semmle.label | xs | -| test.cpp:587:14:587:31 | new[] | semmle.label | new[] | -| test.cpp:592:5:592:6 | xs | semmle.label | xs | -| test.cpp:598:14:598:31 | new[] | semmle.label | new[] | -| test.cpp:603:5:603:6 | xs | semmle.label | xs | -| test.cpp:609:14:609:31 | new[] | semmle.label | new[] | -| test.cpp:614:5:614:6 | xs | semmle.label | xs | -| test.cpp:620:14:620:31 | new[] | semmle.label | new[] | -| test.cpp:625:5:625:6 | xs | semmle.label | xs | -| test.cpp:631:14:631:31 | new[] | semmle.label | new[] | -| test.cpp:636:5:636:6 | xs | semmle.label | xs | +| test.cpp:559:5:559:19 | ... = ... | semmle.label | ... = ... | | test.cpp:642:14:642:31 | new[] | semmle.label | new[] | -| test.cpp:647:5:647:6 | xs | semmle.label | xs | -| test.cpp:647:5:647:15 | access to array | semmle.label | access to array | -| test.cpp:647:5:647:19 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:647:5:647:19 | ... = ... | semmle.label | ... = ... | | test.cpp:652:14:652:27 | new[] | semmle.label | new[] | -| test.cpp:653:16:653:17 | xs | semmle.label | xs | -| test.cpp:656:3:656:4 | xs | semmle.label | xs | | test.cpp:656:3:656:6 | ... ++ | semmle.label | ... ++ | | test.cpp:656:3:656:6 | ... ++ | semmle.label | ... ++ | -| test.cpp:656:3:656:6 | ... ++ | semmle.label | ... ++ | -| test.cpp:656:3:656:6 | ... ++ | semmle.label | ... ++ | -| test.cpp:657:7:657:8 | xs | semmle.label | xs | -| test.cpp:662:3:662:11 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:662:3:662:11 | ... = ... | semmle.label | ... = ... | | test.cpp:667:14:667:31 | new[] | semmle.label | new[] | -| test.cpp:675:7:675:8 | xs | semmle.label | xs | -| test.cpp:675:7:675:19 | access to array | semmle.label | access to array | -| test.cpp:675:7:675:23 | Store: ... = ... | semmle.label | Store: ... = ... | +| test.cpp:675:7:675:23 | ... = ... | semmle.label | ... = ... | subpaths #select -| test.cpp:6:14:6:15 | Load: * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:6:14:6:15 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size | -| test.cpp:8:14:8:21 | Load: * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:8:14:8:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size | -| test.cpp:20:14:20:21 | Load: * ... | test.cpp:16:15:16:20 | call to malloc | test.cpp:20:14:20:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:16:15:16:20 | call to malloc | call to malloc | test.cpp:17:19:17:22 | size | size | -| test.cpp:30:14:30:15 | Load: * ... | test.cpp:28:15:28:20 | call to malloc | test.cpp:30:14:30:15 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:28:15:28:20 | call to malloc | call to malloc | test.cpp:29:20:29:27 | ... + ... | ... + ... | -| test.cpp:32:14:32:21 | Load: * ... | test.cpp:28:15:28:20 | call to malloc | test.cpp:32:14:32:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:28:15:28:20 | call to malloc | call to malloc | test.cpp:29:20:29:27 | ... + ... | ... + ... | -| test.cpp:42:14:42:15 | Load: * ... | test.cpp:40:15:40:20 | call to malloc | test.cpp:42:14:42:15 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:40:15:40:20 | call to malloc | call to malloc | test.cpp:41:20:41:27 | ... - ... | ... - ... | -| test.cpp:44:14:44:21 | Load: * ... | test.cpp:40:15:40:20 | call to malloc | test.cpp:44:14:44:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:40:15:40:20 | call to malloc | call to malloc | test.cpp:41:20:41:27 | ... - ... | ... - ... | -| test.cpp:67:9:67:14 | Store: ... = ... | test.cpp:52:19:52:24 | call to malloc | test.cpp:67:9:67:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:52:19:52:24 | call to malloc | call to malloc | test.cpp:53:20:53:23 | size | size | -| test.cpp:96:9:96:14 | Store: ... = ... | test.cpp:82:17:82:22 | call to malloc | test.cpp:96:9:96:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:82:17:82:22 | call to malloc | call to malloc | test.cpp:83:27:83:30 | size | size | -| test.cpp:110:9:110:14 | Store: ... = ... | test.cpp:82:17:82:22 | call to malloc | test.cpp:110:9:110:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:82:17:82:22 | call to malloc | call to malloc | test.cpp:83:27:83:30 | size | size | -| test.cpp:157:9:157:14 | Store: ... = ... | test.cpp:143:18:143:23 | call to malloc | test.cpp:157:9:157:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:143:18:143:23 | call to malloc | call to malloc | test.cpp:144:29:144:32 | size | size | -| test.cpp:171:9:171:14 | Store: ... = ... | test.cpp:143:18:143:23 | call to malloc | test.cpp:171:9:171:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:143:18:143:23 | call to malloc | call to malloc | test.cpp:144:29:144:32 | size | size | -| test.cpp:201:5:201:19 | Store: ... = ... | test.cpp:194:23:194:28 | call to malloc | test.cpp:201:5:201:19 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:194:23:194:28 | call to malloc | call to malloc | test.cpp:195:21:195:23 | len | len | -| test.cpp:213:5:213:13 | Store: ... = ... | test.cpp:205:23:205:28 | call to malloc | test.cpp:213:5:213:13 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:205:23:205:28 | call to malloc | call to malloc | test.cpp:206:21:206:23 | len | len | -| test.cpp:232:3:232:20 | Store: ... = ... | test.cpp:231:18:231:30 | new[] | test.cpp:232:3:232:20 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:231:18:231:30 | new[] | new[] | test.cpp:232:11:232:15 | index | index | -| test.cpp:239:5:239:22 | Store: ... = ... | test.cpp:238:20:238:32 | new[] | test.cpp:239:5:239:22 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:238:20:238:32 | new[] | new[] | test.cpp:239:13:239:17 | index | index | -| test.cpp:254:9:254:16 | Store: ... = ... | test.cpp:248:24:248:30 | call to realloc | test.cpp:254:9:254:16 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:248:24:248:30 | call to realloc | call to realloc | test.cpp:254:11:254:11 | i | i | -| test.cpp:264:13:264:14 | Load: * ... | test.cpp:260:13:260:24 | new[] | test.cpp:264:13:264:14 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:260:13:260:24 | new[] | new[] | test.cpp:261:19:261:21 | len | len | -| test.cpp:274:5:274:10 | Store: ... = ... | test.cpp:270:13:270:24 | new[] | test.cpp:274:5:274:10 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:270:13:270:24 | new[] | new[] | test.cpp:271:19:271:21 | len | len | -| test.cpp:308:5:308:29 | Store: ... = ... | test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:29 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:304:15:304:26 | new[] | new[] | test.cpp:308:8:308:10 | ... + ... | ... + ... | -| test.cpp:358:14:358:26 | Load: * ... | test.cpp:355:14:355:27 | new[] | test.cpp:358:14:358:26 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:355:14:355:27 | new[] | new[] | test.cpp:356:20:356:23 | size | size | -| test.cpp:359:14:359:32 | Load: * ... | test.cpp:355:14:355:27 | new[] | test.cpp:359:14:359:32 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 2. | test.cpp:355:14:355:27 | new[] | new[] | test.cpp:356:20:356:23 | size | size | -| test.cpp:384:13:384:16 | Load: * ... | test.cpp:377:14:377:27 | new[] | test.cpp:384:13:384:16 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:377:14:377:27 | new[] | new[] | test.cpp:378:20:378:23 | size | size | -| test.cpp:415:7:415:15 | Store: ... = ... | test.cpp:410:14:410:27 | new[] | test.cpp:415:7:415:15 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:410:14:410:27 | new[] | new[] | test.cpp:411:19:411:22 | size | size | -| test.cpp:426:7:426:15 | Store: ... = ... | test.cpp:421:14:421:27 | new[] | test.cpp:426:7:426:15 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:421:14:421:27 | new[] | new[] | test.cpp:422:19:422:22 | size | size | -| test.cpp:438:7:438:15 | Store: ... = ... | test.cpp:432:14:432:27 | new[] | test.cpp:438:7:438:15 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:432:14:432:27 | new[] | new[] | test.cpp:433:19:433:22 | size | size | -| test.cpp:450:7:450:15 | Store: ... = ... | test.cpp:444:14:444:27 | new[] | test.cpp:450:7:450:15 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:444:14:444:27 | new[] | new[] | test.cpp:445:19:445:22 | size | size | -| test.cpp:486:7:486:15 | Store: ... = ... | test.cpp:480:14:480:27 | new[] | test.cpp:486:7:486:15 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@ + 498. | test.cpp:480:14:480:27 | new[] | new[] | test.cpp:481:19:481:22 | size | size | -| test.cpp:548:5:548:19 | Store: ... = ... | test.cpp:543:14:543:27 | new[] | test.cpp:548:5:548:19 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:543:14:543:27 | new[] | new[] | test.cpp:548:8:548:14 | src_pos | src_pos | -| test.cpp:559:5:559:19 | Store: ... = ... | test.cpp:554:14:554:27 | new[] | test.cpp:559:5:559:19 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:554:14:554:27 | new[] | new[] | test.cpp:559:8:559:14 | src_pos | src_pos | -| test.cpp:647:5:647:19 | Store: ... = ... | test.cpp:642:14:642:31 | new[] | test.cpp:647:5:647:19 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:642:14:642:31 | new[] | new[] | test.cpp:647:8:647:14 | src_pos | src_pos | -| test.cpp:662:3:662:11 | Store: ... = ... | test.cpp:652:14:652:27 | new[] | test.cpp:662:3:662:11 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:652:14:652:27 | new[] | new[] | test.cpp:653:19:653:22 | size | size | -| test.cpp:675:7:675:23 | Store: ... = ... | test.cpp:667:14:667:31 | new[] | test.cpp:675:7:675:23 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:667:14:667:31 | new[] | new[] | test.cpp:675:10:675:18 | ... ++ | ... ++ | +| test.cpp:6:14:6:15 | * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:6:14:6:15 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size | +| test.cpp:8:14:8:21 | * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:8:14:8:21 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size | +| test.cpp:20:14:20:21 | * ... | test.cpp:16:15:16:20 | call to malloc | test.cpp:20:14:20:21 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:16:15:16:20 | call to malloc | call to malloc | test.cpp:17:19:17:22 | size | size | +| test.cpp:30:14:30:15 | * ... | test.cpp:28:15:28:20 | call to malloc | test.cpp:30:14:30:15 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:28:15:28:20 | call to malloc | call to malloc | test.cpp:29:20:29:27 | ... + ... | ... + ... | +| test.cpp:32:14:32:21 | * ... | test.cpp:28:15:28:20 | call to malloc | test.cpp:32:14:32:21 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:28:15:28:20 | call to malloc | call to malloc | test.cpp:29:20:29:27 | ... + ... | ... + ... | +| test.cpp:42:14:42:15 | * ... | test.cpp:40:15:40:20 | call to malloc | test.cpp:42:14:42:15 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:40:15:40:20 | call to malloc | call to malloc | test.cpp:41:20:41:27 | ... - ... | ... - ... | +| test.cpp:44:14:44:21 | * ... | test.cpp:40:15:40:20 | call to malloc | test.cpp:44:14:44:21 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:40:15:40:20 | call to malloc | call to malloc | test.cpp:41:20:41:27 | ... - ... | ... - ... | +| test.cpp:67:9:67:14 | ... = ... | test.cpp:52:19:52:24 | call to malloc | test.cpp:67:9:67:14 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:52:19:52:24 | call to malloc | call to malloc | test.cpp:53:20:53:23 | size | size | +| test.cpp:96:9:96:14 | ... = ... | test.cpp:82:17:82:22 | call to malloc | test.cpp:96:9:96:14 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:82:17:82:22 | call to malloc | call to malloc | test.cpp:83:27:83:30 | size | size | +| test.cpp:110:9:110:14 | ... = ... | test.cpp:82:17:82:22 | call to malloc | test.cpp:110:9:110:14 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:82:17:82:22 | call to malloc | call to malloc | test.cpp:83:27:83:30 | size | size | +| test.cpp:157:9:157:14 | ... = ... | test.cpp:143:18:143:23 | call to malloc | test.cpp:157:9:157:14 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:143:18:143:23 | call to malloc | call to malloc | test.cpp:144:29:144:32 | size | size | +| test.cpp:171:9:171:14 | ... = ... | test.cpp:143:18:143:23 | call to malloc | test.cpp:171:9:171:14 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:143:18:143:23 | call to malloc | call to malloc | test.cpp:144:29:144:32 | size | size | +| test.cpp:201:5:201:19 | ... = ... | test.cpp:194:23:194:28 | call to malloc | test.cpp:201:5:201:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:194:23:194:28 | call to malloc | call to malloc | test.cpp:195:21:195:23 | len | len | +| test.cpp:213:5:213:13 | ... = ... | test.cpp:205:23:205:28 | call to malloc | test.cpp:213:5:213:13 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:205:23:205:28 | call to malloc | call to malloc | test.cpp:206:21:206:23 | len | len | +| test.cpp:232:3:232:20 | ... = ... | test.cpp:231:18:231:30 | new[] | test.cpp:232:3:232:20 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:231:18:231:30 | new[] | new[] | test.cpp:232:11:232:15 | index | index | +| test.cpp:239:5:239:22 | ... = ... | test.cpp:238:20:238:32 | new[] | test.cpp:239:5:239:22 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:238:20:238:32 | new[] | new[] | test.cpp:239:13:239:17 | index | index | +| test.cpp:254:9:254:16 | ... = ... | test.cpp:248:24:248:30 | call to realloc | test.cpp:254:9:254:16 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:248:24:248:30 | call to realloc | call to realloc | test.cpp:254:11:254:11 | i | i | +| test.cpp:264:13:264:14 | * ... | test.cpp:260:13:260:24 | new[] | test.cpp:264:13:264:14 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:260:13:260:24 | new[] | new[] | test.cpp:261:19:261:21 | len | len | +| test.cpp:274:5:274:10 | ... = ... | test.cpp:270:13:270:24 | new[] | test.cpp:274:5:274:10 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:270:13:270:24 | new[] | new[] | test.cpp:271:19:271:21 | len | len | +| test.cpp:308:5:308:29 | ... = ... | test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:29 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:304:15:304:26 | new[] | new[] | test.cpp:308:8:308:10 | ... + ... | ... + ... | +| test.cpp:358:14:358:26 | * ... | test.cpp:355:14:355:27 | new[] | test.cpp:358:14:358:26 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:355:14:355:27 | new[] | new[] | test.cpp:356:20:356:23 | size | size | +| test.cpp:359:14:359:32 | * ... | test.cpp:355:14:355:27 | new[] | test.cpp:359:14:359:32 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 2. | test.cpp:355:14:355:27 | new[] | new[] | test.cpp:356:20:356:23 | size | size | +| test.cpp:384:13:384:16 | * ... | test.cpp:377:14:377:27 | new[] | test.cpp:384:13:384:16 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:377:14:377:27 | new[] | new[] | test.cpp:378:20:378:23 | size | size | +| test.cpp:415:7:415:15 | ... = ... | test.cpp:410:14:410:27 | new[] | test.cpp:415:7:415:15 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:410:14:410:27 | new[] | new[] | test.cpp:411:19:411:22 | size | size | +| test.cpp:426:7:426:15 | ... = ... | test.cpp:421:14:421:27 | new[] | test.cpp:426:7:426:15 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:421:14:421:27 | new[] | new[] | test.cpp:422:19:422:22 | size | size | +| test.cpp:438:7:438:15 | ... = ... | test.cpp:432:14:432:27 | new[] | test.cpp:438:7:438:15 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:432:14:432:27 | new[] | new[] | test.cpp:433:19:433:22 | size | size | +| test.cpp:450:7:450:15 | ... = ... | test.cpp:444:14:444:27 | new[] | test.cpp:450:7:450:15 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:444:14:444:27 | new[] | new[] | test.cpp:445:19:445:22 | size | size | +| test.cpp:486:7:486:15 | ... = ... | test.cpp:480:14:480:27 | new[] | test.cpp:486:7:486:15 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@ + 498. | test.cpp:480:14:480:27 | new[] | new[] | test.cpp:481:19:481:22 | size | size | +| test.cpp:548:5:548:19 | ... = ... | test.cpp:543:14:543:27 | new[] | test.cpp:548:5:548:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:543:14:543:27 | new[] | new[] | test.cpp:548:8:548:14 | src_pos | src_pos | +| test.cpp:559:5:559:19 | ... = ... | test.cpp:554:14:554:27 | new[] | test.cpp:559:5:559:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:554:14:554:27 | new[] | new[] | test.cpp:559:8:559:14 | src_pos | src_pos | +| test.cpp:647:5:647:19 | ... = ... | test.cpp:642:14:642:31 | new[] | test.cpp:647:5:647:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:642:14:642:31 | new[] | new[] | test.cpp:647:8:647:14 | src_pos | src_pos | +| test.cpp:662:3:662:11 | ... = ... | test.cpp:652:14:652:27 | new[] | test.cpp:662:3:662:11 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:652:14:652:27 | new[] | new[] | test.cpp:653:19:653:22 | size | size | +| test.cpp:675:7:675:23 | ... = ... | test.cpp:667:14:667:31 | new[] | test.cpp:675:7:675:23 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:667:14:667:31 | new[] | new[] | test.cpp:675:10:675:18 | ... ++ | ... ++ | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerToDereference.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerToDereference.expected new file mode 100644 index 00000000000..48de9172b36 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerToDereference.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerToDereference.ql b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerToDereference.ql new file mode 100644 index 00000000000..c4d9be5cb8b --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerToDereference.ql @@ -0,0 +1,81 @@ +import cpp +import semmle.code.cpp.security.InvalidPointerDereference.InvalidPointerToDereference +import TestUtilities.InlineExpectationsTest +import semmle.code.cpp.ir.IR +import semmle.code.cpp.dataflow.new.DataFlow + +string case3(DataFlow::Node derefSource, DataFlow::Node derefSink, DataFlow::Node operation) { + operationIsOffBy(_, _, derefSource, derefSink, _, operation, _) and + not exists(case2(_, _, operation)) and + not exists(case1(_, _, operation)) and + exists(int derefSourceLine, int derefSinkLine, int operationLine | + derefSourceLine = derefSource.getLocation().getStartLine() and + derefSinkLine = derefSink.getLocation().getStartLine() and + operationLine = operation.getLocation().getStartLine() and + derefSourceLine != derefSinkLine and + derefSinkLine != operationLine and + result = "L" + derefSourceLine + "->L" + derefSinkLine + "->L" + operationLine + ) +} + +string case2(DataFlow::Node derefSource, DataFlow::Node derefSink, DataFlow::Node operation) { + operationIsOffBy(_, _, derefSource, derefSink, _, operation, _) and + not exists(case1(_, _, operation)) and + exists(int derefSourceLine, int derefSinkLine, int operationLine | + derefSourceLine = derefSource.getLocation().getStartLine() and + derefSinkLine = derefSink.getLocation().getStartLine() and + operationLine = operation.getLocation().getStartLine() and + derefSourceLine = derefSinkLine and + derefSinkLine != operationLine and + result = "L" + derefSourceLine + "->L" + operationLine + ) +} + +string case1(DataFlow::Node derefSource, DataFlow::Node derefSink, DataFlow::Node operation) { + operationIsOffBy(_, _, derefSource, derefSink, _, operation, _) and + exists(int derefSourceLine, int derefSinkLine, int operationLine | + derefSourceLine = derefSource.getLocation().getStartLine() and + derefSinkLine = derefSink.getLocation().getStartLine() and + operationLine = operation.getLocation().getStartLine() and + derefSourceLine = derefSinkLine and + derefSinkLine = operationLine and + result = "L" + derefSourceLine + ) +} + +module InvalidPointerToDereferenceTest implements TestSig { + string getARelevantTag() { result = "deref" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists( + DataFlow::Node derefSource, DataFlow::Node derefSink, DataFlow::Node operation, int delta, + string value1, string value2 + | + operationIsOffBy(_, _, derefSource, derefSink, _, operation, delta) and + location = operation.getLocation() and + element = operation.toString() and + tag = "deref" and + value = value1 + value2 + | + ( + value1 = case3(derefSource, derefSink, operation) + or + value1 = case2(derefSource, derefSink, operation) + or + value1 = case1(derefSource, derefSink, operation) + ) and + ( + delta > 0 and + value2 = "+" + delta + or + delta = 0 and + value2 = "" + or + delta < 0 and + value2 = "-" + (-delta) + ) + ) + } +} + +import MakeTest diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp index 13e373bac10..cfcbb9de9a5 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp @@ -2,10 +2,10 @@ char *malloc(int size); void test1(int size) { char* p = malloc(size); - char* q = p + size; - char a = *q; // BAD + char* q = p + size; // $ alloc=L4 + char a = *q; // $ deref=L6 // BAD char b = *(q - 1); // GOOD - char c = *(q + 1); // BAD + char c = *(q + 1); // $ deref=L8+1 // BAD char d = *(q + size); // BAD [NOT DETECTED] char e = *(q - size); // GOOD char f = *(q + size + 1); // BAD [NOT DETECTED] @@ -14,10 +14,10 @@ void test1(int size) { void test2(int size) { char* p = malloc(size); - char* q = p + size - 1; + char* q = p + size - 1; // $ alloc=L16 char a = *q; // GOOD char b = *(q - 1); // GOOD - char c = *(q + 1); // BAD + char c = *(q + 1); // $ deref=L20 // BAD char d = *(q + size); // BAD [NOT DETECTED] char e = *(q - size); // GOOD char f = *(q + size + 1); // BAD [NOT DETECTED] @@ -26,10 +26,10 @@ void test2(int size) { void test3(int size) { char* p = malloc(size + 1); - char* q = p + (size + 1); - char a = *q; // BAD + char* q = p + (size + 1); // $ alloc=L28+1 + char a = *q; // $ deref=L30 // BAD char b = *(q - 1); // GOOD - char c = *(q + 1); // BAD + char c = *(q + 1); // $ deref=L32+1 // BAD char d = *(q + size); // BAD [NOT DETECTED] char e = *(q - size); // GOOD char f = *(q + size + 1); // BAD [NOT DETECTED] @@ -38,10 +38,10 @@ void test3(int size) { void test4(int size) { char* p = malloc(size - 1); - char* q = p + (size - 1); - char a = *q; // BAD + char* q = p + (size - 1); // $ alloc=L40-1 + char a = *q; // $ deref=L42 // BAD char b = *(q - 1); // GOOD - char c = *(q + 1); // BAD + char c = *(q + 1); // $ deref=L44+1 // BAD char d = *(q + size); // BAD [NOT DETECTED] char e = *(q - size); // GOOD char f = *(q + size + 1); // BAD [NOT DETECTED] @@ -50,7 +50,7 @@ void test4(int size) { char* mk_array(int size, char** end) { char* begin = malloc(size); - *end = begin + size; + *end = begin + size; // $ alloc=L52 return begin; } @@ -64,7 +64,7 @@ void test5(int size) { } for (char* p = begin; p <= end; ++p) { - *p = 0; // BAD + *p = 0; // $ deref=L53->L62->L67 deref=L53->L66->L67 // BAD } for (char* p = begin; p < end; ++p) { @@ -80,7 +80,7 @@ struct array_t { array_t mk_array(int size) { array_t arr; arr.begin = malloc(size); - arr.end = arr.begin + size; + arr.end = arr.begin + size; // $ alloc=L82 return arr; } @@ -93,7 +93,7 @@ void test6(int size) { } for (char* p = arr.begin; p <= arr.end; ++p) { - *p = 0; // BAD + *p = 0; // $ deref=L83->L91->L96 deref=L83->L95->L96 // BAD } for (char* p = arr.begin; p < arr.end; ++p) { @@ -107,7 +107,7 @@ void test7_callee(array_t arr) { } for (char* p = arr.begin; p <= arr.end; ++p) { - *p = 0; // BAD + *p = 0; // $ deref=L83->L105->L110 deref=L83->L109->L110 // BAD } for (char* p = arr.begin; p < arr.end; ++p) { @@ -123,7 +123,7 @@ void test8(int size) { array_t arr; char* p = malloc(size); arr.begin = p; - arr.end = p + size; + arr.end = p + size; // $ alloc=L124 for (int i = 0; i < arr.end - arr.begin; i++) { *(arr.begin + i) = 0; // GOOD @@ -141,7 +141,7 @@ void test8(int size) { array_t *mk_array_p(int size) { array_t *arr = (array_t*) malloc(sizeof(array_t)); arr->begin = malloc(size); - arr->end = arr->begin + size; + arr->end = arr->begin + size; // $ alloc=L143 return arr; } @@ -154,7 +154,7 @@ void test9(int size) { } for (char* p = arr->begin; p <= arr->end; ++p) { - *p = 0; // BAD + *p = 0; // $ deref=L144->L156->L157 // BAD } for (char* p = arr->begin; p < arr->end; ++p) { @@ -168,7 +168,7 @@ void test10_callee(array_t *arr) { } for (char* p = arr->begin; p <= arr->end; ++p) { - *p = 0; // BAD + *p = 0; // $ deref=L144->L166->L171 deref=L144->L170->L171 // BAD } for (char* p = arr->begin; p < arr->end; ++p) { @@ -186,31 +186,31 @@ void deref_plus_one(char* q) { void test11(unsigned size) { char *p = malloc(size); - char *q = p + size - 1; + char *q = p + size - 1; // $ alloc=L188 deref_plus_one(q); } void test12(unsigned len, unsigned index) { char* p = (char *)malloc(len); - char* end = p + len; + char* end = p + len; // $ alloc=L194 if(p + index > end) { return; } - p[index] = '\0'; // BAD + p[index] = '\0'; // $ deref=L201 // BAD } void test13(unsigned len, unsigned index) { char* p = (char *)malloc(len); - char* end = p + len; + char* end = p + len; // $ alloc=L205 char* q = p + index; if(q > end) { return; } - *q = '\0'; // BAD + *q = '\0'; // $ deref=L213 // BAD } bool unknown(); @@ -229,14 +229,14 @@ void test15(unsigned index) { return; } int* newname = new int[size]; - newname[index] = 0; // GOOD [FALSE POSITIVE] + newname[index] = 0; // $ alloc=L231 deref=L232 // GOOD [FALSE POSITIVE] } void test16(unsigned index) { unsigned size = index + 13; if(size >= index) { int* newname = new int[size]; - newname[index] = 0; // GOOD [FALSE POSITIVE] + newname[index] = 0; // $ alloc=L238 deref=L239 // GOOD [FALSE POSITIVE] } } @@ -251,34 +251,34 @@ void test17(unsigned *p, unsigned x, unsigned k) { // The following access is okay because: // n = 3*p[0] + k >= p[0] + k >= p[1] + k > p[1] = i // (where p[0] denotes the original value for p[0]) - p[i] = x; // GOOD [FALSE POSITIVE] + p[i] = x; // $ alloc=L248 deref=L254 // GOOD [FALSE POSITIVE] } } void test17(unsigned len) { int *xs = new int[len]; - int *end = xs + len; + int *end = xs + len; // $ alloc=L260 for (int *x = xs; x <= end; x++) { - int i = *x; // BAD + int i = *x; // $ deref=L264 // BAD } } void test18(unsigned len) { int *xs = new int[len]; - int *end = xs + len; + int *end = xs + len; // $ alloc=L270 for (int *x = xs; x <= end; x++) { - *x = 0; // BAD + *x = 0; // $ deref=L274 // BAD } } void test19(unsigned len) { int *xs = new int[len]; - int *end = xs + len; + int *end = xs + len; // $ alloc=L280 for (int *x = xs; x < end; x++) { int i = *x; // GOOD @@ -288,7 +288,7 @@ void test19(unsigned len) void test20(unsigned len) { int *xs = new int[len]; - int *end = xs + len; + int *end = xs + len; // $ alloc=L290 for (int *x = xs; x < end; x++) { *x = 0; // GOOD @@ -305,13 +305,13 @@ void test21() { for (int i = 0; i < n; i += 2) { xs[i] = test21_get(i); // GOOD - xs[i+1] = test21_get(i+1); // GOOD [FALSE POSITIVE] + xs[i+1] = test21_get(i+1); // $ alloc=L304 alloc=L304-1 deref=L308 // GOOD [FALSE POSITIVE] } } void test22(unsigned size, int val) { char *xs = new char[size]; - char *end = xs + size; // GOOD + char *end = xs + size; // $ alloc=L313 // GOOD char **current = &end; do { if (*current - xs < 1) // GOOD @@ -323,7 +323,7 @@ void test22(unsigned size, int val) { void test23(unsigned size, int val) { char *xs = new char[size]; - char *end = xs + size; + char *end = xs + size; // $ alloc=L325 char **current = &end; if (val < 1) { @@ -345,7 +345,7 @@ void test23(unsigned size, int val) { void test24(unsigned size) { char *xs = new char[size]; - char *end = xs + size; + char *end = xs + size; // $ alloc=L347 if (xs < end) { int val = *xs++; // GOOD } @@ -353,16 +353,16 @@ void test24(unsigned size) { void test25(unsigned size) { char *xs = new char[size]; - char *end = xs + size; + char *end = xs + size; // $ alloc=L355 char *end_plus_one = end + 1; - int val1 = *end_plus_one; // BAD - int val2 = *(end_plus_one + 1); // BAD + int val1 = *end_plus_one; // $ deref=L358+1 // BAD + int val2 = *(end_plus_one + 1); // $ deref=L359+2 // BAD } void test26(unsigned size) { char *xs = new char[size]; char *p = xs; - char *end = p + size; + char *end = p + size; // $ alloc=L363 if (p + 4 <= end) { p += 4; @@ -375,18 +375,18 @@ void test26(unsigned size) { void test27(unsigned size, bool b) { char *xs = new char[size]; - char *end = xs + size; + char *end = xs + size; // $ alloc=L377 if (b) { end++; } - int val = *end; // BAD + int val = *end; // $ deref=L384+1 // BAD } void test28(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L388 if (xs >= end) return; xs++; @@ -397,7 +397,7 @@ void test28(unsigned size) { void test28_simple(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L399 if (xs < end) { xs++; if (xs < end) { @@ -408,46 +408,46 @@ void test28_simple(unsigned size) { void test28_simple2(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L410 if (xs < end) { xs++; if (xs < end + 1) { - xs[0] = 0; // BAD + xs[0] = 0; // $ deref=L415 // BAD } } } void test28_simple3(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L421 if (xs < end) { xs++; if (xs - 1 < end) { - xs[0] = 0; // BAD + xs[0] = 0; // $ deref=L426 // BAD } } } void test28_simple4(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L432 if (xs < end) { end++; xs++; if (xs < end) { - xs[0] = 0; // BAD + xs[0] = 0; // $ deref=L438 // BAD } } } void test28_simple5(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L444 end++; if (xs < end) { xs++; if (xs < end) { - xs[0] = 0; // BAD + xs[0] = 0; // $ deref=L450 // BAD } } } @@ -466,7 +466,7 @@ void test28_simple6(unsigned size) { void test28_simple7(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L468 end++; if (xs < end) { xs++; @@ -478,12 +478,12 @@ void test28_simple7(unsigned size) { void test28_simple8(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L480 end += 500; if (xs < end) { xs++; if (xs < end - 1) { - xs[0] = 0; // BAD + xs[0] = 0; // $ deref=L486+498 // BAD } } } @@ -545,7 +545,7 @@ void test31_simple2(unsigned size, unsigned src_pos) src_pos = size; } if (src_pos < size + 1) { - xs[src_pos] = 0; // BAD + xs[src_pos] = 0; // $ alloc=L543 deref=L548 // BAD } } @@ -556,7 +556,7 @@ void test31_simple3(unsigned size, unsigned src_pos) src_pos = size; } if (src_pos - 1 < size) { - xs[src_pos] = 0; // BAD + xs[src_pos] = 0; // $ alloc=L554 deref=L559 // BAD } } @@ -644,13 +644,13 @@ void test31_simple1_sub1(unsigned size, unsigned src_pos) src_pos = size; } if (src_pos < size) { - xs[src_pos] = 0; // BAD + xs[src_pos] = 0; // $ alloc=L642-1 deref=L647 // BAD } } void test32(unsigned size) { char *xs = new char[size]; - char *end = &xs[size]; + char *end = &xs[size]; // $ alloc=L652 if (xs >= end) return; xs++; @@ -659,7 +659,7 @@ void test32(unsigned size) { xs++; if (xs >= end) return; - xs[0] = 0; // GOOD [FALSE POSITIVE] + xs[0] = 0; // $ deref=L656->L662+1 deref=L657->L662+1 GOOD [FALSE POSITIVE] } void test33(unsigned size, unsigned src_pos) @@ -672,6 +672,21 @@ void test33(unsigned size, unsigned src_pos) while (dst_pos < size - 1) { dst_pos++; if (true) - xs[dst_pos++] = 0; // GOOD [FALSE POSITIVE] + xs[dst_pos++] = 0; // $ alloc=L667+1 deref=L675 // GOOD [FALSE POSITIVE] } } + +int* pointer_arithmetic(int *p, int offset) { + return p + offset; // $ alloc=L684 +} + +void test_missing_call_context_1(unsigned size) { + int* p = new int[size]; + int* end = pointer_arithmetic(p, size); +} + +void test_missing_call_context_2(unsigned size) { + int* p = new int[size]; + int* end_minus_one = pointer_arithmetic(p, size - 1); + *end_minus_one = '0'; // $ deref=L680->L690->L691 // GOOD +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/controlflow/guards-ir/test.c b/cpp/ql/test/library-tests/controlflow/guards-ir/test.c index fd2d7f08f93..9bb5e91805d 100644 --- a/cpp/ql/test/library-tests/controlflow/guards-ir/test.c +++ b/cpp/ql/test/library-tests/controlflow/guards-ir/test.c @@ -151,3 +151,19 @@ void test5(int x) { void test6(int x, int y) { return x && y; } + +int ptr_test(int *x, int *y) { + if (x == y + 42) { + } + + if (x == y - 42) { + } + + if (x < y + 42) { + } + + if (x < y - 42) { + } + + return 0; +} diff --git a/cpp/ql/test/library-tests/controlflow/guards-ir/tests.expected b/cpp/ql/test/library-tests/controlflow/guards-ir/tests.expected index 524a74155c0..01b46187b69 100644 --- a/cpp/ql/test/library-tests/controlflow/guards-ir/tests.expected +++ b/cpp/ql/test/library-tests/controlflow/guards-ir/tests.expected @@ -30,6 +30,10 @@ astGuards | test.c:152:10:152:10 | x | | test.c:152:10:152:15 | ... && ... | | test.c:152:15:152:15 | y | +| test.c:156:9:156:19 | ... == ... | +| test.c:159:9:159:19 | ... == ... | +| test.c:162:9:162:18 | ... < ... | +| test.c:165:9:165:18 | ... < ... | | test.cpp:18:8:18:10 | call to get | | test.cpp:31:7:31:13 | ... == ... | | test.cpp:42:13:42:20 | call to getABool | @@ -122,6 +126,38 @@ astGuardsCompare | 109 | y < 0+0 when ... < ... is true | | 109 | y >= 0+0 when ... < ... is false | | 109 | y >= 0+0 when ... \|\| ... is false | +| 156 | ... + ... != x+0 when ... == ... is false | +| 156 | ... + ... == x+0 when ... == ... is true | +| 156 | x != ... + ...+0 when ... == ... is false | +| 156 | x != y+42 when ... == ... is false | +| 156 | x == ... + ...+0 when ... == ... is true | +| 156 | x == y+42 when ... == ... is true | +| 156 | y != x+-42 when ... == ... is false | +| 156 | y == x+-42 when ... == ... is true | +| 159 | ... - ... != x+0 when ... == ... is false | +| 159 | ... - ... == x+0 when ... == ... is true | +| 159 | x != ... - ...+0 when ... == ... is false | +| 159 | x != y+-42 when ... == ... is false | +| 159 | x == ... - ...+0 when ... == ... is true | +| 159 | x == y+-42 when ... == ... is true | +| 159 | y != x+42 when ... == ... is false | +| 159 | y == x+42 when ... == ... is true | +| 162 | ... + ... < x+1 when ... < ... is false | +| 162 | ... + ... >= x+1 when ... < ... is true | +| 162 | x < ... + ...+0 when ... < ... is true | +| 162 | x < y+42 when ... < ... is true | +| 162 | x >= ... + ...+0 when ... < ... is false | +| 162 | x >= y+42 when ... < ... is false | +| 162 | y < x+-41 when ... < ... is false | +| 162 | y >= x+-41 when ... < ... is true | +| 165 | ... - ... < x+1 when ... < ... is false | +| 165 | ... - ... >= x+1 when ... < ... is true | +| 165 | x < ... - ...+0 when ... < ... is true | +| 165 | x < y+-42 when ... < ... is true | +| 165 | x >= ... - ...+0 when ... < ... is false | +| 165 | x >= y+-42 when ... < ... is false | +| 165 | y < x+43 when ... < ... is false | +| 165 | y >= x+43 when ... < ... is true | astGuardsControl | test.c:7:9:7:13 | ... > ... | false | 10 | 11 | | test.c:7:9:7:13 | ... > ... | true | 7 | 9 | @@ -208,6 +244,10 @@ astGuardsControl | test.c:152:10:152:10 | x | true | 152 | 152 | | test.c:152:10:152:15 | ... && ... | true | 151 | 152 | | test.c:152:15:152:15 | y | true | 151 | 152 | +| test.c:156:9:156:19 | ... == ... | true | 156 | 157 | +| test.c:159:9:159:19 | ... == ... | true | 159 | 160 | +| test.c:162:9:162:18 | ... < ... | true | 162 | 163 | +| test.c:165:9:165:18 | ... < ... | true | 165 | 166 | | test.cpp:18:8:18:10 | call to get | true | 19 | 19 | | test.cpp:31:7:31:13 | ... == ... | false | 30 | 30 | | test.cpp:31:7:31:13 | ... == ... | false | 34 | 34 | @@ -364,6 +404,22 @@ astGuardsEnsure | test.c:109:9:109:23 | ... \|\| ... | test.c:109:23:109:23 | 0 | < | test.c:109:19:109:19 | y | 1 | 113 | 113 | | test.c:109:19:109:23 | ... < ... | test.c:109:19:109:19 | y | >= | test.c:109:23:109:23 | 0 | 0 | 113 | 113 | | test.c:109:19:109:23 | ... < ... | test.c:109:23:109:23 | 0 | < | test.c:109:19:109:19 | y | 1 | 113 | 113 | +| test.c:156:9:156:19 | ... == ... | test.c:156:9:156:9 | x | == | test.c:156:14:156:14 | y | 42 | 156 | 157 | +| test.c:156:9:156:19 | ... == ... | test.c:156:9:156:9 | x | == | test.c:156:14:156:19 | ... + ... | 0 | 156 | 157 | +| test.c:156:9:156:19 | ... == ... | test.c:156:14:156:14 | y | == | test.c:156:9:156:9 | x | -42 | 156 | 157 | +| test.c:156:9:156:19 | ... == ... | test.c:156:14:156:19 | ... + ... | == | test.c:156:9:156:9 | x | 0 | 156 | 157 | +| test.c:159:9:159:19 | ... == ... | test.c:159:9:159:9 | x | == | test.c:159:14:159:14 | y | -42 | 159 | 160 | +| test.c:159:9:159:19 | ... == ... | test.c:159:9:159:9 | x | == | test.c:159:14:159:19 | ... - ... | 0 | 159 | 160 | +| test.c:159:9:159:19 | ... == ... | test.c:159:14:159:14 | y | == | test.c:159:9:159:9 | x | 42 | 159 | 160 | +| test.c:159:9:159:19 | ... == ... | test.c:159:14:159:19 | ... - ... | == | test.c:159:9:159:9 | x | 0 | 159 | 160 | +| test.c:162:9:162:18 | ... < ... | test.c:162:9:162:9 | x | < | test.c:162:13:162:13 | y | 42 | 162 | 163 | +| test.c:162:9:162:18 | ... < ... | test.c:162:9:162:9 | x | < | test.c:162:13:162:18 | ... + ... | 0 | 162 | 163 | +| test.c:162:9:162:18 | ... < ... | test.c:162:13:162:13 | y | >= | test.c:162:9:162:9 | x | -41 | 162 | 163 | +| test.c:162:9:162:18 | ... < ... | test.c:162:13:162:18 | ... + ... | >= | test.c:162:9:162:9 | x | 1 | 162 | 163 | +| test.c:165:9:165:18 | ... < ... | test.c:165:9:165:9 | x | < | test.c:165:13:165:13 | y | -42 | 165 | 166 | +| test.c:165:9:165:18 | ... < ... | test.c:165:9:165:9 | x | < | test.c:165:13:165:18 | ... - ... | 0 | 165 | 166 | +| test.c:165:9:165:18 | ... < ... | test.c:165:13:165:13 | y | >= | test.c:165:9:165:9 | x | 43 | 165 | 166 | +| test.c:165:9:165:18 | ... < ... | test.c:165:13:165:18 | ... - ... | >= | test.c:165:9:165:9 | x | 1 | 165 | 166 | | test.cpp:31:7:31:13 | ... == ... | test.cpp:31:7:31:7 | x | != | test.cpp:31:12:31:13 | - ... | 0 | 30 | 30 | | test.cpp:31:7:31:13 | ... == ... | test.cpp:31:7:31:7 | x | != | test.cpp:31:12:31:13 | - ... | 0 | 34 | 34 | | test.cpp:31:7:31:13 | ... == ... | test.cpp:31:7:31:7 | x | == | test.cpp:31:12:31:13 | - ... | 0 | 30 | 30 | @@ -397,6 +453,10 @@ irGuards | test.c:146:8:146:8 | Load: x | | test.c:152:10:152:10 | Load: x | | test.c:152:15:152:15 | Load: y | +| test.c:156:9:156:19 | CompareEQ: ... == ... | +| test.c:159:9:159:19 | CompareEQ: ... == ... | +| test.c:162:9:162:18 | CompareLT: ... < ... | +| test.c:165:9:165:18 | CompareLT: ... < ... | | test.cpp:18:8:18:12 | CompareNE: (bool)... | | test.cpp:31:7:31:13 | CompareEQ: ... == ... | | test.cpp:42:13:42:20 | Call: call to getABool | @@ -473,6 +533,38 @@ irGuardsCompare | 109 | x == 0+0 when CompareEQ: ... == ... is true | | 109 | y < 0+0 when CompareLT: ... < ... is true | | 109 | y >= 0+0 when CompareLT: ... < ... is false | +| 156 | ... + ... != x+0 when CompareEQ: ... == ... is false | +| 156 | ... + ... == x+0 when CompareEQ: ... == ... is true | +| 156 | x != ... + ...+0 when CompareEQ: ... == ... is false | +| 156 | x != y+42 when CompareEQ: ... == ... is false | +| 156 | x == ... + ...+0 when CompareEQ: ... == ... is true | +| 156 | x == y+42 when CompareEQ: ... == ... is true | +| 156 | y != x+-42 when CompareEQ: ... == ... is false | +| 156 | y == x+-42 when CompareEQ: ... == ... is true | +| 159 | ... - ... != x+0 when CompareEQ: ... == ... is false | +| 159 | ... - ... == x+0 when CompareEQ: ... == ... is true | +| 159 | x != ... - ...+0 when CompareEQ: ... == ... is false | +| 159 | x != y+-42 when CompareEQ: ... == ... is false | +| 159 | x == ... - ...+0 when CompareEQ: ... == ... is true | +| 159 | x == y+-42 when CompareEQ: ... == ... is true | +| 159 | y != x+42 when CompareEQ: ... == ... is false | +| 159 | y == x+42 when CompareEQ: ... == ... is true | +| 162 | ... + ... < x+1 when CompareLT: ... < ... is false | +| 162 | ... + ... >= x+1 when CompareLT: ... < ... is true | +| 162 | x < ... + ...+0 when CompareLT: ... < ... is true | +| 162 | x < y+42 when CompareLT: ... < ... is true | +| 162 | x >= ... + ...+0 when CompareLT: ... < ... is false | +| 162 | x >= y+42 when CompareLT: ... < ... is false | +| 162 | y < x+-41 when CompareLT: ... < ... is false | +| 162 | y >= x+-41 when CompareLT: ... < ... is true | +| 165 | ... - ... < x+1 when CompareLT: ... < ... is false | +| 165 | ... - ... >= x+1 when CompareLT: ... < ... is true | +| 165 | x < ... - ...+0 when CompareLT: ... < ... is true | +| 165 | x < y+-42 when CompareLT: ... < ... is true | +| 165 | x >= ... - ...+0 when CompareLT: ... < ... is false | +| 165 | x >= y+-42 when CompareLT: ... < ... is false | +| 165 | y < x+43 when CompareLT: ... < ... is false | +| 165 | y >= x+43 when CompareLT: ... < ... is true | irGuardsControl | test.c:7:9:7:13 | CompareGT: ... > ... | false | 11 | 11 | | test.c:7:9:7:13 | CompareGT: ... > ... | true | 8 | 8 | @@ -551,6 +643,10 @@ irGuardsControl | test.c:146:8:146:8 | Load: x | false | 147 | 147 | | test.c:152:10:152:10 | Load: x | true | 152 | 152 | | test.c:152:15:152:15 | Load: y | true | 152 | 152 | +| test.c:156:9:156:19 | CompareEQ: ... == ... | true | 156 | 157 | +| test.c:159:9:159:19 | CompareEQ: ... == ... | true | 159 | 160 | +| test.c:162:9:162:18 | CompareLT: ... < ... | true | 162 | 163 | +| test.c:165:9:165:18 | CompareLT: ... < ... | true | 165 | 166 | | test.cpp:18:8:18:12 | CompareNE: (bool)... | true | 19 | 19 | | test.cpp:31:7:31:13 | CompareEQ: ... == ... | false | 34 | 34 | | test.cpp:31:7:31:13 | CompareEQ: ... == ... | true | 30 | 30 | @@ -690,6 +786,22 @@ irGuardsEnsure | test.c:109:9:109:14 | CompareEQ: ... == ... | test.c:109:14:109:14 | Constant: 0 | != | test.c:109:9:109:9 | Load: x | 0 | 113 | 113 | | test.c:109:19:109:23 | CompareLT: ... < ... | test.c:109:19:109:19 | Load: y | >= | test.c:109:23:109:23 | Constant: (long)... | 0 | 113 | 113 | | test.c:109:19:109:23 | CompareLT: ... < ... | test.c:109:23:109:23 | Constant: (long)... | < | test.c:109:19:109:19 | Load: y | 1 | 113 | 113 | +| test.c:156:9:156:19 | CompareEQ: ... == ... | test.c:156:9:156:9 | Load: x | == | test.c:156:14:156:14 | Load: y | 42 | 156 | 157 | +| test.c:156:9:156:19 | CompareEQ: ... == ... | test.c:156:9:156:9 | Load: x | == | test.c:156:14:156:19 | PointerAdd: ... + ... | 0 | 156 | 157 | +| test.c:156:9:156:19 | CompareEQ: ... == ... | test.c:156:14:156:14 | Load: y | == | test.c:156:9:156:9 | Load: x | -42 | 156 | 157 | +| test.c:156:9:156:19 | CompareEQ: ... == ... | test.c:156:14:156:19 | PointerAdd: ... + ... | == | test.c:156:9:156:9 | Load: x | 0 | 156 | 157 | +| test.c:159:9:159:19 | CompareEQ: ... == ... | test.c:159:9:159:9 | Load: x | == | test.c:159:14:159:14 | Load: y | -42 | 159 | 160 | +| test.c:159:9:159:19 | CompareEQ: ... == ... | test.c:159:9:159:9 | Load: x | == | test.c:159:14:159:19 | PointerSub: ... - ... | 0 | 159 | 160 | +| test.c:159:9:159:19 | CompareEQ: ... == ... | test.c:159:14:159:14 | Load: y | == | test.c:159:9:159:9 | Load: x | 42 | 159 | 160 | +| test.c:159:9:159:19 | CompareEQ: ... == ... | test.c:159:14:159:19 | PointerSub: ... - ... | == | test.c:159:9:159:9 | Load: x | 0 | 159 | 160 | +| test.c:162:9:162:18 | CompareLT: ... < ... | test.c:162:9:162:9 | Load: x | < | test.c:162:13:162:13 | Load: y | 42 | 162 | 163 | +| test.c:162:9:162:18 | CompareLT: ... < ... | test.c:162:9:162:9 | Load: x | < | test.c:162:13:162:18 | PointerAdd: ... + ... | 0 | 162 | 163 | +| test.c:162:9:162:18 | CompareLT: ... < ... | test.c:162:13:162:13 | Load: y | >= | test.c:162:9:162:9 | Load: x | -41 | 162 | 163 | +| test.c:162:9:162:18 | CompareLT: ... < ... | test.c:162:13:162:18 | PointerAdd: ... + ... | >= | test.c:162:9:162:9 | Load: x | 1 | 162 | 163 | +| test.c:165:9:165:18 | CompareLT: ... < ... | test.c:165:9:165:9 | Load: x | < | test.c:165:13:165:13 | Load: y | -42 | 165 | 166 | +| test.c:165:9:165:18 | CompareLT: ... < ... | test.c:165:9:165:9 | Load: x | < | test.c:165:13:165:18 | PointerSub: ... - ... | 0 | 165 | 166 | +| test.c:165:9:165:18 | CompareLT: ... < ... | test.c:165:13:165:13 | Load: y | >= | test.c:165:9:165:9 | Load: x | 43 | 165 | 166 | +| test.c:165:9:165:18 | CompareLT: ... < ... | test.c:165:13:165:18 | PointerSub: ... - ... | >= | test.c:165:9:165:9 | Load: x | 1 | 165 | 166 | | test.cpp:18:8:18:12 | CompareNE: (bool)... | test.cpp:18:8:18:10 | Call: call to get | != | test.cpp:18:8:18:12 | Constant: (bool)... | 0 | 19 | 19 | | test.cpp:18:8:18:12 | CompareNE: (bool)... | test.cpp:18:8:18:12 | Constant: (bool)... | != | test.cpp:18:8:18:10 | Call: call to get | 0 | 19 | 19 | | test.cpp:31:7:31:13 | CompareEQ: ... == ... | test.cpp:31:7:31:7 | Load: x | != | test.cpp:31:12:31:13 | Constant: - ... | 0 | 34 | 34 | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-edge-tests/additionalEdges.expected b/cpp/ql/test/library-tests/dataflow/dataflow-edge-tests/additionalEdges.expected index 2047554655c..293d4ac5577 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-edge-tests/additionalEdges.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-edge-tests/additionalEdges.expected @@ -1,3 +1,6 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (additionalEdges.ql:31,6-14) +WARNING: Module DataFlow has been deprecated and may be removed in future (additionalEdges.ql:31,31-39) +WARNING: Module DataFlow has been deprecated and may be removed in future (additionalEdges.ql:32,7-15) | tryExcept.c:7:7:7:7 | x | tryExcept.c:14:10:14:10 | x | | tryExcept.c:7:13:7:14 | 0 | tryExcept.c:10:9:10:9 | y | | tryExcept.c:10:9:10:9 | y | tryExcept.c:10:5:10:9 | ... = ... | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-edge-tests/standardEdges.expected b/cpp/ql/test/library-tests/dataflow/dataflow-edge-tests/standardEdges.expected index 58039251203..9eff1329858 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-edge-tests/standardEdges.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-edge-tests/standardEdges.expected @@ -1,2 +1,5 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (standardEdges.ql:4,6-14) +WARNING: Module DataFlow has been deprecated and may be removed in future (standardEdges.ql:4,31-39) +WARNING: Module DataFlow has been deprecated and may be removed in future (standardEdges.ql:5,7-15) | tryExcept.c:7:13:7:14 | 0 | tryExcept.c:10:9:10:9 | y | | tryExcept.c:10:9:10:9 | y | tryExcept.c:10:5:10:9 | ... = ... | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/has-parameter-flow-out.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/has-parameter-flow-out.expected index 48de9172b36..6e4b117b7bb 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/has-parameter-flow-out.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/has-parameter-flow-out.expected @@ -1,2 +1,3 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (has-parameter-flow-out.ql:5,18-61) failures testFailures diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/localFlow.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/localFlow.expected index b62fad5bd9a..525e6b22da5 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/localFlow.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/localFlow.expected @@ -1,3 +1,6 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (localFlow.ql:4,6-14) +WARNING: Module DataFlow has been deprecated and may be removed in future (localFlow.ql:4,31-39) +WARNING: Module DataFlow has been deprecated and may be removed in future (localFlow.ql:6,3-11) | example.c:15:37:15:37 | b | example.c:15:37:15:37 | b | | example.c:15:37:15:37 | b | example.c:19:6:19:6 | b | | example.c:15:44:15:46 | pos | example.c:24:24:24:26 | pos | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test-number-of-outnodes.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test-number-of-outnodes.expected index 48de9172b36..4916df02519 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test-number-of-outnodes.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test-number-of-outnodes.expected @@ -1,2 +1,3 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (test-number-of-outnodes.ql:5,18-61) failures testFailures diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected index 48de9172b36..0260ed62b05 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected @@ -1,2 +1,9 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (test.ql:19,45-53) +WARNING: Module DataFlow has been deprecated and may be removed in future (test.ql:20,24-32) +WARNING: Module DataFlow has been deprecated and may be removed in future (test.ql:27,15-23) +WARNING: Module DataFlow has been deprecated and may be removed in future (test.ql:33,22-30) +WARNING: Module DataFlow has been deprecated and may be removed in future (test.ql:40,25-33) +WARNING: Module DataFlow has been deprecated and may be removed in future (test.ql:42,17-25) +WARNING: Module DataFlow has been deprecated and may be removed in future (test.ql:46,20-28) failures testFailures diff --git a/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected b/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected index a90f04df3cf..6dce17b97bf 100644 --- a/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected +++ b/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected @@ -12,8 +12,10 @@ edges | A.cpp:31:14:31:21 | call to B [c] | A.cpp:29:15:29:18 | make indirection [c] | | A.cpp:31:20:31:20 | c | A.cpp:23:10:23:10 | c | | A.cpp:31:20:31:20 | c | A.cpp:31:14:31:21 | call to B [c] | -| A.cpp:41:15:41:21 | new | A.cpp:43:10:43:12 | & ... indirection | -| A.cpp:41:15:41:21 | new | A.cpp:43:10:43:12 | & ... indirection | +| A.cpp:41:5:41:6 | insert output argument | A.cpp:43:10:43:12 | & ... indirection | +| A.cpp:41:15:41:21 | new | A.cpp:41:5:41:6 | insert output argument | +| A.cpp:41:15:41:21 | new | A.cpp:41:5:41:6 | insert output argument | +| A.cpp:41:15:41:21 | new | A.cpp:41:15:41:21 | new | | A.cpp:47:12:47:18 | new | A.cpp:48:20:48:20 | c | | A.cpp:48:12:48:18 | call to make indirection [c] | A.cpp:49:10:49:10 | b indirection [c] | | A.cpp:48:20:48:20 | c | A.cpp:29:23:29:23 | c | @@ -1049,6 +1051,7 @@ nodes | A.cpp:29:23:29:23 | c | semmle.label | c | | A.cpp:31:14:31:21 | call to B [c] | semmle.label | call to B [c] | | A.cpp:31:20:31:20 | c | semmle.label | c | +| A.cpp:41:5:41:6 | insert output argument | semmle.label | insert output argument | | A.cpp:41:15:41:21 | new | semmle.label | new | | A.cpp:41:15:41:21 | new | semmle.label | new | | A.cpp:43:10:43:12 | & ... indirection | semmle.label | & ... indirection | diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected index ad76accce67..131a8cf05c6 100644 --- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected +++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected @@ -1,3 +1,4 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (partial-definition-diff.ql:7,8-51) | A.cpp:25:13:25:13 | c | AST only | | A.cpp:27:28:27:28 | c | AST only | | A.cpp:28:29:28:29 | this | IR only | diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected b/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected index c37eca67c75..608f884ddc0 100644 --- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected +++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected @@ -1,3 +1,4 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (partial-definition.ql:6,8-51) | A.cpp:25:7:25:10 | this | | A.cpp:25:13:25:13 | c | | A.cpp:27:22:27:25 | this | diff --git a/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected b/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected index 00a5f1a3f28..78b3832ea92 100644 --- a/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected +++ b/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected @@ -9,7 +9,9 @@ edges | A.cpp:31:14:31:21 | call to B [c] | A.cpp:31:14:31:21 | new [c] | | A.cpp:31:20:31:20 | c | A.cpp:23:10:23:10 | c | | A.cpp:31:20:31:20 | c | A.cpp:31:14:31:21 | call to B [c] | -| A.cpp:41:15:41:21 | new | A.cpp:43:10:43:12 | & ... | +| A.cpp:41:5:41:6 | ref arg ct | A.cpp:43:11:43:12 | ct | +| A.cpp:41:15:41:21 | new | A.cpp:41:5:41:6 | ref arg ct | +| A.cpp:43:11:43:12 | ct | A.cpp:43:10:43:12 | & ... | | A.cpp:47:12:47:18 | new | A.cpp:48:20:48:20 | c | | A.cpp:48:12:48:18 | call to make [c] | A.cpp:49:10:49:10 | b [c] | | A.cpp:48:20:48:20 | c | A.cpp:29:23:29:23 | c | @@ -207,11 +209,12 @@ edges | E.cpp:28:21:28:23 | ref arg raw | E.cpp:31:10:31:12 | raw | | E.cpp:29:21:29:21 | b [post update] [buffer] | E.cpp:32:10:32:10 | b [buffer] | | E.cpp:29:24:29:29 | ref arg buffer | E.cpp:29:21:29:21 | b [post update] [buffer] | -| E.cpp:30:21:30:21 | p [post update] [data, buffer] | E.cpp:33:18:33:19 | & ... [data, buffer] | +| E.cpp:30:21:30:21 | p [post update] [data, buffer] | E.cpp:33:19:33:19 | p [data, buffer] | | E.cpp:30:23:30:26 | data [post update] [buffer] | E.cpp:30:21:30:21 | p [post update] [data, buffer] | | E.cpp:30:28:30:33 | ref arg buffer | E.cpp:30:23:30:26 | data [post update] [buffer] | | E.cpp:32:10:32:10 | b [buffer] | E.cpp:32:13:32:18 | buffer | | E.cpp:33:18:33:19 | & ... [data, buffer] | E.cpp:19:27:19:27 | p [data, buffer] | +| E.cpp:33:19:33:19 | p [data, buffer] | E.cpp:33:18:33:19 | & ... [data, buffer] | | aliasing.cpp:8:23:8:23 | s [m1] | aliasing.cpp:25:17:25:19 | ref arg & ... [m1] | | aliasing.cpp:9:3:9:3 | s [post update] [m1] | aliasing.cpp:8:23:8:23 | s [m1] | | aliasing.cpp:9:3:9:3 | s [post update] [m1] | aliasing.cpp:25:17:25:19 | ref arg & ... [m1] | @@ -362,11 +365,12 @@ edges | by_reference.cpp:62:25:62:34 | call to user_input | by_reference.cpp:62:3:62:3 | ref arg s [a] | | by_reference.cpp:63:8:63:8 | s [a] | by_reference.cpp:43:9:43:27 | this [a] | | by_reference.cpp:63:8:63:8 | s [a] | by_reference.cpp:63:10:63:28 | call to getThroughNonMember | -| by_reference.cpp:68:17:68:18 | ref arg & ... [a] | by_reference.cpp:69:22:69:23 | & ... [a] | +| by_reference.cpp:68:17:68:18 | ref arg & ... [a] | by_reference.cpp:69:23:69:23 | s [a] | | by_reference.cpp:68:21:68:30 | call to user_input | by_reference.cpp:11:48:11:52 | value | | by_reference.cpp:68:21:68:30 | call to user_input | by_reference.cpp:68:17:68:18 | ref arg & ... [a] | | by_reference.cpp:69:22:69:23 | & ... [a] | by_reference.cpp:31:46:31:46 | s [a] | | by_reference.cpp:69:22:69:23 | & ... [a] | by_reference.cpp:69:8:69:20 | call to nonMemberGetA | +| by_reference.cpp:69:23:69:23 | s [a] | by_reference.cpp:69:22:69:23 | & ... [a] | | by_reference.cpp:83:31:83:35 | inner [a] | by_reference.cpp:102:21:102:39 | ref arg & ... [a] | | by_reference.cpp:83:31:83:35 | inner [a] | by_reference.cpp:103:27:103:35 | ref arg inner_ptr [a] | | by_reference.cpp:83:31:83:35 | inner [a] | by_reference.cpp:106:21:106:41 | ref arg & ... [a] | @@ -701,25 +705,27 @@ edges | struct_init.c:15:12:15:12 | a | struct_init.c:15:12:15:12 | ref arg a | | struct_init.c:15:12:15:12 | ref arg a | struct_init.c:15:8:15:9 | ab [post update] [a] | | struct_init.c:20:17:20:36 | {...} [a] | struct_init.c:22:8:22:9 | ab [a] | -| struct_init.c:20:17:20:36 | {...} [a] | struct_init.c:24:10:24:12 | & ... [a] | -| struct_init.c:20:17:20:36 | {...} [a] | struct_init.c:28:5:28:7 | & ... [a] | +| struct_init.c:20:17:20:36 | {...} [a] | struct_init.c:24:11:24:12 | ab [a] | +| struct_init.c:20:17:20:36 | {...} [a] | struct_init.c:28:6:28:7 | ab [a] | | struct_init.c:20:20:20:29 | call to user_input | struct_init.c:20:17:20:36 | {...} [a] | | struct_init.c:22:8:22:9 | ab [a] | struct_init.c:22:11:22:11 | a | | struct_init.c:22:8:22:9 | ab [a] | struct_init.c:22:11:22:11 | a | -| struct_init.c:22:8:22:9 | ab [post update] [a] | struct_init.c:24:10:24:12 | & ... [a] | -| struct_init.c:22:8:22:9 | ab [post update] [a] | struct_init.c:28:5:28:7 | & ... [a] | +| struct_init.c:22:8:22:9 | ab [post update] [a] | struct_init.c:24:11:24:12 | ab [a] | +| struct_init.c:22:8:22:9 | ab [post update] [a] | struct_init.c:28:6:28:7 | ab [a] | | struct_init.c:22:11:22:11 | a | realistic.cpp:41:17:41:17 | o | | struct_init.c:22:11:22:11 | a | struct_init.c:22:11:22:11 | ref arg a | | struct_init.c:22:11:22:11 | ref arg a | struct_init.c:22:8:22:9 | ab [post update] [a] | | struct_init.c:24:10:24:12 | & ... [a] | struct_init.c:14:24:14:25 | ab [a] | | struct_init.c:24:10:24:12 | & ... [a] | struct_init.c:24:10:24:12 | ref arg & ... [a] | -| struct_init.c:24:10:24:12 | ref arg & ... [a] | struct_init.c:28:5:28:7 | & ... [a] | +| struct_init.c:24:10:24:12 | ref arg & ... [a] | struct_init.c:28:6:28:7 | ab [a] | +| struct_init.c:24:11:24:12 | ab [a] | struct_init.c:24:10:24:12 | & ... [a] | | struct_init.c:26:23:29:3 | {...} [nestedAB, a] | struct_init.c:31:8:31:12 | outer [nestedAB, a] | | struct_init.c:26:23:29:3 | {...} [nestedAB, a] | struct_init.c:36:11:36:15 | outer [nestedAB, a] | | struct_init.c:26:23:29:3 | {...} [pointerAB, a] | struct_init.c:33:8:33:12 | outer [pointerAB, a] | | struct_init.c:27:5:27:23 | {...} [a] | struct_init.c:26:23:29:3 | {...} [nestedAB, a] | | struct_init.c:27:7:27:16 | call to user_input | struct_init.c:27:5:27:23 | {...} [a] | | struct_init.c:28:5:28:7 | & ... [a] | struct_init.c:26:23:29:3 | {...} [pointerAB, a] | +| struct_init.c:28:6:28:7 | ab [a] | struct_init.c:28:5:28:7 | & ... [a] | | struct_init.c:31:8:31:12 | outer [nestedAB, a] | struct_init.c:31:14:31:21 | nestedAB [a] | | struct_init.c:31:8:31:12 | outer [post update] [nestedAB, a] | struct_init.c:36:11:36:15 | outer [nestedAB, a] | | struct_init.c:31:14:31:21 | nestedAB [a] | struct_init.c:31:23:31:23 | a | @@ -733,10 +739,11 @@ edges | struct_init.c:36:10:36:24 | & ... [a] | struct_init.c:14:24:14:25 | ab [a] | | struct_init.c:36:11:36:15 | outer [nestedAB, a] | struct_init.c:36:17:36:24 | nestedAB [a] | | struct_init.c:36:17:36:24 | nestedAB [a] | struct_init.c:36:10:36:24 | & ... [a] | -| struct_init.c:40:17:40:36 | {...} [a] | struct_init.c:43:5:43:7 | & ... [a] | +| struct_init.c:40:17:40:36 | {...} [a] | struct_init.c:43:6:43:7 | ab [a] | | struct_init.c:40:20:40:29 | call to user_input | struct_init.c:40:17:40:36 | {...} [a] | | struct_init.c:41:23:44:3 | {...} [pointerAB, a] | struct_init.c:46:10:46:14 | outer [pointerAB, a] | | struct_init.c:43:5:43:7 | & ... [a] | struct_init.c:41:23:44:3 | {...} [pointerAB, a] | +| struct_init.c:43:6:43:7 | ab [a] | struct_init.c:43:5:43:7 | & ... [a] | | struct_init.c:46:10:46:14 | outer [pointerAB, a] | struct_init.c:46:16:46:24 | pointerAB [a] | | struct_init.c:46:16:46:24 | pointerAB [a] | struct_init.c:14:24:14:25 | ab [a] | nodes @@ -753,8 +760,10 @@ nodes | A.cpp:31:14:31:21 | call to B [c] | semmle.label | call to B [c] | | A.cpp:31:14:31:21 | new [c] | semmle.label | new [c] | | A.cpp:31:20:31:20 | c | semmle.label | c | +| A.cpp:41:5:41:6 | ref arg ct | semmle.label | ref arg ct | | A.cpp:41:15:41:21 | new | semmle.label | new | | A.cpp:43:10:43:12 | & ... | semmle.label | & ... | +| A.cpp:43:11:43:12 | ct | semmle.label | ct | | A.cpp:47:12:47:18 | new | semmle.label | new | | A.cpp:48:12:48:18 | call to make [c] | semmle.label | call to make [c] | | A.cpp:48:20:48:20 | c | semmle.label | c | @@ -963,6 +972,7 @@ nodes | E.cpp:32:10:32:10 | b [buffer] | semmle.label | b [buffer] | | E.cpp:32:13:32:18 | buffer | semmle.label | buffer | | E.cpp:33:18:33:19 | & ... [data, buffer] | semmle.label | & ... [data, buffer] | +| E.cpp:33:19:33:19 | p [data, buffer] | semmle.label | p [data, buffer] | | aliasing.cpp:8:23:8:23 | s [m1] | semmle.label | s [m1] | | aliasing.cpp:9:3:9:3 | s [post update] [m1] | semmle.label | s [post update] [m1] | | aliasing.cpp:9:3:9:22 | ... = ... | semmle.label | ... = ... | @@ -1116,6 +1126,7 @@ nodes | by_reference.cpp:68:21:68:30 | call to user_input | semmle.label | call to user_input | | by_reference.cpp:69:8:69:20 | call to nonMemberGetA | semmle.label | call to nonMemberGetA | | by_reference.cpp:69:22:69:23 | & ... [a] | semmle.label | & ... [a] | +| by_reference.cpp:69:23:69:23 | s [a] | semmle.label | s [a] | | by_reference.cpp:83:31:83:35 | inner [a] | semmle.label | inner [a] | | by_reference.cpp:84:3:84:7 | inner [post update] [a] | semmle.label | inner [post update] [a] | | by_reference.cpp:84:3:84:25 | ... = ... | semmle.label | ... = ... | @@ -1474,11 +1485,13 @@ nodes | struct_init.c:22:11:22:11 | ref arg a | semmle.label | ref arg a | | struct_init.c:24:10:24:12 | & ... [a] | semmle.label | & ... [a] | | struct_init.c:24:10:24:12 | ref arg & ... [a] | semmle.label | ref arg & ... [a] | +| struct_init.c:24:11:24:12 | ab [a] | semmle.label | ab [a] | | struct_init.c:26:23:29:3 | {...} [nestedAB, a] | semmle.label | {...} [nestedAB, a] | | struct_init.c:26:23:29:3 | {...} [pointerAB, a] | semmle.label | {...} [pointerAB, a] | | struct_init.c:27:5:27:23 | {...} [a] | semmle.label | {...} [a] | | struct_init.c:27:7:27:16 | call to user_input | semmle.label | call to user_input | | struct_init.c:28:5:28:7 | & ... [a] | semmle.label | & ... [a] | +| struct_init.c:28:6:28:7 | ab [a] | semmle.label | ab [a] | | struct_init.c:31:8:31:12 | outer [nestedAB, a] | semmle.label | outer [nestedAB, a] | | struct_init.c:31:8:31:12 | outer [post update] [nestedAB, a] | semmle.label | outer [post update] [nestedAB, a] | | struct_init.c:31:14:31:21 | nestedAB [a] | semmle.label | nestedAB [a] | @@ -1496,6 +1509,7 @@ nodes | struct_init.c:40:20:40:29 | call to user_input | semmle.label | call to user_input | | struct_init.c:41:23:44:3 | {...} [pointerAB, a] | semmle.label | {...} [pointerAB, a] | | struct_init.c:43:5:43:7 | & ... [a] | semmle.label | & ... [a] | +| struct_init.c:43:6:43:7 | ab [a] | semmle.label | ab [a] | | struct_init.c:46:10:46:14 | outer [pointerAB, a] | semmle.label | outer [pointerAB, a] | | struct_init.c:46:16:46:24 | pointerAB [a] | semmle.label | pointerAB [a] | subpaths diff --git a/cpp/ql/test/library-tests/dataflow/smart-pointers-taint/taint.expected b/cpp/ql/test/library-tests/dataflow/smart-pointers-taint/taint.expected index 48de9172b36..5e4f9083393 100644 --- a/cpp/ql/test/library-tests/dataflow/smart-pointers-taint/taint.expected +++ b/cpp/ql/test/library-tests/dataflow/smart-pointers-taint/taint.expected @@ -1,2 +1,6 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (taint.ql:6,48-56) +WARNING: Module DataFlow has been deprecated and may be removed in future (taint.ql:7,24-32) +WARNING: Module DataFlow has been deprecated and may be removed in future (taint.ql:11,22-30) +WARNING: Module TaintTracking has been deprecated and may be removed in future (taint.ql:19,20-33) failures testFailures diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected index 44965a9f2d9..f134b647a14 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected @@ -1,3 +1,7 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (localTaint.ql:4,6-14) +WARNING: Module DataFlow has been deprecated and may be removed in future (localTaint.ql:4,31-39) +WARNING: Module DataFlow has been deprecated and may be removed in future (localTaint.ql:7,6-14) +WARNING: Module TaintTracking has been deprecated and may be removed in future (localTaint.ql:6,3-16) | arrayassignment.cpp:9:9:9:10 | 0 | arrayassignment.cpp:10:14:10:14 | x | | | arrayassignment.cpp:9:9:9:10 | 0 | arrayassignment.cpp:11:15:11:15 | x | | | arrayassignment.cpp:9:9:9:10 | 0 | arrayassignment.cpp:12:13:12:13 | x | | diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.expected index 48de9172b36..97e2352000a 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.expected @@ -1,2 +1,7 @@ +WARNING: Module DataFlow has been deprecated and may be removed in future (taint.ql:46,45-53) +WARNING: Module DataFlow has been deprecated and may be removed in future (taint.ql:47,24-32) +WARNING: Module DataFlow has been deprecated and may be removed in future (taint.ql:61,22-30) +WARNING: Module DataFlow has been deprecated and may be removed in future (taint.ql:68,25-33) +WARNING: Module TaintTracking has been deprecated and may be removed in future (taint.ql:73,20-33) failures testFailures diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected index 8321812e547..fe7b07a6ec9 100644 --- a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected +++ b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected @@ -14562,6 +14562,332 @@ ir.cpp: # 1930| Value = [Literal] 40 # 1930| ValueCategory = prvalue # 1931| getStmt(2): [ReturnStmt] return ... +# 1933| [TopLevelFunction] void test_assign_with_assign_operation() +# 1933| : +# 1933| getEntryPoint(): [BlockStmt] { ... } +# 1934| getStmt(0): [DeclStmt] declaration +# 1934| getDeclarationEntry(0): [VariableDeclarationEntry] definition of i +# 1934| Type = [IntType] int +# 1934| getDeclarationEntry(1): [VariableDeclarationEntry] definition of j +# 1934| Type = [IntType] int +# 1934| getVariable().getInitializer(): [Initializer] initializer for j +# 1934| getExpr(): [Literal] 0 +# 1934| Type = [IntType] int +# 1934| Value = [Literal] 0 +# 1934| ValueCategory = prvalue +# 1935| getStmt(1): [ExprStmt] ExprStmt +# 1935| getExpr(): [AssignExpr] ... = ... +# 1935| Type = [IntType] int +# 1935| ValueCategory = lvalue +# 1935| getLValue(): [VariableAccess] i +# 1935| Type = [IntType] int +# 1935| ValueCategory = lvalue +# 1935| getRValue(): [AssignAddExpr] ... += ... +# 1935| Type = [IntType] int +# 1935| ValueCategory = prvalue +# 1935| getLValue(): [VariableAccess] j +# 1935| Type = [IntType] int +# 1935| ValueCategory = lvalue +# 1935| getRValue(): [Literal] 40 +# 1935| Type = [IntType] int +# 1935| Value = [Literal] 40 +# 1935| ValueCategory = prvalue +# 1935| getRValue().getFullyConverted(): [ParenthesisExpr] (...) +# 1935| Type = [IntType] int +# 1935| ValueCategory = prvalue +# 1936| getStmt(2): [ReturnStmt] return ... +# 1938| [CopyAssignmentOperator] D& D::operator=(D const&) +# 1938| : +#-----| getParameter(0): [Parameter] (unnamed parameter 0) +#-----| Type = [LValueReferenceType] const D & +# 1938| [MoveAssignmentOperator] D& D::operator=(D&&) +# 1938| : +#-----| getParameter(0): [Parameter] (unnamed parameter 0) +#-----| Type = [RValueReferenceType] D && +# 1942| [MemberFunction] D& D::ReferenceStaticMemberFunction() +# 1942| : +# 1942| getEntryPoint(): [BlockStmt] { ... } +# 1943| getStmt(0): [ReturnStmt] return ... +# 1943| getExpr(): [VariableAccess] x +# 1943| Type = [Class] D +# 1943| ValueCategory = lvalue +# 1943| getExpr().getFullyConverted(): [ReferenceToExpr] (reference to) +# 1943| Type = [LValueReferenceType] D & +# 1943| ValueCategory = prvalue +# 1945| [MemberFunction] D D::ObjectStaticMemberFunction() +# 1945| : +# 1945| getEntryPoint(): [BlockStmt] { ... } +# 1946| getStmt(0): [ReturnStmt] return ... +# 1946| getExpr(): [VariableAccess] x +# 1946| Type = [Class] D +# 1946| ValueCategory = prvalue(load) +# 1950| [TopLevelFunction] void test_static_member_functions_with_reference_return() +# 1950| : +# 1950| getEntryPoint(): [BlockStmt] { ... } +# 1951| getStmt(0): [DeclStmt] declaration +# 1951| getDeclarationEntry(0): [VariableDeclarationEntry] definition of d +# 1951| Type = [Class] D +# 1953| getStmt(1): [ExprStmt] ExprStmt +# 1953| getExpr(): [FunctionCall] call to ReferenceStaticMemberFunction +# 1953| Type = [LValueReferenceType] D & +# 1953| ValueCategory = prvalue +# 1953| getQualifier(): [VariableAccess] d +# 1953| Type = [Class] D +# 1953| ValueCategory = lvalue +# 1953| getExpr().getFullyConverted(): [ReferenceDereferenceExpr] (reference dereference) +# 1953| Type = [Class] D +# 1953| ValueCategory = lvalue +# 1954| getStmt(2): [ExprStmt] ExprStmt +# 1954| getExpr(): [FunctionCall] call to ReferenceStaticMemberFunction +# 1954| Type = [LValueReferenceType] D & +# 1954| ValueCategory = prvalue +# 1954| getExpr().getFullyConverted(): [ReferenceDereferenceExpr] (reference dereference) +# 1954| Type = [Class] D +# 1954| ValueCategory = lvalue +# 1955| getStmt(3): [ExprStmt] ExprStmt +# 1955| getExpr(): [FunctionCall] call to ObjectStaticMemberFunction +# 1955| Type = [Class] D +# 1955| ValueCategory = prvalue +# 1955| getQualifier(): [VariableAccess] d +# 1955| Type = [Class] D +# 1955| ValueCategory = lvalue +# 1956| getStmt(4): [ExprStmt] ExprStmt +# 1956| getExpr(): [FunctionCall] call to ObjectStaticMemberFunction +# 1956| Type = [Class] D +# 1956| ValueCategory = prvalue +# 1958| getStmt(5): [DeclStmt] declaration +# 1958| getDeclarationEntry(0): [VariableDeclarationEntry] definition of x +# 1958| Type = [Class] D +# 1959| getStmt(6): [ExprStmt] ExprStmt +# 1959| getExpr(): [AssignExpr] ... = ... +# 1959| Type = [Class] D +# 1959| ValueCategory = lvalue +# 1959| getLValue(): [VariableAccess] x +# 1959| Type = [Class] D +# 1959| ValueCategory = lvalue +# 1959| getRValue(): [FunctionCall] call to ReferenceStaticMemberFunction +# 1959| Type = [LValueReferenceType] D & +# 1959| ValueCategory = prvalue +# 1959| getQualifier(): [VariableAccess] d +# 1959| Type = [Class] D +# 1959| ValueCategory = lvalue +# 1959| getRValue().getFullyConverted(): [ReferenceDereferenceExpr] (reference dereference) +# 1959| Type = [Class] D +# 1959| ValueCategory = prvalue(load) +# 1960| getStmt(7): [DeclStmt] declaration +# 1960| getDeclarationEntry(0): [VariableDeclarationEntry] definition of y +# 1960| Type = [Class] D +# 1961| getStmt(8): [ExprStmt] ExprStmt +# 1961| getExpr(): [AssignExpr] ... = ... +# 1961| Type = [Class] D +# 1961| ValueCategory = lvalue +# 1961| getLValue(): [VariableAccess] y +# 1961| Type = [Class] D +# 1961| ValueCategory = lvalue +# 1961| getRValue(): [FunctionCall] call to ReferenceStaticMemberFunction +# 1961| Type = [LValueReferenceType] D & +# 1961| ValueCategory = prvalue +# 1961| getRValue().getFullyConverted(): [ReferenceDereferenceExpr] (reference dereference) +# 1961| Type = [Class] D +# 1961| ValueCategory = prvalue(load) +# 1962| getStmt(9): [DeclStmt] declaration +# 1962| getDeclarationEntry(0): [VariableDeclarationEntry] definition of j +# 1962| Type = [Class] D +# 1963| getStmt(10): [ExprStmt] ExprStmt +# 1963| getExpr(): [AssignExpr] ... = ... +# 1963| Type = [Class] D +# 1963| ValueCategory = lvalue +# 1963| getLValue(): [VariableAccess] j +# 1963| Type = [Class] D +# 1963| ValueCategory = lvalue +# 1963| getRValue(): [FunctionCall] call to ObjectStaticMemberFunction +# 1963| Type = [Class] D +# 1963| ValueCategory = prvalue +# 1963| getQualifier(): [VariableAccess] d +# 1963| Type = [Class] D +# 1963| ValueCategory = lvalue +# 1964| getStmt(11): [DeclStmt] declaration +# 1964| getDeclarationEntry(0): [VariableDeclarationEntry] definition of k +# 1964| Type = [Class] D +# 1965| getStmt(12): [ExprStmt] ExprStmt +# 1965| getExpr(): [AssignExpr] ... = ... +# 1965| Type = [Class] D +# 1965| ValueCategory = lvalue +# 1965| getLValue(): [VariableAccess] k +# 1965| Type = [Class] D +# 1965| ValueCategory = lvalue +# 1965| getRValue(): [FunctionCall] call to ObjectStaticMemberFunction +# 1965| Type = [Class] D +# 1965| ValueCategory = prvalue +# 1966| getStmt(13): [ReturnStmt] return ... +# 1968| [TopLevelFunction] void test_volatile() +# 1968| : +# 1968| getEntryPoint(): [BlockStmt] { ... } +# 1969| getStmt(0): [DeclStmt] declaration +# 1969| getDeclarationEntry(0): [VariableDeclarationEntry] definition of x +# 1969| Type = [SpecifiedType] volatile int +# 1970| getStmt(1): [ExprStmt] ExprStmt +# 1970| getExpr(): [VariableAccess] x +# 1970| Type = [IntType] int +# 1970| ValueCategory = prvalue(load) +# 1971| getStmt(2): [ReturnStmt] return ... +# 1973| [CopyAssignmentOperator] ValCat& ValCat::operator=(ValCat const&) +# 1973| : +#-----| getParameter(0): [Parameter] (unnamed parameter 0) +#-----| Type = [LValueReferenceType] const ValCat & +# 1973| [MoveAssignmentOperator] ValCat& ValCat::operator=(ValCat&&) +# 1973| : +#-----| getParameter(0): [Parameter] (unnamed parameter 0) +#-----| Type = [RValueReferenceType] ValCat && +# 1974| [MemberFunction] ValCat& ValCat::lvalue() +# 1974| : +# 1975| [MemberFunction] ValCat&& ValCat::xvalue() +# 1975| : +# 1976| [MemberFunction] ValCat ValCat::prvalue() +# 1976| : +# 1979| [TopLevelFunction] void value_category_test() +# 1979| : +# 1979| getEntryPoint(): [BlockStmt] { ... } +# 1980| getStmt(0): [DeclStmt] declaration +# 1980| getDeclarationEntry(0): [VariableDeclarationEntry] definition of c +# 1980| Type = [Struct] ValCat +# 1982| getStmt(1): [ExprStmt] ExprStmt +# 1982| getExpr(): [AssignExpr] ... = ... +# 1982| Type = [Struct] ValCat +# 1982| ValueCategory = lvalue +# 1982| getLValue(): [FunctionCall] call to lvalue +# 1982| Type = [LValueReferenceType] ValCat & +# 1982| ValueCategory = prvalue +# 1982| getQualifier(): [VariableAccess] c +# 1982| Type = [Struct] ValCat +# 1982| ValueCategory = lvalue +# 1982| getRValue(): [ClassAggregateLiteral] {...} +# 1982| Type = [Struct] ValCat +# 1982| ValueCategory = prvalue +# 1982| getLValue().getFullyConverted(): [ReferenceDereferenceExpr] (reference dereference) +# 1982| Type = [Struct] ValCat +# 1982| ValueCategory = lvalue +#-----| getRValue().getFullyConverted(): [TemporaryObjectExpr] temporary object +#-----| Type = [Struct] ValCat +#-----| ValueCategory = prvalue(load) +# 1983| getStmt(2): [ExprStmt] ExprStmt +# 1983| getExpr(): [AssignExpr] ... = ... +# 1983| Type = [Struct] ValCat +# 1983| ValueCategory = lvalue +# 1983| getLValue(): [FunctionCall] call to xvalue +# 1983| Type = [RValueReferenceType] ValCat && +# 1983| ValueCategory = prvalue +# 1983| getQualifier(): [VariableAccess] c +# 1983| Type = [Struct] ValCat +# 1983| ValueCategory = lvalue +# 1983| getRValue(): [ClassAggregateLiteral] {...} +# 1983| Type = [Struct] ValCat +# 1983| ValueCategory = prvalue +# 1983| getLValue().getFullyConverted(): [ReferenceDereferenceExpr] (reference dereference) +# 1983| Type = [Struct] ValCat +# 1983| ValueCategory = lvalue +#-----| getRValue().getFullyConverted(): [TemporaryObjectExpr] temporary object +#-----| Type = [Struct] ValCat +#-----| ValueCategory = prvalue(load) +# 1984| getStmt(3): [ExprStmt] ExprStmt +# 1984| getExpr(): [AssignExpr] ... = ... +# 1984| Type = [Struct] ValCat +# 1984| ValueCategory = lvalue +# 1984| getLValue(): [FunctionCall] call to prvalue +# 1984| Type = [Struct] ValCat +# 1984| ValueCategory = prvalue +# 1984| getQualifier(): [VariableAccess] c +# 1984| Type = [Struct] ValCat +# 1984| ValueCategory = lvalue +# 1984| getRValue(): [ClassAggregateLiteral] {...} +# 1984| Type = [Struct] ValCat +# 1984| ValueCategory = prvalue +# 1984| getLValue().getFullyConverted(): [TemporaryObjectExpr] temporary object +# 1984| Type = [Struct] ValCat +# 1984| ValueCategory = lvalue +#-----| getRValue().getFullyConverted(): [TemporaryObjectExpr] temporary object +#-----| Type = [Struct] ValCat +#-----| ValueCategory = prvalue(load) +# 1985| getStmt(4): [ExprStmt] ExprStmt +# 1985| getExpr(): [AssignExpr] ... = ... +# 1985| Type = [Struct] ValCat +# 1985| ValueCategory = lvalue +# 1985| getLValue(): [FunctionCall] call to lvalue +# 1985| Type = [LValueReferenceType] ValCat & +# 1985| ValueCategory = prvalue +# 1985| getRValue(): [ClassAggregateLiteral] {...} +# 1985| Type = [Struct] ValCat +# 1985| ValueCategory = prvalue +# 1985| getLValue().getFullyConverted(): [ReferenceDereferenceExpr] (reference dereference) +# 1985| Type = [Struct] ValCat +# 1985| ValueCategory = lvalue +#-----| getRValue().getFullyConverted(): [TemporaryObjectExpr] temporary object +#-----| Type = [Struct] ValCat +#-----| ValueCategory = prvalue(load) +# 1986| getStmt(5): [ExprStmt] ExprStmt +# 1986| getExpr(): [AssignExpr] ... = ... +# 1986| Type = [Struct] ValCat +# 1986| ValueCategory = lvalue +# 1986| getLValue(): [FunctionCall] call to xvalue +# 1986| Type = [RValueReferenceType] ValCat && +# 1986| ValueCategory = prvalue +# 1986| getRValue(): [ClassAggregateLiteral] {...} +# 1986| Type = [Struct] ValCat +# 1986| ValueCategory = prvalue +# 1986| getLValue().getFullyConverted(): [ReferenceDereferenceExpr] (reference dereference) +# 1986| Type = [Struct] ValCat +# 1986| ValueCategory = lvalue +#-----| getRValue().getFullyConverted(): [TemporaryObjectExpr] temporary object +#-----| Type = [Struct] ValCat +#-----| ValueCategory = prvalue(load) +# 1987| getStmt(6): [ExprStmt] ExprStmt +# 1987| getExpr(): [AssignExpr] ... = ... +# 1987| Type = [Struct] ValCat +# 1987| ValueCategory = lvalue +# 1987| getLValue(): [FunctionCall] call to prvalue +# 1987| Type = [Struct] ValCat +# 1987| ValueCategory = prvalue +# 1987| getRValue(): [ClassAggregateLiteral] {...} +# 1987| Type = [Struct] ValCat +# 1987| ValueCategory = prvalue +# 1987| getLValue().getFullyConverted(): [TemporaryObjectExpr] temporary object +# 1987| Type = [Struct] ValCat +# 1987| ValueCategory = lvalue +#-----| getRValue().getFullyConverted(): [TemporaryObjectExpr] temporary object +#-----| Type = [Struct] ValCat +#-----| ValueCategory = prvalue(load) +# 1988| getStmt(7): [ReturnStmt] return ... +# 1990| [TopLevelFunction] void SetStaticFuncPtr() +# 1990| : +# 1990| getEntryPoint(): [BlockStmt] { ... } +# 1991| getStmt(0): [DeclStmt] declaration +# 1991| getDeclarationEntry(0): [VariableDeclarationEntry] definition of c +# 1991| Type = [Class] C +# 1991| getVariable().getInitializer(): [Initializer] initializer for c +# 1991| getExpr(): [ConstructorCall] call to C +# 1991| Type = [VoidType] void +# 1991| ValueCategory = prvalue +# 1992| getStmt(1): [DeclStmt] declaration +# 1992| getDeclarationEntry(0): [VariableDeclarationEntry] definition of pfn +# 1992| Type = [FunctionPointerType] ..(*)(..) +# 1992| getVariable().getInitializer(): [Initializer] initializer for pfn +# 1992| getExpr(): [FunctionAccess] StaticMemberFunction +# 1992| Type = [FunctionPointerType] ..(*)(..) +# 1992| ValueCategory = prvalue(load) +# 1993| getStmt(2): [ExprStmt] ExprStmt +# 1993| getExpr(): [AssignExpr] ... = ... +# 1993| Type = [FunctionPointerType] ..(*)(..) +# 1993| ValueCategory = lvalue +# 1993| getLValue(): [VariableAccess] pfn +# 1993| Type = [FunctionPointerType] ..(*)(..) +# 1993| ValueCategory = lvalue +# 1993| getRValue(): [FunctionAccess] StaticMemberFunction +# 1993| Type = [FunctionPointerType] ..(*)(..) +# 1993| ValueCategory = prvalue +# 1993| getQualifier(): [VariableAccess] c +# 1993| Type = [Class] C +# 1993| ValueCategory = lvalue +# 1994| getStmt(3): [ReturnStmt] return ... perf-regression.cpp: # 4| [CopyAssignmentOperator] Big& Big::operator=(Big const&) # 4| : diff --git a/cpp/ql/test/library-tests/ir/ir/ir.cpp b/cpp/ql/test/library-tests/ir/ir/ir.cpp index cd6e37472c1..73d6c2381c6 100644 --- a/cpp/ql/test/library-tests/ir/ir/ir.cpp +++ b/cpp/ql/test/library-tests/ir/ir/ir.cpp @@ -1930,4 +1930,67 @@ void test_double_assign() { i = j = 40; } +void test_assign_with_assign_operation() { + int i, j = 0; + i = (j += 40); +} + +class D { + static D x; + +public: + static D& ReferenceStaticMemberFunction() { + return x; + } + static D ObjectStaticMemberFunction() { + return x; + } +}; + +void test_static_member_functions_with_reference_return() { + D d; + + d.ReferenceStaticMemberFunction(); + D::ReferenceStaticMemberFunction(); + d.ObjectStaticMemberFunction(); + D::ObjectStaticMemberFunction(); + + D x; + x = d.ReferenceStaticMemberFunction(); + D y; + y = D::ReferenceStaticMemberFunction(); + D j; + j = d.ObjectStaticMemberFunction(); + D k; + k = D::ObjectStaticMemberFunction(); +} + +void test_volatile() { + volatile int x; + x; +} + +struct ValCat { + static ValCat& lvalue(); + static ValCat&& xvalue(); + static ValCat prvalue(); +}; + +void value_category_test() { + ValCat c; + + c.lvalue() = {}; + c.xvalue() = {}; + c.prvalue() = {}; + ValCat::lvalue() = {}; + ValCat::xvalue() = {}; + ValCat::prvalue() = {}; +} + +void SetStaticFuncPtr() { + C c; + int (*pfn)(int) = C::StaticMemberFunction; + pfn = c.StaticMemberFunction; +} + // semmle-extractor-options: -std=c++17 --clang diff --git a/cpp/ql/test/library-tests/ir/ir/operand_locations.expected b/cpp/ql/test/library-tests/ir/ir/operand_locations.expected index 713e23ca312..e8561a7709e 100644 --- a/cpp/ql/test/library-tests/ir/ir/operand_locations.expected +++ b/cpp/ql/test/library-tests/ir/ir/operand_locations.expected @@ -678,6 +678,8 @@ | file://:0:0:0:0 | Address | &:r0_1 | | file://:0:0:0:0 | Address | &:r0_1 | | file://:0:0:0:0 | Address | &:r0_1 | +| file://:0:0:0:0 | Address | &:r0_1 | +| file://:0:0:0:0 | Address | &:r0_1 | | file://:0:0:0:0 | Address | &:r0_2 | | file://:0:0:0:0 | Address | &:r0_3 | | file://:0:0:0:0 | Address | &:r0_3 | @@ -702,6 +704,8 @@ | file://:0:0:0:0 | Address | &:r0_3 | | file://:0:0:0:0 | Address | &:r0_3 | | file://:0:0:0:0 | Address | &:r0_3 | +| file://:0:0:0:0 | Address | &:r0_4 | +| file://:0:0:0:0 | Address | &:r0_4 | | file://:0:0:0:0 | Address | &:r0_5 | | file://:0:0:0:0 | Address | &:r0_5 | | file://:0:0:0:0 | Address | &:r0_5 | @@ -714,6 +718,8 @@ | file://:0:0:0:0 | Address | &:r0_6 | | file://:0:0:0:0 | Address | &:r0_7 | | file://:0:0:0:0 | Address | &:r0_7 | +| file://:0:0:0:0 | Address | &:r0_7 | +| file://:0:0:0:0 | Address | &:r0_7 | | file://:0:0:0:0 | Address | &:r0_8 | | file://:0:0:0:0 | Address | &:r0_8 | | file://:0:0:0:0 | Address | &:r0_8 | @@ -724,16 +730,22 @@ | file://:0:0:0:0 | Address | &:r0_9 | | file://:0:0:0:0 | Address | &:r0_9 | | file://:0:0:0:0 | Address | &:r0_10 | +| file://:0:0:0:0 | Address | &:r0_10 | +| file://:0:0:0:0 | Address | &:r0_10 | | file://:0:0:0:0 | Address | &:r0_11 | | file://:0:0:0:0 | Address | &:r0_11 | | file://:0:0:0:0 | Address | &:r0_11 | | file://:0:0:0:0 | Address | &:r0_13 | +| file://:0:0:0:0 | Address | &:r0_13 | +| file://:0:0:0:0 | Address | &:r0_13 | | file://:0:0:0:0 | Address | &:r0_15 | | file://:0:0:0:0 | Address | &:r0_15 | | file://:0:0:0:0 | Address | &:r0_15 | | file://:0:0:0:0 | Address | &:r0_15 | | file://:0:0:0:0 | Address | &:r0_16 | | file://:0:0:0:0 | Address | &:r0_16 | +| file://:0:0:0:0 | Address | &:r0_16 | +| file://:0:0:0:0 | Address | &:r0_16 | | file://:0:0:0:0 | Address | &:r0_17 | | file://:0:0:0:0 | Address | &:r0_18 | | file://:0:0:0:0 | Address | &:r0_18 | @@ -786,6 +798,12 @@ | file://:0:0:0:0 | Load | m0_2 | | file://:0:0:0:0 | Load | m0_2 | | file://:0:0:0:0 | Load | m0_2 | +| file://:0:0:0:0 | Load | m0_2 | +| file://:0:0:0:0 | Load | m0_5 | +| file://:0:0:0:0 | Load | m0_8 | +| file://:0:0:0:0 | Load | m0_11 | +| file://:0:0:0:0 | Load | m0_14 | +| file://:0:0:0:0 | Load | m0_17 | | file://:0:0:0:0 | Load | m745_6 | | file://:0:0:0:0 | Load | m754_6 | | file://:0:0:0:0 | Load | m763_6 | @@ -843,14 +861,20 @@ | file://:0:0:0:0 | StoreValue | r0_1 | | file://:0:0:0:0 | StoreValue | r0_1 | | file://:0:0:0:0 | StoreValue | r0_3 | +| file://:0:0:0:0 | StoreValue | r0_3 | | file://:0:0:0:0 | StoreValue | r0_4 | | file://:0:0:0:0 | StoreValue | r0_4 | | file://:0:0:0:0 | StoreValue | r0_6 | +| file://:0:0:0:0 | StoreValue | r0_6 | | file://:0:0:0:0 | StoreValue | r0_7 | | file://:0:0:0:0 | StoreValue | r0_9 | +| file://:0:0:0:0 | StoreValue | r0_9 | +| file://:0:0:0:0 | StoreValue | r0_12 | | file://:0:0:0:0 | StoreValue | r0_12 | | file://:0:0:0:0 | StoreValue | r0_13 | | file://:0:0:0:0 | StoreValue | r0_13 | +| file://:0:0:0:0 | StoreValue | r0_15 | +| file://:0:0:0:0 | StoreValue | r0_18 | | file://:0:0:0:0 | StoreValue | r0_19 | | file://:0:0:0:0 | StoreValue | r0_20 | | file://:0:0:0:0 | StoreValue | r0_22 | @@ -8877,6 +8901,163 @@ | ir.cpp:1930:7:1930:12 | StoreValue | r1930_4 | | ir.cpp:1930:11:1930:12 | StoreValue | r1930_1 | | ir.cpp:1930:11:1930:12 | Unary | r1930_1 | +| ir.cpp:1933:6:1933:38 | ChiPartial | partial:m1933_3 | +| ir.cpp:1933:6:1933:38 | ChiTotal | total:m1933_2 | +| ir.cpp:1933:6:1933:38 | SideEffect | m1933_3 | +| ir.cpp:1934:7:1934:7 | Address | &:r1934_1 | +| ir.cpp:1934:10:1934:10 | Address | &:r1934_3 | +| ir.cpp:1934:13:1934:14 | StoreValue | r1934_4 | +| ir.cpp:1935:3:1935:3 | Address | &:r1935_6 | +| ir.cpp:1935:8:1935:8 | Address | &:r1935_2 | +| ir.cpp:1935:8:1935:8 | Address | &:r1935_2 | +| ir.cpp:1935:8:1935:8 | Left | r1935_3 | +| ir.cpp:1935:8:1935:8 | Load | m1934_5 | +| ir.cpp:1935:8:1935:14 | StoreValue | r1935_4 | +| ir.cpp:1935:8:1935:14 | StoreValue | r1935_4 | +| ir.cpp:1935:13:1935:14 | Right | r1935_1 | +| ir.cpp:1942:15:1942:43 | Address | &:r1942_5 | +| ir.cpp:1942:15:1942:43 | ChiPartial | partial:m1942_3 | +| ir.cpp:1942:15:1942:43 | ChiTotal | total:m1942_2 | +| ir.cpp:1942:15:1942:43 | Load | m1943_4 | +| ir.cpp:1942:15:1942:43 | SideEffect | m1942_3 | +| ir.cpp:1943:9:1943:17 | Address | &:r1943_1 | +| ir.cpp:1943:16:1943:16 | StoreValue | r1943_3 | +| ir.cpp:1943:16:1943:16 | Unary | r1943_2 | +| ir.cpp:1945:14:1945:39 | Address | &:r1945_5 | +| ir.cpp:1945:14:1945:39 | ChiPartial | partial:m1945_3 | +| ir.cpp:1945:14:1945:39 | ChiTotal | total:m1945_2 | +| ir.cpp:1945:14:1945:39 | Load | m1946_4 | +| ir.cpp:1945:14:1945:39 | SideEffect | m1945_3 | +| ir.cpp:1946:9:1946:17 | Address | &:r1946_1 | +| ir.cpp:1946:16:1946:16 | Address | &:r1946_2 | +| ir.cpp:1946:16:1946:16 | Load | ~m1945_3 | +| ir.cpp:1946:16:1946:16 | StoreValue | r1946_3 | +| ir.cpp:1950:6:1950:55 | ChiPartial | partial:m1950_3 | +| ir.cpp:1950:6:1950:55 | ChiTotal | total:m1950_2 | +| ir.cpp:1950:6:1950:55 | SideEffect | ~m1965_4 | +| ir.cpp:1951:7:1951:7 | Address | &:r1951_1 | +| ir.cpp:1953:7:1953:35 | CallTarget | func:r1953_2 | +| ir.cpp:1953:7:1953:35 | ChiPartial | partial:m1953_4 | +| ir.cpp:1953:7:1953:35 | ChiTotal | total:m1950_4 | +| ir.cpp:1953:7:1953:35 | SideEffect | ~m1950_4 | +| ir.cpp:1953:7:1953:35 | Unary | r1953_3 | +| ir.cpp:1954:5:1954:36 | CallTarget | func:r1954_1 | +| ir.cpp:1954:5:1954:36 | ChiPartial | partial:m1954_3 | +| ir.cpp:1954:5:1954:36 | ChiTotal | total:m1953_5 | +| ir.cpp:1954:5:1954:36 | SideEffect | ~m1953_5 | +| ir.cpp:1954:5:1954:36 | Unary | r1954_2 | +| ir.cpp:1955:7:1955:32 | CallTarget | func:r1955_2 | +| ir.cpp:1955:7:1955:32 | ChiPartial | partial:m1955_4 | +| ir.cpp:1955:7:1955:32 | ChiTotal | total:m1954_4 | +| ir.cpp:1955:7:1955:32 | SideEffect | ~m1954_4 | +| ir.cpp:1956:5:1956:33 | CallTarget | func:r1956_1 | +| ir.cpp:1956:5:1956:33 | ChiPartial | partial:m1956_3 | +| ir.cpp:1956:5:1956:33 | ChiTotal | total:m1955_5 | +| ir.cpp:1956:5:1956:33 | SideEffect | ~m1955_5 | +| ir.cpp:1958:7:1958:7 | Address | &:r1958_1 | +| ir.cpp:1959:5:1959:5 | Address | &:r1959_7 | +| ir.cpp:1959:11:1959:39 | Address | &:r1959_3 | +| ir.cpp:1959:11:1959:39 | CallTarget | func:r1959_2 | +| ir.cpp:1959:11:1959:39 | ChiPartial | partial:m1959_4 | +| ir.cpp:1959:11:1959:39 | ChiTotal | total:m1956_4 | +| ir.cpp:1959:11:1959:39 | SideEffect | ~m1956_4 | +| ir.cpp:1959:40:1959:42 | Load | ~m1959_5 | +| ir.cpp:1959:40:1959:42 | StoreValue | r1959_6 | +| ir.cpp:1960:7:1960:7 | Address | &:r1960_1 | +| ir.cpp:1961:5:1961:5 | Address | &:r1961_6 | +| ir.cpp:1961:9:1961:40 | Address | &:r1961_2 | +| ir.cpp:1961:9:1961:40 | CallTarget | func:r1961_1 | +| ir.cpp:1961:9:1961:40 | ChiPartial | partial:m1961_3 | +| ir.cpp:1961:9:1961:40 | ChiTotal | total:m1959_5 | +| ir.cpp:1961:9:1961:40 | SideEffect | ~m1959_5 | +| ir.cpp:1961:41:1961:43 | Load | ~m1961_4 | +| ir.cpp:1961:41:1961:43 | StoreValue | r1961_5 | +| ir.cpp:1962:7:1962:7 | Address | &:r1962_1 | +| ir.cpp:1963:5:1963:5 | Address | &:r1963_6 | +| ir.cpp:1963:11:1963:36 | CallTarget | func:r1963_2 | +| ir.cpp:1963:11:1963:36 | ChiPartial | partial:m1963_4 | +| ir.cpp:1963:11:1963:36 | ChiTotal | total:m1961_4 | +| ir.cpp:1963:11:1963:36 | SideEffect | ~m1961_4 | +| ir.cpp:1963:11:1963:36 | StoreValue | r1963_3 | +| ir.cpp:1964:7:1964:7 | Address | &:r1964_1 | +| ir.cpp:1965:5:1965:5 | Address | &:r1965_5 | +| ir.cpp:1965:9:1965:37 | CallTarget | func:r1965_1 | +| ir.cpp:1965:9:1965:37 | ChiPartial | partial:m1965_3 | +| ir.cpp:1965:9:1965:37 | ChiTotal | total:m1963_5 | +| ir.cpp:1965:9:1965:37 | SideEffect | ~m1963_5 | +| ir.cpp:1965:9:1965:37 | StoreValue | r1965_2 | +| ir.cpp:1968:6:1968:18 | ChiPartial | partial:m1968_3 | +| ir.cpp:1968:6:1968:18 | ChiTotal | total:m1968_2 | +| ir.cpp:1968:6:1968:18 | SideEffect | m1968_3 | +| ir.cpp:1969:18:1969:18 | Address | &:r1969_1 | +| ir.cpp:1970:5:1970:5 | Address | &:r1970_1 | +| ir.cpp:1970:5:1970:5 | Load | m1969_2 | +| ir.cpp:1979:6:1979:24 | ChiPartial | partial:m1979_3 | +| ir.cpp:1979:6:1979:24 | ChiTotal | total:m1979_2 | +| ir.cpp:1979:6:1979:24 | SideEffect | ~m1987_5 | +| ir.cpp:1980:12:1980:12 | Address | &:r1980_1 | +| ir.cpp:1982:5:1982:19 | ChiPartial | partial:m1982_7 | +| ir.cpp:1982:5:1982:19 | ChiTotal | total:m1982_5 | +| ir.cpp:1982:7:1982:12 | CallTarget | func:r1982_2 | +| ir.cpp:1982:7:1982:12 | ChiPartial | partial:m1982_4 | +| ir.cpp:1982:7:1982:12 | ChiTotal | total:m1979_4 | +| ir.cpp:1982:7:1982:12 | SideEffect | ~m1979_4 | +| ir.cpp:1982:7:1982:12 | Unary | r1982_3 | +| ir.cpp:1982:13:1982:16 | Address | &:r1982_6 | +| ir.cpp:1983:5:1983:19 | ChiPartial | partial:m1983_7 | +| ir.cpp:1983:5:1983:19 | ChiTotal | total:m1983_5 | +| ir.cpp:1983:7:1983:12 | CallTarget | func:r1983_2 | +| ir.cpp:1983:7:1983:12 | ChiPartial | partial:m1983_4 | +| ir.cpp:1983:7:1983:12 | ChiTotal | total:m1982_8 | +| ir.cpp:1983:7:1983:12 | SideEffect | ~m1982_8 | +| ir.cpp:1983:7:1983:12 | Unary | r1983_3 | +| ir.cpp:1983:13:1983:16 | Address | &:r1983_6 | +| ir.cpp:1984:5:1984:15 | Address | &:r1984_1 | +| ir.cpp:1984:5:1984:15 | Address | &:r1984_1 | +| ir.cpp:1984:7:1984:13 | CallTarget | func:r1984_3 | +| ir.cpp:1984:7:1984:13 | ChiPartial | partial:m1984_5 | +| ir.cpp:1984:7:1984:13 | ChiTotal | total:m1983_8 | +| ir.cpp:1984:7:1984:13 | SideEffect | ~m1983_8 | +| ir.cpp:1984:7:1984:13 | StoreValue | r1984_4 | +| ir.cpp:1985:5:1985:18 | CallTarget | func:r1985_1 | +| ir.cpp:1985:5:1985:18 | ChiPartial | partial:m1985_3 | +| ir.cpp:1985:5:1985:18 | ChiTotal | total:m1984_6 | +| ir.cpp:1985:5:1985:18 | SideEffect | ~m1984_6 | +| ir.cpp:1985:5:1985:18 | Unary | r1985_2 | +| ir.cpp:1985:5:1985:25 | ChiPartial | partial:m1985_6 | +| ir.cpp:1985:5:1985:25 | ChiTotal | total:m1985_4 | +| ir.cpp:1985:19:1985:22 | Address | &:r1985_5 | +| ir.cpp:1986:5:1986:18 | CallTarget | func:r1986_1 | +| ir.cpp:1986:5:1986:18 | ChiPartial | partial:m1986_3 | +| ir.cpp:1986:5:1986:18 | ChiTotal | total:m1985_7 | +| ir.cpp:1986:5:1986:18 | SideEffect | ~m1985_7 | +| ir.cpp:1986:5:1986:18 | Unary | r1986_2 | +| ir.cpp:1986:5:1986:25 | ChiPartial | partial:m1986_6 | +| ir.cpp:1986:5:1986:25 | ChiTotal | total:m1986_4 | +| ir.cpp:1986:19:1986:22 | Address | &:r1986_5 | +| ir.cpp:1987:5:1987:19 | CallTarget | func:r1987_2 | +| ir.cpp:1987:5:1987:19 | ChiPartial | partial:m1987_4 | +| ir.cpp:1987:5:1987:19 | ChiTotal | total:m1986_7 | +| ir.cpp:1987:5:1987:19 | SideEffect | ~m1986_7 | +| ir.cpp:1987:5:1987:19 | StoreValue | r1987_3 | +| ir.cpp:1987:5:1987:21 | Address | &:r1987_1 | +| ir.cpp:1987:5:1987:21 | Address | &:r1987_1 | +| ir.cpp:1990:6:1990:21 | ChiPartial | partial:m1990_3 | +| ir.cpp:1990:6:1990:21 | ChiTotal | total:m1990_2 | +| ir.cpp:1990:6:1990:21 | SideEffect | ~m1991_6 | +| ir.cpp:1991:7:1991:7 | Address | &:r1991_1 | +| ir.cpp:1991:7:1991:7 | Address | &:r1991_1 | +| ir.cpp:1991:7:1991:7 | Arg(this) | this:r1991_1 | +| ir.cpp:1991:7:1991:7 | CallTarget | func:r1991_3 | +| ir.cpp:1991:7:1991:7 | ChiPartial | partial:m1991_5 | +| ir.cpp:1991:7:1991:7 | ChiPartial | partial:m1991_7 | +| ir.cpp:1991:7:1991:7 | ChiTotal | total:m1990_4 | +| ir.cpp:1991:7:1991:7 | ChiTotal | total:m1991_2 | +| ir.cpp:1991:7:1991:7 | SideEffect | ~m1990_4 | +| ir.cpp:1992:11:1992:13 | Address | &:r1992_1 | +| ir.cpp:1992:23:1992:45 | StoreValue | r1992_2 | +| ir.cpp:1993:5:1993:7 | Address | &:r1993_3 | +| ir.cpp:1993:13:1993:32 | StoreValue | r1993_2 | | perf-regression.cpp:6:3:6:5 | Address | &:r6_5 | | perf-regression.cpp:6:3:6:5 | Address | &:r6_5 | | perf-regression.cpp:6:3:6:5 | Address | &:r6_7 | diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected index 38d9bb438ad..db549003927 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected @@ -10244,6 +10244,218 @@ ir.cpp: # 1928| v1928_5(void) = AliasedUse : ~m? # 1928| v1928_6(void) = ExitFunction : +# 1933| void test_assign_with_assign_operation() +# 1933| Block 0 +# 1933| v1933_1(void) = EnterFunction : +# 1933| mu1933_2(unknown) = AliasedDefinition : +# 1933| mu1933_3(unknown) = InitializeNonLocal : +# 1934| r1934_1(glval) = VariableAddress[i] : +# 1934| mu1934_2(int) = Uninitialized[i] : &:r1934_1 +# 1934| r1934_3(glval) = VariableAddress[j] : +# 1934| r1934_4(int) = Constant[0] : +# 1934| mu1934_5(int) = Store[j] : &:r1934_3, r1934_4 +# 1935| r1935_1(int) = Constant[40] : +# 1935| r1935_2(glval) = VariableAddress[j] : +# 1935| r1935_3(int) = Load[j] : &:r1935_2, ~m? +# 1935| r1935_4(int) = Add : r1935_3, r1935_1 +# 1935| mu1935_5(int) = Store[j] : &:r1935_2, r1935_4 +# 1935| r1935_6(glval) = VariableAddress[i] : +# 1935| mu1935_7(int) = Store[i] : &:r1935_6, r1935_4 +# 1936| v1936_1(void) = NoOp : +# 1933| v1933_4(void) = ReturnVoid : +# 1933| v1933_5(void) = AliasedUse : ~m? +# 1933| v1933_6(void) = ExitFunction : + +# 1942| D& D::ReferenceStaticMemberFunction() +# 1942| Block 0 +# 1942| v1942_1(void) = EnterFunction : +# 1942| mu1942_2(unknown) = AliasedDefinition : +# 1942| mu1942_3(unknown) = InitializeNonLocal : +# 1943| r1943_1(glval) = VariableAddress[#return] : +# 1943| r1943_2(glval) = VariableAddress[x] : +# 1943| r1943_3(D &) = CopyValue : r1943_2 +# 1943| mu1943_4(D &) = Store[#return] : &:r1943_1, r1943_3 +# 1942| r1942_4(glval) = VariableAddress[#return] : +# 1942| v1942_5(void) = ReturnValue : &:r1942_4, ~m? +# 1942| v1942_6(void) = AliasedUse : ~m? +# 1942| v1942_7(void) = ExitFunction : + +# 1945| D D::ObjectStaticMemberFunction() +# 1945| Block 0 +# 1945| v1945_1(void) = EnterFunction : +# 1945| mu1945_2(unknown) = AliasedDefinition : +# 1945| mu1945_3(unknown) = InitializeNonLocal : +# 1946| r1946_1(glval) = VariableAddress[#return] : +# 1946| r1946_2(glval) = VariableAddress[x] : +# 1946| r1946_3(D) = Load[x] : &:r1946_2, ~m? +# 1946| mu1946_4(D) = Store[#return] : &:r1946_1, r1946_3 +# 1945| r1945_4(glval) = VariableAddress[#return] : +# 1945| v1945_5(void) = ReturnValue : &:r1945_4, ~m? +# 1945| v1945_6(void) = AliasedUse : ~m? +# 1945| v1945_7(void) = ExitFunction : + +# 1950| void test_static_member_functions_with_reference_return() +# 1950| Block 0 +# 1950| v1950_1(void) = EnterFunction : +# 1950| mu1950_2(unknown) = AliasedDefinition : +# 1950| mu1950_3(unknown) = InitializeNonLocal : +# 1951| r1951_1(glval) = VariableAddress[d] : +# 1951| mu1951_2(D) = Uninitialized[d] : &:r1951_1 +# 1953| r1953_1(glval) = VariableAddress[d] : +# 1953| r1953_2(glval) = FunctionAddress[ReferenceStaticMemberFunction] : +# 1953| r1953_3(D &) = Call[ReferenceStaticMemberFunction] : func:r1953_2 +# 1953| mu1953_4(unknown) = ^CallSideEffect : ~m? +# 1953| r1953_5(glval) = CopyValue : r1953_3 +# 1954| r1954_1(glval) = FunctionAddress[ReferenceStaticMemberFunction] : +# 1954| r1954_2(D &) = Call[ReferenceStaticMemberFunction] : func:r1954_1 +# 1954| mu1954_3(unknown) = ^CallSideEffect : ~m? +# 1954| r1954_4(glval) = CopyValue : r1954_2 +# 1955| r1955_1(glval) = VariableAddress[d] : +# 1955| r1955_2(glval) = FunctionAddress[ObjectStaticMemberFunction] : +# 1955| r1955_3(D) = Call[ObjectStaticMemberFunction] : func:r1955_2 +# 1955| mu1955_4(unknown) = ^CallSideEffect : ~m? +# 1956| r1956_1(glval) = FunctionAddress[ObjectStaticMemberFunction] : +# 1956| r1956_2(D) = Call[ObjectStaticMemberFunction] : func:r1956_1 +# 1956| mu1956_3(unknown) = ^CallSideEffect : ~m? +# 1958| r1958_1(glval) = VariableAddress[x] : +# 1958| mu1958_2(D) = Uninitialized[x] : &:r1958_1 +# 1959| r1959_1(glval) = VariableAddress[d] : +# 1959| r1959_2(glval) = FunctionAddress[ReferenceStaticMemberFunction] : +# 1959| r1959_3(D &) = Call[ReferenceStaticMemberFunction] : func:r1959_2 +# 1959| mu1959_4(unknown) = ^CallSideEffect : ~m? +# 1959| r1959_5(D) = Load[?] : &:r1959_3, ~m? +# 1959| r1959_6(glval) = VariableAddress[x] : +# 1959| mu1959_7(D) = Store[x] : &:r1959_6, r1959_5 +# 1960| r1960_1(glval) = VariableAddress[y] : +# 1960| mu1960_2(D) = Uninitialized[y] : &:r1960_1 +# 1961| r1961_1(glval) = FunctionAddress[ReferenceStaticMemberFunction] : +# 1961| r1961_2(D &) = Call[ReferenceStaticMemberFunction] : func:r1961_1 +# 1961| mu1961_3(unknown) = ^CallSideEffect : ~m? +# 1961| r1961_4(D) = Load[?] : &:r1961_2, ~m? +# 1961| r1961_5(glval) = VariableAddress[y] : +# 1961| mu1961_6(D) = Store[y] : &:r1961_5, r1961_4 +# 1962| r1962_1(glval) = VariableAddress[j] : +# 1962| mu1962_2(D) = Uninitialized[j] : &:r1962_1 +# 1963| r1963_1(glval) = VariableAddress[d] : +# 1963| r1963_2(glval) = FunctionAddress[ObjectStaticMemberFunction] : +# 1963| r1963_3(D) = Call[ObjectStaticMemberFunction] : func:r1963_2 +# 1963| mu1963_4(unknown) = ^CallSideEffect : ~m? +# 1963| r1963_5(glval) = VariableAddress[j] : +# 1963| mu1963_6(D) = Store[j] : &:r1963_5, r1963_3 +# 1964| r1964_1(glval) = VariableAddress[k] : +# 1964| mu1964_2(D) = Uninitialized[k] : &:r1964_1 +# 1965| r1965_1(glval) = FunctionAddress[ObjectStaticMemberFunction] : +# 1965| r1965_2(D) = Call[ObjectStaticMemberFunction] : func:r1965_1 +# 1965| mu1965_3(unknown) = ^CallSideEffect : ~m? +# 1965| r1965_4(glval) = VariableAddress[k] : +# 1965| mu1965_5(D) = Store[k] : &:r1965_4, r1965_2 +# 1966| v1966_1(void) = NoOp : +# 1950| v1950_4(void) = ReturnVoid : +# 1950| v1950_5(void) = AliasedUse : ~m? +# 1950| v1950_6(void) = ExitFunction : + +# 1968| void test_volatile() +# 1968| Block 0 +# 1968| v1968_1(void) = EnterFunction : +# 1968| mu1968_2(unknown) = AliasedDefinition : +# 1968| mu1968_3(unknown) = InitializeNonLocal : +# 1969| r1969_1(glval) = VariableAddress[x] : +# 1969| mu1969_2(int) = Uninitialized[x] : &:r1969_1 +# 1970| r1970_1(glval) = VariableAddress[x] : +# 1970| r1970_2(int) = Load[x] : &:r1970_1, ~m? +# 1971| v1971_1(void) = NoOp : +# 1968| v1968_4(void) = ReturnVoid : +# 1968| v1968_5(void) = AliasedUse : ~m? +# 1968| v1968_6(void) = ExitFunction : + +# 1979| void value_category_test() +# 1979| Block 0 +# 1979| v1979_1(void) = EnterFunction : +# 1979| mu1979_2(unknown) = AliasedDefinition : +# 1979| mu1979_3(unknown) = InitializeNonLocal : +# 1980| r1980_1(glval) = VariableAddress[c] : +# 1980| mu1980_2(ValCat) = Uninitialized[c] : &:r1980_1 +#-----| r0_1(glval) = VariableAddress[#temp0:0] : +#-----| mu0_2(ValCat) = Uninitialized[#temp0:0] : &:r0_1 +#-----| r0_3(ValCat) = Load[#temp0:0] : &:r0_1, ~m? +# 1982| r1982_1(glval) = VariableAddress[c] : +# 1982| r1982_2(glval) = FunctionAddress[lvalue] : +# 1982| r1982_3(ValCat &) = Call[lvalue] : func:r1982_2 +# 1982| mu1982_4(unknown) = ^CallSideEffect : ~m? +# 1982| r1982_5(glval) = CopyValue : r1982_3 +# 1982| mu1982_6(ValCat) = Store[?] : &:r1982_5, r0_3 +#-----| r0_4(glval) = VariableAddress[#temp0:0] : +#-----| mu0_5(ValCat) = Uninitialized[#temp0:0] : &:r0_4 +#-----| r0_6(ValCat) = Load[#temp0:0] : &:r0_4, ~m? +# 1983| r1983_1(glval) = VariableAddress[c] : +# 1983| r1983_2(glval) = FunctionAddress[xvalue] : +# 1983| r1983_3(ValCat &&) = Call[xvalue] : func:r1983_2 +# 1983| mu1983_4(unknown) = ^CallSideEffect : ~m? +# 1983| r1983_5(glval) = CopyValue : r1983_3 +# 1983| mu1983_6(ValCat) = Store[?] : &:r1983_5, r0_6 +#-----| r0_7(glval) = VariableAddress[#temp0:0] : +#-----| mu0_8(ValCat) = Uninitialized[#temp0:0] : &:r0_7 +#-----| r0_9(ValCat) = Load[#temp0:0] : &:r0_7, ~m? +# 1984| r1984_1(glval) = VariableAddress[#temp1984:5] : +# 1984| r1984_2(glval) = VariableAddress[c] : +# 1984| r1984_3(glval) = FunctionAddress[prvalue] : +# 1984| r1984_4(ValCat) = Call[prvalue] : func:r1984_3 +# 1984| mu1984_5(unknown) = ^CallSideEffect : ~m? +# 1984| mu1984_6(ValCat) = Store[#temp1984:5] : &:r1984_1, r1984_4 +# 1984| mu1984_7(ValCat) = Store[#temp1984:5] : &:r1984_1, r0_9 +#-----| r0_10(glval) = VariableAddress[#temp0:0] : +#-----| mu0_11(ValCat) = Uninitialized[#temp0:0] : &:r0_10 +#-----| r0_12(ValCat) = Load[#temp0:0] : &:r0_10, ~m? +# 1985| r1985_1(glval) = FunctionAddress[lvalue] : +# 1985| r1985_2(ValCat &) = Call[lvalue] : func:r1985_1 +# 1985| mu1985_3(unknown) = ^CallSideEffect : ~m? +# 1985| r1985_4(glval) = CopyValue : r1985_2 +# 1985| mu1985_5(ValCat) = Store[?] : &:r1985_4, r0_12 +#-----| r0_13(glval) = VariableAddress[#temp0:0] : +#-----| mu0_14(ValCat) = Uninitialized[#temp0:0] : &:r0_13 +#-----| r0_15(ValCat) = Load[#temp0:0] : &:r0_13, ~m? +# 1986| r1986_1(glval) = FunctionAddress[xvalue] : +# 1986| r1986_2(ValCat &&) = Call[xvalue] : func:r1986_1 +# 1986| mu1986_3(unknown) = ^CallSideEffect : ~m? +# 1986| r1986_4(glval) = CopyValue : r1986_2 +# 1986| mu1986_5(ValCat) = Store[?] : &:r1986_4, r0_15 +#-----| r0_16(glval) = VariableAddress[#temp0:0] : +#-----| mu0_17(ValCat) = Uninitialized[#temp0:0] : &:r0_16 +#-----| r0_18(ValCat) = Load[#temp0:0] : &:r0_16, ~m? +# 1987| r1987_1(glval) = VariableAddress[#temp1987:5] : +# 1987| r1987_2(glval) = FunctionAddress[prvalue] : +# 1987| r1987_3(ValCat) = Call[prvalue] : func:r1987_2 +# 1987| mu1987_4(unknown) = ^CallSideEffect : ~m? +# 1987| mu1987_5(ValCat) = Store[#temp1987:5] : &:r1987_1, r1987_3 +# 1987| mu1987_6(ValCat) = Store[#temp1987:5] : &:r1987_1, r0_18 +# 1988| v1988_1(void) = NoOp : +# 1979| v1979_4(void) = ReturnVoid : +# 1979| v1979_5(void) = AliasedUse : ~m? +# 1979| v1979_6(void) = ExitFunction : + +# 1990| void SetStaticFuncPtr() +# 1990| Block 0 +# 1990| v1990_1(void) = EnterFunction : +# 1990| mu1990_2(unknown) = AliasedDefinition : +# 1990| mu1990_3(unknown) = InitializeNonLocal : +# 1991| r1991_1(glval) = VariableAddress[c] : +# 1991| mu1991_2(C) = Uninitialized[c] : &:r1991_1 +# 1991| r1991_3(glval) = FunctionAddress[C] : +# 1991| v1991_4(void) = Call[C] : func:r1991_3, this:r1991_1 +# 1991| mu1991_5(unknown) = ^CallSideEffect : ~m? +# 1991| mu1991_6(C) = ^IndirectMayWriteSideEffect[-1] : &:r1991_1 +# 1992| r1992_1(glval<..(*)(..)>) = VariableAddress[pfn] : +# 1992| r1992_2(..(*)(..)) = FunctionAddress[StaticMemberFunction] : +# 1992| mu1992_3(..(*)(..)) = Store[pfn] : &:r1992_1, r1992_2 +# 1993| r1993_1(glval) = VariableAddress[c] : +# 1993| r1993_2(..(*)(..)) = FunctionAddress[StaticMemberFunction] : +# 1993| r1993_3(glval<..(*)(..)>) = VariableAddress[pfn] : +# 1993| mu1993_4(..(*)(..)) = Store[pfn] : &:r1993_3, r1993_2 +# 1994| v1994_1(void) = NoOp : +# 1990| v1990_4(void) = ReturnVoid : +# 1990| v1990_5(void) = AliasedUse : ~m? +# 1990| v1990_6(void) = ExitFunction : + perf-regression.cpp: # 6| void Big::Big() # 6| Block 0 diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/semmle/ExecTainted/ExecTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-078/semmle/ExecTainted/ExecTainted.expected index 24b2320c83f..4916d84cf47 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-078/semmle/ExecTainted/ExecTainted.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-078/semmle/ExecTainted/ExecTainted.expected @@ -22,7 +22,8 @@ edges | test.cpp:113:20:113:25 | call to getenv | test.cpp:114:19:114:22 | path indirection | | test.cpp:113:20:113:38 | call to getenv indirection | test.cpp:114:19:114:22 | path indirection | | test.cpp:114:10:114:23 | call to operator+ | test.cpp:114:25:114:29 | call to c_str indirection | -| test.cpp:114:17:114:17 | call to operator+ | test.cpp:114:25:114:29 | call to c_str indirection | +| test.cpp:114:10:114:23 | call to operator+ | test.cpp:114:25:114:29 | call to c_str indirection | +| test.cpp:114:17:114:17 | call to operator+ | test.cpp:114:10:114:23 | call to operator+ | | test.cpp:114:19:114:22 | path indirection | test.cpp:114:10:114:23 | call to operator+ | | test.cpp:114:19:114:22 | path indirection | test.cpp:114:17:114:17 | call to operator+ | | test.cpp:119:20:119:25 | call to getenv | test.cpp:120:19:120:22 | path indirection | @@ -33,18 +34,23 @@ edges | test.cpp:142:11:142:17 | sprintf output argument | test.cpp:143:10:143:16 | command indirection | | test.cpp:142:31:142:33 | str indirection | test.cpp:142:11:142:17 | sprintf output argument | | test.cpp:174:9:174:16 | fread output argument | test.cpp:177:20:177:27 | filename indirection | -| test.cpp:174:9:174:16 | fread output argument | test.cpp:178:22:178:26 | flags indirection | | test.cpp:174:9:174:16 | fread output argument | test.cpp:180:22:180:29 | filename indirection | -| test.cpp:177:13:177:17 | strncat output argument | test.cpp:183:32:183:38 | command indirection | +| test.cpp:177:13:177:17 | strncat output argument | test.cpp:178:22:178:26 | flags indirection | +| test.cpp:177:13:177:17 | strncat output argument | test.cpp:178:22:178:26 | flags indirection | +| test.cpp:177:20:177:27 | filename indirection | test.cpp:177:13:177:17 | strncat output argument | | test.cpp:177:20:177:27 | filename indirection | test.cpp:177:13:177:17 | strncat output argument | | test.cpp:178:13:178:19 | strncat output argument | test.cpp:183:32:183:38 | command indirection | +| test.cpp:178:13:178:19 | strncat output argument | test.cpp:183:32:183:38 | command indirection | +| test.cpp:178:22:178:26 | flags indirection | test.cpp:178:13:178:19 | strncat output argument | | test.cpp:178:22:178:26 | flags indirection | test.cpp:178:13:178:19 | strncat output argument | | test.cpp:180:13:180:19 | strncat output argument | test.cpp:183:32:183:38 | command indirection | | test.cpp:180:22:180:29 | filename indirection | test.cpp:180:13:180:19 | strncat output argument | | test.cpp:186:47:186:54 | filename indirection | test.cpp:187:18:187:25 | filename indirection | -| test.cpp:186:47:186:54 | filename indirection | test.cpp:188:20:188:24 | flags indirection | -| test.cpp:187:11:187:15 | strncat output argument | test.cpp:188:11:188:17 | strncat output argument | +| test.cpp:187:11:187:15 | strncat output argument | test.cpp:188:20:188:24 | flags indirection | +| test.cpp:187:11:187:15 | strncat output argument | test.cpp:188:20:188:24 | flags indirection | | test.cpp:187:18:187:25 | filename indirection | test.cpp:187:11:187:15 | strncat output argument | +| test.cpp:187:18:187:25 | filename indirection | test.cpp:187:11:187:15 | strncat output argument | +| test.cpp:188:20:188:24 | flags indirection | test.cpp:188:11:188:17 | strncat output argument | | test.cpp:188:20:188:24 | flags indirection | test.cpp:188:11:188:17 | strncat output argument | | test.cpp:194:9:194:16 | fread output argument | test.cpp:196:26:196:33 | filename indirection | | test.cpp:196:10:196:16 | concat output argument | test.cpp:198:32:198:38 | command indirection | @@ -53,11 +59,16 @@ edges | test.cpp:196:26:196:33 | filename indirection | test.cpp:196:10:196:16 | concat output argument | | test.cpp:196:26:196:33 | filename indirection | test.cpp:196:10:196:16 | concat output argument | | test.cpp:218:9:218:16 | fread output argument | test.cpp:220:19:220:26 | filename indirection | -| test.cpp:218:9:218:16 | fread output argument | test.cpp:220:19:220:26 | filename indirection | +| test.cpp:220:10:220:16 | strncat output argument | test.cpp:220:10:220:16 | strncat output argument | +| test.cpp:220:10:220:16 | strncat output argument | test.cpp:220:10:220:16 | strncat output argument | +| test.cpp:220:10:220:16 | strncat output argument | test.cpp:220:10:220:16 | strncat output argument | +| test.cpp:220:10:220:16 | strncat output argument | test.cpp:222:32:222:38 | command indirection | +| test.cpp:220:10:220:16 | strncat output argument | test.cpp:222:32:222:38 | command indirection | | test.cpp:220:10:220:16 | strncat output argument | test.cpp:222:32:222:38 | command indirection | | test.cpp:220:10:220:16 | strncat output argument | test.cpp:222:32:222:38 | command indirection | | test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument | | test.cpp:220:19:220:26 | filename indirection | test.cpp:220:10:220:16 | strncat output argument | +| test.cpp:220:19:220:26 | filename indirection | test.cpp:220:19:220:26 | filename indirection | nodes | test.cpp:15:27:15:30 | argv indirection | semmle.label | argv indirection | | test.cpp:15:27:15:30 | argv indirection | semmle.label | argv indirection | @@ -88,6 +99,7 @@ nodes | test.cpp:113:20:113:25 | call to getenv | semmle.label | call to getenv | | test.cpp:113:20:113:38 | call to getenv indirection | semmle.label | call to getenv indirection | | test.cpp:114:10:114:23 | call to operator+ | semmle.label | call to operator+ | +| test.cpp:114:10:114:23 | call to operator+ | semmle.label | call to operator+ | | test.cpp:114:17:114:17 | call to operator+ | semmle.label | call to operator+ | | test.cpp:114:19:114:22 | path indirection | semmle.label | path indirection | | test.cpp:114:25:114:29 | call to c_str indirection | semmle.label | call to c_str indirection | @@ -103,8 +115,11 @@ nodes | test.cpp:143:10:143:16 | command indirection | semmle.label | command indirection | | test.cpp:174:9:174:16 | fread output argument | semmle.label | fread output argument | | test.cpp:177:13:177:17 | strncat output argument | semmle.label | strncat output argument | +| test.cpp:177:13:177:17 | strncat output argument | semmle.label | strncat output argument | | test.cpp:177:20:177:27 | filename indirection | semmle.label | filename indirection | | test.cpp:178:13:178:19 | strncat output argument | semmle.label | strncat output argument | +| test.cpp:178:13:178:19 | strncat output argument | semmle.label | strncat output argument | +| test.cpp:178:22:178:26 | flags indirection | semmle.label | flags indirection | | test.cpp:178:22:178:26 | flags indirection | semmle.label | flags indirection | | test.cpp:180:13:180:19 | strncat output argument | semmle.label | strncat output argument | | test.cpp:180:22:180:29 | filename indirection | semmle.label | filename indirection | @@ -113,10 +128,12 @@ nodes | test.cpp:183:32:183:38 | command indirection | semmle.label | command indirection | | test.cpp:186:47:186:54 | filename indirection | semmle.label | filename indirection | | test.cpp:187:11:187:15 | strncat output argument | semmle.label | strncat output argument | +| test.cpp:187:11:187:15 | strncat output argument | semmle.label | strncat output argument | | test.cpp:187:18:187:25 | filename indirection | semmle.label | filename indirection | | test.cpp:188:11:188:17 | strncat output argument | semmle.label | strncat output argument | | test.cpp:188:11:188:17 | strncat output argument | semmle.label | strncat output argument | | test.cpp:188:20:188:24 | flags indirection | semmle.label | flags indirection | +| test.cpp:188:20:188:24 | flags indirection | semmle.label | flags indirection | | test.cpp:194:9:194:16 | fread output argument | semmle.label | fread output argument | | test.cpp:196:10:196:16 | concat output argument | semmle.label | concat output argument | | test.cpp:196:10:196:16 | concat output argument | semmle.label | concat output argument | @@ -126,6 +143,8 @@ nodes | test.cpp:218:9:218:16 | fread output argument | semmle.label | fread output argument | | test.cpp:220:10:220:16 | strncat output argument | semmle.label | strncat output argument | | test.cpp:220:10:220:16 | strncat output argument | semmle.label | strncat output argument | +| test.cpp:220:10:220:16 | strncat output argument | semmle.label | strncat output argument | +| test.cpp:220:10:220:16 | strncat output argument | semmle.label | strncat output argument | | test.cpp:220:19:220:26 | filename indirection | semmle.label | filename indirection | | test.cpp:220:19:220:26 | filename indirection | semmle.label | filename indirection | | test.cpp:222:32:222:38 | command indirection | semmle.label | command indirection | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/UninitializedLocal.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/UninitializedLocal.expected index 6ef3b5d555d..2b5e6549a30 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/UninitializedLocal.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/UninitializedLocal.expected @@ -14,3 +14,7 @@ | test.cpp:378:9:378:11 | val | The variable $@ may not be initialized at this access. | test.cpp:359:6:359:8 | val | val | | test.cpp:417:10:417:10 | j | The variable $@ may not be initialized at this access. | test.cpp:414:9:414:9 | j | j | | test.cpp:436:9:436:9 | j | The variable $@ may not be initialized at this access. | test.cpp:431:9:431:9 | j | j | +| test.cpp:454:2:454:2 | x | The variable $@ may not be initialized at this access. | test.cpp:452:6:452:6 | x | x | +| test.cpp:460:7:460:7 | x | The variable $@ may not be initialized at this access. | test.cpp:458:6:458:6 | x | x | +| test.cpp:467:2:467:2 | x | The variable $@ may not be initialized at this access. | test.cpp:464:6:464:6 | x | x | +| test.cpp:474:7:474:7 | x | The variable $@ may not be initialized at this access. | test.cpp:471:6:471:6 | x | x | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/test.cpp index d289d223a48..7660f564d6d 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/test.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/test.cpp @@ -435,3 +435,41 @@ int test38() { return j; // BAD } + +void test39() { + int x; + + x; // GOOD, in void context +} + +void test40() { + int x; + + (void)x; // GOOD, explicitly cast to void +} + +void test41() { + int x; + + x++; // BAD +} + +void test42() { + int x; + + void(x++); // BAD +} + +void test43() { + int x; + int y = 1; + + x + y; // BAD +} + +void test44() { + int x; + int y = 1; + + void(x + y); // BAD +} \ No newline at end of file diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs index ad501c9e758..f4aa614a345 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs @@ -66,7 +66,7 @@ namespace Semmle.BuildAnalyser // Find DLLs in the .Net Framework if (options.ScanNetFrameworkDlls) { - var runtimeLocation = Runtime.GetRuntime(options.UseSelfContainedDotnet); + var runtimeLocation = new Runtime(dotnet).GetRuntime(options.UseSelfContainedDotnet); progressMonitor.Log(Util.Logging.Severity.Debug, $"Runtime location selected: {runtimeLocation}"); dllDirNames.Add(runtimeLocation); } @@ -133,9 +133,9 @@ namespace Semmle.BuildAnalyser DateTime.Now - startTime); } - private IEnumerable GetFiles(string pattern) + private IEnumerable GetFiles(string pattern, bool recurseSubdirectories = true) { - return sourceDir.GetFiles(pattern, SearchOption.AllDirectories) + return sourceDir.GetFiles(pattern, new EnumerationOptions { RecurseSubdirectories = recurseSubdirectories, MatchCasing = MatchCasing.CaseInsensitive }) .Select(d => d.FullName) .Where(d => !options.ExcludesFile(d)); } @@ -318,16 +318,16 @@ namespace Semmle.BuildAnalyser } - private bool Restore(string target) + private bool Restore(string target, string? pathToNugetConfig = null) { - return dotnet.RestoreToDirectory(target, packageDirectory.DirInfo.FullName); + return dotnet.RestoreToDirectory(target, packageDirectory.DirInfo.FullName, pathToNugetConfig); } - private void Restore(IEnumerable targets) + private void Restore(IEnumerable targets, string? pathToNugetConfig = null) { foreach (var target in targets) { - Restore(target); + Restore(target, pathToNugetConfig); } } @@ -336,7 +336,23 @@ namespace Semmle.BuildAnalyser var alreadyDownloadedPackages = Directory.GetDirectories(packageDirectory.DirInfo.FullName).Select(d => Path.GetFileName(d).ToLowerInvariant()).ToHashSet(); var notYetDownloadedPackages = new HashSet(); - var allFiles = GetFiles("*.*").ToArray(); + var nugetConfigs = GetFiles("nuget.config", recurseSubdirectories: true).ToArray(); + string? nugetConfig = null; + if (nugetConfigs.Length > 1) + { + progressMonitor.MultipleNugetConfig(nugetConfigs); + nugetConfig = GetFiles("nuget.config", recurseSubdirectories: false).FirstOrDefault(); + if (nugetConfig == null) + { + progressMonitor.NoTopLevelNugetConfig(); + } + } + else + { + nugetConfig = nugetConfigs.FirstOrDefault(); + } + + var allFiles = GetFiles("*.*"); foreach (var file in allFiles) { try @@ -390,7 +406,7 @@ namespace Semmle.BuildAnalyser continue; } - success = Restore(tempDir.DirInfo.FullName); + success = Restore(tempDir.DirInfo.FullName, nugetConfig); // TODO: the restore might fail, we could retry with a prerelease (*-* instead of *) version of the package. diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs index dbe3b2c4a1e..b9e641a9de1 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs @@ -1,13 +1,24 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using Semmle.Util; namespace Semmle.BuildAnalyser { + internal interface IDotNet + { + bool RestoreToDirectory(string project, string directory, string? pathToNugetConfig = null); + bool New(string folder); + bool AddPackage(string folder, string package); + public IList GetListedRuntimes(); + } + /// /// Utilities to run the "dotnet" command. /// - internal class DotNet + internal class DotNet : IDotNet { + private const string dotnet = "dotnet"; private readonly ProgressMonitor progressMonitor; public DotNet(ProgressMonitor progressMonitor) @@ -19,35 +30,37 @@ namespace Semmle.BuildAnalyser private void Info() { // TODO: make sure the below `dotnet` version is matching the one specified in global.json - progressMonitor.RunningProcess("dotnet --info"); - using var proc = Process.Start("dotnet", "--info"); + progressMonitor.RunningProcess($"{dotnet} --info"); + using var proc = Process.Start(dotnet, "--info"); proc.WaitForExit(); var ret = proc.ExitCode; if (ret != 0) { - progressMonitor.CommandFailed("dotnet", "--info", ret); - throw new Exception($"dotnet --info failed with exit code {ret}."); + progressMonitor.CommandFailed(dotnet, "--info", ret); + throw new Exception($"{dotnet} --info failed with exit code {ret}."); } } private bool RunCommand(string args) { - progressMonitor.RunningProcess($"dotnet {args}"); - using var proc = Process.Start("dotnet", args); + progressMonitor.RunningProcess($"{dotnet} {args}"); + using var proc = Process.Start(dotnet, args); proc.WaitForExit(); if (proc.ExitCode != 0) { - progressMonitor.CommandFailed("dotnet", args, proc.ExitCode); + progressMonitor.CommandFailed(dotnet, args, proc.ExitCode); return false; } return true; } - public bool RestoreToDirectory(string projectOrSolutionFile, string packageDirectory) + public bool RestoreToDirectory(string projectOrSolutionFile, string packageDirectory, string? pathToNugetConfig = null) { var args = $"restore --no-dependencies \"{projectOrSolutionFile}\" --packages \"{packageDirectory}\" /p:DisableImplicitNuGetFallbackFolder=true"; + if (pathToNugetConfig != null) + args += $" --configfile \"{pathToNugetConfig}\""; return RunCommand(args); } @@ -62,5 +75,22 @@ namespace Semmle.BuildAnalyser var args = $"add \"{folder}\" package \"{package}\" --no-restore"; return RunCommand(args); } + + public IList GetListedRuntimes() + { + var args = "--list-runtimes"; + var pi = new ProcessStartInfo(dotnet, args) + { + RedirectStandardOutput = true, + UseShellExecute = false + }; + var exitCode = pi.ReadOutput(out var runtimes); + if (exitCode != 0) + { + progressMonitor.CommandFailed(dotnet, args, exitCode); + return new List(); + } + return runtimes; + } } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/ProgressMonitor.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/ProgressMonitor.cs index de1c5274c37..233ba969fea 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/ProgressMonitor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/ProgressMonitor.cs @@ -118,5 +118,15 @@ namespace Semmle.BuildAnalyser logger.Log(Severity.Info, $"Failed to read file {file}"); logger.Log(Severity.Debug, $"Failed to read file {file}, exception: {ex}"); } + + public void MultipleNugetConfig(string[] nugetConfigs) + { + logger.Log(Severity.Info, $"Found multiple nuget.config files: {string.Join(", ", nugetConfigs)}."); + } + + internal void NoTopLevelNugetConfig() + { + logger.Log(Severity.Info, $"Could not find a top-level nuget.config file."); + } } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Properties/AssemblyInfo.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Properties/AssemblyInfo.cs index 4c5502fcde5..ed0de1e457b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Properties/AssemblyInfo.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -13,6 +14,9 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +// Expose internals for testing purposes. +[assembly: InternalsVisibleTo("Semmle.Extraction.Tests")] + // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs index 4b76efc3d65..bb521523ead 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using System.IO; using System.Linq; +using System.Text.RegularExpressions; +using Semmle.BuildAnalyser; using Semmle.Util; namespace Semmle.Extraction.CSharp.Standalone @@ -10,31 +12,105 @@ namespace Semmle.Extraction.CSharp.Standalone /// /// Locates .NET Runtimes. /// - internal static class Runtime + internal partial class Runtime { + private const string netCoreApp = "Microsoft.NETCore.App"; + private const string aspNetCoreApp = "Microsoft.AspNetCore.App"; + + private readonly IDotNet dotNet; private static string ExecutingRuntime => RuntimeEnvironment.GetRuntimeDirectory(); - /// - /// Locates .NET Core Runtimes. - /// - private static IEnumerable CoreRuntimes - { - get - { - var dotnetPath = FileUtils.FindProgramOnPath(Win32.IsWindows() ? "dotnet.exe" : "dotnet"); - var dotnetDirs = dotnetPath is not null - ? new[] { dotnetPath } - : new[] { "/usr/share/dotnet", @"C:\Program Files\dotnet" }; - var coreDirs = dotnetDirs.Select(d => Path.Combine(d, "shared", "Microsoft.NETCore.App")); + public Runtime(IDotNet dotNet) => this.dotNet = dotNet; - var dir = coreDirs.FirstOrDefault(Directory.Exists); - if (dir is not null) + internal sealed class RuntimeVersion : IComparable + { + private readonly string dir; + private readonly Version version; + private readonly Version? preReleaseVersion; + private readonly string? preReleaseVersionType; + private bool IsPreRelease => preReleaseVersionType is not null && preReleaseVersion is not null; + public string FullPath + { + get { - return Directory.EnumerateDirectories(dir).OrderByDescending(Path.GetFileName); + var preRelease = IsPreRelease ? $"-{preReleaseVersionType}.{preReleaseVersion}" : ""; + var version = this.version + preRelease; + return Path.Combine(dir, version); + } + } + + public RuntimeVersion(string dir, string version, string preReleaseVersionType, string preReleaseVersion) + { + this.dir = dir; + this.version = Version.Parse(version); + if (!string.IsNullOrEmpty(preReleaseVersion) && !string.IsNullOrEmpty(preReleaseVersionType)) + { + this.preReleaseVersionType = preReleaseVersionType; + this.preReleaseVersion = Version.Parse(preReleaseVersion); + } + } + + public int CompareTo(RuntimeVersion? other) + { + var c = version.CompareTo(other?.version); + if (c == 0 && IsPreRelease) + { + if (!other!.IsPreRelease) + { + return -1; + } + + // Both are pre-release like runtime versions. + // The pre-release version types are sorted alphabetically (e.g. alpha, beta, preview, rc) + // and the pre-release version types are more important that the pre-release version numbers. + return preReleaseVersionType != other!.preReleaseVersionType + ? preReleaseVersionType!.CompareTo(other!.preReleaseVersionType) + : preReleaseVersion!.CompareTo(other!.preReleaseVersion); } - return Enumerable.Empty(); + return c; } + + public override bool Equals(object? obj) => + obj is not null && obj is RuntimeVersion other && other.FullPath == FullPath; + + public override int GetHashCode() => FullPath.GetHashCode(); + + public override string ToString() => FullPath; + } + + [GeneratedRegex(@"^(\S+)\s(\d+\.\d+\.\d+)(-([a-z]+)\.(\d+\.\d+\.\d+))?\s\[(\S+)\]$")] + private static partial Regex RuntimeRegex(); + + /// + /// Parses the output of `dotnet --list-runtimes` to get a map from a runtime to the location of + /// the newest version of the runtime. + /// It is assume that the format of a listed runtime is something like: + /// Microsoft.NETCore.App 7.0.2 [/usr/share/dotnet/shared/Microsoft.NETCore.App] + /// + private static Dictionary ParseRuntimes(IList listed) + { + // Parse listed runtimes. + var runtimes = new Dictionary(); + listed.ForEach(r => + { + var match = RuntimeRegex().Match(r); + if (match.Success) + { + runtimes.AddOrUpdate(match.Groups[1].Value, new RuntimeVersion(match.Groups[6].Value, match.Groups[2].Value, match.Groups[4].Value, match.Groups[5].Value)); + } + }); + + return runtimes; + } + + /// + /// Returns a dictionary mapping runtimes to their newest version. + /// + internal Dictionary GetNewestRuntimes() + { + var listed = dotNet.GetListedRuntimes(); + return ParseRuntimes(listed); } /// @@ -69,24 +145,33 @@ namespace Semmle.Extraction.CSharp.Standalone } } + private IEnumerable GetRuntimes() + { + // Gets the newest version of the installed runtimes. + var newestRuntimes = GetNewestRuntimes(); + + // Location of the newest .NET Core Runtime. + if (newestRuntimes.TryGetValue(netCoreApp, out var netCoreVersion)) + { + yield return netCoreVersion.FullPath; + } + + // Location of the newest ASP.NET Core Runtime. + if (newestRuntimes.TryGetValue(aspNetCoreApp, out var aspNetCoreVersion)) + { + yield return aspNetCoreVersion.FullPath; + } + + foreach (var r in DesktopRuntimes) + yield return r; + + // A bad choice if it's the self-contained runtime distributed in codeql dist. + yield return ExecutingRuntime; + } + /// /// Gets the .NET runtime location to use for extraction /// - public static string GetRuntime(bool useSelfContained) => useSelfContained ? ExecutingRuntime : Runtimes.First(); - - private static IEnumerable Runtimes - { - get - { - foreach (var r in CoreRuntimes) - yield return r; - - foreach (var r in DesktopRuntimes) - yield return r; - - // A bad choice if it's the self-contained runtime distributed in codeql dist. - yield return ExecutingRuntime; - } - } + public string GetRuntime(bool useSelfContained) => useSelfContained ? ExecutingRuntime : GetRuntimes().First(); } } diff --git a/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs b/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs new file mode 100644 index 00000000000..07102d9a758 --- /dev/null +++ b/csharp/extractor/Semmle.Extraction.Tests/Runtime.cs @@ -0,0 +1,104 @@ +using Xunit; +using System.Collections.Generic; +using Semmle.BuildAnalyser; +using Semmle.Extraction.CSharp.Standalone; + +namespace Semmle.Extraction.Tests +{ + internal class DotNetStub : IDotNet + { + private readonly IList runtimes; + + public DotNetStub(IList runtimes) => this.runtimes = runtimes; + + public bool AddPackage(string folder, string package) => true; + + public bool New(string folder) => true; + + public bool RestoreToDirectory(string project, string directory, string? pathToNugetConfig = null) => true; + + public IList GetListedRuntimes() => runtimes; + } + + public class RuntimeTests + { + private static string FixExpectedPathOnWindows(string path) => path.Replace('\\', '/'); + + [Fact] + public void TestRuntime1() + { + // Setup + var listedRuntimes = new List { + "Microsoft.AspNetCore.App 5.0.12 [/path/dotnet/shared/Microsoft.AspNetCore.App]", + "Microsoft.AspNetCore.App 6.0.4 [/path/dotnet/shared/Microsoft.AspNetCore.App]", + "Microsoft.AspNetCore.App 7.0.0 [/path/dotnet/shared/Microsoft.AspNetCore.App]", + "Microsoft.AspNetCore.App 7.0.2 [/path/dotnet/shared/Microsoft.AspNetCore.App]", + "Microsoft.NETCore.App 5.0.12 [/path/dotnet/shared/Microsoft.NETCore.App]", + "Microsoft.NETCore.App 6.0.4 [/path/dotnet/shared/Microsoft.NETCore.App]", + "Microsoft.NETCore.App 7.0.0 [/path/dotnet/shared/Microsoft.NETCore.App]", + "Microsoft.NETCore.App 7.0.2 [/path/dotnet/shared/Microsoft.NETCore.App]" + }; + var dotnet = new DotNetStub(listedRuntimes); + var runtime = new Runtime(dotnet); + + // Execute + var runtimes = runtime.GetNewestRuntimes(); + + // Verify + Assert.Equal(2, runtimes.Count); + + Assert.True(runtimes.TryGetValue("Microsoft.AspNetCore.App", out var aspNetCoreApp)); + Assert.Equal("/path/dotnet/shared/Microsoft.AspNetCore.App/7.0.2", FixExpectedPathOnWindows(aspNetCoreApp.FullPath)); + + Assert.True(runtimes.TryGetValue("Microsoft.NETCore.App", out var netCoreApp)); + Assert.Equal("/path/dotnet/shared/Microsoft.NETCore.App/7.0.2", FixExpectedPathOnWindows(netCoreApp.FullPath)); + } + + [Fact] + public void TestRuntime2() + { + // Setup + var listedRuntimes = new List + { + "Microsoft.NETCore.App 7.0.2 [/path/dotnet/shared/Microsoft.NETCore.App]", + "Microsoft.NETCore.App 8.0.0-preview.5.43280.8 [/path/dotnet/shared/Microsoft.NETCore.App]", + "Microsoft.NETCore.App 8.0.0-preview.5.23280.8 [/path/dotnet/shared/Microsoft.NETCore.App]" + }; + var dotnet = new DotNetStub(listedRuntimes); + var runtime = new Runtime(dotnet); + + // Execute + var runtimes = runtime.GetNewestRuntimes(); + + // Verify + Assert.Single(runtimes); + + Assert.True(runtimes.TryGetValue("Microsoft.NETCore.App", out var netCoreApp)); + Assert.Equal("/path/dotnet/shared/Microsoft.NETCore.App/8.0.0-preview.5.43280.8", FixExpectedPathOnWindows(netCoreApp.FullPath)); + } + + [Fact] + public void TestRuntime3() + { + // Setup + var listedRuntimes = new List + { + "Microsoft.NETCore.App 7.0.2 [/path/dotnet/shared/Microsoft.NETCore.App]", + "Microsoft.NETCore.App 8.0.0-rc.4.43280.8 [/path/dotnet/shared/Microsoft.NETCore.App]", + "Microsoft.NETCore.App 8.0.0-preview.5.23280.8 [/path/dotnet/shared/Microsoft.NETCore.App]" + }; + var dotnet = new DotNetStub(listedRuntimes); + var runtime = new Runtime(dotnet); + + // Execute + var runtimes = runtime.GetNewestRuntimes(); + + // Verify + Assert.Single(runtimes); + + Assert.True(runtimes.TryGetValue("Microsoft.NETCore.App", out var netCoreApp)); + Assert.Equal("/path/dotnet/shared/Microsoft.NETCore.App/8.0.0-rc.4.43280.8", FixExpectedPathOnWindows(netCoreApp.FullPath)); + } + + } +} diff --git a/csharp/extractor/Semmle.Util/DictionaryExtensions.cs b/csharp/extractor/Semmle.Util/DictionaryExtensions.cs index 95c5443585f..fa932f0cd9c 100644 --- a/csharp/extractor/Semmle.Util/DictionaryExtensions.cs +++ b/csharp/extractor/Semmle.Util/DictionaryExtensions.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Semmle.Util { @@ -18,5 +19,17 @@ namespace Semmle.Util } list.Add(element); } + + /// + /// Adds a new value or replaces the existing value (if the new value is greater than the existing) + /// in dictionary for the given key. + /// + public static void AddOrUpdate(this Dictionary dict, T1 key, T2 value) where T1 : notnull where T2 : IComparable + { + if (!dict.TryGetValue(key, out var existing) || existing.CompareTo(value) < 0) + { + dict[key] = value; + } + } } } diff --git a/csharp/ql/campaigns/Solorigate/lib/CHANGELOG.md b/csharp/ql/campaigns/Solorigate/lib/CHANGELOG.md index f410a14eae6..3de1098514d 100644 --- a/csharp/ql/campaigns/Solorigate/lib/CHANGELOG.md +++ b/csharp/ql/campaigns/Solorigate/lib/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.6.1 + +No user-facing changes. + ## 1.6.0 No user-facing changes. diff --git a/csharp/ql/campaigns/Solorigate/lib/change-notes/released/1.6.1.md b/csharp/ql/campaigns/Solorigate/lib/change-notes/released/1.6.1.md new file mode 100644 index 00000000000..898f6201ed7 --- /dev/null +++ b/csharp/ql/campaigns/Solorigate/lib/change-notes/released/1.6.1.md @@ -0,0 +1,3 @@ +## 1.6.1 + +No user-facing changes. diff --git a/csharp/ql/campaigns/Solorigate/lib/codeql-pack.release.yml b/csharp/ql/campaigns/Solorigate/lib/codeql-pack.release.yml index c4f0b07d533..ef7a789e0cf 100644 --- a/csharp/ql/campaigns/Solorigate/lib/codeql-pack.release.yml +++ b/csharp/ql/campaigns/Solorigate/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 1.6.0 +lastReleaseVersion: 1.6.1 diff --git a/csharp/ql/campaigns/Solorigate/lib/qlpack.yml b/csharp/ql/campaigns/Solorigate/lib/qlpack.yml index e83567f77f5..327f33fd5b3 100644 --- a/csharp/ql/campaigns/Solorigate/lib/qlpack.yml +++ b/csharp/ql/campaigns/Solorigate/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-solorigate-all -version: 1.6.0 +version: 1.6.1 groups: - csharp - solorigate diff --git a/csharp/ql/campaigns/Solorigate/src/CHANGELOG.md b/csharp/ql/campaigns/Solorigate/src/CHANGELOG.md index f410a14eae6..3de1098514d 100644 --- a/csharp/ql/campaigns/Solorigate/src/CHANGELOG.md +++ b/csharp/ql/campaigns/Solorigate/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.6.1 + +No user-facing changes. + ## 1.6.0 No user-facing changes. diff --git a/csharp/ql/campaigns/Solorigate/src/change-notes/released/1.6.1.md b/csharp/ql/campaigns/Solorigate/src/change-notes/released/1.6.1.md new file mode 100644 index 00000000000..898f6201ed7 --- /dev/null +++ b/csharp/ql/campaigns/Solorigate/src/change-notes/released/1.6.1.md @@ -0,0 +1,3 @@ +## 1.6.1 + +No user-facing changes. diff --git a/csharp/ql/campaigns/Solorigate/src/codeql-pack.release.yml b/csharp/ql/campaigns/Solorigate/src/codeql-pack.release.yml index c4f0b07d533..ef7a789e0cf 100644 --- a/csharp/ql/campaigns/Solorigate/src/codeql-pack.release.yml +++ b/csharp/ql/campaigns/Solorigate/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 1.6.0 +lastReleaseVersion: 1.6.1 diff --git a/csharp/ql/campaigns/Solorigate/src/qlpack.yml b/csharp/ql/campaigns/Solorigate/src/qlpack.yml index 4ff99e03b0e..cb168a76984 100644 --- a/csharp/ql/campaigns/Solorigate/src/qlpack.yml +++ b/csharp/ql/campaigns/Solorigate/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-solorigate-queries -version: 1.6.0 +version: 1.6.1 groups: - csharp - solorigate diff --git a/csharp/ql/lib/CHANGELOG.md b/csharp/ql/lib/CHANGELOG.md index 57ddb064fe6..b2a792d29a9 100644 --- a/csharp/ql/lib/CHANGELOG.md +++ b/csharp/ql/lib/CHANGELOG.md @@ -1,3 +1,18 @@ +## 0.7.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. + ## 0.7.0 ### Major Analysis Improvements diff --git a/csharp/ql/lib/change-notes/released/0.7.1.md b/csharp/ql/lib/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..99739d7931c --- /dev/null +++ b/csharp/ql/lib/change-notes/released/0.7.1.md @@ -0,0 +1,14 @@ +## 0.7.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. diff --git a/csharp/ql/lib/codeql-pack.release.yml b/csharp/ql/lib/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/csharp/ql/lib/codeql-pack.release.yml +++ b/csharp/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/csharp/ql/lib/qlpack.yml b/csharp/ql/lib/qlpack.yml index 46c97087657..414e410efa5 100644 --- a/csharp/ql/lib/qlpack.yml +++ b/csharp/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-all -version: 0.7.0 +version: 0.7.1 groups: csharp dbscheme: semmlecode.csharp.dbscheme extractor: csharp diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/ContentDataFlow.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/ContentDataFlow.qll index 9d483900aeb..fc937b88fa9 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/ContentDataFlow.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/ContentDataFlow.qll @@ -107,8 +107,6 @@ module Global { predicate isBarrier = ContentConfig::isBarrier/1; - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - DataFlow::FlowFeature getAFeature() { result = ContentConfig::getAFeature() } // needed to record reads/stores inside summarized callables diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll index f34554d6eea..03975c6a54a 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll @@ -46,6 +46,14 @@ signature module ConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or isAdditionalFlowStep(_, node) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -114,7 +122,7 @@ signature module StateConfigSig { * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state); + default predicate isBarrier(Node node, FlowState state) { none() } /** Holds if data flow into `node` is prohibited. */ default predicate isBarrierIn(Node node) { none() } @@ -131,7 +139,9 @@ signature module StateConfigSig { * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + default predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } /** * Holds if an arbitrary number of implicit read steps of content `c` may be @@ -139,6 +149,17 @@ signature module StateConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or + isAdditionalFlowStep(_, node) or + isAdditionalFlowStep(node, _, _, _) or + isAdditionalFlowStep(_, _, node, _) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 410543e0fc9..29561b0f0a6 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -66,6 +66,12 @@ signature module FullStateConfigSig { */ predicate allowImplicitRead(Node node, ContentSet c); + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + predicate neverSkip(Node node); + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -254,6 +260,11 @@ module Impl { not fullBarrier(node2) } + pragma[nomagic] + private predicate isUnreachableInCall1(NodeEx n, LocalCallContextSpecificCall cc) { + isUnreachableInCallCached(n.asNode(), cc.getCall()) + } + /** * Holds if data can flow in one local step from `node1` to `node2`. */ @@ -2019,7 +2030,8 @@ module Impl { castNode(this.asNode()) or clearsContentCached(this.asNode(), _) or expectsContentCached(this.asNode(), _) or - neverSkipInPathGraph(this.asNode()) + neverSkipInPathGraph(this.asNode()) or + Config::neverSkip(this.asNode()) } } @@ -2108,7 +2120,7 @@ module Impl { NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, LocalCallContext cc ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCall1(node2, cc) and ( localFlowEntry(node1, pragma[only_bind_into](state)) and ( @@ -2123,7 +2135,7 @@ module Impl { ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) + not isUnreachableInCall1(node1, cc) or exists(NodeEx mid | localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and @@ -2160,10 +2172,8 @@ module Impl { preservesValue = false and t = node2.getDataFlowType() and callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) + not isUnreachableInCall1(node1, callContext) and + not isUnreachableInCall1(node2, callContext) } } @@ -2703,7 +2713,7 @@ module Impl { ParamNodeEx getParamNode() { result = p } - override string toString() { result = p + ": " + ap } + override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn @@ -2755,12 +2765,21 @@ module Impl { ) } + private predicate forceUnfold(AccessPathApprox apa) { + forceHighPrecision(apa.getHead()) + or + exists(Content c2 | + apa = TConsCons(_, _, c2, _) and + forceHighPrecision(c2) + ) + } + /** * Holds with `unfold = false` if a precise head-tail representation of `apa` is * expected to be expensive. Holds with `unfold = true` otherwise. */ private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { - if forceHighPrecision(apa.getHead()) + if forceUnfold(apa) then unfold = true else exists(int aps, int nodes, int apLimit, int tupleLimit | @@ -3089,6 +3108,12 @@ module Impl { result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } + private string ppSummaryCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">" + } + /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() } @@ -3097,7 +3122,9 @@ module Impl { * representation of the call context. */ string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + result = + this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + + this.ppSummaryCtx() } /** diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll index be70086a93a..b0de9745816 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index be70086a93a..b0de9745816 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index be70086a93a..b0de9745816 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index be70086a93a..b0de9745816 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index be70086a93a..b0de9745816 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/csharp/ql/src/CHANGELOG.md b/csharp/ql/src/CHANGELOG.md index 302ba09808f..74584ff4772 100644 --- a/csharp/ql/src/CHANGELOG.md +++ b/csharp/ql/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.1 + +No user-facing changes. + ## 0.7.0 ### New Queries diff --git a/csharp/ql/src/change-notes/released/0.7.1.md b/csharp/ql/src/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..86973d36042 --- /dev/null +++ b/csharp/ql/src/change-notes/released/0.7.1.md @@ -0,0 +1,3 @@ +## 0.7.1 + +No user-facing changes. diff --git a/csharp/ql/src/codeql-pack.release.yml b/csharp/ql/src/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/csharp/ql/src/codeql-pack.release.yml +++ b/csharp/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/csharp/ql/src/qlpack.yml b/csharp/ql/src/qlpack.yml index 3a159ef7586..4df6fd6a92d 100644 --- a/csharp/ql/src/qlpack.yml +++ b/csharp/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-queries -version: 0.7.0 +version: 0.7.1 groups: - csharp - queries diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll index e2a0e130ca4..9c19e5b9cbb 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -160,8 +160,6 @@ module ThroughFlowConfig implements DataFlow::StateConfigSig { exists(Type t | t = n.getType() and not isRelevantType(t)) } - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureEqualSourceSinkCallContext } diff --git a/csharp/ql/test/experimental/Security Features/CWE-759/HashWithoutSalt.expected b/csharp/ql/test/experimental/Security Features/CWE-759/HashWithoutSalt.expected index 667438f70e6..0ee0a6b7f61 100644 --- a/csharp/ql/test/experimental/Security Features/CWE-759/HashWithoutSalt.expected +++ b/csharp/ql/test/experimental/Security Features/CWE-759/HashWithoutSalt.expected @@ -1,10 +1,12 @@ edges -| HashWithoutSalt.cs:18:70:18:77 | access to parameter password : String | HashWithoutSalt.cs:20:49:20:56 | access to local variable passBuff | +| HashWithoutSalt.cs:18:28:18:105 | call to method ConvertStringToBinary : IBuffer | HashWithoutSalt.cs:20:49:20:56 | access to local variable passBuff | +| HashWithoutSalt.cs:18:70:18:77 | access to parameter password : String | HashWithoutSalt.cs:18:28:18:105 | call to method ConvertStringToBinary : IBuffer | | HashWithoutSalt.cs:38:28:38:72 | call to method GetBytes : Byte[] | HashWithoutSalt.cs:39:51:39:59 | access to local variable passBytes | | HashWithoutSalt.cs:38:64:38:71 | access to parameter password : String | HashWithoutSalt.cs:38:28:38:72 | call to method GetBytes : Byte[] | | HashWithoutSalt.cs:70:28:70:72 | call to method GetBytes : Byte[] | HashWithoutSalt.cs:71:48:71:56 | access to local variable passBytes | | HashWithoutSalt.cs:70:64:70:71 | access to parameter password : String | HashWithoutSalt.cs:70:28:70:72 | call to method GetBytes : Byte[] | nodes +| HashWithoutSalt.cs:18:28:18:105 | call to method ConvertStringToBinary : IBuffer | semmle.label | call to method ConvertStringToBinary : IBuffer | | HashWithoutSalt.cs:18:70:18:77 | access to parameter password : String | semmle.label | access to parameter password : String | | HashWithoutSalt.cs:20:49:20:56 | access to local variable passBuff | semmle.label | access to local variable passBuff | | HashWithoutSalt.cs:38:28:38:72 | call to method GetBytes : Byte[] | semmle.label | call to method GetBytes : Byte[] | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-338/InsecureRandomness.expected b/csharp/ql/test/query-tests/Security Features/CWE-338/InsecureRandomness.expected index 011ec3faee0..fa510ff2498 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-338/InsecureRandomness.expected +++ b/csharp/ql/test/query-tests/Security Features/CWE-338/InsecureRandomness.expected @@ -7,10 +7,12 @@ edges | InsecureRandomness.cs:29:57:29:60 | access to local variable data : Byte[] [element] : Byte | InsecureRandomness.cs:29:27:29:61 | call to method GetString : String | | InsecureRandomness.cs:31:16:31:21 | access to local variable result : StringBuilder [element] : String | InsecureRandomness.cs:31:16:31:32 | call to method ToString : String | | InsecureRandomness.cs:31:16:31:32 | call to method ToString : String | InsecureRandomness.cs:12:27:12:50 | call to method InsecureRandomString | -| InsecureRandomness.cs:60:31:60:39 | call to method Next : Int32 | InsecureRandomness.cs:62:16:62:21 | access to local variable result : String | +| InsecureRandomness.cs:60:23:60:40 | access to array element : String | InsecureRandomness.cs:62:16:62:21 | access to local variable result : String | +| InsecureRandomness.cs:60:31:60:39 | call to method Next : Int32 | InsecureRandomness.cs:60:23:60:40 | access to array element : String | | InsecureRandomness.cs:62:16:62:21 | access to local variable result : String | InsecureRandomness.cs:62:16:62:32 | call to method ToString : String | | InsecureRandomness.cs:62:16:62:32 | call to method ToString : String | InsecureRandomness.cs:13:20:13:56 | call to method InsecureRandomStringFromSelection | -| InsecureRandomness.cs:72:31:72:39 | call to method Next : Int32 | InsecureRandomness.cs:74:16:74:21 | access to local variable result : String | +| InsecureRandomness.cs:72:23:72:40 | access to indexer : String | InsecureRandomness.cs:74:16:74:21 | access to local variable result : String | +| InsecureRandomness.cs:72:31:72:39 | call to method Next : Int32 | InsecureRandomness.cs:72:23:72:40 | access to indexer : String | | InsecureRandomness.cs:74:16:74:21 | access to local variable result : String | InsecureRandomness.cs:14:20:14:54 | call to method InsecureRandomStringFromIndexer | nodes | InsecureRandomness.cs:12:27:12:50 | call to method InsecureRandomString | semmle.label | call to method InsecureRandomString | @@ -24,9 +26,11 @@ nodes | InsecureRandomness.cs:29:57:29:60 | access to local variable data : Byte[] [element] : Byte | semmle.label | access to local variable data : Byte[] [element] : Byte | | InsecureRandomness.cs:31:16:31:21 | access to local variable result : StringBuilder [element] : String | semmle.label | access to local variable result : StringBuilder [element] : String | | InsecureRandomness.cs:31:16:31:32 | call to method ToString : String | semmle.label | call to method ToString : String | +| InsecureRandomness.cs:60:23:60:40 | access to array element : String | semmle.label | access to array element : String | | InsecureRandomness.cs:60:31:60:39 | call to method Next : Int32 | semmle.label | call to method Next : Int32 | | InsecureRandomness.cs:62:16:62:21 | access to local variable result : String | semmle.label | access to local variable result : String | | InsecureRandomness.cs:62:16:62:32 | call to method ToString : String | semmle.label | call to method ToString : String | +| InsecureRandomness.cs:72:23:72:40 | access to indexer : String | semmle.label | access to indexer : String | | InsecureRandomness.cs:72:31:72:39 | call to method Next : Int32 | semmle.label | call to method Next : Int32 | | InsecureRandomness.cs:74:16:74:21 | access to local variable result : String | semmle.label | access to local variable result : String | | InsecureRandomness.cs:80:28:80:81 | call to method GeneratePassword | semmle.label | call to method GeneratePassword | diff --git a/docs/codeql/codeql-language-guides/analyzing-data-flow-in-cpp.rst b/docs/codeql/codeql-language-guides/analyzing-data-flow-in-cpp.rst index b70bfa7a3f8..0776834d243 100644 --- a/docs/codeql/codeql-language-guides/analyzing-data-flow-in-cpp.rst +++ b/docs/codeql/codeql-language-guides/analyzing-data-flow-in-cpp.rst @@ -2,7 +2,7 @@ .. pull-quote:: Note - The data flow library used in this article has been replaced with an improved library which is available from CodeQL 2.12.5 onwards, see :ref:`Analyzing data flow in C and C++ (new) `. The old library will be deprecated in the near future and removed a year after deprecation. + The data flow library used in this article has been replaced with an improved library which is available from CodeQL 2.12.5 onwards, see :ref:`Analyzing data flow in C and C++ (new) `. The old library has been deprecated in CodeQL 2.14.1 and will be removed in a later release. Analyzing data flow in C and C++ ================================ diff --git a/docs/codeql/ql-training/cpp/bad-overflow-guard.rst b/docs/codeql/ql-training/cpp/bad-overflow-guard.rst index 8912a4259ce..55657871b4f 100644 --- a/docs/codeql/ql-training/cpp/bad-overflow-guard.rst +++ b/docs/codeql/ql-training/cpp/bad-overflow-guard.rst @@ -9,22 +9,7 @@ CodeQL for C/C++ Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `ChakraCore database `__ - -.. note:: - - For the examples in this presentation, we will be analyzing `ChakraCore `__. - - You can query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `ChakraCore `__ from GitHub. Checking for overflow in C ========================== diff --git a/docs/codeql/ql-training/cpp/control-flow-cpp.rst b/docs/codeql/ql-training/cpp/control-flow-cpp.rst index f02c5232d42..b18348509c2 100644 --- a/docs/codeql/ql-training/cpp/control-flow-cpp.rst +++ b/docs/codeql/ql-training/cpp/control-flow-cpp.rst @@ -11,22 +11,7 @@ CodeQL for C/C++ Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `ChakraCore database `__ - -.. note:: - - For the examples in this presentation, we will be analyzing `ChakraCore `__. - - You can query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `ChakraCore `__ from GitHub. .. rst-class:: agenda diff --git a/docs/codeql/ql-training/cpp/data-flow-cpp.rst b/docs/codeql/ql-training/cpp/data-flow-cpp.rst index 426c6d4563d..da4f287b392 100644 --- a/docs/codeql/ql-training/cpp/data-flow-cpp.rst +++ b/docs/codeql/ql-training/cpp/data-flow-cpp.rst @@ -9,22 +9,7 @@ Finding string formatting vulnerabilities in C/C++ Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `dotnet/coreclr database `__ - -.. note:: - - For the examples in this presentation, we will be analyzing `dotnet/coreclr `__. - - You can query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `dotnet/coreclr `__ from GitHub. .. rst-class:: agenda diff --git a/docs/codeql/ql-training/cpp/global-data-flow-cpp.rst b/docs/codeql/ql-training/cpp/global-data-flow-cpp.rst index 7be2a07c4af..5fdac594389 100644 --- a/docs/codeql/ql-training/cpp/global-data-flow-cpp.rst +++ b/docs/codeql/ql-training/cpp/global-data-flow-cpp.rst @@ -9,22 +9,7 @@ CodeQL for C/C++ Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `dotnet/coreclr database `__ - -.. note:: - - For the examples in this presentation, we will be analyzing `dotnet/coreclr `__. - - You can query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `dotnet/coreclr `__ from GitHub. .. rst-class:: agenda diff --git a/docs/codeql/ql-training/cpp/intro-ql-cpp.rst b/docs/codeql/ql-training/cpp/intro-ql-cpp.rst index 3298e4d3706..dff2c50ec0c 100644 --- a/docs/codeql/ql-training/cpp/intro-ql-cpp.rst +++ b/docs/codeql/ql-training/cpp/intro-ql-cpp.rst @@ -9,22 +9,7 @@ CodeQL for C/C++ Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `exiv2 database `__ - -.. note:: - - For this example, we will be analyzing `exiv2 `__. - - You can also query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `exiv2 `__ from GitHub. .. Include language-agnostic section here @@ -66,7 +51,7 @@ A simple CodeQL query .. note:: - We are going to write a simple query which finds “if statements” with empty “then” blocks, so we can highlight the results like those on the previous slide. The query can be run in the `query console on LGTM `__, or in your `IDE `__. + We are going to write a simple query which finds “if statements” with empty “then” blocks, so we can highlight the results like those on the previous slide. A `query `__ consists of a “select” clause that indicates what results should be returned. Typically it will also provide a “from” clause to declare some variables, and a “where” clause to state conditions over those variables. For more information on the structure of query files (including links to useful topics in the `QL language reference `__), see `About CodeQL queries `__. @@ -203,6 +188,3 @@ Model answer: redundant if-statement .. literalinclude:: ../query-examples/cpp/empty-if-cpp-model.ql -.. note:: - - You can explore the results generated when this query is run on exiv2 in LGTM `here `__. diff --git a/docs/codeql/ql-training/cpp/snprintf.rst b/docs/codeql/ql-training/cpp/snprintf.rst index 657f5f29675..58b2c31d2e4 100644 --- a/docs/codeql/ql-training/cpp/snprintf.rst +++ b/docs/codeql/ql-training/cpp/snprintf.rst @@ -9,22 +9,7 @@ CodeQL for C/C++ Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `rsyslog database `__ - -.. note:: - - For this example, we will be analyzing `rsyslog `__. - - You can also query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `rsyslog `__ from GitHub. ``snprintf`` ============ @@ -94,8 +79,6 @@ Model answer .. rst-class:: build -- More full-featured version: `https://lgtm.com/rules/1505913226124 `__. - .. note:: The regular expression for matching the format string uses the “(?s)” directive to ensure that “.” also matches any newline characters embedded in the string. \ No newline at end of file diff --git a/docs/codeql/ql-training/java/apache-struts-java.rst b/docs/codeql/ql-training/java/apache-struts-java.rst index e85276e14f0..133c64a5851 100644 --- a/docs/codeql/ql-training/java/apache-struts-java.rst +++ b/docs/codeql/ql-training/java/apache-struts-java.rst @@ -13,22 +13,7 @@ Exercise: Apache Struts Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `Apache Struts database `__ - -.. note:: - - For this example, we will be analyzing `Apache Struts `__. - - You can also query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `Apache Struts `__ from GitHub. Unsafe deserialization in Struts ================================ @@ -45,7 +30,7 @@ which is intended to populate the ``target`` object with data from the reader, u RCE in Apache Struts ==================== -- Vulnerable code looked like this (`original `__): +- Vulnerable code looked like this: .. code-block:: java diff --git a/docs/codeql/ql-training/java/data-flow-java.rst b/docs/codeql/ql-training/java/data-flow-java.rst index 98cb02db1db..f55b25ff5ab 100644 --- a/docs/codeql/ql-training/java/data-flow-java.rst +++ b/docs/codeql/ql-training/java/data-flow-java.rst @@ -9,22 +9,7 @@ Finding SPARQL injection vulnerabilities in Java Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `VIVO Vitro database `__ - -.. note:: - - For this example, we will be analyzing `VIVO Vitro `__. - - You can also query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `VIVO Vitro `__ from GitHub. .. rst-class:: agenda diff --git a/docs/codeql/ql-training/java/global-data-flow-java.rst b/docs/codeql/ql-training/java/global-data-flow-java.rst index 2c1827a937c..940411d41a3 100644 --- a/docs/codeql/ql-training/java/global-data-flow-java.rst +++ b/docs/codeql/ql-training/java/global-data-flow-java.rst @@ -9,22 +9,7 @@ CodeQL for Java Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `Apache Struts database `__ - -.. note:: - - For this example, we will be analyzing `Apache Struts `__. - - You can also query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `Apache Struts `__ from GitHub. .. rst-class:: agenda diff --git a/docs/codeql/ql-training/java/intro-ql-java.rst b/docs/codeql/ql-training/java/intro-ql-java.rst index 72de876ddae..fec966e2fe4 100644 --- a/docs/codeql/ql-training/java/intro-ql-java.rst +++ b/docs/codeql/ql-training/java/intro-ql-java.rst @@ -9,22 +9,7 @@ CodeQL for Java Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `Apache Struts database `__ - -.. note:: - - For this example, we will be analyzing `Apache Struts `__. - - You can also query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `Apache Struts `__ from GitHub. .. Include language-agnostic section here @@ -66,7 +51,7 @@ A simple CodeQL query .. note:: - We are going to write a simple query which finds “if statements” with empty “then” blocks, so we can highlight the results like those on the previous slide. The query can be run in the `query console on LGTM `__, or in your `IDE `__. + We are going to write a simple query which finds “if statements” with empty “then” blocks, so we can highlight the results like those on the previous slide. A `query `__ consists of a “select” clause that indicates what results should be returned. Typically it will also provide a “from” clause to declare some variables, and a “where” clause to state conditions over those variables. For more information on the structure of query files (including links to useful topics in the `QL language reference `__), see `About CodeQL queries `__. @@ -201,7 +186,3 @@ Model answer: redundant if-statement ==================================== .. literalinclude:: ../query-examples/java/empty-if-java-model.ql - -.. note:: - - You can explore the results generated when this query is run on apache/struts in LGTM `here `__. diff --git a/docs/codeql/ql-training/java/query-injection-java.rst b/docs/codeql/ql-training/java/query-injection-java.rst index 7bad1e3cbee..a16ac49b6a3 100644 --- a/docs/codeql/ql-training/java/query-injection-java.rst +++ b/docs/codeql/ql-training/java/query-injection-java.rst @@ -9,22 +9,7 @@ CodeQL for Java Setup ===== -For this example you should download: - -- `CodeQL for Visual Studio Code `__ -- `VIVO Vitro database `__ - -.. note:: - - For this example, we will be analyzing `VIVO Vitro `__. - - You can also query the project in `the query console `__ on LGTM.com. - - .. insert database-note.rst to explain differences between database available to download and the version available in the query console. - - .. include:: ../slide-snippets/database-note.rst - - .. resume slides +For this example you need to set up `CodeQL for Visual Studio Code `__ and download the CodeQL database for `VIVO Vitro `__ from GitHub. SQL injection ============= diff --git a/docs/codeql/ql-training/slide-snippets/database-note.rst b/docs/codeql/ql-training/slide-snippets/database-note.rst deleted file mode 100644 index 909f56e3585..00000000000 --- a/docs/codeql/ql-training/slide-snippets/database-note.rst +++ /dev/null @@ -1,9 +0,0 @@ -You can download the database as a zip file by clicking the link on the slide above. To use the database in CodeQL for Visual Studio Code: - -#. Unzip the file -#. Add the unzipped database to Visual Studio Code -#. Upgrade the database if necessary - -For further information, see `Analyzing your projects `__ in the CodeQL for Visual Studio Code help. - -Note that results generated in the query console are likely to differ to those generated in CodeQL for Visual Studio Code as LGTM.com analyzes the most recent revisions of each project that has been added–the CodeQL database available to download above is based on an historical version of the codebase. \ No newline at end of file diff --git a/docs/codeql/ql-training/slide-snippets/intro-ql-general.rst b/docs/codeql/ql-training/slide-snippets/intro-ql-general.rst index 5ffe6b1dae3..991edb3ae65 100644 --- a/docs/codeql/ql-training/slide-snippets/intro-ql-general.rst +++ b/docs/codeql/ql-training/slide-snippets/intro-ql-general.rst @@ -107,7 +107,7 @@ Analysis overview Queries are written in QL and usually depend on one or more of the `standard CodeQL libraries `__ (and of course you can write your own custom libraries). They are compiled into an efficiently executable format by the QL compiler and then run on a CodeQL database by the QL evaluator, either on a remote worker machine or locally on a developer’s machine. - Query results can be interpreted and presented in a variety of ways, including displaying them in an `IDE extension `__ such as CodeQL for Visual Studio Code, or in a web dashboard as on `LGTM `__. + Query results can be interpreted and presented in a variety of ways, including displaying them in CodeQL for Visual Studio Code. Introducing QL ============== @@ -131,5 +131,3 @@ QL is: - The language is declarative–the user focuses on stating what they would like to find, and leaves the details of how to evaluate the query to the engine. - The object-oriented layer allows us to develop rich standard libraries for program analysis. These model the common AST node types, control flow and name lookup, and define further layers on top–for example control flow or data flow analysis. The `standard CodeQL libraries and queries `__ ship as source and can be inspected by the user, and new abstractions are readily defined. - The database generated by the CodeQL tools is treated as read-only; queries cannot insert new data into it, though they can inspect its contents in various ways. - - You can start writing running queries on open source projects in the `query console `__ on LGTM.com. You can also download CodeQL databases from LGTM.com to query locally, by `running queries in your IDE `__. diff --git a/docs/codeql/reusables/supported-versions-compilers.rst b/docs/codeql/reusables/supported-versions-compilers.rst index 42d4eaad956..81de83cef95 100644 --- a/docs/codeql/reusables/supported-versions-compilers.rst +++ b/docs/codeql/reusables/supported-versions-compilers.rst @@ -4,7 +4,7 @@ :stub-columns: 1 Language,Variants,Compilers,Extensions - C/C++,"C89, C99, C11, C17, C++98, C++03, C++11, C++14, C++17, C++20 [1]_","Clang (and clang-cl [2]_) extensions (up to Clang 12.0), + C/C++,"C89, C99, C11, C17, C++98, C++03, C++11, C++14, C++17, C++20 [1]_","Clang (including clang-cl [2]_ and armclang) extensions (up to Clang 12.0), GNU extensions (up to GCC 11.1), diff --git a/go/extractor/cli/go-autobuilder/go-autobuilder.go b/go/extractor/cli/go-autobuilder/go-autobuilder.go index fc56d4b8715..3f065dc3597 100644 --- a/go/extractor/cli/go-autobuilder/go-autobuilder.go +++ b/go/extractor/cli/go-autobuilder/go-autobuilder.go @@ -10,6 +10,7 @@ import ( "path/filepath" "regexp" "runtime" + "sort" "strings" "golang.org/x/mod/semver" @@ -206,6 +207,12 @@ func (m ModMode) argsForGoVersion(version string) []string { return nil } +type BuildInfo struct { + DepMode DependencyInstallerMode + ModMode ModMode + BaseDir string +} + // addVersionToMod add a go version directive, e.g. `go 1.14` to a `go.mod` file. func addVersionToMod(version string) bool { cmd := exec.Command("go", "mod", "edit", "-go="+version) @@ -240,28 +247,131 @@ func getSourceDir() string { return srcdir } +func getDirs(paths []string) []string { + dirs := make([]string, len(paths)) + for i, path := range paths { + dirs[i] = filepath.Dir(path) + } + return dirs +} + +func checkDirsNested(inputDirs []string) (string, bool) { + // replace "." with "" so that we can check if all the paths are nested + dirs := make([]string, len(inputDirs)) + for i, inputDir := range inputDirs { + if inputDir == "." { + dirs[i] = "" + } else { + dirs[i] = inputDir + } + } + // the paths were generated by a depth-first search so I think they might + // be sorted, but we sort them just in case + sort.Strings(dirs) + for _, dir := range dirs { + if !strings.HasPrefix(dir, dirs[0]) { + return "", false + } + } + return dirs[0], true +} + +// Returns the directory to run the go build in and whether to use a go.mod +// file. +func findGoModFiles(emitDiagnostics bool) (baseDir string, useGoMod bool) { + goModPaths := util.FindAllFilesWithName(".", "go.mod", "vendor") + if len(goModPaths) == 0 { + baseDir = "." + useGoMod = false + return + } + goModDirs := getDirs(goModPaths) + if util.AnyGoFilesOutsideDirs(".", goModDirs...) { + if emitDiagnostics { + diagnostics.EmitGoFilesOutsideGoModules(goModPaths) + } + baseDir = "." + useGoMod = false + return + } + if len(goModPaths) > 1 { + // currently not supported + baseDir = "." + commonRoot, nested := checkDirsNested(goModDirs) + if nested && commonRoot == "" { + useGoMod = true + } else { + useGoMod = false + } + if emitDiagnostics { + if nested { + diagnostics.EmitMultipleGoModFoundNested(goModPaths) + } else { + diagnostics.EmitMultipleGoModFoundNotNested(goModPaths) + } + } + return + } + if emitDiagnostics { + if goModDirs[0] == "." { + diagnostics.EmitSingleRootGoModFound(goModPaths[0]) + } else { + diagnostics.EmitSingleNonRootGoModFound(goModPaths[0]) + } + } + baseDir = goModDirs[0] + useGoMod = true + return +} + // Returns the appropriate DependencyInstallerMode for the current project -func getDepMode() DependencyInstallerMode { - if util.FileExists("go.mod") { +func getDepMode(emitDiagnostics bool) (DependencyInstallerMode, string) { + bazelPaths := util.FindAllFilesWithName(".", "BUILD", "vendor") + bazelPaths = append(bazelPaths, util.FindAllFilesWithName(".", "BUILD.bazel", "vendor")...) + if len(bazelPaths) > 0 { + // currently not supported + if emitDiagnostics { + diagnostics.EmitBazelBuildFilesFound(bazelPaths) + } + } + + goWorkPaths := util.FindAllFilesWithName(".", "go.work", "vendor") + if len(goWorkPaths) > 0 { + // currently not supported + if emitDiagnostics { + diagnostics.EmitGoWorkFound(goWorkPaths) + } + } + + baseDir, useGoMod := findGoModFiles(emitDiagnostics) + if useGoMod { log.Println("Found go.mod, enabling go modules") - return GoGetWithModules + return GoGetWithModules, baseDir } + if util.FileExists("Gopkg.toml") { + if emitDiagnostics { + diagnostics.EmitGopkgTomlFound() + } log.Println("Found Gopkg.toml, using dep instead of go get") - return Dep + return Dep, "." } + if util.FileExists("glide.yaml") { - log.Println("Found glide.yaml, enabling go modules") - return Glide + if emitDiagnostics { + diagnostics.EmitGlideYamlFound() + } + log.Println("Found glide.yaml, using Glide instead of go get") + return Glide, "." } - return GoGetNoModules + return GoGetNoModules, "." } // Tries to open `go.mod` and read a go directive, returning the version and whether it was found. -func tryReadGoDirective(depMode DependencyInstallerMode) (string, bool) { - if depMode == GoGetWithModules { +func tryReadGoDirective(buildInfo BuildInfo) (string, bool) { + if buildInfo.DepMode == GoGetWithModules { versionRe := regexp.MustCompile(`(?m)^go[ \t\r]+([0-9]+\.[0-9]+)$`) - goMod, err := os.ReadFile("go.mod") + goMod, err := os.ReadFile(filepath.Join(buildInfo.BaseDir, "go.mod")) if err != nil { log.Println("Failed to read go.mod to check for missing Go version") } else { @@ -277,13 +387,13 @@ func tryReadGoDirective(depMode DependencyInstallerMode) (string, bool) { } // Returns the appropriate ModMode for the current project -func getModMode(depMode DependencyInstallerMode) ModMode { +func getModMode(depMode DependencyInstallerMode, baseDir string) ModMode { if depMode == GoGetWithModules { // if a vendor/modules.txt file exists, we assume that there are vendored Go dependencies, and // skip the dependency installation step and run the extractor with `-mod=vendor` - if util.FileExists("vendor/modules.txt") { + if util.FileExists(filepath.Join(baseDir, "vendor", "modules.txt")) { return ModVendor - } else if util.DirExists("vendor") { + } else if util.DirExists(filepath.Join(baseDir, "vendor")) { return ModMod } } @@ -291,8 +401,8 @@ func getModMode(depMode DependencyInstallerMode) ModMode { } // fixGoVendorIssues fixes issues with go vendor for go version >= 1.14 -func fixGoVendorIssues(modMode ModMode, depMode DependencyInstallerMode, goModVersionFound bool) ModMode { - if modMode == ModVendor { +func fixGoVendorIssues(buildInfo *BuildInfo, goModVersionFound bool) { + if buildInfo.ModMode == ModVendor { // fix go vendor issues with go versions >= 1.14 when no go version is specified in the go.mod // if this is the case, and dependencies were vendored with an old go version (and therefore // do not contain a '## explicit' annotation, the go command will fail and refuse to do any @@ -300,7 +410,7 @@ func fixGoVendorIssues(modMode ModMode, depMode DependencyInstallerMode, goModVe // // we work around this by adding an explicit go version of 1.13, which is the last version // where this is not an issue - if depMode == GoGetWithModules { + if buildInfo.DepMode == GoGetWithModules { if !goModVersionFound { // if the go.mod does not contain a version line modulesTxt, err := os.ReadFile("vendor/modules.txt") @@ -311,19 +421,18 @@ func fixGoVendorIssues(modMode ModMode, depMode DependencyInstallerMode, goModVe log.Println("Adding a version directive to the go.mod file as the modules.txt does not have explicit annotations") if !addVersionToMod("1.13") { log.Println("Failed to add a version to the go.mod file to fix explicitly required package bug; not using vendored dependencies") - return ModMod + buildInfo.ModMode = ModMod } } } } } - return modMode } // Determines whether the project needs a GOPATH set up -func getNeedGopath(depMode DependencyInstallerMode, importpath string) bool { +func getNeedGopath(buildInfo BuildInfo, importpath string) bool { needGopath := true - if depMode == GoGetWithModules { + if buildInfo.DepMode == GoGetWithModules { needGopath = false } // if `LGTM_INDEX_NEED_GOPATH` is set, it overrides the value for `needGopath` inferred above @@ -344,25 +453,29 @@ func getNeedGopath(depMode DependencyInstallerMode, importpath string) bool { } // Try to update `go.mod` and `go.sum` if the go version is >= 1.16. -func tryUpdateGoModAndGoSum(modMode ModMode, depMode DependencyInstallerMode) { +func tryUpdateGoModAndGoSum(buildInfo BuildInfo) { // Go 1.16 and later won't automatically attempt to update go.mod / go.sum during package loading, so try to update them here: - if modMode != ModVendor && depMode == GoGetWithModules && semver.Compare(getEnvGoSemVer(), "v1.16") >= 0 { + if buildInfo.ModMode != ModVendor && buildInfo.DepMode == GoGetWithModules && semver.Compare(getEnvGoSemVer(), "v1.16") >= 0 { // stat go.mod and go.sum - beforeGoModFileInfo, beforeGoModErr := os.Stat("go.mod") + goModPath := filepath.Join(buildInfo.BaseDir, "go.mod") + beforeGoModFileInfo, beforeGoModErr := os.Stat(goModPath) if beforeGoModErr != nil { log.Println("Failed to stat go.mod before running `go mod tidy -e`") } - beforeGoSumFileInfo, beforeGoSumErr := os.Stat("go.sum") + goSumPath := filepath.Join(buildInfo.BaseDir, "go.sum") + beforeGoSumFileInfo, beforeGoSumErr := os.Stat(goSumPath) // run `go mod tidy -e` - res := util.RunCmd(exec.Command("go", "mod", "tidy", "-e")) + cmd := exec.Command("go", "mod", "tidy", "-e") + cmd.Dir = buildInfo.BaseDir + res := util.RunCmd(cmd) if !res { log.Println("Failed to run `go mod tidy -e`") } else { if beforeGoModFileInfo != nil { - afterGoModFileInfo, afterGoModErr := os.Stat("go.mod") + afterGoModFileInfo, afterGoModErr := os.Stat(goModPath) if afterGoModErr != nil { log.Println("Failed to stat go.mod after running `go mod tidy -e`") } else if afterGoModFileInfo.ModTime().After(beforeGoModFileInfo.ModTime()) { @@ -371,7 +484,7 @@ func tryUpdateGoModAndGoSum(modMode ModMode, depMode DependencyInstallerMode) { } } - afterGoSumFileInfo, afterGoSumErr := os.Stat("go.sum") + afterGoSumFileInfo, afterGoSumErr := os.Stat(goSumPath) if afterGoSumErr != nil { log.Println("Failed to stat go.sum after running `go mod tidy -e`") } else { @@ -560,10 +673,10 @@ func buildWithCustomCommands(inst string) { } // Install dependencies using the given dependency installer mode. -func installDependencies(depMode DependencyInstallerMode) { +func installDependencies(buildInfo BuildInfo) { // automatically determine command to install dependencies var install *exec.Cmd - if depMode == Dep { + if buildInfo.DepMode == Dep { // set up the dep cache if SEMMLE_CACHE is set cacheDir := os.Getenv("SEMMLE_CACHE") if cacheDir != "" { @@ -593,44 +706,41 @@ func installDependencies(depMode DependencyInstallerMode) { install = exec.Command("dep", "ensure", "-v") } log.Println("Installing dependencies using `dep ensure`.") - } else if depMode == Glide { + } else if buildInfo.DepMode == Glide { install = exec.Command("glide", "install") log.Println("Installing dependencies using `glide install`") } else { // explicitly set go module support - if depMode == GoGetWithModules { + if buildInfo.DepMode == GoGetWithModules { os.Setenv("GO111MODULE", "on") - } else if depMode == GoGetNoModules { + } else if buildInfo.DepMode == GoGetNoModules { os.Setenv("GO111MODULE", "off") } // get dependencies install = exec.Command("go", "get", "-v", "./...") - log.Println("Installing dependencies using `go get -v ./...`.") + install.Dir = buildInfo.BaseDir + log.Printf("Installing dependencies using `go get -v ./...` in `%s`.\n", buildInfo.BaseDir) } util.RunCmd(install) } // Run the extractor. -func extract(depMode DependencyInstallerMode, modMode ModMode) { +func extract(buildInfo BuildInfo) { extractor, err := util.GetExtractorPath() if err != nil { log.Fatalf("Could not determine path of extractor: %v.\n", err) } - cwd, err := os.Getwd() - if err != nil { - log.Fatalf("Unable to determine current directory: %s\n", err.Error()) - } - extractorArgs := []string{} - if depMode == GoGetWithModules { - extractorArgs = append(extractorArgs, modMode.argsForGoVersion(getEnvGoSemVer())...) + if buildInfo.DepMode == GoGetWithModules { + extractorArgs = append(extractorArgs, buildInfo.ModMode.argsForGoVersion(getEnvGoSemVer())...) } extractorArgs = append(extractorArgs, "./...") - log.Printf("Running extractor command '%s %v' from directory '%s'.\n", extractor, extractorArgs, cwd) + log.Printf("Running extractor command '%s %v' from directory '%s'.\n", extractor, extractorArgs, buildInfo.BaseDir) cmd := exec.Command(extractor, extractorArgs...) + cmd.Dir = buildInfo.BaseDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() @@ -639,6 +749,12 @@ func extract(depMode DependencyInstallerMode, modMode ModMode) { } } +func getBuildInfo(emitDiagnostics bool) BuildInfo { + depMode, baseDir := getDepMode(true) + modMode := getModMode(depMode, baseDir) + return BuildInfo{depMode, modMode, baseDir} +} + // Build the project and run the extractor. func installDependenciesAndBuild() { log.Printf("Autobuilder was built with %s, environment has %s\n", runtime.Version(), getEnvGoVersion()) @@ -650,24 +766,23 @@ func installDependenciesAndBuild() { // determine how to install dependencies and whether a GOPATH needs to be set up before // extraction - depMode := getDepMode() + buildInfo := getBuildInfo(true) if _, present := os.LookupEnv("GO111MODULE"); !present { os.Setenv("GO111MODULE", "auto") } - goModVersion, goModVersionFound := tryReadGoDirective(depMode) + goModVersion, goModVersionFound := tryReadGoDirective(buildInfo) - if semver.Compare("v"+goModVersion, getEnvGoSemVer()) >= 0 { + if goModVersionFound && semver.Compare("v"+goModVersion, getEnvGoSemVer()) >= 0 { diagnostics.EmitNewerGoVersionNeeded() } - modMode := getModMode(depMode) - modMode = fixGoVendorIssues(modMode, depMode, goModVersionFound) + fixGoVendorIssues(&buildInfo, goModVersionFound) - tryUpdateGoModAndGoSum(modMode, depMode) + tryUpdateGoModAndGoSum(buildInfo) importpath := getImportPath() - needGopath := getNeedGopath(depMode, importpath) + needGopath := getNeedGopath(buildInfo, importpath) inLGTM := os.Getenv("LGTM_SRC") != "" || os.Getenv("LGTM_INDEX_NEED_GOPATH") != "" @@ -688,30 +803,30 @@ func installDependenciesAndBuild() { inst := util.Getenv("CODEQL_EXTRACTOR_GO_BUILD_COMMAND", "LGTM_INDEX_BUILD_COMMAND") shouldInstallDependencies := false if inst == "" { - shouldInstallDependencies = buildWithoutCustomCommands(modMode) + shouldInstallDependencies = buildWithoutCustomCommands(buildInfo.ModMode) } else { buildWithCustomCommands(inst) } - if modMode == ModVendor { + if buildInfo.ModMode == ModVendor { // test if running `go` with -mod=vendor works, and if it doesn't, try to fallback to -mod=mod // or not set if the go version < 1.14. Note we check this post-build in case the build brings // the vendor directory up to date. if !checkVendor() { - modMode = ModMod + buildInfo.ModMode = ModMod log.Println("The vendor directory is not consistent with the go.mod; not using vendored dependencies.") } } if shouldInstallDependencies { - if modMode == ModVendor { + if buildInfo.ModMode == ModVendor { log.Printf("Skipping dependency installation because a Go vendor directory was found.") } else { - installDependencies(depMode) + installDependencies(buildInfo) } } - extract(depMode, modMode) + extract(buildInfo) } const minGoVersion = "1.11" @@ -976,8 +1091,8 @@ func isGoInstalled() bool { // Get the version of Go to install and output it to stdout as json. func identifyEnvironment() { var v versionInfo - depMode := getDepMode() - v.goModVersion, v.goModVersionFound = tryReadGoDirective(depMode) + buildInfo := getBuildInfo(false) + v.goModVersion, v.goModVersionFound = tryReadGoDirective(buildInfo) v.goEnvVersionFound = isGoInstalled() if v.goEnvVersionFound { diff --git a/go/extractor/diagnostics/diagnostics.go b/go/extractor/diagnostics/diagnostics.go index e92599417a5..728122cb787 100644 --- a/go/extractor/diagnostics/diagnostics.go +++ b/go/extractor/diagnostics/diagnostics.go @@ -129,16 +129,19 @@ func EmitPackageDifferentOSArchitecture(pkgPath string) { ) } +func plural(n int, singular, plural string) string { + if n == 1 { + return singular + } else { + return plural + } +} + const maxNumPkgPaths = 5 func EmitCannotFindPackages(pkgPaths []string) { numPkgPaths := len(pkgPaths) - ending := "s" - if numPkgPaths == 1 { - ending = "" - } - numPrinted := numPkgPaths truncated := false if numPrinted > maxNumPkgPaths { @@ -154,7 +157,11 @@ func EmitCannotFindPackages(pkgPaths []string) { emitDiagnostic( "go/autobuilder/package-not-found", "Some packages could not be found", - fmt.Sprintf("%d package%s could not be found:\n\n%s.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", numPkgPaths, ending, secondLine), + fmt.Sprintf( + "%d package%s could not be found:\n\n%s.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + numPkgPaths, + plural(len(pkgPaths), "", "s"), + secondLine), severityWarning, fullVisibility, noLocation, @@ -194,6 +201,123 @@ func EmitRelativeImportPaths() { ) } +// The following diagnostics are telemetry-only. + +func EmitBazelBuildFilesFound(bazelPaths []string) { + emitDiagnostic( + "go/autobuilder/bazel-build-file-found", + "Bazel BUILD files were found", + fmt.Sprintf( + "%d bazel BUILD %s found:\n\n`%s`", + len(bazelPaths), + plural(len(bazelPaths), "file was", "files were"), + strings.Join(bazelPaths, "`, `")), + severityNote, + telemetryOnly, + noLocation, + ) +} + +func EmitGopkgTomlFound() { + emitDiagnostic( + "go/autobuilder/gopkg-toml-found", + "A dep `Gopkg.toml` file was found", + "A dep `Gopkg.toml` file was found", + severityNote, + telemetryOnly, + noLocation, + ) +} + +func EmitGlideYamlFound() { + emitDiagnostic( + "go/autobuilder/glide-yaml-found", + "A Glide `glide.yaml` file was found", + "A Glide `glide.yaml` file was found", + severityNote, + telemetryOnly, + noLocation, + ) +} + +func EmitGoWorkFound(goWorkPaths []string) { + emitDiagnostic( + "go/autobuilder/go-work-found", + "`go.work` file found", + fmt.Sprintf( + "%d `go.work` %s found:\n\n`%s`", + len(goWorkPaths), + plural(len(goWorkPaths), "file was", "files were"), + strings.Join(goWorkPaths, "`, `")), + severityNote, + telemetryOnly, + noLocation, + ) +} + +func EmitGoFilesOutsideGoModules(goModPaths []string) { + emitDiagnostic( + "go/autobuilder/go-files-outside-go-modules", + "Go files were found outside Go modules", + "Go files were found outside of the Go modules corresponding to these `go.mod` files.\n\n`"+strings.Join(goModPaths, "`, `")+"`", + severityNote, + telemetryOnly, + noLocation, + ) +} + +func EmitMultipleGoModFoundNested(goModPaths []string) { + emitDiagnostic( + "go/autobuilder/multiple-go-mod-found-nested", + "Multiple `go.mod` files were found, all nested under one root `go.mod` file", + fmt.Sprintf( + "%d `go.mod` files were found:\n\n`%s`", + len(goModPaths), + strings.Join(goModPaths, "`, `")), + severityNote, + telemetryOnly, + noLocation, + ) +} + +func EmitMultipleGoModFoundNotNested(goModPaths []string) { + emitDiagnostic( + "go/autobuilder/multiple-go-mod-found-not-nested", + "Multiple `go.mod` files found, not all nested under one root `go.mod` file", + fmt.Sprintf( + "%d `go.mod` files were found:\n\n`%s`", + len(goModPaths), + strings.Join(goModPaths, "`, `")), + severityNote, + telemetryOnly, + noLocation, + ) +} + +func EmitSingleRootGoModFound(goModPath string) { + emitDiagnostic( + "go/autobuilder/single-root-go-mod-found", + "A single `go.mod` file was found in the root", + "A single `go.mod` file was found.\n\n`"+goModPath+"`", + severityNote, + telemetryOnly, + noLocation, + ) +} + +func EmitSingleNonRootGoModFound(goModPath string) { + emitDiagnostic( + "go/autobuilder/single-non-root-go-mod-found", + "A single, non-root `go.mod` file was found", + "A single, non-root `go.mod` file was found.\n\n`"+goModPath+"`", + severityNote, + telemetryOnly, + noLocation, + ) +} + +// The following diagnostics are related to identifying the build environment. + func EmitNoGoModAndNoGoEnv(msg string) { emitDiagnostic( "go/autobuilder/env-no-go-mod-no-go-env", diff --git a/go/extractor/extractor.go b/go/extractor/extractor.go index 9f21984061f..a55279edffb 100644 --- a/go/extractor/extractor.go +++ b/go/extractor/extractor.go @@ -1946,48 +1946,14 @@ func populateTypeParamParents(tw *trap.Writer, typeparams *types.TypeParamList, // some changes to the object to avoid returning objects relating to instantiated // types. func getObjectBeingUsed(tw *trap.Writer, ident *ast.Ident) types.Object { - obj := tw.Package.TypesInfo.Uses[ident] - if obj == nil { - return nil + switch obj := tw.Package.TypesInfo.Uses[ident].(type) { + case *types.Var: + return obj.Origin() + case *types.Func: + return obj.Origin() + default: + return obj } - if override, ok := tw.ObjectsOverride[obj]; ok { - return override - } - if funcObj, ok := obj.(*types.Func); ok { - sig := funcObj.Type().(*types.Signature) - if recv := sig.Recv(); recv != nil { - recvType := recv.Type() - originType, isSame := tryGetGenericType(recvType) - - if originType == nil { - if pointerType, ok := recvType.(*types.Pointer); ok { - originType, isSame = tryGetGenericType(pointerType.Elem()) - } - } - - if originType == nil || isSame { - return obj - } - - for i := 0; i < originType.NumMethods(); i++ { - meth := originType.Method(i) - if meth.Name() == funcObj.Name() { - return meth - } - } - if interfaceType, ok := originType.Underlying().(*types.Interface); ok { - for i := 0; i < interfaceType.NumMethods(); i++ { - meth := interfaceType.Method(i) - if meth.Name() == funcObj.Name() { - return meth - } - } - } - log.Fatalf("Could not find method %s on type %s", funcObj.Name(), originType) - } - } - - return obj } // tryGetGenericType returns the generic type of `tp`, and a boolean indicating diff --git a/go/extractor/util/util.go b/go/extractor/util/util.go index 813938e1f73..313c000d30d 100644 --- a/go/extractor/util/util.go +++ b/go/extractor/util/util.go @@ -297,3 +297,46 @@ func FindGoFiles(root string) bool { }) return found } + +func FindAllFilesWithName(root string, name string, dirsToSkip ...string) []string { + paths := make([]string, 0, 1) + filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + for _, dirToSkip := range dirsToSkip { + if path == dirToSkip { + return filepath.SkipDir + } + } + } + if d.Name() == name { + paths = append(paths, path) + } + return nil + }) + return paths +} + +func AnyGoFilesOutsideDirs(root string, dirsToSkip ...string) bool { + found := false + filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + for _, dirToSkip := range dirsToSkip { + if path == dirToSkip { + return filepath.SkipDir + } + } + } + if filepath.Ext(d.Name()) == ".go" { + found = true + return filepath.SkipAll + } + return nil + }) + return found +} diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/BUILD.bazel b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/BUILD.bazel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/diagnostics.expected new file mode 100644 index 00000000000..4e061f03fc3 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/diagnostics.expected @@ -0,0 +1,28 @@ +{ + "markdownMessage": "1 bazel BUILD file was found:\n\n`BUILD.bazel`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/bazel-build-file-found", + "name": "Bazel BUILD files were found" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} +{ + "markdownMessage": "A single `go.mod` file was found.\n\n`go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/single-root-go-mod-found", + "name": "A single `go.mod` file was found in the root" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/go.mod b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/go.mod new file mode 100644 index 00000000000..64e39913d21 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/go.mod @@ -0,0 +1,7 @@ +go 1.14 + +require ( + golang.org/x/net v0.0.0-20200505041828-1ed23360d12c +) + +module bazelsample diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/go.sum b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.expected b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.expected new file mode 100644 index 00000000000..00f0cbe3e01 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.expected @@ -0,0 +1,7 @@ +htmlFiles +extractionErrors +| Extraction failed in test.go with error \tother declaration of test | 2 | +| Extraction failed in todel.go with error test redeclared in this block | 2 | +#select +| test.go:0:0:0:0 | test.go | +| todel.go:0:0:0:0 | todel.go | diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.go b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.go new file mode 100644 index 00000000000..57b29dc9cc0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.go @@ -0,0 +1,12 @@ +package bazelsample + +import ( + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = 4 + +} diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.py b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.ql b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.ql new file mode 100644 index 00000000000..362eddafa59 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/test.ql @@ -0,0 +1,9 @@ +import go +import semmle.go.DiagnosticsReporting + +from GoFile f +select f + +query predicate htmlFiles(HtmlFile x) { any() } + +query predicate extractionErrors(string msg, int sev) { reportableDiagnostics(_, msg, sev) } diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-1/todel.go b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/todel.go new file mode 100644 index 00000000000..57b29dc9cc0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-1/todel.go @@ -0,0 +1,12 @@ +package bazelsample + +import ( + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = 4 + +} diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/BUILD b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/BUILD new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/diagnostics.expected new file mode 100644 index 00000000000..2ee5be03391 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/diagnostics.expected @@ -0,0 +1,28 @@ +{ + "markdownMessage": "1 bazel BUILD file was found:\n\n`BUILD`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/bazel-build-file-found", + "name": "Bazel BUILD files were found" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} +{ + "markdownMessage": "A single `go.mod` file was found.\n\n`go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/single-root-go-mod-found", + "name": "A single `go.mod` file was found in the root" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/go.mod b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/go.mod new file mode 100644 index 00000000000..64e39913d21 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/go.mod @@ -0,0 +1,7 @@ +go 1.14 + +require ( + golang.org/x/net v0.0.0-20200505041828-1ed23360d12c +) + +module bazelsample diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/go.sum b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.expected b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.expected new file mode 100644 index 00000000000..00f0cbe3e01 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.expected @@ -0,0 +1,7 @@ +htmlFiles +extractionErrors +| Extraction failed in test.go with error \tother declaration of test | 2 | +| Extraction failed in todel.go with error test redeclared in this block | 2 | +#select +| test.go:0:0:0:0 | test.go | +| todel.go:0:0:0:0 | todel.go | diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.go b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.go new file mode 100644 index 00000000000..57b29dc9cc0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.go @@ -0,0 +1,12 @@ +package bazelsample + +import ( + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = 4 + +} diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.py b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.ql b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.ql new file mode 100644 index 00000000000..362eddafa59 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/test.ql @@ -0,0 +1,9 @@ +import go +import semmle.go.DiagnosticsReporting + +from GoFile f +select f + +query predicate htmlFiles(HtmlFile x) { any() } + +query predicate extractionErrors(string msg, int sev) { reportableDiagnostics(_, msg, sev) } diff --git a/go/ql/integration-tests/all-platforms/go/bazel-sample-2/todel.go b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/todel.go new file mode 100644 index 00000000000..57b29dc9cc0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/bazel-sample-2/todel.go @@ -0,0 +1,12 @@ +package bazelsample + +import ( + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = 4 + +} diff --git a/go/ql/integration-tests/all-platforms/go/diagnostics/build-constraints-exclude-all-go-files/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/diagnostics/build-constraints-exclude-all-go-files/diagnostics.expected index dc7a5dca282..65a0f5be04c 100644 --- a/go/ql/integration-tests/all-platforms/go/diagnostics/build-constraints-exclude-all-go-files/diagnostics.expected +++ b/go/ql/integration-tests/all-platforms/go/diagnostics/build-constraints-exclude-all-go-files/diagnostics.expected @@ -1,3 +1,17 @@ +{ + "markdownMessage": "A single `go.mod` file was found.\n\n`go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/single-root-go-mod-found", + "name": "A single `go.mod` file was found in the root" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} { "markdownMessage": "`syscall/js` could not be imported. Make sure the `GOOS` and `GOARCH` [environment variables are correctly set](https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow). Alternatively, [change your OS and architecture](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#using-a-github-hosted-runner).", "severity": "warning", diff --git a/go/ql/integration-tests/all-platforms/go/diagnostics/go-files-found-not-processed/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/diagnostics/go-files-found-not-processed/diagnostics.expected index 405113fe93a..f17d5a9a242 100644 --- a/go/ql/integration-tests/all-platforms/go/diagnostics/go-files-found-not-processed/diagnostics.expected +++ b/go/ql/integration-tests/all-platforms/go/diagnostics/go-files-found-not-processed/diagnostics.expected @@ -1,3 +1,17 @@ +{ + "markdownMessage": "2 `go.mod` files were found:\n\n`go.mod`, `subdir/go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/multiple-go-mod-found-nested", + "name": "Multiple `go.mod` files were found, all nested under one root `go.mod` file" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} { "markdownMessage": "[Specify a custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages) that includes one or more `go build` commands to build the `.go` files to be analyzed.", "severity": "error", diff --git a/go/ql/integration-tests/all-platforms/go/diagnostics/go-files-found-not-processed/work/subdir/test.go b/go/ql/integration-tests/all-platforms/go/diagnostics/go-files-found-not-processed/work/subdir/test.go index 3f8cd1e2951..eede07b9874 100644 --- a/go/ql/integration-tests/all-platforms/go/diagnostics/go-files-found-not-processed/work/subdir/test.go +++ b/go/ql/integration-tests/all-platforms/go/diagnostics/go-files-found-not-processed/work/subdir/test.go @@ -1,4 +1,4 @@ -package test/subdir +package subdir func Test() { } diff --git a/go/ql/integration-tests/all-platforms/go/diagnostics/newer-go-version-needed/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/diagnostics/newer-go-version-needed/diagnostics.expected index e50dcf5c093..1db1354f164 100644 --- a/go/ql/integration-tests/all-platforms/go/diagnostics/newer-go-version-needed/diagnostics.expected +++ b/go/ql/integration-tests/all-platforms/go/diagnostics/newer-go-version-needed/diagnostics.expected @@ -1,3 +1,17 @@ +{ + "markdownMessage": "A single `go.mod` file was found.\n\n`go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/single-root-go-mod-found", + "name": "A single `go.mod` file was found in the root" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} { "markdownMessage": "The detected version of Go is lower than the version specified in `go.mod`. [Install a newer version](https://github.com/actions/setup-go#basic).", "severity": "error", diff --git a/go/ql/integration-tests/all-platforms/go/diagnostics/package-not-found-with-go-mod/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/diagnostics/package-not-found-with-go-mod/diagnostics.expected index dff5dc5bb92..31204020a02 100644 --- a/go/ql/integration-tests/all-platforms/go/diagnostics/package-not-found-with-go-mod/diagnostics.expected +++ b/go/ql/integration-tests/all-platforms/go/diagnostics/package-not-found-with-go-mod/diagnostics.expected @@ -12,3 +12,17 @@ "telemetry": true } } +{ + "markdownMessage": "A single `go.mod` file was found.\n\n`go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/single-root-go-mod-found", + "name": "A single `go.mod` file was found in the root" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/diagnostics.expected new file mode 100644 index 00000000000..ed2d87ac207 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/diagnostics.expected @@ -0,0 +1,28 @@ +{ + "markdownMessage": "1 package could not be found:\n\n`subdir/subsubdir`.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "warning", + "source": { + "extractorName": "go", + "id": "go/autobuilder/package-not-found", + "name": "Some packages could not be found" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "markdownMessage": "Go files were found outside of the Go modules corresponding to these `go.mod` files.\n\n`subdir/go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/go-files-outside-go-modules", + "name": "Go files were found outside Go modules" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/main.go b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/main.go new file mode 100644 index 00000000000..8f618807311 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/go.mod b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/go.mod new file mode 100644 index 00000000000..e217b3613a6 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module subdir diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/go.sum b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/subsubdir/add.go b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/subsubdir/add.go new file mode 100644 index 00000000000..15233c47f62 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/subsubdir/add.go @@ -0,0 +1,5 @@ +package subsubdir + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/test.go b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/test.go new file mode 100644 index 00000000000..291a8591372 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/subdir/test.go @@ -0,0 +1,14 @@ +package subdir + +import ( + "subdir/subsubdir" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subsubdir.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.expected b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.expected new file mode 100644 index 00000000000..18120f350ef --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.expected @@ -0,0 +1,2 @@ +| Extraction failed in subdir/test.go with error cannot find package "subdir/subsubdir" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 | +| Extraction failed in subdir/test.go with error could not import subdir/subsubdir (invalid package name: "") | 2 | diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.py b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.ql b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.ql new file mode 100644 index 00000000000..e817bb2938a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-and-go-files-not-under-it/test.ql @@ -0,0 +1,6 @@ +import go +import semmle.go.DiagnosticsReporting + +from string msg, int sev +where reportableDiagnostics(_, msg, sev) +select msg, sev diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/diagnostics.expected new file mode 100644 index 00000000000..56d774b7037 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/diagnostics.expected @@ -0,0 +1,14 @@ +{ + "markdownMessage": "A single `go.mod` file was found.\n\n`go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/single-root-go-mod-found", + "name": "A single `go.mod` file was found in the root" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/go.mod b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/go.mod new file mode 100644 index 00000000000..4a38c9773d7 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module test diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/go.sum b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/subdir/add.go b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/subdir/add.go new file mode 100644 index 00000000000..910b449d808 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/subdir/add.go @@ -0,0 +1,5 @@ +package subdir + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.expected b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.go b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.go new file mode 100644 index 00000000000..15e54322353 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.go @@ -0,0 +1,14 @@ +package test + +import ( + "test/subdir" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subdir.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.py b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.ql b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.ql new file mode 100644 index 00000000000..e817bb2938a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-in-root/test.ql @@ -0,0 +1,6 @@ +import go +import semmle.go.DiagnosticsReporting + +from string msg, int sev +where reportableDiagnostics(_, msg, sev) +select msg, sev diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/diagnostics.expected new file mode 100644 index 00000000000..1c7a2d28fe1 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/diagnostics.expected @@ -0,0 +1,14 @@ +{ + "markdownMessage": "A single, non-root `go.mod` file was found.\n\n`subdir/go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/single-non-root-go-mod-found", + "name": "A single, non-root `go.mod` file was found" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/go.mod b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/go.mod new file mode 100644 index 00000000000..e217b3613a6 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module subdir diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/go.sum b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/subsubdir/add.go b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/subsubdir/add.go new file mode 100644 index 00000000000..15233c47f62 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/subsubdir/add.go @@ -0,0 +1,5 @@ +package subsubdir + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/test.go b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/test.go new file mode 100644 index 00000000000..291a8591372 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/subdir/test.go @@ -0,0 +1,14 @@ +package subdir + +import ( + "subdir/subsubdir" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subsubdir.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/test.expected b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/test.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/test.py b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/test.ql b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/test.ql new file mode 100644 index 00000000000..e817bb2938a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-mod-not-in-root/test.ql @@ -0,0 +1,6 @@ +import go +import semmle.go.DiagnosticsReporting + +from string msg, int sev +where reportableDiagnostics(_, msg, sev) +select msg, sev diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/diagnostics.expected new file mode 100644 index 00000000000..dc018d92b3d --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/diagnostics.expected @@ -0,0 +1,42 @@ +{ + "markdownMessage": "1 `go.work` file was found:\n\n`modules/go.work`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/go-work-found", + "name": "`go.work` file found" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} +{ + "markdownMessage": "2 `go.mod` files were found:\n\n`modules/subdir1/go.mod`, `modules/subdir2/go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/multiple-go-mod-found-not-nested", + "name": "Multiple `go.mod` files found, not all nested under one root `go.mod` file" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} +{ + "markdownMessage": "2 packages could not be found:\n\n`subdir1/subsubdir1`, `subdir2/subsubdir2`.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "warning", + "source": { + "extractorName": "go", + "id": "go/autobuilder/package-not-found", + "name": "Some packages could not be found" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/go.work b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/go.work new file mode 100644 index 00000000000..6610d43e9c0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/go.work @@ -0,0 +1,6 @@ +go 1.19 + +use ( + ./subdir1 + ./subdir2 +) diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/go.mod b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/go.mod new file mode 100644 index 00000000000..147c51b8386 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module subdir1 diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/go.sum b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/subsubdir1/add.go b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/subsubdir1/add.go new file mode 100644 index 00000000000..900c2b5f266 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/subsubdir1/add.go @@ -0,0 +1,5 @@ +package subsubdir1 + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/test.go b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/test.go new file mode 100644 index 00000000000..6081be52248 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir1/test.go @@ -0,0 +1,14 @@ +package subdir + +import ( + "subdir1/subsubdir1" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subsubdir1.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/go.mod b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/go.mod new file mode 100644 index 00000000000..c6eec7d9ca5 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module subdir2 diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/go.sum b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/subsubdir2/add.go b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/subsubdir2/add.go new file mode 100644 index 00000000000..99810d11db0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/subsubdir2/add.go @@ -0,0 +1,5 @@ +package subsubdir2 + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/test.go b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/test.go new file mode 100644 index 00000000000..e2d423cc2b6 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/modules/subdir2/test.go @@ -0,0 +1,14 @@ +package subdir + +import ( + "subdir2/subsubdir2" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subsubdir2.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.expected b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.expected new file mode 100644 index 00000000000..a2c09b1171a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.expected @@ -0,0 +1,4 @@ +| Extraction failed in modules/subdir1/test.go with error cannot find package "subdir1/subsubdir1" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 | +| Extraction failed in modules/subdir1/test.go with error could not import subdir1/subsubdir1 (invalid package name: "") | 2 | +| Extraction failed in modules/subdir2/test.go with error cannot find package "subdir2/subsubdir2" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 | +| Extraction failed in modules/subdir2/test.go with error could not import subdir2/subsubdir2 (invalid package name: "") | 2 | diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.py b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.ql b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.ql new file mode 100644 index 00000000000..e817bb2938a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/single-go-work-not-in-root/test.ql @@ -0,0 +1,6 @@ +import go +import semmle.go.DiagnosticsReporting + +from string msg, int sev +where reportableDiagnostics(_, msg, sev) +select msg, sev diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/diagnostics.expected new file mode 100644 index 00000000000..226eb35d3ab --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/diagnostics.expected @@ -0,0 +1,28 @@ +{ + "markdownMessage": "2 `go.mod` files were found:\n\n`subdir0/go.mod`, `subdir0/subdir1/go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/multiple-go-mod-found-nested", + "name": "Multiple `go.mod` files were found, all nested under one root `go.mod` file" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} +{ + "markdownMessage": "2 packages could not be found:\n\n`test/subdir2`, `subdir1/subsubdir1`.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "warning", + "source": { + "extractorName": "go", + "id": "go/autobuilder/package-not-found", + "name": "Some packages could not be found" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/go.mod b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/go.mod new file mode 100644 index 00000000000..4a38c9773d7 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module test diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/go.sum b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/go.mod b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/go.mod new file mode 100644 index 00000000000..147c51b8386 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module subdir1 diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/go.sum b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/subsubdir1/add.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/subsubdir1/add.go new file mode 100644 index 00000000000..900c2b5f266 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/subsubdir1/add.go @@ -0,0 +1,5 @@ +package subsubdir1 + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/test.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/test.go new file mode 100644 index 00000000000..5d4e906107b --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir1/test.go @@ -0,0 +1,14 @@ +package subdir1 + +import ( + "subdir1/subsubdir1" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subsubdir1.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir2/add.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir2/add.go new file mode 100644 index 00000000000..dfbcd101cf4 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/subdir2/add.go @@ -0,0 +1,5 @@ +package subdir2 + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/test.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/test.go new file mode 100644 index 00000000000..9db7bd16521 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/subdir0/test.go @@ -0,0 +1,14 @@ +package test + +import ( + "test/subdir2" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subdir2.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.expected b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.expected new file mode 100644 index 00000000000..38b4f954433 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.expected @@ -0,0 +1,4 @@ +| Extraction failed in subdir0/subdir1/test.go with error cannot find package "subdir1/subsubdir1" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 | +| Extraction failed in subdir0/subdir1/test.go with error could not import subdir1/subsubdir1 (invalid package name: "") | 2 | +| Extraction failed in subdir0/test.go with error cannot find package "test/subdir2" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 | +| Extraction failed in subdir0/test.go with error could not import test/subdir2 (invalid package name: "") | 2 | diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.py b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.ql b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.ql new file mode 100644 index 00000000000..e817bb2938a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-none-in-root/test.ql @@ -0,0 +1,6 @@ +import go +import semmle.go.DiagnosticsReporting + +from string msg, int sev +where reportableDiagnostics(_, msg, sev) +select msg, sev diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/diagnostics.expected new file mode 100644 index 00000000000..9b5d4cbc784 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/diagnostics.expected @@ -0,0 +1,14 @@ +{ + "markdownMessage": "2 `go.mod` files were found:\n\n`go.mod`, `subdir1/go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/multiple-go-mod-found-nested", + "name": "Multiple `go.mod` files were found, all nested under one root `go.mod` file" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/go.mod b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/go.mod new file mode 100644 index 00000000000..944de0f05b0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module main diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/go.sum b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/go.mod b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/go.mod new file mode 100644 index 00000000000..147c51b8386 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module subdir1 diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/go.sum b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/subsubdir1/add.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/subsubdir1/add.go new file mode 100644 index 00000000000..900c2b5f266 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/subsubdir1/add.go @@ -0,0 +1,5 @@ +package subsubdir1 + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/test.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/test.go new file mode 100644 index 00000000000..6081be52248 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir1/test.go @@ -0,0 +1,14 @@ +package subdir + +import ( + "subdir1/subsubdir1" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subsubdir1.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir2/add.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir2/add.go new file mode 100644 index 00000000000..dfbcd101cf4 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/subdir2/add.go @@ -0,0 +1,5 @@ +package subdir2 + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.expected b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.go new file mode 100644 index 00000000000..a298e0070ca --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.go @@ -0,0 +1,14 @@ +package main + +import ( + "main/subdir2" + + "golang.org/x/net/ipv4" +) + +func main() { + + header := ipv4.Header{} + header.Version = subdir2.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.py b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.ql b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.ql new file mode 100644 index 00000000000..e817bb2938a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-nested-one-in-root/test.ql @@ -0,0 +1,6 @@ +import go +import semmle.go.DiagnosticsReporting + +from string msg, int sev +where reportableDiagnostics(_, msg, sev) +select msg, sev diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/diagnostics.expected b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/diagnostics.expected new file mode 100644 index 00000000000..e07da8154d4 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/diagnostics.expected @@ -0,0 +1,28 @@ +{ + "markdownMessage": "2 `go.mod` files were found:\n\n`subdir1/go.mod`, `subdir2/go.mod`", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/multiple-go-mod-found-not-nested", + "name": "Multiple `go.mod` files found, not all nested under one root `go.mod` file" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} +{ + "markdownMessage": "2 packages could not be found:\n\n`subdir1/subsubdir1`, `subdir2/subsubdir2`.\n\nDefinitions in those packages may not be recognized by CodeQL, and files that use them may only be partially analyzed.\n\nCheck that the paths are correct and make sure any private packages can be accessed. If any of the packages are present in the repository then you may need a [custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "warning", + "source": { + "extractorName": "go", + "id": "go/autobuilder/package-not-found", + "name": "Some packages could not be found" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/go.mod b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/go.mod new file mode 100644 index 00000000000..147c51b8386 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module subdir1 diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/go.sum b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/subsubdir1/add.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/subsubdir1/add.go new file mode 100644 index 00000000000..900c2b5f266 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/subsubdir1/add.go @@ -0,0 +1,5 @@ +package subsubdir1 + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/test.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/test.go new file mode 100644 index 00000000000..6081be52248 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir1/test.go @@ -0,0 +1,14 @@ +package subdir + +import ( + "subdir1/subsubdir1" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subsubdir1.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/go.mod b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/go.mod new file mode 100644 index 00000000000..c6eec7d9ca5 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/go.mod @@ -0,0 +1,5 @@ +go 1.14 + +require golang.org/x/net v0.0.0-20200505041828-1ed23360d12c + +module subdir2 diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/go.sum b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/go.sum new file mode 100644 index 00000000000..6c5ffa613d0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/subsubdir2/add.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/subsubdir2/add.go new file mode 100644 index 00000000000..99810d11db0 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/subsubdir2/add.go @@ -0,0 +1,5 @@ +package subsubdir2 + +func Add(a, b int) int { + return a + b +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/test.go b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/test.go new file mode 100644 index 00000000000..e2d423cc2b6 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/subdir2/test.go @@ -0,0 +1,14 @@ +package subdir + +import ( + "subdir2/subsubdir2" + + "golang.org/x/net/ipv4" +) + +func test() { + + header := ipv4.Header{} + header.Version = subsubdir2.Add(2, 2) + +} diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.expected b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.expected new file mode 100644 index 00000000000..8435a54f44a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.expected @@ -0,0 +1,4 @@ +| Extraction failed in subdir1/test.go with error cannot find package "subdir1/subsubdir1" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 | +| Extraction failed in subdir1/test.go with error could not import subdir1/subsubdir1 (invalid package name: "") | 2 | +| Extraction failed in subdir2/test.go with error cannot find package "subdir2/subsubdir2" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) | 2 | +| Extraction failed in subdir2/test.go with error could not import subdir2/subsubdir2 (invalid package name: "") | 2 | diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.py b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.py new file mode 100644 index 00000000000..71ace021c22 --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.py @@ -0,0 +1,8 @@ +import sys + +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], lang="go") + +check_diagnostics() diff --git a/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.ql b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.ql new file mode 100644 index 00000000000..e817bb2938a --- /dev/null +++ b/go/ql/integration-tests/all-platforms/go/two-go-mods-not-nested/test.ql @@ -0,0 +1,6 @@ +import go +import semmle.go.DiagnosticsReporting + +from string msg, int sev +where reportableDiagnostics(_, msg, sev) +select msg, sev diff --git a/go/ql/integration-tests/linux-only/go/dep-sample/diagnostics.expected b/go/ql/integration-tests/linux-only/go/dep-sample/diagnostics.expected new file mode 100644 index 00000000000..e62ab7a3421 --- /dev/null +++ b/go/ql/integration-tests/linux-only/go/dep-sample/diagnostics.expected @@ -0,0 +1,14 @@ +{ + "markdownMessage": "A dep `Gopkg.toml` file was found", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/gopkg-toml-found", + "name": "A dep `Gopkg.toml` file was found" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/linux-only/go/dep-sample/test.py b/go/ql/integration-tests/linux-only/go/dep-sample/test.py index 43b1914296f..11673fd71e9 100644 --- a/go/ql/integration-tests/linux-only/go/dep-sample/test.py +++ b/go/ql/integration-tests/linux-only/go/dep-sample/test.py @@ -2,6 +2,9 @@ import os import sys from create_database_utils import * +from diagnostics_test_utils import * os.environ['LGTM_INDEX_IMPORT_PATH'] = "deptest" run_codeql_database_create([], lang="go", source="work") + +check_diagnostics() diff --git a/go/ql/integration-tests/linux-only/go/glide-sample/diagnostics.expected b/go/ql/integration-tests/linux-only/go/glide-sample/diagnostics.expected new file mode 100644 index 00000000000..1cf9bcfa389 --- /dev/null +++ b/go/ql/integration-tests/linux-only/go/glide-sample/diagnostics.expected @@ -0,0 +1,14 @@ +{ + "markdownMessage": "A Glide `glide.yaml` file was found", + "severity": "note", + "source": { + "extractorName": "go", + "id": "go/autobuilder/glide-yaml-found", + "name": "A Glide `glide.yaml` file was found" + }, + "visibility": { + "cliSummaryTable": false, + "statusPage": false, + "telemetry": true + } +} diff --git a/go/ql/integration-tests/linux-only/go/glide-sample/test.py b/go/ql/integration-tests/linux-only/go/glide-sample/test.py index 6497dfe9cf7..0595b51c07b 100644 --- a/go/ql/integration-tests/linux-only/go/glide-sample/test.py +++ b/go/ql/integration-tests/linux-only/go/glide-sample/test.py @@ -2,6 +2,9 @@ import os import sys from create_database_utils import * +from diagnostics_test_utils import * os.environ['LGTM_INDEX_IMPORT_PATH'] = "glidetest" run_codeql_database_create([], lang="go", source="work") + +check_diagnostics() diff --git a/go/ql/lib/CHANGELOG.md b/go/ql/lib/CHANGELOG.md index 6a9a07074b8..92bc9a062ad 100644 --- a/go/ql/lib/CHANGELOG.md +++ b/go/ql/lib/CHANGELOG.md @@ -1,3 +1,23 @@ +## 0.6.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* Parameter nodes now exist for unused parameters as well as used parameters. +* Add support for v4 of the [Go Micro framework](https://github.com/go-micro/go-micro). +* Support for the [Bun framework](https://bun.uptrace.dev/) has been added. +* Support for [gqlgen](https://github.com/99designs/gqlgen) has been added. +* Support for the [go-pg framework](https://github.com/go-pg/pg) has been improved. + ## 0.6.0 ### Deprecated APIs diff --git a/go/ql/lib/change-notes/released/0.6.1.md b/go/ql/lib/change-notes/released/0.6.1.md new file mode 100644 index 00000000000..8178174d68f --- /dev/null +++ b/go/ql/lib/change-notes/released/0.6.1.md @@ -0,0 +1,19 @@ +## 0.6.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* Parameter nodes now exist for unused parameters as well as used parameters. +* Add support for v4 of the [Go Micro framework](https://github.com/go-micro/go-micro). +* Support for the [Bun framework](https://bun.uptrace.dev/) has been added. +* Support for [gqlgen](https://github.com/99designs/gqlgen) has been added. +* Support for the [go-pg framework](https://github.com/go-pg/pg) has been improved. diff --git a/go/ql/lib/codeql-pack.release.yml b/go/ql/lib/codeql-pack.release.yml index a3f820f884d..80fb0899f64 100644 --- a/go/ql/lib/codeql-pack.release.yml +++ b/go/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.6.0 +lastReleaseVersion: 0.6.1 diff --git a/go/ql/lib/go.qll b/go/ql/lib/go.qll index 038916ed76a..8ade2f7f4ab 100644 --- a/go/ql/lib/go.qll +++ b/go/ql/lib/go.qll @@ -40,7 +40,9 @@ import semmle.go.frameworks.Email import semmle.go.frameworks.Encoding import semmle.go.frameworks.Gin import semmle.go.frameworks.Glog +import semmle.go.frameworks.GoMicro import semmle.go.frameworks.GoRestfulHttp +import semmle.go.frameworks.Gqlgen import semmle.go.frameworks.K8sIoApimachineryPkgRuntime import semmle.go.frameworks.K8sIoApiCoreV1 import semmle.go.frameworks.K8sIoClientGo diff --git a/go/ql/lib/qlpack.yml b/go/ql/lib/qlpack.yml index a93ef4564f1..c67e35c49cf 100644 --- a/go/ql/lib/qlpack.yml +++ b/go/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/go-all -version: 0.6.0 +version: 0.6.1 groups: go dbscheme: go.dbscheme extractor: go diff --git a/go/ql/lib/semmle/go/DiagnosticsReporting.qll b/go/ql/lib/semmle/go/DiagnosticsReporting.qll index d74a709dffb..653e3ad7c5e 100644 --- a/go/ql/lib/semmle/go/DiagnosticsReporting.qll +++ b/go/ql/lib/semmle/go/DiagnosticsReporting.qll @@ -35,6 +35,26 @@ private class Diagnostic extends @diagnostic { string toString() { result = this.getMessage() } } +bindingset[msg] +private string removeAbsolutePaths(string msg) { + exists(string r | + // turn both + // cannot find package "subdir1/subsubdir1" in any of:\n\t/usr/local/Cellar/go/1.20.5/libexec/src/subdir1/subsubdir1 (from $GOROOT)\n\t/Users/owen-mc/go/src/subdir1/subsubdir1 (from $GOPATH) + // and + // cannot find package "subdir1/subsubdir1" in any of:\n\tC:\\hostedtoolcache\\windows\\go\\1.20.5\\x64\\src\\subdir1\\subsubdir1 (from $GOROOT)\n\tC:\\Users\\runneradmin\\go\\src\\subdir1\\subsubdir1 (from $GOPATH) + // into + // cannot find package "subdir1/subsubdir1" in any of:\n\t(absolute path) (from $GOROOT)\n\t(absolute path) (from $GOPATH) + r = + "(cannot find package [^ ]* in any of:\\n\\t).*( \\(from \\$GOROOT\\)\\n\\t).*( \\(from \\$GOPATH\\))" and + if exists(msg.regexpCapture(r, 1)) + then + result = + msg.regexpCapture(r, 1) + "(absolute path)" + msg.regexpCapture(r, 2) + "(absolute path)" + + msg.regexpCapture(r, 3) + else result = msg + ) +} + /** * Holds if an extraction error or warning occurred that should be reported to end users, * with the error message `msg` and SARIF severity `sev`. @@ -47,10 +67,11 @@ predicate reportableDiagnostics(Diagnostic d, string msg, int sev) { exists(File f | f = d.getFile() | exists(f.getAChild()) and msg = - "Extraction failed in " + d.getFile().getRelativePath() + " with error " + d.getMessage() + "Extraction failed in " + d.getFile().getRelativePath() + " with error " + + removeAbsolutePaths(d.getMessage()) ) or not exists(d.getFile()) and - msg = "Extraction failed with error " + d.getMessage() + msg = "Extraction failed with error " + removeAbsolutePaths(d.getMessage()) ) } diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll index f34554d6eea..03975c6a54a 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll @@ -46,6 +46,14 @@ signature module ConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or isAdditionalFlowStep(_, node) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -114,7 +122,7 @@ signature module StateConfigSig { * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state); + default predicate isBarrier(Node node, FlowState state) { none() } /** Holds if data flow into `node` is prohibited. */ default predicate isBarrierIn(Node node) { none() } @@ -131,7 +139,9 @@ signature module StateConfigSig { * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + default predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } /** * Holds if an arbitrary number of implicit read steps of content `c` may be @@ -139,6 +149,17 @@ signature module StateConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or + isAdditionalFlowStep(_, node) or + isAdditionalFlowStep(node, _, _, _) or + isAdditionalFlowStep(_, _, node, _) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll index 410543e0fc9..29561b0f0a6 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll @@ -66,6 +66,12 @@ signature module FullStateConfigSig { */ predicate allowImplicitRead(Node node, ContentSet c); + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + predicate neverSkip(Node node); + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -254,6 +260,11 @@ module Impl { not fullBarrier(node2) } + pragma[nomagic] + private predicate isUnreachableInCall1(NodeEx n, LocalCallContextSpecificCall cc) { + isUnreachableInCallCached(n.asNode(), cc.getCall()) + } + /** * Holds if data can flow in one local step from `node1` to `node2`. */ @@ -2019,7 +2030,8 @@ module Impl { castNode(this.asNode()) or clearsContentCached(this.asNode(), _) or expectsContentCached(this.asNode(), _) or - neverSkipInPathGraph(this.asNode()) + neverSkipInPathGraph(this.asNode()) or + Config::neverSkip(this.asNode()) } } @@ -2108,7 +2120,7 @@ module Impl { NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, LocalCallContext cc ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCall1(node2, cc) and ( localFlowEntry(node1, pragma[only_bind_into](state)) and ( @@ -2123,7 +2135,7 @@ module Impl { ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) + not isUnreachableInCall1(node1, cc) or exists(NodeEx mid | localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and @@ -2160,10 +2172,8 @@ module Impl { preservesValue = false and t = node2.getDataFlowType() and callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) + not isUnreachableInCall1(node1, callContext) and + not isUnreachableInCall1(node2, callContext) } } @@ -2703,7 +2713,7 @@ module Impl { ParamNodeEx getParamNode() { result = p } - override string toString() { result = p + ": " + ap } + override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn @@ -2755,12 +2765,21 @@ module Impl { ) } + private predicate forceUnfold(AccessPathApprox apa) { + forceHighPrecision(apa.getHead()) + or + exists(Content c2 | + apa = TConsCons(_, _, c2, _) and + forceHighPrecision(c2) + ) + } + /** * Holds with `unfold = false` if a precise head-tail representation of `apa` is * expected to be expensive. Holds with `unfold = true` otherwise. */ private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { - if forceHighPrecision(apa.getHead()) + if forceUnfold(apa) then unfold = true else exists(int aps, int nodes, int apLimit, int tupleLimit | @@ -3089,6 +3108,12 @@ module Impl { result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } + private string ppSummaryCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">" + } + /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() } @@ -3097,7 +3122,9 @@ module Impl { * representation of the call context. */ string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + result = + this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + + this.ppSummaryCtx() } /** diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll index be70086a93a..b0de9745816 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll index be70086a93a..b0de9745816 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll index be70086a93a..b0de9745816 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll index 84ed6d5f61c..59224024ec3 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll @@ -680,6 +680,24 @@ module Public { } } + /** A representation of a parameter initialization, defined in source via an SSA node. */ + class UnusedParameterNode extends ParameterNode, InstructionNode { + override IR::InitParameterInstruction insn; + Parameter parm; + + UnusedParameterNode() { + insn = IR::initParamInstruction(parm) and + not exists(SsaExplicitDefinition ssa | ssa.getInstruction() = insn) + } + + /** Gets the parameter this node initializes. */ + override Parameter asParameter() { result = parm } + + override predicate isParameterOf(DataFlowCallable c, int i) { + parm.isParameterOf(c.asCallable().getFuncDef(), i) + } + } + /** A representation of a parameter initialization, defined in source via an SSA node. */ class SsaParameterNode extends ParameterNode, SsaNode { override SsaExplicitDefinition ssa; diff --git a/go/ql/lib/semmle/go/frameworks/GoMicro.qll b/go/ql/lib/semmle/go/frameworks/GoMicro.qll new file mode 100644 index 00000000000..de775761c64 --- /dev/null +++ b/go/ql/lib/semmle/go/frameworks/GoMicro.qll @@ -0,0 +1,154 @@ +/** + * Provides models of the [Go Micro library](https://github.com/go-micro/go-micro). + */ + +import go +private import semmle.go.security.RequestForgeryCustomizations + +/** + * Module for Go Micro framework. + */ +module GoMicro { + /** + * A GoMicro server type. + */ + class GoMicroServerType extends Type { + GoMicroServerType() { this.hasQualifiedName("go-micro.dev/v4/server", "Server") } + } + + /** + * A GoMicro client type. + */ + class GoMicroClientType extends Type { + GoMicroClientType() { this.hasQualifiedName("go-micro.dev/v4/client", "Client") } + } + + /** + * A file that is generated by the protobuf compiler. + */ + class ProtocGeneratedFile extends File { + ProtocGeneratedFile() { this.getBaseName().regexpMatch(".*\\.pb(\\.micro)?\\.go$") } + } + + /** + * A type that is generated by the protobuf compiler. + */ + class ProtocMessageType extends Type { + ProtocMessageType() { + this.hasLocationInfo(any(ProtocGeneratedFile f).getAbsolutePath(), _, _, _, _) and + exists(MethodDecl md | + md.getName() = "ProtoMessage" and + this = md.getReceiverDecl().getTypeExpr().getAChild().(TypeName).getType() + ) + } + } + + /** + * A Server Interface type. + */ + class ServiceInterfaceType extends InterfaceType { + NamedType namedType; + + ServiceInterfaceType() { + this = namedType.getUnderlyingType() and + namedType.hasLocationInfo(any(ProtocGeneratedFile f).getAbsolutePath(), _, _, _, _) + } + + /** + * Gets the name of the interface. + */ + override string getName() { result = namedType.getName() } + + /** + * Gets the named type on top of this interface type. + */ + NamedType getNamedType() { result = namedType } + } + + /** + * A Service server handler type. + */ + class ServiceServerType extends NamedType { + ServiceServerType() { + this.implements(any(ServiceInterfaceType i)) and + this.getName().regexpMatch("(?i).*Handler") and + this.hasLocationInfo(any(ProtocGeneratedFile f).getAbsolutePath(), _, _, _, _) + } + } + + /** + * A Client server handler type. + */ + class ClientServiceType extends NamedType { + ClientServiceType() { + this.implements(any(ServiceInterfaceType i)) and + this.getName().regexpMatch("(?i).*Service") and + this.hasLocationInfo(any(ProtocGeneratedFile f).getAbsolutePath(), _, _, _, _) + } + } + + /** + * A service register handler. + */ + class ServiceRegisterHandler extends Function { + ServiceRegisterHandler() { + this.getName().regexpMatch("(?i)register" + any(ServiceServerType c).getName()) and + this.getParameterType(0) instanceof GoMicroServerType and + this.hasLocationInfo(any(ProtocGeneratedFile f).getAbsolutePath(), _, _, _, _) + } + } + + /** + * A service handler. + */ + class ServiceHandler extends Method { + ServiceHandler() { + exists(DataFlow::CallNode call | + call.getTarget() instanceof ServiceRegisterHandler and + this = call.getArgument(1).getType().getMethod(_) and + this.implements(any(ServiceInterfaceType i).getNamedType().getMethod(_)) + ) + } + } + + /** + * A client service function. + */ + class ClientService extends Function { + ClientService() { + this.getName().regexpMatch("(?i)new" + any(ClientServiceType c).getName()) and + this.getParameterType(0) instanceof StringType and + this.getParameterType(1) instanceof GoMicroClientType and + this.hasLocationInfo(any(ProtocGeneratedFile f).getAbsolutePath(), _, _, _, _) + } + } + + /** + * An SSRF sink for the Client service function. + */ + class ClientRequestUrlAsSink extends RequestForgery::Sink { + ClientRequestUrlAsSink() { + exists(DataFlow::CallNode call | + call.getArgument(0) = this and + call.getTarget() instanceof ClientService + ) + } + + override DataFlow::Node getARequest() { result = this } + + override string getKind() { result = "URL" } + } + + /** + * A set of remote requests from a service handler. + */ + class Request extends UntrustedFlowSource::Range instanceof DataFlow::ParameterNode { + Request() { + exists(ServiceHandler handler | + this.asParameter().isParameterOf(handler.getFuncDecl(), 1) and + handler.getParameterType(0).hasQualifiedName("context", "Context") and + this.getType().(PointerType).getBaseType() instanceof ProtocMessageType + ) + } + } +} diff --git a/go/ql/lib/semmle/go/frameworks/Gqlgen.qll b/go/ql/lib/semmle/go/frameworks/Gqlgen.qll new file mode 100644 index 00000000000..a4c3993d5d4 --- /dev/null +++ b/go/ql/lib/semmle/go/frameworks/Gqlgen.qll @@ -0,0 +1,47 @@ +/** Provides models of commonly used functions and types in the gqlgen packages. */ + +import go + +/** Provides models of commonly used functions and types in the gqlgen packages. */ +module Gqlgen { + /** An autogenerated file containing gqlgen code. */ + private class GqlgenGeneratedFile extends File { + GqlgenGeneratedFile() { + exists(DataFlow::CallNode call | + call.getReceiver().getType().hasQualifiedName("github.com/99designs/gqlgen/graphql", _) and + call.getFile() = this + ) + } + } + + /** A resolver interface. */ + private class ResolverInterface extends Type { + ResolverInterface() { + this.getQualifiedName().matches("%Resolver") and + this.getEntity().getDeclaration().getFile() instanceof GqlgenGeneratedFile + } + } + + /** A resolver implementation. */ + private class ResolverInterfaceMethod extends Method { + ResolverInterfaceMethod() { this.getReceiver().getType() instanceof ResolverInterface } + } + + /** A resolver method which is exposed as a Graphql endpoint */ + private class ResolverImplementationMethod extends Method { + ResolverImplementationMethod() { this.implements(any(ResolverInterfaceMethod r)) } + + Parameter getAnUntrustedParameter() { + result.getFunction() = this.getFuncDecl() and + not result.getType().hasQualifiedName("context", "Context") and + result.getIndex() > 0 + } + } + + /** A parameter of a resolver method which receives untrusted input. */ + class ResolverParameter extends UntrustedFlowSource::Range instanceof DataFlow::ParameterNode { + ResolverParameter() { + this.asParameter() = any(ResolverImplementationMethod h).getAnUntrustedParameter() + } + } +} diff --git a/go/ql/lib/semmle/go/frameworks/SQL.qll b/go/ql/lib/semmle/go/frameworks/SQL.qll index 185f0b3f2bf..f76182fb111 100644 --- a/go/ql/lib/semmle/go/frameworks/SQL.qll +++ b/go/ql/lib/semmle/go/frameworks/SQL.qll @@ -145,9 +145,25 @@ module SQL { f.hasQualifiedName(gopgorm(), "Q") and arg = 0 or - exists(string tp, string m | f.(Method).hasQualifiedName(gopgorm(), tp, m) | + exists(string tp, string m | f.(Method).hasQualifiedName([gopgorm(), gopg()], tp, m) | + tp = ["DB", "Conn"] and + m = ["QueryContext", "QueryOneContext"] and + arg = 2 + or + tp = ["DB", "Conn"] and + m = ["ExecContext", "ExecOneContext", "Query", "QueryOne"] and + arg = 1 + or + tp = ["DB", "Conn"] and + m = ["Exec", "ExecOne", "Prepare"] and + arg = 0 + or tp = "Query" and - m = ["ColumnExpr", "For", "Having", "Where", "WhereIn", "WhereInMulti", "WhereOr"] and + m = + [ + "ColumnExpr", "For", "GroupExpr", "Having", "Join", "OrderExpr", "TableExpr", + "Where", "WhereIn", "WhereInMulti", "WhereOr" + ] and arg = 0 or tp = "Query" and @@ -289,3 +305,47 @@ module Xorm { } } } + +/** + * Provides classes for working with the [Bun](https://bun.uptrace.dev/) package. + */ +module Bun { + /** Gets the package name for Bun package. */ + private string packagePath() { result = package("github.com/uptrace/bun", "") } + + /** A model for sinks of Bun. */ + private class BunSink extends SQL::QueryString::Range { + BunSink() { + exists(Function f, string m, int arg | this = f.getACall().getArgument(arg) | + f.hasQualifiedName(packagePath(), m) and + m = "NewRawQuery" and + arg = 1 + ) + or + exists(Method f, string tp, string m, int arg | this = f.getACall().getArgument(arg) | + f.hasQualifiedName(packagePath(), tp, m) and + ( + tp = ["DB", "Conn"] and + m = ["ExecContext", "PrepareContext", "QueryContext", "QueryRowContext"] and + arg = 1 + or + tp = ["DB", "Conn"] and + m = ["Exec", "NewRaw", "Prepare", "Query", "QueryRow", "Raw"] and + arg = 0 + or + tp.matches("%Query") and + m = + [ + "ColumnExpr", "DistinctOn", "For", "GroupExpr", "Having", "ModelTableExpr", + "OrderExpr", "TableExpr", "Where", "WhereOr" + ] and + arg = 0 + or + tp = "RawQuery" and + m = "NewRaw" and + arg = 0 + ) + ) + } + } +} diff --git a/go/ql/lib/semmle/go/frameworks/Twirp.qll b/go/ql/lib/semmle/go/frameworks/Twirp.qll index c4fc0737ac1..03451fcac8d 100644 --- a/go/ql/lib/semmle/go/frameworks/Twirp.qll +++ b/go/ql/lib/semmle/go/frameworks/Twirp.qll @@ -35,83 +35,59 @@ module Twirp { } } - /** - * A type representing a protobuf message. - */ + /** A type representing a protobuf message. */ class ProtobufMessageType extends Type { ProtobufMessageType() { - exists(TypeEntity te | - te.getType() = this and - te.getDeclaration().getLocation().getFile() instanceof ProtobufGeneratedFile - ) + this.hasLocationInfo(any(ProtobufGeneratedFile f).getAbsolutePath(), _, _, _, _) } } - /** - * An interface type representing a Twirp service. - */ + /** An interface type representing a Twirp service. */ class ServiceInterfaceType extends InterfaceType { NamedType namedType; ServiceInterfaceType() { - exists(TypeEntity te | - te.getType() = namedType and - namedType.getUnderlyingType() = this and - te.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile - ) + namedType.getUnderlyingType() = this and + namedType.hasLocationInfo(any(ServicesGeneratedFile f).getAbsolutePath(), _, _, _, _) } - /** - * Gets the name of the interface. - */ + /** Gets the name of the interface. */ override string getName() { result = namedType.getName() } - /** - * Gets the named type on top of this interface type. - */ + /** Gets the named type on top of this interface type. */ NamedType getNamedType() { result = namedType } } - /** - * A Twirp client. - */ + /** A Twirp client. */ class ServiceClientType extends NamedType { ServiceClientType() { - exists(ServiceInterfaceType i, PointerType p, TypeEntity te | + exists(ServiceInterfaceType i, PointerType p | p.implements(i) and this = p.getBaseType() and this.getName().regexpMatch("(?i)" + i.getName() + "(protobuf|json)client") and - te.getType() = this and - te.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile + this.hasLocationInfo(any(ServicesGeneratedFile f).getAbsolutePath(), _, _, _, _) ) } } - /** - * A Twirp server. - */ + /** A Twirp server. */ class ServiceServerType extends NamedType { ServiceServerType() { - exists(ServiceInterfaceType i, TypeEntity te | + exists(ServiceInterfaceType i | this.implements(i) and this.getName().regexpMatch("(?i)" + i.getName() + "server") and - te.getType() = this and - te.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile + this.hasLocationInfo(any(ServicesGeneratedFile f).getAbsolutePath(), _, _, _, _) ) } } - /** - * A Twirp function to construct a Client. - */ + /** A Twirp function to construct a Client. */ class ClientConstructor extends Function { ClientConstructor() { - exists(ServiceClientType c | - this.getName().regexpMatch("(?i)new" + c.getName()) and - this.getParameterType(0) instanceof StringType and - this.getParameterType(1).getName() = "HTTPClient" and - this.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile - ) + this.getName().regexpMatch("(?i)new" + any(ServiceClientType c).getName()) and + this.getParameterType(0) instanceof StringType and + this.getParameterType(1).getName() = "HTTPClient" and + this.hasLocationInfo(any(ServicesGeneratedFile f).getAbsolutePath(), _, _, _, _) } } @@ -122,17 +98,13 @@ module Twirp { */ class ServerConstructor extends Function { ServerConstructor() { - exists(ServiceServerType c, ServiceInterfaceType i | - this.getName().regexpMatch("(?i)new" + c.getName()) and - this.getParameterType(0) = i.getNamedType() and - this.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile - ) + this.getName().regexpMatch("(?i)new" + any(ServiceServerType c).getName()) and + this.getParameterType(0) = any(ServiceInterfaceType i).getNamedType() and + this.hasLocationInfo(any(ServicesGeneratedFile f).getAbsolutePath(), _, _, _, _) } } - /** - * An SSRF sink for the Client constructor. - */ + /** An SSRF sink for the Client constructor. */ class ClientRequestUrlAsSink extends RequestForgery::Sink { ClientRequestUrlAsSink() { exists(DataFlow::CallNode call | @@ -146,27 +118,22 @@ module Twirp { override string getKind() { result = "URL" } } - /** - * A service handler. - */ + /** A service handler. */ class ServiceHandler extends Method { ServiceHandler() { - exists(DataFlow::CallNode call, Type handlerType, ServiceInterfaceType i | + exists(DataFlow::CallNode call | call.getTarget() instanceof ServerConstructor and - call.getArgument(0).getType() = handlerType and - this = handlerType.getMethod(_) and - this.implements(i.getNamedType().getMethod(_)) + this = call.getArgument(0).getType().getMethod(_) and + this.implements(any(ServiceInterfaceType i).getNamedType().getMethod(_)) ) } } - /** - * A request coming to the service handler. - */ + /** A request coming to the service handler. */ class Request extends UntrustedFlowSource::Range instanceof DataFlow::ParameterNode { Request() { - exists(FuncDef c, ServiceHandler handler | handler.getFuncDecl() = c | - this.asParameter().isParameterOf(c, 1) and + exists(ServiceHandler handler | + this.asParameter().isParameterOf(handler.getFuncDecl(), 1) and handler.getParameterType(0).hasQualifiedName("context", "Context") and this.getType().(PointerType).getBaseType() instanceof ProtobufMessageType ) diff --git a/go/ql/src/CHANGELOG.md b/go/ql/src/CHANGELOG.md index 2b87cb252c4..aa3b9019d46 100644 --- a/go/ql/src/CHANGELOG.md +++ b/go/ql/src/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.1 + +No user-facing changes. + ## 0.6.0 ### Bug Fixes diff --git a/go/ql/src/change-notes/released/0.6.1.md b/go/ql/src/change-notes/released/0.6.1.md new file mode 100644 index 00000000000..6008e49b8e7 --- /dev/null +++ b/go/ql/src/change-notes/released/0.6.1.md @@ -0,0 +1,3 @@ +## 0.6.1 + +No user-facing changes. diff --git a/go/ql/src/codeql-pack.release.yml b/go/ql/src/codeql-pack.release.yml index a3f820f884d..80fb0899f64 100644 --- a/go/ql/src/codeql-pack.release.yml +++ b/go/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.6.0 +lastReleaseVersion: 0.6.1 diff --git a/go/ql/src/experimental/CWE-134/DsnBad.go b/go/ql/src/experimental/CWE-74/DsnBad.go similarity index 100% rename from go/ql/src/experimental/CWE-134/DsnBad.go rename to go/ql/src/experimental/CWE-74/DsnBad.go diff --git a/go/ql/src/experimental/CWE-134/DsnGood.go b/go/ql/src/experimental/CWE-74/DsnGood.go similarity index 100% rename from go/ql/src/experimental/CWE-134/DsnGood.go rename to go/ql/src/experimental/CWE-74/DsnGood.go diff --git a/go/ql/src/experimental/CWE-134/DsnInjection.qhelp b/go/ql/src/experimental/CWE-74/DsnInjection.qhelp similarity index 100% rename from go/ql/src/experimental/CWE-134/DsnInjection.qhelp rename to go/ql/src/experimental/CWE-74/DsnInjection.qhelp diff --git a/go/ql/src/experimental/CWE-134/DsnInjection.ql b/go/ql/src/experimental/CWE-74/DsnInjection.ql similarity index 81% rename from go/ql/src/experimental/CWE-134/DsnInjection.ql rename to go/ql/src/experimental/CWE-74/DsnInjection.ql index 89bb83f9284..3197d04534b 100644 --- a/go/ql/src/experimental/CWE-134/DsnInjection.ql +++ b/go/ql/src/experimental/CWE-74/DsnInjection.ql @@ -6,7 +6,7 @@ * @id go/dsn-injection * @tags security * experimental - * external/cwe/cwe-134 + * external/cwe/cwe-74 */ import go @@ -18,5 +18,5 @@ private class UntrustedFlowAsSource extends Source instanceof UntrustedFlowSourc from DsnInjection cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "This query depends on a $@.", source.getNode(), - "user-provided value" +select sink.getNode(), source, sink, "Data-Source Name is built using $@.", source.getNode(), + "untrusted user input" diff --git a/go/ql/src/experimental/CWE-134/DsnInjectionCustomizations.qll b/go/ql/src/experimental/CWE-74/DsnInjectionCustomizations.qll similarity index 90% rename from go/ql/src/experimental/CWE-134/DsnInjectionCustomizations.qll rename to go/ql/src/experimental/CWE-74/DsnInjectionCustomizations.qll index de547b8a07d..e66cb7cba73 100644 --- a/go/ql/src/experimental/CWE-134/DsnInjectionCustomizations.qll +++ b/go/ql/src/experimental/CWE-74/DsnInjectionCustomizations.qll @@ -14,8 +14,11 @@ class DsnInjection extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node node) { node instanceof Source } override predicate isSink(DataFlow::Node node) { - exists(Function f | f.hasQualifiedName("database/sql", "Open") | - node = f.getACall().getArgument(1) + exists(DataFlow::CallNode c | + c.getTarget().hasQualifiedName("database/sql", "Open") and + c.getArgument(0).getStringValue() = "mysql" + | + node = c.getArgument(1) ) } diff --git a/go/ql/src/experimental/CWE-134/DsnInjectionLocal.ql b/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql similarity index 96% rename from go/ql/src/experimental/CWE-134/DsnInjectionLocal.ql rename to go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql index 7ecd3b1cc8a..ff4142f12d4 100644 --- a/go/ql/src/experimental/CWE-134/DsnInjectionLocal.ql +++ b/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql @@ -6,7 +6,7 @@ * @id go/dsn-injection-local * @tags security * experimental - * external/cwe/cwe-134 + * external/cwe/cwe-74 */ import go diff --git a/go/ql/src/qlpack.yml b/go/ql/src/qlpack.yml index f9bbdca3b73..0da34268dc8 100644 --- a/go/ql/src/qlpack.yml +++ b/go/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/go-queries -version: 0.6.0 +version: 0.6.1 groups: - go - queries diff --git a/go/ql/test/experimental/CWE-134/DsnInjection.qlref b/go/ql/test/experimental/CWE-134/DsnInjection.qlref deleted file mode 100644 index c2308280884..00000000000 --- a/go/ql/test/experimental/CWE-134/DsnInjection.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/CWE-134/DsnInjection.ql \ No newline at end of file diff --git a/go/ql/test/experimental/CWE-134/DsnInjectionLocal.qlref b/go/ql/test/experimental/CWE-134/DsnInjectionLocal.qlref deleted file mode 100644 index b7b7e2bdbdd..00000000000 --- a/go/ql/test/experimental/CWE-134/DsnInjectionLocal.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/CWE-134/DsnInjectionLocal.ql \ No newline at end of file diff --git a/go/ql/test/experimental/CWE-134/Dsn.go b/go/ql/test/experimental/CWE-74/Dsn.go similarity index 100% rename from go/ql/test/experimental/CWE-134/Dsn.go rename to go/ql/test/experimental/CWE-74/Dsn.go diff --git a/go/ql/test/experimental/CWE-134/DsnInjection.expected b/go/ql/test/experimental/CWE-74/DsnInjection.expected similarity index 80% rename from go/ql/test/experimental/CWE-134/DsnInjection.expected rename to go/ql/test/experimental/CWE-74/DsnInjection.expected index 531bdb0ead2..2f92b181757 100644 --- a/go/ql/test/experimental/CWE-134/DsnInjection.expected +++ b/go/ql/test/experimental/CWE-74/DsnInjection.expected @@ -9,4 +9,4 @@ nodes | Dsn.go:50:29:50:33 | dbDSN | semmle.label | dbDSN | subpaths #select -| Dsn.go:50:29:50:33 | dbDSN | Dsn.go:47:10:47:30 | call to FormValue | Dsn.go:50:29:50:33 | dbDSN | This query depends on a $@. | Dsn.go:47:10:47:30 | call to FormValue | user-provided value | +| Dsn.go:50:29:50:33 | dbDSN | Dsn.go:47:10:47:30 | call to FormValue | Dsn.go:50:29:50:33 | dbDSN | Data-Source Name is built using $@. | Dsn.go:47:10:47:30 | call to FormValue | untrusted user input | diff --git a/go/ql/test/experimental/CWE-74/DsnInjection.qlref b/go/ql/test/experimental/CWE-74/DsnInjection.qlref new file mode 100644 index 00000000000..7bd852e221f --- /dev/null +++ b/go/ql/test/experimental/CWE-74/DsnInjection.qlref @@ -0,0 +1 @@ +experimental/CWE-74/DsnInjection.ql \ No newline at end of file diff --git a/go/ql/test/experimental/CWE-134/DsnInjectionLocal.expected b/go/ql/test/experimental/CWE-74/DsnInjectionLocal.expected similarity index 100% rename from go/ql/test/experimental/CWE-134/DsnInjectionLocal.expected rename to go/ql/test/experimental/CWE-74/DsnInjectionLocal.expected diff --git a/go/ql/test/experimental/CWE-74/DsnInjectionLocal.qlref b/go/ql/test/experimental/CWE-74/DsnInjectionLocal.qlref new file mode 100644 index 00000000000..97fa2ad022a --- /dev/null +++ b/go/ql/test/experimental/CWE-74/DsnInjectionLocal.qlref @@ -0,0 +1 @@ +experimental/CWE-74/DsnInjectionLocal.ql \ No newline at end of file diff --git a/go/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected b/go/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected index a78fff479a4..b9878f7e169 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected +++ b/go/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected @@ -4,6 +4,7 @@ | parameter 0 | reset.go:8:1:16:1 | function declaration | reset.go:8:27:8:27 | definition of r | | parameter 0 | tst2.go:8:1:12:1 | function declaration | tst2.go:8:12:8:15 | definition of data | | parameter 0 | tst.go:8:1:11:1 | function declaration | tst.go:8:12:8:17 | definition of reader | +| parameter 0 | tst.go:13:1:13:25 | function declaration | tst.go:13:12:13:13 | initialization of xs | | parameter 0 | tst.go:15:1:19:1 | function declaration | tst.go:15:12:15:12 | definition of x | | parameter 1 | main.go:5:1:11:1 | function declaration | main.go:5:20:5:20 | definition of x | | parameter 1 | main.go:13:1:20:1 | function declaration | main.go:13:21:13:21 | definition of x | diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/LogInjection.expected b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/LogInjection.expected new file mode 100644 index 00000000000..d23496fe4c5 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/LogInjection.expected @@ -0,0 +1,24 @@ +edges +| main.go:18:46:18:48 | definition of req | main.go:18:46:18:48 | definition of req | +| main.go:18:46:18:48 | definition of req | main.go:18:46:18:48 | definition of req | +| main.go:18:46:18:48 | definition of req | main.go:21:28:21:31 | name | +| main.go:18:46:18:48 | definition of req | main.go:21:28:21:31 | name | +| main.go:18:46:18:48 | definition of req | proto/Hello.pb.micro.go:85:53:85:54 | definition of in | +| proto/Hello.pb.micro.go:85:53:85:54 | definition of in | proto/Hello.pb.micro.go:85:53:85:54 | definition of in | +| proto/Hello.pb.micro.go:85:53:85:54 | definition of in | proto/Hello.pb.micro.go:86:37:86:38 | in | +| proto/Hello.pb.micro.go:85:53:85:54 | definition of in | proto/Hello.pb.micro.go:86:37:86:38 | in | +| proto/Hello.pb.micro.go:86:37:86:38 | in | main.go:18:46:18:48 | definition of req | +| proto/Hello.pb.micro.go:86:37:86:38 | in | main.go:18:46:18:48 | definition of req | +| proto/Hello.pb.micro.go:86:37:86:38 | in | proto/Hello.pb.micro.go:85:53:85:54 | definition of in | +| proto/Hello.pb.micro.go:86:37:86:38 | in | proto/Hello.pb.micro.go:85:53:85:54 | definition of in | +nodes +| main.go:18:46:18:48 | definition of req | semmle.label | definition of req | +| main.go:18:46:18:48 | definition of req | semmle.label | definition of req | +| main.go:21:28:21:31 | name | semmle.label | name | +| proto/Hello.pb.micro.go:85:53:85:54 | definition of in | semmle.label | definition of in | +| proto/Hello.pb.micro.go:85:53:85:54 | definition of in | semmle.label | definition of in | +| proto/Hello.pb.micro.go:86:37:86:38 | in | semmle.label | in | +| proto/Hello.pb.micro.go:86:37:86:38 | in | semmle.label | in | +subpaths +#select +| main.go:21:28:21:31 | name | main.go:18:46:18:48 | definition of req | main.go:21:28:21:31 | name | This log entry depends on a $@. | main.go:18:46:18:48 | definition of req | user-provided value | diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/LogInjection.qlref b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/LogInjection.qlref new file mode 100644 index 00000000000..1837c628c33 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/LogInjection.qlref @@ -0,0 +1 @@ +Security/CWE-117/LogInjection.ql diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/client/main.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/client/main.go new file mode 100644 index 00000000000..5126e855bb8 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/client/main.go @@ -0,0 +1,29 @@ +package main + +import ( + pb "codeql-go-tests/frameworks/GoMicro/proto" + "context" + "fmt" + "log" + + micro "go-micro.dev/v4" +) + +func main() { + // service + service := micro.NewService() + service.Init() + // context + ctx := context.Background() + + greeterService := pb.NewGreeterService("http://localhost:8000", service.Client()) // $ clientRequest="http:\/\/localhost:8000" + // request + req := pb.Request{Name: "Mona"} + resp, err := greeterService.Hello(ctx, &req) + + if err != nil { + log.Fatal(err) + } + + fmt.Println("Hello :: %s", resp.Greeting) +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/go.mod b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/go.mod new file mode 100644 index 00000000000..ee80bdcd3e1 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/go.mod @@ -0,0 +1,8 @@ +module codeql-go-tests/frameworks/GoMicro + +go 1.15 + +require ( + go-micro.dev/v4 v4.10.2 + google.golang.org/protobuf v1.28.1 +) diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/gomicro.expected b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/gomicro.expected new file mode 100644 index 00000000000..48de9172b36 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/gomicro.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/gomicro.ql b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/gomicro.ql new file mode 100644 index 00000000000..c4aa98a0048 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/gomicro.ql @@ -0,0 +1,26 @@ +import go +import TestUtilities.InlineExpectationsTest + +module GoMicroTest implements TestSig { + string getARelevantTag() { result = ["serverRequest", "clientRequest"] } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(DataFlow::Node node | + node.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), + location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and + ( + node instanceof GoMicro::Request and + element = node.toString() and + value = "\"" + node.toString() + "\"" and + tag = "serverRequest" + or + node instanceof GoMicro::ClientRequestUrlAsSink and + element = node.toString() and + value = node.toString().replaceAll("/", "\\/") and + tag = "clientRequest" + ) + ) + } +} + +import MakeTest diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/main.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/main.go new file mode 100644 index 00000000000..3eaacef9822 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/main.go @@ -0,0 +1,39 @@ +package main + +//go:generate depstubber -vendor go-micro.dev/v4 Service,Option,Options NewService,Name,Handle,Server,Client +//go:generate depstubber -vendor go-micro.dev/v4/server Server Handle +//go:generate depstubber -vendor go-micro.dev/v4/client Client Call + +import ( + pb "codeql-go-tests/frameworks/GoMicro/proto" + "context" + "fmt" + "log" + + micro "go-micro.dev/v4" +) + +type Greeter struct{} + +func (g *Greeter) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error { // $ serverRequest="definition of req" + // var access + name := req.Name + fmt.Println("Name :: %s", name) + return nil +} + +func main() { + // service + service := micro.NewService( + micro.Name("helloworld"), + micro.Handle(":8080"), + ) + + service.Init() + + pb.RegisterGreeterHandler(service.Server(), new(Greeter)) + + if err := service.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.pb.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.pb.go new file mode 100644 index 00000000000..cd0f3f66c27 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.pb.go @@ -0,0 +1,209 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.23.3 +// source: proto/Hello.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Request struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *Request) Reset() { + *x = Request{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_Hello_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Request) ProtoMessage() {} + +func (x *Request) ProtoReflect() protoreflect.Message { + mi := &file_proto_Hello_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Request.ProtoReflect.Descriptor instead. +func (*Request) Descriptor() ([]byte, []int) { + return file_proto_Hello_proto_rawDescGZIP(), []int{0} +} + +func (x *Request) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Response struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Greeting string `protobuf:"bytes,2,opt,name=greeting,proto3" json:"greeting,omitempty"` +} + +func (x *Response) Reset() { + *x = Response{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_Hello_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Response) ProtoMessage() {} + +func (x *Response) ProtoReflect() protoreflect.Message { + mi := &file_proto_Hello_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Response.ProtoReflect.Descriptor instead. +func (*Response) Descriptor() ([]byte, []int) { + return file_proto_Hello_proto_rawDescGZIP(), []int{1} +} + +func (x *Response) GetGreeting() string { + if x != nil { + return x.Greeting + } + return "" +} + +var File_proto_Hello_proto protoreflect.FileDescriptor + +var file_proto_Hello_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x22, 0x1d, 0x0a, 0x07, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x08, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x72, 0x65, 0x65, 0x74, + 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x72, 0x65, 0x65, 0x74, + 0x69, 0x6e, 0x67, 0x32, 0x39, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x2e, + 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x10, 0x2e, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, + 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x65, 0x65, + 0x74, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, + 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_Hello_proto_rawDescOnce sync.Once + file_proto_Hello_proto_rawDescData = file_proto_Hello_proto_rawDesc +) + +func file_proto_Hello_proto_rawDescGZIP() []byte { + file_proto_Hello_proto_rawDescOnce.Do(func() { + file_proto_Hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_Hello_proto_rawDescData) + }) + return file_proto_Hello_proto_rawDescData +} + +var file_proto_Hello_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_Hello_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: greeter.Request + (*Response)(nil), // 1: greeter.Response +} +var file_proto_Hello_proto_depIdxs = []int32{ + 0, // 0: greeter.Greeter.Hello:input_type -> greeter.Request + 1, // 1: greeter.Greeter.Hello:output_type -> greeter.Response + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_proto_Hello_proto_init() } +func file_proto_Hello_proto_init() { + if File_proto_Hello_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_Hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Request); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_Hello_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Response); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_Hello_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_Hello_proto_goTypes, + DependencyIndexes: file_proto_Hello_proto_depIdxs, + MessageInfos: file_proto_Hello_proto_msgTypes, + }.Build() + File_proto_Hello_proto = out.File + file_proto_Hello_proto_rawDesc = nil + file_proto_Hello_proto_goTypes = nil + file_proto_Hello_proto_depIdxs = nil +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.pb.micro.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.pb.micro.go new file mode 100644 index 00000000000..51e6ab03b93 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.pb.micro.go @@ -0,0 +1,87 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: proto/Hello.proto + +package proto + +import ( + fmt "fmt" + proto "google.golang.org/protobuf/proto" + math "math" +) + +import ( + context "context" + api "go-micro.dev/v4/api" + client "go-micro.dev/v4/client" + server "go-micro.dev/v4/server" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// Reference imports to suppress errors if they are not otherwise used. +var _ api.Endpoint +var _ context.Context +var _ client.Option +var _ server.Option + +// Api Endpoints for Greeter service + +func NewGreeterEndpoints() []*api.Endpoint { + return []*api.Endpoint{} +} + +// Client API for Greeter service + +type GreeterService interface { + Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) +} + +type greeterService struct { + c client.Client + name string +} + +func NewGreeterService(name string, c client.Client) GreeterService { + return &greeterService{ + c: c, + name: name, + } +} + +func (c *greeterService) Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) { + req := c.c.NewRequest(c.name, "Greeter.Hello", in) + out := new(Response) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Greeter service + +type GreeterHandler interface { + Hello(context.Context, *Request, *Response) error +} + +func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error { + type greeter interface { + Hello(ctx context.Context, in *Request, out *Response) error + } + type Greeter struct { + greeter + } + h := &greeterHandler{hdlr} + return s.Handle(s.NewHandler(&Greeter{h}, opts...)) +} + +type greeterHandler struct { + GreeterHandler +} + +func (h *greeterHandler) Hello(ctx context.Context, in *Request, out *Response) error { + return h.GreeterHandler.Hello(ctx, in, out) +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.proto b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.proto new file mode 100644 index 00000000000..d2015853398 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/proto/Hello.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package greeter; +option go_package = "/proto"; + +service Greeter { + rpc Hello(Request) returns (Response) {} +} + +message Request { + string name = 1; +} + +message Response { + string greeting = 2; +} \ No newline at end of file diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/api/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/api/stub.go new file mode 100644 index 00000000000..965bcabc1c5 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/api/stub.go @@ -0,0 +1,18 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for go-micro.dev/v4/api, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: go-micro.dev/v4/api (exports: Endpoint; functions: ) + +// Package api is a stub of go-micro.dev/v4/api, generated by depstubber. +package api + +type Endpoint struct { + Name string + Description string + Handler string + Host []string + Method []string + Path []string + Stream bool +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/client/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/client/stub.go new file mode 100644 index 00000000000..a6b4e405a7d --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/client/stub.go @@ -0,0 +1,146 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for go-micro.dev/v4/client, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: go-micro.dev/v4/client (exports: Client; functions: Call) + +// Package client is a stub of go-micro.dev/v4/client, generated by depstubber. +package client + +import ( + context "context" + time "time" +) + +type BackoffFunc func(context.Context, Request, int) (time.Duration, error) + +type Cache struct{} + +func (_ *Cache) Get(_ context.Context, _ *Request) (interface{}, bool) { + return nil, false +} + +func (_ *Cache) List() map[string]string { + return nil +} + +func (_ *Cache) Set(_ context.Context, _ *Request, _ interface{}, _ time.Duration) {} + +func Call(_ context.Context, _ Request, _ interface{}, _ ...CallOption) error { + return nil +} + +type CallFunc func(context.Context, interface{}, Request, interface{}, CallOptions) error + +type CallOption func(*CallOptions) + +type CallOptions struct { + Context context.Context + Backoff BackoffFunc + Retry RetryFunc + SelectOptions []interface{} + Address []string + CallWrappers []CallWrapper + ConnectionTimeout time.Duration + RequestTimeout time.Duration + StreamTimeout time.Duration + CacheExpiry time.Duration + DialTimeout time.Duration + Retries int + ServiceToken bool + ConnClose bool +} + +type CallWrapper func(CallFunc) CallFunc + +type Client interface { + Call(_ context.Context, _ Request, _ interface{}, _ ...CallOption) error + Init(_ ...Option) error + NewMessage(_ string, _ interface{}, _ ...MessageOption) Message + NewRequest(_ string, _ string, _ interface{}, _ ...RequestOption) Request + Options() Options + Publish(_ context.Context, _ Message, _ ...PublishOption) error + Stream(_ context.Context, _ Request, _ ...CallOption) (Stream, error) + String() string +} + +type Message interface { + ContentType() string + Payload() interface{} + Topic() string +} + +type MessageOption func(*MessageOptions) + +type MessageOptions struct { + ContentType string +} + +type Option func(*Options) + +type Options struct { + CallOptions CallOptions + Router Router + Registry interface{} + Selector interface{} + Transport interface{} + Broker interface{} + Logger interface{} + Context context.Context + Codecs map[string]interface{} + Cache *Cache + ContentType string + Wrappers []Wrapper + PoolSize int + PoolTTL time.Duration +} + +type PublishOption func(*PublishOptions) + +type PublishOptions struct { + Context context.Context + Exchange string +} + +type Request interface { + Body() interface{} + Codec() interface{} + ContentType() string + Endpoint() string + Method() string + Service() string + Stream() bool +} + +type RequestOption func(*RequestOptions) + +type RequestOptions struct { + Context context.Context + ContentType string + Stream bool +} + +type Response interface { + Codec() interface{} + Header() map[string]string + Read() ([]byte, error) +} + +type RetryFunc func(context.Context, Request, int, error) (bool, error) + +type Router interface { + SendRequest(_ context.Context, _ Request) (Response, error) +} + +type Stream interface { + Close() error + CloseSend() error + Context() context.Context + Error() error + Recv(_ interface{}) error + Request() Request + Response() Response + Send(_ interface{}) error +} + +type Wrapper func(Client) Client diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/server/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/server/stub.go new file mode 100644 index 00000000000..8ca59456e75 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/server/stub.go @@ -0,0 +1,126 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for go-micro.dev/v4/server, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: go-micro.dev/v4/server (exports: Server; functions: Handle) + +// Package server is a stub of go-micro.dev/v4/server, generated by depstubber. +package server + +import ( + context "context" + tls "crypto/tls" + time "time" +) + +func Handle(_ Handler) error { + return nil +} + +type Handler interface { + Endpoints() []interface{} + Handler() interface{} + Name() string + Options() HandlerOptions +} + +type HandlerFunc func(context.Context, Request, interface{}) error + +type HandlerOption func(*HandlerOptions) + +type HandlerOptions struct { + Metadata map[string]map[string]string + Internal bool +} + +type HandlerWrapper func(HandlerFunc) HandlerFunc + +type Message interface { + Body() []byte + Codec() interface{} + ContentType() string + Header() map[string]string + Payload() interface{} + Topic() string +} + +type Option func(*Options) + +type Options struct { + Logger interface{} + Broker interface{} + Registry interface{} + Tracer interface{} + Transport interface{} + Context context.Context + Router Router + RegisterCheck func(context.Context) error + Metadata map[string]string + TLSConfig *tls.Config + Codecs map[string]interface{} + Name string + Id string + Version string + Advertise string + Address string + HdlrWrappers []HandlerWrapper + ListenOptions []interface{} + SubWrappers []SubscriberWrapper + RegisterInterval time.Duration + RegisterTTL time.Duration +} + +type Request interface { + Body() interface{} + Codec() interface{} + ContentType() string + Endpoint() string + Header() map[string]string + Method() string + Read() ([]byte, error) + Service() string + Stream() bool +} + +type Response interface { + Codec() interface{} + Write(_ []byte) error + WriteHeader(_ map[string]string) +} + +type Router interface { + ProcessMessage(_ context.Context, _ Message) error + ServeRequest(_ context.Context, _ Request, _ Response) error +} + +type Server interface { + Handle(_ Handler) error + Init(_ ...Option) error + NewHandler(_ interface{}, _ ...HandlerOption) Handler + NewSubscriber(_ string, _ interface{}, _ ...SubscriberOption) Subscriber + Options() Options + Start() error + Stop() error + String() string + Subscribe(_ Subscriber) error +} + +type Subscriber interface { + Endpoints() []interface{} + Options() SubscriberOptions + Subscriber() interface{} + Topic() string +} + +type SubscriberFunc func(context.Context, Message) error + +type SubscriberOption func(*SubscriberOptions) + +type SubscriberOptions struct { + Context context.Context + Queue string + AutoAck bool + Internal bool +} + +type SubscriberWrapper func(SubscriberFunc) SubscriberFunc diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/stub.go new file mode 100644 index 00000000000..73fb230153c --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/go-micro.dev/v4/stub.go @@ -0,0 +1,69 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for go-micro.dev/v4, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: go-micro.dev/v4 (exports: Service,Option,Options; functions: NewService,Name,Handle,Server,Client) + +// Package go_pkg is a stub of go-micro.dev/v4, generated by depstubber. +package go_pkg + +import ( + context "context" + + "go-micro.dev/v4/client" + "go-micro.dev/v4/server" +) + +func Client(_ interface{}) Option { + return nil +} + +func Handle(_ interface{}) Option { + return nil +} + +func Name(_ string) Option { + return nil +} + +func NewService(_ ...Option) Service { + return nil +} + +type Option func(*Options) + +type Options struct { + Registry interface{} + Store interface{} + Auth interface{} + Cmd interface{} + Config interface{} + Client interface{} + Server interface{} + Context context.Context + Cache interface{} + Runtime interface{} + Profile interface{} + Transport interface{} + Logger interface{} + Broker interface{} + BeforeStart []func() error + AfterStart []func() error + AfterStop []func() error + BeforeStop []func() error + Signal bool +} + +func Server(_ interface{}) Option { + return nil +} + +type Service interface { + Client() client.Client + Init(_ ...Option) + Name() string + Options() Options + Run() error + Server() server.Server + String() string +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/LICENSE b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/LICENSE new file mode 100644 index 00000000000..49ea0f92882 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2018 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/internal/impl/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/internal/impl/stub.go new file mode 100644 index 00000000000..6d87b1ed810 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/internal/impl/stub.go @@ -0,0 +1,132 @@ +// This is a simple stub for google.golang.org/protobuf/internal/impl, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: google.golang.org/protobuf/internal/impl (exports: MessageState,Pointer; functions: ) + +// Package impl is a stub of google.golang.org/protobuf/internal/impl. +package impl + +import ( + "google.golang.org/protobuf/reflect/protoreflect" +) + +type MessageState struct { + NoUnkeyedLiterals interface{} + DoNotCompare interface{} + DoNotCopy interface{} +} + +type Pointer interface{} + +type MessageInfo struct { + Exporter interface{} +} + +func (*MessageInfo) MessageOf(_ interface{}) protoreflect.Message { return nil } + +type EnumInfo struct{} + +func (_ *EnumInfo) Descriptor() protoreflect.EnumDescriptor { return nil } +func (_ *EnumInfo) New(_ protoreflect.EnumNumber) protoreflect.Enum { return nil } + +type DescBuilder struct { + GoPackagePath string + RawDescriptor []byte + NumEnums int + NumMessages int + NumExtensions int + NumServices int +} + +type TypeBuilder struct { + File DescBuilder + GoTypes []interface{} + DependencyIndexes []int32 + EnumInfos []EnumInfo + MessageInfos []MessageInfo +} + +type BuilderOut struct { + File protoreflect.FileDescriptor +} + +func (tb TypeBuilder) Build() BuilderOut { + return BuilderOut{nil} +} + +func (ms *MessageState) LoadMessageInfo() *MessageInfo { return nil } +func (ms *MessageState) StoreMessageInfo(mi *MessageInfo) {} + +func (ms *MessageState) Clear(_ protoreflect.FieldDescriptor) {} +func (ms *MessageState) Descriptor() protoreflect.MessageDescriptor { return nil } +func (ms *MessageState) Get(_ protoreflect.FieldDescriptor) protoreflect.Value { + return protoreflect.Value{} +} +func (ms *MessageState) GetUnknown() protoreflect.RawFields { return nil } +func (ms *MessageState) Has(_ protoreflect.FieldDescriptor) bool { return false } +func (ms *MessageState) Interface() protoreflect.ProtoMessage { return nil } +func (ms *MessageState) IsValid() bool { return false } +func (ms *MessageState) Mutable(_ protoreflect.FieldDescriptor) protoreflect.Value { + return protoreflect.Value{} +} +func (ms *MessageState) New() protoreflect.Message { return nil } +func (ms *MessageState) NewField(_ protoreflect.FieldDescriptor) protoreflect.Value { + return protoreflect.Value{} +} +func (ms *MessageState) ProtoMethods() *struct { + NoUnkeyedLiterals interface{} + Flags uint64 + Size func(struct { + NoUnkeyedLiterals interface{} + Message protoreflect.Message + Flags byte + }) struct { + NoUnkeyedLiterals interface{} + Size int + } + Marshal func(struct { + NoUnkeyedLiterals interface{} + Message protoreflect.Message + Buf []byte + Flags byte + }) (struct { + NoUnkeyedLiterals interface{} + Buf []byte + }, error) + Unmarshal func(struct { + NoUnkeyedLiterals interface{} + Message protoreflect.Message + Buf []byte + Flags byte + Resolver interface { + FindExtensionByName(_ protoreflect.FullName) (protoreflect.ExtensionType, error) + FindExtensionByNumber(_ protoreflect.FullName, _ interface{}) (protoreflect.ExtensionType, error) + } + }) (struct { + NoUnkeyedLiterals interface{} + Flags byte + }, error) + Merge func(struct { + NoUnkeyedLiterals interface{} + Source protoreflect.Message + Destination protoreflect.Message + }) struct { + NoUnkeyedLiterals interface{} + Flags byte + } + CheckInitialized func(struct { + NoUnkeyedLiterals interface{} + Message protoreflect.Message + }) (struct { + NoUnkeyedLiterals interface{} + }, error) +} { + return nil +} +func (ms *MessageState) Range(_ func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {} +func (ms *MessageState) Set(_ protoreflect.FieldDescriptor, _ protoreflect.Value) {} +func (ms *MessageState) SetUnknown(_ protoreflect.RawFields) {} +func (ms *MessageState) Type() protoreflect.MessageType { return nil } +func (ms *MessageState) WhichOneof(_ protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + return nil +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/proto/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/proto/stub.go new file mode 100644 index 00000000000..979a31e7fbd --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/proto/stub.go @@ -0,0 +1,68 @@ +// This is a simple stub for github.com/golang/protobuf/proto, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/golang/protobuf/proto (exports: Message; functions: Marshal,Unmarshal,ProtoPackageIsVersion4) + +// Package proto is a stub of github.com/golang/protobuf/proto. +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoiface "google.golang.org/protobuf/runtime/protoiface" +) + +func Marshal(_ interface{}) ([]byte, error) { + return nil, nil +} + +type Message = protoreflect.ProtoMessage + +var ProtoPackageIsVersion4 bool = false + +func Unmarshal(_ []byte, _ interface{}) error { + return nil +} + +type MarshalOptions struct { + AllowPartial bool + Deterministic bool + UseCachedSize bool +} + +func (_ MarshalOptions) Marshal(_ Message) ([]byte, error) { return nil, nil } +func (_ MarshalOptions) MarshalAppend(b []byte, m Message) ([]byte, error) { return nil, nil } +func (_ MarshalOptions) MarshalState(in protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + return protoiface.MarshalOutput{nil}, nil +} + +type UnmarshalOptions struct { + // Merge merges the input into the destination message. + // The default behavior is to always reset the message before unmarshaling, + // unless Merge is specified. + Merge bool + + // AllowPartial accepts input for messages that will result in missing + // required fields. If AllowPartial is false (the default), Unmarshal will + // return an error if there are any missing required fields. + AllowPartial bool + + // If DiscardUnknown is set, unknown fields are ignored. + DiscardUnknown bool + + // Resolver is used for looking up types when unmarshaling extension fields. + // If nil, this defaults to using protoregistry.GlobalTypes. + Resolver interface { + FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) + FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) + } +} + +func (o UnmarshalOptions) Unmarshal(b []byte, m Message) error { + return nil +} + +func Clone(_ Message) Message { + return nil +} + +func Merge(_, _ Message) {} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/reflect/protoreflect/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/reflect/protoreflect/stub.go new file mode 100644 index 00000000000..0f2fd6e1094 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/reflect/protoreflect/stub.go @@ -0,0 +1,683 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for google.golang.org/protobuf/reflect/protoreflect, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: google.golang.org/protobuf/reflect/protoreflect (exports: EnumDescriptor,EnumType,EnumNumber,Message,FileDescriptor; functions: ) + +// Package protoreflect is a stub of google.golang.org/protobuf/reflect/protoreflect, generated by depstubber. +package protoreflect + +import () + +type Cardinality int8 + +func (_ Cardinality) GoString() string { + return "" +} + +func (_ Cardinality) IsValid() bool { + return false +} + +func (_ Cardinality) String() string { + return "" +} + +type Descriptor interface { + FullName() FullName + Index() int + IsPlaceholder() bool + Name() Name + Options() ProtoMessage + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + Syntax() Syntax +} + +type Enum interface { + Descriptor() EnumDescriptor + Number() EnumNumber + Type() EnumType +} + +type EnumDescriptor interface { + FullName() FullName + Index() int + IsPlaceholder() bool + Name() Name + Options() ProtoMessage + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + ProtoType(_ EnumDescriptor) + ReservedNames() Names + ReservedRanges() EnumRanges + Syntax() Syntax + Values() EnumValueDescriptors +} + +type EnumDescriptors interface { + ByName(_ Name) EnumDescriptor + Get(_ int) EnumDescriptor + Len() int + ProtoInternal(_ interface{}) +} + +type EnumNumber int32 + +type EnumRanges interface { + Get(_ int) [2]EnumNumber + Has(_ EnumNumber) bool + Len() int + ProtoInternal(_ interface{}) +} + +type EnumType interface { + Descriptor() EnumDescriptor + New(_ EnumNumber) Enum +} + +type EnumValueDescriptor interface { + FullName() FullName + Index() int + IsPlaceholder() bool + Name() Name + Number() EnumNumber + Options() ProtoMessage + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + ProtoType(_ EnumValueDescriptor) + Syntax() Syntax +} + +type EnumValueDescriptors interface { + ByName(_ Name) EnumValueDescriptor + ByNumber(_ EnumNumber) EnumValueDescriptor + Get(_ int) EnumValueDescriptor + Len() int + ProtoInternal(_ interface{}) +} + +type ExtensionDescriptors interface { + ByName(_ Name) FieldDescriptor + Get(_ int) FieldDescriptor + Len() int + ProtoInternal(_ interface{}) +} + +type ExtensionType interface { + InterfaceOf(_ Value) interface{} + IsValidInterface(_ interface{}) bool + IsValidValue(_ Value) bool + New() Value + TypeDescriptor() ExtensionTypeDescriptor + ValueOf(_ interface{}) Value + Zero() Value +} + +type ExtensionTypeDescriptor interface { + Cardinality() Cardinality + ContainingMessage() MessageDescriptor + ContainingOneof() OneofDescriptor + Default() Value + DefaultEnumValue() EnumValueDescriptor + Descriptor() FieldDescriptor + Enum() EnumDescriptor + FullName() FullName + HasDefault() bool + HasJSONName() bool + HasOptionalKeyword() bool + HasPresence() bool + Index() int + IsExtension() bool + IsList() bool + IsMap() bool + IsPacked() bool + IsPlaceholder() bool + IsWeak() bool + JSONName() string + Kind() Kind + MapKey() FieldDescriptor + MapValue() FieldDescriptor + Message() MessageDescriptor + Name() Name + Number() interface{} + Options() ProtoMessage + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + ProtoType(_ FieldDescriptor) + Syntax() Syntax + Type() ExtensionType +} + +type FieldDescriptor interface { + Cardinality() Cardinality + ContainingMessage() MessageDescriptor + ContainingOneof() OneofDescriptor + Default() Value + DefaultEnumValue() EnumValueDescriptor + Enum() EnumDescriptor + FullName() FullName + HasDefault() bool + HasJSONName() bool + HasOptionalKeyword() bool + HasPresence() bool + Index() int + IsExtension() bool + IsList() bool + IsMap() bool + IsPacked() bool + IsPlaceholder() bool + IsWeak() bool + JSONName() string + Kind() Kind + MapKey() FieldDescriptor + MapValue() FieldDescriptor + Message() MessageDescriptor + Name() Name + Number() interface{} + Options() ProtoMessage + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + ProtoType(_ FieldDescriptor) + Syntax() Syntax +} + +type FieldDescriptors interface { + ByJSONName(_ string) FieldDescriptor + ByName(_ Name) FieldDescriptor + ByNumber(_ interface{}) FieldDescriptor + Get(_ int) FieldDescriptor + Len() int + ProtoInternal(_ interface{}) +} + +type FieldNumber int32 + +type FieldNumbers interface { + Get(_ int) interface{} + Has(_ interface{}) bool + Len() int + ProtoInternal(_ interface{}) +} + +type FieldRanges interface { + Get(_ int) [2]interface{} + Has(_ interface{}) bool + Len() int + ProtoInternal(_ interface{}) +} + +type FileDescriptor interface { + Enums() EnumDescriptors + Extensions() ExtensionDescriptors + FullName() FullName + Imports() FileImports + Index() int + IsPlaceholder() bool + Messages() MessageDescriptors + Name() Name + Options() ProtoMessage + Package() FullName + Parent() Descriptor + ParentFile() FileDescriptor + Path() string + ProtoInternal(_ interface{}) + ProtoType(_ FileDescriptor) + Services() ServiceDescriptors + SourceLocations() SourceLocations + Syntax() Syntax +} + +type FileImport struct { + FileDescriptor FileDescriptor + IsPublic bool + IsWeak bool +} + +func (_ FileImport) Enums() EnumDescriptors { + return nil +} + +func (_ FileImport) Extensions() ExtensionDescriptors { + return nil +} + +func (_ FileImport) FullName() FullName { + return "" +} + +func (_ FileImport) Imports() FileImports { + return nil +} + +func (_ FileImport) Index() int { + return 0 +} + +func (_ FileImport) IsPlaceholder() bool { + return false +} + +func (_ FileImport) Messages() MessageDescriptors { + return nil +} + +func (_ FileImport) Name() Name { + return "" +} + +func (_ FileImport) Options() ProtoMessage { + return nil +} + +func (_ FileImport) Package() FullName { + return "" +} + +func (_ FileImport) Parent() Descriptor { + return nil +} + +func (_ FileImport) ParentFile() FileDescriptor { + return nil +} + +func (_ FileImport) Path() string { + return "" +} + +func (_ FileImport) ProtoInternal(_ interface{}) {} + +func (_ FileImport) ProtoType(_ FileDescriptor) {} + +func (_ FileImport) Services() ServiceDescriptors { + return nil +} + +func (_ FileImport) SourceLocations() SourceLocations { + return nil +} + +func (_ FileImport) Syntax() Syntax { + return 0 +} + +type FileImports interface { + Get(_ int) FileImport + Len() int + ProtoInternal(_ interface{}) +} + +type FullName string + +func (_ FullName) Append(_ Name) FullName { + return "" +} + +func (_ FullName) IsValid() bool { + return false +} + +func (_ FullName) Name() Name { + return "" +} + +func (_ FullName) Parent() FullName { + return "" +} + +type Kind int8 + +func (_ Kind) GoString() string { + return "" +} + +func (_ Kind) IsValid() bool { + return false +} + +func (_ Kind) String() string { + return "" +} + +type List interface { + Append(_ Value) + AppendMutable() Value + Get(_ int) Value + IsValid() bool + Len() int + NewElement() Value + Set(_ int, _ Value) + Truncate(_ int) +} + +type Map interface { + Clear(_ MapKey) + Get(_ MapKey) Value + Has(_ MapKey) bool + IsValid() bool + Len() int + Mutable(_ MapKey) Value + NewValue() Value + Range(_ func(MapKey, Value) bool) + Set(_ MapKey, _ Value) +} + +type MapKey struct { + DoNotCompare interface{} +} + +func (_ MapKey) Bool() bool { + return false +} + +func (_ MapKey) Int() int64 { + return 0 +} + +func (_ MapKey) Interface() interface{} { + return nil +} + +func (_ MapKey) IsValid() bool { + return false +} + +func (_ MapKey) String() string { + return "" +} + +func (_ MapKey) Uint() uint64 { + return 0 +} + +func (_ MapKey) Value() Value { + return Value{} +} + +type Message interface { + Clear(_ FieldDescriptor) + Descriptor() MessageDescriptor + Get(_ FieldDescriptor) Value + GetUnknown() RawFields + Has(_ FieldDescriptor) bool + Interface() ProtoMessage + IsValid() bool + Mutable(_ FieldDescriptor) Value + New() Message + NewField(_ FieldDescriptor) Value + ProtoMethods() *struct { + NoUnkeyedLiterals interface{} + Flags uint64 + Size func(struct { + NoUnkeyedLiterals interface{} + Message Message + Flags byte + }) struct { + NoUnkeyedLiterals interface{} + Size int + } + Marshal func(struct { + NoUnkeyedLiterals interface{} + Message Message + Buf []byte + Flags byte + }) (struct { + NoUnkeyedLiterals interface{} + Buf []byte + }, error) + Unmarshal func(struct { + NoUnkeyedLiterals interface{} + Message Message + Buf []byte + Flags byte + Resolver interface { + FindExtensionByName(_ FullName) (ExtensionType, error) + FindExtensionByNumber(_ FullName, _ interface{}) (ExtensionType, error) + } + }) (struct { + NoUnkeyedLiterals interface{} + Flags byte + }, error) + Merge func(struct { + NoUnkeyedLiterals interface{} + Source Message + Destination Message + }) struct { + NoUnkeyedLiterals interface{} + Flags byte + } + CheckInitialized func(struct { + NoUnkeyedLiterals interface{} + Message Message + }) (struct { + NoUnkeyedLiterals interface{} + }, error) + } + Range(_ func(FieldDescriptor, Value) bool) + Set(_ FieldDescriptor, _ Value) + SetUnknown(_ RawFields) + Type() MessageType + WhichOneof(_ OneofDescriptor) FieldDescriptor +} + +type MessageDescriptor interface { + Enums() EnumDescriptors + ExtensionRangeOptions(_ int) ProtoMessage + ExtensionRanges() FieldRanges + Extensions() ExtensionDescriptors + Fields() FieldDescriptors + FullName() FullName + Index() int + IsMapEntry() bool + IsPlaceholder() bool + Messages() MessageDescriptors + Name() Name + Oneofs() OneofDescriptors + Options() ProtoMessage + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + ProtoType(_ MessageDescriptor) + RequiredNumbers() FieldNumbers + ReservedNames() Names + ReservedRanges() FieldRanges + Syntax() Syntax +} + +type MessageDescriptors interface { + ByName(_ Name) MessageDescriptor + Get(_ int) MessageDescriptor + Len() int + ProtoInternal(_ interface{}) +} + +type MessageType interface { + Descriptor() MessageDescriptor + New() Message + Zero() Message +} + +type MethodDescriptor interface { + FullName() FullName + Index() int + Input() MessageDescriptor + IsPlaceholder() bool + IsStreamingClient() bool + IsStreamingServer() bool + Name() Name + Options() ProtoMessage + Output() MessageDescriptor + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + ProtoType(_ MethodDescriptor) + Syntax() Syntax +} + +type MethodDescriptors interface { + ByName(_ Name) MethodDescriptor + Get(_ int) MethodDescriptor + Len() int + ProtoInternal(_ interface{}) +} + +type Name string + +func (_ Name) IsValid() bool { + return false +} + +type Names interface { + Get(_ int) Name + Has(_ Name) bool + Len() int + ProtoInternal(_ interface{}) +} + +type OneofDescriptor interface { + Fields() FieldDescriptors + FullName() FullName + Index() int + IsPlaceholder() bool + IsSynthetic() bool + Name() Name + Options() ProtoMessage + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + ProtoType(_ OneofDescriptor) + Syntax() Syntax +} + +type OneofDescriptors interface { + ByName(_ Name) OneofDescriptor + Get(_ int) OneofDescriptor + Len() int + ProtoInternal(_ interface{}) +} + +type ProtoMessage interface { + ProtoReflect() Message +} + +type RawFields []byte + +func (_ RawFields) IsValid() bool { + return false +} + +type ServiceDescriptor interface { + FullName() FullName + Index() int + IsPlaceholder() bool + Methods() MethodDescriptors + Name() Name + Options() ProtoMessage + Parent() Descriptor + ParentFile() FileDescriptor + ProtoInternal(_ interface{}) + ProtoType(_ ServiceDescriptor) + Syntax() Syntax +} + +type ServiceDescriptors interface { + ByName(_ Name) ServiceDescriptor + Get(_ int) ServiceDescriptor + Len() int + ProtoInternal(_ interface{}) +} + +type SourceLocation struct { + Path SourcePath + StartLine int + StartColumn int + EndLine int + EndColumn int + LeadingDetachedComments []string + LeadingComments string + TrailingComments string +} + +type SourceLocations interface { + Get(_ int) SourceLocation + Len() int + ProtoInternal(_ interface{}) +} + +type SourcePath []int32 + +type Syntax int8 + +func (_ Syntax) GoString() string { + return "" +} + +func (_ Syntax) IsValid() bool { + return false +} + +func (_ Syntax) String() string { + return "" +} + +type Value struct { + DoNotCompare interface{} +} + +func (_ Value) Bool() bool { + return false +} + +func (_ Value) Bytes() []byte { + return nil +} + +func (_ Value) Enum() EnumNumber { + return 0 +} + +func (_ Value) Float() float64 { + return 0 +} + +func (_ Value) Int() int64 { + return 0 +} + +func (_ Value) Interface() interface{} { + return nil +} + +func (_ Value) IsValid() bool { + return false +} + +func (_ Value) List() List { + return nil +} + +func (_ Value) Map() Map { + return nil +} + +func (_ Value) MapKey() MapKey { + return MapKey{} +} + +func (_ Value) Message() Message { + return nil +} + +func (_ Value) String() string { + return "" +} + +func (_ Value) Uint() uint64 { + return 0 +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/runtime/protoiface/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/runtime/protoiface/stub.go new file mode 100644 index 00000000000..85e7d573e55 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/runtime/protoiface/stub.go @@ -0,0 +1,29 @@ +// This is a simple stub for google.golang.org/protobuf/runtime/protoiface, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: google.golang.org/protobuf/runtime/protoiface (exports: MessageV1; functions: ) + +// Package protoiface is a stub of google.golang.org/protobuf/runtime/protoiface. +package protoiface + +import ( + "google.golang.org/protobuf/reflect/protoreflect" +) + +type MessageV1 interface { + ProtoMessage() + Reset() + String() string +} + +type MarshalInputFlags = uint8 + +type MarshalInput struct { + Message protoreflect.Message + Buf []byte // output is appended to this buffer + Flags MarshalInputFlags +} + +type MarshalOutput struct { + Buf []byte // contains marshaled message +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/runtime/protoimpl/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/runtime/protoimpl/stub.go new file mode 100644 index 00000000000..c6fb6b140d5 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/google.golang.org/protobuf/runtime/protoimpl/stub.go @@ -0,0 +1,107 @@ +// This is a simple stub for google.golang.org/protobuf/runtime/protoimpl, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: google.golang.org/protobuf/runtime/protoimpl (exports: MessageState,SizeCache,UnknownFields,Pointer,EnforceVersion; functions: MinVersion,MaxVersion,UnsafeEnabled,X) + +// Package protoimpl is a stub of google.golang.org/protobuf/runtime/protoimpl. +package protoimpl + +import ( + impl "google.golang.org/protobuf/internal/impl" +) + +type EnforceVersion uint + +const MaxVersion int = 20 + +type MessageState = impl.MessageState + +const MinVersion int = 20 + +type Pointer = impl.Pointer + +type SizeCache = int32 + +type UnknownFields = []byte + +var UnsafeEnabled bool = false + +// Export is a zero-length named type that exists only to export a set of +// functions that we do not want to appear in godoc. +type Export struct{} + +var X Export = Export{} + +func (Export) NewError(f string, x ...interface{}) error { + return nil +} + +type enum = interface{} + +func (Export) EnumOf(e enum) interface{} { + return nil +} + +func (Export) EnumDescriptorOf(e enum) interface{} { + return nil +} + +func (Export) EnumTypeOf(e enum) interface{} { + return nil +} + +func (Export) EnumStringOf(ed interface{}, n interface{}) string { + return "" +} + +type message = interface{} + +type legacyMessageWrapper struct{ m interface{} } + +func (m legacyMessageWrapper) Reset() {} +func (m legacyMessageWrapper) String() string { return "" } +func (m legacyMessageWrapper) ProtoMessage() {} + +func (Export) ProtoMessageV1Of(m message) interface{} { + return nil +} + +func (Export) protoMessageV2Of(m message) interface{} { + return nil +} + +func (Export) ProtoMessageV2Of(m message) interface{} { + return nil +} + +func (Export) MessageOf(m message) interface{} { + return nil +} + +func (Export) MessageDescriptorOf(m message) interface{} { + return nil +} + +func (Export) MessageTypeOf(m message) interface{} { + return nil +} + +func (Export) MessageStringOf(m interface{}) string { + return "" +} + +func (Export) MessageStateOf(p Pointer) *MessageState { + return nil +} + +func (Export) CompressGZIP(_ []byte) []byte { + return nil +} + +type EnumInfo = impl.EnumInfo + +type MessageInfo = impl.MessageInfo + +type TypeBuilder = impl.TypeBuilder + +type DescBuilder = impl.DescBuilder diff --git a/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/modules.txt b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/modules.txt new file mode 100644 index 00000000000..1884e9622af --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/GoMicro/vendor/modules.txt @@ -0,0 +1,6 @@ +# go-micro.dev/v4 v4.10.2 +## explicit +go-micro.dev/v4 +# google.golang.org/protobuf v1.28.1 +## explicit +google.golang.org/protobuf diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.expected b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.expected new file mode 100644 index 00000000000..f9d12d37a28 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.expected @@ -0,0 +1,21 @@ +| bun.go:25:22:25:30 | untrusted | github.com/uptrace/bun | NewRawQuery | +| bun.go:27:22:27:30 | untrusted | github.com/uptrace/bun.DB | ExecContext | +| bun.go:28:25:28:33 | untrusted | github.com/uptrace/bun.DB | PrepareContext | +| bun.go:29:23:29:31 | untrusted | github.com/uptrace/bun.DB | QueryContext | +| bun.go:30:26:30:34 | untrusted | github.com/uptrace/bun.DB | QueryRowContext | +| bun.go:32:10:32:18 | untrusted | github.com/uptrace/bun.DB | Exec | +| bun.go:33:12:33:20 | untrusted | github.com/uptrace/bun.DB | NewRaw | +| bun.go:34:13:34:21 | untrusted | github.com/uptrace/bun.DB | Prepare | +| bun.go:35:11:35:19 | untrusted | github.com/uptrace/bun.DB | Query | +| bun.go:36:14:36:22 | untrusted | github.com/uptrace/bun.DB | QueryRow | +| bun.go:37:9:37:17 | untrusted | github.com/uptrace/bun.DB | Raw | +| bun.go:39:28:39:36 | untrusted | github.com/uptrace/bun.SelectQuery | ColumnExpr | +| bun.go:40:28:40:36 | untrusted | github.com/uptrace/bun.SelectQuery | DistinctOn | +| bun.go:41:21:41:29 | untrusted | github.com/uptrace/bun.SelectQuery | For | +| bun.go:42:27:42:35 | untrusted | github.com/uptrace/bun.SelectQuery | GroupExpr | +| bun.go:43:24:43:32 | untrusted | github.com/uptrace/bun.SelectQuery | Having | +| bun.go:44:32:44:40 | untrusted | github.com/uptrace/bun.SelectQuery | ModelTableExpr | +| bun.go:45:27:45:35 | untrusted | github.com/uptrace/bun.SelectQuery | OrderExpr | +| bun.go:46:27:46:35 | untrusted | github.com/uptrace/bun.SelectQuery | TableExpr | +| bun.go:47:23:47:31 | untrusted | github.com/uptrace/bun.SelectQuery | Where | +| bun.go:48:25:48:33 | untrusted | github.com/uptrace/bun.SelectQuery | WhereOr | diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.go b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.go new file mode 100644 index 00000000000..8ce4e5b0826 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + "database/sql" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/sqlitedialect" + "github.com/uptrace/bun/driver/sqliteshim" +) + +func getUntrustedString() string { + return "trouble" +} + +func main() { + untrusted := getUntrustedString() + + ctx := context.Background() + sqlite, err := sql.Open(sqliteshim.ShimName, "file::memory:?cache=shared") + if err != nil { + panic(err) + } + db := bun.NewDB(sqlite, sqlitedialect.New()) + bun.NewRawQuery(db, untrusted) + + db.ExecContext(ctx, untrusted) + db.PrepareContext(ctx, untrusted) + db.QueryContext(ctx, untrusted) + db.QueryRowContext(ctx, untrusted) + + db.Exec(untrusted) + db.NewRaw(untrusted) + db.Prepare(untrusted) + db.Query(untrusted) + db.QueryRow(untrusted) + db.Raw(untrusted) + + db.NewSelect().ColumnExpr(untrusted) + db.NewSelect().DistinctOn(untrusted) + db.NewSelect().For(untrusted) + db.NewSelect().GroupExpr(untrusted) + db.NewSelect().Having(untrusted) + db.NewSelect().ModelTableExpr(untrusted) + db.NewSelect().OrderExpr(untrusted) + db.NewSelect().TableExpr(untrusted) + db.NewSelect().Where(untrusted) + db.NewSelect().WhereOr(untrusted) +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.ql b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.ql new file mode 100644 index 00000000000..ba7d0de1650 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/bun.ql @@ -0,0 +1,7 @@ +import go + +from SQL::QueryString qs, Function func, string a, string b +where + func.hasQualifiedName(a, b) and + qs = func.getACall().getSyntacticArgument(_) +select qs, a, b diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/go.mod b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/go.mod new file mode 100644 index 00000000000..590a2e9d99a --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/go.mod @@ -0,0 +1,35 @@ +module pwntester/bun + +go 1.19 + +require ( + github.com/uptrace/bun v1.1.14 + github.com/uptrace/bun/dialect/sqlitedialect v1.1.14 + github.com/uptrace/bun/driver/sqliteshim v1.1.14 +) + +require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/tools v0.9.1 // indirect + lukechampine.com/uint128 v1.3.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.6 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.22.1 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.1.0 // indirect +) diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/dialect/sqlitedialect/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/dialect/sqlitedialect/stub.go new file mode 100644 index 00000000000..3e5e0553fc6 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/dialect/sqlitedialect/stub.go @@ -0,0 +1,73 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/uptrace/bun/dialect/sqlitedialect, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/uptrace/bun/dialect/sqlitedialect (exports: Dialect; functions: New) + +// Package sqlitedialect is a stub of github.com/uptrace/bun/dialect/sqlitedialect, generated by depstubber. +package sqlitedialect + +import ( + sql "database/sql" + time "time" +) + +type Dialect struct { + BaseDialect interface{} +} + +func (_ Dialect) AppendBool(_ []byte, _ bool) []byte { + return nil +} + +func (_ Dialect) AppendJSON(_ []byte, _ []byte) []byte { + return nil +} + +func (_ Dialect) AppendString(_ []byte, _ string) []byte { + return nil +} + +func (_ Dialect) AppendTime(_ []byte, _ time.Time) []byte { + return nil +} + +func (_ Dialect) AppendUint32(_ []byte, _ uint32) []byte { + return nil +} + +func (_ Dialect) AppendUint64(_ []byte, _ uint64) []byte { + return nil +} + +func (_ *Dialect) AppendBytes(_ []byte, _ []byte) []byte { + return nil +} + +func (_ *Dialect) DefaultVarcharLen() int { + return 0 +} + +func (_ *Dialect) Features() interface{} { + return nil +} + +func (_ *Dialect) IdentQuote() byte { + return 0 +} + +func (_ *Dialect) Init(_ *sql.DB) {} + +func (_ *Dialect) Name() interface{} { + return nil +} + +func (_ *Dialect) OnTable(_ interface{}) {} + +func (_ *Dialect) Tables() interface{} { + return nil +} + +func New() *Dialect { + return nil +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/driver/sqliteshim/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/driver/sqliteshim/stub.go new file mode 100644 index 00000000000..de5faeef6df --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/driver/sqliteshim/stub.go @@ -0,0 +1,10 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/uptrace/bun/driver/sqliteshim, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/uptrace/bun/driver/sqliteshim (exports: ; functions: ShimName) + +// Package sqliteshim is a stub of github.com/uptrace/bun/driver/sqliteshim, generated by depstubber. +package sqliteshim + +var ShimName string = "" diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/stub.go new file mode 100644 index 00000000000..59341539564 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/github.com/uptrace/bun/stub.go @@ -0,0 +1,2714 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/uptrace/bun, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/uptrace/bun (exports: DB; functions: NewDB,NewRawQuery) + +// Package bun is a stub of github.com/uptrace/bun, generated by depstubber. +package bun + +import ( + context "context" + sql "database/sql" + driver "database/sql/driver" + reflect "reflect" + time "time" +) + +type AddColumnQuery struct{} + +func (_ *AddColumnQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *AddColumnQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *AddColumnQuery) Apply(_ func(*AddColumnQuery) *AddColumnQuery) *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) ColumnExpr(_ string, _ ...interface{}) *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) Conn(_ IConn) *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) DB() *DB { + return nil +} + +func (_ *AddColumnQuery) Dialect() interface{} { + return nil +} + +func (_ *AddColumnQuery) Err(_ error) *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *AddColumnQuery) GetConn() IConn { + return nil +} + +func (_ *AddColumnQuery) GetModel() interface{} { + return nil +} + +func (_ *AddColumnQuery) GetTableName() string { + return "" +} + +func (_ *AddColumnQuery) IfNotExists() *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) Model(_ interface{}) *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) ModelTableExpr(_ string, _ ...interface{}) *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *AddColumnQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *AddColumnQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *AddColumnQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *AddColumnQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *AddColumnQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *AddColumnQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *AddColumnQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *AddColumnQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *AddColumnQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *AddColumnQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *AddColumnQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *AddColumnQuery) Operation() string { + return "" +} + +func (_ *AddColumnQuery) Table(_ ...string) *AddColumnQuery { + return nil +} + +func (_ *AddColumnQuery) TableExpr(_ string, _ ...interface{}) *AddColumnQuery { + return nil +} + +type Conn struct { + Conn *sql.Conn +} + +func (_ Conn) BeginTx(_ context.Context, _ *sql.TxOptions) (Tx, error) { + return Tx{}, nil +} + +func (_ Conn) Close() error { + return nil +} + +func (_ Conn) Dialect() interface{} { + return nil +} + +func (_ Conn) ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Conn) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ Conn) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ Conn) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ Conn) NewDelete() *DeleteQuery { + return nil +} + +func (_ Conn) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ Conn) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ Conn) NewDropTable() *DropTableQuery { + return nil +} + +func (_ Conn) NewInsert() *InsertQuery { + return nil +} + +func (_ Conn) NewMerge() *MergeQuery { + return nil +} + +func (_ Conn) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ Conn) NewSelect() *SelectQuery { + return nil +} + +func (_ Conn) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ Conn) NewUpdate() *UpdateQuery { + return nil +} + +func (_ Conn) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ Conn) PingContext(_ context.Context) error { + return nil +} + +func (_ Conn) PrepareContext(_ context.Context, _ string) (*sql.Stmt, error) { + return nil, nil +} + +func (_ Conn) QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Conn) QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ Conn) Raw(_ func(interface{}) error) error { + return nil +} + +func (_ Conn) RunInTx(_ context.Context, _ *sql.TxOptions, _ func(context.Context, Tx) error) error { + return nil +} + +type CreateIndexQuery struct{} + +func (_ *CreateIndexQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *CreateIndexQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *CreateIndexQuery) Column(_ ...string) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) ColumnExpr(_ string, _ ...interface{}) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Concurrently() *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Conn(_ IConn) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) DB() *DB { + return nil +} + +func (_ *CreateIndexQuery) Dialect() interface{} { + return nil +} + +func (_ *CreateIndexQuery) Err(_ error) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) ExcludeColumn(_ ...string) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *CreateIndexQuery) GetConn() IConn { + return nil +} + +func (_ *CreateIndexQuery) GetModel() interface{} { + return nil +} + +func (_ *CreateIndexQuery) GetTableName() string { + return "" +} + +func (_ *CreateIndexQuery) IfNotExists() *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Include(_ ...string) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) IncludeExpr(_ string, _ ...interface{}) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Index(_ string) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) IndexExpr(_ string, _ ...interface{}) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Model(_ interface{}) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) ModelTableExpr(_ string, _ ...interface{}) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *CreateIndexQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *CreateIndexQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *CreateIndexQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *CreateIndexQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *CreateIndexQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *CreateIndexQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *CreateIndexQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *CreateIndexQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *CreateIndexQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *CreateIndexQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *CreateIndexQuery) Operation() string { + return "" +} + +func (_ *CreateIndexQuery) Table(_ ...string) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) TableExpr(_ string, _ ...interface{}) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Unique() *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Using(_ string, _ ...interface{}) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) Where(_ string, _ ...interface{}) *CreateIndexQuery { + return nil +} + +func (_ *CreateIndexQuery) WhereOr(_ string, _ ...interface{}) *CreateIndexQuery { + return nil +} + +type CreateTableQuery struct{} + +func (_ *CreateTableQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *CreateTableQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *CreateTableQuery) ColumnExpr(_ string, _ ...interface{}) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) Conn(_ IConn) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) DB() *DB { + return nil +} + +func (_ *CreateTableQuery) Dialect() interface{} { + return nil +} + +func (_ *CreateTableQuery) Err(_ error) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *CreateTableQuery) ForeignKey(_ string, _ ...interface{}) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) GetConn() IConn { + return nil +} + +func (_ *CreateTableQuery) GetModel() interface{} { + return nil +} + +func (_ *CreateTableQuery) GetTableName() string { + return "" +} + +func (_ *CreateTableQuery) IfNotExists() *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) Model(_ interface{}) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) ModelTableExpr(_ string, _ ...interface{}) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *CreateTableQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *CreateTableQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *CreateTableQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *CreateTableQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *CreateTableQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *CreateTableQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *CreateTableQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *CreateTableQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *CreateTableQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *CreateTableQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *CreateTableQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *CreateTableQuery) Operation() string { + return "" +} + +func (_ *CreateTableQuery) PartitionBy(_ string, _ ...interface{}) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) Table(_ ...string) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) TableExpr(_ string, _ ...interface{}) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) TableSpace(_ string) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) Temp() *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) Varchar(_ int) *CreateTableQuery { + return nil +} + +func (_ *CreateTableQuery) WithForeignKeys() *CreateTableQuery { + return nil +} + +type DB struct { + DB *sql.DB +} + +func (_ DB) Close() error { + return nil +} + +func (_ DB) Driver() driver.Driver { + return nil +} + +func (_ DB) Ping() error { + return nil +} + +func (_ DB) PingContext(_ context.Context) error { + return nil +} + +func (_ DB) SetConnMaxIdleTime(_ time.Duration) {} + +func (_ DB) SetConnMaxLifetime(_ time.Duration) {} + +func (_ DB) SetMaxIdleConns(_ int) {} + +func (_ DB) SetMaxOpenConns(_ int) {} + +func (_ DB) Stats() sql.DBStats { + return sql.DBStats{} +} + +func (_ *DB) AddQueryHook(_ QueryHook) {} + +func (_ *DB) Begin() (Tx, error) { + return Tx{}, nil +} + +func (_ *DB) BeginTx(_ context.Context, _ *sql.TxOptions) (Tx, error) { + return Tx{}, nil +} + +func (_ *DB) Conn(_ context.Context) (Conn, error) { + return Conn{}, nil +} + +func (_ *DB) DBStats() DBStats { + return DBStats{} +} + +func (_ *DB) Dialect() interface{} { + return nil +} + +func (_ *DB) Exec(_ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *DB) ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *DB) Formatter() interface{} { + return nil +} + +func (_ *DB) HasFeature(_ interface{}) bool { + return false +} + +func (_ *DB) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *DB) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *DB) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *DB) NewDelete() *DeleteQuery { + return nil +} + +func (_ *DB) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *DB) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *DB) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *DB) NewInsert() *InsertQuery { + return nil +} + +func (_ *DB) NewMerge() *MergeQuery { + return nil +} + +func (_ *DB) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *DB) NewSelect() *SelectQuery { + return nil +} + +func (_ *DB) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *DB) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *DB) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *DB) Prepare(_ string) (Stmt, error) { + return Stmt{}, nil +} + +func (_ *DB) PrepareContext(_ context.Context, _ string) (Stmt, error) { + return Stmt{}, nil +} + +func (_ *DB) Query(_ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ *DB) QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ *DB) QueryRow(_ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ *DB) QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ *DB) Raw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *DB) RegisterModel(_ ...interface{}) {} + +func (_ *DB) ResetModel(_ context.Context, _ ...interface{}) error { + return nil +} + +func (_ *DB) RunInTx(_ context.Context, _ *sql.TxOptions, _ func(context.Context, Tx) error) error { + return nil +} + +func (_ *DB) ScanRow(_ context.Context, _ *sql.Rows, _ ...interface{}) error { + return nil +} + +func (_ *DB) ScanRows(_ context.Context, _ *sql.Rows, _ ...interface{}) error { + return nil +} + +func (_ *DB) String() string { + return "" +} + +func (_ *DB) Table(_ reflect.Type) interface{} { + return nil +} + +func (_ *DB) UpdateFQN(_ string, _ string) interface{} { + return nil +} + +func (_ *DB) WithNamedArg(_ string, _ interface{}) *DB { + return nil +} + +type DBOption func(*DB) + +type DBStats struct { + Queries uint32 + Errors uint32 +} + +type DeleteQuery struct{} + +func (_ *DeleteQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *DeleteQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *DeleteQuery) Apply(_ func(*DeleteQuery) *DeleteQuery) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) ApplyQueryBuilder(_ func(QueryBuilder) QueryBuilder) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) Conn(_ IConn) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) DB() *DB { + return nil +} + +func (_ *DeleteQuery) Dialect() interface{} { + return nil +} + +func (_ *DeleteQuery) Err(_ error) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *DeleteQuery) ForceDelete() *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) GetConn() IConn { + return nil +} + +func (_ *DeleteQuery) GetModel() interface{} { + return nil +} + +func (_ *DeleteQuery) GetTableName() string { + return "" +} + +func (_ *DeleteQuery) Model(_ interface{}) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) ModelTableExpr(_ string, _ ...interface{}) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *DeleteQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *DeleteQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *DeleteQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *DeleteQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *DeleteQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *DeleteQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *DeleteQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *DeleteQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *DeleteQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *DeleteQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *DeleteQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *DeleteQuery) Operation() string { + return "" +} + +func (_ *DeleteQuery) QueryBuilder() QueryBuilder { + return nil +} + +func (_ *DeleteQuery) Returning(_ string, _ ...interface{}) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) Scan(_ context.Context, _ ...interface{}) error { + return nil +} + +func (_ *DeleteQuery) String() string { + return "" +} + +func (_ *DeleteQuery) Table(_ ...string) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) TableExpr(_ string, _ ...interface{}) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) Where(_ string, _ ...interface{}) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) WhereAllWithDeleted() *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) WhereDeleted() *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) WhereGroup(_ string, _ func(*DeleteQuery) *DeleteQuery) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) WhereOr(_ string, _ ...interface{}) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) WherePK(_ ...string) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) With(_ string, _ interface{}) *DeleteQuery { + return nil +} + +func (_ *DeleteQuery) WithRecursive(_ string, _ interface{}) *DeleteQuery { + return nil +} + +type DropColumnQuery struct{} + +func (_ *DropColumnQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *DropColumnQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *DropColumnQuery) Apply(_ func(*DropColumnQuery) *DropColumnQuery) *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) Column(_ ...string) *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) ColumnExpr(_ string, _ ...interface{}) *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) Conn(_ IConn) *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) DB() *DB { + return nil +} + +func (_ *DropColumnQuery) Dialect() interface{} { + return nil +} + +func (_ *DropColumnQuery) Err(_ error) *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *DropColumnQuery) GetConn() IConn { + return nil +} + +func (_ *DropColumnQuery) GetModel() interface{} { + return nil +} + +func (_ *DropColumnQuery) GetTableName() string { + return "" +} + +func (_ *DropColumnQuery) Model(_ interface{}) *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) ModelTableExpr(_ string, _ ...interface{}) *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *DropColumnQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *DropColumnQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *DropColumnQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *DropColumnQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *DropColumnQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *DropColumnQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *DropColumnQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *DropColumnQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *DropColumnQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *DropColumnQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *DropColumnQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *DropColumnQuery) Operation() string { + return "" +} + +func (_ *DropColumnQuery) Table(_ ...string) *DropColumnQuery { + return nil +} + +func (_ *DropColumnQuery) TableExpr(_ string, _ ...interface{}) *DropColumnQuery { + return nil +} + +type DropIndexQuery struct{} + +func (_ *DropIndexQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *DropIndexQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *DropIndexQuery) Cascade() *DropIndexQuery { + return nil +} + +func (_ *DropIndexQuery) Concurrently() *DropIndexQuery { + return nil +} + +func (_ *DropIndexQuery) Conn(_ IConn) *DropIndexQuery { + return nil +} + +func (_ *DropIndexQuery) DB() *DB { + return nil +} + +func (_ *DropIndexQuery) Dialect() interface{} { + return nil +} + +func (_ *DropIndexQuery) Err(_ error) *DropIndexQuery { + return nil +} + +func (_ *DropIndexQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *DropIndexQuery) GetConn() IConn { + return nil +} + +func (_ *DropIndexQuery) GetModel() interface{} { + return nil +} + +func (_ *DropIndexQuery) GetTableName() string { + return "" +} + +func (_ *DropIndexQuery) IfExists() *DropIndexQuery { + return nil +} + +func (_ *DropIndexQuery) Index(_ string, _ ...interface{}) *DropIndexQuery { + return nil +} + +func (_ *DropIndexQuery) Model(_ interface{}) *DropIndexQuery { + return nil +} + +func (_ *DropIndexQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *DropIndexQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *DropIndexQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *DropIndexQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *DropIndexQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *DropIndexQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *DropIndexQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *DropIndexQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *DropIndexQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *DropIndexQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *DropIndexQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *DropIndexQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *DropIndexQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *DropIndexQuery) Operation() string { + return "" +} + +func (_ *DropIndexQuery) Restrict() *DropIndexQuery { + return nil +} + +type DropTableQuery struct{} + +func (_ *DropTableQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *DropTableQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *DropTableQuery) Cascade() *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) Conn(_ IConn) *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) DB() *DB { + return nil +} + +func (_ *DropTableQuery) Dialect() interface{} { + return nil +} + +func (_ *DropTableQuery) Err(_ error) *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *DropTableQuery) GetConn() IConn { + return nil +} + +func (_ *DropTableQuery) GetModel() interface{} { + return nil +} + +func (_ *DropTableQuery) GetTableName() string { + return "" +} + +func (_ *DropTableQuery) IfExists() *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) Model(_ interface{}) *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) ModelTableExpr(_ string, _ ...interface{}) *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *DropTableQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *DropTableQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *DropTableQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *DropTableQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *DropTableQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *DropTableQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *DropTableQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *DropTableQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *DropTableQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *DropTableQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *DropTableQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *DropTableQuery) Operation() string { + return "" +} + +func (_ *DropTableQuery) Restrict() *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) Table(_ ...string) *DropTableQuery { + return nil +} + +func (_ *DropTableQuery) TableExpr(_ string, _ ...interface{}) *DropTableQuery { + return nil +} + +type IConn interface { + ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) + QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) + QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row +} + +type InsertQuery struct{} + +func (_ *InsertQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *InsertQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *InsertQuery) Apply(_ func(*InsertQuery) *InsertQuery) *InsertQuery { + return nil +} + +func (_ *InsertQuery) Column(_ ...string) *InsertQuery { + return nil +} + +func (_ *InsertQuery) ColumnExpr(_ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) Conn(_ IConn) *InsertQuery { + return nil +} + +func (_ *InsertQuery) DB() *DB { + return nil +} + +func (_ *InsertQuery) Dialect() interface{} { + return nil +} + +func (_ *InsertQuery) Err(_ error) *InsertQuery { + return nil +} + +func (_ *InsertQuery) ExcludeColumn(_ ...string) *InsertQuery { + return nil +} + +func (_ *InsertQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *InsertQuery) GetConn() IConn { + return nil +} + +func (_ *InsertQuery) GetModel() interface{} { + return nil +} + +func (_ *InsertQuery) GetTableName() string { + return "" +} + +func (_ *InsertQuery) Ignore() *InsertQuery { + return nil +} + +func (_ *InsertQuery) Model(_ interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) ModelTableExpr(_ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *InsertQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *InsertQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *InsertQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *InsertQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *InsertQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *InsertQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *InsertQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *InsertQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *InsertQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *InsertQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *InsertQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *InsertQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *InsertQuery) On(_ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) Operation() string { + return "" +} + +func (_ *InsertQuery) Replace() *InsertQuery { + return nil +} + +func (_ *InsertQuery) Returning(_ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) Scan(_ context.Context, _ ...interface{}) error { + return nil +} + +func (_ *InsertQuery) Set(_ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) String() string { + return "" +} + +func (_ *InsertQuery) Table(_ ...string) *InsertQuery { + return nil +} + +func (_ *InsertQuery) TableExpr(_ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) Value(_ string, _ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) Where(_ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) WhereOr(_ string, _ ...interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) With(_ string, _ interface{}) *InsertQuery { + return nil +} + +func (_ *InsertQuery) WithRecursive(_ string, _ interface{}) *InsertQuery { + return nil +} + +type MergeQuery struct{} + +func (_ *MergeQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *MergeQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *MergeQuery) Apply(_ func(*MergeQuery) *MergeQuery) *MergeQuery { + return nil +} + +func (_ *MergeQuery) Conn(_ IConn) *MergeQuery { + return nil +} + +func (_ *MergeQuery) DB() *DB { + return nil +} + +func (_ *MergeQuery) Dialect() interface{} { + return nil +} + +func (_ *MergeQuery) Err(_ error) *MergeQuery { + return nil +} + +func (_ *MergeQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *MergeQuery) GetConn() IConn { + return nil +} + +func (_ *MergeQuery) GetModel() interface{} { + return nil +} + +func (_ *MergeQuery) GetTableName() string { + return "" +} + +func (_ *MergeQuery) Model(_ interface{}) *MergeQuery { + return nil +} + +func (_ *MergeQuery) ModelTableExpr(_ string, _ ...interface{}) *MergeQuery { + return nil +} + +func (_ *MergeQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *MergeQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *MergeQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *MergeQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *MergeQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *MergeQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *MergeQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *MergeQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *MergeQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *MergeQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *MergeQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *MergeQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *MergeQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *MergeQuery) On(_ string, _ ...interface{}) *MergeQuery { + return nil +} + +func (_ *MergeQuery) Operation() string { + return "" +} + +func (_ *MergeQuery) Returning(_ string, _ ...interface{}) *MergeQuery { + return nil +} + +func (_ *MergeQuery) Scan(_ context.Context, _ ...interface{}) error { + return nil +} + +func (_ *MergeQuery) String() string { + return "" +} + +func (_ *MergeQuery) Table(_ ...string) *MergeQuery { + return nil +} + +func (_ *MergeQuery) TableExpr(_ string, _ ...interface{}) *MergeQuery { + return nil +} + +func (_ *MergeQuery) Using(_ string, _ ...interface{}) *MergeQuery { + return nil +} + +func (_ *MergeQuery) When(_ string, _ ...interface{}) *MergeQuery { + return nil +} + +func (_ *MergeQuery) WhenDelete(_ string) *MergeQuery { + return nil +} + +func (_ *MergeQuery) WhenInsert(_ string, _ func(*InsertQuery) *InsertQuery) *MergeQuery { + return nil +} + +func (_ *MergeQuery) WhenUpdate(_ string, _ func(*UpdateQuery) *UpdateQuery) *MergeQuery { + return nil +} + +func (_ *MergeQuery) With(_ string, _ interface{}) *MergeQuery { + return nil +} + +func (_ *MergeQuery) WithRecursive(_ string, _ interface{}) *MergeQuery { + return nil +} + +func NewDB(_ *sql.DB, _ interface{}, _ ...DBOption) *DB { + return nil +} + +func NewRawQuery(_ *DB, _ string, _ ...interface{}) *RawQuery { + return nil +} + +type QueryBuilder interface { + AppendQuery(_ interface{}, _ []byte) ([]byte, error) + GetModel() interface{} + GetTableName() string + Operation() string + Unwrap() interface{} + Where(_ string, _ ...interface{}) QueryBuilder + WhereAllWithDeleted() QueryBuilder + WhereDeleted() QueryBuilder + WhereGroup(_ string, _ func(QueryBuilder) QueryBuilder) QueryBuilder + WhereOr(_ string, _ ...interface{}) QueryBuilder + WherePK(_ ...string) QueryBuilder +} + +type QueryEvent struct { + DB *DB + QueryAppender interface{} + IQuery interface{} + Query string + QueryTemplate string + QueryArgs []interface{} + Model interface{} + StartTime time.Time + Result sql.Result + Err error + Stash map[interface{}]interface{} +} + +func (_ *QueryEvent) Operation() string { + return "" +} + +type QueryHook interface { + AfterQuery(_ context.Context, _ *QueryEvent) + BeforeQuery(_ context.Context, _ *QueryEvent) context.Context +} + +type RawQuery struct{} + +func (_ *RawQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *RawQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *RawQuery) Conn(_ IConn) *RawQuery { + return nil +} + +func (_ *RawQuery) DB() *DB { + return nil +} + +func (_ *RawQuery) Dialect() interface{} { + return nil +} + +func (_ *RawQuery) Err(_ error) *RawQuery { + return nil +} + +func (_ *RawQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *RawQuery) GetConn() IConn { + return nil +} + +func (_ *RawQuery) GetModel() interface{} { + return nil +} + +func (_ *RawQuery) GetTableName() string { + return "" +} + +func (_ *RawQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *RawQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *RawQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *RawQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *RawQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *RawQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *RawQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *RawQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *RawQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *RawQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *RawQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *RawQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *RawQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *RawQuery) Operation() string { + return "" +} + +func (_ *RawQuery) Scan(_ context.Context, _ ...interface{}) error { + return nil +} + +type SelectQuery struct{} + +func (_ *SelectQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *SelectQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *SelectQuery) Apply(_ func(*SelectQuery) *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ApplyQueryBuilder(_ func(QueryBuilder) QueryBuilder) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Column(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ColumnExpr(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Conn(_ IConn) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Count(_ context.Context) (int, error) { + return 0, nil +} + +func (_ *SelectQuery) DB() *DB { + return nil +} + +func (_ *SelectQuery) Dialect() interface{} { + return nil +} + +func (_ *SelectQuery) Distinct() *SelectQuery { + return nil +} + +func (_ *SelectQuery) DistinctOn(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Err(_ error) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Except(_ *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ExceptAll(_ *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ExcludeColumn(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *SelectQuery) Exists(_ context.Context) (bool, error) { + return false, nil +} + +func (_ *SelectQuery) For(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ForceIndex(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ForceIndexForGroupBy(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ForceIndexForJoin(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ForceIndexForOrderBy(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) GetConn() IConn { + return nil +} + +func (_ *SelectQuery) GetModel() interface{} { + return nil +} + +func (_ *SelectQuery) GetTableName() string { + return "" +} + +func (_ *SelectQuery) Group(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) GroupExpr(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Having(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) IgnoreIndex(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) IgnoreIndexForGroupBy(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) IgnoreIndexForJoin(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) IgnoreIndexForOrderBy(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Intersect(_ *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) IntersectAll(_ *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Join(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) JoinOn(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) JoinOnOr(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Limit(_ int) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Model(_ interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) ModelTableExpr(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *SelectQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *SelectQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *SelectQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *SelectQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *SelectQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *SelectQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *SelectQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *SelectQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *SelectQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *SelectQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *SelectQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *SelectQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *SelectQuery) Offset(_ int) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Operation() string { + return "" +} + +func (_ *SelectQuery) Order(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) OrderExpr(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) QueryBuilder() QueryBuilder { + return nil +} + +func (_ *SelectQuery) Relation(_ string, _ ...func(*SelectQuery) *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Rows(_ context.Context) (*sql.Rows, error) { + return nil, nil +} + +func (_ *SelectQuery) Scan(_ context.Context, _ ...interface{}) error { + return nil +} + +func (_ *SelectQuery) ScanAndCount(_ context.Context, _ ...interface{}) (int, error) { + return 0, nil +} + +func (_ *SelectQuery) String() string { + return "" +} + +func (_ *SelectQuery) Table(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) TableExpr(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Union(_ *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) UnionAll(_ *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) UseIndex(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) UseIndexForGroupBy(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) UseIndexForJoin(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) UseIndexForOrderBy(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) Where(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) WhereAllWithDeleted() *SelectQuery { + return nil +} + +func (_ *SelectQuery) WhereDeleted() *SelectQuery { + return nil +} + +func (_ *SelectQuery) WhereGroup(_ string, _ func(*SelectQuery) *SelectQuery) *SelectQuery { + return nil +} + +func (_ *SelectQuery) WhereOr(_ string, _ ...interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) WherePK(_ ...string) *SelectQuery { + return nil +} + +func (_ *SelectQuery) With(_ string, _ interface{}) *SelectQuery { + return nil +} + +func (_ *SelectQuery) WithRecursive(_ string, _ interface{}) *SelectQuery { + return nil +} + +type Stmt struct { + Stmt *sql.Stmt +} + +func (_ Stmt) Close() error { + return nil +} + +func (_ Stmt) Exec(_ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Stmt) ExecContext(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Stmt) Query(_ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Stmt) QueryContext(_ context.Context, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Stmt) QueryRow(_ ...interface{}) *sql.Row { + return nil +} + +func (_ Stmt) QueryRowContext(_ context.Context, _ ...interface{}) *sql.Row { + return nil +} + +type TruncateTableQuery struct{} + +func (_ *TruncateTableQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *TruncateTableQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *TruncateTableQuery) Cascade() *TruncateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) Conn(_ IConn) *TruncateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) ContinueIdentity() *TruncateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) DB() *DB { + return nil +} + +func (_ *TruncateTableQuery) Dialect() interface{} { + return nil +} + +func (_ *TruncateTableQuery) Err(_ error) *TruncateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *TruncateTableQuery) GetConn() IConn { + return nil +} + +func (_ *TruncateTableQuery) GetModel() interface{} { + return nil +} + +func (_ *TruncateTableQuery) GetTableName() string { + return "" +} + +func (_ *TruncateTableQuery) Model(_ interface{}) *TruncateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *TruncateTableQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *TruncateTableQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *TruncateTableQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *TruncateTableQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *TruncateTableQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *TruncateTableQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *TruncateTableQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *TruncateTableQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *TruncateTableQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *TruncateTableQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *TruncateTableQuery) Operation() string { + return "" +} + +func (_ *TruncateTableQuery) Restrict() *TruncateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) Table(_ ...string) *TruncateTableQuery { + return nil +} + +func (_ *TruncateTableQuery) TableExpr(_ string, _ ...interface{}) *TruncateTableQuery { + return nil +} + +type Tx struct { + Tx *sql.Tx +} + +func (_ Tx) Begin() (Tx, error) { + return Tx{}, nil +} + +func (_ Tx) BeginTx(_ context.Context, _ *sql.TxOptions) (Tx, error) { + return Tx{}, nil +} + +func (_ Tx) Commit() error { + return nil +} + +func (_ Tx) Dialect() interface{} { + return nil +} + +func (_ Tx) Exec(_ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Tx) ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Tx) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ Tx) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ Tx) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ Tx) NewDelete() *DeleteQuery { + return nil +} + +func (_ Tx) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ Tx) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ Tx) NewDropTable() *DropTableQuery { + return nil +} + +func (_ Tx) NewInsert() *InsertQuery { + return nil +} + +func (_ Tx) NewMerge() *MergeQuery { + return nil +} + +func (_ Tx) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ Tx) NewSelect() *SelectQuery { + return nil +} + +func (_ Tx) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ Tx) NewUpdate() *UpdateQuery { + return nil +} + +func (_ Tx) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ Tx) Prepare(_ string) (*sql.Stmt, error) { + return nil, nil +} + +func (_ Tx) PrepareContext(_ context.Context, _ string) (*sql.Stmt, error) { + return nil, nil +} + +func (_ Tx) Query(_ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Tx) QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Tx) QueryRow(_ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ Tx) QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ Tx) Rollback() error { + return nil +} + +func (_ Tx) RunInTx(_ context.Context, _ *sql.TxOptions, _ func(context.Context, Tx) error) error { + return nil +} + +func (_ Tx) Stmt(_ *sql.Stmt) *sql.Stmt { + return nil +} + +func (_ Tx) StmtContext(_ context.Context, _ *sql.Stmt) *sql.Stmt { + return nil +} + +type UpdateQuery struct{} + +func (_ *UpdateQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *UpdateQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *UpdateQuery) Apply(_ func(*UpdateQuery) *UpdateQuery) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) ApplyQueryBuilder(_ func(QueryBuilder) QueryBuilder) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Bulk() *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Column(_ ...string) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Conn(_ IConn) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) DB() *DB { + return nil +} + +func (_ *UpdateQuery) Dialect() interface{} { + return nil +} + +func (_ *UpdateQuery) Err(_ error) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) ExcludeColumn(_ ...string) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Exec(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *UpdateQuery) FQN(_ string) interface{} { + return nil +} + +func (_ *UpdateQuery) ForceIndex(_ ...string) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) GetConn() IConn { + return nil +} + +func (_ *UpdateQuery) GetModel() interface{} { + return nil +} + +func (_ *UpdateQuery) GetTableName() string { + return "" +} + +func (_ *UpdateQuery) IgnoreIndex(_ ...string) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Model(_ interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) ModelTableExpr(_ string, _ ...interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *UpdateQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *UpdateQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *UpdateQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *UpdateQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *UpdateQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *UpdateQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *UpdateQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *UpdateQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *UpdateQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *UpdateQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *UpdateQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *UpdateQuery) OmitZero() *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Operation() string { + return "" +} + +func (_ *UpdateQuery) QueryBuilder() QueryBuilder { + return nil +} + +func (_ *UpdateQuery) Returning(_ string, _ ...interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Scan(_ context.Context, _ ...interface{}) error { + return nil +} + +func (_ *UpdateQuery) Set(_ string, _ ...interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) SetColumn(_ string, _ string, _ ...interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) String() string { + return "" +} + +func (_ *UpdateQuery) Table(_ ...string) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) TableExpr(_ string, _ ...interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) UseIndex(_ ...string) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Value(_ string, _ string, _ ...interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) Where(_ string, _ ...interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) WhereAllWithDeleted() *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) WhereDeleted() *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) WhereGroup(_ string, _ func(*UpdateQuery) *UpdateQuery) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) WhereOr(_ string, _ ...interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) WherePK(_ ...string) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) With(_ string, _ interface{}) *UpdateQuery { + return nil +} + +func (_ *UpdateQuery) WithRecursive(_ string, _ interface{}) *UpdateQuery { + return nil +} + +type ValuesQuery struct{} + +func (_ *ValuesQuery) AppendColumns(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *ValuesQuery) AppendNamedArg(_ interface{}, _ []byte, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *ValuesQuery) AppendQuery(_ interface{}, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *ValuesQuery) Column(_ ...string) *ValuesQuery { + return nil +} + +func (_ *ValuesQuery) Conn(_ IConn) *ValuesQuery { + return nil +} + +func (_ *ValuesQuery) DB() *DB { + return nil +} + +func (_ *ValuesQuery) Dialect() interface{} { + return nil +} + +func (_ *ValuesQuery) Err(_ error) *ValuesQuery { + return nil +} + +func (_ *ValuesQuery) GetConn() IConn { + return nil +} + +func (_ *ValuesQuery) GetModel() interface{} { + return nil +} + +func (_ *ValuesQuery) GetTableName() string { + return "" +} + +func (_ *ValuesQuery) NewAddColumn() *AddColumnQuery { + return nil +} + +func (_ *ValuesQuery) NewCreateIndex() *CreateIndexQuery { + return nil +} + +func (_ *ValuesQuery) NewCreateTable() *CreateTableQuery { + return nil +} + +func (_ *ValuesQuery) NewDelete() *DeleteQuery { + return nil +} + +func (_ *ValuesQuery) NewDropColumn() *DropColumnQuery { + return nil +} + +func (_ *ValuesQuery) NewDropIndex() *DropIndexQuery { + return nil +} + +func (_ *ValuesQuery) NewDropTable() *DropTableQuery { + return nil +} + +func (_ *ValuesQuery) NewInsert() *InsertQuery { + return nil +} + +func (_ *ValuesQuery) NewRaw(_ string, _ ...interface{}) *RawQuery { + return nil +} + +func (_ *ValuesQuery) NewSelect() *SelectQuery { + return nil +} + +func (_ *ValuesQuery) NewTruncateTable() *TruncateTableQuery { + return nil +} + +func (_ *ValuesQuery) NewUpdate() *UpdateQuery { + return nil +} + +func (_ *ValuesQuery) NewValues(_ interface{}) *ValuesQuery { + return nil +} + +func (_ *ValuesQuery) Operation() string { + return "" +} + +func (_ *ValuesQuery) Value(_ string, _ string, _ ...interface{}) *ValuesQuery { + return nil +} + +func (_ *ValuesQuery) WithOrder() *ValuesQuery { + return nil +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/modules.txt b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/modules.txt new file mode 100644 index 00000000000..03ac276ce98 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/bun/vendor/modules.txt @@ -0,0 +1,78 @@ +# github.com/uptrace/bun v1.1.14 +## explicit +github.com/uptrace/bun +# github.com/uptrace/bun/dialect/sqlitedialect v1.1.14 +## explicit +github.com/uptrace/bun/dialect/sqlitedialect +# github.com/uptrace/bun/driver/sqliteshim v1.1.14 +## explicit +github.com/uptrace/bun/driver/sqliteshim +# github.com/dustin/go-humanize v1.0.1 +## explicit +github.com/dustin/go-humanize +# github.com/google/uuid v1.3.0 +## explicit +github.com/google/uuid +# github.com/jinzhu/inflection v1.0.0 +## explicit +github.com/jinzhu/inflection +# github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 +## explicit +github.com/kballard/go-shellquote +# github.com/mattn/go-isatty v0.0.19 +## explicit +github.com/mattn/go-isatty +# github.com/mattn/go-sqlite3 v1.14.16 +## explicit +github.com/mattn/go-sqlite3 +# github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec +## explicit +github.com/remyoudompheng/bigfft +# github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc +## explicit +github.com/tmthrgd/go-hex +# github.com/vmihailenco/msgpack/v5 v5.3.5 +## explicit +github.com/vmihailenco/msgpack/v5 +# github.com/vmihailenco/tagparser/v2 v2.0.0 +## explicit +github.com/vmihailenco/tagparser/v2 +# golang.org/x/mod v0.10.0 +## explicit +golang.org/x/mod +# golang.org/x/sys v0.8.0 +## explicit +golang.org/x/sys +# golang.org/x/tools v0.9.1 +## explicit +golang.org/x/tools +# lukechampine.com/uint128 v1.3.0 +## explicit +lukechampine.com/uint128 +# modernc.org/cc/v3 v3.40.0 +## explicit +modernc.org/cc/v3 +# modernc.org/ccgo/v3 v3.16.13 +## explicit +modernc.org/ccgo/v3 +# modernc.org/libc v1.22.6 +## explicit +modernc.org/libc +# modernc.org/mathutil v1.5.0 +## explicit +modernc.org/mathutil +# modernc.org/memory v1.5.0 +## explicit +modernc.org/memory +# modernc.org/opt v0.1.3 +## explicit +modernc.org/opt +# modernc.org/sqlite v1.22.1 +## explicit +modernc.org/sqlite +# modernc.org/strutil v1.1.3 +## explicit +modernc.org/strutil +# modernc.org/token v1.1.0 +## explicit +modernc.org/token diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.expected b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.expected new file mode 100644 index 00000000000..8aca3839505 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.expected @@ -0,0 +1,20 @@ +| go-pg.go:38:10:38:18 | untrusted | github.com/go-pg/pg/v10 | DB | Exec | +| go-pg.go:39:13:39:21 | untrusted | github.com/go-pg/pg/v10 | DB | ExecOne | +| go-pg.go:40:13:40:21 | untrusted | github.com/go-pg/pg/v10 | DB | Prepare | +| go-pg.go:42:22:42:30 | untrusted | github.com/go-pg/pg/v10 | DB | ExecContext | +| go-pg.go:43:25:43:33 | untrusted | github.com/go-pg/pg/v10 | DB | ExecOneContext | +| go-pg.go:44:21:44:29 | untrusted | github.com/go-pg/pg/v10 | DB | Query | +| go-pg.go:45:24:45:32 | untrusted | github.com/go-pg/pg/v10 | DB | QueryOne | +| go-pg.go:47:45:47:53 | untrusted | github.com/go-pg/pg/v10 | DB | QueryOneContext | +| go-pg.go:48:33:48:41 | untrusted | github.com/go-pg/pg/v10 | DB | QueryContext | +| go-pg.go:52:14:52:22 | untrusted | github.com/go-pg/pg/v10/orm | Query | ColumnExpr | +| go-pg.go:53:8:53:16 | untrusted | github.com/go-pg/pg/v10/orm | Query | Join | +| go-pg.go:54:9:54:17 | untrusted | github.com/go-pg/pg/v10/orm | Query | Where | +| go-pg.go:55:13:55:21 | untrusted | github.com/go-pg/pg/v10/orm | Query | OrderExpr | +| go-pg.go:56:13:56:21 | untrusted | github.com/go-pg/pg/v10/orm | Query | GroupExpr | +| go-pg.go:57:13:57:21 | untrusted | github.com/go-pg/pg/v10/orm | Query | TableExpr | +| go-pg.go:58:11:58:19 | untrusted | github.com/go-pg/pg/v10/orm | Query | WhereIn | +| go-pg.go:59:16:59:24 | untrusted | github.com/go-pg/pg/v10/orm | Query | WhereInMulti | +| go-pg.go:60:11:60:19 | untrusted | github.com/go-pg/pg/v10/orm | Query | WhereOr | +| go-pg.go:61:7:61:15 | untrusted | github.com/go-pg/pg/v10/orm | Query | For | +| go-pg.go:62:10:62:18 | untrusted | github.com/go-pg/pg/v10/orm | Query | Having | diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.go b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.go new file mode 100644 index 00000000000..68a1ee9308a --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + pg "github.com/go-pg/pg/v10" +) + +type Profile struct { + ID int + Lang string +} + +type User struct { + ID int + Name string + ProfileID int + Profile *Profile `pg:"-"` +} + +func getUntrustedString() string { + return "trouble" +} + +func main() { + + untrusted := getUntrustedString() + + ctx := context.Background() + db := pg.Connect(&pg.Options{ + Addr: ":5432", + User: "user", + Password: "pass", + Database: "db_name", + }) + + var version string + + db.Exec(untrusted) + db.ExecOne(untrusted) + db.Prepare(untrusted) + + db.ExecContext(ctx, untrusted) + db.ExecOneContext(ctx, untrusted) + db.Query(&version, untrusted) + db.QueryOne(&version, untrusted) + + db.QueryOneContext(ctx, pg.Scan(&version), untrusted) + db.QueryContext(ctx, &version, untrusted) + + var user User + db.Model(&user). + ColumnExpr(untrusted). + Join(untrusted). + Where(untrusted, 123). + OrderExpr(untrusted). + GroupExpr(untrusted). + TableExpr(untrusted). + WhereIn(untrusted, 1). + WhereInMulti(untrusted, 1). + WhereOr(untrusted, 1). + For(untrusted). + Having(untrusted). + Select() +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.ql b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.ql new file mode 100644 index 00000000000..f40159c050f --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go-pg.ql @@ -0,0 +1,5 @@ +import go + +from SQL::QueryString qs, Method meth, string a, string b, string c +where meth.hasQualifiedName(a, b, c) and qs = meth.getACall().getSyntacticArgument(_) +select qs, a, b, c diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go.mod b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go.mod new file mode 100644 index 00000000000..975aca7b9eb --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/go.mod @@ -0,0 +1,20 @@ +module pwntester/go-pg + +go 1.19 + +require github.com/go-pg/pg/v10 v10.11.0 + +require ( + github.com/go-pg/zerochecker v0.2.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/bufpool v0.1.11 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect + github.com/vmihailenco/tagparser v0.1.2 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + mellium.im/sasl v0.3.1 // indirect +) diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/github.com/go-pg/pg/v10/orm/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/github.com/go-pg/pg/v10/orm/stub.go new file mode 100644 index 00000000000..8723ac37935 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/github.com/go-pg/pg/v10/orm/stub.go @@ -0,0 +1,537 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/go-pg/pg/v10/orm, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/go-pg/pg/v10/orm (exports: Query; functions: ) + +// Package orm is a stub of github.com/go-pg/pg/v10/orm, generated by depstubber. +package orm + +import ( + context "context" + io "io" + reflect "reflect" +) + +type ColumnScanner interface { + ScanColumn(_ interface{}, _ interface{}, _ int) error +} + +type CreateCompositeOptions struct { + Varchar int +} + +type CreateTableOptions struct { + Varchar int + Temp bool + IfNotExists bool + FKConstraints bool +} + +type DB interface { + Context() context.Context + CopyFrom(_ io.Reader, _ interface{}, _ ...interface{}) (Result, error) + CopyTo(_ io.Writer, _ interface{}, _ ...interface{}) (Result, error) + Exec(_ interface{}, _ ...interface{}) (Result, error) + ExecContext(_ context.Context, _ interface{}, _ ...interface{}) (Result, error) + ExecOne(_ interface{}, _ ...interface{}) (Result, error) + ExecOneContext(_ context.Context, _ interface{}, _ ...interface{}) (Result, error) + Formatter() QueryFormatter + Model(_ ...interface{}) *Query + ModelContext(_ context.Context, _ ...interface{}) *Query + Query(_ interface{}, _ interface{}, _ ...interface{}) (Result, error) + QueryContext(_ context.Context, _ interface{}, _ interface{}, _ ...interface{}) (Result, error) + QueryOne(_ interface{}, _ interface{}, _ ...interface{}) (Result, error) + QueryOneContext(_ context.Context, _ interface{}, _ interface{}, _ ...interface{}) (Result, error) +} + +type DropCompositeOptions struct { + IfExists bool + Cascade bool +} + +type DropTableOptions struct { + IfExists bool + Cascade bool +} + +type Field struct { + Field reflect.StructField + Type reflect.Type + Index []int + GoName string + SQLName string + Column interface{} + SQLType string + UserSQLType string + Default interface{} + OnDelete string + OnUpdate string +} + +func (_ *Field) AppendValue(_ []byte, _ reflect.Value, _ int) []byte { + return nil +} + +func (_ *Field) Clone() *Field { + return nil +} + +func (_ *Field) HasZeroValue(_ reflect.Value) bool { + return false +} + +func (_ *Field) NullZero() bool { + return false +} + +func (_ *Field) ScanValue(_ reflect.Value, _ interface{}, _ int) error { + return nil +} + +func (_ *Field) Value(_ reflect.Value) reflect.Value { + return reflect.Value{} +} + +type Method struct { + Index int +} + +func (_ *Method) AppendValue(_ []byte, _ reflect.Value, _ int) []byte { + return nil +} + +func (_ *Method) Has(_ int8) bool { + return false +} + +func (_ *Method) Value(_ reflect.Value) reflect.Value { + return reflect.Value{} +} + +type Model interface { + AddColumnScanner(_ ColumnScanner) error + AfterDelete(_ context.Context) error + AfterInsert(_ context.Context) error + AfterScan(_ context.Context) error + AfterSelect(_ context.Context) error + AfterUpdate(_ context.Context) error + BeforeDelete(_ context.Context) (context.Context, error) + BeforeInsert(_ context.Context) (context.Context, error) + BeforeUpdate(_ context.Context) (context.Context, error) + Init() error + NextColumnScanner() ColumnScanner +} + +type Query struct{} + +func (_ *Query) AllWithDeleted() *Query { + return nil +} + +func (_ *Query) AppendQuery(_ QueryFormatter, _ []byte) ([]byte, error) { + return nil, nil +} + +func (_ *Query) Apply(_ func(*Query) (*Query, error)) *Query { + return nil +} + +func (_ *Query) Clone() *Query { + return nil +} + +func (_ *Query) Column(_ ...string) *Query { + return nil +} + +func (_ *Query) ColumnExpr(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Context(_ context.Context) *Query { + return nil +} + +func (_ *Query) CopyFrom(_ io.Reader, _ interface{}, _ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) CopyTo(_ io.Writer, _ interface{}, _ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) Count() (int, error) { + return 0, nil +} + +func (_ *Query) CountEstimate(_ int) (int, error) { + return 0, nil +} + +func (_ *Query) CreateComposite(_ *CreateCompositeOptions) error { + return nil +} + +func (_ *Query) CreateTable(_ *CreateTableOptions) error { + return nil +} + +func (_ *Query) DB(_ DB) *Query { + return nil +} + +func (_ *Query) Delete(_ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) Deleted() *Query { + return nil +} + +func (_ *Query) Distinct() *Query { + return nil +} + +func (_ *Query) DistinctOn(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) DropComposite(_ *DropCompositeOptions) error { + return nil +} + +func (_ *Query) DropTable(_ *DropTableOptions) error { + return nil +} + +func (_ *Query) Except(_ *Query) *Query { + return nil +} + +func (_ *Query) ExceptAll(_ *Query) *Query { + return nil +} + +func (_ *Query) ExcludeColumn(_ ...string) *Query { + return nil +} + +func (_ *Query) Exec(_ interface{}, _ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) ExecOne(_ interface{}, _ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) Exists() (bool, error) { + return false, nil +} + +func (_ *Query) First() error { + return nil +} + +func (_ *Query) For(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) ForEach(_ interface{}) error { + return nil +} + +func (_ *Query) ForceDelete(_ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) Group(_ ...string) *Query { + return nil +} + +func (_ *Query) GroupExpr(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Having(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Insert(_ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) Intersect(_ *Query) *Query { + return nil +} + +func (_ *Query) IntersectAll(_ *Query) *Query { + return nil +} + +func (_ *Query) Join(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) JoinOn(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) JoinOnOr(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Last() error { + return nil +} + +func (_ *Query) Limit(_ int) *Query { + return nil +} + +func (_ *Query) Model(_ ...interface{}) *Query { + return nil +} + +func (_ *Query) New() *Query { + return nil +} + +func (_ *Query) Offset(_ int) *Query { + return nil +} + +func (_ *Query) OnConflict(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Order(_ ...string) *Query { + return nil +} + +func (_ *Query) OrderExpr(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Query(_ interface{}, _ interface{}, _ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) QueryOne(_ interface{}, _ interface{}, _ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) Relation(_ string, _ ...func(*Query) (*Query, error)) *Query { + return nil +} + +func (_ *Query) Returning(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Select(_ ...interface{}) error { + return nil +} + +func (_ *Query) SelectAndCount(_ ...interface{}) (int, error) { + return 0, nil +} + +func (_ *Query) SelectAndCountEstimate(_ int, _ ...interface{}) (int, error) { + return 0, nil +} + +func (_ *Query) SelectOrInsert(_ ...interface{}) (bool, error) { + return false, nil +} + +func (_ *Query) Set(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Table(_ ...string) *Query { + return nil +} + +func (_ *Query) TableExpr(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) TableModel() TableModel { + return nil +} + +func (_ *Query) Union(_ *Query) *Query { + return nil +} + +func (_ *Query) UnionAll(_ *Query) *Query { + return nil +} + +func (_ *Query) Update(_ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) UpdateNotZero(_ ...interface{}) (Result, error) { + return nil, nil +} + +func (_ *Query) Value(_ string, _ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) Where(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) WhereGroup(_ func(*Query) (*Query, error)) *Query { + return nil +} + +func (_ *Query) WhereIn(_ string, _ interface{}) *Query { + return nil +} + +func (_ *Query) WhereInMulti(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) WhereInOr(_ string, _ interface{}) *Query { + return nil +} + +func (_ *Query) WhereNotGroup(_ func(*Query) (*Query, error)) *Query { + return nil +} + +func (_ *Query) WhereOr(_ string, _ ...interface{}) *Query { + return nil +} + +func (_ *Query) WhereOrGroup(_ func(*Query) (*Query, error)) *Query { + return nil +} + +func (_ *Query) WhereOrNotGroup(_ func(*Query) (*Query, error)) *Query { + return nil +} + +func (_ *Query) WherePK() *Query { + return nil +} + +func (_ *Query) With(_ string, _ *Query) *Query { + return nil +} + +func (_ *Query) WithDelete(_ string, _ *Query) *Query { + return nil +} + +func (_ *Query) WithInsert(_ string, _ *Query) *Query { + return nil +} + +func (_ *Query) WithUpdate(_ string, _ *Query) *Query { + return nil +} + +func (_ *Query) WrapWith(_ string) *Query { + return nil +} + +type QueryFormatter interface { + FormatQuery(_ []byte, _ string, _ ...interface{}) []byte +} + +type Relation struct { + Type int + Field *Field + JoinTable *Table + BaseFKs []*Field + JoinFKs []*Field + Polymorphic *Field + M2MTableName interface{} + M2MTableAlias interface{} + M2MBaseFKs []string + M2MJoinFKs []string +} + +func (_ *Relation) String() string { + return "" +} + +type Result interface { + Model() Model + RowsAffected() int + RowsReturned() int +} + +type Table struct { + Type reflect.Type + TypeName string + Alias interface{} + ModelName string + SQLName interface{} + SQLNameForSelects interface{} + Tablespace interface{} + PartitionBy string + Fields []*Field + PKs []*Field + DataFields []*Field + FieldsMap map[string]*Field + Methods map[string]*Method + Relations map[string]*Relation + Unique map[string][]*Field + SoftDeleteField *Field + SetSoftDeleteField func(reflect.Value) error +} + +func (_ *Table) AddField(_ *Field) {} + +func (_ *Table) AppendParam(_ []byte, _ reflect.Value, _ string) ([]byte, bool) { + return nil, false +} + +func (_ *Table) GetField(_ string) (*Field, error) { + return nil, nil +} + +func (_ *Table) HasField(_ string) bool { + return false +} + +func (_ *Table) RemoveField(_ *Field) {} + +func (_ *Table) String() string { + return "" +} + +type TableModel interface { + AddColumnScanner(_ ColumnScanner) error + AddJoin(_ interface{}) interface{} + AfterDelete(_ context.Context) error + AfterInsert(_ context.Context) error + AfterScan(_ context.Context) error + AfterSelect(_ context.Context) error + AfterUpdate(_ context.Context) error + AppendParam(_ QueryFormatter, _ []byte, _ string) ([]byte, bool) + BeforeDelete(_ context.Context) (context.Context, error) + BeforeInsert(_ context.Context) (context.Context, error) + BeforeUpdate(_ context.Context) (context.Context, error) + GetJoin(_ string) interface{} + GetJoins() []interface{} + Index() []int + Init() error + IsNil() bool + Join(_ string, _ func(*Query) (*Query, error)) interface{} + Kind() reflect.Kind + Mount(_ reflect.Value) + NextColumnScanner() ColumnScanner + ParentIndex() []int + Relation() *Relation + Root() reflect.Value + Table() *Table + Value() reflect.Value +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/github.com/go-pg/pg/v10/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/github.com/go-pg/pg/v10/stub.go new file mode 100644 index 00000000000..9033e96dd53 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/github.com/go-pg/pg/v10/stub.go @@ -0,0 +1,482 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/go-pg/pg/v10, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/go-pg/pg/v10 (exports: DB; functions: Connect,Scan) + +// Package pg is a stub of github.com/go-pg/pg/v10, generated by depstubber. +package pg + +import ( + context "context" + tls "crypto/tls" + io "io" + net "net" + time "time" + orm "github.com/go-pg/pg/v10/orm" +) + +type Conn struct{} + +func (_ Conn) AddQueryHook(_ QueryHook) {} + +func (_ Conn) Begin() (*Tx, error) { + return nil, nil +} + +func (_ Conn) BeginContext(_ context.Context) (*Tx, error) { + return nil, nil +} + +func (_ Conn) Close() error { + return nil +} + +func (_ Conn) CopyFrom(_ io.Reader, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) CopyTo(_ io.Writer, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) Exec(_ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) ExecContext(_ context.Context, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) ExecOne(_ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) ExecOneContext(_ context.Context, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) Formatter() interface{} { + return nil +} + +func (_ Conn) Model(_ ...interface{}) *orm.Query { + return new(orm.Query) +} + +func (_ Conn) ModelContext(_ context.Context, _ ...interface{}) interface{} { + return nil +} + +func (_ Conn) Param(_ string) interface{} { + return nil +} + +func (_ Conn) Ping(_ context.Context) error { + return nil +} + +func (_ Conn) PoolStats() *PoolStats { + return nil +} + +func (_ Conn) Prepare(_ string) (*Stmt, error) { + return nil, nil +} + +func (_ Conn) Query(_ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) QueryContext(_ context.Context, _ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) QueryOne(_ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) QueryOneContext(_ context.Context, _ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ Conn) RunInTransaction(_ context.Context, _ func(*Tx) error) error { + return nil +} + +func (_ *Conn) Context() context.Context { + return nil +} + +func (_ *Conn) WithContext(_ context.Context) *Conn { + return nil +} + +func (_ *Conn) WithParam(_ string, _ interface{}) *Conn { + return nil +} + +func (_ *Conn) WithTimeout(_ time.Duration) *Conn { + return nil +} + +func Connect(_ *Options) *DB { + return nil +} + +type DB struct{} + +func (_ DB) AddQueryHook(_ QueryHook) {} + +func (_ DB) Begin() (*Tx, error) { + return nil, nil +} + +func (_ DB) BeginContext(_ context.Context) (*Tx, error) { + return nil, nil +} + +func (_ DB) Close() error { + return nil +} + +func (_ DB) CopyFrom(_ io.Reader, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) CopyTo(_ io.Writer, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) Exec(_ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) ExecContext(_ context.Context, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) ExecOne(_ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) ExecOneContext(_ context.Context, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) Formatter() interface{} { + return nil +} + +func (_ DB) Model(_ ...interface{}) *orm.Query { + return new(orm.Query) +} + +func (_ DB) ModelContext(_ context.Context, _ ...interface{}) interface{} { + return nil +} + +func (_ DB) Param(_ string) interface{} { + return nil +} + +func (_ DB) Ping(_ context.Context) error { + return nil +} + +func (_ DB) PoolStats() *PoolStats { + return nil +} + +func (_ DB) Prepare(_ string) (*Stmt, error) { + return nil, nil +} + +func (_ DB) Query(_ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) QueryContext(_ context.Context, _ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) QueryOne(_ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) QueryOneContext(_ context.Context, _ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ DB) RunInTransaction(_ context.Context, _ func(*Tx) error) error { + return nil +} + +func (_ *DB) Conn() *Conn { + return nil +} + +func (_ *DB) Context() context.Context { + return nil +} + +func (_ *DB) Listen(_ context.Context, _ ...string) *Listener { + return nil +} + +func (_ *DB) Options() *Options { + return nil +} + +func (_ *DB) String() string { + return "" +} + +func (_ *DB) WithContext(_ context.Context) *DB { + return nil +} + +func (_ *DB) WithParam(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) WithTimeout(_ time.Duration) *DB { + return nil +} + +type Listener struct{} + +func (_ *Listener) Channel() <-chan Notification { + return nil +} + +func (_ *Listener) ChannelSize(_ int) <-chan Notification { + return nil +} + +func (_ *Listener) Close() error { + return nil +} + +func (_ *Listener) Listen(_ context.Context, _ ...string) error { + return nil +} + +func (_ *Listener) Receive(_ context.Context) (string, string, error) { + return "", "", nil +} + +func (_ *Listener) ReceiveTimeout(_ context.Context, _ time.Duration) (string, string, error) { + return "", "", nil +} + +func (_ *Listener) String() string { + return "" +} + +func (_ *Listener) Unlisten(_ context.Context, _ ...string) error { + return nil +} + +type Notification struct { + Channel string + Payload string +} + +type Options struct { + Network string + Addr string + Dialer func(context.Context, string, string) (net.Conn, error) + OnConnect func(context.Context, *Conn) error + User string + Password string + Database string + ApplicationName string + TLSConfig *tls.Config + DialTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + MaxRetries int + RetryStatementTimeout bool + MinRetryBackoff time.Duration + MaxRetryBackoff time.Duration + PoolSize int + MinIdleConns int + MaxConnAge time.Duration + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration +} + +type PoolStats struct { + Hits uint32 + Misses uint32 + Timeouts uint32 + TotalConns uint32 + IdleConns uint32 + StaleConns uint32 +} + +type QueryEvent struct { + StartTime time.Time + DB interface{} + Model interface{} + Query interface{} + Params []interface{} + Result interface{} + Err error + Stash map[interface{}]interface{} +} + +func (_ *QueryEvent) FormattedQuery() ([]byte, error) { + return nil, nil +} + +func (_ *QueryEvent) UnformattedQuery() ([]byte, error) { + return nil, nil +} + +type QueryHook interface { + AfterQuery(_ context.Context, _ *QueryEvent) error + BeforeQuery(_ context.Context, _ *QueryEvent) (context.Context, error) +} + +func Scan(_ ...interface{}) interface{} { + return nil +} + +type Stmt struct{} + +func (_ *Stmt) Close() error { + return nil +} + +func (_ *Stmt) Exec(_ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Stmt) ExecContext(_ context.Context, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Stmt) ExecOne(_ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Stmt) ExecOneContext(_ context.Context, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Stmt) Query(_ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Stmt) QueryContext(_ context.Context, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Stmt) QueryOne(_ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Stmt) QueryOneContext(_ context.Context, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +type Tx struct{} + +func (_ *Tx) Begin() (*Tx, error) { + return nil, nil +} + +func (_ *Tx) Close() error { + return nil +} + +func (_ *Tx) CloseContext(_ context.Context) error { + return nil +} + +func (_ *Tx) Commit() error { + return nil +} + +func (_ *Tx) CommitContext(_ context.Context) error { + return nil +} + +func (_ *Tx) Context() context.Context { + return nil +} + +func (_ *Tx) CopyFrom(_ io.Reader, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) CopyTo(_ io.Writer, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) Exec(_ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) ExecContext(_ context.Context, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) ExecOne(_ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) ExecOneContext(_ context.Context, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) Formatter() interface{} { + return nil +} + +func (_ *Tx) Model(_ ...interface{}) interface{} { + return nil +} + +func (_ *Tx) ModelContext(_ context.Context, _ ...interface{}) interface{} { + return nil +} + +func (_ *Tx) Prepare(_ string) (*Stmt, error) { + return nil, nil +} + +func (_ *Tx) Query(_ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) QueryContext(_ context.Context, _ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) QueryOne(_ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) QueryOneContext(_ context.Context, _ interface{}, _ interface{}, _ ...interface{}) (interface{}, error) { + return nil, nil +} + +func (_ *Tx) Rollback() error { + return nil +} + +func (_ *Tx) RollbackContext(_ context.Context) error { + return nil +} + +func (_ *Tx) RunInTransaction(_ context.Context, _ func(*Tx) error) error { + return nil +} + +func (_ *Tx) Stmt(_ *Stmt) *Stmt { + return nil +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/modules.txt b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/modules.txt new file mode 100644 index 00000000000..88b159e0992 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go-pg/vendor/modules.txt @@ -0,0 +1,39 @@ +# github.com/go-pg/pg/v10 v10.11.0 +## explicit +github.com/go-pg/pg/v10 +# github.com/go-pg/zerochecker v0.2.0 +## explicit +github.com/go-pg/zerochecker +# github.com/jinzhu/inflection v1.0.0 +## explicit +github.com/jinzhu/inflection +# github.com/kr/text v0.2.0 +## explicit +github.com/kr/text +# github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc +## explicit +github.com/tmthrgd/go-hex +# github.com/vmihailenco/bufpool v0.1.11 +## explicit +github.com/vmihailenco/bufpool +# github.com/vmihailenco/msgpack/v5 v5.3.4 +## explicit +github.com/vmihailenco/msgpack/v5 +# github.com/vmihailenco/tagparser v0.1.2 +## explicit +github.com/vmihailenco/tagparser +# github.com/vmihailenco/tagparser/v2 v2.0.0 +## explicit +github.com/vmihailenco/tagparser/v2 +# golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 +## explicit +golang.org/x/crypto +# golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 +## explicit +golang.org/x/sys +# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 +## explicit +golang.org/x/xerrors +# mellium.im/sasl v0.3.1 +## explicit +mellium.im/sasl diff --git a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go.mod b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go.mod index 69db5c96c41..d6d79cd4a53 100644 --- a/go/ql/test/library-tests/semmle/go/frameworks/SQL/go.mod +++ b/go/ql/test/library-tests/semmle/go/frameworks/SQL/go.mod @@ -8,7 +8,10 @@ require ( github.com/go-pg/pg/v9 v9.1.3 github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/go-xorm/xorm v0.7.9 + github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.2 // indirect - github.com/mattn/go-sqlite3 v1.14.7 // indirect + github.com/uptrace/bun v1.1.14 + github.com/uptrace/bun/dialect/sqlitedialect v1.1.14 + github.com/uptrace/bun/driver/sqliteshim v1.1.14 xorm.io/xorm v1.1.0 ) diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/go.mod b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/go.mod new file mode 100644 index 00000000000..ca7f6d16ac3 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/go.mod @@ -0,0 +1,29 @@ +module pwntester/gqlgen-todos + +go 1.19 + +require ( + github.com/99designs/gqlgen v0.17.3 + github.com/vektah/gqlparser/v2 v2.5.4 +) + +require ( + github.com/agnivade/levenshtein v1.1.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/golang-lru v0.5.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/matryer/moq v0.2.3 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/stretchr/testify v1.6.0 // indirect + github.com/urfave/cli/v2 v2.25.5 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/mod v0.6.0-dev // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/tools v0.1.9 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/gqlgen.expected b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/gqlgen.expected new file mode 100644 index 00000000000..48de9172b36 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/gqlgen.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/gqlgen.ql b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/gqlgen.ql new file mode 100644 index 00000000000..693465d8e33 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/gqlgen.ql @@ -0,0 +1,18 @@ +import go +import TestUtilities.InlineExpectationsTest + +module ResolveParameterTest implements TestSig { + string getARelevantTag() { result = "resolverParameter" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "resolverParameter" and + exists(Gqlgen::ResolverParameter p | + element = p.toString() and + value = "\"" + p.toString() + "\"" and + p.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), + location.getStartColumn(), location.getEndLine(), location.getEndColumn()) + ) + } +} + +import MakeTest diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/generated.go b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/generated.go new file mode 100644 index 00000000000..7cbcec1d891 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/generated.go @@ -0,0 +1,28 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package graph + +import ( + "context" + "pwntester/gqlgen-todos/graph/model" + + "github.com/99designs/gqlgen/graphql" +) + +type ResolverRoot interface { + Mutation() MutationResolver + Query() QueryResolver +} + +type MutationResolver interface { + CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) +} +type QueryResolver interface { + Todos(ctx context.Context) ([]*model.Todo, error) +} + +func stub(dg graphql.CollectedField) { + dg.GetPosition() +} + +// endregion ***************************** type.gotpl ***************************** diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/model/models_gen.go b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/model/models_gen.go new file mode 100644 index 00000000000..70ebe4fc538 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/model/models_gen.go @@ -0,0 +1,20 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package model + +type NewTodo struct { + Text string `json:"text"` + UserID string `json:"userId"` +} + +type Todo struct { + ID string `json:"id"` + Text string `json:"text"` + Done bool `json:"done"` + User *User `json:"user"` +} + +type User struct { + ID string `json:"id"` + Name string `json:"name"` +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/resolver.go b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/resolver.go new file mode 100644 index 00000000000..a25c09c619f --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/resolver.go @@ -0,0 +1,7 @@ +package graph + +// This file will not be regenerated automatically. +// +// It serves as dependency injection for your app, add any dependencies you require here. + +type Resolver struct{} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/schema.graphqls b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/schema.graphqls new file mode 100644 index 00000000000..c6a91bb4808 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/schema.graphqls @@ -0,0 +1,28 @@ +# GraphQL schema example +# +# https://gqlgen.com/getting-started/ + +type Todo { + id: ID! + text: String! + done: Boolean! + user: User! +} + +type User { + id: ID! + name: String! +} + +type Query { + todos: [Todo!]! +} + +input NewTodo { + text: String! + userId: String! +} + +type Mutation { + createTodo(input: NewTodo!): Todo! +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/schema.resolvers.go b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/schema.resolvers.go new file mode 100644 index 00000000000..c22fb450e74 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/graph/schema.resolvers.go @@ -0,0 +1,30 @@ +package graph + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.34 + +import ( + "context" + "fmt" + "pwntester/gqlgen-todos/graph/model" +) + +// CreateTodo is the resolver for the createTodo field. +func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { // $ resolverParameter="definition of input" + panic(fmt.Errorf("not implemented: CreateTodo - createTodo %v", input)) +} + +// Todos is the resolver for the todos field. +func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) { + panic(fmt.Errorf("not implemented: Todos - todos")) +} + +// Mutation returns MutationResolver implementation. +func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } + +// Query returns QueryResolver implementation. +func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } + +type mutationResolver struct{ *Resolver } +type queryResolver struct{ *Resolver } diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/github.com/99designs/gqlgen/graphql/stub.go b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/github.com/99designs/gqlgen/graphql/stub.go new file mode 100644 index 00000000000..122a32adee1 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/github.com/99designs/gqlgen/graphql/stub.go @@ -0,0 +1,25 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/99designs/gqlgen/graphql, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/99designs/gqlgen/graphql (exports: CollectedField; functions: ) + +// Package graphql is a stub of github.com/99designs/gqlgen/graphql, generated by depstubber. +package graphql + +type CollectedField struct { + Field interface{} + Selections interface{} +} + +func (_ CollectedField) ArgumentMap(_ map[string]interface{}) map[string]interface{} { + return nil +} + +func (_ CollectedField) GetPosition() interface{} { + return nil +} + +func (_ CollectedField) UnmarshalJSON(_ []byte) error { + return nil +} diff --git a/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/modules.txt b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/modules.txt new file mode 100644 index 00000000000..aae61d6dd09 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/modules.txt @@ -0,0 +1,60 @@ +# github.com/99designs/gqlgen v0.17.3 +## explicit +github.com/99designs/gqlgen +# github.com/vektah/gqlparser/v2 v2.5.4 +## explicit +github.com/vektah/gqlparser/v2 +# github.com/agnivade/levenshtein v1.1.1 +## explicit +github.com/agnivade/levenshtein +# github.com/cpuguy83/go-md2man/v2 v2.0.2 +## explicit +github.com/cpuguy83/go-md2man/v2 +# github.com/gorilla/websocket v1.5.0 +## explicit +github.com/gorilla/websocket +# github.com/hashicorp/golang-lru v0.5.0 +## explicit +github.com/hashicorp/golang-lru +# github.com/kr/text v0.2.0 +## explicit +github.com/kr/text +# github.com/matryer/moq v0.2.3 +## explicit +github.com/matryer/moq +# github.com/mitchellh/mapstructure v1.5.0 +## explicit +github.com/mitchellh/mapstructure +# github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e +## explicit +github.com/niemeyer/pretty +# github.com/russross/blackfriday/v2 v2.1.0 +## explicit +github.com/russross/blackfriday/v2 +# github.com/stretchr/testify v1.6.0 +## explicit +github.com/stretchr/testify +# github.com/urfave/cli/v2 v2.25.5 +## explicit +github.com/urfave/cli/v2 +# github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 +## explicit +github.com/xrash/smetrics +# golang.org/x/mod v0.6.0-dev +## explicit +golang.org/x/mod +# golang.org/x/sys v0.8.0 +## explicit +golang.org/x/sys +# golang.org/x/tools v0.1.9 +## explicit +golang.org/x/tools +# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 +## explicit +golang.org/x/xerrors +# gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f +## explicit +gopkg.in/check.v1 +# gopkg.in/yaml.v2 v2.4.0 +## explicit +gopkg.in/yaml.v2 diff --git a/java/documentation/library-coverage/coverage.csv b/java/documentation/library-coverage/coverage.csv index bf73577e7cc..1681b511d92 100644 --- a/java/documentation/library-coverage/coverage.csv +++ b/java/documentation/library-coverage/coverage.csv @@ -55,12 +55,12 @@ jakarta.ws.rs.container,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,, jakarta.ws.rs.core,2,,149,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,94,55 java.awt,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3 java.beans,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -java.io,49,,45,,,22,,,,,,,,,,,,,,27,,,,,,,,,,,,,,,,,,,43,2 -java.lang,31,,92,,13,,,,,,,,,,,,8,,,5,,,4,,,1,,,,,,,,,,,,,56,36 -java.net,13,3,21,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,3,21, -java.nio,47,,35,,,3,,,,,,,,,,,,,,44,,,,,,,,,,,,,,,,,,,35, +java.io,50,,45,,,22,,,,,,,,,,,,,,28,,,,,,,,,,,,,,,,,,,43,2 +java.lang,31,,93,,13,,,,,,,,,,,,8,,,5,,,4,,,1,,,,,,,,,,,,,57,36 +java.net,13,3,23,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,3,23, +java.nio,53,,36,,,5,,,,,,,,,,,,,,47,,,,,,,,,1,,,,,,,,,,36, java.sql,13,,2,,,,,,,,,,,,,,,,,,,,,,,,,,4,,9,,,,,,,,2, -java.util,44,,484,,,,,,,,,,,,,,34,,,,,,,5,2,,1,2,,,,,,,,,,,44,440 +java.util,45,,485,,,1,,,,,,,,,,,34,,,,,,,5,2,,1,2,,,,,,,,,,,45,440 javafx.scene.web,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, javax.faces.context,2,7,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,, javax.imageio.stream,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, @@ -80,11 +80,11 @@ javax.xml.transform,2,,6,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,1,,,,6, javax.xml.xpath,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,, jenkins,,,446,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,423,23 jodd.json,,,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10 -kotlin,16,,1847,,,,,,,,,,,,,,,,,14,,,,,,,,,2,,,,,,,,,,1836,11 +kotlin,16,,1849,,,,,,,,,,,,,,,,,14,,,,,,,,,2,,,,,,,,,,1836,13 net.sf.json,2,,338,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,321,17 net.sf.saxon.s9api,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,, ognl,6,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,, -okhttp3,4,,48,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,23,25 +okhttp3,4,,50,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,23,27 org.acegisecurity,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,49, org.antlr.runtime,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,, org.apache.commons.codec,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6, @@ -98,7 +98,7 @@ org.apache.commons.jelly,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,, org.apache.commons.jexl2,15,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,, org.apache.commons.jexl3,15,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,, org.apache.commons.lang,,,765,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,594,171 -org.apache.commons.lang3,6,,424,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,293,131 +org.apache.commons.lang3,6,,425,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,294,131 org.apache.commons.logging,6,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,, org.apache.commons.net,9,12,,,,,,,,,,,,,,,,,,3,,,,,,,,,6,,,,,,,,,12,, org.apache.commons.ognl,6,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,, @@ -131,6 +131,7 @@ org.dom4j,20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,20,,,,,, org.eclipse.jetty.client,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, org.fusesource.leveldbjni,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,, org.geogebra.web.full.main,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,, +org.gradle.api.file,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2, org.hibernate,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,,,,,,,, org.influxdb,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, org.jboss.logging,324,,,,,,,,,,,,,,,,324,,,,,,,,,,,,,,,,,,,,,,, @@ -180,4 +181,4 @@ ratpack.func,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35 ratpack.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4, ratpack.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10, ratpack.util,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35 -retrofit2,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, +retrofit2,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,1, diff --git a/java/documentation/library-coverage/coverage.rst b/java/documentation/library-coverage/coverage.rst index c8831af7a5b..d07f04c3897 100644 --- a/java/documentation/library-coverage/coverage.rst +++ b/java/documentation/library-coverage/coverage.rst @@ -11,17 +11,17 @@ Java framework & library support Android extensions,``androidx.*``,5,183,19,,,,,, `Apache Commons Collections `_,"``org.apache.commons.collections``, ``org.apache.commons.collections4``",,1600,,,,,,, `Apache Commons IO `_,``org.apache.commons.io``,,560,111,94,,,,,15 - `Apache Commons Lang `_,``org.apache.commons.lang3``,,424,6,,,,,, + `Apache Commons Lang `_,``org.apache.commons.lang3``,,425,6,,,,,, `Apache Commons Text `_,``org.apache.commons.text``,,272,,,,,,, `Apache HttpComponents `_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,182,122,,3,,,,119 `Apache Log4j 2 `_,``org.apache.logging.log4j``,,8,359,,,,,, `Google Guava `_,``com.google.common.*``,,730,41,7,,,,, JBoss Logging,``org.jboss.logging``,,,324,,,,,, `JSON-java `_,``org.json``,,236,,,,,,, - Java Standard Library,``java.*``,3,683,197,76,,9,,,17 + Java Standard Library,``java.*``,3,688,205,80,,9,,,18 Java extensions,"``javax.*``, ``jakarta.*``",63,672,34,2,4,,1,1,2 - Kotlin Standard Library,``kotlin*``,,1847,16,14,,,,,2 + Kotlin Standard Library,``kotlin*``,,1849,16,14,,,,,2 `Spring `_,``org.springframework.*``,29,483,115,4,,28,14,,35 - Others,"``antlr``, ``cn.hutool.core.codec``, ``com.alibaba.druid.sql``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.google.gson``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.thoughtworks.xstream``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``freemarker.cache``, ``freemarker.template``, ``groovy.lang``, ``groovy.text``, ``groovy.util``, ``hudson``, ``io.jsonwebtoken``, ``io.netty.bootstrap``, ``io.netty.buffer``, ``io.netty.channel``, ``io.netty.handler.codec``, ``io.netty.handler.ssl``, ``io.netty.handler.stream``, ``io.netty.resolver``, ``io.netty.util``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.apache.velocity.app``, ``org.apache.velocity.runtime``, ``org.codehaus.cargo.container.installer``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.eclipse.jetty.client``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.hibernate``, ``org.influxdb``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.jooq``, ``org.kohsuke.stapler``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.scijava.log``, ``org.slf4j``, ``org.thymeleaf``, ``org.xml.sax``, ``org.xmlpull.v1``, ``org.yaml.snakeyaml``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",126,5232,577,89,6,18,18,,200 - Totals,,283,13593,2059,286,16,122,33,1,390 + Others,"``antlr``, ``cn.hutool.core.codec``, ``com.alibaba.druid.sql``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.google.gson``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.thoughtworks.xstream``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``freemarker.cache``, ``freemarker.template``, ``groovy.lang``, ``groovy.text``, ``groovy.util``, ``hudson``, ``io.jsonwebtoken``, ``io.netty.bootstrap``, ``io.netty.buffer``, ``io.netty.channel``, ``io.netty.handler.codec``, ``io.netty.handler.ssl``, ``io.netty.handler.stream``, ``io.netty.resolver``, ``io.netty.util``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.apache.velocity.app``, ``org.apache.velocity.runtime``, ``org.codehaus.cargo.container.installer``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.eclipse.jetty.client``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.hibernate``, ``org.influxdb``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.jooq``, ``org.kohsuke.stapler``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.scijava.log``, ``org.slf4j``, ``org.thymeleaf``, ``org.xml.sax``, ``org.xmlpull.v1``, ``org.yaml.snakeyaml``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",126,5237,577,89,6,18,18,,200 + Totals,,283,13606,2067,290,16,122,33,1,391 diff --git a/java/kotlin-extractor/kotlin_plugin_versions.py b/java/kotlin-extractor/kotlin_plugin_versions.py index bf1c211073a..ea487b4e6cc 100755 --- a/java/kotlin-extractor/kotlin_plugin_versions.py +++ b/java/kotlin-extractor/kotlin_plugin_versions.py @@ -22,7 +22,7 @@ def version_string_to_tuple(version): return tuple([int(m.group(i)) for i in range(1, 4)] + [m.group(4)]) # Version number used by CI. -ci_version = '1.8.10' +ci_version = '1.9.0' many_versions = [ '1.4.32', '1.5.0', '1.5.10', '1.5.20', '1.5.30', '1.6.0', '1.6.20', '1.7.0', '1.7.20', '1.8.0', '1.9.0-Beta' ] diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 6e5d921a406..aa0a137173b 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -643,6 +643,10 @@ open class KotlinFileExtractor( logger.warnElement("Unrecognised class kind $kind", c) } + if (c.origin == IrDeclarationOrigin.FILE_CLASS) { + tw.writeFile_class(id) + } + if (c.isData) { tw.writeKtDataClasses(id) } diff --git a/java/ql/integration-tests/all-platforms/kotlin/file_classes/A.kt b/java/ql/integration-tests/all-platforms/kotlin/file_classes/A.kt new file mode 100644 index 00000000000..8ece2422c02 --- /dev/null +++ b/java/ql/integration-tests/all-platforms/kotlin/file_classes/A.kt @@ -0,0 +1,2 @@ +fun a() { +} diff --git a/java/ql/integration-tests/all-platforms/kotlin/file_classes/B.kt b/java/ql/integration-tests/all-platforms/kotlin/file_classes/B.kt new file mode 100644 index 00000000000..0f1fb9d6fd8 --- /dev/null +++ b/java/ql/integration-tests/all-platforms/kotlin/file_classes/B.kt @@ -0,0 +1,3 @@ +fun b() { + a() +} diff --git a/java/ql/integration-tests/all-platforms/kotlin/file_classes/C.kt b/java/ql/integration-tests/all-platforms/kotlin/file_classes/C.kt new file mode 100644 index 00000000000..2b433c8f2f6 --- /dev/null +++ b/java/ql/integration-tests/all-platforms/kotlin/file_classes/C.kt @@ -0,0 +1,3 @@ +class C { + fun c() {} +} diff --git a/java/ql/integration-tests/all-platforms/kotlin/file_classes/classes.expected b/java/ql/integration-tests/all-platforms/kotlin/file_classes/classes.expected new file mode 100644 index 00000000000..ee1e95c031c --- /dev/null +++ b/java/ql/integration-tests/all-platforms/kotlin/file_classes/classes.expected @@ -0,0 +1,3 @@ +| AKt.class:0:0:0:0 | AKt | true | +| B.kt:0:0:0:0 | BKt | true | +| C.kt:1:1:3:1 | C | false | diff --git a/java/ql/integration-tests/all-platforms/kotlin/file_classes/classes.ql b/java/ql/integration-tests/all-platforms/kotlin/file_classes/classes.ql new file mode 100644 index 00000000000..74dad88a095 --- /dev/null +++ b/java/ql/integration-tests/all-platforms/kotlin/file_classes/classes.ql @@ -0,0 +1,5 @@ +import java + +from Class c +where exists(c.getLocation().getFile().getRelativePath()) +select c, any(boolean b | if c.isFileClass() then b = true else b = false) diff --git a/java/ql/integration-tests/all-platforms/kotlin/file_classes/test.py b/java/ql/integration-tests/all-platforms/kotlin/file_classes/test.py new file mode 100644 index 00000000000..fbc4101be04 --- /dev/null +++ b/java/ql/integration-tests/all-platforms/kotlin/file_classes/test.py @@ -0,0 +1,4 @@ +from create_database_utils import * + +runSuccessfully([get_cmd("kotlinc"), 'A.kt']) +run_codeql_database_create(['kotlinc -cp . B.kt C.kt'], lang="java") diff --git a/java/ql/lib/CHANGELOG.md b/java/ql/lib/CHANGELOG.md index 77961e193da..b6315cf80e4 100644 --- a/java/ql/lib/CHANGELOG.md +++ b/java/ql/lib/CHANGELOG.md @@ -1,3 +1,32 @@ +## 0.7.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. +* A `Class.isFileClass()` predicate, to identify Kotlin file classes, has been added. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* Added models for Apache Commons Lang3 `ToStringBuilder.reflectionToString` method. +* Added support for the Kotlin method `apply`. +* Added models for the following packages: + + * java.io + * java.lang + * java.net + * java.nio.channels + * java.nio.file + * java.util.zip + * okhttp3 + * org.gradle.api.file + * retrofit2 + ## 0.7.0 ### Deprecated APIs diff --git a/java/ql/lib/change-notes/released/0.7.1.md b/java/ql/lib/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..7501e3a4b28 --- /dev/null +++ b/java/ql/lib/change-notes/released/0.7.1.md @@ -0,0 +1,28 @@ +## 0.7.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. +* A `Class.isFileClass()` predicate, to identify Kotlin file classes, has been added. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* Added models for Apache Commons Lang3 `ToStringBuilder.reflectionToString` method. +* Added support for the Kotlin method `apply`. +* Added models for the following packages: + + * java.io + * java.lang + * java.net + * java.nio.channels + * java.nio.file + * java.util.zip + * okhttp3 + * org.gradle.api.file + * retrofit2 diff --git a/java/ql/lib/codeql-pack.release.yml b/java/ql/lib/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/java/ql/lib/codeql-pack.release.yml +++ b/java/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/java/ql/lib/ext/android.webkit.model.yml b/java/ql/lib/ext/android.webkit.model.yml index d88199c04cb..bb375507194 100644 --- a/java/ql/lib/ext/android.webkit.model.yml +++ b/java/ql/lib/ext/android.webkit.model.yml @@ -3,13 +3,13 @@ extensions: pack: codeql/java-all extensible: sourceModel data: - - ["android.webkit", "WebView", False, "getOriginalUrl", "()", "", "ReturnValue", "remote", "manual"] - - ["android.webkit", "WebView", False, "getUrl", "()", "", "ReturnValue", "remote", "manual"] + - ["android.webkit", "WebView", True, "getOriginalUrl", "()", "", "ReturnValue", "remote", "manual"] + - ["android.webkit", "WebView", True, "getUrl", "()", "", "ReturnValue", "remote", "manual"] - addsTo: pack: codeql/java-all extensible: sinkModel data: # Models representing methods susceptible to XSS attacks. - - ["android.webkit", "WebView", False, "evaluateJavascript", "", "", "Argument[0]", "js-injection", "manual"] - - ["android.webkit", "WebView", False, "loadData", "", "", "Argument[0]", "html-injection", "manual"] - - ["android.webkit", "WebView", False, "loadDataWithBaseURL", "", "", "Argument[1]", "html-injection", "manual"] + - ["android.webkit", "WebView", True, "evaluateJavascript", "", "", "Argument[0]", "js-injection", "manual"] + - ["android.webkit", "WebView", True, "loadData", "", "", "Argument[0]", "html-injection", "manual"] + - ["android.webkit", "WebView", True, "loadDataWithBaseURL", "", "", "Argument[1]", "html-injection", "manual"] diff --git a/java/ql/lib/ext/java.io.model.yml b/java/ql/lib/ext/java.io.model.yml index 83e57a68c74..98c51a7bad5 100644 --- a/java/ql/lib/ext/java.io.model.yml +++ b/java/ql/lib/ext/java.io.model.yml @@ -7,6 +7,7 @@ extensions: - ["java.io", "File", False, "File", "(String)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation - ["java.io", "File", False, "File", "(String,String)", "", "Argument[0..1]", "path-injection", "manual"] # old PathCreation - ["java.io", "File", False, "File", "(URI)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation + - ["java.io", "File", True, "createNewFile", "()", "", "Argument[this]", "path-injection", "ai-manual"] - ["java.io", "File", True, "createTempFile", "(String,String,File)", "", "Argument[2]", "path-injection", "ai-manual"] - ["java.io", "File", True, "renameTo", "(File)", "", "Argument[0]", "path-injection", "ai-manual"] - ["java.io", "FileInputStream", True, "FileInputStream", "(File)", "", "Argument[0]", "path-injection", "ai-manual"] @@ -123,7 +124,6 @@ extensions: - ["java.io", "DataInput", "readLong", "()", "summary", "manual"] # taint-numeric - ["java.io", "DataOutput", "writeInt", "(int)", "summary", "manual"] # taint-numeric - ["java.io", "DataOutput", "writeLong", "(long)", "summary", "manual"] # taint-numeric - # sink neutrals - ["java.io", "File", "compareTo", "", "sink", "hq-manual"] - ["java.io", "File", "exists", "()", "sink", "hq-manual"] diff --git a/java/ql/lib/ext/java.lang.model.yml b/java/ql/lib/ext/java.lang.model.yml index 012fb65baab..817683836fb 100644 --- a/java/ql/lib/ext/java.lang.model.yml +++ b/java/ql/lib/ext/java.lang.model.yml @@ -47,6 +47,7 @@ extensions: - ["java.lang", "AbstractStringBuilder", True, "AbstractStringBuilder", "(String)", "", "Argument[0]", "Argument[this]", "taint", "manual"] - ["java.lang", "AbstractStringBuilder", True, "append", "", "", "Argument[this]", "ReturnValue", "value", "manual"] - ["java.lang", "AbstractStringBuilder", True, "append", "", "", "Argument[0]", "Argument[this]", "taint", "manual"] + - ["java.lang", "ProcessBuilder", False, "environment", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"] # When `WithoutElement` is implemented for Java, `java.lang.AbstractStringBuilder#delete` might require a `taint` step of the form `Argument[this].WithoutElement -> Argument[this]` in addition to the below `value` step. - ["java.lang", "AbstractStringBuilder", True, "delete", "(int,int)", "", "Argument[this]", "ReturnValue", "value", "manual"] - ["java.lang", "AbstractStringBuilder", True, "getChars", "", "", "Argument[this]", "Argument[2]", "taint", "manual"] @@ -136,7 +137,6 @@ extensions: - ["java.lang", "Throwable", True, "getLocalizedMessage", "()", "", "Argument[this].SyntheticField[java.lang.Throwable.message]", "ReturnValue", "value", "manual"] - ["java.lang", "Throwable", True, "toString", "()", "", "Argument[this].SyntheticField[java.lang.Throwable.message]", "ReturnValue", "taint", "manual"] - ["java.lang", "UnsupportedOperationException", False, "UnsupportedOperationException", "(String)", "", "Argument[0]", "Argument[this].SyntheticField[java.lang.Throwable.message]", "value", "manual"] - - addsTo: pack: codeql/java-all extensible: neutralModel diff --git a/java/ql/lib/ext/java.net.model.yml b/java/ql/lib/ext/java.net.model.yml index 24591459432..a3bc92dc7b3 100644 --- a/java/ql/lib/ext/java.net.model.yml +++ b/java/ql/lib/ext/java.net.model.yml @@ -43,6 +43,8 @@ extensions: - ["java.net", "URI", False, "toASCIIString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["java.net", "URI", False, "toString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["java.net", "URI", False, "toURL", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] + - ["java.net", "URL", False, "getFile", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"] + - ["java.net", "URL", False, "getPath", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"] - ["java.net", "URL", False, "URL", "(String)", "", "Argument[0]", "Argument[this]", "taint", "manual"] - ["java.net", "URL", False, "URL", "(URL,String)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"] - ["java.net", "URL", False, "URL", "(URL,String)", "", "Argument[1]", "Argument[this]", "taint", "ai-manual"] diff --git a/java/ql/lib/ext/java.nio.channels.model.yml b/java/ql/lib/ext/java.nio.channels.model.yml index c4ba9a77a05..f82d224ca24 100644 --- a/java/ql/lib/ext/java.nio.channels.model.yml +++ b/java/ql/lib/ext/java.nio.channels.model.yml @@ -5,3 +5,11 @@ extensions: data: - ["java.nio.channels", "Channels", False, "newChannel", "(InputStream)", "", "Argument[0]", "ReturnValue", "taint", "manual"] - ["java.nio.channels", "ReadableByteChannel", True, "read", "(ByteBuffer)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - addsTo: + pack: codeql/java-all + extensible: sinkModel + data: + - ["java.nio.channels", "FileChannel", False, "open", "(Path,OpenOption[])", "", "Argument[0]", "path-injection", "ai-manual"] + - ["java.nio.channels", "FileChannel", False, "open", "(Path,Set,FileAttribute[])", "", "Argument[0]", "path-injection", "ai-manual"] + - ["java.nio.channels", "FileChannel", True, "write", "(ByteBuffer,long)", "", "Argument[0]", "file-content-store", "ai-manual"] + - ["java.nio.channels", "FileChannel", True, "write", "(ByteBuffer)", "", "Argument[0]", "file-content-store", "ai-manual"] diff --git a/java/ql/lib/ext/java.nio.file.model.yml b/java/ql/lib/ext/java.nio.file.model.yml index 5e3f32b5e6f..7b151739a07 100644 --- a/java/ql/lib/ext/java.nio.file.model.yml +++ b/java/ql/lib/ext/java.nio.file.model.yml @@ -43,6 +43,8 @@ extensions: - ["java.nio.file", "Files", True, "newInputStream", "(Path,OpenOption[])", "", "Argument[0]", "path-injection", "ai-manual"] - ["java.nio.file", "Files", True, "newOutputStream", "(Path,OpenOption[])", "", "Argument[0]", "path-injection", "ai-manual"] - ["java.nio.file", "FileSystem", False, "getPath", "", "", "Argument[0..1]", "path-injection", "manual"] # old PathCreation + - ["java.nio.file", "FileSystems", False, "newFileSystem", "(URI,Map)", "", "Argument[0]", "path-injection", "ai-manual"] + - ["java.nio.file", "FileSystems", False, "newFileSystem", "(URI,Map)", "", "Argument[0]", "request-forgery", "ai-manual"] - ["java.nio.file", "Path", False, "of", "(String,String[])", "", "Argument[0..1]", "path-injection", "manual"] # old PathCreation - ["java.nio.file", "Path", False, "of", "(URI)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation - ["java.nio.file", "Path", False, "resolve", "(String)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation @@ -79,6 +81,7 @@ extensions: - ["java.nio.file", "Path", True, "relativize", "(Path)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"] - ["java.nio.file", "Path", True, "resolve", "", "", "Argument[0]", "ReturnValue", "taint", "manual"] - ["java.nio.file", "Path", True, "resolve", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] + - ["java.nio.file", "Path", True, "resolveSibling", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"] - ["java.nio.file", "Path", True, "toAbsolutePath", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["java.nio.file", "Path", False, "toFile", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["java.nio.file", "Path", True, "toString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] @@ -96,7 +99,6 @@ extensions: data: # summary neutrals - ["java.nio.file", "Files", "exists", "(Path,LinkOption[])", "summary", "manual"] - # sink neutrals - ["java.nio.file", "Files", "exists", "", "sink", "hq-manual"] - ["java.nio.file", "Files", "getLastModifiedTime", "", "sink", "hq-manual"] diff --git a/java/ql/lib/ext/java.util.zip.model.yml b/java/ql/lib/ext/java.util.zip.model.yml index 8e741f98c24..577e6b35723 100644 --- a/java/ql/lib/ext/java.util.zip.model.yml +++ b/java/ql/lib/ext/java.util.zip.model.yml @@ -4,4 +4,10 @@ extensions: extensible: summaryModel data: - ["java.util.zip", "GZIPInputStream", False, "GZIPInputStream", "", "", "Argument[0]", "Argument[this]", "taint", "manual"] + - ["java.util.zip", "ZipEntry", True, "ZipEntry", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"] - ["java.util.zip", "ZipInputStream", False, "ZipInputStream", "", "", "Argument[0]", "Argument[this]", "taint", "manual"] + - addsTo: + pack: codeql/java-all + extensible: sinkModel + data: + - ["java.util.zip", "ZipOutputStream", True, "putNextEntry", "(ZipEntry)", "", "Argument[0]", "file-content-store", "ai-manual"] diff --git a/java/ql/lib/ext/kotlin.model.yml b/java/ql/lib/ext/kotlin.model.yml index ea275a78515..0ae6a66f9f8 100644 --- a/java/ql/lib/ext/kotlin.model.yml +++ b/java/ql/lib/ext/kotlin.model.yml @@ -3,5 +3,7 @@ extensions: pack: codeql/java-all extensible: summaryModel data: + - ["kotlin", "StandardKt", False, "apply", "", "", "Argument[0]", "Argument[1].Parameter[0]", "value", "manual"] + - ["kotlin", "StandardKt", False, "apply", "", "", "Argument[0]", "ReturnValue", "value", "manual"] - ["kotlin", "StandardKt", False, "with", "", "", "Argument[0]", "Argument[1].Parameter[0]", "value", "manual"] - ["kotlin", "StandardKt", False, "with", "", "", "Argument[1].ReturnValue", "ReturnValue", "value", "manual"] diff --git a/java/ql/lib/ext/okhttp3.model.yml b/java/ql/lib/ext/okhttp3.model.yml index a0662408708..7b24c67975b 100644 --- a/java/ql/lib/ext/okhttp3.model.yml +++ b/java/ql/lib/ext/okhttp3.model.yml @@ -58,4 +58,6 @@ extensions: - ["okhttp3", "HttpUrl$Builder", False, "setQueryParameter", "", "", "Argument[this]", "ReturnValue", "value", "manual"] - ["okhttp3", "HttpUrl$Builder", False, "setQueryParameter", "", "", "Argument[0]", "Argument[this]", "taint", "manual"] - ["okhttp3", "HttpUrl$Builder", False, "username", "", "", "Argument[this]", "ReturnValue", "value", "manual"] + - ["okhttp3", "Request$Builder", False, "get", "()", "", "Argument[this]", "ReturnValue", "value", "ai-manual"] + - ["okhttp3", "Request$Builder", False, "url", "(String)", "", "Argument[this]", "ReturnValue", "value", "ai-manual"] - ["okhttp3", "Request$Builder", True, "build", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"] diff --git a/java/ql/lib/ext/org.apache.commons.lang3.builder.model.yml b/java/ql/lib/ext/org.apache.commons.lang3.builder.model.yml index 654eb626d84..8bb0de9bb1a 100644 --- a/java/ql/lib/ext/org.apache.commons.lang3.builder.model.yml +++ b/java/ql/lib/ext/org.apache.commons.lang3.builder.model.yml @@ -19,4 +19,5 @@ extensions: - ["org.apache.commons.lang3.builder", "ToStringBuilder", False, "appendToString", "", "", "Argument[0]", "Argument[this]", "taint", "manual"] - ["org.apache.commons.lang3.builder", "ToStringBuilder", False, "build", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["org.apache.commons.lang3.builder", "ToStringBuilder", False, "getStringBuffer", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] + - ["org.apache.commons.lang3.builder", "ToStringBuilder", False, "reflectionToString", "", "", "Argument[0]", "ReturnValue", "taint", "manual"] - ["org.apache.commons.lang3.builder", "ToStringBuilder", False, "toString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"] diff --git a/java/ql/lib/ext/org.gradle.api.file.model.yml b/java/ql/lib/ext/org.gradle.api.file.model.yml new file mode 100644 index 00000000000..4f492cdbcbc --- /dev/null +++ b/java/ql/lib/ext/org.gradle.api.file.model.yml @@ -0,0 +1,7 @@ +extensions: + - addsTo: + pack: codeql/java-all + extensible: summaryModel + data: + - ["org.gradle.api.file", "Directory", True, "getAsFile", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"] + - ["org.gradle.api.file", "DirectoryProperty", True, "file", "(String)", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"] diff --git a/java/ql/lib/ext/retrofit2.model.yml b/java/ql/lib/ext/retrofit2.model.yml index 4ea997169a9..7096588aed6 100644 --- a/java/ql/lib/ext/retrofit2.model.yml +++ b/java/ql/lib/ext/retrofit2.model.yml @@ -4,3 +4,8 @@ extensions: extensible: sinkModel data: - ["retrofit2", "Retrofit$Builder", True, "baseUrl", "", "", "Argument[0]", "request-forgery", "manual"] + - addsTo: + pack: codeql/java-all + extensible: summaryModel + data: + - ["retrofit2", "Retrofit$Builder", False, "baseUrl", "(String)", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"] diff --git a/java/ql/lib/qlpack.yml b/java/ql/lib/qlpack.yml index 6a87f245283..dc92e3ce94d 100644 --- a/java/ql/lib/qlpack.yml +++ b/java/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/java-all -version: 0.7.0 +version: 0.7.1 groups: java dbscheme: config/semmlecode.dbscheme extractor: java diff --git a/java/ql/lib/semmle/code/java/Type.qll b/java/ql/lib/semmle/code/java/Type.qll index e8e9c2bf916..a85df42eb55 100644 --- a/java/ql/lib/semmle/code/java/Type.qll +++ b/java/ql/lib/semmle/code/java/Type.qll @@ -709,6 +709,12 @@ class Class extends ClassOrInterface { ) } + /** + * Holds if this class is a Kotlin "file class", e.g. the class FooKt + * for top-level entities in Foo.kt. + */ + predicate isFileClass() { file_class(this) } + override string getAPrimaryQlClass() { result = "Class" } } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll index f34554d6eea..03975c6a54a 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll @@ -46,6 +46,14 @@ signature module ConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or isAdditionalFlowStep(_, node) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -114,7 +122,7 @@ signature module StateConfigSig { * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state); + default predicate isBarrier(Node node, FlowState state) { none() } /** Holds if data flow into `node` is prohibited. */ default predicate isBarrierIn(Node node) { none() } @@ -131,7 +139,9 @@ signature module StateConfigSig { * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + default predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } /** * Holds if an arbitrary number of implicit read steps of content `c` may be @@ -139,6 +149,17 @@ signature module StateConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or + isAdditionalFlowStep(_, node) or + isAdditionalFlowStep(node, _, _, _) or + isAdditionalFlowStep(_, _, node, _) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 410543e0fc9..29561b0f0a6 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -66,6 +66,12 @@ signature module FullStateConfigSig { */ predicate allowImplicitRead(Node node, ContentSet c); + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + predicate neverSkip(Node node); + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -254,6 +260,11 @@ module Impl { not fullBarrier(node2) } + pragma[nomagic] + private predicate isUnreachableInCall1(NodeEx n, LocalCallContextSpecificCall cc) { + isUnreachableInCallCached(n.asNode(), cc.getCall()) + } + /** * Holds if data can flow in one local step from `node1` to `node2`. */ @@ -2019,7 +2030,8 @@ module Impl { castNode(this.asNode()) or clearsContentCached(this.asNode(), _) or expectsContentCached(this.asNode(), _) or - neverSkipInPathGraph(this.asNode()) + neverSkipInPathGraph(this.asNode()) or + Config::neverSkip(this.asNode()) } } @@ -2108,7 +2120,7 @@ module Impl { NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, LocalCallContext cc ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCall1(node2, cc) and ( localFlowEntry(node1, pragma[only_bind_into](state)) and ( @@ -2123,7 +2135,7 @@ module Impl { ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) + not isUnreachableInCall1(node1, cc) or exists(NodeEx mid | localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and @@ -2160,10 +2172,8 @@ module Impl { preservesValue = false and t = node2.getDataFlowType() and callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) + not isUnreachableInCall1(node1, callContext) and + not isUnreachableInCall1(node2, callContext) } } @@ -2703,7 +2713,7 @@ module Impl { ParamNodeEx getParamNode() { result = p } - override string toString() { result = p + ": " + ap } + override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn @@ -2755,12 +2765,21 @@ module Impl { ) } + private predicate forceUnfold(AccessPathApprox apa) { + forceHighPrecision(apa.getHead()) + or + exists(Content c2 | + apa = TConsCons(_, _, c2, _) and + forceHighPrecision(c2) + ) + } + /** * Holds with `unfold = false` if a precise head-tail representation of `apa` is * expected to be expensive. Holds with `unfold = true` otherwise. */ private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { - if forceHighPrecision(apa.getHead()) + if forceUnfold(apa) then unfold = true else exists(int aps, int nodes, int apLimit, int tupleLimit | @@ -3089,6 +3108,12 @@ module Impl { result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } + private string ppSummaryCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">" + } + /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() } @@ -3097,7 +3122,9 @@ module Impl { * representation of the call context. */ string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + result = + this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + + this.ppSummaryCtx() } /** diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll index be70086a93a..b0de9745816 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index be70086a93a..b0de9745816 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index be70086a93a..b0de9745816 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index be70086a93a..b0de9745816 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index be70086a93a..b0de9745816 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll index be70086a93a..b0de9745816 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/java/ql/lib/semmle/code/java/frameworks/kotlin/Kotlin.qll b/java/ql/lib/semmle/code/java/frameworks/kotlin/Kotlin.qll new file mode 100644 index 00000000000..71e0af2018d --- /dev/null +++ b/java/ql/lib/semmle/code/java/frameworks/kotlin/Kotlin.qll @@ -0,0 +1,21 @@ +/** Provides classes and predicates related to `kotlin`. */ + +import java + +/** A call to Kotlin's `apply` method. */ +class KotlinApply extends MethodAccess { + ExtensionMethod m; + + KotlinApply() { + this.getMethod() = m and + m.hasQualifiedName("kotlin", "StandardKt", "apply") + } + + /** Gets the function block argument of this call. */ + LambdaExpr getLambdaArg() { + result = this.getArgument(m.getExtensionReceiverParameterIndex() + 1) + } + + /** Gets the receiver argument of this call. */ + Argument getReceiver() { result = this.getArgument(m.getExtensionReceiverParameterIndex()) } +} diff --git a/java/ql/lib/semmle/code/java/security/ArbitraryApkInstallationQuery.qll b/java/ql/lib/semmle/code/java/security/ArbitraryApkInstallationQuery.qll index 9df15067be9..fa3dbdb4474 100644 --- a/java/ql/lib/semmle/code/java/security/ArbitraryApkInstallationQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ArbitraryApkInstallationQuery.qll @@ -68,8 +68,6 @@ private module InstallPackageActionConfig implements DataFlow::StateConfigSig { predicate isSink(DataFlow::Node node, FlowState state) { state instanceof HasInstallPackageAction and node.asExpr().getType() instanceof TypeIntent } - - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } } private module InstallPackageActionFlow = @@ -113,8 +111,6 @@ private module PackageArchiveMimeTypeConfig implements DataFlow::StateConfigSig state instanceof HasPackageArchiveMimeType and node instanceof SetDataSink } - - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } } private module PackageArchiveMimeTypeFlow = diff --git a/java/ql/lib/semmle/code/java/security/ArithmeticTaintedQuery.qll b/java/ql/lib/semmle/code/java/security/ArithmeticTaintedQuery.qll index fba7ac38cbb..f2be743e11b 100644 --- a/java/ql/lib/semmle/code/java/security/ArithmeticTaintedQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ArithmeticTaintedQuery.qll @@ -11,6 +11,8 @@ module RemoteUserInputOverflowConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node sink) { overflowSink(_, sink.asExpr()) } predicate isBarrier(DataFlow::Node n) { overflowBarrier(n) } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node) } } /** A taint-tracking configuration to reason about underflow from unvalidated user input. */ @@ -20,6 +22,8 @@ module RemoteUserInputUnderflowConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node sink) { underflowSink(_, sink.asExpr()) } predicate isBarrier(DataFlow::Node n) { underflowBarrier(n) } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node) } } /** Taint-tracking flow for overflow from unvalidated user input. */ diff --git a/java/ql/lib/semmle/code/java/security/ImplicitPendingIntentsQuery.qll b/java/ql/lib/semmle/code/java/security/ImplicitPendingIntentsQuery.qll index c02abb4de81..9ef330459bf 100644 --- a/java/ql/lib/semmle/code/java/security/ImplicitPendingIntentsQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ImplicitPendingIntentsQuery.qll @@ -72,8 +72,6 @@ module ImplicitPendingIntentStartConfig implements DataFlow::StateConfigSig { predicate isBarrier(DataFlow::Node sanitizer) { sanitizer instanceof ExplicitIntentSanitizer } - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { any(ImplicitPendingIntentAdditionalTaintStep c).step(node1, node2) } diff --git a/java/ql/lib/semmle/code/java/security/ImproperValidationOfArrayIndexQuery.qll b/java/ql/lib/semmle/code/java/security/ImproperValidationOfArrayIndexQuery.qll index 07b6b5e28cf..fd93b73b7c3 100644 --- a/java/ql/lib/semmle/code/java/security/ImproperValidationOfArrayIndexQuery.qll +++ b/java/ql/lib/semmle/code/java/security/ImproperValidationOfArrayIndexQuery.qll @@ -15,6 +15,8 @@ module ImproperValidationOfArrayIndexConfig implements DataFlow::ConfigSig { } predicate isBarrier(DataFlow::Node node) { node.getType() instanceof BooleanType } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node) } } /** diff --git a/java/ql/lib/semmle/code/java/security/LogInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/LogInjectionQuery.qll index 2610870b383..5b57847d286 100644 --- a/java/ql/lib/semmle/code/java/security/LogInjectionQuery.qll +++ b/java/ql/lib/semmle/code/java/security/LogInjectionQuery.qll @@ -36,6 +36,8 @@ module LogInjectionConfig implements DataFlow::ConfigSig { predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { any(LogInjectionAdditionalTaintStep c).step(node1, node2) } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node) } } /** diff --git a/java/ql/lib/semmle/code/java/security/NumericCastTaintedQuery.qll b/java/ql/lib/semmle/code/java/security/NumericCastTaintedQuery.qll index d2b0e75f052..d3098a18a11 100644 --- a/java/ql/lib/semmle/code/java/security/NumericCastTaintedQuery.qll +++ b/java/ql/lib/semmle/code/java/security/NumericCastTaintedQuery.qll @@ -100,6 +100,8 @@ module NumericCastFlowConfig implements DataFlow::ConfigSig { node.getEnclosingCallable() instanceof HashCodeMethod or exists(RightShiftOp e | e.getShiftedVariable().getAnAccess() = node.asExpr()) } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node) } } /** diff --git a/java/ql/lib/semmle/code/java/security/RequestForgeryConfig.qll b/java/ql/lib/semmle/code/java/security/RequestForgeryConfig.qll index 0e3afacb83f..6a79193c4fd 100644 --- a/java/ql/lib/semmle/code/java/security/RequestForgeryConfig.qll +++ b/java/ql/lib/semmle/code/java/security/RequestForgeryConfig.qll @@ -51,6 +51,8 @@ module RequestForgeryConfig implements DataFlow::ConfigSig { } predicate isBarrier(DataFlow::Node node) { node instanceof RequestForgerySanitizer } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node) } } module RequestForgeryFlow = TaintTracking::Global; diff --git a/java/ql/lib/semmle/code/java/security/UnsafeAndroidAccess.qll b/java/ql/lib/semmle/code/java/security/UnsafeAndroidAccess.qll index ba162ede986..9a85a771406 100644 --- a/java/ql/lib/semmle/code/java/security/UnsafeAndroidAccess.qll +++ b/java/ql/lib/semmle/code/java/security/UnsafeAndroidAccess.qll @@ -5,6 +5,7 @@ import java private import semmle.code.java.dataflow.DataFlow private import semmle.code.java.frameworks.android.WebView +private import semmle.code.java.frameworks.kotlin.Kotlin /** * A sink that represents a method that fetches a web resource in Android. @@ -62,10 +63,26 @@ private class WebViewRef extends Element { t.isOwnInstanceAccess() or t.getInstanceAccess().isEnclosingInstanceAccess(this) ) or - result = DataFlow::exprNode(this.(Variable).getAnAccess()) + exists(Variable v | result.asExpr() = v.getAnAccess() | + v = this + or + applyReceiverVariable(this, v) + ) } } +/** + * Holds if `p` is the lambda parameter that holds the receiver of an `apply` expression in Kotlin, + * and `v` is the variable of the receiver in the outer scope. + */ +private predicate applyReceiverVariable(Parameter p, Variable v) { + exists(LambdaExpr lambda, KotlinApply apply | + p.getCallable() = lambda.asMethod() and + lambda = apply.getLambdaArg() and + v = apply.getReceiver().(VarAccess).getVariable() + ) +} + /** * Holds if a `WebViewLoadUrlMethod` is called on an access of `webview` * with `urlArg` as its first argument. diff --git a/java/ql/src/CHANGELOG.md b/java/ql/src/CHANGELOG.md index 63dffc3382f..d2517609d01 100644 --- a/java/ql/src/CHANGELOG.md +++ b/java/ql/src/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.7.1 + +### Minor Analysis Improvements + +* The query "Unsafe resource fetching in Android WebView" (`java/android/unsafe-android-webview-fetch`) now recognizes WebViews where `setJavascriptEnabled`, `setAllowFileAccess`, `setAllowUniversalAccessFromFileURLs`, and/or `setAllowFileAccessFromFileURLs` are set inside the function block of the Kotlin `apply` function. + ## 0.7.0 ### Minor Analysis Improvements diff --git a/java/ql/src/Security/CWE/CWE-200/AndroidWebViewSettingsAllowsContentAccess.ql b/java/ql/src/Security/CWE/CWE-200/AndroidWebViewSettingsAllowsContentAccess.ql index c16b8577d73..6fd6da06a27 100644 --- a/java/ql/src/Security/CWE/CWE-200/AndroidWebViewSettingsAllowsContentAccess.ql +++ b/java/ql/src/Security/CWE/CWE-200/AndroidWebViewSettingsAllowsContentAccess.ql @@ -97,8 +97,6 @@ module WebViewDisallowContentAccessConfig implements DataFlow::StateConfigSig { state instanceof IsSettings and node instanceof WebSettingsDisallowContentAccessSink } - - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } } module WebViewDisallowContentAccessFlow = diff --git a/java/ql/src/Telemetry/AutomodelAlertSinkUtil.qll b/java/ql/src/Telemetry/AutomodelAlertSinkUtil.qll new file mode 100644 index 00000000000..4196ba8add3 --- /dev/null +++ b/java/ql/src/Telemetry/AutomodelAlertSinkUtil.qll @@ -0,0 +1,187 @@ +private import java +private import semmle.code.java.dataflow.ExternalFlow as ExternalFlow +private import semmle.code.java.dataflow.internal.DataFlow +private import semmle.code.java.dataflow.TaintTracking +private import semmle.code.java.security.RequestForgeryConfig +private import semmle.code.java.security.CommandLineQuery +private import semmle.code.java.security.SqlConcatenatedQuery +private import semmle.code.java.security.SqlInjectionQuery +private import semmle.code.java.security.UrlRedirectQuery +private import semmle.code.java.security.TaintedPathQuery +private import semmle.code.java.security.SqlInjectionQuery +private import AutomodelJavaUtil + +private newtype TSinkModel = + MkSinkModel( + string package, string type, boolean subtypes, string name, string signature, string ext, + string input, string kind, string provenance + ) { + ExternalFlow::sinkModel(package, type, subtypes, name, signature, ext, input, kind, provenance) + } + +class SinkModel extends TSinkModel { + string package; + string type; + boolean subtypes; + string name; + string signature; + string ext; + string input; + string kind; + string provenance; + + SinkModel() { + this = MkSinkModel(package, type, subtypes, name, signature, ext, input, kind, provenance) + } + + /** Gets the package for this sink model. */ + string getPackage() { result = package } + + /** Gets the type for this sink model. */ + string getType() { result = type } + + /** Gets whether this sink model considers subtypes. */ + boolean getSubtypes() { result = subtypes } + + /** Gets the name for this sink model. */ + string getName() { result = name } + + /** Gets the signature for this sink model. */ + string getSignature() { result = signature } + + /** Gets the input for this sink model. */ + string getInput() { result = input } + + /** Gets the extension for this sink model. */ + string getExt() { result = ext } + + /** Gets the kind for this sink model. */ + string getKind() { result = kind } + + /** Gets the provenance for this sink model. */ + string getProvenance() { result = provenance } + + /** Gets the number of instances of this sink model. */ + int getInstanceCount() { result = count(PotentialSinkModelExpr p | p.getSinkModel() = this) } + + /** Gets a string representation of this sink model. */ + string toString() { + result = + "SinkModel(" + package + ", " + type + ", " + subtypes + ", " + name + ", " + signature + ", " + + ext + ", " + input + ", " + kind + ", " + provenance + ")" + } + + /** Gets a string representation of this sink model as it would appear in a Models-as-Data file. */ + string getRepr() { + result = + "\"" + package + "\", \"" + type + "\", " + pyBool(subtypes) + ", \"" + name + "\", \"" + + signature + "\", \"" + ext + "\", \"" + input + "\", \"" + kind + "\", \"" + provenance + + "\"" + } +} + +/** An expression that may correspond to a sink model. */ +class PotentialSinkModelExpr extends Expr { + /** + * Holds if this expression has the given signature. The signature should contain enough + * information to determine a corresponding sink model, if one exists. + */ + pragma[nomagic] + predicate hasSignature( + string package, string type, boolean subtypes, string name, string signature, string input + ) { + exists(Call call, Callable callable, int argIdx | + call.getCallee() = callable and + ( + this = call.getArgument(argIdx) + or + this = call.getQualifier() and argIdx = -1 + ) and + input = getArgumentForIndex(argIdx) and + package = callable.getDeclaringType().getPackage().getName() and + type = callable.getDeclaringType().getErasure().(RefType).nestedName() and + subtypes = considerSubtypes(callable) and + name = callable.getName() and + signature = ExternalFlow::paramsString(callable) + ) + } + + /** Gets a sink model that corresponds to this expression. */ + SinkModel getSinkModel() { + this.hasSignature(result.getPackage(), result.getType(), result.getSubtypes(), result.getName(), + result.getSignature(), result.getInput()) + } +} + +private string pyBool(boolean b) { + b = true and result = "True" + or + b = false and result = "False" +} + +/** + * Gets a string representation of the existing sink model at the expression `e`, in the format in + * which it would appear in a Models-as-Data file. Also restricts the provenance of the sink model + * to be `ai-generated`. + */ +string getSinkModelRepr(PotentialSinkModelExpr e) { + result = e.getSinkModel().getRepr() and + e.getSinkModel().getProvenance() = "ai-generated" +} + +/** + * Gets the string representation of a sink model in a format suitable for appending to an alert + * message. + */ +string getSinkModelQueryRepr(PotentialSinkModelExpr e) { + result = "\nsinkModel: " + getSinkModelRepr(e) +} + +/** + * A parameterised module that takes a dataflow config, and exposes a predicate for counting the + * number of AI-generated sink models that appear in alerts for that query. + */ +private module SinkTallier { + module ConfigFlow = TaintTracking::Global; + + predicate getSinkModelCount(int c, SinkModel s) { + s = any(ConfigFlow::PathNode sink).getNode().asExpr().(PotentialSinkModelExpr).getSinkModel() and + c = + strictcount(ConfigFlow::PathNode sink | + ConfigFlow::flowPath(_, sink) and + s = sink.getNode().asExpr().(PotentialSinkModelExpr).getSinkModel() + ) + } +} + +predicate sinkModelTallyPerQuery(string queryName, int alertCount, SinkModel sinkModel) { + queryName = "java/request-forgery" and + SinkTallier::getSinkModelCount(alertCount, sinkModel) + or + queryName = "java/command-line-injection" and + exists(int c1, int c2 | + SinkTallier::getSinkModelCount(c1, sinkModel) and + SinkTallier::getSinkModelCount(c2, sinkModel) and + alertCount = c1 + c2 + ) + or + queryName = "java/concatenated-sql-query" and + SinkTallier::getSinkModelCount(alertCount, sinkModel) + or + queryName = "java/ssrf" and + SinkTallier::getSinkModelCount(alertCount, sinkModel) + or + queryName = "java/path-injection" and + SinkTallier::getSinkModelCount(alertCount, sinkModel) + or + queryName = "java/unvalidated-url-redirection" and + SinkTallier::getSinkModelCount(alertCount, sinkModel) + or + queryName = "java/sql-injection" and + SinkTallier::getSinkModelCount(alertCount, sinkModel) +} + +predicate sinkModelTally(int alertCount, SinkModel sinkModel) { + sinkModelTallyPerQuery(_, _, sinkModel) and + alertCount = sum(int c | sinkModelTallyPerQuery(_, c, sinkModel)) +} diff --git a/java/ql/src/Telemetry/AutomodelAlertSinks.ql b/java/ql/src/Telemetry/AutomodelAlertSinks.ql new file mode 100644 index 00000000000..e9af51b4d63 --- /dev/null +++ b/java/ql/src/Telemetry/AutomodelAlertSinks.ql @@ -0,0 +1,16 @@ +/** + * @name Number of alerts per sink model + * @description Counts the number of alerts using `ai-generated` sink models. + * @kind table + * @id java/ml/metrics-count-alerts-per-sink-model + * @tags internal automodel metrics + */ + +private import java +private import AutomodelAlertSinkUtil + +from int alertCount, SinkModel s +where sinkModelTally(alertCount, s) and s.getProvenance() = "ai-generated" +select alertCount, s.getPackage() as package, s.getType() as type, s.getSubtypes() as subtypes, + s.getName() as name, s.getSignature() as signature, s.getInput() as input, s.getExt() as ext, + s.getKind() as kind, s.getProvenance() as provenance order by alertCount desc diff --git a/java/ql/src/Telemetry/AutomodelAlertSinksPerQuery.ql b/java/ql/src/Telemetry/AutomodelAlertSinksPerQuery.ql new file mode 100644 index 00000000000..64a5038d116 --- /dev/null +++ b/java/ql/src/Telemetry/AutomodelAlertSinksPerQuery.ql @@ -0,0 +1,19 @@ +/** + * @name Number of alerts per sink model and query + * @description Counts the number of alerts per query using `ai-generated` sink models. + * @kind table + * @id java/ml/metrics-count-alerts-per-sink-model-and-query + * @tags internal automodel metrics + */ + +private import java +private import AutomodelAlertSinkUtil + +from string queryId, int alertCount, SinkModel s +where + sinkModelTallyPerQuery(queryId, alertCount, s) and + s.getProvenance() = "ai-generated" +select queryId, alertCount, s.getPackage() as package, s.getType() as type, + s.getSubtypes() as subtypes, s.getName() as name, s.getSignature() as signature, + s.getInput() as input, s.getExt() as ext, s.getKind() as kind, s.getProvenance() as provenance + order by queryId, alertCount desc diff --git a/java/ql/src/Telemetry/AutomodelApplicationModeExtractCandidates.ql b/java/ql/src/Telemetry/AutomodelApplicationModeExtractCandidates.ql index 1e4c9c7e248..4e40fadd750 100644 --- a/java/ql/src/Telemetry/AutomodelApplicationModeExtractCandidates.ql +++ b/java/ql/src/Telemetry/AutomodelApplicationModeExtractCandidates.ql @@ -12,9 +12,44 @@ * @tags internal extract automodel application-mode candidates */ +import java private import AutomodelApplicationModeCharacteristics private import AutomodelJavaUtil +/** + * Gets a sample of endpoints (of at most `limit` samples) with the given method signature. + * + * The main purpose of this helper predicate is to avoid selecting too many candidates, as this may + * cause the SARIF file to exceed the maximum size limit. + */ +bindingset[limit] +private Endpoint getSampleForSignature( + int limit, string package, string type, string subtypes, string name, string signature, + string input +) { + exists(int n, int num_endpoints, ApplicationModeMetadataExtractor meta | + num_endpoints = + count(Endpoint e | meta.hasMetadata(e, package, type, subtypes, name, signature, input)) + | + result = + rank[n](Endpoint e, Location loc | + loc = e.getLocation() and + meta.hasMetadata(e, package, type, subtypes, name, signature, input) + | + e + order by + loc.getFile().getAbsolutePath(), loc.getStartLine(), loc.getStartColumn(), + loc.getEndLine(), loc.getEndColumn() + ) and + // To avoid selecting samples that are too close together (as the ranking above goes by file + // path first), we select `limit` evenly spaced samples from the ranked list of endpoints. By + // default this would always include the first sample, so we add a random-chosen prime offset + // to the first sample index, and reduce modulo the number of endpoints. + // Finally, we add 1 to the result, as ranking results in a 1-indexed relation. + n = 1 + (([0 .. limit - 1] * (num_endpoints / limit).floor() + 46337) % num_endpoints) + ) +} + from Endpoint endpoint, string message, ApplicationModeMetadataExtractor meta, DollarAtString package, DollarAtString type, DollarAtString subtypes, DollarAtString name, DollarAtString signature, @@ -23,6 +58,7 @@ where not exists(CharacteristicsImpl::UninterestingToModelCharacteristic u | u.appliesToEndpoint(endpoint) ) and + endpoint = getSampleForSignature(9, package, type, subtypes, name, signature, input) and // If a node is already a known sink for any of our existing ATM queries and is already modeled as a MaD sink, we // don't include it as a candidate. Otherwise, we might include it as a candidate for query A, but the model will // label it as a sink for one of the sink types of query B, for which it's already a known sink. This would result in diff --git a/java/ql/src/Telemetry/AutomodelCountGeneratedSinks.ql b/java/ql/src/Telemetry/AutomodelCountGeneratedSinks.ql new file mode 100644 index 00000000000..475e3753810 --- /dev/null +++ b/java/ql/src/Telemetry/AutomodelCountGeneratedSinks.ql @@ -0,0 +1,19 @@ +/** + * @name Number of instances of each sink model + * @description Counts the number of instances of `ai-generated` sink models. + * @kind table + * @id java/ml/metrics-count-instances-per-sink-model + * @tags internal automodel metrics + */ + +private import java +private import AutomodelAlertSinkUtil + +from int instanceCount, SinkModel s +where + instanceCount = s.getInstanceCount() and + instanceCount > 0 and + s.getProvenance() = "ai-generated" +select instanceCount, s.getPackage() as package, s.getType() as type, s.getSubtypes() as subtypes, + s.getName() as name, s.getSignature() as signature, s.getInput() as input, s.getExt() as ext, + s.getKind() as kind, s.getProvenance() as provenance order by instanceCount desc diff --git a/java/ql/src/Telemetry/AutomodelJavaUtil.qll b/java/ql/src/Telemetry/AutomodelJavaUtil.qll index 03b73da1015..65be12ce1f9 100644 --- a/java/ql/src/Telemetry/AutomodelJavaUtil.qll +++ b/java/ql/src/Telemetry/AutomodelJavaUtil.qll @@ -56,6 +56,7 @@ string getArgumentForIndex(int index) { * It would technically be ok to always use the value 'true', but this would * break convention. */ +pragma[nomagic] boolean considerSubtypes(Callable callable) { if callable.isStatic() or diff --git a/java/ql/src/Telemetry/AutomodelSinkModelMrvaQueries.ql b/java/ql/src/Telemetry/AutomodelSinkModelMrvaQueries.ql new file mode 100644 index 00000000000..ed61ccfbbfd --- /dev/null +++ b/java/ql/src/Telemetry/AutomodelSinkModelMrvaQueries.ql @@ -0,0 +1,62 @@ +/** + * This file contains query predicates for use when gathering metrics at scale using Multi Repo + * Variant Analysis. + */ + +private import java +private import AutomodelAlertSinkUtil + +/** + * Holds if `alertCount` is the number of alerts for the query with ID `queryId` for which the + * sinks correspond to the given `ai-generated` sink model. + */ +query predicate sinkModelCountPerQuery( + string queryId, int alertCount, string package, string type, boolean subtypes, string name, + string signature, string input, string ext, string kind, string provenance +) { + exists(SinkModel s | + sinkModelTallyPerQuery(queryId, alertCount, s) and + s.getProvenance() = "ai-generated" and + s.getPackage() = package and + s.getType() = type and + s.getSubtypes() = subtypes and + s.getName() = name and + s.getSignature() = signature and + s.getInput() = input and + s.getExt() = ext and + s.getKind() = kind and + s.getProvenance() = provenance + ) +} + +/** + * Holds if `instanceCount` is the number of instances corresponding to the given `ai-generated` + * sink model (as identified by the `package`, `name`, `input`, etc.). + */ +query predicate instanceCount( + int instanceCount, string package, string type, boolean subtypes, string name, string signature, + string input, string ext, string kind, string provenance +) { + exists(SinkModel s | + instanceCount = s.getInstanceCount() and + instanceCount > 0 and + s.getProvenance() = "ai-generated" and + s.getPackage() = package and + s.getType() = type and + s.getSubtypes() = subtypes and + s.getName() = name and + s.getSignature() = signature and + s.getInput() = input and + s.getExt() = ext and + s.getKind() = kind and + s.getProvenance() = provenance + ) +} + +// MRVA requires a select clause, so we repurpose it to tell us which query predicates had results. +from string hadResults +where + sinkModelCountPerQuery(_, _, _, _, _, _, _, _, _, _, _) and hadResults = "sinkModelCountPerQuery" + or + instanceCount(_, _, _, _, _, _, _, _, _, _) and hadResults = "instanceCount" +select hadResults diff --git a/java/ql/src/change-notes/released/0.7.1.md b/java/ql/src/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..e6fe8cd960d --- /dev/null +++ b/java/ql/src/change-notes/released/0.7.1.md @@ -0,0 +1,5 @@ +## 0.7.1 + +### Minor Analysis Improvements + +* The query "Unsafe resource fetching in Android WebView" (`java/android/unsafe-android-webview-fetch`) now recognizes WebViews where `setJavascriptEnabled`, `setAllowFileAccess`, `setAllowUniversalAccessFromFileURLs`, and/or `setAllowFileAccessFromFileURLs` are set inside the function block of the Kotlin `apply` function. diff --git a/java/ql/src/codeql-pack.release.yml b/java/ql/src/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/java/ql/src/codeql-pack.release.yml +++ b/java/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/java/ql/src/qlpack.yml b/java/ql/src/qlpack.yml index ccf777a554a..2a7f20df88b 100644 --- a/java/ql/src/qlpack.yml +++ b/java/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/java-queries -version: 0.7.0 +version: 0.7.1 groups: - java - queries diff --git a/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll index e2a0e130ca4..9c19e5b9cbb 100644 --- a/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -160,8 +160,6 @@ module ThroughFlowConfig implements DataFlow::StateConfigSig { exists(Type t | t = n.getType() and not isRelevantType(t)) } - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureEqualSourceSinkCallContext } diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/BeanShellInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/BeanShellInjection.expected index 96d87e43693..62d42d57864 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/BeanShellInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-094/BeanShellInjection.expected @@ -1,13 +1,19 @@ edges -| BeanShellInjection.java:13:17:13:44 | getParameter(...) : String | BeanShellInjection.java:15:22:15:49 | new StaticScriptSource(...) | +| BeanShellInjection.java:13:17:13:44 | getParameter(...) : String | BeanShellInjection.java:15:45:15:48 | code : String | +| BeanShellInjection.java:15:45:15:48 | code : String | BeanShellInjection.java:15:22:15:49 | new StaticScriptSource(...) | | BeanShellInjection.java:20:17:20:44 | getParameter(...) : String | BeanShellInjection.java:22:20:22:23 | code | -| BeanShellInjection.java:27:17:27:44 | getParameter(...) : String | BeanShellInjection.java:31:22:31:39 | staticScriptSource | +| BeanShellInjection.java:27:17:27:44 | getParameter(...) : String | BeanShellInjection.java:29:32:29:35 | code : String | +| BeanShellInjection.java:29:3:29:20 | staticScriptSource : StaticScriptSource | BeanShellInjection.java:31:22:31:39 | staticScriptSource | +| BeanShellInjection.java:29:32:29:35 | code : String | BeanShellInjection.java:29:3:29:20 | staticScriptSource : StaticScriptSource | nodes | BeanShellInjection.java:13:17:13:44 | getParameter(...) : String | semmle.label | getParameter(...) : String | | BeanShellInjection.java:15:22:15:49 | new StaticScriptSource(...) | semmle.label | new StaticScriptSource(...) | +| BeanShellInjection.java:15:45:15:48 | code : String | semmle.label | code : String | | BeanShellInjection.java:20:17:20:44 | getParameter(...) : String | semmle.label | getParameter(...) : String | | BeanShellInjection.java:22:20:22:23 | code | semmle.label | code | | BeanShellInjection.java:27:17:27:44 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| BeanShellInjection.java:29:3:29:20 | staticScriptSource : StaticScriptSource | semmle.label | staticScriptSource : StaticScriptSource | +| BeanShellInjection.java:29:32:29:35 | code : String | semmle.label | code : String | | BeanShellInjection.java:31:22:31:39 | staticScriptSource | semmle.label | staticScriptSource | subpaths #select diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JShellInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JShellInjection.expected index 621477330e2..f084660ced8 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/JShellInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JShellInjection.expected @@ -1,13 +1,26 @@ edges | JShellInjection.java:12:18:12:45 | getParameter(...) : String | JShellInjection.java:15:15:15:19 | input | | JShellInjection.java:20:18:20:45 | getParameter(...) : String | JShellInjection.java:24:31:24:35 | input | -| JShellInjection.java:29:18:29:45 | getParameter(...) : String | JShellInjection.java:37:16:37:28 | source(...) | +| JShellInjection.java:29:18:29:45 | getParameter(...) : String | JShellInjection.java:33:37:33:41 | input : String | +| JShellInjection.java:33:15:33:42 | analyzeCompletion(...) : CompletionInfo | JShellInjection.java:37:16:37:19 | info : CompletionInfo | +| JShellInjection.java:33:37:33:41 | input : String | JShellInjection.java:33:15:33:42 | analyzeCompletion(...) : CompletionInfo | +| JShellInjection.java:35:12:35:50 | analyzeCompletion(...) : CompletionInfo | JShellInjection.java:37:16:37:19 | info : CompletionInfo | +| JShellInjection.java:35:34:35:37 | info : CompletionInfo | JShellInjection.java:35:34:35:49 | remaining(...) : String | +| JShellInjection.java:35:34:35:49 | remaining(...) : String | JShellInjection.java:35:12:35:50 | analyzeCompletion(...) : CompletionInfo | +| JShellInjection.java:37:16:37:19 | info : CompletionInfo | JShellInjection.java:35:34:35:37 | info : CompletionInfo | +| JShellInjection.java:37:16:37:19 | info : CompletionInfo | JShellInjection.java:37:16:37:28 | source(...) | nodes | JShellInjection.java:12:18:12:45 | getParameter(...) : String | semmle.label | getParameter(...) : String | | JShellInjection.java:15:15:15:19 | input | semmle.label | input | | JShellInjection.java:20:18:20:45 | getParameter(...) : String | semmle.label | getParameter(...) : String | | JShellInjection.java:24:31:24:35 | input | semmle.label | input | | JShellInjection.java:29:18:29:45 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JShellInjection.java:33:15:33:42 | analyzeCompletion(...) : CompletionInfo | semmle.label | analyzeCompletion(...) : CompletionInfo | +| JShellInjection.java:33:37:33:41 | input : String | semmle.label | input : String | +| JShellInjection.java:35:12:35:50 | analyzeCompletion(...) : CompletionInfo | semmle.label | analyzeCompletion(...) : CompletionInfo | +| JShellInjection.java:35:34:35:37 | info : CompletionInfo | semmle.label | info : CompletionInfo | +| JShellInjection.java:35:34:35:49 | remaining(...) : String | semmle.label | remaining(...) : String | +| JShellInjection.java:37:16:37:19 | info : CompletionInfo | semmle.label | info : CompletionInfo | | JShellInjection.java:37:16:37:28 | source(...) | semmle.label | source(...) | subpaths #select diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected index 24fc6f51f6b..ef5fefa21a3 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected @@ -13,12 +13,22 @@ edges | JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:95:24:95:33 | expression : String | | JakartaExpressionInjection.java:32:24:32:33 | expression : String | JakartaExpressionInjection.java:34:28:34:37 | expression | | JakartaExpressionInjection.java:40:24:40:33 | expression : String | JakartaExpressionInjection.java:42:32:42:41 | expression | -| JakartaExpressionInjection.java:48:24:48:33 | expression : String | JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | +| JakartaExpressionInjection.java:48:24:48:33 | expression : String | JakartaExpressionInjection.java:51:86:51:95 | expression : String | +| JakartaExpressionInjection.java:51:47:51:110 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:52:89:52:103 | valueExpression : ValueExpression | +| JakartaExpressionInjection.java:51:86:51:95 | expression : String | JakartaExpressionInjection.java:51:47:51:110 | createValueExpression(...) : ValueExpression | +| JakartaExpressionInjection.java:52:49:52:104 | new LambdaExpression(...) : LambdaExpression | JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | +| JakartaExpressionInjection.java:52:89:52:103 | valueExpression : ValueExpression | JakartaExpressionInjection.java:52:49:52:104 | new LambdaExpression(...) : LambdaExpression | | JakartaExpressionInjection.java:59:24:59:33 | expression : String | JakartaExpressionInjection.java:61:32:61:41 | expression | | JakartaExpressionInjection.java:67:24:67:33 | expression : String | JakartaExpressionInjection.java:69:43:69:52 | expression | -| JakartaExpressionInjection.java:75:24:75:33 | expression : String | JakartaExpressionInjection.java:79:13:79:13 | e | -| JakartaExpressionInjection.java:85:24:85:33 | expression : String | JakartaExpressionInjection.java:89:13:89:13 | e | -| JakartaExpressionInjection.java:95:24:95:33 | expression : String | JakartaExpressionInjection.java:99:13:99:13 | e | +| JakartaExpressionInjection.java:75:24:75:33 | expression : String | JakartaExpressionInjection.java:78:72:78:81 | expression : String | +| JakartaExpressionInjection.java:78:33:78:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:79:13:79:13 | e | +| JakartaExpressionInjection.java:78:72:78:81 | expression : String | JakartaExpressionInjection.java:78:33:78:96 | createValueExpression(...) : ValueExpression | +| JakartaExpressionInjection.java:85:24:85:33 | expression : String | JakartaExpressionInjection.java:88:72:88:81 | expression : String | +| JakartaExpressionInjection.java:88:33:88:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:89:13:89:13 | e | +| JakartaExpressionInjection.java:88:72:88:81 | expression : String | JakartaExpressionInjection.java:88:33:88:96 | createValueExpression(...) : ValueExpression | +| JakartaExpressionInjection.java:95:24:95:33 | expression : String | JakartaExpressionInjection.java:98:74:98:83 | expression : String | +| JakartaExpressionInjection.java:98:34:98:112 | createMethodExpression(...) : MethodExpression | JakartaExpressionInjection.java:99:13:99:13 | e | +| JakartaExpressionInjection.java:98:74:98:83 | expression : String | JakartaExpressionInjection.java:98:34:98:112 | createMethodExpression(...) : MethodExpression | nodes | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | | JakartaExpressionInjection.java:23:54:23:58 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] | @@ -30,16 +40,26 @@ nodes | JakartaExpressionInjection.java:40:24:40:33 | expression : String | semmle.label | expression : String | | JakartaExpressionInjection.java:42:32:42:41 | expression | semmle.label | expression | | JakartaExpressionInjection.java:48:24:48:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:51:47:51:110 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression | +| JakartaExpressionInjection.java:51:86:51:95 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:52:49:52:104 | new LambdaExpression(...) : LambdaExpression | semmle.label | new LambdaExpression(...) : LambdaExpression | +| JakartaExpressionInjection.java:52:89:52:103 | valueExpression : ValueExpression | semmle.label | valueExpression : ValueExpression | | JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | semmle.label | lambdaExpression | | JakartaExpressionInjection.java:59:24:59:33 | expression : String | semmle.label | expression : String | | JakartaExpressionInjection.java:61:32:61:41 | expression | semmle.label | expression | | JakartaExpressionInjection.java:67:24:67:33 | expression : String | semmle.label | expression : String | | JakartaExpressionInjection.java:69:43:69:52 | expression | semmle.label | expression | | JakartaExpressionInjection.java:75:24:75:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:78:33:78:96 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression | +| JakartaExpressionInjection.java:78:72:78:81 | expression : String | semmle.label | expression : String | | JakartaExpressionInjection.java:79:13:79:13 | e | semmle.label | e | | JakartaExpressionInjection.java:85:24:85:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:88:33:88:96 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression | +| JakartaExpressionInjection.java:88:72:88:81 | expression : String | semmle.label | expression : String | | JakartaExpressionInjection.java:89:13:89:13 | e | semmle.label | e | | JakartaExpressionInjection.java:95:24:95:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:98:34:98:112 | createMethodExpression(...) : MethodExpression | semmle.label | createMethodExpression(...) : MethodExpression | +| JakartaExpressionInjection.java:98:74:98:83 | expression : String | semmle.label | expression : String | | JakartaExpressionInjection.java:99:13:99:13 | e | semmle.label | e | subpaths #select diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected index a5097188312..ca4eb5e9917 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected @@ -1,6 +1,5 @@ edges | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:25:39:25:52 | tokenCookieStr : String | -| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | | SensitiveCookieNotHttpOnly.java:25:28:25:64 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | | SensitiveCookieNotHttpOnly.java:25:39:25:52 | tokenCookieStr : String | SensitiveCookieNotHttpOnly.java:25:28:25:64 | new Cookie(...) : Cookie | | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | @@ -15,7 +14,6 @@ edges | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:89:36:89:51 | PRESTO_UI_COOKIE : String | -| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | | SensitiveCookieNotHttpOnly.java:89:25:89:57 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | | SensitiveCookieNotHttpOnly.java:89:36:89:51 | PRESTO_UI_COOKIE : String | SensitiveCookieNotHttpOnly.java:89:25:89:57 | new Cookie(...) : Cookie | | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie | diff --git a/java/ql/test/experimental/query-tests/security/CWE-470/UnsafeReflection.expected b/java/ql/test/experimental/query-tests/security/CWE-470/UnsafeReflection.expected index 606cf994976..2e37b50f3f7 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-470/UnsafeReflection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-470/UnsafeReflection.expected @@ -1,9 +1,19 @@ edges -| UnsafeReflection.java:21:28:21:60 | getParameter(...) : String | UnsafeReflection.java:25:29:25:62 | ...[...] | +| UnsafeReflection.java:21:28:21:60 | getParameter(...) : String | UnsafeReflection.java:24:41:24:49 | className : String | | UnsafeReflection.java:22:33:22:70 | getParameter(...) : String | UnsafeReflection.java:25:76:25:89 | parameterValue | -| UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | UnsafeReflection.java:39:13:39:41 | ...[...] | -| UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | UnsafeReflection.java:39:50:39:55 | object | +| UnsafeReflection.java:24:27:24:50 | forName(...) : Class | UnsafeReflection.java:25:29:25:33 | clazz : Class | +| UnsafeReflection.java:24:41:24:49 | className : String | UnsafeReflection.java:24:27:24:50 | forName(...) : Class | +| UnsafeReflection.java:25:29:25:33 | clazz : Class | UnsafeReflection.java:25:29:25:59 | getDeclaredConstructors(...) : Constructor[] | +| UnsafeReflection.java:25:29:25:59 | getDeclaredConstructors(...) : Constructor[] | UnsafeReflection.java:25:29:25:62 | ...[...] | +| UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | UnsafeReflection.java:37:49:37:57 | className : String | | UnsafeReflection.java:34:33:34:70 | getParameter(...) : String | UnsafeReflection.java:39:58:39:71 | parameterValue | +| UnsafeReflection.java:37:27:37:58 | loadClass(...) : Class | UnsafeReflection.java:38:29:38:33 | clazz : Class | +| UnsafeReflection.java:37:49:37:57 | className : String | UnsafeReflection.java:37:27:37:58 | loadClass(...) : Class | +| UnsafeReflection.java:38:29:38:33 | clazz : Class | UnsafeReflection.java:38:29:38:47 | newInstance(...) : Object | +| UnsafeReflection.java:38:29:38:33 | clazz : Class | UnsafeReflection.java:39:13:39:17 | clazz : Class | +| UnsafeReflection.java:38:29:38:47 | newInstance(...) : Object | UnsafeReflection.java:39:50:39:55 | object | +| UnsafeReflection.java:39:13:39:17 | clazz : Class | UnsafeReflection.java:39:13:39:38 | getDeclaredMethods(...) : Method[] | +| UnsafeReflection.java:39:13:39:38 | getDeclaredMethods(...) : Method[] | UnsafeReflection.java:39:13:39:41 | ...[...] | | UnsafeReflection.java:46:24:46:82 | beanIdOrClassName : String | UnsafeReflection.java:53:30:53:46 | beanIdOrClassName : String | | UnsafeReflection.java:46:132:46:168 | body : Map | UnsafeReflection.java:49:37:49:40 | body : Map | | UnsafeReflection.java:49:23:49:59 | (...)... : List | UnsafeReflection.java:53:67:53:73 | rawData : List | @@ -14,16 +24,33 @@ edges | UnsafeReflection.java:62:33:62:70 | getParameter(...) : String | UnsafeReflection.java:68:76:68:89 | parameterValue | | UnsafeReflection.java:77:33:77:70 | getParameter(...) : String | UnsafeReflection.java:83:76:83:89 | parameterValue | | UnsafeReflection.java:92:33:92:70 | getParameter(...) : String | UnsafeReflection.java:98:76:98:89 | parameterValue | -| UnsafeReflection.java:104:34:104:57 | beanIdOrClassName : String | UnsafeReflection.java:119:21:119:26 | method | -| UnsafeReflection.java:104:34:104:57 | beanIdOrClassName : String | UnsafeReflection.java:119:35:119:38 | bean | +| UnsafeReflection.java:104:34:104:57 | beanIdOrClassName : String | UnsafeReflection.java:108:39:108:55 | beanIdOrClassName : String | | UnsafeReflection.java:104:102:104:118 | data : List | UnsafeReflection.java:119:41:119:44 | data | +| UnsafeReflection.java:108:25:108:56 | forName(...) : Class | UnsafeReflection.java:109:31:109:39 | beanClass : Class | +| UnsafeReflection.java:108:39:108:55 | beanIdOrClassName : String | UnsafeReflection.java:108:25:108:56 | forName(...) : Class | +| UnsafeReflection.java:109:11:109:40 | getBean(...) : Object | UnsafeReflection.java:113:30:113:33 | bean : Object | +| UnsafeReflection.java:109:31:109:39 | beanClass : Class | UnsafeReflection.java:109:11:109:40 | getBean(...) : Object | +| UnsafeReflection.java:113:30:113:33 | bean : Object | UnsafeReflection.java:113:30:113:44 | getClass(...) : Class | +| UnsafeReflection.java:113:30:113:33 | bean : Object | UnsafeReflection.java:119:35:119:38 | bean | +| UnsafeReflection.java:113:30:113:44 | getClass(...) : Class | UnsafeReflection.java:113:30:113:57 | getMethods(...) : Method[] | +| UnsafeReflection.java:113:30:113:57 | getMethods(...) : Method[] | UnsafeReflection.java:119:21:119:26 | method | nodes | UnsafeReflection.java:21:28:21:60 | getParameter(...) : String | semmle.label | getParameter(...) : String | | UnsafeReflection.java:22:33:22:70 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| UnsafeReflection.java:24:27:24:50 | forName(...) : Class | semmle.label | forName(...) : Class | +| UnsafeReflection.java:24:41:24:49 | className : String | semmle.label | className : String | +| UnsafeReflection.java:25:29:25:33 | clazz : Class | semmle.label | clazz : Class | +| UnsafeReflection.java:25:29:25:59 | getDeclaredConstructors(...) : Constructor[] | semmle.label | getDeclaredConstructors(...) : Constructor[] | | UnsafeReflection.java:25:29:25:62 | ...[...] | semmle.label | ...[...] | | UnsafeReflection.java:25:76:25:89 | parameterValue | semmle.label | parameterValue | | UnsafeReflection.java:33:28:33:60 | getParameter(...) : String | semmle.label | getParameter(...) : String | | UnsafeReflection.java:34:33:34:70 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| UnsafeReflection.java:37:27:37:58 | loadClass(...) : Class | semmle.label | loadClass(...) : Class | +| UnsafeReflection.java:37:49:37:57 | className : String | semmle.label | className : String | +| UnsafeReflection.java:38:29:38:33 | clazz : Class | semmle.label | clazz : Class | +| UnsafeReflection.java:38:29:38:47 | newInstance(...) : Object | semmle.label | newInstance(...) : Object | +| UnsafeReflection.java:39:13:39:17 | clazz : Class | semmle.label | clazz : Class | +| UnsafeReflection.java:39:13:39:38 | getDeclaredMethods(...) : Method[] | semmle.label | getDeclaredMethods(...) : Method[] | | UnsafeReflection.java:39:13:39:41 | ...[...] | semmle.label | ...[...] | | UnsafeReflection.java:39:50:39:55 | object | semmle.label | object | | UnsafeReflection.java:39:58:39:71 | parameterValue | semmle.label | parameterValue | @@ -42,6 +69,13 @@ nodes | UnsafeReflection.java:98:76:98:89 | parameterValue | semmle.label | parameterValue | | UnsafeReflection.java:104:34:104:57 | beanIdOrClassName : String | semmle.label | beanIdOrClassName : String | | UnsafeReflection.java:104:102:104:118 | data : List | semmle.label | data : List | +| UnsafeReflection.java:108:25:108:56 | forName(...) : Class | semmle.label | forName(...) : Class | +| UnsafeReflection.java:108:39:108:55 | beanIdOrClassName : String | semmle.label | beanIdOrClassName : String | +| UnsafeReflection.java:109:11:109:40 | getBean(...) : Object | semmle.label | getBean(...) : Object | +| UnsafeReflection.java:109:31:109:39 | beanClass : Class | semmle.label | beanClass : Class | +| UnsafeReflection.java:113:30:113:33 | bean : Object | semmle.label | bean : Object | +| UnsafeReflection.java:113:30:113:44 | getClass(...) : Class | semmle.label | getClass(...) : Class | +| UnsafeReflection.java:113:30:113:57 | getMethods(...) : Method[] | semmle.label | getMethods(...) : Method[] | | UnsafeReflection.java:119:21:119:26 | method | semmle.label | method | | UnsafeReflection.java:119:35:119:38 | bean | semmle.label | bean | | UnsafeReflection.java:119:41:119:44 | data | semmle.label | data | diff --git a/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeUrlForward.expected b/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeUrlForward.expected index 57874f96e18..5d809244fdb 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeUrlForward.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeUrlForward.expected @@ -10,13 +10,23 @@ edges | UnsafeResourceGet2.java:17:20:17:40 | get(...) : String | UnsafeResourceGet2.java:19:93:19:99 | loadUrl | | UnsafeResourceGet2.java:32:32:32:79 | getRequestParameterMap(...) : Map | UnsafeResourceGet2.java:33:20:33:25 | params : Map | | UnsafeResourceGet2.java:33:20:33:25 | params : Map | UnsafeResourceGet2.java:33:20:33:40 | get(...) : String | -| UnsafeResourceGet2.java:33:20:33:40 | get(...) : String | UnsafeResourceGet2.java:37:20:37:22 | url | -| UnsafeResourceGet.java:32:23:32:56 | getParameter(...) : String | UnsafeResourceGet.java:41:20:41:22 | url | +| UnsafeResourceGet2.java:33:20:33:40 | get(...) : String | UnsafeResourceGet2.java:35:49:35:55 | loadUrl : String | +| UnsafeResourceGet2.java:35:13:35:56 | getResource(...) : URL | UnsafeResourceGet2.java:37:20:37:22 | url | +| UnsafeResourceGet2.java:35:49:35:55 | loadUrl : String | UnsafeResourceGet2.java:35:13:35:56 | getResource(...) : URL | +| UnsafeResourceGet.java:32:23:32:56 | getParameter(...) : String | UnsafeResourceGet.java:39:28:39:37 | requestUrl : String | +| UnsafeResourceGet.java:39:13:39:38 | getResource(...) : URL | UnsafeResourceGet.java:41:20:41:22 | url | +| UnsafeResourceGet.java:39:28:39:37 | requestUrl : String | UnsafeResourceGet.java:39:13:39:38 | getResource(...) : URL | | UnsafeResourceGet.java:111:24:111:58 | getParameter(...) : String | UnsafeResourceGet.java:115:68:115:78 | requestPath | -| UnsafeResourceGet.java:143:23:143:56 | getParameter(...) : String | UnsafeResourceGet.java:150:20:150:22 | url | +| UnsafeResourceGet.java:143:23:143:56 | getParameter(...) : String | UnsafeResourceGet.java:148:36:148:45 | requestUrl : String | +| UnsafeResourceGet.java:148:13:148:46 | getResource(...) : URL | UnsafeResourceGet.java:150:20:150:22 | url | +| UnsafeResourceGet.java:148:36:148:45 | requestUrl : String | UnsafeResourceGet.java:148:13:148:46 | getResource(...) : URL | | UnsafeResourceGet.java:181:24:181:58 | getParameter(...) : String | UnsafeResourceGet.java:189:68:189:78 | requestPath | -| UnsafeResourceGet.java:219:23:219:56 | getParameter(...) : String | UnsafeResourceGet.java:226:20:226:22 | url | -| UnsafeResourceGet.java:237:24:237:58 | getParameter(...) : String | UnsafeResourceGet.java:245:21:245:22 | rs : Resource | +| UnsafeResourceGet.java:219:23:219:56 | getParameter(...) : String | UnsafeResourceGet.java:224:53:224:62 | requestUrl : String | +| UnsafeResourceGet.java:224:13:224:63 | getResource(...) : URL | UnsafeResourceGet.java:226:20:226:22 | url | +| UnsafeResourceGet.java:224:53:224:62 | requestUrl : String | UnsafeResourceGet.java:224:13:224:63 | getResource(...) : URL | +| UnsafeResourceGet.java:237:24:237:58 | getParameter(...) : String | UnsafeResourceGet.java:241:33:241:43 | requestPath : String | +| UnsafeResourceGet.java:241:18:241:44 | getResource(...) : Resource | UnsafeResourceGet.java:245:21:245:22 | rs : Resource | +| UnsafeResourceGet.java:241:33:241:43 | requestPath : String | UnsafeResourceGet.java:241:18:241:44 | getResource(...) : Resource | | UnsafeResourceGet.java:245:21:245:22 | rs : Resource | UnsafeResourceGet.java:245:21:245:32 | getPath(...) | | UnsafeServletRequestDispatch.java:23:22:23:54 | getParameter(...) : String | UnsafeServletRequestDispatch.java:32:51:32:59 | returnURL | | UnsafeServletRequestDispatch.java:42:22:42:54 | getParameter(...) : String | UnsafeServletRequestDispatch.java:48:56:48:64 | returnURL | @@ -47,18 +57,28 @@ nodes | UnsafeResourceGet2.java:32:32:32:79 | getRequestParameterMap(...) : Map | semmle.label | getRequestParameterMap(...) : Map | | UnsafeResourceGet2.java:33:20:33:25 | params : Map | semmle.label | params : Map | | UnsafeResourceGet2.java:33:20:33:40 | get(...) : String | semmle.label | get(...) : String | +| UnsafeResourceGet2.java:35:13:35:56 | getResource(...) : URL | semmle.label | getResource(...) : URL | +| UnsafeResourceGet2.java:35:49:35:55 | loadUrl : String | semmle.label | loadUrl : String | | UnsafeResourceGet2.java:37:20:37:22 | url | semmle.label | url | | UnsafeResourceGet.java:32:23:32:56 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| UnsafeResourceGet.java:39:13:39:38 | getResource(...) : URL | semmle.label | getResource(...) : URL | +| UnsafeResourceGet.java:39:28:39:37 | requestUrl : String | semmle.label | requestUrl : String | | UnsafeResourceGet.java:41:20:41:22 | url | semmle.label | url | | UnsafeResourceGet.java:111:24:111:58 | getParameter(...) : String | semmle.label | getParameter(...) : String | | UnsafeResourceGet.java:115:68:115:78 | requestPath | semmle.label | requestPath | | UnsafeResourceGet.java:143:23:143:56 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| UnsafeResourceGet.java:148:13:148:46 | getResource(...) : URL | semmle.label | getResource(...) : URL | +| UnsafeResourceGet.java:148:36:148:45 | requestUrl : String | semmle.label | requestUrl : String | | UnsafeResourceGet.java:150:20:150:22 | url | semmle.label | url | | UnsafeResourceGet.java:181:24:181:58 | getParameter(...) : String | semmle.label | getParameter(...) : String | | UnsafeResourceGet.java:189:68:189:78 | requestPath | semmle.label | requestPath | | UnsafeResourceGet.java:219:23:219:56 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| UnsafeResourceGet.java:224:13:224:63 | getResource(...) : URL | semmle.label | getResource(...) : URL | +| UnsafeResourceGet.java:224:53:224:62 | requestUrl : String | semmle.label | requestUrl : String | | UnsafeResourceGet.java:226:20:226:22 | url | semmle.label | url | | UnsafeResourceGet.java:237:24:237:58 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| UnsafeResourceGet.java:241:18:241:44 | getResource(...) : Resource | semmle.label | getResource(...) : Resource | +| UnsafeResourceGet.java:241:33:241:43 | requestPath : String | semmle.label | requestPath : String | | UnsafeResourceGet.java:245:21:245:22 | rs : Resource | semmle.label | rs : Resource | | UnsafeResourceGet.java:245:21:245:32 | getPath(...) | semmle.label | getPath(...) | | UnsafeServletRequestDispatch.java:23:22:23:54 | getParameter(...) : String | semmle.label | getParameter(...) : String | diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected index 66c4093f011..2269d357ec6 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected @@ -13,7 +13,8 @@ edges | SpringUrlRedirect.java:89:38:89:55 | redirectUrl : String | SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String | | SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String | SpringUrlRedirect.java:91:27:91:49 | create(...) | | SpringUrlRedirect.java:96:39:96:56 | redirectUrl : String | SpringUrlRedirect.java:98:44:98:54 | redirectUrl : String | -| SpringUrlRedirect.java:98:33:98:55 | create(...) : URI | SpringUrlRedirect.java:100:37:100:47 | httpHeaders | +| SpringUrlRedirect.java:98:9:98:19 | httpHeaders : HttpHeaders | SpringUrlRedirect.java:100:37:100:47 | httpHeaders | +| SpringUrlRedirect.java:98:33:98:55 | create(...) : URI | SpringUrlRedirect.java:98:9:98:19 | httpHeaders : HttpHeaders | | SpringUrlRedirect.java:98:44:98:54 | redirectUrl : String | SpringUrlRedirect.java:98:33:98:55 | create(...) : URI | | SpringUrlRedirect.java:104:39:104:56 | redirectUrl : String | SpringUrlRedirect.java:106:37:106:47 | redirectUrl : String | | SpringUrlRedirect.java:106:9:106:19 | httpHeaders [post update] : HttpHeaders | SpringUrlRedirect.java:108:68:108:78 | httpHeaders | @@ -31,7 +32,8 @@ edges | SpringUrlRedirect.java:122:37:122:47 | redirectUrl : String | SpringUrlRedirect.java:122:9:122:19 | httpHeaders [post update] : HttpHeaders | | SpringUrlRedirect.java:122:37:122:47 | redirectUrl : String | SpringUrlRedirect.java:122:9:122:19 | httpHeaders [post update] : HttpHeaders [, ] : String | | SpringUrlRedirect.java:128:33:128:50 | redirectUrl : String | SpringUrlRedirect.java:130:44:130:54 | redirectUrl : String | -| SpringUrlRedirect.java:130:33:130:55 | create(...) : URI | SpringUrlRedirect.java:132:49:132:59 | httpHeaders | +| SpringUrlRedirect.java:130:9:130:19 | httpHeaders : HttpHeaders | SpringUrlRedirect.java:132:49:132:59 | httpHeaders | +| SpringUrlRedirect.java:130:33:130:55 | create(...) : URI | SpringUrlRedirect.java:130:9:130:19 | httpHeaders : HttpHeaders | | SpringUrlRedirect.java:130:44:130:54 | redirectUrl : String | SpringUrlRedirect.java:130:33:130:55 | create(...) : URI | nodes | SpringUrlRedirect.java:17:30:17:47 | redirectUrl : String | semmle.label | redirectUrl : String | @@ -57,6 +59,7 @@ nodes | SpringUrlRedirect.java:91:27:91:49 | create(...) | semmle.label | create(...) | | SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String | semmle.label | redirectUrl : String | | SpringUrlRedirect.java:96:39:96:56 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:98:9:98:19 | httpHeaders : HttpHeaders | semmle.label | httpHeaders : HttpHeaders | | SpringUrlRedirect.java:98:33:98:55 | create(...) : URI | semmle.label | create(...) : URI | | SpringUrlRedirect.java:98:44:98:54 | redirectUrl : String | semmle.label | redirectUrl : String | | SpringUrlRedirect.java:100:37:100:47 | httpHeaders | semmle.label | httpHeaders | @@ -76,6 +79,7 @@ nodes | SpringUrlRedirect.java:122:37:122:47 | redirectUrl : String | semmle.label | redirectUrl : String | | SpringUrlRedirect.java:124:49:124:59 | httpHeaders | semmle.label | httpHeaders | | SpringUrlRedirect.java:128:33:128:50 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:130:9:130:19 | httpHeaders : HttpHeaders | semmle.label | httpHeaders : HttpHeaders | | SpringUrlRedirect.java:130:33:130:55 | create(...) : URI | semmle.label | create(...) : URI | | SpringUrlRedirect.java:130:44:130:54 | redirectUrl : String | semmle.label | redirectUrl : String | | SpringUrlRedirect.java:132:49:132:59 | httpHeaders | semmle.label | httpHeaders | diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected index ec022c75802..47dfb5f426e 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected @@ -1,14 +1,22 @@ edges -| XQueryInjection.java:45:23:45:50 | getParameter(...) : String | XQueryInjection.java:51:35:51:38 | xqpe | +| XQueryInjection.java:45:23:45:50 | getParameter(...) : String | XQueryInjection.java:50:60:50:64 | query : String | +| XQueryInjection.java:50:37:50:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:51:35:51:38 | xqpe | +| XQueryInjection.java:50:60:50:64 | query : String | XQueryInjection.java:50:37:50:65 | prepareExpression(...) : XQPreparedExpression | | XQueryInjection.java:59:23:59:50 | getParameter(...) : String | XQueryInjection.java:65:53:65:57 | query | -| XQueryInjection.java:73:32:73:59 | nameStr : String | XQueryInjection.java:79:35:79:38 | xqpe | +| XQueryInjection.java:73:32:73:59 | nameStr : String | XQueryInjection.java:78:60:78:64 | query : String | +| XQueryInjection.java:78:37:78:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:79:35:79:38 | xqpe | +| XQueryInjection.java:78:60:78:64 | query : String | XQueryInjection.java:78:37:78:65 | prepareExpression(...) : XQPreparedExpression | | XQueryInjection.java:86:33:86:60 | nameStr : String | XQueryInjection.java:92:53:92:57 | query | -| XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:104:35:104:38 | xqpe | +| XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:103:60:103:63 | name : ServletInputStream | +| XQueryInjection.java:103:37:103:64 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:104:35:104:38 | xqpe | +| XQueryInjection.java:103:60:103:63 | name : ServletInputStream | XQueryInjection.java:103:37:103:64 | prepareExpression(...) : XQPreparedExpression | | XQueryInjection.java:112:28:112:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:116:53:116:56 | name | | XQueryInjection.java:124:28:124:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:125:70:125:73 | name : ServletInputStream | -| XQueryInjection.java:125:29:125:75 | new BufferedReader(...) : BufferedReader | XQueryInjection.java:129:35:129:38 | xqpe | +| XQueryInjection.java:125:29:125:75 | new BufferedReader(...) : BufferedReader | XQueryInjection.java:128:60:128:61 | br : BufferedReader | | XQueryInjection.java:125:48:125:74 | new InputStreamReader(...) : InputStreamReader | XQueryInjection.java:125:29:125:75 | new BufferedReader(...) : BufferedReader | | XQueryInjection.java:125:70:125:73 | name : ServletInputStream | XQueryInjection.java:125:48:125:74 | new InputStreamReader(...) : InputStreamReader | +| XQueryInjection.java:128:37:128:62 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:129:35:129:38 | xqpe | +| XQueryInjection.java:128:60:128:61 | br : BufferedReader | XQueryInjection.java:128:37:128:62 | prepareExpression(...) : XQPreparedExpression | | XQueryInjection.java:137:28:137:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:138:70:138:73 | name : ServletInputStream | | XQueryInjection.java:138:29:138:75 | new BufferedReader(...) : BufferedReader | XQueryInjection.java:142:53:142:54 | br | | XQueryInjection.java:138:48:138:74 | new InputStreamReader(...) : InputStreamReader | XQueryInjection.java:138:29:138:75 | new BufferedReader(...) : BufferedReader | @@ -20,14 +28,20 @@ edges | XQueryInjection.java:158:70:158:71 | is : ServletInputStream | XQueryInjection.java:158:48:158:72 | new InputStreamReader(...) : InputStreamReader | nodes | XQueryInjection.java:45:23:45:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:50:37:50:65 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | +| XQueryInjection.java:50:60:50:64 | query : String | semmle.label | query : String | | XQueryInjection.java:51:35:51:38 | xqpe | semmle.label | xqpe | | XQueryInjection.java:59:23:59:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | | XQueryInjection.java:65:53:65:57 | query | semmle.label | query | | XQueryInjection.java:73:32:73:59 | nameStr : String | semmle.label | nameStr : String | +| XQueryInjection.java:78:37:78:65 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | +| XQueryInjection.java:78:60:78:64 | query : String | semmle.label | query : String | | XQueryInjection.java:79:35:79:38 | xqpe | semmle.label | xqpe | | XQueryInjection.java:86:33:86:60 | nameStr : String | semmle.label | nameStr : String | | XQueryInjection.java:92:53:92:57 | query | semmle.label | query | | XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:103:37:103:64 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | +| XQueryInjection.java:103:60:103:63 | name : ServletInputStream | semmle.label | name : ServletInputStream | | XQueryInjection.java:104:35:104:38 | xqpe | semmle.label | xqpe | | XQueryInjection.java:112:28:112:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | | XQueryInjection.java:116:53:116:56 | name | semmle.label | name | @@ -35,6 +49,8 @@ nodes | XQueryInjection.java:125:29:125:75 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader | | XQueryInjection.java:125:48:125:74 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader | | XQueryInjection.java:125:70:125:73 | name : ServletInputStream | semmle.label | name : ServletInputStream | +| XQueryInjection.java:128:37:128:62 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | +| XQueryInjection.java:128:60:128:61 | br : BufferedReader | semmle.label | br : BufferedReader | | XQueryInjection.java:129:35:129:38 | xqpe | semmle.label | xqpe | | XQueryInjection.java:137:28:137:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | | XQueryInjection.java:138:29:138:75 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader | diff --git a/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.ql b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.ql index 1f245499552..65576a7c19d 100644 --- a/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.ql +++ b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.ql @@ -1,6 +1,7 @@ import java query predicate classExprs(Expr e, string tstr) { + exists(e.getFile().getRelativePath()) and tstr = e.getType().toString() and tstr.matches("%Class%") } diff --git a/java/ql/test/kotlin/library-tests/annotation_classes/PrintAst.expected b/java/ql/test/kotlin/library-tests/annotation_classes/PrintAst.expected index 0ff19b747fa..643c416b736 100644 --- a/java/ql/test/kotlin/library-tests/annotation_classes/PrintAst.expected +++ b/java/ql/test/kotlin/library-tests/annotation_classes/PrintAst.expected @@ -169,15 +169,18 @@ def.kt: # 33| 0: [SuperConstructorInvocationStmt] super(...) # 33| 1: [BlockStmt] { ... } # 34| 5: [Class] Y -# 0| 2: [Method] valueOf +# 0| 2: [Method] getEntries +# 0| 3: [TypeAccess] EnumEntries +# 0| 0: [TypeAccess] Y +# 0| 3: [Method] valueOf # 0| 3: [TypeAccess] Y #-----| 4: (Parameters) # 0| 0: [Parameter] value # 0| 0: [TypeAccess] String -# 0| 3: [Method] values +# 0| 4: [Method] values # 0| 3: [TypeAccess] Y[] # 0| 0: [TypeAccess] Y -# 34| 4: [Constructor] Y +# 34| 5: [Constructor] Y # 34| 5: [BlockStmt] { ... } # 34| 0: [ExprStmt] ; # 34| 0: [ClassInstanceExpr] new Enum(...) @@ -186,15 +189,15 @@ def.kt: # 34| 0: [NullLiteral] null # 34| 1: [IntegerLiteral] 0 # 34| 1: [BlockStmt] { ... } -# 35| 5: [FieldDeclaration] Y A; +# 35| 6: [FieldDeclaration] Y A; # 35| -1: [TypeAccess] Y # 35| 0: [ClassInstanceExpr] new Y(...) # 35| -3: [TypeAccess] Y -# 35| 6: [FieldDeclaration] Y B; +# 35| 7: [FieldDeclaration] Y B; # 35| -1: [TypeAccess] Y # 35| 0: [ClassInstanceExpr] new Y(...) # 35| -3: [TypeAccess] Y -# 35| 7: [FieldDeclaration] Y C; +# 35| 8: [FieldDeclaration] Y C; # 35| -1: [TypeAccess] Y # 35| 0: [ClassInstanceExpr] new Y(...) # 35| -3: [TypeAccess] Y diff --git a/java/ql/test/kotlin/library-tests/classes/PrintAst.expected b/java/ql/test/kotlin/library-tests/classes/PrintAst.expected index 6f19fb37fee..8da4a380a80 100644 --- a/java/ql/test/kotlin/library-tests/classes/PrintAst.expected +++ b/java/ql/test/kotlin/library-tests/classes/PrintAst.expected @@ -160,15 +160,18 @@ classes.kt: # 42| -1: [TypeAccess] int # 42| 0: [IntegerLiteral] 3 # 49| 11: [Class] Direction -# 0| 2: [Method] valueOf +# 0| 2: [Method] getEntries +# 0| 3: [TypeAccess] EnumEntries +# 0| 0: [TypeAccess] Direction +# 0| 3: [Method] valueOf # 0| 3: [TypeAccess] Direction #-----| 4: (Parameters) # 0| 0: [Parameter] value # 0| 0: [TypeAccess] String -# 0| 3: [Method] values +# 0| 4: [Method] values # 0| 3: [TypeAccess] Direction[] # 0| 0: [TypeAccess] Direction -# 49| 4: [Constructor] Direction +# 49| 5: [Constructor] Direction # 49| 5: [BlockStmt] { ... } # 49| 0: [ExprStmt] ; # 49| 0: [ClassInstanceExpr] new Enum(...) @@ -177,32 +180,35 @@ classes.kt: # 49| 0: [NullLiteral] null # 49| 1: [IntegerLiteral] 0 # 49| 1: [BlockStmt] { ... } -# 50| 5: [FieldDeclaration] Direction NORTH; +# 50| 6: [FieldDeclaration] Direction NORTH; # 50| -1: [TypeAccess] Direction # 50| 0: [ClassInstanceExpr] new Direction(...) # 50| -3: [TypeAccess] Direction -# 50| 6: [FieldDeclaration] Direction SOUTH; +# 50| 7: [FieldDeclaration] Direction SOUTH; # 50| -1: [TypeAccess] Direction # 50| 0: [ClassInstanceExpr] new Direction(...) # 50| -3: [TypeAccess] Direction -# 50| 7: [FieldDeclaration] Direction WEST; +# 50| 8: [FieldDeclaration] Direction WEST; # 50| -1: [TypeAccess] Direction # 50| 0: [ClassInstanceExpr] new Direction(...) # 50| -3: [TypeAccess] Direction -# 50| 8: [FieldDeclaration] Direction EAST; +# 50| 9: [FieldDeclaration] Direction EAST; # 50| -1: [TypeAccess] Direction # 50| 0: [ClassInstanceExpr] new Direction(...) # 50| -3: [TypeAccess] Direction # 53| 12: [Class] Color -# 0| 2: [Method] valueOf +# 0| 2: [Method] getEntries +# 0| 3: [TypeAccess] EnumEntries +# 0| 0: [TypeAccess] Color +# 0| 3: [Method] valueOf # 0| 3: [TypeAccess] Color #-----| 4: (Parameters) # 0| 0: [Parameter] value # 0| 0: [TypeAccess] String -# 0| 3: [Method] values +# 0| 4: [Method] values # 0| 3: [TypeAccess] Color[] # 0| 0: [TypeAccess] Color -# 53| 4: [Constructor] Color +# 53| 5: [Constructor] Color #-----| 4: (Parameters) # 53| 0: [Parameter] rgb # 53| 0: [TypeAccess] int @@ -217,26 +223,26 @@ classes.kt: # 53| 0: [ExprStmt] ; # 53| 0: [KtInitializerAssignExpr] ...=... # 53| 0: [VarAccess] rgb -# 53| 5: [Method] getRgb +# 53| 6: [Method] getRgb # 53| 3: [TypeAccess] int # 53| 5: [BlockStmt] { ... } # 53| 0: [ReturnStmt] return ... # 53| 0: [VarAccess] this.rgb # 53| -1: [ThisAccess] this -# 53| 6: [FieldDeclaration] int rgb; +# 53| 7: [FieldDeclaration] int rgb; # 53| -1: [TypeAccess] int # 53| 0: [VarAccess] rgb -# 54| 7: [FieldDeclaration] Color RED; +# 54| 8: [FieldDeclaration] Color RED; # 54| -1: [TypeAccess] Color # 54| 0: [ClassInstanceExpr] new Color(...) # 54| -3: [TypeAccess] Color # 54| 0: [IntegerLiteral] 16711680 -# 55| 8: [FieldDeclaration] Color GREEN; +# 55| 9: [FieldDeclaration] Color GREEN; # 55| -1: [TypeAccess] Color # 55| 0: [ClassInstanceExpr] new Color(...) # 55| -3: [TypeAccess] Color # 55| 0: [IntegerLiteral] 65280 -# 56| 9: [FieldDeclaration] Color BLUE; +# 56| 10: [FieldDeclaration] Color BLUE; # 56| -1: [TypeAccess] Color # 56| 0: [ClassInstanceExpr] new Color(...) # 56| -3: [TypeAccess] Color diff --git a/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.expected b/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.expected new file mode 100644 index 00000000000..62e8d2b0ff5 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.expected @@ -0,0 +1,2 @@ +| apply.kt:6:9:6:41 | apply(...) | +| apply.kt:7:14:7:40 | apply(...) | diff --git a/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.kt b/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.kt new file mode 100644 index 00000000000..8d5373d081f --- /dev/null +++ b/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.kt @@ -0,0 +1,9 @@ +class ApplyFlowTest { + fun taint(t: T) = t + fun sink(s: String) { } + + fun test(input: String) { + taint(input).apply { sink(this) } // $ hasValueFlow + sink(taint(input).apply { this }) // $ hasValueFlow + } +} diff --git a/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.ql b/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.ql new file mode 100644 index 00000000000..540cea703f0 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/dataflow/summaries/apply.ql @@ -0,0 +1,5 @@ +import java +import semmle.code.java.frameworks.kotlin.Kotlin + +from KotlinApply a +select a diff --git a/java/ql/test/kotlin/library-tests/exprs/PrintAst.expected b/java/ql/test/kotlin/library-tests/exprs/PrintAst.expected index 3b4cc35d4d4..fbcea06528f 100644 --- a/java/ql/test/kotlin/library-tests/exprs/PrintAst.expected +++ b/java/ql/test/kotlin/library-tests/exprs/PrintAst.expected @@ -3344,15 +3344,18 @@ exprs.kt: # 154| 0: [SuperConstructorInvocationStmt] super(...) # 154| 1: [BlockStmt] { ... } # 174| 6: [Class] Direction -# 0| 2: [Method] valueOf +# 0| 2: [Method] getEntries +# 0| 3: [TypeAccess] EnumEntries +# 0| 0: [TypeAccess] Direction +# 0| 3: [Method] valueOf # 0| 3: [TypeAccess] Direction #-----| 4: (Parameters) # 0| 0: [Parameter] value # 0| 0: [TypeAccess] String -# 0| 3: [Method] values +# 0| 4: [Method] values # 0| 3: [TypeAccess] Direction[] # 0| 0: [TypeAccess] Direction -# 174| 4: [Constructor] Direction +# 174| 5: [Constructor] Direction # 174| 5: [BlockStmt] { ... } # 174| 0: [ExprStmt] ; # 174| 0: [ClassInstanceExpr] new Enum(...) @@ -3361,32 +3364,35 @@ exprs.kt: # 174| 0: [NullLiteral] null # 174| 1: [IntegerLiteral] 0 # 174| 1: [BlockStmt] { ... } -# 175| 5: [FieldDeclaration] Direction NORTH; +# 175| 6: [FieldDeclaration] Direction NORTH; # 175| -1: [TypeAccess] Direction # 175| 0: [ClassInstanceExpr] new Direction(...) # 175| -3: [TypeAccess] Direction -# 175| 6: [FieldDeclaration] Direction SOUTH; +# 175| 7: [FieldDeclaration] Direction SOUTH; # 175| -1: [TypeAccess] Direction # 175| 0: [ClassInstanceExpr] new Direction(...) # 175| -3: [TypeAccess] Direction -# 175| 7: [FieldDeclaration] Direction WEST; +# 175| 8: [FieldDeclaration] Direction WEST; # 175| -1: [TypeAccess] Direction # 175| 0: [ClassInstanceExpr] new Direction(...) # 175| -3: [TypeAccess] Direction -# 175| 8: [FieldDeclaration] Direction EAST; +# 175| 9: [FieldDeclaration] Direction EAST; # 175| -1: [TypeAccess] Direction # 175| 0: [ClassInstanceExpr] new Direction(...) # 175| -3: [TypeAccess] Direction # 178| 7: [Class] Color -# 0| 2: [Method] valueOf +# 0| 2: [Method] getEntries +# 0| 3: [TypeAccess] EnumEntries +# 0| 0: [TypeAccess] Color +# 0| 3: [Method] valueOf # 0| 3: [TypeAccess] Color #-----| 4: (Parameters) # 0| 0: [Parameter] value # 0| 0: [TypeAccess] String -# 0| 3: [Method] values +# 0| 4: [Method] values # 0| 3: [TypeAccess] Color[] # 0| 0: [TypeAccess] Color -# 178| 4: [Constructor] Color +# 178| 5: [Constructor] Color #-----| 4: (Parameters) # 178| 0: [Parameter] rgb # 178| 0: [TypeAccess] int @@ -3401,26 +3407,26 @@ exprs.kt: # 178| 0: [ExprStmt] ; # 178| 0: [KtInitializerAssignExpr] ...=... # 178| 0: [VarAccess] rgb -# 178| 5: [Method] getRgb +# 178| 6: [Method] getRgb # 178| 3: [TypeAccess] int # 178| 5: [BlockStmt] { ... } # 178| 0: [ReturnStmt] return ... # 178| 0: [VarAccess] this.rgb # 178| -1: [ThisAccess] this -# 178| 6: [FieldDeclaration] int rgb; +# 178| 7: [FieldDeclaration] int rgb; # 178| -1: [TypeAccess] int # 178| 0: [VarAccess] rgb -# 179| 7: [FieldDeclaration] Color RED; +# 179| 8: [FieldDeclaration] Color RED; # 179| -1: [TypeAccess] Color # 179| 0: [ClassInstanceExpr] new Color(...) # 179| -3: [TypeAccess] Color # 179| 0: [IntegerLiteral] 16711680 -# 180| 8: [FieldDeclaration] Color GREEN; +# 180| 9: [FieldDeclaration] Color GREEN; # 180| -1: [TypeAccess] Color # 180| 0: [ClassInstanceExpr] new Color(...) # 180| -3: [TypeAccess] Color # 180| 0: [IntegerLiteral] 65280 -# 181| 9: [FieldDeclaration] Color BLUE; +# 181| 10: [FieldDeclaration] Color BLUE; # 181| -1: [TypeAccess] Color # 181| 0: [ClassInstanceExpr] new Color(...) # 181| -3: [TypeAccess] Color diff --git a/java/ql/test/kotlin/library-tests/exprs/exprs.expected b/java/ql/test/kotlin/library-tests/exprs/exprs.expected index 07d57c444e4..69c4f096de9 100644 --- a/java/ql/test/kotlin/library-tests/exprs/exprs.expected +++ b/java/ql/test/kotlin/library-tests/exprs/exprs.expected @@ -885,10 +885,14 @@ | delegatedProperties.kt:87:34:87:46 | this | delegatedProperties.kt:87:34:87:46 | invoke | ThisAccess | | exprs.kt:0:0:0:0 | Color | file://:0:0:0:0 | | TypeAccess | | exprs.kt:0:0:0:0 | Color | file://:0:0:0:0 | | TypeAccess | +| exprs.kt:0:0:0:0 | Color | file://:0:0:0:0 | | TypeAccess | | exprs.kt:0:0:0:0 | Color[] | file://:0:0:0:0 | | TypeAccess | | exprs.kt:0:0:0:0 | Direction | file://:0:0:0:0 | | TypeAccess | | exprs.kt:0:0:0:0 | Direction | file://:0:0:0:0 | | TypeAccess | +| exprs.kt:0:0:0:0 | Direction | file://:0:0:0:0 | | TypeAccess | | exprs.kt:0:0:0:0 | Direction[] | file://:0:0:0:0 | | TypeAccess | +| exprs.kt:0:0:0:0 | EnumEntries | file://:0:0:0:0 | | TypeAccess | +| exprs.kt:0:0:0:0 | EnumEntries | file://:0:0:0:0 | | TypeAccess | | exprs.kt:0:0:0:0 | String | file://:0:0:0:0 | | TypeAccess | | exprs.kt:0:0:0:0 | String | file://:0:0:0:0 | | TypeAccess | | exprs.kt:4:1:142:1 | int | file://:0:0:0:0 | | TypeAccess | diff --git a/java/ql/test/kotlin/library-tests/exprs_typeaccess/PrintAst.expected b/java/ql/test/kotlin/library-tests/exprs_typeaccess/PrintAst.expected index 5af83a46a01..56a295cfaf7 100644 --- a/java/ql/test/kotlin/library-tests/exprs_typeaccess/PrintAst.expected +++ b/java/ql/test/kotlin/library-tests/exprs_typeaccess/PrintAst.expected @@ -74,15 +74,18 @@ A.kt: # 20| 0: [VarAccess] B.x # 20| -1: [TypeAccess] B # 23| 11: [Class] Enu -# 0| 2: [Method] valueOf +# 0| 2: [Method] getEntries +# 0| 3: [TypeAccess] EnumEntries +# 0| 0: [TypeAccess] Enu +# 0| 3: [Method] valueOf # 0| 3: [TypeAccess] Enu #-----| 4: (Parameters) # 0| 0: [Parameter] value # 0| 0: [TypeAccess] String -# 0| 3: [Method] values +# 0| 4: [Method] values # 0| 3: [TypeAccess] Enu[] # 0| 0: [TypeAccess] Enu -# 23| 4: [Constructor] Enu +# 23| 5: [Constructor] Enu # 23| 5: [BlockStmt] { ... } # 23| 0: [ExprStmt] ; # 23| 0: [ClassInstanceExpr] new Enum(...) @@ -91,15 +94,15 @@ A.kt: # 23| 0: [NullLiteral] null # 23| 1: [IntegerLiteral] 0 # 23| 1: [BlockStmt] { ... } -# 24| 5: [FieldDeclaration] Enu A; +# 24| 6: [FieldDeclaration] Enu A; # 24| -1: [TypeAccess] Enu # 24| 0: [ClassInstanceExpr] new Enu(...) # 24| -3: [TypeAccess] Enu -# 24| 6: [FieldDeclaration] Enu B; +# 24| 7: [FieldDeclaration] Enu B; # 24| -1: [TypeAccess] Enu # 24| 0: [ClassInstanceExpr] new Enu(...) # 24| -3: [TypeAccess] Enu -# 24| 7: [FieldDeclaration] Enu C; +# 24| 8: [FieldDeclaration] Enu C; # 24| -1: [TypeAccess] Enu # 24| 0: [ClassInstanceExpr] new Enu(...) # 24| -3: [TypeAccess] Enu diff --git a/java/ql/test/kotlin/library-tests/methods/exprs.expected b/java/ql/test/kotlin/library-tests/methods/exprs.expected index 5de17a12bf9..ed0a9fbd6d8 100644 --- a/java/ql/test/kotlin/library-tests/methods/exprs.expected +++ b/java/ql/test/kotlin/library-tests/methods/exprs.expected @@ -225,7 +225,11 @@ | delegates.kt:10:33:10:35 | new | VarAccess | | enumClass.kt:0:0:0:0 | EnumClass | TypeAccess | | enumClass.kt:0:0:0:0 | EnumClass | TypeAccess | +| enumClass.kt:0:0:0:0 | EnumClass | TypeAccess | | enumClass.kt:0:0:0:0 | EnumClass[] | TypeAccess | +| enumClass.kt:0:0:0:0 | EnumEntries | TypeAccess | +| enumClass.kt:0:0:0:0 | EnumEntries | TypeAccess | +| enumClass.kt:0:0:0:0 | EnumWithFunctions | TypeAccess | | enumClass.kt:0:0:0:0 | EnumWithFunctions | TypeAccess | | enumClass.kt:0:0:0:0 | EnumWithFunctions | TypeAccess | | enumClass.kt:0:0:0:0 | EnumWithFunctions[] | TypeAccess | diff --git a/java/ql/test/kotlin/library-tests/methods/methods.expected b/java/ql/test/kotlin/library-tests/methods/methods.expected index 89cdd03f303..e254e862090 100644 --- a/java/ql/test/kotlin/library-tests/methods/methods.expected +++ b/java/ql/test/kotlin/library-tests/methods/methods.expected @@ -26,10 +26,12 @@ methods | delegates.kt:8:32:11:5 | new KMutableProperty1(...) { ... } | delegates.kt:8:32:11:5 | set | set(MyClass,java.lang.String) | override, public | | | delegates.kt:8:66:11:5 | new Function3,String,String,Unit>(...) { ... } | delegates.kt:8:66:11:5 | invoke | invoke(kotlin.reflect.KProperty,java.lang.String,java.lang.String) | final, override, public | | | enumClass.kt:1:1:4:1 | EnumClass | enumClass.kt:0:0:0:0 | | () | static | Compiler generated | +| enumClass.kt:1:1:4:1 | EnumClass | enumClass.kt:0:0:0:0 | getEntries | getEntries() | final, public, static | Compiler generated | | enumClass.kt:1:1:4:1 | EnumClass | enumClass.kt:0:0:0:0 | valueOf | valueOf(java.lang.String) | final, public, static | Compiler generated | | enumClass.kt:1:1:4:1 | EnumClass | enumClass.kt:0:0:0:0 | values | values() | final, public, static | Compiler generated | | enumClass.kt:1:1:4:1 | EnumClass | enumClass.kt:1:22:1:31 | getV | getV() | final, public | Compiler generated | | enumClass.kt:6:1:16:1 | EnumWithFunctions | enumClass.kt:0:0:0:0 | | () | static | Compiler generated | +| enumClass.kt:6:1:16:1 | EnumWithFunctions | enumClass.kt:0:0:0:0 | getEntries | getEntries() | final, public, static | Compiler generated | | enumClass.kt:6:1:16:1 | EnumWithFunctions | enumClass.kt:0:0:0:0 | valueOf | valueOf(java.lang.String) | final, public, static | Compiler generated | | enumClass.kt:6:1:16:1 | EnumWithFunctions | enumClass.kt:0:0:0:0 | values | values() | final, public, static | Compiler generated | | enumClass.kt:6:1:16:1 | EnumWithFunctions | enumClass.kt:13:12:13:29 | f | f(int) | abstract, public | | diff --git a/java/ql/test/library-tests/file_classes/A.kt b/java/ql/test/library-tests/file_classes/A.kt new file mode 100644 index 00000000000..8ece2422c02 --- /dev/null +++ b/java/ql/test/library-tests/file_classes/A.kt @@ -0,0 +1,2 @@ +fun a() { +} diff --git a/java/ql/test/library-tests/file_classes/B.kt b/java/ql/test/library-tests/file_classes/B.kt new file mode 100644 index 00000000000..0f1fb9d6fd8 --- /dev/null +++ b/java/ql/test/library-tests/file_classes/B.kt @@ -0,0 +1,3 @@ +fun b() { + a() +} diff --git a/java/ql/test/library-tests/file_classes/C.kt b/java/ql/test/library-tests/file_classes/C.kt new file mode 100644 index 00000000000..2b433c8f2f6 --- /dev/null +++ b/java/ql/test/library-tests/file_classes/C.kt @@ -0,0 +1,3 @@ +class C { + fun c() {} +} diff --git a/java/ql/test/library-tests/file_classes/classes.expected b/java/ql/test/library-tests/file_classes/classes.expected new file mode 100644 index 00000000000..ea6fece1c4c --- /dev/null +++ b/java/ql/test/library-tests/file_classes/classes.expected @@ -0,0 +1,3 @@ +| A.kt:0:0:0:0 | AKt | true | +| B.kt:0:0:0:0 | BKt | true | +| C.kt:1:1:3:1 | C | false | diff --git a/java/ql/test/library-tests/file_classes/classes.ql b/java/ql/test/library-tests/file_classes/classes.ql new file mode 100644 index 00000000000..9f78c7f3b0f --- /dev/null +++ b/java/ql/test/library-tests/file_classes/classes.ql @@ -0,0 +1,5 @@ +import java + +from Class c +where c.fromSource() +select c, any(boolean b | if c.isFileClass() then b = true else b = false) diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected index 86f20972e2e..fab1b8e43bc 100644 --- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected +++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected @@ -9,11 +9,16 @@ edges | Test.java:80:31:80:32 | br : BufferedReader | Test.java:80:31:80:43 | readLine(...) : String | | Test.java:80:31:80:43 | readLine(...) : String | Test.java:82:67:82:81 | ... + ... | | Test.java:88:17:88:37 | getHostName(...) : String | Test.java:90:26:90:29 | temp | -| Test.java:95:14:95:34 | getHostName(...) : String | Test.java:97:12:97:33 | new URI(...) | -| Test.java:95:14:95:34 | getHostName(...) : String | Test.java:98:12:98:33 | new URI(...) | -| Test.java:95:14:95:34 | getHostName(...) : String | Test.java:99:12:99:33 | new URI(...) | -| Test.java:95:14:95:34 | getHostName(...) : String | Test.java:100:12:100:45 | new URI(...) | -| Test.java:95:14:95:34 | getHostName(...) : String | Test.java:101:12:101:54 | new URI(...) | +| Test.java:95:14:95:34 | getHostName(...) : String | Test.java:97:26:97:26 | t : String | +| Test.java:97:26:97:26 | t : String | Test.java:97:12:97:33 | new URI(...) | +| Test.java:97:26:97:26 | t : String | Test.java:98:23:98:23 | t : String | +| Test.java:98:23:98:23 | t : String | Test.java:98:12:98:33 | new URI(...) | +| Test.java:98:23:98:23 | t : String | Test.java:99:29:99:29 | t : String | +| Test.java:99:29:99:29 | t : String | Test.java:99:12:99:33 | new URI(...) | +| Test.java:99:29:99:29 | t : String | Test.java:100:32:100:32 | t : String | +| Test.java:100:32:100:32 | t : String | Test.java:100:12:100:45 | new URI(...) | +| Test.java:100:32:100:32 | t : String | Test.java:101:41:101:41 | t : String | +| Test.java:101:41:101:41 | t : String | Test.java:101:12:101:54 | new URI(...) | | mad/Test.java:29:16:29:36 | getHostName(...) : String | mad/Test.java:34:61:34:68 | source(...) : String | | mad/Test.java:29:16:29:36 | getHostName(...) : String | mad/Test.java:36:41:36:48 | source(...) : String | | mad/Test.java:29:16:29:36 | getHostName(...) : String | mad/Test.java:38:56:38:63 | source(...) : String | @@ -134,10 +139,15 @@ nodes | Test.java:90:26:90:29 | temp | semmle.label | temp | | Test.java:95:14:95:34 | getHostName(...) : String | semmle.label | getHostName(...) : String | | Test.java:97:12:97:33 | new URI(...) | semmle.label | new URI(...) | +| Test.java:97:26:97:26 | t : String | semmle.label | t : String | | Test.java:98:12:98:33 | new URI(...) | semmle.label | new URI(...) | +| Test.java:98:23:98:23 | t : String | semmle.label | t : String | | Test.java:99:12:99:33 | new URI(...) | semmle.label | new URI(...) | +| Test.java:99:29:99:29 | t : String | semmle.label | t : String | | Test.java:100:12:100:45 | new URI(...) | semmle.label | new URI(...) | +| Test.java:100:32:100:32 | t : String | semmle.label | t : String | | Test.java:101:12:101:54 | new URI(...) | semmle.label | new URI(...) | +| Test.java:101:41:101:41 | t : String | semmle.label | t : String | | mad/Test.java:29:16:29:36 | getHostName(...) : String | semmle.label | getHostName(...) : String | | mad/Test.java:34:52:34:68 | (...)... | semmle.label | (...)... | | mad/Test.java:34:61:34:68 | source(...) : String | semmle.label | source(...) : String | diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected index 9ceaa1d7829..1884d76f811 100644 --- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected +++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected @@ -1,6 +1,7 @@ edges -| Mongo.java:10:29:10:41 | args : String[] | Mongo.java:17:45:17:67 | parse(...) | +| Mongo.java:10:29:10:41 | args : String[] | Mongo.java:17:56:17:66 | stringQuery : String | | Mongo.java:10:29:10:41 | args : String[] | Mongo.java:21:49:21:52 | json | +| Mongo.java:17:56:17:66 | stringQuery : String | Mongo.java:17:45:17:67 | parse(...) | | Test.java:29:30:29:42 | args : String[] | Test.java:36:47:36:52 | query1 | | Test.java:29:30:29:42 | args : String[] | Test.java:42:57:42:62 | query2 | | Test.java:29:30:29:42 | args : String[] | Test.java:50:62:50:67 | query3 | @@ -19,6 +20,7 @@ edges nodes | Mongo.java:10:29:10:41 | args : String[] | semmle.label | args : String[] | | Mongo.java:17:45:17:67 | parse(...) | semmle.label | parse(...) | +| Mongo.java:17:56:17:66 | stringQuery : String | semmle.label | stringQuery : String | | Mongo.java:21:49:21:52 | json | semmle.label | json | | Test.java:29:30:29:42 | args : String[] | semmle.label | args : String[] | | Test.java:36:47:36:52 | query1 | semmle.label | query1 | diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected index 1363da8634d..961ad3ed6ef 100644 --- a/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected @@ -2,63 +2,142 @@ edges | LdapInjection.java:45:28:45:52 | jBad : String | LdapInjection.java:47:38:47:57 | ... + ... | | LdapInjection.java:45:55:45:81 | jBadDN : String | LdapInjection.java:47:16:47:35 | ... + ... | | LdapInjection.java:51:28:51:52 | jBad : String | LdapInjection.java:53:56:53:75 | ... + ... | -| LdapInjection.java:51:55:51:85 | jBadDNName : String | LdapInjection.java:53:16:53:53 | new LdapName(...) | +| LdapInjection.java:51:55:51:85 | jBadDNName : String | LdapInjection.java:53:29:53:52 | ... + ... : String | +| LdapInjection.java:53:29:53:52 | ... + ... : String | LdapInjection.java:53:16:53:53 | new LdapName(...) | | LdapInjection.java:57:28:57:52 | jBad : String | LdapInjection.java:59:63:59:82 | ... + ... | | LdapInjection.java:63:28:63:59 | jBadInitial : String | LdapInjection.java:65:29:65:55 | ... + ... | | LdapInjection.java:69:28:69:52 | jBad : String | LdapInjection.java:71:84:71:103 | ... + ... | -| LdapInjection.java:69:55:69:88 | jBadDNNameAdd : String | LdapInjection.java:71:16:71:81 | addAll(...) | +| LdapInjection.java:69:55:69:88 | jBadDNNameAdd : String | LdapInjection.java:71:53:71:79 | ... + ... : String | +| LdapInjection.java:71:40:71:80 | new LdapName(...) : LdapName | LdapInjection.java:71:16:71:81 | addAll(...) | +| LdapInjection.java:71:53:71:79 | ... + ... : String | LdapInjection.java:71:40:71:80 | new LdapName(...) : LdapName | | LdapInjection.java:75:28:75:52 | jBad : String | LdapInjection.java:79:47:79:66 | ... + ... | -| LdapInjection.java:75:55:75:89 | jBadDNNameAdd2 : String | LdapInjection.java:79:16:79:44 | addAll(...) | +| LdapInjection.java:75:55:75:89 | jBadDNNameAdd2 : String | LdapInjection.java:78:30:78:57 | ... + ... : String | +| LdapInjection.java:78:5:78:8 | name : LdapName | LdapInjection.java:79:40:79:43 | name : LdapName | +| LdapInjection.java:78:17:78:58 | new LdapName(...) : LdapName | LdapInjection.java:78:17:78:68 | getRdns(...) : List | +| LdapInjection.java:78:17:78:68 | getRdns(...) : List | LdapInjection.java:78:5:78:8 | name : LdapName | +| LdapInjection.java:78:30:78:57 | ... + ... : String | LdapInjection.java:78:17:78:58 | new LdapName(...) : LdapName | +| LdapInjection.java:79:40:79:43 | name : LdapName | LdapInjection.java:79:16:79:44 | addAll(...) | | LdapInjection.java:83:28:83:52 | jBad : String | LdapInjection.java:85:75:85:94 | ... + ... | -| LdapInjection.java:83:55:83:93 | jBadDNNameToString : String | LdapInjection.java:85:16:85:72 | toString(...) | +| LdapInjection.java:83:55:83:93 | jBadDNNameToString : String | LdapInjection.java:85:29:85:60 | ... + ... : String | +| LdapInjection.java:85:16:85:61 | new LdapName(...) : LdapName | LdapInjection.java:85:16:85:72 | toString(...) | +| LdapInjection.java:85:29:85:60 | ... + ... : String | LdapInjection.java:85:16:85:61 | new LdapName(...) : LdapName | | LdapInjection.java:89:28:89:52 | jBad : String | LdapInjection.java:91:76:91:95 | ... + ... | -| LdapInjection.java:89:55:89:90 | jBadDNNameClone : String | LdapInjection.java:91:16:91:73 | (...)... | +| LdapInjection.java:89:55:89:90 | jBadDNNameClone : String | LdapInjection.java:91:36:91:64 | ... + ... : String | +| LdapInjection.java:91:23:91:65 | new LdapName(...) : LdapName | LdapInjection.java:91:23:91:73 | clone(...) : Object | +| LdapInjection.java:91:23:91:73 | clone(...) : Object | LdapInjection.java:91:16:91:73 | (...)... | +| LdapInjection.java:91:36:91:64 | ... + ... : String | LdapInjection.java:91:23:91:65 | new LdapName(...) : LdapName | | LdapInjection.java:106:31:106:55 | uBad : String | LdapInjection.java:108:67:108:86 | ... + ... | | LdapInjection.java:106:58:106:84 | uBadDN : String | LdapInjection.java:108:20:108:39 | ... + ... | -| LdapInjection.java:112:31:112:67 | uBadFilterCreate : String | LdapInjection.java:113:58:113:88 | create(...) | -| LdapInjection.java:117:31:117:70 | uBadROSearchRequest : String | LdapInjection.java:121:14:121:14 | s | -| LdapInjection.java:117:73:117:103 | uBadROSRDN : String | LdapInjection.java:121:14:121:14 | s | -| LdapInjection.java:125:31:125:68 | uBadSearchRequest : String | LdapInjection.java:129:14:129:14 | s | -| LdapInjection.java:125:71:125:99 | uBadSRDN : String | LdapInjection.java:129:14:129:14 | s | +| LdapInjection.java:112:31:112:67 | uBadFilterCreate : String | LdapInjection.java:113:72:113:87 | uBadFilterCreate : String | +| LdapInjection.java:113:72:113:87 | uBadFilterCreate : String | LdapInjection.java:113:58:113:88 | create(...) | +| LdapInjection.java:117:31:117:70 | uBadROSearchRequest : String | LdapInjection.java:120:9:120:43 | ... + ... : String | +| LdapInjection.java:117:73:117:103 | uBadROSRDN : String | LdapInjection.java:119:55:119:78 | ... + ... : String | +| LdapInjection.java:119:31:120:44 | new SearchRequest(...) : SearchRequest | LdapInjection.java:121:14:121:14 | s | +| LdapInjection.java:119:55:119:78 | ... + ... : String | LdapInjection.java:119:31:120:44 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:120:9:120:43 | ... + ... : String | LdapInjection.java:119:31:120:44 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:125:31:125:68 | uBadSearchRequest : String | LdapInjection.java:128:9:128:41 | ... + ... : String | +| LdapInjection.java:125:71:125:99 | uBadSRDN : String | LdapInjection.java:127:47:127:68 | ... + ... : String | +| LdapInjection.java:127:23:128:42 | new SearchRequest(...) : SearchRequest | LdapInjection.java:129:14:129:14 | s | +| LdapInjection.java:127:47:127:68 | ... + ... : String | LdapInjection.java:127:23:128:42 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:128:9:128:41 | ... + ... : String | LdapInjection.java:127:23:128:42 | new SearchRequest(...) : SearchRequest | | LdapInjection.java:133:31:133:55 | uBad : String | LdapInjection.java:135:69:135:88 | ... + ... | | LdapInjection.java:133:58:133:87 | uBadDNSFR : String | LdapInjection.java:135:22:135:44 | ... + ... | -| LdapInjection.java:139:31:139:75 | uBadROSearchRequestAsync : String | LdapInjection.java:143:19:143:19 | s | -| LdapInjection.java:139:78:139:113 | uBadROSRDNAsync : String | LdapInjection.java:143:19:143:19 | s | -| LdapInjection.java:147:31:147:73 | uBadSearchRequestAsync : String | LdapInjection.java:151:19:151:19 | s | -| LdapInjection.java:147:76:147:109 | uBadSRDNAsync : String | LdapInjection.java:151:19:151:19 | s | -| LdapInjection.java:155:31:155:70 | uBadFilterCreateNOT : String | LdapInjection.java:156:58:156:115 | createNOTFilter(...) | -| LdapInjection.java:160:31:160:75 | uBadFilterCreateToString : String | LdapInjection.java:161:58:161:107 | toString(...) | -| LdapInjection.java:165:32:165:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:168:58:168:58 | b : StringBuilder | +| LdapInjection.java:139:31:139:75 | uBadROSearchRequestAsync : String | LdapInjection.java:142:9:142:48 | ... + ... : String | +| LdapInjection.java:139:78:139:113 | uBadROSRDNAsync : String | LdapInjection.java:141:55:141:83 | ... + ... : String | +| LdapInjection.java:141:31:142:49 | new SearchRequest(...) : SearchRequest | LdapInjection.java:143:19:143:19 | s | +| LdapInjection.java:141:55:141:83 | ... + ... : String | LdapInjection.java:141:31:142:49 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:142:9:142:48 | ... + ... : String | LdapInjection.java:141:31:142:49 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:147:31:147:73 | uBadSearchRequestAsync : String | LdapInjection.java:150:9:150:46 | ... + ... : String | +| LdapInjection.java:147:76:147:109 | uBadSRDNAsync : String | LdapInjection.java:149:47:149:73 | ... + ... : String | +| LdapInjection.java:149:23:150:47 | new SearchRequest(...) : SearchRequest | LdapInjection.java:151:19:151:19 | s | +| LdapInjection.java:149:47:149:73 | ... + ... : String | LdapInjection.java:149:23:150:47 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:150:9:150:46 | ... + ... : String | LdapInjection.java:149:23:150:47 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:155:31:155:70 | uBadFilterCreateNOT : String | LdapInjection.java:156:95:156:113 | uBadFilterCreateNOT : String | +| LdapInjection.java:156:81:156:114 | create(...) : Filter | LdapInjection.java:156:58:156:115 | createNOTFilter(...) | +| LdapInjection.java:156:95:156:113 | uBadFilterCreateNOT : String | LdapInjection.java:156:81:156:114 | create(...) : Filter | +| LdapInjection.java:160:31:160:75 | uBadFilterCreateToString : String | LdapInjection.java:161:72:161:95 | uBadFilterCreateToString : String | +| LdapInjection.java:161:58:161:96 | create(...) : Filter | LdapInjection.java:161:58:161:107 | toString(...) | +| LdapInjection.java:161:72:161:95 | uBadFilterCreateToString : String | LdapInjection.java:161:58:161:96 | create(...) : Filter | +| LdapInjection.java:165:32:165:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:167:19:167:48 | uBadFilterCreateToStringBuffer : String | +| LdapInjection.java:167:5:167:49 | create(...) : Filter | LdapInjection.java:167:70:167:70 | b : StringBuilder | +| LdapInjection.java:167:19:167:48 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:167:5:167:49 | create(...) : Filter | +| LdapInjection.java:167:70:167:70 | b : StringBuilder | LdapInjection.java:168:58:168:58 | b : StringBuilder | | LdapInjection.java:168:58:168:58 | b : StringBuilder | LdapInjection.java:168:58:168:69 | toString(...) | -| LdapInjection.java:172:32:172:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:176:14:176:26 | duplicate(...) | -| LdapInjection.java:180:32:180:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:184:14:184:26 | duplicate(...) | -| LdapInjection.java:188:32:188:74 | uBadSearchRequestSetDN : String | LdapInjection.java:192:14:192:14 | s | -| LdapInjection.java:196:32:196:78 | uBadSearchRequestSetFilter : String | LdapInjection.java:200:14:200:14 | s | +| LdapInjection.java:172:32:172:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:175:9:175:50 | ... + ... : String | +| LdapInjection.java:174:23:175:51 | new SearchRequest(...) : SearchRequest | LdapInjection.java:176:14:176:14 | s : SearchRequest | +| LdapInjection.java:175:9:175:50 | ... + ... : String | LdapInjection.java:174:23:175:51 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:176:14:176:14 | s : SearchRequest | LdapInjection.java:176:14:176:26 | duplicate(...) | +| LdapInjection.java:180:32:180:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:183:9:183:52 | ... + ... : String | +| LdapInjection.java:182:31:183:53 | new SearchRequest(...) : SearchRequest | LdapInjection.java:184:14:184:14 | s : SearchRequest | +| LdapInjection.java:183:9:183:52 | ... + ... : String | LdapInjection.java:182:31:183:53 | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:184:14:184:14 | s : SearchRequest | LdapInjection.java:184:14:184:26 | duplicate(...) | +| LdapInjection.java:188:32:188:74 | uBadSearchRequestSetDN : String | LdapInjection.java:191:17:191:38 | uBadSearchRequestSetDN : String | +| LdapInjection.java:191:5:191:5 | s : SearchRequest | LdapInjection.java:192:14:192:14 | s | +| LdapInjection.java:191:17:191:38 | uBadSearchRequestSetDN : String | LdapInjection.java:191:5:191:5 | s : SearchRequest | +| LdapInjection.java:196:32:196:78 | uBadSearchRequestSetFilter : String | LdapInjection.java:199:17:199:42 | uBadSearchRequestSetFilter : String | +| LdapInjection.java:199:5:199:5 | s : SearchRequest | LdapInjection.java:200:14:200:14 | s | +| LdapInjection.java:199:17:199:42 | uBadSearchRequestSetFilter : String | LdapInjection.java:199:5:199:5 | s : SearchRequest | | LdapInjection.java:229:30:229:54 | sBad : String | LdapInjection.java:230:36:230:55 | ... + ... | | LdapInjection.java:229:57:229:83 | sBadDN : String | LdapInjection.java:230:14:230:33 | ... + ... | | LdapInjection.java:234:30:234:54 | sBad : String | LdapInjection.java:235:88:235:107 | ... + ... | -| LdapInjection.java:234:57:234:92 | sBadDNLNBuilder : String | LdapInjection.java:235:20:235:85 | build(...) | +| LdapInjection.java:234:57:234:92 | sBadDNLNBuilder : String | LdapInjection.java:235:48:235:76 | ... + ... : String | +| LdapInjection.java:235:20:235:77 | newInstance(...) : LdapNameBuilder | LdapInjection.java:235:20:235:85 | build(...) | +| LdapInjection.java:235:48:235:76 | ... + ... : String | LdapInjection.java:235:20:235:77 | newInstance(...) : LdapNameBuilder | | LdapInjection.java:239:30:239:54 | sBad : String | LdapInjection.java:240:100:240:119 | ... + ... | -| LdapInjection.java:239:57:239:95 | sBadDNLNBuilderAdd : String | LdapInjection.java:240:23:240:97 | build(...) | -| LdapInjection.java:244:30:244:63 | sBadLdapQuery : String | LdapInjection.java:245:15:245:76 | filter(...) | -| LdapInjection.java:249:30:249:60 | sBadFilter : String | LdapInjection.java:250:66:250:112 | new HardcodedFilter(...) | -| LdapInjection.java:249:63:249:98 | sBadDNLdapUtils : String | LdapInjection.java:250:12:250:63 | newLdapName(...) | -| LdapInjection.java:254:30:254:63 | sBadLdapQuery : String | LdapInjection.java:255:24:255:85 | filter(...) | -| LdapInjection.java:259:30:259:64 | sBadLdapQuery2 : String | LdapInjection.java:261:24:261:24 | q | -| LdapInjection.java:265:30:265:73 | sBadLdapQueryWithFilter : String | LdapInjection.java:266:24:266:116 | filter(...) | -| LdapInjection.java:270:30:270:74 | sBadLdapQueryWithFilter2 : String | LdapInjection.java:272:24:272:57 | filter(...) | -| LdapInjection.java:276:31:276:68 | sBadLdapQueryBase : String | LdapInjection.java:277:12:277:66 | base(...) | -| LdapInjection.java:281:31:281:71 | sBadLdapQueryComplex : String | LdapInjection.java:282:24:282:98 | is(...) | -| LdapInjection.java:286:31:286:69 | sBadFilterToString : String | LdapInjection.java:287:18:287:83 | toString(...) | -| LdapInjection.java:291:31:291:67 | sBadFilterEncode : String | LdapInjection.java:294:18:294:18 | s : StringBuffer | +| LdapInjection.java:239:57:239:95 | sBadDNLNBuilderAdd : String | LdapInjection.java:240:57:240:88 | ... + ... : String | +| LdapInjection.java:240:23:240:89 | add(...) : LdapNameBuilder | LdapInjection.java:240:23:240:97 | build(...) | +| LdapInjection.java:240:57:240:88 | ... + ... : String | LdapInjection.java:240:23:240:89 | add(...) : LdapNameBuilder | +| LdapInjection.java:244:30:244:63 | sBadLdapQuery : String | LdapInjection.java:245:47:245:75 | ... + ... : String | +| LdapInjection.java:245:47:245:75 | ... + ... : String | LdapInjection.java:245:15:245:76 | filter(...) | +| LdapInjection.java:249:30:249:60 | sBadFilter : String | LdapInjection.java:250:86:250:111 | ... + ... : String | +| LdapInjection.java:249:63:249:98 | sBadDNLdapUtils : String | LdapInjection.java:250:34:250:62 | ... + ... : String | +| LdapInjection.java:250:34:250:62 | ... + ... : String | LdapInjection.java:250:12:250:63 | newLdapName(...) | +| LdapInjection.java:250:86:250:111 | ... + ... : String | LdapInjection.java:250:66:250:112 | new HardcodedFilter(...) | +| LdapInjection.java:254:30:254:63 | sBadLdapQuery : String | LdapInjection.java:255:56:255:84 | ... + ... : String | +| LdapInjection.java:255:56:255:84 | ... + ... : String | LdapInjection.java:255:24:255:85 | filter(...) | +| LdapInjection.java:259:30:259:64 | sBadLdapQuery2 : String | LdapInjection.java:260:51:260:80 | ... + ... : String | +| LdapInjection.java:260:19:260:81 | filter(...) : LdapQuery | LdapInjection.java:261:24:261:24 | q | +| LdapInjection.java:260:51:260:80 | ... + ... : String | LdapInjection.java:260:19:260:81 | filter(...) : LdapQuery | +| LdapInjection.java:265:30:265:73 | sBadLdapQueryWithFilter : String | LdapInjection.java:266:76:266:114 | ... + ... : String | +| LdapInjection.java:266:56:266:115 | new HardcodedFilter(...) : HardcodedFilter | LdapInjection.java:266:24:266:116 | filter(...) | +| LdapInjection.java:266:76:266:114 | ... + ... : String | LdapInjection.java:266:56:266:115 | new HardcodedFilter(...) : HardcodedFilter | +| LdapInjection.java:270:30:270:74 | sBadLdapQueryWithFilter2 : String | LdapInjection.java:271:68:271:107 | ... + ... : String | +| LdapInjection.java:271:48:271:108 | new HardcodedFilter(...) : HardcodedFilter | LdapInjection.java:272:56:272:56 | f : HardcodedFilter | +| LdapInjection.java:271:68:271:107 | ... + ... : String | LdapInjection.java:271:48:271:108 | new HardcodedFilter(...) : HardcodedFilter | +| LdapInjection.java:272:56:272:56 | f : HardcodedFilter | LdapInjection.java:272:24:272:57 | filter(...) | +| LdapInjection.java:276:31:276:68 | sBadLdapQueryBase : String | LdapInjection.java:277:42:277:58 | sBadLdapQueryBase : String | +| LdapInjection.java:277:12:277:59 | base(...) : LdapQueryBuilder | LdapInjection.java:277:12:277:66 | base(...) | +| LdapInjection.java:277:42:277:58 | sBadLdapQueryBase : String | LdapInjection.java:277:12:277:59 | base(...) : LdapQueryBuilder | +| LdapInjection.java:281:31:281:71 | sBadLdapQueryComplex : String | LdapInjection.java:282:54:282:73 | sBadLdapQueryComplex : String | +| LdapInjection.java:282:24:282:74 | base(...) : LdapQueryBuilder | LdapInjection.java:282:24:282:87 | where(...) : ConditionCriteria | +| LdapInjection.java:282:24:282:87 | where(...) : ConditionCriteria | LdapInjection.java:282:24:282:98 | is(...) | +| LdapInjection.java:282:54:282:73 | sBadLdapQueryComplex : String | LdapInjection.java:282:24:282:74 | base(...) : LdapQueryBuilder | +| LdapInjection.java:286:31:286:69 | sBadFilterToString : String | LdapInjection.java:287:38:287:71 | ... + ... : String | +| LdapInjection.java:287:18:287:72 | new HardcodedFilter(...) : HardcodedFilter | LdapInjection.java:287:18:287:83 | toString(...) | +| LdapInjection.java:287:38:287:71 | ... + ... : String | LdapInjection.java:287:18:287:72 | new HardcodedFilter(...) : HardcodedFilter | +| LdapInjection.java:291:31:291:67 | sBadFilterEncode : String | LdapInjection.java:293:25:293:56 | ... + ... : String | +| LdapInjection.java:293:5:293:57 | new HardcodedFilter(...) : HardcodedFilter | LdapInjection.java:293:66:293:66 | s : StringBuffer | +| LdapInjection.java:293:25:293:56 | ... + ... : String | LdapInjection.java:293:5:293:57 | new HardcodedFilter(...) : HardcodedFilter | +| LdapInjection.java:293:66:293:66 | s : StringBuffer | LdapInjection.java:294:18:294:18 | s : StringBuffer | | LdapInjection.java:294:18:294:18 | s : StringBuffer | LdapInjection.java:294:18:294:29 | toString(...) | | LdapInjection.java:314:30:314:54 | aBad : String | LdapInjection.java:316:36:316:55 | ... + ... | | LdapInjection.java:314:57:314:83 | aBadDN : String | LdapInjection.java:316:14:316:33 | ... + ... | | LdapInjection.java:320:30:320:54 | aBad : String | LdapInjection.java:322:65:322:84 | ... + ... | -| LdapInjection.java:320:57:320:94 | aBadDNObjToString : String | LdapInjection.java:322:14:322:62 | getName(...) | -| LdapInjection.java:326:30:326:67 | aBadSearchRequest : String | LdapInjection.java:330:14:330:14 | s | -| LdapInjection.java:334:74:334:103 | aBadDNObj : String | LdapInjection.java:338:14:338:14 | s | -| LdapInjection.java:342:30:342:72 | aBadDNSearchRequestGet : String | LdapInjection.java:346:14:346:24 | getBase(...) | +| LdapInjection.java:320:57:320:94 | aBadDNObjToString : String | LdapInjection.java:322:21:322:51 | ... + ... : String | +| LdapInjection.java:322:14:322:52 | new Dn(...) : Dn | LdapInjection.java:322:14:322:62 | getName(...) | +| LdapInjection.java:322:21:322:51 | ... + ... : String | LdapInjection.java:322:14:322:52 | new Dn(...) : Dn | +| LdapInjection.java:326:30:326:67 | aBadSearchRequest : String | LdapInjection.java:329:17:329:49 | ... + ... : String | +| LdapInjection.java:329:5:329:5 | s : SearchRequestImpl | LdapInjection.java:330:14:330:14 | s | +| LdapInjection.java:329:17:329:49 | ... + ... : String | LdapInjection.java:329:5:329:5 | s : SearchRequestImpl | +| LdapInjection.java:334:74:334:103 | aBadDNObj : String | LdapInjection.java:337:22:337:44 | ... + ... : String | +| LdapInjection.java:337:5:337:5 | s : SearchRequestImpl | LdapInjection.java:338:14:338:14 | s | +| LdapInjection.java:337:15:337:45 | new Dn(...) : Dn | LdapInjection.java:337:5:337:5 | s : SearchRequestImpl | +| LdapInjection.java:337:22:337:44 | ... + ... : String | LdapInjection.java:337:15:337:45 | new Dn(...) : Dn | +| LdapInjection.java:342:30:342:72 | aBadDNSearchRequestGet : String | LdapInjection.java:345:22:345:57 | ... + ... : String | +| LdapInjection.java:345:5:345:5 | s : SearchRequestImpl | LdapInjection.java:346:14:346:14 | s : SearchRequestImpl | +| LdapInjection.java:345:15:345:58 | new Dn(...) : Dn | LdapInjection.java:345:5:345:5 | s : SearchRequestImpl | +| LdapInjection.java:345:22:345:57 | ... + ... : String | LdapInjection.java:345:15:345:58 | new Dn(...) : Dn | +| LdapInjection.java:346:14:346:14 | s : SearchRequestImpl | LdapInjection.java:346:14:346:24 | getBase(...) | nodes | LdapInjection.java:45:28:45:52 | jBad : String | semmle.label | jBad : String | | LdapInjection.java:45:55:45:81 | jBadDN : String | semmle.label | jBadDN : String | @@ -67,6 +146,7 @@ nodes | LdapInjection.java:51:28:51:52 | jBad : String | semmle.label | jBad : String | | LdapInjection.java:51:55:51:85 | jBadDNName : String | semmle.label | jBadDNName : String | | LdapInjection.java:53:16:53:53 | new LdapName(...) | semmle.label | new LdapName(...) | +| LdapInjection.java:53:29:53:52 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:53:56:53:75 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:57:28:57:52 | jBad : String | semmle.label | jBad : String | | LdapInjection.java:59:63:59:82 | ... + ... | semmle.label | ... + ... | @@ -75,18 +155,30 @@ nodes | LdapInjection.java:69:28:69:52 | jBad : String | semmle.label | jBad : String | | LdapInjection.java:69:55:69:88 | jBadDNNameAdd : String | semmle.label | jBadDNNameAdd : String | | LdapInjection.java:71:16:71:81 | addAll(...) | semmle.label | addAll(...) | +| LdapInjection.java:71:40:71:80 | new LdapName(...) : LdapName | semmle.label | new LdapName(...) : LdapName | +| LdapInjection.java:71:53:71:79 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:71:84:71:103 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:75:28:75:52 | jBad : String | semmle.label | jBad : String | | LdapInjection.java:75:55:75:89 | jBadDNNameAdd2 : String | semmle.label | jBadDNNameAdd2 : String | +| LdapInjection.java:78:5:78:8 | name : LdapName | semmle.label | name : LdapName | +| LdapInjection.java:78:17:78:58 | new LdapName(...) : LdapName | semmle.label | new LdapName(...) : LdapName | +| LdapInjection.java:78:17:78:68 | getRdns(...) : List | semmle.label | getRdns(...) : List | +| LdapInjection.java:78:30:78:57 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:79:16:79:44 | addAll(...) | semmle.label | addAll(...) | +| LdapInjection.java:79:40:79:43 | name : LdapName | semmle.label | name : LdapName | | LdapInjection.java:79:47:79:66 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:83:28:83:52 | jBad : String | semmle.label | jBad : String | | LdapInjection.java:83:55:83:93 | jBadDNNameToString : String | semmle.label | jBadDNNameToString : String | +| LdapInjection.java:85:16:85:61 | new LdapName(...) : LdapName | semmle.label | new LdapName(...) : LdapName | | LdapInjection.java:85:16:85:72 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:85:29:85:60 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:85:75:85:94 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:89:28:89:52 | jBad : String | semmle.label | jBad : String | | LdapInjection.java:89:55:89:90 | jBadDNNameClone : String | semmle.label | jBadDNNameClone : String | | LdapInjection.java:91:16:91:73 | (...)... | semmle.label | (...)... | +| LdapInjection.java:91:23:91:65 | new LdapName(...) : LdapName | semmle.label | new LdapName(...) : LdapName | +| LdapInjection.java:91:23:91:73 | clone(...) : Object | semmle.label | clone(...) : Object | +| LdapInjection.java:91:36:91:64 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:91:76:91:95 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:106:31:106:55 | uBad : String | semmle.label | uBad : String | | LdapInjection.java:106:58:106:84 | uBadDN : String | semmle.label | uBadDN : String | @@ -94,11 +186,18 @@ nodes | LdapInjection.java:108:67:108:86 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:112:31:112:67 | uBadFilterCreate : String | semmle.label | uBadFilterCreate : String | | LdapInjection.java:113:58:113:88 | create(...) | semmle.label | create(...) | +| LdapInjection.java:113:72:113:87 | uBadFilterCreate : String | semmle.label | uBadFilterCreate : String | | LdapInjection.java:117:31:117:70 | uBadROSearchRequest : String | semmle.label | uBadROSearchRequest : String | | LdapInjection.java:117:73:117:103 | uBadROSRDN : String | semmle.label | uBadROSRDN : String | +| LdapInjection.java:119:31:120:44 | new SearchRequest(...) : SearchRequest | semmle.label | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:119:55:119:78 | ... + ... : String | semmle.label | ... + ... : String | +| LdapInjection.java:120:9:120:43 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:121:14:121:14 | s | semmle.label | s | | LdapInjection.java:125:31:125:68 | uBadSearchRequest : String | semmle.label | uBadSearchRequest : String | | LdapInjection.java:125:71:125:99 | uBadSRDN : String | semmle.label | uBadSRDN : String | +| LdapInjection.java:127:23:128:42 | new SearchRequest(...) : SearchRequest | semmle.label | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:127:47:127:68 | ... + ... : String | semmle.label | ... + ... : String | +| LdapInjection.java:128:9:128:41 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:129:14:129:14 | s | semmle.label | s | | LdapInjection.java:133:31:133:55 | uBad : String | semmle.label | uBad : String | | LdapInjection.java:133:58:133:87 | uBadDNSFR : String | semmle.label | uBadDNSFR : String | @@ -106,24 +205,47 @@ nodes | LdapInjection.java:135:69:135:88 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:139:31:139:75 | uBadROSearchRequestAsync : String | semmle.label | uBadROSearchRequestAsync : String | | LdapInjection.java:139:78:139:113 | uBadROSRDNAsync : String | semmle.label | uBadROSRDNAsync : String | +| LdapInjection.java:141:31:142:49 | new SearchRequest(...) : SearchRequest | semmle.label | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:141:55:141:83 | ... + ... : String | semmle.label | ... + ... : String | +| LdapInjection.java:142:9:142:48 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:143:19:143:19 | s | semmle.label | s | | LdapInjection.java:147:31:147:73 | uBadSearchRequestAsync : String | semmle.label | uBadSearchRequestAsync : String | | LdapInjection.java:147:76:147:109 | uBadSRDNAsync : String | semmle.label | uBadSRDNAsync : String | +| LdapInjection.java:149:23:150:47 | new SearchRequest(...) : SearchRequest | semmle.label | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:149:47:149:73 | ... + ... : String | semmle.label | ... + ... : String | +| LdapInjection.java:150:9:150:46 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:151:19:151:19 | s | semmle.label | s | | LdapInjection.java:155:31:155:70 | uBadFilterCreateNOT : String | semmle.label | uBadFilterCreateNOT : String | | LdapInjection.java:156:58:156:115 | createNOTFilter(...) | semmle.label | createNOTFilter(...) | +| LdapInjection.java:156:81:156:114 | create(...) : Filter | semmle.label | create(...) : Filter | +| LdapInjection.java:156:95:156:113 | uBadFilterCreateNOT : String | semmle.label | uBadFilterCreateNOT : String | | LdapInjection.java:160:31:160:75 | uBadFilterCreateToString : String | semmle.label | uBadFilterCreateToString : String | +| LdapInjection.java:161:58:161:96 | create(...) : Filter | semmle.label | create(...) : Filter | | LdapInjection.java:161:58:161:107 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:161:72:161:95 | uBadFilterCreateToString : String | semmle.label | uBadFilterCreateToString : String | | LdapInjection.java:165:32:165:82 | uBadFilterCreateToStringBuffer : String | semmle.label | uBadFilterCreateToStringBuffer : String | +| LdapInjection.java:167:5:167:49 | create(...) : Filter | semmle.label | create(...) : Filter | +| LdapInjection.java:167:19:167:48 | uBadFilterCreateToStringBuffer : String | semmle.label | uBadFilterCreateToStringBuffer : String | +| LdapInjection.java:167:70:167:70 | b : StringBuilder | semmle.label | b : StringBuilder | | LdapInjection.java:168:58:168:58 | b : StringBuilder | semmle.label | b : StringBuilder | | LdapInjection.java:168:58:168:69 | toString(...) | semmle.label | toString(...) | | LdapInjection.java:172:32:172:78 | uBadSearchRequestDuplicate : String | semmle.label | uBadSearchRequestDuplicate : String | +| LdapInjection.java:174:23:175:51 | new SearchRequest(...) : SearchRequest | semmle.label | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:175:9:175:50 | ... + ... : String | semmle.label | ... + ... : String | +| LdapInjection.java:176:14:176:14 | s : SearchRequest | semmle.label | s : SearchRequest | | LdapInjection.java:176:14:176:26 | duplicate(...) | semmle.label | duplicate(...) | | LdapInjection.java:180:32:180:80 | uBadROSearchRequestDuplicate : String | semmle.label | uBadROSearchRequestDuplicate : String | +| LdapInjection.java:182:31:183:53 | new SearchRequest(...) : SearchRequest | semmle.label | new SearchRequest(...) : SearchRequest | +| LdapInjection.java:183:9:183:52 | ... + ... : String | semmle.label | ... + ... : String | +| LdapInjection.java:184:14:184:14 | s : SearchRequest | semmle.label | s : SearchRequest | | LdapInjection.java:184:14:184:26 | duplicate(...) | semmle.label | duplicate(...) | | LdapInjection.java:188:32:188:74 | uBadSearchRequestSetDN : String | semmle.label | uBadSearchRequestSetDN : String | +| LdapInjection.java:191:5:191:5 | s : SearchRequest | semmle.label | s : SearchRequest | +| LdapInjection.java:191:17:191:38 | uBadSearchRequestSetDN : String | semmle.label | uBadSearchRequestSetDN : String | | LdapInjection.java:192:14:192:14 | s | semmle.label | s | | LdapInjection.java:196:32:196:78 | uBadSearchRequestSetFilter : String | semmle.label | uBadSearchRequestSetFilter : String | +| LdapInjection.java:199:5:199:5 | s : SearchRequest | semmle.label | s : SearchRequest | +| LdapInjection.java:199:17:199:42 | uBadSearchRequestSetFilter : String | semmle.label | uBadSearchRequestSetFilter : String | | LdapInjection.java:200:14:200:14 | s | semmle.label | s | | LdapInjection.java:229:30:229:54 | sBad : String | semmle.label | sBad : String | | LdapInjection.java:229:57:229:83 | sBadDN : String | semmle.label | sBadDN : String | @@ -131,33 +253,58 @@ nodes | LdapInjection.java:230:36:230:55 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:234:30:234:54 | sBad : String | semmle.label | sBad : String | | LdapInjection.java:234:57:234:92 | sBadDNLNBuilder : String | semmle.label | sBadDNLNBuilder : String | +| LdapInjection.java:235:20:235:77 | newInstance(...) : LdapNameBuilder | semmle.label | newInstance(...) : LdapNameBuilder | | LdapInjection.java:235:20:235:85 | build(...) | semmle.label | build(...) | +| LdapInjection.java:235:48:235:76 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:235:88:235:107 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:239:30:239:54 | sBad : String | semmle.label | sBad : String | | LdapInjection.java:239:57:239:95 | sBadDNLNBuilderAdd : String | semmle.label | sBadDNLNBuilderAdd : String | +| LdapInjection.java:240:23:240:89 | add(...) : LdapNameBuilder | semmle.label | add(...) : LdapNameBuilder | | LdapInjection.java:240:23:240:97 | build(...) | semmle.label | build(...) | +| LdapInjection.java:240:57:240:88 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:240:100:240:119 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:244:30:244:63 | sBadLdapQuery : String | semmle.label | sBadLdapQuery : String | | LdapInjection.java:245:15:245:76 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:245:47:245:75 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:249:30:249:60 | sBadFilter : String | semmle.label | sBadFilter : String | | LdapInjection.java:249:63:249:98 | sBadDNLdapUtils : String | semmle.label | sBadDNLdapUtils : String | | LdapInjection.java:250:12:250:63 | newLdapName(...) | semmle.label | newLdapName(...) | +| LdapInjection.java:250:34:250:62 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:250:66:250:112 | new HardcodedFilter(...) | semmle.label | new HardcodedFilter(...) | +| LdapInjection.java:250:86:250:111 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:254:30:254:63 | sBadLdapQuery : String | semmle.label | sBadLdapQuery : String | | LdapInjection.java:255:24:255:85 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:255:56:255:84 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:259:30:259:64 | sBadLdapQuery2 : String | semmle.label | sBadLdapQuery2 : String | +| LdapInjection.java:260:19:260:81 | filter(...) : LdapQuery | semmle.label | filter(...) : LdapQuery | +| LdapInjection.java:260:51:260:80 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:261:24:261:24 | q | semmle.label | q | | LdapInjection.java:265:30:265:73 | sBadLdapQueryWithFilter : String | semmle.label | sBadLdapQueryWithFilter : String | | LdapInjection.java:266:24:266:116 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:266:56:266:115 | new HardcodedFilter(...) : HardcodedFilter | semmle.label | new HardcodedFilter(...) : HardcodedFilter | +| LdapInjection.java:266:76:266:114 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:270:30:270:74 | sBadLdapQueryWithFilter2 : String | semmle.label | sBadLdapQueryWithFilter2 : String | +| LdapInjection.java:271:48:271:108 | new HardcodedFilter(...) : HardcodedFilter | semmle.label | new HardcodedFilter(...) : HardcodedFilter | +| LdapInjection.java:271:68:271:107 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:272:24:272:57 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:272:56:272:56 | f : HardcodedFilter | semmle.label | f : HardcodedFilter | | LdapInjection.java:276:31:276:68 | sBadLdapQueryBase : String | semmle.label | sBadLdapQueryBase : String | +| LdapInjection.java:277:12:277:59 | base(...) : LdapQueryBuilder | semmle.label | base(...) : LdapQueryBuilder | | LdapInjection.java:277:12:277:66 | base(...) | semmle.label | base(...) | +| LdapInjection.java:277:42:277:58 | sBadLdapQueryBase : String | semmle.label | sBadLdapQueryBase : String | | LdapInjection.java:281:31:281:71 | sBadLdapQueryComplex : String | semmle.label | sBadLdapQueryComplex : String | +| LdapInjection.java:282:24:282:74 | base(...) : LdapQueryBuilder | semmle.label | base(...) : LdapQueryBuilder | +| LdapInjection.java:282:24:282:87 | where(...) : ConditionCriteria | semmle.label | where(...) : ConditionCriteria | | LdapInjection.java:282:24:282:98 | is(...) | semmle.label | is(...) | +| LdapInjection.java:282:54:282:73 | sBadLdapQueryComplex : String | semmle.label | sBadLdapQueryComplex : String | | LdapInjection.java:286:31:286:69 | sBadFilterToString : String | semmle.label | sBadFilterToString : String | +| LdapInjection.java:287:18:287:72 | new HardcodedFilter(...) : HardcodedFilter | semmle.label | new HardcodedFilter(...) : HardcodedFilter | | LdapInjection.java:287:18:287:83 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:287:38:287:71 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:291:31:291:67 | sBadFilterEncode : String | semmle.label | sBadFilterEncode : String | +| LdapInjection.java:293:5:293:57 | new HardcodedFilter(...) : HardcodedFilter | semmle.label | new HardcodedFilter(...) : HardcodedFilter | +| LdapInjection.java:293:25:293:56 | ... + ... : String | semmle.label | ... + ... : String | +| LdapInjection.java:293:66:293:66 | s : StringBuffer | semmle.label | s : StringBuffer | | LdapInjection.java:294:18:294:18 | s : StringBuffer | semmle.label | s : StringBuffer | | LdapInjection.java:294:18:294:29 | toString(...) | semmle.label | toString(...) | | LdapInjection.java:314:30:314:54 | aBad : String | semmle.label | aBad : String | @@ -166,13 +313,24 @@ nodes | LdapInjection.java:316:36:316:55 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:320:30:320:54 | aBad : String | semmle.label | aBad : String | | LdapInjection.java:320:57:320:94 | aBadDNObjToString : String | semmle.label | aBadDNObjToString : String | +| LdapInjection.java:322:14:322:52 | new Dn(...) : Dn | semmle.label | new Dn(...) : Dn | | LdapInjection.java:322:14:322:62 | getName(...) | semmle.label | getName(...) | +| LdapInjection.java:322:21:322:51 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:322:65:322:84 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:326:30:326:67 | aBadSearchRequest : String | semmle.label | aBadSearchRequest : String | +| LdapInjection.java:329:5:329:5 | s : SearchRequestImpl | semmle.label | s : SearchRequestImpl | +| LdapInjection.java:329:17:329:49 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:330:14:330:14 | s | semmle.label | s | | LdapInjection.java:334:74:334:103 | aBadDNObj : String | semmle.label | aBadDNObj : String | +| LdapInjection.java:337:5:337:5 | s : SearchRequestImpl | semmle.label | s : SearchRequestImpl | +| LdapInjection.java:337:15:337:45 | new Dn(...) : Dn | semmle.label | new Dn(...) : Dn | +| LdapInjection.java:337:22:337:44 | ... + ... : String | semmle.label | ... + ... : String | | LdapInjection.java:338:14:338:14 | s | semmle.label | s | | LdapInjection.java:342:30:342:72 | aBadDNSearchRequestGet : String | semmle.label | aBadDNSearchRequestGet : String | +| LdapInjection.java:345:5:345:5 | s : SearchRequestImpl | semmle.label | s : SearchRequestImpl | +| LdapInjection.java:345:15:345:58 | new Dn(...) : Dn | semmle.label | new Dn(...) : Dn | +| LdapInjection.java:345:22:345:57 | ... + ... : String | semmle.label | ... + ... : String | +| LdapInjection.java:346:14:346:14 | s : SearchRequestImpl | semmle.label | s : SearchRequestImpl | | LdapInjection.java:346:14:346:24 | getBase(...) | semmle.label | getBase(...) | subpaths #select diff --git a/java/ql/test/query-tests/security/CWE-311/CWE-319/HttpsUrls.expected b/java/ql/test/query-tests/security/CWE-311/CWE-319/HttpsUrls.expected index 6c184d0516f..baef4c539b8 100644 --- a/java/ql/test/query-tests/security/CWE-311/CWE-319/HttpsUrls.expected +++ b/java/ql/test/query-tests/security/CWE-311/CWE-319/HttpsUrls.expected @@ -1,26 +1,32 @@ edges | HttpsUrlsTest.java:23:23:23:31 | "http://" : String | HttpsUrlsTest.java:24:21:24:56 | ... + ... : String | -| HttpsUrlsTest.java:23:23:23:31 | "http://" : String | HttpsUrlsTest.java:28:50:28:50 | u | | HttpsUrlsTest.java:24:13:24:57 | new URL(...) : URL | HttpsUrlsTest.java:28:50:28:50 | u | | HttpsUrlsTest.java:24:21:24:56 | ... + ... : String | HttpsUrlsTest.java:24:13:24:57 | new URL(...) : URL | -| HttpsUrlsTest.java:36:23:36:28 | "http" : String | HttpsUrlsTest.java:41:50:41:50 | u | +| HttpsUrlsTest.java:36:23:36:28 | "http" : String | HttpsUrlsTest.java:37:21:37:28 | protocol : String | +| HttpsUrlsTest.java:37:13:37:62 | new URL(...) : URL | HttpsUrlsTest.java:41:50:41:50 | u | +| HttpsUrlsTest.java:37:21:37:28 | protocol : String | HttpsUrlsTest.java:37:13:37:62 | new URL(...) : URL | | HttpsUrlsTest.java:49:23:49:31 | "http://" : String | HttpsUrlsTest.java:51:64:51:98 | ... + ... : String | -| HttpsUrlsTest.java:49:23:49:31 | "http://" : String | HttpsUrlsTest.java:55:50:55:50 | u | | HttpsUrlsTest.java:51:13:51:99 | new URL(...) : URL | HttpsUrlsTest.java:55:50:55:50 | u | | HttpsUrlsTest.java:51:64:51:98 | ... + ... : String | HttpsUrlsTest.java:51:13:51:99 | new URL(...) : URL | -| HttpsUrlsTest.java:87:23:87:28 | "http" : String | HttpsUrlsTest.java:92:50:92:50 | u | +| HttpsUrlsTest.java:87:23:87:28 | "http" : String | HttpsUrlsTest.java:88:21:88:28 | protocol : String | +| HttpsUrlsTest.java:88:13:88:52 | new URL(...) : URL | HttpsUrlsTest.java:92:50:92:50 | u | +| HttpsUrlsTest.java:88:21:88:28 | protocol : String | HttpsUrlsTest.java:88:13:88:52 | new URL(...) : URL | nodes | HttpsUrlsTest.java:23:23:23:31 | "http://" : String | semmle.label | "http://" : String | | HttpsUrlsTest.java:24:13:24:57 | new URL(...) : URL | semmle.label | new URL(...) : URL | | HttpsUrlsTest.java:24:21:24:56 | ... + ... : String | semmle.label | ... + ... : String | | HttpsUrlsTest.java:28:50:28:50 | u | semmle.label | u | | HttpsUrlsTest.java:36:23:36:28 | "http" : String | semmle.label | "http" : String | +| HttpsUrlsTest.java:37:13:37:62 | new URL(...) : URL | semmle.label | new URL(...) : URL | +| HttpsUrlsTest.java:37:21:37:28 | protocol : String | semmle.label | protocol : String | | HttpsUrlsTest.java:41:50:41:50 | u | semmle.label | u | | HttpsUrlsTest.java:49:23:49:31 | "http://" : String | semmle.label | "http://" : String | | HttpsUrlsTest.java:51:13:51:99 | new URL(...) : URL | semmle.label | new URL(...) : URL | | HttpsUrlsTest.java:51:64:51:98 | ... + ... : String | semmle.label | ... + ... : String | | HttpsUrlsTest.java:55:50:55:50 | u | semmle.label | u | | HttpsUrlsTest.java:87:23:87:28 | "http" : String | semmle.label | "http" : String | +| HttpsUrlsTest.java:88:13:88:52 | new URL(...) : URL | semmle.label | new URL(...) : URL | +| HttpsUrlsTest.java:88:21:88:28 | protocol : String | semmle.label | protocol : String | | HttpsUrlsTest.java:92:50:92:50 | u | semmle.label | u | subpaths #select diff --git a/java/ql/test/query-tests/security/CWE-749/UnsafeActivityKt.kt b/java/ql/test/query-tests/security/CWE-749/UnsafeActivityKt.kt index d20845f5c77..32004071c3c 100644 --- a/java/ql/test/query-tests/security/CWE-749/UnsafeActivityKt.kt +++ b/java/ql/test/query-tests/security/CWE-749/UnsafeActivityKt.kt @@ -9,12 +9,19 @@ import android.webkit.WebViewClient class UnsafeActivityKt : Activity() { override fun onCreate(savedInstanceState : Bundle) { + val src : String = intent.extras.getString("url") + val wv = findViewById(-1) // Implicit not-nulls happening here wv.settings.setJavaScriptEnabled(true) wv.settings.setAllowFileAccessFromFileURLs(true) - val thisUrl : String = intent.extras.getString("url") - wv.loadUrl(thisUrl) // $ hasUnsafeAndroidAccess + wv.loadUrl(src) // $ hasUnsafeAndroidAccess + + val wv2 = findViewById(-1) + wv2.apply { + settings.setJavaScriptEnabled(true) + } + wv2.loadUrl(src) // $ hasUnsafeAndroidAccess } } diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll index 5daac270292..eff490b638d 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll @@ -23,7 +23,8 @@ class DomBasedXssAtmConfig extends AtmConfig { override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or - node instanceof DomBasedXss::Sanitizer + node instanceof DomBasedXss::Sanitizer or + DomBasedXss::isOptionallySanitizedNode(node) } override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) { @@ -31,10 +32,6 @@ class DomBasedXssAtmConfig extends AtmConfig { guard instanceof QuoteGuard or guard instanceof ContainsHtmlGuard } - - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { - DomBasedXss::isOptionallySanitizedEdge(pred, succ) - } } private import semmle.javascript.security.dataflow.Xss::Shared as Shared diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssThroughDomATM.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssThroughDomATM.qll index e188da15a7e..0eeba5d23ad 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssThroughDomATM.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssThroughDomATM.qll @@ -23,7 +23,8 @@ class XssThroughDomAtmConfig extends AtmConfig { override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or - node instanceof DomBasedXss::Sanitizer + node instanceof DomBasedXss::Sanitizer or + DomBasedXss::isOptionallySanitizedNode(node) } override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) { @@ -34,10 +35,6 @@ class XssThroughDomAtmConfig extends AtmConfig { guard instanceof QuoteGuard or guard instanceof ContainsHtmlGuard } - - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { - DomBasedXss::isOptionallySanitizedEdge(pred, succ) - } } /** diff --git a/javascript/ql/lib/CHANGELOG.md b/javascript/ql/lib/CHANGELOG.md index 14cbfac4141..ed2b926666e 100644 --- a/javascript/ql/lib/CHANGELOG.md +++ b/javascript/ql/lib/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.1 + +No user-facing changes. + ## 0.7.0 ### Minor Analysis Improvements diff --git a/javascript/ql/lib/change-notes/released/0.7.1.md b/javascript/ql/lib/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..86973d36042 --- /dev/null +++ b/javascript/ql/lib/change-notes/released/0.7.1.md @@ -0,0 +1,3 @@ +## 0.7.1 + +No user-facing changes. diff --git a/javascript/ql/lib/codeql-pack.release.yml b/javascript/ql/lib/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/javascript/ql/lib/codeql-pack.release.yml +++ b/javascript/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/javascript/ql/lib/qlpack.yml b/javascript/ql/lib/qlpack.yml index 20867487b74..f5bf5786071 100644 --- a/javascript/ql/lib/qlpack.yml +++ b/javascript/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/javascript-all -version: 0.7.0 +version: 0.7.1 groups: javascript dbscheme: semmlecode.javascript.dbscheme extractor: javascript diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll index 29c50fca302..179e1b4dfe5 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll @@ -166,6 +166,26 @@ abstract class Configuration extends string { ) } + /** + * Holds if flow into `node` is prohibited. + */ + predicate isBarrierIn(DataFlow::Node node) { none() } + + /** + * Holds if flow out `node` is prohibited. + */ + predicate isBarrierOut(DataFlow::Node node) { none() } + + /** + * Holds if flow into `node` is prohibited for the flow label `lbl`. + */ + predicate isBarrierIn(DataFlow::Node node, FlowLabel lbl) { none() } + + /** + * Holds if flow out `node` is prohibited for the flow label `lbl`. + */ + predicate isBarrierOut(DataFlow::Node node, FlowLabel lbl) { none() } + /** * Holds if flow from `pred` to `succ` is prohibited. */ @@ -494,7 +514,7 @@ private BasicBlock getADominatedBasicBlock(BarrierGuardNode guard, ConditionGuar * * Only holds for barriers that should apply to all flow labels. */ -private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ) { +private predicate isBarrierEdgeRaw(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ) { cfg.isBarrierEdge(pred, succ) or exists(DataFlow::BarrierGuardNode guard | @@ -503,11 +523,26 @@ private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow ) } +/** + * Holds if there is a barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge + * or one implied by a barrier guard, or by an out/in barrier for `pred` or `succ`, respectively. + * + * Only holds for barriers that should apply to all flow labels. + */ +pragma[inline] +private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ) { + isBarrierEdgeRaw(cfg, pred, succ) + or + cfg.isBarrierOut(pred) + or + cfg.isBarrierIn(succ) +} + /** * Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge * or one implied by a barrier guard. */ -private predicate isLabeledBarrierEdge( +private predicate isLabeledBarrierEdgeRaw( Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label ) { cfg.isBarrierEdge(pred, succ, label) @@ -518,6 +553,21 @@ private predicate isLabeledBarrierEdge( ) } +/** + * Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge + * or one implied by a barrier guard, or by an out/in barrier for `pred` or `succ`, respectively. + */ +pragma[inline] +private predicate isLabeledBarrierEdge( + Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label +) { + isLabeledBarrierEdgeRaw(cfg, pred, succ, label) + or + cfg.isBarrierOut(pred, label) + or + cfg.isBarrierIn(succ, label) +} + /** * A guard node that only blocks specific labels. */ diff --git a/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll index 96d97a270c6..11ce802ac72 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll @@ -62,6 +62,26 @@ module TaintTracking { */ predicate isSanitizer(DataFlow::Node node) { none() } + /** + * Holds if flow into `node` is prohibited. + */ + predicate isSanitizerIn(DataFlow::Node node) { none() } + + /** + * Holds if flow out `node` is prohibited. + */ + predicate isSanitizerOut(DataFlow::Node node) { none() } + + /** + * Holds if flow into `node` is prohibited for the flow label `lbl`. + */ + predicate isSanitizerIn(DataFlow::Node node, DataFlow::FlowLabel lbl) { none() } + + /** + * Holds if flow out `node` is prohibited for the flow label `lbl`. + */ + predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowLabel lbl) { none() } + /** Holds if the edge from `pred` to `succ` is a taint sanitizer. */ predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { none() } @@ -108,6 +128,22 @@ module TaintTracking { this.isSanitizerEdge(source, sink) and lbl.isTaint() } + final override predicate isBarrierIn(DataFlow::Node node) { none() } + + final override predicate isBarrierOut(DataFlow::Node node) { none() } + + final override predicate isBarrierIn(DataFlow::Node node, DataFlow::FlowLabel lbl) { + this.isSanitizerIn(node, lbl) + or + this.isSanitizerIn(node) and lbl.isTaint() + } + + final override predicate isBarrierOut(DataFlow::Node node, DataFlow::FlowLabel lbl) { + this.isSanitizerOut(node, lbl) + or + this.isSanitizerOut(node) and lbl.isTaint() + } + final override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) { super.isBarrierGuard(guard) or guard.(AdditionalSanitizerGuardNode).appliesTo(this) or diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll index 9ca15b1872f..258a583e1ca 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll @@ -554,7 +554,11 @@ module NodeJSLib { t.start() or t.start() and - result = DataFlow::moduleMember("fs", "promises") + ( + result = DataFlow::moduleMember("fs", "promises") + or + result = DataFlow::moduleImport("fs/promises") + ) or exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred | pred = fsModule(t2) | result = pred.track(t2, t) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/BuildArtifactLeakQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/BuildArtifactLeakQuery.qll index 8af94f2f12c..db48ae25952 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/BuildArtifactLeakQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/BuildArtifactLeakQuery.qll @@ -27,10 +27,6 @@ class Configuration extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node node) { node instanceof CleartextLogging::Barrier } - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { - CleartextLogging::isSanitizerEdge(pred, succ) - } - override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) { CleartextLogging::isAdditionalTaintStep(src, trg) } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll index 4caad07634c..c783a9c3cfc 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll @@ -175,12 +175,24 @@ module CleartextLogging { } /** + * DEPRECATED. Use `Barrier` instead, sanitized have been replaced by sanitized nodes. + * * Holds if the edge `pred` -> `succ` should be sanitized for clear-text logging of sensitive information. */ - predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { + deprecated predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { succ.(DataFlow::PropRead).getBase() = pred } + private class PropReadAsBarrier extends Barrier { + PropReadAsBarrier() { + this = any(DataFlow::PropRead read).getBase() and + // the 'foo' in 'foo.bar()' may have flow, we only want to suppress plain property reads + not this = any(DataFlow::MethodCallNode call).getReceiver() and + // do not block custom taint steps from this node + not isAdditionalTaintStep(this, _) + } + } + /** * Holds if the edge `src` -> `trg` is an additional taint-step for clear-text logging of sensitive information. */ diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CleartextLoggingQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CleartextLoggingQuery.qll index ca53cad67ec..fe0a1073e08 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CleartextLoggingQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CleartextLoggingQuery.qll @@ -33,10 +33,6 @@ class Configuration extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node node) { node instanceof Barrier } - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { - CleartextLogging::isSanitizerEdge(pred, succ) - } - override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) { CleartextLogging::isAdditionalTaintStep(src, trg) } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideRequestForgeryQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideRequestForgeryQuery.qll index 6b3e9e76537..8e5a46576f2 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideRequestForgeryQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideRequestForgeryQuery.qll @@ -31,9 +31,7 @@ class Configuration extends TaintTracking::Configuration { node instanceof Sanitizer } - override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) { - sanitizingPrefixEdge(source, sink) - } + override predicate isSanitizerOut(DataFlow::Node node) { sanitizingPrefixEdge(node, _) } override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { isAdditionalRequestForgeryStep(pred, succ) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectQuery.qll index 1f0d4c02702..0e1ceb955dd 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectQuery.qll @@ -33,9 +33,7 @@ class Configuration extends TaintTracking::Configuration { node instanceof Sanitizer } - override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) { - hostnameSanitizingPrefixEdge(source, sink) - } + override predicate isSanitizerOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) } override predicate isAdditionalFlowStep( DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel f, DataFlow::FlowLabel g diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll index f372f62cbd7..f62fff2d886 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll @@ -290,9 +290,13 @@ module DomBasedXss { private class HtmlSanitizerAsSanitizer extends Sanitizer instanceof HtmlSanitizerCall { } /** + * DEPRECATED. Use `isOptionallySanitizedNode` instead. + * * Holds if there exists two dataflow edges to `succ`, where one edges is sanitized, and the other edge starts with `pred`. */ - predicate isOptionallySanitizedEdge(DataFlow::Node pred, DataFlow::Node succ) { + deprecated predicate isOptionallySanitizedEdge = isOptionallySanitizedEdgeInternal/2; + + private predicate isOptionallySanitizedEdgeInternal(DataFlow::Node pred, DataFlow::Node succ) { exists(HtmlSanitizerCall sanitizer | // sanitized = sanitize ? sanitizer(source) : source; exists(ConditionalExpr branch, Variable var, VarAccess access | @@ -319,6 +323,17 @@ module DomBasedXss { ) } + /** + * Holds if `node` should be considered optionally sanitized as it occurs in a branch + * that controls whether sanitization is enabled. + * + * For example, in `sanitized = sanitize ? sanitizer(source) : source`, the right-hand `source` expression + * is considered an optionally sanitized node. + */ + predicate isOptionallySanitizedNode(DataFlow::Node node) { + isOptionallySanitizedEdgeInternal(_, node) + } + /** A source of remote user input, considered as a flow source for DOM-based XSS. */ class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssQuery.qll index fa64b7300b0..cc4fc0c47ea 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssQuery.qll @@ -86,13 +86,9 @@ class Configuration extends TaintTracking::Configuration { // we assume that `.join()` calls have a prefix, and thus block the prefix label. node = any(DataFlow::MethodCallNode call | call.getMethodName() = "join") and lbl = prefixLabel() - } - - override predicate isSanitizerEdge( - DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label - ) { - isOptionallySanitizedEdge(pred, succ) and - label = [DataFlow::FlowLabel::taint(), prefixLabel(), TaintedUrlSuffix::label()] + or + isOptionallySanitizedNode(node) and + lbl = [DataFlow::FlowLabel::taint(), prefixLabel(), TaintedUrlSuffix::label()] } override predicate isAdditionalFlowStep( diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataQuery.qll index 857cf837de2..b6d8c7fa088 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataQuery.qll @@ -46,15 +46,11 @@ class Configuration extends TaintTracking::Configuration { ) } - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { + override predicate isSanitizerIn(DataFlow::Node node) { // Block flow from the location to its properties, as the relevant properties (hash and search) are taint sources of their own. // The location source is only used for propagating through API calls like `new URL(location)` and into external APIs where // the whole location object escapes. - exists(DataFlow::PropRead read | - read = DOM::locationRef().getAPropertyRead() and - pred = read.getBase() and - succ = read - ) + node = DOM::locationRef().getAPropertyRead() } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/InsecureRandomnessQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/InsecureRandomnessQuery.qll index 7911fa7c619..78dfdbfe833 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/InsecureRandomnessQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/InsecureRandomnessQuery.qll @@ -27,11 +27,9 @@ class Configuration extends TaintTracking::Configuration { node instanceof Sanitizer } - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { + override predicate isSanitizerOut(DataFlow::Node node) { // stop propagation at the sinks to avoid double reporting - pred instanceof Sink and - // constrain succ - pred = succ.getAPredecessor() + this.isSink(node) } override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll index 4c624e7950c..0ba2f26b24c 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentQuery.qll @@ -55,20 +55,11 @@ class Configuration extends TaintTracking::Configuration { ) } - override predicate isSanitizerEdge( - DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel lbl - ) { + override predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowLabel lbl) { // Suppress the value-preserving step src -> dst in `extend(dst, src)`. This is modeled as a value-preserving // step because it preserves all properties, but the destination is not actually Object.prototype. - exists(ExtendCall call | - pred = call.getASourceOperand() and - ( - succ = call.getDestinationOperand().getALocalSource() - or - succ = call - ) and - lbl instanceof ObjectPrototype - ) + node = any(ExtendCall call).getASourceOperand() and + lbl instanceof ObjectPrototype } override predicate isAdditionalFlowStep( diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryQuery.qll index 7ab858dcc14..9c67df35ed9 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryQuery.qll @@ -26,9 +26,7 @@ class Configuration extends TaintTracking::Configuration { node instanceof Sanitizer } - override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) { - sanitizingPrefixEdge(source, sink) - } + override predicate isSanitizerOut(DataFlow::Node node) { sanitizingPrefixEdge(node, _) } override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { isAdditionalRequestForgeryStep(pred, succ) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionQuery.qll index 457d6e15080..366d1db6973 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionQuery.qll @@ -22,7 +22,8 @@ class Configuration extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or - node instanceof Sanitizer + node instanceof Sanitizer or + node = any(DataFlow::PropRead read | read.getPropertyName() = "length") } override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node dst) { @@ -32,10 +33,6 @@ class Configuration extends TaintTracking::Configuration { override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) { guard instanceof UpperBoundsCheckSanitizerGuard } - - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { - succ.(DataFlow::PropRead).accesses(pred, "length") - } } /** Holds if data is converted to a number from `src` to `dst`. */ diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectQuery.qll index 8c499047277..7f16f7f49dd 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectQuery.qll @@ -27,9 +27,7 @@ class Configuration extends TaintTracking::Configuration { node instanceof Sanitizer } - override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) { - hostnameSanitizingPrefixEdge(source, sink) - } + override predicate isSanitizerOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) } override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) { guard instanceof LocalUrlSanitizingGuard or diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index 3ede2560c31..cd1bb80fce4 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -847,6 +847,22 @@ module TaintedPath { dst = call and srclabel = dstlabel ) + or + exists(DataFlow::CallNode join | + // path.join() with spread argument + join = NodeJSLib::Path::moduleMember("join").getACall() and + src = join.getASpreadArgument() and + dst = join and + ( + srclabel.(Label::PosixPath).canContainDotDotSlash() + or + srclabel instanceof Label::SplitPath + ) and + dstlabel.(Label::PosixPath).isNormalized() and + if isRelative(join.getArgument(0).getStringValue()) + then dstlabel.(Label::PosixPath).isRelative() + else dstlabel.(Label::PosixPath).isAbsolute() + ) } /** diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionQuery.qll index fd0c3ee76aa..ed655e60412 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionQuery.qll @@ -31,10 +31,8 @@ class Configration extends TaintTracking::Configuration { node instanceof DomBasedXss::Sanitizer or node instanceof UnsafeJQueryPlugin::Sanitizer - } - - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { - DomBasedXss::isOptionallySanitizedEdge(pred, succ) + or + DomBasedXss::isOptionallySanitizedNode(node) } // override to require that there is a path without unmatched return steps diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeJQueryPluginQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeJQueryPluginQuery.qll index 1c82f7ff0c6..e4b70c176cc 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeJQueryPluginQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeJQueryPluginQuery.qll @@ -31,16 +31,13 @@ class Configuration extends TaintTracking::Configuration { aliasPropertyPresenceStep(src, sink) } - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { + override predicate isSanitizerOut(DataFlow::Node node) { // prefixing prevents forced html/css confusion: // prefixing through concatenation: - StringConcatenation::taintStep(pred, succ, _, any(int i | i >= 1)) + StringConcatenation::taintStep(node, _, _, any(int i | i >= 1)) or // prefixing through a poor-mans templating system: - exists(StringReplaceCall replace | - replace = succ and - pred = replace.getRawReplacement() - ) + node = any(StringReplaceCall call).getRawReplacement() } override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) { diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll index 704bd4a94a5..d81227bcd68 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll @@ -34,11 +34,24 @@ module UnvalidatedDynamicMethodCall { /** * A sanitizer for unvalidated dynamic method calls. - * Override the `sanitizes` predicate to specify an edge that should be sanitized. - * The `this` value is not seen as a sanitizer. */ abstract class Sanitizer extends DataFlow::Node { - abstract predicate sanitizes(DataFlow::Node source, DataFlow::Node sink, DataFlow::FlowLabel lbl); + /** + * Gets the flow label blocked by this sanitizer. + */ + DataFlow::FlowLabel getFlowLabel() { result.isTaint() } + + /** + * DEPRECATED. Use sanitizer nodes instead. + * + * This predicate no longer has any effect. The `this` value of `Sanitizer` is instead + * treated as a sanitizing node, that is, flow in and out of that node is prohibited. + */ + deprecated predicate sanitizes( + DataFlow::Node source, DataFlow::Node sink, DataFlow::FlowLabel lbl + ) { + none() + } } /** diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallQuery.qll index 7a0b6594194..921ab7f88e2 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallQuery.qll @@ -38,10 +38,10 @@ class Configuration extends TaintTracking::Configuration { sink.(Sink).getFlowLabel() = label } - override predicate isSanitizerEdge( - DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel lbl - ) { - any(Sanitizer s).sanitizes(pred, succ, lbl) + override predicate isLabeledBarrier(DataFlow::Node node, DataFlow::FlowLabel label) { + super.isLabeledBarrier(node, label) + or + node.(Sanitizer).getFlowLabel() = label } override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) { diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomQuery.qll index 70c1f65d5da..cc75078fd67 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomQuery.qll @@ -20,7 +20,8 @@ class Configuration extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or - node instanceof DomBasedXss::Sanitizer + node instanceof DomBasedXss::Sanitizer or + DomBasedXss::isOptionallySanitizedNode(node) } override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) { @@ -32,10 +33,6 @@ class Configuration extends TaintTracking::Configuration { guard instanceof ContainsHtmlGuard } - override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { - DomBasedXss::isOptionallySanitizedEdge(pred, succ) - } - override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { succ = DataFlow::globalVarRef("URL").getAMemberCall("createObjectURL") and pred = succ.(DataFlow::InvokeNode).getArgument(0) diff --git a/javascript/ql/src/CHANGELOG.md b/javascript/ql/src/CHANGELOG.md index 1adbe57d5f5..05fd164a7f1 100644 --- a/javascript/ql/src/CHANGELOG.md +++ b/javascript/ql/src/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.7.1 + +### Minor Analysis Improvements + +* The `fs/promises` package is now recognised as an alias for `require('fs').promises`. +* The `js/path-injection` query can now track taint through calls to `path.join()` with a spread argument, such as `path.join(baseDir, ...args)`. + ## 0.7.0 ### Bug Fixes diff --git a/javascript/ql/src/Security/CWE-078/CommandInjection.inc.qhelp b/javascript/ql/src/Security/CWE-078/CommandInjection.inc.qhelp index b7fbc9493fc..5e598d23595 100644 --- a/javascript/ql/src/Security/CWE-078/CommandInjection.inc.qhelp +++ b/javascript/ql/src/Security/CWE-078/CommandInjection.inc.qhelp @@ -2,43 +2,65 @@ "-//Semmle//qhelp//EN" "qhelp.dtd"> + -

Code that passes user input directly to -require('child_process').exec, or some other library -routine that executes a command, allows the user to execute malicious -code.

- +

Code that passes untrusted user input directly to +child_process.exec or similar APIs that execute shell commands +allows the user to execute malicious code.

+ +

If possible, use APIs that don't run shell commands and that accept command +arguments as an array of strings rather than a single concatenated string. This +is both safer and more portable.

-

If possible, use hard-coded string literals to specify the command to run -or library to load. Instead of passing the user input directly to the -process or library function, examine the user input and then choose -among hard-coded string literals.

- -

If the applicable libraries or commands cannot be determined at -compile time, then add code to verify that the user input string is -safe before using it.

+

If given arguments as a single string, avoid simply splitting the string on +whitespace. Arguments may contain quoted whitespace, causing them to split into +multiple arguments. Use a library like shell-quote to parse the string +into an array of arguments instead.

+

If this approach is not viable, then add code to verify that the user input +string is safe before using it.

- -

The following example shows code that takes a shell script that can be changed -maliciously by a user, and passes it straight to child_process.exec -without examining it first.

+ +

The following example shows code that extracts a filename from an HTTP query +parameter that may contain untrusted data, and then embeds it into a shell +command to count its lines without examining it first:

-
- +

A malicious user can take advantage of this code by executing arbitrary shell commands. For example, by providing a filename like foo.txt; rm -rf ., the user can first count the lines in foo.txt and subsequently delete all files in the current directory.

+

To avoid this catastrophic behavior, use an API such as +child_process.execFileSync that does not spawn a shell by +default:

+ + + +

If you want to allow the user to specify other options to wc, +you can use a library like shell-quote to parse the user input into +an array of arguments without risking command injection:

+ + + +

Alternatively, the original example can be made safe by checking the filename +against an allowlist of safe characters before using it:

+ + +
+ +
  • OWASP: Command Injection.
  • - +
  • +npm: +shell-quote. +
  • -
    +
    diff --git a/javascript/ql/src/Security/CWE-078/IndirectCommandInjection.qhelp b/javascript/ql/src/Security/CWE-078/IndirectCommandInjection.qhelp index 41898a1d097..83ddaef0814 100644 --- a/javascript/ql/src/Security/CWE-078/IndirectCommandInjection.qhelp +++ b/javascript/ql/src/Security/CWE-078/IndirectCommandInjection.qhelp @@ -27,27 +27,25 @@

    - If possible, use hard-coded string literals to specify the - command to run or library to load. Instead of forwarding the - command-line arguments to the process, examine the command-line - arguments and then choose among hard-coded string literals. + If possible, use APIs that don't run shell commands and accept + command arguments as an array of strings rather than a single + concatenated string. This is both safer and more portable.

    - If the applicable libraries or commands cannot be determined - at compile time, then add code to verify that each forwarded - command-line argument is properly escaped before using it. +If given arguments as a single string, avoid simply splitting the string on +whitespace. Arguments may contain quoted whitespace, causing them to split into +multiple arguments. Use a library like shell-quote to parse the string +into an array of arguments instead.

    - If the forwarded command-line arguments are part of the - arguments of the system command, prefer a library routine that handles - the arguments as an array of strings rather than a single concatenated - string. This prevents the unexpected evaluation of special characters. + If this approach is not viable, then add code to verify that each + forwarded command-line argument is properly escaped before using it.

    @@ -91,6 +89,17 @@ +

    + + If you want to allow the user to specify other options to + node, you can use a library like + shell-quote to parse the user input into an array of + arguments without risking command injection: + +

    + + + @@ -100,6 +109,11 @@ Command Injection. +
  • + npm: + shell-quote. +
  • +
    diff --git a/javascript/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.qhelp b/javascript/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.qhelp index 5017db6119d..e2829cf85e0 100644 --- a/javascript/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.qhelp +++ b/javascript/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.qhelp @@ -27,9 +27,22 @@

    - Alternatively, if the shell command must be constructed - dynamically, then add code to ensure that special characters - do not alter the shell command unexpectedly. + + If given arguments as a single string, avoid simply splitting the string + on whitespace. Arguments may contain quoted whitespace, causing them to + split into multiple arguments. Use a library like + shell-quote to parse the string into an array of arguments + instead. + +

    + +

    + + Alternatively, if the command must be interpreted by a shell (for + example because it includes I/O redirections), you can use + shell-quote to escape any special characters in the input + before embedding it in the command. +

    @@ -63,6 +76,27 @@ +

    + + As another example, consider the following code which is similar to the + preceding example, but pipes the output of wget into wc -l + to count the number of lines in the downloaded file. + +

    + + + +

    + + In this case, using child_process.execFile is not an option + because the shell is needed to interpret the pipe operator. Instead, you + can use shell-quote to escape the input before embedding it + in the command: + +

    + + + @@ -71,5 +105,10 @@ Command Injection. +
  • + npm: + shell-quote. +
  • +
    diff --git a/javascript/ql/src/Security/CWE-078/examples/command-injection.js b/javascript/ql/src/Security/CWE-078/examples/command-injection.js index e88e7571043..60031ab18e3 100644 --- a/javascript/ql/src/Security/CWE-078/examples/command-injection.js +++ b/javascript/ql/src/Security/CWE-078/examples/command-injection.js @@ -3,7 +3,7 @@ var cp = require("child_process"), url = require('url'); var server = http.createServer(function(req, res) { - let cmd = url.parse(req.url, true).query.path; + let file = url.parse(req.url, true).query.path; - cp.exec(cmd); // BAD + cp.execSync(`wc -l ${file}`); // BAD }); diff --git a/javascript/ql/src/Security/CWE-078/examples/command-injection_allowlist.js b/javascript/ql/src/Security/CWE-078/examples/command-injection_allowlist.js new file mode 100644 index 00000000000..96573c4840f --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/command-injection_allowlist.js @@ -0,0 +1,12 @@ +var cp = require("child_process"), + http = require('http'), + url = require('url'); + +var server = http.createServer(function(req, res) { + let file = url.parse(req.url, true).query.path; + + // only allow safe characters in file name + if (file.match(/^[\w\.\-\/]+$/)) { + cp.execSync(`wc -l ${file}`); // GOOD + } +}); diff --git a/javascript/ql/src/Security/CWE-078/examples/command-injection_fixed.js b/javascript/ql/src/Security/CWE-078/examples/command-injection_fixed.js new file mode 100644 index 00000000000..17011e6e662 --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/command-injection_fixed.js @@ -0,0 +1,9 @@ +var cp = require("child_process"), + http = require('http'), + url = require('url'); + +var server = http.createServer(function(req, res) { + let file = url.parse(req.url, true).query.path; + + cp.execFileSync('wc', ['-l', file]); // GOOD +}); diff --git a/javascript/ql/src/Security/CWE-078/examples/command-injection_shellquote.js b/javascript/ql/src/Security/CWE-078/examples/command-injection_shellquote.js new file mode 100644 index 00000000000..ba566e89d41 --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/command-injection_shellquote.js @@ -0,0 +1,10 @@ +var cp = require("child_process"), + http = require('http'), + url = require('url'), + shellQuote = require('shell-quote'); + +var server = http.createServer(function(req, res) { + let options = url.parse(req.url, true).query.options; + + cp.execFileSync('wc', shellQuote.parse(options)); // GOOD +}); diff --git a/javascript/ql/src/Security/CWE-078/examples/indirect-command-injection.js b/javascript/ql/src/Security/CWE-078/examples/indirect-command-injection.js index bb14b35f363..3926ea284ce 100644 --- a/javascript/ql/src/Security/CWE-078/examples/indirect-command-injection.js +++ b/javascript/ql/src/Security/CWE-078/examples/indirect-command-injection.js @@ -2,4 +2,4 @@ var cp = require("child_process"); const args = process.argv.slice(2); const script = path.join(__dirname, 'bin', 'main.js'); -cp.execSync(`node ${script} ${args.join(' ')}"`); // BAD +cp.execSync(`node ${script} ${args.join(' ')}`); // BAD diff --git a/javascript/ql/src/Security/CWE-078/examples/indirect-command-injection_shellquote.js b/javascript/ql/src/Security/CWE-078/examples/indirect-command-injection_shellquote.js new file mode 100644 index 00000000000..58dd322e412 --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/indirect-command-injection_shellquote.js @@ -0,0 +1,11 @@ +var cp = require("child_process"), + shellQuote = require("shell-quote"); + +const args = process.argv.slice(2); +let nodeOpts = ''; +if (args[0] === '--node-opts') { + nodeOpts = args[1]; + args.splice(0, 2); +} +const script = path.join(__dirname, 'bin', 'main.js'); +cp.execFileSync('node', shellQuote.parse(nodeOpts).concat(script).concat(args)); // GOOD diff --git a/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction.js b/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction.js index f8f3d8b7514..d2d1869746f 100644 --- a/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction.js +++ b/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction.js @@ -1,5 +1,5 @@ var cp = require("child_process"); module.exports = function download(path, callback) { - cp.execSync("wget " + path, callback); + cp.exec("wget " + path, callback); } diff --git a/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_fixed.js b/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_fixed.js index 4a8c880ad8f..9f6bb249adc 100644 --- a/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_fixed.js +++ b/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_fixed.js @@ -1,5 +1,5 @@ var cp = require("child_process"); module.exports = function download(path, callback) { - cp.execFileSync("wget", [path], callback); + cp.execFile("wget", [path], callback); } diff --git a/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_pipe.js b/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_pipe.js new file mode 100644 index 00000000000..3ebf2e615a9 --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_pipe.js @@ -0,0 +1,5 @@ +var cp = require("child_process"); + +module.exports = function download(path, callback) { + cp.exec("wget " + path + " | wc -l", callback); +}; diff --git a/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_pipe_fixed.js b/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_pipe_fixed.js new file mode 100644 index 00000000000..147f10ae78b --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/unsafe-shell-command-construction_pipe_fixed.js @@ -0,0 +1,5 @@ +var cp = require("child_process"); + +module.exports = function download(path, callback) { + cp.exec("wget " + shellQuote.quote([path]) + " | wc -l", callback); +}; diff --git a/javascript/ql/src/change-notes/released/0.7.1.md b/javascript/ql/src/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..9c2217a2a3d --- /dev/null +++ b/javascript/ql/src/change-notes/released/0.7.1.md @@ -0,0 +1,6 @@ +## 0.7.1 + +### Minor Analysis Improvements + +* The `fs/promises` package is now recognised as an alias for `require('fs').promises`. +* The `js/path-injection` query can now track taint through calls to `path.join()` with a spread argument, such as `path.join(baseDir, ...args)`. diff --git a/javascript/ql/src/codeql-pack.release.yml b/javascript/ql/src/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/javascript/ql/src/codeql-pack.release.yml +++ b/javascript/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/javascript/ql/src/experimental/Security/CWE-918/SSRF.qll b/javascript/ql/src/experimental/Security/CWE-918/SSRF.qll index c43418d453a..95d46aad868 100644 --- a/javascript/ql/src/experimental/Security/CWE-918/SSRF.qll +++ b/javascript/ql/src/experimental/Security/CWE-918/SSRF.qll @@ -29,8 +29,8 @@ class Configuration extends TaintTracking::Configuration { ) } - override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) { - this.strictSanitizingPrefixEdge(source, sink) + override predicate isSanitizerOut(DataFlow::Node node) { + this.strictSanitizingPrefixEdge(node, _) } override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) { diff --git a/javascript/ql/src/qlpack.yml b/javascript/ql/src/qlpack.yml index bf8d5393824..9837128cf26 100644 --- a/javascript/ql/src/qlpack.yml +++ b/javascript/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/javascript-queries -version: 0.7.0 +version: 0.7.1 groups: - javascript - queries diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlowConfig.qll b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlowConfig.qll index eccf834a0e1..12edfc8b713 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlowConfig.qll +++ b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlowConfig.qll @@ -22,10 +22,7 @@ class TestDataFlowConfiguration extends DataFlow::Configuration { f.getName().matches("%noReturnTracking%") and node = f.getAReturnedExpr().flow() ) - } - - override predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node snk) { - src = src and - snk.asExpr().(PropAccess).getPropertyName() = "notTracked" + or + node.asExpr().(PropAccess).getPropertyName() = "notTracked" } } diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/tests.ql b/javascript/ql/test/library-tests/InterProceduralFlow/tests.ql index a6733749676..a490c4c9146 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/tests.ql +++ b/javascript/ql/test/library-tests/InterProceduralFlow/tests.ql @@ -61,11 +61,8 @@ class TestTaintTrackingConfiguration extends TaintTracking::Configuration { f.getName().matches("%noReturnTracking%") and node = f.getAReturnedExpr().flow() ) - } - - override predicate isSanitizerEdge(DataFlow::Node src, DataFlow::Node snk) { - src = src and - snk.asExpr().(PropAccess).getPropertyName() = "notTracked" + or + node.asExpr().(PropAccess).getPropertyName() = "notTracked" } } @@ -99,11 +96,8 @@ class GermanFlowConfig extends DataFlow::Configuration { f.getName().matches("%noReturnTracking%") and node = f.getAReturnedExpr().flow() ) - } - - override predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node snk) { - src = src and - snk.asExpr().(PropAccess).getPropertyName() = "notTracked" + or + node.asExpr().(PropAccess).getPropertyName() = "notTracked" } } diff --git a/javascript/ql/test/library-tests/TaintBarriers/ExampleConfiguration.qll b/javascript/ql/test/library-tests/TaintBarriers/ExampleConfiguration.qll index f730dd81891..50ac0fbfd24 100644 --- a/javascript/ql/test/library-tests/TaintBarriers/ExampleConfiguration.qll +++ b/javascript/ql/test/library-tests/TaintBarriers/ExampleConfiguration.qll @@ -1,10 +1,18 @@ import javascript +DataFlow::Node sourceVariable() { result.asExpr().(VarRef).getName() = "sourceVariable" } + +StringOps::ConcatenationRoot sinkConcatenation() { + result.getConstantStringParts().matches("%") +} + class ExampleConfiguration extends TaintTracking::Configuration { ExampleConfiguration() { this = "ExampleConfiguration" } override predicate isSource(DataFlow::Node source) { source.asExpr().(CallExpr).getCalleeName() = "SOURCE" + or + source = sourceVariable() } override predicate isSink(DataFlow::Node sink) { @@ -12,8 +20,14 @@ class ExampleConfiguration extends TaintTracking::Configuration { callExpr.getCalleeName() = "SINK" and DataFlow::valueNode(callExpr.getArgument(0)) = sink ) + or + sink = sinkConcatenation() } + override predicate isSanitizerIn(DataFlow::Node node) { node = sourceVariable() } + + override predicate isSanitizerOut(DataFlow::Node node) { node = sinkConcatenation() } + override predicate isSanitizer(DataFlow::Node node) { exists(CallExpr callExpr | callExpr.getCalleeName() = "SANITIZE" and diff --git a/javascript/ql/test/library-tests/TaintBarriers/sanitizer-in-out.js b/javascript/ql/test/library-tests/TaintBarriers/sanitizer-in-out.js new file mode 100644 index 00000000000..244d3899856 --- /dev/null +++ b/javascript/ql/test/library-tests/TaintBarriers/sanitizer-in-out.js @@ -0,0 +1,18 @@ +import 'dummy'; + +function barrierIn() { + var sourceVariable = 123; + SINK(sourceVariable); // NOT OK + + flowWithSourceParam(sourceVariable); +} + +function barrierInParameter(sourceVariable) { + SINK(sourceVariable); // NOT OK, but only report the parameter as the source +} + +function barrierOut() { + let taint = SOURCE(); + taint = "" + taint + ""; // NOT OK + taint = "" + taint + ""; // OK - only report first instance +} diff --git a/javascript/ql/test/library-tests/TaintBarriers/tests.expected b/javascript/ql/test/library-tests/TaintBarriers/tests.expected index 3ee1223b87d..4417a918423 100644 --- a/javascript/ql/test/library-tests/TaintBarriers/tests.expected +++ b/javascript/ql/test/library-tests/TaintBarriers/tests.expected @@ -133,6 +133,9 @@ sanitizingGuard | tst.js:399:16:399:41 | o.hasOw ... "p.q"]) | tst.js:399:33:399:40 | v["p.q"] | true | | tst.js:401:16:401:34 | Object.hasOwn(o, v) | tst.js:401:33:401:33 | v | true | taintedSink +| sanitizer-in-out.js:5:10:5:23 | sourceVariable | sanitizer-in-out.js:5:10:5:23 | sourceVariable | +| sanitizer-in-out.js:11:10:11:23 | sourceVariable | sanitizer-in-out.js:11:10:11:23 | sourceVariable | +| sanitizer-in-out.js:15:17:15:24 | SOURCE() | sanitizer-in-out.js:16:13:16:40 | " ... /sink>" | | tst.js:2:13:2:20 | SOURCE() | tst.js:3:10:3:10 | v | | tst.js:2:13:2:20 | SOURCE() | tst.js:8:14:8:14 | v | | tst.js:2:13:2:20 | SOURCE() | tst.js:12:14:12:14 | v | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index b41071a3989..2d1692dce00 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -2163,6 +2163,28 @@ nodes | normalizedPaths.js:399:21:399:24 | path | | normalizedPaths.js:399:21:399:24 | path | | normalizedPaths.js:399:21:399:24 | path | +| normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:45:407:55 | req.query.x | +| normalizedPaths.js:407:45:407:55 | req.query.x | +| normalizedPaths.js:407:45:407:55 | req.query.x | +| normalizedPaths.js:407:45:407:55 | req.query.x | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:38:408:48 | req.query.x | +| normalizedPaths.js:408:38:408:48 | req.query.x | +| normalizedPaths.js:408:38:408:48 | req.query.x | +| normalizedPaths.js:408:38:408:48 | req.query.x | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | | other-fs-libraries.js:9:7:9:48 | path | | other-fs-libraries.js:9:7:9:48 | path | | other-fs-libraries.js:9:7:9:48 | path | @@ -2813,6 +2835,92 @@ nodes | other-fs-libraries.js:72:15:72:18 | path | | other-fs-libraries.js:72:15:72:18 | path | | other-fs-libraries.js:72:15:72:18 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:24:77:30 | req.url | +| other-fs-libraries.js:77:24:77:30 | req.url | +| other-fs-libraries.js:77:24:77:30 | req.url | +| other-fs-libraries.js:77:24:77:30 | req.url | +| other-fs-libraries.js:77:24:77:30 | req.url | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:79:16:79:19 | path | | prettier.js:6:11:6:28 | p | | prettier.js:6:11:6:28 | p | | prettier.js:6:11:6:28 | p | @@ -7264,6 +7372,30 @@ edges | normalizedPaths.js:385:35:385:45 | req.query.x | normalizedPaths.js:385:14:385:46 | pathMod ... uery.x) | | normalizedPaths.js:385:35:385:45 | req.query.x | normalizedPaths.js:385:14:385:46 | pathMod ... uery.x) | | normalizedPaths.js:385:35:385:45 | req.query.x | normalizedPaths.js:385:14:385:46 | pathMod ... uery.x) | +| normalizedPaths.js:407:45:407:55 | req.query.x | normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:407:45:407:55 | req.query.x | normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:407:45:407:55 | req.query.x | normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:407:45:407:55 | req.query.x | normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:407:45:407:55 | req.query.x | normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:407:45:407:55 | req.query.x | normalizedPaths.js:407:45:407:66 | req.que ... it('/') | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:407:45:407:66 | req.que ... it('/') | normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | +| normalizedPaths.js:408:38:408:48 | req.query.x | normalizedPaths.js:408:38:408:59 | req.que ... it('/') | +| normalizedPaths.js:408:38:408:48 | req.query.x | normalizedPaths.js:408:38:408:59 | req.que ... it('/') | +| normalizedPaths.js:408:38:408:48 | req.query.x | normalizedPaths.js:408:38:408:59 | req.que ... it('/') | +| normalizedPaths.js:408:38:408:48 | req.query.x | normalizedPaths.js:408:38:408:59 | req.que ... it('/') | +| normalizedPaths.js:408:38:408:48 | req.query.x | normalizedPaths.js:408:38:408:59 | req.que ... it('/') | +| normalizedPaths.js:408:38:408:48 | req.query.x | normalizedPaths.js:408:38:408:59 | req.que ... it('/') | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | +| normalizedPaths.js:408:38:408:59 | req.que ... it('/') | normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | | other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | | other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | | other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | @@ -8288,6 +8420,118 @@ edges | other-fs-libraries.js:68:24:68:30 | req.url | other-fs-libraries.js:68:14:68:37 | url.par ... , true) | | other-fs-libraries.js:68:24:68:30 | req.url | other-fs-libraries.js:68:14:68:37 | url.par ... , true) | | other-fs-libraries.js:68:24:68:30 | req.url | other-fs-libraries.js:68:14:68:37 | url.par ... , true) | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:7:77:48 | path | other-fs-libraries.js:79:16:79:19 | path | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:37 | url.par ... , true) | other-fs-libraries.js:77:14:77:43 | url.par ... ).query | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:43 | url.par ... ).query | other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:14:77:48 | url.par ... ry.path | other-fs-libraries.js:77:7:77:48 | path | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | +| other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:77:14:77:37 | url.par ... , true) | | prettier.js:6:11:6:28 | p | prettier.js:7:28:7:28 | p | | prettier.js:6:11:6:28 | p | prettier.js:7:28:7:28 | p | | prettier.js:6:11:6:28 | p | prettier.js:7:28:7:28 | p | @@ -10165,6 +10409,8 @@ edges | normalizedPaths.js:381:19:381:29 | slash(path) | normalizedPaths.js:377:14:377:27 | req.query.path | normalizedPaths.js:381:19:381:29 | slash(path) | This path depends on a $@. | normalizedPaths.js:377:14:377:27 | req.query.path | user-provided value | | normalizedPaths.js:388:19:388:22 | path | normalizedPaths.js:385:35:385:45 | req.query.x | normalizedPaths.js:388:19:388:22 | path | This path depends on a $@. | normalizedPaths.js:385:35:385:45 | req.query.x | user-provided value | | normalizedPaths.js:399:21:399:24 | path | normalizedPaths.js:385:35:385:45 | req.query.x | normalizedPaths.js:399:21:399:24 | path | This path depends on a $@. | normalizedPaths.js:385:35:385:45 | req.query.x | user-provided value | +| normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | normalizedPaths.js:407:45:407:55 | req.query.x | normalizedPaths.js:407:19:407:67 | pathMod ... t('/')) | This path depends on a $@. | normalizedPaths.js:407:45:407:55 | req.query.x | user-provided value | +| normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | normalizedPaths.js:408:38:408:48 | req.query.x | normalizedPaths.js:408:19:408:60 | pathMod ... t('/')) | This path depends on a $@. | normalizedPaths.js:408:38:408:48 | req.query.x | user-provided value | | other-fs-libraries.js:11:19:11:22 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:11:19:11:22 | path | This path depends on a $@. | other-fs-libraries.js:9:24:9:30 | req.url | user-provided value | | other-fs-libraries.js:12:27:12:30 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:12:27:12:30 | path | This path depends on a $@. | other-fs-libraries.js:9:24:9:30 | req.url | user-provided value | | other-fs-libraries.js:13:24:13:27 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:13:24:13:27 | path | This path depends on a $@. | other-fs-libraries.js:9:24:9:30 | req.url | user-provided value | @@ -10187,6 +10433,7 @@ edges | other-fs-libraries.js:70:19:70:22 | path | other-fs-libraries.js:68:24:68:30 | req.url | other-fs-libraries.js:70:19:70:22 | path | This path depends on a $@. | other-fs-libraries.js:68:24:68:30 | req.url | user-provided value | | other-fs-libraries.js:71:10:71:13 | path | other-fs-libraries.js:68:24:68:30 | req.url | other-fs-libraries.js:71:10:71:13 | path | This path depends on a $@. | other-fs-libraries.js:68:24:68:30 | req.url | user-provided value | | other-fs-libraries.js:72:15:72:18 | path | other-fs-libraries.js:68:24:68:30 | req.url | other-fs-libraries.js:72:15:72:18 | path | This path depends on a $@. | other-fs-libraries.js:68:24:68:30 | req.url | user-provided value | +| other-fs-libraries.js:79:16:79:19 | path | other-fs-libraries.js:77:24:77:30 | req.url | other-fs-libraries.js:79:16:79:19 | path | This path depends on a $@. | other-fs-libraries.js:77:24:77:30 | req.url | user-provided value | | prettier.js:7:28:7:28 | p | prettier.js:6:13:6:13 | p | prettier.js:7:28:7:28 | p | This path depends on a $@. | prettier.js:6:13:6:13 | p | user-provided value | | prettier.js:11:44:11:44 | p | prettier.js:6:13:6:13 | p | prettier.js:11:44:11:44 | p | This path depends on a $@. | prettier.js:6:13:6:13 | p | user-provided value | | pupeteer.js:9:28:9:34 | tainted | pupeteer.js:5:28:5:53 | parseTo ... t).name | pupeteer.js:9:28:9:34 | tainted | This path depends on a $@. | pupeteer.js:5:28:5:53 | parseTo ... t).name | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/fs.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/fs.js index fc6b4ab3581..721f301093c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/fs.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/fs.js @@ -45,4 +45,12 @@ var fs = {}; */ fs.readFileSync = function(filename, encoding) {}; +/** + * @param {string} filename + * @param {string} encoding + * @param {(function(NodeJS.ErrnoException, string): void)} callback + * @return {void} + */ +fs.readFile = function(filename, encoding, callback) {}; + module.exports = fs; diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js index f5caae46c45..a5453a728d3 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js @@ -32,17 +32,17 @@ app.get('/normalize-notAbsolute', (req, res) => { if (pathModule.isAbsolute(path)) return; - + fs.readFileSync(path); // NOT OK if (!path.startsWith(".")) fs.readFileSync(path); // OK else fs.readFileSync(path); // NOT OK - wrong polarity - + if (!path.startsWith("..")) fs.readFileSync(path); // OK - + if (!path.startsWith("../")) fs.readFileSync(path); // OK @@ -52,7 +52,7 @@ app.get('/normalize-notAbsolute', (req, res) => { app.get('/normalize-noInitialDotDot', (req, res) => { let path = pathModule.normalize(req.query.path); - + if (path.startsWith("..")) return; @@ -80,7 +80,7 @@ app.get('/prepend-normalize', (req, res) => { app.get('/absolute', (req, res) => { let path = req.query.path; - + if (!pathModule.isAbsolute(path)) return; @@ -92,10 +92,10 @@ app.get('/absolute', (req, res) => { app.get('/normalized-absolute', (req, res) => { let path = pathModule.normalize(req.query.path); - + if (!pathModule.isAbsolute(path)) return; - + res.write(fs.readFileSync(path)); // NOT OK if (path.startsWith('/home/user/www')) @@ -104,7 +104,7 @@ app.get('/normalized-absolute', (req, res) => { app.get('/combined-check', (req, res) => { let path = pathModule.normalize(req.query.path); - + // Combined absoluteness and folder check in one startsWith call if (path.startsWith("/home/user/www")) fs.readFileSync(path); // OK @@ -121,7 +121,7 @@ app.get('/realpath', (req, res) => { if (path.startsWith("/home/user/www")) fs.readFileSync(path); // OK - both absolute and normalized before check - + fs.readFileSync(pathModule.join('.', path)); // OK - normalized and coerced to relative fs.readFileSync(pathModule.join('/home/user/www', path)); // OK }); @@ -212,7 +212,7 @@ app.get('/join-regression', (req, res) => { app.get('/decode-after-normalization', (req, res) => { let path = pathModule.normalize(req.query.path); - + if (!pathModule.isAbsolute(path) && !path.startsWith('..')) fs.readFileSync(path); // OK @@ -238,7 +238,7 @@ app.get('/resolve-path', (req, res) => { fs.readFileSync(path); // NOT OK var self = something(); - + if (path.substring(0, self.dir.length) === self.dir) fs.readFileSync(path); // OK else @@ -256,12 +256,12 @@ app.get('/relative-startswith', (req, res) => { fs.readFileSync(path); // NOT OK var self = something(); - + var relative = pathModule.relative(self.webroot, path); if(relative.startsWith(".." + pathModule.sep) || relative == "..") { - fs.readFileSync(path); // NOT OK! + fs.readFileSync(path); // NOT OK! } else { - fs.readFileSync(path); // OK! + fs.readFileSync(path); // OK! } let newpath = pathModule.normalize(path); @@ -277,7 +277,7 @@ app.get('/relative-startswith', (req, res) => { if (relativePath.indexOf('../') === 0) { fs.readFileSync(newpath); // NOT OK! } else { - fs.readFileSync(newpath); // OK! + fs.readFileSync(newpath); // OK! } let newpath = pathModule.normalize(path); @@ -285,7 +285,7 @@ app.get('/relative-startswith', (req, res) => { if (pathModule.normalize(relativePath).indexOf('../') === 0) { fs.readFileSync(newpath); // NOT OK! } else { - fs.readFileSync(newpath); // OK! + fs.readFileSync(newpath); // OK! } let newpath = pathModule.normalize(path); @@ -293,7 +293,7 @@ app.get('/relative-startswith', (req, res) => { if (pathModule.normalize(relativePath).indexOf('../')) { fs.readFileSync(newpath); // OK! } else { - fs.readFileSync(newpath); // NOT OK! + fs.readFileSync(newpath); // NOT OK! } }); @@ -340,7 +340,7 @@ app.get('/yet-another-prefix', (req, res) => { fs.readFileSync(path); // NOT OK - var abs = pathModule.resolve(path); + var abs = pathModule.resolve(path); if (abs.indexOf(root) !== 0) { fs.readFileSync(path); // NOT OK @@ -402,3 +402,8 @@ app.get('/dotdot-regexp', (req, res) => { fs.readFileSync(path); // OK } }); + +app.get('/join-spread', (req, res) => { + fs.readFileSync(pathModule.join('foo', ...req.query.x.split('/'))); // NOT OK + fs.readFileSync(pathModule.join(...req.query.x.split('/'))); // NOT OK +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js index bda7051ba80..1a618105226 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js @@ -71,3 +71,10 @@ http.createServer(function(req, res) { mkdirp(path); // NOT OK mkdirp.sync(path); // NOT OK }); + +const fsp = require("fs/promises"); +http.createServer(function(req, res) { + var path = url.parse(req.url, true).query.path; + + fsp.readFile(path); // NOT OK +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-312/BuildArtifactLeak.expected b/javascript/ql/test/query-tests/Security/CWE-312/BuildArtifactLeak.expected index e7f3be74b04..8514ae58104 100644 --- a/javascript/ql/test/query-tests/Security/CWE-312/BuildArtifactLeak.expected +++ b/javascript/ql/test/query-tests/Security/CWE-312/BuildArtifactLeak.expected @@ -9,12 +9,15 @@ nodes | build-leaks.js:14:18:14:20 | env | | build-leaks.js:15:24:15:34 | process.env | | build-leaks.js:15:24:15:34 | process.env | +| build-leaks.js:15:24:15:39 | process.env[key] | | build-leaks.js:16:20:16:22 | env | | build-leaks.js:21:11:26:5 | stringifed | | build-leaks.js:21:24:26:5 | {\\n ... )\\n } | | build-leaks.js:22:24:25:14 | Object. ... }, {}) | | build-leaks.js:22:49:22:51 | env | +| build-leaks.js:23:24:23:47 | JSON.st ... w[key]) | | build-leaks.js:23:39:23:41 | raw | +| build-leaks.js:23:39:23:46 | raw[key] | | build-leaks.js:24:20:24:22 | env | | build-leaks.js:30:22:30:31 | stringifed | | build-leaks.js:34:26:34:57 | getEnv( ... ngified | @@ -36,13 +39,19 @@ edges | build-leaks.js:14:18:14:20 | env | build-leaks.js:16:20:16:22 | env | | build-leaks.js:15:24:15:34 | process.env | build-leaks.js:14:18:14:20 | env | | build-leaks.js:15:24:15:34 | process.env | build-leaks.js:14:18:14:20 | env | +| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:15:24:15:39 | process.env[key] | +| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:15:24:15:39 | process.env[key] | +| build-leaks.js:15:24:15:39 | process.env[key] | build-leaks.js:14:18:14:20 | env | | build-leaks.js:16:20:16:22 | env | build-leaks.js:13:17:19:10 | Object. ... }) | | build-leaks.js:16:20:16:22 | env | build-leaks.js:14:18:14:20 | env | | build-leaks.js:21:11:26:5 | stringifed | build-leaks.js:30:22:30:31 | stringifed | | build-leaks.js:21:24:26:5 | {\\n ... )\\n } | build-leaks.js:21:11:26:5 | stringifed | | build-leaks.js:22:24:25:14 | Object. ... }, {}) | build-leaks.js:21:24:26:5 | {\\n ... )\\n } | | build-leaks.js:22:49:22:51 | env | build-leaks.js:24:20:24:22 | env | +| build-leaks.js:23:24:23:47 | JSON.st ... w[key]) | build-leaks.js:22:49:22:51 | env | | build-leaks.js:23:39:23:41 | raw | build-leaks.js:22:49:22:51 | env | +| build-leaks.js:23:39:23:41 | raw | build-leaks.js:23:39:23:46 | raw[key] | +| build-leaks.js:23:39:23:46 | raw[key] | build-leaks.js:23:24:23:47 | JSON.st ... w[key]) | | build-leaks.js:24:20:24:22 | env | build-leaks.js:22:24:25:14 | Object. ... }, {}) | | build-leaks.js:24:20:24:22 | env | build-leaks.js:22:49:22:51 | env | | build-leaks.js:30:22:30:31 | stringifed | build-leaks.js:34:26:34:57 | getEnv( ... ngified | diff --git a/misc/suite-helpers/CHANGELOG.md b/misc/suite-helpers/CHANGELOG.md index f3a5d5d8a89..d1c4b5782a9 100644 --- a/misc/suite-helpers/CHANGELOG.md +++ b/misc/suite-helpers/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.1 + +No user-facing changes. + ## 0.6.0 No user-facing changes. diff --git a/misc/suite-helpers/change-notes/released/0.6.1.md b/misc/suite-helpers/change-notes/released/0.6.1.md new file mode 100644 index 00000000000..6008e49b8e7 --- /dev/null +++ b/misc/suite-helpers/change-notes/released/0.6.1.md @@ -0,0 +1,3 @@ +## 0.6.1 + +No user-facing changes. diff --git a/misc/suite-helpers/codeql-pack.release.yml b/misc/suite-helpers/codeql-pack.release.yml index a3f820f884d..80fb0899f64 100644 --- a/misc/suite-helpers/codeql-pack.release.yml +++ b/misc/suite-helpers/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.6.0 +lastReleaseVersion: 0.6.1 diff --git a/misc/suite-helpers/qlpack.yml b/misc/suite-helpers/qlpack.yml index 401b2ae7ef1..51344ba29b3 100644 --- a/misc/suite-helpers/qlpack.yml +++ b/misc/suite-helpers/qlpack.yml @@ -1,4 +1,4 @@ name: codeql/suite-helpers -version: 0.6.0 +version: 0.6.1 groups: shared warnOnImplicitThis: true diff --git a/python/ql/lib/CHANGELOG.md b/python/ql/lib/CHANGELOG.md index b94cc6f9162..6c74cee16c1 100644 --- a/python/ql/lib/CHANGELOG.md +++ b/python/ql/lib/CHANGELOG.md @@ -1,3 +1,20 @@ +## 0.10.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* Add support for Models as Data for Reflected XSS query +* Parameters with a default value are now considered a `DefinitionNode`. This improvement was motivated by allowing type-tracking and API graphs to follow flow from such a default value to a use by a captured variable. + ## 0.10.0 ### New Features diff --git a/python/ql/lib/change-notes/released/0.10.1.md b/python/ql/lib/change-notes/released/0.10.1.md new file mode 100644 index 00000000000..0987e609030 --- /dev/null +++ b/python/ql/lib/change-notes/released/0.10.1.md @@ -0,0 +1,16 @@ +## 0.10.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* Add support for Models as Data for Reflected XSS query +* Parameters with a default value are now considered a `DefinitionNode`. This improvement was motivated by allowing type-tracking and API graphs to follow flow from such a default value to a use by a captured variable. diff --git a/python/ql/lib/codeql-pack.release.yml b/python/ql/lib/codeql-pack.release.yml index b21db623245..af7510b3cd6 100644 --- a/python/ql/lib/codeql-pack.release.yml +++ b/python/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.10.0 +lastReleaseVersion: 0.10.1 diff --git a/python/ql/lib/qlpack.yml b/python/ql/lib/qlpack.yml index eb1e0ea9231..a259fb34fd2 100644 --- a/python/ql/lib/qlpack.yml +++ b/python/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/python-all -version: 0.10.0 +version: 0.10.1 groups: python dbscheme: semmlecode.python.dbscheme extractor: python diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index 21ef902f2e5..f3026d8faad 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -680,6 +680,9 @@ module Escaping { /** Gets the escape-kind for escaping a string so it can safely be included in HTML. */ string getHtmlKind() { result = "html" } + /** Gets the escape-kind for escaping a string so it can safely be included in XML. */ + string getXmlKind() { result = "xml" } + /** Gets the escape-kind for escaping a string so it can safely be included in a regular expression. */ string getRegexKind() { result = "regex" } @@ -710,6 +713,15 @@ class HtmlEscaping extends Escaping { HtmlEscaping() { super.getKind() = Escaping::getHtmlKind() } } +/** + * An escape of a string so it can be safely included in + * the body of an XML element, for example, replacing `&` and `<>` in + * `&xxe;`. + */ +class XmlEscaping extends Escaping { + XmlEscaping() { super.getKind() = Escaping::getXmlKind() } +} + /** * An escape of a string so it can be safely included in * the body of a regex. diff --git a/python/ql/lib/semmle/python/Flow.qll b/python/ql/lib/semmle/python/Flow.qll index 8dfdc4a6341..17a408c6713 100644 --- a/python/ql/lib/semmle/python/Flow.qll +++ b/python/ql/lib/semmle/python/Flow.qll @@ -640,12 +640,23 @@ class DefinitionNode extends ControlFlowNode { exists(Assign a | list_or_tuple_nested_element(a.getATarget()).getAFlowNode() = this) or exists(For for | for.getTarget().getAFlowNode() = this) + or + exists(Parameter param | this = param.asName().getAFlowNode() and exists(param.getDefault())) } /** flow node corresponding to the value assigned for the definition corresponding to this flow node */ ControlFlowNode getValue() { result = assigned_value(this.getNode()).getAFlowNode() and - (result.getBasicBlock().dominates(this.getBasicBlock()) or result.isImport()) + ( + result.getBasicBlock().dominates(this.getBasicBlock()) + or + result.isImport() + or + // since the default value for a parameter is evaluated in the same basic block as + // the function definition, but the parameter belongs to the basic block of the function, + // there is no dominance relationship between the two. + exists(Parameter param | this = param.asName().getAFlowNode()) + ) } } @@ -795,6 +806,8 @@ private AstNode assigned_value(Expr lhs) { or /* for lhs in seq: => `result` is the `for` node, representing the `iter(next(seq))` operation. */ result.(For).getTarget() = lhs + or + exists(Parameter param | lhs = param.asName() and result = param.getDefault()) } predicate nested_sequence_assign( diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll index f34554d6eea..03975c6a54a 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll @@ -46,6 +46,14 @@ signature module ConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or isAdditionalFlowStep(_, node) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -114,7 +122,7 @@ signature module StateConfigSig { * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state); + default predicate isBarrier(Node node, FlowState state) { none() } /** Holds if data flow into `node` is prohibited. */ default predicate isBarrierIn(Node node) { none() } @@ -131,7 +139,9 @@ signature module StateConfigSig { * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + default predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } /** * Holds if an arbitrary number of implicit read steps of content `c` may be @@ -139,6 +149,17 @@ signature module StateConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or + isAdditionalFlowStep(_, node) or + isAdditionalFlowStep(node, _, _, _) or + isAdditionalFlowStep(_, _, node, _) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll index 410543e0fc9..29561b0f0a6 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll @@ -66,6 +66,12 @@ signature module FullStateConfigSig { */ predicate allowImplicitRead(Node node, ContentSet c); + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + predicate neverSkip(Node node); + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -254,6 +260,11 @@ module Impl { not fullBarrier(node2) } + pragma[nomagic] + private predicate isUnreachableInCall1(NodeEx n, LocalCallContextSpecificCall cc) { + isUnreachableInCallCached(n.asNode(), cc.getCall()) + } + /** * Holds if data can flow in one local step from `node1` to `node2`. */ @@ -2019,7 +2030,8 @@ module Impl { castNode(this.asNode()) or clearsContentCached(this.asNode(), _) or expectsContentCached(this.asNode(), _) or - neverSkipInPathGraph(this.asNode()) + neverSkipInPathGraph(this.asNode()) or + Config::neverSkip(this.asNode()) } } @@ -2108,7 +2120,7 @@ module Impl { NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, LocalCallContext cc ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCall1(node2, cc) and ( localFlowEntry(node1, pragma[only_bind_into](state)) and ( @@ -2123,7 +2135,7 @@ module Impl { ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) + not isUnreachableInCall1(node1, cc) or exists(NodeEx mid | localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and @@ -2160,10 +2172,8 @@ module Impl { preservesValue = false and t = node2.getDataFlowType() and callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) + not isUnreachableInCall1(node1, callContext) and + not isUnreachableInCall1(node2, callContext) } } @@ -2703,7 +2713,7 @@ module Impl { ParamNodeEx getParamNode() { result = p } - override string toString() { result = p + ": " + ap } + override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn @@ -2755,12 +2765,21 @@ module Impl { ) } + private predicate forceUnfold(AccessPathApprox apa) { + forceHighPrecision(apa.getHead()) + or + exists(Content c2 | + apa = TConsCons(_, _, c2, _) and + forceHighPrecision(c2) + ) + } + /** * Holds with `unfold = false` if a precise head-tail representation of `apa` is * expected to be expensive. Holds with `unfold = true` otherwise. */ private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { - if forceHighPrecision(apa.getHead()) + if forceUnfold(apa) then unfold = true else exists(int aps, int nodes, int apLimit, int tupleLimit | @@ -3089,6 +3108,12 @@ module Impl { result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } + private string ppSummaryCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">" + } + /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() } @@ -3097,7 +3122,9 @@ module Impl { * representation of the call context. */ string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + result = + this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + + this.ppSummaryCtx() } /** diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl1.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl1.qll index be70086a93a..b0de9745816 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl1.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl1.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll index be70086a93a..b0de9745816 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll index be70086a93a..b0de9745816 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll index be70086a93a..b0de9745816 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll index 29504b6aa38..eaf65a4d503 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll @@ -304,8 +304,12 @@ module EssaFlow { // see `with_flow` in `python/ql/src/semmle/python/dataflow/Implementation.qll` with.getContextExpr() = contextManager.getNode() and with.getOptionalVars() = var.getNode() and - not with.isAsync() and contextManager.strictlyDominates(var) + // note: we allow this for both `with` and `async with`, since some + // implementations do `async def __aenter__(self): return self`, so you can do + // both: + // * `foo = x.foo(); await foo.async_method(); foo.close()` and + // * `async with x.foo() as foo: await foo.async_method()`. ) or // Async with var definition @@ -314,6 +318,12 @@ module EssaFlow { // nodeTo is `x`, essa var // // This makes the cfg node the local source of the awaited value. + // + // We have this step in addition to the step above, to handle cases where the QL + // modeling of `f(42)` requires a `.getAwaited()` step (in API graphs) when not + // using `async with`, so you can do both: + // * `foo = await x.foo(); await foo.async_method(); foo.close()` and + // * `async with x.foo() as foo: await foo.async_method()`. exists(With with, ControlFlowNode var | nodeFrom.(CfgNode).getNode() = var and nodeTo.(EssaNode).getVar().getDefinition().(WithDefinition).getDefiningNode() = var and @@ -463,6 +473,15 @@ predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) { or // Setting the possible values of the variable at the end of import time nodeFrom = nodeTo.(ModuleVariableNode).getADefiningWrite() + or + // a parameter with a default value, since the parameter will be in the scope of the + // function, while the default value itself will be in the scope that _defines_ the + // function. + exists(ParameterDefinition param | + // note: we go to the _control-flow node_ of the parameter, and not the ESSA node of the parameter, since for type-tracking, the ESSA node is not a LocalSourceNode, so we would get in trouble. + nodeFrom.asCfgNode() = param.getDefault() and + nodeTo.asCfgNode() = param.getDefiningNode() + ) } /** @@ -564,9 +583,6 @@ predicate jumpStepSharedWithTypeTracker(Node nodeFrom, Node nodeTo) { r.getAttributeName(), nodeFrom) and nodeTo = r ) - or - // Default value for parameter flows to that parameter - defaultValueFlowStep(nodeFrom, nodeTo) } /** @@ -787,19 +803,6 @@ predicate attributeStoreStep(Node nodeFrom, AttributeContent c, PostUpdateNode n ) } -predicate defaultValueFlowStep(CfgNode nodeFrom, CfgNode nodeTo) { - exists(Function f, Parameter p, ParameterDefinition def | - // `getArgByName` supports, unlike `getAnArg`, keyword-only parameters - p = f.getArgByName(_) and - nodeFrom.asExpr() = p.getDefault() and - // The following expresses - // nodeTo.(ParameterNode).getParameter() = p - // without non-monotonic recursion - def.getParameter() = p and - nodeTo.getNode() = def.getDefiningNode() - ) -} - /** * Holds if data can flow from `nodeFrom` to `nodeTo` via a read of content `c`. */ diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll index fb3d7bf828f..001375b4dc5 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll @@ -55,10 +55,9 @@ private module Cached { ) } - pragma[nomagic] - private TypeTracker noContentTypeTracker(boolean hasCall) { - result = MkTypeTracker(hasCall, noContent()) - } + /** Gets a type tracker with no content and the call bit set to the given value. */ + cached + TypeTracker noContentTypeTracker(boolean hasCall) { result = MkTypeTracker(hasCall, noContent()) } /** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */ cached @@ -318,6 +317,8 @@ class StepSummary extends TStepSummary { /** Provides predicates for updating step summaries (`StepSummary`s). */ module StepSummary { + predicate append = Cached::append/2; + /** * Gets the summary that corresponds to having taken a forwards * inter-procedural step from `nodeFrom` to `nodeTo`. @@ -378,6 +379,35 @@ module StepSummary { } deprecated predicate localSourceStoreStep = flowsToStoreStep/3; + + /** Gets the step summary for a level step. */ + StepSummary levelStep() { result = LevelStep() } + + /** Gets the step summary for a call step. */ + StepSummary callStep() { result = CallStep() } + + /** Gets the step summary for a return step. */ + StepSummary returnStep() { result = ReturnStep() } + + /** Gets the step summary for storing into `content`. */ + StepSummary storeStep(TypeTrackerContent content) { result = StoreStep(content) } + + /** Gets the step summary for loading from `content`. */ + StepSummary loadStep(TypeTrackerContent content) { result = LoadStep(content) } + + /** Gets the step summary for loading from `load` and then storing into `store`. */ + StepSummary loadStoreStep(TypeTrackerContent load, TypeTrackerContent store) { + result = LoadStoreStep(load, store) + } + + /** Gets the step summary for a step that only permits contents matched by `filter`. */ + StepSummary withContent(ContentFilter filter) { result = WithContent(filter) } + + /** Gets the step summary for a step that blocks contents matched by `filter`. */ + StepSummary withoutContent(ContentFilter filter) { result = WithoutContent(filter) } + + /** Gets the step summary for a jump step. */ + StepSummary jumpStep() { result = JumpStep() } } /** @@ -540,6 +570,13 @@ module TypeTracker { * Gets a valid end point of type tracking. */ TypeTracker end() { result.end() } + + /** + * INTERNAL USE ONLY. + * + * Gets a valid end point of type tracking with the call bit set to the given value. + */ + predicate end = Cached::noContentTypeTracker/1; } pragma[nomagic] diff --git a/python/ql/lib/semmle/python/essa/SsaDefinitions.qll b/python/ql/lib/semmle/python/essa/SsaDefinitions.qll index 61007c7129b..8d9de611659 100644 --- a/python/ql/lib/semmle/python/essa/SsaDefinitions.qll +++ b/python/ql/lib/semmle/python/essa/SsaDefinitions.qll @@ -20,7 +20,12 @@ module SsaSource { /** Holds if `v` is defined by assignment at `defn` and given `value`. */ cached predicate assignment_definition(Variable v, ControlFlowNode defn, ControlFlowNode value) { - defn.(NameNode).defines(v) and defn.(DefinitionNode).getValue() = value + defn.(NameNode).defines(v) and + defn.(DefinitionNode).getValue() = value and + // since parameter will be considered a DefinitionNode, if it has a default value, + // we need to exclude it here since it is already covered by parameter_definition + // (and points-to was unhappy that it was included in both) + not parameter_definition(v, defn) } /** Holds if `v` is defined by assignment of the captured exception. */ diff --git a/python/ql/lib/semmle/python/frameworks/MarkupSafe.qll b/python/ql/lib/semmle/python/frameworks/MarkupSafe.qll index e77b92a40dc..99cfd75bec6 100644 --- a/python/ql/lib/semmle/python/frameworks/MarkupSafe.qll +++ b/python/ql/lib/semmle/python/frameworks/MarkupSafe.qll @@ -83,7 +83,7 @@ private module MarkupSafeModel { } /** Taint propagation for `markupsafe.Markup`. */ - private class AddtionalTaintStep extends TaintTracking::AdditionalTaintStep { + private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { nodeTo.(ClassInstantiation).getArg(0) = nodeFrom } @@ -92,11 +92,7 @@ private module MarkupSafeModel { /** Any escaping performed via the `markupsafe` package. */ abstract private class MarkupSafeEscape extends Escaping::Range { - override string getKind() { - // TODO: this package claims to escape for both HTML and XML, but for now we don't - // model XML. - result = Escaping::getHtmlKind() - } + override string getKind() { result in [Escaping::getHtmlKind(), Escaping::getXmlKind()] } } /** A call to any of the escaping functions in `markupsafe` */ diff --git a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll index 2229d0c758c..3e6f74c84cd 100644 --- a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll @@ -7,6 +7,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts +private import semmle.python.frameworks.data.ModelsAsData private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.BarrierGuards @@ -43,6 +44,15 @@ module ReflectedXss { */ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { } + /** + * A data flow sink for "reflected cross-site scripting" vulnerabilities. + */ + private class SinkFromModel extends Sink { + SinkFromModel() { + this = ModelOutput::getASinkNode(["html-injection", "js-injection"]).asSink() + } + } + /** * The body of a HTTP response that will be returned from a server, considered as a flow sink. */ diff --git a/python/ql/lib/semmle/python/security/dataflow/XxeCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/XxeCustomizations.qll index 525bceeb04a..c98e8153be9 100644 --- a/python/ql/lib/semmle/python/security/dataflow/XxeCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/XxeCustomizations.qll @@ -44,4 +44,11 @@ module Xxe { ) } } + + /** + * An XML escaping, considered as a sanitizer. + */ + class XmlEscapingAsSanitizer extends Sanitizer { + XmlEscapingAsSanitizer() { this = any(XmlEscaping esc).getOutput() } + } } diff --git a/python/ql/src/CHANGELOG.md b/python/ql/src/CHANGELOG.md index d97cca6084c..0d2fc2b6968 100644 --- a/python/ql/src/CHANGELOG.md +++ b/python/ql/src/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.8.1 + +### Minor Analysis Improvements + +* Fixed modeling of `aiohttp.ClientSession` so we properly handle `async with` uses. This can impact results of server-side request forgery queries (`py/full-ssrf`, `py/partial-ssrf`). + ## 0.8.0 ### Bug Fixes diff --git a/python/ql/src/change-notes/released/0.8.1.md b/python/ql/src/change-notes/released/0.8.1.md new file mode 100644 index 00000000000..1adf44eee7c --- /dev/null +++ b/python/ql/src/change-notes/released/0.8.1.md @@ -0,0 +1,5 @@ +## 0.8.1 + +### Minor Analysis Improvements + +* Fixed modeling of `aiohttp.ClientSession` so we properly handle `async with` uses. This can impact results of server-side request forgery queries (`py/full-ssrf`, `py/partial-ssrf`). diff --git a/python/ql/src/codeql-pack.release.yml b/python/ql/src/codeql-pack.release.yml index 37eab3197dc..2f693f95ba6 100644 --- a/python/ql/src/codeql-pack.release.yml +++ b/python/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.8.0 +lastReleaseVersion: 0.8.1 diff --git a/python/ql/src/qlpack.yml b/python/ql/src/qlpack.yml index 77308df34ba..f98131de0da 100644 --- a/python/ql/src/qlpack.yml +++ b/python/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/python-queries -version: 0.8.0 +version: 0.8.1 groups: - python - queries diff --git a/python/ql/test/experimental/dataflow/TestUtil/FlowTest.qll b/python/ql/test/experimental/dataflow/TestUtil/FlowTest.qll index e6abf741b36..f2068ebe723 100644 --- a/python/ql/test/experimental/dataflow/TestUtil/FlowTest.qll +++ b/python/ql/test/experimental/dataflow/TestUtil/FlowTest.qll @@ -9,7 +9,7 @@ signature module FlowTestSig { predicate relevantFlow(DataFlow::Node fromNode, DataFlow::Node toNode); } -private module FlowTest implements TestSig { +module MakeTestSig implements TestSig { string getARelevantTag() { result = Impl::flowTag() } predicate hasActualResult(Location location, string element, string tag, string value) { @@ -37,11 +37,3 @@ private module FlowTest implements TestSig { ) } } - -module MakeFlowTest { - import MakeTest> -} - -module MakeFlowTest2 { - import MakeTest, FlowTest>> -} diff --git a/python/ql/test/experimental/dataflow/TestUtil/LocalFlowStepTest.qll b/python/ql/test/experimental/dataflow/TestUtil/LocalFlowStepTest.qll index 6cbfe917fd4..8fe65e53153 100644 --- a/python/ql/test/experimental/dataflow/TestUtil/LocalFlowStepTest.qll +++ b/python/ql/test/experimental/dataflow/TestUtil/LocalFlowStepTest.qll @@ -10,4 +10,4 @@ module LocalFlowStepTest implements FlowTestSig { } } -import MakeFlowTest +import MakeTest> diff --git a/python/ql/test/experimental/dataflow/TestUtil/MaximalFlowTest.qll b/python/ql/test/experimental/dataflow/TestUtil/MaximalFlowTest.qll index 681e51ca604..7587584a269 100644 --- a/python/ql/test/experimental/dataflow/TestUtil/MaximalFlowTest.qll +++ b/python/ql/test/experimental/dataflow/TestUtil/MaximalFlowTest.qll @@ -12,7 +12,7 @@ module MaximalFlowTest implements FlowTestSig { } } -import MakeFlowTest +import MakeTest> /** * A configuration to find all "maximal" flows. diff --git a/python/ql/test/experimental/dataflow/TestUtil/NormalDataflowTest.qll b/python/ql/test/experimental/dataflow/TestUtil/NormalDataflowTest.qll index a327886fedd..d62262cec98 100644 --- a/python/ql/test/experimental/dataflow/TestUtil/NormalDataflowTest.qll +++ b/python/ql/test/experimental/dataflow/TestUtil/NormalDataflowTest.qll @@ -11,7 +11,7 @@ module DataFlowTest implements FlowTestSig { } } -import MakeFlowTest +import MakeTest> query predicate missingAnnotationOnSink(Location location, string error, string element) { error = "ERROR, you should add `# $ MISSING: flow` annotation" and diff --git a/python/ql/test/experimental/dataflow/TestUtil/NormalTaintTrackingTest.qll b/python/ql/test/experimental/dataflow/TestUtil/NormalTaintTrackingTest.qll index 4a07dc4d2d6..23262dfb3e5 100644 --- a/python/ql/test/experimental/dataflow/TestUtil/NormalTaintTrackingTest.qll +++ b/python/ql/test/experimental/dataflow/TestUtil/NormalTaintTrackingTest.qll @@ -11,7 +11,7 @@ module DataFlowTest implements FlowTestSig { } } -import MakeFlowTest +import MakeTest> query predicate missingAnnotationOnSink(Location location, string error, string element) { error = "ERROR, you should add `# $ MISSING: flow` annotation" and diff --git a/python/ql/test/experimental/dataflow/TestUtil/RoutingTest.qll b/python/ql/test/experimental/dataflow/TestUtil/RoutingTest.qll index 36b603baa78..6c2df0e4348 100644 --- a/python/ql/test/experimental/dataflow/TestUtil/RoutingTest.qll +++ b/python/ql/test/experimental/dataflow/TestUtil/RoutingTest.qll @@ -10,22 +10,25 @@ private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPr * the functions tested sink their arguments sequentially, that is * `SINK1(arg1)`, etc. */ -abstract class RoutingTest extends InlineExpectationsTest { - bindingset[this] - RoutingTest() { any() } +signature module RoutingTestSig { + class Argument; - abstract string flowTag(); + string flowTag(Argument arg); - abstract predicate relevantFlow(DataFlow::Node fromNode, DataFlow::Node toNode); + predicate relevantFlow(DataFlow::Node fromNode, DataFlow::Node toNode, Argument arg); +} - override string getARelevantTag() { result in ["func", this.flowTag()] } +module MakeTestSig implements TestSig { + string getARelevantTag() { result in ["func", Impl::flowTag(_)] } - override predicate hasActualResult(Location location, string element, string tag, string value) { - exists(DataFlow::Node fromNode, DataFlow::Node toNode | this.relevantFlow(fromNode, toNode) | + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(DataFlow::Node fromNode, DataFlow::Node toNode, Impl::Argument arg | + Impl::relevantFlow(fromNode, toNode, arg) + | location = fromNode.getLocation() and element = fromNode.toString() and ( - tag = this.flowTag() and + tag = Impl::flowTag(arg) and if "\"" + tag + "\"" = fromValue(fromNode) then value = "" else value = fromValue(fromNode) or // only have result for `func` tag if the function where `arg` is used, is diff --git a/python/ql/test/experimental/dataflow/coverage-py2/argumentRoutingTest.expected b/python/ql/test/experimental/dataflow/coverage-py2/argumentRoutingTest.expected index e69de29bb2d..48de9172b36 100644 --- a/python/ql/test/experimental/dataflow/coverage-py2/argumentRoutingTest.expected +++ b/python/ql/test/experimental/dataflow/coverage-py2/argumentRoutingTest.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/python/ql/test/experimental/dataflow/coverage-py3/argumentRoutingTest.expected b/python/ql/test/experimental/dataflow/coverage-py3/argumentRoutingTest.expected index e69de29bb2d..48de9172b36 100644 --- a/python/ql/test/experimental/dataflow/coverage-py3/argumentRoutingTest.expected +++ b/python/ql/test/experimental/dataflow/coverage-py3/argumentRoutingTest.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected b/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected index e69de29bb2d..48de9172b36 100644 --- a/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected +++ b/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.ql b/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.ql index 2adbd635090..eccbbea2b4d 100644 --- a/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.ql +++ b/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.ql @@ -3,19 +3,22 @@ import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate import experimental.dataflow.TestUtil.RoutingTest -class Argument1RoutingTest extends RoutingTest { - Argument1RoutingTest() { this = "Argument1RoutingTest" } +module Argument1RoutingTest implements RoutingTestSig { + class Argument = Unit; - override string flowTag() { result = "arg1" } + string flowTag(Argument arg) { result = "arg1" and exists(arg) } - override predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink) { - exists(Argument1ExtraRoutingConfig cfg | cfg.hasFlow(source, sink)) - or - exists(ArgumentRoutingConfig cfg | - cfg.hasFlow(source, sink) and - cfg.isArgSource(source, 1) and - cfg.isGoodSink(sink, 1) - ) + predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink, Argument arg) { + ( + exists(Argument1ExtraRoutingConfig cfg | cfg.hasFlow(source, sink)) + or + exists(ArgumentRoutingConfig cfg | + cfg.hasFlow(source, sink) and + cfg.isArgSource(source, 1) and + cfg.isGoodSink(sink, 1) + ) + ) and + exists(arg) } } @@ -87,59 +90,54 @@ class Argument1ExtraRoutingConfig extends DataFlow::Configuration { override predicate isBarrierIn(DataFlow::Node node) { this.isSource(node) } } -class RestArgumentRoutingTest extends RoutingTest { - ArgNumber argNumber; +module RestArgumentRoutingTest implements RoutingTestSig { + class Argument = ArgNumber; - RestArgumentRoutingTest() { - argNumber > 1 and - this = "Argument" + argNumber + "RoutingTest" - } + string flowTag(Argument arg) { result = "arg" + arg } - override string flowTag() { result = "arg" + argNumber } - - override predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink) { + predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink, Argument arg) { exists(ArgumentRoutingConfig cfg | cfg.hasFlow(source, sink) and - cfg.isArgSource(source, argNumber) and - cfg.isGoodSink(sink, argNumber) - ) + cfg.isArgSource(source, arg) and + cfg.isGoodSink(sink, arg) + ) and + arg > 1 } } /** Bad flow from `arg` to `SINK_F` */ -class BadArgumentRoutingTestSinkF extends RoutingTest { - ArgNumber argNumber; +module BadArgumentRoutingTestSinkF implements RoutingTestSig { + class Argument = ArgNumber; - BadArgumentRoutingTestSinkF() { this = "BadArgumentRoutingTestSinkF" + argNumber } + string flowTag(Argument arg) { result = "bad" + arg } - override string flowTag() { result = "bad" + argNumber } - - override predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink) { + predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink, Argument arg) { exists(ArgumentRoutingConfig cfg | cfg.hasFlow(source, sink) and - cfg.isArgSource(source, argNumber) and - cfg.isBadSink(sink, argNumber) + cfg.isArgSource(source, arg) and + cfg.isBadSink(sink, arg) ) } } /** Bad flow from `arg` to `SINK` or `SINK_F`, where `n != m`. */ -class BadArgumentRoutingTestWrongSink extends RoutingTest { - ArgNumber argNumber; +module BadArgumentRoutingTestWrongSink implements RoutingTestSig { + class Argument = ArgNumber; - BadArgumentRoutingTestWrongSink() { this = "BadArgumentRoutingTestWrongSink" + argNumber } + string flowTag(Argument arg) { result = "bad" + arg } - override string flowTag() { result = "bad" + argNumber } - - override predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink) { + predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink, Argument arg) { exists(ArgumentRoutingConfig cfg | cfg.hasFlow(source, sink) and - cfg.isArgSource(source, any(ArgNumber i | not i = argNumber)) and + cfg.isArgSource(source, any(ArgNumber i | not i = arg)) and ( - cfg.isGoodSink(sink, argNumber) + cfg.isGoodSink(sink, arg) or - cfg.isBadSink(sink, argNumber) + cfg.isBadSink(sink, arg) ) ) } } + +import MakeTest, MakeTestSig, + MakeTestSig, MakeTestSig>> diff --git a/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql b/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql index 8ef3860955d..179ab2e2d88 100644 --- a/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql +++ b/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql @@ -31,4 +31,4 @@ module RuntimeLocalFlowTest implements FlowTestSig { } } -import MakeFlowTest2 +import MakeTest, MakeTestSig>> diff --git a/python/ql/test/experimental/dataflow/typetracking-summaries/tracked.expected b/python/ql/test/experimental/dataflow/typetracking-summaries/tracked.expected index e69de29bb2d..48de9172b36 100644 --- a/python/ql/test/experimental/dataflow/typetracking-summaries/tracked.expected +++ b/python/ql/test/experimental/dataflow/typetracking-summaries/tracked.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/python/ql/test/experimental/dataflow/typetracking-summaries/tracked.ql b/python/ql/test/experimental/dataflow/typetracking-summaries/tracked.ql index e5bf62053a0..6ce1afa8d60 100644 --- a/python/ql/test/experimental/dataflow/typetracking-summaries/tracked.ql +++ b/python/ql/test/experimental/dataflow/typetracking-summaries/tracked.ql @@ -15,12 +15,10 @@ private DataFlow::TypeTrackingNode tracked(TypeTracker t) { exists(TypeTracker t2 | result = tracked(t2).track(t2, t)) } -class TrackedTest extends InlineExpectationsTest { - TrackedTest() { this = "TrackedTest" } +module TrackedTest implements TestSig { + string getARelevantTag() { result = "tracked" } - override string getARelevantTag() { result = "tracked" } - - override predicate hasActualResult(Location location, string element, string tag, string value) { + predicate hasActualResult(Location location, string element, string tag, string value) { exists(DataFlow::Node e, TypeTracker t | exists(e.getLocation().getFile().getRelativePath()) and e.getLocation().getStartLine() > 0 and @@ -34,3 +32,5 @@ class TrackedTest extends InlineExpectationsTest { ) } } + +import MakeTest diff --git a/python/ql/test/experimental/dataflow/typetracking/test.py b/python/ql/test/experimental/dataflow/typetracking/test.py index f0e93d79af2..a822ef683eb 100644 --- a/python/ql/test/experimental/dataflow/typetracking/test.py +++ b/python/ql/test/experimental/dataflow/typetracking/test.py @@ -65,6 +65,19 @@ def to_inner_scope(): also_x = foo() # $ tracked print(also_x) # $ tracked + +def from_parameter_default(): + x_alias = tracked # $tracked + def outer(x=tracked): # $tracked + print(x) # $tracked + def inner(): + print(x) # $ tracked + print(x_alias) # $tracked + return x # $tracked + also_x = outer() # $tracked + print(also_x) # $tracked + + # ------------------------------------------------------------------------------ # Function decorator # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/CallGraph/InlineCallGraphTest.ql b/python/ql/test/experimental/library-tests/CallGraph/InlineCallGraphTest.ql index 42a7d90af5a..1f2b37168b7 100644 --- a/python/ql/test/experimental/library-tests/CallGraph/InlineCallGraphTest.ql +++ b/python/ql/test/experimental/library-tests/CallGraph/InlineCallGraphTest.ql @@ -108,7 +108,7 @@ query predicate pointsTo_found_typeTracker_notFound(CallNode call, string qualna not typeTrackerCallEdge(call, target) and qualname = getCallEdgeValue(call, target) and // ignore SPURIOUS call edges - not exists(FalsePositiveExpectation spuriousResult | + not exists(FalsePositiveTestExpectation spuriousResult | spuriousResult.getTag() = "pt" and spuriousResult.getValue() = getCallEdgeValue(call, target) and spuriousResult.getLocation().getFile() = call.getLocation().getFile() and @@ -127,7 +127,7 @@ query predicate typeTracker_found_pointsTo_notFound(CallNode call, string qualna // between the two). not typeTrackerClassCall(call, target) and // ignore SPURIOUS call edges - not exists(FalsePositiveExpectation spuriousResult | + not exists(FalsePositiveTestExpectation spuriousResult | spuriousResult.getTag() = "tt" and spuriousResult.getValue() = getCallEdgeValue(call, target) and spuriousResult.getLocation().getFile() = call.getLocation().getFile() and diff --git a/python/ql/test/experimental/meta/ConceptsTest.qll b/python/ql/test/experimental/meta/ConceptsTest.qll index 48803e11fb4..13dac722c3b 100644 --- a/python/ql/test/experimental/meta/ConceptsTest.qll +++ b/python/ql/test/experimental/meta/ConceptsTest.qll @@ -255,52 +255,64 @@ module HttpServerRequestHandlerTest implements TestSig { } } -class HttpServerHttpResponseTest extends InlineExpectationsTest { - File file; +abstract class DedicatedResponseTest extends string { + bindingset[this] + DedicatedResponseTest() { any() } - HttpServerHttpResponseTest() { - file.getExtension() = "py" and - this = "HttpServerHttpResponseTest: " + file - } + string toString() { result = this } - override string getARelevantTag() { result in ["HttpResponse", "responseBody", "mimetype"] } + abstract predicate isDedicatedFile(File file); +} - override predicate hasActualResult(Location location, string element, string tag, string value) { +module HttpServerHttpResponseTest implements TestSig { + string getARelevantTag() { result in ["HttpResponse", "responseBody", "mimetype"] } + + predicate hasActualResult(Location location, string element, string tag, string value) { // By adding `file` as a class field, and these two restrictions, it's possible to // say that we only want to check _some_ tags for certain files. This helped make // flask tests more readable since adding full annotations for HttpResponses in the // the tests for routing setup is both annoying and not very useful. - location.getFile() = file and - exists(file.getRelativePath()) and - // we need to do this step since we expect subclasses could override getARelevantTag - tag = this.getARelevantTag() and - ( - exists(Http::Server::HttpResponse response | - location = response.getLocation() and - element = response.toString() and - value = "" and - tag = "HttpResponse" - ) - or - exists(Http::Server::HttpResponse response | - location = response.getLocation() and - element = response.toString() and - value = prettyNodeForInlineTest(response.getBody()) and - tag = "responseBody" - ) - or - exists(Http::Server::HttpResponse response | - location = response.getLocation() and - element = response.toString() and - // Ensure that an expectation value such as "mimetype=text/html; charset=utf-8" is parsed as a - // single expectation with tag mimetype, and not as two expectations with tags mimetype and - // charset. + exists(File file | + location.getFile() = file and + file.getExtension() = "py" and + exists(file.getRelativePath()) and + // we need to do this step since we expect subclasses could override getARelevantTag + tag = getARelevantTag() and + ( + exists(Http::Server::HttpResponse response | + location = response.getLocation() and + element = response.toString() and + value = "" and + tag = "HttpResponse" + ) + or ( - if exists(response.getMimetype().indexOf(" ")) - then value = "\"" + response.getMimetype() + "\"" - else value = response.getMimetype() + not exists(DedicatedResponseTest d) + or + exists(DedicatedResponseTest d | d.isDedicatedFile(file)) ) and - tag = "mimetype" + ( + exists(Http::Server::HttpResponse response | + location = response.getLocation() and + element = response.toString() and + value = prettyNodeForInlineTest(response.getBody()) and + tag = "responseBody" + ) + or + exists(Http::Server::HttpResponse response | + location = response.getLocation() and + element = response.toString() and + // Ensure that an expectation value such as "mimetype=text/html; charset=utf-8" is parsed as a + // single expectation with tag mimetype, and not as two expectations with tags mimetype and + // charset. + ( + if exists(response.getMimetype().indexOf(" ")) + then value = "\"" + response.getMimetype() + "\"" + else value = response.getMimetype() + ) and + tag = "mimetype" + ) + ) ) ) } @@ -545,7 +557,7 @@ import MakeTest, MergeTests5, - MergeTests4, MergeTests5, diff --git a/python/ql/test/experimental/query-tests/Security/CWE-327-UnsafeUsageOfClientSideEncryptionVersion/UnsafeUsageOfClientSideEncryptionVersion.expected b/python/ql/test/experimental/query-tests/Security/CWE-327-UnsafeUsageOfClientSideEncryptionVersion/UnsafeUsageOfClientSideEncryptionVersion.expected index 4656ccd9236..c4be08ae476 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-327-UnsafeUsageOfClientSideEncryptionVersion/UnsafeUsageOfClientSideEncryptionVersion.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-327-UnsafeUsageOfClientSideEncryptionVersion/UnsafeUsageOfClientSideEncryptionVersion.expected @@ -3,23 +3,30 @@ edges | test.py:3:1:3:3 | GSSA Variable BSC | test.py:35:19:35:21 | ControlFlowNode for BSC | | test.py:3:1:3:3 | GSSA Variable BSC | test.py:66:19:66:21 | ControlFlowNode for BSC | | test.py:3:7:3:51 | ControlFlowNode for Attribute() | test.py:3:1:3:3 | GSSA Variable BSC | -| test.py:7:19:7:21 | ControlFlowNode for BSC | test.py:8:5:8:15 | ControlFlowNode for blob_client | +| test.py:7:19:7:21 | ControlFlowNode for BSC | test.py:7:19:7:42 | ControlFlowNode for Attribute() | +| test.py:7:19:7:42 | ControlFlowNode for Attribute() | test.py:8:5:8:15 | ControlFlowNode for blob_client | | test.py:8:5:8:15 | ControlFlowNode for blob_client | test.py:9:5:9:15 | ControlFlowNode for blob_client | | test.py:9:5:9:15 | ControlFlowNode for blob_client | test.py:9:5:9:15 | [post] ControlFlowNode for blob_client | | test.py:9:5:9:15 | [post] ControlFlowNode for blob_client | test.py:11:9:11:19 | ControlFlowNode for blob_client | | test.py:15:27:15:71 | ControlFlowNode for Attribute() | test.py:16:5:16:23 | ControlFlowNode for blob_service_client | | test.py:16:5:16:23 | ControlFlowNode for blob_service_client | test.py:17:5:17:23 | ControlFlowNode for blob_service_client | | test.py:17:5:17:23 | ControlFlowNode for blob_service_client | test.py:17:5:17:23 | [post] ControlFlowNode for blob_service_client | -| test.py:17:5:17:23 | [post] ControlFlowNode for blob_service_client | test.py:21:9:21:19 | ControlFlowNode for blob_client | +| test.py:17:5:17:23 | [post] ControlFlowNode for blob_service_client | test.py:19:19:19:37 | ControlFlowNode for blob_service_client | +| test.py:19:19:19:37 | ControlFlowNode for blob_service_client | test.py:19:19:19:58 | ControlFlowNode for Attribute() | +| test.py:19:19:19:58 | ControlFlowNode for Attribute() | test.py:21:9:21:19 | ControlFlowNode for blob_client | | test.py:25:24:25:66 | ControlFlowNode for Attribute() | test.py:26:5:26:20 | ControlFlowNode for container_client | | test.py:26:5:26:20 | ControlFlowNode for container_client | test.py:27:5:27:20 | ControlFlowNode for container_client | | test.py:27:5:27:20 | ControlFlowNode for container_client | test.py:27:5:27:20 | [post] ControlFlowNode for container_client | -| test.py:27:5:27:20 | [post] ControlFlowNode for container_client | test.py:31:9:31:19 | ControlFlowNode for blob_client | -| test.py:35:19:35:21 | ControlFlowNode for BSC | test.py:36:5:36:15 | ControlFlowNode for blob_client | +| test.py:27:5:27:20 | [post] ControlFlowNode for container_client | test.py:29:19:29:34 | ControlFlowNode for container_client | +| test.py:29:19:29:34 | ControlFlowNode for container_client | test.py:29:19:29:55 | ControlFlowNode for Attribute() | +| test.py:29:19:29:55 | ControlFlowNode for Attribute() | test.py:31:9:31:19 | ControlFlowNode for blob_client | +| test.py:35:19:35:21 | ControlFlowNode for BSC | test.py:35:19:35:42 | ControlFlowNode for Attribute() | +| test.py:35:19:35:42 | ControlFlowNode for Attribute() | test.py:36:5:36:15 | ControlFlowNode for blob_client | | test.py:36:5:36:15 | ControlFlowNode for blob_client | test.py:37:5:37:15 | ControlFlowNode for blob_client | | test.py:37:5:37:15 | ControlFlowNode for blob_client | test.py:37:5:37:15 | [post] ControlFlowNode for blob_client | | test.py:37:5:37:15 | [post] ControlFlowNode for blob_client | test.py:43:9:43:19 | ControlFlowNode for blob_client | -| test.py:66:19:66:21 | ControlFlowNode for BSC | test.py:67:5:67:15 | ControlFlowNode for blob_client | +| test.py:66:19:66:21 | ControlFlowNode for BSC | test.py:66:19:66:42 | ControlFlowNode for Attribute() | +| test.py:66:19:66:42 | ControlFlowNode for Attribute() | test.py:67:5:67:15 | ControlFlowNode for blob_client | | test.py:67:5:67:15 | ControlFlowNode for blob_client | test.py:68:5:68:15 | ControlFlowNode for blob_client | | test.py:68:5:68:15 | ControlFlowNode for blob_client | test.py:68:5:68:15 | [post] ControlFlowNode for blob_client | | test.py:68:5:68:15 | [post] ControlFlowNode for blob_client | test.py:69:12:69:22 | ControlFlowNode for blob_client | @@ -29,6 +36,7 @@ nodes | test.py:3:1:3:3 | GSSA Variable BSC | semmle.label | GSSA Variable BSC | | test.py:3:7:3:51 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:7:19:7:21 | ControlFlowNode for BSC | semmle.label | ControlFlowNode for BSC | +| test.py:7:19:7:42 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:8:5:8:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:9:5:9:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:9:5:9:15 | [post] ControlFlowNode for blob_client | semmle.label | [post] ControlFlowNode for blob_client | @@ -37,18 +45,24 @@ nodes | test.py:16:5:16:23 | ControlFlowNode for blob_service_client | semmle.label | ControlFlowNode for blob_service_client | | test.py:17:5:17:23 | ControlFlowNode for blob_service_client | semmle.label | ControlFlowNode for blob_service_client | | test.py:17:5:17:23 | [post] ControlFlowNode for blob_service_client | semmle.label | [post] ControlFlowNode for blob_service_client | +| test.py:19:19:19:37 | ControlFlowNode for blob_service_client | semmle.label | ControlFlowNode for blob_service_client | +| test.py:19:19:19:58 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:21:9:21:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:25:24:25:66 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:26:5:26:20 | ControlFlowNode for container_client | semmle.label | ControlFlowNode for container_client | | test.py:27:5:27:20 | ControlFlowNode for container_client | semmle.label | ControlFlowNode for container_client | | test.py:27:5:27:20 | [post] ControlFlowNode for container_client | semmle.label | [post] ControlFlowNode for container_client | +| test.py:29:19:29:34 | ControlFlowNode for container_client | semmle.label | ControlFlowNode for container_client | +| test.py:29:19:29:55 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:31:9:31:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:35:19:35:21 | ControlFlowNode for BSC | semmle.label | ControlFlowNode for BSC | +| test.py:35:19:35:42 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:36:5:36:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:37:5:37:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:37:5:37:15 | [post] ControlFlowNode for blob_client | semmle.label | [post] ControlFlowNode for blob_client | | test.py:43:9:43:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:66:19:66:21 | ControlFlowNode for BSC | semmle.label | ControlFlowNode for BSC | +| test.py:66:19:66:42 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:67:5:67:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:68:5:68:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client | | test.py:68:5:68:15 | [post] ControlFlowNode for blob_client | semmle.label | [post] ControlFlowNode for blob_client | diff --git a/python/ql/test/library-tests/frameworks/aiohttp/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/aiohttp/ConceptsTest.ql index 1e2c1fab3ee..c379783c55e 100644 --- a/python/ql/test/library-tests/frameworks/aiohttp/ConceptsTest.ql +++ b/python/ql/test/library-tests/frameworks/aiohttp/ConceptsTest.ql @@ -1,12 +1,8 @@ import python import experimental.meta.ConceptsTest -class DedicatedResponseTest extends HttpServerHttpResponseTest { - DedicatedResponseTest() { file.getShortName() = "response_test.py" } -} +class DedicatedTest extends DedicatedResponseTest { + DedicatedTest() { this = "response_test.py" } -class OtherResponseTest extends HttpServerHttpResponseTest { - OtherResponseTest() { not this instanceof DedicatedResponseTest } - - override string getARelevantTag() { result = "HttpResponse" } + override predicate isDedicatedFile(File file) { file.getShortName() = this } } diff --git a/python/ql/test/library-tests/frameworks/aiohttp/client_request.py b/python/ql/test/library-tests/frameworks/aiohttp/client_request.py index 28efedf703f..1bafb4ef583 100644 --- a/python/ql/test/library-tests/frameworks/aiohttp/client_request.py +++ b/python/ql/test/library-tests/frameworks/aiohttp/client_request.py @@ -1,35 +1,35 @@ import aiohttp -import asyncio import ssl -s = aiohttp.ClientSession() -resp = s.request("method", "url") # $ clientRequestUrlPart="url" -resp = s.request("method", url="url") # $ clientRequestUrlPart="url" +async def test(): + s = aiohttp.ClientSession() + resp = await s.request("method", "url") # $ clientRequestUrlPart="url" + resp = await s.request("method", url="url") # $ clientRequestUrlPart="url" -with aiohttp.ClientSession() as session: - resp = session.get("url") # $ clientRequestUrlPart="url" - resp = session.request(method="GET", url="url") # $ clientRequestUrlPart="url" + async with aiohttp.ClientSession() as session: + resp = await session.get("url") # $ clientRequestUrlPart="url" + resp = await session.request(method="GET", url="url") # $ clientRequestUrlPart="url" -# other methods than GET -s = aiohttp.ClientSession() -resp = s.post("url") # $ clientRequestUrlPart="url" -resp = s.patch("url") # $ clientRequestUrlPart="url" -resp = s.options("url") # $ clientRequestUrlPart="url" + # other methods than GET + s = aiohttp.ClientSession() + resp = await s.post("url") # $ clientRequestUrlPart="url" + resp = await s.patch("url") # $ clientRequestUrlPart="url" + resp = await s.options("url") # $ clientRequestUrlPart="url" -# disabling of SSL validation -# see https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientSession.request -s.get("url", ssl=False) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled -s.get("url", ssl=0) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled -# None is treated as default and so does _not_ disable the check -s.get("url", ssl=None) # $ clientRequestUrlPart="url" + # disabling of SSL validation + # see https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientSession.request + s.get("url", ssl=False) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled + s.get("url", ssl=0) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled + # None is treated as default and so does _not_ disable the check + s.get("url", ssl=None) # $ clientRequestUrlPart="url" -# deprecated since 3.0, but still supported -s.get("url", verify_ssl=False) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled + # deprecated since 3.0, but still supported + s.get("url", verify_ssl=False) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled -# A manually constructed SSLContext does not have safe defaults, so is effectively the -# same as turning off SSL validation -context = ssl.SSLContext() -assert context.check_hostname == False -assert context.verify_mode == ssl.VerifyMode.CERT_NONE + # A manually constructed SSLContext does not have safe defaults, so is effectively the + # same as turning off SSL validation + context = ssl.SSLContext() + assert context.check_hostname == False + assert context.verify_mode == ssl.VerifyMode.CERT_NONE -s.get("url", ssl=context) # $ clientRequestUrlPart="url" MISSING: clientRequestCertValidationDisabled + s.get("url", ssl=context) # $ clientRequestUrlPart="url" MISSING: clientRequestCertValidationDisabled diff --git a/python/ql/test/library-tests/frameworks/django-v1/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/django-v1/ConceptsTest.ql index 1e2c1fab3ee..c379783c55e 100644 --- a/python/ql/test/library-tests/frameworks/django-v1/ConceptsTest.ql +++ b/python/ql/test/library-tests/frameworks/django-v1/ConceptsTest.ql @@ -1,12 +1,8 @@ import python import experimental.meta.ConceptsTest -class DedicatedResponseTest extends HttpServerHttpResponseTest { - DedicatedResponseTest() { file.getShortName() = "response_test.py" } -} +class DedicatedTest extends DedicatedResponseTest { + DedicatedTest() { this = "response_test.py" } -class OtherResponseTest extends HttpServerHttpResponseTest { - OtherResponseTest() { not this instanceof DedicatedResponseTest } - - override string getARelevantTag() { result = "HttpResponse" } + override predicate isDedicatedFile(File file) { file.getShortName() = this } } diff --git a/python/ql/test/library-tests/frameworks/django-v2-v3/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/django-v2-v3/ConceptsTest.ql index 1e2c1fab3ee..c379783c55e 100644 --- a/python/ql/test/library-tests/frameworks/django-v2-v3/ConceptsTest.ql +++ b/python/ql/test/library-tests/frameworks/django-v2-v3/ConceptsTest.ql @@ -1,12 +1,8 @@ import python import experimental.meta.ConceptsTest -class DedicatedResponseTest extends HttpServerHttpResponseTest { - DedicatedResponseTest() { file.getShortName() = "response_test.py" } -} +class DedicatedTest extends DedicatedResponseTest { + DedicatedTest() { this = "response_test.py" } -class OtherResponseTest extends HttpServerHttpResponseTest { - OtherResponseTest() { not this instanceof DedicatedResponseTest } - - override string getARelevantTag() { result = "HttpResponse" } + override predicate isDedicatedFile(File file) { file.getShortName() = this } } diff --git a/python/ql/test/library-tests/frameworks/fastapi/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/fastapi/ConceptsTest.ql index 1e2c1fab3ee..c379783c55e 100644 --- a/python/ql/test/library-tests/frameworks/fastapi/ConceptsTest.ql +++ b/python/ql/test/library-tests/frameworks/fastapi/ConceptsTest.ql @@ -1,12 +1,8 @@ import python import experimental.meta.ConceptsTest -class DedicatedResponseTest extends HttpServerHttpResponseTest { - DedicatedResponseTest() { file.getShortName() = "response_test.py" } -} +class DedicatedTest extends DedicatedResponseTest { + DedicatedTest() { this = "response_test.py" } -class OtherResponseTest extends HttpServerHttpResponseTest { - OtherResponseTest() { not this instanceof DedicatedResponseTest } - - override string getARelevantTag() { result = "HttpResponse" } + override predicate isDedicatedFile(File file) { file.getShortName() = this } } diff --git a/python/ql/test/library-tests/frameworks/flask/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/flask/ConceptsTest.ql index 1e2c1fab3ee..c379783c55e 100644 --- a/python/ql/test/library-tests/frameworks/flask/ConceptsTest.ql +++ b/python/ql/test/library-tests/frameworks/flask/ConceptsTest.ql @@ -1,12 +1,8 @@ import python import experimental.meta.ConceptsTest -class DedicatedResponseTest extends HttpServerHttpResponseTest { - DedicatedResponseTest() { file.getShortName() = "response_test.py" } -} +class DedicatedTest extends DedicatedResponseTest { + DedicatedTest() { this = "response_test.py" } -class OtherResponseTest extends HttpServerHttpResponseTest { - OtherResponseTest() { not this instanceof DedicatedResponseTest } - - override string getARelevantTag() { result = "HttpResponse" } + override predicate isDedicatedFile(File file) { file.getShortName() = this } } diff --git a/python/ql/test/library-tests/frameworks/flask_admin/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/flask_admin/ConceptsTest.ql index 1e2c1fab3ee..c379783c55e 100644 --- a/python/ql/test/library-tests/frameworks/flask_admin/ConceptsTest.ql +++ b/python/ql/test/library-tests/frameworks/flask_admin/ConceptsTest.ql @@ -1,12 +1,8 @@ import python import experimental.meta.ConceptsTest -class DedicatedResponseTest extends HttpServerHttpResponseTest { - DedicatedResponseTest() { file.getShortName() = "response_test.py" } -} +class DedicatedTest extends DedicatedResponseTest { + DedicatedTest() { this = "response_test.py" } -class OtherResponseTest extends HttpServerHttpResponseTest { - OtherResponseTest() { not this instanceof DedicatedResponseTest } - - override string getARelevantTag() { result = "HttpResponse" } + override predicate isDedicatedFile(File file) { file.getShortName() = this } } diff --git a/python/ql/test/library-tests/frameworks/markupsafe/taint_test.py b/python/ql/test/library-tests/frameworks/markupsafe/taint_test.py index 7f3c2b37cf7..8d25b6d05ba 100644 --- a/python/ql/test/library-tests/frameworks/markupsafe/taint_test.py +++ b/python/ql/test/library-tests/frameworks/markupsafe/taint_test.py @@ -27,40 +27,40 @@ def test(): # as tainted even after it has been escaped in some place. This _might_ not be the # case since data-flow library has taint-steps from adjacent uses... ensure_tainted(ts) # $ tainted - ensure_not_tainted(escape(ts)) # $ escapeInput=ts escapeKind=html escapeOutput=escape(..) + ensure_not_tainted(escape(ts)) # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=escape(..) ensure_tainted(ts) # $ tainted ensure_tainted( ts, # $ tainted m_unsafe, # $ tainted - m_unsafe + SAFE, # $ escapeInput=SAFE escapeKind=html escapeOutput=BinaryExpr MISSING: tainted - SAFE + m_unsafe, # $ escapeInput=SAFE escapeKind=html escapeOutput=BinaryExpr MISSING: tainted - m_unsafe.format(SAFE), # $ escapeInput=SAFE escapeKind=html escapeOutput=m_unsafe.format(..) MISSING: tainted - m_unsafe % SAFE, # $ escapeInput=SAFE escapeKind=html escapeOutput=BinaryExpr MISSING: tainted - m_unsafe + ts, # $ escapeInput=ts escapeKind=html escapeOutput=BinaryExpr MISSING: tainted + m_unsafe + SAFE, # $ escapeInput=SAFE escapeKind=html escapeKind=xml escapeOutput=BinaryExpr MISSING: tainted + SAFE + m_unsafe, # $ escapeInput=SAFE escapeKind=html escapeKind=xml escapeOutput=BinaryExpr MISSING: tainted + m_unsafe.format(SAFE), # $ escapeInput=SAFE escapeKind=html escapeKind=xml escapeOutput=m_unsafe.format(..) MISSING: tainted + m_unsafe % SAFE, # $ escapeInput=SAFE escapeKind=html escapeKind=xml escapeOutput=BinaryExpr MISSING: tainted + m_unsafe + ts, # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=BinaryExpr MISSING: tainted m_safe.format(m_unsafe), # $ tainted m_safe % m_unsafe, # $ tainted - escape(ts).unescape(), # $ escapeInput=ts escapeKind=html escapeOutput=escape(..) MISSING: tainted - escape_silent(ts).unescape(), # $ escapeInput=ts escapeKind=html escapeOutput=escape_silent(..) MISSING: tainted + escape(ts).unescape(), # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=escape(..) MISSING: tainted + escape_silent(ts).unescape(), # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=escape_silent(..) MISSING: tainted ) ensure_not_tainted( - escape(ts), # $ escapeInput=ts escapeKind=html escapeOutput=escape(..) - escape_silent(ts), # $ escapeInput=ts escapeKind=html escapeOutput=escape_silent(..) + escape(ts), # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=escape(..) + escape_silent(ts), # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=escape_silent(..) - Markup.escape(ts), # $ escapeInput=ts escapeKind=html escapeOutput=Markup.escape(..) + Markup.escape(ts), # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=Markup.escape(..) m_safe, - m_safe + ts, # $ escapeInput=ts escapeKind=html escapeOutput=BinaryExpr - ts + m_safe, # $ escapeInput=ts escapeKind=html escapeOutput=BinaryExpr - m_safe.format(ts), # $ escapeInput=ts escapeKind=html escapeOutput=m_safe.format(..) - m_safe % ts, # $ escapeInput=ts escapeKind=html escapeOutput=BinaryExpr + m_safe + ts, # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=BinaryExpr + ts + m_safe, # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=BinaryExpr + m_safe.format(ts), # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=m_safe.format(..) + m_safe % ts, # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=BinaryExpr - escape(ts) + ts, # $ escapeInput=ts escapeKind=html escapeOutput=BinaryExpr escapeOutput=escape(..) - escape_silent(ts) + ts, # $ escapeInput=ts escapeKind=html escapeOutput=BinaryExpr escapeOutput=escape_silent(..) - Markup.escape(ts) + ts, # $ escapeInput=ts escapeKind=html escapeOutput=BinaryExpr escapeOutput=Markup.escape(..) + escape(ts) + ts, # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=BinaryExpr escapeOutput=escape(..) + escape_silent(ts) + ts, # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=BinaryExpr escapeOutput=escape_silent(..) + Markup.escape(ts) + ts, # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=BinaryExpr escapeOutput=Markup.escape(..) ) # flask re-exports these, as: @@ -73,8 +73,8 @@ def test(): ) ensure_not_tainted( - flask.escape(ts), # $ escapeInput=ts escapeKind=html escapeOutput=flask.escape(..) - flask.Markup.escape(ts), # $ escapeInput=ts escapeKind=html escapeOutput=flask.Markup.escape(..) + flask.escape(ts), # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=flask.escape(..) + flask.Markup.escape(ts), # $ escapeInput=ts escapeKind=html escapeKind=xml escapeOutput=flask.Markup.escape(..) ) diff --git a/python/ql/test/library-tests/frameworks/tornado/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/tornado/ConceptsTest.ql index 1e2c1fab3ee..c379783c55e 100644 --- a/python/ql/test/library-tests/frameworks/tornado/ConceptsTest.ql +++ b/python/ql/test/library-tests/frameworks/tornado/ConceptsTest.ql @@ -1,12 +1,8 @@ import python import experimental.meta.ConceptsTest -class DedicatedResponseTest extends HttpServerHttpResponseTest { - DedicatedResponseTest() { file.getShortName() = "response_test.py" } -} +class DedicatedTest extends DedicatedResponseTest { + DedicatedTest() { this = "response_test.py" } -class OtherResponseTest extends HttpServerHttpResponseTest { - OtherResponseTest() { not this instanceof DedicatedResponseTest } - - override string getARelevantTag() { result = "HttpResponse" } + override predicate isDedicatedFile(File file) { file.getShortName() = this } } diff --git a/python/ql/test/library-tests/frameworks/twisted/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/twisted/ConceptsTest.ql index 1e2c1fab3ee..c379783c55e 100644 --- a/python/ql/test/library-tests/frameworks/twisted/ConceptsTest.ql +++ b/python/ql/test/library-tests/frameworks/twisted/ConceptsTest.ql @@ -1,12 +1,8 @@ import python import experimental.meta.ConceptsTest -class DedicatedResponseTest extends HttpServerHttpResponseTest { - DedicatedResponseTest() { file.getShortName() = "response_test.py" } -} +class DedicatedTest extends DedicatedResponseTest { + DedicatedTest() { this = "response_test.py" } -class OtherResponseTest extends HttpServerHttpResponseTest { - OtherResponseTest() { not this instanceof DedicatedResponseTest } - - override string getARelevantTag() { result = "HttpResponse" } + override predicate isDedicatedFile(File file) { file.getShortName() = this } } diff --git a/python/ql/test/library-tests/variables/definitions/definition-values.expected b/python/ql/test/library-tests/variables/definitions/definition-values.expected new file mode 100644 index 00000000000..c81fafd6a64 --- /dev/null +++ b/python/ql/test/library-tests/variables/definitions/definition-values.expected @@ -0,0 +1,4 @@ +| test.py:3:5:3:9 | ControlFlowNode for fail5 | test.py:3:1:3:13 | ControlFlowNode for FunctionExpr | +| test.py:4:5:4:8 | ControlFlowNode for Tuple | test.py:4:12:4:12 | ControlFlowNode for t | +| test.py:7:5:7:26 | ControlFlowNode for default_value_in_param | test.py:7:1:7:33 | ControlFlowNode for FunctionExpr | +| test.py:7:28:7:28 | ControlFlowNode for x | test.py:7:30:7:31 | ControlFlowNode for IntegerLiteral | diff --git a/python/ql/test/library-tests/variables/definitions/definition-values.ql b/python/ql/test/library-tests/variables/definitions/definition-values.ql new file mode 100644 index 00000000000..7c864541746 --- /dev/null +++ b/python/ql/test/library-tests/variables/definitions/definition-values.ql @@ -0,0 +1,4 @@ +import python + +from DefinitionNode d +select d, d.getValue() diff --git a/python/ql/test/library-tests/variables/definitions/test.expected b/python/ql/test/library-tests/variables/definitions/test.expected index dc853ee7f2f..f5e119e6fb2 100644 --- a/python/ql/test/library-tests/variables/definitions/test.expected +++ b/python/ql/test/library-tests/variables/definitions/test.expected @@ -1,4 +1,6 @@ | 3 | 5 | ControlFlowNode for fail5 | | 4 | 5 | ControlFlowNode for Tuple | | 4 | 5 | ControlFlowNode for x | -| 4 | 8 | ControlFlowNode for y | \ No newline at end of file +| 4 | 8 | ControlFlowNode for y | +| 7 | 5 | ControlFlowNode for default_value_in_param | +| 7 | 28 | ControlFlowNode for x | diff --git a/python/ql/test/library-tests/variables/definitions/test.py b/python/ql/test/library-tests/variables/definitions/test.py index 0695a275b3c..c8cd2507399 100644 --- a/python/ql/test/library-tests/variables/definitions/test.py +++ b/python/ql/test/library-tests/variables/definitions/test.py @@ -3,3 +3,6 @@ def fail5(t): x, y = t return x + +def default_value_in_param(x=42): + print(x) diff --git a/python/ql/test/query-tests/Security/CWE-611-Xxe/Xxe.expected b/python/ql/test/query-tests/Security/CWE-611-Xxe/Xxe.expected index c18b417db90..53dc9f018cb 100644 --- a/python/ql/test/query-tests/Security/CWE-611-Xxe/Xxe.expected +++ b/python/ql/test/query-tests/Security/CWE-611-Xxe/Xxe.expected @@ -1,25 +1,25 @@ edges | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:1:26:1:32 | GSSA Variable request | -| test.py:1:26:1:32 | GSSA Variable request | test.py:8:19:8:25 | ControlFlowNode for request | -| test.py:1:26:1:32 | GSSA Variable request | test.py:19:19:19:25 | ControlFlowNode for request | -| test.py:8:19:8:25 | ControlFlowNode for request | test.py:8:19:8:30 | ControlFlowNode for Attribute | -| test.py:8:19:8:30 | ControlFlowNode for Attribute | test.py:8:19:8:45 | ControlFlowNode for Subscript | -| test.py:8:19:8:45 | ControlFlowNode for Subscript | test.py:9:34:9:44 | ControlFlowNode for xml_content | -| test.py:19:19:19:25 | ControlFlowNode for request | test.py:19:19:19:30 | ControlFlowNode for Attribute | -| test.py:19:19:19:30 | ControlFlowNode for Attribute | test.py:19:19:19:45 | ControlFlowNode for Subscript | -| test.py:19:19:19:45 | ControlFlowNode for Subscript | test.py:30:34:30:44 | ControlFlowNode for xml_content | +| test.py:1:26:1:32 | GSSA Variable request | test.py:9:19:9:25 | ControlFlowNode for request | +| test.py:1:26:1:32 | GSSA Variable request | test.py:20:19:20:25 | ControlFlowNode for request | +| test.py:9:19:9:25 | ControlFlowNode for request | test.py:9:19:9:30 | ControlFlowNode for Attribute | +| test.py:9:19:9:30 | ControlFlowNode for Attribute | test.py:9:19:9:45 | ControlFlowNode for Subscript | +| test.py:9:19:9:45 | ControlFlowNode for Subscript | test.py:10:34:10:44 | ControlFlowNode for xml_content | +| test.py:20:19:20:25 | ControlFlowNode for request | test.py:20:19:20:30 | ControlFlowNode for Attribute | +| test.py:20:19:20:30 | ControlFlowNode for Attribute | test.py:20:19:20:45 | ControlFlowNode for Subscript | +| test.py:20:19:20:45 | ControlFlowNode for Subscript | test.py:31:34:31:44 | ControlFlowNode for xml_content | nodes | test.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | | test.py:1:26:1:32 | GSSA Variable request | semmle.label | GSSA Variable request | -| test.py:8:19:8:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test.py:8:19:8:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| test.py:8:19:8:45 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | -| test.py:9:34:9:44 | ControlFlowNode for xml_content | semmle.label | ControlFlowNode for xml_content | -| test.py:19:19:19:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| test.py:19:19:19:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| test.py:19:19:19:45 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | -| test.py:30:34:30:44 | ControlFlowNode for xml_content | semmle.label | ControlFlowNode for xml_content | +| test.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:9:19:9:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:9:19:9:45 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test.py:10:34:10:44 | ControlFlowNode for xml_content | semmle.label | ControlFlowNode for xml_content | +| test.py:20:19:20:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:20:19:20:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:20:19:20:45 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test.py:31:34:31:44 | ControlFlowNode for xml_content | semmle.label | ControlFlowNode for xml_content | subpaths #select -| test.py:9:34:9:44 | ControlFlowNode for xml_content | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:9:34:9:44 | ControlFlowNode for xml_content | XML parsing depends on a $@ without guarding against external entity expansion. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | -| test.py:30:34:30:44 | ControlFlowNode for xml_content | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:30:34:30:44 | ControlFlowNode for xml_content | XML parsing depends on a $@ without guarding against external entity expansion. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:10:34:10:44 | ControlFlowNode for xml_content | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:10:34:10:44 | ControlFlowNode for xml_content | XML parsing depends on a $@ without guarding against external entity expansion. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:31:34:31:44 | ControlFlowNode for xml_content | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:31:34:31:44 | ControlFlowNode for xml_content | XML parsing depends on a $@ without guarding against external entity expansion. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-611-Xxe/test.py b/python/ql/test/query-tests/Security/CWE-611-Xxe/test.py index d9181c4cf34..104f2663d59 100644 --- a/python/ql/test/query-tests/Security/CWE-611-Xxe/test.py +++ b/python/ql/test/query-tests/Security/CWE-611-Xxe/test.py @@ -1,5 +1,6 @@ from flask import Flask, request import lxml.etree +import markupsafe app = Flask(__name__) @@ -28,3 +29,9 @@ def super_vuln_handler(): huge_tree=True, ) return lxml.etree.fromstring(xml_content, parser=parser).text + +@app.route("/sanitized-handler") +def sanitized_handler(): + xml_content = request.args['xml_content'] + xml_content = markupsafe.escape(xml_content) + return lxml.etree.fromstring(xml_content).text \ No newline at end of file diff --git a/ql/Cargo.lock b/ql/Cargo.lock index 7e498db849d..5ce63783ea8 100644 Binary files a/ql/Cargo.lock and b/ql/Cargo.lock differ diff --git a/ql/buramu/Cargo.toml b/ql/buramu/Cargo.toml index bfbcacef710..d6b3ea6d7a6 100644 --- a/ql/buramu/Cargo.toml +++ b/ql/buramu/Cargo.toml @@ -9,4 +9,4 @@ edition = "2018" lazy_static = "1.4.0" chrono = "0.4.26" rayon = "1.7.0" -regex = "1.9.0" +regex = "1.9.1" diff --git a/ql/extractor/Cargo.toml b/ql/extractor/Cargo.toml index 92d10a57d5c..438173b45cf 100644 --- a/ql/extractor/Cargo.toml +++ b/ql/extractor/Cargo.toml @@ -16,5 +16,5 @@ clap = { version = "4.2", features = ["derive"] } tracing = "0.1" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } rayon = "1.7.0" -regex = "1.9.0" +regex = "1.9.1" codeql-extractor = { path = "../../shared/tree-sitter-extractor" } diff --git a/ql/ql/src/queries/performance/MissingNoinline.ql b/ql/ql/src/queries/performance/MissingNoinline.ql index 3eb3c7eab59..78426379dbe 100644 --- a/ql/ql/src/queries/performance/MissingNoinline.ql +++ b/ql/ql/src/queries/performance/MissingNoinline.ql @@ -18,7 +18,9 @@ where not decl.getAnAnnotation() instanceof NoMagic and not decl.getAnAnnotation() instanceof NoOpt and not decl.getAnAnnotation().getName() = "cached" and - // If it's marked as inline it's probably because the QLDoc says something like - // "this predicate is inlined because it gives a better join-order". - not decl.getAnAnnotation() instanceof Inline -select decl, "This predicate might be inlined." + // If it's marked as inline (or has at least one bindingset annotation) + // it's probably because the QLDoc says something like "this predicate + // is inlined because it gives a better join-order". + not decl.getAnAnnotation() instanceof Inline and + not decl.getAnAnnotation() instanceof BindingSet +select decl, "This predicate should probably be marked pragma[noinline] to prevent inlining." diff --git a/ql/ql/test/queries/performance/MissingNoInline/MissingNoInline.expected b/ql/ql/test/queries/performance/MissingNoInline/MissingNoInline.expected new file mode 100644 index 00000000000..199b529a32c --- /dev/null +++ b/ql/ql/test/queries/performance/MissingNoInline/MissingNoInline.expected @@ -0,0 +1 @@ +| Test.qll:8:11:8:25 | ClasslessPredicate missingNoInline | This predicate should probably be marked pragma[noinline] to prevent inlining. | diff --git a/ql/ql/test/queries/performance/MissingNoInline/MissingNoInline.qlref b/ql/ql/test/queries/performance/MissingNoInline/MissingNoInline.qlref new file mode 100644 index 00000000000..aee3346d730 --- /dev/null +++ b/ql/ql/test/queries/performance/MissingNoInline/MissingNoInline.qlref @@ -0,0 +1 @@ +queries/performance/MissingNoinline.ql \ No newline at end of file diff --git a/ql/ql/test/queries/performance/MissingNoInline/Test.qll b/ql/ql/test/queries/performance/MissingNoInline/Test.qll new file mode 100644 index 00000000000..a55315be7e2 --- /dev/null +++ b/ql/ql/test/queries/performance/MissingNoInline/Test.qll @@ -0,0 +1,73 @@ +import ql + +/** + * Holds if `add.getLeftOperand() = e1` and `add.getRightOperand() = e2`. + * + * This predicate exists to fix a join order. + */ +predicate missingNoInline(AddExpr add, Expr e1, Expr e2) { + // BAD + add.getLeftOperand() = e1 and + add.getRightOperand() = e2 +} + +/** + * Holds if `add.getLeftOperand() = e1` and `add.getRightOperand() = e2`. + * + * This predicate exists to fix a join order. + */ +pragma[noinline] +predicate noInlined(AddExpr add, Expr e1, Expr e2) { + // GOOD + add.getLeftOperand() = e1 and + add.getRightOperand() = e2 +} + +/** + * Holds if `add.getLeftOperand() = e1` and `add.getRightOperand() = e2`. + * + * This predicate exists to fix a join order. + */ +pragma[nomagic] +predicate nomagicd(AddExpr add, Expr e1, Expr e2) { + // GOOD + add.getLeftOperand() = e1 and + add.getRightOperand() = e2 +} + +/** + * Holds if `add.getLeftOperand() = e1` and `add.getRightOperand() = e2`. + * + * This predicate exists to fix a join order. + */ +pragma[inline] +predicate inlined(AddExpr add, Expr e1, Expr e2) { + // GOOD + add.getLeftOperand() = e1 and + add.getRightOperand() = e2 +} + +/** + * Holds if `add.getLeftOperand() = e1` and `add.getRightOperand() = e2`. + * + * This predicate exists to fix a join order. + */ +bindingset[add] +predicate hasBindingset(AddExpr add, Expr e1, Expr e2) { + // GOOD + add.getLeftOperand() = e1 and + add.getRightOperand() = e2 +} + +/** + * Holds if `add.getLeftOperand() = e1` and `add.getRightOperand() = e2`. + * + * This predicate exists to fix a join order. + */ +pragma[noopt] +predicate noOpted(AddExpr add, Expr e1, Expr e2) { + // GOOD + add instanceof AddExpr and + add.getLeftOperand() = e1 and + add.getRightOperand() = e2 +} diff --git a/ruby/ql/lib/CHANGELOG.md b/ruby/ql/lib/CHANGELOG.md index a06ccb6f8ad..47221ac14e3 100644 --- a/ruby/ql/lib/CHANGELOG.md +++ b/ruby/ql/lib/CHANGELOG.md @@ -1,3 +1,28 @@ +## 0.7.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Major Analysis Improvements + +* The API graph library (`codeql.ruby.ApiGraphs`) has been significantly improved, with better support for inheritance, + and data-flow nodes can now be converted to API nodes by calling `.track()` or `.backtrack()` on the node. + API graphs allow for efficient modelling of how a given value is used by the code base, or how values produced by the code base + are consumed by a library. See the documentation for `API::Node` for details and examples. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* The `'QUERY_STRING'` field of a Rack `env` parameter is now recognized as a source of remote user input. +* Query parameters and cookies from `Rack::Response` objects are recognized as potential sources of remote flow input. +* Calls to `Rack::Utils.parse_query` now propagate taint. + ## 0.7.0 ### Deprecated APIs diff --git a/ruby/ql/lib/change-notes/released/0.7.1.md b/ruby/ql/lib/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..2a1c50ddc91 --- /dev/null +++ b/ruby/ql/lib/change-notes/released/0.7.1.md @@ -0,0 +1,24 @@ +## 0.7.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Major Analysis Improvements + +* The API graph library (`codeql.ruby.ApiGraphs`) has been significantly improved, with better support for inheritance, + and data-flow nodes can now be converted to API nodes by calling `.track()` or `.backtrack()` on the node. + API graphs allow for efficient modelling of how a given value is used by the code base, or how values produced by the code base + are consumed by a library. See the documentation for `API::Node` for details and examples. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* The `'QUERY_STRING'` field of a Rack `env` parameter is now recognized as a source of remote user input. +* Query parameters and cookies from `Rack::Response` objects are recognized as potential sources of remote flow input. +* Calls to `Rack::Utils.parse_query` now propagate taint. diff --git a/ruby/ql/lib/codeql-pack.release.yml b/ruby/ql/lib/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/ruby/ql/lib/codeql-pack.release.yml +++ b/ruby/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 1cf4c445781..0a650ea4ca2 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -1,13 +1,13 @@ /** - * Provides an implementation of _API graphs_, which are an abstract representation of the API - * surface used and/or defined by a code base. + * Provides an implementation of _API graphs_, which allow efficient modelling of how a given + * value is used by the code base or how values produced by the code base are consumed by a library. * - * The nodes of the API graph represent definitions and uses of API components. The edges are - * directed and labeled; they specify how the components represented by nodes relate to each other. + * See `API::Node` for more details. */ private import codeql.ruby.AST private import codeql.ruby.DataFlow +private import codeql.ruby.typetracking.ApiGraphShared private import codeql.ruby.typetracking.TypeTracker private import codeql.ruby.typetracking.TypeTrackerSpecific as TypeTrackerSpecific private import codeql.ruby.controlflow.CfgNodes @@ -19,85 +19,140 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatc */ module API { /** - * A node in the API graph, representing a value that has crossed the boundary between this - * codebase and an external library (or in general, any external codebase). + * A node in the API graph, that is, a value that can be tracked interprocedurally. * - * ### Basic usage + * The API graph is a graph for tracking values of certain types in a way that accounts for inheritance + * and interprocedural data flow. * * API graphs are typically used to identify "API calls", that is, calls to an external function * whose implementation is not necessarily part of the current codebase. * + * ### Basic usage + * * The most basic use of API graphs is typically as follows: * 1. Start with `API::getTopLevelMember` for the relevant library. * 2. Follow up with a chain of accessors such as `getMethod` describing how to get to the relevant API function. - * 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`. + * 3. Map the resulting API graph nodes to data-flow nodes, using `asSource`, `asSink`, or `asCall`. * - * For example, a simplified way to get arguments to `Foo.bar` would be - * ```ql - * API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).asSink() + * The following examples demonstrate how to identify the expression `x` in various basic cases: + * ```rb + * # API::getTopLevelMember("Foo").getMethod("bar").getArgument(0).asSink() + * Foo.bar(x) + * + * # API::getTopLevelMember("Foo").getMethod("bar").getKeywordArgument("foo").asSink() + * Foo.bar(foo: x) + * + * # API::getTopLevelMember("Foo").getInstance().getMethod("bar").getArgument(0).asSink() + * Foo.new.bar(x) + * + * Foo.bar do |x| # API::getTopLevelMember("Foo").getMethod("bar").getBlock().getParameter(0).asSource() + * end * ``` * - * The most commonly used accessors are `getMember`, `getMethod`, `getParameter`, and `getReturn`. + * ### Data flow * - * ### API graph nodes + * The members predicates on this class generally take inheritance and data flow into account. * - * There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value: - * - **Use-nodes** represent values held by the current codebase, which came from an external library. - * (The current codebase is "using" a value that came from the library). - * - **Def-nodes** represent values held by the external library, which came from this codebase. - * (The current codebase "defines" the value seen by the library). - * - * API graph nodes are associated with data-flow nodes in the current codebase. - * (Since external libraries are not part of the database, there is no way to associate with concrete - * data-flow nodes from the external library). - * - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase, - * such as the return value of a call to an external function. - * - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase, - * such as an argument passed in a call to an external function. - * - * - * ### Access paths and edge labels - * - * Nodes in the API graph are associated with a set of access paths, describing a series of operations - * that may be performed to obtain that value. - * - * For example, the access path `API::getTopLevelMember("Foo").getMethod("bar")` represents the action of - * reading the top-level constant `Foo` and then accessing the method `bar` on the resulting object. - * It would be associated with a call such as `Foo.bar()`. - * - * Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node - * determines who is performing the operation, and the type of the `B` node determines who ends up holding - * the result: - * - An edge starting from a use-node describes what the current codebase is doing to a value that - * came from a library. - * - An edge starting from a def-node describes what the external library might do to a value that - * came from the current codebase. - * - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node). - * - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is - * the place where it was "last seen" in the current codebase before flowing out) - * - * Because the implementation of the external library is not visible, it is not known exactly what operations - * it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would - * lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform - * those operations. (When constructing these edges, we assume the library is somewhat well-behaved). - * - * For example, given this snippet: + * The following example demonstrates a case where data flow was used to find the sink `x`: * ```ruby - * Foo.bar(->(x) { doSomething(x) }) + * def doSomething f + * f.bar(x) # API::getTopLevelMember("Foo").getInstance().getMethod("bar").getArgument(0).asSink() + * end + * doSomething Foo.new * ``` - * A callback is passed to the external function `Foo.bar`. We can't know if `Foo.bar` will actually invoke this callback. - * But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter. - * For that reason, an edge is generated representing the argument-passing operation that might be performed by `Foo.bar`. - * This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x` of the lambda. + * The call `API::getTopLevelMember("Foo").getInstance()` identifies the `Foo.new` call, and `getMethod("bar")` + * then follows data flow from there to find calls to `bar` where that object flows to the receiver. + * This results in the `f.bar` call. + * + * ### Backward data flow + * + * When inspecting the arguments of a call, the data flow direction is backwards. + * The following example illustrates this when we match the `x` parameter of a block: + * ```ruby + * def doSomething &blk + * Foo.bar &blk + * end + * doSomething do |x| # API::getTopLevelMember("Foo").getMethod("bar").getBlock().getParameter(0).asSource() + * end + * ``` + * When `getParameter(0)` is evaluated, the API graph backtracks the `&blk` argument to the block argument a few + * lines below. As a result, it eventually matches the `x` parameter of that block. + * + * ### Inheritance + * + * When a class or module object is tracked, inheritance is taken into account. + * + * In the following example, a call to `Foo.bar` was found via a subclass of `Foo`, + * because classes inherit singleton methods from their base class: + * ```ruby + * class Subclass < Foo + * def self.doSomething + * bar(x) # API::getTopLevelMember("Foo").getMethod("bar").getArgument(0).asSink() + * end + * end + * ``` + * + * Similarly, instance methods can be found in subclasses, or ancestors of subclases in cases of multiple inheritance: + * ```rb + * module Mixin + * def doSomething + * bar(x) # API::getTopLevelMember("Foo").getInstance().getMethod("bar").getArgument(0).asSink() + * end + * end + * class Subclass < Foo + * include Mixin + * end + * ``` + * The value of `self` in `Mixin#doSomething` is seen as a potential instance of `Foo`, and is thus found by `getTopLevelMember("Foo").getInstance()`. + * This eventually results in finding the call `bar`, due to its implicit `self` receiver, and finally its argument `x` is found as the sink. + * + * ### Backward data flow and classes + * + * When inspecting the arguments of a call, and the value flowing into that argument is a user-defined class (or an instance thereof), + * uses of `getMethod` will find method definitions in that class (including inherited ones) rather than finding method calls. + * + * This example illustrates how this can be used to model cases where the library calls a specific named method on a user-defined class: + * ```rb + * class MyClass + * def doSomething + * x # API::getTopLevelMember("Foo").getMethod("bar").getArgument(0).getMethod("doSomething").getReturn().asSink() + * end + * end + * Foo.bar MyClass.new + * ``` + * + * When modeling an external library that is known to call a specific method on a parameter (in this case `doSomething`), this makes + * it possible to find the corresponding method definition in user code. + * + * ### Strict left-to-right evaluation + * + * Most member predicates on this class are intended to be chained, and are always evaluated from left to right, which means + * the caller should restrict the initial set of values. + * + * For example, in the following snippet, we always find the uses of `Foo` before finding calls to `bar`: + * ```ql + * API::getTopLevelMember("Foo").getMethod("bar") + * ``` + * In particular, the implementation will never look for calls to `bar` and work backward from there. + * + * Beware of the footgun that is to use API graphs with an unrestricted receiver: + * ```ql + * API::Node barCall(API::Node base) { + * result = base.getMethod("bar") // Do not do this! + * } + * ``` + * The above predicate does not restrict the receiver, and will thus perform an interprocedural data flow + * search starting at every node in the graph, which is very expensive. */ class Node extends Impl::TApiNode { /** - * Gets a data-flow node where this value may flow after entering the current codebase. + * Gets a data-flow node where this value may flow interprocedurally. * * This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow. * See `asSource()` for examples. */ - pragma[inline] + bindingset[this] + pragma[inline_late] DataFlow::Node getAValueReachableFromSource() { result = getAValueReachableFromSourceInline(this) } @@ -119,16 +174,14 @@ module API { * end * ``` */ - pragma[inline] - DataFlow::LocalSourceNode asSource() { - result = pragma[only_bind_out](this).(Node::Internal).asSourceInternal() - } + bindingset[this] + pragma[inline_late] + DataFlow::LocalSourceNode asSource() { result = asSourceInline(this) } /** - * Gets a data-flow node where this value leaves the current codebase and flows into an - * external library (or in general, any external codebase). + * Gets a data-flow node where this value potentially flows into an external library. * - * Concretely, this corresponds to an argument passed to a call to external code. + * This is usually the argument of a call, but can also be the return value of a callback. * * For example: * ```ruby @@ -143,15 +196,443 @@ module API { * }) * ``` */ - DataFlow::Node asSink() { Impl::def(this, result) } + bindingset[this] + pragma[inline_late] + DataFlow::Node asSink() { result = asSinkInline(this) } /** - * Get a data-flow node that transitively flows to an external library (or in general, any external codebase). + * Gets a callable that can reach this sink. + * + * For example: + * ```ruby + * Foo.bar do |x| # API::getTopLevelMember("Foo").getMethod("bar").getBlock().asCallable() + * end + * + * class Baz + * def m # API::getTopLevelMember("Foo").getMethod("bar").getArgument(0).getMethod("m").asCallable() + * end + * end + * Foo.bar(Baz.new) + * ``` + */ + bindingset[this] + pragma[inline_late] + DataFlow::CallableNode asCallable() { Impl::asCallable(this.getAnEpsilonSuccessor(), result) } + + /** + * Get a data-flow node that transitively flows to this value, provided that this value corresponds + * to a sink. * * This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow. * See `asSink()` for examples. */ - DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) } + bindingset[this] + pragma[inline_late] + DataFlow::Node getAValueReachingSink() { result = getAValueReachingSinkInline(this) } + + /** + * Gets a module or class referred to by this API node. + * + * For example: + * ```ruby + * module Foo + * class Bar # API::getTopLevelMember("Foo").getMember("Bar").asModule() + * end + * end + * ``` + */ + bindingset[this] + pragma[inline_late] + DataFlow::ModuleNode asModule() { this = Impl::MkModuleObjectDown(result) } + + /** + * Gets the call referred to by this API node. + * + * For example: + * ```ruby + * # API::getTopLevelMember("Foo").getMethod("bar").asCall() + * Foo.bar + * + * class Bar < Foo + * def doSomething + * # API::getTopLevelMember("Foo").getInstance().getMethod("baz").asCall() + * baz + * end + * end + * ``` + */ + bindingset[this] + pragma[inline_late] + DataFlow::CallNode asCall() { this = Impl::MkMethodAccessNode(result) } + + /** + * DEPRECATED. Use `asCall()` instead. + */ + pragma[inline] + deprecated DataFlow::CallNode getCallNode() { this = Impl::MkMethodAccessNode(result) } + + /** + * Gets a module or class that descends from the module or class referenced by this API node. + */ + bindingset[this] + pragma[inline_late] + DataFlow::ModuleNode getADescendentModule() { result = this.getAnEpsilonSuccessor().asModule() } + + /** + * Gets a call to a method on the receiver represented by this API node. + * + * This is a shorthand for `getMethod(method).asCall()`, and thus returns a data-flow node + * rather than an API node. + * + * For example: + * ```ruby + * # API::getTopLevelMember("Foo").getAMethodCall("bar") + * Foo.bar + * ``` + */ + pragma[inline] + DataFlow::CallNode getAMethodCall(string method) { + // This predicate is currently not 'inline_late' because 'method' can be an input or output + result = this.getMethod(method).asCall() + } + + /** + * Gets an access to the constant `m` with this value as the base of the access. + * + * For example: + * ```ruby + * A::B # API::getATopLevelMember("A").getMember("B") + * + * module A + * class B # API::getATopLevelMember("A").getMember("B") + * end + * end + * ``` + */ + pragma[inline] + Node getMember(string m) { + // This predicate is currently not 'inline_late' because 'm' can be an input or output + Impl::memberEdge(this.getAnEpsilonSuccessor(), m, result) + } + + /** + * Gets an access to a constant with this value as the base of the access. + * + * This is equivalent to `getMember(_)` but can be more efficient. + */ + bindingset[this] + pragma[inline_late] + Node getAMember() { Impl::anyMemberEdge(this.getAnEpsilonSuccessor(), result) } + + /** + * Gets a node that may refer to an instance of the module or class represented by this API node. + * + * This includes the following: + * - Calls to `new` on this module or class or a descendent thereof + * - References to `self` in instance methods declared in any ancestor of any descendent of this module or class + * + * For example: + * ```ruby + * A.new # API::getTopLevelMember("A").getInstance() + * + * class B < A + * def m + * self # API::getTopLevelMember("A").getInstance() + * end + * end + * + * B.new # API::getTopLevelMember("A").getInstance() + * + * class C < A + * include Mixin + * end + * module Mixin + * def m + * # Although 'Mixin' is not directly related to 'A', 'self' may refer to an instance of 'A' + * # due to its inclusion in a subclass of 'A'. + * self # API::getTopLevelMember("A").getInstance() + * end + * end + * ``` + */ + bindingset[this] + pragma[inline_late] + Node getInstance() { Impl::instanceEdge(this.getAnEpsilonSuccessor(), result) } + + /** + * Gets a call to `method` with this value as the receiver, or the definition of `method` on + * an object that can reach this sink. + * + * If the receiver represents a module or class object, this includes calls on descendents of that module or class. + * + * For example: + * ```ruby + * # API::getTopLevelMember("Foo").getMethod("bar") + * Foo.bar + * + * # API::getTopLevelMember("Foo").getInstance().getMethod("bar") + * Foo.new.bar + * + * class B < Foo + * end + * B.bar # API::getTopLevelMember("Foo").getMethod("bar") + * + * class C + * def m # API::getTopLevelMember("Foo").getMethod("bar").getArgument(0).getMethod("m") + * end + * end + * Foo.bar(C.new) + * ``` + */ + pragma[inline] + Node getMethod(string method) { + // TODO: Consider 'getMethodTarget(method)' for looking up method definitions? + // This predicate is currently not 'inline_late' because 'method' can be an input or output + Impl::methodEdge(this.getAnEpsilonSuccessor(), method, result) + } + + /** + * Gets the result of this call, or the return value of this callable. + */ + bindingset[this] + pragma[inline_late] + Node getReturn() { Impl::returnEdge(this.getAnEpsilonSuccessor(), result) } + + /** + * Gets the result of a call to `method` with this value as the receiver, or the return value of `method` defined on + * an object that can reach this sink. + * + * This is a shorthand for `getMethod(method).getReturn()`. + */ + pragma[inline] + Node getReturn(string method) { + // This predicate is currently not 'inline_late' because 'method' can be an input or output + result = this.getMethod(method).getReturn() + } + + /** + * Gets the `n`th positional argument to this call. + * + * For example, this would get `x` in the following snippet: + * ```ruby + * Foo.bar(x) # API::getTopLevelMember("Foo").getMethod("bar").getArgument(0) + * ``` + */ + pragma[inline] + Node getArgument(int n) { + // This predicate is currently not 'inline_late' because 'n' can be an input or output + Impl::positionalArgumentEdge(this, n, result) + } + + /** + * Gets the given keyword argument to this call. + * + * For example, this would get `x` in the following snippet: + * ```ruby + * Foo.bar(baz: x) # API::getTopLevelMember("Foo").getMethod("bar").getKeywordArgument("baz") + * ``` + */ + pragma[inline] + Node getKeywordArgument(string name) { + // This predicate is currently not 'inline_late' because 'name' can be an input or output + Impl::keywordArgumentEdge(this, name, result) + } + + /** + * Gets the block parameter of a callable that can reach this sink. + * + * For example, this would get the `&blk` in the following snippet: + * ```ruby + * # API::getTopLevelMember("Foo").getMethod("bar").getArgument(0).getBlockParameter() + * Foo.bar(->(&blk) {}) + * end + * ``` + */ + bindingset[this] + pragma[inline_late] + Node getBlockParameter() { Impl::blockParameterEdge(this.getAnEpsilonSuccessor(), result) } + + /** + * Gets the `n`th positional parameter of this callable, or the `n`th positional argument to this call. + * + * Note: for historical reasons, this predicate may refer to an argument of a call, but this may change in the future. + * When referring to an argument, it is recommended to use `getArgument(n)` instead. + */ + pragma[inline] + Node getParameter(int n) { + // This predicate is currently not 'inline_late' because 'n' can be an input or output + Impl::positionalParameterOrArgumentEdge(this.getAnEpsilonSuccessor(), n, result) + } + + /** + * Gets the given keyword parameter of this callable, or keyword argument to this call. + * + * Note: for historical reasons, this predicate may refer to an argument of a call, but this may change in the future. + * When referring to an argument, it is recommended to use `getKeywordArgument(n)` instead. + */ + pragma[inline] + Node getKeywordParameter(string name) { + // This predicate is currently not 'inline_late' because 'name' can be an input or output + Impl::keywordParameterOrArgumentEdge(this.getAnEpsilonSuccessor(), name, result) + } + + /** + * Gets the block argument to this call, or the block parameter of this callable. + * + * Note: this predicate may refer to either an argument or a parameter. When referring to a block parameter, + * it is recommended to use `getBlockParameter()` instead. + * + * For example: + * ```ruby + * Foo.bar do |x| # API::getTopLevelMember("Foo").getMethod("bar").getBlock().getParameter(0) + * end + * ``` + */ + bindingset[this] + pragma[inline_late] + Node getBlock() { Impl::blockParameterOrArgumentEdge(this.getAnEpsilonSuccessor(), result) } + + /** + * Gets the argument passed in argument position `pos` at this call. + */ + pragma[inline] + Node getArgumentAtPosition(DataFlowDispatch::ArgumentPosition pos) { + // This predicate is currently not 'inline_late' because 'pos' can be an input or output + Impl::argumentEdge(pragma[only_bind_out](this), pos, result) // note: no need for epsilon step since 'this' must be a call + } + + /** + * Gets the parameter at position `pos` of this callable. + */ + pragma[inline] + Node getParameterAtPosition(DataFlowDispatch::ParameterPosition pos) { + // This predicate is currently not 'inline_late' because 'pos' can be an input or output + Impl::parameterEdge(this.getAnEpsilonSuccessor(), pos, result) + } + + /** + * Gets a `new` call with this value as the receiver. + */ + bindingset[this] + pragma[inline_late] + DataFlow::ExprNode getAnInstantiation() { result = this.getReturn("new").asSource() } + + /** + * Gets a representative for the `content` of this value. + * + * When possible, it is preferrable to use one of the specialized variants of this predicate, such as `getAnElement`. + * + * Concretely, this gets sources where `content` is read from this value, and as well as sinks where + * `content` is stored onto this value or onto an object that can reach this sink. + */ + pragma[inline] + Node getContent(DataFlow::Content content) { + // This predicate is currently not 'inline_late' because 'content' can be an input or output + Impl::contentEdge(this.getAnEpsilonSuccessor(), content, result) + } + + /** + * Gets a representative for the `contents` of this value. + * + * See `getContent()` for more details. + */ + bindingset[this, contents] + pragma[inline_late] + Node getContents(DataFlow::ContentSet contents) { + // We always use getAStoreContent when generating content edges, and we always use getAReadContent when querying the graph. + result = this.getContent(contents.getAReadContent()) + } + + /** + * Gets a representative for the instance field of the given `name`, which must include the `@` character. + * + * This can be used to find cases where a class accesses the fields used by a base class. + * + * ```ruby + * class A < B + * def m + * @foo # API::getTopLevelMember("B").getInstance().getField("@foo") + * end + * end + * ``` + */ + pragma[inline] + Node getField(string name) { + // This predicate is currently not 'inline_late' because 'name' can be an input or output + Impl::fieldEdge(this.getAnEpsilonSuccessor(), name, result) + } + + /** + * Gets a representative for an arbitrary element of this collection. + * + * For example: + * ```ruby + * Foo.bar.each do |x| # API::getTopLevelMember("Foo").getMethod("bar").getReturn().getAnElement() + * end + * + * Foo.bar[0] # API::getTopLevelMember("Foo").getMethod("bar").getReturn().getAnElement() + * ``` + */ + bindingset[this] + pragma[inline_late] + Node getAnElement() { Impl::elementEdge(this.getAnEpsilonSuccessor(), result) } + + /** + * Gets the data-flow node that gives rise to this node, if any. + */ + DataFlow::Node getInducingNode() { + this = Impl::MkMethodAccessNode(result) or + this = Impl::MkBackwardNode(result, _) or + this = Impl::MkForwardNode(result, _) or + this = Impl::MkSinkNode(result) + } + + /** Gets the location of this node. */ + Location getLocation() { + result = this.getInducingNode().getLocation() + or + exists(DataFlow::ModuleNode mod | + this = Impl::MkModuleObjectDown(mod) + or + this = Impl::MkModuleInstanceUp(mod) + | + result = mod.getLocation() + ) + or + this instanceof RootNode and + result instanceof EmptyLocation + } + + /** + * Gets a textual representation of this element. + */ + string toString() { none() } + + /** + * Gets a node representing a (direct or indirect) subclass of the class represented by this node. + * ```rb + * class A; end + * class B < A; end + * class C < B; end + * ``` + * In the example above, `getMember("A").getASubclass()` will return uses of `A`, `B` and `C`. + */ + pragma[inline] + deprecated Node getASubclass() { result = this } + + /** + * Gets a node representing a direct subclass of the class represented by this node. + * ```rb + * class A; end + * class B < A; end + * class C < B; end + * ``` + * In the example above, `getMember("A").getAnImmediateSubclass()` will return uses of `B` only. + */ + pragma[inline] + deprecated Node getAnImmediateSubclass() { + result = this.asModule().getAnImmediateDescendent().trackModule() + } /** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource()`. */ deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() } @@ -166,326 +647,143 @@ module API { deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() } /** - * Gets a call to a method on the receiver represented by this API component. - */ - pragma[inline] - DataFlow::CallNode getAMethodCall(string method) { result = this.getReturn(method).asSource() } - - /** - * Gets a node representing member `m` of this API component. + * DEPRECATED. API graph nodes are no longer associated with specific paths. * - * For example, a member can be: - * - * - A submodule of a module - * - An attribute of an object - */ - pragma[inline] - Node getMember(string m) { - result = pragma[only_bind_out](this).(Node::Internal).getMemberInternal(m) - } - - /** - * Gets a node representing a member of this API component where the name of the member may - * or may not be known statically. - */ - cached - Node getAMember() { - Impl::forceCachingInSameStage() and - result = this.getASuccessor(Label::member(_)) - } - - /** - * Gets a node representing an instance of this API component, that is, an object whose - * constructor is the function represented by this node. - * - * For example, if this node represents a use of some class `A`, then there might be a node - * representing instances of `A`, typically corresponding to expressions `A.new` at the - * source level. - * - * This predicate may have multiple results when there are multiple constructor calls invoking this API component. - * Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls. - */ - pragma[inline] - Node getInstance() { result = this.getASubclass().getReturn("new") } - - /** - * Gets a node representing a call to `method` on the receiver represented by this node. - */ - pragma[inline] - MethodAccessNode getMethod(string method) { - result = pragma[only_bind_out](this).(Node::Internal).getMethodInternal(method) - } - - /** - * Gets a node representing the result of this call. - */ - pragma[inline] - Node getReturn() { result = pragma[only_bind_out](this).(Node::Internal).getReturnInternal() } - - /** - * Gets a node representing the result of calling a method on the receiver represented by this node. - */ - pragma[inline] - Node getReturn(string method) { result = this.getMethod(method).getReturn() } - - /** Gets an API node representing the `n`th positional parameter. */ - cached - Node getParameter(int n) { - Impl::forceCachingInSameStage() and - result = this.getASuccessor(Label::parameter(n)) - } - - /** Gets an API node representing the given keyword parameter. */ - cached - Node getKeywordParameter(string name) { - Impl::forceCachingInSameStage() and - result = this.getASuccessor(Label::keywordParameter(name)) - } - - /** Gets an API node representing the block parameter. */ - cached - Node getBlock() { - Impl::forceCachingInSameStage() and - result = this.getASuccessor(Label::blockParameter()) - } - - /** - * Gets a `new` call to the function represented by this API component. - */ - pragma[inline] - DataFlow::ExprNode getAnInstantiation() { result = this.getInstance().asSource() } - - /** - * Gets a node representing a (direct or indirect) subclass of the class represented by this node. - * ```rb - * class A; end - * class B < A; end - * class C < B; end - * ``` - * In the example above, `getMember("A").getASubclass()` will return uses of `A`, `B` and `C`. - */ - Node getASubclass() { result = this.getAnImmediateSubclass*() } - - /** - * Gets a node representing a direct subclass of the class represented by this node. - * ```rb - * class A; end - * class B < A; end - * class C < B; end - * ``` - * In the example above, `getMember("A").getAnImmediateSubclass()` will return uses of `B` only. - */ - cached - Node getAnImmediateSubclass() { - Impl::forceCachingInSameStage() and result = this.getASuccessor(Label::subclass()) - } - - /** - * Gets a node representing the `content` stored on the base object. - */ - cached - Node getContent(DataFlow::Content content) { - Impl::forceCachingInSameStage() and - result = this.getASuccessor(Label::content(content)) - } - - /** - * Gets a node representing the `contents` stored on the base object. - */ - pragma[inline] - Node getContents(DataFlow::ContentSet contents) { - // We always use getAStoreContent when generating the graph, and we always use getAReadContent when querying the graph. - result = this.getContent(contents.getAReadContent()) - } - - /** Gets a node representing the instance field of the given `name`, which must include the `@` character. */ - cached - Node getField(string name) { - Impl::forceCachingInSameStage() and - result = this.getContent(DataFlowPrivate::TFieldContent(name)) - } - - /** Gets a node representing an element of this collection (known or unknown). */ - cached - Node getAnElement() { - Impl::forceCachingInSameStage() and - result = this.getContents(any(DataFlow::ContentSet set | set.isAnyElement())) - } - - /** * Gets a string representation of the lexicographically least among all shortest access paths * from the root to this node. */ - string getPath() { - result = min(string p | p = this.getAPath(Impl::distanceFromRoot(this)) | p) - } + deprecated string getPath() { none() } /** + * DEPRECATED. Use label-specific predicates in this class, such as `getMember`, instead of using `getASuccessor`. + * * Gets a node such that there is an edge in the API graph between this node and the other * one, and that edge is labeled with `lbl`. */ - Node getASuccessor(Label::ApiLabel lbl) { Impl::edge(this, lbl, result) } + pragma[inline] + deprecated Node getASuccessor(Label::ApiLabel lbl) { + labelledEdge(this.getAnEpsilonSuccessor(), lbl, result) + } /** + * DEPRECATED. API graphs no longer support backward traversal of edges. If possible use `.backtrack()` to get + * a node intended for backtracking. + * * Gets a node such that there is an edge in the API graph between that other node and * this one, and that edge is labeled with `lbl` */ - Node getAPredecessor(Label::ApiLabel lbl) { this = result.getASuccessor(lbl) } + deprecated Node getAPredecessor(Label::ApiLabel lbl) { this = result.getASuccessor(lbl) } /** + * DEPRECATED. API graphs no longer support backward traversal of edges. If possible use `.backtrack()` to get + * a node intended for backtracking. + * * Gets a node such that there is an edge in the API graph between this node and the other * one. */ - Node getAPredecessor() { result = this.getAPredecessor(_) } + deprecated Node getAPredecessor() { result = this.getAPredecessor(_) } /** * Gets a node such that there is an edge in the API graph between that other node and * this one. */ - Node getASuccessor() { result = this.getASuccessor(_) } + pragma[inline] + deprecated Node getASuccessor() { result = this.getASuccessor(_) } - /** - * Gets the data-flow node that gives rise to this node, if any. - */ - DataFlow::Node getInducingNode() { - this = Impl::MkUse(result) - or - this = Impl::MkDef(result) - or - this = Impl::MkMethodAccessNode(result) - } + /** DEPRECATED. API graphs are no longer associated with a depth. */ + deprecated int getDepth() { none() } - /** Gets the location of this node. */ - Location getLocation() { - result = this.getInducingNode().getLocation() - or - exists(DataFlow::ModuleNode mod | - this = Impl::MkModuleObject(mod) and - result = mod.getLocation() - ) - or - // For nodes that do not have a meaningful location, `path` is the empty string and all other - // parameters are zero. - not exists(this.getInducingNode()) and - result instanceof EmptyLocation - } - - /** - * Gets a textual representation of this element. - */ - string toString() { none() } - - /** - * Gets a path of the given `length` from the root to this node. - */ - private string getAPath(int length) { - this instanceof Impl::MkRoot and - length = 0 and - result = "" - or - exists(Node pred, Label::ApiLabel lbl, string predpath | - Impl::edge(pred, lbl, this) and - predpath = pred.getAPath(length - 1) and - exists(string dot | if length = 1 then dot = "" else dot = "." | - result = predpath + dot + lbl and - // avoid producing strings longer than 1MB - result.length() < 1000 * 1000 - ) - ) and - length in [1 .. Impl::distanceFromRoot(this)] - } - - /** Gets the shortest distance from the root to this node in the API graph. */ - int getDepth() { result = Impl::distanceFromRoot(this) } + pragma[inline] + private Node getAnEpsilonSuccessor() { result = getAnEpsilonSuccessorInline(this) } } - /** Companion module to the `Node` class. */ - module Node { - /** - * INTERNAL USE ONLY. - * - * An API node, with some internal predicates exposed. - */ - class Internal extends Node { - /** - * INTERNAL USE ONLY. - * - * Same as `asSource()` but without join-order hints. - */ - cached - DataFlow::LocalSourceNode asSourceInternal() { - Impl::forceCachingInSameStage() and - Impl::use(this, result) - } + /** DEPRECATED. Use `API::root()` to access the root node. */ + deprecated class Root = RootNode; - /** - * Same as `getMember` but without join-order hints. - */ - cached - Node getMemberInternal(string m) { - Impl::forceCachingInSameStage() and - result = this.getASuccessor(Label::member(m)) - } + /** DEPRECATED. A node corresponding to the use of an API component. */ + deprecated class Use = ForwardNode; - /** - * Same as `getMethod` but without join-order hints. - */ - cached - MethodAccessNode getMethodInternal(string method) { - Impl::forceCachingInSameStage() and - result = this.getASubclass().getASuccessor(Label::method(method)) - } - - /** - * INTERNAL USE ONLY. - * - * Same as `getReturn()` but without join-order hints. - */ - cached - Node getReturnInternal() { - Impl::forceCachingInSameStage() and result = this.getASuccessor(Label::return()) - } - } - } - - bindingset[node] - pragma[inline_late] - private DataFlow::Node getAValueReachableFromSourceInline(Node node) { - exists(DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode dst | - Impl::use(node, pragma[only_bind_into](src)) and - pragma[only_bind_into](dst) = Impl::trackUseNode(src) and - dst.flowsTo(result) - ) - } + /** DEPRECATED. A node corresponding to a value escaping into an API component. */ + deprecated class Def = SinkNode; /** The root node of an API graph. */ - class Root extends Node, Impl::MkRoot { - override string toString() { result = "root" } + private class RootNode extends Node, Impl::MkRoot { + override string toString() { result = "Root()" } } - private string tryGetPath(Node node) { - result = node.getPath() - or - not exists(node.getPath()) and - result = "with no path" + /** A node representing a given type-tracking state when tracking forwards. */ + private class ForwardNode extends Node, Impl::MkForwardNode { + private DataFlow::LocalSourceNode node; + private TypeTracker tracker; + + ForwardNode() { this = Impl::MkForwardNode(node, tracker) } + + override string toString() { + if tracker.start() + then result = "ForwardNode(" + node + ")" + else result = "ForwardNode(" + node + ", " + tracker + ")" + } } - /** A node corresponding to the use of an API component. */ - class Use extends Node, Impl::MkUse { - override string toString() { result = "Use " + tryGetPath(this) } + /** A node representing a given type-tracking state when tracking backwards. */ + private class BackwardNode extends Node, Impl::MkBackwardNode { + private DataFlow::LocalSourceNode node; + private TypeTracker tracker; + + BackwardNode() { this = Impl::MkBackwardNode(node, tracker) } + + override string toString() { + if tracker.start() + then result = "BackwardNode(" + node + ")" + else result = "BackwardNode(" + node + ", " + tracker + ")" + } } - /** A node corresponding to a value escaping into an API component. */ - class Def extends Node, Impl::MkDef { - override string toString() { result = "Def " + tryGetPath(this) } + /** A node representing a module/class object with epsilon edges to its descendents. */ + private class ModuleObjectDownNode extends Node, Impl::MkModuleObjectDown { + /** Gets the module represented by this API node. */ + DataFlow::ModuleNode getModule() { this = Impl::MkModuleObjectDown(result) } + + override string toString() { result = "ModuleObjectDown(" + this.getModule() + ")" } + } + + /** A node representing a module/class object with epsilon edges to its ancestors. */ + private class ModuleObjectUpNode extends Node, Impl::MkModuleObjectUp { + /** Gets the module represented by this API node. */ + DataFlow::ModuleNode getModule() { this = Impl::MkModuleObjectUp(result) } + + override string toString() { result = "ModuleObjectUp(" + this.getModule() + ")" } + } + + /** A node representing instances of a module/class with epsilon edges to its ancestors. */ + private class ModuleInstanceUpNode extends Node, Impl::MkModuleInstanceUp { + /** Gets the module whose instances are represented by this API node. */ + DataFlow::ModuleNode getModule() { this = Impl::MkModuleInstanceUp(result) } + + override string toString() { result = "ModuleInstanceUp(" + this.getModule() + ")" } + } + + /** A node representing instances of a module/class with epsilon edges to its descendents. */ + private class ModuleInstanceDownNode extends Node, Impl::MkModuleInstanceDown { + /** Gets the module whose instances are represented by this API node. */ + DataFlow::ModuleNode getModule() { this = Impl::MkModuleInstanceDown(result) } + + override string toString() { result = "ModuleInstanceDown(" + this.getModule() + ")" } } /** A node corresponding to the method being invoked at a method call. */ class MethodAccessNode extends Node, Impl::MkMethodAccessNode { - override string toString() { result = "MethodAccessNode " + tryGetPath(this) } + override string toString() { result = "MethodAccessNode(" + this.asCall() + ")" } + } - /** Gets the call node corresponding to this method access. */ - DataFlow::CallNode getCallNode() { this = Impl::MkMethodAccessNode(result) } + /** + * A node corresponding to an argument, right-hand side of a store, or return value from a callable. + * + * Such a node may serve as the starting-point of backtracking, and has epsilon edges going to + * the backward nodes corresponding to `getALocalSource`. + */ + private class SinkNode extends Node, Impl::MkSinkNode { + override string toString() { result = "SinkNode(" + this.getInducingNode() + ")" } } /** @@ -499,6 +797,8 @@ module API { * additional entry points may be added by extending this class. */ abstract class EntryPoint extends string { + // Note: this class can be deprecated in Ruby, but is still referenced by shared code in ApiGraphModels.qll, + // where it can't be removed since other languages are still dependent on the EntryPoint class. bindingset[this] EntryPoint() { any() } @@ -518,7 +818,7 @@ module API { DataFlow::CallNode getACall() { none() } /** Gets an API-node for this entry point. */ - API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) } + API::Node getANode() { Impl::entryPointEdge(this, result) } } // Ensure all entry points are imported from ApiGraphs.qll @@ -527,88 +827,307 @@ module API { } /** Gets the root node. */ - Root root() { any() } + Node root() { result instanceof RootNode } /** - * Gets a node corresponding to a top-level member `m` (typically a module). + * Gets an access to the top-level constant `name`. * - * This is equivalent to `root().getAMember("m")`. - * - * Note: You should only use this predicate for top level modules or classes. If you want nodes corresponding to a nested module or class, - * you should use `.getMember` on the parent module/class. For example, for nodes corresponding to the class `Gem::Version`, + * To access nested constants, use `getMember()` on the resulting node. For example, for nodes corresponding to the class `Gem::Version`, * use `getTopLevelMember("Gem").getMember("Version")`. */ - cached - Node getTopLevelMember(string m) { - Impl::forceCachingInSameStage() and result = root().(Node::Internal).getMemberInternal(m) - } + pragma[inline] + Node getTopLevelMember(string name) { Impl::topLevelMember(name, result) } /** - * Provides the actual implementation of API graphs, cached for performance. - * - * Ideally, we'd like nodes to correspond to (global) access paths, with edge labels - * corresponding to extending the access path by one element. We also want to be able to map - * nodes to their definitions and uses in the data-flow graph, and this should happen modulo - * (inter-procedural) data flow. - * - * This, however, is not easy to implement, since access paths can have unbounded length - * and we need some way of recognizing cycles to avoid non-termination. Unfortunately, expressing - * a condition like "this node hasn't been involved in constructing any predecessor of - * this node in the API graph" without negative recursion is tricky. - * - * So instead most nodes are directly associated with a data-flow node, representing - * either a use or a definition of an API component. This ensures that we only have a finite - * number of nodes. However, we can now have multiple nodes with the same access - * path, which are essentially indistinguishable for a client of the API. - * - * On the other hand, a single node can have multiple access paths (which is, of - * course, unavoidable). We pick as canonical the alphabetically least access path with - * shortest length. + * Gets an unqualified call at the top-level with the given method name. */ - cached - private module Impl { - cached - predicate forceCachingInSameStage() { any() } + pragma[inline] + MethodAccessNode getTopLevelCall(string name) { Impl::toplevelCall(name, result) } - cached - predicate forceCachingBackref() { - 1 = 1 + pragma[nomagic] + private predicate isReachable(DataFlow::LocalSourceNode node, TypeTracker t) { + t.start() and exists(node) + or + exists(DataFlow::LocalSourceNode prev, TypeTracker t2 | + isReachable(prev, t2) and + node = prev.track(t2, t) and + notSelfParameter(node) + ) + } + + bindingset[node] + pragma[inline_late] + private predicate notSelfParameter(DataFlow::Node node) { + not node instanceof DataFlow::SelfParameterNode + } + + private module SharedArg implements ApiGraphSharedSig { + class ApiNode = Node; + + ApiNode getForwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { + result = Impl::MkForwardNode(node, t) + } + + ApiNode getBackwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { + result = Impl::MkBackwardNode(node, t) + } + + ApiNode getSinkNode(DataFlow::Node node) { result = Impl::MkSinkNode(node) } + + pragma[nomagic] + predicate specificEpsilonEdge(ApiNode pred, ApiNode succ) { + exists(DataFlow::ModuleNode mod | + moduleReferenceEdge(mod, pred, succ) + or + moduleInheritanceEdge(mod, pred, succ) + or + pred = getForwardEndNode(getSuperClassNode(mod)) and + succ = Impl::MkModuleObjectDown(mod) + ) or - exists(getTopLevelMember(_)) + implicitCallEdge(pred, succ) or - exists( - any(Node n) - .(Node::Internal) - .getMemberInternal("foo") - .getAMember() - .(Node::Internal) - .getMethodInternal("foo") - .(Node::Internal) - .getReturnInternal() - .getParameter(0) - .getKeywordParameter("foo") - .getBlock() - .getAnImmediateSubclass() - .getContent(_) - .getField(_) - .getAnElement() - .(Node::Internal) - .asSourceInternal() + exists(DataFlow::HashLiteralNode splat | hashSplatEdge(splat, pred, succ)) + } + + /** + * Holds if the epsilon edge `pred -> succ` should be generated, to handle inheritance relations of `mod`. + */ + pragma[inline] + private predicate moduleInheritanceEdge(DataFlow::ModuleNode mod, ApiNode pred, ApiNode succ) { + pred = Impl::MkModuleObjectDown(mod) and + succ = Impl::MkModuleObjectDown(mod.getAnImmediateDescendent()) + or + pred = Impl::MkModuleInstanceDown(mod) and + succ = Impl::MkModuleInstanceDown(mod.getAnImmediateDescendent()) + or + exists(DataFlow::ModuleNode ancestor | + ancestor = mod.getAnImmediateAncestor() and + // Restrict flow back to Object to avoid spurious flow for methods that happen + // to exist on Object, such as top-level methods. + not ancestor.getQualifiedName() = "Object" + | + pred = Impl::MkModuleInstanceUp(mod) and + succ = Impl::MkModuleInstanceUp(ancestor) + or + pred = Impl::MkModuleObjectUp(mod) and + succ = Impl::MkModuleObjectUp(ancestor) + ) + or + // Due to multiple inheritance, allow upwards traversal after downward traversal, + // so we can detect calls sideways in the hierarchy. + // Note that a similar case does not exist for ModuleObject since singleton methods are only inherited + // from the superclass, and there can only be one superclass. + pred = Impl::MkModuleInstanceDown(mod) and + succ = Impl::MkModuleInstanceUp(mod) + } + + /** + * Holds if the epsilon `pred -> succ` should be generated, to associate `mod` with its references in the codebase. + */ + bindingset[mod] + pragma[inline_late] + private predicate moduleReferenceEdge(DataFlow::ModuleNode mod, ApiNode pred, ApiNode succ) { + pred = Impl::MkModuleObjectDown(mod) and + succ = getForwardStartNode(getAModuleReference(mod)) + or + pred = getBackwardEndNode(getAModuleReference(mod)) and + ( + succ = Impl::MkModuleObjectUp(mod) + or + succ = Impl::MkModuleObjectDown(mod) + ) + or + pred = Impl::MkModuleInstanceUp(mod) and + succ = getAModuleInstanceUseNode(mod) + or + pred = getAModuleInstanceDefNode(mod) and + succ = Impl::MkModuleInstanceUp(mod) + or + pred = getAModuleDescendentInstanceDefNode(mod) and + succ = Impl::MkModuleInstanceDown(mod) + } + + /** + * Holds if the epsilon step `pred -> succ` should be generated to account for the fact that `getMethod("call")` + * may be omitted when dealing with blocks, lambda, or procs. + * + * For example, a block may be invoked by a `yield`, or can be converted to a proc and then invoked via `.call`. + * To simplify this, the implicit proc conversion is seen as a no-op and the `.call` is omitted. + */ + pragma[nomagic] + private predicate implicitCallEdge(ApiNode pred, ApiNode succ) { + // Step from &block parameter to yield call without needing `getMethod("call")`. + exists(DataFlow::MethodNode method | + pred = getForwardEndNode(method.getBlockParameter()) and + succ = Impl::MkMethodAccessNode(method.getABlockCall()) + ) + or + // Step from x -> x.call (the call itself, not its return value), without needing `getMethod("call")`. + exists(DataFlow::CallNode call | + call.getMethodName() = "call" and + pred = getForwardEndNode(getALocalSourceStrict(call.getReceiver())) and + succ = Impl::MkMethodAccessNode(call) + ) + or + exists(DataFlow::ModuleNode mod | + // Step from module/class to its own `call` method without needing `getMethod("call")`. + (pred = Impl::MkModuleObjectDown(mod) or pred = Impl::MkModuleObjectUp(mod)) and + succ = getBackwardEndNode(mod.getOwnSingletonMethod("call")) + or + pred = Impl::MkModuleInstanceUp(mod) and + succ = getBackwardEndNode(mod.getOwnInstanceMethod("call")) + ) + or + // Step through callable wrappers like `proc` and `lambda` calls. + exists(DataFlow::Node node | + pred = getBackwardEndNode(node) and + succ = getBackwardStartNode(node.asCallable()) ) } + pragma[nomagic] + private DataFlow::Node getHashSplatArgument(DataFlow::HashLiteralNode literal) { + result = DataFlowPrivate::TSynthHashSplatArgumentNode(literal.asExpr()) + } + + /** + * Holds if the epsilon edge `pred -> succ` should be generated to account for the members of a hash literal. + * + * This currently exists because hash literals are desugared to `Hash.[]` calls, whose summary relies on `WithContent`. + * However, `contentEdge` does not currently generate edges for `WithContent` steps. + */ + bindingset[literal] + pragma[inline_late] + private predicate hashSplatEdge(DataFlow::HashLiteralNode literal, ApiNode pred, ApiNode succ) { + exists(TypeTracker t | + pred = Impl::MkForwardNode(getALocalSourceStrict(getHashSplatArgument(literal)), t) and + succ = Impl::MkForwardNode(pragma[only_bind_out](literal), pragma[only_bind_out](t)) + or + succ = Impl::MkBackwardNode(getALocalSourceStrict(getHashSplatArgument(literal)), t) and + pred = Impl::MkBackwardNode(pragma[only_bind_out](literal), pragma[only_bind_out](t)) + ) + } + + pragma[nomagic] + private DataFlow::LocalSourceNode getAModuleReference(DataFlow::ModuleNode mod) { + result = mod.getAnImmediateReference() + or + mod.getAnAncestor().getAnOwnInstanceSelf() = getANodeReachingClassCall(result) + } + + /** + * Gets an API node that may refer to an instance of `mod`. + */ + bindingset[mod] + pragma[inline_late] + private ApiNode getAModuleInstanceUseNode(DataFlow::ModuleNode mod) { + result = getForwardStartNode(mod.getAnOwnInstanceSelf()) + } + + /** + * Gets a node that can be backtracked to an instance of `mod`. + */ + bindingset[mod] + pragma[inline_late] + private ApiNode getAModuleInstanceDefNode(DataFlow::ModuleNode mod) { + result = getBackwardEndNode(mod.getAnImmediateReference().getAMethodCall("new")) + } + + /** + * Gets a node that can be backtracked to an instance of `mod` or any of its descendents. + */ + bindingset[mod] + pragma[inline_late] + private ApiNode getAModuleDescendentInstanceDefNode(DataFlow::ModuleNode mod) { + result = getBackwardEndNode(mod.getAnOwnInstanceSelf()) + } + + /** + * Holds if `superclass` is the superclass of `mod`. + */ + pragma[nomagic] + private DataFlow::LocalSourceNode getSuperClassNode(DataFlow::ModuleNode mod) { + result.getALocalUse().asExpr().getExpr() = + mod.getADeclaration().(ClassDeclaration).getSuperclassExpr() + } + + /** Gets a node that can reach the receiver of the given `.class` call. */ + private DataFlow::LocalSourceNode getANodeReachingClassCall( + DataFlow::CallNode call, TypeBackTracker t + ) { + t.start() and + call.getMethodName() = "class" and + result = getALocalSourceStrict(call.getReceiver()) + or + exists(DataFlow::LocalSourceNode prev, TypeBackTracker t2 | + prev = getANodeReachingClassCall(call, t2) and + result = prev.backtrack(t2, t) and + notSelfParameter(prev) + ) + } + + /** Gets a node that can reach the receiver of the given `.class` call. */ + private DataFlow::LocalSourceNode getANodeReachingClassCall(DataFlow::CallNode call) { + result = getANodeReachingClassCall(call, TypeBackTracker::end()) + } + } + + /** INTERNAL USE ONLY. */ + module Internal { + private module Shared = ApiGraphShared; + + import Shared + + /** Gets the API node corresponding to the module/class object for `mod`. */ + bindingset[mod] + pragma[inline_late] + Node getModuleNode(DataFlow::ModuleNode mod) { result = Impl::MkModuleObjectDown(mod) } + + /** Gets the API node corresponding to instances of `mod`. */ + bindingset[mod] + pragma[inline_late] + Node getModuleInstance(DataFlow::ModuleNode mod) { result = getModuleNode(mod).getInstance() } + } + + private import Internal + import Internal::Public + + cached + private module Impl { cached newtype TApiNode = /** The root of the API graph. */ MkRoot() or /** The method accessed at `call`, synthetically treated as a separate object. */ - MkMethodAccessNode(DataFlow::CallNode call) { isUse(call) } or - /** A use of an API member at the node `nd`. */ - MkUse(DataFlow::Node nd) { isUse(nd) } or - /** A value that escapes into an external library at the node `nd` */ - MkDef(DataFlow::Node nd) { isDef(nd) } or - /** A module object seen as a use node. */ - MkModuleObject(DataFlow::ModuleNode mod) + MkMethodAccessNode(DataFlow::CallNode call) or + /** The module object `mod` with epsilon edges to its ancestors. */ + MkModuleObjectUp(DataFlow::ModuleNode mod) { not mod.getQualifiedName() = "Object" } or + /** The module object `mod` with epsilon edges to its descendents. */ + MkModuleObjectDown(DataFlow::ModuleNode mod) { not mod.getQualifiedName() = "Object" } or + /** Instances of `mod` with epsilon edges to its ancestors. */ + MkModuleInstanceUp(DataFlow::ModuleNode mod) { not mod.getQualifiedName() = "Object" } or + /** Instances of `mod` with epsilon edges to its descendents, and to its upward node. */ + MkModuleInstanceDown(DataFlow::ModuleNode mod) { not mod.getQualifiedName() = "Object" } or + /** Intermediate node for following forward data flow. */ + MkForwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or + /** Intermediate node for following backward data flow. */ + MkBackwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or + MkSinkNode(DataFlow::Node node) { needsSinkNode(node) } + + private predicate needsSinkNode(DataFlow::Node node) { + node instanceof DataFlowPrivate::ArgumentNode + or + TypeTrackerSpecific::basicStoreStep(node, _, _) + or + node = any(DataFlow::CallableNode callable).getAReturnNode() + or + node = any(EntryPoint e).getASink() + } + + bindingset[e] + pragma[inline_late] + private DataFlow::Node getNodeFromExpr(Expr e) { result.asExpr().getExpr() = e } private string resolveTopLevel(ConstantReadAccess read) { result = read.getModule().getQualifiedName() and @@ -616,300 +1135,288 @@ module API { } /** - * Holds if `ref` is a use of a node that should have an incoming edge from the root - * node labeled `lbl` in the API graph (not including those from API::EntryPoint). + * Holds `pred` should have a member edge to `mod`. */ pragma[nomagic] - private predicate useRoot(Label::ApiLabel lbl, DataFlow::Node ref) { - exists(string name, ConstantReadAccess read | - read = ref.asExpr().getExpr() and - lbl = Label::member(read.getName()) + private predicate moduleScope(DataFlow::ModuleNode mod, Node pred, string name) { + exists(Namespace namespace | + name = namespace.getName() and + namespace = mod.getADeclaration() | - name = resolveTopLevel(read) + exists(DataFlow::Node scopeNode | + scopeNode.asExpr().getExpr() = namespace.getScopeExpr() and + pred = getForwardEndNode(getALocalSourceStrict(scopeNode)) + ) or - name = read.getName() and - not exists(resolveTopLevel(read)) and - not exists(read.getScopeExpr()) + not exists(namespace.getScopeExpr()) and + if namespace.hasGlobalScope() or namespace.getEnclosingModule() instanceof Toplevel + then pred = MkRoot() + else pred = MkModuleObjectDown(namespace.getEnclosingModule().getModule()) ) } - /** - * Holds if `ref` is a use of a node that should have an incoming edge labeled `lbl`, - * from a use node that flows to `node`. - */ - private predicate useStep(Label::ApiLabel lbl, DataFlow::Node node, DataFlow::Node ref) { - // // Referring to an attribute on a node that is a use of `base`: - // pred = `Rails` part of `Rails::Whatever` - // lbl = `Whatever` - // ref = `Rails::Whatever` - exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read | - not exists(resolveTopLevel(read)) and - node.asExpr() = c.getScopeExpr() and - lbl = Label::member(read.getName()) and - ref.asExpr() = c and - read = c.getExpr() - ) - or - exists(TypeTrackerSpecific::TypeTrackerContent c | - TypeTrackerSpecific::basicLoadStep(node, ref, c) and - lbl = Label::content(c.getAStoreContent()) and - not c.isSingleton(any(DataFlow::Content::AttributeNameContent k)) - ) - // note: method calls are not handled here as there is no DataFlow::Node for the intermediate MkMethodAccessNode API node - } - - /** - * Holds if `rhs` is a definition of a node that should have an incoming edge labeled `lbl`, - * from a def node that is reachable from `node`. - */ - private predicate defStep(Label::ApiLabel lbl, DataFlow::Node node, DataFlow::Node rhs) { - exists(TypeTrackerSpecific::TypeTrackerContent c | - TypeTrackerSpecific::basicStoreStep(rhs, node, c) and - lbl = Label::content(c.getAStoreContent()) - ) - } - - pragma[nomagic] - private predicate isUse(DataFlow::Node nd) { - useRoot(_, nd) - or - exists(DataFlow::Node node | - useCandFwd().flowsTo(node) and - useStep(_, node, nd) - ) - or - useCandFwd().flowsTo(nd.(DataFlow::CallNode).getReceiver()) - or - parameterStep(_, defCand(), nd) - or - nd = any(EntryPoint entry).getASource() - or - nd = any(EntryPoint entry).getACall() - } - - /** - * Holds if `ref` is a use of node `nd`. - */ cached - predicate use(TApiNode nd, DataFlow::Node ref) { - nd = MkUse(ref) + predicate memberEdge(Node pred, string name, Node succ) { + exists(ConstantReadAccess read | succ = getForwardStartNode(getNodeFromExpr(read)) | + name = resolveTopLevel(read) and + pred = MkRoot() + or + not exists(resolveTopLevel(read)) and + not exists(read.getScopeExpr()) and + name = read.getName() and + pred = MkRoot() + or + pred = getForwardEndNode(getALocalSourceStrict(getNodeFromExpr(read.getScopeExpr()))) and + name = read.getName() + ) or exists(DataFlow::ModuleNode mod | - nd = MkModuleObject(mod) and - ref = mod.getAnImmediateReference() + moduleScope(mod, pred, name) and + (succ = MkModuleObjectDown(mod) or succ = MkModuleObjectUp(mod)) ) } - /** - * Holds if `rhs` is a RHS of node `nd`. - */ cached - predicate def(TApiNode nd, DataFlow::Node rhs) { nd = MkDef(rhs) } + predicate topLevelMember(string name, Node node) { memberEdge(root(), name, node) } - /** Gets a node reachable from a use-node. */ - private DataFlow::LocalSourceNode useCandFwd(TypeTracker t) { - t.start() and - isUse(result) - or - exists(TypeTracker t2 | result = useCandFwd(t2).track(t2, t)) - } - - /** Gets a node reachable from a use-node. */ - private DataFlow::LocalSourceNode useCandFwd() { result = useCandFwd(TypeTracker::end()) } - - private predicate isDef(DataFlow::Node rhs) { - // If a call node is relevant as a use-node, treat its arguments as def-nodes - argumentStep(_, useCandFwd(), rhs) - or - defStep(_, defCand(), rhs) - or - rhs = any(EntryPoint entry).getASink() - } - - /** Gets a data flow node that flows to the RHS of a def-node. */ - private DataFlow::LocalSourceNode defCand(TypeBackTracker t) { - t.start() and - exists(DataFlow::Node rhs | - isDef(rhs) and - result = rhs.getALocalSource() - ) - or - exists(TypeBackTracker t2 | result = defCand(t2).backtrack(t2, t)) - } - - /** Gets a data flow node that flows to the RHS of a def-node. */ - private DataFlow::LocalSourceNode defCand() { result = defCand(TypeBackTracker::end()) } - - /** - * Holds if there should be a `lbl`-edge from the given call to an argument. - */ - pragma[nomagic] - private predicate argumentStep( - Label::ApiLabel lbl, DataFlow::CallNode call, DataFlowPrivate::ArgumentNode argument - ) { - exists(DataFlowDispatch::ArgumentPosition argPos | - argument.sourceArgumentOf(call.asExpr(), argPos) and - lbl = Label::getLabelFromArgumentPosition(argPos) - ) - } - - /** - * Holds if there should be a `lbl`-edge from the given callable to a parameter. - */ - pragma[nomagic] - private predicate parameterStep( - Label::ApiLabel lbl, DataFlow::Node callable, DataFlowPrivate::ParameterNodeImpl paramNode - ) { - exists(DataFlowDispatch::ParameterPosition paramPos | - paramNode.isSourceParameterOf(callable.asExpr().getExpr(), paramPos) and - lbl = Label::getLabelFromParameterPosition(paramPos) - ) - } - - /** - * Gets a data-flow node to which `src`, which is a use of an API-graph node, flows. - * - * The flow from `src` to the returned node may be inter-procedural. - */ - private DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src, TypeTracker t) { - result = src and - isUse(src) and - t.start() - or - exists(TypeTracker t2 | - result = trackUseNode(src, t2).track(t2, t) and - not result instanceof DataFlow::SelfParameterNode - ) - } - - /** - * Gets a data-flow node to which `src`, which is a use of an API-graph node, flows. - * - * The flow from `src` to the returned node may be inter-procedural. - */ cached - DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) { - result = trackUseNode(src, TypeTracker::end()) - } - - /** Gets a data flow node reaching the RHS of the given def node. */ - private DataFlow::LocalSourceNode trackDefNode(DataFlow::Node rhs, TypeBackTracker t) { - t.start() and - isDef(rhs) and - result = rhs.getALocalSource() - or - exists(TypeBackTracker t2, DataFlow::LocalSourceNode mid | - mid = trackDefNode(rhs, t2) and - not mid instanceof DataFlow::SelfParameterNode and - result = mid.backtrack(t2, t) + predicate toplevelCall(string name, Node node) { + exists(DataFlow::CallNode call | + call.asExpr().getExpr().getCfgScope() instanceof Toplevel and + call.getMethodName() = name and + node = MkMethodAccessNode(call) ) } - /** Gets a data flow node reaching the RHS of the given def node. */ cached - DataFlow::LocalSourceNode trackDefNode(DataFlow::Node rhs) { - result = trackDefNode(rhs, TypeBackTracker::end()) - } + predicate anyMemberEdge(Node pred, Node succ) { memberEdge(pred, _, succ) } - pragma[nomagic] - private predicate useNodeReachesReceiver(DataFlow::Node use, DataFlow::CallNode call) { - trackUseNode(use).flowsTo(call.getReceiver()) - } - - /** - * Holds if `superclass` is the superclass of `mod`. - */ - pragma[nomagic] - private predicate superclassNode(DataFlow::ModuleNode mod, DataFlow::Node superclass) { - superclass.asExpr().getExpr() = mod.getADeclaration().(ClassDeclaration).getSuperclassExpr() - } - - /** - * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. - */ cached - predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) { - /* Every node that is a use of an API component is itself added to the API graph. */ - exists(DataFlow::LocalSourceNode ref | succ = MkUse(ref) | - pred = MkRoot() and - useRoot(lbl, ref) + predicate methodEdge(Node pred, string name, Node succ) { + exists(DataFlow::ModuleNode mod, DataFlow::CallNode call | + // Treat super calls as if they were calls to the module object/instance. + succ = MkMethodAccessNode(call) and + name = call.getMethodName() + | + pred = MkModuleObjectDown(mod) and + call = mod.getAnOwnSingletonMethod().getASuperCall() or - exists(DataFlow::Node node, DataFlow::Node src | - use(pred, src) and - trackUseNode(src).flowsTo(node) and - useStep(lbl, node, ref) - ) - or - exists(DataFlow::Node callback | - def(pred, callback) and - parameterStep(lbl, trackDefNode(callback), ref) - ) - ) - or - exists(DataFlow::Node predNode, DataFlow::Node succNode | - def(pred, predNode) and - succ = MkDef(succNode) and - defStep(lbl, trackDefNode(predNode), succNode) - ) - or - exists(DataFlow::Node predNode, DataFlow::Node superclassNode, DataFlow::ModuleNode mod | - use(pred, predNode) and - trackUseNode(predNode).flowsTo(superclassNode) and - superclassNode(mod, superclassNode) and - succ = MkModuleObject(mod) and - lbl = Label::subclass() + pred = MkModuleInstanceUp(mod) and + call = mod.getAnOwnInstanceMethod().getASuperCall() ) or exists(DataFlow::CallNode call | // from receiver to method call node - exists(DataFlow::Node receiver | - use(pred, receiver) and - useNodeReachesReceiver(receiver, call) and - lbl = Label::method(call.getMethodName()) and - succ = MkMethodAccessNode(call) - ) - or - // from method call node to return and arguments - pred = MkMethodAccessNode(call) and - ( - lbl = Label::return() and - succ = MkUse(call) - or - exists(DataFlow::Node rhs | - argumentStep(lbl, call, rhs) and - succ = MkDef(rhs) - ) - ) + pred = getForwardEndNode(getALocalSourceStrict(call.getReceiver())) and + succ = MkMethodAccessNode(call) and + name = call.getMethodName() ) or - exists(EntryPoint entry | - pred = root() and - lbl = Label::entryPoint(entry) - | - succ = MkDef(entry.getASink()) + exists(DataFlow::ModuleNode mod | + (pred = MkModuleObjectDown(mod) or pred = MkModuleObjectUp(mod)) and + succ = getBackwardStartNode(mod.getOwnSingletonMethod(name)) or - succ = MkUse(entry.getASource()) - or - succ = MkMethodAccessNode(entry.getACall()) + pred = MkModuleInstanceUp(mod) and + succ = getBackwardStartNode(mod.getOwnInstanceMethod(name)) ) } - /** - * Holds if there is an edge from `pred` to `succ` in the API graph. - */ - private predicate edge(TApiNode pred, TApiNode succ) { edge(pred, _, succ) } - - /** Gets the shortest distance from the root to `nd` in the API graph. */ cached - int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) + predicate asCallable(Node apiNode, DataFlow::CallableNode callable) { + apiNode = getBackwardStartNode(callable) + } + cached + predicate contentEdge(Node pred, DataFlow::Content content, Node succ) { + exists( + DataFlow::Node object, DataFlow::Node value, TypeTrackerSpecific::TypeTrackerContent c + | + TypeTrackerSpecific::basicLoadStep(object, value, c) and + content = c.getAStoreContent() and + not c.isSingleton(any(DataFlow::Content::AttributeNameContent k)) and + // `x -> x.foo` with content "foo" + pred = getForwardOrBackwardEndNode(getALocalSourceStrict(object)) and + succ = getForwardStartNode(value) + or + // Based on `object.c = value` generate `object -> value` with content `c` + TypeTrackerSpecific::basicStoreStep(value, object, c) and + content = c.getAStoreContent() and + pred = getForwardOrBackwardEndNode(getALocalSourceStrict(object)) and + succ = MkSinkNode(value) + ) + } + + cached + predicate fieldEdge(Node pred, string name, Node succ) { + Impl::contentEdge(pred, DataFlowPrivate::TFieldContent(name), succ) + } + + cached + predicate elementEdge(Node pred, Node succ) { + contentEdge(pred, any(DataFlow::ContentSet set | set.isAnyElement()).getAReadContent(), succ) + } + + cached + predicate parameterEdge(Node pred, DataFlowDispatch::ParameterPosition paramPos, Node succ) { + exists(DataFlowPrivate::ParameterNodeImpl parameter, DataFlow::CallableNode callable | + parameter.isSourceParameterOf(callable.asExpr().getExpr(), paramPos) and + pred = getBackwardEndNode(callable) and + succ = getForwardStartNode(parameter) + ) + } + + cached + predicate argumentEdge(Node pred, DataFlowDispatch::ArgumentPosition argPos, Node succ) { + exists(DataFlow::CallNode call, DataFlowPrivate::ArgumentNode argument | + argument.sourceArgumentOf(call.asExpr(), argPos) and + pred = MkMethodAccessNode(call) and + succ = MkSinkNode(argument) + ) + } + + cached + predicate positionalArgumentEdge(Node pred, int n, Node succ) { + argumentEdge(pred, any(DataFlowDispatch::ArgumentPosition pos | pos.isPositional(n)), succ) + } + + cached + predicate keywordArgumentEdge(Node pred, string name, Node succ) { + argumentEdge(pred, any(DataFlowDispatch::ArgumentPosition pos | pos.isKeyword(name)), succ) + } + + private predicate blockArgumentEdge(Node pred, Node succ) { + argumentEdge(pred, any(DataFlowDispatch::ArgumentPosition pos | pos.isBlock()), succ) + } + + private predicate positionalParameterEdge(Node pred, int n, Node succ) { + parameterEdge(pred, any(DataFlowDispatch::ParameterPosition pos | pos.isPositional(n)), succ) + } + + private predicate keywordParameterEdge(Node pred, string name, Node succ) { + parameterEdge(pred, any(DataFlowDispatch::ParameterPosition pos | pos.isKeyword(name)), succ) + } + + cached + predicate blockParameterEdge(Node pred, Node succ) { + parameterEdge(pred, any(DataFlowDispatch::ParameterPosition pos | pos.isBlock()), succ) + } + + cached + predicate positionalParameterOrArgumentEdge(Node pred, int n, Node succ) { + positionalArgumentEdge(pred, n, succ) + or + positionalParameterEdge(pred, n, succ) + } + + cached + predicate keywordParameterOrArgumentEdge(Node pred, string name, Node succ) { + keywordArgumentEdge(pred, name, succ) + or + keywordParameterEdge(pred, name, succ) + } + + cached + predicate blockParameterOrArgumentEdge(Node pred, Node succ) { + blockArgumentEdge(pred, succ) + or + blockParameterEdge(pred, succ) + } + + pragma[nomagic] + private predicate newCall(DataFlow::LocalSourceNode receiver, DataFlow::CallNode call) { + call = receiver.getAMethodCall("new") + } + + cached + predicate instanceEdge(Node pred, Node succ) { + exists(DataFlow::ModuleNode mod | + pred = MkModuleObjectDown(mod) and + succ = MkModuleInstanceUp(mod) + ) + or + exists(DataFlow::LocalSourceNode receiver, DataFlow::CallNode call | + newCall(receiver, call) and + pred = getForwardEndNode(receiver) and + succ = getForwardStartNode(call) + ) + } + + cached + predicate returnEdge(Node pred, Node succ) { + exists(DataFlow::CallNode call | + pred = MkMethodAccessNode(call) and + succ = getForwardStartNode(call) + ) + or + exists(DataFlow::CallableNode callable | + pred = getBackwardEndNode(callable) and + succ = MkSinkNode(callable.getAReturnNode()) + ) + } + + cached + predicate entryPointEdge(EntryPoint entry, Node node) { + node = MkSinkNode(entry.getASink()) or + node = getForwardStartNode(entry.getASource()) or + node = MkMethodAccessNode(entry.getACall()) + } + } + + /** + * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. + */ + pragma[nomagic] + deprecated private predicate labelledEdge(Node pred, Label::ApiLabel lbl, Node succ) { + exists(string name | + Impl::memberEdge(pred, name, succ) and + lbl = Label::member(name) + ) + or + exists(string name | + Impl::methodEdge(pred, name, succ) and + lbl = Label::method(name) + ) + or + exists(DataFlow::Content content | + Impl::contentEdge(pred, content, succ) and + lbl = Label::content(content) + ) + or + exists(DataFlowDispatch::ParameterPosition pos | + Impl::parameterEdge(pred, pos, succ) and + lbl = Label::getLabelFromParameterPosition(pos) + ) + or + exists(DataFlowDispatch::ArgumentPosition pos | + Impl::argumentEdge(pred, pos, succ) and + lbl = Label::getLabelFromArgumentPosition(pos) + ) + or + Impl::instanceEdge(pred, succ) and + lbl = Label::instance() + or + Impl::returnEdge(pred, succ) and + lbl = Label::return() + or + exists(EntryPoint entry | + Impl::entryPointEdge(entry, succ) and + pred = root() and + lbl = Label::entryPoint(entry) + ) + } + + /** + * DEPRECATED. Treating the API graph as an explicit labelled graph is deprecated - instead use the methods on `API:Node` directly. + * + * Provides classes modeling the various edges (labels) in the API graph. + */ + deprecated module Label { /** All the possible labels in the API graph. */ - cached - newtype TLabel = + private newtype TLabel = MkLabelMember(string member) { member = any(ConstantReadAccess a).getName() } or MkLabelMethod(string m) { m = any(DataFlow::CallNode c).getMethodName() } or MkLabelReturn() or - MkLabelSubclass() or + MkLabelInstance() or MkLabelKeywordParameter(string name) { any(DataFlowDispatch::ArgumentPosition arg).isKeyword(name) or @@ -923,12 +1430,9 @@ module API { MkLabelBlockParameter() or MkLabelEntryPoint(EntryPoint name) or MkLabelContent(DataFlow::Content content) - } - /** Provides classes modeling the various edges (labels) in the API graph. */ - module Label { /** A label in the API-graph */ - class ApiLabel extends Impl::TLabel { + class ApiLabel extends TLabel { /** Gets a string representation of this label. */ string toString() { result = "???" } } @@ -967,9 +1471,9 @@ module API { override string toString() { result = "getReturn()" } } - /** A label for the subclass relationship. */ - class LabelSubclass extends ApiLabel, MkLabelSubclass { - override string toString() { result = "getASubclass()" } + /** A label for getting instances of a module/class. */ + class LabelInstance extends ApiLabel, MkLabelInstance { + override string toString() { result = "getInstance()" } } /** A label for a keyword parameter. */ @@ -1037,8 +1541,8 @@ module API { /** Gets the `return` edge label. */ LabelReturn return() { any() } - /** Gets the `subclass` edge label. */ - LabelSubclass subclass() { any() } + /** Gets the `instance` edge label. */ + LabelInstance instance() { any() } /** Gets the label representing the given keyword argument/parameter. */ LabelKeywordParameter keywordParameter(string name) { result.getName() = name } diff --git a/ruby/ql/lib/codeql/ruby/Concepts.qll b/ruby/ql/lib/codeql/ruby/Concepts.qll index 642ad791885..ed132cd2049 100644 --- a/ruby/ql/lib/codeql/ruby/Concepts.qll +++ b/ruby/ql/lib/codeql/ruby/Concepts.qll @@ -843,6 +843,58 @@ module XmlParserCall { } } +/** + * A data-flow node that constructs an XPath expression. + * + * If it is important that the XPath expression is indeed executed, then use `XPathExecution`. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `XPathConstruction::Range` instead. + */ +class XPathConstruction extends DataFlow::Node instanceof XPathConstruction::Range { + /** Gets the argument that specifies the XPath expressions to be constructed. */ + DataFlow::Node getXPath() { result = super.getXPath() } +} + +/** Provides a class for modeling new XPath construction APIs. */ +module XPathConstruction { + /** + * A data-flow node that constructs an XPath expression. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `XPathConstruction` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the argument that specifies the XPath expressions to be constructed. */ + abstract DataFlow::Node getXPath(); + } +} + +/** + * A data-flow node that executes an XPath expression. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `XPathExecution::Range` instead. + */ +class XPathExecution extends DataFlow::Node instanceof XPathExecution::Range { + /** Gets the argument that specifies the XPath expressions to be executed. */ + DataFlow::Node getXPath() { result = super.getXPath() } +} + +/** Provides a class for modeling new XPath execution APIs. */ +module XPathExecution { + /** + * A data-flow node that executes an XPath expression. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `XPathExecution` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the argument that specifies the XPath expressions to be executed. */ + abstract DataFlow::Node getXPath(); + } +} + /** * A data-flow node that may represent a database object in an ORM system. * diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlow.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlow.qll index f34554d6eea..03975c6a54a 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlow.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlow.qll @@ -46,6 +46,14 @@ signature module ConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or isAdditionalFlowStep(_, node) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -114,7 +122,7 @@ signature module StateConfigSig { * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state); + default predicate isBarrier(Node node, FlowState state) { none() } /** Holds if data flow into `node` is prohibited. */ default predicate isBarrierIn(Node node) { none() } @@ -131,7 +139,9 @@ signature module StateConfigSig { * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + default predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } /** * Holds if an arbitrary number of implicit read steps of content `c` may be @@ -139,6 +149,17 @@ signature module StateConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or + isAdditionalFlowStep(_, node) or + isAdditionalFlowStep(node, _, _, _) or + isAdditionalFlowStep(_, _, node, _) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll index 410543e0fc9..29561b0f0a6 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll @@ -66,6 +66,12 @@ signature module FullStateConfigSig { */ predicate allowImplicitRead(Node node, ContentSet c); + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + predicate neverSkip(Node node); + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -254,6 +260,11 @@ module Impl { not fullBarrier(node2) } + pragma[nomagic] + private predicate isUnreachableInCall1(NodeEx n, LocalCallContextSpecificCall cc) { + isUnreachableInCallCached(n.asNode(), cc.getCall()) + } + /** * Holds if data can flow in one local step from `node1` to `node2`. */ @@ -2019,7 +2030,8 @@ module Impl { castNode(this.asNode()) or clearsContentCached(this.asNode(), _) or expectsContentCached(this.asNode(), _) or - neverSkipInPathGraph(this.asNode()) + neverSkipInPathGraph(this.asNode()) or + Config::neverSkip(this.asNode()) } } @@ -2108,7 +2120,7 @@ module Impl { NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, LocalCallContext cc ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCall1(node2, cc) and ( localFlowEntry(node1, pragma[only_bind_into](state)) and ( @@ -2123,7 +2135,7 @@ module Impl { ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) + not isUnreachableInCall1(node1, cc) or exists(NodeEx mid | localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and @@ -2160,10 +2172,8 @@ module Impl { preservesValue = false and t = node2.getDataFlowType() and callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) + not isUnreachableInCall1(node1, callContext) and + not isUnreachableInCall1(node2, callContext) } } @@ -2703,7 +2713,7 @@ module Impl { ParamNodeEx getParamNode() { result = p } - override string toString() { result = p + ": " + ap } + override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn @@ -2755,12 +2765,21 @@ module Impl { ) } + private predicate forceUnfold(AccessPathApprox apa) { + forceHighPrecision(apa.getHead()) + or + exists(Content c2 | + apa = TConsCons(_, _, c2, _) and + forceHighPrecision(c2) + ) + } + /** * Holds with `unfold = false` if a precise head-tail representation of `apa` is * expected to be expensive. Holds with `unfold = true` otherwise. */ private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { - if forceHighPrecision(apa.getHead()) + if forceUnfold(apa) then unfold = true else exists(int aps, int nodes, int apLimit, int tupleLimit | @@ -3089,6 +3108,12 @@ module Impl { result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } + private string ppSummaryCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">" + } + /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() } @@ -3097,7 +3122,9 @@ module Impl { * representation of the call context. */ string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + result = + this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + + this.ppSummaryCtx() } /** diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl1.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl1.qll index be70086a93a..b0de9745816 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl1.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl1.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll index be70086a93a..b0de9745816 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForHttpClientLibraries.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForHttpClientLibraries.qll index be70086a93a..b0de9745816 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForHttpClientLibraries.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForHttpClientLibraries.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForPathname.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForPathname.qll index be70086a93a..b0de9745816 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForPathname.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForPathname.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index f8469e99a23..f3249d377d8 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -1333,11 +1333,20 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) creation.asExpr() = any(CfgNodes::ExprNodes::MethodCallCfgNode mc | c.asCallable() = mc.getBlock().getExpr() and - mc.getExpr().getMethodName() = ["lambda", "proc"] + isProcCreationCall(mc.getExpr()) ) ) } +/** Holds if `call` is a call to `lambda`, `proc`, or `Proc.new` */ +pragma[nomagic] +private predicate isProcCreationCall(MethodCall call) { + call.getMethodName() = ["proc", "lambda"] + or + call.getMethodName() = "new" and + call.getReceiver().(ConstantReadAccess).getAQualifiedName() = "Proc" +} + /** * Holds if `call` is a from-source lambda call of kind `kind` where `receiver` * is the lambda expression. diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll index a98238e85d9..7772e5235d7 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -6,12 +6,17 @@ private import codeql.ruby.typetracking.TypeTracker private import codeql.ruby.dataflow.SSA private import FlowSummaryImpl as FlowSummaryImpl private import SsaImpl as SsaImpl +private import codeql.ruby.ApiGraphs /** * An element, viewed as a node in a data flow graph. Either an expression * (`ExprNode`) or a parameter (`ParameterNode`). */ class Node extends TNode { + /** Starts backtracking from this node using API graphs. */ + pragma[inline] + API::Node backtrack() { result = API::Internal::getNodeForBacktracking(this) } + /** Gets the expression corresponding to this node, if any. */ CfgNodes::ExprCfgNode asExpr() { result = this.(ExprNode).getExprNode() } @@ -76,6 +81,11 @@ class Node extends TNode { result.asCallableAstNode() = c.asCallable() ) } + + /** Gets the enclosing method, if any. */ + MethodNode getEnclosingMethod() { + result.asCallableAstNode() = this.asExpr().getExpr().getEnclosingMethod() + } } /** A data-flow node corresponding to a call in the control-flow graph. */ @@ -144,6 +154,18 @@ class CallNode extends LocalSourceNode, ExprNode { result.asExpr() = pair.getValue() ) } + + /** + * Gets a potential target of this call, if any. + */ + final CallableNode getATarget() { + result.asCallableAstNode() = this.asExpr().getExpr().(Call).getATarget() + } + + /** + * Holds if this is a `super` call. + */ + final predicate isSuperCall() { this.asExpr().getExpr() instanceof SuperCall } } /** @@ -217,6 +239,10 @@ class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl { class LocalSourceNode extends Node { LocalSourceNode() { isLocalSourceNode(this) } + /** Starts tracking this node forward using API graphs. */ + pragma[inline] + API::Node track() { result = API::Internal::getNodeForForwardTracking(this) } + /** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */ pragma[inline] predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) } @@ -359,6 +385,11 @@ private module Cached { ) } + cached + predicate methodHasSuperCall(MethodNode method, CallNode call) { + call.isSuperCall() and method = call.getEnclosingMethod() + } + /** * A place in which a named constant can be looked up during constant lookup. */ @@ -387,6 +418,39 @@ private module Cached { result.asExpr().getExpr() = access } + /** + * Gets a module for which `constRef` is the reference to an ancestor module. + * + * For example, `M` is the ancestry target of `C` in the following examples: + * ```rb + * class M < C {} + * + * module M + * include C + * end + * + * module M + * prepend C + * end + * ``` + */ + private ModuleNode getAncestryTarget(ConstRef constRef) { result.getAnAncestorExpr() = constRef } + + /** + * Gets a scope in which a constant lookup may access the contents of the module referenced by `constRef`. + */ + cached + TConstLookupScope getATargetScope(ConstRef constRef) { + result = MkAncestorLookup(getAncestryTarget(constRef).getAnImmediateDescendent*()) + or + constRef.asConstantAccess() = any(ConstantAccess ac).getScopeExpr() and + result = MkQualifiedLookup(constRef.asConstantAccess()) + or + result = MkNestedLookup(getAncestryTarget(constRef)) + or + result = MkExactLookup(constRef.asConstantAccess().(Namespace).getModule()) + } + cached predicate forceCachingInSameStage() { any() } @@ -1028,6 +1092,33 @@ class ModuleNode instanceof Module { * this predicate. */ ModuleNode getNestedModule(string name) { result = super.getNestedModule(name) } + + /** + * Starts tracking the module object using API graphs. + * + * Concretely, this tracks forward from the following starting points: + * - A constant access that resolves to this module. + * - `self` in the module scope or in a singleton method of the module. + * - A call to `self.class` in an instance method of this module or an ancestor module. + */ + bindingset[this] + pragma[inline] + API::Node trackModule() { result = API::Internal::getModuleNode(this) } + + /** + * Starts tracking instances of this module forward using API graphs. + * + * Concretely, this tracks forward from the following starting points: + * - `self` in instance methods of this module and ancestor modules + * - Calls to `new` on the module object + * + * Note that this includes references to `self` in ancestor modules, but not in descendent modules. + * This is usually the desired behavior, particularly if this module was itself found using + * a call to `getADescendentModule()`. + */ + bindingset[this] + pragma[inline] + API::Node trackInstance() { result = API::Internal::getModuleInstance(this) } } /** @@ -1216,6 +1307,9 @@ class MethodNode extends CallableNode { /** Holds if this method is protected. */ predicate isProtected() { this.asCallableAstNode().isProtected() } + + /** Gets a `super` call in this method. */ + CallNode getASuperCall() { methodHasSuperCall(this, result) } } /** @@ -1334,24 +1428,6 @@ class ConstRef extends LocalSourceNode { not exists(access.getScopeExpr()) } - /** - * Gets a module for which this constant is the reference to an ancestor module. - * - * For example, `M` is the ancestry target of `C` in the following examples: - * ```rb - * class M < C {} - * - * module M - * include C - * end - * - * module M - * prepend C - * end - * ``` - */ - private ModuleNode getAncestryTarget() { result.getAnAncestorExpr() = this } - /** * Gets the known target module. * @@ -1359,22 +1435,6 @@ class ConstRef extends LocalSourceNode { */ private Module getExactTarget() { result.getAnImmediateReference() = access } - /** - * Gets a scope in which a constant lookup may access the contents of the module referenced by this constant. - */ - cached - private TConstLookupScope getATargetScope() { - forceCachingInSameStage() and - result = MkAncestorLookup(this.getAncestryTarget().getAnImmediateDescendent*()) - or - access = any(ConstantAccess ac).getScopeExpr() and - result = MkQualifiedLookup(access) - or - result = MkNestedLookup(this.getAncestryTarget()) - or - result = MkExactLookup(access.(Namespace).getModule()) - } - /** * Gets the scope expression, or the immediately enclosing `Namespace` (skipping over singleton classes). * @@ -1436,7 +1496,7 @@ class ConstRef extends LocalSourceNode { pragma[inline] ConstRef getConstant(string name) { exists(TConstLookupScope scope | - pragma[only_bind_into](scope) = pragma[only_bind_out](this).getATargetScope() and + pragma[only_bind_into](scope) = getATargetScope(pragma[only_bind_out](this)) and result.accesses(pragma[only_bind_out](scope), name) ) } @@ -1458,7 +1518,9 @@ class ConstRef extends LocalSourceNode { * end * ``` */ - ModuleNode getADescendentModule() { MkAncestorLookup(result) = this.getATargetScope() } + bindingset[this] + pragma[inline_late] + ModuleNode getADescendentModule() { MkAncestorLookup(result) = getATargetScope(this) } } /** diff --git a/ruby/ql/lib/codeql/ruby/experimental/UnicodeBypassValidationQuery.qll b/ruby/ql/lib/codeql/ruby/experimental/UnicodeBypassValidationQuery.qll index 5c24978c4c3..449e05ad9e8 100644 --- a/ruby/ql/lib/codeql/ruby/experimental/UnicodeBypassValidationQuery.qll +++ b/ruby/ql/lib/codeql/ruby/experimental/UnicodeBypassValidationQuery.qll @@ -91,19 +91,19 @@ class Configuration extends TaintTracking::Configuration { // unicode_utils exists(API::MethodAccessNode mac | mac = API::getTopLevelMember("UnicodeUtils").getMethod(["nfkd", "nfc", "nfd", "nfkc"]) and - sink = mac.getParameter(0).asSink() + sink = mac.getArgument(0).asSink() ) or // eprun exists(API::MethodAccessNode mac | mac = API::getTopLevelMember("Eprun").getMethod("normalize") and - sink = mac.getParameter(0).asSink() + sink = mac.getArgument(0).asSink() ) or // unf exists(API::MethodAccessNode mac | mac = API::getTopLevelMember("UNF").getMember("Normalizer").getMethod("normalize") and - sink = mac.getParameter(0).asSink() + sink = mac.getArgument(0).asSink() ) or // ActiveSupport::Multibyte::Chars @@ -113,7 +113,7 @@ class Configuration extends TaintTracking::Configuration { .getMember("Multibyte") .getMember("Chars") .getMethod("new") - .getCallNode() and + .asCall() and n = cn.getAMethodCall("normalize") and sink = cn.getArgument(0) ) diff --git a/ruby/ql/lib/codeql/ruby/experimental/ZipSlipCustomizations.qll b/ruby/ql/lib/codeql/ruby/experimental/ZipSlipCustomizations.qll index e94cabb414c..656ceedbe2b 100644 --- a/ruby/ql/lib/codeql/ruby/experimental/ZipSlipCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/experimental/ZipSlipCustomizations.qll @@ -89,7 +89,7 @@ module ZipSlip { // If argument refers to a string object, then it's a hardcoded path and // this file is safe. not zipOpen - .getCallNode() + .asCall() .getArgument(0) .getALocalSource() .getConstantValue() diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll index 31bdc42e350..a687837f8fd 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll @@ -83,8 +83,8 @@ class ActionControllerClass extends DataFlow::ClassNode { } } -private DataFlow::LocalSourceNode actionControllerInstance() { - result = any(ActionControllerClass cls).getSelf() +private API::Node actionControllerInstance() { + result = any(ActionControllerClass cls).getSelf().track() } /** @@ -222,19 +222,19 @@ private class ActionControllerRenderToCall extends RenderToCallImpl { } } +pragma[nomagic] +private DataFlow::CallNode renderCall() { + // ActionController#render is an alias for ActionController::Renderer#render + result = + [ + any(ActionControllerClass c).trackModule().getAMethodCall("render"), + any(ActionControllerClass c).trackModule().getReturn("renderer").getAMethodCall("render") + ] +} + /** A call to `ActionController::Renderer#render`. */ private class RendererRenderCall extends RenderCallImpl { - RendererRenderCall() { - this = - [ - // ActionController#render is an alias for ActionController::Renderer#render - any(ActionControllerClass c).getAnImmediateReference().getAMethodCall("render"), - any(ActionControllerClass c) - .getAnImmediateReference() - .getAMethodCall("renderer") - .getAMethodCall("render") - ].asExpr().getExpr() - } + RendererRenderCall() { this = renderCall().asExpr().getExpr() } } /** A call to `html_escape` from within a controller. */ @@ -260,6 +260,7 @@ class RedirectToCall extends MethodCall { this = controller .getSelf() + .track() .getAMethodCall(["redirect_to", "redirect_back", "redirect_back_or_to"]) .asExpr() .getExpr() @@ -600,9 +601,7 @@ private module ParamsSummaries { * response. */ private module Response { - DataFlow::LocalSourceNode response() { - result = actionControllerInstance().getAMethodCall("response") - } + API::Node response() { result = actionControllerInstance().getReturn("response") } class BodyWrite extends DataFlow::CallNode, Http::Server::HttpResponse::Range { BodyWrite() { this = response().getAMethodCall("body=") } @@ -628,7 +627,7 @@ private module Response { HeaderWrite() { // response.header[key] = val // response.headers[key] = val - this = response().getAMethodCall(["header", "headers"]).getAMethodCall("[]=") + this = response().getReturn(["header", "headers"]).getAMethodCall("[]=") or // response.set_header(key) = val // response[header] = val @@ -673,18 +672,12 @@ private module Response { } } -private class ActionControllerLoggerInstance extends DataFlow::Node { - ActionControllerLoggerInstance() { - this = actionControllerInstance().getAMethodCall("logger") - or - any(ActionControllerLoggerInstance i).(DataFlow::LocalSourceNode).flowsTo(this) - } -} - private class ActionControllerLoggingCall extends DataFlow::CallNode, Logging::Range { ActionControllerLoggingCall() { - this.getReceiver() instanceof ActionControllerLoggerInstance and - this.getMethodName() = ["debug", "error", "fatal", "info", "unknown", "warn"] + this = + actionControllerInstance() + .getReturn("logger") + .getAMethodCall(["debug", "error", "fatal", "info", "unknown", "warn"]) } // Note: this is identical to the definition `stdlib.Logger.LoggerInfoStyleCall`. diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionMailbox.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionMailbox.qll index 13607f67926..f237e42a9a9 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActionMailbox.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionMailbox.qll @@ -27,8 +27,8 @@ module ActionMailbox { Mail() { this = [ - controller().getAnInstanceSelf().getAMethodCall("inbound_email").getAMethodCall("mail"), - controller().getAnInstanceSelf().getAMethodCall("mail") + controller().trackInstance().getReturn("inbound_email").getAMethodCall("mail"), + controller().trackInstance().getAMethodCall("mail") ] } } @@ -40,7 +40,7 @@ module ActionMailbox { RemoteContent() { this = any(Mail m) - .(DataFlow::LocalSourceNode) + .track() .getAMethodCall([ "body", "to", "from", "raw_source", "subject", "from_address", "recipients_addresses", "cc_addresses", "bcc_addresses", "in_reply_to", diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionMailer.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionMailer.qll index af183333d3d..4884f46aac3 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActionMailer.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionMailer.qll @@ -4,12 +4,26 @@ private import codeql.ruby.AST private import codeql.ruby.ApiGraphs +private import codeql.ruby.DataFlow private import codeql.ruby.frameworks.internal.Rails /** * Provides modeling for the `ActionMailer` library. */ module ActionMailer { + private DataFlow::ClassNode actionMailerClass() { + result = + [ + DataFlow::getConstant("ActionMailer").getConstant("Base"), + // In Rails applications `ApplicationMailer` typically extends + // `ActionMailer::Base`, but we treat it separately in case the + // `ApplicationMailer` definition is not in the database. + DataFlow::getConstant("ApplicationMailer") + ].getADescendentModule() + } + + private API::Node actionMailerInstance() { result = actionMailerClass().trackInstance() } + /** * A `ClassDeclaration` for a class that extends `ActionMailer::Base`. * For example, @@ -21,33 +35,11 @@ module ActionMailer { * ``` */ class MailerClass extends ClassDeclaration { - MailerClass() { - this.getSuperclassExpr() = - [ - API::getTopLevelMember("ActionMailer").getMember("Base"), - // In Rails applications `ApplicationMailer` typically extends - // `ActionMailer::Base`, but we treat it separately in case the - // `ApplicationMailer` definition is not in the database. - API::getTopLevelMember("ApplicationMailer") - ].getASubclass().getAValueReachableFromSource().asExpr().getExpr() - } - } - - /** A method call with a `self` receiver from within a mailer class */ - private class ContextCall extends MethodCall { - private MailerClass mailerClass; - - ContextCall() { - this.getReceiver() instanceof SelfVariableAccess and - this.getEnclosingModule() = mailerClass - } - - /** Gets the mailer class containing this method. */ - MailerClass getMailerClass() { result = mailerClass } + MailerClass() { this = actionMailerClass().getADeclaration() } } /** A call to `params` from within a mailer. */ - class ParamsCall extends ContextCall, ParamsCallImpl { - ParamsCall() { this.getMethodName() = "params" } + class ParamsCall extends ParamsCallImpl { + ParamsCall() { this = actionMailerInstance().getAMethodCall("params").asExpr().getExpr() } } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll index fcca078f933..4a5d342eeba 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll @@ -30,10 +30,8 @@ private predicate isBuiltInMethodForActiveRecordModelInstance(string methodName) methodName = objectInstanceMethodName() } -private API::Node activeRecordClassApiNode() { +private API::Node activeRecordBaseClass() { result = - // class Foo < ActiveRecord::Base - // class Bar < Foo [ API::getTopLevelMember("ActiveRecord").getMember("Base"), // In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we @@ -42,6 +40,46 @@ private API::Node activeRecordClassApiNode() { ] } +/** + * Gets an object with methods from the ActiveRecord query interface. + */ +private API::Node activeRecordQueryBuilder() { + result = activeRecordBaseClass() + or + result = activeRecordBaseClass().getInstance() + or + // Assume any method call might return an ActiveRecord::Relation + // These are dynamically generated + result = activeRecordQueryBuilderMethodAccess(_).getReturn() +} + +/** Gets a call targeting the ActiveRecord query interface. */ +private API::MethodAccessNode activeRecordQueryBuilderMethodAccess(string name) { + result = activeRecordQueryBuilder().getMethod(name) and + // Due to the heuristic tracking of query builder objects, add a restriction for methods with a known call target + not isUnlikelyExternalCall(result) +} + +/** Gets a call targeting the ActiveRecord query interface. */ +private DataFlow::CallNode activeRecordQueryBuilderCall(string name) { + result = activeRecordQueryBuilderMethodAccess(name).asCall() +} + +/** + * Holds if `call` is unlikely to call into an external library, since it has a possible + * call target in its enclosing module. + */ +private predicate isUnlikelyExternalCall(API::MethodAccessNode node) { + exists(DataFlow::ModuleNode mod, DataFlow::CallNode call | call = node.asCall() | + call.getATarget() = [mod.getAnOwnSingletonMethod(), mod.getAnOwnInstanceMethod()] and + call.getEnclosingMethod() = [mod.getAnOwnSingletonMethod(), mod.getAnOwnInstanceMethod()] + ) +} + +private API::Node activeRecordConnectionInstance() { + result = activeRecordBaseClass().getReturn("connection") +} + /** * A `ClassDeclaration` for a class that inherits from `ActiveRecord::Base`. For example, * @@ -55,20 +93,19 @@ private API::Node activeRecordClassApiNode() { * ``` */ class ActiveRecordModelClass extends ClassDeclaration { + private DataFlow::ClassNode cls; + ActiveRecordModelClass() { - this.getSuperclassExpr() = - activeRecordClassApiNode().getASubclass().getAValueReachableFromSource().asExpr().getExpr() + cls = activeRecordBaseClass().getADescendentModule() and this = cls.getADeclaration() } // Gets the class declaration for this class and all of its super classes - private ModuleBase getAllClassDeclarations() { - result = this.getModule().getSuperClass*().getADeclaration() - } + private ModuleBase getAllClassDeclarations() { result = cls.getAnAncestor().getADeclaration() } /** * Gets methods defined in this class that may access a field from the database. */ - Method getAPotentialFieldAccessMethod() { + deprecated Method getAPotentialFieldAccessMethod() { // It's a method on this class or one of its super classes result = this.getAllClassDeclarations().getAMethod() and // There is a value that can be returned by this method which may include field data @@ -90,58 +127,84 @@ class ActiveRecordModelClass extends ClassDeclaration { ) ) } + + /** Gets the class as a `DataFlow::ClassNode`. */ + DataFlow::ClassNode getClassNode() { result = cls } } -/** A class method call whose receiver is an `ActiveRecordModelClass`. */ -class ActiveRecordModelClassMethodCall extends MethodCall { - private ActiveRecordModelClass recvCls; +/** + * Gets a potential reference to an ActiveRecord class object. + */ +deprecated private API::Node getAnActiveRecordModelClassRef() { + result = any(ActiveRecordModelClass cls).getClassNode().trackModule() + or + // For methods with an unknown call target, assume this might be a database field, thus returning another ActiveRecord object. + // In this case we do not know which class it belongs to, which is why this predicate can't associate the reference with a specific class. + result = getAnUnknownActiveRecordModelClassCall().getReturn() +} +/** + * Gets a call performed on an ActiveRecord class object, without a known call target in the codebase. + */ +deprecated private API::MethodAccessNode getAnUnknownActiveRecordModelClassCall() { + result = getAnActiveRecordModelClassRef().getMethod(_) and + result.asCall().asExpr().getExpr() instanceof UnknownMethodCall +} + +/** + * DEPRECATED. Use `ActiveRecordModelClass.getClassNode().trackModule().getMethod()` instead. + * + * A class method call whose receiver is an `ActiveRecordModelClass`. + */ +deprecated class ActiveRecordModelClassMethodCall extends MethodCall { ActiveRecordModelClassMethodCall() { - // e.g. Foo.where(...) - recvCls.getModule() = this.getReceiver().(ConstantReadAccess).getModule() - or - // e.g. Foo.joins(:bars).where(...) - recvCls = this.getReceiver().(ActiveRecordModelClassMethodCall).getReceiverClass() - or - // e.g. self.where(...) within an ActiveRecordModelClass - this.getReceiver() instanceof SelfVariableAccess and - this.getEnclosingModule() = recvCls + this = getAnUnknownActiveRecordModelClassCall().asCall().asExpr().getExpr() } - /** The `ActiveRecordModelClass` of the receiver of this method. */ - ActiveRecordModelClass getReceiverClass() { result = recvCls } + /** Gets the `ActiveRecordModelClass` of the receiver of this method, if it can be determined. */ + ActiveRecordModelClass getReceiverClass() { + this = result.getClassNode().trackModule().getMethod(_).asCall().asExpr().getExpr() + } } -private Expr sqlFragmentArgument(MethodCall call) { - exists(string methodName | - methodName = call.getMethodName() and - ( - methodName = - [ - "delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!", - "find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from", - "group", "having", "joins", "lock", "not", "order", "reorder", "pluck", "where", - "rewhere", "select", "reselect", "update_all" - ] and - result = call.getArgument(0) - or - methodName = "calculate" and result = call.getArgument(1) - or - methodName in ["average", "count", "maximum", "minimum", "sum", "count_by_sql"] and - result = call.getArgument(0) - or - // This format was supported until Rails 2.3.8 - methodName = ["all", "find", "first", "last"] and - result = call.getKeywordArgument("conditions") - or - methodName = "reload" and - result = call.getKeywordArgument("lock") - or - // Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to - // SQLi if user supplied input is passed in as an argument. - methodName = "annotate" and - result = call.getArgument(_) - ) +private predicate sqlFragmentArgumentInner(DataFlow::CallNode call, DataFlow::Node sink) { + call = + activeRecordQueryBuilderCall([ + "delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!", + "find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from", + "group", "having", "joins", "lock", "not", "order", "reorder", "pluck", "where", "rewhere", + "select", "reselect", "update_all" + ]) and + sink = call.getArgument(0) + or + call = activeRecordQueryBuilderCall("calculate") and + sink = call.getArgument(1) + or + call = + activeRecordQueryBuilderCall(["average", "count", "maximum", "minimum", "sum", "count_by_sql"]) and + sink = call.getArgument(0) + or + // This format was supported until Rails 2.3.8 + call = activeRecordQueryBuilderCall(["all", "find", "first", "last"]) and + sink = call.getKeywordArgument("conditions") + or + call = activeRecordQueryBuilderCall("reload") and + sink = call.getKeywordArgument("lock") + or + // Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to + // SQLi if user supplied input is passed in as an argument. + call = activeRecordQueryBuilderCall("annotate") and + sink = call.getArgument(_) + or + call = activeRecordConnectionInstance().getAMethodCall("execute") and + sink = call.getArgument(0) +} + +private predicate sqlFragmentArgument(DataFlow::CallNode call, DataFlow::Node sink) { + exists(DataFlow::Node arg | + sqlFragmentArgumentInner(call, arg) and + sink = [arg, arg.(DataFlow::ArrayLiteralNode).getElement(0)] and + unsafeSqlExpr(sink.asExpr().getExpr()) ) } @@ -162,6 +225,8 @@ private predicate unsafeSqlExpr(Expr sqlFragmentExpr) { } /** + * DEPRECATED. Use the `SqlExecution` concept or `ActiveRecordSqlExecutionRange`. + * * A method call that may result in executing unintended user-controlled SQL * queries if the `getSqlFragmentSinkArgument()` expression is tainted by * unsanitized user-controlled input. For example, supposing that `User` is an @@ -175,55 +240,32 @@ private predicate unsafeSqlExpr(Expr sqlFragmentExpr) { * as `"') OR 1=1 --"` could result in the application looking up all users * rather than just one with a matching name. */ -class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall { - // The SQL fragment argument itself - private Expr sqlFragmentExpr; +deprecated class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall { + private DataFlow::CallNode call; PotentiallyUnsafeSqlExecutingMethodCall() { - exists(Expr arg | - arg = sqlFragmentArgument(this) and - unsafeSqlExpr(sqlFragmentExpr) and - ( - sqlFragmentExpr = arg - or - sqlFragmentExpr = arg.(ArrayLiteral).getElement(0) - ) and - // Check that method has not been overridden - not exists(SingletonMethod m | - m.getName() = this.getMethodName() and - m.getOuterScope() = this.getReceiverClass() - ) - ) + call.asExpr().getExpr() = this and sqlFragmentArgument(call, _) } /** * Gets the SQL fragment argument of this method call. */ - Expr getSqlFragmentSinkArgument() { result = sqlFragmentExpr } + Expr getSqlFragmentSinkArgument() { + exists(DataFlow::Node sink | + sqlFragmentArgument(call, sink) and result = sink.asExpr().getExpr() + ) + } } /** - * An `SqlExecution::Range` for an argument to a - * `PotentiallyUnsafeSqlExecutingMethodCall` that may be vulnerable to being - * controlled by user input. + * A SQL execution arising from a call to the ActiveRecord library. */ class ActiveRecordSqlExecutionRange extends SqlExecution::Range { - ActiveRecordSqlExecutionRange() { - exists(PotentiallyUnsafeSqlExecutingMethodCall mc | - this.asExpr().getNode() = mc.getSqlFragmentSinkArgument() - ) - or - this = activeRecordConnectionInstance().getAMethodCall("execute").getArgument(0) and - unsafeSqlExpr(this.asExpr().getExpr()) - } + ActiveRecordSqlExecutionRange() { sqlFragmentArgument(_, this) } override DataFlow::Node getSql() { result = this } } -private API::Node activeRecordConnectionInstance() { - result = activeRecordClassApiNode().getReturn("connection") -} - // TODO: model `ActiveRecord` sanitizers // https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html /** @@ -241,15 +283,8 @@ abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range, override predicate methodCallMayAccessField(string methodName) { // The method is not a built-in, and... not isBuiltInMethodForActiveRecordModelInstance(methodName) and - ( - // ...There is no matching method definition in the class, or... - not exists(this.getClass().getMethod(methodName)) - or - // ...the called method can access a field. - exists(Method m | m = this.getClass().getAPotentialFieldAccessMethod() | - m.getName() = methodName - ) - ) + // ...There is no matching method definition in the class + not exists(this.getClass().getMethod(methodName)) } } @@ -317,21 +352,10 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation } // A `self` reference that may resolve to an active record model object -private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation, - DataFlow::SelfParameterNode -{ +private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation { private ActiveRecordModelClass cls; - ActiveRecordModelClassSelfReference() { - exists(MethodBase m | - m = this.getCallable() and - m.getEnclosingModule() = cls and - m = cls.getAMethod() - ) and - // In a singleton method, `self` refers to the class itself rather than an - // instance of that class - not this.getSelfVariable().getDeclaringScope() instanceof SingletonMethod - } + ActiveRecordModelClassSelfReference() { this = cls.getClassNode().getAnOwnInstanceSelf() } final override ActiveRecordModelClass getClass() { result = cls } } @@ -342,7 +366,7 @@ private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInsta class ActiveRecordInstance extends DataFlow::Node { private ActiveRecordModelInstantiation instantiation; - ActiveRecordInstance() { this = instantiation or instantiation.flowsTo(this) } + ActiveRecordInstance() { this = instantiation.track().getAValueReachableFromSource() } /** Gets the `ActiveRecordModelClass` that this is an instance of. */ ActiveRecordModelClass getClass() { result = instantiation.getClass() } @@ -380,12 +404,12 @@ private module Persistence { /** A call to e.g. `User.create(name: "foo")` */ private class CreateLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range { CreateLikeCall() { - exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and - this.getMethodName() = - [ - "create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by", - "find_or_create_by!", "insert", "insert!" - ] + this = + activeRecordBaseClass() + .getAMethodCall([ + "create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by", + "find_or_create_by!", "insert", "insert!" + ]) } override DataFlow::Node getValue() { @@ -402,8 +426,7 @@ private module Persistence { /** A call to e.g. `User.update(1, name: "foo")` */ private class UpdateLikeClassMethodCall extends DataFlow::CallNode, PersistentWriteAccess::Range { UpdateLikeClassMethodCall() { - exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and - this.getMethodName() = ["update", "update!", "upsert"] + this = activeRecordBaseClass().getAMethodCall(["update", "update!", "upsert"]) } override DataFlow::Node getValue() { @@ -448,10 +471,7 @@ private module Persistence { * ``` */ private class TouchAllCall extends DataFlow::CallNode, PersistentWriteAccess::Range { - TouchAllCall() { - exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and - this.getMethodName() = "touch_all" - } + TouchAllCall() { this = activeRecordQueryBuilderCall("touch_all") } override DataFlow::Node getValue() { result = this.getKeywordArgument("time") } } @@ -461,8 +481,7 @@ private module Persistence { private ExprNodes::ArrayLiteralCfgNode arr; InsertAllLikeCall() { - exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and - this.getMethodName() = ["insert_all", "insert_all!", "upsert_all"] and + this = activeRecordBaseClass().getAMethodCall(["insert_all", "insert_all!", "upsert_all"]) and arr = this.getArgument(0).asExpr() } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveResource.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveResource.qll index 96219915770..9f0e0f4b859 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveResource.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveResource.qll @@ -18,8 +18,12 @@ module ActiveResource { * An ActiveResource model class. This is any (transitive) subclass of ActiveResource. */ pragma[nomagic] - private API::Node modelApiNode() { - result = API::getTopLevelMember("ActiveResource").getMember("Base").getASubclass() + private API::Node activeResourceBaseClass() { + result = API::getTopLevelMember("ActiveResource").getMember("Base") + } + + private DataFlow::ClassNode activeResourceClass() { + result = activeResourceBaseClass().getADescendentModule() } /** @@ -30,16 +34,8 @@ module ActiveResource { * end * ``` */ - class ModelClass extends ClassDeclaration { - API::Node model; - - ModelClass() { - model = modelApiNode() and - this.getSuperclassExpr() = model.getAValueReachableFromSource().asExpr().getExpr() - } - - /** Gets the API node for this model */ - API::Node getModelApiNode() { result = model } + class ModelClassNode extends DataFlow::ClassNode { + ModelClassNode() { this = activeResourceClass() } /** Gets a call to `site=`, which sets the base URL for this model. */ SiteAssignCall getASiteAssignment() { result.getModelClass() = this } @@ -49,6 +45,46 @@ module ActiveResource { c = this.getASiteAssignment() and c.disablesCertificateValidation() } + + /** Gets a method call on this class that returns an instance of the class. */ + private DataFlow::CallNode getAChainedCall() { + result.(FindCall).getModelClass() = this + or + result.(CreateCall).getModelClass() = this + or + result.(CustomHttpCall).getModelClass() = this + or + result.(CollectionCall).getCollection().getModelClass() = this and + result.getMethodName() = ["first", "last"] + } + + /** Gets an API node referring to an instance of this class. */ + API::Node getAnInstanceReference() { + result = this.trackInstance() + or + result = this.getAChainedCall().track() + } + } + + /** DEPRECATED. Use `ModelClassNode` instead. */ + deprecated class ModelClass extends ClassDeclaration { + private ModelClassNode cls; + + ModelClass() { this = cls.getADeclaration() } + + /** Gets the class for which this is a declaration. */ + ModelClassNode getClassNode() { result = cls } + + /** Gets the API node for this class object. */ + deprecated API::Node getModelApiNode() { result = cls.trackModule() } + + /** Gets a call to `site=`, which sets the base URL for this model. */ + SiteAssignCall getASiteAssignment() { result = cls.getASiteAssignment() } + + /** Holds if `c` sets a base URL which does not use HTTPS. */ + predicate disablesCertificateValidation(SiteAssignCall c) { + cls.disablesCertificateValidation(c) + } } /** @@ -62,25 +98,20 @@ module ActiveResource { * ``` */ class ModelClassMethodCall extends DataFlow::CallNode { - API::Node model; + private ModelClassNode cls; - ModelClassMethodCall() { - model = modelApiNode() and - this = classMethodCall(model, _) - } + ModelClassMethodCall() { this = cls.trackModule().getAMethodCall(_) } /** Gets the model class for this call. */ - ModelClass getModelClass() { result.getModelApiNode() = model } + ModelClassNode getModelClass() { result = cls } } /** * A call to `site=` on an ActiveResource model class. * This sets the base URL for all HTTP requests made by this class. */ - private class SiteAssignCall extends DataFlow::CallNode { - API::Node model; - - SiteAssignCall() { model = modelApiNode() and this = classMethodCall(model, "site=") } + private class SiteAssignCall extends ModelClassMethodCall { + SiteAssignCall() { this.getMethodName() = "site=" } /** * Gets a node that contributes to the URLs used for HTTP requests by the parent @@ -88,12 +119,10 @@ module ActiveResource { */ DataFlow::Node getAUrlPart() { result = this.getArgument(0) } - /** Gets the model class for this call. */ - ModelClass getModelClass() { result.getModelApiNode() = model } - /** Holds if this site value specifies HTTP rather than HTTPS. */ predicate disablesCertificateValidation() { this.getAUrlPart() + // TODO: We should not need all this just to get the string value .asExpr() .(ExprNodes::AssignExprCfgNode) .getRhs() @@ -141,87 +170,70 @@ module ActiveResource { } /** + * DEPRECATED. Use `ModelClassNode.getAnInstanceReference()` instead. + * * An ActiveResource model object. */ - class ModelInstance extends DataFlow::Node { - ModelClass cls; + deprecated class ModelInstance extends DataFlow::Node { + private ModelClassNode cls; - ModelInstance() { - exists(API::Node model | model = modelApiNode() | - this = model.getInstance().getAValueReachableFromSource() and - cls.getModelApiNode() = model - ) - or - exists(FindCall call | call.flowsTo(this) | cls = call.getModelClass()) - or - exists(CreateCall call | call.flowsTo(this) | cls = call.getModelClass()) - or - exists(CustomHttpCall call | call.flowsTo(this) | cls = call.getModelClass()) - or - exists(CollectionCall call | - call.getMethodName() = ["first", "last"] and - call.flowsTo(this) - | - cls = call.getCollection().getModelClass() - ) - } + ModelInstance() { this = cls.getAnInstanceReference().getAValueReachableFromSource() } /** Gets the model class for this instance. */ - ModelClass getModelClass() { result = cls } + ModelClassNode getModelClass() { result = cls } } /** * A call to a method on an ActiveResource model object. */ class ModelInstanceMethodCall extends DataFlow::CallNode { - ModelInstance i; + private ModelClassNode cls; - ModelInstanceMethodCall() { this.getReceiver() = i } + ModelInstanceMethodCall() { this = cls.getAnInstanceReference().getAMethodCall(_) } /** Gets the model instance for this call. */ - ModelInstance getInstance() { result = i } + deprecated ModelInstance getInstance() { result = this.getReceiver() } /** Gets the model class for this call. */ - ModelClass getModelClass() { result = i.getModelClass() } + ModelClassNode getModelClass() { result = cls } } /** - * A collection of ActiveResource model objects. + * DEPRECATED. Use `CollectionSource` instead. + * + * A data flow node that may refer to a collection of ActiveResource model objects. */ - class Collection extends DataFlow::Node { - ModelClassMethodCall classMethodCall; + deprecated class Collection extends DataFlow::Node { + Collection() { this = any(CollectionSource src).track().getAValueReachableFromSource() } + } - Collection() { - classMethodCall.flowsTo(this) and - ( - classMethodCall.getMethodName() = "all" - or - classMethodCall.getMethodName() = "find" and - classMethodCall.getArgument(0).asExpr().getConstantValue().isStringlikeValue("all") - ) + /** + * A call that returns a collection of ActiveResource model objects. + */ + class CollectionSource extends ModelClassMethodCall { + CollectionSource() { + this.getMethodName() = "all" + or + this.getArgument(0).asExpr().getConstantValue().isStringlikeValue("all") } - - /** Gets the model class for this collection. */ - ModelClass getModelClass() { result = classMethodCall.getModelClass() } } /** * A method call on a collection. */ class CollectionCall extends DataFlow::CallNode { - CollectionCall() { this.getReceiver() instanceof Collection } + private CollectionSource collection; + + CollectionCall() { this = collection.track().getAMethodCall(_) } /** Gets the collection for this call. */ - Collection getCollection() { result = this.getReceiver() } + CollectionSource getCollection() { result = collection } } private class ModelClassMethodCallAsHttpRequest extends Http::Client::Request::Range, ModelClassMethodCall { - ModelClass cls; - ModelClassMethodCallAsHttpRequest() { - this.getModelClass() = cls and this.getMethodName() = ["all", "build", "create", "create!", "find", "first", "last"] } @@ -230,12 +242,14 @@ module ActiveResource { override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { - cls.disablesCertificateValidation(disablingNode) and + this.getModelClass().disablesCertificateValidation(disablingNode) and // TODO: highlight real argument origin argumentOrigin = disablingNode } - override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() } + override DataFlow::Node getAUrlPart() { + result = this.getModelClass().getASiteAssignment().getAUrlPart() + } override DataFlow::Node getResponseBody() { result = this } } @@ -243,10 +257,7 @@ module ActiveResource { private class ModelInstanceMethodCallAsHttpRequest extends Http::Client::Request::Range, ModelInstanceMethodCall { - ModelClass cls; - ModelInstanceMethodCallAsHttpRequest() { - this.getModelClass() = cls and this.getMethodName() = [ "exists?", "reload", "save", "save!", "destroy", "delete", "get", "patch", "post", "put", @@ -259,42 +270,15 @@ module ActiveResource { override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { - cls.disablesCertificateValidation(disablingNode) and + this.getModelClass().disablesCertificateValidation(disablingNode) and // TODO: highlight real argument origin argumentOrigin = disablingNode } - override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() } + override DataFlow::Node getAUrlPart() { + result = this.getModelClass().getASiteAssignment().getAUrlPart() + } override DataFlow::Node getResponseBody() { result = this } } - - /** - * A call to a class method. - * - * TODO: is this general enough to be useful elsewhere? - * - * Examples: - * ```rb - * class A - * def self.m; end - * - * m # call - * end - * - * A.m # call - * ``` - */ - private DataFlow::CallNode classMethodCall(API::Node classNode, string methodName) { - // A.m - result = classNode.getAMethodCall(methodName) - or - // class A - // A.m - // end - result.getReceiver().asExpr() instanceof ExprNodes::SelfVariableAccessCfgNode and - result.asExpr().getExpr().getEnclosingModule().(ClassDeclaration).getSuperclassExpr() = - classNode.getAValueReachableFromSource().asExpr().getExpr() and - result.getMethodName() = methodName - } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll b/ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll index 8e673c4255d..98fbe241404 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll @@ -39,13 +39,8 @@ private API::Node graphQlSchema() { result = API::getTopLevelMember("GraphQL").g */ private class GraphqlRelayClassicMutationClass extends ClassDeclaration { GraphqlRelayClassicMutationClass() { - this.getSuperclassExpr() = - graphQlSchema() - .getMember("RelayClassicMutation") - .getASubclass() - .getAValueReachableFromSource() - .asExpr() - .getExpr() + this = + graphQlSchema().getMember("RelayClassicMutation").getADescendentModule().getADeclaration() } } @@ -74,13 +69,7 @@ private class GraphqlRelayClassicMutationClass extends ClassDeclaration { */ private class GraphqlSchemaResolverClass extends ClassDeclaration { GraphqlSchemaResolverClass() { - this.getSuperclassExpr() = - graphQlSchema() - .getMember("Resolver") - .getASubclass() - .getAValueReachableFromSource() - .asExpr() - .getExpr() + this = graphQlSchema().getMember("Resolver").getADescendentModule().getADeclaration() } } @@ -103,13 +92,7 @@ private string getASupportedHttpMethod() { result = ["get", "post"] } */ class GraphqlSchemaObjectClass extends ClassDeclaration { GraphqlSchemaObjectClass() { - this.getSuperclassExpr() = - graphQlSchema() - .getMember("Object") - .getASubclass() - .getAValueReachableFromSource() - .asExpr() - .getExpr() + this = graphQlSchema().getMember("Object").getADescendentModule().getADeclaration() } /** Gets a `GraphqlFieldDefinitionMethodCall` called in this class. */ diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Rack.qll b/ruby/ql/lib/codeql/ruby/frameworks/Rack.qll index 6963f37a81c..928a9f60a5f 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Rack.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Rack.qll @@ -7,7 +7,9 @@ */ module Rack { import rack.internal.App + import rack.internal.Request import rack.internal.Response::Public as Response + import rack.internal.Utils /** DEPRECATED: Alias for App::AppCandidate */ deprecated class AppCandidate = App::AppCandidate; diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll index 77e26ca13d7..981ace2e7da 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll @@ -15,21 +15,20 @@ private import codeql.ruby.Concepts * https://github.com/sparklemotion/sqlite3-ruby */ module Sqlite3 { + private API::Node databaseConst() { + result = API::getTopLevelMember("SQLite3").getMember("Database") + } + + private API::Node dbInstance() { + result = databaseConst().getInstance() + or + // e.g. SQLite3::Database.new("foo.db") |db| { db.some_method } + result = databaseConst().getMethod("new").getBlock().getParameter(0) + } + /** Gets a method call with a receiver that is a database instance. */ private DataFlow::CallNode getADatabaseMethodCall(string methodName) { - exists(API::Node dbInstance | - dbInstance = API::getTopLevelMember("SQLite3").getMember("Database").getInstance() and - ( - result = dbInstance.getAMethodCall(methodName) - or - // e.g. SQLite3::Database.new("foo.db") |db| { db.some_method } - exists(DataFlow::BlockNode block | - result.getMethodName() = methodName and - block = dbInstance.getAValueReachableFromSource().(DataFlow::CallNode).getBlock() and - block.getParameter(0).flowsTo(result.getReceiver()) - ) - ) - ) + result = dbInstance().getAMethodCall(methodName) } /** A prepared but unexecuted SQL statement. */ diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Twirp.qll b/ruby/ql/lib/codeql/ruby/frameworks/Twirp.qll index 73ef87f5fd5..7b8648bd2b1 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Twirp.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Twirp.qll @@ -16,50 +16,28 @@ module Twirp { /** * A Twirp service instantiation */ - class ServiceInstantiation extends DataFlow::CallNode { + deprecated class ServiceInstantiation extends DataFlow::CallNode { ServiceInstantiation() { this = API::getTopLevelMember("Twirp").getMember("Service").getAnInstantiation() } - /** - * Gets a local source node for the Service instantiation argument (the service handler). - */ - private DataFlow::LocalSourceNode getHandlerSource() { - result = this.getArgument(0).getALocalSource() - } - - /** - * Gets the API::Node for the service handler's class. - */ - private API::Node getAHandlerClassApiNode() { - result.getAnInstantiation() = this.getHandlerSource() - } - - /** - * Gets the AST module for the service handler's class. - */ - private Ast::Module getAHandlerClassAstNode() { - result = - this.getAHandlerClassApiNode() - .asSource() - .asExpr() - .(CfgNodes::ExprNodes::ConstantReadAccessCfgNode) - .getExpr() - .getModule() - } - /** * Gets a handler's method. */ - Ast::Method getAHandlerMethod() { - result = this.getAHandlerClassAstNode().getAnInstanceMethod() + DataFlow::MethodNode getAHandlerMethodNode() { + result = this.getArgument(0).backtrack().getMethod(_).asCallable() } + + /** + * Gets a handler's method as an AST node. + */ + Ast::Method getAHandlerMethod() { result = this.getAHandlerMethodNode().asCallableAstNode() } } /** * A Twirp client */ - class ClientInstantiation extends DataFlow::CallNode { + deprecated class ClientInstantiation extends DataFlow::CallNode { ClientInstantiation() { this = API::getTopLevelMember("Twirp").getMember("Client").getAnInstantiation() } @@ -67,7 +45,10 @@ module Twirp { /** The URL of a Twirp service, considered as a sink. */ class ServiceUrlAsSsrfSink extends ServerSideRequestForgery::Sink { - ServiceUrlAsSsrfSink() { exists(ClientInstantiation c | c.getArgument(0) = this) } + ServiceUrlAsSsrfSink() { + this = + API::getTopLevelMember("Twirp").getMember("Client").getMethod("new").getArgument(0).asSink() + } } /** A parameter that will receive parts of the url when handling an incoming request. */ @@ -75,7 +56,14 @@ module Twirp { DataFlow::ParameterNode { UnmarshaledParameter() { - exists(ServiceInstantiation i | i.getAHandlerMethod().getParameter(0) = this.asParameter()) + this = + API::getTopLevelMember("Twirp") + .getMember("Service") + .getMethod("new") + .getArgument(0) + .getMethod(_) + .getParameter(0) + .asSource() } override string getSourceType() { result = "Twirp Unmarhaled Parameter" } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll b/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll index 78f80cf5b2f..74d783fbbc5 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll @@ -45,6 +45,17 @@ private class NokogiriXmlParserCall extends XmlParserCall::Range, DataFlow::Call } } +/** Execution of a XPath statement. */ +private class NokogiriXPathExecution extends XPathExecution::Range, DataFlow::CallNode { + NokogiriXPathExecution() { + exists(NokogiriXmlParserCall parserCall | + this = parserCall.getAMethodCall(["xpath", "at_xpath", "search", "at"]) + ) + } + + override DataFlow::Node getXPath() { result = this.getArgument(0) } +} + /** * Holds if `assign` enables the `default_substitute_entities` option in * libxml-ruby. @@ -123,6 +134,40 @@ private predicate xmlMiniEntitySubstitutionEnabled() { enablesLibXmlDefaultEntitySubstitution(_) } +/** Execution of a XPath statement. */ +private class LibXmlXPathExecution extends XPathExecution::Range, DataFlow::CallNode { + LibXmlXPathExecution() { + exists(LibXmlRubyXmlParserCall parserCall | + this = parserCall.getAMethodCall(["find", "find_first"]) + ) + } + + override DataFlow::Node getXPath() { result = this.getArgument(0) } +} + +/** A call to `REXML::Document.new`, considered as a XML parsing. */ +private class RexmlParserCall extends XmlParserCall::Range, DataFlow::CallNode { + RexmlParserCall() { + this = API::getTopLevelMember("REXML").getMember("Document").getAnInstantiation() + } + + override DataFlow::Node getInput() { result = this.getArgument(0) } + + /** No option for parsing */ + override predicate externalEntitiesEnabled() { none() } +} + +/** Execution of a XPath statement. */ +private class RexmlXPathExecution extends XPathExecution::Range, DataFlow::CallNode { + RexmlXPathExecution() { + this = + [API::getTopLevelMember("REXML").getMember("XPath"), API::getTopLevelMember("XPath")] + .getAMethodCall(["each", "first", "match"]) + } + + override DataFlow::Node getXPath() { result = this.getArgument(1) } +} + /** * A call to `ActiveSupport::XmlMini.parse` considered as an `XmlParserCall`. */ diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll index 4095beb10af..f7345b22ed1 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll @@ -24,7 +24,7 @@ module Gem { GemSpec() { this.getExtension() = "gemspec" and - specCall = API::root().getMember("Gem").getMember("Specification").getMethod("new") and + specCall = API::getTopLevelMember("Gem").getMember("Specification").getMethod("new") and specCall.getLocation().getFile() = this } @@ -42,7 +42,7 @@ module Gem { .getBlock() .getParameter(0) .getMethod(name + "=") - .getParameter(0) + .getArgument(0) .asSink() .asExpr() .getExpr() diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll index b445521adb8..ad87ee37ecd 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll @@ -19,7 +19,8 @@ module Kernel { */ class KernelMethodCall extends DataFlow::CallNode { KernelMethodCall() { - this = API::getTopLevelMember("Kernel").getAMethodCall(_) + // Match Kernel calls using local flow, to avoid finding singleton calls on subclasses + this = DataFlow::getConstant("Kernel").getAMethodCall(_) or this.asExpr().getExpr() instanceof UnknownMethodCall and ( diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll index 2e3fa21a45b..21bc5f69dcb 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll @@ -44,7 +44,7 @@ private class SummarizedCallableFromModel extends SummarizedCallable { override Call getACall() { exists(API::MethodAccessNode base | ModelOutput::resolvedSummaryBase(type, path, base) and - result = base.getCallNode().asExpr().getExpr() + result = base.asCall().asExpr().getExpr() ) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll index 4c03522a9c5..ed7a331c452 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -99,9 +99,10 @@ API::Node getExtraNodeFromPath(string type, AccessPath path, int n) { // A row of form `any;Method[foo]` should match any method named `foo`. type = "any" and n = 1 and - exists(EntryPointFromAnyType entry | - methodMatchedByName(path, entry.getName()) and - result = entry.getANode() + exists(string methodName, DataFlow::CallNode call | + methodMatchedByName(path, methodName) and + call.getMethodName() = methodName and + result.(API::MethodAccessNode).asCall() = call ) } @@ -112,20 +113,10 @@ API::Node getExtraNodeFromType(string type) { constRef = getConstantFromConstPath(consts) | suffix = "!" and - ( - result.(API::Node::Internal).asSourceInternal() = constRef - or - result.(API::Node::Internal).asSourceInternal() = - constRef.getADescendentModule().getAnOwnModuleSelf() - ) + result = constRef.track() or suffix = "" and - ( - result.(API::Node::Internal).asSourceInternal() = constRef.getAMethodCall("new") - or - result.(API::Node::Internal).asSourceInternal() = - constRef.getADescendentModule().getAnInstanceSelf() - ) + result = constRef.track().getInstance() ) or type = "" and @@ -145,21 +136,6 @@ private predicate methodMatchedByName(AccessPath path, string methodName) { ) } -/** - * An API graph entry point corresponding to a method name such as `foo` in `;any;Method[foo]`. - * - * This ensures that the API graph rooted in that method call is materialized. - */ -private class EntryPointFromAnyType extends API::EntryPoint { - string name; - - EntryPointFromAnyType() { this = "AnyMethod[" + name + "]" and methodMatchedByName(_, name) } - - override DataFlow::CallNode getACall() { result.getMethodName() = name } - - string getName() { result = name } -} - /** * Gets a Ruby-specific API graph successor of `node` reachable by resolving `token`. */ @@ -175,9 +151,11 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) { result = node.getInstance() or token.getName() = "Parameter" and - result = - node.getASuccessor(API::Label::getLabelFromParameterPosition(FlowSummaryImplSpecific::parseArgBody(token - .getAnArgument()))) + exists(DataFlowDispatch::ArgumentPosition argPos, DataFlowDispatch::ParameterPosition paramPos | + argPos = FlowSummaryImplSpecific::parseParamBody(token.getAnArgument()) and + DataFlowDispatch::parameterMatch(paramPos, argPos) and + result = node.getParameterAtPosition(paramPos) + ) or exists(DataFlow::ContentSet contents | SummaryComponent::content(contents) = FlowSummaryImplSpecific::interpretComponentSpecific(token) and @@ -191,9 +169,11 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) { bindingset[token] API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathToken token) { token.getName() = "Argument" and - result = - node.getASuccessor(API::Label::getLabelFromArgumentPosition(FlowSummaryImplSpecific::parseParamBody(token - .getAnArgument()))) + exists(DataFlowDispatch::ArgumentPosition argPos, DataFlowDispatch::ParameterPosition paramPos | + paramPos = FlowSummaryImplSpecific::parseArgBody(token.getAnArgument()) and + DataFlowDispatch::parameterMatch(paramPos, argPos) and + result = node.getArgumentAtPosition(argPos) + ) } /** @@ -211,7 +191,7 @@ predicate invocationMatchesExtraCallSiteFilter(InvokeNode invoke, AccessPathToke /** An API graph node representing a method call. */ class InvokeNode extends API::MethodAccessNode { /** Gets the number of arguments to the call. */ - int getNumArgument() { result = this.getCallNode().getNumberOfArguments() } + int getNumArgument() { result = this.asCall().getNumberOfArguments() } } /** Gets the `InvokeNode` corresponding to a specific invocation of `node`. */ diff --git a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll index 748291f55bd..41df3ac0e64 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll @@ -4,6 +4,7 @@ private import codeql.ruby.AST private import codeql.ruby.ApiGraphs +private import codeql.ruby.Concepts private import codeql.ruby.DataFlow private import codeql.ruby.typetracking.TypeTracker private import Response::Private as RP @@ -18,16 +19,7 @@ private class PotentialRequestHandler extends DataFlow::CallableNode { ( this.(DataFlow::MethodNode).getMethodName() = "call" or - not this instanceof DataFlow::MethodNode and - exists(DataFlow::CallNode cn | cn.getMethodName() = "run" | - this.(DataFlow::LocalSourceNode).flowsTo(cn.getArgument(0)) - or - // TODO: `Proc.new` should automatically propagate flow from its block argument - any(DataFlow::CallNode proc | - proc = API::getTopLevelMember("Proc").getAnInstantiation() and - proc.getBlock() = this - ).(DataFlow::LocalSourceNode).flowsTo(cn.getArgument(0)) - ) + this = API::getTopLevelCall("run").getArgument(0).asCallable() ) } } @@ -86,4 +78,20 @@ module App { /** Gets a response returned from this request handler. */ RP::PotentialResponseNode getAResponse() { result = resp } } + + /** A read of the query string via `env['QUERY_STRING']`. */ + private class EnvQueryStringRead extends Http::Server::RequestInputAccess::Range { + EnvQueryStringRead() { + this = + any(RequestHandler h) + .getEnv() + .getAnElementRead(ConstantValue::fromStringlikeValue("QUERY_STRING")) + } + + override string getSourceType() { result = "Rack env" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Request.qll b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Request.qll new file mode 100644 index 00000000000..32d07a48372 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Request.qll @@ -0,0 +1,39 @@ +/** + * Provides modeling for the `Request` component of the `Rack` library. + */ + +private import codeql.ruby.AST +private import codeql.ruby.ApiGraphs +private import codeql.ruby.Concepts +private import codeql.ruby.DataFlow + +/** + * Provides modeling for the `Request` component of the `Rack` library. + */ +module Request { + private class RackRequest extends API::Node { + RackRequest() { this = API::getTopLevelMember("Rack").getMember("Request").getInstance() } + } + + /** An access to the parameters of a request to a rack application via a `Rack::Request` instance. */ + private class RackRequestParamsAccess extends Http::Server::RequestInputAccess::Range { + RackRequestParamsAccess() { + this = any(RackRequest req).getAMethodCall(["params", "query_string", "[]", "fullpath"]) + } + + override string getSourceType() { result = "Rack::Request#params" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + + /** An access to the cookies of a request to a rack application via a `Rack::Request` instance. */ + private class RackRequestCookiesAccess extends Http::Server::RequestInputAccess::Range { + RackRequestCookiesAccess() { this = any(RackRequest req).getAMethodCall("cookies") } + + override string getSourceType() { result = "Rack::Request#cookies" } + + override Http::Server::RequestInputKind getKind() { result = Http::Server::cookieInputKind() } + } +} diff --git a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll new file mode 100644 index 00000000000..4d0b948d650 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll @@ -0,0 +1,29 @@ +/** + * Provides modeling for the `Utils` component of the `Rack` library. + */ + +private import codeql.ruby.ApiGraphs +private import codeql.ruby.dataflow.FlowSummary + +/** + * Provides modeling for the `Utils` component of the `Rack` library. + */ +module Utils { + /** Flow summary for `Rack::Utils.parse_query`, which parses a query string. */ + private class ParseQuerySummary extends SummarizedCallable { + ParseQuerySummary() { this = "Rack::Utils.parse_query" } + + override MethodCall getACall() { + result = + API::getTopLevelMember("Rack") + .getMember("Utils") + .getAMethodCall("parse_query") + .asExpr() + .getExpr() + } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[0]" and output = "ReturnValue" and preservesValue = false + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/InsecureDownloadQuery.qll b/ruby/ql/lib/codeql/ruby/security/InsecureDownloadQuery.qll index 0b4c63f4392..128babcebcc 100644 --- a/ruby/ql/lib/codeql/ruby/security/InsecureDownloadQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/InsecureDownloadQuery.qll @@ -45,14 +45,6 @@ module Config implements DataFlow::StateConfigSig { } predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } - - predicate isBarrier(DataFlow::Node node, FlowState state) { none() } - - predicate isAdditionalFlowStep( - DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 - ) { - none() - } } module Flow = DataFlow::GlobalWithState; diff --git a/ruby/ql/lib/codeql/ruby/security/KernelOpenQuery.qll b/ruby/ql/lib/codeql/ruby/security/KernelOpenQuery.qll index 31c9055d7cc..3653fb23cee 100644 --- a/ruby/ql/lib/codeql/ruby/security/KernelOpenQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/KernelOpenQuery.qll @@ -55,7 +55,8 @@ class AmbiguousPathCall extends DataFlow::CallNode { } private predicate methodCallOnlyOnIO(DataFlow::CallNode node, string methodName) { - node = API::getTopLevelMember("IO").getAMethodCall(methodName) and + // Use local flow to find calls to 'IO' without subclasses + node = DataFlow::getConstant("IO").getAMethodCall(methodName) and not node = API::getTopLevelMember("File").getAMethodCall(methodName) // needed in e.g. opal/opal, where some calls have both paths (opal implements an own corelib) } diff --git a/ruby/ql/lib/codeql/ruby/security/OpenSSL.qll b/ruby/ql/lib/codeql/ruby/security/OpenSSL.qll index 261a7af462f..637f7dab04a 100644 --- a/ruby/ql/lib/codeql/ruby/security/OpenSSL.qll +++ b/ruby/ql/lib/codeql/ruby/security/OpenSSL.qll @@ -597,7 +597,7 @@ private module Digest { call = API::getTopLevelMember("OpenSSL").getMember("Digest").getMethod("new") | this = call.getReturn().getAMethodCall(["digest", "update", "<<"]) and - algo.matchesName(call.getCallNode() + algo.matchesName(call.asCall() .getArgument(0) .asExpr() .getExpr() @@ -619,7 +619,7 @@ private module Digest { Cryptography::HashingAlgorithm algo; DigestCallDirect() { - this = API::getTopLevelMember("OpenSSL").getMember("Digest").getMethod("digest").getCallNode() and + this = API::getTopLevelMember("OpenSSL").getMember("Digest").getMethod("digest").asCall() and algo.matchesName(this.getArgument(0).asExpr().getExpr().getConstantValue().getString()) } diff --git a/ruby/ql/lib/codeql/ruby/security/XpathInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/XpathInjectionCustomizations.qll new file mode 100644 index 00000000000..ee2c1e924f5 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/XpathInjectionCustomizations.qll @@ -0,0 +1,55 @@ +/** + * Provides class and predicates to track external data that + * may represent malicious xpath query objects. + * + * This module is intended to be imported into a taint-tracking query. + */ + +private import codeql.ruby.Concepts +private import codeql.ruby.DataFlow +private import codeql.ruby.dataflow.BarrierGuards +private import codeql.ruby.dataflow.RemoteFlowSources + +/** Models Xpath Injection related classes and functions */ +module XpathInjection { + /** A data flow source for "XPath injection" vulnerabilities. */ + abstract class Source extends DataFlow::Node { } + + /** A data flow sink for "XPath injection" vulnerabilities */ + abstract class Sink extends DataFlow::Node { } + + /** A sanitizer for "XPath injection" vulnerabilities. */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A source of remote user input, considered as a flow source. + */ + private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { } + + /** + * An execution of an XPath expression, considered as a sink. + */ + private class XPathExecutionAsSink extends Sink { + XPathExecutionAsSink() { this = any(XPathExecution e).getXPath() } + } + + /** + * A construction of an XPath expression, considered as a sink. + */ + private class XPathConstructionAsSink extends Sink { + XPathConstructionAsSink() { this = any(XPathConstruction c).getXPath() } + } + + /** + * A comparison with a constant string, considered as a sanitizer-guard. + */ + private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { } + + /** + * An inclusion check against an array of constant strings, considered as a + * sanitizer-guard. + */ + private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer, + StringConstArrayInclusionCallBarrier + { } +} diff --git a/ruby/ql/lib/codeql/ruby/security/XpathInjectionQuery.qll b/ruby/ql/lib/codeql/ruby/security/XpathInjectionQuery.qll new file mode 100644 index 00000000000..f8edacf7ba8 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/XpathInjectionQuery.qll @@ -0,0 +1,27 @@ +/** + * Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities. + * + * Note, for performance reasons: only import this file if + * `XpathInjection::Configuration` is needed, otherwise + * `XpathInjectionCustomizations` should be imported instead. + */ + +private import codeql.ruby.DataFlow +private import codeql.ruby.TaintTracking +import XpathInjectionCustomizations::XpathInjection + +/** Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities. */ +module XpathInjection { + /** + * A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities. + */ + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + } + + import TaintTracking::Global +} diff --git a/ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll b/ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll new file mode 100644 index 00000000000..07b10cb1303 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll @@ -0,0 +1,328 @@ +/** + * Parts of API graphs that can be shared with other dynamic languages. + * + * Depends on TypeTrackerSpecific for the corresponding language. + */ + +private import codeql.Locations +private import codeql.ruby.typetracking.TypeTracker +private import TypeTrackerSpecific + +/** + * The signature to use when instantiating `ApiGraphShared`. + * + * The implementor should define a newtype with at least three branches as follows: + * ```ql + * newtype TApiNode = + * MkForwardNode(LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or + * MkBackwardNode(LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or + * MkSinkNode(Node node) { ... } or + * ... + * ``` + * + * The three branches should be exposed through `getForwardNode`, `getBackwardNode`, and `getSinkNode`, respectively. + */ +signature module ApiGraphSharedSig { + /** A node in the API graph. */ + class ApiNode { + /** Gets a string representation of this API node. */ + string toString(); + + /** Gets the location associated with this API node, if any. */ + Location getLocation(); + } + + /** + * Gets the forward node with the given type-tracking state. + * + * This node will have outgoing epsilon edges to its type-tracking successors. + */ + ApiNode getForwardNode(TypeTrackingNode node, TypeTracker t); + + /** + * Gets the backward node with the given type-tracking state. + * + * This node will have outgoing epsilon edges to its type-tracking predecessors. + */ + ApiNode getBackwardNode(TypeTrackingNode node, TypeTracker t); + + /** + * Gets the sink node corresponding to `node`. + * + * Since sinks are not generally `LocalSourceNode`s, such nodes are materialised separately in order for + * the API graph to include representatives for sinks. Note that there is no corresponding case for "source" + * nodes as these are represented as forward nodes with initial-state type-trackers. + * + * Sink nodes have outgoing epsilon edges to the backward nodes corresponding to their local sources. + */ + ApiNode getSinkNode(Node node); + + /** + * Holds if a language-specific epsilon edge `pred -> succ` should be generated. + */ + predicate specificEpsilonEdge(ApiNode pred, ApiNode succ); +} + +/** + * Parts of API graphs that can be shared between language implementations. + */ +module ApiGraphShared { + private import S + + /** Gets a local source of `node`. */ + bindingset[node] + pragma[inline_late] + TypeTrackingNode getALocalSourceStrict(Node node) { result = node.getALocalSource() } + + cached + private module Cached { + /** + * Holds if there is an epsilon edge `pred -> succ`. + * + * That relation is reflexive, so `fastTC` produces the equivalent of a reflexive, transitive closure. + */ + pragma[noopt] + cached + predicate epsilonEdge(ApiNode pred, ApiNode succ) { + exists( + StepSummary summary, TypeTrackingNode predNode, TypeTracker predState, + TypeTrackingNode succNode, TypeTracker succState + | + StepSummary::stepCall(predNode, succNode, summary) + or + StepSummary::stepNoCall(predNode, succNode, summary) + | + pred = getForwardNode(predNode, predState) and + succState = StepSummary::append(predState, summary) and + succ = getForwardNode(succNode, succState) + or + succ = getBackwardNode(predNode, predState) and // swap order for backward flow + succState = StepSummary::append(predState, summary) and + pred = getBackwardNode(succNode, succState) // swap order for backward flow + ) + or + exists(Node sink, TypeTrackingNode localSource | + pred = getSinkNode(sink) and + localSource = getALocalSourceStrict(sink) and + succ = getBackwardStartNode(localSource) + ) + or + specificEpsilonEdge(pred, succ) + or + succ instanceof ApiNode and + succ = pred + } + + /** + * Holds if `pred` can reach `succ` by zero or more epsilon edges. + */ + cached + predicate epsilonStar(ApiNode pred, ApiNode succ) = fastTC(epsilonEdge/2)(pred, succ) + + /** Gets the API node to use when starting forward flow from `source` */ + cached + ApiNode forwardStartNode(TypeTrackingNode source) { + result = getForwardNode(source, TypeTracker::end(false)) + } + + /** Gets the API node to use when starting backward flow from `sink` */ + cached + ApiNode backwardStartNode(TypeTrackingNode sink) { + // There is backward flow A->B iff there is forward flow B->A. + // The starting point of backward flow corresponds to the end of a forward flow, and vice versa. + result = getBackwardNode(sink, TypeTracker::end(_)) + } + + /** Gets `node` as a data flow source. */ + cached + TypeTrackingNode asSourceCached(ApiNode node) { node = forwardEndNode(result) } + + /** Gets `node` as a data flow sink. */ + cached + Node asSinkCached(ApiNode node) { node = getSinkNode(result) } + } + + private import Cached + + /** Gets an API node corresponding to the end of forward-tracking to `localSource`. */ + pragma[nomagic] + private ApiNode forwardEndNode(TypeTrackingNode localSource) { + result = getForwardNode(localSource, TypeTracker::end(_)) + } + + /** Gets an API node corresponding to the end of backtracking to `localSource`. */ + pragma[nomagic] + private ApiNode backwardEndNode(TypeTrackingNode localSource) { + result = getBackwardNode(localSource, TypeTracker::end(false)) + } + + /** Gets a node reachable from `node` by zero or more epsilon edges, including `node` itself. */ + bindingset[node] + pragma[inline_late] + ApiNode getAnEpsilonSuccessorInline(ApiNode node) { epsilonStar(node, result) } + + /** Gets `node` as a data flow sink. */ + bindingset[node] + pragma[inline_late] + Node asSinkInline(ApiNode node) { result = asSinkCached(node) } + + /** Gets `node` as a data flow source. */ + bindingset[node] + pragma[inline_late] + TypeTrackingNode asSourceInline(ApiNode node) { result = asSourceCached(node) } + + /** Gets a value reachable from `source`. */ + bindingset[source] + pragma[inline_late] + Node getAValueReachableFromSourceInline(ApiNode source) { + exists(TypeTrackingNode src | + src = asSourceInline(getAnEpsilonSuccessorInline(source)) and + src.flowsTo(pragma[only_bind_into](result)) + ) + } + + /** Gets a value that can reach `sink`. */ + bindingset[sink] + pragma[inline_late] + Node getAValueReachingSinkInline(ApiNode sink) { + result = asSinkInline(getAnEpsilonSuccessorInline(sink)) + } + + /** + * Gets the starting point for forward-tracking at `node`. + * + * Should be used to obtain the successor of an edge when constructing labelled edges. + */ + bindingset[node] + pragma[inline_late] + ApiNode getForwardStartNode(Node node) { result = forwardStartNode(node) } + + /** + * Gets the starting point of backtracking from `node`. + * + * Should be used to obtain the successor of an edge when constructing labelled edges. + */ + bindingset[node] + pragma[inline_late] + ApiNode getBackwardStartNode(Node node) { result = backwardStartNode(node) } + + /** + * Gets a possible ending point of forward-tracking at `node`. + * + * Should be used to obtain the predecessor of an edge when constructing labelled edges. + * + * This is not backed by a `cached` predicate, and should only be used for materialising `cached` + * predicates in the API graph implementation - it should not be called in later stages. + */ + bindingset[node] + pragma[inline_late] + ApiNode getForwardEndNode(Node node) { result = forwardEndNode(node) } + + /** + * Gets a possible ending point backtracking to `node`. + * + * Should be used to obtain the predecessor of an edge when constructing labelled edges. + * + * This is not backed by a `cached` predicate, and should only be used for materialising `cached` + * predicates in the API graph implementation - it should not be called in later stages. + */ + bindingset[node] + pragma[inline_late] + ApiNode getBackwardEndNode(Node node) { result = backwardEndNode(node) } + + /** + * Gets a possible eding point of forward or backward tracking at `node`. + * + * Should be used to obtain the predecessor of an edge generated from store or load edges. + */ + bindingset[node] + pragma[inline_late] + ApiNode getForwardOrBackwardEndNode(Node node) { + result = getForwardEndNode(node) or result = getBackwardEndNode(node) + } + + /** Gets an API node for tracking forward starting at `node`. This is the implementation of `DataFlow::LocalSourceNode.track()` */ + bindingset[node] + pragma[inline_late] + ApiNode getNodeForForwardTracking(Node node) { result = forwardStartNode(node) } + + /** Gets an API node for backtracking starting at `node`. The implementation of `DataFlow::Node.backtrack()`. */ + bindingset[node] + pragma[inline_late] + ApiNode getNodeForBacktracking(Node node) { + result = getBackwardStartNode(getALocalSourceStrict(node)) + } + + /** Parts of the shared module to be re-exported by the user-facing `API` module. */ + module Public { + /** + * The signature to use when instantiating the `ExplainFlow` module. + */ + signature module ExplainFlowSig { + /** Holds if `node` should be a source. */ + predicate isSource(ApiNode node); + + /** Holds if `node` should be a sink. */ + default predicate isSink(ApiNode node) { any() } + + /** Holds if `node` should be skipped in the generated paths. */ + default predicate isHidden(ApiNode node) { none() } + } + + /** + * Module to help debug and visualize the data flows underlying API graphs. + * + * This module exports the query predicates for a path-problem query, and should be imported + * into the top-level of such a query. + * + * The module argument should specify source and sink API nodes, and the resulting query + * will show paths of epsilon edges that go from a source to a sink. Only epsilon edges are visualized. + * + * To condense the output a bit, paths in which the source and sink are the same node are omitted. + */ + module ExplainFlow { + private import T + + private ApiNode relevantNode() { + isSink(result) and + result = getAnEpsilonSuccessorInline(any(ApiNode node | isSource(node))) + or + epsilonEdge(result, relevantNode()) + } + + /** Holds if `node` is part of the graph to visualize. */ + query predicate nodes(ApiNode node) { node = relevantNode() and not isHidden(node) } + + private predicate edgeToHiddenNode(ApiNode pred, ApiNode succ) { + epsilonEdge(pred, succ) and + isHidden(succ) and + pred = relevantNode() and + succ = relevantNode() + } + + /** Holds if `pred -> succ` is an edge in the graph to visualize. */ + query predicate edges(ApiNode pred, ApiNode succ) { + nodes(pred) and + nodes(succ) and + exists(ApiNode mid | + edgeToHiddenNode*(pred, mid) and + epsilonEdge(mid, succ) + ) + } + + /** Holds for each source/sink pair to visualize in the graph. */ + query predicate problems( + ApiNode location, ApiNode sourceNode, ApiNode sinkNode, string message + ) { + nodes(sourceNode) and + nodes(sinkNode) and + isSource(sourceNode) and + isSink(sinkNode) and + sinkNode = getAnEpsilonSuccessorInline(sourceNode) and + sourceNode != sinkNode and + location = sinkNode and + message = "Node flows here" + } + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index fb3d7bf828f..001375b4dc5 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -55,10 +55,9 @@ private module Cached { ) } - pragma[nomagic] - private TypeTracker noContentTypeTracker(boolean hasCall) { - result = MkTypeTracker(hasCall, noContent()) - } + /** Gets a type tracker with no content and the call bit set to the given value. */ + cached + TypeTracker noContentTypeTracker(boolean hasCall) { result = MkTypeTracker(hasCall, noContent()) } /** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */ cached @@ -318,6 +317,8 @@ class StepSummary extends TStepSummary { /** Provides predicates for updating step summaries (`StepSummary`s). */ module StepSummary { + predicate append = Cached::append/2; + /** * Gets the summary that corresponds to having taken a forwards * inter-procedural step from `nodeFrom` to `nodeTo`. @@ -378,6 +379,35 @@ module StepSummary { } deprecated predicate localSourceStoreStep = flowsToStoreStep/3; + + /** Gets the step summary for a level step. */ + StepSummary levelStep() { result = LevelStep() } + + /** Gets the step summary for a call step. */ + StepSummary callStep() { result = CallStep() } + + /** Gets the step summary for a return step. */ + StepSummary returnStep() { result = ReturnStep() } + + /** Gets the step summary for storing into `content`. */ + StepSummary storeStep(TypeTrackerContent content) { result = StoreStep(content) } + + /** Gets the step summary for loading from `content`. */ + StepSummary loadStep(TypeTrackerContent content) { result = LoadStep(content) } + + /** Gets the step summary for loading from `load` and then storing into `store`. */ + StepSummary loadStoreStep(TypeTrackerContent load, TypeTrackerContent store) { + result = LoadStoreStep(load, store) + } + + /** Gets the step summary for a step that only permits contents matched by `filter`. */ + StepSummary withContent(ContentFilter filter) { result = WithContent(filter) } + + /** Gets the step summary for a step that blocks contents matched by `filter`. */ + StepSummary withoutContent(ContentFilter filter) { result = WithoutContent(filter) } + + /** Gets the step summary for a jump step. */ + StepSummary jumpStep() { result = JumpStep() } } /** @@ -540,6 +570,13 @@ module TypeTracker { * Gets a valid end point of type tracking. */ TypeTracker end() { result.end() } + + /** + * INTERNAL USE ONLY. + * + * Gets a valid end point of type tracking with the call bit set to the given value. + */ + predicate end = Cached::noContentTypeTracker/1; } pragma[nomagic] diff --git a/ruby/ql/lib/qlpack.yml b/ruby/ql/lib/qlpack.yml index 1cbf8680314..94c5477e126 100644 --- a/ruby/ql/lib/qlpack.yml +++ b/ruby/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ruby-all -version: 0.7.0 +version: 0.7.1 groups: ruby extractor: ruby dbscheme: ruby.dbscheme diff --git a/ruby/ql/src/CHANGELOG.md b/ruby/ql/src/CHANGELOG.md index cbf9e0d037d..f7af0802f0a 100644 --- a/ruby/ql/src/CHANGELOG.md +++ b/ruby/ql/src/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.7.1 + +### New Queries + +* Added a new experimental query, `rb/xpath-injection`, to detect cases where XPath statements are constructed from user input in an unsafe manner. + +### Minor Analysis Improvements + +* Improved resolution of calls performed on an object created with `Proc.new`. + ## 0.7.0 ### Minor Analysis Improvements diff --git a/ruby/ql/src/change-notes/released/0.7.1.md b/ruby/ql/src/change-notes/released/0.7.1.md new file mode 100644 index 00000000000..a5cf1436427 --- /dev/null +++ b/ruby/ql/src/change-notes/released/0.7.1.md @@ -0,0 +1,9 @@ +## 0.7.1 + +### New Queries + +* Added a new experimental query, `rb/xpath-injection`, to detect cases where XPath statements are constructed from user input in an unsafe manner. + +### Minor Analysis Improvements + +* Improved resolution of calls performed on an object created with `Proc.new`. diff --git a/ruby/ql/src/codeql-pack.release.yml b/ruby/ql/src/codeql-pack.release.yml index c761f3e7ab4..e007a9aec3e 100644 --- a/ruby/ql/src/codeql-pack.release.yml +++ b/ruby/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.7.0 +lastReleaseVersion: 0.7.1 diff --git a/ruby/ql/src/experimental/xpath-injection/XpathInjection.qhelp b/ruby/ql/src/experimental/xpath-injection/XpathInjection.qhelp new file mode 100644 index 00000000000..fd11ae8a222 --- /dev/null +++ b/ruby/ql/src/experimental/xpath-injection/XpathInjection.qhelp @@ -0,0 +1,37 @@ + + + + +

    +If an XPath expression is built using string concatenation, and the components of the concatenation +include user input, it makes it very easy for a user to create a malicious XPath expression. +

    +
    + + +

    +If user input must be included in an XPath expression, either sanitize the data or use variable +references to safely embed it without altering the structure of the expression. +

    +
    + + +

    +The following example uses the nokogiri, rexml and libxml XML parsers to parse a string xml. +Then the xpath query is controlled by the user and hence leads to a vulnerability. +

    + + +

    +To guard against XPath Injection attacks, the user input should be sanitized. +

    + +
    + + +
  • +OWASP: +XPath injection. +
  • +
    +
    diff --git a/ruby/ql/src/experimental/xpath-injection/XpathInjection.ql b/ruby/ql/src/experimental/xpath-injection/XpathInjection.ql new file mode 100644 index 00000000000..200ab86fc22 --- /dev/null +++ b/ruby/ql/src/experimental/xpath-injection/XpathInjection.ql @@ -0,0 +1,21 @@ +/** + * @name XPath query built from user-controlled sources + * @description Building a XPath query from user-controlled sources is vulnerable to insertion of + * malicious XPath code by the user. + * @kind path-problem + * @problem.severity error + * @security-severity 9.8 + * @precision high + * @id rb/xpath-injection + * @tags security + * external/cwe/cwe-643 + */ + +import codeql.ruby.DataFlow +import codeql.ruby.security.XpathInjectionQuery +import XpathInjection::PathGraph + +from XpathInjection::PathNode source, XpathInjection::PathNode sink +where XpathInjection::flowPath(source, sink) +select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(), + "user-provided value" diff --git a/ruby/ql/src/experimental/xpath-injection/examples/XPathBad.rb b/ruby/ql/src/experimental/xpath-injection/examples/XPathBad.rb new file mode 100644 index 00000000000..556ff7ae63a --- /dev/null +++ b/ruby/ql/src/experimental/xpath-injection/examples/XPathBad.rb @@ -0,0 +1,45 @@ +require 'nokogiri' +require 'rexml' +require 'libxml' + +class BadNokogiriController < ActionController::Base + def some_request_handler + name = params["name"] + xml = <<-XML + + bar + THIS IS SECRET + + XML + doc = Nokogiri::XML.parse(xml) + results = doc.xpath("//#{name}") + end +end + +class BadRexmlController < ActionController::Base + def some_request_handler + name = params["name"] + xml = <<-XML + + bar + THIS IS SECRET + + XML + doc = REXML::Document.new(xml) + results = REXML::XPath.first(doc, "//#{name}") + end +end + +class BadLibxmlController < ActionController::Base + def some_request_handler + name = params["name"] + xml = <<-XML + + bar + THIS IS SECRET + + XML + doc = LibXML::XML::Document.string(xml) + results = doc.find_first("//#{name}") + end +end \ No newline at end of file diff --git a/ruby/ql/src/experimental/xpath-injection/examples/XPathGood.rb b/ruby/ql/src/experimental/xpath-injection/examples/XPathGood.rb new file mode 100644 index 00000000000..abd3665bc08 --- /dev/null +++ b/ruby/ql/src/experimental/xpath-injection/examples/XPathGood.rb @@ -0,0 +1,60 @@ +require 'nokogiri' +require 'rexml' +require 'libxml' + +class BadNokogiriController < ActionController::Base + def some_request_handler + name = params["name"] + xml = <<-XML + + bar + THIS IS SECRET + + XML + doc = Nokogiri::XML.parse(xml) + name = if ["foo", "foo2"].include? name + name + else + name = "foo" + end + results = doc.xpath("//#{name}") + end +end + +class BadRexmlController < ActionController::Base + def some_request_handler + name = params["name"] + xml = <<-XML + + bar + THIS IS SECRET + + XML + doc = REXML::Document.new(xml) + name = if ["foo", "foo2"].include? name + name + else + name = "foo" + end + results = REXML::XPath.first(doc, "//#{name}") + end +end + +class BadLibxmlController < ActionController::Base + def some_request_handler + name = params["name"] + xml = <<-XML + + bar + THIS IS SECRET + + XML + doc = LibXML::XML::Document.string(xml) + name = if ["foo", "foo2"].include? name + name + else + name = "foo" + end + results = doc.find_first("//#{name}") + end +end \ No newline at end of file diff --git a/ruby/ql/src/qlpack.yml b/ruby/ql/src/qlpack.yml index fd905f3ad51..19997bb0fd3 100644 --- a/ruby/ql/src/qlpack.yml +++ b/ruby/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ruby-queries -version: 0.7.0 +version: 0.7.1 groups: - ruby - queries diff --git a/ruby/ql/src/queries/diagnostics/PerformanceDiagnostics.ql b/ruby/ql/src/queries/diagnostics/PerformanceDiagnostics.ql new file mode 100644 index 00000000000..daedf6f31ea --- /dev/null +++ b/ruby/ql/src/queries/diagnostics/PerformanceDiagnostics.ql @@ -0,0 +1,117 @@ +/** + * @id rb/performance-diagnostics + * @kind table + */ + +/* + * This query outputs some numbers that can be used to help narrow down the cause of performance + * issues in a codebase, without leaking any actual code or identifiers from the codebase. + */ + +import ruby +import codeql.ruby.ApiGraphs + +query int numberOfModuleBases() { result = count(Ast::ModuleBase cls) } + +query int numberOfClasses() { result = count(Ast::ClassDeclaration cls) } + +query int numberOfMethods() { result = count(Ast::MethodBase method) } + +query int numberOfCallables() { result = count(Ast::Callable c) } + +query int numberOfMethodCalls() { result = count(Ast::MethodCall call) } + +query int numberOfCalls() { result = count(Ast::Call call) } + +signature module HistogramSig { + bindingset[this] + class Bucket; + + int getCounts(Bucket bucket); +} + +module MakeHistogram { + predicate histogram(int bucketSize, int frequency) { + frequency = strictcount(H::Bucket bucket | H::getCounts(bucket) = bucketSize) + } +} + +module MethodNames implements HistogramSig { + class Bucket = string; + + int getCounts(string name) { + result = strictcount(Ast::MethodBase method | method.getName() = name) and + name != "initialize" + } +} + +query predicate numberOfMethodsWithNameHistogram = MakeHistogram::histogram/2; + +module CallTargets implements HistogramSig { + class Bucket = Ast::Call; + + int getCounts(Ast::Call call) { result = count(call.getATarget()) } +} + +query predicate numberOfCallTargetsHistogram = MakeHistogram::histogram/2; + +module Callers implements HistogramSig { + class Bucket = Ast::Callable; + + int getCounts(Ast::Callable callable) { + result = count(Ast::Call call | call.getATarget() = callable) + } +} + +query predicate numberOfCallersHistogram = MakeHistogram::histogram/2; + +private DataFlow::MethodNode getAnOverriddenMethod(DataFlow::MethodNode method) { + exists(DataFlow::ModuleNode cls, string name | + method = cls.getInstanceMethod(name) and + result = cls.getAnAncestor().getInstanceMethod(name) and + result != method + ) +} + +module MethodOverrides implements HistogramSig { + class Bucket = DataFlow::MethodNode; + + int getCounts(DataFlow::MethodNode method) { result = count(getAnOverriddenMethod(method)) } +} + +query predicate numberOfOverriddenMethodsHistogram = MakeHistogram::histogram/2; + +module MethodOverriddenBy implements HistogramSig { + class Bucket = DataFlow::MethodNode; + + int getCounts(DataFlow::MethodNode method) { + result = count(DataFlow::MethodNode overrider | method = getAnOverriddenMethod(overrider)) + } +} + +query predicate numberOfOverridingMethodsHistogram = MakeHistogram::histogram/2; + +module Ancestors implements HistogramSig { + class Bucket = DataFlow::ModuleNode; + + int getCounts(DataFlow::ModuleNode mod) { + result = + count(DataFlow::ModuleNode ancestor | ancestor = mod.getAnAncestor() and ancestor != mod) + } +} + +query predicate numberOfAncestorsHistogram = MakeHistogram::histogram/2; + +module Descendents implements HistogramSig { + class Bucket = DataFlow::ModuleNode; + + int getCounts(DataFlow::ModuleNode mod) { + not mod.getQualifiedName() = ["Object", "Kernel", "BasicObject", "Class", "Module"] and + result = + count(DataFlow::ModuleNode descendent | + descendent = mod.getADescendent() and descendent != mod + ) + } +} + +query predicate numberOfDescendentsHistogram = MakeHistogram::histogram/2; diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.expected b/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.expected index 43b6490b052..5677b16fedb 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.expected +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.expected @@ -1,8 +1,8 @@ classMethodCalls -| test1.rb:58:1:58:8 | Use getMember("M1").getMember("C1").getMethod("m").getReturn() | -| test1.rb:59:1:59:8 | Use getMember("M2").getMember("C3").getMethod("m").getReturn() | +| test1.rb:58:1:58:8 | ForwardNode(call to m) | +| test1.rb:59:1:59:8 | ForwardNode(call to m) | instanceMethodCalls -| test1.rb:61:1:61:12 | Use getMember("M1").getMember("C1").getMethod("new").getReturn().getMethod("m").getReturn() | -| test1.rb:62:1:62:12 | Use getMember("M2").getMember("C3").getMethod("new").getReturn().getMethod("m").getReturn() | +| test1.rb:61:1:61:12 | ForwardNode(call to m) | +| test1.rb:62:1:62:12 | ForwardNode(call to m) | flowThroughArray | test1.rb:73:1:73:10 | call to m | diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.expected b/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.expected new file mode 100644 index 00000000000..48de9172b36 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.expected @@ -0,0 +1,2 @@ +failures +testFailures diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.ql b/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.ql new file mode 100644 index 00000000000..93b5aaf745e --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.ql @@ -0,0 +1,77 @@ +import ruby +import codeql.ruby.ast.internal.TreeSitter +import codeql.ruby.dataflow.internal.AccessPathSyntax +import codeql.ruby.frameworks.data.internal.ApiGraphModels +import codeql.ruby.ApiGraphs +import TestUtilities.InlineExpectationsTest + +class AccessPathFromExpectation extends AccessPath::Range { + AccessPathFromExpectation() { hasExpectationWithValue(_, this) } +} + +API::Node evaluatePath(AccessPath path, int n) { + path instanceof AccessPathFromExpectation and + n = 1 and + exists(AccessPathToken token | token = path.getToken(0) | + token.getName() = "Member" and + result = API::getTopLevelMember(token.getAnArgument()) + or + token.getName() = "Method" and + result = API::getTopLevelCall(token.getAnArgument()) + or + token.getName() = "EntryPoint" and + result = token.getAnArgument().(API::EntryPoint).getANode() + ) + or + result = getSuccessorFromNode(evaluatePath(path, n - 1), path.getToken(n - 1)) + or + result = getSuccessorFromInvoke(evaluatePath(path, n - 1), path.getToken(n - 1)) + or + // TODO this is a workaround, support parsing of Method['[]'] instead + path.getToken(n - 1).getName() = "MethodBracket" and + result = evaluatePath(path, n - 1).getMethod("[]") +} + +API::Node evaluatePath(AccessPath path) { result = evaluatePath(path, path.getNumToken()) } + +module ApiUseTest implements TestSig { + string getARelevantTag() { result = ["source", "sink", "call", "reachableFromSource"] } + + predicate hasActualResult(Location location, string element, string tag, string value) { + // All results are considered optional + none() + } + + predicate hasOptionalResult(Location location, string element, string tag, string value) { + exists(API::Node apiNode, DataFlow::Node dataflowNode | + apiNode = evaluatePath(value) and + ( + tag = "source" and dataflowNode = apiNode.asSource() + or + tag = "reachableFromSource" and dataflowNode = apiNode.getAValueReachableFromSource() + or + tag = "sink" and dataflowNode = apiNode.asSink() + or + tag = "call" and dataflowNode = apiNode.asCall() + ) and + location = dataflowNode.getLocation() and + element = dataflowNode.toString() + ) + } +} + +import MakeTest + +class CustomEntryPointCall extends API::EntryPoint { + CustomEntryPointCall() { this = "CustomEntryPointCall" } + + override DataFlow::CallNode getACall() { result.getMethodName() = "customEntryPointCall" } +} + +class CustomEntryPointUse extends API::EntryPoint { + CustomEntryPointUse() { this = "CustomEntryPointUse" } + + override DataFlow::LocalSourceNode getASource() { + result.(DataFlow::CallNode).getMethodName() = "customEntryPointUse" + } +} diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/callbacks.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/callbacks.rb index 34c4d17d212..35cf4471b48 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/callbacks.rb +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/callbacks.rb @@ -1,39 +1,39 @@ -Something.foo.withCallback do |a, b| #$ use=getMember("Something").getMethod("foo").getReturn() - a.something #$ use=getMember("Something").getMethod("foo").getReturn().getMethod("withCallback").getBlock().getParameter(0).getMethod("something").getReturn() - b.somethingElse #$ use=getMember("Something").getMethod("foo").getReturn().getMethod("withCallback").getBlock().getParameter(1).getMethod("somethingElse").getReturn() -end #$ use=getMember("Something").getMethod("foo").getReturn().getMethod("withCallback").getReturn() +Something.foo.withCallback do |a, b| #$ source=Member[Something].Method[foo].ReturnValue + a.something #$ source=Member[Something].Method[foo].ReturnValue.Method[withCallback].Argument[block].Argument[0].Method[something].ReturnValue + b.somethingElse #$ source=Member[Something].Method[foo].ReturnValue.Method[withCallback].Argument[block].Argument[1].Method[somethingElse].ReturnValue +end #$ source=Member[Something].Method[foo].ReturnValue.Method[withCallback].ReturnValue -Something.withNamedArg do |a:, b: nil| #$ use=getMember("Something") - a.something #$ use=getMember("Something").getMethod("withNamedArg").getBlock().getKeywordParameter("a").getMethod("something").getReturn() - b.somethingElse #$ use=getMember("Something").getMethod("withNamedArg").getBlock().getKeywordParameter("b").getMethod("somethingElse").getReturn() -end #$ use=getMember("Something").getMethod("withNamedArg").getReturn() +Something.withNamedArg do |a:, b: nil| #$ source=Member[Something] + a.something #$ source=Member[Something].Method[withNamedArg].Argument[block].Parameter[a:].Method[something].ReturnValue + b.somethingElse #$ source=Member[Something].Method[withNamedArg].Argument[block].Parameter[b:].Method[somethingElse].ReturnValue +end #$ source=Member[Something].Method[withNamedArg].ReturnValue -Something.withLambda ->(a, b) { #$ use=getMember("Something") - a.something #$ use=getMember("Something").getMethod("withLambda").getParameter(0).getParameter(0).getMethod("something").getReturn() - b.something #$ use=getMember("Something").getMethod("withLambda").getParameter(0).getParameter(1).getMethod("something").getReturn() -} #$ use=getMember("Something").getMethod("withLambda").getReturn() +Something.withLambda ->(a, b) { #$ source=Member[Something] + a.something #$ source=Member[Something].Method[withLambda].Argument[0].Parameter[0].Method[something].ReturnValue + b.something #$ source=Member[Something].Method[withLambda].Argument[0].Parameter[1].Method[something].ReturnValue +} #$ source=Member[Something].Method[withLambda].ReturnValue -Something.namedCallback( #$ use=getMember("Something") +Something.namedCallback( #$ source=Member[Something] onEvent: ->(a, b) { - a.something #$ use=getMember("Something").getMethod("namedCallback").getKeywordParameter("onEvent").getParameter(0).getMethod("something").getReturn() - b.something #$ use=getMember("Something").getMethod("namedCallback").getKeywordParameter("onEvent").getParameter(1).getMethod("something").getReturn() + a.something #$ source=Member[Something].Method[namedCallback].Argument[onEvent:].Parameter[0].Method[something].ReturnValue + b.something #$ source=Member[Something].Method[namedCallback].Argument[onEvent:].Parameter[1].Method[something].ReturnValue } -) #$ use=getMember("Something").getMethod("namedCallback").getReturn() +) #$ source=Member[Something].Method[namedCallback].ReturnValue -Something.nestedCall1 do |a| #$ use=getMember("Something") - a.nestedCall2 do |b:| #$ use=getMember("Something").getMethod("nestedCall1").getBlock().getParameter(0) - b.something #$ use=getMember("Something").getMethod("nestedCall1").getBlock().getParameter(0).getMethod("nestedCall2").getBlock().getKeywordParameter("b").getMethod("something").getReturn() - end #$ use=getMember("Something").getMethod("nestedCall1").getBlock().getParameter(0).getMethod("nestedCall2").getReturn() -end #$ use=getMember("Something").getMethod("nestedCall1").getReturn() +Something.nestedCall1 do |a| #$ source=Member[Something] + a.nestedCall2 do |b:| #$ reachableFromSource=Member[Something].Method[nestedCall1].Argument[block].Parameter[0] + b.something #$ source=Member[Something].Method[nestedCall1].Argument[block].Parameter[0].Method[nestedCall2].Argument[block].Parameter[b:].Method[something].ReturnValue + end #$ source=Member[Something].Method[nestedCall1].Argument[block].Parameter[0].Method[nestedCall2].ReturnValue +end #$ source=Member[Something].Method[nestedCall1].ReturnValue def getCallback() ->(x) { - x.something #$ use=getMember("Something").getMethod("indirectCallback").getParameter(0).getParameter(0).getMethod("something").getReturn() + x.something #$ source=Member[Something].Method[indirectCallback].Argument[0].Parameter[0].Method[something].ReturnValue } end -Something.indirectCallback(getCallback()) #$ use=getMember("Something").getMethod("indirectCallback").getReturn() +Something.indirectCallback(getCallback()) #$ source=Member[Something].Method[indirectCallback].ReturnValue -Something.withMixed do |a, *args, b| #$ use=getMember("Something") - a.something #$ use=getMember("Something").getMethod("withMixed").getBlock().getParameter(0).getMethod("something").getReturn() +Something.withMixed do |a, *args, b| #$ source=Member[Something] + a.something #$ source=Member[Something].Method[withMixed].Argument[block].Parameter[0].Method[something].ReturnValue # b.something # not currently handled correctly -end #$ use=getMember("Something").getMethod("withMixed").getReturn() +end #$ source=Member[Something].Method[withMixed].ReturnValue diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/chained-access.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/chained-access.rb new file mode 100644 index 00000000000..b0e4f07e701 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/chained-access.rb @@ -0,0 +1,31 @@ +def chained_access1 + Something.foo [[[ + 'sink' # $ sink=Member[Something].Method[foo].Argument[0].Element[0].Element[0].Element[0] + ]]] +end + +def chained_access2 + array = [] + array[0] = [[ + 'sink' # $ sink=Member[Something].Method[foo].Argument[0].Element[0].Element[0].Element[0] + ]] + Something.foo array +end + +def chained_access3 + array = [[]] + array[0][0] = [ + 'sink' # $ sink=Member[Something].Method[foo].Argument[0].Element[0].Element[0].Element[0] + ] + Something.foo array +end + +def chained_access4 + Something.foo { + :one => { + :two => { + :three => 'sink' # $ sink=Member[Something].Method[foo].Argument[0].Element[:one].Element[:two].Element[:three] + } + } + } +end diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/explicit-proc.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/explicit-proc.rb new file mode 100644 index 00000000000..f68b0333531 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/explicit-proc.rb @@ -0,0 +1,11 @@ +Foo.bar proc { |x| + x # $ reachableFromSource=Member[Foo].Method[bar].Argument[0].Parameter[0] +} + +Foo.bar lambda { |x| + x # $ reachableFromSource=Member[Foo].Method[bar].Argument[0].Parameter[0] +} + +Foo.bar Proc.new { |x| + x # $ reachableFromSource=Member[Foo].Method[bar].Argument[0].Parameter[0] +} diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/method-callbacks.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/method-callbacks.rb new file mode 100644 index 00000000000..63a4b4c3dce --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/method-callbacks.rb @@ -0,0 +1,64 @@ +class BaseClass + def inheritedInstanceMethod + yield "taint" # $ sink=Member[Something].Method[foo].Argument[block].ReturnValue.Method[inheritedInstanceMethod].Parameter[block].Argument[0] + end + + def self.inheritedSingletonMethod + yield "taint" # $ sink=Member[Something].Method[bar].Argument[block].ReturnValue.Method[inheritedSingletonMethod].Parameter[block].Argument[0] + end +end + +class ClassWithCallbacks < BaseClass + def instanceMethod + yield "taint" # $ sink=Member[Something].Method[foo].Argument[block].ReturnValue.Method[instanceMethod].Parameter[block].Argument[0] + end + + def self.singletonMethod + yield "bar" # $ sink=Member[Something].Method[bar].Argument[block].ReturnValue.Method[singletonMethod].Parameter[block].Argument[0] + end + + def escapeSelf + Something.baz { self } + end + + def self.escapeSingletonSelf + Something.baz { self } + end + + def self.foo x + x # $ reachableFromSource=Member[BaseClass].Method[foo].Parameter[0] + x # $ reachableFromSource=Member[ClassWithCallbacks].Method[foo].Parameter[0] + x # $ reachableFromSource=Member[Subclass].Method[foo].Parameter[0] + end + + def bar x + x # $ reachableFromSource=Member[BaseClass].Instance.Method[bar].Parameter[0] + x # $ reachableFromSource=Member[ClassWithCallbacks].Instance.Method[bar].Parameter[0] + x # $ reachableFromSource=Member[Subclass].Instance.Method[bar].Parameter[0] + end +end + +class Subclass < ClassWithCallbacks + def instanceMethodInSubclass + yield "bar" # $ sink=Member[Something].Method[baz].Argument[block].ReturnValue.Method[instanceMethodInSubclass].Parameter[block].Argument[0] + end + + def self.singletonMethodInSubclass + yield "bar" # $ sink=Member[Something].Method[baz].Argument[block].ReturnValue.Method[singletonMethodInSubclass].Parameter[block].Argument[0] + end +end + +Something.foo { ClassWithCallbacks.new } +Something.bar { ClassWithCallbacks } + +class ClassWithCallMethod + def call x + x # $ reachableFromSource=Method[topLevelMethod].Argument[0].Parameter[0] + "bar" # $ sink=Method[topLevelMethod].Argument[0].ReturnValue + end +end + +topLevelMethod ClassWithCallMethod.new + +blah = topLevelMethod +blah # $ reachableFromSource=Method[topLevelMethod].ReturnValue diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/self-dot-class.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/self-dot-class.rb new file mode 100644 index 00000000000..178cacbe2c0 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/self-dot-class.rb @@ -0,0 +1,10 @@ +module SelfDotClass + module Mixin + def foo + self.class.bar # $ call=Member[Foo].Method[bar] + end + end + class Subclass < Foo + include Mixin + end +end diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb index 86b8bce9587..3af793dd4f7 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb @@ -1,34 +1,34 @@ -MyModule #$ use=getMember("MyModule") -print MyModule.foo #$ use=getMember("MyModule").getMethod("foo").getReturn() -Kernel.print(e) #$ use=getMember("Kernel").getMethod("print").getReturn() def=getMember("Kernel").getMethod("print").getParameter(0) -Object::Kernel #$ use=getMember("Kernel") -Object::Kernel.print(e) #$ use=getMember("Kernel").getMethod("print").getReturn() +MyModule #$ source=Member[MyModule] +print MyModule.foo #$ source=Member[MyModule].Method[foo].ReturnValue +Kernel.print(e) #$ source=Member[Kernel].Method[print].ReturnValue sink=Member[Kernel].Method[print].Argument[0] +Object::Kernel #$ source=Member[Kernel] +Object::Kernel.print(e) #$ source=Member[Kernel].Method[print].ReturnValue begin - print MyModule.bar #$ use=getMember("MyModule").getMethod("bar").getReturn() - raise AttributeError #$ use=getMember("AttributeError") -rescue AttributeError => e #$ use=getMember("AttributeError") - Kernel.print(e) #$ use=getMember("Kernel").getMethod("print").getReturn() + print MyModule.bar #$ source=Member[MyModule].Method[bar].ReturnValue + raise AttributeError #$ source=Member[AttributeError] +rescue AttributeError => e #$ source=Member[AttributeError] + Kernel.print(e) #$ source=Member[Kernel].Method[print].ReturnValue end -Unknown.new.run #$ use=getMember("Unknown").getMethod("new").getReturn().getMethod("run").getReturn() -Foo::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz") +Unknown.new.run #$ source=Member[Unknown].Method[new].ReturnValue.Method[run].ReturnValue +Foo::Bar::Baz #$ source=Member[Foo].Member[Bar].Member[Baz] -Const = [1, 2, 3] #$ use=getMember("Array").getMethod("[]").getReturn() -Const.each do |c| #$ use=getMember("Const") - puts c #$ use=getMember("Const").getMethod("each").getBlock().getParameter(0) use=getMember("Const").getContent(element) -end #$ use=getMember("Const").getMethod("each").getReturn() def=getMember("Const").getMethod("each").getBlock() +Const = [1, 2, 3] #$ source=Member[Array].MethodBracket.ReturnValue +Const.each do |c| #$ source=Member[Const] + puts c #$ reachableFromSource=Member[Const].Method[each].Argument[block].Parameter[0] reachableFromSource=Member[Const].Element[any] +end #$ source=Member[Const].Method[each].ReturnValue sink=Member[Const].Method[each].Argument[block] -foo = Foo #$ use=getMember("Foo") -foo::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz") +foo = Foo #$ source=Member[Foo] +foo::Bar::Baz #$ source=Member[Foo].Member[Bar].Member[Baz] -FooAlias = Foo #$ use=getMember("Foo") -FooAlias::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz") +FooAlias = Foo #$ source=Member[Foo] +FooAlias::Bar::Baz #$ source=Member[Foo].Member[Bar].Member[Baz] source=Member[FooAlias].Member[Bar].Member[Baz] module Outer module Inner end end -Outer::Inner.foo #$ use=getMember("Outer").getMember("Inner").getMethod("foo").getReturn() +Outer::Inner.foo #$ source=Member[Outer].Member[Inner].Method[foo].ReturnValue module M1 class C1 @@ -40,36 +40,36 @@ module M1 end end -class C2 < M1::C1 #$ use=getMember("M1").getMember("C1") +class C2 < M1::C1 #$ source=Member[M1].Member[C1] end module M2 - class C3 < M1::C1 #$ use=getMember("M1").getMember("C1") + class C3 < M1::C1 #$ source=Member[M1].Member[C1] end - class C4 < C2 #$ use=getMember("C2") + class C4 < C2 #$ source=Member[C2] end end -C2 #$ use=getMember("C2") use=getMember("M1").getMember("C1").getASubclass() -M2::C3 #$ use=getMember("M2").getMember("C3") use=getMember("M1").getMember("C1").getASubclass() -M2::C4 #$ use=getMember("M2").getMember("C4") use=getMember("C2").getASubclass() use=getMember("M1").getMember("C1").getASubclass().getASubclass() +C2 #$ source=Member[C2] reachableFromSource=Member[M1].Member[C1] +M2::C3 #$ source=Member[M2].Member[C3] reachableFromSource=Member[M1].Member[C1] +M2::C4 #$ source=Member[M2].Member[C4] reachableFromSource=Member[C2] reachableFromSource=Member[M1].Member[C1] -M1::C1.m #$ use=getMember("M1").getMember("C1").getMethod("m").getReturn() -M2::C3.m #$ use=getMember("M2").getMember("C3").getMethod("m").getReturn() use=getMember("M1").getMember("C1").getASubclass().getMethod("m").getReturn() +M1::C1.m #$ source=Member[M1].Member[C1].Method[m].ReturnValue +M2::C3.m #$ source=Member[M2].Member[C3].Method[m].ReturnValue source=Member[M1].Member[C1].Method[m].ReturnValue -M1::C1.new.m #$ use=getMember("M1").getMember("C1").getMethod("new").getReturn().getMethod("m").getReturn() -M2::C3.new.m #$ use=getMember("M2").getMember("C3").getMethod("new").getReturn().getMethod("m").getReturn() +M1::C1.new.m #$ source=Member[M1].Member[C1].Method[new].ReturnValue.Method[m].ReturnValue +M2::C3.new.m #$ source=Member[M2].Member[C3].Method[new].ReturnValue.Method[m].ReturnValue -Foo.foo(a,b:c) #$ use=getMember("Foo").getMethod("foo").getReturn() def=getMember("Foo").getMethod("foo").getParameter(0) def=getMember("Foo").getMethod("foo").getKeywordParameter("b") +Foo.foo(a,b:c) #$ source=Member[Foo].Method[foo].ReturnValue sink=Member[Foo].Method[foo].Argument[0] sink=Member[Foo].Method[foo].Argument[b:] def userDefinedFunction(x, y) x.noApiGraph(y) - x.customEntryPointCall(y) #$ call=entryPoint("CustomEntryPointCall") use=entryPoint("CustomEntryPointCall").getReturn() rhs=entryPoint("CustomEntryPointCall").getParameter(0) - x.customEntryPointUse(y) #$ use=entryPoint("CustomEntryPointUse") + x.customEntryPointCall(y) #$ call=EntryPoint[CustomEntryPointCall] source=EntryPoint[CustomEntryPointCall].ReturnValue sink=EntryPoint[CustomEntryPointCall].Parameter[0] + x.customEntryPointUse(y) #$ source=EntryPoint[CustomEntryPointUse] end -array = [A::B::C] #$ use=getMember("Array").getMethod("[]").getReturn() -array[0].m #$ use=getMember("A").getMember("B").getMember("C").getMethod("m").getReturn() +array = [A::B::C] #$ source=Member[Array].MethodBracket.ReturnValue +array[0].m #$ source=Member[A].Member[B].Member[C].Method[m].ReturnValue source=Member[Array].MethodBracket.ReturnValue.Element[0].Method[m].ReturnValue -A::B::C[0] #$ use=getMember("A").getMember("B").getMember("C").getContent(element_0) +A::B::C[0] #$ source=Member[A].Member[B].Member[C].Element[0] diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql b/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql deleted file mode 100644 index a0c11640ce0..00000000000 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql +++ /dev/null @@ -1,88 +0,0 @@ -import codeql.ruby.AST -import codeql.ruby.DataFlow -import TestUtilities.InlineExpectationsTest -import codeql.ruby.ApiGraphs - -class CustomEntryPointCall extends API::EntryPoint { - CustomEntryPointCall() { this = "CustomEntryPointCall" } - - override DataFlow::CallNode getACall() { result.getMethodName() = "customEntryPointCall" } -} - -class CustomEntryPointUse extends API::EntryPoint { - CustomEntryPointUse() { this = "CustomEntryPointUse" } - - override DataFlow::LocalSourceNode getASource() { - result.(DataFlow::CallNode).getMethodName() = "customEntryPointUse" - } -} - -module ApiUseTest implements TestSig { - string getARelevantTag() { result = ["use", "def", "call"] } - - private predicate relevantNode(API::Node a, DataFlow::Node n, Location l, string tag) { - l = n.getLocation() and - ( - tag = "use" and - n = a.getAValueReachableFromSource() - or - tag = "def" and - n = a.asSink() - or - tag = "call" and - n = a.(API::MethodAccessNode).getCallNode() - ) - } - - predicate hasActualResult(Location location, string element, string tag, string value) { - tag = "use" and // def tags are always optional - exists(DataFlow::Node n | relevantNode(_, n, location, tag) | - // Only report the longest path on this line: - value = - max(API::Node a2, Location l2, DataFlow::Node n2 | - relevantNode(a2, n2, l2, tag) and - l2.getFile() = location.getFile() and - l2.getEndLine() = location.getEndLine() - | - a2.getPath() - order by - size(n2.asExpr().getExpr()), a2.getPath().length() desc, a2.getPath() desc - ) and - element = n.toString() - ) - } - - // We also permit optional annotations for any other path on the line. - // This is used to test subclass paths, which typically have a shorter canonical path. - predicate hasOptionalResult(Location location, string element, string tag, string value) { - exists(API::Node a, DataFlow::Node n | relevantNode(a, n, location, tag) | - element = n.toString() and - value = getAPath(a, _) - ) - } -} - -import MakeTest - -private int size(AstNode n) { not n instanceof StmtSequence and result = count(n.getAChild*()) } - -/** - * Gets a path of the given `length` from the root to the given node. - * This is a copy of `API::getAPath()` without the restriction on path length, - * which would otherwise rule out paths involving `getASubclass()`. - */ -string getAPath(API::Node node, int length) { - node instanceof API::Root and - length = 0 and - result = "" - or - exists(API::Node pred, API::Label::ApiLabel lbl, string predpath | - pred.getASuccessor(lbl) = node and - predpath = getAPath(pred, length - 1) and - exists(string dot | if length = 1 then dot = "" else dot = "." | - result = predpath + dot + lbl and - // avoid producing strings longer than 1MB - result.length() < 1000 * 1000 - ) - ) -} diff --git a/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected b/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected index a153be439f4..b92a74018fe 100644 --- a/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected +++ b/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected @@ -2816,6 +2816,7 @@ | file://:0:0:0:0 | [summary param] position 0 in Mysql2::Client.escape() | file://:0:0:0:0 | [summary] to write: ReturnValue in Mysql2::Client.escape() | | file://:0:0:0:0 | [summary param] position 0 in Mysql2::Client.new() | file://:0:0:0:0 | [summary] to write: ReturnValue in Mysql2::Client.new() | | file://:0:0:0:0 | [summary param] position 0 in PG.new() | file://:0:0:0:0 | [summary] to write: ReturnValue in PG.new() | +| file://:0:0:0:0 | [summary param] position 0 in Rack::Utils.parse_query | file://:0:0:0:0 | [summary] to write: ReturnValue in Rack::Utils.parse_query | | file://:0:0:0:0 | [summary param] position 0 in SQLite3::Database.quote() | file://:0:0:0:0 | [summary] to write: ReturnValue in SQLite3::Database.quote() | | file://:0:0:0:0 | [summary param] position 0 in Sequel.connect | file://:0:0:0:0 | [summary] to write: ReturnValue in Sequel.connect | | file://:0:0:0:0 | [summary param] position 0 in String.try_convert | file://:0:0:0:0 | [summary] to write: ReturnValue in String.try_convert | diff --git a/ruby/ql/test/library-tests/frameworks/Twirp/Twirp.expected b/ruby/ql/test/library-tests/frameworks/Twirp/Twirp.expected index 66da43ab78b..4f1b0c30920 100644 --- a/ruby/ql/test/library-tests/frameworks/Twirp/Twirp.expected +++ b/ruby/ql/test/library-tests/frameworks/Twirp/Twirp.expected @@ -1,6 +1,8 @@ sourceTest | hello_world_server.rb:8:13:8:15 | req | +| hello_world_server.rb:32:18:32:20 | req | ssrfSinkTest | hello_world_client.rb:6:47:6:75 | "http://localhost:8080/twirp" | serviceInstantiationTest | hello_world_server.rb:24:11:24:61 | call to new | +| hello_world_server.rb:38:1:38:57 | call to new | diff --git a/ruby/ql/test/library-tests/frameworks/Twirp/Twirp.ql b/ruby/ql/test/library-tests/frameworks/Twirp/Twirp.ql index 4c0494f9100..fee49cbb48c 100644 --- a/ruby/ql/test/library-tests/frameworks/Twirp/Twirp.ql +++ b/ruby/ql/test/library-tests/frameworks/Twirp/Twirp.ql @@ -5,4 +5,4 @@ query predicate sourceTest(Twirp::UnmarshaledParameter source) { any() } query predicate ssrfSinkTest(Twirp::ServiceUrlAsSsrfSink sink) { any() } -query predicate serviceInstantiationTest(Twirp::ServiceInstantiation si) { any() } +deprecated query predicate serviceInstantiationTest(Twirp::ServiceInstantiation si) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/Twirp/hello_world_server.rb b/ruby/ql/test/library-tests/frameworks/Twirp/hello_world_server.rb index 1aa0b9aa8de..7cd117a5843 100644 --- a/ruby/ql/test/library-tests/frameworks/Twirp/hello_world_server.rb +++ b/ruby/ql/test/library-tests/frameworks/Twirp/hello_world_server.rb @@ -5,7 +5,7 @@ require_relative 'hello_world/service_twirp.rb' class HelloWorldHandler # test: request - def hello(req, env) + def hello(req, env) puts ">> Hello #{req.name}" {message: "Hello #{req.name}"} end @@ -13,7 +13,7 @@ end class FakeHelloWorldHandler # test: !request - def hello(req, env) + def hello(req, env) puts ">> Hello #{req.name}" {message: "Hello #{req.name}"} end @@ -21,9 +21,18 @@ end handler = HelloWorldHandler.new() # test: serviceInstantiation -service = Example::HelloWorld::HelloWorldService.new(handler) +service = Example::HelloWorld::HelloWorldService.new(handler) path_prefix = "/twirp/" + service.full_name server = WEBrick::HTTPServer.new(Port: 8080) server.mount path_prefix, Rack::Handler::WEBrick, service server.start + +class StaticHandler + def self.hello(req, env) + puts ">> Hello #{req.name}" + {message: "Hello #{req.name}"} + end +end + +Example::HelloWorld::HelloWorldService.new(StaticHandler) diff --git a/ruby/ql/test/library-tests/frameworks/action_dispatch/ActionDispatch.expected b/ruby/ql/test/library-tests/frameworks/action_dispatch/ActionDispatch.expected index 71327350941..4eacd48bd60 100644 --- a/ruby/ql/test/library-tests/frameworks/action_dispatch/ActionDispatch.expected +++ b/ruby/ql/test/library-tests/frameworks/action_dispatch/ActionDispatch.expected @@ -55,12 +55,12 @@ underscore | LotsOfCapitalLetters | lots_of_capital_letters | | invalid | invalid | mimeTypeInstances -| mime_type.rb:2:6:2:28 | Use getMember("Mime").getContent(element_text/html) | -| mime_type.rb:3:6:3:32 | Use getMember("Mime").getMember("Type").getMethod("new").getReturn() | -| mime_type.rb:4:6:4:35 | Use getMember("Mime").getMember("Type").getMethod("lookup").getReturn() | -| mime_type.rb:5:6:5:43 | Use getMember("Mime").getMember("Type").getMethod("lookup_by_extension").getReturn() | -| mime_type.rb:6:6:6:47 | Use getMember("Mime").getMember("Type").getMethod("register").getReturn() | -| mime_type.rb:7:6:7:64 | Use getMember("Mime").getMember("Type").getMethod("register_alias").getReturn() | +| mime_type.rb:2:6:2:28 | ForwardNode(call to fetch) | +| mime_type.rb:3:6:3:32 | ForwardNode(call to new) | +| mime_type.rb:4:6:4:35 | ForwardNode(call to lookup) | +| mime_type.rb:5:6:5:43 | ForwardNode(call to lookup_by_extension) | +| mime_type.rb:6:6:6:47 | ForwardNode(call to register) | +| mime_type.rb:7:6:7:64 | ForwardNode(call to register_alias) | mimeTypeMatchRegExpInterpretations | mime_type.rb:11:11:11:19 | "foo/bar" | | mime_type.rb:12:7:12:15 | "foo/bar" | diff --git a/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expected b/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expected index 0374a54c0c1..a3ee4ebebf5 100644 --- a/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expected +++ b/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expected @@ -10,6 +10,7 @@ activeRecordInstances | ActiveRecord.rb:9:5:9:68 | call to find | | ActiveRecord.rb:13:5:13:40 | call to find_by | | ActiveRecord.rb:13:5:13:46 | call to users | +| ActiveRecord.rb:35:5:35:51 | call to authenticate | | ActiveRecord.rb:36:5:36:30 | call to find_by_name | | ActiveRecord.rb:55:5:57:7 | if ... | | ActiveRecord.rb:55:43:56:40 | then ... | @@ -107,12 +108,14 @@ activeRecordSqlExecutionRanges | ActiveRecord.rb:19:16:19:24 | condition | | ActiveRecord.rb:28:30:28:44 | ...[...] | | ActiveRecord.rb:29:20:29:42 | "id = '#{...}'" | +| ActiveRecord.rb:30:21:30:45 | call to [] | | ActiveRecord.rb:30:22:30:44 | "id = '#{...}'" | | ActiveRecord.rb:31:16:31:21 | <<-SQL | | ActiveRecord.rb:34:20:34:47 | "user.id = '#{...}'" | | ActiveRecord.rb:46:20:46:32 | ... + ... | | ActiveRecord.rb:52:16:52:28 | "name #{...}" | | ActiveRecord.rb:56:20:56:39 | "username = #{...}" | +| ActiveRecord.rb:68:21:68:44 | ...[...] | | ActiveRecord.rb:106:27:106:76 | "this is an unsafe annotation:..." | activeRecordModelClassMethodCalls | ActiveRecord.rb:2:3:2:17 | call to has_many | @@ -127,7 +130,6 @@ activeRecordModelClassMethodCalls | ActiveRecord.rb:31:5:31:35 | call to where | | ActiveRecord.rb:34:5:34:14 | call to where | | ActiveRecord.rb:34:5:34:48 | call to not | -| ActiveRecord.rb:35:5:35:51 | call to authenticate | | ActiveRecord.rb:36:5:36:30 | call to find_by_name | | ActiveRecord.rb:37:5:37:36 | call to not_a_find_by_method | | ActiveRecord.rb:46:5:46:33 | call to delete_by | @@ -135,7 +137,6 @@ activeRecordModelClassMethodCalls | ActiveRecord.rb:56:7:56:40 | call to find_by | | ActiveRecord.rb:60:5:60:33 | call to find_by | | ActiveRecord.rb:62:5:62:34 | call to find | -| ActiveRecord.rb:68:5:68:45 | call to delete_by | | ActiveRecord.rb:72:5:72:24 | call to create | | ActiveRecord.rb:76:5:76:66 | call to create | | ActiveRecord.rb:80:5:80:68 | call to create | @@ -152,6 +153,96 @@ activeRecordModelClassMethodCalls | associations.rb:12:3:12:32 | call to has_and_belongs_to_many | | associations.rb:16:3:16:18 | call to belongs_to | | associations.rb:19:11:19:20 | call to new | +| associations.rb:21:9:21:21 | call to posts | +| associations.rb:21:9:21:28 | call to create | +| associations.rb:23:12:23:25 | call to comments | +| associations.rb:23:12:23:32 | call to create | +| associations.rb:25:11:25:22 | call to author | +| associations.rb:27:9:27:21 | call to posts | +| associations.rb:27:9:27:28 | call to create | +| associations.rb:29:1:29:13 | call to posts | +| associations.rb:29:1:29:22 | ... << ... | +| associations.rb:31:1:31:12 | call to author= | +| associations.rb:35:1:35:14 | call to comments | +| associations.rb:35:1:35:21 | call to create | +| associations.rb:35:1:35:28 | call to create | +| associations.rb:37:1:37:13 | call to posts | +| associations.rb:37:1:37:20 | call to reload | +| associations.rb:37:1:37:27 | call to create | +| associations.rb:39:1:39:15 | call to build_tag | +| associations.rb:40:1:40:15 | call to build_tag | +| associations.rb:42:1:42:13 | call to posts | +| associations.rb:42:1:42:25 | call to push | +| associations.rb:43:1:43:13 | call to posts | +| associations.rb:43:1:43:27 | call to concat | +| associations.rb:44:1:44:13 | call to posts | +| associations.rb:44:1:44:19 | call to build | +| associations.rb:45:1:45:13 | call to posts | +| associations.rb:45:1:45:20 | call to create | +| associations.rb:46:1:46:13 | call to posts | +| associations.rb:46:1:46:21 | call to create! | +| associations.rb:47:1:47:13 | call to posts | +| associations.rb:47:1:47:20 | call to delete | +| associations.rb:48:1:48:13 | call to posts | +| associations.rb:48:1:48:24 | call to delete_all | +| associations.rb:49:1:49:13 | call to posts | +| associations.rb:49:1:49:21 | call to destroy | +| associations.rb:50:1:50:13 | call to posts | +| associations.rb:50:1:50:25 | call to destroy_all | +| associations.rb:51:1:51:13 | call to posts | +| associations.rb:51:1:51:22 | call to distinct | +| associations.rb:51:1:51:36 | call to find | +| associations.rb:52:1:52:13 | call to posts | +| associations.rb:52:1:52:19 | call to reset | +| associations.rb:52:1:52:33 | call to find | +| associations.rb:53:1:53:13 | call to posts | +| associations.rb:53:1:53:20 | call to reload | +| associations.rb:53:1:53:34 | call to find | +activeRecordModelClassMethodCallsReplacement +| ActiveRecord.rb:1:1:3:3 | UserGroup | ActiveRecord.rb:2:3:2:17 | call to has_many | +| ActiveRecord.rb:1:1:3:3 | UserGroup | ActiveRecord.rb:13:5:13:40 | call to find_by | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:6:3:6:24 | call to belongs_to | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:9:5:9:68 | call to find | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:19:5:19:25 | call to destroy_by | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:28:5:28:45 | call to calculate | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:29:5:29:43 | call to delete_by | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:30:5:30:46 | call to destroy_by | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:31:5:31:35 | call to where | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:34:5:34:14 | call to where | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:35:5:35:51 | call to authenticate | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:36:5:36:30 | call to find_by_name | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:37:5:37:36 | call to not_a_find_by_method | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:46:5:46:33 | call to delete_by | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:52:5:52:29 | call to order | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:56:7:56:40 | call to find_by | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:60:5:60:33 | call to find_by | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:62:5:62:34 | call to find | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:68:5:68:45 | call to delete_by | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:72:5:72:24 | call to create | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:76:5:76:66 | call to create | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:80:5:80:68 | call to create | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:84:5:84:16 | call to create | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:88:5:88:27 | call to update | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:92:5:92:69 | call to update | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:96:5:96:71 | call to update | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:102:13:102:54 | call to annotate | +| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:106:13:106:77 | call to annotate | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:19:5:19:25 | call to destroy_by | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:68:5:68:45 | call to delete_by | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:72:5:72:24 | call to create | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:76:5:76:66 | call to create | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:80:5:80:68 | call to create | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:84:5:84:16 | call to create | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:88:5:88:27 | call to update | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:92:5:92:69 | call to update | +| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:96:5:96:71 | call to update | +| associations.rb:1:1:3:3 | Author | associations.rb:2:3:2:17 | call to has_many | +| associations.rb:1:1:3:3 | Author | associations.rb:19:11:19:20 | call to new | +| associations.rb:5:1:9:3 | Post | associations.rb:6:3:6:20 | call to belongs_to | +| associations.rb:5:1:9:3 | Post | associations.rb:7:3:7:20 | call to has_many | +| associations.rb:5:1:9:3 | Post | associations.rb:8:3:8:31 | call to has_and_belongs_to_many | +| associations.rb:11:1:13:3 | Tag | associations.rb:12:3:12:32 | call to has_and_belongs_to_many | +| associations.rb:15:1:17:3 | Comment | associations.rb:16:3:16:18 | call to belongs_to | potentiallyUnsafeSqlExecutingMethodCall | ActiveRecord.rb:9:5:9:68 | call to find | | ActiveRecord.rb:19:5:19:25 | call to destroy_by | diff --git a/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.ql b/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.ql index 731679e437b..348ca1456e2 100644 --- a/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.ql +++ b/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.ql @@ -9,9 +9,19 @@ query predicate activeRecordInstances(ActiveRecordInstance i) { any() } query predicate activeRecordSqlExecutionRanges(ActiveRecordSqlExecutionRange range) { any() } -query predicate activeRecordModelClassMethodCalls(ActiveRecordModelClassMethodCall call) { any() } +deprecated query predicate activeRecordModelClassMethodCalls(ActiveRecordModelClassMethodCall call) { + any() +} -query predicate potentiallyUnsafeSqlExecutingMethodCall(PotentiallyUnsafeSqlExecutingMethodCall call) { +query predicate activeRecordModelClassMethodCallsReplacement( + ActiveRecordModelClass cls, DataFlow::CallNode call +) { + call = cls.getClassNode().trackModule().getAMethodCall(_) +} + +deprecated query predicate potentiallyUnsafeSqlExecutingMethodCall( + PotentiallyUnsafeSqlExecutingMethodCall call +) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/active_resource/ActiveResource.expected b/ruby/ql/test/library-tests/frameworks/active_resource/ActiveResource.expected index a55946c1852..e6d3b056971 100644 --- a/ruby/ql/test/library-tests/frameworks/active_resource/ActiveResource.expected +++ b/ruby/ql/test/library-tests/frameworks/active_resource/ActiveResource.expected @@ -33,6 +33,13 @@ modelInstances | active_resource.rb:26:9:26:14 | people | | active_resource.rb:26:9:26:20 | call to first | | active_resource.rb:27:1:27:5 | alice | +modelInstancesAsSource +| active_resource.rb:1:1:3:3 | Person | active_resource.rb:5:9:5:33 | call to new | +| active_resource.rb:1:1:3:3 | Person | active_resource.rb:8:9:8:22 | call to find | +| active_resource.rb:1:1:3:3 | Person | active_resource.rb:16:1:16:23 | call to new | +| active_resource.rb:1:1:3:3 | Person | active_resource.rb:18:1:18:22 | call to get | +| active_resource.rb:1:1:3:3 | Person | active_resource.rb:24:10:24:26 | call to find | +| active_resource.rb:1:1:3:3 | Person | active_resource.rb:26:9:26:20 | call to first | modelInstanceMethodCalls | active_resource.rb:6:1:6:10 | call to save | | active_resource.rb:9:1:9:13 | call to address= | @@ -50,3 +57,6 @@ collections | active_resource.rb:24:1:24:26 | ... = ... | | active_resource.rb:24:10:24:26 | call to find | | active_resource.rb:26:9:26:14 | people | +collectionSources +| active_resource.rb:23:10:23:19 | call to all | +| active_resource.rb:24:10:24:26 | call to find | diff --git a/ruby/ql/test/library-tests/frameworks/active_resource/ActiveResource.ql b/ruby/ql/test/library-tests/frameworks/active_resource/ActiveResource.ql index 1f2fd1efcf1..f1898ddbc98 100644 --- a/ruby/ql/test/library-tests/frameworks/active_resource/ActiveResource.ql +++ b/ruby/ql/test/library-tests/frameworks/active_resource/ActiveResource.ql @@ -3,7 +3,8 @@ import codeql.ruby.DataFlow import codeql.ruby.frameworks.ActiveResource query predicate modelClasses( - ActiveResource::ModelClass c, DataFlow::Node siteAssignCall, boolean disablesCertificateValidation + ActiveResource::ModelClassNode c, DataFlow::Node siteAssignCall, + boolean disablesCertificateValidation ) { c.getASiteAssignment() = siteAssignCall and if c.disablesCertificateValidation(siteAssignCall) @@ -13,8 +14,16 @@ query predicate modelClasses( query predicate modelClassMethodCalls(ActiveResource::ModelClassMethodCall c) { any() } -query predicate modelInstances(ActiveResource::ModelInstance c) { any() } +deprecated query predicate modelInstances(ActiveResource::ModelInstance c) { any() } + +query predicate modelInstancesAsSource( + ActiveResource::ModelClassNode cls, DataFlow::LocalSourceNode node +) { + node = cls.getAnInstanceReference().asSource() +} query predicate modelInstanceMethodCalls(ActiveResource::ModelInstanceMethodCall c) { any() } -query predicate collections(ActiveResource::Collection c) { any() } +deprecated query predicate collections(ActiveResource::Collection c) { any() } + +query predicate collectionSources(ActiveResource::CollectionSource c) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/rack/Rack.expected b/ruby/ql/test/library-tests/frameworks/rack/Rack.expected index 01892a08dec..8473cf913ca 100644 --- a/ruby/ql/test/library-tests/frameworks/rack/Rack.expected +++ b/ruby/ql/test/library-tests/frameworks/rack/Rack.expected @@ -3,9 +3,12 @@ rackRequestHandlers | rack.rb:17:3:21:5 | call | rack.rb:17:12:17:18 | the_env | rack.rb:20:5:20:27 | call to [] | | rack.rb:30:3:36:5 | call | rack.rb:30:12:30:14 | env | rack.rb:35:5:35:26 | call to [] | | rack.rb:40:3:44:5 | call | rack.rb:40:12:40:14 | env | rack.rb:43:5:43:45 | call to [] | -| rack.rb:60:3:62:5 | call | rack.rb:60:12:60:14 | env | rack.rb:66:7:66:22 | call to [] | -| rack.rb:60:3:62:5 | call | rack.rb:60:12:60:14 | env | rack.rb:73:5:73:21 | call to [] | +| rack.rb:60:3:62:5 | call | rack.rb:60:12:60:14 | env | rack.rb:66:7:66:24 | call to [] | +| rack.rb:60:3:62:5 | call | rack.rb:60:12:60:14 | env | rack.rb:73:5:73:23 | call to [] | | rack.rb:79:3:81:5 | call | rack.rb:79:17:79:19 | env | rack.rb:93:5:93:78 | call to finish | +| rack.rb:98:3:107:5 | call | rack.rb:98:12:98:14 | env | rack.rb:110:5:110:28 | call to [] | +| rack.rb:98:3:107:5 | call | rack.rb:98:12:98:14 | env | rack.rb:114:5:114:30 | call to [] | +| rack.rb:119:3:123:5 | call | rack.rb:119:12:119:14 | env | rack.rb:122:5:122:42 | call to [] | | rack_apps.rb:6:3:12:5 | call | rack_apps.rb:6:12:6:14 | env | rack_apps.rb:10:12:10:34 | call to [] | | rack_apps.rb:16:3:18:5 | call | rack_apps.rb:16:17:16:19 | env | rack_apps.rb:17:5:17:28 | call to [] | | rack_apps.rb:21:14:21:50 | -> { ... } | rack_apps.rb:21:17:21:19 | env | rack_apps.rb:21:24:21:48 | call to [] | @@ -16,3 +19,8 @@ rackResponseContentTypes redirectResponses | rack.rb:43:5:43:45 | call to [] | rack.rb:42:30:42:40 | "/foo.html" | | rack.rb:93:5:93:78 | call to finish | rack.rb:93:60:93:70 | redirect_to | +requestInputAccesses +| rack.rb:100:18:100:28 | call to cookies | +| rack.rb:103:14:103:23 | call to params | +| rack.rb:104:18:104:32 | ...[...] | +| rack.rb:120:14:120:32 | ...[...] | diff --git a/ruby/ql/test/library-tests/frameworks/rack/Rack.ql b/ruby/ql/test/library-tests/frameworks/rack/Rack.ql index 29b73b327fb..6e2efb590e8 100644 --- a/ruby/ql/test/library-tests/frameworks/rack/Rack.ql +++ b/ruby/ql/test/library-tests/frameworks/rack/Rack.ql @@ -1,4 +1,5 @@ private import codeql.ruby.AST +private import codeql.ruby.Concepts private import codeql.ruby.frameworks.Rack private import codeql.ruby.DataFlow @@ -17,3 +18,5 @@ query predicate rackResponseContentTypes( query predicate redirectResponses(Rack::Response::RedirectResponse resp, DataFlow::Node location) { location = resp.getRedirectLocation() } + +query predicate requestInputAccesses(Http::Server::RequestInputAccess ria) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/rack/rack.rb b/ruby/ql/test/library-tests/frameworks/rack/rack.rb index 109016f018d..33d05e87fc0 100644 --- a/ruby/ql/test/library-tests/frameworks/rack/rack.rb +++ b/ruby/ql/test/library-tests/frameworks/rack/rack.rb @@ -63,14 +63,14 @@ class Baz def run(env) if env[:foo] == "foo" - [200, {}, "foo"] + [200, {}, ["foo"]] else error end end def error - [400, {}, "nope"] + [400, {}, ["nope"]] end end @@ -93,3 +93,32 @@ class Qux Rack::Response.new(['redirecting'], 302, 'Location' => redirect_to).finish end end + +class UsesRequest + def call(env) + req = Rack::Request.new(env) + if session = req.cookies['session'] + reuse_session(session) + else + name = req.params['name'] + password = req['password'] + login(name, password) + end + end + + def login(name, password) + [200, {}, "new session"] + end + + def reuse_session(name, password) + [200, {}, "reuse session"] + end +end + +class UsesEnvQueryParams + def call(env) + params = env['QUERY_STRING'] + user = Rack::Utils.parse_query(params)["user"] + [200, {}, [lookup_user_profile(user)]] + end +end diff --git a/ruby/ql/test/query-tests/experimental/XPathInjection/LibxmlInjection.rb b/ruby/ql/test/query-tests/experimental/XPathInjection/LibxmlInjection.rb new file mode 100644 index 00000000000..3bde2f1e40b --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/XPathInjection/LibxmlInjection.rb @@ -0,0 +1,59 @@ +require 'libxml' + +class FooController < ActionController::Base + def libxml_handler(event:, context:) + name = params[:user_name] + + xml = <<-XML + + bar + THIS IS SECRET + + XML + + # Parse the XML + doc = LibXML::XML::Document.string(xml) + + # GOOD: XPath query is not constructed from user input + results1 = doc.find_first('//foo') + + # BAD: XPath query is constructed from user input + results2 = doc.find_first("//#{name}") + + # GOOD: XPath query is not constructed from user input + results3 = doc.find('//foo') + + # BAD: XPath query is constructed from user input + results4 = doc.find("//#{name}") + end +end + +class BarController < ActionController::Base + def libxml_safe_handler(event:, context:) + safe_name = params[:user_name] + + xml = <<-XML + + bar + THIS IS SECRET + + XML + + # Parse the XML + doc = REXML::Document.new(xml) + + # GOOD: barrier guard prevents taint flow + safe_name = if ["foo", "foo2"].include? safe_name + safe_name + else + safe_name = "foo" + end + + # GOOD: XPath query is not constructed from unsanitized user input + results5 = doc.find_first("//#{safe_name}") + + # GOOD: XPath query is not constructed from unsanitized user input + results6 = doc.find("//#{safe_name}") + + end +end diff --git a/ruby/ql/test/query-tests/experimental/XPathInjection/NokogiriInjection.rb b/ruby/ql/test/query-tests/experimental/XPathInjection/NokogiriInjection.rb new file mode 100644 index 00000000000..e3ac8055f48 --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/XPathInjection/NokogiriInjection.rb @@ -0,0 +1,88 @@ +require 'nokogiri' + +class FooController < ActionController::Base + def nokogiri_handler(event:, context:) + name = params[:user_name] + + xml = <<-XML + + bar + THIS IS SECRET + + XML + + # Parse the XML + doc = Nokogiri::XML.parse(xml) + + # GOOD: XPath query is not constructed from user input + results1 = doc.at('//foo') + + # BAD: XPath query is constructed from user input + results2 = doc.at("//#{name}") + + # GOOD: XPath query is not constructed from user input + results3 = doc.xpath('//foo') + + # BAD: XPath query is constructed from user input + results4 = doc.xpath("//#{name}") + + # GOOD: XPath query is not constructed from user input + results5 = doc.at_xpath('//foo') + + # BAD: XPath query is constructed from user input + results6 = doc.at_xpath("//#{name}") + + # GOOD: XPath query is not constructed from user input + doc.xpath('//foo').each do |element| + puts element.text + end + + # BAD: XPath query constructed from user input + doc.xpath("//#{name}").each do |element| + puts element.text + end + + # GOOD: XPath query is not constructed from user input + doc.search('//foo').each do |element| + puts element.text + end + + # BAD: XPath query constructed from user input + doc.search("//#{name}").each do |element| + puts element.text + end + end +end + +class BarController < ActionController::Base + def nokogiri_safe_handler(event:, context:) + safe_name = params[:user_name] + + xml = <<-XML + + bar + THIS IS SECRET + + XML + + # Parse the XML + doc = Nokogiri::XML.parse(xml) + + # GOOD: barrier guard prevents taint flow + safe_name = if ["foo", "foo2"].include? safe_name + safe_name + else + safe_name = "foo" + end + + # GOOD: XPath query is not constructed from unsanitized user input + results7 = doc.at("//#{safe_name}") + + # GOOD: XPath query is not constructed from unsanitized user input + results8 = doc.xpath("//#{safe_name}") + + # GOOD: XPath query is not constructed from unsanitized user input + results9 = doc.at_xpath("//#{safe_name}") + + end +end \ No newline at end of file diff --git a/ruby/ql/test/query-tests/experimental/XPathInjection/RexmlInjection.rb b/ruby/ql/test/query-tests/experimental/XPathInjection/RexmlInjection.rb new file mode 100644 index 00000000000..6ee16d125b4 --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/XPathInjection/RexmlInjection.rb @@ -0,0 +1,69 @@ +require 'rexml' + +class FooController < ActionController::Base + def rexml_handler(event:, context:) + name = params[:user_name] + + xml = <<-XML + + bar + THIS IS SECRET + + XML + + # Parse the XML + doc = REXML::Document.new(xml) + + # GOOD: XPath query is not constructed from user input + results1 = REXML::XPath.first(doc, "//foo") + + # BAD: XPath query is constructed from user input + results2 = REXML::XPath.first(doc, "//#{name}") + + # GOOD: XPath query is not constructed from user input + results3 = REXML::XPath.match(doc, "//foo", nil) + + # BAD: XPath query is constructed from user input + results4 = REXML::XPath.match(doc, "//#{name}", nil) + + # GOOD: XPath query is not constructed from user input + REXML::XPath.each(doc, "//foo") do |element| + puts element.text + end + + # BAD: XPath query constructed from user input + REXML::XPath.each(doc, "//#{name}") do |element| + puts element.text + end + end +end + +class BarController < ActionController::Base + def rexml_safe_handler(event:, context:) + safe_name = params[:user_name] + + xml = <<-XML + + bar + THIS IS SECRET + + XML + + # Parse the XML + doc = REXML::Document.new(xml) + + # GOOD: barrier guard prevents taint flow + safe_name = if ["foo", "foo2"].include? safe_name + safe_name + else + safe_name = "foo" + end + + # GOOD: XPath query is not constructed from unsanitized user input + results5 = REXML::XPath.first(doc, "//#{safe_name}") + + # GOOD: XPath query is not constructed from unsanitized user input + results6 = REXML::XPath.match(doc, "//#{safe_name}", nil) + + end +end \ No newline at end of file diff --git a/ruby/ql/test/query-tests/experimental/XPathInjection/XPathInjection.expected b/ruby/ql/test/query-tests/experimental/XPathInjection/XPathInjection.expected new file mode 100644 index 00000000000..789eef9f4fc --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/XPathInjection/XPathInjection.expected @@ -0,0 +1,49 @@ +edges +| LibxmlInjection.rb:5:5:5:8 | name | LibxmlInjection.rb:21:31:21:41 | "//#{...}" | +| LibxmlInjection.rb:5:5:5:8 | name | LibxmlInjection.rb:27:25:27:35 | "//#{...}" | +| LibxmlInjection.rb:5:12:5:17 | call to params | LibxmlInjection.rb:5:12:5:29 | ...[...] | +| LibxmlInjection.rb:5:12:5:29 | ...[...] | LibxmlInjection.rb:5:5:5:8 | name | +| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:21:23:21:33 | "//#{...}" | +| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:27:26:27:36 | "//#{...}" | +| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:33:29:33:39 | "//#{...}" | +| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:41:15:41:25 | "//#{...}" | +| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:51:16:51:26 | "//#{...}" | +| NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:5:12:5:29 | ...[...] | +| NokogiriInjection.rb:5:12:5:29 | ...[...] | NokogiriInjection.rb:5:5:5:8 | name | +| RexmlInjection.rb:5:5:5:8 | name | RexmlInjection.rb:21:40:21:50 | "//#{...}" | +| RexmlInjection.rb:5:5:5:8 | name | RexmlInjection.rb:27:40:27:50 | "//#{...}" | +| RexmlInjection.rb:5:5:5:8 | name | RexmlInjection.rb:35:28:35:38 | "//#{...}" | +| RexmlInjection.rb:5:12:5:17 | call to params | RexmlInjection.rb:5:12:5:29 | ...[...] | +| RexmlInjection.rb:5:12:5:29 | ...[...] | RexmlInjection.rb:5:5:5:8 | name | +nodes +| LibxmlInjection.rb:5:5:5:8 | name | semmle.label | name | +| LibxmlInjection.rb:5:12:5:17 | call to params | semmle.label | call to params | +| LibxmlInjection.rb:5:12:5:29 | ...[...] | semmle.label | ...[...] | +| LibxmlInjection.rb:21:31:21:41 | "//#{...}" | semmle.label | "//#{...}" | +| LibxmlInjection.rb:27:25:27:35 | "//#{...}" | semmle.label | "//#{...}" | +| NokogiriInjection.rb:5:5:5:8 | name | semmle.label | name | +| NokogiriInjection.rb:5:12:5:17 | call to params | semmle.label | call to params | +| NokogiriInjection.rb:5:12:5:29 | ...[...] | semmle.label | ...[...] | +| NokogiriInjection.rb:21:23:21:33 | "//#{...}" | semmle.label | "//#{...}" | +| NokogiriInjection.rb:27:26:27:36 | "//#{...}" | semmle.label | "//#{...}" | +| NokogiriInjection.rb:33:29:33:39 | "//#{...}" | semmle.label | "//#{...}" | +| NokogiriInjection.rb:41:15:41:25 | "//#{...}" | semmle.label | "//#{...}" | +| NokogiriInjection.rb:51:16:51:26 | "//#{...}" | semmle.label | "//#{...}" | +| RexmlInjection.rb:5:5:5:8 | name | semmle.label | name | +| RexmlInjection.rb:5:12:5:17 | call to params | semmle.label | call to params | +| RexmlInjection.rb:5:12:5:29 | ...[...] | semmle.label | ...[...] | +| RexmlInjection.rb:21:40:21:50 | "//#{...}" | semmle.label | "//#{...}" | +| RexmlInjection.rb:27:40:27:50 | "//#{...}" | semmle.label | "//#{...}" | +| RexmlInjection.rb:35:28:35:38 | "//#{...}" | semmle.label | "//#{...}" | +subpaths +#select +| LibxmlInjection.rb:21:31:21:41 | "//#{...}" | LibxmlInjection.rb:5:12:5:17 | call to params | LibxmlInjection.rb:21:31:21:41 | "//#{...}" | XPath expression depends on a $@. | LibxmlInjection.rb:5:12:5:17 | call to params | user-provided value | +| LibxmlInjection.rb:27:25:27:35 | "//#{...}" | LibxmlInjection.rb:5:12:5:17 | call to params | LibxmlInjection.rb:27:25:27:35 | "//#{...}" | XPath expression depends on a $@. | LibxmlInjection.rb:5:12:5:17 | call to params | user-provided value | +| NokogiriInjection.rb:21:23:21:33 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:21:23:21:33 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value | +| NokogiriInjection.rb:27:26:27:36 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:27:26:27:36 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value | +| NokogiriInjection.rb:33:29:33:39 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:33:29:33:39 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value | +| NokogiriInjection.rb:41:15:41:25 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:41:15:41:25 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value | +| NokogiriInjection.rb:51:16:51:26 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:51:16:51:26 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value | +| RexmlInjection.rb:21:40:21:50 | "//#{...}" | RexmlInjection.rb:5:12:5:17 | call to params | RexmlInjection.rb:21:40:21:50 | "//#{...}" | XPath expression depends on a $@. | RexmlInjection.rb:5:12:5:17 | call to params | user-provided value | +| RexmlInjection.rb:27:40:27:50 | "//#{...}" | RexmlInjection.rb:5:12:5:17 | call to params | RexmlInjection.rb:27:40:27:50 | "//#{...}" | XPath expression depends on a $@. | RexmlInjection.rb:5:12:5:17 | call to params | user-provided value | +| RexmlInjection.rb:35:28:35:38 | "//#{...}" | RexmlInjection.rb:5:12:5:17 | call to params | RexmlInjection.rb:35:28:35:38 | "//#{...}" | XPath expression depends on a $@. | RexmlInjection.rb:5:12:5:17 | call to params | user-provided value | diff --git a/ruby/ql/test/query-tests/experimental/XPathInjection/XPathInjection.qlref b/ruby/ql/test/query-tests/experimental/XPathInjection/XPathInjection.qlref new file mode 100644 index 00000000000..a5b1b23c203 --- /dev/null +++ b/ruby/ql/test/query-tests/experimental/XPathInjection/XPathInjection.qlref @@ -0,0 +1 @@ +experimental/xpath-injection/XpathInjection.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/security/cwe-079/StoredXSS.expected b/ruby/ql/test/query-tests/security/cwe-079/StoredXSS.expected index 0eaf24029ef..04f4dd2dd1a 100644 --- a/ruby/ql/test/query-tests/security/cwe-079/StoredXSS.expected +++ b/ruby/ql/test/query-tests/security/cwe-079/StoredXSS.expected @@ -1,9 +1,8 @@ edges | app/controllers/foo/stores_controller.rb:8:5:8:6 | dt | app/controllers/foo/stores_controller.rb:9:22:9:23 | dt | -| app/controllers/foo/stores_controller.rb:8:5:8:6 | dt | app/controllers/foo/stores_controller.rb:13:55:13:56 | dt | | app/controllers/foo/stores_controller.rb:8:10:8:29 | call to read | app/controllers/foo/stores_controller.rb:8:5:8:6 | dt | +| app/controllers/foo/stores_controller.rb:9:22:9:23 | dt | app/controllers/foo/stores_controller.rb:13:55:13:56 | dt | | app/controllers/foo/stores_controller.rb:9:22:9:23 | dt | app/views/foo/stores/show.html.erb:37:3:37:16 | @instance_text | -| app/controllers/foo/stores_controller.rb:12:28:12:48 | call to raw_name | app/views/foo/stores/show.html.erb:82:5:82:24 | @other_user_raw_name | | app/controllers/foo/stores_controller.rb:13:55:13:56 | dt | app/views/foo/stores/show.html.erb:2:9:2:20 | call to display_text | | app/controllers/foo/stores_controller.rb:13:55:13:56 | dt | app/views/foo/stores/show.html.erb:5:9:5:21 | call to local_assigns [element :display_text] | | app/controllers/foo/stores_controller.rb:13:55:13:56 | dt | app/views/foo/stores/show.html.erb:9:9:9:21 | call to local_assigns [element :display_text] | @@ -22,7 +21,6 @@ nodes | app/controllers/foo/stores_controller.rb:8:5:8:6 | dt | semmle.label | dt | | app/controllers/foo/stores_controller.rb:8:10:8:29 | call to read | semmle.label | call to read | | app/controllers/foo/stores_controller.rb:9:22:9:23 | dt | semmle.label | dt | -| app/controllers/foo/stores_controller.rb:12:28:12:48 | call to raw_name | semmle.label | call to raw_name | | app/controllers/foo/stores_controller.rb:13:55:13:56 | dt | semmle.label | dt | | app/views/foo/bars/_widget.html.erb:5:9:5:20 | call to display_text | semmle.label | call to display_text | | app/views/foo/bars/_widget.html.erb:8:9:8:21 | call to local_assigns [element :display_text] | semmle.label | call to local_assigns [element :display_text] | @@ -39,11 +37,7 @@ nodes | app/views/foo/stores/show.html.erb:40:64:40:87 | ... + ... | semmle.label | ... + ... | | app/views/foo/stores/show.html.erb:40:76:40:87 | call to display_text | semmle.label | call to display_text | | app/views/foo/stores/show.html.erb:46:5:46:16 | call to handle | semmle.label | call to handle | -| app/views/foo/stores/show.html.erb:49:5:49:18 | call to raw_name | semmle.label | call to raw_name | | app/views/foo/stores/show.html.erb:63:3:63:18 | call to handle | semmle.label | call to handle | -| app/views/foo/stores/show.html.erb:69:3:69:20 | call to raw_name | semmle.label | call to raw_name | -| app/views/foo/stores/show.html.erb:79:5:79:22 | call to display_name | semmle.label | call to display_name | -| app/views/foo/stores/show.html.erb:82:5:82:24 | @other_user_raw_name | semmle.label | @other_user_raw_name | | app/views/foo/stores/show.html.erb:86:3:86:29 | call to sprintf | semmle.label | call to sprintf | | app/views/foo/stores/show.html.erb:86:17:86:28 | call to handle | semmle.label | call to handle | subpaths @@ -57,9 +51,5 @@ subpaths | app/views/foo/stores/show.html.erb:32:3:32:14 | call to display_text | app/controllers/foo/stores_controller.rb:8:10:8:29 | call to read | app/views/foo/stores/show.html.erb:32:3:32:14 | call to display_text | Stored cross-site scripting vulnerability due to $@. | app/controllers/foo/stores_controller.rb:8:10:8:29 | call to read | stored value | | app/views/foo/stores/show.html.erb:37:3:37:16 | @instance_text | app/controllers/foo/stores_controller.rb:8:10:8:29 | call to read | app/views/foo/stores/show.html.erb:37:3:37:16 | @instance_text | Stored cross-site scripting vulnerability due to $@. | app/controllers/foo/stores_controller.rb:8:10:8:29 | call to read | stored value | | app/views/foo/stores/show.html.erb:46:5:46:16 | call to handle | app/views/foo/stores/show.html.erb:46:5:46:16 | call to handle | app/views/foo/stores/show.html.erb:46:5:46:16 | call to handle | Stored cross-site scripting vulnerability due to $@. | app/views/foo/stores/show.html.erb:46:5:46:16 | call to handle | stored value | -| app/views/foo/stores/show.html.erb:49:5:49:18 | call to raw_name | app/views/foo/stores/show.html.erb:49:5:49:18 | call to raw_name | app/views/foo/stores/show.html.erb:49:5:49:18 | call to raw_name | Stored cross-site scripting vulnerability due to $@. | app/views/foo/stores/show.html.erb:49:5:49:18 | call to raw_name | stored value | | app/views/foo/stores/show.html.erb:63:3:63:18 | call to handle | app/views/foo/stores/show.html.erb:63:3:63:18 | call to handle | app/views/foo/stores/show.html.erb:63:3:63:18 | call to handle | Stored cross-site scripting vulnerability due to $@. | app/views/foo/stores/show.html.erb:63:3:63:18 | call to handle | stored value | -| app/views/foo/stores/show.html.erb:69:3:69:20 | call to raw_name | app/views/foo/stores/show.html.erb:69:3:69:20 | call to raw_name | app/views/foo/stores/show.html.erb:69:3:69:20 | call to raw_name | Stored cross-site scripting vulnerability due to $@. | app/views/foo/stores/show.html.erb:69:3:69:20 | call to raw_name | stored value | -| app/views/foo/stores/show.html.erb:79:5:79:22 | call to display_name | app/views/foo/stores/show.html.erb:79:5:79:22 | call to display_name | app/views/foo/stores/show.html.erb:79:5:79:22 | call to display_name | Stored cross-site scripting vulnerability due to $@. | app/views/foo/stores/show.html.erb:79:5:79:22 | call to display_name | stored value | -| app/views/foo/stores/show.html.erb:82:5:82:24 | @other_user_raw_name | app/controllers/foo/stores_controller.rb:12:28:12:48 | call to raw_name | app/views/foo/stores/show.html.erb:82:5:82:24 | @other_user_raw_name | Stored cross-site scripting vulnerability due to $@. | app/controllers/foo/stores_controller.rb:12:28:12:48 | call to raw_name | stored value | | app/views/foo/stores/show.html.erb:86:3:86:29 | call to sprintf | app/views/foo/stores/show.html.erb:86:17:86:28 | call to handle | app/views/foo/stores/show.html.erb:86:3:86:29 | call to sprintf | Stored cross-site scripting vulnerability due to $@. | app/views/foo/stores/show.html.erb:86:17:86:28 | call to handle | stored value | diff --git a/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/stores/show.html.erb b/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/stores/show.html.erb index 29656a15a3d..d8afec1c432 100644 --- a/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/stores/show.html.erb +++ b/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/stores/show.html.erb @@ -63,7 +63,7 @@ some_user.handle.html_safe %> -<%# BAD: Indirect to a database value without escaping %> +<%# BAD: Indirect to a database value without escaping (currently missed due to lack of 'self' handling in ORM tracking) %> <%= some_user = User.find 1 some_user.raw_name.html_safe @@ -75,10 +75,10 @@ some_user.handle %> -<%# BAD: Indirect to a database value without escaping %> +<%# BAD: Indirect to a database value without escaping (currently missed due to lack of 'self' handling in ORM tracking) %> <%= @user.display_name.html_safe %> -<%# BAD: Indirect to a database value without escaping %> +<%# BAD: Indirect to a database value without escaping (currently missed due to lack of 'self' handling in ORM tracking) %> <%= @other_user_raw_name.html_safe %> <%# BAD: Kernel.sprintf is a taint-step %> diff --git a/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected b/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected index 0cc0d213dcc..161cdcc7751 100644 --- a/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected +++ b/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected @@ -22,6 +22,7 @@ edges | ActiveRecordInjection.rb:70:38:70:50 | ...[...] | ActiveRecordInjection.rb:8:31:8:34 | pass | | ActiveRecordInjection.rb:74:41:74:46 | call to params | ActiveRecordInjection.rb:74:41:74:51 | ...[...] | | ActiveRecordInjection.rb:74:41:74:51 | ...[...] | ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | +| ActiveRecordInjection.rb:79:23:79:28 | call to params | ActiveRecordInjection.rb:79:23:79:35 | ...[...] | | ActiveRecordInjection.rb:83:17:83:22 | call to params | ActiveRecordInjection.rb:83:17:83:31 | ...[...] | | ActiveRecordInjection.rb:84:19:84:24 | call to params | ActiveRecordInjection.rb:84:19:84:33 | ...[...] | | ActiveRecordInjection.rb:88:18:88:23 | call to params | ActiveRecordInjection.rb:88:18:88:35 | ...[...] | @@ -35,6 +36,7 @@ edges | ActiveRecordInjection.rb:103:11:103:17 | ...[...] | ActiveRecordInjection.rb:103:5:103:7 | uid | | ActiveRecordInjection.rb:104:5:104:9 | uidEq | ActiveRecordInjection.rb:108:20:108:32 | ... + ... | | ActiveRecordInjection.rb:141:21:141:26 | call to params | ActiveRecordInjection.rb:141:21:141:44 | ...[...] | +| ActiveRecordInjection.rb:141:21:141:26 | call to params | ActiveRecordInjection.rb:141:21:141:44 | ...[...] | | ActiveRecordInjection.rb:141:21:141:44 | ...[...] | ActiveRecordInjection.rb:20:22:20:30 | condition | | ActiveRecordInjection.rb:155:59:155:64 | call to params | ActiveRecordInjection.rb:155:59:155:74 | ...[...] | | ActiveRecordInjection.rb:155:59:155:74 | ...[...] | ActiveRecordInjection.rb:155:27:155:76 | "this is an unsafe annotation:..." | @@ -102,6 +104,8 @@ nodes | ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | semmle.label | "id = '#{...}'" | | ActiveRecordInjection.rb:74:41:74:46 | call to params | semmle.label | call to params | | ActiveRecordInjection.rb:74:41:74:51 | ...[...] | semmle.label | ...[...] | +| ActiveRecordInjection.rb:79:23:79:28 | call to params | semmle.label | call to params | +| ActiveRecordInjection.rb:79:23:79:35 | ...[...] | semmle.label | ...[...] | | ActiveRecordInjection.rb:83:17:83:22 | call to params | semmle.label | call to params | | ActiveRecordInjection.rb:83:17:83:31 | ...[...] | semmle.label | ...[...] | | ActiveRecordInjection.rb:84:19:84:24 | call to params | semmle.label | call to params | @@ -123,6 +127,7 @@ nodes | ActiveRecordInjection.rb:108:20:108:32 | ... + ... | semmle.label | ... + ... | | ActiveRecordInjection.rb:141:21:141:26 | call to params | semmle.label | call to params | | ActiveRecordInjection.rb:141:21:141:44 | ...[...] | semmle.label | ...[...] | +| ActiveRecordInjection.rb:141:21:141:44 | ...[...] | semmle.label | ...[...] | | ActiveRecordInjection.rb:155:27:155:76 | "this is an unsafe annotation:..." | semmle.label | "this is an unsafe annotation:..." | | ActiveRecordInjection.rb:155:59:155:64 | call to params | semmle.label | call to params | | ActiveRecordInjection.rb:155:59:155:74 | ...[...] | semmle.label | ...[...] | @@ -172,6 +177,7 @@ subpaths | ActiveRecordInjection.rb:61:16:61:21 | <<-SQL | ActiveRecordInjection.rb:62:21:62:26 | call to params | ActiveRecordInjection.rb:61:16:61:21 | <<-SQL | This SQL query depends on a $@. | ActiveRecordInjection.rb:62:21:62:26 | call to params | user-provided value | | ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" | ActiveRecordInjection.rb:68:34:68:39 | call to params | ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" | This SQL query depends on a $@. | ActiveRecordInjection.rb:68:34:68:39 | call to params | user-provided value | | ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | ActiveRecordInjection.rb:74:41:74:46 | call to params | ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | This SQL query depends on a $@. | ActiveRecordInjection.rb:74:41:74:46 | call to params | user-provided value | +| ActiveRecordInjection.rb:79:23:79:35 | ...[...] | ActiveRecordInjection.rb:79:23:79:28 | call to params | ActiveRecordInjection.rb:79:23:79:35 | ...[...] | This SQL query depends on a $@. | ActiveRecordInjection.rb:79:23:79:28 | call to params | user-provided value | | ActiveRecordInjection.rb:83:17:83:31 | ...[...] | ActiveRecordInjection.rb:83:17:83:22 | call to params | ActiveRecordInjection.rb:83:17:83:31 | ...[...] | This SQL query depends on a $@. | ActiveRecordInjection.rb:83:17:83:22 | call to params | user-provided value | | ActiveRecordInjection.rb:84:19:84:33 | ...[...] | ActiveRecordInjection.rb:84:19:84:24 | call to params | ActiveRecordInjection.rb:84:19:84:33 | ...[...] | This SQL query depends on a $@. | ActiveRecordInjection.rb:84:19:84:24 | call to params | user-provided value | | ActiveRecordInjection.rb:88:18:88:35 | ...[...] | ActiveRecordInjection.rb:88:18:88:23 | call to params | ActiveRecordInjection.rb:88:18:88:35 | ...[...] | This SQL query depends on a $@. | ActiveRecordInjection.rb:88:18:88:23 | call to params | user-provided value | @@ -179,6 +185,7 @@ subpaths | ActiveRecordInjection.rb:94:18:94:35 | ...[...] | ActiveRecordInjection.rb:94:18:94:23 | call to params | ActiveRecordInjection.rb:94:18:94:35 | ...[...] | This SQL query depends on a $@. | ActiveRecordInjection.rb:94:18:94:23 | call to params | user-provided value | | ActiveRecordInjection.rb:96:23:96:47 | ...[...] | ActiveRecordInjection.rb:96:23:96:28 | call to params | ActiveRecordInjection.rb:96:23:96:47 | ...[...] | This SQL query depends on a $@. | ActiveRecordInjection.rb:96:23:96:28 | call to params | user-provided value | | ActiveRecordInjection.rb:108:20:108:32 | ... + ... | ActiveRecordInjection.rb:102:10:102:15 | call to params | ActiveRecordInjection.rb:108:20:108:32 | ... + ... | This SQL query depends on a $@. | ActiveRecordInjection.rb:102:10:102:15 | call to params | user-provided value | +| ActiveRecordInjection.rb:141:21:141:44 | ...[...] | ActiveRecordInjection.rb:141:21:141:26 | call to params | ActiveRecordInjection.rb:141:21:141:44 | ...[...] | This SQL query depends on a $@. | ActiveRecordInjection.rb:141:21:141:26 | call to params | user-provided value | | ActiveRecordInjection.rb:155:27:155:76 | "this is an unsafe annotation:..." | ActiveRecordInjection.rb:155:59:155:64 | call to params | ActiveRecordInjection.rb:155:27:155:76 | "this is an unsafe annotation:..." | This SQL query depends on a $@. | ActiveRecordInjection.rb:155:59:155:64 | call to params | user-provided value | | ActiveRecordInjection.rb:168:37:168:41 | query | ActiveRecordInjection.rb:173:5:173:10 | call to params | ActiveRecordInjection.rb:168:37:168:41 | query | This SQL query depends on a $@. | ActiveRecordInjection.rb:173:5:173:10 | call to params | user-provided value | | ActiveRecordInjection.rb:177:43:177:104 | "SELECT * FROM users WHERE id ..." | ActiveRecordInjection.rb:173:5:173:10 | call to params | ActiveRecordInjection.rb:177:43:177:104 | "SELECT * FROM users WHERE id ..." | This SQL query depends on a $@. | ActiveRecordInjection.rb:173:5:173:10 | call to params | user-provided value | @@ -189,4 +196,4 @@ subpaths | PgInjection.rb:20:22:20:25 | qry2 | PgInjection.rb:6:12:6:17 | call to params | PgInjection.rb:20:22:20:25 | qry2 | This SQL query depends on a $@. | PgInjection.rb:6:12:6:17 | call to params | user-provided value | | PgInjection.rb:21:28:21:31 | qry2 | PgInjection.rb:6:12:6:17 | call to params | PgInjection.rb:21:28:21:31 | qry2 | This SQL query depends on a $@. | PgInjection.rb:6:12:6:17 | call to params | user-provided value | | PgInjection.rb:32:29:32:32 | qry3 | PgInjection.rb:6:12:6:17 | call to params | PgInjection.rb:32:29:32:32 | qry3 | This SQL query depends on a $@. | PgInjection.rb:6:12:6:17 | call to params | user-provided value | -| PgInjection.rb:44:29:44:32 | qry3 | PgInjection.rb:6:12:6:17 | call to params | PgInjection.rb:44:29:44:32 | qry3 | This SQL query depends on a $@. | PgInjection.rb:6:12:6:17 | call to params | user-provided value | \ No newline at end of file +| PgInjection.rb:44:29:44:32 | qry3 | PgInjection.rb:6:12:6:17 | call to params | PgInjection.rb:44:29:44:32 | qry3 | This SQL query depends on a $@. | PgInjection.rb:6:12:6:17 | call to params | user-provided value | diff --git a/shared/mad/CHANGELOG.md b/shared/mad/CHANGELOG.md index 7b4d4fc699c..5712c750565 100644 --- a/shared/mad/CHANGELOG.md +++ b/shared/mad/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1 + +No user-facing changes. + ## 0.1.0 No user-facing changes. diff --git a/shared/mad/change-notes/released/0.1.1.md b/shared/mad/change-notes/released/0.1.1.md new file mode 100644 index 00000000000..481c4392f3d --- /dev/null +++ b/shared/mad/change-notes/released/0.1.1.md @@ -0,0 +1,3 @@ +## 0.1.1 + +No user-facing changes. diff --git a/shared/mad/codeql-pack.release.yml b/shared/mad/codeql-pack.release.yml index 2e08f40f6aa..92d1505475f 100644 --- a/shared/mad/codeql-pack.release.yml +++ b/shared/mad/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.0 +lastReleaseVersion: 0.1.1 diff --git a/shared/mad/qlpack.yml b/shared/mad/qlpack.yml index 19ec5fe1ad9..a24493a6c10 100644 --- a/shared/mad/qlpack.yml +++ b/shared/mad/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/mad -version: 0.1.0 +version: 0.1.1 groups: shared library: true dependencies: diff --git a/shared/regex/CHANGELOG.md b/shared/regex/CHANGELOG.md index 0db043e73e0..932a90a2b80 100644 --- a/shared/regex/CHANGELOG.md +++ b/shared/regex/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1 + +No user-facing changes. + ## 0.1.0 No user-facing changes. diff --git a/shared/regex/change-notes/released/0.1.1.md b/shared/regex/change-notes/released/0.1.1.md new file mode 100644 index 00000000000..481c4392f3d --- /dev/null +++ b/shared/regex/change-notes/released/0.1.1.md @@ -0,0 +1,3 @@ +## 0.1.1 + +No user-facing changes. diff --git a/shared/regex/codeql-pack.release.yml b/shared/regex/codeql-pack.release.yml index 2e08f40f6aa..92d1505475f 100644 --- a/shared/regex/codeql-pack.release.yml +++ b/shared/regex/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.0 +lastReleaseVersion: 0.1.1 diff --git a/shared/regex/qlpack.yml b/shared/regex/qlpack.yml index 828e0f8fb85..f83925a92b7 100644 --- a/shared/regex/qlpack.yml +++ b/shared/regex/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/regex -version: 0.1.0 +version: 0.1.1 groups: shared library: true dependencies: diff --git a/shared/ssa/CHANGELOG.md b/shared/ssa/CHANGELOG.md index db77b7b0fdb..df49982f4bd 100644 --- a/shared/ssa/CHANGELOG.md +++ b/shared/ssa/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1 + +No user-facing changes. + ## 0.1.0 No user-facing changes. diff --git a/shared/ssa/change-notes/released/0.1.1.md b/shared/ssa/change-notes/released/0.1.1.md new file mode 100644 index 00000000000..481c4392f3d --- /dev/null +++ b/shared/ssa/change-notes/released/0.1.1.md @@ -0,0 +1,3 @@ +## 0.1.1 + +No user-facing changes. diff --git a/shared/ssa/codeql-pack.release.yml b/shared/ssa/codeql-pack.release.yml index 2e08f40f6aa..92d1505475f 100644 --- a/shared/ssa/codeql-pack.release.yml +++ b/shared/ssa/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.0 +lastReleaseVersion: 0.1.1 diff --git a/shared/ssa/qlpack.yml b/shared/ssa/qlpack.yml index 48c0f4d4717..c154e9a3cd2 100644 --- a/shared/ssa/qlpack.yml +++ b/shared/ssa/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ssa -version: 0.1.0 +version: 0.1.1 groups: shared library: true warnOnImplicitThis: true diff --git a/shared/tutorial/CHANGELOG.md b/shared/tutorial/CHANGELOG.md index ca80e208096..01dff93e6be 100644 --- a/shared/tutorial/CHANGELOG.md +++ b/shared/tutorial/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1 + +No user-facing changes. + ## 0.1.0 No user-facing changes. diff --git a/shared/tutorial/change-notes/released/0.1.1.md b/shared/tutorial/change-notes/released/0.1.1.md new file mode 100644 index 00000000000..481c4392f3d --- /dev/null +++ b/shared/tutorial/change-notes/released/0.1.1.md @@ -0,0 +1,3 @@ +## 0.1.1 + +No user-facing changes. diff --git a/shared/tutorial/codeql-pack.release.yml b/shared/tutorial/codeql-pack.release.yml index 2e08f40f6aa..92d1505475f 100644 --- a/shared/tutorial/codeql-pack.release.yml +++ b/shared/tutorial/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.0 +lastReleaseVersion: 0.1.1 diff --git a/shared/tutorial/qlpack.yml b/shared/tutorial/qlpack.yml index fb5dbc243bc..c8d13182ed6 100644 --- a/shared/tutorial/qlpack.yml +++ b/shared/tutorial/qlpack.yml @@ -1,6 +1,6 @@ name: codeql/tutorial description: Library for the CodeQL detective tutorials, helping new users learn to write CodeQL queries. -version: 0.1.0 +version: 0.1.1 groups: shared library: true warnOnImplicitThis: true diff --git a/shared/typetracking/CHANGELOG.md b/shared/typetracking/CHANGELOG.md index 2ac1e6e8688..84420295d07 100644 --- a/shared/typetracking/CHANGELOG.md +++ b/shared/typetracking/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1 + +No user-facing changes. + ## 0.1.0 No user-facing changes. diff --git a/shared/typetracking/change-notes/released/0.1.1.md b/shared/typetracking/change-notes/released/0.1.1.md new file mode 100644 index 00000000000..481c4392f3d --- /dev/null +++ b/shared/typetracking/change-notes/released/0.1.1.md @@ -0,0 +1,3 @@ +## 0.1.1 + +No user-facing changes. diff --git a/shared/typetracking/codeql-pack.release.yml b/shared/typetracking/codeql-pack.release.yml index 2e08f40f6aa..92d1505475f 100644 --- a/shared/typetracking/codeql-pack.release.yml +++ b/shared/typetracking/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.0 +lastReleaseVersion: 0.1.1 diff --git a/shared/typetracking/qlpack.yml b/shared/typetracking/qlpack.yml index 754600f5d37..5af6571003f 100644 --- a/shared/typetracking/qlpack.yml +++ b/shared/typetracking/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/typetracking -version: 0.1.0 +version: 0.1.1 groups: shared library: true dependencies: diff --git a/shared/typos/CHANGELOG.md b/shared/typos/CHANGELOG.md index 81740128f3e..da65658ea76 100644 --- a/shared/typos/CHANGELOG.md +++ b/shared/typos/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1 + +No user-facing changes. + ## 0.1.0 No user-facing changes. diff --git a/shared/typos/change-notes/released/0.1.1.md b/shared/typos/change-notes/released/0.1.1.md new file mode 100644 index 00000000000..481c4392f3d --- /dev/null +++ b/shared/typos/change-notes/released/0.1.1.md @@ -0,0 +1,3 @@ +## 0.1.1 + +No user-facing changes. diff --git a/shared/typos/codeql-pack.release.yml b/shared/typos/codeql-pack.release.yml index 2e08f40f6aa..92d1505475f 100644 --- a/shared/typos/codeql-pack.release.yml +++ b/shared/typos/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.0 +lastReleaseVersion: 0.1.1 diff --git a/shared/typos/qlpack.yml b/shared/typos/qlpack.yml index 1691c1c191c..d9b78979125 100644 --- a/shared/typos/qlpack.yml +++ b/shared/typos/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/typos -version: 0.1.0 +version: 0.1.1 groups: shared library: true warnOnImplicitThis: true diff --git a/shared/util/CHANGELOG.md b/shared/util/CHANGELOG.md index ede5b6ee805..e90bdd0f977 100644 --- a/shared/util/CHANGELOG.md +++ b/shared/util/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.1 + +### Deprecated APIs + +* The `InlineExpectationsTest` class has been deprecated. Use `TestSig` and `MakeTest` instead. + ## 0.1.0 No user-facing changes. diff --git a/shared/util/change-notes/released/0.1.1.md b/shared/util/change-notes/released/0.1.1.md new file mode 100644 index 00000000000..2b70bed87dd --- /dev/null +++ b/shared/util/change-notes/released/0.1.1.md @@ -0,0 +1,5 @@ +## 0.1.1 + +### Deprecated APIs + +* The `InlineExpectationsTest` class has been deprecated. Use `TestSig` and `MakeTest` instead. diff --git a/shared/util/codeql-pack.release.yml b/shared/util/codeql-pack.release.yml index 2e08f40f6aa..92d1505475f 100644 --- a/shared/util/codeql-pack.release.yml +++ b/shared/util/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.0 +lastReleaseVersion: 0.1.1 diff --git a/shared/util/codeql/util/test/InlineExpectationsTest.qll b/shared/util/codeql/util/test/InlineExpectationsTest.qll index 83c6a851f7d..1e95f0d0c9d 100644 --- a/shared/util/codeql/util/test/InlineExpectationsTest.qll +++ b/shared/util/codeql/util/test/InlineExpectationsTest.qll @@ -453,7 +453,7 @@ module Make { } } - private module LegacyImpl implements TestSig { + deprecated private module LegacyImpl implements TestSig { string getARelevantTag() { result = any(InlineExpectationsTest t).getARelevantTag() } predicate hasActualResult(Impl::Location location, string element, string tag, string value) { @@ -473,7 +473,7 @@ module Make { * list of failure messages that point out where the actual results differ from the expected * results. */ - abstract class InlineExpectationsTest extends string { + abstract deprecated class InlineExpectationsTest extends string { bindingset[this] InlineExpectationsTest() { any() } @@ -488,19 +488,32 @@ module Make { } } - import MakeTest as LegacyTest + deprecated import MakeTest as LegacyTest - query predicate failures = LegacyTest::testFailures/2; + deprecated query predicate failures = LegacyTest::testFailures/2; - class ActualResult = LegacyTest::ActualTestResult; + deprecated class ActualResult = LegacyTest::ActualTestResult; - class GoodExpectation = LegacyTest::GoodTestExpectation; + deprecated class GoodExpectation = LegacyTest::GoodTestExpectation; - class FalsePositiveExpectation = LegacyTest::FalsePositiveTestExpectation; + deprecated class FalsePositiveExpectation = LegacyTest::FalsePositiveTestExpectation; - class FalseNegativeExpectation = LegacyTest::FalseNegativeTestExpectation; + deprecated class FalseNegativeExpectation = LegacyTest::FalseNegativeTestExpectation; - class InvalidExpectation = LegacyTest::InvalidTestExpectation; + deprecated class InvalidExpectation = LegacyTest::InvalidTestExpectation; + + /** + * Holds if the expectation `tag=value` is found in one or more expectation comments. + * + * This can be used when writing tests where the set of possible values must be known in advance, + * for example, when testing a predicate for which `value` is part of the binding set. + */ + predicate hasExpectationWithValue(string tag, string value) { + exists(string tags | + getAnExpectation(_, _, _, tags, value) and + tag = tags.splitAt(",") + ) + } } /** diff --git a/shared/util/qlpack.yml b/shared/util/qlpack.yml index 2b44398c0f8..6f5546633d8 100644 --- a/shared/util/qlpack.yml +++ b/shared/util/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/util -version: 0.1.0 +version: 0.1.1 groups: shared library: true dependencies: diff --git a/shared/yaml/CHANGELOG.md b/shared/yaml/CHANGELOG.md index 4c37b59dbed..84397a7f5ef 100644 --- a/shared/yaml/CHANGELOG.md +++ b/shared/yaml/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1 + +No user-facing changes. + ## 0.1.0 No user-facing changes. diff --git a/shared/yaml/change-notes/released/0.1.1.md b/shared/yaml/change-notes/released/0.1.1.md new file mode 100644 index 00000000000..481c4392f3d --- /dev/null +++ b/shared/yaml/change-notes/released/0.1.1.md @@ -0,0 +1,3 @@ +## 0.1.1 + +No user-facing changes. diff --git a/shared/yaml/codeql-pack.release.yml b/shared/yaml/codeql-pack.release.yml index 2e08f40f6aa..92d1505475f 100644 --- a/shared/yaml/codeql-pack.release.yml +++ b/shared/yaml/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.1.0 +lastReleaseVersion: 0.1.1 diff --git a/shared/yaml/qlpack.yml b/shared/yaml/qlpack.yml index 5d21475aaf5..99029a70f3b 100644 --- a/shared/yaml/qlpack.yml +++ b/shared/yaml/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/yaml -version: 0.1.0 +version: 0.1.1 groups: shared library: true warnOnImplicitThis: true diff --git a/swift/extractor/infra/SwiftTagTraits.h b/swift/extractor/infra/SwiftTagTraits.h index 0371a783bb7..b58a6f4ef83 100644 --- a/swift/extractor/infra/SwiftTagTraits.h +++ b/swift/extractor/infra/SwiftTagTraits.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace codeql { @@ -26,6 +27,9 @@ namespace codeql { using type = TAG; \ }; +#define CODEQL_SWIFT_VERSION_GE(MAJOR, MINOR) \ + CODEQL_SWIFT_VERSION_MAJOR >= (MAJOR) && CODEQL_SWIFT_VERSION_MINOR >= (MINOR) + // clang-format off // use indentation to recreate all involved type hierarchies MAP(std::filesystem::path, DbFileTag) @@ -56,6 +60,9 @@ MAP(swift::Stmt, StmtTag) MAP(swift::FailStmt, FailStmtTag) MAP(swift::ThrowStmt, ThrowStmtTag) MAP(swift::PoundAssertStmt, PoundAssertStmtTag) +#if CODEQL_SWIFT_VERSION_GE(5, 9) + MAP(swift::DiscardStmt, void) // TODO (introduced in 5.9) +#endif MAP(swift::Argument, ArgumentTag) MAP(swift::KeyPathExpr::Component, KeyPathComponentTag) @@ -95,7 +102,11 @@ MAP(swift::Expr, ExprTag) MAP(swift::IdentityExpr, IdentityExprTag) MAP(swift::ParenExpr, ParenExprTag) MAP(swift::DotSelfExpr, DotSelfExprTag) - MAP(swift::MoveExpr, void) // TODO (introduced in 5.8) +#if CODEQL_SWIFT_VERSION_GE(5, 9) + MAP(swift::BorrowExpr, void) // TODO (introduced in 5.9) +#else + MAP(swift::MoveExpr, void) // TODO (introduced in 5.8, gone in 5.9) +#endif MAP(swift::AwaitExpr, AwaitExprTag) MAP(swift::UnresolvedMemberChainResultExpr, UnresolvedMemberChainResultExprTag) MAP(swift::AnyTryExpr, AnyTryExprTag) @@ -189,6 +200,13 @@ MAP(swift::Expr, ExprTag) MAP(swift::TapExpr, TapExprTag) MAP(swift::TypeJoinExpr, void) // TODO (introduced in 5.8) MAP(swift::MacroExpansionExpr, void) // TODO (introduced in 5.8) +#if CODEQL_SWIFT_VERSION_GE(5, 9) + MAP(swift::CopyExpr, void) // TODO (introduced in 5.9) + MAP(swift::ConsumeExpr, void) // TODO (introduced in 5.9) + MAP(swift::MaterializePackExpr, void) // TODO (introduced in 5.9) + MAP(swift::SingleValueStmtExpr, void) // TODO (introduced in 5.9) +#endif + MAP(swift::Decl, DeclTag) MAP(swift::ValueDecl, ValueDeclTag) MAP(swift::TypeDecl, TypeDeclTag) @@ -231,6 +249,9 @@ MAP(swift::Decl, DeclTag) MAP(swift::PrefixOperatorDecl, PrefixOperatorDeclTag) MAP(swift::PostfixOperatorDecl, PostfixOperatorDeclTag) MAP(swift::MacroExpansionDecl, void) // TODO (introduced in 5.8) +#if CODEQL_SWIFT_VERSION_GE(5, 9) + MAP(swift::MissingDecl, void) // TODO (introduced in 5.9) +#endif MAP(swift::Pattern, PatternTag) MAP(swift::ParenPattern, ParenPatternTag) @@ -266,6 +287,10 @@ MAP(swift::TypeBase, TypeTag) MAP(swift::BuiltinUnsafeValueBufferType, BuiltinUnsafeValueBufferTypeTag) MAP(swift::BuiltinDefaultActorStorageType, BuiltinDefaultActorStorageTypeTag) MAP(swift::BuiltinVectorType, BuiltinVectorTypeTag) +#if CODEQL_SWIFT_VERSION_GE(5, 9) + MAP(swift::BuiltinPackIndexType, void) // TODO: (introduced in 5.9) + MAP(swift::BuiltinNonDefaultDistributedActorStorageType, void) // TODO: (introduced in 5.9) +#endif MAP(swift::TupleType, TupleTypeTag) MAP(swift::ReferenceStorageType, ReferenceStorageTypeTag) MAP(swift::WeakStorageType, WeakStorageTypeTag) @@ -307,6 +332,9 @@ MAP(swift::TypeBase, TypeTag) MAP(swift::SILBoxType, void) // SIL types cannot really appear in the frontend run) MAP(swift::SILMoveOnlyWrappedType, void) // SIL types cannot really appear in the frontend run) MAP(swift::SILTokenType, void) // SIL types cannot really appear in the frontend run) +#if CODEQL_SWIFT_VERSION_GE(5, 9) + MAP(swift::SILPackType, void) // TODO: (introduced in 5.9) +#endif MAP(swift::ProtocolCompositionType, ProtocolCompositionTypeTag) MAP(swift::ParameterizedProtocolType, ParameterizedProtocolTypeTag) MAP(swift::ExistentialType, ExistentialTypeTag) @@ -314,6 +342,9 @@ MAP(swift::TypeBase, TypeTag) MAP(swift::InOutType, InOutTypeTag) MAP(swift::PackType, void) // experimental variadic generics MAP(swift::PackExpansionType, void) // experimental variadic generics +#if CODEQL_SWIFT_VERSION_GE(5, 9) + MAP(swift::PackElementType, void) // TODO: (introduced in 5.9) +#endif MAP(swift::TypeVariableType, void) // created during type checking and only used for constraint checking MAP(swift::SugarType, SugarTypeTag) MAP(swift::ParenType, ParenTypeTag) diff --git a/swift/ql/lib/CHANGELOG.md b/swift/ql/lib/CHANGELOG.md index e31a562c13c..433d053b3ab 100644 --- a/swift/ql/lib/CHANGELOG.md +++ b/swift/ql/lib/CHANGELOG.md @@ -1,3 +1,22 @@ +## 0.2.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* The regular expression library now understands mode flags specified by `Regex` methods and the `NSRegularExpression` initializer. +* The regular expression library now understands mode flags specified at the beginning of a regular expression (for example `(?is)`). +* Added detail to the taint model for `URL`. +* Added new heuristics to `SensitiveExprs.qll`, enhancing detection from the library. + ## 0.2.0 ### Breaking Changes diff --git a/swift/ql/lib/change-notes/released/0.2.1.md b/swift/ql/lib/change-notes/released/0.2.1.md new file mode 100644 index 00000000000..d5118ce86ee --- /dev/null +++ b/swift/ql/lib/change-notes/released/0.2.1.md @@ -0,0 +1,18 @@ +## 0.2.1 + +### New Features + +* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`. + Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed. + +### Minor Analysis Improvements + +* Data flow configurations can now include a predicate `neverSkip(Node node)` + in order to ensure inclusion of certain nodes in the path explanations. The + predicate defaults to the end-points of the additional flow steps provided in + the configuration, which means that such steps now always are visible by + default in path explanations. +* The regular expression library now understands mode flags specified by `Regex` methods and the `NSRegularExpression` initializer. +* The regular expression library now understands mode flags specified at the beginning of a regular expression (for example `(?is)`). +* Added detail to the taint model for `URL`. +* Added new heuristics to `SensitiveExprs.qll`, enhancing detection from the library. diff --git a/swift/ql/lib/codeql-pack.release.yml b/swift/ql/lib/codeql-pack.release.yml index 5274e27ed52..df29a726bcc 100644 --- a/swift/ql/lib/codeql-pack.release.yml +++ b/swift/ql/lib/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.2.0 +lastReleaseVersion: 0.2.1 diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlow.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlow.qll index f34554d6eea..03975c6a54a 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlow.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlow.qll @@ -46,6 +46,14 @@ signature module ConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or isAdditionalFlowStep(_, node) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -114,7 +122,7 @@ signature module StateConfigSig { * Holds if data flow through `node` is prohibited when the flow state is * `state`. */ - predicate isBarrier(Node node, FlowState state); + default predicate isBarrier(Node node, FlowState state) { none() } /** Holds if data flow into `node` is prohibited. */ default predicate isBarrierIn(Node node) { none() } @@ -131,7 +139,9 @@ signature module StateConfigSig { * Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps. * This step is only applicable in `state1` and updates the flow state to `state2`. */ - predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2); + default predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { + none() + } /** * Holds if an arbitrary number of implicit read steps of content `c` may be @@ -139,6 +149,17 @@ signature module StateConfigSig { */ default predicate allowImplicitRead(Node node, ContentSet c) { none() } + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + default predicate neverSkip(Node node) { + isAdditionalFlowStep(node, _) or + isAdditionalFlowStep(_, node) or + isAdditionalFlowStep(node, _, _, _) or + isAdditionalFlowStep(_, _, node, _) + } + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll index 410543e0fc9..29561b0f0a6 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll @@ -66,6 +66,12 @@ signature module FullStateConfigSig { */ predicate allowImplicitRead(Node node, ContentSet c); + /** + * Holds if `node` should never be skipped over in the `PathGraph` and in path + * explanations. + */ + predicate neverSkip(Node node); + /** * Gets the virtual dispatch branching limit when calculating field flow. * This can be overridden to a smaller value to improve performance (a @@ -254,6 +260,11 @@ module Impl { not fullBarrier(node2) } + pragma[nomagic] + private predicate isUnreachableInCall1(NodeEx n, LocalCallContextSpecificCall cc) { + isUnreachableInCallCached(n.asNode(), cc.getCall()) + } + /** * Holds if data can flow in one local step from `node1` to `node2`. */ @@ -2019,7 +2030,8 @@ module Impl { castNode(this.asNode()) or clearsContentCached(this.asNode(), _) or expectsContentCached(this.asNode(), _) or - neverSkipInPathGraph(this.asNode()) + neverSkipInPathGraph(this.asNode()) or + Config::neverSkip(this.asNode()) } } @@ -2108,7 +2120,7 @@ module Impl { NodeEx node1, FlowState state, NodeEx node2, boolean preservesValue, DataFlowType t, LocalCallContext cc ) { - not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCall1(node2, cc) and ( localFlowEntry(node1, pragma[only_bind_into](state)) and ( @@ -2123,7 +2135,7 @@ module Impl { ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and - not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) + not isUnreachableInCall1(node1, cc) or exists(NodeEx mid | localFlowStepPlus(node1, pragma[only_bind_into](state), mid, preservesValue, t, cc) and @@ -2160,10 +2172,8 @@ module Impl { preservesValue = false and t = node2.getDataFlowType() and callContext.relevantFor(node1.getEnclosingCallable()) and - not exists(DataFlowCall call | call = callContext.(LocalCallContextSpecificCall).getCall() | - isUnreachableInCallCached(node1.asNode(), call) or - isUnreachableInCallCached(node2.asNode(), call) - ) + not isUnreachableInCall1(node1, callContext) and + not isUnreachableInCall1(node2, callContext) } } @@ -2703,7 +2713,7 @@ module Impl { ParamNodeEx getParamNode() { result = p } - override string toString() { result = p + ": " + ap } + override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn @@ -2755,12 +2765,21 @@ module Impl { ) } + private predicate forceUnfold(AccessPathApprox apa) { + forceHighPrecision(apa.getHead()) + or + exists(Content c2 | + apa = TConsCons(_, _, c2, _) and + forceHighPrecision(c2) + ) + } + /** * Holds with `unfold = false` if a precise head-tail representation of `apa` is * expected to be expensive. Holds with `unfold = true` otherwise. */ private predicate evalUnfold(AccessPathApprox apa, boolean unfold) { - if forceHighPrecision(apa.getHead()) + if forceUnfold(apa) then unfold = true else exists(int aps, int nodes, int apLimit, int tupleLimit | @@ -3089,6 +3108,12 @@ module Impl { result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" } + private string ppSummaryCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">" + } + /** Gets a textual representation of this element. */ string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() } @@ -3097,7 +3122,9 @@ module Impl { * representation of the call context. */ string toStringWithContext() { - result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + result = + this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() + + this.ppSummaryCtx() } /** diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl1.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl1.qll index be70086a93a..b0de9745816 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl1.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl1.qll @@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig { any(Configuration config).allowImplicitRead(node, c) } + predicate neverSkip(Node node) { none() } + int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) } FlowFeature getAFeature() { result = any(Configuration config).getAFeature() } diff --git a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/CustomUrlSchemes.qll b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/CustomUrlSchemes.qll index e217de4478d..109d1acec9c 100644 --- a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/CustomUrlSchemes.qll +++ b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/CustomUrlSchemes.qll @@ -56,7 +56,9 @@ private class ApplicationWithLaunchOptionsFunc extends Function { private class LaunchOptionsUrlVarDecl extends VarDecl { LaunchOptionsUrlVarDecl() { - this.getEnclosingDecl().asNominalTypeDecl().getFullName() = "UIApplication.LaunchOptionsKey" and + // ideally this would be the more accurate, but currently less robust: + // this.getEnclosingDecl().asNominalTypeDecl().getFullName() = "UIApplication.LaunchOptionsKey" and + this.getType().(NominalType).getFullName() = "UIApplication.LaunchOptionsKey" and this.getName() = "url" } } diff --git a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Url.qll b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Url.qll index 60141e4d31f..2f03fdd5327 100644 --- a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Url.qll +++ b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Url.qll @@ -35,6 +35,36 @@ private class UrlRequestFieldsInheritTaint extends TaintInheritingContent, } } +/** + * A content implying that, if a `URLResource` is tainted, then its fields `name` + * and `subdirectory` are tainted. + */ +private class UrlResourceFieldsInheritTaint extends TaintInheritingContent, + DataFlow::Content::FieldContent +{ + UrlResourceFieldsInheritTaint() { + this.getField().getEnclosingDecl().asNominalTypeDecl().getName() = "URLResource" and + this.getField().getName() = ["name", "subdirectory"] + } +} + +/** + * A content implying that, if a `URLResourceValues` is tainted, then certain + * fields are tainted. + */ +private class UrlResourceValuesFieldsInheritTaint extends TaintInheritingContent, + DataFlow::Content::FieldContent +{ + UrlResourceValuesFieldsInheritTaint() { + this.getField().getEnclosingDecl().asNominalTypeDecl().getName() = "URLResourceValues" and + this.getField().getName() = + [ + "name", "path", "canonicalPath", "localizedLabel", "localizedName", "parentDirectory", + "thumbnail" + ] + } +} + /** * A model for `URL` members that are sources of remote flow. */ @@ -49,14 +79,74 @@ private class UrlRemoteFlowSource extends SourceModelCsv { } /** - * A model for `URL` members that permit taint flow. + * A model for `URL` and related class members that permit taint flow. */ private class UrlSummaries extends SummaryModelCsv { override predicate row(string row) { row = [ ";URL;true;init(string:);(String);;Argument[0];ReturnValue;taint", - ";URL;true;init(string:relativeTo:);(String,URL?);;Argument[0,1];ReturnValue;taint" + ";URL;true;init(string:relativeTo:);(String,URL?);;Argument[0..1];ReturnValue;taint", + ";URL;true;init(fileURLWithPath:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(fileURLWithPath:isDirectory:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(fileURLWithPath:relativeTo:);;;Argument[0..1];ReturnValue;taint", + ";URL;true;init(fileURLWithPath:isDirectory:relativeTo:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(fileURLWithPath:isDirectory:relativeTo:);;;Argument[2];ReturnValue;taint", + ";URL;true;init(fileURLWithFileSystemRepresentation:isDirectory:relativeTo:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(fileURLWithFileSystemRepresentation:isDirectory:relativeTo:);;;Argument[2];ReturnValue;taint", + ";URL;true;init(fileReferenceLiteralResourceName:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(_:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(_:isDirectory:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(resolvingBookmarkData:options:relativeTo:bookmarkDataIsStale:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(resolvingBookmarkData:options:relativeTo:bookmarkDataIsStale:);;;Argument[2];ReturnValue;taint", + ";URL;true;init(resolvingAliasFileAt:options:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(resource:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(dataRepresentation:relativeTo:isAbsolute:);;;Argument[0..1];ReturnValue;taint", + ";URL;true;init(_:strategy:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(filePath:directoryHint:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(filePath:directoryHint:relativeTo:);;;Argument[0];ReturnValue;taint", + ";URL;true;init(filePath:directoryHint:relativeTo:);;;Argument[2];ReturnValue;taint", + ";URL;true;init(for:in:appropriateFor:create:);;;Argument[0..2];ReturnValue;taint", + ";URL;true;init(string:encodingInvalidCharacters:);;;Argument[0];ReturnValue;taint", + ";URL;true;resourceValues(forKeys:);;;Argument[-1];ReturnValue;taint", + ";URL;true;setResourceValues(_:);;;Argument[0];Argument[-1];taint", + ";URL;true;setTemporaryResourceValue(_:forKey:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;withUnsafeFileSystemRepresentation(_:);;;Argument[-1],Argument[0].Parameter[0];ReturnValue;taint", + ";URL;true;withUnsafeFileSystemRepresentation(_:);;;Argument[0].ReturnValue;ReturnValue;taint", + ";URL;true;resolvingSymlinksInPath();;;Argument[-1];ReturnValue;taint", + ";URL;true;appendPathComponent(_:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;appendPathComponent(_:isDirectory:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;appendPathComponent(_:conformingTo:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;appendingPathComponent(_:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;appendingPathComponent(_:isDirectory:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;appendingPathComponent(_:conformingTo:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;appendPathExtension(_:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;appendingPathExtension(_:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;deletingLastPathComponent();;;Argument[-1];ReturnValue;taint", + ";URL;true;deletingPathExtension();;;Argument[-1];ReturnValue;taint", + ";URL;true;bookmarkData(options:includingResourceValuesForKeys:relativeTo:);;;Argument[-1];ReturnValue;taint", + ";URL;true;bookmarkData(options:includingResourceValuesForKeys:relativeTo:);;;Argument[1..2];ReturnValue;taint", + ";URL;true;bookmarkData(withContentsOf:);;;Argument[0];ReturnValue;taint", + ";URL;true;resourceValues(forKeys:fromBookmarkData:);;;Argument[1];ReturnValue;taint", + ";URL;true;promisedItemResourceValues(forKeys:);;;Argument[-1];ReturnValue;taint", + ";URL;true;append(component:directoryHint:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;append(components:directoryHint:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;append(path:directoryHint:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;append(queryItems:);;;Argument[-1..0];Argument[-1];taint", + ";URL;true;appending(component:directoryHint:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;appending(components:directoryHint:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;appending(path:directoryHint:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;appending(queryItems:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;formatted();;;Argument[-1];ReturnValue;taint", + ";URL;true;formatted(_:);;;Argument[-1..0];ReturnValue;taint", + ";URL;true;fragment(percentEncoded:);;;Argument[-1];ReturnValue;taint", + ";URL;true;host(percentEncoded:);;;Argument[-1];ReturnValue;taint", + ";URL;true;password(percentEncoded:);;;Argument[-1];ReturnValue;taint", + ";URL;true;path(percentEncoded:);;;Argument[-1];ReturnValue;taint", + ";URL;true;query(percentEncoded:);;;Argument[-1];ReturnValue;taint", + ";URL;true;user(percentEncoded:);;;Argument[-1];ReturnValue;taint", + ";URL;true;homeDirectory(forUser:);;;Argument[0];ReturnValue;taint", + ";URLResource;true;init(name:subdirectory:locale:bundle:);;;Argument[0..1];ReturnValue;taint", ] } } diff --git a/swift/ql/lib/codeql/swift/regex/Regex.qll b/swift/ql/lib/codeql/swift/regex/Regex.qll index b97847a1ac6..0ae533d6843 100644 --- a/swift/ql/lib/codeql/swift/regex/Regex.qll +++ b/swift/ql/lib/codeql/swift/regex/Regex.qll @@ -6,51 +6,221 @@ import swift import codeql.swift.regex.RegexTreeView private import codeql.swift.dataflow.DataFlow private import internal.ParseRegex +private import internal.RegexTracking /** - * A data flow configuration for tracking string literals that are used as - * regular expressions. - */ -private module RegexUseConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node node) { node.asExpr() instanceof StringLiteralExpr } - - predicate isSink(DataFlow::Node node) { node.asExpr() = any(RegexEval eval).getRegexInput() } - - predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { - // flow through `Regex` initializer, i.e. from a string to a `Regex` object. - exists(CallExpr call | - ( - call.getStaticTarget().(Method).hasQualifiedName("Regex", ["init(_:)", "init(_:as:)"]) or - call.getStaticTarget() - .(Method) - .hasQualifiedName("NSRegularExpression", "init(pattern:options:)") - ) and - nodeFrom.asExpr() = call.getArgument(0).getExpr() and - nodeTo.asExpr() = call - ) - } -} - -private module RegexUseFlow = DataFlow::Global; - -/** - * A string literal that is used as a regular expression in a regular - * expression evaluation. For example the string literal `"(a|b).*"` in: + * A string literal that is used as a regular expression. For example + * the string literal `"(a|b).*"` in: * ``` * Regex("(a|b).*").firstMatch(in: myString) * ``` */ private class ParsedStringRegex extends RegExp, StringLiteralExpr { - RegexEval eval; + DataFlow::Node use; - ParsedStringRegex() { - RegexUseFlow::flow(DataFlow::exprNode(this), DataFlow::exprNode(eval.getRegexInput())) + ParsedStringRegex() { StringLiteralUseFlow::flow(DataFlow::exprNode(this), use) } + + /** + * Gets a dataflow node where this string literal is used as a regular + * expression. + */ + DataFlow::Node getUse() { result = use } +} + +/** + * A data-flow node where a regular expression object is created. + */ +abstract class RegexCreation extends DataFlow::Node { + /** + * Gets a dataflow node for the string that the regular expression object is + * created from. + */ + abstract DataFlow::Node getStringInput(); + + /** + * Gets a dataflow node for the options input that might contain parse mode + * flags (if any). + */ + DataFlow::Node getOptionsInput() { none() } +} + +/** + * A data-flow node where a `Regex` object is created. + */ +private class RegexRegexCreation extends RegexCreation { + DataFlow::Node input; + + RegexRegexCreation() { + exists(CallExpr call | + call.getStaticTarget().(Method).hasQualifiedName("Regex", ["init(_:)", "init(_:as:)"]) and + input.asExpr() = call.getArgument(0).getExpr() and + this.asExpr() = call + ) + } + + override DataFlow::Node getStringInput() { result = input } +} + +/** + * A data-flow node where an `NSRegularExpression` object is created. + */ +private class NSRegularExpressionRegexCreation extends RegexCreation { + DataFlow::Node input; + + NSRegularExpressionRegexCreation() { + exists(CallExpr call | + call.getStaticTarget() + .(Method) + .hasQualifiedName("NSRegularExpression", "init(pattern:options:)") and + input.asExpr() = call.getArgument(0).getExpr() and + this.asExpr() = call + ) + } + + override DataFlow::Node getStringInput() { result = input } + + override DataFlow::Node getOptionsInput() { + result.asExpr() = this.asExpr().(CallExpr).getArgument(1).getExpr() + } +} + +private newtype TRegexParseMode = + MkIgnoreCase() or // case insensitive + MkVerbose() or // ignores whitespace and `#` comments within patterns + MkDotAll() or // dot matches all characters, including line terminators + MkMultiLine() or // `^` and `$` also match beginning and end of lines + MkUnicode() // Unicode UAX 29 word boundary mode + +/** + * A regular expression parse mode flag. + */ +class RegexParseMode extends TRegexParseMode { + /** + * Gets the name of this parse mode flag. + */ + string getName() { + this = MkIgnoreCase() and result = "IGNORECASE" + or + this = MkVerbose() and result = "VERBOSE" + or + this = MkDotAll() and result = "DOTALL" + or + this = MkMultiLine() and result = "MULTILINE" + or + this = MkUnicode() and result = "UNICODE" } /** - * Gets a call that evaluates this regular expression. + * Gets a textual representation of this `RegexParseMode`. */ - RegexEval getEval() { result = eval } + string toString() { result = this.getName() } +} + +/** + * A unit class for adding additional flow steps for regular expressions. + */ +class RegexAdditionalFlowStep extends Unit { + /** + * Holds if the step from `node1` to `node2` should be considered a flow + * step for regular expressions. + */ + abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo); + + /** + * Holds if a regular expression parse mode is either set (`isSet` = true) + * or unset (`isSet` = false) at `node`. Parse modes propagate through + * array construction and regex construction. + */ + abstract predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet); +} + +/** + * An additional flow step for `Regex`. + */ +class RegexRegexAdditionalFlowStep extends RegexAdditionalFlowStep { + override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + this.setsParseModeEdge(nodeFrom, nodeTo, _, _) + } + + override predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet) { + this.setsParseModeEdge(_, node, mode, isSet) + } + + private predicate setsParseModeEdge( + DataFlow::Node nodeFrom, DataFlow::Node nodeTo, RegexParseMode mode, boolean isSet + ) { + // `Regex` methods that modify the parse mode of an existing `Regex` object. + exists(CallExpr ce | + nodeFrom.asExpr() = ce.getQualifier() and + nodeTo.asExpr() = ce and + // decode the parse mode being set + ( + ce.getStaticTarget().(Method).hasQualifiedName("Regex", "ignoresCase(_:)") and + mode = MkIgnoreCase() + or + ce.getStaticTarget().(Method).hasQualifiedName("Regex", "dotMatchesNewlines(_:)") and + mode = MkDotAll() + or + ce.getStaticTarget().(Method).hasQualifiedName("Regex", "anchorsMatchLineEndings(_:)") and + mode = MkMultiLine() + ) and + // decode the value being set + if ce.getArgument(0).getExpr().(BooleanLiteralExpr).getValue() = false + then isSet = false // mode is set to false + else isSet = true // mode is set to true OR mode is set to default (=true) OR mode is set to an unknown value + ) + } +} + +/** + * An additional flow step for `NSRegularExpression`. + */ +class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep { + override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() } + + override predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet) { + // `NSRegularExpression.Options` values (these are typically combined, then passed into + // the `NSRegularExpression` initializer). + node.asExpr() + .(MemberRefExpr) + .getMember() + .(FieldDecl) + .hasQualifiedName("NSRegularExpression.Options", "caseInsensitive") and + mode = MkIgnoreCase() and + isSet = true + or + node.asExpr() + .(MemberRefExpr) + .getMember() + .(FieldDecl) + .hasQualifiedName("NSRegularExpression.Options", "allowCommentsAndWhitespace") and + mode = MkVerbose() and + isSet = true + or + node.asExpr() + .(MemberRefExpr) + .getMember() + .(FieldDecl) + .hasQualifiedName("NSRegularExpression.Options", "dotMatchesLineSeparators") and + mode = MkDotAll() and + isSet = true + or + node.asExpr() + .(MemberRefExpr) + .getMember() + .(FieldDecl) + .hasQualifiedName("NSRegularExpression.Options", "anchorsMatchLines") and + mode = MkMultiLine() and + isSet = true + or + node.asExpr() + .(MemberRefExpr) + .getMember() + .(FieldDecl) + .hasQualifiedName("NSRegularExpression.Options", "useUnicodeWordBoundaries") and + mode = MkUnicode() and + isSet = true + } } /** @@ -61,7 +231,8 @@ private class ParsedStringRegex extends RegExp, StringLiteralExpr { */ abstract class RegexEval extends CallExpr { /** - * Gets the input to this call that is the regular expression being evaluated. + * Gets the input to this call that is the regular expression being evaluated. This may + * be a regular expression object or a string literal. */ abstract Expr getRegexInput(); @@ -73,7 +244,29 @@ abstract class RegexEval extends CallExpr { /** * Gets a regular expression value that is evaluated here (if any can be identified). */ - RegExp getARegex() { result.(ParsedStringRegex).getEval() = this } + RegExp getARegex() { + // string literal used directly as a regex + result.(ParsedStringRegex).getUse().asExpr() = this.getRegexInput() + or + // string literal -> regex object -> use + exists(RegexCreation regexCreation | + result.(ParsedStringRegex).getUse() = regexCreation.getStringInput() and + RegexUseFlow::flow(regexCreation, DataFlow::exprNode(this.getRegexInput())) + ) + } + + /** + * Gets a parse mode that is set at this evaluation (in at least one path + * from the creation of the regular expression object). + */ + RegexParseMode getAParseMode() { + exists(DataFlow::Node setNode | + // parse mode flag is set + any(RegexAdditionalFlowStep s).setsParseMode(setNode, result, true) and + // reaches this eval + RegexParseModeFlow::flow(setNode, DataFlow::exprNode(this.getRegexInput())) + ) + } } /** diff --git a/swift/ql/lib/codeql/swift/regex/internal/ParseRegex.qll b/swift/ql/lib/codeql/swift/regex/internal/ParseRegex.qll index 7a837cd733e..f49f61cb365 100644 --- a/swift/ql/lib/codeql/swift/regex/internal/ParseRegex.qll +++ b/swift/ql/lib/codeql/swift/regex/internal/ParseRegex.qll @@ -6,6 +6,8 @@ */ import swift +private import RegexTracking +private import codeql.swift.regex.Regex /** * A `Expr` containing a regular expression term, that is, either @@ -16,17 +18,17 @@ abstract class RegExp extends Expr { /** * Holds if this `RegExp` has the `s` flag for multi-line matching. */ - predicate isDotAll() { none() } + predicate isDotAll() { this.getAMode() = "DOTALL" } /** * Holds if this `RegExp` has the `i` flag for case-insensitive matching. */ - predicate isIgnoreCase() { none() } + predicate isIgnoreCase() { this.getAMode() = "IGNORECASE" } /** - * Gets the flags for this `RegExp`, or the empty string if it has no flags. + * Gets a string representing the flags for this `RegExp`, or the empty string if it has no flags. */ - string getFlags() { result = "" } + string getFlags() { result = concat(string mode | mode = this.getAMode() | mode, " | ") } /** * Helper predicate for `charSetStart(int start, int end)`. @@ -274,6 +276,68 @@ abstract class RegExp extends Expr { private predicate isGroupStart(int i) { this.nonEscapedCharAt(i) = "(" and not this.inCharSet(i) } + /** + * Holds if a parse mode starts between `start` and `end`. + */ + private predicate flagGroupStart(int start, int end) { + this.isGroupStart(start) and + this.getChar(start + 1) = "?" and + this.getChar(start + 2) in ["i", "x", "s", "m", "w"] and + end = start + 2 + } + + /** + * Holds if a parse mode group is between `start` and `end`, and includes the + * mode flag `c`. For example the following span, with mode flag `i`: + * ``` + * (?i) + * ``` + */ + private predicate flagGroup(int start, int end, string c) { + exists(int inStart, int inEnd | + this.flagGroupStart(start, inStart) and + this.groupContents(start, end, inStart, inEnd) and + this.getChar([inStart .. inEnd - 1]) = c + ) + } + + /** + * Gets a mode of this regular expression string if it is defined by a mode prefix. + */ + string getModeFromPrefix() { + exists(string c | this.flagGroup(_, _, c) | + c = "i" and result = "IGNORECASE" // case insensitive + or + c = "x" and result = "VERBOSE" // ignores whitespace and `#` comments within patterns + or + c = "s" and result = "DOTALL" // dot matches all characters, including line terminators + or + c = "m" and result = "MULTILINE" // `^` and `$` also match beginning and end of lines + or + c = "w" and result = "UNICODE" // Unicode UAX 29 word boundary mode + ) + } + + /** + * Gets a mode (if any) of this regular expression in any evaluation. Can be + * any of: + * IGNORECASE + * VERBOSE + * DOTALL + * MULTILINE + * UNICODE + */ + string getAMode() { + // mode flags from inside the regex string + result = this.getModeFromPrefix() + or + // mode flags applied to the regex object before evaluation + exists(RegexEval e | + e.getARegex() = this and + result = e.getAParseMode().getName() + ) + } + /** * Holds if the `i`th character could not be parsed. */ @@ -653,6 +717,8 @@ abstract class RegExp extends Expr { this.commentGroupStart(start, end) or this.simpleGroupStart(start, end) + or + this.flagGroupStart(start, end) } /** Matches the start of a non-capturing group, e.g. `(?:` */ diff --git a/swift/ql/lib/codeql/swift/regex/internal/RegexTracking.qll b/swift/ql/lib/codeql/swift/regex/internal/RegexTracking.qll new file mode 100644 index 00000000000..46360b66636 --- /dev/null +++ b/swift/ql/lib/codeql/swift/regex/internal/RegexTracking.qll @@ -0,0 +1,104 @@ +/** + * Provides classes and predicates that track strings and regular expressions + * to where they are used, along with properties of the regex such as parse + * mode flags that have been set. + */ + +import swift +import codeql.swift.regex.RegexTreeView +import codeql.swift.dataflow.DataFlow +private import ParseRegex +private import codeql.swift.regex.Regex + +/** + * A data flow configuration for tracking string literals that are used to + * create regular expression objects, or are evaluated directly as regular + * expressions. + */ +private module StringLiteralUseConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { node.asExpr() instanceof StringLiteralExpr } + + predicate isSink(DataFlow::Node node) { + // evaluated directly as a regular expression + node.asExpr() = any(RegexEval eval).getRegexInput() + or + // used to create a regular expression object + node = any(RegexCreation regexCreation).getStringInput() + } +} + +module StringLiteralUseFlow = DataFlow::Global; + +/** + * A data flow configuration for tracking regular expression objects from + * creation to the point of use. + */ +private module RegexUseConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { + // creation of the regex + node instanceof RegexCreation + } + + predicate isSink(DataFlow::Node node) { + // evaluation of the regex + node.asExpr() = any(RegexEval eval).getRegexInput() + } + + predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + any(RegexAdditionalFlowStep s).step(nodeFrom, nodeTo) + } +} + +module RegexUseFlow = DataFlow::Global; + +/** + * A data flow configuration for tracking regular expression parse mode + * flags from wherever they are created or set through to regular expression + * evaluation. The flow state encodes which parse mode flag was set. + */ +private module RegexParseModeConfig implements DataFlow::StateConfigSig { + class FlowState = RegexParseMode; + + predicate isSource(DataFlow::Node node, FlowState flowstate) { + // parse mode flag is set + any(RegexAdditionalFlowStep s).setsParseMode(node, flowstate, true) + } + + predicate isSink(DataFlow::Node node, FlowState flowstate) { + // evaluation of the regex + node.asExpr() = any(RegexEval eval).getRegexInput() and + exists(flowstate) + } + + predicate isBarrier(DataFlow::Node node) { none() } + + predicate isBarrier(DataFlow::Node node, FlowState flowstate) { + // parse mode flag is unset + any(RegexAdditionalFlowStep s).setsParseMode(node, flowstate, false) + } + + predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + // flow through array construction + exists(ArrayExpr arr | + nodeFrom.asExpr() = arr.getAnElement() and + nodeTo.asExpr() = arr + ) + or + // flow through regex creation + exists(RegexCreation create | + nodeFrom = create.getOptionsInput() and + nodeTo = create + ) + or + // additional flow steps for regular expression objects + any(RegexAdditionalFlowStep s).step(nodeFrom, nodeTo) + } + + predicate isAdditionalFlowStep( + DataFlow::Node nodeFrom, FlowState flowstateFrom, DataFlow::Node nodeTo, FlowState flowStateTo + ) { + none() + } +} + +module RegexParseModeFlow = DataFlow::GlobalWithState; diff --git a/swift/ql/lib/codeql/swift/security/SensitiveExprs.qll b/swift/ql/lib/codeql/swift/security/SensitiveExprs.qll index a991f8e2c37..9de1047642e 100644 --- a/swift/ql/lib/codeql/swift/security/SensitiveExprs.qll +++ b/swift/ql/lib/codeql/swift/security/SensitiveExprs.qll @@ -35,7 +35,7 @@ class SensitiveCredential extends SensitiveDataType, TCredential { result = HeuristicNames::maybeSensitiveRegexp(classification) ) or - result = "(?is).*(account|accnt|license).?(id|key).*" + result = "(?is).*((account|accnt|licen(se|ce)).?(id|key)|one.?time.?code|pass.?phrase).*" } } @@ -50,21 +50,26 @@ class SensitivePrivateInfo extends SensitiveDataType, TPrivateInfo { "(?is).*(" + // Inspired by the list on https://cwe.mitre.org/data/definitions/359.html // Government identifiers, such as Social Security Numbers - "social.?security|national.?insurance|" + + "social.?security|employer.?identification|national.?insurance|resident.?id|" + + "passport.?(num|no)|" + // Contact information, such as home addresses - "post.?code|zip.?code|home.?address|" + + "post.?code|zip.?code|home.?addr|" + // and telephone numbers - "(mob(ile)?|home).?(num|no|tel|phone)|(tel|fax).?(num|no)|telephone|" + + "(mob(ile)?|home).?(num|no|tel|phone)|(tel|fax).?(num|no|phone)|" + "emergency.?contact|" + // Geographic location - where the user is (or was) - "latitude|longitude|" + + "l(atitude|ongitude)|nationality|" + // Financial data - such as credit card numbers, salary, bank accounts, and debts - "credit.?card|debit.?card|salary|bank.?account|acc(ou)?nt.?(no|num)|" + + "(credit|debit|bank|visa).?(card|num|no|acc(ou?)nt)|acc(ou)?nt.?(no|num|credit)|" + + "salary|billing|credit.?(rating|score)|" + // Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc. - "email|" + + "e(mail|_mail)|" + // Health - medical conditions, insurance status, prescription records - "birthday|birth.?date|date.?of.?birth|medical|" + + "birth.?da(te|y)|da(te|y).?(of.?)?birth|" + + "medical|(health|care).?plan|healthkit|appointment|prescription|" + + "blood.?(type|alcohol|glucose|pressure)|heart.?(rate|rhythm)|body.?(mass|fat)|" + + "menstrua|pregnan|insulin|inhaler|" + // Relationships - work and family - "employer|spouse" + + "employ(er|ee)|spouse|maiden.?name" + // --- ").*" } @@ -80,13 +85,32 @@ private string regexpProbablySafe() { result = "(?is).*(file|path|url|invalid).*" } +/** + * Gets a string that is to be tested for sensitivity. + */ +private string sensitiveCandidateStrings() { + result = any(VarDecl v).getName() + or + result = any(Function f).getShortName() + or + result = any(Argument a).getLabel() +} + +/** + * Gets a string from the candidates that is sensitive. + */ +private string sensitiveStrings(SensitiveDataType sensitiveType) { + result = sensitiveCandidateStrings() and + result.regexpMatch(sensitiveType.getRegexp()) +} + /** * A `VarDecl` that might be used to contain sensitive data. */ private class SensitiveVarDecl extends VarDecl { SensitiveDataType sensitiveType; - SensitiveVarDecl() { this.getName().regexpMatch(sensitiveType.getRegexp()) } + SensitiveVarDecl() { this.getName() = sensitiveStrings(sensitiveType) } predicate hasInfo(string label, SensitiveDataType type) { label = this.getName() and @@ -99,15 +123,11 @@ private class SensitiveVarDecl extends VarDecl { */ private class SensitiveFunction extends Function { SensitiveDataType sensitiveType; - string name; // name of the function, not including the argument list. - SensitiveFunction() { - name = this.getShortName() and - name.regexpMatch(sensitiveType.getRegexp()) - } + SensitiveFunction() { this.getShortName() = sensitiveStrings(sensitiveType) } predicate hasInfo(string label, SensitiveDataType type) { - label = name and + label = this.getShortName() and sensitiveType = type } } @@ -118,7 +138,7 @@ private class SensitiveFunction extends Function { private class SensitiveArgument extends Argument { SensitiveDataType sensitiveType; - SensitiveArgument() { this.getLabel().regexpMatch(sensitiveType.getRegexp()) } + SensitiveArgument() { this.getLabel() = sensitiveStrings(sensitiveType) } predicate hasInfo(string label, SensitiveDataType type) { label = this.getLabel() and @@ -169,6 +189,7 @@ class SensitiveExpr extends Expr { * A function that is likely used to encrypt or hash data. */ private class EncryptionFunction extends Function { + cached EncryptionFunction() { this.getName().regexpMatch("(?is).*(crypt|hash|encode|protect).*") } } diff --git a/swift/ql/lib/codeql/swift/security/StringLengthConflationQuery.qll b/swift/ql/lib/codeql/swift/security/StringLengthConflationQuery.qll index 1aabb4ccbda..af3ae1f7cc1 100644 --- a/swift/ql/lib/codeql/swift/security/StringLengthConflationQuery.qll +++ b/swift/ql/lib/codeql/swift/security/StringLengthConflationQuery.qll @@ -31,17 +31,9 @@ module StringLengthConflationConfig implements DataFlow::StateConfigSig { predicate isBarrier(DataFlow::Node barrier) { barrier instanceof StringLengthConflationBarrier } - predicate isBarrier(DataFlow::Node barrier, FlowState flowstate) { none() } - predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { any(StringLengthConflationAdditionalFlowStep s).step(nodeFrom, nodeTo) } - - predicate isAdditionalFlowStep( - DataFlow::Node nodeFrom, FlowState flowstateFrom, DataFlow::Node nodeTo, FlowState flowStateTo - ) { - none() - } } /** diff --git a/swift/ql/lib/codeql/swift/security/regex/RegexInjectionExtensions.qll b/swift/ql/lib/codeql/swift/security/regex/RegexInjectionExtensions.qll new file mode 100644 index 00000000000..3c6003b8bc8 --- /dev/null +++ b/swift/ql/lib/codeql/swift/security/regex/RegexInjectionExtensions.qll @@ -0,0 +1,59 @@ +/** + * Provides classes and predicates to reason about regular expression injection + * vulnerabilities. + */ + +import swift +import codeql.swift.dataflow.DataFlow +import codeql.swift.dataflow.ExternalFlow +import codeql.swift.regex.Regex + +/** + * A data flow sink for regular expression injection vulnerabilities. + */ +abstract class RegexInjectionSink extends DataFlow::Node { } + +/** + * A barrier for regular expression injection vulnerabilities. + */ +abstract class RegexInjectionBarrier extends DataFlow::Node { } + +/** + * A unit class for adding additional flow steps. + */ +class RegexInjectionAdditionalFlowStep extends Unit { + /** + * Holds if the step from `node1` to `node2` should be considered a flow + * step for paths related to regular expression injection vulnerabilities. + */ + abstract predicate step(DataFlow::Node node1, DataFlow::Node node2); +} + +/** + * A sink that is a regular expression evaluation defined in the Regex library. + * This includes various methods that consume a regular expression string, but + * in general misses cases where a regular expression string is converted into + * an object (such as a `Regex` or `NSRegularExpression`) for later evaluation. + * These cases are modeled separately. + */ +private class EvalRegexInjectionSink extends RegexInjectionSink { + EvalRegexInjectionSink() { this.asExpr() = any(RegexEval e).getRegexInput() } +} + +/** + * A sink that is a regular expression use defined in a CSV model. + */ +private class DefaultRegexInjectionSink extends RegexInjectionSink { + DefaultRegexInjectionSink() { sinkNode(this, "regex-use") } +} + +private class RegexInjectionSinks extends SinkModelCsv { + override predicate row(string row) { + row = + [ + ";Regex;true;init(_:);;;Argument[0];regex-use", + ";Regex;true;init(_:as:);;;Argument[0];regex-use", + ";NSRegularExpression;true;init(pattern:options:);;;Argument[0];regex-use", + ] + } +} diff --git a/swift/ql/lib/codeql/swift/security/regex/RegexInjectionQuery.qll b/swift/ql/lib/codeql/swift/security/regex/RegexInjectionQuery.qll new file mode 100644 index 00000000000..8fee12442d3 --- /dev/null +++ b/swift/ql/lib/codeql/swift/security/regex/RegexInjectionQuery.qll @@ -0,0 +1,30 @@ +/** + * Provides a taint-tracking configuration for detecting regular expression + * injection vulnerabilities. + */ + +import swift +import codeql.swift.dataflow.DataFlow +import codeql.swift.dataflow.TaintTracking +import codeql.swift.dataflow.FlowSources +import codeql.swift.security.regex.RegexInjectionExtensions + +/** + * A taint configuration for detecting regular expression injection vulnerabilities. + */ +module RegexInjectionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof FlowSource } + + predicate isSink(DataFlow::Node sink) { sink instanceof RegexInjectionSink } + + predicate isBarrier(DataFlow::Node barrier) { barrier instanceof RegexInjectionBarrier } + + predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + any(RegexInjectionAdditionalFlowStep s).step(nodeFrom, nodeTo) + } +} + +/** + * Detect taint flow of tainted data that reaches a regular expression sink. + */ +module RegexInjectionFlow = TaintTracking::Global; diff --git a/swift/ql/lib/qlpack.yml b/swift/ql/lib/qlpack.yml index 6dab746da5b..9f87668fce7 100644 --- a/swift/ql/lib/qlpack.yml +++ b/swift/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/swift-all -version: 0.2.0 +version: 0.2.1 groups: swift extractor: swift dbscheme: swift.dbscheme diff --git a/swift/ql/src/CHANGELOG.md b/swift/ql/src/CHANGELOG.md index 6e2f1c94742..00033599985 100644 --- a/swift/ql/src/CHANGELOG.md +++ b/swift/ql/src/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.2.1 + +### New Queries + +* Added new query "Regular expression injection" (`swift/regex-injection`). The query finds places where user input is used to construct a regular expression without proper escaping. +* Added new query "Inefficient regular expression" (`swift/redos`). This query finds regular expressions that require exponential time to match certain inputs and may make an application vulnerable to denial-of-service attacks. + ## 0.2.0 ### Bug Fixes diff --git a/swift/ql/src/change-notes/released/0.2.1.md b/swift/ql/src/change-notes/released/0.2.1.md new file mode 100644 index 00000000000..9439ef46fe2 --- /dev/null +++ b/swift/ql/src/change-notes/released/0.2.1.md @@ -0,0 +1,6 @@ +## 0.2.1 + +### New Queries + +* Added new query "Regular expression injection" (`swift/regex-injection`). The query finds places where user input is used to construct a regular expression without proper escaping. +* Added new query "Inefficient regular expression" (`swift/redos`). This query finds regular expressions that require exponential time to match certain inputs and may make an application vulnerable to denial-of-service attacks. diff --git a/swift/ql/src/codeql-pack.release.yml b/swift/ql/src/codeql-pack.release.yml index 5274e27ed52..df29a726bcc 100644 --- a/swift/ql/src/codeql-pack.release.yml +++ b/swift/ql/src/codeql-pack.release.yml @@ -1,2 +1,2 @@ --- -lastReleaseVersion: 0.2.0 +lastReleaseVersion: 0.2.1 diff --git a/swift/ql/src/qlpack.yml b/swift/ql/src/qlpack.yml index 80416ee1ddd..8844d5320a5 100644 --- a/swift/ql/src/qlpack.yml +++ b/swift/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/swift-queries -version: 0.2.0 +version: 0.2.1 groups: - swift - queries diff --git a/swift/ql/src/queries/Security/CWE-1333/ReDoS.qhelp b/swift/ql/src/queries/Security/CWE-1333/ReDoS.qhelp new file mode 100644 index 00000000000..ddbb2835bc2 --- /dev/null +++ b/swift/ql/src/queries/Security/CWE-1333/ReDoS.qhelp @@ -0,0 +1,26 @@ + + + + +

    Consider the following regular expression:

    + +/^_(__|.)+_$/ +

    + Its sub-expression "(__|.)+" can match the string + "__" either by the first alternative "__" to the + left of the "|" operator, or by two repetitions of the second + alternative "." to the right. Therefore, a string consisting of an + odd number of underscores followed by some other character will cause the + regular expression engine to run for an exponential amount of time before + rejecting the input. +

    +

    + This problem can be avoided by rewriting the regular expression to remove + the ambiguity between the two branches of the alternative inside the + repetition: +

    + +/^_(__|[^_])+_$/ +
    + +
    diff --git a/swift/ql/src/queries/Security/CWE-1333/ReDoS.ql b/swift/ql/src/queries/Security/CWE-1333/ReDoS.ql new file mode 100644 index 00000000000..beaee208915 --- /dev/null +++ b/swift/ql/src/queries/Security/CWE-1333/ReDoS.ql @@ -0,0 +1,25 @@ +/** + * @name Inefficient regular expression + * @description A regular expression that requires exponential time to match certain inputs + * can be a performance bottleneck, and may be vulnerable to denial-of-service + * attacks. + * @kind problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id swift/redos + * @tags security + * external/cwe/cwe-1333 + * external/cwe/cwe-730 + * external/cwe/cwe-400 + */ + +import codeql.swift.regex.Regex +private import codeql.swift.regex.RegexTreeView::RegexTreeView as TreeView +import codeql.regex.nfa.ExponentialBackTracking::Make + +from TreeView::RegExpTerm t, string pump, State s, string prefixMsg +where hasReDoSResult(t, pump, s, prefixMsg) +select t, + "This part of the regular expression may cause exponential backtracking on strings " + prefixMsg + + "containing many repetitions of '" + pump + "'." diff --git a/swift/ql/src/queries/Security/CWE-1333/ReDoSIntroduction.inc.qhelp b/swift/ql/src/queries/Security/CWE-1333/ReDoSIntroduction.inc.qhelp new file mode 100644 index 00000000000..63cfa0fd23f --- /dev/null +++ b/swift/ql/src/queries/Security/CWE-1333/ReDoSIntroduction.inc.qhelp @@ -0,0 +1,37 @@ + + + +

    + Some regular expressions take a long time to match certain input strings + to the point where the time it takes to match a string of length n + is proportional to nk or even 2n. + Such regular expressions can negatively affect performance, and potentially allow + a malicious user to perform a Denial of Service ("DoS") attack by crafting + an expensive input string for the regular expression to match. +

    +

    + The regular expression engine used by Swift uses + backtracking non-deterministic finite automata to implement regular + expression matching. While this approach is space-efficient and allows + supporting advanced features like capture groups, it is not time-efficient + in general. The worst-case time complexity of such an automaton can be + polynomial or exponential, meaning that for strings of a certain + shape, increasing the input length by ten characters may make the + automaton about 1000 times slower. +

    +

    + Typically, a regular expression is affected by this problem if it contains + a repetition of the form r* or r+ where the + sub-expression r is ambiguous in the sense that it can match + some string in multiple ways. More information about the precise + circumstances can be found in the references. +

    +
    + +

    + Modify the regular expression to remove the ambiguity, or ensure that the + strings matched with the regular expression are short enough that the + time complexity does not matter. +

    +
    +
    diff --git a/swift/ql/src/queries/Security/CWE-1333/ReDoSReferences.inc.qhelp b/swift/ql/src/queries/Security/CWE-1333/ReDoSReferences.inc.qhelp new file mode 100644 index 00000000000..641b1e56b7c --- /dev/null +++ b/swift/ql/src/queries/Security/CWE-1333/ReDoSReferences.inc.qhelp @@ -0,0 +1,13 @@ + + + +
  • OWASP: + Regular expression Denial of Service - ReDoS. +
  • +
  • Wikipedia: ReDoS.
  • +
  • Wikipedia: Time complexity.
  • +
  • James Kirrage, Asiri Rathnayake, Hayo Thielecke: + Static Analysis for Regular Expression Denial-of-Service Attack. +
  • +
    +
    diff --git a/swift/ql/src/queries/Security/CWE-730/RegexInjection.qhelp b/swift/ql/src/queries/Security/CWE-730/RegexInjection.qhelp new file mode 100644 index 00000000000..9b25131d65f --- /dev/null +++ b/swift/ql/src/queries/Security/CWE-730/RegexInjection.qhelp @@ -0,0 +1,50 @@ + + + + +

    +Constructing a regular expression with unsanitized user input is dangerous, +since a malicious user may be able to modify the meaning of the expression. They +may be able to cause unexpected program behaviour, or perform a denial-of-service +attack. For example, they may provide a regular expression fragment that takes +exponential time to evaluate in the worst case. +

    +
    + + +

    +Before embedding user input into a regular expression, use a sanitization +function such as NSRegularExpression::escapedPattern(for:) to escape +meta-characters that have special meaning. +

    +
    + + +

    +The following examples construct regular expressions from user input without +sanitizing it first: +

    + +

    +If user input is used to construct a regular expression it should be sanitized +first. This ensures that the user cannot insert characters that have special +meanings in regular expressions. +

    + +
    + + +
  • +OWASP: +Regular expression Denial of Service - ReDoS. +
  • +
  • +Wikipedia: ReDoS. +
  • +
  • +Swift: NSRegularExpression.escapedPattern(for:). +
  • +
    +
    diff --git a/swift/ql/src/queries/Security/CWE-730/RegexInjection.ql b/swift/ql/src/queries/Security/CWE-730/RegexInjection.ql new file mode 100644 index 00000000000..98422c0b79d --- /dev/null +++ b/swift/ql/src/queries/Security/CWE-730/RegexInjection.ql @@ -0,0 +1,24 @@ +/** + * @name Regular expression injection + * @description User input should not be used in regular expressions without first being escaped, + * otherwise a malicious user may be able to provide a regex that could require + * exponential time on certain inputs. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id swift/regex-injection + * @tags security + * external/cwe/cwe-730 + * external/cwe/cwe-400 + */ + +import swift +import codeql.swift.dataflow.DataFlow +import codeql.swift.security.regex.RegexInjectionQuery +import RegexInjectionFlow::PathGraph + +from RegexInjectionFlow::PathNode sourceNode, RegexInjectionFlow::PathNode sinkNode +where RegexInjectionFlow::flowPath(sourceNode, sinkNode) +select sinkNode.getNode(), sourceNode, sinkNode, + "This regular expression is constructed from a $@.", sourceNode.getNode(), "user-provided value" diff --git a/swift/ql/src/queries/Security/CWE-730/RegexInjectionBad.swift b/swift/ql/src/queries/Security/CWE-730/RegexInjectionBad.swift new file mode 100644 index 00000000000..a6f93673f05 --- /dev/null +++ b/swift/ql/src/queries/Security/CWE-730/RegexInjectionBad.swift @@ -0,0 +1,12 @@ +func processRemoteInput(remoteInput: String) { + ... + + // BAD: Unsanitized user input is used to construct a regular expression + let regex1 = try Regex(remoteInput) + + // BAD: Unsanitized user input is used to construct a regular expression + let regexStr = "abc|\(remoteInput)" + let regex2 = try NSRegularExpression(pattern: regexStr) + + ... +} diff --git a/swift/ql/src/queries/Security/CWE-730/RegexInjectionGood.swift b/swift/ql/src/queries/Security/CWE-730/RegexInjectionGood.swift new file mode 100644 index 00000000000..4c7ea6fb6ad --- /dev/null +++ b/swift/ql/src/queries/Security/CWE-730/RegexInjectionGood.swift @@ -0,0 +1,13 @@ +func processRemoteInput(remoteInput: String) { + ... + + // GOOD: Regular expression is not derived from user input + let regex1 = try Regex(myRegex) + + // GOOD: User input is sanitized before being used to construct a regular expression + let escapedInput = NSRegularExpression.escapedPattern(for: remoteInput) + let regexStr = "abc|\(escapedInput)" + let regex2 = try NSRegularExpression(pattern: regexStr) + + ... +} diff --git a/swift/ql/test/library-tests/dataflow/taint/libraries/CONSISTENCY/CfgConsistency.expected b/swift/ql/test/library-tests/dataflow/taint/libraries/CONSISTENCY/CfgConsistency.expected new file mode 100644 index 00000000000..34ad2196338 --- /dev/null +++ b/swift/ql/test/library-tests/dataflow/taint/libraries/CONSISTENCY/CfgConsistency.expected @@ -0,0 +1,2 @@ +deadEnd +| url.swift:493:2:493:28 | call to sink(any:) | diff --git a/swift/ql/test/library-tests/dataflow/taint/libraries/url.swift b/swift/ql/test/library-tests/dataflow/taint/libraries/url.swift index 79d8ade4078..d45bdd905b2 100644 --- a/swift/ql/test/library-tests/dataflow/taint/libraries/url.swift +++ b/swift/ql/test/library-tests/dataflow/taint/libraries/url.swift @@ -5,8 +5,53 @@ class NSObject struct URL { + typealias BookmarkResolutionOptions = NSURL.BookmarkResolutionOptions + + struct URLResourceKey : Hashable { + init(_ rawValue: String) { } + init(rawValue: String) { } + + // … + } + + struct URLResourceValues { + var name: String? + var path: String? + var canonicalPath: String? + var localizedLabel: String? + var localizedName: String? + var parentDirectory: URL? + + // … + } + + struct AsyncBytes { + // … + } + + enum DirectoryHint { + case inferFromPath + } + init?(string: String) {} init?(string: String, relativeTo: URL?) {} + init(fileURLWithPath path: String) { } + init(fileURLWithPath path: String, isDirectory: Bool) { } + init(fileURLWithPath path: String, relativeTo base: URL?) { } + init(fileURLWithPath path: String, isDirectory: Bool, relativeTo base: URL?) { } + init(fileURLWithFileSystemRepresentation path: UnsafePointer, isDirectory: Bool, relativeTo baseURL: URL?) { } + init(fileReferenceLiteralResourceName name: String) { } + init?(_ path: FilePath) { } + init?(_ path: FilePath, isDirectory: Bool) { } + init(resolvingBookmarkData data: Data, options: URL.BookmarkResolutionOptions = [], relativeTo url: URL? = nil, bookmarkDataIsStale: inout Bool) throws { } + init(resolvingAliasFileAt url: URL, options: URL.BookmarkResolutionOptions = []) throws { } + init?(resource: URLResource) { } + init?(dataRepresentation: Data, relativeTo url: URL?, isAbsolute: Bool = false) { } + init?(filePath path: FilePath, directoryHint: URL.DirectoryHint = .inferFromPath) { } + init(filePath path: String, directoryHint: URL.DirectoryHint = .inferFromPath, relativeTo base: URL? = nil) { } + + var dataRepresentation: Data { get { return Data(0) } } + var absoluteString: String { get { return "" } } var absoluteURL: URL { get {return URL(string: "")!} } var baseURL: URL { get {return URL(string: "")!} } var fragment: String? { get {return nil} } @@ -24,6 +69,54 @@ struct URL var standardizedFileURL: URL { get {return URL(string: "")!} } var user: String? { get {return nil} } var password: String? { get {return nil} } + var resourceBytes: URL.AsyncBytes { get { return (nil as AsyncBytes?)! } } + var lines: AsyncLineSequence { get { return (nil as AsyncLineSequence?)! } } + + func resourceValues(forKeys keys: Set) throws -> URLResourceValues { return URLResourceValues() } + static func resourceValues(forKeys keys: Set, fromBookmarkData data: Data) -> URLResourceValues? { return nil } + mutating func setResourceValues(_ values: URLResourceValues) throws { } + mutating func setTemporaryResourceValue(_ value: Any, forKey key: URLResourceKey) { } + func withUnsafeFileSystemRepresentation(_ block: (UnsafePointer?) throws -> ResultType) rethrows -> ResultType { return (nil as ResultType?)! } + func resolvingSymlinksInPath() -> URL { return URL(string: "")! } + mutating func appendPathComponent(_ pathComponent: String) { } + mutating func appendPathComponent(_ pathComponent: String, isDirectory: Bool) { } + func appendingPathComponent(_ pathComponent: String) -> URL { return URL(string: "")! } + func appendingPathComponent(_ pathComponent: String, isDirectory: Bool) -> URL { return URL(string: "")! } + mutating func appendPathExtension(_ pathExtension: String) { } + func appendingPathExtension(_ pathExtension: String) -> URL { return URL(string: "")! } + func deletingLastPathComponent() -> URL { return URL(string: "")! } + func deletingPathExtension() -> URL { return URL(string: "")! } + mutating func append(component: S, directoryHint: URL.DirectoryHint = .inferFromPath) where S: StringProtocol { } + mutating func append(components: S..., directoryHint: URL.DirectoryHint = .inferFromPath) where S: StringProtocol { } + mutating func append(path: S, directoryHint: URL.DirectoryHint = .inferFromPath) where S: StringProtocol { } + mutating func append(queryItems: [URLQueryItem]) { } + func appending(component: S, directoryHint: URL.DirectoryHint = .inferFromPath) -> URL where S: StringProtocol { return URL(string: "")! } + func appending(components: S..., directoryHint: URL.DirectoryHint = .inferFromPath) -> URL where S: StringProtocol { return URL(string: "")! } + func appending(path: S, directoryHint: URL.DirectoryHint = .inferFromPath) -> URL where S: StringProtocol { return URL(string: "")! } + func appending(queryItems: [URLQueryItem]) -> URL { return URL(string: "")! } + func promisedItemResourceValues(forKeys keys: Set) throws -> URLResourceValues { return (nil as URLResourceValues?)! } + func formatted() -> String { return "" } + func fragment(percentEncoded: Bool = true) -> String? { return nil } + func host(percentEncoded: Bool = true) -> String? { return nil } + func password(percentEncoded: Bool = true) -> String? { return nil } + func path(percentEncoded: Bool = true) -> String { return "" } + func query(percentEncoded: Bool = true) -> String? { return nil } + func user(percentEncoded: Bool = true) -> String? { return nil } + + // simplified: + func bookmarkData(options: Int = 0, includingResourceValuesForKeys keys: Set? = nil, relativeTo url: URL? = nil) throws -> Data { return Data(0) } + static func bookmarkData(withContentsOf url: URL) throws -> Data { return Data(0) } + + static var homeDirectory: URL { get { return URL(string: "")! } } + static func homeDirectory(forUser user: String) -> URL? { return nil } +} + +class NSURL { + struct BookmarkResolutionOptions : OptionSet { + let rawValue: Int + } + + init?(string: String) {} } class Data @@ -31,12 +124,36 @@ class Data init(_ elements: S) {} } +struct FilePath { + init(_ string: String) {} +} + +struct AsyncLineSequence : AsyncSequence { + typealias Element = String + struct AsyncIterator : AsyncIteratorProtocol { + typealias Element = String + mutating func next() async -> String? { return nil } + } + func makeAsyncIterator() -> AsyncIterator { return AsyncIterator() } +} + class InputStream {} struct Mirror {} typealias TimeInterval = Double +struct URLResource { + // simplified: + init(name: String, subdirectory: String? = nil, locale: Int = 0, bundle: Int = 0) { + self.name = name + self.subdirectory = subdirectory + } + + let name: String + let subdirectory: String? +} + struct URLRequest { enum CachePolicy { case none } enum NetworkServiceType { case none } @@ -64,7 +181,9 @@ struct URLRequest { var requiresDNSSECValidation: Bool = false } -class URLResponse : NSObject {} +struct URLQueryItem { } + +class URLResponse : NSObject { } class URLSessionTask : NSObject { } @@ -93,54 +212,56 @@ func taintThroughURL() { let urlTainted = URL(string: tainted)! sink(arg: urlClean) - sink(arg: urlTainted) // $ tainted=91 + sink(arg: urlTainted) // $ tainted=210 // Fields - sink(arg: urlTainted.absoluteURL) // $ tainted=91 - sink(arg: urlTainted.baseURL) // $ SPURIOUS: $ tainted=91 - sink(string: urlTainted.fragment!) // $ tainted=91 - sink(string: urlTainted.host!) // $ tainted=91 - sink(string: urlTainted.lastPathComponent) // $ tainted=91 - sink(string: urlTainted.path) // $ tainted=91 - sink(string: urlTainted.pathComponents[0]) // $ tainted=91 - sink(string: urlTainted.pathExtension) // $ tainted=91 - sink(int: urlTainted.port!) // $ tainted=91 - sink(string: urlTainted.query!) // $ tainted=91 - sink(string: urlTainted.relativePath) // $ tainted=91 - sink(string: urlTainted.relativeString) // $ tainted=91 - sink(string: urlTainted.scheme!) // $ tainted=91 - sink(arg: urlTainted.standardized) // $ tainted=91 - sink(arg: urlTainted.standardizedFileURL) // $ tainted=91 - sink(string: urlTainted.user!) // $ tainted=91 - sink(string: urlTainted.password!) // $ tainted=91 + sink(data: urlTainted.dataRepresentation) // $ tainted=210 + sink(string: urlTainted.absoluteString) // $ tainted=210 + sink(arg: urlTainted.absoluteURL) // $ tainted=210 + sink(arg: urlTainted.baseURL) // $ tainted=210 + sink(string: urlTainted.fragment!) // $ tainted=210 + sink(string: urlTainted.host!) // $ tainted=210 + sink(string: urlTainted.lastPathComponent) // $ tainted=210 + sink(string: urlTainted.path) // $ tainted=210 + sink(string: urlTainted.pathComponents[0]) // $ tainted=210 + sink(string: urlTainted.pathExtension) // $ tainted=210 + sink(int: urlTainted.port!) // $ tainted=210 + sink(string: urlTainted.query!) // $ tainted=210 + sink(string: urlTainted.relativePath) // $ tainted=210 + sink(string: urlTainted.relativeString) // $ tainted=210 + sink(string: urlTainted.scheme!) // $ tainted=210 + sink(arg: urlTainted.standardized) // $ tainted=210 + sink(arg: urlTainted.standardizedFileURL) // $ tainted=210 + sink(string: urlTainted.user!) // $ tainted=210 + sink(string: urlTainted.password!) // $ tainted=210 + sink(any: urlTainted.resourceBytes) // $ tainted=210 sink(arg: URL(string: clean, relativeTo: nil)!) - sink(arg: URL(string: tainted, relativeTo: nil)!) // $ tainted=91 + sink(arg: URL(string: tainted, relativeTo: nil)!) // $ tainted=210 sink(arg: URL(string: clean, relativeTo: urlClean)!) // Fields (assuming `clean` was a relative path instead of a full URL) - sink(arg: URL(string: clean, relativeTo: urlTainted)!.absoluteURL) // $ tainted=91 - sink(arg: URL(string: clean, relativeTo: urlTainted)!.baseURL) // $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.fragment!) // $ SPURIOUS: $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.host!) // $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.lastPathComponent) // $ SPURIOUS: $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.path) // $ SPURIOUS: $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.pathComponents[0]) // $ SPURIOUS: $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.pathExtension) // $ SPURIOUS: $ tainted=91 - sink(int: URL(string: clean, relativeTo: urlTainted)!.port!) // $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.query!) // $ SPURIOUS: $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.relativePath) // $ SPURIOUS: $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.relativeString) // $ SPURIOUS: $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.scheme!) // $ tainted=91 - sink(arg: URL(string: clean, relativeTo: urlTainted)!.standardized) // $ tainted=91 - sink(arg: URL(string: clean, relativeTo: urlTainted)!.standardizedFileURL) // $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.user!) // $ tainted=91 - sink(string: URL(string: clean, relativeTo: urlTainted)!.password!) // $ tainted=91 + sink(arg: URL(string: clean, relativeTo: urlTainted)!.absoluteURL) // $ tainted=210 + sink(arg: URL(string: clean, relativeTo: urlTainted)!.baseURL) // $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.fragment!) // $ $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.host!) // $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.lastPathComponent) // $ $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.path) // $ $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.pathComponents[0]) // $ $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.pathExtension) // $ $ tainted=210 + sink(int: URL(string: clean, relativeTo: urlTainted)!.port!) // $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.query!) // $ $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.relativePath) // $ $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.relativeString) // $ $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.scheme!) // $ tainted=210 + sink(arg: URL(string: clean, relativeTo: urlTainted)!.standardized) // $ tainted=210 + sink(arg: URL(string: clean, relativeTo: urlTainted)!.standardizedFileURL) // $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.user!) // $ tainted=210 + sink(string: URL(string: clean, relativeTo: urlTainted)!.password!) // $ tainted=210 if let x = URL(string: clean) { sink(arg: x) } - if let y = URL(string: tainted) { - sink(arg: y) // $ tainted=91 + sink(arg: y) // $ tainted=210 } var urlClean2 : URL! @@ -149,11 +270,160 @@ func taintThroughURL() { var urlTainted2 : URL! urlTainted2 = URL(string: tainted) - sink(arg: urlTainted2) // $ tainted=91 + sink(arg: urlTainted2) // $ tainted=210 - let task = URLSession.shared.dataTask(with: urlTainted) { (data, response, error) in - sink(data: data!) // $ tainted=91 + let _ = URLSession.shared.dataTask(with: urlTainted) { (data, response, error) in + sink(data: data!) // $ tainted=210 } + + sink(arg: URL(fileURLWithPath: tainted)) // $ tainted=210 + sink(arg: URL(fileURLWithPath: tainted, isDirectory: false)) // $ tainted=210 + sink(arg: URL(fileURLWithPath: tainted, relativeTo: urlClean)) // $ tainted=210 + sink(arg: URL(fileURLWithPath: clean, relativeTo: urlTainted)) // $ tainted=210 + sink(arg: URL(fileURLWithPath: tainted, isDirectory: false, relativeTo: urlClean)) // $ tainted=210 + sink(arg: URL(fileURLWithPath: clean, isDirectory: false, relativeTo: urlTainted)) // $ tainted=210 + sink(arg: URL(fileURLWithPath: tainted)) // $ tainted=210 + + let _ = clean.withCString({ + ptrClean in + sink(arg: URL(fileURLWithFileSystemRepresentation: ptrClean, isDirectory: false, relativeTo: nil)) + sink(arg: URL(fileURLWithFileSystemRepresentation: ptrClean, isDirectory: false, relativeTo: urlTainted)) // $ MISSING: tainted=210 + }); + sink(arg: URL(fileURLWithFileSystemRepresentation: 0 as! UnsafePointer, isDirectory: false, relativeTo: urlTainted)) // $ tainted=210 + let _ = tainted.withCString({ + ptrTainted in + sink(arg: URL(fileURLWithFileSystemRepresentation: ptrTainted, isDirectory: false, relativeTo: nil)) // $ MISSING: tainted=210 + }) + + sink(arg: URL(fileReferenceLiteralResourceName: tainted)) // $ tainted=210 + sink(arg: URL(FilePath(tainted))!) // $ tainted=210 + sink(arg: URL(FilePath(tainted), isDirectory: false)!) // $ tainted=210 + + if let values = try? urlTainted.resourceValues(forKeys: []) { + sink(any: values) // $ tainted=210 + sink(string: values.name!) // $ tainted=210 + sink(string: values.path!) // $ tainted=210 + sink(string: values.canonicalPath!) // $ tainted=210 + sink(string: values.localizedLabel!) // $ tainted=210 + sink(string: values.localizedName!) // $ tainted=210 + sink(any: values.parentDirectory!) // $ tainted=210 + } + if let values = try? urlTainted.promisedItemResourceValues(forKeys: []) { + sink(any: values) // $ tainted=210 + sink(string: values.name!) // $ tainted=210 + sink(string: values.path!) // $ tainted=210 + sink(string: values.canonicalPath!) // $ tainted=210 + sink(string: values.localizedLabel!) // $ tainted=210 + sink(string: values.localizedName!) // $ tainted=210 + sink(any: values.parentDirectory!) // $ tainted=210 + } + + urlClean.withUnsafeFileSystemRepresentation({ + ptr in + sink(any: ptr!) + }) + urlTainted.withUnsafeFileSystemRepresentation({ + ptr in + sink(any: ptr!) // $ MISSING: tainted=210 + }) + + sink(arg: urlTainted.resolvingSymlinksInPath()) // $ tainted=210 + sink(arg: urlTainted.appendingPathComponent(clean)) // $ tainted=210 + sink(arg: urlClean.appendingPathComponent(tainted)) // $ tainted=210 + sink(arg: urlTainted.appendingPathComponent(clean, isDirectory: false)) // $ tainted=210 + sink(arg: urlClean.appendingPathComponent(tainted, isDirectory: false)) // $ tainted=210 + sink(arg: urlTainted.appendingPathExtension(clean)) // $ tainted=210 + sink(arg: urlClean.appendingPathExtension(tainted)) // $ tainted=210 + sink(arg: urlTainted.deletingLastPathComponent()) // $ tainted=210 + sink(arg: urlTainted.deletingPathExtension()) // $ tainted=210 + sink(arg: urlTainted.appending(component: clean)) // $ tainted=210 + sink(arg: urlClean.appending(component: tainted)) // $ tainted=210 + sink(arg: urlTainted.appending(components: clean)) // $ tainted=210 + sink(arg: urlClean.appending(components: tainted)) // $ MISSING: tainted=210 + sink(arg: urlClean.appending(components: clean, tainted)) // $ MISSING: tainted=210 + sink(arg: urlTainted.appending(path: clean)) // $ tainted=210 + sink(arg: urlClean.appending(path: tainted)) // $ tainted=210 + sink(arg: urlTainted.appending(queryItems: [])) // $ tainted=210 + sink(arg: urlClean.appending(queryItems: [source() as! URLQueryItem])) // $ MISSING: tainted=210 + + sink(arg: URL(filePath: tainted)) // $ tainted=210 + sink(arg: URL(filePath: tainted, relativeTo: nil)) // $ tainted=210 + sink(arg: URL(filePath: clean, relativeTo: urlTainted)) // $ tainted=210 + sink(arg: try! URL(resolvingAliasFileAt: urlTainted)) // $ tainted=210 + sink(arg: URL(resource: URLResource(name: tainted))!) // $ tainted=210 + sink(arg: URL(resource: URLResource(name: clean, subdirectory: tainted))!) // $ tainted=210 + + let dataClean = Data(clean) + let dataTainted = Data(tainted) + var stale = true + sink(arg: URL(dataRepresentation: dataTainted, relativeTo: urlClean)!) // $ tainted=210 + sink(arg: URL(dataRepresentation: dataClean, relativeTo: urlTainted)!) // $ tainted=210 + sink(arg: try! URL(resolvingBookmarkData: dataTainted, bookmarkDataIsStale: &stale)) // $ tainted=210 + sink(arg: try! URL(resolvingBookmarkData: dataClean, relativeTo: urlTainted, bookmarkDataIsStale: &stale)) // $ tainted=210 + + sink(string: urlTainted.formatted()) // $ tainted=210 + sink(string: urlTainted.fragment()!) // $ tainted=210 + sink(string: urlTainted.host()!) // $ tainted=210 + sink(string: urlTainted.password()!) // $ tainted=210 + sink(string: urlTainted.path()) // $ tainted=210 + sink(string: urlTainted.query()!) // $ tainted=210 + sink(string: urlTainted.user()!) // $ tainted=210 + + var url1 = URL(string: clean)! + if let values = try? urlClean.resourceValues(forKeys: []) { + try! url1.setResourceValues(values) + } + sink(arg: url1) + if let values = try? urlTainted.resourceValues(forKeys: []) { + try! url1.setResourceValues(values) + } + sink(arg: url1) // $ tainted=210 + + var url2 = URL(string: clean)! + url2.setTemporaryResourceValue(source(), forKey: URL.URLResourceKey("")) + sink(arg: url2) // $ tainted=383 + + var url3 = URL(string: clean)! + url3.appendPathComponent(clean) + sink(arg: url3) + url3.appendPathComponent(tainted) + sink(arg: url3) // $ tainted=210 + + var url4 = URL(string: clean)! + url4.appendPathComponent(tainted, isDirectory: false) + sink(arg: url4) // $ tainted=210 + + var url5 = URL(string: clean)! + url5.appendPathExtension(tainted) + sink(arg: url5) // $ tainted=210 + + var url6 = URL(string: clean)! + url6.append(component: tainted) + sink(arg: url6) // $ tainted=210 + + var url7 = URL(string: clean)! + url7.append(components: tainted) + sink(arg: url7) // $ MISSING: tainted=210 + + var url8 = URL(string: clean)! + url8.append(components: clean, tainted) + sink(arg: url8) // $ MISSING: tainted=210 + + var url9 = URL(string: clean)! + url9.append(path: tainted) + sink(arg: url9) // $ tainted=210 + + var url10 = URL(string: clean)! + url10.append(queryItems: [source() as! URLQueryItem]) + sink(arg: url10) // $ MISSING: tainted=210 + + sink(data: try! urlTainted.bookmarkData()) // $ tainted=210 + sink(data: try! URL.bookmarkData(withContentsOf: urlTainted)) // $ tainted=210 + sink(any: URL.resourceValues(forKeys: [], fromBookmarkData: dataTainted)!) // $ tainted=210 + + sink(arg: URL.homeDirectory) // (static var, not tainted) + sink(arg: URL.homeDirectory(forUser: clean)!) + sink(arg: URL.homeDirectory(forUser: tainted)!) // $ tainted=210 } func taintThroughUrlRequest() { @@ -161,21 +431,21 @@ func taintThroughUrlRequest() { let tainted = source() as! URLRequest sink(any: clean) - sink(any: tainted) // $tainted=161 + sink(any: tainted) // $ tainted=431 sink(any: clean.cachePolicy) sink(any: tainted.cachePolicy) sink(any: clean.httpMethod) sink(any: tainted.httpMethod) sink(any: clean.url) - sink(any: tainted.url) // $tainted=161 + sink(any: tainted.url) // $ tainted=431 sink(any: clean.httpBody) - sink(any: tainted.httpBody) // $tainted=161 - sink(any: clean.httpBodyStream) - sink(any: tainted.httpBodyStream) // $tainted=161 + sink(any: tainted.httpBody) // $ tainted=431 + sink(any: clean.httpBodyStream!) + sink(any: tainted.httpBodyStream!) // $ tainted=431 sink(any: clean.mainDocument) - sink(any: tainted.mainDocument) // $tainted=161 - sink(any: clean.allHTTPHeaderFields) - sink(any: tainted.allHTTPHeaderFields) // $tainted=161 + sink(any: tainted.mainDocument) // $ tainted=431 + sink(any: clean.allHTTPHeaderFields!) + sink(any: tainted.allHTTPHeaderFields!) // $ tainted=431 sink(any: clean.timeoutInterval) sink(any: tainted.timeoutInterval) sink(any: clean.httpShouldHandleCookies) @@ -204,4 +474,41 @@ func taintThroughUrlRequest() { sink(any: tainted.assumesHTTP3Capable) sink(any: clean.requiresDNSSECValidation) sink(any: tainted.requiresDNSSECValidation) -} \ No newline at end of file +} + +func taintThroughUrlResource() { + let clean = URLResource(name: "") + let tainted = source() as! URLResource + + sink(string: clean.name) + sink(string: tainted.name) // $ tainted=481 + sink(string: clean.subdirectory!) + sink(string: tainted.subdirectory!) // $ tainted=481 +} + +func taintUrlAsync() async throws { + let tainted = source() as! String + let urlTainted = URL(string: tainted)! + + sink(any: urlTainted.lines) // $ tainted=490 + + for try await line in urlTainted.lines { + sink(string: line) // $ MISSING: tainted=490 + } +} + +func closureReturnValue() { + let url = URL(string: "http://example.com/")! + + let r1 = url.withUnsafeFileSystemRepresentation({ + ptr in + return "abc" + }) + sink(string: r1) + + let r2 = url.withUnsafeFileSystemRepresentation({ + ptr in + return source() as! String + }) + sink(string: r2) // $ tainted=511 +} diff --git a/swift/ql/test/library-tests/regex/parse.expected b/swift/ql/test/library-tests/regex/parse.expected index 34c746bf94d..c38c5aa6331 100644 --- a/swift/ql/test/library-tests/regex/parse.expected +++ b/swift/ql/test/library-tests/regex/parse.expected @@ -1618,6 +1618,14 @@ redos_variants.swift: # 142| [RegExpConstant, RegExpNormalChar] ! +# 146| [RegExpGroup] (?s) +#-----| 0 -> [RegExpConstant, RegExpNormalChar] s + +# 146| [RegExpSequence] (?s)(.|\n)*! +#-----| 0 -> [RegExpGroup] (?s) +#-----| 1 -> [RegExpStar] (.|\n)* +#-----| 2 -> [RegExpConstant, RegExpNormalChar] ! + # 146| [RegExpConstant, RegExpNormalChar] s # 146| [RegExpGroup] (.|\n) @@ -6319,167 +6327,282 @@ redos_variants.swift: # 579| [RegExpConstant, RegExpNormalChar] c regex.swift: -# 103| [RegExpDot] . +# 111| [RegExpDot] . -# 103| [RegExpStar] .* +# 111| [RegExpStar] .* #-----| 0 -> [RegExpDot] . -# 125| [RegExpDot] . +# 133| [RegExpDot] . -# 125| [RegExpStar] .* +# 133| [RegExpStar] .* #-----| 0 -> [RegExpDot] . -# 142| [RegExpDot] . +# 150| [RegExpDot] . -# 142| [RegExpStar] .* +# 150| [RegExpStar] .* #-----| 0 -> [RegExpDot] . -# 142| [RegExpDot] . +# 150| [RegExpDot] . -# 142| [RegExpPlus] .+ +# 150| [RegExpPlus] .+ #-----| 0 -> [RegExpDot] . -# 149| [RegExpGroup] ([\w.]+) +# 157| [RegExpGroup] ([\w.]+) #-----| 0 -> [RegExpPlus] [\w.]+ -# 149| [RegExpStar] ([\w.]+)* +# 157| [RegExpStar] ([\w.]+)* #-----| 0 -> [RegExpGroup] ([\w.]+) -# 149| [RegExpCharacterClass] [\w.] +# 157| [RegExpCharacterClass] [\w.] #-----| 0 -> [RegExpCharacterClassEscape] \w #-----| 1 -> [RegExpConstant, RegExpNormalChar] . -# 149| [RegExpPlus] [\w.]+ +# 157| [RegExpPlus] [\w.]+ #-----| 0 -> [RegExpCharacterClass] [\w.] -# 149| [RegExpCharacterClassEscape] \w +# 157| [RegExpCharacterClassEscape] \w -# 149| [RegExpConstant, RegExpNormalChar] . +# 157| [RegExpConstant, RegExpNormalChar] . -# 156| [RegExpConstant, RegExpNormalChar] -# 156| +# 164| [RegExpConstant, RegExpNormalChar] +# 164| -# 157| [RegExpConstant, RegExpEscape] \n +# 165| [RegExpConstant, RegExpEscape] \n -# 158| [RegExpConstant, RegExpEscape] \n +# 166| [RegExpConstant, RegExpEscape] \n -# 168| [RegExpConstant, RegExpNormalChar] aa +# 176| [RegExpConstant, RegExpNormalChar] aa -# 168| [RegExpAlt] aa|bb +# 176| [RegExpAlt] aa|bb #-----| 0 -> [RegExpConstant, RegExpNormalChar] aa #-----| 1 -> [RegExpConstant, RegExpNormalChar] bb -# 168| [RegExpConstant, RegExpNormalChar] bb +# 176| [RegExpConstant, RegExpNormalChar] bb -# 172| [RegExpConstant, RegExpNormalChar] aa +# 180| [RegExpConstant, RegExpNormalChar] aa -# 172| [RegExpAlt] aa| -# 172| bb +# 180| [RegExpAlt] aa| +# 180| bb #-----| 0 -> [RegExpConstant, RegExpNormalChar] aa #-----| 1 -> [RegExpConstant, RegExpNormalChar] #-----| bb -# 172| [RegExpConstant, RegExpNormalChar] -# 172| bb +# 180| [RegExpConstant, RegExpNormalChar] +# 180| bb -# 180| [RegExpCharacterClass] [a-z] +# 188| [RegExpCharacterClass] [a-z] #-----| 0 -> [RegExpCharacterRange] a-z -# 180| [RegExpConstant, RegExpNormalChar] a - -# 180| [RegExpCharacterRange] a-z -#-----| 0 -> [RegExpConstant, RegExpNormalChar] a -#-----| 1 -> [RegExpConstant, RegExpNormalChar] z - -# 180| [RegExpConstant, RegExpNormalChar] z - -# 181| [RegExpCharacterClass] [a-zA-Z] -#-----| 0 -> [RegExpCharacterRange] a-z -#-----| 1 -> [RegExpCharacterRange] A-Z - -# 181| [RegExpConstant, RegExpNormalChar] a - -# 181| [RegExpCharacterRange] a-z -#-----| 0 -> [RegExpConstant, RegExpNormalChar] a -#-----| 1 -> [RegExpConstant, RegExpNormalChar] z - -# 181| [RegExpConstant, RegExpNormalChar] z - -# 181| [RegExpConstant, RegExpNormalChar] A - -# 181| [RegExpCharacterRange] A-Z -#-----| 0 -> [RegExpConstant, RegExpNormalChar] A -#-----| 1 -> [RegExpConstant, RegExpNormalChar] Z - -# 181| [RegExpConstant, RegExpNormalChar] Z - -# 184| [RegExpCharacterClass] [a-] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] a -#-----| 1 -> [RegExpConstant, RegExpNormalChar] - - -# 184| [RegExpConstant, RegExpNormalChar] a - -# 184| [RegExpConstant, RegExpNormalChar] - - -# 185| [RegExpCharacterClass] [-a] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] - -#-----| 1 -> [RegExpConstant, RegExpNormalChar] a - -# 185| [RegExpConstant, RegExpNormalChar] - - -# 185| [RegExpConstant, RegExpNormalChar] a - -# 186| [RegExpCharacterClass] [-] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] - - -# 186| [RegExpConstant, RegExpNormalChar] - - -# 187| [RegExpCharacterClass] [*] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] * - -# 187| [RegExpConstant, RegExpNormalChar] * - -# 188| [RegExpCharacterClass] [^a] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] a - # 188| [RegExpConstant, RegExpNormalChar] a -# 189| [RegExpCharacterClass] [a^] +# 188| [RegExpCharacterRange] a-z #-----| 0 -> [RegExpConstant, RegExpNormalChar] a -#-----| 1 -> [RegExpConstant, RegExpNormalChar] ^ +#-----| 1 -> [RegExpConstant, RegExpNormalChar] z + +# 188| [RegExpConstant, RegExpNormalChar] z + +# 189| [RegExpCharacterClass] [a-zA-Z] +#-----| 0 -> [RegExpCharacterRange] a-z +#-----| 1 -> [RegExpCharacterRange] A-Z # 189| [RegExpConstant, RegExpNormalChar] a -# 189| [RegExpConstant, RegExpNormalChar] ^ +# 189| [RegExpCharacterRange] a-z +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] z -# 190| [RegExpCharacterClass] [\\] -#-----| 0 -> [RegExpConstant, RegExpEscape] \\ +# 189| [RegExpConstant, RegExpNormalChar] z -# 190| [RegExpConstant, RegExpEscape] \\ +# 189| [RegExpConstant, RegExpNormalChar] A -# 191| [RegExpCharacterClass] [\\\]] -#-----| 0 -> [RegExpConstant, RegExpEscape] \\ -#-----| 1 -> [RegExpConstant, RegExpEscape] \] +# 189| [RegExpCharacterRange] A-Z +#-----| 0 -> [RegExpConstant, RegExpNormalChar] A +#-----| 1 -> [RegExpConstant, RegExpNormalChar] Z -# 191| [RegExpConstant, RegExpEscape] \\ +# 189| [RegExpConstant, RegExpNormalChar] Z -# 191| [RegExpConstant, RegExpEscape] \] +# 192| [RegExpCharacterClass] [a-] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] - -# 192| [RegExpCharacterClass] [:] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] : +# 192| [RegExpConstant, RegExpNormalChar] a -# 192| [RegExpConstant, RegExpNormalChar] : +# 192| [RegExpConstant, RegExpNormalChar] - -# 193| [RegExpNamedCharacterProperty] [:digit:] - -# 194| [RegExpNamedCharacterProperty] [:alnum:] - -# 197| [RegExpCharacterClass] []a] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] ] +# 193| [RegExpCharacterClass] [-a] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] - #-----| 1 -> [RegExpConstant, RegExpNormalChar] a -# 197| [RegExpConstant, RegExpNormalChar] ] +# 193| [RegExpConstant, RegExpNormalChar] - + +# 193| [RegExpConstant, RegExpNormalChar] a + +# 194| [RegExpCharacterClass] [-] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] - + +# 194| [RegExpConstant, RegExpNormalChar] - + +# 195| [RegExpCharacterClass] [*] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] * + +# 195| [RegExpConstant, RegExpNormalChar] * + +# 196| [RegExpCharacterClass] [^a] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a + +# 196| [RegExpConstant, RegExpNormalChar] a + +# 197| [RegExpCharacterClass] [a^] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] ^ # 197| [RegExpConstant, RegExpNormalChar] a -# 198| [RegExpNamedCharacterProperty] [:aaaaa:] +# 197| [RegExpConstant, RegExpNormalChar] ^ + +# 198| [RegExpCharacterClass] [\\] +#-----| 0 -> [RegExpConstant, RegExpEscape] \\ + +# 198| [RegExpConstant, RegExpEscape] \\ + +# 199| [RegExpCharacterClass] [\\\]] +#-----| 0 -> [RegExpConstant, RegExpEscape] \\ +#-----| 1 -> [RegExpConstant, RegExpEscape] \] + +# 199| [RegExpConstant, RegExpEscape] \\ + +# 199| [RegExpConstant, RegExpEscape] \] + +# 200| [RegExpCharacterClass] [:] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] : + +# 200| [RegExpConstant, RegExpNormalChar] : + +# 201| [RegExpNamedCharacterProperty] [:digit:] + +# 202| [RegExpNamedCharacterProperty] [:alnum:] + +# 205| [RegExpCharacterClass] []a] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] ] +#-----| 1 -> [RegExpConstant, RegExpNormalChar] a + +# 205| [RegExpConstant, RegExpNormalChar] ] + +# 205| [RegExpConstant, RegExpNormalChar] a + +# 206| [RegExpNamedCharacterProperty] [:aaaaa:] + +# 211| [RegExpGroup] (?i) +#-----| 0 -> [RegExpConstant, RegExpNormalChar] i + +# 211| [RegExpSequence] (?i)abc +#-----| 0 -> [RegExpGroup] (?i) +#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc + +# 211| [RegExpConstant, RegExpNormalChar] i + +# 211| [RegExpConstant, RegExpNormalChar] abc + +# 212| [RegExpGroup] (?s) +#-----| 0 -> [RegExpConstant, RegExpNormalChar] s + +# 212| [RegExpSequence] (?s)abc +#-----| 0 -> [RegExpGroup] (?s) +#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc + +# 212| [RegExpConstant, RegExpNormalChar] s + +# 212| [RegExpConstant, RegExpNormalChar] abc + +# 213| [RegExpGroup] (?is) +#-----| 0 -> [RegExpConstant, RegExpNormalChar] is + +# 213| [RegExpSequence] (?is)abc +#-----| 0 -> [RegExpGroup] (?is) +#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc + +# 213| [RegExpConstant, RegExpNormalChar] is + +# 213| [RegExpConstant, RegExpNormalChar] abc + +# 214| [RegExpGroup] (?i-s) +#-----| 0 -> [RegExpConstant, RegExpNormalChar] i-s + +# 214| [RegExpSequence] (?i-s)abc +#-----| 0 -> [RegExpGroup] (?i-s) +#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc + +# 214| [RegExpConstant, RegExpNormalChar] i-s + +# 214| [RegExpConstant, RegExpNormalChar] abc + +# 217| [RegExpConstant, RegExpNormalChar] abc + +# 217| [RegExpSequence] abc(?i)def +#-----| 0 -> [RegExpConstant, RegExpNormalChar] abc +#-----| 1 -> [RegExpGroup] (?i) +#-----| 2 -> [RegExpConstant, RegExpNormalChar] def + +# 217| [RegExpGroup] (?i) +#-----| 0 -> [RegExpConstant, RegExpNormalChar] i + +# 217| [RegExpConstant, RegExpNormalChar] i + +# 217| [RegExpConstant, RegExpNormalChar] def + +# 218| [RegExpConstant, RegExpNormalChar] abc + +# 218| [RegExpSequence] abc(?i:def)ghi +#-----| 0 -> [RegExpConstant, RegExpNormalChar] abc +#-----| 1 -> [RegExpGroup] (?i:def) +#-----| 2 -> [RegExpConstant, RegExpNormalChar] ghi + +# 218| [RegExpGroup] (?i:def) +#-----| 0 -> [RegExpConstant, RegExpNormalChar] i:def + +# 218| [RegExpConstant, RegExpNormalChar] i:def + +# 218| [RegExpConstant, RegExpNormalChar] ghi + +# 219| [RegExpGroup] (?i) +#-----| 0 -> [RegExpConstant, RegExpNormalChar] i + +# 219| [RegExpConstant, RegExpNormalChar] i + +# 219| [RegExpConstant, RegExpNormalChar] abc + +# 219| [RegExpConstant, RegExpNormalChar] -i + +# 219| [RegExpConstant, RegExpNormalChar] def + +# 222| [RegExpConstant, RegExpNormalChar] abc + +# 223| [RegExpConstant, RegExpNormalChar] abc + +# 224| [RegExpConstant, RegExpNormalChar] abc + +# 225| [RegExpConstant, RegExpNormalChar] abc + +# 226| [RegExpConstant, RegExpNormalChar] abc + +# 227| [RegExpConstant, RegExpNormalChar] abc + +# 230| [RegExpDot] . + +# 230| [RegExpStar] .* +#-----| 0 -> [RegExpDot] . + +# 231| [RegExpDot] . + +# 231| [RegExpStar] .* +#-----| 0 -> [RegExpDot] . + +# 232| [RegExpDot] . + +# 232| [RegExpStar] .* +#-----| 0 -> [RegExpDot] . + +# 235| [RegExpDot] . + +# 235| [RegExpStar] .* +#-----| 0 -> [RegExpDot] . diff --git a/swift/ql/test/library-tests/regex/redos_variants.swift b/swift/ql/test/library-tests/regex/redos_variants.swift index d09a512b042..1f00139ef77 100644 --- a/swift/ql/test/library-tests/regex/redos_variants.swift +++ b/swift/ql/test/library-tests/regex/redos_variants.swift @@ -143,7 +143,7 @@ func myRegexpVariantsTests(myUrl: URL) throws { // BAD // attack string: "\n".repeat(100) + "." - _ = try Regex(#"(?s)(.|\n)*!"#).firstMatch(in: tainted) // $ hasParseFailure MISSING: redos-vulnerable= + _ = try Regex(#"(?s)(.|\n)*!"#).firstMatch(in: tainted) // $ modes=DOTALL redos-vulnerable= // GOOD _ = try Regex(#"([\w.]+)*"#).firstMatch(in: tainted) diff --git a/swift/ql/test/library-tests/regex/regex.ql b/swift/ql/test/library-tests/regex/regex.ql index f3d0d67f120..1ed05cd3cf4 100644 --- a/swift/ql/test/library-tests/regex/regex.ql +++ b/swift/ql/test/library-tests/regex/regex.ql @@ -9,7 +9,9 @@ bindingset[s] string quote(string s) { if s.matches("% %") then result = "\"" + s + "\"" else result = s } module RegexTest implements TestSig { - string getARelevantTag() { result = ["regex", "input", "redos-vulnerable", "hasParseFailure"] } + string getARelevantTag() { + result = ["regex", "input", "redos-vulnerable", "hasParseFailure", "modes"] + } predicate hasActualResult(Location location, string element, string tag, string value) { exists(TreeView::RegExpTerm t | @@ -28,6 +30,15 @@ module RegexTest implements TestSig { tag = "hasParseFailure" and value = "" ) + or + exists(RegexEval eval, RegExp regex | + eval.getARegex() = regex and + location = eval.getLocation() and + element = eval.toString() and + tag = "modes" and + value = quote(regex.getFlags()) and + value != "" + ) } predicate hasOptionalResult(Location location, string element, string tag, string value) { diff --git a/swift/ql/test/library-tests/regex/regex.swift b/swift/ql/test/library-tests/regex/regex.swift index f48e8e0dc7d..a53e3d389fd 100644 --- a/swift/ql/test/library-tests/regex/regex.swift +++ b/swift/ql/test/library-tests/regex/regex.swift @@ -17,9 +17,13 @@ struct Regex : RegexComponent { init(_ pattern: String) throws where Output == AnyRegexOutput { } - func firstMatch(in string: String) throws -> Regex.Match? { return nil} - func prefixMatch(in string: String) throws -> Regex.Match? { return nil} - func wholeMatch(in string: String) throws -> Regex.Match? { return nil} + func ignoresCase(_ ignoresCase: Bool = true) -> Regex.RegexOutput> { return 0 as! Self } + func dotMatchesNewlines(_ dotMatchesNewlines: Bool = true) -> Regex.RegexOutput> { return 0 as! Self } + func anchorsMatchLineEndings(_ matchLineEndings: Bool = true) -> Regex.RegexOutput> { return 0 as! Self } + + func firstMatch(in string: String) throws -> Regex.Match? { return nil } + func prefixMatch(in string: String) throws -> Regex.Match? { return nil } + func wholeMatch(in string: String) throws -> Regex.Match? { return nil } typealias RegexOutput = Output } @@ -49,6 +53,7 @@ class NSString : NSObject { var rawValue: UInt static var regularExpression: NSString.CompareOptions { get { return CompareOptions(rawValue: 1) } } + static var caseInsensitive: NSString.CompareOptions { get { return CompareOptions(rawValue: 2) } } } convenience init(string aString: String) { self.init() } @@ -76,6 +81,9 @@ class NSTextCheckingResult : NSObject { class NSRegularExpression : NSObject { struct Options : OptionSet { var rawValue: UInt + + static var caseInsensitive: NSRegularExpression.Options { get { return Options(rawValue: 1) } } + static var dotMatchesLineSeparators: NSRegularExpression.Options { get { return Options(rawValue: 2) } } } struct MatchingOptions : OptionSet { @@ -196,4 +204,41 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws { // invalid (Swift doesn't like these regexs) _ = try Regex("[]a]").firstMatch(in: input) // this is valid in other regex implementations, and is likely harmless to accept _ = try Regex("[:aaaaa:]").firstMatch(in: input) // $ hasParseFailure + + // --- parse modes --- + + // parse modes encoded in the string + _ = try Regex("(?i)abc").firstMatch(in: input) // $ input=input modes=IGNORECASE regex=(?i)abc + _ = try Regex("(?s)abc").firstMatch(in: input) // $ input=input modes=DOTALL regex=(?s)abc + _ = try Regex("(?is)abc").firstMatch(in: input) // $ input=input modes="DOTALL | IGNORECASE" regex=(?is)abc + _ = try Regex("(?i-s)abc").firstMatch(in: input) // $ input=input regex=(?i-s)abc MISSING: modes=IGNORECASE SPURIOUS: modes="DOTALL | IGNORECASE" + + // these cases use parse modes on localized areas of the regex, which we don't currently support + _ = try Regex("abc(?i)def").firstMatch(in: input) // $ input=input modes=IGNORECASE regex=abc(?i)def + _ = try Regex("abc(?i:def)ghi").firstMatch(in: input) // $ input=input modes=IGNORECASE regex=abc(?i:def)ghi + _ = try Regex("(?i)abc(?-i)def").firstMatch(in: input) // $ input=input modes=IGNORECASE regex=(?i)abc(?-i)def SPURIOUS: hasParseFailure= + + // parse modes set through Regex + _ = try Regex("abc").dotMatchesNewlines(true).firstMatch(in: input) // $ input=input regex=abc modes=DOTALL + _ = try Regex("abc").dotMatchesNewlines(false).firstMatch(in: input) // $ input=input regex=abc + _ = try Regex("abc").dotMatchesNewlines(true).dotMatchesNewlines(false).firstMatch(in: input) // $ input=input regex=abc + _ = try Regex("abc").dotMatchesNewlines(false).dotMatchesNewlines(true).firstMatch(in: input) // $ input=input regex=abc modes=DOTALL + _ = try Regex("abc").dotMatchesNewlines().ignoresCase().firstMatch(in: input) // $ input=input regex=abc modes="DOTALL | IGNORECASE" + _ = try Regex("abc").anchorsMatchLineEndings().firstMatch(in: input) // $ input=input regex=abc modes=MULTILINE + + // parse modes set through NSRegularExpression + _ = try NSRegularExpression(pattern: ".*", options: .caseInsensitive).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input modes=IGNORECASE + _ = try NSRegularExpression(pattern: ".*", options: .dotMatchesLineSeparators).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input modes=DOTALL + _ = try NSRegularExpression(pattern: ".*", options: [.caseInsensitive, .dotMatchesLineSeparators]).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input modes="DOTALL | IGNORECASE" + + let myOptions1 : NSRegularExpression.Options = [.caseInsensitive, .dotMatchesLineSeparators] + _ = try NSRegularExpression(pattern: ".*", options: myOptions1).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input modes="DOTALL | IGNORECASE" + + // parse modes set through other methods + + let myOptions2 : NSString.CompareOptions = [.regularExpression, .caseInsensitive] + _ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ MISSING: regex=.* input=input modes=IGNORECASE + _ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ MISSING: regex=.* input=input modes=IGNORECASE + _ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input=inputNS modes=IGNORECASE + _ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input=inputNS modes=IGNORECASE } diff --git a/swift/ql/test/query-tests/Security/CWE-094/UnsafeJsEval.expected b/swift/ql/test/query-tests/Security/CWE-094/UnsafeJsEval.expected index 3e810179bcf..3020634d197 100644 --- a/swift/ql/test/query-tests/Security/CWE-094/UnsafeJsEval.expected +++ b/swift/ql/test/query-tests/Security/CWE-094/UnsafeJsEval.expected @@ -41,20 +41,20 @@ edges | UnsafeJsEval.swift:279:13:279:13 | string | UnsafeJsEval.swift:280:26:280:26 | string | | UnsafeJsEval.swift:285:13:285:13 | string | UnsafeJsEval.swift:286:3:286:10 | .utf16 | | UnsafeJsEval.swift:286:3:286:10 | .utf16 | UnsafeJsEval.swift:286:51:286:51 | stringBytes | -| UnsafeJsEval.swift:286:51:286:51 | stringBytes | UnsafeJsEval.swift:287:31:287:97 | call to JSStringCreateWithCharacters(_:_:) | -| UnsafeJsEval.swift:286:51:286:51 | stringBytes | UnsafeJsEval.swift:291:17:291:17 | jsstr | +| UnsafeJsEval.swift:286:51:286:51 | stringBytes | UnsafeJsEval.swift:287:60:287:60 | stringBytes | | UnsafeJsEval.swift:287:16:287:98 | call to JSStringRetain(_:) | UnsafeJsEval.swift:291:17:291:17 | jsstr | | UnsafeJsEval.swift:287:31:287:97 | call to JSStringCreateWithCharacters(_:_:) | UnsafeJsEval.swift:124:21:124:42 | string | | UnsafeJsEval.swift:287:31:287:97 | call to JSStringCreateWithCharacters(_:_:) | UnsafeJsEval.swift:287:16:287:98 | call to JSStringRetain(_:) | -| UnsafeJsEval.swift:287:31:287:97 | call to JSStringCreateWithCharacters(_:_:) | UnsafeJsEval.swift:291:17:291:17 | jsstr | +| UnsafeJsEval.swift:287:60:287:60 | stringBytes | UnsafeJsEval.swift:287:60:287:72 | .baseAddress | +| UnsafeJsEval.swift:287:60:287:72 | .baseAddress | UnsafeJsEval.swift:287:31:287:97 | call to JSStringCreateWithCharacters(_:_:) | | UnsafeJsEval.swift:299:13:299:13 | string | UnsafeJsEval.swift:300:3:300:10 | .utf8CString | | UnsafeJsEval.swift:300:3:300:10 | .utf8CString | UnsafeJsEval.swift:300:48:300:48 | stringBytes | -| UnsafeJsEval.swift:300:48:300:48 | stringBytes | UnsafeJsEval.swift:301:31:301:84 | call to JSStringCreateWithUTF8CString(_:) | -| UnsafeJsEval.swift:300:48:300:48 | stringBytes | UnsafeJsEval.swift:305:17:305:17 | jsstr | +| UnsafeJsEval.swift:300:48:300:48 | stringBytes | UnsafeJsEval.swift:301:61:301:61 | stringBytes | | UnsafeJsEval.swift:301:16:301:85 | call to JSStringRetain(_:) | UnsafeJsEval.swift:305:17:305:17 | jsstr | | UnsafeJsEval.swift:301:31:301:84 | call to JSStringCreateWithUTF8CString(_:) | UnsafeJsEval.swift:124:21:124:42 | string | | UnsafeJsEval.swift:301:31:301:84 | call to JSStringCreateWithUTF8CString(_:) | UnsafeJsEval.swift:301:16:301:85 | call to JSStringRetain(_:) | -| UnsafeJsEval.swift:301:31:301:84 | call to JSStringCreateWithUTF8CString(_:) | UnsafeJsEval.swift:305:17:305:17 | jsstr | +| UnsafeJsEval.swift:301:61:301:61 | stringBytes | UnsafeJsEval.swift:301:61:301:73 | .baseAddress | +| UnsafeJsEval.swift:301:61:301:73 | .baseAddress | UnsafeJsEval.swift:301:31:301:84 | call to JSStringCreateWithUTF8CString(_:) | | UnsafeJsEval.swift:318:24:318:87 | call to String.init(contentsOf:) | UnsafeJsEval.swift:320:44:320:74 | ... .+(_:_:) ... | nodes | UnsafeJsEval.swift:124:21:124:42 | string | semmle.label | string | @@ -85,12 +85,16 @@ nodes | UnsafeJsEval.swift:286:51:286:51 | stringBytes | semmle.label | stringBytes | | UnsafeJsEval.swift:287:16:287:98 | call to JSStringRetain(_:) | semmle.label | call to JSStringRetain(_:) | | UnsafeJsEval.swift:287:31:287:97 | call to JSStringCreateWithCharacters(_:_:) | semmle.label | call to JSStringCreateWithCharacters(_:_:) | +| UnsafeJsEval.swift:287:60:287:60 | stringBytes | semmle.label | stringBytes | +| UnsafeJsEval.swift:287:60:287:72 | .baseAddress | semmle.label | .baseAddress | | UnsafeJsEval.swift:291:17:291:17 | jsstr | semmle.label | jsstr | | UnsafeJsEval.swift:299:13:299:13 | string | semmle.label | string | | UnsafeJsEval.swift:300:3:300:10 | .utf8CString | semmle.label | .utf8CString | | UnsafeJsEval.swift:300:48:300:48 | stringBytes | semmle.label | stringBytes | | UnsafeJsEval.swift:301:16:301:85 | call to JSStringRetain(_:) | semmle.label | call to JSStringRetain(_:) | | UnsafeJsEval.swift:301:31:301:84 | call to JSStringCreateWithUTF8CString(_:) | semmle.label | call to JSStringCreateWithUTF8CString(_:) | +| UnsafeJsEval.swift:301:61:301:61 | stringBytes | semmle.label | stringBytes | +| UnsafeJsEval.swift:301:61:301:73 | .baseAddress | semmle.label | .baseAddress | | UnsafeJsEval.swift:305:17:305:17 | jsstr | semmle.label | jsstr | | UnsafeJsEval.swift:318:24:318:87 | call to String.init(contentsOf:) | semmle.label | call to String.init(contentsOf:) | | UnsafeJsEval.swift:320:44:320:74 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | diff --git a/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.expected b/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.expected new file mode 100644 index 00000000000..ef9d74258b7 --- /dev/null +++ b/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.expected @@ -0,0 +1,7 @@ +| ReDoS.swift:64:22:64:22 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| ReDoS.swift:65:22:65:22 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| ReDoS.swift:66:22:66:22 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| ReDoS.swift:69:18:69:18 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| ReDoS.swift:75:46:75:46 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| ReDoS.swift:77:57:77:57 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| ReDoS.swift:80:57:80:57 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | diff --git a/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.qlref b/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.qlref new file mode 100644 index 00000000000..a0bdcd8a864 --- /dev/null +++ b/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.qlref @@ -0,0 +1 @@ +queries/Security/CWE-1333/ReDoS.ql \ No newline at end of file diff --git a/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.swift b/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.swift new file mode 100644 index 00000000000..ef099f555df --- /dev/null +++ b/swift/ql/test/query-tests/Security/CWE-1333/ReDoS.swift @@ -0,0 +1,85 @@ + +// --- stubs --- + +struct URL { + init?(string: String) {} +} + +struct AnyRegexOutput { +} + +protocol RegexComponent { +} + +struct Regex : RegexComponent { + struct Match { + } + + init(_ pattern: String) throws where Output == AnyRegexOutput { } + + func firstMatch(in string: String) throws -> Regex.Match? { return nil} + + typealias RegexOutput = Output +} + +extension String { + init(contentsOf: URL) { + let data = "" + self.init(data) + } +} + +class NSObject { +} + +struct _NSRange { + init(location: Int, length: Int) { } +} + +typealias NSRange = _NSRange + +class NSRegularExpression : NSObject { + struct Options : OptionSet { + var rawValue: UInt + } + + struct MatchingOptions : OptionSet { + var rawValue: UInt + } + + init(pattern: String, options: NSRegularExpression.Options = []) throws { } + + func stringByReplacingMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange, withTemplate templ: String) -> String { return "" } +} + +// --- tests --- + +func myRegexpTests(myUrl: URL) throws { + let tainted = String(contentsOf: myUrl) // tainted + let untainted = "abcdef" + + // Regex + + _ = "((a*)*b)" // GOOD (never used) + _ = try Regex("((a*)*b)") // DUBIOUS (never used) [FLAGGED] + _ = try Regex("((a*)*b)").firstMatch(in: untainted) // DUBIOUS (never used on tainted input) [FLAGGED] + _ = try Regex("((a*)*b)").firstMatch(in: tainted) // BAD + _ = try Regex(".*").firstMatch(in: tainted) // GOOD (safe regex) + + let str = "((a*)*b)" // BAD + let regex = try Regex(str) + _ = try regex.firstMatch(in: tainted) + + // NSRegularExpression + + _ = try? NSRegularExpression(pattern: "((a*)*b)") // DUBIOUS (never used) [FLAGGED] + + let nsregex1 = try? NSRegularExpression(pattern: "((a*)*b)") // DUBIOUS (never used on tainted input) [FLAGGED] + _ = nsregex1?.stringByReplacingMatches(in: untainted, range: NSRange(location: 0, length: untainted.utf16.count), withTemplate: "") + + let nsregex2 = try? NSRegularExpression(pattern: "((a*)*b)") // BAD + _ = nsregex2?.stringByReplacingMatches(in: tainted, range: NSRange(location: 0, length: tainted.utf16.count), withTemplate: "") + + let nsregex3 = try? NSRegularExpression(pattern: ".*") // GOOD (safe regex) + _ = nsregex3?.stringByReplacingMatches(in: tainted, range: NSRange(location: 0, length: tainted.utf16.count), withTemplate: "") +} diff --git a/swift/ql/test/query-tests/Security/CWE-311/CleartextStorageDatabase.expected b/swift/ql/test/query-tests/Security/CWE-311/CleartextStorageDatabase.expected index 320f4873d08..50925efb7f1 100644 --- a/swift/ql/test/query-tests/Security/CWE-311/CleartextStorageDatabase.expected +++ b/swift/ql/test/query-tests/Security/CWE-311/CleartextStorageDatabase.expected @@ -1,4 +1,6 @@ edges +| file://:0:0:0:0 | self | file://:0:0:0:0 | .value | +| file://:0:0:0:0 | self | file://:0:0:0:0 | .value2 | | file://:0:0:0:0 | self [value] | file://:0:0:0:0 | .value | | file://:0:0:0:0 | value | file://:0:0:0:0 | [post] self [data] | | file://:0:0:0:0 | value | file://:0:0:0:0 | [post] self [notStoredBankAccountNumber] | @@ -31,10 +33,10 @@ edges | testCoreData2.swift:62:30:62:30 | bankAccountNo | testCoreData2.swift:62:4:62:4 | [post] obj [myBankAccountNumber] | | testCoreData2.swift:65:3:65:3 | [post] obj [myBankAccountNumber] | testCoreData2.swift:65:3:65:3 | [post] obj | | testCoreData2.swift:65:29:65:29 | bankAccountNo | testCoreData2.swift:65:3:65:3 | [post] obj [myBankAccountNumber] | -| testCoreData2.swift:70:9:70:9 | self | file://:0:0:0:0 | .value | +| testCoreData2.swift:70:9:70:9 | self | file://:0:0:0:0 | self | | testCoreData2.swift:70:9:70:9 | self [value] | file://:0:0:0:0 | self [value] | | testCoreData2.swift:70:9:70:9 | value | file://:0:0:0:0 | value | -| testCoreData2.swift:71:9:71:9 | self | file://:0:0:0:0 | .value2 | +| testCoreData2.swift:71:9:71:9 | self | file://:0:0:0:0 | self | | testCoreData2.swift:79:2:79:2 | [post] dbObj [myValue] | testCoreData2.swift:79:2:79:2 | [post] dbObj | | testCoreData2.swift:79:18:79:28 | .bankAccountNo | testCoreData2.swift:79:2:79:2 | [post] dbObj [myValue] | | testCoreData2.swift:80:2:80:2 | [post] dbObj [myValue] | testCoreData2.swift:80:2:80:2 | [post] dbObj | @@ -46,7 +48,6 @@ edges | testCoreData2.swift:82:18:82:32 | .value | testCoreData2.swift:82:2:82:2 | [post] dbObj [myValue] | | testCoreData2.swift:83:2:83:2 | [post] dbObj [myValue] | testCoreData2.swift:83:2:83:2 | [post] dbObj | | testCoreData2.swift:83:18:83:18 | bankAccountNo | testCoreData2.swift:71:9:71:9 | self | -| testCoreData2.swift:83:18:83:18 | bankAccountNo | testCoreData2.swift:83:18:83:32 | ...! | | testCoreData2.swift:83:18:83:18 | bankAccountNo | testCoreData2.swift:83:18:83:32 | .value2 | | testCoreData2.swift:83:18:83:32 | ...! | testCoreData2.swift:83:2:83:2 | [post] dbObj [myValue] | | testCoreData2.swift:83:18:83:32 | .value2 | testCoreData2.swift:83:18:83:32 | ...! | @@ -54,13 +55,11 @@ edges | testCoreData2.swift:84:18:84:18 | ...! | testCoreData2.swift:70:9:70:9 | self | | testCoreData2.swift:84:18:84:18 | ...! | testCoreData2.swift:84:18:84:33 | .value | | testCoreData2.swift:84:18:84:18 | bankAccountNo2 | testCoreData2.swift:84:18:84:18 | ...! | -| testCoreData2.swift:84:18:84:18 | bankAccountNo2 | testCoreData2.swift:84:18:84:33 | .value | | testCoreData2.swift:84:18:84:33 | .value | testCoreData2.swift:84:2:84:2 | [post] dbObj [myValue] | | testCoreData2.swift:85:2:85:2 | [post] dbObj [myValue] | testCoreData2.swift:85:2:85:2 | [post] dbObj | | testCoreData2.swift:85:18:85:18 | ...! | testCoreData2.swift:71:9:71:9 | self | | testCoreData2.swift:85:18:85:18 | ...! | testCoreData2.swift:85:18:85:33 | .value2 | | testCoreData2.swift:85:18:85:18 | bankAccountNo2 | testCoreData2.swift:85:18:85:18 | ...! | -| testCoreData2.swift:85:18:85:18 | bankAccountNo2 | testCoreData2.swift:85:18:85:33 | ...! | | testCoreData2.swift:85:18:85:33 | ...! | testCoreData2.swift:85:2:85:2 | [post] dbObj [myValue] | | testCoreData2.swift:85:18:85:33 | .value2 | testCoreData2.swift:85:18:85:33 | ...! | | testCoreData2.swift:87:2:87:10 | [post] ...? [myValue] | testCoreData2.swift:87:2:87:10 | [post] ...? | @@ -73,18 +72,15 @@ edges | testCoreData2.swift:89:22:89:22 | ...! | testCoreData2.swift:71:9:71:9 | self | | testCoreData2.swift:89:22:89:22 | ...! | testCoreData2.swift:89:22:89:37 | .value2 | | testCoreData2.swift:89:22:89:22 | bankAccountNo2 | testCoreData2.swift:89:22:89:22 | ...! | -| testCoreData2.swift:89:22:89:22 | bankAccountNo2 | testCoreData2.swift:89:22:89:37 | ...! | | testCoreData2.swift:89:22:89:37 | ...! | testCoreData2.swift:89:2:89:10 | [post] ...? [myValue] | | testCoreData2.swift:89:22:89:37 | .value2 | testCoreData2.swift:89:22:89:37 | ...! | | testCoreData2.swift:91:10:91:10 | bankAccountNo | testCoreData2.swift:92:10:92:10 | a | -| testCoreData2.swift:91:10:91:10 | bankAccountNo | testCoreData2.swift:93:18:93:18 | b | | testCoreData2.swift:92:10:92:10 | a | testCoreData2.swift:70:9:70:9 | self | | testCoreData2.swift:92:10:92:10 | a | testCoreData2.swift:92:10:92:12 | .value | | testCoreData2.swift:92:10:92:12 | .value | testCoreData2.swift:93:18:93:18 | b | | testCoreData2.swift:93:2:93:2 | [post] dbObj [myValue] | testCoreData2.swift:93:2:93:2 | [post] dbObj | | testCoreData2.swift:93:18:93:18 | b | testCoreData2.swift:93:2:93:2 | [post] dbObj [myValue] | | testCoreData2.swift:95:10:95:10 | bankAccountNo | testCoreData2.swift:97:12:97:12 | c | -| testCoreData2.swift:95:10:95:10 | bankAccountNo | testCoreData2.swift:97:12:97:14 | .value | | testCoreData2.swift:97:2:97:2 | [post] d [value] | testCoreData2.swift:98:18:98:18 | d [value] | | testCoreData2.swift:97:12:97:12 | c | testCoreData2.swift:70:9:70:9 | self | | testCoreData2.swift:97:12:97:12 | c | testCoreData2.swift:97:12:97:14 | .value | @@ -94,13 +90,13 @@ edges | testCoreData2.swift:98:18:98:18 | d [value] | testCoreData2.swift:70:9:70:9 | self [value] | | testCoreData2.swift:98:18:98:18 | d [value] | testCoreData2.swift:98:18:98:20 | .value | | testCoreData2.swift:98:18:98:20 | .value | testCoreData2.swift:98:2:98:2 | [post] dbObj [myValue] | -| testCoreData2.swift:101:10:101:10 | bankAccountNo | testCoreData2.swift:104:18:104:18 | e | -| testCoreData2.swift:101:10:101:10 | bankAccountNo | testCoreData2.swift:104:18:104:20 | .value | -| testCoreData2.swift:101:10:101:10 | bankAccountNo | testCoreData2.swift:105:18:105:18 | e | -| testCoreData2.swift:101:10:101:10 | bankAccountNo | testCoreData2.swift:105:18:105:20 | ...! | +| testCoreData2.swift:101:10:101:10 | bankAccountNo | testCoreData2.swift:103:13:103:13 | e | +| testCoreData2.swift:103:13:103:13 | e | testCoreData2.swift:70:9:70:9 | self | +| testCoreData2.swift:103:13:103:13 | e | testCoreData2.swift:104:18:104:18 | e | | testCoreData2.swift:104:2:104:2 | [post] dbObj [myValue] | testCoreData2.swift:104:2:104:2 | [post] dbObj | | testCoreData2.swift:104:18:104:18 | e | testCoreData2.swift:70:9:70:9 | self | | testCoreData2.swift:104:18:104:18 | e | testCoreData2.swift:104:18:104:20 | .value | +| testCoreData2.swift:104:18:104:18 | e | testCoreData2.swift:105:18:105:18 | e | | testCoreData2.swift:104:18:104:20 | .value | testCoreData2.swift:104:2:104:2 | [post] dbObj [myValue] | | testCoreData2.swift:105:2:105:2 | [post] dbObj [myValue] | testCoreData2.swift:105:2:105:2 | [post] dbObj | | testCoreData2.swift:105:18:105:18 | e | testCoreData2.swift:71:9:71:9 | self | @@ -194,6 +190,8 @@ nodes | file://:0:0:0:0 | [post] self [notStoredBankAccountNumber] | semmle.label | [post] self [notStoredBankAccountNumber] | | file://:0:0:0:0 | [post] self [password] | semmle.label | [post] self [password] | | file://:0:0:0:0 | [post] self [value] | semmle.label | [post] self [value] | +| file://:0:0:0:0 | self | semmle.label | self | +| file://:0:0:0:0 | self | semmle.label | self | | file://:0:0:0:0 | self [value] | semmle.label | self [value] | | file://:0:0:0:0 | value | semmle.label | value | | file://:0:0:0:0 | value | semmle.label | value | @@ -295,6 +293,7 @@ nodes | testCoreData2.swift:98:18:98:18 | d [value] | semmle.label | d [value] | | testCoreData2.swift:98:18:98:20 | .value | semmle.label | .value | | testCoreData2.swift:101:10:101:10 | bankAccountNo | semmle.label | bankAccountNo | +| testCoreData2.swift:103:13:103:13 | e | semmle.label | e | | testCoreData2.swift:104:2:104:2 | [post] dbObj | semmle.label | [post] dbObj | | testCoreData2.swift:104:2:104:2 | [post] dbObj [myValue] | semmle.label | [post] dbObj [myValue] | | testCoreData2.swift:104:18:104:18 | e | semmle.label | e | diff --git a/swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected b/swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected index 78ddf30855b..52e5ca35b47 100644 --- a/swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected +++ b/swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected @@ -5,14 +5,17 @@ edges | testSend.swift:33:14:33:32 | call to Data.init(_:) | testSend.swift:37:19:37:19 | data2 | | testSend.swift:33:19:33:19 | passwordPlain | testSend.swift:33:14:33:32 | call to Data.init(_:) | | testSend.swift:41:10:41:18 | data | testSend.swift:41:45:41:45 | data | -| testSend.swift:52:13:52:13 | password | testSend.swift:59:27:59:27 | str1 | -| testSend.swift:53:13:53:13 | password | testSend.swift:60:27:60:27 | str2 | -| testSend.swift:54:13:54:25 | call to pad(_:) | testSend.swift:61:27:61:27 | str3 | -| testSend.swift:54:17:54:17 | password | testSend.swift:41:10:41:18 | data | -| testSend.swift:54:17:54:17 | password | testSend.swift:54:13:54:25 | call to pad(_:) | -| testURL.swift:13:54:13:54 | passwd | testURL.swift:13:22:13:54 | ... .+(_:_:) ... | -| testURL.swift:15:55:15:55 | account_no | testURL.swift:15:22:15:55 | ... .+(_:_:) ... | -| testURL.swift:16:55:16:55 | credit_card_no | testURL.swift:16:22:16:55 | ... .+(_:_:) ... | +| testSend.swift:58:13:58:13 | password | testSend.swift:65:27:65:27 | str1 | +| testSend.swift:59:13:59:13 | password | testSend.swift:66:27:66:27 | str2 | +| testSend.swift:60:13:60:25 | call to pad(_:) | testSend.swift:67:27:67:27 | str3 | +| testSend.swift:60:17:60:17 | password | testSend.swift:41:10:41:18 | data | +| testSend.swift:60:17:60:17 | password | testSend.swift:60:13:60:25 | call to pad(_:) | +| testURL.swift:17:54:17:54 | passwd | testURL.swift:17:22:17:54 | ... .+(_:_:) ... | +| testURL.swift:19:55:19:55 | account_no | testURL.swift:19:22:19:55 | ... .+(_:_:) ... | +| testURL.swift:20:55:20:55 | credit_card_no | testURL.swift:20:22:20:55 | ... .+(_:_:) ... | +| testURL.swift:28:55:28:55 | e_mail | testURL.swift:28:22:28:55 | ... .+(_:_:) ... | +| testURL.swift:30:57:30:57 | a_homeaddr_z | testURL.swift:30:22:30:57 | ... .+(_:_:) ... | +| testURL.swift:32:55:32:55 | resident_ID | testURL.swift:32:22:32:55 | ... .+(_:_:) ... | nodes | testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | | testAlamofire.swift:150:45:150:45 | password | semmle.label | password | @@ -26,36 +29,55 @@ nodes | testSend.swift:37:19:37:19 | data2 | semmle.label | data2 | | testSend.swift:41:10:41:18 | data | semmle.label | data | | testSend.swift:41:45:41:45 | data | semmle.label | data | -| testSend.swift:52:13:52:13 | password | semmle.label | password | -| testSend.swift:53:13:53:13 | password | semmle.label | password | -| testSend.swift:54:13:54:25 | call to pad(_:) | semmle.label | call to pad(_:) | -| testSend.swift:54:17:54:17 | password | semmle.label | password | -| testSend.swift:59:27:59:27 | str1 | semmle.label | str1 | -| testSend.swift:60:27:60:27 | str2 | semmle.label | str2 | -| testSend.swift:61:27:61:27 | str3 | semmle.label | str3 | -| testSend.swift:65:27:65:27 | license_key | semmle.label | license_key | -| testSend.swift:66:27:66:30 | .mobileNumber | semmle.label | .mobileNumber | -| testURL.swift:13:22:13:54 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | -| testURL.swift:13:54:13:54 | passwd | semmle.label | passwd | -| testURL.swift:15:22:15:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | -| testURL.swift:15:55:15:55 | account_no | semmle.label | account_no | -| testURL.swift:16:22:16:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | -| testURL.swift:16:55:16:55 | credit_card_no | semmle.label | credit_card_no | -| testURL.swift:20:22:20:22 | passwd | semmle.label | passwd | +| testSend.swift:58:13:58:13 | password | semmle.label | password | +| testSend.swift:59:13:59:13 | password | semmle.label | password | +| testSend.swift:60:13:60:25 | call to pad(_:) | semmle.label | call to pad(_:) | +| testSend.swift:60:17:60:17 | password | semmle.label | password | +| testSend.swift:65:27:65:27 | str1 | semmle.label | str1 | +| testSend.swift:66:27:66:27 | str2 | semmle.label | str2 | +| testSend.swift:67:27:67:27 | str3 | semmle.label | str3 | +| testSend.swift:71:27:71:27 | license_key | semmle.label | license_key | +| testSend.swift:72:27:72:30 | .mobileNumber | semmle.label | .mobileNumber | +| testSend.swift:76:27:76:30 | .Telephone | semmle.label | .Telephone | +| testSend.swift:77:27:77:30 | .birth_day | semmle.label | .birth_day | +| testSend.swift:78:27:78:30 | .CarePlanID | semmle.label | .CarePlanID | +| testSend.swift:79:27:79:30 | .BankCardNo | semmle.label | .BankCardNo | +| testSend.swift:80:27:80:30 | .MyCreditRating | semmle.label | .MyCreditRating | +| testURL.swift:17:22:17:54 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | +| testURL.swift:17:54:17:54 | passwd | semmle.label | passwd | +| testURL.swift:19:22:19:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | +| testURL.swift:19:55:19:55 | account_no | semmle.label | account_no | +| testURL.swift:20:22:20:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | +| testURL.swift:20:55:20:55 | credit_card_no | semmle.label | credit_card_no | +| testURL.swift:24:22:24:22 | passwd | semmle.label | passwd | +| testURL.swift:28:22:28:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | +| testURL.swift:28:55:28:55 | e_mail | semmle.label | e_mail | +| testURL.swift:30:22:30:57 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | +| testURL.swift:30:57:30:57 | a_homeaddr_z | semmle.label | a_homeaddr_z | +| testURL.swift:32:22:32:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | +| testURL.swift:32:55:32:55 | resident_ID | semmle.label | resident_ID | subpaths -| testSend.swift:54:17:54:17 | password | testSend.swift:41:10:41:18 | data | testSend.swift:41:45:41:45 | data | testSend.swift:54:13:54:25 | call to pad(_:) | +| testSend.swift:60:17:60:17 | password | testSend.swift:41:10:41:18 | data | testSend.swift:41:45:41:45 | data | testSend.swift:60:13:60:25 | call to pad(_:) | #select | testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | testAlamofire.swift:150:45:150:45 | password | testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:150:45:150:45 | password | password | | testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | testAlamofire.swift:152:51:152:51 | password | testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:152:51:152:51 | password | password | | testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | testAlamofire.swift:154:38:154:38 | email | testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:154:38:154:38 | email | email | | testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | This operation transmits 'passwordPlain', which may contain unencrypted sensitive data from $@. | testSend.swift:29:19:29:19 | passwordPlain | passwordPlain | | testSend.swift:37:19:37:19 | data2 | testSend.swift:33:19:33:19 | passwordPlain | testSend.swift:37:19:37:19 | data2 | This operation transmits 'data2', which may contain unencrypted sensitive data from $@. | testSend.swift:33:19:33:19 | passwordPlain | passwordPlain | -| testSend.swift:59:27:59:27 | str1 | testSend.swift:52:13:52:13 | password | testSend.swift:59:27:59:27 | str1 | This operation transmits 'str1', which may contain unencrypted sensitive data from $@. | testSend.swift:52:13:52:13 | password | password | -| testSend.swift:60:27:60:27 | str2 | testSend.swift:53:13:53:13 | password | testSend.swift:60:27:60:27 | str2 | This operation transmits 'str2', which may contain unencrypted sensitive data from $@. | testSend.swift:53:13:53:13 | password | password | -| testSend.swift:61:27:61:27 | str3 | testSend.swift:54:17:54:17 | password | testSend.swift:61:27:61:27 | str3 | This operation transmits 'str3', which may contain unencrypted sensitive data from $@. | testSend.swift:54:17:54:17 | password | password | -| testSend.swift:65:27:65:27 | license_key | testSend.swift:65:27:65:27 | license_key | testSend.swift:65:27:65:27 | license_key | This operation transmits 'license_key', which may contain unencrypted sensitive data from $@. | testSend.swift:65:27:65:27 | license_key | license_key | -| testSend.swift:66:27:66:30 | .mobileNumber | testSend.swift:66:27:66:30 | .mobileNumber | testSend.swift:66:27:66:30 | .mobileNumber | This operation transmits '.mobileNumber', which may contain unencrypted sensitive data from $@. | testSend.swift:66:27:66:30 | .mobileNumber | .mobileNumber | -| testURL.swift:13:22:13:54 | ... .+(_:_:) ... | testURL.swift:13:54:13:54 | passwd | testURL.swift:13:22:13:54 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:13:54:13:54 | passwd | passwd | -| testURL.swift:15:22:15:55 | ... .+(_:_:) ... | testURL.swift:15:55:15:55 | account_no | testURL.swift:15:22:15:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:15:55:15:55 | account_no | account_no | -| testURL.swift:16:22:16:55 | ... .+(_:_:) ... | testURL.swift:16:55:16:55 | credit_card_no | testURL.swift:16:22:16:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:16:55:16:55 | credit_card_no | credit_card_no | -| testURL.swift:20:22:20:22 | passwd | testURL.swift:20:22:20:22 | passwd | testURL.swift:20:22:20:22 | passwd | This operation transmits 'passwd', which may contain unencrypted sensitive data from $@. | testURL.swift:20:22:20:22 | passwd | passwd | +| testSend.swift:65:27:65:27 | str1 | testSend.swift:58:13:58:13 | password | testSend.swift:65:27:65:27 | str1 | This operation transmits 'str1', which may contain unencrypted sensitive data from $@. | testSend.swift:58:13:58:13 | password | password | +| testSend.swift:66:27:66:27 | str2 | testSend.swift:59:13:59:13 | password | testSend.swift:66:27:66:27 | str2 | This operation transmits 'str2', which may contain unencrypted sensitive data from $@. | testSend.swift:59:13:59:13 | password | password | +| testSend.swift:67:27:67:27 | str3 | testSend.swift:60:17:60:17 | password | testSend.swift:67:27:67:27 | str3 | This operation transmits 'str3', which may contain unencrypted sensitive data from $@. | testSend.swift:60:17:60:17 | password | password | +| testSend.swift:71:27:71:27 | license_key | testSend.swift:71:27:71:27 | license_key | testSend.swift:71:27:71:27 | license_key | This operation transmits 'license_key', which may contain unencrypted sensitive data from $@. | testSend.swift:71:27:71:27 | license_key | license_key | +| testSend.swift:72:27:72:30 | .mobileNumber | testSend.swift:72:27:72:30 | .mobileNumber | testSend.swift:72:27:72:30 | .mobileNumber | This operation transmits '.mobileNumber', which may contain unencrypted sensitive data from $@. | testSend.swift:72:27:72:30 | .mobileNumber | .mobileNumber | +| testSend.swift:76:27:76:30 | .Telephone | testSend.swift:76:27:76:30 | .Telephone | testSend.swift:76:27:76:30 | .Telephone | This operation transmits '.Telephone', which may contain unencrypted sensitive data from $@. | testSend.swift:76:27:76:30 | .Telephone | .Telephone | +| testSend.swift:77:27:77:30 | .birth_day | testSend.swift:77:27:77:30 | .birth_day | testSend.swift:77:27:77:30 | .birth_day | This operation transmits '.birth_day', which may contain unencrypted sensitive data from $@. | testSend.swift:77:27:77:30 | .birth_day | .birth_day | +| testSend.swift:78:27:78:30 | .CarePlanID | testSend.swift:78:27:78:30 | .CarePlanID | testSend.swift:78:27:78:30 | .CarePlanID | This operation transmits '.CarePlanID', which may contain unencrypted sensitive data from $@. | testSend.swift:78:27:78:30 | .CarePlanID | .CarePlanID | +| testSend.swift:79:27:79:30 | .BankCardNo | testSend.swift:79:27:79:30 | .BankCardNo | testSend.swift:79:27:79:30 | .BankCardNo | This operation transmits '.BankCardNo', which may contain unencrypted sensitive data from $@. | testSend.swift:79:27:79:30 | .BankCardNo | .BankCardNo | +| testSend.swift:80:27:80:30 | .MyCreditRating | testSend.swift:80:27:80:30 | .MyCreditRating | testSend.swift:80:27:80:30 | .MyCreditRating | This operation transmits '.MyCreditRating', which may contain unencrypted sensitive data from $@. | testSend.swift:80:27:80:30 | .MyCreditRating | .MyCreditRating | +| testURL.swift:17:22:17:54 | ... .+(_:_:) ... | testURL.swift:17:54:17:54 | passwd | testURL.swift:17:22:17:54 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:17:54:17:54 | passwd | passwd | +| testURL.swift:19:22:19:55 | ... .+(_:_:) ... | testURL.swift:19:55:19:55 | account_no | testURL.swift:19:22:19:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:19:55:19:55 | account_no | account_no | +| testURL.swift:20:22:20:55 | ... .+(_:_:) ... | testURL.swift:20:55:20:55 | credit_card_no | testURL.swift:20:22:20:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:20:55:20:55 | credit_card_no | credit_card_no | +| testURL.swift:24:22:24:22 | passwd | testURL.swift:24:22:24:22 | passwd | testURL.swift:24:22:24:22 | passwd | This operation transmits 'passwd', which may contain unencrypted sensitive data from $@. | testURL.swift:24:22:24:22 | passwd | passwd | +| testURL.swift:28:22:28:55 | ... .+(_:_:) ... | testURL.swift:28:55:28:55 | e_mail | testURL.swift:28:22:28:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:28:55:28:55 | e_mail | e_mail | +| testURL.swift:30:22:30:57 | ... .+(_:_:) ... | testURL.swift:30:57:30:57 | a_homeaddr_z | testURL.swift:30:22:30:57 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:30:57:30:57 | a_homeaddr_z | a_homeaddr_z | +| testURL.swift:32:22:32:55 | ... .+(_:_:) ... | testURL.swift:32:55:32:55 | resident_ID | testURL.swift:32:22:32:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:32:55:32:55 | resident_ID | resident_ID | diff --git a/swift/ql/test/query-tests/Security/CWE-311/SensitiveExprs.expected b/swift/ql/test/query-tests/Security/CWE-311/SensitiveExprs.expected index 69de9137213..2b93ec420b8 100644 --- a/swift/ql/test/query-tests/Security/CWE-311/SensitiveExprs.expected +++ b/swift/ql/test/query-tests/Security/CWE-311/SensitiveExprs.expected @@ -119,16 +119,24 @@ | testRealm.swift:73:15:73:15 | myPassword | label:myPassword, type:credential | | testSend.swift:29:19:29:19 | passwordPlain | label:passwordPlain, type:credential | | testSend.swift:33:19:33:19 | passwordPlain | label:passwordPlain, type:credential | -| testSend.swift:52:13:52:13 | password | label:password, type:credential | -| testSend.swift:53:13:53:13 | password | label:password, type:credential | -| testSend.swift:54:17:54:17 | password | label:password, type:credential | -| testSend.swift:55:23:55:23 | password | label:password, type:credential | -| testSend.swift:56:27:56:27 | password | label:password, type:credential | -| testSend.swift:57:27:57:27 | password | label:password, type:credential | -| testSend.swift:65:27:65:27 | license_key | label:license_key, type:credential | -| testSend.swift:66:27:66:30 | .mobileNumber | label:mobileNumber, type:private information | -| testSend.swift:69:27:69:30 | .passwordFeatureEnabled | label:passwordFeatureEnabled, type:credential | -| testURL.swift:13:54:13:54 | passwd | label:passwd, type:credential | -| testURL.swift:15:55:15:55 | account_no | label:account_no, type:private information | -| testURL.swift:16:55:16:55 | credit_card_no | label:credit_card_no, type:private information | -| testURL.swift:20:22:20:22 | passwd | label:passwd, type:credential | +| testSend.swift:58:13:58:13 | password | label:password, type:credential | +| testSend.swift:59:13:59:13 | password | label:password, type:credential | +| testSend.swift:60:17:60:17 | password | label:password, type:credential | +| testSend.swift:61:23:61:23 | password | label:password, type:credential | +| testSend.swift:62:27:62:27 | password | label:password, type:credential | +| testSend.swift:63:27:63:27 | password | label:password, type:credential | +| testSend.swift:71:27:71:27 | license_key | label:license_key, type:credential | +| testSend.swift:72:27:72:30 | .mobileNumber | label:mobileNumber, type:private information | +| testSend.swift:75:27:75:30 | .passwordFeatureEnabled | label:passwordFeatureEnabled, type:credential | +| testSend.swift:76:27:76:30 | .Telephone | label:Telephone, type:private information | +| testSend.swift:77:27:77:30 | .birth_day | label:birth_day, type:private information | +| testSend.swift:78:27:78:30 | .CarePlanID | label:CarePlanID, type:private information | +| testSend.swift:79:27:79:30 | .BankCardNo | label:BankCardNo, type:private information | +| testSend.swift:80:27:80:30 | .MyCreditRating | label:MyCreditRating, type:private information | +| testURL.swift:17:54:17:54 | passwd | label:passwd, type:credential | +| testURL.swift:19:55:19:55 | account_no | label:account_no, type:private information | +| testURL.swift:20:55:20:55 | credit_card_no | label:credit_card_no, type:private information | +| testURL.swift:24:22:24:22 | passwd | label:passwd, type:credential | +| testURL.swift:28:55:28:55 | e_mail | label:e_mail, type:private information | +| testURL.swift:30:57:30:57 | a_homeaddr_z | label:a_homeaddr_z, type:private information | +| testURL.swift:32:55:32:55 | resident_ID | label:resident_ID, type:private information | diff --git a/swift/ql/test/query-tests/Security/CWE-311/testSend.swift b/swift/ql/test/query-tests/Security/CWE-311/testSend.swift index e7021f6e99a..cd94a60136b 100644 --- a/swift/ql/test/query-tests/Security/CWE-311/testSend.swift +++ b/swift/ql/test/query-tests/Security/CWE-311/testSend.swift @@ -46,6 +46,12 @@ struct MyStruct { var mobileUrl: String var mobilePlayer: String var passwordFeatureEnabled: Bool + var Telephone: String + var birth_day: String + var CarePlanID: String + var BankCardNo: String + var MyCreditRating: String + var OneTimeCode: String } func test2(password : String, license_key: String, ms: MyStruct, connection : NWConnection) { @@ -67,4 +73,10 @@ func test2(password : String, license_key: String, ms: MyStruct, connection : NW connection.send(content: ms.mobileUrl, completion: .idempotent) // GOOD (not sensitive) connection.send(content: ms.mobilePlayer, completion: .idempotent) // GOOD (not sensitive) connection.send(content: ms.passwordFeatureEnabled, completion: .idempotent) // GOOD (not sensitive) + connection.send(content: ms.Telephone, completion: .idempotent) // BAD + connection.send(content: ms.birth_day, completion: .idempotent) // BAD + connection.send(content: ms.CarePlanID, completion: .idempotent) // BAD + connection.send(content: ms.BankCardNo, completion: .idempotent) // BAD + connection.send(content: ms.MyCreditRating, completion: .idempotent) // BAD + connection.send(content: ms.OneTimeCode, completion: .idempotent) // BAD [NOT DETECTED] } diff --git a/swift/ql/test/query-tests/Security/CWE-311/testURL.swift b/swift/ql/test/query-tests/Security/CWE-311/testURL.swift index 7f9b64ff4f6..d5a5ca7c3ec 100644 --- a/swift/ql/test/query-tests/Security/CWE-311/testURL.swift +++ b/swift/ql/test/query-tests/Security/CWE-311/testURL.swift @@ -9,6 +9,10 @@ struct URL // --- tests --- +var myString = "" +func setMyString(str: String) { myString = str } +func getMyString() -> String { return myString } + func test1(passwd : String, encrypted_passwd : String, account_no : String, credit_card_no : String) { let a = URL(string: "http://example.com/login?p=" + passwd); // BAD let b = URL(string: "http://example.com/login?p=" + encrypted_passwd); // GOOD (not sensitive) @@ -19,4 +23,11 @@ func test1(passwd : String, encrypted_passwd : String, account_no : String, cred let e = URL(string: "abc", relativeTo: base); // GOOD (not sensitive) let f = URL(string: passwd, relativeTo: base); // BAD let g = URL(string: "abc", relativeTo: f); // BAD (reported on line above) + + let e_mail = myString + let h = URL(string: "http://example.com/login?em=" + e_mail); // BAD + var a_homeaddr_z = getMyString() + let i = URL(string: "http://example.com/login?home=" + a_homeaddr_z); // BAD + var resident_ID = getMyString() + let j = URL(string: "http://example.com/login?id=" + resident_ID); // BAD } diff --git a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift index 9151ab0f44f..8f8cd40c7cf 100644 --- a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift +++ b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift @@ -83,7 +83,7 @@ struct Logger { // --- tests --- -func test1(password: String, passwordHash : String) { +func test1(password: String, passwordHash : String, passphrase: String, pass_phrase: String) { print(password) // $ MISSING: hasCleartextLogging=87 print(password, separator: "") // $ MISSING: $ hasCleartextLogging=88 print("", separator: password) // $ hasCleartextLogging=89 @@ -132,6 +132,9 @@ func test1(password: String, passwordHash : String) { log.critical("\(passwordHash, privacy: .public)") // Safe log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=133 log.fault("\(passwordHash, privacy: .public)") // Safe + + NSLog(passphrase) // $ hasCleartextLogging=136 + NSLog(pass_phrase) // $ hasCleartextLogging=137 } class MyClass { @@ -145,14 +148,14 @@ func doSomething(password: String) { } func test3(x: String) { // alternative evidence of sensitivity... - NSLog(x) // $ MISSING: hasCleartextLogging=148 + NSLog(x) // $ MISSING: hasCleartextLogging=152 doSomething(password: x); - NSLog(x) // $ hasCleartextLogging=149 + NSLog(x) // $ hasCleartextLogging=152 let y = getPassword(); - NSLog(y) // $ hasCleartextLogging=152 + NSLog(y) // $ hasCleartextLogging=155 let z = MyClass() NSLog(z.harmless) // Safe - NSLog(z.password) // $ hasCleartextLogging=157 + NSLog(z.password) // $ hasCleartextLogging=160 } diff --git a/swift/ql/test/query-tests/Security/CWE-730/RegexInjection.expected b/swift/ql/test/query-tests/Security/CWE-730/RegexInjection.expected new file mode 100644 index 00000000000..974accb3001 --- /dev/null +++ b/swift/ql/test/query-tests/Security/CWE-730/RegexInjection.expected @@ -0,0 +1,68 @@ +edges +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:101:16:101:16 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:104:16:104:40 | ... .+(_:_:) ... | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:106:16:106:16 | "..." | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:109:16:109:39 | ... ? ... : ... | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:110:16:110:37 | ... ? ... : ... | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:113:24:113:24 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:114:45:114:45 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:120:19:120:19 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:131:39:131:39 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:144:16:144:16 | remoteInput | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:147:39:147:39 | regexStr | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:162:17:162:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:164:17:164:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:167:17:167:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:170:17:170:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:173:17:173:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:176:17:176:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:179:17:179:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:182:17:182:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:185:17:185:17 | taintedString | +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:190:21:190:21 | taintedString | +nodes +| tests.swift:95:22:95:46 | call to String.init(contentsOf:) | semmle.label | call to String.init(contentsOf:) | +| tests.swift:101:16:101:16 | taintedString | semmle.label | taintedString | +| tests.swift:104:16:104:40 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... | +| tests.swift:106:16:106:16 | "..." | semmle.label | "..." | +| tests.swift:109:16:109:39 | ... ? ... : ... | semmle.label | ... ? ... : ... | +| tests.swift:110:16:110:37 | ... ? ... : ... | semmle.label | ... ? ... : ... | +| tests.swift:113:24:113:24 | taintedString | semmle.label | taintedString | +| tests.swift:114:45:114:45 | taintedString | semmle.label | taintedString | +| tests.swift:120:19:120:19 | taintedString | semmle.label | taintedString | +| tests.swift:131:39:131:39 | taintedString | semmle.label | taintedString | +| tests.swift:144:16:144:16 | remoteInput | semmle.label | remoteInput | +| tests.swift:147:39:147:39 | regexStr | semmle.label | regexStr | +| tests.swift:162:17:162:17 | taintedString | semmle.label | taintedString | +| tests.swift:164:17:164:17 | taintedString | semmle.label | taintedString | +| tests.swift:167:17:167:17 | taintedString | semmle.label | taintedString | +| tests.swift:170:17:170:17 | taintedString | semmle.label | taintedString | +| tests.swift:173:17:173:17 | taintedString | semmle.label | taintedString | +| tests.swift:176:17:176:17 | taintedString | semmle.label | taintedString | +| tests.swift:179:17:179:17 | taintedString | semmle.label | taintedString | +| tests.swift:182:17:182:17 | taintedString | semmle.label | taintedString | +| tests.swift:185:17:185:17 | taintedString | semmle.label | taintedString | +| tests.swift:190:21:190:21 | taintedString | semmle.label | taintedString | +subpaths +#select +| tests.swift:101:16:101:16 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:101:16:101:16 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:104:16:104:40 | ... .+(_:_:) ... | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:104:16:104:40 | ... .+(_:_:) ... | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:106:16:106:16 | "..." | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:106:16:106:16 | "..." | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:109:16:109:39 | ... ? ... : ... | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:109:16:109:39 | ... ? ... : ... | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:110:16:110:37 | ... ? ... : ... | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:110:16:110:37 | ... ? ... : ... | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:113:24:113:24 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:113:24:113:24 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:114:45:114:45 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:114:45:114:45 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:120:19:120:19 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:120:19:120:19 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:131:39:131:39 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:131:39:131:39 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:144:16:144:16 | remoteInput | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:144:16:144:16 | remoteInput | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:147:39:147:39 | regexStr | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:147:39:147:39 | regexStr | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:162:17:162:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:162:17:162:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:164:17:164:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:164:17:164:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:167:17:167:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:167:17:167:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:170:17:170:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:170:17:170:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:173:17:173:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:173:17:173:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:176:17:176:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:176:17:176:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:179:17:179:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:179:17:179:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:182:17:182:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:182:17:182:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:185:17:185:17 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:185:17:185:17 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | +| tests.swift:190:21:190:21 | taintedString | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | tests.swift:190:21:190:21 | taintedString | This regular expression is constructed from a $@. | tests.swift:95:22:95:46 | call to String.init(contentsOf:) | user-provided value | diff --git a/swift/ql/test/query-tests/Security/CWE-730/RegexInjection.qlref b/swift/ql/test/query-tests/Security/CWE-730/RegexInjection.qlref new file mode 100644 index 00000000000..6171cd82074 --- /dev/null +++ b/swift/ql/test/query-tests/Security/CWE-730/RegexInjection.qlref @@ -0,0 +1 @@ +queries/Security/CWE-730/RegexInjection.ql diff --git a/swift/ql/test/query-tests/Security/CWE-730/tests.swift b/swift/ql/test/query-tests/Security/CWE-730/tests.swift new file mode 100644 index 00000000000..068472741fb --- /dev/null +++ b/swift/ql/test/query-tests/Security/CWE-730/tests.swift @@ -0,0 +1,193 @@ + +// --- stubs --- + +struct URL { + init?(string: String) {} +} + +struct AnyRegexOutput { +} + +protocol RegexComponent { + associatedtype RegexOutput +} + +struct Regex : RegexComponent { + struct Match { + } + + init(_ pattern: String) throws where Output == AnyRegexOutput { } + + func firstMatch(in string: String) throws -> Regex.Match? { return nil} + + typealias RegexOutput = Output +} + +extension RangeReplaceableCollection { + mutating func replace(_ regex: some RegexComponent, with replacement: Replacement, maxReplacements: Int = .max) where Replacement : Collection, Replacement.Element == Character { } +} + +extension StringProtocol { + func replacingOccurrences(of target: Target, with replacement: Replacement, options: String.CompareOptions = [], range searchRange: Range? = nil) -> String where Target : StringProtocol, Replacement : StringProtocol { return "" } +} + +extension String : RegexComponent { + typealias CompareOptions = NSString.CompareOptions + typealias Output = Substring + typealias RegexOutput = String.Output +} + +class NSObject { +} + +class NSString : NSObject { + struct CompareOptions : OptionSet { + var rawValue: UInt + + static var regularExpression: NSString.CompareOptions { get { return CompareOptions(rawValue: 1) } } + } + + convenience init(string aString: String) { self.init() } + + func replacingOccurrences(of target: String, with replacement: String, options: NSString.CompareOptions = [], range searchRange: NSRange) -> String { return "" } + + var length: Int { get { return 0 } } +} + +struct _NSRange { + init(location: Int, length: Int) { } +} + +typealias NSRange = _NSRange + +func NSMakeRange(_ loc: Int, _ len: Int) -> NSRange { return NSRange(location: loc, length: len) } + +class NSTextCheckingResult : NSObject { +} + +class NSRegularExpression : NSObject { + struct Options : OptionSet { + var rawValue: UInt + } + + struct MatchingOptions : OptionSet { + var rawValue: UInt + } + + init(pattern: String, options: NSRegularExpression.Options = []) throws { } + + func firstMatch(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> NSTextCheckingResult? { return nil } + + class func escapedPattern(for string: String) -> String { return "" } +} + +extension String { + init(contentsOf: URL) { + let data = "" + self.init(data) + } +} + +// --- tests --- + +func regexInjectionTests(cond: Bool, varString: String, myUrl: URL) throws { + let constString = ".*" + let taintedString = String(contentsOf: myUrl) // tainted + + // --- Regex --- + + _ = try Regex(constString).firstMatch(in: varString) + _ = try Regex(varString).firstMatch(in: varString) + _ = try Regex(taintedString).firstMatch(in: varString) // BAD + + _ = try Regex("(a|" + constString + ")").firstMatch(in: varString) + _ = try Regex("(a|" + taintedString + ")").firstMatch(in: varString) // BAD + _ = try Regex("(a|\(constString))").firstMatch(in: varString) + _ = try Regex("(a|\(taintedString))").firstMatch(in: varString) // BAD + + _ = try Regex(cond ? constString : constString).firstMatch(in: varString) + _ = try Regex(cond ? taintedString : constString).firstMatch(in: varString) // BAD + _ = try Regex(cond ? constString : taintedString).firstMatch(in: varString) // BAD + + _ = try (cond ? Regex(constString) : Regex(constString)).firstMatch(in: varString) + _ = try (cond ? Regex(taintedString) : Regex(constString)).firstMatch(in: varString) // BAD + _ = try (cond ? Regex(constString) : Regex(taintedString)).firstMatch(in: varString) // BAD + + // --- RangeReplaceableCollection --- + + var inputVar = varString + inputVar.replace(constString, with: "") + inputVar.replace(taintedString, with: "") // BAD + inputVar.replace(constString, with: taintedString) + + // --- StringProtocol --- + + _ = inputVar.replacingOccurrences(of: constString, with: "", options: .regularExpression) + _ = inputVar.replacingOccurrences(of: taintedString, with: "", options: .regularExpression) // BAD [NOT DETECTED] + + // --- NSRegularExpression --- + + _ = try NSRegularExpression(pattern: constString).firstMatch(in: varString, range: NSMakeRange(0, varString.utf16.count)) + _ = try NSRegularExpression(pattern: taintedString).firstMatch(in: varString, range: NSMakeRange(0, varString.utf16.count)) // BAD + + // --- NSString --- + + let nsString = NSString(string: varString) + _ = nsString.replacingOccurrences(of: constString, with: "", options: .regularExpression, range: NSMakeRange(0, nsString.length)) + _ = nsString.replacingOccurrences(of: taintedString, with: "", options: .regularExpression, range: NSMakeRange(0, nsString.length)) // BAD [NOT DETECTED] + + // --- from the qhelp --- + + let remoteInput = taintedString + let myRegex = ".*" + + _ = try Regex(remoteInput) // BAD + + let regexStr = "abc|\(remoteInput)" + _ = try NSRegularExpression(pattern: regexStr) // BAD + + _ = try Regex(myRegex) + + let escapedInput = NSRegularExpression.escapedPattern(for: remoteInput) + let regexStr4 = "abc|\(escapedInput)" + _ = try NSRegularExpression(pattern: regexStr4) + + // --- barriers --- + + let okInput = "abc" + let okInputs = ["abc", "def"] + let okSet: Set = ["abc", "def"] + + if (taintedString == okInput) { + _ = try Regex(taintedString).firstMatch(in: varString) // GOOD (effectively sanitized by the check) [FALSE POSITIVE] + } else { + _ = try Regex(taintedString).firstMatch(in: varString) // BAD + } + if (taintedString != okInput) { + _ = try Regex(taintedString).firstMatch(in: varString) // BAD + } + if (varString == okInput) { + _ = try Regex(taintedString).firstMatch(in: varString) // BAD + } + if (okInputs.contains(taintedString)) { + _ = try Regex(taintedString).firstMatch(in: varString) // GOOD (effectively sanitized by the check) [FALSE POSITIVE] + } + if (okInputs.firstIndex(of: taintedString) != nil) { + _ = try Regex(taintedString).firstMatch(in: varString) // GOOD (effectively sanitized by the check) [FALSE POSITIVE] + } + if let index = okInputs.firstIndex(of: taintedString) { + _ = try Regex(taintedString).firstMatch(in: varString) // GOOD (effectively sanitized by the check) [FALSE POSITIVE] + } + if let index = okInputs.index(of: taintedString) { + _ = try Regex(taintedString).firstMatch(in: varString) // GOOD (effectively sanitized by the check) [FALSE POSITIVE] + } + if (okSet.contains(taintedString)) { + _ = try Regex(taintedString).firstMatch(in: varString) // GOOD (effectively sanitized by the check) [FALSE POSITIVE] + } + + // --- multiple evaluations --- + + let re = try Regex(taintedString) // BAD + _ = try re.firstMatch(in: varString) // (we only want to flag one location total) + _ = try re.firstMatch(in: varString) +} diff --git a/swift/third_party/load.bzl b/swift/third_party/load.bzl index 9cefc77948d..3319886ff8d 100644 --- a/swift/third_party/load.bzl +++ b/swift/third_party/load.bzl @@ -4,11 +4,11 @@ load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") # TODO: remove `remove-result-of.patch` once we update to a Swift version containing # https://github.com/apple/swift/commit/2ed2cea2 # (probably when updating to 5.9) -_swift_prebuilt_version = "swift-5.8.1-RELEASE.212" +_swift_prebuilt_version = "swift-5.8.1-RELEASE.214" _swift_sha_map = { - "Linux-X64": "3e902cc9dbf02129f6bcac84902524a235df0e36d30f3ac54e642b64d3f95a2b", - "macOS-ARM64": "4d93f326bd8a41c89bcf593676407fab2dd84b665f6bfb7ab667a9673084bcda", - "macOS-X64": "988cd193a0590abd282d8d8f3ec2489583d3d2b34162a4e91208fb91e5fb5981", + "Linux-X64": "009594131d2f6327e0033c4b7b0479a5730427575eb59a81a439fe0e343aa777", + "macOS-ARM64": "304a918e3699d404f57e967eff79b982388d8c5330c2135272c9f3a825920a39", + "macOS-X64": "c763c493e5782869b54887dc72df2aad00d59af7272c6d96377f1debb98741f2", } _swift_arch_map = {