Skip to content

Commit b9dc3d0

Browse files
committed
Ratpack: Better support for Promise API
1 parent cdfdcc6 commit b9dc3d0

File tree

5 files changed

+288
-24
lines changed

5 files changed

+288
-24
lines changed

java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ private class RatpackHttpSource extends SourceModelCsv {
2929
row =
3030
["ratpack.handling;", "ratpack.core.handling;"] + "Context;true;parse;" +
3131
[
32-
"(java.lang.Class);", "(com.google.common.reflect.TypeToken);", "(java.lang.Class,O);",
33-
"(com.google.common.reflect.TypeToken,O);", "(ratpack.core.parse.Parse);",
32+
"(java.lang.Class);", "(com.google.common.reflect.TypeToken);", "(java.lang.Class,java.lang.Object);",
33+
"(com.google.common.reflect.TypeToken,java.lang.Object);", "(ratpack.core.parse.Parse);",
3434
"(ratpack.parse.Parse);"
3535
] + ";ReturnValue;remote"
3636
}

java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,82 @@ private class RatpackPromiseValueMethod extends Method, TaintPreservingCallable
1515
override predicate returnsTaintFrom(int arg) { arg = 0 }
1616
}
1717

18-
abstract private class SimpleFluentLambdaMethod extends Method {
19-
SimpleFluentLambdaMethod() { getNumberOfParameters() = 1 }
20-
18+
abstract private class FluentLambdaMethod extends Method {
2119
/**
22-
* Holds if this lambda consumes taint from the quaifier when `arg` is tainted.
20+
* Holds if this lambda consumes taint from the quaifier when `lambdaArg`
21+
* for `methodArg` is tainted.
2322
* Eg. `tainted.map(stillTainted -> ..)`
2423
*/
25-
abstract predicate consumesTaint(int arg);
24+
abstract predicate consumesTaint(int methodArg, int lambdaArg);
2625

2726
/**
28-
* Holds if the lambda passed produces taint that taints the result of this method.
27+
* Holds if the lambda passed at the given `arg` position produces taint
28+
* that taints the result of this method.
2929
* Eg. `var tainted = CompletableFuture.supplyAsync(() -> taint());`
3030
*/
31-
predicate doesReturnTaint() { none() }
31+
predicate doesReturnTaint(int arg) { none() }
32+
}
33+
34+
private class RatpackPromiseProviderethod extends Method, FluentLambdaMethod {
35+
RatpackPromiseProviderethod() { isStatic() and hasName(["flatten", "sync"]) }
36+
37+
override predicate consumesTaint(int methodArg, int lambdaArg) { none() }
38+
39+
override predicate doesReturnTaint(int arg) { arg = 0 }
40+
}
41+
42+
abstract private class SimpleFluentLambdaMethod extends FluentLambdaMethod {
43+
override predicate consumesTaint(int methodArg, int lambdaArg) {
44+
methodArg = 0 and consumesTaint(lambdaArg)
45+
}
46+
47+
/**
48+
* Holds if this lambda consumes taint from the quaifier when `arg` is tainted.
49+
* Eg. `tainted.map(stillTainted -> ..)`
50+
*/
51+
abstract predicate consumesTaint(int lambdaArg);
3252
}
3353

3454
private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod {
3555
RatpackPromiseMapMethod() {
3656
getDeclaringType() instanceof RatpackPromise and
37-
hasName(["map", "flatMap"])
57+
hasName(["map", "flatMap", "blockingMap"])
3858
}
3959

40-
override predicate consumesTaint(int arg) { arg = 0 }
60+
override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 }
4161

42-
override predicate doesReturnTaint() { any() }
62+
override predicate doesReturnTaint(int arg) { arg = 0 }
63+
}
64+
65+
/**
66+
* Represents the `mapIf` and `flatMapIf` method.
67+
*
68+
* `<O> Promise<O> mapIf(Predicate<T> predicate, Function<T, O> onTrue, Function<T, O> onFalse)`
69+
* `<O> Promise<O> flatMapIf(Predicate<T> predicate, Function<T, Promise<O>> onTrue, Function<T, Promise<O>> onFalse)`
70+
*/
71+
private class RatpackPromiseMapIfMethod extends FluentLambdaMethod {
72+
RatpackPromiseMapIfMethod() {
73+
getDeclaringType() instanceof RatpackPromise and
74+
hasName(["mapIf", "flatMapIf"]) and
75+
getNumberOfParameters() = 3
76+
}
77+
78+
override predicate consumesTaint(int methodArg, int lambdaArg) {
79+
methodArg = [1, 2, 3] and lambdaArg = 0
80+
}
81+
82+
override predicate doesReturnTaint(int arg) { arg = [1, 2] }
83+
}
84+
85+
private class RatpackPromiseMapErrorMethod extends FluentLambdaMethod {
86+
RatpackPromiseMapErrorMethod() {
87+
getDeclaringType() instanceof RatpackPromise and
88+
hasName(["mapError", "flatMapError"])
89+
}
90+
91+
override predicate consumesTaint(int methodArg, int lambdaArg) { none() }
92+
93+
override predicate doesReturnTaint(int arg) { arg = getNumberOfParameters() - 1 }
4394
}
4495

4596
private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod {
@@ -48,16 +99,29 @@ private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod {
4899
hasName("then")
49100
}
50101

51-
override predicate consumesTaint(int arg) { arg = 0 }
102+
override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 }
52103
}
53104

54-
private class RatpackPromiseNextMethod extends FluentMethod, SimpleFluentLambdaMethod {
55-
RatpackPromiseNextMethod() {
105+
private class RatpackPromiseFluentMethod extends FluentMethod, FluentLambdaMethod {
106+
RatpackPromiseFluentMethod() {
56107
getDeclaringType() instanceof RatpackPromise and
57-
hasName("next")
108+
not isStatic() and
109+
exists(ParameterizedType t |
110+
t instanceof RatpackPromise and
111+
t = getDeclaringType() and
112+
t = getReturnType()
113+
)
58114
}
59115

60-
override predicate consumesTaint(int arg) { arg = 0 }
116+
override predicate consumesTaint(int methodArg, int lambdaArg) {
117+
hasName(["next"]) and methodArg = 0 and lambdaArg = 0
118+
or
119+
hasName(["cacheIf"]) and methodArg = 0 and lambdaArg = 0
120+
or
121+
hasName(["route"]) and methodArg = [0, 1] and lambdaArg = 0
122+
}
123+
124+
override predicate doesReturnTaint(int arg) { hasName(["flatMapIf"]) and arg = 1 }
61125
}
62126

63127
private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep {
@@ -70,10 +134,13 @@ private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep {
70134
* Holds if the method access qualifier `node1` has dataflow to the functional expression parameter `node2`.
71135
*/
72136
predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) {
73-
exists(MethodAccess ma, SimpleFluentLambdaMethod sflm, int arg | sflm.consumesTaint(arg) |
74-
ma.getMethod() = sflm and
137+
exists(MethodAccess ma, FluentLambdaMethod flm, int methodArg, int lambdaArg |
138+
flm.consumesTaint(methodArg, lambdaArg)
139+
|
140+
ma.getMethod() = flm and
75141
node1.asExpr() = ma.getQualifier() and
76-
ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(arg) = node2.asParameter()
142+
ma.getArgument(methodArg).(FunctionalExpr).asMethod().getParameter(lambdaArg) =
143+
node2.asParameter()
77144
)
78145
}
79146

@@ -82,13 +149,13 @@ private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep {
82149
* method access result `node2`.
83150
*/
84151
predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) {
85-
exists(SimpleFluentLambdaMethod sflm, MethodAccess ma, FunctionalExpr fe |
86-
sflm.doesReturnTaint()
152+
exists(FluentLambdaMethod flm, MethodAccess ma, FunctionalExpr fe, int arg |
153+
flm.doesReturnTaint(arg)
87154
|
88155
fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and
89-
ma.getMethod() = sflm and
156+
ma.getMethod() = flm and
90157
node2.asExpr() = ma and
91-
ma.getArgument(0) = fe
158+
ma.getArgument(arg) = fe
92159
)
93160
}
94161
}

java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import ratpack.core.form.UploadedFile;
55
import ratpack.core.parse.Parse;
66
import ratpack.exec.Promise;
7+
import ratpack.func.Action;
78
import java.io.OutputStream;
89

910
class Resource {
@@ -100,5 +101,92 @@ void test6(Context ctx) {
100101
.then(form -> {
101102
sink(form); //$hasTaintFlow
102103
});
104+
ctx
105+
.parse(Form.class, "Some Object")
106+
.then(form -> {
107+
sink(form); //$hasTaintFlow
108+
});
109+
}
110+
111+
void test7() {
112+
String tainted = taint();
113+
Promise
114+
.flatten(() -> Promise.value(tainted))
115+
.next(value -> {
116+
sink(value); //$hasTaintFlow
117+
})
118+
.onError(Action.noop())
119+
.next(value -> {
120+
sink(value); //$hasTaintFlow
121+
})
122+
.cache()
123+
.next(value -> {
124+
sink(value); //$hasTaintFlow
125+
})
126+
.fork()
127+
.next(value -> {
128+
sink(value); //$hasTaintFlow
129+
})
130+
.route(value -> {
131+
sink(value); //$hasTaintFlow
132+
return false;
133+
}, value -> {
134+
sink(value); //$hasTaintFlow
135+
})
136+
.next(value -> {
137+
sink(value); //$hasTaintFlow
138+
})
139+
.cacheIf(value -> {
140+
sink(value); //$hasTaintFlow
141+
return true;
142+
})
143+
.next(value -> {
144+
sink(value); //$hasTaintFlow
145+
})
146+
.onError(RuntimeException.class, Action.noop())
147+
.next(value -> {
148+
sink(value); //$hasTaintFlow
149+
})
150+
.then(value -> {
151+
sink(value); //$hasTaintFlow
152+
});
153+
}
154+
155+
void test8() {
156+
String tainted = taint();
157+
Promise
158+
.sync(() -> tainted)
159+
.mapError(RuntimeException.class, exception -> {
160+
sink(exception); // no taint
161+
return "potato";
162+
})
163+
.then(value -> {
164+
sink(value); //$hasTaintFlow
165+
});
166+
Promise
167+
.value("potato")
168+
.mapError(RuntimeException.class, exception -> {
169+
return taint();
170+
})
171+
.then(value -> {
172+
sink(value); //$hasTaintFlow
173+
});
174+
Promise
175+
.value(tainted)
176+
.flatMapError(RuntimeException.class, exception -> {
177+
sink(exception); // no taint
178+
return Promise.value("potato");
179+
})
180+
.then(value -> {
181+
sink(value); //$hasTaintFlow
182+
});
183+
Promise
184+
.value("potato")
185+
.flatMapError(RuntimeException.class, exception -> {
186+
return Promise.value(taint());
187+
})
188+
.then(value -> {
189+
sink(value); //$hasTaintFlow
190+
});
103191
}
104192
}

java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)