[RFC PATCH 10/10] f2fs: fs-verity support

From: Eric Biggers
Date: Fri Aug 24 2018 - 12:23:58 EST


From: Eric Biggers <ebiggers@xxxxxxxxxx>

Add fs-verity support to f2fs. fs-verity is a filesystem feature that
provides efficient, transparent integrity verification and
authentication of read-only files. It uses a dm-verity like mechanism
at the file level: a Merkle tree hidden past the end of the file is used
to verify any block in the file in log(filesize) time. It is
implemented mainly by helper functions in fs/verity/.

In f2fs, the main change is to the I/O path: ->readpage() and
->readpages() now verify data as it is read from verity files. Pages
that fail verification are set to PG_error && !PG_uptodate, causing
applications to see an I/O error.

Hooks are also added to several other f2fs filesystem operations:

* ->open(), to deny opening verity files for writing and to set up
the fsverity_info to prepare for I/O

* ->getattr() to set up the fsverity_info to make stat() show the
original data size of verity files

* ->setattr() to deny truncating verity files

* update_inode() to write out the full file size rather than the
original data size, since for verity files the in-memory ->i_size is
overridden with the original data size.

Finally, the FS_IOC_ENABLE_VERITY and FS_IOC_MEASURE_VERITY ioctls are
wired up. On f2fs, these ioctls require that the filesystem has the
'verity' feature, i.e. it was created with 'mkfs.f2fs -O verity'.

Signed-off-by: Eric Biggers <ebiggers@xxxxxxxxxx>
---
fs/f2fs/Kconfig | 20 +++++++++++++++++
fs/f2fs/data.c | 43 +++++++++++++++++++++++++++++++-----
fs/f2fs/f2fs.h | 17 ++++++++++++---
fs/f2fs/file.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++
fs/f2fs/inode.c | 3 ++-
fs/f2fs/super.c | 22 +++++++++++++++++++
fs/f2fs/sysfs.c | 11 ++++++++++
7 files changed, 165 insertions(+), 9 deletions(-)

diff --git a/fs/f2fs/Kconfig b/fs/f2fs/Kconfig
index 9a20ef42fadde..c8396c7220f2a 100644
--- a/fs/f2fs/Kconfig
+++ b/fs/f2fs/Kconfig
@@ -81,6 +81,26 @@ 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
+ This option enables fs-verity for f2fs. fs-verity is the
+ dm-verity mechanism implemented at the file level. Userspace
+ can append a Merkle tree (hash tree) to a file, then enable
+ fs-verity on the file. f2fs will then transparently verify
+ any data read from the file against the Merkle tree. The file
+ is also made read-only.
+
+ This serves as an integrity check, but the availability of the
+ Merkle tree root hash also allows efficiently supporting
+ various use cases where normally the whole file would need to
+ be hashed at once, such as auditing and authenticity
+ verification (appraisal).
+
+ If unsure, say N.
+
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 8f931d699287a..fc9ea831f7235 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -59,6 +59,7 @@ static bool __is_cp_guaranteed(struct page *page)
enum bio_post_read_step {
STEP_INITIAL = 0,
STEP_DECRYPT,
+ STEP_VERITY,
};

struct bio_post_read_ctx {
@@ -103,8 +104,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)) {
@@ -114,6 +130,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);
}
@@ -534,7 +558,7 @@ void f2fs_submit_page_write(struct f2fs_io_info *fio)
}

static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
- unsigned nr_pages)
+ unsigned nr_pages, pgoff_t first_idx)
{
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct bio *bio;
@@ -550,6 +574,11 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,

if (f2fs_encrypted_file(inode))
post_read_steps |= 1 << STEP_DECRYPT;
+#ifdef CONFIG_F2FS_FS_VERITY
+ if (inode->i_verity_info != NULL &&
+ (first_idx < ((i_size_read(inode) + PAGE_SIZE - 1) >> PAGE_SHIFT)))
+ post_read_steps |= 1 << STEP_VERITY;
+#endif
if (post_read_steps) {
ctx = mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
if (!ctx) {
@@ -571,7 +600,7 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
static int f2fs_submit_page_read(struct inode *inode, struct page *page,
block_t blkaddr)
{
- struct bio *bio = f2fs_grab_read_bio(inode, blkaddr, 1);
+ struct bio *bio = f2fs_grab_read_bio(inode, blkaddr, 1, page->index);

if (IS_ERR(bio))
return PTR_ERR(bio);
@@ -1459,8 +1488,8 @@ static int f2fs_mpage_readpages(struct address_space *mapping,

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;
+ last_block_in_file = (fsverity_full_i_size(inode) +
+ blocksize - 1) >> blkbits;
if (last_block > last_block_in_file)
last_block = last_block_in_file;

@@ -1497,6 +1526,9 @@ static int f2fs_mpage_readpages(struct address_space *mapping,
}
} else {
zero_user_segment(page, 0, PAGE_SIZE);
+ if (f2fs_verity_file(inode) &&
+ !fsverity_verify_page(page))
+ goto set_error_page;
if (!PageUptodate(page))
SetPageUptodate(page);
unlock_page(page);
@@ -1514,7 +1546,8 @@ static int f2fs_mpage_readpages(struct address_space *mapping,
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,
+ page->index);
if (IS_ERR(bio)) {
bio = NULL;
goto set_error_page;
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 4d8b1de831439..e59781b13c5c8 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -29,6 +29,9 @@
#define __FS_HAS_ENCRYPTION IS_ENABLED(CONFIG_F2FS_FS_ENCRYPTION)
#include <linux/fscrypt.h>

+#define __FS_HAS_VERITY IS_ENABLED(CONFIG_F2FS_FS_VERITY)
+#include <linux/fsverity.h>
+
#ifdef CONFIG_F2FS_CHECK_FS
#define f2fs_bug_on(sbi, condition) BUG_ON(condition)
#else
@@ -146,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_HAS_FEATURE(sb, mask) \
((F2FS_SB(sb)->raw_super->feature & cpu_to_le32(mask)) != 0)
@@ -598,7 +601,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 file_is_cold(inode) is_file(inode, FADVISE_COLD_BIT)
#define file_wrong_pino(inode) is_file(inode, FADVISE_LOST_PINO_BIT)
@@ -616,6 +619,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

@@ -3294,13 +3299,18 @@ static inline void f2fs_set_encrypted_inode(struct inode *inode)
#endif
}

+static inline bool f2fs_verity_file(struct inode *inode)
+{
+ return file_is_verity(inode);
+}
+
/*
* Returns true if the reads of the inode's data need to undergo some
* postprocessing step, like decryption or authenticity verification.
*/
static inline bool f2fs_post_read_required(struct inode *inode)
{
- return f2fs_encrypted_file(inode);
+ return f2fs_encrypted_file(inode) || f2fs_verity_file(inode);
}

#define F2FS_FEATURE_FUNCS(name, flagname) \
@@ -3318,6 +3328,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);

#ifdef CONFIG_BLK_DEV_ZONED
static inline int get_blkz_type(struct f2fs_sb_info *sbi,
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 6880c6f78d58d..ea86dd35685ff 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -486,6 +486,12 @@ static int f2fs_file_open(struct inode *inode, struct file *filp)
if (err)
return err;

+ if (f2fs_verity_file(inode)) {
+ err = fsverity_file_open(inode, filp);
+ if (err)
+ return err;
+ }
+
filp->f_mode |= FMODE_NOWAIT;

return dquot_file_open(inode, filp);
@@ -684,6 +690,22 @@ int f2fs_getattr(const struct path *path, struct kstat *stat,
struct f2fs_inode *ri;
unsigned int flags;

+ if (f2fs_verity_file(inode)) {
+ /*
+ * For fs-verity we need to override i_size with the original
+ * data i_size. This requires I/O to the file which with
+ * fscrypt requires that the key be set up. But, if the key is
+ * unavailable just continue on without the i_size override.
+ */
+ int err = fscrypt_require_key(inode);
+
+ if (!err) {
+ err = fsverity_prepare_getattr(inode);
+ if (err)
+ return err;
+ }
+ }
+
if (f2fs_has_extra_attr(inode) &&
f2fs_sb_has_inode_crtime(inode->i_sb) &&
F2FS_FITS_IN_INODE(ri, fi->i_extra_isize, i_crtime)) {
@@ -767,6 +789,12 @@ int f2fs_setattr(struct dentry *dentry, struct iattr *attr)
if (err)
return err;

+ if (f2fs_verity_file(inode)) {
+ err = fsverity_prepare_setattr(dentry, attr);
+ if (err)
+ return err;
+ }
+
if (is_quota_modification(inode, attr)) {
err = dquot_initialize(inode);
if (err)
@@ -2851,6 +2879,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(inode->i_sb)) {
+ f2fs_msg(inode->i_sb, KERN_WARNING,
+ "Can't enable fs-verity on inode %lu: the fs-verity feature is disabled 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(file_inode(filp)->i_sb))
+ 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)))))
@@ -2907,6 +2959,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;
}
@@ -3013,6 +3069,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 f121c864f4c0d..e363e9f0c699e 100644
--- a/fs/f2fs/inode.c
+++ b/fs/f2fs/inode.c
@@ -407,7 +407,7 @@ void f2fs_update_inode(struct inode *inode, struct page *node_page)
ri->i_uid = cpu_to_le32(i_uid_read(inode));
ri->i_gid = cpu_to_le32(i_gid_read(inode));
ri->i_links = cpu_to_le32(inode->i_nlink);
- ri->i_size = cpu_to_le64(i_size_read(inode));
+ ri->i_size = cpu_to_le64(fsverity_full_i_size(inode));
ri->i_blocks = cpu_to_le64(SECTOR_TO_BLOCK(inode->i_blocks) + 1);

if (et) {
@@ -618,6 +618,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 3995e926ba3a3..52a0de200fb79 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -1943,6 +1943,25 @@ static const struct fscrypt_operations f2fs_cryptops = {
};
#endif

+#ifdef CONFIG_F2FS_FS_VERITY
+static int f2fs_set_verity(struct inode *inode, loff_t data_i_size)
+{
+ int err;
+
+ err = f2fs_convert_inline_inode(inode);
+ if (err)
+ return err;
+
+ file_set_verity(inode);
+ f2fs_mark_inode_dirty_sync(inode, true);
+ return 0;
+}
+
+static const struct fsverity_operations f2fs_verityops = {
+ .set_verity = f2fs_set_verity,
+};
+#endif /* CONFIG_F2FS_FS_VERITY */
+
static struct inode *f2fs_nfs_get_inode(struct super_block *sb,
u64 ino, u32 generation)
{
@@ -2758,6 +2777,9 @@ static int f2fs_fill_super(struct super_block *sb, void *data, int silent)
sb->s_op = &f2fs_sops;
#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;
diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c
index 2e7e611deaef2..f11aa34a8be18 100644
--- a/fs/f2fs/sysfs.c
+++ b/fs/f2fs/sysfs.c
@@ -119,6 +119,9 @@ static ssize_t features_show(struct f2fs_attr *a,
if (f2fs_sb_has_lost_found(sb))
len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
len ? ", " : "", "lost_found");
+ if (f2fs_sb_has_verity(sb))
+ len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
+ len ? ", " : "", "verity");
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
return len;
}
@@ -333,6 +336,7 @@ enum feat_id {
FEAT_QUOTA_INO,
FEAT_INODE_CRTIME,
FEAT_LOST_FOUND,
+ FEAT_VERITY,
};

static ssize_t f2fs_feature_show(struct f2fs_attr *a,
@@ -349,6 +353,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:
return snprintf(buf, PAGE_SIZE, "supported\n");
}
return 0;
@@ -429,6 +434,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_F2FS_FS_VERITY
+F2FS_FEATURE_RO_ATTR(verity, FEAT_VERITY);
+#endif

#define ATTR_LIST(name) (&f2fs_attr_##name.attr)
static struct attribute *f2fs_attrs[] = {
@@ -485,6 +493,9 @@ static struct attribute *f2fs_feat_attrs[] = {
ATTR_LIST(quota_ino),
ATTR_LIST(inode_crtime),
ATTR_LIST(lost_found),
+#ifdef CONFIG_F2FS_FS_VERITY
+ ATTR_LIST(verity),
+#endif
NULL,
};

--
2.18.0