Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1adb0dbc authored by Eric Biggers's avatar Eric Biggers Committed by Gerrit Code Review
Browse files

Merge "Re-land ArrayUtils: add secure zeroization support" into main

parents 0603d70e 94a9f7c8
Loading
Loading
Loading
Loading
+68 −0
Original line number Original line Diff line number Diff line
@@ -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;


@@ -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 {
@@ -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.
     *
     *
+1 −0
Original line number Original line Diff line number Diff line
@@ -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",
+2 −0
Original line number Original line Diff line number Diff line
@@ -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);
@@ -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),
+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
+54 −0
Original line number Original line Diff line number Diff line
@@ -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);
    }
}
}