LoadPin: Enable loading from trusted dm-verity devices
[linux-2.6-microblaze.git] / security / loadpin / loadpin.c
index ad4e675..6ab5f2b 100644 (file)
@@ -18,6 +18,8 @@
 #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>
 
 static void report_load(const char *origin, struct file *file, char *operation)
 {
@@ -43,6 +45,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
 
@@ -171,7 +176,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;
@@ -237,6 +243,7 @@ static int __init loadpin_init(void)
                enforce ? "" : "not ");
        parse_exclude();
        security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks), "loadpin");
+
        return 0;
 }
 
@@ -245,6 +252,164 @@ 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 = strlen(d);
+               struct dm_verity_loadpin_trusted_root_digest *trd;
+
+               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;
+       int rc;
+
+       switch (cmd) {
+       case LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS:
+               rc = copy_from_user(&fd, uarg, sizeof(fd));
+               if (rc)
+                       return rc;
+
+               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");