diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll b/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll index f797b3bcf35..1f322a02201 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll @@ -7,7 +7,7 @@ * on each other without introducing mutual recursion among those configurations. */ -private import DataFlowImplCommon +private import DataFlowImplCommon::Public private import DataFlowImplSpecific::Private import DataFlowImplSpecific::Public @@ -114,6 +114,32 @@ abstract class Configuration extends string { */ predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + /** + * Gets the exploration limit for `hasPartialFlow` measured in approximate + * number of interprocedural steps. + */ + int explorationLimit() { none() } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for dataflow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * This predicate is disabled (has no results) by default. Override + * `explorationLimit()` with a suitable number to enable this predicate. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node, this) and + dist = node.getSourceDistance() + } + /** DEPRECATED: use `hasFlow` instead. */ deprecated predicate hasFlowForward(Node source, Node sink) { hasFlow(source, sink) } @@ -121,6 +147,27 @@ abstract class Configuration extends string { deprecated predicate hasFlowBackward(Node source, Node sink) { hasFlow(source, sink) } } +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } +} + private predicate inBarrier(Node node, Configuration config) { config.isBarrierIn(node) and config.isSource(node) @@ -162,7 +209,7 @@ private predicate isAdditionalFlowStep( * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - localFlowStep(node1, node2) and + simpleLocalFlowStep(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -211,8 +258,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } pragma[noinline] -private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKind kind) { - viableImpl(call) = result.getCallable() and +private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { + viableCallable(call) = result.getCallable() and kind = result.getKind() } @@ -220,81 +267,86 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKind kind) { * Holds if `node` is reachable from a source in the given configuration * ignoring call contexts. */ -private predicate nodeCandFwd1(Node node, boolean stored, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) and stored = false + config.isSource(node) or exists(Node mid | - nodeCandFwd1(mid, stored, config) and + nodeCandFwd1(mid, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, stored, config) and - additionalLocalFlowStep(mid, node, config) and - stored = false + nodeCandFwd1(mid, config) and + additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, stored, config) and + nodeCandFwd1(mid, config) and jumpStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, stored, config) and - additionalJumpStep(mid, node, config) and - stored = false + nodeCandFwd1(mid, config) and + additionalJumpStep(mid, node, config) ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, _, config) and + nodeCandFwd1(mid, config) and store(mid, _, node) and - stored = true and not outBarrier(mid, config) ) or // read - exists(Node mid, Content f | - nodeCandFwd1(mid, true, config) and - read(mid, f, node) and - storeCandFwd1(f, unbind(config)) and - (stored = false or stored = true) and + exists(Content f | + nodeCandFwd1Read(f, node, config) and + storeCandFwd1(f, config) and not inBarrier(node, config) ) or // flow into a callable exists(Node arg | - nodeCandFwd1(arg, stored, config) and + nodeCandFwd1(arg, config) and viableParamArg(_, node, arg) ) or - // flow out of an argument - exists(PostUpdateNode mid, ParameterNode p | - nodeCandFwd1(mid, stored, config) and - parameterValueFlowsToUpdate(p, mid) and - viableParamArg(_, p, node.(PostUpdateNode).getPreUpdateNode()) - ) - or // flow out of a callable - exists(DataFlowCall call, ReturnNode ret, ReturnKind kind | - nodeCandFwd1(ret, stored, config) and - getReturnPosition(ret) = viableReturnPos(call, kind) and - node = getAnOutNode(call, kind) + exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | + nodeCandFwd1ReturnPosition(pos, config) and + pos = viableReturnPos(call, kind) and + node = kind.getAnOutNode(call) ) ) } +pragma[noinline] +private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { + exists(ReturnNodeExt ret | + nodeCandFwd1(ret, config) and + getReturnPosition(ret) = pos + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { + exists(Node mid | + nodeCandFwd1(mid, config) and + read(mid, f, node) + ) +} + /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ +pragma[noinline] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and useFieldFlow(config) and - nodeCandFwd1(mid, _, config) and + nodeCandFwd1(mid, config) and store(mid, f, node) ) } @@ -307,88 +359,93 @@ private boolean unbindBool(boolean b) { result != b.booleanNot() } * configuration ignoring call contexts. */ pragma[nomagic] -private predicate nodeCand1(Node node, boolean stored, Configuration config) { - nodeCandFwd1(node, false, config) and - config.isSink(node) and - stored = false +private predicate nodeCand1(Node node, Configuration config) { + nodeCandFwd1(node, config) and + config.isSink(node) or - nodeCandFwd1(node, unbindBool(stored), unbind(config)) and + nodeCandFwd1(node, unbind(config)) and ( exists(Node mid | localFlowStep(node, mid, config) and - nodeCand1(mid, stored, config) + nodeCand1(mid, config) ) or exists(Node mid | additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, stored, config) and - stored = false + nodeCand1(mid, config) ) or exists(Node mid | jumpStep(node, mid, config) and - nodeCand1(mid, stored, config) + nodeCand1(mid, config) ) or exists(Node mid | additionalJumpStep(node, mid, config) and - nodeCand1(mid, stored, config) and - stored = false + nodeCand1(mid, config) ) or // store - exists(Node mid, Content f | - store(node, f, mid) and - readCand1(f, unbind(config)) and - nodeCand1(mid, true, config) and - (stored = false or stored = true) + exists(Content f | + nodeCand1Store(f, node, config) and + readCand1(f, config) ) or // read exists(Node mid, Content f | read(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, _, config) and - stored = true + nodeCand1(mid, config) ) or // flow into a callable exists(Node param | viableParamArg(_, param, node) and - nodeCand1(param, stored, config) - ) - or - // flow out of an argument - exists(PostUpdateNode mid, ParameterNode p | - parameterValueFlowsToUpdate(p, node) and - viableParamArg(_, p, mid.getPreUpdateNode()) and - nodeCand1(mid, stored, config) + nodeCand1(param, config) ) or // flow out of a callable - exists(DataFlowCall call, ReturnKind kind, OutNode out | - nodeCand1(out, stored, config) and - getReturnPosition(node) = viableReturnPos(call, kind) and - out = getAnOutNode(call, kind) + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos ) ) } +pragma[noinline] +private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, ReturnKindExt kind, Node out | + nodeCand1(out, config) and + pos = viableReturnPos(call, kind) and + out = kind.getAnOutNode(call) + ) +} + /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ +pragma[noinline] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and - nodeCandFwd1(node, true, unbind(config)) and + nodeCandFwd1(node, unbind(config)) and read(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, _, config) + nodeCand1(mid, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1Store(Content f, Node node, Configuration config) { + exists(Node mid | + nodeCand1(mid, config) and + storeCandFwd1(f, unbind(config)) and + store(node, f, mid) ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, false, config) and + nodeCand1(node, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -407,7 +464,7 @@ private predicate simpleParameterFlow( ) { throughFlowNodeCand(node, config) and p = node and - t = getErasedRepr(node.getType()) and + t = getErasedNodeType(node) and exists(ReturnNode ret, ReturnKind kind | returnNodeGetEnclosingCallable(ret) = p.getEnclosingCallable() and kind = ret.getKind() and @@ -418,21 +475,21 @@ private predicate simpleParameterFlow( exists(Node mid | simpleParameterFlow(p, mid, t, config) and localFlowStep(mid, node, config) and - compatibleTypes(t, node.getType()) + compatibleTypes(t, getErasedNodeType(node)) ) or throughFlowNodeCand(node, unbind(config)) and exists(Node mid | simpleParameterFlow(p, mid, _, config) and additionalLocalFlowStep(mid, node, config) and - t = getErasedRepr(node.getType()) + t = getErasedNodeType(node) ) or throughFlowNodeCand(node, unbind(config)) and exists(Node mid | simpleParameterFlow(p, mid, t, config) and localStoreReadStep(mid, node) and - compatibleTypes(t, node.getType()) + compatibleTypes(t, getErasedNodeType(node)) ) or // value flow through a callable @@ -440,7 +497,7 @@ private predicate simpleParameterFlow( exists(Node arg | simpleParameterFlow(p, arg, t, config) and argumentValueFlowsThrough(arg, node, _) and - compatibleTypes(t, node.getType()) + compatibleTypes(t, getErasedNodeType(node)) ) or // flow through a callable @@ -453,13 +510,20 @@ private predicate simpleParameterFlow( pragma[noinline] private predicate simpleArgumentFlowsThrough0( + ParameterNode p, ReturnNode ret, ReturnKind kind, DataFlowType t, Configuration config +) { + simpleParameterFlow(p, ret, t, config) and + kind = ret.getKind() +} + +pragma[noinline] +private predicate simpleArgumentFlowsThrough1( DataFlowCall call, ArgumentNode arg, ReturnKind kind, DataFlowType t, Configuration config ) { - nodeCand1(arg, false, unbind(config)) and + nodeCand1(arg, unbind(config)) and not outBarrier(arg, config) and exists(ParameterNode p, ReturnNode ret | - simpleParameterFlow(p, ret, t, config) and - kind = ret.getKind() and + simpleArgumentFlowsThrough0(p, ret, kind, t, config) and viableParamArg(call, p, arg) ) } @@ -475,9 +539,9 @@ private predicate simpleArgumentFlowsThrough( ArgumentNode arg, Node out, DataFlowType t, Configuration config ) { exists(DataFlowCall call, ReturnKind kind | - nodeCand1(out, false, unbind(config)) and + nodeCand1(out, unbind(config)) and not inBarrier(out, config) and - simpleArgumentFlowsThrough0(call, arg, kind, t, config) and + simpleArgumentFlowsThrough1(call, arg, kind, t, config) and out = getAnOutNode(call, kind) ) } @@ -488,10 +552,10 @@ private predicate simpleArgumentFlowsThrough( */ pragma[noinline] private predicate localFlowStepOrFlowThroughCallable(Node node1, Node node2, Configuration config) { - nodeCand1(node1, _, config) and + nodeCand1(node1, config) and localFlowStep(node1, node2, config) or - nodeCand1(node1, _, config) and + nodeCand1(node1, config) and argumentValueFlowsThrough(node1, node2, _) } @@ -504,34 +568,30 @@ pragma[noinline] private predicate additionalLocalFlowStepOrFlowThroughCallable( Node node1, Node node2, Configuration config ) { - nodeCand1(node1, _, config) and + nodeCand1(node1, config) and additionalLocalFlowStep(node1, node2, config) or simpleArgumentFlowsThrough(node1, node2, _, config) } +pragma[noinline] +private ReturnPosition getReturnPosition1(Node node, Configuration config) { + result = getReturnPosition(node) and + nodeCand1(node, config) +} + /** * Holds if data can flow out of a callable from `node1` to `node2`, either * through a `ReturnNode` or through an argument that has been mutated, and * that this step is part of a path from a source to a sink. */ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) { - nodeCand1(node1, _, unbind(config)) and - nodeCand1(node2, _, config) and + nodeCand1(node2, config) and not outBarrier(node1, config) and not inBarrier(node2, config) and - ( - // flow out of an argument - exists(ParameterNode p | - parameterValueFlowsToUpdate(p, node1) and - viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode()) - ) - or - // flow out of a callable - exists(DataFlowCall call, ReturnKind kind | - getReturnPosition(node1) = viableReturnPos(call, kind) and - node2 = getAnOutNode(call, kind) - ) + exists(DataFlowCall call, ReturnKindExt kind | + getReturnPosition1(node1, unbind(config)) = viableReturnPos(call, kind) and + node2 = kind.getAnOutNode(call) ) } @@ -541,8 +601,8 @@ private predicate flowOutOfCallable(Node node1, Node node2, Configuration config */ private predicate flowIntoCallable(Node node1, Node node2, Configuration config) { viableParamArg(_, node2, node1) and - nodeCand1(node1, _, unbind(config)) and - nodeCand1(node2, _, config) and + nodeCand1(node1, unbind(config)) and + nodeCand1(node2, config) and not outBarrier(node1, config) and not inBarrier(node2, config) } @@ -608,12 +668,12 @@ private predicate flowIntoCallable( * configuration taking simple call contexts into consideration. */ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Configuration config) { - nodeCand1(node, false, config) and + nodeCand1(node, config) and config.isSource(node) and fromArg = false and stored = false or - nodeCand1(node, unbindBool(stored), unbind(config)) and + nodeCand1(node, unbind(config)) and ( exists(Node mid | nodeCandFwd2(mid, fromArg, stored, config) and @@ -648,10 +708,9 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi ) or // read - exists(Node mid, Content f | - nodeCandFwd2(mid, fromArg, true, config) and - read(mid, f, node) and - storeCandFwd2(f, unbind(config)) and + exists(Content f | + nodeCandFwd2Read(f, node, fromArg, config) and + storeCandFwd2(f, config) and (stored = false or stored = true) ) or @@ -674,16 +733,26 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd2`. */ +pragma[noinline] private predicate storeCandFwd2(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and - nodeCand1(node, true, unbind(config)) and + nodeCand1(node, unbind(config)) and nodeCandFwd2(mid, _, _, config) and store(mid, f, node) and readCand1(f, unbind(config)) ) } +pragma[nomagic] +private predicate nodeCandFwd2Read(Content f, Node node, boolean fromArg, Configuration config) { + exists(Node mid | + nodeCandFwd2(mid, fromArg, true, config) and + read(mid, f, node) and + readCand1(f, unbind(config)) + ) +} + /** * Holds if `node` is part of a path from a source to a sink in the given * configuration taking simple call contexts into consideration. @@ -721,10 +790,9 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu ) or // store - exists(Node mid, Content f | - store(node, f, mid) and - readCand2(f, unbind(config)) and - nodeCand2(mid, toReturn, true, config) and + exists(Content f | + nodeCand2Store(f, node, toReturn, config) and + readCand2(f, config) and (stored = false or stored = true) ) or @@ -755,6 +823,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu /** * Holds if `f` is the target of a read in the flow covered by `nodeCand2`. */ +pragma[noinline] private predicate readCand2(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and @@ -765,16 +834,21 @@ private predicate readCand2(Content f, Configuration config) { ) } -pragma[nomagic] -private predicate storeCand(Content f, Configuration conf) { - exists(Node n1, Node n2 | - store(n1, f, n2) and - nodeCand2(n1, _, _, conf) and - nodeCand2(n2, _, _, unbind(conf)) +pragma[noinline] +private predicate nodeCand2Store(Content f, Node node, boolean toReturn, Configuration config) { + exists(Node mid | + store(node, f, mid) and + nodeCand2(mid, toReturn, true, config) ) } -private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) } +pragma[nomagic] +private predicate storeCand(Content f, Configuration conf) { + exists(Node node | + nodeCand2Store(f, node, _, conf) and + nodeCand2(node, _, _, conf) + ) +} /** * Holds if `f` is the target of both a store and a read in the path graph @@ -783,7 +857,7 @@ private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) } pragma[noinline] private predicate readStoreCand(Content f, Configuration conf) { storeCand(f, conf) and - readCand(f, conf) + readCand2(f, conf) } private predicate nodeCand(Node node, Configuration config) { nodeCand2(node, _, _, config) } @@ -836,30 +910,35 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config + Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc ) { - localFlowEntry(node1, config) and + not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowEntry(node1, config) and + ( + localFlowStep(node1, node2, config) and preservesValue = true + or + additionalLocalFlowStep(node1, node2, config) and preservesValue = false + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + nodeCand(node2, unbind(config)) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false - ) and - node1 != node2 and - nodeCand(node2, unbind(config)) - or - exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config) and - localFlowStep(mid, node2, config) and - not mid instanceof CastNode and - nodeCand(node2, unbind(config)) - ) - or - exists(Node mid | - localFlowStepPlus(node1, mid, _, config) and - additionalLocalFlowStep(mid, node2, config) and - not mid instanceof CastNode and - preservesValue = false and - nodeCand(node2, unbind(config)) + exists(Node mid | + localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStep(mid, node2, config) and + not mid instanceof CastNode and + nodeCand(node2, unbind(config)) + ) + or + exists(Node mid | + localFlowStepPlus(node1, mid, _, config, cc) and + additionalLocalFlowStep(mid, node2, config) and + not mid instanceof CastNode and + preservesValue = false and + nodeCand(node2, unbind(config)) + ) ) } @@ -867,11 +946,11 @@ private predicate localFlowStepPlus( * Holds if `node1` can step to `node2` in one or more local steps and this * path can occur as a maximal subsequence of local steps in a dataflow path. */ -pragma[noinline] +pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config + Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config) and + localFlowStepPlus(node1, node2, preservesValue, config, callContext) and localFlowExit(node2, config) } @@ -917,7 +996,9 @@ private class CastingNode extends Node { */ private predicate flowCandFwd(Node node, boolean fromArg, AccessPathFront apf, Configuration config) { flowCandFwd0(node, fromArg, apf, config) and - if node instanceof CastingNode then compatibleTypes(node.getType(), apf.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getErasedNodeType(node), apf.getType()) + else any() } /** @@ -931,14 +1012,14 @@ private class AccessPathFrontNilNode extends Node { ( any(Configuration c).isSource(this) or - localFlowBigStep(_, this, false, _) + localFlowBigStep(_, this, false, _, _) or additionalJumpStep(_, this, _) ) } pragma[noinline] - private DataFlowType getErasedReprType() { result = getErasedRepr(this.getType()) } + private DataFlowType getErasedReprType() { result = getErasedNodeType(this) } /** Gets the `nil` path front for this node. */ AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedReprType()) } @@ -954,12 +1035,12 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, ( exists(Node mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStep(mid, node, true, config) + localFlowBigStep(mid, node, true, config, _) ) or exists(Node mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStep(mid, node, false, config) and + localFlowBigStep(mid, node, false, config, _) and apf = node.(AccessPathFrontNilNode).getApf() ) or @@ -1006,18 +1087,17 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, flowCandFwd(mid, fromArg, _, config) and store(mid, f, node) and nodeCand(node, unbind(config)) and + readStoreCand(f, unbind(config)) and apf.headUsesContent(f) ) or - exists(Node mid, Content f, AccessPathFront apf0 | - flowCandFwd(mid, fromArg, apf0, config) and - read(mid, f, node) and - nodeCand(node, config) and - apf0.headUsesContent(f) and - consCandFwd(f, apf, unbind(config)) + exists(Content f | + flowCandFwdRead(f, node, fromArg, config) and + consCandFwd(f, apf, config) ) } +pragma[noinline] private predicate consCandFwd(Content f, AccessPathFront apf, Configuration config) { exists(Node mid, Node n | flowCandFwd(mid, _, apf, config) and @@ -1028,6 +1108,16 @@ private predicate consCandFwd(Content f, AccessPathFront apf, Configuration conf ) } +pragma[nomagic] +private predicate flowCandFwdRead(Content f, Node node, boolean fromArg, Configuration config) { + exists(Node mid, AccessPathFront apf | + flowCandFwd(mid, fromArg, apf, config) and + read(mid, f, node) and + apf.headUsesContent(f) and + nodeCand(node, unbind(config)) + ) +} + /** * Holds if data can flow from a source to `node` with the given `apf` and * from there flow to a sink. @@ -1044,13 +1134,13 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co apf instanceof AccessPathFrontNil or exists(Node mid | - localFlowBigStep(node, mid, true, config) and + localFlowBigStep(node, mid, true, config, _) and flowCand(mid, toReturn, apf, config) ) or exists(Node mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStep(node, mid, false, config) and + localFlowBigStep(node, mid, false, config, _) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1098,12 +1188,12 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co exists(Content f, AccessPathFront apf0 | flowCandStore(node, f, toReturn, apf0, config) and apf0.headUsesContent(f) and - consCand(f, apf, unbind(config)) + consCand(f, apf, config) ) or exists(Content f, AccessPathFront apf0 | flowCandRead(node, f, toReturn, apf0, config) and - consCandFwd(f, apf0, unbind(config)) and + consCandFwd(f, apf0, config) and apf.headUsesContent(f) ) } @@ -1118,6 +1208,7 @@ private predicate flowCandRead( ) } +pragma[nomagic] private predicate flowCandStore( Node node, Content f, boolean toReturn, AccessPathFront apf0, Configuration config ) { @@ -1127,6 +1218,7 @@ private predicate flowCandStore( ) } +pragma[noinline] private predicate consCand(Content f, AccessPathFront apf, Configuration config) { consCandFwd(f, apf, config) and exists(Node n, AccessPathFront apf0 | @@ -1138,61 +1230,103 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(DataFlowType t) or - TCons(Content f, int len) { len in [1 .. 5] } + TConsNil(Content f, DataFlowType t) { consCand(f, TFrontNil(t), _) } or + TConsCons(Content f1, Content f2, int len) { consCand(f1, TFrontHead(f2), _) and len in [2 .. 5] } /** - * Conceptually a list of `Content`s followed by a `Type`, but only the first - * element of the list and its length are tracked. If data flows from a source to + * Conceptually a list of `Content`s followed by a `Type`, but only the first two + * elements of the list and its length are tracked. If data flows from a source to * a given node with a given `AccessPath`, this indicates the sequence of * dereference operations needed to get from the value in the node to the * tracked object. The final type indicates the type of the tracked object. */ -private class AccessPath extends TAccessPath { +abstract private class AccessPath extends TAccessPath { abstract string toString(); - Content getHead() { this = TCons(result, _) } + Content getHead() { + this = TConsNil(result, _) + or + this = TConsCons(result, _, _) + } int len() { this = TNil(_) and result = 0 or - this = TCons(_, result) + this = TConsNil(_, _) and result = 1 + or + this = TConsCons(_, _, result) } DataFlowType getType() { this = TNil(result) or - exists(Content head | this = TCons(head, _) | result = head.getContainerType()) + result = this.getHead().getContainerType() } abstract AccessPathFront getFront(); + + /** + * Holds if this access path has `head` at the front and may be followed by `tail`. + */ + abstract predicate pop(Content head, AccessPath tail); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(DataFlowType t | this = TNil(t) | result = ppReprType(t)) } + override string toString() { + exists(DataFlowType t | this = TNil(t) | result = concat(": " + ppReprType(t))) + } override AccessPathFront getFront() { exists(DataFlowType t | this = TNil(t) | result = TFrontNil(t)) } + + override predicate pop(Content head, AccessPath tail) { none() } } -private class AccessPathCons extends AccessPath, TCons { +abstract private class AccessPathCons extends AccessPath { } + +private class AccessPathConsNil extends AccessPathCons, TConsNil { override string toString() { - exists(Content f, int len | this = TCons(f, len) | - result = f.toString() + ", ... (" + len.toString() + ")" + exists(Content f, DataFlowType t | this = TConsNil(f, t) | + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + f.toString() + "]" + concat(" : " + ppReprType(t)) ) } override AccessPathFront getFront() { - exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TConsNil(f, _) | result = TFrontHead(f)) + } + + override predicate pop(Content head, AccessPath tail) { + exists(DataFlowType t | this = TConsNil(head, t) and tail = TNil(t)) + } +} + +private class AccessPathConsCons extends AccessPathCons, TConsCons { + override string toString() { + exists(Content f1, Content f2, int len | this = TConsCons(f1, f2, len) | + if len = 2 + then result = "[" + f1.toString() + ", " + f2.toString() + "]" + else result = "[" + f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")]" + ) + } + + override AccessPathFront getFront() { + exists(Content f | this = TConsCons(f, _, _) | result = TFrontHead(f)) + } + + override predicate pop(Content head, AccessPath tail) { + exists(int len, Content next | this = TConsCons(head, next, len) | + tail = TConsCons(next, _, len - 1) + or + len = 2 and + tail = TConsNil(next, _) + ) } } /** Holds if `ap0` corresponds to the cons of `f` and `ap`. */ -private predicate pop(AccessPath ap0, Content f, AccessPath ap) { - ap0.getFront().headUsesContent(f) and - consCand(f, ap.getFront(), _) and - ap0.len() = 1 + ap.len() -} +private predicate pop(AccessPath ap0, Content f, AccessPath ap) { ap0.pop(f, ap) } /** Holds if `ap0` corresponds to the cons of `f` and `ap` and `apf` is the front of `ap`. */ pragma[noinline] @@ -1212,7 +1346,7 @@ private class AccessPathNilNode extends Node { AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } pragma[noinline] - private DataFlowType getErasedReprType() { result = getErasedRepr(this.getType()) } + private DataFlowType getErasedReprType() { result = getErasedNodeType(this) } /** Gets the `nil` path for this node. */ AccessPathNil getAp() { result = TNil(this.getErasedReprType()) } @@ -1241,12 +1375,12 @@ private predicate flowFwd0( ( exists(Node mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStep(mid, node, true, config) + localFlowBigStep(mid, node, true, config, _) ) or exists(Node mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStep(mid, node, false, config) and + localFlowBigStep(mid, node, false, config, _) and ap = node.(AccessPathNilNode).getAp() and apf = ap.(AccessPathNil).getFront() ) @@ -1350,13 +1484,13 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio ap instanceof AccessPathNil or exists(Node mid | - localFlowBigStep(node, mid, true, config) and + localFlowBigStep(node, mid, true, config, _) and flow(mid, toReturn, ap, config) ) or exists(Node mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStep(node, mid, false, config) and + localFlowBigStep(node, mid, false, config, _) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -1497,11 +1631,14 @@ abstract class PathNode extends TPathNode { /** Gets a successor of this node, if any. */ abstract PathNode getASuccessor(); + /** Holds if this node is a source. */ + abstract predicate isSource(); + private string ppAp() { this instanceof PathNodeSink and result = "" or exists(string s | s = this.(PathNodeMid).getAp().toString() | - if s = "" then result = "" else result = " [" + s + "]" + if s = "" then result = "" else result = " " + s ) } @@ -1526,6 +1663,11 @@ private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1 module PathGraph { /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ query predicate edges(PathNode a, PathNode b) { pathSucc(a, b) } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + reach(n) and key = "semmle.label" and val = n.toString() + } } /** @@ -1534,11 +1676,8 @@ module PathGraph { */ private class PathNodeMid extends PathNode, TPathNodeMid { Node node; - CallContext cc; - AccessPath ap; - Configuration config; PathNodeMid() { this = TPathNodeMid(node, cc, ap, config) } @@ -1560,12 +1699,6 @@ private class PathNodeMid extends PathNode, TPathNodeMid { // an intermediate step to another intermediate node result = getSuccMid() or - // a final step to a sink via one or more local steps - localFlowStepPlus(node, result.getNode(), _, config) and - ap instanceof AccessPathNil and - result instanceof PathNodeSink and - result.getConfiguration() = unbind(this.getConfiguration()) - or // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges exists(PathNodeMid mid | mid = getSuccMid() and @@ -1574,23 +1707,12 @@ private class PathNodeMid extends PathNode, TPathNodeMid { result instanceof PathNodeSink and result.getConfiguration() = unbind(mid.getConfiguration()) ) - or - // a direct step from a source to a sink if a node is both - this instanceof PathNodeSource and - result instanceof PathNodeSink and - this.getNode() = result.getNode() and - result.getConfiguration() = unbind(this.getConfiguration()) } -} -/** - * A flow graph node corresponding to a source. - */ -private class PathNodeSource extends PathNodeMid { - PathNodeSource() { - getConfiguration().isSource(getNode()) and - getCallContext() instanceof CallContextAny and - getAp() instanceof AccessPathNil + override predicate isSource() { + config.isSource(node) and + cc instanceof CallContextAny and + ap instanceof AccessPathNil } } @@ -1601,7 +1723,6 @@ private class PathNodeSource extends PathNodeMid { */ private class PathNodeSink extends PathNode, TPathNodeSink { Node node; - Configuration config; PathNodeSink() { this = TPathNodeSink(node, config) } @@ -1611,6 +1732,8 @@ private class PathNodeSink extends PathNode, TPathNodeSink { override Configuration getConfiguration() { result = config } override PathNode getASuccessor() { none() } + + override predicate isSource() { config.isSource(node) } } /** @@ -1618,14 +1741,20 @@ private class PathNodeSink extends PathNode, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPath ap) { - localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration()) and - cc = mid.getCallContext() and - ap = mid.getAp() - or - localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration()) and - cc = mid.getCallContext() and - mid.getAp() instanceof AccessPathNil and - ap = node.(AccessPathNilNode).getAp() + exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | + midnode = mid.getNode() and + conf = mid.getConfiguration() and + cc = mid.getCallContext() and + localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and + ap0 = mid.getAp() + | + localFlowBigStep(midnode, node, true, conf, localCC) and + ap = ap0 + or + localFlowBigStep(midnode, node, false, conf, localCC) and + ap0 instanceof AccessPathNil and + ap = node.(AccessPathNilNode).getAp() + ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and cc instanceof CallContextAny and @@ -1640,8 +1769,6 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat or exists(Content f, AccessPath ap0 | contentStoreStep(mid, node, ap0, f, cc) and push(ap0, f, ap)) or - pathOutOfArgument(mid, node, cc) and ap = mid.getAp() - or pathIntoCallable(mid, node, _, cc, _) and ap = mid.getAp() or pathOutOfCallable(mid, node, cc) and ap = mid.getAp() @@ -1651,6 +1778,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat valuePathThroughCallable(mid, node, cc) and ap = mid.getAp() } +pragma[noinline] private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) { exists(Content f, AccessPath ap0 | ap0 = mid.getAp() and @@ -1674,9 +1802,9 @@ private predicate pathOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallCo not innercc instanceof CallContextCall } -pragma[noinline] +pragma[nomagic] private predicate pathOutOfCallable1( - PathNodeMid mid, DataFlowCall call, ReturnKind kind, CallContext cc + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc ) { exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | pathOutOfCallable0(mid, pos, innercc) and @@ -1693,29 +1821,9 @@ private predicate pathOutOfCallable1( * is a return from a callable and is recorded by `cc`, if needed. */ pragma[noinline] -private predicate pathOutOfCallable(PathNodeMid mid, OutNode out, CallContext cc) { - exists(ReturnKind kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) | - out = getAnOutNode(call, kind) - ) -} - -private predicate pathOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) { - exists( - PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i, - DataFlowCall call, ArgumentNode arg - | - mid.getNode() = n and - parameterValueFlowsToUpdate(p, n) and - innercc = mid.getCallContext() and - p.isParameterOf(callable, i) and - resolveReturn(innercc, callable, call) and - node.getPreUpdateNode() = arg and - arg.argumentOf(call, i) and - flow(node, unbind(mid.getConfiguration())) - | - if reducedViableImplInReturn(callable, call) - then cc = TReturn(callable, call) - else cc = TAnyCallContext() +private predicate pathOutOfCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) | + out = kind.getAnOutNode(call) ) } @@ -1768,7 +1876,7 @@ private predicate pathIntoCallable( pathIntoCallable0(mid, callable, i, outercc, call, emptyAp) and p.isParameterOf(callable, i) | - if reducedViableImplInCallContext(_, callable, call) + if recordDataFlowCallSite(call, callable) then innercc = TSpecificCall(call, i, emptyAp) else innercc = TSomeCall(p, emptyAp) ) @@ -1777,9 +1885,9 @@ private predicate pathIntoCallable( /** Holds if data may flow from `p` to a return of kind `kind`. */ pragma[nomagic] private predicate paramFlowsThrough( - ParameterNode p, ReturnKind kind, CallContextCall cc, AccessPathNil apnil, Configuration config + ParameterNode p, ReturnKindExt kind, CallContextCall cc, AccessPathNil apnil, Configuration config ) { - exists(PathNodeMid mid, ReturnNode ret | + exists(PathNodeMid mid, ReturnNodeExt ret | mid.getNode() = ret and kind = ret.getKind() and cc = mid.getCallContext() and @@ -1794,14 +1902,14 @@ private predicate paramFlowsThrough( ) } -pragma[noinline] +pragma[nomagic] private predicate pathThroughCallable0( - DataFlowCall call, PathNodeMid mid, ReturnKind kind, CallContext cc, AccessPathNil apnil + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPathNil apnil ) { exists(ParameterNode p, CallContext innercc | pathIntoCallable(mid, p, cc, innercc, call) and paramFlowsThrough(p, kind, innercc, apnil, unbind(mid.getConfiguration())) and - not parameterValueFlowsThrough(p, kind, innercc) and + not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc) and mid.getAp() instanceof AccessPathNil ) } @@ -1811,12 +1919,10 @@ private predicate pathThroughCallable0( * The context `cc` is restored to its value prior to entering the callable. */ pragma[noinline] -private predicate pathThroughCallable( - PathNodeMid mid, OutNode out, CallContext cc, AccessPathNil apnil -) { - exists(DataFlowCall call, ReturnKind kind | +private predicate pathThroughCallable(PathNodeMid mid, Node out, CallContext cc, AccessPathNil apnil) { + exists(DataFlowCall call, ReturnKindExt kind | pathThroughCallable0(call, mid, kind, cc, apnil) and - out = getAnOutNode(call, kind) + out = kind.getAnOutNode(call) ) } @@ -1844,12 +1950,12 @@ private predicate valuePathThroughCallable(PathNodeMid mid, OutNode out, CallCon * sinks. */ private predicate flowsTo( - PathNodeSource flowsource, PathNodeSink flowsink, Node source, Node sink, - Configuration configuration + PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration ) { + flowsource.isSource() and flowsource.getConfiguration() = configuration and flowsource.getNode() = source and - pathSuccPlus(flowsource, flowsink) and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and flowsink.getNode() = sink } @@ -1862,3 +1968,458 @@ private predicate flowsTo( predicate flowsTo(Node source, Node sink, Configuration configuration) { flowsTo(_, _, source, sink, configuration) } + +private module FlowExploration { + private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { + exists(Node node1, Node node2 | + jumpStep(node1, node2, config) + or + additionalJumpStep(node1, node2, config) + or + // flow into callable + viableParamArg(_, node2, node1) + or + // flow out of a callable + exists(DataFlowCall call, ReturnKindExt kind | + getReturnPosition(node1) = viableReturnPos(call, kind) and + node2 = kind.getAnOutNode(call) + ) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { + exists(Node n | config.isSource(n) and c = n.getEnclosingCallable()) + or + exists(DataFlowCallable mid | + interestingCallableSrc(mid, config) and callableStep(mid, c, config) + ) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c, Configuration config) { interestingCallableSrc(c, config) } or + TCallableSrc() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | + callableStep(c1, c2, config) and + ce1 = TCallable(c1, config) and + ce2 = TCallable(c2, unbind(config)) + ) + or + exists(Node n, Configuration config | + ce1 = TCallableSrc() and + config.isSource(n) and + ce2 = TCallable(n.getEnclosingCallable(), config) + ) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSrc(DataFlowCallable c, Configuration config) { + result = distSrcExt(TCallable(c, config)) - 1 + } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(Content f, int len) { len in [1 .. 5] } + + /** + * Conceptually a list of `Content`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(Content head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + + abstract AccessPathFront getFront(); + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + + override AccessPathFront getFront() { + exists(DataFlowType t | this = TPartialNil(t) | result = TFrontNil(t)) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(Content f, int len | this = TPartialCons(f, len) | + if len = 1 + then result = "[" + f.toString() + "]" + else result = "[" + f.toString() + ", ... (" + len.toString() + ")]" + ) + } + + override AccessPathFront getFront() { + exists(Content f | this = TPartialCons(f, _) | result = TFrontHead(f)) + } + } + + private newtype TPartialPathNode = + TPartialPathNodeMk(Node node, CallContext cc, PartialAccessPath ap, Configuration config) { + config.isSource(node) and + cc instanceof CallContextAny and + ap = TPartialNil(getErasedNodeType(node)) and + not fullBarrier(node, config) and + exists(config.explorationLimit()) + or + partialPathNodeMk0(node, cc, ap, config) and + distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + Node node, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(PartialPathNode mid | + partialPathStep(mid, node, cc, ap, config) and + not fullBarrier(node, config) and + if node instanceof CastingNode + then compatibleTypes(getErasedNodeType(node), ap.getType()) + else any() + ) + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = getNode().toString() + ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = getNode().toString() + ppAp() + ppCtx() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + abstract Node getNode(); + + /** Gets the associated configuration. */ + abstract Configuration getConfiguration(); + + /** Gets a successor of this node, if any. */ + abstract PartialPathNode getASuccessor(); + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { + result = distSrc(this.getNode().getEnclosingCallable(), this.getConfiguration()) + } + + private string ppAp() { + exists(string s | s = this.(PartialPathNodePriv).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodePriv).getCallContext().toString() + ">" + } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodePriv extends PartialPathNode { + Node node; + CallContext cc; + PartialAccessPath ap; + Configuration config; + + PartialPathNodePriv() { this = TPartialPathNodeMk(node, cc, ap, config) } + + override Node getNode() { result = node } + + CallContext getCallContext() { result = cc } + + PartialAccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + private PartialPathNodePriv getSuccMid() { + partialPathStep(this, result.getNode(), result.getCallContext(), result.getAp(), + result.getConfiguration()) + } + + override PartialPathNode getASuccessor() { result = getSuccMid() } + } + + private predicate partialPathStep( + PartialPathNodePriv mid, Node node, CallContext cc, PartialAccessPath ap, Configuration config + ) { + not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStep(mid.getNode(), node, config) and + cc = mid.getCallContext() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalLocalFlowStep(mid.getNode(), node, config) and + cc = mid.getCallContext() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(getErasedNodeType(node)) and + config = mid.getConfiguration() + ) + or + jumpStep(mid.getNode(), node, config) and + cc instanceof CallContextAny and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalJumpStep(mid.getNode(), node, config) and + cc instanceof CallContextAny and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(getErasedNodeType(node)) and + config = mid.getConfiguration() + or + partialPathStoreStep(mid, _, _, node, ap) and + cc = mid.getCallContext() and + config = mid.getConfiguration() + or + exists(PartialAccessPath ap0, Content f | + partialPathReadStep(mid, ap0, f, node, cc, config) and + apConsFwd(ap, f, ap0, config) + ) + or + partialPathIntoCallable(mid, node, _, cc, _, ap, config) + or + partialPathOutOfCallable(mid, node, cc, ap, config) + or + partialPathThroughCallable(mid, node, cc, ap, config) + or + valuePartialPathThroughCallable(mid, node, cc, ap, config) + } + + bindingset[result, i] + private int unbindInt(int i) { i <= result and i >= result } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodePriv mid, PartialAccessPath ap1, Content f, Node node, PartialAccessPath ap2 + ) { + ap1 = mid.getAp() and + store(mid.getNode(), f, node) and + ap2.getHead() = f and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), f.getType()) + } + + pragma[nomagic] + private predicate apConsFwd( + PartialAccessPath ap1, Content f, PartialAccessPath ap2, Configuration config + ) { + exists(PartialPathNodePriv mid | + partialPathStoreStep(mid, ap1, f, _, ap2) and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodePriv mid, PartialAccessPath ap, Content f, Node node, CallContext cc, + Configuration config + ) { + ap = mid.getAp() and + read(mid.getNode(), f, node) and + ap.getHead() = f and + config = mid.getConfiguration() and + cc = mid.getCallContext() + } + + private predicate partialPathOutOfCallable0( + PartialPathNodePriv mid, ReturnPosition pos, CallContext innercc, PartialAccessPath ap, + Configuration config + ) { + pos = getReturnPosition(mid.getNode()) and + innercc = mid.getCallContext() and + not innercc instanceof CallContextCall and + ap = mid.getAp() and + config = mid.getConfiguration() + } + + pragma[noinline] + private predicate partialPathOutOfCallable1( + PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, + PartialAccessPath ap, Configuration config + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, innercc, ap, config) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } + + private predicate partialPathOutOfCallable( + PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, cc, ap, config) + | + out = kind.getAnOutNode(call) + ) + } + + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodePriv mid, int i, CallContext cc, DataFlowCall call, boolean emptyAp, + PartialAccessPath ap, Configuration config + ) { + exists(ArgumentNode arg | + arg = mid.getNode() and + cc = mid.getCallContext() and + arg.argumentOf(call, i) and + ap = mid.getAp() and + config = mid.getConfiguration() + | + ap instanceof PartialAccessPathNil and emptyAp = true + or + ap instanceof PartialAccessPathCons and emptyAp = false + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodePriv mid, DataFlowCallable callable, int i, CallContext outercc, + DataFlowCall call, boolean emptyAp, PartialAccessPath ap, Configuration config + ) { + partialPathIntoArg(mid, i, outercc, call, emptyAp, ap, config) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodePriv mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + DataFlowCall call, PartialAccessPath ap, Configuration config + ) { + exists(int i, DataFlowCallable callable, boolean emptyAp | + partialPathIntoCallable0(mid, callable, i, outercc, call, emptyAp, ap, config) and + p.isParameterOf(callable, i) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call, i, emptyAp) + else innercc = TSomeCall(p, emptyAp) + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ParameterNode p, ReturnKindExt kind, CallContextCall cc, PartialAccessPathNil apnil, + Configuration config + ) { + exists(PartialPathNodePriv mid, ReturnNodeExt ret | + mid.getNode() = ret and + kind = ret.getKind() and + cc = mid.getCallContext() and + config = mid.getConfiguration() and + apnil = mid.getAp() + | + cc = TSomeCall(p, true) + or + exists(int i | cc = TSpecificCall(_, i, true) | + p.isParameterOf(returnNodeGetEnclosingCallable(ret), i) + ) + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodePriv mid, ReturnKindExt kind, CallContext cc, + PartialAccessPathNil apnil, Configuration config + ) { + exists(ParameterNode p, CallContext innercc, PartialAccessPathNil midapnil | + partialPathIntoCallable(mid, p, cc, innercc, call, midapnil, config) and + paramFlowsThroughInPartialPath(p, kind, innercc, apnil, config) and + not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPathNil apnil, + Configuration config + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, cc, apnil, config) and + out = kind.getAnOutNode(call) + ) + } + + pragma[noinline] + private predicate valuePartialPathThroughCallable0( + DataFlowCall call, PartialPathNodePriv mid, ReturnKind kind, CallContext cc, + PartialAccessPath ap, Configuration config + ) { + exists(ParameterNode p, CallContext innercc | + partialPathIntoCallable(mid, p, cc, innercc, call, ap, config) and + parameterValueFlowsThrough(p, kind, innercc) + ) + } + + private predicate valuePartialPathThroughCallable( + PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(DataFlowCall call, ReturnKind kind | + valuePartialPathThroughCallable0(call, mid, kind, cc, ap, config) and + out = getAnOutNode(call, kind) + ) + } +} + +import FlowExploration + +private predicate partialFlow( + PartialPathNode source, PartialPathNode node, Configuration configuration +) { + source.getConfiguration() = configuration and + configuration.isSource(source.getNode()) and + node = source.getASuccessor+() +} diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowImplCommon.qll b/ql/src/semmle/go/dataflow/internal/DataFlowImplCommon.qll index c89e861b21a..87b33a79b8b 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowImplCommon.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowImplCommon.qll @@ -3,467 +3,867 @@ import DataFlowImplSpecific::Public private ReturnNode getAReturnNodeOfKind(ReturnKind kind) { result.getKind() = kind } -cached +module Public { + import ImplCommon + import FlowThrough_v2 +} + private module ImplCommon { - /** - * Holds if `p` is the `i`th parameter of a viable dispatch target of `call`. - * The instance parameter is considered to have index `-1`. - */ - pragma[nomagic] - private predicate viableParam(DataFlowCall call, int i, ParameterNode p) { - p.isParameterOf(viableCallable(call), i) - } + import Cached - /** - * Holds if `arg` is a possible argument to `p` in `call`, taking virtual - * dispatch into account. - */ cached - predicate viableParamArg(DataFlowCall call, ParameterNode p, ArgumentNode arg) { - exists(int i | - viableParam(call, i, p) and - arg.argumentOf(call, i) - ) - } + private module Cached { + /** + * Holds if `p` is the `i`th parameter of a viable dispatch target of `call`. + * The instance parameter is considered to have index `-1`. + */ + pragma[nomagic] + private predicate viableParam(DataFlowCall call, int i, ParameterNode p) { + p.isParameterOf(viableCallable(call), i) + } - /** - * Holds if `p` can flow to `node` in the same callable using only - * value-preserving steps, not taking call contexts into account. - */ - private predicate parameterValueFlowNoCtx(ParameterNode p, Node node) { - p = node - or - exists(Node mid | - parameterValueFlowNoCtx(p, mid) and - localFlowStep(mid, node) and - compatibleTypes(p.getType(), node.getType()) - ) - or - // flow through a callable - exists(Node arg | - parameterValueFlowNoCtx(p, arg) and - argumentValueFlowsThroughNoCtx(arg, node) and - compatibleTypes(p.getType(), node.getType()) - ) - } - - /** - * Holds if `p` can flow to a return node of kind `kind` in the same - * callable using only value-preserving steps, not taking call contexts - * into account. - */ - private predicate parameterValueFlowsThroughNoCtx(ParameterNode p, ReturnKind kind) { - parameterValueFlowNoCtx(p, getAReturnNodeOfKind(kind)) - } - - pragma[nomagic] - private predicate argumentValueFlowsThroughNoCtx0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind - ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | - parameterValueFlowsThroughNoCtx(param, kind) - ) - } - - /** - * Holds if `arg` flows to `out` through a call using only value-preserving steps, - * not taking call contexts into account. - */ - private predicate argumentValueFlowsThroughNoCtx(ArgumentNode arg, OutNode out) { - exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThroughNoCtx0(call, arg, kind) | - out = getAnOutNode(call, kind) and - compatibleTypes(arg.getType(), out.getType()) - ) - } - - /** - * Holds if `arg` is the `i`th argument of `call` inside the callable - * `enclosing`, and `arg` may flow through `call`. - */ - pragma[noinline] - private predicate argumentOf( - DataFlowCall call, int i, ArgumentNode arg, DataFlowCallable enclosing - ) { - arg.argumentOf(call, i) and - argumentValueFlowsThroughNoCtx(arg, _) and - enclosing = arg.getEnclosingCallable() - } - - pragma[noinline] - private ParameterNode getAParameter(DataFlowCallable c) { result.getEnclosingCallable() = c } - - pragma[noinline] - private predicate viableParamArg0(int i, ArgumentNode arg, CallContext outercc, DataFlowCall call) { - exists(DataFlowCallable c | argumentOf(call, i, arg, c) | - outercc = TAnyCallContext() - or - outercc = TSomeCall(getAParameter(c), _) - or - exists(DataFlowCall other | outercc = TSpecificCall(other, _, _) | - reducedViableImplInCallContext(_, c, other) + /** + * Holds if `arg` is a possible argument to `p` in `call`, taking virtual + * dispatch into account. + */ + cached + predicate viableParamArg(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + exists(int i | + viableParam(call, i, p) and + arg.argumentOf(call, i) ) - ) - } + } - pragma[noinline] - private predicate viableParamArg1( - ParameterNode p, DataFlowCallable callable, int i, ArgumentNode arg, CallContext outercc, - DataFlowCall call - ) { - viableParamArg0(i, arg, outercc, call) and - callable = resolveCall(call, outercc) and - p.isParameterOf(callable, any(int j | j <= i and j >= i)) - } + /* + * The `FlowThrough_*` modules take a `step` relation as input and provide + * an `argumentValueFlowsThrough` relation as output. + * + * `FlowThrough_v1` includes just `simpleLocalFlowStep`, which is then used + * to detect getters and setters. + * `FlowThrough_v2` then includes a little bit of local field flow on top + * of `simpleLocalFlowStep`. + */ - /** - * Holds if `arg` is a possible argument to `p`, in the call `call`, and - * `arg` may flow through `call`. The possible contexts before and after - * entering the callable are `outercc` and `innercc`, respectively. - */ - private predicate viableParamArg( - DataFlowCall call, ParameterNode p, ArgumentNode arg, CallContext outercc, - CallContextCall innercc - ) { - exists(int i, DataFlowCallable callable | viableParamArg1(p, callable, i, arg, outercc, call) | - if reducedViableImplInCallContext(_, callable, call) - then innercc = TSpecificCall(call, i, true) - else innercc = TSomeCall(p, true) - ) - } + private module FlowThrough_v1 { + private predicate step = simpleLocalFlowStep/2; - private CallContextCall getAValidCallContextForParameter(ParameterNode p) { - result = TSomeCall(p, _) - or - exists(DataFlowCall call, int i, DataFlowCallable callable | - result = TSpecificCall(call, i, _) and - p.isParameterOf(callable, i) and - reducedViableImplInCallContext(_, callable, call) - ) - } - - /** - * Holds if `p` can flow to `node` in the same callable using only - * value-preserving steps, in call context `cc`. - */ - private predicate parameterValueFlow(ParameterNode p, Node node, CallContextCall cc) { - p = node and - parameterValueFlowsThroughNoCtx(p, _) and - cc = getAValidCallContextForParameter(p) - or - exists(Node mid | - parameterValueFlow(p, mid, cc) and - localFlowStep(mid, node) and - compatibleTypes(p.getType(), node.getType()) - ) - or - // flow through a callable - exists(Node arg | - parameterValueFlow(p, arg, cc) and - argumentValueFlowsThrough(arg, node, cc) and - compatibleTypes(p.getType(), node.getType()) - ) - } - - /** - * Holds if `p` can flow to a return node of kind `kind` in the same - * callable using only value-preserving steps, in call context `cc`. - */ - cached - predicate parameterValueFlowsThrough(ParameterNode p, ReturnKind kind, CallContextCall cc) { - parameterValueFlow(p, getAReturnNodeOfKind(kind), cc) - } - - pragma[nomagic] - private predicate argumentValueFlowsThrough0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, CallContext cc - ) { - exists(ParameterNode param, CallContext innercc | - viableParamArg(call, param, arg, cc, innercc) and - parameterValueFlowsThrough(param, kind, innercc) - ) - } - - /** - * Holds if `arg` flows to `out` through a call using only value-preserving steps, - * in call context cc. - */ - cached - predicate argumentValueFlowsThrough(ArgumentNode arg, OutNode out, CallContext cc) { - exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThrough0(call, arg, kind, cc) | - out = getAnOutNode(call, kind) and - compatibleTypes(arg.getType(), out.getType()) - ) - } - - /** - * Holds if `p` can flow to the pre-update node of `n` in the same callable - * using only value-preserving steps. - */ - cached - predicate parameterValueFlowsToUpdate(ParameterNode p, PostUpdateNode n) { - parameterValueFlowNoCtx(p, n.getPreUpdateNode()) - } - - /** - * Holds if data can flow from `node1` to `node2` in one local step or a step - * through a value-preserving method. - */ - private predicate localValueStep(Node node1, Node node2) { - localFlowStep(node1, node2) or - argumentValueFlowsThrough(node1, node2, _) - } - - /* - * Calculation of `predicate store(Node node1, Content f, Node node2)`: - * There are three cases: - * - The base case: A direct local assignment given by `storeStep`. - * - A call to a method or constructor with two arguments, `arg1` and `arg2`, - * such the call has the side-effect `arg2.f = arg1`. - * - A call to a method that returns an object in which an argument has been - * stored. - * `storeViaSideEffect` covers the first two cases, and `storeReturn` covers - * the third case. - */ - - /** - * Holds if data can flow from `node1` to `node2` via a direct assignment to - * `f` or via a call that acts as a setter. - */ - cached - predicate store(Node node1, Content f, Node node2) { - storeViaSideEffect(node1, f, node2) or - storeReturn(node1, f, node2) - } - - private predicate storeViaSideEffect(Node node1, Content f, PostUpdateNode node2) { - storeStep(node1, f, node2) and readStep(_, f, _) - or - exists(DataFlowCall call, int i1, int i2 | - setterCall(call, i1, i2, f) and - node1.(ArgumentNode).argumentOf(call, i1) and - node2.getPreUpdateNode().(ArgumentNode).argumentOf(call, i2) and - compatibleTypes(node1.getTypeBound(), f.getType()) and - compatibleTypes(node2.getTypeBound(), f.getContainerType()) - ) - } - - pragma[nomagic] - private predicate setterInParam(ParameterNode p1, Content f, ParameterNode p2) { - exists(Node n1, PostUpdateNode n2 | - parameterValueFlowNoCtx(p1, n1) and - storeViaSideEffect(n1, f, n2) and - parameterValueFlowNoCtx(p2, n2.getPreUpdateNode()) and - p1 != p2 - ) - } - - pragma[nomagic] - private predicate setterCall(DataFlowCall call, int i1, int i2, Content f) { - exists(DataFlowCallable callable, ParameterNode p1, ParameterNode p2 | - setterInParam(p1, f, p2) and - callable = viableCallable(call) and - p1.isParameterOf(callable, i1) and - p2.isParameterOf(callable, i2) - ) - } - - pragma[noinline] - private predicate storeReturn0(DataFlowCall call, ReturnKind kind, ArgumentNode arg, Content f) { - exists(ParameterNode p | - viableParamArg(call, p, arg) and - setterReturn(p, f, kind) - ) - } - - private predicate storeReturn(Node node1, Content f, Node node2) { - exists(DataFlowCall call, ReturnKind kind | - storeReturn0(call, kind, node1, f) and - node2 = getAnOutNode(call, kind) and - compatibleTypes(node1.getTypeBound(), f.getType()) and - compatibleTypes(node2.getTypeBound(), f.getContainerType()) - ) - } - - private predicate setterReturn(ParameterNode p, Content f, ReturnKind kind) { - exists(Node n1, Node n2 | - parameterValueFlowNoCtx(p, n1) and - store(n1, f, n2) and - localValueStep*(n2, getAReturnNodeOfKind(kind)) - ) - } - - pragma[noinline] - private predicate read0(DataFlowCall call, ReturnKind kind, ArgumentNode arg, Content f) { - exists(ParameterNode p | - viableParamArg(call, p, arg) and - getter(p, f, kind) - ) - } - - /** - * Holds if data can flow from `node1` to `node2` via a direct read of `f` or - * via a getter. - */ - cached - predicate read(Node node1, Content f, Node node2) { - readStep(node1, f, node2) and storeStep(_, f, _) - or - exists(DataFlowCall call, ReturnKind kind | - read0(call, kind, node1, f) and - node2 = getAnOutNode(call, kind) and - compatibleTypes(node1.getTypeBound(), f.getContainerType()) and - compatibleTypes(node2.getTypeBound(), f.getType()) - ) - } - - private predicate getter(ParameterNode p, Content f, ReturnKind kind) { - exists(Node n1, Node n2 | - parameterValueFlowNoCtx(p, n1) and - read(n1, f, n2) and - localValueStep*(n2, getAReturnNodeOfKind(kind)) - ) - } - - cached - predicate localStoreReadStep(Node node1, Node node2) { - exists(Node mid1, Node mid2, Content f | - store(node1, f, mid1) and - localValueStep*(mid1, mid2) and - read(mid2, f, node2) - ) - } - - /** - * Holds if `call` passes an implicit or explicit instance argument, i.e., an - * expression that reaches a `this` parameter. - */ - private predicate callHasInstanceArgument(DataFlowCall call) { - exists(ArgumentNode arg | arg.argumentOf(call, -1)) - } - - cached - newtype TCallContext = - TAnyCallContext() or - TSpecificCall(DataFlowCall call, int i, boolean emptyAp) { - reducedViableImplInCallContext(_, _, call) and - (emptyAp = true or emptyAp = false) and - ( - exists(call.getArgument(i)) + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps, not taking call contexts into account. + */ + private predicate parameterValueFlowCand(ParameterNode p, Node node) { + p = node or - i = -1 and callHasInstanceArgument(call) + exists(Node mid | + parameterValueFlowCand(p, mid) and + step(mid, node) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) + ) + or + // flow through a callable + exists(Node arg | + parameterValueFlowCand(p, arg) and + argumentValueFlowsThroughCand(arg, node) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) + ) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, not taking call contexts + * into account. + */ + private predicate parameterValueFlowsThroughCand(ParameterNode p, ReturnKind kind) { + parameterValueFlowCand(p, getAReturnNodeOfKind(kind)) + } + + pragma[nomagic] + private predicate argumentValueFlowsThroughCand0( + DataFlowCall call, ArgumentNode arg, ReturnKind kind + ) { + exists(ParameterNode param | viableParamArg(call, param, arg) | + parameterValueFlowsThroughCand(param, kind) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * not taking call contexts into account. + */ + private predicate argumentValueFlowsThroughCand(ArgumentNode arg, OutNode out) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThroughCand0(call, arg, kind) + | + out = getAnOutNode(call, kind) and + compatibleTypes(getErasedNodeType(arg), getErasedNodeType(out)) + ) + } + + /** + * Holds if `arg` is the `i`th argument of `call` inside the callable + * `enclosing`, and `arg` may flow through `call`. + */ + pragma[noinline] + private predicate argumentOf( + DataFlowCall call, int i, ArgumentNode arg, DataFlowCallable enclosing + ) { + arg.argumentOf(call, i) and + argumentValueFlowsThroughCand(arg, _) and + enclosing = arg.getEnclosingCallable() + } + + pragma[noinline] + private ParameterNode getAParameter(DataFlowCallable c) { result.getEnclosingCallable() = c } + + pragma[noinline] + private predicate viableParamArg0( + int i, ArgumentNode arg, CallContext outercc, DataFlowCall call + ) { + exists(DataFlowCallable c | argumentOf(call, i, arg, c) | + ( + outercc = TAnyCallContext() + or + outercc = TSomeCall(getAParameter(c), _) + or + exists(DataFlowCall other | outercc = TSpecificCall(other, _, _) | + recordDataFlowCallSite(other, c) + ) + ) and + not isUnreachableInCall(arg, outercc.(CallContextSpecificCall).getCall()) + ) + } + + pragma[noinline] + private predicate viableParamArg1( + ParameterNode p, DataFlowCallable callable, int i, ArgumentNode arg, CallContext outercc, + DataFlowCall call + ) { + viableParamArg0(i, arg, outercc, call) and + callable = resolveCall(call, outercc) and + p.isParameterOf(callable, any(int j | j <= i and j >= i)) + } + + /** + * Holds if `arg` is a possible argument to `p`, in the call `call`, and + * `arg` may flow through `call`. The possible contexts before and after + * entering the callable are `outercc` and `innercc`, respectively. + */ + private predicate viableParamArg( + DataFlowCall call, ParameterNode p, ArgumentNode arg, CallContext outercc, + CallContextCall innercc + ) { + exists(int i, DataFlowCallable callable | + viableParamArg1(p, callable, i, arg, outercc, call) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call, i, true) + else innercc = TSomeCall(p, true) + ) + } + + private CallContextCall getAValidCallContextForParameter(ParameterNode p) { + result = TSomeCall(p, _) + or + exists(DataFlowCall call, int i, DataFlowCallable callable | + result = TSpecificCall(call, i, _) and + p.isParameterOf(callable, i) and + recordDataFlowCallSite(call, callable) + ) + } + + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps, in call context `cc`. + */ + private predicate parameterValueFlow(ParameterNode p, Node node, CallContextCall cc) { + p = node and + parameterValueFlowsThroughCand(p, _) and + cc = getAValidCallContextForParameter(p) + or + exists(Node mid | + parameterValueFlow(p, mid, cc) and + step(mid, node) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) and + not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) + ) + or + // flow through a callable + exists(Node arg | + parameterValueFlow(p, arg, cc) and + argumentValueFlowsThrough(arg, node, cc) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) and + not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) + ) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, in call context `cc`. + */ + private predicate parameterValueFlowsThrough( + ParameterNode p, ReturnKind kind, CallContextCall cc + ) { + parameterValueFlow(p, getAReturnNodeOfKind(kind), cc) + } + + pragma[nomagic] + private predicate argumentValueFlowsThrough0( + DataFlowCall call, ArgumentNode arg, ReturnKind kind, CallContext cc + ) { + exists(ParameterNode param, CallContext innercc | + viableParamArg(call, param, arg, cc, innercc) and + parameterValueFlowsThrough(param, kind, innercc) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * in call context cc. + */ + predicate argumentValueFlowsThrough(ArgumentNode arg, OutNode out, CallContext cc) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThrough0(call, arg, kind, cc) + | + out = getAnOutNode(call, kind) and + not isUnreachableInCall(out, cc.(CallContextSpecificCall).getCall()) and + compatibleTypes(getErasedNodeType(arg), getErasedNodeType(out)) + ) + } + } + + /** + * Holds if `p` can flow to the pre-update node of `n` in the same callable + * using only value-preserving steps. + */ + cached + predicate parameterValueFlowsToUpdate(ParameterNode p, PostUpdateNode n) { + parameterValueFlowNoCtx(p, n.getPreUpdateNode()) + } + + /** + * Holds if data can flow from `node1` to `node2` in one local step or a step + * through a value-preserving method. + */ + private predicate localValueStep(Node node1, Node node2) { + simpleLocalFlowStep(node1, node2) or + FlowThrough_v1::argumentValueFlowsThrough(node1, node2, _) + } + + /** + * Holds if `p` can flow to `node` in the same callable allowing local flow + * steps and value flow through methods. Call contexts are only accounted + * for in the nested calls. + */ + private predicate parameterValueFlowNoCtx(ParameterNode p, Node node) { + p = node + or + exists(Node mid | + parameterValueFlowNoCtx(p, mid) and + localValueStep(mid, node) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) ) - } or - TSomeCall(ParameterNode p, boolean emptyAp) { emptyAp = true or emptyAp = false } or - TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) } + } - cached - newtype TReturnPosition = - TReturnPosition0(DataFlowCallable c, ReturnKind kind) { returnPosition(_, c, kind) } -} -import ImplCommon + /* + * Calculation of `predicate store(Node node1, Content f, Node node2)`: + * There are four cases: + * - The base case: A direct local assignment given by `storeStep`. + * - A call to a method or constructor with two arguments, `arg1` and `arg2`, + * such that the call has the side-effect `arg2.f = arg1`. + * - A call to a method that returns an object in which an argument has been + * stored. + * - A reverse step through a read when the result of the read has been + * stored into. This handles cases like `x.f1.f2 = y`. + * `storeViaSideEffect` covers the first two cases, and `storeReturn` covers + * the third case. + */ -pragma[noinline] -private predicate returnPosition(ReturnNode ret, DataFlowCallable c, ReturnKind kind) { - c = returnNodeGetEnclosingCallable(ret) and - kind = ret.getKind() -} + /** + * Holds if data can flow from `node1` to `node2` via a direct assignment to + * `f` or via a call that acts as a setter. + */ + cached + predicate store(Node node1, Content f, Node node2) { + storeViaSideEffect(node1, f, node2) or + storeReturn(node1, f, node2) or + read(node2.(PostUpdateNode).getPreUpdateNode(), f, node1.(PostUpdateNode).getPreUpdateNode()) + } -/** - * A call context to restrict the targets of virtual dispatch and match the - * call sites of flow into a method with flow out of a method. - * - * There are four cases: - * - `TAnyCallContext()` : No restrictions on method flow. - * - `TSpecificCall(DataFlowCall call, int i)` : Flow entered through the `i`th - * parameter at the given `call`. This call improves the set of viable - * dispatch targets for at least one method call in the current callable. - * - `TSomeCall(ParameterNode p)` : Flow entered through parameter `p`. The - * originating call does not improve the set of dispatch targets for any - * method call in the current callable and was therefore not recorded. - * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and - * this dispatch target of `call` implies a reduced set of dispatch origins - * to which data may flow if it should reach a `return` statement. - */ -abstract class CallContext extends TCallContext { - abstract string toString(); -} + private predicate storeViaSideEffect(Node node1, Content f, PostUpdateNode node2) { + storeStep(node1, f, node2) and readStep(_, f, _) + or + exists(DataFlowCall call, int i1, int i2 | + setterCall(call, i1, i2, f) and + node1.(ArgumentNode).argumentOf(call, i1) and + node2.getPreUpdateNode().(ArgumentNode).argumentOf(call, i2) and + compatibleTypes(getErasedNodeTypeBound(node1), f.getType()) and + compatibleTypes(getErasedNodeTypeBound(node2), f.getContainerType()) + ) + } -class CallContextAny extends CallContext, TAnyCallContext { - override string toString() { result = "CcAny" } -} + pragma[nomagic] + private predicate setterInParam(ParameterNode p1, Content f, ParameterNode p2) { + exists(Node n1, PostUpdateNode n2 | + parameterValueFlowNoCtx(p1, n1) and + storeViaSideEffect(n1, f, n2) and + parameterValueFlowNoCtx(p2, n2.getPreUpdateNode()) and + p1 != p2 + ) + } -abstract class CallContextCall extends CallContext { } + pragma[nomagic] + private predicate setterCall(DataFlowCall call, int i1, int i2, Content f) { + exists(DataFlowCallable callable, ParameterNode p1, ParameterNode p2 | + setterInParam(p1, f, p2) and + callable = viableCallable(call) and + p1.isParameterOf(callable, i1) and + p2.isParameterOf(callable, i2) + ) + } -class CallContextSpecificCall extends CallContextCall, TSpecificCall { - override string toString() { - exists(DataFlowCall call, int i | this = TSpecificCall(call, i, _) | - result = "CcCall(" + call + ", " + i + ")" + pragma[noinline] + private predicate storeReturn0(DataFlowCall call, ReturnKind kind, ArgumentNode arg, Content f) { + exists(ParameterNode p | + viableParamArg(call, p, arg) and + setterReturn(p, f, kind) + ) + } + + private predicate storeReturn(Node node1, Content f, Node node2) { + exists(DataFlowCall call, ReturnKind kind | + storeReturn0(call, kind, node1, f) and + node2 = getAnOutNode(call, kind) and + compatibleTypes(getErasedNodeTypeBound(node1), f.getType()) and + compatibleTypes(getErasedNodeTypeBound(node2), f.getContainerType()) + ) + } + + private predicate setterReturn(ParameterNode p, Content f, ReturnKind kind) { + exists(Node n1, Node n2 | + parameterValueFlowNoCtx(p, n1) and + store(n1, f, n2) and + localValueStep*(n2, getAReturnNodeOfKind(kind)) + ) + } + + pragma[noinline] + private predicate read0(DataFlowCall call, ReturnKind kind, ArgumentNode arg, Content f) { + exists(ParameterNode p | + viableParamArg(call, p, arg) and + getter(p, f, kind) + ) + } + + /** + * Holds if data can flow from `node1` to `node2` via a direct read of `f` or + * via a getter. + */ + cached + predicate read(Node node1, Content f, Node node2) { + readStep(node1, f, node2) + or + exists(DataFlowCall call, ReturnKind kind | + read0(call, kind, node1, f) and + node2 = getAnOutNode(call, kind) and + compatibleTypes(getErasedNodeTypeBound(node1), f.getContainerType()) and + compatibleTypes(getErasedNodeTypeBound(node2), f.getType()) + ) + } + + private predicate getter(ParameterNode p, Content f, ReturnKind kind) { + exists(Node n1, Node n2 | + parameterValueFlowNoCtx(p, n1) and + read(n1, f, n2) and + localValueStep*(n2, getAReturnNodeOfKind(kind)) + ) + } + + cached + predicate localStoreReadStep(Node node1, Node node2) { + exists(Node mid1, Node mid2, Content f | + store(node1, f, mid1) and + localValueStep*(mid1, mid2) and + read(mid2, f, node2) and + compatibleTypes(getErasedNodeTypeBound(node1), getErasedNodeTypeBound(node2)) + ) + } + + cached + module FlowThrough_v2 { + private predicate step(Node node1, Node node2) { + simpleLocalFlowStep(node1, node2) or + localStoreReadStep(node1, node2) + } + + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps, not taking call contexts into account. + */ + private predicate parameterValueFlowCand(ParameterNode p, Node node) { + p = node + or + exists(Node mid | + parameterValueFlowCand(p, mid) and + step(mid, node) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) + ) + or + // flow through a callable + exists(Node arg | + parameterValueFlowCand(p, arg) and + argumentValueFlowsThroughCand(arg, node) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) + ) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, not taking call contexts + * into account. + */ + private predicate parameterValueFlowsThroughCand(ParameterNode p, ReturnKind kind) { + parameterValueFlowCand(p, getAReturnNodeOfKind(kind)) + } + + pragma[nomagic] + private predicate argumentValueFlowsThroughCand0( + DataFlowCall call, ArgumentNode arg, ReturnKind kind + ) { + exists(ParameterNode param | viableParamArg(call, param, arg) | + parameterValueFlowsThroughCand(param, kind) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * not taking call contexts into account. + */ + private predicate argumentValueFlowsThroughCand(ArgumentNode arg, OutNode out) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThroughCand0(call, arg, kind) + | + out = getAnOutNode(call, kind) and + compatibleTypes(getErasedNodeType(arg), getErasedNodeType(out)) + ) + } + + /** + * Holds if `arg` is the `i`th argument of `call` inside the callable + * `enclosing`, and `arg` may flow through `call`. + */ + pragma[noinline] + private predicate argumentOf( + DataFlowCall call, int i, ArgumentNode arg, DataFlowCallable enclosing + ) { + arg.argumentOf(call, i) and + argumentValueFlowsThroughCand(arg, _) and + enclosing = arg.getEnclosingCallable() + } + + pragma[noinline] + private ParameterNode getAParameter(DataFlowCallable c) { result.getEnclosingCallable() = c } + + pragma[noinline] + private predicate viableParamArg0( + int i, ArgumentNode arg, CallContext outercc, DataFlowCall call + ) { + exists(DataFlowCallable c | argumentOf(call, i, arg, c) | + ( + outercc = TAnyCallContext() + or + outercc = TSomeCall(getAParameter(c), _) + or + exists(DataFlowCall other | outercc = TSpecificCall(other, _, _) | + recordDataFlowCallSite(other, c) + ) + ) and + not isUnreachableInCall(arg, outercc.(CallContextSpecificCall).getCall()) + ) + } + + pragma[noinline] + private predicate viableParamArg1( + ParameterNode p, DataFlowCallable callable, int i, ArgumentNode arg, CallContext outercc, + DataFlowCall call + ) { + viableParamArg0(i, arg, outercc, call) and + callable = resolveCall(call, outercc) and + p.isParameterOf(callable, any(int j | j <= i and j >= i)) + } + + /** + * Holds if `arg` is a possible argument to `p`, in the call `call`, and + * `arg` may flow through `call`. The possible contexts before and after + * entering the callable are `outercc` and `innercc`, respectively. + */ + private predicate viableParamArg( + DataFlowCall call, ParameterNode p, ArgumentNode arg, CallContext outercc, + CallContextCall innercc + ) { + exists(int i, DataFlowCallable callable | + viableParamArg1(p, callable, i, arg, outercc, call) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call, i, true) + else innercc = TSomeCall(p, true) + ) + } + + private CallContextCall getAValidCallContextForParameter(ParameterNode p) { + result = TSomeCall(p, _) + or + exists(DataFlowCall call, int i, DataFlowCallable callable | + result = TSpecificCall(call, i, _) and + p.isParameterOf(callable, i) and + recordDataFlowCallSite(call, callable) + ) + } + + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps, in call context `cc`. + */ + private predicate parameterValueFlow(ParameterNode p, Node node, CallContextCall cc) { + p = node and + parameterValueFlowsThroughCand(p, _) and + cc = getAValidCallContextForParameter(p) + or + exists(Node mid | + parameterValueFlow(p, mid, cc) and + step(mid, node) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) and + not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) + ) + or + // flow through a callable + exists(Node arg | + parameterValueFlow(p, arg, cc) and + argumentValueFlowsThrough(arg, node, cc) and + compatibleTypes(getErasedNodeType(p), getErasedNodeType(node)) and + not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) + ) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, in call context `cc`. + */ + cached + predicate parameterValueFlowsThrough(ParameterNode p, ReturnKind kind, CallContextCall cc) { + parameterValueFlow(p, getAReturnNodeOfKind(kind), cc) + } + + pragma[nomagic] + private predicate argumentValueFlowsThrough0( + DataFlowCall call, ArgumentNode arg, ReturnKind kind, CallContext cc + ) { + exists(ParameterNode param, CallContext innercc | + viableParamArg(call, param, arg, cc, innercc) and + parameterValueFlowsThrough(param, kind, innercc) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * in call context cc. + */ + cached + predicate argumentValueFlowsThrough(ArgumentNode arg, OutNode out, CallContext cc) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThrough0(call, arg, kind, cc) + | + out = getAnOutNode(call, kind) and + not isUnreachableInCall(out, cc.(CallContextSpecificCall).getCall()) and + compatibleTypes(getErasedNodeType(arg), getErasedNodeType(out)) + ) + } + } + + /** + * Holds if `call` passes an implicit or explicit instance argument, i.e., an + * expression that reaches a `this` parameter. + */ + private predicate callHasInstanceArgument(DataFlowCall call) { + exists(ArgumentNode arg | arg.argumentOf(call, -1)) + } + + /** + * Holds if the call context `call` either improves virtual dispatch in + * `callable` or if it allows us to prune unreachable nodes in `callable`. + */ + cached + predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { + reducedViableImplInCallContext(_, callable, call) + or + exists(Node n | n.getEnclosingCallable() = callable | isUnreachableInCall(n, call)) + } + + cached + newtype TCallContext = + TAnyCallContext() or + TSpecificCall(DataFlowCall call, int i, boolean emptyAp) { + recordDataFlowCallSite(call, _) and + (emptyAp = true or emptyAp = false) and + ( + exists(call.getArgument(i)) + or + i = -1 and callHasInstanceArgument(call) + ) + } or + TSomeCall(ParameterNode p, boolean emptyAp) { emptyAp = true or emptyAp = false } or + TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) } + + cached + newtype TReturnPosition = + TReturnPosition0(DataFlowCallable c, ReturnKindExt kind) { returnPosition(_, c, kind) } + + cached + newtype TLocalFlowCallContext = + TAnyLocalCall() or + TSpecificLocalCall(DataFlowCall call) { isUnreachableInCall(_, call) } + } + + pragma[noinline] + private predicate returnPosition(ReturnNodeExt ret, DataFlowCallable c, ReturnKindExt kind) { + c = returnNodeGetEnclosingCallable(ret) and + kind = ret.getKind() + } + + /** + * A call context to restrict the targets of virtual dispatch, prune local flow, + * and match the call sites of flow into a method with flow out of a method. + * + * There are four cases: + * - `TAnyCallContext()` : No restrictions on method flow. + * - `TSpecificCall(DataFlowCall call, int i)` : Flow entered through the `i`th + * parameter at the given `call`. This call improves the set of viable + * dispatch targets for at least one method call in the current callable + * or helps prune unreachable nodes in the current callable. + * - `TSomeCall(ParameterNode p)` : Flow entered through parameter `p`. The + * originating call does not improve the set of dispatch targets for any + * method call in the current callable and was therefore not recorded. + * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and + * this dispatch target of `call` implies a reduced set of dispatch origins + * to which data may flow if it should reach a `return` statement. + */ + abstract class CallContext extends TCallContext { + abstract string toString(); + + /** Holds if this call context is relevant for `callable`. */ + abstract predicate relevantFor(DataFlowCallable callable); + } + + class CallContextAny extends CallContext, TAnyCallContext { + override string toString() { result = "CcAny" } + + override predicate relevantFor(DataFlowCallable callable) { any() } + } + + abstract class CallContextCall extends CallContext { } + + class CallContextSpecificCall extends CallContextCall, TSpecificCall { + override string toString() { + exists(DataFlowCall call, int i | this = TSpecificCall(call, i, _) | + result = "CcCall(" + call + ", " + i + ")" + ) + } + + override predicate relevantFor(DataFlowCallable callable) { + recordDataFlowCallSite(getCall(), callable) + } + + DataFlowCall getCall() { this = TSpecificCall(result, _, _) } + } + + class CallContextSomeCall extends CallContextCall, TSomeCall { + override string toString() { result = "CcSomeCall" } + + override predicate relevantFor(DataFlowCallable callable) { + exists(ParameterNode p | this = TSomeCall(p, _) and p.getEnclosingCallable() = callable) + } + } + + class CallContextReturn extends CallContext, TReturn { + override string toString() { + exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")") + } + + override predicate relevantFor(DataFlowCallable callable) { + exists(DataFlowCall call | this = TReturn(_, call) and call.getEnclosingCallable() = callable) + } + } + + /** + * A call context that is relevant for pruning local flow. + */ + abstract class LocalCallContext extends TLocalFlowCallContext { + abstract string toString(); + + /** Holds if this call context is relevant for `callable`. */ + abstract predicate relevantFor(DataFlowCallable callable); + } + + class LocalCallContextAny extends LocalCallContext, TAnyLocalCall { + override string toString() { result = "LocalCcAny" } + + override predicate relevantFor(DataFlowCallable callable) { any() } + } + + class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall { + LocalCallContextSpecificCall() { this = TSpecificLocalCall(call) } + + DataFlowCall call; + + DataFlowCall getCall() { result = call } + + override string toString() { result = "LocalCcCall(" + call + ")" } + + override predicate relevantFor(DataFlowCallable callable) { relevantLocalCCtx(call, callable) } + } + + private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { + exists(Node n | n.getEnclosingCallable() = callable and isUnreachableInCall(n, call)) + } + + /** + * Gets the local call context given the call context and the callable that + * the contexts apply to. + */ + LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) { + ctx.relevantFor(callable) and + if relevantLocalCCtx(ctx.(CallContextSpecificCall).getCall(), callable) + then result.(LocalCallContextSpecificCall).getCall() = ctx.(CallContextSpecificCall).getCall() + else result instanceof LocalCallContextAny + } + + /** + * A node from which flow can return to the caller. This is either a regular + * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. + */ + class ReturnNodeExt extends Node { + ReturnNodeExt() { + this instanceof ReturnNode or + parameterValueFlowsToUpdate(_, this) + } + + /** Gets the kind of this returned value. */ + ReturnKindExt getKind() { + result = TValueReturn(this.(ReturnNode).getKind()) + or + exists(ParameterNode p, int pos | + parameterValueFlowsToUpdate(p, this) and + p.isParameterOf(_, pos) and + result = TParamUpdate(pos) + ) + } + } + + private newtype TReturnKindExt = + TValueReturn(ReturnKind kind) or + TParamUpdate(int pos) { + exists(ParameterNode p | parameterValueFlowsToUpdate(p, _) and p.isParameterOf(_, pos)) + } + + /** + * An extended return kind. A return kind describes how data can be returned + * from a callable. This can either be through a returned value or an updated + * parameter. + */ + abstract class ReturnKindExt extends TReturnKindExt { + /** Gets a textual representation of this return kind. */ + abstract string toString(); + + /** Gets a node corresponding to data flow out of `call`. */ + abstract Node getAnOutNode(DataFlowCall call); + } + + class ValueReturnKind extends ReturnKindExt, TValueReturn { + private ReturnKind kind; + + ValueReturnKind() { this = TValueReturn(kind) } + + ReturnKind getKind() { result = kind } + + override string toString() { result = kind.toString() } + + override Node getAnOutNode(DataFlowCall call) { result = getAnOutNode(call, this.getKind()) } + } + + class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { + private int pos; + + ParamUpdateReturnKind() { this = TParamUpdate(pos) } + + int getPosition() { result = pos } + + override string toString() { result = "param update " + pos } + + override Node getAnOutNode(DataFlowCall call) { + exists(ArgumentNode arg | + result.(PostUpdateNode).getPreUpdateNode() = arg and + arg.argumentOf(call, this.getPosition()) + ) + } + } + + /** A callable tagged with a relevant return kind. */ + class ReturnPosition extends TReturnPosition0 { + private DataFlowCallable c; + private ReturnKindExt kind; + + ReturnPosition() { this = TReturnPosition0(c, kind) } + + /** Gets the callable. */ + DataFlowCallable getCallable() { result = c } + + /** Gets the return kind. */ + ReturnKindExt getKind() { result = kind } + + /** Gets a textual representation of this return position. */ + string toString() { result = "[" + kind + "] " + c } + } + + pragma[noinline] + DataFlowCallable returnNodeGetEnclosingCallable(ReturnNodeExt ret) { + result = ret.getEnclosingCallable() + } + + pragma[noinline] + ReturnPosition getReturnPosition(ReturnNodeExt ret) { + exists(DataFlowCallable c, ReturnKindExt k | returnPosition(ret, c, k) | + result = TReturnPosition0(c, k) ) } -} -class CallContextSomeCall extends CallContextCall, TSomeCall { - override string toString() { result = "CcSomeCall" } -} - -class CallContextReturn extends CallContext, TReturn { - override string toString() { - exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")") + bindingset[cc, callable] + predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) { + cc instanceof CallContextAny and callable = viableCallable(call) + or + exists(DataFlowCallable c0, DataFlowCall call0 | + call0.getEnclosingCallable() = callable and + cc = TReturn(c0, call0) and + c0 = prunedViableImplInCallContextReverse(call0, call) + ) } -} - -/** A callable tagged with a relevant return kind. */ -class ReturnPosition extends TReturnPosition0 { - private DataFlowCallable c; - - private ReturnKind kind; - - ReturnPosition() { this = TReturnPosition0(c, kind) } - - /** Gets the callable. */ - DataFlowCallable getCallable() { result = c } - - /** Gets the return kind. */ - ReturnKind getKind() { result = kind } - - /** Gets a textual representation of this return position. */ - string toString() { result = "[" + kind + "] " + c } -} - -pragma[noinline] -DataFlowCallable returnNodeGetEnclosingCallable(ReturnNode ret) { - result = ret.getEnclosingCallable() -} - -pragma[noinline] -ReturnPosition getReturnPosition(ReturnNode ret) { - exists(DataFlowCallable c, ReturnKind k | returnPosition(ret, c, k) | - result = TReturnPosition0(c, k) - ) -} - -bindingset[cc, callable] -predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) { - cc instanceof CallContextAny and callable = viableCallable(call) - or - exists(DataFlowCallable c0, DataFlowCall call0 | - call0.getEnclosingCallable() = callable and - cc = TReturn(c0, call0) and - c0 = prunedViableImplInCallContextReverse(call0, call) - ) -} - -bindingset[call, cc] -DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { - exists(DataFlowCall ctx | cc = TSpecificCall(ctx, _, _) | - if reducedViableImplInCallContext(call, _, ctx) - then result = prunedViableImplInCallContext(call, ctx) - else result = viableCallable(call) - ) - or - result = viableCallable(call) and cc instanceof CallContextSomeCall - or - result = viableCallable(call) and cc instanceof CallContextAny - or - result = viableCallable(call) and cc instanceof CallContextReturn + + bindingset[call, cc] + DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { + exists(DataFlowCall ctx | cc = TSpecificCall(ctx, _, _) | + if reducedViableImplInCallContext(call, _, ctx) + then result = prunedViableImplInCallContext(call, ctx) + else result = viableCallable(call) + ) + or + result = viableCallable(call) and cc instanceof CallContextSomeCall + or + result = viableCallable(call) and cc instanceof CallContextAny + or + result = viableCallable(call) and cc instanceof CallContextReturn + } + + pragma[noinline] + DataFlowType getErasedNodeType(Node n) { result = getErasedRepr(n.getType()) } + + pragma[noinline] + DataFlowType getErasedNodeTypeBound(Node n) { result = getErasedRepr(n.getTypeBound()) } } diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowPrivate.qll b/ql/src/semmle/go/dataflow/internal/DataFlowPrivate.qll index 9154fd65c70..9651dd64840 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowPrivate.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowPrivate.qll @@ -1,9 +1,9 @@ private import go private import DataFlowUtil +private import DataFlowImplCommon::Public private newtype TReturnKind = - TSingleReturn() - or + TSingleReturn() or TMultiReturn(int i) { exists(SignatureType st | exists(st.getResultType(i))) } /** @@ -17,9 +17,7 @@ class ReturnKind extends TReturnKind { this = TSingleReturn() and result = "return" or - exists(int i | this = TMultiReturn(i) | - result = "return[" + i + "]" - ) + exists(int i | this = TMultiReturn(i) | result = "return[" + i + "]") } } @@ -29,10 +27,7 @@ class ReturnNode extends ResultNode { ReturnNode() { exists(int nr | nr = fd.getType().getNumResult() | - if nr = 1 then - kind = TSingleReturn() - else - kind = TMultiReturn(i) + if nr = 1 then kind = TSingleReturn() else kind = TMultiReturn(i) ) } @@ -43,7 +38,6 @@ class ReturnNode extends ResultNode { /** A data flow node that represents the output of a call. */ class OutNode extends DataFlow::Node { DataFlow::CallNode call; - int i; OutNode() { @@ -66,9 +60,7 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { kind = TSingleReturn() and result = c.getResult() or - exists(int i | kind = TMultiReturn(i) | - result = c.getResult(i) - ) + exists(int i | kind = TMultiReturn(i) | result = c.getResult(i)) ) } @@ -241,3 +233,38 @@ class DataFlowCall extends Expr { /** Gets the enclosing callable of this call. */ DataFlowCallable getEnclosingCallable() { result = this.getEnclosingFunction() } } + +/** Holds if `e` is an expression that always has the same Boolean value `val`. */ +private predicate constantBooleanExpr(Expr e, boolean val) { + e.getBoolValue() = val + or + exists(SsaExplicitDefinition v, Expr src | + IR::evalExprInstruction(e) = v.getVariable().getAUse() and + IR::evalExprInstruction(src) = v.getRhs() and + constantBooleanExpr(src, val) + ) +} + +/** An argument that always has the same Boolean value. */ +private class ConstantBooleanArgumentNode extends ArgumentNode, ExprNode { + ConstantBooleanArgumentNode() { constantBooleanExpr(this.getExpr(), _) } + + /** Gets the Boolean value of this expression. */ + boolean getBooleanValue() { constantBooleanExpr(this.getExpr(), result) } +} + +/** + * Holds if the node `n` is unreachable when the call context is `call`. + */ +cached +predicate isUnreachableInCall(Node n, DataFlowCall call) { + exists( + ParameterNode param, ConstantBooleanArgumentNode arg, ControlFlow::ConditionGuardNode guard + | + // get constant bool argument and parameter for this call + viableParamArg(call, param, arg) and + // which is used in a guard controlling `n` with the opposite value of `arg` + guard.ensures(param.getAUse(), arg.getBooleanValue().booleanNot()) and + guard.dominates(n.getBasicBlock()) + ) +} diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll index e99970d1321..434b3808aa0 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -683,8 +683,18 @@ Node extractTupleElement(Node t, int i) { * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local * (intra-procedural) step. */ -cached predicate localFlowStep(Node nodeFrom, Node nodeTo) { + simpleLocalFlowStep(nodeFrom, nodeTo) +} + +/** + * INTERNAL: do not use. + * + * This is the local flow predicate that's used as a building block in global + * data flow. It may have less flow than the `localFlowStep` predicate. + */ +cached +predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { // Instruction -> Instruction exists(Expr pred, Expr succ | succ.(LogicalBinaryExpr).getAnOperand() = pred or diff --git a/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.expected b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.expected index 8cbde9e7985..611637df31e 100644 --- a/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.expected +++ b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.expected @@ -1,6 +1,9 @@ edges -| IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" [string] | IncompleteHostnameRegexp.go:12:41:12:42 | re | -| main.go:12:15:12:39 | `https://www.example.com` [string] | main.go:12:15:12:39 | `https://www.example.com` | +| IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" : string | IncompleteHostnameRegexp.go:12:41:12:42 | re | +nodes +| IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" : string | semmle.label | "^((www\|beta).)?example.com/" : string | +| IncompleteHostnameRegexp.go:12:41:12:42 | re | semmle.label | re | +| main.go:12:15:12:39 | `https://www.example.com` | semmle.label | `https://www.example.com` | #select -| IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" [string] | IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" [string] | IncompleteHostnameRegexp.go:12:41:12:42 | re | This regular expression has an unescaped dot before ')?example.com', so it might match more hosts than expected when used $@. | IncompleteHostnameRegexp.go:12:41:12:42 | re | here | -| main.go:12:15:12:39 | `https://www.example.com` [string] | main.go:12:15:12:39 | `https://www.example.com` [string] | main.go:12:15:12:39 | `https://www.example.com` | This regular expression has an unescaped dot before 'example.com', so it might match more hosts than expected when used $@. | main.go:12:15:12:39 | `https://www.example.com` | here | +| IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" : string | IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" : string | IncompleteHostnameRegexp.go:12:41:12:42 | re | This regular expression has an unescaped dot before ')?example.com', so it might match more hosts than expected when used $@. | IncompleteHostnameRegexp.go:12:41:12:42 | re | here | +| main.go:12:15:12:39 | `https://www.example.com` | main.go:12:15:12:39 | `https://www.example.com` | main.go:12:15:12:39 | `https://www.example.com` | This regular expression has an unescaped dot before 'example.com', so it might match more hosts than expected when used $@. | main.go:12:15:12:39 | `https://www.example.com` | here | diff --git a/ql/test/query-tests/Security/CWE-022/TaintedPath.expected b/ql/test/query-tests/Security/CWE-022/TaintedPath.expected index 38bd589e6ab..04e3459926c 100644 --- a/ql/test/query-tests/Security/CWE-022/TaintedPath.expected +++ b/ql/test/query-tests/Security/CWE-022/TaintedPath.expected @@ -1,6 +1,10 @@ edges -| TaintedPath.go:10:10:10:14 | selection of URL [pointer type] | TaintedPath.go:13:29:13:32 | path | -| TaintedPath.go:10:10:10:14 | selection of URL [pointer type] | TaintedPath.go:17:28:17:61 | call to Join | +| TaintedPath.go:10:10:10:14 | selection of URL : pointer type | TaintedPath.go:13:29:13:32 | path | +| TaintedPath.go:10:10:10:14 | selection of URL : pointer type | TaintedPath.go:17:28:17:61 | call to Join | +nodes +| TaintedPath.go:10:10:10:14 | selection of URL : pointer type | semmle.label | selection of URL : pointer type | +| TaintedPath.go:13:29:13:32 | path | semmle.label | path | +| TaintedPath.go:17:28:17:61 | call to Join | semmle.label | call to Join | #select -| TaintedPath.go:13:29:13:32 | path | TaintedPath.go:10:10:10:14 | selection of URL [pointer type] | TaintedPath.go:13:29:13:32 | path | This path depends on $@. | TaintedPath.go:10:10:10:14 | selection of URL | a user-provided value | -| TaintedPath.go:17:28:17:61 | call to Join | TaintedPath.go:10:10:10:14 | selection of URL [pointer type] | TaintedPath.go:17:28:17:61 | call to Join | This path depends on $@. | TaintedPath.go:10:10:10:14 | selection of URL | a user-provided value | +| TaintedPath.go:13:29:13:32 | path | TaintedPath.go:10:10:10:14 | selection of URL : pointer type | TaintedPath.go:13:29:13:32 | path | This path depends on $@. | TaintedPath.go:10:10:10:14 | selection of URL | a user-provided value | +| TaintedPath.go:17:28:17:61 | call to Join | TaintedPath.go:10:10:10:14 | selection of URL : pointer type | TaintedPath.go:17:28:17:61 | call to Join | This path depends on $@. | TaintedPath.go:10:10:10:14 | selection of URL | a user-provided value | diff --git a/ql/test/query-tests/Security/CWE-022/ZipSlip.expected b/ql/test/query-tests/Security/CWE-022/ZipSlip.expected index 4199928f4d3..81c345ca909 100644 --- a/ql/test/query-tests/Security/CWE-022/ZipSlip.expected +++ b/ql/test/query-tests/Security/CWE-022/ZipSlip.expected @@ -1,6 +1,11 @@ edges -| ZipSlip.go:12:24:12:29 | selection of Name [string] | ZipSlip.go:14:20:14:20 | p | -| tst.go:15:11:15:16 | selection of Name [string] | tst.go:20:20:20:23 | path | +| ZipSlip.go:12:24:12:29 | selection of Name : string | ZipSlip.go:14:20:14:20 | p | +| tst.go:15:11:15:16 | selection of Name : string | tst.go:20:20:20:23 | path | +nodes +| ZipSlip.go:12:24:12:29 | selection of Name : string | semmle.label | selection of Name : string | +| ZipSlip.go:14:20:14:20 | p | semmle.label | p | +| tst.go:15:11:15:16 | selection of Name : string | semmle.label | selection of Name : string | +| tst.go:20:20:20:23 | path | semmle.label | path | #select -| ZipSlip.go:12:24:12:29 | selection of Name | ZipSlip.go:12:24:12:29 | selection of Name [string] | ZipSlip.go:14:20:14:20 | p | Unsanitized archive entry, which may contain '..', is used in a $@. | ZipSlip.go:14:20:14:20 | p | file system operation | -| tst.go:15:11:15:16 | selection of Name | tst.go:15:11:15:16 | selection of Name [string] | tst.go:20:20:20:23 | path | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:20:20:20:23 | path | file system operation | +| ZipSlip.go:12:24:12:29 | selection of Name | ZipSlip.go:12:24:12:29 | selection of Name : string | ZipSlip.go:14:20:14:20 | p | Unsanitized archive entry, which may contain '..', is used in a $@. | ZipSlip.go:14:20:14:20 | p | file system operation | +| tst.go:15:11:15:16 | selection of Name | tst.go:15:11:15:16 | selection of Name : string | tst.go:20:20:20:23 | path | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:20:20:20:23 | path | file system operation | diff --git a/ql/test/query-tests/Security/CWE-078/CommandInjection.expected b/ql/test/query-tests/Security/CWE-078/CommandInjection.expected index 177a0980a04..9b193aceccc 100644 --- a/ql/test/query-tests/Security/CWE-078/CommandInjection.expected +++ b/ql/test/query-tests/Security/CWE-078/CommandInjection.expected @@ -1,4 +1,7 @@ edges -| CommandInjection.go:9:13:9:19 | selection of URL [pointer type] | CommandInjection.go:10:22:10:28 | cmdName | +| CommandInjection.go:9:13:9:19 | selection of URL : pointer type | CommandInjection.go:10:22:10:28 | cmdName | +nodes +| CommandInjection.go:9:13:9:19 | selection of URL : pointer type | semmle.label | selection of URL : pointer type | +| CommandInjection.go:10:22:10:28 | cmdName | semmle.label | cmdName | #select -| CommandInjection.go:10:22:10:28 | cmdName | CommandInjection.go:9:13:9:19 | selection of URL [pointer type] | CommandInjection.go:10:22:10:28 | cmdName | This command depends on $@. | CommandInjection.go:9:13:9:19 | selection of URL | a user-provided value | +| CommandInjection.go:10:22:10:28 | cmdName | CommandInjection.go:9:13:9:19 | selection of URL : pointer type | CommandInjection.go:10:22:10:28 | cmdName | This command depends on $@. | CommandInjection.go:9:13:9:19 | selection of URL | a user-provided value | diff --git a/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index 1d25218cd42..c3eedee3b79 100644 --- a/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -1,6 +1,11 @@ edges -| ReflectedXss.go:11:15:11:20 | selection of Form [Values] | ReflectedXss.go:14:39:14:46 | username | -| contenttype.go:10:11:10:16 | selection of Form [Values] | contenttype.go:16:11:16:22 | type conversion | +| ReflectedXss.go:11:15:11:20 | selection of Form : Values | ReflectedXss.go:14:39:14:46 | username | +| contenttype.go:10:11:10:16 | selection of Form : Values | contenttype.go:16:11:16:22 | type conversion | +nodes +| ReflectedXss.go:11:15:11:20 | selection of Form : Values | semmle.label | selection of Form : Values | +| ReflectedXss.go:14:39:14:46 | username | semmle.label | username | +| contenttype.go:10:11:10:16 | selection of Form : Values | semmle.label | selection of Form : Values | +| contenttype.go:16:11:16:22 | type conversion | semmle.label | type conversion | #select -| ReflectedXss.go:14:39:14:46 | username | ReflectedXss.go:11:15:11:20 | selection of Form [Values] | ReflectedXss.go:14:39:14:46 | username | Cross-site scripting vulnerability due to $@. | ReflectedXss.go:11:15:11:20 | selection of Form | user-provided value | -| contenttype.go:16:11:16:22 | type conversion | contenttype.go:10:11:10:16 | selection of Form [Values] | contenttype.go:16:11:16:22 | type conversion | Cross-site scripting vulnerability due to $@. | contenttype.go:10:11:10:16 | selection of Form | user-provided value | +| ReflectedXss.go:14:39:14:46 | username | ReflectedXss.go:11:15:11:20 | selection of Form : Values | ReflectedXss.go:14:39:14:46 | username | Cross-site scripting vulnerability due to $@. | ReflectedXss.go:11:15:11:20 | selection of Form | user-provided value | +| contenttype.go:16:11:16:22 | type conversion | contenttype.go:10:11:10:16 | selection of Form : Values | contenttype.go:16:11:16:22 | type conversion | Cross-site scripting vulnerability due to $@. | contenttype.go:10:11:10:16 | selection of Form | user-provided value | diff --git a/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/ql/test/query-tests/Security/CWE-089/SqlInjection.expected index b45e18da3d6..a342a147e04 100644 --- a/ql/test/query-tests/Security/CWE-089/SqlInjection.expected +++ b/ql/test/query-tests/Security/CWE-089/SqlInjection.expected @@ -1,6 +1,11 @@ edges -| SqlInjection.go:11:3:11:9 | selection of URL [pointer type] | SqlInjection.go:12:11:12:11 | q | -| main.go:9:11:9:16 | selection of Form [Values] | main.go:9:11:9:28 | index expression | +| SqlInjection.go:11:3:11:9 | selection of URL : pointer type | SqlInjection.go:12:11:12:11 | q | +| main.go:9:11:9:16 | selection of Form : Values | main.go:9:11:9:28 | index expression | +nodes +| SqlInjection.go:11:3:11:9 | selection of URL : pointer type | semmle.label | selection of URL : pointer type | +| SqlInjection.go:12:11:12:11 | q | semmle.label | q | +| main.go:9:11:9:16 | selection of Form : Values | semmle.label | selection of Form : Values | +| main.go:9:11:9:28 | index expression | semmle.label | index expression | #select -| SqlInjection.go:12:11:12:11 | q | SqlInjection.go:11:3:11:9 | selection of URL [pointer type] | SqlInjection.go:12:11:12:11 | q | This query depends on $@. | SqlInjection.go:11:3:11:9 | selection of URL | a user-provided value | -| main.go:9:11:9:28 | index expression | main.go:9:11:9:16 | selection of Form [Values] | main.go:9:11:9:28 | index expression | This query depends on $@. | main.go:9:11:9:16 | selection of Form | a user-provided value | +| SqlInjection.go:12:11:12:11 | q | SqlInjection.go:11:3:11:9 | selection of URL : pointer type | SqlInjection.go:12:11:12:11 | q | This query depends on $@. | SqlInjection.go:11:3:11:9 | selection of URL | a user-provided value | +| main.go:9:11:9:28 | index expression | main.go:9:11:9:16 | selection of Form : Values | main.go:9:11:9:28 | index expression | This query depends on $@. | main.go:9:11:9:16 | selection of Form | a user-provided value | diff --git a/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected b/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected index 4b565ae2c0a..3c4f58fbe4d 100644 --- a/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected +++ b/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected @@ -1,54 +1,88 @@ edges -| passwords.go:8:12:8:12 | definition of x [string] | passwords.go:9:14:9:14 | x | -| passwords.go:25:14:25:21 | password [string] | passwords.go:25:14:25:21 | password | -| passwords.go:26:14:26:23 | selection of password [string] | passwords.go:26:14:26:23 | selection of password | -| passwords.go:27:14:27:26 | call to getPassword [string] | passwords.go:27:14:27:26 | call to getPassword | -| passwords.go:28:14:28:28 | call to getPassword [string] | passwords.go:28:14:28:28 | call to getPassword | -| passwords.go:30:8:30:15 | password [string] | passwords.go:8:12:8:12 | definition of x [string] | -| passwords.go:32:12:32:19 | password [string] | passwords.go:32:12:32:19 | password | -| passwords.go:34:28:34:35 | password [string] | passwords.go:34:14:34:35 | ...+... | -| passwords.go:36:10:38:2 | composite literal [passStruct] | passwords.go:39:14:39:17 | obj1 | -| passwords.go:42:6:42:13 | password [string] | passwords.go:44:14:44:17 | obj2 | -| passwords.go:48:11:48:18 | password [string] | passwords.go:47:14:47:17 | obj3 | -| passwords.go:51:14:51:27 | fixed_password [string] | passwords.go:51:14:51:27 | fixed_password | -| passwords.go:85:19:87:2 | composite literal [passSetStruct] | passwords.go:88:14:88:26 | utilityObject | -| passwords.go:90:12:90:19 | password [string] | passwords.go:91:23:91:28 | secret | -| passwords.go:101:33:101:40 | password [string] | passwords.go:101:15:101:40 | ...+... | -| passwords.go:107:34:107:41 | password [string] | passwords.go:107:16:107:41 | ...+... | -| passwords.go:112:33:112:40 | password [string] | passwords.go:112:15:112:40 | ...+... | -| passwords.go:116:28:116:36 | password1 [stringable] | passwords.go:116:14:116:45 | ...+... | -| passwords.go:118:12:123:2 | composite literal [Config] | passwords.go:125:14:125:19 | config | -| passwords.go:118:12:123:2 | composite literal [x, ... (1)] | passwords.go:126:14:126:19 | config [x, ... (1)] | -| passwords.go:118:12:123:2 | composite literal [y, ... (1)] | passwords.go:127:14:127:19 | config [y, ... (1)] | -| passwords.go:121:13:121:20 | password [string] | passwords.go:118:12:123:2 | composite literal [x, ... (1)] | -| passwords.go:121:13:121:20 | password [string] | passwords.go:125:14:125:19 | config | -| passwords.go:122:13:122:25 | call to getPassword [string] | passwords.go:118:12:123:2 | composite literal [y, ... (1)] | -| passwords.go:122:13:122:25 | call to getPassword [string] | passwords.go:125:14:125:19 | config | -| passwords.go:126:14:126:19 | config [x, ... (1)] | passwords.go:126:14:126:21 | selection of x | -| passwords.go:127:14:127:19 | config [y, ... (1)] | passwords.go:127:14:127:21 | selection of y | -| util.go:14:9:14:18 | selection of password [string] | passwords.go:28:14:28:28 | call to getPassword | -| util.go:14:9:14:18 | selection of password [string] | passwords.go:28:14:28:28 | call to getPassword [string] | +| passwords.go:8:12:8:12 | definition of x : string | passwords.go:9:14:9:14 | x | +| passwords.go:30:8:30:15 | password : string | passwords.go:8:12:8:12 | definition of x : string | +| passwords.go:34:28:34:35 | password : string | passwords.go:34:14:34:35 | ...+... | +| passwords.go:36:10:38:2 | composite literal : passStruct | passwords.go:39:14:39:17 | obj1 | +| passwords.go:42:6:42:13 | password : string | passwords.go:44:14:44:17 | obj2 | +| passwords.go:48:11:48:18 | password : string | passwords.go:47:14:47:17 | obj3 | +| passwords.go:85:19:87:2 | composite literal : passSetStruct | passwords.go:88:14:88:26 | utilityObject | +| passwords.go:90:12:90:19 | password : string | passwords.go:91:23:91:28 | secret | +| passwords.go:101:33:101:40 | password : string | passwords.go:101:15:101:40 | ...+... | +| passwords.go:107:34:107:41 | password : string | passwords.go:107:16:107:41 | ...+... | +| passwords.go:112:33:112:40 | password : string | passwords.go:112:15:112:40 | ...+... | +| passwords.go:116:28:116:36 | password1 : stringable | passwords.go:116:14:116:45 | ...+... | +| passwords.go:118:12:123:2 | composite literal : Config | passwords.go:125:14:125:19 | config | +| passwords.go:118:12:123:2 | composite literal [x] : string | passwords.go:126:14:126:19 | config [x] : string | +| passwords.go:118:12:123:2 | composite literal [y] : string | passwords.go:127:14:127:19 | config [y] : string | +| passwords.go:121:13:121:20 | password : string | passwords.go:118:12:123:2 | composite literal [x] : string | +| passwords.go:121:13:121:20 | password : string | passwords.go:125:14:125:19 | config | +| passwords.go:122:13:122:25 | call to getPassword : string | passwords.go:118:12:123:2 | composite literal [y] : string | +| passwords.go:122:13:122:25 | call to getPassword : string | passwords.go:125:14:125:19 | config | +| passwords.go:126:14:126:19 | config [x] : string | passwords.go:126:14:126:21 | selection of x | +| passwords.go:127:14:127:19 | config [y] : string | passwords.go:127:14:127:21 | selection of y | +| util.go:14:9:14:18 | selection of password : string | passwords.go:28:14:28:28 | call to getPassword | +nodes +| passwords.go:8:12:8:12 | definition of x : string | semmle.label | definition of x : string | +| passwords.go:9:14:9:14 | x | semmle.label | x | +| passwords.go:25:14:25:21 | password | semmle.label | password | +| passwords.go:26:14:26:23 | selection of password | semmle.label | selection of password | +| passwords.go:27:14:27:26 | call to getPassword | semmle.label | call to getPassword | +| passwords.go:28:14:28:28 | call to getPassword | semmle.label | call to getPassword | +| passwords.go:30:8:30:15 | password : string | semmle.label | password : string | +| passwords.go:32:12:32:19 | password | semmle.label | password | +| passwords.go:34:14:34:35 | ...+... | semmle.label | ...+... | +| passwords.go:34:28:34:35 | password : string | semmle.label | password : string | +| passwords.go:36:10:38:2 | composite literal : passStruct | semmle.label | composite literal : passStruct | +| passwords.go:39:14:39:17 | obj1 | semmle.label | obj1 | +| passwords.go:42:6:42:13 | password : string | semmle.label | password : string | +| passwords.go:44:14:44:17 | obj2 | semmle.label | obj2 | +| passwords.go:47:14:47:17 | obj3 | semmle.label | obj3 | +| passwords.go:48:11:48:18 | password : string | semmle.label | password : string | +| passwords.go:51:14:51:27 | fixed_password | semmle.label | fixed_password | +| passwords.go:85:19:87:2 | composite literal : passSetStruct | semmle.label | composite literal : passSetStruct | +| passwords.go:88:14:88:26 | utilityObject | semmle.label | utilityObject | +| passwords.go:90:12:90:19 | password : string | semmle.label | password : string | +| passwords.go:91:23:91:28 | secret | semmle.label | secret | +| passwords.go:101:15:101:40 | ...+... | semmle.label | ...+... | +| passwords.go:101:33:101:40 | password : string | semmle.label | password : string | +| passwords.go:107:16:107:41 | ...+... | semmle.label | ...+... | +| passwords.go:107:34:107:41 | password : string | semmle.label | password : string | +| passwords.go:112:15:112:40 | ...+... | semmle.label | ...+... | +| passwords.go:112:33:112:40 | password : string | semmle.label | password : string | +| passwords.go:116:14:116:45 | ...+... | semmle.label | ...+... | +| passwords.go:116:28:116:36 | password1 : stringable | semmle.label | password1 : stringable | +| passwords.go:118:12:123:2 | composite literal : Config | semmle.label | composite literal : Config | +| passwords.go:118:12:123:2 | composite literal [x] : string | semmle.label | composite literal [x] : string | +| passwords.go:118:12:123:2 | composite literal [y] : string | semmle.label | composite literal [y] : string | +| passwords.go:121:13:121:20 | password : string | semmle.label | password : string | +| passwords.go:122:13:122:25 | call to getPassword : string | semmle.label | call to getPassword : string | +| passwords.go:125:14:125:19 | config | semmle.label | config | +| passwords.go:126:14:126:19 | config [x] : string | semmle.label | config [x] : string | +| passwords.go:126:14:126:21 | selection of x | semmle.label | selection of x | +| passwords.go:127:14:127:19 | config [y] : string | semmle.label | config [y] : string | +| passwords.go:127:14:127:21 | selection of y | semmle.label | selection of y | +| util.go:14:9:14:18 | selection of password : string | semmle.label | selection of password : string | #select -| passwords.go:9:14:9:14 | x | passwords.go:30:8:30:15 | password [string] | passwords.go:9:14:9:14 | x | Sensitive data returned by $@ is logged here. | passwords.go:30:8:30:15 | password | an access to password | -| passwords.go:25:14:25:21 | password | passwords.go:25:14:25:21 | password [string] | passwords.go:25:14:25:21 | password | Sensitive data returned by $@ is logged here. | passwords.go:25:14:25:21 | password | an access to password | -| passwords.go:26:14:26:23 | selection of password | passwords.go:26:14:26:23 | selection of password [string] | passwords.go:26:14:26:23 | selection of password | Sensitive data returned by $@ is logged here. | passwords.go:26:14:26:23 | selection of password | an access to password | -| passwords.go:27:14:27:26 | call to getPassword | passwords.go:27:14:27:26 | call to getPassword [string] | passwords.go:27:14:27:26 | call to getPassword | Sensitive data returned by $@ is logged here. | passwords.go:27:14:27:26 | call to getPassword | a call to getPassword | -| passwords.go:28:14:28:28 | call to getPassword | passwords.go:28:14:28:28 | call to getPassword [string] | passwords.go:28:14:28:28 | call to getPassword | Sensitive data returned by $@ is logged here. | passwords.go:28:14:28:28 | call to getPassword | a call to getPassword | -| passwords.go:28:14:28:28 | call to getPassword | util.go:14:9:14:18 | selection of password [string] | passwords.go:28:14:28:28 | call to getPassword | Sensitive data returned by $@ is logged here. | util.go:14:9:14:18 | selection of password | an access to password | -| passwords.go:32:12:32:19 | password | passwords.go:32:12:32:19 | password [string] | passwords.go:32:12:32:19 | password | Sensitive data returned by $@ is logged here. | passwords.go:32:12:32:19 | password | an access to password | -| passwords.go:34:14:34:35 | ...+... | passwords.go:34:28:34:35 | password [string] | passwords.go:34:14:34:35 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:34:28:34:35 | password | an access to password | -| passwords.go:39:14:39:17 | obj1 | passwords.go:36:10:38:2 | composite literal [passStruct] | passwords.go:39:14:39:17 | obj1 | Sensitive data returned by $@ is logged here. | passwords.go:36:10:38:2 | composite literal | an access to password | -| passwords.go:44:14:44:17 | obj2 | passwords.go:42:6:42:13 | password [string] | passwords.go:44:14:44:17 | obj2 | Sensitive data returned by $@ is logged here. | passwords.go:42:6:42:13 | password | an access to password | -| passwords.go:47:14:47:17 | obj3 | passwords.go:48:11:48:18 | password [string] | passwords.go:47:14:47:17 | obj3 | Sensitive data returned by $@ is logged here. | passwords.go:48:11:48:18 | password | an access to password | -| passwords.go:51:14:51:27 | fixed_password | passwords.go:51:14:51:27 | fixed_password [string] | passwords.go:51:14:51:27 | fixed_password | Sensitive data returned by $@ is logged here. | passwords.go:51:14:51:27 | fixed_password | an access to fixed_password | -| passwords.go:88:14:88:26 | utilityObject | passwords.go:85:19:87:2 | composite literal [passSetStruct] | passwords.go:88:14:88:26 | utilityObject | Sensitive data returned by $@ is logged here. | passwords.go:85:19:87:2 | composite literal | an access to passwordSet | -| passwords.go:91:23:91:28 | secret | passwords.go:90:12:90:19 | password [string] | passwords.go:91:23:91:28 | secret | Sensitive data returned by $@ is logged here. | passwords.go:90:12:90:19 | password | an access to password | -| passwords.go:101:15:101:40 | ...+... | passwords.go:101:33:101:40 | password [string] | passwords.go:101:15:101:40 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:101:33:101:40 | password | an access to password | -| passwords.go:107:16:107:41 | ...+... | passwords.go:107:34:107:41 | password [string] | passwords.go:107:16:107:41 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:107:34:107:41 | password | an access to password | -| passwords.go:112:15:112:40 | ...+... | passwords.go:112:33:112:40 | password [string] | passwords.go:112:15:112:40 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:112:33:112:40 | password | an access to password | -| passwords.go:116:14:116:45 | ...+... | passwords.go:116:28:116:36 | password1 [stringable] | passwords.go:116:14:116:45 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:116:28:116:36 | password1 | an access to password1 | -| passwords.go:125:14:125:19 | config | passwords.go:118:12:123:2 | composite literal [Config] | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:118:12:123:2 | composite literal | an access to password | -| passwords.go:125:14:125:19 | config | passwords.go:121:13:121:20 | password [string] | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:121:13:121:20 | password | an access to password | -| passwords.go:125:14:125:19 | config | passwords.go:122:13:122:25 | call to getPassword [string] | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:122:13:122:25 | call to getPassword | a call to getPassword | -| passwords.go:126:14:126:21 | selection of x | passwords.go:121:13:121:20 | password [string] | passwords.go:126:14:126:21 | selection of x | Sensitive data returned by $@ is logged here. | passwords.go:121:13:121:20 | password | an access to password | -| passwords.go:127:14:127:21 | selection of y | passwords.go:122:13:122:25 | call to getPassword [string] | passwords.go:127:14:127:21 | selection of y | Sensitive data returned by $@ is logged here. | passwords.go:122:13:122:25 | call to getPassword | a call to getPassword | +| passwords.go:9:14:9:14 | x | passwords.go:30:8:30:15 | password : string | passwords.go:9:14:9:14 | x | Sensitive data returned by $@ is logged here. | passwords.go:30:8:30:15 | password | an access to password | +| passwords.go:25:14:25:21 | password | passwords.go:25:14:25:21 | password | passwords.go:25:14:25:21 | password | Sensitive data returned by $@ is logged here. | passwords.go:25:14:25:21 | password | an access to password | +| passwords.go:26:14:26:23 | selection of password | passwords.go:26:14:26:23 | selection of password | passwords.go:26:14:26:23 | selection of password | Sensitive data returned by $@ is logged here. | passwords.go:26:14:26:23 | selection of password | an access to password | +| passwords.go:27:14:27:26 | call to getPassword | passwords.go:27:14:27:26 | call to getPassword | passwords.go:27:14:27:26 | call to getPassword | Sensitive data returned by $@ is logged here. | passwords.go:27:14:27:26 | call to getPassword | a call to getPassword | +| passwords.go:28:14:28:28 | call to getPassword | passwords.go:28:14:28:28 | call to getPassword | passwords.go:28:14:28:28 | call to getPassword | Sensitive data returned by $@ is logged here. | passwords.go:28:14:28:28 | call to getPassword | a call to getPassword | +| passwords.go:28:14:28:28 | call to getPassword | util.go:14:9:14:18 | selection of password : string | passwords.go:28:14:28:28 | call to getPassword | Sensitive data returned by $@ is logged here. | util.go:14:9:14:18 | selection of password | an access to password | +| passwords.go:32:12:32:19 | password | passwords.go:32:12:32:19 | password | passwords.go:32:12:32:19 | password | Sensitive data returned by $@ is logged here. | passwords.go:32:12:32:19 | password | an access to password | +| passwords.go:34:14:34:35 | ...+... | passwords.go:34:28:34:35 | password : string | passwords.go:34:14:34:35 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:34:28:34:35 | password | an access to password | +| passwords.go:39:14:39:17 | obj1 | passwords.go:36:10:38:2 | composite literal : passStruct | passwords.go:39:14:39:17 | obj1 | Sensitive data returned by $@ is logged here. | passwords.go:36:10:38:2 | composite literal | an access to password | +| passwords.go:44:14:44:17 | obj2 | passwords.go:42:6:42:13 | password : string | passwords.go:44:14:44:17 | obj2 | Sensitive data returned by $@ is logged here. | passwords.go:42:6:42:13 | password | an access to password | +| passwords.go:47:14:47:17 | obj3 | passwords.go:48:11:48:18 | password : string | passwords.go:47:14:47:17 | obj3 | Sensitive data returned by $@ is logged here. | passwords.go:48:11:48:18 | password | an access to password | +| passwords.go:51:14:51:27 | fixed_password | passwords.go:51:14:51:27 | fixed_password | passwords.go:51:14:51:27 | fixed_password | Sensitive data returned by $@ is logged here. | passwords.go:51:14:51:27 | fixed_password | an access to fixed_password | +| passwords.go:88:14:88:26 | utilityObject | passwords.go:85:19:87:2 | composite literal : passSetStruct | passwords.go:88:14:88:26 | utilityObject | Sensitive data returned by $@ is logged here. | passwords.go:85:19:87:2 | composite literal | an access to passwordSet | +| passwords.go:91:23:91:28 | secret | passwords.go:90:12:90:19 | password : string | passwords.go:91:23:91:28 | secret | Sensitive data returned by $@ is logged here. | passwords.go:90:12:90:19 | password | an access to password | +| passwords.go:101:15:101:40 | ...+... | passwords.go:101:33:101:40 | password : string | passwords.go:101:15:101:40 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:101:33:101:40 | password | an access to password | +| passwords.go:107:16:107:41 | ...+... | passwords.go:107:34:107:41 | password : string | passwords.go:107:16:107:41 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:107:34:107:41 | password | an access to password | +| passwords.go:112:15:112:40 | ...+... | passwords.go:112:33:112:40 | password : string | passwords.go:112:15:112:40 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:112:33:112:40 | password | an access to password | +| passwords.go:116:14:116:45 | ...+... | passwords.go:116:28:116:36 | password1 : stringable | passwords.go:116:14:116:45 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:116:28:116:36 | password1 | an access to password1 | +| passwords.go:125:14:125:19 | config | passwords.go:118:12:123:2 | composite literal : Config | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:118:12:123:2 | composite literal | an access to password | +| passwords.go:125:14:125:19 | config | passwords.go:121:13:121:20 | password : string | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:121:13:121:20 | password | an access to password | +| passwords.go:125:14:125:19 | config | passwords.go:122:13:122:25 | call to getPassword : string | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:122:13:122:25 | call to getPassword | a call to getPassword | +| passwords.go:126:14:126:21 | selection of x | passwords.go:121:13:121:20 | password : string | passwords.go:126:14:126:21 | selection of x | Sensitive data returned by $@ is logged here. | passwords.go:121:13:121:20 | password | an access to password | +| passwords.go:127:14:127:21 | selection of y | passwords.go:122:13:122:25 | call to getPassword : string | passwords.go:127:14:127:21 | selection of y | Sensitive data returned by $@ is logged here. | passwords.go:122:13:122:25 | call to getPassword | a call to getPassword | diff --git a/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.expected b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.expected index d2b8710bec8..61b5890e01b 100644 --- a/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.expected +++ b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.expected @@ -1,16 +1,31 @@ edges -| OpenUrlRedirect.go:10:23:10:28 | selection of Form [Values] | OpenUrlRedirect.go:10:23:10:42 | call to Get | -| stdlib.go:12:13:12:18 | selection of Form [Values] | stdlib.go:14:30:14:35 | target | -| stdlib.go:21:13:21:18 | selection of Form [Values] | stdlib.go:23:30:23:35 | target | -| stdlib.go:30:13:30:18 | selection of Form [Values] | stdlib.go:34:30:34:39 | ...+... | -| stdlib.go:43:13:43:18 | selection of Form [Values] | stdlib.go:45:23:45:28 | target | -| stdlib.go:63:13:63:18 | selection of Form [Values] | stdlib.go:66:23:66:40 | ...+... | -| stdlib.go:88:13:88:18 | selection of Form [Values] | stdlib.go:91:23:91:28 | target | +| OpenUrlRedirect.go:10:23:10:28 | selection of Form : Values | OpenUrlRedirect.go:10:23:10:42 | call to Get | +| stdlib.go:12:13:12:18 | selection of Form : Values | stdlib.go:14:30:14:35 | target | +| stdlib.go:21:13:21:18 | selection of Form : Values | stdlib.go:23:30:23:35 | target | +| stdlib.go:30:13:30:18 | selection of Form : Values | stdlib.go:34:30:34:39 | ...+... | +| stdlib.go:43:13:43:18 | selection of Form : Values | stdlib.go:45:23:45:28 | target | +| stdlib.go:63:13:63:18 | selection of Form : Values | stdlib.go:66:23:66:40 | ...+... | +| stdlib.go:88:13:88:18 | selection of Form : Values | stdlib.go:91:23:91:28 | target | +nodes +| OpenUrlRedirect.go:10:23:10:28 | selection of Form : Values | semmle.label | selection of Form : Values | +| OpenUrlRedirect.go:10:23:10:42 | call to Get | semmle.label | call to Get | +| stdlib.go:12:13:12:18 | selection of Form : Values | semmle.label | selection of Form : Values | +| stdlib.go:14:30:14:35 | target | semmle.label | target | +| stdlib.go:21:13:21:18 | selection of Form : Values | semmle.label | selection of Form : Values | +| stdlib.go:23:30:23:35 | target | semmle.label | target | +| stdlib.go:30:13:30:18 | selection of Form : Values | semmle.label | selection of Form : Values | +| stdlib.go:34:30:34:39 | ...+... | semmle.label | ...+... | +| stdlib.go:43:13:43:18 | selection of Form : Values | semmle.label | selection of Form : Values | +| stdlib.go:45:23:45:28 | target | semmle.label | target | +| stdlib.go:63:13:63:18 | selection of Form : Values | semmle.label | selection of Form : Values | +| stdlib.go:66:23:66:40 | ...+... | semmle.label | ...+... | +| stdlib.go:88:13:88:18 | selection of Form : Values | semmle.label | selection of Form : Values | +| stdlib.go:91:23:91:28 | target | semmle.label | target | #select -| OpenUrlRedirect.go:10:23:10:42 | call to Get | OpenUrlRedirect.go:10:23:10:28 | selection of Form [Values] | OpenUrlRedirect.go:10:23:10:42 | call to Get | Untrusted URL redirection due to $@. | OpenUrlRedirect.go:10:23:10:28 | selection of Form | user-provided value | -| stdlib.go:14:30:14:35 | target | stdlib.go:12:13:12:18 | selection of Form [Values] | stdlib.go:14:30:14:35 | target | Untrusted URL redirection due to $@. | stdlib.go:12:13:12:18 | selection of Form | user-provided value | -| stdlib.go:23:30:23:35 | target | stdlib.go:21:13:21:18 | selection of Form [Values] | stdlib.go:23:30:23:35 | target | Untrusted URL redirection due to $@. | stdlib.go:21:13:21:18 | selection of Form | user-provided value | -| stdlib.go:34:30:34:39 | ...+... | stdlib.go:30:13:30:18 | selection of Form [Values] | stdlib.go:34:30:34:39 | ...+... | Untrusted URL redirection due to $@. | stdlib.go:30:13:30:18 | selection of Form | user-provided value | -| stdlib.go:45:23:45:28 | target | stdlib.go:43:13:43:18 | selection of Form [Values] | stdlib.go:45:23:45:28 | target | Untrusted URL redirection due to $@. | stdlib.go:43:13:43:18 | selection of Form | user-provided value | -| stdlib.go:66:23:66:40 | ...+... | stdlib.go:63:13:63:18 | selection of Form [Values] | stdlib.go:66:23:66:40 | ...+... | Untrusted URL redirection due to $@. | stdlib.go:63:13:63:18 | selection of Form | user-provided value | -| stdlib.go:91:23:91:28 | target | stdlib.go:88:13:88:18 | selection of Form [Values] | stdlib.go:91:23:91:28 | target | Untrusted URL redirection due to $@. | stdlib.go:88:13:88:18 | selection of Form | user-provided value | +| OpenUrlRedirect.go:10:23:10:42 | call to Get | OpenUrlRedirect.go:10:23:10:28 | selection of Form : Values | OpenUrlRedirect.go:10:23:10:42 | call to Get | Untrusted URL redirection due to $@. | OpenUrlRedirect.go:10:23:10:28 | selection of Form | user-provided value | +| stdlib.go:14:30:14:35 | target | stdlib.go:12:13:12:18 | selection of Form : Values | stdlib.go:14:30:14:35 | target | Untrusted URL redirection due to $@. | stdlib.go:12:13:12:18 | selection of Form | user-provided value | +| stdlib.go:23:30:23:35 | target | stdlib.go:21:13:21:18 | selection of Form : Values | stdlib.go:23:30:23:35 | target | Untrusted URL redirection due to $@. | stdlib.go:21:13:21:18 | selection of Form | user-provided value | +| stdlib.go:34:30:34:39 | ...+... | stdlib.go:30:13:30:18 | selection of Form : Values | stdlib.go:34:30:34:39 | ...+... | Untrusted URL redirection due to $@. | stdlib.go:30:13:30:18 | selection of Form | user-provided value | +| stdlib.go:45:23:45:28 | target | stdlib.go:43:13:43:18 | selection of Form : Values | stdlib.go:45:23:45:28 | target | Untrusted URL redirection due to $@. | stdlib.go:43:13:43:18 | selection of Form | user-provided value | +| stdlib.go:66:23:66:40 | ...+... | stdlib.go:63:13:63:18 | selection of Form : Values | stdlib.go:66:23:66:40 | ...+... | Untrusted URL redirection due to $@. | stdlib.go:63:13:63:18 | selection of Form | user-provided value | +| stdlib.go:91:23:91:28 | target | stdlib.go:88:13:88:18 | selection of Form : Values | stdlib.go:91:23:91:28 | target | Untrusted URL redirection due to $@. | stdlib.go:88:13:88:18 | selection of Form | user-provided value |