From 089d030bc26b741ee2eb3c22d58f032c9e1c1a92 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 19 Nov 2021 11:08:25 +0100 Subject: [PATCH 1/7] make ApiLabel into a IPA type, and cache the public API of ApiGraphs --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 412 ++++++++++++------ .../lib/semmle/javascript/frameworks/D3.qll | 2 +- .../semmle/javascript/frameworks/History.qll | 2 +- .../javascript/frameworks/Immutable.qll | 2 +- .../semmle/javascript/frameworks/Logging.qll | 2 +- .../lib/semmle/javascript/frameworks/Nest.qll | 2 +- .../semmle/javascript/frameworks/Redux.qll | 4 +- .../lib/semmle/javascript/frameworks/Vue.qll | 4 +- .../javascript/internal/CachedStages.qll | 37 ++ ...APIUsedWithUntrustedDataCustomizations.qll | 8 +- .../security/dataflow/RemoteFlowSources.qll | 15 +- .../ql/src/meta/ApiGraphs/ApiGraphEdges.ql | 2 +- .../ql/test/ApiGraphs/VerifyAssertions.qll | 9 +- 13 files changed, 343 insertions(+), 158 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index c3083d57d9da..740ac525b54a 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -11,6 +11,7 @@ import javascript private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps +private import internal.CachedStages /** * Provides classes and predicates for working with APIs defined or used in a database. @@ -33,6 +34,7 @@ module API { * As another example, in the assignment `exports.plusOne = (x) => x+1` the two references to * `x` are uses of the first parameter of `plusOne`. */ + pragma[inline] DataFlow::Node getAUse() { exists(DataFlow::SourceNode src | Impl::use(this, src) | Impl::trackUseNode(src).flowsTo(result) @@ -105,22 +107,31 @@ module API { * For example, modules have an `exports` member representing their exports, and objects have * their properties as members. */ - bindingset[m] - bindingset[result] - Node getMember(string m) { result = getASuccessor(Label::member(m)) } + cached + Node getMember(string m) { + Stages::APIStage::ref() and + result = getASuccessor(Label::member(m)) + } /** * Gets a node representing a member of this API component where the name of the member is * not known statically. */ - Node getUnknownMember() { result = getASuccessor(Label::unknownMember()) } + cached + Node getUnknownMember() { + Stages::APIStage::ref() and + result = getASuccessor(Label::unknownMember()) + } /** * Gets a node representing a member of this API component where the name of the member may * or may not be known statically. */ + cached Node getAMember() { - result = getASuccessor(Label::member(_)) or + Stages::APIStage::ref() and + result = getMember(_) + or result = getUnknownMember() } @@ -135,7 +146,11 @@ module API { * This predicate may have multiple results when there are multiple constructor calls invoking this API component. * Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls. */ - Node getInstance() { result = getASuccessor(Label::instance()) } + cached + Node getInstance() { + Stages::APIStage::ref() and + result = getASuccessor(Label::instance()) + } /** * Gets a node representing the `i`th parameter of the function represented by this node. @@ -143,16 +158,16 @@ module API { * This predicate may have multiple results when there are multiple invocations of this API component. * Consider using `getAnInvocation()` if there is a need to distingiush between individual calls. */ - bindingset[i] - Node getParameter(int i) { result = getASuccessor(Label::parameter(i)) } + cached + Node getParameter(int i) { + Stages::APIStage::ref() and + result = getASuccessor(Label::parameter(i)) + } /** * Gets the number of parameters of the function represented by this node. */ - int getNumParameter() { - result = - max(string s | exists(getASuccessor(Label::parameterByStringIndex(s))) | s.toInt()) + 1 - } + int getNumParameter() { result = max(int s | exists(getParameter(s))) + 1 } /** * Gets a node representing the last parameter of the function represented by this node. @@ -165,7 +180,11 @@ module API { /** * Gets a node representing the receiver of the function represented by this node. */ - Node getReceiver() { result = getASuccessor(Label::receiver()) } + cached + Node getReceiver() { + Stages::APIStage::ref() and + result = getASuccessor(Label::receiver()) + } /** * Gets a node representing a parameter or the receiver of the function represented by this @@ -175,8 +194,11 @@ module API { * there are multiple invocations of this API component. * Consider using `getAnInvocation()` if there is a need to distingiush between individual calls. */ + cached Node getAParameter() { - result = getASuccessor(Label::parameterByStringIndex(_)) or + Stages::APIStage::ref() and + result = getParameter(_) + or result = getReceiver() } @@ -186,18 +208,30 @@ module API { * This predicate may have multiple results when there are multiple invocations of this API component. * Consider using `getACall()` if there is a need to distingiush between individual calls. */ - Node getReturn() { result = getASuccessor(Label::return()) } + cached + Node getReturn() { + Stages::APIStage::ref() and + result = getASuccessor(Label::return()) + } /** * Gets a node representing the promised value wrapped in the `Promise` object represented by * this node. */ - Node getPromised() { result = getASuccessor(Label::promised()) } + cached + Node getPromised() { + Stages::APIStage::ref() and + result = getASuccessor(Label::promised()) + } /** * Gets a node representing the error wrapped in the `Promise` object represented by this node. */ - Node getPromisedError() { result = getASuccessor(Label::promisedError()) } + cached + Node getPromisedError() { + Stages::APIStage::ref() and + result = getASuccessor(Label::promisedError()) + } /** * Gets a string representation of the lexicographically least among all shortest access paths @@ -209,13 +243,13 @@ module API { * Gets a node such that there is an edge in the API graph between this node and the other * one, and that edge is labeled with `lbl`. */ - Node getASuccessor(string lbl) { Impl::edge(this, lbl, result) } + Node getASuccessor(ApiLabel lbl) { Impl::edge(this, lbl, result) } /** * Gets a node such that there is an edge in the API graph between that other node and * this one, and that edge is labeled with `lbl` */ - Node getAPredecessor(string lbl) { this = result.getASuccessor(lbl) } + Node getAPredecessor(ApiLabel lbl) { this = result.getASuccessor(lbl) } /** * Gets a node such that there is an edge in the API graph between this node and the other @@ -281,9 +315,9 @@ module API { length = 0 and result = "" or - exists(Node pred, string lbl, string predpath | + exists(Node pred, ApiLabel lbl, string predpath | Impl::edge(pred, lbl, this) and - lbl != "" and + not lbl instanceof Label::LabelAlias and predpath = pred.getAPath(length - 1) and exists(string space | if length = 1 then space = "" else space = " " | result = "(" + lbl + space + predpath + ")" and @@ -348,6 +382,11 @@ module API { /** Gets a data-flow node that defines this entry point. */ abstract DataFlow::Node getARhs(); + + /** Gets an API-node for this entry point. */ + API::Node getNode() { + result = root().getASuccessor(any(Label::LabelEntryPoint l | l.getEntryPoint() = this)) + } } /** @@ -431,27 +470,16 @@ module API { hasSemantics(imp) } - /** Gets the definition of module `m`. */ - private Module importableModule(string m) { - exists(NPMPackage pkg, PackageJSON json | - json = pkg.getPackageJSON() and not json.isPrivate() - | - result = pkg.getMainModule() and - not result.isExterns() and - m = pkg.getPackageName() - ) - } - /** * Holds if `rhs` is the right-hand side of a definition of a node that should have an * incoming edge from `base` labeled `lbl` in the API graph. */ cached - predicate rhs(TApiNode base, string lbl, DataFlow::Node rhs) { + predicate rhs(TApiNode base, ApiLabel lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and - rhs = lbl.(EntryPoint).getARhs() + rhs = lbl.(Label::LabelEntryPoint).getEntryPoint().getARhs() or exists(string m, string prop | base = MkModuleExport(m) and @@ -563,7 +591,7 @@ module API { */ pragma[noinline] private predicate propertyRead( - DataFlow::SourceNode pred, string propDesc, string lbl, DataFlow::Node ref + DataFlow::SourceNode pred, string propDesc, ApiLabel lbl, DataFlow::Node ref ) { ref = pred.getAPropertyRead() and lbl = Label::memberFromRef(ref) and @@ -587,11 +615,11 @@ module API { * `lbl` in the API graph. */ cached - predicate use(TApiNode base, string lbl, DataFlow::Node ref) { + predicate use(TApiNode base, ApiLabel lbl, DataFlow::Node ref) { hasSemantics(ref) and ( base = MkRoot() and - ref = lbl.(EntryPoint).getAUse() + ref = lbl.(Label::LabelEntryPoint).getEntryPoint().getAUse() or // property reads exists(DataFlow::SourceNode src, DataFlow::SourceNode pred, string propDesc | @@ -678,33 +706,6 @@ module API { nd = MkUse(ref) } - /** Holds if module `m` exports `rhs`. */ - private predicate exports(string m, DataFlow::Node rhs) { - exists(Module mod | mod = importableModule(m) | - rhs = mod.(AmdModule).getDefine().getModuleExpr().flow() - or - exports(m, "default", rhs) - or - exists(ExportAssignDeclaration assgn | assgn.getTopLevel() = mod | - rhs = assgn.getExpression().flow() - ) - or - rhs = mod.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr().flow() - ) - } - - /** Holds if module `m` exports `rhs` under the name `prop`. */ - private predicate exports(string m, string prop, DataFlow::Node rhs) { - exists(ExportDeclaration exp | exp.getEnclosingModule() = importableModule(m) | - rhs = exp.getSourceNode(prop) - or - exists(Variable v | - exp.exportsAs(v, prop) and - rhs = v.getAnAssignedExpr().flow() - ) - ) - } - private import semmle.javascript.dataflow.TypeTracking /** @@ -863,7 +864,8 @@ module API { * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. */ cached - predicate edge(TApiNode pred, string lbl, TApiNode succ) { + predicate edge(TApiNode pred, ApiLabel lbl, TApiNode succ) { + Stages::APIStage::ref() and exists(string m | pred = MkRoot() and lbl = Label::mod(m) @@ -970,6 +972,7 @@ module API { /** * Gets an API node where a RHS of the node is the `i`th argument to this call. */ + pragma[noinline] private Node getAParameterCandidate(int i) { result.getARhs() = getArgument(i) } /** Gets the API node for a parameter of this invocation. */ @@ -996,89 +999,224 @@ module API { /** A `new` call connected to the API graph. */ class NewNode extends InvokeNode, DataFlow::NewNode { } -} -private module Label { - /** Gets the edge label for the module `m`. */ - bindingset[m] - bindingset[result] - string mod(string m) { result = "module " + m } + /** A label in the API-graph */ + abstract class ApiLabel extends Label::TLabel { + string toString() { result = "???" } + } - /** Gets the `member` edge label for member `m`. */ - bindingset[m] - bindingset[result] - string member(string m) { result = "member " + m } + private module Label { + newtype TLabel = + MkLabelMod(string mod) { + exists(Impl::MkModuleExport(mod)) or + exists(Impl::MkModuleImport(mod)) + } or + MkLabelInstance() or + MkLabelMember(string prop) { + exports(_, prop, _) or + exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or + prop = "exports" or + prop = any(CanonicalName c).getName() or + prop = any(DataFlow::PropRef p).getPropertyName() or + exists(Impl::MkTypeUse(_, prop)) or + exists(any(Module m).getAnExportedValue(prop)) + } or + MkLabelUnknownMember() or + MkLabelParameter(int i) { + i = + [-1 .. max(int args | + args = any(InvokeExpr invk).getNumArgument() or + args = any(Function f).getNumParameter() + )] or + i = [0 .. 10] + } or + MkLabelReturn() or + MkLabelPromised() or + MkLabelPromisedError() or + MkLabelAlias() or + MkLabelEntryPoint(API::EntryPoint e) - /** Gets the `member` edge label for the unknown member. */ - string unknownMember() { result = "member *" } + class LabelEntryPoint extends ApiLabel { + API::EntryPoint e; - /** - * Gets a property name referred to by the given dynamic property access, - * allowing one property flow step in the process (to allow flow through imports). - * - * This is to support code patterns where the property name is actually constant, - * but the property name has been factored into a library. - */ - private string getAnIndirectPropName(DataFlow::PropRef ref) { - exists(DataFlow::Node pred | - FlowSteps::propertyFlowStep(pred, ref.getPropertyNameExpr().flow()) and - result = pred.getStringValue() - ) - } + LabelEntryPoint() { this = MkLabelEntryPoint(e) } - /** - * Gets unique result of `getAnIndirectPropName` if there is one. - */ - private string getIndirectPropName(DataFlow::PropRef ref) { - result = unique(string s | s = getAnIndirectPropName(ref)) - } + API::EntryPoint getEntryPoint() { result = e } - /** Gets the `member` edge label for the given property reference. */ - string memberFromRef(DataFlow::PropRef pr) { - exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) | - result = member(pn) and - // only consider properties with alphanumeric(-ish) names, excluding special properties - // and properties whose names look like they are meant to be internal - pn.regexpMatch("(?!prototype$|__)[\\w_$][\\w\\-.$]*") - ) - or - not exists(pr.getPropertyName()) and - not exists(getIndirectPropName(pr)) and - result = unknownMember() - } + override string toString() { result = e } + } - /** Gets the `instance` edge label. */ - string instance() { result = "instance" } + class LabelAlias extends ApiLabel { + LabelAlias() { this = MkLabelAlias() } - /** - * Gets the `parameter` edge label for the parameter `s`. - * - * This is an internal helper predicate; use `parameter` instead. - */ - bindingset[result] - bindingset[s] - string parameterByStringIndex(string s) { - result = "parameter " + s and - s.toInt() >= -1 - } + override string toString() { result = "" } + } - /** - * Gets the `parameter` edge label for the `i`th parameter. - * - * The receiver is considered to be parameter -1. - */ - bindingset[i] - string parameter(int i) { result = parameterByStringIndex(i.toString()) } + class LabelPromised extends ApiLabel { + LabelPromised() { this = MkLabelPromised() } + + override string toString() { result = "promised" } + } + + class LabelPromisedError extends ApiLabel { + LabelPromisedError() { this = MkLabelPromisedError() } + + override string toString() { result = "promised" } + } + + class LabelReturn extends ApiLabel { + LabelReturn() { this = MkLabelReturn() } + + override string toString() { result = "return" } + } + + class LabelMod extends ApiLabel { + string mod; + + LabelMod() { this = MkLabelMod(mod) } + + string getMod() { result = mod } + + override string toString() { result = "module " + mod } + } + + class LabelInstance extends ApiLabel { + LabelInstance() { this = MkLabelInstance() } + + override string toString() { result = "instance" } + } + + class LabelMember extends ApiLabel { + string prop; + + LabelMember() { this = MkLabelMember(prop) } + + string getProperty() { result = prop } + + override string toString() { result = "member " + prop } + } + + class LabelUnknownMember extends ApiLabel { + LabelUnknownMember() { this = MkLabelUnknownMember() } + + override string toString() { result = "member *" } + } + + class LabelParameter extends ApiLabel { + int i; + + LabelParameter() { this = MkLabelParameter(i) } + + override string toString() { result = "parameter " + i } + + int getIndex() { result = i } + } + + /** Gets the edge label for the module `m`. */ + LabelMod mod(string m) { result.getMod() = m } + + /** Gets the `member` edge label for member `m`. */ + bindingset[m] + bindingset[result] + LabelMember member(string m) { result.getProperty() = m } - /** Gets the `parameter` edge label for the receiver. */ - string receiver() { result = "parameter -1" } + /** Gets the `member` edge label for the unknown member. */ + LabelUnknownMember unknownMember() { any() } - /** Gets the `return` edge label. */ - string return() { result = "return" } + /** + * Gets a property name referred to by the given dynamic property access, + * allowing one property flow step in the process (to allow flow through imports). + * + * This is to support code patterns where the property name is actually constant, + * but the property name has been factored into a library. + */ + private string getAnIndirectPropName(DataFlow::PropRef ref) { + exists(DataFlow::Node pred | + FlowSteps::propertyFlowStep(pred, ref.getPropertyNameExpr().flow()) and + result = pred.getStringValue() + ) + } + + /** + * Gets unique result of `getAnIndirectPropName` if there is one. + */ + private string getIndirectPropName(DataFlow::PropRef ref) { + result = unique(string s | s = getAnIndirectPropName(ref)) + } + + /** Gets the `member` edge label for the given property reference. */ + ApiLabel memberFromRef(DataFlow::PropRef pr) { + exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) | + result = member(pn) and + // only consider properties with alphanumeric(-ish) names, excluding special properties + // and properties whose names look like they are meant to be internal + pn.regexpMatch("(?!prototype$|__)[\\w_$][\\w\\-.$]*") + ) + or + not exists(pr.getPropertyName()) and + not exists(getIndirectPropName(pr)) and + result = unknownMember() + } + + /** Gets the `instance` edge label. */ + LabelInstance instance() { any() } + + /** + * Gets the `parameter` edge label for the `i`th parameter. + * + * The receiver is considered to be parameter -1. + */ + LabelParameter parameter(int i) { result.getIndex() = i } + + /** Gets the `parameter` edge label for the receiver. */ + LabelParameter receiver() { result = parameter(-1) } + + /** Gets the `return` edge label. */ + LabelReturn return() { any() } + + /** Gets the `alias` (empty) edge label. */ + LabelAlias alias() { any() } + + /** Gets the `promised` edge label connecting a promise to its contained value. */ + MkLabelPromised promised() { any() } - /** Gets the `promised` edge label connecting a promise to its contained value. */ - string promised() { result = "promised" } + /** Gets the `promisedError` edge label connecting a promise to its rejected value. */ + MkLabelPromisedError promisedError() { any() } + } +} + +/** Holds if module `m` exports `rhs`. */ +private predicate exports(string m, DataFlow::Node rhs) { + exists(Module mod | mod = importableModule(m) | + rhs = mod.(AmdModule).getDefine().getModuleExpr().flow() + or + exports(m, "default", rhs) + or + exists(ExportAssignDeclaration assgn | assgn.getTopLevel() = mod | + rhs = assgn.getExpression().flow() + ) + or + rhs = mod.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr().flow() + ) +} + +/** Holds if module `m` exports `rhs` under the name `prop`. */ +private predicate exports(string m, string prop, DataFlow::Node rhs) { + exists(ExportDeclaration exp | exp.getEnclosingModule() = importableModule(m) | + rhs = exp.getSourceNode(prop) + or + exists(Variable v | + exp.exportsAs(v, prop) and + rhs = v.getAnAssignedExpr().flow() + ) + ) +} - /** Gets the `promisedError` edge label connecting a promise to its rejected value. */ - string promisedError() { result = "promisedError" } +/** Gets the definition of module `m`. */ +private Module importableModule(string m) { + exists(NPMPackage pkg, PackageJSON json | json = pkg.getPackageJSON() and not json.isPrivate() | + result = pkg.getMainModule() and + not result.isExterns() and + m = pkg.getPackageName() + ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/D3.qll b/javascript/ql/lib/semmle/javascript/frameworks/D3.qll index 49ec68e24241..252e87d3c1dd 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/D3.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/D3.qll @@ -23,7 +23,7 @@ module D3 { or result = API::moduleImport("d3-node").getInstance().getMember("d3") or - result = API::root().getASuccessor(any(D3GlobalEntry i)) + result = any(D3GlobalEntry i).getNode() } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/History.qll b/javascript/ql/lib/semmle/javascript/frameworks/History.qll index 05be7ec36ddb..6913fe93daff 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/History.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/History.qll @@ -17,7 +17,7 @@ module History { * Gets a reference to the [`history`](https://npmjs.org/package/history) library. */ private API::Node history() { - result = [API::moduleImport("history"), API::root().getASuccessor(any(HistoryGlobalEntry h))] + result = [API::moduleImport("history"), any(HistoryGlobalEntry h).getNode()] } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll b/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll index c039199bbd4b..6afb60f73d31 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll @@ -27,7 +27,7 @@ private module Immutable { API::Node immutableImport() { result = API::moduleImport("immutable") or - result = API::root().getASuccessor(any(ImmutableGlobalEntry i)) + result = any(ImmutableGlobalEntry i).getNode() } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll b/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll index 4a64ed2a7e17..ea8f97ca9346 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll @@ -45,7 +45,7 @@ private module Console { */ private API::Node console() { result = API::moduleImport("console") or - result = API::root().getASuccessor(any(ConsoleGlobalEntry e)) + result = any(ConsoleGlobalEntry e).getNode() } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll b/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll index d216fac17125..88d626e53985 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll @@ -151,7 +151,7 @@ module NestJS { private API::Node validationPipe() { result = nestjs().getMember("ValidationPipe") or - result = API::root().getASuccessor(any(ValidationNodeEntry e)) + result = any(ValidationNodeEntry e).getNode() } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll index d26982cef7f6..1451c69ada85 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll @@ -1111,9 +1111,7 @@ module Redux { /** A heuristic call to `connect`, recognized by it taking arguments named `mapStateToProps` and `mapDispatchToProps`. */ private class HeuristicConnectFunction extends ConnectCall { - HeuristicConnectFunction() { - this = API::root().getASuccessor(any(HeuristicConnectEntryPoint e)).getACall() - } + HeuristicConnectFunction() { this = any(HeuristicConnectEntryPoint e).getNode().getACall() } override API::Node getMapStateToProps() { result = getAParameter() and diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll index 2101f29fdab6..8689140f126d 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll @@ -35,7 +35,7 @@ module Vue { API::Node vueLibrary() { result = API::moduleImport("vue") or - result = API::root().getASuccessor(any(GlobalVueEntryPoint e)) + result = any(GlobalVueEntryPoint e).getNode() } /** @@ -51,7 +51,7 @@ module Vue { or result = vueLibrary().getMember("component").getReturn() or - result = API::root().getASuccessor(any(VueFileImportEntryPoint e)) + result = any(VueFileImportEntryPoint e).getNode() } /** diff --git a/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll b/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll index e2df38f0c7c1..da7658fe872c 100644 --- a/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll +++ b/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll @@ -233,6 +233,43 @@ module Stages { } } + /** + * The `APIStage` stage. + */ + cached + module APIStage { + /** + * Always holds. + * Ensures that a predicate is evaluated as part of the APIStage stage. + */ + cached + predicate ref() { 1 = 1 } + + /** + * DONT USE! + * Contains references to each predicate that use the above `ref` predicate. + */ + cached + predicate backref() { + 1 = 1 + or + exists( + API::moduleImport("foo") + .getMember("bar") + .getUnknownMember() + .getAMember() + .getAParameter() + .getPromised() + .getReturn() + .getParameter(2) + .getUnknownMember() + .getInstance() + .getReceiver() + .getPromisedError() + ) + } + } + /** * The `taint` stage. */ diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll index f9eaa7c7ce71..c2a3736e7786 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll @@ -211,11 +211,9 @@ module ExternalAPIUsedWithUntrustedData { node = getNamedParameter(base.getAParameter(), paramName) and result = basename + ".[callback].[param '" + paramName + "']" or - exists(string callbackName, string index | - node = - getNamedParameter(base.getASuccessor("parameter " + index).getMember(callbackName), - paramName) and - index != "-1" and // ignore receiver + exists(string callbackName, int index | + node = getNamedParameter(base.getParameter(index).getMember(callbackName), paramName) and + index != -1 and // ignore receiver result = basename + ".[callback " + index + " '" + callbackName + "'].[param '" + paramName + "']" diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll index 89574f4886b1..81fb8dd2cf50 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -117,16 +117,23 @@ private class RemoteFlowSourceAccessPath extends JSONString { string getSourceType() { result = sourceType } /** Gets the `i`th component of the access path specifying this remote flow source. */ - string getComponent(int i) { + API::ApiLabel getComponent(int i) { exists(string raw | raw = this.getValue().splitAt(".", i + 1) | i = 0 and - result = "ExternalRemoteFlowSourceSpec " + raw + result + .(API::EdgeLabel::LabelEntryPoint) + .getEntryPoint() + .(ExternalRemoteFlowSourceSpecEntryPoint) + .getName() = raw or i > 0 and result = API::EdgeLabel::member(raw) ) } + /** Gets the first part of this access path. E.g. for "window.user.name" the result is "window". */ + string getRootPath() { result = this.getValue().splitAt(".", 1) } + /** Gets the index of the last component of this access path. */ int getMaxComponentIndex() { result = max(int i | exists(getComponent(i))) } @@ -154,10 +161,12 @@ private class ExternalRemoteFlowSourceSpecEntryPoint extends API::EntryPoint { string name; ExternalRemoteFlowSourceSpecEntryPoint() { - this = any(RemoteFlowSourceAccessPath s).getComponent(0) and + name = any(RemoteFlowSourceAccessPath s).getRootPath() and this = "ExternalRemoteFlowSourceSpec " + name } + string getName() { result = name } + override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef(name) } override DataFlow::Node getARhs() { none() } diff --git a/javascript/ql/src/meta/ApiGraphs/ApiGraphEdges.ql b/javascript/ql/src/meta/ApiGraphs/ApiGraphEdges.ql index 16718a9d1227..1920bfb183fe 100644 --- a/javascript/ql/src/meta/ApiGraphs/ApiGraphEdges.ql +++ b/javascript/ql/src/meta/ApiGraphs/ApiGraphEdges.ql @@ -12,4 +12,4 @@ import javascript import meta.MetaMetrics select projectRoot(), - count(API::Node pred, string lbl, API::Node succ | succ = pred.getASuccessor(lbl)) + count(API::Node pred, API::ApiLabel lbl, API::Node succ | succ = pred.getASuccessor(lbl)) diff --git a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll index 4c92792cce5e..6a57d85518af 100644 --- a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll +++ b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll @@ -62,7 +62,8 @@ class Assertion extends Comment { i = getPathLength() and result = API::root() or - result = lookup(i + 1).getASuccessor(getEdgeLabel(i)) + result = + lookup(i + 1).getASuccessor(any(API::ApiLabel label | label.toString() = getEdgeLabel(i))) } predicate isNegative() { polarity = "!" } @@ -79,7 +80,11 @@ class Assertion extends Comment { then suffix = "it does have outgoing edges labelled " + - concat(string lbl | exists(nd.getASuccessor(lbl)) | lbl, ", ") + "." + concat(string lbl | + exists(nd.getASuccessor(any(API::ApiLabel label | label.toString() = lbl))) + | + lbl, ", " + ) + "." else suffix = "it has no outgoing edges at all." | result = prefix + " " + suffix From f39872e649486ee9ea029a7cf27050adb5d4ae03 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 19 Nov 2021 11:15:34 +0100 Subject: [PATCH 2/7] cache more predicates --- .../ql/lib/semmle/javascript/Constants.qll | 28 ++++++++++++++++++- javascript/ql/lib/semmle/javascript/Expr.qll | 9 ++++-- .../javascript/MembershipCandidates.qll | 1 + .../javascript/dataflow/TaintTracking.qll | 4 ++- .../javascript/internal/CachedStages.qll | 22 +++++++++++++++ 5 files changed, 60 insertions(+), 4 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/Constants.qll b/javascript/ql/lib/semmle/javascript/Constants.qll index b9d47fbd8e8a..d914dea8d9c9 100644 --- a/javascript/ql/lib/semmle/javascript/Constants.qll +++ b/javascript/ql/lib/semmle/javascript/Constants.qll @@ -3,10 +3,12 @@ */ import javascript +private import semmle.javascript.internal.CachedStages /** * An expression that evaluates to a constant primitive value. */ +cached abstract class ConstantExpr extends Expr { } /** @@ -16,6 +18,7 @@ module SyntacticConstants { /** * An expression that evaluates to a constant value according to a bottom-up syntactic analysis. */ + cached abstract class SyntacticConstant extends ConstantExpr { } /** @@ -23,8 +26,11 @@ module SyntacticConstants { * * Note that `undefined`, `NaN` and `Infinity` are global variables, and are not covered by this class. */ + cached class PrimitiveLiteralConstant extends SyntacticConstant { + cached PrimitiveLiteralConstant() { + Stages::Ast::ref() and this instanceof NumberLiteral or this instanceof StringLiteral @@ -43,19 +49,27 @@ module SyntacticConstants { /** * A literal null expression. */ - class NullConstant extends SyntacticConstant, NullLiteral { } + cached + class NullConstant extends SyntacticConstant, NullLiteral { + cached + NullConstant() { Stages::Ast::ref() and this = this } + } /** * A unary operation on a syntactic constant. */ + cached class UnaryConstant extends SyntacticConstant, UnaryExpr { + cached UnaryConstant() { getOperand() instanceof SyntacticConstant } } /** * A binary operation on syntactic constants. */ + cached class BinaryConstant extends SyntacticConstant, BinaryExpr { + cached BinaryConstant() { getLeftOperand() instanceof SyntacticConstant and getRightOperand() instanceof SyntacticConstant @@ -65,7 +79,9 @@ module SyntacticConstants { /** * A conditional expression on syntactic constants. */ + cached class ConditionalConstant extends SyntacticConstant, ConditionalExpr { + cached ConditionalConstant() { getCondition() instanceof SyntacticConstant and getConsequent() instanceof SyntacticConstant and @@ -76,7 +92,9 @@ module SyntacticConstants { /** * A use of the global variable `undefined` or `void e`. */ + cached class UndefinedConstant extends SyntacticConstant { + cached UndefinedConstant() { this.(GlobalVarAccess).getName() = "undefined" or this instanceof VoidExpr @@ -86,21 +104,27 @@ module SyntacticConstants { /** * A use of the global variable `NaN`. */ + cached class NaNConstant extends SyntacticConstant { + cached NaNConstant() { this.(GlobalVarAccess).getName() = "NaN" } } /** * A use of the global variable `Infinity`. */ + cached class InfinityConstant extends SyntacticConstant { + cached InfinityConstant() { this.(GlobalVarAccess).getName() = "Infinity" } } /** * An expression that wraps the syntactic constant it evaluates to. */ + cached class WrappedConstant extends SyntacticConstant { + cached WrappedConstant() { getUnderlyingValue() instanceof SyntacticConstant } } @@ -123,6 +147,8 @@ module SyntacticConstants { /** * An expression that evaluates to a constant string. */ +cached class ConstantString extends ConstantExpr { + cached ConstantString() { exists(getStringValue()) } } diff --git a/javascript/ql/lib/semmle/javascript/Expr.qll b/javascript/ql/lib/semmle/javascript/Expr.qll index 4d91fdb218ef..a9b881c6e536 100644 --- a/javascript/ql/lib/semmle/javascript/Expr.qll +++ b/javascript/ql/lib/semmle/javascript/Expr.qll @@ -89,7 +89,8 @@ class ExprOrType extends @expr_or_type, Documentable { * * Also see `getUnderlyingReference` and `stripParens`. */ - Expr getUnderlyingValue() { result = this } + cached + Expr getUnderlyingValue() { Stages::Ast::ref() and result = this } } /** @@ -274,7 +275,11 @@ private DataFlow::Node getCatchParameterFromStmt(Stmt stmt) { */ class Identifier extends @identifier, ExprOrType { /** Gets the name of this identifier. */ - string getName() { literals(result, _, this) } + cached + string getName() { + Stages::Ast::ref() and + literals(result, _, this) + } override string getAPrimaryQlClass() { result = "Identifier" } } diff --git a/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll b/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll index fe46eff040e7..6c51b487f43c 100644 --- a/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll +++ b/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll @@ -165,6 +165,7 @@ module MembershipCandidate { EnumerationRegExp enumeration; boolean polarity; + pragma[nomagic] RegExpEnumerationCandidate() { exists(DataFlow::MethodCallNode mcn, DataFlow::Node base, string m, DataFlow::Node firstArg | ( diff --git a/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll index 3b58b7e046fc..cb3cdec8132f 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll @@ -160,10 +160,12 @@ module TaintTracking { * of the standard library. Override `Configuration::isSanitizerGuard` * for analysis-specific taint sanitizer guards. */ + cached abstract class AdditionalSanitizerGuardNode extends SanitizerGuardNode { /** * Holds if this guard applies to the flow in `cfg`. */ + cached abstract predicate appliesTo(Configuration cfg); } @@ -1127,7 +1129,7 @@ module TaintTracking { idx = astNode.getAnOperand() and idx.getPropertyNameExpr() = x and // and the other one is guaranteed to be `undefined` - forex(InferredType tp | tp = undef.getAType() | tp = TTUndefined()) + unique(InferredType tp | tp = pragma[only_bind_into](undef.getAType())) = TTUndefined() ) } diff --git a/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll b/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll index da7658fe872c..cda0727f1360 100644 --- a/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll +++ b/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll @@ -69,6 +69,14 @@ module Stages { exists(any(Expr e).getStringValue()) or any(ASTNode node).isAmbient() + or + exists(any(Identifier e).getName()) + or + exists(any(ExprOrType e).getUnderlyingValue()) + or + exists(ConstantExpr e) + or + exists(SyntacticConstants::NullConstant n) } } @@ -299,6 +307,20 @@ module Stages { exists(Exports::getALibraryInputParameter()) or any(RegExpTerm t).isUsedAsRegExp() + or + any(TaintTracking::AdditionalSanitizerGuardNode e).appliesTo(_) + } + + cached + class DummySanitizer extends TaintTracking::AdditionalSanitizerGuardNode { + cached + DummySanitizer() { none() } + + cached + override predicate appliesTo(TaintTracking::Configuration cfg) { none() } + + cached + override predicate sanitizes(boolean outcome, Expr e) { none() } } } } From c369b28a2a76cdef1c716e2fdb87680eafd7dab2 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 19 Nov 2021 11:15:59 +0100 Subject: [PATCH 3/7] optimizations in global data flow --- .../javascript/dataflow/Configuration.qll | 66 +++++++++++-------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll index de96295ce219..835c2f7e6269 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll @@ -939,18 +939,21 @@ private predicate basicFlowStepNoBarrier( * This predicate is field insensitive (it does not distinguish between `x` and `x.p`) * and hence should only be used for purposes of approximation. */ -pragma[inline] +pragma[noinline] private predicate exploratoryFlowStep( DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg ) { - basicFlowStepNoBarrier(pred, succ, _, cfg) or - exploratoryLoadStep(pred, succ, cfg) or - isAdditionalLoadStoreStep(pred, succ, _, _, cfg) or - // the following three disjuncts taken together over-approximate flow through - // higher-order calls - exploratoryCallbackStep(pred, succ) or - succ = pred.(DataFlow::FunctionNode).getAParameter() or - exploratoryBoundInvokeStep(pred, succ) + isRelevantForward(pred, cfg) and + ( + basicFlowStepNoBarrier(pred, succ, _, cfg) or + exploratoryLoadStep(pred, succ, cfg) or + isAdditionalLoadStoreStep(pred, succ, _, _, cfg) or + // the following three disjuncts taken together over-approximate flow through + // higher-order calls + exploratoryCallbackStep(pred, succ) or + succ = pred.(DataFlow::FunctionNode).getAParameter() or + exploratoryBoundInvokeStep(pred, succ) + ) } /** @@ -1024,6 +1027,7 @@ private string getAPropertyUsedInLoadStore(DataFlow::Configuration cfg) { * Holds if there exists a store-step from `pred` to `succ` under configuration `cfg`, * and somewhere in the program there exists a load-step that could possibly read the stored value. */ +pragma[noinline] private predicate exploratoryForwardStoreStep( DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg ) { @@ -1075,8 +1079,10 @@ private string getABackwardsRelevantStoreProperty(DataFlow::Configuration cfg) { private predicate isRelevantForward(DataFlow::Node nd, DataFlow::Configuration cfg) { isSource(nd, cfg, _) and isLive() or - exists(DataFlow::Node mid | isRelevantForward(mid, cfg) | - exploratoryFlowStep(mid, nd, cfg) or + exists(DataFlow::Node mid | + exploratoryFlowStep(mid, nd, cfg) + or + isRelevantForward(mid, cfg) and exploratoryForwardStoreStep(mid, nd, cfg) ) } @@ -1098,11 +1104,10 @@ private predicate isRelevant(DataFlow::Node nd, DataFlow::Configuration cfg) { private predicate isRelevantBackStep( DataFlow::Node mid, DataFlow::Node nd, DataFlow::Configuration cfg ) { + exploratoryFlowStep(nd, mid, cfg) + or isRelevantForward(nd, cfg) and - ( - exploratoryFlowStep(nd, mid, cfg) or - exploratoryBackwardStoreStep(nd, mid, cfg) - ) + exploratoryBackwardStoreStep(nd, mid, cfg) } /** @@ -1273,23 +1278,30 @@ private predicate parameterPropRead( DataFlow::Node arg, string prop, DataFlow::Node succ, DataFlow::Configuration cfg, PathSummary summary ) { - exists(Function f, DataFlow::Node read, DataFlow::Node invk | + exists(Function f, DataFlow::Node read, DataFlow::Node invk, DataFlow::Node parm | + reachesReturn(f, read, cfg, summary) and + parameterPropReadStep(parm, read, prop, cfg, arg, invk, f, succ) + ) +} + +// all the non-recursive parts of parameterPropRead outlined into a precomputed predicate +pragma[noinline] +private predicate parameterPropReadStep( + DataFlow::SourceNode parm, DataFlow::Node read, string prop, DataFlow::Configuration cfg, + DataFlow::Node arg, DataFlow::Node invk, Function f, DataFlow::Node succ +) { + ( not f.isAsyncOrGenerator() and invk = succ or // load from an immediately awaited function call f.isAsync() and invk = getAwaitOperand(succ) - | - exists(DataFlow::SourceNode parm | - callInputStep(f, invk, arg, parm, cfg) and - ( - reachesReturn(f, read, cfg, summary) and - read = parm.getAPropertyRead(prop) - or - reachesReturn(f, read, cfg, summary) and - exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg)) - ) - ) + ) and + callInputStep(f, invk, arg, parm, cfg) and + ( + read = parm.getAPropertyRead(prop) + or + exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg)) ) } From 6060f2e3e3c3eef2b53ad436db02d290f42c8555 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 19 Nov 2021 23:04:34 +0100 Subject: [PATCH 4/7] remove unused alias edge --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 740ac525b54a..c90f542070c8 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -317,7 +317,6 @@ module API { or exists(Node pred, ApiLabel lbl, string predpath | Impl::edge(pred, lbl, this) and - not lbl instanceof Label::LabelAlias and predpath = pred.getAPath(length - 1) and exists(string space | if length = 1 then space = "" else space = " " | result = "(" + lbl + space + predpath + ")" and @@ -1033,7 +1032,6 @@ module API { MkLabelReturn() or MkLabelPromised() or MkLabelPromisedError() or - MkLabelAlias() or MkLabelEntryPoint(API::EntryPoint e) class LabelEntryPoint extends ApiLabel { @@ -1046,12 +1044,6 @@ module API { override string toString() { result = e } } - class LabelAlias extends ApiLabel { - LabelAlias() { this = MkLabelAlias() } - - override string toString() { result = "" } - } - class LabelPromised extends ApiLabel { LabelPromised() { this = MkLabelPromised() } @@ -1174,9 +1166,6 @@ module API { /** Gets the `return` edge label. */ LabelReturn return() { any() } - /** Gets the `alias` (empty) edge label. */ - LabelAlias alias() { any() } - /** Gets the `promised` edge label connecting a promise to its contained value. */ MkLabelPromised promised() { any() } From e9df860431f2b932bd04f1cc1e19494dd0e1963e Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 22 Nov 2021 09:43:13 +0100 Subject: [PATCH 5/7] refactor implementation to make Label implementations private --- .../ql/lib/semmle/javascript/ApiGraphs.qll | 262 ++++++++++-------- .../security/dataflow/RemoteFlowSources.qll | 11 +- .../ql/src/meta/ApiGraphs/ApiGraphEdges.ql | 2 +- .../ql/test/ApiGraphs/VerifyAssertions.qll | 5 +- 4 files changed, 151 insertions(+), 129 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index c90f542070c8..4aa3d20ffb7f 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -243,13 +243,13 @@ module API { * Gets a node such that there is an edge in the API graph between this node and the other * one, and that edge is labeled with `lbl`. */ - Node getASuccessor(ApiLabel lbl) { Impl::edge(this, lbl, result) } + Node getASuccessor(Label::ApiLabel lbl) { Impl::edge(this, lbl, result) } /** * Gets a node such that there is an edge in the API graph between that other node and * this one, and that edge is labeled with `lbl` */ - Node getAPredecessor(ApiLabel lbl) { this = result.getASuccessor(lbl) } + Node getAPredecessor(Label::ApiLabel lbl) { this = result.getASuccessor(lbl) } /** * Gets a node such that there is an edge in the API graph between this node and the other @@ -315,7 +315,7 @@ module API { length = 0 and result = "" or - exists(Node pred, ApiLabel lbl, string predpath | + exists(Node pred, Label::ApiLabel lbl, string predpath | Impl::edge(pred, lbl, this) and predpath = pred.getAPath(length - 1) and exists(string space | if length = 1 then space = "" else space = " " | @@ -383,9 +383,7 @@ module API { abstract DataFlow::Node getARhs(); /** Gets an API-node for this entry point. */ - API::Node getNode() { - result = root().getASuccessor(any(Label::LabelEntryPoint l | l.getEntryPoint() = this)) - } + API::Node getNode() { result = root().getASuccessor(Label::entryPoint(this)) } } /** @@ -474,11 +472,14 @@ module API { * incoming edge from `base` labeled `lbl` in the API graph. */ cached - predicate rhs(TApiNode base, ApiLabel lbl, DataFlow::Node rhs) { + predicate rhs(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and - rhs = lbl.(Label::LabelEntryPoint).getEntryPoint().getARhs() + exists(EntryPoint e | + lbl = Label::entryPoint(e) and + rhs = e.getARhs() + ) or exists(string m, string prop | base = MkModuleExport(m) and @@ -590,7 +591,7 @@ module API { */ pragma[noinline] private predicate propertyRead( - DataFlow::SourceNode pred, string propDesc, ApiLabel lbl, DataFlow::Node ref + DataFlow::SourceNode pred, string propDesc, Label::ApiLabel lbl, DataFlow::Node ref ) { ref = pred.getAPropertyRead() and lbl = Label::memberFromRef(ref) and @@ -614,11 +615,14 @@ module API { * `lbl` in the API graph. */ cached - predicate use(TApiNode base, ApiLabel lbl, DataFlow::Node ref) { + predicate use(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { hasSemantics(ref) and ( base = MkRoot() and - ref = lbl.(Label::LabelEntryPoint).getEntryPoint().getAUse() + exists(EntryPoint e | + lbl = Label::entryPoint(e) and + ref = e.getAUse() + ) or // property reads exists(DataFlow::SourceNode src, DataFlow::SourceNode pred, string propDesc | @@ -863,7 +867,7 @@ module API { * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. */ cached - predicate edge(TApiNode pred, ApiLabel lbl, TApiNode succ) { + predicate edge(TApiNode pred, Label::ApiLabel lbl, TApiNode succ) { Stages::APIStage::ref() and exists(string m | pred = MkRoot() and @@ -941,8 +945,6 @@ module API { } } - import Label as EdgeLabel - /** * An `InvokeNode` that is connected to the API graph. * @@ -999,109 +1001,12 @@ module API { /** A `new` call connected to the API graph. */ class NewNode extends InvokeNode, DataFlow::NewNode { } - /** A label in the API-graph */ - abstract class ApiLabel extends Label::TLabel { - string toString() { result = "???" } - } - - private module Label { - newtype TLabel = - MkLabelMod(string mod) { - exists(Impl::MkModuleExport(mod)) or - exists(Impl::MkModuleImport(mod)) - } or - MkLabelInstance() or - MkLabelMember(string prop) { - exports(_, prop, _) or - exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or - prop = "exports" or - prop = any(CanonicalName c).getName() or - prop = any(DataFlow::PropRef p).getPropertyName() or - exists(Impl::MkTypeUse(_, prop)) or - exists(any(Module m).getAnExportedValue(prop)) - } or - MkLabelUnknownMember() or - MkLabelParameter(int i) { - i = - [-1 .. max(int args | - args = any(InvokeExpr invk).getNumArgument() or - args = any(Function f).getNumParameter() - )] or - i = [0 .. 10] - } or - MkLabelReturn() or - MkLabelPromised() or - MkLabelPromisedError() or - MkLabelEntryPoint(API::EntryPoint e) - - class LabelEntryPoint extends ApiLabel { - API::EntryPoint e; - - LabelEntryPoint() { this = MkLabelEntryPoint(e) } - - API::EntryPoint getEntryPoint() { result = e } - - override string toString() { result = e } - } - - class LabelPromised extends ApiLabel { - LabelPromised() { this = MkLabelPromised() } - - override string toString() { result = "promised" } - } - - class LabelPromisedError extends ApiLabel { - LabelPromisedError() { this = MkLabelPromisedError() } - - override string toString() { result = "promised" } - } - - class LabelReturn extends ApiLabel { - LabelReturn() { this = MkLabelReturn() } - - override string toString() { result = "return" } - } - - class LabelMod extends ApiLabel { - string mod; - - LabelMod() { this = MkLabelMod(mod) } - - string getMod() { result = mod } - - override string toString() { result = "module " + mod } - } - - class LabelInstance extends ApiLabel { - LabelInstance() { this = MkLabelInstance() } - - override string toString() { result = "instance" } - } - - class LabelMember extends ApiLabel { - string prop; - - LabelMember() { this = MkLabelMember(prop) } - - string getProperty() { result = prop } - - override string toString() { result = "member " + prop } - } - - class LabelUnknownMember extends ApiLabel { - LabelUnknownMember() { this = MkLabelUnknownMember() } - - override string toString() { result = "member *" } - } - - class LabelParameter extends ApiLabel { - int i; - - LabelParameter() { this = MkLabelParameter(i) } - - override string toString() { result = "parameter " + i } - - int getIndex() { result = i } + /** Provides classes modeling the various edges (labels) in the API graph. */ + module Label { + /** A label in the API-graph */ + abstract class ApiLabel extends TLabel { + /** Gets a string representation of this label. */ + string toString() { result = "???" } } /** Gets the edge label for the module `m`. */ @@ -1167,10 +1072,129 @@ module API { LabelReturn return() { any() } /** Gets the `promised` edge label connecting a promise to its contained value. */ - MkLabelPromised promised() { any() } + LabelPromised promised() { any() } /** Gets the `promisedError` edge label connecting a promise to its rejected value. */ - MkLabelPromisedError promisedError() { any() } + LabelPromisedError promisedError() { any() } + + /** Gets an entry-point label for the entry-point `e`. */ + LabelEntryPoint entryPoint(API::EntryPoint e) { result.getEntryPoint() = e } + + private import LabelImpl + + private module LabelImpl { + newtype TLabel = + MkLabelMod(string mod) { + exists(Impl::MkModuleExport(mod)) or + exists(Impl::MkModuleImport(mod)) + } or + MkLabelInstance() or + MkLabelMember(string prop) { + exports(_, prop, _) or + exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or + prop = "exports" or + prop = any(CanonicalName c).getName() or + prop = any(DataFlow::PropRef p).getPropertyName() or + exists(Impl::MkTypeUse(_, prop)) or + exists(any(Module m).getAnExportedValue(prop)) + } or + MkLabelUnknownMember() or + MkLabelParameter(int i) { + i = + [-1 .. max(int args | + args = any(InvokeExpr invk).getNumArgument() or + args = any(Function f).getNumParameter() + )] or + i = [0 .. 10] + } or + MkLabelReturn() or + MkLabelPromised() or + MkLabelPromisedError() or + MkLabelEntryPoint(API::EntryPoint e) + + /** A label for an entry-point. */ + class LabelEntryPoint extends ApiLabel { + API::EntryPoint e; + + LabelEntryPoint() { this = MkLabelEntryPoint(e) } + + /** Gets the EntryPoint associated with this label. */ + API::EntryPoint getEntryPoint() { result = e } + + override string toString() { result = e } + } + + /** A label that gets a promised value. */ + class LabelPromised extends ApiLabel { + LabelPromised() { this = MkLabelPromised() } + + override string toString() { result = "promised" } + } + + /** A label that gets a rejected promise. */ + class LabelPromisedError extends ApiLabel { + LabelPromisedError() { this = MkLabelPromisedError() } + + override string toString() { result = "promisedError" } + } + + /** A label that gets the return value of a function. */ + class LabelReturn extends ApiLabel { + LabelReturn() { this = MkLabelReturn() } + + override string toString() { result = "return" } + } + + /** A label for a module. */ + class LabelMod extends ApiLabel { + string mod; + + LabelMod() { this = MkLabelMod(mod) } + + /** Gets the module associated with this label. */ + string getMod() { result = mod } + + override string toString() { result = "module " + mod } + } + + /** A label that gets an instance from a `new` call. */ + class LabelInstance extends ApiLabel { + LabelInstance() { this = MkLabelInstance() } + + override string toString() { result = "instance" } + } + + /** A label for the member named `prop`. */ + class LabelMember extends ApiLabel { + string prop; + + LabelMember() { this = MkLabelMember(prop) } + + /** Gets the property associated with this label. */ + string getProperty() { result = prop } + + override string toString() { result = "member " + prop } + } + + /** A label for a member with an unknown name. */ + class LabelUnknownMember extends ApiLabel { + LabelUnknownMember() { this = MkLabelUnknownMember() } + + override string toString() { result = "member *" } + } + + /** A label for parameter `i`. */ + class LabelParameter extends ApiLabel { + int i; + + LabelParameter() { this = MkLabelParameter(i) } + + override string toString() { result = "parameter " + i } + + /** Gets the index of the parameter for this label. */ + int getIndex() { result = i } + } + } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll index 81fb8dd2cf50..2aba7f8fed60 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -117,17 +117,14 @@ private class RemoteFlowSourceAccessPath extends JSONString { string getSourceType() { result = sourceType } /** Gets the `i`th component of the access path specifying this remote flow source. */ - API::ApiLabel getComponent(int i) { + API::Label::ApiLabel getComponent(int i) { exists(string raw | raw = this.getValue().splitAt(".", i + 1) | i = 0 and - result - .(API::EdgeLabel::LabelEntryPoint) - .getEntryPoint() - .(ExternalRemoteFlowSourceSpecEntryPoint) - .getName() = raw + result = + API::Label::entryPoint(any(ExternalRemoteFlowSourceSpecEntryPoint e | e.getName() = raw)) or i > 0 and - result = API::EdgeLabel::member(raw) + result = API::Label::member(raw) ) } diff --git a/javascript/ql/src/meta/ApiGraphs/ApiGraphEdges.ql b/javascript/ql/src/meta/ApiGraphs/ApiGraphEdges.ql index 1920bfb183fe..23bdad597542 100644 --- a/javascript/ql/src/meta/ApiGraphs/ApiGraphEdges.ql +++ b/javascript/ql/src/meta/ApiGraphs/ApiGraphEdges.ql @@ -12,4 +12,4 @@ import javascript import meta.MetaMetrics select projectRoot(), - count(API::Node pred, API::ApiLabel lbl, API::Node succ | succ = pred.getASuccessor(lbl)) + count(API::Node pred, API::Label::ApiLabel lbl, API::Node succ | succ = pred.getASuccessor(lbl)) diff --git a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll index 6a57d85518af..1c8c494863ed 100644 --- a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll +++ b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll @@ -63,7 +63,8 @@ class Assertion extends Comment { result = API::root() or result = - lookup(i + 1).getASuccessor(any(API::ApiLabel label | label.toString() = getEdgeLabel(i))) + lookup(i + 1) + .getASuccessor(any(API::Label::ApiLabel label | label.toString() = getEdgeLabel(i))) } predicate isNegative() { polarity = "!" } @@ -81,7 +82,7 @@ class Assertion extends Comment { suffix = "it does have outgoing edges labelled " + concat(string lbl | - exists(nd.getASuccessor(any(API::ApiLabel label | label.toString() = lbl))) + exists(nd.getASuccessor(any(API::Label::ApiLabel label | label.toString() = lbl))) | lbl, ", " ) + "." From 148da611c6ca20604be2945bd9b7e45f01631142 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 1 Dec 2021 13:45:52 +0100 Subject: [PATCH 6/7] make the ApiLabel class non-abstract --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 2188eb8fadc6..e51c5a82fdc7 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1006,7 +1006,7 @@ module API { /** Provides classes modeling the various edges (labels) in the API graph. */ module Label { /** A label in the API-graph */ - abstract class ApiLabel extends TLabel { + class ApiLabel extends TLabel { /** Gets a string representation of this label. */ string toString() { result = "???" } } From 0a3d62c92a956677e791629761a17f9198855760 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 1 Dec 2021 13:48:16 +0100 Subject: [PATCH 7/7] rename `mod` -> `module` --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index e51c5a82fdc7..0f8dd3178d7b 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -873,7 +873,7 @@ module API { Stages::APIStage::ref() and exists(string m | pred = MkRoot() and - lbl = Label::mod(m) + lbl = Label::moduleLabel(m) | succ = MkModuleDef(m) or @@ -1012,7 +1012,7 @@ module API { } /** Gets the edge label for the module `m`. */ - LabelMod mod(string m) { result.getMod() = m } + LabelModule moduleLabel(string m) { result.getMod() = m } /** Gets the `member` edge label for member `m`. */ bindingset[m] @@ -1086,7 +1086,7 @@ module API { private module LabelImpl { newtype TLabel = - MkLabelMod(string mod) { + MkLabelModule(string mod) { exists(Impl::MkModuleExport(mod)) or exists(Impl::MkModuleImport(mod)) } or @@ -1148,10 +1148,10 @@ module API { } /** A label for a module. */ - class LabelMod extends ApiLabel { + class LabelModule extends ApiLabel { string mod; - LabelMod() { this = MkLabelMod(mod) } + LabelModule() { this = MkLabelModule(mod) } /** Gets the module associated with this label. */ string getMod() { result = mod } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy