From 1580d23b2b4668d2f3fdf2ab0f5143c3f6ef8e13 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 17 Feb 2021 09:33:10 +0000 Subject: [PATCH 1/8] Add models for WordUtils and StrTokenizer Both of these have commons-text and commons-lang variants. --- .../code/java/frameworks/apache/Lang.qll | 55 +++++++++++ .../StrTokenizerTest.java | 46 +++++++++ .../StrTokenizerTextTest.java | 46 +++++++++ .../StringTokenizerTest.java | 46 +++++++++ .../apache-commons-lang3/WordUtilsTest.java | 26 +++++ .../WordUtilsTextTest.java | 28 ++++++ .../commons/lang3/text/StrTokenizer.java | 1 + .../apache/commons/lang3/text/WordUtils.java | 79 +++++++++++++++ .../org/apache/commons/text/StrTokenizer.java | 4 + .../org/apache/commons/text/WordUtils.java | 98 +++++++++++++++++++ 10 files changed, 429 insertions(+) create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/StrTokenizerTest.java create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/StrTokenizerTextTest.java create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/StringTokenizerTest.java create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/WordUtilsTest.java create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/WordUtilsTextTest.java create mode 100644 java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/WordUtils.java create mode 100644 java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/WordUtils.java diff --git a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll index e7383affae33..74c12d0e2f80 100644 --- a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll +++ b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll @@ -211,3 +211,58 @@ private class ApacheStrBuilderTaintWriter extends ApacheStrBuilderCallable, Tain toArg = 2 } } + +private class ApacheWordUtilsTaintPreservingMethod extends TaintPreservingCallable { + ApacheWordUtilsTaintPreservingMethod() { + this.getDeclaringType() + .hasQualifiedName(["org.apache.commons.lang3.text", "org.apache.commons.text"], "WordUtils") and + this.getReturnType() instanceof TypeString + } + + override predicate returnsTaintFrom(int arg) { + this.getParameterType(arg) instanceof TypeString and + arg != 4 // Exclude the wrapOn parameter from `wrap(String, int, String, boolean, String)` + } +} + +private class ApacheStrTokenizer extends RefType { + ApacheStrTokenizer() { + this.hasQualifiedName(["org.apache.commons.lang3.text", "org.apache.commons.text"], + "StrTokenizer") or + this.hasQualifiedName("org.apache.commons.text", "StringTokenizer") + } +} + +/** + * A callable that sets the string to be tokenized by an Apache Commons `Str[ing]Tokenizer`. + * + * Note `reset` is an instance method that taints an existing instance; all others return a fresh instance. + */ +private class ApacheStrTokenizerTaintingMethod extends TaintPreservingCallable { + ApacheStrTokenizerTaintingMethod() { + this.getDeclaringType() instanceof ApacheStrTokenizer and + ( + this instanceof Constructor or + this.getName() in ["getCSVInstance", "getTSVInstance", "reset"] + ) + } + + override predicate returnsTaintFrom(int arg) { arg = 0 } + + override predicate transfersTaint(int fromArg, int toArg) { + this.getName() = "reset" and + returnsTaintFrom(fromArg) and + toArg = -1 + } +} + +private class ApacheStrTokenizerTaintGetter extends TaintPreservingCallable { + ApacheStrTokenizerTaintGetter() { + this.getDeclaringType() instanceof ApacheStrTokenizer and + this.getName() in [ + "getContent", "getTokenArray", "getTokenList", "nextToken", "previousToken", "toString" + ] + } + + override predicate returnsTaintFrom(int arg) { arg = -1 } +} diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrTokenizerTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrTokenizerTest.java new file mode 100644 index 000000000000..16e8e6d8e22f --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrTokenizerTest.java @@ -0,0 +1,46 @@ +import org.apache.commons.lang3.text.StrTokenizer; +import org.apache.commons.lang3.text.StrMatcher; + +public class StrTokenizerTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + + // Test constructors: + sink((new StrTokenizer(taint().toCharArray())).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), ',')).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), ',', '"')).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), ",")).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), (StrMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), (StrMatcher)null, (StrMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), ',')).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), ',', '"')).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), ",")).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), (StrMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), (StrMatcher)null, (StrMatcher)null)).toString()); // $hasTaintFlow=y + + // Test constructing static methods: + sink(StrTokenizer.getCSVInstance(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink(StrTokenizer.getCSVInstance(taint()).toString()); // $hasTaintFlow=y + sink(StrTokenizer.getTSVInstance(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink(StrTokenizer.getTSVInstance(taint()).toString()); // $hasTaintFlow=y + + // Test accessors: + sink((new StrTokenizer(taint())).clone()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).getContent()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).getTokenArray()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).getTokenList()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).next()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).nextToken()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).previous()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).previousToken()); // $hasTaintFlow=y + + // Test mutators: + sink((new StrTokenizer()).reset(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink((new StrTokenizer()).reset(taint()).toString()); // $hasTaintFlow=y + + } +} \ No newline at end of file diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrTokenizerTextTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrTokenizerTextTest.java new file mode 100644 index 000000000000..55acb1e26043 --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrTokenizerTextTest.java @@ -0,0 +1,46 @@ +import org.apache.commons.text.StrTokenizer; +import org.apache.commons.text.StrMatcher; + +public class StrTokenizerTextTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + + // Test constructors: + sink((new StrTokenizer(taint().toCharArray())).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), ',')).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), ',', '"')).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), ",")).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), (StrMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint().toCharArray(), (StrMatcher)null, (StrMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), ',')).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), ',', '"')).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), ",")).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), (StrMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StrTokenizer(taint(), (StrMatcher)null, (StrMatcher)null)).toString()); // $hasTaintFlow=y + + // Test constructing static methods: + sink(StrTokenizer.getCSVInstance(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink(StrTokenizer.getCSVInstance(taint()).toString()); // $hasTaintFlow=y + sink(StrTokenizer.getTSVInstance(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink(StrTokenizer.getTSVInstance(taint()).toString()); // $hasTaintFlow=y + + // Test accessors: + sink((new StrTokenizer(taint())).clone()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).getContent()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).getTokenArray()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).getTokenList()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).next()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).nextToken()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).previous()); // $hasTaintFlow=y + sink((new StrTokenizer(taint())).previousToken()); // $hasTaintFlow=y + + // Test mutators: + sink((new StrTokenizer()).reset(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink((new StrTokenizer()).reset(taint()).toString()); // $hasTaintFlow=y + + } +} \ No newline at end of file diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringTokenizerTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringTokenizerTest.java new file mode 100644 index 000000000000..6c38fbfc3cdd --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringTokenizerTest.java @@ -0,0 +1,46 @@ +import org.apache.commons.text.StringTokenizer; +import org.apache.commons.text.matcher.StringMatcher; + +public class StringTokenizerTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + + // Test constructors: + sink((new StringTokenizer(taint().toCharArray())).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint().toCharArray(), ',')).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint().toCharArray(), ',', '"')).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint().toCharArray(), ",")).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint().toCharArray(), (StringMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint().toCharArray(), (StringMatcher)null, (StringMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint())).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint(), ',')).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint(), ',', '"')).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint(), ",")).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint(), (StringMatcher)null)).toString()); // $hasTaintFlow=y + sink((new StringTokenizer(taint(), (StringMatcher)null, (StringMatcher)null)).toString()); // $hasTaintFlow=y + + // Test constructing static methods: + sink(StringTokenizer.getCSVInstance(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink(StringTokenizer.getCSVInstance(taint()).toString()); // $hasTaintFlow=y + sink(StringTokenizer.getTSVInstance(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink(StringTokenizer.getTSVInstance(taint()).toString()); // $hasTaintFlow=y + + // Test accessors: + sink((new StringTokenizer(taint())).clone()); // $hasTaintFlow=y + sink((new StringTokenizer(taint())).getContent()); // $hasTaintFlow=y + sink((new StringTokenizer(taint())).getTokenArray()); // $hasTaintFlow=y + sink((new StringTokenizer(taint())).getTokenList()); // $hasTaintFlow=y + sink((new StringTokenizer(taint())).next()); // $hasTaintFlow=y + sink((new StringTokenizer(taint())).nextToken()); // $hasTaintFlow=y + sink((new StringTokenizer(taint())).previous()); // $hasTaintFlow=y + sink((new StringTokenizer(taint())).previousToken()); // $hasTaintFlow=y + + // Test mutators: + sink((new StringTokenizer()).reset(taint().toCharArray()).toString()); // $hasTaintFlow=y + sink((new StringTokenizer()).reset(taint()).toString()); // $hasTaintFlow=y + + } +} \ No newline at end of file diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/WordUtilsTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/WordUtilsTest.java new file mode 100644 index 000000000000..ea22802d0b19 --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/WordUtilsTest.java @@ -0,0 +1,26 @@ +import org.apache.commons.lang3.text.WordUtils; + +public class WordUtilsTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + sink(WordUtils.capitalize(taint())); // $hasTaintFlow=y + sink(WordUtils.capitalize(taint(), ' ', ',')); // $hasTaintFlow=y + sink(WordUtils.capitalizeFully(taint())); // $hasTaintFlow=y + sink(WordUtils.capitalizeFully(taint(), ' ', ',')); // $hasTaintFlow=y + sink(WordUtils.initials(taint())); // $hasTaintFlow=y + sink(WordUtils.initials(taint(), ' ', ',')); // $hasTaintFlow=y + sink(WordUtils.swapCase(taint())); // $hasTaintFlow=y + sink(WordUtils.uncapitalize(taint())); // $hasTaintFlow=y + sink(WordUtils.uncapitalize(taint(), ' ', ',')); // $hasTaintFlow=y + sink(WordUtils.wrap(taint(), 0)); // $hasTaintFlow=y + sink(WordUtils.wrap(taint(), 0, "\n", false)); // $hasTaintFlow=y + sink(WordUtils.wrap("wrap me", 0, taint(), false)); // $hasTaintFlow=y + sink(WordUtils.wrap(taint(), 0, "\n", false, "\n")); // $hasTaintFlow=y + sink(WordUtils.wrap("wrap me", 0, taint(), false, "\n")); // $hasTaintFlow=y + // GOOD: the wrap-on line terminator does not propagate to the return value + sink(WordUtils.wrap("wrap me", 0, "\n", false, taint())); + } +} \ No newline at end of file diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/WordUtilsTextTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/WordUtilsTextTest.java new file mode 100644 index 000000000000..40d0cdc36276 --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/WordUtilsTextTest.java @@ -0,0 +1,28 @@ +import org.apache.commons.text.WordUtils; + +public class WordUtilsTextTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + sink(WordUtils.abbreviate(taint(), 0, 0, "append me")); // $hasTaintFlow=y + sink(WordUtils.abbreviate("abbreviate me", 0, 0, taint())); // $hasTaintFlow=y + sink(WordUtils.capitalize(taint())); // $hasTaintFlow=y + sink(WordUtils.capitalize(taint(), ' ', ',')); // $hasTaintFlow=y + sink(WordUtils.capitalizeFully(taint())); // $hasTaintFlow=y + sink(WordUtils.capitalizeFully(taint(), ' ', ',')); // $hasTaintFlow=y + sink(WordUtils.initials(taint())); // $hasTaintFlow=y + sink(WordUtils.initials(taint(), ' ', ',')); // $hasTaintFlow=y + sink(WordUtils.swapCase(taint())); // $hasTaintFlow=y + sink(WordUtils.uncapitalize(taint())); // $hasTaintFlow=y + sink(WordUtils.uncapitalize(taint(), ' ', ',')); // $hasTaintFlow=y + sink(WordUtils.wrap(taint(), 0)); // $hasTaintFlow=y + sink(WordUtils.wrap(taint(), 0, "\n", false)); // $hasTaintFlow=y + sink(WordUtils.wrap("wrap me", 0, taint(), false)); // $hasTaintFlow=y + sink(WordUtils.wrap(taint(), 0, "\n", false, "\n")); // $hasTaintFlow=y + sink(WordUtils.wrap("wrap me", 0, taint(), false, "\n")); // $hasTaintFlow=y + // GOOD: the wrap-on line terminator does not propagate to the return value + sink(WordUtils.wrap("wrap me", 0, "\n", false, taint())); + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrTokenizer.java b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrTokenizer.java index c558b35dd540..9b8d45f91a0c 100644 --- a/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrTokenizer.java +++ b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrTokenizer.java @@ -17,6 +17,7 @@ package org.apache.commons.lang3.text; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.ListIterator; diff --git a/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/WordUtils.java b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/WordUtils.java new file mode 100644 index 000000000000..cf2487d339ce --- /dev/null +++ b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/WordUtils.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.lang3.text; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class WordUtils { + public WordUtils() { + } + + public static String wrap(final String str, final int wrapLength) { + return null; + } + + public static String wrap(final String str, final int wrapLength, final String newLineStr, final boolean wrapLongWords) { + return null; + } + + public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn) { + return null; + } + + public static String capitalize(final String str) { + return null; + } + + public static String capitalize(final String str, final char... delimiters) { + return null; + } + + public static String capitalizeFully(final String str) { + return null; + } + + public static String capitalizeFully(String str, final char... delimiters) { + return null; + } + + public static String uncapitalize(final String str) { + return null; + } + + public static String uncapitalize(final String str, final char... delimiters) { + return null; + } + + public static String swapCase(final String str) { + return null; + } + + public static String initials(final String str) { + return null; + } + + public static String initials(final String str, final char... delimiters) { + return null; + } + + public static boolean containsAllWords(final CharSequence word, final CharSequence... words) { + return false; + } + +} diff --git a/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/StrTokenizer.java b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/StrTokenizer.java index db19fde3a02b..4f004767de5e 100644 --- a/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/StrTokenizer.java +++ b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/StrTokenizer.java @@ -16,8 +16,12 @@ */ package org.apache.commons.text; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.ListIterator; +import java.util.NoSuchElementException; + public class StrTokenizer implements ListIterator, Cloneable { public static StrTokenizer getCSVInstance() { diff --git a/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/WordUtils.java b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/WordUtils.java new file mode 100644 index 000000000000..d4a6875bd2dc --- /dev/null +++ b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/WordUtils.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.text; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class WordUtils { + public WordUtils() { + } + + public static String wrap(final String str, final int wrapLength) { + return null; + } + + public static String wrap(final String str, + final int wrapLength, + final String newLineStr, + final boolean wrapLongWords) { + return null; + } + + public static String wrap(final String str, + int wrapLength, + String newLineStr, + final boolean wrapLongWords, + String wrapOn) { + return null; + } + + public static String capitalize(final String str) { + return null; + } + + public static String capitalize(final String str, final char... delimiters) { + return null; + } + + public static String capitalizeFully(final String str) { + return null; + } + + public static String capitalizeFully(String str, final char... delimiters) { + return null; + } + + public static String uncapitalize(final String str) { + return null; + } + + public static String uncapitalize(final String str, final char... delimiters) { + return null; + } + + public static String swapCase(final String str) { + return null; + } + + public static String initials(final String str) { + return null; + } + + public static String initials(final String str, final char... delimiters) { + return null; + } + + public static boolean containsAllWords(final CharSequence word, final CharSequence... words) { + return false; + } + + public static boolean isDelimiter(final char ch, final char[] delimiters) { + return false; + } + + public static boolean isDelimiter(final int codePoint, final char[] delimiters) { + return false; + } + + public static String abbreviate(final String str, int lower, int upper, final String appendToEnd) { + return null; + } + +} From f749c311365e09e3cccc4baddc3c8bc15f5b61c6 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 18 Feb 2021 11:43:48 +0000 Subject: [PATCH 2/8] Add models for commons lang/text's Str[ing]Lookup class --- .../code/java/frameworks/apache/Lang.qll | 40 +++++ .../apache-commons-lang3/StrLookupTest.java | 17 +++ .../StringLookupTextTest.java | 18 +++ .../apache/commons/lang3/text/StrLookup.java | 36 +++++ .../commons/text/lookup/BiStringLookup.java | 28 ++++ .../commons/text/lookup/StringLookup.java | 37 +++++ .../text/lookup/StringLookupFactory.java | 143 ++++++++++++++++++ 7 files changed, 319 insertions(+) create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/StrLookupTest.java create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/StringLookupTextTest.java create mode 100644 java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrLookup.java create mode 100644 java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/BiStringLookup.java create mode 100644 java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/StringLookup.java create mode 100644 java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/StringLookupFactory.java diff --git a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll index 74c12d0e2f80..0f8c3598c4b1 100644 --- a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll +++ b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll @@ -266,3 +266,43 @@ private class ApacheStrTokenizerTaintGetter extends TaintPreservingCallable { override predicate returnsTaintFrom(int arg) { arg = -1 } } + +private class ApacheStrLookup extends RefType { + ApacheStrLookup() { + this.hasQualifiedName("org.apache.commons.lang3.text", "StrLookup") or + this.hasQualifiedName("org.apache.commons.text.lookup", "StringLookup") + } +} + +private class ApacheStringLookupFactory extends RefType { + ApacheStringLookupFactory() { + this.hasQualifiedName("org.apache.commons.text.lookup", "StringLookupFactory") + } +} + +/** + * A callable that constructs an Apache Commons `Str[ing]Lookup` from a map. + */ +private class ApacheStrLookupTaintingMethod extends TaintPreservingCallable { + ApacheStrLookupTaintingMethod() { + this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrLookup and + this.getName() = "mapLookup" + or + this.getDeclaringType() instanceof ApacheStringLookupFactory and + this.getName() = "mapStringLookup" + } + + override predicate returnsTaintFrom(int arg) { arg = 0 } +} + +/** + * A callable that looks up a value in a Apache Commons `Str[ing]Lookup` map. + */ +private class ApacheStrLookupTaintGetter extends TaintPreservingCallable { + ApacheStrLookupTaintGetter() { + this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrLookup and + this.getName() = "lookup" + } + + override predicate returnsTaintFrom(int arg) { arg = -1 } +} diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrLookupTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrLookupTest.java new file mode 100644 index 000000000000..ee96a10f6fac --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrLookupTest.java @@ -0,0 +1,17 @@ +import org.apache.commons.lang3.text.StrLookup; +import java.util.HashMap; +import java.util.Map; + +class StrLookupTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + Map map = new HashMap(); + map.put("key", taint()); + StrLookup lookup = StrLookup.mapLookup(map); + sink(lookup.lookup("key")); // $hasTaintFlow=y + } + +} \ No newline at end of file diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringLookupTextTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringLookupTextTest.java new file mode 100644 index 000000000000..db0b0392fad1 --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringLookupTextTest.java @@ -0,0 +1,18 @@ +import org.apache.commons.text.lookup.StringLookup; +import org.apache.commons.text.lookup.StringLookupFactory; +import java.util.HashMap; +import java.util.Map; + +class StringLookupTextTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + Map map = new HashMap(); + map.put("key", taint()); + StringLookup lookup = StringLookupFactory.INSTANCE.mapStringLookup(map); + sink(lookup.lookup("key")); // $hasTaintFlow=y + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrLookup.java b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrLookup.java new file mode 100644 index 000000000000..494793c21730 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrLookup.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.lang3.text; + +import java.util.Map; + +public abstract class StrLookup { + public static StrLookup noneLookup() { + return null; + } + + public static StrLookup systemPropertiesLookup() { + return null; + } + + public static StrLookup mapLookup(final Map map) { + return null; + } + + public abstract String lookup(String key); + +} diff --git a/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/BiStringLookup.java b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/BiStringLookup.java new file mode 100644 index 000000000000..e4dcd1705650 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/BiStringLookup.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.text.lookup; + +import java.util.function.BiFunction; +import java.util.function.Function; + +public interface BiStringLookup extends StringLookup { + default String lookup(final String key, final U object) { + return null; + } + +} diff --git a/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/StringLookup.java b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/StringLookup.java new file mode 100644 index 000000000000..4903d2273e72 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/StringLookup.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.text.lookup; + +/** + * Lookups a String key for a String value. + *

+ * This class represents the simplest form of a string to string map. It has a benefit over a map in that it can create + * the result on demand based on the key. + *

+ *

+ * For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the value + * on demand from the database. + *

+ * + * @since 1.3 + */ +@FunctionalInterface +public interface StringLookup { + String lookup(String key); + +} diff --git a/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/StringLookupFactory.java b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/StringLookupFactory.java new file mode 100644 index 000000000000..9756a548b3b4 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/lookup/StringLookupFactory.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.text.lookup; + +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; + + +public final class StringLookupFactory { + public static final StringLookupFactory INSTANCE = new StringLookupFactory(); + + public static void clear() { + } + + public void addDefaultStringLookups(final Map stringLookupMap) { + } + + public StringLookup base64DecoderStringLookup() { + return null; + } + + public StringLookup base64EncoderStringLookup() { + return null; + } + + public StringLookup base64StringLookup() { + return null; + } + + public BiStringLookup biFunctionStringLookup(final BiFunction biFunction) { + return null; + } + + public StringLookup constantStringLookup() { + return null; + } + + public StringLookup dateStringLookup() { + return null; + } + + public StringLookup dnsStringLookup() { + return null; + } + + public StringLookup environmentVariableStringLookup() { + return null; + } + + public StringLookup fileStringLookup() { + return null; + } + + public StringLookup functionStringLookup(final Function function) { + return null; + } + + public StringLookup interpolatorStringLookup() { + return null; + } + + public StringLookup interpolatorStringLookup(final Map stringLookupMap, + final StringLookup defaultStringLookup, final boolean addDefaultLookups) { + return null; + } + + public StringLookup interpolatorStringLookup(final Map map) { + return null; + } + + public StringLookup interpolatorStringLookup(final StringLookup defaultStringLookup) { + return null; + } + + public StringLookup javaPlatformStringLookup() { + return null; + } + + public StringLookup localHostStringLookup() { + return null; + } + + public StringLookup mapStringLookup(final Map map) { + return null; + } + + public StringLookup nullStringLookup() { + return null; + } + + public StringLookup propertiesStringLookup() { + return null; + } + + public StringLookup resourceBundleStringLookup() { + return null; + } + + public StringLookup resourceBundleStringLookup(final String bundleName) { + return null; + } + + public StringLookup scriptStringLookup() { + return null; + } + + public StringLookup systemPropertyStringLookup() { + return null; + } + + public StringLookup urlDecoderStringLookup() { + return null; + } + + public StringLookup urlEncoderStringLookup() { + return null; + } + + public StringLookup urlStringLookup() { + return null; + } + + public StringLookup xmlStringLookup() { + return null; + } + +} From b0ba0585a7f27da1da7d3f7fba9ccbd19e84b823 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 18 Feb 2021 14:43:23 +0000 Subject: [PATCH 3/8] Add models for Apache Commons Lang and Text's Str[ing]Substitutor --- .../code/java/frameworks/apache/Lang.qll | 40 +++ .../StrSubstitutorTest.java | 82 ++++++ .../StringSubstitutorTextTest.java | 83 ++++++ .../commons/lang3/text/StrSubstitutor.java | 227 ++++++++++++++++ .../commons/text/StringSubstitutor.java | 255 ++++++++++++++++++ 5 files changed, 687 insertions(+) create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/StrSubstitutorTest.java create mode 100644 java/ql/test/library-tests/frameworks/apache-commons-lang3/StringSubstitutorTextTest.java create mode 100644 java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrSubstitutor.java create mode 100644 java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/StringSubstitutor.java diff --git a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll index 0f8c3598c4b1..381e1b5205ab 100644 --- a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll +++ b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll @@ -306,3 +306,43 @@ private class ApacheStrLookupTaintGetter extends TaintPreservingCallable { override predicate returnsTaintFrom(int arg) { arg = -1 } } + +private class ApacheStrSubstitutor extends RefType { + ApacheStrSubstitutor() { + this.hasQualifiedName("org.apache.commons.lang3.text", "StrSubstitutor") or + this.hasQualifiedName("org.apache.commons.text", "StringSubstitutor") + } +} + +/** + * A callable declared on Apache Commons `StrSubstitutor` that returns taint. + */ +private class ApacheStrSubstitutorTaintGetter extends TaintPreservingCallable { + ApacheStrSubstitutorTaintGetter() { + this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrSubstitutor and + ( + this instanceof Constructor or + this.getName() = "replace" + ) + } + + override predicate returnsTaintFrom(int arg) { + arg in [0, -1] + or + this.isStatic() and arg = 1 + } +} + +/** + * A callable declared on Apache Commons `StrSubstitutor` that transfers taint. + */ +private class ApacheStrSubstitutorTaintTransfer extends TaintPreservingCallable { + ApacheStrSubstitutorTaintTransfer() { + this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrSubstitutor and + this.getName() in ["replaceIn", "setVariableResolver"] + } + + override predicate transfersTaint(int src, int sink) { + if this.getName() = "replaceIn" then (src = -1 and sink = 0) else (src = 0 and sink = -1) + } +} diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrSubstitutorTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrSubstitutorTest.java new file mode 100644 index 000000000000..15704554d83e --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StrSubstitutorTest.java @@ -0,0 +1,82 @@ +import org.apache.commons.lang3.text.StrSubstitutor; +import org.apache.commons.lang3.text.StrLookup; +import org.apache.commons.lang3.text.StrMatcher; +import org.apache.commons.lang3.text.StrBuilder; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +class StrSubstitutorTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + Map taintedMap = new HashMap(); + taintedMap.put("key", taint()); + StrLookup taintedLookup = StrLookup.mapLookup(taintedMap); + + // Test constructors: + StrSubstitutor ss1 = new StrSubstitutor(); ss1.setVariableResolver(taintedLookup); sink(ss1.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss2 = new StrSubstitutor(taintedMap); sink(ss2.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss3 = new StrSubstitutor(taintedMap, "{", "}"); sink(ss3.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss4 = new StrSubstitutor(taintedMap, "{", "}", ' '); sink(ss4.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss5 = new StrSubstitutor(taintedMap, "{", "}", ' ', ","); sink(ss5.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss6 = new StrSubstitutor(taintedLookup); sink(ss6.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss7 = new StrSubstitutor(taintedLookup, "{", "}", ' '); sink(ss7.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss8 = new StrSubstitutor(taintedLookup, "{", "}", ' ', ","); sink(ss8.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss9 = new StrSubstitutor(taintedLookup, (StrMatcher)null, null, ' '); sink(ss9.replace("input")); // $hasTaintFlow=y + StrSubstitutor ss10 = new StrSubstitutor(taintedLookup, (StrMatcher)null, null, ' ', null); sink(ss10.replace("input")); // $hasTaintFlow=y + + // Test replace overloads (tainted substitution map): + StrSubstitutor taintedSubst = ss2; + sink(taintedSubst.replace((Object)"input")); // $hasTaintFlow=y + sink(taintedSubst.replace("input")); // $hasTaintFlow=y + sink(taintedSubst.replace("input", 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace("input".toCharArray())); // $hasTaintFlow=y + sink(taintedSubst.replace("input".toCharArray(), 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace((CharSequence)"input")); // $hasTaintFlow=y + sink(taintedSubst.replace((CharSequence)"input", 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace(new StrBuilder("input"))); // $hasTaintFlow=y + sink(taintedSubst.replace(new StrBuilder("input"), 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace(new StringBuilder("input"))); // $hasTaintFlow=y + sink(taintedSubst.replace(new StringBuilder("input"), 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace(new StringBuffer("input"))); // $hasTaintFlow=y + sink(taintedSubst.replace(new StringBuffer("input"), 0, 0)); // $hasTaintFlow=y + + // Test replace overloads (tainted input): + StrSubstitutor untaintedSubst = ss1; + sink(untaintedSubst.replace((Object)taint())); // $hasTaintFlow=y + sink(untaintedSubst.replace(taint())); // $hasTaintFlow=y + sink(untaintedSubst.replace(taint(), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace(taint().toCharArray())); // $hasTaintFlow=y + sink(untaintedSubst.replace(taint().toCharArray(), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace((CharSequence)taint())); // $hasTaintFlow=y + sink(untaintedSubst.replace((CharSequence)taint(), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StrBuilder(taint()))); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StrBuilder(taint()), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StringBuilder(taint()))); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StringBuilder(taint()), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StringBuffer(taint()))); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StringBuffer(taint()), 0, 0)); // $hasTaintFlow=y + + // Test static replace methods: + sink(StrSubstitutor.replace(taint(), new HashMap())); // $hasTaintFlow=y + sink(StrSubstitutor.replace(taint(), new HashMap(), "{", "}")); // $hasTaintFlow=y + sink(StrSubstitutor.replace("input", taintedMap)); // $hasTaintFlow=y + sink(StrSubstitutor.replace("input", taintedMap, "{", "}")); // $hasTaintFlow=y + Properties taintedProps = new Properties(); + taintedProps.put("key", taint()); + sink(StrSubstitutor.replace(taint(), new Properties())); // $hasTaintFlow=y + sink(StrSubstitutor.replace("input", taintedProps)); // $hasTaintFlow=y + + // Test replaceIn methods: + StrBuilder strBuilder1 = new StrBuilder(); taintedSubst.replaceIn(strBuilder1); sink(strBuilder1.toString()); // $hasTaintFlow=y + StrBuilder strBuilder2 = new StrBuilder(); taintedSubst.replaceIn(strBuilder2, 0, 0); sink(strBuilder2.toString()); // $hasTaintFlow=y + StringBuilder stringBuilder1 = new StringBuilder(); taintedSubst.replaceIn(stringBuilder1); sink(stringBuilder1.toString()); // $hasTaintFlow=y + StringBuilder stringBuilder2 = new StringBuilder(); taintedSubst.replaceIn(stringBuilder2, 0, 0); sink(stringBuilder2.toString()); // $hasTaintFlow=y + StringBuffer stringBuffer1 = new StringBuffer(); taintedSubst.replaceIn(stringBuffer1); sink(stringBuffer1.toString()); // $hasTaintFlow=y + StringBuffer stringBuffer2 = new StringBuffer(); taintedSubst.replaceIn(stringBuffer2, 0, 0); sink(stringBuffer2.toString()); // $hasTaintFlow=y + } + +} \ No newline at end of file diff --git a/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringSubstitutorTextTest.java b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringSubstitutorTextTest.java new file mode 100644 index 000000000000..b8d5f1566e1f --- /dev/null +++ b/java/ql/test/library-tests/frameworks/apache-commons-lang3/StringSubstitutorTextTest.java @@ -0,0 +1,83 @@ +import org.apache.commons.text.StringSubstitutor; +import org.apache.commons.text.lookup.StringLookup; +import org.apache.commons.text.lookup.StringLookupFactory; +import org.apache.commons.text.matcher.StringMatcher; +import org.apache.commons.text.TextStringBuilder; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +class StringSubstitutorTextTest { + String taint() { return "tainted"; } + + void sink(Object o) {} + + void test() throws Exception { + Map taintedMap = new HashMap(); + taintedMap.put("key", taint()); + StringLookup taintedLookup = StringLookupFactory.INSTANCE.mapStringLookup(taintedMap); + + // Test constructors: + StringSubstitutor ss1 = new StringSubstitutor(); ss1.setVariableResolver(taintedLookup); sink(ss1.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss2 = new StringSubstitutor(taintedMap); sink(ss2.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss3 = new StringSubstitutor(taintedMap, "{", "}"); sink(ss3.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss4 = new StringSubstitutor(taintedMap, "{", "}", ' '); sink(ss4.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss5 = new StringSubstitutor(taintedMap, "{", "}", ' ', ","); sink(ss5.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss6 = new StringSubstitutor(taintedLookup); sink(ss6.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss7 = new StringSubstitutor(taintedLookup, "{", "}", ' '); sink(ss7.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss8 = new StringSubstitutor(taintedLookup, "{", "}", ' ', ","); sink(ss8.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss9 = new StringSubstitutor(taintedLookup, (StringMatcher)null, null, ' '); sink(ss9.replace("input")); // $hasTaintFlow=y + StringSubstitutor ss10 = new StringSubstitutor(taintedLookup, (StringMatcher)null, null, ' ', null); sink(ss10.replace("input")); // $hasTaintFlow=y + + // Test replace overloads (tainted substitution map): + StringSubstitutor taintedSubst = ss2; + sink(taintedSubst.replace((Object)"input")); // $hasTaintFlow=y + sink(taintedSubst.replace("input")); // $hasTaintFlow=y + sink(taintedSubst.replace("input", 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace("input".toCharArray())); // $hasTaintFlow=y + sink(taintedSubst.replace("input".toCharArray(), 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace((CharSequence)"input")); // $hasTaintFlow=y + sink(taintedSubst.replace((CharSequence)"input", 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace(new TextStringBuilder("input"))); // $hasTaintFlow=y + sink(taintedSubst.replace(new TextStringBuilder("input"), 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace(new StringBuilder("input"))); // $hasTaintFlow=y + sink(taintedSubst.replace(new StringBuilder("input"), 0, 0)); // $hasTaintFlow=y + sink(taintedSubst.replace(new StringBuffer("input"))); // $hasTaintFlow=y + sink(taintedSubst.replace(new StringBuffer("input"), 0, 0)); // $hasTaintFlow=y + + // Test replace overloads (tainted input): + StringSubstitutor untaintedSubst = ss1; + sink(untaintedSubst.replace((Object)taint())); // $hasTaintFlow=y + sink(untaintedSubst.replace(taint())); // $hasTaintFlow=y + sink(untaintedSubst.replace(taint(), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace(taint().toCharArray())); // $hasTaintFlow=y + sink(untaintedSubst.replace(taint().toCharArray(), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace((CharSequence)taint())); // $hasTaintFlow=y + sink(untaintedSubst.replace((CharSequence)taint(), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace(new TextStringBuilder(taint()))); // $hasTaintFlow=y + sink(untaintedSubst.replace(new TextStringBuilder(taint()), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StringBuilder(taint()))); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StringBuilder(taint()), 0, 0)); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StringBuffer(taint()))); // $hasTaintFlow=y + sink(untaintedSubst.replace(new StringBuffer(taint()), 0, 0)); // $hasTaintFlow=y + + // Test static replace methods: + sink(StringSubstitutor.replace(taint(), new HashMap())); // $hasTaintFlow=y + sink(StringSubstitutor.replace(taint(), new HashMap(), "{", "}")); // $hasTaintFlow=y + sink(StringSubstitutor.replace("input", taintedMap)); // $hasTaintFlow=y + sink(StringSubstitutor.replace("input", taintedMap, "{", "}")); // $hasTaintFlow=y + Properties taintedProps = new Properties(); + taintedProps.put("key", taint()); + sink(StringSubstitutor.replace(taint(), new Properties())); // $hasTaintFlow=y + sink(StringSubstitutor.replace("input", taintedProps)); // $hasTaintFlow=y + + // Test replaceIn methods: + TextStringBuilder strBuilder1 = new TextStringBuilder(); taintedSubst.replaceIn(strBuilder1); sink(strBuilder1.toString()); // $hasTaintFlow=y + TextStringBuilder strBuilder2 = new TextStringBuilder(); taintedSubst.replaceIn(strBuilder2, 0, 0); sink(strBuilder2.toString()); // $hasTaintFlow=y + StringBuilder stringBuilder1 = new StringBuilder(); taintedSubst.replaceIn(stringBuilder1); sink(stringBuilder1.toString()); // $hasTaintFlow=y + StringBuilder stringBuilder2 = new StringBuilder(); taintedSubst.replaceIn(stringBuilder2, 0, 0); sink(stringBuilder2.toString()); // $hasTaintFlow=y + StringBuffer stringBuffer1 = new StringBuffer(); taintedSubst.replaceIn(stringBuffer1); sink(stringBuffer1.toString()); // $hasTaintFlow=y + StringBuffer stringBuffer2 = new StringBuffer(); taintedSubst.replaceIn(stringBuffer2, 0, 0); sink(stringBuffer2.toString()); // $hasTaintFlow=y + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrSubstitutor.java b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrSubstitutor.java new file mode 100644 index 000000000000..322d726a817e --- /dev/null +++ b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/text/StrSubstitutor.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.lang3.text; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + + +public class StrSubstitutor { + public static String replace(final Object source, final Map valueMap) { + return null; + } + + public static String replace(final Object source, final Map valueMap, final String prefix, final String suffix) { + return null; + } + + public static String replace(final Object source, final Properties valueProperties) { + return null; + } + + public static String replaceSystemProperties(final Object source) { + return null; + } + + public StrSubstitutor() { + } + + public StrSubstitutor(final Map valueMap) { + } + + public StrSubstitutor(final Map valueMap, final String prefix, final String suffix) { + } + + public StrSubstitutor(final Map valueMap, final String prefix, final String suffix, + final char escape) { + } + + public StrSubstitutor(final Map valueMap, final String prefix, final String suffix, + final char escape, final String valueDelimiter) { + } + + public StrSubstitutor(final StrLookup variableResolver) { + } + + public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, + final char escape) { + } + + public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, + final char escape, final String valueDelimiter) { + } + + public StrSubstitutor( + final StrLookup variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, + final char escape) { + } + + public StrSubstitutor( + final StrLookup variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, + final char escape, final StrMatcher valueDelimiterMatcher) { + } + + public String replace(final String source) { + return null; + } + + public String replace(final String source, final int offset, final int length) { + return null; + } + + public String replace(final char[] source) { + return null; + } + + public String replace(final char[] source, final int offset, final int length) { + return null; + } + + public String replace(final StringBuffer source) { + return null; + } + + public String replace(final StringBuffer source, final int offset, final int length) { + return null; + } + + public String replace(final CharSequence source) { + return null; + } + + public String replace(final CharSequence source, final int offset, final int length) { + return null; + } + + public String replace(final StrBuilder source) { + return null; + } + + public String replace(final StrBuilder source, final int offset, final int length) { + return null; + } + + public String replace(final Object source) { + return null; + } + + public boolean replaceIn(final StringBuffer source) { + return false; + } + + public boolean replaceIn(final StringBuffer source, final int offset, final int length) { + return false; + } + + public boolean replaceIn(final StringBuilder source) { + return false; + } + + public boolean replaceIn(final StringBuilder source, final int offset, final int length) { + return false; + } + + public boolean replaceIn(final StrBuilder source) { + return false; + } + + public boolean replaceIn(final StrBuilder source, final int offset, final int length) { + return false; + } + + public char getEscapeChar() { + return 0; + } + + public void setEscapeChar(final char escapeCharacter) { + } + + public StrMatcher getVariablePrefixMatcher() { + return null; + } + + public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { + return null; + } + + public StrSubstitutor setVariablePrefix(final char prefix) { + return null; + } + + public StrSubstitutor setVariablePrefix(final String prefix) { + return null; + } + + public StrMatcher getVariableSuffixMatcher() { + return null; + } + + public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { + return null; + } + + public StrSubstitutor setVariableSuffix(final char suffix) { + return null; + } + + public StrSubstitutor setVariableSuffix(final String suffix) { + return null; + } + + public StrMatcher getValueDelimiterMatcher() { + return null; + } + + public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { + return null; + } + + public StrSubstitutor setValueDelimiter(final char valueDelimiter) { + return null; + } + + public StrSubstitutor setValueDelimiter(final String valueDelimiter) { + return null; + } + + public StrLookup getVariableResolver() { + return null; + } + + public void setVariableResolver(final StrLookup variableResolver) { + } + + public boolean isEnableSubstitutionInVariables() { + return false; + } + + public void setEnableSubstitutionInVariables( + final boolean enableSubstitutionInVariables) { + } + + public boolean isPreserveEscapes() { + return false; + } + + public void setPreserveEscapes(final boolean preserveEscapes) { + } + +} diff --git a/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/StringSubstitutor.java b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/StringSubstitutor.java new file mode 100644 index 000000000000..6f96bd56ce6a --- /dev/null +++ b/java/ql/test/stubs/apache-commons-text-1.9/org/apache/commons/text/StringSubstitutor.java @@ -0,0 +1,255 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.text; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +import org.apache.commons.text.lookup.StringLookup; +import org.apache.commons.text.matcher.StringMatcher; + +public class StringSubstitutor { + public static StringSubstitutor createInterpolator() { + return null; + } + + public static String replace(final Object source, final Map valueMap) { + return null; + } + + public static String replace(final Object source, final Map valueMap, final String prefix, + final String suffix) { + return null; + } + + public static String replace(final Object source, final Properties valueProperties) { + return null; + } + + public static String replaceSystemProperties(final Object source) { + return null; + } + + public StringSubstitutor() { + } + + public StringSubstitutor(final Map valueMap) { + } + + public StringSubstitutor(final Map valueMap, final String prefix, final String suffix) { + } + + public StringSubstitutor(final Map valueMap, final String prefix, final String suffix, + final char escape) { + } + + public StringSubstitutor(final Map valueMap, final String prefix, final String suffix, + final char escape, final String valueDelimiter) { + } + + public StringSubstitutor(final StringLookup variableResolver) { + } + + public StringSubstitutor(final StringLookup variableResolver, final String prefix, final String suffix, + final char escape) { + } + + public StringSubstitutor(final StringLookup variableResolver, final String prefix, final String suffix, + final char escape, final String valueDelimiter) { + } + + public StringSubstitutor(final StringLookup variableResolver, final StringMatcher prefixMatcher, + final StringMatcher suffixMatcher, final char escape) { + } + + public StringSubstitutor(final StringLookup variableResolver, final StringMatcher prefixMatcher, + final StringMatcher suffixMatcher, final char escape, final StringMatcher valueDelimiterMatcher) { + } + + public StringSubstitutor(final StringSubstitutor other) { + } + + public char getEscapeChar() { + return 0; + } + + public StringLookup getStringLookup() { + return null; + } + + public StringMatcher getValueDelimiterMatcher() { + return null; + } + + public StringMatcher getVariablePrefixMatcher() { + return null; + } + + public StringMatcher getVariableSuffixMatcher() { + return null; + } + + public boolean isDisableSubstitutionInValues() { + return false; + } + + public boolean isEnableSubstitutionInVariables() { + return false; + } + + public boolean isEnableUndefinedVariableException() { + return false; + } + + public boolean isPreserveEscapes() { + return false; + } + + public String replace(final char[] source) { + return null; + } + + public String replace(final char[] source, final int offset, final int length) { + return null; + } + + public String replace(final CharSequence source) { + return null; + } + + public String replace(final CharSequence source, final int offset, final int length) { + return null; + } + + public String replace(final Object source) { + return null; + } + + public String replace(final String source) { + return null; + } + + public String replace(final String source, final int offset, final int length) { + return null; + } + + public String replace(final StringBuffer source) { + return null; + } + + public String replace(final StringBuffer source, final int offset, final int length) { + return null; + } + + public String replace(final TextStringBuilder source) { + return null; + } + + public String replace(final TextStringBuilder source, final int offset, final int length) { + return null; + } + + public boolean replaceIn(final StringBuffer source) { + return false; + } + + public boolean replaceIn(final StringBuffer source, final int offset, final int length) { + return false; + } + + public boolean replaceIn(final StringBuilder source) { + return false; + } + + public boolean replaceIn(final StringBuilder source, final int offset, final int length) { + return false; + } + + public boolean replaceIn(final TextStringBuilder source) { + return false; + } + + public boolean replaceIn(final TextStringBuilder source, final int offset, final int length) { + return false; + } + + public StringSubstitutor setDisableSubstitutionInValues(final boolean disableSubstitutionInValues) { + return null; + } + + public StringSubstitutor setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) { + return null; + } + + public StringSubstitutor setEnableUndefinedVariableException(final boolean failOnUndefinedVariable) { + return null; + } + + public StringSubstitutor setEscapeChar(final char escapeCharacter) { + return null; + } + + public StringSubstitutor setPreserveEscapes(final boolean preserveEscapes) { + return null; + } + + public StringSubstitutor setValueDelimiter(final char valueDelimiter) { + return null; + } + + public StringSubstitutor setValueDelimiter(final String valueDelimiter) { + return null; + } + + public StringSubstitutor setValueDelimiterMatcher(final StringMatcher valueDelimiterMatcher) { + return null; + } + + public StringSubstitutor setVariablePrefix(final char prefix) { + return null; + } + + public StringSubstitutor setVariablePrefix(final String prefix) { + return null; + } + + public StringSubstitutor setVariablePrefixMatcher(final StringMatcher prefixMatcher) { + return null; + } + + public StringSubstitutor setVariableResolver(final StringLookup variableResolver) { + return null; + } + + public StringSubstitutor setVariableSuffix(final char suffix) { + return null; + } + + public StringSubstitutor setVariableSuffix(final String suffix) { + return null; + } + + public StringSubstitutor setVariableSuffixMatcher(final StringMatcher suffixMatcher) { + return null; + } + +} From 224e537459031906a60c09d67c8417df8a008551 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 2 Mar 2021 11:44:35 +0000 Subject: [PATCH 4/8] Add change note --- java/change-notes/2021-03-02-apache-text-misc.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 java/change-notes/2021-03-02-apache-text-misc.md diff --git a/java/change-notes/2021-03-02-apache-text-misc.md b/java/change-notes/2021-03-02-apache-text-misc.md new file mode 100644 index 000000000000..16ed196df584 --- /dev/null +++ b/java/change-notes/2021-03-02-apache-text-misc.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Added models for the Apache Commons Lang (and Commons Text) classes WordUtils, StrTokenizer, StrLookup and StrSubstitutor. This may result in extra results for a wide array of queries where any of these text-processing classes are used. From 0029d3b743c151b89af70e250c32990c3c9cc5c6 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 2 Mar 2021 18:35:44 +0000 Subject: [PATCH 5/8] Java CSV flow summaries: allow specifying an unqualified typename to imply either the type itself or any generic specialisation. It is still possible to specify a precise generic signature if need be. --- java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll index 86f536f8ed72..0e3022f5a12f 100644 --- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll @@ -287,7 +287,7 @@ private predicate elementSpec( bindingset[namespace, type, subtypes] private RefType interpretType(string namespace, string type, boolean subtypes) { exists(RefType t | - t.hasQualifiedName(namespace, type) and + [t, t.getSourceDeclaration()].hasQualifiedName(namespace, type) and if subtypes = true then result.getASourceSupertype*() = t else result = t ) } From 43b9436bb8d3c143df3926d2731f2fee18fc0f62 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 2 Mar 2021 18:36:08 +0000 Subject: [PATCH 6/8] Convert Apache misc text models to CSV taint-flow specifications --- .../code/java/frameworks/apache/Lang.qll | 258 ++++++++++-------- 1 file changed, 144 insertions(+), 114 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll index 381e1b5205ab..26b52b2716b9 100644 --- a/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll +++ b/java/ql/src/semmle/code/java/frameworks/apache/Lang.qll @@ -2,6 +2,7 @@ import java private import semmle.code.java.dataflow.FlowSteps +private import semmle.code.java.dataflow.ExternalFlow /** * The class `org.apache.commons.lang.RandomStringUtils` or `org.apache.commons.lang3.RandomStringUtils`. @@ -212,137 +213,166 @@ private class ApacheStrBuilderTaintWriter extends ApacheStrBuilderCallable, Tain } } -private class ApacheWordUtilsTaintPreservingMethod extends TaintPreservingCallable { - ApacheWordUtilsTaintPreservingMethod() { - this.getDeclaringType() - .hasQualifiedName(["org.apache.commons.lang3.text", "org.apache.commons.text"], "WordUtils") and - this.getReturnType() instanceof TypeString - } - - override predicate returnsTaintFrom(int arg) { - this.getParameterType(arg) instanceof TypeString and - arg != 4 // Exclude the wrapOn parameter from `wrap(String, int, String, boolean, String)` - } -} - -private class ApacheStrTokenizer extends RefType { - ApacheStrTokenizer() { - this.hasQualifiedName(["org.apache.commons.lang3.text", "org.apache.commons.text"], - "StrTokenizer") or - this.hasQualifiedName("org.apache.commons.text", "StringTokenizer") - } -} - /** - * A callable that sets the string to be tokenized by an Apache Commons `Str[ing]Tokenizer`. - * - * Note `reset` is an instance method that taints an existing instance; all others return a fresh instance. + * Taint-propagating models for `WordUtils`. */ -private class ApacheStrTokenizerTaintingMethod extends TaintPreservingCallable { - ApacheStrTokenizerTaintingMethod() { - this.getDeclaringType() instanceof ApacheStrTokenizer and - ( - this instanceof Constructor or - this.getName() in ["getCSVInstance", "getTSVInstance", "reset"] - ) - } - - override predicate returnsTaintFrom(int arg) { arg = 0 } - - override predicate transfersTaint(int fromArg, int toArg) { - this.getName() = "reset" and - returnsTaintFrom(fromArg) and - toArg = -1 - } -} - -private class ApacheStrTokenizerTaintGetter extends TaintPreservingCallable { - ApacheStrTokenizerTaintGetter() { - this.getDeclaringType() instanceof ApacheStrTokenizer and - this.getName() in [ - "getContent", "getTokenArray", "getTokenList", "nextToken", "previousToken", "toString" +private class ApacheWordUtilsModel extends SummaryModelCsv { + override predicate row(string row) { + row = + [ + "org.apache.commons.lang3.text;WordUtils;false;wrap;;;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;wrap;(java.lang.String,int,java.lang.String,boolean);;Argument[2];ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;wrap;(java.lang.String,int,java.lang.String,boolean,java.lang.String);;Argument[2];ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;uncapitalize;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;uncapitalize;(java.lang.String,char[]);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;swapCase;;;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;capitalize;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;capitalize;(java.lang.String,char[]);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;initials;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;initials;(java.lang.String,char[]);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;capitalizeFully;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;WordUtils;false;capitalizeFully;(java.lang.String,char[]);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;wrap;;;Argument[0];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;wrap;(java.lang.String,int,java.lang.String,boolean);;Argument[2];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;wrap;(java.lang.String,int,java.lang.String,boolean,java.lang.String);;Argument[2];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;uncapitalize;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;uncapitalize;(java.lang.String,char[]);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;swapCase;;;Argument;ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;capitalize;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;capitalize;(java.lang.String,char[]);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;abbreviate;;;Argument[0];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;abbreviate;;;Argument[3];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;initials;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;initials;(java.lang.String,char[]);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;capitalizeFully;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.text;WordUtils;false;capitalizeFully;(java.lang.String,char[]);;Argument[0];ReturnValue;taint" ] } - - override predicate returnsTaintFrom(int arg) { arg = -1 } -} - -private class ApacheStrLookup extends RefType { - ApacheStrLookup() { - this.hasQualifiedName("org.apache.commons.lang3.text", "StrLookup") or - this.hasQualifiedName("org.apache.commons.text.lookup", "StringLookup") - } -} - -private class ApacheStringLookupFactory extends RefType { - ApacheStringLookupFactory() { - this.hasQualifiedName("org.apache.commons.text.lookup", "StringLookupFactory") - } } /** - * A callable that constructs an Apache Commons `Str[ing]Lookup` from a map. + * Taint-propagating models for `StrTokenizer`. */ -private class ApacheStrLookupTaintingMethod extends TaintPreservingCallable { - ApacheStrLookupTaintingMethod() { - this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrLookup and - this.getName() = "mapLookup" - or - this.getDeclaringType() instanceof ApacheStringLookupFactory and - this.getName() = "mapStringLookup" - } - - override predicate returnsTaintFrom(int arg) { arg = 0 } -} - -/** - * A callable that looks up a value in a Apache Commons `Str[ing]Lookup` map. - */ -private class ApacheStrLookupTaintGetter extends TaintPreservingCallable { - ApacheStrLookupTaintGetter() { - this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrLookup and - this.getName() = "lookup" - } - - override predicate returnsTaintFrom(int arg) { arg = -1 } -} - -private class ApacheStrSubstitutor extends RefType { - ApacheStrSubstitutor() { - this.hasQualifiedName("org.apache.commons.lang3.text", "StrSubstitutor") or - this.hasQualifiedName("org.apache.commons.text", "StringSubstitutor") +private class ApacheStrTokenizerModel extends SummaryModelCsv { + override predicate row(string row) { + row = + [ + "org.apache.commons.lang3.text;StrTokenizer;false;StrTokenizer;;;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;clone;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;toString;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;reset;;;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;reset;;;Argument;Argument[-1];taint", + "org.apache.commons.lang3.text;StrTokenizer;false;next;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;getContent;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;previous;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;getTokenList;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;getTokenArray;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;previousToken;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;nextToken;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;getTSVInstance;;;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrTokenizer;false;getCSVInstance;;;Argument;ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;StrTokenizer;;;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;clone;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;toString;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;reset;;;Argument;ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;reset;;;Argument;Argument[-1];taint", + "org.apache.commons.text;StrTokenizer;false;next;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;getContent;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;previous;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;getTokenList;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;getTokenArray;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;previousToken;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;nextToken;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;getTSVInstance;;;Argument;ReturnValue;taint", + "org.apache.commons.text;StrTokenizer;false;getCSVInstance;;;Argument;ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;StringTokenizer;;;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;clone;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;toString;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;reset;;;Argument;ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;reset;;;Argument;Argument[-1];taint", + "org.apache.commons.text;StringTokenizer;false;next;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;getContent;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;previous;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;getTokenList;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;getTokenArray;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;previousToken;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;nextToken;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;getTSVInstance;;;Argument;ReturnValue;taint", + "org.apache.commons.text;StringTokenizer;false;getCSVInstance;;;Argument;ReturnValue;taint" + ] } } /** - * A callable declared on Apache Commons `StrSubstitutor` that returns taint. + * Taint-propagating models for `StrLookup`. */ -private class ApacheStrSubstitutorTaintGetter extends TaintPreservingCallable { - ApacheStrSubstitutorTaintGetter() { - this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrSubstitutor and - ( - this instanceof Constructor or - this.getName() = "replace" - ) - } - - override predicate returnsTaintFrom(int arg) { - arg in [0, -1] - or - this.isStatic() and arg = 1 +private class ApacheStrLookupModel extends SummaryModelCsv { + override predicate row(string row) { + row = + [ + "org.apache.commons.lang3.text;StrLookup;false;lookup;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrLookup;false;mapLookup;;;Argument;ReturnValue;taint", + "org.apache.commons.text.lookup;StringLookup;true;lookup;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text.lookup;StringLookupFactory;false;mapStringLookup;;;Argument;ReturnValue;taint" + ] } } /** - * A callable declared on Apache Commons `StrSubstitutor` that transfers taint. + * Taint-propagating models for `StrSubstitutor`. */ -private class ApacheStrSubstitutorTaintTransfer extends TaintPreservingCallable { - ApacheStrSubstitutorTaintTransfer() { - this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrSubstitutor and - this.getName() in ["replaceIn", "setVariableResolver"] - } - - override predicate transfersTaint(int src, int sink) { - if this.getName() = "replaceIn" then (src = -1 and sink = 0) else (src = 0 and sink = -1) +private class ApacheStrSubstitutorModel extends SummaryModelCsv { + override predicate row(string row) { + row = + [ + "org.apache.commons.lang3.text;StrSubstitutor;false;StrSubstitutor;;;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.Object);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(char[]);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(char[],int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.CharSequence);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.CharSequence,int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(org.apache.commons.lang3.text.StrBuilder);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.StringBuffer);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.StringBuffer,int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.String,int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(org.apache.commons.lang3.text.StrBuilder,int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.Object,java.util.Map);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.Object,java.util.Map,java.lang.String,java.lang.String);;Argument[0];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.Object,java.util.Map,java.lang.String,java.lang.String);;Argument[1];ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replace;(java.lang.Object,java.util.Properties);;Argument;ReturnValue;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;setVariableResolver;;;Argument;Argument[-1];taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replaceIn;(org.apache.commons.lang3.text.StrBuilder);;Argument[-1];Argument;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replaceIn;(java.lang.StringBuffer);;Argument[-1];Argument;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replaceIn;(java.lang.StringBuffer,int,int);;Argument[-1];Argument[0];taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replaceIn;(java.lang.StringBuilder);;Argument[-1];Argument;taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replaceIn;(java.lang.StringBuilder,int,int);;Argument[-1];Argument[0];taint", + "org.apache.commons.lang3.text;StrSubstitutor;false;replaceIn;(org.apache.commons.lang3.text.StrBuilder,int,int);;Argument[-1];Argument[0];taint", + "org.apache.commons.text;StringSubstitutor;false;StringSubstitutor;;;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;;;Argument[-1];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.Object);;Argument;ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(char[]);;Argument;ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(char[],int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.CharSequence);;Argument;ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.CharSequence,int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.String);;Argument;ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.StringBuffer);;Argument;ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.StringBuffer,int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.String,int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.Object,java.util.Map);;Argument;ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.Object,java.util.Map,java.lang.String,java.lang.String);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.Object,java.util.Map,java.lang.String,java.lang.String);;Argument[1];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(java.lang.Object,java.util.Properties);;Argument;ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(org.apache.commons.text.TextStringBuilder);;Argument;ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;replace;(org.apache.commons.text.TextStringBuilder,int,int);;Argument[0];ReturnValue;taint", + "org.apache.commons.text;StringSubstitutor;false;setVariableResolver;;;Argument;Argument[-1];taint", + "org.apache.commons.text;StringSubstitutor;false;replaceIn;(java.lang.StringBuffer);;Argument[-1];Argument;taint", + "org.apache.commons.text;StringSubstitutor;false;replaceIn;(java.lang.StringBuffer,int,int);;Argument[-1];Argument[0];taint", + "org.apache.commons.text;StringSubstitutor;false;replaceIn;(java.lang.StringBuilder);;Argument[-1];Argument;taint", + "org.apache.commons.text;StringSubstitutor;false;replaceIn;(java.lang.StringBuilder,int,int);;Argument[-1];Argument[0];taint", + "org.apache.commons.text;StringSubstitutor;false;replaceIn;(org.apache.commons.text.TextStringBuilder);;Argument[-1];Argument;taint", + "org.apache.commons.text;StringSubstitutor;false;replaceIn;(org.apache.commons.text.TextStringBuilder,int,int);;Argument[-1];Argument[0];taint" + ] } } From 563404120f5529638341010ad555c10349944cba Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 3 Mar 2021 20:35:33 +0000 Subject: [PATCH 7/8] Move calls to getSourceDeclaration --- .../src/semmle/code/java/dataflow/ExternalFlow.qll | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll index 0e3022f5a12f..c3a31a9ca2af 100644 --- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll @@ -287,7 +287,7 @@ private predicate elementSpec( bindingset[namespace, type, subtypes] private RefType interpretType(string namespace, string type, boolean subtypes) { exists(RefType t | - [t, t.getSourceDeclaration()].hasQualifiedName(namespace, type) and + t.hasQualifiedName(namespace, type) and if subtypes = true then result.getASourceSupertype*() = t else result = t ) } @@ -409,21 +409,27 @@ private predicate outputNeedsReference(string c) { private predicate sourceElementRef(Top ref, string output, string kind) { exists(Element e | sourceElement(e, output, kind) and - if outputNeedsReference(getLast(output)) then ref.(Call).getCallee() = e else ref = e + if outputNeedsReference(getLast(output)) + then ref.(Call).getCallee().getSourceDeclaration() = e + else ref = e ) } private predicate sinkElementRef(Top ref, string input, string kind) { exists(Element e | sinkElement(e, input, kind) and - if inputNeedsReference(getLast(input)) then ref.(Call).getCallee() = e else ref = e + if inputNeedsReference(getLast(input)) + then ref.(Call).getCallee().getSourceDeclaration() = e + else ref = e ) } private predicate summaryElementRef(Top ref, string input, string output, string kind) { exists(Element e | summaryElement(e, input, output, kind) and - if inputNeedsReference(getLast(input)) then ref.(Call).getCallee() = e else ref = e + if inputNeedsReference(getLast(input)) + then ref.(Call).getCallee().getSourceDeclaration() = e + else ref = e ) } From 71cd329ded5710012443f60a6e88c0a0c6b676c3 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 4 Mar 2021 11:12:21 +0000 Subject: [PATCH 8/8] Directly import Lang from ExternalFlow's Frameworks module --- java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll | 1 + 1 file changed, 1 insertion(+) diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll index c3a31a9ca2af..f5fbad778fb2 100644 --- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll @@ -70,6 +70,7 @@ private import internal.DataFlowPrivate */ private module Frameworks { private import semmle.code.java.frameworks.ApacheHttp + private import semmle.code.java.frameworks.apache.Lang } private predicate sourceModelCsv(string row) {