diff options
author | Michael Halcrow <mhalcrow@google.com> | 2018-03-02 14:55:02 -0800 |
---|---|---|
committer | Michael Halcrow <mhalcrow@google.com> | 2018-03-05 16:32:23 -0800 |
commit | 077ca41f1cb6ebe504d933017f08d0c0c4787109 (patch) | |
tree | 2f7a5dff7c543c118da755fa210530e10a8e12ee | |
parent | 7e30309968c18b504320275d527914272fbe1a1c (diff) | |
download | linux-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/Kconfig | 2 | ||||
-rw-r--r-- | fs/Makefile | 1 | ||||
-rw-r--r-- | fs/f2fs/Kconfig | 10 | ||||
-rw-r--r-- | fs/f2fs/data.c | 324 | ||||
-rw-r--r-- | fs/f2fs/dir.c | 4 | ||||
-rw-r--r-- | fs/f2fs/f2fs.h | 73 | ||||
-rw-r--r-- | fs/f2fs/file.c | 162 | ||||
-rw-r--r-- | fs/f2fs/gc.c | 10 | ||||
-rw-r--r-- | fs/f2fs/inode.c | 2 | ||||
-rw-r--r-- | fs/f2fs/super.c | 11 | ||||
-rw-r--r-- | fs/super.c | 27 | ||||
-rw-r--r-- | fs/verity/Kconfig | 17 | ||||
-rw-r--r-- | fs/verity/Makefile | 3 | ||||
-rw-r--r-- | fs/verity/fsverity_private.h | 96 | ||||
-rw-r--r-- | fs/verity/verity.c | 1257 | ||||
-rw-r--r-- | include/linux/blk_types.h | 10 | ||||
-rw-r--r-- | include/linux/fs.h | 14 | ||||
-rw-r--r-- | include/linux/fsverity.h | 105 | ||||
-rw-r--r-- | include/linux/mm_types.h | 12 | ||||
-rw-r--r-- | include/uapi/linux/fs.h | 21 |
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) * |