diff --git a/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll b/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll index 23a7b96826549..2e5d1b9e09fd5 100644 --- a/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll +++ b/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll @@ -2572,23 +2572,25 @@ module MakeImpl Lang> { pragma[nomagic] private predicate revFlowThroughArg( - DataFlowCall call, ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, - Ap ap + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, FlowState state, ReturnCtx returnCtx, + ApOption returnAp, Ap ap ) { - exists(ParamNodeEx p, Ap innerReturnAp | + exists(Ap innerReturnAp | revFlowThrough(call, returnCtx, p, state, _, returnAp, ap, innerReturnAp) and flowThroughIntoCall(call, arg, p, _, ap, innerReturnAp) ) } pragma[nomagic] - predicate callMayFlowThroughRev(DataFlowCall call) { + additional predicate callMayFlowThroughRev(DataFlowCall call, ParamNodeEx p) { exists(ArgNodeEx arg, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap | revFlow(arg, state, returnCtx, returnAp, ap) and - revFlowThroughArg(call, arg, state, returnCtx, returnAp, ap) + revFlowThroughArg(call, arg, p, state, returnCtx, returnAp, ap) ) } + predicate callMayFlowThroughRev(DataFlowCall call) { callMayFlowThroughRev(call, _) } + predicate callEdgeArgParam( DataFlowCall call, DataFlowCallable c, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Ap ap @@ -3856,24 +3858,68 @@ module MakeImpl Lang> { private module StoreReadMatchingInput implements StoreReadMatchingInputSig { class NodeEx = NodeExAlias; - predicate nodeRange(NodeEx node, boolean fromArg) { - exists(PrevStage::Ap ap | - PrevStage::revFlowAp(node, ap) and + /** + * Gets a call context for `node` that is relevant for either improved virtual + * dispatch or for flow-through. + */ + pragma[nomagic] + private DataFlowCallOption getACallCtx(NodeEx node, PrevStage::Ap ap, boolean fromArg) { + exists(PrevStage::Cc cc, ParamNodeOption summaryCtx, PrevStage::ApOption argAp | + PrevStage::fwdFlow(node, _, cc, summaryCtx, _, argAp, _, ap, _) + | + PrevStage::instanceofCcCall(cc) and + fromArg = true and ( - ap = true - or - PrevStage::storeStepCand(node, ap, _, _, _, _) + // virtual dispatch may be improved + exists(DataFlowCall call, DataFlowCallable c | + PrevStage::callEdgeArgParam(call, c, _, _, _, _) and + cc = Stage2Param::getSpecificCallContextCall(call, c) and + c = node.getEnclosingCallable() and + result = TDataFlowCallSome(call) + ) or - PrevStage::readStepCand(_, _, node) + not cc = Stage2Param::getSpecificCallContextCall(_, _) and + ( + // flow-through not possible + summaryCtx instanceof TParamNodeNone and + result = TDataFlowCallNone() + or + exists(DataFlowCall call, ParamNodeEx p, PrevStage::Ap argApSome | + summaryCtx = TParamNodeSome(p.asNode()) and + argAp = PrevStage::apSome(argApSome) + | + if + PrevStage::parameterMayFlowThrough(p, argApSome) and + PrevStage::callMayFlowThroughRev(call, p) + then + // flow-through possible + result = TDataFlowCallSome(call) + else ( + // flow-through not possible, but `node` can reach a sink without + // flowing back out + PrevStage::callEdgeArgParam(call, _, _, p, _, _) and + result = TDataFlowCallNone() + ) + ) + ) ) + or + PrevStage::instanceofCcNoCall(cc) and + fromArg = false and + result = TDataFlowCallNone() + ) + } + + predicate nodeRange(NodeEx node, boolean fromArg, DataFlowCallOption summaryCtx) { + exists(PrevStage::Ap ap | + PrevStage::revFlowAp(node, ap) and + summaryCtx = getACallCtx(node, ap, fromArg) | - exists(PrevStage::Cc cc | PrevStage::fwdFlow(node, _, cc, _, _, _, _, ap, _) | - PrevStage::instanceofCcCall(cc) and - fromArg = true - or - PrevStage::instanceofCcNoCall(cc) and - fromArg = false - ) + ap = true + or + PrevStage::storeStepCand(node, ap, _, _, _, _) + or + PrevStage::readStepCand(_, _, node) ) } @@ -3899,12 +3945,51 @@ module MakeImpl Lang> { ) } - predicate callEdgeArgParam(NodeEx arg, NodeEx param) { - PrevStage::callEdgeArgParam(_, _, arg, param, true, true) + pragma[nomagic] + private predicate callEdgeArgParam( + DataFlowCall call, DataFlowCallable c, NodeEx arg, NodeEx param, + DataFlowCallOption innerCallCtx + ) { + PrevStage::callEdgeArgParam(call, c, arg, param, true, true) and + innerCallCtx = getACallCtx(param, true, true) and + ( + innerCallCtx = TDataFlowCallNone() + or + innerCallCtx = TDataFlowCallSome(call) + ) + } + + pragma[nomagic] + private predicate callEdgeArgParamCallContextReduced( + DataFlowCall call, NodeEx arg, NodeEx param, Stage2Param::CcCall outerCcCall, + DataFlowCallOption innerCallCtx + ) { + exists(DataFlowCallable c | + callEdgeArgParam(call, c, arg, param, innerCallCtx) and + Stage2Param::viableImplCallContextReduced(call, outerCcCall) = c + ) + } + + bindingset[outerCallCtx] + predicate callEdgeArgParam( + NodeEx arg, NodeEx param, DataFlowCallOption outerCallCtx, DataFlowCallOption innerCallCtx + ) { + exists(DataFlowCall call | callEdgeArgParam(call, _, arg, param, innerCallCtx) | + outerCallCtx = TDataFlowCallNone() + or + exists(DataFlowCall outerCall, Stage2Param::CcCall outerCcCall | + outerCallCtx = TDataFlowCallSome(outerCall) and + outerCcCall = Stage2Param::getCallContextCall(outerCall, call.getEnclosingCallable()) + | + callEdgeArgParamCallContextReduced(call, arg, param, outerCcCall, innerCallCtx) + or + Stage2Param::viableImplNotCallContextReduced(call, outerCcCall) + ) + ) } - predicate callEdgeReturn(NodeEx ret, NodeEx out, boolean mayFlowThrough) { - PrevStage::callEdgeReturn(_, _, ret, _, out, true, true) and + predicate callEdgeReturn(DataFlowCall call, NodeEx ret, NodeEx out, boolean mayFlowThrough) { + PrevStage::callEdgeReturn(call, _, ret, _, out, true, true) and if flowThroughOutOfCall(ret, out) then mayFlowThrough = true else mayFlowThrough = false } diff --git a/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll b/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll index 8e64530799321..a78af4a815353 100644 --- a/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll +++ b/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll @@ -2358,15 +2358,18 @@ module MakeImplCommon Lang> { string toString(); } - predicate nodeRange(NodeEx node, boolean fromArg); + predicate nodeRange(NodeEx node, boolean fromArg, DataFlowCallOption callCtx); predicate localValueStep(NodeEx node1, NodeEx node2); predicate jumpValueStep(NodeEx node1, NodeEx node2); - predicate callEdgeArgParam(NodeEx arg, NodeEx param); + bindingset[outerCallCtx] + predicate callEdgeArgParam( + NodeEx arg, NodeEx param, DataFlowCallOption outerCallCtx, DataFlowCallOption innerCallCtx + ); - predicate callEdgeReturn(NodeEx ret, NodeEx out, boolean mayFlowThrough); + predicate callEdgeReturn(DataFlowCall call, NodeEx ret, NodeEx out, boolean mayFlowThrough); predicate readContentStep(NodeEx node1, Content c, NodeEx node2); @@ -2380,7 +2383,7 @@ module MakeImplCommon Lang> { * * In order to determine whether a store can be matched with a compatible read, we * check whether the target of a store may reach the source of a read, using over- - * approximated data flow (no call contexts). + * approximated data flow (depth 1 call contexts only). * * The implementation is based on `doublyBoundedFastTC`, and in order to avoid poor * performance through recursion, we unroll the recursion manually 4 times, in order to @@ -2401,22 +2404,28 @@ module MakeImplCommon Lang> { int iteration(); predicate storeMayReachReadDelta( - NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2 + NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2, + DataFlowCallOption callCtx1, DataFlowCallOption callCtx2 ); predicate storeMayReachReadPrev( - NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2 + NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2, + DataFlowCallOption callCtx1, DataFlowCallOption callCtx2 ); } private newtype TNodeOrContent = - TNodeOrContentNode(NodeEx n, Boolean usesPrevDelta, boolean fromArg) { nodeRange(n, fromArg) } or + TNodeOrContentNode( + NodeEx n, Boolean usesPrevDelta, boolean fromArg, DataFlowCallOption callCtx + ) { + nodeRange(n, fromArg, callCtx) + } or TNodeOrContentStoreContent(Content c) { storeContentStep(_, c, _) } or TNodeOrContentReadContent(Content c) { readContentStep(_, c, _) } private class NodeOrContent extends TNodeOrContent { - NodeEx asNodeEx(boolean usesPrevDelta, boolean fromArg) { - this = TNodeOrContentNode(result, usesPrevDelta, fromArg) + NodeEx asNodeEx(boolean usesPrevDelta, boolean fromArg, DataFlowCallOption callCtx) { + this = TNodeOrContentNode(result, usesPrevDelta, fromArg, callCtx) } Content asStoreContent() { this = TNodeOrContentStoreContent(result) } @@ -2428,29 +2437,36 @@ module MakeImplCommon Lang> { or result = this.asReadContent().toString() or - result = this.asNodeEx(_, _).toString() + result = this.asNodeEx(_, _, _).toString() } } pragma[nomagic] private predicate stepNodeCommon( - NodeOrContent node1, NodeEx n2, boolean usesPrevDelta2, Boolean fromArg2 + NodeOrContent node1, NodeEx n2, boolean usesPrevDelta2, Boolean fromArg2, + DataFlowCallOption callCtx2 ) { - exists(NodeEx n1, boolean fromArg1 | n1 = node1.asNodeEx(usesPrevDelta2, fromArg1) | + exists(NodeEx n1, boolean fromArg1, DataFlowCallOption callCtx1 | + n1 = node1.asNodeEx(usesPrevDelta2, fromArg1, callCtx1) + | localValueStep(n1, n2) and - fromArg1 = fromArg2 + fromArg1 = fromArg2 and + callCtx1 = callCtx2 or jumpValueStep(n1, n2) and - fromArg2 = false + fromArg2 = false and + callCtx2 = TDataFlowCallNone() or - callEdgeArgParam(n1, n2) and + callEdgeArgParam(n1, n2, callCtx1, callCtx2) and fromArg2 = true or - exists(boolean mayFlowThrough | - callEdgeReturn(n1, n2, mayFlowThrough) and - nodeRange(n2, fromArg2) + exists(DataFlowCall call, boolean mayFlowThrough | + callEdgeReturn(call, n1, n2, mayFlowThrough) and + nodeRange(n2, fromArg2, callCtx2) // depth 1 call contexts only | - fromArg1 = false or mayFlowThrough = true + fromArg1 = false + or + callCtx1 = TDataFlowCallSome(call) and mayFlowThrough = true ) ) } @@ -2469,19 +2485,20 @@ module MakeImplCommon Lang> { pragma[nomagic] private predicate stepNode( - NodeOrContent node1, NodeEx n2, boolean usesPrevDelta2, Boolean fromArg2 + NodeOrContent node1, NodeEx n2, boolean usesPrevDelta2, Boolean fromArg2, + DataFlowCallOption callCtx2 ) { enabled() and ( - stepNodeCommon(node1, n2, usesPrevDelta2, fromArg2) + stepNodeCommon(node1, n2, usesPrevDelta2, fromArg2, callCtx2) or - exists(NodeEx n1, boolean usesPrevDelta1, boolean fromArg1 | - n1 = node1.asNodeEx(usesPrevDelta1, fromArg1) + exists(NodeEx n1, boolean usesPrevDelta1, boolean fromArg1, DataFlowCallOption callCtx1 | + n1 = node1.asNodeEx(usesPrevDelta1, fromArg1, callCtx1) | - Prev::storeMayReachReadDelta(n1, _, n2, fromArg1, fromArg2) and + Prev::storeMayReachReadDelta(n1, _, n2, fromArg1, fromArg2, callCtx1, callCtx2) and usesPrevDelta2 = true or - Prev::storeMayReachReadPrev(n1, _, n2, fromArg1, fromArg2) and + Prev::storeMayReachReadPrev(n1, _, n2, fromArg1, fromArg2, callCtx1, callCtx2) and usesPrevDelta1 = usesPrevDelta2 ) ) @@ -2489,22 +2506,22 @@ module MakeImplCommon Lang> { pragma[nomagic] private predicate step(NodeOrContent node1, NodeOrContent node2) { - exists(NodeEx n2, boolean usesPrevDelta2, boolean fromArg2 | - n2 = node2.asNodeEx(usesPrevDelta2, fromArg2) and - stepNode(node1, n2, usesPrevDelta2, fromArg2) + exists(NodeEx n2, boolean usesPrevDelta2, boolean fromArg2, DataFlowCallOption callCtx2 | + n2 = node2.asNodeEx(usesPrevDelta2, fromArg2, callCtx2) and + stepNode(node1, n2, usesPrevDelta2, fromArg2, callCtx2) ) or enabled() and ( exists(NodeEx n2, Content c, boolean usesPrevDelta2 | - n2 = node2.asNodeEx(usesPrevDelta2, _) and + n2 = node2.asNodeEx(usesPrevDelta2, _, _) and c = node1.asStoreContent() and storeContentStep(_, c, n2) and usesPrevDelta2 = false ) or exists(NodeEx n1, Content c, boolean usesPrevDelta1 | - n1 = node1.asNodeEx(usesPrevDelta1, _) and + n1 = node1.asNodeEx(usesPrevDelta1, _, _) and c = node2.asReadContent() and readContentStep(n1, c, _) and usesPrevDelta(usesPrevDelta1) @@ -2527,12 +2544,12 @@ module MakeImplCommon Lang> { pragma[nomagic] private predicate contentIsReadAndStoredJoin(NodeOrContent c1, NodeOrContent c2, Content c) { + enabled() and c1.asStoreContent() = c and c2.asReadContent() = c } additional predicate contentIsReadAndStored(Content c) { - enabled() and exists(NodeOrContent n1, NodeOrContent n2 | contentReachesReadTc(n1, n2) and contentIsReadAndStoredJoin(n1, n2, c) @@ -2543,7 +2560,7 @@ module MakeImplCommon Lang> { private predicate isStoreTarget0(NodeOrContent node, Content c) { exists(boolean usesPrevDelta | contentIsReadAndStored(c) and - storeContentStep(_, c, node.asNodeEx(usesPrevDelta, _)) and + storeContentStep(_, c, node.asNodeEx(usesPrevDelta, _, _)) and usesPrevDelta = false ) } @@ -2554,7 +2571,7 @@ module MakeImplCommon Lang> { private predicate isReadSource0(NodeOrContent node, Content c) { exists(boolean usesPrevDelta | contentIsReadAndStored(c) and - readContentStep(node.asNodeEx(usesPrevDelta, _), c, _) and + readContentStep(node.asNodeEx(usesPrevDelta, _, _), c, _) and usesPrevDelta(usesPrevDelta) ) } @@ -2602,45 +2619,50 @@ module MakeImplCommon Lang> { pragma[nomagic] private predicate storeMayReachReadDeltaJoinLeft( - NodeEx node1, Content c, NodeOrContent node2, boolean fromArg + NodeEx node1, Content c, NodeOrContent node2, boolean fromArg2, DataFlowCallOption callCtx2 ) { exists(boolean usesPrevDelta | storeMayReachARead(pragma[only_bind_into](node2), pragma[only_bind_into](c)) and - storeContentStep(node1, c, node2.asNodeEx(usesPrevDelta, fromArg)) and + storeContentStep(node1, c, node2.asNodeEx(usesPrevDelta, fromArg2, callCtx2)) and usesPrevDelta = false ) } pragma[nomagic] private predicate storeMayReachReadDeltaJoinRight( - NodeOrContent node1, Content c, NodeEx node2, boolean fromArg + NodeOrContent node1, Content c, NodeEx node2, boolean fromArg1, DataFlowCallOption callCtx1 ) { exists(boolean usesPrevDelta | aStoreMayReachRead(pragma[only_bind_into](node1), pragma[only_bind_into](c)) and - readContentStep(node1.asNodeEx(usesPrevDelta, fromArg), c, node2) and + readContentStep(node1.asNodeEx(usesPrevDelta, fromArg1, callCtx1), c, node2) and usesPrevDelta(usesPrevDelta) ) } pragma[nomagic] predicate storeMayReachReadDelta( - NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2 + NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2, + DataFlowCallOption callCtx1, DataFlowCallOption callCtx2 ) { exists(NodeOrContent storeTarget, NodeOrContent readSource | storeMayReachReadTc(storeTarget, readSource) and - storeMayReachReadDeltaJoinLeft(storeSource, c, storeTarget, fromArg1) and - storeMayReachReadDeltaJoinRight(readSource, c, readTarget, fromArg2) + storeMayReachReadDeltaJoinLeft(storeSource, c, storeTarget, fromArg1, callCtx1) and + storeMayReachReadDeltaJoinRight(readSource, c, readTarget, fromArg2, callCtx2) ) and - not Prev::storeMayReachReadPrev(storeSource, c, readTarget, fromArg1, fromArg2) + not Prev::storeMayReachReadPrev(storeSource, c, readTarget, fromArg1, fromArg2, callCtx1, + callCtx2) } pragma[nomagic] predicate storeMayReachReadPrev( - NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2 + NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2, + DataFlowCallOption callCtx1, DataFlowCallOption callCtx2 ) { - Prev::storeMayReachReadPrev(storeSource, c, readTarget, fromArg1, fromArg2) + Prev::storeMayReachReadPrev(storeSource, c, readTarget, fromArg1, fromArg2, callCtx1, + callCtx2) or - Prev::storeMayReachReadDelta(storeSource, c, readTarget, fromArg1, fromArg2) + Prev::storeMayReachReadDelta(storeSource, c, readTarget, fromArg1, fromArg2, callCtx1, + callCtx2) } } @@ -2648,13 +2670,15 @@ module MakeImplCommon Lang> { int iteration() { result = 0 } predicate storeMayReachReadDelta( - NodeEx node1, Content c, NodeEx node2, boolean fromArg1, boolean fromArg2 + NodeEx node1, Content c, NodeEx node2, boolean fromArg1, boolean fromArg2, + DataFlowCallOption callCtx1, DataFlowCallOption callCtx2 ) { none() } predicate storeMayReachReadPrev( - NodeEx node1, Content c, NodeEx node2, boolean fromArg1, boolean fromArg2 + NodeEx node1, Content c, NodeEx node2, boolean fromArg1, boolean fromArg2, + DataFlowCallOption callCtx1, DataFlowCallOption callCtx2 ) { none() } @@ -2666,9 +2690,10 @@ module MakeImplCommon Lang> { import M predicate storeMayReachReadDelta( - NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2 + NodeEx storeSource, Content c, NodeEx readTarget, boolean fromArg1, boolean fromArg2, + DataFlowCallOption callCtx1, DataFlowCallOption callCtx2 ) { - M::storeMayReachReadDelta(storeSource, c, readTarget, fromArg1, fromArg2) + M::storeMayReachReadDelta(storeSource, c, readTarget, fromArg1, fromArg2, callCtx1, callCtx2) or // special case only needed for the first iteration: a store immediately followed by a read exists(NodeEx storeTargetReadSource | @@ -2676,9 +2701,11 @@ module MakeImplCommon Lang> { storeContentStep(storeSource, c, storeTargetReadSource) and readContentStep(storeTargetReadSource, c, readTarget) ) and - nodeRange(storeSource, fromArg1) and - nodeRange(readTarget, fromArg2) and - fromArg1 = fromArg2 + nodeRange(storeSource, fromArg1, callCtx1) and + nodeRange(readTarget, fromArg2, callCtx2) and + fromArg1 = fromArg2 and + pragma[only_bind_into](pragma[only_bind_out](callCtx1)) = + pragma[only_bind_into](pragma[only_bind_out](callCtx2)) } } @@ -2691,9 +2718,9 @@ module MakeImplCommon Lang> { private module StoreReachesRead5 = StoreReachesRead; predicate storeMayReachRead(NodeEx storeSource, Content c, NodeEx readTarget) { - StoreReachesRead5::storeMayReachReadDelta(storeSource, c, readTarget, _, _) + StoreReachesRead5::storeMayReachReadDelta(storeSource, c, readTarget, _, _, _, _) or - StoreReachesRead5::storeMayReachReadPrev(storeSource, c, readTarget, _, _) + StoreReachesRead5::storeMayReachReadPrev(storeSource, c, readTarget, _, _, _, _) } } }