diff options
Diffstat (limited to 'sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt')
-rw-r--r-- | sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt new file mode 100644 index 00000000..def5f6e3 --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt @@ -0,0 +1,160 @@ +// Copyright 2022 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.code_intelligence.jazzer.sanitizers + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow +import com.code_intelligence.jazzer.api.HookType +import com.code_intelligence.jazzer.api.Jazzer +import com.code_intelligence.jazzer.api.MethodHook +import com.code_intelligence.jazzer.api.MethodHooks +import java.lang.invoke.MethodHandle +import java.util.regex.Pattern +import java.util.regex.PatternSyntaxException + +@Suppress("unused_parameter", "unused") +object RegexInjection { + /** + * Part of an OOM "exploit" for [java.util.regex.Pattern.compile] with the + * [java.util.regex.Pattern.CANON_EQ] flag, formed by three consecutive combining marks, in this + * case grave accents: ◌̀. + * See [compileWithFlagsHook] for details. + */ + private const val CANON_EQ_ALMOST_EXPLOIT = "\u0300\u0300\u0300" + + /** + * When injected into a regex pattern, helps the fuzzer break out of quotes and character + * classes in order to cause a [PatternSyntaxException]. + */ + private const val FORCE_PATTERN_SYNTAX_EXCEPTION_PATTERN = "\\E]\\E]]]]]]" + + @MethodHook( + type = HookType.REPLACE, + targetClassName = "java.util.regex.Pattern", + targetMethod = "compile", + targetMethodDescriptor = "(Ljava/lang/String;I)Ljava/util/regex/Pattern;" + ) + @JvmStatic + fun compileWithFlagsHook(method: MethodHandle, alwaysNull: Any?, args: Array<Any?>, hookId: Int): Any? { + val pattern = args[0] as String? + val hasCanonEqFlag = ((args[1] as Int) and Pattern.CANON_EQ) != 0 + return hookInternal(method, pattern, hasCanonEqFlag, hookId, *args) + } + + @MethodHooks( + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.util.regex.Pattern", + targetMethod = "compile", + targetMethodDescriptor = "(Ljava/lang/String;)Ljava/util/regex/Pattern;" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.util.regex.Pattern", + targetMethod = "matches", + targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/CharSequence;)Z" + ), + ) + @JvmStatic + fun patternHook(method: MethodHandle, alwaysNull: Any?, args: Array<Any?>, hookId: Int): Any? { + return hookInternal(method, args[0] as String?, false, hookId, *args) + } + + @MethodHooks( + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "matches", + targetMethodDescriptor = "(Ljava/lang/String;)Z", + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "replaceAll", + targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "replaceFirst", + targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "split", + targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/String;", + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "split", + targetMethodDescriptor = "(Ljava/lang/String;I)Ljava/lang/String;", + ), + ) + @JvmStatic + fun stringHook(method: MethodHandle, thisObject: Any?, args: Array<Any?>, hookId: Int): Any? { + return hookInternal(method, args[0] as String?, false, hookId, thisObject, *args) + } + + private fun hookInternal( + method: MethodHandle, + pattern: String?, + hasCanonEqFlag: Boolean, + hookId: Int, + vararg args: Any? + ): Any? { + if (hasCanonEqFlag && pattern != null) { + // With CANON_EQ enabled, Pattern.compile allocates an array with a size that is + // (super-)exponential in the number of consecutive Unicode combining marks. We use a mild case + // of this as a magic string based on which we trigger a finding. + // Note: The fuzzer might trigger an OutOfMemoryError or NegativeArraySizeException (if the size + // of the array overflows an int) by chance before it correctly emits this "exploit". In that + // case, we report the original exception instead. + if (pattern.contains(CANON_EQ_ALMOST_EXPLOIT)) { + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueLow( + """Regular Expression Injection with CANON_EQ +When java.util.regex.Pattern.compile is used with the Pattern.CANON_EQ flag, +every injection into the regular expression pattern can cause arbitrarily large +memory allocations, even when wrapped with Pattern.quote(...).""" + ) + ) + } else { + Jazzer.guideTowardsContainment(pattern, CANON_EQ_ALMOST_EXPLOIT, hookId) + } + } + try { + return method.invokeWithArguments(*args).also { + // Only submit a fuzzer hint if no exception has been thrown. + if (!hasCanonEqFlag && pattern != null) { + Jazzer.guideTowardsContainment(pattern, FORCE_PATTERN_SYNTAX_EXCEPTION_PATTERN, hookId) + } + } + } catch (e: Exception) { + if (e is PatternSyntaxException) { + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueLow( + """Regular Expression Injection +Regular expression patterns that contain unescaped untrusted input can consume +arbitrary amounts of CPU time. To properly escape the input, wrap it with +Pattern.quote(...).""", + e + ) + ) + } + throw e + } + } +} |