Merge branch 'master' into taintedmalloc

This commit is contained in:
Jonas Jensen
2019-03-29 09:12:38 +01:00
committed by GitHub
56 changed files with 1201 additions and 739 deletions

View File

@@ -13,6 +13,9 @@
|----------------------------|------------------------|------------------------------------------------------------------|
| Mismatching new/free or malloc/delete (`cpp/new-free-mismatch`) | Fewer false positive results | Fixed an issue where functions were being identified as allocation functions inappropriately. Also affects `cpp/new-array-delete-mismatch` and `cpp/new-delete-array-mismatch`. |
| Overflow in uncontrolled allocation size (`cpp/uncontrolled-allocation-size`) | More correct results | This query has been reworked so that it can find a wider variety of results. |
| Memory may not be freed (`cpp/memory-may-not-be-freed`) | More correct results | Support added for more Microsoft-specific allocation functions, including `LocalAlloc`, `GlobalAlloc`, `HeapAlloc` and `CoTaskMemAlloc`. |
| Memory is never freed (`cpp/memory-never-freed`) | More correct results | Support added for more Microsoft-specific allocation functions, including `LocalAlloc`, `GlobalAlloc`, `HeapAlloc` and `CoTaskMemAlloc`. |
| Resource not released in destructor (`cpp/resource-not-released-in-destructor`) | Fewer false positive results | Resource allocation and deallocation functions are now determined more accurately. |
| Comparison result is always the same | Fewer false positive results | The range analysis library is now more conservative about floating point values being possibly `NaN` |
## Changes to QL libraries

View File

@@ -0,0 +1,17 @@
# Improvements to C# analysis
## Changes to existing queries
| **Query** | **Expected impact** | **Change** |
|------------------------------|------------------------|-----------------------------------|
## Changes to code extraction
* Named attribute arguments are now extracted.
## Changes to QL libraries
* The class `Attribute` has two new predicates: `getConstructorArgument()` and `getNamedArgument()`. The first predicate returns arguments to the underlying constructor call and the latter returns named arguments for initializing fields and properties.
## Changes to autobuilder

View File

@@ -18,7 +18,9 @@
| **Query** | **Expected impact** | **Change** |
|--------------------------------|------------------------------|---------------------------------------------------------------------------|
| Expression has no effect | Fewer false-positive results | This rule now treats uses of `Object.defineProperty` more conservatively. |
| Useless assignment to property | Fewer false-positive results | This rule now ignore reads of additional getters. |
| Useless assignment to property | Fewer false-positive results | This rule now ignores reads of additional getters. |
| Arbitrary file write during zip extraction ("Zip Slip") | More results | This rule now considers more libraries, including tar as well as zip. |
| Client-side URL redirect | Fewer false-positive results | This rule now treats URLs as safe in more cases where the hostname cannot be tampered with. |
| Server-side URL redirect | Fewer false-positive results | This rule now treats URLs as safe in more cases where the hostname cannot be tampered with. |
## Changes to QL libraries

View File

@@ -12,6 +12,7 @@
* readability
*/
import cpp
private import semmle.code.cpp.commons.Exclusions
private import semmle.code.cpp.rangeanalysis.PointlessComparison
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
import UnsignedGEZero
@@ -31,6 +32,7 @@ from
where
not cmp.isInMacroExpansion() and
not cmp.isFromTemplateInstantiation(_) and
not functionContainsDisabledCode(cmp.getEnclosingFunction()) and
reachablePointlessComparison(cmp, left, right, value, ss) and
// a comparison between an enum and zero is always valid because whether

View File

@@ -11,6 +11,7 @@
* external/cwe/cwe-561
*/
import cpp
private import semmle.code.cpp.commons.Exclusions
class PureExprInVoidContext extends ExprInVoidContext {
PureExprInVoidContext() { this.isPure() }
@@ -23,71 +24,29 @@ predicate accessInInitOfForStmt(Expr e) {
s.getExpr() = e)
}
/**
* Holds if the preprocessor branch `pbd` is on line `pbdStartLine` in file `file`.
*/
predicate pbdLocation(PreprocessorBranchDirective pbd, string file, int pbdStartLine) {
pbd.getLocation().hasLocationInfo(file, pbdStartLine, _, _, _)
}
/**
* Holds if the body of the function `f` is on lines `fBlockStartLine` to `fBlockEndLine` in file `file`.
*/
predicate functionLocation(Function f, string file, int fBlockStartLine, int fBlockEndLine) {
f.getBlock().getLocation().hasLocationInfo(file, fBlockStartLine, _, fBlockEndLine, _)
}
/**
* Holds if the function `f`, or a function called by it, contains
* code excluded by the preprocessor.
*/
predicate containsDisabledCode(Function f) {
// `f` contains a preprocessor branch that was not taken
exists(PreprocessorBranchDirective pbd, string file, int pbdStartLine, int fBlockStartLine, int fBlockEndLine |
functionLocation(f, file, fBlockStartLine, fBlockEndLine) and
pbdLocation(pbd, file, pbdStartLine) and
pbdStartLine <= fBlockEndLine and
pbdStartLine >= fBlockStartLine and
(
pbd.(PreprocessorBranch).wasNotTaken() or
// an else either was not taken, or it's corresponding branch
// was not taken.
pbd instanceof PreprocessorElse
)
) or
predicate functionContainsDisabledCodeRecursive(Function f) {
functionContainsDisabledCode(f) or
// recurse into function calls
exists(FunctionCall fc |
fc.getEnclosingFunction() = f and
containsDisabledCode(fc.getTarget())
functionContainsDisabledCodeRecursive(fc.getTarget())
)
}
/**
* Holds if the function `f`, or a function called by it, is inside a
* preprocessor branch that may have code in another arm
*/
predicate definedInIfDef(Function f) {
exists(PreprocessorBranchDirective pbd, string file, int pbdStartLine, int pbdEndLine, int fBlockStartLine, int fBlockEndLine |
functionLocation(f, file, fBlockStartLine, fBlockEndLine) and
pbdLocation(pbd, file, pbdStartLine) and
pbdLocation(pbd.getNext(), file, pbdEndLine) and
pbdStartLine <= fBlockStartLine and
pbdEndLine >= fBlockEndLine and
// pbd is a preprocessor branch where multiple branches exist
(
pbd.getNext() instanceof PreprocessorElse or
pbd instanceof PreprocessorElse or
pbd.getNext() instanceof PreprocessorElif or
pbd instanceof PreprocessorElif
)
) or
predicate functionDefinedInIfDefRecursive(Function f) {
functionDefinedInIfDef(f) or
// recurse into function calls
exists(FunctionCall fc |
fc.getEnclosingFunction() = f and
definedInIfDef(fc.getTarget())
functionDefinedInIfDefRecursive(fc.getTarget())
)
}
@@ -121,8 +80,8 @@ where // EQExprs are covered by CompareWhereAssignMeant.ql
not parent instanceof PureExprInVoidContext and
not peivc.getEnclosingFunction().isCompilerGenerated() and
not peivc.getType() instanceof UnknownType and
not containsDisabledCode(peivc.(FunctionCall).getTarget()) and
not definedInIfDef(peivc.(FunctionCall).getTarget()) and
not functionContainsDisabledCodeRecursive(peivc.(FunctionCall).getTarget()) and
not functionDefinedInIfDefRecursive(peivc.(FunctionCall).getTarget()) and
if peivc instanceof FunctionCall then
exists(Function target |
target = peivc.(FunctionCall).getTarget() and

View File

@@ -0,0 +1,42 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p> The <code>alloca</code> macro allocates memory by expanding the current stack frame. Invoking
<code>alloca</code> within a loop may lead to a stack overflow because the memory is not released
until the function returns.
</p>
</overview>
<recommendation>
<p>
Consider invoking <code>alloca</code> once outside the loop, or using <code>malloc</code> or
<code>new</code> to allocate memory on the heap if the allocation must be done inside the loop.
</p>
</recommendation>
<example>
<p>The variable <code>path</code> is allocated inside a loop with <code>alloca</code>. Consequently,
storage for all copies of the path is present in the stack frame until the end of the function.
</p>
<sample src="AllocaInLoopBad.cpp" />
<p>In the revised example, <code>path</code> is allocated with <code>malloc</code> and freed at the
end of the loop.
</p>
<sample src="AllocaInLoopGood.cpp" />
</example>
<references>
<li>Linux Programmer's Manual: <a href="http://man7.org/linux/man-pages/man3/alloca.3.html">ALLOCA(3)</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,8 @@
char *dir_path;
char **dir_entries;
int count;
for (int i = 0; i < count; i++) {
char *path = (char*)alloca(strlen(dir_path) + strlen(dir_entry[i]) + 2);
// use path
}

View File

@@ -0,0 +1,9 @@
char *dir_path;
char **dir_entries;
int count;
for (int i = 0; i < count; i++) {
char *path = (char*)malloc(strlen(dir_path) + strlen(dir_entry[i]) + 2);
// use path
free(path);
}

View File

@@ -68,6 +68,9 @@ some are after the final <code>#endif</code>. All three of these things must be
<li>
<a href="http://www.cplusplus.com/forum/articles/10627/">Headers and Includes: Why and How</a>
</li>
<li>
<a href="https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html">The Multiple-Include Optimization</a>
</li>
</references>

View File

@@ -39,7 +39,16 @@ predicate allocationFunction(Function f)
name = "MmAllocateNodePagesForMdlEx" or
name = "MmMapLockedPagesWithReservedMapping" or
name = "MmMapLockedPages" or
name = "MmMapLockedPagesSpecifyCache"
name = "MmMapLockedPagesSpecifyCache" or
name = "LocalAlloc" or
name = "LocalReAlloc" or
name = "GlobalAlloc" or
name = "GlobalReAlloc" or
name = "HeapAlloc" or
name = "HeapReAlloc" or
name = "VirtualAlloc" or
name = "CoTaskMemAlloc" or
name = "CoTaskMemRealloc"
)
)
}
@@ -81,7 +90,17 @@ predicate freeFunction(Function f, int argNum)
(name = "MmFreeMappingAddress" and argNum = 0) or
(name = "MmFreePagesFromMdl" and argNum = 0) or
(name = "MmUnmapReservedMapping" and argNum = 0) or
(name = "MmUnmapLockedPages" and argNum = 0)
(name = "MmUnmapLockedPages" and argNum = 0) or
(name = "LocalFree" and argNum = 0) or
(name = "GlobalFree" and argNum = 0) or
(name = "HeapFree" and argNum = 2) or
(name = "VirtualFree" and argNum = 0) or
(name = "CoTaskMemFree" and argNum = 0) or
(name = "SysFreeString" and argNum = 0) or
(name = "LocalReAlloc" and argNum = 0) or
(name = "GlobalReAlloc" and argNum = 0) or
(name = "HeapReAlloc" and argNum = 2) or
(name = "CoTaskMemRealloc" and argNum = 0)
)
)
}

View File

@@ -0,0 +1,60 @@
/**
* Common predicates used to exclude results from a query based on heuristics.
*/
import cpp
/**
* Holds if the preprocessor branch `pbd` is on line `pbdStartLine` in file `file`.
*/
private predicate pbdLocation(PreprocessorBranchDirective pbd, string file, int pbdStartLine) {
pbd.getLocation().hasLocationInfo(file, pbdStartLine, _, _, _)
}
/**
* Holds if the body of the function `f` is on lines `fBlockStartLine` to `fBlockEndLine` in file `file`.
*/
private predicate functionLocation(Function f, string file, int fBlockStartLine, int fBlockEndLine) {
f.getBlock().getLocation().hasLocationInfo(file, fBlockStartLine, _, fBlockEndLine, _)
}
/**
* Holds if the function `f` is inside a preprocessor branch that may have code in another arm.
*/
predicate functionDefinedInIfDef(Function f) {
exists(PreprocessorBranchDirective pbd, string file, int pbdStartLine, int pbdEndLine, int fBlockStartLine,
int fBlockEndLine |
functionLocation(f, file, fBlockStartLine, fBlockEndLine) and
pbdLocation(pbd, file, pbdStartLine) and
pbdLocation(pbd.getNext(), file, pbdEndLine) and
pbdStartLine <= fBlockStartLine and
pbdEndLine >= fBlockEndLine and
// pbd is a preprocessor branch where multiple branches exist
(
pbd.getNext() instanceof PreprocessorElse or
pbd instanceof PreprocessorElse or
pbd.getNext() instanceof PreprocessorElif or
pbd instanceof PreprocessorElif
)
)
}
/**
* Holds if the function `f` contains code excluded by the preprocessor.
*/
predicate functionContainsDisabledCode(Function f) {
// `f` contains a preprocessor branch that was not taken
exists(PreprocessorBranchDirective pbd, string file, int pbdStartLine, int fBlockStartLine, int fBlockEndLine |
functionLocation(f, file, fBlockStartLine, fBlockEndLine) and
pbdLocation(pbd, file, pbdStartLine) and
pbdStartLine <= fBlockEndLine and
pbdStartLine >= fBlockStartLine and
(
pbd.(PreprocessorBranch).wasNotTaken() or
// an else either was not taken, or it's corresponding branch
// was not taken.
pbd instanceof PreprocessorElse
)
)
}

View File

@@ -0,0 +1,41 @@
import cpp
private import semmle.code.cpp.rangeanalysis.RangeSSA
/**
* Holds if `guard` won't return the value `polarity` when either
* operand is NaN.
*/
predicate nanExcludingComparison(ComparisonOperation guard, boolean polarity) {
polarity = true and
(
guard instanceof LTExpr or
guard instanceof LEExpr or
guard instanceof GTExpr or
guard instanceof GEExpr or
guard instanceof EQExpr
)
or
polarity = false and
guard instanceof NEExpr
}
/**
* Holds if `v` is a use of an SSA definition in `def` which cannot be NaN,
* by virtue of the guard in `def`.
*/
private predicate excludesNan(RangeSsaDefinition def, VariableAccess v) {
exists(VariableAccess inCond, ComparisonOperation guard, boolean branch, LocalScopeVariable lsv |
def.isGuardPhi(inCond, guard, branch) and
inCond.getTarget() = lsv and
v = def.getAUse(lsv) and
guard.getAnOperand() = inCond and
nanExcludingComparison(guard, branch)
)
}
/**
* A variable access which cannot be NaN.
*/
class NonNanVariableAccess extends VariableAccess {
NonNanVariableAccess() { excludesNan(_, this) }
}

View File

@@ -45,6 +45,7 @@ import cpp
private import RangeAnalysisUtils
import RangeSSA
import SimpleRangeAnalysisCached
private import NanAnalysis
/**
* This fixed set of lower bounds is used when the lower bounds of an
@@ -993,6 +994,25 @@ predicate unanalyzableDefBounds(
ub = varMaxVal(v)
}
/**
* Holds if in the `branch` branch of a guard `guard` involving `v`,
* we know that `v` is not NaN, and therefore it is safe to make range
* inferences about `v`.
*/
bindingset[guard, v, branch]
predicate nonNanGuardedVariable(ComparisonOperation guard, VariableAccess v, boolean branch) {
v.getType().getUnspecifiedType() instanceof IntegralType
or
v.getType().getUnspecifiedType() instanceof FloatingPointType and v instanceof NonNanVariableAccess
or
// The reason the following case is here is to ensure that when we say
// `if (x > 5) { ...then... } else { ...else... }`
// it is ok to conclude that `x > 5` in the `then`, (though not safe
// to conclude that x <= 5 in `else`) even if we had no prior
// knowledge of `x` not being `NaN`.
nanExcludingComparison(guard, branch)
}
/**
* If the guard is a comparison of the form `p*v + q <CMP> r`, then this
* predicate uses the bounds information for `r` to compute a lower bound
@@ -1004,10 +1024,12 @@ predicate lowerBoundFromGuard(
) {
exists (float childLB, RelationStrictness strictness
| boundFromGuard(guard, v, childLB, true, strictness, branch)
| if (strictness = Nonstrict() or
| if nonNanGuardedVariable(guard, v, branch)
then (if (strictness = Nonstrict() or
not (v.getType().getUnspecifiedType() instanceof IntegralType))
then lb = childLB
else lb = childLB+1)
else lb = varMinVal(v.getTarget()))
}
/**
@@ -1021,10 +1043,12 @@ predicate upperBoundFromGuard(
) {
exists (float childUB, RelationStrictness strictness
| boundFromGuard(guard, v, childUB, false, strictness, branch)
| if (strictness = Nonstrict() or
| if nonNanGuardedVariable(guard, v, branch)
then (if (strictness = Nonstrict() or
not (v.getType().getUnspecifiedType() instanceof IntegralType))
then ub = childUB
else ub = childUB-1)
else ub = varMaxVal(v.getTarget()))
}
/**

View File

@@ -265,3 +265,30 @@ int negative_zero(double dbl) {
}
return 0;
}
int nan(double x) {
if (x < 0.0) {
return 100;
}
else if (x >= 0.0) { // GOOD [x could be NaN]
return 200;
}
else {
return 300;
}
}
int nan2(double x) {
if (x == x) {
// If x compares with anything at all, it's not NaN
if (x < 0.0) {
return 100;
}
else if (x >= 0.0) { // BAD [Always true]
return 200;
}
else {
return 300;
}
}
}

View File

@@ -32,5 +32,6 @@
| PointlessComparison.c:129:12:129:16 | ... > ... | Comparison is always false because a <= 3. |
| PointlessComparison.c:197:7:197:11 | ... < ... | Comparison is always false because x >= 0. |
| PointlessComparison.c:264:12:264:22 | ... >= ... | Comparison is always true because dbl >= 0 and -0 >= - .... |
| PointlessComparison.c:287:14:287:21 | ... >= ... | Comparison is always true because x >= 0. |
| RegressionTests.cpp:57:7:57:22 | ... <= ... | Comparison is always true because * ... <= 4294967295. |
| Templates.cpp:9:10:9:24 | ... <= ... | Comparison is always true because local <= 32767. |

View File

@@ -66,3 +66,17 @@ int regression_test_01(unsigned long bb) {
return 1;
}
}
int containsIfDef(int x) {
int result = 0;
if (x > 0) {
result = 1;
}
#if _CONDITION
if (x < 0) {
result = -1;
}
#endif
return result >= 0;
}

View File

@@ -51,9 +51,12 @@ namespace Semmle.Extraction.CSharp.Entities
int child = 0;
foreach (var arg in syntax.ArgumentList.Arguments)
{
Expression.Create(cx, arg.Expression, this, child++);
var expr = Expression.Create(cx, arg.Expression, this, child++);
if (!(arg.NameEquals is null))
{
cx.Emit(Tuples.expr_argument_name(expr, arg.NameEquals.Name.Identifier.Text));
}
}
// !! Handle named arguments
});
}
}
@@ -66,14 +69,6 @@ namespace Semmle.Extraction.CSharp.Entities
}
}
public static void ExtractAttributes(Context cx, IEnumerable<AttributeListSyntax> attributes, IEntity entity)
{
foreach (var attributeSyntax in attributes.SelectMany(l => l.Attributes))
{
new Attribute(cx, attributeSyntax, entity);
}
}
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
}
}

View File

@@ -28,7 +28,10 @@ class Attributable extends @attributable {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.(Element).getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
this
.(Element)
.getLocation()
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
@@ -51,9 +54,37 @@ class Attribute extends TopLevelExprParent, @attribute {
/** Gets the element that this attribute is attached to. */
Attributable getTarget() { attributes(this, _, result) }
/** Gets the `i`th argument of this attribute. */
/**
* Gets the `i`th argument of this attribute. This includes both constructor
* arguments and named arguments.
*/
Expr getArgument(int i) { result = this.getChildExpr(i) }
/**
* Gets the `i`th constructor argument of this attribute. For example, only
* `true` is a constructor argument in
*
* ```
* MyAttribute[true, Foo = 0]
* ```
*/
Expr getConstructorArgument(int i) {
result = this.getArgument(i) and not exists(result.getExplicitArgumentName())
}
/**
* Gets the named argument `name` of this attribute. For example, only
* `0` is a named argument in
*
* ```
* MyAttribute[true, Foo = 0]
* ```
*/
Expr getNamedArgument(string name) {
result = this.getArgument(_) and
result.getExplicitArgumentName() = name
}
override Location getALocation() { attribute_location(this, result) }
override string toString() {

View File

@@ -202,7 +202,7 @@ private module Internal {
* Gets a non-exact (see `hasQualifierType()`) qualifier type of this call
* that does not contain type parameters.
*/
TypeWithoutTypeParameters getANonExactQualifierTypeWithoutTypeParameters() {
private TypeWithoutTypeParameters getANonExactQualifierTypeWithoutTypeParameters() {
exists(Type qualifierType | hasQualifierType(qualifierType, false) |
// Qualifier type contains no type parameters: use it
result = qualifierType
@@ -220,6 +220,20 @@ private module Internal {
result = qualifierType.(QualifierTypeWithTypeParameters).getAPotentialInstance()
)
}
/**
* Gets a non-exact (see `hasQualifierType()`) qualifier type of this call.
*/
ValueOrRefType getANonExactQualifierType() {
exists(TypeWithoutTypeParameters t |
t = this.getANonExactQualifierTypeWithoutTypeParameters()
|
result.(ConstructedType).getUnboundGeneric() = t
or
not t instanceof UnboundGenericType and
result = t
)
}
}
private class DynamicFieldOrProperty extends Assignable {
@@ -480,9 +494,9 @@ private module Internal {
* qualifier types are `B.M` and `C.M`, `C.M`, and none, respectively.
*/
private RuntimeInstanceMethod getAViableOverrider() {
exists(TypeWithoutTypeParameters t, NonConstructedOverridableMethod m |
t = getANonExactQualifierTypeWithoutTypeParameters() and
getAStaticTarget() = m.getAConstructingMethodOrSelf() and
exists(ValueOrRefType t, NonConstructedOverridableMethod m |
t = this.getANonExactQualifierType() and
this.getAStaticTarget() = m.getAConstructingMethodOrSelf() and
result = m.getAnOverrider(t)
)
}
@@ -620,9 +634,9 @@ private module Internal {
* respectively.
*/
private RuntimeAccessor getAViableOverrider() {
exists(TypeWithoutTypeParameters t |
t = getANonExactQualifierTypeWithoutTypeParameters() and
result = getAStaticTarget().(OverridableAccessor).getAnOverrider(t)
exists(ValueOrRefType t |
t = this.getANonExactQualifierType() |
result = this.getAStaticTarget().(OverridableAccessor).getAnOverrider(t)
)
}
}

View File

@@ -135,27 +135,33 @@ class OverridableCallable extends Callable {
* - `C2.M = C2.M.getInherited(C3)`.
*/
Callable getInherited(SourceDeclarationType t) {
exists(Callable sourceDecl | result = getInherited1(t, sourceDecl) |
exists(Callable sourceDecl | result = this.getInherited2(t, sourceDecl) |
hasSourceDeclarationCallable(t, sourceDecl)
)
}
private Callable getInherited0(SourceDeclarationType t) {
private Callable getInherited0(ValueOrRefType t) {
// A (transitive, reflexive) overrider
t = this.hasOverrider(result).getASubType*().getSourceDeclaration()
t = this.hasOverrider(result)
or
// A (transitive) overrider of an interface implementation
t = this.hasOverridingImplementor(result)
or
exists(ValueOrRefType mid | result = this.getInherited0(mid) | t = mid.getASubType())
}
private Callable getInherited1(SourceDeclarationType t) {
exists(ValueOrRefType t0 | result = getInherited0(t0) | t = t0.getSourceDeclaration())
or
// An interface implementation
exists(ValueOrRefType s |
result = getAnImplementorSubType(s) and
t = s.getSourceDeclaration()
)
or
// A (transitive) overrider of an interface implementation
t = this.hasOverridingImplementor(result).getASubType*().getSourceDeclaration()
}
private Callable getInherited1(SourceDeclarationType t, Callable sourceDecl) {
result = this.getInherited0(t) and
private Callable getInherited2(SourceDeclarationType t, Callable sourceDecl) {
result = this.getInherited1(t) and
sourceDecl = result.getSourceDeclaration()
}
@@ -171,9 +177,42 @@ class OverridableCallable extends Callable {
result = c.getDeclaringType()
}
private predicate isDeclaringSubType(ValueOrRefType t) {
t = this.getDeclaringType()
or
exists(ValueOrRefType mid | isDeclaringSubType(mid) | t = mid.getASubType())
}
pragma[noinline]
private Callable getAnOverrider0(ValueOrRefType t) {
not this.declaredInTypeWithTypeParameters() and
(
// A (transitive) overrider
result = this.getAnOverrider+() and
t = result.getDeclaringType()
or
// An interface implementation
result = this.getAnImplementorSubType(t)
or
// A (transitive) overrider of an interface implementation
result = this.getAnOverridingImplementor() and
t = result.getDeclaringType()
)
}
private Callable getAnOverrider1(ValueOrRefType t) {
result = this.getAnOverrider0(t)
or
exists(ValueOrRefType mid | result = this.getAnOverrider1(mid) |
t = mid.getABaseType() and
this.isDeclaringSubType(t)
)
}
/**
* Gets a callable defined in a sub type of `t` that overrides/implements
* this callable, if any.
* Gets a callable defined in a sub type of `t` (which is itself a sub type
* of this callable's declaring type) that overrides/implements this callable,
* if any.
*
* The type `t` may be a constructed type: For example, if `t = C<int>`,
* then only callables defined in sub types of `C<int>` (and e.g. not
@@ -181,39 +220,7 @@ class OverridableCallable extends Callable {
* contains a callable that overrides this callable, then only if `C2<int>`
* is ever constructed will the callable in `C2` be considered valid.
*/
Callable getAnOverrider(TypeWithoutTypeParameters t) {
exists(OverridableCallable oc, ValueOrRefType sub |
result = oc.getAnOverriderAux(sub) and
t = oc.getAnOverriderBaseType(sub) and
oc = getABoundInstance()
)
}
// predicate folding to get proper join order
private Callable getAnOverriderAux(ValueOrRefType t) {
not declaredInTypeWithTypeParameters() and
(
// A (transitive) overrider
result = getAnOverrider+() and
t = result.getDeclaringType()
or
// An interface implementation
result = getAnImplementorSubType(t)
or
// A (transitive) overrider of an interface implementation
result = getAnOverridingImplementor() and
t = result.getDeclaringType()
)
}
private TypeWithoutTypeParameters getAnOverriderBaseType(ValueOrRefType t) {
exists(getAnOverriderAux(t)) and
exists(Type t0 | t0 = t.getABaseType*() |
result = t0
or
result = t0.(ConstructedType).getUnboundGeneric()
)
}
Callable getAnOverrider(ValueOrRefType t) { result = this.getABoundInstance().getAnOverrider1(t) }
/**
* Gets a bound instance of this callable.
@@ -264,7 +271,7 @@ class OverridableMethod extends Method, OverridableCallable {
result = OverridableCallable.super.getInherited(t)
}
override Method getAnOverrider(TypeWithoutTypeParameters t) {
override Method getAnOverrider(ValueOrRefType t) {
result = OverridableCallable.super.getAnOverrider(t)
}
}
@@ -311,7 +318,7 @@ class OverridableAccessor extends Accessor, OverridableCallable {
result = OverridableCallable.super.getInherited(t)
}
override Accessor getAnOverrider(TypeWithoutTypeParameters t) {
override Accessor getAnOverrider(ValueOrRefType t) {
result = OverridableCallable.super.getAnOverrider(t)
}
}

View File

@@ -7,3 +7,5 @@
| arguments.cs:39:27:39:27 | 0 | o |
| arguments.cs:40:18:40:35 | array creation of type Int32[] | args |
| arguments.cs:40:41:40:41 | 0 | o |
| arguments.cs:68:28:68:29 | "" | y |
| arguments.cs:68:36:68:36 | 0 | x |

View File

@@ -61,4 +61,17 @@ class ArgumentsTest
var tuple = (13, 14);
(Prop, this[15, 16]) = tuple;
}
[MyAttribute(false)]
void f6() { }
[MyAttribute(true, y = "", x = 0)]
void f7() { }
}
class MyAttribute : Attribute
{
public int x;
public string y { get; set; }
public MyAttribute(bool b) { }
}

View File

@@ -1,3 +1,4 @@
arguments
| attributes.cs:10:12:10:24 | [AssemblyTitle(...)] | 0 | attributes.cs:10:26:10:45 | "C# attributes test" |
| attributes.cs:11:12:11:30 | [AssemblyDescription(...)] | 0 | attributes.cs:11:32:11:56 | "A test of C# attributes" |
| attributes.cs:12:12:12:32 | [AssemblyConfiguration(...)] | 0 | attributes.cs:12:34:12:35 | "" |
@@ -12,3 +13,27 @@
| attributes.cs:38:12:38:30 | [AssemblyFileVersion(...)] | 0 | attributes.cs:38:32:38:40 | "1.0.0.0" |
| attributes.cs:40:2:40:22 | [AttributeUsage(...)] | 0 | attributes.cs:40:24:40:50 | access to constant All |
| attributes.cs:43:6:43:16 | [Conditional(...)] | 0 | attributes.cs:43:18:43:25 | "DEBUG2" |
| attributes.cs:51:6:51:16 | [My(...)] | 0 | attributes.cs:51:18:51:22 | false |
| attributes.cs:54:6:54:16 | [My(...)] | 0 | attributes.cs:54:18:54:21 | true |
| attributes.cs:54:6:54:16 | [My(...)] | 1 | attributes.cs:54:28:54:29 | "" |
| attributes.cs:54:6:54:16 | [My(...)] | 2 | attributes.cs:54:36:54:36 | 0 |
constructorArguments
| attributes.cs:10:12:10:24 | [AssemblyTitle(...)] | 0 | attributes.cs:10:26:10:45 | "C# attributes test" |
| attributes.cs:11:12:11:30 | [AssemblyDescription(...)] | 0 | attributes.cs:11:32:11:56 | "A test of C# attributes" |
| attributes.cs:12:12:12:32 | [AssemblyConfiguration(...)] | 0 | attributes.cs:12:34:12:35 | "" |
| attributes.cs:13:12:13:26 | [AssemblyCompany(...)] | 0 | attributes.cs:13:28:13:39 | "Semmle Plc" |
| attributes.cs:14:12:14:26 | [AssemblyProduct(...)] | 0 | attributes.cs:14:28:14:34 | "Odasa" |
| attributes.cs:15:12:15:28 | [AssemblyCopyright(...)] | 0 | attributes.cs:15:30:15:54 | "Copyright \ufffd Semmle 2018" |
| attributes.cs:16:12:16:28 | [AssemblyTrademark(...)] | 0 | attributes.cs:16:30:16:31 | "" |
| attributes.cs:17:12:17:26 | [AssemblyCulture(...)] | 0 | attributes.cs:17:28:17:29 | "" |
| attributes.cs:22:12:22:21 | [ComVisible(...)] | 0 | attributes.cs:22:23:22:27 | false |
| attributes.cs:25:12:25:15 | [Guid(...)] | 0 | attributes.cs:25:17:25:54 | "2f70fdd6-14aa-4850-b053-d547adb1f476" |
| attributes.cs:37:12:37:26 | [AssemblyVersion(...)] | 0 | attributes.cs:37:28:37:36 | "1.0.0.0" |
| attributes.cs:38:12:38:30 | [AssemblyFileVersion(...)] | 0 | attributes.cs:38:32:38:40 | "1.0.0.0" |
| attributes.cs:40:2:40:22 | [AttributeUsage(...)] | 0 | attributes.cs:40:24:40:50 | access to constant All |
| attributes.cs:43:6:43:16 | [Conditional(...)] | 0 | attributes.cs:43:18:43:25 | "DEBUG2" |
| attributes.cs:51:6:51:16 | [My(...)] | 0 | attributes.cs:51:18:51:22 | false |
| attributes.cs:54:6:54:16 | [My(...)] | 0 | attributes.cs:54:18:54:21 | true |
namedArguments
| attributes.cs:54:6:54:16 | [My(...)] | x | attributes.cs:54:36:54:36 | 0 |
| attributes.cs:54:6:54:16 | [My(...)] | y | attributes.cs:54:28:54:29 | "" |

View File

@@ -1,4 +1,13 @@
import csharp
from Attribute attribute, int index
select attribute, index, attribute.getArgument(index)
query predicate arguments(Attribute attribute, int index, Expr e) {
e = attribute.getArgument(index)
}
query predicate constructorArguments(Attribute attribute, int index, Expr e) {
e = attribute.getConstructorArgument(index)
}
query predicate namedArguments(Attribute attribute, string name, Expr e) {
e = attribute.getNamedArgument(name)
}

View File

@@ -1,6 +1,8 @@
| attributes.cs:41:7:41:9 | Foo | attributes.cs:40:2:40:22 | [AttributeUsage(...)] | System.AttributeUsageAttribute |
| attributes.cs:44:17:44:19 | foo | attributes.cs:43:6:43:16 | [Conditional(...)] | System.Diagnostics.ConditionalAttribute |
| attributes.cs:49:23:49:23 | x | attributes.cs:49:14:49:16 | [Foo(...)] | Foo |
| attributes.cs:52:10:52:11 | M1 | attributes.cs:51:6:51:16 | [My(...)] | MyAttribute |
| attributes.cs:55:10:55:11 | M2 | attributes.cs:54:6:54:16 | [My(...)] | MyAttribute |
| attributes.dll:0:0:0:0 | attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null | attributes.cs:10:12:10:24 | [AssemblyTitle(...)] | System.Reflection.AssemblyTitleAttribute |
| attributes.dll:0:0:0:0 | attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null | attributes.cs:11:12:11:30 | [AssemblyDescription(...)] | System.Reflection.AssemblyDescriptionAttribute |
| attributes.dll:0:0:0:0 | attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null | attributes.cs:12:12:12:32 | [AssemblyConfiguration(...)] | System.Reflection.AssemblyConfigurationAttribute |

View File

@@ -47,4 +47,17 @@ class Foo : Attribute
class Bar
{
int inc([Foo] int x) { return x + 1; }
[MyAttribute(false)]
void M1() { }
[MyAttribute(true, y = "", x = 0)]
void M2() { }
}
class MyAttribute : Attribute
{
public int x;
public string y { get; set; }
public MyAttribute(bool b) { }
}

View File

@@ -170,20 +170,19 @@ class AMDModuleDefinition extends CallExpr {
}
}
/** A path expression appearing in the list of dependencies of an AMD module. */
private class AMDDependencyPath extends PathExprInModule, ConstantString {
AMDDependencyPath() {
exists(AMDModuleDefinition amd | this.getParentExpr*() = amd.getDependencies().getAnElement())
/** An AMD dependency, considered as a path expression. */
private class AmdDependencyPath extends PathExprCandidate {
AmdDependencyPath() {
exists(AMDModuleDefinition amd |
this = amd.getDependencies().getAnElement() or
this = amd.getARequireCall().getAnArgument()
)
}
override string getValue() { result = this.(ConstantString).getStringValue() }
}
/** A path expression appearing in a `require` call in an AMD module. */
private class AMDRequirePath extends PathExprInModule, ConstantString {
AMDRequirePath() {
exists(AMDModuleDefinition amd | this.getParentExpr*() = amd.getARequireCall().getAnArgument())
}
/** A constant path element appearing in an AMD dependency expression. */
private class ConstantAmdDependencyPathElement extends PathExprInModule, ConstantString {
ConstantAmdDependencyPathElement() { this = any(AmdDependencyPath amd).getAPart() }
override string getValue() { result = this.(ConstantString).getStringValue() }
}

View File

@@ -64,7 +64,7 @@ module Closure {
* a top-level expression statement.
*/
private predicate isTopLevelExpr(DataFlow::Node node) {
node.getTopLevel().getAChildStmt().(ExprStmt).getExpr().flow() = node
any(TopLevel tl).getAChildStmt().(ExprStmt).getExpr().flow() = node
}
/**

View File

@@ -121,6 +121,23 @@ private predicate isGeneratedHtml(File f) {
e.getName() = "meta" and
e.getAttributeByName("name").getValue() = "generator"
)
or
20 < countStartingHtmlElements(f, _)
}
/**
* Gets an element that starts at line `l` in file `f`.
*/
private HTML::Element getAStartingElement(File f, int l) {
result.getFile() = f and result.getLocation().getStartLine() = l
}
/**
* Gets the number of HTML elements that start at line `l` in file `f`.
*/
private int countStartingHtmlElements(File f, int l) {
result = strictcount(getAStartingElement(f, l))
}
/**

View File

@@ -239,22 +239,22 @@ class Require extends CallExpr, Import {
}
}
/** A literal path expression appearing in a `require` import. */
private class LiteralRequiredPath extends PathExprInModule, ConstantString {
LiteralRequiredPath() { exists(Require req | this.getParentExpr*() = req.getArgument(0)) }
override string getValue() { result = this.getStringValue() }
}
/** A literal path expression appearing in a call to `require.resolve`. */
private class LiteralRequireResolvePath extends PathExprInModule, ConstantString {
LiteralRequireResolvePath() {
/** An argument to `require` or `require.resolve`, considered as a path expression. */
private class RequirePath extends PathExprCandidate {
RequirePath() {
this = any(Require req).getArgument(0)
or
exists(RequireVariable req, MethodCallExpr reqres |
reqres.getReceiver() = req.getAnAccess() and
reqres.getMethodName() = "resolve" and
this.getParentExpr*() = reqres.getArgument(0)
this = reqres.getArgument(0)
)
}
}
/** A constant path element appearing in a call to `require` or `require.resolve`. */
private class ConstantRequirePathElement extends PathExprInModule, ConstantString {
ConstantRequirePathElement() { this = any(RequirePath rp).getAPart() }
override string getValue() { result = this.getStringValue() }
}

View File

@@ -236,3 +236,20 @@ private class ConcatPath extends PathExpr {
result = this.(AddExpr).getAnOperand().(PathExpr).getSearchRoot(priority)
}
}
/**
* An expression that appears in a syntactic position where it may represent a path.
*
* Examples include arguments to the CommonJS `require` function or AMD dependency arguments.
*/
abstract class PathExprCandidate extends Expr {
/**
* Gets an expression that is nested inside this expression.
*
* Equivalent to `getAChildExpr*()`, but useful to enforce a better join order (in spite of
* what the optimizer thinks, there are generally far fewer `PathExprCandidate`s than
* `ConstantString`s).
*/
pragma[nomagic]
Expr getAPart() { result = this or result = getAPart().getAChildExpr() }
}

View File

@@ -162,10 +162,11 @@ class SourceNode extends DataFlow::Node {
*
* See `TypeTracker` for more details about how to use this.
*/
pragma[inline]
DataFlow::SourceNode track(TypeTracker t2, TypeTracker t) {
exists(StepSummary summary |
StepSummary::step(this, result, summary) and
t = StepSummary::append(t2, summary)
t = t2.append(summary)
)
}
@@ -176,10 +177,11 @@ class SourceNode extends DataFlow::Node {
*
* See `TypeBackTracker` for more details about how to use this.
*/
pragma[inline]
DataFlow::SourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) {
exists(StepSummary summary |
StepSummary::step(result, this, summary) and
t = StepSummary::prepend(summary, t2)
t = t2.prepend(summary)
)
}
}

View File

@@ -9,133 +9,87 @@
import javascript
private import internal.FlowSteps
/**
* A pair of booleans, indicating whether a path goes through a return and/or a call.
*
* Identical to `TPathSummary` except without flow labels.
*/
private newtype TStepSummary = MkStepSummary(boolean hasReturn, boolean hasCall) {
(hasReturn = true or hasReturn = false) and
(hasCall = true or hasCall = false)
private class PropertyName extends string {
PropertyName() { this = any(DataFlow::PropRef pr).getPropertyName() }
}
private class OptionalPropertyName extends string {
OptionalPropertyName() { this instanceof PropertyName or this = "" }
}
/**
* A description of a step on an inter-procedural data flow path.
*/
private newtype TStepSummary =
LevelStep() or
CallStep() or
ReturnStep() or
StoreStep(PropertyName prop) or
LoadStep(PropertyName prop)
/**
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
*
* Summary of the steps needed to track a value to a given dataflow node.
* A description of a step on an inter-procedural data flow path.
*/
class StepSummary extends TStepSummary {
Boolean hasReturn;
Boolean hasCall;
StepSummary() { this = MkStepSummary(hasReturn, hasCall) }
/** Indicates whether the path represented by this summary contains any return steps. */
boolean hasReturn() { result = hasReturn }
/** Indicates whether the path represented by this summary contains any call steps. */
boolean hasCall() { result = hasCall }
/**
* Gets the summary for the path obtained by appending `that` to `this`.
*
* Note that a path containing a `return` step cannot be appended to a path containing
* a `call` step in order to maintain well-formedness.
*/
StepSummary append(StepSummary that) {
exists(Boolean hasReturn2, Boolean hasCall2 |
that = MkStepSummary(hasReturn2, hasCall2)
|
result = MkStepSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2)) and
// avoid constructing invalid paths
not (hasCall = true and hasReturn2 = true)
)
}
/**
* Gets the summary for the path obtained by appending `this` to `that`.
*/
StepSummary prepend(StepSummary that) { result = that.append(this) }
/** Gets a textual representation of this path summary. */
/** Gets a textual representation of this step summary. */
string toString() {
exists(string withReturn, string withCall |
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
(if hasCall = true then withCall = "with" else withCall = "without")
|
result = "path " + withReturn + " return steps and " + withCall + " call steps"
this instanceof LevelStep and result = "level"
or
this instanceof CallStep and result = "call"
or
this instanceof ReturnStep and result = "return"
or
exists(string prop | this = StoreStep(prop) |
result = "store " + prop
)
or
exists(string prop | this = LoadStep(prop) |
result = "load" + prop
)
}
}
module StepSummary {
/**
* Gets a summary describing a path without any calls or returns.
*/
StepSummary level() { result = MkStepSummary(false, false) }
/**
* Gets a summary describing a path with one or more calls, but no returns.
*/
StepSummary call() { result = MkStepSummary(false, true) }
/**
* Gets a summary describing a path with one or more returns, but no calls.
*/
StepSummary return() { result = MkStepSummary(true, false) }
/**
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
*/
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
exists (DataFlow::Node predNode | pred.flowsTo(predNode) |
exists(DataFlow::Node predNode | pred.flowsTo(predNode) |
// Flow through properties of objects
propertyFlowStep(predNode, succ) and
summary = level()
summary = LevelStep()
or
// Flow through global variables
globalFlowStep(predNode, succ) and
summary = level()
summary = LevelStep()
or
// Flow into function
callStep(predNode, succ) and
summary = call()
summary = CallStep()
or
// Flow out of function
returnStep(predNode, succ) and
summary = return()
summary = ReturnStep()
or
// Flow through an instance field between members of the same class
DataFlow::localFieldStep(predNode, succ) and
summary = level()
summary = LevelStep()
or
exists(string prop |
basicStoreStep(predNode, succ, prop) and
summary = StoreStep(prop)
or
loadStep(predNode, succ, prop) and
summary = LoadStep(prop)
)
)
}
/**
* INTERNAL. Do not use.
*
* Appends a step summary onto a type-tracking summary.
*/
TypeTracker append(TypeTracker type, StepSummary summary) {
not (type.hasCall() = true and summary.hasReturn() = true) and
result.hasCall() = type.hasCall().booleanOr(summary.hasCall())
}
/**
* INTERNAL. Do not use.
*
* Prepends a step summary before a backwards type-tracking summary.
*/
TypeBackTracker prepend(StepSummary summary, TypeBackTracker type) {
not (type.hasReturn() = true and summary.hasCall() = true) and
result.hasReturn() = type.hasReturn().booleanOr(summary.hasReturn())
}
}
private newtype TTypeTracker = MkTypeTracker(boolean hasCall) {
hasCall = true or hasCall = false
}
private newtype TTypeTracker =
MkTypeTracker(Boolean hasCall, OptionalPropertyName prop)
/**
* EXPERIMENTAL.
@@ -159,7 +113,7 @@ private newtype TTypeTracker = MkTypeTracker(boolean hasCall) {
* )
* }
*
* DataFlow::SourceNode myType() { result = myType(_) }
* DataFlow::SourceNode myType() { result = myType(DataFlow::TypeTracker::end()) }
* ```
*
* To track values backwards, which can be useful for tracking
@@ -168,35 +122,56 @@ private newtype TTypeTracker = MkTypeTracker(boolean hasCall) {
class TypeTracker extends TTypeTracker {
Boolean hasCall;
TypeTracker() { this = MkTypeTracker(hasCall) }
string prop;
TypeTracker() { this = MkTypeTracker(hasCall, prop) }
TypeTracker append(StepSummary step) {
step = LevelStep() and result = this
or
step = CallStep() and result = MkTypeTracker(true, prop)
or
step = ReturnStep() and hasCall = false and result = this
or
step = LoadStep(prop) and result = MkTypeTracker(hasCall, "")
or
exists(string p |
step = StoreStep(p) and prop = "" and result = MkTypeTracker(hasCall, p)
)
}
string toString() {
hasCall = true and result = "type tracker with call steps"
or
hasCall = false and result = "type tracker without call steps"
exists(string withCall, string withProp |
(if hasCall = true then withCall = "with" else withCall = "without") and
(if prop != "" then withProp = " with property " + prop else withProp = "") and
result = "type tracker " + withCall + " call steps" + withProp
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() {
hasCall = false
}
predicate start() { hasCall = false and prop = "" }
predicate end() { prop = "" }
/**
* INTERNAL. DO NOT USE.
*
* Holds if this type has been tracked into a call.
*/
boolean hasCall() {
result = hasCall
}
boolean hasCall() { result = hasCall }
string getProp() { result = prop }
}
private newtype TTypeBackTracker = MkTypeBackTracker(boolean hasReturn) {
hasReturn = true or hasReturn = false
module TypeTracker {
TypeTracker end() { result.end() }
}
private newtype TTypeBackTracker =
MkTypeBackTracker(Boolean hasReturn, OptionalPropertyName prop)
/**
* EXPERIMENTAL.
*
@@ -221,33 +196,55 @@ private newtype TTypeBackTracker = MkTypeBackTracker(boolean hasReturn) {
* )
* }
*
* DataFlow::SourceNode myCallback() { result = myCallback(_) }
* DataFlow::SourceNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) }
* ```
*/
class TypeBackTracker extends TTypeBackTracker {
Boolean hasReturn;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn) }
string prop;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, prop) }
TypeBackTracker prepend(StepSummary step) {
step = LevelStep() and result = this
or
step = CallStep() and hasReturn = false and result = this
or
step = ReturnStep() and result = MkTypeBackTracker(true, prop)
or
exists(string p |
step = LoadStep(p) and prop = "" and result = MkTypeBackTracker(hasReturn, p)
)
or
step = StoreStep(prop) and result = MkTypeBackTracker(hasReturn, "")
}
string toString() {
hasReturn = true and result = "type back-tracker with return steps"
or
hasReturn = false and result = "type back-tracker without return steps"
exists(string withReturn, string withProp |
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
(if prop != "" then withProp = " with property " + prop else withProp = "") and
result = "type back-tracker " + withReturn + " return steps" + withProp
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() {
hasReturn = false
}
predicate start() { hasReturn = false and prop = "" }
predicate end() { prop = "" }
/**
* INTERNAL. DO NOT USE.
*
* Holds if this type has been back-tracked into a call through return edge.
*/
boolean hasReturn() {
result = hasReturn
}
boolean hasReturn() { result = hasReturn }
string getProp() { result = prop }
}
module TypeBackTracker {
TypeBackTracker end() { result.end() }
}

View File

@@ -17,36 +17,6 @@ predicate shouldTrackProperties(AbstractValue obj) {
obj instanceof AbstractModuleObject
}
/**
* Holds if `invk` may invoke `f`.
*/
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
/**
* Holds if `invk` may invoke `f` indirectly through the given `callback` argument.
*
* This only holds for explicitly modeled partial calls.
*/
private predicate partiallyCalls(
DataFlow::AdditionalPartialInvokeNode invk, DataFlow::AnalyzedNode callback, Function f
) {
invk.isPartialArgument(callback, _, _) and
exists(AbstractFunction callee | callee = callback.getAValue() |
if callback.getAValue().isIndefinite("global")
then f = callee.getFunction() and f.getFile() = invk.getFile()
else f = callee.getFunction()
)
}
/**
* Holds if `f` captures the variable defined by `def` in `cap`.
*/
cached
predicate captures(Function f, SsaExplicitDefinition def, SsaVariableCapture cap) {
def.getSourceVariable() = cap.getSourceVariable() and
f = cap.getContainer()
}
/**
* Holds if `source` corresponds to an expression returned by `f`, and
* `sink` equals `source`.
@@ -84,12 +54,50 @@ predicate localFlowStep(
}
/**
* Implements a set of data flow predicates that are used by multiple predicates and
* hence should only be computed once.
*/
cached
private module CachedSteps {
/**
* Holds if `f` captures the variable defined by `def` in `cap`.
*/
cached
predicate captures(Function f, SsaExplicitDefinition def, SsaVariableCapture cap) {
def.getSourceVariable() = cap.getSourceVariable() and
f = cap.getContainer()
}
/**
* Holds if `invk` may invoke `f`.
*/
cached
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
/**
* Holds if `invk` may invoke `f` indirectly through the given `callback` argument.
*
* This only holds for explicitly modeled partial calls.
*/
private predicate partiallyCalls(
DataFlow::AdditionalPartialInvokeNode invk, DataFlow::AnalyzedNode callback, Function f
) {
invk.isPartialArgument(callback, _, _) and
exists(AbstractFunction callee | callee = callback.getAValue() |
if callback.getAValue().isIndefinite("global")
then f = callee.getFunction() and f.getFile() = invk.getFile()
else f = callee.getFunction()
)
}
/**
* Holds if `arg` is passed as an argument into parameter `parm`
* through invocation `invk` of function `f`.
*/
predicate argumentPassing(
cached
predicate argumentPassing(
DataFlow::InvokeNode invk, DataFlow::ValueNode arg, Function f, DataFlow::ParameterNode parm
) {
) {
calls(invk, f) and
exists(int i |
f.getParameter(i) = parm.getParameter() and
@@ -103,105 +111,109 @@ predicate argumentPassing(
parm.getParameter() = f.getParameter(i) and
not parm.isRestParameter()
)
}
}
/**
/**
* Holds if there is a flow step from `pred` to `succ` through parameter passing
* to a function call.
*/
predicate callStep(DataFlow::Node pred, DataFlow::Node succ) { argumentPassing(_, pred, _, succ) }
cached
predicate callStep(DataFlow::Node pred, DataFlow::Node succ) { argumentPassing(_, pred, _, succ) }
/**
/**
* Holds if there is a flow step from `pred` to `succ` through returning
* from a function call or the receiver flowing out of a constructor call.
*/
predicate returnStep(DataFlow::Node pred, DataFlow::Node succ) {
cached
predicate returnStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Function f | calls(succ, f) |
returnExpr(f, pred, _)
or
succ instanceof DataFlow::NewNode and
DataFlow::thisNode(pred, f)
)
}
}
/**
/**
* Holds if there is an assignment to property `prop` of an object represented by `obj`
* with right hand side `rhs` somewhere, and properties of `obj` should be tracked.
*/
pragma[noinline]
private predicate trackedPropertyWrite(AbstractValue obj, string prop, DataFlow::Node rhs) {
pragma[noinline]
private predicate trackedPropertyWrite(AbstractValue obj, string prop, DataFlow::Node rhs) {
exists(AnalyzedPropertyWrite pw |
pw.writes(obj, prop, rhs) and
shouldTrackProperties(obj) and
// avoid introducing spurious global flow
not pw.baseIsIncomplete("global")
)
}
}
/**
/**
* Holds if there is a flow step from `pred` to `succ` through an object property.
*/
predicate propertyFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
cached
predicate propertyFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(AbstractValue obj, string prop |
trackedPropertyWrite(obj, prop, pred) and
succ.(AnalyzedPropertyRead).reads(obj, prop)
)
}
}
/**
/**
* Gets a node whose value is assigned to `gv` in `f`.
*/
pragma[noinline]
private DataFlow::ValueNode getADefIn(GlobalVariable gv, File f) {
pragma[noinline]
private DataFlow::ValueNode getADefIn(GlobalVariable gv, File f) {
exists(VarDef def |
def.getFile() = f and
def.getTarget() = gv.getAReference() and
result = DataFlow::valueNode(def.getSource())
)
}
}
/**
/**
* Gets a use of `gv` in `f`.
*/
pragma[noinline]
DataFlow::ValueNode getAUseIn(GlobalVariable gv, File f) {
pragma[noinline]
private DataFlow::ValueNode getAUseIn(GlobalVariable gv, File f) {
result.getFile() = f and
result = DataFlow::valueNode(gv.getAnAccess())
}
}
/**
/**
* Holds if there is a flow step from `pred` to `succ` through a global
* variable. Both `pred` and `succ` must be in the same file.
*/
predicate globalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
cached
predicate globalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(GlobalVariable gv, File f |
pred = getADefIn(gv, f) and
succ = getAUseIn(gv, f)
)
}
}
/**
/**
* Holds if there is a write to property `prop` of global variable `gv`
* in file `f`, where the right-hand side of the write is `rhs`.
*/
pragma[noinline]
predicate globalPropertyWrite(GlobalVariable gv, File f, string prop, DataFlow::Node rhs) {
pragma[noinline]
private predicate globalPropertyWrite(GlobalVariable gv, File f, string prop, DataFlow::Node rhs) {
exists(DataFlow::PropWrite pw | pw.writes(getAUseIn(gv, f), prop, rhs))
}
}
/**
/**
* Holds if there is a read from property `prop` of `base`, which is
* an access to global variable `base` in file `f`.
*/
pragma[noinline]
predicate globalPropertyRead(GlobalVariable gv, File f, string prop, DataFlow::Node base) {
pragma[noinline]
private predicate globalPropertyRead(GlobalVariable gv, File f, string prop, DataFlow::Node base) {
exists(DataFlow::PropRead pr |
base = getAUseIn(gv, f) and
pr.accesses(base, prop)
)
}
}
/**
/**
* Holds if there is a store step from `pred` to `succ` under property `prop`,
* that is, `succ` is the local source of the base of a write of property
* `prop` with right-hand side `pred`.
@@ -219,24 +231,26 @@ predicate globalPropertyRead(GlobalVariable gv, File f, string prop, DataFlow::N
* then there is a store step from the right-hand side of the write to any
* read of the same property from the same global variable in the same file.
*/
predicate basicStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
cached
predicate basicStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
succ.(DataFlow::SourceNode).hasPropertyWrite(prop, pred)
or
exists(GlobalVariable gv, File f |
globalPropertyWrite(gv, f, prop, pred) and
globalPropertyRead(gv, f, prop, succ)
)
}
}
/**
/**
* Holds if there is a load step from `pred` to `succ` under property `prop`,
* that is, `succ` is a read of property `prop` from `pred`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::PropRead succ, string prop) {
cached
predicate loadStep(DataFlow::Node pred, DataFlow::PropRead succ, string prop) {
succ.accesses(pred, prop)
}
}
/**
/**
* Holds if there is a higher-order call with argument `arg`, and `cb` is the local
* source of an argument that flows into the callee position of that call:
*
@@ -255,7 +269,8 @@ predicate loadStep(DataFlow::Node pred, DataFlow::PropRead succ, string prop) {
* This is an over-approximation of a possible data flow step through a callback
* invocation.
*/
predicate callback(DataFlow::Node arg, DataFlow::SourceNode cb) {
cached
predicate callback(DataFlow::Node arg, DataFlow::SourceNode cb) {
exists(DataFlow::InvokeNode invk, DataFlow::ParameterNode cbParm, DataFlow::Node cbArg |
arg = invk.getAnArgument() and
cbParm.flowsTo(invk.getCalleeNode()) and
@@ -268,22 +283,26 @@ predicate callback(DataFlow::Node arg, DataFlow::SourceNode cb) {
callStep(cbArg, cbParm) and
cb.flowsTo(cbArg)
)
}
}
/**
/**
* Holds if `f` may return `base`, which has a write of property `prop` with right-hand side `rhs`.
*/
predicate returnedPropWrite(Function f, DataFlow::SourceNode base, string prop, DataFlow::Node rhs) {
cached
predicate returnedPropWrite(Function f, DataFlow::SourceNode base, string prop, DataFlow::Node rhs) {
base.hasPropertyWrite(prop, rhs) and
base.flowsToExpr(f.getAReturnedExpr())
}
}
/**
/**
* Holds if `f` may assign `rhs` to `this.prop`.
*/
predicate receiverPropWrite(Function f, string prop, DataFlow::Node rhs) {
cached
predicate receiverPropWrite(Function f, string prop, DataFlow::Node rhs) {
DataFlow::thisNode(f).hasPropertyWrite(prop, rhs)
}
}
import CachedSteps
/**
* A utility class that is equivalent to `boolean` but does not require type joining.

View File

@@ -118,8 +118,16 @@ module Connect {
}
override DataFlow::SourceNode getARouteHandler() {
result.(DataFlow::SourceNode).flowsTo(getARouteHandlerExpr().flow()) or
result.(DataFlow::TrackedNode).flowsTo(getARouteHandlerExpr().flow())
result = getARouteHandler(DataFlow::TypeBackTracker::end())
}
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
t.start() and
result = getARouteHandlerExpr().flow().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = getARouteHandler(t2).backtrack(t2, t)
)
}
override Expr getServer() { result = server }
@@ -169,24 +177,13 @@ module Connect {
}
/**
* Tracking for `RouteHandlerCandidate`.
*/
private class TrackedRouteHandlerCandidate extends DataFlow::TrackedNode {
TrackedRouteHandlerCandidate() { this instanceof RouteHandlerCandidate }
}
/**
* A function that looks like a Connect route handler and flows to a route setup.
* A function that flows to a route setup.
*/
private class TrackedRouteHandlerCandidateWithSetup extends RouteHandler,
HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
override Function astNode;
HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
TrackedRouteHandlerCandidateWithSetup() {
exists(TrackedRouteHandlerCandidate tracked |
tracked.flowsTo(any(RouteSetup s).getARouteHandlerExpr().flow()) and
this = tracked
)
this = any(RouteSetup s).getARouteHandler()
}
override SimpleParameter getRouteHandlerParameter(string kind) {

View File

@@ -16,7 +16,7 @@ module Electron {
/**
* An instantiation of `BrowserWindow` or `BrowserView`.
*/
abstract private class NewBrowserObject extends BrowserObject, DataFlow::SourceNode {
abstract private class NewBrowserObject extends BrowserObject {
DataFlow::NewNode self;
NewBrowserObject() { this = self }
@@ -56,11 +56,20 @@ module Electron {
}
}
private DataFlow::SourceNode browserObject(DataFlow::TypeTracker t) {
t.start() and
result instanceof NewBrowserObject
or
exists(DataFlow::TypeTracker t2 |
result = browserObject(t2).track(t2, t)
)
}
/**
* A data flow node whose value may originate from a browser object instantiation.
*/
private class BrowserObjectByFlow extends BrowserObject {
BrowserObjectByFlow() { any(NewBrowserObject nbo).flowsTo(this) }
BrowserObjectByFlow() { browserObject(DataFlow::TypeTracker::end()).flowsTo(this) }
}
/**

View File

@@ -111,8 +111,16 @@ module Express {
Expr getLastRouteHandlerExpr() { result = max(int i | | getRouteHandlerExpr(i) order by i) }
override DataFlow::SourceNode getARouteHandler() {
result.(DataFlow::SourceNode).flowsTo(getARouteHandlerExpr().flow()) or
result.(DataFlow::TrackedNode).flowsTo(getARouteHandlerExpr().flow())
result = getARouteHandler(DataFlow::TypeBackTracker::end())
}
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
t.start() and
result = getARouteHandlerExpr().flow().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = getARouteHandler(t2).backtrack(t2, t)
)
}
override Expr getServer() { result.(Application).getARouteHandler() = getARouteHandler() }
@@ -766,24 +774,13 @@ module Express {
}
/**
* Tracking for `RouteHandlerCandidate`.
*/
private class TrackedRouteHandlerCandidate extends DataFlow::TrackedNode {
TrackedRouteHandlerCandidate() { this instanceof RouteHandlerCandidate }
}
/**
* A function that looks like an Express route handler and flows to a route setup.
* A function that flows to a route setup.
*/
private class TrackedRouteHandlerCandidateWithSetup extends RouteHandler,
HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
override Function astNode;
HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
TrackedRouteHandlerCandidateWithSetup() {
exists(TrackedRouteHandlerCandidate tracked |
tracked.flowsTo(any(RouteSetup s).getARouteHandlerExpr().flow()) and
this = tracked
)
this = any(RouteSetup s).getARouteHandler()
}
override SimpleParameter getRouteHandlerParameter(string kind) {

View File

@@ -276,11 +276,20 @@ module HTTP {
* a request object enters the flow graph, such as the request
* parameter of a route handler.
*/
abstract class RequestSource extends DataFlow::TrackedNode {
abstract class RequestSource extends DataFlow::Node {
/**
* Gets the route handler that handles this request.
*/
abstract RouteHandler getRouteHandler();
predicate flowsTo(DataFlow::Node nd) { ref(DataFlow::TypeTracker::end()).flowsTo(nd) }
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
result = this
or
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
}
/**
@@ -288,11 +297,20 @@ module HTTP {
* a response object enters the flow graph, such as the response
* parameter of a route handler.
*/
abstract class ResponseSource extends DataFlow::TrackedNode {
abstract class ResponseSource extends DataFlow::Node {
/**
* Gets the route handler that provides this response.
*/
abstract RouteHandler getRouteHandler();
predicate flowsTo(DataFlow::Node nd) { ref(DataFlow::TypeTracker::end()).flowsTo(nd) }
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
result = this
or
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
}
/**

View File

@@ -193,8 +193,16 @@ module Hapi {
}
override DataFlow::SourceNode getARouteHandler() {
result.(DataFlow::SourceNode).flowsTo(handler.flow()) or
result.(DataFlow::TrackedNode).flowsTo(handler.flow())
result = getARouteHandler(DataFlow::TypeBackTracker::end())
}
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
t.start() and
result = handler.flow().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = getARouteHandler(t2).backtrack(t2, t)
)
}
Expr getRouteHandlerExpr() { result = handler }
@@ -223,25 +231,13 @@ module Hapi {
}
}
/**
* Tracking for `RouteHandlerCandidate`.
*/
private class TrackedRouteHandlerCandidate extends DataFlow::TrackedNode {
TrackedRouteHandlerCandidate() { this instanceof RouteHandlerCandidate }
}
/**
* A function that looks like a Hapi route handler and flows to a route setup.
*/
private class TrackedRouteHandlerCandidateWithSetup extends RouteHandler,
HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
override Function astNode;
HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
TrackedRouteHandlerCandidateWithSetup() {
exists(TrackedRouteHandlerCandidate tracked |
tracked.flowsTo(any(RouteSetup s).getRouteHandlerExpr().flow()) and
this = tracked
)
this = any(RouteSetup s).getARouteHandler()
}
}
}

View File

@@ -64,7 +64,7 @@ module Koa {
* A Koa context source, that is, the context parameter of a
* route handler, or a `this` access in a route handler.
*/
private class ContextSource extends DataFlow::TrackedNode {
private class ContextSource extends DataFlow::Node {
RouteHandler rh;
ContextSource() {
@@ -77,6 +77,19 @@ module Koa {
* Gets the route handler that handles this request.
*/
RouteHandler getRouteHandler() { result = rh }
predicate flowsTo(DataFlow::Node nd) {
ref(DataFlow::TypeTracker::end()).flowsTo(nd)
}
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
result = this
or
exists(DataFlow::TypeTracker t2 |
result = ref(t2).track(t2, t)
)
}
}
/**

View File

@@ -189,8 +189,16 @@ module NodeJSLib {
}
override DataFlow::SourceNode getARouteHandler() {
result.(DataFlow::SourceNode).flowsTo(handler.flow()) or
result.(DataFlow::TrackedNode).flowsTo(handler.flow())
result = getARouteHandler(DataFlow::TypeBackTracker::end())
}
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
t.start() and
result = handler.flow().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = getARouteHandler(t2).backtrack(t2, t)
)
}
override Expr getServer() { result = server }
@@ -613,24 +621,12 @@ module NodeJSLib {
}
/**
* Tracking for `RouteHandlerCandidate`.
*/
private class TrackedRouteHandlerCandidate extends DataFlow::TrackedNode {
TrackedRouteHandlerCandidate() { this instanceof RouteHandlerCandidate }
}
/**
* A function that looks like a Node.js route handler and flows to a route setup.
* A function that flows to a route setup.
*/
private class TrackedRouteHandlerCandidateWithSetup extends RouteHandler,
HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
override Function astNode;
HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
TrackedRouteHandlerCandidateWithSetup() {
exists(TrackedRouteHandlerCandidate tracked |
tracked.flowsTo(any(RouteSetup s).getRouteHandlerExpr().flow()) and
this = tracked
)
this = any(RouteSetup s).getARouteHandler()
}
}

View File

@@ -24,16 +24,19 @@ module SocketIO {
result = DataFlow::moduleImport("socket.io").getAMemberCall("listen")
}
/** A data flow node that may produce (that is, create or return) a socket.io server. */
class ServerNode extends DataFlow::SourceNode {
ServerObject srv;
ServerNode() {
this = newServer() and
srv = MkServer(this)
/**
* Gets a data flow node that may refer to the socket.io server created at `srv`.
*/
private DataFlow::SourceNode server(ServerObject srv, DataFlow::TypeTracker t) {
result = newServer() and
srv = MkServer(result) and
t.start()
or
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred | pred = server(srv, t2) |
result = pred.track(t2, t)
or
// invocation of a chainable method
exists(ServerNode base, DataFlow::MethodCallNode mcn, string m |
exists(DataFlow::MethodCallNode mcn, string m |
m = "adapter" or
m = "attach" or
m = "bind" or
@@ -44,82 +47,99 @@ module SocketIO {
m = "serveClient" or
m = "set"
|
mcn = base.getAMethodCall(m) and
mcn = pred.getAMethodCall(m) and
// exclude getter versions
exists(mcn.getAnArgument()) and
this = mcn and
srv = base.getServer()
result = mcn and
t = t2
)
)
}
/** A data flow node that may produce (that is, create or return) a socket.io server. */
class ServerNode extends DataFlow::SourceNode {
ServerObject srv;
ServerNode() { this = server(srv, DataFlow::TypeTracker::end()) }
/** Gets the server to which this node refers. */
ServerObject getServer() { result = srv }
}
/**
* Gets the name of a chainable method on socket.io namespace objects, which servers forward
* to their default namespace.
*/
private string namespaceChainableMethod() {
result = "binary" or
result = "clients" or
result = "compress" or
result = "emit" or
result = "in" or
result = "send" or
result = "to" or
result = "use" or
result = "write" or
result = EventEmitter::chainableMethod()
}
/**
* Gets a data flow node that may refer to the socket.io namespace created at `ns`.
*/
private DataFlow::SourceNode namespace(NamespaceObject ns, DataFlow::TypeTracker t) {
t.start() and
exists(ServerNode srv |
// namespace lookup on `srv`
result = srv.getAPropertyRead("sockets") and
ns = srv.getServer().getDefaultNamespace()
or
exists(DataFlow::MethodCallNode mcn, string path |
mcn = srv.getAMethodCall("of") and
mcn.getArgument(0).mayHaveStringValue(path) and
result = mcn and
ns = MkNamespace(srv.getServer(), path)
)
or
// invocation of a method that `srv` forwards to its default namespace
result = srv.getAMethodCall(namespaceChainableMethod()) and
ns = srv.getServer().getDefaultNamespace()
)
or
exists(DataFlow::SourceNode pred, DataFlow::TypeTracker t2 | pred = namespace(ns, t2) |
result = pred.track(t2, t)
or
// invocation of a chainable method
result = pred.getAMethodCall(namespaceChainableMethod()) and
t = t2
or
// invocation of chainable getter method
exists(string m |
m = "json" or
m = "local" or
m = "volatile"
|
result = pred.getAPropertyRead(m) and
t = t2
)
)
}
/** A data flow node that may produce a namespace object. */
class NamespaceNode extends DataFlow::SourceNode {
NamespaceObject ns;
NamespaceNode() {
// namespace lookup
exists(ServerNode srv |
this = srv.getAPropertyRead("sockets") and
ns = srv.getServer().getDefaultNamespace()
or
exists(DataFlow::MethodCallNode mcn, string path |
mcn = srv.getAMethodCall("of") and
mcn.getArgument(0).mayHaveStringValue(path) and
this = mcn and
ns = MkNamespace(srv.getServer(), path)
)
)
or
// invocation of a chainable method
exists(string m |
m = "binary" or
m = "clients" or
m = "compress" or
m = "emit" or
m = "in" or
m = "send" or
m = "to" or
m = "use" or
m = "write" or
m = EventEmitter::chainableMethod()
|
exists(NamespaceNode base |
this = base.getAMethodCall(m) and
ns = base.getNamespace()
)
or
// server objects forward these methods to their default namespace
exists(ServerNode srv |
this = srv.getAMethodCall(m) and
ns = srv.getServer().getDefaultNamespace()
)
)
or
// invocation of chainable getter method
exists(NamespaceNode base, string m |
m = "json" or
m = "local" or
m = "volatile"
|
this = base.getAPropertyRead(m) and
ns = base.getNamespace()
)
}
NamespaceNode() { this = namespace(ns, DataFlow::TypeTracker::end()) }
/** Gets the namespace to which this node refers. */
NamespaceObject getNamespace() { result = ns }
}
/** A data flow node that may produce a socket object. */
class SocketNode extends DataFlow::SourceNode {
NamespaceObject ns;
SocketNode() {
/**
* Gets a data flow node that may refer to a socket.io socket belonging to namespace `ns`.
*/
private DataFlow::SourceNode socket(NamespaceObject ns, DataFlow::TypeTracker t) {
// callback accepting a socket
t.start() and
exists(DataFlow::SourceNode base, string connect, DataFlow::MethodCallNode on |
(
ns = base.(ServerNode).getServer().getDefaultNamespace() or
@@ -129,11 +149,14 @@ module SocketIO {
|
on = base.getAMethodCall(EventEmitter::on()) and
on.getArgument(0).mayHaveStringValue(connect) and
this = on.getCallback(1).getParameter(0)
result = on.getCallback(1).getParameter(0)
)
or
exists(DataFlow::SourceNode pred, DataFlow::TypeTracker t2 | pred = socket(ns, t2) |
result = pred.track(t2, t)
or
// invocation of a chainable method
exists(SocketNode base, string m |
exists(string m |
m = "binary" or
m = "compress" or
m = "disconnect" or
@@ -147,22 +170,29 @@ module SocketIO {
m = "write" or
m = EventEmitter::chainableMethod()
|
this = base.getAMethodCall(m) and
ns = base.getNamespace()
result = pred.getAMethodCall(m) and
t = t2
)
or
// invocation of a chainable getter method
exists(SocketNode base, string m |
exists(string m |
m = "broadcast" or
m = "json" or
m = "local" or
m = "volatile"
|
this = base.getAPropertyRead(m) and
ns = base.getNamespace()
result = pred.getAPropertyRead(m) and
t = t2
)
)
}
/** A data flow node that may produce a socket object. */
class SocketNode extends DataFlow::SourceNode {
NamespaceObject ns;
SocketNode() { this = socket(ns, DataFlow::TypeTracker::end()) }
/** Gets the namespace to which this socket belongs. */
NamespaceObject getNamespace() { result = ns }
}
@@ -361,11 +391,11 @@ module SocketIO {
* (npm package `socket.io-client`).
*/
module SocketIOClient {
/** A data flow node that may produce a socket object. */
class SocketNode extends DataFlow::SourceNode {
DataFlow::InvokeNode invk;
SocketNode() {
/**
* Gets a data flow node that may refer to the socket.io socket created at `invk`.
*/
private DataFlow::SourceNode socket(DataFlow::InvokeNode invk, DataFlow::TypeTracker t) {
t.start() and
exists(DataFlow::SourceNode io |
io = DataFlow::globalVarRef("io") or
io = DataFlow::globalVarRef("io").getAPropertyRead("connect") or
@@ -373,10 +403,18 @@ module SocketIOClient {
io = DataFlow::moduleMember("socket.io-client", "connect")
|
invk = io.getAnInvocation() and
this = invk
result = invk
)
or
exists(DataFlow::TypeTracker t2 | result = socket(invk, t2).track(t2, t))
}
/** A data flow node that may produce a socket object. */
class SocketNode extends DataFlow::SourceNode {
DataFlow::InvokeNode invk;
SocketNode() { this = socket(invk, DataFlow::TypeTracker::end()) }
/** Gets the path of the namespace this socket belongs to, if it can be determined. */
string getNamespacePath() {
// the path name of the specified URL

View File

@@ -43,12 +43,13 @@ predicate sanitizingPrefixEdge(DataFlow::Node source, DataFlow::Node sink) {
* - `?` (any suffix becomes part of query)
* - `#` (any suffix becomes part of fragment)
* - `/` or `\`, immediately prefixed by a character other than `:`, `/`, or `\` (any suffix becomes part of the path)
* - a leading `/` or `\` followed by a character other than `/` or `\` (any suffix becomes part of the path)
*
* In the latter case, the additional prefix check is necessary to avoid a `/` that could be interpreted as
* In the latter two cases, the additional check is necessary to avoid a `/` that could be interpreted as
* the `//` separating the (optional) scheme from the hostname.
*/
private predicate hasHostnameSanitizingSubstring(DataFlow::Node nd) {
nd.getStringValue().regexpMatch(".*([?#]|[^?#:/\\\\][/\\\\]).*")
nd.getStringValue().regexpMatch(".*([?#]|[^?#:/\\\\][/\\\\]).*|[/\\\\][^/\\\\].*")
or
hasHostnameSanitizingSubstring(StringConcatenation::getAnOperand(nd))
or

View File

@@ -6,86 +6,68 @@ string chainableMethod() {
}
class ApiObject extends DataFlow::NewNode {
ApiObject() {
this = DataFlow::moduleImport("@test/myapi").getAnInstantiation()
}
ApiObject() { this = DataFlow::moduleImport("@test/myapi").getAnInstantiation() }
DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
result = this
or
t.start() and
result = ref(_).getAMethodCall(chainableMethod())
result = ref(DataFlow::TypeTracker::end()).getAMethodCall(chainableMethod())
or
exists(DataFlow::TypeTracker t2 |
result = ref(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
DataFlow::SourceNode ref() {
result = ref(_)
}
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
}
class Connection extends DataFlow::SourceNode {
ApiObject api;
Connection() {
this = api.ref().getAMethodCall("createConnection")
}
Connection() { this = api.ref().getAMethodCall("createConnection") }
DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
result = this
or
exists(DataFlow::TypeTracker t2 |
result = ref(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
DataFlow::SourceNode ref() {
result = ref(_)
}
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
DataFlow::SourceNode getACallbackNode(DataFlow::TypeBackTracker t) {
t.start() and
result = ref().getAMethodCall("getData").getArgument(0).getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = getACallbackNode(t2).backtrack(t2, t)
)
exists(DataFlow::TypeBackTracker t2 | result = getACallbackNode(t2).backtrack(t2, t))
}
DataFlow::FunctionNode getACallback() {
result = getACallbackNode(_).getAFunctionValue()
result = getACallbackNode(DataFlow::TypeBackTracker::end()).getAFunctionValue()
}
}
class DataValue extends DataFlow::SourceNode {
Connection connection;
DataValue() {
this = connection.getACallback().getParameter(0)
}
DataValue() { this = connection.getACallback().getParameter(0) }
DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and
result = this
or
exists(DataFlow::TypeTracker t2 |
result = ref(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
DataFlow::SourceNode ref() {
result = ref(_)
}
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
}
query DataFlow::SourceNode test_ApiObject() { result = any(ApiObject obj).ref() }
query DataFlow::SourceNode test_Connection() { result = any(Connection c).ref() }
query DataFlow::SourceNode test_DataCallback() { result = any(Connection c).getACallbackNode(_) }
query DataFlow::SourceNode test_DataCallback() {
result = any(Connection c).getACallbackNode(DataFlow::TypeBackTracker::end())
}
query DataFlow::SourceNode test_DataValue() { result = any(DataValue v).ref() }

View File

@@ -5,6 +5,7 @@ apiObject
connection
| type tracker with call steps | tst.js:6:15:6:18 | conn |
| type tracker with call steps | tst.js:10:5:10:19 | this.connection |
| type tracker with call steps with property connection | tst.js:6:14:6:13 | this |
| type tracker without call steps | tst.js:15:10:15:49 | api.cha ... ction() |
| type tracker without call steps | tst.js:18:7:18:21 | getConnection() |
| type tracker without call steps | tst.js:30:9:30:23 | getConnection() |

View File

@@ -10,52 +10,38 @@ DataFlow::SourceNode apiObject(DataFlow::TypeTracker t) {
result = DataFlow::moduleImport("@test/myapi").getAnInstantiation()
or
t.start() and
result = apiObject(_).getAMethodCall(chainableMethod())
result = apiObject(DataFlow::TypeTracker::end()).getAMethodCall(chainableMethod())
or
exists(DataFlow::TypeTracker t2 |
result = apiObject(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = apiObject(t2).track(t2, t))
}
query DataFlow::SourceNode apiObject() {
result = apiObject(_)
}
query DataFlow::SourceNode apiObject() { result = apiObject(DataFlow::TypeTracker::end()) }
query DataFlow::SourceNode connection(DataFlow::TypeTracker t) {
t.start() and
result = apiObject().getAMethodCall("createConnection")
or
exists(DataFlow::TypeTracker t2 |
result = connection(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = connection(t2).track(t2, t))
}
DataFlow::SourceNode connection() {
result = connection(_)
}
DataFlow::SourceNode connection() { result = connection(DataFlow::TypeTracker::end()) }
DataFlow::SourceNode dataCallback(DataFlow::TypeBackTracker t) {
t.start() and
result = connection().getAMethodCall("getData").getArgument(0).getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = dataCallback(t2).backtrack(t2, t)
)
exists(DataFlow::TypeBackTracker t2 | result = dataCallback(t2).backtrack(t2, t))
}
query DataFlow::SourceNode dataCallback() {
result = dataCallback(_)
result = dataCallback(DataFlow::TypeBackTracker::end())
}
DataFlow::SourceNode dataValue(DataFlow::TypeTracker t) {
t.start() and
result = dataCallback().getAFunctionValue().getParameter(0)
or
exists(DataFlow::TypeTracker t2 |
result = dataValue(t2).track(t2, t)
)
exists(DataFlow::TypeTracker t2 | result = dataValue(t2).track(t2, t))
}
query DataFlow::SourceNode dataValue() {
result = dataValue(_)
}
query DataFlow::SourceNode dataValue() { result = dataValue(DataFlow::TypeTracker::end()) }

View File

@@ -4,6 +4,8 @@
| electron.js:3:10:3:48 | new Bro ... s: {}}) |
| electron.js:4:5:4:46 | bv |
| electron.js:4:10:4:46 | new Bro ... s: {}}) |
| electron.js:35:14:35:14 | x |
| electron.js:36:12:36:12 | x |
| electron.js:39:5:39:6 | bw |
| electron.js:40:5:40:6 | bv |
| electron.ts:3:12:3:13 | bw |

View File

@@ -3,6 +3,9 @@
| src/hapi.js:1:1:1:30 | functio ... t, h){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/iterated-handlers.js:4:2:4:22 | functio ... res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/middleware-attacher-getter.js:29:32:29:51 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/nodejs.js:5:22:5:41 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/nodejs.js:11:23:11:42 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/nodejs.js:12:25:12:44 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/route-objects.js:7:19:7:38 | function(req, res){} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/route-objects.js:8:12:10:5 | (req, res) {\\n\\n } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/route-objects.js:20:16:22:9 | (req, r ... } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
@@ -10,8 +13,20 @@
| src/route-objects.js:40:12:42:5 | (req, res) {\\n\\n } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/route-objects.js:50:12:52:5 | (req, res) {\\n\\n } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/route-objects.js:56:12:58:5 | functio ... ;\\n } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:6:32:6:52 | functio ... res) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:8:32:8:61 | functio ... nse) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:30:36:30:56 | functio ... res) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:33:18:33:38 | functio ... res) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:34:18:34:38 | functio ... res) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:37:5:37:25 | functio ... res) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:38:5:38:25 | functio ... res) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:43:18:43:38 | functio ... res) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:46:1:46:23 | functio ... res) {} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:52:1:54:1 | functio ... req()\\n} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:61:1:63:1 | functio ... turn;\\n} | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:70:5:72:5 | functio ... \\n\\n } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:79:5:81:5 | functio ... \\n\\n } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:84:5:86:5 | functio ... \\n\\n } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:109:16:111:9 | functio ... } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:124:16:126:9 | functio ... } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |
| src/tst.js:132:16:134:9 | functio ... } | A `RouteHandlerCandidate` that did not get promoted to `RouteHandler`, and it is not used in a `RouteSetupCandidate`. |

View File

@@ -1,17 +0,0 @@
| src/nodejs.js:5:1:5:42 | unknown ... res){}) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/nodejs.js:11:1:11:43 | unknown ... res){}) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/nodejs.js:12:1:12:45 | unknown ... res){}) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:6:1:6:53 | someOth ... es) {}) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:8:1:8:62 | someOth ... se) {}) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:30:1:30:57 | someOth ... es) {}) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:32:1:34:39 | someOth ... es) {}) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:36:1:39:2 | someOth ... ) {}\\n]) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:41:1:43:39 | someOth ... es) {}) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:87:5:87:57 | unknown ... cSetup) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:96:5:96:36 | unknown ... h', rh) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:98:5:98:38 | unknown ... , [rh]) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:104:5:104:45 | unknown ... wn, rh) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:137:5:137:57 | unknown ... cSetup) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:149:5:149:36 | unknown ... h', rh) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:151:5:151:38 | unknown ... , [rh]) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |
| src/tst.js:157:5:157:45 | unknown ... wn, rh) | A `RouteSetupCandidate` that did not get promoted to `RouteSetup`, and it is using at least one `RouteHandlerCandidate`. |

View File

@@ -22,6 +22,18 @@ nodes
| tst9.js:2:21:2:37 | document.location |
| tst9.js:2:21:2:42 | documen ... on.hash |
| tst9.js:2:21:2:55 | documen ... ring(1) |
| tst10.js:5:17:5:46 | '/' + d ... .search |
| tst10.js:5:23:5:39 | document.location |
| tst10.js:5:23:5:46 | documen ... .search |
| tst10.js:8:17:8:47 | '//' + ... .search |
| tst10.js:8:24:8:40 | document.location |
| tst10.js:8:24:8:47 | documen ... .search |
| tst10.js:11:17:11:50 | '//foo' ... .search |
| tst10.js:11:27:11:43 | document.location |
| tst10.js:11:27:11:50 | documen ... .search |
| tst10.js:14:17:14:56 | 'https: ... .search |
| tst10.js:14:33:14:49 | document.location |
| tst10.js:14:33:14:56 | documen ... .search |
| tst.js:2:19:2:69 | /.*redi ... n.href) |
| tst.js:2:19:2:72 | /.*redi ... ref)[1] |
| tst.js:2:47:2:63 | document.location |
@@ -46,6 +58,14 @@ edges
| tst9.js:2:21:2:37 | document.location | tst9.js:2:21:2:42 | documen ... on.hash |
| tst9.js:2:21:2:42 | documen ... on.hash | tst9.js:2:21:2:55 | documen ... ring(1) |
| tst9.js:2:21:2:55 | documen ... ring(1) | tst9.js:2:21:2:37 | document.location |
| tst10.js:5:23:5:39 | document.location | tst10.js:5:23:5:46 | documen ... .search |
| tst10.js:5:23:5:46 | documen ... .search | tst10.js:5:17:5:46 | '/' + d ... .search |
| tst10.js:8:24:8:40 | document.location | tst10.js:8:24:8:47 | documen ... .search |
| tst10.js:8:24:8:47 | documen ... .search | tst10.js:8:17:8:47 | '//' + ... .search |
| tst10.js:11:27:11:43 | document.location | tst10.js:11:27:11:50 | documen ... .search |
| tst10.js:11:27:11:50 | documen ... .search | tst10.js:11:17:11:50 | '//foo' ... .search |
| tst10.js:14:33:14:49 | document.location | tst10.js:14:33:14:56 | documen ... .search |
| tst10.js:14:33:14:56 | documen ... .search | tst10.js:14:17:14:56 | 'https: ... .search |
| tst.js:2:19:2:69 | /.*redi ... n.href) | tst.js:2:19:2:72 | /.*redi ... ref)[1] |
| tst.js:2:47:2:63 | document.location | tst.js:2:47:2:68 | documen ... on.href |
| tst.js:2:47:2:68 | documen ... on.href | tst.js:2:19:2:69 | /.*redi ... n.href) |
@@ -59,4 +79,8 @@ edges
| tst7.js:5:27:5:50 | documen ... .search | tst7.js:5:27:5:43 | document.location | tst7.js:5:27:5:50 | documen ... .search | Untrusted URL redirection due to $@. | tst7.js:5:27:5:43 | document.location | user-provided value |
| tst9.js:2:21:2:55 | documen ... ring(1) | tst9.js:2:21:2:37 | document.location | tst9.js:2:21:2:55 | documen ... ring(1) | Untrusted URL redirection due to $@. | tst9.js:2:21:2:37 | document.location | user-provided value |
| tst9.js:2:21:2:55 | documen ... ring(1) | tst9.js:2:21:2:37 | document.location | tst9.js:2:21:2:55 | documen ... ring(1) | Untrusted URL redirection due to $@. | tst9.js:2:21:2:37 | document.location | user-provided value |
| tst10.js:5:17:5:46 | '/' + d ... .search | tst10.js:5:23:5:39 | document.location | tst10.js:5:17:5:46 | '/' + d ... .search | Untrusted URL redirection due to $@. | tst10.js:5:23:5:39 | document.location | user-provided value |
| tst10.js:8:17:8:47 | '//' + ... .search | tst10.js:8:24:8:40 | document.location | tst10.js:8:17:8:47 | '//' + ... .search | Untrusted URL redirection due to $@. | tst10.js:8:24:8:40 | document.location | user-provided value |
| tst10.js:11:17:11:50 | '//foo' ... .search | tst10.js:11:27:11:43 | document.location | tst10.js:11:17:11:50 | '//foo' ... .search | Untrusted URL redirection due to $@. | tst10.js:11:27:11:43 | document.location | user-provided value |
| tst10.js:14:17:14:56 | 'https: ... .search | tst10.js:14:33:14:49 | document.location | tst10.js:14:17:14:56 | 'https: ... .search | Untrusted URL redirection due to $@. | tst10.js:14:33:14:49 | document.location | user-provided value |
| tst.js:2:19:2:72 | /.*redi ... ref)[1] | tst.js:2:47:2:63 | document.location | tst.js:2:19:2:72 | /.*redi ... ref)[1] | Untrusted URL redirection due to $@. | tst.js:2:47:2:63 | document.location | user-provided value |

View File

@@ -0,0 +1,14 @@
// OK - cannot affect hostname
location.href = '/foo' + document.location.search;
// NOT OK
location.href = '/' + document.location.search;
// NOT OK
location.href = '//' + document.location.search;
// NOT OK
location.href = '//foo' + document.location.search;
// NOT OK
location.href = 'https://foo' + document.location.search;

View File

@@ -22,12 +22,6 @@ nodes
| express.js:135:23:135:37 | req.params.user |
| express.js:136:16:136:36 | 'u' + r ... ms.user |
| express.js:136:22:136:36 | req.params.user |
| express.js:138:16:138:45 | '/' + ( ... s.user) |
| express.js:138:22:138:45 | ('/u' + ... s.user) |
| express.js:138:23:138:44 | '/u' + ... ms.user |
| express.js:138:30:138:44 | req.params.user |
| express.js:139:16:139:37 | '/u' + ... ms.user |
| express.js:139:23:139:37 | req.params.user |
| node.js:6:7:6:52 | target |
| node.js:6:16:6:39 | url.par ... , true) |
| node.js:6:16:6:45 | url.par ... ).query |
@@ -66,10 +60,6 @@ edges
| express.js:134:22:134:36 | req.params.user | express.js:134:16:134:36 | '/' + r ... ms.user |
| express.js:135:23:135:37 | req.params.user | express.js:135:16:135:37 | '//' + ... ms.user |
| express.js:136:22:136:36 | req.params.user | express.js:136:16:136:36 | 'u' + r ... ms.user |
| express.js:138:22:138:45 | ('/u' + ... s.user) | express.js:138:16:138:45 | '/' + ( ... s.user) |
| express.js:138:23:138:44 | '/u' + ... ms.user | express.js:138:22:138:45 | ('/u' + ... s.user) |
| express.js:138:30:138:44 | req.params.user | express.js:138:23:138:44 | '/u' + ... ms.user |
| express.js:139:23:139:37 | req.params.user | express.js:139:16:139:37 | '/u' + ... ms.user |
| node.js:6:7:6:52 | target | node.js:7:34:7:39 | target |
| node.js:6:16:6:39 | url.par ... , true) | node.js:6:16:6:45 | url.par ... ).query |
| node.js:6:16:6:45 | url.par ... ).query | node.js:6:16:6:52 | url.par ... .target |
@@ -105,8 +95,6 @@ edges
| express.js:134:16:134:36 | '/' + r ... ms.user | express.js:134:22:134:36 | req.params.user | express.js:134:16:134:36 | '/' + r ... ms.user | Untrusted URL redirection due to $@. | express.js:134:22:134:36 | req.params.user | user-provided value |
| express.js:135:16:135:37 | '//' + ... ms.user | express.js:135:23:135:37 | req.params.user | express.js:135:16:135:37 | '//' + ... ms.user | Untrusted URL redirection due to $@. | express.js:135:23:135:37 | req.params.user | user-provided value |
| express.js:136:16:136:36 | 'u' + r ... ms.user | express.js:136:22:136:36 | req.params.user | express.js:136:16:136:36 | 'u' + r ... ms.user | Untrusted URL redirection due to $@. | express.js:136:22:136:36 | req.params.user | user-provided value |
| express.js:138:16:138:45 | '/' + ( ... s.user) | express.js:138:30:138:44 | req.params.user | express.js:138:16:138:45 | '/' + ( ... s.user) | Untrusted URL redirection due to $@. | express.js:138:30:138:44 | req.params.user | user-provided value |
| express.js:139:16:139:37 | '/u' + ... ms.user | express.js:139:23:139:37 | req.params.user | express.js:139:16:139:37 | '/u' + ... ms.user | Untrusted URL redirection due to $@. | express.js:139:23:139:37 | req.params.user | user-provided value |
| node.js:7:34:7:39 | target | node.js:6:26:6:32 | req.url | node.js:7:34:7:39 | target | Untrusted URL redirection due to $@. | node.js:6:26:6:32 | req.url | user-provided value |
| node.js:15:34:15:45 | '/' + target | node.js:11:26:11:32 | req.url | node.js:15:34:15:45 | '/' + target | Untrusted URL redirection due to $@. | node.js:11:26:11:32 | req.url | user-provided value |
| node.js:32:34:32:55 | target ... =" + me | node.js:29:26:29:32 | req.url | node.js:32:34:32:55 | target ... =" + me | Untrusted URL redirection due to $@. | node.js:29:26:29:32 | req.url | user-provided value |

View File

@@ -135,6 +135,6 @@ app.get('/redirect/:user', function(req, res) {
res.redirect('//' + req.params.user); // BAD - could go to //evil.com
res.redirect('u' + req.params.user); // BAD - could go to u.evil.com
res.redirect('/' + ('/u' + req.params.user)); // BAD - could go to //u.evil.com
res.redirect('/u' + req.params.user); // GOOD - but flagged anyway
res.redirect('/' + ('/u' + req.params.user)); // BAD - could go to //u.evil.com, but not flagged
res.redirect('/u' + req.params.user); // GOOD
});

View File

@@ -0,0 +1,4 @@
<html>
<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
<script>42</script>
</html>

View File

@@ -1,4 +1,5 @@
| AutoRest.js:0:0:0:0 | AutoRest.js | generated |
| ManyElementsOnLine.html:0:0:0:0 | ManyElementsOnLine.html | generated |
| ai.1.2.3-build0123.js:0:0:0:0 | ai.1.2.3-build0123.js | library |
| bundle-directive.js:0:0:0:0 | bundle-directive.js | generated |
| data.js:0:0:0:0 | data.js | generated |

View File

@@ -0,0 +1,4 @@
<html>
<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
<script>42</script>
</html>