diff --git a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll index 645c62e773e..2f4a8ec9fe7 100644 --- a/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll +++ b/powershell/ql/lib/semmle/code/powershell/ApiGraphs.qll @@ -326,19 +326,25 @@ module API { /** A node representing a module/class object with epsilon edges to its descendents. */ private class ModuleNode extends Node, Impl::MkModule { - /** Gets the module represented by this API node. */ - string getModule() { this = Impl::MkModule(result) } + string qualifiedModule; + int n; + + ModuleNode() { this = Impl::MkModule(qualifiedModule, n) } + + ModuleNode getNext() { result = Impl::MkModule(qualifiedModule, n + 1) } + + ModuleNode getPred() { result.getNext() = this } + + string getComponent() { result = qualifiedModule.splitAt(".", n) } + + string getModule() { + not exists(this.getPred()) and + result = this.getComponent() + or + result = this.getPred().getModule() + "." + this.getComponent() + } override string toString() { result = "Module(" + this.getModule() + ")" } - - TypeNode getType(string name) { result.getType() = this.getModule() + "." + name } // TODO: Check that name exists in module - } - - private class TypeNode extends Node, Impl::MkType { - /** Gets the type represented by this API node. */ - string getType() { this = Impl::MkType(result) } - - override string toString() { result = "Type(" + this.getType() + ")" } } /** A node representing instances of a module/class with epsilon edges to its ancestors. */ @@ -413,13 +419,7 @@ module API { * Gets the node that represents the module with qualified * name `qualifiedModule`. */ - ModuleNode mod(string qualifiedModule) { result = Impl::MkModule(qualifiedModule) } - - /** - * Gets the node that represents the type with qualified - * name `qualifiedType`. - */ - TypeNode type(string qualifiedType) { result = Impl::MkType(qualifiedType) } + ModuleNode mod(string qualifiedModule, int n) { result = Impl::MkModule(qualifiedModule, n) } /** * Gets an unqualified call at the top-level with the given method name. @@ -466,26 +466,31 @@ module API { cached private module Impl { + + private predicate isModule(string s, int n) { + ( + any(UsingStmt using).getName() = s + or + any(Cmd cmd).getNamespaceQualifier() = s + or + any(TypeNameExpr tn).getName() = s + or + any(ModuleManifest manifest).getModuleName() = s + ) and + exists(s.splitAt(".", n)) + } + cached newtype TApiNode = /** The root of the API graph. */ MkRoot() or /** The method accessed at `call`, synthetically treated as a separate object. */ MkMethodAccessNode(DataFlow::CallNode call) or - MkModule(string qualifiedModule) { - any(UsingStmt using).getName() = qualifiedModule - or - any(Cmd cmd).getNamespaceQualifier() = qualifiedModule - or - any(TypeNameExpr tn).getName() = qualifiedModule - or - any(ModuleManifest manifest).getModuleName() = qualifiedModule - } or - MkType(string qualifiedType) { any(ConstantValue cv).asString() = qualifiedType } or // TODO + MkModule(string qualifiedModule, int n) { isModule(qualifiedModule, n) } or /** Instances of `mod` with epsilon edges to its ancestors. */ - MkInstanceUp(string qualifiedType) { exists(MkType(qualifiedType)) } or + MkInstanceUp(string qualifiedType) { exists(MkModule(qualifiedType, _)) } or /** Instances of `mod` with epsilon edges to its descendents, and to its upward node. */ - MkInstanceDown(string qualifiedType) { exists(MkType(qualifiedType)) } or + MkInstanceDown(string qualifiedType) { exists(MkModule(qualifiedType, _)) } or /** Intermediate node for following forward data flow. */ MkForwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or /** Intermediate node for following backward data flow. */ @@ -525,14 +530,6 @@ module API { ) } - cached - predicate typeEdge(Node pred, string name, Node succ) { - exists(ModuleNode mod | - pred = mod and - succ = mod.getType(name) - ) - } - cached predicate memberEdge(Node pred, string name, Node succ) { exists(MemberExpr member | succ = getForwardStartNode(getNodeFromExpr(member)) | @@ -546,8 +543,9 @@ module API { exists(DataFlow::CallNode call | succ = MkMethodAccessNode(call) and name = call.getName() | pred = getForwardEndNode(getALocalSourceStrict(call.getQualifier())) or - exists(string qualifiedModule, ModuleManifest manifest | - pred = mod(qualifiedModule) and + exists(string qualifiedModule, ModuleManifest manifest, int n | + pred = mod(qualifiedModule, n) and + not exists(mod(qualifiedModule, n + 1)) and manifest.getModuleName() = qualifiedModule | manifest.getACmdLetToExport() = name @@ -647,8 +645,15 @@ module API { cached predicate instanceEdge(Node pred, Node succ) { - // An instance of a type - exists(string qualifiedType | pred = MkType(qualifiedType) | + exists(string qualifiedType, int n | + pred = MkModule(qualifiedType, n) and + not exists(MkModule(qualifiedType, n + 1)) + | + exists(DataFlow::TypeNameNode typeName | + typeName.getTypeName() = qualifiedType and + succ = getForwardStartNode(typeName) + ) + or exists(DataFlow::ObjectCreationNode objCreation | objCreation.getConstructedTypeName() = qualifiedType and succ = getForwardStartNode(objCreation) @@ -659,15 +664,6 @@ module API { succ = getForwardStartNode(p) ) ) - or - // A use of a module (or static type?) - // TODO: Consider implicit module qualiifers and use instance on all of them - exists(string qualifiedType, DataFlow::TypeNameNode typeName | - pred = MkModule(qualifiedType) and - typeName.getTypeName() = qualifiedType - | - succ = getForwardStartNode(typeName) - ) } cached diff --git a/powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll b/powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll index cd48e0a36ba..8c8efcbb42d 100644 --- a/powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll +++ b/powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll @@ -610,7 +610,13 @@ module StmtNodes { final override ExprCfgNode getCommand() { s.hasCfgChild(s.getCommand(), this, result) } - final override string getName() { result = s.getCmdName().getValue().getValue() } + final override string getName() { result = s.getCommandName() } + + /** Holds if the command is qualified. */ + predicate isQualified() { s.isQualified() } + + /** Gets the namespace qualifier of this command, if any. */ + string getNamespaceQualifier() { result = s.getNamespaceQualifier() } } /** A control-flow node that wraps a call to operator `&` */ 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 d051ebde7d6..bbd17e0574b 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll @@ -202,6 +202,7 @@ private module Cached { isProcessPropertyByNameNode(iter, _) } or TScriptBlockNode(ScriptBlock scriptBlock) or + TTypePathNode(int n, CfgNode cfg) { isTypePathNode(_, n, cfg) } or TForbiddenRecursionGuard() { none() and // We want to prune irrelevant models before materialising data flow nodes, so types contributed @@ -1148,6 +1149,64 @@ class ScriptBlockNode extends TScriptBlockNode, NodeImpl { override predicate nodeIsHidden() { any() } } +private predicate isTypePathNode(string type, int n, CfgNode cfg) { + exists(CfgNodes::ExprNodes::TypeNameCfgNode typeName, string s | + cfg = typeName and + type = typeName.getTypeName() and + s = type.splitAt(".", n) + ) + or + exists(CfgNodes::StmtNodes::CmdCfgNode cmd, string s | + cfg = cmd.getCommand() and + type = cmd.getNamespaceQualifier() and + s = type.splitAt(".", n) + ) +} + +/** + * A dataflow node that represents a component of a type or module path. + * + * For example, `System`, `System.Management`, `System.Management.Automation`, + * and `System.Management.Automation.PowerShell` in the type + * name `[System.Management.Automation.PowerShell]`. + */ +class TypePathNodeImpl extends TTypePathNode, NodeImpl { + int n; + CfgNode cfg; + + TypePathNodeImpl() { this = TTypePathNode(n, cfg) } + + string getType() { isTypePathNode(result, n, cfg) } + + predicate isComplete() { not exists(this.getNext()) } + + int getIndex() { result = n } + + string getComponent() { result = this.getType().splitAt(".", n) } + + override CfgScope getCfgScope() { result = cfg.getScope() } + + override Location getLocationImpl() { result = cfg.getLocation() } + + override string toStringImpl() { + not exists(this.getPrev()) and + result = this.getComponent() + or + result = this.getPrev() + "." + this.getComponent() + } + + override predicate nodeIsHidden() { any() } + + TypePathNodeImpl getNext() { result = TTypePathNode(n + 1, cfg) } + + TypePathNodeImpl getPrev() { result.getNext() = this } + + TypePathNodeImpl getConstant(string s) { + s = result.getComponent() and + result = this.getNext() + } +} + /** A node that performs a type cast. */ class CastNode extends Node { CastNode() { none() } 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 45d3c5ddc39..b8ae6a48e38 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPublic.qll @@ -193,6 +193,21 @@ class PostUpdateNode extends Node { Node getPreUpdateNode() { result = pre } } +/** + * A dataflow node that represents a component of a type or module path. + * + * For example, `System`, `System.Management`, `System.Management.Automation`, + * and `System.Management.Automation.PowerShell` in the type + * name `[System.Management.Automation.PowerShell]`. + */ +class TypePathNode extends Node instanceof TypePathNodeImpl { + string getComponent() { result = super.getComponent() } + + TypePathNode getConstant(string s) { result = super.getConstant(s) } + + API::Node track() { result = API::mod(super.getType(), super.getIndex()) } +} + cached private module Cached { cached diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftPowershellUtility/model.yml b/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftPowershellUtility/model.yml index 3db1b7e4d58..bd653b0d327 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftPowershellUtility/model.yml +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftPowershellUtility/model.yml @@ -3,4 +3,4 @@ extensions: pack: microsoft-sdl/powershell-all extensible: sourceModel data: - - ["Microsoft.PowerShell.Utility", "Method[Read-Host].ReturnValue", "stdin"] + - ["Microsoft.PowerShell.Utility!", "Method[Read-Host].ReturnValue", "stdin"] diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftWin32Registry/model.yml b/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftWin32Registry/model.yml index 567209acd71..edbe11bbaa1 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftWin32Registry/model.yml +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftWin32Registry/model.yml @@ -3,4 +3,4 @@ extensions: pack: microsoft-sdl/powershell-all extensible: sourceModel data: - - ["Microsoft.Win32.Registry", "Method[GetValue]", "windows-registry"] \ No newline at end of file + - ["Microsoft.Win32.Registry!", "Method[GetValue]", "windows-registry"] \ No newline at end of file diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftWin32RegistryKey/model.yml b/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftWin32RegistryKey/model.yml index af7e5e3608d..b0b6a91b1b3 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftWin32RegistryKey/model.yml +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/MicrosoftWin32RegistryKey/model.yml @@ -3,6 +3,6 @@ extensions: pack: microsoft-sdl/powershell-all extensible: sourceModel data: - - ["Microsoft.Win32.RegistryKey", "Instance.Method[GetValue].ReturnValue", "windows-registry"] - - ["Microsoft.Win32.RegistryKey", "Instance.Method[GetValueNames].ReturnValue", "windows-registry"] - - ["Microsoft.Win32.RegistryKey", "Instance.Method[GetSubKeyNames].ReturnValue", "windows-registry"] \ No newline at end of file + - ["Microsoft.Win32.RegistryKey", "Method[GetValue].ReturnValue", "windows-registry"] + - ["Microsoft.Win32.RegistryKey", "Method[GetValueNames].ReturnValue", "windows-registry"] + - ["Microsoft.Win32.RegistryKey", "Method[GetSubKeyNames].ReturnValue", "windows-registry"] \ No newline at end of file diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/SystemEnvironment/model.yml b/powershell/ql/lib/semmle/code/powershell/frameworks/SystemEnvironment/model.yml index 4d5a13178aa..c85e710be62 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/SystemEnvironment/model.yml +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/SystemEnvironment/model.yml @@ -3,7 +3,7 @@ extensions: pack: microsoft-sdl/powershell-all extensible: sourceModel data: - - ["System.Environment", "Method[ExpandEnvironmentVariables].ReturnValue", "environment"] - - ["System.Environment", "Method[GetCommandLineArgs].ReturnValue", "commandargs"] - - ["System.Environment", "Method[GetEnvironmentVariable].ReturnValue", "environment"] - - ["System.Environment", "Method[GetEnvironmentVariables].ReturnValue", "environment"] \ No newline at end of file + - ["System.Environment!", "Method[ExpandEnvironmentVariables].ReturnValue", "environment"] + - ["System.Environment!", "Method[GetCommandLineArgs].ReturnValue", "commandargs"] + - ["System.Environment!", "Method[GetEnvironmentVariable].ReturnValue", "environment"] + - ["System.Environment!", "Method[GetEnvironmentVariables].ReturnValue", "environment"] \ No newline at end of file diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/SystemIOFile/model.yml b/powershell/ql/lib/semmle/code/powershell/frameworks/SystemIOFile/model.yml index 01a5ee38870..87a270fc29e 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/SystemIOFile/model.yml +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/SystemIOFile/model.yml @@ -3,19 +3,19 @@ extensions: pack: microsoft-sdl/powershell-all extensible: sourceModel data: - - ["System.IO.File", "Method[AppendText].ReturnValue", "file-write"] - - ["System.IO.File", "Method[Create].ReturnValue", "file-write"] - - ["System.IO.File", "Method[CreateText].ReturnValue", "file-write"] - - ["System.IO.File", "Method[Open].ReturnValue", "file-write"] - - ["System.IO.File", "Method[Open].ReturnValue", "file"] - - ["System.IO.File", "Method[OpenRead].ReturnValue", "file"] - - ["System.IO.File", "Method[OpenText].ReturnValue", "file"] - - ["System.IO.File", "Method[OpenWrite].ReturnValue", "file-write"] - - ["System.IO.File", "Method[ReadAllBytes].ReturnValue", "file"] - - ["System.IO.File", "Method[ReadAllBytesAsync].ReturnValue", "file"] - - ["System.IO.File", "Method[ReadAllLines].ReturnValue", "file"] - - ["System.IO.File", "Method[ReadAllLinesAsync].ReturnValue", "file"] - - ["System.IO.File", "Method[ReadAllText].ReturnValue", "file"] - - ["System.IO.File", "Method[ReadAllTextAsync].ReturnValue", "file"] - - ["System.IO.File", "Method[ReadLines].ReturnValue", "file"] - - ["System.IO.File", "Method[ReadLinesAsync].ReturnValue", "file"] \ No newline at end of file + - ["System.IO.File!", "Method[AppendText].ReturnValue", "file-write"] + - ["System.IO.File!", "Method[Create].ReturnValue", "file-write"] + - ["System.IO.File!", "Method[CreateText].ReturnValue", "file-write"] + - ["System.IO.File!", "Method[Open].ReturnValue", "file-write"] + - ["System.IO.File!", "Method[Open].ReturnValue", "file"] + - ["System.IO.File!", "Method[OpenRead].ReturnValue", "file"] + - ["System.IO.File!", "Method[OpenText].ReturnValue", "file"] + - ["System.IO.File!", "Method[OpenWrite].ReturnValue", "file-write"] + - ["System.IO.File!", "Method[ReadAllBytes].ReturnValue", "file"] + - ["System.IO.File!", "Method[ReadAllBytesAsync].ReturnValue", "file"] + - ["System.IO.File!", "Method[ReadAllLines].ReturnValue", "file"] + - ["System.IO.File!", "Method[ReadAllLinesAsync].ReturnValue", "file"] + - ["System.IO.File!", "Method[ReadAllText].ReturnValue", "file"] + - ["System.IO.File!", "Method[ReadAllTextAsync].ReturnValue", "file"] + - ["System.IO.File!", "Method[ReadLines].ReturnValue", "file"] + - ["System.IO.File!", "Method[ReadLinesAsync].ReturnValue", "file"] \ No newline at end of file diff --git a/powershell/ql/lib/semmle/code/powershell/frameworks/SystemIOFileInfo/model.yml b/powershell/ql/lib/semmle/code/powershell/frameworks/SystemIOFileInfo/model.yml index c10519e9ef7..82406744f09 100644 --- a/powershell/ql/lib/semmle/code/powershell/frameworks/SystemIOFileInfo/model.yml +++ b/powershell/ql/lib/semmle/code/powershell/frameworks/SystemIOFileInfo/model.yml @@ -3,11 +3,11 @@ extensions: pack: microsoft-sdl/powershell-all extensible: sourceModel data: - - ["System.IO.FileInfo", "Method[AppendText].ReturnValue", "file-write"] - - ["System.IO.FileInfo", "Method[Create].ReturnValue", "file-write"] - - ["System.IO.FileInfo", "Method[CreateText].ReturnValue", "file-write"] - - ["System.IO.FileInfo", "Method[Open].ReturnValue", "file-write"] - - ["System.IO.FileInfo", "Method[Open].ReturnValue", "file"] - - ["System.IO.FileInfo", "Method[OpenRead].ReturnValue", "file"] - - ["System.IO.FileInfo", "Method[OpenText].ReturnValue", "file"] - - ["System.IO.FileInfo", "Method[OpenWrite].ReturnValue", "file-write"] + - ["System.IO.FileInfo!", "Method[AppendText].ReturnValue", "file-write"] + - ["System.IO.FileInfo!", "Method[Create].ReturnValue", "file-write"] + - ["System.IO.FileInfo!", "Method[CreateText].ReturnValue", "file-write"] + - ["System.IO.FileInfo!", "Method[Open].ReturnValue", "file-write"] + - ["System.IO.FileInfo!", "Method[Open].ReturnValue", "file"] + - ["System.IO.FileInfo!", "Method[OpenRead].ReturnValue", "file"] + - ["System.IO.FileInfo!", "Method[OpenText].ReturnValue", "file"] + - ["System.IO.FileInfo!", "Method[OpenWrite].ReturnValue", "file-write"] 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 96a233146a0..9bf974f325b 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 @@ -32,11 +32,11 @@ bindingset[rawType] predicate isTypeUsed(string rawType) { any() } bindingset[rawType] -private predicate parseType(string rawType, string mod, string type) { +private predicate parseType(string rawType, string consts, string suffix) { exists(string regexp | - regexp = "(.+)\\.([^\\.]+)" and - mod = rawType.regexpCapture(regexp, 1) and - type = rawType.regexpCapture(regexp, 2) + regexp = "([^!]+)(!|)" and + consts = rawType.regexpCapture(regexp, 1) and + suffix = rawType.regexpCapture(regexp, 2) ) } @@ -50,7 +50,31 @@ private predicate parseRelevantType(string rawType, string consts, string suffix * language semantics modeled by `getExtraNodeFromType`. */ bindingset[otherType] -predicate hasImplicitTypeModel(string type, string otherType) { none() } +predicate hasImplicitTypeModel(string type, string otherType) { + // A::B! can be used to obtain A::B + parseType(otherType, type, _) +} + +pragma[nomagic] +string getConstComponent(string consts, int n) { + parseRelevantType(_, consts, _) and + result = consts.splitAt(".", n) +} + +private int getNumConstComponents(string consts) { + result = strictcount(int n | exists(getConstComponent(consts, n))) +} + +private DataFlow::TypePathNode getConstantFromConstPath(string consts, int n) { + n = 1 and + result.getComponent() = getConstComponent(consts, 0) + or + result = getConstantFromConstPath(consts, n - 1).getConstant(getConstComponent(consts, n - 1)) +} + +private DataFlow::TypePathNode getConstantFromConstPath(string consts) { + result = getConstantFromConstPath(consts, getNumConstComponents(consts)) +} /** Gets a Powershell-specific interpretation of the `(type, path)` tuple after resolving the first `n` access path tokens. */ bindingset[type, path] @@ -66,18 +90,20 @@ API::Node getExtraNodeFromPath(string type, AccessPath path, int n) { } /** Gets a Powershell-specific interpretation of the given `type`. */ -API::Node getExtraNodeFromType(string qualifiedType) { - qualifiedType = "" and - result = API::root() - or - // TODO: How to distinguish between these cases? And do we need to? - exists(string mod, string type | parseRelevantType(qualifiedType, mod, type) | - result = API::mod(qualifiedType) +API::Node getExtraNodeFromType(string type) { + exists(string consts, string suffix, DataFlow::TypePathNode constRef | + parseRelevantType(type, consts, suffix) and + constRef = getConstantFromConstPath(consts) + | + suffix = "!" and + result = constRef.track() or - result = API::mod(qualifiedType).getInstance() - or - result = API::mod(mod).getType(type) + suffix = "" and + result = constRef.track().getInstance() ) + or + type = "" and + result = API::root() } /**