From 296aa52ef084eb53321739387cc972ebeec8f5b6 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 2 Sep 2022 13:14:49 +0200 Subject: [PATCH] Python: Add API::EntryPoint Python: add EntryPoint test --- python/ql/lib/semmle/python/ApiGraphs.qll | 51 ++++++++++++++++++- .../ApiGraphs/py3/test_entry_point.py | 2 + .../ApiGraphs/py3/verifyApiGraphs.ql | 8 +++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 python/ql/test/library-tests/ApiGraphs/py3/test_entry_point.py diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 066dd0ce7d4..1b1851114bf 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -453,6 +453,30 @@ module API { int getNumArgument() { result = count(this.getArg(_)) } } + /** + * An API entry point. + * + * By default, API graph nodes are only created for nodes that come from an external + * library or escape into an external library. The points where values are cross the boundary + * between codebases are called "entry points". + * + * Anything imported from an external package is considered to be an entry point, but + * additional entry points may be added by extending this class. + */ + abstract class EntryPoint extends string { + bindingset[this] + EntryPoint() { any() } + + /** Gets a data-flow node corresponding to a use-node for this entry point. */ + DataFlow::LocalSourceNode getASource() { none() } + + /** Gets a data-flow node corresponding to a def-node for this entry point. */ + DataFlow::Node getASink() { none() } + + /** Gets an API-node for this entry point. */ + API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) } + } + /** * Provides the actual implementation of API graphs, cached for performance. * @@ -652,6 +676,12 @@ module API { | lbl = Label::memberFromRef(aw) ) + or + exists(EntryPoint entry | + base = root() and + lbl = Label::entryPoint(entry) and + rhs = entry.getASink() + ) } /** @@ -735,6 +765,12 @@ module API { ImportStar::namePossiblyDefinedInImportStar(ref.asCfgNode(), name, s) )) ) + or + exists(EntryPoint entry | + base = root() and + lbl = Label::entryPoint(entry) and + ref = entry.getASource() + ) } /** @@ -909,7 +945,8 @@ module API { MkLabelSelfParameter() or MkLabelReturn() or MkLabelSubclass() or - MkLabelAwait() + MkLabelAwait() or + MkLabelEntryPoint(EntryPoint ep) /** A label for a module. */ class LabelModule extends ApiLabel, MkLabelModule { @@ -983,6 +1020,15 @@ module API { class LabelAwait extends ApiLabel, MkLabelAwait { override string toString() { result = "getAwaited()" } } + + /** A label for entry points. */ + class LabelEntryPoint extends ApiLabel, MkLabelEntryPoint { + private EntryPoint entry; + + LabelEntryPoint() { this = MkLabelEntryPoint(entry) } + + override string toString() { result = "entryPoint(\"" + entry + "\")" } + } } /** Gets the edge label for the module `m`. */ @@ -1019,5 +1065,8 @@ module API { /** Gets the `await` edge label. */ LabelAwait await() { any() } + + /** Gets the label going from the root node to the nodes associated with the given entry point. */ + LabelEntryPoint entryPoint(EntryPoint ep) { result = MkLabelEntryPoint(ep) } } } diff --git a/python/ql/test/library-tests/ApiGraphs/py3/test_entry_point.py b/python/ql/test/library-tests/ApiGraphs/py3/test_entry_point.py new file mode 100644 index 00000000000..17d0cfacc4b --- /dev/null +++ b/python/ql/test/library-tests/ApiGraphs/py3/test_entry_point.py @@ -0,0 +1,2 @@ +"magic_string".foo.bar #$ use=entryPoint("CustomEntryPoint").getMember("foo").getMember("bar") +"magic_string2".foo.bar diff --git a/python/ql/test/library-tests/ApiGraphs/py3/verifyApiGraphs.ql b/python/ql/test/library-tests/ApiGraphs/py3/verifyApiGraphs.ql index 477ed4454fb..c1f7e8ad5e1 100644 --- a/python/ql/test/library-tests/ApiGraphs/py3/verifyApiGraphs.ql +++ b/python/ql/test/library-tests/ApiGraphs/py3/verifyApiGraphs.ql @@ -1,3 +1,11 @@ // Note: This is not using standard inline-expectation tests, so will not alert if you // have not manually added an annotation to a line! import TestUtilities.VerifyApiGraphs + +class CustomEntryPoint extends API::EntryPoint { + CustomEntryPoint() { this = "CustomEntryPoint" } + + override DataFlow::LocalSourceNode getASource() { + result.asExpr().(StrConst).getText() = "magic_string" + } +}