blob: a94202f0e57d34b043c998f2f77fe11a7f0d57f0 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* fs-verity: read-only file-based integrity/authenticity
*
* ioctl.c: fs-verity ioctls
*
* Copyright (C) 2018, Google, Inc.
*
* Originally written by Jaegeuk Kim and Michael Halcrow;
* heavily rewritten by Eric Biggers.
*/
#include "fsverity_private.h"
#include <linux/capability.h>
#include <linux/mm.h>
#include <linux/mount.h>
#include <linux/uaccess.h>
/**
* fsverity_ioctl_enable - enable fs-verity on a file
*
* Enable the fs-verity bit on a file. Userspace must have already appended the
* fs-verity metadata to the file.
*
* Enabling fs-verity makes the file contents immutable, and the filesystem
* doesn't allow disabling it (other than by replacing the file).
*
* To avoid races with the file contents being modified, no processes must have
* the file open for writing. This includes the caller!
*
* Return: 0 on success, -errno on failure
*/
int fsverity_ioctl_enable(struct file *filp, const void __user *arg)
{
struct inode *inode = file_inode(filp);
struct fsverity_info *vi;
int err;
if (!capable(CAP_SYS_ADMIN)) {
pr_debug("Process '%s' is not authorized to enable fs-verity\n",
current->comm);
err = -EACCES;
goto out;
}
if (arg) {
pr_debug("FS_IOC_ENABLE_VERITY doesn't take an argument\n");
err = -EINVAL;
goto out;
}
if (S_ISDIR(inode->i_mode)) {
pr_debug("Inode is a directory\n");
err = -EISDIR;
goto out;
}
if (!S_ISREG(inode->i_mode)) {
pr_debug("Inode is not a regular file\n");
err = -EINVAL;
goto out;
}
err = mnt_want_write_file(filp);
if (err)
goto out;
/*
* Temporarily lock out writers via writable file descriptors or
* truncate(). This should stabilize the contents of the file as well
* as its size. Note that at the end of this ioctl we will unlock
* writers, but at that point the fs-verity bit will be set (if the
* ioctl succeeded), preventing future writers.
*/
err = deny_write_access(filp);
if (err) {
pr_debug("File is open for writing!\n");
goto out_drop_write;
}
/*
* fsync so that the fs-verity bit can't be persisted to disk prior to
* the data, causing verification errors after a crash.
*/
err = vfs_fsync(filp, 1);
if (err) {
pr_debug("I/O error occurred during fsync\n");
goto out_allow_write;
}
/* Serialize concurrent use of this ioctl on the same inode */
inode_lock(inode);
if (inode->i_sb->s_vop->is_verity(inode)) {
pr_debug("Fs-verity is already enabled on this file\n");
err = -EEXIST;
goto out_unlock;
}
/* Validate the fs-verity footer */
vi = create_fsverity_info(inode);
if (IS_ERR(vi)) {
pr_debug("create_fsverity_info() failed\n");
err = PTR_ERR(vi);
goto out_unlock;
}
/* Set the fs-verity bit */
err = inode->i_sb->s_vop->set_verity(inode);
if (err) {
pr_debug("Filesystem ->set_verity() method failed\n");
goto out_free_vi;
}
/* Invalidate all cached pages, forcing re-verification */
truncate_inode_pages(inode->i_mapping, 0);
/* Set ->i_verity_info */
if (set_fsverity_info(inode, vi))
vi = NULL;
err = 0;
out_free_vi:
free_fsverity_info(vi);
out_unlock:
inode_unlock(inode);
out_allow_write:
allow_write_access(filp);
out_drop_write:
mnt_drop_write_file(filp);
out:
if (err)
pr_debug("Failed to enable fs-verity on inode %lu (err=%d)\n",
inode->i_ino, err);
else
pr_debug("Successfully enabled fs-verity on inode %lu\n",
inode->i_ino);
return err;
}
EXPORT_SYMBOL_GPL(fsverity_ioctl_enable);
#ifdef CONFIG_FS_VERITY_USERSPACE_SIG_VERIFY
/**
* fsverity_ioctl_set_measurement - set a verity file's expected measurement
*
* The FS_IOC_SET_VERITY_MEASUREMENT ioctl informs the kernel which
* "measurement" (hash of the Merkle tree root hash and the fsverity footer) is
* expected for an fs-verity file. If the actual measurement matches the
* expected one, then the ioctl succeeds and further reads from the file are
* allowed without warnings. Additionally, the inode is pinned in memory so
* that the "authenticated" status doesn't get lost on eviction. Otherwise,
* reads from the file are forbidden and the ioctl fails.
*
* Return: 0 on success, -errno on failure
*/
int fsverity_ioctl_set_measurement(struct file *filp, const void __user *_uarg)
{
const struct fsverity_measurement __user *uarg = _uarg;
struct inode *inode = file_inode(filp);
struct super_block *sb = inode->i_sb;
struct fsverity_measurement arg;
u8 digest[FS_VERITY_MAX_DIGEST_SIZE];
const struct fsverity_hash_alg *hash_alg;
struct fsverity_info *vi;
int err;
if (!capable(CAP_SYS_ADMIN)) {
pr_debug("Process '%s' is not authorized to provide fs-verity measurements\n",
current->comm);
err = -EACCES;
goto out;
}
if (copy_from_user(&arg, uarg, sizeof(arg))) {
err = -EFAULT;
goto out;
}
hash_alg = fsverity_check_measurement_struct(&arg);
if (IS_ERR(hash_alg)) {
err = PTR_ERR(hash_alg);
goto out;
}
if (copy_from_user(digest, uarg->digest, hash_alg->digest_size)) {
err = -EFAULT;
goto out;
}
vi = get_fsverity_info(inode);
if (!vi) {
pr_debug("File does not have fs-verity enabled\n");
err = -EINVAL;
goto out;
}
inode_lock(inode);
if (hash_alg != vi->hash_alg) {
pr_warn("Hash algorithm mismatch: provided %s, but file uses %s\n",
hash_alg->friendly_name, vi->hash_alg->friendly_name);
err = -EBADMSG;
vi->mode = FS_VERITY_MODE_AUTHENTICATION_FAILED;
truncate_inode_pages(inode->i_mapping, 0);
goto out_unlock;
}
if (memcmp(digest, vi->measurement, hash_alg->digest_size)) {
if (vi->mode == FS_VERITY_MODE_AUTHENTICATED)
pr_warn("Inconsistent measurements were provided! %*phN and %*phN\n",
hash_alg->digest_size, digest,
hash_alg->digest_size, vi->measurement);
else
pr_warn("File corrupted! Wanted measurement: %*phN, real measurement: %*phN\n",
hash_alg->digest_size, digest,
hash_alg->digest_size, vi->measurement);
vi->mode = FS_VERITY_MODE_AUTHENTICATION_FAILED;
truncate_inode_pages(inode->i_mapping, 0);
err = -EBADMSG;
goto out_unlock;
}
if (vi->mode == FS_VERITY_MODE_AUTHENTICATED) {
pr_debug("Measurement already being enforced: %*phN\n",
hash_alg->digest_size, vi->measurement);
} else {
pr_debug("Measurement validated: %*phN\n",
hash_alg->digest_size, vi->measurement);
vi->mode = FS_VERITY_MODE_AUTHENTICATED;
/*
* No need to truncate pages here. Any pages that were read
* without authentication still had their integrity verified up
* to ->root_hash, so now we know they are authentic as we've
* just authenticated ->root_hash.
*/
/* Pin the inode so that the measurement doesn't go away */
ihold(inode);
spin_lock(&sb->s_inode_fsveritylist_lock);
list_add(&inode->i_fsverity_list, &sb->s_inodes_fsverity);
spin_unlock(&sb->s_inode_fsveritylist_lock);
}
err = 0;
out_unlock:
inode_unlock(inode);
out:
if (err)
pr_debug("FS_IOC_SET_VERITY_MEASUREMENT failed on inode %lu (err %d)\n",
inode->i_ino, err);
else
pr_debug("FS_IOC_SET_VERITY_MEASUREMENT succeeded on inode %lu\n",
inode->i_ino);
return err;
}
EXPORT_SYMBOL_GPL(fsverity_ioctl_set_measurement);
#endif /* CONFIG_FS_VERITY_USERSPACE_SIG_VERIFY */