Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2050f82

Browse files
Merge pull request #4383 from joefarebrother/guava-strings
Java: Add modelling for Guava
2 parents 492b114 + 980fdd8 commit 2050f82

12 files changed

Lines changed: 445 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* 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.

java/ql/src/semmle/code/java/dataflow/FlowSteps.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module Frameworks {
1515
private import semmle.code.java.frameworks.android.SQLite
1616
private import semmle.code.java.frameworks.Guice
1717
private import semmle.code.java.frameworks.Protobuf
18+
private import semmle.code.java.frameworks.guava.Guava
1819
}
1920

2021
/**

java/ql/src/semmle/code/java/dataflow/NullGuards.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ Expr clearlyNotNullExpr(Expr reason) {
7979
)
8080
or
8181
exists(SsaVariable v | clearlyNotNull(v, reason) and result = v.getAUse())
82+
or
83+
exists(Method m | m = result.(MethodAccess).getMethod() and reason = result |
84+
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Strings") and
85+
m.hasName("nullToEmpty")
86+
)
8287
}
8388

8489
/** Holds if `v` is an SSA variable that is provably not `null`. */
@@ -146,6 +151,11 @@ predicate nullCheckMethod(Method m, boolean branch, boolean isnull) {
146151
m.hasName("isNotEmpty") and
147152
branch = true and
148153
isnull = false
154+
or
155+
m.getDeclaringType().hasQualifiedName("com.google.common.base", "Strings") and
156+
m.hasName("isNullOrEmpty") and
157+
branch = false and
158+
isnull = false
149159
}
150160

151161
/**
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Definitions for tracking taint steps through the Guava framework.
3+
*/
4+
5+
import java
6+
import StringUtils
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/** Definitions of flow steps through the various string utility functions in the Guava framework. */
2+
3+
import java
4+
private import semmle.code.java.dataflow.FlowSteps
5+
6+
/**
7+
* The class `com.google.common.base.Strings`.
8+
*/
9+
class TypeGuavaStrings extends Class {
10+
TypeGuavaStrings() { this.hasQualifiedName("com.google.common.base", "Strings") }
11+
}
12+
13+
/**
14+
* The class `com.google.common.base.Joiner`.
15+
*/
16+
class TypeGuavaJoiner extends Class {
17+
TypeGuavaJoiner() { this.hasQualifiedName("com.google.common.base", "Joiner") }
18+
}
19+
20+
/**
21+
* The nested class `Joiner.MapJoiner`.
22+
*/
23+
class TypeGuavaMapJoiner extends NestedClass {
24+
TypeGuavaMapJoiner() {
25+
this.getEnclosingType() instanceof TypeGuavaJoiner and
26+
this.hasName("MapJoiner")
27+
}
28+
}
29+
30+
/**
31+
* The class `com.google.common.base.Splitter`.
32+
*/
33+
class TypeGuavaSplitter extends Class {
34+
TypeGuavaSplitter() { this.hasQualifiedName("com.google.common.base", "Splitter") }
35+
}
36+
37+
/**
38+
* The nested class `Splitter.MapSplitter`.
39+
*/
40+
class TypeGuavaMapSplitter extends NestedClass {
41+
TypeGuavaMapSplitter() {
42+
this.getEnclosingType() instanceof TypeGuavaSplitter and
43+
this.hasName("MapSplitter")
44+
}
45+
}
46+
47+
/**
48+
* A taint preserving method on `com.google.common.base.Strings`.
49+
*/
50+
private class GuavaStringsTaintPreservingMethod extends TaintPreservingCallable {
51+
GuavaStringsTaintPreservingMethod() {
52+
this.getDeclaringType() instanceof TypeGuavaStrings and
53+
// static String emptyToNull(String string)
54+
// static String nullToEmpty(String string)
55+
// static String padStart(String string, int minLength, char padChar)
56+
// static String padEnd(String string, int minLength, char padChar)
57+
// static String repeat(String string, int count)
58+
// static String lenientFormat(String template, Object ... args)
59+
this.hasName(["emptyToNull", "nullToEmpty", "padStart", "padEnd", "repeat", "lenientFormat"])
60+
}
61+
62+
override predicate returnsTaintFrom(int src) {
63+
src = 0
64+
or
65+
this.hasName("lenientFormat") and
66+
src = [0 .. getNumberOfParameters()]
67+
}
68+
}
69+
70+
/**
71+
* A method of `Joiner` or `MapJoiner`.
72+
*/
73+
private class GuavaJoinerMethod extends Method {
74+
GuavaJoinerMethod() {
75+
this.getDeclaringType().getASourceSupertype*() instanceof TypeGuavaJoiner or
76+
this.getDeclaringType().getASourceSupertype*() instanceof TypeGuavaMapJoiner
77+
}
78+
}
79+
80+
/**
81+
* A method that builds a `Joiner` or `MapJoiner`.
82+
*/
83+
private class GuavaJoinerBuilderMethod extends GuavaJoinerMethod, TaintPreservingCallable {
84+
GuavaJoinerBuilderMethod() {
85+
// static Joiner on(char separator)
86+
// static Joiner on(String separator)
87+
// Joiner skipNulls()
88+
// Joiner useForNull(String nullText)
89+
// Joiner.MapJoiner withKeyValueSeparator(char keyValueSeparator)
90+
// Joiner.MapJoiner withKeyValueSeparator(String keyValueSeparator)
91+
// Joiner.MapJoiner useForNull(String nullText) [on MapJoiner]
92+
this.hasName(["on", "skipNulls", "useForNull", "withKeyValueSeparator"])
93+
}
94+
95+
override predicate returnsTaintFrom(int src) {
96+
src = 0
97+
or
98+
src = -1 and not isStatic()
99+
}
100+
}
101+
102+
/**
103+
* An `appendTo` method on `Joiner` or `MapJoiner`.
104+
*/
105+
private class GuavaJoinerAppendToMethod extends GuavaJoinerMethod, TaintPreservingCallable {
106+
GuavaJoinerAppendToMethod() {
107+
// <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)
108+
// <A extends Appendable> A appendTo(A appendable, Iterator<?> parts)
109+
// <A extends Appendable> A appendTo(A appendable, Object[] parts)
110+
// <A extends Appendable> A appendTo(A appendable, Object first, Object second, Object... rest)
111+
// StringBuilder appendTo(StringBuilder builder, Iterable<?> parts)
112+
// StringBuilder appendTo(StringBuilder builder, Iterator<?> parts)
113+
// StringBuilder appendTo(StringBuilder builder, Object[] parts)
114+
// StringBuilder appendTo(StringBuilder builder, Object first, Object second, Object... rest)
115+
// <A extends Appendable> A appendTo(A appendable, Iterable<? extends Map.Entry<?,?>> entries) [on MapJoiner]
116+
// <A extends Appendable> A appendTo(A appendable, Iterator<? extends Map.Entry<?,?>> parts)
117+
// <A extends Appendable> A appendTo(A appendable, Map<?,?> map)
118+
// StringBuilder appendTo(StringBuilder builder, Iterable<? extends Map.Entry<?,?>> entries)
119+
// StringBuilder appendTo(StringBuilder builder, Iterator<? extends Map.Entry<?,?>> entries)
120+
// StringBuilder appendTo(StringBuilder builder, Map<?,?> map)
121+
this.hasName("appendTo")
122+
}
123+
124+
override predicate transfersTaint(int src, int sink) {
125+
src = [-1 .. getNumberOfParameters()] and
126+
src != sink and
127+
sink = 0
128+
}
129+
130+
override predicate returnsTaintFrom(int src) { src = [-1 .. getNumberOfParameters()] }
131+
}
132+
133+
/**
134+
* A `join` method on `Joiner` or `MapJoiner`.
135+
*/
136+
private class GuavaJoinMethod extends GuavaJoinerMethod, TaintPreservingCallable {
137+
GuavaJoinMethod() {
138+
// String join(Iterable<?> parts)
139+
// String join(Iterator<?> parts)
140+
// String join(Object[] parts)
141+
// String join(Object first, Object second, Object... rest)
142+
// String join(Iterable<? extends Map.Entry<?,?>> entries) [on MapJoiner]
143+
// String join(Iterator<? extends Map.Entry<?,?>> entries)
144+
// String join(Map<?,?> map)
145+
this.hasName("join")
146+
}
147+
148+
override predicate returnsTaintFrom(int src) { src = [-1 .. getNumberOfParameters()] }
149+
}
150+
151+
/**
152+
* A method of `Splitter` or `MapSplitter` that splits its input string.
153+
*/
154+
private class GuavaSplitMethod extends TaintPreservingCallable {
155+
GuavaSplitMethod() {
156+
(
157+
this.getDeclaringType() instanceof TypeGuavaSplitter
158+
or
159+
this.getDeclaringType() instanceof TypeGuavaMapSplitter
160+
) and
161+
// Iterable<String> split(CharSequence sequence)
162+
// List<String> splitToList(CharSequence sequence)
163+
// Stream<String> splitToStream(CharSequence sequence)
164+
// Map<String,String> split(CharSequence sequence) [on MapSplitter]
165+
this.hasName(["split", "splitToList", "splitToStream"])
166+
}
167+
168+
override predicate returnsTaintFrom(int src) { src = 0 }
169+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
import com.google.common.base.Strings;
3+
import com.google.common.base.Splitter;
4+
import com.google.common.base.Joiner;
5+
6+
import java.util.Map;
7+
import java.util.HashMap;
8+
9+
class Test {
10+
String taint() { return "tainted"; }
11+
12+
void sink(Object o) {}
13+
14+
void test1() {
15+
String x = taint();
16+
17+
sink(Strings.padStart(x, 10, ' '));
18+
sink(Strings.padEnd(x, 10, ' '));
19+
sink(Strings.repeat(x, 3));
20+
sink(Strings.emptyToNull(Strings.nullToEmpty(x)));
21+
sink(Strings.lenientFormat(x, 3));
22+
sink(Strings.commonPrefix(x, "abc"));
23+
sink(Strings.commonSuffix(x, "cde"));
24+
sink(Strings.lenientFormat("%s = %s", x, 3));
25+
}
26+
27+
void test2() {
28+
String x = taint();
29+
Splitter s = Splitter.on(x).omitEmptyStrings();
30+
31+
sink(s.split("x y z"));
32+
sink(s.split(x));
33+
sink(s.splitToList(x));
34+
sink(s.withKeyValueSeparator("=").split("a=b"));
35+
sink(s.withKeyValueSeparator("=").split(x));
36+
}
37+
38+
void test3() {
39+
String x = taint();
40+
Joiner taintedJoiner = Joiner.on(x);
41+
Joiner safeJoiner = Joiner.on(", ");
42+
43+
StringBuilder sb = new StringBuilder();
44+
sink(safeJoiner.appendTo(sb, "a", "b", "c"));
45+
sink(sb.toString());
46+
sink(taintedJoiner.appendTo(sb, "a", "b", "c"));
47+
sink(sb.toString());
48+
sink(safeJoiner.appendTo(sb, "a", "b", "c"));
49+
sink(sb.toString());
50+
51+
sb = new StringBuilder();
52+
sink(safeJoiner.appendTo(sb, x, x));
53+
54+
Map<String, String> m = new HashMap<String, String>();
55+
m.put("k", "v");
56+
sink(safeJoiner.withKeyValueSeparator("=").join(m));
57+
sink(safeJoiner.withKeyValueSeparator(x).join(m));
58+
sink(taintedJoiner.useForNull("(null)").withKeyValueSeparator("=").join(m));
59+
m.put("k2", x);
60+
sink(safeJoiner.withKeyValueSeparator("=").join(m));
61+
}
62+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
| Test.java:15:20:15:26 | taint(...) | Test.java:17:14:17:41 | padStart(...) |
2+
| Test.java:15:20:15:26 | taint(...) | Test.java:18:14:18:39 | padEnd(...) |
3+
| Test.java:15:20:15:26 | taint(...) | Test.java:19:14:19:33 | repeat(...) |
4+
| Test.java:15:20:15:26 | taint(...) | Test.java:20:14:20:56 | emptyToNull(...) |
5+
| Test.java:15:20:15:26 | taint(...) | Test.java:21:14:21:40 | lenientFormat(...) |
6+
| Test.java:15:20:15:26 | taint(...) | Test.java:24:14:24:51 | lenientFormat(...) |
7+
| Test.java:28:20:28:26 | taint(...) | Test.java:32:14:32:23 | split(...) |
8+
| Test.java:28:20:28:26 | taint(...) | Test.java:33:14:33:29 | splitToList(...) |
9+
| Test.java:28:20:28:26 | taint(...) | Test.java:35:14:35:50 | split(...) |
10+
| Test.java:39:20:39:26 | taint(...) | Test.java:46:14:46:54 | appendTo(...) |
11+
| Test.java:39:20:39:26 | taint(...) | Test.java:47:14:47:26 | toString(...) |
12+
| Test.java:39:20:39:26 | taint(...) | Test.java:48:14:48:51 | appendTo(...) |
13+
| Test.java:39:20:39:26 | taint(...) | Test.java:49:14:49:26 | toString(...) |
14+
| Test.java:39:20:39:26 | taint(...) | Test.java:52:14:52:42 | appendTo(...) |
15+
| Test.java:39:20:39:26 | taint(...) | Test.java:57:14:57:56 | join(...) |
16+
| Test.java:39:20:39:26 | taint(...) | Test.java:58:14:58:82 | join(...) |
17+
| Test.java:39:20:39:26 | taint(...) | Test.java:60:14:60:58 | join(...) |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import java
2+
import semmle.code.java.dataflow.TaintTracking
3+
4+
class Conf extends TaintTracking::Configuration {
5+
Conf() { this = "qltest:frameworks:guava" }
6+
7+
override predicate isSource(DataFlow::Node n) {
8+
n.asExpr().(MethodAccess).getMethod().hasName("taint")
9+
}
10+
11+
override predicate isSink(DataFlow::Node n) {
12+
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
13+
}
14+
}
15+
16+
from DataFlow::Node src, DataFlow::Node sink, Conf conf
17+
where conf.hasFlow(src, sink)
18+
select src, sink
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/guava-30.0
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (C) 2008 The Guava Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.common.base;
16+
17+
import java.util.Iterator;
18+
import java.util.Map;
19+
20+
public class Joiner {
21+
public static Joiner on(String separator) {
22+
return null;
23+
}
24+
25+
public final StringBuilder appendTo(StringBuilder builder, Object first, Object second, Object... rest) {
26+
return null;
27+
}
28+
29+
public final String join(Object first, Object second, Object... rest) {
30+
return null;
31+
}
32+
33+
public Joiner useForNull(final String nullText) {
34+
return null;
35+
}
36+
37+
public Joiner skipNulls() {
38+
return null;
39+
}
40+
41+
public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
42+
return null;
43+
}
44+
45+
public static final class MapJoiner {
46+
public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
47+
return null;
48+
}
49+
50+
public String join(Map<?, ?> map) {
51+
return null;
52+
}
53+
54+
public MapJoiner useForNull(String nullText) {
55+
return null;
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)