Android FBE - novelinux/android GitHub Wiki

Android FBE

  • 名称: FBE, File-Based Encryption,基于文件的加密
  • Android官方文档: https://source.android.com/security/encryption/file-based.html
  • 支持版本: Android 7.0支持
  • 主要作用: Android 7.0及更高版本支持基于文件的加密(FBE)。基于文件的加密允许不同文件被不同keys加密并且解锁也是独立的.
  • 题外话: 工作于 Google 的开发者Ted(Theodore Ts’o / Ted Ts’o)2015年提供了一套用于 EXT4 文件系统加密的方案,这套方案 在Android N被引入。此外,这套方案仍在持续开发,以便成为上游内核的默认方案。这些补丁由Ted本人和 Michael Halcrow 共同完成。 对开发来说,在内核中引入加密支持,仅需要大概三千多行代码。类似的加密机制之后还可能在 F2FS 文件系统中实现——Flash-Friendly File-System,这是一种面向闪存介质的文件系统。

提到Android FBE就不得不提到Android Direct Boot,下面我们就从Direct Boot开始介绍FBE.

Direct Boot

当设备通电但用户尚未解锁设备时,Android 7.0以安全的直接引导模式(Direct boot)运行。为了支持这一点,系统提供了两个数据存储位置:

  • 设备加密存储: 这是在直接引导模式期间和用户解锁设备后可用的存储位置。(/data/user_de/)
  • 凭证加密存储: 它是默认存储位置,仅在用户解锁设备后可用。(/data/data --> /data/user/)

默认情况下,应用程序不会在直接引导模式下运行。如果您的应用程序需要在直接引导模式下采取行动,您可以注册应在此模式下运行的应用程序组件。在直接引导模式下需要运行的应用程序的一些常见用例包括:

  • 1.已安排通知的应用程式,例如闹钟应用程式;
  • 2.提供重要用户通知的应用,例如短信应用;
  • 3.提供无障碍服务的应用,如Talkback.

如果您的应用程序需要在直接引导模式下运行时访问数据,请使用设备加密存储。设备加密存储包含使用仅在设备执行成功验证的引导后可用的密钥加密的数据。 对于应使用与用户凭据关联的密钥(如PIN或密码)加密的数据,请使用凭据加密存储。凭证加密存储仅在用户成功解锁设备后才可用,直到用户再次重新启动设备为止。 如果用户在解锁设备后启用锁定屏幕,则不会锁定凭证加密存储。

Android FBE 启动流程

挂载(mount)

首先init进程启动过程通过fstab将数据分区挂载到/data

path: system/core/init/builtins.cpp

/* mount_all <fstab> [ <path> ]*
 *
 * This function might request a reboot, in which case it will
 * not return.
 */
static int do_mount_all(const std::vector<std::string>& args) {
      ...
    } else if (ret == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED) {
        if (e4crypt_install_keyring()) {
            return -1;
        }
        property_set("ro.crypto.state", "encrypted");
        property_set("ro.crypto.type", "file");

        // Although encrypted, we have device key, so we do not need to
        // do anything different from the nonencrypted case.
        ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
    } else if (ret > 0) {
        ERROR("fs_mgr_mount_all returned unexpected error %d\n", ret);
    }
    /* else ... < 0: error */

    return ret;
}

FBE Key Generate

/data/目录下的目录一共有三套密钥来设置对应的加密策略,分别是:

  • unencrypted key: 用来设置/data下除了如下目录的所有目录的policy:
        "lost+found",
        "system_ce", "system_de",
        "misc_ce", "misc_de",
        "media",
        "data", "user", "user_de",
  • de key: 通常用来设置/data下后缀含有_de的目录的policy.

  • ce key: 通常用来设置/data下后缀含有_ce的目录的policy.

Generate "unencrypted" Key

  • device_key_path - "/data/unencrypted/key"
sagit:/ # ls -l /data/unencrypted/key/
total 64
-rw------- 1 root root    92 1970-02-03 08:46 encrypted_key
-rw------- 1 root root   427 1970-02-03 08:46 keymaster_key_blob
-rw------- 1 root root 16384 1970-02-03 08:46 secdiscardable
-rw------- 1 root root    10 1970-02-03 08:46 stretching
-rw------- 1 root root     1 1970-02-03 08:46 version
  • device_key_ref - "/data/unencrypted/ref"
sagit:/ # ls -l /data/unencrypted/ref
-rw------- 1 root root 8 1970-02-03 08:46 /data/unencrypted/ref
  • LOGS
01-15 06:48:24.514   592   598 E keymaster1_device: HwKmClose
01-15 06:48:24.514   592   598 D vold    : Created key /data/unencrypted/key
01-15 06:48:24.514   592   598 D vold    : Added key 123488388 (ext4:0e30728e813bbb1b) to keyring 1063057322 in process 592
01-15 06:48:24.515     1     1 I vdc     : 200 593 1

do_installkey

path: system/core/init/builtins.cpp

static int do_installkey(const std::vector<std::string>& args) {
    if (!is_file_crypto()) {
        return 0;
    }
    return e4crypt_create_device_key(args[1].c_str(),
                                     do_installkeys_ensure_dir_exists);
}

e4crypt_create_device_key

path: system/extras/ext4_utils/ext4_crypt.h

static const char* e4crypt_unencrypted_folder = "/unencrypted";
static const char* e4crypt_key_ref = "/unencrypted/ref";

path: system/extras/ext4_utils/ext4_crypt_init_extensions.cpp

int e4crypt_create_device_key(const char* dir,
                              int ensure_dir_exists(const char*))
{
    init_logging();

    // Make sure folder exists. Use make_dir to set selinux permissions.
    std::string unencrypted_dir = std::string(dir) + e4crypt_unencrypted_folder;
    if (ensure_dir_exists(unencrypted_dir.c_str())) {
        KLOG_ERROR(TAG, "Failed to create %s (%s)\n",
                   unencrypted_dir.c_str(),
                   strerror(errno));
        return -1;
    }

    const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "enablefilecrypto" };
    int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
    LOG(INFO) << "enablefilecrypto result: " << rc;
    return rc;
}

cryptfs_enable_file

path: system/vold/cryptfs.c

int cryptfs_enable_file()
{
    return e4crypt_initialize_global_de();
}

e4crypt_initialize_global_de

path: system/vold/Ext4Crypt.cpp

const std::string device_key_dir = std::string() + DATA_MNT_POINT + e4crypt_unencrypted_folder;
const std::string device_key_path = device_key_dir + "/key";
const std::string device_key_temp = device_key_dir + "/temp";
...
bool e4crypt_initialize_global_de() {
    LOG(INFO) << "e4crypt_initialize_global_de";

    if (s_global_de_initialized) {
        LOG(INFO) << "Already initialized";
        return true;
    }

    std::string mode_filename = std::string("/data") + e4crypt_key_mode;
    std::string mode = cryptfs_get_file_encryption_mode();
    if (!android::base::WriteStringToFile(mode, mode_filename)) {
        PLOG(ERROR) << "Cannot save type";
        return false;
    }

    std::string device_key;
    if (path_exists(device_key_path)) {
        if (!android::vold::retrieveKey(device_key_path,
                kEmptyAuthentication, &device_key)) return false;
    } else {
        LOG(INFO) << "Creating new key";
        if (!random_key(&device_key)) return false;
        if (!store_key(device_key_path, device_key_temp,
                kEmptyAuthentication, device_key)) return false;
    }

    std::string device_key_ref;
    if (!install_key(device_key, &device_key_ref)) {
        LOG(ERROR) << "Failed to install device key";
        return false;
    }

    std::string ref_filename = std::string("/data") + e4crypt_key_ref;
    if (!android::base::WriteStringToFile(device_key_ref, ref_filename)) {
        PLOG(ERROR) << "Cannot save key reference";
        return false;
    }

    s_global_de_initialized = true;
    return true;
}

Generate DE vs CE directory Keys

  • /data/misc/vold/user_keys:
sagit:/data/misc/vold/user_keys # ls -l de/0/
total 104
-rw------- 1 root root    92 1970-02-02 03:34 encrypted_key
-rw------- 1 root root   427 1970-02-02 03:34 keymaster_key_blob
-rw------- 1 root root 16384 1970-02-02 03:34 secdiscardable
-rw------- 1 root root    10 1970-02-02 03:34 stretching
-rw------- 1 root root     1 1970-02-02 03:34 version
sagit:/data/misc/vold/user_keys # ls -l ce/0/current/
total 104
-rw------- 1 root root    92 1970-02-02 03:34 encrypted_key
-rw------- 1 root root   427 1970-02-02 03:34 keymaster_key_blob
-rw------- 1 root root 16384 1970-02-02 03:34 secdiscardable
-rw------- 1 root root    10 1970-02-02 03:34 stretching
-rw------- 1 root root     1 1970-02-02 03:34 version
  • LOGS
01-15 06:48:24.543   592   598 D vold    : e4crypt_init_user0
01-15 06:48:24.543   592   598 D vold    : Preparing: /data/misc/vold/user_keys
01-15 06:48:24.543   592   598 D vold    : Preparing: /data/misc/vold/user_keys/ce
01-15 06:48:24.544   592   598 D vold    : Preparing: /data/misc/vold/user_keys/de
01-15 06:48:24.544   592   598 D vold    : Preparing: /data/misc/vold/user_keys/ce/0
01-15 06:48:24.544   592   598 D vold    : Skipping non-key .
01-15 06:48:24.544   592   598 D vold    : Skipping non-key ..
01-15 06:48:24.553   592   598 E keymaster1_device: Keymaster Initialized
01-15 06:48:24.553   592   598 E keymaster1_device: TA API Major Verion: 2
01-15 06:48:24.553   592   598 E keymaster1_device: TA API Minor Verion: 0
01-15 06:48:24.553   592   598 E keymaster1_device: TA Major Verion: 2
01-15 06:48:24.553   592   598 E keymaster1_device: TA Minor Verion: 25
01-15 06:48:24.553   592   598 E keymaster1_device: set_version_req->flags: 1
01-15 06:48:24.554   592   598 D vold    : Creating key that doesn't need auth token
01-15 06:48:24.588   592   598 D vold    : Created key /data/misc/vold/user_keys/de/0
01-15 06:48:24.588   592   598 D vold    : Added key 1043496796 (ext4:5b317534021c2524) to keyring 1063057322 in process 592
01-15 06:48:24.588   592   598 D vold    : Added key 884894402 (ext4:5ebd155fc20b3ffa) to keyring 1063057322 in process 592
01-15 06:48:24.588   592   598 D vold    : Created keys for user 0
01-15 06:48:24.588   592   598 D vold    : Skipping non-de-key .
01-15 06:48:24.588   592   598 D vold    : Skipping non-de-key ..

do_init_user0

path: system/core/init/builtins.cpp

static int do_init_user0(const std::vector<std::string>& args) {
    return e4crypt_do_init_user0();
}

e4crypt_do_init_user0

path: system/extras/ext4_utils/ext4_crypt_init_extensions.cpp

int e4crypt_do_init_user0()
{
    init_logging();

    const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "init_user0" };
    int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
    LOG(INFO) << "init_user0 result: " << rc;
    return rc;
}

e4crypt_init_user0

path: system/vold/Ext4Crypt.cpp

bool e4crypt_init_user0() {
    LOG(DEBUG) << "e4crypt_init_user0";
    if (e4crypt_is_native()) {
        if (!prepare_dir(user_key_dir, 0700, AID_ROOT, AID_ROOT)) return false;
        if (!prepare_dir(user_key_dir + "/ce", 0700, AID_ROOT, AID_ROOT)) return false;
        if (!prepare_dir(user_key_dir + "/de", 0700, AID_ROOT, AID_ROOT)) return false;
        if (!path_exists(get_de_key_path(0))) {
            if (!create_and_install_user_keys(0, false)) return false;
        }
        // TODO: switch to loading only DE_0 here once framework makes
        // explicit calls to install DE keys for secondary users
        if (!load_all_de_keys()) return false;
    }

    // We can only safely prepare DE storage here, since CE keys are probably
    // entangled with user credentials.  The framework will always prepare CE
    // storage once CE keys are installed.
    if (!e4crypt_prepare_user_storage(nullptr, 0, 0, FLAG_STORAGE_DE)) {
        LOG(ERROR) << "Failed to prepare user 0 storage";
        return false;
    }

    // If this is a non-FBE device that recently left an emulated mode,
    // restore user data directories to known-good state.
    if (!e4crypt_is_native() && !e4crypt_is_emulated()) {
        e4crypt_unlock_user_key(0, 0, "!", "!");
    }

    return true;
}

create_and_install_user_keys

static bool create_and_install_user_keys(userid_t user_id, bool create_ephemeral) {
    std::string de_key, ce_key;
    if (!random_key(&de_key)) return false;
    if (!random_key(&ce_key)) return false;
    if (create_ephemeral) {
        // If the key should be created as ephemeral, don't store it.
        s_ephemeral_users.insert(user_id);
    } else {
        auto const directory_path = get_ce_key_directory_path(user_id);
        if (!prepare_dir(directory_path, 0700, AID_ROOT, AID_ROOT)) return false;
        auto const paths = get_ce_key_paths(directory_path);
        std::string ce_key_path;
        if (!get_ce_key_new_path(directory_path, paths, &ce_key_path)) return false;
        if (!store_key(ce_key_path, user_key_temp,
                kEmptyAuthentication, ce_key)) return false;
        fixate_user_ce_key(directory_path, ce_key_path, paths);
        // Write DE key second; once this is written, all is good.
        if (!store_key(get_de_key_path(user_id), user_key_temp,
                kEmptyAuthentication, de_key)) return false;
    }
    std::string de_raw_ref;
    if (!install_key(de_key, &de_raw_ref)) return false;
    s_de_key_raw_refs[user_id] = de_raw_ref;
    std::string ce_raw_ref;
    if (!install_key(ce_key, &ce_raw_ref)) return false;
    s_ce_keys[user_id] = ce_key;
    s_ce_key_raw_refs[user_id] = ce_raw_ref;
    LOG(DEBUG) << "Created keys for user " << user_id;
    return true;
}

load_all_de_keys

static bool load_all_de_keys() {
    auto de_dir = user_key_dir + "/de";
    auto dirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(de_dir.c_str()), closedir);
    if (!dirp) {
        PLOG(ERROR) << "Unable to read de key directory";
        return false;
    }的
    for (;;) {
        errno = 0;
        auto entry = readdir(dirp.get());
        if (!entry) {
            if (errno) {
                PLOG(ERROR) << "Unable to read de key directory";
                return false;
            }
            break;
        }
        if (entry->d_type != DT_DIR || !is_numeric(entry->d_name)) {
            LOG(DEBUG) << "Skipping non-de-key " << entry->d_name;
            continue;
        }
        userid_t user_id = atoi(entry->d_name);
        if (s_de_key_raw_refs.count(user_id) == 0) {
            auto key_path = de_dir + "/" + entry->d_name;
            std::string key;
            if (!android::vold::retrieveKey(key_path, kEmptyAuthentication, &key)) return false;
            std::string raw_ref;
            if (!install_key(key, &raw_ref)) return false;
            s_de_key_raw_refs[user_id] = raw_ref;
            LOG(DEBUG) << "Installed de key for user " << user_id;
        }
    }
    // ext4enc:TODO: go through all DE directories, ensure that all user dirs have the
    // correct policy set on them, and that no rogue ones exist.
    return true;
}

random_key

random_key是随机生成加密密钥的函数.

path: system/vold/Ext4Crypt.cpp

static bool random_key(std::string* key) {
    if (android::vold::ReadRandomBytes(EXT4_AES_256_XTS_KEY_SIZE, *key) != 0) {
        // TODO status_t plays badly with PLOG, fix it.
        LOG(ERROR) << "Random read failed";
        return false;
    }
    return true;
}

ReadRandomBytes

path: system/vold/Utils.cpp

status_t ReadRandomBytes(size_t bytes, std::string& out) {
    out.clear();

    int fd = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
    if (fd == -1) {
        return -errno;
    }

    char buf[BUFSIZ];
    size_t n;
    while ((n = TEMP_FAILURE_RETRY(read(fd, &buf[0], std::min(sizeof(buf), bytes)))) > 0) {
        out.append(buf, n);
        bytes -= n;
    }
    close(fd);

    if (bytes == 0) {
        return OK;
    } else {
        return -EIO;
    }
}

install_key

install_key函数主要功能是将生成的key通知到内核并将密钥hash返回给调用函数.

path: system/vold/Ext4Crypt.cpp

// Install password into global keyring
// Return raw key reference for use in policy
static bool install_key(const std::string& key, std::string* raw_ref) {
    ext4_encryption_key ext4_key;
    if (!fill_key(key, &ext4_key)) return false;
    *raw_ref = generate_key_ref(ext4_key.raw, ext4_key.size);
    auto ref = keyname(*raw_ref);
    key_serial_t device_keyring;
    if (!e4crypt_keyring(&device_keyring)) return false;
    key_serial_t key_id =
        add_key("logon", ref.c_str(), (void*)&ext4_key, sizeof(ext4_key), device_keyring);
    if (key_id == -1) {
        PLOG(ERROR) << "Failed to insert key into keyring " << device_keyring;
        return false;
    }
    LOG(DEBUG) << "Added key " << key_id << " (" << ref << ") to keyring " << device_keyring
               << " in process " << getpid();
    return true;
}

fill_key

path: system/vold/Ext4Crypt.cpp

static bool fill_key(const std::string& key, ext4_encryption_key* ext4_key) {
    if (key.size() != EXT4_AES_256_XTS_KEY_SIZE) {
        LOG(ERROR) << "Wrong size key " << key.size();
        return false;
    }
    static_assert(EXT4_AES_256_XTS_KEY_SIZE <= sizeof(ext4_key->raw), "Key too long!");
    ext4_key->mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
    ext4_key->size = key.size();
    memset(ext4_key->raw, 0, sizeof(ext4_key->raw));
    memcpy(ext4_key->raw, key.data(), key.size());
    return true;
}

generate_key_ref

path: system/vold/Ext4Crypt.cpp

// Get raw keyref - used to make keyname and to pass to ioctl
static std::string generate_key_ref(const char* key, int length) {
    SHA512_CTX c;

    SHA512_Init(&c);
    SHA512_Update(&c, key, length);
    unsigned char key_ref1[SHA512_DIGEST_LENGTH];
    SHA512_Final(key_ref1, &c);

    SHA512_Init(&c);
    SHA512_Update(&c, key_ref1, SHA512_DIGEST_LENGTH);
    unsigned char key_ref2[SHA512_DIGEST_LENGTH];
    SHA512_Final(key_ref2, &c);

    static_assert(EXT4_KEY_DESCRIPTOR_SIZE <= SHA512_DIGEST_LENGTH,
                  "Hash too short for descriptor");
    return std::string((char*)key_ref2, EXT4_KEY_DESCRIPTOR_SIZE);
}

keyname

path: system/vold/Ext4Crypt.cpp

static std::string keyname(const std::string& raw_ref) {
    std::ostringstream o;
    o << "ext4:";
    for (auto i : raw_ref) {
        o << std::hex << std::setw(2) << std::setfill('0') << (int)i;
    }
    return o.str();
}

e4crypt_keyring

path: system/vold/Ext4Crypt.cpp

// Get the keyring we store all keys in
static bool e4crypt_keyring(key_serial_t* device_keyring) {
    *device_keyring = keyctl_search(KEY_SPEC_SESSION_KEYRING, "keyring", "e4crypt", 0);
    if (*device_keyring == -1) {
        PLOG(ERROR) << "Unable to find device keyring";
        return false;
    }
    return true;
}

system calls

  • add_key

path: system/extras/ext4_utils/key_control.cpp

key_serial_t add_key(const char *type,
                     const char *description,
                     const void *payload,
                     size_t plen,
                     key_serial_t ringid)
{
    return syscall(__NR_add_key, type, description, payload, plen, ringid);
}
  • keyctl_search

path: system/extras/ext4_utils/key_control.cpp

long keyctl_search(key_serial_t ringid, const char *type,
                   const char *description, key_serial_t destringid)
{
    return keyctl(KEYCTL_SEARCH, ringid, type, description, destringid);
}
  • Documentation

path: kernel/Documentation/security/keys.txt

store_key

store_key函数主要目的是将密钥加密保存到制定位置.

path: system/vold/Ext4Crypt.cpp

// NB this assumes that there is only one thread listening for crypt commands, because
// it creates keys in a fixed location.
static bool store_key(const std::string& key_path, const std::string& tmp_path,
                      const android::vold::KeyAuthentication& auth, const std::string& key) {
    if (path_exists(key_path)) {
        LOG(ERROR) << "Already exists, cannot create key at: " << key_path;
        return false;
    }
    if (path_exists(tmp_path)) {
        android::vold::destroyKey(tmp_path);  // May be partially created so ignore errors
    }
    if (!android::vold::storeKey(tmp_path, auth, key)) return false;
    if (rename(tmp_path.c_str(), key_path.c_str()) != 0) {
        PLOG(ERROR) << "Unable to move new key to location: " << key_path;
        return false;
    }
    LOG(DEBUG) << "Created key " << key_path;
    return true;
}

android::vold::storeKey

path: system/vold/KeyStorage.cpp

bool storeKey(const std::string& dir, const KeyAuthentication& auth, const std::string& key) {
    if (TEMP_FAILURE_RETRY(mkdir(dir.c_str(), 0700)) == -1) {
        PLOG(ERROR) << "key mkdir " << dir;
        return false;
    }
    if (!writeStringToFile(kCurrentVersion, dir + "/" + kFn_version)) return false;
    std::string secdiscardable;
    if (ReadRandomBytes(SECDISCARDABLE_BYTES, secdiscardable) != OK) {
        // TODO status_t plays badly with PLOG, fix it.
        LOG(ERROR) << "Random read failed";
        return false;
    }
    if (!writeStringToFile(secdiscardable, dir + "/" + kFn_secdiscardable)) return false;
    std::string stretching = auth.secret.empty() ? kStretch_nopassword : getStretching();
    if (!writeStringToFile(stretching, dir + "/" + kFn_stretching)) return false;
    std::string salt;
    if (stretchingNeedsSalt(stretching)) {
        if (ReadRandomBytes(SALT_BYTES, salt) != OK) {
            LOG(ERROR) << "Random read failed";
            return false;
        }
        if (!writeStringToFile(salt, dir + "/" + kFn_salt)) return false;
    }
    std::string appId;
    if (!generateAppId(auth, stretching, salt, secdiscardable, &appId)) return false;
    Keymaster keymaster;
    if (!keymaster) return false;
    std::string kmKey;
    if (!generateKeymasterKey(keymaster, auth, appId, &kmKey)) return false;
    if (!writeStringToFile(kmKey, dir + "/" + kFn_keymaster_key_blob)) return false;
    std::string encryptedKey;
    if (!encryptWithKeymasterKey(keymaster, kmKey, auth, appId, key, &encryptedKey)) return false;
    if (!writeStringToFile(encryptedKey, dir + "/" + kFn_encrypted_key)) return false;
    return true;
}

android::vold::retrieveKey

retrieveKey函数的主要目的是解密storeKey函数加密保存的key.

path: system/vold/KeyStorage.cpp

static const char* kFn_encrypted_key = "encrypted_key";
static const char* kFn_keymaster_key_blob = "keymaster_key_blob";
static const char* kFn_salt = "salt";
static const char* kFn_secdiscardable = "secdiscardable";
static const char* kFn_stretching = "stretching";
static const char* kFn_version = "version";

...

bool retrieveKey(const std::string& dir, const KeyAuthentication& auth, std::string* key) {
    std::string version;
    if (!readFileToString(dir + "/" + kFn_version, &version)) return false;
    if (version != kCurrentVersion) {
        LOG(ERROR) << "Version mismatch, expected " << kCurrentVersion << " got " << version;
        return false;
    }
    std::string secdiscardable;
    if (!readFileToString(dir + "/" + kFn_secdiscardable, &secdiscardable)) return false;
    std::string stretching;
    if (!readFileToString(dir + "/" + kFn_stretching, &stretching)) return false;
    std::string salt;
    if (stretchingNeedsSalt(stretching)) {
        if (!readFileToString(dir + "/" + kFn_salt, &salt)) return false;
    }
    std::string appId;
    if (!generateAppId(auth, stretching, salt, secdiscardable, &appId)) return false;
    std::string kmKey;
    if (!readFileToString(dir + "/" + kFn_keymaster_key_blob, &kmKey)) return false;
    std::string encryptedMessage;
    if (!readFileToString(dir + "/" + kFn_encrypted_key, &encryptedMessage)) return false;
    Keymaster keymaster;
    if (!keymaster) return false;
    return decryptWithKeymasterKey(keymaster, kmKey, auth, appId, encryptedMessage, key);
}

创建使用unencrypt key加密的目录

  • policy - "/data/unencrypted/ref"

  • LOGS

[    5.612948] ext4_utils: Setting 3fcbc19c policy on /data/bootchart!
[    5.616033] ext4_utils: Setting 3fcbc19c policy on /data/misc!
[    5.623805] ext4_utils: Setting 3fcbc19c policy on /data/local!
[    5.629432] ext4_utils: Setting 3fcbc19c policy on /data/app-private!
[    5.630260] ext4_utils: Setting 3fcbc19c policy on /data/app-ephemeral!
[    5.631094] ext4_utils: Setting 3fcbc19c policy on /data/app-asec!
[    5.631901] ext4_utils: Setting 3fcbc19c policy on /data/app-lib!
[    5.633430] ext4_utils: Setting 3fcbc19c policy on /data/app!

mkdir

  • policy - "/data/unencrypted/ref"

do_mkdir

path: system/core/init/builtins.cpp

static int do_mkdir(const std::vector<std::string>& args) {
    mode_t mode = 0755;
    int ret;

    /* mkdir <path> [mode] [owner] policy - "/data/unencrypted/ref"[group] */

    if (args.size() >= 3) {
        mode = std::stoul(args[2], 0, 8);
    }

    ret = make_dir(args[1].c_str(), mode);
    /* chmod in case the directory already exists */
    if (ret == -1 && errno == EEXIST) {
        ret = fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW);
    }
    if (ret == -1) {
        return -errno;
    }

    if (args.size() >= 4) {
        uid_t uid = decode_uid(args[3].c_str());
        gid_t gid = -1;

        if (args.size() == 5) {
            gid = decode_uid(args[4].c_str());
        }

        if (lchown(args[1].c_str(), uid, gid) == -1) {
            return -errno;
        }

        /* chown may have cleared S_ISUID and S_ISGID, chmod again */
        if (mode & (S_ISUID | S_ISGID)) {
            ret = fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW);
            if (ret == -1) {
                return -errno;
            }
        }
    }

    if (e4crypt_is_native()) {
        if (e4crypt_set_directory_policy(args[1].c_str())) {
            wipe_data_via_recovery(std::string() + "set_policy_failed:" + args[1]);
            return -1;
        }
    }
    return 0;
}

e4crypt_is_native

path: path: system/extras/ext4_utils/ext4_crypt.cpp

bool e4crypt_is_native() {
    char value[PROPERTY_VALUE_MAX];
    property_get("ro.crypto.type", value, "none");
    return !strcmp(value, "file");
}

policy - "/data/unencrypted/ref"

e4crypt_set_directory_policy

path: system/extras/ext4_utils/ext4_crypt_init_extensions.cpp

int e4crypt_set_directory_policy(const char* dir)
{
    init_logging();

    // Only set policy on first level /data directories
    // To make this less restrictive, consider using a policy file.
    // However this is overkill for as long as the policy is simply
    // to apply a global policy to all /data folders created via makedir
    if (!dir || strncmp(dir, "/data/", 6) || strchr(dir + 6, '/')) {
        return 0;
    }

    // Special case various directories that must not be encrypted,
    // often because their subdirectories must be encrypted.
    // This isn't a nice way to do this, see b/26641735
    std::vector<std::string> directories_to_exclude = {
        "lost+found",
        "system_ce", "system_de",
        "misc_ce", "misc_de",
        "media",
        "data", "user", "user_de",
    };
    std::string prefix = "/data/";
    for (auto d: directories_to_exclude) {
        if ((prefix + d) == dir) {
            KLOG_INFO(TAG, "Not setting policy on %s\n", dir);
            return 0;
        }
    }

    std::string ref_filename = std::string("/data") + e4crypt_key_ref;
    std::string policy;
    if (!android::base::ReadFileToString(ref_filename, &policy)) {
        KLOG_ERROR(TAG, "Unable to read system policy to set on %s\n", dir);
        return -1;
    }
    KLOG_INFO(TAG, "Setting policy on %s\n", dir);
    int result = e4crypt_policy_ensure(dir, policy.c_str(), policy.size());
    if (result) {
        KLOG_ERROR(TAG, "Setting %02x%02x%02x%02x policy on %s failed!\n",
                   policy[0], policy[1], policy[2], policy[3], dir);
        return -1;
    }

    return 0;
}

创建使用 de key加密的目录

  • LOGS
01-15 06:48:24.588   592   598 D vold    : e4crypt_prepare_user_storage for volume null, user 0, serial 0, flags 1
01-15 06:48:24.588   592   598 D vold    : Preparing: /data/system/users/0
01-15 06:48:24.588   592   598 D vold    : Preparing: /data/misc/profiles/cur/0
01-15 06:48:24.588   592   598 D vold    : Preparing: /data/misc/profiles/cur/0/foreign-dex
01-15 06:48:24.589   592   598 D vold    : Preparing: /data/system_de/0
01-15 06:48:24.589   592   598 D vold    : Preparing: /data/misc_de/0
01-15 06:48:24.589   592   598 D vold    : Preparing: /data/user_de/0
01-15 06:48:24.589   592   598 I vold    : Policy for /data/system_de/0 set to 5b317534021c2524
01-15 06:48:24.589   592   598 I vold    : Policy for /data/misc_de/0 set to 5b317534021c2524
01-15 06:48:24.589   592   598 I vold    : Policy for /data/user_de/0 set to 5b317534021c2524
  • flowchart
e4crypt_init_user0()
 |
 +-> create_and_install_user_keys (/data/misc/vold/user_keys)
 |
 +-> load_all_de_keys
 |
 +-> e4crypt_prepare_user_storage
 |   |
 |   +-> ensure_policy
 |       |
 |       +-> e4crypt_policy_ensure -> e4crypt_policy_set -> ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep);
 |
 +- non-FBE -> e4crypt_unlock_user_key(0, 0, "!", "!")

init_user0

path: system/core/init/builtins.cpp

static int do_init_user0(const std::vector<std::string>& args) {
    return e4crypt_do_init_user0();
}

1.e4crypt_do_init_user0

path: system/extras/ext4_utils/ext4_crypt_init_extensions.cpp

int e4crypt_do_init_user0()
{
    init_logging();

    const char* argv[] = { "/system/bin/vdc", "--wait", "cryptfs", "init_user0" };
    int rc = android_fork_execvp(4, (char**) argv, NULL, false, true);
    LOG(INFO) << "init_user0 result: " << rc;
    return rc;
}

2.e4crypt_init_user0

path: system/vold/Ext4Crypt.cpp

bool e4crypt_init_user0() {
    LOG(DEBUG) << "e4crypt_init_user0";
    if (e4crypt_is_native()) {
        if (!prepare_dir(user_key_dir, 0700, AID_ROOT, AID_ROOT)) return false;
        if (!prepare_dir(user_key_dir + "/ce", 0700, AID_ROOT, AID_ROOT)) return false;
        if (!prepare_dir(user_key_dir + "/de", 0700, AID_ROOT, AID_ROOT)) return false;
        if (!path_exists(get_de_key_path(0))) {
            if (!create_and_install_user_keys(0, false)) return false;
        }
        // TODO: switch to loading only DE_0 here once framework makes
        // explicit calls to install DE keys for secondary users
        if (!load_all_de_keys()) return false;
    }
    // We can only safely prepare DE storage here, since CE keys are probably
    // entangled with user credentials.  The framework will always prepare CE
    // storage once CE keys are installed.
    if (!e4crypt_prepare_user_storage(nullptr, 0, 0, FLAG_STORAGE_DE)) {
        LOG(ERROR) << "Failed to prepare user 0 storage";
        return false;
    }

    // If this is a non-FBE device that recently left an emulated mode,
    // restore user data directories to known-good state.
    if (!e4crypt_is_native() && !e4crypt_is_emulated()) {
        e4crypt_unlock_user_key(0, 0, "!", "!");
    }

    return true;
}

3.e4crypt_prepare_user_storage

path: system/vold/Ext4Crypt.cpp

bool e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id, int serial,
        int flags) {
    LOG(DEBUG) << "e4crypt_prepare_user_storage for volume " << escape_null(volume_uuid)
               << ", user " << user_id << ", serial " << serial << ", flags " << flags;

    if (flags & FLAG_STORAGE_DE) {
        // DE_sys key
        auto system_legacy_path = android::vold::BuildDataSystemLegacyPath(user_id);
        auto misc_legacy_path = android::vold::BuildDataMiscLegacyPath(user_id);
        auto profiles_de_path = android::vold::BuildDataProfilesDePath(user_id);
        auto foreign_de_path = android::vold::BuildDataProfilesForeignDexDePath(user_id);

        // DE_n key
        auto system_de_path = android::vold::BuildDataSystemDePath(user_id);
        auto misc_de_path = android::vold::BuildDataMiscDePath(user_id);
        auto user_de_path = android::vold::BuildDataUserDePath(volume_uuid, user_id);

        if (!prepare_dir(system_legacy_path, 0700, AID_SYSTEM, AID_SYSTEM)) return false;
#if MANAGE_MISC_DIRS
        if (!prepare_dir(misc_legacy_path, 0750, multiuser_get_uid(user_id, AID_SYSTEM),
                multiuser_get_uid(user_id, AID_EVERYBODY))) return false;
#endif
        if (!prepare_dir(profiles_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;
        if (!prepare_dir(foreign_de_path, 0773, AID_SYSTEM, AID_SYSTEM)) return false;

        if (!prepare_dir(system_de_path, 0770, AID_SYSTEM, AID_SYSTEM)) return false;
        if (!prepare_dir(misc_de_path, 01771, AID_SYSTEM, AID_MISC)) return false;
        if (!prepare_dir(user_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;

        // For now, FBE is only supported on internal storage
        if (e4crypt_is_native() && volume_uuid == nullptr) {
            std::string de_raw_ref;
            if (!lookup_key_ref(s_de_key_raw_refs, user_id, &de_raw_ref)) return false;
            if (!ensure_policy(de_raw_ref, system_de_path)) return false;
            if (!ensure_policy(de_raw_ref, misc_de_path)) return false;
            if (!ensure_policy(de_raw_ref, user_de_path)) return false;
        }
    }

    if (flags & FLAG_STORAGE_CE) {
        // CE_n key
        auto system_ce_path = android::vold::BuildDataSystemCePath(user_id);
        auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id);
        auto media_ce_path = android::vold::BuildDataMediaCePath(volume_uuid, user_id);
        auto user_ce_path = android::vold::BuildDataUserCePath(volume_uuid, user_id);

        if (!prepare_dir(system_ce_path, 0770, AID_SYSTEM, AID_SYSTEM)) return false;
        if (!prepare_dir(misc_ce_path, 01771, AID_SYSTEM, AID_MISC)) return false;
        if (!prepare_dir(media_ce_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW)) return false;
        if (!prepare_dir(user_ce_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;

        // For now, FBE is only supported on internal storage
        if (e4crypt_is_native() && volume_uuid == nullptr) {
            std::string ce_raw_ref;
            if (!lookup_key_ref(s_ce_key_raw_refs, user_id, &ce_raw_ref)) return false;
            if (!ensure_policy(ce_raw_ref, system_ce_path)) return false;
            if (!ensure_policy(ce_raw_ref, misc_ce_path)) return false;
            if (!ensure_policy(ce_raw_ref, media_ce_path)) return false;
            if (!ensure_policy(ce_raw_ref, user_ce_path)) return false;

            // Now that credentials have been installed, we can run restorecon
            // over these paths
            // NOTE: these paths need to be kept in sync with libselinux
            android::vold::RestoreconRecursive(system_ce_path);
            android::vold::RestoreconRecursive(misc_ce_path);
        }
    }

    return true;
}

lookup_key_ref

path: system/vold/Ext4Crypt.cpp

static bool lookup_key_ref(const std::map<userid_t, std::string>& key_map, userid_t user_id,
                           std::string* raw_ref) {
    auto refi = key_map.find(user_id);
    if (refi == key_map.end()) {
        LOG(ERROR) << "Cannot find key for " << user_id;
        return false;
    }
    *raw_ref = refi->second;
    return true;
}

ensure_policy

path: system/vold/Ext4Crypt.cpp

static bool ensure_policy(const std::string& raw_ref, const std::string& path) {
    if (e4crypt_policy_ensure(path.c_str(),
                              raw_ref.data(), raw_ref.size(),
                              cryptfs_get_file_encryption_mode()) != 0) {
        LOG(ERROR) << "Failed to set policy on: " << path;
        return false;
    }
    return true;
}

reconcileAppsData

  • LOG
01-15 06:48:45.367  1639  1639 I SystemServer: StartPackageManagerService
...
01-15 06:48:58.591  1639  2809 I PackageManager: /system/priv-app/CNEService changed; collecting certs
...
01-15 06:49:05.285  1639  2812 I PackageManager: /data/app/partner-BaiduMap changed; collecting certs

01-15 06:49:08.026  1639  1639 V PackageManager: reconcileAppsData for null u0 0x1

01-15 06:49:08.027   746   746 I SELinux : SELinux: Loaded file_contexts contexts from /file_contexts.bin.
01-15 06:49:08.028   746   746 D installd: Detected label change from u:object_r:system_data_file:s0 to u:object_r:app_data_file:s0:c512,c768 at /data/user_de/0/com.android.cts.priv.ctsshim; running recursive restorecon
...
01-15 06:49:08.213   746   746 D installd: Detected label change from u:object_r:system_data_file:s0 to u:object_r:radio_data_file:s0 at /data/user_de/0/com.qti.editnumber; running recursive restorecon
01-15 06:49:08.214  1639  1639 V PackageManager: reconcileAppsData finished 214 packages
...

  • Flowchart
PackageManagerService
 |
 +-> reconcileAppsDataLI +-> prepareAppDataLeafLIF -> mInstaller.createAppData -> create_app_data(installd)
     |                   |
     +-> prepareAppDataLIF  <-+
     |                        |
     +-> maybeMigrateAppDataLIF

创建使用ce key加密的目录

  • LOG
01-15 06:50:41.581  1639  1755 I ActivityManager: Start proc 5566:com.android.defcontainer/u0a9 for on-hold
01-15 06:50:41.582  1639  1755 D ActivityManager: Finishing user boot 0
...
01-15 06:50:41.585  1639  1755 D CryptdConnector: SND -> {2 cryptfs unlock_user_key 0 0 [scrubbed] [scrubbed]}
01-15 06:50:41.586   592   598 D vold    : e4crypt_unlock_user_key 0 serial=0 token_present=0
01-15 06:50:41.586   592   598 W vold    : Tried to unlock already-unlocked key for user 0
...
01-15 06:50:41.586  1639  3249 D CryptdConnector: RCV <- {200 2 Command succeeded}
01-15 06:50:41.587  1639  1755 D CryptdConnector: SND -> {3 cryptfs prepare_user_storage ! 0 0 2}
01-15 06:50:41.587   592   598 D vold    : e4crypt_prepare_user_storage for volume null, user 0, serial 0, flags 2
01-15 06:50:41.587   592   598 D vold    : Preparing: /data/system_ce/0
01-15 06:50:41.587   592   598 D vold    : Preparing: /data/misc_ce/0
01-15 06:50:41.587   592   598 D vold    : Preparing: /data/media/0
01-15 06:50:41.588   592   598 D vold    : Preparing: /data/data
01-15 06:50:41.630   592   598 I vold    : Policy for /data/system_ce/0 set to 5ebd155fc20b3ffa
01-15 06:50:41.630   592   598 I vold    : Policy for /data/misc_ce/0 set to 5ebd155fc20b3ffa
01-15 06:50:41.631   592   598 I vold    : Policy for /data/media/0 set to 5ebd155fc20b3ffa
01-15 06:50:41.631   592   598 I vold    : Policy for /data/data set to 5ebd155fc20b3ffa
01-15 06:50:41.631   592   598 V vold    : Starting restorecon of /data/system_ce/0
01-15 06:50:41.635   592   598 V vold    : Finished restorecon of /data/system_ce/0
01-15 06:50:41.635   592   598 V vold    : Starting restorecon of /data/misc_ce/0
01-15 06:50:41.636   592   598 V vold    : Finished restorecon of /data/misc_ce/0
01-15 06:50:41.637  1639  3249 D CryptdConnector: RCV <- {200 5 Command succeeded}
  • Flowchart
ActivityManagerService.systemReady
 |
UserController.startUser
 |
UserController.finishUserBoot
 |
UserController.maybeUnlockUser
 |
UserController.unlockUserCleared
 |
 +-> MountService.unlockUserKey
 |   |
 |   +-> e4crypt_unlock_user_key
 |
UserController.finishUserUnlocking
 |
UserManagerService.onBeforeUnlockUser
 |
PackageManagerService.prepareUserData
 |
PackageManagerService.prepareUserDataLI
 |
MountService.prepareUserStorage
 |
 +-> e4crypt_prepare_user_storage

reconcileAppsData

01-15 06:50:41.639  1639  1755 V PackageManager: reconcileAppsData for null u0 0x2
...
01-15 06:50:42.037   746   746 D installd: Detected label change from u:object_r:system_data_file:s0 to u:object_r:radio_data_file:s0 at /data/data/com.qti.editnumber; running recursive restorecon
01-15 06:50:42.038  1639  1755 V PackageManager: reconcileAppsData finished 214 packages

e4crypt_policy_ensure

不论使用什么key进行加密最终的policy(密钥hash)设置都是通过函数e4crypt_policy_ensure来实现的,其主要作用是将对应的policy 添加到对应的目录的xattr属性中保存起来, 其具体实现如下所示:

path: system/extras/ext4_utils/ext4_crypt.cpp

int e4crypt_policy_ensure(const char *directory, const char *policy, size_t policy_length) {
    bool is_empty;
    if (!is_dir_empty(directory, &is_empty)) return -1;
    if (is_empty) {
        if (!e4crypt_policy_set(directory, policy, policy_length)) return -1;
    } else {
        if (!e4crypt_policy_check(directory, policy, policy_length)) return -1;
    }
    return 0;
}

struct ext4_encryption_policy

path: system/extras/ext4_utils/ext4_crypt.cpp

struct ext4_encryption_policy {
    char version;
    char contents_encryption_mode;
    char filenames_encryption_mode;
    char flags;
    char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];
} __attribute__((__packed__));

e4crypt_policy_set

path: system/extras/ext4_utils/ext4_crypt.cpp

static bool e4crypt_policy_set(const char *directory, const char *policy, size_t policy_length) {
    if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
        LOG(ERROR) << "Policy wrong length: " << policy_length;
        return false;
    }
    int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
    if (fd == -1) {
        PLOG(ERROR) << "Failed to open directory " << directory;
        return false;
    }

    ext4_encryption_policy eep;
    eep.version = 0;
    eep.contents_encryption_mode = EXT4_ENCRYPTION_MODE_PRIVATE;
    eep.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
    eep.flags = 0;
    memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
    if (ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep)) {
        PLOG(ERROR) << "Failed to set encryption policy for " << directory;
        close(fd);
        return false;
    }
    close(fd);

    char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
    policy_to_hex(policy, policy_hex);
    LOG(INFO) << "Policy for " << directory << " set to " << policy_hex;
    return true;
}

ioctl

path: fs/ext4/ioctl.c

long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct inode *inode = file_inode(filp);
	struct super_block *sb = inode->i_sb;
	struct ext4_inode_info *ei = EXT4_I(inode);
	unsigned int flags;

	ext4_debug("cmd = %u, arg = %lu\n", cmd, arg);

	switch (cmd) {
        ...
        case EXT4_IOC_SET_ENCRYPTION_POLICY: {
#ifdef CONFIG_EXT4_FS_ENCRYPTION
		struct ext4_encryption_policy policy;
		int err = 0;

		if (copy_from_user(&policy,
				   (struct ext4_encryption_policy __user *)arg,
				   sizeof(policy))) {
			err = -EFAULT;
			goto encryption_policy_out;
		}

		err = ext4_process_policy(&policy, inode);
encryption_policy_out:
		return err;
#else
		return -EOPNOTSUPP;
#endif
	}
        ...
        }
        ...
}

ext4_process_policy

path: fs/ext4/crypto_policy.c

int ext4_process_policy(const struct ext4_encryption_policy *policy,
			struct inode *inode)
{
	if (policy->version != 0)
		return -EINVAL;

	if (!ext4_inode_has_encryption_context(inode)) {
		if (!S_ISDIR(inode->i_mode))
			return -EINVAL;
		if (!ext4_empty_dir(inode))
			return -ENOTEMPTY;
		return ext4_create_encryption_context_from_policy(inode,
								  policy);
	}

	if (ext4_is_encryption_context_consistent_with_policy(inode, policy))
		return 0;

	printk(KERN_WARNING "%s: Policy inconsistent with encryption context\n",
	       __func__);
	return -EINVAL;
}
  • ext4_inode_has_encryption_context
static int ext4_inode_has_encryption_context(struct inode *inode)
{
	int res = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
				 EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, NULL, 0);
	return (res > 0);
}
  • ext4_create_encryption_context_from_policy
static int ext4_create_encryption_context_from_policy(
	struct inode *inode, const struct ext4_encryption_policy *policy)
{
	struct ext4_encryption_context ctx;
	handle_t *handle;
	int res, res2;

	res = ext4_convert_inline_data(inode);
	if (res)
		return res;

	ctx.format = EXT4_ENCRYPTION_CONTEXT_FORMAT_V1;
	memcpy(ctx.master_key_descriptor, policy->master_key_descriptor,
	       EXT4_KEY_DESCRIPTOR_SIZE);
	if (!ext4_valid_contents_enc_mode(policy->contents_encryption_mode)) {
		printk(KERN_WARNING
		       "%s: Invalid contents encryption mode %d\n", __func__,
			policy->contents_encryption_mode);
		return -EINVAL;
	}
	if (!ext4_valid_filenames_enc_mode(policy->filenames_encryption_mode)) {
		printk(KERN_WARNING
		       "%s: Invalid filenames encryption mode %d\n", __func__,
			policy->filenames_encryption_mode);
		return -EINVAL;
	}
	if (policy->flags & ~EXT4_POLICY_FLAGS_VALID)
		return -EINVAL;
	ctx.contents_encryption_mode = policy->contents_encryption_mode;
	ctx.filenames_encryption_mode = policy->filenames_encryption_mode;
	ctx.flags = policy->flags;
	BUILD_BUG_ON(sizeof(ctx.nonce) != EXT4_KEY_DERIVATION_NONCE_SIZE);
	get_random_bytes(ctx.nonce, EXT4_KEY_DERIVATION_NONCE_SIZE);

	handle = ext4_journal_start(inode, EXT4_HT_MISC,
				    ext4_jbd2_credits_xattr(inode));
	if (IS_ERR(handle))
		return PTR_ERR(handle);
	res = ext4_xattr_set(inode, EXT4_XATTR_INDEX_ENCRYPTION,
			     EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, &ctx,
			     sizeof(ctx), 0);
	if (!res) {
		ext4_set_inode_flag(inode, EXT4_INODE_ENCRYPT);
		res = ext4_mark_inode_dirty(handle, inode);
		if (res)
			EXT4_ERROR_INODE(inode, "Failed to mark inode dirty");
	}
	res2 = ext4_journal_stop(handle);
	if (!res)
		res = res2;
	return res;
}
⚠️ **GitHub.com Fallback** ⚠️