/* * 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; } }