Skip to content

Java: Simple support for Ratpack HTTP Framework #4991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f5c3723
Java: Simple support for Ratpack HTTP Framework
JLLeitschuh Jan 20, 2021
dabf00e
Add Tests to Ratpack Framework Support
JLLeitschuh Apr 30, 2021
170657b
Add additional Ratpack test and improve Promise based dataflow tracking
JLLeitschuh Apr 30, 2021
b2ad128
Refactors Ratpack lambda taint tracking to use generic API
JLLeitschuh Apr 30, 2021
b2e3df2
Add support for Promise.value and Promise::flatMap
JLLeitschuh Apr 30, 2021
18c74c5
Simplify Ratpack API using standard abstract classes
JLLeitschuh Apr 30, 2021
4f658df
Apply suggestions from code review
JLLeitschuh May 3, 2021
563e569
Refactor Ratpack to use CSV format
JLLeitschuh May 10, 2021
ac185d9
Remove RatpackGetRequestDataMethod
JLLeitschuh May 10, 2021
a3b1736
Ratpack improve support for parsing types
JLLeitschuh May 10, 2021
cdfdcc6
Ratpack fix formatting and non-ascii characters
JLLeitschuh May 11, 2021
b9dc3d0
Ratpack: Better support for Promise API
JLLeitschuh May 11, 2021
901631c
Ratpack Promise add support for `apply` method
JLLeitschuh May 12, 2021
af90b00
Ratpack: Release note and typo fix
JLLeitschuh May 12, 2021
6497a61
Ratpack: Drop support for `flatMap` like methods
JLLeitschuh May 18, 2021
4f90f0a
Begin refactoring Ratpack to use functional taint tracking
JLLeitschuh Oct 6, 2021
6562ac3
Ratpack conversion to new lambda model
JLLeitschuh Oct 6, 2021
fe374f5
Ratpack: Add support for `Promise::apply`
JLLeitschuh Oct 6, 2021
ebbbda7
Ratpack tests all passing
JLLeitschuh Oct 7, 2021
23e60e2
Add full integration test for Ratpack example
JLLeitschuh Oct 11, 2021
8fecc15
Add support for Map.forEach
JLLeitschuh Oct 11, 2021
5a2bdc9
Jackson taint tracking of elements
JLLeitschuh Oct 13, 2021
db2892b
Resove taint tracking issues from `asMultimap`
JLLeitschuh Oct 18, 2021
8231907
Ratpack code cleanup from code review
JLLeitschuh Oct 19, 2021
584c27a
Move CollectionPassingTest to correct directory
JLLeitschuh Oct 19, 2021
cce3aad
Remove non-ASCII characters from Handler.java
JLLeitschuh Oct 20, 2021
5eb2839
Remove non-ASCII characters from Promise.java
JLLeitschuh Oct 22, 2021
ebe2c26
Remove the last non-ascii quote from Promise
JLLeitschuh Oct 25, 2021
21aeee6
Actually remove the last non-ascii quote from Promise
JLLeitschuh Oct 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions java/change-notes/2021-05-11-ratpack-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lgtm,codescanning
* Add support for [Ratpack](https://ratpack.io/) HTTP framework.
2 changes: 2 additions & 0 deletions java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ private module Frameworks {
private import semmle.code.java.frameworks.Optional
private import semmle.code.java.frameworks.Stream
private import semmle.code.java.frameworks.Strings
private import semmle.code.java.frameworks.ratpack.Ratpack
private import semmle.code.java.frameworks.ratpack.RatpackExec
private import semmle.code.java.frameworks.spring.SpringCache
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.frameworks.spring.SpringUtil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ private class ContainerFlowSummaries extends SummaryModelCsv {
"java.util;Map;true;merge;(Object,Object,BiFunction);;Argument[1];MapValue of Argument[-1];value",
"java.util;Map;true;putAll;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value",
"java.util;Map;true;putAll;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value",
"java.util;Map;true;forEach;(BiConsumer);;MapKey of Argument[-1];Parameter[0] of Argument[0];value",
"java.util;Map;true;forEach;(BiConsumer);;MapValue of Argument[-1];Parameter[1] of Argument[0];value",
"java.util;Collection;true;parallelStream;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Collection;true;stream;();;Element of Argument[-1];Element of ReturnValue;value",
"java.util;Collection;true;toArray;;;Element of Argument[-1];ArrayElement of ReturnValue;value",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ private class JacksonModel extends SummaryModelCsv {
[
"com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;Argument[0];ReturnValue;taint",
"com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;MapValue of Argument[0];ReturnValue;taint",
"com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;Element of MapValue of Argument[0];ReturnValue;taint",
"com.fasterxml.jackson.databind;ObjectMapper;true;convertValue;;;Argument[0];ReturnValue;taint",
"com.fasterxml.jackson.databind;ObjectMapper;false;createParser;;;Argument[0];ReturnValue;taint",
"com.fasterxml.jackson.databind;ObjectReader;false;createParser;;;Argument[0];ReturnValue;taint",
Expand Down
87 changes: 87 additions & 0 deletions java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Provides classes and predicates related to `ratpack.*`.
*/

import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow

/**
* Ratpack methods that access user-supplied request data.
*/
private class RatpackHttpSource extends SourceModelCsv {
override predicate row(string row) {
row =
["ratpack.http;", "ratpack.core.http;"] +
[
"Request;true;getContentLength;;;ReturnValue;remote",
"Request;true;getCookies;;;ReturnValue;remote",
"Request;true;oneCookie;;;ReturnValue;remote",
"Request;true;getHeaders;;;ReturnValue;remote",
"Request;true;getPath;;;ReturnValue;remote", "Request;true;getQuery;;;ReturnValue;remote",
"Request;true;getQueryParams;;;ReturnValue;remote",
"Request;true;getRawUri;;;ReturnValue;remote", "Request;true;getUri;;;ReturnValue;remote",
"Request;true;getBody;;;ReturnValue;remote"
]
or
// All Context#parse methods that return a Promise are remote flow sources.
row =
["ratpack.handling;", "ratpack.core.handling;"] + "Context;true;parse;" +
[
"(java.lang.Class);", "(com.google.common.reflect.TypeToken);",
"(java.lang.Class,java.lang.Object);",
"(com.google.common.reflect.TypeToken,java.lang.Object);", "(ratpack.core.parse.Parse);",
"(ratpack.parse.Parse);"
] + ";ReturnValue;remote"
}
}

/**
* Ratpack methods that propagate user-supplied request data as tainted.
*/
private class RatpackModel extends SummaryModelCsv {
override predicate row(string row) {
row =
["ratpack.http;", "ratpack.core.http;"] +
[
"TypedData;true;getBuffer;;;Argument[-1];ReturnValue;taint",
"TypedData;true;getBytes;;;Argument[-1];ReturnValue;taint",
"TypedData;true;getContentType;;;Argument[-1];ReturnValue;taint",
"TypedData;true;getInputStream;;;Argument[-1];ReturnValue;taint",
"TypedData;true;getText;;;Argument[-1];ReturnValue;taint",
"TypedData;true;writeTo;;;Argument[-1];Argument[0];taint",
"Headers;true;get;;;Argument[-1];ReturnValue;taint",
"Headers;true;getAll;;;Argument[-1];ReturnValue;taint",
"Headers;true;getNames;;;Argument[-1];ReturnValue;taint",
"Headers;true;asMultiValueMap;;;Argument[-1];ReturnValue;taint"
]
or
row =
["ratpack.form;", "ratpack.core.form;"] +
[
"UploadedFile;true;getFileName;;;Argument[-1];ReturnValue;taint",
"Form;true;file;;;Argument[-1];ReturnValue;taint",
"Form;true;files;;;Argument[-1];ReturnValue;taint"
]
or
row =
["ratpack.handling;", "ratpack.core.handling;"] +
[
"Context;true;parse;(ratpack.http.TypedData,ratpack.parse.Parse);;Argument[0];ReturnValue;taint",
"Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];ReturnValue;taint",
"Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];MapKey of ReturnValue;taint",
"Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];MapValue of ReturnValue;taint"
]
or
row =
["ratpack.util;", "ratpack.func;"] +
[
"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;getAll;(Object);;MapValue of Argument[-1];Element of ReturnValue;value",
"MultiValueMap;true;asMultimap;;;MapKey of Argument[-1];MapKey of ReturnValue;value",
"MultiValueMap;true;asMultimap;;;MapValue of Argument[-1];MapValue of ReturnValue;value"
]
}
}
79 changes: 79 additions & 0 deletions java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Provides classes and predicates related to `ratpack.exec.*`.
*/

import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow

/**
* Model for Ratpack `Promise` methods.
*/
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[1];Element of ReturnValue;value",
// `apply` passes the qualifier to the function as the first argument
"apply;;;Element of Argument[-1];Element of Parameter[0] of Argument[0];value",
"apply;;;Element of 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[1];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() {
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("ratpack.exec", "Promise")
}
}

/**
* Ratpack `Promise` method that will return `this`.
*/
private class RatpackPromiseFluentMethod extends FluentMethod {
RatpackPromiseFluentMethod() {
not this.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 = this.getDeclaringType() and
t = this.getReturnType()
)
}
}
Empty file.
33 changes: 33 additions & 0 deletions java/ql/test/library-tests/frameworks/ratpack/flow.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineExpectationsTest

class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:frameworks:ratpack" }

override predicate isSource(DataFlow::Node n) {
n.asExpr().(MethodAccess).getMethod().hasName("taint")
or
n instanceof RemoteFlowSource
}

override predicate isSink(DataFlow::Node n) {
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
}
}

class HasFlowTest extends InlineExpectationsTest {
HasFlowTest() { this = "HasFlowTest" }

override string getARelevantTag() { result = "hasTaintFlow" }

override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasTaintFlow" and
exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) |
sink.getLocation() = location and
element = sink.toString() and
value = ""
)
}
}
1 change: 1 addition & 0 deletions java/ql/test/library-tests/frameworks/ratpack/options
Original file line number Diff line number Diff line change
@@ -0,0 +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
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import ratpack.core.handling.Context;
import ratpack.core.form.Form;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.function.Predicate;

public class CollectionPassingTest {

void sink(Object o) {}

String taint() {
return null;
}

void test_1(Context ctx) {
// Given
ctx
.getRequest()
.getBody()
.map(data -> ctx.parse(data, Form.form()))
.then(form -> {
// When
Map<String, Object> pojoMap = new HashMap<>();
merge(form.asMultimap().asMap(), pojoMap);
// Then
sink(pojoMap.get("value")); //$hasTaintFlow
pojoMap.forEach((key, value) -> {
sink(value); //$hasTaintFlow
List<Object> values = (List<Object>) value;
sink(values.get(0)); //$hasTaintFlow
});
});
}

void test_2() {
// Given
Map<String, Collection<String>> taintedMap = new HashMap<>();
taintedMap.put("value", ImmutableList.of(taint()));
Map<String, Object> pojoMap = new HashMap<>();
// When
merge(taintedMap, pojoMap);
// Then
sink(pojoMap.get("value")); //$hasTaintFlow
pojoMap.forEach((key, value) -> {
sink(value); //$hasTaintFlow
List<Object> values = (List<Object>) value;
sink(values.get(0)); //$hasTaintFlow
});
}


private static void merge(Map<String, Collection<String>> params, Map<String, Object> defaults) {
for(Map.Entry<String, Collection<String>> entry : params.entrySet()) {
String name = entry.getKey();
Collection<String> values = entry.getValue();
defaults.put(name, extractSingleValueIfPossible(values));
}
}

private static Object extractSingleValueIfPossible(Collection<String> values) {
return values.size() == 1 ? values.iterator().next() : ImmutableList.copyOf(values);
}

}
Loading