mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
JS: Only extract local vars in TemplateTopLevel
Angular template expressions cannot refer to global variables, any unqualified identifier is a reference to a property provided by the component. We extract them as implicitly declared local variables which the QL model can then connect with data flow steps.
This commit is contained in:
@@ -704,7 +704,10 @@ public class ASTExtractor {
|
|||||||
+ locationManager.getStartLine()
|
+ locationManager.getStartLine()
|
||||||
+ ","
|
+ ","
|
||||||
+ locationManager.getStartColumn());
|
+ locationManager.getStartColumn());
|
||||||
scopeManager.enterScope(ScopeKind.module, moduleScopeKey, toplevelLabel);
|
Scope moduleScope = scopeManager.enterScope(ScopeKind.module, moduleScopeKey, toplevelLabel);
|
||||||
|
if (sourceType.hasNoGlobalScope()) {
|
||||||
|
scopeManager.setImplicitVariableScope(moduleScope);
|
||||||
|
}
|
||||||
scopeManager.addVariables(
|
scopeManager.addVariables(
|
||||||
sourceType.getPredefinedLocals(platform, locationManager.getSourceFileExtension()));
|
sourceType.getPredefinedLocals(platform, locationManager.getSourceFileExtension()));
|
||||||
trapwriter.addTuple("is_module", toplevelLabel);
|
trapwriter.addTuple("is_module", toplevelLabel);
|
||||||
|
|||||||
@@ -113,6 +113,14 @@ public class ExtractorConfig {
|
|||||||
return this != SCRIPT;
|
return this != SCRIPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this source type cannot access the global scope directly, and undeclared
|
||||||
|
* variables are implicitly declared in its local scope. Implies {@link #hasLocalScope()}.
|
||||||
|
*/
|
||||||
|
public boolean hasNoGlobalScope() {
|
||||||
|
return this == ANGULAR_TEMPLATE;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns true if this source is implicitly in strict mode. */
|
/** Returns true if this source is implicitly in strict mode. */
|
||||||
public boolean isStrictMode() {
|
public boolean isStrictMode() {
|
||||||
return this == MODULE;
|
return this == MODULE;
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class HTMLExtractor implements IExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
extractSnippet(
|
extractSnippet(
|
||||||
TopLevelKind.eventHandler,
|
TopLevelKind.angularTemplate,
|
||||||
config.withSourceType(SourceType.ANGULAR_TEMPLATE),
|
config.withSourceType(SourceType.ANGULAR_TEMPLATE),
|
||||||
scopeManager,
|
scopeManager,
|
||||||
textualExtractor,
|
textualExtractor,
|
||||||
|
|||||||
@@ -102,11 +102,21 @@ public class ScopeManager {
|
|||||||
private final Scope toplevelScope;
|
private final Scope toplevelScope;
|
||||||
private final ECMAVersion ecmaVersion;
|
private final ECMAVersion ecmaVersion;
|
||||||
private final Set<String> implicitGlobals = new LinkedHashSet<String>();
|
private final Set<String> implicitGlobals = new LinkedHashSet<String>();
|
||||||
|
private Scope implicitVariableScope;
|
||||||
|
|
||||||
public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion) {
|
public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion) {
|
||||||
this.trapWriter = trapWriter;
|
this.trapWriter = trapWriter;
|
||||||
this.toplevelScope = enterScope(ScopeKind.global, trapWriter.globalID("global_scope"), null);
|
this.toplevelScope = enterScope(ScopeKind.global, trapWriter.globalID("global_scope"), null);
|
||||||
this.ecmaVersion = ecmaVersion;
|
this.ecmaVersion = ecmaVersion;
|
||||||
|
this.implicitVariableScope = toplevelScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scope in which to declare variables that are referenced without
|
||||||
|
* being declared. This defaults to the global scope.
|
||||||
|
*/
|
||||||
|
public void setImplicitVariableScope(Scope implicitVariableScope) {
|
||||||
|
this.implicitVariableScope = implicitVariableScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,12 +203,12 @@ public class ScopeManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the label for a given variable in the current scope; if it cannot be found, add it to the
|
* Get the label for a given variable in the current scope; if it cannot be found, add it to the
|
||||||
* global scope.
|
* implicit variable scope (usually the global scope).
|
||||||
*/
|
*/
|
||||||
public Label getVarKey(String name) {
|
public Label getVarKey(String name) {
|
||||||
Label lbl = curScope.lookupVariable(name);
|
Label lbl = curScope.lookupVariable(name);
|
||||||
if (lbl == null) {
|
if (lbl == null) {
|
||||||
lbl = addVariable(name, toplevelScope);
|
lbl = addVariable(name, implicitVariableScope);
|
||||||
implicitGlobals.add(name);
|
implicitGlobals.add(name);
|
||||||
}
|
}
|
||||||
return lbl;
|
return lbl;
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ public enum TopLevelKind {
|
|||||||
script(0),
|
script(0),
|
||||||
inlineScript(1),
|
inlineScript(1),
|
||||||
eventHandler(2),
|
eventHandler(2),
|
||||||
javascriptUrl(3);
|
javascriptUrl(3),
|
||||||
|
angularTemplate(4);
|
||||||
|
|
||||||
private int value;
|
private int value;
|
||||||
|
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ class InlineScript extends @inline_script, Script { }
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class CodeInAttribute extends TopLevel {
|
class CodeInAttribute extends TopLevel {
|
||||||
CodeInAttribute() { this instanceof @event_handler or this instanceof @javascript_url }
|
CodeInAttribute() { this instanceof @event_handler or this instanceof @javascript_url or this instanceof @angular_template_toplevel }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -309,7 +309,8 @@ module SourceNode {
|
|||||||
astNode instanceof ImportSpecifier or
|
astNode instanceof ImportSpecifier or
|
||||||
astNode instanceof ImportMetaExpr or
|
astNode instanceof ImportMetaExpr or
|
||||||
astNode instanceof TaggedTemplateExpr or
|
astNode instanceof TaggedTemplateExpr or
|
||||||
astNode instanceof Angular2::PipeRefExpr
|
astNode instanceof Angular2::PipeRefExpr or
|
||||||
|
astNode instanceof Angular2::TemplateVarRefExpr
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
DataFlow::parameterNode(this, _)
|
DataFlow::parameterNode(this, _)
|
||||||
|
|||||||
@@ -307,6 +307,11 @@ class AnalyzedNegativeConditionGuard extends AnalyzedRefinement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Holds if `v` is a variable in an Angular template. */
|
||||||
|
private predicate isAngularTemplateVariable(LocalVariable v) {
|
||||||
|
v = any(Angular2::TemplateTopLevel tl).getScope().getAVariable()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the abstract value representing the initial value of variable `v`.
|
* Gets the abstract value representing the initial value of variable `v`.
|
||||||
*
|
*
|
||||||
@@ -325,7 +330,10 @@ private AbstractValue getImplicitInitValue(LocalVariable v) {
|
|||||||
then
|
then
|
||||||
// model hoisting
|
// model hoisting
|
||||||
result = TAbstractFunction(getAFunDecl(v))
|
result = TAbstractFunction(getAFunDecl(v))
|
||||||
else result = TAbstractUndefined()
|
else
|
||||||
|
if isAngularTemplateVariable(v)
|
||||||
|
then result = TIndefiniteAbstractValue("heap")
|
||||||
|
else result = TAbstractUndefined()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -241,6 +241,33 @@ module Angular2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to a variable in a template expression, corresponding
|
||||||
|
* to a property on the component class.
|
||||||
|
*/
|
||||||
|
class TemplateVarRefExpr extends Expr {
|
||||||
|
TemplateVarRefExpr() {
|
||||||
|
this = any(TemplateTopLevel tl).getScope().getAVariable().getAnAccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The top-level containing an Angular expression. */
|
||||||
|
class TemplateTopLevel extends TopLevel, @angular_template_toplevel {
|
||||||
|
/** Gets the expression in this top-level. */
|
||||||
|
Expr getExpression() {
|
||||||
|
result = getChildStmt(0).(ExprStmt).getExpr()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the data flow node representing the initialization of the given variable in this scope. */
|
||||||
|
DataFlow::Node getVariableInit(string name) {
|
||||||
|
result = DataFlow::ssaDefinitionNode(SSA::implicitInit(getScope().getVariable(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFlow::SourceNode getAVariableUse(string name) {
|
||||||
|
result = getScope().getVariable(name).getAnAccess().flow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** The RHS of a `templateUrl` property, seen as a path expression. */
|
/** The RHS of a `templateUrl` property, seen as a path expression. */
|
||||||
private class TemplateUrlPath extends PathExpr {
|
private class TemplateUrlPath extends PathExpr {
|
||||||
TemplateUrlPath() {
|
TemplateUrlPath() {
|
||||||
@@ -264,18 +291,8 @@ module Angular2 {
|
|||||||
attrib.getName().matches("*ng%")
|
attrib.getName().matches("*ng%")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a global variable access to `name` within the given attribute.
|
|
||||||
*/
|
|
||||||
pragma[noinline]
|
|
||||||
private GlobalVarAccess getAGlobalVarAccessInAttribute(CodeInAttribute code, string name) {
|
|
||||||
exists(ComponentClass cls) and // do not materialize for non-Angular codebases
|
|
||||||
result.getTopLevel() = code and
|
|
||||||
result.getName() = name
|
|
||||||
}
|
|
||||||
|
|
||||||
private DataFlow::Node getAttributeValueAsNode(HTML::Attribute attrib) {
|
private DataFlow::Node getAttributeValueAsNode(HTML::Attribute attrib) {
|
||||||
result = attrib.getCodeInAttribute().getChildStmt(0).(ExprStmt).getExpr().flow()
|
result = attrib.getCodeInAttribute().(TemplateTopLevel).getExpression().flow()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -361,11 +378,7 @@ module Angular2 {
|
|||||||
* Gets an access to the variable `name` in the template body.
|
* Gets an access to the variable `name` in the template body.
|
||||||
*/
|
*/
|
||||||
DataFlow::Node getATemplateVarAccess(string name) {
|
DataFlow::Node getATemplateVarAccess(string name) {
|
||||||
exists(HTML::Attribute attrib |
|
result = getATemplateElement().getAnAttribute().getCodeInAttribute().(TemplateTopLevel).getAVariableUse(name)
|
||||||
attrib = getATemplateElement().getAnAttribute() and
|
|
||||||
isAngularExpressionAttribute(attrib) and
|
|
||||||
result = getAGlobalVarAccessInAttribute(attrib.getCodeInAttribute(), name).flow()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,11 +463,7 @@ module Angular2 {
|
|||||||
|
|
||||||
/** Gets a reference to the iterator variable. */
|
/** Gets a reference to the iterator variable. */
|
||||||
DataFlow::Node getAnIteratorAccess() {
|
DataFlow::Node getAnIteratorAccess() {
|
||||||
exists(HTML::Attribute attrib |
|
result = getAnElementInScope().getAnAttribute().getCodeInAttribute().(TemplateTopLevel).getAVariableUse(getIteratorName())
|
||||||
attrib = getAnElementInScope().getAnAttribute() and
|
|
||||||
isAngularExpressionAttribute(attrib) and
|
|
||||||
result = getAGlobalVarAccessInAttribute(attrib.getCodeInAttribute(), getIteratorName()).flow()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,4 +483,13 @@ module Angular2 {
|
|||||||
succ = attrib.getAnIteratorAccess()
|
succ = attrib.getAnIteratorAccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class AnyCastStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||||
|
AnyCastStep() { this = any(TemplateTopLevel tl).getAVariableUse("$any").getACall() }
|
||||||
|
|
||||||
|
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
|
pred = getArgument(0) and
|
||||||
|
succ = this
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,8 @@ case @toplevel.kind of
|
|||||||
0 = @script
|
0 = @script
|
||||||
| 1 = @inline_script
|
| 1 = @inline_script
|
||||||
| 2 = @event_handler
|
| 2 = @event_handler
|
||||||
| 3 = @javascript_url;
|
| 3 = @javascript_url
|
||||||
|
| 4 = @angular_template_toplevel;
|
||||||
|
|
||||||
is_module (int tl: @toplevel ref);
|
is_module (int tl: @toplevel ref);
|
||||||
is_nodejs (int tl: @toplevel ref);
|
is_nodejs (int tl: @toplevel ref);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
[sink3]="taint | unknownPipe:'safe'"
|
[sink3]="taint | unknownPipe:'safe'"
|
||||||
[sink4]="taint | testPipe:'safe'"
|
[sink4]="taint | testPipe:'safe'"
|
||||||
[sink5]="42 | testPipe:taint"
|
[sink5]="42 | testPipe:taint"
|
||||||
|
(someEvent)="methodOnComponent(taint)"
|
||||||
></sink-component>
|
></sink-component>
|
||||||
|
|
||||||
<div *ngFor="let element of taintedArray">
|
<div *ngFor="let element of taintedArray">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "source-component",
|
selector: "source-component",
|
||||||
@@ -9,9 +10,13 @@ export class Source {
|
|||||||
taintedArray: string[];
|
taintedArray: string[];
|
||||||
safeArray: string[];
|
safeArray: string[];
|
||||||
|
|
||||||
constructor() {
|
constructor(private sanitizer: DomSanitizer) {
|
||||||
this.taint = source();
|
this.taint = source();
|
||||||
this.taintedArray = [...source()];
|
this.taintedArray = [...source()];
|
||||||
this.safeArray = ['a', 'b'];
|
this.safeArray = ['a', 'b'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
methodOnComponent(x) {
|
||||||
|
this.sanitizer.bypassSecurityTrustHtml(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ pipeClassRef
|
|||||||
| TestPipe.ts:4:8:9:1 | class T ... ;\\n }\\n} | source.component.html:6:19:6:26 | testPipe |
|
| TestPipe.ts:4:8:9:1 | class T ... ;\\n }\\n} | source.component.html:6:19:6:26 | testPipe |
|
||||||
taintFlow
|
taintFlow
|
||||||
| inline.component.ts:15:22:15:29 | source() | sink.component.ts:26:48:26:57 | this.sink7 |
|
| inline.component.ts:15:22:15:29 | source() | sink.component.ts:26:48:26:57 | this.sink7 |
|
||||||
| source.component.ts:13:22:13:29 | source() | sink.component.ts:20:48:20:57 | this.sink1 |
|
| source.component.ts:14:22:14:29 | source() | sink.component.ts:20:48:20:57 | this.sink1 |
|
||||||
| source.component.ts:13:22:13:29 | source() | sink.component.ts:23:48:23:57 | this.sink4 |
|
| source.component.ts:14:22:14:29 | source() | sink.component.ts:23:48:23:57 | this.sink4 |
|
||||||
| source.component.ts:13:22:13:29 | source() | sink.component.ts:24:48:24:57 | this.sink5 |
|
| source.component.ts:14:22:14:29 | source() | sink.component.ts:24:48:24:57 | this.sink5 |
|
||||||
| source.component.ts:13:22:13:29 | source() | sink.component.ts:25:48:25:57 | this.sink6 |
|
| source.component.ts:14:22:14:29 | source() | sink.component.ts:25:48:25:57 | this.sink6 |
|
||||||
| source.component.ts:14:33:14:40 | source() | sink.component.ts:20:48:20:57 | this.sink1 |
|
| source.component.ts:14:22:14:29 | source() | source.component.ts:20:48:20:48 | x |
|
||||||
|
| source.component.ts:15:33:15:40 | source() | sink.component.ts:20:48:20:57 | this.sink1 |
|
||||||
|
|||||||
Reference in New Issue
Block a user