Merge pull request #4383 from joefarebrother/guava-strings

Java: Add modelling for Guava
This commit is contained in:
Joe Farebrother
2020-10-26 10:16:55 +00:00
committed by GitHub
12 changed files with 445 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Some methods of the [Guava](https://guava.dev/) framework have been added as flow steps (specifically those of the [Splitter](https://guava.dev/releases/30.0-jre/api/docs/com/google/common/base/Splitter.html), [Joiner](https://guava.dev/releases/30.0-jre/api/docs/com/google/common/base/Joiner.html), and [Strings](https://guava.dev/releases/30.0-jre/api/docs/com/google/common/base/Strings.html) classes), which may lead to more results from the security queries.

View File

@@ -15,6 +15,7 @@ module Frameworks {
private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.Guice
private import semmle.code.java.frameworks.Protobuf
private import semmle.code.java.frameworks.guava.Guava
}
/**

View File

@@ -79,6 +79,11 @@ Expr clearlyNotNullExpr(Expr reason) {
)
or
exists(SsaVariable v | clearlyNotNull(v, reason) and result = v.getAUse())
or
exists(Method m | m = result.(MethodAccess).getMethod() and reason = result |
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Strings") and
m.hasName("nullToEmpty")
)
}
/** Holds if `v` is an SSA variable that is provably not `null`. */
@@ -146,6 +151,11 @@ predicate nullCheckMethod(Method m, boolean branch, boolean isnull) {
m.hasName("isNotEmpty") and
branch = true and
isnull = false
or
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Strings") and
m.hasName("isNullOrEmpty") and
branch = false and
isnull = false
}
/**

View File

@@ -0,0 +1,6 @@
/**
* Definitions for tracking taint steps through the Guava framework.
*/
import java
import StringUtils

View File

@@ -0,0 +1,169 @@
/** Definitions of flow steps through the various string utility functions in the Guava framework. */
import java
private import semmle.code.java.dataflow.FlowSteps
/**
* The class `com.google.common.base.Strings`.
*/
class TypeGuavaStrings extends Class {
TypeGuavaStrings() { this.hasQualifiedName("com.google.common.base", "Strings") }
}
/**
* The class `com.google.common.base.Joiner`.
*/
class TypeGuavaJoiner extends Class {
TypeGuavaJoiner() { this.hasQualifiedName("com.google.common.base", "Joiner") }
}
/**
* The nested class `Joiner.MapJoiner`.
*/
class TypeGuavaMapJoiner extends NestedClass {
TypeGuavaMapJoiner() {
this.getEnclosingType() instanceof TypeGuavaJoiner and
this.hasName("MapJoiner")
}
}
/**
* The class `com.google.common.base.Splitter`.
*/
class TypeGuavaSplitter extends Class {
TypeGuavaSplitter() { this.hasQualifiedName("com.google.common.base", "Splitter") }
}
/**
* The nested class `Splitter.MapSplitter`.
*/
class TypeGuavaMapSplitter extends NestedClass {
TypeGuavaMapSplitter() {
this.getEnclosingType() instanceof TypeGuavaSplitter and
this.hasName("MapSplitter")
}
}
/**
* A taint preserving method on `com.google.common.base.Strings`.
*/
private class GuavaStringsTaintPreservingMethod extends TaintPreservingCallable {
GuavaStringsTaintPreservingMethod() {
this.getDeclaringType() instanceof TypeGuavaStrings and
// static String emptyToNull(String string)
// static String nullToEmpty(String string)
// static String padStart(String string, int minLength, char padChar)
// static String padEnd(String string, int minLength, char padChar)
// static String repeat(String string, int count)
// static String lenientFormat(String template, Object ... args)
this.hasName(["emptyToNull", "nullToEmpty", "padStart", "padEnd", "repeat", "lenientFormat"])
}
override predicate returnsTaintFrom(int src) {
src = 0
or
this.hasName("lenientFormat") and
src = [0 .. getNumberOfParameters()]
}
}
/**
* A method of `Joiner` or `MapJoiner`.
*/
private class GuavaJoinerMethod extends Method {
GuavaJoinerMethod() {
this.getDeclaringType().getASourceSupertype*() instanceof TypeGuavaJoiner or
this.getDeclaringType().getASourceSupertype*() instanceof TypeGuavaMapJoiner
}
}
/**
* A method that builds a `Joiner` or `MapJoiner`.
*/
private class GuavaJoinerBuilderMethod extends GuavaJoinerMethod, TaintPreservingCallable {
GuavaJoinerBuilderMethod() {
// static Joiner on(char separator)
// static Joiner on(String separator)
// Joiner skipNulls()
// Joiner useForNull(String nullText)
// Joiner.MapJoiner withKeyValueSeparator(char keyValueSeparator)
// Joiner.MapJoiner withKeyValueSeparator(String keyValueSeparator)
// Joiner.MapJoiner useForNull(String nullText) [on MapJoiner]
this.hasName(["on", "skipNulls", "useForNull", "withKeyValueSeparator"])
}
override predicate returnsTaintFrom(int src) {
src = 0
or
src = -1 and not isStatic()
}
}
/**
* An `appendTo` method on `Joiner` or `MapJoiner`.
*/
private class GuavaJoinerAppendToMethod extends GuavaJoinerMethod, TaintPreservingCallable {
GuavaJoinerAppendToMethod() {
// <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)
// <A extends Appendable> A appendTo(A appendable, Iterator<?> parts)
// <A extends Appendable> A appendTo(A appendable, Object[] parts)
// <A extends Appendable> A appendTo(A appendable, Object first, Object second, Object... rest)
// StringBuilder appendTo(StringBuilder builder, Iterable<?> parts)
// StringBuilder appendTo(StringBuilder builder, Iterator<?> parts)
// StringBuilder appendTo(StringBuilder builder, Object[] parts)
// StringBuilder appendTo(StringBuilder builder, Object first, Object second, Object... rest)
// <A extends Appendable> A appendTo(A appendable, Iterable<? extends Map.Entry<?,?>> entries) [on MapJoiner]
// <A extends Appendable> A appendTo(A appendable, Iterator<? extends Map.Entry<?,?>> parts)
// <A extends Appendable> A appendTo(A appendable, Map<?,?> map)
// StringBuilder appendTo(StringBuilder builder, Iterable<? extends Map.Entry<?,?>> entries)
// StringBuilder appendTo(StringBuilder builder, Iterator<? extends Map.Entry<?,?>> entries)
// StringBuilder appendTo(StringBuilder builder, Map<?,?> map)
this.hasName("appendTo")
}
override predicate transfersTaint(int src, int sink) {
src = [-1 .. getNumberOfParameters()] and
src != sink and
sink = 0
}
override predicate returnsTaintFrom(int src) { src = [-1 .. getNumberOfParameters()] }
}
/**
* A `join` method on `Joiner` or `MapJoiner`.
*/
private class GuavaJoinMethod extends GuavaJoinerMethod, TaintPreservingCallable {
GuavaJoinMethod() {
// String join(Iterable<?> parts)
// String join(Iterator<?> parts)
// String join(Object[] parts)
// String join(Object first, Object second, Object... rest)
// String join(Iterable<? extends Map.Entry<?,?>> entries) [on MapJoiner]
// String join(Iterator<? extends Map.Entry<?,?>> entries)
// String join(Map<?,?> map)
this.hasName("join")
}
override predicate returnsTaintFrom(int src) { src = [-1 .. getNumberOfParameters()] }
}
/**
* A method of `Splitter` or `MapSplitter` that splits its input string.
*/
private class GuavaSplitMethod extends TaintPreservingCallable {
GuavaSplitMethod() {
(
this.getDeclaringType() instanceof TypeGuavaSplitter
or
this.getDeclaringType() instanceof TypeGuavaMapSplitter
) and
// Iterable<String> split(CharSequence sequence)
// List<String> splitToList(CharSequence sequence)
// Stream<String> splitToStream(CharSequence sequence)
// Map<String,String> split(CharSequence sequence) [on MapSplitter]
this.hasName(["split", "splitToList", "splitToStream"])
}
override predicate returnsTaintFrom(int src) { src = 0 }
}

View File

@@ -0,0 +1,62 @@
import com.google.common.base.Strings;
import com.google.common.base.Splitter;
import com.google.common.base.Joiner;
import java.util.Map;
import java.util.HashMap;
class Test {
String taint() { return "tainted"; }
void sink(Object o) {}
void test1() {
String x = taint();
sink(Strings.padStart(x, 10, ' '));
sink(Strings.padEnd(x, 10, ' '));
sink(Strings.repeat(x, 3));
sink(Strings.emptyToNull(Strings.nullToEmpty(x)));
sink(Strings.lenientFormat(x, 3));
sink(Strings.commonPrefix(x, "abc"));
sink(Strings.commonSuffix(x, "cde"));
sink(Strings.lenientFormat("%s = %s", x, 3));
}
void test2() {
String x = taint();
Splitter s = Splitter.on(x).omitEmptyStrings();
sink(s.split("x y z"));
sink(s.split(x));
sink(s.splitToList(x));
sink(s.withKeyValueSeparator("=").split("a=b"));
sink(s.withKeyValueSeparator("=").split(x));
}
void test3() {
String x = taint();
Joiner taintedJoiner = Joiner.on(x);
Joiner safeJoiner = Joiner.on(", ");
StringBuilder sb = new StringBuilder();
sink(safeJoiner.appendTo(sb, "a", "b", "c"));
sink(sb.toString());
sink(taintedJoiner.appendTo(sb, "a", "b", "c"));
sink(sb.toString());
sink(safeJoiner.appendTo(sb, "a", "b", "c"));
sink(sb.toString());
sb = new StringBuilder();
sink(safeJoiner.appendTo(sb, x, x));
Map<String, String> m = new HashMap<String, String>();
m.put("k", "v");
sink(safeJoiner.withKeyValueSeparator("=").join(m));
sink(safeJoiner.withKeyValueSeparator(x).join(m));
sink(taintedJoiner.useForNull("(null)").withKeyValueSeparator("=").join(m));
m.put("k2", x);
sink(safeJoiner.withKeyValueSeparator("=").join(m));
}
}

View File

@@ -0,0 +1,17 @@
| Test.java:15:20:15:26 | taint(...) | Test.java:17:14:17:41 | padStart(...) |
| Test.java:15:20:15:26 | taint(...) | Test.java:18:14:18:39 | padEnd(...) |
| Test.java:15:20:15:26 | taint(...) | Test.java:19:14:19:33 | repeat(...) |
| Test.java:15:20:15:26 | taint(...) | Test.java:20:14:20:56 | emptyToNull(...) |
| Test.java:15:20:15:26 | taint(...) | Test.java:21:14:21:40 | lenientFormat(...) |
| Test.java:15:20:15:26 | taint(...) | Test.java:24:14:24:51 | lenientFormat(...) |
| Test.java:28:20:28:26 | taint(...) | Test.java:32:14:32:23 | split(...) |
| Test.java:28:20:28:26 | taint(...) | Test.java:33:14:33:29 | splitToList(...) |
| Test.java:28:20:28:26 | taint(...) | Test.java:35:14:35:50 | split(...) |
| Test.java:39:20:39:26 | taint(...) | Test.java:46:14:46:54 | appendTo(...) |
| Test.java:39:20:39:26 | taint(...) | Test.java:47:14:47:26 | toString(...) |
| Test.java:39:20:39:26 | taint(...) | Test.java:48:14:48:51 | appendTo(...) |
| Test.java:39:20:39:26 | taint(...) | Test.java:49:14:49:26 | toString(...) |
| Test.java:39:20:39:26 | taint(...) | Test.java:52:14:52:42 | appendTo(...) |
| Test.java:39:20:39:26 | taint(...) | Test.java:57:14:57:56 | join(...) |
| Test.java:39:20:39:26 | taint(...) | Test.java:58:14:58:82 | join(...) |
| Test.java:39:20:39:26 | taint(...) | Test.java:60:14:60:58 | join(...) |

View File

@@ -0,0 +1,18 @@
import java
import semmle.code.java.dataflow.TaintTracking
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:frameworks:guava" }
override predicate isSource(DataFlow::Node n) {
n.asExpr().(MethodAccess).getMethod().hasName("taint")
}
override predicate isSink(DataFlow::Node n) {
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
}
}
from DataFlow::Node src, DataFlow::Node sink, Conf conf
where conf.hasFlow(src, sink)
select src, sink

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/guava-30.0

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2008 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.common.base;
import java.util.Iterator;
import java.util.Map;
public class Joiner {
public static Joiner on(String separator) {
return null;
}
public final StringBuilder appendTo(StringBuilder builder, Object first, Object second, Object... rest) {
return null;
}
public final String join(Object first, Object second, Object... rest) {
return null;
}
public Joiner useForNull(final String nullText) {
return null;
}
public Joiner skipNulls() {
return null;
}
public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
return null;
}
public static final class MapJoiner {
public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
return null;
}
public String join(Map<?, ?> map) {
return null;
}
public MapJoiner useForNull(String nullText) {
return null;
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2009 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.common.base;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public final class Splitter {
public static Splitter on(final String separator) {
return null;
}
public Splitter omitEmptyStrings() {
return null;;
}
public Iterable<String> split(final CharSequence sequence) {
return null;
}
public List<String> splitToList(CharSequence sequence) {
return null;
}
public MapSplitter withKeyValueSeparator(String separator) {
return null;
}
public static final class MapSplitter {
public Map<String, String> split(CharSequence sequence) {
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2010 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.common.base;
public final class Strings {
public static String nullToEmpty(String string) {
return null;
}
public static String emptyToNull(String string) {
return null;
}
public static boolean isNullOrEmpty(String string) {
return true;
}
public static String padStart(String string, int minLength, char padChar) {
return null;
}
public static String padEnd(String string, int minLength, char padChar) {
return null;
}
public static String repeat(String string, int count) {
return null;
}
public static String commonPrefix(CharSequence a, CharSequence b) {
return null;
}
public static String commonSuffix(CharSequence a, CharSequence b) {
return null;
}
public static String lenientFormat(String template, Object ... args) {
return null;
}
}