summaryrefslogtreecommitdiffstats
path: root/src/fundamental
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/fundamental/bootspec-fundamental.c62
-rw-r--r--src/fundamental/bootspec-fundamental.h17
-rw-r--r--src/fundamental/efivars-fundamental.c37
-rw-r--r--src/fundamental/efivars-fundamental.h39
-rw-r--r--src/fundamental/macro-fundamental.h354
-rw-r--r--src/fundamental/meson.build24
-rw-r--r--src/fundamental/sbat.h8
-rw-r--r--src/fundamental/sha256.c297
-rw-r--r--src/fundamental/sha256.h34
-rw-r--r--src/fundamental/string-util-fundamental.c230
-rw-r--r--src/fundamental/string-util-fundamental.h120
-rw-r--r--src/fundamental/tpm-pcr.c17
-rw-r--r--src/fundamental/tpm-pcr.h48
13 files changed, 1287 insertions, 0 deletions
diff --git a/src/fundamental/bootspec-fundamental.c b/src/fundamental/bootspec-fundamental.c
new file mode 100644
index 0000000..4ba7c4c
--- /dev/null
+++ b/src/fundamental/bootspec-fundamental.c
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bootspec-fundamental.h"
+
+bool bootspec_pick_name_version_sort_key(
+ const sd_char *os_pretty_name,
+ const sd_char *os_image_id,
+ const sd_char *os_name,
+ const sd_char *os_id,
+ const sd_char *os_image_version,
+ const sd_char *os_version,
+ const sd_char *os_version_id,
+ const sd_char *os_build_id,
+ const sd_char **ret_name,
+ const sd_char **ret_version,
+ const sd_char **ret_sort_key) {
+
+ const sd_char *good_name, *good_version, *good_sort_key;
+
+ /* Find the best human readable title, version string and sort key for a boot entry (using the
+ * os-release(5) fields). Precise is preferred over vague, and human readable over machine
+ * readable. Thus:
+ *
+ * 1. First priority gets the PRETTY_NAME field, which is the primary string intended for display,
+ * and should already contain both a nice description and a version indication (if that concept
+ * applies).
+ *
+ * 2. Otherwise we go for IMAGE_ID and IMAGE_VERSION (thus we show details about the image,
+ * i.e. specific combination of packages and configuration), if that concept applies.
+ *
+ * 3. Otherwise we go for NAME and VERSION (i.e. human readable OS name and version)
+ *
+ * 4. Otherwise we go for ID and VERSION_ID (i.e. machine readable OS name and version)
+ *
+ * 5. Finally, for the version we'll use BUILD_ID (i.e. a machine readable version that identifies
+ * the original OS build used during installation)
+ *
+ * Note that the display logic will show only the name by default, except if that isn't unique in
+ * which case the version is shown too.
+ *
+ * Note that name/version determined here are used only for display purposes. Boot entry preference
+ * sorting (i.e. algorithmic ordering of boot entries) is done based on the order of the sort key (if
+ * defined) or entry "id" string (i.e. entry file name) otherwise. */
+
+ good_name = os_pretty_name ?: (os_image_id ?: (os_name ?: os_id));
+ good_version = os_image_version ?: (os_version ?: (os_version_id ? : os_build_id));
+ good_sort_key = os_image_id ?: os_id;
+
+ if (!good_name)
+ return false;
+
+ if (ret_name)
+ *ret_name = good_name;
+
+ if (ret_version)
+ *ret_version = good_version;
+
+ if (ret_sort_key)
+ *ret_sort_key = good_sort_key;
+
+ return true;
+}
diff --git a/src/fundamental/bootspec-fundamental.h b/src/fundamental/bootspec-fundamental.h
new file mode 100644
index 0000000..19b489c
--- /dev/null
+++ b/src/fundamental/bootspec-fundamental.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "string-util-fundamental.h"
+
+bool bootspec_pick_name_version_sort_key(
+ const sd_char *os_pretty_name,
+ const sd_char *os_image_id,
+ const sd_char *os_name,
+ const sd_char *os_id,
+ const sd_char *os_image_version,
+ const sd_char *os_version,
+ const sd_char *os_version_id,
+ const sd_char *os_build_id,
+ const sd_char **ret_name,
+ const sd_char **ret_version,
+ const sd_char **ret_sort_key);
diff --git a/src/fundamental/efivars-fundamental.c b/src/fundamental/efivars-fundamental.c
new file mode 100644
index 0000000..2ec3bfb
--- /dev/null
+++ b/src/fundamental/efivars-fundamental.c
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "efivars-fundamental.h"
+
+static const sd_char * const table[_SECURE_BOOT_MAX] = {
+ [SECURE_BOOT_UNSUPPORTED] = STR_C("unsupported"),
+ [SECURE_BOOT_DISABLED] = STR_C("disabled"),
+ [SECURE_BOOT_UNKNOWN] = STR_C("unknown"),
+ [SECURE_BOOT_AUDIT] = STR_C("audit"),
+ [SECURE_BOOT_DEPLOYED] = STR_C("deployed"),
+ [SECURE_BOOT_SETUP] = STR_C("setup"),
+ [SECURE_BOOT_USER] = STR_C("user"),
+};
+
+const sd_char *secure_boot_mode_to_string(SecureBootMode m) {
+ return (m >= 0 && m < _SECURE_BOOT_MAX) ? table[m] : NULL;
+}
+
+SecureBootMode decode_secure_boot_mode(bool secure, bool audit, bool deployed, bool setup) {
+ /* See figure 32-4 Secure Boot Modes from UEFI Specification 2.9 */
+ if (secure && deployed && !audit && !setup)
+ return SECURE_BOOT_DEPLOYED;
+ if (secure && !deployed && !audit && !setup)
+ return SECURE_BOOT_USER;
+ if (!secure && !deployed && audit && setup)
+ return SECURE_BOOT_AUDIT;
+ if (!secure && !deployed && !audit && setup)
+ return SECURE_BOOT_SETUP;
+
+ /* Some firmware allows disabling secure boot while not being in
+ * setup mode unless the PK is cleared. */
+ if (!secure && !deployed && !audit && !setup)
+ return SECURE_BOOT_DISABLED;
+
+ /* Well, this should not happen. */
+ return SECURE_BOOT_UNKNOWN;
+}
diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h
new file mode 100644
index 0000000..fe34e6c
--- /dev/null
+++ b/src/fundamental/efivars-fundamental.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <errno.h>
+#include "string-util-fundamental.h"
+
+/* Features of the loader, i.e. systemd-boot */
+#define EFI_LOADER_FEATURE_CONFIG_TIMEOUT (UINT64_C(1) << 0)
+#define EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT (UINT64_C(1) << 1)
+#define EFI_LOADER_FEATURE_ENTRY_DEFAULT (UINT64_C(1) << 2)
+#define EFI_LOADER_FEATURE_ENTRY_ONESHOT (UINT64_C(1) << 3)
+#define EFI_LOADER_FEATURE_BOOT_COUNTING (UINT64_C(1) << 4)
+#define EFI_LOADER_FEATURE_XBOOTLDR (UINT64_C(1) << 5)
+#define EFI_LOADER_FEATURE_RANDOM_SEED (UINT64_C(1) << 6)
+#define EFI_LOADER_FEATURE_LOAD_DRIVER (UINT64_C(1) << 7)
+#define EFI_LOADER_FEATURE_SORT_KEY (UINT64_C(1) << 8)
+#define EFI_LOADER_FEATURE_SAVED_ENTRY (UINT64_C(1) << 9)
+#define EFI_LOADER_FEATURE_DEVICETREE (UINT64_C(1) << 10)
+
+/* Features of the stub, i.e. systemd-stub */
+#define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0)
+#define EFI_STUB_FEATURE_PICK_UP_CREDENTIALS (UINT64_C(1) << 1)
+#define EFI_STUB_FEATURE_PICK_UP_SYSEXTS (UINT64_C(1) << 2)
+#define EFI_STUB_FEATURE_THREE_PCRS (UINT64_C(1) << 3)
+
+typedef enum SecureBootMode {
+ SECURE_BOOT_UNSUPPORTED,
+ SECURE_BOOT_DISABLED,
+ SECURE_BOOT_UNKNOWN,
+ SECURE_BOOT_AUDIT,
+ SECURE_BOOT_DEPLOYED,
+ SECURE_BOOT_SETUP,
+ SECURE_BOOT_USER,
+ _SECURE_BOOT_MAX,
+ _SECURE_BOOT_INVALID = -EINVAL,
+} SecureBootMode;
+
+const sd_char *secure_boot_mode_to_string(SecureBootMode m);
+SecureBootMode decode_secure_boot_mode(bool secure, bool audit, bool deployed, bool setup);
diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h
new file mode 100644
index 0000000..097ad18
--- /dev/null
+++ b/src/fundamental/macro-fundamental.h
@@ -0,0 +1,354 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#ifndef SD_BOOT
+# include <assert.h>
+#endif
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define _align_(x) __attribute__((__aligned__(x)))
+#define _alignas_(x) __attribute__((__aligned__(__alignof__(x))))
+#define _alignptr_ __attribute__((__aligned__(sizeof(void *))))
+#define _cleanup_(x) __attribute__((__cleanup__(x)))
+#define _const_ __attribute__((__const__))
+#define _deprecated_ __attribute__((__deprecated__))
+#define _destructor_ __attribute__((__destructor__))
+#define _hidden_ __attribute__((__visibility__("hidden")))
+#define _likely_(x) (__builtin_expect(!!(x), 1))
+#define _malloc_ __attribute__((__malloc__))
+#define _noinline_ __attribute__((noinline))
+#define _noreturn_ _Noreturn
+#define _packed_ __attribute__((__packed__))
+#define _printf_(a, b) __attribute__((__format__(printf, a, b)))
+#define _public_ __attribute__((__visibility__("default")))
+#define _pure_ __attribute__((__pure__))
+#define _retain_ __attribute__((__retain__))
+#define _returns_nonnull_ __attribute__((__returns_nonnull__))
+#define _section_(x) __attribute__((__section__(x)))
+#define _sentinel_ __attribute__((__sentinel__))
+#define _unlikely_(x) (__builtin_expect(!!(x), 0))
+#define _unused_ __attribute__((__unused__))
+#define _used_ __attribute__((__used__))
+#define _warn_unused_result_ __attribute__((__warn_unused_result__))
+#define _weak_ __attribute__((__weak__))
+#define _weakref_(x) __attribute__((__weakref__(#x)))
+
+#ifdef __clang__
+# define _alloc_(...)
+#else
+# define _alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__)))
+#endif
+
+#if __GNUC__ >= 7 || (defined(__clang__) && __clang_major__ >= 10)
+# define _fallthrough_ __attribute__((__fallthrough__))
+#else
+# define _fallthrough_
+#endif
+
+#define XSTRINGIFY(x) #x
+#define STRINGIFY(x) XSTRINGIFY(x)
+
+#ifndef __COVERITY__
+# define VOID_0 ((void)0)
+#else
+# define VOID_0 ((void*)0)
+#endif
+
+#define ELEMENTSOF(x) \
+ (__builtin_choose_expr( \
+ !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \
+ sizeof(x)/sizeof((x)[0]), \
+ VOID_0))
+
+#define XCONCATENATE(x, y) x ## y
+#define CONCATENATE(x, y) XCONCATENATE(x, y)
+
+#ifdef SD_BOOT
+ _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function);
+
+ #ifdef NDEBUG
+ #define assert(expr) ({ if (!(expr)) __builtin_unreachable(); })
+ #define assert_not_reached() __builtin_unreachable()
+ #else
+ #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); })
+ #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __PRETTY_FUNCTION__)
+ #endif
+ #define static_assert _Static_assert
+ #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); })
+#endif
+
+/* This passes the argument through after (if asserts are enabled) checking that it is not null. */
+#define ASSERT_PTR(expr) _ASSERT_PTR(expr, UNIQ_T(_expr_, UNIQ), assert)
+#define ASSERT_SE_PTR(expr) _ASSERT_PTR(expr, UNIQ_T(_expr_, UNIQ), assert_se)
+#define _ASSERT_PTR(expr, var, check) \
+ ({ \
+ typeof(expr) var = (expr); \
+ check(var); \
+ var; \
+ })
+
+#define ASSERT_NONNEG(expr) \
+ ({ \
+ typeof(expr) _expr_ = (expr), _zero = 0; \
+ assert(_expr_ >= _zero); \
+ _expr_; \
+ })
+
+#define ASSERT_SE_NONNEG(expr) \
+ ({ \
+ typeof(expr) _expr_ = (expr), _zero = 0; \
+ assert_se(_expr_ >= _zero); \
+ _expr_; \
+ })
+
+#define assert_cc(expr) static_assert(expr, #expr)
+
+
+#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
+#define UNIQ __COUNTER__
+
+/* Note that this works differently from pthread_once(): this macro does
+ * not synchronize code execution, i.e. code that is run conditionalized
+ * on this macro will run concurrently to all other code conditionalized
+ * the same way, there's no ordering or completion enforced. */
+#define ONCE __ONCE(UNIQ_T(_once_, UNIQ))
+#define __ONCE(o) \
+ ({ \
+ static bool (o) = false; \
+ __atomic_exchange_n(&(o), true, __ATOMIC_SEQ_CST); \
+ })
+
+#undef MAX
+#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b))
+#define __MAX(aq, a, bq, b) \
+ ({ \
+ const typeof(a) UNIQ_T(A, aq) = (a); \
+ const typeof(b) UNIQ_T(B, bq) = (b); \
+ UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
+ })
+
+#define IS_UNSIGNED_INTEGER_TYPE(type) \
+ (__builtin_types_compatible_p(typeof(type), unsigned char) || \
+ __builtin_types_compatible_p(typeof(type), unsigned short) || \
+ __builtin_types_compatible_p(typeof(type), unsigned) || \
+ __builtin_types_compatible_p(typeof(type), unsigned long) || \
+ __builtin_types_compatible_p(typeof(type), unsigned long long))
+
+#define IS_SIGNED_INTEGER_TYPE(type) \
+ (__builtin_types_compatible_p(typeof(type), signed char) || \
+ __builtin_types_compatible_p(typeof(type), signed short) || \
+ __builtin_types_compatible_p(typeof(type), signed) || \
+ __builtin_types_compatible_p(typeof(type), signed long) || \
+ __builtin_types_compatible_p(typeof(type), signed long long))
+
+/* Evaluates to (void) if _A or _B are not constant or of different types (being integers of different sizes
+ * is also OK as long as the signedness matches) */
+#define CONST_MAX(_A, _B) \
+ (__builtin_choose_expr( \
+ __builtin_constant_p(_A) && \
+ __builtin_constant_p(_B) && \
+ (__builtin_types_compatible_p(typeof(_A), typeof(_B)) || \
+ (IS_UNSIGNED_INTEGER_TYPE(_A) && IS_UNSIGNED_INTEGER_TYPE(_B)) || \
+ (IS_SIGNED_INTEGER_TYPE(_A) && IS_SIGNED_INTEGER_TYPE(_B))), \
+ ((_A) > (_B)) ? (_A) : (_B), \
+ VOID_0))
+
+/* takes two types and returns the size of the larger one */
+#define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; }))
+
+#define MAX3(x, y, z) \
+ ({ \
+ const typeof(x) _c = MAX(x, y); \
+ MAX(_c, z); \
+ })
+
+#define MAX4(x, y, z, a) \
+ ({ \
+ const typeof(x) _d = MAX3(x, y, z); \
+ MAX(_d, a); \
+ })
+
+#undef MIN
+#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b))
+#define __MIN(aq, a, bq, b) \
+ ({ \
+ const typeof(a) UNIQ_T(A, aq) = (a); \
+ const typeof(b) UNIQ_T(B, bq) = (b); \
+ UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
+ })
+
+/* evaluates to (void) if _A or _B are not constant or of different types */
+#define CONST_MIN(_A, _B) \
+ (__builtin_choose_expr( \
+ __builtin_constant_p(_A) && \
+ __builtin_constant_p(_B) && \
+ __builtin_types_compatible_p(typeof(_A), typeof(_B)), \
+ ((_A) < (_B)) ? (_A) : (_B), \
+ VOID_0))
+
+#define MIN3(x, y, z) \
+ ({ \
+ const typeof(x) _c = MIN(x, y); \
+ MIN(_c, z); \
+ })
+
+/* Returns true if the passed integer is a positive power of two */
+#define CONST_ISPOWEROF2(x) \
+ ((x) > 0 && ((x) & ((x) - 1)) == 0)
+
+#define ISPOWEROF2(x) \
+ __builtin_choose_expr( \
+ __builtin_constant_p(x), \
+ CONST_ISPOWEROF2(x), \
+ ({ \
+ const typeof(x) _x = (x); \
+ CONST_ISPOWEROF2(_x); \
+ }))
+
+#define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b))
+#define __LESS_BY(aq, a, bq, b) \
+ ({ \
+ const typeof(a) UNIQ_T(A, aq) = (a); \
+ const typeof(b) UNIQ_T(B, bq) = (b); \
+ UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) - UNIQ_T(B, bq) : 0; \
+ })
+
+#define CMP(a, b) __CMP(UNIQ, (a), UNIQ, (b))
+#define __CMP(aq, a, bq, b) \
+ ({ \
+ const typeof(a) UNIQ_T(A, aq) = (a); \
+ const typeof(b) UNIQ_T(B, bq) = (b); \
+ UNIQ_T(A, aq) < UNIQ_T(B, bq) ? -1 : \
+ UNIQ_T(A, aq) > UNIQ_T(B, bq) ? 1 : 0; \
+ })
+
+#undef CLAMP
+#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high))
+#define __CLAMP(xq, x, lowq, low, highq, high) \
+ ({ \
+ const typeof(x) UNIQ_T(X, xq) = (x); \
+ const typeof(low) UNIQ_T(LOW, lowq) = (low); \
+ const typeof(high) UNIQ_T(HIGH, highq) = (high); \
+ UNIQ_T(X, xq) > UNIQ_T(HIGH, highq) ? \
+ UNIQ_T(HIGH, highq) : \
+ UNIQ_T(X, xq) < UNIQ_T(LOW, lowq) ? \
+ UNIQ_T(LOW, lowq) : \
+ UNIQ_T(X, xq); \
+ })
+
+/* [(x + y - 1) / y] suffers from an integer overflow, even though the
+ * computation should be possible in the given type. Therefore, we use
+ * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the
+ * quotient and the remainder, so both should be equally fast. */
+#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, (x), UNIQ, (y))
+#define __DIV_ROUND_UP(xq, x, yq, y) \
+ ({ \
+ const typeof(x) UNIQ_T(X, xq) = (x); \
+ const typeof(y) UNIQ_T(Y, yq) = (y); \
+ (UNIQ_T(X, xq) / UNIQ_T(Y, yq) + !!(UNIQ_T(X, xq) % UNIQ_T(Y, yq))); \
+ })
+
+#define CASE_F_1(X) case X:
+#define CASE_F_2(X, ...) case X: CASE_F_1( __VA_ARGS__)
+#define CASE_F_3(X, ...) case X: CASE_F_2( __VA_ARGS__)
+#define CASE_F_4(X, ...) case X: CASE_F_3( __VA_ARGS__)
+#define CASE_F_5(X, ...) case X: CASE_F_4( __VA_ARGS__)
+#define CASE_F_6(X, ...) case X: CASE_F_5( __VA_ARGS__)
+#define CASE_F_7(X, ...) case X: CASE_F_6( __VA_ARGS__)
+#define CASE_F_8(X, ...) case X: CASE_F_7( __VA_ARGS__)
+#define CASE_F_9(X, ...) case X: CASE_F_8( __VA_ARGS__)
+#define CASE_F_10(X, ...) case X: CASE_F_9( __VA_ARGS__)
+#define CASE_F_11(X, ...) case X: CASE_F_10( __VA_ARGS__)
+#define CASE_F_12(X, ...) case X: CASE_F_11( __VA_ARGS__)
+#define CASE_F_13(X, ...) case X: CASE_F_12( __VA_ARGS__)
+#define CASE_F_14(X, ...) case X: CASE_F_13( __VA_ARGS__)
+#define CASE_F_15(X, ...) case X: CASE_F_14( __VA_ARGS__)
+#define CASE_F_16(X, ...) case X: CASE_F_15( __VA_ARGS__)
+#define CASE_F_17(X, ...) case X: CASE_F_16( __VA_ARGS__)
+#define CASE_F_18(X, ...) case X: CASE_F_17( __VA_ARGS__)
+#define CASE_F_19(X, ...) case X: CASE_F_18( __VA_ARGS__)
+#define CASE_F_20(X, ...) case X: CASE_F_19( __VA_ARGS__)
+
+#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME
+#define FOR_EACH_MAKE_CASE(...) \
+ GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \
+ CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
+ (__VA_ARGS__)
+
+#define IN_SET(x, ...) \
+ ({ \
+ bool _found = false; \
+ /* If the build breaks in the line below, you need to extend the case macros. We use typeof(+x) \
+ * here to widen the type of x if it is a bit-field as this would otherwise be illegal. */ \
+ static const typeof(+x) __assert_in_set[] _unused_ = { __VA_ARGS__ }; \
+ assert_cc(ELEMENTSOF(__assert_in_set) <= 20); \
+ switch (x) { \
+ FOR_EACH_MAKE_CASE(__VA_ARGS__) \
+ _found = true; \
+ break; \
+ default: \
+ break; \
+ } \
+ _found; \
+ })
+
+/* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time
+ * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */
+#define TAKE_PTR(ptr) \
+ ({ \
+ typeof(ptr) *_pptr_ = &(ptr); \
+ typeof(ptr) _ptr_ = *_pptr_; \
+ *_pptr_ = NULL; \
+ _ptr_; \
+ })
+
+/*
+ * STRLEN - return the length of a string literal, minus the trailing NUL byte.
+ * Contrary to strlen(), this is a constant expression.
+ * @x: a string literal.
+ */
+#define STRLEN(x) (sizeof(""x"") - sizeof(typeof(x[0])))
+
+#define mfree(memory) \
+ ({ \
+ free(memory); \
+ (typeof(memory)) NULL; \
+ })
+
+static inline size_t ALIGN_TO(size_t l, size_t ali) {
+ assert(ISPOWEROF2(ali));
+
+ if (l > SIZE_MAX - (ali - 1))
+ return SIZE_MAX; /* indicate overflow */
+
+ return ((l + ali - 1) & ~(ali - 1));
+}
+
+#define ALIGN4(l) ALIGN_TO(l, 4)
+#define ALIGN8(l) ALIGN_TO(l, 8)
+#ifndef SD_BOOT
+/* libefi also provides ALIGN, and we do not use them in sd-boot explicitly. */
+#define ALIGN(l) ALIGN_TO(l, sizeof(void*))
+#define ALIGN_PTR(p) ((void*) ALIGN((uintptr_t) (p)))
+#endif
+
+/* Same as ALIGN_TO but callable in constant contexts. */
+#define CONST_ALIGN_TO(l, ali) \
+ __builtin_choose_expr( \
+ __builtin_constant_p(l) && \
+ __builtin_constant_p(ali) && \
+ CONST_ISPOWEROF2(ali) && \
+ (l <= SIZE_MAX - (ali - 1)), /* overflow? */ \
+ ((l) + (ali) - 1) & ~((ali) - 1), \
+ VOID_0)
+
+#define UPDATE_FLAG(orig, flag, b) \
+ ((b) ? ((orig) | (flag)) : ((orig) & ~(flag)))
+#define SET_FLAG(v, flag, b) \
+ (v) = UPDATE_FLAG(v, flag, b)
+#define FLAGS_SET(v, flags) \
+ ((~(v) & (flags)) == 0)
diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build
new file mode 100644
index 0000000..3810d6b
--- /dev/null
+++ b/src/fundamental/meson.build
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+fundamental_path = meson.current_source_dir()
+
+fundamental_headers = files(
+ 'bootspec-fundamental.h',
+ 'efivars-fundamental.h',
+ 'macro-fundamental.h',
+ 'sha256.h',
+ 'string-util-fundamental.h',
+ 'tpm-pcr.h',
+)
+
+# for sd-boot
+fundamental_source_paths = files(
+ 'bootspec-fundamental.c',
+ 'efivars-fundamental.c',
+ 'sha256.c',
+ 'string-util-fundamental.c',
+ 'tpm-pcr.c',
+)
+
+# for libbasic
+fundamental_sources = fundamental_source_paths + fundamental_headers
diff --git a/src/fundamental/sbat.h b/src/fundamental/sbat.h
new file mode 100644
index 0000000..b3c09dc
--- /dev/null
+++ b/src/fundamental/sbat.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifdef SBAT_DISTRO
+# define SBAT_SECTION_TEXT \
+ "sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md\n" \
+ SBAT_PROJECT ",1,The systemd Developers," SBAT_PROJECT "," PROJECT_VERSION "," PROJECT_URL "\n" \
+ SBAT_PROJECT "." SBAT_DISTRO "," STRINGIFY(SBAT_DISTRO_GENERATION) "," SBAT_DISTRO_SUMMARY "," SBAT_DISTRO_PKGNAME "," SBAT_DISTRO_VERSION "," SBAT_DISTRO_URL "\n"
+#endif
diff --git a/src/fundamental/sha256.c b/src/fundamental/sha256.c
new file mode 100644
index 0000000..9b71764
--- /dev/null
+++ b/src/fundamental/sha256.c
@@ -0,0 +1,297 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* Stolen from glibc and converted to our style. In glibc it comes with the following copyright blurb: */
+
+/* Functions to compute SHA256 message digest of files or memory blocks.
+ according to the definition of SHA256 in FIPS 180-2.
+ Copyright (C) 2007-2022 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdbool.h>
+#ifdef SD_BOOT
+# include "efi-string.h"
+#else
+# include <string.h>
+#endif
+
+#include "macro-fundamental.h"
+#include "sha256.h"
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+# define SWAP(n) \
+ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+# define SWAP64(n) \
+ (((n) << 56) \
+ | (((n) & 0xff00) << 40) \
+ | (((n) & 0xff0000) << 24) \
+ | (((n) & 0xff000000) << 8) \
+ | (((n) >> 8) & 0xff000000) \
+ | (((n) >> 24) & 0xff0000) \
+ | (((n) >> 40) & 0xff00) \
+ | ((n) >> 56))
+#else
+# define SWAP(n) (n)
+# define SWAP64(n) (n)
+#endif
+
+/* The condition below is from glibc's string/string-inline.c.
+ * See definition of _STRING_INLINE_unaligned. */
+#if !defined(__mc68020__) && !defined(__s390__) && !defined(__i386__)
+# define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__(uint32_t) != 0)
+#else
+# define UNALIGNED_P(p) false
+#endif
+
+/* This array contains the bytes used to pad the buffer to the next
+ 64-byte boundary. (FIPS 180-2:5.1.1) */
+static const uint8_t fillbuf[64] = {
+ 0x80, 0 /* , 0, 0, ... */
+};
+
+/* Constants for SHA256 from FIPS 180-2:4.2.2. */
+static const uint32_t K[64] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+static void sha256_process_block(const void *, size_t, struct sha256_ctx *);
+
+/* Initialize structure containing state of computation.
+ (FIPS 180-2:5.3.2) */
+void sha256_init_ctx(struct sha256_ctx *ctx) {
+ assert(ctx);
+
+ ctx->H[0] = 0x6a09e667;
+ ctx->H[1] = 0xbb67ae85;
+ ctx->H[2] = 0x3c6ef372;
+ ctx->H[3] = 0xa54ff53a;
+ ctx->H[4] = 0x510e527f;
+ ctx->H[5] = 0x9b05688c;
+ ctx->H[6] = 0x1f83d9ab;
+ ctx->H[7] = 0x5be0cd19;
+
+ ctx->total64 = 0;
+ ctx->buflen = 0;
+}
+
+/* Process the remaining bytes in the internal buffer and the usual
+ prolog according to the standard and write the result to RESBUF. */
+uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]) {
+ /* Take yet unprocessed bytes into account. */
+ uint32_t bytes = ctx->buflen;
+ size_t pad;
+
+ assert(ctx);
+ assert(resbuf);
+
+ /* Now count remaining bytes. */
+ ctx->total64 += bytes;
+
+ pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+ memcpy(&ctx->buffer[bytes], fillbuf, pad);
+
+ /* Put the 64-bit file length in *bits* at the end of the buffer. */
+ ctx->buffer32[(bytes + pad + 4) / 4] = SWAP(ctx->total[TOTAL64_low] << 3);
+ ctx->buffer32[(bytes + pad) / 4] = SWAP((ctx->total[TOTAL64_high] << 3)
+ | (ctx->total[TOTAL64_low] >> 29));
+
+ /* Process last bytes. */
+ sha256_process_block(ctx->buffer, bytes + pad + 8, ctx);
+
+ /* Put result from CTX in first 32 bytes following RESBUF. */
+ for (size_t i = 0; i < 8; ++i)
+ if (UNALIGNED_P(resbuf))
+ memcpy(resbuf + i * sizeof(uint32_t), (uint32_t[]) { SWAP(ctx->H[i]) }, sizeof(uint32_t));
+ else
+ ((uint32_t *) resbuf)[i] = SWAP(ctx->H[i]);
+
+ return resbuf;
+}
+
+void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx) {
+ assert(buffer);
+ assert(ctx);
+
+ /* When we already have some bits in our internal buffer concatenate
+ both inputs first. */
+
+ if (ctx->buflen != 0) {
+ size_t left_over = ctx->buflen;
+ size_t add = 128 - left_over > len ? len : 128 - left_over;
+
+ memcpy(&ctx->buffer[left_over], buffer, add);
+ ctx->buflen += add;
+
+ if (ctx->buflen > 64) {
+ sha256_process_block(ctx->buffer, ctx->buflen & ~63, ctx);
+
+ ctx->buflen &= 63;
+ /* The regions in the following copy operation cannot overlap. */
+ memcpy(ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+ ctx->buflen);
+ }
+
+ buffer = (const char *) buffer + add;
+ len -= add;
+ }
+
+ /* Process available complete blocks. */
+ if (len >= 64) {
+ if (UNALIGNED_P(buffer))
+ while (len > 64) {
+ memcpy(ctx->buffer, buffer, 64);
+ sha256_process_block(ctx->buffer, 64, ctx);
+ buffer = (const char *) buffer + 64;
+ len -= 64;
+ }
+ else {
+ sha256_process_block(buffer, len & ~63, ctx);
+ buffer = (const char *) buffer + (len & ~63);
+ len &= 63;
+ }
+ }
+
+ /* Move remaining bytes into internal buffer. */
+ if (len > 0) {
+ size_t left_over = ctx->buflen;
+
+ memcpy(&ctx->buffer[left_over], buffer, len);
+ left_over += len;
+ if (left_over >= 64) {
+ sha256_process_block(ctx->buffer, 64, ctx);
+ left_over -= 64;
+ memcpy(ctx->buffer, &ctx->buffer[64], left_over);
+ }
+ ctx->buflen = left_over;
+ }
+}
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+ It is assumed that LEN % 64 == 0. */
+static void sha256_process_block(const void *buffer, size_t len, struct sha256_ctx *ctx) {
+ const uint32_t *words = ASSERT_PTR(buffer);
+ size_t nwords = len / sizeof(uint32_t);
+
+ assert(ctx);
+
+ uint32_t a = ctx->H[0];
+ uint32_t b = ctx->H[1];
+ uint32_t c = ctx->H[2];
+ uint32_t d = ctx->H[3];
+ uint32_t e = ctx->H[4];
+ uint32_t f = ctx->H[5];
+ uint32_t g = ctx->H[6];
+ uint32_t h = ctx->H[7];
+
+ /* First increment the byte count. FIPS 180-2 specifies the possible
+ length of the file up to 2^64 bits. Here we only compute the
+ number of bytes. */
+ ctx->total64 += len;
+
+ /* Process all bytes in the buffer with 64 bytes in each round of
+ the loop. */
+ while (nwords > 0) {
+ uint32_t W[64];
+ uint32_t a_save = a;
+ uint32_t b_save = b;
+ uint32_t c_save = c;
+ uint32_t d_save = d;
+ uint32_t e_save = e;
+ uint32_t f_save = f;
+ uint32_t g_save = g;
+ uint32_t h_save = h;
+
+ /* Operators defined in FIPS 180-2:4.1.2. */
+#define Ch(x, y, z) ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22))
+#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25))
+#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3))
+#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10))
+
+ /* It is unfortunate that C does not provide an operator for
+ cyclic rotation. Hope the C compiler is smart enough. */
+#define CYCLIC(w, s) ((w >> s) | (w << (32 - s)))
+
+ /* Compute the message schedule according to FIPS 180-2:6.2.2 step 2. */
+ for (size_t t = 0; t < 16; ++t) {
+ W[t] = SWAP (*words);
+ ++words;
+ }
+ for (size_t t = 16; t < 64; ++t)
+ W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16];
+
+ /* The actual computation according to FIPS 180-2:6.2.2 step 3. */
+ for (size_t t = 0; t < 64; ++t) {
+ uint32_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t];
+ uint32_t T2 = S0 (a) + Maj (a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + T1;
+ d = c;
+ c = b;
+ b = a;
+ a = T1 + T2;
+ }
+
+ /* Add the starting values of the context according to FIPS 180-2:6.2.2
+ step 4. */
+ a += a_save;
+ b += b_save;
+ c += c_save;
+ d += d_save;
+ e += e_save;
+ f += f_save;
+ g += g_save;
+ h += h_save;
+
+ /* Prepare for the next round. */
+ nwords -= 16;
+ }
+
+ /* Put checksum in context given as argument. */
+ ctx->H[0] = a;
+ ctx->H[1] = b;
+ ctx->H[2] = c;
+ ctx->H[3] = d;
+ ctx->H[4] = e;
+ ctx->H[5] = f;
+ ctx->H[6] = g;
+ ctx->H[7] = h;
+}
+
+uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]) {
+ struct sha256_ctx ctx;
+ sha256_init_ctx(&ctx);
+ sha256_process_bytes(buffer, sz, &ctx);
+ return sha256_finish_ctx(&ctx, result);
+}
diff --git a/src/fundamental/sha256.h b/src/fundamental/sha256.h
new file mode 100644
index 0000000..d30c81e
--- /dev/null
+++ b/src/fundamental/sha256.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SHA256_DIGEST_SIZE 32
+
+struct sha256_ctx {
+ uint32_t H[8];
+
+ union {
+ uint64_t total64;
+#define TOTAL64_low (1 - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+#define TOTAL64_high (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+ uint32_t total[2];
+ };
+
+ uint32_t buflen;
+
+ union {
+ uint8_t buffer[128]; /* NB: always correctly aligned for UINT32. */
+ uint32_t buffer32[32];
+ uint64_t buffer64[16];
+ };
+};
+
+void sha256_init_ctx(struct sha256_ctx *ctx);
+uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]);
+void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx);
+
+uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]);
+
+#define SHA256_DIRECT(buffer, sz) sha256_direct(buffer, sz, (uint8_t[SHA256_DIGEST_SIZE]) {})
diff --git a/src/fundamental/string-util-fundamental.c b/src/fundamental/string-util-fundamental.c
new file mode 100644
index 0000000..11701eb
--- /dev/null
+++ b/src/fundamental/string-util-fundamental.c
@@ -0,0 +1,230 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef SD_BOOT
+# include <ctype.h>
+#endif
+
+#include "macro-fundamental.h"
+#include "string-util-fundamental.h"
+
+sd_char *startswith(const sd_char *s, const sd_char *prefix) {
+ size_t l;
+
+ assert(s);
+ assert(prefix);
+
+ l = strlen(prefix);
+ if (!strneq(s, prefix, l))
+ return NULL;
+
+ return (sd_char*) s + l;
+}
+
+#ifndef SD_BOOT
+sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) {
+ size_t l;
+
+ assert(s);
+ assert(prefix);
+
+ l = strlen(prefix);
+ if (!strncaseeq(s, prefix, l))
+ return NULL;
+
+ return (sd_char*) s + l;
+}
+#endif
+
+sd_char* endswith(const sd_char *s, const sd_char *postfix) {
+ size_t sl, pl;
+
+ assert(s);
+ assert(postfix);
+
+ sl = strlen(s);
+ pl = strlen(postfix);
+
+ if (pl == 0)
+ return (sd_char*) s + sl;
+
+ if (sl < pl)
+ return NULL;
+
+ if (strcmp(s + sl - pl, postfix) != 0)
+ return NULL;
+
+ return (sd_char*) s + sl - pl;
+}
+
+sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) {
+ size_t sl, pl;
+
+ assert(s);
+ assert(postfix);
+
+ sl = strlen(s);
+ pl = strlen(postfix);
+
+ if (pl == 0)
+ return (sd_char*) s + sl;
+
+ if (sl < pl)
+ return NULL;
+
+ if (strcasecmp(s + sl - pl, postfix) != 0)
+ return NULL;
+
+ return (sd_char*) s + sl - pl;
+}
+
+static bool is_valid_version_char(sd_char a) {
+ return ascii_isdigit(a) || ascii_isalpha(a) || IN_SET(a, '~', '-', '^', '.');
+}
+
+int strverscmp_improved(const sd_char *a, const sd_char *b) {
+ /* This function is similar to strverscmp(3), but it treats '-' and '.' as separators.
+ *
+ * The logic is based on rpm's rpmvercmp(), but unlike rpmvercmp(), it distiguishes e.g.
+ * '123a' and '123.a', with '123a' being newer.
+ *
+ * It allows direct comparison of strings which contain both a version and a release; e.g.
+ * '247.2-3.1.fc33.x86_64' or '5.11.0-0.rc5.20210128git76c057c84d28.137.fc34'.
+ *
+ * The input string is split into segments. Each segment is numeric or alphabetic, and may be
+ * prefixed with the following:
+ * '~' : used for pre-releases, a segment prefixed with this is the oldest,
+ * '-' : used for the separator between version and release,
+ * '^' : used for patched releases, a segment with this is newer than one with '-'.
+ * '.' : used for point releases.
+ * Note that no prefix segment is the newest. All non-supported characters are dropped, and
+ * handled as a separator of segments, e.g., '123_a' is equivalent to '123a'.
+ *
+ * By using this, version strings can be sorted like following:
+ * (older) 122.1
+ * ^ 123~rc1-1
+ * | 123
+ * | 123-a
+ * | 123-a.1
+ * | 123-1
+ * | 123-1.1
+ * | 123^post1
+ * | 123.a-1
+ * | 123.1-1
+ * v 123a-1
+ * (newer) 124-1
+ */
+
+ a = strempty(a);
+ b = strempty(b);
+
+ for (;;) {
+ const sd_char *aa, *bb;
+ int r;
+
+ /* Drop leading invalid characters. */
+ while (*a != '\0' && !is_valid_version_char(*a))
+ a++;
+ while (*b != '\0' && !is_valid_version_char(*b))
+ b++;
+
+ /* Handle '~'. Used for pre-releases, e.g. 123~rc1, or 4.5~alpha1 */
+ if (*a == '~' || *b == '~') {
+ /* The string prefixed with '~' is older. */
+ r = CMP(*a != '~', *b != '~');
+ if (r != 0)
+ return r;
+
+ /* Now both strings are prefixed with '~'. Compare remaining strings. */
+ a++;
+ b++;
+ }
+
+ /* If at least one string reaches the end, then longer is newer.
+ * Note that except for '~' prefixed segments, a string which has more segments is newer.
+ * So, this check must be after the '~' check. */
+ if (*a == '\0' || *b == '\0')
+ return CMP(*a, *b);
+
+ /* Handle '-', which separates version and release, e.g 123.4-3.1.fc33.x86_64 */
+ if (*a == '-' || *b == '-') {
+ /* The string prefixed with '-' is older (e.g., 123-9 vs 123.1-1) */
+ r = CMP(*a != '-', *b != '-');
+ if (r != 0)
+ return r;
+
+ a++;
+ b++;
+ }
+
+ /* Handle '^'. Used for patched release. */
+ if (*a == '^' || *b == '^') {
+ r = CMP(*a != '^', *b != '^');
+ if (r != 0)
+ return r;
+
+ a++;
+ b++;
+ }
+
+ /* Handle '.'. Used for point releases. */
+ if (*a == '.' || *b == '.') {
+ r = CMP(*a != '.', *b != '.');
+ if (r != 0)
+ return r;
+
+ a++;
+ b++;
+ }
+
+ if (ascii_isdigit(*a) || ascii_isdigit(*b)) {
+ /* Find the leading numeric segments. One may be an empty string. So,
+ * numeric segments are always newer than alpha segments. */
+ for (aa = a; ascii_isdigit(*aa); aa++)
+ ;
+ for (bb = b; ascii_isdigit(*bb); bb++)
+ ;
+
+ /* Check if one of the strings was empty, but the other not. */
+ r = CMP(a != aa, b != bb);
+ if (r != 0)
+ return r;
+
+ /* Skip leading '0', to make 00123 equivalent to 123. */
+ while (*a == '0')
+ a++;
+ while (*b == '0')
+ b++;
+
+ /* To compare numeric segments without parsing their values, first compare the
+ * lengths of the segments. Eg. 12345 vs 123, longer is newer. */
+ r = CMP(aa - a, bb - b);
+ if (r != 0)
+ return r;
+
+ /* Then, compare them as strings. */
+ r = CMP(strncmp(a, b, aa - a), 0);
+ if (r != 0)
+ return r;
+ } else {
+ /* Find the leading non-numeric segments. */
+ for (aa = a; ascii_isalpha(*aa); aa++)
+ ;
+ for (bb = b; ascii_isalpha(*bb); bb++)
+ ;
+
+ /* Note that the segments are usually not NUL-terminated. */
+ r = CMP(strncmp(a, b, MIN(aa - a, bb - b)), 0);
+ if (r != 0)
+ return r;
+
+ /* Longer is newer, e.g. abc vs abcde. */
+ r = CMP(aa - a, bb - b);
+ if (r != 0)
+ return r;
+ }
+
+ /* The current segments are equivalent. Let's move to the next one. */
+ a = aa;
+ b = bb;
+ }
+}
diff --git a/src/fundamental/string-util-fundamental.h b/src/fundamental/string-util-fundamental.h
new file mode 100644
index 0000000..d823147
--- /dev/null
+++ b/src/fundamental/string-util-fundamental.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#ifdef SD_BOOT
+# include <efi.h>
+# include <efilib.h>
+# include "efi-string.h"
+#else
+# include <string.h>
+#endif
+
+#include "macro-fundamental.h"
+
+#ifdef SD_BOOT
+# define strlen strlen16
+# define strcmp strcmp16
+# define strncmp strncmp16
+# define strcasecmp strcasecmp16
+# define strncasecmp strncasecmp16
+# define STR_C(str) (L ## str)
+typedef char16_t sd_char;
+#else
+# define STR_C(str) (str)
+typedef char sd_char;
+#endif
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0)
+#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0)
+#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0)
+
+static inline int strcmp_ptr(const sd_char *a, const sd_char *b) {
+ if (a && b)
+ return strcmp(a, b);
+
+ return CMP(a, b);
+}
+
+static inline int strcasecmp_ptr(const sd_char *a, const sd_char *b) {
+ if (a && b)
+ return strcasecmp(a, b);
+
+ return CMP(a, b);
+}
+
+static inline bool streq_ptr(const sd_char *a, const sd_char *b) {
+ return strcmp_ptr(a, b) == 0;
+}
+
+static inline bool strcaseeq_ptr(const sd_char *a, const sd_char *b) {
+ return strcasecmp_ptr(a, b) == 0;
+}
+
+static inline size_t strlen_ptr(const sd_char *s) {
+ if (!s)
+ return 0;
+
+ return strlen(s);
+}
+
+sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_;
+#ifndef SD_BOOT
+sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_;
+#endif
+sd_char *endswith(const sd_char *s, const sd_char *postfix) _pure_;
+sd_char *endswith_no_case(const sd_char *s, const sd_char *postfix) _pure_;
+
+static inline bool isempty(const sd_char *a) {
+ return !a || a[0] == '\0';
+}
+
+static inline const sd_char *strempty(const sd_char *s) {
+ return s ?: STR_C("");
+}
+
+static inline const sd_char *yes_no(bool b) {
+ return b ? STR_C("yes") : STR_C("no");
+}
+
+static inline const sd_char* comparison_operator(int result) {
+ return result < 0 ? STR_C("<") : result > 0 ? STR_C(">") : STR_C("==");
+}
+
+int strverscmp_improved(const sd_char *a, const sd_char *b);
+
+/* Like startswith(), but operates on arbitrary memory blocks */
+static inline void *memory_startswith(const void *p, size_t sz, const sd_char *token) {
+ assert(token);
+
+ size_t n = strlen(token) * sizeof(sd_char);
+ if (sz < n)
+ return NULL;
+
+ assert(p);
+
+ if (memcmp(p, token, n) != 0)
+ return NULL;
+
+ return (uint8_t*) p + n;
+}
+
+#define _STRV_FOREACH(s, l, i) \
+ for (typeof(*(l)) *s, *i = (l); (s = i) && *i; i++)
+
+#define STRV_FOREACH(s, l) \
+ _STRV_FOREACH(s, l, UNIQ_T(i, UNIQ))
+
+static inline bool ascii_isdigit(sd_char a) {
+ /* A pure ASCII, locale independent version of isdigit() */
+ return a >= '0' && a <= '9';
+}
+
+static inline bool ascii_ishex(sd_char a) {
+ return ascii_isdigit(a) || (a >= 'a' && a <= 'f') || (a >= 'A' && a <= 'F');
+}
+
+static inline bool ascii_isalpha(sd_char a) {
+ /* A pure ASCII, locale independent version of isalpha() */
+ return (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
+}
diff --git a/src/fundamental/tpm-pcr.c b/src/fundamental/tpm-pcr.c
new file mode 100644
index 0000000..7609d83
--- /dev/null
+++ b/src/fundamental/tpm-pcr.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stddef.h>
+
+#include "tpm-pcr.h"
+
+const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = {
+ [UNIFIED_SECTION_LINUX] = ".linux",
+ [UNIFIED_SECTION_OSREL] = ".osrel",
+ [UNIFIED_SECTION_CMDLINE] = ".cmdline",
+ [UNIFIED_SECTION_INITRD] = ".initrd",
+ [UNIFIED_SECTION_SPLASH] = ".splash",
+ [UNIFIED_SECTION_DTB] = ".dtb",
+ [UNIFIED_SECTION_PCRSIG] = ".pcrsig",
+ [UNIFIED_SECTION_PCRPKEY] = ".pcrpkey",
+ NULL,
+};
diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h
new file mode 100644
index 0000000..235d484
--- /dev/null
+++ b/src/fundamental/tpm-pcr.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "macro-fundamental.h"
+
+/* The various TPM PCRs we measure into from sd-stub and sd-boot. */
+
+/* This TPM PCR is where we extend the sd-stub "payloads" into, before using them. i.e. the kernel ELF image,
+ * embedded initrd, and so on. In contrast to PCR 4 (which also contains this data, given the whole
+ * surrounding PE image is measured into it) this should be reasonably pre-calculatable, because it *only*
+ * consists of static data from the kernel PE image. */
+#define TPM_PCR_INDEX_KERNEL_IMAGE 11U
+
+/* This TPM PCR is where sd-stub extends the kernel command line and any passed credentials into. */
+#define TPM_PCR_INDEX_KERNEL_PARAMETERS 12U
+
+/* sd-stub used to write the kernel command line/credentials into PCR 8, in systemd <= 250. Let's provide for
+ * some compatibility. (Remove in 2023!) */
+#if EFI_TPM_PCR_COMPAT
+#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT 8U
+#else
+#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT UINT32_MAX
+#endif
+
+/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */
+#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U
+
+/* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in
+ * which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */
+typedef enum UnifiedSection {
+ UNIFIED_SECTION_LINUX,
+ UNIFIED_SECTION_OSREL,
+ UNIFIED_SECTION_CMDLINE,
+ UNIFIED_SECTION_INITRD,
+ UNIFIED_SECTION_SPLASH,
+ UNIFIED_SECTION_DTB,
+ UNIFIED_SECTION_PCRSIG,
+ UNIFIED_SECTION_PCRPKEY,
+ _UNIFIED_SECTION_MAX,
+} UnifiedSection;
+
+extern const char* const unified_sections[_UNIFIED_SECTION_MAX + 1];
+
+static inline bool unified_section_measure(UnifiedSection section) {
+ /* Don't include the PCR signature in the PCR measurements, since they sign the expected result of
+ * the measurement, and hence shouldn't be input to it. */
+ return section >= 0 && section < _UNIFIED_SECTION_MAX && section != UNIFIED_SECTION_PCRSIG;
+}