Commit 64b4a25c authored by Eric Biggers's avatar Eric Biggers Committed by Theodore Ts'o
Browse files

ext4: add missing validation of fast-commit record lengths



Validate the inode and filename lengths in fast-commit journal records
so that a malicious fast-commit journal cannot cause a crash by having
invalid values for these.  Also validate EXT4_FC_TAG_DEL_RANGE.

Fixes: aa75f4d3 ("ext4: main fast-commit commit path")
Cc: <stable@vger.kernel.org> # v5.10+
Signed-off-by: default avatarEric Biggers <ebiggers@google.com>
Link: https://lore.kernel.org/r/20221106224841.279231-5-ebiggers@kernel.org


Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
parent 594bc43b
Loading
Loading
Loading
Loading
+19 −19
Original line number Diff line number Diff line
@@ -1991,32 +1991,31 @@ void ext4_fc_replay_cleanup(struct super_block *sb)
	kfree(sbi->s_fc_replay_state.fc_modified_inodes);
}

static inline bool ext4_fc_tag_len_isvalid(struct ext4_fc_tl *tl,
					   u8 *val, u8 *end)
static bool ext4_fc_value_len_isvalid(struct ext4_sb_info *sbi,
				      int tag, int len)
{
	if (val + tl->fc_len > end)
		return false;

	/* Here only check ADD_RANGE/TAIL/HEAD which will read data when do
	 * journal rescan before do CRC check. Other tags length check will
	 * rely on CRC check.
	 */
	switch (tl->fc_tag) {
	switch (tag) {
	case EXT4_FC_TAG_ADD_RANGE:
		return (sizeof(struct ext4_fc_add_range) == tl->fc_len);
	case EXT4_FC_TAG_TAIL:
		return (sizeof(struct ext4_fc_tail) <= tl->fc_len);
	case EXT4_FC_TAG_HEAD:
		return (sizeof(struct ext4_fc_head) == tl->fc_len);
		return len == sizeof(struct ext4_fc_add_range);
	case EXT4_FC_TAG_DEL_RANGE:
		return len == sizeof(struct ext4_fc_del_range);
	case EXT4_FC_TAG_CREAT:
	case EXT4_FC_TAG_LINK:
	case EXT4_FC_TAG_UNLINK:
	case EXT4_FC_TAG_CREAT:
		len -= sizeof(struct ext4_fc_dentry_info);
		return len >= 1 && len <= EXT4_NAME_LEN;
	case EXT4_FC_TAG_INODE:
		len -= sizeof(struct ext4_fc_inode);
		return len >= EXT4_GOOD_OLD_INODE_SIZE &&
			len <= sbi->s_inode_size;
	case EXT4_FC_TAG_PAD:
	default:
		return true;
		return true; /* padding can have any length */
	case EXT4_FC_TAG_TAIL:
		return len >= sizeof(struct ext4_fc_tail);
	case EXT4_FC_TAG_HEAD:
		return len == sizeof(struct ext4_fc_head);
	}
	return false;
}

/*
@@ -2079,7 +2078,8 @@ static int ext4_fc_replay_scan(journal_t *journal,
	     cur = cur + EXT4_FC_TAG_BASE_LEN + tl.fc_len) {
		ext4_fc_get_tl(&tl, cur);
		val = cur + EXT4_FC_TAG_BASE_LEN;
		if (!ext4_fc_tag_len_isvalid(&tl, val, end)) {
		if (tl.fc_len > end - val ||
		    !ext4_fc_value_len_isvalid(sbi, tl.fc_tag, tl.fc_len)) {
			ret = state->fc_replay_num_tags ?
				JBD2_FC_REPLAY_STOP : -ECANCELED;
			goto out_err;
+1 −1
Original line number Diff line number Diff line
@@ -58,7 +58,7 @@ struct ext4_fc_dentry_info {
	__u8 fc_dname[];
};

/* Value structure for EXT4_FC_TAG_INODE and EXT4_FC_TAG_INODE_PARTIAL. */
/* Value structure for EXT4_FC_TAG_INODE. */
struct ext4_fc_inode {
	__le32 fc_ino;
	__u8 fc_raw_inode[];