diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..398849d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Define hexdump textconv with "git config diff.efivars.textconv hd" +tests/efivars/* diff=efivars diff --git a/.gitignore b/.gitignore index be16645..986c9ab 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ config.status config.sub configure depcomp +eos-update-efi-uuid fallback-fb-setup/fallback-fb-setup install-sh missing diff --git a/Makefile.am b/Makefile.am index 8382b16..be61e58 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,6 +2,8 @@ SUBDIRS = dracut factory-reset fallback-fb-setup flatpak-repos nvidia psi-monito EXTRA_DIST = debian .flake8 CLEANFILES = +AM_CFLAGS = -Wall -Werror + # Install systemd units, generators, preset files, and udev rules # under $prefix for distcheck AM_DISTCHECK_CONFIGURE_FLAGS = \ @@ -92,6 +94,14 @@ dist_sbin_SCRIPTS = \ eos-update-flatpak-repos \ $(NULL) +sbin_PROGRAMS = \ + eos-update-efi-uuid \ + $(NULL) + +eos_update_efi_uuid_SOURCES = eos-update-efi-uuid.c +eos_update_efi_uuid_CFLAGS = $(AM_CFLAGS) $(EFIBOOT_CFLAGS) +eos_update_efi_uuid_LDADD = $(EFIBOOT_LIBS) + dist_libexec_SCRIPTS = \ eos-migrate-chromium-profile \ eos-migrate-firefox-profile \ diff --git a/configure.ac b/configure.ac index 854e112..ab3bc11 100644 --- a/configure.ac +++ b/configure.ac @@ -120,6 +120,8 @@ case $target_cpu in esac AC_SUBST(FLATPAK_ARCH) +PKG_CHECK_MODULES([EFIBOOT], [efiboot efivar]) + AC_CONFIG_FILES([ Makefile dracut/Makefile diff --git a/debian/control b/debian/control index 9bf1eaf..555c185 100644 --- a/debian/control +++ b/debian/control @@ -10,11 +10,14 @@ Build-Depends: automake, gir1.2-ostree-1.0 , gir1.2-eosmetrics-0 , libdbus-1-dev, + libefiboot-dev, + libefivar-dev, libpolkit-gobject-1-dev, pkg-config, python3 , python3-gi , python3-parted , + python3-pytest , python3-systemd , systemd, udev, diff --git a/dracut/repartition/endless-repartition.sh b/dracut/repartition/endless-repartition.sh index d116b95..a4c93bb 100755 --- a/dracut/repartition/endless-repartition.sh +++ b/dracut/repartition/endless-repartition.sh @@ -103,6 +103,12 @@ else marker=$(sfdisk --force --part-attrs $root_disk $partno) fi +# Read the ESP's current UUID so we can update load options below. +orig_esp_uuid= +if [ "$pt_label" = "gpt" ]; then + orig_esp_uuid=$(sfdisk --force --part-uuid $root_disk 1) +fi + case "$marker" in *GUID:55* | dd) ;; @@ -199,6 +205,15 @@ udevadm settle [ "$ret" != "0" ] && exit 0 +# Read the ESP's new UUID and update any invalidated load options. +new_esp_uuid= +if [ "$pt_label" = "gpt" ]; then + new_esp_uuid=$(sfdisk --force --part-uuid $root_disk 1) +fi +if [ -d /sys/firmware/efi ] && [ -n "$orig_esp_uuid" ] && [ -n "$new_esp_uuid" ]; then + eos-update-efi-uuid -v "$orig_esp_uuid" "$new_esp_uuid" +fi + # Loop devices need a prod if [ -n "$using_loop" ]; then partx --update --verbose $root_disk @@ -214,8 +229,7 @@ udevadm settle # If we're an sd-booting image we need to fix-up the copy of the # ESP UUID that sd-boot gave us so systemd mounts the ESP esp_var=$(echo /sys/firmware/efi/efivars/LoaderDevicePartUUID*) -if [ -f "${esp_var}" ]; then - new_esp_uuid=$(sfdisk $root_disk --part-uuid 1) +if [ -f "${esp_var}" ] && [ -n "${new_esp_uuid}" ]; then #We need to start with 0x06 0x00 0x00 0x00 and end with 0x00 0x00 #iconv will add the extra 0s # Shell gotcha - \06\00 works... until ${new_esp_uuid} starts with a decimal digit. diff --git a/dracut/repartition/module-setup.sh b/dracut/repartition/module-setup.sh index 38c95c7..c7b0933 100644 --- a/dracut/repartition/module-setup.sh +++ b/dracut/repartition/module-setup.sh @@ -10,6 +10,7 @@ depends() { } install() { + dracut_install eos-update-efi-uuid dracut_install sfdisk dracut_install blockdev dracut_install readlink diff --git a/eos-update-efi-uuid.c b/eos-update-efi-uuid.c new file mode 100644 index 0000000..daa8d20 --- /dev/null +++ b/eos-update-efi-uuid.c @@ -0,0 +1,393 @@ +/* + * Copyright 2024 Endless OS Foundation LLC + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOAD_OPTION_ACTIVE 0x00000001 + +static bool opt_verbose = false; +static bool opt_dry_run = false; +static const char *short_options = "vnh"; +static struct option long_options[] = { + {"verbose", 0, 0, 'v'}, + {"dry-run", 0, 0, 'n'}, + {"help", 0, 0, 'h'}, + {0, 0, 0, 0} +}; + +/* This and the cleanup_free macro are ripped off from g_autofree. */ +static void +_cleanup_free (void *pp) +{ + void *p = *(void **)pp; + free (p); +} + +#define cleanup_free __attribute__ ((cleanup (_cleanup_free))) + +/* Check if an EFI variable is a BootXXXX load option. */ +static bool +is_load_option (const efi_guid_t *guid, const char *name) +{ + if (guid == NULL || name == NULL) + { + warnx ("%s: guid or name is NULL", __func__); + return false; + } + + /* Check that this is a global EFI variable. */ + if (memcmp (guid, &efi_guid_global, sizeof (efi_guid_t)) != 0) + return false; + + /* Check that the name matches BootXXXX. */ + if (strlen (name) != 8 || + strncmp (name, "Boot", 4) != 0 || + isxdigit (name[4]) == 0 || + isxdigit (name[5]) == 0 || + isxdigit (name[6]) == 0 || + isxdigit (name[7]) == 0) + return false; + + return true; +} + +/* Read an EFI variable and parse it into an efi_load_option when appropriate. + * Returns false if the variable is not a load option. + */ +static bool +read_load_option (const efi_guid_t *guid, + const char *name, + efi_load_option **out_opt, + size_t *out_opt_size, + uint32_t *out_attributes) +{ + if (guid == NULL || name == NULL) + { + errno = EINVAL; + warnx ("%s: guid or name is NULL", __func__); + return false; + } + + cleanup_free uint8_t *data = NULL; + size_t data_size = 0; + uint32_t attributes = 0; + if (efi_get_variable (*guid, name, &data, &data_size, &attributes) < 0) + { + warn ("efi_get_variable"); + return false; + } + + efi_load_option *opt = (efi_load_option *)data; + if (!efi_loadopt_is_valid (opt, data_size)) + { + errno = EINVAL; + warn ("efi_loadopt_is_valid"); + return false; + } + + if (out_opt) + { + *out_opt = opt; + data = NULL; + } + if (out_opt_size) + *out_opt_size = data_size; + if (out_attributes) + *out_attributes = attributes; + + return true; +} + +/* A very minimal hexdump. */ +static void +hexdump (const uint8_t *data, size_t size) +{ + const uint8_t *cur; + size_t offset; + + for (cur = data, offset = 0; offset < size; cur++, offset++) + { + const char *prefix; + + if (offset % 16 == 0) + prefix = offset == 0 ? "" : "\n"; + else if (offset % 8 == 0) + prefix = " "; + else + prefix = " "; + + printf ("%s%.2x", prefix, *cur); + } + + putchar ('\n'); +} + +/* Check if an EFI load option matches a partition UUID. */ +static bool +load_option_matches_partition (efi_load_option *opt, + size_t opt_size, + efi_guid_t *part_uuid) +{ + if (opt == NULL || part_uuid == NULL) + { + errno = EINVAL; + warnx ("%s: opt or part_uuid is NULL", __func__); + return false; + } + + efidp dp = efi_loadopt_path (opt, opt_size); + if (dp == NULL) + { + errno = EINVAL; + warn ("efi_loadopt_path"); + return false; + } + + /* Only Hard Drive Media Device Paths are supported. */ + if (efidp_type (dp) != EFIDP_MEDIA_TYPE || + efidp_subtype (dp) != EFIDP_MEDIA_HD) + return false; + + /* Only GPT formatted hard drives with UUID signatures are supported. */ + efidp_hd *hd = (efidp_hd *)dp; + if (hd->format != EFIDP_HD_FORMAT_GPT || + hd->signature_type != EFIDP_HD_SIGNATURE_GUID) + return false; + + if (memcmp (hd->signature, part_uuid, sizeof (efi_guid_t)) != 0) + return false; + + return true; +} + +/* Dump a load option to stdout. A single line summary similar to efibootmgr is + * provided followed by a hexdump of the load option. + */ +static bool +dump_load_option (const char *name, + efi_load_option *opt, + size_t opt_size) +{ + if (name == NULL || opt == NULL) + { + errno = EINVAL; + warnx ("%s: name or opt is NULL", __func__); + return false; + } + + const unsigned char *desc = efi_loadopt_desc (opt, opt_size); + + efidp dp = efi_loadopt_path (opt, opt_size); + if (dp == NULL) + { + errno = EINVAL; + warn ("efi_loadopt_path"); + return false; + } + + uint16_t dp_len = efi_loadopt_pathlen (opt, opt_size); + if (dp <= 0) + { + errno = EINVAL; + warn ("efi_loadopt_pathlen"); + return false; + } + + ssize_t len = efidp_format_device_path (NULL, 0, dp, dp_len); + if (len < 0) + { + errno = EINVAL; + warn ("efi_format_device_path"); + return false; + } + + size_t path_len = len + 1; + cleanup_free char *path = calloc (path_len, sizeof (char)); + if (path == NULL) + return false; + + len = efidp_format_device_path (path, path_len, dp, dp_len); + if (len < 0) + { + errno = EINVAL; + warn ("efi_format_device_path"); + return false; + } + + printf ("%s: %s%s %s\n", + name, + (efi_loadopt_attrs(opt) & LOAD_OPTION_ACTIVE) ? "* " : "", + desc, + path); + + hexdump ((const uint8_t *)opt, opt_size); + + return true; +} + +static bool +update_load_option_partition (efi_load_option *opt, + size_t opt_size, + efi_guid_t *part_uuid) +{ + if (opt == NULL || part_uuid == NULL) + { + errno = EINVAL; + warnx ("%s: opt or part_uuid is NULL", __func__); + return false; + } + + efidp dp = efi_loadopt_path (opt, opt_size); + if (dp == NULL) + { + errno = EINVAL; + warn ("efi_loadopt_path"); + return false; + } + + /* Make sure this is a Hard Drive Media Device Path before updating. */ + if (efidp_type (dp) != EFIDP_MEDIA_TYPE || + efidp_subtype (dp) != EFIDP_MEDIA_HD) + { + errno = EINVAL; + warnx ("Only Hard Drive Media Device Paths can be updated"); + return false; + } + + /* Make sure this is a GPT formatted drive with a UUID signature. */ + efidp_hd *hd = (efidp_hd *)dp; + if (hd->format != EFIDP_HD_FORMAT_GPT || + hd->signature_type != EFIDP_HD_SIGNATURE_GUID) + { + errno = EINVAL; + warnx ("Only GPT formatted hard drives with UUID signatures can be updated"); + return false; + } + + /* Finally, update the signature UUID. */ + memmove (hd->signature, part_uuid, sizeof (efi_guid_t)); + + return true; +} + +static void +usage (const char *progname) +{ + printf ("Usage: %s [OPTION]... CUR_UUID NEW_UUID\n" + "\n" + "Update all BootXXXX options using partition CUR_UUID to NEW_UUID.\n" + "\n" + " -v, --verbose\tprint verbose messages\n" + " -n, --dry-run\tonly show what would be done\n" + " -h, --help\tdisplay this help and exit\n", + progname); +} + +int +main (int argc, char *argv[]) +{ + while (true) + { + int opt = getopt_long (argc, argv, short_options, long_options, NULL); + if (opt == -1) + break; + + switch (opt) + { + case 'v': + opt_verbose = true; + break; + case 'n': + opt_dry_run = true; + break; + case 'h': + usage (argv[0]); + return 0; + default: + return 1; + } + } + + argc -= optind; + argv += optind; + if (argc < 2) + errx (EXIT_FAILURE, "No partition UUIDs supplied"); + + const char *cur_part_uuid_str = argv[0]; + efi_guid_t cur_part_uuid = { 0 }; + if (efi_str_to_guid (cur_part_uuid_str, &cur_part_uuid) < 0) + errx (EXIT_FAILURE, "Invalid partition UUID \"%s\"", cur_part_uuid_str); + + const char *new_part_uuid_str = argv[1]; + efi_guid_t new_part_uuid = { 0 }; + if (efi_str_to_guid (new_part_uuid_str, &new_part_uuid) < 0) + errx (EXIT_FAILURE, "Invalid partition UUID \"%s\"", new_part_uuid_str); + + /* Iterate all EFI variables looking for load options to update. */ + while (true) + { + efi_guid_t *guid = NULL; + char *name = NULL; + int ret = efi_get_next_variable_name (&guid, &name); + if (ret < 0) + err (EXIT_FAILURE, "Getting next EFI variable"); + else if (ret == 0) + break; + + if (guid == NULL || name == NULL) + errx (EXIT_FAILURE, "efi_get_next_variable_name returned NULL guid or name"); + + if (!is_load_option (guid, name)) + { + if (opt_verbose) + printf ("Variable %s is not a load option\n", name); + continue; + } + + cleanup_free efi_load_option *opt = NULL; + size_t opt_size = 0; + uint32_t attributes = 0; + if (!read_load_option (guid, name, &opt, &opt_size, &attributes)) + err (EXIT_FAILURE, "Reading load option %s", name); + + errno = 0; + if (!load_option_matches_partition (opt, opt_size, &cur_part_uuid)) + { + if (errno != 0) + err (EXIT_FAILURE, "Matching load option %s partition", name); + if (opt_verbose) + printf ("Load option %s does not match partition %s\n", + name, cur_part_uuid_str); + continue; + } + + if (opt_verbose && !dump_load_option (name, opt, opt_size)) + err (EXIT_FAILURE, "Dump load option %s", name); + + if (!update_load_option_partition (opt, opt_size, &new_part_uuid)) + err (EXIT_FAILURE, "Updating load option %s partition", name); + + if (opt_verbose && !dump_load_option (name, opt, opt_size)) + err (EXIT_FAILURE, "Dump load option %s", name); + + printf ("Updating %s HD UUID from %s to %s\n", + name, cur_part_uuid_str, new_part_uuid_str); + if (!opt_dry_run) + if (efi_set_variable (*guid, name, (uint8_t *)opt, opt_size, attributes, 0644) < 0) + err (EXIT_FAILURE, "Setting load option %s", name); + } + + return 0; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 12aaada..9efd4f6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,3 +1,14 @@ +AM_TESTS_ENVIRONMENT = \ + BUILDDIR='$(builddir)' \ + TOP_BUILDDIR='$(top_builddir)' \ + ABS_BUILDDIR='$(abs_builddir)' \ + ABS_TOP_BUILDDIR='$(abs_top_builddir)' \ + SRCDIR='$(srcdir)' \ + TOP_SRCDIR='$(top_srcdir)' \ + ABS_SRCDIR='$(abs_srcdir)' \ + ABS_TOP_SRCDIR='$(abs_top_srcdir)' \ + $(NULL) + TESTS = \ check-syntax \ run-tests \ @@ -6,12 +17,15 @@ TESTS = \ EXTRA_DIST = \ $(TESTS) \ __init__.py \ + conftest.py \ + efivars \ test_image_boot.py \ test_live_storage.py \ test_migrate_chromium_profile.py \ test_migrate_firefox_profile.py \ test_repartition.py \ test_repartition_mbr.py \ + test_update_efi_uuid.py \ test_update_flatpak_repos.py \ util.py \ $(NULL) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..6f28746 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,28 @@ +# Copyright 2024 Endless OS Foundation LLC +# SPDX-License-Identifier: GPL-2.0-or-later + +# pytest fixtures + +import pytest +from shutil import copyfile, copytree + +from .util import EFIVARFS_PATH + + +@pytest.fixture +def efivarfs(tmp_path, monkeypatch): + """Temporary efivarfs data + + Copy the test efivarfs data to a temporary location. The environment + variable EFIVARFS_PATH is set to the temporary location. This is supported + by libefivar. + """ + # Only the data is copied in case EFIVARFS_PATH is read only like + # during distcheck. None of the metadata matters here. + tmp_efivarfs = tmp_path / 'efivars' + copytree(EFIVARFS_PATH, tmp_efivarfs, copy_function=copyfile) + + # libefivar expects this to end with a trailing /. + monkeypatch.setenv('EFIVARFS_PATH', f'{tmp_efivarfs}/') + + return tmp_efivarfs diff --git a/tests/efivars/Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..4f85562 Binary files /dev/null and b/tests/efivars/Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..41299a7 Binary files /dev/null and b/tests/efivars/Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/Boot0002-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/Boot0002-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..6aaec5f Binary files /dev/null and b/tests/efivars/Boot0002-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/Boot0003-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/Boot0003-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..f6cfbeb Binary files /dev/null and b/tests/efivars/Boot0003-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..3c4f60f Binary files /dev/null and b/tests/efivars/BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/BootOptionSupport-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/BootOptionSupport-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..346f078 Binary files /dev/null and b/tests/efivars/BootOptionSupport-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..b3ac290 Binary files /dev/null and b/tests/efivars/BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/ConIn-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/ConIn-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..ffd332f Binary files /dev/null and b/tests/efivars/ConIn-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/ConInDev-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/ConInDev-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..143fc0e Binary files /dev/null and b/tests/efivars/ConInDev-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/ConOut-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/ConOut-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..f294e34 Binary files /dev/null and b/tests/efivars/ConOut-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/ConOutDev-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/ConOutDev-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..1c1cca6 Binary files /dev/null and b/tests/efivars/ConOutDev-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/ErrOut-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/ErrOut-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..1f5bf61 Binary files /dev/null and b/tests/efivars/ErrOut-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/ErrOutDev-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/ErrOutDev-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..1c1cca6 Binary files /dev/null and b/tests/efivars/ErrOutDev-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/FALLBACK_VERBOSE-605dab50-e046-4300-abb6-3dd810dd8b23 b/tests/efivars/FALLBACK_VERBOSE-605dab50-e046-4300-abb6-3dd810dd8b23 new file mode 100644 index 0000000..a652cc8 Binary files /dev/null and b/tests/efivars/FALLBACK_VERBOSE-605dab50-e046-4300-abb6-3dd810dd8b23 differ diff --git a/tests/efivars/KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..b5f1d02 Binary files /dev/null and b/tests/efivars/KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/Key0000-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/Key0000-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..656fd27 Binary files /dev/null and b/tests/efivars/Key0000-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/Key0001-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/Key0001-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..8f63601 Binary files /dev/null and b/tests/efivars/Key0001-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/Lang-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/Lang-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..71516d7 Binary files /dev/null and b/tests/efivars/Lang-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/LangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/LangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..118883b Binary files /dev/null and b/tests/efivars/LangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/MTC-eb704011-1402-11d3-8e77-00a0c969723b b/tests/efivars/MTC-eb704011-1402-11d3-8e77-00a0c969723b new file mode 100644 index 0000000..a98f4e2 Binary files /dev/null and b/tests/efivars/MTC-eb704011-1402-11d3-8e77-00a0c969723b differ diff --git a/tests/efivars/MokListRT-605dab50-e046-4300-abb6-3dd810dd8b23 b/tests/efivars/MokListRT-605dab50-e046-4300-abb6-3dd810dd8b23 new file mode 100644 index 0000000..97f7167 Binary files /dev/null and b/tests/efivars/MokListRT-605dab50-e046-4300-abb6-3dd810dd8b23 differ diff --git a/tests/efivars/MokListTrustedRT-605dab50-e046-4300-abb6-3dd810dd8b23 b/tests/efivars/MokListTrustedRT-605dab50-e046-4300-abb6-3dd810dd8b23 new file mode 100644 index 0000000..687e561 Binary files /dev/null and b/tests/efivars/MokListTrustedRT-605dab50-e046-4300-abb6-3dd810dd8b23 differ diff --git a/tests/efivars/MokListXRT-605dab50-e046-4300-abb6-3dd810dd8b23 b/tests/efivars/MokListXRT-605dab50-e046-4300-abb6-3dd810dd8b23 new file mode 100644 index 0000000..44b4cc7 Binary files /dev/null and b/tests/efivars/MokListXRT-605dab50-e046-4300-abb6-3dd810dd8b23 differ diff --git a/tests/efivars/OsIndicationsSupported-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/OsIndicationsSupported-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..10fbafd Binary files /dev/null and b/tests/efivars/OsIndicationsSupported-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/PK-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/PK-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..a738c67 Binary files /dev/null and b/tests/efivars/PK-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/PlatformLang-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/PlatformLang-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..43fb1fb Binary files /dev/null and b/tests/efivars/PlatformLang-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/PlatformLangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/PlatformLangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..89d0fa2 Binary files /dev/null and b/tests/efivars/PlatformLangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/PlatformRecovery0000-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/PlatformRecovery0000-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..2016880 Binary files /dev/null and b/tests/efivars/PlatformRecovery0000-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/SbatLevelRT-605dab50-e046-4300-abb6-3dd810dd8b23 b/tests/efivars/SbatLevelRT-605dab50-e046-4300-abb6-3dd810dd8b23 new file mode 100644 index 0000000..4ab45e8 Binary files /dev/null and b/tests/efivars/SbatLevelRT-605dab50-e046-4300-abb6-3dd810dd8b23 differ diff --git a/tests/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..687e561 Binary files /dev/null and b/tests/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/SetupMode-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/SetupMode-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..39e2858 Binary files /dev/null and b/tests/efivars/SetupMode-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/SignatureSupport-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/SignatureSupport-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..becf55f Binary files /dev/null and b/tests/efivars/SignatureSupport-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/Timeout-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/Timeout-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..f781342 Binary files /dev/null and b/tests/efivars/Timeout-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/VarErrorFlag-04b37fe8-f6ae-480b-bdd5-37d98c5e89aa b/tests/efivars/VarErrorFlag-04b37fe8-f6ae-480b-bdd5-37d98c5e89aa new file mode 100644 index 0000000..bcb29d7 Binary files /dev/null and b/tests/efivars/VarErrorFlag-04b37fe8-f6ae-480b-bdd5-37d98c5e89aa differ diff --git a/tests/efivars/VendorKeys-8be4df61-93ca-11d2-aa0d-00e098032b8c b/tests/efivars/VendorKeys-8be4df61-93ca-11d2-aa0d-00e098032b8c new file mode 100644 index 0000000..39e2858 Binary files /dev/null and b/tests/efivars/VendorKeys-8be4df61-93ca-11d2-aa0d-00e098032b8c differ diff --git a/tests/efivars/certdb-d9bee56e-75dc-49d9-b4d7-b534210f637a b/tests/efivars/certdb-d9bee56e-75dc-49d9-b4d7-b534210f637a new file mode 100644 index 0000000..6d809a2 Binary files /dev/null and b/tests/efivars/certdb-d9bee56e-75dc-49d9-b4d7-b534210f637a differ diff --git a/tests/efivars/certdbv-d9bee56e-75dc-49d9-b4d7-b534210f637a b/tests/efivars/certdbv-d9bee56e-75dc-49d9-b4d7-b534210f637a new file mode 100644 index 0000000..fa40121 Binary files /dev/null and b/tests/efivars/certdbv-d9bee56e-75dc-49d9-b4d7-b534210f637a differ diff --git a/tests/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f b/tests/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f new file mode 100644 index 0000000..0457fe4 Binary files /dev/null and b/tests/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f differ diff --git a/tests/run-tests b/tests/run-tests index fee1e1e..b574aaf 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -2,8 +2,4 @@ set -e cd "$(dirname "$0")" -if pytest=$(which py.test-3); then - exec "$pytest" "$@" -else - exec python3 -m unittest discover --top-level-directory .. "$@" -fi +exec python3 -m pytest "$@" diff --git a/tests/test_update_efi_uuid.py b/tests/test_update_efi_uuid.py new file mode 100644 index 0000000..5ce44aa --- /dev/null +++ b/tests/test_update_efi_uuid.py @@ -0,0 +1,119 @@ +# Copyright 2024 Endless OS Foundation LLC +# SPDX-License-Identifier: GPL-2.0-or-later + +import logging +import shlex +import subprocess +import sys +from uuid import UUID + +from .util import built_program, EFIVARFS_PATH + +logger = logging.getLogger(__name__) + +EFI_UPDATE_UUID = built_program('eos-update-efi-uuid') + +ORIG_ESP_UUID = '9cf7d938-86c5-4f09-8401-fd0d6e4c646c' +NEW_ESP_UUID = '0de64583-f397-4783-a8f0-f101dd91def4' +OTHER_ESP_UUID = '5538ba7e-e641-4560-84c9-6194f68b8d32' +ENDLESS_LOAD_OPTION = 'Boot0003-8be4df61-93ca-11d2-aa0d-00e098032b8c' + +# Offset info the EFI variable where the UUID is located. Note that this +# is dependent on the length of the title from EFI/endless/BOOTX64.CSV. +# This comes from the shim package and is currently "Endless OS". +UUID_OFFSET = 56 + + +def run(cmd, check=True, log_level=logging.INFO, **kwargs): + cmd_str = shlex.join(cmd) + logger.log(log_level, f'Executing {cmd_str}') + return subprocess.run(cmd, check=check, **kwargs) + + +def hexdump(path): + proc = run( + ['hexdump', '-C', str(path)], + check=True, + stdout=subprocess.PIPE, + text=True, + log_level=logging.DEBUG, + ) + return proc.stdout + + +def test_update(efivarfs): + """Update with correct partition UUID""" + run([EFI_UPDATE_UUID, '-v', ORIG_ESP_UUID, NEW_ESP_UUID]) + + # Keep track of the hexdumps for a repeat. + test_hexdumps = {} + + for test_path in efivarfs.iterdir(): + var = test_path.name + test_hex = hexdump(test_path) + test_hexdumps[str(test_path)] = test_hex + src_path = EFIVARFS_PATH / var + src_hex = hexdump(src_path) + + if test_path.name != ENDLESS_LOAD_OPTION: + assert test_hex == src_hex, f'{var} has changed' + continue + + assert test_hex != src_hex, f'{var} has not changed' + with open(src_path, 'rb') as src, open(test_path, 'rb') as test: + src_data = src.read() + test_data = test.read() + + assert len(src_data) == len(test_data), f'{var} size has changed' + + # Up until the UUID offset should match. + assert test_data[:UUID_OFFSET] == src_data[:UUID_OFFSET] + + # The next 16 bytes are the UUID. + uuid_end = UUID_OFFSET + 16 + src_uuid = UUID(ORIG_ESP_UUID) + test_uuid = UUID(NEW_ESP_UUID) + if sys.byteorder == 'little': + src_uuid_bytes = src_uuid.bytes_le + test_uuid_bytes = test_uuid.bytes_le + else: + src_uuid_bytes = src_uuid.bytes + test_uuid_bytes = test_uuid.bytes + assert test_data[UUID_OFFSET:uuid_end] != src_data[UUID_OFFSET:uuid_end] + assert src_data[UUID_OFFSET:uuid_end] == src_uuid_bytes + assert test_data[UUID_OFFSET:uuid_end] == test_uuid_bytes + + # The rest should match. + assert test_data[uuid_end:] == src_data[uuid_end:] + + # Running again should cause no changes since there aren't any load + # options matching the original UUID. + run([EFI_UPDATE_UUID, '-v', ORIG_ESP_UUID, NEW_ESP_UUID]) + for test_path in efivarfs.iterdir(): + test_hex = hexdump(test_path) + prev_hex = test_hexdumps[str(test_path)] + assert test_hex == prev_hex, f'{test_path.name} has changed' + + +def test_dry_run(efivarfs): + """Dry run should produce no changes""" + run([EFI_UPDATE_UUID, '-v', '--dry-run', ORIG_ESP_UUID, NEW_ESP_UUID]) + + for test_path in efivarfs.iterdir(): + test_hex = hexdump(test_path) + src_path = EFIVARFS_PATH / test_path.name + src_hex = hexdump(src_path) + + assert test_hex == src_hex, f'{test_path.name} has changed' + + +def test_other(efivarfs): + """Other partition UUID should produce no changes""" + run([EFI_UPDATE_UUID, '-v', OTHER_ESP_UUID, NEW_ESP_UUID]) + + for test_path in efivarfs.iterdir(): + test_hex = hexdump(test_path) + src_path = EFIVARFS_PATH / test_path.name + src_hex = hexdump(src_path) + + assert test_hex == src_hex, f'{test_path.name} has changed' diff --git a/tests/util.py b/tests/util.py index 567e594..74d8d1d 100644 --- a/tests/util.py +++ b/tests/util.py @@ -2,16 +2,33 @@ import importlib.machinery import importlib.util import os +from pathlib import Path import subprocess import tempfile import unittest +TESTS_PATH = Path(__file__).parent.resolve() +EFIVARFS_PATH = TESTS_PATH / 'efivars' run_needs_root_tests = bool(os.environ.get('EBH_ROOT_TESTS')) needs_root = unittest.skipIf(not run_needs_root_tests, "needs root; set EBH_ROOT_TESTS=1 to enable") +def built_program(program): + '''Gets the absolute path to a built program in the top level of this + repository. + + This uses the ABS_TOP_BUILDDIR environment variable to find the top build + directory. Otherwise it falls back to system_script, which uses the top + source directory. + ''' + abs_top_builddir = os.environ.get('ABS_TOP_BUILDDIR') + if abs_top_builddir: + return os.path.join(abs_top_builddir, program) + return system_script(program) + + def system_script(script): '''Gets the absolute path to a script in the top level of this repository.'''