diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index ffa30114a..fbf7bec65 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -31,6 +31,7 @@ _sanitizer_class_names = [ "ScriptEngineInjection", "ServerSideRequestForgery", "SqlInjection", + "UnsafeSanitizer", "XPathInjection", ] 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 60d2e02e9..3711d89c0 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 @@ -48,6 +48,15 @@ java_library( ], ) +java_library( + name = "unsafe_sanitizer", + srcs = ["UnsafeSanitizer.java"], + deps = [ + "//src/main/java/com/code_intelligence/jazzer/api:hooks", + "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider", + ], +) + kt_jvm_library( name = "sanitizers", srcs = [ @@ -69,6 +78,7 @@ kt_jvm_library( ":script_engine_injection", ":server_side_request_forgery", ":sql_injection", + ":unsafe_sanitizer", ], deps = [ "//src/main/java/com/code_intelligence/jazzer/api:hooks", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeSanitizer.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeSanitizer.java new file mode 100644 index 000000000..36a37e0ef --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeSanitizer.java @@ -0,0 +1,521 @@ +/* + * Copyright 2025 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.utils.UnsafeProvider; +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Array; +import sun.misc.Unsafe; + +/** Sanitizer for {@link Unsafe sun.misc.Unsafe} usage. */ +public class UnsafeSanitizer { + /* + * Implementation notes: + * - This only covers the 'public' class sun.misc.Unsafe, not the JDK-internal jdk.internal.misc.Unsafe since + * it is rather unlikely that user code uses that, especially with strong encapsulation of JDK internals (JEP 403) + * - This only covers Unsafe access for arrays: + * - Array access checks can be implemented in a stateless way because all information is available from the + * arguments passed to the Unsafe method + * - Sanitizing field access is probably not interesting because that is normally performed with hardcoded + * offsets, which cannot be influenced by fuzzing input data + * - Sanitizing native memory access would require keeping track of allocations, and is therefore due to its + * complexity out of scope for now + * - Alignment when reading a primitive value larger than 1 byte (e.g. an 8-byte long) from a primitive array of + * smaller element size is not checked + * It depends on the platform where the application is running whether unaligned access is supported, but many + * applications using Unsafe assume that unaligned access is supported. + * - Where necessary the hooks use `@MethodHook#targetMethodDescriptor` to select the Unsafe method overload + * with Object parameter, instead of the overload without Object parameter which only supports native memory access. + */ + + private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); + private static final String UNSAFE_NAME = "sun.misc.Unsafe"; + + /* + Script for generating the @MethodHook annotations: + ==================================================== + Method[] methods = sun.misc.Unsafe.class.getMethods(); + Set methodsWithNativeOnlyOverload = Arrays.stream(methods) + .filter(m -> Arrays.stream(m.getParameterTypes()).noneMatch(p -> p == Object.class)) + .map(Method::getName) + .collect(Collectors.toSet()); + + Set emittedWithoutDesc = new HashSet<>(); + Arrays.stream(methods) + .filter(m -> Arrays.stream(m.getParameterTypes()).anyMatch(p -> p == Object.class)) + .filter(m -> !Arrays.asList("equals", "monitorEnter", "monitorExit", "tryMonitorEnter", "unpark") + .contains(m.getName())) + .sorted(Comparator.comparing(Method::getName)) + .forEach(m -> { + String methodName = m.getName(); + String s = "@MethodHook(\n type = HookType.BEFORE,\n targetClassName = UNSAFE_NAME,\n targetMethod = \"" + + methodName + + "\""; + if (methodsWithNativeOnlyOverload.contains(methodName)) { + String methodDesc; + try { + methodDesc = MethodHandles.lookup().unreflect(m).type() + // Drop receiver type (i.e. `Unsafe this`) + .dropParameterTypes(0, 1) + .toMethodDescriptorString(); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + s += ",\n targetMethodDescriptor = \"" + + methodDesc + + "\""; + System.out.println(s + ")"); + } + // Avoid emitting same annotation twice for overloads where no native-only overload exists + else if (emittedWithoutDesc.add(methodName)) { + System.out.println(s + ")"); + } + }); + ==================================================== + */ + + private static int getBytesCount(Class c) { + int bytesCount; + if (c == boolean.class) { + // Assumes that `Unsafe.ARRAY_BOOLEAN_INDEX_SCALE > 0` + bytesCount = 1; + } else if (c == byte.class) { + bytesCount = 1; + } else if (c == char.class || c == short.class) { + bytesCount = 2; + } else if (c == int.class || c == float.class) { + bytesCount = 4; + } else if (c == long.class || c == double.class) { + bytesCount = 8; + } else { + throw new AssertionError("Unexpected type: " + c); + } + return bytesCount; + } + + /** Hook for all {@link Unsafe} methods which read or write an {@code Object} reference. */ + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "compareAndSwapObject") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getAndSetObject") + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getObject") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getObjectVolatile") + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putObject") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putObjectVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putOrderedObject") + public static void objectAccessHook( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + checkObjectSizedAccess(arguments); + } + + /** + * Hook for all {@link Unsafe} {@code get...} primitive methods, where the access size can be + * derived from the return type. + */ + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getAndAddInt") + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getAndAddLong") + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getAndSetInt") + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getAndSetLong") + // `getBoolean` has no overload without Object parameter, so no need to specify method descriptor + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getBoolean") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getBooleanVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getByte", + targetMethodDescriptor = "(Ljava/lang/Object;J)B") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getByte", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;I)B") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getByteVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getChar", + targetMethodDescriptor = "(Ljava/lang/Object;J)C") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getChar", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;I)C") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getCharVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getDouble", + targetMethodDescriptor = "(Ljava/lang/Object;J)D") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getDouble", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;I)D") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getDoubleVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getFloat", + targetMethodDescriptor = "(Ljava/lang/Object;J)F") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getFloat", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;I)F") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getFloatVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getInt", + targetMethodDescriptor = "(Ljava/lang/Object;J)I") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getInt", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;I)I") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getIntVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getLong", + targetMethodDescriptor = "(Ljava/lang/Object;J)J") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getLong", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;I)J") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getLongVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getShort", + targetMethodDescriptor = "(Ljava/lang/Object;J)S") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getShort", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;I)S") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getShortVolatile") + public static void primitiveGetterHook( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + int accessSize = getBytesCount(method.type().returnType()); + checkByteSizedAccess(arguments, accessSize); + } + + /** + * Hook for all {@link Unsafe} {@code compareAndSwap...} and {@code put...} primitive methods, + * where the access size can be derived from the parameter type at index 2 (0-based). + */ + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "compareAndSwapInt") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "compareAndSwapLong") + // `putBoolean` has no overload without Object parameter, so no need to specify method descriptor + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putBoolean") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putBooleanVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putByte", + targetMethodDescriptor = "(Ljava/lang/Object;JB)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putByte", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;IB)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putByteVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putChar", + targetMethodDescriptor = "(Ljava/lang/Object;JC)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putChar", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;IC)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putCharVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putDouble", + targetMethodDescriptor = "(Ljava/lang/Object;JD)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putDouble", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;ID)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putDoubleVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putFloat", + targetMethodDescriptor = "(Ljava/lang/Object;JF)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putFloat", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;IF)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putFloatVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putInt", + targetMethodDescriptor = "(Ljava/lang/Object;JI)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putInt", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;II)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putIntVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putLong", + targetMethodDescriptor = "(Ljava/lang/Object;JJ)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putLong", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;IJ)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putLongVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putShort", + targetMethodDescriptor = "(Ljava/lang/Object;JS)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putShort", + // Overload with `int offset`, removed in Java 9 + targetMethodDescriptor = "(Ljava/lang/Object;IS)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putShortVolatile") + @MethodHook(type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putOrderedInt") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putOrderedLong") + public static void primitiveSetterHook( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + int accessSize = + getBytesCount(method.type().parameterType(2 + 1)); // + 1 for implicit Unsafe instance + checkByteSizedAccess(arguments, accessSize); + } + + /** Hook for {@link Unsafe#setMemory(Object, long, long, byte)} */ + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "setMemory", + // Only handle overload with Object parameter + targetMethodDescriptor = "(Ljava/lang/Object;JJB)V") + public static void setMemoryHook( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + checkByteSizedAccess(arguments[0], (long) arguments[1], (long) arguments[2]); + } + + /** Hook for {@link Unsafe#copyMemory(Object, long, Object, long, long)} */ + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "copyMemory", + // Only handle overload with Object parameters + targetMethodDescriptor = "(Ljava/lang/Object;JLjava/lang/Object;JJ)V") + public static void copyMemoryHook( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + long size = (long) arguments[4]; + checkByteSizedAccess(arguments[0], (long) arguments[1], size); + checkByteSizedAccess(arguments[2], (long) arguments[3], size); + } + + private static void report(String message) { + Jazzer.reportFindingFromHook(new FuzzerSecurityIssueCritical(message)); + } + + private static long offsetValue(Object obj) { + // Java 8 also had deprecated Unsafe method overloads with `int offset` parameter, therefore + // cannot just cast to `long` here + return ((Number) obj).longValue(); + } + + private static void checkByteSizedAccess(Object[] args, long accessSize) { + Object obj = args[0]; + long offset = offsetValue(args[1]); + checkByteSizedAccess(obj, offset, accessSize); + } + + private static void checkByteSizedAccess(Object obj, long offset, long accessSize) { + checkAccess(obj, offset, accessSize, false); + } + + private static void checkObjectSizedAccess(Object[] args) { + Object obj = args[0]; + long offset = offsetValue(args[1]); + long accessSize = Unsafe.ARRAY_OBJECT_INDEX_SCALE; + checkAccess(obj, offset, accessSize, true); + } + + /** + * Checks {@link Unsafe} memory access. + * + * @param obj the base object for memory access; e.g. for {@link Unsafe#getInt(Object, long)} it + * is the argument at index 0 + * @param offset the offset for the memory access; e.g. for {@link Unsafe#getInt(Object, long)} it + * is the argument at index 1 + * @param accessSize the number of bytes which is accessed; e.g. for {@link Unsafe#getInt(Object, + * long)} it is 4 (due to {@code int} being 4 bytes large) + * @param isObjectAccess whether an object reference (instead of a primitive value) is being + * accessed + */ + private static void checkAccess( + Object obj, long offset, long accessSize, boolean isObjectAccess) { + if (accessSize < 0) { + report("Negative access size: " + accessSize); + } + + if (obj == null) { + // Native memory access; not sanitized here + return; + } + + Class objClass = obj.getClass(); + Class componentType = objClass.getComponentType(); + if (componentType == null) { + // Not an array + return; + } + + // Mixing up bytes and object references (e.g. reading an object reference from a primitive + // array) seems error-prone and might mess with the garbage collector + if (isObjectAccess) { + if (componentType.isPrimitive()) { + report("Reading or writing an object reference from a " + objClass.getTypeName()); + } + } else { + if (!componentType.isPrimitive()) { + report("Reading or writing bytes from a " + objClass.getTypeName()); + } + } + + long baseOffset = UNSAFE.arrayBaseOffset(objClass); + long indexScale = UNSAFE.arrayIndexScale(objClass); + + if (offset < baseOffset) { + report("Offset " + offset + " is lower than baseOffset " + baseOffset); + } + long endOffset = baseOffset + Array.getLength(obj) * indexScale; + // Uses `compareUnsigned` to account for overflow + if (Long.compareUnsigned(endOffset, offset + accessSize) < 0) { + report( + "Access at offset " + + offset + + " with size " + + accessSize + + " exceeds end offset " + + endOffset); + } + + if (isObjectAccess && (offset - baseOffset) % indexScale != 0) { + // Trying to read or write object at an offset which spans two array elements + report("Access at offset " + offset + " is not aligned"); + } + // For primitive arrays don't check if access is aligned; depends on platform if unaligned + // access is supported and some libraries which are using Unsafe assume that it is supported + } +} diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 695380c79..d7f4c9d6e 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -218,6 +218,150 @@ java_fuzz_target_test( ], ) +[java_fuzz_target_test( + name = "UnsafeArrayOutOfBounds_" + method, + srcs = [ + "UnsafeArrayOutOfBounds.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", + ], + env = { + "JAZZER_FUZZ": "1", + }, + expect_number_of_findings = 1, + fuzzer_args = [ + "-print_final_stats=1", + "-runs=0", + ], + target_class = "com.example.UnsafeArrayOutOfBounds", + target_method = method, + verify_crash_reproducer = False, + runtime_deps = [ + "@maven//:org_junit_jupiter_junit_jupiter_engine", + ], + deps = [ + "//deploy:jazzer-junit", + ], +) for method in [ + "byteAccessOnObjectArray", + "compareAndSwapInt", + "compareAndSwapInt_end", + "compareAndSwapLong", + "compareAndSwapLong_end", + "compareAndSwapObject", + "compareAndSwapObject_end", + "copyMemory", + "copyMemory_end", + "copyMemory_dest", + "copyMemory_dest_end", + "getAndAddInt", + "getAndAddInt_end", + "getAndAddLong", + "getAndAddLong_end", + "getAndSetInt", + "getAndSetInt_end", + "getAndSetLong", + "getAndSetLong_end", + "getAndSetObject", + "getAndSetObject_end", + "getBoolean", + "getBoolean_end", + "getBooleanVolatile", + "getBooleanVolatile_end", + "getByte", + "getByte_end", + "getByteVolatile", + "getByteVolatile_end", + "getChar", + "getChar_end", + "getCharVolatile", + "getCharVolatile_end", + "getDouble", + "getDouble_end", + "getDoubleVolatile", + "getDoubleVolatile_end", + "getFloat", + "getFloat_end", + "getFloatVolatile", + "getFloatVolatile_end", + "getInt", + "getInt_end", + "getIntVolatile", + "getIntVolatile_end", + "getLong", + "getLong_end", + "getLongVolatile", + "getLongVolatile_end", + "getObject", + "getObject_end", + "getObjectVolatile", + "getObjectVolatile_end", + "getShort", + "getShort_end", + "getShortVolatile", + "getShortVolatile_end", + "objectAccessOnPrimitiveArray", + "putBoolean", + "putBoolean_end", + "putBooleanVolatile", + "putBooleanVolatile_end", + "putByte", + "putByte_end", + "putByteVolatile", + "putByteVolatile_end", + "putChar", + "putChar_end", + "putCharVolatile", + "putCharVolatile_end", + "putDouble", + "putDouble_end", + "putDoubleVolatile", + "putDoubleVolatile_end", + "putFloat", + "putFloat_end", + "putFloatVolatile", + "putFloatVolatile_end", + "putInt", + "putInt_end", + "putIntVolatile", + "putIntVolatile_end", + "putLong", + "putLong_end", + "putLongVolatile", + "putLongVolatile_end", + "putObject", + "putObject_end", + "putObjectVolatile", + "putObjectVolatile_end", + "putOrderedInt", + "putOrderedInt_end", + "putOrderedLong", + "putOrderedLong_end", + "putOrderedObject", + "putOrderedObject_end", + "putShort", + "putShort_end", + "putShortVolatile", + "putShortVolatile_end", + "setMemory", + "setMemory_end", + "unalignedObjectAccess", +]] + +java_fuzz_target_test( + name = "UnsafeArrayValid", + srcs = [ + "UnsafeArrayValid.java", + ], + allowed_findings = [], + fuzzer_args = [ + # Test does not depend on fuzzing input; just run it once + "-runs=1", + ], + target_class = "com.example.UnsafeArrayValid", +) + java_fuzz_target_test( name = "ClojureTests", srcs = [ diff --git a/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java b/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java index 2ff084798..acb0cd7df 100644 --- a/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java +++ b/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java @@ -16,12 +16,11 @@ package com.example; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.mutation.annotation.Ascii; import java.util.concurrent.TimeUnit; public class OsCommandInjectionProcessBuilder { - public static void fuzzerTestOneInput(FuzzedDataProvider data) { - String input = data.consumeRemainingAsAsciiString(); + public static void fuzzerTestOneInput(@Ascii String input) { try { ProcessBuilder processBuilder = new ProcessBuilder(input); processBuilder.environment().clear(); diff --git a/sanitizers/src/test/java/com/example/UnsafeArrayOutOfBounds.java b/sanitizers/src/test/java/com/example/UnsafeArrayOutOfBounds.java new file mode 100644 index 000000000..a166784e2 --- /dev/null +++ b/sanitizers/src/test/java/com/example/UnsafeArrayOutOfBounds.java @@ -0,0 +1,633 @@ +/* + * Copyright 2025 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.junit.FuzzTest; +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public class UnsafeArrayOutOfBounds { + private static final Unsafe UNSAFE; + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + UNSAFE = (Unsafe) f.get(null); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + /* + * These test methods all perform invalid memory access with Unsafe. + * IMPORTANT: When adding new methods, the list of method names in `BUILD.bazel` has to be adjusted as well. + */ + + @FuzzTest + public void compareAndSwapInt(Boolean ignored) { + UNSAFE.compareAndSwapInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 0, 1); + } + + @FuzzTest + public void compareAndSwapInt_end(Boolean ignored) { + UNSAFE.compareAndSwapInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 0, 1); + } + + @FuzzTest + public void compareAndSwapLong(Boolean ignored) { + UNSAFE.compareAndSwapLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 0, 1); + } + + @FuzzTest + public void compareAndSwapLong_end(Boolean ignored) { + UNSAFE.compareAndSwapLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0, 1); + } + + @FuzzTest + public void compareAndSwapObject(Boolean ignored) { + UNSAFE.compareAndSwapObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, "a", "b"); + } + + @FuzzTest + public void compareAndSwapObject_end(Boolean ignored) { + UNSAFE.compareAndSwapObject( + new Object[5], + Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, + "a", + "b"); + } + + @FuzzTest + public void getAndAddInt(Boolean ignored) { + UNSAFE.getAndAddInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 1); + } + + @FuzzTest + public void getAndAddInt_end(Boolean ignored) { + UNSAFE.getAndAddInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 1); + } + + @FuzzTest + public void getAndAddLong(Boolean ignored) { + UNSAFE.getAndAddLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 1); + } + + @FuzzTest + public void getAndAddLong_end(Boolean ignored) { + UNSAFE.getAndAddLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 1); + } + + @FuzzTest + public void getAndSetInt(Boolean ignored) { + UNSAFE.getAndSetInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 1); + } + + @FuzzTest + public void getAndSetInt_end(Boolean ignored) { + UNSAFE.getAndSetInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 1); + } + + @FuzzTest + public void getAndSetLong(Boolean ignored) { + UNSAFE.getAndSetLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 1); + } + + @FuzzTest + public void getAndSetLong_end(Boolean ignored) { + UNSAFE.getAndSetLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 1); + } + + @FuzzTest + public void getAndSetObject(Boolean ignored) { + UNSAFE.getAndSetObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, "a"); + } + + @FuzzTest + public void getAndSetObject_end(Boolean ignored) { + UNSAFE.getAndSetObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, "a"); + } + + @FuzzTest + public void getBoolean(Boolean ignored) { + UNSAFE.getBoolean(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET - 1); + } + + @FuzzTest + public void getBoolean_end(Boolean ignored) { + UNSAFE.getBoolean( + new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 5L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE); + } + + @FuzzTest + public void getBooleanVolatile(Boolean ignored) { + UNSAFE.getBooleanVolatile(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET - 1); + } + + @FuzzTest + public void getBooleanVolatile_end(Boolean ignored) { + UNSAFE.getBooleanVolatile( + new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 5L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE); + } + + @FuzzTest + public void getByte(Boolean ignored) { + UNSAFE.getByte(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1); + } + + @FuzzTest + public void getByte_end(Boolean ignored) { + UNSAFE.getByte(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 5L * Unsafe.ARRAY_BYTE_INDEX_SCALE); + } + + @FuzzTest + public void getByteVolatile(Boolean ignored) { + UNSAFE.getByteVolatile(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1); + } + + @FuzzTest + public void getByteVolatile_end(Boolean ignored) { + UNSAFE.getByteVolatile( + new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 5L * Unsafe.ARRAY_BYTE_INDEX_SCALE); + } + + @FuzzTest + public void getChar(Boolean ignored) { + UNSAFE.getChar(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET - 1); + } + + @FuzzTest + public void getChar_end(Boolean ignored) { + UNSAFE.getChar(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 5L * Unsafe.ARRAY_CHAR_INDEX_SCALE); + } + + @FuzzTest + public void getCharVolatile(Boolean ignored) { + UNSAFE.getCharVolatile(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET - 1); + } + + @FuzzTest + public void getCharVolatile_end(Boolean ignored) { + UNSAFE.getCharVolatile( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 5L * Unsafe.ARRAY_CHAR_INDEX_SCALE); + } + + @FuzzTest + public void getDouble(Boolean ignored) { + UNSAFE.getDouble(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET - 1); + } + + @FuzzTest + public void getDouble_end(Boolean ignored) { + UNSAFE.getDouble( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 5L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE); + } + + @FuzzTest + public void getDoubleVolatile(Boolean ignored) { + UNSAFE.getDoubleVolatile(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET - 1); + } + + @FuzzTest + public void getDoubleVolatile_end(Boolean ignored) { + UNSAFE.getDoubleVolatile( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 5L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE); + } + + @FuzzTest + public void getFloat(Boolean ignored) { + UNSAFE.getFloat(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET - 1); + } + + @FuzzTest + public void getFloat_end(Boolean ignored) { + UNSAFE.getFloat( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 5L * Unsafe.ARRAY_FLOAT_INDEX_SCALE); + } + + @FuzzTest + public void getFloatVolatile(Boolean ignored) { + UNSAFE.getFloatVolatile(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET - 1); + } + + @FuzzTest + public void getFloatVolatile_end(Boolean ignored) { + UNSAFE.getFloatVolatile( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 5L * Unsafe.ARRAY_FLOAT_INDEX_SCALE); + } + + @FuzzTest + public void getInt(Boolean ignored) { + UNSAFE.getInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1); + } + + @FuzzTest + public void getInt_end(Boolean ignored) { + UNSAFE.getInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE); + } + + @FuzzTest + public void getIntVolatile(Boolean ignored) { + UNSAFE.getIntVolatile(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1); + } + + @FuzzTest + public void getIntVolatile_end(Boolean ignored) { + UNSAFE.getIntVolatile( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE); + } + + @FuzzTest + public void getLong(Boolean ignored) { + UNSAFE.getLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1); + } + + @FuzzTest + public void getLong_end(Boolean ignored) { + UNSAFE.getLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE); + } + + @FuzzTest + public void getLongVolatile(Boolean ignored) { + UNSAFE.getLongVolatile(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1); + } + + @FuzzTest + public void getLongVolatile_end(Boolean ignored) { + UNSAFE.getLongVolatile( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE); + } + + @FuzzTest + public void getObject(Boolean ignored) { + UNSAFE.getObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1); + } + + @FuzzTest + public void getObject_end(Boolean ignored) { + UNSAFE.getObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE); + } + + @FuzzTest + public void getObjectVolatile(Boolean ignored) { + UNSAFE.getObjectVolatile(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1); + } + + @FuzzTest + public void getObjectVolatile_end(Boolean ignored) { + UNSAFE.getObjectVolatile( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE); + } + + @FuzzTest + public void getShort(Boolean ignored) { + UNSAFE.getShort(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET - 1); + } + + @FuzzTest + public void getShort_end(Boolean ignored) { + UNSAFE.getShort( + new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET + 5L * Unsafe.ARRAY_SHORT_INDEX_SCALE); + } + + @FuzzTest + public void getShortVolatile(Boolean ignored) { + UNSAFE.getShortVolatile(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET - 1); + } + + @FuzzTest + public void getShortVolatile_end(Boolean ignored) { + UNSAFE.getShortVolatile( + new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET + 5L * Unsafe.ARRAY_SHORT_INDEX_SCALE); + } + + @FuzzTest + public void putBoolean(Boolean ignored) { + UNSAFE.putBoolean(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET - 1, true); + } + + @FuzzTest + public void putBoolean_end(Boolean ignored) { + UNSAFE.putBoolean( + new boolean[5], + Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 5L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE, + true); + } + + @FuzzTest + public void putBooleanVolatile(Boolean ignored) { + UNSAFE.putBooleanVolatile(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET - 1, true); + } + + @FuzzTest + public void putBooleanVolatile_end(Boolean ignored) { + UNSAFE.putBooleanVolatile( + new boolean[5], + Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 5L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE, + true); + } + + @FuzzTest + public void putByte(Boolean ignored) { + UNSAFE.putByte(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, (byte) 0); + } + + @FuzzTest + public void putByte_end(Boolean ignored) { + UNSAFE.putByte( + new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 5L * Unsafe.ARRAY_BYTE_INDEX_SCALE, (byte) 0); + } + + @FuzzTest + public void putByteVolatile(Boolean ignored) { + UNSAFE.putByteVolatile(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, (byte) 0); + } + + @FuzzTest + public void putByteVolatile_end(Boolean ignored) { + UNSAFE.putByteVolatile( + new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 5L * Unsafe.ARRAY_BYTE_INDEX_SCALE, (byte) 0); + } + + @FuzzTest + public void putChar(Boolean ignored) { + UNSAFE.putChar(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET - 1, 'a'); + } + + @FuzzTest + public void putChar_end(Boolean ignored) { + UNSAFE.putChar( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 5L * Unsafe.ARRAY_CHAR_INDEX_SCALE, 'a'); + } + + @FuzzTest + public void putCharVolatile(Boolean ignored) { + UNSAFE.putCharVolatile(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET - 1, 'a'); + } + + @FuzzTest + public void putCharVolatile_end(Boolean ignored) { + UNSAFE.putCharVolatile( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 5L * Unsafe.ARRAY_CHAR_INDEX_SCALE, 'a'); + } + + @FuzzTest + public void putDouble(Boolean ignored) { + UNSAFE.putDouble(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putDouble_end(Boolean ignored) { + UNSAFE.putDouble( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 5L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE, 0); + } + + @FuzzTest + public void putDoubleVolatile(Boolean ignored) { + UNSAFE.putDoubleVolatile(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putDoubleVolatile_end(Boolean ignored) { + UNSAFE.putDoubleVolatile( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 5L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE, 0); + } + + @FuzzTest + public void putFloat(Boolean ignored) { + UNSAFE.putFloat(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putFloat_end(Boolean ignored) { + UNSAFE.putFloat( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 5L * Unsafe.ARRAY_FLOAT_INDEX_SCALE, 0); + } + + @FuzzTest + public void putFloatVolatile(Boolean ignored) { + UNSAFE.putFloatVolatile(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putFloatVolatile_end(Boolean ignored) { + UNSAFE.putFloatVolatile( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 5L * Unsafe.ARRAY_FLOAT_INDEX_SCALE, 0); + } + + @FuzzTest + public void putInt(Boolean ignored) { + UNSAFE.putInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putInt_end(Boolean ignored) { + UNSAFE.putInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + } + + @FuzzTest + public void putIntVolatile(Boolean ignored) { + UNSAFE.putIntVolatile(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putIntVolatile_end(Boolean ignored) { + UNSAFE.putIntVolatile( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + } + + @FuzzTest + public void putLong(Boolean ignored) { + UNSAFE.putLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putLong_end(Boolean ignored) { + UNSAFE.putLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + } + + @FuzzTest + public void putLongVolatile(Boolean ignored) { + UNSAFE.putLongVolatile(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putLongVolatile_end(Boolean ignored) { + UNSAFE.putLongVolatile( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + } + + @FuzzTest + public void putObject(Boolean ignored) { + UNSAFE.putObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putObject_end(Boolean ignored) { + UNSAFE.putObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + } + + @FuzzTest + public void putObjectVolatile(Boolean ignored) { + UNSAFE.putObjectVolatile(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putObjectVolatile_end(Boolean ignored) { + UNSAFE.putObjectVolatile( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + } + + @FuzzTest + public void putOrderedInt(Boolean ignored) { + UNSAFE.putOrderedInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putOrderedInt_end(Boolean ignored) { + UNSAFE.putOrderedInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + } + + @FuzzTest + public void putOrderedLong(Boolean ignored) { + UNSAFE.putOrderedLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putOrderedLong_end(Boolean ignored) { + UNSAFE.putOrderedLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + } + + @FuzzTest + public void putOrderedObject(Boolean ignored) { + UNSAFE.putOrderedObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, 0); + } + + @FuzzTest + public void putOrderedObject_end(Boolean ignored) { + UNSAFE.putOrderedObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + } + + @FuzzTest + public void putShort(Boolean ignored) { + UNSAFE.putShort(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET - 1, (short) 0); + } + + @FuzzTest + public void putShort_end(Boolean ignored) { + UNSAFE.putShort( + new short[5], + Unsafe.ARRAY_SHORT_BASE_OFFSET + 5L * Unsafe.ARRAY_SHORT_INDEX_SCALE, + (short) 0); + } + + @FuzzTest + public void putShortVolatile(Boolean ignored) { + UNSAFE.putShortVolatile(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET - 1, (short) 0); + } + + @FuzzTest + public void putShortVolatile_end(Boolean ignored) { + UNSAFE.putShortVolatile( + new short[5], + Unsafe.ARRAY_SHORT_BASE_OFFSET + 5L * Unsafe.ARRAY_SHORT_INDEX_SCALE, + (short) 0); + } + + @FuzzTest + public void copyMemory(Boolean ignored) { + UNSAFE.copyMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET, + 2); + } + + @FuzzTest + public void copyMemory_end(Boolean ignored) { + UNSAFE.copyMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET + 4L * Unsafe.ARRAY_BYTE_INDEX_SCALE, + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET, + 2); + } + + @FuzzTest + public void copyMemory_dest(Boolean ignored) { + UNSAFE.copyMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET, + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, + 2); + } + + @FuzzTest + public void copyMemory_dest_end(Boolean ignored) { + UNSAFE.copyMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET, + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET + 4L * Unsafe.ARRAY_BYTE_INDEX_SCALE, + 2); + } + + @FuzzTest + public void setMemory(Boolean ignored) { + UNSAFE.setMemory(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, 2, (byte) 0); + } + + @FuzzTest + public void setMemory_end(Boolean ignored) { + UNSAFE.setMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET + 4L * Unsafe.ARRAY_BYTE_INDEX_SCALE, + 2, + (byte) 0); + } + + // The following covers some additional special cases of invalid memory access + @FuzzTest + public void byteAccessOnObjectArray(Boolean ignored) { + UNSAFE.getByte(new String[10], Unsafe.ARRAY_OBJECT_BASE_OFFSET); + } + + @FuzzTest + public void objectAccessOnPrimitiveArray(Boolean ignored) { + UNSAFE.getObject(new byte[100], Unsafe.ARRAY_BYTE_BASE_OFFSET); + } + + @FuzzTest + public void unalignedObjectAccess(Boolean ignored) { + assert Unsafe.ARRAY_OBJECT_INDEX_SCALE != 1; + UNSAFE.getObject(new String[2], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 1); + } +} diff --git a/sanitizers/src/test/java/com/example/UnsafeArrayValid.java b/sanitizers/src/test/java/com/example/UnsafeArrayValid.java new file mode 100644 index 000000000..47f73063b --- /dev/null +++ b/sanitizers/src/test/java/com/example/UnsafeArrayValid.java @@ -0,0 +1,275 @@ +/* + * Copyright 2025 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.Field; +import sun.misc.Unsafe; + +/** + * Verifies that valid array access through {@link Unsafe} does not cause a spurious sanitizer + * exception. + */ +public class UnsafeArrayValid { + private static final Unsafe UNSAFE; + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + UNSAFE = (Unsafe) f.get(null); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + public static void fuzzerTestOneInput(FuzzedDataProvider ignored) throws Exception { + UNSAFE.compareAndSwapInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET, 0, 1); + UNSAFE.compareAndSwapInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 4L * Unsafe.ARRAY_INT_INDEX_SCALE, 0, 1); + + UNSAFE.compareAndSwapLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET, 0, 1); + UNSAFE.compareAndSwapLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 4L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0, 1); + + UNSAFE.compareAndSwapObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET, "a", "b"); + UNSAFE.compareAndSwapObject( + new Object[5], + Unsafe.ARRAY_OBJECT_BASE_OFFSET + 4L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, + "a", + "b"); + + UNSAFE.getAndAddInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET, 1); + UNSAFE.getAndAddInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 4L * Unsafe.ARRAY_INT_INDEX_SCALE, 1); + + UNSAFE.getAndAddLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET, 1); + UNSAFE.getAndAddLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 4L * Unsafe.ARRAY_LONG_INDEX_SCALE, 1); + + UNSAFE.getAndSetInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET, 1); + UNSAFE.getAndSetInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 4L * Unsafe.ARRAY_INT_INDEX_SCALE, 1); + + UNSAFE.getAndSetLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET, 1); + UNSAFE.getAndSetLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 4L * Unsafe.ARRAY_LONG_INDEX_SCALE, 1); + + UNSAFE.getAndSetObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET, "a"); + UNSAFE.getAndSetObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 4L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, "a"); + + UNSAFE.getBoolean(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET); + UNSAFE.getBoolean( + new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 4L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE); + + UNSAFE.getBooleanVolatile(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET); + UNSAFE.getBooleanVolatile( + new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 4L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE); + + UNSAFE.getByte(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET); + UNSAFE.getByte(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 4L * Unsafe.ARRAY_BYTE_INDEX_SCALE); + + UNSAFE.getByteVolatile(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET); + UNSAFE.getByteVolatile( + new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 4L * Unsafe.ARRAY_BYTE_INDEX_SCALE); + + UNSAFE.getChar(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET); + UNSAFE.getChar(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 4L * Unsafe.ARRAY_CHAR_INDEX_SCALE); + + UNSAFE.getCharVolatile(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET); + UNSAFE.getCharVolatile( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 4L * Unsafe.ARRAY_CHAR_INDEX_SCALE); + + UNSAFE.getDouble(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET); + UNSAFE.getDouble( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 4L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE); + + UNSAFE.getDoubleVolatile(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET); + UNSAFE.getDoubleVolatile( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 4L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE); + + UNSAFE.getFloat(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET); + UNSAFE.getFloat( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * Unsafe.ARRAY_FLOAT_INDEX_SCALE); + + UNSAFE.getFloatVolatile(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET); + UNSAFE.getFloatVolatile( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * Unsafe.ARRAY_FLOAT_INDEX_SCALE); + + UNSAFE.getInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET); + UNSAFE.getInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 4L * Unsafe.ARRAY_INT_INDEX_SCALE); + + UNSAFE.getIntVolatile(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET); + UNSAFE.getIntVolatile( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 4L * Unsafe.ARRAY_INT_INDEX_SCALE); + + UNSAFE.getLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET); + UNSAFE.getLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 4L * Unsafe.ARRAY_LONG_INDEX_SCALE); + + UNSAFE.getLongVolatile(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET); + UNSAFE.getLongVolatile( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 4L * Unsafe.ARRAY_LONG_INDEX_SCALE); + + UNSAFE.getObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET); + UNSAFE.getObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 4L * Unsafe.ARRAY_OBJECT_INDEX_SCALE); + + UNSAFE.getObjectVolatile(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET); + UNSAFE.getObjectVolatile( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 4L * Unsafe.ARRAY_OBJECT_INDEX_SCALE); + + UNSAFE.getShort(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET); + UNSAFE.getShort( + new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET + 4L * Unsafe.ARRAY_SHORT_INDEX_SCALE); + + UNSAFE.getShortVolatile(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET); + UNSAFE.getShortVolatile( + new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET + 4L * Unsafe.ARRAY_SHORT_INDEX_SCALE); + + UNSAFE.putBoolean(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET, true); + UNSAFE.putBoolean( + new boolean[5], + Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 4L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE, + true); + + UNSAFE.putBooleanVolatile(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET, true); + UNSAFE.putBooleanVolatile( + new boolean[5], + Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 4L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE, + true); + + UNSAFE.putByte(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET, (byte) 0); + UNSAFE.putByte( + new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 4L * Unsafe.ARRAY_BYTE_INDEX_SCALE, (byte) 0); + + UNSAFE.putByteVolatile(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET, (byte) 0); + UNSAFE.putByteVolatile( + new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 4L * Unsafe.ARRAY_BYTE_INDEX_SCALE, (byte) 0); + + UNSAFE.putChar(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET, 'a'); + UNSAFE.putChar( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 4L * Unsafe.ARRAY_CHAR_INDEX_SCALE, 'a'); + + UNSAFE.putCharVolatile(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET, 'a'); + UNSAFE.putCharVolatile( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 4L * Unsafe.ARRAY_CHAR_INDEX_SCALE, 'a'); + + UNSAFE.putDouble(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET, 0); + UNSAFE.putDouble( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 4L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE, 0); + + UNSAFE.putDoubleVolatile(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET, 0); + UNSAFE.putDoubleVolatile( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 4L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE, 0); + + UNSAFE.putFloat(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET, 0); + UNSAFE.putFloat( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * Unsafe.ARRAY_FLOAT_INDEX_SCALE, 0); + + UNSAFE.putFloatVolatile(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET, 0); + UNSAFE.putFloatVolatile( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * Unsafe.ARRAY_FLOAT_INDEX_SCALE, 0); + + UNSAFE.putInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET, 0); + UNSAFE.putInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 4L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + + UNSAFE.putIntVolatile(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET, 0); + UNSAFE.putIntVolatile( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 4L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + + UNSAFE.putLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET, 0); + UNSAFE.putLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 4L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + + UNSAFE.putLongVolatile(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET, 0); + UNSAFE.putLongVolatile( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 4L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + + UNSAFE.putObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET, 0); + UNSAFE.putObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 4L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + + UNSAFE.putObjectVolatile(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET, 0); + UNSAFE.putObjectVolatile( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 4L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + + UNSAFE.putOrderedInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET, 0); + UNSAFE.putOrderedInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 4L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + + UNSAFE.putOrderedLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET, 0); + UNSAFE.putOrderedLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 4L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + + UNSAFE.putOrderedObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET, 0); + UNSAFE.putOrderedObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 4L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + + UNSAFE.putShort(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET, (short) 0); + UNSAFE.putShort( + new short[5], + Unsafe.ARRAY_SHORT_BASE_OFFSET + 4L * Unsafe.ARRAY_SHORT_INDEX_SCALE, + (short) 0); + + UNSAFE.putShortVolatile(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET, (short) 0); + UNSAFE.putShortVolatile( + new short[5], + Unsafe.ARRAY_SHORT_BASE_OFFSET + 4L * Unsafe.ARRAY_SHORT_INDEX_SCALE, + (short) 0); + + UNSAFE.copyMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET, + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET + 3L * Unsafe.ARRAY_BYTE_INDEX_SCALE, + 2); + UNSAFE.copyMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET + 3L * Unsafe.ARRAY_BYTE_INDEX_SCALE, + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET, + 2); + + UNSAFE.setMemory(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET, 2, (byte) 0); + UNSAFE.setMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET + 3L * Unsafe.ARRAY_BYTE_INDEX_SCALE, + 2, + (byte) 0); + + // Assumes that byte[] address is aligned for 8-byte long access, or platform supports unaligned + // access + UNSAFE.getLong(new byte[8], Unsafe.ARRAY_BYTE_BASE_OFFSET); + UNSAFE.getLong( + new byte[16], Unsafe.ARRAY_BYTE_BASE_OFFSET + 8L * Unsafe.ARRAY_BYTE_INDEX_SCALE); + + long address = UNSAFE.allocateMemory(10); + // Native memory access with `null` as object should be unaffected + UNSAFE.putLong(null, address, 1); + UNSAFE.getLong(null, address); + UNSAFE.freeMemory(address); + + // Field access should be unaffected + class Dummy { + int i; + } + Field f = Dummy.class.getDeclaredField("i"); + long offset = UNSAFE.objectFieldOffset(f); + UNSAFE.putInt(new Dummy(), offset, 1); + UNSAFE.getInt(new Dummy(), offset); + } +} diff --git a/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java b/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java index bb1a8a405..d37163e59 100644 --- a/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java +++ b/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java @@ -192,7 +192,10 @@ *

The descriptor of a method is an internal representation of the method's signature, which * includes the types of its parameters and its return value. For more information on descriptors, * see the JVM - * Specification, Section 4.3.3 and {@link MethodType#toMethodDescriptorString()} + * Specification, Section 4.3.3 and {@link MethodType#toMethodDescriptorString()}. + * + *

Important: For instance methods the parameter for the argument representing {@code + * this} has to be omitted. * * @return the descriptor of the method to be hooked */