fs-verity: support builtin file signatures

To meet some users' needs, add optional support for having fs-verity
handle a portion of the authentication policy in the kernel.  An
".fs-verity" keyring is created to which X.509 certificates can be
added; then a sysctl 'fs.verity.require_signatures' can be set to cause
the kernel to enforce that all fs-verity files contain a signature of
their file measurement by a key in this keyring.

See the "Built-in signature verification" section of
Documentation/filesystems/fsverity.rst for the full documentation.

Reviewed-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Eric Biggers <ebiggers@google.com>
This commit is contained in:
Eric Biggers 2019-07-22 09:26:23 -07:00
parent add890c9f9
commit 432434c9f8
8 changed files with 269 additions and 14 deletions

View File

@ -36,3 +36,20 @@ config FS_VERITY_DEBUG
Enable debugging messages related to fs-verity by default.
Say N unless you are an fs-verity developer.
config FS_VERITY_BUILTIN_SIGNATURES
bool "FS Verity builtin signature support"
depends on FS_VERITY
select SYSTEM_DATA_VERIFICATION
help
Support verifying signatures of verity files against the X.509
certificates that have been loaded into the ".fs-verity"
kernel keyring.
This is meant as a relatively simple mechanism that can be
used to provide an authenticity guarantee for verity files, as
an alternative to IMA appraisal. Userspace programs still
need to check that the verity bit is set in order to get an
authenticity guarantee.
If unsure, say N.

View File

@ -6,3 +6,5 @@ obj-$(CONFIG_FS_VERITY) += enable.o \
measure.o \
open.o \
verify.o
obj-$(CONFIG_FS_VERITY_BUILTIN_SIGNATURES) += signature.o

View File

@ -161,7 +161,7 @@ static int enable_verity(struct file *filp,
const struct fsverity_operations *vops = inode->i_sb->s_vop;
struct merkle_tree_params params = { };
struct fsverity_descriptor *desc;
size_t desc_size = sizeof(*desc);
size_t desc_size = sizeof(*desc) + arg->sig_size;
struct fsverity_info *vi;
int err;
@ -183,6 +183,16 @@ static int enable_verity(struct file *filp,
}
desc->salt_size = arg->salt_size;
/* Get the signature if the user provided one */
if (arg->sig_size &&
copy_from_user(desc->signature,
(const u8 __user *)(uintptr_t)arg->sig_ptr,
arg->sig_size)) {
err = -EFAULT;
goto out;
}
desc->sig_size = cpu_to_le32(arg->sig_size);
desc->data_size = cpu_to_le64(inode->i_size);
/* Prepare the Merkle tree parameters */
@ -238,6 +248,10 @@ static int enable_verity(struct file *filp,
goto rollback;
}
if (arg->sig_size)
pr_debug("Storing a %u-byte PKCS#7 signature alongside the file\n",
arg->sig_size);
/*
* Tell the filesystem to finish enabling verity on the file.
* Serialized with ->begin_enable_verity() by the inode lock.
@ -304,8 +318,8 @@ int fsverity_ioctl_enable(struct file *filp, const void __user *uarg)
if (arg.salt_size > FIELD_SIZEOF(struct fsverity_descriptor, salt))
return -EMSGSIZE;
if (arg.sig_size)
return -EINVAL;
if (arg.sig_size > FS_VERITY_MAX_SIGNATURE_SIZE)
return -EMSGSIZE;
/*
* Require a regular file with write access. But the actual fd must

View File

@ -75,23 +75,41 @@ struct fsverity_info {
};
/*
* Merkle tree properties. The file measurement is the hash of this structure.
* Merkle tree properties. The file measurement is the hash of this structure
* excluding the signature and with the sig_size field set to 0.
*/
struct fsverity_descriptor {
__u8 version; /* must be 1 */
__u8 hash_algorithm; /* Merkle tree hash algorithm */
__u8 log_blocksize; /* log2 of size of data and tree blocks */
__u8 salt_size; /* size of salt in bytes; 0 if none */
__le32 sig_size; /* reserved, must be 0 */
__le32 sig_size; /* size of signature in bytes; 0 if none */
__le64 data_size; /* size of file the Merkle tree is built over */
__u8 root_hash[64]; /* Merkle tree root hash */
__u8 salt[32]; /* salt prepended to each hashed block */
__u8 __reserved[144]; /* must be 0's */
__u8 signature[]; /* optional PKCS#7 signature */
};
/* Arbitrary limit to bound the kmalloc() size. Can be changed. */
#define FS_VERITY_MAX_DESCRIPTOR_SIZE 16384
#define FS_VERITY_MAX_SIGNATURE_SIZE (FS_VERITY_MAX_DESCRIPTOR_SIZE - \
sizeof(struct fsverity_descriptor))
/*
* Format in which verity file measurements are signed. This is the same as
* 'struct fsverity_digest', except here some magic bytes are prepended to
* provide some context about what is being signed in case the same key is used
* for non-fsverity purposes, and here the fields have fixed endianness.
*/
struct fsverity_signed_digest {
char magic[8]; /* must be "FSVerity" */
__le16 digest_algorithm;
__le16 digest_size;
__u8 digest[];
};
/* hash_algs.c */
extern struct fsverity_hash_alg fsverity_hash_algs[];
@ -127,7 +145,7 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
const u8 *salt, size_t salt_size);
struct fsverity_info *fsverity_create_info(const struct inode *inode,
const void *desc, size_t desc_size);
void *desc, size_t desc_size);
void fsverity_set_info(struct inode *inode, struct fsverity_info *vi);
@ -136,8 +154,32 @@ void fsverity_free_info(struct fsverity_info *vi);
int __init fsverity_init_info_cache(void);
void __init fsverity_exit_info_cache(void);
/* signature.c */
#ifdef CONFIG_FS_VERITY_BUILTIN_SIGNATURES
int fsverity_verify_signature(const struct fsverity_info *vi,
const struct fsverity_descriptor *desc,
size_t desc_size);
int __init fsverity_init_signature(void);
#else /* !CONFIG_FS_VERITY_BUILTIN_SIGNATURES */
static inline int
fsverity_verify_signature(const struct fsverity_info *vi,
const struct fsverity_descriptor *desc,
size_t desc_size)
{
return 0;
}
static inline int fsverity_init_signature(void)
{
return 0;
}
#endif /* !CONFIG_FS_VERITY_BUILTIN_SIGNATURES */
/* verify.c */
int __init fsverity_init_workqueue(void);
void __init fsverity_exit_workqueue(void);
#endif /* _FSVERITY_PRIVATE_H */

View File

@ -45,9 +45,15 @@ static int __init fsverity_init(void)
if (err)
goto err_exit_info_cache;
err = fsverity_init_signature();
if (err)
goto err_exit_workqueue;
pr_debug("Initialized fs-verity\n");
return 0;
err_exit_workqueue:
fsverity_exit_workqueue();
err_exit_info_cache:
fsverity_exit_info_cache();
return err;

View File

@ -122,22 +122,32 @@ out_err:
return err;
}
/* Compute the file measurement by hashing the fsverity_descriptor. */
/*
* Compute the file measurement by hashing the fsverity_descriptor excluding the
* signature and with the sig_size field set to 0.
*/
static int compute_file_measurement(const struct fsverity_hash_alg *hash_alg,
const struct fsverity_descriptor *desc,
struct fsverity_descriptor *desc,
u8 *measurement)
{
return fsverity_hash_buffer(hash_alg, desc, sizeof(*desc), measurement);
__le32 sig_size = desc->sig_size;
int err;
desc->sig_size = 0;
err = fsverity_hash_buffer(hash_alg, desc, sizeof(*desc), measurement);
desc->sig_size = sig_size;
return err;
}
/*
* Validate the given fsverity_descriptor and create a new fsverity_info from
* it.
* it. The signature (if present) is also checked.
*/
struct fsverity_info *fsverity_create_info(const struct inode *inode,
const void *_desc, size_t desc_size)
void *_desc, size_t desc_size)
{
const struct fsverity_descriptor *desc = _desc;
struct fsverity_descriptor *desc = _desc;
struct fsverity_info *vi;
int err;
@ -153,8 +163,7 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode,
return ERR_PTR(-EINVAL);
}
if (desc->sig_size ||
memchr_inv(desc->__reserved, 0, sizeof(desc->__reserved))) {
if (memchr_inv(desc->__reserved, 0, sizeof(desc->__reserved))) {
fsverity_err(inode, "Reserved bits set in descriptor");
return ERR_PTR(-EINVAL);
}
@ -198,6 +207,8 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode,
pr_debug("Computed file measurement: %s:%*phN\n",
vi->tree_params.hash_alg->name,
vi->tree_params.digest_size, vi->measurement);
err = fsverity_verify_signature(vi, desc, desc_size);
out:
if (err) {
fsverity_free_info(vi);

157
fs/verity/signature.c Normal file
View File

@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0
/*
* fs/verity/signature.c: verification of builtin signatures
*
* Copyright 2019 Google LLC
*/
#include "fsverity_private.h"
#include <linux/cred.h>
#include <linux/key.h>
#include <linux/slab.h>
#include <linux/verification.h>
/*
* /proc/sys/fs/verity/require_signatures
* If 1, all verity files must have a valid builtin signature.
*/
static int fsverity_require_signatures;
/*
* Keyring that contains the trusted X.509 certificates.
*
* Only root (kuid=0) can modify this. Also, root may use
* keyctl_restrict_keyring() to prevent any more additions.
*/
static struct key *fsverity_keyring;
/**
* fsverity_verify_signature() - check a verity file's signature
*
* If the file's fs-verity descriptor includes a signature of the file
* measurement, verify it against the certificates in the fs-verity keyring.
*
* Return: 0 on success (signature valid or not required); -errno on failure
*/
int fsverity_verify_signature(const struct fsverity_info *vi,
const struct fsverity_descriptor *desc,
size_t desc_size)
{
const struct inode *inode = vi->inode;
const struct fsverity_hash_alg *hash_alg = vi->tree_params.hash_alg;
const u32 sig_size = le32_to_cpu(desc->sig_size);
struct fsverity_signed_digest *d;
int err;
if (sig_size == 0) {
if (fsverity_require_signatures) {
fsverity_err(inode,
"require_signatures=1, rejecting unsigned file!");
return -EPERM;
}
return 0;
}
if (sig_size > desc_size - sizeof(*desc)) {
fsverity_err(inode, "Signature overflows verity descriptor");
return -EBADMSG;
}
d = kzalloc(sizeof(*d) + hash_alg->digest_size, GFP_KERNEL);
if (!d)
return -ENOMEM;
memcpy(d->magic, "FSVerity", 8);
d->digest_algorithm = cpu_to_le16(hash_alg - fsverity_hash_algs);
d->digest_size = cpu_to_le16(hash_alg->digest_size);
memcpy(d->digest, vi->measurement, hash_alg->digest_size);
err = verify_pkcs7_signature(d, sizeof(*d) + hash_alg->digest_size,
desc->signature, sig_size,
fsverity_keyring,
VERIFYING_UNSPECIFIED_SIGNATURE,
NULL, NULL);
kfree(d);
if (err) {
if (err == -ENOKEY)
fsverity_err(inode,
"File's signing cert isn't in the fs-verity keyring");
else if (err == -EKEYREJECTED)
fsverity_err(inode, "Incorrect file signature");
else if (err == -EBADMSG)
fsverity_err(inode, "Malformed file signature");
else
fsverity_err(inode, "Error %d verifying file signature",
err);
return err;
}
pr_debug("Valid signature for file measurement %s:%*phN\n",
hash_alg->name, hash_alg->digest_size, vi->measurement);
return 0;
}
#ifdef CONFIG_SYSCTL
static struct ctl_table_header *fsverity_sysctl_header;
static const struct ctl_path fsverity_sysctl_path[] = {
{ .procname = "fs", },
{ .procname = "verity", },
{ }
};
static struct ctl_table fsverity_sysctl_table[] = {
{
.procname = "require_signatures",
.data = &fsverity_require_signatures,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{ }
};
static int __init fsverity_sysctl_init(void)
{
fsverity_sysctl_header = register_sysctl_paths(fsverity_sysctl_path,
fsverity_sysctl_table);
if (!fsverity_sysctl_header) {
pr_err("sysctl registration failed!\n");
return -ENOMEM;
}
return 0;
}
#else /* !CONFIG_SYSCTL */
static inline int __init fsverity_sysctl_init(void)
{
return 0;
}
#endif /* !CONFIG_SYSCTL */
int __init fsverity_init_signature(void)
{
struct key *ring;
int err;
ring = keyring_alloc(".fs-verity", KUIDT_INIT(0), KGIDT_INIT(0),
current_cred(), KEY_POS_SEARCH |
KEY_USR_VIEW | KEY_USR_READ | KEY_USR_WRITE |
KEY_USR_SEARCH | KEY_USR_SETATTR,
KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
if (IS_ERR(ring))
return PTR_ERR(ring);
err = fsverity_sysctl_init();
if (err)
goto err_put_ring;
fsverity_keyring = ring;
return 0;
err_put_ring:
key_put(ring);
return err;
}

View File

@ -273,3 +273,9 @@ int __init fsverity_init_workqueue(void)
return -ENOMEM;
return 0;
}
void __init fsverity_exit_workqueue(void)
{
destroy_workqueue(fsverity_read_workqueue);
fsverity_read_workqueue = NULL;
}