Merge pull request #3189 from jbj/DefaultTaintTracking-Configuration

C++: Path explanations in DefaultTaintTracking
This commit is contained in:
Robert Marsh
2020-04-07 08:38:10 -07:00
committed by GitHub
31 changed files with 1469 additions and 197 deletions

View File

@@ -2,7 +2,7 @@
* @name Uncontrolled data used in path expression
* @description Accessing paths influenced by users can allow an
* attacker to access unexpected resources.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/path-injection
@@ -17,6 +17,7 @@ import cpp
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
/**
* A function for opening a file.
@@ -51,12 +52,19 @@ class FileFunction extends FunctionWithWrappers {
override predicate interestingArg(int arg) { arg = 0 }
}
class TaintedPathConfiguration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(FileFunction fileFunction | fileFunction.outermostWrapperFunctionCall(tainted, _))
}
}
from
FileFunction fileFunction, Expr taintedArg, Expr taintSource, string taintCause, string callChain
FileFunction fileFunction, Expr taintedArg, Expr taintSource, PathNode sourceNode,
PathNode sinkNode, string taintCause, string callChain
where
fileFunction.outermostWrapperFunctionCall(taintedArg, callChain) and
tainted(taintSource, taintedArg) and
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
isUserInput(taintSource, taintCause)
select taintedArg,
select taintedArg, sourceNode, sinkNode,
"This argument to a file access function is derived from $@ and then passed to " + callChain,
taintSource, "user input (" + taintCause + ")"

View File

@@ -2,7 +2,7 @@
* @name CGI script vulnerable to cross-site scripting
* @description Writing user input directly to a web page
* allows for a cross-site scripting vulnerability.
* @kind problem
* @kind path-problem
* @problem.severity error
* @precision high
* @id cpp/cgi-xss
@@ -13,6 +13,7 @@
import cpp
import semmle.code.cpp.commons.Environment
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
/** A call that prints its arguments to `stdout`. */
class PrintStdoutCall extends FunctionCall {
@@ -27,8 +28,13 @@ class QueryString extends EnvironmentRead {
QueryString() { getEnvironmentVariable() = "QUERY_STRING" }
}
from QueryString query, PrintStdoutCall call, Element printedArg
where
call.getAnArgument() = printedArg and
tainted(query, printedArg)
select printedArg, "Cross-site scripting vulnerability due to $@.", query, "this query data"
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(PrintStdoutCall call | call.getAnArgument() = tainted)
}
}
from QueryString query, Element printedArg, PathNode sourceNode, PathNode sinkNode
where taintedWithPath(query, printedArg, sourceNode, sinkNode)
select printedArg, sourceNode, sinkNode, "Cross-site scripting vulnerability due to $@.", query,
"this query data"

View File

@@ -3,7 +3,7 @@
* @description Including user-supplied data in a SQL query without
* neutralizing special elements can make code vulnerable
* to SQL Injection.
* @kind problem
* @kind path-problem
* @problem.severity error
* @precision high
* @id cpp/sql-injection
@@ -15,6 +15,7 @@ import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
class SQLLikeFunction extends FunctionWithWrappers {
SQLLikeFunction() { sqlArgument(this.getName(), _) }
@@ -22,11 +23,19 @@ class SQLLikeFunction extends FunctionWithWrappers {
override predicate interestingArg(int arg) { sqlArgument(this.getName(), arg) }
}
from SQLLikeFunction runSql, Expr taintedArg, Expr taintSource, string taintCause, string callChain
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(SQLLikeFunction runSql | runSql.outermostWrapperFunctionCall(tainted, _))
}
}
from
SQLLikeFunction runSql, Expr taintedArg, Expr taintSource, PathNode sourceNode, PathNode sinkNode,
string taintCause, string callChain
where
runSql.outermostWrapperFunctionCall(taintedArg, callChain) and
tainted(taintSource, taintedArg) and
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
isUserInput(taintSource, taintCause)
select taintedArg,
select taintedArg, sourceNode, sinkNode,
"This argument to a SQL query function is derived from $@ and then passed to " + callChain,
taintSource, "user input (" + taintCause + ")"

View File

@@ -3,7 +3,7 @@
* @description Using externally controlled strings in a process
* operation can allow an attacker to execute malicious
* commands.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/uncontrolled-process-operation
@@ -14,13 +14,24 @@
import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
from string processOperation, int processOperationArg, FunctionCall call, Expr arg, Element source
predicate isProcessOperationExplanation(Expr arg, string processOperation) {
exists(int processOperationArg, FunctionCall call |
isProcessOperationArgument(processOperation, processOperationArg) and
call.getTarget().getName() = processOperation and
call.getArgument(processOperationArg) = arg
)
}
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element arg) { isProcessOperationExplanation(arg, _) }
}
from string processOperation, Expr arg, Expr source, PathNode sourceNode, PathNode sinkNode
where
isProcessOperationArgument(processOperation, processOperationArg) and
call.getTarget().getName() = processOperation and
call.getArgument(processOperationArg) = arg and
tainted(source, arg)
select arg,
isProcessOperationExplanation(arg, processOperation) and
taintedWithPath(source, arg, sourceNode, sinkNode)
select arg, sourceNode, sinkNode,
"The value of this argument may come from $@ and is being passed to " + processOperation, source,
source.toString()

View File

@@ -2,7 +2,7 @@
* @name Unbounded write
* @description Buffer write operations that do not control the length
* of data written may overflow.
* @kind problem
* @kind path-problem
* @problem.severity error
* @precision medium
* @id cpp/unbounded-write
@@ -16,6 +16,7 @@
import semmle.code.cpp.security.BufferWrite
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
/*
* --- Summary of CWE-120 alerts ---
@@ -54,32 +55,48 @@ predicate isUnboundedWrite(BufferWrite bw) {
* }
*/
/**
* Holds if `e` is a source buffer going into an unbounded write `bw` or a
* qualifier of (a qualifier of ...) such a source.
*/
predicate unboundedWriteSource(Expr e, BufferWrite bw) {
isUnboundedWrite(bw) and e = bw.getASource()
or
exists(FieldAccess fa | unboundedWriteSource(fa, bw) and e = fa.getQualifier())
}
/*
* --- user input reach ---
*/
/**
* Identifies expressions that are potentially tainted with user
* input. Most of the work for this is actually done by the
* TaintTracking library.
*/
predicate tainted2(Expr expr, Expr inputSource, string inputCause) {
taintedIncludingGlobalVars(inputSource, expr, _) and
inputCause = inputSource.toString()
or
exists(Expr e | tainted2(e, inputSource, inputCause) |
// field accesses of a tainted struct are tainted
e = expr.(FieldAccess).getQualifier()
)
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { unboundedWriteSource(tainted, _) }
override predicate taintThroughGlobals() { any() }
}
/*
* --- put it together ---
*/
from BufferWrite bw, Expr inputSource, string inputCause
/*
* An unbounded write is, for example `strcpy(..., tainted)`. We're looking
* for a tainted source buffer of an unbounded write, where this source buffer
* is a sink in the taint-tracking analysis.
*
* In the case of `gets` and `scanf`, where the source buffer is implicit, the
* `BufferWrite` library reports the source buffer to be the same as the
* destination buffer. Since those destination-buffer arguments are also
* modeled in the taint-tracking library as being _sources_ of taint, they are
* in practice reported as being tainted because the `security.TaintTracking`
* library does not distinguish between taint going into an argument and out of
* an argument. Thus, we get the desired alerts.
*/
from BufferWrite bw, Expr inputSource, Expr tainted, PathNode sourceNode, PathNode sinkNode
where
isUnboundedWrite(bw) and
tainted2(bw.getASource(), inputSource, inputCause)
select bw, "This '" + bw.getBWDesc() + "' with input from $@ may overflow the destination.",
inputSource, inputCause
taintedWithPath(inputSource, tainted, sourceNode, sinkNode) and
unboundedWriteSource(tainted, bw)
select bw, sourceNode, sinkNode,
"This '" + bw.getBWDesc() + "' with input from $@ may overflow the destination.", inputSource,
inputSource.toString()

View File

@@ -3,7 +3,7 @@
* @description Using externally-controlled format strings in
* printf-style functions can lead to buffer overflows
* or data representation problems.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/tainted-format-string
@@ -16,12 +16,21 @@ import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
from PrintfLikeFunction printf, Expr arg, string printfFunction, Expr userValue, string cause
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(PrintfLikeFunction printf | printf.outermostWrapperFunctionCall(tainted, _))
}
}
from
PrintfLikeFunction printf, Expr arg, PathNode sourceNode, PathNode sinkNode,
string printfFunction, Expr userValue, string cause
where
printf.outermostWrapperFunctionCall(arg, printfFunction) and
tainted(userValue, arg) and
taintedWithPath(userValue, arg, sourceNode, sinkNode) and
isUserInput(userValue, cause)
select arg,
select arg, sourceNode, sinkNode,
"The value of this argument may come from $@ and is being used as a formatting argument to " +
printfFunction, userValue, cause

View File

@@ -3,7 +3,7 @@
* @description Using externally-controlled format strings in
* printf-style functions can lead to buffer overflows
* or data representation problems.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/tainted-format-string-through-global
@@ -16,15 +16,24 @@ import cpp
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(PrintfLikeFunction printf | printf.outermostWrapperFunctionCall(tainted, _))
}
override predicate taintThroughGlobals() { any() }
}
from
PrintfLikeFunction printf, Expr arg, string printfFunction, Expr userValue, string cause,
string globalVar
PrintfLikeFunction printf, Expr arg, PathNode sourceNode, PathNode sinkNode,
string printfFunction, Expr userValue, string cause
where
printf.outermostWrapperFunctionCall(arg, printfFunction) and
not tainted(_, arg) and
taintedIncludingGlobalVars(userValue, arg, globalVar) and
not taintedWithoutGlobals(arg) and
taintedWithPath(userValue, arg, sourceNode, sinkNode) and
isUserInput(userValue, cause)
select arg,
"This value may flow through $@, originating from $@, and is a formatting argument to " +
printfFunction + ".", globalVarFromId(globalVar), globalVar, userValue, cause
select arg, sourceNode, sinkNode,
"The value of this argument may come from $@ and is being used as a formatting argument to " +
printfFunction, userValue, cause

View File

@@ -2,7 +2,7 @@
* @name Uncontrolled data in arithmetic expression
* @description Arithmetic operations on uncontrolled data that is not
* validated can cause overflows.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/uncontrolled-arithmetic
@@ -15,6 +15,7 @@ import cpp
import semmle.code.cpp.security.Overflow
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
predicate isRandCall(FunctionCall fc) { fc.getTarget().getName() = "rand" }
@@ -40,9 +41,22 @@ class SecurityOptionsArith extends SecurityOptions {
}
}
predicate taintedVarAccess(Expr origin, VariableAccess va) {
isUserInput(origin, _) and
tainted(origin, va)
predicate isDiv(VariableAccess va) { exists(AssignDivExpr div | div.getLValue() = va) }
predicate missingGuard(VariableAccess va, string effect) {
exists(Operation op | op.getAnOperand() = va |
missingGuardAgainstUnderflow(op, va) and effect = "underflow"
or
missingGuardAgainstOverflow(op, va) and effect = "overflow"
)
}
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element e) {
isDiv(e)
or
missingGuard(e, _)
}
}
/**
@@ -50,19 +64,17 @@ predicate taintedVarAccess(Expr origin, VariableAccess va) {
* range.
*/
predicate guardedByAssignDiv(Expr origin) {
isUserInput(origin, _) and
exists(AssignDivExpr div, VariableAccess va | tainted(origin, va) and div.getLValue() = va)
exists(VariableAccess va |
taintedWithPath(origin, va, _, _) and
isDiv(va)
)
}
from Expr origin, Operation op, VariableAccess va, string effect
from Expr origin, VariableAccess va, string effect, PathNode sourceNode, PathNode sinkNode
where
taintedVarAccess(origin, va) and
op.getAnOperand() = va and
(
missingGuardAgainstUnderflow(op, va) and effect = "underflow"
or
missingGuardAgainstOverflow(op, va) and effect = "overflow"
) and
taintedWithPath(origin, va, sourceNode, sinkNode) and
missingGuard(va, effect) and
not guardedByAssignDiv(origin)
select va, "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".",
origin, "Uncontrolled value"
select va, sourceNode, sinkNode,
"$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", origin,
"Uncontrolled value"

View File

@@ -2,7 +2,7 @@
* @name Overflow in uncontrolled allocation size
* @description Allocating memory with a size controlled by an external
* user can result in integer overflow.
* @kind problem
* @kind path-problem
* @problem.severity error
* @precision high
* @id cpp/uncontrolled-allocation-size
@@ -13,21 +13,33 @@
import cpp
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
predicate taintedAllocSize(Expr e, Expr source, string taintCause) {
predicate taintedChild(Expr e, Expr tainted) {
(
isAllocationExpr(e) or
isAllocationExpr(e)
or
any(MulExpr me | me.getAChild() instanceof SizeofOperator) = e
) and
tainted = e.getAChild() and
tainted.getUnspecifiedType() instanceof IntegralType
}
class TaintedAllocationSizeConfiguration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { taintedChild(_, tainted) }
}
predicate taintedAllocSize(
Expr e, Expr source, PathNode sourceNode, PathNode sinkNode, string taintCause
) {
isUserInput(source, taintCause) and
exists(Expr tainted |
tainted = e.getAChild() and
tainted.getUnspecifiedType() instanceof IntegralType and
isUserInput(source, taintCause) and
tainted(source, tainted)
taintedChild(e, tainted) and
taintedWithPath(source, tainted, sourceNode, sinkNode)
)
}
from Expr e, Expr source, string taintCause
where taintedAllocSize(e, source, taintCause)
select e, "This allocation size is derived from $@ and might overflow", source,
"user input (" + taintCause + ")"
from Expr e, Expr source, PathNode sourceNode, PathNode sinkNode, string taintCause
where taintedAllocSize(e, source, sourceNode, sinkNode, taintCause)
select e, sourceNode, sinkNode, "This allocation size is derived from $@ and might overflow",
source, "user input (" + taintCause + ")"

View File

@@ -3,7 +3,7 @@
* @description Authentication by checking that the peer's address
* matches a known IP or web address is unsafe as it is
* vulnerable to spoofing attacks.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/user-controlled-bypass
@@ -12,6 +12,7 @@
*/
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
predicate hardCodedAddressOrIP(StringLiteral txt) {
exists(string s | s = txt.getValueText() |
@@ -102,16 +103,21 @@ predicate useOfHardCodedAddressOrIP(Expr use) {
* untrusted input then it might be vulnerable to a spoofing
* attack.
*/
predicate hardCodedAddressInCondition(Expr source, Expr condition) {
// One of the sub-expressions of the condition is tainted.
exists(Expr taintedExpr | taintedExpr.getParent+() = condition | tainted(source, taintedExpr)) and
predicate hardCodedAddressInCondition(Expr subexpression, Expr condition) {
subexpression = condition.getAChild+() and
// One of the sub-expressions of the condition is a hard-coded
// IP or web-address.
exists(Expr use | use.getParent+() = condition | useOfHardCodedAddressOrIP(use)) and
exists(Expr use | use = condition.getAChild+() | useOfHardCodedAddressOrIP(use)) and
condition = any(IfStmt ifStmt).getCondition()
}
from Expr source, Expr condition
where hardCodedAddressInCondition(source, condition)
select condition, "Untrusted input $@ might be vulnerable to a spoofing attack.", source,
source.toString()
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element sink) { hardCodedAddressInCondition(sink, _) }
}
from Expr subexpression, Expr source, Expr condition, PathNode sourceNode, PathNode sinkNode
where
hardCodedAddressInCondition(subexpression, condition) and
taintedWithPath(source, subexpression, sourceNode, sinkNode)
select condition, sourceNode, sinkNode,
"Untrusted input $@ might be vulnerable to a spoofing attack.", source, source.toString()

View File

@@ -2,7 +2,7 @@
* @name Cleartext storage of sensitive information in buffer
* @description Storing sensitive information in cleartext can expose it
* to an attacker.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/cleartext-storage-buffer
@@ -14,12 +14,20 @@ import cpp
import semmle.code.cpp.security.BufferWrite
import semmle.code.cpp.security.TaintTracking
import semmle.code.cpp.security.SensitiveExprs
import TaintedWithPath
from BufferWrite w, Expr taintedArg, Expr taintSource, string taintCause, SensitiveExpr dest
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { exists(BufferWrite w | w.getASource() = tainted) }
}
from
BufferWrite w, Expr taintedArg, Expr taintSource, PathNode sourceNode, PathNode sinkNode,
string taintCause, SensitiveExpr dest
where
tainted(taintSource, taintedArg) and
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
isUserInput(taintSource, taintCause) and
w.getASource() = taintedArg and
dest = w.getDest()
select w, "This write into buffer '" + dest.toString() + "' may contain unencrypted data from $@",
select w, sourceNode, sinkNode,
"This write into buffer '" + dest.toString() + "' may contain unencrypted data from $@",
taintSource, "user input (" + taintCause + ")"

View File

@@ -2,7 +2,7 @@
* @name Cleartext storage of sensitive information in an SQLite database
* @description Storing sensitive information in a non-encrypted
* database can expose it to an attacker.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/cleartext-storage-database
@@ -13,6 +13,7 @@
import cpp
import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
class UserInputIsSensitiveExpr extends SecurityOptions {
override predicate isUserInput(Expr expr, string cause) {
@@ -32,10 +33,21 @@ predicate sqlite_encryption_used() {
any(FunctionCall fc).getTarget().getName().matches("sqlite%\\_key\\_%")
}
from SensitiveExpr taintSource, Expr taintedArg, SqliteFunctionCall sqliteCall
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element taintedArg) {
exists(SqliteFunctionCall sqliteCall |
taintedArg = sqliteCall.getASource() and
not sqlite_encryption_used()
)
}
}
from
SensitiveExpr taintSource, Expr taintedArg, SqliteFunctionCall sqliteCall, PathNode sourceNode,
PathNode sinkNode
where
tainted(taintSource, taintedArg) and
taintedArg = sqliteCall.getASource() and
not sqlite_encryption_used()
select sqliteCall, "This SQLite call may store $@ in a non-encrypted SQLite database", taintSource,
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
taintedArg = sqliteCall.getASource()
select sqliteCall, sourceNode, sinkNode,
"This SQLite call may store $@ in a non-encrypted SQLite database", taintSource,
"sensitive information"

View File

@@ -3,7 +3,7 @@
* @description Using untrusted inputs in a statement that makes a
* security decision makes code vulnerable to
* attack.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/tainted-permissions-check
@@ -12,14 +12,9 @@
*/
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
/**
* Holds if there is an 'if' statement whose condition `condition`
* is influenced by tainted data `source`, and the body contains
* `raise` which escalates privilege.
*/
predicate cwe807violation(Expr source, Expr condition, Expr raise) {
tainted(source, condition) and
predicate sensitiveCondition(Expr condition, Expr raise) {
raisesPrivilege(raise) and
exists(IfStmt ifstmt |
ifstmt.getCondition() = condition and
@@ -27,7 +22,19 @@ predicate cwe807violation(Expr source, Expr condition, Expr raise) {
)
}
from Expr source, Expr condition, Expr raise
where cwe807violation(source, condition, raise)
select condition, "Reliance on untrusted input $@ to raise privilege at $@", source,
source.toString(), raise, raise.toString()
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { sensitiveCondition(tainted, _) }
}
/*
* Produce an alert if there is an 'if' statement whose condition `condition`
* is influenced by tainted data `source`, and the body contains
* `raise` which escalates privilege.
*/
from Expr source, Expr condition, Expr raise, PathNode sourceNode, PathNode sinkNode
where
taintedWithPath(source, condition, sourceNode, sinkNode) and
sensitiveCondition(condition, raise)
select condition, sourceNode, sinkNode, "Reliance on untrusted input $@ to raise privilege at $@",
source, source.toString(), raise, raise.toString()

View File

@@ -2,6 +2,7 @@ import cpp
import semmle.code.cpp.security.Security
private import semmle.code.cpp.ir.dataflow.DataFlow
private import semmle.code.cpp.ir.dataflow.DataFlow2
private import semmle.code.cpp.ir.dataflow.DataFlow3
private import semmle.code.cpp.ir.IR
private import semmle.code.cpp.ir.dataflow.internal.DataFlowDispatch as Dispatch
private import semmle.code.cpp.models.interfaces.Taint
@@ -171,6 +172,7 @@ private predicate nodeIsBarrierIn(DataFlow::Node node) {
node = getNodeForSource(any(Expr e))
}
cached
private predicate instructionTaintStep(Instruction i1, Instruction i2) {
// Expressions computed from tainted data are also tainted
exists(CallInstruction call, int argIndex | call = i2 |
@@ -424,3 +426,219 @@ Function resolveCall(Call call) {
result = Dispatch::viableCallable(callInstruction)
)
}
/**
* Provides definitions for augmenting source/sink pairs with data-flow paths
* between them. From a `@kind path-problem` query, import this module in the
* global scope, extend `TaintTrackingConfiguration`, and use `taintedWithPath`
* in place of `tainted`.
*
* Importing this module will also import the query predicates that contain the
* taint paths.
*/
module TaintedWithPath {
private newtype TSingleton = MkSingleton()
/**
* A taint-tracking configuration that matches sources and sinks in the same
* way as the `tainted` predicate.
*
* Override `isSink` and `taintThroughGlobals` as needed, but do not provide
* a characteristic predicate.
*/
class TaintTrackingConfiguration extends TSingleton {
/** Override this to specify which elements are sinks in this configuration. */
abstract predicate isSink(Element e);
/**
* Override this predicate to `any()` to allow taint to flow through global
* variables.
*/
predicate taintThroughGlobals() { none() }
/** Gets a textual representation of this element. */
string toString() { result = "TaintTrackingConfiguration" }
}
private class AdjustedConfiguration extends DataFlow3::Configuration {
AdjustedConfiguration() { this = "AdjustedConfiguration" }
override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) }
override predicate isSink(DataFlow::Node sink) {
exists(TaintTrackingConfiguration cfg | cfg.isSink(adjustedSink(sink)))
}
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
instructionTaintStep(n1.asInstruction(), n2.asInstruction())
or
exists(TaintTrackingConfiguration cfg | cfg.taintThroughGlobals() |
writesVariable(n1.asInstruction(), n2.asVariable().(GlobalOrNamespaceVariable))
or
readsVariable(n2.asInstruction(), n1.asVariable().(GlobalOrNamespaceVariable))
)
}
override predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
override predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
}
/*
* A sink `Element` may map to multiple `DataFlowX::PathNode`s via (the
* inverse of) `adjustedSink`. For example, an `Expr` maps to all its
* conversions, and a `Variable` maps to all loads and stores from it. Because
* the path node is part of the tuple that constitutes the alert, this leads
* to duplicate alerts.
*
* To avoid showing duplicates, we edit the graph to replace the final node
* coming from the data-flow library with a node that matches exactly the
* `Element` sink that's requested.
*
* The same is done for sources.
*/
private newtype TPathNode =
TWrapPathNode(DataFlow3::PathNode n) or
// There's a single newtype constructor for both sources and sinks since
// that makes it easiest to deal with the case where source = sink.
TEndpointPathNode(Element e) {
exists(AdjustedConfiguration cfg, DataFlow3::Node sourceNode, DataFlow3::Node sinkNode |
cfg.hasFlow(sourceNode, sinkNode)
|
sourceNode = getNodeForSource(e)
or
e = adjustedSink(sinkNode) and
exists(TaintTrackingConfiguration ttCfg | ttCfg.isSink(e))
)
}
/** An opaque type used for the nodes of a data-flow path. */
class PathNode extends TPathNode {
/** Gets a textual representation of this element. */
string toString() { none() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
none()
}
}
private class WrapPathNode extends PathNode, TWrapPathNode {
DataFlow3::PathNode inner() { this = TWrapPathNode(result) }
override string toString() { result = this.inner().toString() }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.inner().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
private class EndpointPathNode extends PathNode, TEndpointPathNode {
Expr inner() { this = TEndpointPathNode(result) }
override string toString() { result = this.inner().toString() }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this
.inner()
.getLocation()
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
/** A PathNode whose `Element` is a source. It may also be a sink. */
private class InitialPathNode extends EndpointPathNode {
InitialPathNode() { exists(getNodeForSource(this.inner())) }
}
/** A PathNode whose `Element` is a sink. It may also be a source. */
private class FinalPathNode extends EndpointPathNode {
FinalPathNode() { exists(TaintTrackingConfiguration cfg | cfg.isSink(this.inner())) }
}
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
query predicate edges(PathNode a, PathNode b) {
DataFlow3::PathGraph::edges(a.(WrapPathNode).inner(), b.(WrapPathNode).inner())
or
// To avoid showing trivial-looking steps, we _replace_ the last node instead
// of adding an edge out of it.
exists(WrapPathNode sinkNode |
DataFlow3::PathGraph::edges(a.(WrapPathNode).inner(), sinkNode.inner()) and
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
)
or
// Same for the first node
exists(WrapPathNode sourceNode |
DataFlow3::PathGraph::edges(sourceNode.inner(), b.(WrapPathNode).inner()) and
sourceNode.inner().getNode() = getNodeForSource(a.(InitialPathNode).inner())
)
or
// Finally, handle the case where the path goes directly from a source to a
// sink, meaning that they both need to be translated.
exists(WrapPathNode sinkNode, WrapPathNode sourceNode |
DataFlow3::PathGraph::edges(sourceNode.inner(), sinkNode.inner()) and
sourceNode.inner().getNode() = getNodeForSource(a.(InitialPathNode).inner()) and
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
)
}
/** Holds if `n` is a node in the graph of data flow path explanations. */
query predicate nodes(PathNode n, string key, string val) {
key = "semmle.label" and val = n.toString()
}
/**
* Holds if `tainted` may contain taint from `source`, where `sourceNode` and
* `sinkNode` are the corresponding `PathNode`s that can be used in a query
* to provide path explanations. Extend `TaintTrackingConfiguration` to use
* this predicate.
*
* A tainted expression is either directly user input, or is computed from
* user input in a way that users can probably control the exact output of
* the computation.
*/
predicate taintedWithPath(Expr source, Element tainted, PathNode sourceNode, PathNode sinkNode) {
exists(AdjustedConfiguration cfg, DataFlow3::Node flowSource, DataFlow3::Node flowSink |
source = sourceNode.(InitialPathNode).inner() and
flowSource = getNodeForSource(source) and
cfg.hasFlow(flowSource, flowSink) and
tainted = adjustedSink(flowSink) and
tainted = sinkNode.(FinalPathNode).inner()
)
}
private predicate isGlobalVariablePathNode(WrapPathNode n) {
n.inner().getNode().asVariable() instanceof GlobalOrNamespaceVariable
}
private predicate edgesWithoutGlobals(PathNode a, PathNode b) {
edges(a, b) and
not isGlobalVariablePathNode(a) and
not isGlobalVariablePathNode(b)
}
/**
* Holds if `tainted` can be reached from a taint source without passing
* through a global variable.
*/
predicate taintedWithoutGlobals(Element tainted) {
exists(PathNode sourceNode, FinalPathNode sinkNode |
sourceNode.(WrapPathNode).inner().getNode() = getNodeForSource(_) and
edgesWithoutGlobals+(sourceNode, sinkNode) and
tainted = sinkNode.inner()
)
}
}

View File

@@ -154,7 +154,7 @@ Type getErasedRepr(Type t) {
}
/** Gets a string representation of a type returned by `getErasedRepr`. */
string ppReprType(Type t) { result = t.toString() }
string ppReprType(Type t) { none() } // stub implementation
/**
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from

View File

@@ -239,6 +239,17 @@ class DefinitionByReferenceNode extends InstructionNode {
Parameter getParameter() {
exists(CallInstruction ci | result = ci.getStaticCallTarget().getParameter(instr.getIndex()))
}
override string toString() {
// This string should be unique enough to be helpful but common enough to
// avoid storing too many different strings.
result =
instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget().getName() +
" output argument"
or
not exists(instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget()) and
result = "output argument"
}
}
/**
@@ -289,7 +300,7 @@ ExprNode exprNode(Expr e) { result.getExpr() = e }
* Gets the `Node` corresponding to `e`, if any. Here, `e` may be a
* `Conversion`.
*/
ExprNode convertedExprNode(Expr e) { result.getExpr() = e }
ExprNode convertedExprNode(Expr e) { result.getConvertedExpr() = e }
/**
* Gets the `Node` corresponding to the value of `p` at function entry.
@@ -323,6 +334,7 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
simpleInstructionLocalFlowStep(nodeFrom.asInstruction(), nodeTo.asInstruction())
}
cached
private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction iTo) {
iTo.(CopyInstruction).getSourceValue() = iFrom
or