aboutsummaryrefslogtreecommitdiffstats
path: root/security/loadpin/loadpin.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/loadpin/loadpin.c')
-rw-r--r--security/loadpin/loadpin.c184
1 files changed, 179 insertions, 5 deletions
diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c
index b12f7d986b1e..de41621f4998 100644
--- a/security/loadpin/loadpin.c
+++ b/security/loadpin/loadpin.c
@@ -18,6 +18,10 @@
#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)
{
@@ -43,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
@@ -78,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");
@@ -174,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;
@@ -240,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;
}
@@ -248,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");