mirror of
https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git
synced 2024-11-26 19:43:41 +08:00
2ba05753f7
By design, the kernel enforces that all files in an encrypted directory use the same encryption policy as the directory. It's not possible to violate this constraint using syscalls. Lookups of files that violate this constraint also fail, in case the disk was manipulated. But this constraint can also be violated by accidental filesystem corruption. E.g., a power cut when using ext4 without a journal might leave new files without the encryption bit and/or xattr. Thus, it's important that e2fsck correct this condition. Therefore, this patch makes the following changes to e2fsck: - During pass 1 (inode table scan), create 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. Also, if an encryption xattr is missing, offer to clear the encrypt flag. If an encryption xattr is clearly corrupt, offer to clear the inode. - During pass 2 (directory structure check), use the map to verify that all regular files, directories, and symlinks in encrypted directories use the directory's encryption policy. Offer to clear any directory entries for which this isn't the case. Add a new test "f_bad_encryption" to test the new behavior. Due to the new checks, it was also necessary to update the existing test "f_short_encrypted_dirent" to add an encryption xattr to the test file, since it was missing one before, which is now considered invalid. Google-Bug-Id: 135138675 Signed-off-by: Eric Biggers <ebiggers@google.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu> Reviewed-by: Andreas Dilger <adilger@dilger.ca>
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;
|
|
}
|
|
}
|