mirror of
https://github.com/github/codeql.git
synced 2026-05-01 11:45:14 +02:00
Merge pull request #4383 from joefarebrother/guava-strings
Java: Add modelling for Guava
This commit is contained in:
2
java/change-notes/2020-10-16-guava-flow-steps.md
Normal file
2
java/change-notes/2020-10-16-guava-flow-steps.md
Normal 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.
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
6
java/ql/src/semmle/code/java/frameworks/guava/Guava.qll
Normal file
6
java/ql/src/semmle/code/java/frameworks/guava/Guava.qll
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Definitions for tracking taint steps through the Guava framework.
|
||||
*/
|
||||
|
||||
import java
|
||||
import StringUtils
|
||||
169
java/ql/src/semmle/code/java/frameworks/guava/StringUtils.qll
Normal file
169
java/ql/src/semmle/code/java/frameworks/guava/StringUtils.qll
Normal 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 }
|
||||
}
|
||||
62
java/ql/test/library-tests/frameworks/guava/Test.java
Normal file
62
java/ql/test/library-tests/frameworks/guava/Test.java
Normal 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));
|
||||
}
|
||||
}
|
||||
17
java/ql/test/library-tests/frameworks/guava/flow.expected
Normal file
17
java/ql/test/library-tests/frameworks/guava/flow.expected
Normal 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(...) |
|
||||
18
java/ql/test/library-tests/frameworks/guava/flow.ql
Normal file
18
java/ql/test/library-tests/frameworks/guava/flow.ql
Normal 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
|
||||
1
java/ql/test/library-tests/frameworks/guava/options
Normal file
1
java/ql/test/library-tests/frameworks/guava/options
Normal file
@@ -0,0 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/guava-30.0
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user