Merge branch 'master' of github.com:Semmle/ql into attribute

This commit is contained in:
Pavel Avgustinov
2019-08-23 09:55:35 +01:00
214 changed files with 13101 additions and 4065 deletions

View File

@@ -57,5 +57,7 @@ predicate declaredInLoop(LocalVariableDecl v, LoopStmt loop) {
from Assignment a, Variable v
where
useAndDef(a, v) and
exists(LoopStmt loop | a.getEnclosingStmt().getEnclosingStmt*() = loop | not declaredInLoop(v, loop))
exists(LoopStmt loop | a.getEnclosingStmt().getEnclosingStmt*() = loop |
not declaredInLoop(v, loop)
)
select a, "The string " + v.getName() + " is built-up in a loop: use string buffer."

View File

@@ -12,10 +12,11 @@
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking2
import semmle.code.java.security.XSS
import DataFlow2::PathGraph
class XSSConfig extends TaintTracking::Configuration2 {
class XSSConfig extends TaintTracking2::Configuration {
XSSConfig() { this = "XSSConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

View File

@@ -12,10 +12,11 @@
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking2
import semmle.code.java.security.XSS
import DataFlow2::PathGraph
class XSSLocalConfig extends TaintTracking::Configuration2 {
class XSSLocalConfig extends TaintTracking2::Configuration {
XSSLocalConfig() { this = "XSSLocalConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }

View File

@@ -14,6 +14,7 @@
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking2
import semmle.code.java.security.XSS
/**
@@ -80,7 +81,7 @@ predicate stackTraceExpr(Expr exception, MethodAccess stackTraceString) {
)
}
class StackTraceStringToXssSinkFlowConfig extends TaintTracking::Configuration2 {
class StackTraceStringToXssSinkFlowConfig extends TaintTracking2::Configuration {
StackTraceStringToXssSinkFlowConfig() {
this = "StackTraceExposure::StackTraceStringToXssSinkFlowConfig"
}
@@ -119,7 +120,7 @@ class GetMessageFlowSource extends MethodAccess {
}
}
class GetMessageFlowSourceToXssSinkFlowConfig extends TaintTracking::Configuration2 {
class GetMessageFlowSourceToXssSinkFlowConfig extends TaintTracking2::Configuration {
GetMessageFlowSourceToXssSinkFlowConfig() {
this = "StackTraceExposure::GetMessageFlowSourceToXssSinkFlowConfig"
}

View File

@@ -13,9 +13,10 @@
import java
import XmlParsers
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking2
import DataFlow::PathGraph
class SafeSAXSourceFlowConfig extends TaintTracking::Configuration2 {
class SafeSAXSourceFlowConfig extends TaintTracking2::Configuration {
SafeSAXSourceFlowConfig() { this = "XmlParsers::SafeSAXSourceFlowConfig" }
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeSAXSource }

View File

@@ -1117,9 +1117,7 @@ class SwitchExpr extends Expr, @switchexpr {
Expr getAResult() {
result = getACase().getRuleExpression()
or
exists(BreakStmt break |
break.(JumpStmt).getTarget() = this and result = break.getValue()
)
exists(BreakStmt break | break.(JumpStmt).getTarget() = this and result = break.getValue())
}
/** Gets a printable representation of this expression. */

View File

@@ -120,9 +120,7 @@ class Callable extends StmtParent, Member, @callable {
*
* This includes both static call targets and dynamic dispatch targets.
*/
predicate polyCalls(Callable m) {
this.calls(m) or this.callsImpl(m)
}
predicate polyCalls(Callable m) { this.calls(m) or this.callsImpl(m) }
/**
* Holds if `c` is a viable implementation of a callable called by this

View File

@@ -3,6 +3,7 @@
* `com.google.common.base.Preconditions` and
* `org.apache.commons.lang3.Validate`.
*/
import java
/**

View File

@@ -7,23 +7,4 @@ import java
module DataFlow {
import semmle.code.java.dataflow.internal.DataFlowImpl
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
* Four copies are available: `DataFlow` through `DataFlow4`.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0 or
strictcount(Node n | this.isSink(n)) < 0 or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 or
super.hasFlow(source, sink)
}
}
}

View File

@@ -7,23 +7,4 @@ import java
module DataFlow2 {
import semmle.code.java.dataflow.internal.DataFlowImpl2
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
* Four copies are available: `DataFlow` through `DataFlow4`.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0 or
strictcount(Node n | this.isSink(n)) < 0 or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 or
super.hasFlow(source, sink)
}
}
}

View File

@@ -7,23 +7,4 @@ import java
module DataFlow3 {
import semmle.code.java.dataflow.internal.DataFlowImpl3
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
* Four copies are available: `DataFlow` through `DataFlow4`.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0 or
strictcount(Node n | this.isSink(n)) < 0 or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 or
super.hasFlow(source, sink)
}
}
}

View File

@@ -7,23 +7,4 @@ import java
module DataFlow4 {
import semmle.code.java.dataflow.internal.DataFlowImpl4
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
* Four copies are available: `DataFlow` through `DataFlow4`.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0 or
strictcount(Node n | this.isSink(n)) < 0 or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 or
super.hasFlow(source, sink)
}
}
}

View File

@@ -7,23 +7,4 @@ import java
module DataFlow5 {
import semmle.code.java.dataflow.internal.DataFlowImpl5
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
* Four copies are available: `DataFlow` through `DataFlow4`.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0 or
strictcount(Node n | this.isSink(n)) < 0 or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 or
super.hasFlow(source, sink)
}
}
}

View File

@@ -3,802 +3,16 @@
* global (inter-procedural) taint-tracking analyses.
*/
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow2
import semmle.code.java.Collections
private import SSA
private import DefUse
private import semmle.code.java.security.SecurityTests
private import semmle.code.java.security.Validation
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.Guice
private import semmle.code.java.frameworks.Protobuf
private import semmle.code.java.Maps
private import semmle.code.java.dataflow.internal.ContainerFlow
import semmle.code.java.dataflow.internal.TaintTrackingUtil::StringBuilderVarModule
module TaintTracking {
/**
* A taint tracking configuration.
*
* A taint tracking configuration is a special dataflow configuration
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
* necessarily preserve values, but are still relevant from a taint tracking
* perspective. (For example, string concatenation, where one of the operands
* is tainted.)
*
* Each use of the taint tracking library must define its own unique extension
* of this abstract class. A configuration defines a set of relevant sources
* (`isSource`) and sinks (`isSink`), and may additionally treat intermediate
* nodes as "sanitizers" (`isSanitizer`) as well as add custom taint flow steps
* (`isAdditionalTaintStep()`).
*/
abstract class Configuration extends DataFlow::Configuration {
bindingset[this]
Configuration() { any() }
/**
* Holds if `source` is a relevant taint source.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
isSanitizer(node) or
// Ignore paths through test code.
node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or
node.asExpr() instanceof ValidatedVariableAccess
}
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
isSanitizerEdge(node1, node2)
}
/** Holds if data flow into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
/** Holds if data flow out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
* must be taken into account in the analysis.
*/
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
isAdditionalTaintStep(node1, node2) or
localAdditionalTaintStep(node1, node2)
}
/**
* Holds if taint may flow from `source` to `sink` for this configuration.
*/
// overridden to provide taint-tracking specific qldoc
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
super.hasFlow(source, sink)
}
}
import semmle.code.java.dataflow.internal.tainttracking1.TaintTrackingImpl
private import semmle.code.java.dataflow.TaintTracking2
/**
* A taint tracking configuration.
*
* A taint tracking configuration is a special dataflow configuration
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
* necessarily preserve values, but are still relevant from a taint tracking
* perspective. (For example, string concatenation, where one of the operands
* is tainted.)
*
* Each use of the taint tracking library must define its own unique extension
* of this abstract class. A configuration defines a set of relevant sources
* (`isSource`) and sinks (`isSink`), and may additionally treat intermediate
* nodes as "sanitizers" (`isSanitizer`) as well as add custom taint flow steps
* (`isAdditionalTaintStep()`).
* DEPRECATED: Use TaintTracking2::Configuration instead.
*/
abstract class Configuration2 extends DataFlow2::Configuration {
bindingset[this]
Configuration2() { any() }
/**
* Holds if `source` is a relevant taint source.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
isSanitizer(node) or
// Ignore paths through test code.
node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or
node.asExpr() instanceof ValidatedVariableAccess
}
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
isSanitizerEdge(node1, node2)
}
/** Holds if data flow into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
/** Holds if data flow out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
* must be taken into account in the analysis.
*/
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
isAdditionalTaintStep(node1, node2) or
localAdditionalTaintStep(node1, node2)
}
/**
* Holds if taint may flow from `source` to `sink` for this configuration.
*/
// overridden to provide taint-tracking specific qldoc
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
super.hasFlow(source, sink)
}
}
/**
* Holds if taint can flow from `src` to `sink` in zero or more
* local (intra-procedural) steps.
*/
predicate localTaint(DataFlow::Node src, DataFlow::Node sink) { localTaintStep*(src, sink) }
/**
* Holds if taint can flow in one local step from `src` to `sink`.
*/
predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
DataFlow::localFlowStep(src, sink) or
localAdditionalTaintStep(src, sink)
}
/**
* Holds if taint can flow in one local step from `src` to `sink` excluding
* local data flow steps. That is, `src` and `sink` are likely to represent
* different objects.
*/
predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
localAdditionalTaintExprStep(src.asExpr(), sink.asExpr())
or
exists(Argument arg |
src.asExpr() = arg and
arg.isVararg() and
sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall()
)
}
/**
* Holds if taint can flow in one local step from `src` to `sink` excluding
* local data flow steps. That is, `src` and `sink` are likely to represent
* different objects.
*/
private predicate localAdditionalTaintExprStep(Expr src, Expr sink) {
sink.(AddExpr).getAnOperand() = src and sink.getType() instanceof TypeString
or
sink.(AssignAddExpr).getSource() = src and sink.getType() instanceof TypeString
or
sink.(ArrayCreationExpr).getInit() = src
or
sink.(ArrayInit).getAnInit() = src
or
sink.(ArrayAccess).getArray() = src
or
sink.(LogicExpr).getAnOperand() = src
or
exists(Assignment assign | assign.getSource() = src |
sink = assign.getDest().(ArrayAccess).getArray()
)
or
exists(EnhancedForStmt for, SsaExplicitUpdate v |
for.getExpr() = src and
v.getDefiningExpr() = for.getVariable() and
v.getAFirstUse() = sink
)
or
containerStep(src, sink)
or
constructorStep(src, sink)
or
qualifierToMethodStep(src, sink)
or
qualifierToArgumentStep(src, sink)
or
argToMethodStep(src, sink)
or
argToArgStep(src, sink)
or
argToQualifierStep(src, sink)
or
comparisonStep(src, sink)
or
stringBuilderStep(src, sink)
or
serializationStep(src, sink)
}
private class BulkData extends RefType {
BulkData() {
this.(Array).getElementType().(PrimitiveType).getName().regexpMatch("byte|char")
or
exists(RefType t | this.getASourceSupertype*() = t |
t.hasQualifiedName("java.io", "InputStream") or
t.hasQualifiedName("java.nio", "ByteBuffer") or
t.hasQualifiedName("java.lang", "Readable") or
t.hasQualifiedName("java.io", "DataInput") or
t.hasQualifiedName("java.nio.channels", "ReadableByteChannel")
)
}
}
/**
* Holds if `c` is a constructor for a subclass of `java.io.InputStream` that
* wraps an underlying data source. The underlying data source is given as a
* the `argi`'th parameter to the constructor.
*
* An object construction of such a wrapper is likely to preserve the data flow
* status of its argument.
*/
private predicate inputStreamWrapper(Constructor c, int argi) {
c.getParameterType(argi) instanceof BulkData and
c.getDeclaringType().getASourceSupertype().hasQualifiedName("java.io", "InputStream")
}
/** An object construction that preserves the data flow status of any of its arguments. */
private predicate constructorStep(Expr tracked, ConstructorCall sink) {
exists(int argi | sink.getArgument(argi) = tracked |
exists(string s | sink.getConstructedType().getQualifiedName() = s |
// String constructor does nothing to data
s = "java.lang.String" and argi = 0
or
// some readers preserve the content of streams
s = "java.io.InputStreamReader" and argi = 0
or
s = "java.io.BufferedReader" and argi = 0
or
s = "java.io.CharArrayReader" and argi = 0
or
s = "java.io.StringReader" and argi = 0
or
// data preserved through streams
s = "java.io.ObjectInputStream" and argi = 0
or
s = "java.io.ByteArrayInputStream" and argi = 0
or
s = "java.io.DataInputStream" and argi = 0
or
s = "java.io.BufferedInputStream" and argi = 0
or
s = "com.esotericsoftware.kryo.io.Input" and argi = 0
or
s = "java.beans.XMLDecoder" and argi = 0
or
// a tokenizer preserves the content of a string
s = "java.util.StringTokenizer" and argi = 0
or
// unzipping the stream preserves content
s = "java.util.zip.ZipInputStream" and argi = 0
or
s = "java.util.zip.GZIPInputStream" and argi = 0
or
// string builders and buffers
s = "java.lang.StringBuilder" and argi = 0
or
s = "java.lang.StringBuffer" and argi = 0
or
// a cookie with tainted ingredients is tainted
s = "javax.servlet.http.Cookie" and argi = 0
or
s = "javax.servlet.http.Cookie" and argi = 1
or
// various xml stream source constructors.
s = "org.xml.sax.InputSource" and argi = 0
or
s = "javax.xml.transform.sax.SAXSource" and argi = 0 and sink.getNumArgument() = 1
or
s = "javax.xml.transform.sax.SAXSource" and argi = 1 and sink.getNumArgument() = 2
or
s = "javax.xml.transform.stream.StreamSource" and argi = 0
or
//a URI constructed from a tainted string is tainted.
s = "java.net.URI" and argi = 0 and sink.getNumArgument() = 1
)
or
exists(RefType t | t.getQualifiedName() = "java.lang.Number" |
hasSubtype*(t, sink.getConstructedType())
) and
argi = 0
or
// wrappers constructed by extension
exists(Constructor c, Parameter p, SuperConstructorInvocationStmt sup |
c = sink.getConstructor() and
p = c.getParameter(argi) and
sup.getEnclosingCallable() = c and
constructorStep(p.getAnAccess(), sup)
)
or
// a custom InputStream that wraps a tainted data source is tainted
inputStreamWrapper(sink.getConstructor(), argi)
)
}
/** Access to a method that passes taint from qualifier to argument. */
private predicate qualifierToArgumentStep(Expr tracked, RValue sink) {
exists(MethodAccess ma, int arg |
taintPreservingQualifierToArgument(ma.getMethod(), arg) and
tracked = ma.getQualifier() and
sink = ma.getArgument(arg)
)
}
/** Methods that passes tainted data from qualifier to argument. */
private predicate taintPreservingQualifierToArgument(Method m, int arg) {
m.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and
m.hasName("writeTo") and
arg = 0
or
m.getDeclaringType().hasQualifiedName("java.io", "InputStream") and
m.hasName("read") and
arg = 0
or
m.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Reader") and
m.hasName("read") and
arg = 0
}
/** Access to a method that passes taint from the qualifier. */
private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
(taintPreservingQualifierToMethod(sink.getMethod()) or unsafeEscape(sink)) and
tracked = sink.getQualifier()
}
/**
* Methods that return tainted data when called on tainted data.
*/
private predicate taintPreservingQualifierToMethod(Method m) {
m.getDeclaringType() instanceof TypeString and
(
m.getName() = "concat" or
m.getName() = "endsWith" or
m.getName() = "getBytes" or
m.getName() = "split" or
m.getName() = "substring" or
m.getName() = "toCharArray" or
m.getName() = "toLowerCase" or
m.getName() = "toString" or
m.getName() = "toUpperCase" or
m.getName() = "trim"
)
or
exists(Class c | c.getQualifiedName() = "java.lang.Number" |
hasSubtype*(c, m.getDeclaringType())
) and
(
m.getName().matches("to%String") or
m.getName() = "toByteArray" or
m.getName().matches("%Value")
)
or
m.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Reader") and
(
m.getName() = "read" and m.getNumberOfParameters() = 0
or
m.getName() = "readLine"
)
or
m.getDeclaringType().getQualifiedName().matches("%StringWriter") and
m.getName() = "toString"
or
m.getDeclaringType().hasQualifiedName("java.util", "StringTokenizer") and
m.getName().matches("next%")
or
m.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and
(m.getName() = "toByteArray" or m.getName() = "toString")
or
m.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and
m.getName().matches("read%")
or
(
m.getDeclaringType().hasQualifiedName("java.lang", "StringBuilder") or
m.getDeclaringType().hasQualifiedName("java.lang", "StringBuffer")
) and
(m.getName() = "toString" or m.getName() = "append")
or
m.getDeclaringType().hasQualifiedName("javax.xml.transform.sax", "SAXSource") and
m.hasName("getInputSource")
or
m.getDeclaringType().hasQualifiedName("javax.xml.transform.stream", "StreamSource") and
m.hasName("getInputStream")
or
m instanceof IntentGetExtraMethod
or
m.getDeclaringType().hasQualifiedName("java.nio", "ByteBuffer") and
m.hasName("get")
or
m = any(GuiceProvider gp).getAnOverridingGetMethod()
or
m = any(ProtobufMessageLite p).getAGetterMethod()
}
private class StringReplaceMethod extends Method {
StringReplaceMethod() {
getDeclaringType() instanceof TypeString and
(
hasName("replace") or
hasName("replaceAll") or
hasName("replaceFirst")
)
}
}
private predicate unsafeEscape(MethodAccess ma) {
// Removing `<script>` tags using a string-replace method is
// unsafe if such a tag is embedded inside another one (e.g. `<scr<script>ipt>`).
exists(StringReplaceMethod m | ma.getMethod() = m |
ma.getArgument(0).(StringLiteral).getRepresentedString() = "(<script>)" and
ma.getArgument(1).(StringLiteral).getRepresentedString() = ""
)
}
/** Access to a method that passes taint from an argument. */
private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
exists(Method m, int i |
m = sink.(MethodAccess).getMethod() and
taintPreservingArgumentToMethod(m, i) and
tracked = sink.(MethodAccess).getArgument(i)
)
}
/**
* Holds if `method` is a library method that return tainted data if its
* `arg`th argument is tainted.
*/
private predicate taintPreservingArgumentToMethod(Method method, int arg) {
method instanceof StringReplaceMethod and arg = 1
or
exists(Class c | c.getQualifiedName() = "java.lang.Number" |
hasSubtype*(c, method.getDeclaringType())
) and
(
method.getName().matches("parse%") and arg = 0
or
method.getName().matches("valueOf%") and arg = 0
or
method.getName().matches("to%String") and arg = 0
)
or
method.getDeclaringType() instanceof TypeString and
method.getName() = "concat" and
arg = 0
or
(
method.getDeclaringType().hasQualifiedName("java.lang", "StringBuilder") or
method.getDeclaringType().hasQualifiedName("java.lang", "StringBuffer")
) and
(
method.getName() = "append" and arg = 0
or
method.getName() = "insert" and arg = 1
or
method.getName() = "replace" and arg = 2
)
or
(
method.getDeclaringType().hasQualifiedName("java.util", "Base64$Encoder") or
method.getDeclaringType().hasQualifiedName("java.util", "Base64$Decoder")
) and
(
method.getName() = "encode" and arg = 0 and method.getNumberOfParameters() = 1
or
method.getName() = "decode" and arg = 0 and method.getNumberOfParameters() = 1
or
method.getName() = "encodeToString" and arg = 0
or
method.getName() = "wrap" and arg = 0
)
or
method.getDeclaringType().hasQualifiedName("org.apache.commons.io", "IOUtils") and
(
method.getName() = "buffer" and arg = 0
or
method.getName() = "readLines" and arg = 0
or
method.getName() = "readFully" and arg = 0 and method.getParameterType(1).hasName("int")
or
method.getName() = "toBufferedInputStream" and arg = 0
or
method.getName() = "toBufferedReader" and arg = 0
or
method.getName() = "toByteArray" and arg = 0
or
method.getName() = "toCharArray" and arg = 0
or
method.getName() = "toInputStream" and arg = 0
or
method.getName() = "toString" and arg = 0
)
or
// A URI created from a tainted string is still tainted.
method.getDeclaringType().hasQualifiedName("java.net", "URI") and
method.hasName("create") and
arg = 0
or
method.getDeclaringType().hasQualifiedName("javax.xml.transform.sax", "SAXSource") and
method.hasName("sourceToInputSource") and
arg = 0
or
exists(ProtobufParser p | method = p.getAParseFromMethod()) and
arg = 0
or
exists(ProtobufMessageLite m | method = m.getAParseFromMethod()) and
arg = 0
}
/**
* Holds if `tracked` and `sink` are arguments to a method that transfers taint
* between arguments.
*/
private predicate argToArgStep(Expr tracked, RValue sink) {
exists(MethodAccess ma, Method method, int input, int output |
taintPreservingArgToArg(method, input, output) and
ma.getMethod() = method and
ma.getArgument(input) = tracked and
ma.getArgument(output) = sink
)
}
/**
* Holds if `method` is a library method that writes tainted data to the
* `output`th argument if the `input`th argument is tainted.
*/
private predicate taintPreservingArgToArg(Method method, int input, int output) {
method.getDeclaringType().hasQualifiedName("org.apache.commons.io", "IOUtils") and
(
method.hasName("copy") and input = 0 and output = 1
or
method.hasName("copyLarge") and input = 0 and output = 1
or
method.hasName("read") and input = 0 and output = 1
or
method.hasName("readFully") and
input = 0 and
output = 1 and
not method.getParameterType(1).hasName("int")
or
method.hasName("write") and input = 0 and output = 1
or
method.hasName("writeChunked") and input = 0 and output = 1
or
method.hasName("writeLines") and input = 0 and output = 2
or
method.hasName("writeLines") and input = 1 and output = 2
)
or
method.getDeclaringType().hasQualifiedName("java.lang", "System") and
method.hasName("arraycopy") and
input = 0 and
output = 2
}
/**
* Holds if `tracked` is the argument of a method that transfers taint
* from the argument to the qualifier and `sink` is the qualifier.
*/
private predicate argToQualifierStep(Expr tracked, Expr sink) {
exists(Method m, int i, MethodAccess ma |
taintPreservingArgumentToQualifier(m, i) and
ma.getMethod() = m and
tracked = ma.getArgument(i) and
sink = ma.getQualifier()
)
}
/**
* Holds if `method` is a method that transfers taint from argument to qualifier and
* `arg` is the index of the argument.
*/
private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
method.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and
method.hasName("write") and
arg = 0
}
/** A comparison or equality test with a constant. */
private predicate comparisonStep(Expr tracked, Expr sink) {
exists(Expr other |
exists(BinaryExpr e | e instanceof ComparisonExpr or e instanceof EqualityTest |
e = sink and
e.hasOperands(tracked, other)
)
or
exists(MethodAccess m | m.getMethod() instanceof EqualsMethod |
m = sink and
(
m.getQualifier() = tracked and m.getArgument(0) = other
or
m.getQualifier() = other and m.getArgument(0) = tracked
)
)
|
other.isCompileTimeConstant() or other instanceof NullLiteral
)
}
/** Flow through a `StringBuilder`. */
private predicate stringBuilderStep(Expr tracked, Expr sink) {
exists(StringBuilderVar sbvar, MethodAccess input, int arg |
input = sbvar.getAnInput(arg) and
tracked = input.getArgument(arg) and
sink = sbvar.getToStringCall()
)
}
/** Flow through data serialization. */
private predicate serializationStep(Expr tracked, Expr sink) {
exists(ObjectOutputStreamVar v, VariableAssign def |
def = v.getADef() and
exists(MethodAccess ma, RValue use |
ma.getArgument(0) = tracked and
ma = v.getAWriteObjectMethodAccess() and
use = ma.getQualifier() and
defUsePair(def, use)
) and
exists(RValue outputstream, ClassInstanceExpr cie |
cie = def.getSource() and
outputstream = cie.getArgument(0) and
adjacentUseUse(outputstream, sink)
)
)
}
/**
* A local variable that is assigned an `ObjectOutputStream`.
* Writing tainted data to such a stream causes the underlying
* `OutputStream` to be tainted.
*/
class ObjectOutputStreamVar extends LocalVariableDecl {
ObjectOutputStreamVar() {
exists(ClassInstanceExpr cie | cie = this.getAnAssignedValue() |
cie.getType() instanceof TypeObjectOutputStream
)
}
VariableAssign getADef() {
result.getSource().(ClassInstanceExpr).getType() instanceof TypeObjectOutputStream and
result.getDestVar() = this
}
MethodAccess getAWriteObjectMethodAccess() {
result.getQualifier() = getAnAccess() and
result.getMethod().hasName("writeObject")
}
}
}
/**
* A local variable that is initialized to a `StringBuilder`
* or `StringBuffer`. Such variables are often used to
* build up a query using string concatenation.
*/
class StringBuilderVar extends LocalVariableDecl {
StringBuilderVar() {
this.getType() instanceof TypeStringBuilder or
this.getType() instanceof TypeStringBuffer
}
/**
* Gets a call that adds something to this string builder, from the argument at the given index.
*/
MethodAccess getAnInput(int arg) {
result.getQualifier() = getAChainedReference() and
(
result.getMethod().getName() = "append" and arg = 0
or
result.getMethod().getName() = "insert" and arg = 1
or
result.getMethod().getName() = "replace" and arg = 2
)
}
/**
* Gets a call that appends something to this string builder.
*/
MethodAccess getAnAppend() {
result.getQualifier() = getAChainedReference() and
result.getMethod().getName() = "append"
}
MethodAccess getNextAppend(MethodAccess append) {
result = getAnAppend() and
append = getAnAppend() and
(
result.getQualifier() = append
or
not exists(MethodAccess chainAccess | chainAccess.getQualifier() = append) and
exists(RValue sbva1, RValue sbva2 |
adjacentUseUse(sbva1, sbva2) and
append.getQualifier() = getAChainedReference(sbva1) and
result.getQualifier() = sbva2
)
)
}
/**
* Gets a call that converts this string builder to a string.
*/
MethodAccess getToStringCall() {
result.getQualifier() = getAChainedReference() and
result.getMethod().getName() = "toString"
}
private Expr getAChainedReference(VarAccess sbva) {
// All the methods on `StringBuilder` that return the same type return
// the same object.
result = callReturningSameType+(sbva) and sbva = this.getAnAccess()
or
result = sbva and sbva = this.getAnAccess()
}
/**
* Gets an expression that refers to this `StringBuilder`, possibly after some chained calls.
*/
Expr getAChainedReference() { result = getAChainedReference(_) }
}
private MethodAccess callReturningSameType(Expr ref) {
ref = result.getQualifier() and
result.getMethod().getReturnType() = ref.getType()
deprecated class Configuration2 = TaintTracking2::Configuration;
}

View File

@@ -0,0 +1,7 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
module TaintTracking2 {
import semmle.code.java.dataflow.internal.tainttracking2.TaintTrackingImpl
}

View File

@@ -121,6 +121,27 @@ abstract class Configuration extends string {
deprecated predicate hasFlowBackward(Node source, Node sink) { hasFlow(source, sink) }
}
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0
or
strictcount(Node n | this.isSink(n)) < 0
or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
or
super.hasFlow(source, sink)
}
}
private predicate inBarrier(Node node, Configuration config) {
config.isBarrierIn(node) and
config.isSource(node)
@@ -162,7 +183,7 @@ private predicate isAdditionalFlowStep(
* Holds if data can flow in one local step from `node1` to `node2`.
*/
private predicate localFlowStep(Node node1, Node node2, Configuration config) {
localFlowStep(node1, node2) and
simpleLocalFlowStep(node1, node2) and
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
@@ -257,10 +278,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
or
// read
exists(Node mid, Content f |
nodeCandFwd1(mid, true, config) and
read(mid, f, node) and
storeCandFwd1(f, unbind(config)) and
exists(Content f |
nodeCandFwd1Read(f, node, config) and
storeCandFwd1(f, config) and
(stored = false or stored = true) and
not inBarrier(node, config)
)
@@ -287,9 +307,18 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
}
pragma[nomagic]
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCandFwd1(mid, true, config) and
read(mid, f, node)
)
}
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`.
*/
pragma[noinline]
private predicate storeCandFwd1(Content f, Configuration config) {
exists(Node mid, Node node |
not fullBarrier(node, config) and
@@ -337,10 +366,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand1(f, unbind(config)) and
nodeCand1(mid, true, config) and
exists(Content f |
nodeCand1Store(f, node, config) and
readCand1(f, config) and
(stored = false or stored = true)
)
or
@@ -377,6 +405,7 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
*/
pragma[noinline]
private predicate readCand1(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -387,6 +416,15 @@ private predicate readCand1(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCand1Store(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCand1(mid, true, config) and
storeCandFwd1(f, unbind(config)) and
store(node, f, mid)
)
}
private predicate throughFlowNodeCand(Node node, Configuration config) {
nodeCand1(node, false, config) and
not fullBarrier(node, config) and
@@ -648,10 +686,9 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
)
or
// read
exists(Node mid, Content f |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
storeCandFwd2(f, unbind(config)) and
exists(Content f |
nodeCandFwd2Read(f, node, fromArg, config) and
storeCandFwd2(f, config) and
(stored = false or stored = true)
)
or
@@ -674,6 +711,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd2`.
*/
pragma[noinline]
private predicate storeCandFwd2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -684,6 +722,15 @@ private predicate storeCandFwd2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCandFwd2Read(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
readCand1(f, unbind(config))
)
}
/**
* Holds if `node` is part of a path from a source to a sink in the given
* configuration taking simple call contexts into consideration.
@@ -721,10 +768,9 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand2(f, unbind(config)) and
nodeCand2(mid, toReturn, true, config) and
exists(Content f |
nodeCand2Store(f, node, toReturn, config) and
readCand2(f, config) and
(stored = false or stored = true)
)
or
@@ -755,6 +801,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand2`.
*/
pragma[noinline]
private predicate readCand2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -765,16 +812,21 @@ private predicate readCand2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node n1, Node n2 |
store(n1, f, n2) and
nodeCand2(n1, _, _, conf) and
nodeCand2(n2, _, _, unbind(conf))
pragma[noinline]
private predicate nodeCand2Store(Content f, Node node, boolean toReturn, Configuration config) {
exists(Node mid |
store(node, f, mid) and
nodeCand2(mid, toReturn, true, config)
)
}
private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node node |
nodeCand2Store(f, node, _, conf) and
nodeCand2(node, _, _, conf)
)
}
/**
* Holds if `f` is the target of both a store and a read in the path graph
@@ -783,7 +835,7 @@ private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[noinline]
private predicate readStoreCand(Content f, Configuration conf) {
storeCand(f, conf) and
readCand(f, conf)
readCand2(f, conf)
}
private predicate nodeCand(Node node, Configuration config) { nodeCand2(node, _, _, config) }
@@ -1009,15 +1061,13 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
apf.headUsesContent(f)
)
or
exists(Node mid, Content f, AccessPathFront apf0 |
flowCandFwd(mid, fromArg, apf0, config) and
read(mid, f, node) and
nodeCand(node, config) and
apf0.headUsesContent(f) and
consCandFwd(f, apf, unbind(config))
exists(Content f |
flowCandFwdRead(f, node, fromArg, config) and
consCandFwd(f, apf, config)
)
}
pragma[noinline]
private predicate consCandFwd(Content f, AccessPathFront apf, Configuration config) {
exists(Node mid, Node n |
flowCandFwd(mid, _, apf, config) and
@@ -1028,6 +1078,16 @@ private predicate consCandFwd(Content f, AccessPathFront apf, Configuration conf
)
}
pragma[nomagic]
private predicate flowCandFwdRead(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid, AccessPathFront apf |
flowCandFwd(mid, fromArg, apf, config) and
read(mid, f, node) and
apf.headUsesContent(f) and
nodeCand(node, unbind(config))
)
}
/**
* Holds if data can flow from a source to `node` with the given `apf` and
* from there flow to a sink.
@@ -1118,6 +1178,7 @@ private predicate flowCandRead(
)
}
pragma[nomagic]
private predicate flowCandStore(
Node node, Content f, boolean toReturn, AccessPathFront apf0, Configuration config
) {
@@ -1127,6 +1188,7 @@ private predicate flowCandStore(
)
}
pragma[noinline]
private predicate consCand(Content f, AccessPathFront apf, Configuration config) {
consCandFwd(f, apf, config) and
exists(Node n, AccessPathFront apf0 |
@@ -1651,6 +1713,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
valuePathThroughCallable(mid, node, cc) and ap = mid.getAp()
}
pragma[noinline]
private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) {
exists(Content f, AccessPath ap0 |
ap0 = mid.getAp() and

View File

@@ -121,6 +121,27 @@ abstract class Configuration extends string {
deprecated predicate hasFlowBackward(Node source, Node sink) { hasFlow(source, sink) }
}
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0
or
strictcount(Node n | this.isSink(n)) < 0
or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
or
super.hasFlow(source, sink)
}
}
private predicate inBarrier(Node node, Configuration config) {
config.isBarrierIn(node) and
config.isSource(node)
@@ -162,7 +183,7 @@ private predicate isAdditionalFlowStep(
* Holds if data can flow in one local step from `node1` to `node2`.
*/
private predicate localFlowStep(Node node1, Node node2, Configuration config) {
localFlowStep(node1, node2) and
simpleLocalFlowStep(node1, node2) and
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
@@ -257,10 +278,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
or
// read
exists(Node mid, Content f |
nodeCandFwd1(mid, true, config) and
read(mid, f, node) and
storeCandFwd1(f, unbind(config)) and
exists(Content f |
nodeCandFwd1Read(f, node, config) and
storeCandFwd1(f, config) and
(stored = false or stored = true) and
not inBarrier(node, config)
)
@@ -287,9 +307,18 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
}
pragma[nomagic]
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCandFwd1(mid, true, config) and
read(mid, f, node)
)
}
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`.
*/
pragma[noinline]
private predicate storeCandFwd1(Content f, Configuration config) {
exists(Node mid, Node node |
not fullBarrier(node, config) and
@@ -337,10 +366,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand1(f, unbind(config)) and
nodeCand1(mid, true, config) and
exists(Content f |
nodeCand1Store(f, node, config) and
readCand1(f, config) and
(stored = false or stored = true)
)
or
@@ -377,6 +405,7 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
*/
pragma[noinline]
private predicate readCand1(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -387,6 +416,15 @@ private predicate readCand1(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCand1Store(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCand1(mid, true, config) and
storeCandFwd1(f, unbind(config)) and
store(node, f, mid)
)
}
private predicate throughFlowNodeCand(Node node, Configuration config) {
nodeCand1(node, false, config) and
not fullBarrier(node, config) and
@@ -648,10 +686,9 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
)
or
// read
exists(Node mid, Content f |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
storeCandFwd2(f, unbind(config)) and
exists(Content f |
nodeCandFwd2Read(f, node, fromArg, config) and
storeCandFwd2(f, config) and
(stored = false or stored = true)
)
or
@@ -674,6 +711,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd2`.
*/
pragma[noinline]
private predicate storeCandFwd2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -684,6 +722,15 @@ private predicate storeCandFwd2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCandFwd2Read(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
readCand1(f, unbind(config))
)
}
/**
* Holds if `node` is part of a path from a source to a sink in the given
* configuration taking simple call contexts into consideration.
@@ -721,10 +768,9 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand2(f, unbind(config)) and
nodeCand2(mid, toReturn, true, config) and
exists(Content f |
nodeCand2Store(f, node, toReturn, config) and
readCand2(f, config) and
(stored = false or stored = true)
)
or
@@ -755,6 +801,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand2`.
*/
pragma[noinline]
private predicate readCand2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -765,16 +812,21 @@ private predicate readCand2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node n1, Node n2 |
store(n1, f, n2) and
nodeCand2(n1, _, _, conf) and
nodeCand2(n2, _, _, unbind(conf))
pragma[noinline]
private predicate nodeCand2Store(Content f, Node node, boolean toReturn, Configuration config) {
exists(Node mid |
store(node, f, mid) and
nodeCand2(mid, toReturn, true, config)
)
}
private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node node |
nodeCand2Store(f, node, _, conf) and
nodeCand2(node, _, _, conf)
)
}
/**
* Holds if `f` is the target of both a store and a read in the path graph
@@ -783,7 +835,7 @@ private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[noinline]
private predicate readStoreCand(Content f, Configuration conf) {
storeCand(f, conf) and
readCand(f, conf)
readCand2(f, conf)
}
private predicate nodeCand(Node node, Configuration config) { nodeCand2(node, _, _, config) }
@@ -1009,15 +1061,13 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
apf.headUsesContent(f)
)
or
exists(Node mid, Content f, AccessPathFront apf0 |
flowCandFwd(mid, fromArg, apf0, config) and
read(mid, f, node) and
nodeCand(node, config) and
apf0.headUsesContent(f) and
consCandFwd(f, apf, unbind(config))
exists(Content f |
flowCandFwdRead(f, node, fromArg, config) and
consCandFwd(f, apf, config)
)
}
pragma[noinline]
private predicate consCandFwd(Content f, AccessPathFront apf, Configuration config) {
exists(Node mid, Node n |
flowCandFwd(mid, _, apf, config) and
@@ -1028,6 +1078,16 @@ private predicate consCandFwd(Content f, AccessPathFront apf, Configuration conf
)
}
pragma[nomagic]
private predicate flowCandFwdRead(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid, AccessPathFront apf |
flowCandFwd(mid, fromArg, apf, config) and
read(mid, f, node) and
apf.headUsesContent(f) and
nodeCand(node, unbind(config))
)
}
/**
* Holds if data can flow from a source to `node` with the given `apf` and
* from there flow to a sink.
@@ -1118,6 +1178,7 @@ private predicate flowCandRead(
)
}
pragma[nomagic]
private predicate flowCandStore(
Node node, Content f, boolean toReturn, AccessPathFront apf0, Configuration config
) {
@@ -1127,6 +1188,7 @@ private predicate flowCandStore(
)
}
pragma[noinline]
private predicate consCand(Content f, AccessPathFront apf, Configuration config) {
consCandFwd(f, apf, config) and
exists(Node n, AccessPathFront apf0 |
@@ -1651,6 +1713,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
valuePathThroughCallable(mid, node, cc) and ap = mid.getAp()
}
pragma[noinline]
private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) {
exists(Content f, AccessPath ap0 |
ap0 = mid.getAp() and

View File

@@ -121,6 +121,27 @@ abstract class Configuration extends string {
deprecated predicate hasFlowBackward(Node source, Node sink) { hasFlow(source, sink) }
}
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0
or
strictcount(Node n | this.isSink(n)) < 0
or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
or
super.hasFlow(source, sink)
}
}
private predicate inBarrier(Node node, Configuration config) {
config.isBarrierIn(node) and
config.isSource(node)
@@ -162,7 +183,7 @@ private predicate isAdditionalFlowStep(
* Holds if data can flow in one local step from `node1` to `node2`.
*/
private predicate localFlowStep(Node node1, Node node2, Configuration config) {
localFlowStep(node1, node2) and
simpleLocalFlowStep(node1, node2) and
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
@@ -257,10 +278,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
or
// read
exists(Node mid, Content f |
nodeCandFwd1(mid, true, config) and
read(mid, f, node) and
storeCandFwd1(f, unbind(config)) and
exists(Content f |
nodeCandFwd1Read(f, node, config) and
storeCandFwd1(f, config) and
(stored = false or stored = true) and
not inBarrier(node, config)
)
@@ -287,9 +307,18 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
}
pragma[nomagic]
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCandFwd1(mid, true, config) and
read(mid, f, node)
)
}
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`.
*/
pragma[noinline]
private predicate storeCandFwd1(Content f, Configuration config) {
exists(Node mid, Node node |
not fullBarrier(node, config) and
@@ -337,10 +366,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand1(f, unbind(config)) and
nodeCand1(mid, true, config) and
exists(Content f |
nodeCand1Store(f, node, config) and
readCand1(f, config) and
(stored = false or stored = true)
)
or
@@ -377,6 +405,7 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
*/
pragma[noinline]
private predicate readCand1(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -387,6 +416,15 @@ private predicate readCand1(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCand1Store(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCand1(mid, true, config) and
storeCandFwd1(f, unbind(config)) and
store(node, f, mid)
)
}
private predicate throughFlowNodeCand(Node node, Configuration config) {
nodeCand1(node, false, config) and
not fullBarrier(node, config) and
@@ -648,10 +686,9 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
)
or
// read
exists(Node mid, Content f |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
storeCandFwd2(f, unbind(config)) and
exists(Content f |
nodeCandFwd2Read(f, node, fromArg, config) and
storeCandFwd2(f, config) and
(stored = false or stored = true)
)
or
@@ -674,6 +711,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd2`.
*/
pragma[noinline]
private predicate storeCandFwd2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -684,6 +722,15 @@ private predicate storeCandFwd2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCandFwd2Read(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
readCand1(f, unbind(config))
)
}
/**
* Holds if `node` is part of a path from a source to a sink in the given
* configuration taking simple call contexts into consideration.
@@ -721,10 +768,9 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand2(f, unbind(config)) and
nodeCand2(mid, toReturn, true, config) and
exists(Content f |
nodeCand2Store(f, node, toReturn, config) and
readCand2(f, config) and
(stored = false or stored = true)
)
or
@@ -755,6 +801,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand2`.
*/
pragma[noinline]
private predicate readCand2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -765,16 +812,21 @@ private predicate readCand2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node n1, Node n2 |
store(n1, f, n2) and
nodeCand2(n1, _, _, conf) and
nodeCand2(n2, _, _, unbind(conf))
pragma[noinline]
private predicate nodeCand2Store(Content f, Node node, boolean toReturn, Configuration config) {
exists(Node mid |
store(node, f, mid) and
nodeCand2(mid, toReturn, true, config)
)
}
private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node node |
nodeCand2Store(f, node, _, conf) and
nodeCand2(node, _, _, conf)
)
}
/**
* Holds if `f` is the target of both a store and a read in the path graph
@@ -783,7 +835,7 @@ private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[noinline]
private predicate readStoreCand(Content f, Configuration conf) {
storeCand(f, conf) and
readCand(f, conf)
readCand2(f, conf)
}
private predicate nodeCand(Node node, Configuration config) { nodeCand2(node, _, _, config) }
@@ -1009,15 +1061,13 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
apf.headUsesContent(f)
)
or
exists(Node mid, Content f, AccessPathFront apf0 |
flowCandFwd(mid, fromArg, apf0, config) and
read(mid, f, node) and
nodeCand(node, config) and
apf0.headUsesContent(f) and
consCandFwd(f, apf, unbind(config))
exists(Content f |
flowCandFwdRead(f, node, fromArg, config) and
consCandFwd(f, apf, config)
)
}
pragma[noinline]
private predicate consCandFwd(Content f, AccessPathFront apf, Configuration config) {
exists(Node mid, Node n |
flowCandFwd(mid, _, apf, config) and
@@ -1028,6 +1078,16 @@ private predicate consCandFwd(Content f, AccessPathFront apf, Configuration conf
)
}
pragma[nomagic]
private predicate flowCandFwdRead(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid, AccessPathFront apf |
flowCandFwd(mid, fromArg, apf, config) and
read(mid, f, node) and
apf.headUsesContent(f) and
nodeCand(node, unbind(config))
)
}
/**
* Holds if data can flow from a source to `node` with the given `apf` and
* from there flow to a sink.
@@ -1118,6 +1178,7 @@ private predicate flowCandRead(
)
}
pragma[nomagic]
private predicate flowCandStore(
Node node, Content f, boolean toReturn, AccessPathFront apf0, Configuration config
) {
@@ -1127,6 +1188,7 @@ private predicate flowCandStore(
)
}
pragma[noinline]
private predicate consCand(Content f, AccessPathFront apf, Configuration config) {
consCandFwd(f, apf, config) and
exists(Node n, AccessPathFront apf0 |
@@ -1651,6 +1713,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
valuePathThroughCallable(mid, node, cc) and ap = mid.getAp()
}
pragma[noinline]
private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) {
exists(Content f, AccessPath ap0 |
ap0 = mid.getAp() and

View File

@@ -121,6 +121,27 @@ abstract class Configuration extends string {
deprecated predicate hasFlowBackward(Node source, Node sink) { hasFlow(source, sink) }
}
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0
or
strictcount(Node n | this.isSink(n)) < 0
or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
or
super.hasFlow(source, sink)
}
}
private predicate inBarrier(Node node, Configuration config) {
config.isBarrierIn(node) and
config.isSource(node)
@@ -162,7 +183,7 @@ private predicate isAdditionalFlowStep(
* Holds if data can flow in one local step from `node1` to `node2`.
*/
private predicate localFlowStep(Node node1, Node node2, Configuration config) {
localFlowStep(node1, node2) and
simpleLocalFlowStep(node1, node2) and
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
@@ -257,10 +278,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
or
// read
exists(Node mid, Content f |
nodeCandFwd1(mid, true, config) and
read(mid, f, node) and
storeCandFwd1(f, unbind(config)) and
exists(Content f |
nodeCandFwd1Read(f, node, config) and
storeCandFwd1(f, config) and
(stored = false or stored = true) and
not inBarrier(node, config)
)
@@ -287,9 +307,18 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
}
pragma[nomagic]
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCandFwd1(mid, true, config) and
read(mid, f, node)
)
}
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`.
*/
pragma[noinline]
private predicate storeCandFwd1(Content f, Configuration config) {
exists(Node mid, Node node |
not fullBarrier(node, config) and
@@ -337,10 +366,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand1(f, unbind(config)) and
nodeCand1(mid, true, config) and
exists(Content f |
nodeCand1Store(f, node, config) and
readCand1(f, config) and
(stored = false or stored = true)
)
or
@@ -377,6 +405,7 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
*/
pragma[noinline]
private predicate readCand1(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -387,6 +416,15 @@ private predicate readCand1(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCand1Store(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCand1(mid, true, config) and
storeCandFwd1(f, unbind(config)) and
store(node, f, mid)
)
}
private predicate throughFlowNodeCand(Node node, Configuration config) {
nodeCand1(node, false, config) and
not fullBarrier(node, config) and
@@ -648,10 +686,9 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
)
or
// read
exists(Node mid, Content f |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
storeCandFwd2(f, unbind(config)) and
exists(Content f |
nodeCandFwd2Read(f, node, fromArg, config) and
storeCandFwd2(f, config) and
(stored = false or stored = true)
)
or
@@ -674,6 +711,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd2`.
*/
pragma[noinline]
private predicate storeCandFwd2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -684,6 +722,15 @@ private predicate storeCandFwd2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCandFwd2Read(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
readCand1(f, unbind(config))
)
}
/**
* Holds if `node` is part of a path from a source to a sink in the given
* configuration taking simple call contexts into consideration.
@@ -721,10 +768,9 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand2(f, unbind(config)) and
nodeCand2(mid, toReturn, true, config) and
exists(Content f |
nodeCand2Store(f, node, toReturn, config) and
readCand2(f, config) and
(stored = false or stored = true)
)
or
@@ -755,6 +801,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand2`.
*/
pragma[noinline]
private predicate readCand2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -765,16 +812,21 @@ private predicate readCand2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node n1, Node n2 |
store(n1, f, n2) and
nodeCand2(n1, _, _, conf) and
nodeCand2(n2, _, _, unbind(conf))
pragma[noinline]
private predicate nodeCand2Store(Content f, Node node, boolean toReturn, Configuration config) {
exists(Node mid |
store(node, f, mid) and
nodeCand2(mid, toReturn, true, config)
)
}
private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node node |
nodeCand2Store(f, node, _, conf) and
nodeCand2(node, _, _, conf)
)
}
/**
* Holds if `f` is the target of both a store and a read in the path graph
@@ -783,7 +835,7 @@ private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[noinline]
private predicate readStoreCand(Content f, Configuration conf) {
storeCand(f, conf) and
readCand(f, conf)
readCand2(f, conf)
}
private predicate nodeCand(Node node, Configuration config) { nodeCand2(node, _, _, config) }
@@ -1009,15 +1061,13 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
apf.headUsesContent(f)
)
or
exists(Node mid, Content f, AccessPathFront apf0 |
flowCandFwd(mid, fromArg, apf0, config) and
read(mid, f, node) and
nodeCand(node, config) and
apf0.headUsesContent(f) and
consCandFwd(f, apf, unbind(config))
exists(Content f |
flowCandFwdRead(f, node, fromArg, config) and
consCandFwd(f, apf, config)
)
}
pragma[noinline]
private predicate consCandFwd(Content f, AccessPathFront apf, Configuration config) {
exists(Node mid, Node n |
flowCandFwd(mid, _, apf, config) and
@@ -1028,6 +1078,16 @@ private predicate consCandFwd(Content f, AccessPathFront apf, Configuration conf
)
}
pragma[nomagic]
private predicate flowCandFwdRead(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid, AccessPathFront apf |
flowCandFwd(mid, fromArg, apf, config) and
read(mid, f, node) and
apf.headUsesContent(f) and
nodeCand(node, unbind(config))
)
}
/**
* Holds if data can flow from a source to `node` with the given `apf` and
* from there flow to a sink.
@@ -1118,6 +1178,7 @@ private predicate flowCandRead(
)
}
pragma[nomagic]
private predicate flowCandStore(
Node node, Content f, boolean toReturn, AccessPathFront apf0, Configuration config
) {
@@ -1127,6 +1188,7 @@ private predicate flowCandStore(
)
}
pragma[noinline]
private predicate consCand(Content f, AccessPathFront apf, Configuration config) {
consCandFwd(f, apf, config) and
exists(Node n, AccessPathFront apf0 |
@@ -1651,6 +1713,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
valuePathThroughCallable(mid, node, cc) and ap = mid.getAp()
}
pragma[noinline]
private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) {
exists(Content f, AccessPath ap0 |
ap0 = mid.getAp() and

View File

@@ -121,6 +121,27 @@ abstract class Configuration extends string {
deprecated predicate hasFlowBackward(Node source, Node sink) { hasFlow(source, sink) }
}
/**
* This class exists to prevent mutual recursion between the user-overridden
* member predicates of `Configuration` and the rest of the data-flow library.
* Good performance cannot be guaranteed in the presence of such recursion, so
* it should be replaced by using more than one copy of the data flow library.
*/
abstract private class ConfigurationRecursionPrevention extends Configuration {
bindingset[this]
ConfigurationRecursionPrevention() { any() }
override predicate hasFlow(Node source, Node sink) {
strictcount(Node n | this.isSource(n)) < 0
or
strictcount(Node n | this.isSink(n)) < 0
or
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
or
super.hasFlow(source, sink)
}
}
private predicate inBarrier(Node node, Configuration config) {
config.isBarrierIn(node) and
config.isSource(node)
@@ -162,7 +183,7 @@ private predicate isAdditionalFlowStep(
* Holds if data can flow in one local step from `node1` to `node2`.
*/
private predicate localFlowStep(Node node1, Node node2, Configuration config) {
localFlowStep(node1, node2) and
simpleLocalFlowStep(node1, node2) and
not outBarrier(node1, config) and
not inBarrier(node2, config) and
not fullBarrier(node1, config) and
@@ -257,10 +278,9 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
or
// read
exists(Node mid, Content f |
nodeCandFwd1(mid, true, config) and
read(mid, f, node) and
storeCandFwd1(f, unbind(config)) and
exists(Content f |
nodeCandFwd1Read(f, node, config) and
storeCandFwd1(f, config) and
(stored = false or stored = true) and
not inBarrier(node, config)
)
@@ -287,9 +307,18 @@ private predicate nodeCandFwd1(Node node, boolean stored, Configuration config)
)
}
pragma[nomagic]
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCandFwd1(mid, true, config) and
read(mid, f, node)
)
}
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`.
*/
pragma[noinline]
private predicate storeCandFwd1(Content f, Configuration config) {
exists(Node mid, Node node |
not fullBarrier(node, config) and
@@ -337,10 +366,9 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand1(f, unbind(config)) and
nodeCand1(mid, true, config) and
exists(Content f |
nodeCand1Store(f, node, config) and
readCand1(f, config) and
(stored = false or stored = true)
)
or
@@ -377,6 +405,7 @@ private predicate nodeCand1(Node node, boolean stored, Configuration config) {
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
*/
pragma[noinline]
private predicate readCand1(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -387,6 +416,15 @@ private predicate readCand1(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCand1Store(Content f, Node node, Configuration config) {
exists(Node mid |
nodeCand1(mid, true, config) and
storeCandFwd1(f, unbind(config)) and
store(node, f, mid)
)
}
private predicate throughFlowNodeCand(Node node, Configuration config) {
nodeCand1(node, false, config) and
not fullBarrier(node, config) and
@@ -648,10 +686,9 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
)
or
// read
exists(Node mid, Content f |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
storeCandFwd2(f, unbind(config)) and
exists(Content f |
nodeCandFwd2Read(f, node, fromArg, config) and
storeCandFwd2(f, config) and
(stored = false or stored = true)
)
or
@@ -674,6 +711,7 @@ private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Confi
/**
* Holds if `f` is the target of a store in the flow covered by `nodeCandFwd2`.
*/
pragma[noinline]
private predicate storeCandFwd2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -684,6 +722,15 @@ private predicate storeCandFwd2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate nodeCandFwd2Read(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid |
nodeCandFwd2(mid, fromArg, true, config) and
read(mid, f, node) and
readCand1(f, unbind(config))
)
}
/**
* Holds if `node` is part of a path from a source to a sink in the given
* configuration taking simple call contexts into consideration.
@@ -721,10 +768,9 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
)
or
// store
exists(Node mid, Content f |
store(node, f, mid) and
readCand2(f, unbind(config)) and
nodeCand2(mid, toReturn, true, config) and
exists(Content f |
nodeCand2Store(f, node, toReturn, config) and
readCand2(f, config) and
(stored = false or stored = true)
)
or
@@ -755,6 +801,7 @@ private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configu
/**
* Holds if `f` is the target of a read in the flow covered by `nodeCand2`.
*/
pragma[noinline]
private predicate readCand2(Content f, Configuration config) {
exists(Node mid, Node node |
useFieldFlow(config) and
@@ -765,16 +812,21 @@ private predicate readCand2(Content f, Configuration config) {
)
}
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node n1, Node n2 |
store(n1, f, n2) and
nodeCand2(n1, _, _, conf) and
nodeCand2(n2, _, _, unbind(conf))
pragma[noinline]
private predicate nodeCand2Store(Content f, Node node, boolean toReturn, Configuration config) {
exists(Node mid |
store(node, f, mid) and
nodeCand2(mid, toReturn, true, config)
)
}
private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[nomagic]
private predicate storeCand(Content f, Configuration conf) {
exists(Node node |
nodeCand2Store(f, node, _, conf) and
nodeCand2(node, _, _, conf)
)
}
/**
* Holds if `f` is the target of both a store and a read in the path graph
@@ -783,7 +835,7 @@ private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) }
pragma[noinline]
private predicate readStoreCand(Content f, Configuration conf) {
storeCand(f, conf) and
readCand(f, conf)
readCand2(f, conf)
}
private predicate nodeCand(Node node, Configuration config) { nodeCand2(node, _, _, config) }
@@ -1009,15 +1061,13 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
apf.headUsesContent(f)
)
or
exists(Node mid, Content f, AccessPathFront apf0 |
flowCandFwd(mid, fromArg, apf0, config) and
read(mid, f, node) and
nodeCand(node, config) and
apf0.headUsesContent(f) and
consCandFwd(f, apf, unbind(config))
exists(Content f |
flowCandFwdRead(f, node, fromArg, config) and
consCandFwd(f, apf, config)
)
}
pragma[noinline]
private predicate consCandFwd(Content f, AccessPathFront apf, Configuration config) {
exists(Node mid, Node n |
flowCandFwd(mid, _, apf, config) and
@@ -1028,6 +1078,16 @@ private predicate consCandFwd(Content f, AccessPathFront apf, Configuration conf
)
}
pragma[nomagic]
private predicate flowCandFwdRead(Content f, Node node, boolean fromArg, Configuration config) {
exists(Node mid, AccessPathFront apf |
flowCandFwd(mid, fromArg, apf, config) and
read(mid, f, node) and
apf.headUsesContent(f) and
nodeCand(node, unbind(config))
)
}
/**
* Holds if data can flow from a source to `node` with the given `apf` and
* from there flow to a sink.
@@ -1118,6 +1178,7 @@ private predicate flowCandRead(
)
}
pragma[nomagic]
private predicate flowCandStore(
Node node, Content f, boolean toReturn, AccessPathFront apf0, Configuration config
) {
@@ -1127,6 +1188,7 @@ private predicate flowCandStore(
)
}
pragma[noinline]
private predicate consCand(Content f, AccessPathFront apf, Configuration config) {
consCandFwd(f, apf, config) and
exists(Node n, AccessPathFront apf0 |
@@ -1651,6 +1713,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
valuePathThroughCallable(mid, node, cc) and ap = mid.getAp()
}
pragma[noinline]
private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) {
exists(Content f, AccessPath ap0 |
ap0 = mid.getAp() and

View File

@@ -35,7 +35,7 @@ private module ImplCommon {
or
exists(Node mid |
parameterValueFlowNoCtx(p, mid) and
localFlowStep(mid, node) and
simpleLocalFlowStep(mid, node) and
compatibleTypes(p.getType(), node.getType())
)
or
@@ -152,7 +152,7 @@ private module ImplCommon {
or
exists(Node mid |
parameterValueFlow(p, mid, cc) and
localFlowStep(mid, node) and
simpleLocalFlowStep(mid, node) and
compatibleTypes(p.getType(), node.getType())
)
or
@@ -209,7 +209,7 @@ private module ImplCommon {
* through a value-preserving method.
*/
private predicate localValueStep(Node node1, Node node2) {
localFlowStep(node1, node2) or
simpleLocalFlowStep(node1, node2) or
argumentValueFlowsThrough(node1, node2, _)
}

View File

@@ -350,8 +350,18 @@ predicate hasNonlocalValue(FieldRead fr) {
/**
* Holds if data can flow from `node1` to `node2` in one local step.
*/
cached
predicate localFlowStep(Node node1, Node node2) {
simpleLocalFlowStep(node1, node2)
}
/**
* INTERNAL: do not use.
*
* This is the local flow predicate that's used as a building block in global
* data flow. It may have less flow than the `localFlowStep` predicate.
*/
cached
predicate simpleLocalFlowStep(Node node1, Node node2) {
// Variable flow steps through adjacent def-use and use-use pairs.
exists(SsaExplicitUpdate upd |
upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or

View File

@@ -0,0 +1,641 @@
private import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.Collections
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.dataflow.DefUse
private import semmle.code.java.security.SecurityTests
private import semmle.code.java.security.Validation
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.Guice
private import semmle.code.java.frameworks.Protobuf
private import semmle.code.java.Maps
private import semmle.code.java.dataflow.internal.ContainerFlow
/**
* Holds if taint can flow from `src` to `sink` in zero or more
* local (intra-procedural) steps.
*/
predicate localTaint(DataFlow::Node src, DataFlow::Node sink) { localTaintStep*(src, sink) }
/**
* Holds if taint can flow in one local step from `src` to `sink`.
*/
predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
DataFlow::localFlowStep(src, sink) or
localAdditionalTaintStep(src, sink)
}
/**
* Holds if taint can flow in one local step from `src` to `sink` excluding
* local data flow steps. That is, `src` and `sink` are likely to represent
* different objects.
*/
predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
localAdditionalTaintExprStep(src.asExpr(), sink.asExpr())
or
exists(Argument arg |
src.asExpr() = arg and
arg.isVararg() and
sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall()
)
}
/**
* Holds if the additional step from `src` to `sink` should be included in all
* global taint flow configurations.
*/
predicate defaultAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
localAdditionalTaintStep(src, sink)
}
/**
* Holds if `node` should be a barrier in all global taint flow configurations
* but not in local taint.
*/
predicate defaultTaintBarrier(DataFlow::Node node) {
// Ignore paths through test code.
node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or
node.asExpr() instanceof ValidatedVariableAccess
}
/**
* Holds if taint can flow in one local step from `src` to `sink` excluding
* local data flow steps. That is, `src` and `sink` are likely to represent
* different objects.
*/
private predicate localAdditionalTaintExprStep(Expr src, Expr sink) {
sink.(AddExpr).getAnOperand() = src and sink.getType() instanceof TypeString
or
sink.(AssignAddExpr).getSource() = src and sink.getType() instanceof TypeString
or
sink.(ArrayCreationExpr).getInit() = src
or
sink.(ArrayInit).getAnInit() = src
or
sink.(ArrayAccess).getArray() = src
or
sink.(LogicExpr).getAnOperand() = src
or
exists(Assignment assign | assign.getSource() = src |
sink = assign.getDest().(ArrayAccess).getArray()
)
or
exists(EnhancedForStmt for, SsaExplicitUpdate v |
for.getExpr() = src and
v.getDefiningExpr() = for.getVariable() and
v.getAFirstUse() = sink
)
or
containerStep(src, sink)
or
constructorStep(src, sink)
or
qualifierToMethodStep(src, sink)
or
qualifierToArgumentStep(src, sink)
or
argToMethodStep(src, sink)
or
argToArgStep(src, sink)
or
argToQualifierStep(src, sink)
or
comparisonStep(src, sink)
or
stringBuilderStep(src, sink)
or
serializationStep(src, sink)
}
private class BulkData extends RefType {
BulkData() {
this.(Array).getElementType().(PrimitiveType).getName().regexpMatch("byte|char")
or
exists(RefType t | this.getASourceSupertype*() = t |
t.hasQualifiedName("java.io", "InputStream") or
t.hasQualifiedName("java.nio", "ByteBuffer") or
t.hasQualifiedName("java.lang", "Readable") or
t.hasQualifiedName("java.io", "DataInput") or
t.hasQualifiedName("java.nio.channels", "ReadableByteChannel")
)
}
}
/**
* Holds if `c` is a constructor for a subclass of `java.io.InputStream` that
* wraps an underlying data source. The underlying data source is given as a
* the `argi`'th parameter to the constructor.
*
* An object construction of such a wrapper is likely to preserve the data flow
* status of its argument.
*/
private predicate inputStreamWrapper(Constructor c, int argi) {
c.getParameterType(argi) instanceof BulkData and
c.getDeclaringType().getASourceSupertype().hasQualifiedName("java.io", "InputStream")
}
/** An object construction that preserves the data flow status of any of its arguments. */
private predicate constructorStep(Expr tracked, ConstructorCall sink) {
exists(int argi | sink.getArgument(argi) = tracked |
exists(string s | sink.getConstructedType().getQualifiedName() = s |
// String constructor does nothing to data
s = "java.lang.String" and argi = 0
or
// some readers preserve the content of streams
s = "java.io.InputStreamReader" and argi = 0
or
s = "java.io.BufferedReader" and argi = 0
or
s = "java.io.CharArrayReader" and argi = 0
or
s = "java.io.StringReader" and argi = 0
or
// data preserved through streams
s = "java.io.ObjectInputStream" and argi = 0
or
s = "java.io.ByteArrayInputStream" and argi = 0
or
s = "java.io.DataInputStream" and argi = 0
or
s = "java.io.BufferedInputStream" and argi = 0
or
s = "com.esotericsoftware.kryo.io.Input" and argi = 0
or
s = "java.beans.XMLDecoder" and argi = 0
or
// a tokenizer preserves the content of a string
s = "java.util.StringTokenizer" and argi = 0
or
// unzipping the stream preserves content
s = "java.util.zip.ZipInputStream" and argi = 0
or
s = "java.util.zip.GZIPInputStream" and argi = 0
or
// string builders and buffers
s = "java.lang.StringBuilder" and argi = 0
or
s = "java.lang.StringBuffer" and argi = 0
or
// a cookie with tainted ingredients is tainted
s = "javax.servlet.http.Cookie" and argi = 0
or
s = "javax.servlet.http.Cookie" and argi = 1
or
// various xml stream source constructors.
s = "org.xml.sax.InputSource" and argi = 0
or
s = "javax.xml.transform.sax.SAXSource" and argi = 0 and sink.getNumArgument() = 1
or
s = "javax.xml.transform.sax.SAXSource" and argi = 1 and sink.getNumArgument() = 2
or
s = "javax.xml.transform.stream.StreamSource" and argi = 0
or
//a URI constructed from a tainted string is tainted.
s = "java.net.URI" and argi = 0 and sink.getNumArgument() = 1
)
or
exists(RefType t | t.getQualifiedName() = "java.lang.Number" |
hasSubtype*(t, sink.getConstructedType())
) and
argi = 0
or
// wrappers constructed by extension
exists(Constructor c, Parameter p, SuperConstructorInvocationStmt sup |
c = sink.getConstructor() and
p = c.getParameter(argi) and
sup.getEnclosingCallable() = c and
constructorStep(p.getAnAccess(), sup)
)
or
// a custom InputStream that wraps a tainted data source is tainted
inputStreamWrapper(sink.getConstructor(), argi)
)
}
/** Access to a method that passes taint from qualifier to argument. */
private predicate qualifierToArgumentStep(Expr tracked, RValue sink) {
exists(MethodAccess ma, int arg |
taintPreservingQualifierToArgument(ma.getMethod(), arg) and
tracked = ma.getQualifier() and
sink = ma.getArgument(arg)
)
}
/** Methods that passes tainted data from qualifier to argument. */
private predicate taintPreservingQualifierToArgument(Method m, int arg) {
m.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and
m.hasName("writeTo") and
arg = 0
or
m.getDeclaringType().hasQualifiedName("java.io", "InputStream") and
m.hasName("read") and
arg = 0
or
m.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Reader") and
m.hasName("read") and
arg = 0
}
/** Access to a method that passes taint from the qualifier. */
private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
(taintPreservingQualifierToMethod(sink.getMethod()) or unsafeEscape(sink)) and
tracked = sink.getQualifier()
}
/**
* Methods that return tainted data when called on tainted data.
*/
private predicate taintPreservingQualifierToMethod(Method m) {
m.getDeclaringType() instanceof TypeString and
(
m.getName() = "concat" or
m.getName() = "endsWith" or
m.getName() = "getBytes" or
m.getName() = "split" or
m.getName() = "substring" or
m.getName() = "toCharArray" or
m.getName() = "toLowerCase" or
m.getName() = "toString" or
m.getName() = "toUpperCase" or
m.getName() = "trim"
)
or
exists(Class c | c.getQualifiedName() = "java.lang.Number" | hasSubtype*(c, m.getDeclaringType())) and
(
m.getName().matches("to%String") or
m.getName() = "toByteArray" or
m.getName().matches("%Value")
)
or
m.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Reader") and
(
m.getName() = "read" and m.getNumberOfParameters() = 0
or
m.getName() = "readLine"
)
or
m.getDeclaringType().getQualifiedName().matches("%StringWriter") and
m.getName() = "toString"
or
m.getDeclaringType().hasQualifiedName("java.util", "StringTokenizer") and
m.getName().matches("next%")
or
m.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and
(m.getName() = "toByteArray" or m.getName() = "toString")
or
m.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and
m.getName().matches("read%")
or
(
m.getDeclaringType().hasQualifiedName("java.lang", "StringBuilder") or
m.getDeclaringType().hasQualifiedName("java.lang", "StringBuffer")
) and
(m.getName() = "toString" or m.getName() = "append")
or
m.getDeclaringType().hasQualifiedName("javax.xml.transform.sax", "SAXSource") and
m.hasName("getInputSource")
or
m.getDeclaringType().hasQualifiedName("javax.xml.transform.stream", "StreamSource") and
m.hasName("getInputStream")
or
m instanceof IntentGetExtraMethod
or
m.getDeclaringType().hasQualifiedName("java.nio", "ByteBuffer") and
m.hasName("get")
or
m = any(GuiceProvider gp).getAnOverridingGetMethod()
or
m = any(ProtobufMessageLite p).getAGetterMethod()
}
private class StringReplaceMethod extends Method {
StringReplaceMethod() {
getDeclaringType() instanceof TypeString and
(
hasName("replace") or
hasName("replaceAll") or
hasName("replaceFirst")
)
}
}
private predicate unsafeEscape(MethodAccess ma) {
// Removing `<script>` tags using a string-replace method is
// unsafe if such a tag is embedded inside another one (e.g. `<scr<script>ipt>`).
exists(StringReplaceMethod m | ma.getMethod() = m |
ma.getArgument(0).(StringLiteral).getRepresentedString() = "(<script>)" and
ma.getArgument(1).(StringLiteral).getRepresentedString() = ""
)
}
/** Access to a method that passes taint from an argument. */
private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
exists(Method m, int i |
m = sink.(MethodAccess).getMethod() and
taintPreservingArgumentToMethod(m, i) and
tracked = sink.(MethodAccess).getArgument(i)
)
}
/**
* Holds if `method` is a library method that return tainted data if its
* `arg`th argument is tainted.
*/
private predicate taintPreservingArgumentToMethod(Method method, int arg) {
method instanceof StringReplaceMethod and arg = 1
or
exists(Class c | c.getQualifiedName() = "java.lang.Number" |
hasSubtype*(c, method.getDeclaringType())
) and
(
method.getName().matches("parse%") and arg = 0
or
method.getName().matches("valueOf%") and arg = 0
or
method.getName().matches("to%String") and arg = 0
)
or
method.getDeclaringType() instanceof TypeString and
method.getName() = "concat" and
arg = 0
or
(
method.getDeclaringType().hasQualifiedName("java.lang", "StringBuilder") or
method.getDeclaringType().hasQualifiedName("java.lang", "StringBuffer")
) and
(
method.getName() = "append" and arg = 0
or
method.getName() = "insert" and arg = 1
or
method.getName() = "replace" and arg = 2
)
or
(
method.getDeclaringType().hasQualifiedName("java.util", "Base64$Encoder") or
method.getDeclaringType().hasQualifiedName("java.util", "Base64$Decoder")
) and
(
method.getName() = "encode" and arg = 0 and method.getNumberOfParameters() = 1
or
method.getName() = "decode" and arg = 0 and method.getNumberOfParameters() = 1
or
method.getName() = "encodeToString" and arg = 0
or
method.getName() = "wrap" and arg = 0
)
or
method.getDeclaringType().hasQualifiedName("org.apache.commons.io", "IOUtils") and
(
method.getName() = "buffer" and arg = 0
or
method.getName() = "readLines" and arg = 0
or
method.getName() = "readFully" and arg = 0 and method.getParameterType(1).hasName("int")
or
method.getName() = "toBufferedInputStream" and arg = 0
or
method.getName() = "toBufferedReader" and arg = 0
or
method.getName() = "toByteArray" and arg = 0
or
method.getName() = "toCharArray" and arg = 0
or
method.getName() = "toInputStream" and arg = 0
or
method.getName() = "toString" and arg = 0
)
or
// A URI created from a tainted string is still tainted.
method.getDeclaringType().hasQualifiedName("java.net", "URI") and
method.hasName("create") and
arg = 0
or
method.getDeclaringType().hasQualifiedName("javax.xml.transform.sax", "SAXSource") and
method.hasName("sourceToInputSource") and
arg = 0
or
exists(ProtobufParser p | method = p.getAParseFromMethod()) and
arg = 0
or
exists(ProtobufMessageLite m | method = m.getAParseFromMethod()) and
arg = 0
}
/**
* Holds if `tracked` and `sink` are arguments to a method that transfers taint
* between arguments.
*/
private predicate argToArgStep(Expr tracked, RValue sink) {
exists(MethodAccess ma, Method method, int input, int output |
taintPreservingArgToArg(method, input, output) and
ma.getMethod() = method and
ma.getArgument(input) = tracked and
ma.getArgument(output) = sink
)
}
/**
* Holds if `method` is a library method that writes tainted data to the
* `output`th argument if the `input`th argument is tainted.
*/
private predicate taintPreservingArgToArg(Method method, int input, int output) {
method.getDeclaringType().hasQualifiedName("org.apache.commons.io", "IOUtils") and
(
method.hasName("copy") and input = 0 and output = 1
or
method.hasName("copyLarge") and input = 0 and output = 1
or
method.hasName("read") and input = 0 and output = 1
or
method.hasName("readFully") and
input = 0 and
output = 1 and
not method.getParameterType(1).hasName("int")
or
method.hasName("write") and input = 0 and output = 1
or
method.hasName("writeChunked") and input = 0 and output = 1
or
method.hasName("writeLines") and input = 0 and output = 2
or
method.hasName("writeLines") and input = 1 and output = 2
)
or
method.getDeclaringType().hasQualifiedName("java.lang", "System") and
method.hasName("arraycopy") and
input = 0 and
output = 2
}
/**
* Holds if `tracked` is the argument of a method that transfers taint
* from the argument to the qualifier and `sink` is the qualifier.
*/
private predicate argToQualifierStep(Expr tracked, Expr sink) {
exists(Method m, int i, MethodAccess ma |
taintPreservingArgumentToQualifier(m, i) and
ma.getMethod() = m and
tracked = ma.getArgument(i) and
sink = ma.getQualifier()
)
}
/**
* Holds if `method` is a method that transfers taint from argument to qualifier and
* `arg` is the index of the argument.
*/
private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
method.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and
method.hasName("write") and
arg = 0
}
/** A comparison or equality test with a constant. */
private predicate comparisonStep(Expr tracked, Expr sink) {
exists(Expr other |
exists(BinaryExpr e | e instanceof ComparisonExpr or e instanceof EqualityTest |
e = sink and
e.hasOperands(tracked, other)
)
or
exists(MethodAccess m | m.getMethod() instanceof EqualsMethod |
m = sink and
(
m.getQualifier() = tracked and m.getArgument(0) = other
or
m.getQualifier() = other and m.getArgument(0) = tracked
)
)
|
other.isCompileTimeConstant() or other instanceof NullLiteral
)
}
/** Flow through a `StringBuilder`. */
private predicate stringBuilderStep(Expr tracked, Expr sink) {
exists(StringBuilderVar sbvar, MethodAccess input, int arg |
input = sbvar.getAnInput(arg) and
tracked = input.getArgument(arg) and
sink = sbvar.getToStringCall()
)
}
/** Flow through data serialization. */
private predicate serializationStep(Expr tracked, Expr sink) {
exists(ObjectOutputStreamVar v, VariableAssign def |
def = v.getADef() and
exists(MethodAccess ma, RValue use |
ma.getArgument(0) = tracked and
ma = v.getAWriteObjectMethodAccess() and
use = ma.getQualifier() and
defUsePair(def, use)
) and
exists(RValue outputstream, ClassInstanceExpr cie |
cie = def.getSource() and
outputstream = cie.getArgument(0) and
adjacentUseUse(outputstream, sink)
)
)
}
/**
* A local variable that is assigned an `ObjectOutputStream`.
* Writing tainted data to such a stream causes the underlying
* `OutputStream` to be tainted.
*/
class ObjectOutputStreamVar extends LocalVariableDecl {
ObjectOutputStreamVar() {
exists(ClassInstanceExpr cie | cie = this.getAnAssignedValue() |
cie.getType() instanceof TypeObjectOutputStream
)
}
VariableAssign getADef() {
result.getSource().(ClassInstanceExpr).getType() instanceof TypeObjectOutputStream and
result.getDestVar() = this
}
MethodAccess getAWriteObjectMethodAccess() {
result.getQualifier() = getAnAccess() and
result.getMethod().hasName("writeObject")
}
}
private import StringBuilderVarModule
module StringBuilderVarModule {
/**
* A local variable that is initialized to a `StringBuilder`
* or `StringBuffer`. Such variables are often used to
* build up a query using string concatenation.
*/
class StringBuilderVar extends LocalVariableDecl {
StringBuilderVar() {
this.getType() instanceof TypeStringBuilder or
this.getType() instanceof TypeStringBuffer
}
/**
* Gets a call that adds something to this string builder, from the argument at the given index.
*/
MethodAccess getAnInput(int arg) {
result.getQualifier() = getAChainedReference() and
(
result.getMethod().getName() = "append" and arg = 0
or
result.getMethod().getName() = "insert" and arg = 1
or
result.getMethod().getName() = "replace" and arg = 2
)
}
/**
* Gets a call that appends something to this string builder.
*/
MethodAccess getAnAppend() {
result.getQualifier() = getAChainedReference() and
result.getMethod().getName() = "append"
}
MethodAccess getNextAppend(MethodAccess append) {
result = getAnAppend() and
append = getAnAppend() and
(
result.getQualifier() = append
or
not exists(MethodAccess chainAccess | chainAccess.getQualifier() = append) and
exists(RValue sbva1, RValue sbva2 |
adjacentUseUse(sbva1, sbva2) and
append.getQualifier() = getAChainedReference(sbva1) and
result.getQualifier() = sbva2
)
)
}
/**
* Gets a call that converts this string builder to a string.
*/
MethodAccess getToStringCall() {
result.getQualifier() = getAChainedReference() and
result.getMethod().getName() = "toString"
}
private Expr getAChainedReference(VarAccess sbva) {
// All the methods on `StringBuilder` that return the same type return
// the same object.
result = callReturningSameType+(sbva) and sbva = this.getAnAccess()
or
result = sbva and sbva = this.getAnAccess()
}
/**
* Gets an expression that refers to this `StringBuilder`, possibly after some chained calls.
*/
Expr getAChainedReference() { result = getAChainedReference(_) }
}
}
private MethodAccess callReturningSameType(Expr ref) {
ref = result.getQualifier() and
result.getMethod().getReturnType() = ref.getType()
}

View File

@@ -0,0 +1,112 @@
import TaintTrackingParameter::Public
private import TaintTrackingParameter::Private
/**
* A configuration of interprocedural taint tracking analysis. This defines
* sources, sinks, and any other configurable aspect of the analysis. Each
* use of the taint tracking library must define its own unique extension of
* this abstract class.
*
* A taint-tracking configuration is a special data flow configuration
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
* necessarily preserve values but are still relevant from a taint tracking
* perspective. (For example, string concatenation, where one of the operands
* is tainted.)
*
* To create a configuration, extend this class with a subclass whose
* characteristic predicate is a unique singleton string. For example, write
*
* ```
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
* // Override `isSource` and `isSink`.
* // Optionally override `isSanitizer`.
* // Optionally override `isSanitizerIn`.
* // Optionally override `isSanitizerOut`.
* // Optionally override `isSanitizerGuard`.
* // Optionally override `isAdditionalTaintStep`.
* }
* ```
*
* Then, to query whether there is flow between some `source` and `sink`,
* write
*
* ```
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
* ```
*
* Multiple configurations can coexist, but it is unsupported to depend on
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
* overridden predicates that define sources, sinks, or additional steps.
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
*/
abstract class Configuration extends DataFlow::Configuration {
bindingset[this]
Configuration() { any() }
/**
* Holds if `source` is a relevant taint source.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
isSanitizer(node) or
defaultTaintBarrier(node)
}
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
isSanitizerEdge(node1, node2)
}
/** Holds if data flow into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
/** Holds if data flow out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
* must be taken into account in the analysis.
*/
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
/**
* Holds if taint may flow from `source` to `sink` for this configuration.
*/
// overridden to provide taint-tracking specific qldoc
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
super.hasFlow(source, sink)
}
}

View File

@@ -0,0 +1,5 @@
import semmle.code.java.dataflow.internal.TaintTrackingUtil as Public
module Private {
import semmle.code.java.dataflow.DataFlow::DataFlow as DataFlow
}

View File

@@ -0,0 +1,112 @@
import TaintTrackingParameter::Public
private import TaintTrackingParameter::Private
/**
* A configuration of interprocedural taint tracking analysis. This defines
* sources, sinks, and any other configurable aspect of the analysis. Each
* use of the taint tracking library must define its own unique extension of
* this abstract class.
*
* A taint-tracking configuration is a special data flow configuration
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
* necessarily preserve values but are still relevant from a taint tracking
* perspective. (For example, string concatenation, where one of the operands
* is tainted.)
*
* To create a configuration, extend this class with a subclass whose
* characteristic predicate is a unique singleton string. For example, write
*
* ```
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
* // Override `isSource` and `isSink`.
* // Optionally override `isSanitizer`.
* // Optionally override `isSanitizerIn`.
* // Optionally override `isSanitizerOut`.
* // Optionally override `isSanitizerGuard`.
* // Optionally override `isAdditionalTaintStep`.
* }
* ```
*
* Then, to query whether there is flow between some `source` and `sink`,
* write
*
* ```
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
* ```
*
* Multiple configurations can coexist, but it is unsupported to depend on
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
* overridden predicates that define sources, sinks, or additional steps.
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
*/
abstract class Configuration extends DataFlow::Configuration {
bindingset[this]
Configuration() { any() }
/**
* Holds if `source` is a relevant taint source.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
isSanitizer(node) or
defaultTaintBarrier(node)
}
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
isSanitizerEdge(node1, node2)
}
/** Holds if data flow into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
/** Holds if data flow out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
* must be taken into account in the analysis.
*/
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
/**
* Holds if taint may flow from `source` to `sink` for this configuration.
*/
// overridden to provide taint-tracking specific qldoc
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
super.hasFlow(source, sink)
}
}

View File

@@ -0,0 +1,5 @@
import semmle.code.java.dataflow.internal.TaintTrackingUtil as Public
module Private {
import semmle.code.java.dataflow.DataFlow2::DataFlow2 as DataFlow
}

View File

@@ -6,6 +6,7 @@
* The set of dispatch targets for `Object.toString()` calls are reduced based
* on possible data flow from objects of more specific types to the qualifier.
*/
import java
private import VirtualDispatch
private import semmle.code.java.controlflow.Guards

View File

@@ -2,4 +2,5 @@ import java
import semmle.code.xml.AndroidManifest
from AndroidActivityXmlElement e
select e.getResolvedComponentName(), e.getAnIntentFilterElement().getAnActionElement().getActionName()
select e.getResolvedComponentName(),
e.getAnIntentFilterElement().getAnActionElement().getActionName()

View File

@@ -5,9 +5,7 @@ import semmle.code.java.dataflow.TaintTracking
class Conf extends TaintTracking::Configuration {
Conf() { this = "conf" }
override predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |