2019-02-21 20:34:42

by Helen Koike

[permalink] [raw]
Subject: [PATCH v12] dm: add support to directly boot to a mapped device

Add a "create" module parameter, which allows device-mapper targets to be
configured at boot time. This enables early use of dm targets in the boot
process (as the root device or otherwise) without the need of an initramfs.

The syntax used in the boot param is based on the concise format from the
dmsetup tool to follow the rule of least surprise:

sudo dmsetup table --concise /dev/mapper/lroot

Which is:
dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]

Where,
<name> ::= The device name.
<uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
<minor> ::= The device minor number | ""
<flags> ::= "ro" | "rw"
<table> ::= <start_sector> <num_sectors> <target_type> <target_args>
<target_type> ::= "verity" | "linear" | ...

For example, the following could be added in the boot parameters:
dm-mod.create="lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0" root=/dev/dm-0

Only the targets that were tested are allowed and the ones that doesn't
change any block device when the dm is create as read-only. For example,
mirror and cache targets are not allowed. The rationale behind this is
that if the user makes a mistake, choosing the wrong device to be the
mirror or the cache can corrupt data.

The only targets allowed are:
* crypt
* delay
* linear
* snapshot-origin
* striped
* verity

Co-developed-by: Will Drewry <[email protected]>
Co-developed-by: Kees Cook <[email protected]>
Co-developed-by: Enric Balletbo i Serra <[email protected]>
Signed-off-by: Helen Koike <[email protected]>

---
Hello,

Just re-sending updating the patch header.

Thanks
Helen

Changes in v12:
- commit message updated
- v11:
https://www.redhat.com/archives/dm-devel/2019-February/msg00108.html

Changes in v11:
- Configure the device directly instead of performing in IOCTL (this
removed a lot of parsing code)
- Just enable the targets that were tested.
- Simplify/refactor parsing, as a consequence, escaping characters is not
allowed (but it wans't properly used in the previous version anyway)
- don't use sscanf, the size wans't being limited and we could have a
buffer overflow.
- change constrained targets list
- remove code from init/
- use a module parameter instead of a kernel comand line parameter in
init/
- rename dm-boot to dm-init

Changes in v10:
- https://lore.kernel.org/patchwork/project/lkml/list/?series=371523
- new file: drivers/md/dm-boot.c
- most of the parsing code was moved from init/do_mounts_dm.c to drivers/md/dm-boot.c
- parsing code was in essence replaced by the concise parser from dmsetup
_create_concise function:
https://sourceware.org/git/?p=lvm2.git;a=blob;f=libdm/dm-tools/dmsetup.c;h=835fdcdc75e8f0f0f7c4ed46cc9788a6616f58b8;hb=7498f8383397a93db95655ca227257836cbcac82#l1265
the main reason is that this code is already being used/tested by dmsetup, so
we can have some level of confidence that it works as expected. Besides this,
it also looks more efficient.
- Not all targets are allowed to be used by dm=, as pointed previously, there
are some risks in creating a mapped device without some validation from
userspace (see documentation from the patch listing which targets are allowed).
- Instead of using a simple singly linked list (for devices and tables), use
the struct list_head. This occupies unnecessary space in the code, but it makes
the code cleaner and easier to read and less prone to silly errors.
- Documentation and comments were reviewed and refactored, e.g.:
* "is to possible" was removed
* s/specified as a simple string/specified as a string/
- Added docs above __align function, make it clear that the second parameter @a
must be a power of two.
- Clean ups: removal of unnecessary includes, macros, variables, some redundant
checks and warnings.
- when calling ioctls, the code was allocating and freeing the same structure
a couple of times. So instead of executing kzalloc/kfree 3 times, execute
kmalloc once and reuse the structure after a memset, then finally kfree it once.
- update commit message

Changes in v9:
- https://www.redhat.com/archives/linux-lvm/2018-September/msg00016.html
- Add minor number to make it compatible with dmsetup concise format

Changes in v8:
- https://www.redhat.com/archives/linux-lvm/2017-May/msg00055.html
- Fix build error due commit
e516db4f67 (dm ioctl: add a new DM_DEV_ARM_POLL ioctl)

Changes in v7:
- http://lkml.iu.edu/hypermail/linux/kernel/1705.2/02657.html
- Add a new function to issue the equivalent of a DM ioctl programatically.
- Use the new ioctl interface to create the devices.
- Use a comma-delimited and semi-colon delimited dmsetup-like commands.

Changes in v6:
- https://www.redhat.com/archives/dm-devel/2017-April/msg00316.html

Changes in v5:
- https://www.redhat.com/archives/dm-devel/2016-February/msg00112.html

Documentation/device-mapper/dm-init.txt | 114 +++++++++
drivers/md/Kconfig | 10 +
drivers/md/Makefile | 4 +
drivers/md/dm-init.c | 308 ++++++++++++++++++++++++
drivers/md/dm-ioctl.c | 103 ++++++++
include/linux/device-mapper.h | 9 +
6 files changed, 548 insertions(+)
create mode 100644 Documentation/device-mapper/dm-init.txt
create mode 100644 drivers/md/dm-init.c

diff --git a/Documentation/device-mapper/dm-init.txt b/Documentation/device-mapper/dm-init.txt
new file mode 100644
index 000000000000..8464ee7c01b8
--- /dev/null
+++ b/Documentation/device-mapper/dm-init.txt
@@ -0,0 +1,114 @@
+Early creation of mapped devices
+====================================
+
+It is possible to configure a device-mapper device to act as the root device for
+your system in two ways.
+
+The first is to build an initial ramdisk which boots to a minimal userspace
+which configures the device, then pivot_root(8) in to it.
+
+The second is to create one or more device-mappers using the module parameter
+"dm-mod.create=" through the kernel boot command line argument.
+
+The format is specified as a string of data separated by commas and optionally
+semi-colons, where:
+ - a comma is used to separate fields like name, uuid, flags and table
+ (specifies one device)
+ - a semi-colon is used to separate devices.
+
+So the format will look like this:
+
+ dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
+
+Where,
+ <name> ::= The device name.
+ <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
+ <minor> ::= The device minor number | ""
+ <flags> ::= "ro" | "rw"
+ <table> ::= <start_sector> <num_sectors> <target_type> <target_args>
+ <target_type> ::= "verity" | "linear" | ... (see list below)
+
+The dm line should be equivalent to the one used by the dmsetup tool with the
+--concise argument.
+
+Target types
+============
+
+Not all target types are available as there are serious risks in allowing
+activation of certain DM targets without first using userspace tools to check
+the validity of associated metadata.
+
+ "cache": constrained, userspace should verify cache device
+ "crypt": allowed
+ "delay": allowed
+ "era": constrained, userspace should verify metadata device
+ "flakey": constrained, meant for test
+ "linear": allowed
+ "log-writes": constrained, userspace should verify metadata device
+ "mirror": constrained, userspace should verify main/mirror device
+ "raid": constrained, userspace should verify metadata device
+ "snapshot": constrained, userspace should verify src/dst device
+ "snapshot-origin": allowed
+ "snapshot-merge": constrained, userspace should verify src/dst device
+ "striped": allowed
+ "switch": constrained, userspace should verify dev path
+ "thin": constrained, requires dm target message from userspace
+ "thin-pool": constrained, requires dm target message from userspace
+ "verity": allowed
+ "writecache": constrained, userspace should verify cache device
+ "zero": constrained, not meant for rootfs
+
+If the target is not listed above, it is constrained by default (not tested).
+
+Examples
+========
+An example of booting to a linear array made up of user-mode linux block
+devices:
+
+ dm-mod.create="lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0" root=/dev/dm-0
+
+This will boot to a rw dm-linear target of 8192 sectors split across two block
+devices identified by their major:minor numbers. After boot, udev will rename
+this target to /dev/mapper/lroot (depending on the rules). No uuid was assigned.
+
+An example of multiple device-mappers, with the dm-mod.create="..." contents is shown here
+split on multiple lines for readability:
+
+ vroot,,,ro,
+ 0 1740800 verity 254:0 254:0 1740800 sha1
+ 76e9be054b15884a9fa85973e9cb274c93afadb6
+ 5b3549d54d6c7a3837b9b81ed72e49463a64c03680c47835bef94d768e5646fe;
+ vram,,,rw,
+ 0 32768 linear 1:0 0,
+ 32768 32768 linear 1:1 0
+
+Other examples (per target):
+
+"crypt":
+ dm-crypt,,8,ro,
+ 0 1048576 crypt aes-xts-plain64
+ babebabebabebabebabebabebabebabebabebabebabebabebabebabebabebabe 0
+ /dev/sda 0 1 allow_discards
+
+"delay":
+ dm-delay,,4,ro,0 409600 delay /dev/sda1 0 500
+
+"linear":
+ dm-linear,,,rw,
+ 0 32768 linear /dev/sda1 0,
+ 32768 1024000 linear /dev/sda2 0,
+ 1056768 204800 linear /dev/sda3 0,
+ 1261568 512000 linear /dev/sda4 0
+
+"snapshot-origin":
+ dm-snap-orig,,4,ro,0 409600 snapshot-origin 8:2
+
+"striped":
+ dm-striped,,4,ro,0 1638400 striped 4 4096
+ /dev/sda1 0 /dev/sda2 0 /dev/sda3 0 /dev/sda4 0
+
+"verity":
+ dm-verity,,4,ro,
+ 0 1638400 verity 1 8:1 8:2 4096 4096 204800 1 sha256
+ fb1a5a0f00deb908d8b53cb270858975e76cf64105d412ce764225d53b8f3cfd
+ 51934789604d1b92399c52e7cb149d1b3a1b74bbbcb103b2a0aaacbed5c08584
diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig
index 3db222509e44..644e868cc2eb 100644
--- a/drivers/md/Kconfig
+++ b/drivers/md/Kconfig
@@ -436,6 +436,16 @@ config DM_DELAY

If unsure, say N.

+config DM_INIT
+ bool "DM \"dm-mod.create=\" parameter support"
+ depends on BLK_DEV_DM=y
+ ---help---
+ Enable "dm-mod.create=" parameter to create mapped devices at init time.
+ This option is useful to allow mounting rootfs without requiring an
+ initramfs.
+ See Documentation/device-mapper/dm-init.txt for dm-mod.create="..."
+ format
+
config DM_UEVENT
bool "DM uevents"
depends on BLK_DEV_DM
diff --git a/drivers/md/Makefile b/drivers/md/Makefile
index 822f4e8753bc..a52b703e588e 100644
--- a/drivers/md/Makefile
+++ b/drivers/md/Makefile
@@ -69,6 +69,10 @@ obj-$(CONFIG_DM_INTEGRITY) += dm-integrity.o
obj-$(CONFIG_DM_ZONED) += dm-zoned.o
obj-$(CONFIG_DM_WRITECACHE) += dm-writecache.o

+ifeq ($(CONFIG_DM_INIT),y)
+dm-mod-objs += dm-init.o
+endif
+
ifeq ($(CONFIG_DM_UEVENT),y)
dm-mod-objs += dm-uevent.o
endif
diff --git a/drivers/md/dm-init.c b/drivers/md/dm-init.c
new file mode 100644
index 000000000000..24ea75911c7c
--- /dev/null
+++ b/drivers/md/dm-init.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * dm-init.c
+ * Copyright (C) 2017 The Chromium OS Authors <[email protected]>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/device-mapper.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/moduleparam.h>
+
+#define DM_MSG_PREFIX "dm"
+#define DM_MAX_DEVICES 256
+#define DM_MAX_TARGETS 256
+#define DM_MAX_STR_SIZE 4096
+
+static char *create;
+
+/*
+ * Format: dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
+ * Table format: <start_sector> <num_sectors> <target_type> <target_args>
+ *
+ * See Documentation/device-mapper/dm-init.txt for dm-mod.create="..." format
+ * details.
+ */
+
+struct dm_device {
+ struct dm_ioctl dmi;
+ struct dm_target_spec *table[DM_MAX_TARGETS];
+ char *target_args_array[DM_MAX_TARGETS];
+ struct list_head list;
+};
+
+const char *dm_allowed_targets[] __initconst = {
+ "crypt",
+ "delay",
+ "linear",
+ "snapshot-origin",
+ "striped",
+ "verity",
+};
+
+static int __init dm_verify_target_type(const char *target)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(dm_allowed_targets); i++) {
+ if (!strcmp(dm_allowed_targets[i], target))
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static void __init dm_setup_cleanup(struct list_head *devices)
+{
+ struct dm_device *dev, *tmp;
+ unsigned int i;
+
+ list_for_each_entry_safe(dev, tmp, devices, list) {
+ list_del(&dev->list);
+ for (i = 0; i < dev->dmi.target_count; i++) {
+ kfree(dev->table[i]);
+ kfree(dev->target_args_array[i]);
+ }
+ kfree(dev);
+ }
+}
+
+/**
+ * str_field_delimit - delimit a string based on a separator char.
+ * @str: the pointer to the string to delimit.
+ * @separator: char that delimits the field
+ *
+ * Find a @separator and replace it by '\0'.
+ * Remove leading and trailing spaces.
+ * Return the remainder string after the @separator.
+ */
+static char __init *str_field_delimit(char **str, char separator)
+{
+ char *s;
+
+ /* TODO: add support for escaped characters */
+ *str = skip_spaces(*str);
+ s = strchr(*str, separator);
+ /* Delimit the field and remove trailing spaces */
+ if (s)
+ *s = '\0';
+ *str = strim(*str);
+ return s ? ++s : NULL;
+}
+
+/**
+ * dm_parse_table_entry - parse a table entry
+ * @dev: device to store the parsed information.
+ * @str: the pointer to a string with the format:
+ * <start_sector> <num_sectors> <target_type> <target_args>[, ...]
+ *
+ * Return the remainder string after the table entry, i.e, after the comma which
+ * delimits the entry or NULL if reached the end of the string.
+ */
+static char __init *dm_parse_table_entry(struct dm_device *dev, char *str)
+{
+ const unsigned int n = dev->dmi.target_count - 1;
+ struct dm_target_spec *sp;
+ unsigned int i;
+ /* fields: */
+ char *field[4];
+ char *next;
+
+ field[0] = str;
+ /* Delimit first 3 fields that are separated by space */
+ for (i = 0; i < ARRAY_SIZE(field) - 1; i++) {
+ field[i + 1] = str_field_delimit(&field[i], ' ');
+ if (!field[i + 1])
+ return ERR_PTR(-EINVAL);
+ }
+ /* Delimit last field that can be terminated by comma */
+ next = str_field_delimit(&field[i], ',');
+
+ sp = kzalloc(sizeof(*sp), GFP_KERNEL);
+ if (!sp)
+ return ERR_PTR(-ENOMEM);
+ dev->table[n] = sp;
+
+ /* start_sector */
+ if (kstrtoull(field[0], 0, &sp->sector_start))
+ return ERR_PTR(-EINVAL);
+ /* num_sector */
+ if (kstrtoull(field[1], 0, &sp->length))
+ return ERR_PTR(-EINVAL);
+ /* target_type */
+ strscpy(sp->target_type, field[2], sizeof(sp->target_type));
+ if (dm_verify_target_type(sp->target_type)) {
+ DMERR("invalid type \"%s\"", sp->target_type);
+ return ERR_PTR(-EINVAL);
+ }
+ /* target_args */
+ dev->target_args_array[n] = kstrndup(field[3], GFP_KERNEL,
+ DM_MAX_STR_SIZE);
+ if (!dev->target_args_array[n])
+ return ERR_PTR(-ENOMEM);
+
+ return next;
+}
+
+/**
+ * dm_parse_table - parse "dm-mod.create=" table field
+ * @dev: device to store the parsed information.
+ * @str: the pointer to a string with the format:
+ * <table>[,<table>+]
+ */
+static int __init dm_parse_table(struct dm_device *dev, char *str)
+{
+ char *table_entry = str;
+
+ while (table_entry) {
+ DMDEBUG("parsing table \"%s\"", str);
+ if (++dev->dmi.target_count >= DM_MAX_TARGETS) {
+ DMERR("too many targets %u > %d",
+ dev->dmi.target_count, DM_MAX_TARGETS);
+ return -EINVAL;
+ }
+ table_entry = dm_parse_table_entry(dev, table_entry);
+ if (IS_ERR(table_entry)) {
+ DMERR("couldn't parse table");
+ return PTR_ERR(table_entry);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * dm_parse_device_entry - parse a device entry
+ * @dev: device to store the parsed information.
+ * @str: the pointer to a string with the format:
+ * name,uuid,minor,flags,table[; ...]
+ *
+ * Return the remainder string after the table entry, i.e, after the semi-colon
+ * which delimits the entry or NULL if reached the end of the string.
+ */
+static char __init *dm_parse_device_entry(struct dm_device *dev, char *str)
+{
+ /* There are 5 fields: name,uuid,minor,flags,table; */
+ char *field[5];
+ unsigned int i;
+ char *next;
+
+ field[0] = str;
+ /* Delimit first 4 fields that are separated by comma */
+ for (i = 0; i < ARRAY_SIZE(field) - 1; i++) {
+ field[i+1] = str_field_delimit(&field[i], ',');
+ if (!field[i+1])
+ return ERR_PTR(-EINVAL);
+ }
+ /* Delimit last field that can be delimited by semi-colon */
+ next = str_field_delimit(&field[i], ';');
+
+ /* name */
+ strscpy(dev->dmi.name, field[0], sizeof(dev->dmi.name));
+ /* uuid */
+ strscpy(dev->dmi.uuid, field[1], sizeof(dev->dmi.uuid));
+ /* minor */
+ if (strlen(field[2])) {
+ if (kstrtoull(field[2], 0, &dev->dmi.dev))
+ return ERR_PTR(-EINVAL);
+ dev->dmi.flags |= DM_PERSISTENT_DEV_FLAG;
+ }
+ /* flags */
+ if (!strcmp(field[3], "ro"))
+ dev->dmi.flags |= DM_READONLY_FLAG;
+ else if (strcmp(field[3], "rw"))
+ return ERR_PTR(-EINVAL);
+ /* table */
+ if (dm_parse_table(dev, field[4]))
+ return ERR_PTR(-EINVAL);
+
+ return next;
+}
+
+/**
+ * dm_parse_devices - parse "dm-mod.create=" argument
+ * @devices: list of struct dm_device to store the parsed information.
+ * @str: the pointer to a string with the format:
+ * <device>[;<device>+]
+ */
+static int __init dm_parse_devices(struct list_head *devices, char *str)
+{
+ unsigned long ndev = 0;
+ struct dm_device *dev;
+ char *device = str;
+
+ DMDEBUG("parsing \"%s\"", str);
+ while (device) {
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+ list_add_tail(&dev->list, devices);
+
+ if (++ndev >= DM_MAX_DEVICES) {
+ DMERR("too many targets %u > %d",
+ dev->dmi.target_count, DM_MAX_TARGETS);
+ return -EINVAL;
+ }
+
+ device = dm_parse_device_entry(dev, device);
+ if (IS_ERR(device)) {
+ DMERR("couldn't parse device");
+ return PTR_ERR(device);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * dm_init_init - parse "dm-mod.create=" argument and configure drivers
+ */
+static int __init dm_init_init(void)
+{
+ struct dm_device *dev;
+ LIST_HEAD(devices);
+ char *str;
+ int r;
+
+ if (!create)
+ return 0;
+
+ if (strlen(create) >= DM_MAX_STR_SIZE) {
+ DMERR("Argument is too big. Limit is %d\n", DM_MAX_STR_SIZE);
+ return -EINVAL;
+ }
+ str = kstrndup(create, GFP_KERNEL, DM_MAX_STR_SIZE);
+ if (!str)
+ return -ENOMEM;
+
+ r = dm_parse_devices(&devices, str);
+ if (r)
+ goto out;
+
+ DMINFO("waiting for all devices to be available before creating mapped devices\n");
+ wait_for_device_probe();
+
+ list_for_each_entry(dev, &devices, list) {
+ if (dm_early_create(&dev->dmi, dev->table,
+ dev->target_args_array))
+ break;
+ }
+out:
+ kfree(str);
+ dm_setup_cleanup(&devices);
+ return r;
+}
+
+late_initcall(dm_init_init);
+
+module_param(create, charp, 0);
+MODULE_PARM_DESC(create, "Create a mapped device when the module is loaded using this argument\n"
+ "This is mostly useful in early boot to allow mounting rootfs without requiring an initramfs\n"
+ "Format: <name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]\n"
+ "Table format: <start_sector> <num_sectors> <target_type> <target_args>\n"
+ "Examples:\n"
+ "dm-mod.create=\"lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0\"\n");
diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c
index f666778ad237..c740153b4e52 100644
--- a/drivers/md/dm-ioctl.c
+++ b/drivers/md/dm-ioctl.c
@@ -2018,3 +2018,106 @@ int dm_copy_name_and_uuid(struct mapped_device *md, char *name, char *uuid)

return r;
}
+
+
+/**
+ * dm_early_create - create a mapped device in early boot.
+ *
+ * @dmi: Contains main information of the device mapping to be created.
+ * @spec_array: array of pointers to struct dm_target_spec. Describes the
+ * mapping table of the device.
+ * @target_params_array: array of strings with the parameters to a specific
+ * target.
+ *
+ * Instead of having the struct dm_target_spec and the parameters for every
+ * target embedded at the end of struct dm_ioctl (as performed in a normal
+ * ioctl), pass them as arguments, so the caller doesn't need to serialize them.
+ * The size of the spec_array and target_params_array is given by
+ * @dmi->target_count.
+ * This function is supposed to be called in early boot, so locking mechanisms
+ * to protect against concurrent loads are not required.
+ */
+int __init dm_early_create(struct dm_ioctl *dmi,
+ struct dm_target_spec **spec_array,
+ char **target_params_array)
+{
+ int r, m = DM_ANY_MINOR;
+ struct dm_table *t, *old_map;
+ struct mapped_device *md;
+ unsigned int i;
+
+ if (!dmi->target_count)
+ return -EINVAL;
+
+ r = check_name(dmi->name);
+ if (r)
+ return r;
+
+ if (dmi->flags & DM_PERSISTENT_DEV_FLAG)
+ m = MINOR(huge_decode_dev(dmi->dev));
+
+ /* alloc dm device */
+ r = dm_create(m, &md);
+ if (r)
+ return r;
+
+ /* hash insert */
+ r = dm_hash_insert(dmi->name, *dmi->uuid ? dmi->uuid : NULL, md);
+ if (r)
+ goto err_destroy_dm;
+
+ /* alloc table */
+ r = dm_table_create(&t, get_mode(dmi), dmi->target_count, md);
+ if (r)
+ goto err_destroy_dm;
+
+ /* add targets */
+ for (i = 0; i < dmi->target_count; i++) {
+ r = dm_table_add_target(t, spec_array[i]->target_type,
+ (sector_t) spec_array[i]->sector_start,
+ (sector_t) spec_array[i]->length,
+ target_params_array[i]);
+ if (r) {
+ DMWARN("error adding target to table");
+ goto err_destroy_table;
+ }
+ }
+
+ /* finish table */
+ r = dm_table_complete(t);
+ if (r)
+ goto err_destroy_table;
+
+ md->type = dm_table_get_type(t);
+ /* setup md->queue to reflect md's type (may block) */
+ r = dm_setup_md_queue(md, t);
+ if (r) {
+ DMWARN("unable to set up device queue for new table.");
+ goto err_destroy_table;
+ }
+
+ /* Set new map */
+ dm_suspend(md, 0);
+ old_map = dm_swap_table(md, t);
+ if (IS_ERR(old_map)) {
+ r = PTR_ERR(old_map);
+ goto err_destroy_table;
+ }
+ set_disk_ro(dm_disk(md), !!(dmi->flags & DM_READONLY_FLAG));
+
+ /* resume device */
+ r = dm_resume(md);
+ if (r)
+ goto err_destroy_table;
+
+ DMINFO("%s (%s) is ready", md->disk->disk_name, dmi->name);
+ dm_put(md);
+ return 0;
+
+err_destroy_table:
+ dm_table_destroy(t);
+err_destroy_dm:
+ dm_put(md);
+ dm_destroy(md);
+ return r;
+}
diff --git a/include/linux/device-mapper.h b/include/linux/device-mapper.h
index e528baebad69..8fa618381e78 100644
--- a/include/linux/device-mapper.h
+++ b/include/linux/device-mapper.h
@@ -10,6 +10,7 @@

#include <linux/bio.h>
#include <linux/blkdev.h>
+#include <linux/dm-ioctl.h>
#include <linux/math64.h>
#include <linux/ratelimit.h>

@@ -431,6 +432,14 @@ void dm_remap_zone_report(struct dm_target *ti, sector_t start,
struct blk_zone *zones, unsigned int *nr_zones);
union map_info *dm_get_rq_mapinfo(struct request *rq);

+/*
+ * Device mapper functions to parse and create devices specified by the
+ * parameter "dm-mod.create="
+ */
+int __init dm_early_create(struct dm_ioctl *dmi,
+ struct dm_target_spec **spec_array,
+ char **target_params_array);
+
struct queue_limits *dm_get_queue_limits(struct mapped_device *md);

/*
--
2.20.1



2019-02-21 22:43:54

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v12] dm: add support to directly boot to a mapped device

On Thu, Feb 21, 2019 at 12:33 PM Helen Koike <[email protected]> wrote:
>
> Add a "create" module parameter, which allows device-mapper targets to be
> configured at boot time. This enables early use of dm targets in the boot
> process (as the root device or otherwise) without the need of an initramfs.
>
> The syntax used in the boot param is based on the concise format from the
> dmsetup tool to follow the rule of least surprise:
>
> sudo dmsetup table --concise /dev/mapper/lroot
>
> Which is:
> dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
>
> Where,
> <name> ::= The device name.
> <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
> <minor> ::= The device minor number | ""
> <flags> ::= "ro" | "rw"
> <table> ::= <start_sector> <num_sectors> <target_type> <target_args>
> <target_type> ::= "verity" | "linear" | ...
>
> For example, the following could be added in the boot parameters:
> dm-mod.create="lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0" root=/dev/dm-0
>
> Only the targets that were tested are allowed and the ones that doesn't
> change any block device when the dm is create as read-only. For example,
> mirror and cache targets are not allowed. The rationale behind this is
> that if the user makes a mistake, choosing the wrong device to be the
> mirror or the cache can corrupt data.
>
> The only targets allowed are:
> * crypt
> * delay
> * linear
> * snapshot-origin
> * striped
> * verity
>
> Co-developed-by: Will Drewry <[email protected]>
> Co-developed-by: Kees Cook <[email protected]>
> Co-developed-by: Enric Balletbo i Serra <[email protected]>
> Signed-off-by: Helen Koike <[email protected]>

Thanks! This appears to have everything Chrome OS needs. I've asked a
few other folks to look at it too.

Reviewed-by: Kees Cook <[email protected]>

--
Kees Cook

2019-06-03 23:04:26

by Stephen Boyd

[permalink] [raw]
Subject: Re: [PATCH v12] dm: add support to directly boot to a mapped device

Quoting Helen Koike (2019-02-21 12:33:34)
> Add a "create" module parameter, which allows device-mapper targets to be
> configured at boot time. This enables early use of dm targets in the boot
> process (as the root device or otherwise) without the need of an initramfs.
>
> The syntax used in the boot param is based on the concise format from the
> dmsetup tool to follow the rule of least surprise:
>
> sudo dmsetup table --concise /dev/mapper/lroot
>
> Which is:
> dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
>
> Where,
> <name> ::= The device name.
> <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
> <minor> ::= The device minor number | ""
> <flags> ::= "ro" | "rw"
> <table> ::= <start_sector> <num_sectors> <target_type> <target_args>
> <target_type> ::= "verity" | "linear" | ...
>
> For example, the following could be added in the boot parameters:
> dm-mod.create="lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0" root=/dev/dm-0
>
> Only the targets that were tested are allowed and the ones that doesn't
> change any block device when the dm is create as read-only. For example,
> mirror and cache targets are not allowed. The rationale behind this is
> that if the user makes a mistake, choosing the wrong device to be the
> mirror or the cache can corrupt data.
>
> The only targets allowed are:
> * crypt
> * delay
> * linear
> * snapshot-origin
> * striped
> * verity
>
> Co-developed-by: Will Drewry <[email protected]>
> Co-developed-by: Kees Cook <[email protected]>
> Co-developed-by: Enric Balletbo i Serra <[email protected]>
> Signed-off-by: Helen Koike <[email protected]>
>
> ---
>

I'm trying to boot a mainline linux kernel on a chromeos device with dm
verity and a USB stick but it's not working for me even with this patch.
I've had to hack around two problems:

1) rootwait isn't considered

2) verity doesn't seem to accept UUID for <hash_dev> or <dev>

For the first problem, it happens every boot for me because I'm trying
to boot off of a USB stick and it's behind a hub that takes a few
seconds to enumerate. If I hack up the code to call dm_init_init() after
the 'rootdelay' cmdline parameter is used then I can make this work. It
would be much nicer if the whole mechanism didn't use a late initcall
though. If it used a hook from prepare_namespace() and then looped
waiting for devices to create when rootwait was specified it would work.

The second problem is that in chromeos we have the bootloader fill out
the UUID of the kernel partition (%U) and then we have another parameter
that indicates the offset from that kernel partition to add to the
kernel partition (typically 1, i.e. PARTNROFF=1) to find the root
filesystem partition. The way verity seems to work here is that we need
to specify a path like /dev/sda3 or the major:minor number of the device
on the commandline to make this work. It would be better if we could add
in support for the PARTNROFF style that name_to_dev_t() handles so we
can specify the root partition like we're currently doing. I suspect we
should be able to add support for this into the device mapper layer so
that we can specify devices this way.

If it helps, an example commandline I've been using to test out a usb
stick is as follows:

dm-mod.create="vroot,,0,ro, 0 4710400 verity 0 8:19 8:19 4096 4096 588800 588800 sha1 9b0a223aedbf74b06442b0f05fbff33c55edd010 414b21fba60a1901e23aec373e994942e991d6762631e54a39bc42411f244bd2"

Also, the documentation (Documentation/device-mapper/dm-init.txt) says
we can use a way that doesn't specify so many arguments, but dm verity
complains about not enough arguments (10) when following the example:

vroot,,,ro,
0 1740800 verity 254:0 254:0 1740800 sha1
76e9be054b15884a9fa85973e9cb274c93afadb6
5b3549d54d6c7a3837b9b81ed72e49463a64c03680c47835bef94d768e5646fe;

So the documentation needs an update?

2019-06-04 17:40:57

by Helen Koike

[permalink] [raw]
Subject: Re: [PATCH v12] dm: add support to directly boot to a mapped device

Hi Stephen,

On 6/3/19 8:02 PM, Stephen Boyd wrote:
> Quoting Helen Koike (2019-02-21 12:33:34)
>> Add a "create" module parameter, which allows device-mapper targets to be
>> configured at boot time. This enables early use of dm targets in the boot
>> process (as the root device or otherwise) without the need of an initramfs.
>>
>> The syntax used in the boot param is based on the concise format from the
>> dmsetup tool to follow the rule of least surprise:
>>
>> sudo dmsetup table --concise /dev/mapper/lroot
>>
>> Which is:
>> dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
>>
>> Where,
>> <name> ::= The device name.
>> <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
>> <minor> ::= The device minor number | ""
>> <flags> ::= "ro" | "rw"
>> <table> ::= <start_sector> <num_sectors> <target_type> <target_args>
>> <target_type> ::= "verity" | "linear" | ...
>>
>> For example, the following could be added in the boot parameters:
>> dm-mod.create="lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0" root=/dev/dm-0
>>
>> Only the targets that were tested are allowed and the ones that doesn't
>> change any block device when the dm is create as read-only. For example,
>> mirror and cache targets are not allowed. The rationale behind this is
>> that if the user makes a mistake, choosing the wrong device to be the
>> mirror or the cache can corrupt data.
>>
>> The only targets allowed are:
>> * crypt
>> * delay
>> * linear
>> * snapshot-origin
>> * striped
>> * verity
>>
>> Co-developed-by: Will Drewry <[email protected]>
>> Co-developed-by: Kees Cook <[email protected]>
>> Co-developed-by: Enric Balletbo i Serra <[email protected]>
>> Signed-off-by: Helen Koike <[email protected]>
>>
>> ---
>>
>
> I'm trying to boot a mainline linux kernel on a chromeos device with dm
> verity and a USB stick but it's not working for me even with this patch.
> I've had to hack around two problems:
>
> 1) rootwait isn't considered
>
> 2) verity doesn't seem to accept UUID for <hash_dev> or <dev>
>
> For the first problem, it happens every boot for me because I'm trying
> to boot off of a USB stick and it's behind a hub that takes a few
> seconds to enumerate. If I hack up the code to call dm_init_init() after
> the 'rootdelay' cmdline parameter is used then I can make this work. It
> would be much nicer if the whole mechanism didn't use a late initcall
> though. If it used a hook from prepare_namespace() and then looped
> waiting for devices to create when rootwait was specified it would work.

The patch was implemented with late initcall partially to be contained
in drivers/md/*, but to support rootwait, adding a hook from
prepare_namespace seems the way to go indeed.

>
> The second problem is that in chromeos we have the bootloader fill out
> the UUID of the kernel partition (%U) and then we have another parameter
> that indicates the offset from that kernel partition to add to the
> kernel partition (typically 1, i.e. PARTNROFF=1) to find the root
> filesystem partition. The way verity seems to work here is that we need
> to specify a path like /dev/sda3 or the major:minor number of the device
> on the commandline to make this work. It would be better if we could add
> in support for the PARTNROFF style that name_to_dev_t() handles so we
> can specify the root partition like we're currently doing. I suspect we
> should be able to add support for this into the device mapper layer so
> that we can specify devices this way.

hmm, I didn't test this yet but at least from what I can see in the
code, verity_ctr() calls dm_get_device() that ends up calling
name_to_dev_t() which should take care of PARTNROFF, this requires a bit
more investigation.

>
> If it helps, an example commandline I've been using to test out a usb
> stick is as follows:
>
> dm-mod.create="vroot,,0,ro, 0 4710400 verity 0 8:19 8:19 4096 4096 588800 588800 sha1 9b0a223aedbf74b06442b0f05fbff33c55edd010 414b21fba60a1901e23aec373e994942e991d6762631e54a39bc42411f244bd2"

Thanks

>
> Also, the documentation (Documentation/device-mapper/dm-init.txt) says
> we can use a way that doesn't specify so many arguments, but dm verity
> complains about not enough arguments (10) when following the example:
>
> vroot,,,ro,
> 0 1740800 verity 254:0 254:0 1740800 sha1
> 76e9be054b15884a9fa85973e9cb274c93afadb6
> 5b3549d54d6c7a3837b9b81ed72e49463a64c03680c47835bef94d768e5646fe;
>
> So the documentation needs an update?
>

Ack, I'll update this.

Thanks
Helen

2019-06-04 19:23:42

by Ezequiel Garcia

[permalink] [raw]
Subject: Re: [PATCH v12] dm: add support to directly boot to a mapped device

Hi Stephen,

On Tue, 2019-06-04 at 14:38 -0300, Helen Koike wrote:
> Hi Stephen,
>
> On 6/3/19 8:02 PM, Stephen Boyd wrote:
> > Quoting Helen Koike (2019-02-21 12:33:34)
> > > Add a "create" module parameter, which allows device-mapper targets to be
> > > configured at boot time. This enables early use of dm targets in the boot
> > > process (as the root device or otherwise) without the need of an initramfs.
> > >
> > > The syntax used in the boot param is based on the concise format from the
> > > dmsetup tool to follow the rule of least surprise:
> > >
> > > sudo dmsetup table --concise /dev/mapper/lroot
> > >
> > > Which is:
> > > dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
> > >
> > > Where,
> > > <name> ::= The device name.
> > > <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
> > > <minor> ::= The device minor number | ""
> > > <flags> ::= "ro" | "rw"
> > > <table> ::= <start_sector> <num_sectors> <target_type> <target_args>
> > > <target_type> ::= "verity" | "linear" | ...
> > >
> > > For example, the following could be added in the boot parameters:
> > > dm-mod.create="lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0" root=/dev/dm-0
> > >
> > > Only the targets that were tested are allowed and the ones that doesn't
> > > change any block device when the dm is create as read-only. For example,
> > > mirror and cache targets are not allowed. The rationale behind this is
> > > that if the user makes a mistake, choosing the wrong device to be the
> > > mirror or the cache can corrupt data.
> > >
> > > The only targets allowed are:
> > > * crypt
> > > * delay
> > > * linear
> > > * snapshot-origin
> > > * striped
> > > * verity
> > >
> > > Co-developed-by: Will Drewry <[email protected]>
> > > Co-developed-by: Kees Cook <[email protected]>
> > > Co-developed-by: Enric Balletbo i Serra <[email protected]>
> > > Signed-off-by: Helen Koike <[email protected]>
> > >
> > > ---
> > >
> >
> > I'm trying to boot a mainline linux kernel on a chromeos device with dm
> > verity and a USB stick but it's not working for me even with this patch.
> > I've had to hack around two problems:
> >
> > 1) rootwait isn't considered
> >
> > 2) verity doesn't seem to accept UUID for <hash_dev> or <dev>
> >
> > For the first problem, it happens every boot for me because I'm trying
> > to boot off of a USB stick and it's behind a hub that takes a few
> > seconds to enumerate. If I hack up the code to call dm_init_init() after
> > the 'rootdelay' cmdline parameter is used then I can make this work. It
> > would be much nicer if the whole mechanism didn't use a late initcall
> > though. If it used a hook from prepare_namespace() and then looped
> > waiting for devices to create when rootwait was specified it would work.
>
> The patch was implemented with late initcall partially to be contained
> in drivers/md/*, but to support rootwait, adding a hook from
> prepare_namespace seems the way to go indeed.
>

Thanks for bringing this up.

Helen and I have been looking at this code, and we think it's possible
to move things around and add some helpers, so we can implement rootwait
behavior, without actually cluttering init/do_mounts.c.

And along the way, we might get the chance to clean-up this code even
further.

Regards,
Eze

2019-06-04 19:37:31

by Stephen Boyd

[permalink] [raw]
Subject: Re: [PATCH v12] dm: add support to directly boot to a mapped device

Quoting Helen Koike (2019-06-04 10:38:59)
> On 6/3/19 8:02 PM, Stephen Boyd wrote:
> >
> > I'm trying to boot a mainline linux kernel on a chromeos device with dm
> > verity and a USB stick but it's not working for me even with this patch.
> > I've had to hack around two problems:
> >
> > 1) rootwait isn't considered
> >
> > 2) verity doesn't seem to accept UUID for <hash_dev> or <dev>
> >
> > For the first problem, it happens every boot for me because I'm trying
> > to boot off of a USB stick and it's behind a hub that takes a few
> > seconds to enumerate. If I hack up the code to call dm_init_init() after
> > the 'rootdelay' cmdline parameter is used then I can make this work. It
> > would be much nicer if the whole mechanism didn't use a late initcall
> > though. If it used a hook from prepare_namespace() and then looped
> > waiting for devices to create when rootwait was specified it would work.
>
> The patch was implemented with late initcall partially to be contained
> in drivers/md/*, but to support rootwait, adding a hook from
> prepare_namespace seems the way to go indeed.

Alright, great.

>
> >
> > The second problem is that in chromeos we have the bootloader fill out
> > the UUID of the kernel partition (%U) and then we have another parameter
> > that indicates the offset from that kernel partition to add to the
> > kernel partition (typically 1, i.e. PARTNROFF=1) to find the root
> > filesystem partition. The way verity seems to work here is that we need
> > to specify a path like /dev/sda3 or the major:minor number of the device
> > on the commandline to make this work. It would be better if we could add
> > in support for the PARTNROFF style that name_to_dev_t() handles so we
> > can specify the root partition like we're currently doing. I suspect we
> > should be able to add support for this into the device mapper layer so
> > that we can specify devices this way.
>
> hmm, I didn't test this yet but at least from what I can see in the
> code, verity_ctr() calls dm_get_device() that ends up calling
> name_to_dev_t() which should take care of PARTNROFF, this requires a bit
> more investigation.
>

Ok, thanks for pointing that out. Sorry I totally missed this codepath
and I should have investigated more. It works for me with the PARTNROFF
syntax that we've been using, so the problem is the rootwait stuff.

2019-06-05 08:38:17

by Zdenek Kabelac

[permalink] [raw]
Subject: Re: [dm-devel] [PATCH v12] dm: add support to directly boot to a mapped device

Dne 04. 06. 19 v 21:35 Stephen Boyd napsal(a):
> Quoting Helen Koike (2019-06-04 10:38:59)
>> On 6/3/19 8:02 PM, Stephen Boyd wrote:
>>>
>>> I'm trying to boot a mainline linux kernel on a chromeos device with dm
>>> verity and a USB stick but it's not working for me even with this patch.
>>> I've had to hack around two problems:
>>>
>>> 1) rootwait isn't considered
>>>
>>> 2) verity doesn't seem to accept UUID for <hash_dev> or <dev>
>>>
>>> For the first problem, it happens every boot for me because I'm trying
>>> to boot off of a USB stick and it's behind a hub that takes a few
>>> seconds to enumerate. If I hack up the code to call dm_init_init() after
>>> the 'rootdelay' cmdline parameter is used then I can make this work. It
>>> would be much nicer if the whole mechanism didn't use a late initcall
>>> though. If it used a hook from prepare_namespace() and then looped
>>> waiting for devices to create when rootwait was specified it would work.
>>
>> The patch was implemented with late initcall partially to be contained
>> in drivers/md/*, but to support rootwait, adding a hook from
>> prepare_namespace seems the way to go indeed.
>
> Alright, great.
>
>>
>>>
>>> The second problem is that in chromeos we have the bootloader fill out
>>> the UUID of the kernel partition (%U) and then we have another parameter
>>> that indicates the offset from that kernel partition to add to the
>>> kernel partition (typically 1, i.e. PARTNROFF=1) to find the root
>>> filesystem partition. The way verity seems to work here is that we need
>>> to specify a path like /dev/sda3 or the major:minor number of the device


Hi

As not a direct dm developer - isn't this going a bit too far ? -
This way you will need to soon move halve of the userspace functionality into
kernel space.

IMHO would be way more progressive to start using initramdisk and let
userspace resolve all the issue.

Clearly once you start to wait for some 'devices' to appear - then you will
need to way for CORRECT device as well - since sda,sdb... goes in random
order, so you would need to parse disk headers and so on.

What you are effectively doing at this moment is you are shifting/ballooning
'ramdisk' code into kernel image - just to be named a kernel.

So why it is so big deal to start to use ramdisk on ChromeOS?
That would have solved most of problems you have or you will have instantly.

Regards

Zdenek