Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
LoadPin: Enable loading from trusted dm-verity devices
Extend LoadPin to allow loading of kernel files from trusted
dm-verity [1] devices.

This change adds the concept of trusted verity devices to
LoadPin. Userspace can use the new systl file
'loadpin/trusted_verity_root_digests_path' to provide
LoadPin with the path of a file with a list of root digests
from dm-verity devices that LoadPin should consider as
trusted. This file must be located on the pinned root.

When a kernel file is read LoadPin first checks
(as usual) whether the file is located on the pinned root,
if so the file can be loaded. Otherwise, if the verity
extension is enabled, LoadPin determines whether the
file is located on a verity backed device and whether
the root digest of that device is in the list of trusted
digests. The file can be loaded if the verity device has
a trusted root digest.

The path of the file with the trusted root digests can
only be written once, which is typically done at boot time.

Background:

As of now LoadPin restricts loading of kernel files to a single
pinned filesystem, typically the rootfs. This works for many
systems, however it can result in a bloated rootfs (and OTA
updates) on platforms where multiple boards with different
hardware configurations use the same rootfs image. Especially
when 'optional' files are large it may be preferable to
download/install them only when they are actually needed by a
given board. Chrome OS uses Downloadable Content (DLC) [2] to
deploy certain 'packages' at runtime. As an example a DLC
package could contain firmware for a peripheral that is not
present on all boards. DLCs use dm-verity to verify the
integrity of the DLC content.

[1] https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html
[2] https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/dlcservice/docs/developer.md

Signed-off-by: Matthias Kaehlcke <mka@chromium.org>
  • Loading branch information
Matthias Kaehlcke authored and intel-lab-lkp committed Apr 26, 2022
1 parent 8724fb8 commit f3a5490
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 1 deletion.
16 changes: 16 additions & 0 deletions security/loadpin/Kconfig
Expand Up @@ -18,3 +18,19 @@ 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 DM_VERITY=y && SYSCTL
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.

Userspace can populate the list of trusted digests by writing
the path of a file with the digests to the syctl file
'trusted_verity_root_digests_path'. The file must be located
on the pinned root and contain a comma separated list of
digests.
200 changes: 199 additions & 1 deletion security/loadpin/loadpin.c
Expand Up @@ -18,6 +18,8 @@
#include <linux/path.h>
#include <linux/sched.h> /* current */
#include <linux/string_helpers.h>
#include <linux/device-mapper.h>
#include <linux/dm-verity-loadpin.h>

static void report_load(const char *origin, struct file *file, char *operation)
{
Expand All @@ -43,6 +45,10 @@ 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 LIST_HEAD(trusted_verity_root_digests);
static const char *verity_digests_path;
#endif

#ifdef CONFIG_SYSCTL

Expand All @@ -65,6 +71,176 @@ static struct ctl_table loadpin_sysctl_table[] = {
{ }
};

#ifdef CONFIG_SECURITY_LOADPIN_VERITY

static int loadpin_read_file(struct file *file, enum kernel_read_file_id id,
bool contents);

static int read_trusted_verity_root_digests(struct file *file)
{
void *data;
char *p, *d;
int err, rc;

data = kzalloc(SZ_4K, GFP_KERNEL);
if (!data)
return -ENOMEM;

rc = kernel_read_file(file, 0, &data, SZ_4K - 1, NULL, READING_POLICY);
if (rc < 0)
return rc;

((char *)data)[rc] = '\0';

p = strim(data);
while ((d = strsep(&p, ",")) != NULL) {
int len = strlen(d);
struct trusted_root_digest *trd;

if (len % 2) {
err = -EPROTO;
goto free_mem;
}

len /= 2;

trd = kzalloc(sizeof(*trd), GFP_KERNEL);
if (!trd) {
err = -ENOMEM;
goto free_mem;
}

trd->data = kzalloc(len, GFP_KERNEL);
if (!trd->data) {
kfree(trd);
err = -ENOMEM;
goto free_mem;
}

if (hex2bin(trd->data, d, len)) {
kfree(trd);
err = -EPROTO;
goto free_mem;
}

list_add_tail(&trd->node, &trusted_verity_root_digests);

trd->len = len;
}

kfree(data);

return 0;

free_mem:
kfree(data);

{
struct trusted_root_digest *trd, *tmp;

list_for_each_entry_safe(trd, tmp, &trusted_verity_root_digests, node) {
kfree(trd->data);
list_del(&trd->node);
kfree(trd);
}
}

return err;
}

static int proc_verity_digests(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
struct ctl_table tbl = *table;

if (write) {
int rc;
char *digests_path;
struct file *file;

if (*ppos)
return -EINVAL;

if (verity_digests_path != NULL)
return -EPERM;

digests_path = kzalloc(tbl.maxlen, GFP_KERNEL);
if (!digests_path)
return -ENOMEM;

tbl.data = digests_path;

rc = proc_dostring(&tbl, write, buffer, lenp, ppos);
if (rc) {
kfree(digests_path);
return rc;
}

/* only absolute paths are allowed */
if (digests_path[0] != '/') {
kfree(digests_path);
return -EINVAL;
}

file = filp_open(digests_path, O_RDONLY, 0);
if (IS_ERR(file)) {
int err = PTR_ERR(file);

if (err == -ENOENT) {
kfree(digests_path);
return -EINVAL;
}

return err;
}

/* verify the root digests stem from a trusted file system */
if (loadpin_read_file(file, READING_POLICY, true)) {
fput(file);
kfree(digests_path);
return -EPERM;
}

rc = read_trusted_verity_root_digests(file);
fput(file);
if (rc) {
kfree(digests_path);
return rc;
}

verity_digests_path = digests_path;
} else {
if (verity_digests_path) {
tbl.data = kzalloc(strlen(verity_digests_path) + 1, GFP_KERNEL);
strcpy(tbl.data, verity_digests_path);
} else {
tbl.data = kzalloc(1, GFP_KERNEL);
}

proc_dostring(&tbl, write, buffer, lenp, ppos);
kfree(tbl.data);
}

return 0;
}

static struct ctl_table loadpin_sysctl_table_verity_digests[] = {
{
.procname = "trusted_verity_root_digests_path",
.maxlen = SZ_256,
.mode = 0644,
.proc_handler = proc_verity_digests,
},

{ }
};

#else

static struct ctl_table loadpin_sysctl_table_verity_digests[] = {};

#endif /* CONFIG_SECURITY_LOADPIN_VERITY */

/*
* This must be called after early kernel init, since then the rootdev
* is available.
Expand Down Expand Up @@ -118,6 +294,20 @@ static void loadpin_sb_free_security(struct super_block *mnt_sb)
}
}

static bool loadpin_is_fs_trusted(struct super_block *sb)
{
struct mapped_device *md = dm_get_md(sb->s_bdev->bd_dev);
bool trusted;

if (!md)
return false;

trusted = dm_verity_loadpin_is_md_trusted(md);
dm_put(md);

return trusted;
}

static int loadpin_read_file(struct file *file, enum kernel_read_file_id id,
bool contents)
{
Expand Down Expand Up @@ -174,7 +364,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) && !loadpin_is_fs_trusted(load_root))) {
if (unlikely(!enforce)) {
report_load(origin, file, "pinning-ignored");
return 0;
Expand Down Expand Up @@ -240,6 +431,13 @@ static int __init loadpin_init(void)
enforce ? "" : "not ");
parse_exclude();
security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks), "loadpin");

if (IS_ENABLED(CONFIG_SECURITY_LOADPIN_VERITY)) {
if (!register_sysctl_paths(loadpin_sysctl_path,
loadpin_sysctl_table_verity_digests))
pr_notice("sysctl registration failed!\n");
}

return 0;
}

Expand Down

0 comments on commit f3a5490

Please sign in to comment.