Whamcloud - gitweb
AOSP: ANDROID: e2fsck: Handle casefolded encryption
authorDaniel Rosenberg <drosen@google.com>
Fri, 13 Mar 2020 23:08:52 +0000 (16:08 -0700)
committerTheodore Ts'o <tytso@mit.edu>
Thu, 28 Jan 2021 04:53:36 +0000 (23:53 -0500)
Adds support for EXT2_HASH_SIPHASH, and reading the hash from disk in
that case. We cannot compute the siphash without the key, so we must
not modify the names of any encrypted and casefolded directories,
which limits some recovery options, and we must assume the hashes
stored in dirents are correct.
This is in preparation for upcoming kernel support for encryption and
casefolding at the same time.

Google-Bug-Id: 138322712
Test: Create fs with casefold and encryption enabled via mke2fs and
      tune2fs, run fsck after creating casefolded + encrypted folder

Change-Id: Icca32d7d9dd3c7f52da03d60e4d89273cbec0a7d
From AOSP commit: 67eae926bdac1a54dbb8335731c5e1581f93e4bb

e2fsck/e2fsck.c
e2fsck/e2fsck.h
e2fsck/pass1.c
e2fsck/pass2.c
e2fsck/problem.c
e2fsck/problem.h
e2fsck/rehash.c
lib/ext2fs/ext2_fs.h

index 929bd78..6bf6819 100644 (file)
@@ -158,6 +158,10 @@ errcode_t e2fsck_reset_context(e2fsck_t ctx)
                ext2fs_u32_list_free(ctx->encrypted_dirs);
                ctx->encrypted_dirs = 0;
        }
+       if (ctx->casefolded_dirs) {
+               ext2fs_u32_list_free(ctx->casefolded_dirs);
+               ctx->casefolded_dirs = 0;
+       }
        if (ctx->inode_count) {
                ext2fs_free_icount(ctx->inode_count);
                ctx->inode_count = 0;
index 7e0895c..166cc1f 100644 (file)
@@ -390,6 +390,7 @@ struct e2fsck_struct {
        profile_t       profile;
        int blocks_per_page;
        ext2_u32_list encrypted_dirs;
+       ext2_u32_list casefolded_dirs;
 
        /* Reserve blocks for root and l+f re-creation */
        blk64_t root_repair_block, lnf_repair_block;
index 38afda4..bafbe8b 100644 (file)
@@ -79,6 +79,7 @@ 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 add_casefolded_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);
@@ -1890,6 +1891,8 @@ void e2fsck_pass1(e2fsck_t ctx)
                        ctx->fs_directory_count++;
                        if (inode->i_flags & EXT4_ENCRYPT_FL)
                                add_encrypted_dir(ctx, ino);
+                       if (inode->i_flags & EXT4_CASEFOLD_FL)
+                               add_casefolded_dir(ctx, ino);
                } else if (LINUX_S_ISREG (inode->i_mode)) {
                        ext2fs_mark_inode_bitmap2(ctx->inode_reg_map, ino);
                        ctx->fs_regular_count++;
@@ -2220,6 +2223,24 @@ error:
        ctx->flags |= E2F_FLAG_ABORT;
 }
 
+static void add_casefolded_dir(e2fsck_t ctx, ino_t ino)
+{
+       struct          problem_context pctx;
+
+       if (!ctx->casefolded_dirs) {
+               pctx.errcode = ext2fs_u32_list_create(&ctx->casefolded_dirs, 0);
+               if (pctx.errcode)
+                       goto error;
+       }
+       pctx.errcode = ext2fs_u32_list_add(ctx->casefolded_dirs, ino);
+       if (pctx.errcode == 0)
+               return;
+error:
+       fix_problem(ctx, PR_1_ALLOCATE_CASEFOLDED_DIRLIST, &pctx);
+       /* Should never get here */
+       ctx->flags |= E2F_FLAG_ABORT;
+}
+
 /*
  * This procedure will allocate the inode "bb" (badblock) map table
  */
@@ -2677,9 +2698,20 @@ static int handle_htree(e2fsck_t ctx, struct problem_context *pctx,
        if ((root->hash_version != EXT2_HASH_LEGACY) &&
            (root->hash_version != EXT2_HASH_HALF_MD4) &&
            (root->hash_version != EXT2_HASH_TEA) &&
+           (root->hash_version != EXT2_HASH_SIPHASH) &&
            fix_problem(ctx, PR_1_HTREE_HASHV, pctx))
                return 1;
 
+       if (ext4_hash_in_dirent(inode)) {
+               if (root->hash_version != EXT2_HASH_SIPHASH &&
+                   fix_problem(ctx, PR_1_HTREE_NEEDS_SIPHASH, pctx))
+                       return 1;
+       } else {
+               if (root->hash_version == EXT2_HASH_SIPHASH &&
+                  fix_problem(ctx, PR_1_HTREE_CANNOT_SIPHASH, pctx))
+                       return 1;
+       }
+
        if ((root->unused_flags & EXT2_HASH_FLAG_INCOMPAT) &&
            fix_problem(ctx, PR_1_HTREE_INCOMPAT, pctx))
                return 1;
index cb1e587..c1db3a1 100644 (file)
@@ -289,6 +289,10 @@ void e2fsck_pass2(e2fsck_t ctx)
                ext2fs_u32_list_free(ctx->encrypted_dirs);
                ctx->encrypted_dirs = 0;
        }
+       if (ctx->casefolded_dirs) {
+               ext2fs_u32_list_free(ctx->casefolded_dirs);
+               ctx->casefolded_dirs = 0;
+       }
 
        clear_problem_context(&pctx);
        if (ctx->large_files) {
@@ -706,7 +710,8 @@ static void salvage_directory(ext2_filsys fs,
                              struct ext2_dir_entry *dirent,
                              struct ext2_dir_entry *prev,
                              unsigned int *offset,
-                             unsigned int block_len)
+                             unsigned int block_len,
+                             int hash_in_dirent)
 {
        char    *cp = (char *) dirent;
        int left;
@@ -731,7 +736,8 @@ static void salvage_directory(ext2_filsys fs,
         * Special case of directory entry of size 8: copy what's left
         * of the directory block up to cover up the invalid hole.
         */
-       if ((left >= 12) && (rec_len == EXT2_DIR_ENTRY_HEADER_LEN)) {
+       if ((left >= ext2fs_dir_rec_len(1, hash_in_dirent)) &&
+            (rec_len == EXT2_DIR_ENTRY_HEADER_LEN)) {
                memmove(cp, cp+EXT2_DIR_ENTRY_HEADER_LEN, left);
                memset(cp + left, 0, EXT2_DIR_ENTRY_HEADER_LEN);
                return;
@@ -743,7 +749,7 @@ static void salvage_directory(ext2_filsys fs,
         */
        if ((left < 0) &&
            ((int) rec_len + left > EXT2_DIR_ENTRY_HEADER_LEN) &&
-           ((int) name_len + EXT2_DIR_ENTRY_HEADER_LEN <= (int) rec_len + left) &&
+           ((int) ext2fs_dir_rec_len(name_len, hash_in_dirent) <= (int) rec_len + left) &&
            dirent->inode <= fs->super->s_inodes_count &&
            strnlen(dirent->name, name_len) == name_len) {
                (void) ext2fs_set_rec_len(fs, (int) rec_len + left, dirent);
@@ -933,6 +939,8 @@ static int check_dir_block(ext2_filsys fs,
        size_t  inline_data_size = 0;
        int     filetype = 0;
        int     encrypted = 0;
+       int     hash_in_dirent = 0;
+       int     casefolded = 0;
        size_t  max_block_size;
        int     hash_flags = 0;
        static char *eop_read_dirblock = NULL;
@@ -1156,6 +1164,9 @@ skip_checksum:
 
        if (ctx->encrypted_dirs)
                encrypted = ext2fs_u32_list_test(ctx->encrypted_dirs, ino);
+       if (ctx->casefolded_dirs)
+               casefolded = ext2fs_u32_list_test(ctx->casefolded_dirs, ino);
+       hash_in_dirent = encrypted && casefolded;
 
        dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp);
        prev = 0;
@@ -1163,6 +1174,9 @@ skip_checksum:
                dgrp_t group;
                ext2_ino_t first_unused_inode;
                unsigned int name_len;
+               /* csum entry is not checked here, so don't worry about it */
+               int extended = (dot_state > 1) && hash_in_dirent;
+               int min_dir_len = ext2fs_dir_rec_len(1, extended);
 
                problem = 0;
                if (!inline_data_size || dot_state > 1) {
@@ -1172,15 +1186,16 @@ skip_checksum:
                         * force salvaging this dir.
                         */
                        if (max_block_size - offset < EXT2_DIR_ENTRY_HEADER_LEN)
-                               rec_len = EXT2_DIR_REC_LEN(1);
+                               rec_len = ext2fs_dir_rec_len(1, extended);
                        else
                                (void) ext2fs_get_rec_len(fs, dirent, &rec_len);
                        cd->pctx.dirent = dirent;
                        cd->pctx.num = offset;
                        if ((offset + rec_len > max_block_size) ||
-                           (rec_len < 12) ||
+                           (rec_len < min_dir_len) ||
                            ((rec_len % 4) != 0) ||
-                           (((unsigned) ext2fs_dirent_name_len(dirent) + EXT2_DIR_ENTRY_HEADER_LEN) > rec_len)) {
+                           ((ext2fs_dir_rec_len(ext2fs_dirent_name_len(dirent),
+                                                extended)) > rec_len)) {
                                if (fix_problem(ctx, PR_2_DIR_CORRUPTED,
                                                &cd->pctx)) {
 #ifdef WORDS_BIGENDIAN
@@ -1213,7 +1228,8 @@ skip_checksum:
 #endif
                                        salvage_directory(fs, dirent, prev,
                                                          &offset,
-                                                         max_block_size);
+                                                         max_block_size,
+                                                         hash_in_dirent);
 #ifdef WORDS_BIGENDIAN
                                        if (need_reswab) {
                                                (void) ext2fs_get_rec_len(fs,
@@ -1435,10 +1451,17 @@ skip_checksum:
                        if (dx_dir->casefolded_hash)
                                hash_flags = EXT4_CASEFOLD_FL;
 
-                       ext2fs_dirhash2(dx_dir->hashversion, dirent->name,
-                                       ext2fs_dirent_name_len(dirent),
-                                       fs->encoding, hash_flags,
-                                       fs->super->s_hash_seed, &hash, 0);
+                       if (dx_dir->hashversion == EXT2_HASH_SIPHASH) {
+                               if (dot_state > 1)
+                                       hash = EXT2_DIRENT_HASH(dirent);
+                       } else {
+                               ext2fs_dirhash2(dx_dir->hashversion,
+                                               dirent->name,
+                                               ext2fs_dirent_name_len(dirent),
+                                               fs->encoding, hash_flags,
+                                               fs->super->s_hash_seed,
+                                               &hash, 0);
+                       }
                        if (hash < dx_db->min_hash)
                                dx_db->min_hash = hash;
                        if (hash > dx_db->max_hash)
index f8882a5..da261fc 100644 (file)
@@ -1253,6 +1253,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 },
 
+       /* Htree directory should use SipHash but does not */
+       { PR_1_HTREE_NEEDS_SIPHASH,
+         N_("@h %i uses hash version (%N), but should use SipHash (6) \n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK, 0, 0, 0 },
+
+       /* Htree directory uses SipHash but should not */
+       { PR_1_HTREE_CANNOT_SIPHASH,
+         N_("@h %i uses SipHash, but should not.  "),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK, 0, 0, 0 },
+
        /* Pass 1b errors */
 
        /* Pass 1B: Rescan for duplicate/bad blocks */
index 7e144ca..5e67b17 100644 (file)
@@ -701,6 +701,15 @@ struct problem_context {
 /* Casefold flag set, but file system is missing the casefold feature */
 #define PR_1_CASEFOLD_FEATURE                  0x010089
 
+/* Error allocating memory for casefolded directory list */
+#define PR_1_ALLOCATE_CASEFOLDED_DIRLIST       0x01008C
+
+/* Htree directory should use SipHash but does not */
+#define PR_1_HTREE_NEEDS_SIPHASH               0x01008D
+
+/* Htree directory uses SipHash but should not */
+#define PR_1_HTREE_CANNOT_SIPHASH              0x01008E
+
 
 /*
  * Pass 1b errors
index 0d218e8..57b5ad4 100644 (file)
@@ -101,6 +101,21 @@ struct out_dir {
        ext2_dirhash_t  *hashes;
 };
 
+#define DOTDOT_OFFSET 12
+
+static int is_fake_entry(ext2_filsys fs, int lblk, unsigned int offset)
+{
+       /* Entries in the first block before this value refer to . or .. */
+       if (lblk == 0 && offset <= DOTDOT_OFFSET)
+               return 1;
+       /* Check if this is likely the csum entry */
+       if (ext2fs_has_feature_metadata_csum(fs->super) &&
+           (offset & (fs->blocksize - 1)) ==
+                           fs->blocksize - sizeof(struct ext2_dir_entry_tail))
+               return 1;
+       return 0;
+}
+
 static int fill_dir_block(ext2_filsys fs,
                          blk64_t *block_nr,
                          e2_blkcnt_t blockcnt,
@@ -113,7 +128,7 @@ static int fill_dir_block(ext2_filsys fs,
        struct ext2_dir_entry   *dirent;
        char                    *dir;
        unsigned int            offset, dir_offset, rec_len, name_len;
-       int                     hash_alg, hash_flags;
+       int                     hash_alg, hash_flags, hash_in_entry;
 
        if (blockcnt < 0)
                return 0;
@@ -140,6 +155,7 @@ static int fill_dir_block(ext2_filsys fs,
                        return BLOCK_ABORT;
        }
        hash_flags = fd->inode->i_flags & EXT4_CASEFOLD_FL;
+       hash_in_entry = ext4_hash_in_dirent(fd->inode);
        hash_alg = fs->super->s_def_hash_version;
        if ((hash_alg <= EXT2_HASH_TEA) &&
            (fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH))
@@ -147,13 +163,18 @@ static int fill_dir_block(ext2_filsys fs,
        /* While the directory block is "hot", index it. */
        dir_offset = 0;
        while (dir_offset < fs->blocksize) {
+               int min_rec = EXT2_DIR_ENTRY_HEADER_LEN;
+               int extended = hash_in_entry && !is_fake_entry(fs, blockcnt, dir_offset);
+
+               if (extended)
+                       min_rec += EXT2_DIR_ENTRY_HASH_LEN;
                dirent = (struct ext2_dir_entry *) (dir + dir_offset);
                (void) ext2fs_get_rec_len(fs, dirent, &rec_len);
                name_len = ext2fs_dirent_name_len(dirent);
                if (((dir_offset + rec_len) > fs->blocksize) ||
-                   (rec_len < 8) ||
+                   (rec_len < min_rec) ||
                    ((rec_len % 4) != 0) ||
-                   (name_len + 8 > rec_len)) {
+                   (name_len + min_rec > rec_len)) {
                        fd->err = EXT2_ET_DIR_CORRUPTED;
                        return BLOCK_ABORT;
                }
@@ -187,11 +208,14 @@ static int fill_dir_block(ext2_filsys fs,
                }
                ent = fd->harray + fd->num_array++;
                ent->dir = dirent;
-               fd->dir_size += EXT2_DIR_REC_LEN(name_len);
+               fd->dir_size += ext2fs_dir_rec_len(name_len, extended);
                ent->ino = dirent->inode;
-               if (fd->compress)
+               if (extended) {
+                       ent->hash = EXT2_DIRENT_HASH(dirent);
+                       ent->minor_hash = EXT2_DIRENT_MINOR_HASH(dirent);
+               } else if (fd->compress) {
                        ent->hash = ent->minor_hash = 0;
-               else {
+               else {
                        fd->err = ext2fs_dirhash2(hash_alg,
                                                  dirent->name, name_len,
                                                  fs->encoding, hash_flags,
@@ -473,6 +497,8 @@ static errcode_t copy_dir_entries(e2fsck_t ctx,
        ext2_dirhash_t          prev_hash;
        int                     csum_size = 0;
        struct                  ext2_dir_entry_tail *t;
+       int hash_in_entry = ext4_hash_in_dirent(fd->inode);
+       int min_rec_len = ext2fs_dir_rec_len(1, hash_in_entry);
 
        if (ctx->htree_slack_percentage == 255) {
                profile_get_uint(ctx->profile, "options",
@@ -501,15 +527,16 @@ static errcode_t copy_dir_entries(e2fsck_t ctx,
        prev_rec_len = 0;
        rec_len = 0;
        left = fs->blocksize - csum_size;
-       slack = fd->compress ? 12 :
+       slack = fd->compress ? min_rec_len :
                ((fs->blocksize - csum_size) * ctx->htree_slack_percentage)/100;
-       if (slack < 12)
-               slack = 12;
+       if (slack < min_rec_len)
+               slack = min_rec_len;
        for (i = 0; i < fd->num_array; i++) {
                ent = fd->harray + i;
                if (ent->dir->inode == 0)
                        continue;
-               rec_len = EXT2_DIR_REC_LEN(ext2fs_dirent_name_len(ent->dir));
+               rec_len = ext2fs_dir_rec_len(ext2fs_dirent_name_len(ent->dir),
+                                            hash_in_entry);
                if (rec_len > left) {
                        if (left) {
                                left += prev_rec_len;
@@ -546,6 +573,11 @@ static errcode_t copy_dir_entries(e2fsck_t ctx,
                prev_rec_len = rec_len;
                memcpy(dirent->name, ent->dir->name,
                       ext2fs_dirent_name_len(dirent));
+               if (hash_in_entry) {
+                       EXT2_DIRENT_HASHES(dirent)->hash = ext2fs_cpu_to_le32(ent->hash);
+                       EXT2_DIRENT_HASHES(dirent)->minor_hash =
+                                                       ext2fs_cpu_to_le32(ent->minor_hash);
+               }
                offset += rec_len;
                left -= rec_len;
                if (left < slack) {
@@ -570,7 +602,8 @@ static errcode_t copy_dir_entries(e2fsck_t ctx,
 
 
 static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf,
-                                   ext2_ino_t ino, ext2_ino_t parent)
+                                   ext2_ino_t ino, ext2_ino_t parent,
+                                   struct ext2_inode *inode)
 {
        struct ext2_dir_entry           *dir;
        struct ext2_dx_root_info        *root;
@@ -598,7 +631,10 @@ static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf,
 
        root = (struct ext2_dx_root_info *) (buf+24);
        root->reserved_zero = 0;
-       root->hash_version = fs->super->s_def_hash_version;
+       if (ext4_hash_in_dirent(inode))
+               root->hash_version = EXT2_HASH_SIPHASH;
+       else
+               root->hash_version = fs->super->s_def_hash_version;
        root->info_length = 8;
        root->indirect_levels = 0;
        root->unused_flags = 0;
@@ -684,7 +720,8 @@ static int alloc_blocks(ext2_filsys fs,
 static errcode_t calculate_tree(ext2_filsys fs,
                                struct out_dir *outdir,
                                ext2_ino_t ino,
-                               ext2_ino_t parent)
+                               ext2_ino_t parent,
+                               struct ext2_inode *inode)
 {
        struct ext2_dx_root_info        *root_info;
        struct ext2_dx_entry            *root, *int_ent, *dx_ent = 0;
@@ -693,7 +730,7 @@ static errcode_t calculate_tree(ext2_filsys fs,
        int                             i, c1, c2, c3, nblks;
        int                             limit_offset, int_offset, root_offset;
 
-       root_info = set_root_node(fs, outdir->buf, ino, parent);
+       root_info = set_root_node(fs, outdir->buf, ino, parent, inode);
        root_offset = limit_offset = ((char *) root_info - outdir->buf) +
                root_info->info_length;
        root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset);
@@ -992,7 +1029,7 @@ resort:
 
        if (!fd.compress) {
                /* Calculate the interior nodes */
-               retval = calculate_tree(fs, &outdir, ino, fd.parent);
+               retval = calculate_tree(fs, &outdir, ino, fd.parent, fd.inode);
                if (retval)
                        goto errout;
        }
index 475ecf9..905eb32 100644 (file)
@@ -238,6 +238,7 @@ struct ext2_dx_root_info {
 #define EXT2_HASH_LEGACY_UNSIGNED      3 /* reserved for userspace lib */
 #define EXT2_HASH_HALF_MD4_UNSIGNED    4 /* reserved for userspace lib */
 #define EXT2_HASH_TEA_UNSIGNED         5 /* reserved for userspace lib */
+#define EXT2_HASH_SIPHASH              6
 
 #define EXT2_HASH_FLAG_INCOMPAT        0x1
 
@@ -983,6 +984,12 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold,              4, CASEFOLD)
 #define EXT4_DEFM_DISCARD      0x0400
 #define EXT4_DEFM_NODELALLOC   0x0800
 
+static inline int ext4_hash_in_dirent(const struct ext2_inode *inode)
+{
+       return (inode->i_flags & EXT4_ENCRYPT_FL) &&
+               (inode->i_flags & EXT4_CASEFOLD_FL);
+}
+
 /*
  * Structure of a directory entry
  */
@@ -1018,6 +1025,25 @@ struct ext2_dir_entry_2 {
 };
 
 /*
+ * Hashes for ext4_dir_entry for casefolded and ecrypted directories.
+ * This is located at the first 4 bit aligned location after the name.
+ */
+
+struct ext2_dir_entry_hash {
+       __le32 hash;
+       __le32 minor_hash;
+};
+
+#define EXT2_DIRENT_HASHES(entry) \
+       ((struct ext2_dir_entry_hash *) &entry->name[\
+               (ext2fs_dirent_name_len(entry) + \
+                       EXT2_DIR_ROUND) & ~EXT2_DIR_ROUND])
+#define EXT2_DIRENT_HASH(entry) \
+               ext2fs_le32_to_cpu(EXT2_DIRENT_HASHES(entry)->hash)
+#define EXT2_DIRENT_MINOR_HASH(entry) \
+               ext2fs_le32_to_cpu(EXT2_DIRENT_HASHES(entry)->minor_hash)
+
+/*
  * This is a bogus directory entry at the end of each leaf block that
  * records checksums.
  */
@@ -1057,12 +1083,21 @@ struct ext2_dir_entry_tail {
  * NOTE: It must be a multiple of 4
  */
 #define EXT2_DIR_ENTRY_HEADER_LEN      8
+#define EXT2_DIR_ENTRY_HASH_LEN                8
 #define EXT2_DIR_PAD                   4
 #define EXT2_DIR_ROUND                 (EXT2_DIR_PAD - 1)
-#define EXT2_DIR_REC_LEN(name_len)     (((name_len) + \
-                                         EXT2_DIR_ENTRY_HEADER_LEN + \
-                                         EXT2_DIR_ROUND) & \
-                                        ~EXT2_DIR_ROUND)
+#define EXT2_DIR_REC_LEN(name_len) ext2fs_dir_rec_len(name_len, 0)
+
+static inline unsigned int ext2fs_dir_rec_len(__u8 name_len,
+                                               int extended)
+{
+       int rec_len = (name_len + EXT2_DIR_ENTRY_HEADER_LEN + EXT2_DIR_ROUND);
+
+       rec_len &= ~EXT2_DIR_ROUND;
+       if (extended)
+               rec_len += EXT2_DIR_ENTRY_HASH_LEN;
+       return rec_len;
+}
 
 /*
  * Constants for ext4's extended time encoding