ANDROID: Incremental fs: Store fs-verity state in backing file
Now fsverity state is preserved across inode eviction. Added incfs.verity xattr to track when a file is fs-verity enabled. Bug: 160634504 Test: incfs_test passes Signed-off-by: Paul Lawrence <paullawrence@google.com> Change-Id: I41d90abd55527884d9eff642c9834ad837ff6918
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fsverity.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/lz4.h>
|
||||
@@ -18,6 +19,7 @@
|
||||
#include "data_mgmt.h"
|
||||
#include "format.h"
|
||||
#include "integrity.h"
|
||||
#include "verity.h"
|
||||
|
||||
static int incfs_scan_metadata_chain(struct data_file *df);
|
||||
|
||||
@@ -332,6 +334,7 @@ void incfs_free_data_file(struct data_file *df)
|
||||
incfs_free_bfc(df->df_backing_file_context);
|
||||
kfree(df->df_signature);
|
||||
kfree(df->df_verity_file_digest.data);
|
||||
kfree(df->df_verity_signature);
|
||||
mutex_destroy(&df->df_enable_verity);
|
||||
kfree(df);
|
||||
}
|
||||
@@ -1471,7 +1474,31 @@ static int process_status_md(struct incfs_status *is,
|
||||
df->df_initial_hash_blocks_written);
|
||||
|
||||
df->df_status_offset = handler->md_record_offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_file_verity_signature_md(
|
||||
struct incfs_file_verity_signature *vs,
|
||||
struct metadata_handler *handler)
|
||||
{
|
||||
struct data_file *df = handler->context;
|
||||
struct incfs_df_verity_signature *verity_signature;
|
||||
|
||||
if (!df)
|
||||
return -EFAULT;
|
||||
|
||||
verity_signature = kzalloc(sizeof(*verity_signature), GFP_NOFS);
|
||||
if (!verity_signature)
|
||||
return -ENOMEM;
|
||||
|
||||
verity_signature->offset = le64_to_cpu(vs->vs_offset);
|
||||
verity_signature->size = le32_to_cpu(vs->vs_size);
|
||||
if (verity_signature->size > FS_VERITY_MAX_SIGNATURE_SIZE) {
|
||||
kfree(verity_signature);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
df->df_verity_signature = verity_signature;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1497,6 +1524,7 @@ static int incfs_scan_metadata_chain(struct data_file *df)
|
||||
handler->handle_blockmap = process_blockmap_md;
|
||||
handler->handle_signature = process_file_signature_md;
|
||||
handler->handle_status = process_status_md;
|
||||
handler->handle_verity_signature = process_file_verity_signature_md;
|
||||
|
||||
while (handler->md_record_offset > 0) {
|
||||
error = incfs_read_next_metadata_record(bfc, handler);
|
||||
|
||||
@@ -297,6 +297,8 @@ struct data_file {
|
||||
* been opened
|
||||
*/
|
||||
struct mem_range df_verity_file_digest;
|
||||
|
||||
struct incfs_df_verity_signature *df_verity_signature;
|
||||
};
|
||||
|
||||
struct dir_file {
|
||||
|
||||
@@ -324,9 +324,6 @@ static int write_new_status_to_backing_file(struct backing_file_context *bfc,
|
||||
.is_hash_blocks_written = cpu_to_le32(hash_blocks_written),
|
||||
};
|
||||
|
||||
if (!bfc)
|
||||
return -EFAULT;
|
||||
|
||||
LOCK_REQUIRED(bfc->bc_mutex);
|
||||
rollback_pos = incfs_get_end_offset(bfc->bc_file);
|
||||
result = append_md_to_backing_file(bfc, &is.is_header);
|
||||
@@ -344,6 +341,9 @@ int incfs_write_status_to_backing_file(struct backing_file_context *bfc,
|
||||
struct incfs_status is;
|
||||
int result;
|
||||
|
||||
if (!bfc)
|
||||
return -EFAULT;
|
||||
|
||||
if (status_offset == 0)
|
||||
return write_new_status_to_backing_file(bfc,
|
||||
data_blocks_written, hash_blocks_written);
|
||||
@@ -361,6 +361,46 @@ int incfs_write_status_to_backing_file(struct backing_file_context *bfc,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int incfs_write_verity_signature_to_backing_file(
|
||||
struct backing_file_context *bfc, struct mem_range signature,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct incfs_file_verity_signature vs = {};
|
||||
int result;
|
||||
loff_t pos;
|
||||
|
||||
/* No verity signature section is equivalent to an empty section */
|
||||
if (signature.data == NULL || signature.len == 0)
|
||||
return 0;
|
||||
|
||||
pos = incfs_get_end_offset(bfc->bc_file);
|
||||
|
||||
vs = (struct incfs_file_verity_signature) {
|
||||
.vs_header = (struct incfs_md_header) {
|
||||
.h_md_entry_type = INCFS_MD_VERITY_SIGNATURE,
|
||||
.h_record_size = cpu_to_le16(sizeof(vs)),
|
||||
.h_next_md_offset = cpu_to_le64(0),
|
||||
},
|
||||
.vs_size = cpu_to_le32(signature.len),
|
||||
.vs_offset = cpu_to_le64(pos),
|
||||
};
|
||||
|
||||
result = write_to_bf(bfc, signature.data, signature.len, pos);
|
||||
if (result)
|
||||
goto err;
|
||||
|
||||
result = append_md_to_backing_file(bfc, &vs.vs_header);
|
||||
if (result)
|
||||
goto err;
|
||||
|
||||
*offset = pos;
|
||||
err:
|
||||
if (result)
|
||||
/* Error, rollback file changes */
|
||||
truncate_backing_file(bfc, pos);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a backing file header
|
||||
* It should always be called only on empty file.
|
||||
@@ -660,6 +700,11 @@ int incfs_read_next_metadata_record(struct backing_file_context *bfc,
|
||||
res = handler->handle_status(
|
||||
&handler->md_buffer.status, handler);
|
||||
break;
|
||||
case INCFS_MD_VERITY_SIGNATURE:
|
||||
if (handler->handle_verity_signature)
|
||||
res = handler->handle_verity_signature(
|
||||
&handler->md_buffer.verity_signature, handler);
|
||||
break;
|
||||
default:
|
||||
res = -ENOTSUPP;
|
||||
break;
|
||||
|
||||
@@ -120,6 +120,7 @@ enum incfs_metadata_type {
|
||||
INCFS_MD_FILE_ATTR = 2,
|
||||
INCFS_MD_SIGNATURE = 3,
|
||||
INCFS_MD_STATUS = 4,
|
||||
INCFS_MD_VERITY_SIGNATURE = 5,
|
||||
};
|
||||
|
||||
enum incfs_file_header_flags {
|
||||
@@ -228,7 +229,14 @@ struct incfs_blockmap {
|
||||
__le32 m_block_count;
|
||||
} __packed;
|
||||
|
||||
/* Metadata record for file signature. Type = INCFS_MD_SIGNATURE */
|
||||
/*
|
||||
* Metadata record for file signature. Type = INCFS_MD_SIGNATURE
|
||||
*
|
||||
* The signature stored here is the APK V4 signature data blob. See the
|
||||
* definition of incfs_new_file_args::signature_info for an explanation of this
|
||||
* blob. Specifically, it contains the root hash, but it does *not* contain
|
||||
* anything that the kernel treats as a signature.
|
||||
*/
|
||||
struct incfs_file_signature {
|
||||
struct incfs_md_header sg_header;
|
||||
|
||||
@@ -259,6 +267,29 @@ struct incfs_status {
|
||||
__le32 is_dummy[6]; /* Spare fields */
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Metadata record for verity signature. Type = INCFS_MD_VERITY_SIGNATURE
|
||||
*
|
||||
* This record will only exist for verity-enabled files with signatures. Verity
|
||||
* enabled files without signatures do not have this record. This signature is
|
||||
* checked by fs-verity identically to any other fs-verity signature.
|
||||
*/
|
||||
struct incfs_file_verity_signature {
|
||||
struct incfs_md_header vs_header;
|
||||
|
||||
/* The size of the signature */
|
||||
__le32 vs_size;
|
||||
|
||||
/* Signature's offset in the backing file */
|
||||
__le64 vs_offset;
|
||||
} __packed;
|
||||
|
||||
/* In memory version of above */
|
||||
struct incfs_df_verity_signature {
|
||||
u32 size;
|
||||
u64 offset;
|
||||
};
|
||||
|
||||
/* State of the backing file. */
|
||||
struct backing_file_context {
|
||||
/* Protects writes to bc_file */
|
||||
@@ -291,6 +322,7 @@ struct metadata_handler {
|
||||
struct incfs_blockmap blockmap;
|
||||
struct incfs_file_signature signature;
|
||||
struct incfs_status status;
|
||||
struct incfs_file_verity_signature verity_signature;
|
||||
} md_buffer;
|
||||
|
||||
int (*handle_blockmap)(struct incfs_blockmap *bm,
|
||||
@@ -299,6 +331,8 @@ struct metadata_handler {
|
||||
struct metadata_handler *handler);
|
||||
int (*handle_status)(struct incfs_status *sig,
|
||||
struct metadata_handler *handler);
|
||||
int (*handle_verity_signature)(struct incfs_file_verity_signature *s,
|
||||
struct metadata_handler *handler);
|
||||
};
|
||||
#define INCFS_MAX_METADATA_RECORD_SIZE \
|
||||
sizeof_field(struct metadata_handler, md_buffer)
|
||||
@@ -339,6 +373,9 @@ int incfs_write_status_to_backing_file(struct backing_file_context *bfc,
|
||||
loff_t status_offset,
|
||||
u32 data_blocks_written,
|
||||
u32 hash_blocks_written);
|
||||
int incfs_write_verity_signature_to_backing_file(
|
||||
struct backing_file_context *bfc, struct mem_range signature,
|
||||
loff_t *offset);
|
||||
|
||||
/* Reading stuff */
|
||||
int incfs_read_file_header(struct backing_file_context *bfc,
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "verity.h"
|
||||
|
||||
#include "data_mgmt.h"
|
||||
#include "format.h"
|
||||
#include "integrity.h"
|
||||
#include "vfs.h"
|
||||
|
||||
@@ -67,12 +68,59 @@ static int incfs_get_root_hash(struct file *filp, u8 *root_hash)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int incfs_end_enable_verity(struct file *filp)
|
||||
static int incfs_end_enable_verity(struct file *filp, u8 *sig, size_t sig_size)
|
||||
{
|
||||
struct inode *inode = file_inode(filp);
|
||||
struct mem_range signature = {
|
||||
.data = sig,
|
||||
.len = sig_size,
|
||||
};
|
||||
struct data_file *df = get_incfs_data_file(filp);
|
||||
struct backing_file_context *bfc;
|
||||
int error;
|
||||
struct incfs_df_verity_signature *vs;
|
||||
loff_t offset;
|
||||
|
||||
if (!df || !df->df_backing_file_context)
|
||||
return -EFSCORRUPTED;
|
||||
|
||||
vs = kzalloc(sizeof(*vs), GFP_NOFS);
|
||||
if (!vs)
|
||||
return -ENOMEM;
|
||||
|
||||
bfc = df->df_backing_file_context;
|
||||
error = mutex_lock_interruptible(&bfc->bc_mutex);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
error = incfs_write_verity_signature_to_backing_file(bfc, signature,
|
||||
&offset);
|
||||
mutex_unlock(&bfc->bc_mutex);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Set verity xattr so we can set S_VERITY without opening backing file
|
||||
*/
|
||||
error = vfs_setxattr(bfc->bc_file->f_path.dentry,
|
||||
INCFS_XATTR_VERITY_NAME, NULL, 0, XATTR_CREATE);
|
||||
if (error) {
|
||||
pr_warn("incfs: error setting verity xattr: %d\n", error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*vs = (struct incfs_df_verity_signature) {
|
||||
.size = signature.len,
|
||||
.offset = offset,
|
||||
};
|
||||
|
||||
df->df_verity_signature = vs;
|
||||
vs = NULL;
|
||||
inode_set_flags(inode, S_VERITY, S_VERITY);
|
||||
return 0;
|
||||
|
||||
out:
|
||||
kfree(vs);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int incfs_compute_file_digest(struct incfs_hash_alg *alg,
|
||||
@@ -246,6 +294,11 @@ static int incfs_enable_verity(struct file *filp,
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (IS_VERITY(inode)) {
|
||||
err = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Get the signature if the user provided one */
|
||||
if (arg->sig_size) {
|
||||
signature = memdup_user(u64_to_user_ptr(arg->sig_ptr),
|
||||
@@ -265,7 +318,7 @@ static int incfs_enable_verity(struct file *filp,
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = incfs_end_enable_verity(filp);
|
||||
err = incfs_end_enable_verity(filp, signature, arg->sig_size);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
@@ -316,3 +369,94 @@ int incfs_ioctl_enable_verity(struct file *filp, const void __user *uarg)
|
||||
|
||||
return incfs_enable_verity(filp, &arg);
|
||||
}
|
||||
|
||||
static u8 *incfs_get_verity_signature(struct file *filp, size_t *sig_size)
|
||||
{
|
||||
struct data_file *df = get_incfs_data_file(filp);
|
||||
struct incfs_df_verity_signature *vs;
|
||||
u8 *signature;
|
||||
int res;
|
||||
|
||||
if (!df || !df->df_backing_file_context)
|
||||
return ERR_PTR(-EFSCORRUPTED);
|
||||
|
||||
vs = df->df_verity_signature;
|
||||
if (!vs) {
|
||||
*sig_size = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
signature = kzalloc(vs->size, GFP_KERNEL);
|
||||
if (!signature)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
res = incfs_kread(df->df_backing_file_context,
|
||||
signature, vs->size, vs->offset);
|
||||
|
||||
if (res < 0)
|
||||
goto err_out;
|
||||
|
||||
if (res != vs->size) {
|
||||
res = -EINVAL;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
*sig_size = vs->size;
|
||||
return signature;
|
||||
|
||||
err_out:
|
||||
kfree(signature);
|
||||
return ERR_PTR(res);
|
||||
}
|
||||
|
||||
/* Ensure data_file->df_verity_file_digest is populated */
|
||||
static int ensure_verity_info(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct mem_range verity_file_digest;
|
||||
u8 *signature = NULL;
|
||||
size_t sig_size;
|
||||
int err = 0;
|
||||
|
||||
/* See if this file's verity file digest is already cached */
|
||||
verity_file_digest = incfs_get_verity_digest(inode);
|
||||
if (verity_file_digest.data)
|
||||
return 0;
|
||||
|
||||
signature = incfs_get_verity_signature(filp, &sig_size);
|
||||
if (IS_ERR(signature))
|
||||
return PTR_ERR(signature);
|
||||
|
||||
verity_file_digest = incfs_calc_verity_digest(inode, filp, signature,
|
||||
sig_size,
|
||||
FS_VERITY_HASH_ALG_SHA256);
|
||||
if (IS_ERR(verity_file_digest.data)) {
|
||||
err = PTR_ERR(verity_file_digest.data);
|
||||
goto out;
|
||||
}
|
||||
|
||||
incfs_set_verity_digest(inode, verity_file_digest);
|
||||
|
||||
out:
|
||||
kfree(signature);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* incfs_fsverity_file_open() - prepare to open a file that may be
|
||||
* verity-enabled
|
||||
* @inode: the inode being opened
|
||||
* @filp: the struct file being set up
|
||||
*
|
||||
* When opening a verity file, set up data_file->df_verity_file_digest if not
|
||||
* already done. Note that incfs does not allow opening for writing, so there is
|
||||
* no need for that check.
|
||||
*
|
||||
* Return: 0 on success, -errno on failure
|
||||
*/
|
||||
int incfs_fsverity_file_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
if (IS_VERITY(inode))
|
||||
return ensure_verity_info(inode, filp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,15 @@
|
||||
#ifndef _INCFS_VERITY_H
|
||||
#define _INCFS_VERITY_H
|
||||
|
||||
/* Arbitrary limit to bound the kmalloc() size. Can be changed. */
|
||||
#define FS_VERITY_MAX_SIGNATURE_SIZE 16128
|
||||
|
||||
#ifdef CONFIG_FS_VERITY
|
||||
|
||||
int incfs_ioctl_enable_verity(struct file *filp, const void __user *uarg);
|
||||
|
||||
int incfs_fsverity_file_open(struct inode *inode, struct file *filp);
|
||||
|
||||
#else /* !CONFIG_FS_VERITY */
|
||||
|
||||
static inline int incfs_ioctl_enable_verity(struct file *filp,
|
||||
@@ -18,6 +23,12 @@ static inline int incfs_ioctl_enable_verity(struct file *filp,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int incfs_fsverity_file_open(struct inode *inode,
|
||||
struct file *filp)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
#endif /* !CONFIG_FS_VERITY */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -159,6 +159,8 @@ struct inode_search {
|
||||
struct dentry *backing_dentry;
|
||||
|
||||
size_t size;
|
||||
|
||||
bool verity;
|
||||
};
|
||||
|
||||
enum parse_parameter {
|
||||
@@ -255,6 +257,13 @@ static u64 read_size_attr(struct dentry *backing_dentry)
|
||||
return le64_to_cpu(attr_value);
|
||||
}
|
||||
|
||||
/* Read verity flag from the attribute. Quicker than reading the header */
|
||||
static bool read_verity_attr(struct dentry *backing_dentry)
|
||||
{
|
||||
return vfs_getxattr(backing_dentry, INCFS_XATTR_VERITY_NAME, NULL, 0)
|
||||
>= 0;
|
||||
}
|
||||
|
||||
static int inode_test(struct inode *inode, void *opaque)
|
||||
{
|
||||
struct inode_search *search = opaque;
|
||||
@@ -285,6 +294,8 @@ static int inode_set(struct inode *inode, void *opaque)
|
||||
inode->i_op = &incfs_file_inode_ops;
|
||||
inode->i_fop = &incfs_file_ops;
|
||||
inode->i_mode &= ~0222;
|
||||
if (search->verity)
|
||||
inode_set_flags(inode, S_VERITY, S_VERITY);
|
||||
} else if (S_ISDIR(inode->i_mode)) {
|
||||
inode->i_size = 0;
|
||||
inode->i_blocks = 1;
|
||||
@@ -319,6 +330,7 @@ static struct inode *fetch_regular_inode(struct super_block *sb,
|
||||
.ino = backing_inode->i_ino,
|
||||
.backing_dentry = backing_dentry,
|
||||
.size = read_size_attr(backing_dentry),
|
||||
.verity = read_verity_attr(backing_dentry),
|
||||
};
|
||||
struct inode *inode = iget5_locked(sb, search.ino, inode_test,
|
||||
inode_set, &search);
|
||||
@@ -1370,7 +1382,12 @@ static int file_open(struct inode *inode, struct file *file)
|
||||
file->private_data = fd;
|
||||
|
||||
err = make_inode_ready_for_data_ops(mi, inode, backing_file);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = incfs_fsverity_file_open(inode, file);
|
||||
if (err)
|
||||
goto out;
|
||||
} else if (S_ISDIR(inode->i_mode)) {
|
||||
struct dir_file *dir = NULL;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#define INCFS_XATTR_ID_NAME (XATTR_USER_PREFIX "incfs.id")
|
||||
#define INCFS_XATTR_SIZE_NAME (XATTR_USER_PREFIX "incfs.size")
|
||||
#define INCFS_XATTR_METADATA_NAME (XATTR_USER_PREFIX "incfs.metadata")
|
||||
#define INCFS_XATTR_VERITY_NAME (XATTR_USER_PREFIX "incfs.verity")
|
||||
|
||||
#define INCFS_MAX_SIGNATURE_SIZE 8096
|
||||
#define INCFS_SIGNATURE_VERSION 2
|
||||
|
||||
@@ -3929,6 +3929,15 @@ static int verity_test_optional_sigs(const char *mount_dir, bool use_signatures)
|
||||
0);
|
||||
}
|
||||
|
||||
for (i = 0; i < file_num; i++)
|
||||
TESTEQUAL(validate_verity(mount_dir, &test.files[i]), 0);
|
||||
|
||||
close(cmd_fd);
|
||||
cmd_fd = -1;
|
||||
TESTEQUAL(umount(mount_dir), 0);
|
||||
TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false),
|
||||
0);
|
||||
|
||||
for (i = 0; i < file_num; i++)
|
||||
TESTEQUAL(validate_verity(mount_dir, &test.files[i]), 0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user