You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
12 KiB
330 lines
12 KiB
4 months ago
|
/*
|
||
|
* Copyright (C) 2020 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.
|
||
|
*/
|
||
|
|
||
|
//
|
||
|
// Test that metadata encryption is working, via:
|
||
|
//
|
||
|
// - Correctness tests. These test the standard metadata encryption formats
|
||
|
// supported by Android R and higher via dm-default-key v2.
|
||
|
//
|
||
|
// - Randomness test. This runs on all devices that use metadata encryption.
|
||
|
//
|
||
|
// The correctness tests create a temporary default-key mapping over the raw
|
||
|
// userdata partition, read from it, and verify that the data got decrypted
|
||
|
// correctly. This only tests decryption, since this avoids having to find a
|
||
|
// region on disk that can safely be modified. This should be good enough since
|
||
|
// the device wouldn't work anyway if decryption didn't invert encryption.
|
||
|
//
|
||
|
// Note that this temporary default-key mapping will overlap the device's "real"
|
||
|
// default-key mapping, if the device has one. The kernel allows this. The
|
||
|
// tests don't use a loopback device instead, since dm-default-key over a
|
||
|
// loopback device can't use the real inline encryption hardware.
|
||
|
//
|
||
|
// The correctness tests cover the following settings:
|
||
|
//
|
||
|
// metadata_encryption=aes-256-xts
|
||
|
// metadata_encryption=adiantum
|
||
|
// metadata_encryption=aes-256-xts:wrappedkey_v0
|
||
|
//
|
||
|
// The tests don't check which one of those settings, if any, the device is
|
||
|
// actually using; they just try to test everything they can.
|
||
|
//
|
||
|
// These tests don't specifically test that file contents aren't encrypted
|
||
|
// twice. That's already implied by the file-based encryption test cases,
|
||
|
// provided that the device actually has metadata encryption enabled.
|
||
|
//
|
||
|
|
||
|
#include <android-base/file.h>
|
||
|
#include <android-base/unique_fd.h>
|
||
|
#include <asm/byteorder.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <fstab/fstab.h>
|
||
|
#include <gtest/gtest.h>
|
||
|
#include <libdm/dm.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <chrono>
|
||
|
|
||
|
#include "vts_kernel_encryption.h"
|
||
|
|
||
|
using namespace android::dm;
|
||
|
|
||
|
namespace android {
|
||
|
namespace kernel {
|
||
|
|
||
|
#define cpu_to_le64 __cpu_to_le64
|
||
|
#define le64_to_cpu __le64_to_cpu
|
||
|
|
||
|
// Alignment to use for direct I/O reads of block devices
|
||
|
static constexpr int kDirectIOAlignment = 4096;
|
||
|
|
||
|
// Assumed size of filesystem blocks, in bytes
|
||
|
static constexpr int kFilesystemBlockSize = 4096;
|
||
|
|
||
|
// Checks whether the kernel supports version 2 or higher of dm-default-key.
|
||
|
static bool IsDmDefaultKeyV2Supported(DeviceMapper &dm) {
|
||
|
DmTargetTypeInfo info;
|
||
|
if (!dm.GetTargetByName("default-key", &info)) {
|
||
|
GTEST_LOG_(INFO) << "dm-default-key not enabled";
|
||
|
return false;
|
||
|
}
|
||
|
if (!info.IsAtLeast(2, 0, 0)) {
|
||
|
// The legacy version of dm-default-key (which was never supported by the
|
||
|
// Android common kernels) used a vendor-specific on-disk format, so it's
|
||
|
// not testable by a vendor-independent test.
|
||
|
GTEST_LOG_(INFO) << "Detected legacy dm-default-key";
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Reads |count| bytes from the beginning of |blk_device|, using direct I/O to
|
||
|
// avoid getting any stale cached data. Direct I/O requires using a hardware
|
||
|
// sector size aligned buffer.
|
||
|
static bool ReadBlockDevice(const std::string &blk_device, size_t count,
|
||
|
std::vector<uint8_t> *data) {
|
||
|
GTEST_LOG_(INFO) << "Reading " << count << " bytes from " << blk_device;
|
||
|
std::unique_ptr<void, void (*)(void *)> buf_mem(
|
||
|
aligned_alloc(kDirectIOAlignment, count), free);
|
||
|
if (buf_mem == nullptr) {
|
||
|
ADD_FAILURE() << "out of memory";
|
||
|
return false;
|
||
|
}
|
||
|
uint8_t *buffer = static_cast<uint8_t *>(buf_mem.get());
|
||
|
|
||
|
android::base::unique_fd fd(
|
||
|
open(blk_device.c_str(), O_RDONLY | O_DIRECT | O_CLOEXEC));
|
||
|
if (fd < 0) {
|
||
|
ADD_FAILURE() << "Failed to open " << blk_device << Errno();
|
||
|
return false;
|
||
|
}
|
||
|
if (!android::base::ReadFully(fd, buffer, count)) {
|
||
|
ADD_FAILURE() << "Failed to read from " << blk_device << Errno();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
*data = std::vector<uint8_t>(buffer, buffer + count);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
class DmDefaultKeyTest : public ::testing::Test {
|
||
|
// Name to assign to the dm-default-key test device
|
||
|
static constexpr const char *kTestDmDeviceName = "vts-test-default-key";
|
||
|
|
||
|
// Filesystem whose underlying partition the test will use
|
||
|
static constexpr const char *kTestMountpoint = "/data";
|
||
|
|
||
|
// Size of the dm-default-key crypto sector size (data unit size) in bytes
|
||
|
static constexpr int kCryptoSectorSize = 4096;
|
||
|
|
||
|
// Size of the test data in crypto sectors
|
||
|
static constexpr int kTestDataSectors = 256;
|
||
|
|
||
|
// Size of the test data in bytes
|
||
|
static constexpr int kTestDataBytes = kTestDataSectors * kCryptoSectorSize;
|
||
|
|
||
|
// Device-mapper API sector size in bytes.
|
||
|
// This is unrelated to the crypto sector size.
|
||
|
static constexpr int kDmApiSectorSize = 512;
|
||
|
|
||
|
protected:
|
||
|
void SetUp() override;
|
||
|
void TearDown() override;
|
||
|
bool CreateTestDevice(const std::string &cipher,
|
||
|
const std::vector<uint8_t> &key, bool is_wrapped_key);
|
||
|
void VerifyDecryption(const std::vector<uint8_t> &key, const Cipher &cipher);
|
||
|
void DoTest(const std::string &cipher_string, const Cipher &cipher);
|
||
|
bool skip_test_ = false;
|
||
|
DeviceMapper *dm_ = nullptr;
|
||
|
std::string raw_blk_device_;
|
||
|
std::string dm_device_path_;
|
||
|
};
|
||
|
|
||
|
// Test setup procedure. Checks for the needed kernel support, finds the raw
|
||
|
// partition to use, and does other preparations. skip_test_ is set to true if
|
||
|
// the test should be skipped.
|
||
|
void DmDefaultKeyTest::SetUp() {
|
||
|
dm_ = &DeviceMapper::Instance();
|
||
|
|
||
|
if (!IsDmDefaultKeyV2Supported(*dm_)) {
|
||
|
int first_api_level;
|
||
|
ASSERT_TRUE(GetFirstApiLevel(&first_api_level));
|
||
|
// Devices launching with R or higher must support dm-default-key v2.
|
||
|
ASSERT_LE(first_api_level, __ANDROID_API_Q__);
|
||
|
GTEST_LOG_(INFO)
|
||
|
<< "Skipping test because dm-default-key v2 is unsupported";
|
||
|
skip_test_ = true;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FilesystemInfo fs_info;
|
||
|
ASSERT_TRUE(GetFilesystemInfo(kTestMountpoint, &fs_info));
|
||
|
raw_blk_device_ = fs_info.raw_blk_device;
|
||
|
|
||
|
dm_->DeleteDevice(kTestDmDeviceName);
|
||
|
}
|
||
|
|
||
|
void DmDefaultKeyTest::TearDown() { dm_->DeleteDevice(kTestDmDeviceName); }
|
||
|
|
||
|
// Creates the test dm-default-key mapping using the given key and settings.
|
||
|
// If the dm device creation fails, then it is assumed the kernel doesn't
|
||
|
// support the given encryption settings, and a failure is not added.
|
||
|
bool DmDefaultKeyTest::CreateTestDevice(const std::string &cipher,
|
||
|
const std::vector<uint8_t> &key,
|
||
|
bool is_wrapped_key) {
|
||
|
static_assert(kTestDataBytes % kDmApiSectorSize == 0);
|
||
|
std::unique_ptr<DmTargetDefaultKey> target =
|
||
|
std::make_unique<DmTargetDefaultKey>(0, kTestDataBytes / kDmApiSectorSize,
|
||
|
cipher.c_str(), BytesToHex(key),
|
||
|
raw_blk_device_, 0);
|
||
|
target->SetSetDun();
|
||
|
if (is_wrapped_key) target->SetWrappedKeyV0();
|
||
|
|
||
|
DmTable table;
|
||
|
if (!table.AddTarget(std::move(target))) {
|
||
|
ADD_FAILURE() << "Failed to add default-key target to table";
|
||
|
return false;
|
||
|
}
|
||
|
if (!table.valid()) {
|
||
|
ADD_FAILURE() << "Device-mapper table failed to validate";
|
||
|
return false;
|
||
|
}
|
||
|
if (!dm_->CreateDevice(kTestDmDeviceName, table, &dm_device_path_,
|
||
|
std::chrono::seconds(5))) {
|
||
|
GTEST_LOG_(INFO) << "Unable to create default-key mapping" << Errno()
|
||
|
<< ". Assuming that the encryption settings cipher=\""
|
||
|
<< cipher << "\", is_wrapped_key=" << is_wrapped_key
|
||
|
<< " are unsupported and skipping the test.";
|
||
|
return false;
|
||
|
}
|
||
|
GTEST_LOG_(INFO) << "Created default-key mapping at " << dm_device_path_
|
||
|
<< " using cipher=\"" << cipher
|
||
|
<< "\", key=" << BytesToHex(key)
|
||
|
<< ", is_wrapped_key=" << is_wrapped_key;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void DmDefaultKeyTest::VerifyDecryption(const std::vector<uint8_t> &key,
|
||
|
const Cipher &cipher) {
|
||
|
std::vector<uint8_t> raw_data;
|
||
|
std::vector<uint8_t> decrypted_data;
|
||
|
|
||
|
ASSERT_TRUE(ReadBlockDevice(raw_blk_device_, kTestDataBytes, &raw_data));
|
||
|
ASSERT_TRUE(
|
||
|
ReadBlockDevice(dm_device_path_, kTestDataBytes, &decrypted_data));
|
||
|
|
||
|
// Verify that the decrypted data encrypts to the raw data.
|
||
|
|
||
|
GTEST_LOG_(INFO) << "Verifying correctness of decrypted data";
|
||
|
|
||
|
// Initialize the IV for crypto sector 0.
|
||
|
ASSERT_GE(cipher.ivsize(), sizeof(__le64));
|
||
|
std::unique_ptr<__le64> iv(new (::operator new(cipher.ivsize())) __le64);
|
||
|
memset(iv.get(), 0, cipher.ivsize());
|
||
|
|
||
|
// Encrypt each sector.
|
||
|
std::vector<uint8_t> encrypted_data(kTestDataBytes);
|
||
|
static_assert(kTestDataBytes % kCryptoSectorSize == 0);
|
||
|
for (size_t i = 0; i < kTestDataBytes; i += kCryptoSectorSize) {
|
||
|
ASSERT_TRUE(cipher.Encrypt(key, reinterpret_cast<const uint8_t *>(iv.get()),
|
||
|
&decrypted_data[i], &encrypted_data[i],
|
||
|
kCryptoSectorSize));
|
||
|
|
||
|
// Update the IV by incrementing the crypto sector number.
|
||
|
*iv = cpu_to_le64(le64_to_cpu(*iv) + 1);
|
||
|
}
|
||
|
|
||
|
ASSERT_EQ(encrypted_data, raw_data);
|
||
|
}
|
||
|
|
||
|
void DmDefaultKeyTest::DoTest(const std::string &cipher_string,
|
||
|
const Cipher &cipher) {
|
||
|
if (skip_test_) return;
|
||
|
|
||
|
std::vector<uint8_t> key = GenerateTestKey(cipher.keysize());
|
||
|
|
||
|
if (!CreateTestDevice(cipher_string, key, false)) return;
|
||
|
|
||
|
VerifyDecryption(key, cipher);
|
||
|
}
|
||
|
|
||
|
// Tests dm-default-key parameters matching metadata_encryption=aes-256-xts.
|
||
|
TEST_F(DmDefaultKeyTest, TestAes256Xts) {
|
||
|
DoTest("aes-xts-plain64", Aes256XtsCipher());
|
||
|
}
|
||
|
|
||
|
// Tests dm-default-key parameters matching metadata_encryption=adiantum.
|
||
|
TEST_F(DmDefaultKeyTest, TestAdiantum) {
|
||
|
DoTest("xchacha12,aes-adiantum-plain64", AdiantumCipher());
|
||
|
}
|
||
|
|
||
|
// Tests dm-default-key parameters matching
|
||
|
// metadata_encryption=aes-256-xts:wrappedkey_v0.
|
||
|
TEST_F(DmDefaultKeyTest, TestHwWrappedKey) {
|
||
|
if (skip_test_) return;
|
||
|
|
||
|
std::vector<uint8_t> master_key, exported_key;
|
||
|
if (!CreateHwWrappedKey(&master_key, &exported_key)) return;
|
||
|
|
||
|
if (!CreateTestDevice("aes-xts-plain64", exported_key, true)) return;
|
||
|
|
||
|
std::vector<uint8_t> enc_key;
|
||
|
ASSERT_TRUE(DeriveHwWrappedEncryptionKey(master_key, &enc_key));
|
||
|
|
||
|
VerifyDecryption(enc_key, Aes256XtsCipher());
|
||
|
}
|
||
|
|
||
|
// Tests that if the device uses metadata encryption, then the first
|
||
|
// kFilesystemBlockSize bytes of the userdata partition appear random. For ext4
|
||
|
// and f2fs, this block should contain the filesystem superblock; it therefore
|
||
|
// should be initialized and metadata-encrypted. Ideally we'd check additional
|
||
|
// blocks too, but that would require awareness of the filesystem structure.
|
||
|
//
|
||
|
// This isn't as strong a test as the correctness tests, but it's useful because
|
||
|
// it applies regardless of the encryption format and key. Thus it runs even on
|
||
|
// old devices, including ones that used a vendor-specific encryption format.
|
||
|
TEST(MetadataEncryptionTest, TestRandomness) {
|
||
|
constexpr const char *mountpoint = "/data";
|
||
|
|
||
|
android::fs_mgr::Fstab fstab;
|
||
|
ASSERT_TRUE(android::fs_mgr::ReadDefaultFstab(&fstab));
|
||
|
const fs_mgr::FstabEntry *entry = GetEntryForMountPoint(&fstab, mountpoint);
|
||
|
ASSERT_TRUE(entry != nullptr);
|
||
|
|
||
|
if (entry->metadata_key_dir.empty()) {
|
||
|
int first_api_level;
|
||
|
ASSERT_TRUE(GetFirstApiLevel(&first_api_level));
|
||
|
ASSERT_LE(first_api_level, __ANDROID_API_Q__)
|
||
|
<< "Metadata encryption is required";
|
||
|
GTEST_LOG_(INFO)
|
||
|
<< "Skipping test because device doesn't use metadata encryption";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GTEST_LOG_(INFO) << "Verifying randomness of ciphertext";
|
||
|
std::vector<uint8_t> raw_data;
|
||
|
FilesystemInfo fs_info;
|
||
|
ASSERT_TRUE(GetFilesystemInfo(mountpoint, &fs_info));
|
||
|
ASSERT_TRUE(
|
||
|
ReadBlockDevice(fs_info.raw_blk_device, kFilesystemBlockSize, &raw_data));
|
||
|
ASSERT_TRUE(VerifyDataRandomness(raw_data));
|
||
|
}
|
||
|
|
||
|
} // namespace kernel
|
||
|
} // namespace android
|