Loading core/java/com/android/internal/util/ArrayUtils.java +68 −0 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Build; import android.ravenwood.annotation.RavenwoodReplace; import android.util.ArraySet; import android.util.ArraySet; import android.util.EmptyArray; import android.util.EmptyArray; Loading @@ -39,6 +40,10 @@ import java.util.function.IntFunction; /** /** * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}. * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}. * <p> * Test with: * <code>atest FrameworksUtilTests:com.android.internal.util.ArrayUtilsTest</code> * <code>atest FrameworksUtilTestsRavenwood:com.android.internal.util.ArrayUtilsTest</code> */ */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodKeepWholeClass public class ArrayUtils { public class ArrayUtils { Loading Loading @@ -84,6 +89,69 @@ public class ArrayUtils { return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen); return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen); } } /** * This is like <code>new byte[length]</code>, but it allocates the array as non-movable. This * prevents copies of the data from being left on the Java heap as a result of heap compaction. * Use this when the array will contain sensitive data such as a password or cryptographic key * that needs to be wiped from memory when no longer needed. The owner of the array is still * responsible for the zeroization; {@link #zeroize(byte[])} should be used to do so. * * @param length the length of the array to allocate * @return the new array */ public static byte[] newNonMovableByteArray(int length) { return (byte[]) VMRuntime.getRuntime().newNonMovableArray(byte.class, length); } /** * Like {@link #newNonMovableByteArray(int)}, but allocates a char array. * * @param length the length of the array to allocate * @return the new array */ public static char[] newNonMovableCharArray(int length) { return (char[]) VMRuntime.getRuntime().newNonMovableArray(char.class, length); } /** * Zeroizes a byte array as securely as possible. Use this when the array contains sensitive * data such as a password or cryptographic key. * <p> * This zeroizes the array in a way that is guaranteed to not be optimized out by the compiler. * If supported by the architecture, it zeroizes the data not just in the L1 data cache but also * in other levels of the memory hierarchy up to and including main memory (but not above that). * <p> * This works on any <code>byte[]</code>, but to ensure that copies of the array aren't left on * the Java heap the array should have been allocated with {@link #newNonMovableByteArray(int)}. * Use on other arrays might also introduce performance anomalies. * * @param array the array to zeroize. If null, this method has no effect. */ @RavenwoodReplace public static native void zeroize(byte[] array); /** * Replacement of the above method for host-side unit testing that doesn't support JNI yet. */ public static void zeroize$ravenwood(byte[] array) { if (array != null) { Arrays.fill(array, (byte) 0); } } /** * Like {@link #zeroize(byte[])}, but for char arrays. */ @RavenwoodReplace public static native void zeroize(char[] array); /** * Replacement of the above method for host-side unit testing that doesn't support JNI yet. */ public static void zeroize$ravenwood(char[] array) { if (array != null) { Arrays.fill(array, (char) 0); } } /** /** * Checks if the beginnings of two byte arrays are equal. * Checks if the beginnings of two byte arrays are equal. * * Loading core/jni/Android.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -90,6 +90,7 @@ cc_library_shared_for_libandroid_runtime { "android_view_VelocityTracker.cpp", "android_view_VelocityTracker.cpp", "android_view_VerifiedKeyEvent.cpp", "android_view_VerifiedKeyEvent.cpp", "android_view_VerifiedMotionEvent.cpp", "android_view_VerifiedMotionEvent.cpp", "com_android_internal_util_ArrayUtils.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", "core_jni_helpers.cpp", "core_jni_helpers.cpp", ":deviceproductinfoconstants_aidl", ":deviceproductinfoconstants_aidl", Loading core/jni/AndroidRuntime.cpp +2 −0 Original line number Original line Diff line number Diff line Loading @@ -215,6 +215,7 @@ extern int register_com_android_internal_os_Zygote(JNIEnv *env); extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_security_VerityUtils(JNIEnv* env); extern int register_com_android_internal_security_VerityUtils(JNIEnv* env); extern int register_com_android_internal_util_ArrayUtils(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_android_window_WindowInfosListener(JNIEnv* env); extern int register_android_window_WindowInfosListener(JNIEnv* env); extern int register_android_window_ScreenCapture(JNIEnv* env); extern int register_android_window_ScreenCapture(JNIEnv* env); Loading Loading @@ -1613,6 +1614,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_com_android_internal_security_VerityUtils), REG_JNI(register_com_android_internal_security_VerityUtils), REG_JNI(register_com_android_internal_util_ArrayUtils), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_camera2_CameraMetadata), REG_JNI(register_android_hardware_camera2_CameraMetadata), Loading core/jni/com_android_internal_util_ArrayUtils.cpp 0 → 100644 +119 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * 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. */ #define LOG_TAG "ArrayUtils" #include <android-base/logging.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <string.h> #include <unistd.h> #include <utils/Log.h> namespace android { static size_t GetCacheLineSize() { long size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); if (size <= 0) { ALOGE("Unable to determine L1 data cache line size. Assuming 32 bytes"); return 32; } // The cache line size should always be a power of 2. CHECK((size & (size - 1)) == 0); return size; } static void CleanCacheLineContainingAddress(const uint8_t* p) { #if defined(__aarch64__) // 'dc cvac' stands for "Data Cache line Clean by Virtual Address to point-of-Coherency". // It writes the cache line back to the "point-of-coherency", i.e. main memory. asm volatile("dc cvac, %0" ::"r"(p)); #elif defined(__i386__) || defined(__x86_64__) asm volatile("clflush (%0)" ::"r"(p)); #elif defined(__riscv) // This should eventually work, but it is not ready to be enabled yet: // 1.) The Android emulator needs to add support for zicbom. // 2.) Kernel needs to enable zicbom in usermode. // 3.) Android clang needs to add zicbom to the target. // asm volatile("cbo.clean (%0)" ::"r"(p)); #elif defined(__arm__) // arm32 has a cacheflush() syscall, but it is undocumented and only flushes the icache. // It is not the same as cacheflush(2) as documented in the Linux man-pages project. #else #error "Unknown architecture" #endif } static void CleanDataCache(const uint8_t* p, size_t buffer_size, size_t cache_line_size) { // Clean the first line that overlaps the buffer. CleanCacheLineContainingAddress(p); // Clean any additional lines that overlap the buffer. Use cache-line-aligned addresses to // ensure that (a) the last cache line gets flushed, and (b) no cache line is flushed twice. for (size_t i = cache_line_size - ((uintptr_t)p & (cache_line_size - 1)); i < buffer_size; i += cache_line_size) { CleanCacheLineContainingAddress(p + i); } } static void ZeroizePrimitiveArray(JNIEnv* env, jclass clazz, jarray array, size_t component_len) { static const size_t cache_line_size = GetCacheLineSize(); if (array == nullptr) { return; } size_t buffer_size = env->GetArrayLength(array) * component_len; if (buffer_size == 0) { return; } // ART guarantees that GetPrimitiveArrayCritical never copies. jboolean isCopy; void* elems = env->GetPrimitiveArrayCritical(array, &isCopy); CHECK(!isCopy); #ifdef __BIONIC__ memset_explicit(elems, 0, buffer_size); #else memset(elems, 0, buffer_size); #endif // Clean the data cache so that the data gets zeroized in main memory right away. Without this, // it might not be written to main memory until the cache line happens to be evicted. CleanDataCache(static_cast<const uint8_t*>(elems), buffer_size, cache_line_size); env->ReleasePrimitiveArrayCritical(array, elems, /* mode= */ 0); } static void ZeroizeByteArray(JNIEnv* env, jclass clazz, jbyteArray array) { ZeroizePrimitiveArray(env, clazz, array, sizeof(jbyte)); } static void ZeroizeCharArray(JNIEnv* env, jclass clazz, jcharArray array) { ZeroizePrimitiveArray(env, clazz, array, sizeof(jchar)); } static const JNINativeMethod sMethods[] = { {"zeroize", "([B)V", (void*)ZeroizeByteArray}, {"zeroize", "([C)V", (void*)ZeroizeCharArray}, }; int register_com_android_internal_util_ArrayUtils(JNIEnv* env) { return jniRegisterNativeMethods(env, "com/android/internal/util/ArrayUtils", sMethods, NELEM(sMethods)); } } // namespace android core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java +54 −0 Original line number Original line Diff line number Diff line Loading @@ -496,4 +496,58 @@ public class ArrayUtilsTest { // expected // expected } } } } // Note: the zeroize() tests only test the behavior that can be tested from a Java test. // They do not verify that no copy of the data is left anywhere. @Test @SmallTest public void testZeroizeNonMovableByteArray() { final int length = 10; byte[] array = ArrayUtils.newNonMovableByteArray(length); assertArrayEquals(array, new byte[length]); Arrays.fill(array, (byte) 0xff); ArrayUtils.zeroize(array); assertArrayEquals(array, new byte[length]); } @Test @SmallTest public void testZeroizeRegularByteArray() { final int length = 10; byte[] array = new byte[length]; assertArrayEquals(array, new byte[length]); Arrays.fill(array, (byte) 0xff); ArrayUtils.zeroize(array); assertArrayEquals(array, new byte[length]); } @Test @SmallTest public void testZeroizeNonMovableCharArray() { final int length = 10; char[] array = ArrayUtils.newNonMovableCharArray(length); assertArrayEquals(array, new char[length]); Arrays.fill(array, (char) 0xff); ArrayUtils.zeroize(array); assertArrayEquals(array, new char[length]); } @Test @SmallTest public void testZeroizeRegularCharArray() { final int length = 10; char[] array = new char[length]; assertArrayEquals(array, new char[length]); Arrays.fill(array, (char) 0xff); ArrayUtils.zeroize(array); assertArrayEquals(array, new char[length]); } @Test @SmallTest public void testZeroize_acceptsNull() { ArrayUtils.zeroize((byte[]) null); ArrayUtils.zeroize((char[]) null); } } } Loading
core/java/com/android/internal/util/ArrayUtils.java +68 −0 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Build; import android.ravenwood.annotation.RavenwoodReplace; import android.util.ArraySet; import android.util.ArraySet; import android.util.EmptyArray; import android.util.EmptyArray; Loading @@ -39,6 +40,10 @@ import java.util.function.IntFunction; /** /** * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}. * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}. * <p> * Test with: * <code>atest FrameworksUtilTests:com.android.internal.util.ArrayUtilsTest</code> * <code>atest FrameworksUtilTestsRavenwood:com.android.internal.util.ArrayUtilsTest</code> */ */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodKeepWholeClass public class ArrayUtils { public class ArrayUtils { Loading Loading @@ -84,6 +89,69 @@ public class ArrayUtils { return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen); return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen); } } /** * This is like <code>new byte[length]</code>, but it allocates the array as non-movable. This * prevents copies of the data from being left on the Java heap as a result of heap compaction. * Use this when the array will contain sensitive data such as a password or cryptographic key * that needs to be wiped from memory when no longer needed. The owner of the array is still * responsible for the zeroization; {@link #zeroize(byte[])} should be used to do so. * * @param length the length of the array to allocate * @return the new array */ public static byte[] newNonMovableByteArray(int length) { return (byte[]) VMRuntime.getRuntime().newNonMovableArray(byte.class, length); } /** * Like {@link #newNonMovableByteArray(int)}, but allocates a char array. * * @param length the length of the array to allocate * @return the new array */ public static char[] newNonMovableCharArray(int length) { return (char[]) VMRuntime.getRuntime().newNonMovableArray(char.class, length); } /** * Zeroizes a byte array as securely as possible. Use this when the array contains sensitive * data such as a password or cryptographic key. * <p> * This zeroizes the array in a way that is guaranteed to not be optimized out by the compiler. * If supported by the architecture, it zeroizes the data not just in the L1 data cache but also * in other levels of the memory hierarchy up to and including main memory (but not above that). * <p> * This works on any <code>byte[]</code>, but to ensure that copies of the array aren't left on * the Java heap the array should have been allocated with {@link #newNonMovableByteArray(int)}. * Use on other arrays might also introduce performance anomalies. * * @param array the array to zeroize. If null, this method has no effect. */ @RavenwoodReplace public static native void zeroize(byte[] array); /** * Replacement of the above method for host-side unit testing that doesn't support JNI yet. */ public static void zeroize$ravenwood(byte[] array) { if (array != null) { Arrays.fill(array, (byte) 0); } } /** * Like {@link #zeroize(byte[])}, but for char arrays. */ @RavenwoodReplace public static native void zeroize(char[] array); /** * Replacement of the above method for host-side unit testing that doesn't support JNI yet. */ public static void zeroize$ravenwood(char[] array) { if (array != null) { Arrays.fill(array, (char) 0); } } /** /** * Checks if the beginnings of two byte arrays are equal. * Checks if the beginnings of two byte arrays are equal. * * Loading
core/jni/Android.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -90,6 +90,7 @@ cc_library_shared_for_libandroid_runtime { "android_view_VelocityTracker.cpp", "android_view_VelocityTracker.cpp", "android_view_VerifiedKeyEvent.cpp", "android_view_VerifiedKeyEvent.cpp", "android_view_VerifiedMotionEvent.cpp", "android_view_VerifiedMotionEvent.cpp", "com_android_internal_util_ArrayUtils.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", "core_jni_helpers.cpp", "core_jni_helpers.cpp", ":deviceproductinfoconstants_aidl", ":deviceproductinfoconstants_aidl", Loading
core/jni/AndroidRuntime.cpp +2 −0 Original line number Original line Diff line number Diff line Loading @@ -215,6 +215,7 @@ extern int register_com_android_internal_os_Zygote(JNIEnv *env); extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_security_VerityUtils(JNIEnv* env); extern int register_com_android_internal_security_VerityUtils(JNIEnv* env); extern int register_com_android_internal_util_ArrayUtils(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_android_window_WindowInfosListener(JNIEnv* env); extern int register_android_window_WindowInfosListener(JNIEnv* env); extern int register_android_window_ScreenCapture(JNIEnv* env); extern int register_android_window_ScreenCapture(JNIEnv* env); Loading Loading @@ -1613,6 +1614,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_com_android_internal_security_VerityUtils), REG_JNI(register_com_android_internal_security_VerityUtils), REG_JNI(register_com_android_internal_util_ArrayUtils), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_camera2_CameraMetadata), REG_JNI(register_android_hardware_camera2_CameraMetadata), Loading
core/jni/com_android_internal_util_ArrayUtils.cpp 0 → 100644 +119 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * 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. */ #define LOG_TAG "ArrayUtils" #include <android-base/logging.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <string.h> #include <unistd.h> #include <utils/Log.h> namespace android { static size_t GetCacheLineSize() { long size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); if (size <= 0) { ALOGE("Unable to determine L1 data cache line size. Assuming 32 bytes"); return 32; } // The cache line size should always be a power of 2. CHECK((size & (size - 1)) == 0); return size; } static void CleanCacheLineContainingAddress(const uint8_t* p) { #if defined(__aarch64__) // 'dc cvac' stands for "Data Cache line Clean by Virtual Address to point-of-Coherency". // It writes the cache line back to the "point-of-coherency", i.e. main memory. asm volatile("dc cvac, %0" ::"r"(p)); #elif defined(__i386__) || defined(__x86_64__) asm volatile("clflush (%0)" ::"r"(p)); #elif defined(__riscv) // This should eventually work, but it is not ready to be enabled yet: // 1.) The Android emulator needs to add support for zicbom. // 2.) Kernel needs to enable zicbom in usermode. // 3.) Android clang needs to add zicbom to the target. // asm volatile("cbo.clean (%0)" ::"r"(p)); #elif defined(__arm__) // arm32 has a cacheflush() syscall, but it is undocumented and only flushes the icache. // It is not the same as cacheflush(2) as documented in the Linux man-pages project. #else #error "Unknown architecture" #endif } static void CleanDataCache(const uint8_t* p, size_t buffer_size, size_t cache_line_size) { // Clean the first line that overlaps the buffer. CleanCacheLineContainingAddress(p); // Clean any additional lines that overlap the buffer. Use cache-line-aligned addresses to // ensure that (a) the last cache line gets flushed, and (b) no cache line is flushed twice. for (size_t i = cache_line_size - ((uintptr_t)p & (cache_line_size - 1)); i < buffer_size; i += cache_line_size) { CleanCacheLineContainingAddress(p + i); } } static void ZeroizePrimitiveArray(JNIEnv* env, jclass clazz, jarray array, size_t component_len) { static const size_t cache_line_size = GetCacheLineSize(); if (array == nullptr) { return; } size_t buffer_size = env->GetArrayLength(array) * component_len; if (buffer_size == 0) { return; } // ART guarantees that GetPrimitiveArrayCritical never copies. jboolean isCopy; void* elems = env->GetPrimitiveArrayCritical(array, &isCopy); CHECK(!isCopy); #ifdef __BIONIC__ memset_explicit(elems, 0, buffer_size); #else memset(elems, 0, buffer_size); #endif // Clean the data cache so that the data gets zeroized in main memory right away. Without this, // it might not be written to main memory until the cache line happens to be evicted. CleanDataCache(static_cast<const uint8_t*>(elems), buffer_size, cache_line_size); env->ReleasePrimitiveArrayCritical(array, elems, /* mode= */ 0); } static void ZeroizeByteArray(JNIEnv* env, jclass clazz, jbyteArray array) { ZeroizePrimitiveArray(env, clazz, array, sizeof(jbyte)); } static void ZeroizeCharArray(JNIEnv* env, jclass clazz, jcharArray array) { ZeroizePrimitiveArray(env, clazz, array, sizeof(jchar)); } static const JNINativeMethod sMethods[] = { {"zeroize", "([B)V", (void*)ZeroizeByteArray}, {"zeroize", "([C)V", (void*)ZeroizeCharArray}, }; int register_com_android_internal_util_ArrayUtils(JNIEnv* env) { return jniRegisterNativeMethods(env, "com/android/internal/util/ArrayUtils", sMethods, NELEM(sMethods)); } } // namespace android
core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java +54 −0 Original line number Original line Diff line number Diff line Loading @@ -496,4 +496,58 @@ public class ArrayUtilsTest { // expected // expected } } } } // Note: the zeroize() tests only test the behavior that can be tested from a Java test. // They do not verify that no copy of the data is left anywhere. @Test @SmallTest public void testZeroizeNonMovableByteArray() { final int length = 10; byte[] array = ArrayUtils.newNonMovableByteArray(length); assertArrayEquals(array, new byte[length]); Arrays.fill(array, (byte) 0xff); ArrayUtils.zeroize(array); assertArrayEquals(array, new byte[length]); } @Test @SmallTest public void testZeroizeRegularByteArray() { final int length = 10; byte[] array = new byte[length]; assertArrayEquals(array, new byte[length]); Arrays.fill(array, (byte) 0xff); ArrayUtils.zeroize(array); assertArrayEquals(array, new byte[length]); } @Test @SmallTest public void testZeroizeNonMovableCharArray() { final int length = 10; char[] array = ArrayUtils.newNonMovableCharArray(length); assertArrayEquals(array, new char[length]); Arrays.fill(array, (char) 0xff); ArrayUtils.zeroize(array); assertArrayEquals(array, new char[length]); } @Test @SmallTest public void testZeroizeRegularCharArray() { final int length = 10; char[] array = new char[length]; assertArrayEquals(array, new char[length]); Arrays.fill(array, (char) 0xff); ArrayUtils.zeroize(array); assertArrayEquals(array, new char[length]); } @Test @SmallTest public void testZeroize_acceptsNull() { ArrayUtils.zeroize((byte[]) null); ArrayUtils.zeroize((char[]) null); } } }