Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions powershell/ql/lib/qlpack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ dependencies:
dataExtensions:
- semmle/code/powershell/frameworks/**/*.model.yml
- semmle/code/powershell/frameworks/**/*.typemodel.yml
- semmle/code/powershell/frameworks/data/internal/cmdlet.model.yml
- semmle/code/powershell/frameworks/data/internal/alias.model.yml
warnOnImplicitThis: true
235 changes: 106 additions & 129 deletions powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ private import semmle.code.powershell.dataflow.DataFlow
private import semmle.code.powershell.typetracking.ApiGraphShared
private import semmle.code.powershell.typetracking.internal.TypeTrackingImpl
private import semmle.code.powershell.controlflow.Cfg
private import frameworks.data.internal.ApiGraphModels
private import frameworks.data.internal.ApiGraphModelsExtensions as Extensions
private import frameworks.data.internal.ApiGraphModelsSpecific as Specific
private import semmle.code.powershell.dataflow.internal.DataFlowPrivate as DataFlowPrivate
Expand Down Expand Up @@ -204,18 +205,6 @@ module API {
Impl::positionalParameterOrArgumentEdge(this.getAnEpsilonSuccessor(), n, result)
}

/**
* Gets the given keyword parameter of this callable, or keyword argument to this call.
*
* Note: for historical reasons, this predicate may refer to an argument of a call, but this may change in the future.
* When referring to an argument, it is recommended to use `getKeywordArgument(n)` instead.
*/
pragma[inline]
Node getKeywordParameter(string name) {
// This predicate is currently not 'inline_late' because 'name' can be an input or output
Impl::keywordParameterOrArgumentEdge(this.getAnEpsilonSuccessor(), name, result)
}

/**
* Gets the argument passed in argument position `pos` at this call.
*/
Expand Down Expand Up @@ -260,15 +249,6 @@ module API {
result = this.getContent(contents.getAReadContent())
}

/**
* Gets a representative for the instance field of the given `name`.
*/
pragma[inline]
Node getField(string name) {
// This predicate is currently not 'inline_late' because 'name' can be an input or output
Impl::fieldEdge(this.getAnEpsilonSuccessor(), name, result)
}

/**
* Gets a representative for an arbitrary element of this collection.
*/
Expand All @@ -283,8 +263,7 @@ module API {
this = Impl::MkMethodAccessNode(result) or
this = Impl::MkBackwardNode(result, _) or
this = Impl::MkForwardNode(result, _) or
this = Impl::MkSinkNode(result) or
this = Impl::MkNamespaceOfTypeNameNode(result)
this = Impl::MkSinkNode(result)
}

/** Gets the location of this node. */
Expand All @@ -293,6 +272,10 @@ module API {
or
this instanceof RootNode and
result instanceof EmptyLocation
or
not this instanceof RootNode and
not exists(this.getInducingNode()) and
result instanceof EmptyLocation
}

/**
Expand Down Expand Up @@ -352,20 +335,84 @@ module API {
override string toString() { result = "SinkNode(" + this.getInducingNode() + ")" }
}

private class UsingNode extends Node, Impl::MkUsingNode {
UsingStmt using; // TODO: This should really be the cfg node, I think
abstract private class AbstractTypeNameNode extends Node {
string prefix;

bindingset[prefix]
AbstractTypeNameNode() { any() }

override string toString() { result = "TypeNameNode(" + this.getTypeName() + ")" }

string getComponent() {
exists(int n |
result = prefix.splitAt(".", n) and
not exists(prefix.splitAt(".", n + 1))
)
}

string getTypeName() { result = prefix }

abstract Node getSuccessor(string name);

Node memberEdge(string name) { none() }

UsingNode() { this = Impl::MkUsingNode(using) }
Node methodEdge(string name) { none() }

override string toString() { result = "UsingNode(" + using + ")" }
final predicate isImplicit() { not this.isExplicit(_) }

predicate isExplicit(DataFlow::TypeNameNode typeName) { none() }
}

private class NamespaceOfTypeNameNode extends Node, Impl::MkNamespaceOfTypeNameNode {
DataFlow::QualifiedTypeNameNode typeName;
final class TypeNameNode = AbstractTypeNameNode;

private class ExplicitTypeNameNode extends AbstractTypeNameNode, Impl::MkExplicitTypeNameNode {
ExplicitTypeNameNode() { this = Impl::MkExplicitTypeNameNode(prefix) }

final override Node getSuccessor(string name) {
exists(ExplicitTypeNameNode succ |
succ = Impl::MkExplicitTypeNameNode(prefix + "." + name) and
result = succ
)
or
exists(DataFlow::TypeNameNode typeName, int n, string lowerCaseName |
Specific::needsExplicitTypeNameNode(typeName, prefix) and
lowerCaseName = typeName.getLowerCaseName() and
name = lowerCaseName.splitAt(".", n) and
not lowerCaseName.matches("%.%") and
result = getForwardStartNode(typeName)
)
}

final override predicate isExplicit(DataFlow::TypeNameNode typeName) {
Specific::needsExplicitTypeNameNode(typeName, prefix)
}
}

NamespaceOfTypeNameNode() { this = Impl::MkNamespaceOfTypeNameNode(typeName) }
private string getAnAlias(string cmdlet) { Specific::aliasModel(cmdlet, result) }

override string toString() { result = "NamespaceOfTypeNameNode(" + typeName + ")" }
predicate implicitCmdlet(string mod, string cmdlet) {
exists(string cmdlet0 |
Specific::cmdletModel(mod, cmdlet0) and
cmdlet = [cmdlet0, getAnAlias(cmdlet0)]
)
}

private class ImplicitTypeNameNode extends AbstractTypeNameNode, Impl::MkImplicitTypeNameNode {
ImplicitTypeNameNode() { this = Impl::MkImplicitTypeNameNode(prefix) }

final override Node getSuccessor(string name) {
result = Impl::MkImplicitTypeNameNode(prefix + "." + name)
}

final override Node memberEdge(string name) { result = this.methodEdge(name) }

final override Node methodEdge(string name) {
exists(DataFlow::CallNode call |
result = Impl::MkMethodAccessNode(call) and
name = call.getLowerCaseName() and
implicitCmdlet(prefix, name)
)
}
}

/**
Expand Down Expand Up @@ -405,13 +452,6 @@ module API {
/** Gets the root node. */
Node root() { result instanceof RootNode }

bindingset[name]
pragma[inline_late]
Node namespace(string name) {
// This predicate is currently not 'inline_late' because 'n' can be an input or output
Impl::namespace(name, result)
}

pragma[inline]
Node getTopLevelMember(string name) { Impl::topLevelMember(name, result) }

Expand Down Expand Up @@ -466,8 +506,8 @@ module API {
MkRoot() or
/** The method accessed at `call`, synthetically treated as a separate object. */
MkMethodAccessNode(DataFlow::CallNode call) or
MkUsingNode(UsingStmt using) or
MkNamespaceOfTypeNameNode(DataFlow::QualifiedTypeNameNode typeName) or
MkExplicitTypeNameNode(string prefix) { Specific::needsExplicitTypeNameNode(_, prefix) } or
MkImplicitTypeNameNode(string prefix) { Specific::needsImplicitTypeNameNode(prefix) } or
MkForwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
/** Intermediate node for following backward data flow. */
MkBackwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
Expand All @@ -483,27 +523,8 @@ module API {
node = any(EntryPoint e).getASink()
}

bindingset[e]
pragma[inline_late]
private DataFlow::Node getNodeFromExpr(Expr e) { result.asExpr().getExpr() = e }

private import frameworks.data.ModelsAsData

cached
predicate namespace(string name, Node node) {
exists(DataFlow::QualifiedTypeNameNode typeName |
typeName.getNamespace() = name and
node = MkNamespaceOfTypeNameNode(typeName)
)
or
exists(UsingStmt using |
using.getName().toLowerCase() = name and
node = MkUsingNode(using)
)
or
node = ModelOutput::getATypeNode(name)
}

cached
predicate topLevelMember(string name, Node node) { memberEdge(root(), name, node) }

Expand All @@ -516,83 +537,55 @@ module API {
)
}

cached
predicate callEdge(Node pred, string name, Node succ) {
exists(DataFlow::CallNode call |
// from receiver to method call node
pred = getForwardEndNode(getALocalSourceStrict(call.getQualifier())) and
succ = MkMethodAccessNode(call) and
name = call.getLowerCaseName()
)
}

bindingset[name]
private string memberOrMethodReturnValue(string name) {
// This predicate is a bit ad-hoc, but it's okay for now.
// We can delete it once we no longer use the typeModel and summaryModel
// tables to represent implicit root members.
result = "Method[" + name + "]"
or
result = "Method[" + name + "].ReturnValue"
or
result = "Member[" + name + "]"
}

private Node getAnImplicitRootMember(string name) {
exists(DataFlow::CallNode call |
Extensions::typeModel(_, Specific::getAnImplicitImport(), memberOrMethodReturnValue(name))
or
Extensions::summaryModel(Specific::getAnImplicitImport(), memberOrMethodReturnValue(name),
_, _, _, _)
or
Extensions::sourceModel(Specific::getAnImplicitImport(), memberOrMethodReturnValue(name), _,
_)
|
result = MkMethodAccessNode(call) and
name = call.getLowerCaseName()
)
}

cached
predicate memberEdge(Node pred, string name, Node succ) {
pred = API::root() and
(
exists(StringConstExpr read |
succ = getForwardStartNode(getNodeFromExpr(read)) and
name = read.getValueString()
)
succ.(TypeNameNode).getTypeName() = name
or
exists(DataFlow::AutomaticVariableNode automatic |
automatic.getLowerCaseName() = name and
succ = getForwardStartNode(automatic)
)
or
succ = getAnImplicitRootMember(name)
)
or
exists(DataFlow::QualifiedTypeNameNode typeName |
typeName.getLowerCaseName() = name and
pred = MkNamespaceOfTypeNameNode(typeName) and
succ = getForwardStartNode(typeName)
exists(TypeNameNode typeName | pred = typeName |
typeName.getSuccessor(name) = succ
or
typeName.memberEdge(name) = succ
)
or
exists(MemberExprReadAccess read |
read.getLowerCaseMemberName().toLowerCase() = name and
pred = getForwardEndNode(getALocalSourceStrict(getNodeFromExpr(read.getQualifier()))) and
succ = getForwardStartNode(getNodeFromExpr(read))
exists(DataFlow::Node qualifier | pred = getForwardEndNode(getALocalSourceStrict(qualifier)) |
exists(CfgNodes::ExprNodes::MemberExprReadAccessCfgNode read |
read.getQualifier() = qualifier.asExpr() and
read.getLowerCaseMemberName() = name and
succ = getForwardStartNode(DataFlow::exprNode(read))
)
or
exists(DataFlow::CallNode call |
call.getLowerCaseName() = name and
call.getQualifier() = qualifier and
succ = MkMethodAccessNode(call)
)
)
}

cached
predicate methodEdge(Node pred, string name, Node succ) {
exists(DataFlow::CallNode call |
succ = MkMethodAccessNode(call) and name = call.getLowerCaseName()
|
succ = MkMethodAccessNode(call) and
name = call.getLowerCaseName() and
pred = getForwardEndNode(getALocalSourceStrict(call.getQualifier()))
)
or
pred.(TypeNameNode).methodEdge(name) = succ
or
pred = API::root() and
succ = getAnImplicitRootMember(name)
exists(DataFlow::CallNode call |
not exists(call.getQualifier()) and
succ = MkMethodAccessNode(call) and
name = call.getLowerCaseName()
)
}

cached
Expand All @@ -617,11 +610,6 @@ module API {
)
}

cached
predicate fieldEdge(Node pred, string name, Node succ) {
Impl::contentEdge(pred, DataFlowPrivate::TFieldContent(name), succ)
}

cached
predicate elementEdge(Node pred, Node succ) {
contentEdge(pred, any(DataFlow::ContentSet set | set.isAnyElement()).getAReadContent(), succ)
Expand Down Expand Up @@ -665,24 +653,13 @@ module API {
), succ)
}

private predicate keywordParameterEdge(Node pred, string name, Node succ) {
parameterEdge(pred, any(DataFlowDispatch::ParameterPosition pos | pos.isKeyword(name)), succ)
}

cached
predicate positionalParameterOrArgumentEdge(Node pred, int n, Node succ) {
positionalArgumentEdge(pred, n, succ)
or
positionalParameterEdge(pred, n, succ)
}

cached
predicate keywordParameterOrArgumentEdge(Node pred, string name, Node succ) {
keywordArgumentEdge(pred, name, succ)
or
keywordParameterEdge(pred, name, succ)
}

cached
predicate instanceEdge(Node pred, Node succ) {
// TODO: Also model parameters with a given type here
Expand Down
15 changes: 0 additions & 15 deletions powershell/ql/lib/semmle/code/powershell/dataflow/FlowSummary.qll
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,3 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari
)
}
}

/**
* A callable with a flow summary, identified by a unique string, where all
* calls to a method with the same name are considered relevant.
*/
abstract class SimpleSummarizedCallable extends SummarizedCallable {
CallExpr c;

bindingset[this]
SimpleSummarizedCallable() { c.getLowerCaseName() = this }

final override CallExpr getACall() { result = c }

final override CallExpr getACallSimple() { result = c }
}
Loading
Loading