mirror of
https://github.com/github/codeql.git
synced 2026-04-29 02:35:15 +02:00
Merge pull request #2498 from aschackmull/java/taint-getter
Java/C++/C#: Add support for taint-getter/setter summaries in data flow.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -112,9 +112,6 @@ private module ImplCommon {
|
||||
enclosing = arg.getEnclosingCallable()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ParameterNode getAParameter(DataFlowCallable c) { result.getEnclosingCallable() = c }
|
||||
|
||||
pragma[noinline]
|
||||
private predicate viableParamArg0(
|
||||
int i, ArgumentNode arg, CallContext outercc, DataFlowCall call
|
||||
@@ -123,9 +120,9 @@ private module ImplCommon {
|
||||
(
|
||||
outercc = TAnyCallContext()
|
||||
or
|
||||
outercc = TSomeCall(getAParameter(c), _)
|
||||
outercc = TSomeCall()
|
||||
or
|
||||
exists(DataFlowCall other | outercc = TSpecificCall(other, _, _) |
|
||||
exists(DataFlowCall other | outercc = TSpecificCall(other) |
|
||||
recordDataFlowCallSite(other, c)
|
||||
)
|
||||
) and
|
||||
@@ -156,17 +153,17 @@ private module ImplCommon {
|
||||
viableParamArg1(p, callable, i, arg, outercc, call)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, true)
|
||||
else innercc = TSomeCall(p, true)
|
||||
then innercc = TSpecificCall(call)
|
||||
else innercc = TSomeCall()
|
||||
)
|
||||
}
|
||||
|
||||
private CallContextCall getAValidCallContextForParameter(ParameterNode p) {
|
||||
result = TSomeCall(p, _)
|
||||
result = TSomeCall()
|
||||
or
|
||||
exists(DataFlowCall call, int i, DataFlowCallable callable |
|
||||
result = TSpecificCall(call, i, _) and
|
||||
p.isParameterOf(callable, i) and
|
||||
exists(DataFlowCall call, DataFlowCallable callable |
|
||||
result = TSpecificCall(call) and
|
||||
p.isParameterOf(callable, _) and
|
||||
recordDataFlowCallSite(call, callable)
|
||||
)
|
||||
}
|
||||
@@ -460,9 +457,6 @@ private module ImplCommon {
|
||||
enclosing = arg.getEnclosingCallable()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ParameterNode getAParameter(DataFlowCallable c) { result.getEnclosingCallable() = c }
|
||||
|
||||
pragma[noinline]
|
||||
private predicate viableParamArg0(
|
||||
int i, ArgumentNode arg, CallContext outercc, DataFlowCall call
|
||||
@@ -471,9 +465,9 @@ private module ImplCommon {
|
||||
(
|
||||
outercc = TAnyCallContext()
|
||||
or
|
||||
outercc = TSomeCall(getAParameter(c), _)
|
||||
outercc = TSomeCall()
|
||||
or
|
||||
exists(DataFlowCall other | outercc = TSpecificCall(other, _, _) |
|
||||
exists(DataFlowCall other | outercc = TSpecificCall(other) |
|
||||
recordDataFlowCallSite(other, c)
|
||||
)
|
||||
) and
|
||||
@@ -504,17 +498,17 @@ private module ImplCommon {
|
||||
viableParamArg1(p, callable, i, arg, outercc, call)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, true)
|
||||
else innercc = TSomeCall(p, true)
|
||||
then innercc = TSpecificCall(call)
|
||||
else innercc = TSomeCall()
|
||||
)
|
||||
}
|
||||
|
||||
private CallContextCall getAValidCallContextForParameter(ParameterNode p) {
|
||||
result = TSomeCall(p, _)
|
||||
result = TSomeCall()
|
||||
or
|
||||
exists(DataFlowCall call, int i, DataFlowCallable callable |
|
||||
result = TSpecificCall(call, i, _) and
|
||||
p.isParameterOf(callable, i) and
|
||||
exists(DataFlowCall call, DataFlowCallable callable |
|
||||
result = TSpecificCall(call) and
|
||||
p.isParameterOf(callable, _) and
|
||||
recordDataFlowCallSite(call, callable)
|
||||
)
|
||||
}
|
||||
@@ -579,14 +573,6 @@ private module ImplCommon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` passes an implicit or explicit instance argument, i.e., an
|
||||
* expression that reaches a `this` parameter.
|
||||
*/
|
||||
private predicate callHasInstanceArgument(DataFlowCall call) {
|
||||
exists(ArgumentNode arg | arg.argumentOf(call, -1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the call context `call` either improves virtual dispatch in
|
||||
* `callable` or if it allows us to prune unreachable nodes in `callable`.
|
||||
@@ -601,16 +587,8 @@ private module ImplCommon {
|
||||
cached
|
||||
newtype TCallContext =
|
||||
TAnyCallContext() or
|
||||
TSpecificCall(DataFlowCall call, int i, boolean emptyAp) {
|
||||
recordDataFlowCallSite(call, _) and
|
||||
(emptyAp = true or emptyAp = false) and
|
||||
(
|
||||
exists(call.getArgument(i))
|
||||
or
|
||||
i = -1 and callHasInstanceArgument(call)
|
||||
)
|
||||
} or
|
||||
TSomeCall(ParameterNode p, boolean emptyAp) { emptyAp = true or emptyAp = false } or
|
||||
TSpecificCall(DataFlowCall call) { recordDataFlowCallSite(call, _) } or
|
||||
TSomeCall() or
|
||||
TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) }
|
||||
|
||||
cached
|
||||
@@ -635,11 +613,11 @@ private module ImplCommon {
|
||||
*
|
||||
* There are four cases:
|
||||
* - `TAnyCallContext()` : No restrictions on method flow.
|
||||
* - `TSpecificCall(DataFlowCall call, int i)` : Flow entered through the `i`th
|
||||
* parameter at the given `call`. This call improves the set of viable
|
||||
* - `TSpecificCall(DataFlowCall call)` : Flow entered through the
|
||||
* given `call`. This call improves the set of viable
|
||||
* dispatch targets for at least one method call in the current callable
|
||||
* or helps prune unreachable nodes in the current callable.
|
||||
* - `TSomeCall(ParameterNode p)` : Flow entered through parameter `p`. The
|
||||
* - `TSomeCall()` : Flow entered through a parameter. The
|
||||
* originating call does not improve the set of dispatch targets for any
|
||||
* method call in the current callable and was therefore not recorded.
|
||||
* - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and
|
||||
@@ -663,23 +641,21 @@ private module ImplCommon {
|
||||
|
||||
class CallContextSpecificCall extends CallContextCall, TSpecificCall {
|
||||
override string toString() {
|
||||
exists(DataFlowCall call, int i | this = TSpecificCall(call, i, _) |
|
||||
result = "CcCall(" + call + ", " + i + ")"
|
||||
)
|
||||
exists(DataFlowCall call | this = TSpecificCall(call) | result = "CcCall(" + call + ")")
|
||||
}
|
||||
|
||||
override predicate relevantFor(DataFlowCallable callable) {
|
||||
recordDataFlowCallSite(getCall(), callable)
|
||||
}
|
||||
|
||||
DataFlowCall getCall() { this = TSpecificCall(result, _, _) }
|
||||
DataFlowCall getCall() { this = TSpecificCall(result) }
|
||||
}
|
||||
|
||||
class CallContextSomeCall extends CallContextCall, TSomeCall {
|
||||
override string toString() { result = "CcSomeCall" }
|
||||
|
||||
override predicate relevantFor(DataFlowCallable callable) {
|
||||
exists(ParameterNode p | this = TSomeCall(p, _) and p.getEnclosingCallable() = callable)
|
||||
exists(ParameterNode p | p.getEnclosingCallable() = callable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,7 +824,7 @@ private module ImplCommon {
|
||||
|
||||
bindingset[call, cc]
|
||||
DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
|
||||
exists(DataFlowCall ctx | cc = TSpecificCall(ctx, _, _) |
|
||||
exists(DataFlowCall ctx | cc = TSpecificCall(ctx) |
|
||||
if reducedViableImplInCallContext(call, _, ctx)
|
||||
then result = prunedViableImplInCallContext(call, ctx)
|
||||
else result = viableCallable(call)
|
||||
@@ -861,6 +837,76 @@ private module ImplCommon {
|
||||
result = viableCallable(call) and cc instanceof CallContextReturn
|
||||
}
|
||||
|
||||
newtype TSummary =
|
||||
TSummaryVal() or
|
||||
TSummaryTaint() or
|
||||
TSummaryReadVal(Content f) or
|
||||
TSummaryReadTaint(Content f) or
|
||||
TSummaryTaintStore(Content f)
|
||||
|
||||
/**
|
||||
* A summary of flow through a callable. This can either be value-preserving
|
||||
* if no additional steps are used, taint-flow if at least one additional step
|
||||
* is used, or any one of those combined with a store or a read. Summaries
|
||||
* recorded at a return node are restricted to include at least one additional
|
||||
* step, as the value-based summaries are calculated independent of the
|
||||
* configuration.
|
||||
*/
|
||||
class Summary extends TSummary {
|
||||
string toString() {
|
||||
result = "Val" and this = TSummaryVal()
|
||||
or
|
||||
result = "Taint" and this = TSummaryTaint()
|
||||
or
|
||||
exists(Content f |
|
||||
result = "ReadVal " + f.toString() and this = TSummaryReadVal(f)
|
||||
or
|
||||
result = "ReadTaint " + f.toString() and this = TSummaryReadTaint(f)
|
||||
or
|
||||
result = "TaintStore " + f.toString() and this = TSummaryTaintStore(f)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the summary that results from extending this with an additional step. */
|
||||
Summary additionalStep() {
|
||||
this = TSummaryVal() and result = TSummaryTaint()
|
||||
or
|
||||
this = TSummaryTaint() and result = TSummaryTaint()
|
||||
or
|
||||
exists(Content f | this = TSummaryReadVal(f) and result = TSummaryReadTaint(f))
|
||||
or
|
||||
exists(Content f | this = TSummaryReadTaint(f) and result = TSummaryReadTaint(f))
|
||||
}
|
||||
|
||||
/** Gets the summary that results from extending this with a read. */
|
||||
Summary readStep(Content f) { this = TSummaryVal() and result = TSummaryReadVal(f) }
|
||||
|
||||
/** Gets the summary that results from extending this with a store. */
|
||||
Summary storeStep(Content f) { this = TSummaryTaint() and result = TSummaryTaintStore(f) }
|
||||
|
||||
/** Gets the summary that results from extending this with `step`. */
|
||||
bindingset[this, step]
|
||||
Summary compose(Summary step) {
|
||||
this = TSummaryVal() and result = step
|
||||
or
|
||||
this = TSummaryTaint() and
|
||||
(step = TSummaryTaint() or step = TSummaryTaintStore(_)) and
|
||||
result = step
|
||||
or
|
||||
exists(Content f |
|
||||
this = TSummaryReadVal(f) and step = TSummaryTaint() and result = TSummaryReadTaint(f)
|
||||
)
|
||||
or
|
||||
this = TSummaryReadTaint(_) and step = TSummaryTaint() and result = this
|
||||
}
|
||||
|
||||
/** Holds if this summary does not include any taint steps. */
|
||||
predicate isPartial() {
|
||||
this = TSummaryVal() or
|
||||
this = TSummaryReadVal(_)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
DataFlowType getErasedNodeType(Node n) { result = getErasedRepr(n.getType()) }
|
||||
|
||||
|
||||
@@ -154,11 +154,11 @@ class Content extends TContent {
|
||||
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
|
||||
}
|
||||
|
||||
/** Gets the type of the object containing this content. */
|
||||
abstract RefType getContainerType();
|
||||
/** Gets the erased type of the object containing this content. */
|
||||
abstract DataFlowType getContainerType();
|
||||
|
||||
/** Gets the type of this content. */
|
||||
abstract Type getType();
|
||||
/** Gets the erased type of this content. */
|
||||
abstract DataFlowType getType();
|
||||
}
|
||||
|
||||
private class FieldContent extends Content, TFieldContent {
|
||||
@@ -174,25 +174,25 @@ private class FieldContent extends Content, TFieldContent {
|
||||
f.getLocation().hasLocationInfo(path, sl, sc, el, ec)
|
||||
}
|
||||
|
||||
override RefType getContainerType() { result = f.getDeclaringType() }
|
||||
override DataFlowType getContainerType() { result = getErasedRepr(f.getDeclaringType()) }
|
||||
|
||||
override Type getType() { result = getFieldTypeBound(f) }
|
||||
override DataFlowType getType() { result = getErasedRepr(getFieldTypeBound(f)) }
|
||||
}
|
||||
|
||||
private class CollectionContent extends Content, TCollectionContent {
|
||||
override string toString() { result = "collection" }
|
||||
|
||||
override RefType getContainerType() { none() }
|
||||
override DataFlowType getContainerType() { none() }
|
||||
|
||||
override Type getType() { none() }
|
||||
override DataFlowType getType() { none() }
|
||||
}
|
||||
|
||||
private class ArrayContent extends Content, TArrayContent {
|
||||
override string toString() { result = "array" }
|
||||
|
||||
override RefType getContainerType() { none() }
|
||||
override DataFlowType getContainerType() { none() }
|
||||
|
||||
override Type getType() { none() }
|
||||
override DataFlowType getType() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,7 +226,7 @@ predicate readStep(Node node1, Content f, Node node2) {
|
||||
* possible flow. A single type is used for all numeric types to account for
|
||||
* numeric conversions, and otherwise the erasure is used.
|
||||
*/
|
||||
RefType getErasedRepr(Type t) {
|
||||
DataFlowType getErasedRepr(Type t) {
|
||||
exists(Type e | e = t.getErasure() |
|
||||
if e instanceof NumericOrCharType
|
||||
then result.(BoxedType).getPrimitiveType().getName() = "double"
|
||||
|
||||
37
java/ql/test/library-tests/dataflow/taintgettersetter/A.java
Normal file
37
java/ql/test/library-tests/dataflow/taintgettersetter/A.java
Normal file
@@ -0,0 +1,37 @@
|
||||
public class A {
|
||||
String taint() { return "tainted"; }
|
||||
void sink(Object o) { }
|
||||
|
||||
static String step(String s) { return s + "0"; }
|
||||
|
||||
static class Box {
|
||||
String s;
|
||||
Box(String s) {
|
||||
this.s = s + "1";
|
||||
}
|
||||
String getS1() { return s + "2"; }
|
||||
String getS2() { return step(s + "_") + "2"; }
|
||||
void setS1(String s) { this.s = "3" + s; }
|
||||
void setS2(String s) { this.s = "3" + step("_" + s); }
|
||||
static Box mk(String s) {
|
||||
Box b = new Box("");
|
||||
b.s = step(s);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
void foo(Box b1, Box b2) {
|
||||
b1.setS1(taint());
|
||||
sink(b1.getS1());
|
||||
|
||||
b2.setS2(taint());
|
||||
sink(b2.getS2());
|
||||
|
||||
String t3 = taint();
|
||||
Box b3 = new Box(step(t3));
|
||||
sink(b3.s);
|
||||
|
||||
Box b4 = Box.mk(taint());
|
||||
sink(b4.getS1());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
| A.java:24:14:24:20 | taint(...) | A.java:25:10:25:19 | getS1(...) |
|
||||
| A.java:27:14:27:20 | taint(...) | A.java:28:10:28:19 | getS2(...) |
|
||||
| A.java:30:17:30:23 | taint(...) | A.java:32:10:32:13 | b3.s |
|
||||
| A.java:34:21:34:27 | taint(...) | A.java:35:10:35:19 | getS1(...) |
|
||||
@@ -0,0 +1,25 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import DataFlow
|
||||
|
||||
class Conf extends Configuration {
|
||||
Conf() { this = "taintgettersetter" }
|
||||
|
||||
override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("taint") }
|
||||
|
||||
override predicate isSink(Node n) {
|
||||
exists(MethodAccess sink |
|
||||
sink.getAnArgument() = n.asExpr() and sink.getMethod().hasName("sink")
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(Node n1, Node n2) {
|
||||
exists(AddExpr add |
|
||||
add.getType() instanceof TypeString and add.getAnOperand() = n1.asExpr() and n2.asExpr() = add
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from Node src, Node sink, Conf conf
|
||||
where conf.hasFlow(src, sink)
|
||||
select src, sink
|
||||
Reference in New Issue
Block a user