Skip to content

Commit 4a67ac5

Browse files
authored
Merge pull request #4991 from JLLeitschuh/feat/JLL/early_ratpack_support
Java: Simple support for Ratpack HTTP Framework
2 parents 02b440b + 21aeee6 commit 4a67ac5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+4637
-2
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Add support for [Ratpack](https://ratpack.io/) HTTP framework.

java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ private module Frameworks {
9696
private import semmle.code.java.frameworks.Optional
9797
private import semmle.code.java.frameworks.Stream
9898
private import semmle.code.java.frameworks.Strings
99+
private import semmle.code.java.frameworks.ratpack.Ratpack
100+
private import semmle.code.java.frameworks.ratpack.RatpackExec
99101
private import semmle.code.java.frameworks.spring.SpringCache
100102
private import semmle.code.java.frameworks.spring.SpringHttp
101103
private import semmle.code.java.frameworks.spring.SpringUtil

java/ql/lib/semmle/code/java/frameworks/jackson/JacksonSerializability.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ private class JacksonModel extends SummaryModelCsv {
285285
[
286286
"com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;Argument[0];ReturnValue;taint",
287287
"com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;MapValue of Argument[0];ReturnValue;taint",
288+
"com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;Element of MapValue of Argument[0];ReturnValue;taint",
288289
"com.fasterxml.jackson.databind;ObjectMapper;true;convertValue;;;Argument[0];ReturnValue;taint",
289290
"com.fasterxml.jackson.databind;ObjectMapper;false;createParser;;;Argument[0];ReturnValue;taint",
290291
"com.fasterxml.jackson.databind;ObjectReader;false;createParser;;;Argument[0];ReturnValue;taint",
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Provides classes and predicates related to `ratpack.*`.
3+
*/
4+
5+
import java
6+
private import semmle.code.java.dataflow.DataFlow
7+
private import semmle.code.java.dataflow.FlowSteps
8+
private import semmle.code.java.dataflow.ExternalFlow
9+
10+
/**
11+
* Ratpack methods that access user-supplied request data.
12+
*/
13+
private class RatpackHttpSource extends SourceModelCsv {
14+
override predicate row(string row) {
15+
row =
16+
["ratpack.http;", "ratpack.core.http;"] +
17+
[
18+
"Request;true;getContentLength;;;ReturnValue;remote",
19+
"Request;true;getCookies;;;ReturnValue;remote",
20+
"Request;true;oneCookie;;;ReturnValue;remote",
21+
"Request;true;getHeaders;;;ReturnValue;remote",
22+
"Request;true;getPath;;;ReturnValue;remote", "Request;true;getQuery;;;ReturnValue;remote",
23+
"Request;true;getQueryParams;;;ReturnValue;remote",
24+
"Request;true;getRawUri;;;ReturnValue;remote", "Request;true;getUri;;;ReturnValue;remote",
25+
"Request;true;getBody;;;ReturnValue;remote"
26+
]
27+
or
28+
// All Context#parse methods that return a Promise are remote flow sources.
29+
row =
30+
["ratpack.handling;", "ratpack.core.handling;"] + "Context;true;parse;" +
31+
[
32+
"(java.lang.Class);", "(com.google.common.reflect.TypeToken);",
33+
"(java.lang.Class,java.lang.Object);",
34+
"(com.google.common.reflect.TypeToken,java.lang.Object);", "(ratpack.core.parse.Parse);",
35+
"(ratpack.parse.Parse);"
36+
] + ";ReturnValue;remote"
37+
}
38+
}
39+
40+
/**
41+
* Ratpack methods that propagate user-supplied request data as tainted.
42+
*/
43+
private class RatpackModel extends SummaryModelCsv {
44+
override predicate row(string row) {
45+
row =
46+
["ratpack.http;", "ratpack.core.http;"] +
47+
[
48+
"TypedData;true;getBuffer;;;Argument[-1];ReturnValue;taint",
49+
"TypedData;true;getBytes;;;Argument[-1];ReturnValue;taint",
50+
"TypedData;true;getContentType;;;Argument[-1];ReturnValue;taint",
51+
"TypedData;true;getInputStream;;;Argument[-1];ReturnValue;taint",
52+
"TypedData;true;getText;;;Argument[-1];ReturnValue;taint",
53+
"TypedData;true;writeTo;;;Argument[-1];Argument[0];taint",
54+
"Headers;true;get;;;Argument[-1];ReturnValue;taint",
55+
"Headers;true;getAll;;;Argument[-1];ReturnValue;taint",
56+
"Headers;true;getNames;;;Argument[-1];ReturnValue;taint",
57+
"Headers;true;asMultiValueMap;;;Argument[-1];ReturnValue;taint"
58+
]
59+
or
60+
row =
61+
["ratpack.form;", "ratpack.core.form;"] +
62+
[
63+
"UploadedFile;true;getFileName;;;Argument[-1];ReturnValue;taint",
64+
"Form;true;file;;;Argument[-1];ReturnValue;taint",
65+
"Form;true;files;;;Argument[-1];ReturnValue;taint"
66+
]
67+
or
68+
row =
69+
["ratpack.handling;", "ratpack.core.handling;"] +
70+
[
71+
"Context;true;parse;(ratpack.http.TypedData,ratpack.parse.Parse);;Argument[0];ReturnValue;taint",
72+
"Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];ReturnValue;taint",
73+
"Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];MapKey of ReturnValue;taint",
74+
"Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];MapValue of ReturnValue;taint"
75+
]
76+
or
77+
row =
78+
["ratpack.util;", "ratpack.func;"] +
79+
[
80+
"MultiValueMap;true;getAll;;;MapKey of Argument[-1];MapKey of ReturnValue;value",
81+
"MultiValueMap;true;getAll;();;MapValue of Argument[-1];Element of MapValue of ReturnValue;value",
82+
"MultiValueMap;true;getAll;(Object);;MapValue of Argument[-1];Element of ReturnValue;value",
83+
"MultiValueMap;true;asMultimap;;;MapKey of Argument[-1];MapKey of ReturnValue;value",
84+
"MultiValueMap;true;asMultimap;;;MapValue of Argument[-1];MapValue of ReturnValue;value"
85+
]
86+
}
87+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Provides classes and predicates related to `ratpack.exec.*`.
3+
*/
4+
5+
import java
6+
private import semmle.code.java.dataflow.DataFlow
7+
private import semmle.code.java.dataflow.FlowSteps
8+
private import semmle.code.java.dataflow.ExternalFlow
9+
10+
/**
11+
* Model for Ratpack `Promise` methods.
12+
*/
13+
private class RatpackExecModel extends SummaryModelCsv {
14+
override predicate row(string row) {
15+
//"namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind",
16+
row =
17+
["ratpack.exec;Promise;true;"] +
18+
[
19+
// `Promise` creation methods
20+
"value;;;Argument[0];Element of ReturnValue;value",
21+
"flatten;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value",
22+
"sync;;;ReturnValue of Argument[0];Element of ReturnValue;value",
23+
// `Promise` value transformation methods
24+
"map;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
25+
"map;;;ReturnValue of Argument[0];Element of ReturnValue;value",
26+
"blockingMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
27+
"blockingMap;;;ReturnValue of Argument[0];Element of ReturnValue;value",
28+
"mapError;;;ReturnValue of Argument[1];Element of ReturnValue;value",
29+
// `apply` passes the qualifier to the function as the first argument
30+
"apply;;;Element of Argument[-1];Element of Parameter[0] of Argument[0];value",
31+
"apply;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value",
32+
// `Promise` termination method
33+
"then;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
34+
// 'next' accesses qualfier the 'Promise' value and also returns the qualifier
35+
"next;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
36+
"next;;;Argument[-1];ReturnValue;value",
37+
// 'cacheIf' accesses qualfier the 'Promise' value and also returns the qualifier
38+
"cacheIf;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
39+
"cacheIf;;;Argument[-1];ReturnValue;value",
40+
// 'route' accesses qualfier the 'Promise' value, and conditionally returns the qualifier or
41+
// the result of the second argument
42+
"route;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
43+
"route;;;Element of Argument[-1];Parameter[0] of Argument[1];value",
44+
"route;;;Argument[-1];ReturnValue;value",
45+
// `flatMap` type methods return their returned `Promise`
46+
"flatMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
47+
"flatMap;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value",
48+
"flatMapError;;;Element of ReturnValue of Argument[1];Element of ReturnValue;value",
49+
// `mapIf` methods conditionally map their values, or return themselves
50+
"mapIf;;;Element of Argument[-1];Parameter[0] of Argument[0];value",
51+
"mapIf;;;Element of Argument[-1];Parameter[0] of Argument[1];value",
52+
"mapIf;;;Element of Argument[-1];Parameter[0] of Argument[2];value",
53+
"mapIf;;;ReturnValue of Argument[1];Element of ReturnValue;value",
54+
"mapIf;;;ReturnValue of Argument[2];Element of ReturnValue;value"
55+
]
56+
}
57+
}
58+
59+
/** A reference type that extends a parameterization the Promise type. */
60+
private class RatpackPromise extends RefType {
61+
RatpackPromise() {
62+
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("ratpack.exec", "Promise")
63+
}
64+
}
65+
66+
/**
67+
* Ratpack `Promise` method that will return `this`.
68+
*/
69+
private class RatpackPromiseFluentMethod extends FluentMethod {
70+
RatpackPromiseFluentMethod() {
71+
not this.isStatic() and
72+
// It's generally safe to assume that if the return type exactly matches the declaring type, `this` will be returned.
73+
exists(ParameterizedType t |
74+
t instanceof RatpackPromise and
75+
t = this.getDeclaringType() and
76+
t = this.getReturnType()
77+
)
78+
}
79+
}

java/ql/test/library-tests/frameworks/ratpack/flow.expected

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import java
2+
import semmle.code.java.dataflow.TaintTracking
3+
import semmle.code.java.dataflow.FlowSources
4+
import TestUtilities.InlineExpectationsTest
5+
6+
class Conf extends TaintTracking::Configuration {
7+
Conf() { this = "qltest:frameworks:ratpack" }
8+
9+
override predicate isSource(DataFlow::Node n) {
10+
n.asExpr().(MethodAccess).getMethod().hasName("taint")
11+
or
12+
n instanceof RemoteFlowSource
13+
}
14+
15+
override predicate isSink(DataFlow::Node n) {
16+
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
17+
}
18+
}
19+
20+
class HasFlowTest extends InlineExpectationsTest {
21+
HasFlowTest() { this = "HasFlowTest" }
22+
23+
override string getARelevantTag() { result = "hasTaintFlow" }
24+
25+
override predicate hasActualResult(Location location, string element, string tag, string value) {
26+
tag = "hasTaintFlow" and
27+
exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) |
28+
sink.getLocation() = location and
29+
element = sink.toString() and
30+
value = ""
31+
)
32+
}
33+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/ratpack-1.9.x:${testdir}/../../../stubs/jackson-core-2.12:${testdir}/../../../stubs/jackson-databind-2.12:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/netty-4.1.x
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import com.google.common.collect.ImmutableList;
2+
import com.google.common.collect.ImmutableMap;
3+
4+
import ratpack.core.handling.Context;
5+
import ratpack.core.form.Form;
6+
7+
import java.util.Collection;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.List;
11+
import java.util.function.Predicate;
12+
13+
public class CollectionPassingTest {
14+
15+
void sink(Object o) {}
16+
17+
String taint() {
18+
return null;
19+
}
20+
21+
void test_1(Context ctx) {
22+
// Given
23+
ctx
24+
.getRequest()
25+
.getBody()
26+
.map(data -> ctx.parse(data, Form.form()))
27+
.then(form -> {
28+
// When
29+
Map<String, Object> pojoMap = new HashMap<>();
30+
merge(form.asMultimap().asMap(), pojoMap);
31+
// Then
32+
sink(pojoMap.get("value")); //$hasTaintFlow
33+
pojoMap.forEach((key, value) -> {
34+
sink(value); //$hasTaintFlow
35+
List<Object> values = (List<Object>) value;
36+
sink(values.get(0)); //$hasTaintFlow
37+
});
38+
});
39+
}
40+
41+
void test_2() {
42+
// Given
43+
Map<String, Collection<String>> taintedMap = new HashMap<>();
44+
taintedMap.put("value", ImmutableList.of(taint()));
45+
Map<String, Object> pojoMap = new HashMap<>();
46+
// When
47+
merge(taintedMap, pojoMap);
48+
// Then
49+
sink(pojoMap.get("value")); //$hasTaintFlow
50+
pojoMap.forEach((key, value) -> {
51+
sink(value); //$hasTaintFlow
52+
List<Object> values = (List<Object>) value;
53+
sink(values.get(0)); //$hasTaintFlow
54+
});
55+
}
56+
57+
58+
private static void merge(Map<String, Collection<String>> params, Map<String, Object> defaults) {
59+
for(Map.Entry<String, Collection<String>> entry : params.entrySet()) {
60+
String name = entry.getKey();
61+
Collection<String> values = entry.getValue();
62+
defaults.put(name, extractSingleValueIfPossible(values));
63+
}
64+
}
65+
66+
private static Object extractSingleValueIfPossible(Collection<String> values) {
67+
return values.size() == 1 ? values.iterator().next() : ImmutableList.copyOf(values);
68+
}
69+
70+
}

0 commit comments

Comments
 (0)