Skip to content

Commit 9b5897c

Browse files
committed
PS: Improve api graphs.
1 parent 1bd93b9 commit 9b5897c

File tree

6 files changed

+697
-87
lines changed

6 files changed

+697
-87
lines changed

powershell/ql/lib/qlpack.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ dependencies:
1616
dataExtensions:
1717
- semmle/code/powershell/frameworks/**/*.model.yml
1818
- semmle/code/powershell/frameworks/**/*.typemodel.yml
19+
- semmle/code/powershell/frameworks/data/internal/cmdlet.model.yml
20+
- semmle/code/powershell/frameworks/data/internal/alias.model.yml
1921
warnOnImplicitThis: true

powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll

Lines changed: 106 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private import semmle.code.powershell.dataflow.DataFlow
1010
private import semmle.code.powershell.typetracking.ApiGraphShared
1111
private import semmle.code.powershell.typetracking.internal.TypeTrackingImpl
1212
private import semmle.code.powershell.controlflow.Cfg
13+
private import frameworks.data.internal.ApiGraphModels
1314
private import frameworks.data.internal.ApiGraphModelsExtensions as Extensions
1415
private import frameworks.data.internal.ApiGraphModelsSpecific as Specific
1516
private import semmle.code.powershell.dataflow.internal.DataFlowPrivate as DataFlowPrivate
@@ -262,8 +263,7 @@ module API {
262263
this = Impl::MkMethodAccessNode(result) or
263264
this = Impl::MkBackwardNode(result, _) or
264265
this = Impl::MkForwardNode(result, _) or
265-
this = Impl::MkSinkNode(result) or
266-
this = Impl::MkNamespaceOfTypeNameNode(result)
266+
this = Impl::MkSinkNode(result)
267267
}
268268

269269
/** Gets the location of this node. */
@@ -272,6 +272,10 @@ module API {
272272
or
273273
this instanceof RootNode and
274274
result instanceof EmptyLocation
275+
or
276+
not this instanceof RootNode and
277+
not exists(this.getInducingNode()) and
278+
result instanceof EmptyLocation
275279
}
276280

277281
/**
@@ -331,20 +335,84 @@ module API {
331335
override string toString() { result = "SinkNode(" + this.getInducingNode() + ")" }
332336
}
333337

334-
private class UsingNode extends Node, Impl::MkUsingNode {
335-
UsingStmt using; // TODO: This should really be the cfg node, I think
338+
abstract private class AbstractTypeNameNode extends Node {
339+
string prefix;
340+
341+
bindingset[prefix]
342+
AbstractTypeNameNode() { any() }
343+
344+
override string toString() { result = "TypeNameNode(" + this.getTypeName() + ")" }
345+
346+
string getComponent() {
347+
exists(int n |
348+
result = prefix.splitAt(".", n) and
349+
not exists(prefix.splitAt(".", n + 1))
350+
)
351+
}
352+
353+
string getTypeName() { result = prefix }
354+
355+
abstract Node getSuccessor(string name);
356+
357+
Node memberEdge(string name) { none() }
358+
359+
Node methodEdge(string name) { none() }
360+
361+
final predicate isImplicit() { not this.isExplicit(_) }
362+
363+
predicate isExplicit(DataFlow::TypeNameNode typeName) { none() }
364+
}
365+
366+
final class TypeNameNode = AbstractTypeNameNode;
367+
368+
private class ExplicitTypeNameNode extends AbstractTypeNameNode, Impl::MkExplicitTypeNameNode {
369+
ExplicitTypeNameNode() { this = Impl::MkExplicitTypeNameNode(prefix) }
370+
371+
final override Node getSuccessor(string name) {
372+
exists(ExplicitTypeNameNode succ |
373+
succ = Impl::MkExplicitTypeNameNode(prefix + "." + name) and
374+
result = succ
375+
)
376+
or
377+
exists(DataFlow::TypeNameNode typeName, int n, string lowerCaseName |
378+
Specific::needsExplicitTypeNameNode(typeName, prefix) and
379+
lowerCaseName = typeName.getLowerCaseName() and
380+
name = lowerCaseName.splitAt(".", n) and
381+
not lowerCaseName.matches("%.%") and
382+
result = getForwardStartNode(typeName)
383+
)
384+
}
385+
386+
final override predicate isExplicit(DataFlow::TypeNameNode typeName) {
387+
Specific::needsExplicitTypeNameNode(typeName, prefix)
388+
}
389+
}
336390

337-
UsingNode() { this = Impl::MkUsingNode(using) }
391+
private string getAnAlias(string cmdlet) { Specific::aliasModel(cmdlet, result) }
338392

339-
override string toString() { result = "UsingNode(" + using + ")" }
393+
predicate implicitCmdlet(string mod, string cmdlet) {
394+
exists(string cmdlet0 |
395+
Specific::cmdletModel(mod, cmdlet0) and
396+
cmdlet = [cmdlet0, getAnAlias(cmdlet0)]
397+
)
340398
}
341399

342-
private class NamespaceOfTypeNameNode extends Node, Impl::MkNamespaceOfTypeNameNode {
343-
DataFlow::QualifiedTypeNameNode typeName;
400+
private class ImplicitTypeNameNode extends AbstractTypeNameNode, Impl::MkImplicitTypeNameNode {
401+
ImplicitTypeNameNode() { this = Impl::MkImplicitTypeNameNode(prefix) }
402+
403+
final override Node getSuccessor(string name) {
404+
result = Impl::MkImplicitTypeNameNode(prefix + "." + name)
405+
}
344406

345-
NamespaceOfTypeNameNode() { this = Impl::MkNamespaceOfTypeNameNode(typeName) }
407+
final override Node memberEdge(string name) { result = this.methodEdge(name) }
346408

347-
override string toString() { result = "NamespaceOfTypeNameNode(" + typeName + ")" }
409+
final override Node methodEdge(string name) {
410+
exists(DataFlow::CallNode call |
411+
result = Impl::MkMethodAccessNode(call) and
412+
name = call.getLowerCaseName() and
413+
implicitCmdlet(prefix, name)
414+
)
415+
}
348416
}
349417

350418
/**
@@ -438,8 +506,8 @@ module API {
438506
MkRoot() or
439507
/** The method accessed at `call`, synthetically treated as a separate object. */
440508
MkMethodAccessNode(DataFlow::CallNode call) or
441-
MkUsingNode(UsingStmt using) or
442-
MkNamespaceOfTypeNameNode(DataFlow::QualifiedTypeNameNode typeName) or
509+
MkExplicitTypeNameNode(string prefix) { Specific::needsExplicitTypeNameNode(_, prefix) } or
510+
MkImplicitTypeNameNode(string prefix) { Specific::needsImplicitTypeNameNode(prefix) } or
443511
MkForwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
444512
/** Intermediate node for following backward data flow. */
445513
MkBackwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
@@ -455,27 +523,8 @@ module API {
455523
node = any(EntryPoint e).getASink()
456524
}
457525

458-
bindingset[e]
459-
pragma[inline_late]
460-
private DataFlow::Node getNodeFromExpr(Expr e) { result.asExpr().getExpr() = e }
461-
462526
private import frameworks.data.ModelsAsData
463527

464-
cached
465-
predicate namespace(string name, Node node) {
466-
exists(DataFlow::QualifiedTypeNameNode typeName |
467-
typeName.getNamespace() = name and
468-
node = MkNamespaceOfTypeNameNode(typeName)
469-
)
470-
or
471-
exists(UsingStmt using |
472-
using.getName().toLowerCase() = name and
473-
node = MkUsingNode(using)
474-
)
475-
or
476-
node = ModelOutput::getATypeNode(name)
477-
}
478-
479528
cached
480529
predicate topLevelMember(string name, Node node) { memberEdge(root(), name, node) }
481530

@@ -492,42 +541,51 @@ module API {
492541
predicate memberEdge(Node pred, string name, Node succ) {
493542
pred = API::root() and
494543
(
495-
exists(StringConstExpr read |
496-
succ = getForwardStartNode(getNodeFromExpr(read)) and
497-
name = read.getValueString()
498-
)
544+
succ.(TypeNameNode).getTypeName() = name
499545
or
500546
exists(DataFlow::AutomaticVariableNode automatic |
501547
automatic.getLowerCaseName() = name and
502548
succ = getForwardStartNode(automatic)
503549
)
504-
or
505-
succ = getAnImplicitRootMember(name)
506550
)
507551
or
508-
exists(DataFlow::QualifiedTypeNameNode typeName |
509-
typeName.getLowerCaseName() = name and
510-
pred = MkNamespaceOfTypeNameNode(typeName) and
511-
succ = getForwardStartNode(typeName)
552+
exists(TypeNameNode typeName | pred = typeName |
553+
typeName.getSuccessor(name) = succ
554+
or
555+
typeName.memberEdge(name) = succ
512556
)
513557
or
514-
exists(MemberExprReadAccess read |
515-
read.getLowerCaseMemberName().toLowerCase() = name and
516-
pred = getForwardEndNode(getALocalSourceStrict(getNodeFromExpr(read.getQualifier()))) and
517-
succ = getForwardStartNode(getNodeFromExpr(read))
558+
exists(DataFlow::Node qualifier | pred = getForwardEndNode(getALocalSourceStrict(qualifier)) |
559+
exists(CfgNodes::ExprNodes::MemberExprReadAccessCfgNode read |
560+
read.getQualifier() = qualifier.asExpr() and
561+
read.getLowerCaseMemberName() = name and
562+
succ = getForwardStartNode(DataFlow::exprNode(read))
563+
)
564+
or
565+
exists(DataFlow::CallNode call |
566+
call.getLowerCaseName() = name and
567+
call.getQualifier() = qualifier and
568+
succ = MkMethodAccessNode(call)
569+
)
518570
)
519571
}
520572

521573
cached
522574
predicate methodEdge(Node pred, string name, Node succ) {
523575
exists(DataFlow::CallNode call |
524-
succ = MkMethodAccessNode(call) and name = call.getLowerCaseName()
525-
|
576+
succ = MkMethodAccessNode(call) and
577+
name = call.getLowerCaseName() and
526578
pred = getForwardEndNode(getALocalSourceStrict(call.getQualifier()))
527579
)
528580
or
581+
pred.(TypeNameNode).methodEdge(name) = succ
582+
or
529583
pred = API::root() and
530-
succ = getAnImplicitRootMember(name)
584+
exists(DataFlow::CallNode call |
585+
not exists(call.getQualifier()) and
586+
succ = MkMethodAccessNode(call) and
587+
name = call.getLowerCaseName()
588+
)
531589
}
532590

533591
cached

powershell/ql/lib/semmle/code/powershell/frameworks/data/empty.model.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,13 @@ extensions:
3030
pack: microsoft/powershell-all
3131
extensible: typeVariableModel
3232
data: []
33+
34+
- addsTo:
35+
pack: microsoft/powershell-all
36+
extensible: cmdletModel
37+
data: []
38+
39+
- addsTo:
40+
pack: microsoft/powershell-all
41+
extensible: aliasModel
42+
data: []

powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,13 @@ private import codeql.dataflow.internal.AccessPathSyntax
2626
import semmle.code.powershell.ApiGraphs
2727
import semmle.code.powershell.dataflow.DataFlow::DataFlow as DataFlow
2828
private import FlowSummaryImpl::Public
29+
private import semmle.code.powershell.controlflow.Cfg
2930
private import semmle.code.powershell.dataflow.internal.DataFlowDispatch as DataFlowDispatch
3031

32+
extensible predicate cmdletModel(string mod, string cmdlet);
33+
34+
extensible predicate aliasModel(string cmdlet, string alias);
35+
3136
bindingset[rawType]
3237
predicate isTypeUsed(string rawType) { any() }
3338

@@ -40,7 +45,7 @@ private predicate parseType(string rawType, string consts, string suffix) {
4045
)
4146
}
4247

43-
private predicate parseRelevantType(string rawType, string consts, string suffix) {
48+
predicate parseRelevantType(string rawType, string consts, string suffix) {
4449
isRelevantType(rawType) and
4550
parseType(rawType, consts, suffix)
4651
}
@@ -57,58 +62,63 @@ predicate hasImplicitTypeModel(string type, string otherType) {
5762

5863
/** Gets a Powershell-specific interpretation of the `(type, path)` tuple after resolving the first `n` access path tokens. */
5964
bindingset[type, path]
60-
API::Node getExtraNodeFromPath(string type, AccessPath path, int n) {
61-
// A row of form `any;Method[foo]` should match any method named `foo`.
62-
type = "any" and
63-
n = 1 and
64-
exists(string methodName, DataFlow::CallNode call |
65-
methodMatchedByName(path, methodName) and
66-
call.matchesName(methodName) and
67-
result.(API::MethodAccessNode).asCall() = call
65+
API::Node getExtraNodeFromPath(string type, AccessPath path, int n) { none() }
66+
67+
bindingset[type, name, namespace]
68+
private predicate typeMatches(string type, string name, string namespace) {
69+
if namespace = "" then type.matches("%" + name) else type.matches("%" + namespace + "." + name)
70+
}
71+
72+
bindingset[type]
73+
private DataFlow::TypeNameNode getTypeNameNode(string type) {
74+
exists(string name, string namespace |
75+
name = result.getLowerCaseName() and
76+
namespace = result.getNamespace() and
77+
typeMatches(type, name, namespace)
6878
)
6979
}
7080

71-
/**
72-
* Gets a string that represents a module that is always implicitly
73-
* imported in any powershell script.
74-
*/
75-
string getAnImplicitImport() {
76-
result = "microsoft.powershell.management!"
77-
or
78-
result = "microsoft.powershell.utility!"
81+
private string getTypeNameComponent(string type, int index) {
82+
index = [0 .. strictcount(type.indexOf("."))] and
83+
parseRelevantType(_, type, _) and
84+
result =
85+
strictconcat(int i, string s | s = type.splitAt(".", i) and i <= index | s, "." order by i)
86+
}
87+
88+
predicate needsExplicitTypeNameNode(DataFlow::TypeNameNode typeName, string component) {
89+
exists(string type, int index |
90+
component = getTypeNameComponent(type, index) and
91+
typeName = getTypeNameNode(type) and
92+
index = [0 .. strictcount(type.indexOf(".")) - 1]
93+
)
94+
}
95+
96+
predicate needsImplicitTypeNameNode(string component) {
97+
exists(string type, int index |
98+
component = getTypeNameComponent(type, index) and
99+
index = [0 .. strictcount(type.indexOf("."))] and
100+
type.matches("microsoft.powershell.%")
101+
)
79102
}
80103

81104
/** Gets a Powershell-specific interpretation of the given `type`. */
82105
API::Node getExtraNodeFromType(string rawType) {
83-
exists(
84-
string type, string suffix, DataFlow::QualifiedTypeNameNode qualifiedTypeName, string namespace,
85-
string typename
86-
|
106+
exists(string type, string suffix, DataFlow::TypeNameNode typeName |
87107
parseRelevantType(rawType, type, suffix) and
88-
qualifiedTypeName.hasQualifiedName(namespace, typename) and
89-
(namespace + "." + typename).toLowerCase() = type
108+
typeName = getTypeNameNode(type)
90109
|
91110
suffix = "!" and
92-
result = qualifiedTypeName.(DataFlow::LocalSourceNode).track()
111+
result = typeName.(DataFlow::LocalSourceNode).track()
93112
or
94113
suffix = "" and
95-
result = qualifiedTypeName.(DataFlow::LocalSourceNode).track().getInstance()
114+
result = typeName.(DataFlow::LocalSourceNode).track().getInstance()
96115
)
97116
or
98-
rawType = ["", getAnImplicitImport()] and
99-
result = API::root()
100-
}
101-
102-
/**
103-
* Holds if `path` occurs in a CSV row with type `any`, meaning it can start
104-
* matching anywhere, and the path begins with `Method[methodName]`.
105-
*/
106-
private predicate methodMatchedByName(AccessPath path, string methodName) {
107-
isRelevantFullPath("any", path) and
108-
exists(AccessPathToken token |
109-
token = path.getToken(0) and
110-
token.getName() = "Method" and
111-
methodName = token.getAnArgument()
117+
exists(string name, API::TypeNameNode typeName |
118+
parseRelevantType(rawType, name, _) and
119+
typeName = API::getTopLevelMember(name) and
120+
typeName.isImplicit() and
121+
result = typeName
112122
)
113123
}
114124

0 commit comments

Comments
 (0)