From 9a7b65345548e857555aff03dc1a515cc4303d72 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 10 Sep 2024 16:35:04 -0600 Subject: [PATCH 1/5] tests: Require pytest It's now 2024. Pytest is superior to unittest and has been available in Debian since at least buster. Just use it. While here, invoke it as a module to avoid whatever Debian is naming the CLI entrypoint these days. https://phabricator.endlessm.com/T31604 --- debian/control | 1 + tests/run-tests | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/debian/control b/debian/control index 9bf1eafa..87fa84c2 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Build-Depends: automake, python3 , python3-gi , python3-parted , + python3-pytest , python3-systemd , systemd, udev, diff --git a/tests/run-tests b/tests/run-tests index fee1e1ed..b574aaf3 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 "$@" From 795fa239c8d758030e3d5cada6fb8f09dedf50d2 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Sun, 8 Sep 2024 12:43:29 -0600 Subject: [PATCH 2/5] tests: Add helper to get path to built program --- tests/Makefile.am | 11 +++++++++++ tests/util.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/tests/Makefile.am b/tests/Makefile.am index 12aaada9..1e543512 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 \ diff --git a/tests/util.py b/tests/util.py index 567e594f..6b008c74 100644 --- a/tests/util.py +++ b/tests/util.py @@ -12,6 +12,20 @@ "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.''' From b4081f9314656a8574f188e99220bf3d7e7b89cf Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 5 Sep 2024 17:07:23 -0600 Subject: [PATCH 3/5] tests: Add EFI variable data This is from an Endless VM using TianoCore with Secure Boot enabled (and throwaway keys in the databases). A pytest fixture is provided to make testing with the data safer. --- .gitattributes | 2 ++ tests/Makefile.am | 2 ++ tests/conftest.py | 28 ++++++++++++++++++ ...t0000-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 66 bytes ...t0001-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 88 bytes ...t0002-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 92 bytes ...t0003-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 132 bytes ...rrent-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 6 bytes ...pport-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 8 bytes ...Order-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 12 bytes ...ConIn-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 126 bytes ...InDev-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 695 bytes ...onOut-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 107 bytes ...utDev-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 691 bytes ...rrOut-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 77 bytes ...utDev-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 691 bytes ...RBOSE-605dab50-e046-4300-abb6-3dd810dd8b23 | Bin 0 -> 5 bytes .../KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 880 bytes ...y0000-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 18 bytes ...y0001-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 18 bytes .../Lang-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 8 bytes ...Codes-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 17 bytes .../MTC-eb704011-1402-11d3-8e77-00a0c969723b | Bin 0 -> 8 bytes ...istRT-605dab50-e046-4300-abb6-3dd810dd8b23 | Bin 0 -> 894 bytes ...tedRT-605dab50-e046-4300-abb6-3dd810dd8b23 | Bin 0 -> 5 bytes ...stXRT-605dab50-e046-4300-abb6-3dd810dd8b23 | Bin 0 -> 2511 bytes ...orted-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 12 bytes .../PK-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 878 bytes ...mLang-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 7 bytes ...Codes-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 22 bytes ...y0000-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 112 bytes ...velRT-605dab50-e046-4300-abb6-3dd810dd8b23 | Bin 0 -> 50 bytes ...eBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 5 bytes ...pMode-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 5 bytes ...pport-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 68 bytes ...meout-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 6 bytes ...rFlag-04b37fe8-f6ae-480b-bdd5-37d98c5e89aa | Bin 0 -> 5 bytes ...rKeys-8be4df61-93ca-11d2-aa0d-00e098032b8c | Bin 0 -> 5 bytes ...ertdb-d9bee56e-75dc-49d9-b4d7-b534210f637a | Bin 0 -> 8 bytes ...rtdbv-d9bee56e-75dc-49d9-b4d7-b534210f637a | Bin 0 -> 8 bytes .../db-d719b2cb-3d3a-4596-a3bc-dad00e67656f | Bin 0 -> 882 bytes tests/util.py | 3 ++ 42 files changed, 35 insertions(+) create mode 100644 .gitattributes create mode 100644 tests/conftest.py create mode 100644 tests/efivars/Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/Boot0002-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/Boot0003-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/BootOptionSupport-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/ConIn-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/ConInDev-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/ConOut-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/ConOutDev-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/ErrOut-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/ErrOutDev-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/FALLBACK_VERBOSE-605dab50-e046-4300-abb6-3dd810dd8b23 create mode 100644 tests/efivars/KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/Key0000-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/Key0001-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/Lang-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/LangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/MTC-eb704011-1402-11d3-8e77-00a0c969723b create mode 100644 tests/efivars/MokListRT-605dab50-e046-4300-abb6-3dd810dd8b23 create mode 100644 tests/efivars/MokListTrustedRT-605dab50-e046-4300-abb6-3dd810dd8b23 create mode 100644 tests/efivars/MokListXRT-605dab50-e046-4300-abb6-3dd810dd8b23 create mode 100644 tests/efivars/OsIndicationsSupported-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/PK-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/PlatformLang-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/PlatformLangCodes-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/PlatformRecovery0000-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/SbatLevelRT-605dab50-e046-4300-abb6-3dd810dd8b23 create mode 100644 tests/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/SetupMode-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/SignatureSupport-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/Timeout-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/VarErrorFlag-04b37fe8-f6ae-480b-bdd5-37d98c5e89aa create mode 100644 tests/efivars/VendorKeys-8be4df61-93ca-11d2-aa0d-00e098032b8c create mode 100644 tests/efivars/certdb-d9bee56e-75dc-49d9-b4d7-b534210f637a create mode 100644 tests/efivars/certdbv-d9bee56e-75dc-49d9-b4d7-b534210f637a create mode 100644 tests/efivars/db-d719b2cb-3d3a-4596-a3bc-dad00e67656f diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..398849d3 --- /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/tests/Makefile.am b/tests/Makefile.am index 1e543512..ce826e11 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -17,6 +17,8 @@ TESTS = \ EXTRA_DIST = \ $(TESTS) \ __init__.py \ + conftest.py \ + efivars \ test_image_boot.py \ test_live_storage.py \ test_migrate_chromium_profile.py \ diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..6f287461 --- /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 0000000000000000000000000000000000000000..4f85562dc456640f9ef0b90543c1aa3e708eb320 GIT binary patch literal 66 zcmZQ)U|`^6WMI%?2xZ7*aAYU|LIwsFb`ge?dw0~l{$b+3>XqG-^{HYDS=dAv6j$lE ViIg$BHs^J1`;w+?SpT1e0RRE96Kenf literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..41299a7378c51097750b3bc17317e38017c9e8ed GIT binary patch literal 88 zcmZQ)U|?VbVi|@|23H0*22TbB2499uhGK?fAj^dzm7xsCO9s*m3`~qX3>O@kxqwUt iMn*OUW+o8Hz)=66g~4wPN2B2UNZ(ezM^ZsLk(&U}_YNNb literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6aaec5f52cdbc8a5aa13515903b591028dc6aa0d GIT binary patch literal 92 zcmZQ)U|?VbVjTuo1~&#z1_dC_V<=%rWhesDi3~YFkzj@lAU_8P85mgDMHo)*-BI)U phl&5HS9VXO5?BFuckAc}$E5F?o4U}R+G5@85-KlotZ z&r5={co-(A+aL6;XJjGIpwxtA_a(Pn7M!I^p+Vfo*Hu3VFZ1bZ*Po}s(d@sY zUHhi{yw`%_3$oAabUVm4s3CV^?Z)UH*CqXr;_n8&pF*}l^iNpKbq!vuBvgTD3uSr`ECkPhAe literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1c1cca6428e2891da4ce0efbf06fad2e74da3469 GIT binary patch literal 691 zcmZQ$U|?WkiNpKbq!vuBvgT7=Nl$x;YzT}q6g0plfG>H57y6OkvWj=lF`tvk6 znw`mZ(X|DlYu|LA_gYYVLH2o_ZU@;0HRMjL-5A~Dx}^V6{N2F!Q^+=`-plNX>JL8W zxmI&#KWnU6O144SOaJ^f+g;(kYV|(p>2GRIlWox5yKa4+wsXC9l-h9QXD!-Gwm}cB X)&8+*^mX2H%lew<%ZE|*|5+FSs7-N{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1f5bf61eaad9259b4a7e03560be2d299a096a504 GIT binary patch literal 77 zcmZQ)U|?WkO5?BFuckAc}$E5F?o4U}R+G5@85-KlotZ U&r5={co-(A+aL6;|Ifkz03*^2l>h($ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1c1cca6428e2891da4ce0efbf06fad2e74da3469 GIT binary patch literal 691 zcmZQ$U|?WkiNpKbq!vuBvgT7=Nl$x;YzT}q6g0plfG>H57y6OkvWj=lF`tvk6 znw`mZ(X|DlYu|LA_gYYVLH2o_ZU@;0HRMjL-5A~Dx}^V6{N2F!Q^+=`-plNX>JL8W zxmI&#KWnU6O144SOaJ^f+g;(kYV|(p>2GRIlWox5yKa4+wsXC9l-h9QXD!-Gwm}cB X)&8+*^mX2H%lew<%ZE|*|5+FSs7-N{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a652cc8655921fbc429c1cf4f08bca8aad2b0763 GIT binary patch literal 5 McmZQ)U|?Vb001cf2><{9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b5f1d02f7695235a7392d58f1648a246ec54700d GIT binary patch literal 880 zcmY#qU|?7nd0^?2Da*aux2_hA(f&}B!weJzf&eHD;Xz1)CT1IhCMJ~y%uI|-Od=U^ zKMXbpCx?BR`eV|s#dB_X9y_+dfR~L^tIebBJ1-+6H!FjIl%a%y7#nja3o{SDtADV9 zx2v~;v!jBMfsu)Uf;cabv@oB3(FHwC-ykpVEeifyjzd4s~Fmvd~2!Atu_DtM}r-v)%pYE^ITK6Y^ znU|I6`g6$}*ZyIc{q@%#IpNda zS({U4`7Wpo{C$?|W|7o-_s+HMj)9q1KdgN}eZl4_n-(>fMo&%LIEU|vglP*$#_M~Y z=5JoUExg)ZzCn=NUaprRgp2p!p2UCh%BQWDb9*M-+{JP8V3X@k=?A-+Rf72bwPaaF z^19nTaFdkR-Ig?4VNYmjQD3dbJaJWbMJemR4O2D-=lu=RVSM}2h%f79N1(x-t~}}d zm7Hw?UXvw?XLZUlYTW$G#LURRxVXZg%s>_x{<3^5Vk{!wEjsU}|KEDbFjxBBmItZt znK$2jY#p(l<-fS5_L$Rj@dLLo zjsCNF)the}t(H)_=ei`a&a_9>(l2mFz^6502fLEvH$=8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8f6360188b6be653962974f2a368de9c75b88371 GIT binary patch literal 18 UcmZQ)00W1>>(l2mFo**=02g)wy#N3J literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..71516d7ca9777bc83d48c6ad110a5e53159cbe25 GIT binary patch literal 8 PcmZQ)U|>kiOJ@K81MC4p literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..118883b71f7640d97d03a30ac9ff5e2b66b79913 GIT binary patch literal 17 TcmZQ$U|>kiOHV6GgpdpXB|QY~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a98f4e275d98dfa7b6c55657fcfa970b3e0a5819 GIT binary patch literal 8 NcmZQ)U|>)OVgLZ#051Rl literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..97f716760b088e860231266be63944b980c3f7fb GIT binary patch literal 894 zcmZQ$U|?7nd0^?2Da*aux2_hA(f&|W#S9b#f;b=@usSxu?E!=H>TR|+1nzb#8#FO{ z88k5&EnsG1WMUG@;wcPL=IL5D?UM3e#|?`*oxeQxHQ;6A)N1o+`_9YA$j!=NplB#> zAj`%a%EHVe=9-t1lUiJ?5S*G^T9m5bl%HRs;OuCiAkJ%KWMF1sZeVO^YG4{A!Ea<> z3Pi>RPyu2LX<}4DwwjTZfw_s1pTVGsk&CH`k&$7wjNJORtDp2`qwm-hDfYY&v%mO9 z#FHt4*DUCr%1a0T9oug(J$E|JY!PZ+{PFaiSf^{3?j9^@xni(z-{GCTrc4O|PJS}) zi?&7gnaXk%&ghXk^l7`&%2|8XNIjWS{Qtpv+wdKJ3d;$Yg+yPO*LEC4zc|)XkBnuLW?tVP1r1h zT|32cx=U-)ce>>VedFs%bINE~;&)q2GVR)`)1Btt^J{8@KD}yYVrFDuTwGyLW*`iV z4Ovwd9s@2m4sA9@R#tXqW&=KuAU`AHe-;*C+_o9;gZRQAKC1yUkOIe{EFX&)i-^hF zl07G{zFRc?tM{F2&(8AidirjsfjmgPGK++PSOazisL28tH;fFKnuiyMm48@h^^&L7 zzU4_sMv~^eh5zhX{hRk*fBw|7ZevjW!Ok`N_sw>^HK$O$`$dd}(%q>pbN}vsecHnR z(5>#mm8m-~e+}AXw~({{=$8m&r}T$as-!!7ki~1Re4Y?@vD&%Obi(=~YVm(bFHol~UIp zPLb!clWg5HA?o|RWh)gn7TlcpCi9%-?|g}ykq`a}JPFx+bx*;m7@x%Bg`XvuqX9N3 BVhjKP literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..687e561126750b618ef12a9607841f2e302cbf1d GIT binary patch literal 5 McmZQ$U|?Vb001Na2mk;8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..44b4cc7cf4d538051e269cff0d0ba44edf05e446 GIT binary patch literal 2511 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..43fb1fbbc741a206aaa85c1aa9db0d70d1fffc7d GIT binary patch literal 7 OcmZQ)U|>kiV*mgGLjckivra3rPR-K|4F)oF-GUeZI~fJ2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2016880d7da5884e0b63e15057c4108003a66cfd GIT binary patch literal 112 zcmZQ$U|?VbViN`zhE#?$hD3%^h8%_x1_g!yAe{(gr7`3)6fxv71Oerff$TCMUBplc o)WgDJz!1aW%HYP}38bAE{DCk8i50s)B)`{yz%?0K4cBg8%>k literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4ab45e8f9644c42bb7799f0160f187624b41ea25 GIT binary patch literal 50 zcmZQ$U|=XtN-WVa)G;zJGBGeTurx5>D$dBv)iL2pFDgyaF$Pn5DXB@BiFrUl04o*^ A6#xJL literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..687e561126750b618ef12a9607841f2e302cbf1d GIT binary patch literal 5 McmZQ$U|?Vb001Na2mk;8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..39e285823439390db551edacfebdbfc6638e5212 GIT binary patch literal 5 KcmZQ$00IC2BLD{g literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..becf55f4be8c766331a4107afa5f8243978b2d58 GIT binary patch literal 68 zcmV-K0K5MN0000IrEG!_&&f)$hrR(xW-+}c7R13!P?A8bsX_TRlS3%zW>-9%CNxf} a6zz9zg|oJyS-_>_l&4CEwW}3eEAVo)CmwVF literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f7813428e3421947f0e6c6de14411ee71cf8522a GIT binary patch literal 6 NcmZQ)U|?Wo0000s01E&B literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bcb29d7ea8f7ebe69a9e1cfaeee904d4eb30050f GIT binary patch literal 5 McmZQ)U|{$U00Acf2LJ#7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..39e285823439390db551edacfebdbfc6638e5212 GIT binary patch literal 5 KcmZQ$00IC2BLD{g literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6d809a24427dc36ee6c5e9e9b95296c962b4a60b GIT binary patch literal 8 NcmY#qU|?VYVgLb904x9i literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fa4012112d311e51ce5a7a69116de978de0fe765 GIT binary patch literal 8 NcmY#mU|?VYVgLb104o3h literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0457fe442362e0074f3b54cae2c2c68a8298a2e2 GIT binary patch literal 882 zcmY#qU|?7nd0^?2Da*aux2_hA(f&}B#|#t%f*>dj;Xz1)CT2T>CMLB7%uI|-Od<}R zHE#2_Dtoa3FB_*;n@8JsUPeZ4Rt5uULrDX1Hs(+kW*z}o|6qks zS2s@uXGaAi10xdy1#w;=X<=w-Xl7z$U>YUC4Z>@H-NmsR_cJm)44lGqSCr8@@a)452I*Oe`wm4;7e9Xd1^fSxOUyMtOw)f9@W|%s z-u17Qd_G?;`k=LDZHtz-aI^N7cZ`i;SJWdroWqnX3mfF-JxH!)h`wCk_@{Bheq*j_ zSrZ&CM8EG4;*4lp@af&3H9kx2C%UrGIBjP?r(bAc`*AA z=i8D!w+jB$7rnGCcp0AD#Zz>3}dq*rV zrQhQAl!?`EmSx{IB4HrbfL#GUNP#dT<9`-b17;v)zy}iG2MMqM(?XkpFo>_p!ehY2 z#-Yu|$jZvj%#0j2z@z|-8%BocEDoJ;f$wjo*9oPqTDa)?E%V-Ye&Vh1d!KGie(!hY z+sh=TpKbOkqTRO+2b@@0cvY=T+*8A-b^Xy7o_Yss&ImLfIGDDU@x@xMbuT6yK6US^ z!lr*}CLL|=8z$X5{O8o^TP71#+meE1$)2_R_{+HkCRsP4NGd*&Pm#}xIUFRl7!`Q8-DxYt;ykBl+ zQa;Z=scmbvDm*=Y#@~5MdTW63OZQzbFCFT-Ft4L$bx~X6np;yhg>*bycRwtuC{D@c i>xAcV(p7)mW<8v8HPJ;hLq(OTb5-Zdp1)!&ERO- Date: Fri, 30 Aug 2024 19:50:20 -0600 Subject: [PATCH 4/5] Add eos-update-efi-uuid for updating EFI load option partition UUIDs When the partition UUID for the ESP is updated, any load options referencing the partition become invalid. Add a tool to update all load options using the old partition UUID to the new partition UUID. This uses the libefivar and libefiboot packages to access EFI variables and interpret the data appropriately. https://phabricator.endlessm.com/T31604 --- .gitignore | 1 + Makefile.am | 10 + configure.ac | 2 + debian/control | 2 + eos-update-efi-uuid.c | 393 ++++++++++++++++++++++++++++++++++ tests/Makefile.am | 1 + tests/test_update_efi_uuid.py | 119 ++++++++++ 7 files changed, 528 insertions(+) create mode 100644 eos-update-efi-uuid.c create mode 100644 tests/test_update_efi_uuid.py diff --git a/.gitignore b/.gitignore index be166451..986c9ab8 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 8382b163..be61e581 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 854e1123..ab3bc113 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 87fa84c2..555c1851 100644 --- a/debian/control +++ b/debian/control @@ -10,6 +10,8 @@ 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 , diff --git a/eos-update-efi-uuid.c b/eos-update-efi-uuid.c new file mode 100644 index 00000000..daa8d200 --- /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 ce826e11..9efd4f68 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -25,6 +25,7 @@ EXTRA_DIST = \ 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/test_update_efi_uuid.py b/tests/test_update_efi_uuid.py new file mode 100644 index 00000000..5ce44aa8 --- /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' From f2e5767ff6eb7781e835658496ddbbdad5f7e4a3 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 5 Sep 2024 16:52:37 -0600 Subject: [PATCH 5/5] repartition: Update partitions in EFI load options When the ESP partition ID is regenerated, any EFI load options referencing the old partition ID become invalid. While shim's fallback will generate a new load option on the next boot, a downstream patch is needed to make sure the invalid option is removed. Rather than having shim cleanup this situation, update the load options at the time the partition UUID is changed. Since both the old and new UUIDs are known, eos-update-efi-uuid can update only the affected load options without changing or deleting load options that aren't affected by the UUID change. Note that this runs eos-update-efi-uuid in verbose mode, which is pretty noisy. When we have confidence this is doing the right thing, we can drop the -v option. https://phabricator.endlessm.com/T31604 --- dracut/repartition/endless-repartition.sh | 18 ++++++++++++++++-- dracut/repartition/module-setup.sh | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/dracut/repartition/endless-repartition.sh b/dracut/repartition/endless-repartition.sh index d116b95a..a4c93bbc 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 38c95c77..c7b0933f 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