diff options
Diffstat (limited to 'e2fsck/journal.c')
-rw-r--r-- | e2fsck/journal.c | 227 |
1 files changed, 131 insertions, 96 deletions
diff --git a/e2fsck/journal.c b/e2fsck/journal.c index 0aeaf416..8ae89bf7 100644 --- a/e2fsck/journal.c +++ b/e2fsck/journal.c @@ -281,11 +281,11 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh, e2fsck_t ctx = j->j_fs_dev->k_ctx; struct e2fsck_fc_replay_state *state; int ret = JBD2_FC_REPLAY_CONTINUE; - struct ext4_fc_add_range *ext; - struct ext4_fc_tl *tl; - struct ext4_fc_tail *tail; - __u8 *start, *end; - struct ext4_fc_head *head; + struct ext4_fc_add_range ext; + struct ext4_fc_tl tl; + struct ext4_fc_tail tail; + __u8 *start, *cur, *end, *val; + struct ext4_fc_head head; struct ext2fs_extent ext2fs_ex = {0}; state = &ctx->fc_replay_state; @@ -310,14 +310,18 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh, } state->fc_replay_expected_off++; - fc_for_each_tl(start, end, tl) { + for (cur = start; cur < end; cur = cur + le16_to_cpu(tl.fc_len) + sizeof(tl)) { + memcpy(&tl, cur, sizeof(tl)); + val = cur + sizeof(tl); + jbd_debug(3, "Scan phase, tag:%s, blk %lld\n", - tag2str(le16_to_cpu(tl->fc_tag)), bh->b_blocknr); - switch (le16_to_cpu(tl->fc_tag)) { + tag2str(le16_to_cpu(tl.fc_tag)), bh->b_blocknr); + switch (le16_to_cpu(tl.fc_tag)) { case EXT4_FC_TAG_ADD_RANGE: - ext = (struct ext4_fc_add_range *)ext4_fc_tag_val(tl); - ret = ext2fs_decode_extent(&ext2fs_ex, (void *)&ext->fc_ex, - sizeof(ext->fc_ex)); + memcpy(&ext, val, sizeof(ext)); + ret = ext2fs_decode_extent(&ext2fs_ex, + (void *)&ext.fc_ex, + sizeof(ext.fc_ex)); if (ret) ret = JBD2_FC_REPLAY_STOP; else @@ -330,21 +334,20 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh, case EXT4_FC_TAG_INODE: case EXT4_FC_TAG_PAD: state->fc_cur_tag++; - state->fc_crc = jbd2_chksum(j, state->fc_crc, tl, - sizeof(*tl) + ext4_fc_tag_len(tl)); + state->fc_crc = jbd2_chksum(j, state->fc_crc, cur, + sizeof(tl) + ext4_fc_tag_len(&tl)); break; case EXT4_FC_TAG_TAIL: state->fc_cur_tag++; - tail = (struct ext4_fc_tail *)ext4_fc_tag_val(tl); - state->fc_crc = jbd2_chksum(j, state->fc_crc, tl, - sizeof(*tl) + + memcpy(&tail, val, sizeof(tail)); + state->fc_crc = jbd2_chksum(j, state->fc_crc, cur, + sizeof(tl) + offsetof(struct ext4_fc_tail, fc_crc)); jbd_debug(1, "tail tid %d, expected %d\n", - le32_to_cpu(tail->fc_tid), - expected_tid); - if (le32_to_cpu(tail->fc_tid) == expected_tid && - le32_to_cpu(tail->fc_crc) == state->fc_crc) { + le32_to_cpu(tail.fc_tid), expected_tid); + if (le32_to_cpu(tail.fc_tid) == expected_tid && + le32_to_cpu(tail.fc_crc) == state->fc_crc) { state->fc_replay_num_tags = state->fc_cur_tag; } else { ret = state->fc_replay_num_tags ? @@ -353,19 +356,19 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh, state->fc_crc = 0; break; case EXT4_FC_TAG_HEAD: - head = (struct ext4_fc_head *)ext4_fc_tag_val(tl); - if (le32_to_cpu(head->fc_features) & - ~EXT4_FC_SUPPORTED_FEATURES) { + memcpy(&head, val, sizeof(head)); + if (le32_to_cpu(head.fc_features) & + ~EXT4_FC_SUPPORTED_FEATURES) { ret = -EOPNOTSUPP; break; } - if (le32_to_cpu(head->fc_tid) != expected_tid) { + if (le32_to_cpu(head.fc_tid) != expected_tid) { ret = -EINVAL; break; } state->fc_cur_tag++; - state->fc_crc = jbd2_chksum(j, state->fc_crc, tl, - sizeof(*tl) + ext4_fc_tag_len(tl)); + state->fc_crc = jbd2_chksum(j, state->fc_crc, cur, + sizeof(tl) + ext4_fc_tag_len(&tl)); break; default: ret = state->fc_replay_num_tags ? @@ -416,8 +419,8 @@ static int make_room(struct extent_list *list, int i) static int ex_compar(const void *arg1, const void *arg2) { - struct ext2fs_extent *ex1 = (struct ext2fs_extent *)arg1; - struct ext2fs_extent *ex2 = (struct ext2fs_extent *)arg2; + const struct ext2fs_extent *ex1 = (const struct ext2fs_extent *)arg1; + const struct ext2fs_extent *ex2 = (const struct ext2fs_extent *)arg2; if (ex1->e_lblk < ex2->e_lblk) return -1; @@ -428,8 +431,8 @@ static int ex_compar(const void *arg1, const void *arg2) static int ex_len_compar(const void *arg1, const void *arg2) { - struct ext2fs_extent *ex1 = (struct ext2fs_extent *)arg1; - struct ext2fs_extent *ex2 = (struct ext2fs_extent *)arg2; + const struct ext2fs_extent *ex1 = (const struct ext2fs_extent *)arg1; + const struct ext2fs_extent *ex2 = (const struct ext2fs_extent *)arg2; if (ex1->e_len < ex2->e_len) return 1; @@ -442,7 +445,7 @@ static int ex_len_compar(const void *arg1, const void *arg2) static void ex_sort_and_merge(struct extent_list *list) { - int i, j; + unsigned int i, j; if (list->count < 2) return; @@ -461,6 +464,9 @@ static void ex_sort_and_merge(struct extent_list *list) } } + if (list->count == 0) + return; + /* Now sort by logical offset */ qsort(list->extents, list->count, sizeof(list->extents[0]), ex_compar); @@ -487,8 +493,8 @@ static void ex_sort_and_merge(struct extent_list *list) static int ext4_modify_extent_list(e2fsck_t ctx, struct extent_list *list, struct ext2fs_extent *ex, int del) { - int ret; - int i, offset; + int ret, offset; + unsigned int i; struct ext2fs_extent add_ex = *ex; /* First let's create a hole from ex->e_lblk of length ex->e_len */ @@ -572,7 +578,7 @@ static int ext4_del_extent_from_list(e2fsck_t ctx, struct extent_list *list, return ext4_modify_extent_list(ctx, list, ex, 1 /* delete */); } -static int ext4_fc_read_extents(e2fsck_t ctx, int ino) +static int ext4_fc_read_extents(e2fsck_t ctx, ext2_ino_t ino) { struct extent_list *extent_list = &ctx->fc_replay_state.fc_extent_list; @@ -591,7 +597,7 @@ static int ext4_fc_read_extents(e2fsck_t ctx, int ino) * for the inode so that we can flush all of them at once and it also saves us * from continuously growing and shrinking the extent tree. */ -static void ext4_fc_flush_extents(e2fsck_t ctx, int ino) +static void ext4_fc_flush_extents(e2fsck_t ctx, ext2_ino_t ino) { struct extent_list *extent_list = &ctx->fc_replay_state.fc_extent_list; @@ -604,56 +610,65 @@ static void ext4_fc_flush_extents(e2fsck_t ctx, int ino) /* Helper struct for dentry replay routines */ struct dentry_info_args { - int parent_ino, dname_len, ino, inode_len; + ext2_ino_t parent_ino; + int dname_len; + ext2_ino_t ino; char *dname; }; -static inline void tl_to_darg(struct dentry_info_args *darg, - struct ext4_fc_tl *tl) +static inline int tl_to_darg(struct dentry_info_args *darg, + struct ext4_fc_tl *tl, __u8 *val) { - struct ext4_fc_dentry_info *fcd; - int tag = le16_to_cpu(tl->fc_tag); + struct ext4_fc_dentry_info fcd; - fcd = (struct ext4_fc_dentry_info *)ext4_fc_tag_val(tl); + memcpy(&fcd, val, sizeof(fcd)); - darg->parent_ino = le32_to_cpu(fcd->fc_parent_ino); - darg->ino = le32_to_cpu(fcd->fc_ino); - darg->dname = (char *) fcd->fc_dname; + darg->parent_ino = le32_to_cpu(fcd.fc_parent_ino); + darg->ino = le32_to_cpu(fcd.fc_ino); darg->dname_len = ext4_fc_tag_len(tl) - sizeof(struct ext4_fc_dentry_info); darg->dname = malloc(darg->dname_len + 1); - memcpy(darg->dname, fcd->fc_dname, darg->dname_len); + if (!darg->dname) + return -ENOMEM; + memcpy(darg->dname, + val + sizeof(struct ext4_fc_dentry_info), + darg->dname_len); darg->dname[darg->dname_len] = 0; - jbd_debug(1, "%s: %s, ino %d, parent %d\n", - tag == EXT4_FC_TAG_CREAT ? "create" : - (tag == EXT4_FC_TAG_LINK ? "link" : - (tag == EXT4_FC_TAG_UNLINK ? "unlink" : "error")), - darg->dname, darg->ino, darg->parent_ino); + jbd_debug(1, "%s: %s, ino %u, parent %u\n", + le16_to_cpu(tl->fc_tag) == EXT4_FC_TAG_CREAT ? "create" : + (le16_to_cpu(tl->fc_tag) == EXT4_FC_TAG_LINK ? "link" : + (le16_to_cpu(tl->fc_tag) == EXT4_FC_TAG_UNLINK ? "unlink" : + "error")), darg->dname, darg->ino, darg->parent_ino); + return 0; } -static int ext4_fc_handle_unlink(e2fsck_t ctx, struct ext4_fc_tl *tl) +static int ext4_fc_handle_unlink(e2fsck_t ctx, struct ext4_fc_tl *tl, __u8 *val) { struct dentry_info_args darg; int ret; - tl_to_darg(&darg, tl); + ret = tl_to_darg(&darg, tl, val); + if (ret) + return ret; ext4_fc_flush_extents(ctx, darg.ino); - ret = errcode_to_errno( - ext2fs_unlink(ctx->fs, darg.parent_ino, - darg.dname, darg.ino, 0)); + ret = errcode_to_errno(ext2fs_unlink(ctx->fs, darg.parent_ino, + darg.dname, darg.ino, 0)); /* It's okay if the above call fails */ free(darg.dname); + return ret; } -static int ext4_fc_handle_link_and_create(e2fsck_t ctx, struct ext4_fc_tl *tl) +static int ext4_fc_handle_link_and_create(e2fsck_t ctx, struct ext4_fc_tl *tl, __u8 *val) { struct dentry_info_args darg; ext2_filsys fs = ctx->fs; struct ext2_inode_large inode_large; int ret, filetype, mode; - tl_to_darg(&darg, tl); + ret = tl_to_darg(&darg, tl, val); + if (ret) + return ret; ext4_fc_flush_extents(ctx, 0); ret = errcode_to_errno(ext2fs_read_inode(fs, darg.ino, (struct ext2_inode *)&inode_large)); @@ -719,21 +734,32 @@ static void ext4_fc_replay_fixup_iblocks(struct ext2_inode_large *ondisk_inode, } } -static int ext4_fc_handle_inode(e2fsck_t ctx, struct ext4_fc_tl *tl) +static int ext4_fc_handle_inode(e2fsck_t ctx, __u8 *val) { int ino, inode_len = EXT2_GOOD_OLD_INODE_SIZE; struct ext2_inode_large *inode = NULL, *fc_inode = NULL; - struct ext4_fc_inode *fc_inode_val; + __le32 fc_ino; + __u8 *fc_raw_inode; errcode_t err; blk64_t blks; - fc_inode_val = (struct ext4_fc_inode *)ext4_fc_tag_val(tl); - ino = le32_to_cpu(fc_inode_val->fc_ino); + memcpy(&fc_ino, val, sizeof(fc_ino)); + fc_raw_inode = val + sizeof(fc_ino); + ino = le32_to_cpu(fc_ino); + + if (EXT2_INODE_SIZE(ctx->fs->super) > EXT2_GOOD_OLD_INODE_SIZE) { + __u16 extra_isize = ext2fs_le16_to_cpu( + ((struct ext2_inode_large *)fc_raw_inode)->i_extra_isize); - if (EXT2_INODE_SIZE(ctx->fs->super) > EXT2_GOOD_OLD_INODE_SIZE) - inode_len += ext2fs_le16_to_cpu( - ((struct ext2_inode_large *)fc_inode_val->fc_raw_inode) - ->i_extra_isize); + if ((extra_isize < (sizeof(inode->i_extra_isize) + + sizeof(inode->i_checksum_hi))) || + (extra_isize > (EXT2_INODE_SIZE(ctx->fs->super) - + EXT2_GOOD_OLD_INODE_SIZE))) { + err = EFSCORRUPTED; + goto out; + } + inode_len += extra_isize; + } err = ext2fs_get_mem(inode_len, &inode); if (err) goto out; @@ -746,12 +772,9 @@ static int ext4_fc_handle_inode(e2fsck_t ctx, struct ext4_fc_tl *tl) inode_len); if (err) goto out; + memcpy(fc_inode, fc_raw_inode, inode_len); #ifdef WORDS_BIGENDIAN - ext2fs_swap_inode_full(ctx->fs, fc_inode, - (struct ext2_inode_large *)fc_inode_val->fc_raw_inode, - 0, sizeof(*inode)); -#else - memcpy(fc_inode, fc_inode_val->fc_raw_inode, inode_len); + ext2fs_swap_inode_full(ctx->fs, fc_inode, fc_inode, 0, inode_len); #endif memcpy(inode, fc_inode, offsetof(struct ext2_inode_large, i_block)); memcpy(&inode->i_generation, &fc_inode->i_generation, @@ -782,14 +805,15 @@ out: /* * Handle add extent replay tag. */ -static int ext4_fc_handle_add_extent(e2fsck_t ctx, struct ext4_fc_tl *tl) +static int ext4_fc_handle_add_extent(e2fsck_t ctx, __u8 *val) { struct ext2fs_extent extent; - struct ext4_fc_add_range *add_range; - int ret = 0, ino; + struct ext4_fc_add_range add_range; + ext2_ino_t ino; + int ret = 0; - add_range = (struct ext4_fc_add_range *)ext4_fc_tag_val(tl); - ino = le32_to_cpu(add_range->fc_ino); + memcpy(&add_range, val, sizeof(add_range)); + ino = le32_to_cpu(add_range.fc_ino); ext4_fc_flush_extents(ctx, ino); ret = ext4_fc_read_extents(ctx, ino); @@ -797,8 +821,8 @@ static int ext4_fc_handle_add_extent(e2fsck_t ctx, struct ext4_fc_tl *tl) return ret; memset(&extent, 0, sizeof(extent)); ret = errcode_to_errno(ext2fs_decode_extent( - &extent, (void *)(add_range->fc_ex), - sizeof(add_range->fc_ex))); + &extent, (void *)add_range.fc_ex, + sizeof(add_range.fc_ex))); if (ret) return ret; return ext4_add_extent_to_list(ctx, @@ -808,19 +832,19 @@ static int ext4_fc_handle_add_extent(e2fsck_t ctx, struct ext4_fc_tl *tl) /* * Handle delete logical range replay tag. */ -static int ext4_fc_handle_del_range(e2fsck_t ctx, struct ext4_fc_tl *tl) +static int ext4_fc_handle_del_range(e2fsck_t ctx, __u8 *val) { struct ext2fs_extent extent; - struct ext4_fc_del_range *del_range; + struct ext4_fc_del_range del_range; int ret, ino; - del_range = (struct ext4_fc_del_range *)ext4_fc_tag_val(tl); - ino = le32_to_cpu(del_range->fc_ino); + memcpy(&del_range, val, sizeof(del_range)); + ino = le32_to_cpu(del_range.fc_ino); ext4_fc_flush_extents(ctx, ino); memset(&extent, 0, sizeof(extent)); - extent.e_lblk = ext2fs_le32_to_cpu(del_range->fc_lblk); - extent.e_len = ext2fs_le32_to_cpu(del_range->fc_len); + extent.e_lblk = le32_to_cpu(del_range.fc_lblk); + extent.e_len = le32_to_cpu(del_range.fc_len); ret = ext4_fc_read_extents(ctx, ino); if (ret) return ret; @@ -839,8 +863,8 @@ static int ext4_fc_replay(journal_t *journal, struct buffer_head *bh, e2fsck_t ctx = journal->j_fs_dev->k_ctx; struct e2fsck_fc_replay_state *state = &ctx->fc_replay_state; int ret = JBD2_FC_REPLAY_CONTINUE; - struct ext4_fc_tl *tl; - __u8 *start, *end; + struct ext4_fc_tl tl; + __u8 *start, *end, *cur, *val; if (pass == PASS_SCAN) { state->fc_current_pass = PASS_SCAN; @@ -864,7 +888,7 @@ static int ext4_fc_replay(journal_t *journal, struct buffer_head *bh, /* * Mark the file system to indicate it contains errors. That's * because the updates performed by fast commit replay code are - * not atomic and may result in incosistent file system if it + * not atomic and may result in inconsistent file system if it * crashes before the replay is complete. */ ctx->fs->super->s_state |= EXT2_ERROR_FS; @@ -876,28 +900,31 @@ static int ext4_fc_replay(journal_t *journal, struct buffer_head *bh, start = (__u8 *)bh->b_data; end = (__u8 *)bh->b_data + journal->j_blocksize - 1; - fc_for_each_tl(start, end, tl) { + for (cur = start; cur < end; cur = cur + le16_to_cpu(tl.fc_len) + sizeof(tl)) { + memcpy(&tl, cur, sizeof(tl)); + val = cur + sizeof(tl); + if (state->fc_replay_num_tags == 0) goto replay_done; jbd_debug(3, "Replay phase processing %s tag\n", - tag2str(le16_to_cpu(tl->fc_tag))); + tag2str(le16_to_cpu(tl.fc_tag))); state->fc_replay_num_tags--; - switch (le16_to_cpu(tl->fc_tag)) { + switch (le16_to_cpu(tl.fc_tag)) { case EXT4_FC_TAG_CREAT: case EXT4_FC_TAG_LINK: - ret = ext4_fc_handle_link_and_create(ctx, tl); + ret = ext4_fc_handle_link_and_create(ctx, &tl, val); break; case EXT4_FC_TAG_UNLINK: - ret = ext4_fc_handle_unlink(ctx, tl); + ret = ext4_fc_handle_unlink(ctx, &tl, val); break; case EXT4_FC_TAG_ADD_RANGE: - ret = ext4_fc_handle_add_extent(ctx, tl); + ret = ext4_fc_handle_add_extent(ctx, val); break; case EXT4_FC_TAG_DEL_RANGE: - ret = ext4_fc_handle_del_range(ctx, tl); + ret = ext4_fc_handle_del_range(ctx, val); break; case EXT4_FC_TAG_INODE: - ret = ext4_fc_handle_inode(ctx, tl); + ret = ext4_fc_handle_inode(ctx, val); break; case EXT4_FC_TAG_TAIL: ext4_fc_flush_extents(ctx, 0); @@ -971,7 +998,14 @@ static errcode_t e2fsck_get_journal(e2fsck_t ctx, journal_t **ret_journal) journal->j_blocksize = ctx->fs->blocksize; if (uuid_is_null(sb->s_journal_uuid)) { - if (!sb->s_journal_inum) { + /* + * The full set of superblock sanity checks haven't + * been performed yet, so we need to do some basic + * checks here to avoid potential array overruns. + */ + if (!sb->s_journal_inum || + (sb->s_journal_inum > + (ctx->fs->group_desc_count * sb->s_inodes_per_group))) { retval = EXT2_ET_BAD_INODE_NUM; goto errout; } @@ -1005,7 +1039,8 @@ static errcode_t e2fsck_get_journal(e2fsck_t ctx, journal_t **ret_journal) tried_backup_jnl++; } if (!j_inode->i_ext2.i_links_count || - !LINUX_S_ISREG(j_inode->i_ext2.i_mode)) { + !LINUX_S_ISREG(j_inode->i_ext2.i_mode) || + (j_inode->i_ext2.i_flags & EXT4_ENCRYPT_FL)) { retval = EXT2_ET_NO_JOURNAL; goto try_backup_journal; } |