aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Halcrow <mhalcrow@google.com>2018-03-02 14:55:02 -0800
committerMichael Halcrow <mhalcrow@google.com>2018-03-05 16:32:23 -0800
commit077ca41f1cb6ebe504d933017f08d0c0c4787109 (patch)
tree2f7a5dff7c543c118da755fa210530e10a8e12ee
parent7e30309968c18b504320275d527914272fbe1a1c (diff)
downloadlinux-fs-verity-wip.tar.gz
fs-verity: Work-in-progress prototype w/ F2FS integrationfs-verity-wip
fs-verity makes efficient authenticity measurements over file contents. It does so by implementing the dm-verity mechanism at a file-granular level. This uses an authenticated dictionary structure to measure any given block in the file in log(file size) time. This is a major new VFS feature that will take some time to get right. If you would like to participate in the design and implementation, please do so on the linux-fscrypt mailing list. Note that this will be a topic of discussion at LSF/MM 2018. This is proof-of-concept code only that does not offer any security guarantees at the moment. (In fact, just about the only thing I can promise is that you can probably exploit the kernel via this code as it currently stands.) http://vger.kernel.org/vger-lists.html#linux-fscrypt Signed-off-by: Michael Halcrow <mhalcrow@google.com>
-rw-r--r--fs/Kconfig2
-rw-r--r--fs/Makefile1
-rw-r--r--fs/f2fs/Kconfig10
-rw-r--r--fs/f2fs/data.c324
-rw-r--r--fs/f2fs/dir.c4
-rw-r--r--fs/f2fs/f2fs.h73
-rw-r--r--fs/f2fs/file.c162
-rw-r--r--fs/f2fs/gc.c10
-rw-r--r--fs/f2fs/inode.c2
-rw-r--r--fs/f2fs/super.c11
-rw-r--r--fs/super.c27
-rw-r--r--fs/verity/Kconfig17
-rw-r--r--fs/verity/Makefile3
-rw-r--r--fs/verity/fsverity_private.h96
-rw-r--r--fs/verity/verity.c1257
-rw-r--r--include/linux/blk_types.h10
-rw-r--r--include/linux/fs.h14
-rw-r--r--include/linux/fsverity.h105
-rw-r--r--include/linux/mm_types.h12
-rw-r--r--include/uapi/linux/fs.h21
20 files changed, 2123 insertions, 38 deletions
diff --git a/fs/Kconfig b/fs/Kconfig
index bc821a86d9651..795a574760248 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -104,6 +104,8 @@ config MANDATORY_FILE_LOCKING
source "fs/crypto/Kconfig"
+source "fs/verity/Kconfig"
+
source "fs/notify/Kconfig"
source "fs/quota/Kconfig"
diff --git a/fs/Makefile b/fs/Makefile
index add789ea270ad..384c2cb515e75 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_USERFAULTFD) += userfaultfd.o
obj-$(CONFIG_AIO) += aio.o
obj-$(CONFIG_FS_DAX) += dax.o
obj-$(CONFIG_FS_ENCRYPTION) += crypto/
+obj-$(CONFIG_FS_VERITY) += verity/
obj-$(CONFIG_FILE_LOCKING) += locks.o
obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o
obj-$(CONFIG_BINFMT_AOUT) += binfmt_aout.o
diff --git a/fs/f2fs/Kconfig b/fs/f2fs/Kconfig
index 9a20ef42fadde..3473ba96a0545 100644
--- a/fs/f2fs/Kconfig
+++ b/fs/f2fs/Kconfig
@@ -81,6 +81,16 @@ config F2FS_FS_ENCRYPTION
efficient since it avoids caching the encrypted and
decrypted pages in the page cache.
+config F2FS_FS_VERITY
+ bool "F2FS Verity"
+ depends on F2FS_FS
+ select FS_VERITY
+ help
+ Enable file-based authentication of f2fs files. This
+ efficiently validates the randomly-accessed content of files
+ using an authenticated dictionary structure hidden at the
+ end of immutable files.
+
config F2FS_IO_TRACE
bool "F2FS IO tracer"
depends on F2FS_FS
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 7578ed1a85e05..a23db2061330a 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -23,6 +23,7 @@
#include <linux/memcontrol.h>
#include <linux/cleancache.h>
#include <linux/sched/signal.h>
+#include <linux/list.h>
#include "f2fs.h"
#include "node.h"
@@ -63,6 +64,7 @@ static void f2fs_read_end_io(struct bio *bio)
#endif
if (f2fs_bio_encrypted(bio)) {
+ BUG_ON(f2fs_bio_verity(bio)); /* TODO(mhalcrow): support this */
if (bio->bi_status) {
fscrypt_release_ctx(bio->bi_private);
} else {
@@ -71,6 +73,25 @@ static void f2fs_read_end_io(struct bio *bio)
}
}
+ if (f2fs_bio_verity(bio)) {
+ BUG_ON(f2fs_bio_encrypted(bio)); /* TODO(mhalcrow) */
+ if (bio->bi_status) {
+ /* TODO(mhalcrow) */
+ fsverity_release_bio_ctrl(bio->bi_verity_ctrl);
+ } else {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Calling verify_bio w/ bio = "
+ "[0x%p]\n", __func__, bio);
+#endif
+ fsverity_verify_bio(bio);
+ return;
+ }
+ }
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: bio [0x%p] isn't encrypted or veritied; "
+ "completing pages\n", __func__, bio);
+#endif
bio_for_each_segment_all(bvec, bio, i) {
struct page *page = bvec->bv_page;
@@ -470,12 +491,14 @@ out_fail:
}
static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
- unsigned nr_pages)
+ unsigned nr_pages,
+ struct fsverity_bio_ctrl *ctrl)
{
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct fscrypt_ctx *ctx = NULL;
struct bio *bio;
+ /* TODO(mhalcrow): Consolidate fscrypt and fsverity ctx */
if (f2fs_encrypted_file(inode)) {
ctx = fscrypt_get_ctx(inode, GFP_NOFS);
if (IS_ERR(ctx))
@@ -487,23 +510,53 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
bio = f2fs_bio_alloc(sbi, min_t(int, nr_pages, BIO_MAX_PAGES), false);
if (!bio) {
- if (ctx)
+ if (ctx) /* TODO: Fold into bio_ctx */
fscrypt_release_ctx(ctx);
return ERR_PTR(-ENOMEM);
}
f2fs_target_device(sbi, blkaddr, bio);
bio->bi_end_io = f2fs_read_end_io;
bio->bi_private = ctx;
+#ifdef CONFIG_F2FS_FS_VERITY
+ if (f2fs_verity_file(inode)) {
+ bio->bi_verity_ctrl = ctrl;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Setting ctrl [0x%p] on bio [0x%p]\n",
+ __func__, ctrl, bio);
+#endif /* CONFIG_FS_VERITY_DEBUG */
+ }
+#endif /* CONFIG_F2FS_FS_VERITY */
bio_set_op_attrs(bio, REQ_OP_READ, 0);
return bio;
}
+#ifdef CONFIG_F2FS_FS_VERITY
+static void __queue_or_submit_bio(struct inode *inode, struct bio *bio)
+{
+ if (f2fs_verity_file(inode) && bio->bi_verity_ctrl) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Queuing bio [0x%p] for submit\n",
+ __func__, bio);
+#endif /* CONFIG_FS_VERITY_DEBUG */
+ list_add_tail(&bio->bi_group, &bio->bi_verity_ctrl->bio_group);
+ } else {
+ __submit_bio(F2FS_I_SB(inode), bio, DATA);
+ }
+}
+#else
+static void __queue_or_submit_bio(struct inode *inode, struct bio *bio)
+{
+ __submit_bio(F2FS_I_SB(inode), bio, DATA);
+}
+#endif /* CONFIG_F2FS_FS_VERITY */
+
/* This can handle encryption stuffs */
static int f2fs_submit_page_read(struct inode *inode, struct page *page,
- block_t blkaddr)
+ block_t blkaddr,
+ struct fsverity_bio_ctrl *ctrl)
{
- struct bio *bio = f2fs_grab_read_bio(inode, blkaddr, 1);
+ struct bio *bio = f2fs_grab_read_bio(inode, blkaddr, 1, ctrl);
if (IS_ERR(bio))
return PTR_ERR(bio);
@@ -512,7 +565,13 @@ static int f2fs_submit_page_read(struct inode *inode, struct page *page,
bio_put(bio);
return -EFAULT;
}
- __submit_bio(F2FS_I_SB(inode), bio, DATA);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (f2fs_verity_file(inode)) {
+ printk(KERN_WARNING "%s: Queue-or-submit bio [0x%p] for "
+ "page in verity; ctrl = [0x%p]\n", __func__, bio, ctrl);
+ }
+#endif
+ __queue_or_submit_bio(inode, bio);
return 0;
}
@@ -626,7 +685,8 @@ int f2fs_get_block(struct dnode_of_data *dn, pgoff_t index)
}
struct page *get_read_data_page(struct inode *inode, pgoff_t index,
- int op_flags, bool for_write)
+ int op_flags, bool for_write,
+ struct fsverity_bio_ctrl *ctrl)
{
struct address_space *mapping = inode->i_mapping;
struct dnode_of_data dn;
@@ -634,10 +694,20 @@ struct page *get_read_data_page(struct inode *inode, pgoff_t index,
struct extent_info ei = {0,0,0};
int err;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Called w/ index = [%lu]\n", __func__, index);
+#endif
page = f2fs_grab_cache_page(mapping, index, for_write);
if (!page)
return ERR_PTR(-ENOMEM);
+#ifdef CONFIG_F2FS_VERITY
+ page->queued = false;
+#endif
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Page [0x%p] successfully grabbed in the "
+ "cache\n", __func__, page);
+#endif
if (f2fs_lookup_extent_cache(inode, index, &ei)) {
dn.data_blkaddr = ei.blk + index - ei.fofs;
goto got_it;
@@ -655,8 +725,23 @@ struct page *get_read_data_page(struct inode *inode, pgoff_t index,
}
got_it:
if (PageUptodate(page)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Page [0x%p] is up to date\n",
+ __func__, page);
+#endif
+#ifdef CONFIG_F2FS_FS_VERITY
+ /* TODO(mhalcrow): Hack until I can figure out why a
+ * zero page is being read in here */
+ if (f2fs_verity_file(inode) && !page->contents_hashed) {
+ ClearPageUptodate(page);
+ } else {
+ unlock_page(page);
+ return page;
+ }
+#else
unlock_page(page);
return page;
+#endif
}
/*
@@ -666,6 +751,13 @@ got_it:
* see, f2fs_add_link -> get_new_data_page -> init_inode_metadata.
*/
if (dn.data_blkaddr == NEW_ADDR) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (f2fs_verity_file(inode)) {
+ printk(KERN_WARNING "%s: Page with index [%lu] is "
+ "NEW_ADDR; zeroing and setting up to date\n",
+ __func__, page->index);
+ }
+#endif
zero_user_segment(page, 0, PAGE_SIZE);
if (!PageUptodate(page))
SetPageUptodate(page);
@@ -673,12 +765,26 @@ got_it:
return page;
}
- err = f2fs_submit_page_read(inode, page, dn.data_blkaddr);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (f2fs_verity_file(inode)) {
+ printk(KERN_WARNING "%s: Calling f2fs_submit_page_read() for "
+ "page->index [%lu] and dn.data_blkaddr = [%u]\n",
+ __func__, page->index, dn.data_blkaddr);
+ }
+#endif
+#ifdef CONFIG_F2FS_FS_VERITY
+ page->queued = (ctrl != NULL);
+#endif
+ err = f2fs_submit_page_read(inode, page, dn.data_blkaddr, ctrl);
if (err)
goto put_err;
return page;
put_err:
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: put_err reached w/ err = [%d]",
+ __func__, err);
+#endif
f2fs_put_page(page, 1);
return ERR_PTR(err);
}
@@ -693,7 +799,7 @@ struct page *find_data_page(struct inode *inode, pgoff_t index)
return page;
f2fs_put_page(page, 0);
- page = get_read_data_page(inode, index, 0, false);
+ page = get_read_data_page(inode, index, 0, false, NULL);
if (IS_ERR(page))
return page;
@@ -714,13 +820,19 @@ struct page *find_data_page(struct inode *inode, pgoff_t index)
* whether this page exists or not.
*/
struct page *get_lock_data_page(struct inode *inode, pgoff_t index,
- bool for_write)
+ bool for_write, struct fsverity_bio_ctrl *ctrl)
{
struct address_space *mapping = inode->i_mapping;
struct page *page;
repeat:
- page = get_read_data_page(inode, index, 0, for_write);
- if (IS_ERR(page))
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (f2fs_verity_file(inode)) {
+ printk(KERN_WARNING "%s: Calling get_read_data_page for "
+ "index [%lu]\n", __func__, index);
+ }
+#endif
+ page = get_read_data_page(inode, index, 0, for_write, ctrl);
+ if (ctrl || IS_ERR(page))
return page;
/* wait for read completion */
@@ -784,7 +896,7 @@ struct page *get_new_data_page(struct inode *inode,
/* if ipage exists, blkaddr should be NEW_ADDR */
f2fs_bug_on(F2FS_I_SB(inode), ipage);
- page = get_lock_data_page(inode, index, true);
+ page = get_lock_data_page(inode, index, true, NULL);
if (IS_ERR(page))
return page;
}
@@ -984,6 +1096,12 @@ next_block:
blkaddr = datablock_addr(dn.inode, dn.node_page, dn.ofs_in_node);
if (blkaddr == NEW_ADDR || blkaddr == NULL_ADDR) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (blkaddr == NEW_ADDR) {
+ printk(KERN_WARNING "%s: blkaddr == NEW_ADDR\n",
+ __func__);
+ }
+#endif
if (create) {
if (unlikely(f2fs_cp_error(sbi))) {
err = -EIO;
@@ -1068,10 +1186,12 @@ skip:
dn.ofs_in_node = end_offset;
}
- if (pgofs >= end)
+ if (pgofs >= end) {
goto sync_out;
- else if (dn.ofs_in_node < end_offset)
+ }
+ else if (dn.ofs_in_node < end_offset) {
goto next_block;
+ }
if (flag == F2FS_GET_BLOCK_PRECACHE) {
if (map->m_flags & F2FS_MAP_MAPPED) {
@@ -1335,9 +1455,10 @@ out:
* This function was originally taken from fs/mpage.c, and customized for f2fs.
* Major change was from block_size == page_size in f2fs by default.
*/
-static int f2fs_mpage_readpages(struct address_space *mapping,
- struct list_head *pages, struct page *page,
- unsigned nr_pages)
+static int __f2fs_mpage_readpages(struct address_space *mapping,
+ struct list_head *pages, struct page *page,
+ unsigned nr_pages,
+ struct fsverity_bio_ctrl *ctrl)
{
struct bio *bio = NULL;
sector_t last_block_in_bio = 0;
@@ -1369,11 +1490,17 @@ static int f2fs_mpage_readpages(struct address_space *mapping,
readahead_gfp_mask(mapping)))
goto next_page;
}
+#ifdef CONFIG_FS_VERITY_DEBUG
+ else {
+ printk(KERN_WARNING "%s: !pages; mapping single page\n",
+ __func__);
+ }
+#endif
block_in_file = (sector_t)page->index;
last_block = block_in_file + nr_pages;
last_block_in_file = (i_size_read(inode) + blocksize - 1) >>
- blkbits;
+ blkbits;
if (last_block > last_block_in_file)
last_block = last_block_in_file;
@@ -1382,8 +1509,9 @@ static int f2fs_mpage_readpages(struct address_space *mapping,
*/
if ((map.m_flags & F2FS_MAP_MAPPED) &&
block_in_file > map.m_lblk &&
- block_in_file < (map.m_lblk + map.m_len))
+ block_in_file < (map.m_lblk + map.m_len)) {
goto got_it;
+ }
/*
* Then do more f2fs_map_blocks() calls until we are
@@ -1394,21 +1522,75 @@ static int f2fs_mpage_readpages(struct address_space *mapping,
if (block_in_file < last_block) {
map.m_lblk = block_in_file;
map.m_len = last_block - block_in_file;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Mapping page w/ map.m_lblk = "
+ "[%d] and map.m_len = [%d]\n",
+ __func__, map.m_lblk, map.m_len);
+#endif
if (f2fs_map_blocks(inode, &map, 0,
- F2FS_GET_BLOCK_DEFAULT))
+ F2FS_GET_BLOCK_DEFAULT)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Error mapping page\n",
+ __func__);
+#endif
goto set_error_page;
+ }
}
+#ifdef CONFIG_FS_VERITY_DEBUG
+ else {
+ printk(KERN_WARNING "%s: Not mapping; block_in_file = [%lu]; last_block = [%lu]; map.m_lblk = [%u]; map.m_len = [%u]\n", __func__, block_in_file, last_block, map.m_lblk, map.m_len);
+ }
+#endif
got_it:
if ((map.m_flags & F2FS_MAP_MAPPED)) {
block_nr = map.m_pblk + block_in_file - map.m_lblk;
SetPageMappedToDisk(page);
- if (!PageUptodate(page) && !cleancache_get_page(page)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ {
+ int cc_res = cleancache_get_page(page);
+ if (!PageUptodate(page) && !cc_res) {
+ printk(KERN_WARNING "%s: !PageUptodate("
+ "page=[0x%p]); "
+ "cleancache_get_page() "
+ "returned [%d]\n", __func__,
+ page, cc_res);
+ SetPageUptodate(page);
+ goto confused;
+ }
+ }
+#else
+ if (!PageUptodate(page) && cleancache_get_page(page)) {
SetPageUptodate(page);
goto confused;
}
+#endif
+#ifdef CONFIG_F2FS_FS_VERITY
+ if (f2fs_verity_file(inode) &&
+ fsverity_page_in_metadata_region(page)) {
+ /* TODO(mhalcrow): What's causing this
+ * to be issued in the first place?
+ * Readahead? Stop it at the
+ * source. */
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Page with index "
+ "[%lu] is in fsverity "
+ "metadata region; skipping\n", __func__,
+ page->index);
+#endif
+ zero_user_segment(page, 0, PAGE_SIZE);
+ if (!PageUptodate(page))
+ SetPageUptodate(page);
+ unlock_page(page);
+ goto next_page;
+ }
+#endif
} else {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Page not mapped; zeroing "
+ "page and unlocking\n", __func__);
+#endif
zero_user_segment(page, 0, PAGE_SIZE);
if (!PageUptodate(page))
SetPageUptodate(page);
@@ -1423,17 +1605,23 @@ got_it:
if (bio && (last_block_in_bio != block_nr - 1 ||
!__same_bdev(F2FS_I_SB(inode), block_nr, bio))) {
submit_and_realloc:
- __submit_bio(F2FS_I_SB(inode), bio, DATA);
+ __queue_or_submit_bio(inode, bio);
bio = NULL;
}
if (bio == NULL) {
- bio = f2fs_grab_read_bio(inode, block_nr, nr_pages);
+ bio = f2fs_grab_read_bio(inode, block_nr, nr_pages,
+ ctrl);
if (IS_ERR(bio)) {
bio = NULL;
goto set_error_page;
}
}
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Adding page = [0x%p]) with index "
+ "[%lu] to bio = [0x%p]\n", __func__, page, page->index,
+ bio);
+#endif
if (bio_add_page(bio, page, blocksize, 0) < blocksize)
goto submit_and_realloc;
@@ -1446,9 +1634,13 @@ set_error_page:
goto next_page;
confused:
if (bio) {
- __submit_bio(F2FS_I_SB(inode), bio, DATA);
+ __queue_or_submit_bio(inode, bio);
bio = NULL;
}
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Confused about page [0x%p]\n",
+ __func__, page);
+#endif
unlock_page(page);
next_page:
if (pages)
@@ -1456,10 +1648,89 @@ next_page:
}
BUG_ON(pages && !list_empty(pages));
if (bio)
- __submit_bio(F2FS_I_SB(inode), bio, DATA);
+ __queue_or_submit_bio(inode, bio);
return 0;
}
+#ifdef CONFIG_F2FS_FS_VERITY
+static int f2fs_mpage_readpages(struct address_space *mapping,
+ struct list_head *pages, struct page *page,
+ unsigned nr_pages)
+{
+ struct inode *inode = mapping->host;
+ struct fsverity_bio_ctrl *ctrl = NULL;
+ int err = 0;
+
+ if (f2fs_verity_file(inode)) {
+ struct bio *bio;
+
+ /* TODO(mhalcrow): Don't bother with the control
+ * structure if the file size is <= PAGE_SIZE; we're
+ * only going to measure the data page itself and
+ * compare against the auth root hash. */
+ ctrl = fsverity_alloc_bio_ctrl(GFP_NOFS);
+ if (IS_ERR(ctrl))
+ return PTR_ERR(ctrl);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Allocated ctrl [0x%p]\n",
+ __func__, ctrl);
+#endif
+ err = __f2fs_mpage_readpages(mapping, pages, page, nr_pages,
+ ctrl);
+ if (err)
+ goto out_err;
+ if (i_size_read(inode) > PAGE_SIZE) {
+ err = fsverity_queue_auth_pages(inode, ctrl);
+ if (err)
+ goto out_err;
+ }
+ list_for_each_entry(bio, &ctrl->bio_group, bi_group) {
+ atomic_inc(&ctrl->nr_bios);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ {
+ unsigned nr_bios = atomic_read(&ctrl->nr_bios);
+
+ printk(KERN_WARNING "%s: Submitting bio "
+ "[0x%p] with ctrl [0x%p] that has "
+ "nr_bios = [%d]\n",
+ __func__, bio, ctrl, nr_bios);
+ }
+#endif
+ __submit_bio(F2FS_I_SB(inode), bio, DATA);
+ }
+ if (atomic_dec_and_test(&ctrl->nr_bios)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: All bios in bio_group "
+ "completed before the submit path finalized. "
+ "Doing bio_group completion in the submit "
+ "path.\n", __func__);
+#endif
+ bio = list_first_entry(&ctrl->bio_group, struct bio,
+ bi_group);
+ fsverity_verify_bio(bio);
+ }
+ return 0;
+ } else {
+ return __f2fs_mpage_readpages(mapping, pages, page, nr_pages,
+ NULL);
+ }
+out_err:
+ if (ctrl) {
+ /* TODO(mhalcrow): Make sure this frees bio_group
+ * members for which submit failed */
+ fsverity_release_bio_ctrl(ctrl);
+ }
+ return err;
+}
+#else
+static int f2fs_mpage_readpages(struct address_space *mapping,
+ struct list_head *pages, struct page *page,
+ unsigned nr_pages)
+{
+ return __f2fs_mpage_readpages(mapping, pages, page, nr_pages, NULL);
+}
+#endif /* CONFIG_F2FS_FS_VERITY */
+
static int f2fs_read_data_page(struct file *file, struct page *page)
{
struct inode *inode = page->mapping->host;
@@ -2212,7 +2483,8 @@ repeat:
zero_user_segment(page, 0, PAGE_SIZE);
SetPageUptodate(page);
} else {
- err = f2fs_submit_page_read(inode, page, blkaddr);
+ /* ctrl is NULL because fs-verity doesn't support writes */
+ err = f2fs_submit_page_read(inode, page, blkaddr, NULL);
if (err)
goto fail;
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
index f00b5ed8c0115..1b4e4d82eb26e 100644
--- a/fs/f2fs/dir.c
+++ b/fs/f2fs/dir.c
@@ -767,7 +767,7 @@ bool f2fs_empty_dir(struct inode *dir)
return f2fs_empty_inline_dir(dir);
for (bidx = 0; bidx < nblock; bidx++) {
- dentry_page = get_lock_data_page(dir, bidx, false);
+ dentry_page = get_lock_data_page(dir, bidx, false, NULL);
if (IS_ERR(dentry_page)) {
if (PTR_ERR(dentry_page) == -ENOENT)
continue;
@@ -890,7 +890,7 @@ static int f2fs_readdir(struct file *file, struct dir_context *ctx)
page_cache_sync_readahead(inode->i_mapping, ra, file, n,
min(npages - n, (pgoff_t)MAX_DIR_RA_PAGES));
- dentry_page = get_lock_data_page(inode, n, false);
+ dentry_page = get_lock_data_page(inode, n, false, NULL);
if (IS_ERR(dentry_page)) {
err = PTR_ERR(dentry_page);
if (err == -ENOENT) {
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 6300ac5bcbe49..d8a6de9f7bd23 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -24,6 +24,7 @@
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <linux/quotaops.h>
+#include <linux/fsverity.h>
#include <crypto/hash.h>
#define __FS_HAS_ENCRYPTION IS_ENABLED(CONFIG_F2FS_FS_ENCRYPTION)
@@ -125,6 +126,7 @@ struct f2fs_mount_info {
#define F2FS_FEATURE_FLEXIBLE_INLINE_XATTR 0x0040
#define F2FS_FEATURE_QUOTA_INO 0x0080
#define F2FS_FEATURE_INODE_CRTIME 0x0100
+#define F2FS_FEATURE_VERITY 0x0200
#define F2FS_HAS_FEATURE(sb, mask) \
((F2FS_SB(sb)->raw_super->feature & cpu_to_le32(mask)) != 0)
@@ -576,6 +578,8 @@ enum {
#define FADVISE_ENCRYPT_BIT 0x04
#define FADVISE_ENC_NAME_BIT 0x08
#define FADVISE_KEEP_SIZE_BIT 0x10
+#define FADVISE_VERITY_BIT 0x20
+#define FADVISE_KEEP_VERITY_BIT 0x40
#define file_is_cold(inode) is_file(inode, FADVISE_COLD_BIT)
#define file_wrong_pino(inode) is_file(inode, FADVISE_LOST_PINO_BIT)
@@ -590,6 +594,10 @@ enum {
#define file_set_enc_name(inode) set_file(inode, FADVISE_ENC_NAME_BIT)
#define file_keep_isize(inode) is_file(inode, FADVISE_KEEP_SIZE_BIT)
#define file_set_keep_isize(inode) set_file(inode, FADVISE_KEEP_SIZE_BIT)
+#define file_is_verity(inode) is_file(inode, FADVISE_VERITY_BIT)
+#define file_set_verity(inode) set_file(inode, FADVISE_VERITY_BIT)
+#define file_is_keep_verity(inode) is_file(inode, FADVISE_KEEP_VERITY_BIT)
+#define file_set_keep_verity(inode) set_file(inode, FADVISE_KEEP_VERITY_BIT)
#define DEF_DIR_LEVEL 0
@@ -2826,10 +2834,11 @@ int f2fs_get_block(struct dnode_of_data *dn, pgoff_t index);
int f2fs_preallocate_blocks(struct kiocb *iocb, struct iov_iter *from);
int f2fs_reserve_block(struct dnode_of_data *dn, pgoff_t index);
struct page *get_read_data_page(struct inode *inode, pgoff_t index,
- int op_flags, bool for_write);
+ int op_flags, bool for_write,
+ struct fsverity_bio_ctrl *ctrl);
struct page *find_data_page(struct inode *inode, pgoff_t index);
struct page *get_lock_data_page(struct inode *inode, pgoff_t index,
- bool for_write);
+ bool for_write, struct fsverity_bio_ctrl *ctrl);
struct page *get_new_data_page(struct inode *inode,
struct page *ipage, pgoff_t index, bool new_i_size);
int do_write_data_page(struct f2fs_io_info *fio);
@@ -3172,11 +3181,60 @@ static inline bool f2fs_bio_encrypted(struct bio *bio)
return bio->bi_private != NULL;
}
+static inline bool f2fs_bio_verity(struct bio *bio)
+{
+ return bio->bi_verity_ctrl != NULL;
+}
+
static inline int f2fs_sb_has_crypto(struct super_block *sb)
{
return F2FS_HAS_FEATURE(sb, F2FS_FEATURE_ENCRYPT);
}
+static inline int f2fs_sb_has_verity(struct super_block *sb)
+{
+ return F2FS_HAS_FEATURE(sb, F2FS_FEATURE_VERITY);
+}
+
+/*
+ * fsverity support
+ */
+static inline int f2fs_verity_file(struct inode *inode)
+{
+ return file_is_verity(inode);
+}
+
+static inline int f2fs_set_verity_file(struct inode *inode,
+ int flag)
+{
+ if (mapping_tagged(inode->i_mapping, PAGECACHE_TAG_DIRTY))
+ filemap_write_and_wait(inode->i_mapping);
+ inode_lock(inode);
+ /* TODO(mhalcrow): Do this?
+ i_size_write(inode, fsverity_i_size(inode));
+ */
+ file_set_verity(inode);
+ inode_unlock(inode);
+ f2fs_mark_inode_dirty_sync(inode, true);
+ return 0;
+}
+
+/**
+ * @inode:
+ * @index:
+ * @ctrl: When not NULL, queues the bio rather than submitting
+ */
+static inline struct page *f2fs_read_file_page(struct inode *inode,
+ pgoff_t index,
+ struct fsverity_bio_ctrl *ctrl)
+{
+ if (f2fs_has_inline_data(inode)) {
+ WARN_ON(1);
+ return ERR_PTR(-EINVAL);
+ }
+ return get_lock_data_page(inode, index, false, ctrl);
+}
+
static inline int f2fs_sb_mounted_blkzoned(struct super_block *sb)
{
return F2FS_HAS_FEATURE(sb, F2FS_FEATURE_BLKZONED);
@@ -3259,4 +3317,15 @@ static inline bool f2fs_may_encrypt(struct inode *inode)
#endif
}
+static inline bool f2fs_may_verity(struct inode *inode)
+{
+#ifdef CONFIG_F2FS_FS_VERITY
+ umode_t mode = inode->i_mode;
+
+ return (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode));
+#else
+ return 0;
+#endif
+}
+
#endif
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 672a542e54647..954c6b40e1fb4 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -23,6 +23,7 @@
#include <linux/uio.h>
#include <linux/uuid.h>
#include <linux/file.h>
+#include <linux/fsverity.h>
#include "f2fs.h"
#include "node.h"
@@ -111,7 +112,7 @@ mapped:
f2fs_wait_on_page_writeback(page, DATA, false);
/* wait for GCed encrypted page writeback */
- if (f2fs_encrypted_file(inode))
+ if (f2fs_encrypted_file(inode) || f2fs_verity_file(inode))
f2fs_wait_on_block_writeback(sbi, dn.data_blkaddr);
out_sem:
@@ -479,6 +480,23 @@ static int f2fs_file_open(struct inode *inode, struct file *filp)
if (err)
return err;
+
+#ifdef CONFIG_F2FS_FS_VERITY
+ if (f2fs_verity_file(inode)) {
+ int ret;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Called on verity file\n", __func__);
+#endif
+ if (filp->f_mode & FMODE_WRITE)
+ return -EACCES;
+
+ ret = fsverity_get_info(inode);
+ if (ret)
+ return ret;
+ }
+#endif
+
return dquot_file_open(inode, filp);
}
@@ -551,7 +569,7 @@ static int truncate_partial_data_page(struct inode *inode, u64 from,
return 0;
}
- page = get_lock_data_page(inode, index, true);
+ page = get_lock_data_page(inode, index, true, NULL);
if (IS_ERR(page))
return PTR_ERR(page) == -ENOENT ? 0 : PTR_ERR(page);
truncate_out:
@@ -703,6 +721,17 @@ int f2fs_getattr(const struct path *path, struct kstat *stat,
generic_fillattr(inode, stat);
+#ifdef CONFIG_F2FS_FS_VERITY
+ if (f2fs_verity_file(inode)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Verity file; replacing stat->size = "
+ "[%lld] with [%lu]\n", __func__, stat->size,
+ fsverity_i_size(inode));
+#endif
+ stat->size = fsverity_i_size(inode);
+ }
+#endif
+
/* we need to show initial sectors used for inline_data/dentries */
if ((S_ISREG(inode->i_mode) && f2fs_has_inline_data(inode)) ||
f2fs_has_inline_dentry(inode))
@@ -1074,7 +1103,8 @@ static int __clone_blkaddrs(struct inode *src_inode, struct inode *dst_inode,
} else {
struct page *psrc, *pdst;
- psrc = get_lock_data_page(src_inode, src + i, true);
+ psrc = get_lock_data_page(src_inode, src + i, true,
+ NULL);
if (IS_ERR(psrc))
return PTR_ERR(psrc);
pdst = get_new_data_page(dst_inode, NULL, dst + i,
@@ -1495,6 +1525,9 @@ static long f2fs_fallocate(struct file *file, int mode,
if (!S_ISREG(inode->i_mode))
return -EINVAL;
+ if (f2fs_verity_file(inode))
+ return -EOPNOTSUPP;
+
if (f2fs_encrypted_inode(inode) &&
(mode & (FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_INSERT_RANGE)))
return -EOPNOTSUPP;
@@ -2199,7 +2232,7 @@ do_map:
while (idx < map.m_lblk + map.m_len && cnt < blk_per_seg) {
struct page *page;
- page = get_lock_data_page(inode, idx, true);
+ page = get_lock_data_page(inode, idx, true, NULL);
if (IS_ERR(page)) {
err = PTR_ERR(page);
goto clear_out;
@@ -2814,6 +2847,118 @@ static int f2fs_ioc_precache_extents(struct file *filp, unsigned long arg)
return f2fs_precache_extents(file_inode(filp));
}
+#define FS_VERITY_ROOT_HASH_SIZE 64
+
+static int f2fs_ioc_measure_fsverity(struct file *filp, unsigned long arg)
+{
+ struct inode *inode = file_inode(filp);
+ struct fsverity_root_hash root_hash;
+ int err;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (!capable(CAP_SYS_ADMIN)) {
+ printk(KERN_WARNING "%s: !capable(CAP_SYS_ADMIN)\n", __func__);
+ return -EPERM;
+ }
+#else
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+#endif
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (copy_from_user(&root_hash, (struct fsverity_root_hash __user *)arg,
+ sizeof(root_hash))) {
+ printk(KERN_WARNING "%s: copy_from_user() failed\n", __func__);
+ return -EFAULT;
+ }
+#else
+ if (copy_from_user(&root_hash, (struct fsverity_root_hash __user *)arg,
+ sizeof(root_hash)))
+ return -EFAULT;
+#endif
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (!f2fs_verity_file(inode)) {
+ printk(KERN_WARNING "%s: !f2fs_verity_file(inode)\n", __func__);
+ return -EINVAL;
+ }
+#else
+ if (!f2fs_verity_file(inode))
+ return -EINVAL;
+#endif
+
+ err = fsverity_measure_info(inode, &root_hash);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (err) {
+ printk(KERN_WARNING "%s: fsverity_measure_info() returned "
+ "[%d]\n", __func__, err);
+ }
+#endif
+ return err;
+}
+
+static int f2fs_ioc_set_fsverity(struct file *filp, unsigned long arg)
+{
+ struct inode *inode = file_inode(filp);
+ struct fsverity_set set;
+ int err;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (!f2fs_sb_has_verity(inode->i_sb)) {
+ printk(KERN_WARNING "%s: !f2fs_sb_has_verity(inode->i_sb)\n",
+ __func__);
+ return -EOPNOTSUPP;
+ }
+#else
+ if (!f2fs_sb_has_verity(inode->i_sb))
+ return -EOPNOTSUPP;
+#endif
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (!capable(CAP_SYS_ADMIN)) {
+ printk(KERN_WARNING "%s: !capable(CAP_SYS_ADMIN)\n",
+ __func__);
+ return -EPERM;
+ }
+#else
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+#endif
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (f2fs_verity_file(inode)) {
+ printk(KERN_WARNING "%s: f2fs_verity_file(inode)\n",
+ __func__);
+ return -EINVAL;
+ }
+#else
+ if (f2fs_verity_file(inode))
+ return -EINVAL;
+#endif
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (copy_from_user(&set, (struct fsverity_set __user *)arg,
+ sizeof(set))) {
+ printk(KERN_WARNING "%s: copy_from_user() failed\n",
+ __func__);
+ return -EFAULT;
+ }
+#else
+ if (copy_from_user(&set, (struct fsverity_set __user *)arg,
+ sizeof(set)))
+ return -EFAULT;
+#endif
+
+ f2fs_update_time(F2FS_I_SB(inode), REQ_TIME);
+
+ err = fsverity_enable(inode, &set);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: fsverity_enable() returned [%d]\n",
+ __func__, err);
+#endif
+ return err;
+}
+
long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(filp)))))
@@ -2870,6 +3015,13 @@ long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return f2fs_ioc_set_pin_file(filp, arg);
case F2FS_IOC_PRECACHE_EXTENTS:
return f2fs_ioc_precache_extents(filp, arg);
+ case FS_IOC_MEASURE_FSVERITY:
+#ifdef CONFIG_F2FS_FS_VERITY
+ printk(KERN_WARNING "%s: FS_IOC_MEASURE_FSVERITY\n", __func__);
+#endif
+ return f2fs_ioc_measure_fsverity(filp, arg);
+ case FS_IOC_SET_FSVERITY:
+ return f2fs_ioc_set_fsverity(filp, arg);
default:
return -ENOTTY;
}
@@ -2948,6 +3100,8 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
case F2FS_IOC_GET_PIN_FILE:
case F2FS_IOC_SET_PIN_FILE:
case F2FS_IOC_PRECACHE_EXTENTS:
+ case FS_IOC_GET_FSVERITY:
+ case FS_IOC_SET_FSVERITY:
break;
default:
return -ENOIOCTLCMD;
diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
index aa720cc445094..f580744c3c087 100644
--- a/fs/f2fs/gc.c
+++ b/fs/f2fs/gc.c
@@ -721,7 +721,7 @@ static void move_data_page(struct inode *inode, block_t bidx, int gc_type,
{
struct page *page;
- page = get_lock_data_page(inode, bidx, true);
+ page = get_lock_data_page(inode, bidx, true, NULL);
if (IS_ERR(page))
return;
@@ -842,7 +842,8 @@ next_step:
continue;
/* if encrypted inode, let's go phase 3 */
- if (f2fs_encrypted_file(inode)) {
+ if (f2fs_encrypted_file(inode) ||
+ f2fs_verity_file(inode)) {
add_gc_inode(gc_list, inode);
continue;
}
@@ -856,7 +857,7 @@ next_step:
start_bidx = start_bidx_of_node(nofs, inode);
data_page = get_read_data_page(inode,
start_bidx + ofs_in_node, REQ_RAHEAD,
- true);
+ true, NULL);
up_write(&F2FS_I(inode)->dio_rwsem[WRITE]);
if (IS_ERR(data_page)) {
iput(inode);
@@ -890,7 +891,8 @@ next_step:
start_bidx = start_bidx_of_node(nofs, inode)
+ ofs_in_node;
- if (f2fs_encrypted_file(inode))
+ if (f2fs_encrypted_file(inode) ||
+ f2fs_verity_file(inode))
move_data_block(inode, start_bidx, segno, off);
else
move_data_page(inode, start_bidx, gc_type,
diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c
index 205add3d0f3a0..2c2e67cd418ad 100644
--- a/fs/f2fs/inode.c
+++ b/fs/f2fs/inode.c
@@ -13,6 +13,7 @@
#include <linux/buffer_head.h>
#include <linux/backing-dev.h>
#include <linux/writeback.h>
+#include <linux/fsverity.h>
#include "f2fs.h"
#include "node.h"
@@ -586,6 +587,7 @@ no_delete:
}
out_clear:
fscrypt_put_encryption_info(inode);
+ fsverity_put_info(inode);
clear_inode(inode);
}
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 8173ae688814a..755e610589a25 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -1817,6 +1817,14 @@ static const struct fscrypt_operations f2fs_cryptops = {
};
#endif
+#ifdef CONFIG_F2FS_FS_VERITY
+static const struct fsverity_operations f2fs_verityops = {
+ .is_verity = f2fs_verity_file,
+ .set_verity = f2fs_set_verity_file,
+ .read_file_page = f2fs_read_file_page,
+};
+#endif
+
static struct inode *f2fs_nfs_get_inode(struct super_block *sb,
u64 ino, u32 generation)
{
@@ -2553,6 +2561,9 @@ try_onemore:
#ifdef CONFIG_F2FS_FS_ENCRYPTION
sb->s_cop = &f2fs_cryptops;
#endif
+#ifdef CONFIG_F2FS_FS_VERITY
+ sb->s_vop = &f2fs_verityops;
+#endif
sb->s_xattr = f2fs_xattr_handlers;
sb->s_export_op = &f2fs_export_ops;
sb->s_magic = F2FS_SUPER_MAGIC;
diff --git a/fs/super.c b/fs/super.c
index 672538ca98318..3b81ab2b43596 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -231,6 +231,11 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
spin_lock_init(&s->s_inode_list_lock);
INIT_LIST_HEAD(&s->s_inodes_wb);
spin_lock_init(&s->s_inode_wblist_lock);
+#ifdef CONFIG_FS_VERITY
+ /* TODO(mhalcrow): Not for upstream */
+ INIT_LIST_HEAD(&s->s_inodes_fsverity);
+ spin_lock_init(&s->s_inode_fsveritylist_lock);
+#endif
if (list_lru_init_memcg(&s->s_dentry_lru))
goto fail;
@@ -421,6 +426,10 @@ void generic_shutdown_super(struct super_block *sb)
const struct super_operations *sop = sb->s_op;
if (sb->s_root) {
+#ifdef CONFIG_FS_VERITY
+ struct inode *inode, *tmp;
+#endif
+
shrink_dcache_for_umount(sb);
sync_filesystem(sb);
sb->s_flags &= ~SB_ACTIVE;
@@ -428,6 +437,24 @@ void generic_shutdown_super(struct super_block *sb)
fsnotify_unmount_inodes(sb);
cgroup_writeback_umount();
+#ifdef CONFIG_FS_VERITY
+ /* TODO(mhalcrow): Not for upstream (fsverity list on the sb) */
+ spin_lock(&sb->s_inode_fsveritylist_lock);
+ list_for_each_entry_safe(inode, tmp, &sb->s_inodes_fsverity,
+ i_fsverity_list) {
+ spin_lock(&inode->i_lock);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Deleting &inode->i_fsverity_"
+ "list = [0x%p]\n", __func__,
+ &inode->i_fsverity_list);
+#endif
+ list_del(&inode->i_fsverity_list);
+ spin_unlock(&inode->i_lock);
+ iput(inode);
+ }
+ spin_unlock(&sb->s_inode_fsveritylist_lock);
+#endif
+
evict_inodes(sb);
if (sb->s_dio_done_wq) {
diff --git a/fs/verity/Kconfig b/fs/verity/Kconfig
new file mode 100644
index 0000000000000..450c79ac94080
--- /dev/null
+++ b/fs/verity/Kconfig
@@ -0,0 +1,17 @@
+config FS_VERITY
+ tristate "FS Verity (File-based authentication)"
+ select CRYPTO
+ select CRYPTO_CRC32
+ select CRYPTO_SHA256
+ select CRYPTO_HASH
+ help
+ This option enables file-based verity feature. The
+ filesystem checks data blocks' authenticity when reading
+ them from underlying disk, similar to how dm-verity does
+ this when reading from a block device.
+
+config FS_VERITY_DEBUG
+ bool "FS Verity Debug"
+ select FS_VERITY
+ help
+ Debug FS Verity
diff --git a/fs/verity/Makefile b/fs/verity/Makefile
new file mode 100644
index 0000000000000..ad8113ee29cc8
--- /dev/null
+++ b/fs/verity/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_FS_VERITY) += fsverity.o
+
+fsverity-y := verity.o
diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h
new file mode 100644
index 0000000000000..3b5565c01b4f6
--- /dev/null
+++ b/fs/verity/fsverity_private.h
@@ -0,0 +1,96 @@
+/*
+ * fsverity.h: common declarations for per-file verity
+ *
+ * Copyright (C) 2017, Google, Inc.
+ *
+ * Written by Jaegeuk Kim, 2017.
+ * : lots of codes are borrowed from dm-verity.
+ */
+
+#ifndef _FSVERITY_PRIVATE_H
+#define _FSVERITY_PRIVATE_H
+
+#include <linux/fsverity.h>
+#include <linux/workqueue.h>
+
+#define FS_VERITY_MAGIC "TrueBrew"
+#define FS_VERITY_SALT_SIZE 8
+
+struct fsverity_header {
+ u8 magic[8]; /* Must be FS_VERITY_MAGIC */
+ u8 maj_version; /* Must be FS_VERITY_MAJOR */
+ u8 min_version; /* Must be FS_VERITY_MINOR */
+ u8 log_blocksize; /* log2(data-bytes-per-hash) - 12 for (4Kb) */
+ u8 log_arity; /* log2(leaves-per-node) (E.g., 7 for SHA2 ) */
+ __le16 meta_algorithm; /* Cryptographic digest for tree blocks */
+ __le16 data_algorithm; /* Cryptographic digest for data blocks */
+ __le32 flags; /* Only flag is '1' if there are fsverity_extensions */
+ __le32 reserved1; /* Must be 0 */
+ __le64 size; /* Size of the original, unpadded data. */
+ /* The number of blocks from the start of this header where
+ * the authenticated data structure resides */
+ u8 auth_blk_offset;
+ u8 extension_count; /* The number of extensions */
+ u8 salt[FS_VERITY_SALT_SIZE];
+ u8 reserved2[22]; /* Must be 0 */
+ /* This structure is 64 bytes long */
+} __packed;
+
+struct fsverity_extension {
+ __le16 length; /* The length (zero-padded to 64 bits) of the
+ * extension item that follows aligned-up to 8 bytes */
+ u8 type;
+ u8 reserved[5];
+} __packed;
+
+struct fsverity_extension_elide {
+ __le64 offset;
+ __le64 length;
+} __packed;
+
+struct fsverity_extension_patch {
+ __le64 offset;
+ u8 length;
+ u8 reserved[7];
+ u8 databytes[];
+} __packed;
+
+/* Supported algorithms */
+enum {
+ CRC32_MODE = 0,
+ SHA256_MODE,
+ AVAIL_ALGS,
+};
+
+enum {
+ DATA_INTEGRITY_VISIBLE,
+};
+
+#define FS_VERITY_MAX_LEVELS 64
+#define FS_VERITY_BLOCK_BITS 12
+
+struct fsverity_info {
+ struct crypto_ahash *tfm;
+ char *alg_name; /* algorithm name */
+ u16 meta_algorithm; /* metadata hash algorithm */
+ u16 data_algorithm; /* data hash algorithm */
+
+ char depth; /* Depth of the merkle tree */
+ u32 flags; /* flags */
+ u8 salt[8]; /* Used to salt the hash */
+
+ size_t i_size; /* original data i_size */
+ size_t verity_i_size; /* i_size including verity metadata */
+ size_t tree_size; /* Size of the tree */
+ unsigned hashes_per_block_bits; /* log_2(blk_sz / hash_sz) */
+
+ bool root_hashed;
+ char root_hash[SHA256_DIGEST_SIZE]; /* Merkle tree root hash */
+
+ /* starting blocks for each tree level. 0 is the lowest level. */
+ sector_t hash_lvl_region_idx[FS_VERITY_MAX_LEVELS];
+} __packed;
+
+extern struct workqueue_struct *fsverity_read_workqueue;
+
+#endif /* _FSVERITY_PRIVATE_H */
diff --git a/fs/verity/verity.c b/fs/verity/verity.c
new file mode 100644
index 0000000000000..98af280da4b5b
--- /dev/null
+++ b/fs/verity/verity.c
@@ -0,0 +1,1257 @@
+/*
+ * Read-only file-based authenticity.
+ *
+ * Copyright (C) 2018, Google, Inc.
+ *
+ * This contains file-based verity functions.
+ *
+ * Written by Jaegeuk Kim and Michael Halcrow.
+ */
+
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/printk.h>
+#include <linux/ratelimit.h>
+#include <linux/bio.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <crypto/algapi.h>
+#include <crypto/hash.h>
+#include <crypto/sha.h>
+#include "fsverity_private.h"
+
+struct workqueue_struct *fsverity_read_workqueue;
+struct kmem_cache *fsverity_info_cachep;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+static void __debug_fsverity_header(struct fsverity_header *header)
+{
+ printk("\n========================\n");
+ printk("\tMagic = %s\n", header->magic);
+ printk("\tMajor = %d\n", header->maj_version);
+ printk("\tMinor = %d\n", header->min_version);
+ printk("\tlogblock = %d\n", header->log_blocksize);
+ printk("\tlog arity = %d\n", header->log_arity);
+ printk("\tmeta alg = %d\n", le16_to_cpu(header->meta_algorithm));
+ printk("\tdata alg = %d\n", le16_to_cpu(header->data_algorithm));
+ printk("\tflags = %x\n", le32_to_cpu(header->flags));
+ printk("\tsize = %llu\n", le64_to_cpu(header->size));
+ printk("\tauth_blk_offset = %d\n", header->auth_blk_offset);
+ printk("\textension_count = %d\n", header->extension_count);
+
+ printk("\tsalt = %x%x%x%x\n",
+ header->salt[0],
+ header->salt[1],
+ header->salt[2],
+ header->salt[3]);
+ printk("\tfilesize = %llu\n", le64_to_cpu(header->size));
+ printk("========================\n");
+}
+
+static void __debug_fsverity_info(struct fsverity_info *vi)
+{
+ printk("\n========================\n");
+ printk("\tmeta alg = %d\n", vi->meta_algorithm);
+ printk("\tdata alg = %d\n", vi->data_algorithm);
+ printk("\tsalt = %x%x%x%x\n",
+ vi->salt[0], vi->salt[1],
+ vi->salt[2], vi->salt[3]);
+ printk("\tflags = %x\n", vi->flags);
+ printk("========================\n");
+}
+#endif
+
+static bool __is_verity_doable(struct inode *inode)
+{
+ if (!inode->i_sb)
+ return false;
+ if (!inode->i_sb->s_vop)
+ return false;
+ if (!inode->i_sb->s_vop->is_verity)
+ return false;
+ if (!inode->i_sb->s_vop->set_verity)
+ return false;
+ if (!inode->i_sb->s_vop->read_file_page)
+ return false;
+ return true;
+}
+
+
+static int __sanity_check_header(struct fsverity_header *header)
+{
+ if (memcmp(header->magic, FS_VERITY_MAGIC, 8)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Header magic mismatch\n", __func__);
+#endif
+ return -EINVAL;
+ }
+ if (header->maj_version != 1) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Unsupported major version: [%d]\n",
+ __func__, header->maj_version);
+#endif
+ return -EINVAL;
+ }
+ if (header->min_version != 0) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Unsupported minor version: [%d]\n",
+ __func__, header->min_version);
+#endif
+ return -EINVAL;
+ }
+ if (header->log_blocksize != FS_VERITY_BLOCK_BITS) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: log_blocksize = [%d]; supported "
+ "value is [%d]\n", __func__, header->log_blocksize,
+ FS_VERITY_BLOCK_BITS);
+#endif
+ return -EINVAL;
+ }
+ if (le16_to_cpu(header->data_algorithm) != SHA256_MODE ||
+ le16_to_cpu(header->meta_algorithm) != SHA256_MODE) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Data [%d] and/or metadata [%d] "
+ "algorithm unsupported; must be [%d]\n", __func__,
+ le16_to_cpu(header->data_algorithm),
+ le16_to_cpu(header->meta_algorithm), SHA256_MODE);
+#endif
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int __measure_fs_verity_root_hash(
+ struct fsverity_info *vi, const char *root, const char *hdr_virt,
+ unsigned hdr_len, const struct fsverity_root_hash *root_hash,
+ struct crypto_shash *tfm, char *salt)
+{
+ char fsverity_root_hash[SHA256_DIGEST_SIZE];
+ SHASH_DESC_ON_STACK(desc, tfm);
+ int err;
+
+ desc->tfm = tfm;
+ desc->flags = 0;
+
+ err = crypto_shash_init(desc);
+ if (err) {
+ pr_warn_ratelimited("fsverity: Error hashing tree: "
+ "init returned [%d]\n", err);
+ goto out;
+ }
+ err = crypto_shash_update(desc, salt, FS_VERITY_SALT_SIZE);
+ if (err) {
+ pr_warn_ratelimited("fsverity: Error hashing root salt: "
+ "update 1 returned [%d]\n", err);
+ goto out;
+ }
+ err = crypto_shash_update(desc, root, PAGE_SIZE);
+ if (err) {
+ pr_warn_ratelimited("fsverity: Error hashing root: "
+ "update 2 returned [%d]\n", err);
+ goto out;
+ }
+ err = crypto_shash_final(desc, vi->root_hash);
+ if (err) {
+ pr_warn_ratelimited("fsverity: Error hashing root: "
+ "final returned [%d]\n", err);
+ goto out;
+ }
+#ifdef CONFIG_FS_VERITY_DEBUG
+ {
+ char computed_hex_hash[2 * SHA256_DIGEST_SIZE + 1];
+
+ bin2hex(computed_hex_hash, vi->root_hash,
+ SHA256_DIGEST_SIZE);
+ computed_hex_hash[2 * SHA256_DIGEST_SIZE] = '\0';
+
+ printk(KERN_WARNING
+ "%s: Computed Merkle tree root hash: [%s]\n", __func__,
+ computed_hex_hash);
+ }
+#endif
+ err = crypto_shash_init(desc);
+ if (err) {
+ pr_warn_ratelimited("fsverity: Error hashing header: "
+ "init returned [%d]\n", err);
+ goto out;
+ }
+ err = crypto_shash_update(desc, hdr_virt, hdr_len);
+ if (err) {
+ pr_warn_ratelimited("fsverity: Error hashing header "
+ ": update 1 returned [%d]\n", err);
+ goto out;
+ }
+ err = crypto_shash_update(desc, vi->root_hash, SHA256_DIGEST_SIZE);
+ if (err) {
+ pr_warn_ratelimited("fsverity: Error hashing root hash "
+ ": update 2 returned [%d]\n", err);
+ goto out;
+ }
+ err = crypto_shash_final(desc, fsverity_root_hash);
+ if (err) {
+ pr_warn_ratelimited("fsverity: Error finalizing fsverity "
+ "root hash: final returned [%d]\n", err);
+ goto out;
+ }
+ /* TODO(mhalcrow): Drop support for this behavior from the
+ * prototype once we have root-of-trust wired in. */
+ if (!root_hash) {
+ pr_warn_ratelimited("fsverity: WARNING: root_hash not "
+ "present. No root-of-trust validation of "
+ "the hashes being done.\n");
+ goto out;
+ }
+ if (crypto_memneq(root_hash->root_hash, fsverity_root_hash,
+ SHA256_DIGEST_SIZE)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ char computed_hex_hash[2 * SHA256_DIGEST_SIZE + 1];
+ char expected_hex_hash[2 * SHA256_DIGEST_SIZE + 1];
+
+ bin2hex(computed_hex_hash, fsverity_root_hash,
+ SHA256_DIGEST_SIZE);
+ computed_hex_hash[2 * SHA256_DIGEST_SIZE] = '\0';
+
+ bin2hex(expected_hex_hash, root_hash->root_hash,
+ SHA256_DIGEST_SIZE);
+ expected_hex_hash[2 * SHA256_DIGEST_SIZE] = '\0';
+
+ printk(KERN_WARNING "Computed fs-verity root hash: [%s]; "
+ "root hash required by userspace is [%s]\n",
+ computed_hex_hash, expected_hex_hash);
+#endif
+ err = -EINVAL;
+ }
+#ifdef CONFIG_FS_VERITY_DEBUG
+ else {
+ printk(KERN_WARNING "%s: Root hash validated\n", __func__);
+ }
+#endif
+out:
+ return err;
+}
+
+static int __full_header_size(const char *hdr_virt)
+{
+ const struct fsverity_header *header;
+ unsigned ext_pos = 0;
+ u8 extension_count;
+
+ header = (struct fsverity_header *)hdr_virt;
+ ext_pos = sizeof(struct fsverity_header);
+ if (!(le32_to_cpu(header->flags) & 1)) /* No extensions */
+ goto out;
+ extension_count = header->extension_count;
+ if (extension_count == 0) {
+ printk(KERN_WARNING "%s: Extension flag set, but extension "
+ "count is 0\n", __func__);
+ goto out;
+ }
+ do {
+ struct fsverity_extension *ext;
+
+ if (ext_pos + sizeof(struct fsverity_extension) > PAGE_SIZE) {
+ ext_pos = 0;
+ goto out;
+ }
+ ext = (struct fsverity_extension *)&hdr_virt[ext_pos];
+ ext_pos = ext_pos + le16_to_cpu(ext->length);
+ if (ext_pos > PAGE_SIZE) {
+ ext_pos = 0;
+ goto out;
+ }
+ extension_count--;
+ } while (extension_count > 0);
+out:
+ return ext_pos;
+}
+
+static void __set_depth_and_hashes_per_blk(struct fsverity_info *info)
+{
+ unsigned block_size = PAGE_SIZE;
+ loff_t data_blocks = (info->i_size + (block_size - 1)) / block_size;
+ unsigned block_bits = __ffs(block_size);
+ unsigned digest_size = SHA256_DIGEST_SIZE;
+ unsigned digests_per_block = (1 << block_bits) / digest_size;
+ unsigned hash_per_block_bits = __ffs(digests_per_block);
+ unsigned levels = 0;
+
+ while (hash_per_block_bits * levels < 64 &&
+ (unsigned long long)(data_blocks - 1) >>
+ (hash_per_block_bits * levels)) {
+ levels++;
+ }
+ info->depth = levels;
+ info->hashes_per_block_bits = hash_per_block_bits;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: block_size = [%d]\n", __func__, block_size);
+ printk(KERN_WARNING "%s: data_blocks = [%lld]\n", __func__,
+ data_blocks);
+ printk(KERN_WARNING "%s: block_bits = [%d]\n", __func__, block_bits);
+ printk(KERN_WARNING "%s: digest_size = [%d]\n", __func__, digest_size);
+ printk(KERN_WARNING "%s: digests_per_block = [%d]\n", __func__,
+ digests_per_block);
+ printk(KERN_WARNING "%s: hash_per_block_bits = [%d]\n", __func__,
+ hash_per_block_bits);
+ printk(KERN_WARNING "%s: levels = [%d]\n", __func__, levels);
+#endif
+}
+
+/**
+ *
+ *
+ * Return: Real size of the file, including header and tree content
+ */
+static size_t __set_hash_lvl_region_idxs(struct fsverity_info *info)
+{
+ unsigned i;
+ size_t tree_lvl_start_block;
+ size_t tree_lvl_region_nr_blocks;
+
+ /* TODO(mhalcrow): This is pre-header-on-end-only
+ tree_lvl_start_block = ((info->i_size + (PAGE_SIZE - 1)) / PAGE_SIZE)
+ + 1;
+ */
+ /* TODO(mhalcrow): Isn't there a round-up macro in the kernel? */
+ tree_lvl_start_block = ((info->i_size + (PAGE_SIZE - 1)) / PAGE_SIZE);
+ tree_lvl_region_nr_blocks = 1;
+ for (i = 0; i < info->depth; i++) {
+ info->hash_lvl_region_idx[i] = tree_lvl_start_block;
+ tree_lvl_start_block += tree_lvl_region_nr_blocks;
+ tree_lvl_region_nr_blocks <<= info->hashes_per_block_bits;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: info->hash_lvl_region_idx[%d] = "
+ "[%lu]\n", __func__, i, info->hash_lvl_region_idx[i]);
+#endif
+ }
+ if (info->depth > 0) {
+ loff_t start_last_region =
+ info->hash_lvl_region_idx[info->depth - 1];
+ loff_t hashes_in_last_region =
+ (info->i_size + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
+ loff_t hashes_per_block = 1 << info->hashes_per_block_bits;
+ loff_t num_blocks_last_region =
+ (hashes_in_last_region + (hashes_per_block - 1)) >>
+ info->hashes_per_block_bits;
+ loff_t last_region_size = num_blocks_last_region * PAGE_SIZE;
+ info->tree_size =
+ ((start_last_region - info->hash_lvl_region_idx[0]) *
+ PAGE_SIZE) + last_region_size;
+ } else {
+ info->tree_size = 0;
+ }
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: info->tree_size = [%lu]", __func__,
+ info->tree_size);
+#endif
+ return tree_lvl_start_block << PAGE_SHIFT;
+}
+
+static int __read_fsverity_header(struct inode *inode,
+ struct fsverity_info *info,
+ const struct fsverity_root_hash *root_hash)
+{
+ struct fsverity_header *header;
+ struct page *hdr_page = NULL;
+ struct page *root_page = NULL;
+ char *hdr_virt;
+ char *root = NULL;
+ unsigned hdr_len;
+ size_t last_off;
+ __le16 root_hash_algo;
+ __le32 hdr_reverse_offset;
+ loff_t full_file_size;
+ size_t hdr_sz_offset_within_pg;
+ struct crypto_shash *tfm = NULL;
+ int err = 0;
+
+ full_file_size = i_size_read(inode);
+ /* TODO(mhalcrow): This logic only works with full-size Merkle
+ * trees that include all padding and/or when header/extent
+ * content fits in one page. */
+ last_off = full_file_size >> PAGE_SHIFT;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: i_size_read(inode) = [%lld]; reading header "
+ "page at offset [%lu]\n",
+ __func__, full_file_size, last_off);
+#endif
+ /* TODO(mhalcrow): ->read_locked_file_page() instead? */
+ hdr_page = inode->i_sb->s_vop->read_file_page(inode, last_off, NULL);
+ if (IS_ERR(hdr_page)) {
+ printk_ratelimited(KERN_ERR "%s: can't find header block\n",
+ __func__);
+ return PTR_ERR(hdr_page);
+ }
+ /* TODO(mhalcrow): What's keeping hdr_page from being evicted
+ * before the kmap()? Is it locked? If so, when should we
+ * unlock? */
+ hdr_virt = kmap(hdr_page);
+ hdr_sz_offset_within_pg =
+ ((full_file_size - sizeof(hdr_reverse_offset)) % PAGE_SIZE);
+ hdr_reverse_offset = *((__le32*)&hdr_virt[hdr_sz_offset_within_pg]);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: hdr_sz_offset_within_pg = [%lu]; "
+ "hdr_reverse_offset = [%d]\n",
+ __func__, hdr_sz_offset_within_pg, hdr_reverse_offset);
+#endif
+ /* TODO(mhalcrow): Great! Now we ignore hdr_reverse_offset
+ * because we know the header should immediately follow the
+ * page-aligned tree, and the size of the header fits within a
+ * page. When that's not longer the case, hdr_reverse_offset
+ * will matter. Maybe just validate it at this point? */
+ header = (struct fsverity_header *)hdr_virt;
+ err = __sanity_check_header(header);
+ if (err) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ /* TODO: This can mess up your terminal :-)
+ * _debug_fsverity_header(header); */
+#endif
+ printk_ratelimited(KERN_ERR "%s: fs-verity header failed "
+ "validation\n", __func__);
+ goto put_out;
+ }
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ __debug_fsverity_header(header);
+ BUG_ON(!info);
+#endif
+
+ info->meta_algorithm = le16_to_cpu(header->meta_algorithm);
+ info->data_algorithm = le16_to_cpu(header->data_algorithm);
+ info->flags = le32_to_cpu(header->flags);
+ info->i_size = le64_to_cpu(header->size);
+ __set_depth_and_hashes_per_blk(info);
+ info->verity_i_size = __set_hash_lvl_region_idxs(info);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: info->i_size = [%lu]; info->verity_i_size = "
+ "[%lu]\n", __func__, info->i_size, info->verity_i_size);
+#endif
+
+ memcpy(info->salt, header->salt, FS_VERITY_SALT_SIZE);
+
+ hdr_len = __full_header_size(hdr_virt);
+ if (hdr_len == 0) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: hdr_len == 0\n", __func__);
+#endif
+ err = -EINVAL;
+ goto put_out;
+ }
+
+ if (!root_hash) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: !root_hash\n", __func__);
+#endif
+ /* TODO(mhalcrow): Drop support for root_hash-less
+ * mode of operation after prototype. */
+ root_hash_algo = FS_VERITY_ROOT_HASH_ALGO_SHA256;
+ if (!inode->i_verity_info)
+ goto put_out;
+ } else {
+ root_hash_algo = le16_to_cpu(root_hash->root_hash_algorithm);
+ }
+ if (root_hash_algo != FS_VERITY_ROOT_HASH_ALGO_SHA256) {
+ printk_ratelimited(KERN_ERR
+ "%s: Invalid fsverity root hash algo: "
+ "[%d]; expected [%d]\n", __func__,
+ root_hash_algo,
+ FS_VERITY_ROOT_HASH_ALGO_SHA256);
+ err = -EINVAL;
+ goto put_out;
+ }
+
+ /* TODO(mhalcrow): Skip past auth_blk_offset */
+ root_page = inode->i_sb->s_vop->read_file_page(
+ inode, info->i_size <= PAGE_SIZE ? 0 :
+ info->hash_lvl_region_idx[0], NULL);
+ if (IS_ERR(root_page)) {
+ printk_ratelimited(KERN_ERR "%s: can't find root block\n",
+ __func__);
+ err = PTR_ERR(root_page);
+ goto put_out;
+ }
+ root_page->is_root = true;
+ /* TODO(mhalcrow): What's keeping root_page from being evicted
+ * before the kmap()? Is it locked? If so, when should we
+ * unlock? */
+ root = kmap(root_page);
+
+ tfm = crypto_alloc_shash("sha256", 0, 0);
+ if (IS_ERR(tfm)) {
+ pr_warn_ratelimited("fsverity: error allocating SHA-256 "
+ "transform: [%ld]\n", PTR_ERR(tfm));
+ err = PTR_ERR(tfm);
+ goto put_out;
+ }
+
+ err = __measure_fs_verity_root_hash(inode->i_verity_info, root,
+ hdr_virt, hdr_len, root_hash, tfm,
+ header->salt);
+ if (err) {
+ pr_warn_ratelimited("Error measuring fs-verity root hash: [%d]",
+ err);
+ goto put_out;
+ }
+put_out:
+ if (hdr_page) {
+ kunmap(hdr_page);
+ unlock_page(hdr_page);
+ put_page(hdr_page);
+ }
+ if (root_page) {
+ kunmap(root_page);
+ unlock_page(root_page);
+ put_page(root_page);
+ }
+ if (tfm)
+ crypto_free_shash(tfm);
+ return err;
+}
+EXPORT_SYMBOL(fsverity_read_header);
+
+static void __put_verity_info(struct fsverity_info *vi)
+{
+ if (!vi)
+ return;
+ kmem_cache_free(fsverity_info_cachep, vi);
+}
+
+
+static int __get_info(struct inode *inode, struct fsverity_info *vi,
+ struct fsverity_root_hash *root_hash)
+{
+ int err;
+
+ if (!__is_verity_doable(inode)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: !is_verity_doable(inode [0x%p])\n",
+ __func__, inode);
+#endif
+ return 0;
+ }
+ if (!inode->i_sb->s_vop->is_verity(inode)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: !inode([0x%p])->i_sb->"
+ "s_vop->is_verity(inode)\n", __func__, inode);
+#endif
+ return 0;
+ }
+ if (vi && inode->i_verity_info) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: vi && inode([0x%p])->i_verity_info, "
+ "so skipping header read/verify\n", __func__, inode);
+#endif
+ return 0;
+ }
+ err = __read_fsverity_header(inode, vi, root_hash);
+ if (err) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Error reading fs-verity header: [%d]"
+ "\n", __func__, err);
+#endif
+ goto out;
+ }
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (vi)
+ __debug_fsverity_info(vi);
+#endif
+
+out:
+ return err;
+}
+
+int fsverity_measure_info(struct inode *inode,
+ struct fsverity_root_hash *root_hash)
+{
+ struct fsverity_info info;
+ /* TODO(mhalcrow): Not for upstream (fsverity list on the sb) */
+ struct inode *sb_inode;
+ struct super_block *sb;
+ bool found = false;
+
+ sb = inode->i_sb;
+ spin_lock(&sb->s_inode_fsveritylist_lock);
+ list_for_each_entry(sb_inode, &sb->s_inodes_fsverity,
+ i_fsverity_list) {
+ if (sb_inode == inode) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ igrab(inode);
+ spin_lock(&inode->i_lock);
+ list_add(&inode->i_fsverity_list, &sb->s_inodes_fsverity);
+ spin_unlock(&inode->i_lock);
+ }
+ spin_unlock(&sb->s_inode_fsveritylist_lock);
+
+ return __get_info(inode, &info, root_hash);
+}
+EXPORT_SYMBOL(fsverity_measure_info);
+
+int fsverity_get_info(struct inode *inode)
+{
+ struct fsverity_info *vi;
+ int err;
+
+ vi = kmem_cache_alloc(fsverity_info_cachep, GFP_NOFS);
+ if (!vi)
+ return -ENOMEM;
+
+ /* TODO(mhalcrow): This is the point where we need to retrieve
+ * a trusted Merkle tree root measurement and pass it
+ * in. Hacking in a NULL for the prototype. */
+ err = __get_info(inode, vi, NULL);
+ if (err)
+ goto free_out;
+
+ if (cmpxchg(&inode->i_verity_info, NULL, vi) == NULL)
+ vi = NULL;
+free_out:
+ __put_verity_info(vi);
+ return err;
+}
+EXPORT_SYMBOL(fsverity_get_info);
+
+void fsverity_put_info(struct inode *inode)
+{
+ /* only evict_inode can release this */
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Calling __put_verity_info for inode [0x%p]; "
+ "inode->i_verity_info [0x%p]\n", __func__, inode,
+ inode->i_verity_info);
+#endif
+ __put_verity_info(inode->i_verity_info);
+}
+EXPORT_SYMBOL(fsverity_put_info);
+
+/* there's no way to deactivate the verity-enabled file */
+int fsverity_enable(struct inode *inode, struct fsverity_set *set)
+{
+ struct fsverity_info info;
+ int ret;
+
+ if (!S_ISREG(inode->i_mode)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: inode [0x%p] is not a regular file\n",
+ __func__, inode);
+#endif
+ return -EINVAL;
+ }
+
+ if (!__is_verity_doable(inode)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: inode [0x%p] cannot support "
+ "fs-verity\n",
+ __func__, inode);
+#endif
+ /* TODO(mhalcrow): Isn't this a programming error? */
+ return -ENOTSUPP;
+ }
+
+ /* only support 4KB block */
+ if (inode->i_blkbits != FS_VERITY_BLOCK_BITS) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: inode->i_blkbits = [%d]; expected "
+ "[%d]\n",
+ __func__, inode->i_blkbits, FS_VERITY_BLOCK_BITS);
+#endif
+ return -ENOTSUPP;
+ }
+
+ ret = __read_fsverity_header(inode, &info, NULL);
+ if (ret) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: __read_fsverity_header(inode([0x%p]), "
+ "...) returned [%d]\n",
+ __func__, inode, ret);
+#endif
+ return ret;
+ }
+
+ ret = inode->i_sb->s_vop->set_verity(inode, set->flags);
+ if (ret) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: inode([0x%p])->i_sb->s_vop->"
+ "set_verity(inode, "
+ "set->flags([%lld])) returned [%d]\n",
+ __func__, inode, set->flags, ret);
+#endif
+ return ret;
+ }
+
+ return fsverity_get_info(inode);
+}
+EXPORT_SYMBOL(fsverity_enable);
+
+struct fsverity_bio_ctrl *fsverity_alloc_bio_ctrl(gfp_t gfp_flags)
+{
+ struct fsverity_bio_ctrl *ctrl;
+
+ ctrl = kmalloc(sizeof(struct fsverity_bio_ctrl), gfp_flags);
+ if (!ctrl)
+ return ERR_PTR(-ENOMEM);
+ INIT_LIST_HEAD(&ctrl->bio_group);
+ /* The submit path keeps its reference in case all of the bio
+ * completions happen before submit path completes, in which
+ * case the submit path does the completion tasks. */
+ atomic_set(&ctrl->nr_bios, 1);
+ return ctrl;
+}
+EXPORT_SYMBOL(fsverity_alloc_bio_ctrl);
+
+void fsverity_release_bio_ctrl(struct fsverity_bio_ctrl *ctrl)
+{
+ if (atomic_dec_and_test(&ctrl->nr_bios)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Freeing ctrl [0x%p]\n",
+ __func__, ctrl);
+#endif
+ kfree(ctrl);
+ }
+#ifdef CONFIG_FS_VERITY_DEBUG
+ else {
+ printk(KERN_WARNING "%s: ctrl [0x%p] released but not freed\n",
+ __func__, ctrl);
+
+ }
+#endif
+}
+EXPORT_SYMBOL(fsverity_release_bio_ctrl);
+
+#define FS_VERITY_INLINE_AUTH_LVLS 10
+
+struct auth_lvl_desc {
+ pgoff_t index; /* auth page index */
+ unsigned nr; /* hash nr within auth page */
+};
+
+/**
+ * __hash_at_level() - calculate the page index and hash offset in tree
+ * @vi: fsverity_info
+ * @dpg_idx: The page index of the data page
+ * @lvl: The level of the tree for the target hash
+ * @hpg_idx: The hash tree page index for the target hash
+ * @hash_nr: The hash number within the page containing target hash
+ */
+static void __hash_at_level(struct fsverity_info *vi, pgoff_t dpg_idx,
+ unsigned lvl, pgoff_t *hpg_idx,
+ unsigned *hash_nr)
+{
+ u64 hash_within_lvl_region;
+ u64 idx_nr_containing_hash;
+
+ /**
+ * The number of the hash in the group starting at the level
+ * region. For example, if the hash is 32 bytes in length and
+ * idx corresponds to blocks of size 4KiB, then there will be
+ * 128 (2^7) hashes per block of hashes. If the data page
+ * index is 65668 and we're at level 1, then we're (65668 /
+ * 2^7) = 513 hashes into the level 1 region.
+ */
+ hash_within_lvl_region = dpg_idx >> (((vi->depth - 1) - lvl) *
+ vi->hashes_per_block_bits);
+
+ /**
+ * If we're 513 hashes into the level 1 region, and if each
+ * block in the region contains 128 (2^7) hashes, then we're
+ * at block number (513 / 2^7) = 4 in the level 1 region.
+ */
+ idx_nr_containing_hash =
+ hash_within_lvl_region >> vi->hashes_per_block_bits;
+
+ /**
+ * We offset past the start of the level's region to get to
+ * the block that contains the hash. For example, if level 1
+ * region starts at block offset 12, then the block index
+ * containing the target hash is at (12 + 4) = 16.
+ */
+ *hpg_idx = vi->hash_lvl_region_idx[lvl] + idx_nr_containing_hash;
+
+ /**
+ * If we're 513 hashes into the level 1 region, and if there
+ * are 128 (2^7) hashes per block, then within the target
+ * block we are at hash number (513 & 127) = 1 within the
+ * target block.
+ */
+ *hash_nr = hash_within_lvl_region &
+ ((1 << vi->hashes_per_block_bits) - 1);
+}
+
+/**
+ * @page: Locked and up-to-date
+ */
+static int __hash_page(struct page *page)
+{
+ int err;
+ char *virt;
+ struct fsverity_info *vi;
+ struct crypto_shash *tfm = crypto_alloc_shash("sha256", 0, 0);
+ SHASH_DESC_ON_STACK(desc, tfm);
+
+ desc->tfm = tfm;
+ desc->flags = 0;
+
+ virt = kmap(page);
+ err = crypto_shash_init(desc);
+ if (err) {
+ WARN_ON_ONCE(1);
+ goto done_hashing;
+ }
+ vi = page->mapping->host->i_verity_info;
+ err = crypto_shash_update(desc, vi->salt, FS_VERITY_SALT_SIZE);
+ if (err) {
+ WARN_ON_ONCE(1);
+ goto done_hashing;
+ }
+ err = crypto_shash_update(desc, virt, PAGE_SIZE);
+ if (err) {
+ WARN_ON_ONCE(1);
+ goto done_hashing;
+ }
+ err = crypto_shash_final(desc, page->contents_hash);
+ if (err) {
+ WARN_ON_ONCE(1);
+ goto done_hashing;
+ }
+ page->contents_hashed = true;
+done_hashing:
+ kunmap(page);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (!err) {
+ char page_hash_hex[2 * SHA256_DIGEST_SIZE + 1];
+
+ bin2hex(page_hash_hex, page->contents_hash,
+ SHA256_DIGEST_SIZE);
+ page_hash_hex[2 * SHA256_DIGEST_SIZE] = '\0';
+
+ printk(KERN_WARNING "%s: Hashed page [0x%p] with index [%lu]: "
+ "[%s]\n", __func__, page, page->index, page_hash_hex);
+ }
+#endif
+ if (tfm)
+ crypto_free_shash(tfm);
+ return err;
+}
+
+/**
+ * __auth_pages_for_data_page() -
+ * @data_page:
+ *
+ * Return: Zero on success; non-zero on error.
+ */
+static int __auth_pages_for_data_page(struct fsverity_info *vi,
+ struct page *data_page,
+ struct list_head *auth_pages,
+ struct fsverity_bio_ctrl *ctrl)
+{
+ struct auth_lvl_desc lvls[FS_VERITY_INLINE_AUTH_LVLS];
+ unsigned i;
+ int err = 0;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ if (vi->depth > FS_VERITY_INLINE_AUTH_LVLS) {
+ printk_ratelimited(KERN_WARNING "%s: vi->depth = [%d]; max is "
+ "[%d]\n", __func__, vi->depth,
+ FS_VERITY_INLINE_AUTH_LVLS);
+ BUG();
+ }
+#endif
+
+ if (fsverity_page_in_metadata_region(data_page)) {
+ /* We might get here because of readahead. */
+ return 0;
+ }
+
+ for (i = 0; i < vi->depth; i++) {
+ __hash_at_level(vi, data_page->index, i, &lvls[i].index,
+ &lvls[i].nr);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: __hash_at_level(data_page->index = "
+ "[%lu], i (depth) = [%d], lvls[i].index = [%lu], "
+ "lvls[i].nr = %d)\n",
+ __func__, data_page->index, i, lvls[i].index,
+ lvls[i].nr);
+#endif
+ }
+ for (i = 0; i < vi->depth; i++) {
+ struct page *auth_page;
+ /* TODO(mhalcrow): Tree? Hlist? */
+ list_for_each_entry(auth_page, auth_pages, lru) {
+ if (auth_page->index == lvls[i].index) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Found existing "
+ "auth_page in get queue with index "
+ "[%lu]; skipping\n",
+ __func__, auth_page->index);
+#endif
+ goto next_lvl;
+ }
+ }
+ auth_page =
+ data_page->mapping->host->i_sb->s_vop->read_file_page(
+ data_page->mapping->host, lvls[i].index, ctrl);
+ /* Potential optimization: skip locked pages, then go
+ * back later to try again on the ones that were
+ * skipped */
+ if (!auth_page) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: ERROR queuing page with "
+ "index [%lu]\n", __func__, lvls[i].index);
+#endif
+ err = -ENOMEM;
+ goto out;
+ }
+ auth_page->is_auth_pg = true;
+ auth_page->nr_auth_lvls = 0;
+ list_add_tail(&auth_page->lru, auth_pages);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Added auth_page "
+ "[0x%p] with index [%lu] to pending "
+ "auth_pages to read\n",
+ __func__, auth_page, auth_page->index);
+#endif
+next_lvl:
+ data_page->auth_pgs[i] = auth_page;
+ data_page->hash_nrs[i] = lvls[i].nr;
+ }
+ data_page->nr_auth_lvls = vi->depth;
+out:
+ return err;
+}
+
+/**
+ * fsverity_auth_pages() -
+ * @inode:
+ * @ctrl:
+ *
+ *
+ *
+ * Return:
+ */
+int fsverity_queue_auth_pages(struct inode *inode,
+ struct fsverity_bio_ctrl *ctrl)
+{
+ struct bio_vec *bvec;
+ struct bio *bio;
+ struct fsverity_info *vi;
+ LIST_HEAD(auth_pages);
+ int err = 0;
+
+ vi = inode->i_verity_info;
+ list_for_each_entry(bio, &ctrl->bio_group, bi_group) {
+ int i;
+
+ bio_for_each_segment_all(bvec, bio, i) {
+ err = __auth_pages_for_data_page(vi, bvec->bv_page,
+ &auth_pages, ctrl);
+ if (err) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: ERROR queuing auth "
+ "pages for data page [0x%p]: [%d]\n",
+ __func__, bvec->bv_page, err);
+#endif
+ goto out;
+ }
+ }
+ }
+out:
+ return err;
+}
+EXPORT_SYMBOL(fsverity_auth_pages);
+
+static bool __are_hashes_equal(char *expected, char *actual)
+{
+ if (crypto_memneq(expected, actual, SHA256_DIGEST_SIZE)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ char actual_hash_hex[2 * SHA256_DIGEST_SIZE + 1];
+ char expected_hash_hex[2 * SHA256_DIGEST_SIZE + 1];
+
+ bin2hex(actual_hash_hex, actual, SHA256_DIGEST_SIZE);
+ actual_hash_hex[2 * SHA256_DIGEST_SIZE] = '\0';
+ bin2hex(expected_hash_hex, expected, SHA256_DIGEST_SIZE);
+ expected_hash_hex[2 * SHA256_DIGEST_SIZE] = '\0';
+
+ printk(KERN_WARNING "%s: page has hash [%s]; expected [%s]\n",
+ __func__, actual_hash_hex, expected_hash_hex);
+#endif /* CONFIG_FS_VERITY_DEBUG */
+ return false;
+ }
+ return true;
+}
+
+static bool __verify_root_hash(struct page *page)
+{
+ struct fsverity_info *vi;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ bool equal;
+#endif
+
+ vi = page->mapping->host->i_verity_info;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: page [0x%p] is the only data page in the "
+ "file; checking against root hash\n", __func__, page);
+#endif
+ /* TODO(mhalcrow): Do this right. For now we're going to rely
+ * on pre-measure and superblock fs-verity inode pinning. */
+ if (!vi->root_hashed) {
+ printk(KERN_WARNING "%s: TODO: Get trusted root hash\n",
+ __func__);
+ memcpy(vi->root_hash, page->contents_hash, SHA256_DIGEST_SIZE);
+ vi->root_hashed = true;
+ }
+#ifdef CONFIG_FS_VERITY_DEBUG
+ equal = __are_hashes_equal(vi->root_hash, page->contents_hash);
+ if (!equal) {
+ printk(KERN_WARNING "%s: Page [0x%p] with index [%lu] hash "
+ "doesn't match root hash\n", __func__, page,
+ page->index);
+ }
+ return equal;
+#else
+ return __are_hashes_equal(vi->root_hash, page->contents_hash);
+#endif
+}
+
+static void __complete_bio_group(struct work_struct *work)
+{
+ struct bio *bio;
+ struct bio_vec *bvec;
+ struct fsverity_bio_ctrl *ctrl;
+ blk_status_t status = 0;
+ int err = 0;
+
+ ctrl = container_of(work, struct fsverity_bio_ctrl, work);
+#ifdef CONFIG_FS_VERITY_DEBUG
+ {
+ unsigned nr_bios = atomic_read(&ctrl->nr_bios);
+
+ printk(KERN_WARNING "%s: Called w/ ctrl = [0x%p]; nr_bios = "
+ "[%d]\n", __func__, ctrl, nr_bios);
+ }
+#endif
+ /* See if any bio has failed */
+ list_for_each_entry(bio, &ctrl->bio_group, bi_group) {
+ if (!status && bio->bi_status) {
+ status = bio->bi_status;
+ break;
+ }
+ }
+ if (status) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: status = [%d]; jumping straight to "
+ "complete\n", __func__, status);
+#endif
+ goto complete;
+ }
+
+ /* Hash all the pages */
+ list_for_each_entry(bio, &ctrl->bio_group, bi_group) {
+ int i;
+
+ bio_for_each_segment_all(bvec, bio, i) {
+ struct page *page = bvec->bv_page;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Looking at page [0x%p] with "
+ "index [%lu] in bio [0x%p]\n",
+ __func__, page, page->index, bio);
+#endif
+ if (!page->contents_hashed) {
+ SetPageUptodate(page);
+ err = __hash_page(page);
+ if (err) {
+ WARN_ON_ONCE(1);
+ status = BLK_STS_IOERR;
+ goto complete;
+ }
+ if (page->is_root &&
+ !__verify_root_hash(page)) {
+ WARN_ON_ONCE(1);
+ status = BLK_STS_IOERR;
+ goto complete;
+ }
+#ifdef CONFIG_FS_VERITY_DEBUG
+ BUG_ON(!page->contents_hashed);
+#endif
+ }
+ }
+ }
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ BUG_ON(status);
+#endif
+
+ /* Verify all the hashes */
+ list_for_each_entry(bio, &ctrl->bio_group, bi_group) {
+ int i;
+
+ bio_for_each_segment_all(bvec, bio, i) {
+ struct page *page = bvec->bv_page;
+ unsigned lvl;
+ char *actual_page_hash;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: page [0x%p] w/ index [%lu] "
+ "has nr_auth_lvls = [%d]\n", __func__,
+ page, page->index, page->nr_auth_lvls);
+ if (page->nr_auth_lvls == 0 && !page->is_auth_pg) {
+ /* TODO(mhalcrow): Label root at source */
+ page->is_root = true;
+ /* TODO: BUG_ON(!page->is_root); */
+ }
+#endif
+ if (page->nr_auth_lvls == 0)
+ goto verify_root;
+ actual_page_hash = page->contents_hash;
+ for (lvl = page->nr_auth_lvls - 1; lvl > 0 && !status;
+ lvl--) {
+ char *auth_pg_virt;
+ char *expected_page_hash;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Looking at lvl [%d]\n",
+ __func__, lvl);
+ if (!page->auth_pgs[lvl]->contents_hashed) {
+ printk(KERN_WARNING "%s: Auth page "
+ "[0x%p] at lvl [%d] "
+ "contents not hashed\n",
+ __func__, page->auth_pgs[lvl],
+ lvl);
+ BUG();
+ }
+#endif
+ auth_pg_virt = kmap(page->auth_pgs[lvl]);
+ expected_page_hash =
+ &auth_pg_virt[page->hash_nrs[lvl] *
+ SHA256_DIGEST_SIZE];
+ if (!__are_hashes_equal(expected_page_hash,
+ actual_page_hash)) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING
+ "%s: hash mismatch on page "
+ "[0x%p] with index [%lu], "
+ "hash_nr [%d] at lvl [%d]\n",
+ __func__, page, page->index,
+ page->hash_nrs[lvl], lvl);
+#endif
+ status = BLK_STS_IOERR;
+ }
+ kunmap(page->auth_pgs[lvl]);
+ actual_page_hash =
+ page->auth_pgs[lvl]->contents_hash;
+ }
+verify_root:
+ /* TODO(mhalcrow): Now validate level 0
+ * against the root hash */
+ printk(KERN_WARNING "%s: Skipping root verification in "
+ "prototype\n", __func__);
+ }
+ }
+
+complete:
+ /* Complete all pages */
+ list_for_each_entry(bio, &ctrl->bio_group, bi_group) {
+ int i;
+
+ if (!bio->bi_status && status)
+ bio->bi_status = status;
+ bio_for_each_segment_all(bvec, bio, i) {
+ struct page *page = bvec->bv_page;
+
+ if (bio->bi_status) {
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Setting error flag "
+ "on page [0x%p]\n",
+ __func__, page);
+#endif
+ SetPageError(page);
+ }
+#ifdef CONFIG_FS_VERITY_DEBUG
+ printk(KERN_WARNING "%s: Unlocking page [0x%p]\n",
+ __func__, page);
+#endif
+ unlock_page(page);
+ }
+ }
+ fsverity_release_bio_ctrl(ctrl);
+}
+
+void fsverity_verify_bio(struct bio *bio)
+{
+ struct fsverity_bio_ctrl *ctrl;
+
+#ifdef CONFIG_FS_VERITY_DEBUG
+ BUG_ON(!bio->bi_verity_ctrl);
+#endif
+ ctrl = bio->bi_verity_ctrl;
+#ifdef CONFIG_FS_VERITY_DEBUG
+ {
+ unsigned nr_bios = atomic_read(&ctrl->nr_bios);
+
+ printk(KERN_WARNING "%s: Called w/ bio = [0x%p]; "
+ "ctrl = [0x%p]; nr_bios = [%d]\n",
+ __func__, bio, ctrl, nr_bios);
+ }
+#endif
+ if (!atomic_dec_and_test(&ctrl->nr_bios))
+ return;
+ atomic_inc(&ctrl->nr_bios);
+ INIT_WORK(&ctrl->work, __complete_bio_group);
+ queue_work(fsverity_read_workqueue, &ctrl->work);
+}
+EXPORT_SYMBOL(fsverity_verify_bio);
+
+size_t fsverity_i_size(struct inode *inode)
+{
+ return inode->i_verity_info->i_size;
+}
+EXPORT_SYMBOL(fsverity_i_size);
+
+bool fsverity_page_in_metadata_region(struct page *page)
+{
+ struct fsverity_info *vi;
+
+ vi = page->mapping->host->i_verity_info;
+ if (!vi)
+ return false;
+ if (page->index >= ((vi->i_size + PAGE_SIZE - 1) >> PAGE_SHIFT))
+ return true;
+ return false;
+}
+EXPORT_SYMBOL(struct page *page);
+
+/**
+ * fsverity_init() - set up for fs-verity
+ */
+static int __init fsverity_init(void)
+{
+ fsverity_read_workqueue = alloc_workqueue("fsverity_read_queue",
+ WQ_HIGHPRI, 0);
+ if (!fsverity_read_workqueue)
+ goto fail;
+
+ fsverity_info_cachep = KMEM_CACHE(fsverity_info, SLAB_RECLAIM_ACCOUNT);
+ if (!fsverity_info_cachep)
+ goto fail;
+
+ return 0;
+fail:
+ return -ENOMEM;
+}
+module_init(fsverity_init)
+
+/**
+ * fsverity_exit() - shutdown the fs-verity
+ */
+static void __exit fsverity_exit(void)
+{
+ kmem_cache_destroy(fsverity_info_cachep);
+
+ if (fsverity_read_workqueue)
+ destroy_workqueue(fsverity_read_workqueue);
+}
+module_exit(fsverity_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
index bf18b95ed92d5..664bbd854e5f5 100644
--- a/include/linux/blk_types.h
+++ b/include/linux/blk_types.h
@@ -124,6 +124,11 @@ struct bio {
bio_end_io_t *bi_end_io;
void *bi_private;
+
+ /* TODO(mhalcrow): Fold into bi_private structure together w/ fscrypt */
+#if defined(CONFIG_FS_VERITY)
+ struct fsverity_bio_ctrl *bi_verity_ctrl;
+#endif
#ifdef CONFIG_BLK_CGROUP
/*
* Optional ioc and css associated with this bio. Put on bio
@@ -142,6 +147,11 @@ struct bio {
#endif
};
+ /* TODO(mhalcrow): Link together via bi_private structure? */
+#if defined(CONFIG_FS_VERITY)
+ struct list_head bi_group; /* bios completing together */
+#endif
+
unsigned short bi_vcnt; /* how many bio_vec's */
/*
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 2a815560fda0e..809f600238c26 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -59,6 +59,8 @@ struct workqueue_struct;
struct iov_iter;
struct fscrypt_info;
struct fscrypt_operations;
+struct fsverity_info;
+struct fsverity_operations;
extern void __init inode_init(void);
extern void __init inode_init_early(void);
@@ -669,6 +671,11 @@ struct inode {
struct fscrypt_info *i_crypt_info;
#endif
+#if IS_ENABLED(CONFIG_FS_VERITY)
+ struct fsverity_info *i_verity_info;
+ /* TODO(mhalcrow): Not for upstream */
+ struct list_head i_fsverity_list; /* Not for upstream */
+#endif
void *i_private; /* fs or device private pointer */
} __randomize_layout;
@@ -1363,6 +1370,7 @@ struct super_block {
const struct xattr_handler **s_xattr;
const struct fscrypt_operations *s_cop;
+ const struct fsverity_operations *s_vop;
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
@@ -1446,6 +1454,12 @@ struct super_block {
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
+#ifdef CONFIG_FS_VERITY
+ /* TODO(mhalcrow): Not for upstream */
+ /* fs-verity inodes being pinned in memory */
+ spinlock_t s_inode_fsveritylist_lock;
+ struct list_head s_inodes_fsverity;
+#endif
} __randomize_layout;
/* Helper functions so that in most cases filesystems will
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
new file mode 100644
index 0000000000000..21a9c16e84c69
--- /dev/null
+++ b/include/linux/fsverity.h
@@ -0,0 +1,105 @@
+/*
+ * fsverity.h: declarations for file-based verity
+ *
+ * Copyright (C) 2018, Google, Inc.
+ *
+ * Written by Jaegeuk Kim and Michael Halcrow.
+ */
+
+#ifndef _LINUX_FSVERITY_H
+#define _LINUX_FSVERITY_H
+
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <uapi/linux/fs.h>
+
+/*
+ * fsverity operations for filesystems
+ */
+struct fsverity_operations {
+ int (*is_verity)(struct inode *);
+ int (*set_verity)(struct inode *, int);
+ struct page *(*read_file_page)(struct inode *, pgoff_t,
+ struct fsverity_bio_ctrl *);
+};
+
+/* Allocated once per bio group and shared among all members. The last
+ * bio to complete assumes ownership and finalizes/destructs. */
+struct fsverity_bio_ctrl {
+ struct work_struct work;
+ struct list_head bio_group;
+ atomic_t nr_bios;
+};
+
+#if IS_ENABLED(CONFIG_FS_VERITY)
+
+extern int fsverity_measure_info(struct inode *inode,
+ struct fsverity_root_hash *root_hash);
+extern int fsverity_get_info(struct inode *inode);
+extern void fsverity_put_info(struct inode *inode);
+extern int fsverity_enable(struct inode *inode, struct fsverity_set *set);
+extern struct fsverity_bio_ctrl *fsverity_alloc_bio_ctrl(gfp_t gfp_flags);
+extern void fsverity_release_bio_ctrl(struct fsverity_bio_ctrl *ctrl);
+extern int fsverity_queue_auth_pages(struct inode *inode,
+ struct fsverity_bio_ctrl *ctrl);
+extern void fsverity_verify_bio(struct bio *bio);
+extern size_t fsverity_i_size(struct inode *inode);
+extern bool fsverity_page_in_metadata_region(struct page *page);
+
+#else
+
+static inline int fsverity_measure_info(struct inode *inode,
+ struct fsverity_root_hash *root_hash)
+{
+ return -ENOTSUPP;
+}
+
+static inline int fsverity_get_info(struct inode *inode)
+{
+ return -ENOTSUPP;
+}
+
+static inline void fsverity_put_info(struct inode *inode)
+{
+ return;
+}
+
+static inline int fsverity_enable(struct inode *inode, struct fsverity_set *set)
+{
+ return -ENOTSUPP;
+}
+
+static struct fsverity_bio_ctrl *fsverity_alloc_bio_ctrl(gfp_t gfp_flags)
+{
+ return -ENOTSUPP;
+}
+
+static void fsverity_release_bio_ctrl(struct fsverity_bio_ctrl *ctrl)
+{
+ return;
+}
+
+static int fsverity_queue_auth_pages(struct inode *inode,
+ struct fsverity_bio_ctrl *ctrl)
+{
+ return -ENOTSUPP;
+}
+
+static void fsverity_verify_bio(struct bio *bio)
+{
+ return;
+}
+
+static size_t fsverity_i_size(struct inode *inode)
+{
+ return 0;
+}
+
+static bool fsverity_page_in_metadata_region(struct page *page)
+{
+ return false;
+}
+
+#endif /* CONFIG_FS_VERITY */
+
+#endif /* _LINUX_FSVERITY_H */
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index fd1af6b9591d5..f3b1912f3d488 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -192,6 +192,18 @@ struct page {
struct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */
};
+ /* TODO(mhalcrow): Hang something off private instead */
+#ifdef CONFIG_FS_VERITY
+ bool is_auth_pg;
+ bool is_root;
+ bool queued;
+ unsigned nr_auth_lvls; /* When 0, there's just the root hash */
+ struct page *auth_pgs[64]; /* TODO: Dynamically allocate */
+ unsigned hash_nrs[64]; /* TODO: Dynamically allocate */
+ bool contents_hashed;
+ char contents_hash[32]; /* TODO: Dynamically allocate */
+#endif /* CONFIG_FS_VERITY */
+
#ifdef CONFIG_MEMCG
struct mem_cgroup *mem_cgroup;
#endif
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index d2a8313fabd72..0f1f803cd323b 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -301,6 +301,27 @@ struct fscrypt_key {
__u32 size;
};
+/* file-based verity support */
+struct fsverity_set {
+ __u64 offset;
+ __u64 flags;
+};
+
+#define FS_VERITY_ROOT_HASH_ALGO_SHA256 0x0000
+
+struct fsverity_root_hash {
+ short root_hash_algorithm;
+ short flags;
+ __u8 reserved[4];
+ __u8 root_hash[64];
+};
+
+#define FS_IOC_MEASURE_FSVERITY _IOW('f', 133, \
+ struct fsverity_root_hash)
+#define FS_IOC_SET_FSVERITY _IOW('f', 134, struct fsverity_set)
+
+#define FSVERITY_FLAG_ENABLED 0x0001
+
/*
* Inode flags (FS_IOC_GETFLAGS / FS_IOC_SETFLAGS)
*