diff options
-rw-r--r-- | e2fsck/e2fsck.c | 4 | ||||
-rw-r--r-- | e2fsck/e2fsck.h | 1 | ||||
-rw-r--r-- | e2fsck/pass1.c | 32 | ||||
-rw-r--r-- | e2fsck/pass2.c | 45 | ||||
-rw-r--r-- | e2fsck/problem.c | 10 | ||||
-rw-r--r-- | e2fsck/problem.h | 9 | ||||
-rw-r--r-- | e2fsck/rehash.c | 67 | ||||
-rw-r--r-- | lib/ext2fs/ext2_fs.h | 43 |
8 files changed, 181 insertions, 30 deletions
diff --git a/e2fsck/e2fsck.c b/e2fsck/e2fsck.c index 929bd78d..6bf68197 100644 --- a/e2fsck/e2fsck.c +++ b/e2fsck/e2fsck.c @@ -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; diff --git a/e2fsck/e2fsck.h b/e2fsck/e2fsck.h index 2d359b38..18c08a16 100644 --- a/e2fsck/e2fsck.h +++ b/e2fsck/e2fsck.h @@ -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; diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index b1469088..8ea9d20d 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -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); @@ -1889,6 +1890,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++; @@ -2219,6 +2222,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 */ @@ -2676,9 +2697,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; diff --git a/e2fsck/pass2.c b/e2fsck/pass2.c index 8b40e93d..c45c73ef 100644 --- a/e2fsck/pass2.c +++ b/e2fsck/pass2.c @@ -288,6 +288,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) { @@ -705,7 +709,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; @@ -730,7 +735,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; @@ -742,7 +748,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); @@ -932,6 +938,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; @@ -1152,6 +1160,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; @@ -1159,6 +1170,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) { @@ -1168,15 +1182,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 @@ -1209,7 +1224,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, @@ -1431,10 +1447,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) diff --git a/e2fsck/problem.c b/e2fsck/problem.c index ea9b2969..a3530f52 100644 --- a/e2fsck/problem.c +++ b/e2fsck/problem.c @@ -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 */ diff --git a/e2fsck/problem.h b/e2fsck/problem.h index 7bcecfab..e67e4cac 100644 --- a/e2fsck/problem.h +++ b/e2fsck/problem.h @@ -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 diff --git a/e2fsck/rehash.c b/e2fsck/rehash.c index 81d67c68..17ab1843 100644 --- a/e2fsck/rehash.c +++ b/e2fsck/rehash.c @@ -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; } @@ -180,11 +201,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, @@ -452,6 +476,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", @@ -480,15 +506,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; @@ -525,6 +552,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) { @@ -549,7 +581,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; @@ -577,7 +610,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; @@ -660,7 +696,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; @@ -669,7 +706,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); @@ -960,7 +997,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; } diff --git a/lib/ext2fs/ext2_fs.h b/lib/ext2fs/ext2_fs.h index c303ff44..857658d9 100644 --- a/lib/ext2fs/ext2_fs.h +++ b/lib/ext2fs/ext2_fs.h @@ -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 @@ -981,6 +982,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 */ @@ -1016,6 +1023,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. */ @@ -1055,12 +1081,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 |