From 6b829d4e50188e4b0da46c96174ccb4f997bb4be Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Thu, 3 Jul 2025 17:42:00 +0200 Subject: [PATCH 1/4] Add `Unsafe` array access sanitizer --- sanitizers/sanitizers.bzl | 1 + .../jazzer/sanitizers/BUILD.bazel | 10 + .../sanitizers/UnsafeArrayOutOfBounds.java | 457 +++++++++++++++ .../src/test/java/com/example/BUILD.bazel | 25 + .../com/example/UnsafeArrayOutOfBounds.java | 552 ++++++++++++++++++ .../example/UnsafeArrayOutOfBoundsValid.java | 272 +++++++++ 6 files changed, 1317 insertions(+) create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java create mode 100644 sanitizers/src/test/java/com/example/UnsafeArrayOutOfBounds.java create mode 100644 sanitizers/src/test/java/com/example/UnsafeArrayOutOfBoundsValid.java diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index ffa30114a..3b17c1bb2 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -31,6 +31,7 @@ _sanitizer_class_names = [ "ScriptEngineInjection", "ServerSideRequestForgery", "SqlInjection", + "UnsafeArrayOutOfBounds", "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..2de8432c8 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_array_out_of_bounds", + srcs = ["UnsafeArrayOutOfBounds.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_array_out_of_bounds", ], deps = [ "//src/main/java/com/code_intelligence/jazzer/api:hooks", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java new file mode 100644 index 000000000..912063312 --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java @@ -0,0 +1,457 @@ +/* + * 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.jvm.hotspot.utilities.AssertionFailure; +import sun.misc.Unsafe; + +/** + * Sanitizer for {@link Unsafe sun.misc.Unsafe} usage which performs out-of-bounds and some other + * invalid access on arrays. + */ +public class UnsafeArrayOutOfBounds { + /* + * 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 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 allMethodNames = new HashSet<>(); + Set duplicateMethodNames = new HashSet<>(); + Arrays.stream(methods).map(Method::getName).forEach(n -> { + if (!allMethodNames.add(n)) { + duplicateMethodNames.add(n); + } + }); + + Arrays.stream(methods) + .filter(m -> Arrays.stream(m.getParameterTypes()).anyMatch(p -> p == Object.class)) + .filter(m -> !Set.of("equals", "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 (duplicateMethodNames.contains(methodName)) { + String methodDesc; + try { + methodDesc = MethodHandles.lookup().unreflect(m).type().toMethodDescriptorString(); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + s += ",\n targetMethodDescriptor = \"" + + methodDesc + + "\""; + } + System.out.println(s + ")"); + }); + ==================================================== + */ + + private static int getAccessSize(Class c) { + int accessSize; + if (c == boolean.class) { + // Assumes that `Unsafe.ARRAY_BOOLEAN_INDEX_SCALE > 0` + accessSize = 1; + } else if (c == byte.class) { + accessSize = 1; + } else if (c == char.class || c == short.class) { + accessSize = 2; + } else if (c == int.class || c == float.class) { + accessSize = 4; + } else if (c == long.class || c == double.class) { + accessSize = 8; + } else { + throw new AssertionFailure("Unexpected type: " + c); + } + return accessSize; + } + + /** 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") + @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 = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)B") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getByteVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getChar", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)C") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getCharVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getDouble", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)D") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getDoubleVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getFloat", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)F") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getFloatVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getInt", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)I") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getIntVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getLong", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)J") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getLongVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getShort", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)S") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "getShortVolatile") + public static void primitiveGetterHook( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + int accessSize = getAccessSize(method.type().returnType()); + checkAccess(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") + @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 = "(Lsun/misc/Unsafe;Ljava/lang/Object;JB)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putByteVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putChar", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JC)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putCharVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putDouble", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JD)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putDoubleVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putFloat", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JF)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putFloatVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putInt", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JI)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putIntVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putLong", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JJ)V") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putLongVolatile") + @MethodHook( + type = HookType.BEFORE, + targetClassName = UNSAFE_NAME, + targetMethod = "putShort", + targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JS)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 = + getAccessSize(method.type().parameterType(2 + 1)); // + 1 for implicit Unsafe instance + checkAccess(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 = "(Lsun/misc/Unsafe;Ljava/lang/Object;JJB)V") + public static void setMemoryHook( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + checkAccess(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 = "(Lsun/misc/Unsafe;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]; + checkAccess(arguments[0], (long) arguments[1], size); + checkAccess(arguments[2], (long) arguments[3], size); + } + + private static void report(String message) { + Jazzer.reportFindingFromHook(new FuzzerSecurityIssueCritical(message)); + } + + private static void checkAccess(Object[] args, long accessSize) { + Object obj = args[0]; + long offset = (long) args[1]; + checkAccess(obj, offset, accessSize); + } + + /** + * Checks {@link Unsafe} memory access where the access size is measured in number of bytes. + * + * @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) + * @see #checkObjectSizedAccess(Object, long) + */ + private static void checkAccess(Object obj, long offset, long accessSize) { + 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; + } + + if (!componentType.isPrimitive()) { + // Reading or writing bytes to an array of references; might be possible but seems + // rather unreliable and might mess with the garbage collector? + 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 + " 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); + } + + // 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 + } + + private static void checkObjectSizedAccess(Object[] args) { + Object obj = args[0]; + long offset = (long) args[1]; + checkObjectSizedAccess(obj, offset); + } + + /** + * Checks {@link Unsafe} memory access where an object reference is accessed. + * + * @param obj the base object for memory access; e.g. for {@link Unsafe#getObject(Object, long)} + * it is the argument at index 0 + * @param offset the offset for the memory access; e.g. for {@link Unsafe#getObject(Object, long)} + * it is the argument at index 1 + * @see #checkAccess(Object, long, long) + */ + private static void checkObjectSizedAccess(Object obj, long offset) { + 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; + } + + if (componentType.isPrimitive()) { + // Reading or writing object references from a primitive array; might be possible but seems + // rather unreliable and might mess with the garbage collector? + report("Reading or writing object reference from a " + objClass.getTypeName()); + } + + long baseOffset = UNSAFE.arrayBaseOffset(objClass); + long indexScale = UNSAFE.arrayIndexScale(objClass); + + if (offset < baseOffset) { + report("Offset " + offset + " lower than baseOffset " + baseOffset); + } + + if ((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"); + } + + long endOffset = baseOffset + Array.getLength(obj) * indexScale; + long accessSize = indexScale; + if (Long.compareUnsigned(endOffset, offset + accessSize) < 0) { + report("Access at offset " + offset + " exceeds end offset " + endOffset); + } + } +} diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 695380c79..67b47831b 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -218,6 +218,31 @@ java_fuzz_target_test( ], ) +java_fuzz_target_test( + name = "UnsafeArrayOutOfBounds", + srcs = [ + "UnsafeArrayOutOfBounds.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", + ], + target_class = "com.example.UnsafeArrayOutOfBounds", + verify_crash_reproducer = False, +) + +java_fuzz_target_test( + name = "UnsafeArrayOutOfBoundsValid", + srcs = [ + "UnsafeArrayOutOfBoundsValid.java", + ], + allowed_findings = [], + fuzzer_args = [ + # Test does not depend on fuzzing input; just run it once + "-runs=1", + ], + target_class = "com.example.UnsafeArrayOutOfBoundsValid", +) + java_fuzz_target_test( name = "ClojureTests", srcs = [ 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..a33b74c89 --- /dev/null +++ b/sanitizers/src/test/java/com/example/UnsafeArrayOutOfBounds.java @@ -0,0 +1,552 @@ +/* + * 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 java.lang.reflect.Method; +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); + } + } + + /** Defines test methods which all perform invalid memory access with {@link Unsafe}. */ + @SuppressWarnings("unused") // test methods are accessed through reflection + private static class TestMethods { + static void compareAndSwapInt() { + UNSAFE.compareAndSwapInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 0, 1); + } + + static void compareAndSwapInt_end() { + UNSAFE.compareAndSwapInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 0, 1); + } + + static void compareAndSwapLong() { + UNSAFE.compareAndSwapLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 0, 1); + } + + static void compareAndSwapLong_end() { + UNSAFE.compareAndSwapLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0, 1); + } + + static void compareAndSwapObject() { + UNSAFE.compareAndSwapObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, "a", "b"); + } + + static void compareAndSwapObject_end() { + UNSAFE.compareAndSwapObject( + new Object[5], + Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, + "a", + "b"); + } + + static void getAndAddInt() { + UNSAFE.getAndAddInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 1); + } + + static void getAndAddInt_end() { + UNSAFE.getAndAddInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 1); + } + + static void getAndAddLong() { + UNSAFE.getAndAddLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 1); + } + + static void getAndAddLong_end() { + UNSAFE.getAndAddLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 1); + } + + static void getAndSetInt() { + UNSAFE.getAndSetInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 1); + } + + static void getAndSetInt_end() { + UNSAFE.getAndSetInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 1); + } + + static void getAndSetLong() { + UNSAFE.getAndSetLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 1); + } + + static void getAndSetLong_end() { + UNSAFE.getAndSetLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 1); + } + + static void getAndSetObject() { + UNSAFE.getAndSetObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, "a"); + } + + static void getAndSetObject_end() { + UNSAFE.getAndSetObject( + new Object[5], + Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, + "a"); + } + + static void getBoolean() { + UNSAFE.getBoolean(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET - 1); + } + + static void getBoolean_end() { + UNSAFE.getBoolean( + new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 5L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE); + } + + static void getBooleanVolatile() { + UNSAFE.getBooleanVolatile(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET - 1); + } + + static void getBooleanVolatile_end() { + UNSAFE.getBooleanVolatile( + new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 5L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE); + } + + static void getByte() { + UNSAFE.getByte(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1); + } + + static void getByte_end() { + UNSAFE.getByte( + new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 5L * Unsafe.ARRAY_BYTE_INDEX_SCALE); + } + + static void getByteVolatile() { + UNSAFE.getByteVolatile(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1); + } + + static void getByteVolatile_end() { + UNSAFE.getByteVolatile( + new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET + 5L * Unsafe.ARRAY_BYTE_INDEX_SCALE); + } + + static void getChar() { + UNSAFE.getChar(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET - 1); + } + + static void getChar_end() { + UNSAFE.getChar( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 5L * Unsafe.ARRAY_CHAR_INDEX_SCALE); + } + + static void getCharVolatile() { + UNSAFE.getCharVolatile(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET - 1); + } + + static void getCharVolatile_end() { + UNSAFE.getCharVolatile( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 5L * Unsafe.ARRAY_CHAR_INDEX_SCALE); + } + + static void getDouble() { + UNSAFE.getDouble(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET - 1); + } + + static void getDouble_end() { + UNSAFE.getDouble( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 5L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE); + } + + static void getDoubleVolatile() { + UNSAFE.getDoubleVolatile(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET - 1); + } + + static void getDoubleVolatile_end() { + UNSAFE.getDoubleVolatile( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 5L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE); + } + + static void getFloat() { + UNSAFE.getFloat(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET - 1); + } + + static void getFloat_end() { + UNSAFE.getFloat( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 5L * Unsafe.ARRAY_FLOAT_INDEX_SCALE); + } + + static void getFloatVolatile() { + UNSAFE.getFloatVolatile(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET - 1); + } + + static void getFloatVolatile_end() { + UNSAFE.getFloatVolatile( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 5L * Unsafe.ARRAY_FLOAT_INDEX_SCALE); + } + + static void getInt() { + UNSAFE.getInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1); + } + + static void getInt_end() { + UNSAFE.getInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE); + } + + static void getIntVolatile() { + UNSAFE.getIntVolatile(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1); + } + + static void getIntVolatile_end() { + UNSAFE.getIntVolatile( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE); + } + + static void getLong() { + UNSAFE.getLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1); + } + + static void getLong_end() { + UNSAFE.getLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE); + } + + static void getLongVolatile() { + UNSAFE.getLongVolatile(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1); + } + + static void getLongVolatile_end() { + UNSAFE.getLongVolatile( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE); + } + + static void getObject() { + UNSAFE.getObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1); + } + + static void getObject_end() { + UNSAFE.getObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE); + } + + static void getObjectVolatile() { + UNSAFE.getObjectVolatile(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1); + } + + static void getObjectVolatile_end() { + UNSAFE.getObjectVolatile( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE); + } + + static void getShort() { + UNSAFE.getShort(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET - 1); + } + + static void getShort_end() { + UNSAFE.getShort( + new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET + 5L * Unsafe.ARRAY_SHORT_INDEX_SCALE); + } + + static void getShortVolatile() { + UNSAFE.getShortVolatile(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET - 1); + } + + static void getShortVolatile_end() { + UNSAFE.getShortVolatile( + new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET + 5L * Unsafe.ARRAY_SHORT_INDEX_SCALE); + } + + static void putBoolean() { + UNSAFE.putBoolean(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET - 1, true); + } + + static void putBoolean_end() { + UNSAFE.putBoolean( + new boolean[5], + Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 5L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE, + true); + } + + static void putBooleanVolatile() { + UNSAFE.putBooleanVolatile(new boolean[5], Unsafe.ARRAY_BOOLEAN_BASE_OFFSET - 1, true); + } + + static void putBooleanVolatile_end() { + UNSAFE.putBooleanVolatile( + new boolean[5], + Unsafe.ARRAY_BOOLEAN_BASE_OFFSET + 5L * Unsafe.ARRAY_BOOLEAN_INDEX_SCALE, + true); + } + + static void putByte() { + UNSAFE.putByte(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, (byte) 0); + } + + static void putByte_end() { + UNSAFE.putByte( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET + 5L * Unsafe.ARRAY_BYTE_INDEX_SCALE, + (byte) 0); + } + + static void putByteVolatile() { + UNSAFE.putByteVolatile(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, (byte) 0); + } + + static void putByteVolatile_end() { + UNSAFE.putByteVolatile( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET + 5L * Unsafe.ARRAY_BYTE_INDEX_SCALE, + (byte) 0); + } + + static void putChar() { + UNSAFE.putChar(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET - 1, 'a'); + } + + static void putChar_end() { + UNSAFE.putChar( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 5L * Unsafe.ARRAY_CHAR_INDEX_SCALE, 'a'); + } + + static void putCharVolatile() { + UNSAFE.putCharVolatile(new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET - 1, 'a'); + } + + static void putCharVolatile_end() { + UNSAFE.putCharVolatile( + new char[5], Unsafe.ARRAY_CHAR_BASE_OFFSET + 5L * Unsafe.ARRAY_CHAR_INDEX_SCALE, 'a'); + } + + static void putDouble() { + UNSAFE.putDouble(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET - 1, 0); + } + + static void putDouble_end() { + UNSAFE.putDouble( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 5L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE, 0); + } + + static void putDoubleVolatile() { + UNSAFE.putDoubleVolatile(new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET - 1, 0); + } + + static void putDoubleVolatile_end() { + UNSAFE.putDoubleVolatile( + new double[5], Unsafe.ARRAY_DOUBLE_BASE_OFFSET + 5L * Unsafe.ARRAY_DOUBLE_INDEX_SCALE, 0); + } + + static void putFloat() { + UNSAFE.putFloat(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET - 1, 0); + } + + static void putFloat_end() { + UNSAFE.putFloat( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 5L * Unsafe.ARRAY_FLOAT_INDEX_SCALE, 0); + } + + static void putFloatVolatile() { + UNSAFE.putFloatVolatile(new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET - 1, 0); + } + + static void putFloatVolatile_end() { + UNSAFE.putFloatVolatile( + new float[5], Unsafe.ARRAY_FLOAT_BASE_OFFSET + 5L * Unsafe.ARRAY_FLOAT_INDEX_SCALE, 0); + } + + static void putInt() { + UNSAFE.putInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 0); + } + + static void putInt_end() { + UNSAFE.putInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + } + + static void putIntVolatile() { + UNSAFE.putIntVolatile(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 0); + } + + static void putIntVolatile_end() { + UNSAFE.putIntVolatile( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + } + + static void putLong() { + UNSAFE.putLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 0); + } + + static void putLong_end() { + UNSAFE.putLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + } + + static void putLongVolatile() { + UNSAFE.putLongVolatile(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 0); + } + + static void putLongVolatile_end() { + UNSAFE.putLongVolatile( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + } + + static void putObject() { + UNSAFE.putObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, 0); + } + + static void putObject_end() { + UNSAFE.putObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + } + + static void putObjectVolatile() { + UNSAFE.putObjectVolatile(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, 0); + } + + static void putObjectVolatile_end() { + UNSAFE.putObjectVolatile( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + } + + static void putOrderedInt() { + UNSAFE.putOrderedInt(new int[5], Unsafe.ARRAY_INT_BASE_OFFSET - 1, 0); + } + + static void putOrderedInt_end() { + UNSAFE.putOrderedInt( + new int[5], Unsafe.ARRAY_INT_BASE_OFFSET + 5L * Unsafe.ARRAY_INT_INDEX_SCALE, 0); + } + + static void putOrderedLong() { + UNSAFE.putOrderedLong(new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET - 1, 0); + } + + static void putOrderedLong_end() { + UNSAFE.putOrderedLong( + new long[5], Unsafe.ARRAY_LONG_BASE_OFFSET + 5L * Unsafe.ARRAY_LONG_INDEX_SCALE, 0); + } + + static void putOrderedObject() { + UNSAFE.putOrderedObject(new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET - 1, 0); + } + + static void putOrderedObject_end() { + UNSAFE.putOrderedObject( + new Object[5], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 5L * Unsafe.ARRAY_OBJECT_INDEX_SCALE, 0); + } + + static void putShort() { + UNSAFE.putShort(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET - 1, (short) 0); + } + + static void putShort_end() { + UNSAFE.putShort( + new short[5], + Unsafe.ARRAY_SHORT_BASE_OFFSET + 5L * Unsafe.ARRAY_SHORT_INDEX_SCALE, + (short) 0); + } + + static void putShortVolatile() { + UNSAFE.putShortVolatile(new short[5], Unsafe.ARRAY_SHORT_BASE_OFFSET - 1, (short) 0); + } + + static void putShortVolatile_end() { + UNSAFE.putShortVolatile( + new short[5], + Unsafe.ARRAY_SHORT_BASE_OFFSET + 5L * Unsafe.ARRAY_SHORT_INDEX_SCALE, + (short) 0); + } + + static void copyMemory() { + UNSAFE.copyMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET, + 2); + } + + static void copyMemory_end() { + 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); + } + + static void copyMemory_dest() { + UNSAFE.copyMemory( + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET, + new byte[5], + Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, + 2); + } + + static void copyMemory_dest_end() { + 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); + } + + static void setMemory() { + UNSAFE.setMemory(new byte[5], Unsafe.ARRAY_BYTE_BASE_OFFSET - 1, 2, (byte) 0); + } + + static void setMemory_end() { + 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 + static void byteAccessOnObjectArray() { + UNSAFE.getByte(new String[10], Unsafe.ARRAY_OBJECT_BASE_OFFSET); + } + + static void objectAccessOnPrimitiveArray() { + UNSAFE.getObject(new byte[100], Unsafe.ARRAY_BYTE_BASE_OFFSET); + } + + static void unalignedObjectAccess() { + assert Unsafe.ARRAY_OBJECT_INDEX_SCALE != 1; + UNSAFE.getObject(new String[2], Unsafe.ARRAY_OBJECT_BASE_OFFSET + 1); + } + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception { + Method[] testMethods = TestMethods.class.getDeclaredMethods(); + // Since all of these methods are expected to cause a sanitizer exception, pick a random one and + // run it + // TODO: Is this a proper way to implement this? + Method testMethod = testMethods[data.consumeInt(0, testMethods.length - 1)]; + + testMethod.invoke(null); + + throw new AssertionError("No sanitizer exception was thrown for " + testMethod); + } +} diff --git a/sanitizers/src/test/java/com/example/UnsafeArrayOutOfBoundsValid.java b/sanitizers/src/test/java/com/example/UnsafeArrayOutOfBoundsValid.java new file mode 100644 index 000000000..62d937ba2 --- /dev/null +++ b/sanitizers/src/test/java/com/example/UnsafeArrayOutOfBoundsValid.java @@ -0,0 +1,272 @@ +/* + * 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 {@link Unsafe} usage does not cause a spurious sanitizer exception. */ +public class UnsafeArrayOutOfBoundsValid { + 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); + } +} From 507eeaa9c6d66f2b0753182dff260e8b1af5f7f0 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Thu, 3 Jul 2025 18:06:19 +0200 Subject: [PATCH 2/4] Rename `getAccessSize` --- .../sanitizers/UnsafeArrayOutOfBounds.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java index 912063312..1d0b29656 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java @@ -22,7 +22,6 @@ import com.code_intelligence.jazzer.utils.UnsafeProvider; import java.lang.invoke.MethodHandle; import java.lang.reflect.Array; -import sun.jvm.hotspot.utilities.AssertionFailure; import sun.misc.Unsafe; /** @@ -89,23 +88,23 @@ public class UnsafeArrayOutOfBounds { ==================================================== */ - private static int getAccessSize(Class c) { - int accessSize; + private static int getBytesCount(Class c) { + int bytesCount; if (c == boolean.class) { // Assumes that `Unsafe.ARRAY_BOOLEAN_INDEX_SCALE > 0` - accessSize = 1; + bytesCount = 1; } else if (c == byte.class) { - accessSize = 1; + bytesCount = 1; } else if (c == char.class || c == short.class) { - accessSize = 2; + bytesCount = 2; } else if (c == int.class || c == float.class) { - accessSize = 4; + bytesCount = 4; } else if (c == long.class || c == double.class) { - accessSize = 8; + bytesCount = 8; } else { - throw new AssertionFailure("Unexpected type: " + c); + throw new AssertionError("Unexpected type: " + c); } - return accessSize; + return bytesCount; } /** Hook for all {@link Unsafe} methods which read or write an {@code Object} reference. */ @@ -214,7 +213,7 @@ public static void objectAccessHook( targetMethod = "getShortVolatile") public static void primitiveGetterHook( MethodHandle method, Object thisObject, Object[] arguments, int hookId) { - int accessSize = getAccessSize(method.type().returnType()); + int accessSize = getBytesCount(method.type().returnType()); checkAccess(arguments, accessSize); } @@ -306,7 +305,7 @@ public static void primitiveGetterHook( public static void primitiveSetterHook( MethodHandle method, Object thisObject, Object[] arguments, int hookId) { int accessSize = - getAccessSize(method.type().parameterType(2 + 1)); // + 1 for implicit Unsafe instance + getBytesCount(method.type().parameterType(2 + 1)); // + 1 for implicit Unsafe instance checkAccess(arguments, accessSize); } From 3dc54519c97c0b3f3b559b260bcc775b311a3816 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Thu, 3 Jul 2025 20:51:14 +0200 Subject: [PATCH 3/4] Fix wrong method descriptors --- .../sanitizers/UnsafeArrayOutOfBounds.java | 41 ++++++++++--------- .../jazzer/api/MethodHook.java | 5 ++- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java index 1d0b29656..8c1e082f8 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java @@ -75,7 +75,10 @@ public class UnsafeArrayOutOfBounds { if (duplicateMethodNames.contains(methodName)) { String methodDesc; try { - methodDesc = MethodHandles.lookup().unreflect(m).type().toMethodDescriptorString(); + methodDesc = MethodHandles.lookup().unreflect(m).type() + // Drop receiver type (i.e. `Unsafe this`) + .dropParameterTypes(0, 1) + .toMethodDescriptorString(); } catch (IllegalAccessException e) { throw new RuntimeException(e); } @@ -152,7 +155,7 @@ public static void objectAccessHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getByte", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)B") + targetMethodDescriptor = "(Ljava/lang/Object;J)B") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -161,7 +164,7 @@ public static void objectAccessHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getChar", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)C") + targetMethodDescriptor = "(Ljava/lang/Object;J)C") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -170,7 +173,7 @@ public static void objectAccessHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getDouble", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)D") + targetMethodDescriptor = "(Ljava/lang/Object;J)D") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -179,7 +182,7 @@ public static void objectAccessHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getFloat", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)F") + targetMethodDescriptor = "(Ljava/lang/Object;J)F") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -188,7 +191,7 @@ public static void objectAccessHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getInt", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)I") + targetMethodDescriptor = "(Ljava/lang/Object;J)I") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -197,7 +200,7 @@ public static void objectAccessHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getLong", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)J") + targetMethodDescriptor = "(Ljava/lang/Object;J)J") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -206,7 +209,7 @@ public static void objectAccessHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "getShort", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;J)S") + targetMethodDescriptor = "(Ljava/lang/Object;J)S") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -238,7 +241,7 @@ public static void primitiveGetterHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putByte", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JB)V") + targetMethodDescriptor = "(Ljava/lang/Object;JB)V") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -247,7 +250,7 @@ public static void primitiveGetterHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putChar", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JC)V") + targetMethodDescriptor = "(Ljava/lang/Object;JC)V") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -256,7 +259,7 @@ public static void primitiveGetterHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putDouble", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JD)V") + targetMethodDescriptor = "(Ljava/lang/Object;JD)V") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -265,7 +268,7 @@ public static void primitiveGetterHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putFloat", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JF)V") + targetMethodDescriptor = "(Ljava/lang/Object;JF)V") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -274,7 +277,7 @@ public static void primitiveGetterHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putInt", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JI)V") + targetMethodDescriptor = "(Ljava/lang/Object;JI)V") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -283,7 +286,7 @@ public static void primitiveGetterHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putLong", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JJ)V") + targetMethodDescriptor = "(Ljava/lang/Object;JJ)V") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -292,7 +295,7 @@ public static void primitiveGetterHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, targetMethod = "putShort", - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JS)V") + targetMethodDescriptor = "(Ljava/lang/Object;JS)V") @MethodHook( type = HookType.BEFORE, targetClassName = UNSAFE_NAME, @@ -315,7 +318,7 @@ public static void primitiveSetterHook( targetClassName = UNSAFE_NAME, targetMethod = "setMemory", // Only handle overload with Object parameter - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JJB)V") + targetMethodDescriptor = "(Ljava/lang/Object;JJB)V") public static void setMemoryHook( MethodHandle method, Object thisObject, Object[] arguments, int hookId) { checkAccess(arguments[0], (long) arguments[1], (long) arguments[2]); @@ -327,7 +330,7 @@ public static void setMemoryHook( targetClassName = UNSAFE_NAME, targetMethod = "copyMemory", // Only handle overload with Object parameters - targetMethodDescriptor = "(Lsun/misc/Unsafe;Ljava/lang/Object;JLjava/lang/Object;JJ)V") + 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]; @@ -383,7 +386,7 @@ private static void checkAccess(Object obj, long offset, long accessSize) { long indexScale = UNSAFE.arrayIndexScale(objClass); if (offset < baseOffset) { - report("Offset " + offset + " lower than baseOffset " + baseOffset); + report("Offset " + offset + " is lower than baseOffset " + baseOffset); } long endOffset = baseOffset + Array.getLength(obj) * indexScale; // Uses `compareUnsigned` to account for overflow @@ -439,7 +442,7 @@ private static void checkObjectSizedAccess(Object obj, long offset) { long indexScale = UNSAFE.arrayIndexScale(objClass); if (offset < baseOffset) { - report("Offset " + offset + " lower than baseOffset " + baseOffset); + report("Offset " + offset + " is lower than baseOffset " + baseOffset); } if ((offset - baseOffset) % indexScale != 0) { 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 */ From feace131c249ed270e30a71e08fb0b5e54eae6ee Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Fri, 4 Jul 2025 13:11:19 +0200 Subject: [PATCH 4/4] Make compatible with Java 8 --- .../sanitizers/UnsafeArrayOutOfBounds.java | 119 ++++++++++++++++-- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java index 8c1e082f8..f1c5b7abe 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java @@ -55,24 +55,23 @@ public class UnsafeArrayOutOfBounds { Script for generating the @MethodHook annotations: ==================================================== Method[] methods = sun.misc.Unsafe.class.getMethods(); - Set allMethodNames = new HashSet<>(); - Set duplicateMethodNames = new HashSet<>(); - Arrays.stream(methods).map(Method::getName).forEach(n -> { - if (!allMethodNames.add(n)) { - duplicateMethodNames.add(n); - } - }); + 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 -> !Set.of("equals", "unpark").contains(m.getName())) + .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 (duplicateMethodNames.contains(methodName)) { + if (methodsWithNativeOnlyOverload.contains(methodName)) { String methodDesc; try { methodDesc = MethodHandles.lookup().unreflect(m).type() @@ -85,8 +84,12 @@ public class UnsafeArrayOutOfBounds { 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 + ")"); } - System.out.println(s + ")"); }); ==================================================== */ @@ -146,6 +149,7 @@ public static void objectAccessHook( @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, @@ -156,6 +160,12 @@ public static void objectAccessHook( 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, @@ -165,6 +175,12 @@ public static void objectAccessHook( 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, @@ -174,6 +190,12 @@ public static void objectAccessHook( 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, @@ -183,6 +205,12 @@ public static void objectAccessHook( 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, @@ -192,6 +220,12 @@ public static void objectAccessHook( 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, @@ -201,6 +235,12 @@ public static void objectAccessHook( 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, @@ -210,6 +250,12 @@ public static void objectAccessHook( 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, @@ -232,6 +278,7 @@ public static void primitiveGetterHook( 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, @@ -242,6 +289,12 @@ public static void primitiveGetterHook( 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, @@ -251,6 +304,12 @@ public static void primitiveGetterHook( 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, @@ -260,6 +319,12 @@ public static void primitiveGetterHook( 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, @@ -269,6 +334,12 @@ public static void primitiveGetterHook( 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, @@ -278,6 +349,12 @@ public static void primitiveGetterHook( 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, @@ -287,6 +364,12 @@ public static void primitiveGetterHook( 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, @@ -296,6 +379,12 @@ public static void primitiveGetterHook( 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, @@ -342,9 +431,15 @@ 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 checkAccess(Object[] args, long accessSize) { Object obj = args[0]; - long offset = (long) args[1]; + long offset = offsetValue(args[1]); checkAccess(obj, offset, accessSize); } @@ -406,7 +501,7 @@ private static void checkAccess(Object obj, long offset, long accessSize) { private static void checkObjectSizedAccess(Object[] args) { Object obj = args[0]; - long offset = (long) args[1]; + long offset = offsetValue(args[1]); checkObjectSizedAccess(obj, offset); }