diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll index 39a8d1e159ad..b14979470b04 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll @@ -255,27 +255,9 @@ class DataFlowCall extends Expr instanceof Call { */ Expr getArgument(int n) { result = super.getArgument(n) } - /** Gets an argument to this call. */ - Expr getAnArgument(){ result = super.getAnArgument() } - - /** Gets an argument to this call as a Node. */ - ArgumentNode getAnArgumentNode(){ result = this.getNode() } - /** Gets the data flow node corresponding to this call. */ ExprNode getNode() { result.getExpr() = this } - /** Gets the data flow node corresponding to this call. (Alias of `getNode()`) */ - ExprNode getDataFlowNode() { result = this.getNode() } - - /** Gets the target of the call, as best as makes sense for this kind of call. - * - * The precise meaning depends on the kind of call it is: - * - For a call to a function, it’s the function being called. - * - For a C++ method call, it’s the statically resolved method. - * - For an Objective C message expression, it’s the statically resolved method, and it might not exist. - * - For a variable call, it never exists. - */ - DataFlowCallable getARuntimeTarget(){ result = super.getTarget() } /** Gets the enclosing callable of this call. */ DataFlowCallable getEnclosingCallable() { result = this.getEnclosingFunction() } } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index eebfc1154ca6..40740d956dcd 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -1058,16 +1058,6 @@ class DataFlowCallable extends TDataFlowCallable { result = this.asSummarizedCallable() or // SummarizedCallable = Function (in CPP) result = this.asSourceCallable() } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCallable c, string file, int startline, int startcolumn | - c.getLocation().hasLocationInfo(file, startline, startcolumn, _, _) - | - c order by file, startline, startcolumn - ) - } } /** @@ -1169,23 +1159,6 @@ class DataFlowCall extends TDataFlowCall { * Gets the location of this call. */ Location getLocation() { none() } - - // #43: Stub Implementation - /** Gets an argument to this call as a Node. */ - ArgumentNode getAnArgumentNode(){ none() } // TODO: JB1 return an argument as a DataFlow ArgumentNode - - // #43: Stub Implementation - /** Gets the target of the call, as a DataFlowCallable. */ - DataFlowCallable getARuntimeTarget(){ none() } // TODO getCallTarget() returns `Instruction` - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCall c, int startline, int startcolumn | - c.getLocation().hasLocationInfo(_, startline, startcolumn, _, _) - | - c order by startline, startcolumn - ) - } } /** @@ -1280,15 +1253,6 @@ module IsUnreachableInCall { string toString() { result = "NodeRegion" } predicate contains(Node n) { this = n.getBasicBlock() } - - int totalOrder() { - this = - rank[result](IRBlock b, int startline, int startcolumn | - b.getLocation().hasLocationInfo(_, startline, startcolumn, _, _) - | - b order by startline, startcolumn - ) - } } predicate isUnreachableInCall(NodeRegion block, DataFlowCall call) { diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlowStack.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlowStack.qll index 21e353abb715..0cbca2e92ff5 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlowStack.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlowStack.qll @@ -1,15 +1,36 @@ - import csharp private import codeql.dataflow.DataFlow private import semmle.code.csharp.dataflow.internal.DataFlowImplSpecific - private import codeql.dataflowstack.DataFlowStack as DFS private import DFS::DataFlowStackMake as DataFlowStackFactory -module DataFlowStackMake{ - import DataFlowStackFactory::FlowStack +private module DataFlowStackInput implements + DFS::DataFlowStackSig +{ + private module Flow = DataFlow::Global; + + CsharpDataFlow::Node getNode(Flow::PathNode n) { result = n.getNode() } + + predicate isSource(Flow::PathNode n) { n.isSource() } + + Flow::PathNode getASuccessor(Flow::PathNode n) { result = n.getASuccessor() } + + CsharpDataFlow::DataFlowCallable getARuntimeTarget(CsharpDataFlow::DataFlowCall call) { + result = call.getARuntimeTarget() + } + + CsharpDataFlow::Node getAnArgumentNode(CsharpDataFlow::DataFlowCall call) { + result = call.getArgument(_) + } } -module BiStackAnalysisMake{ - import DataFlowStackFactory::BiStackAnalysis -} \ No newline at end of file +module DataFlowStackMake { + import DataFlowStackFactory::FlowStack> +} + +module BiStackAnalysisMake< + DataFlowStackFactory::DataFlow::ConfigSig ConfigA, + DataFlowStackFactory::DataFlow::ConfigSig ConfigB> +{ + import DataFlowStackFactory::BiStackAnalysis, ConfigB, DataFlowStackInput> +} diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll index d65def04f853..e17bff759159 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll @@ -288,16 +288,6 @@ class DataFlowCallable extends TDataFlowCallable { or result = this.asCapturedVariable().getLocation() } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCallable c, string file, int startline, int startcolumn | - c.getLocation().hasLocationInfo(file, startline, startcolumn, _, _) - | - c order by file, startline, startcolumn - ) - } } /** A call relevant for data flow. */ @@ -323,9 +313,6 @@ abstract class DataFlowCall extends TDataFlowCall { /** Gets the argument at position `pos` of this call. */ final ArgumentNode getArgument(ArgumentPosition pos) { result.argumentOf(this, pos) } - /** Gets an argument of this call. */ - final ArgumentNode getAnArgumentNode() { result.argumentOf(this, _) } - /** Gets a textual representation of this call. */ abstract string toString(); @@ -344,16 +331,6 @@ abstract class DataFlowCall extends TDataFlowCall { ) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCall c, int startline, int startcolumn | - c.hasLocationInfo(_, startline, startcolumn, _, _) - | - c order by startline, startcolumn - ) - } } private predicate relevantFolder(Folder f) { diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll index 26e7a9e376e1..2fcbf2d350f2 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll @@ -315,16 +315,6 @@ class DataFlowCallable extends TDataFlowCallable { result = this.asFileScope().getLocation() or result = getCallableLocation(this.asSummarizedCallable()) } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCallable c, string file, int startline, int startcolumn | - c.hasLocationInfo(file, startline, startcolumn, _, _) - | - c order by file, startline, startcolumn - ) - } } private Location getCallableLocation(Callable c) { @@ -358,23 +348,6 @@ class DataFlowCall extends Expr { /** Gets the location of this call. */ Location getLocation() { result = super.getLocation() } - - // #45 - Stub Implementation - /** Gets an argument to this call as a Node. */ - ArgumentNode getAnArgumentNode(){ result = this.getArgument(_) } - - /** Gets the target of the call, as a DataFlowCallable. */ - DataFlowCallable getARuntimeTarget(){ result.asCallable() = call.getACalleeIncludingExternals() } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCall c, int startline, int startcolumn | - c.getLocation().hasLocationInfo(_, startline, startcolumn, _, _) - | - c order by startline, startcolumn - ) - } } /** Holds if `e` is an expression that always has the same Boolean value `val`. */ @@ -417,15 +390,6 @@ class NodeRegion instanceof BasicBlock { string toString() { result = "NodeRegion" } predicate contains(Node n) { n.getBasicBlock() = this } - - int totalOrder() { - this = - rank[result](BasicBlock b, int startline, int startcolumn | - b.hasLocationInfo(_, startline, startcolumn, _, _) - | - b order by startline, startcolumn - ) - } } /** diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index 961fe586b032..704e5714784c 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -423,21 +423,6 @@ predicate cloneStep(Node n1, Node n2) { bindingset[node1, node2] predicate validParameterAliasStep(Node node1, Node node2) { not cloneStep(node1, node2) } -private predicate id_member(Member x, Member y) { x = y } - -private predicate idOf_member(Member x, int y) = equivalenceRelation(id_member/2)(x, y) - -private int summarizedCallableId(SummarizedCallable c) { - c = - rank[result](SummarizedCallable c0, int b, int i, string s | - b = 0 and idOf_member(c0.asCallable(), i) and s = "" - or - b = 1 and i = 0 and s = c0.asSyntheticCallable() - | - c0 order by b, i, s - ) -} - private newtype TDataFlowCallable = TSrcCallable(Callable c) or TSummarizedCallable(SummarizedCallable c) or @@ -471,28 +456,10 @@ class DataFlowCallable extends TDataFlowCallable { result = this.asSummarizedCallable().getLocation() or result = this.asFieldScope().getLocation() } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCallable c, int b, int i | - b = 0 and idOf_member(c.asCallable(), i) - or - b = 1 and i = summarizedCallableId(c.asSummarizedCallable()) - or - b = 2 and idOf_member(c.asFieldScope(), i) - | - c order by b, i - ) - } } class DataFlowExpr = Expr; -private predicate id_call(Call x, Call y) { x = y } - -private predicate idOf_call(Call x, int y) = equivalenceRelation(id_call/2)(x, y) - private newtype TDataFlowCall = TCall(Call c) or TSummaryCall(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver) { @@ -525,29 +492,6 @@ class DataFlowCall extends TDataFlowCall { ) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } - - /** Gets an argument to this call as a Node. */ - ArgumentNode getAnArgumentNode(){ - result = exprNode(this.asCall().getAnArgument()) - } - - /** Gets the target of the call, as a DataFlowCallable. */ - DataFlowCallable getARuntimeTarget(){ - result.asCallable() = this.asCall().getCallee() - } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCall c, int b, int i | - b = 0 and idOf_call(c.asCall(), i) - or - b = 1 and // not guaranteed to be total - exists(SummarizedCallable sc | c = TSummaryCall(sc, _) and i = summarizedCallableId(sc)) - | - c order by b, i - ) - } } /** A source call, that is, a `Call`. */ @@ -582,16 +526,10 @@ class SummaryCall extends DataFlowCall, TSummaryCall { override Location getLocation() { result = c.getLocation() } } -private predicate id(BasicBlock x, BasicBlock y) { x = y } - -private predicate idOf(BasicBlock x, int y) = equivalenceRelation(id/2)(x, y) - class NodeRegion instanceof BasicBlock { string toString() { result = "NodeRegion" } predicate contains(Node n) { n.asExpr().getBasicBlock() = this } - - int totalOrder() { idOf(this, result) } } /** Holds if `e` is an expression that always has the same Boolean value `val`. */ diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll index a5eec0338b72..1a38593bce48 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll @@ -347,16 +347,6 @@ abstract class DataFlowCallable extends TDataFlowCallable { /** Gets the location of this dataflow callable. */ abstract Location getLocation(); - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCallable c, string file, int startline, int startcolumn | - c.getLocation().hasLocationInfo(file, startline, startcolumn, _, _) - | - c order by file, startline, startcolumn - ) - } } /** A callable function. */ @@ -1439,23 +1429,6 @@ abstract class DataFlowCall extends TDataFlowCall { ) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } - - // #47: Stubs below - /** Gets an argument to this call as a Node. */ - ArgumentNode getAnArgumentNode(){ none() } // TODO: JB1 return an argument as a DataFlow ArgumentNode - - /** Gets the target of the call, as a DataFlowCallable. */ - DataFlowCallable getARuntimeTarget(){ none() } // TODO - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCall c, int startline, int startcolumn | - c.hasLocationInfo(_, startline, startcolumn, _, _) - | - c order by startline, startcolumn - ) - } } /** A call found in the program source (as opposed to a synthesised call). */ diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll index 6daa14a2bf90..268c289259e8 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll @@ -113,16 +113,6 @@ class DataFlowCallable extends TDataFlowCallable { this instanceof TLibraryCallable and result instanceof EmptyLocation } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCallable c, string file, int startline, int startcolumn | - c.getLocation().hasLocationInfo(file, startline, startcolumn, _, _) - | - c order by file, startline, startcolumn - ) - } } /** @@ -154,23 +144,6 @@ abstract class DataFlowCall extends TDataFlowCall { ) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } - - // #46: Stubs Below - /** Gets an argument to this call as a Node. */ - ArgumentNode getAnArgumentNode(){ none() } // TODO: JB1 return an argument as a DataFlow ArgumentNode - - /** Gets the target of the call, as a DataFlowCallable. */ - DataFlowCallable getARuntimeTarget(){ none() } // TODO - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCall c, int startline, int startcolumn | - c.hasLocationInfo(_, startline, startcolumn, _, _) - | - c order by startline, startcolumn - ) - } } /** @@ -264,7 +237,7 @@ class NormalCall extends DataFlowCall, TNormalCall { * need to track the `View` instance (2) into the receiver of the adjusted method * call, in order to figure out that the call target is in fact `view.html.erb`. */ -module ViewComponentRenderModeling { +private module ViewComponentRenderModeling { private import codeql.ruby.frameworks.ViewComponent private class RenderMethod extends SummarizedCallable, LibraryCallableToIncludeInTypeTracking { @@ -360,7 +333,7 @@ private predicate selfInModule(SelfVariable self, Module m) { /** Holds if `self` belongs to method `method` inside module `m`. */ pragma[nomagic] -predicate selfInMethod(SelfVariable self, MethodBase method, Module m) { +private predicate selfInMethod(SelfVariable self, MethodBase method, Module m) { exists(ModuleBase encl | method = self.getDeclaringScope() and encl = method.getEnclosingModule() and @@ -370,6 +343,67 @@ predicate selfInMethod(SelfVariable self, MethodBase method, Module m) { ) } +/** Holds if `self` belongs to the top-level. */ +pragma[nomagic] +private predicate selfInToplevel(SelfVariable self, Module m) { + ViewComponentRenderModeling::selfInErbToplevel(self, m) + or + not ViewComponentRenderModeling::selfInErbToplevel(self, _) and + self.getDeclaringScope() instanceof Toplevel and + m = Module::TResolved("Object") +} + +/** + * Holds if SSA definition `def` belongs to a variable introduced via pattern + * matching on type `m`. For example, in + * + * ```rb + * case object + * in C => c then c.foo + * end + * ``` + * + * the SSA definition for `c` is introduced by matching on `C`. + */ +private predicate asModulePattern(SsaDefinitionExtNode def, Module m) { + exists(AsPattern ap | + m = Module::resolveConstantReadAccess(ap.getPattern()) and + def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getAstNode() = + ap.getVariableAccess() + ) +} + +/** + * Holds if `read1` and `read2` are adjacent reads of SSA definition `def`, + * and `read2` is checked to have type `m`. For example, in + * + * ```rb + * case object + * when C then object.foo + * end + * ``` + * + * the two reads of `object` are adjacent, and the second is checked to have type `C`. + */ +private predicate hasAdjacentTypeCheckedReads( + Ssa::Definition def, CfgNodes::ExprCfgNode read1, CfgNodes::ExprCfgNode read2, Module m +) { + exists( + CfgNodes::ExprCfgNode pattern, ConditionBlock cb, CfgNodes::ExprNodes::CaseExprCfgNode case + | + m = Module::resolveConstantReadAccess(pattern.getExpr()) and + cb.getLastNode() = pattern and + cb.controls(read2.getBasicBlock(), + any(SuccessorTypes::MatchingSuccessor match | match.getValue() = true)) and + def.hasAdjacentReads(read1, read2) and + case.getValue() = read1 + | + pattern = case.getBranch(_).(CfgNodes::ExprNodes::WhenClauseCfgNode).getPattern(_) + or + pattern = case.getBranch(_).(CfgNodes::ExprNodes::InClauseCfgNode).getPattern() + ) +} + /** Holds if `new` is a user-defined `self.new` method. */ predicate isUserDefinedNew(SingletonMethod new) { exists(Expr object | singletonMethod(new, "new", object) | @@ -604,7 +638,7 @@ private predicate hasUserDefinedNew(Module m) { * `self.new` on `m`. */ pragma[nomagic] -predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) { +private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) { exists(DataFlow::LocalSourceNode sourceNode | flowsToMethodCallReceiver(TNormalCall(new), sourceNode, "new") and // `m` should not have a user-defined `self.new` method @@ -633,11 +667,106 @@ private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, } private module TrackInstanceInput implements CallGraphConstruction::InputSig { + pragma[nomagic] + private predicate isInstanceNoCall(DataFlow::Node n, Module tp, boolean exact) { + n.asExpr().getExpr() instanceof NilLiteral and + tp = Module::TResolved("NilClass") and + exact = true + or + n.asExpr().getExpr().(BooleanLiteral).isFalse() and + tp = Module::TResolved("FalseClass") and + exact = true + or + n.asExpr().getExpr().(BooleanLiteral).isTrue() and + tp = Module::TResolved("TrueClass") and + exact = true + or + n.asExpr().getExpr() instanceof IntegerLiteral and + tp = Module::TResolved("Integer") and + exact = true + or + n.asExpr().getExpr() instanceof FloatLiteral and + tp = Module::TResolved("Float") and + exact = true + or + n.asExpr().getExpr() instanceof RationalLiteral and + tp = Module::TResolved("Rational") and + exact = true + or + n.asExpr().getExpr() instanceof ComplexLiteral and + tp = Module::TResolved("Complex") and + exact = true + or + n.asExpr().getExpr() instanceof StringlikeLiteral and + tp = Module::TResolved("String") and + exact = true + or + n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and + tp = Module::TResolved("Array") and + exact = true + or + n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and + tp = Module::TResolved("Hash") and + exact = true + or + n.asExpr().getExpr() instanceof MethodBase and + tp = Module::TResolved("Symbol") and + exact = true + or + n.asParameter() instanceof BlockParameter and + tp = Module::TResolved("Proc") and + exact = true + or + n.asExpr().getExpr() instanceof Lambda and + tp = Module::TResolved("Proc") and + exact = true + or + // `self` reference in method or top-level (but not in module or singleton method, + // where instance methods cannot be called; only singleton methods) + n = + any(SelfLocalSourceNode self | + exists(MethodBase m | + selfInMethod(self.getVariable(), m, tp) and + not m instanceof SingletonMethod and + if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false + ) + or + selfInToplevel(self.getVariable(), tp) and + exact = true + ) + or + // `in C => c then c.foo` + asModulePattern(n, tp) and + exact = false + or + // `case object when C then object.foo` + hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and + exact = false + } + + pragma[nomagic] + private predicate isInstanceCall(DataFlow::Node n, Module tp, boolean exact) { + isStandardNewCall(n.asExpr(), tp, exact) + } + + /** Holds if `n` is an instance of type `tp`. */ + pragma[inline] + private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) { + isInstanceNoCall(n, tp, exact) + or + isInstanceCall(n, tp, exact) + } + + pragma[nomagic] + private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) { + hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _) + } + newtype State = additional MkState(Module m, Boolean exact) predicate start(DataFlow::Node start, State state) { exists(Module tp, boolean exact | state = MkState(tp, exact) | - TypeInference::hasType(start, tp, exact) + isInstance(start, tp, exact) or exists(Module m | (if m.isClass() then tp = Module::TResolved("Class") else tp = Module::TResolved("Module")) and @@ -655,11 +784,6 @@ private module TrackInstanceInput implements CallGraphConstruction::InputSig { ) } - pragma[nomagic] - private predicate hasAdjacentTypeCheckedRead(DataFlow::Node node) { - TypeInference::hasAdjacentTypeCheckedRead(node.asExpr(), _) - } - pragma[nomagic] predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { smallStepNoCall(nodeFrom, nodeTo, summary) @@ -667,8 +791,8 @@ private module TrackInstanceInput implements CallGraphConstruction::InputSig { // We exclude steps into type checked variables. For those, we instead rely on the // type being checked against localFlowStep(nodeFrom, nodeTo, summary) and - not hasAdjacentTypeCheckedRead(nodeTo) and - not TypeInference::asModulePattern(nodeTo.(SsaDefinitionExtNode).getDefinitionExt(), _) + not hasAdjacentTypeCheckedReads(nodeTo) and + not asModulePattern(nodeTo, _) } predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 471d0c4cc3c4..260fb3cab6f9 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -93,14 +93,6 @@ module SsaFlow { result = TSelfToplevelParameterNode(p.asToplevelSelf()) } - ParameterNodeImpl toParameterNodeImpl(SsaDefinitionExtNode node) { - exists(SsaImpl::WriteDefinition def, SsaImpl::ParameterExt p | - def = node.getDefinitionExt() and - result = toParameterNode(p) and - p.isInitializedBy(def) - ) - } - Impl::Node asNode(Node n) { n = TSsaNode(result) or @@ -702,9 +694,7 @@ private module Cached { cached newtype TDataFlowType = - TModuleDataFlowType(Module m) or TLambdaDataFlowType(Callable c) { c = any(LambdaSelfReferenceNode n).getCallable() } or - TCollectionType() or TUnknownDataFlowType() } @@ -1893,83 +1883,21 @@ predicate expectsContent(Node n, ContentSet c) { } class DataFlowType extends TDataFlowType { - string toString() { - exists(Module m | - this = TModuleDataFlowType(m) and - result = m.toString() - ) - or - this = TLambdaDataFlowType(_) and result = "[lambda]" - or - this = TCollectionType() and result = "[collection]" - or - this = TUnknownDataFlowType() and - result = "" - } - - predicate isUnknown() { this = TUnknownDataFlowType() } - - Location getLocation() { - exists(Module m | - this = TModuleDataFlowType(m) and - result = m.getLocation() - ) - or - exists(Callable c | this = TLambdaDataFlowType(c) and result = c.getLocation()) - } + string toString() { result = "" } } -pragma[nomagic] -private predicate isProcClass(DataFlowType t) { - t = TModuleDataFlowType(any(TypeInference::ProcClass m)) -} - -pragma[nomagic] -private predicate isArrayClass(DataFlowType t) { - t = TModuleDataFlowType(any(TypeInference::ArrayClass m).getADescendent()) -} - -pragma[nomagic] -private predicate isHashClass(DataFlowType t) { - t = TModuleDataFlowType(any(TypeInference::HashClass m).getADescendent()) -} - -private predicate isCollectionClass(DataFlowType t) { isArrayClass(t) or isHashClass(t) } - predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { - not t1.isUnknown() and - t2.isUnknown() - or - exists(Module m1, Module m2 | - t1 = TModuleDataFlowType(m1) and - t2 = TModuleDataFlowType(m2) and - m1.getAnImmediateAncestor+() = m2 - ) - or - t1 instanceof TLambdaDataFlowType and - isProcClass(t2) + t1 != TUnknownDataFlowType() and + t2 = TUnknownDataFlowType() } -private predicate mustHaveLambdaType(Node n, Callable c) { +private predicate mustHaveLambdaType(ExprNode n, Callable c) { exists(VariableCapture::ClosureExpr ce, CfgNodes::ExprCfgNode e | e = n.asExpr() and ce.hasBody(c) | e = ce or ce.hasAliasedAccess(e) ) - or - n.(CaptureNode).getSynthesizedCaptureNode().isInstanceAccess() and - c = n.(CaptureNode).getSynthesizedCaptureNode().getEnclosingCallable() -} - -private predicate mustHaveCollectionType(Node n, DataFlowType t) { - exists(ContentSet c | readStep(n, c, _) or storeStep(_, c, n) or expectsContent(n, c) | - c.isElement() and - t = TCollectionType() - ) and - not n instanceof SynthHashSplatOrSplatArgumentNode and - not n instanceof SynthHashSplatParameterNode and - not n instanceof SynthSplatParameterNode } predicate localMustFlowStep(Node node1, Node node2) { none() } @@ -1983,40 +1911,15 @@ DataFlowType getNodeType(Node n) { result = TLambdaDataFlowType(c) ) or - mustHaveCollectionType(n, result) - or not n instanceof LambdaSelfReferenceNode and not mustHaveLambdaType(n, _) and - not mustHaveCollectionType(n, _) and - ( - TypeInference::hasModuleType(n, result) - or - not TypeInference::hasModuleType(n, _) and - result.isUnknown() - ) + result = TUnknownDataFlowType() } -pragma[nomagic] +pragma[inline] private predicate compatibleTypesNonSymRefl(DataFlowType t1, DataFlowType t2) { - not t1.isUnknown() and - t2.isUnknown() - or - t1 instanceof TLambdaDataFlowType and - isProcClass(t2) - or - t1 instanceof TCollectionType and - isCollectionClass(t2) -} - -pragma[nomagic] -private predicate compatibleModuleTypes(TModuleDataFlowType t1, TModuleDataFlowType t2) { - exists(Module m1, Module m2, Module m3 | - t1 = TModuleDataFlowType(m1) and - t2 = TModuleDataFlowType(m2) - | - m3.getAnAncestor() = m1 and - m3.getAnAncestor() = m2 - ) + t1 != TUnknownDataFlowType() and + t2 = TUnknownDataFlowType() } /** @@ -2029,8 +1932,6 @@ predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { compatibleTypesNonSymRefl(t1, t2) or compatibleTypesNonSymRefl(t2, t1) - or - compatibleModuleTypes(t1, t2) } abstract class PostUpdateNodeImpl extends Node { @@ -2090,11 +1991,7 @@ private import PostUpdateNodes /** A node that performs a type cast. */ class CastNode extends Node { - CastNode() { - TypeInference::hasAdjacentTypeCheckedRead(this.asExpr(), _) - or - TypeInference::asModulePattern(this.(SsaDefinitionNode).getDefinition(), _) - } + CastNode() { none() } } /** @@ -2122,8 +2019,6 @@ class NodeRegion instanceof Unit { string toString() { result = "NodeRegion" } predicate contains(Node n) { none() } - - int totalOrder() { result = 1 } } /** @@ -2296,245 +2191,3 @@ class AdditionalJumpStep extends Unit { */ abstract predicate step(Node pred, Node succ); } - -/** Provides logic for assigning types to data flow nodes. */ -module TypeInference { - private import codeql.ruby.ast.internal.Module - private import DataFlowDispatch - - /** The built-in `Proc` class. */ - class ProcClass extends Module { - ProcClass() { this = TResolved("Proc") } - } - - /** The built-in `Array` class. */ - class ArrayClass extends Module { - ArrayClass() { this = TResolved("Array") } - } - - /** The built-in `Hash` class. */ - class HashClass extends Module { - HashClass() { this = TResolved("Hash") } - } - - /** The built-in `String` class. */ - class StringClass extends Module { - StringClass() { this = TResolved("String") } - } - - /** Holds if `self` belongs to the top-level. */ - pragma[nomagic] - private predicate selfInToplevel(SelfVariable self, Module m) { - ViewComponentRenderModeling::selfInErbToplevel(self, m) - or - not ViewComponentRenderModeling::selfInErbToplevel(self, _) and - self.getDeclaringScope() instanceof Toplevel and - m = TResolved("Object") - } - - /** - * Holds if SSA definition `def` belongs to a variable introduced via pattern - * matching on type `m`. For example, in - * - * ```rb - * case object - * in C => c then c.foo - * end - * ``` - * - * the SSA definition for `c` is introduced by matching on `C`. - */ - predicate asModulePattern(Ssa::WriteDefinition def, Module m) { - exists(AsPattern ap | - m = resolveConstantReadAccess(ap.getPattern()) and - def.getWriteAccess().getAstNode() = ap.getVariableAccess() - ) - } - - /** - * Holds if `caseRead` and `read` are reads of SSA definition `def`, - * and `read` is checked to have type `m`. For example, in - * - * ```rb - * case object - * when C then object.foo - * end - * ``` - * - * the second read of `object` is known to have type `C`. - */ - private predicate hasTypeCheckedRead( - Ssa::Definition def, CfgNodes::ExprCfgNode caseRead, CfgNodes::ExprCfgNode read, Module m - ) { - exists( - CfgNodes::ExprCfgNode pattern, ConditionBlock cb, CfgNodes::ExprNodes::CaseExprCfgNode case - | - m = resolveConstantReadAccess(pattern.getExpr()) and - cb.getLastNode() = pattern and - cb.controls(read.getBasicBlock(), - any(SuccessorTypes::MatchingSuccessor match | match.getValue() = true)) and - caseRead = def.getARead() and - read = def.getARead() and - case.getValue() = caseRead - | - pattern = case.getBranch(_).(CfgNodes::ExprNodes::WhenClauseCfgNode).getPattern(_) - or - pattern = case.getBranch(_).(CfgNodes::ExprNodes::InClauseCfgNode).getPattern() - ) - } - - predicate hasAdjacentTypeCheckedRead(CfgNodes::ExprCfgNode read, Module m) { - exists(Ssa::Definition def, CfgNodes::ExprCfgNode caseRead | - hasTypeCheckedRead(def, caseRead, read, m) and - def.hasAdjacentReads(caseRead, read) - ) - } - - private predicate isTypeCheckedRead(CfgNodes::ExprCfgNode read, Module m) { - exists(Ssa::Definition def | - hasTypeCheckedRead(def, _, read, m) and - // could in principle be checked against a new type - not exists(CfgNodes::ExprCfgNode innerCaseRead | - hasTypeCheckedRead(def, _, innerCaseRead, m) and - hasTypeCheckedRead(def, innerCaseRead, read, _) - ) - ) - } - - pragma[nomagic] - private predicate selfInMethodOrToplevelHasType(SelfVariable self, Module tp, boolean exact) { - exists(MethodBase m | - selfInMethod(self, m, tp) and - not m instanceof SingletonMethod and - if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false - ) - or - selfInToplevel(self, tp) and - exact = true - } - - pragma[nomagic] - private predicate parameterNodeHasType(ParameterNodeImpl p, Module tp, boolean exact) { - exists(ParameterPosition pos | - p.isParameterOf(_, pos) and - exact = true - | - (pos.isSplat(_) or pos.isSynthSplat(_)) and - tp instanceof ArrayClass - or - (pos.isHashSplat() or pos.isSynthHashSplat()) and - tp instanceof HashClass - ) - or - selfInMethodOrToplevelHasType(p.(SelfParameterNodeImpl).getSelfVariable(), tp, exact) - } - - pragma[nomagic] - private predicate ssaDefHasType(SsaDefinitionExtNode def, Module tp, boolean exact) { - exists(ParameterNodeImpl p | - parameterNodeHasType(p, tp, exact) and - p = SsaFlow::toParameterNodeImpl(def) - ) - or - selfInMethodOrToplevelHasType(def.getVariable(), tp, exact) - or - asModulePattern(def.getDefinitionExt(), tp) and - exact = false - } - - pragma[nomagic] - private predicate hasTypeNoCall(Node n, Module tp, boolean exact) { - n.asExpr().getExpr() instanceof NilLiteral and - tp = TResolved("NilClass") and - exact = true - or - n.asExpr().getExpr().(BooleanLiteral).isFalse() and - tp = TResolved("FalseClass") and - exact = true - or - n.asExpr().getExpr().(BooleanLiteral).isTrue() and - tp = TResolved("TrueClass") and - exact = true - or - n.asExpr().getExpr() instanceof IntegerLiteral and - tp = TResolved("Integer") and - exact = true - or - n.asExpr().getExpr() instanceof FloatLiteral and - tp = TResolved("Float") and - exact = true - or - n.asExpr().getExpr() instanceof RationalLiteral and - tp = TResolved("Rational") and - exact = true - or - n.asExpr().getExpr() instanceof ComplexLiteral and - tp = TResolved("Complex") and - exact = true - or - n.asExpr().getExpr() instanceof StringlikeLiteral and - tp instanceof StringClass and - exact = true - or - ( - n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode or - n instanceof SynthSplatArgumentNode - ) and - tp instanceof ArrayClass and - exact = true - or - ( - n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode - or - n instanceof SynthHashSplatArgumentNode - ) and - tp instanceof HashClass and - exact = true - or - n.asExpr().getExpr() instanceof MethodBase and - tp = TResolved("Symbol") and - exact = true - or - ( - n.asParameter() instanceof BlockParameter - or - n instanceof BlockParameterNode - or - n.asExpr().getExpr() instanceof Lambda - ) and - tp instanceof ProcClass and - exact = true - or - parameterNodeHasType(n, tp, exact) - or - exists(SsaDefinitionExtNode def | ssaDefHasType(def, tp, exact) | - n = def or - n.asExpr() = - any(CfgNodes::ExprCfgNode read | - read = def.getDefinitionExt().getARead() and - not isTypeCheckedRead(read, _) // could in principle be checked against a new type - ) - ) - or - // `case object when C then object.foo` - isTypeCheckedRead(n.asExpr(), tp) and - exact = false - } - - pragma[nomagic] - private predicate hasTypeCall(Node n, Module tp, boolean exact) { - isStandardNewCall(n.asExpr(), tp, exact) - } - - pragma[inline] - predicate hasType(Node n, Module tp, boolean exact) { - hasTypeNoCall(n, tp, exact) - or - hasTypeCall(n, tp, exact) - } - - pragma[nomagic] - predicate hasModuleType(Node n, DataFlowType t) { - exists(Module tp | t = TModuleDataFlowType(tp) | hasType(n, tp, _)) - } -} diff --git a/shared/dataflow/codeql/dataflow/DataFlow.qll b/shared/dataflow/codeql/dataflow/DataFlow.qll index a7917029577e..7c437adabb84 100644 --- a/shared/dataflow/codeql/dataflow/DataFlow.qll +++ b/shared/dataflow/codeql/dataflow/DataFlow.qll @@ -69,14 +69,6 @@ signature module InputSig { Node exprNode(DataFlowExpr e); class DataFlowCall { - - /** - * Gets a run-time target of this call. A target is always a source - * declaration, and if the callable has both CIL and source code, only - * the source code version is returned. - */ - DataFlowCallable getARuntimeTarget(); - /** Gets a textual representation of this element. */ string toString(); @@ -84,11 +76,6 @@ signature module InputSig { Location getLocation(); DataFlowCallable getEnclosingCallable(); - - ArgumentNode getAnArgumentNode(); - - /** Gets a best-effort total ordering. */ - int totalorder(); } class DataFlowCallable { @@ -97,9 +84,6 @@ signature module InputSig { /** Gets the location of this callable. */ Location getLocation(); - - /** Gets a best-effort total ordering. */ - int totalorder(); } class ReturnKind { @@ -274,8 +258,6 @@ signature module InputSig { class NodeRegion { /** Holds if this region contains `n`. */ predicate contains(Node n); - - int totalOrder(); } /** @@ -460,6 +442,28 @@ module Configs Lang> { * are used directly in a query result. */ default predicate observeDiffInformedIncrementalMode() { none() } + + /** + * Gets a location that will be associated with the given `source` in a + * diff-informed query that uses this configuration (see + * `observeDiffInformedIncrementalMode`). By default, this is the location + * of the source itself, but this predicate should include any locations + * that are reported as the primary-location of the query or as an + * additional location ("$@" interpolation). For a query that doesn't + * report the source at all, this predicate can be `none()`. + */ + default Location getASelectedSourceLocation(Node source) { result = source.getLocation() } + + /** + * Gets a location that will be associated with the given `sink` in a + * diff-informed query that uses this configuration (see + * `observeDiffInformedIncrementalMode`). By default, this is the location + * of the sink itself, but this predicate should include any locations + * that are reported as the primary-location of the query or as an + * additional location ("$@" interpolation). For a query that doesn't + * report the sink at all, this predicate can be `none()`. + */ + default Location getASelectedSinkLocation(Node sink) { result = sink.getLocation() } } /** An input configuration for data flow using flow state. */ @@ -587,6 +591,28 @@ module Configs Lang> { * are used directly in a query result. */ default predicate observeDiffInformedIncrementalMode() { none() } + + /** + * Gets a location that will be associated with the given `source` in a + * diff-informed query that uses this configuration (see + * `observeDiffInformedIncrementalMode`). By default, this is the location + * of the source itself, but this predicate should include any locations + * that are reported as the primary-location of the query or as an + * additional location ("$@" interpolation). For a query that doesn't + * report the source at all, this predicate can be `none()`. + */ + default Location getASelectedSourceLocation(Node source) { result = source.getLocation() } + + /** + * Gets a location that will be associated with the given `sink` in a + * diff-informed query that uses this configuration (see + * `observeDiffInformedIncrementalMode`). By default, this is the location + * of the sink itself, but this predicate should include any locations + * that are reported as the primary-location of the query or as an + * additional location ("$@" interpolation). For a query that doesn't + * report the sink at all, this predicate can be `none()`. + */ + default Location getASelectedSinkLocation(Node sink) { result = sink.getLocation() } } } @@ -633,16 +659,7 @@ module DataFlowMake Lang> { * A `Node` augmented with a call context (except for sinks) and an access path. * Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated. */ - class PathNode{ - /** Gets the underlying Node. */ - Node getNode(); - - /** Holds if this node is a source. */ - predicate isSource(); - - /** Gets a successor of this node, if any. */ - PathNode getASuccessor(); - } + class PathNode; /** * Holds if data can flow from `source` to `sink`. @@ -726,9 +743,6 @@ module DataFlowMake Lang> { /** Gets the underlying `Node`. */ Node getNode(); - /** Holds if this node is a source. */ - predicate isSource(); - /** Gets the location of this node. */ Location getLocation(); } @@ -781,15 +795,6 @@ module DataFlowMake Lang> { ) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } - - predicate isSource(){ - this.asPathNode1().isSource() or - this.asPathNode2().isSource() - } - - PathNode getASuccessor(){ - none() - } } /** @@ -862,16 +867,6 @@ module DataFlowMake Lang> { /** Gets the underlying `Node`. */ Node getNode() { result = super.getNode() } - predicate isSource(){ - this.asPathNode1().isSource() or - this.asPathNode2().isSource() or - this.asPathNode3().isSource() - } - - PathNode getASuccessor(){ - none() - } - /** Gets the location of this node. */ Location getLocation() { result = super.getLocation() } } diff --git a/shared/dataflowstack/codeql/dataflowstack/DataFlowStack.qll b/shared/dataflowstack/codeql/dataflowstack/DataFlowStack.qll index 56a3f93d3914..f99b3125c1b9 100644 --- a/shared/dataflowstack/codeql/dataflowstack/DataFlowStack.qll +++ b/shared/dataflowstack/codeql/dataflowstack/DataFlowStack.qll @@ -1,390 +1,430 @@ - private import codeql.dataflow.DataFlow as DF private import codeql.util.Location -module DataFlowStackMake Lang>{ - - import DF::DataFlowMake as DataFlow - - module BiStackAnalysis{ - - module FlowStackA = FlowStack; - module FlowStackB = FlowStack; - - /** - * Holds if either the Stack associated with `sourceNodeA` is a subset of the stack associated with `sourceNodeB` - * or vice-versa. - */ - predicate eitherStackSubset(FlowA::PathNode sourceNodeA, FlowA::PathNode sinkNodeA, FlowB::PathNode sourceNodeB, FlowB::PathNode sinkNodeB){ - FlowStackA::isSource(sourceNodeA) and - FlowStackB::isSource(sourceNodeB) and - FlowStackA::isSink(sinkNodeA) and - FlowStackB::isSink(sinkNodeB) and - exists(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB | - flowStackA = FlowStackA::createFlowStack(sourceNodeA, sinkNodeA) and - flowStackB = FlowStackB::createFlowStack(sourceNodeB, sinkNodeB) and ( - BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackA, flowStackB) - or - BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackB, flowStackA) - ) - - ) - } - - /** - * Holds if the stack associated with path `sourceNodeA` is a subset (and shares a common stack bottom) with - * the stack associated with path `sourceNodeB`, or vice-versa. - * - * For the given pair of (source, sink) for two (potentially disparate) DataFlows, - * determine whether one Flow's Stack (at time of sink execution) is a subset of the other flow's Stack. - */ - predicate eitherStackTerminatingSubset(FlowA::PathNode sourceNodeA, FlowA::PathNode sinkNodeA, FlowB::PathNode sourceNodeB, FlowB::PathNode sinkNodeB){ - FlowStackA::isSource(sourceNodeA) and - FlowStackB::isSource(sourceNodeB) and - FlowStackA::isSink(sinkNodeA) and - FlowStackB::isSink(sinkNodeB) and - exists(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB | - flowStackA = FlowStackA::createFlowStack(sourceNodeA, sinkNodeA) and - flowStackB = FlowStackB::createFlowStack(sourceNodeB, sinkNodeB) and ( - BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackA, flowStackB) - or - BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackB, flowStackA) - ) - ) - } - - /** - * Alias for BiStackAnalysisImpl::flowStackIsSubsetOf - * - * Holds if stackA is a subset of stackB, - * The top of stackA is in stackB and the bottom of stackA is then some successor further down stackB. - */ - predicate flowStackIsSubsetOf(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB){ - BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackA, flowStackB) - } - - /** - * Alias for BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf - * - * If the top of stackA is in stackB at any location, and the bottoms of the stack are the same call. - */ - predicate flowStackIsConvergingTerminatingSubsetOf(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB){ - BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackA, flowStackB) - } +signature module DataFlowStackSig< + LocationSig Location, DF::InputSig Lang, DF::Configs::ConfigSig Config> +{ + Lang::Node getNode(DF::DataFlowMake::Global::PathNode n); + + predicate isSource(DF::DataFlowMake::Global::PathNode n); + + DF::DataFlowMake::Global::PathNode getASuccessor( + DF::DataFlowMake::Global::PathNode n + ); + + Lang::DataFlowCallable getARuntimeTarget(Lang::DataFlowCall call); + + Lang::Node getAnArgumentNode(Lang::DataFlowCall call); +} + +module DataFlowStackMake Lang> { + module DataFlow = DF::DataFlowMake; + + module BiStackAnalysis< + DF::Configs::ConfigSig ConfigA, + DataFlowStackSig DataFlowStackA, + DF::Configs::ConfigSig ConfigB, + DataFlowStackSig DataFlowStackB> + { + module FlowA = DataFlow::Global; + + module FlowStackA = FlowStack; + + module FlowB = DataFlow::Global; + + module FlowStackB = FlowStack; + + /** + * Holds if either the Stack associated with `sourceNodeA` is a subset of the stack associated with `sourceNodeB` + * or vice-versa. + */ + predicate eitherStackSubset( + FlowA::PathNode sourceNodeA, FlowA::PathNode sinkNodeA, FlowB::PathNode sourceNodeB, + FlowB::PathNode sinkNodeB + ) { + FlowStackA::isSource(sourceNodeA) and + FlowStackB::isSource(sourceNodeB) and + FlowStackA::isSink(sinkNodeA) and + FlowStackB::isSink(sinkNodeB) and + exists(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB | + flowStackA = FlowStackA::createFlowStack(sourceNodeA, sinkNodeA) and + flowStackB = FlowStackB::createFlowStack(sourceNodeB, sinkNodeB) and + ( + BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackA, + flowStackB) + or + BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackB, + flowStackA) + ) + ) + } + + /** + * Holds if the stack associated with path `sourceNodeA` is a subset (and shares a common stack bottom) with + * the stack associated with path `sourceNodeB`, or vice-versa. + * + * For the given pair of (source, sink) for two (potentially disparate) DataFlows, + * determine whether one Flow's Stack (at time of sink execution) is a subset of the other flow's Stack. + */ + predicate eitherStackTerminatingSubset( + FlowA::PathNode sourceNodeA, FlowA::PathNode sinkNodeA, FlowB::PathNode sourceNodeB, + FlowB::PathNode sinkNodeB + ) { + FlowStackA::isSource(sourceNodeA) and + FlowStackB::isSource(sourceNodeB) and + FlowStackA::isSink(sinkNodeA) and + FlowStackB::isSink(sinkNodeB) and + exists(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB | + flowStackA = FlowStackA::createFlowStack(sourceNodeA, sinkNodeA) and + flowStackB = FlowStackB::createFlowStack(sourceNodeB, sinkNodeB) and + ( + BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackA, + flowStackB) + or + BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackB, + flowStackA) + ) + ) + } + + /** + * Alias for BiStackAnalysisImpl::flowStackIsSubsetOf + * + * Holds if stackA is a subset of stackB, + * The top of stackA is in stackB and the bottom of stackA is then some successor further down stackB. + */ + predicate flowStackIsSubsetOf(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB) { + BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackA, + flowStackB) + } + + /** + * Alias for BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf + * + * If the top of stackA is in stackB at any location, and the bottoms of the stack are the same call. + */ + predicate flowStackIsConvergingTerminatingSubsetOf( + FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB + ) { + BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackA, + flowStackB) + } + } + + private module BiStackAnalysisImpl< + DF::Configs::ConfigSig ConfigA, + DataFlowStackSig DataFlowStackA, + DF::Configs::ConfigSig ConfigB, + DataFlowStackSig DataFlowStackB> + { + module FlowStackA = FlowStack; + + module FlowStackB = FlowStack; + + /** + * Holds if stackA is a subset of stackB, + * The top of stackA is in stackB and the bottom of stackA is then some successor further down stackB. + */ + predicate flowStackIsSubsetOf(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB) { + exists( + FlowStackA::FlowStackFrame highestStackFrameA, FlowStackB::FlowStackFrame highestStackFrameB + | + highestStackFrameA = flowStackA.getTopFrame() and + highestStackFrameB = flowStackB.getTopFrame() and + // Check if some intermediary frame `intStackFrameB`of StackB is in the stack of highestStackFrameA + exists(FlowStackB::FlowStackFrame intStackFrameB | + intStackFrameB = highestStackFrameB.getASucceedingTerminalStateFrame*() and + sharesCallWith(highestStackFrameA, intStackFrameB) and + sharesCallWith(flowStackA.getTerminalFrame(), + intStackFrameB.getASucceedingTerminalStateFrame*()) + ) + ) + } + + /** + * If the top of stackA is in stackB at any location, and the bottoms of the stack are the same call. + */ + predicate flowStackIsConvergingTerminatingSubsetOf( + FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB + ) { + flowStackA.getTerminalFrame().getCall() = flowStackB.getTerminalFrame().getCall() and + exists(FlowStackB::FlowStackFrame intStackFrameB | + intStackFrameB = flowStackB.getTopFrame().getASucceedingTerminalStateFrame*() and + sharesCallWith(flowStackA.getTopFrame(), intStackFrameB) + ) + } + + /** + * Holds if the given FlowStackFrames share the same call. + * i.e. they are both arguments of the same function call. + */ + predicate sharesCallWith(FlowStackA::FlowStackFrame frameA, FlowStackB::FlowStackFrame frameB) { + frameA.getCall() = frameB.getCall() + } + } + + module FlowStack< + DF::Configs::ConfigSig Config, + DataFlowStackSig DataFlowStack> + { + private module Flow = DF::DataFlowMake::Global; + + /** + * Determines whether or not the given PathNode is a source + * TODO: Refactor to Flow::PathNode signature + */ + predicate isSource(Flow::PathNode node) { DataFlowStack::isSource(node) } + + /** + * Determines whether or not the given PathNode is a sink + * TODO: Refactor to Flow::PathNode signature + */ + predicate isSink(Flow::PathNode node) { not exists(DataFlowStack::getASuccessor(node)) } + + /** A FlowStack encapsulates flows between a source and a sink, and all the pathways inbetween (possibly multiple) */ + private newtype FlowStackType = + TFlowStack(Flow::PathNode source, Flow::PathNode sink) { + DataFlowStack::isSource(source) and + not exists(DataFlowStack::getASuccessor(sink)) and + DataFlowStack::getASuccessor*(source) = sink + } + + class FlowStack extends FlowStackType, TFlowStack { + string toString() { result = "FlowStack" } + + /** + * Get the first frame in the DataFlowStack, irregardless of whether or not it has a parent. + */ + FlowStackFrame getFirstFrame() { + exists(FlowStackFrame flowStackFrame, CallFrame frame | + flowStackFrame = TFlowStackFrame(this, frame) and + not exists(frame.getPredecessor()) and + result = flowStackFrame + ) + } + + /** + * Get the top frame in the DataFlowStack, ie the frame that is the highest in the stack for the given flow. + */ + FlowStackFrame getTopFrame() { + exists(FlowStackFrame flowStackFrame | + flowStackFrame = TFlowStackFrame(this, _) and + not exists(flowStackFrame.getParentStackFrame()) and + result = flowStackFrame + ) + } + + /** + * Get the terminal frame in the DataFlowStack, ie the frame that is the end of the flow. + */ + FlowStackFrame getTerminalFrame() { + exists(FlowStackFrame flowStackFrame, CallFrame frame | + flowStackFrame = TFlowStackFrame(this, frame) and + not exists(frame.getSuccessor()) and + result = flowStackFrame + ) + } + } + + FlowStack createFlowStack(Flow::PathNode source, Flow::PathNode sink) { + result = TFlowStack(source, sink) + } + + /** A FlowStackFrame encapsulates a Stack frame that is bound between a given source and sink. */ + private newtype FlowStackFrameType = + TFlowStackFrame(FlowStack flowStack, CallFrame frame) { + exists(Flow::PathNode source, Flow::PathNode sink | + flowStack = TFlowStack(source, sink) and + frame.getPathNode() = DataFlowStack::getASuccessor*(source) and + DataFlowStack::getASuccessor(frame.getPathNode()) = sink + ) + } + + class FlowStackFrame extends FlowStackFrameType, TFlowStackFrame { + string toString() { result = "FlowStackFrame" } + + /** + * Get the next frame in the DataFlow Stack + */ + FlowStackFrame getASuccessor() { + exists(FlowStack flowStack, CallFrame frame, CallFrame nextFrame | + this = TFlowStackFrame(flowStack, frame) and + nextFrame = frame.getSuccessor() and + result = TFlowStackFrame(flowStack, nextFrame) + ) + } + + /** + * Gets the next FlowStackFrame from the direct descendents that is a frame in the end-state (terminal) stack. + */ + FlowStackFrame getASucceedingTerminalStateFrame() { + result = this.getChildStackFrame() and + // There are no other direct children that are further in the flow + not result.getASuccessor+() = this.getChildStackFrame() + } + + /** + * Gets a predecessor FlowStackFrame of this FlowStackFrame. + */ + FlowStackFrame getAPredecessor() { result.getASuccessor() = this } + + /** + * Gets a predecessor FlowStackFrame that is a parent in the stack. + */ + FlowStackFrame getParentStackFrame() { result.getChildStackFrame() = this } + + /** + * Gets the set of succeeding FlowStackFrame which are a direct descendant of this frame in the Stack. + */ + FlowStackFrame getChildStackFrame() { + exists(FlowStackFrame transitiveSuccessor | + transitiveSuccessor = this.getASuccessor+() and + DataFlowStack::getARuntimeTarget(this.getCall()) = + transitiveSuccessor.getCall().getEnclosingCallable() and + result = transitiveSuccessor + ) + } + + /** + * Unpacks the PathNode associated with this FlowStackFrame + */ + Flow::PathNode getPathNode() { + exists(CallFrame callFrame | + this = TFlowStackFrame(_, callFrame) and + result = callFrame.getPathNode() + ) + } + + /** + * Unpacks the DataFlowCall associated with this FlowStackFrame + */ + Lang::DataFlowCall getCall() { result = this.getCallFrame().getCall() } + + /** + * Unpacks the CallFrame associated with this FlowStackFrame + */ + CallFrame getCallFrame() { this = TFlowStackFrame(_, result) } + } + + /** + * A CallFrame is a PathNode that represents a (DataFlowCall/Accessor). + */ + private newtype TCallFrameType = + TCallFrame(Flow::PathNode node) { + exists(Lang::DataFlowCall c | + DataFlowStack::getAnArgumentNode(c) = DataFlowStack::getNode(node) + ) + } + + /** + * The CallFrame is a PathNode that represents an argument to a Call. + */ + private class CallFrame extends TCallFrameType, TCallFrame { + string toString() { + exists(Lang::DataFlowCall call | + call = this.getCall() and + result = call.toString() + ) + } + + /** + * Find the set of CallFrames that are immediate successors of this CallFrame. + */ + CallFrame getSuccessor() { result = TCallFrame(getSuccessorCall(this.getPathNode())) } + + /** + * Find the set of CallFrames that are an immediate predecessor of this CallFrame. + */ + CallFrame getPredecessor() { + exists(CallFrame prior | + prior.getSuccessor() = this and + result = prior + ) + } + + /** + * Unpack the CallFrame and retrieve the associated DataFlowCall. + */ + Lang::DataFlowCall getCall() { + exists(Lang::DataFlowCall call, Flow::PathNode node | + this = TCallFrame(node) and + DataFlowStack::getAnArgumentNode(call) = DataFlowStack::getNode(node) and + result = call + ) + } + + /** + * Unpack the CallFrame and retrieve the associated PathNode. + */ + Flow::PathNode getPathNode() { + exists(Flow::PathNode n | + this = TCallFrame(n) and + result = n + ) + } } - private module BiStackAnalysisImpl{ - - module FlowStackA = FlowStack; - module FlowStackB = FlowStack; - - /** - * Holds if stackA is a subset of stackB, - * The top of stackA is in stackB and the bottom of stackA is then some successor further down stackB. - */ - predicate flowStackIsSubsetOf(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB){ - exists(FlowStackA::FlowStackFrame highestStackFrameA, FlowStackB::FlowStackFrame highestStackFrameB | - highestStackFrameA = flowStackA.getTopFrame() and - highestStackFrameB = flowStackB.getTopFrame() and - // Check if some intermediary frame `intStackFrameB`of StackB is in the stack of highestStackFrameA - exists(FlowStackB::FlowStackFrame intStackFrameB | - intStackFrameB = highestStackFrameB.getASucceedingTerminalStateFrame*() and - sharesCallWith(highestStackFrameA, intStackFrameB) and - sharesCallWith(flowStackA.getTerminalFrame(), intStackFrameB.getASucceedingTerminalStateFrame*()) - ) - ) - } - - /** - * If the top of stackA is in stackB at any location, and the bottoms of the stack are the same call. - */ - predicate flowStackIsConvergingTerminatingSubsetOf(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB){ - flowStackA.getTerminalFrame().getCall() = flowStackB.getTerminalFrame().getCall() and - exists(FlowStackB::FlowStackFrame intStackFrameB | - intStackFrameB = flowStackB.getTopFrame().getASucceedingTerminalStateFrame*() and - sharesCallWith(flowStackA.getTopFrame(), intStackFrameB) - ) - } - - /** - * Holds if the given FlowStackFrames share the same call. - * i.e. they are both arguments of the same function call. - */ - predicate sharesCallWith(FlowStackA::FlowStackFrame frameA, FlowStackB::FlowStackFrame frameB){ - frameA.getCall() = frameB.getCall() - } + /** + * From the given PathNode argument, find the set of successors that are an argument in a DataFlowCall, + * and return them as the result. + */ + private Flow::PathNode getSuccessorCall(Flow::PathNode n) { + exists(Flow::PathNode succ | + succ = DataFlowStack::getASuccessor(n) and + if + exists(Lang::DataFlowCall c | + DataFlowStack::getAnArgumentNode(c) = DataFlowStack::getNode(succ) + ) + then result = succ + else result = getSuccessorCall(succ) + ) } - module FlowStack{ - - /** Determines whether or not the given PathNode is a source - * TODO: Refactor to Flow::PathNode signature */ - predicate isSource(Flow::PathNode node){ - node.isSource() - } - - /** Determines whether or not the given PathNode is a sink - * TODO: Refactor to Flow::PathNode signature */ - predicate isSink(Flow::PathNode node){ - not exists(node.getASuccessor()) - } - - /** A FlowStack encapsulates flows between a source and a sink, and all the pathways inbetween (possibly multiple) */ - private newtype FlowStackType = - TFlowStack(Flow::PathNode source, Flow::PathNode sink){ - source.isSource() and - not exists(sink.getASuccessor()) and - source.getASuccessor*() = sink - } - - class FlowStack extends FlowStackType, TFlowStack{ - string toString(){ - result = "FlowStack" - } - - /** - * Get the first frame in the DataFlowStack, irregardless of whether or not it has a parent. - */ - FlowStackFrame getFirstFrame(){ - exists(FlowStackFrame flowStackFrame, CallFrame frame | - flowStackFrame = TFlowStackFrame(this, frame) and - not exists(frame.getPredecessor()) and - result = flowStackFrame - ) - } - - /** - * Get the top frame in the DataFlowStack, ie the frame that is the highest in the stack for the given flow. - */ - FlowStackFrame getTopFrame(){ - exists(FlowStackFrame flowStackFrame | - flowStackFrame = TFlowStackFrame(this, _) and - not exists(flowStackFrame.getParentStackFrame()) and - result = flowStackFrame - ) - } - - /** - * Get the terminal frame in the DataFlowStack, ie the frame that is the end of the flow. - */ - FlowStackFrame getTerminalFrame(){ - exists(FlowStackFrame flowStackFrame, CallFrame frame | - flowStackFrame = TFlowStackFrame(this, frame) and - not exists(frame.getSuccessor()) and - result = flowStackFrame - ) - } - } - - FlowStack createFlowStack(Flow::PathNode source, Flow::PathNode sink){ - result = TFlowStack(source, sink) - } - - /** A FlowStackFrame encapsulates a Stack frame that is bound between a given source and sink. */ - private newtype FlowStackFrameType = - TFlowStackFrame(FlowStack flowStack, CallFrame frame){ - exists(Flow::PathNode source, Flow::PathNode sink | - flowStack = TFlowStack(source, sink) and - frame.getPathNode() = source.getASuccessor*() and - frame.getPathNode().getASuccessor*() = sink - ) - } - - class FlowStackFrame extends FlowStackFrameType, TFlowStackFrame{ - string toString(){ - result = "FlowStackFrame" - } - - /** - * Get the next frame in the DataFlow Stack - */ - FlowStackFrame getASuccessor(){ - exists(FlowStack flowStack, CallFrame frame, CallFrame nextFrame | - this = TFlowStackFrame(flowStack, frame) and - nextFrame = frame.getSuccessor() and - result = TFlowStackFrame(flowStack, nextFrame) - ) - } - - /** - * Gets the next FlowStackFrame from the direct descendents that is a frame in the end-state (terminal) stack. - */ - FlowStackFrame getASucceedingTerminalStateFrame(){ - result = this.getChildStackFrame() and - // There are no other direct children that are further in the flow - not result.getASuccessor+() = this.getChildStackFrame() - } - - /** - * Gets a predecessor FlowStackFrame of this FlowStackFrame. - */ - FlowStackFrame getAPredecessor(){ - result.getASuccessor() = this - } - - /** - * Gets a predecessor FlowStackFrame that is a parent in the stack. - */ - FlowStackFrame getParentStackFrame(){ - result.getChildStackFrame() = this - } - - /** - * Gets the set of succeeding FlowStackFrame which are a direct descendant of this frame in the Stack. - */ - FlowStackFrame getChildStackFrame(){ - exists(FlowStackFrame transitiveSuccessor | - transitiveSuccessor = this.getASuccessor+() and - this.getCall().getARuntimeTarget() = transitiveSuccessor.getCall().getEnclosingCallable() and - result = transitiveSuccessor - ) - } - - /** - * Unpacks the PathNode associated with this FlowStackFrame - */ - Flow::PathNode getPathNode(){ - exists(CallFrame callFrame | - this = TFlowStackFrame(_, callFrame) and - result = callFrame.getPathNode() - ) - } - - /** - * Unpacks the DataFlowCall associated with this FlowStackFrame - */ - Lang::DataFlowCall getCall(){ - result = this.getCallFrame().getCall() - } - - /** - * Unpacks the CallFrame associated with this FlowStackFrame - */ - CallFrame getCallFrame(){ - this = TFlowStackFrame(_, result) - } - } - - - /** - * A CallFrame is a PathNode that represents a (DataFlowCall/Accessor). - */ - private newtype TCallFrameType = - TCallFrame(Flow::PathNode node) { - exists(Lang::DataFlowCall c | - c.getAnArgumentNode() = node.getNode() - ) - } - - /** - * The CallFrame is a PathNode that represents an argument to a Call. - */ - private class CallFrame extends TCallFrameType, TCallFrame{ - string toString(){ - exists(Lang::DataFlowCall call | - call = this.getCall() and - result = call.toString() - ) - } - - /** - * Find the set of CallFrames that are immediate successors of this CallFrame. - */ - CallFrame getSuccessor(){ - result = TCallFrame(getSuccessorCall(this.getPathNode())) - } - - /** - * Find the set of CallFrames that are an immediate predecessor of this CallFrame. - */ - CallFrame getPredecessor(){ - exists(CallFrame prior | - prior.getSuccessor() = this and - result = prior - ) - } - - /** - * Unpack the CallFrame and retrieve the associated DataFlowCall. - */ - Lang::DataFlowCall getCall(){ - exists(Lang::DataFlowCall call, Flow::PathNode node | - this = TCallFrame(node) and - call.getAnArgumentNode() = node.getNode() and - result = call - ) - } - - /** - * Unpack the CallFrame and retrieve the associated PathNode. - */ - Flow::PathNode getPathNode(){ - exists(Flow::PathNode n | - this = TCallFrame(n) and - result = n - ) - } - } - - /** - * From the given PathNode argument, find the set of successors that are an argument in a DataFlowCall, - * and return them as the result. - */ - private Flow::PathNode getSuccessorCall(Flow::PathNode n){ - exists(Flow::PathNode succ | - succ = n.getASuccessor() and - if exists(Lang::DataFlowCall c | c.getAnArgumentNode() = succ.getNode()) - then result = succ - else result = getSuccessorCall(succ) - ) - } - - /** - * A user-supplied predicate which given a Stack Frame, returns some Node associated with it. - */ - signature Lang::Node extractNodeFromFrame(Flow::PathNode pathNode); - - /** - * Provides some higher-order predicates for analyzing Stacks - */ - module StackFrameAnalysis{ - - /** - * Find the highest stack frame that satisfies the given predicate, - * and return the Node(s) that the user-supplied predicate returns. - * - * There should be no higher stack frame that satisfies the user-supplied predicate FROM the point that the - * argument . - */ - Lang::Node extractingFromHighestStackFrame(FlowStack flowStack){ - exists(FlowStackFrame topStackFrame, FlowStackFrame someStackFrame | - topStackFrame = flowStack.getTopFrame() and - someStackFrame = topStackFrame.getASuccessor*() and - result = customFrameCond(someStackFrame.getPathNode()) and - not exists(FlowStackFrame predecessor | - predecessor = someStackFrame.getAPredecessor+() and - // The predecessor is *not* prior to the user-given 'top' of the stack frame. - not predecessor = topStackFrame.getAPredecessor+() and - exists(customFrameCond(predecessor.getPathNode())) - ) - ) - } - - /** - * Find the lowest stack frame that satisfies the given predicate, - * and return the Node(s) that the user-supplied predicate returns. - */ - Lang::Node extractingFromLowestStackFrame(FlowStack flowStack){ - exists(FlowStackFrame topStackFrame, FlowStackFrame someStackFrame | - topStackFrame = flowStack.getTopFrame() and - someStackFrame = topStackFrame.getChildStackFrame*() and - result = customFrameCond(someStackFrame.getPathNode()) and - not exists(FlowStackFrame successor | - successor = someStackFrame.getChildStackFrame+() and - exists(customFrameCond(successor.getPathNode())) - ) - ) - } - } + /** + * A user-supplied predicate which given a Stack Frame, returns some Node associated with it. + */ + signature Lang::Node extractNodeFromFrame(Flow::PathNode pathNode); + + /** + * Provides some higher-order predicates for analyzing Stacks + */ + module StackFrameAnalysis { + /** + * Find the highest stack frame that satisfies the given predicate, + * and return the Node(s) that the user-supplied predicate returns. + * + * There should be no higher stack frame that satisfies the user-supplied predicate FROM the point that the + * argument . + */ + Lang::Node extractingFromHighestStackFrame(FlowStack flowStack) { + exists(FlowStackFrame topStackFrame, FlowStackFrame someStackFrame | + topStackFrame = flowStack.getTopFrame() and + someStackFrame = topStackFrame.getASuccessor*() and + result = customFrameCond(someStackFrame.getPathNode()) and + not exists(FlowStackFrame predecessor | + predecessor = someStackFrame.getAPredecessor+() and + // The predecessor is *not* prior to the user-given 'top' of the stack frame. + not predecessor = topStackFrame.getAPredecessor+() and + exists(customFrameCond(predecessor.getPathNode())) + ) + ) + } + + /** + * Find the lowest stack frame that satisfies the given predicate, + * and return the Node(s) that the user-supplied predicate returns. + */ + Lang::Node extractingFromLowestStackFrame(FlowStack flowStack) { + exists(FlowStackFrame topStackFrame, FlowStackFrame someStackFrame | + topStackFrame = flowStack.getTopFrame() and + someStackFrame = topStackFrame.getChildStackFrame*() and + result = customFrameCond(someStackFrame.getPathNode()) and + not exists(FlowStackFrame successor | + successor = someStackFrame.getChildStackFrame+() and + exists(customFrameCond(successor.getPathNode())) + ) + ) + } } -} \ No newline at end of file + } +} diff --git a/shared/dataflowstack/codeql/dataflowstack/TaintTrackingStack.qll b/shared/dataflowstack/codeql/dataflowstack/TaintTrackingStack.qll new file mode 100644 index 000000000000..e2a81f81264e --- /dev/null +++ b/shared/dataflowstack/codeql/dataflowstack/TaintTrackingStack.qll @@ -0,0 +1,436 @@ +private import codeql.dataflow.DataFlow as DF +private import codeql.dataflow.TaintTracking as TT +private import codeql.util.Location + +signature module TaintTrackingStackSig< + LocationSig Location, DF::InputSig Lang, TT::InputSig TTLang, + DF::Configs::ConfigSig Config> +{ + Lang::Node getNode(TT::TaintFlowMake::Global::PathNode n); + + predicate isSource(TT::TaintFlowMake::Global::PathNode n); + + TT::TaintFlowMake::Global::PathNode getASuccessor( + TT::TaintFlowMake::Global::PathNode n + ); + + Lang::DataFlowCallable getARuntimeTarget(Lang::DataFlowCall call); + + Lang::Node getAnArgumentNode(Lang::DataFlowCall call); +} + +module TaintTrackingStackMake< + LocationSig Location, DF::InputSig Lang, TT::InputSig TTLang> +{ + module DataFlow = DF::DataFlowMake; + + module TaintTracking = TT::TaintFlowMake; + + module BiStackAnalysis< + DF::Configs::ConfigSig ConfigA, + TaintTrackingStackSig TaintTrackingStackA, + DF::Configs::ConfigSig ConfigB, + TaintTrackingStackSig TaintTrackingStackB> + { + module FlowA = TaintTracking::Global; + + module FlowStackA = FlowStack; + + module FlowB = TaintTracking::Global; + + module FlowStackB = FlowStack; + + /** + * Holds if either the Stack associated with `sourceNodeA` is a subset of the stack associated with `sourceNodeB` + * or vice-versa. + */ + predicate eitherStackSubset( + FlowA::PathNode sourceNodeA, FlowA::PathNode sinkNodeA, FlowB::PathNode sourceNodeB, + FlowB::PathNode sinkNodeB + ) { + FlowStackA::isSource(sourceNodeA) and + FlowStackB::isSource(sourceNodeB) and + FlowStackA::isSink(sinkNodeA) and + FlowStackB::isSink(sinkNodeB) and + exists(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB | + flowStackA = FlowStackA::createFlowStack(sourceNodeA, sinkNodeA) and + flowStackB = FlowStackB::createFlowStack(sourceNodeB, sinkNodeB) and + ( + BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackA, + flowStackB) + or + BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackB, + flowStackA) + ) + ) + } + + /** + * Holds if the stack associated with path `sourceNodeA` is a subset (and shares a common stack bottom) with + * the stack associated with path `sourceNodeB`, or vice-versa. + * + * For the given pair of (source, sink) for two (potentially disparate) DataFlows, + * determine whether one Flow's Stack (at time of sink execution) is a subset of the other flow's Stack. + */ + predicate eitherStackTerminatingSubset( + FlowA::PathNode sourceNodeA, FlowA::PathNode sinkNodeA, FlowB::PathNode sourceNodeB, + FlowB::PathNode sinkNodeB + ) { + FlowStackA::isSource(sourceNodeA) and + FlowStackB::isSource(sourceNodeB) and + FlowStackA::isSink(sinkNodeA) and + FlowStackB::isSink(sinkNodeB) and + exists(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB | + flowStackA = FlowStackA::createFlowStack(sourceNodeA, sinkNodeA) and + flowStackB = FlowStackB::createFlowStack(sourceNodeB, sinkNodeB) and + ( + BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackA, + flowStackB) + or + BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackB, + flowStackA) + ) + ) + } + + /** + * Alias for BiStackAnalysisImpl::flowStackIsSubsetOf + * + * Holds if stackA is a subset of stackB, + * The top of stackA is in stackB and the bottom of stackA is then some successor further down stackB. + */ + predicate flowStackIsSubsetOf(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB) { + BiStackAnalysisImpl::flowStackIsSubsetOf(flowStackA, + flowStackB) + } + + /** + * Alias for BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf + * + * If the top of stackA is in stackB at any location, and the bottoms of the stack are the same call. + */ + predicate flowStackIsConvergingTerminatingSubsetOf( + FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB + ) { + BiStackAnalysisImpl::flowStackIsConvergingTerminatingSubsetOf(flowStackA, + flowStackB) + } + } + + private module BiStackAnalysisImpl< + DF::Configs::ConfigSig ConfigA, + TaintTrackingStackSig DataFlowStackA, + DF::Configs::ConfigSig ConfigB, + TaintTrackingStackSig DataFlowStackB> + { + module FlowStackA = FlowStack; + + module FlowStackB = FlowStack; + + /** + * Holds if stackA is a subset of stackB, + * The top of stackA is in stackB and the bottom of stackA is then some successor further down stackB. + */ + predicate flowStackIsSubsetOf(FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB) { + exists( + FlowStackA::FlowStackFrame highestStackFrameA, FlowStackB::FlowStackFrame highestStackFrameB + | + highestStackFrameA = flowStackA.getTopFrame() and + highestStackFrameB = flowStackB.getTopFrame() and + // Check if some intermediary frame `intStackFrameB`of StackB is in the stack of highestStackFrameA + exists(FlowStackB::FlowStackFrame intStackFrameB | + intStackFrameB = highestStackFrameB.getASucceedingTerminalStateFrame*() and + sharesCallWith(highestStackFrameA, intStackFrameB) and + sharesCallWith(flowStackA.getTerminalFrame(), + intStackFrameB.getASucceedingTerminalStateFrame*()) + ) + ) + } + + /** + * If the top of stackA is in stackB at any location, and the bottoms of the stack are the same call. + */ + predicate flowStackIsConvergingTerminatingSubsetOf( + FlowStackA::FlowStack flowStackA, FlowStackB::FlowStack flowStackB + ) { + flowStackA.getTerminalFrame().getCall() = flowStackB.getTerminalFrame().getCall() and + exists(FlowStackB::FlowStackFrame intStackFrameB | + intStackFrameB = flowStackB.getTopFrame().getASucceedingTerminalStateFrame*() and + sharesCallWith(flowStackA.getTopFrame(), intStackFrameB) + ) + } + + /** + * Holds if the given FlowStackFrames share the same call. + * i.e. they are both arguments of the same function call. + */ + predicate sharesCallWith(FlowStackA::FlowStackFrame frameA, FlowStackB::FlowStackFrame frameB) { + frameA.getCall() = frameB.getCall() + } + } + + module FlowStack< + DF::Configs::ConfigSig Config, + TaintTrackingStackSig TaintTrackingStack> + { + private module Flow = TT::TaintFlowMake::Global; + + /** + * Determines whether or not the given PathNode is a source + * TODO: Refactor to Flow::PathNode signature + */ + predicate isSource(Flow::PathNode node) { TaintTrackingStack::isSource(node) } + + /** + * Determines whether or not the given PathNode is a sink + * TODO: Refactor to Flow::PathNode signature + */ + predicate isSink(Flow::PathNode node) { not exists(TaintTrackingStack::getASuccessor(node)) } + + /** A FlowStack encapsulates flows between a source and a sink, and all the pathways inbetween (possibly multiple) */ + private newtype FlowStackType = + TFlowStack(Flow::PathNode source, Flow::PathNode sink) { + TaintTrackingStack::isSource(source) and + not exists(TaintTrackingStack::getASuccessor(sink)) and + TaintTrackingStack::getASuccessor*(source) = sink + } + + class FlowStack extends FlowStackType, TFlowStack { + string toString() { result = "FlowStack" } + + /** + * Get the first frame in the DataFlowStack, irregardless of whether or not it has a parent. + */ + FlowStackFrame getFirstFrame() { + exists(FlowStackFrame flowStackFrame, CallFrame frame | + flowStackFrame = TFlowStackFrame(this, frame) and + not exists(frame.getPredecessor()) and + result = flowStackFrame + ) + } + + /** + * Get the top frame in the DataFlowStack, ie the frame that is the highest in the stack for the given flow. + */ + FlowStackFrame getTopFrame() { + exists(FlowStackFrame flowStackFrame | + flowStackFrame = TFlowStackFrame(this, _) and + not exists(flowStackFrame.getParentStackFrame()) and + result = flowStackFrame + ) + } + + /** + * Get the terminal frame in the DataFlowStack, ie the frame that is the end of the flow. + */ + FlowStackFrame getTerminalFrame() { + exists(FlowStackFrame flowStackFrame, CallFrame frame | + flowStackFrame = TFlowStackFrame(this, frame) and + not exists(frame.getSuccessor()) and + result = flowStackFrame + ) + } + } + + FlowStack createFlowStack(Flow::PathNode source, Flow::PathNode sink) { + result = TFlowStack(source, sink) + } + + /** A FlowStackFrame encapsulates a Stack frame that is bound between a given source and sink. */ + private newtype FlowStackFrameType = + TFlowStackFrame(FlowStack flowStack, CallFrame frame) { + exists(Flow::PathNode source, Flow::PathNode sink | + flowStack = TFlowStack(source, sink) and + frame.getPathNode() = TaintTrackingStack::getASuccessor*(source) and + TaintTrackingStack::getASuccessor(frame.getPathNode()) = sink + ) + } + + class FlowStackFrame extends FlowStackFrameType, TFlowStackFrame { + string toString() { result = "FlowStackFrame" } + + /** + * Get the next frame in the DataFlow Stack + */ + FlowStackFrame getASuccessor() { + exists(FlowStack flowStack, CallFrame frame, CallFrame nextFrame | + this = TFlowStackFrame(flowStack, frame) and + nextFrame = frame.getSuccessor() and + result = TFlowStackFrame(flowStack, nextFrame) + ) + } + + /** + * Gets the next FlowStackFrame from the direct descendents that is a frame in the end-state (terminal) stack. + */ + FlowStackFrame getASucceedingTerminalStateFrame() { + result = this.getChildStackFrame() and + // There are no other direct children that are further in the flow + not result.getASuccessor+() = this.getChildStackFrame() + } + + /** + * Gets a predecessor FlowStackFrame of this FlowStackFrame. + */ + FlowStackFrame getAPredecessor() { result.getASuccessor() = this } + + /** + * Gets a predecessor FlowStackFrame that is a parent in the stack. + */ + FlowStackFrame getParentStackFrame() { result.getChildStackFrame() = this } + + /** + * Gets the set of succeeding FlowStackFrame which are a direct descendant of this frame in the Stack. + */ + FlowStackFrame getChildStackFrame() { + exists(FlowStackFrame transitiveSuccessor | + transitiveSuccessor = this.getASuccessor+() and + TaintTrackingStack::getARuntimeTarget(this.getCall()) = + transitiveSuccessor.getCall().getEnclosingCallable() and + result = transitiveSuccessor + ) + } + + /** + * Unpacks the PathNode associated with this FlowStackFrame + */ + Flow::PathNode getPathNode() { + exists(CallFrame callFrame | + this = TFlowStackFrame(_, callFrame) and + result = callFrame.getPathNode() + ) + } + + /** + * Unpacks the DataFlowCall associated with this FlowStackFrame + */ + Lang::DataFlowCall getCall() { result = this.getCallFrame().getCall() } + + /** + * Unpacks the CallFrame associated with this FlowStackFrame + */ + CallFrame getCallFrame() { this = TFlowStackFrame(_, result) } + } + + /** + * A CallFrame is a PathNode that represents a (DataFlowCall/Accessor). + */ + private newtype TCallFrameType = + TCallFrame(Flow::PathNode node) { + exists(Lang::DataFlowCall c | + TaintTrackingStack::getAnArgumentNode(c) = TaintTrackingStack::getNode(node) + ) + } + + /** + * The CallFrame is a PathNode that represents an argument to a Call. + */ + private class CallFrame extends TCallFrameType, TCallFrame { + string toString() { + exists(Lang::DataFlowCall call | + call = this.getCall() and + result = call.toString() + ) + } + + /** + * Find the set of CallFrames that are immediate successors of this CallFrame. + */ + CallFrame getSuccessor() { result = TCallFrame(getSuccessorCall(this.getPathNode())) } + + /** + * Find the set of CallFrames that are an immediate predecessor of this CallFrame. + */ + CallFrame getPredecessor() { + exists(CallFrame prior | + prior.getSuccessor() = this and + result = prior + ) + } + + /** + * Unpack the CallFrame and retrieve the associated DataFlowCall. + */ + Lang::DataFlowCall getCall() { + exists(Lang::DataFlowCall call, Flow::PathNode node | + this = TCallFrame(node) and + TaintTrackingStack::getAnArgumentNode(call) = TaintTrackingStack::getNode(node) and + result = call + ) + } + + /** + * Unpack the CallFrame and retrieve the associated PathNode. + */ + Flow::PathNode getPathNode() { + exists(Flow::PathNode n | + this = TCallFrame(n) and + result = n + ) + } + } + + /** + * From the given PathNode argument, find the set of successors that are an argument in a DataFlowCall, + * and return them as the result. + */ + private Flow::PathNode getSuccessorCall(Flow::PathNode n) { + exists(Flow::PathNode succ | + succ = TaintTrackingStack::getASuccessor(n) and + if + exists(Lang::DataFlowCall c | + TaintTrackingStack::getAnArgumentNode(c) = TaintTrackingStack::getNode(succ) + ) + then result = succ + else result = getSuccessorCall(succ) + ) + } + + /** + * A user-supplied predicate which given a Stack Frame, returns some Node associated with it. + */ + signature Lang::Node extractNodeFromFrame(Flow::PathNode pathNode); + + /** + * Provides some higher-order predicates for analyzing Stacks + */ + module StackFrameAnalysis { + /** + * Find the highest stack frame that satisfies the given predicate, + * and return the Node(s) that the user-supplied predicate returns. + * + * There should be no higher stack frame that satisfies the user-supplied predicate FROM the point that the + * argument . + */ + Lang::Node extractingFromHighestStackFrame(FlowStack flowStack) { + exists(FlowStackFrame topStackFrame, FlowStackFrame someStackFrame | + topStackFrame = flowStack.getTopFrame() and + someStackFrame = topStackFrame.getASuccessor*() and + result = customFrameCond(someStackFrame.getPathNode()) and + not exists(FlowStackFrame predecessor | + predecessor = someStackFrame.getAPredecessor+() and + // The predecessor is *not* prior to the user-given 'top' of the stack frame. + not predecessor = topStackFrame.getAPredecessor+() and + exists(customFrameCond(predecessor.getPathNode())) + ) + ) + } + + /** + * Find the lowest stack frame that satisfies the given predicate, + * and return the Node(s) that the user-supplied predicate returns. + */ + Lang::Node extractingFromLowestStackFrame(FlowStack flowStack) { + exists(FlowStackFrame topStackFrame, FlowStackFrame someStackFrame | + topStackFrame = flowStack.getTopFrame() and + someStackFrame = topStackFrame.getChildStackFrame*() and + result = customFrameCond(someStackFrame.getPathNode()) and + not exists(FlowStackFrame successor | + successor = someStackFrame.getChildStackFrame+() and + exists(customFrameCond(successor.getPathNode())) + ) + ) + } + } + } +} diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowDispatch.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowDispatch.qll index 915334031169..3cbc09c09911 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowDispatch.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowDispatch.qll @@ -65,16 +65,6 @@ class DataFlowCallable extends TDataFlowCallable { Callable::TypeRange getUnderlyingCallable() { result = this.asSummarizedCallable() or result = this.asSourceCallable() } - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCallable c, string file, int startline, int startcolumn | - c.getLocation().hasLocationInfo(file, startline, startcolumn, _, _) - | - c order by file, startline, startcolumn - ) - } } cached @@ -130,23 +120,6 @@ class DataFlowCall extends TDataFlowCall { ) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } - - // #48: Stubs Below - /** Gets an argument to this call as a Node. */ - ArgumentNode getAnArgumentNode(){ none() } // TODO: JB1 return an argument as a DataFlow ArgumentNode - - /** Gets the target of the call, as a DataFlowCallable. */ - DataFlowCallable getARuntimeTarget(){ none() } // TODO getCallTarget() returns `Instruction` - - /** Gets a best-effort total ordering. */ - int totalorder() { - this = - rank[result](DataFlowCall c, int startline, int startcolumn | - c.hasLocationInfo(_, startline, startcolumn, _, _) - | - c order by startline, startcolumn - ) - } } private class NormalCall extends DataFlowCall, TNormalCall {