Ratpack conversion to new lambda model

Signed-off-by: Jonathan Leitschuh <Jonathan.Leitschuh@gmail.com>
This commit is contained in:
Jonathan Leitschuh
2021-10-06 17:57:14 +02:00
parent 4f90f0a748
commit 6562ac3680
4 changed files with 129 additions and 167 deletions

View File

@@ -75,8 +75,9 @@ private class RatpackModel extends SummaryModelCsv {
row =
["ratpack.util;", "ratpack.func;"] +
[
"MultiValueMap;true;getAll;;;Element of Argument[-1];ReturnValue;value",
"MultiValueMap;true;asMultimap;;;Element of Argument[-1];ReturnValue;value"
"MultiValueMap;true;getAll;;;MapKey of Argument[-1];MapKey of ReturnValue;value",
"MultiValueMap;true;getAll;;;MapValue of Argument[-1];Element of MapValue of ReturnValue;value",
"MultiValueMap;true;asMultimap;;;Element of Argument[-1];Element of ReturnValue;value"
]
}
}

View File

@@ -3,6 +3,52 @@ private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
/**
* Ratpack methods that propagate user-supplied data as tainted.
*/
private class RatpackExecModel extends SummaryModelCsv {
override predicate row(string row) {
//"namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind",
row =
["ratpack.exec;Promise;true;"] +
[
// `Promise` creation methods
"value;;;Argument[0];Element of ReturnValue;value",
"flatten;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value",
"sync;;;ReturnValue of Argument[0];Element of ReturnValue;value",
// `Promise` value transformation methods
"map;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
"map;;;ReturnValue of Argument[0];Element of ReturnValue;value",
"blockingMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
"blockingMap;;;ReturnValue of Argument[0];Element of ReturnValue;value",
"mapError;;;ReturnValue of Argument[0];Element of ReturnValue;value",
// `Promise` termination method
"then;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
// 'next' accesses qualfier the 'Promise' value and also returns the qualifier
"next;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
"next;;;Argument[-1];ReturnValue;value",
// 'cacheIf' accesses qualfier the 'Promise' value and also returns the qualifier
"cacheIf;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
"cacheIf;;;Argument[-1];ReturnValue;value",
// 'route' accesses qualfier the 'Promise' value, and conditionally returns the qualifier or
// the result of the second argument
"route;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
"route;;;Element of Argument[-1];Parameter[0] of Argument[1];value",
"route;;;Argument[-1];ReturnValue;value",
// `flatMap` type methods return their returned `Promise`
"flatMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
"flatMap;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value",
"flatMapError;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value",
// `mapIf` methods conditionally map their values, or return themselves
"mapIf;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
"mapIf;;;Element of Argument[-1];Parameter[0] of Argument[1];value",
"mapIf;;;Element of Argument[-1];Parameter[0] of Argument[2];value",
"mapIf;;;ReturnValue of Argument[1];Element of ReturnValue;value",
"mapIf;;;ReturnValue of Argument[2];Element of ReturnValue;value"
]
}
}
/** A reference type that extends a parameterization the Promise type. */
private class RatpackPromise extends RefType {
RatpackPromise() {
@@ -10,167 +56,18 @@ private class RatpackPromise extends RefType {
}
}
/**
* Ratpack methods that propagate user-supplied data as tainted.
*/
private class RatpackExecModel extends SummaryModelCsv {
override predicate row(string row) {
//"namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind",
row = [
"ratpack.exec;Promise;true;map;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
"ratpack.exec;Promise;true;map;;;ReturnValue of Argument[0];Element of ReturnValue;value",
"ratpack.exec;Promise;true;then;;;Element of Argument[-1];Parameter[0] of Argument[0];value"
]
}
}
private class RatpackPromiseValueMethod extends Method, TaintPreservingCallable {
RatpackPromiseValueMethod() { isStatic() and hasName("value") }
override predicate returnsTaintFrom(int arg) { arg = 0 }
}
abstract private class FluentLambdaMethod extends Method {
/**
* Holds if this lambda consumes taint from the quaifier when `lambdaArg`
* for `methodArg` is tainted.
* Eg. `tainted.map(stillTainted -> ..)`
*/
abstract predicate consumesTaint(int methodArg, int lambdaArg);
/**
* Holds if the lambda passed at the given `arg` position produces taint
* that taints the result of this method.
* Eg. `var tainted = CompletableFuture.supplyAsync(() -> taint());`
*/
predicate doesReturnTaint(int arg) { none() }
}
private class RatpackPromiseProviderMethod extends Method, FluentLambdaMethod {
RatpackPromiseProviderMethod() { isStatic() and hasName(["flatten", "sync"]) }
override predicate consumesTaint(int methodArg, int lambdaArg) { none() }
override predicate doesReturnTaint(int arg) { arg = 0 }
}
abstract private class SimpleFluentLambdaMethod extends FluentLambdaMethod {
override predicate consumesTaint(int methodArg, int lambdaArg) {
methodArg = 0 and consumesTaint(lambdaArg)
}
/**
* Holds if this lambda consumes taint from the quaifier when `arg` is tainted.
* Eg. `tainted.map(stillTainted -> ..)`
*/
abstract predicate consumesTaint(int lambdaArg);
}
private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod {
RatpackPromiseMapMethod() {
getDeclaringType() instanceof RatpackPromise and
hasName(["blockingMap"]) // "flatMap" & "apply" cause false positives. Wait for fluent lambda support.
}
override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 }
override predicate doesReturnTaint(int arg) { arg = 0 }
}
/**
* Represents the `mapIf` method.
*
* `<O> Promise<O> mapIf(Predicate<T> predicate, Function<T, O> onTrue, Function<T, O> onFalse)`
* Ratpack `Promise` method that will return `this`.
*/
private class RatpackPromiseMapIfMethod extends FluentLambdaMethod {
RatpackPromiseMapIfMethod() {
getDeclaringType() instanceof RatpackPromise and
hasName(["mapIf"]) and // "flatMapIf" causes false positives. Wait for fluent lambda support.
getNumberOfParameters() = 3
}
override predicate consumesTaint(int methodArg, int lambdaArg) {
methodArg = [1, 2, 3] and lambdaArg = 0
}
override predicate doesReturnTaint(int arg) { arg = [1, 2] }
}
private class RatpackPromiseMapErrorMethod extends FluentLambdaMethod {
RatpackPromiseMapErrorMethod() {
getDeclaringType() instanceof RatpackPromise and
hasName(["mapError"]) // "flatMapError" causes false positives. Wait for fluent lambda support.
}
override predicate consumesTaint(int methodArg, int lambdaArg) { none() }
override predicate doesReturnTaint(int arg) { arg = getNumberOfParameters() - 1 }
}
// private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod {
// RatpackPromiseThenMethod() {
// getDeclaringType() instanceof RatpackPromise and
// hasName("then")
// }
// override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 }
// }
private class RatpackPromiseFluentMethod extends FluentMethod, FluentLambdaMethod {
private class RatpackPromiseFluentMethod extends FluentMethod {
RatpackPromiseFluentMethod() {
getDeclaringType() instanceof RatpackPromise and
not isStatic() and
// It's generally safe to assume that if the return type exactly matches the declaring type, `this` will be returned.
exists(ParameterizedType t |
t instanceof RatpackPromise and
t = getDeclaringType() and
t = getReturnType()
)
}
override predicate consumesTaint(int methodArg, int lambdaArg) {
hasName(["next"]) and methodArg = 0 and lambdaArg = 0
or
hasName(["cacheIf"]) and methodArg = 0 and lambdaArg = 0
or
hasName(["route"]) and methodArg = [0, 1] and lambdaArg = 0
}
override predicate doesReturnTaint(int arg) { none() } // "flatMapIf" causes false positives. Wait for fluent lambda support.
}
/**
* Holds if the method access qualifier `node1` has dataflow to the functional expression parameter `node2`.
*/
private predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma, FluentLambdaMethod flm, int methodArg, int lambdaArg |
flm.consumesTaint(methodArg, lambdaArg)
|
ma.getMethod() = flm and
node1.asExpr() = ma.getQualifier() and
ma.getArgument(methodArg).(FunctionalExpr).asMethod().getParameter(lambdaArg) =
node2.asParameter()
)
}
/**
* Holds if the return statement result of the functional expression `node1` has dataflow to the
* method access result `node2`.
*/
private predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) {
exists(FluentLambdaMethod flm, MethodAccess ma, FunctionalExpr fe, int arg |
flm.doesReturnTaint(arg)
|
fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and
ma.getMethod() = flm and
node2.asExpr() = ma and
ma.getArgument(arg) = fe
)
}
private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep {
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
stepIntoLambda(node1, node2) or
stepOutOfLambda(node1, node2)
}
}

View File

@@ -53,13 +53,16 @@ class Resource {
}
void test3(Context ctx) {
sink(ctx.getRequest().getBody().map(TypedData::getText)); //$hasTaintFlow
Promise<String> mapResult = ctx.getRequest().getBody().map(b -> {
ctx.getRequest().getBody().map(TypedData::getText).then(s -> {
sink(s); //$hasTaintFlow
});
ctx.getRequest().getBody().map(b -> {
sink(b); //$hasTaintFlow
sink(b.getText()); //$hasTaintFlow
return b.getText();
}).then(t -> {
sink(t); //$hasTaintFlow
});
sink(mapResult); //$hasTaintFlow
ctx.getRequest().getBody().map(TypedData::getText).then(this::sink); //$hasTaintFlow
ctx
.getRequest()
@@ -72,7 +75,9 @@ class Resource {
void test4() {
String tainted = taint();
Promise.value(tainted);
sink(Promise.value(tainted)); //$hasTaintFlow
Promise
.value(tainted)
.then(this::sink); //$hasTaintFlow
Promise
.value(tainted)
.map(a -> a)
@@ -188,15 +193,14 @@ class Resource {
.then(value -> {
sink(value); //$hasTaintFlow
});
// Waiting for support for lambda data flow
// Promise
// .value("potato")
// .flatMapError(RuntimeException.class, exception -> {
// return Promise.value(taint());
// })
// .then(value -> {
// sink(value); //$hasTaintFlow
// });
Promise
.value("potato")
.flatMapError(RuntimeException.class, exception -> {
return Promise.value(taint());
})
.then(value -> {
sink(value); //$hasTaintFlow
});
}
void test9() {
@@ -213,6 +217,12 @@ class Resource {
.then(value -> {
sink(value); // no taints flow
});
Promise
.value(tainted)
.flatMap(v -> Promise.value(v))
.then(value -> {
sink(value); //$hasTaintFlow
});
}
public static String identity(String input) {
@@ -234,5 +244,51 @@ class Resource {
sink(value); // no taints flow
});
}
void test11() {
String tainted = taint();
Promise
.sync(() -> tainted)
.mapIf(v -> {
sink(v); //$hasTaintFlow
return true;
}, v -> {
sink(v); //$hasTaintFlow
return v;
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.sync(() -> tainted)
.mapIf(v -> {
sink(v); //$hasTaintFlow
return true;
}, vTrue -> {
sink(vTrue); //$hasTaintFlow
return vTrue;
}, vFalse -> {
sink(vFalse); //$hasTaintFlow
return vFalse;
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.sync(() -> tainted)
.mapIf(v -> {
sink(v); //$hasTaintFlow
return true;
}, vTrue -> {
sink(vTrue); //$hasTaintFlow
return "potato";
}, vFalse -> {
sink(vFalse); //$hasTaintFlow
return "potato";
})
.then(value -> {
sink(value); // no tainted flow
});
}
}

View File

@@ -74,6 +74,14 @@ public interface Promise<T> {
return null;
}
default Promise<T> mapIf(Predicate<? super T> predicate, Function<? super T, ? extends T> transformer) {
return null;
}
default <O> Promise<O> mapIf(Predicate<? super T> predicate, Function<? super T, ? extends O> onTrue, Function<? super T, ? extends O> onFalse) {
return null;
}
default Promise<T> mapError(Function<? super Throwable, ? extends T> transformer) {
return null;
}