summaryrefslogtreecommitdiff
path: root/src/shared
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2016-12-07 18:28:13 +0100
committerLennart Poettering <lennart@poettering.net>2016-12-07 18:38:41 +0100
commit4623e8e6ac7c7a36b16ec2dc9ad8507fd820c9fa (patch)
tree38e370801554adb34fdcfd72ee79e3b915991334 /src/shared
parent4827ab4854d3107d05b65194ac72729955fb3585 (diff)
nspawn/dissect: automatically discover dm-verity verity partitions
This adds support for discovering and making use of properly tagged dm-verity data integrity partitions. This extends both systemd-nspawn and systemd-dissect with a new --root-hash= switch that takes the root hash to use for the root partition, and is otherwise fully automatic. Verity partitions are discovered automatically by GPT table type UUIDs, as listed in https://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ (which I updated prior to this change, to include new UUIDs for this purpose. mkosi with https://github.com/systemd/mkosi/pull/39 applied may generate images that carry the necessary integrity data. With that PR and this commit, the following simply lines suffice to boot up an integrity-protected container image: ``` # mkdir test # cd test # mkosi --verity # systemd-nspawn -i ./image.raw -bn ``` Note that mkosi writes the image file to "image.raw" next to a a file "image.roothash" that contains the root hash. systemd-nspawn will look for that file and use it if it exists, in case --root-hash= is not specified explicitly.
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/dissect-image.c245
-rw-r--r--src/shared/dissect-image.h20
-rw-r--r--src/shared/gpt.h17
3 files changed, 251 insertions, 31 deletions
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index bc4e45be6e..257af78781 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -84,9 +84,10 @@ not_found:
#endif
}
-int dissect_image(int fd, DissectedImage **ret) {
+int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectedImage **ret) {
#ifdef HAVE_BLKID
+ sd_id128_t root_uuid = SD_ID128_NULL, verity_uuid = SD_ID128_NULL;
_cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
bool is_gpt, is_mbr, generic_rw, multiple_generic = false;
_cleanup_udev_device_unref_ struct udev_device *d = NULL;
@@ -103,10 +104,29 @@ int dissect_image(int fd, DissectedImage **ret) {
assert(fd >= 0);
assert(ret);
+ assert(root_hash || root_hash_size == 0);
/* Probes a disk image, and returns information about what it found in *ret.
*
- * Returns -ENOPKG if no suitable partition table or file system could be found. */
+ * Returns -ENOPKG if no suitable partition table or file system could be found.
+ * Returns -EADDRNOTAVAIL if a root hash was specified but no matching root/verity partitions found. */
+
+ if (root_hash) {
+ /* If a root hash is supplied, then we use the root partition that has a UUID that match the first
+ * 128bit of the root hash. And we use the verity partition that has a UUID that match the final
+ * 128bit. */
+
+ if (root_hash_size < sizeof(sd_id128_t))
+ return -EINVAL;
+
+ memcpy(&root_uuid, root_hash, sizeof(sd_id128_t));
+ memcpy(&verity_uuid, (const uint8_t*) root_hash + root_hash_size - sizeof(sd_id128_t), sizeof(sd_id128_t));
+
+ if (sd_id128_is_null(root_uuid))
+ return -EINVAL;
+ if (sd_id128_is_null(verity_uuid))
+ return -EINVAL;
+ }
if (fstat(fd, &st) < 0)
return -errno;
@@ -313,17 +333,22 @@ int dissect_image(int fd, DissectedImage **ret) {
if (is_gpt) {
int designator = _PARTITION_DESIGNATOR_INVALID, architecture = _ARCHITECTURE_INVALID;
- const char *stype, *fstype = NULL;
- sd_id128_t type_id;
+ const char *stype, *sid, *fstype = NULL;
+ sd_id128_t type_id, id;
bool rw = true;
if (flags & GPT_FLAG_NO_AUTO)
continue;
+ sid = blkid_partition_get_uuid(pp);
+ if (!sid)
+ continue;
+ if (sd_id128_from_string(sid, &id) < 0)
+ continue;
+
stype = blkid_partition_get_type_string(pp);
if (!stype)
continue;
-
if (sd_id128_from_string(stype, &type_id) < 0)
continue;
@@ -339,17 +364,57 @@ int dissect_image(int fd, DissectedImage **ret) {
}
#ifdef GPT_ROOT_NATIVE
else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
+
+ /* If a root ID is specified, ignore everything but the root id */
+ if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id))
+ continue;
+
designator = PARTITION_ROOT;
architecture = native_architecture();
rw = !(flags & GPT_FLAG_READ_ONLY);
}
+#ifdef GPT_ROOT_NATIVE_VERITY
+ else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE_VERITY)) {
+
+ m->can_verity = true;
+
+ /* Ignore verity unless a root hash is specified */
+ if (sd_id128_is_null(verity_uuid) || !sd_id128_equal(verity_uuid, id))
+ continue;
+
+ designator = PARTITION_ROOT_VERITY;
+ fstype = "DM_verity_hash";
+ architecture = native_architecture();
+ rw = false;
+ }
+#endif
#endif
#ifdef GPT_ROOT_SECONDARY
else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) {
+
+ /* If a root ID is specified, ignore everything but the root id */
+ if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id))
+ continue;
+
designator = PARTITION_ROOT_SECONDARY;
architecture = SECONDARY_ARCHITECTURE;
rw = !(flags & GPT_FLAG_READ_ONLY);
}
+#ifdef GPT_ROOT_SECONDARY_VERITY
+ else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY_VERITY)) {
+
+ m->can_verity = true;
+
+ /* Ignore verity unless root has is specified */
+ if (sd_id128_is_null(verity_uuid) || !sd_id128_equal(verity_uuid, id))
+ continue;
+
+ designator = PARTITION_ROOT_SECONDARY_VERITY;
+ fstype = "DM_verity_hash";
+ architecture = SECONDARY_ARCHITECTURE;
+ rw = false;
+ }
+#endif
#endif
else if (sd_id128_equal(type_id, GPT_SWAP)) {
designator = PARTITION_SWAP;
@@ -420,10 +485,17 @@ int dissect_image(int fd, DissectedImage **ret) {
/* No root partition found? Then let's see if ther's one for the secondary architecture. And if not
* either, then check if there's a single generic one, and use that. */
+ if (m->partitions[PARTITION_ROOT_VERITY].found)
+ return -ENXIO;
+
if (m->partitions[PARTITION_ROOT_SECONDARY].found) {
m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY];
zero(m->partitions[PARTITION_ROOT_SECONDARY]);
- } else if (generic_node) {
+
+ m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY];
+ zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]);
+
+ } else if (generic_node && !root_hash) {
if (multiple_generic)
return -ENOTUNIQ;
@@ -441,6 +513,24 @@ int dissect_image(int fd, DissectedImage **ret) {
return -ENXIO;
}
+ assert(m->partitions[PARTITION_ROOT].found);
+
+ if (root_hash) {
+ if (!m->partitions[PARTITION_ROOT_VERITY].found)
+ return -EADDRNOTAVAIL;
+
+ /* If we found the primary root with the hash, then we definitely want to suppress any secondary root
+ * (which would be weird, after all the root hash should only be assigned to one pair of
+ * partitions... */
+ m->partitions[PARTITION_ROOT_SECONDARY].found = false;
+ m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found = false;
+
+ /* If we found a verity setup, then the root partition is necessarily read-only. */
+ m->partitions[PARTITION_ROOT].rw = false;
+
+ m->verity = true;
+ }
+
blkid_free_probe(b);
b = NULL;
@@ -637,6 +727,40 @@ DecryptedImage* decrypted_image_unref(DecryptedImage* d) {
}
#ifdef HAVE_LIBCRYPTSETUP
+
+static int make_dm_name_and_node(const void *original_node, const char *suffix, char **ret_name, char **ret_node) {
+ _cleanup_free_ char *name = NULL, *node = NULL;
+ const char *base;
+
+ assert(original_node);
+ assert(suffix);
+ assert(ret_name);
+ assert(ret_node);
+
+ base = strrchr(original_node, '/');
+ if (!base)
+ return -EINVAL;
+ base++;
+ if (isempty(base))
+ return -EINVAL;
+
+ name = strjoin(base, suffix);
+ if (!name)
+ return -ENOMEM;
+ if (!filename_is_valid(name))
+ return -EINVAL;
+
+ node = strjoin(crypt_get_dir(), "/", name);
+ if (!node)
+ return -ENOMEM;
+
+ *ret_name = name;
+ *ret_node = node;
+
+ name = node = NULL;
+ return 0;
+}
+
static int decrypt_partition(
DissectedPartition *m,
const char *passphrase,
@@ -645,7 +769,6 @@ static int decrypt_partition(
_cleanup_free_ char *node = NULL, *name = NULL;
struct crypt_device *cd;
- const char *suffix;
int r;
assert(m);
@@ -657,22 +780,9 @@ static int decrypt_partition(
if (!streq(m->fstype, "crypto_LUKS"))
return 0;
- suffix = strrchr(m->node, '/');
- if (!suffix)
- return -EINVAL;
- suffix++;
- if (isempty(suffix))
- return -EINVAL;
-
- name = strjoin(suffix, "-decrypted");
- if (!name)
- return -ENOMEM;
- if (!filename_is_valid(name))
- return -EINVAL;
-
- node = strjoin(crypt_get_dir(), "/", name);
- if (!node)
- return -ENOMEM;
+ r = make_dm_name_and_node(m->node, "-decrypted", &name, &node);
+ if (r < 0)
+ return r;
if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1))
return -ENOMEM;
@@ -710,11 +820,78 @@ fail:
crypt_free(cd);
return r;
}
+
+static int verity_partition(
+ DissectedPartition *m,
+ DissectedPartition *v,
+ const void *root_hash,
+ size_t root_hash_size,
+ DissectImageFlags flags,
+ DecryptedImage *d) {
+
+ _cleanup_free_ char *node = NULL, *name = NULL;
+ struct crypt_device *cd;
+ int r;
+
+ assert(m);
+ assert(v);
+
+ if (!root_hash)
+ return 0;
+
+ if (!m->found || !m->node || !m->fstype)
+ return 0;
+ if (!v->found || !v->node || !v->fstype)
+ return 0;
+
+ if (!streq(v->fstype, "DM_verity_hash"))
+ return 0;
+
+ r = make_dm_name_and_node(m->node, "-verity", &name, &node);
+ if (r < 0)
+ return r;
+
+ if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1))
+ return -ENOMEM;
+
+ r = crypt_init(&cd, v->node);
+ if (r < 0)
+ return r;
+
+ r = crypt_load(cd, CRYPT_VERITY, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = crypt_set_data_device(cd, m->node);
+ if (r < 0)
+ goto fail;
+
+ r = crypt_activate_by_volume_key(cd, name, root_hash, root_hash_size, CRYPT_ACTIVATE_READONLY);
+ if (r < 0)
+ goto fail;
+
+ d->decrypted[d->n_decrypted].name = name;
+ name = NULL;
+
+ d->decrypted[d->n_decrypted].device = cd;
+ d->n_decrypted++;
+
+ m->decrypted_node = node;
+ node = NULL;
+
+ return 0;
+
+fail:
+ crypt_free(cd);
+ return r;
+}
#endif
int dissected_image_decrypt(
DissectedImage *m,
const char *passphrase,
+ const void *root_hash,
+ size_t root_hash_size,
DissectImageFlags flags,
DecryptedImage **ret) {
@@ -725,6 +902,7 @@ int dissected_image_decrypt(
#endif
assert(m);
+ assert(root_hash || root_hash_size == 0);
/* Returns:
*
@@ -734,13 +912,16 @@ int dissected_image_decrypt(
* -EKEYREJECTED → Passed key was not correct
*/
- if (!m->encrypted) {
+ if (root_hash && root_hash_size < sizeof(sd_id128_t))
+ return -EINVAL;
+
+ if (!m->encrypted && !m->verity) {
*ret = NULL;
return 0;
}
#ifdef HAVE_LIBCRYPTSETUP
- if (!passphrase)
+ if (m->encrypted && !passphrase)
return -ENOKEY;
d = new0(DecryptedImage, 1);
@@ -749,6 +930,7 @@ int dissected_image_decrypt(
for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
DissectedPartition *p = m->partitions + i;
+ int k;
if (!p->found)
continue;
@@ -757,6 +939,13 @@ int dissected_image_decrypt(
if (r < 0)
return r;
+ k = PARTITION_VERITY_OF(i);
+ if (k >= 0) {
+ r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, flags, d);
+ if (r < 0)
+ return r;
+ }
+
if (!p->decrypted_fstype && p->decrypted_node) {
r = probe_filesystem(p->decrypted_node, &p->decrypted_fstype);
if (r < 0)
@@ -776,6 +965,8 @@ int dissected_image_decrypt(
int dissected_image_decrypt_interactively(
DissectedImage *m,
const char *passphrase,
+ const void *root_hash,
+ size_t root_hash_size,
DissectImageFlags flags,
DecryptedImage **ret) {
@@ -786,7 +977,7 @@ int dissected_image_decrypt_interactively(
n--;
for (;;) {
- r = dissected_image_decrypt(m, passphrase, flags, ret);
+ r = dissected_image_decrypt(m, passphrase, root_hash, root_hash_size, flags, ret);
if (r >= 0)
return r;
if (r == -EKEYREJECTED)
@@ -880,6 +1071,8 @@ static const char *const partition_designator_table[] = {
[PARTITION_SRV] = "srv",
[PARTITION_ESP] = "esp",
[PARTITION_SWAP] = "swap",
+ [PARTITION_ROOT_VERITY] = "root-verity",
+ [PARTITION_ROOT_SECONDARY_VERITY] = "root-secondary-verity",
};
DEFINE_STRING_TABLE_LOOKUP(partition_designator, int);
diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h
index 69484eb32c..902c8d4a37 100644
--- a/src/shared/dissect-image.h
+++ b/src/shared/dissect-image.h
@@ -45,10 +45,20 @@ enum {
PARTITION_SRV,
PARTITION_ESP,
PARTITION_SWAP,
+ PARTITION_ROOT_VERITY, /* verity data for the PARTITION_ROOT partition */
+ PARTITION_ROOT_SECONDARY_VERITY, /* verity data for the PARTITION_ROOT_SECONDARY partition */
_PARTITION_DESIGNATOR_MAX,
_PARTITION_DESIGNATOR_INVALID = -1
};
+static inline int PARTITION_VERITY_OF(int p) {
+ if (p == PARTITION_ROOT)
+ return PARTITION_ROOT_VERITY;
+ if (p == PARTITION_ROOT_SECONDARY)
+ return PARTITION_ROOT_SECONDARY_VERITY;
+ return _PARTITION_DESIGNATOR_INVALID;
+}
+
typedef enum DissectImageFlags {
DISSECT_IMAGE_READ_ONLY = 1,
DISSECT_IMAGE_DISCARD_ON_LOOP = 2, /* Turn on "discard" if on loop device and file system supports it */
@@ -57,17 +67,19 @@ typedef enum DissectImageFlags {
} DissectImageFlags;
struct DissectedImage {
- bool encrypted;
+ bool encrypted:1;
+ bool verity:1; /* verity available and usable */
+ bool can_verity:1; /* verity available, but not necessarily used */
DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX];
};
-int dissect_image(int fd, DissectedImage **ret);
+int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectedImage **ret);
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
-int dissected_image_decrypt(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret);
-int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret);
+int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret);
+int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_mount(DissectedImage *m, const char *dest, DissectImageFlags flags);
DecryptedImage* decrypted_image_unref(DecryptedImage *p);
diff --git a/src/shared/gpt.h b/src/shared/gpt.h
index 55b41bbcd8..13d80d611c 100644
--- a/src/shared/gpt.h
+++ b/src/shared/gpt.h
@@ -32,28 +32,43 @@
#define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3)
#define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae)
#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97)
-
#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b)
#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f)
#define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15)
#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
+/* Verity partitions for the root partitions above (we only define them for the root partitions, because only they are
+ * are commonly read-only and hence suitable for verity). */
+#define GPT_ROOT_X86_VERITY SD_ID128_MAKE(d1,3c,5d,3b,b5,d1,42,2a,b2,9f,94,54,fd,c8,9d,76)
+#define GPT_ROOT_X86_64_VERITY SD_ID128_MAKE(2c,73,57,ed,eb,d2,46,d9,ae,c1,23,d4,37,ec,2b,f5)
+#define GPT_ROOT_ARM_VERITY SD_ID128_MAKE(73,86,cd,f2,20,3c,47,a9,a4,98,f2,ec,ce,45,a2,d6)
+#define GPT_ROOT_ARM_64_VERITY SD_ID128_MAKE(df,33,00,ce,d6,9f,4c,92,97,8c,9b,fb,0f,38,d8,20)
+#define GPT_ROOT_IA64_VERITY SD_ID128_MAKE(86,ed,10,d5,b6,07,45,bb,89,57,d3,50,f2,3d,05,71)
+
+
#if defined(__x86_64__)
# define GPT_ROOT_NATIVE GPT_ROOT_X86_64
# define GPT_ROOT_SECONDARY GPT_ROOT_X86
+# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_X86_64_VERITY
+# define GPT_ROOT_SECONDARY_VERITY GPT_ROOT_X86_VERITY
#elif defined(__i386__)
# define GPT_ROOT_NATIVE GPT_ROOT_X86
+# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_X86_VERITY
#endif
#if defined(__ia64__)
# define GPT_ROOT_NATIVE GPT_ROOT_IA64
+# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_IA64_VERITY
#endif
#if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN)
# define GPT_ROOT_NATIVE GPT_ROOT_ARM_64
# define GPT_ROOT_SECONDARY GPT_ROOT_ARM
+# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_ARM_64_VERITY
+# define GPT_ROOT_SECONDARY_VERITY GPT_ROOT_ARM_VERITY
#elif defined(__arm__) && (__BYTE_ORDER != __BIG_ENDIAN)
# define GPT_ROOT_NATIVE GPT_ROOT_ARM
+# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_ARM_VERITY
#endif
/* Flags we recognize on the root, swap, home and srv partitions when