459 lines
13 KiB
C
459 lines
13 KiB
C
/*
|
|
* encrypted_files.c --- save information about encrypted files
|
|
*
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* %Begin-Header%
|
|
* This file may be redistributed under the terms of the GNU Public
|
|
* License.
|
|
* %End-Header%
|
|
*/
|
|
|
|
/*
|
|
* e2fsck pass 1 (inode table scan) creates a map from inode number to
|
|
* encryption policy for all encrypted inodes. But it's optimized so that the
|
|
* full xattrs aren't saved but rather only 32-bit "policy IDs", since usually
|
|
* many inodes share the same encryption policy. This requires also maintaining
|
|
* a second map, from policy to policy ID. See add_encrypted_file().
|
|
*
|
|
* We also use run-length encoding to save memory when many adjacent inodes
|
|
* share the same encryption policy, which is often the case too.
|
|
*
|
|
* e2fsck pass 2 (directory structure check) uses the inode => policy ID map to
|
|
* verify that all regular files, directories, and symlinks in encrypted
|
|
* directories use the directory's encryption policy.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "e2fsck.h"
|
|
#include "problem.h"
|
|
#include "ext2fs/rbtree.h"
|
|
|
|
#define FSCRYPT_KEY_DESCRIPTOR_SIZE 8
|
|
#define FSCRYPT_KEY_IDENTIFIER_SIZE 16
|
|
#define FS_KEY_DERIVATION_NONCE_SIZE 16
|
|
|
|
struct fscrypt_context_v1 {
|
|
__u8 version;
|
|
__u8 contents_encryption_mode;
|
|
__u8 filenames_encryption_mode;
|
|
__u8 flags;
|
|
__u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
|
|
__u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE];
|
|
};
|
|
|
|
struct fscrypt_context_v2 {
|
|
__u8 version;
|
|
__u8 contents_encryption_mode;
|
|
__u8 filenames_encryption_mode;
|
|
__u8 flags;
|
|
__u8 __reserved[4];
|
|
__u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
|
|
__u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE];
|
|
};
|
|
|
|
/* On-disk format of encryption xattr */
|
|
union fscrypt_context {
|
|
__u8 version;
|
|
struct fscrypt_context_v1 v1;
|
|
struct fscrypt_context_v2 v2;
|
|
};
|
|
|
|
struct fscrypt_policy_v1 {
|
|
__u8 version;
|
|
__u8 contents_encryption_mode;
|
|
__u8 filenames_encryption_mode;
|
|
__u8 flags;
|
|
__u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
|
|
};
|
|
|
|
struct fscrypt_policy_v2 {
|
|
__u8 version;
|
|
__u8 contents_encryption_mode;
|
|
__u8 filenames_encryption_mode;
|
|
__u8 flags;
|
|
__u8 __reserved[4];
|
|
__u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
|
|
};
|
|
|
|
/* The encryption "policy" is the fscrypt_context excluding the nonce. */
|
|
union fscrypt_policy {
|
|
__u8 version;
|
|
struct fscrypt_policy_v1 v1;
|
|
struct fscrypt_policy_v2 v2;
|
|
};
|
|
|
|
/* A range of inodes which share the same encryption policy */
|
|
struct encrypted_file_range {
|
|
ext2_ino_t first_ino;
|
|
ext2_ino_t last_ino;
|
|
__u32 policy_id;
|
|
};
|
|
|
|
/* Information about the encrypted files which have been seen so far */
|
|
struct encrypted_file_info {
|
|
/*
|
|
* Map from inode number to encryption policy ID, implemented as a
|
|
* sorted array of inode ranges, each of which shares the same policy.
|
|
* Inodes are added in order of increasing inode number.
|
|
*
|
|
* Freed after pass 2.
|
|
*/
|
|
struct encrypted_file_range *file_ranges;
|
|
size_t file_ranges_count;
|
|
size_t file_ranges_capacity;
|
|
|
|
/*
|
|
* Map from encryption policy to encryption policy ID, for the unique
|
|
* encryption policies that have been seen so far. next_policy_id is
|
|
* the next available ID, starting at 0.
|
|
*
|
|
* Freed after pass 1.
|
|
*/
|
|
struct rb_root policies;
|
|
__u32 next_policy_id;
|
|
};
|
|
|
|
/* Entry in encrypted_file_info::policies */
|
|
struct policy_map_entry {
|
|
union fscrypt_policy policy;
|
|
__u32 policy_id;
|
|
struct rb_node node;
|
|
};
|
|
|
|
static int cmp_fscrypt_policies(e2fsck_t ctx, const union fscrypt_policy *a,
|
|
const union fscrypt_policy *b)
|
|
{
|
|
if (a->version != b->version)
|
|
return (int)a->version - (int)b->version;
|
|
|
|
switch (a->version) {
|
|
case 1:
|
|
return memcmp(a, b, sizeof(a->v1));
|
|
case 2:
|
|
return memcmp(a, b, sizeof(a->v2));
|
|
}
|
|
fatal_error(ctx, "Unhandled encryption policy version");
|
|
return 0;
|
|
}
|
|
|
|
/* Read an inode's encryption xattr. */
|
|
static errcode_t read_encryption_xattr(e2fsck_t ctx, ext2_ino_t ino,
|
|
void **value, size_t *value_len)
|
|
{
|
|
struct ext2_xattr_handle *h;
|
|
errcode_t retval;
|
|
|
|
retval = ext2fs_xattrs_open(ctx->fs, ino, &h);
|
|
if (retval)
|
|
return retval;
|
|
|
|
retval = ext2fs_xattrs_read(h);
|
|
if (retval == 0)
|
|
retval = ext2fs_xattr_get(h, "c", value, value_len);
|
|
|
|
ext2fs_xattrs_close(&h);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Convert an fscrypt_context to an fscrypt_policy. Returns 0,
|
|
* CORRUPT_ENCRYPTION_POLICY, or UNRECOGNIZED_ENCRYPTION_POLICY.
|
|
*/
|
|
static __u32 fscrypt_context_to_policy(const void *xattr, size_t xattr_size,
|
|
union fscrypt_policy *policy_u)
|
|
{
|
|
const union fscrypt_context *ctx_u = xattr;
|
|
|
|
if (xattr_size < 1)
|
|
return CORRUPT_ENCRYPTION_POLICY;
|
|
switch (ctx_u->version) {
|
|
case 0:
|
|
return CORRUPT_ENCRYPTION_POLICY;
|
|
case 1: {
|
|
struct fscrypt_policy_v1 *policy = &policy_u->v1;
|
|
const struct fscrypt_context_v1 *ctx = &ctx_u->v1;
|
|
|
|
if (xattr_size != sizeof(*ctx))
|
|
return CORRUPT_ENCRYPTION_POLICY;
|
|
policy->version = ctx->version;
|
|
policy->contents_encryption_mode =
|
|
ctx->contents_encryption_mode;
|
|
policy->filenames_encryption_mode =
|
|
ctx->filenames_encryption_mode;
|
|
policy->flags = ctx->flags;
|
|
memcpy(policy->master_key_descriptor,
|
|
ctx->master_key_descriptor,
|
|
sizeof(policy->master_key_descriptor));
|
|
return 0;
|
|
}
|
|
case 2: {
|
|
struct fscrypt_policy_v2 *policy = &policy_u->v2;
|
|
const struct fscrypt_context_v2 *ctx = &ctx_u->v2;
|
|
|
|
if (xattr_size != sizeof(*ctx))
|
|
return CORRUPT_ENCRYPTION_POLICY;
|
|
policy->version = ctx->version;
|
|
policy->contents_encryption_mode =
|
|
ctx->contents_encryption_mode;
|
|
policy->filenames_encryption_mode =
|
|
ctx->filenames_encryption_mode;
|
|
policy->flags = ctx->flags;
|
|
memcpy(policy->__reserved, ctx->__reserved,
|
|
sizeof(policy->__reserved));
|
|
memcpy(policy->master_key_identifier,
|
|
ctx->master_key_identifier,
|
|
sizeof(policy->master_key_identifier));
|
|
return 0;
|
|
}
|
|
}
|
|
return UNRECOGNIZED_ENCRYPTION_POLICY;
|
|
}
|
|
|
|
/*
|
|
* Read an inode's encryption xattr and get/allocate its encryption policy ID,
|
|
* or alternatively use one of the special IDs NO_ENCRYPTION_POLICY,
|
|
* CORRUPT_ENCRYPTION_POLICY, or UNRECOGNIZED_ENCRYPTION_POLICY.
|
|
*
|
|
* Returns nonzero only if out of memory.
|
|
*/
|
|
static errcode_t get_encryption_policy_id(e2fsck_t ctx, ext2_ino_t ino,
|
|
__u32 *policy_id_ret)
|
|
{
|
|
struct encrypted_file_info *info = ctx->encrypted_files;
|
|
struct rb_node **new = &info->policies.rb_node;
|
|
struct rb_node *parent = NULL;
|
|
void *xattr;
|
|
size_t xattr_size;
|
|
union fscrypt_policy policy;
|
|
__u32 policy_id;
|
|
struct policy_map_entry *entry;
|
|
errcode_t retval;
|
|
|
|
retval = read_encryption_xattr(ctx, ino, &xattr, &xattr_size);
|
|
if (retval == EXT2_ET_NO_MEMORY)
|
|
return retval;
|
|
if (retval) {
|
|
*policy_id_ret = NO_ENCRYPTION_POLICY;
|
|
return 0;
|
|
}
|
|
|
|
/* Translate the xattr to an fscrypt_policy, if possible. */
|
|
policy_id = fscrypt_context_to_policy(xattr, xattr_size, &policy);
|
|
ext2fs_free_mem(&xattr);
|
|
if (policy_id != 0)
|
|
goto out;
|
|
|
|
/* Check if the policy was already seen. */
|
|
while (*new) {
|
|
int res;
|
|
|
|
parent = *new;
|
|
entry = ext2fs_rb_entry(parent, struct policy_map_entry, node);
|
|
res = cmp_fscrypt_policies(ctx, &policy, &entry->policy);
|
|
if (res < 0) {
|
|
new = &parent->rb_left;
|
|
} else if (res > 0) {
|
|
new = &parent->rb_right;
|
|
} else {
|
|
/* Policy already seen. Use existing ID. */
|
|
policy_id = entry->policy_id;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* First time seeing this policy. Allocate a new policy ID. */
|
|
retval = ext2fs_get_mem(sizeof(*entry), &entry);
|
|
if (retval)
|
|
goto out;
|
|
policy_id = info->next_policy_id++;
|
|
entry->policy_id = policy_id;
|
|
entry->policy = policy;
|
|
ext2fs_rb_link_node(&entry->node, parent, new);
|
|
ext2fs_rb_insert_color(&entry->node, &info->policies);
|
|
out:
|
|
*policy_id_ret = policy_id;
|
|
return retval;
|
|
}
|
|
|
|
static int handle_nomem(e2fsck_t ctx, struct problem_context *pctx,
|
|
size_t size_needed)
|
|
{
|
|
pctx->num = size_needed;
|
|
fix_problem(ctx, PR_1_ALLOCATE_ENCRYPTED_INODE_LIST, pctx);
|
|
/* Should never get here */
|
|
ctx->flags |= E2F_FLAG_ABORT;
|
|
return 0;
|
|
}
|
|
|
|
static int append_ino_and_policy_id(e2fsck_t ctx, struct problem_context *pctx,
|
|
ext2_ino_t ino, __u32 policy_id)
|
|
{
|
|
struct encrypted_file_info *info = ctx->encrypted_files;
|
|
struct encrypted_file_range *range;
|
|
|
|
/* See if we can just extend the last range. */
|
|
if (info->file_ranges_count > 0) {
|
|
range = &info->file_ranges[info->file_ranges_count - 1];
|
|
|
|
if (ino <= range->last_ino) {
|
|
/* Should never get here */
|
|
fatal_error(ctx,
|
|
"Encrypted inodes processed out of order");
|
|
}
|
|
|
|
if (ino == range->last_ino + 1 &&
|
|
policy_id == range->policy_id) {
|
|
range->last_ino++;
|
|
return 0;
|
|
}
|
|
}
|
|
/* Nope, a new range is needed. */
|
|
|
|
if (info->file_ranges_count == info->file_ranges_capacity) {
|
|
/* Double the capacity by default. */
|
|
size_t new_capacity = info->file_ranges_capacity * 2;
|
|
|
|
/* ... but go from 0 to 128 right away. */
|
|
if (new_capacity < 128)
|
|
new_capacity = 128;
|
|
|
|
/* We won't need more than the filesystem's inode count. */
|
|
if (new_capacity > ctx->fs->super->s_inodes_count)
|
|
new_capacity = ctx->fs->super->s_inodes_count;
|
|
|
|
/* To be safe, ensure the capacity really increases. */
|
|
if (new_capacity < info->file_ranges_capacity + 1)
|
|
new_capacity = info->file_ranges_capacity + 1;
|
|
|
|
if (ext2fs_resize_mem(info->file_ranges_capacity *
|
|
sizeof(*range),
|
|
new_capacity * sizeof(*range),
|
|
&info->file_ranges) != 0)
|
|
return handle_nomem(ctx, pctx,
|
|
new_capacity * sizeof(*range));
|
|
|
|
info->file_ranges_capacity = new_capacity;
|
|
}
|
|
range = &info->file_ranges[info->file_ranges_count++];
|
|
range->first_ino = ino;
|
|
range->last_ino = ino;
|
|
range->policy_id = policy_id;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Handle an inode that has EXT4_ENCRYPT_FL set during pass 1. Normally this
|
|
* just finds the unique ID that identifies the inode's encryption policy
|
|
* (allocating a new ID if needed), and adds the inode number and its policy ID
|
|
* to the encrypted_file_info so that it's available in pass 2.
|
|
*
|
|
* But this also handles:
|
|
* - If the inode doesn't have an encryption xattr at all, offer to clear the
|
|
* encrypt flag.
|
|
* - If the encryption xattr is clearly corrupt, tell the caller that the whole
|
|
* inode should be cleared.
|
|
* - To be future-proof: if the encryption xattr has an unrecognized version
|
|
* number, it *might* be valid, so we don't consider it invalid. But we can't
|
|
* do much with it, so give all such policies the same ID,
|
|
* UNRECOGNIZED_ENCRYPTION_POLICY.
|
|
*
|
|
* Returns -1 if the inode should be cleared, otherwise 0.
|
|
*/
|
|
int add_encrypted_file(e2fsck_t ctx, struct problem_context *pctx)
|
|
{
|
|
struct encrypted_file_info *info = ctx->encrypted_files;
|
|
ext2_ino_t ino = pctx->ino;
|
|
__u32 policy_id;
|
|
|
|
/* Allocate the encrypted_file_info if needed. */
|
|
if (info == NULL) {
|
|
if (ext2fs_get_memzero(sizeof(*info), &info) != 0)
|
|
return handle_nomem(ctx, pctx, sizeof(*info));
|
|
ctx->encrypted_files = info;
|
|
}
|
|
|
|
/* Get a unique ID for this inode's encryption policy. */
|
|
if (get_encryption_policy_id(ctx, ino, &policy_id) != 0)
|
|
return handle_nomem(ctx, pctx, 0 /* unknown size */);
|
|
if (policy_id == NO_ENCRYPTION_POLICY) {
|
|
if (fix_problem(ctx, PR_1_MISSING_ENCRYPTION_XATTR, pctx)) {
|
|
pctx->inode->i_flags &= ~EXT4_ENCRYPT_FL;
|
|
e2fsck_write_inode(ctx, ino, pctx->inode, "pass1");
|
|
}
|
|
return 0;
|
|
} else if (policy_id == CORRUPT_ENCRYPTION_POLICY) {
|
|
if (fix_problem(ctx, PR_1_CORRUPT_ENCRYPTION_XATTR, pctx))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* Store this ino => policy_id mapping in the encrypted_file_info. */
|
|
return append_ino_and_policy_id(ctx, pctx, ino, policy_id);
|
|
}
|
|
|
|
/*
|
|
* Find the ID of an inode's encryption policy, using the information saved
|
|
* earlier.
|
|
*
|
|
* If the inode is encrypted, returns the policy ID or
|
|
* UNRECOGNIZED_ENCRYPTION_POLICY. Else, returns NO_ENCRYPTION_POLICY.
|
|
*/
|
|
__u32 find_encryption_policy(e2fsck_t ctx, ext2_ino_t ino)
|
|
{
|
|
const struct encrypted_file_info *info = ctx->encrypted_files;
|
|
size_t l, r;
|
|
|
|
if (info == NULL)
|
|
return NO_ENCRYPTION_POLICY;
|
|
l = 0;
|
|
r = info->file_ranges_count;
|
|
while (l < r) {
|
|
size_t m = l + (r - l) / 2;
|
|
const struct encrypted_file_range *range =
|
|
&info->file_ranges[m];
|
|
|
|
if (ino < range->first_ino)
|
|
r = m;
|
|
else if (ino > range->last_ino)
|
|
l = m + 1;
|
|
else
|
|
return range->policy_id;
|
|
}
|
|
return NO_ENCRYPTION_POLICY;
|
|
}
|
|
|
|
/* Destroy ctx->encrypted_files->policies */
|
|
void destroy_encryption_policy_map(e2fsck_t ctx)
|
|
{
|
|
struct encrypted_file_info *info = ctx->encrypted_files;
|
|
|
|
if (info) {
|
|
struct rb_root *policies = &info->policies;
|
|
|
|
while (!ext2fs_rb_empty_root(policies)) {
|
|
struct policy_map_entry *entry;
|
|
|
|
entry = ext2fs_rb_entry(policies->rb_node,
|
|
struct policy_map_entry, node);
|
|
ext2fs_rb_erase(&entry->node, policies);
|
|
ext2fs_free_mem(&entry);
|
|
}
|
|
info->next_policy_id = 0;
|
|
}
|
|
}
|
|
|
|
/* Destroy ctx->encrypted_files */
|
|
void destroy_encrypted_file_info(e2fsck_t ctx)
|
|
{
|
|
struct encrypted_file_info *info = ctx->encrypted_files;
|
|
|
|
if (info) {
|
|
destroy_encryption_policy_map(ctx);
|
|
ext2fs_free_mem(&info->file_ranges);
|
|
ext2fs_free_mem(&info);
|
|
ctx->encrypted_files = NULL;
|
|
}
|
|
}
|