Metadata Encryption

Android 7.0 and later supports file-based encryption (FBE). FBE allows different files to be encrypted with different keys that can be unlocked independently. These keys are used to encrypt both file contents and file names. When FBE is used, other information, such as directory layouts, file sizes, permissions, and creation/modification times, is not encrypted. Collectively, this is known as filesystem metadata.

Android 9 introduces support for metadata encryption where hardware support is present. With metadata encryption, a single key present at boot time encrypts whatever content is not encrypted by FBE. This key is protected by Keymaster, which in turn is protected by verified boot.

Implementation

You can set up metadata encryption on new devices running Android 9 by setting up the metadata filesystem, changing the init sequence, and turning on metadata encryption in the device's fstab file.

Hardware requirements

Metadata encryption can only be set up when the data partition is first formatted. As a result, this feature is only for new devices; this is not something an OTA should change.

To support metadata encryption currently, your hardware needs to support an inline crypto engine to use for file-based encryption. This is indicated by a fileencryption=ice directive for the userdata partition in fstab.hardware.

Additionally, the dm-default-key module must be present and enabled in the kernel.

Set up metadata filesystem

Because nothing in the userdata partition can be read until the metadata encryption key is present, the partition table must set aside a separate partition called the "metadata partition" for storing the keymaster blobs that protect this key. The metadata partition should be 16MB.

fstab.hardware must include an entry for the metadata filesystem that lives on that partition mounting it at /metadata, including the formattable flag to ensure it is formatted at boot time. The f2fs filesystem does not work on smaller partitions; we recommend using ext4 instead. For example:

/dev/block/bootdevice/by-name/metadata              /metadata          ext4        noatime,nosuid,nodev,discard                          wait,check,formattable

To ensure the /metadata mount point exists, add the following line to BoardConfig-common.mk:

BOARD_USES_METADATA_PARTITION := true

Changes to the init sequence

When metadata encryption is used, vold must be running before /data is mounted. To ensure that it is started early enough, add the following stanza to init.hardware.rc:

# We need vold early for metadata encryption
on early-fs
    start vold

Keymaster must be running and ready before init attempts to mount /data.

init.hardware.rc should already contain a mount_all instruction which mounts /data itself in the on late-fs stanza. Before this line, add the directive to exec the wait_for_keymaster service:

on late-fs
   … 
    # Wait for keymaster
    exec_start wait_for_keymaster

    # Mount RW partitions which need run fsck
    mount_all /vendor/etc/fstab.${ro.boot.hardware.platform} --late

Switching on metadata encryption

Finally add keydirectory=/metadata/vold/metadata_encryption to the fstab.hardware entry for userdata:

/dev/block/bootdevice/by-name/userdata              /data              f2fs        noatime,nosuid,nodev,discard latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable

Validation

While implementing metadata encryption, be mindful of these common issues and test your implementation.

Common issues

During the call to mount_all, which mounts the metadata-encrypted /data partition, init executes the vdc tool. The vdc tool connects to vold over binder to set up the metadata-encrypted device and mount the partition. For the duration of this call, init is blocked, and attempts to either read or set init properties will block until mount_all finishes. If, at this stage, any part of vold's work is directly or indirectly blocked on reading or setting a property, deadlock will result. It is important to ensure that vold can complete the work of reading the keys, interacting with Keymaster, and mounting the data directory without interacting further with init.

If Keymaster is not fully started when mount_all runs, it will not respond to vold until it has read certain properties from init, resulting in exactly the deadlock described. Placing exec_start wait_for_keymaster above the relevant mount_all invocation as set out ensures that Keymaster is fully running in advance and so avoids this deadlock.

Metadata encryption test

We're upstreaming these tests, but in the meantime, add a few lines to Android.bp and add check_encryption.cpp to platform/system/vold to test your implementation.

Changes to Android.bp

Changes to Android.bp called out below.

...
}

cc_binary {
    name: "vold",
    defaults: [
        "vold_default_flags",
        "vold_default_libs",
    ],

    srcs: ["main.cpp"],
    static_libs: ["libvold"],
    product_variables: {
        arc: {
            static_libs: [
                "arc_services_aidl",
                "libarcobbvolume",
            ],
        },
    },
    init_rc: [
        "vold.rc",
        "wait_for_keymaster.rc",
    ],

    required: [
        "check_encryption",
        "mke2fs",
        "vold_prepare_subdirs",
        "wait_for_keymaster",
    ],
}
...

}

cc_binary {
    name: "check_encryption",
    defaults: ["vold_default_flags"],
    srcs: [
        "FileDeviceUtils.cpp",
        "check_encryption.cpp",
    ],
    shared_libs: [
        "libbase",
    ],
}

Add check_encryption.cpp

Add check_encryption.cpp to platform/system/vold.

/*
 * Copyright (C) 2017 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 "FileDeviceUtils.h"

#include <cmath>
#include <string>

#include <assert.h>
#include <stdio.h>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
using android::base::unique_fd;
using android::base::ReadFileToString;
using android::base::WriteStringToFile;

namespace android {
namespace vold {
const size_t sectorsize = 1 << 12;
const int sectorcount = 1024;
static double randomness_score(const std::string& checkme) {
    unsigned int freq[256] = {0};
    unsigned int sum = 256;
    double loglaplace = 0;
    for (auto b : checkme) {
        loglaplace -= 8 + log2(static_cast<double>(++freq[static_cast<uint8_t>(b)]) / (sum++));
    }
    return loglaplace;
    LOG(INFO) << "Score: " << loglaplace;  // if negative, not random
    return loglaplace < 0;
}
static bool run_test(const std::string& device) {
    unique_fd device_fd(open(device.c_str(), O_RDONLY | O_CLOEXEC));
    if (device_fd.get() == -1) {
        PLOG(ERROR) << "Failed to open " << device;
        return false;
    }
    int randompassed = 0;
    auto buf = std::string(sectorsize, '\0');
    for (int i = 0; i < sectorcount; i++) {
        auto l = read(device_fd.get(), &buf[0], buf.size());
        if (l < 1) {
            PLOG(ERROR) << "Failed read on sector " << i;
            return false;
        }
        if (((size_t)l) != buf.size()) {
            LOG(ERROR) << "Short read on sector " << i;
            return false;
        }
        auto score = randomness_score(buf);
        if (score >= 0) {
            randompassed++;
            LOG(INFO) << "Passed randomness check on sector " << i << " with score " << score;
        } else {
            LOG(ERROR) << "Failed randomness check on sector " << i << " with score " << score;
        }
    }
    LOG(INFO) << "Passed randomness check on " << randompassed << "/" << sectorcount << " sectors";
    return randompassed == sectorcount;
}
}  // namespace vold
}  // namespace android
int main(int argc, const char* const argv[]) {
    setenv("ANDROID_LOG_TAGS", "*:v", 1);
    android::base::InitLogging(const_cast<char**>(argv), android::base::StderrLogger);
    if (argc != 2) {
        LOG(ERROR) << "Usage: " << argv[0] << " <device>";
        LOG(ERROR) << "example: " << argv[0] << " /dev/block/bootdevice/by-name/userdata";
        return -1;
    }
    android::vold::run_test(std::string(argv[1]));
    return 0;
}