Skip to content

Commit a63f18e

Browse files
committed
Add models for Commons-Lang's StrBuilder class. These exclude its fluent methods for the time being, which will be added in a forthcoming PR.
1 parent 5188ad1 commit a63f18e

File tree

6 files changed

+1238
-0
lines changed

6 files changed

+1238
-0
lines changed

java/ql/src/semmle/code/java/frameworks/apache/Lang.qll

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,90 @@ private class ApacheStringUtilsTaintPreservingMethod extends TaintPreservingCall
118118
not isExcludedParameter(arg)
119119
}
120120
}
121+
122+
/**
123+
* A method declared on `org.apache.commons.lang3.text.StrBuilder`.
124+
*/
125+
abstract class ApacheStrBuilderMethod extends Callable {
126+
ApacheStrBuilderMethod() {
127+
this.getDeclaringType().hasQualifiedName("org.apache.commons.lang3.text", "StrBuilder")
128+
}
129+
}
130+
131+
/**
132+
* An Apache Commons-Lang StrBuilder methods that add taint to the StrBuilder.
133+
*/
134+
private class ApacheStrBuilderTaintingMethod extends ApacheStrBuilderMethod, TaintPreservingCallable {
135+
ApacheStrBuilderTaintingMethod() {
136+
this.hasName([
137+
"append", "appendAll", "appendFixedWidthPadLeft", "appendFixedWidthPadRight", "appendln",
138+
"appendSeparator", "appendWithSeparators", "insert", "readFrom", "replace", "replaceAll",
139+
"replaceFirst"
140+
])
141+
}
142+
143+
private predicate consumesTaintFromAllArgs() {
144+
// Specifically the append[ln](String, Object...) overloads also consume taint from its other arguments:
145+
this.getName() in ["appendAll", "appendWithSeparators"]
146+
or
147+
this.getName() = ["append", "appendln"] and this.getAParameter().isVarargs()
148+
or
149+
this.getName() = "appendSeparator" and this.getParameterType(1) instanceof TypeString
150+
}
151+
152+
override predicate transfersTaint(int fromArg, int toArg) {
153+
// Taint the qualifier
154+
toArg = -1 and
155+
(
156+
this.getName().matches(["append%", "readFrom"]) and fromArg = 0
157+
or
158+
this.getName() = "insert" and fromArg = 1
159+
or
160+
this.getName().matches("replace%") and
161+
(
162+
if this.getParameterType(0).(PrimitiveType).getName() = "int"
163+
then fromArg = 2
164+
else fromArg = 1
165+
)
166+
or
167+
consumesTaintFromAllArgs() and fromArg in [0 .. this.getNumberOfParameters() - 1]
168+
)
169+
}
170+
}
171+
172+
/**
173+
* An Apache Commons-Lang StrBuilder methods that return taint from the StrBuilder.
174+
*/
175+
private class ApacheStrBuilderTaintGetter extends ApacheStrBuilderMethod, TaintPreservingCallable {
176+
ApacheStrBuilderTaintGetter() {
177+
// Taint getters:
178+
this.hasName([
179+
"asReader", "asTokenizer", "build", "getChars", "leftString", "midString", "rightString",
180+
"subSequence", "substring", "toCharArray", "toString", "toStringBuffer", "toStringBuilder"
181+
])
182+
or
183+
// Fluent methods that return an alias of `this`:
184+
this.getReturnType() = this.getDeclaringType()
185+
}
186+
187+
override predicate returnsTaintFrom(int arg) { arg = -1 }
188+
}
189+
190+
/**
191+
* An Apache Commons-Lang StrBuilder methods that write taint from the StrBuilder to some parameter.
192+
*/
193+
private class ApacheStrBuilderTaintWriter extends ApacheStrBuilderMethod, TaintPreservingCallable {
194+
ApacheStrBuilderTaintWriter() { this.hasName(["appendTo", "getChars"]) }
195+
196+
override predicate transfersTaint(int fromArg, int toArg) {
197+
fromArg = -1 and
198+
(
199+
// appendTo(Readable) and getChars(char[])
200+
if this.getNumberOfParameters() = 1
201+
then toArg = 0
202+
else
203+
// getChars(int, int, char[], int)
204+
toArg = 2
205+
)
206+
}
207+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import org.apache.commons.lang3.builder.Builder;
2+
import org.apache.commons.lang3.text.StrBuilder;
3+
import org.apache.commons.lang3.text.StrMatcher;
4+
import org.apache.commons.lang3.text.StrTokenizer;
5+
import java.io.StringReader;
6+
import java.nio.CharBuffer;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Locale;
10+
11+
class StrBuilderTest {
12+
String taint() { return "tainted"; }
13+
14+
void sink(Object o) {}
15+
16+
void test() throws Exception {
17+
18+
StrBuilder sb1 = new StrBuilder(); sb1.append(taint().toCharArray()); sink(sb1.toString()); // $hasTaintFlow=y
19+
StrBuilder sb2 = new StrBuilder(); sb2.append(taint().toCharArray(), 0, 0); sink(sb2.toString()); // $hasTaintFlow=y
20+
StrBuilder sb3 = new StrBuilder(); sb3.append(CharBuffer.wrap(taint().toCharArray())); sink(sb3.toString()); // BAD (but not detected because we don't model CharBuffer yet)
21+
StrBuilder sb4 = new StrBuilder(); sb4.append(CharBuffer.wrap(taint().toCharArray()), 0, 0); sink(sb4.toString()); // BAD (but not detected because we don't model CharBuffer yet)
22+
StrBuilder sb5 = new StrBuilder(); sb5.append((CharSequence)taint()); sink(sb5.toString()); // $hasTaintFlow=y
23+
StrBuilder sb6 = new StrBuilder(); sb6.append((CharSequence)taint(), 0, 0); sink(sb6.toString()); // $hasTaintFlow=y
24+
StrBuilder sb7 = new StrBuilder(); sb7.append((Object)taint()); sink(sb7.toString()); // $hasTaintFlow=y
25+
{
26+
StrBuilder auxsb = new StrBuilder(); auxsb.append(taint());
27+
StrBuilder sb8 = new StrBuilder(); sb8.append(auxsb); sink(sb8.toString()); // $hasTaintFlow=y
28+
}
29+
StrBuilder sb9 = new StrBuilder(); sb9.append(new StringBuffer(taint())); sink(sb9.toString()); // $hasTaintFlow=y
30+
StrBuilder sb10 = new StrBuilder(); sb10.append(new StringBuffer(taint()), 0, 0); sink(sb10.toString()); // $hasTaintFlow=y
31+
StrBuilder sb11 = new StrBuilder(); sb11.append(new StringBuilder(taint())); sink(sb11.toString()); // $hasTaintFlow=y
32+
StrBuilder sb12 = new StrBuilder(); sb12.append(new StringBuilder(taint()), 0, 0); sink(sb12.toString()); // $hasTaintFlow=y
33+
StrBuilder sb13 = new StrBuilder(); sb13.append(taint()); sink(sb13.toString()); // $hasTaintFlow=y
34+
StrBuilder sb14 = new StrBuilder(); sb14.append(taint(), 0, 0); sink(sb14.toString()); // $hasTaintFlow=y
35+
StrBuilder sb15 = new StrBuilder(); sb15.append(taint(), "format", "args"); sink(sb15.toString()); // $hasTaintFlow=y
36+
StrBuilder sb16 = new StrBuilder(); sb16.append("Format string", taint(), "args"); sink(sb16.toString()); // $hasTaintFlow=y
37+
{
38+
List<String> taintedList = new ArrayList<>();
39+
taintedList.add(taint());
40+
StrBuilder sb17 = new StrBuilder(); sb17.appendAll(taintedList); sink(sb17.toString()); // $hasTaintFlow=y
41+
StrBuilder sb18 = new StrBuilder(); sb18.appendAll(taintedList.iterator()); sink(sb18.toString()); // $hasTaintFlow=y
42+
}
43+
StrBuilder sb19 = new StrBuilder(); sb19.appendAll("clean", taint()); sink(sb19.toString()); // $hasTaintFlow=y
44+
StrBuilder sb20 = new StrBuilder(); sb20.appendAll(taint(), "clean"); sink(sb20.toString()); // $hasTaintFlow=y
45+
StrBuilder sb21 = new StrBuilder(); sb21.appendFixedWidthPadLeft(taint(), 0, ' '); sink(sb21.toString()); // $hasTaintFlow=y
46+
StrBuilder sb22 = new StrBuilder(); sb22.appendFixedWidthPadRight(taint(), 0, ' '); sink(sb22.toString()); // $hasTaintFlow=y
47+
StrBuilder sb23 = new StrBuilder(); sb23.appendln(taint().toCharArray()); sink(sb23.toString()); // $hasTaintFlow=y
48+
StrBuilder sb24 = new StrBuilder(); sb24.appendln(taint().toCharArray(), 0, 0); sink(sb24.toString()); // $hasTaintFlow=y
49+
StrBuilder sb25 = new StrBuilder(); sb25.appendln((Object)taint()); sink(sb25.toString()); // $hasTaintFlow=y
50+
{
51+
StrBuilder auxsb = new StrBuilder(); auxsb.appendln(taint());
52+
StrBuilder sb26 = new StrBuilder(); sb26.appendln(auxsb); sink(sb26.toString()); // $hasTaintFlow=y
53+
}
54+
StrBuilder sb27 = new StrBuilder(); sb27.appendln(new StringBuffer(taint())); sink(sb27.toString()); // $hasTaintFlow=y
55+
StrBuilder sb28 = new StrBuilder(); sb28.appendln(new StringBuffer(taint()), 0, 0); sink(sb28.toString()); // $hasTaintFlow=y
56+
StrBuilder sb29 = new StrBuilder(); sb29.appendln(new StringBuilder(taint())); sink(sb29.toString()); // $hasTaintFlow=y
57+
StrBuilder sb30 = new StrBuilder(); sb30.appendln(new StringBuilder(taint()), 0, 0); sink(sb30.toString()); // $hasTaintFlow=y
58+
StrBuilder sb31 = new StrBuilder(); sb31.appendln(taint()); sink(sb31.toString()); // $hasTaintFlow=y
59+
StrBuilder sb32 = new StrBuilder(); sb32.appendln(taint(), 0, 0); sink(sb32.toString()); // $hasTaintFlow=y
60+
StrBuilder sb33 = new StrBuilder(); sb33.appendln(taint(), "format", "args"); sink(sb33.toString()); // $hasTaintFlow=y
61+
StrBuilder sb34 = new StrBuilder(); sb34.appendln("Format string", taint(), "args"); sink(sb34.toString()); // $hasTaintFlow=y
62+
StrBuilder sb35 = new StrBuilder(); sb35.appendSeparator(taint()); sink(sb35.toString()); // $hasTaintFlow=y
63+
StrBuilder sb36 = new StrBuilder(); sb36.appendSeparator(taint(), 0); sink(sb36.toString()); // $hasTaintFlow=y
64+
StrBuilder sb37 = new StrBuilder(); sb37.appendSeparator(taint(), "default"); sink(sb37.toString()); // $hasTaintFlow=y
65+
StrBuilder sb38 = new StrBuilder(); sb38.appendSeparator("", taint()); sink(sb38.toString()); // $hasTaintFlow=y
66+
{
67+
StrBuilder auxsb = new StrBuilder(); auxsb.appendln(taint());
68+
StrBuilder sb39 = new StrBuilder(); auxsb.appendTo(sb39); sink(sb39.toString()); // $hasTaintFlow=y
69+
}
70+
{
71+
List<String> taintedList = new ArrayList<>();
72+
taintedList.add(taint());
73+
StrBuilder sb40 = new StrBuilder(); sb40.appendWithSeparators(taintedList, ", "); sink(sb40.toString()); // $hasTaintFlow=y
74+
StrBuilder sb41 = new StrBuilder(); sb41.appendWithSeparators(taintedList.iterator(), ", "); sink(sb41.toString()); // $hasTaintFlow=y
75+
List<String> untaintedList = new ArrayList<>();
76+
StrBuilder sb42 = new StrBuilder(); sb42.appendWithSeparators(untaintedList, taint()); sink(sb42.toString()); // $hasTaintFlow=y
77+
StrBuilder sb43 = new StrBuilder(); sb43.appendWithSeparators(untaintedList.iterator(), taint()); sink(sb43.toString()); // $hasTaintFlow=y
78+
String[] taintedArray = new String[] { taint() };
79+
String[] untaintedArray = new String[] {};
80+
StrBuilder sb44 = new StrBuilder(); sb44.appendWithSeparators(taintedArray, ", "); sink(sb44.toString()); // $hasTaintFlow=y
81+
StrBuilder sb45 = new StrBuilder(); sb45.appendWithSeparators(untaintedArray, taint()); sink(sb45.toString()); // $hasTaintFlow=y
82+
}
83+
{
84+
StrBuilder sb46 = new StrBuilder(); sb46.append(taint());
85+
char[] target = new char[100];
86+
sb46.asReader().read(target);
87+
sink(target); // $hasTaintFlow=y
88+
}
89+
StrBuilder sb47 = new StrBuilder(); sb47.append(taint()); sink(sb47.asTokenizer().next()); // $hasTaintFlow=y
90+
StrBuilder sb48 = new StrBuilder(); sb48.append(taint()); sink(sb48.build()); // $hasTaintFlow=y
91+
StrBuilder sb49 = new StrBuilder(); sb49.append(taint()); sink(sb49.getChars(null)); // $hasTaintFlow=y
92+
{
93+
StrBuilder sb50 = new StrBuilder(); sb50.append(taint());
94+
char[] target = new char[100];
95+
sb50.getChars(target);
96+
sink(target); // $hasTaintFlow=y
97+
}
98+
{
99+
StrBuilder sb51 = new StrBuilder(); sb51.append(taint());
100+
char[] target = new char[100];
101+
sb51.getChars(0, 0, target, 0);
102+
sink(target); // $hasTaintFlow=y
103+
}
104+
StrBuilder sb52 = new StrBuilder(); sb52.insert(0, taint().toCharArray()); sink(sb52.toString()); // $hasTaintFlow=y
105+
StrBuilder sb53 = new StrBuilder(); sb53.insert(0, taint().toCharArray(), 0, 0); sink(sb53.toString()); // $hasTaintFlow=y
106+
StrBuilder sb54 = new StrBuilder(); sb54.insert(0, taint()); sink(sb54.toString()); // $hasTaintFlow=y
107+
StrBuilder sb55 = new StrBuilder(); sb55.insert(0, (Object)taint()); sink(sb55.toString()); // $hasTaintFlow=y
108+
StrBuilder sb56 = new StrBuilder(); sb56.append(taint()); sink(sb56.leftString(0)); // $hasTaintFlow=y
109+
StrBuilder sb57 = new StrBuilder(); sb57.append(taint()); sink(sb57.midString(0, 0)); // $hasTaintFlow=y
110+
{
111+
StringReader reader = new StringReader(taint());
112+
StrBuilder sb58 = new StrBuilder(); sb58.readFrom(reader); sink(sb58.toString()); // $hasTaintFlow=y
113+
}
114+
StrBuilder sb59 = new StrBuilder(); sb59.replace(0, 0, taint()); sink(sb59.toString()); // $hasTaintFlow=y
115+
StrBuilder sb60 = new StrBuilder(); sb60.replace(null, taint(), 0, 0, 0); sink(sb60.toString()); // $hasTaintFlow=y
116+
StrBuilder sb61 = new StrBuilder(); sb61.replaceAll((StrMatcher)null, taint()); sink(sb61.toString()); // $hasTaintFlow=y
117+
StrBuilder sb62 = new StrBuilder(); sb62.replaceAll("search", taint()); sink(sb62.toString()); // $hasTaintFlow=y
118+
StrBuilder sb63 = new StrBuilder(); sb63.replaceAll(taint(), "replace"); sink(sb63.toString()); // GOOD (search string doesn't convey taint)
119+
StrBuilder sb64 = new StrBuilder(); sb64.replaceFirst((StrMatcher)null, taint()); sink(sb64.toString()); // $hasTaintFlow=y
120+
StrBuilder sb65 = new StrBuilder(); sb65.replaceFirst("search", taint()); sink(sb65.toString()); // $hasTaintFlow=y
121+
StrBuilder sb66 = new StrBuilder(); sb66.replaceFirst(taint(), "replace"); sink(sb66.toString()); // GOOD (search string doesn't convey taint)
122+
StrBuilder sb67 = new StrBuilder(); sb67.append(taint()); sink(sb67.rightString(0)); // $hasTaintFlow=y
123+
StrBuilder sb68 = new StrBuilder(); sb68.append(taint()); sink(sb68.subSequence(0, 0)); // $hasTaintFlow=y
124+
StrBuilder sb69 = new StrBuilder(); sb69.append(taint()); sink(sb69.substring(0)); // $hasTaintFlow=y
125+
StrBuilder sb70 = new StrBuilder(); sb70.append(taint()); sink(sb70.substring(0, 0)); // $hasTaintFlow=y
126+
StrBuilder sb71 = new StrBuilder(); sb71.append(taint()); sink(sb71.toCharArray()); // $hasTaintFlow=y
127+
StrBuilder sb72 = new StrBuilder(); sb72.append(taint()); sink(sb72.toCharArray(0, 0)); // $hasTaintFlow=y
128+
StrBuilder sb73 = new StrBuilder(); sb73.append(taint()); sink(sb73.toStringBuffer()); // $hasTaintFlow=y
129+
StrBuilder sb74 = new StrBuilder(); sb74.append(taint()); sink(sb74.toStringBuilder()); // $hasTaintFlow=y
130+
}
131+
132+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.lang3.builder;
18+
19+
/**
20+
* <p>
21+
* The Builder interface is designed to designate a class as a <em>builder</em>
22+
* object in the Builder design pattern. Builders are capable of creating and
23+
* configuring objects or results that normally take multiple steps to construct
24+
* or are very complex to derive.
25+
* </p>
26+
*
27+
* <p>
28+
* The builder interface defines a single method, {@link #build()}, that
29+
* classes must implement. The result of this method should be the final
30+
* configured object or result after all building operations are performed.
31+
* </p>
32+
*
33+
* <p>
34+
* It is a recommended practice that the methods supplied to configure the
35+
* object or result being built return a reference to {@code this} so that
36+
* method calls can be chained together.
37+
* </p>
38+
*
39+
* <p>
40+
* Example Builder:
41+
* <pre><code>
42+
* class FontBuilder implements Builder&lt;Font&gt; {
43+
* private Font font;
44+
*
45+
* public FontBuilder(String fontName) {
46+
* this.font = new Font(fontName, Font.PLAIN, 12);
47+
* }
48+
*
49+
* public FontBuilder bold() {
50+
* this.font = this.font.deriveFont(Font.BOLD);
51+
* return this; // Reference returned so calls can be chained
52+
* }
53+
*
54+
* public FontBuilder size(float pointSize) {
55+
* this.font = this.font.deriveFont(pointSize);
56+
* return this; // Reference returned so calls can be chained
57+
* }
58+
*
59+
* // Other Font construction methods
60+
*
61+
* public Font build() {
62+
* return this.font;
63+
* }
64+
* }
65+
* </code></pre>
66+
*
67+
* Example Builder Usage:
68+
* <pre><code>
69+
* Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
70+
* .size(14.0f)
71+
* .build();
72+
* </code></pre>
73+
*
74+
*
75+
* @param <T> the type of object that the builder will construct or compute.
76+
*
77+
* @since 3.0
78+
*/
79+
public interface Builder<T> {
80+
T build();
81+
82+
}

0 commit comments

Comments
 (0)