Merge remote-tracking branch 'upstream/master' into promiseAll

This commit is contained in:
Erik Krogh Kristensen
2019-11-25 14:34:58 +01:00
179 changed files with 4849 additions and 1150 deletions

View File

@@ -166,7 +166,7 @@ class Require extends CallExpr, Import {
exists(RequireVariable req |
this.getCallee() = req.getAnAccess() and
// `mjs` files explicitly disallow `require`
getFile().getExtension() != "mjs"
not getFile().getExtension() = "mjs"
)
}

View File

@@ -173,6 +173,23 @@ class RegExpTerm extends Locatable, @regexpterm {
parent.(StringLiteral).flow() instanceof RegExpPatternSource
)
}
/**
* Gets the single string this regular-expression term matches.
*
* This predicate is only defined for (sequences/groups of) constant regular expressions.
* In particular, terms involving zero-width assertions like `^` or `\b` are not considered
* to have a constant value.
*
* Note that this predicate does not take flags of the enclosing regular-expression literal
* into account.
*/
string getConstantValue() { none() }
/**
* Gets a string that is matched by this regular-expression term.
*/
string getAMatchedString() { result = getConstantValue() }
}
/**
@@ -223,6 +240,8 @@ class RegExpConstant extends RegExpTerm, @regexp_constant {
predicate isCharacter() { any() }
override predicate isNullable() { none() }
override string getConstantValue() { result = getValue() }
}
/**
@@ -266,6 +285,8 @@ class RegExpAlt extends RegExpTerm, @regexp_alt {
int getNumAlternative() { result = getNumChild() }
override predicate isNullable() { getAlternative().isNullable() }
override string getAMatchedString() { result = getAlternative().getAMatchedString() }
}
/**
@@ -289,6 +310,21 @@ class RegExpSequence extends RegExpTerm, @regexp_seq {
override predicate isNullable() {
forall(RegExpTerm child | child = getAChild() | child.isNullable())
}
override string getConstantValue() {
result = getConstantValue(0)
}
/**
* Gets the single string matched by the `i`th child and all following children of
* this sequence, if any.
*/
private string getConstantValue(int i) {
i = getNumChild() and
result = ""
or
result = getChild(i).getConstantValue() + getConstantValue(i+1)
}
}
/**
@@ -549,6 +585,10 @@ class RegExpGroup extends RegExpTerm, @regexp_group {
string getName() { isNamedCapture(this, result) }
override predicate isNullable() { getAChild().isNullable() }
override string getConstantValue() { result = getAChild().getConstantValue() }
override string getAMatchedString() { result = getAChild().getAMatchedString() }
}
/**
@@ -734,6 +774,10 @@ class RegExpCharacterClass extends RegExpTerm, @regexp_char_class {
predicate isInverted() { isInverted(this) }
override predicate isNullable() { none() }
override string getAMatchedString() {
not isInverted() and result = getAChild().getAMatchedString()
}
}
/**

View File

@@ -263,3 +263,62 @@ private class IteratorExceptionStep extends DataFlow::MethodCallNode, DataFlow::
succ = this.getExceptionalReturn()
}
}
/**
* A call to `String.prototype.replace`.
*
* We heuristically include any call to a method called `replace`, provided it either
* has exactly two arguments, or local data flow suggests that the receiver may be a string.
*/
class StringReplaceCall extends DataFlow::MethodCallNode {
StringReplaceCall() {
getMethodName() = "replace" and
(getNumArgument() = 2 or getReceiver().mayHaveStringValue(_))
}
/** Gets the regular expression passed as the first argument to `replace`, if any. */
DataFlow::RegExpLiteralNode getRegExp() {
result.flowsTo(getArgument(0))
}
/** Gets a string that is being replaced by this call. */
string getAReplacedString() {
result = getRegExp().getRoot().getAMatchedString() or
getArgument(0).mayHaveStringValue(result)
}
/**
* Gets the second argument of this call to `replace`, which is either a string
* or a callback.
*/
DataFlow::Node getRawReplacement() {
result = getArgument(1)
}
/**
* Holds if this is a global replacement, that is, the first argument is a regular expression
* with the `g` flag.
*/
predicate isGlobal() {
getRegExp().isGlobal()
}
/**
* Holds if this call to `replace` replaces `old` with `new`.
*/
predicate replaces(string old, string new) {
exists(string rawNew |
old = getAReplacedString() and
getRawReplacement().mayHaveStringValue(rawNew) and
new = rawNew.replaceAll("$&", old)
)
or
exists(DataFlow::FunctionNode replacer, DataFlow::PropRead pr, DataFlow::ObjectLiteralNode map |
replacer = getCallback(1) and
replacer.getParameter(0).flowsToExpr(pr.getPropertyNameExpr()) and
pr = map.getAPropertyRead() and
pr.flowsTo(replacer.getAReturn()) and
map.hasPropertyWrite(old, any(DataFlow::Node repl | repl.getStringValue() = new))
)
}
}

View File

@@ -1816,7 +1816,7 @@ class Type extends @type {
*
* For example, for a type `(S & T) | U` this gets the types `S`, `T`, and `U`.
*/
Type unfold() {
Type unfoldUnionAndIntersection() {
not result instanceof UnionOrIntersectionType and
(
result = this
@@ -1829,6 +1829,27 @@ class Type extends @type {
)
}
/**
* Repeatedly unfolds unions, intersections, and type aliases and gets any of the underlying types,
* or this type itself if it is not a union or intersection.
*
* For example, the type `(S & T) | U` unfolds to `S`, `T`, and `U`.
*
* If this is a type alias, the alias is itself included in the result, but this is not the case for intermediate type aliases.
* For example:
* ```js
* type One = number | string;
* type Two = One | Function & {x: string};
* One; // unfolds to number, string, and One
* Two; // unfolds to number, string, One, Function, {x: string}, and Two
* ```
*/
Type unfold() {
result = unfoldUnionAndIntersection()
or
result = this.(TypeAliasReference).getAliasedType().unfoldUnionAndIntersection()
}
/**
* Holds if this refers to the given named type, or is declared as a subtype thereof,
* or is a union or intersection containing such a type.
@@ -2287,6 +2308,24 @@ class EnumLiteralType extends TypeReference {
EnumMember getEnumMember() { result = declaration }
}
/**
* A type that refers to a type alias.
*/
class TypeAliasReference extends TypeReference {
TypeAliasReference() {
type_alias(this, _)
}
/**
* Gets the type behind the type alias.
*
* For example, for `type B<T> = T[][]`, this maps the type `B<number>` to `number[][]`.
*/
Type getAliasedType() {
type_alias(this, result)
}
}
/**
* An anonymous interface type, such as `{ x: number }`.
*/
@@ -2516,17 +2555,19 @@ class CallSignatureType extends @signature_type {
predicate hasTypeParameters() { getNumTypeParameter() > 0 }
/**
* Gets the type of the `n`th parameter of this signature.
* Gets the type of the `n`th parameter declared in this signature.
*
* If the `n`th parameter is a rest parameter `...T[]`, gets type `T`.
*/
Type getParameter(int n) { n >= 0 and result = getChild(n + getNumTypeParameter()) }
/**
* Gets the type of a parameter of this signature.
* Gets the type of a parameter of this signature, including the rest parameter, if any.
*/
Type getAParameter() { result = getParameter(_) }
/**
* Gets the number of parameters.
* Gets the number of parameters, including the rest parameter, if any.
*/
int getNumParameter() { result = count(int i | exists(getParameter(i))) }
@@ -2538,7 +2579,7 @@ class CallSignatureType extends @signature_type {
/**
* Gets the number of optional parameters, that is,
* parameters that are marked as optional with the `?` suffix.
* parameters that are marked as optional with the `?` suffix or is a rest parameter.
*/
int getNumOptionalParameter() { result = getNumParameter() - getNumRequiredParameter() }
@@ -2552,7 +2593,9 @@ class CallSignatureType extends @signature_type {
}
/**
* Holds if the `n`th parameter is declared optional with the `?` suffix.
* Holds if the `n`th parameter is declared optional with the `?` suffix or is the rest parameter.
*
* Note that rest parameters are not considered optional in this sense.
*/
predicate isOptionalParameter(int n) {
exists(getParameter(n)) and
@@ -2571,6 +2614,30 @@ class CallSignatureType extends @signature_type {
* Gets the name of a parameter of this signature.
*/
string getAParameterName() { result = getParameterName(_) }
/**
* Holds if this signature declares a rest parameter, such as `(x: number, ...y: string[])`.
*/
predicate hasRestParameter() { signature_rest_parameter(this, _) }
/**
* Gets the type of the rest parameter, if any.
*
* For example, for the signature `(...y: string[])`, this gets the type `string`.
*/
Type getRestParameterType() {
hasRestParameter() and
result = getParameter(getNumParameter() - 1)
}
/**
* Gets the type of the rest parameter as an array, if it exists.
*
* For example, for the signature `(...y: string[])`, this gets the type `string[]`.
*/
PlainArrayType getRestParameterArrayType() {
signature_rest_parameter(this, result)
}
}
/**

View File

@@ -749,6 +749,21 @@ class Parameter extends BindingPattern {
JSDocTag getJSDocTag() {
none() // overridden in SimpleParameter
}
/**
* Holds if this is a parameter declared optional with the `?` token.
*
* Note that this does not hold for rest parameters, and does not in general
* hold for parameters with defaults.
*
* For example, `x`, is declared optional below:
* ```
* function f(x?: number) {}
* ```
*/
predicate isDeclaredOptional() {
isOptionalParameterDeclaration(this)
}
}
/**

View File

@@ -530,6 +530,24 @@ class ArrayLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
int getSize() { result = astNode.getSize() }
}
/**
* A data-flow node corresponding to a regular-expression literal.
*
* Examples:
* ```js
* /https?/i
* ```
*/
class RegExpLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
override RegExpLiteral astNode;
/** Holds if this regular expression has a `g` flag. */
predicate isGlobal() { astNode.isGlobal() }
/** Gets the root term of this regular expression. */
RegExpTerm getRoot() { result = astNode.getRoot() }
}
/**
* A data flow node corresponding to a `new Array()` or `Array()` invocation.
*

View File

@@ -659,6 +659,20 @@ module TaintTracking {
}
}
/**
* A taint step through the Node.JS function `util.inspect(..)`.
*/
class UtilInspectTaintStep extends AdditionalTaintStep, DataFlow::InvokeNode {
UtilInspectTaintStep() {
this = DataFlow::moduleImport("util").getAMemberCall("inspect")
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
succ = this and
this.getAnArgument() = pred
}
}
/**
* A conditional checking a tainted string against a regular expression, which is
* considered to be a sanitizer for all configurations.

View File

@@ -61,6 +61,15 @@ private module Console {
if name = "assert"
then result = getArgument([1 .. getNumArgument()])
else result = getAnArgument()
or
result = getASpreadArgument()
}
/**
* Gets the name of the console logging method, e.g. "log", "error", "assert", etc.
*/
string getName() {
result = name
}
}
}

View File

@@ -136,7 +136,12 @@ abstract class ReactComponent extends ASTNode {
result = arg0
)
or
result.flowsToExpr(getStaticMethod("getDerivedStateFromProps").getAReturnedExpr())
exists(string staticMember |
staticMember = "getDerivedStateFromProps" or
staticMember = "getDerivedStateFromError"
|
result.flowsToExpr(getStaticMethod(staticMember).getAReturnedExpr())
)
or
// shouldComponentUpdate: (nextProps, nextState)
result = DataFlow::parameterNode(getInstanceMethod("shouldComponentUpdate").getParameter(1))

View File

@@ -13,7 +13,7 @@ module CleartextLogging {
import CleartextLoggingCustomizations::CleartextLogging
/**
* A dataflow tracking configuration for clear-text logging of sensitive information.
* A taint tracking configuration for clear-text logging of sensitive information.
*
* This configuration identifies flows from `Source`s, which are sources of
* sensitive data, to `Sink`s, which is an abstract class representing all
@@ -21,27 +21,37 @@ module CleartextLogging {
* added either by extending the relevant class, or by subclassing this configuration itself,
* and amending the sources and sinks.
*/
class Configuration extends DataFlow::Configuration {
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "CleartextLogging" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel lbl) {
source.(Source).getLabel() = lbl
}
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
sink.(Sink).getLabel() = lbl
}
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Barrier }
override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg) {
StringConcatenation::taintStep(src, trg)
or
exists(string name | name = "toString" or name = "valueOf" |
src.(DataFlow::SourceNode).getAMethodCall(name) = trg
)
or
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
succ.(DataFlow::PropRead).getBase() = pred
}
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
// A taint propagating data flow edge through objects: a tainted write taints the entire object.
exists(DataFlow::PropWrite write |
write.getRhs() = src and
trg.(DataFlow::SourceNode).flowsTo(write.getBase())
)
or
// Taint through the arguments object.
exists(DataFlow::CallNode call, Function f |
src = call.getAnArgument() and
f = call.getACallee() and
not call.isImprecise() and
trg.asExpr() = f.getArgumentsVariable().getAnAccess()
)
}
}
}

View File

@@ -15,17 +15,39 @@ module CleartextLogging {
abstract class Source extends DataFlow::Node {
/** Gets a string that describes the type of this data flow source. */
abstract string describe();
abstract DataFlow::FlowLabel getLabel();
}
/**
* A data flow sink for clear-text logging of sensitive information.
*/
abstract class Sink extends DataFlow::Node { }
abstract class Sink extends DataFlow::Node {
DataFlow::FlowLabel getLabel() {
result.isDataOrTaint()
}
}
/**
* A barrier for clear-text logging of sensitive information.
*/
abstract class Barrier extends DataFlow::Node { }
/**
* A call to `.replace()` that seems to mask sensitive information.
*/
class MaskingReplacer extends Barrier, DataFlow::MethodCallNode {
MaskingReplacer() {
this.getCalleeName() = "replace" and
exists(RegExpLiteral reg |
reg = this.getArgument(0).getALocalSource().asExpr() and
reg.isGlobal() and
any(RegExpDot term).getLiteral() = reg
)
and
exists(this.getArgument(1).getStringValue())
}
}
/**
* An argument to a logging mechanism.
@@ -107,6 +129,10 @@ module CleartextLogging {
}
override string describe() { result = "an access to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** An access to a variable or property that might contain a password. */
@@ -131,6 +157,10 @@ module CleartextLogging {
}
override string describe() { result = "an access to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** A call that might return a password. */
@@ -143,5 +173,33 @@ module CleartextLogging {
}
override string describe() { result = "a call to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** An access to the sensitive object `process.env`. */
class ProcessEnvSource extends Source {
ProcessEnvSource() {
this = NodeJSLib::process().getAPropertyRead("env")
}
override string describe() { result = "process environment" }
override DataFlow::FlowLabel getLabel() {
result.isData() or
result instanceof PartiallySensitiveMap
}
}
/**
* A flow label describing a map that might contain sensitive information in some properties.
* Property reads on such maps where the property name is fixed is unlikely to leak sensitive information.
*/
class PartiallySensitiveMap extends DataFlow::FlowLabel {
PartiallySensitiveMap() {
this = "PartiallySensitiveMap"
}
}
}