196 lines
5.2 KiB
C
196 lines
5.2 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* 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
|
||
|
* @inode: the file's inode
|
||
|
* @signature: the file's signature
|
||
|
* @sig_size: size of @signature. Can be 0 if there is no signature
|
||
|
* @file_digest: the file's digest
|
||
|
* @digest_algorithm: the digest algorithm used
|
||
|
*
|
||
|
* Takes the file's digest and optional signature and verifies the signature
|
||
|
* against the digest and the fs-verity keyring if appropriate
|
||
|
*
|
||
|
* Return: 0 on success (signature valid or not required); -errno on failure
|
||
|
*/
|
||
|
int __fsverity_verify_signature(const struct inode *inode, const u8 *signature,
|
||
|
u32 sig_size, const u8 *file_digest,
|
||
|
unsigned int digest_algorithm)
|
||
|
{
|
||
|
struct fsverity_formatted_digest *d;
|
||
|
struct fsverity_hash_alg *hash_alg = fsverity_get_hash_alg(inode,
|
||
|
digest_algorithm);
|
||
|
int err;
|
||
|
|
||
|
if (IS_ERR(hash_alg))
|
||
|
return PTR_ERR(hash_alg);
|
||
|
|
||
|
if (sig_size == 0) {
|
||
|
if (fsverity_require_signatures) {
|
||
|
fsverity_err(inode,
|
||
|
"require_signatures=1, rejecting unsigned file!");
|
||
|
return -EPERM;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
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, file_digest, hash_alg->digest_size);
|
||
|
|
||
|
err = verify_pkcs7_signature(d, sizeof(*d) + hash_alg->digest_size,
|
||
|
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 digest %s:%*phN\n",
|
||
|
hash_alg->name, hash_alg->digest_size, file_digest);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(__fsverity_verify_signature);
|
||
|
|
||
|
/**
|
||
|
* fsverity_verify_signature() - check a verity file's signature
|
||
|
* @vi: the file's fsverity_info
|
||
|
* @desc: the file's fsverity_descriptor
|
||
|
* @desc_size: size of @desc
|
||
|
*
|
||
|
* If the file's fs-verity descriptor includes a signature of the file digest,
|
||
|
* 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);
|
||
|
|
||
|
if (sig_size > desc_size - sizeof(*desc)) {
|
||
|
fsverity_err(inode, "Signature overflows verity descriptor");
|
||
|
return -EBADMSG;
|
||
|
}
|
||
|
|
||
|
return __fsverity_verify_signature(inode, desc->signature, sig_size,
|
||
|
vi->file_digest, hash_alg - fsverity_hash_algs);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_SYSCTL
|
||
|
static struct ctl_table_header *fsverity_sysctl_header;
|
||
|
|
||
|
static const struct ctl_path fsverity_sysctl_path[] = {
|
||
|
{ .procname = "fs", },
|
||
|
{ .procname = "verity", },
|
||
|
{ }
|
||
|
};
|
||
|
|
||
|
/* shared constants to be used in various sysctls */
|
||
|
static int sysctl_vals[] = { 0, 1, INT_MAX };
|
||
|
|
||
|
#define SYSCTL_ZERO ((void *)&sysctl_vals[0])
|
||
|
#define SYSCTL_ONE ((void *)&sysctl_vals[1])
|
||
|
#define SYSCTL_INT_MAX ((void *)&sysctl_vals[2])
|
||
|
|
||
|
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;
|
||
|
}
|