diff options
Diffstat (limited to 'src/fundamental')
-rw-r--r-- | src/fundamental/bootspec-fundamental.c | 62 | ||||
-rw-r--r-- | src/fundamental/bootspec-fundamental.h | 17 | ||||
-rw-r--r-- | src/fundamental/efivars-fundamental.c | 37 | ||||
-rw-r--r-- | src/fundamental/efivars-fundamental.h | 39 | ||||
-rw-r--r-- | src/fundamental/macro-fundamental.h | 354 | ||||
-rw-r--r-- | src/fundamental/meson.build | 24 | ||||
-rw-r--r-- | src/fundamental/sbat.h | 8 | ||||
-rw-r--r-- | src/fundamental/sha256.c | 297 | ||||
-rw-r--r-- | src/fundamental/sha256.h | 34 | ||||
-rw-r--r-- | src/fundamental/string-util-fundamental.c | 230 | ||||
-rw-r--r-- | src/fundamental/string-util-fundamental.h | 120 | ||||
-rw-r--r-- | src/fundamental/tpm-pcr.c | 17 | ||||
-rw-r--r-- | src/fundamental/tpm-pcr.h | 48 |
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; +} |