[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20190622231237.GM19686@jaegeuk-macbookpro.roam.corp.google.com>
Date: Sat, 22 Jun 2019 16:12:37 -0700
From: Jaegeuk Kim <jaegeuk@...nel.org>
To: Eric Biggers <ebiggers@...nel.org>
Cc: linux-fscrypt@...r.kernel.org, linux-ext4@...r.kernel.org,
linux-f2fs-devel@...ts.sourceforge.net,
linux-fsdevel@...r.kernel.org, linux-api@...r.kernel.org,
linux-integrity@...r.kernel.org,
"Theodore Y . Ts'o" <tytso@....edu>,
Victor Hsieh <victorhsieh@...gle.com>,
Chandan Rajendra <chandan@...ux.vnet.ibm.com>,
Dave Chinner <david@...morbit.com>,
Christoph Hellwig <hch@....de>,
"Darrick J . Wong" <darrick.wong@...cle.com>,
Linus Torvalds <torvalds@...ux-foundation.org>
Subject: Re: [PATCH v5 16/16] f2fs: add fs-verity support
On 06/20, Eric Biggers wrote:
> From: Eric Biggers <ebiggers@...gle.com>
>
> Add fs-verity support to f2fs. fs-verity is a filesystem feature that
> enables transparent integrity protection and authentication of read-only
> files. It uses a dm-verity like mechanism at the file level: a Merkle
> tree is used to verify any block in the file in log(filesize) time. It
> is implemented mainly by helper functions in fs/verity/. See
> Documentation/filesystems/fsverity.rst for the full documentation.
>
> The f2fs support for fs-verity consists of:
>
> - Adding a filesystem feature flag and an inode flag for fs-verity.
>
> - Implementing the fsverity_operations to support enabling verity on an
> inode and reading/writing the verity metadata.
>
> - Updating ->readpages() to verify data as it's read from verity files
> and to support reading verity metadata pages.
>
> - Updating ->write_begin(), ->write_end(), and ->writepages() to support
> writing verity metadata pages.
>
> - Calling the fs-verity hooks for ->open(), ->setattr(), and ->ioctl().
>
> Like ext4, f2fs stores the verity metadata (Merkle tree and
> fsverity_descriptor) past the end of the file, starting at the first 64K
> boundary beyond i_size. This approach works because (a) verity files
> are readonly, and (b) pages fully beyond i_size aren't visible to
> userspace but can be read/written internally by f2fs with only some
> relatively small changes to f2fs. Extended attributes cannot be used
> because (a) f2fs limits the total size of an inode's xattr entries to
> 4096 bytes, which wouldn't be enough for even a single Merkle tree
> block, and (b) f2fs encryption doesn't encrypt xattrs, yet the verity
> metadata *must* be encrypted when the file is because it contains hashes
> of the plaintext data.
>
Acked-by: Jaegeuk Kim <jaegeuk@...nel.org>
> Signed-off-by: Eric Biggers <ebiggers@...gle.com>
> ---
> fs/f2fs/Makefile | 1 +
> fs/f2fs/data.c | 72 +++++++++++++--
> fs/f2fs/f2fs.h | 23 ++++-
> fs/f2fs/file.c | 40 ++++++++
> fs/f2fs/inode.c | 5 +-
> fs/f2fs/super.c | 3 +
> fs/f2fs/sysfs.c | 11 +++
> fs/f2fs/verity.c | 233 +++++++++++++++++++++++++++++++++++++++++++++++
> fs/f2fs/xattr.h | 2 +
> 9 files changed, 376 insertions(+), 14 deletions(-)
> create mode 100644 fs/f2fs/verity.c
>
> diff --git a/fs/f2fs/Makefile b/fs/f2fs/Makefile
> index 776c4b93650496..2aaecc63834fc8 100644
> --- a/fs/f2fs/Makefile
> +++ b/fs/f2fs/Makefile
> @@ -8,3 +8,4 @@ f2fs-$(CONFIG_F2FS_STAT_FS) += debug.o
> f2fs-$(CONFIG_F2FS_FS_XATTR) += xattr.o
> f2fs-$(CONFIG_F2FS_FS_POSIX_ACL) += acl.o
> f2fs-$(CONFIG_F2FS_IO_TRACE) += trace.o
> +f2fs-$(CONFIG_FS_VERITY) += verity.o
> diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
> index eda4181d20926b..8f175d47291d0b 100644
> --- a/fs/f2fs/data.c
> +++ b/fs/f2fs/data.c
> @@ -73,6 +73,7 @@ static enum count_type __read_io_type(struct page *page)
> enum bio_post_read_step {
> STEP_INITIAL = 0,
> STEP_DECRYPT,
> + STEP_VERITY,
> };
>
> struct bio_post_read_ctx {
> @@ -119,8 +120,23 @@ static void decrypt_work(struct work_struct *work)
> bio_post_read_processing(ctx);
> }
>
> +static void verity_work(struct work_struct *work)
> +{
> + struct bio_post_read_ctx *ctx =
> + container_of(work, struct bio_post_read_ctx, work);
> +
> + fsverity_verify_bio(ctx->bio);
> +
> + bio_post_read_processing(ctx);
> +}
> +
> static void bio_post_read_processing(struct bio_post_read_ctx *ctx)
> {
> + /*
> + * We use different work queues for decryption and for verity because
> + * verity may require reading metadata pages that need decryption, and
> + * we shouldn't recurse to the same workqueue.
> + */
> switch (++ctx->cur_step) {
> case STEP_DECRYPT:
> if (ctx->enabled_steps & (1 << STEP_DECRYPT)) {
> @@ -130,6 +146,14 @@ static void bio_post_read_processing(struct bio_post_read_ctx *ctx)
> }
> ctx->cur_step++;
> /* fall-through */
> + case STEP_VERITY:
> + if (ctx->enabled_steps & (1 << STEP_VERITY)) {
> + INIT_WORK(&ctx->work, verity_work);
> + fsverity_enqueue_verify_work(&ctx->work);
> + return;
> + }
> + ctx->cur_step++;
> + /* fall-through */
> default:
> __read_end_io(ctx->bio);
> }
> @@ -553,8 +577,15 @@ void f2fs_submit_page_write(struct f2fs_io_info *fio)
> up_write(&io->io_rwsem);
> }
>
> +static inline bool f2fs_need_verity(const struct inode *inode, pgoff_t idx)
> +{
> + return fsverity_active(inode) &&
> + idx < DIV_ROUND_UP(inode->i_size, PAGE_SIZE);
> +}
> +
> static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
> - unsigned nr_pages, unsigned op_flag)
> + unsigned nr_pages, unsigned op_flag,
> + pgoff_t first_idx)
> {
> struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
> struct bio *bio;
> @@ -570,6 +601,10 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
>
> if (f2fs_encrypted_file(inode))
> post_read_steps |= 1 << STEP_DECRYPT;
> +
> + if (f2fs_need_verity(inode, first_idx))
> + post_read_steps |= 1 << STEP_VERITY;
> +
> if (post_read_steps) {
> ctx = mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
> if (!ctx) {
> @@ -591,7 +626,7 @@ static int f2fs_submit_page_read(struct inode *inode, struct page *page,
> struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
> struct bio *bio;
>
> - bio = f2fs_grab_read_bio(inode, blkaddr, 1, 0);
> + bio = f2fs_grab_read_bio(inode, blkaddr, 1, 0, page->index);
> if (IS_ERR(bio))
> return PTR_ERR(bio);
>
> @@ -1514,6 +1549,15 @@ int f2fs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
> return ret;
> }
>
> +static inline loff_t f2fs_readpage_limit(struct inode *inode)
> +{
> + if (IS_ENABLED(CONFIG_FS_VERITY) &&
> + (IS_VERITY(inode) || f2fs_verity_in_progress(inode)))
> + return inode->i_sb->s_maxbytes;
> +
> + return i_size_read(inode);
> +}
> +
> static int f2fs_read_single_page(struct inode *inode, struct page *page,
> unsigned nr_pages,
> struct f2fs_map_blocks *map,
> @@ -1532,7 +1576,7 @@ static int f2fs_read_single_page(struct inode *inode, struct page *page,
>
> block_in_file = (sector_t)page->index;
> last_block = block_in_file + nr_pages;
> - last_block_in_file = (i_size_read(inode) + blocksize - 1) >>
> + last_block_in_file = (f2fs_readpage_limit(inode) + blocksize - 1) >>
> blkbits;
> if (last_block > last_block_in_file)
> last_block = last_block_in_file;
> @@ -1576,6 +1620,11 @@ static int f2fs_read_single_page(struct inode *inode, struct page *page,
> } else {
> zero_out:
> zero_user_segment(page, 0, PAGE_SIZE);
> + if (f2fs_need_verity(inode, page->index) &&
> + !fsverity_verify_page(page)) {
> + ret = -EIO;
> + goto out;
> + }
> if (!PageUptodate(page))
> SetPageUptodate(page);
> unlock_page(page);
> @@ -1594,7 +1643,7 @@ static int f2fs_read_single_page(struct inode *inode, struct page *page,
> }
> if (bio == NULL) {
> bio = f2fs_grab_read_bio(inode, block_nr, nr_pages,
> - is_readahead ? REQ_RAHEAD : 0);
> + is_readahead ? REQ_RAHEAD : 0, page->index);
> if (IS_ERR(bio)) {
> ret = PTR_ERR(bio);
> bio = NULL;
> @@ -1991,7 +2040,7 @@ static int __write_data_page(struct page *page, bool *submitted,
> if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
> goto redirty_out;
>
> - if (page->index < end_index)
> + if (page->index < end_index || f2fs_verity_in_progress(inode))
> goto write;
>
> /*
> @@ -2383,7 +2432,8 @@ static int prepare_write_begin(struct f2fs_sb_info *sbi,
> * the block addresses when there is no need to fill the page.
> */
> if (!f2fs_has_inline_data(inode) && len == PAGE_SIZE &&
> - !is_inode_flag_set(inode, FI_NO_PREALLOC))
> + !is_inode_flag_set(inode, FI_NO_PREALLOC) &&
> + !f2fs_verity_in_progress(inode))
> return 0;
>
> /* f2fs_lock_op avoids race between write CP and convert_inline_page */
> @@ -2522,7 +2572,8 @@ static int f2fs_write_begin(struct file *file, struct address_space *mapping,
> if (len == PAGE_SIZE || PageUptodate(page))
> return 0;
>
> - if (!(pos & (PAGE_SIZE - 1)) && (pos + len) >= i_size_read(inode)) {
> + if (!(pos & (PAGE_SIZE - 1)) && (pos + len) >= i_size_read(inode) &&
> + !f2fs_verity_in_progress(inode)) {
> zero_user_segment(page, len, PAGE_SIZE);
> return 0;
> }
> @@ -2585,7 +2636,8 @@ static int f2fs_write_end(struct file *file,
>
> set_page_dirty(page);
>
> - if (pos + copied > i_size_read(inode))
> + if (pos + copied > i_size_read(inode) &&
> + !f2fs_verity_in_progress(inode))
> f2fs_i_size_write(inode, pos + copied);
> unlock_out:
> f2fs_put_page(page, 1);
> @@ -2906,7 +2958,9 @@ void f2fs_clear_page_cache_dirty_tag(struct page *page)
>
> int __init f2fs_init_post_read_processing(void)
> {
> - bio_post_read_ctx_cache = KMEM_CACHE(bio_post_read_ctx, 0);
> + bio_post_read_ctx_cache =
> + kmem_cache_create("f2fs_bio_post_read_ctx",
> + sizeof(struct bio_post_read_ctx), 0, 0, NULL);
> if (!bio_post_read_ctx_cache)
> goto fail;
> bio_post_read_ctx_pool =
> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
> index 06b89a9862ab2b..8477191ad1c9b2 100644
> --- a/fs/f2fs/f2fs.h
> +++ b/fs/f2fs/f2fs.h
> @@ -25,6 +25,7 @@
> #include <crypto/hash.h>
>
> #include <linux/fscrypt.h>
> +#include <linux/fsverity.h>
>
> #ifdef CONFIG_F2FS_CHECK_FS
> #define f2fs_bug_on(sbi, condition) BUG_ON(condition)
> @@ -148,7 +149,7 @@ struct f2fs_mount_info {
> #define F2FS_FEATURE_QUOTA_INO 0x0080
> #define F2FS_FEATURE_INODE_CRTIME 0x0100
> #define F2FS_FEATURE_LOST_FOUND 0x0200
> -#define F2FS_FEATURE_VERITY 0x0400 /* reserved */
> +#define F2FS_FEATURE_VERITY 0x0400
> #define F2FS_FEATURE_SB_CHKSUM 0x0800
>
> #define __F2FS_HAS_FEATURE(raw_super, mask) \
> @@ -626,7 +627,7 @@ enum {
> #define FADVISE_ENC_NAME_BIT 0x08
> #define FADVISE_KEEP_SIZE_BIT 0x10
> #define FADVISE_HOT_BIT 0x20
> -#define FADVISE_VERITY_BIT 0x40 /* reserved */
> +#define FADVISE_VERITY_BIT 0x40
>
> #define FADVISE_MODIFIABLE_BITS (FADVISE_COLD_BIT | FADVISE_HOT_BIT)
>
> @@ -646,6 +647,8 @@ enum {
> #define file_is_hot(inode) is_file(inode, FADVISE_HOT_BIT)
> #define file_set_hot(inode) set_file(inode, FADVISE_HOT_BIT)
> #define file_clear_hot(inode) clear_file(inode, FADVISE_HOT_BIT)
> +#define file_is_verity(inode) is_file(inode, FADVISE_VERITY_BIT)
> +#define file_set_verity(inode) set_file(inode, FADVISE_VERITY_BIT)
>
> #define DEF_DIR_LEVEL 0
>
> @@ -2344,6 +2347,7 @@ static inline void f2fs_change_bit(unsigned int nr, char *addr)
> #define F2FS_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/
> #define F2FS_HUGE_FILE_FL 0x00040000 /* Set to each huge file */
> #define F2FS_EXTENTS_FL 0x00080000 /* Inode uses extents */
> +#define F2FS_VERITY_FL 0x00100000 /* Verity protected inode */
> #define F2FS_EA_INODE_FL 0x00200000 /* Inode used for large EA */
> #define F2FS_EOFBLOCKS_FL 0x00400000 /* Blocks allocated beyond EOF */
> #define F2FS_NOCOW_FL 0x00800000 /* Do not cow file */
> @@ -2351,7 +2355,7 @@ static inline void f2fs_change_bit(unsigned int nr, char *addr)
> #define F2FS_PROJINHERIT_FL 0x20000000 /* Create with parents projid */
> #define F2FS_RESERVED_FL 0x80000000 /* reserved for ext4 lib */
>
> -#define F2FS_FL_USER_VISIBLE 0x30CBDFFF /* User visible flags */
> +#define F2FS_FL_USER_VISIBLE 0x30DBDFFF /* User visible flags */
> #define F2FS_FL_USER_MODIFIABLE 0x204BC0FF /* User modifiable flags */
>
> /* Flags we can manipulate with through F2FS_IOC_FSSETXATTR */
> @@ -2417,6 +2421,7 @@ enum {
> FI_PROJ_INHERIT, /* indicate file inherits projectid */
> FI_PIN_FILE, /* indicate file should not be gced */
> FI_ATOMIC_REVOKE_REQUEST, /* request to drop atomic data */
> + FI_VERITY_IN_PROGRESS, /* building fs-verity Merkle tree */
> };
>
> static inline void __mark_inode_dirty_flag(struct inode *inode,
> @@ -2456,6 +2461,12 @@ static inline void clear_inode_flag(struct inode *inode, int flag)
> __mark_inode_dirty_flag(inode, flag, false);
> }
>
> +static inline bool f2fs_verity_in_progress(struct inode *inode)
> +{
> + return IS_ENABLED(CONFIG_FS_VERITY) &&
> + is_inode_flag_set(inode, FI_VERITY_IN_PROGRESS);
> +}
> +
> static inline void set_acl_inode(struct inode *inode, umode_t mode)
> {
> F2FS_I(inode)->i_acl_mode = mode;
> @@ -3524,6 +3535,9 @@ void f2fs_exit_sysfs(void);
> int f2fs_register_sysfs(struct f2fs_sb_info *sbi);
> void f2fs_unregister_sysfs(struct f2fs_sb_info *sbi);
>
> +/* verity.c */
> +extern const struct fsverity_operations f2fs_verityops;
> +
> /*
> * crypto support
> */
> @@ -3546,7 +3560,7 @@ static inline void f2fs_set_encrypted_inode(struct inode *inode)
> */
> static inline bool f2fs_post_read_required(struct inode *inode)
> {
> - return f2fs_encrypted_file(inode);
> + return f2fs_encrypted_file(inode) || fsverity_active(inode);
> }
>
> #define F2FS_FEATURE_FUNCS(name, flagname) \
> @@ -3564,6 +3578,7 @@ F2FS_FEATURE_FUNCS(flexible_inline_xattr, FLEXIBLE_INLINE_XATTR);
> F2FS_FEATURE_FUNCS(quota_ino, QUOTA_INO);
> F2FS_FEATURE_FUNCS(inode_crtime, INODE_CRTIME);
> F2FS_FEATURE_FUNCS(lost_found, LOST_FOUND);
> +F2FS_FEATURE_FUNCS(verity, VERITY);
> F2FS_FEATURE_FUNCS(sb_chksum, SB_CHKSUM);
>
> #ifdef CONFIG_BLK_DEV_ZONED
> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
> index 45b45f37d347e4..6706c2081941a2 100644
> --- a/fs/f2fs/file.c
> +++ b/fs/f2fs/file.c
> @@ -493,6 +493,10 @@ static int f2fs_file_open(struct inode *inode, struct file *filp)
> {
> int err = fscrypt_file_open(inode, filp);
>
> + if (err)
> + return err;
> +
> + err = fsverity_file_open(inode, filp);
> if (err)
> return err;
>
> @@ -781,6 +785,10 @@ int f2fs_setattr(struct dentry *dentry, struct iattr *attr)
> if (err)
> return err;
>
> + err = fsverity_prepare_setattr(dentry, attr);
> + if (err)
> + return err;
> +
> if (is_quota_modification(inode, attr)) {
> err = dquot_initialize(inode);
> if (err)
> @@ -1656,6 +1664,8 @@ static int f2fs_ioc_getflags(struct file *filp, unsigned long arg)
>
> if (IS_ENCRYPTED(inode))
> flags |= F2FS_ENCRYPT_FL;
> + if (IS_VERITY(inode))
> + flags |= F2FS_VERITY_FL;
> if (f2fs_has_inline_data(inode) || f2fs_has_inline_dentry(inode))
> flags |= F2FS_INLINE_DATA_FL;
> if (is_inode_flag_set(inode, FI_PIN_FILE))
> @@ -2980,6 +2990,30 @@ static int f2fs_ioc_precache_extents(struct file *filp, unsigned long arg)
> return f2fs_precache_extents(file_inode(filp));
> }
>
> +static int f2fs_ioc_enable_verity(struct file *filp, unsigned long arg)
> +{
> + struct inode *inode = file_inode(filp);
> +
> + f2fs_update_time(F2FS_I_SB(inode), REQ_TIME);
> +
> + if (!f2fs_sb_has_verity(F2FS_I_SB(inode))) {
> + f2fs_msg(inode->i_sb, KERN_WARNING,
> + "Can't enable fs-verity on inode %lu: the verity feature is not enabled on this filesystem.\n",
> + inode->i_ino);
> + return -EOPNOTSUPP;
> + }
> +
> + return fsverity_ioctl_enable(filp, (const void __user *)arg);
> +}
> +
> +static int f2fs_ioc_measure_verity(struct file *filp, unsigned long arg)
> +{
> + if (!f2fs_sb_has_verity(F2FS_I_SB(file_inode(filp))))
> + return -EOPNOTSUPP;
> +
> + return fsverity_ioctl_measure(filp, (void __user *)arg);
> +}
> +
> long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> {
> if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(filp)))))
> @@ -3036,6 +3070,10 @@ 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_ENABLE_VERITY:
> + return f2fs_ioc_enable_verity(filp, arg);
> + case FS_IOC_MEASURE_VERITY:
> + return f2fs_ioc_measure_verity(filp, arg);
> default:
> return -ENOTTY;
> }
> @@ -3149,6 +3187,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_ENABLE_VERITY:
> + case FS_IOC_MEASURE_VERITY:
> break;
> default:
> return -ENOIOCTLCMD;
> diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c
> index ccb02226dd2c0c..b2f945b1afe501 100644
> --- a/fs/f2fs/inode.c
> +++ b/fs/f2fs/inode.c
> @@ -46,9 +46,11 @@ void f2fs_set_inode_flags(struct inode *inode)
> new_fl |= S_DIRSYNC;
> if (file_is_encrypt(inode))
> new_fl |= S_ENCRYPTED;
> + if (file_is_verity(inode))
> + new_fl |= S_VERITY;
> inode_set_flags(inode, new_fl,
> S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC|
> - S_ENCRYPTED);
> + S_ENCRYPTED|S_VERITY);
> }
>
> static void __get_inode_rdev(struct inode *inode, struct f2fs_inode *ri)
> @@ -749,6 +751,7 @@ void f2fs_evict_inode(struct inode *inode)
> }
> out_clear:
> fscrypt_put_encryption_info(inode);
> + fsverity_cleanup_inode(inode);
> clear_inode(inode);
> }
>
> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
> index 6b959bbb336a30..ea4a247d6ed6f7 100644
> --- a/fs/f2fs/super.c
> +++ b/fs/f2fs/super.c
> @@ -3177,6 +3177,9 @@ static int f2fs_fill_super(struct super_block *sb, void *data, int silent)
> sb->s_op = &f2fs_sops;
> #ifdef CONFIG_FS_ENCRYPTION
> sb->s_cop = &f2fs_cryptops;
> +#endif
> +#ifdef CONFIG_FS_VERITY
> + sb->s_vop = &f2fs_verityops;
> #endif
> sb->s_xattr = f2fs_xattr_handlers;
> sb->s_export_op = &f2fs_export_ops;
> diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c
> index 729f46a3c9ee0b..b3e28467db7279 100644
> --- a/fs/f2fs/sysfs.c
> +++ b/fs/f2fs/sysfs.c
> @@ -117,6 +117,9 @@ static ssize_t features_show(struct f2fs_attr *a,
> if (f2fs_sb_has_lost_found(sbi))
> len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
> len ? ", " : "", "lost_found");
> + if (f2fs_sb_has_verity(sbi))
> + len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
> + len ? ", " : "", "verity");
> if (f2fs_sb_has_sb_chksum(sbi))
> len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
> len ? ", " : "", "sb_checksum");
> @@ -350,6 +353,7 @@ enum feat_id {
> FEAT_QUOTA_INO,
> FEAT_INODE_CRTIME,
> FEAT_LOST_FOUND,
> + FEAT_VERITY,
> FEAT_SB_CHECKSUM,
> };
>
> @@ -367,6 +371,7 @@ static ssize_t f2fs_feature_show(struct f2fs_attr *a,
> case FEAT_QUOTA_INO:
> case FEAT_INODE_CRTIME:
> case FEAT_LOST_FOUND:
> + case FEAT_VERITY:
> case FEAT_SB_CHECKSUM:
> return snprintf(buf, PAGE_SIZE, "supported\n");
> }
> @@ -455,6 +460,9 @@ F2FS_FEATURE_RO_ATTR(flexible_inline_xattr, FEAT_FLEXIBLE_INLINE_XATTR);
> F2FS_FEATURE_RO_ATTR(quota_ino, FEAT_QUOTA_INO);
> F2FS_FEATURE_RO_ATTR(inode_crtime, FEAT_INODE_CRTIME);
> F2FS_FEATURE_RO_ATTR(lost_found, FEAT_LOST_FOUND);
> +#ifdef CONFIG_FS_VERITY
> +F2FS_FEATURE_RO_ATTR(verity, FEAT_VERITY);
> +#endif
> F2FS_FEATURE_RO_ATTR(sb_checksum, FEAT_SB_CHECKSUM);
>
> #define ATTR_LIST(name) (&f2fs_attr_##name.attr)
> @@ -517,6 +525,9 @@ static struct attribute *f2fs_feat_attrs[] = {
> ATTR_LIST(quota_ino),
> ATTR_LIST(inode_crtime),
> ATTR_LIST(lost_found),
> +#ifdef CONFIG_FS_VERITY
> + ATTR_LIST(verity),
> +#endif
> ATTR_LIST(sb_checksum),
> NULL,
> };
> diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
> new file mode 100644
> index 00000000000000..dd9bb47ced0093
> --- /dev/null
> +++ b/fs/f2fs/verity.c
> @@ -0,0 +1,233 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * fs/f2fs/verity.c: fs-verity support for f2fs
> + *
> + * Copyright 2019 Google LLC
> + */
> +
> +/*
> + * Implementation of fsverity_operations for f2fs.
> + *
> + * Like ext4, f2fs stores the verity metadata (Merkle tree and
> + * fsverity_descriptor) past the end of the file, starting at the first 64K
> + * boundary beyond i_size. This approach works because (a) verity files are
> + * readonly, and (b) pages fully beyond i_size aren't visible to userspace but
> + * can be read/written internally by f2fs with only some relatively small
> + * changes to f2fs. Extended attributes cannot be used because (a) f2fs limits
> + * the total size of an inode's xattr entries to 4096 bytes, which wouldn't be
> + * enough for even a single Merkle tree block, and (b) f2fs encryption doesn't
> + * encrypt xattrs, yet the verity metadata *must* be encrypted when the file is
> + * because it contains hashes of the plaintext data.
> + *
> + * Using a 64K boundary rather than a 4K one keeps things ready for
> + * architectures with 64K pages, and it doesn't necessarily waste space on-disk
> + * since there can be a hole between i_size and the start of the Merkle tree.
> + */
> +
> +#include <linux/f2fs_fs.h>
> +
> +#include "f2fs.h"
> +#include "xattr.h"
> +
> +static inline loff_t f2fs_verity_metadata_pos(const struct inode *inode)
> +{
> + return round_up(inode->i_size, 65536);
> +}
> +
> +/*
> + * Read some verity metadata from the inode. __vfs_read() can't be used because
> + * we need to read beyond i_size.
> + */
> +static int pagecache_read(struct inode *inode, void *buf, size_t count,
> + loff_t pos)
> +{
> + while (count) {
> + size_t n = min_t(size_t, count,
> + PAGE_SIZE - offset_in_page(pos));
> + struct page *page;
> + void *addr;
> +
> + page = read_mapping_page(inode->i_mapping, pos >> PAGE_SHIFT,
> + NULL);
> + if (IS_ERR(page))
> + return PTR_ERR(page);
> +
> + addr = kmap_atomic(page);
> + memcpy(buf, addr + offset_in_page(pos), n);
> + kunmap_atomic(addr);
> +
> + put_page(page);
> +
> + buf += n;
> + pos += n;
> + count -= n;
> + }
> + return 0;
> +}
> +
> +/*
> + * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.
> + * kernel_write() can't be used because the file descriptor is readonly.
> + */
> +static int pagecache_write(struct inode *inode, const void *buf, size_t count,
> + loff_t pos)
> +{
> + while (count) {
> + size_t n = min_t(size_t, count,
> + PAGE_SIZE - offset_in_page(pos));
> + struct page *page;
> + void *fsdata;
> + void *addr;
> + int res;
> +
> + res = pagecache_write_begin(NULL, inode->i_mapping, pos, n, 0,
> + &page, &fsdata);
> + if (res)
> + return res;
> +
> + addr = kmap_atomic(page);
> + memcpy(addr + offset_in_page(pos), buf, n);
> + kunmap_atomic(addr);
> +
> + res = pagecache_write_end(NULL, inode->i_mapping, pos, n, n,
> + page, fsdata);
> + if (res < 0)
> + return res;
> + if (res != n)
> + return -EIO;
> +
> + buf += n;
> + pos += n;
> + count -= n;
> + }
> + return 0;
> +}
> +
> +/*
> + * Format of f2fs verity xattr. This points to the location of the verity
> + * descriptor within the file data rather than containing it directly because
> + * the verity descriptor *must* be encrypted when f2fs encryption is used. But,
> + * f2fs encryption does not encrypt xattrs.
> + */
> +struct fsverity_descriptor_location {
> + __le32 version;
> + __le32 size;
> + __le64 pos;
> +};
> +
> +static int f2fs_begin_enable_verity(struct file *filp)
> +{
> + struct inode *inode = file_inode(filp);
> + int err;
> +
> + err = f2fs_convert_inline_inode(inode);
> + if (err)
> + return err;
> +
> + err = dquot_initialize(inode);
> + if (err)
> + return err;
> +
> + set_inode_flag(inode, FI_VERITY_IN_PROGRESS);
> + return 0;
> +}
> +
> +static int f2fs_end_enable_verity(struct file *filp, const void *desc,
> + size_t desc_size, u64 merkle_tree_size)
> +{
> + struct inode *inode = file_inode(filp);
> + u64 desc_pos = f2fs_verity_metadata_pos(inode) + merkle_tree_size;
> + struct fsverity_descriptor_location dloc = {
> + .version = cpu_to_le32(1),
> + .size = cpu_to_le32(desc_size),
> + .pos = cpu_to_le64(desc_pos),
> + };
> + int err = 0;
> +
> + if (desc != NULL) {
> + /* Succeeded; write the verity descriptor. */
> + err = pagecache_write(inode, desc, desc_size, desc_pos);
> +
> + /* Write all pages before clearing FI_VERITY_IN_PROGRESS. */
> + if (!err)
> + err = filemap_write_and_wait(inode->i_mapping);
> + } else {
> + /* Failed; truncate anything we wrote past i_size. */
> + f2fs_truncate(inode);
> + }
> +
> + clear_inode_flag(inode, FI_VERITY_IN_PROGRESS);
> +
> + if (desc != NULL && !err) {
> + err = f2fs_setxattr(inode, F2FS_XATTR_INDEX_VERITY,
> + F2FS_XATTR_NAME_VERITY, &dloc, sizeof(dloc),
> + NULL, XATTR_CREATE);
> + if (!err) {
> + file_set_verity(inode);
> + f2fs_set_inode_flags(inode);
> + f2fs_mark_inode_dirty_sync(inode, true);
> + }
> + }
> + return err;
> +}
> +
> +static int f2fs_get_verity_descriptor(struct inode *inode, void *buf,
> + size_t buf_size)
> +{
> + struct fsverity_descriptor_location dloc;
> + int res;
> + u32 size;
> + u64 pos;
> +
> + /* Get the descriptor location */
> + res = f2fs_getxattr(inode, F2FS_XATTR_INDEX_VERITY,
> + F2FS_XATTR_NAME_VERITY, &dloc, sizeof(dloc), NULL);
> + if (res < 0 && res != -ERANGE)
> + return res;
> + if (res != sizeof(dloc) || dloc.version != cpu_to_le32(1)) {
> + f2fs_msg(inode->i_sb, KERN_WARNING,
> + "unknown verity xattr format");
> + return -EINVAL;
> + }
> + size = le32_to_cpu(dloc.size);
> + pos = le64_to_cpu(dloc.pos);
> +
> + /* Get the descriptor */
> + if (pos + size < pos || pos + size > inode->i_sb->s_maxbytes ||
> + pos < f2fs_verity_metadata_pos(inode) || size > INT_MAX) {
> + f2fs_msg(inode->i_sb, KERN_WARNING, "invalid verity xattr");
> + return -EUCLEAN; /* EFSCORRUPTED */
> + }
> + if (buf_size) {
> + if (size > buf_size)
> + return -ERANGE;
> + res = pagecache_read(inode, buf, size, pos);
> + if (res)
> + return res;
> + }
> + return size;
> +}
> +
> +static struct page *f2fs_read_merkle_tree_page(struct inode *inode,
> + pgoff_t index)
> +{
> + index += f2fs_verity_metadata_pos(inode) >> PAGE_SHIFT;
> +
> + return read_mapping_page(inode->i_mapping, index, NULL);
> +}
> +
> +static int f2fs_write_merkle_tree_block(struct inode *inode, const void *buf,
> + u64 index, int log_blocksize)
> +{
> + loff_t pos = f2fs_verity_metadata_pos(inode) + (index << log_blocksize);
> +
> + return pagecache_write(inode, buf, 1 << log_blocksize, pos);
> +}
> +
> +const struct fsverity_operations f2fs_verityops = {
> + .begin_enable_verity = f2fs_begin_enable_verity,
> + .end_enable_verity = f2fs_end_enable_verity,
> + .get_verity_descriptor = f2fs_get_verity_descriptor,
> + .read_merkle_tree_page = f2fs_read_merkle_tree_page,
> + .write_merkle_tree_block = f2fs_write_merkle_tree_block,
> +};
> diff --git a/fs/f2fs/xattr.h b/fs/f2fs/xattr.h
> index a90920e2f94980..de0c600b9cab09 100644
> --- a/fs/f2fs/xattr.h
> +++ b/fs/f2fs/xattr.h
> @@ -34,8 +34,10 @@
> #define F2FS_XATTR_INDEX_ADVISE 7
> /* Should be same as EXT4_XATTR_INDEX_ENCRYPTION */
> #define F2FS_XATTR_INDEX_ENCRYPTION 9
> +#define F2FS_XATTR_INDEX_VERITY 11
>
> #define F2FS_XATTR_NAME_ENCRYPTION_CONTEXT "c"
> +#define F2FS_XATTR_NAME_VERITY "v"
>
> struct f2fs_xattr_header {
> __le32 h_magic; /* magic number for identification */
> --
> 2.22.0.410.gd8fdbe21b5-goog
Powered by blists - more mailing lists