Merge branch 'DefaultTaintTracking-argv' into dataflow-indirect-args

This commit is contained in:
Jonas Jensen
2020-03-26 20:47:50 +01:00
54 changed files with 769 additions and 232 deletions

View File

@@ -47,6 +47,8 @@
- [request](https://www.npmjs.com/package/request)
- [rimraf](https://www.npmjs.com/package/rimraf)
- [send](https://www.npmjs.com/package/send)
- [SockJS](https://www.npmjs.com/package/sockjs)
- [SockJS-client](https://www.npmjs.com/package/sockjs-client)
- [typeahead.js](https://www.npmjs.com/package/typeahead.js)
- [vinyl-fs](https://www.npmjs.com/package/vinyl-fs)
- [write-file-atomic](https://www.npmjs.com/package/write-file-atomic)

View File

@@ -133,10 +133,16 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
*/
Type getUnspecifiedType() { result = getType().getUnspecifiedType() }
/** Gets the nth parameter of this function. */
/**
* Gets the nth parameter of this function. There is no result for the
* implicit `this` parameter, and there is no `...` varargs pseudo-parameter.
*/
Parameter getParameter(int n) { params(unresolveElement(result), underlyingElement(this), n, _) }
/** Gets a parameter of this function. */
/**
* Gets a parameter of this function. There is no result for the implicit
* `this` parameter, and there is no `...` varargs pseudo-parameter.
*/
Parameter getAParameter() { params(unresolveElement(result), underlyingElement(this), _, _) }
/**

View File

@@ -60,7 +60,14 @@ private DataFlow::Node getNodeForSource(Expr source) {
(
result = DataFlow::exprNode(source)
or
result = DataFlow::definitionByReferenceNode(source)
// Some of the sources in `isUserInput` are intended to match the value of
// an expression, while others (those modeled below) are intended to match
// the taint that propagates out of an argument, like the `char *` argument
// to `gets`. It's impossible here to tell which is which, but the "access
// to argv" source is definitely not intended to match an output argument,
// and it causes false positives if we let it.
result = DataFlow::definitionByReferenceNode(source) and
not argv(source.(VariableAccess).getTarget())
)
}

View File

@@ -16,6 +16,8 @@ class IRConfiguration extends TIRConfiguration {
* Holds if IR should be created for function `func`. By default, holds for all functions.
*/
predicate shouldCreateIRForFunction(Language::Function func) { any() }
predicate shouldEvaluateDebugStringsForFunction(Language::Function func) { any() }
}
private newtype TIREscapeAnalysisConfiguration = MkIREscapeAnalysisConfiguration()

View File

@@ -27,6 +27,9 @@ class IRBlockBase extends TIRBlock {
* by debugging and printing code only.
*/
int getDisplayIndex() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
) and
this =
rank[result + 1](IRBlock funcBlock |
funcBlock.getEnclosingFunction() = getEnclosingFunction()

View File

@@ -15,6 +15,9 @@ private import Imports::OperandTag
* `File` and line number. Used for assigning register names when printing IR.
*/
private Instruction getAnInstructionAtLine(IRFunction irFunc, Language::File file, int line) {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(irFunc.getFunction())
) and
exists(Language::Location location |
irFunc = result.getEnclosingIRFunction() and
location = result.getLocation() and
@@ -39,6 +42,12 @@ class Instruction extends Construction::TInstruction {
result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
)
}
/**
* Gets a string describing the operation of this instruction. This includes
* the opcode and the immediate value, if any. For example:
@@ -46,6 +55,7 @@ class Instruction extends Construction::TInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
shouldGenerateDumpStrings() and
if exists(getImmediateString())
then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
else result = getOperationPrefix() + getOpcode().toString()
@@ -57,10 +67,12 @@ class Instruction extends Construction::TInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
shouldGenerateDumpStrings() and
if getResultIRType() instanceof IRVoidType
then result = "v"
else
@@ -74,6 +86,7 @@ class Instruction extends Construction::TInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -87,6 +100,7 @@ class Instruction extends Construction::TInstruction {
}
private int getLineRank() {
shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
@@ -105,6 +119,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1`
*/
string getResultId() {
shouldGenerateDumpStrings() and
result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
}
@@ -116,6 +131,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
shouldGenerateDumpStrings() and
result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
}
@@ -126,6 +142,7 @@ class Instruction extends Construction::TInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
shouldGenerateDumpStrings() and
result =
concat(Operand operand |
operand = getAnOperand()

View File

@@ -18,19 +18,19 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
predicate shouldPrintFunction(Language::Function func) { any() }
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
/**
* Override of `IRConfiguration` to only create IR for the functions that are to be dumped.
* Override of `IRConfiguration` to only evaluate debug strings for the functions that are to be dumped.
*/
private class FilteredIRConfiguration extends IRConfiguration {
override predicate shouldCreateIRForFunction(Language::Function func) {
override predicate shouldEvaluateDebugStringsForFunction(Language::Function func) {
shouldPrintFunction(func)
}
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
private string getAdditionalInstructionProperty(Instruction instr, string key) {
exists(IRPropertyProvider provider | result = provider.getInstructionProperty(instr, key))
}

View File

@@ -1,2 +1,3 @@
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
import SSAConstruction as Construction
import semmle.code.cpp.ir.implementation.IRConfiguration as IRConfiguration

View File

@@ -27,6 +27,9 @@ class IRBlockBase extends TIRBlock {
* by debugging and printing code only.
*/
int getDisplayIndex() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
) and
this =
rank[result + 1](IRBlock funcBlock |
funcBlock.getEnclosingFunction() = getEnclosingFunction()

View File

@@ -15,6 +15,9 @@ private import Imports::OperandTag
* `File` and line number. Used for assigning register names when printing IR.
*/
private Instruction getAnInstructionAtLine(IRFunction irFunc, Language::File file, int line) {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(irFunc.getFunction())
) and
exists(Language::Location location |
irFunc = result.getEnclosingIRFunction() and
location = result.getLocation() and
@@ -39,6 +42,12 @@ class Instruction extends Construction::TInstruction {
result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
)
}
/**
* Gets a string describing the operation of this instruction. This includes
* the opcode and the immediate value, if any. For example:
@@ -46,6 +55,7 @@ class Instruction extends Construction::TInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
shouldGenerateDumpStrings() and
if exists(getImmediateString())
then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
else result = getOperationPrefix() + getOpcode().toString()
@@ -57,10 +67,12 @@ class Instruction extends Construction::TInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
shouldGenerateDumpStrings() and
if getResultIRType() instanceof IRVoidType
then result = "v"
else
@@ -74,6 +86,7 @@ class Instruction extends Construction::TInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -87,6 +100,7 @@ class Instruction extends Construction::TInstruction {
}
private int getLineRank() {
shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
@@ -105,6 +119,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1`
*/
string getResultId() {
shouldGenerateDumpStrings() and
result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
}
@@ -116,6 +131,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
shouldGenerateDumpStrings() and
result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
}
@@ -126,6 +142,7 @@ class Instruction extends Construction::TInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
shouldGenerateDumpStrings() and
result =
concat(Operand operand |
operand = getAnOperand()

View File

@@ -18,19 +18,19 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
predicate shouldPrintFunction(Language::Function func) { any() }
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
/**
* Override of `IRConfiguration` to only create IR for the functions that are to be dumped.
* Override of `IRConfiguration` to only evaluate debug strings for the functions that are to be dumped.
*/
private class FilteredIRConfiguration extends IRConfiguration {
override predicate shouldCreateIRForFunction(Language::Function func) {
override predicate shouldEvaluateDebugStringsForFunction(Language::Function func) {
shouldPrintFunction(func)
}
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
private string getAdditionalInstructionProperty(Instruction instr, string key) {
exists(IRPropertyProvider provider | result = provider.getInstructionProperty(instr, key))
}

View File

@@ -1,2 +1,3 @@
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
import IRConstruction as Construction
import semmle.code.cpp.ir.implementation.IRConfiguration as IRConfiguration

View File

@@ -27,6 +27,9 @@ class IRBlockBase extends TIRBlock {
* by debugging and printing code only.
*/
int getDisplayIndex() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
) and
this =
rank[result + 1](IRBlock funcBlock |
funcBlock.getEnclosingFunction() = getEnclosingFunction()

View File

@@ -15,6 +15,9 @@ private import Imports::OperandTag
* `File` and line number. Used for assigning register names when printing IR.
*/
private Instruction getAnInstructionAtLine(IRFunction irFunc, Language::File file, int line) {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(irFunc.getFunction())
) and
exists(Language::Location location |
irFunc = result.getEnclosingIRFunction() and
location = result.getLocation() and
@@ -39,6 +42,12 @@ class Instruction extends Construction::TInstruction {
result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
)
}
/**
* Gets a string describing the operation of this instruction. This includes
* the opcode and the immediate value, if any. For example:
@@ -46,6 +55,7 @@ class Instruction extends Construction::TInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
shouldGenerateDumpStrings() and
if exists(getImmediateString())
then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
else result = getOperationPrefix() + getOpcode().toString()
@@ -57,10 +67,12 @@ class Instruction extends Construction::TInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
shouldGenerateDumpStrings() and
if getResultIRType() instanceof IRVoidType
then result = "v"
else
@@ -74,6 +86,7 @@ class Instruction extends Construction::TInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -87,6 +100,7 @@ class Instruction extends Construction::TInstruction {
}
private int getLineRank() {
shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
@@ -105,6 +119,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1`
*/
string getResultId() {
shouldGenerateDumpStrings() and
result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
}
@@ -116,6 +131,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
shouldGenerateDumpStrings() and
result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
}
@@ -126,6 +142,7 @@ class Instruction extends Construction::TInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
shouldGenerateDumpStrings() and
result =
concat(Operand operand |
operand = getAnOperand()

View File

@@ -18,19 +18,19 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
predicate shouldPrintFunction(Language::Function func) { any() }
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
/**
* Override of `IRConfiguration` to only create IR for the functions that are to be dumped.
* Override of `IRConfiguration` to only evaluate debug strings for the functions that are to be dumped.
*/
private class FilteredIRConfiguration extends IRConfiguration {
override predicate shouldCreateIRForFunction(Language::Function func) {
override predicate shouldEvaluateDebugStringsForFunction(Language::Function func) {
shouldPrintFunction(func)
}
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
private string getAdditionalInstructionProperty(Instruction instr, string key) {
exists(IRPropertyProvider provider | result = provider.getInstructionProperty(instr, key))
}

View File

@@ -1,2 +1,3 @@
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
import SSAConstruction as Construction
import semmle.code.cpp.ir.implementation.IRConfiguration as IRConfiguration

View File

@@ -1,19 +1 @@
/**
* Module used to configure the IR generation process.
*/
import csharp
private newtype TIRConfiguration = MkIRConfiguration()
/**
* The query can extend this class to control which functions have IR generated for them.
*/
class IRConfiguration extends TIRConfiguration {
string toString() { result = "IRConfiguration" }
/**
* Holds if IR should be created for callable `callable`. By default, holds for all callables.
*/
predicate shouldCreateIRForFunction(Callable callable) { any() }
}
import implementation.IRConfiguration

View File

@@ -16,6 +16,8 @@ class IRConfiguration extends TIRConfiguration {
* Holds if IR should be created for function `func`. By default, holds for all functions.
*/
predicate shouldCreateIRForFunction(Language::Function func) { any() }
predicate shouldEvaluateDebugStringsForFunction(Language::Function func) { any() }
}
private newtype TIREscapeAnalysisConfiguration = MkIREscapeAnalysisConfiguration()

View File

@@ -27,6 +27,9 @@ class IRBlockBase extends TIRBlock {
* by debugging and printing code only.
*/
int getDisplayIndex() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
) and
this =
rank[result + 1](IRBlock funcBlock |
funcBlock.getEnclosingFunction() = getEnclosingFunction()

View File

@@ -15,6 +15,9 @@ private import Imports::OperandTag
* `File` and line number. Used for assigning register names when printing IR.
*/
private Instruction getAnInstructionAtLine(IRFunction irFunc, Language::File file, int line) {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(irFunc.getFunction())
) and
exists(Language::Location location |
irFunc = result.getEnclosingIRFunction() and
location = result.getLocation() and
@@ -39,6 +42,12 @@ class Instruction extends Construction::TInstruction {
result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
)
}
/**
* Gets a string describing the operation of this instruction. This includes
* the opcode and the immediate value, if any. For example:
@@ -46,6 +55,7 @@ class Instruction extends Construction::TInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
shouldGenerateDumpStrings() and
if exists(getImmediateString())
then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
else result = getOperationPrefix() + getOpcode().toString()
@@ -57,10 +67,12 @@ class Instruction extends Construction::TInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
shouldGenerateDumpStrings() and
if getResultIRType() instanceof IRVoidType
then result = "v"
else
@@ -74,6 +86,7 @@ class Instruction extends Construction::TInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -87,6 +100,7 @@ class Instruction extends Construction::TInstruction {
}
private int getLineRank() {
shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
@@ -105,6 +119,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1`
*/
string getResultId() {
shouldGenerateDumpStrings() and
result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
}
@@ -116,6 +131,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
shouldGenerateDumpStrings() and
result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
}
@@ -126,6 +142,7 @@ class Instruction extends Construction::TInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
shouldGenerateDumpStrings() and
result =
concat(Operand operand |
operand = getAnOperand()

View File

@@ -18,19 +18,19 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
predicate shouldPrintFunction(Language::Function func) { any() }
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
/**
* Override of `IRConfiguration` to only create IR for the functions that are to be dumped.
* Override of `IRConfiguration` to only evaluate debug strings for the functions that are to be dumped.
*/
private class FilteredIRConfiguration extends IRConfiguration {
override predicate shouldCreateIRForFunction(Language::Function func) {
override predicate shouldEvaluateDebugStringsForFunction(Language::Function func) {
shouldPrintFunction(func)
}
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
private string getAdditionalInstructionProperty(Instruction instr, string key) {
exists(IRPropertyProvider provider | result = provider.getInstructionProperty(instr, key))
}

View File

@@ -1,2 +1,3 @@
import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language
import IRConstruction as Construction
import semmle.code.csharp.ir.implementation.IRConfiguration as IRConfiguration

View File

@@ -27,6 +27,9 @@ class IRBlockBase extends TIRBlock {
* by debugging and printing code only.
*/
int getDisplayIndex() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
) and
this =
rank[result + 1](IRBlock funcBlock |
funcBlock.getEnclosingFunction() = getEnclosingFunction()

View File

@@ -15,6 +15,9 @@ private import Imports::OperandTag
* `File` and line number. Used for assigning register names when printing IR.
*/
private Instruction getAnInstructionAtLine(IRFunction irFunc, Language::File file, int line) {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(irFunc.getFunction())
) and
exists(Language::Location location |
irFunc = result.getEnclosingIRFunction() and
location = result.getLocation() and
@@ -39,6 +42,12 @@ class Instruction extends Construction::TInstruction {
result = getResultString() + " = " + getOperationString() + " " + getOperandsString()
}
private predicate shouldGenerateDumpStrings() {
exists(IRConfiguration::IRConfiguration config |
config.shouldEvaluateDebugStringsForFunction(this.getEnclosingFunction())
)
}
/**
* Gets a string describing the operation of this instruction. This includes
* the opcode and the immediate value, if any. For example:
@@ -46,6 +55,7 @@ class Instruction extends Construction::TInstruction {
* VariableAddress[x]
*/
final string getOperationString() {
shouldGenerateDumpStrings() and
if exists(getImmediateString())
then result = getOperationPrefix() + getOpcode().toString() + "[" + getImmediateString() + "]"
else result = getOperationPrefix() + getOpcode().toString()
@@ -57,10 +67,12 @@ class Instruction extends Construction::TInstruction {
string getImmediateString() { none() }
private string getOperationPrefix() {
shouldGenerateDumpStrings() and
if this instanceof SideEffectInstruction then result = "^" else result = ""
}
private string getResultPrefix() {
shouldGenerateDumpStrings() and
if getResultIRType() instanceof IRVoidType
then result = "v"
else
@@ -74,6 +86,7 @@ class Instruction extends Construction::TInstruction {
* used by debugging and printing code only.
*/
int getDisplayIndexInBlock() {
shouldGenerateDumpStrings() and
exists(IRBlock block |
this = block.getInstruction(result)
or
@@ -87,6 +100,7 @@ class Instruction extends Construction::TInstruction {
}
private int getLineRank() {
shouldGenerateDumpStrings() and
this =
rank[result](Instruction instr |
instr =
@@ -105,6 +119,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1`
*/
string getResultId() {
shouldGenerateDumpStrings() and
result = getResultPrefix() + getAST().getLocation().getStartLine() + "_" + getLineRank()
}
@@ -116,6 +131,7 @@ class Instruction extends Construction::TInstruction {
* Example: `r1_1(int*)`
*/
final string getResultString() {
shouldGenerateDumpStrings() and
result = getResultId() + "(" + getResultLanguageType().getDumpString() + ")"
}
@@ -126,6 +142,7 @@ class Instruction extends Construction::TInstruction {
* Example: `func:r3_4, this:r3_5`
*/
string getOperandsString() {
shouldGenerateDumpStrings() and
result =
concat(Operand operand |
operand = getAnOperand()

View File

@@ -18,19 +18,19 @@ class PrintIRConfiguration extends TPrintIRConfiguration {
predicate shouldPrintFunction(Language::Function func) { any() }
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
/**
* Override of `IRConfiguration` to only create IR for the functions that are to be dumped.
* Override of `IRConfiguration` to only evaluate debug strings for the functions that are to be dumped.
*/
private class FilteredIRConfiguration extends IRConfiguration {
override predicate shouldCreateIRForFunction(Language::Function func) {
override predicate shouldEvaluateDebugStringsForFunction(Language::Function func) {
shouldPrintFunction(func)
}
}
private predicate shouldPrintFunction(Language::Function func) {
exists(PrintIRConfiguration config | config.shouldPrintFunction(func))
}
private string getAdditionalInstructionProperty(Instruction instr, string key) {
exists(IRPropertyProvider provider | result = provider.getInstructionProperty(instr, key))
}

View File

@@ -1,2 +1,3 @@
import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language
import SSAConstruction as Construction
import semmle.code.csharp.ir.implementation.IRConfiguration as IRConfiguration

View File

@@ -366,6 +366,16 @@ public class E
static int Ex34(string s = null) => s.Length; // BAD (maybe)
static int Ex35(string s = "null") => s.Length; // GOOD
static int Ex36(object o)
{
if (o is string)
{
var s = o as string;
return s.Length; // GOOD (false positive)
}
return -1;
}
}
public static class Extensions

View File

@@ -1231,6 +1231,11 @@
| E.cs:362:18:362:29 | (...) ... | null | E.cs:362:26:362:29 | null | null |
| E.cs:363:13:363:13 | access to local variable x | non-null | E.cs:361:17:361:32 | ... ?? ... | non-null |
| E.cs:363:13:363:13 | access to local variable x | null | E.cs:361:17:361:32 | ... ?? ... | null |
| E.cs:372:13:372:23 | ... is ... | true | E.cs:372:13:372:13 | access to parameter o | non-null |
| E.cs:375:20:375:20 | access to local variable s | empty | E.cs:374:21:374:31 | ... as ... | empty |
| E.cs:375:20:375:20 | access to local variable s | non-empty | E.cs:374:21:374:31 | ... as ... | non-empty |
| E.cs:375:20:375:20 | access to local variable s | non-null | E.cs:374:21:374:31 | ... as ... | non-null |
| E.cs:375:20:375:20 | access to local variable s | null | E.cs:374:21:374:31 | ... as ... | null |
| Forwarding.cs:9:13:9:30 | !... | false | Forwarding.cs:9:14:9:30 | call to method IsNullOrEmpty | true |
| Forwarding.cs:9:13:9:30 | !... | true | Forwarding.cs:9:14:9:30 | call to method IsNullOrEmpty | false |
| Forwarding.cs:9:14:9:14 | access to local variable s | empty | Forwarding.cs:7:20:7:23 | null | empty |

View File

@@ -263,6 +263,7 @@
| E.cs:361:17:361:17 | access to parameter s | E.cs:361:17:361:17 | access to parameter s | null | true |
| E.cs:362:13:362:29 | ... != ... | E.cs:362:13:362:13 | access to local variable x | false | true |
| E.cs:362:13:362:29 | ... != ... | E.cs:362:13:362:13 | access to local variable x | true | false |
| E.cs:372:13:372:23 | ... is ... | E.cs:372:13:372:13 | access to parameter o | true | false |
| Forwarding.cs:9:14:9:30 | call to method IsNullOrEmpty | Forwarding.cs:9:14:9:14 | access to local variable s | false | false |
| Forwarding.cs:14:13:14:32 | call to method IsNotNullOrEmpty | Forwarding.cs:14:13:14:13 | access to local variable s | true | false |
| Forwarding.cs:19:14:19:23 | call to method IsNull | Forwarding.cs:19:14:19:14 | access to local variable s | false | false |

View File

@@ -353,6 +353,8 @@ nodes
| E.cs:349:9:349:9 | access to local variable x |
| E.cs:366:28:366:28 | SSA param(s) |
| E.cs:366:41:366:41 | access to parameter s |
| E.cs:374:17:374:31 | SSA def(s) |
| E.cs:375:20:375:20 | access to local variable s |
| Forwarding.cs:7:16:7:23 | SSA def(s) |
| Forwarding.cs:14:9:17:9 | if (...) ... |
| Forwarding.cs:19:9:22:9 | if (...) ... |
@@ -685,6 +687,7 @@ edges
| E.cs:342:13:342:32 | SSA def(x) | E.cs:343:9:343:9 | access to local variable x |
| E.cs:348:17:348:36 | SSA def(x) | E.cs:349:9:349:9 | access to local variable x |
| E.cs:366:28:366:28 | SSA param(s) | E.cs:366:41:366:41 | access to parameter s |
| E.cs:374:17:374:31 | SSA def(s) | E.cs:375:20:375:20 | access to local variable s |
| Forwarding.cs:7:16:7:23 | SSA def(s) | Forwarding.cs:14:9:17:9 | if (...) ... |
| Forwarding.cs:14:9:17:9 | if (...) ... | Forwarding.cs:19:9:22:9 | if (...) ... |
| Forwarding.cs:19:9:22:9 | if (...) ... | Forwarding.cs:24:9:27:9 | if (...) ... |
@@ -786,6 +789,7 @@ edges
| E.cs:343:9:343:9 | access to local variable x | E.cs:342:13:342:32 | SSA def(x) | E.cs:343:9:343:9 | access to local variable x | Variable $@ may be null here because of $@ assignment. | E.cs:342:13:342:13 | x | x | E.cs:342:13:342:32 | String x = ... | this |
| E.cs:349:9:349:9 | access to local variable x | E.cs:348:17:348:36 | SSA def(x) | E.cs:349:9:349:9 | access to local variable x | Variable $@ may be null here because of $@ assignment. | E.cs:348:17:348:17 | x | x | E.cs:348:17:348:36 | dynamic x = ... | this |
| E.cs:366:41:366:41 | access to parameter s | E.cs:366:28:366:28 | SSA param(s) | E.cs:366:41:366:41 | access to parameter s | Variable $@ may be null here because the parameter has a null default value. | E.cs:366:28:366:28 | s | s | E.cs:366:32:366:35 | null | this |
| E.cs:375:20:375:20 | access to local variable s | E.cs:374:17:374:31 | SSA def(s) | E.cs:375:20:375:20 | access to local variable s | Variable $@ may be null here because of $@ assignment. | E.cs:374:17:374:17 | s | s | E.cs:374:17:374:31 | String s = ... | this |
| GuardedString.cs:35:31:35:31 | access to local variable s | GuardedString.cs:7:16:7:32 | SSA def(s) | GuardedString.cs:35:31:35:31 | access to local variable s | Variable $@ may be null here because of $@ assignment. | GuardedString.cs:7:16:7:16 | s | s | GuardedString.cs:7:16:7:32 | String s = ... | this |
| NullMaybeBad.cs:7:27:7:27 | access to parameter o | NullMaybeBad.cs:13:17:13:20 | null | NullMaybeBad.cs:7:27:7:27 | access to parameter o | Variable $@ may be null here because of $@ null argument. | NullMaybeBad.cs:5:25:5:25 | o | o | NullMaybeBad.cs:13:17:13:20 | null | this |
| StringConcatenation.cs:16:17:16:17 | access to local variable s | StringConcatenation.cs:14:16:14:23 | SSA def(s) | StringConcatenation.cs:16:17:16:17 | access to local variable s | Variable $@ may be null here because of $@ assignment. | StringConcatenation.cs:14:16:14:16 | s | s | StringConcatenation.cs:14:16:14:23 | String s = ... | this |

View File

@@ -113,7 +113,8 @@ private predicate variableCaptureStep(Node node1, ExprNode node2) {
*/
predicate jumpStep(Node node1, Node node2) {
staticFieldStep(node1, node2) or
variableCaptureStep(node1, node2)
variableCaptureStep(node1, node2) or
variableCaptureStep(node1.(PostUpdateNode).getPreUpdateNode(), node2)
}
/**

View File

@@ -0,0 +1,41 @@
public class A {
static class Box {
String elem;
Box(String e) { elem = e; }
void setElem(String e) { elem = e; }
String getElem() { return elem; }
}
String get() {
return null;
}
void f1(int i) {
A a = f2("A", i);
String x = a.get();
}
A f2(String p, int i) {
String s;
if (i == 0) {
s = "B";
} else {
s = "C";
}
Box b1 = new Box("D");
Box b2 = new Box(null);
b2.setElem("E");
A a = new A() {
@Override
String get() {
switch (i) {
case 0: return p;
case 1: return s;
case 2: return b1.getElem();
case 3: return b2.getElem();
}
}
};
return a;
}
}

View File

@@ -0,0 +1,26 @@
| A.java:14:14:14:16 | "A" | A.java:14:14:14:16 | "A" |
| A.java:14:14:14:16 | "A" | A.java:15:16:15:22 | get(...) |
| A.java:14:14:14:16 | "A" | A.java:18:8:18:15 | p |
| A.java:14:14:14:16 | "A" | A.java:32:26:32:26 | p |
| A.java:21:11:21:13 | "B" | A.java:15:16:15:22 | get(...) |
| A.java:21:11:21:13 | "B" | A.java:21:7:21:13 | ...=... |
| A.java:21:11:21:13 | "B" | A.java:21:11:21:13 | "B" |
| A.java:21:11:21:13 | "B" | A.java:33:26:33:26 | s |
| A.java:23:11:23:13 | "C" | A.java:15:16:15:22 | get(...) |
| A.java:23:11:23:13 | "C" | A.java:23:7:23:13 | ...=... |
| A.java:23:11:23:13 | "C" | A.java:23:11:23:13 | "C" |
| A.java:23:11:23:13 | "C" | A.java:33:26:33:26 | s |
| A.java:25:22:25:24 | "D" | A.java:4:9:4:16 | e |
| A.java:25:22:25:24 | "D" | A.java:4:21:4:28 | ...=... |
| A.java:25:22:25:24 | "D" | A.java:4:28:4:28 | e |
| A.java:25:22:25:24 | "D" | A.java:6:31:6:34 | elem |
| A.java:25:22:25:24 | "D" | A.java:15:16:15:22 | get(...) |
| A.java:25:22:25:24 | "D" | A.java:25:22:25:24 | "D" |
| A.java:25:22:25:24 | "D" | A.java:34:26:34:37 | getElem(...) |
| A.java:27:16:27:18 | "E" | A.java:5:18:5:25 | e |
| A.java:27:16:27:18 | "E" | A.java:5:30:5:37 | ...=... |
| A.java:27:16:27:18 | "E" | A.java:5:37:5:37 | e |
| A.java:27:16:27:18 | "E" | A.java:6:31:6:34 | elem |
| A.java:27:16:27:18 | "E" | A.java:15:16:15:22 | get(...) |
| A.java:27:16:27:18 | "E" | A.java:27:16:27:18 | "E" |
| A.java:27:16:27:18 | "E" | A.java:35:26:35:37 | getElem(...) |

View File

@@ -0,0 +1,17 @@
import java
import semmle.code.java.dataflow.DataFlow
import DataFlow
StringLiteral src() { result.getCompilationUnit().fromSource() }
class Conf extends Configuration {
Conf() { this = "qq capture" }
override predicate isSource(Node n) { n.asExpr() = src() }
override predicate isSink(Node n) { any() }
}
from Node src, Node sink, Conf conf
where conf.hasFlow(src, sink)
select src, sink

View File

@@ -1,46 +0,0 @@
/**
* Provides classes for working with [SockJS](http://sockjs.org).
*/
import javascript
/**
* A model of the `SockJS` websocket data handler (https://sockjs.org).
*/
module SockJS {
/**
* Access to user-controlled data object received from websocket
* For example:
* ```
* server.on('connection', function(conn) {
* conn.on('data', function(message) {
* ...
* });
* });
* ```
*/
class SourceFromSocketJS extends RemoteFlowSource {
SourceFromSocketJS() {
exists(
DataFlow::CallNode createServer, DataFlow::CallNode connNode,
DataFlow::CallNode dataHandlerNode
|
createServer = appCreation() and
connNode = createServer.getAMethodCall("on") and
connNode.getArgument(0).getStringValue() = "connection" and
dataHandlerNode = connNode.getCallback(1).getParameter(0).getAMethodCall("on") and
dataHandlerNode.getArgument(0).getStringValue() = "data" and
this = dataHandlerNode.getCallback(1).getParameter(0)
)
}
override string getSourceType() { result = "input from SockJS WebSocket" }
}
/**
* Gets a new SockJS server.
*/
private DataFlow::CallNode appCreation() {
result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer")
}
}

View File

@@ -5,6 +5,7 @@
import Customizations
import semmle.javascript.Aliases
import semmle.javascript.AMD
import semmle.javascript.Arrays
import semmle.javascript.AST
import semmle.javascript.BasicBlocks
import semmle.javascript.Base64

View File

@@ -0,0 +1,264 @@
import javascript
private import semmle.javascript.dataflow.InferredTypes
/**
* Classes and predicates for modelling TaintTracking steps for arrays.
*/
module ArrayTaintTracking {
/**
* A taint propagating data flow edge caused by the builtin array functions.
*/
private class ArrayFunctionTaintStep extends TaintTracking::AdditionalTaintStep {
DataFlow::CallNode call;
ArrayFunctionTaintStep() { this = call }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
arrayFunctionTaintStep(pred, succ, call)
}
}
/**
* A taint propagating data flow edge from `pred` to `succ` caused by a call `call` to a builtin array functions.
*/
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
// `elt` and `ary`; similar for `forEach`
exists(string name, Function f, int i |
(name = "map" or name = "forEach") and
(i = 0 or i = 2) and
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
call.(DataFlow::MethodCallNode).getMethodName() = name and
pred = call.getReceiver() and
succ = DataFlow::parameterNode(f.getParameter(i))
)
or
// `array.map` with tainted return value in callback
exists(DataFlow::FunctionNode f |
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
pred = f.getAReturn() and
succ = call
)
or
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or
name = "unshift"
|
pred = call.getAnArgument() and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or
name = "unshift"
|
pred = call.getASpreadArgument() and
// Make sure we handle reflective calls
succ = call.getReceiver().getALocalSource() and
call.getCalleeName() = name
)
or
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
exists(string name | name = "splice" |
pred = call.getArgument(2) and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
exists(string name |
name = "pop" or
name = "shift" or
name = "slice" or
name = "splice"
|
call.(DataFlow::MethodCallNode).calls(pred, name) and
succ = call
)
or
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
pred = call.getAnArgument() and
succ = call
or
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
succ = call
or
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
succ = call and
pred = call.getAnArgument()
}
}
/**
* Classes and predicates for modelling data-flow for arrays.
*/
private module ArrayDataFlow {
/**
* Gets a pseudo-field representing an element inside an array.
*/
private string arrayElement() { result = "$arrayElement$" }
/**
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
*/
private class ArrayAppendStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayAppendStep() {
this.getMethodName() = "push" or
this.getMethodName() = "unshift"
}
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
prop = arrayElement() and
(element = this.getAnArgument() or element = this.getASpreadArgument()) and
obj = this.getReceiver().getALocalSource()
}
}
/**
* A step for reading/writing an element from an array inside a for-loop.
* E.g. a read from `foo[i]` to `bar` in `for(var i = 0; i < arr.length; i++) {bar = foo[i]}`.
*/
private class ArrayIndexingStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
DataFlow::PropRef read;
ArrayIndexingStep() {
read = this and
forex(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType() |
type = TTNumber()
) and
exists(VarAccess i, ExprOrVarDecl init |
i = read.getPropertyNameExpr() and init = any(ForStmt f).getInit()
|
i.getVariable().getADefinition() = init or
i.getVariable().getADefinition().(VariableDeclarator).getDeclStmt() = init
)
}
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.(DataFlow::PropRead).getBase() and
element = this
}
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
prop = arrayElement() and
element = this.(DataFlow::PropWrite).getRhs() and
this = obj.(DataFlow::SourceNode).getAPropertyWrite()
}
}
/**
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
* E.g. `array.pop()`.
*/
private class ArrayPopStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayPopStep() {
getMethodName() = "pop" or
getMethodName() = "shift"
}
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.getReceiver() and
element = this
}
}
/**
* A step for iterating an array using `map` or `forEach`.
*
* Array elements can be loaded from the array `arr` to `e` in e.g: `arr.forEach(e => ...)`.
*
* And array elements can be stored into a resulting array using `map(...)`.
* E.g. in `arr.map(e => foo)`, the resulting array (`arr.map(e => foo)`) will contain the element `foo`.
*
* And the second parameter in the callback is the array ifself, so there is a `loadStoreStep` from the array to that second parameter.
*/
private class ArrayIteration extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayIteration() {
this.getMethodName() = "map" or
this.getMethodName() = "forEach"
}
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.getReceiver() and
element = getCallback(0).getParameter(0)
}
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
this.getMethodName() = "map" and
prop = arrayElement() and
element = this.getCallback(0).getAReturn() and
obj = this
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
pred = this.getReceiver() and
succ = getCallback(0).getParameter(2)
}
}
/**
* A step for creating an array and storing the elements in the array.
*/
private class ArrayCreationStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
ArrayCreationStep() { this instanceof DataFlow::ArrayCreationNode }
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
prop = arrayElement() and
element = this.(DataFlow::ArrayCreationNode).getAnElement() and
obj = this
}
}
/**
* A step modelling that `splice` can insert elements into an array.
* For example in `array.splice(i, del, e)`: if `e` is tainted, then so is `array
*/
private class ArraySpliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArraySpliceStep() { this.getMethodName() = "splice" }
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
prop = arrayElement() and
element = getArgument(2) and
obj = this.getReceiver().getALocalSource()
}
}
/**
* A step for modelling `concat`.
* For example in `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
*/
private class ArrayConcatStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayConcatStep() { this.getMethodName() = "concat" }
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
(pred = this.getReceiver() or pred = this.getAnArgument()) and
succ = this
}
}
/**
* A step for modelling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
*/
private class ArraySliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArraySliceStep() {
this.getMethodName() = "slice" or
this.getMethodName() = "splice" or
this.getMethodName() = "filter"
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
pred = this.getReceiver() and
succ = this
}
}
}

View File

@@ -587,8 +587,8 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode {
}
/**
* A data flow node corresponding to the creation or a new array, either through an array literal
* or an invocation of the `Array` constructor.
* A data flow node corresponding to the creation or a new array, either through an array literal,
* an invocation of the `Array` constructor, or the `Array.from` method.
*
*
* Examples:

View File

@@ -282,92 +282,7 @@ module TaintTracking {
}
}
/**
* A taint propagating data flow edge caused by the builtin array functions.
*/
private class ArrayFunctionTaintStep extends AdditionalTaintStep {
DataFlow::CallNode call;
ArrayFunctionTaintStep() { this = call }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
arrayFunctionTaintStep(pred, succ, call)
}
}
/**
* A taint propagating data flow edge from `pred` to `succ` caused by a call `call` to a builtin array functions.
*/
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
// `elt` and `ary`; similar for `forEach`
exists(string name, Function f, int i |
(name = "map" or name = "forEach") and
(i = 0 or i = 2) and
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
call.(DataFlow::MethodCallNode).getMethodName() = name and
pred = call.getReceiver() and
succ = DataFlow::parameterNode(f.getParameter(i))
)
or
// `array.map` with tainted return value in callback
exists(DataFlow::FunctionNode f |
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
pred = f.getAReturn() and
succ = call
)
or
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or
name = "unshift"
|
pred = call.getAnArgument() and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or
name = "unshift"
|
pred = call.getASpreadArgument() and
// Make sure we handle reflective calls
succ = call.getReceiver().getALocalSource() and
call.getCalleeName() = name
)
or
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
exists(string name | name = "splice" |
pred = call.getArgument(2) and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
exists(string name |
name = "pop" or
name = "shift" or
name = "slice" or
name = "splice"
|
call.(DataFlow::MethodCallNode).calls(pred, name) and
succ = call
)
or
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
pred = call.getAnArgument() and
succ = call
or
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
succ = call
or
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
succ = call and
pred = call.getAnArgument()
}
predicate arrayFunctionTaintStep = ArrayTaintTracking::arrayFunctionTaintStep/3;
/**
* A taint propagating data flow edge for assignments of the form `o[k] = v`, where

View File

@@ -1,5 +1,5 @@
/**
* Provides classes for working with [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) and [ws](https://github.com/websockets/ws).
* Provides classes for working with [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), [ws](https://github.com/websockets/ws), and [SockJS](http://sockjs.org).
*
* The model is based on the EventEmitter model, and there is therefore a
* data-flow step from where a WebSocket event is sent to where the message
@@ -18,26 +18,64 @@ import javascript
*/
private string channelName() { result = "message" }
/**
* The names of the libraries modelled in this file.
*/
private module LibraryNames {
string sockjs() { result = "SockJS" }
string websocket() { result = "WebSocket" }
string ws() { result = "ws" }
class LibraryName extends string {
LibraryName() { this = sockjs() or this = websocket() or this = ws() }
}
}
/**
* Holds if the websocket library named `client` can send a message to the library named `server`.
* Both `client` and `server` are library names defined in `LibraryNames`.
*/
private predicate areLibrariesCompatible(
LibraryNames::LibraryName client, LibraryNames::LibraryName server
) {
// sockjs is a WebSocket emulating library, but not actually an implementation of WebSockets.
client = LibraryNames::sockjs() and server = LibraryNames::sockjs()
or
server = LibraryNames::ws() and
(client = LibraryNames::ws() or client = LibraryNames::websocket())
}
/**
* Provides classes that model WebSockets clients.
*/
module ClientWebSocket {
private import LibraryNames
/**
* A class that can be used to instantiate a WebSocket instance.
*/
class SocketClass extends DataFlow::SourceNode {
boolean isNode;
LibraryName library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`.
SocketClass() {
this = DataFlow::globalVarRef("WebSocket") and isNode = false
this = DataFlow::globalVarRef("WebSocket") and library = websocket()
or
this = DataFlow::moduleImport("ws") and isNode = true
this = DataFlow::moduleImport("ws") and library = ws()
or
// the sockjs-client library:https://www.npmjs.com/package/sockjs-client
library = sockjs() and
(
this = DataFlow::moduleImport("sockjs-client") or
this = DataFlow::globalVarRef("SockJS")
)
}
/**
* Holds if this class is an import of the "ws" module.
* Gets the WebSocket library name.
*/
predicate isNode() { isNode = true }
LibraryName getLibrary() { result = library }
}
/**
@@ -49,11 +87,9 @@ module ClientWebSocket {
ClientSocket() { this = socketClass.getAnInstantiation() }
/**
* Holds if this ClientSocket is created from the "ws" module.
*
* The predicate is used to differentiate where the behavior of the "ws" module differs from the native WebSocket in browsers.
* Gets the WebSocket library name.
*/
predicate isNode() { socketClass.isNode() }
LibraryName getLibrary() { result = socketClass.getLibrary() }
}
/**
@@ -68,7 +104,10 @@ module ClientWebSocket {
override DataFlow::Node getSentItem(int i) { i = 0 and result = this.getArgument(0) }
override ServerWebSocket::ReceiveNode getAReceiver() { any() }
override ServerWebSocket::ReceiveNode getAReceiver() {
areLibrariesCompatible(emitter.getLibrary(),
result.getEmitter().(ServerWebSocket::ServerSocket).getLibrary())
}
}
/**
@@ -116,7 +155,7 @@ module ClientWebSocket {
*/
private class WSReceiveNode extends ClientWebSocket::ReceiveNode {
WSReceiveNode() {
emitter.isNode() and
emitter.getLibrary() = ws() and
this = getAMessageHandler(emitter, EventEmitter::on())
}
@@ -128,21 +167,38 @@ module ClientWebSocket {
* Provides classes that model WebSocket servers.
*/
module ServerWebSocket {
private import LibraryNames
/**
* Gets a server created by a library named `library`.
*/
DataFlow::SourceNode getAServer(LibraryName library) {
library = ws() and
result = DataFlow::moduleImport("ws").getAConstructorInvocation("Server")
or
library = sockjs() and
result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer")
}
/**
* A server WebSocket instance.
*/
class ServerSocket extends EventEmitter::Range, DataFlow::SourceNode {
LibraryName library;
ServerSocket() {
exists(DataFlow::CallNode onCall |
onCall =
DataFlow::moduleImport("ws")
.getAConstructorInvocation("Server")
.getAMemberCall(EventEmitter::on()) and
onCall = getAServer(library).getAMemberCall(EventEmitter::on()) and
onCall.getArgument(0).mayHaveStringValue("connection")
|
this = onCall.getCallback(1).getParameter(0)
)
}
/**
* Gets the name of the library that created this server socket.
*/
LibraryName getLibrary() { result = library }
}
/**
@@ -151,7 +207,13 @@ module ServerWebSocket {
class SendNode extends EventDispatch::Range, DataFlow::CallNode {
override ServerSocket emitter;
SendNode() { this = emitter.getAMemberCall("send") }
SendNode() {
emitter.getLibrary() = ws() and
this = emitter.getAMemberCall("send")
or
emitter.getLibrary() = sockjs() and
this = emitter.getAMemberCall("write")
}
override string getChannel() { result = channelName() }
@@ -160,7 +222,10 @@ module ServerWebSocket {
result = getArgument(0)
}
override ClientWebSocket::ReceiveNode getAReceiver() { any() }
override ClientWebSocket::ReceiveNode getAReceiver() {
areLibrariesCompatible(result.getEmitter().(ClientWebSocket::ClientSocket).getLibrary(),
emitter.getLibrary())
}
}
/**
@@ -170,8 +235,14 @@ module ServerWebSocket {
override ServerSocket emitter;
ReceiveNode() {
this = emitter.getAMemberCall(EventEmitter::on()) and
this.getArgument(0).mayHaveStringValue("message")
exists(string eventName |
emitter.getLibrary() = ws() and eventName = "message"
or
emitter.getLibrary() = sockjs() and eventName = "data"
|
this = emitter.getAMemberCall(EventEmitter::on()) and
this.getArgument(0).mayHaveStringValue(eventName)
)
}
override string getChannel() { result = channelName() }

View File

@@ -0,0 +1,13 @@
| arrays.js:2:16:2:23 | "source" | arrays.js:5:8:5:14 | obj.foo |
| arrays.js:2:16:2:23 | "source" | arrays.js:11:10:11:15 | arr[i] |
| arrays.js:2:16:2:23 | "source" | arrays.js:15:27:15:27 | e |
| arrays.js:2:16:2:23 | "source" | arrays.js:16:23:16:23 | e |
| arrays.js:2:16:2:23 | "source" | arrays.js:20:8:20:16 | arr.pop() |
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:30:8:30:17 | arr4.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:33:8:33:17 | arr5.pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:35:8:35:26 | arr5.slice(2).pop() |
| arrays.js:29:21:29:28 | "source" | arrays.js:41:8:41:17 | arr6.pop() |
| arrays.js:44:4:44:11 | "source" | arrays.js:45:10:45:18 | ary.pop() |

View File

@@ -0,0 +1,15 @@
import javascript
class ArrayFlowConfig extends DataFlow::Configuration {
ArrayFlowConfig() { this = "ArrayFlowConfig" }
override predicate isSource(DataFlow::Node source) { source.asExpr().getStringValue() = "source" }
override predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument()
}
}
from ArrayFlowConfig config, DataFlow::Node src, DataFlow::Node snk
where config.hasFlow(src, snk)
select src, snk

View File

@@ -0,0 +1,50 @@
(function () {
let source = "source";
var obj = { foo: source };
sink(obj.foo); // NOT OK
var arr = [];
arr.push(source);
for (var i = 0; i < arr.length; i++) {
sink(arr[i]); // NOT OK
}
arr.forEach((e) => sink(e)); // NOT OK
arr.map((e) => sink(e)); // NOT OK
[1, 2, 3].map(i => "source").forEach(e => sink(e)); // NOT OK.
sink(arr.pop()); // NOT OK
var arr2 = ["source"];
sink(arr2.pop()); // NOT OK
var arr3 = ["source"];
sink(arr3.pop()); // NOT OK
var arr4 = [];
arr4.splice(0, 0, "source");
sink(arr4.pop()); // NOT OK
var arr5 = [].concat(arr4);
sink(arr5.pop()); // NOT OK
sink(arr5.slice(2).pop()); // NOT OK
var arr6 = [];
for (var i = 0; i < arr5.length; i++) {
arr6[i] = arr5[i];
}
sink(arr6.pop()); // NOT OK
["source"].forEach((e, i, ary) => {
sink(ary.pop()); // NOT OK
sink(ary); // OK - its the array itself, not an element.
});
sink(arr[0]); // OK - tuple like usage.
});

View File

@@ -2,6 +2,7 @@
| a.js:1:15:1:23 | "tainted" | b.js:6:13:6:13 | x |
| a.js:2:15:2:28 | "also tainted" | b.js:5:13:5:29 | notTaintedTrustMe |
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
| callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
| callback.js:27:15:27:23 | "source3" | callback.js:13:14:13:14 | x |
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
| destructuring.js:19:15:19:23 | "tainted" | destructuring.js:14:15:14:15 | p |
@@ -44,6 +45,7 @@
| tst2.js:6:24:6:37 | "also tainted" | tst2.js:11:15:11:24 | g(source2) |
| tst6.mjs:12:14:12:21 | "source" | tst6.mjs:14:12:14:16 | a.m() |
| tst6.mjs:16:15:16:23 | "source2" | tst6.mjs:18:13:18:24 | a.m.call(a2) |
| tst.js:2:17:2:22 | "src1" | tst.js:28:20:28:22 | elt |
| tst.js:2:17:2:22 | "src1" | tst.js:39:17:39:17 | x |
| tst.js:2:17:2:22 | "src1" | tst.js:41:19:41:19 | x |
| tst.js:2:17:2:22 | "src1" | tst.js:45:17:45:17 | x |

View File

@@ -2,6 +2,7 @@
| a.js:1:15:1:23 | "tainted" | b.js:6:13:6:13 | x |
| a.js:2:15:2:28 | "also tainted" | b.js:5:13:5:29 | notTaintedTrustMe |
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
| callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
| callback.js:27:15:27:23 | "source3" | callback.js:13:14:13:14 | x |
| custom.js:1:14:1:26 | "verschmutzt" | custom.js:2:15:2:20 | quelle |
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
@@ -45,6 +46,7 @@
| tst2.js:6:24:6:37 | "also tainted" | tst2.js:11:15:11:24 | g(source2) |
| tst6.mjs:12:14:12:21 | "source" | tst6.mjs:14:12:14:16 | a.m() |
| tst6.mjs:16:15:16:23 | "source2" | tst6.mjs:18:13:18:24 | a.m.call(a2) |
| tst.js:2:17:2:22 | "src1" | tst.js:28:20:28:22 | elt |
| tst.js:2:17:2:22 | "src1" | tst.js:39:17:39:17 | x |
| tst.js:2:17:2:22 | "src1" | tst.js:41:19:41:19 | x |
| tst.js:2:17:2:22 | "src1" | tst.js:45:17:45:17 | x |

View File

@@ -1,3 +1,6 @@
| missing | callback.js:17:15:17:23 | "source2" | callback.js:8:16:8:20 | xs[i] |
| missing | callback.js:17:15:17:23 | "source2" | callback.js:12:16:12:16 | x |
| missing | callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
| missing | promises.js:1:2:1:2 | source | promises.js:6:26:6:28 | val |
| missing | promises.js:1:2:1:2 | source | promises.js:7:16:7:18 | val |
| missing | promises.js:1:2:1:2 | source | promises.js:37:11:37:11 | v |
@@ -30,3 +33,5 @@
| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:24:20:24:20 | v |
| missing | promises.js:32:24:32:37 | "also tainted" | promises.js:37:11:37:11 | v |
| missing | promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v |
| missing | tst.js:2:17:2:22 | "src1" | tst.js:27:22:27:24 | elt |
| missing | tst.js:2:17:2:22 | "src1" | tst.js:28:20:28:22 | elt |

View File

@@ -2,14 +2,31 @@
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', function (event) {
socket.send('Hi from browser!');
socket.send('Hi from browser!');
});
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
console.log('Message from server ', event.data);
});
socket.onmessage = function (event) {
console.log("Message from server 2", event.data)
};
})();
(function () {
var sock = new SockJS('http://0.0.0.0:9999/echo');
sock.onopen = function () {
sock.send('test');
};
socket.onmessage = function(event) {
console.log("Message from server 2", event.data)
};
})();
sock.onmessage = function (e) {
console.log('message', e.data);
sock.close();
};
sock.addEventListener('message', function (event) {
console.log('Using addEventListener ', event.data);
});
})

View File

@@ -4,10 +4,10 @@
const ws = new WebSocket('ws://example.org');
ws.on('open', function open() {
ws.send('Hi from client!');
ws.send('Hi from client!');
});
ws.on('message', function incoming(data) {
console.log(data);
console.log(data);
});
})();

View File

@@ -4,10 +4,10 @@
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
ws.send('Hi from server!');
ws.send('Hi from server!');
});
})();

View File

@@ -5,12 +5,12 @@ const sockjs = require('sockjs');
const app = express();
const server = http.createServer(app);
const sockjs_echo = sockjs.createServer({});
sockjs_echo.on('connection', function(conn) {
conn.on('data', function(message) {
sockjs_echo.on('connection', function (conn) {
conn.on('data', function (message) {
var data = JSON.parse(message);
conn.write(JSON.stringify(eval(data.test)));
});
});
sockjs_echo.installHandlers(server, {prefix:'/echo'});
sockjs_echo.installHandlers(server, { prefix: '/echo' });
server.listen(9090, '127.0.0.1');

View File

@@ -1,22 +1,35 @@
clientSocket
| browser.js:2:17:2:52 | new Web ... :8080') |
| browser.js:19:13:19:50 | new Soc ... /echo') |
| client.js:4:13:4:45 | new Web ... e.org') |
clientSend
| browser.js:5:6:5:36 | socket. ... wser!') |
| client.js:7:5:7:30 | ws.send ... ient!') |
| browser.js:5:3:5:33 | socket. ... wser!') |
| browser.js:21:3:21:19 | sock.send('test') |
| client.js:7:3:7:28 | ws.send ... ient!') |
clientReceive
| browser.js:8:37:10:2 | functio ... ta);\\n\\t} |
| browser.js:12:21:14:5 | functio ... )\\n } |
| browser.js:12:21:14:2 | functio ... ata)\\n\\t} |
| browser.js:24:19:27:2 | functio ... e();\\n\\t} |
| browser.js:29:35:31:2 | functio ... ta);\\n\\t} |
| client.js:10:19:12:2 | functio ... ta);\\n\\t} |
serverSocket
| server.js:6:43:6:44 | ws |
| sockjs.js:8:40:8:43 | conn |
serverSend
| server.js:11:5:11:30 | ws.send ... rver!') |
| server.js:11:3:11:28 | ws.send ... rver!') |
| sockjs.js:11:9:11:51 | conn.wr ... test))) |
serverReceive
| server.js:7:5:9:6 | ws.on(' ... \\n \\t\\t}) |
| server.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) |
| sockjs.js:9:5:12:6 | conn.on ... \\n }) |
taintStep
| browser.js:5:18:5:35 | 'Hi from browser!' | server.js:7:40:7:46 | message |
| client.js:7:13:7:29 | 'Hi from client!' | server.js:7:40:7:46 | message |
| server.js:11:13:11:29 | 'Hi from server!' | browser.js:9:42:9:51 | event.data |
| server.js:11:13:11:29 | 'Hi from server!' | browser.js:13:44:13:53 | event.data |
| server.js:11:13:11:29 | 'Hi from server!' | client.js:10:37:10:40 | data |
| browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message |
| browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message |
| client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message |
| server.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data |
| server.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data |
| server.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data |
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:25:26:25:31 | e.data |
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:30:42:30:51 | event.data |
remoteFlow
| server.js:7:38:7:44 | message |
| sockjs.js:9:31:9:37 | message |

View File

@@ -15,3 +15,5 @@ query ServerWebSocket::ReceiveNode serverReceive() { any() }
query predicate taintStep(DataFlow::Node pred, DataFlow::Node succ) {
any(DataFlow::AdditionalFlowStep s).step(pred, succ)
}
query RemoteFlowSource remoteFlow() { any() }

View File

@@ -521,6 +521,7 @@ edges
| tst.js:73:3:73:19 | document.location | tst.js:73:3:73:26 | documen ... .search |
| tst.js:73:3:73:19 | document.location | tst.js:73:3:73:26 | documen ... .search |
| tst.js:73:3:73:26 | documen ... .search | tst.js:73:1:73:27 | [,docum ... search] |
| tst.js:73:3:73:26 | documen ... .search | tst.js:73:46:73:46 | x |
| tst.js:73:46:73:46 | x | tst.js:76:20:76:20 | x |
| tst.js:73:46:73:46 | x | tst.js:76:20:76:20 | x |
| tst.js:80:49:80:65 | document.location | tst.js:80:49:80:72 | documen ... .search |

View File

@@ -525,6 +525,7 @@ edges
| tst.js:73:3:73:19 | document.location | tst.js:73:3:73:26 | documen ... .search |
| tst.js:73:3:73:19 | document.location | tst.js:73:3:73:26 | documen ... .search |
| tst.js:73:3:73:26 | documen ... .search | tst.js:73:1:73:27 | [,docum ... search] |
| tst.js:73:3:73:26 | documen ... .search | tst.js:73:46:73:46 | x |
| tst.js:73:46:73:46 | x | tst.js:76:20:76:20 | x |
| tst.js:73:46:73:46 | x | tst.js:76:20:76:20 | x |
| tst.js:80:49:80:65 | document.location | tst.js:80:49:80:72 | documen ... .search |