"sigcatcher.c",
"readahead.c",
"extents.c",
+ "encrypted_files.c",
],
cflags: [
"-Wno-sign-compare",
dx_dirinfo.o ehandler.o problem.o message.o quota.o recovery.o \
region.o revoke.o ea_refcount.o rehash.o \
logfile.o sigcatcher.o $(MTRACE_OBJ) readahead.o \
- extents.o
+ extents.o encrypted_files.o
PROFILED_OBJS= profiled/unix.o profiled/e2fsck.o \
profiled/super.o profiled/pass1.o profiled/pass1b.o \
profiled/recovery.o profiled/region.o profiled/revoke.o \
profiled/ea_refcount.o profiled/rehash.o \
profiled/logfile.o profiled/sigcatcher.o \
- profiled/readahead.o profiled/extents.o
+ profiled/readahead.o profiled/extents.o \
+ profiled/encrypted_files.o
SRCS= $(srcdir)/e2fsck.c \
$(srcdir)/super.c \
$(srcdir)/logfile.c \
$(srcdir)/quota.c \
$(srcdir)/extents.c \
+ $(srcdir)/encrypted_files.c \
$(MTRACE_SRC)
all:: profiled $(PROGS) e2fsck $(MANPAGES) $(FMANPAGES)
$(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
$(top_srcdir)/lib/support/dqblk_v2.h \
$(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/problem.h
+encrypted_files.o: $(srcdir)/encrypted_files.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
+ $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \
+ $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \
+ $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/problem.h \
+ $(top_srcdir)/lib/ext2fs/rbtree.h
ext2fs_u32_list_free(ctx->dirs_to_hash);
ctx->dirs_to_hash = 0;
}
+ destroy_encrypted_file_info(ctx);
/*
* Clear the array of invalid meta-data flags
ext2fs_free_mem(&ctx->invalid_inode_table_flag);
ctx->invalid_inode_table_flag = 0;
}
- if (ctx->encrypted_dirs) {
- ext2fs_u32_list_free(ctx->encrypted_dirs);
- ctx->encrypted_dirs = 0;
- }
if (ctx->inode_count) {
ext2fs_free_icount(ctx->inode_count);
ctx->inode_count = 0;
#define DX_FLAG_FIRST 4
#define DX_FLAG_LAST 8
+struct encrypted_file_info;
+
#define RESOURCE_TRACK
#ifdef RESOURCE_TRACK
ext2_u32_list dirs_to_hash;
/*
+ * Encrypted file information
+ */
+ struct encrypted_file_info *encrypted_files;
+
+ /*
* Tuning parameters
*/
int process_inode_size;
int ext_attr_ver;
profile_t profile;
int blocks_per_page;
- ext2_u32_list encrypted_dirs;
/* Reserve blocks for root and l+f re-creation */
blk64_t root_repair_block, lnf_repair_block;
extern const char *ehandler_operation(const char *op);
extern void ehandler_init(io_channel channel);
-/* extents.c */
+/* encrypted_files.c */
+
struct problem_context;
+int add_encrypted_file(e2fsck_t ctx, struct problem_context *pctx);
+
+#define NO_ENCRYPTION_POLICY ((__u32)-1)
+#define CORRUPT_ENCRYPTION_POLICY ((__u32)-2)
+#define UNRECOGNIZED_ENCRYPTION_POLICY ((__u32)-3)
+__u32 find_encryption_policy(e2fsck_t ctx, ext2_ino_t ino);
+
+void destroy_encryption_policy_map(e2fsck_t ctx);
+void destroy_encrypted_file_info(e2fsck_t ctx);
+
+/* extents.c */
errcode_t e2fsck_rebuild_extents_later(e2fsck_t ctx, ext2_ino_t ino);
int e2fsck_ino_will_be_rebuilt(e2fsck_t ctx, ext2_ino_t ino);
void e2fsck_pass1e(e2fsck_t ctx);
--- /dev/null
+/*
+ * 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;
+ }
+}
* - A bitmap of which blocks are in use by two inodes (block_dup_map)
* - The data blocks of the directory inodes. (dir_map)
* - Ref counts for ea_inodes. (ea_inode_refs)
+ * - The encryption policy ID of each encrypted inode. (encrypted_files)
*
* Pass 1 is designed to stash away enough information so that the
* other passes should not need to read in the inode information
static void alloc_bb_map(e2fsck_t ctx);
static void alloc_imagic_map(e2fsck_t ctx);
static void mark_inode_bad(e2fsck_t ctx, ino_t ino);
-static void add_encrypted_dir(e2fsck_t ctx, ino_t ino);
static void handle_fs_bad_blocks(e2fsck_t ctx);
static void process_inodes(e2fsck_t ctx, char *block_buf);
static EXT2_QSORT_TYPE process_inode_cmp(const void *a, const void *b);
failed_csum = 0;
}
+ if ((inode->i_flags & EXT4_ENCRYPT_FL) &&
+ add_encrypted_file(ctx, &pctx) < 0)
+ goto clear_inode;
+
if (LINUX_S_ISDIR(inode->i_mode)) {
ext2fs_mark_inode_bitmap2(ctx->inode_dir_map, ino);
e2fsck_add_dir_info(ctx, ino, 0);
ctx->fs_directory_count++;
- if (inode->i_flags & EXT4_ENCRYPT_FL)
- add_encrypted_dir(ctx, ino);
} else if (LINUX_S_ISREG (inode->i_mode)) {
ext2fs_mark_inode_bitmap2(ctx->inode_reg_map, ino);
ctx->fs_regular_count++;
ctx->block_ea_map = 0;
}
+ /* We don't need the encryption policy => ID map any more */
+ destroy_encryption_policy_map(ctx);
+
if (ctx->flags & E2F_FLAG_RESIZE_INODE) {
clear_problem_context(&pctx);
pctx.errcode = ext2fs_create_resize_inode(fs);
ext2fs_mark_inode_bitmap2(ctx->inode_bad_map, ino);
}
-static void add_encrypted_dir(e2fsck_t ctx, ino_t ino)
-{
- struct problem_context pctx;
-
- if (!ctx->encrypted_dirs) {
- pctx.errcode = ext2fs_u32_list_create(&ctx->encrypted_dirs, 0);
- if (pctx.errcode)
- goto error;
- }
- pctx.errcode = ext2fs_u32_list_add(ctx->encrypted_dirs, ino);
- if (pctx.errcode == 0)
- return;
-error:
- fix_problem(ctx, PR_1_ALLOCATE_ENCRYPTED_DIRLIST, &pctx);
- /* Should never get here */
- ctx->flags |= E2F_FLAG_ABORT;
-}
-
/*
* This procedure will allocate the inode "bb" (badblock) map table
*/
* - The inode_used_map bitmap
* - The inode_bad_map bitmap
* - The inode_dir_map bitmap
+ * - The encrypted_file_info
*
* Pass 2 frees the following data structures
* - The inode_bad_map bitmap
* - The inode_reg_map bitmap
+ * - The encrypted_file_info
*/
#define _GNU_SOURCE 1 /* get strnlen() */
ext2fs_free_inode_bitmap(ctx->inode_reg_map);
ctx->inode_reg_map = 0;
}
- if (ctx->encrypted_dirs) {
- ext2fs_u32_list_free(ctx->encrypted_dirs);
- ctx->encrypted_dirs = 0;
- }
+ destroy_encrypted_file_info(ctx);
clear_problem_context(&pctx);
if (ctx->large_files) {
}
static int encrypted_check_name(e2fsck_t ctx,
- struct ext2_dir_entry *dirent,
+ const struct ext2_dir_entry *dirent,
struct problem_context *pctx)
{
if (ext2fs_dirent_name_len(dirent) < EXT4_CRYPTO_BLOCK_SIZE) {
- if (fix_problem(ctx, PR_2_BAD_ENCRYPTED_NAME, pctx)) {
- dirent->inode = 0;
+ if (fix_problem(ctx, PR_2_BAD_ENCRYPTED_NAME, pctx))
return 1;
- }
ext2fs_unmark_valid(ctx->fs);
}
return 0;
return retval;
}
+/* Return true if this type of file needs encryption */
+static int needs_encryption(e2fsck_t ctx, const struct ext2_dir_entry *dirent)
+{
+ int filetype = ext2fs_dirent_file_type(dirent);
+ ext2_ino_t ino = dirent->inode;
+ struct ext2_inode inode;
+
+ if (filetype != EXT2_FT_UNKNOWN)
+ return filetype == EXT2_FT_REG_FILE ||
+ filetype == EXT2_FT_DIR ||
+ filetype == EXT2_FT_SYMLINK;
+
+ if (ext2fs_test_inode_bitmap2(ctx->inode_reg_map, ino) ||
+ ext2fs_test_inode_bitmap2(ctx->inode_dir_map, ino))
+ return 1;
+
+ e2fsck_read_inode(ctx, ino, &inode, "check_encryption_policy");
+ return LINUX_S_ISREG(inode.i_mode) ||
+ LINUX_S_ISDIR(inode.i_mode) ||
+ LINUX_S_ISLNK(inode.i_mode);
+}
+
+/*
+ * All regular files, directories, and symlinks in encrypted directories must be
+ * encrypted using the same encryption policy as their directory.
+ *
+ * Returns 1 if the dirent should be cleared, otherwise 0.
+ */
+static int check_encryption_policy(e2fsck_t ctx,
+ const struct ext2_dir_entry *dirent,
+ __u32 dir_encpolicy_id,
+ struct problem_context *pctx)
+{
+ __u32 file_encpolicy_id = find_encryption_policy(ctx, dirent->inode);
+
+ /* Same policy or both UNRECOGNIZED_ENCRYPTION_POLICY? */
+ if (file_encpolicy_id == dir_encpolicy_id)
+ return 0;
+
+ if (file_encpolicy_id == NO_ENCRYPTION_POLICY) {
+ if (!needs_encryption(ctx, dirent))
+ return 0;
+ return fix_problem(ctx, PR_2_UNENCRYPTED_FILE, pctx);
+ }
+
+ return fix_problem(ctx, PR_2_INCONSISTENT_ENCRYPTION_POLICY, pctx);
+}
+
+/*
+ * Check an encrypted directory entry.
+ *
+ * Returns 1 if the dirent should be cleared, otherwise 0.
+ */
+static int check_encrypted_dirent(e2fsck_t ctx,
+ const struct ext2_dir_entry *dirent,
+ __u32 dir_encpolicy_id,
+ struct problem_context *pctx)
+{
+ if (encrypted_check_name(ctx, dirent, pctx))
+ return 1;
+ if (check_encryption_policy(ctx, dirent, dir_encpolicy_id, pctx))
+ return 1;
+ return 0;
+}
+
static int check_dir_block2(ext2_filsys fs,
struct ext2_db_entry2 *db,
void *priv_data)
int is_leaf = 1;
size_t inline_data_size = 0;
int filetype = 0;
- int encrypted = 0;
+ __u32 dir_encpolicy_id = NO_ENCRYPTION_POLICY;
size_t max_block_size;
int hash_flags = 0;
} else
max_block_size = fs->blocksize - de_csum_size;
- if (ctx->encrypted_dirs)
- encrypted = ext2fs_u32_list_test(ctx->encrypted_dirs, ino);
+ dir_encpolicy_id = find_encryption_policy(ctx, ino);
dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp);
prev = 0;
}
}
- if (!encrypted && check_name(ctx, dirent, &cd->pctx))
+ if (check_filetype(ctx, dirent, ino, &cd->pctx))
dir_modified++;
- if (encrypted && (dot_state) > 1 &&
- encrypted_check_name(ctx, dirent, &cd->pctx)) {
- dir_modified++;
- goto next;
+ if (dir_encpolicy_id == NO_ENCRYPTION_POLICY) {
+ /* Unencrypted directory */
+ if (check_name(ctx, dirent, &cd->pctx))
+ dir_modified++;
+ } else {
+ /* Encrypted directory */
+ if (dot_state > 1 &&
+ check_encrypted_dirent(ctx, dirent,
+ dir_encpolicy_id,
+ &cd->pctx)) {
+ dirent->inode = 0;
+ dir_modified++;
+ goto next;
+ }
}
- if (check_filetype(ctx, dirent, ino, &cd->pctx))
- dir_modified++;
-
if (dx_db) {
if (dx_dir->casefolded_hash)
hash_flags = EXT4_CASEFOLD_FL;
N_("@i %i has a duplicate @x mapping\n\t(logical @b %c, @n physical @b %b, len %N)\n"),
PROMPT_CLEAR, 0, 0, 0, 0 },
- /* Error allocating memory for encrypted directory list */
- { PR_1_ALLOCATE_ENCRYPTED_DIRLIST,
- N_("@A memory for encrypted @d list\n"),
+ /* Error allocating memory for encrypted inode list */
+ { PR_1_ALLOCATE_ENCRYPTED_INODE_LIST,
+ N_("@A %N bytes of memory for encrypted @i list\n"),
PROMPT_NONE, PR_FATAL, 0, 0, 0 },
/* Inode extent tree could be more shallow */
N_("@d %p has the casefold flag, but the\ncasefold feature is not enabled. "),
PROMPT_CLEAR_FLAG, 0, 0, 0, 0 },
+ /* Inode has encrypt flag but no encryption extended attribute */
+ { PR_1_MISSING_ENCRYPTION_XATTR,
+ N_("@i %i has encrypt flag but no encryption @a.\n"),
+ PROMPT_CLEAR_FLAG, 0, 0, 0, 0 },
+
+ /* Encrypted inode has corrupt encryption extended attribute */
+ { PR_1_CORRUPT_ENCRYPTION_XATTR,
+ N_("Encrypted @i %i has corrupt encryption @a.\n"),
+ PROMPT_CLEAR_INODE, 0, 0, 0, 0 },
+
/* Pass 1b errors */
/* Pass 1B: Rescan for duplicate/bad blocks */
N_("Encrypted @E is too short.\n"),
PROMPT_CLEAR, 0, 0, 0, 0 },
+ /* Encrypted directory contains unencrypted file */
+ { PR_2_UNENCRYPTED_FILE,
+ N_("Encrypted @E references unencrypted @i %Di.\n"),
+ PROMPT_CLEAR, 0, 0, 0, 0 },
+
+ /* Encrypted directory contains file with different encryption policy */
+ { PR_2_INCONSISTENT_ENCRYPTION_POLICY,
+ N_("Encrypted @E references @i %Di, which has a different encryption policy.\n"),
+ PROMPT_CLEAR, 0, 0, 0, 0 },
+
/* Pass 3 errors */
/* Pass 3: Checking directory connectivity */
/* Inode leaf has a duplicate extent mapping */
#define PR_1_EXTENT_COLLISION 0x01007D
-/* Error allocating memory for encrypted directory list */
-#define PR_1_ALLOCATE_ENCRYPTED_DIRLIST 0x01007E
+/* Error allocating memory for encrypted inode list */
+#define PR_1_ALLOCATE_ENCRYPTED_INODE_LIST 0x01007E
/* Inode extent tree could be more shallow */
#define PR_1_EXTENT_BAD_MAX_DEPTH 0x01007F
/* Casefold flag set, but file system is missing the casefold feature */
#define PR_1_CASEFOLD_FEATURE 0x010089
+/* Inode has encrypt flag but no encryption extended attribute */
+#define PR_1_MISSING_ENCRYPTION_XATTR 0x01008A
+
+/* Encrypted inode has corrupt encryption extended attribute */
+#define PR_1_CORRUPT_ENCRYPTION_XATTR 0x01008B
/*
* Pass 1b errors
/* Encrypted directory entry is too short */
#define PR_2_BAD_ENCRYPTED_NAME 0x020050
+/* Encrypted directory contains unencrypted file */
+#define PR_2_UNENCRYPTED_FILE 0x020051
+
+/* Encrypted directory contains file with different encryption policy */
+#define PR_2_INCONSISTENT_ENCRYPTION_POLICY 0x020052
+
/*
* Pass 3 errors
*/
e2fsck/ea_refcount.c
e2fsck/ehandler.c
e2fsck/emptydir.c
+e2fsck/encrypted_files.c
e2fsck/extend.c
e2fsck/extents.c
e2fsck/flushb.c
--- /dev/null
+Pass 1: Checking inodes, blocks, and sizes
+Inode 17 has encrypt flag but no encryption extended attribute.
+Clear flag? yes
+
+Inode 18 has encrypt flag but no encryption extended attribute.
+Clear flag? yes
+
+Encrypted inode 19 has corrupt encryption extended attribute.
+Clear inode? yes
+
+Encrypted inode 20 has corrupt encryption extended attribute.
+Clear inode? yes
+
+Encrypted inode 21 has corrupt encryption extended attribute.
+Clear inode? yes
+
+Encrypted inode 22 has corrupt encryption extended attribute.
+Clear inode? yes
+
+Pass 2: Checking directory structure
+Encrypted entry 'd6M->'M-#I^VM-^KM-F~^WSJ+M-uM-zM-zXM-^' in /edir (12) references unencrypted inode 17.
+Clear? yes
+
+Encrypted entry '\M-!M-Y%DhM-OM-VM-zM-CM-gVM-R3M-^RM-IkE^JM-^S' in /edir (12) references unencrypted inode 18.
+Clear? yes
+
+Entry 'M-{^Qp-M-sM-U7eM-^C^L^PG^ZM-FM-,M-B' in /edir (12) has deleted/unused inode 19. Clear? yes
+
+Entry 'M-f0M-f3/M-NM-GM-:M-^YM-jM-XM-91DM-^_M-V' in /edir (12) has deleted/unused inode 20. Clear? yes
+
+Entry '^M-R"M-^K^P7M-'M-EM-C}^MM-yM-^LwM-^N^Z' in /edir (12) has deleted/unused inode 21. Clear? yes
+
+Entry 'M-s^J_;uIvM-^Z[M-nIM-5vM-^AcM-o' in /edir (12) has deleted/unused inode 22. Clear? yes
+
+Encrypted entry 'kK=,M-bM-^AM-{M-YM-^J6M-hM-y^XM-^W}M-M' in /edir (12) references unencrypted inode 23.
+Clear? yes
+
+Encrypted entry 'M-VM-cxM-jM-zM-b^WM-o*M-jM-uM-,R^PM-hM-2' in /edir (12) references unencrypted inode 24.
+Clear? yes
+
+Encrypted entry 'UqM-AM-#KM-^PM-_^kM-9P0M-^FM-_^@;A^J"R' in /edir (12) references unencrypted inode 25.
+Clear? yes
+
+Encrypted entry 'M-TM-N8^[M-3M-( M-[A^FR}^ZhkM-^?=M-c^Mo' in /edir (12) references inode 26, which has a different encryption policy.
+Clear? yes
+
+Encrypted entry 'M--aM-^?~M-^\M-u^FM-/!^YM-OZM-^LM-)M-p1' in /edir (12) references inode 27, which has a different encryption policy.
+Clear? yes
+
+Encrypted entry '(M-8RKM-LM-eM-^W^[M-'M-SM-@uM-^VM-|M-GiM-^JbM-nM-z' in /edir (12) references inode 28, which has a different encryption policy.
+Clear? yes
+
+Encrypted entry '\M-ggCeM-/?M-^BM-{(M-^OM-9M-^QQAM-^N=M-c^Mo' in /edir (12) references inode 29, which has a different encryption policy.
+Clear? yes
+
+Pass 3: Checking directory connectivity
+Unconnected directory inode 18 (/edir/???)
+Connect to /lost+found? yes
+
+Unconnected directory inode 24 (/edir/???)
+Connect to /lost+found? yes
+
+Unconnected directory inode 27 (/edir/???)
+Connect to /lost+found? yes
+
+Pass 4: Checking reference counts
+Unattached inode 17
+Connect to /lost+found? yes
+
+Inode 17 ref count is 2, should be 1. Fix? yes
+
+Inode 18 ref count is 3, should be 2. Fix? yes
+
+Unattached inode 23
+Connect to /lost+found? yes
+
+Inode 23 ref count is 2, should be 1. Fix? yes
+
+Inode 24 ref count is 3, should be 2. Fix? yes
+
+Unattached inode 25
+Connect to /lost+found? yes
+
+Inode 25 ref count is 2, should be 1. Fix? yes
+
+Unattached inode 26
+Connect to /lost+found? yes
+
+Inode 26 ref count is 2, should be 1. Fix? yes
+
+Inode 27 ref count is 3, should be 2. Fix? yes
+
+Unattached inode 28
+Connect to /lost+found? yes
+
+Inode 28 ref count is 2, should be 1. Fix? yes
+
+Unattached inode 29
+Connect to /lost+found? yes
+
+Inode 29 ref count is 2, should be 1. Fix? yes
+
+Pass 5: Checking group summary information
+Block bitmap differences: -(25--32)
+Fix? yes
+
+Free blocks count wrong for group #0 (75, counted=83).
+Fix? yes
+
+Free blocks count wrong (75, counted=83).
+Fix? yes
+
+Inode bitmap differences: -(19--22)
+Fix? yes
+
+Free inodes count wrong for group #0 (95, counted=99).
+Fix? yes
+
+Free inodes count wrong (95, counted=99).
+Fix? yes
+
+
+test_filesys: ***** FILE SYSTEM WAS MODIFIED *****
+test_filesys: 29/128 files (0.0% non-contiguous), 45/128 blocks
+Exit status is 1
--- /dev/null
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 29/128 files (0.0% non-contiguous), 45/128 blocks
+Exit status is 0
--- /dev/null
+#!/bin/bash
+#
+# This is the script that was used to create the image.gz in this directory.
+#
+# This requires a patched version of debugfs that understands the "fscrypt."
+# xattr name prefix, so that the encryption xattrs can be manipulated.
+
+set -e -u
+umask 0022
+
+do_debugfs() {
+ umount mnt
+ debugfs -w "$@" image
+ mount image mnt
+}
+
+create_encrypted_file() {
+ local file=$1
+ local ino
+
+ echo foo > "$file"
+
+ # not needed, but makes image more compressible
+ ino=$(stat -c %i "$file")
+ do_debugfs -R "zap_block -f <$ino> 0"
+}
+
+set_encryption_xattr() {
+ local file=$1
+ local value=$2
+ local ino
+
+ ino=$(stat -c %i "$file")
+ do_debugfs -R "ea_set <$ino> fscrypt.c $value"
+}
+
+rm_encryption_xattr() {
+ local file=$1
+ local ino
+
+ ino=$(stat -c %i "$file")
+ do_debugfs -R "ea_rm <$ino> fscrypt.c"
+}
+
+clear_encrypt_flag() {
+ local file=$1
+ local ino
+
+ ino=$(stat -c %i "$file")
+ do_debugfs -R "set_inode_field <$ino> flags 0"
+}
+
+clear_encryption() {
+ local file=$1
+ local ino
+ local is_symlink=false
+
+ if [ -L "$file" ]; then
+ is_symlink=true
+ fi
+ ino=$(stat -c %i "$file")
+
+ do_debugfs -R "ea_rm <$ino> fscrypt.c"
+ do_debugfs -R "set_inode_field <$ino> flags 0"
+ if $is_symlink; then
+ do_debugfs -R "set_inode_field <$ino> block[0] 0xAAAAAAAA"
+ do_debugfs -R "set_inode_field <$ino> block[1] 0"
+ do_debugfs -R "set_inode_field <$ino> size 4"
+ fi
+}
+
+mkdir -p mnt
+umount mnt &> /dev/null || true
+
+dd if=/dev/zero of=image bs=4096 count=128
+mke2fs -O encrypt -b 4096 -N 128 image
+mount image mnt
+
+# Create an encrypted directory (ino 12)
+dir=mnt/edir
+mkdir $dir
+echo password | e4crypt add_key $dir
+
+# Control cases: valid encrypted regular file, dir, and symlink (ino 13-15)
+create_encrypted_file $dir/encrypted_file
+mkdir $dir/encrypted_dir
+ln -s target $dir/encrypted_symlink
+
+# Control case: file type that is never encrypted (ino 16)
+mkfifo $dir/fifo
+
+# Inodes with missing encryption xattr (ino 17-18).
+# e2fsck should offer to clear the encrypt flag on these inodes.
+
+create_encrypted_file $dir/missing_xattr_file
+rm_encryption_xattr $dir/missing_xattr_file
+
+mkdir $dir/missing_xattr_dir
+rm_encryption_xattr $dir/missing_xattr_dir
+
+# Inodes with corrupt encryption xattr (ino 19-22).
+# e2fsck should offer to clear these inodes.
+
+create_encrypted_file $dir/corrupt_xattr_1
+set_encryption_xattr $dir/corrupt_xattr_1 '\0'
+
+create_encrypted_file $dir/corrupt_xattr_2
+set_encryption_xattr $dir/corrupt_xattr_2 \
+ '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
+
+create_encrypted_file $dir/corrupt_xattr_3
+set_encryption_xattr $dir/corrupt_xattr_3 '\1'
+
+create_encrypted_file $dir/corrupt_xattr_4
+set_encryption_xattr $dir/corrupt_xattr_4 '\2'
+
+# Unencrypted inodes in encrypted directory (ino 23-25).
+# e2fsck should offer to clear these directory entries.
+
+create_encrypted_file $dir/unencrypted_file
+clear_encryption $dir/unencrypted_file
+
+mkdir $dir/unencrypted_dir
+clear_encryption $dir/unencrypted_dir
+
+ln -s target $dir/unencrypted_symlink
+clear_encryption $dir/unencrypted_symlink
+
+# Inodes with different encryption policy in encrypted directory (ino 26-29).
+# e2fsck should offer to clear these directory entries.
+
+xattr='\1\1\4\0AAAAAAAABBBBBBBBBBBBBBBB'
+
+create_encrypted_file $dir/inconsistent_file_1
+set_encryption_xattr $dir/inconsistent_file_1 $xattr
+
+mkdir $dir/inconsistent_dir
+set_encryption_xattr $dir/inconsistent_dir $xattr
+
+ln -s target $dir/inconsistent_symlink
+set_encryption_xattr $dir/inconsistent_symlink $xattr
+
+xattr='\2\1\4\0\0\0\0\0AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB'
+create_encrypted_file $dir/inconsistent_file_2
+set_encryption_xattr $dir/inconsistent_file_2 $xattr
+
+# Encrypted file and directory with valid v2 encryption policy (ino 30-31).
+# e2fsck shouldn't change these.
+dir2=mnt/edir2
+mkdir $dir2
+echo password | e4crypt add_key $dir2
+xattr='\2\1\4\0\0\0\0\0AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB'
+create_encrypted_file $dir2/file
+set_encryption_xattr $dir2/file $xattr
+set_encryption_xattr $dir2 $xattr
+
+# Encrypted file and directory with unrecognized encryption policy version
+# (ino 32-33). e2fsck shouldn't change these.
+dir3=mnt/edir3
+mkdir $dir3
+echo password | e4crypt add_key $dir3
+xattr='\3'
+create_encrypted_file $dir3/file
+set_encryption_xattr $dir3/file $xattr
+set_encryption_xattr $dir3 $xattr
+
+umount mnt
+rmdir mnt
+gzip -9 -f image
--- /dev/null
+missing, corrupt, and inconsistent encryption policies
Pass 5: Checking group summary information
test_filesys: ***** FILE SYSTEM WAS MODIFIED *****
-test_filesys: 13/16 files (0.0% non-contiguous), 23/100 blocks
+test_filesys: 13/16 files (0.0% non-contiguous), 24/100 blocks
Exit status is 1
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
-test_filesys: 13/16 files (0.0% non-contiguous), 23/100 blocks
+test_filesys: 13/16 files (0.0% non-contiguous), 24/100 blocks
Exit status is 0