diff options
Diffstat (limited to 'security/loadpin')
-rw-r--r-- | security/loadpin/Kconfig | 21 | ||||
-rw-r--r-- | security/loadpin/loadpin.c | 202 |
2 files changed, 215 insertions, 8 deletions
diff --git a/security/loadpin/Kconfig b/security/loadpin/Kconfig index 91be65dec2ab..6724eaba3d36 100644 --- a/security/loadpin/Kconfig +++ b/security/loadpin/Kconfig @@ -18,3 +18,24 @@ config SECURITY_LOADPIN_ENFORCE If selected, LoadPin will enforce pinning at boot. If not selected, it can be enabled at boot with the kernel parameter "loadpin.enforce=1". + +config SECURITY_LOADPIN_VERITY + bool "Allow reading files from certain other filesystems that use dm-verity" + depends on SECURITY_LOADPIN && DM_VERITY=y && SECURITYFS + help + If selected LoadPin can allow reading files from filesystems + that use dm-verity. LoadPin maintains a list of verity root + digests it considers trusted. A verity backed filesystem is + considered trusted if its root digest is found in the list + of trusted digests. + + The list of trusted verity can be populated through an ioctl + on the LoadPin securityfs entry 'dm-verity'. The ioctl + expects a file descriptor of a file with verity digests as + parameter. The file must be located on the pinned root and + start with the line: + + # LOADPIN_TRUSTED_VERITY_ROOT_DIGESTS + + This is followed by the verity digests, with one digest per + line. diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c index ee5cb944f4ad..de41621f4998 100644 --- a/security/loadpin/loadpin.c +++ b/security/loadpin/loadpin.c @@ -11,11 +11,17 @@ #include <linux/module.h> #include <linux/fs.h> +#include <linux/kernel_read_file.h> #include <linux/lsm_hooks.h> #include <linux/mount.h> +#include <linux/blkdev.h> #include <linux/path.h> #include <linux/sched.h> /* current */ #include <linux/string_helpers.h> +#include <linux/dm-verity-loadpin.h> +#include <uapi/linux/loadpin.h> + +#define VERITY_DIGEST_FILE_HEADER "# LOADPIN_TRUSTED_VERITY_ROOT_DIGESTS" static void report_load(const char *origin, struct file *file, char *operation) { @@ -41,6 +47,9 @@ static char *exclude_read_files[READING_MAX_ID]; static int ignore_read_file_id[READING_MAX_ID] __ro_after_init; static struct super_block *pinned_root; static DEFINE_SPINLOCK(pinned_root_spinlock); +#ifdef CONFIG_SECURITY_LOADPIN_VERITY +static bool deny_reading_verity_digests; +#endif #ifdef CONFIG_SYSCTL @@ -76,11 +85,8 @@ static void check_pinning_enforcement(struct super_block *mnt_sb) * device, allow sysctl to change modes for testing. */ if (mnt_sb->s_bdev) { - char bdev[BDEVNAME_SIZE]; - ro = bdev_read_only(mnt_sb->s_bdev); - bdevname(mnt_sb->s_bdev, bdev); - pr_info("%s (%u:%u): %s\n", bdev, + pr_info("%pg (%u:%u): %s\n", mnt_sb->s_bdev, MAJOR(mnt_sb->s_bdev->bd_dev), MINOR(mnt_sb->s_bdev->bd_dev), ro ? "read-only" : "writable"); @@ -116,11 +122,21 @@ static void loadpin_sb_free_security(struct super_block *mnt_sb) } } -static int loadpin_read_file(struct file *file, enum kernel_read_file_id id) +static int loadpin_read_file(struct file *file, enum kernel_read_file_id id, + bool contents) { struct super_block *load_root; const char *origin = kernel_read_file_id_str(id); + /* + * If we will not know that we'll be seeing the full contents + * then we cannot trust a load will be complete and unchanged + * off disk. Treat all contents=false hooks as if there were + * no associated file struct. + */ + if (!contents) + file = NULL; + /* If the file id is excluded, ignore the pinning. */ if ((unsigned int)id < ARRAY_SIZE(ignore_read_file_id) && ignore_read_file_id[id]) { @@ -162,7 +178,8 @@ static int loadpin_read_file(struct file *file, enum kernel_read_file_id id) spin_unlock(&pinned_root_spinlock); } - if (IS_ERR_OR_NULL(pinned_root) || load_root != pinned_root) { + if (IS_ERR_OR_NULL(pinned_root) || + ((load_root != pinned_root) && !dm_verity_loadpin_is_bdev_trusted(load_root->s_bdev))) { if (unlikely(!enforce)) { report_load(origin, file, "pinning-ignored"); return 0; @@ -175,9 +192,9 @@ static int loadpin_read_file(struct file *file, enum kernel_read_file_id id) return 0; } -static int loadpin_load_data(enum kernel_load_data_id id) +static int loadpin_load_data(enum kernel_load_data_id id, bool contents) { - return loadpin_read_file(NULL, (enum kernel_read_file_id) id); + return loadpin_read_file(NULL, (enum kernel_read_file_id) id, contents); } static struct security_hook_list loadpin_hooks[] __lsm_ro_after_init = { @@ -228,6 +245,7 @@ static int __init loadpin_init(void) enforce ? "" : "not "); parse_exclude(); security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks), "loadpin"); + return 0; } @@ -236,6 +254,174 @@ DEFINE_LSM(loadpin) = { .init = loadpin_init, }; +#ifdef CONFIG_SECURITY_LOADPIN_VERITY + +enum loadpin_securityfs_interface_index { + LOADPIN_DM_VERITY, +}; + +static int read_trusted_verity_root_digests(unsigned int fd) +{ + struct fd f; + void *data; + int rc; + char *p, *d; + + if (deny_reading_verity_digests) + return -EPERM; + + /* The list of trusted root digests can only be set up once */ + if (!list_empty(&dm_verity_loadpin_trusted_root_digests)) + return -EPERM; + + f = fdget(fd); + if (!f.file) + return -EINVAL; + + data = kzalloc(SZ_4K, GFP_KERNEL); + if (!data) { + rc = -ENOMEM; + goto err; + } + + rc = kernel_read_file(f.file, 0, (void **)&data, SZ_4K - 1, NULL, READING_POLICY); + if (rc < 0) + goto err; + + p = data; + p[rc] = '\0'; + p = strim(p); + + p = strim(data); + while ((d = strsep(&p, "\n")) != NULL) { + int len; + struct dm_verity_loadpin_trusted_root_digest *trd; + + if (d == data) { + /* first line, validate header */ + if (strcmp(d, VERITY_DIGEST_FILE_HEADER)) { + rc = -EPROTO; + goto err; + } + + continue; + } + + len = strlen(d); + + if (len % 2) { + rc = -EPROTO; + goto err; + } + + len /= 2; + + trd = kzalloc(struct_size(trd, data, len), GFP_KERNEL); + if (!trd) { + rc = -ENOMEM; + goto err; + } + + if (hex2bin(trd->data, d, len)) { + kfree(trd); + rc = -EPROTO; + goto err; + } + + trd->len = len; + + list_add_tail(&trd->node, &dm_verity_loadpin_trusted_root_digests); + } + + if (list_empty(&dm_verity_loadpin_trusted_root_digests)) { + rc = -EPROTO; + goto err; + } + + kfree(data); + fdput(f); + + return 0; + +err: + kfree(data); + + /* any failure in loading/parsing invalidates the entire list */ + { + struct dm_verity_loadpin_trusted_root_digest *trd, *tmp; + + list_for_each_entry_safe(trd, tmp, &dm_verity_loadpin_trusted_root_digests, node) { + list_del(&trd->node); + kfree(trd); + } + } + + /* disallow further attempts after reading a corrupt/invalid file */ + deny_reading_verity_digests = true; + + fdput(f); + + return rc; +} + +/******************************** securityfs ********************************/ + +static long dm_verity_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + void __user *uarg = (void __user *)arg; + unsigned int fd; + + switch (cmd) { + case LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS: + if (copy_from_user(&fd, uarg, sizeof(fd))) + return -EFAULT; + + return read_trusted_verity_root_digests(fd); + + default: + return -EINVAL; + } +} + +static const struct file_operations loadpin_dm_verity_ops = { + .unlocked_ioctl = dm_verity_ioctl, + .compat_ioctl = compat_ptr_ioctl, +}; + +/** + * init_loadpin_securityfs - create the securityfs directory for LoadPin + * + * We can not put this method normally under the loadpin_init() code path since + * the security subsystem gets initialized before the vfs caches. + * + * Returns 0 if the securityfs directory creation was successful. + */ +static int __init init_loadpin_securityfs(void) +{ + struct dentry *loadpin_dir, *dentry; + + loadpin_dir = securityfs_create_dir("loadpin", NULL); + if (IS_ERR(loadpin_dir)) { + pr_err("LoadPin: could not create securityfs dir: %ld\n", + PTR_ERR(loadpin_dir)); + return PTR_ERR(loadpin_dir); + } + + dentry = securityfs_create_file("dm-verity", 0600, loadpin_dir, + (void *)LOADPIN_DM_VERITY, &loadpin_dm_verity_ops); + if (IS_ERR(dentry)) { + pr_err("LoadPin: could not create securityfs entry 'dm-verity': %ld\n", + PTR_ERR(dentry)); + return PTR_ERR(dentry); + } + + return 0; +} + +fs_initcall(init_loadpin_securityfs); + +#endif /* CONFIG_SECURITY_LOADPIN_VERITY */ + /* Should not be mutable after boot, so not listed in sysfs (perm == 0). */ module_param(enforce, int, 0); MODULE_PARM_DESC(enforce, "Enforce module/firmware pinning"); |