Skip to content

Commit e38f630

Browse files
committed
PS: Also support type tracking of objects constructed with New-Object.
1 parent 32f7f1b commit e38f630

File tree

11 files changed

+194
-19
lines changed

11 files changed

+194
-19
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import semmle.code.powershell.dataflow.DataFlow
2+
import semmle.code.powershell.typetracking.internal.TypeTrackingImpl
3+
4+
private module ConsistencyChecksInput implements ConsistencyChecksInputSig {
5+
predicate unreachableNodeExclude(DataFlow::Node n) { n instanceof DataFlow::PostUpdateNode }
6+
}
7+
8+
import ConsistencyChecks<ConsistencyChecksInput>

powershell/ql/lib/powershell.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import semmle.code.powershell.StringConstantExpression
7171
import semmle.code.powershell.MemberExpr
7272
import semmle.code.powershell.InvokeMemberExpression
7373
import semmle.code.powershell.Call
74+
import semmle.code.powershell.ObjectCreation
7475
import semmle.code.powershell.SubExpression
7576
import semmle.code.powershell.ErrorExpr
7677
import semmle.code.powershell.ConvertExpr

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ abstract private class AbstractCall extends Ast {
1212
/** Gets the i'th positional argument to this call. */
1313
abstract Expr getPositionalArgument(int i);
1414

15+
/** Holds if an argument with name `name` is provided to this call. */
16+
final predicate hasNamedArgument(string name) { exists(this.getNamedArgument(name)) }
17+
1518
/** Gets the argument to this call with the name `name`. */
1619
abstract Expr getNamedArgument(string name);
1720

@@ -25,7 +28,8 @@ abstract private class AbstractCall extends Ast {
2528
abstract Function getATarget();
2629
}
2730

28-
private class CmdCall extends AbstractCall instanceof Cmd {
31+
/** A call to a command. For example, `Write-Host "Hello, world!"`. */
32+
class CmdCall extends AbstractCall instanceof Cmd {
2933
final override Expr getCommand() { result = Cmd.super.getCommand() }
3034

3135
final override Expr getPositionalArgument(int i) { result = Cmd.super.getPositionalArgument(i) }
@@ -43,7 +47,8 @@ private class CmdCall extends AbstractCall instanceof Cmd {
4347
}
4448
}
4549

46-
private class InvokeMemberCall extends AbstractCall instanceof InvokeMemberExpr {
50+
/** A call to a method on an object. For example, `$obj.ToString()`. */
51+
class MethodCall extends AbstractCall instanceof InvokeMemberExpr {
4752
final override Expr getCommand() { result = super.getMember() }
4853

4954
final override Expr getPositionalArgument(int i) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class Cmd extends @command, CmdBase {
4949
)
5050
}
5151

52+
/** Holds if this call has an argument named `name`. */
53+
predicate hasNamedArgument(string name) { exists(this.getNamedArgument(name)) }
54+
5255
/** Gets the named argument with the given name. */
5356
Expr getNamedArgument(string name) {
5457
exists(int i, CmdParameter p |

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,30 @@ class InvokeMemberExpr extends @invoke_member_expression, MemberExprBase {
1818
override predicate isStatic() { this.getQualifier() instanceof TypeNameExpr }
1919
}
2020

21+
/**
22+
* A call to a constructor. For example:
23+
*
24+
* ```powershell
25+
* [System.IO.FileInfo]::new("C:\\file.txt")
26+
* ```
27+
*/
2128
class ConstructorCall extends InvokeMemberExpr {
22-
ConstructorCall() { this.isStatic() and this.getName() = "new" }
23-
24-
Type getConstructedType() { result = this.getQualifier().(TypeNameExpr).getType() }
29+
TypeNameExpr typename;
30+
31+
ConstructorCall() {
32+
this.isStatic() and typename = this.getQualifier() and this.getName() = "new"
33+
}
34+
35+
/**
36+
* Gets the type being constructed by this constructor call.
37+
*
38+
* Note that the type may not exist in the database.
39+
*
40+
* Use `getConstructedTypeName` to get the name of the type (which will
41+
* always exist in the database).
42+
*/
43+
Type getConstructedType() { result = typename.getType() }
44+
45+
/** Gets the name of the type being constructed by this constructor call. */
46+
string getConstructedTypeName() { result = typename.getName() }
2547
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import powershell
2+
3+
abstract private class AbstractObjectCreation extends Call {
4+
/**
5+
* The type of the object being constructed.
6+
* Note that the type may not exist in the database.
7+
*
8+
* Use `getConstructedTypeName` to get the name of the type (which will
9+
* always exist in the database).
10+
*/
11+
abstract Type getConstructedType();
12+
13+
/** The name of the type of the object being constructed. */
14+
abstract string getConstructedTypeName();
15+
}
16+
17+
/**
18+
* An object creation from a call to a constructor. For example:
19+
* ```powershell
20+
* [System.IO.FileInfo]::new("C:\\file.txt")
21+
* ```
22+
*/
23+
class NewObjectCreation extends AbstractObjectCreation instanceof ConstructorCall {
24+
final override Type getConstructedType() { result = ConstructorCall.super.getConstructedType() }
25+
26+
final override string getConstructedTypeName() {
27+
result = ConstructorCall.super.getConstructedTypeName()
28+
}
29+
}
30+
31+
/**
32+
* An object creation from a call to `New-Object`. For example:
33+
* ```powershell
34+
* New-Object -TypeName System.IO.FileInfo -ArgumentList "C:\\file.txt"
35+
* ```
36+
*/
37+
class DotNetObjectCreation extends AbstractObjectCreation instanceof Cmd {
38+
DotNetObjectCreation() { this.getCommandName() = "New-Object" }
39+
40+
final override Type getConstructedType() { none() }
41+
42+
final override string getConstructedTypeName() {
43+
// Either it's the named argument `TypeName`
44+
result = Cmd.super.getNamedArgument("TypeName").(StringConstExpr).getValue().getValue()
45+
or
46+
// Or it's the first positional argument if that's the named argument
47+
not Cmd.super.hasNamedArgument("TypeName") and
48+
exists(StringConstExpr arg | arg = Cmd.super.getPositionalArgument(0) |
49+
result = arg.getValue().getValue() and
50+
not arg = Cmd.super.getNamedArgument(["ArgumentList", "Property"])
51+
)
52+
}
53+
}
54+
55+
final class ObjectCreation = AbstractObjectCreation;

powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@ abstract private class AbstractCallCfgNode extends AstCfgNode {
147147

148148
final class CallCfgNode = AbstractCallCfgNode;
149149

150+
class ObjectCreationCfgNode extends CallCfgNode {
151+
ObjectCreation objectCreation;
152+
153+
ObjectCreationCfgNode() { this.getAstNode() = objectCreation }
154+
155+
ObjectCreation getObjectCreation() { result = objectCreation }
156+
157+
Type getConstructedType() { result = objectCreation.getConstructedType() }
158+
159+
string getConstructedTypeName() { result = objectCreation.getConstructedTypeName() }
160+
}
161+
150162
/** Provides classes for control-flow nodes that wrap AST expressions. */
151163
module ExprNodes {
152164
private class VarAccessChildMapping extends ExprChildMapping, VarAccess {
@@ -228,7 +240,7 @@ module ExprNodes {
228240

229241
final override ExprCfgNode getAnArgument() { e.hasCfgChild(e.getAnArgument(), this, result) }
230242

231-
final override string getName() { none() }
243+
final override string getName() { result = e.getName() }
232244

233245
final override ExprCfgNode getCommand() { none() }
234246
}
@@ -249,6 +261,23 @@ module ExprNodes {
249261
InvokeMemberCfgNode getInvokeMember() { this = result.getQualifier() }
250262
}
251263

264+
class TypeNameChildMapping extends ExprChildMapping, TypeNameExpr {
265+
override predicate relevantChild(Ast n) { none() }
266+
}
267+
268+
/** A control-flow node that wraps a `TypeName` expression. */
269+
class TypeNameCfgNode extends ExprCfgNode {
270+
override string getAPrimaryQlClass() { result = "TypeNameCfgNode" }
271+
272+
override TypeNameChildMapping e;
273+
274+
override TypeNameExpr getExpr() { result = super.getExpr() }
275+
276+
Type getType() { result = this.getExpr().getType() }
277+
278+
string getTypeName() { result = this.getExpr().getName() }
279+
}
280+
252281
class ConditionalChildMapping extends ExprChildMapping, ConditionalExpr {
253282
override predicate relevantChild(Ast n) { n = this.getCondition() or n = this.getABranch() }
254283
}
@@ -352,4 +381,19 @@ module StmtNodes {
352381
/** Gets the RHS of this assignment. */
353382
final StmtCfgNode getRightHandSide() { s.hasCfgChild(s.getRightHandSide(), this, result) }
354383
}
384+
385+
class CmdExprChildMapping extends NonExprChildMapping, CmdExpr {
386+
override predicate relevantChild(Ast n) { n = this.getExpr() }
387+
}
388+
389+
/** A control-flow node that wraps a `CmdExpr` expression. */
390+
class CmdExprCfgNode extends StmtCfgNode {
391+
override string getAPrimaryQlClass() { result = "CmdExprCfgNode" }
392+
393+
override CmdExprChildMapping s;
394+
395+
override CmdExpr getStmt() { result = super.getStmt() }
396+
397+
final ExprCfgNode getExpr() { s.hasCfgChild(s.getExpr(), this, result) }
398+
}
355399
}

powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowDispatch.qll

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,34 @@ class NormalCall extends DataFlowCall, TNormalCall {
122122
override Location getLocation() { result = c.getLocation() }
123123
}
124124

125-
/** A call for which we want to compute call targets. */
126-
private class RelevantCall extends CfgNodes::CallCfgNode { }
125+
private predicate localFlowStep(Node nodeFrom, Node nodeTo, StepSummary summary) {
126+
localFlowStepTypeTracker(nodeFrom, nodeTo) and
127+
summary.toString() = "level"
128+
}
127129

128130
private module TrackInstanceInput implements CallGraphConstruction::InputSig {
129-
newtype State = additional MkState(Type m, Boolean exact)
131+
private predicate start0(Node start, string typename, boolean exact) {
132+
start.(ObjectCreationNode).getObjectCreationNode().getConstructedTypeName() = typename and
133+
exact = true
134+
or
135+
start.asExpr().(CfgNodes::ExprNodes::TypeNameCfgNode).getTypeName() = typename and
136+
exact = true
137+
}
138+
139+
newtype State = additional MkState(string typename, Boolean exact) { start0(_, typename, exact) }
130140

131141
predicate start(Node start, State state) {
132-
exists(Type tp, boolean exact | state = MkState(tp, exact) |
133-
start.asExpr().(CfgNodes::ExprNodes::ConstructorCallCfgNode).getConstructedType() = tp
142+
exists(string typename, boolean exact |
143+
state = MkState(typename, exact) and
144+
start0(start, typename, exact)
134145
)
135146
}
136147

137148
pragma[nomagic]
138149
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
139150
smallStepNoCall(nodeFrom, nodeTo, summary)
151+
or
152+
localFlowStep(nodeFrom, nodeTo, summary)
140153
}
141154

142155
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
@@ -155,16 +168,22 @@ private predicate qualifiedCall(CfgNodes::CallCfgNode call, Node receiver, strin
155168
call.getName() = method
156169
}
157170

158-
Node trackInstance(Type t, boolean exact) {
171+
Node trackInstance(string typename, boolean exact) {
159172
result =
160-
CallGraphConstruction::Make<TrackInstanceInput>::track(TrackInstanceInput::MkState(t, exact))
173+
CallGraphConstruction::Make<TrackInstanceInput>::track(TrackInstanceInput::MkState(typename,
174+
exact))
161175
}
162176

163177
private CfgScope getTargetInstance(CfgNodes::CallCfgNode call) {
164-
exists(Node receiver, string method, Type t |
178+
// TODO: Also match argument/parameter types
179+
exists(Node receiver, string method, string typename, Type t |
165180
qualifiedCall(call, receiver, method) and
166-
receiver = trackInstance(t, _) and
167-
result = t.getMemberFunction(method).getBody()
181+
receiver = trackInstance(typename, _) and
182+
t.getName() = typename
183+
|
184+
if method = "new"
185+
then result = t.getAConstructor().getBody()
186+
else result = t.getMethod(method).getBody()
168187
)
169188
}
170189

powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ module LocalFlow {
9494
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ConditionalCfgNode).getABranch()
9595
or
9696
nodeFrom.asStmt() = nodeTo.asStmt().(CfgNodes::StmtNodes::AssignStmtCfgNode).getRightHandSide()
97+
or
98+
nodeFrom.asExpr() = nodeTo.asStmt().(CfgNodes::StmtNodes::CmdExprCfgNode).getExpr()
9799
}
98100

99101
predicate localMustFlowStep(Node nodeFrom, Node nodeTo) {

powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,20 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
228228
none() // TODO
229229
}
230230
}
231+
232+
/**
233+
* A dataflow node that represents the creation of an object.
234+
*
235+
* For example, `[Foo]::new()` or `New-Object Foo`.
236+
*/
237+
class ObjectCreationNode extends Node {
238+
CfgNodes::ObjectCreationCfgNode objectCreation;
239+
240+
ObjectCreationNode() {
241+
this.asExpr() = objectCreation
242+
or
243+
this.asStmt() = objectCreation
244+
}
245+
246+
final CfgNodes::ObjectCreationCfgNode getObjectCreationNode() { result = objectCreation }
247+
}

0 commit comments

Comments
 (0)