This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
commit b424803abdb2bec818e4fbcb251ce031c22aca53 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sat Sep 21 17:23:08 2024 -0400 Rewrite ClassUtils.getClass() without recursion to avoid StackOverflowError on very long inputs. - This was found fuzz testing Apache Commons Text which relies on ClassUtils. - OssFuzz Issue 42522972: apache-commons-text:StringSubstitutorInterpolatorFuzzer: Security exception in org.apache.commons.lang3.ClassUtils.getClass --- src/changes/changes.xml | 3 +- .../java/org/apache/commons/lang3/ClassUtils.java | 34 ++++++++++----------- .../commons/lang3/ClassUtilsOssFuzzTest.java | Bin 0 -> 17081 bytes 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 5ca2a390e..b9952c966 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -50,7 +50,8 @@ The <action> type attribute can be add,update,fix,remove. <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix flaky FileUtilsWaitForTest.testWaitForNegativeDuration().</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Pick up exec-maven-plugin version from parent POM.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Speed up and sanitize StopWatchTest.</action> - <action type="fix" dev="ggregory" due-to="Fabrice Benhamouda">Fix handling of non-ASCII letters and numbers in RandomStringUtils #1273.</action> + <action type="fix" dev="ggregory" due-to="Fabrice Benhamouda">Fix handling of non-ASCII letters and numbers in RandomStringUtils #1273.</action> + <action type="fix" dev="ggregory" due-to="OSS-Fuzz, Gary Gregory">Rewrite ClassUtils.getClass(...) without recursion to avoid StackOverflowError on very long inputs. OSS-Fuzz Issue 42522972: apache-commons-text:StringSubstitutorInterpolatorFuzzer: Security exception in org.apache.commons.lang3.ClassUtils.getClass.</action> <!-- ADD --> <action type="add" dev="ggregory" due-to="Gary Gregory">Add Strings and refactor StringUtils.</action> <!-- UPDATE --> diff --git a/src/main/java/org/apache/commons/lang3/ClassUtils.java b/src/main/java/org/apache/commons/lang3/ClassUtils.java index 46c15f190..d41fe15a3 100644 --- a/src/main/java/org/apache/commons/lang3/ClassUtils.java +++ b/src/main/java/org/apache/commons/lang3/ClassUtils.java @@ -527,24 +527,21 @@ public class ClassUtils { * @throws ClassNotFoundException if the class is not found */ public static Class<?> getClass(final ClassLoader classLoader, final String className, final boolean initialize) throws ClassNotFoundException { - try { - final Class<?> clazz = getPrimitiveClass(className); - return clazz != null ? clazz : Class.forName(toCanonicalName(className), initialize, classLoader); - } catch (final ClassNotFoundException ex) { - // allow path separators (.) as inner class name separators - final int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR); - - if (lastDotIndex != -1) { - try { - return getClass(classLoader, className.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR_CHAR + className.substring(lastDotIndex + 1), - initialize); - } catch (final ClassNotFoundException ignored) { - // ignore exception + // This method was re-written to avoid recursion and stack overflows found by fuzz testing. + String next = className; + int lastDotIndex = -1; + do { + try { + final Class<?> clazz = getPrimitiveClass(next); + return clazz != null ? clazz : Class.forName(toCanonicalName(next), initialize, classLoader); + } catch (final ClassNotFoundException ex) { + lastDotIndex = next.lastIndexOf(PACKAGE_SEPARATOR_CHAR); + if (lastDotIndex != -1) { + next = next.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR_CHAR + next.substring(lastDotIndex + 1); } } - - throw ex; - } + } while (lastDotIndex != -1); + throw new ClassNotFoundException(next); } /** @@ -1504,9 +1501,10 @@ public class ClassUtils { private static String toCanonicalName(final String className) { String canonicalName = StringUtils.deleteWhitespace(className); Objects.requireNonNull(canonicalName, "className"); - if (canonicalName.endsWith("[]")) { + final String arrayMarker = "[]"; + if (canonicalName.endsWith(arrayMarker)) { final StringBuilder classNameBuffer = new StringBuilder(); - while (canonicalName.endsWith("[]")) { + while (canonicalName.endsWith(arrayMarker)) { canonicalName = canonicalName.substring(0, canonicalName.length() - 2); classNameBuffer.append("["); } diff --git a/src/test/java/org/apache/commons/lang3/ClassUtilsOssFuzzTest.java b/src/test/java/org/apache/commons/lang3/ClassUtilsOssFuzzTest.java new file mode 100644 index 000000000..3c9d39ea2 Binary files /dev/null and b/src/test/java/org/apache/commons/lang3/ClassUtilsOssFuzzTest.java differ