]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dissect: check that roothash in signature matches before selecting partition
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 10 Nov 2025 02:01:57 +0000 (02:01 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 11 Nov 2025 17:43:24 +0000 (17:43 +0000)
If there are multiple verity sig partitions (e.g.: sysupdate and A/B
scheme), dissection will simply pick the last sig partition it encounters,
as no checks are done on the content (like for the usr/root and verity data).

Check that the JSON content matches the requested roothash, if any.

Before:

sda: /usr/lib/udev/rules.d/90-image-dissect.rules:34 IMPORT{builtin}="dissect_image probe": Importing properties from results of builtin command "dissect_image probe".
Dissecting esp partition with label esp and UUID b80070bd-ea4f-49ea-94ab-41a4e4125f80.
Dissecting usr-verity-sig partition with label ParticleOS_27.178_verity_sig and UUID a6d47959-39f7-4686-99b0-660b301d1488.
Dissecting usr-verity partition with label ParticleOS_27.178_verity and UUID d7acad57-995d-297d-bf6c-a58821dcd28a.
Dissecting usr partition with label ParticleOS_27.178 and UUID f5b6aff5-945d-946e-faf4-d482c07f9968.
Dissecting usr-verity-sig partition with label ParticleOS_118.26_verity_sig and UUID c9151ec9-3264-434a-8f42-7b125432d676.
Dissecting usr-verity partition with label ParticleOS_118.26_verity and UUID 88fa8c85-8161-ea32-bf4a-fc8df18d27ae.
Partition UUID '88fa8c85-8161-ea32-bf4a-fc8df18d27ae' does not match expected UUID 'f5b6aff5-945d-946e-faf4-d482c07f9968' derived from usr verity hash, ignoring.
Dissecting usr partition with label ParticleOS_118.26 and UUID 52df1859-e144-348d-2cb1-8d6440254719.
Partition UUID '52df1859-e144-348d-2cb1-8d6440254719' does not match expected UUID 'f5b6aff5-945d-946e-faf4-d482c07f9968' derived from usr verity hash, ignoring.
Dissecting swap partition with label ParticleOS-swap and UUID 7fe77f77-32fb-4957-8c1e-6c04bd2e435f.
Dissecting root partition with label ParticleOS-root and UUID a5c89fc4-e92c-4e83-913f-8c866b94592e.
Dissecting home partition with label ParticleOS-home and UUID 25885d07-baa2-4992-b6aa-56813aa70cef.
Found for designator root: encrypted+unprotected+unused.
Found for designator usr: verity+signed+encrypted+unprotected+unused.
Found for designator home: encrypted+unprotected+unused.
Found for designator srv: absent.
Found for designator esp: encrypted+unprotected+unused.
Found for designator xbootldr: absent.
Found for designator swap: encrypted+unprotected+unused.
Found for designator root-verity: absent.
Found for designator usr-verity: encrypted+unprotected+unused.
Found for designator root-verity-sig: absent.
Found for designator usr-verity-sig: encrypted+unprotected+unused.
Found for designator tmp: absent.
Found for designator var: absent.
Probed fstype 'btrfs' on partition /dev/sda9.
Probed fstype 'erofs' on partition /dev/sda4.
Probed fstype 'btrfs' on partition /dev/sda10.
Probed fstype 'swap' on partition /dev/sda8.
Root hash in signature JSON data (52df1859e144348d2cb18d644025471988fa8c858161ea32bf4afc8df18d27ae) doesn't match configured hash (f5b6aff5945d946efaf4d482c07f9968d7acad57995d297dbf6ca58821dcd28a).
sda: Failed to load verity signature data from image: Invalid argument

After:

Dissecting usr-verity-sig partition with label ParticleOS_27.178_verity_sig and UUID a6d47959-39f7-4686-99b0-660b301d1488.
Dissecting usr-verity partition with label ParticleOS_27.178_verity and UUID d7acad57-995d-297d-bf6c-a58821dcd28a.
Dissecting usr partition with label ParticleOS_27.178 and UUID f5b6aff5-945d-946e-faf4-d482c07f9968.
Dissecting usr-verity-sig partition with label ParticleOS_118.26_verity_sig and UUID c9151ec9-3264-434a-8f42-7b125432d676.
Root hash in signature JSON data (52df1859e144348d2cb18d644025471988fa8c858161ea32bf4afc8df18d27ae) doesn't match configured hash (f5b6aff5945d946efaf4d482c07f9968d7acad57995d297dbf6ca58821dcd28a).
Dissecting usr-verity partition with label ParticleOS_118.26_verity and UUID 88fa8c85-8161-ea32-bf4a-fc8df18d27ae.
Partition UUID '88fa8c85-8161-ea32-bf4a-fc8df18d27ae' does not match expected UUID 'f5b6aff5-945d-946e-faf4-d482c07f9968' derived from usr verity hash, ignoring.
Dissecting usr partition with label ParticleOS_118.26 and UUID 52df1859-e144-348d-2cb1-8d6440254719.
Partition UUID '52df1859-e144-348d-2cb1-8d6440254719' does not match expected UUID 'f5b6aff5-945d-946e-faf4-d482c07f9968' derived from usr verity hash, ignoring.
<...>
  ID_DISSECT_PART2_DESIGNATOR=usr-verity-sig
  ID_DISSECT_PART3_ARCHITECTURE=x86-64
  ID_DISSECT_PART3_DESIGNATOR=usr-verity
  ID_DISSECT_PART4_ARCHITECTURE=x86-64
  ID_DISSECT_PART4_DESIGNATOR=usr
  ID_DISSECT_PART4_HAS_VERITY=1
  ID_DISSECT_PART4_HAS_VERITY_SIG=1
  ID_DISSECT_PART4_ROOTHASH=f5b6aff5945d946efaf4d482c07f9968d7acad57995d297dbf6ca58821dcd28a
  ID_DISSECT_PART4_ROOTHASH_SIG=<...>
  ID_DISSECT_PART4_VERITY_DEVICE=/dev/disk/by-diskseq/9-part3
  ID_DISSECT_PART4_VERITY_SIG_DEVICE=/dev/disk/by-diskseq/9-part2

Fixes https://github.com/systemd/systemd/issues/39655

src/shared/dissect-image.c

index 5a27620244a28a76171b09c17a1168634f0f86b9..1162982f71cfd5dc53ad2b95f55f9d2b89f3b8ab 100644 (file)
@@ -568,6 +568,83 @@ static void dissected_partition_done(DissectedPartition *p) {
         *p = DISSECTED_PARTITION_NULL;
 }
 
+static int acquire_sig_for_roothash(
+                int fd,
+                uint64_t partition_offset,
+                uint64_t partition_size,
+                void **ret_root_hash,
+                size_t *ret_root_hash_size,
+                void **ret_root_hash_sig,
+                size_t *ret_root_hash_sig_size) {
+
+        int r;
+
+        assert(fd >= 0);
+        assert(!!ret_root_hash == !!ret_root_hash_size);
+        assert(!!ret_root_hash_sig == !!ret_root_hash_sig_size);
+
+        if (partition_offset == UINT64_MAX || partition_size == UINT64_MAX)
+                return -EINVAL;
+
+        if (partition_size > 4*1024*1024) /* Signature data cannot possible be larger than 4M, refuse that */
+                return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), "Verity signature partition is larger than 4M, refusing.");
+
+        _cleanup_free_ char *buf = new(char, partition_size+1);
+        if (!buf)
+                return -ENOMEM;
+
+        ssize_t n = pread(fd, buf, partition_size, partition_offset);
+        if (n < 0)
+                return -ENOMEM;
+        if ((uint64_t) n != partition_size)
+                return -EIO;
+
+        const char *e = memchr(buf, 0, partition_size);
+        if (e) {
+                /* If we found a NUL byte then the rest of the data must be NUL too */
+                if (!memeqzero(e, partition_size - (e - buf)))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature data contains embedded NUL byte.");
+        } else
+                buf[partition_size] = 0;
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        r = sd_json_parse(buf, 0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse signature JSON data: %m");
+
+        sd_json_variant *rh = sd_json_variant_by_key(v, "rootHash");
+        if (!rh)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature JSON object lacks 'rootHash' field.");
+
+        _cleanup_free_ void *root_hash = NULL;
+        size_t root_hash_size;
+        r = sd_json_variant_unhex(rh, &root_hash, &root_hash_size);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse root hash field: %m");
+
+        sd_json_variant *sig = sd_json_variant_by_key(v, "signature");
+        if (!sig)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature JSON object lacks 'signature' field.");
+
+        _cleanup_free_ void *root_hash_sig = NULL;
+        size_t root_hash_sig_size;
+        r = sd_json_variant_unbase64(sig, &root_hash_sig, &root_hash_sig_size);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse signature field: %m");
+
+        if (ret_root_hash) {
+                *ret_root_hash = TAKE_PTR(root_hash);
+                *ret_root_hash_size = root_hash_size;
+        }
+
+        if (ret_root_hash_sig) {
+                *ret_root_hash_sig = TAKE_PTR(root_hash_sig);
+                *ret_root_hash_sig_size = root_hash_sig_size;
+        }
+
+        return 0;
+}
+
 #if HAVE_BLKID
 static int diskseq_should_be_used(
                 const char *whole_devname,
@@ -1159,6 +1236,32 @@ static int dissect_image(
                                 rw = false;
 
                         } else if (type.designator == PARTITION_ROOT_VERITY_SIG) {
+                                if (verity && verity->root_hash) {
+                                        _cleanup_free_ void *root_hash = NULL;
+                                        size_t root_hash_size;
+
+                                        r = acquire_sig_for_roothash(
+                                                        fd,
+                                                        start * 512,
+                                                        size * 512,
+                                                        &root_hash,
+                                                        &root_hash_size,
+                                                        /* ret_root_hash_sig= */ NULL,
+                                                        /* ret_root_hash_sig_size= */ NULL);
+                                        if (r < 0)
+                                                return r;
+                                        if (memcmp_nn(verity->root_hash, verity->root_hash_size, root_hash, root_hash_size) != 0) {
+                                                if (DEBUG_LOGGING) {
+                                                        _cleanup_free_ char *found = NULL, *expected = NULL;
+
+                                                        found = hexmem(root_hash, root_hash_size);
+                                                        expected = hexmem(verity->root_hash, verity->root_hash_size);
+
+                                                        log_debug("Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(found), strna(expected));
+                                                }
+                                                continue;
+                                        }
+                                }
 
                                 check_partition_flags(node, pflags,
                                                       SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY);
@@ -1202,6 +1305,32 @@ static int dissect_image(
                                 rw = false;
 
                         } else if (type.designator == PARTITION_USR_VERITY_SIG) {
+                                if (verity && verity->root_hash) {
+                                        _cleanup_free_ void *root_hash = NULL;
+                                        size_t root_hash_size;
+
+                                        r = acquire_sig_for_roothash(
+                                                        fd,
+                                                        start * 512,
+                                                        size * 512,
+                                                        &root_hash,
+                                                        &root_hash_size,
+                                                        /* ret_root_hash_sig= */ NULL,
+                                                        /* ret_root_hash_sig_size= */ NULL);
+                                        if (r < 0)
+                                                return r;
+                                        if (memcmp_nn(verity->root_hash, verity->root_hash_size, root_hash, root_hash_size) != 0) {
+                                                if (DEBUG_LOGGING) {
+                                                        _cleanup_free_ char *found = NULL, *expected = NULL;
+
+                                                        found = hexmem(root_hash, root_hash_size);
+                                                        expected = hexmem(verity->root_hash, verity->root_hash_size);
+
+                                                        log_debug("Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(found), strna(expected));
+                                                }
+                                                continue;
+                                        }
+                                }
 
                                 check_partition_flags(node, pflags,
                                                       SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY);
@@ -3661,44 +3790,13 @@ int dissected_image_load_verity_sig_partition(
         DissectedPartition *p = m->partitions + ds;
         if (!p->found)
                 return 0;
-        if (p->offset == UINT64_MAX || p->size == UINT64_MAX)
-                return -EINVAL;
-
-        if (p->size > 4*1024*1024) /* Signature data cannot possible be larger than 4M, refuse that */
-                return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), "Verity signature partition is larger than 4M, refusing.");
 
-        _cleanup_free_ char *buf = new(char, p->size+1);
-        if (!buf)
-                return -ENOMEM;
-
-        ssize_t n = pread(fd, buf, p->size, p->offset);
-        if (n < 0)
-                return -ENOMEM;
-        if ((uint64_t) n != p->size)
-                return -EIO;
-
-        const char *e = memchr(buf, 0, p->size);
-        if (e) {
-                /* If we found a NUL byte then the rest of the data must be NUL too */
-                if (!memeqzero(e, p->size - (e - buf)))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature data contains embedded NUL byte.");
-        } else
-                buf[p->size] = 0;
-
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
-        r = sd_json_parse(buf, 0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL);
-        if (r < 0)
-                return log_debug_errno(r, "Failed to parse signature JSON data: %m");
-
-        sd_json_variant *rh = sd_json_variant_by_key(v, "rootHash");
-        if (!rh)
-                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature JSON object lacks 'rootHash' field.");
+        _cleanup_free_ void *root_hash = NULL, *root_hash_sig = NULL;
+        size_t root_hash_size, root_hash_sig_size;
 
-        _cleanup_free_ void *root_hash = NULL;
-        size_t root_hash_size;
-        r = sd_json_variant_unhex(rh, &root_hash, &root_hash_size);
+        r = acquire_sig_for_roothash(fd, p->offset, p->size, &root_hash, &root_hash_size, &root_hash_sig, &root_hash_sig_size);
         if (r < 0)
-                return log_debug_errno(r, "Failed to parse root hash field: %m");
+                return r;
 
         /* Check if specified root hash matches if it is specified */
         if (verity->root_hash &&
@@ -3711,16 +3809,6 @@ int dissected_image_load_verity_sig_partition(
                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(a), strna(b));
         }
 
-        sd_json_variant *sig = sd_json_variant_by_key(v, "signature");
-        if (!sig)
-                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature JSON object lacks 'signature' field.");
-
-        _cleanup_free_ void *root_hash_sig = NULL;
-        size_t root_hash_sig_size;
-        r = sd_json_variant_unbase64(sig, &root_hash_sig, &root_hash_sig_size);
-        if (r < 0)
-                return log_debug_errno(r, "Failed to parse signature field: %m");
-
         free_and_replace(verity->root_hash, root_hash);
         verity->root_hash_size = root_hash_size;