PS: Make API graphs compile again. There is still some TODOs here, but at least it compiles.

This commit is contained in:
Mathias Vorreiter Pedersen
2025-03-25 17:15:18 +00:00
parent 8092345fee
commit 7551cce537
6 changed files with 85 additions and 150 deletions

View File

@@ -271,7 +271,8 @@ module API {
this = Impl::MkMethodAccessNode(result) or
this = Impl::MkBackwardNode(result, _) or
this = Impl::MkForwardNode(result, _) or
this = Impl::MkSinkNode(result)
this = Impl::MkSinkNode(result) or
this = Impl::MkNamespaceOfTypeNameNode(result)
}
/** Gets the location of this node. */
@@ -324,45 +325,6 @@ module API {
}
}
/** A node representing a module/class object with epsilon edges to its descendents. */
private class ModuleNode extends Node, Impl::MkModule {
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() + ")" }
}
/** A node representing instances of a module/class with epsilon edges to its ancestors. */
private class InstanceUp extends Node, Impl::MkInstanceUp {
/** Gets the module whose instances are represented by this API node. */
string getType() { this = Impl::MkInstanceUp(result) }
override string toString() { result = "ModuleInstanceUp(" + this.getType() + ")" }
}
/** A node representing instances of a module/class with epsilon edges to its descendents. */
private class InstanceDownNode extends Node, Impl::MkInstanceDown {
/** Gets the module whose instances are represented by this API node. */
string getType() { this = Impl::MkInstanceDown(result) }
override string toString() { result = "ModuleInstanceDown(" + this.getType() + ")" }
}
/** A node corresponding to the method being invoked at a method call. */
class MethodAccessNode extends Node, Impl::MkMethodAccessNode {
override string toString() { result = "MethodAccessNode(" + this.asCall() + ")" }
@@ -378,6 +340,22 @@ 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
UsingNode() { this = Impl::MkUsingNode(using) }
override string toString() { result = "UsingNode(" + using + ")" }
}
private class NamespaceOfTypeNameNode extends Node, Impl::MkNamespaceOfTypeNameNode {
DataFlow::QualifiedTypeNameNode typeName;
NamespaceOfTypeNameNode() { this = Impl::MkNamespaceOfTypeNameNode(typeName) }
override string toString() { result = "NamespaceOfTypeNameNode(" + typeName + ")" }
}
/**
* An API entry point.
*
@@ -415,11 +393,15 @@ module API {
/** Gets the root node. */
Node root() { result instanceof RootNode }
/**
* Gets the node that represents the module with qualified
* name `qualifiedModule`.
*/
ModuleNode mod(string qualifiedModule, int n) { result = Impl::MkModule(qualifiedModule, n) }
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) }
/**
* Gets an unqualified call at the top-level with the given method name.
@@ -466,44 +448,14 @@ module API {
cached
private module Impl {
private predicate isGacModule(string s) {
s =
[
"System.Management.Automation",
"Microsoft.Management.Infrastructure",
"Microsoft.PowerShell.Security",
"Microsoft.PowerShell.Commands.Management",
"Microsoft.PowerShell.Commands.Utility"
]
}
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
or
isGacModule(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, int n) { isModule(qualifiedModule, n) } or
/** Instances of `mod` with epsilon edges to its ancestors. */
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(MkModule(qualifiedType, _)) } or
/** Intermediate node for following forward data flow. */
MkUsingNode(UsingStmt using) or
MkNamespaceOfTypeNameNode(DataFlow::QualifiedTypeNameNode typeName) 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
@@ -523,10 +475,30 @@ module API {
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) }
cached
predicate toplevelCall(string name, Node node) {
exists(DataFlow::CallNode call |
call.asExpr().getExpr().getEnclosingScope() instanceof TopLevel and
call.asExpr().getExpr().getEnclosingScope() instanceof TopLevelScriptBlock and
call.getName() = name and
node = MkMethodAccessNode(call)
)
@@ -544,26 +516,25 @@ module API {
cached
predicate memberEdge(Node pred, string name, Node succ) {
exists(MemberExpr member | succ = getForwardStartNode(getNodeFromExpr(member)) |
pred = getForwardEndNode(getALocalSourceStrict(getNodeFromExpr(member.getQualifier()))) and
name = member.getMemberName()
exists(StringConstExpr read |
succ = getForwardStartNode(getNodeFromExpr(read)) and
pred = MkRoot() and
name = read.getValueString()
)
or
exists(DataFlow::QualifiedTypeNameNode typeName |
typeName.getName() = name and
pred = MkNamespaceOfTypeNameNode(typeName) and
succ = getForwardStartNode(typeName)
)
// or
// TODO: Handle getAMember when the predecessor is a MkUsingNode?
}
cached
predicate methodEdge(Node pred, string name, Node succ) {
exists(DataFlow::CallNode call | succ = MkMethodAccessNode(call) and name = call.getName() |
pred = getForwardEndNode(getALocalSourceStrict(call.getQualifier()))
or
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
or
manifest.getAFunctionToExport() = name
)
)
}
@@ -657,24 +628,10 @@ module API {
cached
predicate instanceEdge(Node pred, Node succ) {
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)
)
or
exists(DataFlow::ParameterNode p |
p.getParameter().getStaticType() = qualifiedType and
succ = getForwardStartNode(p)
)
// TODO: Also model parameters with a given type here
exists(DataFlow::ObjectCreationNode objCreation |
pred = getForwardEndNode(objCreation.getConstructedTypeNode()) and
succ = getForwardStartNode(objCreation)
)
}

View File

@@ -111,7 +111,7 @@ class SplitPath extends CmdCall {
not this.hasNamedArgument("path") and
result = this.getPositionalArgument(0)
or
// TODO: This should not be allowed, but I've seen code doing it
// TODO: This should not be allowed, but I've seen code doing it and somehow it works
result = this.getNamedArgument("parent")
}

View File

@@ -3,6 +3,8 @@ import powershell
abstract private class AbstractObjectCreation extends CallExpr {
/** The name of the type of the object being constructed. */
abstract string getConstructedTypeName();
abstract Expr getConstructedTypeExpr();
}
/**
@@ -11,10 +13,12 @@ abstract private class AbstractObjectCreation extends CallExpr {
* [System.IO.FileInfo]::new("C:\\file.txt")
* ```
*/
class NewObjectCreation extends AbstractObjectCreation instanceof ConstructorCall {
class NewObjectCreation extends AbstractObjectCreation, ConstructorCall {
final override string getConstructedTypeName() {
result = ConstructorCall.super.getConstructedTypeName()
}
final override Expr getConstructedTypeExpr() { result = typename }
}
/**
@@ -23,19 +27,21 @@ class NewObjectCreation extends AbstractObjectCreation instanceof ConstructorCal
* New-Object -TypeName System.IO.FileInfo -ArgumentList "C:\\file.txt"
* ```
*/
class DotNetObjectCreation extends AbstractObjectCreation instanceof CmdCall {
class DotNetObjectCreation extends AbstractObjectCreation, CmdCall {
DotNetObjectCreation() { this.getName() = "New-Object" }
final override string getConstructedTypeName() {
result = this.getConstructedTypeExpr().(StringConstExpr).getValueString()
}
final override Expr getConstructedTypeExpr() {
// Either it's the named argument `TypeName`
result = CmdCall.super.getNamedArgument("TypeName").(StringConstExpr).getValueString()
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
exists(StringConstExpr arg | arg = CmdCall.super.getPositionalArgument(0) |
result = arg.getValueString() and
not arg = CmdCall.super.getNamedArgument(["ArgumentList", "Property"])
)
result = CmdCall.super.getPositionalArgument(0) and
result = CmdCall.super.getNamedArgument(["ArgumentList", "Property"])
}
}

View File

@@ -6,7 +6,7 @@ module EngineIntrinsics {
private class EngineIntrinsicsGlobalEntry extends ModelInput::TypeModel {
override DataFlow::Node getASource(string type) {
type = "System.Management.Automation.EngineIntrinsics" and
result.asExpr().getExpr().(VarReadAccess).getUserPath().toLowerCase() = "executioncontext"
result.asExpr().getExpr().(VarReadAccess).getVariable().getName().toLowerCase() = "executioncontext"
}
}
}

View File

@@ -30,7 +30,7 @@ private class SummarizedCallableFromModel extends SummarizedCallable {
this = type + ";" + path
}
override Call getACall() {
override CallExpr getACall() {
exists(API::MethodAccessNode base |
ModelOutput::resolvedSummaryBase(type, path, base) and
result = base.asCall().asExpr().getExpr()

View File

@@ -55,27 +55,6 @@ predicate hasImplicitTypeModel(string type, string otherType) {
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]
API::Node getExtraNodeFromPath(string type, AccessPath path, int n) {
@@ -91,15 +70,8 @@ API::Node getExtraNodeFromPath(string type, AccessPath path, int n) {
/** Gets a Powershell-specific interpretation of the given `type`. */
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
suffix = "" and
result = constRef.track().getInstance()
exists(string consts, string suffix | parseRelevantType(type, consts, suffix) |
none() // TODO
)
or
type = "" and
@@ -187,7 +159,7 @@ predicate invocationMatchesExtraCallSiteFilter(InvokeNode invoke, AccessPathToke
/** An API graph node representing a method call. */
class InvokeNode extends API::MethodAccessNode {
/** Gets the number of arguments to the call. */
int getNumArgument() { result = this.asCall().getNumberOfArguments() }
int getNumArgument() { result = count(this.asCall().getAnArgument()) }
}
/** Gets the `InvokeNode` corresponding to a specific invocation of `node`. */