From 2aa84f4b1f7bfb82af6403c741b19446c3055b70 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 21 Jan 2026 17:10:39 +0000 Subject: [PATCH 1/6] PS: Fix bug in 'getConstructedTypeExpr' and add a helper predicate to 'ObjectCreationNode'. --- .../semmle/code/powershell/ast/internal/ObjectCreation.qll | 7 +++---- .../code/powershell/dataflow/internal/DataFlowPublic.qll | 4 ++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/ObjectCreation.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/ObjectCreation.qll index d5a51be00a37..4deb68146d57 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/ObjectCreation.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/ObjectCreation.qll @@ -46,12 +46,11 @@ class DotNetObjectCreation extends AbstractObjectCreation, CmdCall { final override Expr getConstructedTypeExpr() { // Either it's the named argument `TypeName` - result = CmdCall.super.getNamedArgument("TypeName") + result = CmdCall.super.getNamedArgument("typename") or // Or it's the first positional argument if that's the named argument - not CmdCall.super.hasNamedArgument("TypeName") and - result = CmdCall.super.getPositionalArgument(0) and - result = CmdCall.super.getNamedArgument(["ArgumentList", "Property"]) + not CmdCall.super.hasNamedArgument("typename") and + result = CmdCall.super.getPositionalArgument(0) } } 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 3a194aada561..ed8b46891302 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll @@ -500,6 +500,10 @@ class ObjectCreationNode extends CallNode { string getAConstructedTypeName() { result = this.getObjectCreationNode().getAConstructedTypeName() } + + string getLowerCaseConstructedTypeName() { + result = this.getObjectCreationNode().getLowerCaseConstructedTypeName() + } } /** A call, viewed as a node in a data flow graph. */ From b3e1f57ec8b4d6162848b34e0f6ef6ca89c6794e Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 21 Jan 2026 17:15:02 +0000 Subject: [PATCH 2/6] PS: Add intermediate api graph nodes for 'New-Object' calls similar to what we have for type name expressions. --- .../lib/semmle/code/powershell/ApiGraphs.qll | 19 ++++++++++++++++--- .../data/internal/ApiGraphModelsSpecific.qll | 10 ++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll index 5e04b6f75795..d8092576136a 100644 --- a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll +++ b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll @@ -369,7 +369,7 @@ module API { final predicate isImplicit() { not this.isExplicit(_) } - predicate isExplicit(DataFlow::TypeNameNode typeName) { none() } + predicate isExplicit(DataFlow::Node node) { none() } } final class TypeNameNode = AbstractTypeNameNode; @@ -392,8 +392,8 @@ module API { ) } - final override predicate isExplicit(DataFlow::TypeNameNode typeName) { - Specific::needsExplicitTypeNameNode(typeName, prefix) + final override predicate isExplicit(DataFlow::Node node) { + Specific::needsExplicitTypeNameNode(node, prefix) } } @@ -424,6 +424,18 @@ module API { } } + class NewObjectTypeNameNode extends AbstractTypeNameNode, Impl::MkNewObjectTypeNameNode { + NewObjectTypeNameNode() { this = Impl::MkNewObjectTypeNameNode(prefix) } + + final override Node getSuccessor(string name) { + result = Impl::MkNewObjectTypeNameNode(prefix + "." + name) + } + + final override predicate isExplicit(DataFlow::Node node) { + Specific::needsNewObjectTypeNameNode(node, prefix) + } + } + /** * An API entry point. * @@ -517,6 +529,7 @@ module API { MkMethodAccessNode(DataFlow::CallNode call) or MkExplicitTypeNameNode(string prefix) { Specific::needsExplicitTypeNameNode(_, prefix) } or MkImplicitTypeNameNode(string prefix) { Specific::needsImplicitTypeNameNode(prefix) } or + MkNewObjectTypeNameNode(string prefix) { Specific::needsNewObjectTypeNameNode(_, 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 diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll b/powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll index 58d4c8323787..cc1bec656997 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -101,6 +101,16 @@ predicate needsImplicitTypeNameNode(string component) { ) } +predicate needsNewObjectTypeNameNode(DataFlow::ObjectCreationNode creation, string component) { + creation.asExpr().getExpr() instanceof DotNetObjectCreation and + exists(string type, int index | + type = creation.getLowerCaseConstructedTypeName() and + index = [0 .. strictcount(type.indexOf("."))] and + component = + strictconcat(int i, string s | s = type.splitAt(".", i) and i <= index | s, "." order by i) + ) +} + /** Gets a Powershell-specific interpretation of the given `type`. */ API::Node getExtraNodeFromType(string rawType) { exists(string type, string suffix, DataFlow::TypeNameNode typeName | From 0b06fcdedb86d3477611816b147ab7af640712bf Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 21 Jan 2026 17:16:37 +0000 Subject: [PATCH 3/6] PS: Add instance edges for New-Object in API graphs. --- .../ql/lib/semmle/code/powershell/ApiGraphs.qll | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll index d8092576136a..616c042ed0d7 100644 --- a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll +++ b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll @@ -367,6 +367,8 @@ module API { Node methodEdge(string name) { none() } + Node instanceEdge() { none() } + final predicate isImplicit() { not this.isExplicit(_) } predicate isExplicit(DataFlow::Node node) { none() } @@ -431,6 +433,14 @@ module API { result = Impl::MkNewObjectTypeNameNode(prefix + "." + name) } + final override Node instanceEdge() { + exists(DataFlow::ObjectCreationNode creation | + prefix = creation.getLowerCaseConstructedTypeName() and + Specific::needsNewObjectTypeNameNode(creation, prefix) and + result = getForwardStartNode(creation) + ) + } + final override predicate isExplicit(DataFlow::Node node) { Specific::needsNewObjectTypeNameNode(node, prefix) } @@ -698,6 +708,8 @@ module API { pred = getForwardEndNode(objCreation.getConstructedTypeNode()) and succ = getForwardStartNode(objCreation) ) + or + pred.(TypeNameNode).instanceEdge() = succ } cached From 8c883e9a8b901b13fcee99c3d9bd0eca81e9b6aa Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 21 Jan 2026 17:17:13 +0000 Subject: [PATCH 4/6] PS: Add instance edges for static constructor-like functions. --- .../ql/lib/semmle/code/powershell/ApiGraphs.qll | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll index 616c042ed0d7..c97f1078390f 100644 --- a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll +++ b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll @@ -701,6 +701,13 @@ module API { positionalParameterEdge(pred, n, succ) } + pragma[nomagic] + private DataFlow::CallNode getStaticConstructorLikeCall() { + exists(string type | + typeModel(type, type + "!", "Method[" + result.getLowerCaseName() + "].ReturnValue") + ) + } + cached predicate instanceEdge(Node pred, Node succ) { // TODO: Also model parameters with a given type here @@ -709,6 +716,12 @@ module API { succ = getForwardStartNode(objCreation) ) or + exists(DataFlow::CallNode call | + call = getStaticConstructorLikeCall() and + pred = getForwardEndNode(call.getQualifier()) and + succ = getForwardStartNode(call) + ) + or pred.(TypeNameNode).instanceEdge() = succ } From 634f7b03da4200fd48c9ce8f2e630e0f8e6f62c8 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 21 Jan 2026 17:21:47 +0000 Subject: [PATCH 5/6] PS: Allow non-read member access from 'memberEdge'. --- powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll index c97f1078390f..d194fbc80cbf 100644 --- a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll +++ b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll @@ -588,7 +588,7 @@ module API { ) or exists(DataFlow::Node qualifier | pred = getForwardEndNode(getALocalSourceStrict(qualifier)) | - exists(CfgNodes::ExprNodes::MemberExprReadAccessCfgNode read | + exists(CfgNodes::ExprNodes::MemberExprCfgNode read | read.getQualifier() = qualifier.asExpr() and read.getLowerCaseMemberName() = name and succ = getForwardStartNode(DataFlow::exprNode(read)) From 6c7715b14bead64f55e92df8081677f28208bcee Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 21 Jan 2026 17:31:13 +0000 Subject: [PATCH 6/6] PS: Accept test changes. --- .../ql/test/library-tests/dataflow/typetracking/test.expected | 2 -- .../ql/test/library-tests/dataflow/typetracking/test.ps1 | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/powershell/ql/test/library-tests/dataflow/typetracking/test.expected b/powershell/ql/test/library-tests/dataflow/typetracking/test.expected index 8748ef879ad2..e69de29bb2d1 100644 --- a/powershell/ql/test/library-tests/dataflow/typetracking/test.expected +++ b/powershell/ql/test/library-tests/dataflow/typetracking/test.expected @@ -1,2 +0,0 @@ -| test.ps1:15:20:15:36 | # $ type=PSObject | Missing result: type=PSObject | -| test.ps1:19:25:19:41 | # $ type=PSObject | Missing result: type=PSObject | diff --git a/powershell/ql/test/library-tests/dataflow/typetracking/test.ps1 b/powershell/ql/test/library-tests/dataflow/typetracking/test.ps1 index e9a36131c400..7680cbbcd083 100644 --- a/powershell/ql/test/library-tests/dataflow/typetracking/test.ps1 +++ b/powershell/ql/test/library-tests/dataflow/typetracking/test.ps1 @@ -12,8 +12,8 @@ Sink $myClass # $ type=myclass $withNamedArg = New-Object -TypeName PSObject -Sink $withNamedArg # $ type=PSObject +Sink $withNamedArg # $ type=psobject $withPositionalArg = New-Object PSObject -Sink $withPositionalArg # $ type=PSObject +Sink $withPositionalArg # $ type=psobject