diff --git a/fs/incfs/data_mgmt.c b/fs/incfs/data_mgmt.c index d9c43d5cca19..7e3205880010 100644 --- a/fs/incfs/data_mgmt.c +++ b/fs/incfs/data_mgmt.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -366,7 +367,8 @@ static void log_block_read(struct mount_info *mi, incfs_uuid_t *id, } static int validate_hash_tree(struct file *bf, struct data_file *df, - int block_index, struct mem_range data, u8 *buf) + int block_index, struct mem_range data, + u8 *tmp_buf) { u8 digest[INCFS_MAX_HASH_SIZE] = {}; struct mtree *tree = NULL; @@ -379,6 +381,7 @@ static int validate_hash_tree(struct file *bf, struct data_file *df, int hash_per_block; int lvl = 0; int res; + struct page *saved_page = NULL; tree = df->df_hash_tree; sig = df->df_signature; @@ -400,17 +403,39 @@ static int validate_hash_tree(struct file *bf, struct data_file *df, INCFS_DATA_FILE_BLOCK_SIZE); size_t hash_off_in_block = hash_block_index * digest_size % INCFS_DATA_FILE_BLOCK_SIZE; - struct mem_range buf_range = range(buf, - INCFS_DATA_FILE_BLOCK_SIZE); - ssize_t read_res = incfs_kread(bf, buf, - INCFS_DATA_FILE_BLOCK_SIZE, hash_block_off); + struct mem_range buf_range; + struct page *page = NULL; + bool aligned = (hash_block_off & + (INCFS_DATA_FILE_BLOCK_SIZE - 1)) == 0; + u8 *actual_buf; - if (read_res < 0) - return read_res; - if (read_res != INCFS_DATA_FILE_BLOCK_SIZE) - return -EIO; + if (aligned) { + page = read_mapping_page( + bf->f_inode->i_mapping, + hash_block_off / INCFS_DATA_FILE_BLOCK_SIZE, + NULL); - saved_digest_rng = range(buf + hash_off_in_block, digest_size); + if (IS_ERR(page)) + return PTR_ERR(page); + + actual_buf = page_address(page); + } else { + size_t read_res = + incfs_kread(bf, tmp_buf, + INCFS_DATA_FILE_BLOCK_SIZE, + hash_block_off); + + if (read_res < 0) + return read_res; + if (read_res != INCFS_DATA_FILE_BLOCK_SIZE) + return -EIO; + + actual_buf = tmp_buf; + } + + buf_range = range(actual_buf, INCFS_DATA_FILE_BLOCK_SIZE); + saved_digest_rng = + range(actual_buf + hash_off_in_block, digest_size); if (!incfs_equal_ranges(calc_digest_rng, saved_digest_rng)) { int i; bool zero = true; @@ -425,9 +450,37 @@ static int validate_hash_tree(struct file *bf, struct data_file *df, if (zero) pr_debug("incfs: Note saved_digest all zero - did you forget to load the hashes?\n"); + + if (saved_page) + put_page(saved_page); + if (page) + put_page(page); return -EBADMSG; } + if (saved_page) { + /* + * This is something of a kludge. The PageChecked flag + * is reserved for the file system, but we are setting + * this on the pages belonging to the underlying file + * system. incfs is only going to be used on f2fs and + * ext4 which only use this flag when fs-verity is being + * used, so this is safe for now, however a better + * mechanism needs to be found. + */ + SetPageChecked(saved_page); + put_page(saved_page); + saved_page = NULL; + } + + if (page && PageChecked(page)) { + put_page(page); + return 0; + } + + saved_page = page; + page = NULL; + res = incfs_calc_digest(tree->alg, buf_range, calc_digest_rng); if (res) return res; @@ -437,8 +490,15 @@ static int validate_hash_tree(struct file *bf, struct data_file *df, root_hash_rng = range(tree->root_hash, digest_size); if (!incfs_equal_ranges(calc_digest_rng, root_hash_rng)) { pr_debug("incfs: Root hash mismatch blk:%d\n", block_index); + if (saved_page) + put_page(saved_page); return -EBADMSG; } + + if (saved_page) { + SetPageChecked(saved_page); + put_page(saved_page); + } return 0; }