blob: 4cd383aa13d39b7d4530c22d3e185ccd7ad42d4e [file] [log] [blame]
/*
* Copyright (C) 2019 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.
*/
#include <fcntl.h>
#include <linux/fscrypt.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <android-base/unique_fd.h>
#include <cutils/properties.h>
#include <gtest/gtest.h>
// Non-upstream encryption modes that are used on some devices.
#define FSCRYPT_MODE_AES_256_HEH 126
#define FSCRYPT_MODE_PRIVATE 127
// The relevant Android API levels
#define Q_API_LEVEL 29
#define R_API_LEVEL 30
static int getFirstApiLevel(void) {
int level = property_get_int32("ro.product.first_api_level", 0);
if (level == 0) {
level = property_get_int32("ro.build.version.sdk", 0);
}
if (level == 0) {
ADD_FAILURE() << "Failed to determine first API level";
}
return level;
}
#ifdef __arm__
// For ARM32, assemble the 'aese.8' instruction as an .inst, since otherwise
// clang does not accept it. It would be allowed in a separate file compiled
// with -march=armv8+crypto, but this way is much easier. And it's not yet
// possible to use a target function attribute, because clang doesn't yet
// support target("fpu=crypto-neon-fp-armv8") like gcc does.
//
// We use the ARM encoding of the instruction, not the Thumb encoding. So make
// sure to use target("arm") to mark the function as containing ARM code.
static void __attribute__((target("arm"))) executeAESInstruction(void) {
// aese.8 q0, q1
asm volatile(".inst 0xf3b00302" : : : "q0");
}
#elif defined(__aarch64__)
static void __attribute__((target("crypto"))) executeAESInstruction(void) {
asm volatile("aese v0.16b, v1.16b" : : : "v0");
}
#elif defined(__i386__) || defined(__x86_64__)
static void __attribute__((target("sse"))) executeAESInstruction(void) {
asm volatile("aesenc %%xmm1, %%xmm0" : : : "xmm0");
}
#else
#warning "unknown architecture, assuming AES instructions are available"
static void executeAESInstruction(void) {}
#endif
static jmp_buf jump_buf;
static void handleSIGILL(int __attribute__((unused)) signum) {
longjmp(jump_buf, 1);
}
// This function checks for the presence of AES instructions, e.g. ARMv8 crypto
// extensions for ARM, or AES-NI for x86.
//
// ARM processors don't have a standard way for user processes to determine CPU
// features. On Linux it's possible to read the AT_HWCAP and AT_HWCAP2 values
// from /proc/self/auxv. But, this relies on the kernel exposing the features
// correctly, which we don't want to rely on. Instead we actually try to
// execute the instruction, and see whether SIGILL is raised or not.
//
// To keep things consistent we use the same approach on x86 to detect AES-NI,
// though in principle the 'cpuid' instruction could be used there.
static bool cpuHasAESInstructions(void) {
struct sigaction act;
struct sigaction oldact;
bool result;
memset(&act, 0, sizeof(act));
act.sa_handler = handleSIGILL;
EXPECT_EQ(0, sigaction(SIGILL, &act, &oldact));
if (setjmp(jump_buf) != 0) {
// SIGILL was received when executing the AES instruction.
result = false;
} else {
executeAESInstruction();
// Successfully executed the AES instruction.
result = true;
}
EXPECT_EQ(0, sigaction(SIGILL, &oldact, NULL));
return result;
}
// CDD 9.9.3/C-1-5: must use AES-256-XTS or Adiantum contents encryption.
// CDD 9.9.3/C-1-6: must use AES-256-CTS or Adiantum filenames encryption.
// CDD 9.9.3/C-1-12: mustn't use Adiantum if the CPU has AES instructions.
static void validateEncryptionModes(int contents_mode, int filenames_mode,
bool allow_legacy_modes) {
bool allowed = false;
switch (contents_mode) {
case FSCRYPT_MODE_AES_256_XTS:
case FSCRYPT_MODE_ADIANTUM:
allowed = true;
break;
case FSCRYPT_MODE_PRIVATE:
// Some devices shipped with custom kernel patches implementing
// AES-256-XTS inline encryption behind "FSCRYPT_MODE_PRIVATE", so
// we need to let it pass on old devices. It's up to the vendor to
// ensure it's really AES-256-XTS.
allowed = allow_legacy_modes;
if (allowed) {
GTEST_LOG_(INFO) << "Allowing FSCRYPT_MODE_PRIVATE because this is an old device";
}
break;
}
if (!allowed) {
ADD_FAILURE() << "Contents encryption mode not allowed: " << contents_mode;
}
allowed = false;
switch (filenames_mode) {
case FSCRYPT_MODE_AES_256_CTS:
case FSCRYPT_MODE_ADIANTUM:
allowed = true;
break;
case FSCRYPT_MODE_AES_256_HEH:
// At least one device shipped with the experimental AES-256-HEH
// filenames encryption, which was never added to the CDD and was
// only supported by one kernel version (android-4.4). It's
// cryptographically superior to AES-256-CTS for the use case,
// though, so it's compliant in spirit; let it pass on old devices.
allowed = allow_legacy_modes;
if (allowed) {
GTEST_LOG_(INFO)
<< "Allowing FSCRYPT_MODE_AES_256_HEH because this is an old device";
}
break;
}
if (!allowed) {
ADD_FAILURE() << "Filenames encryption mode not allowed: " << filenames_mode;
}
if (contents_mode == FSCRYPT_MODE_ADIANTUM || filenames_mode == FSCRYPT_MODE_ADIANTUM) {
// Adiantum encryption is only allowed if the CPU doesn't have AES instructions.
EXPECT_FALSE(cpuHasAESInstructions());
}
}
// Ideally we'd check whether /data is on eMMC, but that is hard to do from a
// CTS test. To keep things simple we just check whether the system knows about
// at least one eMMC device.
static bool usingEmmcStorage() {
return access("/sys/class/block/mmcblk0", F_OK) == 0;
}
// CDD 9.9.3/C-1-15: must not reuse IVs for file contents encryption except when
// limited by hardware that only supports 32-bit IVs. Like most other
// encryption security requirements, CTS can't directly test this. But the most
// likely case where this requirement wouldn't be met is a misconfiguration
// where FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 ("emmc_optimized" in the fstab) is
// used on a non-eMMC based device. CTS can test for that, so we do so below.
static void validateEncryptionFlags(int flags) {
if (flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) {
EXPECT_TRUE(usingEmmcStorage());
}
}
// We check the encryption policy of /data/local/tmp because it's one of the
// only encrypted directories the shell domain has permission to open. Ideally
// we'd check the user's credential-encrypted storage (/data/user/0) instead.
// It shouldn't matter in practice though, since AOSP code doesn't provide any
// way to configure different directories to use different algorithms...
#define DIR_TO_CHECK "/data/local/tmp/"
// Test that the device is using appropriate encryption algorithms for
// file-based encryption. If this test fails, you should ensure the device's
// fstab has the correct fileencryption= option for the userdata partition. See
// https://source.android.com/security/encryption/file-based.html
TEST(FileBasedEncryptionPolicyTest, allowedPolicy) {
int first_api_level = getFirstApiLevel();
struct fscrypt_get_policy_ex_arg arg;
int res;
int contents_mode;
int filenames_mode;
int flags;
bool allow_legacy_modes = false;
android::base::unique_fd fd(open(DIR_TO_CHECK, O_RDONLY | O_CLOEXEC));
if (fd < 0) {
FAIL() << "Failed to open " DIR_TO_CHECK ": " << strerror(errno);
}
GTEST_LOG_(INFO) << "First API level is " << first_api_level;
// Note: SELinux policy allows the shell domain to use these ioctls, but not
// apps. Therefore this test needs to be a real native test that's run
// through the shell, not a JNI test run through an installed APK.
arg.policy_size = sizeof(arg.policy);
res = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY_EX, &arg);
if (res != 0 && errno == ENOTTY) {
// Handle old kernels that don't support FS_IOC_GET_ENCRYPTION_POLICY_EX
GTEST_LOG_(INFO) << "Old kernel, falling back to FS_IOC_GET_ENCRYPTION_POLICY";
res = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &arg.policy.v1);
}
if (res != 0) {
if (errno == ENODATA || // Directory is unencrypted
errno == ENOENT || // Directory is unencrypted (on older kernels)
errno == EOPNOTSUPP || // Filesystem encryption feature not enabled
errno == ENOTTY) { // Very old kernel, doesn't know about encryption at all
// Starting with Android 10, file-based encryption is required on
// new devices [CDD 9.9.2/C-0-3].
if (first_api_level < Q_API_LEVEL) {
GTEST_LOG_(INFO)
<< "Exempt from file-based encryption due to old starting API level";
return;
}
FAIL() << "Device isn't using file-based encryption";
} else {
FAIL() << "Failed to get encryption policy of " DIR_TO_CHECK ": " << strerror(errno);
}
}
switch (arg.policy.version) {
case FSCRYPT_POLICY_V1:
GTEST_LOG_(INFO) << "Detected v1 encryption policy";
contents_mode = arg.policy.v1.contents_encryption_mode;
filenames_mode = arg.policy.v1.filenames_encryption_mode;
flags = arg.policy.v1.flags;
// Starting with Android 11, FBE must use a strong, non-reversible
// key derivation function [CDD 9.9.3/C-1-13], and FBE keys must
// never be never reused for different cryptographic purposes
// [CDD 9.9.3/C-1-14]. Effectively, these requirements mean that
// the fscrypt policy version must not be v1. If this part of the
// test fails, make sure the device's fstab has something like
// "fileencryption=aes-256-xts:aes-256-cts:v2".
if (first_api_level < R_API_LEVEL) {
GTEST_LOG_(INFO) << "Exempt from non-reversible FBE key derivation due to old "
"starting API level";
// On these old devices we also allow the use of some custom
// encryption mode numbers which were never supported by the
// Android common kernel and shouldn't be used on new devices.
allow_legacy_modes = true;
} else {
ADD_FAILURE() << "Device isn't using non-reversible FBE key derivation";
}
break;
case FSCRYPT_POLICY_V2:
GTEST_LOG_(INFO) << "Detected v2 encryption policy";
contents_mode = arg.policy.v2.contents_encryption_mode;
filenames_mode = arg.policy.v2.filenames_encryption_mode;
flags = arg.policy.v2.flags;
break;
default:
FAIL() << "Unknown encryption policy version: " << arg.policy.version;
}
GTEST_LOG_(INFO) << "Contents encryption mode: " << contents_mode;
GTEST_LOG_(INFO) << "Filenames encryption mode: " << filenames_mode;
validateEncryptionModes(contents_mode, filenames_mode, allow_legacy_modes);
validateEncryptionFlags(flags);
}