aboutsummaryrefslogtreecommitdiff
path: root/sanitizers
diff options
context:
space:
mode:
Diffstat (limited to 'sanitizers')
-rw-r--r--sanitizers/BUILD.bazel1
-rw-r--r--sanitizers/sanitizers.bzl5
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel19
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt2
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt18
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt123
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt38
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt61
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt46
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt160
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java322
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt113
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt14
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel7
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/ReflectionUtils.java62
-rw-r--r--sanitizers/src/main/java/jaz/BUILD.bazel12
-rw-r--r--sanitizers/src/main/java/jaz/Zer.java107
-rw-r--r--sanitizers/src/test/java/com/example/BUILD.bazel108
-rw-r--r--sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java30
-rw-r--r--sanitizers/src/test/java/com/example/LdapDnInjection.java39
-rw-r--r--sanitizers/src/test/java/com/example/LdapSearchInjection.java39
-rw-r--r--sanitizers/src/test/java/com/example/LibraryLoad.java29
-rw-r--r--sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java35
-rw-r--r--sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java35
-rw-r--r--sanitizers/src/test/java/com/example/ReflectiveCall.java6
-rw-r--r--sanitizers/src/test/java/com/example/RegexCanonEqInjection.java41
-rw-r--r--sanitizers/src/test/java/com/example/RegexInsecureQuoteInjection.java29
-rw-r--r--sanitizers/src/test/java/com/example/RegexRoadblocks.java89
-rw-r--r--sanitizers/src/test/java/com/example/SqlInjection.java41
-rw-r--r--sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java (renamed from sanitizers/src/main/java/jaz/Ter.java)18
-rw-r--r--sanitizers/src/test/java/com/example/ldap/MockLdapContext.java316
31 files changed, 1785 insertions, 180 deletions
diff --git a/sanitizers/BUILD.bazel b/sanitizers/BUILD.bazel
index fa84208e..fdc616a3 100644
--- a/sanitizers/BUILD.bazel
+++ b/sanitizers/BUILD.bazel
@@ -3,6 +3,5 @@ java_library(
visibility = ["//visibility:public"],
runtime_deps = [
"//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers",
- "//sanitizers/src/main/java/jaz",
],
)
diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl
index 8bdea7a9..cef4cf47 100644
--- a/sanitizers/sanitizers.bzl
+++ b/sanitizers/sanitizers.bzl
@@ -17,8 +17,13 @@ _sanitizer_package_prefix = "com.code_intelligence.jazzer.sanitizers."
_sanitizer_class_names = [
"Deserialization",
"ExpressionLanguageInjection",
+ "LdapInjection",
"NamingContextLookup",
+ "OsCommandInjection",
"ReflectiveCall",
+ "RegexInjection",
+ "RegexRoadblocks",
+ "SqlInjection",
]
SANITIZER_CLASSES = [_sanitizer_package_prefix + class_name for class_name in _sanitizer_class_names]
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
index 65480653..1b156f9e 100644
--- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
@@ -1,17 +1,34 @@
load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+java_library(
+ name = "regex_roadblocks",
+ srcs = ["RegexRoadblocks.java"],
+ deps = [
+ "//agent:jazzer_api_compile_only",
+ "//agent/src/main/java/com/code_intelligence/jazzer/runtime:unsafe_provider",
+ "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils:reflection_utils",
+ ],
+)
+
kt_jvm_library(
name = "sanitizers",
srcs = [
"Deserialization.kt",
"ExpressionLanguageInjection.kt",
+ "LdapInjection.kt",
"NamingContextLookup.kt",
+ "OsCommandInjection.kt",
"ReflectiveCall.kt",
+ "RegexInjection.kt",
+ "SqlInjection.kt",
"Utils.kt",
],
visibility = ["//sanitizers:__pkg__"],
+ runtime_deps = [
+ ":regex_roadblocks",
+ ],
deps = [
"//agent:jazzer_api_compile_only",
- "//sanitizers/src/main/java/jaz",
+ "@maven//:com_github_jsqlparser_jsqlparser",
],
)
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt
index f6401dfd..55691c1a 100644
--- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt
@@ -29,7 +29,7 @@ import java.util.WeakHashMap
/**
* Detects unsafe deserialization that leads to attacker-controlled method calls, in particular to [Object.finalize].
*/
-@Suppress("unused_parameter")
+@Suppress("unused_parameter", "unused")
object Deserialization {
private val OBJECT_INPUT_STREAM_HEADER =
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt
index 9b1e8ca6..1dc1d5f0 100644
--- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt
@@ -24,7 +24,7 @@ import java.lang.invoke.MethodHandle
/**
* Detects injectable inputs to an expression language interpreter which may lead to remote code execution.
*/
-@Suppress("unused_parameter")
+@Suppress("unused_parameter", "unused")
object ExpressionLanguageInjection {
/**
@@ -44,6 +44,16 @@ object ExpressionLanguageInjection {
targetClassName = "javax.el.ExpressionFactory",
targetMethod = "createMethodExpression",
),
+ MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "jakarta.el.ExpressionFactory",
+ targetMethod = "createValueExpression",
+ ),
+ MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "jakarta.el.ExpressionFactory",
+ targetMethod = "createMethodExpression",
+ ),
)
@JvmStatic
fun hookElExpressionFactory(
@@ -52,10 +62,8 @@ object ExpressionLanguageInjection {
arguments: Array<Any>,
hookId: Int
) {
- if (arguments[1] is String) {
- val expression = arguments[1] as String
- Jazzer.guideTowardsContainment(expression, EXPRESSION_LANGUAGE_ATTACK, hookId)
- }
+ val expression = arguments[1] as? String ?: return
+ Jazzer.guideTowardsContainment(expression, EXPRESSION_LANGUAGE_ATTACK, hookId)
}
// With default configurations the argument to
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt
new file mode 100644
index 00000000..1afd614e
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt
@@ -0,0 +1,123 @@
+// Copyright 2021 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.FuzzerSecurityIssueCritical
+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.Exception
+import java.lang.invoke.MethodHandle
+import javax.naming.NamingException
+import javax.naming.directory.InvalidSearchFilterException
+
+/**
+ * Detects LDAP DN and search filter injections.
+ *
+ * Untrusted input has to be escaped in such a way that queries remain valid otherwise an injection
+ * could be possible. This sanitizer guides the fuzzer to inject insecure characters. If an exception
+ * is raised during execution the fuzzer was able to inject an invalid pattern, otherwise all input
+ * was escaped correctly.
+ *
+ * Only the search methods are hooked, other methods are not used in injection attacks. Furthermore,
+ * only string parameters are checked, [javax.naming.Name] already validates inputs according to RFC2253.
+ *
+ * [javax.naming.directory.InitialDirContext] creates an initial context through the context factory
+ * stated in [javax.naming.Context.INITIAL_CONTEXT_FACTORY]. Other method calls are delegated to the
+ * initial context factory of type [javax.naming.directory.DirContext]. This is also the case for
+ * subclass [javax.naming.ldap.InitialLdapContext].
+ */
+@Suppress("unused_parameter", "unused")
+object LdapInjection {
+
+ // Characters to escape in DNs
+ private const val NAME_CHARACTERS = "\\+<>,;\"="
+
+ // Characters to escape in search filter queries
+ private const val FILTER_CHARACTERS = "*()\\\u0000"
+
+ @MethodHooks(
+ // Single object lookup, possible DN injection
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.directory.DirContext",
+ targetMethod = "search",
+ targetMethodDescriptor = "(Ljava/lang/String;Ljavax/naming.directory/Attributes;)Ljavax/naming/NamingEnumeration;",
+ additionalClassesToHook = ["javax.naming.directory.InitialDirContext"]
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.directory.DirContext",
+ targetMethod = "search",
+ targetMethodDescriptor = "(Ljava/lang/String;Ljavax/naming.directory/Attributes;[Ljava/lang/Sting;)Ljavax/naming/NamingEnumeration;",
+ additionalClassesToHook = ["javax.naming.directory.InitialDirContext"]
+ ),
+
+ // Object search, possible DN and search filter injection
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.directory.DirContext",
+ targetMethod = "search",
+ targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/String;Ljavax/naming/directory/SearchControls;)Ljavax/naming/NamingEnumeration;",
+ additionalClassesToHook = ["javax.naming.directory.InitialDirContext"]
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.directory.DirContext",
+ targetMethod = "search",
+ targetMethodDescriptor = "(Ljavax/naming/Name;Ljava/lang/String;[Ljava.lang.Object;Ljavax/naming/directory/SearchControls;)Ljavax/naming/NamingEnumeration;",
+ additionalClassesToHook = ["javax.naming.directory.InitialDirContext"]
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.directory.DirContext",
+ targetMethod = "search",
+ targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;Ljavax/naming/directory/SearchControls;)Ljavax/naming/NamingEnumeration;",
+ additionalClassesToHook = ["javax.naming.directory.InitialDirContext"]
+ )
+ )
+ @JvmStatic
+ fun searchLdapContext(method: MethodHandle, thisObject: Any?, args: Array<Any>, hookId: Int): Any? {
+ try {
+ return method.invokeWithArguments(thisObject, *args).also {
+ (args[0] as? String)?.let { name ->
+ Jazzer.guideTowardsEquality(name, NAME_CHARACTERS, hookId)
+ }
+ (args[1] as? String)?.let { filter ->
+ Jazzer.guideTowardsEquality(filter, FILTER_CHARACTERS, 31 * hookId)
+ }
+ }
+ } catch (e: Exception) {
+ when (e) {
+ is InvalidSearchFilterException ->
+ Jazzer.reportFindingFromHook(
+ FuzzerSecurityIssueCritical(
+ """LDAP Injection
+Search filters based on untrusted data must be escape as specified in RFC 4515."""
+ )
+ )
+ is NamingException ->
+ Jazzer.reportFindingFromHook(
+ FuzzerSecurityIssueCritical(
+ """LDAP Injection
+Distinguished Names based on untrusted data must be escaped as specified in RFC 2253."""
+ )
+ )
+ }
+ throw e
+ }
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt
index 2d4fb9cf..56e12f03 100644
--- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt
@@ -22,6 +22,7 @@ import com.code_intelligence.jazzer.api.MethodHooks
import java.lang.invoke.MethodHandle
import javax.naming.CommunicationException
+@Suppress("unused")
object NamingContextLookup {
// The particular URL g.co is used here since it is:
@@ -31,6 +32,7 @@ object NamingContextLookup {
private const val LDAP_MARKER = "ldap://g.co/"
private const val RMI_MARKER = "rmi://g.co/"
+ @Suppress("UNUSED_PARAMETER")
@MethodHooks(
MethodHook(
type = HookType.REPLACE,
@@ -40,46 +42,10 @@ object NamingContextLookup {
),
MethodHook(
type = HookType.REPLACE,
- targetClassName = "javax.naming.InitialContext",
- targetMethod = "lookup",
- targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
- ),
- MethodHook(
- type = HookType.REPLACE,
- targetClassName = "javax.naming.InitialDirContext",
- targetMethod = "lookup",
- targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
- ),
- MethodHook(
- type = HookType.REPLACE,
- targetClassName = "javax.naming.InitialLdapContext",
- targetMethod = "lookup",
- targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
- ),
- MethodHook(
- type = HookType.REPLACE,
targetClassName = "javax.naming.Context",
targetMethod = "lookupLink",
targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
),
- MethodHook(
- type = HookType.REPLACE,
- targetClassName = "javax.naming.InitialContext",
- targetMethod = "lookupLink",
- targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
- ),
- MethodHook(
- type = HookType.REPLACE,
- targetClassName = "javax.naming.InitialDirContext",
- targetMethod = "lookupLink",
- targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
- ),
- MethodHook(
- type = HookType.REPLACE,
- targetClassName = "javax.naming.InitialLdapContext",
- targetMethod = "lookupLink",
- targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
- ),
)
@JvmStatic
fun lookupHook(method: MethodHandle?, thisObject: Any?, args: Array<Any?>, hookId: Int): Any {
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt
new file mode 100644
index 00000000..d3adc207
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt
@@ -0,0 +1,61 @@
+// Copyright 2021 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.FuzzerSecurityIssueCritical
+import com.code_intelligence.jazzer.api.HookType
+import com.code_intelligence.jazzer.api.Jazzer
+import com.code_intelligence.jazzer.api.MethodHook
+import java.lang.invoke.MethodHandle
+
+/**
+ * Detects unsafe execution of OS commands using [ProcessBuilder].
+ * Executing OS commands based on attacker-controlled data could lead to arbitrary could execution.
+ *
+ * All public methods providing the command to execute end up in [java.lang.ProcessImpl.start],
+ * so calls to this method are hooked.
+ * Only the first entry of the given command array is analyzed. It states the executable and must
+ * not include attacker provided data.
+ */
+@Suppress("unused_parameter", "unused")
+object OsCommandInjection {
+
+ // Short and probably non-existing command name
+ private const val COMMAND = "jazze"
+
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.lang.ProcessImpl",
+ targetMethod = "start",
+ additionalClassesToHook = ["java.lang.ProcessBuilder"]
+ )
+ @JvmStatic
+ fun processImplStartHook(method: MethodHandle?, alwaysNull: Any?, args: Array<Any?>, hookId: Int) {
+ // Calling ProcessBuilder already checks if command array is empty
+ @Suppress("UNCHECKED_CAST")
+ (args[0] as? Array<String>)?.first().let { cmd ->
+ if (cmd == COMMAND) {
+ Jazzer.reportFindingFromHook(
+ FuzzerSecurityIssueCritical(
+ """OS Command Injection
+Executing OS commands with attacker-controlled data can lead to remote code execution."""
+ )
+ )
+ } else {
+ Jazzer.guideTowardsEquality(cmd, COMMAND, hookId)
+ }
+ }
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt
index 7842d879..0fcabe36 100644
--- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt
@@ -14,21 +14,59 @@
package com.code_intelligence.jazzer.sanitizers
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh
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
/**
- * Detects unsafe reflective calls that lead to attacker-controlled method calls.
+ * Detects unsafe calls that lead to attacker-controlled class loading.
+ *
+ * Guide the fuzzer to load honeypot class via [Class.forName] or [ClassLoader.loadClass].
*/
-@Suppress("unused_parameter")
+@Suppress("unused_parameter", "unused")
object ReflectiveCall {
- @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName")
+ @MethodHooks(
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Class;"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Class;"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/String;Z)Ljava/lang/Class;"),
+ )
@JvmStatic
- fun classForNameHook(method: MethodHandle?, alwaysNull: Any?, args: Array<Any?>, hookId: Int) {
+ fun loadClassHook(method: MethodHandle?, alwaysNull: Any?, args: Array<Any?>, hookId: Int) {
val className = args[0] as? String ?: return
Jazzer.guideTowardsEquality(className, HONEYPOT_CLASS_NAME, hookId)
}
+
+ @MethodHooks(
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;"),
+ )
+ @JvmStatic
+ fun loadClassWithModuleHook(method: MethodHandle?, alwaysNull: Any?, args: Array<Any?>, hookId: Int) {
+ val className = args[1] as? String ?: return
+ Jazzer.guideTowardsEquality(className, HONEYPOT_CLASS_NAME, hookId)
+ }
+
+ @MethodHooks(
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", targetMethod = "load"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", targetMethod = "loadLibrary"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "load"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "loadLibrary"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "mapLibraryName"),
+ MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "findLibrary"),
+ )
+ @JvmStatic
+ fun loadLibraryHook(method: MethodHandle?, alwaysNull: Any?, args: Array<Any?>, hookId: Int) {
+ val libraryName = args[0] as? String ?: return
+ if (libraryName == HONEYPOT_LIBRARY_NAME) {
+ Jazzer.reportFindingFromHook(
+ FuzzerSecurityIssueHigh("load arbitrary library")
+ )
+ }
+ Jazzer.guideTowardsEquality(libraryName, HONEYPOT_LIBRARY_NAME, hookId)
+ }
}
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
+ }
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java
new file mode 100644
index 00000000..1043ac02
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java
@@ -0,0 +1,322 @@
+// 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 static com.code_intelligence.jazzer.sanitizers.utils.ReflectionUtils.INVALID_OFFSET;
+import static com.code_intelligence.jazzer.sanitizers.utils.ReflectionUtils.field;
+import static com.code_intelligence.jazzer.sanitizers.utils.ReflectionUtils.nestedClass;
+import static com.code_intelligence.jazzer.sanitizers.utils.ReflectionUtils.offset;
+
+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.runtime.UnsafeProvider;
+import java.lang.invoke.MethodHandle;
+import java.util.WeakHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import sun.misc.Unsafe;
+
+/**
+ * The hooks in this class extend the reach of Jazzer's string compare instrumentation to literals
+ * (both strings and characters) that are part of regular expression patterns.
+ * <p>
+ * Internally, the Java standard library represents a compiled regular expression as a graph of
+ * instances of Pattern$Node instances, each of which represents a single unit of the full
+ * expression and provides a `match` function that takes a {@link Matcher}, a {@link CharSequence}
+ * to match against and an index into the sequence. With a hook on this method for every subclass of
+ * Pattern$Node, the contents of the node can be inspected and an appropriate string comparison
+ * between the relevant part of the input string and the literal string can be reported.
+ */
+public final class RegexRoadblocks {
+ // The number of characters preceding one that failed a character predicate to include in the
+ // reported string comparison.
+ private static final int CHARACTER_COMPARE_CONTEXT_LENGTH = 10;
+
+ private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe();
+ private static final Class<?> SLICE_NODE = nestedClass(Pattern.class, "SliceNode");
+ private static final long SLICE_NODE_BUFFER_OFFSET =
+ offset(UNSAFE, field(SLICE_NODE, "buffer", int[].class));
+ private static final Class<?> CHAR_PREDICATE = nestedClass(Pattern.class, "CharPredicate");
+ private static final Class<?> CHAR_PROPERTY = nestedClass(Pattern.class, "CharProperty");
+ private static final long CHAR_PROPERTY_PREDICATE_OFFSET = offset(
+ UNSAFE, field(CHAR_PROPERTY, "predicate", nestedClass(Pattern.class, "CharPredicate")));
+ private static final Class<?> BIT_CLASS = nestedClass(Pattern.class, "BitClass");
+ private static final long BIT_CLASS_BITS_OFFSET =
+ offset(UNSAFE, field(BIT_CLASS, "bits", boolean[].class));
+
+ // Weakly map CharPredicate instances to characters that satisfy the predicate. Since
+ // CharPredicate instances are usually lambdas, we collect their solutions by hooking the
+ // functions constructing them rather than extracting the solutions via reflection.
+ // Note: Java 8 uses anonymous subclasses of CharProperty instead of lambdas implementing
+ // CharPredicate, hence CharProperty instances are used as keys instead in that case.
+ private static final ThreadLocal<WeakHashMap<Object, Character>> PREDICATE_SOLUTIONS =
+ ThreadLocal.withInitial(WeakHashMap::new);
+
+ // Do not act on instrumented regexes used by Jazzer internally, e.g. by ClassGraph.
+ private static boolean HOOK_DISABLED = true;
+
+ static {
+ Jazzer.onFuzzTargetReady(() -> HOOK_DISABLED = UNSAFE == null);
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$Node",
+ targetMethod = "match",
+ targetMethodDescriptor = "(Ljava/util/regex/Matcher;ILjava/lang/CharSequence;)Z",
+ additionalClassesToHook =
+ {
+ "java.util.regex.Matcher",
+ "java.util.regex.Pattern$BackRef",
+ "java.util.regex.Pattern$Behind",
+ "java.util.regex.Pattern$BehindS",
+ "java.util.regex.Pattern$BmpCharProperty",
+ "java.util.regex.Pattern$BmpCharPropertyGreedy",
+ "java.util.regex.Pattern$BnM",
+ "java.util.regex.Pattern$BnMS",
+ "java.util.regex.Pattern$Bound",
+ "java.util.regex.Pattern$Branch",
+ "java.util.regex.Pattern$BranchConn",
+ "java.util.regex.Pattern$CharProperty",
+ "java.util.regex.Pattern$CharPropertyGreedy",
+ "java.util.regex.Pattern$CIBackRef",
+ "java.util.regex.Pattern$Caret",
+ "java.util.regex.Pattern$Curly",
+ "java.util.regex.Pattern$Conditional",
+ "java.util.regex.Pattern$First",
+ "java.util.regex.Pattern$GraphemeBound",
+ "java.util.regex.Pattern$GroupCurly",
+ "java.util.regex.Pattern$GroupHead",
+ "java.util.regex.Pattern$GroupRef",
+ "java.util.regex.Pattern$LastMatch",
+ "java.util.regex.Pattern$LazyLoop",
+ "java.util.regex.Pattern$LineEnding",
+ "java.util.regex.Pattern$Loop",
+ "java.util.regex.Pattern$Neg",
+ "java.util.regex.Pattern$NFCCharProperty",
+ "java.util.regex.Pattern$NotBehind",
+ "java.util.regex.Pattern$NotBehindS",
+ "java.util.regex.Pattern$Pos",
+ "java.util.regex.Pattern$Ques",
+ "java.util.regex.Pattern$Slice",
+ "java.util.regex.Pattern$SliceI",
+ "java.util.regex.Pattern$SliceIS",
+ "java.util.regex.Pattern$SliceS",
+ "java.util.regex.Pattern$SliceU",
+ "java.util.regex.Pattern$Start",
+ "java.util.regex.Pattern$StartS",
+ "java.util.regex.Pattern$UnixCaret",
+ "java.util.regex.Pattern$UnixDollar",
+ "java.util.regex.Pattern$XGrapheme",
+ })
+ public static void
+ nodeMatchHook(MethodHandle method, Object node, Object[] args, int hookId, Boolean matched) {
+ if (HOOK_DISABLED || matched || node == null)
+ return;
+ Matcher matcher = (Matcher) args[0];
+ if (matcher == null)
+ return;
+ int i = (int) args[1];
+ CharSequence seq = (CharSequence) args[2];
+ if (seq == null)
+ return;
+
+ if (SLICE_NODE != null && SLICE_NODE.isInstance(node)) {
+ // The node encodes a match against a fixed string literal. Extract the literal and report a
+ // comparison between it and the subsequence of seq starting at i.
+ if (SLICE_NODE_BUFFER_OFFSET == INVALID_OFFSET)
+ return;
+ int currentLength = limitedLength(matcher.regionEnd() - i);
+ String current = seq.subSequence(i, i + currentLength).toString();
+
+ // All the subclasses of SliceNode store the literal in an int[], which we have to truncate to
+ // a char[].
+ int[] buffer = (int[]) UNSAFE.getObject(node, SLICE_NODE_BUFFER_OFFSET);
+ char[] charBuffer = new char[limitedLength(buffer.length)];
+ for (int j = 0; j < charBuffer.length; j++) {
+ charBuffer[j] = (char) buffer[j];
+ }
+ String target = new String(charBuffer);
+
+ Jazzer.guideTowardsEquality(current, target, perRegexId(hookId, matcher));
+ } else if (CHAR_PROPERTY != null && CHAR_PROPERTY.isInstance(node)) {
+ // The node encodes a match against a class of characters, which may be hard to guess unicode
+ // characters. We rely on further hooks to track the relation between these nodes and
+ // characters satisfying their match function since the nodes themselves encode this
+ // information in lambdas, which are difficult to dissect via reflection. If we know a
+ // matching character, report a one-character (plus context) string comparison.
+ Object solutionKey;
+ if (CHAR_PROPERTY_PREDICATE_OFFSET == INVALID_OFFSET) {
+ if (CHAR_PREDICATE == null) {
+ // We are likely running against JDK 8, which directly construct subclasses of
+ // CharProperty rather than using lambdas implementing CharPredicate.
+ solutionKey = node;
+ } else {
+ return;
+ }
+ } else {
+ solutionKey = UNSAFE.getObject(node, CHAR_PROPERTY_PREDICATE_OFFSET);
+ }
+ if (solutionKey == null)
+ return;
+ Character solution = predicateSolution(solutionKey);
+ if (solution == null)
+ return;
+ // We report a string comparison rather than an integer comparison for two reasons:
+ // 1. If the characters are four byte codepoints, they will be coded on six bytes (a surrogate
+ // pair) in CESU-8, which is the encoding assumed for the fuzzer input, whereas ASCII
+ // characters will be coded on a single byte. By using the string compare hook, we do not
+ // have to worry about the encoding at this point.
+ // 2. The same character can appear multiple times in both the pattern and the matched string,
+ // which makes it harder for the fuzzer to determine the correct position to mutate the
+ // current character into the matching character. By providing a short section of the
+ // input string preceding the incorrect character, we increase the chance of a hit.
+ String context =
+ seq.subSequence(Math.max(0, i - CHARACTER_COMPARE_CONTEXT_LENGTH), i).toString();
+ String current = seq.subSequence(i, Math.min(i + 1, matcher.regionEnd())).toString();
+ String target = Character.toString(solution);
+ Jazzer.guideTowardsEquality(context + current, context + target, perRegexId(hookId, matcher));
+ }
+ }
+
+ // This and all following hooks track the relation between a CharPredicate or CharProperty
+ // instance and a character that matches it. We use an after hook on the factory methods so that
+ // we have access to the parameters and the created instance at the same time.
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "Single",
+ targetMethodDescriptor = "(I)Ljava/util/regex/Pattern$BmpCharPredicate;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "SingleI",
+ targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharPredicate;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "SingleS",
+ targetMethodDescriptor = "(I)Ljava/util/regex/Pattern$CharPredicate;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "SingleU",
+ targetMethodDescriptor = "(I)Ljava/util/regex/Pattern$CharPredicate;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ public static void
+ singleHook(MethodHandle method, Object node, Object[] args, int hookId, Object predicate) {
+ if (HOOK_DISABLED || predicate == null)
+ return;
+ PREDICATE_SOLUTIONS.get().put(predicate, (char) (int) args[0]);
+ }
+
+ // Java 8 uses classes extending CharProperty instead of lambdas implementing CharPredicate to
+ // match single characters, so also hook those.
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$Single",
+ targetMethod = "<init>", additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$SingleI",
+ targetMethod = "<init>", additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$SingleS",
+ targetMethod = "<init>", additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$SingleU",
+ targetMethod = "<init>", additionalClassesToHook = {"java.util.regex.Pattern"})
+ public static void
+ java8SingleHook(
+ MethodHandle method, Object property, Object[] args, int hookId, Object alwaysNull) {
+ if (HOOK_DISABLED || property == null)
+ return;
+ PREDICATE_SOLUTIONS.get().put(property, (char) (int) args[0]);
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "Range",
+ targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharPredicate;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "CIRange",
+ targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharPredicate;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "CIRangeU",
+ targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharPredicate;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ // Java 8 uses anonymous classes extending CharProperty instead of lambdas implementing
+ // CharPredicate to match single characters, so also hook those.
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "rangeFor",
+ targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharProperty;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "caseInsensitiveRangeFor",
+ targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharProperty;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ public static void
+ rangeHook(MethodHandle method, Object node, Object[] args, int hookId, Object predicate) {
+ if (HOOK_DISABLED || predicate == null)
+ return;
+ PREDICATE_SOLUTIONS.get().put(predicate, (char) (int) args[0]);
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$CharPredicate",
+ targetMethod = "union",
+ targetMethodDescriptor =
+ "(Ljava/util/regex/Pattern$CharPredicate;)Ljava/util/regex/Pattern$CharPredicate;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ // Java 8 uses anonymous classes extending CharProperty instead of lambdas implementing
+ // CharPredicate to match single characters, so also hook union for those. Even though the classes
+ // of the parameters will be different, the actual implementation of the hook is the same in this
+ // case.
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern",
+ targetMethod = "union",
+ targetMethodDescriptor =
+ "(Ljava/util/regex/Pattern$CharProperty;Ljava/util/regex/Pattern$CharProperty;)Ljava/util/regex/Pattern$CharProperty;",
+ additionalClassesToHook = {"java.util.regex.Pattern"})
+ public static void
+ unionHook(
+ MethodHandle method, Object thisObject, Object[] args, int hookId, Object unionPredicate) {
+ if (HOOK_DISABLED || unionPredicate == null)
+ return;
+ Character solution = predicateSolution(thisObject);
+ if (solution == null)
+ solution = predicateSolution(args[0]);
+ if (solution == null)
+ return;
+ PREDICATE_SOLUTIONS.get().put(unionPredicate, solution);
+ }
+
+ private static Character predicateSolution(Object charPredicate) {
+ return PREDICATE_SOLUTIONS.get().computeIfAbsent(charPredicate, unused -> {
+ if (BIT_CLASS != null && BIT_CLASS.isInstance(charPredicate)) {
+ // BitClass instances have an empty bits array at construction time, so we scan their
+ // constants lazily when needed.
+ boolean[] bits = (boolean[]) UNSAFE.getObject(charPredicate, BIT_CLASS_BITS_OFFSET);
+ for (int i = 0; i < bits.length; i++) {
+ if (bits[i]) {
+ PREDICATE_SOLUTIONS.get().put(charPredicate, (char) i);
+ return (char) i;
+ }
+ }
+ }
+ return null;
+ });
+ }
+
+ // Limits a length to the maximum length libFuzzer will read up to in a callback.
+ private static int limitedLength(int length) {
+ return Math.min(length, 64);
+ }
+
+ // hookId only takes one distinct value per Node subclass. In order to get different regex matches
+ // to be tracked similar to different instances of string compares, we mix in the hash of the
+ // underlying pattern. We expect patterns to be static almost always, so that this should not fill
+ // up the value profile map too quickly.
+ private static int perRegexId(int hookId, Matcher matcher) {
+ return hookId ^ matcher.pattern().toString().hashCode();
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt
new file mode 100644
index 00000000..f317bcc8
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt
@@ -0,0 +1,113 @@
+// 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.FuzzerSecurityIssueHigh
+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 net.sf.jsqlparser.JSQLParserException
+import net.sf.jsqlparser.parser.CCJSqlParserUtil
+import java.lang.invoke.MethodHandle
+
+/**
+ * Detects SQL injections.
+ *
+ * Untrusted input has to be escaped in such a way that queries remain valid otherwise an injection
+ * could be possible. This sanitizer guides the fuzzer to inject insecure characters. If an exception
+ * is raised during execution the fuzzer was able to inject an invalid pattern, otherwise all input
+ * was escaped correctly.
+ *
+ * Two types of methods are hooked:
+ * 1. Methods that take an SQL query as the first argument (e.g. [java.sql.Statement.execute]).
+ * 2. Methods that don't take any arguments and execute an already prepared statement
+ * (e.g. [java.sql.PreparedStatement.execute]).
+ * For 1. we validate the syntax of the query using <a href="https://github.com/JSQLParser/JSqlParser">jsqlparser</a>
+ * and if both the syntax is invalid and the query execution throws an exception we report an SQL injection.
+ * Since we can't reliably validate SQL queries in arbitrary dialects this hook is expected to produce some
+ * amount of false positives.
+ * For 2. we can't validate the query syntax and therefore only rethrow any exceptions.
+ */
+@Suppress("unused_parameter", "unused")
+object SqlInjection {
+
+ // Characters that should be escaped in user input.
+ // See https://dev.mysql.com/doc/refman/8.0/en/string-literals.html
+ private const val CHARACTERS_TO_ESCAPE = "'\"\b\n\r\t\\%_"
+
+ private val SQL_SYNTAX_ERROR_EXCEPTIONS = listOf(
+ "java.sql.SQLException",
+ "java.sql.SQLNonTransientException",
+ "java.sql.SQLSyntaxErrorException",
+ "org.h2.jdbc.JdbcSQLSyntaxErrorException",
+ )
+
+ @MethodHooks(
+ MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "execute"),
+ MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeBatch"),
+ MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeLargeBatch"),
+ MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeLargeUpdate"),
+ MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeQuery"),
+ MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeUpdate"),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.persistence.EntityManager",
+ targetMethod = "createNativeQuery"
+ )
+ )
+ @JvmStatic
+ fun checkSqlExecute(method: MethodHandle, thisObject: Any?, arguments: Array<Any>, hookId: Int): Any {
+ var hasValidSqlQuery = false
+
+ if (arguments.isNotEmpty() && arguments[0] is String) {
+ val query = arguments[0] as String
+ hasValidSqlQuery = isValidSql(query)
+ Jazzer.guideTowardsContainment(query, CHARACTERS_TO_ESCAPE, hookId)
+ }
+ return try {
+ method.invokeWithArguments(thisObject, *arguments)
+ } catch (throwable: Throwable) {
+ // If we already validated the query string and know it's correct,
+ // The exception is likely thrown by a non-existent table or something
+ // that we don't want to report.
+ if (!hasValidSqlQuery && SQL_SYNTAX_ERROR_EXCEPTIONS.contains(throwable.javaClass.name)) {
+ Jazzer.reportFindingFromHook(
+ FuzzerSecurityIssueHigh(
+ """
+ SQL Injection
+ Injected query: ${arguments[0]}
+ """.trimIndent(),
+ throwable
+ )
+ )
+ }
+ throw throwable
+ }
+ }
+
+ private fun isValidSql(sql: String): Boolean =
+ try {
+ CCJSqlParserUtil.parseStatements(sql)
+ true
+ } catch (e: JSQLParserException) {
+ false
+ } catch (t: Throwable) {
+ // Catch any unexpected exceptions so that we don't disturb the
+ // instrumented application.
+ t.printStackTrace()
+ true
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt
index 3166773b..219490d8 100644
--- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt
@@ -21,6 +21,7 @@ import java.io.InputStream
* jaz.Zer is a honeypot class: All of its methods report a finding when called.
*/
const val HONEYPOT_CLASS_NAME = "jaz.Zer"
+const val HONEYPOT_LIBRARY_NAME = "jazzer_honeypot"
internal fun Short.toBytes(): ByteArray {
return byteArrayOf(
@@ -43,9 +44,20 @@ internal fun ByteArray.indexOf(needle: ByteArray): Int {
}
internal fun guideMarkableInputStreamTowardsEquality(stream: InputStream, target: ByteArray, id: Int) {
+ fun readBytes(stream: InputStream, size: Int): ByteArray {
+ val current = ByteArray(size)
+ var n = 0
+ while (n < size) {
+ val count = stream.read(current, n, size - n)
+ if (count < 0) break
+ n += count
+ }
+ return current
+ }
+
check(stream.markSupported())
stream.mark(target.size)
- val current = stream.readNBytes(target.size)
+ val current = readBytes(stream, target.size)
stream.reset()
Jazzer.guideTowardsEquality(current, target, id)
}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel
new file mode 100644
index 00000000..c7258447
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel
@@ -0,0 +1,7 @@
+java_library(
+ name = "reflection_utils",
+ srcs = ["ReflectionUtils.java"],
+ visibility = [
+ "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers:__pkg__",
+ ],
+)
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/ReflectionUtils.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/ReflectionUtils.java
new file mode 100644
index 00000000..fd6ac72f
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/ReflectionUtils.java
@@ -0,0 +1,62 @@
+// 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.utils;
+
+import java.lang.reflect.Field;
+import sun.misc.Unsafe;
+
+public final class ReflectionUtils {
+ public static final long INVALID_OFFSET = Long.MIN_VALUE;
+
+ private static final boolean JAZZER_REFLECTION_DEBUG =
+ "1".equals(System.getenv("JAZZER_REFLECTION_DEBUG"));
+
+ public static Class<?> clazz(String className) {
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ if (JAZZER_REFLECTION_DEBUG)
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static Class<?> nestedClass(Class<?> parentClass, String nestedClassName) {
+ return clazz(parentClass.getName() + "$" + nestedClassName);
+ }
+
+ public static Field field(Class<?> clazz, String name, Class<?> type) {
+ if (clazz == null)
+ return null;
+ try {
+ Field field = clazz.getDeclaredField(name);
+ if (!field.getType().equals(type)) {
+ throw new NoSuchFieldException(
+ "Expected " + name + " to be of type " + type + " (is: " + field.getType() + ")");
+ }
+ return field;
+ } catch (NoSuchFieldException e) {
+ if (JAZZER_REFLECTION_DEBUG)
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static long offset(Unsafe unsafe, Field field) {
+ if (unsafe == null || field == null)
+ return INVALID_OFFSET;
+ return unsafe.objectFieldOffset(field);
+ }
+}
diff --git a/sanitizers/src/main/java/jaz/BUILD.bazel b/sanitizers/src/main/java/jaz/BUILD.bazel
deleted file mode 100644
index 81275a31..00000000
--- a/sanitizers/src/main/java/jaz/BUILD.bazel
+++ /dev/null
@@ -1,12 +0,0 @@
-java_library(
- name = "jaz",
- srcs = [
- "Ter.java",
- "Zer.java",
- ],
- visibility = [
- "//sanitizers:__pkg__",
- "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers:__pkg__",
- ],
- deps = ["//agent:jazzer_api_compile_only"],
-)
diff --git a/sanitizers/src/main/java/jaz/Zer.java b/sanitizers/src/main/java/jaz/Zer.java
deleted file mode 100644
index 0b27609c..00000000
--- a/sanitizers/src/main/java/jaz/Zer.java
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2021 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 jaz;
-
-import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
-import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium;
-import com.code_intelligence.jazzer.api.Jazzer;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-
-/**
- * A honeypot class that reports an appropriate finding on any interaction with one of its methods
- * or initializers.
- *
- * Note: This class must not be referenced in any way by the rest of the code, not even statically.
- * When referring to it, always use its hardcoded class name "jaz.Zer".
- */
-@SuppressWarnings("unused")
-public class Zer implements java.io.Serializable {
- static final long serialVersionUID = 42L;
-
- private static final Throwable staticInitializerCause;
-
- static {
- staticInitializerCause = new FuzzerSecurityIssueMedium("finalize call on arbitrary object");
- }
-
- public Zer() {
- Jazzer.reportFindingFromHook(
- new FuzzerSecurityIssueMedium("default constructor call on arbitrary object"));
- }
-
- public Zer(String arg1) {
- Jazzer.reportFindingFromHook(
- new FuzzerSecurityIssueMedium("String constructor call on arbitrary object"));
- }
-
- public Zer(String arg1, Throwable arg2) {
- Jazzer.reportFindingFromHook(
- new FuzzerSecurityIssueMedium("(String, Throwable) constructor call on arbitrary object"));
- }
-
- private String jaz;
-
- public String getJaz() {
- Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("getter call on arbitrary object"));
- return jaz;
- }
-
- public void setJaz(String jaz) {
- Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("setter call on arbitrary object"));
- this.jaz = jaz;
- }
-
- @Override
- public int hashCode() {
- Jazzer.reportFindingFromHook(
- new FuzzerSecurityIssueMedium("hashCode call on arbitrary object"));
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("equals call on arbitrary object"));
- return super.equals(obj);
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("clone call on arbitrary object"));
- return super.clone();
- }
-
- @Override
- public String toString() {
- Jazzer.reportFindingFromHook(
- new FuzzerSecurityIssueMedium("toString call on arbitrary object"));
- return super.toString();
- }
-
- @Override
- protected void finalize() throws Throwable {
- // finalize is invoked automatically by the GC with an uninformative stack trace. We use the
- // stack trace prerecorded in the static initializer.
- Jazzer.reportFindingFromHook(staticInitializerCause);
- super.finalize();
- }
-
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
- Jazzer.reportFindingFromHook(new FuzzerSecurityIssueHigh("Remote Code Execution\n"
- + " Deserialization of arbitrary classes with custom readObject may allow remote\n"
- + " code execution depending on the classpath."));
- in.defaultReadObject();
- }
-}
diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel
index d148545a..5d2e1ca5 100644
--- a/sanitizers/src/test/java/com/example/BUILD.bazel
+++ b/sanitizers/src/test/java/com/example/BUILD.bazel
@@ -1,10 +1,12 @@
load("//bazel:fuzz_target.bzl", "java_fuzz_target_test")
+load("//bazel:compat.bzl", "SKIP_ON_MACOS")
java_fuzz_target_test(
name = "ObjectInputStreamDeserialization",
srcs = [
"ObjectInputStreamDeserialization.java",
],
+ expected_findings = ["java.lang.ExceptionInInitializerError"],
target_class = "com.example.ObjectInputStreamDeserialization",
)
@@ -13,10 +15,22 @@ java_fuzz_target_test(
srcs = [
"ReflectiveCall.java",
],
+ expected_findings = ["java.lang.ExceptionInInitializerError"],
target_class = "com.example.ReflectiveCall",
)
java_fuzz_target_test(
+ name = "LibraryLoad",
+ srcs = [
+ "LibraryLoad.java",
+ ],
+ target_class = "com.example.LibraryLoad",
+ # loading of native libraries is very slow on macos,
+ # especially using Java 17
+ target_compatible_with = SKIP_ON_MACOS,
+)
+
+java_fuzz_target_test(
name = "ExpressionLanguageInjection",
srcs = [
"ExpressionLanguageInjection.java",
@@ -27,6 +41,100 @@ java_fuzz_target_test(
"@maven//:javax_el_javax_el_api",
"@maven//:javax_validation_validation_api",
"@maven//:javax_xml_bind_jaxb_api",
+ "@maven//:org_glassfish_javax_el",
"@maven//:org_hibernate_hibernate_validator",
],
)
+
+java_fuzz_target_test(
+ name = "OsCommandInjectionProcessBuilder",
+ srcs = [
+ "OsCommandInjectionProcessBuilder.java",
+ ],
+ target_class = "com.example.OsCommandInjectionProcessBuilder",
+)
+
+java_fuzz_target_test(
+ name = "OsCommandInjectionRuntimeExec",
+ srcs = [
+ "OsCommandInjectionRuntimeExec.java",
+ ],
+ target_class = "com.example.OsCommandInjectionRuntimeExec",
+)
+
+java_fuzz_target_test(
+ name = "LdapSearchInjection",
+ srcs = [
+ "LdapSearchInjection.java",
+ "ldap/MockInitialContextFactory.java",
+ "ldap/MockLdapContext.java",
+ ],
+ expected_findings = ["javax.naming.directory.InvalidSearchFilterException"],
+ target_class = "com.example.LdapSearchInjection",
+ deps = [
+ "@maven//:com_unboundid_unboundid_ldapsdk",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "LdapDnInjection",
+ srcs = [
+ "LdapDnInjection.java",
+ "ldap/MockInitialContextFactory.java",
+ "ldap/MockLdapContext.java",
+ ],
+ expected_findings = ["javax.naming.NamingException"],
+ target_class = "com.example.LdapDnInjection",
+ deps = [
+ "@maven//:com_unboundid_unboundid_ldapsdk",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "RegexInsecureQuoteInjection",
+ srcs = ["RegexInsecureQuoteInjection.java"],
+ target_class = "com.example.RegexInsecureQuoteInjection",
+)
+
+java_fuzz_target_test(
+ name = "RegexCanonEqInjection",
+ srcs = [
+ "RegexCanonEqInjection.java",
+ ],
+ target_class = "com.example.RegexCanonEqInjection",
+)
+
+java_fuzz_target_test(
+ name = "ClassLoaderLoadClass",
+ srcs = [
+ "ClassLoaderLoadClass.java",
+ ],
+ expected_findings = ["java.lang.ExceptionInInitializerError"],
+ target_class = "com.example.ClassLoaderLoadClass",
+)
+
+java_fuzz_target_test(
+ name = "RegexRoadblocks",
+ srcs = ["RegexRoadblocks.java"],
+ fuzzer_args = [
+ # Limit the number of runs to verify that the regex roadblocks are
+ # cleared quickly.
+ "-runs=22000",
+ ],
+ target_class = "com.example.RegexRoadblocks",
+)
+
+java_fuzz_target_test(
+ name = "SqlInjection",
+ srcs = [
+ "SqlInjection.java",
+ ],
+ expected_findings = [
+ "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh",
+ "org.h2.jdbc.JdbcSQLSyntaxErrorException",
+ ],
+ target_class = "com.example.SqlInjection",
+ deps = [
+ "@maven//:com_h2database_h2",
+ ],
+)
diff --git a/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java b/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java
new file mode 100644
index 00000000..c3fa47ac
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java
@@ -0,0 +1,30 @@
+// Copyright 2021 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.lang.reflect.InvocationTargetException;
+
+public class ClassLoaderLoadClass {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) throws InterruptedException {
+ String input = data.consumeRemainingAsAsciiString();
+ try {
+ // create an instance to trigger class initialization
+ ClassLoaderLoadClass.class.getClassLoader().loadClass(input).getConstructor().newInstance();
+ } catch (ClassNotFoundException | InvocationTargetException | InstantiationException
+ | IllegalAccessException | NoSuchMethodException ignored) {
+ }
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/LdapDnInjection.java b/sanitizers/src/test/java/com/example/LdapDnInjection.java
new file mode 100644
index 00000000..911db1dc
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/LdapDnInjection.java
@@ -0,0 +1,39 @@
+// Copyright 2021 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.util.Hashtable;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+
+public class LdapDnInjection {
+ private static InitialDirContext ctx;
+
+ public static void fuzzerInitialize() throws NamingException {
+ Hashtable<String, String> env = new Hashtable<>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.example.ldap.MockInitialContextFactory");
+ ctx = new InitialDirContext(env);
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider fuzzedDataProvider) throws Exception {
+ // Externally provided DN input needs to be escaped properly
+ String ou = fuzzedDataProvider.consumeRemainingAsString();
+ String base = "ou=" + ou + ",dc=example,dc=com";
+ ctx.search(base, "(&(uid=foo)(cn=bar))", new SearchControls());
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/LdapSearchInjection.java b/sanitizers/src/test/java/com/example/LdapSearchInjection.java
new file mode 100644
index 00000000..b3dfee74
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/LdapSearchInjection.java
@@ -0,0 +1,39 @@
+// Copyright 2021 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.util.Hashtable;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchControls;
+import javax.naming.ldap.InitialLdapContext;
+
+public class LdapSearchInjection {
+ private static InitialLdapContext ctx;
+
+ public static void fuzzerInitialize() throws NamingException {
+ Hashtable<String, String> env = new Hashtable<>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.example.ldap.MockInitialContextFactory");
+ ctx = new InitialLdapContext(env, null);
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider fuzzedDataProvider) throws Exception {
+ // Externally provided LDAP query input needs to be escaped properly
+ String username = fuzzedDataProvider.consumeRemainingAsAsciiString();
+ String filter = "(&(uid=" + username + ")(ou=security))";
+ ctx.search("dc=example,dc=com", filter, new SearchControls());
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/LibraryLoad.java b/sanitizers/src/test/java/com/example/LibraryLoad.java
new file mode 100644
index 00000000..81411767
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/LibraryLoad.java
@@ -0,0 +1,29 @@
+// Copyright 2021 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public class LibraryLoad {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsAsciiString();
+
+ try {
+ System.loadLibrary(input);
+ } catch (SecurityException | UnsatisfiedLinkError | NullPointerException
+ | IllegalArgumentException ignored) {
+ }
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java b/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java
new file mode 100644
index 00000000..f5d52782
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java
@@ -0,0 +1,35 @@
+// Copyright 2021 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.util.concurrent.TimeUnit;
+
+public class OsCommandInjectionProcessBuilder {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsAsciiString();
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(input);
+ processBuilder.environment().clear();
+ Process process = processBuilder.start();
+ // This should be way faster, but we have to wait until the call is done
+ if (!process.waitFor(10, TimeUnit.MILLISECONDS)) {
+ process.destroyForcibly();
+ }
+ } catch (Exception ignored) {
+ // Ignore execution and setup exceptions
+ }
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java b/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java
new file mode 100644
index 00000000..c620a751
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java
@@ -0,0 +1,35 @@
+// Copyright 2021 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.example;
+
+import static java.lang.Runtime.getRuntime;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.util.concurrent.TimeUnit;
+
+public class OsCommandInjectionRuntimeExec {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsAsciiString();
+ try {
+ Process process = getRuntime().exec(input, new String[] {});
+ // This should be way faster, but we have to wait until the call is done
+ if (!process.waitFor(10, TimeUnit.MILLISECONDS)) {
+ process.destroyForcibly();
+ }
+ } catch (Exception ignored) {
+ // Ignore execution and setup exceptions
+ }
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/ReflectiveCall.java b/sanitizers/src/test/java/com/example/ReflectiveCall.java
index 7f85e486..e6b62b45 100644
--- a/sanitizers/src/test/java/com/example/ReflectiveCall.java
+++ b/sanitizers/src/test/java/com/example/ReflectiveCall.java
@@ -15,7 +15,6 @@
package com.example;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
-import java.lang.reflect.InvocationTargetException;
public class ReflectiveCall {
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
@@ -23,9 +22,8 @@ public class ReflectiveCall {
if (input.startsWith("@")) {
String className = input.substring(1);
try {
- Class.forName(className).getConstructor().newInstance();
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException
- | NoSuchMethodException | ClassNotFoundException ignored) {
+ Class.forName(className);
+ } catch (ClassNotFoundException ignored) {
}
}
}
diff --git a/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java b/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java
new file mode 100644
index 00000000..e2d0b722
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java
@@ -0,0 +1,41 @@
+// 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+public class RegexCanonEqInjection {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsString();
+ try {
+ Pattern.compile(Pattern.quote(input), Pattern.CANON_EQ);
+ } catch (PatternSyntaxException ignored) {
+ } catch (IllegalArgumentException ignored) {
+ // "[媼" generates an IllegalArgumentException but only on Windows using
+ // Java 8. We ignore this for now.
+ //
+ // java.lang.IllegalArgumentException
+ // at java.lang.AbstractStringBuilder.appendCodePoint(AbstractStringBuilder.java:800)
+ // at java.lang.StringBuilder.appendCodePoint(StringBuilder.java:240)
+ // at java.util.regex.Pattern.normalizeCharClass(Pattern.java:1430)
+ // at java.util.regex.Pattern.normalize(Pattern.java:1396)
+ // at java.util.regex.Pattern.compile(Pattern.java:1665)
+ // at java.util.regex.Pattern.<init>(Pattern.java:1352)
+ // at java.util.regex.Pattern.compile(Pattern.java:1054)
+ }
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/RegexInsecureQuoteInjection.java b/sanitizers/src/test/java/com/example/RegexInsecureQuoteInjection.java
new file mode 100644
index 00000000..a548cfb2
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/RegexInsecureQuoteInjection.java
@@ -0,0 +1,29 @@
+// 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+public class RegexInsecureQuoteInjection {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsString();
+ try {
+ Pattern.matches("\\Q" + input + "\\E", "foobar");
+ } catch (PatternSyntaxException ignored) {
+ }
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/RegexRoadblocks.java b/sanitizers/src/test/java/com/example/RegexRoadblocks.java
new file mode 100644
index 00000000..21986e3d
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/RegexRoadblocks.java
@@ -0,0 +1,89 @@
+// 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow;
+import java.util.regex.Pattern;
+
+public class RegexRoadblocks {
+ // We accept arbitrary suffixes but not prefixes for the following reasons:
+ // 1. The fuzzer will take much longer to match the exact length of the input than to satisfy the
+ // compare checks, which is what we really want to test.
+ // 2. Accepting arbitrary prefixes could lead to tests passing purely due to ToC entries being
+ // emitted in arbitrary positions, but we want to ensure that compares are correctly reported
+ // including position hints.
+ private static final Pattern LITERAL = Pattern.compile("foobarbaz.*");
+ private static final Pattern QUOTED_LITERAL = Pattern.compile(Pattern.quote("jazzer_is_cool.*"));
+ private static final Pattern CASE_INSENSITIVE_LITERAL =
+ Pattern.compile("JaZzER!.*", Pattern.CASE_INSENSITIVE);
+ private static final Pattern GROUP = Pattern.compile("(always).*");
+ private static final Pattern ALTERNATIVE = Pattern.compile("(to_be|not_to_be).*");
+ private static final Pattern SINGLE_LATIN1_CHAR_PROPERTY = Pattern.compile("[€].*");
+ private static final Pattern MULTIPLE_LATIN1_CHAR_PROPERTY = Pattern.compile("[ẞÄ].*");
+ private static final Pattern RANGE_LATIN1_CHAR_PROPERTY = Pattern.compile("[¢-¥].*");
+
+ private static int run = 0;
+
+ private static boolean matchedLiteral = false;
+ private static boolean matchedQuotedLiteral = false;
+ private static boolean matchedCaseInsensitiveLiteral = false;
+ private static boolean matchedGroup = false;
+ private static boolean matchedAlternative = false;
+ private static boolean matchedSingleLatin1CharProperty = false;
+ private static boolean matchedMultipleLatin1CharProperty = false;
+ private static boolean matchedRangeLatin1CharProperty = false;
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ run++;
+ String input = data.consumeRemainingAsString();
+
+ if (!matchedLiteral && LITERAL.matcher(input).matches()) {
+ System.out.println("Cleared LITERAL");
+ matchedLiteral = true;
+ } else if (!matchedQuotedLiteral && QUOTED_LITERAL.matcher(input).matches()) {
+ System.out.println("Cleared QUOTED_LITERAL");
+ matchedQuotedLiteral = true;
+ } else if (!matchedCaseInsensitiveLiteral
+ && CASE_INSENSITIVE_LITERAL.matcher(input).matches()) {
+ System.out.println("Cleared CASE_INSENSITIVE_LITERAL");
+ matchedCaseInsensitiveLiteral = true;
+ } else if (!matchedGroup && GROUP.matcher(input).matches()) {
+ System.out.println("Cleared GROUP");
+ matchedGroup = true;
+ } else if (!matchedAlternative && ALTERNATIVE.matcher(input).matches()) {
+ System.out.println("Cleared ALTERNATIVE");
+ matchedAlternative = true;
+ } else if (!matchedSingleLatin1CharProperty
+ && SINGLE_LATIN1_CHAR_PROPERTY.matcher(input).matches()) {
+ System.out.println("Cleared SINGLE_LATIN1_CHAR_PROPERTY");
+ matchedSingleLatin1CharProperty = true;
+ } else if (!matchedMultipleLatin1CharProperty
+ && MULTIPLE_LATIN1_CHAR_PROPERTY.matcher(input).matches()) {
+ System.out.println("Cleared MULTIPLE_LATIN1_CHAR_PROPERTY");
+ matchedMultipleLatin1CharProperty = true;
+ } else if (!matchedRangeLatin1CharProperty
+ && RANGE_LATIN1_CHAR_PROPERTY.matcher(input).matches()) {
+ System.out.println("Cleared RANGE_LATIN1_CHAR_PROPERTY");
+ matchedRangeLatin1CharProperty = true;
+ }
+
+ if (matchedLiteral && matchedQuotedLiteral && matchedCaseInsensitiveLiteral && matchedGroup
+ && matchedAlternative && matchedSingleLatin1CharProperty
+ && matchedMultipleLatin1CharProperty && matchedRangeLatin1CharProperty) {
+ throw new FuzzerSecurityIssueLow("Fuzzer matched all regexes in " + run + " runs");
+ }
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/SqlInjection.java b/sanitizers/src/test/java/com/example/SqlInjection.java
new file mode 100644
index 00000000..8a16b5c8
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/SqlInjection.java
@@ -0,0 +1,41 @@
+// 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.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.h2.jdbcx.JdbcDataSource;
+
+public class SqlInjection {
+ static Connection conn = null;
+
+ public static void fuzzerInitialize() throws Exception {
+ JdbcDataSource ds = new JdbcDataSource();
+ ds.setURL("jdbc:h2:./test.db");
+ conn = ds.getConnection();
+ conn.createStatement().execute(
+ "CREATE TABLE IF NOT EXISTS pet (id IDENTITY PRIMARY KEY, name VARCHAR(50))");
+ }
+
+ static void insecureInsertUser(String userName) throws SQLException {
+ // Never use String.format instead of java.sql.Connection.prepareStatement ...
+ conn.createStatement().execute(String.format("INSERT INTO pet (name) VALUES ('%s')", userName));
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception {
+ insecureInsertUser(data.consumeRemainingAsString());
+ }
+}
diff --git a/sanitizers/src/main/java/jaz/Ter.java b/sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java
index 7814396f..b674f5c5 100644
--- a/sanitizers/src/main/java/jaz/Ter.java
+++ b/sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java
@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package jaz;
+package com.example.ldap;
-/**
- * A safe to use companion of {@link jaz.Zer} that is used to produce serializable instances of it
- * with only light patching.
- */
-@SuppressWarnings("unused")
-public class Ter implements java.io.Serializable {
- static final long serialVersionUID = 42L;
+import java.util.Hashtable;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+
+public class MockInitialContextFactory implements InitialContextFactory {
+ public Context getInitialContext(Hashtable environment) {
+ return new MockLdapContext();
+ }
}
diff --git a/sanitizers/src/test/java/com/example/ldap/MockLdapContext.java b/sanitizers/src/test/java/com/example/ldap/MockLdapContext.java
new file mode 100644
index 00000000..a51fadcd
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/ldap/MockLdapContext.java
@@ -0,0 +1,316 @@
+// Copyright 2021 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.example.ldap;
+
+import com.unboundid.ldap.sdk.DN;
+import com.unboundid.ldap.sdk.Filter;
+import com.unboundid.ldap.sdk.LDAPException;
+import java.util.Hashtable;
+import javax.naming.*;
+import javax.naming.directory.*;
+import javax.naming.ldap.*;
+
+/**
+ * Mock LdapContex implementation to test LdapInjection hook configuration.
+ *
+ * Only {@code com.example.ldap.MockLdapContext#search(java.lang.String, java.lang.String,
+ * javax.naming.directory.SearchControls)} is implemented to validate DN and filer query.
+ */
+public class MockLdapContext implements LdapContext {
+ @Override
+ public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public LdapContext newInstance(Control[] requestControls) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public void reconnect(Control[] connCtls) throws NamingException {}
+
+ @Override
+ public Control[] getConnectControls() throws NamingException {
+ return new Control[0];
+ }
+
+ @Override
+ public void setRequestControls(Control[] requestControls) throws NamingException {}
+
+ @Override
+ public Control[] getRequestControls() throws NamingException {
+ return new Control[0];
+ }
+
+ @Override
+ public Control[] getResponseControls() throws NamingException {
+ return new Control[0];
+ }
+
+ @Override
+ public Attributes getAttributes(Name name) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public Attributes getAttributes(String name) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public Attributes getAttributes(Name name, String[] attrIds) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public Attributes getAttributes(String name, String[] attrIds) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException {}
+
+ @Override
+ public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException {}
+
+ @Override
+ public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException {}
+
+ @Override
+ public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException {}
+
+ @Override
+ public void bind(Name name, Object obj, Attributes attrs) throws NamingException {}
+
+ @Override
+ public void bind(String name, Object obj, Attributes attrs) throws NamingException {}
+
+ @Override
+ public void rebind(Name name, Object obj, Attributes attrs) throws NamingException {}
+
+ @Override
+ public void rebind(String name, Object obj, Attributes attrs) throws NamingException {}
+
+ @Override
+ public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public DirContext createSubcontext(String name, Attributes attrs) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public DirContext getSchema(Name name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public DirContext getSchema(String name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public DirContext getSchemaClassDefinition(Name name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public DirContext getSchemaClassDefinition(String name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public NamingEnumeration<SearchResult> search(Name name, Attributes matchingAttributes,
+ String[] attributesToReturn) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes,
+ String[] attributesToReturn) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<SearchResult> search(Name name, Attributes matchingAttributes)
+ throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes)
+ throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<SearchResult> search(Name name, String filter, SearchControls cons)
+ throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls cons)
+ throws NamingException {
+ // Use UnboundID LDAP to validate DN and filter
+ if (!DN.isValidDN(name)) {
+ throw new NamingException("Invalid DN " + name);
+ }
+ try {
+ Filter.create(filter);
+ } catch (LDAPException e) {
+ throw new InvalidSearchFilterException("Invalid search filter " + filter);
+ }
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<SearchResult> search(Name name, String filterExpr, Object[] filterArgs,
+ SearchControls cons) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<SearchResult> search(String name, String filterExpr, Object[] filterArgs,
+ SearchControls cons) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public Object lookup(Name name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public Object lookup(String name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public void bind(Name name, Object obj) throws NamingException {}
+
+ @Override
+ public void bind(String name, Object obj) throws NamingException {}
+
+ @Override
+ public void rebind(Name name, Object obj) throws NamingException {}
+
+ @Override
+ public void rebind(String name, Object obj) throws NamingException {}
+
+ @Override
+ public void unbind(Name name) throws NamingException {}
+
+ @Override
+ public void unbind(String name) throws NamingException {}
+
+ @Override
+ public void rename(Name oldName, Name newName) throws NamingException {}
+
+ @Override
+ public void rename(String oldName, String newName) throws NamingException {}
+
+ @Override
+ public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<NameClassPair> list(String name) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NamingEnumeration<Binding> listBindings(String name) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public void destroySubcontext(Name name) throws NamingException {}
+
+ @Override
+ public void destroySubcontext(String name) throws NamingException {}
+
+ @Override
+ public Context createSubcontext(Name name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public Context createSubcontext(String name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public Object lookupLink(Name name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public Object lookupLink(String name) throws NamingException {
+ return this;
+ }
+
+ @Override
+ public NameParser getNameParser(Name name) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public NameParser getNameParser(String name) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public Name composeName(Name name, Name prefix) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public String composeName(String name, String prefix) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public Object addToEnvironment(String propName, Object propVal) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public Object removeFromEnvironment(String propName) throws NamingException {
+ return null;
+ }
+
+ @Override
+ public Hashtable<?, ?> getEnvironment() throws NamingException {
+ return null;
+ }
+
+ @Override
+ public void close() throws NamingException {}
+
+ @Override
+ public String getNameInNamespace() throws NamingException {
+ return null;
+ }
+}