summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/basic/bitfield.h73
-rw-r--r--src/basic/macro.h9
-rw-r--r--src/boot/measure.c4
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c14
-rw-r--r--src/cryptsetup/cryptsetup.c2
-rw-r--r--src/shared/tpm2-util.c578
-rw-r--r--src/shared/tpm2-util.h49
-rw-r--r--src/test/meson.build1
-rw-r--r--src/test/test-bitfield.c227
-rw-r--r--src/test/test-macro.c184
-rw-r--r--src/test/test-tpm2.c374
11 files changed, 1354 insertions, 161 deletions
diff --git a/src/basic/bitfield.h b/src/basic/bitfield.h
new file mode 100644
index 0000000000..25bc0ebda7
--- /dev/null
+++ b/src/basic/bitfield.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "macro.h"
+
+/* Bit index (0-based) to mask of specified type. Assertion failure if index is out of range. */
+#define _INDEX_TO_MASK(type, i, uniq) \
+ ({ \
+ int UNIQ_T(_i, uniq) = (i); \
+ assert(UNIQ_T(_i, uniq) < (int)sizeof(type) * 8); \
+ ((type)1) << UNIQ_T(_i, uniq); \
+ })
+#define INDEX_TO_MASK(type, i) \
+ ({ \
+ assert_cc(sizeof(type) <= sizeof(unsigned long long)); \
+ assert_cc(__builtin_choose_expr(__builtin_constant_p(i), i, 0) < (int)(sizeof(type) * 8)); \
+ __builtin_choose_expr(__builtin_constant_p(i), \
+ ((type)1) << (i), \
+ _INDEX_TO_MASK(type, i, UNIQ)); \
+ })
+
+/* Builds a mask of specified type with multiple bits set. Note the result will not be constant, even if all
+ * indexes are constant. */
+#define INDEXES_TO_MASK(type, ...) \
+ UNIQ_INDEXES_TO_MASK(type, UNIQ, ##__VA_ARGS__)
+#define UNIQ_INDEXES_TO_MASK(type, uniq, ...) \
+ ({ \
+ typeof(type) UNIQ_T(_mask, uniq) = (type)0; \
+ int UNIQ_T(_i, uniq); \
+ VA_ARGS_FOREACH(UNIQ_T(_i, uniq), ##__VA_ARGS__) \
+ UNIQ_T(_mask, uniq) |= INDEX_TO_MASK(type, UNIQ_T(_i, uniq)); \
+ UNIQ_T(_mask, uniq); \
+ })
+
+/* Same as the FLAG macros, but accept a 0-based bit index instead of a mask. Results in assertion failure if
+ * index is out of range for the type. */
+#define SET_BIT(bits, i) SET_FLAG(bits, INDEX_TO_MASK(typeof(bits), i), true)
+#define CLEAR_BIT(bits, i) SET_FLAG(bits, INDEX_TO_MASK(typeof(bits), i), false)
+#define BIT_SET(bits, i) FLAGS_SET(bits, INDEX_TO_MASK(typeof(bits), i))
+
+/* As above, but accepts multiple indexes. Note the result will not be constant, even if all indexes are
+ * constant. */
+#define SET_BITS(bits, ...) SET_FLAG(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__), true)
+#define CLEAR_BITS(bits, ...) SET_FLAG(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__), false)
+#define BITS_SET(bits, ...) FLAGS_SET(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__))
+
+/* Iterate through each set bit. Index is 0-based and type int. */
+#define BIT_FOREACH(index, bits) _BIT_FOREACH(index, bits, UNIQ)
+#define _BIT_FOREACH(index, bits, uniq) \
+ for (int UNIQ_T(_last, uniq) = -1, index; \
+ (index = BIT_NEXT_SET(bits, UNIQ_T(_last, uniq))) >= 0; \
+ UNIQ_T(_last, uniq) = index)
+
+/* Find the next set bit after 0-based index 'prev'. Result is 0-based index of next set bit, or -1 if no
+ * more bits are set. */
+#define BIT_FIRST_SET(bits) BIT_NEXT_SET(bits, -1)
+#define BIT_NEXT_SET(bits, prev) \
+ UNIQ_BIT_NEXT_SET(bits, prev, UNIQ)
+#define UNIQ_BIT_NEXT_SET(bits, prev, uniq) \
+ ({ \
+ typeof(bits) UNIQ_T(_bits, uniq) = (bits); \
+ int UNIQ_T(_prev, uniq) = (prev); \
+ int UNIQ_T(_next, uniq); \
+ _BIT_NEXT_SET(UNIQ_T(_bits, uniq), \
+ UNIQ_T(_prev, uniq), \
+ UNIQ_T(_next, uniq)); \
+ })
+#define _BIT_NEXT_SET(bits, prev, next) \
+ ((int)(prev + 1) == (int)sizeof(bits) * 8 \
+ ? -1 /* Prev index was msb. */ \
+ : ((next = __builtin_ffsll(((unsigned long long)(bits)) >> (prev + 1))) == 0 \
+ ? -1 /* No more bits set. */ \
+ : prev + next))
diff --git a/src/basic/macro.h b/src/basic/macro.h
index 63344f8d97..2770d01aa9 100644
--- a/src/basic/macro.h
+++ b/src/basic/macro.h
@@ -430,4 +430,13 @@ assert_cc(sizeof(dummy_t) == 0);
_q && _q > (base) ? &_q[-1] : NULL; \
})
+/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly
+ * convertable. The iteration variable 'entry' must already be defined. */
+#define VA_ARGS_FOREACH(entry, ...) \
+ _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__)
+#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...) \
+ for (typeof(entry) _entries_[] = { __VA_ARGS__ }, *_current_ = _entries_; \
+ ((long)(_current_ - _entries_) < (long)ELEMENTSOF(_entries_)) && ({ entry = *_current_; true; }); \
+ _current_++)
+
#include "log.h"
diff --git a/src/boot/measure.c b/src/boot/measure.c
index 18838686a9..072f38f200 100644
--- a/src/boot/measure.c
+++ b/src/boot/measure.c
@@ -870,7 +870,9 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return log_error_errno(tpmalg, "Unsupported PCR bank");
TPML_PCR_SELECTION pcr_selection;
- tpm2_pcr_mask_to_selection(1 << TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, &pcr_selection);
+ tpm2_tpml_pcr_selection_from_mask(1 << TPM_PCR_INDEX_KERNEL_IMAGE,
+ tpmalg,
+ &pcr_selection);
rc = sym_Esys_PolicyPCR(
c->esys_context,
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
index 319b0ca64d..b5d66e389d 100644
--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
@@ -205,13 +205,13 @@ _public_ void cryptsetup_token_dump(
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
- r = pcr_mask_to_string(hash_pcr_mask, &hash_pcrs_str);
- if (r < 0)
- return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
+ hash_pcrs_str = tpm2_pcr_mask_to_string(hash_pcr_mask);
+ if (!hash_pcrs_str)
+ return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m");
- r = pcr_mask_to_string(pubkey_pcr_mask, &pubkey_pcrs_str);
- if (r < 0)
- return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
+ pubkey_pcrs_str = tpm2_pcr_mask_to_string(pubkey_pcr_mask);
+ if (!pubkey_pcrs_str)
+ return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m");
r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str);
if (r < 0)
@@ -271,7 +271,7 @@ _public_ int cryptsetup_token_validate(
}
u = json_variant_unsigned(e);
- if (u >= TPM2_PCRS_MAX) {
+ if (!TPM2_PCR_VALID(u)) {
crypt_log_debug(cd, "TPM2 PCR number out of range.");
return 1;
}
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index e776605501..fa160c1f8c 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -438,7 +438,7 @@ static int parse_one_option(const char *option) {
}
pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX;
- } else if (pcr >= TPM2_PCRS_MAX) {
+ } else if (!TPM2_PCR_VALID(pcr)) {
log_error("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1);
return 0;
}
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index de706a10db..28d743a576 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -462,96 +462,461 @@ static int tpm2_make_primary(
return 0;
}
-void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret) {
+/* Utility functions for TPMS_PCR_SELECTION. */
+
+/* Convert a TPMS_PCR_SELECTION object to a mask. */
+void tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s, uint32_t *ret) {
+ assert(s);
+ assert(s->sizeofSelect <= sizeof(s->pcrSelect));
assert(ret);
- /* We only do 24bit here, as that's what PC TPMs are supposed to support */
- assert(mask <= 0xFFFFFFU);
+ uint32_t mask = 0;
+ for (unsigned i = 0; i < s->sizeofSelect; i++)
+ SET_FLAG(mask, (uint32_t)s->pcrSelect[i] << (i * 8), true);
+ *ret = mask;
+}
- *ret = (TPML_PCR_SELECTION) {
- .count = 1,
- .pcrSelections[0] = {
- .hash = bank,
- .sizeofSelect = 3,
- .pcrSelect[0] = mask & 0xFF,
- .pcrSelect[1] = (mask >> 8) & 0xFF,
- .pcrSelect[2] = (mask >> 16) & 0xFF,
- }
+/* Convert a mask and hash alg to a TPMS_PCR_SELECTION object. */
+void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TPMS_PCR_SELECTION *ret) {
+ assert(ret);
+
+ /* This is currently hardcoded at 24 PCRs, above. */
+ if (!TPM2_PCR_MASK_VALID(mask))
+ log_warning("PCR mask selections (%x) out of range, ignoring.",
+ mask & ~((uint32_t)TPM2_PCRS_MASK));
+
+ *ret = (TPMS_PCR_SELECTION){
+ .hash = hash_alg,
+ .sizeofSelect = TPM2_PCRS_MAX / 8,
+ .pcrSelect[0] = mask & 0xff,
+ .pcrSelect[1] = (mask >> 8) & 0xff,
+ .pcrSelect[2] = (mask >> 16) & 0xff,
};
}
-static unsigned find_nth_bit(uint32_t mask, unsigned n) {
- uint32_t bit = 1;
+/* Add all PCR selections in 'b' to 'a'. Both must have the same hash alg. */
+void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) {
+ assert(a);
+ assert(b);
+ assert(a->hash == b->hash);
+
+ uint32_t maska, maskb;
+ tpm2_tpms_pcr_selection_to_mask(a, &maska);
+ tpm2_tpms_pcr_selection_to_mask(b, &maskb);
+ tpm2_tpms_pcr_selection_from_mask(maska | maskb, a->hash, a);
+}
+
+/* Remove all PCR selections in 'b' from 'a'. Both must have the same hash alg. */
+void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) {
+ assert(a);
+ assert(b);
+ assert(a->hash == b->hash);
+
+ uint32_t maska, maskb;
+ tpm2_tpms_pcr_selection_to_mask(a, &maska);
+ tpm2_tpms_pcr_selection_to_mask(b, &maskb);
+ tpm2_tpms_pcr_selection_from_mask(maska & ~maskb, a->hash, a);
+}
+
+/* Move all PCR selections in 'b' to 'a'. Both must have the same hash alg. */
+void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b) {
+ if (a == b)
+ return;
+
+ tpm2_tpms_pcr_selection_add(a, b);
+ tpm2_tpms_pcr_selection_from_mask(0, b->hash, b);
+}
+
+#define FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms) \
+ _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, UNIQ)
+#define _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, uniq) \
+ FOREACH_PCR_IN_MASK(pcr, \
+ ({ uint32_t UNIQ_T(_mask, uniq); \
+ tpm2_tpms_pcr_selection_to_mask(tpms, &UNIQ_T(_mask, uniq)); \
+ UNIQ_T(_mask, uniq); \
+ }))
+
+#define FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \
+ UNIQ_FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, UNIQ)
+#define UNIQ_FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, uniq) \
+ for (TPML_PCR_SELECTION *UNIQ_T(_tpml, uniq) = (TPML_PCR_SELECTION*)(tpml); \
+ UNIQ_T(_tpml, uniq); UNIQ_T(_tpml, uniq) = NULL) \
+ _FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, UNIQ_T(_tpml, uniq))
+#define _FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \
+ for (TPMS_PCR_SELECTION *tpms = tpml->pcrSelections; \
+ (uint32_t)(tpms - tpml->pcrSelections) < tpml->count; \
+ tpms++)
+
+#define FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, tpms, tpml) \
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \
+ FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms)
+
+char *tpm2_tpms_pcr_selection_to_string(const TPMS_PCR_SELECTION *s) {
+ assert(s);
+
+ const char *algstr = strna(tpm2_hash_alg_to_string(s->hash));
+
+ uint32_t mask;
+ tpm2_tpms_pcr_selection_to_mask(s, &mask);
+ _cleanup_free_ char *maskstr = tpm2_pcr_mask_to_string(mask);
+ if (!maskstr)
+ return NULL;
+
+ return strjoin(algstr, "(", maskstr, ")");
+}
- assert(n < 32);
+size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s) {
+ assert(s);
- /* Returns the bit index of the nth set bit, e.g. mask=0b101001, n=3 → 5 */
+ uint32_t mask;
+ tpm2_tpms_pcr_selection_to_mask(s, &mask);
+ return (size_t)__builtin_popcount(mask);
+}
+
+/* Utility functions for TPML_PCR_SELECTION. */
- for (unsigned i = 0; i < sizeof(mask)*8; i++) {
+/* Remove the (0-based) index entry from 'l', shift all following entries, and update the count. */
+static void tpm2_tpml_pcr_selection_remove_index(TPML_PCR_SELECTION *l, uint32_t index) {
+ assert(l);
+ assert(l->count <= sizeof(l->pcrSelections));
+ assert(index < l->count);
+
+ size_t s = l->count - (index + 1);
+ memmove(&l->pcrSelections[index], &l->pcrSelections[index + 1], s * sizeof(l->pcrSelections[0]));
+ l->count--;
+}
- if (bit & mask) {
- if (n == 0)
- return i;
+/* Get a TPMS_PCR_SELECTION from a TPML_PCR_SELECTION for the given hash alg. Returns NULL if there is no
+ * entry for the hash alg. This guarantees the returned entry contains all the PCR selections for the given
+ * hash alg, which may require modifying the TPML_PCR_SELECTION by removing duplicate entries. */
+static TPMS_PCR_SELECTION *tpm2_tpml_pcr_selection_get_tpms_pcr_selection(
+ TPML_PCR_SELECTION *l,
+ TPMI_ALG_HASH hash_alg) {
- n--;
+ assert(l);
+
+ TPMS_PCR_SELECTION *selection = NULL;
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, l)
+ if (s->hash == hash_alg) {
+ selection = s;
+ break;
}
- bit <<= 1;
+ if (!selection)
+ return NULL;
+
+ /* Iterate backwards through the entries, removing any other entries for the hash alg. */
+ for (uint32_t i = l->count - 1; i > 0; i--) {
+ TPMS_PCR_SELECTION *s = &l->pcrSelections[i];
+
+ if (selection == s)
+ break;
+
+ if (s->hash == hash_alg) {
+ tpm2_tpms_pcr_selection_move(selection, s);
+ tpm2_tpml_pcr_selection_remove_index(l, i);
+ }
}
- return UINT_MAX;
+ return selection;
}
-static int tpm2_pcr_mask_good(
+/* Convert a TPML_PCR_SELECTION object to a mask. Returns -ENOENT if 'hash_alg' is not in the object. */
+int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash_alg, uint32_t *ret) {
+ assert(l);
+ assert(ret);
+
+ /* Make a copy, as tpm2_tpml_pcr_selection_get_tpms_pcr_selection() will modify the object if there
+ * are multiple entries with the requested hash alg. */
+ TPML_PCR_SELECTION lcopy = *l;
+
+ TPMS_PCR_SELECTION *s;
+ s = tpm2_tpml_pcr_selection_get_tpms_pcr_selection(&lcopy, hash_alg);
+ if (!s)
+ return SYNTHETIC_ERRNO(ENOENT);
+
+ tpm2_tpms_pcr_selection_to_mask(s, ret);
+ return 0;
+}
+
+/* Convert a mask and hash alg to a TPML_PCR_SELECTION object. */
+void tpm2_tpml_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TPML_PCR_SELECTION *ret) {
+ assert(ret);
+
+ TPMS_PCR_SELECTION s;
+ tpm2_tpms_pcr_selection_from_mask(mask, hash_alg, &s);
+
+ *ret = (TPML_PCR_SELECTION){
+ .count = 1,
+ .pcrSelections[0] = s,
+ };
+}
+
+/* Combine all duplicate (same hash alg) TPMS_PCR_SELECTION entries in 'l'. */
+static void tpm2_tpml_pcr_selection_cleanup(TPML_PCR_SELECTION *l) {
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, l)
+ /* This removes all duplicates for s->hash. */
+ (void) tpm2_tpml_pcr_selection_get_tpms_pcr_selection(l, s->hash);
+}
+
+/* Add the PCR selections in 's' to the corresponding hash alg TPMS_PCR_SELECTION entry in 'l'. Adds a new
+ * TPMS_PCR_SELECTION entry for the hash alg if needed. This may modify the TPML_PCR_SELECTION by combining
+ * entries with the same hash alg. */
+void tpm2_tpml_pcr_selection_add_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s) {
+ assert(l);
+ assert(s);
+
+ if (tpm2_tpms_pcr_selection_is_empty(s))
+ return;
+
+ TPMS_PCR_SELECTION *selection = tpm2_tpml_pcr_selection_get_tpms_pcr_selection(l, s->hash);
+ if (selection) {
+ tpm2_tpms_pcr_selection_add(selection, s);
+ return;
+ }
+
+ /* It's already broken if the count is higher than the array has size for. */
+ assert(!(l->count > sizeof(l->pcrSelections)));
+
+ /* If full, the cleanup should result in at least one available entry. */
+ if (l->count == sizeof(l->pcrSelections))
+ tpm2_tpml_pcr_selection_cleanup(l);
+
+ assert(l->count < sizeof(l->pcrSelections));
+ l->pcrSelections[l->count++] = *s;
+}
+
+/* Remove the PCR selections in 's' from the corresponding hash alg TPMS_PCR_SELECTION entry in 'l'. This
+ * will combine all entries for 's->hash' in 'l'. */
+void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s) {
+ assert(l);
+ assert(s);
+
+ if (tpm2_tpms_pcr_selection_is_empty(s))
+ return;
+
+ TPMS_PCR_SELECTION *selection = tpm2_tpml_pcr_selection_get_tpms_pcr_selection(l, s->hash);
+ if (selection)
+ tpm2_tpms_pcr_selection_sub(selection, s);
+}
+
+/* Add all PCR selections in 'b' to 'a'. */
+void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b) {
+ assert(a);
+ assert(b);
+
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection_b, (TPML_PCR_SELECTION*) b)
+ tpm2_tpml_pcr_selection_add_tpms_pcr_selection(a, selection_b);
+}
+
+/* Remove all PCR selections in 'b' from 'a'. */
+void tpm2_tpml_pcr_selection_sub(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b) {
+ assert(a);
+ assert(b);
+
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection_b, (TPML_PCR_SELECTION*) b)
+ tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(a, selection_b);
+}
+
+char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l) {
+ assert(l);
+
+ _cleanup_free_ char *banks = NULL;
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, (TPML_PCR_SELECTION*) l) {
+ if (tpm2_tpms_pcr_selection_is_empty(s))
+ continue;
+
+ _cleanup_free_ char *str = tpm2_tpms_pcr_selection_to_string(s);
+ if (!str || !strextend_with_separator(&banks, ",", str))
+ return NULL;
+ }
+
+ return strjoin("[", strempty(banks), "]");
+}
+
+size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l) {
+ assert(l);
+ assert(l->count <= sizeof(l->pcrSelections));
+
+ size_t weight = 0;
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, l) {
+ size_t w = tpm2_tpms_pcr_selection_weight(s);
+ assert(weight <= SIZE_MAX - w);
+ weight += w;
+ }
+
+ return weight;
+}
+
+static void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg) {
+ if (!DEBUG_LOGGING || !l)
+ return;
+
+ _cleanup_free_ char *s = tpm2_tpml_pcr_selection_to_string(l);
+ log_debug("%s: %s", msg ?: "PCR selection", strna(s));
+}
+
+static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) {
+ if (!DEBUG_LOGGING || !buffer || size == 0)
+ return;
+
+ _cleanup_free_ char *h = hexmem(buffer, size);
+ log_debug("%s: %s", msg ?: "Buffer", strna(h));
+}
+
+static void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg) {
+ if (digest)
+ tpm2_log_debug_buffer(digest->buffer, digest->size, msg ?: "Digest");
+}
+
+static int tpm2_get_policy_digest(
Tpm2Context *c,
- TPMI_ALG_HASH bank,
- uint32_t mask) {
+ const Tpm2Handle *session,
+ TPM2B_DIGEST **ret_policy_digest) {
- _cleanup_(Esys_Freep) TPML_DIGEST *pcr_values = NULL;
- TPML_PCR_SELECTION selection;
- bool good = false;
TSS2_RC rc;
- assert(c);
+ if (!DEBUG_LOGGING && !ret_policy_digest)
+ return 0;
- /* So we have the problem that some systems might have working TPM2 chips, but the firmware doesn't
- * actually measure into them, or only into a suboptimal bank. If so, the PCRs should be all zero or
- * all 0xFF. Detect that, so that we can warn and maybe pick a better bank. */
+ assert(c);
+ assert(session);
- tpm2_pcr_mask_to_selection(mask, bank, &selection);
+ log_debug("Acquiring policy digest.");
- rc = sym_Esys_PCR_Read(
+ _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
+ rc = sym_Esys_PolicyGetDigest(
c->esys_context,
+ session->esys_handle,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
- &selection,
- NULL,
- NULL,
- &pcr_values);
+ &policy_digest);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc));
+ "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
+
+ tpm2_log_debug_digest(policy_digest, "Session policy digest");
+
+ if (ret_policy_digest)
+ *ret_policy_digest = TAKE_PTR(policy_digest);
+
+ return 0;
+}
+
+static int tpm2_pcr_read(
+ Tpm2Context *c,
+ const TPML_PCR_SELECTION *pcr_selection,
+ TPML_PCR_SELECTION *ret_pcr_selection,
+ TPM2B_DIGEST **ret_pcr_values,
+ size_t *ret_pcr_values_size) {
+
+ _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL;
+ TPML_PCR_SELECTION remaining, total_read = {};
+ size_t pcr_values_size = 0;
+ TSS2_RC rc;
+
+ assert(c);
+ assert(pcr_selection);
+
+ remaining = *pcr_selection;
+ while (!tpm2_tpml_pcr_selection_is_empty(&remaining)) {
+ _cleanup_(Esys_Freep) TPML_PCR_SELECTION *current_read = NULL;
+ _cleanup_(Esys_Freep) TPML_DIGEST *current_values = NULL;
+
+ tpm2_log_debug_tpml_pcr_selection(&remaining, "Reading PCR selection");
+
+ /* Unfortunately, PCR_Read will not return more than 8 values. */
+ rc = sym_Esys_PCR_Read(
+ c->esys_context,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &remaining,
+ NULL,
+ &current_read,
+ &current_values);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc));
+
+ if (tpm2_tpml_pcr_selection_is_empty(current_read)) {
+ log_warning("TPM2 refused to read possibly unimplemented PCRs, ignoring.");
+ break;
+ }
+
+ tpm2_tpml_pcr_selection_sub(&remaining, current_read);
+ tpm2_tpml_pcr_selection_add(&total_read, current_read);
+
+ if (!GREEDY_REALLOC(pcr_values, pcr_values_size + current_values->count))
+ return log_oom();
+
+ memcpy_safe(&pcr_values[pcr_values_size], current_values->digests,
+ current_values->count * sizeof(TPM2B_DIGEST));
+ pcr_values_size += current_values->count;
- /* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */
- for (unsigned i = 0; i < pcr_values->count; i++) {
if (DEBUG_LOGGING) {
- _cleanup_free_ char *h = NULL;
- unsigned j;
+ unsigned i = 0;
+ FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, current_read) {
+ assert(i < current_values->count);
- h = hexmem(pcr_values->digests[i].buffer, pcr_values->digests[i].size);
- j = find_nth_bit(mask, i);
- assert(j != UINT_MAX);
+ TPM2B_DIGEST *d = &current_values->digests[i];
+ i++;
- log_debug("PCR %u value: %s", j, strna(h));
+ TPML_PCR_SELECTION l;
+ tpm2_tpml_pcr_selection_from_mask(INDEX_TO_MASK(uint32_t, pcr), s->hash, &l);
+
+ _cleanup_free_ char *desc = tpm2_tpml_pcr_selection_to_string(&l);
+ tpm2_log_debug_digest(d, strna(desc));
+ }
}
+ }
- if (!memeqbyte(0x00, pcr_values->digests[i].buffer, pcr_values->digests[i].size) &&
- !memeqbyte(0xFF, pcr_values->digests[i].buffer, pcr_values->digests[i].size))
- good = true;
+ if (ret_pcr_selection)
+ *ret_pcr_selection = total_read;
+ if (ret_pcr_values)
+ *ret_pcr_values = TAKE_PTR(pcr_values);
+ if (ret_pcr_values_size)
+ *ret_pcr_values_size = pcr_values_size;
+
+ return 0;
+}
+
+static int tpm2_pcr_mask_good(
+ Tpm2Context *c,
+ TPMI_ALG_HASH bank,
+ uint32_t mask) {
+
+ _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL;
+ TPML_PCR_SELECTION selection;
+ size_t pcr_values_size = 0;
+ int r;
+
+ assert(c);
+
+ /* So we have the problem that some systems might have working TPM2 chips, but the firmware doesn't
+ * actually measure into them, or only into a suboptimal bank. If so, the PCRs should be all zero or
+ * all 0xFF. Detect that, so that we can warn and maybe pick a better bank. */
+
+ tpm2_tpml_pcr_selection_from_mask(mask, bank, &selection);
+
+ r = tpm2_pcr_read(c, &selection, &selection, &pcr_values, &pcr_values_size);
+ if (r < 0)
+ return r;
+
+ /* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */
+ unsigned i = 0;
+ FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, &selection) {
+ assert(i < pcr_values_size);
+
+ if (!memeqbyte(0x00, pcr_values[i].buffer, pcr_values[i].size) &&
+ !memeqbyte(0xFF, pcr_values[i].buffer, pcr_values[i].size))
+ return true;
+
+ i++;
}
- return good;
+ return false;
}
static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) {
@@ -1108,7 +1473,6 @@ static int tpm2_make_policy_session(
.keyBits.aes = 128,
.mode.aes = TPM2_ALG_CFB,
};
- _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
TSS2_RC rc;
int r;
@@ -1230,7 +1594,7 @@ static int tpm2_make_policy_session(
/* Put together the PCR policy we want to use */
TPML_PCR_SELECTION pcr_selection;
- tpm2_pcr_mask_to_selection(pubkey_pcr_mask, pcr_bank, &pcr_selection);
+ tpm2_tpml_pcr_selection_from_mask(pubkey_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection);
rc = sym_Esys_PolicyPCR(
c->esys_context,
session->esys_handle,
@@ -1245,16 +1609,9 @@ static int tpm2_make_policy_session(
/* Get the policy hash of the PCR policy */
_cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL;
- rc = sym_Esys_PolicyGetDigest(
- c->esys_context,
- session->esys_handle,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- &approved_policy);
- if (rc != TSS2_RC_SUCCESS)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
+ r = tpm2_get_policy_digest(c, session, &approved_policy);
+ if (r < 0)
+ return r;
/* When we are unlocking and have a signature, let's pass it to the TPM */
_cleanup_(Esys_Freep) TPMT_TK_VERIFIED *check_ticket_buffer = NULL;
@@ -1340,7 +1697,7 @@ static int tpm2_make_policy_session(
log_debug("Configuring hash-based PCR policy.");
TPML_PCR_SELECTION pcr_selection;
- tpm2_pcr_mask_to_selection(hash_pcr_mask, pcr_bank, &pcr_selection);
+ tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection);
rc = sym_Esys_PolicyPCR(
c->esys_context,
session->esys_handle,
@@ -1369,38 +1726,13 @@ static int tpm2_make_policy_session(
sym_Tss2_RC_Decode(rc));
}
- if (DEBUG_LOGGING || ret_policy_digest) {
- log_debug("Acquiring policy digest.");
-
- rc = sym_Esys_PolicyGetDigest(
- c->esys_context,
- session->esys_handle,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- &policy_digest);
-
- if (rc != TSS2_RC_SUCCESS)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
-
- if (DEBUG_LOGGING) {
- _cleanup_free_ char *h = NULL;
-
- h = hexmem(policy_digest->buffer, policy_digest->size);
- if (!h)
- return log_oom();
-
- log_debug("Session policy digest: %s", h);
- }
- }
+ r = tpm2_get_policy_digest(c, session, ret_policy_digest);
+ if (r < 0)
+ return r;
if (ret_session)
*ret_session = TAKE_PTR(session);
- if (ret_policy_digest)
- *ret_policy_digest = TAKE_PTR(policy_digest);
-
if (ret_pcr_bank)
*ret_pcr_bank = pcr_bank;
@@ -1422,7 +1754,6 @@ int tpm2_seal(const char *device,
uint16_t *ret_pcr_bank,
uint16_t *ret_primary_alg) {
- _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
_cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
static const TPML_PCR_SELECTION creation_pcr = {};
@@ -1431,7 +1762,6 @@ int tpm2_seal(const char *device,
TPM2B_SENSITIVE_CREATE hmac_sensitive;
TPMI_ALG_PUBLIC primary_alg;
TPM2B_PUBLIC hmac_template;
- TPMI_ALG_HASH pcr_bank;
usec_t start;
TSS2_RC rc;
int r;
@@ -1485,6 +1815,8 @@ int tpm2_seal(const char *device,
if (r < 0)
return r;
+ _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
+ TPMI_ALG_HASH pcr_bank;
r = tpm2_make_policy_session(
c,
primary,
@@ -1616,7 +1948,6 @@ int tpm2_unseal(const char *device,
size_t *ret_secret_size) {
_cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
- _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
_cleanup_(erase_and_freep) char *secret = NULL;
TPM2B_PRIVATE private = {};
TPM2B_PUBLIC public = {};
@@ -1716,6 +2047,7 @@ int tpm2_unseal(const char *device,
for (unsigned i = RETRY_UNSEAL_MAX;; i--) {
_cleanup_tpm2_handle_ Tpm2Handle *policy_session = NULL;
+ _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
r = tpm2_make_policy_session(
c,
primary,
@@ -1981,13 +2313,28 @@ int tpm2_extend_bytes(
}
#endif
-int tpm2_parse_pcrs(const char *s, uint32_t *ret) {
- const char *p = ASSERT_PTR(s);
+char *tpm2_pcr_mask_to_string(uint32_t mask) {
+ _cleanup_free_ char *s = NULL;
+
+ FOREACH_PCR_IN_MASK(n, mask)
+ if (strextendf_with_separator(&s, "+", "%d", n) < 0)
+ return NULL;
+
+ if (!s)
+ return strdup("");
+
+ return TAKE_PTR(s);
+}
+
+int tpm2_pcr_mask_from_string(const char *arg, uint32_t *ret_mask) {
uint32_t mask = 0;
int r;
- if (isempty(s)) {
- *ret = 0;
+ assert(arg);
+ assert(ret_mask);
+
+ if (isempty(arg)) {
+ *ret_mask = 0;
return 0;
}
@@ -1996,6 +2343,7 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret) {
* /etc/crypttab the "," is already used to separate options, hence a different separator is nice to
* avoid escaping. */
+ const char *p = arg;
for (;;) {
_cleanup_free_ char *pcr = NULL;
unsigned n;
@@ -2004,19 +2352,20 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret) {
if (r == 0)
break;
if (r < 0)
- return log_error_errno(r, "Failed to parse PCR list: %s", s);
+ return log_error_errno(r, "Failed to parse PCR list: %s", arg);
r = safe_atou(pcr, &n);
if (r < 0)
return log_error_errno(r, "Failed to parse PCR number: %s", pcr);
if (n >= TPM2_PCRS_MAX)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
- "PCR number out of range (valid range 0…23): %u", n);
+ "PCR number out of range (valid range 0…%u): %u",
+ TPM2_PCRS_MAX - 1, n);
- mask |= UINT32_C(1) << n;
+ SET_BIT(mask, n);;
}
- *ret = mask;
+ *ret_mask = mask;
return 0;
}
@@ -2381,7 +2730,7 @@ int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask) {
return 0;
}
- r = tpm2_parse_pcrs(arg, &m);
+ r = tpm2_pcr_mask_from_string(arg, &m);
if (r < 0)
return r;
@@ -2437,25 +2786,6 @@ int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pu
return 0;
}
-int pcr_mask_to_string(uint32_t mask, char **ret) {
- _cleanup_free_ char *buf = NULL;
- int r;
-
- assert(ret);
-
- for (unsigned i = 0; i < TPM2_PCRS_MAX; i++) {
- if (!(mask & (UINT32_C(1) << i)))
- continue;
-
- r = strextendf_with_separator(&buf, "+", "%u", i);
- if (r < 0)
- return r;
- }
-
- *ret = TAKE_PTR(buf);
- return 0;
-}
-
#define PBKDF2_HMAC_SHA256_ITERATIONS 10000
/*
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index d26a945a90..c2532c61c2 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -3,6 +3,7 @@
#include <stdbool.h>
+#include "bitfield.h"
#include "json.h"
#include "macro.h"
#include "sha256.h"
@@ -11,6 +12,20 @@ typedef enum TPM2Flags {
TPM2_FLAGS_USE_PIN = 1 << 0,
} TPM2Flags;
+
+/* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
+ * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */
+#define TPM2_PCRS_MAX 24U
+#define TPM2_PCRS_MASK ((UINT32_C(1) << TPM2_PCRS_MAX) - 1)
+static inline bool TPM2_PCR_VALID(unsigned pcr) {
+ return pcr < TPM2_PCRS_MAX;
+}
+static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) {
+ return pcr_mask <= TPM2_PCRS_MASK;
+}
+
+#define FOREACH_PCR_IN_MASK(pcr, mask) BIT_FOREACH(pcr, mask)
+
#if HAVE_TPM2
#include <tss2/tss2_esys.h>
@@ -80,8 +95,6 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle);
DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free);
#define _cleanup_tpm2_handle_ _cleanup_(tpm2_handle_freep)
-void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret);
-
static inline void Esys_Freep(void *p) {
if (*(void**) p)
sym_Esys_Free(*(void**) p);
@@ -92,6 +105,25 @@ int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret)
int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size);
+void tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s, uint32_t *ret);
+void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret);
+void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b);
+void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b);
+void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b);
+char *tpm2_tpms_pcr_selection_to_string(const TPMS_PCR_SELECTION *s);
+size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s);
+#define tpm2_tpms_pcr_selection_is_empty(s) (tpm2_tpms_pcr_selection_weight(s) == 0)
+
+int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t *ret);
+void tpm2_tpml_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPML_PCR_SELECTION *ret);
+void tpm2_tpml_pcr_selection_add_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s);
+void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s);
+void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b);
+void tpm2_tpml_pcr_selection_sub(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b);
+char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l);
+size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l);
+#define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0)
+
#else /* HAVE_TPM2 */
typedef struct {} Tpm2Context;
typedef struct {} Tpm2Handle;
@@ -100,20 +132,12 @@ typedef struct {} Tpm2Handle;
int tpm2_list_devices(void);
int tpm2_find_device_auto(int log_level, char **ret);
-int tpm2_parse_pcrs(const char *s, uint32_t *ret);
-
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, TPM2Flags flags, JsonVariant **ret);
int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, TPM2Flags *ret_flags);
-#define TPM2_PCRS_MAX 24U
-
-static inline bool TPM2_PCR_MASK_VALID(uint64_t pcr_mask) {
- return pcr_mask < (UINT64_C(1) << TPM2_PCRS_MAX); /* Support 24 PCR banks */
-}
-
/* Default to PCR 7 only */
#define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7)
@@ -149,6 +173,9 @@ int tpm2_hash_alg_from_string(const char *alg);
const char *tpm2_asym_alg_to_string(uint16_t alg);
int tpm2_asym_alg_from_string(const char *alg);
+char *tpm2_pcr_mask_to_string(uint32_t mask);
+int tpm2_pcr_mask_from_string(const char *arg, uint32_t *mask);
+
typedef struct {
uint32_t search_pcr_mask;
const char *device;
@@ -173,8 +200,6 @@ int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask);
int tpm2_load_pcr_signature(const char *path, JsonVariant **ret);
int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size);
-int pcr_mask_to_string(uint32_t mask, char **ret);
-
int tpm2_util_pbkdf2_hmac_sha256(const void *pass,
size_t passlen,
const void *salt,
diff --git a/src/test/meson.build b/src/test/meson.build
index b57a6bf2f1..55c0881299 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -44,6 +44,7 @@ simple_tests += files(
'test-architecture.c',
'test-argv-util.c',
'test-barrier.c',
+ 'test-bitfield.c',
'test-bitmap.c',
'test-blockdev-util.c',
'test-bootspec.c',
diff --git a/src/test/test-bitfield.c b/src/test/test-bitfield.c
new file mode 100644
index 0000000000..74ebd5cc48
--- /dev/null
+++ b/src/test/test-bitfield.c
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stddef.h>
+
+#include "bitfield.h"
+#include "log.h"
+#include "tests.h"
+
+#define TEST_BITS(bits, v, ...) \
+ ({ \
+ assert_se((!!BITS_SET(bits, ##__VA_ARGS__)) == v); \
+ assert_se((!!BITS_SET(~(bits), ##__VA_ARGS__)) == !v); \
+ })
+#define TEST_BIT(bits, v, i) \
+ ({ \
+ assert_se((!!BIT_SET(bits, i)) == v); \
+ assert_se((!!BIT_SET(~(bits), i)) == !v); \
+ TEST_BITS(bits, v, i); \
+ })
+
+#define TEST_BIT_SET(bits, i) TEST_BIT(bits, 1, i)
+#define TEST_BIT_CLEAR(bits, i) TEST_BIT(bits, 0, i)
+
+#define TEST_BITS_SET(bits, ...) TEST_BITS(bits, 1, ##__VA_ARGS__)
+#define TEST_BITS_CLEAR(bits, ...) TEST_BITS(bits, 0, ##__VA_ARGS__)
+
+TEST(bits) {
+ int count;
+
+ /* Test uint8_t */
+ TEST_BIT_SET(0x81, 0);
+ TEST_BIT_SET(0x81, 7);
+ TEST_BITS_SET(0x81, 0, 7);
+ TEST_BIT_CLEAR(0x81, 4);
+ TEST_BIT_CLEAR(0x81, 6);
+ TEST_BITS_CLEAR(0x81, 1, 2, 3, 4, 5, 6);
+ uint8_t expected8 = 0;
+ BIT_FOREACH(i, 0x81)
+ expected8 |= UINT8_C(1) << i;
+ assert_se(expected8 == 0x81);
+ uint8_t u8 = 0x91;
+ TEST_BIT_SET(u8, 4);
+ TEST_BITS_SET(u8, 0, 4, 7);
+ TEST_BIT_CLEAR(u8, 2);
+ TEST_BITS_CLEAR(u8, 1, 2, 3, 5, 6);
+ SET_BIT(u8, 1);
+ TEST_BITS_SET(u8, 0, 1, 4, 7);
+ TEST_BITS_CLEAR(u8, 2, 3, 5, 6);
+ SET_BITS(u8, 3, 5);
+ TEST_BITS_SET(u8, 0, 1, 3, 4, 5, 7);
+ TEST_BITS_CLEAR(u8, 2, 6);
+ CLEAR_BIT(u8, 4);
+ TEST_BITS_SET(u8, 0, 1, 3, 5, 7);
+ TEST_BITS_CLEAR(u8, 2, 4, 6);
+ CLEAR_BITS(u8, 1);
+ CLEAR_BITS(u8, 0, 7);
+ TEST_BITS_SET(u8, 3, 5);
+ TEST_BITS_CLEAR(u8, 0, 1, 2, 4, 6, 7);
+ expected8 = 0;
+ BIT_FOREACH(i, u8)
+ expected8 |= UINT8_C(1) << i;
+ assert_se(expected8 == u8);
+ u8 = 0;
+ TEST_BITS_CLEAR(u8, 0, 1, 2, 3, 4, 5, 6, 7);
+ BIT_FOREACH(i, u8)
+ assert_se(0);
+ u8 = ~u8;
+ TEST_BITS_SET(u8, 0, 1, 2, 3, 4, 5, 6, 7);
+ count = 0;
+ BIT_FOREACH(i, u8)
+ count++;
+ assert_se(count == 8);
+ uint8_t _u8 = u8;
+ SET_BITS(u8);
+ assert_se(_u8 == u8);
+ CLEAR_BITS(u8);
+ assert_se(_u8 == u8);
+
+ /* Test uint16_t */
+ TEST_BIT_SET(0x1f81, 10);
+ TEST_BITS_SET(0x1f81, 0, 7, 8, 9, 10, 11, 12);
+ TEST_BIT_CLEAR(0x1f81, 13);
+ TEST_BITS_CLEAR(0x1f81, 1, 2, 3, 4, 5, 6, 13, 14, 15);
+ uint16_t expected16 = 0;
+ BIT_FOREACH(i, 0x1f81)
+ expected16 |= UINT16_C(1) << i;
+ assert_se(expected16 == 0x1f81);
+ uint16_t u16 = 0xf060;
+ TEST_BIT_SET(u16, 12);
+ TEST_BITS_SET(u16, 5, 6, 12, 13, 14, 15);
+ TEST_BIT_CLEAR(u16, 9);
+ TEST_BITS_CLEAR(u16, 0, 1, 2, 3, 4, 7, 8, 9, 10, 11);
+ SET_BITS(u16, 1, 8);
+ TEST_BITS_SET(u16, 1, 5, 6, 8, 12, 13, 14, 15);
+ TEST_BITS_CLEAR(u16, 0, 2, 3, 4, 7, 9, 10, 11);
+ CLEAR_BITS(u16, 13, 14);
+ TEST_BITS_SET(u16, 1, 5, 6, 8, 12, 15);
+ TEST_BITS_CLEAR(u16, 0, 2, 3, 4, 7, 9, 10, 11, 13, 14);
+ expected16 = 0;
+ BIT_FOREACH(i, u16)
+ expected16 |= UINT16_C(1) << i;
+ assert_se(expected16 == u16);
+ u16 = 0;
+ TEST_BITS_CLEAR(u16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
+ BIT_FOREACH(i, u16)
+ assert_se(0);
+ u16 = ~u16;
+ TEST_BITS_SET(u16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
+ count = 0;
+ BIT_FOREACH(i, u16)
+ count++;
+ assert_se(count == 16);
+ uint16_t _u16 = u16;
+ SET_BITS(u16);
+ assert_se(_u16 == u16);
+ CLEAR_BITS(u16);
+ assert_se(_u16 == u16);
+
+ /* Test uint32_t */
+ TEST_BIT_SET(0x80224f10, 11);
+ TEST_BITS_SET(0x80224f10, 4, 8, 9, 10, 11, 14, 17, 21, 31);
+ TEST_BIT_CLEAR(0x80224f10, 28);
+ TEST_BITS_CLEAR(0x80224f10, 0, 1, 2, 3, 5, 6, 7, 12, 13, 15, 16, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30);
+ uint32_t expected32 = 0;
+ BIT_FOREACH(i, 0x80224f10)
+ expected32 |= UINT32_C(1) << i;
+ assert_se(expected32 == 0x80224f10);
+ uint32_t u32 = 0x605e0388;
+ TEST_BIT_SET(u32, 3);
+ TEST_BIT_SET(u32, 30);
+ TEST_BITS_SET(u32, 3, 7, 8, 9, 17, 18, 19, 20, 22, 29, 30);
+ TEST_BIT_CLEAR(u32, 0);
+ TEST_BIT_CLEAR(u32, 31);
+ TEST_BITS_CLEAR(u32, 0, 1, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 21, 23, 24, 25, 26, 27, 28, 31);
+ SET_BITS(u32, 1, 25, 26);
+ TEST_BITS_SET(u32, 1, 3, 7, 8, 9, 17, 18, 19, 20, 22, 25, 26, 29, 30);
+ TEST_BITS_CLEAR(u32, 0, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 21, 23, 24, 27, 28, 31);
+ CLEAR_BITS(u32, 29, 17, 1);
+ TEST_BITS_SET(u32, 3, 7, 8, 9, 18, 19, 20, 22, 25, 26, 30);
+ TEST_BITS_CLEAR(u32, 0, 1, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 17, 21, 23, 24, 27, 28, 29, 31);
+ expected32 = 0;
+ BIT_FOREACH(i, u32)
+ expected32 |= UINT32_C(1) << i;
+ assert_se(expected32 == u32);
+ u32 = 0;
+ TEST_BITS_CLEAR(u32, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
+ BIT_FOREACH(i, u32)
+ assert_se(0);
+ u32 = ~u32;
+ TEST_BITS_SET(u32, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
+ count = 0;
+ BIT_FOREACH(i, u32)
+ count++;
+ assert_se(count == 32);
+ uint32_t _u32 = u32;
+ SET_BITS(u32);
+ assert_se(_u32 == u32);
+ CLEAR_BITS(u32);
+ assert_se(_u32 == u32);
+
+ /* Test uint64_t */
+ TEST_BIT_SET(0x18ba1400f4857460, 60);
+ TEST_BITS_SET(0x18ba1400f4857460, 5, 6, 10, 12, 13, 14, 16, 18, 23, 26, 28, 29, 30, 31, 42, 44, 49, 51, 52, 53, 55, 59, 60);
+ TEST_BIT_CLEAR(UINT64_C(0x18ba1400f4857460), 0);
+ TEST_BIT_CLEAR(UINT64_C(0x18ba1400f4857460), 63);
+ TEST_BITS_CLEAR(UINT64_C(0x18ba1400f4857460), 0, 1, 2, 3, 4, 7, 8, 9, 11, 15, 17, 19, 20, 21, 22, 24, 25, 27, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 45, 46, 47, 48, 50, 54, 56, 57, 58, 61, 62, 63);
+ uint64_t expected64 = 0;
+ BIT_FOREACH(i, 0x18ba1400f4857460)
+ expected64 |= UINT64_C(1) << i;
+ assert_se(expected64 == 0x18ba1400f4857460);
+ uint64_t u64 = 0xa90e2d8507a65739;
+ TEST_BIT_SET(u64, 0);
+ TEST_BIT_SET(u64, 63);
+ TEST_BITS_SET(u64, 0, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 63);
+ TEST_BIT_CLEAR(u64, 1);
+ TEST_BITS_CLEAR(u64, 1, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62);
+ SET_BIT(u64, 1);
+ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 63);
+ TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62);
+ CLEAR_BIT(u64, 63);
+ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61);
+ TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62, 63);
+ SET_BIT(u64, 62);
+ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62);
+ TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 63);
+ SET_BITS(u64, 63, 62, 7, 13, 38, 40);
+ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62, 63);
+ TEST_BITS_CLEAR(u64, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60);
+ CLEAR_BIT(u64, 32);
+ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62, 63);
+ TEST_BITS_CLEAR(u64, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60);
+ CLEAR_BITS(u64, 0, 2, 11, 63, 32, 58);
+ TEST_BITS_SET(u64, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62);
+ TEST_BITS_CLEAR(u64, 0, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 63);
+ expected64 = 0;
+ BIT_FOREACH(i, u64)
+ expected64 |= UINT64_C(1) << i;
+ assert_se(expected64 == u64);
+ u64 = 0;
+ TEST_BITS_CLEAR(u64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63);
+ BIT_FOREACH(i, u64)
+ assert_se(0);
+ u64 = ~u64;
+ TEST_BITS_SET(u64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63);
+ count = 0;
+ BIT_FOREACH(i, u64)
+ count++;
+ assert_se(count == 64);
+ uint64_t _u64 = u64;
+ SET_BITS(u64);
+ assert_se(_u64 == u64);
+ CLEAR_BITS(u64);
+ assert_se(_u64 == u64);
+
+ /* Verify these use cases are constant-folded. */
+ assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint8_t, 1)));
+ assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint16_t, 1)));
+ assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint32_t, 1)));
+ assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint64_t, 1)));
+
+ assert_cc(__builtin_constant_p(BIT_SET((uint8_t)2, 1)));
+ assert_cc(__builtin_constant_p(BIT_SET((uint16_t)2, 1)));
+ assert_cc(__builtin_constant_p(BIT_SET((uint32_t)2, 1)));
+ assert_cc(__builtin_constant_p(BIT_SET((uint64_t)2, 1)));
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
diff --git a/src/test/test-macro.c b/src/test/test-macro.c
index ef74b8273e..637d6f1a49 100644
--- a/src/test/test-macro.c
+++ b/src/test/test-macro.c
@@ -300,6 +300,190 @@ TEST(foreach_pointer) {
assert_se(k == 11);
}
+TEST(foreach_va_args) {
+ size_t i;
+
+ i = 0;
+ uint8_t u8, u8_1 = 1, u8_2 = 2, u8_3 = 3;
+ VA_ARGS_FOREACH(u8, u8_2, 8, 0xff, u8_1, u8_3, 0, 1) {
+ switch(i++) {
+ case 0: assert_se(u8 == u8_2); break;
+ case 1: assert_se(u8 == 8); break;
+ case 2: assert_se(u8 == 0xff); break;
+ case 3: assert_se(u8 == u8_1); break;
+ case 4: assert_se(u8 == u8_3); break;
+ case 5: assert_se(u8 == 0); break;
+ case 6: assert_se(u8 == 1); break;
+ default: assert_se(false);
+ }
+ }
+ assert_se(i == 7);
+ i = 0;
+ VA_ARGS_FOREACH(u8, 0) {
+ assert_se(u8 == 0);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ i = 0;
+ VA_ARGS_FOREACH(u8, 0xff) {
+ assert_se(u8 == 0xff);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ VA_ARGS_FOREACH(u8)
+ assert_se(false);
+
+ i = 0;
+ uint32_t u32, u32_1 = 0xffff0000, u32_2 = 10, u32_3 = 0xffff;
+ VA_ARGS_FOREACH(u32, 1, 100, u32_2, 1000, u32_3, u32_1, 1, 0) {
+ switch(i++) {
+ case 0: assert_se(u32 == 1); break;
+ case 1: assert_se(u32 == 100); break;
+ case 2: assert_se(u32 == u32_2); break;
+ case 3: assert_se(u32 == 1000); break;
+ case 4: assert_se(u32 == u32_3); break;
+ case 5: assert_se(u32 == u32_1); break;
+ case 6: assert_se(u32 == 1); break;
+ case 7: assert_se(u32 == 0); break;
+ default: assert_se(false);
+ }
+ }
+ assert_se(i == 8);
+ i = 0;
+ VA_ARGS_FOREACH(u32, 0) {
+ assert_se(u32 == 0);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ i = 0;
+ VA_ARGS_FOREACH(u32, 1000) {
+ assert_se(u32 == 1000);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ VA_ARGS_FOREACH(u32)
+ assert_se(false);
+
+ i = 0;
+ uint64_t u64, u64_1 = 0xffffffffffffffff, u64_2 = 50, u64_3 = 0xffff;
+ VA_ARGS_FOREACH(u64, 44, 0, u64_3, 100, u64_2, u64_1, 50000) {
+ switch(i++) {
+ case 0: assert_se(u64 == 44); break;
+ case 1: assert_se(u64 == 0); break;
+ case 2: assert_se(u64 == u64_3); break;
+ case 3: assert_se(u64 == 100); break;
+ case 4: assert_se(u64 == u64_2); break;
+ case 5: assert_se(u64 == u64_1); break;
+ case 6: assert_se(u64 == 50000); break;
+ default: assert_se(false);
+ }
+ }
+ assert_se(i == 7);
+ i = 0;
+ VA_ARGS_FOREACH(u64, 0) {
+ assert_se(u64 == 0);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ i = 0;
+ VA_ARGS_FOREACH(u64, 0xff00ff00000000) {
+ assert_se(u64 == 0xff00ff00000000);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ VA_ARGS_FOREACH(u64)
+ assert_se(false);
+
+ struct test {
+ int a;
+ char b;
+ };
+
+ i = 0;
+ struct test s,
+ s_1 = { .a = 0, .b = 'c', },
+ s_2 = { .a = 100000, .b = 'z', },
+ s_3 = { .a = 0xff, .b = 'q', },
+ s_4 = { .a = 1, .b = 'x', };
+ VA_ARGS_FOREACH(s, s_1, (struct test){ .a = 10, .b = 'd', }, s_2, (struct test){}, s_3, s_4) {
+ switch(i++) {
+ case 0: assert_se(s.a == 0 ); assert_se(s.b == 'c'); break;
+ case 1: assert_se(s.a == 10 ); assert_se(s.b == 'd'); break;
+ case 2: assert_se(s.a == 100000); assert_se(s.b == 'z'); break;
+ case 3: assert_se(s.a == 0 ); assert_se(s.b == 0 ); break;
+ case 4: assert_se(s.a == 0xff ); assert_se(s.b == 'q'); break;
+ case 5: assert_se(s.a == 1 ); assert_se(s.b == 'x'); break;
+ default: assert_se(false);
+ }
+ }
+ assert_se(i == 6);
+ i = 0;
+ VA_ARGS_FOREACH(s, (struct test){ .a = 1, .b = 'A', }) {
+ assert_se(s.a == 1);
+ assert_se(s.b == 'A');
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ VA_ARGS_FOREACH(s)
+ assert_se(false);
+
+ i = 0;
+ struct test *p, *p_1 = &s_1, *p_2 = &s_2, *p_3 = &s_3, *p_4 = &s_4;
+ VA_ARGS_FOREACH(p, p_1, NULL, p_2, p_3, NULL, p_4, NULL) {
+ switch(i++) {
+ case 0: assert_se(p == p_1); break;
+ case 1: assert_se(p == NULL); break;
+ case 2: assert_se(p == p_2); break;
+ case 3: assert_se(p == p_3); break;
+ case 4: assert_se(p == NULL); break;
+ case 5: assert_se(p == p_4); break;
+ case 6: assert_se(p == NULL); break;
+ default: assert_se(false);
+ }
+ }
+ assert_se(i == 7);
+ i = 0;
+ VA_ARGS_FOREACH(p, p_3) {
+ assert_se(p == p_3);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ VA_ARGS_FOREACH(p)
+ assert_se(false);
+
+ i = 0;
+ void *v, *v_1 = p_1, *v_2 = p_2, *v_3 = p_3;
+ uint32_t *u32p = &u32;
+ VA_ARGS_FOREACH(v, v_1, NULL, u32p, v_3, p_2, p_4, v_2, NULL) {
+ switch(i++) {
+ case 0: assert_se(v == v_1); break;
+ case 1: assert_se(v == NULL); break;
+ case 2: assert_se(v == u32p); break;
+ case 3: assert_se(v == v_3); break;
+ case 4: assert_se(v == p_2); break;
+ case 5: assert_se(v == p_4); break;
+ case 6: assert_se(v == v_2); break;
+ case 7: assert_se(v == NULL); break;
+ default: assert_se(false);
+ }
+ }
+ assert_se(i == 8);
+ i = 0;
+ VA_ARGS_FOREACH(v, NULL) {
+ assert_se(v == NULL);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ i = 0;
+ VA_ARGS_FOREACH(v, v_1) {
+ assert_se(v == v_1);
+ assert_se(i++ == 0);
+ }
+ assert_se(i == 1);
+ VA_ARGS_FOREACH(v)
+ assert_se(false);
+}
+
TEST(align_to) {
assert_se(ALIGN_TO(0, 1) == 0);
assert_se(ALIGN_TO(1, 1) == 1);
diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c
index 04e08490b3..20baa0f261 100644
--- a/src/test/test-tpm2.c
+++ b/src/test/test-tpm2.c
@@ -3,29 +3,29 @@
#include "tpm2-util.h"
#include "tests.h"
-static void test_tpm2_parse_pcrs_one(const char *s, uint32_t mask, int ret) {
+static void test_tpm2_pcr_mask_from_string_one(const char *s, uint32_t mask, int ret) {
uint32_t m;
- assert_se(tpm2_parse_pcrs(s, &m) == ret);
+ assert_se(tpm2_pcr_mask_from_string(s, &m) == ret);
if (ret >= 0)
assert_se(m == mask);
}
-TEST(tpm2_parse_pcrs) {
- test_tpm2_parse_pcrs_one("", 0, 0);
- test_tpm2_parse_pcrs_one("0", 1, 0);
- test_tpm2_parse_pcrs_one("1", 2, 0);
- test_tpm2_parse_pcrs_one("0,1", 3, 0);
- test_tpm2_parse_pcrs_one("0+1", 3, 0);
- test_tpm2_parse_pcrs_one("0-1", 0, -EINVAL);
- test_tpm2_parse_pcrs_one("0,1,2", 7, 0);
- test_tpm2_parse_pcrs_one("0+1+2", 7, 0);
- test_tpm2_parse_pcrs_one("0+1,2", 7, 0);
- test_tpm2_parse_pcrs_one("0,1+2", 7, 0);
- test_tpm2_parse_pcrs_one("0,2", 5, 0);
- test_tpm2_parse_pcrs_one("0+2", 5, 0);
- test_tpm2_parse_pcrs_one("foo", 0, -EINVAL);
+TEST(tpm2_mask_from_string) {
+ test_tpm2_pcr_mask_from_string_one("", 0, 0);
+ test_tpm2_pcr_mask_from_string_one("0", 1, 0);
+ test_tpm2_pcr_mask_from_string_one("1", 2, 0);
+ test_tpm2_pcr_mask_from_string_one("0,1", 3, 0);
+ test_tpm2_pcr_mask_from_string_one("0+1", 3, 0);
+ test_tpm2_pcr_mask_from_string_one("0-1", 0, -EINVAL);
+ test_tpm2_pcr_mask_from_string_one("0,1,2", 7, 0);
+ test_tpm2_pcr_mask_from_string_one("0+1+2", 7, 0);
+ test_tpm2_pcr_mask_from_string_one("0+1,2", 7, 0);
+ test_tpm2_pcr_mask_from_string_one("0,1+2", 7, 0);
+ test_tpm2_pcr_mask_from_string_one("0,2", 5, 0);
+ test_tpm2_pcr_mask_from_string_one("0+2", 5, 0);
+ test_tpm2_pcr_mask_from_string_one("foo", 0, -EINVAL);
}
TEST(tpm2_util_pbkdf2_hmac_sha256) {
@@ -69,4 +69,346 @@ TEST(tpm2_util_pbkdf2_hmac_sha256) {
}
}
+#if HAVE_TPM2
+
+#define POISON(type) \
+ ({ \
+ type _p; \
+ memset(&_p, 0xaa, sizeof(_p)); \
+ _p; \
+ })
+#define POISON_TPML POISON(TPML_PCR_SELECTION)
+#define POISON_TPMS POISON(TPMS_PCR_SELECTION)
+#define POISON_U32 POISON(uint32_t)
+
+static void assert_tpms_pcr_selection_eq(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b) {
+ assert_se(a);
+ assert_se(b);
+
+ assert_se(a->hash == b->hash);
+ assert_se(a->sizeofSelect == b->sizeofSelect);
+
+ for (size_t i = 0; i < a->sizeofSelect; i++)
+ assert_se(a->pcrSelect[i] == b->pcrSelect[i]);
+}
+
+static void assert_tpml_pcr_selection_eq(TPML_PCR_SELECTION *a, TPML_PCR_SELECTION *b) {
+ assert_se(a);
+ assert_se(b);
+
+ assert_se(a->count == b->count);
+ for (size_t i = 0; i < a->count; i++)
+ assert_tpms_pcr_selection_eq(&a->pcrSelections[i], &b->pcrSelections[i]);
+}
+
+static void verify_tpms_pcr_selection(TPMS_PCR_SELECTION *s, uint32_t mask, TPMI_ALG_HASH hash) {
+ assert_se(s->hash == hash);
+ assert_se(s->sizeofSelect == 3);
+ assert_se(s->pcrSelect[0] == (mask & 0xff));
+ assert_se(s->pcrSelect[1] == ((mask >> 8) & 0xff));
+ assert_se(s->pcrSelect[2] == ((mask >> 16) & 0xff));
+ assert_se(s->pcrSelect[3] == 0);
+
+ uint32_t m = POISON_U32;
+ tpm2_tpms_pcr_selection_to_mask(s, &m);
+ assert_se(m == mask);
+}
+
+static void verify_tpml_pcr_selection(TPML_PCR_SELECTION *l, TPMS_PCR_SELECTION s[], size_t count) {
+ assert_se(l->count == count);
+ for (size_t i = 0; i < count; i++) {
+ assert_tpms_pcr_selection_eq(&s[i], &l->pcrSelections[i]);
+
+ uint32_t mask = POISON_U32;
+ TPMI_ALG_HASH hash = l->pcrSelections[i].hash;
+ assert_se(tpm2_tpml_pcr_selection_to_mask(l, hash, &mask) == 0);
+ verify_tpms_pcr_selection(&l->pcrSelections[i], mask, hash);
+ }
+}
+
+static void _test_pcr_selection_mask_hash(uint32_t mask, TPMI_ALG_HASH hash) {
+ TPMS_PCR_SELECTION s = POISON_TPMS;
+ tpm2_tpms_pcr_selection_from_mask(mask, hash, &s);
+ verify_tpms_pcr_selection(&s, mask, hash);
+
+ TPML_PCR_SELECTION l = POISON_TPML;
+ tpm2_tpml_pcr_selection_from_mask(mask, hash, &l);
+ verify_tpml_pcr_selection(&l, &s, 1);
+ verify_tpms_pcr_selection(&l.pcrSelections[0], mask, hash);
+
+ uint32_t test_masks[] = {
+ 0x0, 0x1, 0x100, 0x10000, 0xf0f0f0, 0xaaaaaa, 0xffffff,
+ };
+ for (unsigned i = 0; i < ELEMENTSOF(test_masks); i++) {
+ uint32_t test_mask = test_masks[i];
+
+ TPMS_PCR_SELECTION a = POISON_TPMS, b = POISON_TPMS, test_s = POISON_TPMS;
+ tpm2_tpms_pcr_selection_from_mask(test_mask, hash, &test_s);
+
+ a = s;
+ b = test_s;
+ tpm2_tpms_pcr_selection_add(&a, &b);
+ verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, true), hash);
+ verify_tpms_pcr_selection(&b, test_mask, hash);
+
+ a = s;
+ b = test_s;
+ tpm2_tpms_pcr_selection_sub(&a, &b);
+ verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, false), hash);
+ verify_tpms_pcr_selection(&b, test_mask, hash);
+
+ a = s;
+ b = test_s;
+ tpm2_tpms_pcr_selection_move(&a, &b);
+ verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, true), hash);
+ verify_tpms_pcr_selection(&b, 0, hash);
+ }
+}
+
+TEST(tpms_pcr_selection_mask_and_hash) {
+ TPMI_ALG_HASH HASH_ALGS[] = { TPM2_ALG_SHA1, TPM2_ALG_SHA256, };
+
+ for (unsigned i = 0; i < ELEMENTSOF(HASH_ALGS); i++)
+ for (uint32_t m2 = 0; m2 <= 0xffffff; m2 += 0x30000)
+ for (uint32_t m1 = 0; m1 <= 0xffff; m1 += 0x300)
+ for (uint32_t m0 = 0; m0 <= 0xff; m0 += 0x3)
+ _test_pcr_selection_mask_hash(m0 | m1 | m2, HASH_ALGS[i]);
+}
+
+static void _test_tpms_sw(
+ TPMI_ALG_HASH hash,
+ uint32_t mask,
+ const char *expected_str,
+ size_t expected_weight) {
+
+ TPMS_PCR_SELECTION s = POISON_TPMS;
+ tpm2_tpms_pcr_selection_from_mask(mask, hash, &s);
+
+ _cleanup_free_ char *tpms_str = tpm2_tpms_pcr_selection_to_string(&s);
+ assert_se(streq(tpms_str, expected_str));
+
+ assert_se(tpm2_tpms_pcr_selection_weight(&s) == expected_weight);
+ assert_se(tpm2_tpms_pcr_selection_is_empty(&s) == (expected_weight == 0));
+}
+
+TEST(tpms_pcr_selection_string_and_weight) {
+ TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1, sha256 = TPM2_ALG_SHA256;
+
+ _test_tpms_sw(sha1, 0, "sha1()", 0);
+ _test_tpms_sw(sha1, 1, "sha1(0)", 1);
+ _test_tpms_sw(sha1, 0xf, "sha1(0+1+2+3)", 4);
+ _test_tpms_sw(sha1, 0x00ff00, "sha1(8+9+10+11+12+13+14+15)", 8);
+ _test_tpms_sw(sha1, 0xffffff, "sha1(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23)", 24);
+ _test_tpms_sw(sha256, 0, "sha256()", 0);
+ _test_tpms_sw(sha256, 1, "sha256(0)", 1);
+ _test_tpms_sw(sha256, 7, "sha256(0+1+2)", 3);
+ _test_tpms_sw(sha256, 0xf00000, "sha256(20+21+22+23)", 4);
+ _test_tpms_sw(sha256, 0xffffff, "sha256(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23)", 24);
+}
+
+static void _tpml_pcr_selection_add_tpms(TPMS_PCR_SELECTION s[], size_t count, TPML_PCR_SELECTION *ret) {
+ for (size_t i = 0; i < count; i++)
+ tpm2_tpml_pcr_selection_add_tpms_pcr_selection(ret, &s[i]);
+}
+
+static void _tpml_pcr_selection_sub_tpms(TPMS_PCR_SELECTION s[], size_t count, TPML_PCR_SELECTION *ret) {
+ for (size_t i = 0; i < count; i++)
+ tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(ret, &s[i]);
+}
+
+static void _test_tpml_sw(
+ TPMS_PCR_SELECTION s[],
+ size_t count,
+ size_t expected_count,
+ const char *expected_str,
+ size_t expected_weight) {
+
+ TPML_PCR_SELECTION l = {};
+ _tpml_pcr_selection_add_tpms(s, count, &l);
+ assert_se(l.count == expected_count);
+
+ _cleanup_free_ char *tpml_str = tpm2_tpml_pcr_selection_to_string(&l);
+ assert_se(streq(tpml_str, expected_str));
+
+ assert_se(tpm2_tpml_pcr_selection_weight(&l) == expected_weight);
+ assert_se(tpm2_tpml_pcr_selection_is_empty(&l) == (expected_weight == 0));
+}
+
+TEST(tpml_pcr_selection_string_and_weight) {
+ size_t size = 0xaa;
+ TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1,
+ sha256 = TPM2_ALG_SHA256,
+ sha384 = TPM2_ALG_SHA384,
+ sha512 = TPM2_ALG_SHA512;
+ TPMS_PCR_SELECTION s[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, };
+
+ size = 0;
+ tpm2_tpms_pcr_selection_from_mask(0x000002, sha1 , &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0x0080f0, sha384, &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0x010100, sha512, &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &s[size++]);
+ _test_tpml_sw(s,
+ size,
+ /* expected_count= */ 4,
+ "[sha1(1),sha384(4+5+6+7+15),sha512(8+16),sha256(16+17+18+19+20+21+22+23)]",
+ /* expected_weight= */ 16);
+
+ size = 0;
+ tpm2_tpms_pcr_selection_from_mask(0x0403aa, sha512, &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0x0080f0, sha256, &s[size++]);
+ _test_tpml_sw(s,
+ size,
+ /* expected_count= */ 2,
+ "[sha512(1+3+5+7+8+9+18),sha256(4+5+6+7+15)]",
+ /* expected_weight= */ 12);
+
+ size = 0;
+ /* Empty hashes should be ignored */
+ tpm2_tpms_pcr_selection_from_mask(0x0300ce, sha384, &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0xffffff, sha512, &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0x000000, sha1 , &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0x330010, sha256, &s[size++]);
+ _test_tpml_sw(s,
+ size,
+ /* expected_count= */ 3,
+ "[sha384(1+2+3+6+7+16+17),sha512(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23),sha256(4+16+17+20+21)]",
+ /* expected_weight= */ 36);
+
+ size = 0;
+ /* Verify same-hash entries are properly combined. */
+ tpm2_tpms_pcr_selection_from_mask(0x000001, sha1 , &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0x000001, sha256, &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0x000010, sha1 , &s[size++]);
+ tpm2_tpms_pcr_selection_from_mask(0x000010, sha256, &s[size++]);
+ _test_tpml_sw(s,
+ size,
+ /* expected_count= */ 2,
+ "[sha1(0+4),sha256(0+4)]",
+ /* expected_weight= */ 4);
+}
+
+/* Test tpml add/sub by changing the tpms individually */
+static void _test_tpml_addsub_tpms(
+ TPML_PCR_SELECTION *start,
+ TPMS_PCR_SELECTION add[],
+ size_t add_count,
+ TPMS_PCR_SELECTION expected1[],
+ size_t expected1_count,
+ TPMS_PCR_SELECTION sub[],
+ size_t sub_count,
+ TPMS_PCR_SELECTION expected2[],
+ size_t expected2_count) {
+
+ TPML_PCR_SELECTION l = *start;
+
+ _tpml_pcr_selection_add_tpms(add, add_count, &l);
+ verify_tpml_pcr_selection(&l, expected1, expected1_count);
+
+ _tpml_pcr_selection_sub_tpms(sub, sub_count, &l);
+ verify_tpml_pcr_selection(&l, expected2, expected2_count);
+}
+
+/* Test tpml add/sub by creating new tpmls */
+static void _test_tpml_addsub_tpml(
+ TPML_PCR_SELECTION *start,
+ TPMS_PCR_SELECTION add[],
+ size_t add_count,
+ TPMS_PCR_SELECTION expected1[],
+ size_t expected1_count,
+ TPMS_PCR_SELECTION sub[],
+ size_t sub_count,
+ TPMS_PCR_SELECTION expected2[],
+ size_t expected2_count) {
+
+ TPML_PCR_SELECTION l = {};
+ tpm2_tpml_pcr_selection_add(&l, start);
+ assert_tpml_pcr_selection_eq(&l, start);
+
+ TPML_PCR_SELECTION addl = {};
+ _tpml_pcr_selection_add_tpms(add, add_count, &addl);
+ tpm2_tpml_pcr_selection_add(&l, &addl);
+
+ TPML_PCR_SELECTION e1 = {};
+ _tpml_pcr_selection_add_tpms(expected1, expected1_count, &e1);
+ assert_tpml_pcr_selection_eq(&l, &e1);
+
+ TPML_PCR_SELECTION subl = {};
+ _tpml_pcr_selection_add_tpms(sub, sub_count, &subl);
+ tpm2_tpml_pcr_selection_sub(&l, &subl);
+
+ TPML_PCR_SELECTION e2 = {};
+ _tpml_pcr_selection_add_tpms(expected2, expected2_count, &e2);
+ assert_tpml_pcr_selection_eq(&l, &e2);
+}
+
+#define _test_tpml_addsub(...) \
+ ({ \
+ _test_tpml_addsub_tpms(__VA_ARGS__); \
+ _test_tpml_addsub_tpml(__VA_ARGS__); \
+ })
+
+TEST(tpml_pcr_selection_add_sub) {
+ size_t add_count = 0xaa, expected1_count = 0xaa, sub_count = 0xaa, expected2_count = 0xaa;
+ TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1,
+ sha256 = TPM2_ALG_SHA256,
+ sha384 = TPM2_ALG_SHA384,
+ sha512 = TPM2_ALG_SHA512;
+ TPML_PCR_SELECTION l = POISON_TPML;
+ TPMS_PCR_SELECTION add[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, },
+ sub[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, },
+ expected1[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, },
+ expected2[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, };
+
+ l = (TPML_PCR_SELECTION){};
+ add_count = 0;
+ expected1_count = 0;
+ sub_count = 0;
+ expected2_count = 0;
+ tpm2_tpms_pcr_selection_from_mask(0x010101, sha256, &add[add_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x101010, sha256, &add[add_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &add[add_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x111111, sha256, &expected1[expected1_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected1[expected1_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x000001, sha256, &sub[sub_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha512, &sub[sub_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x111110, sha256, &expected2[expected2_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected2[expected2_count++]);
+ _test_tpml_addsub(&l,
+ add, add_count,
+ expected1, expected1_count,
+ sub, sub_count,
+ expected2, expected2_count);
+
+ l = (TPML_PCR_SELECTION){
+ .count = 1,
+ .pcrSelections[0].hash = sha1,
+ .pcrSelections[0].sizeofSelect = 3,
+ .pcrSelections[0].pcrSelect[0] = 0xf0,
+ };
+ add_count = 0;
+ expected1_count = 0;
+ sub_count = 0;
+ expected2_count = 0;
+ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &add[add_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &add[add_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &add[add_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xf00000, sha1 , &add[add_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xf000f0, sha1 , &expected1[expected1_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &expected1[expected1_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &expected1[expected1_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected1[expected1_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x00ffff, sha256, &sub[sub_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xf000f0, sha1 , &expected2[expected2_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &expected2[expected2_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &expected2[expected2_count++]);
+ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected2[expected2_count++]);
+ _test_tpml_addsub(&l,
+ add, add_count,
+ expected1, expected1_count,
+ sub, sub_count,
+ expected2, expected2_count);
+}
+
+#endif /* HAVE_TPM2 */
+
DEFINE_TEST_MAIN(LOG_DEBUG);