diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index d2229795642f..0f8dd3178d7b 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 = this.getASuccessor(Label::member(m)) } + cached + Node getMember(string m) { + Stages::APIStage::ref() and + result = this.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 = this.getASuccessor(Label::unknownMember()) } + cached + Node getUnknownMember() { + Stages::APIStage::ref() and + result = this.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 = this.getASuccessor(Label::member(_)) or + Stages::APIStage::ref() and + result = this.getMember(_) + or result = this.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 = this.getASuccessor(Label::instance()) } + cached + Node getInstance() { + Stages::APIStage::ref() and + result = this.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 = this.getASuccessor(Label::parameter(i)) } + cached + Node getParameter(int i) { + Stages::APIStage::ref() and + result = this.getASuccessor(Label::parameter(i)) + } /** * Gets the number of parameters of the function represented by this node. */ - int getNumParameter() { - result = - max(string s | exists(this.getASuccessor(Label::parameterByStringIndex(s))) | s.toInt()) + 1 - } + int getNumParameter() { result = max(int s | exists(this.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 = this.getASuccessor(Label::receiver()) } + cached + Node getReceiver() { + Stages::APIStage::ref() and + result = this.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 = this.getASuccessor(Label::parameterByStringIndex(_)) or + Stages::APIStage::ref() and + result = this.getParameter(_) + or result = this.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 = this.getASuccessor(Label::return()) } + cached + Node getReturn() { + Stages::APIStage::ref() and + result = this.getASuccessor(Label::return()) + } /** * Gets a node representing the promised value wrapped in the `Promise` object represented by * this node. */ - Node getPromised() { result = this.getASuccessor(Label::promised()) } + cached + Node getPromised() { + Stages::APIStage::ref() and + result = this.getASuccessor(Label::promised()) + } /** * Gets a node representing the error wrapped in the `Promise` object represented by this node. */ - Node getPromisedError() { result = this.getASuccessor(Label::promisedError()) } + cached + Node getPromisedError() { + Stages::APIStage::ref() and + result = this.getASuccessor(Label::promisedError()) + } /** * Gets a string representation of the lexicographically least among all shortest access paths @@ -211,13 +245,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(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(string 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 @@ -283,9 +317,8 @@ module API { length = 0 and result = "" or - exists(Node pred, string lbl, string predpath | + exists(Node pred, Label::ApiLabel lbl, string predpath | Impl::edge(pred, lbl, this) and - lbl != "" and predpath = pred.getAPath(length - 1) and exists(string space | if length = 1 then space = "" else space = " " | result = "(" + lbl + space + predpath + ")" and @@ -350,6 +383,9 @@ 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(Label::entryPoint(this)) } } /** @@ -433,27 +469,19 @@ 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, Label::ApiLabel lbl, DataFlow::Node rhs) { hasSemantics(rhs) and ( base = MkRoot() and - rhs = lbl.(EntryPoint).getARhs() + exists(EntryPoint e | + lbl = Label::entryPoint(e) and + rhs = e.getARhs() + ) or exists(string m, string prop | base = MkModuleExport(m) and @@ -565,7 +593,7 @@ module API { */ pragma[noinline] private predicate propertyRead( - DataFlow::SourceNode pred, string propDesc, string lbl, DataFlow::Node ref + DataFlow::SourceNode pred, string propDesc, Label::ApiLabel lbl, DataFlow::Node ref ) { ref = pred.getAPropertyRead() and lbl = Label::memberFromRef(ref) and @@ -589,11 +617,14 @@ module API { * `lbl` in the API graph. */ cached - predicate use(TApiNode base, string lbl, DataFlow::Node ref) { + predicate use(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) { hasSemantics(ref) and ( base = MkRoot() and - ref = lbl.(EntryPoint).getAUse() + exists(EntryPoint e | + lbl = Label::entryPoint(e) and + ref = e.getAUse() + ) or // property reads exists(DataFlow::SourceNode src, DataFlow::SourceNode pred, string propDesc | @@ -680,33 +711,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 /** @@ -865,10 +869,11 @@ 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, Label::ApiLabel lbl, TApiNode succ) { + Stages::APIStage::ref() and exists(string m | pred = MkRoot() and - lbl = Label::mod(m) + lbl = Label::moduleLabel(m) | succ = MkModuleDef(m) or @@ -942,8 +947,6 @@ module API { } } - import Label as EdgeLabel - /** * An `InvokeNode` that is connected to the API graph. * @@ -972,7 +975,8 @@ module API { /** * Gets an API node where a RHS of the node is the `i`th argument to this call. */ - private Node getAParameterCandidate(int i) { result.getARhs() = this.getArgument(i) } + pragma[noinline] + private Node getAParameterCandidate(int i) { result.getARhs() = getArgument(i) } /** Gets the API node for a parameter of this invocation. */ Node getAParameter() { result = this.getParameter(_) } @@ -998,89 +1002,236 @@ 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 } + /** Provides classes modeling the various edges (labels) in the API graph. */ + module Label { + /** A label in the API-graph */ + class ApiLabel extends TLabel { + /** Gets a string representation of this label. */ + string toString() { result = "???" } + } - /** Gets the `member` edge label for member `m`. */ - bindingset[m] - bindingset[result] - string member(string m) { result = "member " + m } + /** Gets the edge label for the module `m`. */ + LabelModule moduleLabel(string m) { result.getMod() = m } - /** Gets the `member` edge label for the unknown member. */ - string unknownMember() { result = "member *" } + /** Gets the `member` edge label for member `m`. */ + bindingset[m] + bindingset[result] + LabelMember member(string m) { result.getProperty() = m } - /** - * 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 the `member` edge label for the unknown member. */ + LabelUnknownMember unknownMember() { any() } - /** - * Gets unique result of `getAnIndirectPropName` if there is one. - */ - private string getIndirectPropName(DataFlow::PropRef ref) { - result = unique(string s | s = getAnIndirectPropName(ref)) - } + /** + * 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 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() - } + /** + * Gets unique result of `getAnIndirectPropName` if there is one. + */ + private string getIndirectPropName(DataFlow::PropRef ref) { + result = unique(string s | s = getAnIndirectPropName(ref)) + } - /** Gets the `instance` edge label. */ - string instance() { result = "instance" } + /** 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 `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 - } + /** 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. - */ - bindingset[i] - string parameter(int i) { result = parameterByStringIndex(i.toString()) } + /** + * 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 `promised` edge label connecting a promise to its contained value. */ + LabelPromised promised() { any() } + + /** Gets the `promisedError` edge label connecting a promise to its rejected value. */ + 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 = + MkLabelModule(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" } + } - /** Gets the `parameter` edge label for the receiver. */ - string receiver() { result = "parameter -1" } + /** A label for a module. */ + class LabelModule extends ApiLabel { + string mod; - /** Gets the `return` edge label. */ - string return() { result = "return" } + LabelModule() { this = MkLabelModule(mod) } - /** Gets the `promised` edge label connecting a promise to its contained value. */ - string promised() { result = "promised" } + /** 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 } + } + } + } +} + +/** 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/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 6d9a5b6042b3..b99b4c71be82 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/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)) ) } 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/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..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) } } @@ -233,6 +241,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. */ @@ -262,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() } } } } 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 1049c44caa88..3f7be6ab7544 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -117,16 +117,20 @@ 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::Label::ApiLabel getComponent(int i) { exists(string raw | raw = this.getValue().splitAt(".", i + 1) | i = 0 and - result = "ExternalRemoteFlowSourceSpec " + 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) ) } + /** 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(this.getComponent(i))) } @@ -154,10 +158,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..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, string 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 6cb580e5ce45..532d6ebe3b51 100644 --- a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll +++ b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll @@ -62,7 +62,9 @@ class Assertion extends Comment { i = this.getPathLength() and result = API::root() or - result = this.lookup(i + 1).getASuccessor(this.getEdgeLabel(i)) + result = + this.lookup(i + 1) + .getASuccessor(any(API::Label::ApiLabel label | label.toString() = this.getEdgeLabel(i))) } predicate isNegative() { polarity = "!" } @@ -79,7 +81,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::Label::ApiLabel label | label.toString() = lbl))) + | + lbl, ", " + ) + "." else suffix = "it has no outgoing edges at all." | result = prefix + " " + suffix 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