diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll index 4be6f7916d7..5893a3e8b3d 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll @@ -107,6 +107,8 @@ module VariableCapture { /** A collection of cached types and predicates to be evaluated in the same stage. */ cached private module Cached { + private import semmle.code.powershell.typetracking.internal.TypeTrackingImpl + cached newtype TNode = TExprNode(CfgNodes::ExprCfgNode n) or @@ -152,6 +154,66 @@ private module Cached { SsaFlow::localFlowStep(_, nodeFrom, nodeTo, _) } + /** + * This is the local flow predicate that is used in type tracking. + */ + cached + predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) { + LocalFlow::localFlowStepCommon(nodeFrom, nodeTo) + or + SsaFlow::localFlowStep(_, nodeFrom, nodeTo, _) + } + + /** Holds if `n` wraps an SSA definition without ingoing flow. */ + private predicate entrySsaDefinition(SsaDefinitionExtNode n) { + n.getDefinitionExt() = + any(SsaImpl::WriteDefinition def | not def.(Ssa::WriteDefinition).assigns(_)) + } + + pragma[nomagic] + private predicate reachedFromExprOrEntrySsaDef(Node n) { + localFlowStepTypeTracker(any(Node n0 | + n0 instanceof ExprNode + or + entrySsaDefinition(n0) + ), n) + or + exists(Node mid | + reachedFromExprOrEntrySsaDef(mid) and + localFlowStepTypeTracker(mid, n) + ) + } + + private predicate isStoreTargetNode(Node n) { + TypeTrackingInput::storeStep(_, n, _) + or + TypeTrackingInput::loadStoreStep(_, n, _, _) + or + TypeTrackingInput::withContentStepImpl(_, n, _) + or + TypeTrackingInput::withoutContentStepImpl(_, n, _) + } + + cached + predicate isLocalSourceNode(Node n) { + n instanceof ParameterNode + or + // Expressions that can't be reached from another entry definition or expression + n instanceof ExprNode and + not reachedFromExprOrEntrySsaDef(n) + or + // Ensure all entry SSA definitions are local sources, except those that correspond + // to parameters (which are themselves local sources) + entrySsaDefinition(n) and + not exists(SsaImpl::ParameterExt p | + p.isInitializedBy(n.(SsaDefinitionExtNode).getDefinitionExt()) + ) + or + isStoreTargetNode(n) + or + TypeTrackingInput::loadStep(_, n, _) + } + cached newtype TContentSet = TSingletonContent(Content c) diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll index 95d4e32ee55..1ea9ef5b892 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll @@ -76,6 +76,13 @@ class ParameterNode extends Node { final Parameter getParameter() { result = getParameter(this) } } +/** + * A data-flow node that is a source of local flow. + */ +class LocalSourceNode extends Node { + LocalSourceNode() { isLocalSourceNode(this) } +} + /** * A node associated with an object after an operation that might have * changed its state. diff --git a/powershell/ql/lib/semmle/code/powershell/typetracking/TypeTracking.qll b/powershell/ql/lib/semmle/code/powershell/typetracking/TypeTracking.qll new file mode 100644 index 00000000000..f9156961ce4 --- /dev/null +++ b/powershell/ql/lib/semmle/code/powershell/typetracking/TypeTracking.qll @@ -0,0 +1,7 @@ +/** + * Provides classes and predicates for simple data-flow reachability suitable + * for tracking types. + */ + +private import semmle.code.powershell.typetracking.internal.TypeTrackingImpl as Impl +import Impl::Shared::TypeTracking diff --git a/powershell/ql/lib/semmle/code/powershell/typetracking/internal/TypeTrackingImpl.qll b/powershell/ql/lib/semmle/code/powershell/typetracking/internal/TypeTrackingImpl.qll new file mode 100644 index 00000000000..b806c9400f2 --- /dev/null +++ b/powershell/ql/lib/semmle/code/powershell/typetracking/internal/TypeTrackingImpl.qll @@ -0,0 +1,244 @@ +import codeql.typetracking.TypeTracking as Shared +import codeql.typetracking.internal.TypeTrackingImpl as SharedImpl +private import powershell +private import semmle.code.powershell.controlflow.Cfg as Cfg +private import Cfg::CfgNodes +private import codeql.typetracking.internal.SummaryTypeTracker as SummaryTypeTracker +private import semmle.code.powershell.dataflow.DataFlow +private import semmle.code.powershell.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon +private import semmle.code.powershell.dataflow.internal.DataFlowPublic as DataFlowPublic +private import semmle.code.powershell.dataflow.internal.DataFlowPrivate as DataFlowPrivate +private import semmle.code.powershell.dataflow.internal.DataFlowDispatch as DataFlowDispatch +private import codeql.util.Unit + +pragma[noinline] +private predicate argumentPositionMatch( + DataFlowDispatch::DataFlowCall call, DataFlowPrivate::ArgumentNode arg, + DataFlowDispatch::ParameterPosition ppos +) { + exists(DataFlowDispatch::ArgumentPosition apos | + arg.argumentOf(call, apos) and + DataFlowDispatch::parameterMatch(ppos, apos) + ) +} + +pragma[noinline] +private predicate viableParam( + DataFlowDispatch::DataFlowCall call, DataFlowPrivate::ParameterNodeImpl p, + DataFlowDispatch::ParameterPosition ppos +) { + exists(DataFlowDispatch::DataFlowCallable callable | + DataFlowDispatch::getTarget(call) = callable.asCfgScope() + | + p.isParameterOf(callable, ppos) + ) +} + +/** Holds if there is flow from `arg` to `p` via the call `call`. */ +pragma[nomagic] +predicate callStep( + DataFlowDispatch::DataFlowCall call, DataFlow::Node arg, DataFlowPrivate::ParameterNodeImpl p +) { + exists(DataFlowDispatch::ParameterPosition pos | + argumentPositionMatch(call, arg, pos) and + viableParam(call, p, pos) + ) +} + +private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { + class Node = DataFlow::Node; + + class Content = DataFlowPublic::ContentSet; + + class ContentFilter = TypeTrackingInput::ContentFilter; + + ContentFilter getFilterFromWithoutContentStep(Content content) { + none() // TODO + } + + ContentFilter getFilterFromWithContentStep(Content content) { + none() // TODO + } + + // Summaries and their stacks + class SummaryComponent extends Unit { + SummaryComponent() { none() } + } + + class SummaryComponentStack extends Unit { + SummaryComponent head() { none() } + } + + SummaryComponentStack singleton(SummaryComponent component) { none() } + + SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) { none() } + + SummaryComponent return() { none() } + + SummaryComponent content(Content contents) { none() } + + SummaryComponent withoutContent(Content contents) { none() } + + SummaryComponent withContent(Content contents) { none() } + + class SummarizedCallable extends Unit { + SummarizedCallable() { none() } + + predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + none() + } + } + + Node argumentOf(Node call, SummaryComponent arg, boolean isPostUpdate) { none() } + + Node parameterOf(Node callable, SummaryComponent param) { none() } + + Node returnOf(Node callable, SummaryComponent return) { none() } + + Node callTo(SummarizedCallable callable) { none() } +} + +private module TypeTrackerSummaryFlow = SummaryTypeTracker::SummaryFlow; + +private newtype TContentFilter = MkElementFilter() + +module TypeTrackingInput implements Shared::TypeTrackingInput { + class Node = DataFlowPublic::Node; + + class LocalSourceNode = DataFlowPublic::LocalSourceNode; + + class Content = DataFlowPublic::ContentSet; + + /** + * A label to use for `WithContent` and `WithoutContent` steps, restricting + * which `ContentSet` may pass through. + */ + class ContentFilter extends TContentFilter { + /** Gets a string representation of this content filter. */ + string toString() { this = MkElementFilter() and result = "elements" } + + /** Gets the content of a type-tracker that matches this filter. */ + Content getAMatchingContent() { + none() // TODO + } + } + + /** + * Holds if a value stored with `storeContents` can be read back with `loadContents`. + */ + pragma[inline] + predicate compatibleContents(Content storeContents, Content loadContents) { + storeContents.getAStoreContent() = loadContents.getAReadContent() + } + + /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ + predicate simpleLocalSmallStep = DataFlowPrivate::localFlowStepTypeTracker/2; + + /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */ + pragma[nomagic] + predicate levelStepNoCall(Node nodeFrom, LocalSourceNode nodeTo) { + TypeTrackerSummaryFlow::levelStepNoCall(nodeFrom, nodeTo) + } + + /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */ + pragma[nomagic] + predicate levelStepCall(Node nodeFrom, LocalSourceNode nodeTo) { none() } + + /** + * Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. + * + * Flow into summarized library methods is not included, as that will lead to negative + * recursion (or, at best, terrible performance), since identifying calls to library + * methods is done using API graphs (which uses type tracking). + */ + predicate callStep(Node nodeFrom, LocalSourceNode nodeTo) { callStep(_, nodeFrom, nodeTo) } + + /** + * Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. + */ + predicate returnStep(Node nodeFrom, LocalSourceNode nodeTo) { + exists(CallCfgNode call | + nodeFrom instanceof DataFlowPrivate::ReturnNode and + nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = + DataFlowDispatch::getTarget(DataFlowDispatch::TNormalCall(call)) and + nodeTo.asExpr().getAstNode() = call.getAstNode() + ) + } + + /** + * Holds if `nodeFrom` is being written to the `contents` of the object + * in `nodeTo`. + * + * Note that the choice of `nodeTo` does not have to make sense + * "chronologically". All we care about is whether the `contents` of + * `nodeTo` can have a specific type, and the assumption is that if a specific + * type appears here, then any access of that particular content can yield + * something of that particular type. + */ + predicate storeStep(Node nodeFrom, Node nodeTo, Content contents) { + DataFlowPrivate::storeStep(nodeFrom, contents, nodeTo) + or + TypeTrackerSummaryFlow::basicStoreStep(nodeFrom, nodeTo, contents) + } + + /** + * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`. + */ + predicate loadStep(Node nodeFrom, LocalSourceNode nodeTo, Content contents) { + DataFlowPrivate::readStep(nodeFrom, contents, nodeTo) + or + TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, contents) + } + + /** + * Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`. + */ + predicate loadStoreStep(Node nodeFrom, Node nodeTo, Content loadContent, Content storeContent) { + TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent) + } + + /** + * Same as `withContentStep`, but `nodeTo` has type `Node` instead of `LocalSourceNode`, + * which allows for it by used in the definition of `LocalSourceNode`. + */ + additional predicate withContentStepImpl(Node nodeFrom, Node nodeTo, ContentFilter filter) { + TypeTrackerSummaryFlow::basicWithContentStep(nodeFrom, nodeTo, filter) + } + + /** + * Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a + * content matched by `filter`. + */ + predicate withContentStep(Node nodeFrom, LocalSourceNode nodeTo, ContentFilter filter) { + withContentStepImpl(nodeFrom, nodeTo, filter) + } + + /** + * Same as `withoutContentStep`, but `nodeTo` has type `Node` instead of `LocalSourceNode`, + * which allows for it by used in the definition of `LocalSourceNode`. + */ + additional predicate withoutContentStepImpl(Node nodeFrom, Node nodeTo, ContentFilter filter) { + TypeTrackerSummaryFlow::basicWithoutContentStep(nodeFrom, nodeTo, filter) + } + + /** + * Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block + * flow of contents matched by `filter` through here. + */ + predicate withoutContentStep(Node nodeFrom, LocalSourceNode nodeTo, ContentFilter filter) { + withoutContentStepImpl(nodeFrom, nodeTo, filter) + } + + /** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ + predicate jumpStep(Node nodeFrom, LocalSourceNode nodeTo) { + DataFlowPrivate::jumpStep(nodeFrom, nodeTo) + } + + predicate hasFeatureBacktrackStoreTarget() { none() } +} + +import SharedImpl::TypeTracking