@@ -34,6 +34,7 @@ cc_defaults {
"sigcatcher.c",
"readahead.c",
"extents.c",
+ "encrypted_files.c",
],
cflags: [
"-Wno-sign-compare",
@@ -63,7 +63,7 @@ OBJS= unix.o e2fsck.o super.o pass1.o pass1b.o pass2.o \
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 \
@@ -74,7 +74,8 @@ PROFILED_OBJS= profiled/unix.o profiled/e2fsck.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 \
@@ -103,6 +104,7 @@ SRCS= $(srcdir)/e2fsck.c \
$(srcdir)/logfile.c \
$(srcdir)/quota.c \
$(srcdir)/extents.c \
+ $(srcdir)/encrypted_files.c \
$(MTRACE_SRC)
all:: profiled $(PROGS) e2fsck $(MANPAGES) $(FMANPAGES)
@@ -572,3 +574,15 @@ extents.o: $(srcdir)/extents.c $(top_builddir)/lib/config.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
+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
@@ -138,6 +138,7 @@ errcode_t e2fsck_reset_context(e2fsck_t ctx)
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
@@ -154,10 +155,6 @@ errcode_t e2fsck_reset_context(e2fsck_t ctx)
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;
@@ -135,6 +135,8 @@ struct dx_dirblock_info {
#define DX_FLAG_FIRST 4
#define DX_FLAG_LAST 8
+struct encrypted_file_info;
+
#define RESOURCE_TRACK
#ifdef RESOURCE_TRACK
@@ -328,6 +330,11 @@ struct e2fsck_struct {
*/
ext2_u32_list dirs_to_hash;
+ /*
+ * Encrypted file information
+ */
+ struct encrypted_file_info *encrypted_files;
+
/*
* Tuning parameters
*/
@@ -390,7 +397,6 @@ struct e2fsck_struct {
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;
@@ -505,8 +511,20 @@ extern ea_key_t ea_refcount_intr_next(ext2_refcount_t refcount,
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);
new file mode 100644
@@ -0,0 +1,368 @@
+/*
+ * 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().
+ *
+ * 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 FS_ENCRYPTION_CONTEXT_FORMAT_V1 1
+#define FS_KEY_DESCRIPTOR_SIZE 8
+#define FS_KEY_DERIVATION_NONCE_SIZE 16
+
+/* On-disk format of encryption xattr (v1) */
+struct fscrypt_context {
+ __u8 format;
+ __u8 contents_encryption_mode;
+ __u8 filenames_encryption_mode;
+ __u8 flags;
+ __u8 master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
+ __u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE];
+};
+
+/* The encryption "policy" is the fscrypt_context excluding the nonce. */
+struct fscrypt_policy {
+ __u8 version;
+ __u8 contents_encryption_mode;
+ __u8 filenames_encryption_mode;
+ __u8 flags;
+ __u8 master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
+};
+
+/* 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 {
+ struct fscrypt_policy policy;
+ __u32 policy_id;
+ struct rb_node node;
+};
+
+/* 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;
+}
+
+/*
+ * 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;
+ const struct fscrypt_context *crypt_ctx;
+ struct 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 a struct fscrypt_policy, if possible. */
+ policy_id = CORRUPT_ENCRYPTION_POLICY;
+ if (xattr_size < 1)
+ goto out_free_xattr;
+ crypt_ctx = xattr;
+ if (crypt_ctx->format == 0)
+ goto out_free_xattr;
+ if (crypt_ctx->format != FS_ENCRYPTION_CONTEXT_FORMAT_V1) {
+ policy_id = UNRECOGNIZED_ENCRYPTION_POLICY;
+ goto out_free_xattr;
+ }
+ if (xattr_size != sizeof(*crypt_ctx))
+ goto out_free_xattr;
+ policy.version = crypt_ctx->format;
+ policy.contents_encryption_mode = crypt_ctx->contents_encryption_mode;
+ policy.filenames_encryption_mode = crypt_ctx->filenames_encryption_mode;
+ policy.flags = crypt_ctx->flags;
+ memcpy(policy.master_key_descriptor, crypt_ctx->master_key_descriptor,
+ FS_KEY_DESCRIPTOR_SIZE);
+
+ /* Check if the policy was already seen. */
+ while (*new) {
+ int res;
+
+ parent = *new;
+ entry = ext2fs_rb_entry(parent, struct policy_map_entry, node);
+ res = memcmp(&policy, &entry->policy, sizeof(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;
+ retval = 0;
+ goto out_free_xattr;
+ }
+ }
+
+ /* First time seeing this policy. Allocate a new policy ID. */
+ retval = ext2fs_get_mem(sizeof(*entry), &entry);
+ if (retval)
+ goto out_free_xattr;
+ 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_free_xattr:
+ ext2fs_free_mem(&xattr);
+ *policy_id_ret = policy_id;
+ return retval;
+}
+
+static int handle_nomem(e2fsck_t ctx, struct problem_context *pctx)
+{
+ fix_problem(ctx, PR_1_ALLOCATE_ENCRYPTED_DIRLIST, 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);
+
+ 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);
+ 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);
+ 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;
+ }
+}
@@ -27,6 +27,7 @@
* - 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
@@ -78,7 +79,6 @@ static void mark_table_blocks(e2fsck_t ctx);
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);
@@ -1883,12 +1883,14 @@ void e2fsck_pass1(e2fsck_t ctx)
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++;
@@ -2017,6 +2019,9 @@ void e2fsck_pass1(e2fsck_t ctx)
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);
@@ -2201,24 +2206,6 @@ static void mark_inode_bad(e2fsck_t ctx, ino_t ino)
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
*/
@@ -35,10 +35,12 @@
* - 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() */
@@ -284,10 +286,7 @@ void e2fsck_pass2(e2fsck_t ctx)
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) {
@@ -504,14 +503,12 @@ static int check_name(e2fsck_t ctx,
}
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;
@@ -877,6 +874,71 @@ err:
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)
@@ -931,7 +993,7 @@ static int check_dir_block(ext2_filsys fs,
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;
@@ -1150,8 +1212,7 @@ skip_checksum:
} 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;
@@ -1415,18 +1476,25 @@ skip_checksum:
}
}
- 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;
@@ -1259,6 +1259,16 @@ static struct e2fsck_problem problem_table[] = {
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_FIX, 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 */
@@ -1785,6 +1795,16 @@ static struct e2fsck_problem problem_table[] = {
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 */
@@ -701,6 +701,11 @@ struct problem_context {
/* 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
@@ -1017,6 +1022,12 @@ struct problem_context {
/* 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
*/
@@ -5,6 +5,7 @@ e2fsck/e2fsck.c
e2fsck/ea_refcount.c
e2fsck/ehandler.c
e2fsck/emptydir.c
+e2fsck/encrypted_files.c
e2fsck/extend.c
e2fsck/extents.c
e2fsck/flushb.c
new file mode 100644
@@ -0,0 +1,115 @@
+Pass 1: Checking inodes, blocks, and sizes
+Inode 17 has encrypt flag but no encryption extended attribute.
+Fix? yes
+
+Inode 18 has encrypt flag but no encryption extended attribute.
+Fix? yes
+
+Encrypted inode 19 has corrupt encryption extended attribute.
+Clear inode? yes
+
+Encrypted inode 20 has corrupt encryption extended attribute.
+Clear inode? yes
+
+Pass 2: Checking directory structure
+Encrypted entry '^?M-}^]M-^NSM-^SM-^LM-^RM-AM-{M-=M-TM-^MM-UM-VM-UM-^^^FEM-e' in /edir (12) references unencrypted inode 17.
+Clear? yes
+
+Encrypted entry '>/M-^ZM-z:M-B1M-8^B7M-NM-^MsM-CajM-m^KM-.M-+' in /edir (12) references unencrypted inode 18.
+Clear? yes
+
+Entry 'M-<M-^GM-rM-^N^OM-^R-M-l^SwG^M-^_y^GM-*' in /edir (12) has deleted/unused inode 19. Clear? yes
+
+Entry 'MM- M-FNM-^^/M-a^F^BM-!IM-]CM-VM-G^W' in /edir (12) has deleted/unused inode 20. Clear? yes
+
+Encrypted entry 'M-^DM-xM-^K2M-vM-c^E3vM-~^ZM-/M-iM-sq^P' in /edir (12) references unencrypted inode 21.
+Clear? yes
+
+Encrypted entry 'M-8^RU)VM-wM-}<M-^EM-P^HM-@2\4M-)' in /edir (12) references unencrypted inode 22.
+Clear? yes
+
+Encrypted entry 'n^VcM-`e^\M-=M-M[-M-xM-SM-gM-^AM-YM-N,M-8M-^CM-T' in /edir (12) references unencrypted inode 23.
+Clear? yes
+
+Encrypted entry 'M-CM-];:^Q^W^CM->M-g^_M-_M-(M-^LF^N^PM-^D'T^Q' in /edir (12) references inode 24, which has a different encryption policy.
+Clear? yes
+
+Encrypted entry 'M-#@<M-)chM-rM-<Y]M-^MM-(M->dM-^NM-z' in /edir (12) references inode 25, which has a different encryption policy.
+Clear? yes
+
+Encrypted entry 'VM-^_M-6M-gM-jM-6M-^JM-^OM-r]M-X^I^RM-AyM-^^DM-^_M-QR' in /edir (12) references inode 26, which has a different encryption policy.
+Clear? yes
+
+Encrypted entry 'f#M-^SUM-^IM-u8M-_M-BW^YM-^UBM-#$;M-^D'T^Q' in /edir (12) references inode 27, 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 22 (/edir/???)
+Connect to /lost+found? yes
+
+Unconnected directory inode 25 (/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 21
+Connect to /lost+found? yes
+
+Inode 21 ref count is 2, should be 1. Fix? yes
+
+Inode 22 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
+
+Unattached inode 24
+Connect to /lost+found? yes
+
+Inode 24 ref count is 2, should be 1. Fix? yes
+
+Inode 25 ref count is 3, should be 2. Fix? yes
+
+Unattached inode 26
+Connect to /lost+found? yes
+
+Inode 26 ref count is 2, should be 1. Fix? yes
+
+Unattached inode 27
+Connect to /lost+found? yes
+
+Inode 27 ref count is 2, should be 1. Fix? yes
+
+Pass 5: Checking group summary information
+Block bitmap differences: -(25--28)
+Fix? yes
+
+Free blocks count wrong for group #0 (87, counted=91).
+Fix? yes
+
+Free blocks count wrong (87, counted=91).
+Fix? yes
+
+Inode bitmap differences: -(19--20)
+Fix? yes
+
+Free inodes count wrong for group #0 (101, counted=103).
+Fix? yes
+
+Free inodes count wrong (101, counted=103).
+Fix? yes
+
+
+test_filesys: ***** FILE SYSTEM WAS MODIFIED *****
+test_filesys: 25/128 files (0.0% non-contiguous), 37/128 blocks
+Exit status is 1
new file mode 100644
@@ -0,0 +1,7 @@
+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: 25/128 files (0.0% non-contiguous), 37/128 blocks
+Exit status is 0
new file mode 100644
GIT binary patch
literal 2009
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
BcYpu@
literal 0
HcmV?d00001
new file mode 100755
@@ -0,0 +1,143 @@
+#!/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-20).
+# 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'
+
+# Unencrypted inodes in encrypted directory (ino 21-23).
+# 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 24-27).
+# e2fsck should offer to clear these directory entries.
+
+xattr='\1\1\4\0AAAAAAAAAAAAAAAAAAAAAAAA'
+
+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='\2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+create_encrypted_file $dir/inconsistent_file_2
+set_encryption_xattr $dir/inconsistent_file_2 $xattr
+
+umount mnt
+rmdir mnt
+gzip -9 -f image
new file mode 100644
@@ -0,0 +1 @@
+missing, corrupt, and inconsistent encryption policies
@@ -13,5 +13,5 @@ Inode 13 ref count is 2, should be 1. Fix? yes
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
@@ -3,5 +3,5 @@ Pass 2: Checking directory structure
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