Merge branch 'main' into django-3.2

This commit is contained in:
Rasmus Wriedt Larsen
2021-04-21 17:15:47 +02:00
95 changed files with 1180 additions and 1386 deletions

View File

@@ -0,0 +1,17 @@
while(flagsLoop)
{
...
if(flagsIf) break;
...
}while(flagsLoop); // BAD: when exiting through `break`, it is possible to get into an eternal loop.
...
while(flagsLoop)
{
...
if(flagsIf) break;
...
} // GOOD: correct cycle
...
if(intA+intB) return 1; // BAD: possibly no comparison
...
if(intA+intB>intC) return 1; // GOOD: correct comparison

View File

@@ -0,0 +1,28 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>In some situations, after code refactoring, parts of the old constructs may remain. They are correctly accepted by the compiler, but can critically affect program execution. For example, if you switch from `do {...} while ();` to `while () {...}` forgetting to remove the old construct completely, you get `while(){...}while();` which may be vulnerable. These code snippets look suspicious and require the developer's attention.</p>
</overview>
<recommendation>
<p>We recommend that you use more explicit code transformations.</p>
</recommendation>
<example>
<p>The following example demonstrates the erroneous and corrected sections of the code.</p>
<sample src="InsufficientControlFlowManagementAfterRefactoringTheCode.c" />
</example>
<references>
<li>
CWE Common Weakness Enumeration:
<a href="https://cwe.mitre.org/data/definitions/691.html"> CWE-691: Insufficient Control Flow Management</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,119 @@
/**
* @name Errors After Refactoring
* @description --In some situations, after code refactoring, parts of the old constructs may remain.
* --They are correctly accepted by the compiler, but can critically affect program execution.
* --For example, if you switch from `do {...} while ();` to `while () {...}` with errors, you run the risk of running out of resources.
* --These code snippets look suspicious and require the developer's attention.
* @kind problem
* @id cpp/errors-after-refactoring
* @problem.severity warning
* @precision medium
* @tags correctness
* security
* external/cwe/cwe-691
*/
import cpp
import semmle.code.cpp.valuenumbering.HashCons
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
/**
* Using `while` directly after the body of another` while`.
*/
class UsingWhileAfterWhile extends WhileStmt {
/**
* Using a loop call after another loop has finished running can result in an eternal loop.
* For example, perhaps as a result of refactoring, the `do ... while ()` loop was incorrectly corrected.
* Even in the case of deliberate use of such an expression, it is better to correct it.
*/
UsingWhileAfterWhile() {
exists(WhileStmt wh1 |
wh1.getStmt().getAChild*().(BreakStmt).(ControlFlowNode).getASuccessor().getASuccessor() =
this and
hashCons(wh1.getCondition()) = hashCons(this.getCondition()) and
this.getStmt() instanceof EmptyStmt
)
or
exists(ForStmt fr1 |
fr1.getStmt().getAChild*().(BreakStmt).(ControlFlowNode).getASuccessor().getASuccessor() =
this and
hashCons(fr1.getCondition()) = hashCons(this.getCondition()) and
this.getStmt() instanceof EmptyStmt
)
}
}
/**
* Using arithmetic in a condition.
*/
class UsingArithmeticInComparison extends BinaryArithmeticOperation {
/**
* Using arithmetic operations in a comparison operation can be dangerous.
* For example, part of the comparison may have been lost as a result of refactoring.
* Even if you deliberately use such an expression, it is better to add an explicit comparison.
*/
UsingArithmeticInComparison() {
this.getParent*() instanceof IfStmt and
not this.getAChild*().isConstant() and
not this.getParent*() instanceof Call and
not this.getParent*() instanceof AssignExpr and
not this.getParent*() instanceof ArrayExpr and
not this.getParent*() instanceof RemExpr and
not this.getParent*() instanceof AssignBitwiseOperation and
not this.getParent*() instanceof AssignArithmeticOperation and
not this.getParent*() instanceof EqualityOperation and
not this.getParent*() instanceof RelationalOperation
}
/** Holds when the expression is inside the loop body. */
predicate insideTheLoop() { exists(Loop lp | lp.getStmt().getAChild*() = this.getParent*()) }
/** Holds when the expression is used in binary operations. */
predicate workingWithValue() {
this.getParent*() instanceof BinaryBitwiseOperation or
this.getParent*() instanceof NotExpr
}
/** Holds when the expression contains a pointer. */
predicate workingWithPointer() {
this.getAChild*().getFullyConverted().getType() instanceof DerivedType
}
/** Holds when a null comparison expression exists. */
predicate compareWithZero() {
exists(Expr exp |
exp instanceof ComparisonOperation and
(
globalValueNumber(exp.getAChild*()) = globalValueNumber(this) or
hashCons(exp.getAChild*()) = hashCons(this)
) and
(
exp.(ComparisonOperation).getLeftOperand().getValue() = "0" or
exp.(ComparisonOperation).getRightOperand().getValue() = "0"
)
)
}
/** Holds when a comparison expression exists. */
predicate compareWithOutZero() {
exists(Expr exp |
exp instanceof ComparisonOperation and
(
globalValueNumber(exp.getAChild*()) = globalValueNumber(this) or
hashCons(exp.getAChild*()) = hashCons(this)
)
)
}
}
from Expr exp
where
exp instanceof UsingArithmeticInComparison and
not exp.(UsingArithmeticInComparison).workingWithValue() and
not exp.(UsingArithmeticInComparison).workingWithPointer() and
not exp.(UsingArithmeticInComparison).insideTheLoop() and
not exp.(UsingArithmeticInComparison).compareWithZero() and
exp.(UsingArithmeticInComparison).compareWithOutZero()
or
exists(WhileStmt wst | wst instanceof UsingWhileAfterWhile and exp = wst.getCondition())
select exp, "this expression needs your attention"

View File

@@ -0,0 +1,4 @@
| test.c:15:6:15:16 | ... + ... | this expression needs your attention |
| test.c:17:17:17:27 | ... + ... | this expression needs your attention |
| test.c:22:10:22:15 | ... > ... | this expression needs your attention |
| test.c:26:10:26:15 | ... > ... | this expression needs your attention |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql

View File

@@ -12,14 +12,18 @@ void workFunction_0(char *s) {
void workFunction_1(char *s) {
int intA,intB;
if(intA + intB) return; // BAD [NOT DETECTED]
if(intA + intB) return; // BAD
if(intA + intB>4) return; // GOOD
if(intA>0 && (intA + intB)) return; // BAD [NOT DETECTED]
if(intA>0 && (intA + intB)) return; // BAD
while(intA>0)
{
if(intB - intA<10) break;
intA--;
}while(intA>0); // BAD [NOT DETECTED]
}while(intA>0); // BAD
for(intA=100; intA>0; intA--)
{
if(intB - intA<10) break;
}while(intA>0); // BAD
while(intA>0)
{
if(intB - intA<10) break;

View File

@@ -0,0 +1,11 @@
/**
* @id cs/summary/lines-of-code
* @name Total lines of code in the database
* @description The total number of lines of code across all files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments.
* @kind metric
* @tags summary
*/
import csharp
select sum(File f | f.fromSource() | f.getNumberOfLinesOfCode())

View File

@@ -316,6 +316,15 @@ private module SsaDefReaches {
)
}
/**
* Holds if the reference to `def` at index `i` in basic block `bb` is the
* last reference to `v` inside `bb`.
*/
pragma[noinline]
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
}
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
exists(ssaDefRank(def, v, bb, _, _))
}
@@ -351,8 +360,7 @@ private module SsaDefReaches {
*/
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
varBlockReaches(def, bb1, bb2) and
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
variableRead(bb2, i2, _, _)
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
}
}
@@ -434,15 +442,22 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
bb2 = bb1
)
or
exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and
lastSsaRef(def, _, bb1, i1) and
defAdjacentRead(def, bb1, bb2, i2)
}
pragma[noinline]
private predicate adjacentDefRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
v = def.getSourceVariable()
}
private predicate adjacentDefReachesRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
exists(SourceVariable v | v = def.getSourceVariable() |
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
ssaRef(bb1, i1, v, SsaDef())
or
variableRead(bb1, i1, v, true)
@@ -475,17 +490,19 @@ predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, Ba
*/
pragma[nomagic]
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
exists(SourceVariable v |
// Next reference to `v` inside `bb` is a write
next.definesAt(v, bb, j) and
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
exists(int rnk, int j |
rnk = ssaDefRank(def, v, bb, i, _) and
next.definesAt(v, bb, j) and
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
)
or
// Can reach a write using one or more steps
rnk = maxSsaRefRank(bb, v) and
lastSsaRef(def, v, bb, i) and
exists(BasicBlock bb2 |
varBlockReaches(def, bb, bb2) and
next.definesAt(v, bb2, j) and
1 = ssaRefRank(bb2, j, v, SsaDef())
1 = ssaDefRank(next, v, bb2, _, SsaDef())
)
)
}
@@ -539,7 +556,8 @@ pragma[nomagic]
predicate lastRef(Definition def, BasicBlock bb, int i) {
lastRefRedef(def, bb, i, _)
or
exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) |
lastSsaRef(def, _, bb, i) and
(
// Can reach exit directly
bb instanceof ExitBasicBlock
or

View File

@@ -316,6 +316,15 @@ private module SsaDefReaches {
)
}
/**
* Holds if the reference to `def` at index `i` in basic block `bb` is the
* last reference to `v` inside `bb`.
*/
pragma[noinline]
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
}
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
exists(ssaDefRank(def, v, bb, _, _))
}
@@ -351,8 +360,7 @@ private module SsaDefReaches {
*/
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
varBlockReaches(def, bb1, bb2) and
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
variableRead(bb2, i2, _, _)
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
}
}
@@ -434,15 +442,22 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
bb2 = bb1
)
or
exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and
lastSsaRef(def, _, bb1, i1) and
defAdjacentRead(def, bb1, bb2, i2)
}
pragma[noinline]
private predicate adjacentDefRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
v = def.getSourceVariable()
}
private predicate adjacentDefReachesRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
exists(SourceVariable v | v = def.getSourceVariable() |
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
ssaRef(bb1, i1, v, SsaDef())
or
variableRead(bb1, i1, v, true)
@@ -475,17 +490,19 @@ predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, Ba
*/
pragma[nomagic]
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
exists(SourceVariable v |
// Next reference to `v` inside `bb` is a write
next.definesAt(v, bb, j) and
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
exists(int rnk, int j |
rnk = ssaDefRank(def, v, bb, i, _) and
next.definesAt(v, bb, j) and
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
)
or
// Can reach a write using one or more steps
rnk = maxSsaRefRank(bb, v) and
lastSsaRef(def, v, bb, i) and
exists(BasicBlock bb2 |
varBlockReaches(def, bb, bb2) and
next.definesAt(v, bb2, j) and
1 = ssaRefRank(bb2, j, v, SsaDef())
1 = ssaDefRank(next, v, bb2, _, SsaDef())
)
)
}
@@ -539,7 +556,8 @@ pragma[nomagic]
predicate lastRef(Definition def, BasicBlock bb, int i) {
lastRefRedef(def, bb, i, _)
or
exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) |
lastSsaRef(def, _, bb, i) and
(
// Can reach exit directly
bb instanceof ExitBasicBlock
or

View File

@@ -316,6 +316,15 @@ private module SsaDefReaches {
)
}
/**
* Holds if the reference to `def` at index `i` in basic block `bb` is the
* last reference to `v` inside `bb`.
*/
pragma[noinline]
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
}
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
exists(ssaDefRank(def, v, bb, _, _))
}
@@ -351,8 +360,7 @@ private module SsaDefReaches {
*/
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
varBlockReaches(def, bb1, bb2) and
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
variableRead(bb2, i2, _, _)
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
}
}
@@ -434,15 +442,22 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
bb2 = bb1
)
or
exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and
lastSsaRef(def, _, bb1, i1) and
defAdjacentRead(def, bb1, bb2, i2)
}
pragma[noinline]
private predicate adjacentDefRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
v = def.getSourceVariable()
}
private predicate adjacentDefReachesRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
exists(SourceVariable v | v = def.getSourceVariable() |
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
ssaRef(bb1, i1, v, SsaDef())
or
variableRead(bb1, i1, v, true)
@@ -475,17 +490,19 @@ predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, Ba
*/
pragma[nomagic]
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
exists(SourceVariable v |
// Next reference to `v` inside `bb` is a write
next.definesAt(v, bb, j) and
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
exists(int rnk, int j |
rnk = ssaDefRank(def, v, bb, i, _) and
next.definesAt(v, bb, j) and
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
)
or
// Can reach a write using one or more steps
rnk = maxSsaRefRank(bb, v) and
lastSsaRef(def, v, bb, i) and
exists(BasicBlock bb2 |
varBlockReaches(def, bb, bb2) and
next.definesAt(v, bb2, j) and
1 = ssaRefRank(bb2, j, v, SsaDef())
1 = ssaDefRank(next, v, bb2, _, SsaDef())
)
)
}
@@ -539,7 +556,8 @@ pragma[nomagic]
predicate lastRef(Definition def, BasicBlock bb, int i) {
lastRefRedef(def, bb, i, _)
or
exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) |
lastSsaRef(def, _, bb, i) and
(
// Can reach exit directly
bb instanceof ExitBasicBlock
or

View File

@@ -316,6 +316,15 @@ private module SsaDefReaches {
)
}
/**
* Holds if the reference to `def` at index `i` in basic block `bb` is the
* last reference to `v` inside `bb`.
*/
pragma[noinline]
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
}
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
exists(ssaDefRank(def, v, bb, _, _))
}
@@ -351,8 +360,7 @@ private module SsaDefReaches {
*/
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
varBlockReaches(def, bb1, bb2) and
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
variableRead(bb2, i2, _, _)
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
}
}
@@ -434,15 +442,22 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
bb2 = bb1
)
or
exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and
lastSsaRef(def, _, bb1, i1) and
defAdjacentRead(def, bb1, bb2, i2)
}
pragma[noinline]
private predicate adjacentDefRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
v = def.getSourceVariable()
}
private predicate adjacentDefReachesRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
exists(SourceVariable v | v = def.getSourceVariable() |
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
ssaRef(bb1, i1, v, SsaDef())
or
variableRead(bb1, i1, v, true)
@@ -475,17 +490,19 @@ predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, Ba
*/
pragma[nomagic]
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
exists(SourceVariable v |
// Next reference to `v` inside `bb` is a write
next.definesAt(v, bb, j) and
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
exists(int rnk, int j |
rnk = ssaDefRank(def, v, bb, i, _) and
next.definesAt(v, bb, j) and
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
)
or
// Can reach a write using one or more steps
rnk = maxSsaRefRank(bb, v) and
lastSsaRef(def, v, bb, i) and
exists(BasicBlock bb2 |
varBlockReaches(def, bb, bb2) and
next.definesAt(v, bb2, j) and
1 = ssaRefRank(bb2, j, v, SsaDef())
1 = ssaDefRank(next, v, bb2, _, SsaDef())
)
)
}
@@ -539,7 +556,8 @@ pragma[nomagic]
predicate lastRef(Definition def, BasicBlock bb, int i) {
lastRefRedef(def, bb, i, _)
or
exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) |
lastSsaRef(def, _, bb, i) and
(
// Can reach exit directly
bb instanceof ExitBasicBlock
or

View File

@@ -111,13 +111,24 @@ module HardcodedCredentials {
}
/**
* Gets a regular expression for matching names of locations (variables, parameters, keys) that
* indicate the value being held is a credential.
* An assignable whose name indicates that the value being held is a credential.
*/
private string getACredentialRegex() {
result = "(?i).*pass(wd|word|code|phrase)(?!.*question).*" or
result = "(?i).*(puid|username|userid).*" or
result = "(?i).*(cert)(?!.*(format|name)).*"
private class CredentialVar extends Assignable {
pragma[noinline]
CredentialVar() {
exists(string name | name = this.getName() |
name.regexpMatch("(?i).*pass(wd|word|code|phrase)(?!.*question).*")
or
name.regexpMatch("(?i).*(puid|username|userid).*")
or
name.regexpMatch("(?i).*(cert)(?!.*(format|name)).*")
)
}
}
private class CredentialVariableAccess extends VariableAccess {
pragma[noinline]
CredentialVariableAccess() { this.getTarget() instanceof CredentialVar }
}
/**
@@ -128,11 +139,11 @@ module HardcodedCredentials {
) {
// An argument to a library call that looks like a credential
// "...flows to the [Username] parameter in [call to method CreateUser]"
exists(Call call |
exists(Call call, CredentialVar param |
supplementaryElement = call and
description = "the $@ parameter in $@" and
sink = call.getArgumentForName(sinkName) and
sinkName.regexpMatch(getACredentialRegex()) and
sink = call.getArgumentForParameter(param) and
sinkName = param.getName() and
call.getTarget().fromLibrary()
)
or
@@ -144,22 +155,20 @@ module HardcodedCredentials {
description = "the $@ in $@" and
sink = call.getArgument(0) and
sinkName = "setter call argument" and
p.getName().regexpMatch(getACredentialRegex()) and
p instanceof CredentialVar and
p.fromLibrary()
)
or
// Sink compared to password variable
// "...flows to [] which is compared against [access of UserName]"
exists(ComparisonTest ct, VariableAccess credentialAccess, string varName |
exists(ComparisonTest ct, CredentialVariableAccess credentialAccess |
sinkName = sink.toString() and
supplementaryElement = credentialAccess and
description = "$@ which is compared against $@" and
ct.getAnArgument() = credentialAccess and
ct.getAnArgument() = sink and
ct.getComparisonKind().isEquality() and
not sink = credentialAccess and
varName = credentialAccess.getTarget().getName() and
varName.regexpMatch(getACredentialRegex())
not sink = credentialAccess
)
}

View File

@@ -0,0 +1 @@
| 4 |

View File

@@ -0,0 +1 @@
Metrics/Summaries/LinesOfCode.ql

View File

@@ -0,0 +1,14 @@
class C1
{
/*
int M()
{
return 0;
}
*/
// int M() => 0;
int M() => 0; // Comment
}

View File

@@ -18,7 +18,9 @@ abstract class Modifiable extends Element {
* Holds if this element has modifier `m`.
*
* For most purposes, the more specialized predicates `isAbstract`, `isPublic`, etc.
* should be used, which also take implicit modifiers into account.
* should be used.
*
* Both this method and those specialized predicates take implicit modifiers into account.
* For instance, non-default instance methods in interfaces are implicitly
* abstract, so `isAbstract()` will hold for them even if `hasModifier("abstract")`
* does not.

View File

@@ -77,6 +77,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.security.LdapInjection
}
private predicate sourceModelCsv(string row) {

View File

@@ -6,6 +6,7 @@ import semmle.code.java.frameworks.Jndi
import semmle.code.java.frameworks.UnboundId
import semmle.code.java.frameworks.SpringLdap
import semmle.code.java.frameworks.ApacheLdap
private import semmle.code.java.dataflow.ExternalFlow
/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */
abstract class LdapInjectionSink extends DataFlow::Node { }
@@ -28,70 +29,56 @@ class LdapInjectionAdditionalTaintStep extends Unit {
/** Default sink for LDAP injection vulnerabilities. */
private class DefaultLdapInjectionSink extends LdapInjectionSink {
DefaultLdapInjectionSink() {
exists(MethodAccess ma, Method m, int index |
ma.getMethod() = m and
ma.getArgument(index) = this.asExpr() and
ldapInjectionSinkMethod(m, index)
)
DefaultLdapInjectionSink() { sinkNode(this, "ldap") }
}
private class DefaultLdapInjectionSinkModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
// jndi
"javax.naming.directory;DirContext;true;search;;;Argument[0..1];ldap",
// apache
"org.apache.directory.ldap.client.api;LdapConnection;true;search;;;Argument[0..2];ldap",
// UnboundID: search
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(ReadOnlySearchRequest);;Argument[0];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchRequest);;Argument[0];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,DereferencePolicy,int,int,boolean,Filter,String[]);;Argument[0..7];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,DereferencePolicy,int,int,boolean,String,String[]);;Argument[0..7];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,Filter,String[]);;Argument[0..3];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,String,String[]);;Argument[0..3];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,DereferencePolicy,int,int,boolean,Filter,String[]);;Argument[0..6];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,DereferencePolicy,int,int,boolean,String,String[]);;Argument[0..6];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,Filter,String[]);;Argument[0..2];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,String,String[]);;Argument[0..2];ldap",
// UnboundID: searchForEntry
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(ReadOnlySearchRequest);;Argument[0];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(SearchRequest);;Argument[0];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,DereferencePolicy,int,boolean,Filter,String[]);;Argument[0..5];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,DereferencePolicy,int,boolean,String,String[]);;Argument[0..5];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,Filter,String[]);;Argument[0..2];ldap",
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,String,String[]);;Argument[0..2];ldap",
// UnboundID: asyncSearch
"com.unboundid.ldap.sdk;LDAPConnection;false;asyncSearch;;;Argument[0];ldap",
// Spring
"org.springframework.ldap.core;LdapTemplate;false;find;;;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;findOne;;;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;search;;;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;searchForContext;;;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;searchForObject;;;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(LdapQuery,String);;Argument[0];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String);;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticatedLdapEntryContextCallback);;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticatedLdapEntryContextCallback,AuthenticationErrorCallback);;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticationErrorCallback);;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String);;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticatedLdapEntryContextCallback);;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticatedLdapEntryContextCallback,AuthenticationErrorCallback);;Argument[0..1];ldap",
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticationErrorCallback);;Argument[0..1];ldap"
]
}
}
/** Holds if the method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate ldapInjectionSinkMethod(Method m, int index) {
jndiLdapInjectionSinkMethod(m, index) or
unboundIdLdapInjectionSinkMethod(m, index) or
springLdapInjectionSinkMethod(m, index) or
apacheLdapInjectionSinkMethod(m, index)
}
/** Holds if the JNDI method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate jndiLdapInjectionSinkMethod(Method m, int index) {
m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and
m.hasName("search") and
index in [0 .. 1]
}
/** Holds if the UnboundID method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate unboundIdLdapInjectionSinkMethod(Method m, int index) {
exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() |
m instanceof MethodUnboundIdLDAPConnectionSearch or
m instanceof MethodUnboundIdLDAPConnectionAsyncSearch or
m instanceof MethodUnboundIdLDAPConnectionSearchForEntry
)
}
/** Holds if the Spring method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate springLdapInjectionSinkMethod(Method m, int index) {
// LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method
(
m instanceof MethodSpringLdapTemplateAuthenticate or
m instanceof MethodSpringLdapTemplateFind or
m instanceof MethodSpringLdapTemplateFindOne or
m instanceof MethodSpringLdapTemplateSearch or
m instanceof MethodSpringLdapTemplateSearchForContext or
m instanceof MethodSpringLdapTemplateSearchForObject
) and
(
// Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate
index in [0 .. 1] and
not m instanceof MethodSpringLdapTemplateAuthenticate
or
// But it's not the last parameter in case of authenticate method (last param is password)
index in [0 .. 1] and
index < m.getNumberOfParameters() - 1 and
m instanceof MethodSpringLdapTemplateAuthenticate
)
}
/** Holds if the Apache LDAP API method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate apacheLdapInjectionSinkMethod(Method m, int index) {
exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() |
m.getDeclaringType().getAnAncestor() instanceof TypeApacheLdapConnection and
m.hasName("search")
)
}
/** A sanitizer that clears the taint on (boxed) primitive types. */
private class DefaultLdapSanitizer extends LdapInjectionSanitizer {
DefaultLdapSanitizer() {

View File

@@ -0,0 +1,7 @@
lgtm,codescanning
* The predicates `StepSummary::step` and `TypeTracker::step` in `TypeTracker.qll` have been changed
to use the more restrictive type `LocalSourceNode` for their second argument. For cases where
stepping between non-`LocalSourceNode`s is required, the `StepSummary::smallstep` predicate may be
used instead.
* The methods `Node::track` and `Node::backtrack` have been moved to the class `LocalSourceNode`. If
the old behavior is required, one can use `LocalSourceNode::flowsTo` to add back the missing flow.

View File

@@ -32,21 +32,7 @@ private DataFlow::LocalSourceNode vulnerableHostnameRef(DataFlow::TypeTracker t,
result.asExpr() = allInterfacesStrConst
)
or
// Due to bad performance when using normal setup with `vulnerableHostnameRef(t2, hostname).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
vulnerableHostnameRef_first_join(t2, hostname, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate vulnerableHostnameRef_first_join(
DataFlow::TypeTracker t2, string hostname, DataFlow::Node res, DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(vulnerableHostnameRef(t2, hostname), res, summary)
exists(DataFlow::TypeTracker t2 | result = vulnerableHostnameRef(t2, hostname).track(t2, t))
}
/** Gets a reference to a hostname that can be used to bind to all interfaces. */
@@ -59,21 +45,7 @@ private DataFlow::LocalSourceNode vulnerableAddressTuple(DataFlow::TypeTracker t
t.start() and
result.asExpr() = any(Tuple tup | tup.getElt(0) = vulnerableHostnameRef(hostname).asExpr())
or
// Due to bad performance when using normal setup with `vulnerableAddressTuple(t2, hostname).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
vulnerableAddressTuple_first_join(t2, hostname, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate vulnerableAddressTuple_first_join(
DataFlow::TypeTracker t2, string hostname, DataFlow::Node res, DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(vulnerableAddressTuple(t2, hostname), res, summary)
exists(DataFlow::TypeTracker t2 | result = vulnerableAddressTuple(t2, hostname).track(t2, t))
}
/** Gets a reference to a tuple for which the first element is a hostname that can be used to bind to all interfaces. */

View File

@@ -422,9 +422,9 @@ module API {
}
/**
* Gets a data-flow node to which `nd`, which is a use of an API-graph node, flows.
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
*
* The flow from `nd` to that node may be inter-procedural.
* The flow from `src` to that node may be inter-procedural.
*/
private DataFlow::LocalSourceNode trackUseNode(
DataFlow::LocalSourceNode src, DataFlow::TypeTracker t
@@ -433,23 +433,19 @@ module API {
use(_, src) and
result = src
or
// Due to bad performance when using `trackUseNode(t2, attr_name).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::StepSummary summary |
t = trackUseNode_first_join(src, result, summary).append(summary)
)
}
pragma[nomagic]
private DataFlow::TypeTracker trackUseNode_first_join(
DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode res, DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(trackUseNode(src, result), res, summary)
exists(DataFlow::TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
}
/**
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
*
* The flow from `src` to that node may be inter-procedural.
*/
cached
DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
result = trackUseNode(src, DataFlow::TypeTracker::end())
result = trackUseNode(src, DataFlow::TypeTracker::end()) and
// We exclude module variable nodes, as these do not correspond to real uses.
not result instanceof DataFlow::ModuleVariableNode
}
/**

View File

@@ -570,21 +570,7 @@ module Cryptography {
arg = any(KeyGeneration::Range r).getKeySizeArg() and
result = arg.getALocalSource()
or
// Due to bad performance when using normal setup with we have inlined that code and forced a join
exists(DataFlow::TypeBackTracker t2 |
exists(DataFlow::StepSummary summary |
keysizeBacktracker_first_join(t2, arg, result, summary) and
t = t2.prepend(summary)
)
)
}
pragma[nomagic]
private predicate keysizeBacktracker_first_join(
DataFlow::TypeBackTracker t2, DataFlow::Node arg, DataFlow::Node res,
DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(res, keysizeBacktracker(t2, arg), summary)
exists(DataFlow::TypeBackTracker t2 | result = keysizeBacktracker(t2, arg).backtrack(t2, t))
}
/** Gets a back-reference to the keysize argument `arg` that was used to generate a new key-pair. */

View File

@@ -51,8 +51,8 @@ module StepSummary {
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*/
cached
predicate step(LocalSourceNode nodeFrom, Node nodeTo, StepSummary summary) {
exists(Node mid | typePreservingStep*(nodeFrom, mid) and smallstep(mid, nodeTo, summary))
predicate step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
exists(Node mid | nodeFrom.flowsTo(mid) and smallstep(mid, nodeTo, summary))
}
/**
@@ -63,7 +63,7 @@ module StepSummary {
* type-preserving steps.
*/
predicate smallstep(Node nodeFrom, Node nodeTo, StepSummary summary) {
typePreservingStep(nodeFrom, nodeTo) and
jumpStep(nodeFrom, nodeTo) and
summary = LevelStep()
or
callStep(nodeFrom, nodeTo) and summary = CallStep()
@@ -80,12 +80,6 @@ module StepSummary {
}
}
/** Holds if it's reasonable to expect the data flow step from `nodeFrom` to `nodeTo` to preserve types. */
private predicate typePreservingStep(Node nodeFrom, Node nodeTo) {
simpleLocalFlowStep(nodeFrom, nodeTo) or
jumpStep(nodeFrom, nodeTo)
}
/**
* Gets a callable for the call where `nodeFrom` is used as the `i`'th argument.
*
@@ -274,10 +268,10 @@ class TypeTracker extends TTypeTracker {
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*/
pragma[inline]
TypeTracker step(LocalSourceNode nodeFrom, Node nodeTo) {
TypeTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
exists(StepSummary summary |
StepSummary::step(nodeFrom, nodeTo, summary) and
result = this.append(summary)
StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and
result = this.append(pragma[only_bind_into](summary))
)
}
@@ -312,7 +306,7 @@ class TypeTracker extends TTypeTracker {
result = this.append(summary)
)
or
typePreservingStep(nodeFrom, nodeTo) and
simpleLocalFlowStep(nodeFrom, nodeTo) and
result = this
}
}
@@ -417,8 +411,8 @@ class TypeBackTracker extends TTypeBackTracker {
pragma[inline]
TypeBackTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
exists(StepSummary summary |
StepSummary::step(nodeFrom, nodeTo, summary) and
this = result.prepend(summary)
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
this = result.prepend(pragma[only_bind_into](summary))
)
}
@@ -453,7 +447,7 @@ class TypeBackTracker extends TTypeBackTracker {
this = result.prepend(summary)
)
or
typePreservingStep(nodeFrom, nodeTo) and
simpleLocalFlowStep(nodeFrom, nodeTo) and
this = result
}
}

View File

@@ -119,22 +119,6 @@ class Node extends TNode {
/** Gets the expression corresponding to this node, if any. */
Expr asExpr() { none() }
/**
* Gets a node that this node may flow to using one heap and/or interprocedural step.
*
* See `TypeTracker` for more details about how to use this.
*/
pragma[inline]
Node track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
/**
* Gets a node that may flow into this one using one heap and/or interprocedural step.
*
* See `TypeBackTracker` for more details about how to use this.
*/
pragma[inline]
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
/**
* Gets a local source node from which data may flow to this node in zero or more local data-flow steps.
*/

View File

@@ -10,13 +10,6 @@ import python
import DataFlowPublic
private import DataFlowPrivate
private predicate comes_from_cfgnode(Node node) {
exists(CfgNode first, Node second |
simpleLocalFlowStep(first, second) and
simpleLocalFlowStep*(second, node)
)
}
/**
* A data flow node that is a source of local flow. This includes things like
* - Expressions
@@ -40,8 +33,7 @@ private predicate comes_from_cfgnode(Node node) {
class LocalSourceNode extends Node {
cached
LocalSourceNode() {
not comes_from_cfgnode(this) and
not this instanceof ModuleVariableNode and
not simpleLocalFlowStep(_, this) and
// Currently, we create synthetic post-update nodes for
// - arguments to calls that may modify said argument
// - direct reads a writes of object attributes
@@ -85,6 +77,22 @@ class LocalSourceNode extends Node {
* Gets a call to this node.
*/
CallCfgNode getACall() { Cached::call(this, result) }
/**
* Gets a node that this node may flow to using one heap and/or interprocedural step.
*
* See `TypeTracker` for more details about how to use this.
*/
pragma[inline]
LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
/**
* Gets a node that may flow into this one using one heap and/or interprocedural step.
*
* See `TypeBackTracker` for more details about how to use this.
*/
pragma[inline]
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
}
cached

View File

@@ -83,23 +83,11 @@ private module CryptographyModel {
result.(DataFlow::CallCfgNode).getFunction() = curveClassWithKeySize(keySize) and
origin = result
or
// Due to bad performance when using normal setup with we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
curveClassInstanceWithKeySize_first_join(t2, keySize, origin, result, summary) and
t = t2.append(summary)
)
result = curveClassInstanceWithKeySize(t2, keySize, origin).track(t2, t)
)
}
pragma[nomagic]
private predicate curveClassInstanceWithKeySize_first_join(
DataFlow::TypeTracker t2, int keySize, DataFlow::Node origin, DataFlow::Node res,
DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(curveClassInstanceWithKeySize(t2, keySize, origin), res, summary)
}
/** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */
DataFlow::Node curveClassInstanceWithKeySize(int keySize, DataFlow::Node origin) {
curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize, origin).flowsTo(result)

View File

@@ -1325,7 +1325,7 @@ private module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponse.write` function. */
private DataFlow::Node write(
private DataFlow::LocalSourceNode write(
django::http::response::HttpResponse::InstanceSource instance, DataFlow::TypeTracker t
) {
t.startInAttr("write") and
@@ -1337,7 +1337,7 @@ private module PrivateDjango {
/** Gets a reference to the `django.http.response.HttpResponse.write` function. */
DataFlow::Node write(django::http::response::HttpResponse::InstanceSource instance) {
result = write(instance, DataFlow::TypeTracker::end())
write(instance, DataFlow::TypeTracker::end()).flowsTo(result)
}
/**

View File

@@ -54,7 +54,7 @@ module Connection {
}
/** Gets a reference to an instance of `db.Connection`. */
private DataFlow::Node instance(DataFlow::TypeTracker t) {
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
@@ -62,7 +62,7 @@ module Connection {
}
/** Gets a reference to an instance of `db.Connection`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
}
/**
@@ -71,7 +71,7 @@ module Connection {
*/
module cursor {
/** Gets a reference to the `cursor` method on a connection. */
private DataFlow::Node methodRef(DataFlow::TypeTracker t) {
private DataFlow::LocalSourceNode methodRef(DataFlow::TypeTracker t) {
t.startInAttr("cursor") and
result = Connection::instance()
or
@@ -79,10 +79,10 @@ module cursor {
}
/** Gets a reference to the `cursor` method on a connection. */
DataFlow::Node methodRef() { result = methodRef(DataFlow::TypeTracker::end()) }
DataFlow::Node methodRef() { methodRef(DataFlow::TypeTracker::end()).flowsTo(result) }
/** Gets a reference to a result of calling the `cursor` method on a connection. */
private DataFlow::Node methodResult(DataFlow::TypeTracker t) {
private DataFlow::LocalSourceNode methodResult(DataFlow::TypeTracker t) {
t.start() and
result.asCfgNode().(CallNode).getFunction() = methodRef().asCfgNode()
or
@@ -90,7 +90,7 @@ module cursor {
}
/** Gets a reference to a result of calling the `cursor` method on a connection. */
DataFlow::Node methodResult() { result = methodResult(DataFlow::TypeTracker::end()) }
DataFlow::Node methodResult() { methodResult(DataFlow::TypeTracker::end()).flowsTo(result) }
}
/**
@@ -101,7 +101,7 @@ module cursor {
*
* See https://www.python.org/dev/peps/pep-0249/#id15.
*/
private DataFlow::Node execute(DataFlow::TypeTracker t) {
private DataFlow::LocalSourceNode execute(DataFlow::TypeTracker t) {
t.startInAttr("execute") and
result in [cursor::methodResult(), Connection::instance()]
or
@@ -116,7 +116,7 @@ private DataFlow::Node execute(DataFlow::TypeTracker t) {
*
* See https://www.python.org/dev/peps/pep-0249/#id15.
*/
DataFlow::Node execute() { result = execute(DataFlow::TypeTracker::end()) }
DataFlow::Node execute() { execute(DataFlow::TypeTracker::end()).flowsTo(result) }
/** A call to the `execute` method on a cursor (or on a connection). */
private class ExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {

View File

@@ -82,21 +82,7 @@ private DataFlow::LocalSourceNode re_flag_tracker(string flag_name, DataFlow::Ty
result.asCfgNode() = binop
)
or
// Due to bad performance when using normal setup with `re_flag_tracker(t2, attr_name).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
re_flag_tracker_first_join(t2, flag_name, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate re_flag_tracker_first_join(
DataFlow::TypeTracker t2, string flag_name, DataFlow::Node res, DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(re_flag_tracker(flag_name, t2), res, summary)
exists(DataFlow::TypeTracker t2 | result = re_flag_tracker(flag_name, t2).track(t2, t))
}
/**

View File

@@ -9,7 +9,11 @@ class ApiUseTest extends InlineExpectationsTest {
override string getARelevantTag() { result = "use" }
private predicate relevant_node(API::Node a, DataFlow::Node n, Location l) {
n = a.getAUse() and l = n.getLocation()
n = a.getAUse() and
l = n.getLocation() and
// Module variable nodes have no suitable location, so it's best to simply exclude them entirely
// from the inline tests.
not n instanceof DataFlow::ModuleVariableNode
}
override predicate hasActualResult(Location location, string element, string tag, string value) {

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -1,4 +1,4 @@
import experimental.dataflow.tainttracking.TestTaintLib
import experimental.meta.InlineTaintTest
import semmle.python.dataflow.new.BarrierGuards
class CustomSanitizerOverrides extends TestTaintTrackingConfiguration {

View File

@@ -1,27 +0,0 @@
| test_string_const_compare.py:16 | ok | test_eq | ts |
| test_string_const_compare.py:18 | ok | test_eq | ts |
| test_string_const_compare.py:20 | ok | test_eq | ts |
| test_string_const_compare.py:27 | ok | test_eq_unsafe | ts |
| test_string_const_compare.py:29 | ok | test_eq_unsafe | ts |
| test_string_const_compare.py:35 | fail | test_eq_with_or | ts |
| test_string_const_compare.py:37 | ok | test_eq_with_or | ts |
| test_string_const_compare.py:43 | ok | test_non_eq1 | ts |
| test_string_const_compare.py:45 | ok | test_non_eq1 | ts |
| test_string_const_compare.py:51 | ok | test_non_eq2 | ts |
| test_string_const_compare.py:53 | fail | test_non_eq2 | ts |
| test_string_const_compare.py:59 | ok | test_in_list | ts |
| test_string_const_compare.py:61 | ok | test_in_list | ts |
| test_string_const_compare.py:67 | ok | test_in_tuple | ts |
| test_string_const_compare.py:69 | ok | test_in_tuple | ts |
| test_string_const_compare.py:75 | ok | test_in_set | ts |
| test_string_const_compare.py:77 | ok | test_in_set | ts |
| test_string_const_compare.py:83 | ok | test_in_unsafe1 | ts |
| test_string_const_compare.py:85 | ok | test_in_unsafe1 | ts |
| test_string_const_compare.py:91 | ok | test_in_unsafe2 | ts |
| test_string_const_compare.py:93 | ok | test_in_unsafe2 | ts |
| test_string_const_compare.py:99 | ok | test_not_in1 | ts |
| test_string_const_compare.py:101 | ok | test_not_in1 | ts |
| test_string_const_compare.py:107 | ok | test_not_in2 | ts |
| test_string_const_compare.py:109 | fail | test_not_in2 | ts |
| test_string_const_compare.py:119 | fail | test_eq_thorugh_func | ts |
| test_string_const_compare.py:121 | ok | test_eq_thorugh_func | ts |

View File

@@ -15,32 +15,32 @@ def test_eq():
if ts == "safe":
ensure_not_tainted(ts)
else:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
# ts should still be tainted after exiting the if block
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
def test_eq_unsafe(x="foo"):
"""This test-case might seem strange, but it was a FP in our old points-to based analysis."""
ts = TAINTED_STRING
if ts == ts:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
if ts == x:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
def test_eq_with_or():
ts = TAINTED_STRING
if ts == "safe" or ts == "also_safe":
ensure_not_tainted(ts)
ensure_not_tainted(ts) # $ SPURIOUS: tainted
else:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
def test_non_eq1():
ts = TAINTED_STRING
if ts != "safe":
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
else:
ensure_not_tainted(ts)
@@ -48,9 +48,9 @@ def test_non_eq1():
def test_non_eq2():
ts = TAINTED_STRING
if not ts == "safe":
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
else:
ensure_not_tainted(ts)
ensure_not_tainted(ts) # $ SPURIOUS: tainted
def test_in_list():
@@ -58,7 +58,7 @@ def test_in_list():
if ts in ["safe", "also_safe"]:
ensure_not_tainted(ts)
else:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
def test_in_tuple():
@@ -66,7 +66,7 @@ def test_in_tuple():
if ts in ("safe", "also_safe"):
ensure_not_tainted(ts)
else:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
def test_in_set():
@@ -74,29 +74,29 @@ def test_in_set():
if ts in {"safe", "also_safe"}:
ensure_not_tainted(ts)
else:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
def test_in_unsafe1(xs):
ts = TAINTED_STRING
if ts in xs:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
else:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
def test_in_unsafe2(x):
ts = TAINTED_STRING
if ts in ["safe", x]:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
else:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
def test_not_in1():
ts = TAINTED_STRING
if ts not in ["safe", "also_safe"]:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
else:
ensure_not_tainted(ts)
@@ -104,9 +104,9 @@ def test_not_in1():
def test_not_in2():
ts = TAINTED_STRING
if not ts in ["safe", "also_safe"]:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
else:
ensure_not_tainted(ts)
ensure_not_tainted(ts) # $ SPURIOUS: tainted
def is_safe(x):
@@ -116,9 +116,9 @@ def is_safe(x):
def test_eq_thorugh_func():
ts = TAINTED_STRING
if is_safe(ts):
ensure_not_tainted(ts)
ensure_not_tainted(ts) # $ SPURIOUS: tainted
else:
ensure_tainted(ts)
ensure_tainted(ts) # $ tainted
# Make tests runable

View File

@@ -0,0 +1,20 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures
isSanitizer
| TestTaintTrackingConfiguration | test.py:21:39:21:39 | ControlFlowNode for s |
| TestTaintTrackingConfiguration | test.py:50:10:50:29 | ControlFlowNode for emulated_escaping() |
isSanitizerGuard
| TestTaintTrackingConfiguration | test.py:35:8:35:26 | ControlFlowNode for emulated_is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:29:8:29:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:44:8:44:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:52:12:52:21 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:72:8:72:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:80:12:80:21 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:104:8:104:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:127:12:127:21 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:132:16:132:25 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:137:20:137:29 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_reference.py:30:8:30:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_reference.py:40:8:40:25 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_reference.py:55:8:55:21 | ControlFlowNode for is_safe() |

View File

@@ -1,4 +1,4 @@
import experimental.dataflow.tainttracking.TestTaintLib
import experimental.meta.InlineTaintTest
class IsSafeCheck extends DataFlow::BarrierGuard {
IsSafeCheck() {

View File

@@ -1,61 +0,0 @@
test_taint
| test.py:22 | ok | test_custom_sanitizer | s |
| test.py:36 | ok | test_custom_sanitizer_guard | s |
| test.py:38 | ok | test_custom_sanitizer_guard | s |
| test.py:40 | ok | test_custom_sanitizer_guard | s |
| test.py:51 | ok | test_escape | s2 |
| test_logical.py:30 | ok | test_basic | s |
| test_logical.py:32 | ok | test_basic | s |
| test_logical.py:35 | ok | test_basic | s |
| test_logical.py:37 | fail | test_basic | s |
| test_logical.py:45 | ok | test_or | s |
| test_logical.py:47 | ok | test_or | s |
| test_logical.py:51 | ok | test_or | s |
| test_logical.py:53 | ok | test_or | s |
| test_logical.py:57 | ok | test_or | s |
| test_logical.py:59 | ok | test_or | s |
| test_logical.py:67 | ok | test_and | s |
| test_logical.py:69 | ok | test_and | s |
| test_logical.py:73 | ok | test_and | s |
| test_logical.py:75 | ok | test_and | s |
| test_logical.py:79 | ok | test_and | s |
| test_logical.py:81 | fail | test_and | s |
| test_logical.py:89 | fail | test_tricky | s |
| test_logical.py:93 | fail | test_tricky | s_ |
| test_logical.py:100 | fail | test_nesting_not | s |
| test_logical.py:102 | ok | test_nesting_not | s |
| test_logical.py:105 | ok | test_nesting_not | s |
| test_logical.py:107 | fail | test_nesting_not | s |
| test_logical.py:116 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:118 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:121 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:123 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:126 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:128 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:137 | fail | test_with_return | s |
| test_logical.py:146 | fail | test_with_exception | s |
| test_reference.py:31 | fail | test_basic | s2 |
| test_reference.py:31 | ok | test_basic | s |
| test_reference.py:33 | ok | test_basic | s |
| test_reference.py:33 | ok | test_basic | s2 |
| test_reference.py:41 | fail | test_identical_call | s.strip() |
| test_reference.py:43 | ok | test_identical_call | s.strip() |
| test_reference.py:56 | fail | test_class_attribute_access | c.foo |
| test_reference.py:58 | ok | test_class_attribute_access | c.foo |
isSanitizer
| TestTaintTrackingConfiguration | test.py:21:39:21:39 | ControlFlowNode for s |
| TestTaintTrackingConfiguration | test.py:50:10:50:29 | ControlFlowNode for emulated_escaping() |
isSanitizerGuard
| TestTaintTrackingConfiguration | test.py:35:8:35:26 | ControlFlowNode for emulated_is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:29:8:29:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:44:8:44:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:50:12:50:21 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:66:8:66:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:72:12:72:21 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:92:8:92:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:115:12:115:21 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:120:16:120:25 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_logical.py:125:20:125:29 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_reference.py:30:8:30:17 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_reference.py:40:8:40:25 | ControlFlowNode for is_safe() |
| TestTaintTrackingConfiguration | test_reference.py:55:8:55:21 | ControlFlowNode for is_safe() |

View File

@@ -35,9 +35,9 @@ def test_custom_sanitizer_guard():
if emulated_is_safe(s):
ensure_not_tainted(s)
s = TAINTED_STRING
ensure_tainted(s)
ensure_tainted(s) # $ tainted
else:
ensure_tainted(s)
ensure_tainted(s) # $ tainted
def emulated_escaping(arg):

View File

@@ -29,12 +29,12 @@ def test_basic():
if is_safe(s):
ensure_not_tainted(s)
else:
ensure_tainted(s)
ensure_tainted(s) # $ tainted
if not is_safe(s):
ensure_tainted(s)
ensure_tainted(s) # $ tainted
else:
ensure_not_tainted(s)
ensure_not_tainted(s) # $ SPURIOUS: tainted
def test_or():
@@ -42,21 +42,27 @@ def test_or():
# x or y
if is_safe(s) or random_choice():
ensure_tainted(s) # might be tainted
# might be tainted
ensure_tainted(s) # $ tainted
else:
ensure_tainted(s) # must be tainted
# must be tainted
ensure_tainted(s) # $ tainted
# not (x or y)
if not(is_safe(s) or random_choice()):
ensure_tainted(s) # must be tainted
# must be tainted
ensure_tainted(s) # $ tainted
else:
ensure_tainted(s) # might be tainted
# might be tainted
ensure_tainted(s) # $ tainted
# not (x or y) == not x and not y [de Morgan's laws]
if not is_safe(s) and not random_choice():
ensure_tainted(s) # must be tainted
# must be tainted
ensure_tainted(s) # $ tainted
else:
ensure_tainted(s) # might be tainted
# might be tainted
ensure_tainted(s) # $ tainted
def test_and():
@@ -64,21 +70,27 @@ def test_and():
# x and y
if is_safe(s) and random_choice():
ensure_not_tainted(s) # must not be tainted
# cannot be tainted
ensure_not_tainted(s)
else:
ensure_tainted(s) # might be tainted
# might be tainted
ensure_tainted(s) # $ tainted
# not (x and y)
if not(is_safe(s) and random_choice()):
ensure_tainted(s) # might be tainted
# might be tainted
ensure_tainted(s) # $ tainted
else:
# cannot be tainted
ensure_not_tainted(s)
# not (x and y) == not x or not y [de Morgan's laws]
if not is_safe(s) or not random_choice():
ensure_tainted(s) # might be tainted
# might be tainted
ensure_tainted(s) # $ tainted
else:
ensure_not_tainted(s)
# cannot be tainted
ensure_not_tainted(s) # $ SPURIOUS: tainted
def test_tricky():
@@ -86,25 +98,25 @@ def test_tricky():
x = is_safe(s)
if x:
ensure_not_tainted(s) # FP
ensure_not_tainted(s) # $ SPURIOUS: tainted
s_ = s
if is_safe(s):
ensure_not_tainted(s_) # FP
ensure_not_tainted(s_) # $ SPURIOUS: tainted
def test_nesting_not():
s = TAINTED_STRING
if not(not(is_safe(s))):
ensure_not_tainted(s)
ensure_not_tainted(s) # $ SPURIOUS: tainted
else:
ensure_tainted(s)
ensure_tainted(s) # $ tainted
if not(not(not(is_safe(s)))):
ensure_tainted(s)
ensure_tainted(s) # $ tainted
else:
ensure_not_tainted(s)
ensure_not_tainted(s) # $ SPURIOUS: tainted
# Adding `and True` makes the sanitizer trigger when it would otherwise not. See output in
@@ -113,17 +125,17 @@ def test_nesting_not_with_and_true():
s = TAINTED_STRING
if not(is_safe(s) and True):
ensure_tainted(s)
ensure_tainted(s) # $ tainted
else:
ensure_not_tainted(s)
if not(not(is_safe(s) and True)):
ensure_not_tainted(s)
else:
ensure_tainted(s)
ensure_tainted(s) # $ tainted
if not(not(not(is_safe(s) and True))):
ensure_tainted(s)
ensure_tainted(s) # $ tainted
else:
ensure_not_tainted(s)
@@ -134,7 +146,7 @@ def test_with_return():
if not is_safe(s):
return
ensure_not_tainted(s)
ensure_not_tainted(s) # $ SPURIOUS: tainted
def test_with_exception():
@@ -143,7 +155,7 @@ def test_with_exception():
if not is_safe(s):
raise Exception("unsafe")
ensure_not_tainted(s)
ensure_not_tainted(s) # $ SPURIOUS: tainted
# Make tests runable

View File

@@ -28,9 +28,9 @@ def test_basic():
s2 = s
if is_safe(s):
ensure_not_tainted(s, s2)
ensure_not_tainted(s, s2) # $ SPURIOUS: tainted
else:
ensure_tainted(s, s2)
ensure_tainted(s, s2) # $ tainted
def test_identical_call():
@@ -38,9 +38,9 @@ def test_identical_call():
s = TAINTED_STRING
if is_safe(s.strip()):
ensure_not_tainted(s.strip())
ensure_not_tainted(s.strip()) # $ SPURIOUS: tainted
else:
ensure_tainted(s.strip())
ensure_tainted(s.strip()) # $ tainted
class C(object):
@@ -53,9 +53,9 @@ def test_class_attribute_access():
c = C(s)
if is_safe(c.foo):
ensure_not_tainted(c.foo)
ensure_not_tainted(c.foo) # $ SPURIOUS: tainted
else:
ensure_tainted(c.foo)
ensure_tainted(c.foo) # $ tainted
# Make tests runable

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -1,34 +0,0 @@
| test_collections.py:16 | ok | test_access | tainted_list.copy() |
| test_collections.py:24 | ok | list_clear | tainted_list |
| test_collections.py:27 | fail | list_clear | tainted_list |
| test_pathlib.py:26 | fail | test_basic | tainted_path |
| test_pathlib.py:28 | fail | test_basic | tainted_pure_path |
| test_pathlib.py:29 | fail | test_basic | tainted_pure_posix_path |
| test_pathlib.py:30 | fail | test_basic | tainted_pure_windows_path |
| test_pathlib.py:32 | fail | test_basic | BinaryExpr |
| test_pathlib.py:33 | fail | test_basic | BinaryExpr |
| test_pathlib.py:35 | fail | test_basic | tainted_path.joinpath(..) |
| test_pathlib.py:36 | fail | test_basic | pathlib.Path(..).joinpath(..) |
| test_pathlib.py:37 | fail | test_basic | pathlib.Path(..).joinpath(..) |
| test_pathlib.py:39 | fail | test_basic | str(..) |
| test_pathlib.py:49 | fail | test_basic | tainted_posix_path |
| test_pathlib.py:55 | fail | test_basic | tainted_windows_path |
| test_string.py:17 | ok | str_methods | ts.casefold() |
| test_string.py:19 | ok | str_methods | ts.format_map(..) |
| test_string.py:20 | ok | str_methods | "{unsafe}".format_map(..) |
| test_string.py:31 | ok | binary_decode_encode | base64.a85encode(..) |
| test_string.py:32 | ok | binary_decode_encode | base64.a85decode(..) |
| test_string.py:35 | ok | binary_decode_encode | base64.b85encode(..) |
| test_string.py:36 | ok | binary_decode_encode | base64.b85decode(..) |
| test_string.py:39 | ok | binary_decode_encode | base64.encodebytes(..) |
| test_string.py:40 | ok | binary_decode_encode | base64.decodebytes(..) |
| test_string.py:48 | ok | f_strings | Fstring |
| test_unpacking.py:18 | ok | extended_unpacking | first |
| test_unpacking.py:18 | ok | extended_unpacking | last |
| test_unpacking.py:18 | ok | extended_unpacking | rest |
| test_unpacking.py:23 | ok | also_allowed | a |
| test_unpacking.py:31 | ok | also_allowed | b |
| test_unpacking.py:31 | ok | also_allowed | c |
| test_unpacking.py:39 | ok | nested | x |
| test_unpacking.py:39 | ok | nested | xs |
| test_unpacking.py:39 | ok | nested | ys |

View File

@@ -1 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib

View File

@@ -13,7 +13,7 @@ def test_access():
tainted_list = TAINTED_LIST
ensure_tainted(
tainted_list.copy(),
tainted_list.copy(), # $ tainted
)
@@ -21,10 +21,10 @@ def list_clear():
tainted_string = TAINTED_STRING
tainted_list = [tainted_string]
ensure_tainted(tainted_list)
ensure_tainted(tainted_list) # $ tainted
tainted_list.clear()
ensure_not_tainted(tainted_list)
ensure_not_tainted(tainted_list) # $ SPURIOUS: tainted
# Make tests runable

View File

@@ -23,20 +23,20 @@ def test_basic():
tainted_pure_windows_path = pathlib.PureWindowsPath(ts)
ensure_tainted(
tainted_path,
tainted_path, # $ MISSING: tainted
tainted_pure_path,
tainted_pure_posix_path,
tainted_pure_windows_path,
tainted_pure_path, # $ MISSING: tainted
tainted_pure_posix_path, # $ MISSING: tainted
tainted_pure_windows_path, # $ MISSING: tainted
pathlib.Path("foo") / ts,
ts / pathlib.Path("foo"),
pathlib.Path("foo") / ts, # $ MISSING: tainted
ts / pathlib.Path("foo"), # $ MISSING: tainted
tainted_path.joinpath("foo", "bar"),
pathlib.Path("foo").joinpath(tainted_path, "bar"),
pathlib.Path("foo").joinpath("bar", tainted_path),
tainted_path.joinpath("foo", "bar"), # $ MISSING: tainted
pathlib.Path("foo").joinpath(tainted_path, "bar"), # $ MISSING: tainted
pathlib.Path("foo").joinpath("bar", tainted_path), # $ MISSING: tainted
str(tainted_path),
str(tainted_path), # $ MISSING: tainted
# TODO: Tainted methods and attributes
# https://docs.python.org/3.8/library/pathlib.html#methods-and-properties
@@ -46,13 +46,13 @@ def test_basic():
tainted_posix_path = pathlib.PosixPath(ts)
ensure_tainted(
tainted_posix_path,
tainted_posix_path, # $ MISSING: tainted
)
if os.name == "nt":
tainted_windows_path = pathlib.WindowsPath(ts)
ensure_tainted(
tainted_windows_path,
tainted_windows_path, # $ MISSING: tainted
)
# Make tests runable

View File

@@ -14,10 +14,10 @@ def str_methods():
ts = TAINTED_STRING
tb = TAINTED_BYTES
ensure_tainted(
ts.casefold(),
ts.casefold(), # $ tainted
ts.format_map({}),
"{unsafe}".format_map({"unsafe": ts}),
ts.format_map({}), # $ tainted
"{unsafe}".format_map({"unsafe": ts}), # $ tainted
)
@@ -28,16 +28,16 @@ def binary_decode_encode():
ensure_tainted(
# New in Python 3.4
base64.a85encode(tb),
base64.a85decode(base64.a85encode(tb)),
base64.a85encode(tb), # $ tainted
base64.a85decode(base64.a85encode(tb)), # $ tainted
# New in Python 3.4
base64.b85encode(tb),
base64.b85decode(base64.b85encode(tb)),
base64.b85encode(tb), # $ tainted
base64.b85decode(base64.b85encode(tb)), # $ tainted
# New in Python 3.1
base64.encodebytes(tb),
base64.decodebytes(base64.encodebytes(tb)),
base64.encodebytes(tb), # $ tainted
base64.decodebytes(base64.encodebytes(tb)), # $ tainted
)
@@ -45,7 +45,7 @@ def f_strings():
print("\n# f_strings")
ts = TAINTED_STRING
ensure_tainted(f"foo {ts} bar")
ensure_tainted(f"foo {ts} bar") # $ tainted
# Make tests runable

View File

@@ -15,12 +15,12 @@ if TYPE_CHECKING:
def extended_unpacking():
first, *rest, last = TAINTED_LIST
ensure_tainted(first, rest, last)
ensure_tainted(first, rest, last) # $ tainted
def also_allowed():
*a, = TAINTED_LIST
ensure_tainted(a)
ensure_tainted(a) # $ tainted
# for b, *c in [(1, 2, 3), (4, 5, 6, 7)]:
# print(c)
@@ -28,7 +28,7 @@ def also_allowed():
# i=1; c=[5,6,7]
for b, *c in [TAINTED_LIST, TAINTED_LIST]:
ensure_tainted(b, c)
ensure_tainted(b, c) # $ tainted
def nested():
@@ -36,7 +36,7 @@ def nested():
ll = [l,l]
[[x, *xs], ys] = ll
ensure_tainted(x, xs, ys)
ensure_tainted(x, xs, ys) # $ tainted
# Make tests runable

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -1,176 +0,0 @@
| test_collections.py:23 | ok | test_construction | tainted_string |
| test_collections.py:24 | ok | test_construction | tainted_list |
| test_collections.py:25 | ok | test_construction | tainted_tuple |
| test_collections.py:26 | ok | test_construction | tainted_set |
| test_collections.py:27 | ok | test_construction | tainted_dict |
| test_collections.py:31 | ok | test_construction | list(..) |
| test_collections.py:32 | ok | test_construction | list(..) |
| test_collections.py:33 | ok | test_construction | list(..) |
| test_collections.py:34 | ok | test_construction | list(..) |
| test_collections.py:35 | ok | test_construction | list(..) |
| test_collections.py:37 | ok | test_construction | tuple(..) |
| test_collections.py:38 | ok | test_construction | set(..) |
| test_collections.py:39 | ok | test_construction | frozenset(..) |
| test_collections.py:47 | ok | test_access | tainted_list[0] |
| test_collections.py:48 | ok | test_access | tainted_list[x] |
| test_collections.py:49 | ok | test_access | tainted_list[Slice] |
| test_collections.py:51 | ok | test_access | sorted(..) |
| test_collections.py:52 | ok | test_access | reversed(..) |
| test_collections.py:53 | ok | test_access | iter(..) |
| test_collections.py:54 | ok | test_access | next(..) |
| test_collections.py:58 | ok | test_access | a |
| test_collections.py:58 | ok | test_access | b |
| test_collections.py:58 | ok | test_access | c |
| test_collections.py:61 | ok | test_access | h |
| test_collections.py:63 | ok | test_access | i |
| test_collections.py:70 | ok | test_dict_access | tainted_dict["name"] |
| test_collections.py:71 | ok | test_dict_access | tainted_dict.get(..) |
| test_collections.py:72 | ok | test_dict_access | tainted_dict[x] |
| test_collections.py:73 | ok | test_dict_access | tainted_dict.copy() |
| test_collections.py:77 | ok | test_dict_access | v |
| test_collections.py:79 | ok | test_dict_access | v |
| test_collections.py:87 | fail | test_named_tuple | point[0] |
| test_collections.py:88 | fail | test_named_tuple | point.x |
| test_collections.py:92 | ok | test_named_tuple | point[1] |
| test_collections.py:93 | ok | test_named_tuple | point.y |
| test_collections.py:97 | fail | test_named_tuple | a |
| test_collections.py:98 | ok | test_named_tuple | b |
| test_collections.py:106 | fail | test_defaultdict | tainted_default_dict["name"] |
| test_collections.py:107 | fail | test_defaultdict | tainted_default_dict.get(..) |
| test_collections.py:108 | fail | test_defaultdict | tainted_default_dict[x] |
| test_collections.py:109 | fail | test_defaultdict | tainted_default_dict.copy() |
| test_collections.py:112 | fail | test_defaultdict | v |
| test_collections.py:114 | fail | test_defaultdict | v |
| test_collections.py:121 | ok | test_copy_1 | copy(..) |
| test_collections.py:122 | ok | test_copy_1 | deepcopy(..) |
| test_collections.py:130 | ok | test_copy_2 | copy.copy(..) |
| test_collections.py:131 | ok | test_copy_2 | copy.deepcopy(..) |
| test_collections.py:139 | ok | list_index_assign | my_list |
| test_collections.py:142 | fail | list_index_assign | my_list |
| test_collections.py:149 | ok | list_index_aug_assign | my_list |
| test_collections.py:152 | fail | list_index_aug_assign | my_list |
| test_collections.py:159 | ok | list_append | my_list |
| test_collections.py:162 | ok | list_append | my_list |
| test_collections.py:169 | ok | list_extend | my_list |
| test_collections.py:172 | fail | list_extend | my_list |
| test_collections.py:179 | ok | dict_update_dict | my_dict |
| test_collections.py:182 | fail | dict_update_dict | my_dict |
| test_collections.py:189 | ok | dict_update_kv_list | my_dict |
| test_collections.py:192 | fail | dict_update_kv_list | my_dict |
| test_collections.py:198 | ok | dict_update_kv_arg | my_dict |
| test_collections.py:201 | fail | dict_update_kv_arg | my_dict |
| test_collections.py:208 | ok | dict_manual_update | my_dict |
| test_collections.py:212 | fail | dict_manual_update | my_dict |
| test_collections.py:220 | fail | dict_merge | merged |
| test_collections.py:227 | ok | set_add | my_set |
| test_collections.py:230 | ok | set_add | my_set |
| test_json.py:26 | ok | test | json.dumps(..) |
| test_json.py:27 | ok | test | json.loads(..) |
| test_json.py:34 | fail | test | tainted_filelike |
| test_json.py:35 | fail | test | json.load(..) |
| test_json.py:48 | ok | non_syntacical | dumps(..) |
| test_json.py:49 | ok | non_syntacical | dumps_alias(..) |
| test_json.py:50 | ok | non_syntacical | loads(..) |
| test_json.py:57 | fail | non_syntacical | tainted_filelike |
| test_json.py:58 | fail | non_syntacical | load(..) |
| test_string.py:25 | ok | str_operations | ts |
| test_string.py:26 | ok | str_operations | BinaryExpr |
| test_string.py:27 | ok | str_operations | BinaryExpr |
| test_string.py:28 | ok | str_operations | BinaryExpr |
| test_string.py:29 | ok | str_operations | ts[Slice] |
| test_string.py:30 | ok | str_operations | ts[Slice] |
| test_string.py:31 | ok | str_operations | ts[Slice] |
| test_string.py:32 | ok | str_operations | ts[0] |
| test_string.py:33 | ok | str_operations | str(..) |
| test_string.py:34 | ok | str_operations | bytes(..) |
| test_string.py:35 | ok | str_operations | unicode(..) |
| test_string.py:39 | ok | str_operations | aug_assignment |
| test_string.py:41 | ok | str_operations | aug_assignment |
| test_string.py:49 | ok | str_methods | ts.capitalize() |
| test_string.py:50 | ok | str_methods | ts.center(..) |
| test_string.py:51 | ok | str_methods | ts.expandtabs() |
| test_string.py:53 | ok | str_methods | ts.format() |
| test_string.py:54 | ok | str_methods | "{}".format(..) |
| test_string.py:55 | ok | str_methods | "{unsafe}".format(..) |
| test_string.py:57 | ok | str_methods | ts.join(..) |
| test_string.py:58 | ok | str_methods | "".join(..) |
| test_string.py:60 | ok | str_methods | ts.ljust(..) |
| test_string.py:61 | ok | str_methods | ts.lstrip() |
| test_string.py:62 | ok | str_methods | ts.lower() |
| test_string.py:64 | ok | str_methods | ts.replace(..) |
| test_string.py:65 | ok | str_methods | "safe".replace(..) |
| test_string.py:67 | ok | str_methods | ts.rjust(..) |
| test_string.py:68 | ok | str_methods | ts.rstrip() |
| test_string.py:69 | ok | str_methods | ts.strip() |
| test_string.py:70 | ok | str_methods | ts.swapcase() |
| test_string.py:71 | ok | str_methods | ts.title() |
| test_string.py:72 | ok | str_methods | ts.upper() |
| test_string.py:73 | ok | str_methods | ts.zfill(..) |
| test_string.py:75 | ok | str_methods | ts.encode(..) |
| test_string.py:76 | ok | str_methods | ts.encode(..).decode(..) |
| test_string.py:78 | ok | str_methods | tb.decode(..) |
| test_string.py:79 | ok | str_methods | tb.decode(..).encode(..) |
| test_string.py:82 | ok | str_methods | ts.partition(..) |
| test_string.py:83 | ok | str_methods | ts.rpartition(..) |
| test_string.py:84 | ok | str_methods | ts.rsplit(..) |
| test_string.py:85 | ok | str_methods | ts.split(..) |
| test_string.py:86 | ok | str_methods | ts.splitlines() |
| test_string.py:91 | ok | str_methods | "safe".replace(..) |
| test_string.py:93 | fail | str_methods | ts.join(..) |
| test_string.py:94 | fail | str_methods | ts.join(..) |
| test_string.py:104 | fail | non_syntactic | meth() |
| test_string.py:105 | fail | non_syntactic | _str(..) |
| test_string.py:114 | ok | percent_fmt | BinaryExpr |
| test_string.py:115 | ok | percent_fmt | BinaryExpr |
| test_string.py:116 | ok | percent_fmt | BinaryExpr |
| test_string.py:126 | ok | binary_decode_encode | base64.b64encode(..) |
| test_string.py:127 | ok | binary_decode_encode | base64.b64decode(..) |
| test_string.py:129 | ok | binary_decode_encode | base64.standard_b64encode(..) |
| test_string.py:130 | ok | binary_decode_encode | base64.standard_b64decode(..) |
| test_string.py:132 | ok | binary_decode_encode | base64.urlsafe_b64encode(..) |
| test_string.py:133 | ok | binary_decode_encode | base64.urlsafe_b64decode(..) |
| test_string.py:135 | ok | binary_decode_encode | base64.b32encode(..) |
| test_string.py:136 | ok | binary_decode_encode | base64.b32decode(..) |
| test_string.py:138 | ok | binary_decode_encode | base64.b16encode(..) |
| test_string.py:139 | ok | binary_decode_encode | base64.b16decode(..) |
| test_string.py:142 | ok | binary_decode_encode | base64.encodestring(..) |
| test_string.py:143 | ok | binary_decode_encode | base64.decodestring(..) |
| test_string.py:148 | fail | binary_decode_encode | quopri.encodestring(..) |
| test_string.py:149 | fail | binary_decode_encode | quopri.decodestring(..) |
| test_string.py:159 | ok | test_os_path_join | os.path.join(..) |
| test_string.py:160 | ok | test_os_path_join | os.path.join(..) |
| test_string.py:161 | ok | test_os_path_join | os.path.join(..) |
| test_string.py:162 | ok | test_os_path_join | ospath_alias.join(..) |
| test_unpacking.py:16 | ok | unpacking | a |
| test_unpacking.py:16 | ok | unpacking | b |
| test_unpacking.py:16 | ok | unpacking | c |
| test_unpacking.py:22 | ok | unpacking_to_list | a |
| test_unpacking.py:22 | ok | unpacking_to_list | b |
| test_unpacking.py:22 | ok | unpacking_to_list | c |
| test_unpacking.py:31 | ok | nested | a1 |
| test_unpacking.py:31 | ok | nested | a2 |
| test_unpacking.py:31 | ok | nested | a3 |
| test_unpacking.py:31 | ok | nested | b |
| test_unpacking.py:31 | ok | nested | c |
| test_unpacking.py:35 | ok | nested | a1 |
| test_unpacking.py:35 | ok | nested | a2 |
| test_unpacking.py:35 | ok | nested | a3 |
| test_unpacking.py:35 | ok | nested | b |
| test_unpacking.py:35 | ok | nested | c |
| test_unpacking.py:39 | ok | nested | a1 |
| test_unpacking.py:39 | ok | nested | a2 |
| test_unpacking.py:39 | ok | nested | a3 |
| test_unpacking.py:39 | ok | nested | b |
| test_unpacking.py:39 | ok | nested | c |
| test_unpacking.py:46 | ok | unpack_from_set | a |
| test_unpacking.py:46 | ok | unpack_from_set | b |
| test_unpacking.py:46 | ok | unpack_from_set | c |
| test_unpacking.py:55 | ok | contrived_1 | a |
| test_unpacking.py:55 | ok | contrived_1 | b |
| test_unpacking.py:55 | ok | contrived_1 | c |
| test_unpacking.py:56 | fail | contrived_1 | d |
| test_unpacking.py:56 | fail | contrived_1 | e |
| test_unpacking.py:56 | fail | contrived_1 | f |
| test_unpacking.py:65 | ok | contrived_2 | a |
| test_unpacking.py:65 | ok | contrived_2 | b |
| test_unpacking.py:65 | ok | contrived_2 | c |

View File

@@ -1 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib

View File

@@ -20,23 +20,23 @@ def test_construction():
tainted_dict = {'key': tainted_string}
ensure_tainted(
tainted_string,
tainted_list,
tainted_tuple,
tainted_set,
tainted_dict,
tainted_string, # $ tainted
tainted_list, # $ tainted
tainted_tuple, # $ tainted
tainted_set, # $ tainted
tainted_dict, # $ tainted
)
ensure_tainted(
list(tainted_list),
list(tainted_tuple),
list(tainted_set),
list(tainted_dict.values()),
list(tainted_dict.items()),
list(tainted_list), # $ tainted
list(tainted_tuple), # $ tainted
list(tainted_set), # $ tainted
list(tainted_dict.values()), # $ tainted
list(tainted_dict.items()), # $ tainted
tuple(tainted_list),
set(tainted_list),
frozenset(tainted_list),
tuple(tainted_list), # $ tainted
set(tainted_list), # $ tainted
frozenset(tainted_list), # $ tainted
)
@@ -44,39 +44,39 @@ def test_access(x, y, z):
tainted_list = TAINTED_LIST
ensure_tainted(
tainted_list[0],
tainted_list[x],
tainted_list[y:z],
tainted_list[0], # $ tainted
tainted_list[x], # $ tainted
tainted_list[y:z], # $ tainted
sorted(tainted_list),
reversed(tainted_list),
iter(tainted_list),
next(iter(tainted_list)),
sorted(tainted_list), # $ tainted
reversed(tainted_list), # $ tainted
iter(tainted_list), # $ tainted
next(iter(tainted_list)), # $ tainted
)
a, b, c = tainted_list[0:3]
ensure_tainted(a, b, c)
ensure_tainted(a, b, c) # $ tainted
for h in tainted_list:
ensure_tainted(h)
ensure_tainted(h) # $ tainted
for i in reversed(tainted_list):
ensure_tainted(i)
ensure_tainted(i) # $ tainted
def test_dict_access(x):
tainted_dict = TAINTED_DICT
ensure_tainted(
tainted_dict["name"],
tainted_dict.get("name"),
tainted_dict[x],
tainted_dict.copy(),
tainted_dict["name"], # $ tainted
tainted_dict.get("name"), # $ tainted
tainted_dict[x], # $ tainted
tainted_dict.copy(), # $ tainted
)
for v in tainted_dict.values():
ensure_tainted(v)
ensure_tainted(v) # $ tainted
for k, v in tainted_dict.items():
ensure_tainted(v)
ensure_tainted(v) # $ tainted
def test_named_tuple(): # TODO: namedtuple currently not handled
@@ -84,8 +84,8 @@ def test_named_tuple(): # TODO: namedtuple currently not handled
point = Point(TAINTED_STRING, 'safe')
ensure_tainted(
point[0],
point.x,
point[0], # $ MISSING: tainted
point.x, # $ MISSING: tainted
)
ensure_not_tainted(
@@ -94,7 +94,7 @@ def test_named_tuple(): # TODO: namedtuple currently not handled
)
a, b = point
ensure_tainted(a)
ensure_tainted(a) # $ MISSING: tainted
ensure_not_tainted(b)
@@ -103,23 +103,23 @@ def test_defaultdict(key, x): # TODO: defaultdict currently not handled
tainted_default_dict[key] += TAINTED_STRING
ensure_tainted(
tainted_default_dict["name"],
tainted_default_dict.get("name"),
tainted_default_dict[x],
tainted_default_dict.copy(),
tainted_default_dict["name"], # $ MISSING: tainted
tainted_default_dict.get("name"), # $ MISSING: tainted
tainted_default_dict[x], # $ MISSING: tainted
tainted_default_dict.copy(), # $ MISSING: tainted
)
for v in tainted_default_dict.values():
ensure_tainted(v)
ensure_tainted(v) # $ MISSING: tainted
for k, v in tainted_default_dict.items():
ensure_tainted(v)
ensure_tainted(v) # $ MISSING: tainted
def test_copy_1():
from copy import copy, deepcopy
ensure_tainted(
copy(TAINTED_LIST),
deepcopy(TAINTED_LIST),
copy(TAINTED_LIST), # $ tainted
deepcopy(TAINTED_LIST), # $ tainted
)
@@ -127,8 +127,8 @@ def test_copy_2():
import copy
ensure_tainted(
copy.copy(TAINTED_LIST),
copy.deepcopy(TAINTED_LIST),
copy.copy(TAINTED_LIST), # $ tainted
copy.deepcopy(TAINTED_LIST), # $ tainted
)
@@ -139,7 +139,7 @@ def list_index_assign():
ensure_not_tainted(my_list)
my_list[0] = tainted_string
ensure_tainted(my_list)
ensure_tainted(my_list) # $ MISSING: tainted
def list_index_aug_assign():
@@ -149,7 +149,7 @@ def list_index_aug_assign():
ensure_not_tainted(my_list)
my_list[0] += tainted_string
ensure_tainted(my_list)
ensure_tainted(my_list) # $ MISSING: tainted
def list_append():
@@ -159,7 +159,7 @@ def list_append():
ensure_not_tainted(my_list)
my_list.append(tainted_string)
ensure_tainted(my_list)
ensure_tainted(my_list) # $ tainted
def list_extend():
@@ -169,7 +169,7 @@ def list_extend():
ensure_not_tainted(my_list)
my_list.extend(tainted_list)
ensure_tainted(my_list)
ensure_tainted(my_list) # $ MISSING: tainted
def dict_update_dict():
@@ -179,7 +179,7 @@ def dict_update_dict():
ensure_not_tainted(my_dict)
my_dict.update(tainted_dict)
ensure_tainted(my_dict)
ensure_tainted(my_dict) # $ MISSING: tainted
def dict_update_kv_list():
@@ -189,7 +189,7 @@ def dict_update_kv_list():
ensure_not_tainted(my_dict)
my_dict.update(tainted_kv_list)
ensure_tainted(my_dict)
ensure_tainted(my_dict) # $ MISSING: tainted
def dict_update_kv_arg():
@@ -198,7 +198,7 @@ def dict_update_kv_arg():
ensure_not_tainted(my_dict)
my_dict.update(key2=TAINTED_STRING)
ensure_tainted(my_dict)
ensure_tainted(my_dict) # $ MISSING: tainted
def dict_manual_update():
@@ -209,7 +209,7 @@ def dict_manual_update():
for k in tainted_dict:
my_dict[k] = tainted_dict[k]
ensure_tainted(my_dict)
ensure_tainted(my_dict) # $ MISSING: tainted
def dict_merge():
@@ -217,7 +217,7 @@ def dict_merge():
tainted_dict = {"key2": TAINTED_STRING}
merged = {**my_dict, **tainted_dict}
ensure_tainted(merged)
ensure_tainted(merged) # $ MISSING: tainted
def set_add():
@@ -227,7 +227,7 @@ def set_add():
ensure_not_tainted(my_set)
my_set.add(tainted_string)
ensure_tainted(my_set)
ensure_tainted(my_set) # $ tainted
# Make tests runable

View File

@@ -23,16 +23,16 @@ def test():
import json
ensure_tainted(
json.dumps(ts),
json.loads(json.dumps(ts)),
json.dumps(ts), # $ tainted
json.loads(json.dumps(ts)), # $ tainted
)
# For Python2, need to convert to unicode for StringIO to work
tainted_filelike = StringIO(unicode(json.dumps(ts)))
ensure_tainted(
tainted_filelike,
json.load(tainted_filelike),
tainted_filelike, # $ MISSING: tainted
json.load(tainted_filelike), # $ MISSING: tainted
)
def non_syntacical():
@@ -45,17 +45,17 @@ def non_syntacical():
dumps_alias = dumps
ensure_tainted(
dumps(ts),
dumps_alias(ts),
loads(dumps(ts)),
dumps(ts), # $ tainted
dumps_alias(ts), # $ tainted
loads(dumps(ts)), # $ tainted
)
# For Python2, need to convert to unicode for StringIO to work
tainted_filelike = StringIO(unicode(dumps(ts)))
ensure_tainted(
tainted_filelike,
load(tainted_filelike),
tainted_filelike, # $ MISSING: tainted
load(tainted_filelike), # $ MISSING: tainted
)
# Make tests runable

View File

@@ -22,23 +22,23 @@ def str_operations():
tb = TAINTED_BYTES
ensure_tainted(
ts,
ts + "foo",
"foo" + ts,
ts * 5,
ts[0 : len(ts)],
ts[:],
ts[0:1000],
ts[0],
str(ts),
bytes(tb),
unicode(ts),
ts, # $ tainted
ts + "foo", # $ tainted
"foo" + ts, # $ tainted
ts * 5, # $ tainted
ts[0 : len(ts)], # $ tainted
ts[:], # $ tainted
ts[0:1000], # $ tainted
ts[0], # $ tainted
str(ts), # $ tainted
bytes(tb), # $ tainted
unicode(ts), # $ tainted
)
aug_assignment = "safe"
ensure_not_tainted(aug_assignment)
aug_assignment += TAINTED_STRING
ensure_tainted(aug_assignment)
ensure_tainted(aug_assignment) # $ tainted
def str_methods():
@@ -46,52 +46,54 @@ def str_methods():
ts = TAINTED_STRING
tb = TAINTED_BYTES
ensure_tainted(
ts.capitalize(),
ts.center(100),
ts.expandtabs(),
ts.capitalize(), # $ tainted
ts.center(100), # $ tainted
ts.expandtabs(), # $ tainted
ts.format(),
"{}".format(ts),
"{unsafe}".format(unsafe=ts),
ts.format(), # $ tainted
"{}".format(ts), # $ tainted
"{unsafe}".format(unsafe=ts), # $ tainted
ts.join(["", ""]),
"".join([ts]),
ts.join(["", ""]), # $ tainted
"".join([ts]), # $ tainted
ts.ljust(100),
ts.lstrip(),
ts.lower(),
ts.ljust(100), # $ tainted
ts.lstrip(), # $ tainted
ts.lower(), # $ tainted
ts.replace("old", "new"),
"safe".replace("safe", ts),
ts.replace("old", "new"), # $ tainted
"safe".replace("safe", ts), # $ tainted
ts.rjust(100),
ts.rstrip(),
ts.strip(),
ts.swapcase(),
ts.title(),
ts.upper(),
ts.zfill(100),
ts.rjust(100), # $ tainted
ts.rstrip(), # $ tainted
ts.strip(), # $ tainted
ts.swapcase(), # $ tainted
ts.title(), # $ tainted
ts.upper(), # $ tainted
ts.zfill(100), # $ tainted
ts.encode("utf-8"),
ts.encode("utf-8").decode("utf-8"),
ts.encode("utf-8"), # $ tainted
ts.encode("utf-8").decode("utf-8"), # $ tainted
tb.decode("utf-8"),
tb.decode("utf-8").encode("utf-8"),
tb.decode("utf-8"), # $ tainted
tb.decode("utf-8").encode("utf-8"), # $ tainted
# string methods that return a list
ts.partition("_"),
ts.rpartition("_"),
ts.rsplit("_"),
ts.split("_"),
ts.splitlines(),
ts.partition("_"), # $ tainted
ts.rpartition("_"), # $ tainted
ts.rsplit("_"), # $ tainted
ts.split("_"), # $ tainted
ts.splitlines(), # $ tainted
)
ensure_not_tainted(
# Intuitively I think this should be safe, but better discuss it
"safe".replace(ts, "also-safe"),
ts.join([]), # FP due to separator not being used with zero/one elements
ts.join(["safe"]), # FP due to separator not being used with zero/one elements
# FPs due to separator (`ts`) not ending up in result, when the list only has
# zero/one elements
ts.join([]), # $ SPURIOUS: tainted
ts.join(["safe"]), # $ SPURIOUS: tainted
)
@@ -101,8 +103,8 @@ def non_syntactic():
meth = ts.upper
_str = str
ensure_tainted(
meth(),
_str(ts),
meth(), # $ MISSING: tainted
_str(ts), # $ MISSING: tainted
)
@@ -111,9 +113,9 @@ def percent_fmt():
ts = TAINTED_STRING
tainted_fmt = ts + " %s %s"
ensure_tainted(
tainted_fmt % (1, 2),
"%s foo bar" % ts,
"%s %s %s" % (1, 2, ts),
tainted_fmt % (1, 2), # $ tainted
"%s foo bar" % ts, # $ tainted
"%s %s %s" % (1, 2, ts), # $ tainted
)
@@ -123,30 +125,30 @@ def binary_decode_encode():
import base64
ensure_tainted(
base64.b64encode(tb),
base64.b64decode(base64.b64encode(tb)),
base64.b64encode(tb), # $ tainted
base64.b64decode(base64.b64encode(tb)), # $ tainted
base64.standard_b64encode(tb),
base64.standard_b64decode(base64.standard_b64encode(tb)),
base64.standard_b64encode(tb), # $ tainted
base64.standard_b64decode(base64.standard_b64encode(tb)), # $ tainted
base64.urlsafe_b64encode(tb),
base64.urlsafe_b64decode(base64.urlsafe_b64encode(tb)),
base64.urlsafe_b64encode(tb), # $ tainted
base64.urlsafe_b64decode(base64.urlsafe_b64encode(tb)), # $ tainted
base64.b32encode(tb),
base64.b32decode(base64.b32encode(tb)),
base64.b32encode(tb), # $ tainted
base64.b32decode(base64.b32encode(tb)), # $ tainted
base64.b16encode(tb),
base64.b16decode(base64.b16encode(tb)),
base64.b16encode(tb), # $ tainted
base64.b16decode(base64.b16encode(tb)), # $ tainted
# deprecated since Python 3.1, but still works
base64.encodestring(tb),
base64.decodestring(base64.encodestring(tb)),
base64.encodestring(tb), # $ tainted
base64.decodestring(base64.encodestring(tb)), # $ tainted
)
import quopri
ensure_tainted(
quopri.encodestring(tb),
quopri.decodestring(quopri.encodestring(tb)),
quopri.encodestring(tb), # $ MISSING: tainted
quopri.decodestring(quopri.encodestring(tb)), # $ MISSING: tainted
)
@@ -156,10 +158,10 @@ def test_os_path_join():
print("\n# test_os_path_join")
ts = TAINTED_STRING
ensure_tainted(
os.path.join(ts, "foo", "bar"),
os.path.join(ts),
os.path.join("foo", "bar", ts),
ospath_alias.join("foo", "bar", ts),
os.path.join(ts, "foo", "bar"), # $ tainted
os.path.join(ts), # $ tainted
os.path.join("foo", "bar", ts), # $ tainted
ospath_alias.join("foo", "bar", ts), # $ tainted
)

View File

@@ -13,13 +13,13 @@ if TYPE_CHECKING:
def unpacking():
l = TAINTED_LIST[0:3]
a, b, c = l
ensure_tainted(a, b, c)
ensure_tainted(a, b, c) # $ tainted
def unpacking_to_list():
l = TAINTED_LIST[0:3]
[a, b, c] = l
ensure_tainted(a, b, c)
ensure_tainted(a, b, c) # $ tainted
def nested():
@@ -28,22 +28,22 @@ def nested():
# list
[[a1, a2, a3], b, c] = ll
ensure_tainted(a1, a2, a3, b, c)
ensure_tainted(a1, a2, a3, b, c) # $ tainted
# tuple
((a1, a2, a3), b, c) = ll
ensure_tainted(a1, a2, a3, b, c)
ensure_tainted(a1, a2, a3, b, c) # $ tainted
# mixed
[(a1, a2, a3), b, c] = ll
ensure_tainted(a1, a2, a3, b, c)
ensure_tainted(a1, a2, a3, b, c) # $ tainted
def unpack_from_set():
# no guarantee on ordering ... don't know why you would ever do this
a, b, c = {"foo", "bar", TAINTED_STRING}
# either all should be tainted, or none of them
ensure_tainted(a, b, c)
ensure_tainted(a, b, c) # $ tainted
def contrived_1():
@@ -52,8 +52,8 @@ def contrived_1():
no_taint_list = [1,2,3]
(a, b, c), (d, e, f) = tainted_list, no_taint_list
ensure_tainted(a, b, c)
ensure_not_tainted(d, e, f) # FP: we mark `d`, `e` and `f` as tainted.
ensure_tainted(a, b, c) # $ tainted
ensure_not_tainted(d, e, f) # $ SPURIOUS: tainted
def contrived_2():
@@ -62,7 +62,7 @@ def contrived_2():
# Old taint tracking was only able to handle taint nested 2 levels in sequences,
# so would not mark a, b, c as tainted
[[[ (a, b, c) ]]] = [[[ TAINTED_LIST[0:3] ]]]
ensure_tainted(a, b, c)
ensure_tainted(a, b, c) # $ tainted
# Make tests runable

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -25,14 +25,14 @@ initially_tainted = NOT_TAINTED
ensure_not_tainted(initially_tainted)
def use_of_initially_tainted():
ensure_not_tainted(initially_tainted) # FP
ensure_not_tainted(initially_tainted) # $ SPURIOUS: tainted
# A very similar case to the above, but here we _do_ want taint flow, because the initially tainted
# value is actually used before it gets reassigned to an untainted value.
# value is actually used before it gets reassigned to an untainted value.
def use_of_initially_tainted2():
ensure_tainted(initially_tainted2)
ensure_tainted(initially_tainted2) # $ tainted
initially_tainted2 = TAINTED_STRING
use_of_initially_tainted2()
@@ -47,7 +47,7 @@ def write_tainted():
g = TAINTED_STRING
def sink_global():
ensure_tainted(g)
ensure_tainted(g) # $ tainted
write_global()
write_tainted()

View File

@@ -1,6 +0,0 @@
| test.py:12 | ok | test | tainted_later |
| test.py:25 | ok | test | initially_tainted |
| test.py:28 | fail | use_of_initially_tainted | initially_tainted |
| test.py:35 | ok | use_of_initially_tainted2 | initially_tainted2 |
| test.py:40 | ok | test | initially_tainted2 |
| test.py:50 | ok | sink_global | g |

View File

@@ -1 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib

View File

@@ -1,6 +1,7 @@
module_tracker
| import_as_attr.py:1:6:1:11 | ControlFlowNode for ImportExpr |
module_attr_tracker
| import_as_attr.py:0:0:0:0 | ModuleVariableNode for Global Variable attr_ref in Module import_as_attr |
| import_as_attr.py:1:20:1:35 | ControlFlowNode for ImportMember |
| import_as_attr.py:1:28:1:35 | GSSA Variable attr_ref |
| import_as_attr.py:3:1:3:1 | GSSA Variable x |

View File

@@ -0,0 +1,94 @@
/**
* Defines a InlineExpectationsTest for checking whether any arguments in
* `ensure_tainted` and `ensure_not_tainted` calls are tainted.
*
* Also defines query predicates to ensure that:
* - if any arguments to `ensure_not_tainted` are tainted, their annotation is marked with `SPURIOUS`.
* - if any arguments to `ensure_tainted` are not tainted, their annotation is marked with `MISSING`.
*
* The functionality of this module is tested in `ql/test/experimental/meta/inline-taint-test-demo`.
*/
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
import TestUtilities.InlineExpectationsTest
import experimental.dataflow.TestUtil.PrintNode
DataFlow::Node shouldBeTainted() {
exists(DataFlow::CallCfgNode call |
call.getFunction().asCfgNode().(NameNode).getId() = "ensure_tainted" and
result in [call.getArg(_), call.getArgByName(_)]
)
}
DataFlow::Node shouldNotBeTainted() {
exists(DataFlow::CallCfgNode call |
call.getFunction().asCfgNode().(NameNode).getId() = "ensure_not_tainted" and
result in [call.getArg(_), call.getArgByName(_)]
)
}
class TestTaintTrackingConfiguration extends TaintTracking::Configuration {
TestTaintTrackingConfiguration() { this = "TestTaintTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
source.asCfgNode().(NameNode).getId() in [
"TAINTED_STRING", "TAINTED_BYTES", "TAINTED_LIST", "TAINTED_DICT"
]
or
source instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
sink = shouldBeTainted()
or
sink = shouldNotBeTainted()
}
}
class InlineTaintTest extends InlineExpectationsTest {
InlineTaintTest() { this = "InlineTaintTest" }
override string getARelevantTag() { result = "tainted" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(DataFlow::Node sink |
any(TestTaintTrackingConfiguration config).hasFlow(_, sink) and
location = sink.getLocation() and
element = prettyExp(sink.asExpr()) and
value = "" and
tag = "tainted"
)
}
}
query predicate argumentToEnsureNotTaintedNotMarkedAsSpurious(
Location location, string error, string element
) {
error = "ERROR, you should add `SPURIOUS:` to this annotation" and
location = shouldNotBeTainted().getLocation() and
any(InlineTaintTest test).hasActualResult(location, element, "tainted", _) and
exists(GoodExpectation good, ActualResult actualResult |
good.matchesActualResult(actualResult) and
actualResult.getLocation() = location and
actualResult.toString() = element
)
}
query predicate untaintedArgumentToEnsureTaintedNotMarkedAsMissing(
Location location, string error, string element
) {
error = "ERROR, you should add `# $ MISSING: tainted` annotation" and
exists(DataFlow::Node sink |
sink = shouldBeTainted() and
element = prettyExp(sink.asExpr()) and
not any(TestTaintTrackingConfiguration config).hasFlow(_, sink) and
location = sink.getLocation() and
not exists(FalseNegativeExpectation missingResult |
missingResult.getLocation().getStartLine() = location.getStartLine()
)
)
}

View File

@@ -0,0 +1,7 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
| taint_test.py:48:9:48:29 | taint_test.py:48 | ERROR, you should add `SPURIOUS:` to this annotation | should_not_be_tainted |
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
| taint_test.py:32:9:32:25 | taint_test.py:32 | ERROR, you should add `# $ MISSING: tainted` annotation | should_be_tainted |
| taint_test.py:37:24:37:40 | taint_test.py:37 | ERROR, you should add `# $ MISSING: tainted` annotation | should_be_tainted |
failures
| taint_test.py:41:20:41:21 | ts | Fixed missing result:tainted= |

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -0,0 +1,49 @@
def expected_usage():
ts = TAINTED_STRING
# simulating handling something we _want_ to treat at tainted, but we currently treat as untainted
should_be_tainted = "pretend this is unsafe"
ensure_tainted(
ts, # $ tainted
should_be_tainted, # $ MISSING: tainted
)
# having one annotation for multiple arguments is OK, as long as all arguments
# fulfil the same annotation
ensure_tainted(ts, ts) # $ tainted
# simulating handling something we _want_ to treat at untainted, but we currently treat as tainted
should_not_be_tainted = "pretend this is now safe" + ts
ensure_not_tainted(
should_not_be_tainted, # $ SPURIOUS: tainted
"FOO"
)
def bad_usage():
ts = TAINTED_STRING
# simulating handling something we _want_ to treat at tainted, but we currently treat as untainted
should_be_tainted = "pretend this is unsafe"
# This element _should_ have a `$ MISSING: tainted` annotation, which will be alerted in the .expected output
ensure_tainted(
should_be_tainted,
)
# using one annotation for multiple arguments i not OK when it's mixed whether our
# taint-tracking works as expected
ensure_tainted(ts, should_be_tainted) # $ tainted
# if you try to get around it by adding BOTH annotations, that results in a problem
# from the default set of inline-test-expectation rules
ensure_tainted(ts, should_be_tainted) # $ tainted MISSING: tainted
# simulating handling something we _want_ to treat at untainted, but we currently treat as tainted
should_not_be_tainted = "pretend this is now safe" + ts
# This annotation _should_ have used `SPURIOUS`, which will be alerted on in the .expected output
ensure_not_tainted(
should_not_be_tainted # $ tainted
)

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -1,100 +0,0 @@
| response_test.py:61 | ok | get_redirect_url | foo |
| taint_forms.py:6 | ok | to_python | value |
| taint_forms.py:9 | ok | validate | value |
| taint_forms.py:12 | ok | run_validators | value |
| taint_forms.py:15 | ok | clean | value |
| taint_forms.py:33 | ok | clean | cleaned_data |
| taint_forms.py:34 | ok | clean | cleaned_data["key"] |
| taint_forms.py:35 | ok | clean | cleaned_data.get(..) |
| taint_forms.py:39 | ok | clean | self.cleaned_data |
| taint_forms.py:40 | ok | clean | self.cleaned_data["key"] |
| taint_forms.py:41 | ok | clean | self.cleaned_data.get(..) |
| taint_forms.py:46 | ok | clean_foo | self.cleaned_data |
| taint_test.py:8 | ok | test_taint | bar |
| taint_test.py:8 | ok | test_taint | foo |
| taint_test.py:9 | ok | test_taint | baz |
| taint_test.py:15 | ok | test_taint | request |
| taint_test.py:17 | ok | test_taint | request.body |
| taint_test.py:18 | ok | test_taint | request.path |
| taint_test.py:19 | ok | test_taint | request.path_info |
| taint_test.py:23 | ok | test_taint | request.method |
| taint_test.py:25 | ok | test_taint | request.encoding |
| taint_test.py:26 | ok | test_taint | request.content_type |
| taint_test.py:29 | ok | test_taint | request.content_params |
| taint_test.py:30 | ok | test_taint | request.content_params["key"] |
| taint_test.py:31 | ok | test_taint | request.content_params.get(..) |
| taint_test.py:35 | ok | test_taint | request.GET |
| taint_test.py:36 | ok | test_taint | request.GET["key"] |
| taint_test.py:37 | ok | test_taint | request.GET.get(..) |
| taint_test.py:38 | fail | test_taint | request.GET.getlist(..) |
| taint_test.py:39 | fail | test_taint | request.GET.getlist(..)[0] |
| taint_test.py:40 | ok | test_taint | request.GET.pop(..) |
| taint_test.py:41 | ok | test_taint | request.GET.pop(..)[0] |
| taint_test.py:42 | ok | test_taint | request.GET.popitem()[0] |
| taint_test.py:43 | ok | test_taint | request.GET.popitem()[1] |
| taint_test.py:44 | ok | test_taint | request.GET.popitem()[1][0] |
| taint_test.py:45 | fail | test_taint | request.GET.dict() |
| taint_test.py:46 | fail | test_taint | request.GET.dict()["key"] |
| taint_test.py:47 | fail | test_taint | request.GET.urlencode() |
| taint_test.py:50 | ok | test_taint | request.POST |
| taint_test.py:53 | ok | test_taint | request.COOKIES |
| taint_test.py:54 | ok | test_taint | request.COOKIES["key"] |
| taint_test.py:55 | ok | test_taint | request.COOKIES.get(..) |
| taint_test.py:58 | ok | test_taint | request.FILES |
| taint_test.py:59 | ok | test_taint | request.FILES["key"] |
| taint_test.py:60 | fail | test_taint | request.FILES["key"].content_type |
| taint_test.py:61 | fail | test_taint | request.FILES["key"].content_type_extra |
| taint_test.py:62 | fail | test_taint | request.FILES["key"].content_type_extra["key"] |
| taint_test.py:63 | fail | test_taint | request.FILES["key"].charset |
| taint_test.py:64 | fail | test_taint | request.FILES["key"].name |
| taint_test.py:65 | fail | test_taint | request.FILES["key"].file |
| taint_test.py:66 | fail | test_taint | request.FILES["key"].file.read() |
| taint_test.py:68 | ok | test_taint | request.FILES.get(..) |
| taint_test.py:69 | fail | test_taint | request.FILES.get(..).name |
| taint_test.py:70 | fail | test_taint | request.FILES.getlist(..) |
| taint_test.py:71 | fail | test_taint | request.FILES.getlist(..)[0] |
| taint_test.py:72 | fail | test_taint | request.FILES.getlist(..)[0].name |
| taint_test.py:73 | fail | test_taint | request.FILES.dict() |
| taint_test.py:74 | fail | test_taint | request.FILES.dict()["key"] |
| taint_test.py:75 | fail | test_taint | request.FILES.dict()["key"].name |
| taint_test.py:78 | ok | test_taint | request.META |
| taint_test.py:79 | ok | test_taint | request.META["HTTP_USER_AGENT"] |
| taint_test.py:80 | ok | test_taint | request.META.get(..) |
| taint_test.py:83 | ok | test_taint | request.headers |
| taint_test.py:84 | ok | test_taint | request.headers["user-agent"] |
| taint_test.py:85 | ok | test_taint | request.headers["USER_AGENT"] |
| taint_test.py:88 | ok | test_taint | request.resolver_match |
| taint_test.py:89 | fail | test_taint | request.resolver_match.args |
| taint_test.py:90 | fail | test_taint | request.resolver_match.args[0] |
| taint_test.py:91 | fail | test_taint | request.resolver_match.kwargs |
| taint_test.py:92 | fail | test_taint | request.resolver_match.kwargs["key"] |
| taint_test.py:94 | fail | test_taint | request.get_full_path() |
| taint_test.py:95 | fail | test_taint | request.get_full_path_info() |
| taint_test.py:99 | fail | test_taint | request.read() |
| taint_test.py:100 | fail | test_taint | request.readline() |
| taint_test.py:101 | fail | test_taint | request.readlines() |
| taint_test.py:102 | fail | test_taint | request.readlines()[0] |
| taint_test.py:103 | fail | test_taint | ListComp |
| taint_test.py:109 | ok | test_taint | args |
| taint_test.py:110 | ok | test_taint | args[0] |
| taint_test.py:111 | ok | test_taint | kwargs |
| taint_test.py:112 | ok | test_taint | kwargs["key"] |
| taint_test.py:116 | ok | test_taint | request.current_app |
| taint_test.py:121 | ok | test_taint | request.get_host() |
| taint_test.py:122 | ok | test_taint | request.get_port() |
| taint_test.py:129 | fail | test_taint | request.build_absolute_uri() |
| taint_test.py:130 | fail | test_taint | request.build_absolute_uri(..) |
| taint_test.py:131 | fail | test_taint | request.build_absolute_uri(..) |
| taint_test.py:134 | ok | test_taint | request.build_absolute_uri(..) |
| taint_test.py:135 | ok | test_taint | request.build_absolute_uri(..) |
| taint_test.py:143 | ok | test_taint | request.get_signed_cookie(..) |
| taint_test.py:144 | ok | test_taint | request.get_signed_cookie(..) |
| taint_test.py:145 | ok | test_taint | request.get_signed_cookie(..) |
| taint_test.py:149 | fail | test_taint | request.get_signed_cookie(..) |
| taint_test.py:150 | fail | test_taint | request.get_signed_cookie(..) |
| taint_test.py:157 | ok | some_method | self.request |
| taint_test.py:158 | ok | some_method | self.request.GET["key"] |
| taint_test.py:160 | ok | some_method | self.args |
| taint_test.py:161 | ok | some_method | self.args[0] |
| taint_test.py:163 | ok | some_method | self.kwargs |
| taint_test.py:164 | ok | some_method | self.kwargs["key"] |

View File

@@ -1,6 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib
import semmle.python.dataflow.new.RemoteFlowSources
class RemoteFlowTestTaintConfiguration extends TestTaintTrackingConfiguration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
}

View File

@@ -67,7 +67,7 @@ def redirect_shortcut(request):
class CustomRedirectView(RedirectView):
def get_redirect_url(self, foo): # $ requestHandler routedParameter=foo
ensure_tainted(foo)
ensure_tainted(foo) # $ tainted
next = "https://example.com/{}".format(foo)
return next # $ HttpResponse HttpRedirectResponse redirectLocation=next

View File

@@ -3,16 +3,16 @@ import django.forms
class MyField(django.forms.Field):
def to_python(self, value):
ensure_tainted(value)
ensure_tainted(value) # $ tainted
def validate(self, value):
ensure_tainted(value)
ensure_tainted(value) # $ tainted
def run_validators(self, value):
ensure_tainted(value)
ensure_tainted(value) # $ tainted
def clean(self, value):
ensure_tainted(value)
ensure_tainted(value) # $ tainted
# # Base definition of `clean` looks like the following, so there is actually
# # _data flow_ from the methods, but we will ignore for simplicity.
@@ -30,17 +30,17 @@ class MyForm(django.forms.Form):
cleaned_data = super().clean()
ensure_tainted(
cleaned_data,
cleaned_data["key"],
cleaned_data.get("key"),
cleaned_data, # $ tainted
cleaned_data["key"], # $ tainted
cleaned_data.get("key"), # $ tainted
)
ensure_tainted(
self.cleaned_data,
self.cleaned_data["key"],
self.cleaned_data.get("key"),
self.cleaned_data, # $ tainted
self.cleaned_data["key"], # $ tainted
self.cleaned_data.get("key"), # $ tainted
)
def clean_foo(self):
# This method is supposed to clean the `foo` field in context of this form.
ensure_tainted(self.cleaned_data)
ensure_tainted(self.cleaned_data) # $ tainted

View File

@@ -5,111 +5,114 @@ from django.views import View
def test_taint(request: HttpRequest, foo, bar, baz=None): # $requestHandler routedParameter=foo routedParameter=bar
ensure_tainted(foo, bar)
ensure_tainted(foo, bar) # $ tainted
ensure_not_tainted(baz)
# Manually inspected all fields of the HttpRequest object
# https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects
ensure_tainted(
request,
request, # $ tainted
request.body,
request.path,
request.path_info,
request.body, # $ tainted
request.path, # $ tainted
request.path_info, # $ tainted
# With CSRF middleware disabled, it's possible to use custom methods,
# for example by `curl -X FOO <url>`
request.method,
request.method, # $ tainted
request.encoding,
request.content_type,
request.encoding, # $ tainted
request.content_type, # $ tainted
# Dict[str, str]
request.content_params,
request.content_params["key"],
request.content_params.get("key"),
request.content_params, # $ tainted
request.content_params["key"], # $ tainted
request.content_params.get("key"), # $ tainted
# django.http.QueryDict
# see https://docs.djangoproject.com/en/3.0/ref/request-response/#querydict-objects
request.GET,
request.GET["key"],
request.GET.get("key"),
request.GET.getlist("key"),
request.GET.getlist("key")[0],
request.GET.pop("key"),
request.GET.pop("key")[0],
request.GET.popitem()[0], # key
request.GET.popitem()[1], # values
request.GET.popitem()[1][0], # values[0]
request.GET.dict(),
request.GET.dict()["key"],
request.GET.urlencode(),
request.GET, # $ tainted
request.GET["key"], # $ tainted
request.GET.get("key"), # $ tainted
request.GET.getlist("key"), # $ MISSING: tainted
request.GET.getlist("key")[0], # $ MISSING: tainted
request.GET.pop("key"), # $ tainted
request.GET.pop("key")[0], # $ tainted
# key
request.GET.popitem()[0], # $ tainted
# values
request.GET.popitem()[1], # $ tainted
# values[0]
request.GET.popitem()[1][0], # $ tainted
request.GET.dict(), # $ MISSING: tainted
request.GET.dict()["key"], # $ MISSING: tainted
request.GET.urlencode(), # $ MISSING: tainted
# django.http.QueryDict (same as above, did not duplicate tests)
request.POST,
request.POST, # $ tainted
# Dict[str, str]
request.COOKIES,
request.COOKIES["key"],
request.COOKIES.get("key"),
request.COOKIES, # $ tainted
request.COOKIES["key"], # $ tainted
request.COOKIES.get("key"), # $ tainted
# MultiValueDict[str, UploadedFile]
request.FILES,
request.FILES["key"],
request.FILES["key"].content_type,
request.FILES["key"].content_type_extra,
request.FILES["key"].content_type_extra["key"],
request.FILES["key"].charset,
request.FILES["key"].name,
request.FILES["key"].file,
request.FILES["key"].file.read(),
request.FILES, # $ tainted
request.FILES["key"], # $ tainted
request.FILES["key"].content_type, # $ MISSING: tainted
request.FILES["key"].content_type_extra, # $ MISSING: tainted
request.FILES["key"].content_type_extra["key"], # $ MISSING: tainted
request.FILES["key"].charset, # $ MISSING: tainted
request.FILES["key"].name, # $ MISSING: tainted
request.FILES["key"].file, # $ MISSING: tainted
request.FILES["key"].file.read(), # $ MISSING: tainted
request.FILES.get("key"),
request.FILES.get("key").name,
request.FILES.getlist("key"),
request.FILES.getlist("key")[0],
request.FILES.getlist("key")[0].name,
request.FILES.dict(),
request.FILES.dict()["key"],
request.FILES.dict()["key"].name,
request.FILES.get("key"), # $ tainted
request.FILES.get("key").name, # $ MISSING: tainted
request.FILES.getlist("key"), # $ MISSING: tainted
request.FILES.getlist("key")[0], # $ MISSING: tainted
request.FILES.getlist("key")[0].name, # $ MISSING: tainted
request.FILES.dict(), # $ MISSING: tainted
request.FILES.dict()["key"], # $ MISSING: tainted
request.FILES.dict()["key"].name, # $ MISSING: tainted
# Dict[str, Any]
request.META,
request.META["HTTP_USER_AGENT"],
request.META.get("HTTP_USER_AGENT"),
request.META, # $ tainted
request.META["HTTP_USER_AGENT"], # $ tainted
request.META.get("HTTP_USER_AGENT"), # $ tainted
# HttpHeaders (case insensitive dict-like)
request.headers,
request.headers["user-agent"],
request.headers["USER_AGENT"],
request.headers, # $ tainted
request.headers["user-agent"], # $ tainted
request.headers["USER_AGENT"], # $ tainted
# django.urls.ResolverMatch
request.resolver_match,
request.resolver_match.args,
request.resolver_match.args[0],
request.resolver_match.kwargs,
request.resolver_match.kwargs["key"],
request.resolver_match, # $ tainted
request.resolver_match.args, # $ MISSING: tainted
request.resolver_match.args[0], # $ MISSING: tainted
request.resolver_match.kwargs, # $ MISSING: tainted
request.resolver_match.kwargs["key"], # $ MISSING: tainted
request.get_full_path(),
request.get_full_path_info(),
request.get_full_path(), # $ MISSING: tainted
request.get_full_path_info(), # $ MISSING: tainted
# build_absolute_uri handled below
# get_signed_cookie handled below
request.read(),
request.readline(),
request.readlines(),
request.readlines()[0],
[line for line in request],
request.read(), # $ MISSING: tainted
request.readline(), # $ MISSING: tainted
request.readlines(), # $ MISSING: tainted
request.readlines()[0], # $ MISSING: tainted
[line for line in request], # $ MISSING: tainted
)
# django.urls.ResolverMatch also supports iterable unpacking
_view, args, kwargs = request.resolver_match
ensure_tainted(
args,
args[0],
kwargs,
kwargs["key"],
args, # $ tainted
args[0], # $ tainted
kwargs, # $ tainted
kwargs["key"], # $ tainted
)
ensure_not_tainted(
@@ -126,9 +129,9 @@ def test_taint(request: HttpRequest, foo, bar, baz=None): # $requestHandler rou
# build_absolute_uri
####################################
ensure_tainted(
request.build_absolute_uri(),
request.build_absolute_uri(request.GET["key"]),
request.build_absolute_uri(location=request.GET["key"]),
request.build_absolute_uri(), # $ MISSING: tainted
request.build_absolute_uri(request.GET["key"]), # $ MISSING: tainted
request.build_absolute_uri(location=request.GET["key"]), # $ MISSING: tainted
)
ensure_not_tainted(
request.build_absolute_uri("/hardcoded/"),
@@ -146,22 +149,22 @@ def test_taint(request: HttpRequest, foo, bar, baz=None): # $requestHandler rou
)
# However, providing tainted default value might result in taint
ensure_tainted(
request.get_signed_cookie("key", request.COOKIES["key"]),
request.get_signed_cookie("key", default=request.COOKIES["key"]),
request.get_signed_cookie("key", request.COOKIES["key"]), # $ MISSING: tainted
request.get_signed_cookie("key", default=request.COOKIES["key"]), # $ MISSING: tainted
)
class ClassView(View):
def some_method(self):
ensure_tainted(
self.request,
self.request.GET["key"],
self.request, # $ tainted
self.request.GET["key"], # $ tainted
self.args,
self.args[0],
self.args, # $ tainted
self.args[0], # $ tainted
self.kwargs,
self.kwargs["key"],
self.kwargs, # $ tainted
self.kwargs["key"], # $ tainted
)

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -1,10 +0,0 @@
| fabric_v1_execute.py:7 | fail | unsafe | cmd |
| fabric_v1_execute.py:7 | fail | unsafe | cmd2 |
| fabric_v1_execute.py:8 | ok | unsafe | safe_arg |
| fabric_v1_execute.py:8 | ok | unsafe | safe_optional |
| fabric_v1_execute.py:14 | fail | unsafe | cmd |
| fabric_v1_execute.py:14 | fail | unsafe | cmd2 |
| fabric_v1_execute.py:15 | ok | unsafe | safe_arg |
| fabric_v1_execute.py:15 | ok | unsafe | safe_optional |
| fabric_v1_execute.py:21 | ok | some_http_handler | cmd |
| fabric_v1_execute.py:21 | ok | some_http_handler | cmd2 |

View File

@@ -1 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib

View File

@@ -4,21 +4,21 @@ from fabric.api import run, execute
def unsafe(cmd, safe_arg, cmd2=None, safe_optional=5):
ensure_tainted(cmd, cmd2)
ensure_tainted(cmd, cmd2) # $ MISSING: tainted
ensure_not_tainted(safe_arg, safe_optional)
class Foo(object):
def unsafe(self, cmd, safe_arg, cmd2=None, safe_optional=5):
ensure_tainted(cmd, cmd2)
ensure_tainted(cmd, cmd2) # $ MISSING: tainted
ensure_not_tainted(safe_arg, safe_optional)
def some_http_handler():
cmd = TAINTED_STRING
cmd2 = TAINTED_STRING
ensure_tainted(cmd, cmd2)
ensure_tainted(cmd, cmd2) # $ tainted
execute(unsafe, cmd=cmd, safe_arg='safe_arg', cmd2=cmd2)

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -1,106 +0,0 @@
| taint_test.py:6 | ok | test_taint | name |
| taint_test.py:6 | ok | test_taint | number |
| taint_test.py:7 | ok | test_taint | foo |
| taint_test.py:14 | ok | test_taint | request.environ |
| taint_test.py:15 | ok | test_taint | request.environ.get(..) |
| taint_test.py:17 | ok | test_taint | request.path |
| taint_test.py:18 | ok | test_taint | request.full_path |
| taint_test.py:19 | ok | test_taint | request.base_url |
| taint_test.py:20 | ok | test_taint | request.url |
| taint_test.py:23 | fail | test_taint | request.accept_charsets.best |
| taint_test.py:24 | fail | test_taint | request.accept_charsets.best_match(..) |
| taint_test.py:25 | ok | test_taint | request.accept_charsets[0] |
| taint_test.py:26 | ok | test_taint | request.accept_encodings |
| taint_test.py:27 | ok | test_taint | request.accept_languages |
| taint_test.py:28 | ok | test_taint | request.accept_mimetypes |
| taint_test.py:31 | ok | test_taint | request.access_control_request_headers |
| taint_test.py:33 | ok | test_taint | request.access_control_request_method |
| taint_test.py:35 | ok | test_taint | request.access_route |
| taint_test.py:36 | ok | test_taint | request.access_route[0] |
| taint_test.py:39 | ok | test_taint | request.args |
| taint_test.py:40 | ok | test_taint | request.args['key'] |
| taint_test.py:41 | ok | test_taint | request.args.get(..) |
| taint_test.py:42 | ok | test_taint | request.args.getlist(..) |
| taint_test.py:45 | ok | test_taint | request.authorization |
| taint_test.py:46 | ok | test_taint | request.authorization['username'] |
| taint_test.py:47 | fail | test_taint | request.authorization.username |
| taint_test.py:50 | ok | test_taint | request.cache_control |
| taint_test.py:52 | fail | test_taint | request.cache_control.max_age |
| taint_test.py:53 | fail | test_taint | request.cache_control.max_stale |
| taint_test.py:54 | fail | test_taint | request.cache_control.min_fresh |
| taint_test.py:56 | ok | test_taint | request.content_encoding |
| taint_test.py:58 | ok | test_taint | request.content_md5 |
| taint_test.py:60 | ok | test_taint | request.content_type |
| taint_test.py:63 | ok | test_taint | request.cookies |
| taint_test.py:64 | ok | test_taint | request.cookies['key'] |
| taint_test.py:66 | ok | test_taint | request.data |
| taint_test.py:69 | ok | test_taint | request.files |
| taint_test.py:70 | ok | test_taint | request.files['key'] |
| taint_test.py:71 | fail | test_taint | request.files['key'].filename |
| taint_test.py:72 | fail | test_taint | request.files['key'].stream |
| taint_test.py:73 | ok | test_taint | request.files.get(..) |
| taint_test.py:74 | fail | test_taint | request.files.get(..).filename |
| taint_test.py:75 | fail | test_taint | request.files.get(..).stream |
| taint_test.py:76 | ok | test_taint | request.files.getlist(..) |
| taint_test.py:77 | fail | test_taint | request.files.getlist(..)[0].filename |
| taint_test.py:78 | fail | test_taint | request.files.getlist(..)[0].stream |
| taint_test.py:81 | ok | test_taint | request.form |
| taint_test.py:82 | ok | test_taint | request.form['key'] |
| taint_test.py:83 | ok | test_taint | request.form.get(..) |
| taint_test.py:84 | ok | test_taint | request.form.getlist(..) |
| taint_test.py:86 | ok | test_taint | request.get_data() |
| taint_test.py:88 | ok | test_taint | request.get_json() |
| taint_test.py:89 | ok | test_taint | request.get_json()['foo'] |
| taint_test.py:90 | ok | test_taint | request.get_json()['foo']['bar'] |
| taint_test.py:94 | ok | test_taint | request.headers |
| taint_test.py:95 | ok | test_taint | request.headers['key'] |
| taint_test.py:96 | ok | test_taint | request.headers.get(..) |
| taint_test.py:97 | fail | test_taint | request.headers.get_all(..) |
| taint_test.py:98 | fail | test_taint | request.headers.getlist(..) |
| taint_test.py:99 | ok | test_taint | list(..) |
| taint_test.py:100 | fail | test_taint | request.headers.to_wsgi_list() |
| taint_test.py:102 | ok | test_taint | request.json |
| taint_test.py:103 | ok | test_taint | request.json['foo'] |
| taint_test.py:104 | ok | test_taint | request.json['foo']['bar'] |
| taint_test.py:106 | ok | test_taint | request.method |
| taint_test.py:108 | ok | test_taint | request.mimetype |
| taint_test.py:110 | ok | test_taint | request.mimetype_params |
| taint_test.py:112 | ok | test_taint | request.origin |
| taint_test.py:115 | ok | test_taint | request.pragma |
| taint_test.py:117 | ok | test_taint | request.query_string |
| taint_test.py:119 | ok | test_taint | request.referrer |
| taint_test.py:121 | ok | test_taint | request.remote_addr |
| taint_test.py:123 | ok | test_taint | request.remote_user |
| taint_test.py:126 | ok | test_taint | request.stream |
| taint_test.py:127 | ok | test_taint | request.input_stream |
| taint_test.py:129 | ok | test_taint | request.url |
| taint_test.py:131 | ok | test_taint | request.user_agent |
| taint_test.py:134 | ok | test_taint | request.values |
| taint_test.py:135 | ok | test_taint | request.values['key'] |
| taint_test.py:136 | ok | test_taint | request.values.get(..) |
| taint_test.py:137 | ok | test_taint | request.values.getlist(..) |
| taint_test.py:140 | ok | test_taint | request.view_args |
| taint_test.py:141 | ok | test_taint | request.view_args['key'] |
| taint_test.py:142 | ok | test_taint | request.view_args.get(..) |
| taint_test.py:146 | ok | test_taint | request.script_root |
| taint_test.py:147 | ok | test_taint | request.url_root |
| taint_test.py:151 | ok | test_taint | request.charset |
| taint_test.py:152 | ok | test_taint | request.url_charset |
| taint_test.py:156 | ok | test_taint | request.date |
| taint_test.py:159 | ok | test_taint | request.endpoint |
| taint_test.py:164 | ok | test_taint | request.host |
| taint_test.py:165 | ok | test_taint | request.host_url |
| taint_test.py:167 | ok | test_taint | request.scheme |
| taint_test.py:169 | ok | test_taint | request.script_root |
| taint_test.py:177 | ok | test_taint | request.args |
| taint_test.py:178 | ok | test_taint | a |
| taint_test.py:179 | ok | test_taint | b |
| taint_test.py:181 | ok | test_taint | request.args['key'] |
| taint_test.py:182 | ok | test_taint | a['key'] |
| taint_test.py:183 | ok | test_taint | b['key'] |
| taint_test.py:185 | ok | test_taint | request.args.getlist(..) |
| taint_test.py:186 | ok | test_taint | a.getlist(..) |
| taint_test.py:187 | ok | test_taint | b.getlist(..) |
| taint_test.py:188 | ok | test_taint | gl(..) |
| taint_test.py:195 | ok | test_taint | req.path |
| taint_test.py:196 | ok | test_taint | gd() |

View File

@@ -1,6 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib
import semmle.python.dataflow.new.RemoteFlowSources
class RemoteFlowTestTaintConfiguration extends TestTaintTrackingConfiguration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
}

View File

@@ -3,7 +3,7 @@ app = Flask(__name__)
@app.route("/test_taint/<name>/<int:number>") # $routeSetup="/test_taint/<name>/<int:number>"
def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler routedParameter=name routedParameter=number
ensure_tainted(name, number)
ensure_tainted(name, number) # $ tainted
ensure_not_tainted(foo)
# Manually inspected all fields of the Request object
@@ -11,135 +11,136 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route
ensure_tainted(
request.environ,
request.environ.get('HTTP_AUTHORIZATION'),
request.environ, # $ tainted
request.environ.get('HTTP_AUTHORIZATION'), # $ tainted
request.path,
request.full_path,
request.base_url,
request.url,
request.path, # $ tainted
request.full_path, # $ tainted
request.base_url, # $ tainted
request.url, # $ tainted
# These request.accept_* properties are instances of subclasses of werkzeug.datastructures.Accept
request.accept_charsets.best,
request.accept_charsets.best_match(["utf-8", "utf-16"]),
request.accept_charsets[0],
request.accept_encodings,
request.accept_languages,
request.accept_mimetypes,
request.accept_charsets.best, # $ MISSING: tainted
request.accept_charsets.best_match(["utf-8", "utf-16"]), # $ MISSING: tainted
request.accept_charsets[0], # $ tainted
request.accept_encodings, # $ tainted
request.accept_languages, # $ tainted
request.accept_mimetypes, # $ tainted
# werkzeug.datastructures.HeaderSet (subclass of collections_abc.MutableSet)
request.access_control_request_headers,
request.access_control_request_headers, # $ tainted
request.access_control_request_method,
request.access_control_request_method, # $ tainted
request.access_route,
request.access_route[0],
request.access_route, # $ tainted
request.access_route[0], # $ tainted
# By default werkzeug.datastructures.ImmutableMultiDict -- although can be changed :\
request.args,
request.args['key'],
request.args.get('key'),
request.args.getlist('key'),
request.args, # $ tainted
request.args['key'], # $ tainted
request.args.get('key'), # $ tainted
request.args.getlist('key'), # $ tainted
# werkzeug.datastructures.Authorization (a dict, with some properties)
request.authorization,
request.authorization['username'],
request.authorization.username,
request.authorization, # $ tainted
request.authorization['username'], # $ tainted
request.authorization.username, # $ MISSING: tainted
# werkzeug.datastructures.RequestCacheControl
request.cache_control,
request.cache_control, # $ tainted
# These should be `int`s, but can be strings... see debug method below
request.cache_control.max_age,
request.cache_control.max_stale,
request.cache_control.min_fresh,
request.cache_control.max_age, # $ MISSING: tainted
request.cache_control.max_stale, # $ MISSING: tainted
request.cache_control.min_fresh, # $ MISSING: tainted
request.content_encoding,
request.content_encoding, # $ tainted
request.content_md5,
request.content_md5, # $ tainted
request.content_type,
request.content_type, # $ tainted
# werkzeug.datastructures.ImmutableTypeConversionDict (which is basically just a dict)
request.cookies,
request.cookies['key'],
request.cookies, # $ tainted
request.cookies['key'], # $ tainted
request.data,
request.data, # $ tainted
# a werkzeug.datastructures.MultiDict, mapping [str, werkzeug.datastructures.FileStorage]
request.files,
request.files['key'],
request.files['key'].filename,
request.files['key'].stream,
request.files.get('key'),
request.files.get('key').filename,
request.files.get('key').stream,
request.files.getlist('key'),
request.files.getlist('key')[0].filename,
request.files.getlist('key')[0].stream,
request.files, # $ tainted
request.files['key'], # $ tainted
request.files['key'].filename, # $ MISSING: tainted
request.files['key'].stream, # $ MISSING: tainted
request.files.get('key'), # $ tainted
request.files.get('key').filename, # $ MISSING: tainted
request.files.get('key').stream, # $ MISSING: tainted
request.files.getlist('key'), # $ tainted
request.files.getlist('key')[0].filename, # $ MISSING: tainted
request.files.getlist('key')[0].stream, # $ MISSING: tainted
# By default werkzeug.datastructures.ImmutableMultiDict -- although can be changed :\
request.form,
request.form['key'],
request.form.get('key'),
request.form.getlist('key'),
request.form, # $ tainted
request.form['key'], # $ tainted
request.form.get('key'), # $ tainted
request.form.getlist('key'), # $ tainted
request.get_data(),
request.get_data(), # $ tainted
request.get_json(),
request.get_json()['foo'],
request.get_json()['foo']['bar'],
request.get_json(), # $ tainted
request.get_json()['foo'], # $ tainted
request.get_json()['foo']['bar'], # $ tainted
# werkzeug.datastructures.EnvironHeaders,
# which has same interface as werkzeug.datastructures.Headers
request.headers,
request.headers['key'],
request.headers.get('key'),
request.headers.get_all('key'),
request.headers.getlist('key'),
list(request.headers), # (k, v) list
request.headers.to_wsgi_list(), # (k, v) list
request.headers, # $ tainted
request.headers['key'], # $ tainted
request.headers.get('key'), # $ tainted
request.headers.get_all('key'), # $ MISSING: tainted
request.headers.getlist('key'), # $ MISSING: tainted
# two ways to get (k, v) lists
list(request.headers), # $ tainted
request.headers.to_wsgi_list(), # $ MISSING: tainted
request.json,
request.json['foo'],
request.json['foo']['bar'],
request.json, # $ tainted
request.json['foo'], # $ tainted
request.json['foo']['bar'], # $ tainted
request.method,
request.method, # $ tainted
request.mimetype,
request.mimetype, # $ tainted
request.mimetype_params,
request.mimetype_params, # $ tainted
request.origin,
request.origin, # $ tainted
# werkzeug.datastructures.HeaderSet (subclass of collections_abc.MutableSet)
request.pragma,
request.pragma, # $ tainted
request.query_string,
request.query_string, # $ tainted
request.referrer,
request.referrer, # $ tainted
request.remote_addr,
request.remote_addr, # $ tainted
request.remote_user,
request.remote_user, # $ tainted
# file-like object
request.stream,
request.input_stream,
request.stream, # $ tainted
request.input_stream, # $ tainted
request.url,
request.url, # $ tainted
request.user_agent,
request.user_agent, # $ tainted
# werkzeug.datastructures.CombinedMultiDict, which is basically just a werkzeug.datastructures.MultiDict
request.values,
request.values['key'],
request.values.get('key'),
request.values.getlist('key'),
request.values, # $ tainted
request.values['key'], # $ tainted
request.values.get('key'), # $ tainted
request.values.getlist('key'), # $ tainted
# dict
request.view_args,
request.view_args['key'],
request.view_args.get('key'),
request.view_args, # $ tainted
request.view_args['key'], # $ tainted
request.view_args.get('key'), # $ tainted
)
ensure_not_tainted(
@@ -174,26 +175,26 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route
b = a
gl = b.getlist
ensure_tainted(
request.args,
a,
b,
request.args, # $ tainted
a, # $ tainted
b, # $ tainted
request.args['key'],
a['key'],
b['key'],
request.args['key'], # $ tainted
a['key'], # $ tainted
b['key'], # $ tainted
request.args.getlist('key'),
a.getlist('key'),
b.getlist('key'),
gl('key'),
request.args.getlist('key'), # $ tainted
a.getlist('key'), # $ tainted
b.getlist('key'), # $ tainted
gl('key'), # $ tainted
)
# aliasing tests
req = request
gd = request.get_data
ensure_tainted(
req.path,
gd(),
req.path, # $ tainted
gd(), # $ tainted
)

View File

@@ -6,7 +6,7 @@ private import semmle.python.dataflow.new.TaintTracking
/** A data-flow Node representing an instance of MyClass. */
abstract class MyClass extends DataFlow::Node { }
private DataFlow::Node myClassGetValue(MyClass qualifier, DataFlow::TypeTracker t) {
private DataFlow::LocalSourceNode myClassGetValue(MyClass qualifier, DataFlow::TypeTracker t) {
t.startInAttr("get_value") and
result = qualifier
or
@@ -14,7 +14,7 @@ private DataFlow::Node myClassGetValue(MyClass qualifier, DataFlow::TypeTracker
}
DataFlow::Node myClassGetValue(MyClass qualifier) {
result = myClassGetValue(qualifier, DataFlow::TypeTracker::end())
myClassGetValue(qualifier, DataFlow::TypeTracker::end()).flowsTo(result)
}
// Config

View File

@@ -32,8 +32,8 @@ def test_additional_taint():
cmd3 = builtins.compile(src, "<filename>", "exec")
ensure_tainted(
src,
cmd1,
cmd2,
cmd3,
src, # $ tainted
cmd1, # $ tainted
cmd2, # $ tainted
cmd3, # $ tainted
)

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -1,37 +0,0 @@
| CodeExecution.py:35 | ok | test_additional_taint | src |
| CodeExecution.py:36 | ok | test_additional_taint | cmd1 |
| CodeExecution.py:37 | ok | test_additional_taint | cmd2 |
| CodeExecution.py:38 | ok | test_additional_taint | cmd3 |
| http_server.py:22 | ok | test_cgi_FieldStorage_taint | form |
| http_server.py:24 | ok | test_cgi_FieldStorage_taint | form['key'] |
| http_server.py:25 | ok | test_cgi_FieldStorage_taint | form['key'].value |
| http_server.py:26 | ok | test_cgi_FieldStorage_taint | form['key'].file |
| http_server.py:27 | ok | test_cgi_FieldStorage_taint | form['key'].filename |
| http_server.py:28 | ok | test_cgi_FieldStorage_taint | form['key'][0] |
| http_server.py:29 | ok | test_cgi_FieldStorage_taint | form['key'][0].value |
| http_server.py:30 | ok | test_cgi_FieldStorage_taint | form['key'][0].file |
| http_server.py:31 | ok | test_cgi_FieldStorage_taint | form['key'][0].filename |
| http_server.py:32 | fail | test_cgi_FieldStorage_taint | ListComp |
| http_server.py:34 | ok | test_cgi_FieldStorage_taint | form.getvalue(..) |
| http_server.py:35 | ok | test_cgi_FieldStorage_taint | form.getvalue(..)[0] |
| http_server.py:37 | ok | test_cgi_FieldStorage_taint | form.getfirst(..) |
| http_server.py:39 | ok | test_cgi_FieldStorage_taint | form.getlist(..) |
| http_server.py:40 | ok | test_cgi_FieldStorage_taint | form.getlist(..)[0] |
| http_server.py:41 | fail | test_cgi_FieldStorage_taint | ListComp |
| http_server.py:50 | ok | taint_sources | self |
| http_server.py:52 | ok | taint_sources | self.requestline |
| http_server.py:54 | ok | taint_sources | self.path |
| http_server.py:56 | ok | taint_sources | self.headers |
| http_server.py:57 | ok | taint_sources | self.headers['Foo'] |
| http_server.py:58 | ok | taint_sources | self.headers.get(..) |
| http_server.py:59 | fail | taint_sources | self.headers.get_all(..) |
| http_server.py:60 | fail | taint_sources | self.headers.keys() |
| http_server.py:61 | ok | taint_sources | self.headers.values() |
| http_server.py:62 | ok | taint_sources | self.headers.items() |
| http_server.py:63 | fail | taint_sources | self.headers.as_bytes() |
| http_server.py:64 | fail | taint_sources | self.headers.as_string() |
| http_server.py:65 | ok | taint_sources | str(..) |
| http_server.py:66 | ok | taint_sources | bytes(..) |
| http_server.py:68 | ok | taint_sources | self.rfile |
| http_server.py:69 | fail | taint_sources | self.rfile.read() |
| http_server.py:78 | ok | taint_sources | form |

View File

@@ -1,9 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib
import semmle.python.dataflow.new.RemoteFlowSources
class WithRemoteFlowSources extends TestTaintTrackingConfiguration {
override predicate isSource(DataFlow::Node source) {
super.isSource(source) or
source instanceof RemoteFlowSource
}
}

View File

@@ -19,26 +19,28 @@ def test_cgi_FieldStorage_taint():
form = cgi.FieldStorage()
ensure_tainted(
form,
form, # $ tainted
form['key'], # will be a list, if multiple fields named "key" are provided
form['key'].value,
form['key'].file,
form['key'].filename,
form['key'][0],
form['key'][0].value,
form['key'][0].file,
form['key'][0].filename,
[field.value for field in form['key']],
# `form['key']` will be a list, if multiple fields named "key" are provided
form['key'], # $ tainted
form['key'].value, # $ tainted
form['key'].file, # $ tainted
form['key'].filename, # $ tainted
form['key'][0], # $ tainted
form['key'][0].value, # $ tainted
form['key'][0].file, # $ tainted
form['key'][0].filename, # $ tainted
[field.value for field in form['key']], # $ MISSING: tainted
form.getvalue('key'), # will be a list, if multiple fields named "key" are provided
form.getvalue('key')[0],
# `form.getvalue('key')` will be a list, if multiple fields named "key" are provided
form.getvalue('key'), # $ tainted
form.getvalue('key')[0], # $ tainted
form.getfirst('key'),
form.getfirst('key'), # $ tainted
form.getlist('key'),
form.getlist('key')[0],
[field.value for field in form.getlist('key')],
form.getlist('key'), # $ tainted
form.getlist('key')[0], # $ tainted
[field.value for field in form.getlist('key')], # $ MISSING: tainted
)
@@ -47,26 +49,26 @@ class MyHandler(BaseHTTPRequestHandler):
def taint_sources(self):
ensure_tainted(
self,
self, # $ tainted
self.requestline,
self.requestline, # $ tainted
self.path,
self.path, # $ tainted
self.headers,
self.headers['Foo'],
self.headers.get('Foo'),
self.headers.get_all('Foo'),
self.headers.keys(),
self.headers.values(),
self.headers.items(),
self.headers.as_bytes(),
self.headers.as_string(),
str(self.headers),
bytes(self.headers),
self.headers, # $ tainted
self.headers['Foo'], # $ tainted
self.headers.get('Foo'), # $ tainted
self.headers.get_all('Foo'), # $ MISSING: tainted
self.headers.keys(), # $ MISSING: tainted
self.headers.values(), # $ tainted
self.headers.items(), # $ tainted
self.headers.as_bytes(), # $ MISSING: tainted
self.headers.as_string(), # $ MISSING: tainted
str(self.headers), # $ tainted
bytes(self.headers), # $ tainted
self.rfile,
self.rfile.read(),
self.rfile, # $ tainted
self.rfile.read(), # $ MISSING: tainted
)
form = cgi.FieldStorage(
@@ -75,7 +77,7 @@ class MyHandler(BaseHTTPRequestHandler):
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
)
ensure_tainted(form)
ensure_tainted(form) # $ tainted
def do_GET(self): # $ requestHandler

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -1,41 +0,0 @@
| taint_test.py:6 | ok | get | name |
| taint_test.py:6 | ok | get | number |
| taint_test.py:7 | ok | get | foo |
| taint_test.py:11 | ok | get | self.get_argument(..) |
| taint_test.py:12 | ok | get | self.get_arguments(..) |
| taint_test.py:13 | ok | get | self.get_arguments(..)[0] |
| taint_test.py:15 | ok | get | self.get_body_argument(..) |
| taint_test.py:16 | ok | get | self.get_body_arguments(..) |
| taint_test.py:17 | ok | get | self.get_body_arguments(..)[0] |
| taint_test.py:19 | ok | get | self.get_query_argument(..) |
| taint_test.py:20 | ok | get | self.get_query_arguments(..) |
| taint_test.py:21 | ok | get | self.get_query_arguments(..)[0] |
| taint_test.py:23 | ok | get | self.path_args |
| taint_test.py:24 | ok | get | self.path_args[0] |
| taint_test.py:26 | ok | get | self.path_kwargs |
| taint_test.py:27 | ok | get | self.path_kwargs["name"] |
| taint_test.py:34 | ok | get | request |
| taint_test.py:40 | ok | get | request.uri |
| taint_test.py:41 | ok | get | request.path |
| taint_test.py:42 | ok | get | request.query |
| taint_test.py:43 | ok | get | request.full_url() |
| taint_test.py:45 | ok | get | request.remote_ip |
| taint_test.py:47 | ok | get | request.body |
| taint_test.py:49 | ok | get | request.arguments |
| taint_test.py:50 | ok | get | request.arguments["name"] |
| taint_test.py:51 | ok | get | request.arguments["name"][0] |
| taint_test.py:53 | ok | get | request.query_arguments |
| taint_test.py:54 | ok | get | request.query_arguments["name"] |
| taint_test.py:55 | ok | get | request.query_arguments["name"][0] |
| taint_test.py:57 | ok | get | request.body_arguments |
| taint_test.py:58 | ok | get | request.body_arguments["name"] |
| taint_test.py:59 | ok | get | request.body_arguments["name"][0] |
| taint_test.py:62 | ok | get | request.headers |
| taint_test.py:63 | ok | get | request.headers["header-name"] |
| taint_test.py:64 | fail | get | request.headers.get_list(..) |
| taint_test.py:65 | fail | get | request.headers.get_all() |
| taint_test.py:66 | fail | get | ListComp |
| taint_test.py:69 | ok | get | request.cookies |
| taint_test.py:70 | ok | get | request.cookies["cookie-name"] |
| taint_test.py:71 | fail | get | request.cookies["cookie-name"].key |
| taint_test.py:72 | fail | get | request.cookies["cookie-name"].value |

View File

@@ -1,6 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib
import semmle.python.dataflow.new.RemoteFlowSources
class RemoteFlowTestTaintConfiguration extends TestTaintTrackingConfiguration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
}

View File

@@ -3,73 +3,73 @@ import tornado.web
class TaintTest(tornado.web.RequestHandler):
def get(self, name = "World!", number="0", foo="foo"): # $ requestHandler routedParameter=name routedParameter=number
ensure_tainted(name, number)
ensure_tainted(name, number) # $ tainted
ensure_not_tainted(foo)
ensure_tainted(
# see https://www.tornadoweb.org/en/stable/web.html#input
self.get_argument("name"),
self.get_arguments("name"),
self.get_arguments("name")[0],
self.get_argument("name"), # $ tainted
self.get_arguments("name"), # $ tainted
self.get_arguments("name")[0], # $ tainted
self.get_body_argument("name"),
self.get_body_arguments("name"),
self.get_body_arguments("name")[0],
self.get_body_argument("name"), # $ tainted
self.get_body_arguments("name"), # $ tainted
self.get_body_arguments("name")[0], # $ tainted
self.get_query_argument("name"),
self.get_query_arguments("name"),
self.get_query_arguments("name")[0],
self.get_query_argument("name"), # $ tainted
self.get_query_arguments("name"), # $ tainted
self.get_query_arguments("name")[0], # $ tainted
self.path_args,
self.path_args[0],
self.path_args, # $ tainted
self.path_args[0], # $ tainted
self.path_kwargs,
self.path_kwargs["name"],
self.path_kwargs, # $ tainted
self.path_kwargs["name"], # $ tainted
)
request = self.request
ensure_tainted(
# see https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest
request,
request, # $ tainted
# For the URL https:://example.com/foo/bar?baz=42
# request.uri="/foo/bar?baz=42"
# request.path="/foo/bar"
# request.query="baz=42"
request.uri,
request.path,
request.query,
request.full_url(),
request.uri, # $ tainted
request.path, # $ tainted
request.query, # $ tainted
request.full_url(), # $ tainted
request.remote_ip,
request.remote_ip, # $ tainted
request.body,
request.body, # $ tainted
request.arguments,
request.arguments["name"],
request.arguments["name"][0],
request.arguments, # $ tainted
request.arguments["name"], # $ tainted
request.arguments["name"][0], # $ tainted
request.query_arguments,
request.query_arguments["name"],
request.query_arguments["name"][0],
request.query_arguments, # $ tainted
request.query_arguments["name"], # $ tainted
request.query_arguments["name"][0], # $ tainted
request.body_arguments,
request.body_arguments["name"],
request.body_arguments["name"][0],
request.body_arguments, # $ tainted
request.body_arguments["name"], # $ tainted
request.body_arguments["name"][0], # $ tainted
# dict-like, see https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPHeaders
request.headers,
request.headers["header-name"],
request.headers.get_list("header-name"),
request.headers.get_all(),
[(k, v) for (k, v) in request.headers.get_all()],
request.headers, # $ tainted
request.headers["header-name"], # $ tainted
request.headers.get_list("header-name"), # $ MISSING: tainted
request.headers.get_all(), # $ MISSING: tainted
[(k, v) for (k, v) in request.headers.get_all()], # $ MISSING: tainted
# Dict[str, http.cookies.Morsel]
request.cookies,
request.cookies["cookie-name"],
request.cookies["cookie-name"].key,
request.cookies["cookie-name"].value,
request.cookies, # $ tainted
request.cookies["cookie-name"], # $ tainted
request.cookies["cookie-name"].key, # $ MISSING: tainted
request.cookies["cookie-name"].value, # $ MISSING: tainted
)