From d3e892ecce706953bfa9c50b232b77bd3ad1b9d4 Mon Sep 17 00:00:00 2001 From: Jan Macku Date: Fri, 8 Dec 2023 10:00:06 +0100 Subject: [PATCH] systemd-252-20 Resolves: RHEL-13199,RHEL-16354,RHEL-5988 --- ...ew-link-name-as-an-altname-before-re.patch | 37 + ...t-swap-old-name-and-alternative-name.patch | 64 + ...re-altname-on-error-in-rtnl_set_link.patch | 66 + ...evice-rename-even-if-interface-is-up.patch | 65 + ...nk-add-a-test-for-rtnl_set_link_name.patch | 74 + ...-a-test-for-renaming-device-to-curre.patch | 50 + 0389-udev-align-table.patch | 58 + ...evice_set_syspath-clear-sysname-and-.patch | 47 + ...-directly-access-entry-in-sd-device-.patch | 62 + ...-device_rename-from-device-private.c.patch | 148 + ...re-syspath-and-properties-on-failure.patch | 155 + ...ce-introduce-device_get_property_int.patch | 56 + ...wngrade-log-level-for-ignored-errors.patch | 32 + 0396-core-device-ignore-failed-uevents.patch | 45 + ...or-failure-in-renaming-network-inter.patch | 99 + 0398-test-modernize-test-netlink.c.patch | 623 +++ ...-dummy-interface-to-test-assigning-n.patch | 105 + ...se-SYNTHETIC_ERRNO-at-one-more-place.patch | 26 + ...make-udev_builtin_run-take-UdevEvent.patch | 351 ++ ...ID_NET_XYZ-before-trying-to-assign-i.patch | 26 + ...e-new-network-interface-name-only-on.patch | 29 + ...rtnl_set_link_name-optionally-append.patch | 191 + ...alternative-names-only-on-add-uevent.patch | 233 ++ ...tests-for-renaming-network-interface.patch | 105 + 0407-Backport-ukify-from-upstream.patch | 3533 +++++++++++++++++ ...bootctl-make-json-output-normal-json.patch | 54 + 0409-test-replace-readfp-with-read_file.patch | 28 + ...ument-and-measure-.uname-UKI-section.patch | 81 + 0411-boot-measure-.sbat-section.patch | 96 + ...fy-no-stinky-root-needed-for-signing.patch | 42 + ...r-bin-and-mark-as-non-non-experiment.patch | 36 + 0414-kernel-install-Add-uki-layout.patch | 221 ++ ...tall-remove-math-slang-from-man-page.patch | 25 + ...ll-handle-uki-installs-automatically.patch | 80 + ...all-create-BOOT-EFI-Linux-directory-.patch | 31 + ...og-location-that-uki-is-installed-in.patch | 36 + 0419-bootctl-fix-errno-logging.patch | 25 + ...-bootctl-add-kernel-identity-command.patch | 214 + 0421-bootctl-add-kernel-inspect-command.patch | 150 + ...tctl-add-kernel-inspect-to-help-text.patch | 24 + ...-drop-full-stop-at-end-of-help-texts.patch | 29 + ...ection-title-for-kernel-image-comman.patch | 44 + ...emove-space-that-should-not-be-there.patch | 25 + ...bootctl-kernel-inspect-print-os-info.patch | 73 + ...ctl-uki-several-coding-style-fixlets.patch | 207 + ...how-we-pick-OS-pretty-name-to-displa.patch | 171 + ...several-follow-ups-for-inspect_osrel.patch | 102 + 0430-bootctl-Add-missing-m.patch | 25 + ...bootctl-tweak-DOS-header-magic-check.patch | 41 + systemd.spec | 102 +- 50 files changed, 8241 insertions(+), 1 deletion(-) create mode 100644 0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch create mode 100644 0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch create mode 100644 0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch create mode 100644 0386-udev-attempt-device-rename-even-if-interface-is-up.patch create mode 100644 0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch create mode 100644 0388-test-network-add-a-test-for-renaming-device-to-curre.patch create mode 100644 0389-udev-align-table.patch create mode 100644 0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch create mode 100644 0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch create mode 100644 0392-udev-move-device_rename-from-device-private.c.patch create mode 100644 0393-udev-restore-syspath-and-properties-on-failure.patch create mode 100644 0394-sd-device-introduce-device_get_property_int.patch create mode 100644 0395-core-device-downgrade-log-level-for-ignored-errors.patch create mode 100644 0396-core-device-ignore-failed-uevents.patch create mode 100644 0397-test-add-tests-for-failure-in-renaming-network-inter.patch create mode 100644 0398-test-modernize-test-netlink.c.patch create mode 100644 0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch create mode 100644 0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch create mode 100644 0401-udev-make-udev_builtin_run-take-UdevEvent.patch create mode 100644 0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch create mode 100644 0403-udev-net-generate-new-network-interface-name-only-on.patch create mode 100644 0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch create mode 100644 0405-udev-net-assign-alternative-names-only-on-add-uevent.patch create mode 100644 0406-test-add-tests-for-renaming-network-interface.patch create mode 100644 0407-Backport-ukify-from-upstream.patch create mode 100644 0408-bootctl-make-json-output-normal-json.patch create mode 100644 0409-test-replace-readfp-with-read_file.patch create mode 100644 0410-stub-measure-document-and-measure-.uname-UKI-section.patch create mode 100644 0411-boot-measure-.sbat-section.patch create mode 100644 0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch create mode 100644 0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch create mode 100644 0414-kernel-install-Add-uki-layout.patch create mode 100644 0415-kernel-install-remove-math-slang-from-man-page.patch create mode 100644 0416-kernel-install-handle-uki-installs-automatically.patch create mode 100644 0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch create mode 100644 0418-kernel-install-Log-location-that-uki-is-installed-in.patch create mode 100644 0419-bootctl-fix-errno-logging.patch create mode 100644 0420-bootctl-add-kernel-identity-command.patch create mode 100644 0421-bootctl-add-kernel-inspect-command.patch create mode 100644 0422-bootctl-add-kernel-inspect-to-help-text.patch create mode 100644 0423-bootctl-drop-full-stop-at-end-of-help-texts.patch create mode 100644 0424-bootctl-change-section-title-for-kernel-image-comman.patch create mode 100644 0425-bootctl-remove-space-that-should-not-be-there.patch create mode 100644 0426-bootctl-kernel-inspect-print-os-info.patch create mode 100644 0427-bootctl-uki-several-coding-style-fixlets.patch create mode 100644 0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch create mode 100644 0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch create mode 100644 0430-bootctl-Add-missing-m.patch create mode 100644 0431-bootctl-tweak-DOS-header-magic-check.patch diff --git a/0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch b/0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch new file mode 100644 index 0000000..ace4e34 --- /dev/null +++ b/0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch @@ -0,0 +1,37 @@ +From 57d5e48a572b98d6ab978072daddac2f7faf8dc8 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Wed, 2 Nov 2022 11:05:01 -0400 +Subject: [PATCH] udev/net: allow new link name as an altname before renaming + happens + +When configuring a link's alternative names, the link's new name to-be +is not allowed to be included because interface renaming will fail if +the new name is already present as an alternative name. However, +rtnl_set_link_name will delete the conflicting alternative name before +renaming the device, if necessary. + +Allow the new link name to be set as an alternative name before the +device is renamed. This means that if the rename is later skipped (i.e. +because the link is already up), then the name can at least still be +present as an alternative name. + +(cherry picked from commit d0b31efc1ab7f6826ad834cf6b9e371bf73776aa) + +Related: RHEL-5988 +--- + src/udev/net/link-config.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index e408725b08..5d28526527 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -841,8 +841,6 @@ static int link_apply_alternative_names(Link *link, sd_netlink **rtnl) { + } + } + +- if (link->new_name) +- strv_remove(altnames, link->new_name); + strv_remove(altnames, link->ifname); + + r = rtnl_get_link_alternative_names(rtnl, link->ifindex, ¤t_altnames); diff --git a/0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch b/0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch new file mode 100644 index 0000000..9ecaa8d --- /dev/null +++ b/0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch @@ -0,0 +1,64 @@ +From ded04e17443f1e9a99705d39ae7dde72eb24ef34 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Fri, 2 Dec 2022 15:26:18 -0500 +Subject: [PATCH] sd-netlink: do not swap old name and alternative name + +Commit 434a348380 ("netlink: do not fail when new interface name is +already used as an alternative name") added logic to set the old +interface name as an alternative name, but only when the new name is +currently an alternative name. This is not the desired outcome in most +cases, and the important part of this commit was to delete the new name +from the list of alternative names if necessary. + +(cherry picked from commit 080afbb57c4b2d592c5cf77ab10c6e0be74f0732) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/netlink-util.c | 13 ------------- + 1 file changed, 13 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c +index 12cdc99ff2..6b4c25fe5a 100644 +--- a/src/libsystemd/sd-netlink/netlink-util.c ++++ b/src/libsystemd/sd-netlink/netlink-util.c +@@ -3,7 +3,6 @@ + #include "sd-netlink.h" + + #include "fd-util.h" +-#include "format-util.h" + #include "io-util.h" + #include "memory-util.h" + #include "netlink-internal.h" +@@ -15,7 +14,6 @@ + int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + _cleanup_strv_free_ char **alternative_names = NULL; +- char old_name[IF_NAMESIZE] = {}; + int r; + + assert(rtnl); +@@ -35,10 +33,6 @@ int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + if (r < 0) + return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", + name, ifindex); +- +- r = format_ifname(ifindex, old_name); +- if (r < 0) +- return log_debug_errno(r, "Failed to get current name of network interface %i: %m", ifindex); + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); +@@ -53,13 +47,6 @@ int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + if (r < 0) + return r; + +- if (!isempty(old_name)) { +- r = rtnl_set_link_alternative_names(rtnl, ifindex, STRV_MAKE(old_name)); +- if (r < 0) +- log_debug_errno(r, "Failed to set '%s' as an alternative name on network interface %i, ignoring: %m", +- old_name, ifindex); +- } +- + return 0; + } + diff --git a/0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch b/0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch new file mode 100644 index 0000000..2b924de --- /dev/null +++ b/0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch @@ -0,0 +1,66 @@ +From 9b6a3b192ba0f22ce99aa5c48c6c7143d12dddba Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Wed, 2 Nov 2022 05:36:14 -0400 +Subject: [PATCH] sd-netlink: restore altname on error in rtnl_set_link_name + +If a current alternative name is to be used to rename a network +interface, the alternative name must be removed first. If interface +renaming fails, restore the alternative name that was deleted if +necessary. + +(cherry picked from commit 4d600667f8af2985850b03a46357e068d3fb8570) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/netlink-util.c | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c +index 6b4c25fe5a..cfcf2578d6 100644 +--- a/src/libsystemd/sd-netlink/netlink-util.c ++++ b/src/libsystemd/sd-netlink/netlink-util.c +@@ -14,6 +14,7 @@ + int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + _cleanup_strv_free_ char **alternative_names = NULL; ++ bool altname_deleted = false; + int r; + + assert(rtnl); +@@ -33,21 +34,33 @@ int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + if (r < 0) + return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", + name, ifindex); ++ ++ altname_deleted = true; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) +- return r; ++ goto fail; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); + if (r < 0) +- return r; ++ goto fail; + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) +- return r; ++ goto fail; + + return 0; ++ ++fail: ++ if (altname_deleted) { ++ int q = rtnl_set_link_alternative_names(rtnl, ifindex, STRV_MAKE(name)); ++ if (q < 0) ++ log_debug_errno(q, "Failed to restore '%s' as an alternative name on network interface %i, ignoring: %m", ++ name, ifindex); ++ } ++ ++ return r; + } + + int rtnl_set_link_properties( diff --git a/0386-udev-attempt-device-rename-even-if-interface-is-up.patch b/0386-udev-attempt-device-rename-even-if-interface-is-up.patch new file mode 100644 index 0000000..a6b1875 --- /dev/null +++ b/0386-udev-attempt-device-rename-even-if-interface-is-up.patch @@ -0,0 +1,65 @@ +From ca122b3f1e00ba6a70e7575266502579108c4b47 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Fri, 2 Dec 2022 15:35:25 -0500 +Subject: [PATCH] udev: attempt device rename even if interface is up + +Currently rename_netif() will not attempt to rename a device if it is +already up, because the kernel will return -EBUSY unless live renaming +is allowed on the device. This restriction will be removed in a future +kernel version [1]. + +To cover both cases, always attempt to rename the interface and return 0 +if we get -EBUSY. + +[1] https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=bd039b5ea2a9 + +(cherry picked from commit 53584e7b61373c26635b906eb64e98fbd3fd3ba4) + +Related: RHEL-5988 +--- + src/udev/udev-event.c | 18 ++++++------------ + 1 file changed, 6 insertions(+), 12 deletions(-) + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index b3d92d5150..08d69cf1f0 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -862,7 +862,6 @@ int udev_event_spawn( + static int rename_netif(UdevEvent *event) { + const char *oldname; + sd_device *dev; +- unsigned flags; + int ifindex, r; + + assert(event); +@@ -896,17 +895,7 @@ static int rename_netif(UdevEvent *event) { + return 0; + } + +- r = rtnl_get_link_info(&event->rtnl, ifindex, NULL, &flags, NULL, NULL, NULL); +- if (r < 0) +- return log_device_warning_errno(dev, r, "Failed to get link flags: %m"); +- +- if (FLAGS_SET(flags, IFF_UP)) { +- log_device_info(dev, "Network interface '%s' is already up, refusing to rename to '%s'.", +- oldname, event->name); +- return 0; +- } +- +- /* Set ID_RENAMING boolean property here, and drop it in the corresponding move uevent later. */ ++ /* Set ID_RENAMING boolean property here. It will be dropped when the corresponding move uevent is processed. */ + r = device_add_property(dev, "ID_RENAMING", "1"); + if (r < 0) + return log_device_warning_errno(dev, r, "Failed to add 'ID_RENAMING' property: %m"); +@@ -927,6 +916,11 @@ static int rename_netif(UdevEvent *event) { + return log_device_debug_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m"); + + r = rtnl_set_link_name(&event->rtnl, ifindex, event->name); ++ if (r == -EBUSY) { ++ log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", ++ oldname, event->name); ++ return 0; ++ } + if (r < 0) + return log_device_error_errno(dev, r, "Failed to rename network interface %i from '%s' to '%s': %m", + ifindex, oldname, event->name); diff --git a/0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch b/0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch new file mode 100644 index 0000000..c9ccbd9 --- /dev/null +++ b/0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch @@ -0,0 +1,74 @@ +From 32188058ad3b9a8bfc555215982145a128adfc44 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Tue, 22 Nov 2022 17:01:47 -0500 +Subject: [PATCH] sd-netlink: add a test for rtnl_set_link_name() + +Add a test that verifies a deleted alternative name is restored on error +in rtnl_set_link_name(). + +(cherry picked from commit b338a8bb402a3ab241a617e096b21ae6a7b7badf) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/test-netlink.c | 27 ++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c +index 3f74ecc068..2d93f9eba0 100644 +--- a/src/libsystemd/sd-netlink/test-netlink.c ++++ b/src/libsystemd/sd-netlink/test-netlink.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + + #include "sd-netlink.h" + +@@ -16,6 +17,7 @@ + #include "macro.h" + #include "netlink-genl.h" + #include "netlink-internal.h" ++#include "netlink-util.h" + #include "socket-util.h" + #include "stdio-util.h" + #include "string-util.h" +@@ -667,6 +669,30 @@ static void test_genl(void) { + } + } + ++static void test_rtnl_set_link_name(sd_netlink *rtnl, int ifindex) { ++ _cleanup_strv_free_ char **alternative_names = NULL; ++ int r; ++ ++ log_debug("/* %s */", __func__); ++ ++ if (geteuid() != 0) ++ return (void) log_tests_skipped("not root"); ++ ++ /* Test that the new name (which is currently an alternative name) is ++ * restored as an alternative name on error. Create an error by using ++ * an invalid device name, namely one that exceeds IFNAMSIZ ++ * (alternative names can exceed IFNAMSIZ, but not regular names). */ ++ r = rtnl_set_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")); ++ if (r == -EPERM) ++ return (void) log_tests_skipped("missing required capabilities"); ++ ++ assert_se(r >= 0); ++ assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename") == -EINVAL); ++ assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); ++ assert_se(strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); ++} ++ + int main(void) { + sd_netlink *rtnl; + sd_netlink_message *m; +@@ -698,6 +724,7 @@ int main(void) { + test_pipe(if_loopback); + test_event_loop(if_loopback); + test_link_configure(rtnl, if_loopback); ++ test_rtnl_set_link_name(rtnl, if_loopback); + + test_get_addresses(rtnl); + test_message_link_bridge(rtnl); diff --git a/0388-test-network-add-a-test-for-renaming-device-to-curre.patch b/0388-test-network-add-a-test-for-renaming-device-to-curre.patch new file mode 100644 index 0000000..9026e93 --- /dev/null +++ b/0388-test-network-add-a-test-for-renaming-device-to-curre.patch @@ -0,0 +1,50 @@ +From 6e095bdbd88ddbe289210720e7a55b62fa593ab8 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Wed, 7 Dec 2022 12:28:28 -0500 +Subject: [PATCH] test-network: add a test for renaming device to current + altname + +(cherry picked from commit f68f644a167af3452be853b631fa9144c6716c28) + +Related: RHEL-5988 +--- + .../test-network/conf/12-dummy-rename-to-altname.link | 7 +++++++ + test/test-network/systemd-networkd-tests.py | 11 +++++++++++ + 2 files changed, 18 insertions(+) + create mode 100644 test/test-network/conf/12-dummy-rename-to-altname.link + +diff --git a/test/test-network/conf/12-dummy-rename-to-altname.link b/test/test-network/conf/12-dummy-rename-to-altname.link +new file mode 100644 +index 0000000000..bef4bf3dc5 +--- /dev/null ++++ b/test/test-network/conf/12-dummy-rename-to-altname.link +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++[Match] ++OriginalName=dummy98 ++ ++[Link] ++Name=dummyalt ++AlternativeName=dummyalt hogehogehogehogehogehoge +diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py +index 87710ef3fb..696618790a 100755 +--- a/test/test-network/systemd-networkd-tests.py ++++ b/test/test-network/systemd-networkd-tests.py +@@ -936,6 +936,17 @@ class NetworkctlTests(unittest.TestCase, Utilities): + output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env) + self.assertRegex(output, 'hogehogehogehogehogehoge') + ++ @expectedFailureIfAlternativeNameIsNotAvailable() ++ def test_rename_to_altname(self): ++ copy_network_unit('26-netdev-link-local-addressing-yes.network', ++ '12-dummy.netdev', '12-dummy-rename-to-altname.link') ++ start_networkd() ++ self.wait_online(['dummyalt:degraded']) ++ ++ output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummyalt', env=env) ++ self.assertIn('hogehogehogehogehogehoge', output) ++ self.assertNotIn('dummy98', output) ++ + def test_reconfigure(self): + copy_network_unit('25-address-static.network', '12-dummy.netdev') + start_networkd() diff --git a/0389-udev-align-table.patch b/0389-udev-align-table.patch new file mode 100644 index 0000000..535e1e4 --- /dev/null +++ b/0389-udev-align-table.patch @@ -0,0 +1,58 @@ +From d808bd97790dd8a38d844c827d2d9dbcb700d8c0 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 11:20:35 +0900 +Subject: [PATCH] udev: align table + +(cherry picked from commit bb1234d1d6b7b14424093a917890bb4013b4ff3e) + +Related: RHEL-5988 +--- + src/udev/udev-event.c | 34 +++++++++++++++++----------------- + 1 file changed, 17 insertions(+), 17 deletions(-) + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index 08d69cf1f0..3ac12d9b52 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -119,24 +119,24 @@ struct subst_map_entry { + }; + + static const struct subst_map_entry map[] = { +- { .name = "devnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, +- { .name = "tempnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, /* deprecated */ +- { .name = "attr", .fmt = 's', .type = FORMAT_SUBST_ATTR }, +- { .name = "sysfs", .fmt = 's', .type = FORMAT_SUBST_ATTR }, /* deprecated */ +- { .name = "env", .fmt = 'E', .type = FORMAT_SUBST_ENV }, +- { .name = "kernel", .fmt = 'k', .type = FORMAT_SUBST_KERNEL }, ++ { .name = "devnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, ++ { .name = "tempnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, /* deprecated */ ++ { .name = "attr", .fmt = 's', .type = FORMAT_SUBST_ATTR }, ++ { .name = "sysfs", .fmt = 's', .type = FORMAT_SUBST_ATTR }, /* deprecated */ ++ { .name = "env", .fmt = 'E', .type = FORMAT_SUBST_ENV }, ++ { .name = "kernel", .fmt = 'k', .type = FORMAT_SUBST_KERNEL }, + { .name = "number", .fmt = 'n', .type = FORMAT_SUBST_KERNEL_NUMBER }, +- { .name = "driver", .fmt = 'd', .type = FORMAT_SUBST_DRIVER }, +- { .name = "devpath", .fmt = 'p', .type = FORMAT_SUBST_DEVPATH }, +- { .name = "id", .fmt = 'b', .type = FORMAT_SUBST_ID }, +- { .name = "major", .fmt = 'M', .type = FORMAT_SUBST_MAJOR }, +- { .name = "minor", .fmt = 'm', .type = FORMAT_SUBST_MINOR }, +- { .name = "result", .fmt = 'c', .type = FORMAT_SUBST_RESULT }, +- { .name = "parent", .fmt = 'P', .type = FORMAT_SUBST_PARENT }, +- { .name = "name", .fmt = 'D', .type = FORMAT_SUBST_NAME }, +- { .name = "links", .fmt = 'L', .type = FORMAT_SUBST_LINKS }, +- { .name = "root", .fmt = 'r', .type = FORMAT_SUBST_ROOT }, +- { .name = "sys", .fmt = 'S', .type = FORMAT_SUBST_SYS }, ++ { .name = "driver", .fmt = 'd', .type = FORMAT_SUBST_DRIVER }, ++ { .name = "devpath", .fmt = 'p', .type = FORMAT_SUBST_DEVPATH }, ++ { .name = "id", .fmt = 'b', .type = FORMAT_SUBST_ID }, ++ { .name = "major", .fmt = 'M', .type = FORMAT_SUBST_MAJOR }, ++ { .name = "minor", .fmt = 'm', .type = FORMAT_SUBST_MINOR }, ++ { .name = "result", .fmt = 'c', .type = FORMAT_SUBST_RESULT }, ++ { .name = "parent", .fmt = 'P', .type = FORMAT_SUBST_PARENT }, ++ { .name = "name", .fmt = 'D', .type = FORMAT_SUBST_NAME }, ++ { .name = "links", .fmt = 'L', .type = FORMAT_SUBST_LINKS }, ++ { .name = "root", .fmt = 'r', .type = FORMAT_SUBST_ROOT }, ++ { .name = "sys", .fmt = 'S', .type = FORMAT_SUBST_SYS }, + }; + + static const char *format_type_to_string(FormatSubstitutionType t) { diff --git a/0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch b/0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch new file mode 100644 index 0000000..6834db5 --- /dev/null +++ b/0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch @@ -0,0 +1,47 @@ +From 3b4d91e7ab44738f3773a3bfd4a6c5fb9bbc7322 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 14:00:09 +0900 +Subject: [PATCH] sd-device: make device_set_syspath() clear sysname and sysnum + +Otherwise, when a new syspath is assigned to the sd-device object, +sd_device_get_sysname() or _sysnum() will provide an outdated device +name or number. + +(cherry picked from commit 9a26098e90116fdb5fcffa03485b375ee0c82b6a) + +Related: RHEL-5988 +--- + src/libsystemd/sd-device/device-private.c | 4 ---- + src/libsystemd/sd-device/sd-device.c | 4 ++++ + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c +index bc7a838608..2c1d922ea3 100644 +--- a/src/libsystemd/sd-device/device-private.c ++++ b/src/libsystemd/sd-device/device-private.c +@@ -646,10 +646,6 @@ int device_rename(sd_device *device, const char *name) { + if (r < 0) + return r; + +- /* Here, only clear the sysname and sysnum. They will be set when requested. */ +- device->sysnum = NULL; +- device->sysname = mfree(device->sysname); +- + r = sd_device_get_property_value(device, "INTERFACE", &interface); + if (r == -ENOENT) + return 0; +diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c +index f2e142457b..c822a0b2f0 100644 +--- a/src/libsystemd/sd-device/sd-device.c ++++ b/src/libsystemd/sd-device/sd-device.c +@@ -250,6 +250,10 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { + + free_and_replace(device->syspath, syspath); + device->devpath = devpath; ++ ++ /* Unset sysname and sysnum, they will be assigned when requested. */ ++ device->sysnum = NULL; ++ device->sysname = mfree(device->sysname); + return 0; + } + diff --git a/0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch b/0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch new file mode 100644 index 0000000..989573c --- /dev/null +++ b/0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch @@ -0,0 +1,62 @@ +From 2c6ea8a97986c58954603b587875a52b043e4d9b Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 14:07:16 +0900 +Subject: [PATCH] sd-device: do not directly access entry in sd-device object + +No functional change, just refactoring. + +(cherry picked from commit 1de6a49721957a85a4934ddbdf88d535774597b1) + +Related: RHEL-5988 +--- + src/libsystemd/sd-device/device-private.c | 14 +++++++++----- + 1 file changed, 9 insertions(+), 5 deletions(-) + +diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c +index 2c1d922ea3..9cec037237 100644 +--- a/src/libsystemd/sd-device/device-private.c ++++ b/src/libsystemd/sd-device/device-private.c +@@ -621,7 +621,7 @@ int device_get_devlink_priority(sd_device *device, int *ret) { + + int device_rename(sd_device *device, const char *name) { + _cleanup_free_ char *new_syspath = NULL; +- const char *interface; ++ const char *s; + int r; + + assert(device); +@@ -630,7 +630,11 @@ int device_rename(sd_device *device, const char *name) { + if (!filename_is_valid(name)) + return -EINVAL; + +- r = path_extract_directory(device->syspath, &new_syspath); ++ r = sd_device_get_syspath(device, &s); ++ if (r < 0) ++ return r; ++ ++ r = path_extract_directory(s, &new_syspath); + if (r < 0) + return r; + +@@ -642,18 +646,18 @@ int device_rename(sd_device *device, const char *name) { + + /* At the time this is called, the renamed device may not exist yet. Hence, we cannot validate + * the new syspath. */ +- r = device_set_syspath(device, new_syspath, false); ++ r = device_set_syspath(device, new_syspath, /* verify = */ false); + if (r < 0) + return r; + +- r = sd_device_get_property_value(device, "INTERFACE", &interface); ++ r = sd_device_get_property_value(device, "INTERFACE", &s); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ +- r = device_add_property_internal(device, "INTERFACE_OLD", interface); ++ r = device_add_property_internal(device, "INTERFACE_OLD", s); + if (r < 0) + return r; + diff --git a/0392-udev-move-device_rename-from-device-private.c.patch b/0392-udev-move-device_rename-from-device-private.c.patch new file mode 100644 index 0000000..75fb2ff --- /dev/null +++ b/0392-udev-move-device_rename-from-device-private.c.patch @@ -0,0 +1,148 @@ +From 7f183125fbec97bd6e4c0b3ac792b0e0c23132e0 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 15:00:30 +0900 +Subject: [PATCH] udev: move device_rename() from device-private.c + +The function is used only by udevd. + +(cherry picked from commit ff88b949531e70639c507f74da875a7de2adf543) + +Related: RHEL-5988 +--- + src/libsystemd/sd-device/device-private.c | 45 ---------------------- + src/libsystemd/sd-device/device-private.h | 1 - + src/udev/udev-event.c | 46 +++++++++++++++++++++++ + 3 files changed, 46 insertions(+), 46 deletions(-) + +diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c +index 9cec037237..36b0da4f12 100644 +--- a/src/libsystemd/sd-device/device-private.c ++++ b/src/libsystemd/sd-device/device-private.c +@@ -619,51 +619,6 @@ int device_get_devlink_priority(sd_device *device, int *ret) { + return 0; + } + +-int device_rename(sd_device *device, const char *name) { +- _cleanup_free_ char *new_syspath = NULL; +- const char *s; +- int r; +- +- assert(device); +- assert(name); +- +- if (!filename_is_valid(name)) +- return -EINVAL; +- +- r = sd_device_get_syspath(device, &s); +- if (r < 0) +- return r; +- +- r = path_extract_directory(s, &new_syspath); +- if (r < 0) +- return r; +- +- if (!path_extend(&new_syspath, name)) +- return -ENOMEM; +- +- if (!path_is_safe(new_syspath)) +- return -EINVAL; +- +- /* At the time this is called, the renamed device may not exist yet. Hence, we cannot validate +- * the new syspath. */ +- r = device_set_syspath(device, new_syspath, /* verify = */ false); +- if (r < 0) +- return r; +- +- r = sd_device_get_property_value(device, "INTERFACE", &s); +- if (r == -ENOENT) +- return 0; +- if (r < 0) +- return r; +- +- /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ +- r = device_add_property_internal(device, "INTERFACE_OLD", s); +- if (r < 0) +- return r; +- +- return device_add_property_internal(device, "INTERFACE", name); +-} +- + static int device_shallow_clone(sd_device *device, sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *dest = NULL; + const char *val = NULL; +diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h +index a59f130aff..e57b74ba24 100644 +--- a/src/libsystemd/sd-device/device-private.h ++++ b/src/libsystemd/sd-device/device-private.h +@@ -53,7 +53,6 @@ int device_properties_prepare(sd_device *device); + int device_get_properties_nulstr(sd_device *device, const char **ret_nulstr, size_t *ret_len); + int device_get_properties_strv(sd_device *device, char ***ret); + +-int device_rename(sd_device *device, const char *name); + int device_clone_with_db(sd_device *device, sd_device **ret); + + int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index 3ac12d9b52..1dc05f863d 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -12,6 +12,7 @@ + #include "sd-event.h" + + #include "alloc-util.h" ++#include "device-internal.h" + #include "device-private.h" + #include "device-util.h" + #include "fd-util.h" +@@ -859,6 +860,51 @@ int udev_event_spawn( + return r; /* 0 for success, and positive if the program failed */ + } + ++static int device_rename(sd_device *device, const char *name) { ++ _cleanup_free_ char *new_syspath = NULL; ++ const char *s; ++ int r; ++ ++ assert(device); ++ assert(name); ++ ++ if (!filename_is_valid(name)) ++ return -EINVAL; ++ ++ r = sd_device_get_syspath(device, &s); ++ if (r < 0) ++ return r; ++ ++ r = path_extract_directory(s, &new_syspath); ++ if (r < 0) ++ return r; ++ ++ if (!path_extend(&new_syspath, name)) ++ return -ENOMEM; ++ ++ if (!path_is_safe(new_syspath)) ++ return -EINVAL; ++ ++ /* At the time this is called, the renamed device may not exist yet. Hence, we cannot validate ++ * the new syspath. */ ++ r = device_set_syspath(device, new_syspath, /* verify = */ false); ++ if (r < 0) ++ return r; ++ ++ r = sd_device_get_property_value(device, "INTERFACE", &s); ++ if (r == -ENOENT) ++ return 0; ++ if (r < 0) ++ return r; ++ ++ /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ ++ r = device_add_property_internal(device, "INTERFACE_OLD", s); ++ if (r < 0) ++ return r; ++ ++ return device_add_property_internal(device, "INTERFACE", name); ++} ++ + static int rename_netif(UdevEvent *event) { + const char *oldname; + sd_device *dev; diff --git a/0393-udev-restore-syspath-and-properties-on-failure.patch b/0393-udev-restore-syspath-and-properties-on-failure.patch new file mode 100644 index 0000000..dcba5f2 --- /dev/null +++ b/0393-udev-restore-syspath-and-properties-on-failure.patch @@ -0,0 +1,155 @@ +From 1f4bc8c496d2a310ffa3e7174af40f7e596cd2d1 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 14:58:58 +0900 +Subject: [PATCH] udev: restore syspath and properties on failure + +Otherwise, invalid sysname or properties may be broadcast to udev +listeners. + +(cherry picked from commit 210033847c340c43dd6835520f21f8b23ba29579) + +Related: RHEL-5988 +--- + src/udev/udev-event.c | 93 +++++++++++++++++++++++++++++-------------- + 1 file changed, 64 insertions(+), 29 deletions(-) + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index 1dc05f863d..fab454ae37 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -906,7 +906,8 @@ static int device_rename(sd_device *device, const char *name) { + } + + static int rename_netif(UdevEvent *event) { +- const char *oldname; ++ _cleanup_free_ char *old_syspath = NULL, *old_sysname = NULL; ++ const char *s; + sd_device *dev; + int ifindex, r; + +@@ -917,15 +918,6 @@ static int rename_netif(UdevEvent *event) { + + dev = ASSERT_PTR(event->dev); + +- /* Read sysname from cloned sd-device object, otherwise use-after-free is triggered, as the +- * main object will be renamed and dev->sysname will be freed in device_rename(). */ +- r = sd_device_get_sysname(event->dev_db_clone, &oldname); +- if (r < 0) +- return log_device_error_errno(dev, r, "Failed to get sysname: %m"); +- +- if (streq(event->name, oldname)) +- return 0; /* The interface name is already requested name. */ +- + if (!device_for_action(dev, SD_DEVICE_ADD)) + return 0; /* Rename the interface only when it is added. */ + +@@ -933,7 +925,7 @@ static int rename_netif(UdevEvent *event) { + if (r == -ENOENT) + return 0; /* Device is not a network interface. */ + if (r < 0) +- return log_device_error_errno(dev, r, "Failed to get ifindex: %m"); ++ return log_device_warning_errno(dev, r, "Failed to get ifindex: %m"); + + if (naming_scheme_has(NAMING_REPLACE_STRICTLY) && + !ifname_valid(event->name)) { +@@ -941,39 +933,82 @@ static int rename_netif(UdevEvent *event) { + return 0; + } + +- /* Set ID_RENAMING boolean property here. It will be dropped when the corresponding move uevent is processed. */ +- r = device_add_property(dev, "ID_RENAMING", "1"); ++ r = sd_device_get_sysname(dev, &s); + if (r < 0) +- return log_device_warning_errno(dev, r, "Failed to add 'ID_RENAMING' property: %m"); ++ return log_device_warning_errno(dev, r, "Failed to get sysname: %m"); + +- r = device_rename(dev, event->name); ++ if (streq(event->name, s)) ++ return 0; /* The interface name is already requested name. */ ++ ++ old_sysname = strdup(s); ++ if (!old_sysname) ++ return -ENOMEM; ++ ++ r = sd_device_get_syspath(dev, &s); + if (r < 0) +- return log_device_warning_errno(dev, r, "Failed to update properties with new name '%s': %m", event->name); ++ return log_device_warning_errno(dev, r, "Failed to get syspath: %m"); ++ ++ old_syspath = strdup(s); ++ if (!old_syspath) ++ return -ENOMEM; ++ ++ r = device_rename(dev, event->name); ++ if (r < 0) { ++ log_device_warning_errno(dev, r, "Failed to update properties with new name '%s': %m", event->name); ++ goto revert; ++ } ++ ++ /* Set ID_RENAMING boolean property here. It will be dropped when the corresponding move uevent is processed. */ ++ r = device_add_property(dev, "ID_RENAMING", "1"); ++ if (r < 0) { ++ log_device_warning_errno(dev, r, "Failed to add 'ID_RENAMING' property: %m"); ++ goto revert; ++ } + + /* Also set ID_RENAMING boolean property to cloned sd_device object and save it to database + * before calling rtnl_set_link_name(). Otherwise, clients (e.g., systemd-networkd) may receive + * RTM_NEWLINK netlink message before the database is updated. */ + r = device_add_property(event->dev_db_clone, "ID_RENAMING", "1"); +- if (r < 0) +- return log_device_warning_errno(event->dev_db_clone, r, "Failed to add 'ID_RENAMING' property: %m"); ++ if (r < 0) { ++ log_device_warning_errno(event->dev_db_clone, r, "Failed to add 'ID_RENAMING' property: %m"); ++ goto revert; ++ } + + r = device_update_db(event->dev_db_clone); +- if (r < 0) +- return log_device_debug_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m"); ++ if (r < 0) { ++ log_device_debug_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m"); ++ goto revert; ++ } + + r = rtnl_set_link_name(&event->rtnl, ifindex, event->name); +- if (r == -EBUSY) { +- log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", +- oldname, event->name); +- return 0; ++ if (r < 0) { ++ if (r == -EBUSY) { ++ log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", ++ old_sysname, event->name); ++ r = 0; ++ } else ++ log_device_error_errno(dev, r, "Failed to rename network interface %i from '%s' to '%s': %m", ++ ifindex, old_sysname, event->name); ++ goto revert; + } +- if (r < 0) +- return log_device_error_errno(dev, r, "Failed to rename network interface %i from '%s' to '%s': %m", +- ifindex, oldname, event->name); +- +- log_device_debug(dev, "Network interface %i is renamed from '%s' to '%s'", ifindex, oldname, event->name); + ++ log_device_debug(dev, "Network interface %i is renamed from '%s' to '%s'", ifindex, old_sysname, event->name); + return 1; ++ ++revert: ++ /* Restore 'dev_db_clone' */ ++ (void) device_add_property(event->dev_db_clone, "ID_RENAMING", NULL); ++ (void) device_update_db(event->dev_db_clone); ++ ++ /* Restore 'dev' */ ++ (void) device_set_syspath(dev, old_syspath, /* verify = */ false); ++ if (sd_device_get_property_value(dev, "INTERFACE_OLD", &s) >= 0) { ++ (void) device_add_property_internal(dev, "INTERFACE", s); ++ (void) device_add_property_internal(dev, "INTERFACE_OLD", NULL); ++ } ++ (void) device_add_property(dev, "ID_RENAMING", NULL); ++ ++ return r; + } + + static int update_devnode(UdevEvent *event) { diff --git a/0394-sd-device-introduce-device_get_property_int.patch b/0394-sd-device-introduce-device_get_property_int.patch new file mode 100644 index 0000000..dbd2aab --- /dev/null +++ b/0394-sd-device-introduce-device_get_property_int.patch @@ -0,0 +1,56 @@ +From 284d6f9171ba819bcccb6a2df7c3012ba8483a0c Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 16:44:11 +0900 +Subject: [PATCH] sd-device: introduce device_get_property_int() + +(cherry picked from commit eedfef0f0d2654fcde2a3b694e62518d688af827) + +Related: RHEL-5988 +--- + src/libsystemd/sd-device/device-private.h | 1 + + src/libsystemd/sd-device/sd-device.c | 20 ++++++++++++++++++++ + 2 files changed, 21 insertions(+) + +diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h +index e57b74ba24..d9a519a4d9 100644 +--- a/src/libsystemd/sd-device/device-private.h ++++ b/src/libsystemd/sd-device/device-private.h +@@ -18,6 +18,7 @@ int device_new_from_strv(sd_device **ret, char **strv); + int device_opendir(sd_device *device, const char *subdir, DIR **ret); + + int device_get_property_bool(sd_device *device, const char *key); ++int device_get_property_int(sd_device *device, const char *key, int *ret); + int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value); + int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value); + int device_get_sysattr_bool(sd_device *device, const char *sysattr); +diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c +index c822a0b2f0..7ee67b4641 100644 +--- a/src/libsystemd/sd-device/sd-device.c ++++ b/src/libsystemd/sd-device/sd-device.c +@@ -2186,6 +2186,26 @@ int device_get_property_bool(sd_device *device, const char *key) { + return parse_boolean(value); + } + ++int device_get_property_int(sd_device *device, const char *key, int *ret) { ++ const char *value; ++ int r, v; ++ ++ assert(device); ++ assert(key); ++ ++ r = sd_device_get_property_value(device, key, &value); ++ if (r < 0) ++ return r; ++ ++ r = safe_atoi(value, &v); ++ if (r < 0) ++ return r; ++ ++ if (ret) ++ *ret = v; ++ return 0; ++} ++ + _public_ int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret) { + const char *s; + sd_id128_t id; diff --git a/0395-core-device-downgrade-log-level-for-ignored-errors.patch b/0395-core-device-downgrade-log-level-for-ignored-errors.patch new file mode 100644 index 0000000..d2b1f7b --- /dev/null +++ b/0395-core-device-downgrade-log-level-for-ignored-errors.patch @@ -0,0 +1,32 @@ +From 42a11f89c8836493847a69906ef2765e2e984dbf Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 16:11:04 +0900 +Subject: [PATCH] core/device: downgrade log level for ignored errors + +(cherry picked from commit 58b0a3e5112a27daa181383458f68955eb081551) + +Related: RHEL-5988 +--- + src/core/device.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/core/device.c b/src/core/device.c +index 224fc90835..09b7d56e1e 100644 +--- a/src/core/device.c ++++ b/src/core/device.c +@@ -1095,13 +1095,13 @@ static int device_dispatch_io(sd_device_monitor *monitor, sd_device *dev, void * + + r = sd_device_get_syspath(dev, &sysfs); + if (r < 0) { +- log_device_error_errno(dev, r, "Failed to get device syspath, ignoring: %m"); ++ log_device_warning_errno(dev, r, "Failed to get device syspath, ignoring: %m"); + return 0; + } + + r = sd_device_get_action(dev, &action); + if (r < 0) { +- log_device_error_errno(dev, r, "Failed to get udev action, ignoring: %m"); ++ log_device_warning_errno(dev, r, "Failed to get udev action, ignoring: %m"); + return 0; + } + diff --git a/0396-core-device-ignore-failed-uevents.patch b/0396-core-device-ignore-failed-uevents.patch new file mode 100644 index 0000000..e2464a3 --- /dev/null +++ b/0396-core-device-ignore-failed-uevents.patch @@ -0,0 +1,45 @@ +From b59fda96b0e24b93dcdb061da24c42a924ae0b20 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 16:11:52 +0900 +Subject: [PATCH] core/device: ignore failed uevents + +When udevd failed to process the device, SYSTEMD_ALIAS or any other +properties may contain invalid values. Let's refuse to handle the uevent. + +(cherry picked from commit e9336d6ac346df38d96c91ba0447b3c76ee6697b) + +Related: RHEL-5988 +--- + src/core/device.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/src/core/device.c b/src/core/device.c +index 09b7d56e1e..f007bdfd9b 100644 +--- a/src/core/device.c ++++ b/src/core/device.c +@@ -1108,6 +1108,25 @@ static int device_dispatch_io(sd_device_monitor *monitor, sd_device *dev, void * + if (action == SD_DEVICE_MOVE) + device_remove_old_on_move(m, dev); + ++ /* When udevd failed to process the device, SYSTEMD_ALIAS or any other properties may contain invalid ++ * values. Let's refuse to handle the uevent. */ ++ if (sd_device_get_property_value(dev, "UDEV_WORKER_FAILED", NULL) >= 0) { ++ int v; ++ ++ if (device_get_property_int(dev, "UDEV_WORKER_ERRNO", &v) >= 0) ++ log_device_warning_errno(dev, v, "systemd-udevd failed to process the device, ignoring: %m"); ++ else if (device_get_property_int(dev, "UDEV_WORKER_EXIT_STATUS", &v) >= 0) ++ log_device_warning(dev, "systemd-udevd failed to process the device with exit status %i, ignoring.", v); ++ else if (device_get_property_int(dev, "UDEV_WORKER_SIGNAL", &v) >= 0) { ++ const char *s; ++ (void) sd_device_get_property_value(dev, "UDEV_WORKER_SIGNAL_NAME", &s); ++ log_device_warning(dev, "systemd-udevd failed to process the device with signal %i(%s), ignoring.", v, strna(s)); ++ } else ++ log_device_warning(dev, "systemd-udevd failed to process the device with unknown result, ignoring."); ++ ++ return 0; ++ } ++ + /* A change event can signal that a device is becoming ready, in particular if the device is using + * the SYSTEMD_READY logic in udev so we need to reach the else block of the following if, even for + * change events */ diff --git a/0397-test-add-tests-for-failure-in-renaming-network-inter.patch b/0397-test-add-tests-for-failure-in-renaming-network-inter.patch new file mode 100644 index 0000000..9cc2e65 --- /dev/null +++ b/0397-test-add-tests-for-failure-in-renaming-network-inter.patch @@ -0,0 +1,99 @@ +From 9e9f53612dc2356796cffb25826008944aede0e3 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 16:02:09 +0900 +Subject: [PATCH] test: add tests for failure in renaming network interface + +(cherry picked from commit 2d0d75b279924934c4c8e9acbc48456b01b71f00) + +Related: RHEL-5988 +--- + test/units/testsuite-17.02.sh | 78 +++++++++++++++++++++++++++++++++++ + 1 file changed, 78 insertions(+) + +diff --git a/test/units/testsuite-17.02.sh b/test/units/testsuite-17.02.sh +index 7abbce7747..ed3b39d074 100755 +--- a/test/units/testsuite-17.02.sh ++++ b/test/units/testsuite-17.02.sh +@@ -102,4 +102,82 @@ timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /s + # cleanup + ip link del hoge + ++teardown_netif_renaming_conflict() { ++ set +ex ++ ++ if [[ -n "$KILL_PID" ]]; then ++ kill "$KILL_PID" ++ fi ++ ++ rm -rf "$TMPDIR" ++ ++ rm -f /run/udev/rules.d/50-testsuite.rules ++ udevadm control --reload --timeout=30 ++ ++ ip link del hoge ++ ip link del foobar ++} ++ ++test_netif_renaming_conflict() { ++ local since found= ++ ++ trap teardown_netif_renaming_conflict RETURN ++ ++ cat >/run/udev/rules.d/50-testsuite.rules <"$TMPDIR"/monitor.txt & ++ KILL_PID="$!" ++ ++ # make sure that 'udevadm monitor' actually monitor uevents ++ sleep 1 ++ ++ since="$(date '+%H:%M:%S')" ++ ++ # add another interface which will conflict with an existing interface ++ ip link add foobar type dummy ++ ++ for _ in {1..40}; do ++ if ( ++ grep -q 'ACTION=add' "$TMPDIR"/monitor.txt ++ grep -q 'DEVPATH=/devices/virtual/net/foobar' "$TMPDIR"/monitor.txt ++ grep -q 'SUBSYSTEM=net' "$TMPDIR"/monitor.txt ++ grep -q 'INTERFACE=foobar' "$TMPDIR"/monitor.txt ++ grep -q 'ID_NET_DRIVER=dummy' "$TMPDIR"/monitor.txt ++ grep -q 'ID_NET_NAME=foobar' "$TMPDIR"/monitor.txt ++ # Even when network interface renaming is failed, SYSTEMD_ALIAS with the conflicting name will be broadcast. ++ grep -q 'SYSTEMD_ALIAS=/sys/subsystem/net/devices/hoge' "$TMPDIR"/monitor.txt ++ grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt ++ grep -q 'UDEV_WORKER_ERRNO=17' "$TMPDIR"/monitor.txt ++ grep -q 'UDEV_WORKER_ERRNO_NAME=EEXIST' "$TMPDIR"/monitor.txt ++ ); then ++ cat "$TMPDIR"/monitor.txt ++ found=1 ++ break ++ fi ++ sleep .5 ++ done ++ test -n "$found" ++ ++ timeout 30 bash -c "while ! journalctl _PID=1 _COMM=systemd --since $since | grep -q 'foobar: systemd-udevd failed to process the device, ignoring: File exists'; do sleep 1; done" ++ # check if the invalid SYSTEMD_ALIAS property for the interface foobar is ignored by PID1 ++ assert_eq "$(systemctl show --property=SysFSPath --value /sys/subsystem/net/devices/hoge)" "/sys/devices/virtual/net/hoge" ++} ++ ++test_netif_renaming_conflict ++ + exit 0 diff --git a/0398-test-modernize-test-netlink.c.patch b/0398-test-modernize-test-netlink.c.patch new file mode 100644 index 0000000..0592d24 --- /dev/null +++ b/0398-test-modernize-test-netlink.c.patch @@ -0,0 +1,623 @@ +From a3c14074e6cd91053ffafb0eb4b16054564e4239 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 20:33:35 +0900 +Subject: [PATCH] test: modernize test-netlink.c + +(cherry picked from commit eafff21da2978bfa4c5c4171a595abaeb1d170dc) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/test-netlink.c | 362 ++++++++--------------- + 1 file changed, 116 insertions(+), 246 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c +index 2d93f9eba0..f740035639 100644 +--- a/src/libsystemd/sd-netlink/test-netlink.c ++++ b/src/libsystemd/sd-netlink/test-netlink.c +@@ -24,11 +24,12 @@ + #include "strv.h" + #include "tests.h" + +-static void test_message_link_bridge(sd_netlink *rtnl) { ++TEST(message_newlink_bridge) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + uint32_t cost; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 1) >= 0); + assert_se(sd_rtnl_message_link_set_family(message, AF_BRIDGE) >= 0); +@@ -44,99 +45,81 @@ static void test_message_link_bridge(sd_netlink *rtnl) { + assert_se(sd_netlink_message_exit_container(message) >= 0); + } + +-static void test_link_configure(sd_netlink *rtnl, int ifindex) { ++TEST(message_getlink) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; +- uint32_t mtu_out; +- const char *name_out; +- struct ether_addr mac_out; +- +- log_debug("/* %s */", __func__); +- +- /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ +- assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); +- +- assert_se(sd_netlink_call(rtnl, message, 0, &reply) == 1); +- +- assert_se(sd_netlink_message_read_string(reply, IFLA_IFNAME, &name_out) >= 0); +- assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &mac_out) >= 0); +- assert_se(sd_netlink_message_read_u32(reply, IFLA_MTU, &mtu_out) >= 0); +-} +- +-static void test_link_get(sd_netlink *rtnl, int ifindex) { +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; +- const char *str_data; ++ int ifindex; + uint8_t u8_data; ++ uint16_t u16_data; + uint32_t u32_data; ++ const char *str_data; + struct ether_addr eth_data; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); +- assert_se(m); ++ /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ ++ assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); ++ assert_se(sd_netlink_call(rtnl, message, 0, &reply) == 1); + +- assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); ++ /* u8 */ ++ assert_se(sd_netlink_message_read_u8(reply, IFLA_CARRIER, &u8_data) >= 0); ++ assert_se(sd_netlink_message_read_u8(reply, IFLA_OPERSTATE, &u8_data) >= 0); ++ assert_se(sd_netlink_message_read_u8(reply, IFLA_LINKMODE, &u8_data) >= 0); + +- assert_se(sd_netlink_message_read_string(r, IFLA_IFNAME, &str_data) == 0); ++ /* u16 */ ++ assert_se(sd_netlink_message_get_type(reply, &u16_data) >= 0); ++ assert_se(u16_data == RTM_NEWLINK); + +- assert_se(sd_netlink_message_read_u8(r, IFLA_CARRIER, &u8_data) == 0); +- assert_se(sd_netlink_message_read_u8(r, IFLA_OPERSTATE, &u8_data) == 0); +- assert_se(sd_netlink_message_read_u8(r, IFLA_LINKMODE, &u8_data) == 0); ++ /* u32 */ ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_MTU, &u32_data) >= 0); ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_GROUP, &u32_data) >= 0); ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_TXQLEN, &u32_data) >= 0); ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_NUM_TX_QUEUES, &u32_data) >= 0); ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_NUM_RX_QUEUES, &u32_data) >= 0); + +- assert_se(sd_netlink_message_read_u32(r, IFLA_MTU, &u32_data) == 0); +- assert_se(sd_netlink_message_read_u32(r, IFLA_GROUP, &u32_data) == 0); +- assert_se(sd_netlink_message_read_u32(r, IFLA_TXQLEN, &u32_data) == 0); +- assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_TX_QUEUES, &u32_data) == 0); +- assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_RX_QUEUES, &u32_data) == 0); ++ /* string */ ++ assert_se(sd_netlink_message_read_string(reply, IFLA_IFNAME, &str_data) >= 0); + +- assert_se(sd_netlink_message_read_ether_addr(r, IFLA_ADDRESS, ð_data) == 0); ++ /* ether_addr */ ++ assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, ð_data) >= 0); + } + +-static void test_address_get(sd_netlink *rtnl, int ifindex) { +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; ++TEST(message_address) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; ++ int ifindex; + struct in_addr in_data; + struct ifa_cacheinfo cache; + const char *label; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + +- assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0); +- assert_se(m); +- assert_se(sd_netlink_message_set_request_dump(m, true) >= 0); +- assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); ++ assert_se(sd_rtnl_message_new_addr(rtnl, &message, RTM_GETADDR, ifindex, AF_INET) >= 0); ++ assert_se(sd_netlink_message_set_request_dump(message, true) >= 0); ++ assert_se(sd_netlink_call(rtnl, message, 0, &reply) == 1); + +- assert_se(sd_netlink_message_read_in_addr(r, IFA_LOCAL, &in_data) == 0); +- assert_se(sd_netlink_message_read_in_addr(r, IFA_ADDRESS, &in_data) == 0); +- assert_se(sd_netlink_message_read_string(r, IFA_LABEL, &label) == 0); +- assert_se(sd_netlink_message_read_cache_info(r, IFA_CACHEINFO, &cache) == 0); ++ assert_se(sd_netlink_message_read_in_addr(reply, IFA_LOCAL, &in_data) >= 0); ++ assert_se(sd_netlink_message_read_in_addr(reply, IFA_ADDRESS, &in_data) >= 0); ++ assert_se(sd_netlink_message_read_string(reply, IFA_LABEL, &label) >= 0); ++ assert_se(sd_netlink_message_read_cache_info(reply, IFA_CACHEINFO, &cache) == 0); + } + +-static void test_route(sd_netlink *rtnl) { ++TEST(message_route) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + struct in_addr addr, addr_data; + uint32_t index = 2, u32_data; +- int r; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + +- r = sd_rtnl_message_new_route(rtnl, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC); +- if (r < 0) { +- log_error_errno(r, "Could not create RTM_NEWROUTE message: %m"); +- return; +- } ++ assert_se(sd_rtnl_message_new_route(rtnl, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC) >= 0); + + addr.s_addr = htobe32(INADDR_LOOPBACK); + +- r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr); +- if (r < 0) { +- log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); +- return; +- } +- +- r = sd_netlink_message_append_u32(req, RTA_OIF, index); +- if (r < 0) { +- log_error_errno(r, "Could not append RTA_OIF attribute: %m"); +- return; +- } ++ assert_se(sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr) >= 0); ++ assert_se(sd_netlink_message_append_u32(req, RTA_OIF, index) >= 0); + + assert_se(sd_netlink_message_rewind(req, rtnl) >= 0); + +@@ -149,135 +132,94 @@ static void test_route(sd_netlink *rtnl) { + assert_se((req = sd_netlink_message_unref(req)) == NULL); + } + +-static void test_multiple(void) { +- sd_netlink *rtnl1, *rtnl2; +- +- log_debug("/* %s */", __func__); +- +- assert_se(sd_netlink_open(&rtnl1) >= 0); +- assert_se(sd_netlink_open(&rtnl2) >= 0); +- +- rtnl1 = sd_netlink_unref(rtnl1); +- rtnl2 = sd_netlink_unref(rtnl2); +-} +- + static int link_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { +- char *ifname = userdata; + const char *data; + + assert_se(rtnl); + assert_se(m); +- assert_se(userdata); + +- log_info("%s: got link info about %s", __func__, ifname); +- free(ifname); ++ assert_se(streq_ptr(userdata, "foo")); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &data) >= 0); + assert_se(streq(data, "lo")); + ++ log_info("%s: got link info about %s", __func__, data); + return 1; + } + +-static void test_event_loop(int ifindex) { ++TEST(netlink_event_loop) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; +- char *ifname; +- +- log_debug("/* %s */", __func__); +- +- ifname = strdup("lo2"); +- assert_se(ifname); ++ _cleanup_free_ char *userdata = NULL; ++ int ifindex; + + assert_se(sd_netlink_open(&rtnl) >= 0); +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + +- assert_se(sd_netlink_call_async(rtnl, NULL, m, link_handler, NULL, ifname, 0, NULL) >= 0); ++ assert_se(userdata = strdup("foo")); + + assert_se(sd_event_default(&event) >= 0); +- + assert_se(sd_netlink_attach_event(rtnl, event, 0) >= 0); + ++ assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); ++ assert_se(sd_netlink_call_async(rtnl, NULL, m, link_handler, NULL, userdata, 0, NULL) >= 0); ++ + assert_se(sd_event_run(event, 0) >= 0); + + assert_se(sd_netlink_detach_event(rtnl) >= 0); +- + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + } + + static void test_async_destroy(void *userdata) { + } + +-static void test_async(int ifindex) { ++TEST(netlink_call_async) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; ++ _cleanup_free_ char *userdata = NULL; + sd_netlink_destroy_t destroy_callback; + const char *description; +- char *ifname; +- +- log_debug("/* %s */", __func__); +- +- ifname = strdup("lo"); +- assert_se(ifname); ++ int ifindex; + + assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); ++ assert_se(userdata = strdup("foo")); + +- assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler, test_async_destroy, ifname, 0, "hogehoge") >= 0); ++ assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); ++ assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler, test_async_destroy, userdata, 0, "hogehoge") >= 0); + + assert_se(sd_netlink_slot_get_netlink(slot) == rtnl); +- assert_se(sd_netlink_slot_get_userdata(slot) == ifname); +- assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 1); +- assert_se(destroy_callback == test_async_destroy); +- assert_se(sd_netlink_slot_get_floating(slot) == 0); +- assert_se(sd_netlink_slot_get_description(slot, &description) == 1); +- assert_se(streq(description, "hogehoge")); +- +- assert_se(sd_netlink_wait(rtnl, 0) >= 0); +- assert_se(sd_netlink_process(rtnl, &r) >= 0); +- +- assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +-} + +-static void test_slot_set(int ifindex) { +- _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; +- _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; +- sd_netlink_destroy_t destroy_callback; +- const char *description; +- char *ifname; +- +- log_debug("/* %s */", __func__); +- +- ifname = strdup("lo"); +- assert_se(ifname); +- +- assert_se(sd_netlink_open(&rtnl) >= 0); +- +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); +- +- assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler, NULL, NULL, 0, NULL) >= 0); ++ assert_se(sd_netlink_slot_get_userdata(slot) == userdata); ++ assert_se(sd_netlink_slot_set_userdata(slot, NULL) == userdata); ++ assert_se(sd_netlink_slot_get_userdata(slot) == NULL); ++ assert_se(sd_netlink_slot_set_userdata(slot, userdata) == NULL); ++ assert_se(sd_netlink_slot_get_userdata(slot) == userdata); + +- assert_se(sd_netlink_slot_get_netlink(slot) == rtnl); +- assert_se(!sd_netlink_slot_get_userdata(slot)); +- assert_se(!sd_netlink_slot_set_userdata(slot, ifname)); +- assert_se(sd_netlink_slot_get_userdata(slot) == ifname); +- assert_se(sd_netlink_slot_get_destroy_callback(slot, NULL) == 0); ++ assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 1); ++ assert_se(destroy_callback == test_async_destroy); ++ assert_se(sd_netlink_slot_set_destroy_callback(slot, NULL) >= 0); ++ assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 0); ++ assert_se(destroy_callback == NULL); + assert_se(sd_netlink_slot_set_destroy_callback(slot, test_async_destroy) >= 0); + assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 1); + assert_se(destroy_callback == test_async_destroy); ++ + assert_se(sd_netlink_slot_get_floating(slot) == 0); + assert_se(sd_netlink_slot_set_floating(slot, 1) == 1); + assert_se(sd_netlink_slot_get_floating(slot) == 1); +- assert_se(sd_netlink_slot_get_description(slot, NULL) == 0); +- assert_se(sd_netlink_slot_set_description(slot, "hogehoge") >= 0); ++ + assert_se(sd_netlink_slot_get_description(slot, &description) == 1); + assert_se(streq(description, "hogehoge")); ++ assert_se(sd_netlink_slot_set_description(slot, NULL) >= 0); ++ assert_se(sd_netlink_slot_get_description(slot, &description) == 0); ++ assert_se(description == NULL); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); +- assert_se(sd_netlink_process(rtnl, &r) >= 0); ++ assert_se(sd_netlink_process(rtnl, &reply) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + } +@@ -322,23 +264,21 @@ static void test_async_object_destroy(void *userdata) { + test_async_object_unref(t); + } + +-static void test_async_destroy_callback(int ifindex) { ++TEST(async_destroy_callback) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; + _cleanup_(test_async_object_unrefp) struct test_async_object *t = NULL; + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; +- char *ifname; ++ int ifindex; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + + assert_se(t = new(struct test_async_object, 1)); +- assert_se(ifname = strdup("lo")); + *t = (struct test_async_object) { + .n_ref = 1, +- .ifname = ifname, + }; +- +- assert_se(sd_netlink_open(&rtnl) >= 0); ++ assert_se(t->ifname = strdup("lo")); + + /* destroy callback is called after processing message */ + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); +@@ -349,7 +289,7 @@ static void test_async_destroy_callback(int ifindex) { + assert_se(t->n_ref == 2); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); +- assert_se(sd_netlink_process(rtnl, &r) == 1); ++ assert_se(sd_netlink_process(rtnl, &reply) == 1); + assert_se(t->n_ref == 1); + + assert_se(!sd_netlink_message_unref(m)); +@@ -394,14 +334,13 @@ static int pipe_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) + return 1; + } + +-static void test_pipe(int ifindex) { ++TEST(pipe) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m1 = NULL, *m2 = NULL; +- int counter = 0; +- +- log_debug("/* %s */", __func__); ++ int ifindex, counter = 0; + + assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + + assert_se(sd_rtnl_message_new_link(rtnl, &m1, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_rtnl_message_new_link(rtnl, &m2, RTM_GETLINK, ifindex) >= 0); +@@ -420,13 +359,14 @@ static void test_pipe(int ifindex) { + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + } + +-static void test_container(sd_netlink *rtnl) { ++TEST(message_container) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + uint16_t u16_data; + uint32_t u32_data; + const char *string_data; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0) >= 0); + +@@ -434,9 +374,7 @@ static void test_container(sd_netlink *rtnl) { + assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "vlan") >= 0); + assert_se(sd_netlink_message_append_u16(m, IFLA_VLAN_ID, 100) >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); +- assert_se(sd_netlink_message_append_string(m, IFLA_INFO_KIND, "vlan") >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); +- assert_se(sd_netlink_message_close_container(m) == -EINVAL); + + assert_se(sd_netlink_message_rewind(m, rtnl) >= 0); + +@@ -453,16 +391,12 @@ static void test_container(sd_netlink *rtnl) { + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(sd_netlink_message_read_u32(m, IFLA_LINKINFO, &u32_data) < 0); +- +- assert_se(sd_netlink_message_exit_container(m) == -EINVAL); + } + +-static void test_match(void) { ++TEST(sd_netlink_add_match) { + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *s1 = NULL, *s2 = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + +- log_debug("/* %s */", __func__); +- + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_netlink_add_match(rtnl, &s1, RTM_NEWLINK, link_handler, NULL, NULL, NULL) >= 0); +@@ -475,17 +409,17 @@ static void test_match(void) { + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + } + +-static void test_get_addresses(sd_netlink *rtnl) { ++TEST(dump_addresses) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; +- sd_netlink_message *m; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC) >= 0); + assert_se(sd_netlink_message_set_request_dump(req, true) >= 0); + assert_se(sd_netlink_call(rtnl, req, 0, &reply) >= 0); + +- for (m = reply; m; m = sd_netlink_message_next(m)) { ++ for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { + uint16_t type; + unsigned char scope, flags; + int family, ifindex; +@@ -505,21 +439,20 @@ static void test_get_addresses(sd_netlink *rtnl) { + } + } + +-static void test_message(sd_netlink *rtnl) { ++TEST(sd_netlink_message_get_errno) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(message_new_synthetic_error(rtnl, -ETIMEDOUT, 1, &m) >= 0); + assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT); + } + +-static void test_array(void) { ++TEST(message_array) { + _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + +- log_debug("/* %s */", __func__); +- + assert_se(sd_genl_socket_open(&genl) >= 0); + assert_se(sd_genl_message_new(genl, CTRL_GENL_NAME, CTRL_CMD_GETFAMILY, &m) >= 0); + +@@ -557,12 +490,13 @@ static void test_array(void) { + assert_se(sd_netlink_message_exit_container(m) >= 0); + } + +-static void test_strv(sd_netlink *rtnl) { ++TEST(message_strv) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + _cleanup_strv_free_ char **names_in = NULL, **names_out; + const char *p; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINKPROP, 1) >= 0); + +@@ -624,7 +558,7 @@ static int genl_ctrl_match_callback(sd_netlink *genl, sd_netlink_message *m, voi + return 0; + } + +-static void test_genl(void) { ++TEST(genl) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; +@@ -632,8 +566,6 @@ static void test_genl(void) { + uint8_t cmd; + int r; + +- log_debug("/* %s */", __func__); +- + assert_se(sd_genl_socket_open(&genl) >= 0); + assert_se(sd_event_default(&event) >= 0); + assert_se(sd_netlink_attach_event(genl, event, 0) >= 0); +@@ -669,15 +601,17 @@ static void test_genl(void) { + } + } + +-static void test_rtnl_set_link_name(sd_netlink *rtnl, int ifindex) { ++TEST(rtnl_set_link_name) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_strv_free_ char **alternative_names = NULL; +- int r; +- +- log_debug("/* %s */", __func__); ++ int ifindex, r; + + if (geteuid() != 0) + return (void) log_tests_skipped("not root"); + ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); ++ + /* Test that the new name (which is currently an alternative name) is + * restored as an alternative name on error. Create an error by using + * an invalid device name, namely one that exceeds IFNAMSIZ +@@ -693,68 +627,4 @@ static void test_rtnl_set_link_name(sd_netlink *rtnl, int ifindex) { + assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); + } + +-int main(void) { +- sd_netlink *rtnl; +- sd_netlink_message *m; +- sd_netlink_message *r; +- const char *string_data; +- int if_loopback; +- uint16_t type; +- +- test_setup_logging(LOG_DEBUG); +- +- test_match(); +- test_multiple(); +- +- assert_se(sd_netlink_open(&rtnl) >= 0); +- assert_se(rtnl); +- +- test_route(rtnl); +- test_message(rtnl); +- test_container(rtnl); +- test_array(); +- test_strv(rtnl); +- +- if_loopback = (int) if_nametoindex("lo"); +- assert_se(if_loopback > 0); +- +- test_async(if_loopback); +- test_slot_set(if_loopback); +- test_async_destroy_callback(if_loopback); +- test_pipe(if_loopback); +- test_event_loop(if_loopback); +- test_link_configure(rtnl, if_loopback); +- test_rtnl_set_link_name(rtnl, if_loopback); +- +- test_get_addresses(rtnl); +- test_message_link_bridge(rtnl); +- +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, if_loopback) >= 0); +- assert_se(m); +- +- assert_se(sd_netlink_message_get_type(m, &type) >= 0); +- assert_se(type == RTM_GETLINK); +- +- assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &string_data) == -EPERM); +- +- assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); +- assert_se(sd_netlink_message_get_type(r, &type) >= 0); +- assert_se(type == RTM_NEWLINK); +- +- assert_se((r = sd_netlink_message_unref(r)) == NULL); +- +- assert_se(sd_netlink_call(rtnl, m, -1, &r) == -EPERM); +- assert_se((m = sd_netlink_message_unref(m)) == NULL); +- assert_se((r = sd_netlink_message_unref(r)) == NULL); +- +- test_link_get(rtnl, if_loopback); +- test_address_get(rtnl, if_loopback); +- +- assert_se((m = sd_netlink_message_unref(m)) == NULL); +- assert_se((r = sd_netlink_message_unref(r)) == NULL); +- assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +- +- test_genl(); +- +- return EXIT_SUCCESS; +-} ++DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch b/0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch new file mode 100644 index 0000000..9df6628 --- /dev/null +++ b/0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch @@ -0,0 +1,105 @@ +From 49fa9a23e444f864a4f06fb0c7b1f54ff0513206 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 21:00:53 +0900 +Subject: [PATCH] test-netlink: use dummy interface to test assigning new + interface name + +Fixes #25981. + +(cherry picked from commit 5ccbe7fb197b01e0cf1f1ab523703274ef552555) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/test-netlink.c | 59 ++++++++++++++++++++++-- + 1 file changed, 55 insertions(+), 4 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c +index f740035639..9ad8ecf320 100644 +--- a/src/libsystemd/sd-netlink/test-netlink.c ++++ b/src/libsystemd/sd-netlink/test-netlink.c +@@ -601,30 +601,81 @@ TEST(genl) { + } + } + ++static void remove_dummy_interfacep(int *ifindex) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; ++ ++ if (!ifindex || *ifindex <= 0) ++ return; ++ ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ++ assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_DELLINK, *ifindex) >= 0); ++ assert_se(sd_netlink_call(rtnl, message, 0, NULL) == 1); ++} ++ + TEST(rtnl_set_link_name) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; ++ _cleanup_(remove_dummy_interfacep) int ifindex = 0; + _cleanup_strv_free_ char **alternative_names = NULL; +- int ifindex, r; ++ int r; + + if (geteuid() != 0) + return (void) log_tests_skipped("not root"); + + assert_se(sd_netlink_open(&rtnl) >= 0); +- ifindex = (int) if_nametoindex("lo"); ++ ++ assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 0) >= 0); ++ assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, "test-netlink") >= 0); ++ assert_se(sd_netlink_message_open_container(message, IFLA_LINKINFO) >= 0); ++ assert_se(sd_netlink_message_append_string(message, IFLA_INFO_KIND, "dummy") >= 0); ++ r = sd_netlink_call(rtnl, message, 0, &reply); ++ if (r == -EPERM) ++ return (void) log_tests_skipped("missing required capabilities"); ++ if (r == -EOPNOTSUPP) ++ return (void) log_tests_skipped("dummy network interface is not supported"); ++ assert_se(r >= 0); ++ ++ message = sd_netlink_message_unref(message); ++ reply = sd_netlink_message_unref(reply); ++ ++ assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, 0) >= 0); ++ assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, "test-netlink") >= 0); ++ assert_se(sd_netlink_call(rtnl, message, 0, &reply) == 1); ++ ++ assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); ++ assert_se(ifindex > 0); + + /* Test that the new name (which is currently an alternative name) is + * restored as an alternative name on error. Create an error by using + * an invalid device name, namely one that exceeds IFNAMSIZ + * (alternative names can exceed IFNAMSIZ, but not regular names). */ +- r = rtnl_set_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")); ++ r = rtnl_set_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename", "test-shortname")); + if (r == -EPERM) + return (void) log_tests_skipped("missing required capabilities"); +- ++ if (r == -EOPNOTSUPP) ++ return (void) log_tests_skipped("alternative name is not supported"); + assert_se(r >= 0); ++ ++ assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); ++ assert_se(strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(strv_contains(alternative_names, "test-shortname")); ++ + assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename") == -EINVAL); ++ assert_se(rtnl_set_link_name(&rtnl, ifindex, "test-shortname") >= 0); ++ ++ alternative_names = strv_free(alternative_names); + assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); + assert_se(strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(!strv_contains(alternative_names, "test-shortname")); ++ + assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); ++ ++ alternative_names = strv_free(alternative_names); ++ assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); ++ assert_se(!strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(!strv_contains(alternative_names, "test-shortname")); + } + + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch b/0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch new file mode 100644 index 0000000..88c6416 --- /dev/null +++ b/0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch @@ -0,0 +1,26 @@ +From 2deb458c5fd4ac318018b8464fa677dc4570ba61 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 16:34:31 +0900 +Subject: [PATCH] udev: use SYNTHETIC_ERRNO() at one more place + +(cherry picked from commit b3cfe5900108df81fbf547b297d51ac8c7359a9b) + +Related: RHEL-5988 +--- + src/udev/udevadm-test-builtin.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c +index 81b633611e..5570eec789 100644 +--- a/src/udev/udevadm-test-builtin.c ++++ b/src/udev/udevadm-test-builtin.c +@@ -87,8 +87,7 @@ int builtin_main(int argc, char *argv[], void *userdata) { + + cmd = udev_builtin_lookup(arg_command); + if (cmd < 0) { +- log_error("Unknown command '%s'", arg_command); +- r = -EINVAL; ++ r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command '%s'", arg_command); + goto finish; + } + diff --git a/0401-udev-make-udev_builtin_run-take-UdevEvent.patch b/0401-udev-make-udev_builtin_run-take-UdevEvent.patch new file mode 100644 index 0000000..26cb04d --- /dev/null +++ b/0401-udev-make-udev_builtin_run-take-UdevEvent.patch @@ -0,0 +1,351 @@ +From 09bc1c97130c4e646233ee3ea27ba03c226117d7 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 11:29:49 +0900 +Subject: [PATCH] udev: make udev_builtin_run() take UdevEvent* + +No functional change, preparation for later commits. + +(cherry picked from commit 5668f3a7cfccca704ea8e8bdc84ca7e17a5f101e) + +Related: RHEL-5988 +--- + src/udev/udev-builtin-blkid.c | 3 ++- + src/udev/udev-builtin-btrfs.c | 3 ++- + src/udev/udev-builtin-hwdb.c | 3 ++- + src/udev/udev-builtin-input_id.c | 6 ++---- + src/udev/udev-builtin-keyboard.c | 3 ++- + src/udev/udev-builtin-kmod.c | 5 ++--- + src/udev/udev-builtin-net_id.c | 3 ++- + src/udev/udev-builtin-net_setup_link.c | 7 ++++--- + src/udev/udev-builtin-path_id.c | 5 ++--- + src/udev/udev-builtin-uaccess.c | 3 ++- + src/udev/udev-builtin-usb_id.c | 5 ++--- + src/udev/udev-builtin.c | 7 ++++--- + src/udev/udev-builtin.h | 6 ++++-- + src/udev/udev-event.c | 2 +- + src/udev/udev-rules.c | 2 +- + src/udev/udevadm-test-builtin.c | 10 ++++++++-- + 16 files changed, 42 insertions(+), 31 deletions(-) + +diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c +index 9f5646ffdd..63d1bd579d 100644 +--- a/src/udev/udev-builtin-blkid.c ++++ b/src/udev/udev-builtin-blkid.c +@@ -237,7 +237,8 @@ static int probe_superblocks(blkid_probe pr) { + return blkid_do_safeprobe(pr); + } + +-static int builtin_blkid(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_blkid(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + const char *devnode, *root_partition = NULL, *data, *name; + _cleanup_(blkid_free_probep) blkid_probe pr = NULL; + bool noraid = false, is_gpt = false; +diff --git a/src/udev/udev-builtin-btrfs.c b/src/udev/udev-builtin-btrfs.c +index 8cd627807f..b36eadb47a 100644 +--- a/src/udev/udev-builtin-btrfs.c ++++ b/src/udev/udev-builtin-btrfs.c +@@ -13,7 +13,8 @@ + #include "udev-builtin.h" + #include "util.h" + +-static int builtin_btrfs(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_btrfs(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + struct btrfs_ioctl_vol_args args = {}; + _cleanup_close_ int fd = -1; + int r; +diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c +index 8d652e46fe..19e07e734f 100644 +--- a/src/udev/udev-builtin-hwdb.c ++++ b/src/udev/udev-builtin-hwdb.c +@@ -118,7 +118,7 @@ next: + return r; + } + +-static int builtin_hwdb(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_hwdb(UdevEvent *event, int argc, char *argv[], bool test) { + static const struct option options[] = { + { "filter", required_argument, NULL, 'f' }, + { "device", required_argument, NULL, 'd' }, +@@ -131,6 +131,7 @@ static int builtin_hwdb(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[ + const char *subsystem = NULL; + const char *prefix = NULL; + _cleanup_(sd_device_unrefp) sd_device *srcdev = NULL; ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; + + if (!hwdb) +diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c +index 0742120248..4322ce04b3 100644 +--- a/src/udev/udev-builtin-input_id.c ++++ b/src/udev/udev-builtin-input_id.c +@@ -356,8 +356,8 @@ static bool test_key(sd_device *dev, + return found; + } + +-static int builtin_input_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { +- sd_device *pdev; ++static int builtin_input_id(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *pdev, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + unsigned long bitmask_ev[NBITS(EV_MAX)]; + unsigned long bitmask_abs[NBITS(ABS_MAX)]; + unsigned long bitmask_key[NBITS(KEY_MAX)]; +@@ -367,8 +367,6 @@ static int builtin_input_id(sd_device *dev, sd_netlink **rtnl, int argc, char *a + bool is_pointer; + bool is_key; + +- assert(dev); +- + /* walk up the parental chain until we find the real input device; the + * argument is very likely a subdevice of this, like eventN */ + for (pdev = dev; pdev; ) { +diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c +index 6dd9eebd93..dac087a9e6 100644 +--- a/src/udev/udev-builtin-keyboard.c ++++ b/src/udev/udev-builtin-keyboard.c +@@ -159,7 +159,8 @@ static int set_trackpoint_sensitivity(sd_device *dev, const char *value) { + return 0; + } + +-static int builtin_keyboard(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_keyboard(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + unsigned release[1024]; + unsigned release_count = 0; + _cleanup_close_ int fd = -1; +diff --git a/src/udev/udev-builtin-kmod.c b/src/udev/udev-builtin-kmod.c +index eade042f35..3ab5c485f8 100644 +--- a/src/udev/udev-builtin-kmod.c ++++ b/src/udev/udev-builtin-kmod.c +@@ -22,11 +22,10 @@ _printf_(6,0) static void udev_kmod_log(void *data, int priority, const char *fi + log_internalv(priority, 0, file, line, fn, format, args); + } + +-static int builtin_kmod(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_kmod(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; + +- assert(dev); +- + if (!ctx) + return 0; + +diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c +index c57568f8cb..cecf854b98 100644 +--- a/src/udev/udev-builtin-net_id.c ++++ b/src/udev/udev-builtin-net_id.c +@@ -1109,7 +1109,8 @@ static int get_link_info(sd_device *dev, LinkInfo *info) { + return 0; + } + +-static int builtin_net_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_net_id(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + const char *prefix; + NetNames names = {}; + LinkInfo info = { +diff --git a/src/udev/udev-builtin-net_setup_link.c b/src/udev/udev-builtin-net_setup_link.c +index ea7b1c5f60..4bf42cd492 100644 +--- a/src/udev/udev-builtin-net_setup_link.c ++++ b/src/udev/udev-builtin-net_setup_link.c +@@ -10,14 +10,15 @@ + + static LinkConfigContext *ctx = NULL; + +-static int builtin_net_setup_link(sd_device *dev, sd_netlink **rtnl, int argc, char **argv, bool test) { ++static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + _cleanup_(link_freep) Link *link = NULL; + int r; + + if (argc > 1) + return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + +- r = link_new(ctx, rtnl, dev, &link); ++ r = link_new(ctx, &event->rtnl, dev, &link); + if (r == -ENODEV) { + log_device_debug_errno(dev, r, "Link vanished while getting information, ignoring."); + return 0; +@@ -38,7 +39,7 @@ static int builtin_net_setup_link(sd_device *dev, sd_netlink **rtnl, int argc, c + return log_device_error_errno(dev, r, "Failed to get link config: %m"); + } + +- r = link_apply_config(ctx, rtnl, link); ++ r = link_apply_config(ctx, &event->rtnl, link); + if (r == -ENODEV) + log_device_debug_errno(dev, r, "Link vanished while applying configuration, ignoring."); + else if (r < 0) +diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c +index d58a3d5d60..6f4d7cbc5b 100644 +--- a/src/udev/udev-builtin-path_id.c ++++ b/src/udev/udev-builtin-path_id.c +@@ -581,15 +581,14 @@ static int find_real_nvme_parent(sd_device *dev, sd_device **ret) { + return 0; + } + +-static int builtin_path_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + _cleanup_(sd_device_unrefp) sd_device *dev_other_branch = NULL; + _cleanup_free_ char *path = NULL, *compat_path = NULL; + bool supported_transport = false, supported_parent = false; + const char *subsystem; + int r; + +- assert(dev); +- + /* walk up the chain of devices and compose path */ + for (sd_device *parent = dev; parent; ) { + const char *subsys, *sysname; +diff --git a/src/udev/udev-builtin-uaccess.c b/src/udev/udev-builtin-uaccess.c +index 6e73d99375..36c993cbb0 100644 +--- a/src/udev/udev-builtin-uaccess.c ++++ b/src/udev/udev-builtin-uaccess.c +@@ -16,7 +16,8 @@ + #include "log.h" + #include "udev-builtin.h" + +-static int builtin_uaccess(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_uaccess(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + const char *path = NULL, *seat; + bool changed_acl = false; + uid_t uid; +diff --git a/src/udev/udev-builtin-usb_id.c b/src/udev/udev-builtin-usb_id.c +index 847c2b8316..f3fc3cfdb3 100644 +--- a/src/udev/udev-builtin-usb_id.c ++++ b/src/udev/udev-builtin-usb_id.c +@@ -224,7 +224,8 @@ static int dev_if_packed_info(sd_device *dev, char *ifs_str, size_t len) { + * 6.) If the device supplies a serial number, this number + * is concatenated with the identification with an underscore '_'. + */ +-static int builtin_usb_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_usb_id(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + char vendor_str[64] = ""; + char vendor_str_enc[256]; + const char *vendor_id; +@@ -250,8 +251,6 @@ static int builtin_usb_id(sd_device *dev, sd_netlink **rtnl, int argc, char *arg + const char *syspath, *sysname, *devtype, *interface_syspath; + int r; + +- assert(dev); +- + r = sd_device_get_syspath(dev, &syspath); + if (r < 0) + return r; +diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c +index c98c6fa714..c84db8855c 100644 +--- a/src/udev/udev-builtin.c ++++ b/src/udev/udev-builtin.c +@@ -98,11 +98,12 @@ UdevBuiltinCommand udev_builtin_lookup(const char *command) { + return _UDEV_BUILTIN_INVALID; + } + +-int udev_builtin_run(sd_device *dev, sd_netlink **rtnl, UdevBuiltinCommand cmd, const char *command, bool test) { ++int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *command, bool test) { + _cleanup_strv_free_ char **argv = NULL; + int r; + +- assert(dev); ++ assert(event); ++ assert(event->dev); + assert(cmd >= 0 && cmd < _UDEV_BUILTIN_MAX); + assert(command); + +@@ -115,7 +116,7 @@ int udev_builtin_run(sd_device *dev, sd_netlink **rtnl, UdevBuiltinCommand cmd, + + /* we need '0' here to reset the internal state */ + optind = 0; +- return builtins[cmd]->cmd(dev, rtnl, strv_length(argv), argv, test); ++ return builtins[cmd]->cmd(event, strv_length(argv), argv, test); + } + + int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val) { +diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h +index bcfec03aae..919d51e798 100644 +--- a/src/udev/udev-builtin.h ++++ b/src/udev/udev-builtin.h +@@ -6,6 +6,8 @@ + #include "sd-device.h" + #include "sd-netlink.h" + ++#include "udev-event.h" ++ + typedef enum UdevBuiltinCommand { + #if HAVE_BLKID + UDEV_BUILTIN_BLKID, +@@ -30,7 +32,7 @@ typedef enum UdevBuiltinCommand { + + typedef struct UdevBuiltin { + const char *name; +- int (*cmd)(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test); ++ int (*cmd)(UdevEvent *event, int argc, char *argv[], bool test); + const char *help; + int (*init)(void); + void (*exit)(void); +@@ -74,7 +76,7 @@ void udev_builtin_exit(void); + UdevBuiltinCommand udev_builtin_lookup(const char *command); + const char *udev_builtin_name(UdevBuiltinCommand cmd); + bool udev_builtin_run_once(UdevBuiltinCommand cmd); +-int udev_builtin_run(sd_device *dev, sd_netlink **rtnl, UdevBuiltinCommand cmd, const char *command, bool test); ++int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *command, bool test); + void udev_builtin_list(void); + bool udev_builtin_should_reload(void); + int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val); +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index fab454ae37..cf90d6f205 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -1200,7 +1200,7 @@ void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec, int timeout_s + + if (builtin_cmd != _UDEV_BUILTIN_INVALID) { + log_device_debug(event->dev, "Running built-in command \"%s\"", command); +- r = udev_builtin_run(event->dev, &event->rtnl, builtin_cmd, command, false); ++ r = udev_builtin_run(event, builtin_cmd, command, false); + if (r < 0) + log_device_debug_errno(event->dev, r, "Failed to run built-in command \"%s\", ignoring: %m", command); + } else { +diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c +index a8473041c3..9336ce1cd3 100644 +--- a/src/udev/udev-rules.c ++++ b/src/udev/udev-rules.c +@@ -1972,7 +1972,7 @@ static int udev_rule_apply_token_to_event( + + log_rule_debug(dev, rules, "Importing properties from results of builtin command '%s'", buf); + +- r = udev_builtin_run(dev, &event->rtnl, cmd, buf, false); ++ r = udev_builtin_run(event, cmd, buf, false); + if (r < 0) { + /* remember failure */ + log_rule_debug_errno(dev, rules, r, "Failed to run builtin '%s': %m", buf); +diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c +index 5570eec789..5d1fafbd03 100644 +--- a/src/udev/udevadm-test-builtin.c ++++ b/src/udev/udevadm-test-builtin.c +@@ -72,7 +72,7 @@ static int parse_argv(int argc, char *argv[]) { + } + + int builtin_main(int argc, char *argv[], void *userdata) { +- _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; ++ _cleanup_(udev_event_freep) UdevEvent *event = NULL; + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + UdevBuiltinCommand cmd; + int r; +@@ -97,7 +97,13 @@ int builtin_main(int argc, char *argv[], void *userdata) { + goto finish; + } + +- r = udev_builtin_run(dev, &rtnl, cmd, arg_command, true); ++ event = udev_event_new(dev, 0, NULL, LOG_DEBUG); ++ if (!event) { ++ r = log_oom(); ++ goto finish; ++ } ++ ++ r = udev_builtin_run(event, cmd, arg_command, true); + if (r < 0) + log_debug_errno(r, "Builtin command '%s' fails: %m", arg_command); + diff --git a/0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch b/0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch new file mode 100644 index 0000000..4663dab --- /dev/null +++ b/0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch @@ -0,0 +1,26 @@ +From 87a2e6ccd7989f2b271f557c6303a4eb412a03cb Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 11:46:11 +0900 +Subject: [PATCH] udev/net: verify ID_NET_XYZ before trying to assign it as an + alternative name + +(cherry picked from commit e65c6c1baa8ea905f7e5bad3b8486d509775ec6a) + +Related: RHEL-5988 +--- + src/udev/net/link-config.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index 5d28526527..4fcf373f8e 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -834,7 +834,7 @@ static int link_apply_alternative_names(Link *link, sd_netlink **rtnl) { + default: + assert_not_reached(); + } +- if (!isempty(n)) { ++ if (ifname_valid_full(n, IFNAME_VALID_ALTERNATIVE)) { + r = strv_extend(&altnames, n); + if (r < 0) + return log_oom(); diff --git a/0403-udev-net-generate-new-network-interface-name-only-on.patch b/0403-udev-net-generate-new-network-interface-name-only-on.patch new file mode 100644 index 0000000..441a852 --- /dev/null +++ b/0403-udev-net-generate-new-network-interface-name-only-on.patch @@ -0,0 +1,29 @@ +From dd4c492721ed4be1b4c26cd937566dac2e97ba19 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 13:05:09 +0900 +Subject: [PATCH] udev/net: generate new network interface name only on add + uevent + +On other uevents, the name will be anyway ignored in rename_netif() in +udev-event.c. + +(cherry picked from commit cd941e6596adba6bb139c387ae596f26b35701f7) + +Related: RHEL-5988 +--- + src/udev/net/link-config.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index 4fcf373f8e..c9789bcb7c 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -722,7 +722,7 @@ static int link_generate_new_name(Link *link) { + config = link->config; + device = link->device; + +- if (link->action == SD_DEVICE_MOVE) { ++ if (link->action != SD_DEVICE_ADD) { + log_link_debug(link, "Skipping to apply Name= and NamePolicy= on '%s' uevent.", + device_action_to_string(link->action)); + goto no_rename; diff --git a/0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch b/0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch new file mode 100644 index 0000000..934feb2 --- /dev/null +++ b/0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch @@ -0,0 +1,191 @@ +From ea5725b1e621c2733c28f818c3a58615a385337e Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 13:29:37 +0900 +Subject: [PATCH] sd-netlink: make rtnl_set_link_name() optionally append + alternative names + +(cherry picked from commit 81824455008070253c62bf5c27187028ba8e7e99) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/netlink-util.c | 89 ++++++++++++++++++------ + src/libsystemd/sd-netlink/netlink-util.h | 5 +- + src/libsystemd/sd-netlink/test-netlink.c | 6 +- + src/udev/udev-event.c | 2 +- + 4 files changed, 78 insertions(+), 24 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c +index cfcf2578d6..5438737b42 100644 +--- a/src/libsystemd/sd-netlink/netlink-util.c ++++ b/src/libsystemd/sd-netlink/netlink-util.c +@@ -11,44 +11,93 @@ + #include "process-util.h" + #include "strv.h" + +-int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { ++static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; +- _cleanup_strv_free_ char **alternative_names = NULL; +- bool altname_deleted = false; + int r; + + assert(rtnl); + assert(ifindex > 0); + assert(name); + +- if (!ifname_valid(name)) ++ /* Assign the requested name. */ ++ r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); ++ if (r < 0) ++ return r; ++ ++ r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); ++ if (r < 0) ++ return r; ++ ++ return sd_netlink_call(*rtnl, message, 0, NULL); ++} ++ ++int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const *alternative_names) { ++ _cleanup_strv_free_ char **original_altnames = NULL, **new_altnames = NULL; ++ bool altname_deleted = false; ++ int r; ++ ++ assert(rtnl); ++ assert(ifindex > 0); ++ ++ if (isempty(name) && strv_isempty(alternative_names)) ++ return 0; ++ ++ if (name && !ifname_valid(name)) + return -EINVAL; + +- r = rtnl_get_link_alternative_names(rtnl, ifindex, &alternative_names); ++ /* If the requested name is already assigned as an alternative name, then first drop it. */ ++ r = rtnl_get_link_alternative_names(rtnl, ifindex, &original_altnames); + if (r < 0) + log_debug_errno(r, "Failed to get alternative names on network interface %i, ignoring: %m", + ifindex); + +- if (strv_contains(alternative_names, name)) { +- r = rtnl_delete_link_alternative_names(rtnl, ifindex, STRV_MAKE(name)); +- if (r < 0) +- return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", +- name, ifindex); ++ if (name) { ++ if (strv_contains(original_altnames, name)) { ++ r = rtnl_delete_link_alternative_names(rtnl, ifindex, STRV_MAKE(name)); ++ if (r < 0) ++ return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", ++ name, ifindex); ++ ++ altname_deleted = true; ++ } + +- altname_deleted = true; ++ r = set_link_name(rtnl, ifindex, name); ++ if (r < 0) ++ goto fail; + } + +- r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); +- if (r < 0) +- goto fail; ++ /* Filter out already assigned names from requested alternative names. Also, dedup the request. */ ++ STRV_FOREACH(a, alternative_names) { ++ if (streq_ptr(name, *a)) ++ continue; + +- r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); +- if (r < 0) +- goto fail; ++ if (strv_contains(original_altnames, *a)) ++ continue; + +- r = sd_netlink_call(*rtnl, message, 0, NULL); +- if (r < 0) +- goto fail; ++ if (strv_contains(new_altnames, *a)) ++ continue; ++ ++ if (!ifname_valid_full(*a, IFNAME_VALID_ALTERNATIVE)) ++ continue; ++ ++ r = strv_extend(&new_altnames, *a); ++ if (r < 0) ++ return r; ++ } ++ ++ strv_sort(new_altnames); ++ ++ /* Finally, assign alternative names. */ ++ r = rtnl_set_link_alternative_names(rtnl, ifindex, new_altnames); ++ if (r == -EEXIST) /* Already assigned to another interface? */ ++ STRV_FOREACH(a, new_altnames) { ++ r = rtnl_set_link_alternative_names(rtnl, ifindex, STRV_MAKE(*a)); ++ if (r < 0) ++ log_debug_errno(r, "Failed to assign '%s' as an alternative name on network interface %i, ignoring: %m", ++ *a, ifindex); ++ } ++ else if (r < 0) ++ log_debug_errno(r, "Failed to assign alternative names on network interface %i, ignoring: %m", ifindex); + + return 0; + +diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h +index d14392a018..888e28642d 100644 +--- a/src/libsystemd/sd-netlink/netlink-util.h ++++ b/src/libsystemd/sd-netlink/netlink-util.h +@@ -29,7 +29,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(MultipathRoute*, multipath_route_free); + + int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret); + +-int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); ++int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const* alternative_names); ++static inline int rtnl_append_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names) { ++ return rtnl_set_link_name(rtnl, ifindex, NULL, alternative_names); ++} + int rtnl_set_link_properties( + sd_netlink **rtnl, + int ifindex, +diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c +index 9ad8ecf320..43124b99ae 100644 +--- a/src/libsystemd/sd-netlink/test-netlink.c ++++ b/src/libsystemd/sd-netlink/test-netlink.c +@@ -662,12 +662,13 @@ TEST(rtnl_set_link_name) { + assert_se(strv_contains(alternative_names, "testlongalternativename")); + assert_se(strv_contains(alternative_names, "test-shortname")); + +- assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename") == -EINVAL); +- assert_se(rtnl_set_link_name(&rtnl, ifindex, "test-shortname") >= 0); ++ assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename", NULL) == -EINVAL); ++ assert_se(rtnl_set_link_name(&rtnl, ifindex, "test-shortname", STRV_MAKE("testlongalternativename", "test-shortname", "test-additional-name")) >= 0); + + alternative_names = strv_free(alternative_names); + assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); + assert_se(strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(strv_contains(alternative_names, "test-additional-name")); + assert_se(!strv_contains(alternative_names, "test-shortname")); + + assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); +@@ -675,6 +676,7 @@ TEST(rtnl_set_link_name) { + alternative_names = strv_free(alternative_names); + assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); + assert_se(!strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(strv_contains(alternative_names, "test-additional-name")); + assert_se(!strv_contains(alternative_names, "test-shortname")); + } + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index cf90d6f205..2662806d61 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -980,7 +980,7 @@ static int rename_netif(UdevEvent *event) { + goto revert; + } + +- r = rtnl_set_link_name(&event->rtnl, ifindex, event->name); ++ r = rtnl_set_link_name(&event->rtnl, ifindex, event->name, NULL); + if (r < 0) { + if (r == -EBUSY) { + log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", diff --git a/0405-udev-net-assign-alternative-names-only-on-add-uevent.patch b/0405-udev-net-assign-alternative-names-only-on-add-uevent.patch new file mode 100644 index 0000000..a7946d0 --- /dev/null +++ b/0405-udev-net-assign-alternative-names-only-on-add-uevent.patch @@ -0,0 +1,233 @@ +From 11f76dbf187708c3eda4a4daeb058f544ea28af5 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 12:28:23 +0900 +Subject: [PATCH] udev/net: assign alternative names only on add uevent + +Previously, we first assign alternative names to a network interface, +then later change its main name if requested. So, we could not assign +the name that currently assigned as the main name of an interface as an +alternative name. So, we retry to assign the previous main name as an +alternative name on later move uevent. + +However, that causes some confusing situation. E.g. if a .link file has +``` +Name=foo +AlternativeNames=foo baz +``` +then even if the interface is renamed by a user e.g. by invoking 'ip link' +command manually, the interface can be still referenced as 'foo', as the +name is now assigned as an alternative name. + +This makes the order of name assignment inverse: the main name is first +changed, and then the requested alternative names are assigned. And +udevd do not assign alternative names on move uevent. + +Replaces #27506. + +(cherry picked from commit 9094ae52caca0c19ff6abdbd95d17d8e401ea3b1) + +Resolves: RHEL-5988 +--- + src/udev/net/link-config.c | 37 ++++++++------------- + src/udev/net/link-config.h | 1 + + src/udev/udev-builtin-net_setup_link.c | 2 ++ + src/udev/udev-event.c | 45 ++++++++++++++++++++++---- + src/udev/udev-event.h | 1 + + 5 files changed, 55 insertions(+), 31 deletions(-) + +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index c9789bcb7c..2d8c902fd3 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -363,6 +363,7 @@ Link *link_free(Link *link) { + sd_device_unref(link->device); + free(link->kind); + free(link->driver); ++ strv_free(link->altnames); + return mfree(link); + } + +@@ -791,19 +792,22 @@ no_rename: + return 0; + } + +-static int link_apply_alternative_names(Link *link, sd_netlink **rtnl) { +- _cleanup_strv_free_ char **altnames = NULL, **current_altnames = NULL; ++static int link_generate_alternative_names(Link *link) { ++ _cleanup_strv_free_ char **altnames = NULL; + LinkConfig *config; + sd_device *device; + int r; + + assert(link); +- assert(link->config); +- assert(link->device); +- assert(rtnl); ++ config = ASSERT_PTR(link->config); ++ device = ASSERT_PTR(link->device); ++ assert(!link->altnames); + +- config = link->config; +- device = link->device; ++ if (link->action != SD_DEVICE_ADD) { ++ log_link_debug(link, "Skipping to apply AlternativeNames= and AlternativeNamesPolicy= on '%s' uevent.", ++ device_action_to_string(link->action)); ++ return 0; ++ } + + if (config->alternative_names) { + altnames = strv_copy(config->alternative_names); +@@ -841,22 +845,7 @@ static int link_apply_alternative_names(Link *link, sd_netlink **rtnl) { + } + } + +- strv_remove(altnames, link->ifname); +- +- r = rtnl_get_link_alternative_names(rtnl, link->ifindex, ¤t_altnames); +- if (r < 0) +- log_link_debug_errno(link, r, "Failed to get alternative names, ignoring: %m"); +- +- STRV_FOREACH(p, current_altnames) +- strv_remove(altnames, *p); +- +- strv_uniq(altnames); +- strv_sort(altnames); +- r = rtnl_set_link_alternative_names(rtnl, link->ifindex, altnames); +- if (r < 0) +- log_link_full_errno(link, r == -EOPNOTSUPP ? LOG_DEBUG : LOG_WARNING, r, +- "Could not set AlternativeName= or apply AlternativeNamesPolicy=, ignoring: %m"); +- ++ link->altnames = TAKE_PTR(altnames); + return 0; + } + +@@ -958,7 +947,7 @@ int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link) { + if (r < 0) + return r; + +- r = link_apply_alternative_names(link, rtnl); ++ r = link_generate_alternative_names(link); + if (r < 0) + return r; + +diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h +index ea9f560f45..874a391543 100644 +--- a/src/udev/net/link-config.h ++++ b/src/udev/net/link-config.h +@@ -27,6 +27,7 @@ typedef struct Link { + int ifindex; + const char *ifname; + const char *new_name; ++ char **altnames; + + LinkConfig *config; + sd_device *device; +diff --git a/src/udev/udev-builtin-net_setup_link.c b/src/udev/udev-builtin-net_setup_link.c +index 4bf42cd492..e964bf7bf4 100644 +--- a/src/udev/udev-builtin-net_setup_link.c ++++ b/src/udev/udev-builtin-net_setup_link.c +@@ -49,6 +49,8 @@ static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool + if (link->new_name) + udev_builtin_add_property(dev, test, "ID_NET_NAME", link->new_name); + ++ event->altnames = TAKE_PTR(link->altnames); ++ + return 0; + } + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index 2662806d61..3315d34eff 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -88,6 +88,7 @@ UdevEvent *udev_event_free(UdevEvent *event) { + ordered_hashmap_free_free_free(event->seclabel_list); + free(event->program_result); + free(event->name); ++ strv_free(event->altnames); + + return mfree(event); + } +@@ -918,9 +919,6 @@ static int rename_netif(UdevEvent *event) { + + dev = ASSERT_PTR(event->dev); + +- if (!device_for_action(dev, SD_DEVICE_ADD)) +- return 0; /* Rename the interface only when it is added. */ +- + r = sd_device_get_ifindex(dev, &ifindex); + if (r == -ENOENT) + return 0; /* Device is not a network interface. */ +@@ -980,7 +978,7 @@ static int rename_netif(UdevEvent *event) { + goto revert; + } + +- r = rtnl_set_link_name(&event->rtnl, ifindex, event->name, NULL); ++ r = rtnl_set_link_name(&event->rtnl, ifindex, event->name, event->altnames); + if (r < 0) { + if (r == -EBUSY) { + log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", +@@ -1011,6 +1009,35 @@ revert: + return r; + } + ++static int assign_altnames(UdevEvent *event) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); ++ int ifindex, r; ++ const char *s; ++ ++ if (strv_isempty(event->altnames)) ++ return 0; ++ ++ r = sd_device_get_ifindex(dev, &ifindex); ++ if (r == -ENOENT) ++ return 0; /* Device is not a network interface. */ ++ if (r < 0) ++ return log_device_warning_errno(dev, r, "Failed to get ifindex: %m"); ++ ++ r = sd_device_get_sysname(dev, &s); ++ if (r < 0) ++ return log_device_warning_errno(dev, r, "Failed to get sysname: %m"); ++ ++ /* Filter out the current interface name. */ ++ strv_remove(event->altnames, s); ++ ++ r = rtnl_append_link_alternative_names(&event->rtnl, ifindex, event->altnames); ++ if (r < 0) ++ log_device_full_errno(dev, r == -EOPNOTSUPP ? LOG_DEBUG : LOG_WARNING, r, ++ "Could not set AlternativeName= or apply AlternativeNamesPolicy=, ignoring: %m"); ++ ++ return 0; ++} ++ + static int update_devnode(UdevEvent *event) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; +@@ -1163,9 +1190,13 @@ int udev_event_execute_rules( + + DEVICE_TRACE_POINT(rules_finished, dev); + +- r = rename_netif(event); +- if (r < 0) +- return r; ++ if (action == SD_DEVICE_ADD) { ++ r = rename_netif(event); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ (void) assign_altnames(event); ++ } + + r = update_devnode(event); + if (r < 0) +diff --git a/src/udev/udev-event.h b/src/udev/udev-event.h +index 74d065ce23..13bd85dcf7 100644 +--- a/src/udev/udev-event.h ++++ b/src/udev/udev-event.h +@@ -23,6 +23,7 @@ typedef struct UdevEvent { + sd_device *dev_parent; + sd_device *dev_db_clone; + char *name; ++ char **altnames; + char *program_result; + mode_t mode; + uid_t uid; diff --git a/0406-test-add-tests-for-renaming-network-interface.patch b/0406-test-add-tests-for-renaming-network-interface.patch new file mode 100644 index 0000000..071858d --- /dev/null +++ b/0406-test-add-tests-for-renaming-network-interface.patch @@ -0,0 +1,105 @@ +From dec129d192c7815bdfbb71c88a1d7cdc3092f11f Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 16:28:54 +0900 +Subject: [PATCH] test: add tests for renaming network interface + +(cherry picked from commit 40b6b448bda5294582e685091123952fbcd43502) + +Related: RHEL-5988 +--- + test/units/testsuite-17.12.sh | 86 +++++++++++++++++++++++++++++++++++ + 1 file changed, 86 insertions(+) + create mode 100755 test/units/testsuite-17.12.sh + +diff --git a/test/units/testsuite-17.12.sh b/test/units/testsuite-17.12.sh +new file mode 100755 +index 0000000000..df74d356ee +--- /dev/null ++++ b/test/units/testsuite-17.12.sh +@@ -0,0 +1,86 @@ ++#!/usr/bin/env bash ++# SPDX-License-Identifier: LGPL-2.1-or-later ++set -ex ++set -o pipefail ++ ++# shellcheck source=test/units/assert.sh ++. "$(dirname "$0")"/assert.sh ++ ++create_link_file() { ++ name=${1?} ++ ++ mkdir -p /run/systemd/network/ ++ cat >/run/systemd/network/10-test.link < +Date: Wed, 22 Nov 2023 13:07:56 +0100 +Subject: [PATCH] Backport ukify from upstream + +This is based off v255-rc2. + +Resolves: RHEL-13199 +--- + man/rules/meson.build | 1 + + man/uki.conf.example | 14 + + man/ukify.xml | 685 +++++++ + meson.build | 25 + + meson_options.txt | 2 + + src/ukify/test/example.signing.crt.base64 | 23 + + src/ukify/test/example.signing.key.base64 | 30 + + .../test/example.tpm2-pcr-private.pem.base64 | 30 + + .../test/example.tpm2-pcr-private2.pem.base64 | 30 + + .../test/example.tpm2-pcr-public.pem.base64 | 8 + + .../test/example.tpm2-pcr-public2.pem.base64 | 8 + + src/ukify/test/meson.build | 20 + + src/ukify/test/test_ukify.py | 848 +++++++++ + src/ukify/ukify.py | 1660 +++++++++++++++++ + 14 files changed, 3384 insertions(+) + create mode 100644 man/uki.conf.example + create mode 100644 man/ukify.xml + create mode 100644 src/ukify/test/example.signing.crt.base64 + create mode 100644 src/ukify/test/example.signing.key.base64 + create mode 100644 src/ukify/test/example.tpm2-pcr-private.pem.base64 + create mode 100644 src/ukify/test/example.tpm2-pcr-private2.pem.base64 + create mode 100644 src/ukify/test/example.tpm2-pcr-public.pem.base64 + create mode 100644 src/ukify/test/example.tpm2-pcr-public2.pem.base64 + create mode 100644 src/ukify/test/meson.build + create mode 100755 src/ukify/test/test_ukify.py + create mode 100755 src/ukify/ukify.py + +diff --git a/man/rules/meson.build b/man/rules/meson.build +index bb7799036d..c7045840f2 100644 +--- a/man/rules/meson.build ++++ b/man/rules/meson.build +@@ -1189,6 +1189,7 @@ manpages = [ + ''], + ['udev_new', '3', ['udev_ref', 'udev_unref'], ''], + ['udevadm', '8', [], ''], ++ ['ukify', '1', [], 'ENABLE_UKIFY'], + ['user@.service', + '5', + ['systemd-user-runtime-dir', 'user-runtime-dir@.service'], +diff --git a/man/uki.conf.example b/man/uki.conf.example +new file mode 100644 +index 0000000000..84a9f77b8d +--- /dev/null ++++ b/man/uki.conf.example +@@ -0,0 +1,14 @@ ++[UKI] ++SecureBootPrivateKey=/etc/kernel/secure-boot.key.pem ++SecureBootCertificate=/etc/kernel/secure-boot.cert.pem ++ ++[PCRSignature:initrd] ++Phases=enter-initrd ++PCRPrivateKey=/etc/kernel/pcr-initrd.key.pem ++PCRPublicKey=/etc/kernel/pcr-initrd.pub.pem ++ ++[PCRSignature:system] ++Phases=enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit ++ enter-initrd:leave-initrd:sysinit:ready ++PCRPrivateKey=/etc/kernel/pcr-system.key.pem ++PCRPublicKey=/etc/kernel/pcr-system.pub.pem +diff --git a/man/ukify.xml b/man/ukify.xml +new file mode 100644 +index 0000000000..eff74ca150 +--- /dev/null ++++ b/man/ukify.xml +@@ -0,0 +1,685 @@ ++ ++ ++ ++ ++ ++ ++ ++ ukify ++ systemd ++ ++ ++ ++ ukify ++ 1 ++ ++ ++ ++ ukify ++ Combine components into a signed Unified Kernel Image for UEFI systems ++ ++ ++ ++ ++ ukify ++ OPTIONS ++ build ++ ++ ++ ++ ukify ++ OPTIONS ++ genkey ++ ++ ++ ++ ukify ++ OPTIONS ++ inspect ++ FILE ++ ++ ++ ++ ++ Description ++ ++ ukify is a tool whose primary purpose is to combine components (usually a ++ kernel, an initrd, and a UEFI boot stub) to create a ++ Unified Kernel Image (UKI) ++ — a PE binary that can be executed by the firmware to start the embedded linux kernel. ++ See systemd-stub7 ++ for details about the stub. ++ ++ ++ ++ Commands ++ ++ The following commands are understood: ++ ++ ++ <command>build</command> ++ ++ This command creates a Unified Kernel Image. The two primary options that should be specified for ++ the build verb are Linux=/, and ++ Initrd=/. Initrd= accepts multiple ++ whitespace-separated paths and can be specified multiple times. ++ ++ Additional sections will be inserted into the UKI, either automatically or only if a specific ++ option is provided. See the discussions of ++ Cmdline=/, ++ OSRelease=/, ++ DeviceTree=/, ++ Splash=/, ++ PCRPKey=/, ++ Uname=/, ++ SBAT=/, ++ and ++ below. ++ ++ ukify can also be used to assemble a PE binary that is not executable but ++ contains auxiliary data, for example additional kernel command line entries. ++ ++ If PCR signing keys are provided via the ++ PCRPrivateKey=/ and ++ PCRPublicKey=/ options, PCR values that will be seen ++ after booting with the given kernel, initrd, and other sections, will be calculated, signed, and embedded ++ in the UKI. ++ systemd-measure1 is ++ used to perform this calculation and signing. ++ ++ The calculation of PCR values is done for specific boot phase paths. Those can be specified with ++ the Phases=/ option. If not specified, the default provided ++ by systemd-measure is used. It is also possible to specify the ++ PCRPrivateKey=/, ++ PCRPublicKey=/, and ++ Phases=/ arguments more than once. Signatures will then be ++ performed with each of the specified keys. On the command line, when both and ++ are used, they must be specified the same number of times, and then ++ the n-th boot phase path set will be signed by the n-th key. This can be used to build different trust ++ policies for different phases of the boot. In the config file, PCRPrivateKey=, ++ PCRPublicKey=, and Phases= are grouped into separate sections, ++ describing separate boot phases. ++ ++ If a SecureBoot signing key is provided via the ++ SecureBootPrivateKey=/ option, the resulting ++ PE binary will be signed as a whole, allowing the resulting UKI to be trusted by SecureBoot. Also see the ++ discussion of automatic enrollment in ++ systemd-boot7. ++ ++ ++ If the stub and/or the kernel contain .sbat sections they will be merged in ++ the UKI so that revocation updates affecting either are considered when the UKI is loaded by Shim. For ++ more information on SBAT see ++ Shim documentation. ++ ++ ++ ++ ++ <command>genkey</command> ++ ++ This command creates the keys for PCR signing and the key and certificate used for SecureBoot ++ signing. The same configuration options that determine what keys and in which paths will be needed for ++ signing when build is used, here determine which keys will be created. See the ++ discussion of PCRPrivateKey=/, ++ PCRPublicKey=/, and ++ SecureBootPrivateKey=/ below. ++ ++ The output files must not exist. ++ ++ ++ ++ <command>inspect</command> ++ ++ Display information about the sections in a given binary or binaries. ++ If is given, all sections are shown. ++ Otherwise, if option is specified at least once, only those sections are shown. ++ Otherwise, well-known sections that are typically included in an UKI are shown. ++ For each section, its name, size, and sha256-digest is printed. ++ For text sections, the contents are printed. ++ ++ Also see the description of / and ++ . ++ ++ ++ ++ ++ Configuration settings ++ ++ Settings can appear in configuration files (the syntax with SomeSetting=value) and on the command line (the syntax ++ with ). For some command ++ line parameters, a single-letter shortcut is also allowed. In the configuration files, the setting must ++ be in the appropriate section, so the descriptions are grouped by section below. When the same setting ++ appears in the configuration file and on the command line, generally the command line setting has higher ++ priority and overwrites the config file setting completely. If some setting behaves differently, this is ++ described below. ++ ++ If no config file is provided via the option , ++ ukify will try to look for a default configuration file in the following paths in this ++ order: /run/systemd/ukify.conf, /etc/systemd/ukify.conf, ++ /usr/local/lib/systemd/ukify.conf, and /usr/lib/systemd/ukify.conf, ++ and then load the first one found. ukify will proceed normally if no configuration file ++ is specified and no default one is found. ++ ++ The LINUX and INITRD positional arguments, or ++ the equivalent Linux= and Initrd= settings, are optional. If more ++ than one initrd is specified, they will all be combined into a single PE section. This is useful to, for ++ example, prepend microcode before the actual initrd. ++ ++ The following options and settings are understood: ++ ++ ++ Command line-only options ++ ++ ++ ++ ++ ++ Load configuration from the given config file. In general, settings specified in ++ the config file have lower precedence than the settings specified via options. In cases where the ++ command line option does not fully override the config file setting are explicitly mentioned in the ++ descriptions of individual options. ++ ++ ++ ++ ++ ++ ++ ++ ++ Enable or disable a call to ++ systemd-measure1 ++ to print pre-calculated PCR values. Defaults to false. ++ ++ ++ ++ ++ ++ ++ ++ ++ For all verbs except inspect, the first syntax is used. ++ Specify an arbitrary additional section NAME. ++ The argument may be a literal string, or @ followed by a path name. ++ This option may be specified more than once. Any sections specified in this fashion will be ++ inserted (in order) before the .linux section which is always last. ++ ++ For the inspect verb, the second syntax is used. ++ The section NAME will be inspected (if found). ++ If the second argument is text, the contents will be printed. ++ If the third argument is given, the contents will be saved to file PATH. ++ ++ ++ Note that the name is used as-is, and if the section name should start with a dot, it must be ++ included in NAME. ++ ++ ++ ++ ++ ++ ++ ++ Specify one or more directories with helper tools. ukify will ++ look for helper tools in those directories first, and if not found, try to load them from ++ $PATH in the usual fashion. ++ ++ ++ ++ ++ ++ ++ ++ The output filename. If not specified, the name of the ++ LINUX argument, with the suffix .unsigned.efi or ++ .signed.efi will be used, depending on whether signing for SecureBoot was ++ performed. ++ ++ ++ ++ ++ ++ ++ ++ Print a summary of loaded config and exit. This is useful to check how the options ++ from the configuration file and the command line are combined. ++ ++ ++ ++ ++ ++ ++ ++ Print all sections (with inspect verb). ++ ++ ++ ++ ++ ++ ++ ++ Generate JSON output (with inspect verb). ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ [UKI] section ++ ++ ++ ++ Linux=LINUX ++ ++ ++ A path to the kernel binary. ++ ++ ++ ++ ++ ++ Initrd=INITRD... ++ ++ ++ Zero or more initrd paths. In the configuration file, items are separated by ++ whitespace. The initrds are combined in the order of specification, with the initrds specified in ++ the config file first. ++ ++ ++ ++ ++ ++ Cmdline=TEXT|@PATH ++ ++ ++ The kernel command line (the .cmdline section). The argument may ++ be a literal string, or @ followed by a path name. If not specified, no command ++ line will be embedded. ++ ++ ++ ++ ++ ++ OSRelease=TEXT|@PATH ++ ++ ++ The os-release description (the .osrel section). The argument ++ may be a literal string, or @ followed by a path name. If not specified, the ++ os-release5 file ++ will be picked up from the host system. ++ ++ ++ ++ ++ ++ DeviceTree=PATH ++ ++ ++ The devicetree description (the .dtb section). The argument is a ++ path to a compiled binary DeviceTree file. If not specified, the section will not be present. ++ ++ ++ ++ ++ ++ ++ Splash=PATH ++ ++ ++ A picture to display during boot (the .splash section). The ++ argument is a path to a BMP file. If not specified, the section will not be present. ++ ++ ++ ++ ++ ++ ++ PCRPKey=PATH ++ ++ ++ A path to a public key to embed in the .pcrpkey section. If not ++ specified, and there's exactly one ++ PCRPublicKey=/ argument, that key will be used. ++ Otherwise, the section will not be present. ++ ++ ++ ++ ++ ++ Uname=VERSION ++ ++ ++ Specify the kernel version (as in uname -r, the ++ .uname section). If not specified, an attempt will be made to extract the ++ version string from the kernel image. It is recommended to pass this explicitly if known, because ++ the extraction is based on heuristics and not very reliable. If not specified and extraction fails, ++ the section will not be present. ++ ++ ++ ++ ++ ++ PCRBanks=PATH ++ ++ ++ A comma or space-separated list of PCR banks to sign a policy for. If not present, ++ all known banks will be used (sha1, sha256, ++ sha384, sha512), which will fail if not supported by the ++ system. ++ ++ ++ ++ ++ ++ SecureBootSigningTool=SIGNER ++ ++ ++ Whether to use sbsign or pesign. ++ Depending on this choice, different parameters are required in order to sign an image. ++ Defaults to sbsign. ++ ++ ++ ++ ++ ++ SecureBootPrivateKey=SB_KEY ++ ++ ++ A path to a private key to use for signing of the resulting binary. If the ++ SigningEngine=/ option is used, this may also be ++ an engine-specific designation. This option is required by ++ SecureBootSigningTool=sbsign/. ++ ++ ++ ++ ++ ++ SecureBootCertificate=SB_CERT ++ ++ ++ A path to a certificate to use for signing of the resulting binary. If the ++ SigningEngine=/ option is used, this may also ++ be an engine-specific designation. This option is required by ++ SecureBootSigningTool=sbsign/. ++ ++ ++ ++ ++ ++ SecureBootCertificateDir=SB_PATH ++ ++ ++ A path to a nss certificate database directory to use for signing of the resulting binary. ++ Takes effect when SecureBootSigningTool=pesign/ is used. ++ Defaults to /etc/pki/pesign. ++ ++ ++ ++ ++ ++ SecureBootCertificateName=SB_CERTNAME ++ ++ ++ The name of the nss certificate database entry to use for signing of the resulting binary. ++ This option is required by SecureBootSigningTool=pesign/. ++ ++ ++ ++ ++ ++ SecureBootCertificateValidity=DAYS ++ ++ ++ Period of validity (in days) for a certificate created by ++ genkey. Defaults to 3650, i.e. 10 years. ++ ++ ++ ++ ++ ++ SigningEngine=ENGINE ++ ++ ++ An "engine" for signing of the resulting binary. This option is currently passed ++ verbatim to the option of ++ sbsign1. ++ ++ ++ ++ ++ ++ ++ SignKernel=BOOL ++ ++ ++ ++ Override the detection of whether to sign the Linux binary itself before it is ++ embedded in the combined image. If not specified, it will be signed if a SecureBoot signing key is ++ provided via the ++ SecureBootPrivateKey=/ option and the ++ binary has not already been signed. If ++ SignKernel=/ is true, and the binary has already ++ been signed, the signature will be appended anyway. ++ ++ ++ ++ ++ ++ SBAT=TEXT|@PATH ++ ++ ++ SBAT metadata associated with the UKI or addon. SBAT policies are useful to revoke ++ whole groups of UKIs or addons with a single, static policy update that does not take space in ++ DBX/MOKX. If not specified manually, a default metadata entry consisting of ++ uki,1,UKI,uki,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html ++ will be used, to ensure it is always possible to revoke UKIs and addons. For more information on ++ SBAT see Shim documentation. ++ ++ ++ ++ ++ ++ ++ ++ ++ [PCRSignature:<replaceable>NAME</replaceable>] section ++ ++ In the config file, those options are grouped by section. On the command line, they ++ must be specified in the same order. The sections specified in both sources are combined. ++ ++ ++ ++ ++ PCRPrivateKey=PATH ++ ++ ++ A private key to use for signing PCR policies. On the command line, this option may ++ be specified more than once, in which case multiple signatures will be made. ++ ++ ++ ++ ++ ++ PCRPublicKey=PATH ++ ++ ++ A public key to use for signing PCR policies. ++ ++ On the command line, this option may be specified more than once, similarly to the ++ option. If not present, the public keys will be extracted from ++ the private keys. On the command line, if present, this option must be specified the same number of ++ times as the option. ++ ++ ++ ++ ++ ++ Phases=LIST ++ ++ ++ A comma or space-separated list of colon-separated phase paths to sign a policy ++ for. Each set of boot phase paths will be signed with the corresponding private key. If not ++ present, the default of ++ systemd-measure1 ++ will be used. ++ ++ On the command line, when this argument is present, it must appear the same number of times as ++ the option. ++ ++ ++ ++ ++ ++ ++ ++ ++ Examples ++ ++ ++ Minimal invocation ++ ++ $ ukify build \ ++ --linux=/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \ ++ --initrd=/some/path/initramfs-6.0.9-300.fc37.x86_64.img \ ++ --cmdline='quiet rw' ++ ++ ++ This creates an unsigned UKI ./vmlinuz.unsigned.efi. ++ ++ ++ ++ All the bells and whistles ++ ++ $ ukify build \ ++ --linux=/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \ ++ --initrd=early_cpio \ ++ --initrd=/some/path/initramfs-6.0.9-300.fc37.x86_64.img \ ++ --sbat='sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md ++ uki.author.myimage,1,UKI for System,uki.author.myimage,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html' \ ++ --pcr-private-key=pcr-private-initrd-key.pem \ ++ --pcr-public-key=pcr-public-initrd-key.pem \ ++ --phases='enter-initrd' \ ++ --pcr-private-key=pcr-private-system-key.pem \ ++ --pcr-public-key=pcr-public-system-key.pem \ ++ --phases='enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit \ ++ enter-initrd:leave-initrd:sysinit:ready' \ ++ --pcr-banks=sha384,sha512 \ ++ --secureboot-private-key=sb.key \ ++ --secureboot-certificate=sb.cert \ ++ --sign-kernel \ ++ --cmdline='quiet rw rhgb' ++ ++ ++ This creates a signed UKI ./vmlinuz.signed.efi. ++ The initrd section contains two concatenated parts, early_cpio ++ and initramfs-6.0.9-300.fc37.x86_64.img. ++ The policy embedded in the .pcrsig section will be signed for the initrd (the ++ enter-initrd phase) with the key ++ pcr-private-initrd-key.pem, and for the main system (phases ++ leave-initrd, sysinit, ready) with the ++ key pcr-private-system-key.pem. The Linux binary and the resulting ++ combined image will be signed with the SecureBoot key sb.key. ++ ++ ++ ++ All the bells and whistles, via a config file ++ ++ This is the same as the previous example, but this time the configuration is stored in a ++ file: ++ ++ $ cat ukify.conf ++[UKI] ++Initrd=early_cpio ++Cmdline=quiet rw rhgb ++ ++SecureBootPrivateKey=sb.key ++SecureBootCertificate=sb.cert ++SignKernel=yes ++PCRBanks=sha384,sha512 ++ ++[PCRSignature:initrd] ++PCRPrivateKey=pcr-private-initrd-key.pem ++PCRPublicKey=pcr-public-initrd-key.pem ++Phases=enter-initrd ++ ++[PCRSignature:system] ++PCRPrivateKey=pcr-private-system-key.pem ++PCRPublicKey=pcr-public-system-key.pem ++Phases=enter-initrd:leave-initrd ++ enter-initrd:leave-initrd:sysinit ++ enter-initrd:leave-initrd:sysinit:ready ++ ++$ ukify -c ukify.conf build \ ++ --linux=/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \ ++ --initrd=/some/path/initramfs-6.0.9-300.fc37.x86_64.img ++ ++ ++ One "initrd" (early_cpio) is specified in the config file, and ++ the other initrd (initramfs-6.0.9-300.fc37.x86_64.img) is specified ++ on the command line. This may be useful for example when the first initrd contains microcode for the CPU ++ and does not need to be updated when the kernel version changes, unlike the actual initrd. ++ ++ ++ ++ Kernel command line auxiliary PE ++ ++ ukify build \ ++ --secureboot-private-key=sb.key \ ++ --secureboot-certificate=sb.cert \ ++ --cmdline='debug' \ ++ --sbat='sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md ++ uki.addon.author,1,UKI Addon for System,uki.addon.author,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html' ++ --output=debug.cmdline ++ ++ ++ This creates a signed PE binary that contains the additional kernel command line parameter ++ debug with SBAT metadata referring to the owner of the addon. ++ ++ ++ ++ Decide signing policy and create certificate and keys ++ ++ First, let's create an config file that specifies what signatures shall be made: ++ ++ # cat >/etc/kernel/uki.conf <<EOF ++EOF ++ ++ Next, we can generate the certificate and keys: ++ # ukify genkey --config=/etc/kernel/uki.conf ++Writing SecureBoot private key to /etc/kernel/secure-boot.key.pem ++Writing SecureBoot certificate to /etc/kernel/secure-boot.cert.pem ++Writing private key for PCR signing to /etc/kernel/pcr-initrd.key.pem ++Writing public key for PCR signing to /etc/kernel/pcr-initrd.pub.pem ++Writing private key for PCR signing to /etc/kernel/pcr-system.key.pem ++Writing public key for PCR signing to /etc/kernel/pcr-system.pub.pem ++ ++ ++ (Both operations need to be done as root to allow write access ++ to /etc/kernel/.) ++ ++ Subsequent invocations using the config file ++ (ukify build --config=/etc/kernel/uki.conf) ++ will use this certificate and key files. Note that the ++ kernel-install8 ++ plugin 60-ukify.install uses /etc/kernel/uki.conf ++ by default, so after this file has been created, installations of kernels that create a UKI on the ++ local machine using kernel-install will perform signing using this config. ++ ++ ++ ++ ++ See Also ++ ++ systemd1, ++ systemd-stub7, ++ systemd-boot7, ++ systemd-measure1, ++ systemd-pcrphase.service8 ++ ++ ++ ++ +diff --git a/meson.build b/meson.build +index 5b2e7ca172..b874c2f9b4 100644 +--- a/meson.build ++++ b/meson.build +@@ -1916,6 +1916,18 @@ subdir('src/boot/efi') + + ############################################################ + ++pymod = import('python') ++python = pymod.find_installation('python3', required : true, modules : ['jinja2']) ++python_39 = python.language_version().version_compare('>=3.9') ++ ++want_ukify = get_option('ukify') ++if want_ukify and not python_39 ++ error('ukify requires Python >= 3.9') ++endif ++conf.set10('ENABLE_UKIFY', want_ukify) ++ ++############################################################ ++ + generate_gperfs = find_program('tools/generate-gperfs.py') + make_autosuspend_rules_py = find_program('tools/make-autosuspend-rules.py') + make_directive_index_py = find_program('tools/make-directive-index.py') +@@ -2164,6 +2176,7 @@ subdir('src/test') + subdir('src/fuzz') + subdir('rules.d') + subdir('test') ++subdir('src/ukify/test') # needs to be last for test_env variable + + ############################################################ + +@@ -3841,6 +3854,18 @@ exe = custom_target( + install_dir : bindir) + public_programs += exe + ++ukify = custom_target( ++ 'ukify', ++ input : 'src/ukify/ukify.py', ++ output : 'ukify', ++ command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], ++ install : want_ukify, ++ install_mode : 'rwxr-xr-x', ++ install_dir : rootlibexecdir) ++if want_ukify ++ public_programs += ukify ++endif ++ + if want_tests != 'false' and want_kernel_install + test('test-kernel-install', + test_kernel_install_sh, +diff --git a/meson_options.txt b/meson_options.txt +index 814f340840..903e6681a4 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -499,6 +499,8 @@ option('llvm-fuzz', type : 'boolean', value : false, + description : 'build against LLVM libFuzzer') + option('kernel-install', type: 'boolean', value: true, + description : 'install kernel-install and associated files') ++option('ukify', type : 'boolean', value : false, ++ description : 'install ukify') + option('analyze', type: 'boolean', value: true, + description : 'install systemd-analyze') + +diff --git a/src/ukify/test/example.signing.crt.base64 b/src/ukify/test/example.signing.crt.base64 +new file mode 100644 +index 0000000000..694d13b5a6 +--- /dev/null ++++ b/src/ukify/test/example.signing.crt.base64 +@@ -0,0 +1,23 @@ ++LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURsVENDQW4yZ0F3SUJBZ0lVTzlqUWhhblhj ++b3ViOERzdXlMMWdZbksrR1lvd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNX ++Rmd4RlRBVEJnTlZCQWNNREVSbFptRjFiSFFnUTJsMGVURWNNQm9HQTFVRQpDZ3dUUkdWbVlYVnNk ++Q0JEYjIxd1lXNTVJRXgwWkRFVk1CTUdBMVVFQXd3TWEyVjVJSE5wWjI1cGJtbG5NQ0FYCkRUSXlN ++VEF5T1RFM01qY3dNVm9ZRHpNd01qSXdNekF4TVRjeU56QXhXakJaTVFzd0NRWURWUVFHRXdKWVdE ++RVYKTUJNR0ExVUVCd3dNUkdWbVlYVnNkQ0JEYVhSNU1Sd3dHZ1lEVlFRS0RCTkVaV1poZFd4MElF ++TnZiWEJoYm5rZwpUSFJrTVJVd0V3WURWUVFEREF4clpYa2djMmxuYm1sdWFXY3dnZ0VpTUEwR0NT ++cUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRREtVeHR4Y0d1aGYvdUp1SXRjWEhvdW0v ++RE9RL1RJM3BzUWlaR0ZWRkJzbHBicU5wZDUKa2JDaUFMNmgrY1FYaGRjUmlOT1dBR0wyMFZ1T2Rv ++VTZrYzlkdklGQnFzKzc2NHhvWGY1UGd2SlhvQUxSUGxDZAp4YVdPQzFsOFFIRHpxZ09SdnREMWNI ++WFoveTkvZ1YxVU1GK1FlYm12aUhRN0U4eGw1T2h5MG1TQVZYRDhBTitsCjdpMUR6N0NuTzhrMVph ++alhqYXlpNWV1WEV0TnFSZXNuVktRRElTQ0t2STFueUxySWxHRU1GZmFuUmRLQWthZ3MKalJnTmVh ++T3N3aklHNjV6UzFVdjJTZXcxVFpIaFhtUmd5TzRVT0JySHZlSml2T2hObzU3UlRKd0M2K2lGY0FG ++aApSSnorVmM2QUlSSkI1ZWtJUmdCN3VDNEI5ZmwydXdZKytMODNBZ01CQUFHalV6QlJNQjBHQTFV ++ZERnUVdCQlFqCllIMnpzVFlPQU51MkcweXk1QkxlOHBvbWZUQWZCZ05WSFNNRUdEQVdnQlFqWUgy ++enNUWU9BTnUyRzB5eTVCTGUKOHBvbWZUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEdDU3FHU0li ++M0RRRUJDd1VBQTRJQkFRQ2dxcmFXaE51dQptUmZPUjVxcURVcC83RkpIL1N6Zk1vaDBHL2lWRkhv ++OUpSS0tqMUZ2Q0VZc1NmeThYTmdaUDI5eS81Z0h4cmcrCjhwZWx6bWJLczdhUTRPK01TcmIzTm11 ++V1IzT0M0alBoNENrM09ZbDlhQy9iYlJqSWFvMDJ6K29XQWNZZS9xYTEKK2ZsemZWVEUwMHJ5V1RM ++K0FJdDFEZEVqaG01WXNtYlgvbWtacUV1TjBtSVhhRXhSVE9walczUWRNeVRQaURTdApvanQvQWMv ++R2RUWDd0QkhPTk44Z3djaC91V293aVNORERMUm1wM2VScnlOZ3RPKzBISUd5Qm16ZWNsM0VlVEo2 ++CnJzOGRWUFhqR1Z4dlZDb2tqQllrOWdxbkNGZEJCMGx4VXVNZldWdVkyRUgwSjI3aGh4SXNFc3ls ++VTNIR1EyK2MKN1JicVY4VTNSRzA4Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K +diff --git a/src/ukify/test/example.signing.key.base64 b/src/ukify/test/example.signing.key.base64 +new file mode 100644 +index 0000000000..88baedbcb6 +--- /dev/null ++++ b/src/ukify/test/example.signing.key.base64 +@@ -0,0 +1,30 @@ ++LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZB ++QVNDQktnd2dnU2tBZ0VBQW9JQkFRREtVeHR4Y0d1aGYvdUoKdUl0Y1hIb3VtL0RPUS9USTNwc1Fp ++WkdGVkZCc2xwYnFOcGQ1a2JDaUFMNmgrY1FYaGRjUmlOT1dBR0wyMFZ1Twpkb1U2a2M5ZHZJRkJx ++cys3NjR4b1hmNVBndkpYb0FMUlBsQ2R4YVdPQzFsOFFIRHpxZ09SdnREMWNIWFoveTkvCmdWMVVN ++RitRZWJtdmlIUTdFOHhsNU9oeTBtU0FWWEQ4QU4rbDdpMUR6N0NuTzhrMVphalhqYXlpNWV1WEV0 ++TnEKUmVzblZLUURJU0NLdkkxbnlMcklsR0VNRmZhblJkS0FrYWdzalJnTmVhT3N3aklHNjV6UzFV ++djJTZXcxVFpIaApYbVJneU80VU9Cckh2ZUppdk9oTm81N1JUSndDNitpRmNBRmhSSnorVmM2QUlS ++SkI1ZWtJUmdCN3VDNEI5ZmwyCnV3WSsrTDgzQWdNQkFBRUNnZ0VBQkhZQ28rU3JxdHJzaStQU3hz ++MlBNQm5tSEZZcFBvaVIrTEpmMEFYRTVEQUoKMGM0MFZzemNqU1hoRGljNHFLQWQxdGdpZWlzMkEy ++VW9WS0xPV3pVOTBqNUd4MURoMWEzaTRhWTQ1ajNuNUFDMgpMekRsakNVQWVucExsYzdCN3MxdjJM ++WFJXNmdJSVM5Y043NTlkVTYvdktyQ2FsbGkzcTZZRWlNUzhQMHNsQnZFCkZtdEc1elFsOVJjV0gr ++cHBqdzlIMTJSZ3BldUVJVEQ2cE0vd2xwcXZHRlUwcmZjM0NjMHhzaWdNTnh1Z1FJNGgKbnpjWDVs ++OEs0SHdvbmhOTG9TYkh6OU5BK3p3QkpuUlZVSWFaaEVjSThtaEVPWHRaRkpYc01aRnhjS2l3SHFS ++dApqUUVHOHJRa3lPLytXMmR5Z2czV1lNYXE1OWpUWVdIOUsrQmFyeEMzRVFLQmdRRFBNSFMycjgz ++ZUpRTTlreXpkCndDdnlmWGhQVlVtbVJnOGwyWng0aC9tci9mNUdDeW5SdzRzT2JuZGVQd29tZ1Iz ++cFBleFFGWlFFSExoZ1RGY3UKVk5uYXcrTzBFL1VnL01pRGswZDNXU0hVZXZPZnM1cEM2b3hYNjNT ++VENwNkVLT2VEZlpVMW9OeHRsZ0YyRVhjcgpmVlZpSzFKRGk3N2dtaENLcFNGcjBLK3gyUUtCZ1FE ++NS9VUC9hNU52clExdUhnKzR0SzJZSFhSK1lUOFREZG00Ck8xZmh5TU5lOHRYSkd5UUJjTktVTWg2 ++M2VyR1MwWlRWdGdkNHlGS3RuOGtLU2U4TmlacUl1aitVUVIyZ3pEQVAKQ2VXcXl2Y2pRNmovU1Yw ++WjVvKzlTNytiOStpWWx5RTg2bGZobHh5Z21aNnptYisxUUNteUtNVUdBNis5VmUvMgo1MHhDMXBB ++L2p3S0JnUUNEOHA4UnpVcDFZK3I1WnVaVzN0RGVJSXZqTWpTeVFNSGE0QWhuTm1tSjREcjBUcDIy ++CmFpci82TmY2WEhsUlpqOHZVSEZUMnpvbG1FalBneTZ1WWZsUCtocmtqeVU0ZWVRVTcxRy9Mek45 ++UjBRcCs4Nk4KT1NSaHhhQzdHRE0xaFh0VFlVSUtJa1RmUVgzeXZGTEJqcE0yN3RINEZHSmVWWitk ++UEdiWmE5REltUUtCZ1FENQpHTU5qeExiQnhhY25QYThldG5KdnE1SUR5RFRJY0xtc1dQMkZ6cjNX ++WTVSZzhybGE4aWZ5WVVxNE92cXNPRWZjCjk2ZlVVNUFHejd2TWs4VXZNUmtaK3JRVnJ4aXR2Q2g3 ++STdxRkIvOWdWVEFWU080TE8vR29oczBqeGRBd0ZBK2IKbWtyOVQ4ekh2cXNqZlNWSW51bXRTL0Nl ++d0plaHl2cjBoSjg1em9Fbnd3S0JnR1h6UXVDSjJDb3NHVVhEdnlHKwpyRzVBd3pUZGd0bHg4YTBK ++NTg1OWtZbVd0cW5WYUJmbFdrRmNUcHNEaGZ2ZWVDUkswc29VRlNPWkcranpsbWJrCkpRL09aVkZJ ++dG9MSVZCeE9qeWVXNlhUSkJXUzFRSkVHckkwY0tTbXNKcENtUXVPdUxMVnZYczV0U21CVmc5RXQK ++MjZzUkZwcjVWWmsrZlNRa3RhbkM4NGV1Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +diff --git a/src/ukify/test/example.tpm2-pcr-private.pem.base64 b/src/ukify/test/example.tpm2-pcr-private.pem.base64 +new file mode 100644 +index 0000000000..586b28ef9b +--- /dev/null ++++ b/src/ukify/test/example.tpm2-pcr-private.pem.base64 +@@ -0,0 +1,30 @@ ++LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZB ++QVNDQktnd2dnU2tBZ0VBQW9JQkFRQzVuOHFhbzVNZ1BJUVcKc0F5Y2R3dnB1bjdNNHlRSW9FL3I3 ++ekFGTG1hZlBXclo3d2JaaUIyTkY1MVdHOEo4bnlDQkI3M0RLcmZaeWs5cwphQXdXVW5RR2t0dGFv ++RXpXRzZSRTM3dXdQOUpVM09YdklTNTBhcy9KSHVHNlJPYmE2V0NOOFp2TTdkZGpvTDFKCkZlYnBS ++SXI1Vi82VStMTFhrUnRNYVczUnZ6T0xYeU1NT2QzOEcxZ0d0VlRHcm90ejVldFgrTUNVU2lOVGFE ++OVUKN1dEZXVsZXVpMlRnK1I3TGRoSXg3ZTQ5cEhRM3d6a1NxeFQ4SGpoU3ZURWpITWVSNjIwaUhF ++ZW9uYzdsMXVnagpzY1pwTktHdk13bXUvU2ptWFp6UkpOdjVOU0txcEVnQll2RnFkS3dUdlc4MWl6 ++SUFvN3paMkx6NDJYb25zSWJ2CjNrbGZqTG1mQWdNQkFBRUNnZ0VBQXozYm8yeTAzb3kvLzhkdVNQ ++TTVSWWtvdXJwQ3dGWFFYMzNyV0VQUnJmazgKR3ZjMkp1bGVIcjhwVTc0alhOcklqZ2hORTVIMDZQ ++eEQrOUFyV2Q1eHdVV2lTQWhobnlHWGNrNTM4Q0dGTWs4egpRc1JSRTk1anA0Ny9BU28vMzlYUWhs ++b1FUdmxlV0JLUUM2MHl2YU1oVEM1eHR6ZEtwRUlYK0hNazVGTlMrcDJVCmxtL3AzVE1YWDl1bmc5 ++Mk9pTzUzV1VreFpQN2cwTVJHbGJrNzhqc1dkdjFYY0tLRjhuVmU5WC9NR1lTYlVLNy8KM2NYazFR ++WTRUdVZaQlBFSE12RFRpWWwxbmdDd1ZuL2MyY3JQU3hJRFdFWlhEdm90SFUwQkNQZURVckxGa0F5 ++cQpDaloza3MzdEh4am42STkraEVNcUJDMzY1MHFjdDNkZ0RVV2loc2MzdVFLQmdRRG1mVTNKc29K ++QWFOdmxCbXgyClhzRDRqbXlXV1F2Z244cVNVNG03a2JKdmprcUJ6VnB0T0ZsYmk2ejZHOXR6ZHNX ++a0dJSjh3T0ZRb1hlM0dKOFIKSlVpeEFXTWZOM1JURGo5VjVXbzZJdE5EbzM1N3dNbVVYOW1qeThF ++YXp0RE1BckdSNGJva0Q5RjY3clhqSGdSMQpaZVcvSDlUWHFUV1l4VHl6UDB3ZDBQeUZ4d0tCZ1FE ++T0swWHVQS0o0WG00WmFCemN0OTdETXdDcFBSVmVvUWU3CmkzQjRJQ3orWFZ4cVM2amFTY2xNeEVm ++Nk5tM2tLNERDR1dwVkpXcm9qNjlMck1KWnQzTlI2VUJ5NzNqUVBSamsKRXk5N3YrR04yVGwwNjFw ++ZUxUM0dRS2RhT2VxWldpdElOcFc1dUxHL1poMGhoRUY5c1lSVTRtUFYwUWpla2kvdgp1bnVmcWx0 ++TmFRS0JnQTl6TE1pdFg0L0R0NkcxZVlYQnVqdXZDRlpYcDdVcDRPRklHajVwZU1XRGl6a0NNK0tJ ++CldXMEtndERORnp1NUpXeG5mQyt5bWlmV2V2alovS2Vna1N2VVJQbXR0TzF3VWd5RzhVVHVXcXo1 ++QTV4MkFzMGcKVTYxb0ZneWUrbDRDZkRha0k5OFE5R0RDS1kwTTBRMnhnK0g0MTBLUmhCYzJlV2dt ++Z1FxcW5KSzNBb0dCQU1rZgpnOWZXQlBVQndjdzlPYkxFR0tjNkVSSUlTZG1IbytCOE5kcXFJTnAv ++djFEZXdEazZ0QXFVakZiMlZCdTdxSjh4ClpmN3NRcS9ldzdaQ01WS09XUXgyVEc0VFdUdGo3dTFJ ++SGhGTjdiNlFRN0hnaXNiR3diV3VpdFBGSGl3OXYyMXgKK253MFJnb2VscHFFeDlMVG92R2Y3SjdB ++ampONlR4TkJTNnBGNlUzSkFvR0JBT0tnbHlRWDJpVG5oMXd4RG1TVQo4RXhoQVN3S09iNS8yRmx4 ++aUhtUHVjNTZpS0tHY0lkV1cxMUdtbzdJakNzSTNvRm9iRkFjKzBXZkMvQTdMNWlmCjNBYVNWcmh0 ++cThRRklRaUtyYUQ0YlRtRk9Famg5QVVtUHMrWnd1OE9lSXJBSWtwZDV3YmlhTEJLd0pRbVdtSFAK ++dUNBRTA3cXlSWXJ0c3QvcnVSSG5IdFA1Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +diff --git a/src/ukify/test/example.tpm2-pcr-private2.pem.base64 b/src/ukify/test/example.tpm2-pcr-private2.pem.base64 +new file mode 100644 +index 0000000000..d21a3d6043 +--- /dev/null ++++ b/src/ukify/test/example.tpm2-pcr-private2.pem.base64 +@@ -0,0 +1,30 @@ ++LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZB ++QVNDQktZd2dnU2lBZ0VBQW9JQkFRQzJ2Nk1oZHg3a3VjUHIKbmtFNFIrY3FnV2Y5T3B1c2h2M2o3 ++SG50K08wdi84d2l2T1BFNTlLMHYvRWJOOG94TDZEWUNXU0JCRU4vREJ5MgpMUTYwbldSdHBZN2Ju ++bEcrcEtVeTRvSDRNZXZCR2JqZUhrak9LU3dNYVVWNGs4UmVSSjg4cVZ1U1MxSnVORW1NCmd5SERF ++NGFPNG5ndG5UUFZZdzUydVBIcG1rN0E4VFdXN2lLZE5JWWZWOCtuR1pENXIzRWllekRsUUNORG54 ++UkcKdm5uSFZ6VFhZR3RwY2xaeWlJclpVekpBNFFPZnRueXB5UDVrQS94NVM1MU9QeGFxWlA3eGtP ++S0NicUUvZmZvMApFTi9rTno0N0ZoUGUxbVBHUkZZWldHZXg0aWFPdHlLdHhnU1FYYkdlNEVoeVR4 ++SjJlT3U4QUVoVklTdjh6UU9nClNtbWx2UGQvQWdNQkFBRUNnZ0VBUUFpRERRRlR3bG96QTVhMmpK ++VnBNdlFYNzF0L1c2TUxTRGMrZS90cWhKU1IKUHlUSGZHR3NhMmdMLy9qNjhHUWJiRWRTUDRDeWM4 ++eFhMU0E1bEdESDVVR0svbm9KYzQ3MlVZK2JjYzl3SjMrdgpUcWoyNHNIN2JMZmdQMEVybjhwVXIy ++azZMRmNYSVlWUnRobm1sUmQ4NFFrS2loVVlxZTdsRFFWOXdsZ3V1eHpRCnBmVEtDTWk1bXJlYjIx ++OExHS0QrMUxjVmVYZjExamc3Z2JnMllLZ1dOQ2R3VmIyUzJ5V0hTTjBlT3hPd21kWXIKSUVCekpG ++eEc2MFJxSlJ1RzVIam9iemc2cy9ycUo1THFta3JhUWh6bHFPQVZLblpGOHppbG9vcDhXUXBQY3RN ++cwp0cHBjczhtYkFkWHpoSTVjN0U1VVpDM2NJcEd6SE4raDZLK0F3R3ZEeVFLQmdRRDRBOTdQM29v ++dGhoMHZHQmFWCnZWOXhHTm1YbW5TeUg0b29HcmJvaG1JWkkwVlhZdms5dWViSUJjbDZRMUx4WnN3 ++UFpRMVh5TUhpTjY1Z0E1emgKai9HZGcrdDlvcU5CZml0TUFrUTl1aWxvaXlKVWhYblk5REMvRitl ++ZksycEpNbHdkci9qWEttRHpkQUZBVDgyWQpWRmJ3MlpLVi9GNEJNMUtCdDBZN0RPTmlad0tCZ1FD ++OG9kZk0waytqL25VSzQ4TEV2MGtGbVNMdWdnTVlkM3hVCmZibmx0cUhFTVpJZU45OFVHK2hBWEdw ++dU1Ya0JPM2Mwcm5ZRDVXZkNBRzFxT1V2ZTZzdHd6N0VuK3hWdlkvcWEKU3ZTaDRzMzhnZlBIeXhR ++aGJvNWRwQTZUT3pwT0MyVi9rVXBVRUdJSmVVVllhQ05uWXNpUjRWUGVWL1lvR1htSwpQV29KbnAw ++REtRS0JnQlk3cXBheDJXczVVWlp1TDJBZkNOWkhwd0hySzdqb0VPZUZkWTRrdGRpUkM5OUlsUlZP ++CmUvekVZQXBnektldFVtK3kzRjVaTmVCRW81SWg0TWRyc3ZvdTRFWno5UFNqRGRpVGYzQ1ZKcThq ++Z2VGWDBkTjgKR0g2WTh2K1cwY0ZjRFZ2djhYdkFaYzZOUUt0Mk8vVUM0b1JXek1nN1JtWVBKcjlR ++SWJDYmVDclRBb0dBTjdZbApJbDFMSUVoYkVTaExzZ2c4N09aWnBzL0hVa2FYOWV4Y0p6aFZkcmlk ++UzBkOUgxZE90Uk9XYTQwNUMrQWdTUEx0CjhDQ2xFR3RINVlPZW9Pdi93Z1hWY05WN2N6YTRJVEhh ++SnFYeDZJNEpEZzB3bU44cU5RWHJPQmphRTRyU0kyY3AKNk1JZDhtWmEwTTJSQjB2cHFRdy8xUDl0 ++dUZJdHoySnNHd001cEdFQ2dZQVVnQVV3WENBcEtZVkZFRmxHNlBhYwpvdTBhdzdGNm1aMi9NNUcv ++ek9tMHFDYnNXdGFNU09TdUEvNmlVOXB0NDBaWUFONFUvd2ZxbncyVkVoRnA3dzFNCnpZWmJCRDBx ++ZVlkcDRmc1NuWXFMZmJBVmxQLzB6dmEzdkwwMlJFa25WalBVSnAvaGpKVWhBK21WN252VDZ5VjQK ++cTg4SWVvOEx3Q1c1c2Jtd2lyU3Btdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +diff --git a/src/ukify/test/example.tpm2-pcr-public.pem.base64 b/src/ukify/test/example.tpm2-pcr-public.pem.base64 +new file mode 100644 +index 0000000000..728a0f5362 +--- /dev/null ++++ b/src/ukify/test/example.tpm2-pcr-public.pem.base64 +@@ -0,0 +1,8 @@ ++LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR ++OEFNSUlCQ2dLQ0FRRUF1Wi9LbXFPVElEeUVGckFNbkhjTAo2YnArek9Na0NLQlA2Kzh3QlM1bW56 ++MXEyZThHMllnZGpSZWRWaHZDZko4Z2dRZTl3eXEzMmNwUGJHZ01GbEowCkJwTGJXcUJNMWh1a1JO ++KzdzRC9TVk56bDd5RXVkR3JQeVI3aHVrVG0ydWxnamZHYnpPM1hZNkM5U1JYbTZVU0sKK1ZmK2xQ ++aXkxNUViVEdsdDBiOHppMThqRERuZC9CdFlCclZVeHE2TGMrWHJWL2pBbEVvalUyZy9WTzFnM3Jw ++WApyb3RrNFBrZXkzWVNNZTN1UGFSME44TTVFcXNVL0I0NFVyMHhJeHpIa2V0dEloeEhxSjNPNWRi ++b0k3SEdhVFNoCnJ6TUpydjBvNWwyYzBTVGIrVFVpcXFSSUFXTHhhblNzRTcxdk5Zc3lBS084MmRp ++OCtObDZKN0NHNzk1Slg0eTUKbndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== +diff --git a/src/ukify/test/example.tpm2-pcr-public2.pem.base64 b/src/ukify/test/example.tpm2-pcr-public2.pem.base64 +new file mode 100644 +index 0000000000..44bb3ee9ac +--- /dev/null ++++ b/src/ukify/test/example.tpm2-pcr-public2.pem.base64 +@@ -0,0 +1,8 @@ ++LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR ++OEFNSUlCQ2dLQ0FRRUF0citqSVhjZTVMbkQ2NTVCT0VmbgpLb0ZuL1RxYnJJYjk0K3g1N2ZqdEwv ++L01JcnpqeE9mU3RML3hHemZLTVMrZzJBbGtnUVJEZnd3Y3RpME90SjFrCmJhV08yNTVSdnFTbE11 ++S0IrREhyd1JtNDNoNUl6aWtzREdsRmVKUEVYa1NmUEtsYmtrdFNialJKaklNaHd4T0cKanVKNExa ++MHoxV01PZHJqeDZacE93UEUxbHU0aW5UU0dIMWZQcHhtUSthOXhJbnN3NVVBalE1OFVScjU1eDFj ++MAoxMkJyYVhKV2NvaUsyVk15UU9FRG43WjhxY2orWkFQOGVVdWRUajhXcW1UKzhaRGlnbTZoUDMz ++Nk5CRGY1RGMrCk94WVQzdFpqeGtSV0dWaG5zZUltanJjaXJjWUVrRjJ4bnVCSWNrOFNkbmpydkFC ++SVZTRXIvTTBEb0VwcHBiejMKZndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== +diff --git a/src/ukify/test/meson.build b/src/ukify/test/meson.build +new file mode 100644 +index 0000000000..5870ccc06c +--- /dev/null ++++ b/src/ukify/test/meson.build +@@ -0,0 +1,20 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++ ++if want_ukify and want_tests != 'false' ++ have_pytest_flakes = pymod.find_installation( ++ 'python3', ++ required : false, ++ modules : ['pytest_flakes'], ++ ).found() ++ ++ args = ['-v'] ++ if have_pytest_flakes ++ args += ['--flakes'] ++ endif ++ ++ test('test-ukify', ++ files('test_ukify.py'), ++ args: args, ++ env : test_env, ++ suite : 'ukify') ++endif +diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py +new file mode 100755 +index 0000000000..b12c09d4bf +--- /dev/null ++++ b/src/ukify/test/test_ukify.py +@@ -0,0 +1,848 @@ ++#!/usr/bin/env python3 ++# SPDX-License-Identifier: LGPL-2.1-or-later ++ ++# pylint: disable=unused-import,import-outside-toplevel,useless-else-on-loop ++# pylint: disable=consider-using-with,wrong-import-position,unspecified-encoding ++# pylint: disable=protected-access,redefined-outer-name ++ ++import base64 ++import json ++import os ++import pathlib ++import re ++import shutil ++import subprocess ++import sys ++import tempfile ++import textwrap ++ ++try: ++ import pytest ++except ImportError as e: ++ print(str(e), file=sys.stderr) ++ sys.exit(77) ++ ++try: ++ # pyflakes: noqa ++ import pefile # noqa ++except ImportError as e: ++ print(str(e), file=sys.stderr) ++ sys.exit(77) ++ ++# We import ukify.py, which is a template file. But only __version__ is ++# substituted, which we don't care about here. Having the .py suffix makes it ++# easier to import the file. ++sys.path.append(os.path.dirname(__file__) + '/..') ++import ukify ++ ++build_root = os.getenv('PROJECT_BUILD_ROOT') ++arg_tools = ['--tools', build_root] if build_root else [] ++ ++def systemd_measure(): ++ opts = ukify.create_parser().parse_args(arg_tools) ++ return ukify.find_tool('systemd-measure', opts=opts) ++ ++def test_guess_efi_arch(): ++ arch = ukify.guess_efi_arch() ++ assert arch in ukify.EFI_ARCHES ++ ++def test_shell_join(): ++ assert ukify.shell_join(['a', 'b', ' ']) == "a b ' '" ++ ++def test_round_up(): ++ assert ukify.round_up(0) == 0 ++ assert ukify.round_up(4095) == 4096 ++ assert ukify.round_up(4096) == 4096 ++ assert ukify.round_up(4097) == 8192 ++ ++def test_namespace_creation(): ++ ns = ukify.create_parser().parse_args(()) ++ assert ns.linux is None ++ assert ns.initrd is None ++ ++def test_config_example(): ++ ex = ukify.config_example() ++ assert '[UKI]' in ex ++ assert 'Splash = BMP' in ex ++ ++def test_apply_config(tmp_path): ++ config = tmp_path / 'config1.conf' ++ config.write_text(textwrap.dedent( ++ f''' ++ [UKI] ++ Linux = LINUX ++ Initrd = initrd1 initrd2 ++ initrd3 ++ Cmdline = 1 2 3 4 5 ++ 6 7 8 ++ OSRelease = @some/path1 ++ DeviceTree = some/path2 ++ Splash = some/path3 ++ Uname = 1.2.3 ++ EFIArch=arm ++ Stub = some/path4 ++ PCRBanks = sha512,sha1 ++ SigningEngine = engine1 ++ SecureBootPrivateKey = some/path5 ++ SecureBootCertificate = some/path6 ++ SignKernel = no ++ ++ [PCRSignature:NAME] ++ PCRPrivateKey = some/path7 ++ PCRPublicKey = some/path8 ++ Phases = {':'.join(ukify.KNOWN_PHASES)} ++ ''')) ++ ++ ns = ukify.create_parser().parse_args(['build']) ++ ns.linux = None ++ ns.initrd = [] ++ ukify.apply_config(ns, config) ++ ++ assert ns.linux == pathlib.Path('LINUX') ++ assert ns.initrd == [pathlib.Path('initrd1'), ++ pathlib.Path('initrd2'), ++ pathlib.Path('initrd3')] ++ assert ns.cmdline == '1 2 3 4 5\n6 7 8' ++ assert ns.os_release == '@some/path1' ++ assert ns.devicetree == pathlib.Path('some/path2') ++ assert ns.splash == pathlib.Path('some/path3') ++ assert ns.efi_arch == 'arm' ++ assert ns.stub == pathlib.Path('some/path4') ++ assert ns.pcr_banks == ['sha512', 'sha1'] ++ assert ns.signing_engine == 'engine1' ++ assert ns.sb_key == 'some/path5' ++ assert ns.sb_cert == 'some/path6' ++ assert ns.sign_kernel is False ++ ++ assert ns._groups == ['NAME'] ++ assert ns.pcr_private_keys == [pathlib.Path('some/path7')] ++ assert ns.pcr_public_keys == [pathlib.Path('some/path8')] ++ assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] ++ ++ ukify.finalize_options(ns) ++ ++ assert ns.linux == pathlib.Path('LINUX') ++ assert ns.initrd == [pathlib.Path('initrd1'), ++ pathlib.Path('initrd2'), ++ pathlib.Path('initrd3')] ++ assert ns.cmdline == '1 2 3 4 5 6 7 8' ++ assert ns.os_release == pathlib.Path('some/path1') ++ assert ns.devicetree == pathlib.Path('some/path2') ++ assert ns.splash == pathlib.Path('some/path3') ++ assert ns.efi_arch == 'arm' ++ assert ns.stub == pathlib.Path('some/path4') ++ assert ns.pcr_banks == ['sha512', 'sha1'] ++ assert ns.signing_engine == 'engine1' ++ assert ns.sb_key == 'some/path5' ++ assert ns.sb_cert == 'some/path6' ++ assert ns.sign_kernel is False ++ ++ assert ns._groups == ['NAME'] ++ assert ns.pcr_private_keys == [pathlib.Path('some/path7')] ++ assert ns.pcr_public_keys == [pathlib.Path('some/path8')] ++ assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] ++ ++def test_parse_args_minimal(): ++ with pytest.raises(ValueError): ++ ukify.parse_args([]) ++ ++ opts = ukify.parse_args('arg1 arg2'.split()) ++ assert opts.linux == pathlib.Path('arg1') ++ assert opts.initrd == [pathlib.Path('arg2')] ++ assert opts.os_release in (pathlib.Path('/etc/os-release'), ++ pathlib.Path('/usr/lib/os-release')) ++ ++def test_parse_args_many_deprecated(): ++ opts = ukify.parse_args( ++ ['/ARG1', '///ARG2', '/ARG3 WITH SPACE', ++ '--cmdline=a b c', ++ '--os-release=K1=V1\nK2=V2', ++ '--devicetree=DDDDTTTT', ++ '--splash=splash', ++ '--pcrpkey=PATH', ++ '--uname=1.2.3', ++ '--stub=STUBPATH', ++ '--pcr-private-key=PKEY1', ++ '--pcr-public-key=PKEY2', ++ '--pcr-banks=SHA1,SHA256', ++ '--signing-engine=ENGINE', ++ '--secureboot-private-key=SBKEY', ++ '--secureboot-certificate=SBCERT', ++ '--sign-kernel', ++ '--no-sign-kernel', ++ '--tools=TOOLZ///', ++ '--output=OUTPUT', ++ '--measure', ++ '--no-measure', ++ ]) ++ assert opts.linux == pathlib.Path('/ARG1') ++ assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] ++ assert opts.cmdline == 'a b c' ++ assert opts.os_release == 'K1=V1\nK2=V2' ++ assert opts.devicetree == pathlib.Path('DDDDTTTT') ++ assert opts.splash == pathlib.Path('splash') ++ assert opts.pcrpkey == pathlib.Path('PATH') ++ assert opts.uname == '1.2.3' ++ assert opts.stub == pathlib.Path('STUBPATH') ++ assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] ++ assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] ++ assert opts.pcr_banks == ['SHA1', 'SHA256'] ++ assert opts.signing_engine == 'ENGINE' ++ assert opts.sb_key == 'SBKEY' ++ assert opts.sb_cert == 'SBCERT' ++ assert opts.sign_kernel is False ++ assert opts.tools == [pathlib.Path('TOOLZ/')] ++ assert opts.output == pathlib.Path('OUTPUT') ++ assert opts.measure is False ++ ++def test_parse_args_many(): ++ opts = ukify.parse_args( ++ ['build', ++ '--linux=/ARG1', ++ '--initrd=///ARG2', ++ '--initrd=/ARG3 WITH SPACE', ++ '--cmdline=a b c', ++ '--os-release=K1=V1\nK2=V2', ++ '--devicetree=DDDDTTTT', ++ '--splash=splash', ++ '--pcrpkey=PATH', ++ '--uname=1.2.3', ++ '--stub=STUBPATH', ++ '--pcr-private-key=PKEY1', ++ '--pcr-public-key=PKEY2', ++ '--pcr-banks=SHA1,SHA256', ++ '--signing-engine=ENGINE', ++ '--secureboot-private-key=SBKEY', ++ '--secureboot-certificate=SBCERT', ++ '--sign-kernel', ++ '--no-sign-kernel', ++ '--tools=TOOLZ///', ++ '--output=OUTPUT', ++ '--measure', ++ '--no-measure', ++ ]) ++ assert opts.linux == pathlib.Path('/ARG1') ++ assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] ++ assert opts.cmdline == 'a b c' ++ assert opts.os_release == 'K1=V1\nK2=V2' ++ assert opts.devicetree == pathlib.Path('DDDDTTTT') ++ assert opts.splash == pathlib.Path('splash') ++ assert opts.pcrpkey == pathlib.Path('PATH') ++ assert opts.uname == '1.2.3' ++ assert opts.stub == pathlib.Path('STUBPATH') ++ assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] ++ assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] ++ assert opts.pcr_banks == ['SHA1', 'SHA256'] ++ assert opts.signing_engine == 'ENGINE' ++ assert opts.sb_key == 'SBKEY' ++ assert opts.sb_cert == 'SBCERT' ++ assert opts.sign_kernel is False ++ assert opts.tools == [pathlib.Path('TOOLZ/')] ++ assert opts.output == pathlib.Path('OUTPUT') ++ assert opts.measure is False ++ ++def test_parse_sections(): ++ opts = ukify.parse_args( ++ ['build', ++ '--linux=/ARG1', ++ '--initrd=/ARG2', ++ '--section=test:TESTTESTTEST', ++ '--section=test2:@FILE', ++ ]) ++ ++ assert opts.linux == pathlib.Path('/ARG1') ++ assert opts.initrd == [pathlib.Path('/ARG2')] ++ assert len(opts.sections) == 2 ++ ++ assert opts.sections[0].name == 'test' ++ assert isinstance(opts.sections[0].content, pathlib.Path) ++ assert opts.sections[0].tmpfile ++ assert opts.sections[0].measure is False ++ ++ assert opts.sections[1].name == 'test2' ++ assert opts.sections[1].content == pathlib.Path('FILE') ++ assert opts.sections[1].tmpfile is None ++ assert opts.sections[1].measure is False ++ ++def test_config_priority(tmp_path): ++ config = tmp_path / 'config1.conf' ++ # config: use pesign and give certdir + certname ++ config.write_text(textwrap.dedent( ++ f''' ++ [UKI] ++ Linux = LINUX ++ Initrd = initrd1 initrd2 ++ initrd3 ++ Cmdline = 1 2 3 4 5 ++ 6 7 8 ++ OSRelease = @some/path1 ++ DeviceTree = some/path2 ++ Splash = some/path3 ++ Uname = 1.2.3 ++ EFIArch = arm ++ Stub = some/path4 ++ PCRBanks = sha512,sha1 ++ SigningEngine = engine1 ++ SecureBootSigningTool = pesign ++ SecureBootCertificateDir = some/path5 ++ SecureBootCertificateName = some/name1 ++ SignKernel = no ++ ++ [PCRSignature:NAME] ++ PCRPrivateKey = some/path7 ++ PCRPublicKey = some/path8 ++ Phases = {':'.join(ukify.KNOWN_PHASES)} ++ ''')) ++ ++ # args: use sbsign and give key + cert, should override pesign ++ opts = ukify.parse_args( ++ ['build', ++ '--linux=/ARG1', ++ '--initrd=///ARG2', ++ '--initrd=/ARG3 WITH SPACE', ++ '--cmdline= a b c ', ++ '--os-release=K1=V1\nK2=V2', ++ '--devicetree=DDDDTTTT', ++ '--splash=splash', ++ '--pcrpkey=PATH', ++ '--uname=1.2.3', ++ '--stub=STUBPATH', ++ '--pcr-private-key=PKEY1', ++ '--pcr-public-key=PKEY2', ++ '--pcr-banks=SHA1,SHA256', ++ '--signing-engine=ENGINE', ++ '--signtool=sbsign', ++ '--secureboot-private-key=SBKEY', ++ '--secureboot-certificate=SBCERT', ++ '--sign-kernel', ++ '--no-sign-kernel', ++ '--tools=TOOLZ///', ++ '--output=OUTPUT', ++ '--measure', ++ ]) ++ ++ ukify.apply_config(opts, config) ++ ukify.finalize_options(opts) ++ ++ assert opts.linux == pathlib.Path('/ARG1') ++ assert opts.initrd == [pathlib.Path('initrd1'), ++ pathlib.Path('initrd2'), ++ pathlib.Path('initrd3'), ++ pathlib.Path('/ARG2'), ++ pathlib.Path('/ARG3 WITH SPACE')] ++ assert opts.cmdline == 'a b c' ++ assert opts.os_release == 'K1=V1\nK2=V2' ++ assert opts.devicetree == pathlib.Path('DDDDTTTT') ++ assert opts.splash == pathlib.Path('splash') ++ assert opts.pcrpkey == pathlib.Path('PATH') ++ assert opts.uname == '1.2.3' ++ assert opts.stub == pathlib.Path('STUBPATH') ++ assert opts.pcr_private_keys == [pathlib.Path('PKEY1'), ++ pathlib.Path('some/path7')] ++ assert opts.pcr_public_keys == [pathlib.Path('PKEY2'), ++ pathlib.Path('some/path8')] ++ assert opts.pcr_banks == ['SHA1', 'SHA256'] ++ assert opts.signing_engine == 'ENGINE' ++ assert opts.signtool == 'sbsign' # from args ++ assert opts.sb_key == 'SBKEY' # from args ++ assert opts.sb_cert == 'SBCERT' # from args ++ assert opts.sb_certdir == 'some/path5' # from config ++ assert opts.sb_cert_name == 'some/name1' # from config ++ assert opts.sign_kernel is False ++ assert opts.tools == [pathlib.Path('TOOLZ/')] ++ assert opts.output == pathlib.Path('OUTPUT') ++ assert opts.measure is True ++ ++def test_help(capsys): ++ with pytest.raises(SystemExit): ++ ukify.parse_args(['--help']) ++ out = capsys.readouterr() ++ assert '--section' in out.out ++ assert not out.err ++ ++def test_help_display(capsys): ++ with pytest.raises(SystemExit): ++ ukify.parse_args(['inspect', '--help']) ++ out = capsys.readouterr() ++ assert '--section' in out.out ++ assert not out.err ++ ++def test_help_error_deprecated(capsys): ++ with pytest.raises(SystemExit): ++ ukify.parse_args(['a', 'b', '--no-such-option']) ++ out = capsys.readouterr() ++ assert not out.out ++ assert '--no-such-option' in out.err ++ assert len(out.err.splitlines()) == 1 ++ ++def test_help_error(capsys): ++ with pytest.raises(SystemExit): ++ ukify.parse_args(['build', '--no-such-option']) ++ out = capsys.readouterr() ++ assert not out.out ++ assert '--no-such-option' in out.err ++ assert len(out.err.splitlines()) == 1 ++ ++@pytest.fixture(scope='session') ++def kernel_initrd(): ++ opts = ukify.create_parser().parse_args(arg_tools) ++ bootctl = ukify.find_tool('bootctl', opts=opts) ++ if bootctl is None: ++ return None ++ ++ try: ++ text = subprocess.check_output([bootctl, 'list', '--json=short'], ++ text=True) ++ except subprocess.CalledProcessError: ++ return None ++ ++ items = json.loads(text) ++ ++ for item in items: ++ try: ++ linux = f"{item['root']}{item['linux']}" ++ initrd = f"{item['root']}{item['initrd'][0].split(' ')[0]}" ++ except (KeyError, IndexError): ++ continue ++ return ['--linux', linux, '--initrd', initrd] ++ else: ++ return None ++ ++def test_check_splash(): ++ try: ++ # pyflakes: noqa ++ import PIL # noqa ++ except ImportError: ++ pytest.skip('PIL not available') ++ ++ with pytest.raises(OSError): ++ ukify.check_splash(os.devnull) ++ ++def test_basic_operation(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ ++ output = f'{tmpdir}/basic.efi' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ ]) ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ subprocess.check_output(['objdump', '-h', output]) ++ ++def test_sections(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ ++ output = f'{tmpdir}/basic.efi' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--cmdline=ARG1 ARG2 ARG3', ++ '--os-release=K1=V1\nK2=V2\n', ++ '--section=.test:CONTENTZ', ++ ]) ++ ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ dump = subprocess.check_output(['objdump', '-h', output], text=True) ++ ++ for sect in 'text osrel cmdline linux initrd uname test'.split(): ++ assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE) ++ ++def test_addon(tmpdir): ++ output = f'{tmpdir}/addon.efi' ++ args = [ ++ 'build', ++ f'--output={output}', ++ '--cmdline=ARG1 ARG2 ARG3', ++ """--sbat=sbat,1,foo ++foo,1 ++bar,2 ++""", ++ '--section=.test:CONTENTZ', ++ """--sbat=sbat,1,foo ++baz,3 ++""" ++ ] ++ if stub := os.getenv('EFI_ADDON'): ++ args += [f'--stub={stub}'] ++ expected_exceptions = () ++ else: ++ expected_exceptions = (FileNotFoundError,) ++ ++ opts = ukify.parse_args(args) ++ try: ++ ukify.check_inputs(opts) ++ except expected_exceptions as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ dump = subprocess.check_output(['objdump', '-h', output], text=True) ++ ++ for sect in 'text cmdline test sbat'.split(): ++ assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE) ++ ++ pe = pefile.PE(output, fast_load=True) ++ found = False ++ ++ for section in pe.sections: ++ if section.Name.rstrip(b"\x00").decode() == ".sbat": ++ assert found is False ++ split = section.get_data().rstrip(b"\x00").decode().splitlines() ++ assert split == ["sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md", "foo,1", "bar,2", "baz,3"] ++ found = True ++ ++ assert found is True ++ ++ ++def unbase64(filename): ++ tmp = tempfile.NamedTemporaryFile() ++ base64.decode(filename.open('rb'), tmp) ++ tmp.flush() ++ return tmp ++ ++ ++def test_uname_scraping(kernel_initrd): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ ++ assert kernel_initrd[0] == '--linux' ++ uname = ukify.Uname.scrape(kernel_initrd[1]) ++ assert re.match(r'\d+\.\d+\.\d+', uname) ++ ++def test_efi_signing_sbsign(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if not shutil.which('sbsign'): ++ pytest.skip('sbsign not found') ++ ++ ourdir = pathlib.Path(__file__).parent ++ cert = unbase64(ourdir / 'example.signing.crt.base64') ++ key = unbase64(ourdir / 'example.signing.key.base64') ++ ++ output = f'{tmpdir}/signed.efi' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--cmdline=ARG1 ARG2 ARG3', ++ f'--secureboot-certificate={cert.name}', ++ f'--secureboot-private-key={key.name}', ++ ]) ++ ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ if shutil.which('sbverify'): ++ # let's check that sbverify likes the resulting file ++ dump = subprocess.check_output([ ++ 'sbverify', ++ '--cert', cert.name, ++ output, ++ ], text=True) ++ ++ assert 'Signature verification OK' in dump ++ ++def test_efi_signing_pesign(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if not shutil.which('pesign'): ++ pytest.skip('pesign not found') ++ ++ nss_db = f'{tmpdir}/nss_db' ++ name = 'Test_Secureboot' ++ author = 'systemd' ++ ++ subprocess.check_call(['mkdir', '-p', nss_db]) ++ cmd = f'certutil -N --empty-password -d {nss_db}'.split(' ') ++ subprocess.check_call(cmd) ++ cmd = f'efikeygen -d {nss_db} -S -k -c CN={author} -n {name}'.split(' ') ++ subprocess.check_call(cmd) ++ ++ output = f'{tmpdir}/signed.efi' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--signtool=pesign', ++ '--cmdline=ARG1 ARG2 ARG3', ++ f'--secureboot-certificate-name={name}', ++ f'--secureboot-certificate-dir={nss_db}', ++ ]) ++ ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that sbverify likes the resulting file ++ dump = subprocess.check_output([ ++ 'pesign', '-S', ++ '-i', output, ++ ], text=True) ++ ++ assert f"The signer's common name is {author}" in dump ++ ++def test_inspect(kernel_initrd, tmpdir, capsys): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if not shutil.which('sbsign'): ++ pytest.skip('sbsign not found') ++ ++ ourdir = pathlib.Path(__file__).parent ++ cert = unbase64(ourdir / 'example.signing.crt.base64') ++ key = unbase64(ourdir / 'example.signing.key.base64') ++ ++ output = f'{tmpdir}/signed2.efi' ++ uname_arg='1.2.3' ++ osrel_arg='Linux' ++ cmdline_arg='ARG1 ARG2 ARG3' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--cmdline={cmdline_arg}', ++ f'--os-release={osrel_arg}', ++ f'--uname={uname_arg}', ++ f'--output={output}', ++ f'--secureboot-certificate={cert.name}', ++ f'--secureboot-private-key={key.name}', ++ ]) ++ ++ ukify.check_inputs(opts) ++ ukify.make_uki(opts) ++ ++ opts = ukify.parse_args(['inspect', output]) ++ ukify.inspect_sections(opts) ++ ++ text = capsys.readouterr().out ++ ++ expected_osrel = f'.osrel:\n size: {len(osrel_arg)}' ++ assert expected_osrel in text ++ expected_cmdline = f'.cmdline:\n size: {len(cmdline_arg)}' ++ assert expected_cmdline in text ++ expected_uname = f'.uname:\n size: {len(uname_arg)}' ++ assert expected_uname in text ++ ++ expected_initrd = '.initrd:\n size:' ++ assert expected_initrd in text ++ expected_linux = '.linux:\n size:' ++ assert expected_linux in text ++ ++ ++def test_pcr_signing(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if systemd_measure() is None: ++ pytest.skip('systemd-measure not found') ++ ++ ourdir = pathlib.Path(__file__).parent ++ pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') ++ priv = unbase64(ourdir / 'example.tpm2-pcr-private.pem.base64') ++ ++ output = f'{tmpdir}/signed.efi' ++ args = [ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--cmdline=ARG1 ARG2 ARG3', ++ '--os-release=ID=foobar\n', ++ '--pcr-banks=sha1', # use sha1 because it doesn't really matter ++ f'--pcr-private-key={priv.name}', ++ ] + arg_tools ++ ++ # If the public key is not explicitly specified, it is derived automatically. Let's make sure everything ++ # works as expected both when the public keys is specified explicitly and when it is derived from the ++ # private key. ++ for extra in ([f'--pcrpkey={pub.name}', f'--pcr-public-key={pub.name}'], []): ++ opts = ukify.parse_args(args + extra) ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ dump = subprocess.check_output(['objdump', '-h', output], text=True) ++ ++ for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): ++ assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE) ++ ++ # objcopy fails when called without an output argument (EPERM). ++ # It also fails when called with /dev/null (file truncated). ++ # It also fails when called with /dev/zero (because it reads the ++ # output file, infinitely in this case.) ++ # So let's just call it with a dummy output argument. ++ subprocess.check_call([ ++ 'objcopy', ++ *(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in ( ++ 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')), ++ output, ++ tmpdir / 'dummy', ++ ], ++ text=True) ++ ++ assert open(tmpdir / 'out.pcrpkey').read() == open(pub.name).read() ++ assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n' ++ assert open(tmpdir / 'out.uname').read() == '1.2.3' ++ assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3' ++ sig = open(tmpdir / 'out.pcrsig').read() ++ sig = json.loads(sig) ++ assert list(sig.keys()) == ['sha1'] ++ assert len(sig['sha1']) == 4 # four items for four phases ++ ++def test_pcr_signing2(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if systemd_measure() is None: ++ pytest.skip('systemd-measure not found') ++ ++ ourdir = pathlib.Path(__file__).parent ++ pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') ++ priv = unbase64(ourdir / 'example.tpm2-pcr-private.pem.base64') ++ pub2 = unbase64(ourdir / 'example.tpm2-pcr-public2.pem.base64') ++ priv2 = unbase64(ourdir / 'example.tpm2-pcr-private2.pem.base64') ++ ++ # simulate a microcode file ++ with open(f'{tmpdir}/microcode', 'wb') as microcode: ++ microcode.write(b'1234567890') ++ ++ output = f'{tmpdir}/signed.efi' ++ assert kernel_initrd[0] == '--linux' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd[:2], ++ f'--initrd={microcode.name}', ++ *kernel_initrd[2:], ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--cmdline=ARG1 ARG2 ARG3', ++ '--os-release=ID=foobar\n', ++ '--pcr-banks=sha1', ++ f'--pcrpkey={pub2.name}', ++ f'--pcr-public-key={pub.name}', ++ f'--pcr-private-key={priv.name}', ++ '--phases=enter-initrd enter-initrd:leave-initrd', ++ f'--pcr-public-key={pub2.name}', ++ f'--pcr-private-key={priv2.name}', ++ '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable ++ ] + arg_tools) ++ ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ dump = subprocess.check_output(['objdump', '-h', output], text=True) ++ ++ for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): ++ assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE) ++ ++ subprocess.check_call([ ++ 'objcopy', ++ *(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in ( ++ 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')), ++ output, ++ tmpdir / 'dummy', ++ ], ++ text=True) ++ ++ assert open(tmpdir / 'out.pcrpkey').read() == open(pub2.name).read() ++ assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n' ++ assert open(tmpdir / 'out.uname').read() == '1.2.3' ++ assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3' ++ assert open(tmpdir / 'out.initrd', 'rb').read(10) == b'1234567890' ++ ++ sig = open(tmpdir / 'out.pcrsig').read() ++ sig = json.loads(sig) ++ assert list(sig.keys()) == ['sha1'] ++ assert len(sig['sha1']) == 6 # six items for six phases paths ++ ++def test_key_cert_generation(tmpdir): ++ opts = ukify.parse_args([ ++ 'genkey', ++ f"--pcr-public-key={tmpdir / 'pcr1.pub.pem'}", ++ f"--pcr-private-key={tmpdir / 'pcr1.priv.pem'}", ++ '--phases=enter-initrd enter-initrd:leave-initrd', ++ f"--pcr-public-key={tmpdir / 'pcr2.pub.pem'}", ++ f"--pcr-private-key={tmpdir / 'pcr2.priv.pem'}", ++ '--phases=sysinit ready', ++ f"--secureboot-private-key={tmpdir / 'sb.priv.pem'}", ++ f"--secureboot-certificate={tmpdir / 'sb.cert.pem'}", ++ ]) ++ assert opts.verb == 'genkey' ++ ukify.check_cert_and_keys_nonexistent(opts) ++ ++ pytest.importorskip('cryptography') ++ ++ ukify.generate_keys(opts) ++ ++ if not shutil.which('openssl'): ++ return ++ ++ for key in (tmpdir / 'pcr1.priv.pem', ++ tmpdir / 'pcr2.priv.pem', ++ tmpdir / 'sb.priv.pem'): ++ out = subprocess.check_output([ ++ 'openssl', 'rsa', ++ '-in', key, ++ '-text', ++ '-noout', ++ ], text = True) ++ assert 'Private-Key' in out ++ assert '2048 bit' in out ++ ++ for pub in (tmpdir / 'pcr1.pub.pem', ++ tmpdir / 'pcr2.pub.pem'): ++ out = subprocess.check_output([ ++ 'openssl', 'rsa', ++ '-pubin', ++ '-in', pub, ++ '-text', ++ '-noout', ++ ], text = True) ++ assert 'Public-Key' in out ++ assert '2048 bit' in out ++ ++ out = subprocess.check_output([ ++ 'openssl', 'x509', ++ '-in', tmpdir / 'sb.cert.pem', ++ '-text', ++ '-noout', ++ ], text = True) ++ assert 'Certificate' in out ++ assert 'Issuer: CN = SecureBoot signing key on host' in out ++ ++if __name__ == '__main__': ++ sys.exit(pytest.main(sys.argv)) +diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py +new file mode 100755 +index 0000000000..08f505a271 +--- /dev/null ++++ b/src/ukify/ukify.py +@@ -0,0 +1,1660 @@ ++#!/usr/bin/env python3 ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++# This file is part of systemd. ++# ++# systemd 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. ++# ++# systemd 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 ++# General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public License ++# along with systemd; If not, see . ++ ++# pylint: disable=import-outside-toplevel,consider-using-with,unused-argument ++# pylint: disable=unnecessary-lambda-assignment ++ ++import argparse ++import configparser ++import contextlib ++import collections ++import dataclasses ++import datetime ++import fnmatch ++import itertools ++import json ++import os ++import pathlib ++import pprint ++import pydoc ++import re ++import shlex ++import shutil ++import socket ++import subprocess ++import sys ++import tempfile ++import textwrap ++from hashlib import sha256 ++from typing import (Any, ++ Callable, ++ IO, ++ Optional, ++ Sequence, ++ Union) ++ ++import pefile # type: ignore ++ ++__version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})' ++ ++EFI_ARCH_MAP = { ++ # host_arch glob : [efi_arch, 32_bit_efi_arch if mixed mode is supported] ++ 'x86_64' : ['x64', 'ia32'], ++ 'i[3456]86' : ['ia32'], ++ 'aarch64' : ['aa64'], ++ 'armv[45678]*l': ['arm'], ++ 'loongarch32' : ['loongarch32'], ++ 'loongarch64' : ['loongarch64'], ++ 'riscv32' : ['riscv32'], ++ 'riscv64' : ['riscv64'], ++} ++EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), []) ++ ++# Default configuration directories and file name. ++# When the user does not specify one, the directories are searched in this order and the first file found is used. ++DEFAULT_CONFIG_DIRS = ['/run/systemd', '/etc/systemd', '/usr/local/lib/systemd', '/usr/lib/systemd'] ++DEFAULT_CONFIG_FILE = 'ukify.conf' ++ ++class Style: ++ bold = "\033[0;1;39m" if sys.stderr.isatty() else "" ++ gray = "\033[0;38;5;245m" if sys.stderr.isatty() else "" ++ red = "\033[31;1m" if sys.stderr.isatty() else "" ++ yellow = "\033[33;1m" if sys.stderr.isatty() else "" ++ reset = "\033[0m" if sys.stderr.isatty() else "" ++ ++ ++def guess_efi_arch(): ++ arch = os.uname().machine ++ ++ for glob, mapping in EFI_ARCH_MAP.items(): ++ if fnmatch.fnmatch(arch, glob): ++ efi_arch, *fallback = mapping ++ break ++ else: ++ raise ValueError(f'Unsupported architecture {arch}') ++ ++ # This makes sense only on some architectures, but it also probably doesn't ++ # hurt on others, so let's just apply the check everywhere. ++ if fallback: ++ fw_platform_size = pathlib.Path('/sys/firmware/efi/fw_platform_size') ++ try: ++ size = fw_platform_size.read_text().strip() ++ except FileNotFoundError: ++ pass ++ else: ++ if int(size) == 32: ++ efi_arch = fallback[0] ++ ++ # print(f'Host arch {arch!r}, EFI arch {efi_arch!r}') ++ return efi_arch ++ ++ ++def page(text: str, enabled: Optional[bool]) -> None: ++ if enabled: ++ # Initialize less options from $SYSTEMD_LESS or provide a suitable fallback. ++ os.environ['LESS'] = os.getenv('SYSTEMD_LESS', 'FRSXMK') ++ pydoc.pager(text) ++ else: ++ print(text) ++ ++ ++def shell_join(cmd): ++ # TODO: drop in favour of shlex.join once shlex.join supports pathlib.Path. ++ return ' '.join(shlex.quote(str(x)) for x in cmd) ++ ++ ++def round_up(x, blocksize=4096): ++ return (x + blocksize - 1) // blocksize * blocksize ++ ++ ++def try_import(modname, name=None): ++ try: ++ return __import__(modname) ++ except ImportError as e: ++ raise ValueError(f'Kernel is compressed with {name or modname}, but module unavailable') from e ++ ++ ++def maybe_decompress(filename): ++ """Decompress file if compressed. Return contents.""" ++ f = open(filename, 'rb') ++ start = f.read(4) ++ f.seek(0) ++ ++ if start.startswith(b'\x7fELF'): ++ # not compressed ++ return f.read() ++ ++ if start.startswith(b'MZ'): ++ # not compressed aarch64 and riscv64 ++ return f.read() ++ ++ if start.startswith(b'\x1f\x8b'): ++ gzip = try_import('gzip') ++ return gzip.open(f).read() ++ ++ if start.startswith(b'\x28\xb5\x2f\xfd'): ++ zstd = try_import('zstd') ++ return zstd.uncompress(f.read()) ++ ++ if start.startswith(b'\x02\x21\x4c\x18'): ++ lz4 = try_import('lz4.frame', 'lz4') ++ return lz4.frame.decompress(f.read()) ++ ++ if start.startswith(b'\x04\x22\x4d\x18'): ++ print('Newer lz4 stream format detected! This may not boot!') ++ lz4 = try_import('lz4.frame', 'lz4') ++ return lz4.frame.decompress(f.read()) ++ ++ if start.startswith(b'\x89LZO'): ++ # python3-lzo is not packaged for Fedora ++ raise NotImplementedError('lzo decompression not implemented') ++ ++ if start.startswith(b'BZh'): ++ bz2 = try_import('bz2', 'bzip2') ++ return bz2.open(f).read() ++ ++ if start.startswith(b'\x5d\x00\x00'): ++ lzma = try_import('lzma') ++ return lzma.open(f).read() ++ ++ raise NotImplementedError(f'unknown file format (starts with {start})') ++ ++ ++class Uname: ++ # This class is here purely as a namespace for the functions ++ ++ VERSION_PATTERN = r'(?P[a-z0-9._-]+) \([^ )]+\) (?:#.*)' ++ ++ NOTES_PATTERN = r'^\s+Linux\s+0x[0-9a-f]+\s+OPEN\n\s+description data: (?P[0-9a-f ]+)\s*$' ++ ++ # Linux version 6.0.8-300.fc37.ppc64le (mockbuild@buildvm-ppc64le-03.iad2.fedoraproject.org) ++ # (gcc (GCC) 12.2.1 20220819 (Red Hat 12.2.1-2), GNU ld version 2.38-24.fc37) ++ # #1 SMP Fri Nov 11 14:39:11 UTC 2022 ++ TEXT_PATTERN = rb'Linux version (?P\d\.\S+) \(' ++ ++ @classmethod ++ def scrape_x86(cls, filename, opts=None): ++ # Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L136 ++ # and https://www.kernel.org/doc/html/latest/x86/boot.html#the-real-mode-kernel-header ++ with open(filename, 'rb') as f: ++ f.seek(0x202) ++ magic = f.read(4) ++ if magic != b'HdrS': ++ raise ValueError('Real-Mode Kernel Header magic not found') ++ f.seek(0x20E) ++ offset = f.read(1)[0] + f.read(1)[0]*256 # Pointer to kernel version string ++ f.seek(0x200 + offset) ++ text = f.read(128) ++ text = text.split(b'\0', maxsplit=1)[0] ++ text = text.decode() ++ ++ if not (m := re.match(cls.VERSION_PATTERN, text)): ++ raise ValueError(f'Cannot parse version-host-release uname string: {text!r}') ++ return m.group('version') ++ ++ @classmethod ++ def scrape_elf(cls, filename, opts=None): ++ readelf = find_tool('readelf', opts=opts) ++ ++ cmd = [ ++ readelf, ++ '--notes', ++ filename, ++ ] ++ ++ print('+', shell_join(cmd)) ++ try: ++ notes = subprocess.check_output(cmd, stderr=subprocess.PIPE, text=True) ++ except subprocess.CalledProcessError as e: ++ raise ValueError(e.stderr.strip()) from e ++ ++ if not (m := re.search(cls.NOTES_PATTERN, notes, re.MULTILINE)): ++ raise ValueError('Cannot find Linux version note') ++ ++ text = ''.join(chr(int(c, 16)) for c in m.group('version').split()) ++ return text.rstrip('\0') ++ ++ @classmethod ++ def scrape_generic(cls, filename, opts=None): ++ # import libarchive ++ # libarchive-c fails with ++ # ArchiveError: Unrecognized archive format (errno=84, retcode=-30, archive_p=94705420454656) ++ ++ # Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L209 ++ ++ text = maybe_decompress(filename) ++ if not (m := re.search(cls.TEXT_PATTERN, text)): ++ raise ValueError(f'Cannot find {cls.TEXT_PATTERN!r} in {filename}') ++ ++ return m.group('version').decode() ++ ++ @classmethod ++ def scrape(cls, filename, opts=None): ++ for func in (cls.scrape_x86, cls.scrape_elf, cls.scrape_generic): ++ try: ++ version = func(filename, opts=opts) ++ print(f'Found uname version: {version}') ++ return version ++ except ValueError as e: ++ print(str(e)) ++ return None ++ ++DEFAULT_SECTIONS_TO_SHOW = { ++ '.linux' : 'binary', ++ '.initrd' : 'binary', ++ '.splash' : 'binary', ++ '.dtb' : 'binary', ++ '.cmdline' : 'text', ++ '.osrel' : 'text', ++ '.uname' : 'text', ++ '.pcrpkey' : 'text', ++ '.pcrsig' : 'text', ++ '.sbat' : 'text', ++ '.sbom' : 'binary', ++} ++ ++@dataclasses.dataclass ++class Section: ++ name: str ++ content: Optional[pathlib.Path] ++ tmpfile: Optional[IO] = None ++ measure: bool = False ++ output_mode: Optional[str] = None ++ ++ @classmethod ++ def create(cls, name, contents, **kwargs): ++ if isinstance(contents, (str, bytes)): ++ mode = 'wt' if isinstance(contents, str) else 'wb' ++ tmp = tempfile.NamedTemporaryFile(mode=mode, prefix=f'tmp{name}') ++ tmp.write(contents) ++ tmp.flush() ++ contents = pathlib.Path(tmp.name) ++ else: ++ tmp = None ++ ++ return cls(name, contents, tmpfile=tmp, **kwargs) ++ ++ @classmethod ++ def parse_input(cls, s): ++ try: ++ name, contents, *rest = s.split(':') ++ except ValueError as e: ++ raise ValueError(f'Cannot parse section spec (name or contents missing): {s!r}') from e ++ if rest: ++ raise ValueError(f'Cannot parse section spec (extraneous parameters): {s!r}') ++ ++ if contents.startswith('@'): ++ contents = pathlib.Path(contents[1:]) ++ ++ sec = cls.create(name, contents) ++ sec.check_name() ++ return sec ++ ++ @classmethod ++ def parse_output(cls, s): ++ if not (m := re.match(r'([a-zA-Z0-9_.]+):(text|binary)(?:@(.+))?', s)): ++ raise ValueError(f'Cannot parse section spec: {s!r}') ++ ++ name, ttype, out = m.groups() ++ out = pathlib.Path(out) if out else None ++ ++ return cls.create(name, out, output_mode=ttype) ++ ++ def size(self): ++ return self.content.stat().st_size ++ ++ def check_name(self): ++ # PE section names with more than 8 characters are legal, but our stub does ++ # not support them. ++ if not self.name.isascii() or not self.name.isprintable(): ++ raise ValueError(f'Bad section name: {self.name!r}') ++ if len(self.name) > 8: ++ raise ValueError(f'Section name too long: {self.name!r}') ++ ++ ++@dataclasses.dataclass ++class UKI: ++ executable: list[Union[pathlib.Path, str]] ++ sections: list[Section] = dataclasses.field(default_factory=list, init=False) ++ ++ def add_section(self, section): ++ if section.name in [s.name for s in self.sections]: ++ raise ValueError(f'Duplicate section {section.name}') ++ ++ self.sections += [section] ++ ++ ++def parse_banks(s): ++ banks = re.split(r',|\s+', s) ++ # TODO: do some sanity checking here ++ return banks ++ ++ ++KNOWN_PHASES = ( ++ 'enter-initrd', ++ 'leave-initrd', ++ 'sysinit', ++ 'ready', ++ 'shutdown', ++ 'final', ++) ++ ++def parse_phase_paths(s): ++ # Split on commas or whitespace here. Commas might be hard to parse visually. ++ paths = re.split(r',|\s+', s) ++ ++ for path in paths: ++ for phase in path.split(':'): ++ if phase not in KNOWN_PHASES: ++ raise argparse.ArgumentTypeError(f'Unknown boot phase {phase!r} ({path=})') ++ ++ return paths ++ ++ ++def check_splash(filename): ++ if filename is None: ++ return ++ ++ # import is delayed, to avoid import when the splash image is not used ++ try: ++ from PIL import Image ++ except ImportError: ++ return ++ ++ img = Image.open(filename, formats=['BMP']) ++ print(f'Splash image {filename} is {img.width}×{img.height} pixels') ++ ++ ++def check_inputs(opts): ++ for name, value in vars(opts).items(): ++ if name in {'output', 'tools'}: ++ continue ++ ++ if isinstance(value, pathlib.Path): ++ # Open file to check that we can read it, or generate an exception ++ value.open().close() ++ elif isinstance(value, list): ++ for item in value: ++ if isinstance(item, pathlib.Path): ++ item.open().close() ++ ++ check_splash(opts.splash) ++ ++ ++def check_cert_and_keys_nonexistent(opts): ++ # Raise if any of the keys and certs are found on disk ++ paths = itertools.chain( ++ (opts.sb_key, opts.sb_cert), ++ *((priv_key, pub_key) ++ for priv_key, pub_key, _ in key_path_groups(opts))) ++ for path in paths: ++ if path and path.exists(): ++ raise ValueError(f'{path} is present') ++ ++ ++def find_tool(name, fallback=None, opts=None): ++ if opts and opts.tools: ++ for d in opts.tools: ++ tool = d / name ++ if tool.exists(): ++ return tool ++ ++ if shutil.which(name) is not None: ++ return name ++ ++ if fallback is None: ++ print(f"Tool {name} not installed!") ++ ++ return fallback ++ ++def combine_signatures(pcrsigs): ++ combined = collections.defaultdict(list) ++ for pcrsig in pcrsigs: ++ for bank, sigs in pcrsig.items(): ++ for sig in sigs: ++ if sig not in combined[bank]: ++ combined[bank] += [sig] ++ return json.dumps(combined) ++ ++ ++def key_path_groups(opts): ++ if not opts.pcr_private_keys: ++ return ++ ++ n_priv = len(opts.pcr_private_keys) ++ pub_keys = opts.pcr_public_keys or [None] * n_priv ++ pp_groups = opts.phase_path_groups or [None] * n_priv ++ ++ yield from zip(opts.pcr_private_keys, ++ pub_keys, ++ pp_groups) ++ ++ ++def call_systemd_measure(uki, linux, opts): ++ measure_tool = find_tool('systemd-measure', ++ '/usr/lib/systemd/systemd-measure', ++ opts=opts) ++ ++ banks = opts.pcr_banks or () ++ ++ # PCR measurement ++ ++ if opts.measure: ++ pp_groups = opts.phase_path_groups or [] ++ ++ cmd = [ ++ measure_tool, ++ 'calculate', ++ f'--linux={linux}', ++ *(f"--{s.name.removeprefix('.')}={s.content}" ++ for s in uki.sections ++ if s.measure), ++ *(f'--bank={bank}' ++ for bank in banks), ++ # For measurement, the keys are not relevant, so we can lump all the phase paths ++ # into one call to systemd-measure calculate. ++ *(f'--phase={phase_path}' ++ for phase_path in itertools.chain.from_iterable(pp_groups)), ++ ] ++ ++ print('+', shell_join(cmd)) ++ subprocess.check_call(cmd) ++ ++ # PCR signing ++ ++ if opts.pcr_private_keys: ++ pcrsigs = [] ++ ++ cmd = [ ++ measure_tool, ++ 'sign', ++ f'--linux={linux}', ++ *(f"--{s.name.removeprefix('.')}={s.content}" ++ for s in uki.sections ++ if s.measure), ++ *(f'--bank={bank}' ++ for bank in banks), ++ ] ++ ++ for priv_key, pub_key, group in key_path_groups(opts): ++ extra = [f'--private-key={priv_key}'] ++ if pub_key: ++ extra += [f'--public-key={pub_key}'] ++ extra += [f'--phase={phase_path}' for phase_path in group or ()] ++ ++ print('+', shell_join(cmd + extra)) ++ pcrsig = subprocess.check_output(cmd + extra, text=True) ++ pcrsig = json.loads(pcrsig) ++ pcrsigs += [pcrsig] ++ ++ combined = combine_signatures(pcrsigs) ++ uki.add_section(Section.create('.pcrsig', combined)) ++ ++ ++def join_initrds(initrds): ++ if not initrds: ++ return None ++ if len(initrds) == 1: ++ return initrds[0] ++ ++ seq = [] ++ for file in initrds: ++ initrd = file.read_bytes() ++ n = len(initrd) ++ padding = b'\0' * (round_up(n, 4) - n) # pad to 32 bit alignment ++ seq += [initrd, padding] ++ ++ return b''.join(seq) ++ ++ ++def pairwise(iterable): ++ a, b = itertools.tee(iterable) ++ next(b, None) ++ return zip(a, b) ++ ++ ++class PEError(Exception): ++ pass ++ ++ ++def pe_add_sections(uki: UKI, output: str): ++ pe = pefile.PE(uki.executable, fast_load=True) ++ ++ # Old stubs do not have the symbol/string table stripped, even though image files should not have one. ++ if symbol_table := pe.FILE_HEADER.PointerToSymbolTable: ++ symbol_table_size = 18 * pe.FILE_HEADER.NumberOfSymbols ++ if string_table_size := pe.get_dword_from_offset(symbol_table + symbol_table_size): ++ symbol_table_size += string_table_size ++ ++ # Let's be safe and only strip it if it's at the end of the file. ++ if symbol_table + symbol_table_size == len(pe.__data__): ++ pe.__data__ = pe.__data__[:symbol_table] ++ pe.FILE_HEADER.PointerToSymbolTable = 0 ++ pe.FILE_HEADER.NumberOfSymbols = 0 ++ pe.FILE_HEADER.IMAGE_FILE_LOCAL_SYMS_STRIPPED = True ++ ++ # Old stubs might have been stripped, leading to unaligned raw data values, so let's fix them up here. ++ # pylint thinks that Structure doesn't have various members that it has… ++ # pylint: disable=no-member ++ ++ for i, section in enumerate(pe.sections): ++ oldp = section.PointerToRawData ++ oldsz = section.SizeOfRawData ++ section.PointerToRawData = round_up(oldp, pe.OPTIONAL_HEADER.FileAlignment) ++ section.SizeOfRawData = round_up(oldsz, pe.OPTIONAL_HEADER.FileAlignment) ++ padp = section.PointerToRawData - oldp ++ padsz = section.SizeOfRawData - oldsz ++ ++ for later_section in pe.sections[i+1:]: ++ later_section.PointerToRawData += padp + padsz ++ ++ pe.__data__ = pe.__data__[:oldp] + bytes(padp) + pe.__data__[oldp:oldp+oldsz] + bytes(padsz) + pe.__data__[oldp+oldsz:] ++ ++ # We might not have any space to add new sections. Let's try our best to make some space by padding the ++ # SizeOfHeaders to a multiple of the file alignment. This is safe because the first section's data starts ++ # at a multiple of the file alignment, so all space before that is unused. ++ pe.OPTIONAL_HEADER.SizeOfHeaders = round_up(pe.OPTIONAL_HEADER.SizeOfHeaders, pe.OPTIONAL_HEADER.FileAlignment) ++ pe = pefile.PE(data=pe.write(), fast_load=True) ++ ++ warnings = pe.get_warnings() ++ if warnings: ++ raise PEError(f'pefile warnings treated as errors: {warnings}') ++ ++ security = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']] ++ if security.VirtualAddress != 0: ++ # We could strip the signatures, but why would anyone sign the stub? ++ raise PEError('Stub image is signed, refusing.') ++ ++ for section in uki.sections: ++ new_section = pefile.SectionStructure(pe.__IMAGE_SECTION_HEADER_format__, pe=pe) ++ new_section.__unpack__(b'\0' * new_section.sizeof()) ++ ++ offset = pe.sections[-1].get_file_offset() + new_section.sizeof() ++ if offset + new_section.sizeof() > pe.OPTIONAL_HEADER.SizeOfHeaders: ++ raise PEError(f'Not enough header space to add section {section.name}.') ++ ++ assert section.content ++ data = section.content.read_bytes() ++ ++ new_section.set_file_offset(offset) ++ new_section.Name = section.name.encode() ++ new_section.Misc_VirtualSize = len(data) ++ # Non-stripped stubs might still have an unaligned symbol table at the end, making their size ++ # unaligned, so we make sure to explicitly pad the pointer to new sections to an aligned offset. ++ new_section.PointerToRawData = round_up(len(pe.__data__), pe.OPTIONAL_HEADER.FileAlignment) ++ new_section.SizeOfRawData = round_up(len(data), pe.OPTIONAL_HEADER.FileAlignment) ++ new_section.VirtualAddress = round_up( ++ pe.sections[-1].VirtualAddress + pe.sections[-1].Misc_VirtualSize, ++ pe.OPTIONAL_HEADER.SectionAlignment, ++ ) ++ ++ new_section.IMAGE_SCN_MEM_READ = True ++ if section.name == '.linux': ++ # Old kernels that use EFI handover protocol will be executed inline. ++ new_section.IMAGE_SCN_CNT_CODE = True ++ else: ++ new_section.IMAGE_SCN_CNT_INITIALIZED_DATA = True ++ ++ # Special case, mostly for .sbat: the stub will already have a .sbat section, but we want to append ++ # the one from the kernel to it. It should be small enough to fit in the existing section, so just ++ # swap the data. ++ for i, s in enumerate(pe.sections): ++ if s.Name.rstrip(b"\x00").decode() == section.name: ++ if new_section.Misc_VirtualSize > s.SizeOfRawData: ++ raise PEError(f'Not enough space in existing section {section.name} to append new data.') ++ ++ padding = bytes(new_section.SizeOfRawData - new_section.Misc_VirtualSize) ++ pe.__data__ = pe.__data__[:s.PointerToRawData] + data + padding + pe.__data__[pe.sections[i+1].PointerToRawData:] ++ s.SizeOfRawData = new_section.SizeOfRawData ++ s.Misc_VirtualSize = new_section.Misc_VirtualSize ++ break ++ else: ++ pe.__data__ = pe.__data__[:] + bytes(new_section.PointerToRawData - len(pe.__data__)) + data + bytes(new_section.SizeOfRawData - len(data)) ++ ++ pe.FILE_HEADER.NumberOfSections += 1 ++ pe.OPTIONAL_HEADER.SizeOfInitializedData += new_section.Misc_VirtualSize ++ pe.__structures__.append(new_section) ++ pe.sections.append(new_section) ++ ++ pe.OPTIONAL_HEADER.CheckSum = 0 ++ pe.OPTIONAL_HEADER.SizeOfImage = round_up( ++ pe.sections[-1].VirtualAddress + pe.sections[-1].Misc_VirtualSize, ++ pe.OPTIONAL_HEADER.SectionAlignment, ++ ) ++ ++ pe.write(output) ++ ++def merge_sbat(input_pe: [pathlib.Path], input_text: [str]) -> str: ++ sbat = [] ++ ++ for f in input_pe: ++ try: ++ pe = pefile.PE(f, fast_load=True) ++ except pefile.PEFormatError: ++ print(f"{f} is not a valid PE file, not extracting SBAT section.") ++ continue ++ ++ for section in pe.sections: ++ if section.Name.rstrip(b"\x00").decode() == ".sbat": ++ split = section.get_data().rstrip(b"\x00").decode().splitlines() ++ if not split[0].startswith('sbat,'): ++ print(f"{f} does not contain a valid SBAT section, skipping.") ++ continue ++ # Filter out the sbat line, we'll add it back later, there needs to be only one and it ++ # needs to be first. ++ sbat += split[1:] ++ ++ for t in input_text: ++ if t.startswith('@'): ++ t = pathlib.Path(t[1:]).read_text() ++ split = t.splitlines() ++ if not split[0].startswith('sbat,'): ++ print(f"{t} does not contain a valid SBAT section, skipping.") ++ continue ++ sbat += split[1:] ++ ++ return 'sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md\n' + '\n'.join(sbat) + "\n\x00" ++ ++def signer_sign(cmd): ++ print('+', shell_join(cmd)) ++ subprocess.check_call(cmd) ++ ++def find_sbsign(opts=None): ++ return find_tool('sbsign', opts=opts) ++ ++def sbsign_sign(sbsign_tool, input_f, output_f, opts=None): ++ sign_invocation = [ ++ sbsign_tool, ++ '--key', opts.sb_key, ++ '--cert', opts.sb_cert, ++ input_f, ++ '--output', output_f, ++ ] ++ if opts.signing_engine is not None: ++ sign_invocation += ['--engine', opts.signing_engine] ++ signer_sign(sign_invocation) ++ ++def find_pesign(opts=None): ++ return find_tool('pesign', opts=opts) ++ ++def pesign_sign(pesign_tool, input_f, output_f, opts=None): ++ sign_invocation = [ ++ pesign_tool, '-s', '--force', ++ '-n', opts.sb_certdir, ++ '-c', opts.sb_cert_name, ++ '-i', input_f, ++ '-o', output_f, ++ ] ++ signer_sign(sign_invocation) ++ ++SBVERIFY = { ++ 'name': 'sbverify', ++ 'option': '--list', ++ 'output': 'No signature table present', ++} ++ ++PESIGCHECK = { ++ 'name': 'pesign', ++ 'option': '-i', ++ 'output': 'No signatures found.', ++ 'flags': '-S' ++} ++ ++def verify(tool, opts): ++ verify_tool = find_tool(tool['name'], opts=opts) ++ cmd = [ ++ verify_tool, ++ tool['option'], ++ opts.linux, ++ ] ++ if 'flags' in tool: ++ cmd.append(tool['flags']) ++ ++ print('+', shell_join(cmd)) ++ info = subprocess.check_output(cmd, text=True) ++ ++ return tool['output'] in info ++ ++def make_uki(opts): ++ # kernel payload signing ++ ++ sign_tool = None ++ sign_args_present = opts.sb_key or opts.sb_cert_name ++ sign_kernel = opts.sign_kernel ++ sign = None ++ linux = opts.linux ++ ++ if sign_args_present: ++ if opts.signtool == 'sbsign': ++ sign_tool = find_sbsign(opts=opts) ++ sign = sbsign_sign ++ verify_tool = SBVERIFY ++ else: ++ sign_tool = find_pesign(opts=opts) ++ sign = pesign_sign ++ verify_tool = PESIGCHECK ++ ++ if sign_tool is None: ++ raise ValueError(f'{opts.signtool}, required for signing, is not installed') ++ ++ if sign_kernel is None and opts.linux is not None: ++ # figure out if we should sign the kernel ++ sign_kernel = verify(verify_tool, opts) ++ ++ if sign_kernel: ++ linux_signed = tempfile.NamedTemporaryFile(prefix='linux-signed') ++ linux = pathlib.Path(linux_signed.name) ++ sign(sign_tool, opts.linux, linux, opts=opts) ++ ++ if opts.uname is None and opts.linux is not None: ++ print('Kernel version not specified, starting autodetection 😖.') ++ opts.uname = Uname.scrape(opts.linux, opts=opts) ++ ++ uki = UKI(opts.stub) ++ initrd = join_initrds(opts.initrd) ++ ++ pcrpkey = opts.pcrpkey ++ if pcrpkey is None: ++ if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1: ++ pcrpkey = opts.pcr_public_keys[0] ++ elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1: ++ import cryptography.hazmat.primitives.serialization as serialization ++ privkey = serialization.load_pem_private_key(opts.pcr_private_keys[0].read_bytes(), password=None) ++ pcrpkey = privkey.public_key().public_bytes( ++ encoding=serialization.Encoding.PEM, ++ format=serialization.PublicFormat.SubjectPublicKeyInfo, ++ ) ++ ++ sections = [ ++ # name, content, measure? ++ ('.osrel', opts.os_release, True ), ++ ('.cmdline', opts.cmdline, True ), ++ ('.dtb', opts.devicetree, True ), ++ ('.uname', opts.uname, True ), ++ ('.splash', opts.splash, True ), ++ ('.pcrpkey', pcrpkey, True ), ++ ('.initrd', initrd, True ), ++ ++ # linux shall be last to leave breathing room for decompression. ++ # We'll add it later. ++ ] ++ ++ for name, content, measure in sections: ++ if content: ++ uki.add_section(Section.create(name, content, measure=measure)) ++ ++ # systemd-measure doesn't know about those extra sections ++ for section in opts.sections: ++ uki.add_section(section) ++ ++ if linux is not None: ++ # Merge the .sbat sections from stub, kernel and parameter, so that revocation can be done on either. ++ uki.add_section(Section.create('.sbat', merge_sbat([opts.stub, linux], opts.sbat), measure=True)) ++ else: ++ # Addons don't use the stub so we add SBAT manually ++ if not opts.sbat: ++ opts.sbat = ["""sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md ++uki,1,UKI,uki,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html ++"""] ++ uki.add_section(Section.create('.sbat', merge_sbat([], opts.sbat), measure=False)) ++ ++ # PCR measurement and signing ++ ++ # We pass in the contents for .linux separately because we need them to do the measurement but can't add ++ # the section yet because we want .linux to be the last section. Make sure any other sections are added ++ # before this function is called. ++ call_systemd_measure(uki, linux, opts=opts) ++ ++ # UKI creation ++ ++ if linux is not None: ++ uki.add_section(Section.create('.linux', linux, measure=True)) ++ ++ if sign_args_present: ++ unsigned = tempfile.NamedTemporaryFile(prefix='uki') ++ unsigned_output = unsigned.name ++ else: ++ unsigned_output = opts.output ++ ++ pe_add_sections(uki, unsigned_output) ++ ++ # UKI signing ++ ++ if sign_args_present: ++ assert sign ++ sign(sign_tool, unsigned_output, opts.output, opts=opts) ++ ++ # We end up with no executable bits, let's reapply them ++ os.umask(umask := os.umask(0)) ++ os.chmod(opts.output, 0o777 & ~umask) ++ ++ print(f"Wrote {'signed' if sign_args_present else 'unsigned'} {opts.output}") ++ ++ ++ONE_DAY = datetime.timedelta(1, 0, 0) ++ ++ ++@contextlib.contextmanager ++def temporary_umask(mask: int): ++ # Drop bits from umask ++ old = os.umask(0) ++ os.umask(old | mask) ++ try: ++ yield ++ finally: ++ os.umask(old) ++ ++ ++def generate_key_cert_pair( ++ common_name: str, ++ valid_days: int, ++ keylength: int = 2048, ++) -> tuple[bytes]: ++ ++ from cryptography import x509 ++ from cryptography.hazmat.primitives import serialization, hashes ++ from cryptography.hazmat.primitives.asymmetric import rsa ++ ++ # We use a keylength of 2048 bits. That is what Microsoft documents as ++ # supported/expected: ++ # https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-secure-boot-key-creation-and-management-guidance?view=windows-11#12-public-key-cryptography ++ ++ now = datetime.datetime.utcnow() ++ ++ key = rsa.generate_private_key( ++ public_exponent=65537, ++ key_size=keylength, ++ ) ++ cert = x509.CertificateBuilder( ++ ).subject_name( ++ x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, common_name)]) ++ ).issuer_name( ++ x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, common_name)]) ++ ).not_valid_before( ++ now, ++ ).not_valid_after( ++ now + ONE_DAY * valid_days ++ ).serial_number( ++ x509.random_serial_number() ++ ).public_key( ++ key.public_key() ++ ).add_extension( ++ x509.BasicConstraints(ca=False, path_length=None), ++ critical=True, ++ ).sign( ++ private_key=key, ++ algorithm=hashes.SHA256(), ++ ) ++ ++ cert_pem = cert.public_bytes( ++ encoding=serialization.Encoding.PEM, ++ ) ++ key_pem = key.private_bytes( ++ encoding=serialization.Encoding.PEM, ++ format=serialization.PrivateFormat.TraditionalOpenSSL, ++ encryption_algorithm=serialization.NoEncryption(), ++ ) ++ ++ return key_pem, cert_pem ++ ++ ++def generate_priv_pub_key_pair(keylength : int = 2048) -> tuple[bytes]: ++ from cryptography.hazmat.primitives import serialization ++ from cryptography.hazmat.primitives.asymmetric import rsa ++ ++ key = rsa.generate_private_key( ++ public_exponent=65537, ++ key_size=keylength, ++ ) ++ priv_key_pem = key.private_bytes( ++ encoding=serialization.Encoding.PEM, ++ format=serialization.PrivateFormat.TraditionalOpenSSL, ++ encryption_algorithm=serialization.NoEncryption(), ++ ) ++ pub_key_pem = key.public_key().public_bytes( ++ encoding=serialization.Encoding.PEM, ++ format=serialization.PublicFormat.SubjectPublicKeyInfo, ++ ) ++ ++ return priv_key_pem, pub_key_pem ++ ++ ++def generate_keys(opts): ++ # This will generate keys and certificates and write them to the paths that ++ # are specified as input paths. ++ if opts.sb_key or opts.sb_cert: ++ fqdn = socket.getfqdn() ++ cn = f'SecureBoot signing key on host {fqdn}' ++ key_pem, cert_pem = generate_key_cert_pair( ++ common_name=cn, ++ valid_days=opts.sb_cert_validity, ++ ) ++ print(f'Writing SecureBoot private key to {opts.sb_key}') ++ with temporary_umask(0o077): ++ opts.sb_key.write_bytes(key_pem) ++ print(f'Writing SecureBoot certificate to {opts.sb_cert}') ++ opts.sb_cert.write_bytes(cert_pem) ++ ++ for priv_key, pub_key, _ in key_path_groups(opts): ++ priv_key_pem, pub_key_pem = generate_priv_pub_key_pair() ++ ++ print(f'Writing private key for PCR signing to {priv_key}') ++ with temporary_umask(0o077): ++ priv_key.write_bytes(priv_key_pem) ++ if pub_key: ++ print(f'Writing public key for PCR signing to {pub_key}') ++ pub_key.write_bytes(pub_key_pem) ++ ++ ++def inspect_section(opts, section): ++ name = section.Name.rstrip(b"\x00").decode() ++ ++ # find the config for this section in opts and whether to show it ++ config = opts.sections_by_name.get(name, None) ++ show = (config or ++ opts.all or ++ (name in DEFAULT_SECTIONS_TO_SHOW and not opts.sections)) ++ if not show: ++ return name, None ++ ++ ttype = config.output_mode if config else DEFAULT_SECTIONS_TO_SHOW.get(name, 'binary') ++ ++ size = section.Misc_VirtualSize ++ # TODO: Use ignore_padding once we can depend on a newer version of pefile ++ data = section.get_data(length=size) ++ digest = sha256(data).hexdigest() ++ ++ struct = { ++ 'size' : size, ++ 'sha256' : digest, ++ } ++ ++ if ttype == 'text': ++ try: ++ struct['text'] = data.decode() ++ except UnicodeDecodeError as e: ++ print(f"Section {name!r} is not valid text: {e}") ++ struct['text'] = '(not valid UTF-8)' ++ ++ if config and config.content: ++ assert isinstance(config.content, pathlib.Path) ++ config.content.write_bytes(data) ++ ++ if opts.json == 'off': ++ print(f"{name}:\n size: {size} bytes\n sha256: {digest}") ++ if ttype == 'text': ++ text = textwrap.indent(struct['text'].rstrip(), ' ' * 4) ++ print(f" text:\n{text}") ++ ++ return name, struct ++ ++ ++def inspect_sections(opts): ++ indent = 4 if opts.json == 'pretty' else None ++ ++ for file in opts.files: ++ pe = pefile.PE(file, fast_load=True) ++ gen = (inspect_section(opts, section) for section in pe.sections) ++ descs = {key:val for (key, val) in gen if val} ++ if opts.json != 'off': ++ json.dump(descs, sys.stdout, indent=indent) ++ ++ ++@dataclasses.dataclass(frozen=True) ++class ConfigItem: ++ @staticmethod ++ def config_list_prepend( ++ namespace: argparse.Namespace, ++ group: Optional[str], ++ dest: str, ++ value: Any, ++ ) -> None: ++ "Prepend value to namespace." ++ ++ assert not group ++ ++ old = getattr(namespace, dest, []) ++ if old is None: ++ old = [] ++ setattr(namespace, dest, value + old) ++ ++ @staticmethod ++ def config_set_if_unset( ++ namespace: argparse.Namespace, ++ group: Optional[str], ++ dest: str, ++ value: Any, ++ ) -> None: ++ "Set namespace. to value only if it was None" ++ ++ assert not group ++ ++ if getattr(namespace, dest) is None: ++ setattr(namespace, dest, value) ++ ++ @staticmethod ++ def config_set( ++ namespace: argparse.Namespace, ++ group: Optional[str], ++ dest: str, ++ value: Any, ++ ) -> None: ++ "Set namespace. to value only if it was None" ++ ++ assert not group ++ ++ setattr(namespace, dest, value) ++ ++ @staticmethod ++ def config_set_group( ++ namespace: argparse.Namespace, ++ group: Optional[str], ++ dest: str, ++ value: Any, ++ ) -> None: ++ "Set namespace.[idx] to value, with idx derived from group" ++ ++ # pylint: disable=protected-access ++ if group not in namespace._groups: ++ namespace._groups += [group] ++ idx = namespace._groups.index(group) ++ ++ old = getattr(namespace, dest, None) ++ if old is None: ++ old = [] ++ setattr(namespace, dest, ++ old + ([None] * (idx - len(old))) + [value]) ++ ++ @staticmethod ++ def parse_boolean(s: str) -> bool: ++ "Parse 1/true/yes/y/t/on as true and 0/false/no/n/f/off/None as false" ++ s_l = s.lower() ++ if s_l in {'1', 'true', 'yes', 'y', 't', 'on'}: ++ return True ++ if s_l in {'0', 'false', 'no', 'n', 'f', 'off'}: ++ return False ++ raise ValueError('f"Invalid boolean literal: {s!r}') ++ ++ # arguments for argparse.ArgumentParser.add_argument() ++ name: Union[str, tuple[str, str]] ++ dest: Optional[str] = None ++ metavar: Optional[str] = None ++ type: Optional[Callable] = None ++ nargs: Optional[str] = None ++ action: Optional[Union[str, Callable]] = None ++ default: Any = None ++ version: Optional[str] = None ++ choices: Optional[tuple[str, ...]] = None ++ const: Optional[Any] = None ++ help: Optional[str] = None ++ ++ # metadata for config file parsing ++ config_key: Optional[str] = None ++ config_push: Callable[[argparse.Namespace, Optional[str], str, Any], None] = \ ++ config_set_if_unset ++ ++ def _names(self) -> tuple[str, ...]: ++ return self.name if isinstance(self.name, tuple) else (self.name,) ++ ++ def argparse_dest(self) -> str: ++ # It'd be nice if argparse exported this, but I don't see that in the API ++ if self.dest: ++ return self.dest ++ return self._names()[0].lstrip('-').replace('-', '_') ++ ++ def add_to(self, parser: argparse.ArgumentParser): ++ kwargs = { key:val ++ for key in dataclasses.asdict(self) ++ if (key not in ('name', 'config_key', 'config_push') and ++ (val := getattr(self, key)) is not None) } ++ args = self._names() ++ parser.add_argument(*args, **kwargs) ++ ++ def apply_config(self, namespace, section, group, key, value) -> None: ++ assert f'{section}/{key}' == self.config_key ++ dest = self.argparse_dest() ++ ++ conv: Callable[[str], Any] ++ if self.action == argparse.BooleanOptionalAction: ++ # We need to handle this case separately: the options are called ++ # --foo and --no-foo, and no argument is parsed. But in the config ++ # file, we have Foo=yes or Foo=no. ++ conv = self.parse_boolean ++ elif self.type: ++ conv = self.type ++ else: ++ conv = lambda s:s ++ ++ # This is a bit ugly, but --initrd is the only option which is specified ++ # with multiple args on the command line and a space-separated list in the ++ # config file. ++ if self.name == '--initrd': ++ value = [conv(v) for v in value.split()] ++ else: ++ value = conv(value) ++ ++ self.config_push(namespace, group, dest, value) ++ ++ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: ++ if not self.config_key: ++ return None, None, None ++ section_name, key = self.config_key.split('/', 1) ++ if section_name.endswith(':'): ++ section_name += 'NAME' ++ if self.choices: ++ value = '|'.join(self.choices) ++ else: ++ value = self.metavar or self.argparse_dest().upper() ++ return (section_name, key, value) ++ ++ ++VERBS = ('build', 'genkey', 'inspect') ++ ++CONFIG_ITEMS = [ ++ ConfigItem( ++ 'positional', ++ metavar = 'VERB', ++ nargs = '*', ++ help = argparse.SUPPRESS, ++ ), ++ ++ ConfigItem( ++ '--version', ++ action = 'version', ++ version = f'ukify {__version__}', ++ ), ++ ++ ConfigItem( ++ '--summary', ++ help = 'print parsed config and exit', ++ action = 'store_true', ++ ), ++ ++ ConfigItem( ++ '--linux', ++ type = pathlib.Path, ++ help = 'vmlinuz file [.linux section]', ++ config_key = 'UKI/Linux', ++ ), ++ ++ ConfigItem( ++ '--initrd', ++ metavar = 'INITRD', ++ type = pathlib.Path, ++ action = 'append', ++ help = 'initrd file [part of .initrd section]', ++ config_key = 'UKI/Initrd', ++ config_push = ConfigItem.config_list_prepend, ++ ), ++ ++ ConfigItem( ++ ('--config', '-c'), ++ metavar = 'PATH', ++ type = pathlib.Path, ++ help = 'configuration file', ++ ), ++ ++ ConfigItem( ++ '--cmdline', ++ metavar = 'TEXT|@PATH', ++ help = 'kernel command line [.cmdline section]', ++ config_key = 'UKI/Cmdline', ++ ), ++ ++ ConfigItem( ++ '--os-release', ++ metavar = 'TEXT|@PATH', ++ help = 'path to os-release file [.osrel section]', ++ config_key = 'UKI/OSRelease', ++ ), ++ ++ ConfigItem( ++ '--devicetree', ++ metavar = 'PATH', ++ type = pathlib.Path, ++ help = 'Device Tree file [.dtb section]', ++ config_key = 'UKI/DeviceTree', ++ ), ++ ConfigItem( ++ '--splash', ++ metavar = 'BMP', ++ type = pathlib.Path, ++ help = 'splash image bitmap file [.splash section]', ++ config_key = 'UKI/Splash', ++ ), ++ ConfigItem( ++ '--pcrpkey', ++ metavar = 'KEY', ++ type = pathlib.Path, ++ help = 'embedded public key to seal secrets to [.pcrpkey section]', ++ config_key = 'UKI/PCRPKey', ++ ), ++ ConfigItem( ++ '--uname', ++ metavar='VERSION', ++ help='"uname -r" information [.uname section]', ++ config_key = 'UKI/Uname', ++ ), ++ ++ ConfigItem( ++ '--efi-arch', ++ metavar = 'ARCH', ++ choices = ('ia32', 'x64', 'arm', 'aa64', 'riscv64'), ++ help = 'target EFI architecture', ++ config_key = 'UKI/EFIArch', ++ ), ++ ++ ConfigItem( ++ '--stub', ++ type = pathlib.Path, ++ help = 'path to the sd-stub file [.text,.data,… sections]', ++ config_key = 'UKI/Stub', ++ ), ++ ++ ConfigItem( ++ '--sbat', ++ metavar = 'TEXT|@PATH', ++ help = 'SBAT policy [.sbat section]', ++ default = [], ++ action = 'append', ++ config_key = 'UKI/SBAT', ++ ), ++ ++ ConfigItem( ++ '--section', ++ dest = 'sections', ++ metavar = 'NAME:TEXT|@PATH', ++ action = 'append', ++ default = [], ++ help = 'section as name and contents [NAME section] or section to print', ++ ), ++ ++ ConfigItem( ++ '--pcr-banks', ++ metavar = 'BANK…', ++ type = parse_banks, ++ config_key = 'UKI/PCRBanks', ++ ), ++ ++ ConfigItem( ++ '--signing-engine', ++ metavar = 'ENGINE', ++ help = 'OpenSSL engine to use for signing', ++ config_key = 'UKI/SigningEngine', ++ ), ++ ConfigItem( ++ '--signtool', ++ choices = ('sbsign', 'pesign'), ++ dest = 'signtool', ++ help = 'whether to use sbsign or pesign. It will also be inferred by the other \ ++ parameters given: when using --secureboot-{private-key/certificate}, sbsign \ ++ will be used, otherwise pesign will be used', ++ config_key = 'UKI/SecureBootSigningTool', ++ ), ++ ConfigItem( ++ '--secureboot-private-key', ++ dest = 'sb_key', ++ help = 'required by --signtool=sbsign. Path to key file or engine-specific designation for SB signing', ++ config_key = 'UKI/SecureBootPrivateKey', ++ ), ++ ConfigItem( ++ '--secureboot-certificate', ++ dest = 'sb_cert', ++ help = 'required by --signtool=sbsign. sbsign needs a path to certificate file or engine-specific designation for SB signing', ++ config_key = 'UKI/SecureBootCertificate', ++ ), ++ ConfigItem( ++ '--secureboot-certificate-dir', ++ dest = 'sb_certdir', ++ default = '/etc/pki/pesign', ++ help = 'required by --signtool=pesign. Path to nss certificate database directory for PE signing. Default is /etc/pki/pesign', ++ config_key = 'UKI/SecureBootCertificateDir', ++ config_push = ConfigItem.config_set ++ ), ++ ConfigItem( ++ '--secureboot-certificate-name', ++ dest = 'sb_cert_name', ++ help = 'required by --signtool=pesign. pesign needs a certificate nickname of nss certificate database entry to use for PE signing', ++ config_key = 'UKI/SecureBootCertificateName', ++ ), ++ ConfigItem( ++ '--secureboot-certificate-validity', ++ metavar = 'DAYS', ++ dest = 'sb_cert_validity', ++ default = 365 * 10, ++ help = "period of validity (in days) for a certificate created by 'genkey'", ++ config_key = 'UKI/SecureBootCertificateValidity', ++ config_push = ConfigItem.config_set ++ ), ++ ++ ConfigItem( ++ '--sign-kernel', ++ action = argparse.BooleanOptionalAction, ++ help = 'Sign the embedded kernel', ++ config_key = 'UKI/SignKernel', ++ ), ++ ++ ConfigItem( ++ '--pcr-private-key', ++ dest = 'pcr_private_keys', ++ metavar = 'PATH', ++ type = pathlib.Path, ++ action = 'append', ++ help = 'private part of the keypair for signing PCR signatures', ++ config_key = 'PCRSignature:/PCRPrivateKey', ++ config_push = ConfigItem.config_set_group, ++ ), ++ ConfigItem( ++ '--pcr-public-key', ++ dest = 'pcr_public_keys', ++ metavar = 'PATH', ++ type = pathlib.Path, ++ action = 'append', ++ help = 'public part of the keypair for signing PCR signatures', ++ config_key = 'PCRSignature:/PCRPublicKey', ++ config_push = ConfigItem.config_set_group, ++ ), ++ ConfigItem( ++ '--phases', ++ dest = 'phase_path_groups', ++ metavar = 'PHASE-PATH…', ++ type = parse_phase_paths, ++ action = 'append', ++ help = 'phase-paths to create signatures for', ++ config_key = 'PCRSignature:/Phases', ++ config_push = ConfigItem.config_set_group, ++ ), ++ ++ ConfigItem( ++ '--tools', ++ type = pathlib.Path, ++ action = 'append', ++ help = 'Directories to search for tools (systemd-measure, …)', ++ ), ++ ++ ConfigItem( ++ ('--output', '-o'), ++ type = pathlib.Path, ++ help = 'output file path', ++ ), ++ ++ ConfigItem( ++ '--measure', ++ action = argparse.BooleanOptionalAction, ++ help = 'print systemd-measure output for the UKI', ++ ), ++ ++ ConfigItem( ++ '--json', ++ choices = ('pretty', 'short', 'off'), ++ default = 'off', ++ help = 'generate JSON output', ++ ), ++ ConfigItem( ++ '-j', ++ dest='json', ++ action='store_const', ++ const='pretty', ++ help='equivalent to --json=pretty', ++ ), ++ ++ ConfigItem( ++ '--all', ++ help = 'print all sections', ++ action = 'store_true', ++ ), ++] ++ ++CONFIGFILE_ITEMS = { item.config_key:item ++ for item in CONFIG_ITEMS ++ if item.config_key } ++ ++ ++def apply_config(namespace, filename=None): ++ if filename is None: ++ if namespace.config: ++ # Config set by the user, use that. ++ filename = namespace.config ++ print(f'Using config file: {filename}') ++ else: ++ # Try to look for a config file then use the first one found. ++ for config_dir in DEFAULT_CONFIG_DIRS: ++ filename = pathlib.Path(config_dir) / DEFAULT_CONFIG_FILE ++ if filename.is_file(): ++ # Found a config file, use it. ++ print(f'Using found config file: {filename}') ++ break ++ else: ++ # No config file specified or found, nothing to do. ++ return ++ ++ # Fill in ._groups based on --pcr-public-key=, --pcr-private-key=, and --phases=. ++ assert '_groups' not in namespace ++ n_pcr_priv = len(namespace.pcr_private_keys or ()) ++ namespace._groups = list(range(n_pcr_priv)) # pylint: disable=protected-access ++ ++ cp = configparser.ConfigParser( ++ comment_prefixes='#', ++ inline_comment_prefixes='#', ++ delimiters='=', ++ empty_lines_in_values=False, ++ interpolation=None, ++ strict=False) ++ # Do not make keys lowercase ++ cp.optionxform = lambda option: option ++ ++ # The API is not great. ++ read = cp.read(filename) ++ if not read: ++ raise IOError(f'Failed to read {filename}') ++ ++ for section_name, section in cp.items(): ++ idx = section_name.find(':') ++ if idx >= 0: ++ section_name, group = section_name[:idx+1], section_name[idx+1:] ++ if not section_name or not group: ++ raise ValueError('Section name components cannot be empty') ++ if ':' in group: ++ raise ValueError('Section name cannot contain more than one ":"') ++ else: ++ group = None ++ for key, value in section.items(): ++ if item := CONFIGFILE_ITEMS.get(f'{section_name}/{key}'): ++ item.apply_config(namespace, section_name, group, key, value) ++ else: ++ print(f'Unknown config setting [{section_name}] {key}=') ++ ++ ++def config_example(): ++ prev_section = None ++ for item in CONFIG_ITEMS: ++ section, key, value = item.config_example() ++ if section: ++ if prev_section != section: ++ if prev_section: ++ yield '' ++ yield f'[{section}]' ++ prev_section = section ++ yield f'{key} = {value}' ++ ++ ++class PagerHelpAction(argparse._HelpAction): # pylint: disable=protected-access ++ def __call__( ++ self, ++ parser: argparse.ArgumentParser, ++ namespace: argparse.Namespace, ++ values: Union[str, Sequence[Any], None] = None, ++ option_string: Optional[str] = None ++ ) -> None: ++ page(parser.format_help(), True) ++ parser.exit() ++ ++ ++def create_parser(): ++ p = argparse.ArgumentParser( ++ description='Build and sign Unified Kernel Images', ++ usage='\n ' + textwrap.dedent('''\ ++ ukify {b}build{e} [--linux=LINUX] [--initrd=INITRD] [options…] ++ ukify {b}genkey{e} [options…] ++ ukify {b}inspect{e} FILE… [options…] ++ ''').format(b=Style.bold, e=Style.reset), ++ allow_abbrev=False, ++ add_help=False, ++ epilog='\n '.join(('config file:', *config_example())), ++ formatter_class=argparse.RawDescriptionHelpFormatter, ++ ) ++ ++ for item in CONFIG_ITEMS: ++ item.add_to(p) ++ ++ # Suppress printing of usage synopsis on errors ++ p.error = lambda message: p.exit(2, f'{p.prog}: error: {message}\n') ++ ++ # Make --help paged ++ p.add_argument( ++ '-h', '--help', ++ action=PagerHelpAction, ++ help='show this help message and exit', ++ ) ++ ++ return p ++ ++ ++def finalize_options(opts): ++ # Figure out which syntax is being used, one of: ++ # ukify verb --arg --arg --arg ++ # ukify linux initrd… ++ if len(opts.positional) >= 1 and opts.positional[0] == 'inspect': ++ opts.verb = opts.positional[0] ++ opts.files = opts.positional[1:] ++ if not opts.files: ++ raise ValueError('file(s) to inspect must be specified') ++ if len(opts.files) > 1 and opts.json != 'off': ++ # We could allow this in the future, but we need to figure out the right structure ++ raise ValueError('JSON output is not allowed with multiple files') ++ elif len(opts.positional) == 1 and opts.positional[0] in VERBS: ++ opts.verb = opts.positional[0] ++ elif opts.linux or opts.initrd: ++ raise ValueError('--linux/--initrd options cannot be used with positional arguments') ++ else: ++ print("Assuming obsolete command line syntax with no verb. Please use 'build'.") ++ if opts.positional: ++ opts.linux = pathlib.Path(opts.positional[0]) ++ # If we have initrds from parsing config files, append our positional args at the end ++ opts.initrd = (opts.initrd or []) + [pathlib.Path(arg) for arg in opts.positional[1:]] ++ opts.verb = 'build' ++ ++ # Check that --pcr-public-key=, --pcr-private-key=, and --phases= ++ # have either the same number of arguments are are not specified at all. ++ n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys) ++ n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys) ++ n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups) ++ if n_pcr_pub is not None and n_pcr_pub != n_pcr_priv: ++ raise ValueError('--pcr-public-key= specifications must match --pcr-private-key=') ++ if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv: ++ raise ValueError('--phases= specifications must match --pcr-private-key=') ++ ++ if opts.cmdline and opts.cmdline.startswith('@'): ++ opts.cmdline = pathlib.Path(opts.cmdline[1:]) ++ elif opts.cmdline: ++ # Drop whitespace from the command line. If we're reading from a file, ++ # we copy the contents verbatim. But configuration specified on the command line ++ # or in the config file may contain additional whitespace that has no meaning. ++ opts.cmdline = ' '.join(opts.cmdline.split()) ++ ++ if opts.os_release and opts.os_release.startswith('@'): ++ opts.os_release = pathlib.Path(opts.os_release[1:]) ++ elif not opts.os_release and opts.linux: ++ p = pathlib.Path('/etc/os-release') ++ if not p.exists(): ++ p = pathlib.Path('/usr/lib/os-release') ++ opts.os_release = p ++ ++ if opts.efi_arch is None: ++ opts.efi_arch = guess_efi_arch() ++ ++ if opts.stub is None: ++ if opts.linux is not None: ++ opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub') ++ else: ++ opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/addon{opts.efi_arch}.efi.stub') ++ ++ if opts.signing_engine is None: ++ if opts.sb_key: ++ opts.sb_key = pathlib.Path(opts.sb_key) ++ if opts.sb_cert: ++ opts.sb_cert = pathlib.Path(opts.sb_cert) ++ ++ if bool(opts.sb_key) ^ bool(opts.sb_cert): ++ # one param only given, sbsign needs both ++ raise ValueError('--secureboot-private-key= and --secureboot-certificate= must be specified together') ++ elif bool(opts.sb_key) and bool(opts.sb_cert): ++ # both param given, infer sbsign and in case it was given, ensure signtool=sbsign ++ if opts.signtool and opts.signtool != 'sbsign': ++ raise ValueError(f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=') ++ opts.signtool = 'sbsign' ++ elif bool(opts.sb_cert_name): ++ # sb_cert_name given, infer pesign and in case it was given, ensure signtool=pesign ++ if opts.signtool and opts.signtool != 'pesign': ++ raise ValueError(f'Cannot provide --signtool={opts.signtool} with --secureboot-certificate-name=') ++ opts.signtool = 'pesign' ++ ++ if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name: ++ raise ValueError('--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified') ++ ++ if opts.verb == 'build' and opts.output is None: ++ if opts.linux is None: ++ raise ValueError('--output= must be specified when building a PE addon') ++ suffix = '.efi' if opts.sb_key or opts.sb_cert_name else '.unsigned.efi' ++ opts.output = opts.linux.name + suffix ++ ++ # Now that we know if we're inputting or outputting, really parse section config ++ f = Section.parse_output if opts.verb == 'inspect' else Section.parse_input ++ opts.sections = [f(s) for s in opts.sections] ++ # A convenience dictionary to make it easy to look up sections ++ opts.sections_by_name = {s.name:s for s in opts.sections} ++ ++ if opts.summary: ++ # TODO: replace pprint() with some fancy formatting. ++ pprint.pprint(vars(opts)) ++ sys.exit() ++ ++ ++def parse_args(args=None): ++ opts = create_parser().parse_args(args) ++ apply_config(opts) ++ finalize_options(opts) ++ return opts ++ ++ ++def main(): ++ opts = parse_args() ++ if opts.verb == 'build': ++ check_inputs(opts) ++ make_uki(opts) ++ elif opts.verb == 'genkey': ++ check_cert_and_keys_nonexistent(opts) ++ generate_keys(opts) ++ elif opts.verb == 'inspect': ++ inspect_sections(opts) ++ else: ++ assert False ++ ++ ++if __name__ == '__main__': ++ main() diff --git a/0408-bootctl-make-json-output-normal-json.patch b/0408-bootctl-make-json-output-normal-json.patch new file mode 100644 index 0000000..ab9404e --- /dev/null +++ b/0408-bootctl-make-json-output-normal-json.patch @@ -0,0 +1,54 @@ +From 27e95f2513e24a6abc26c56f05c67c34492442d7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 15 Nov 2022 15:00:57 +0100 +Subject: [PATCH] bootctl: make --json output normal json + +We would output a sequence of concatenated JSON strings. 'jq' accepts such +output without fuss, and can even automatically build an array with --slurp/-s. +Nevertheless, parsing this format is more effort for the reader, since it's not +"standard JSON". E.g. Python's json module cannot do this out-of-the-box, but +needs some loop with json.JSONDecoder.raw_decode() and then collecting the +objects into an array. Such streaming output make sense in case of logs, where +we stream the output and it has no predefined length. But here we expect at +most a few dozen entries, so it's nicer to write normal JSON that is trivial to +parse. + +I'm treating this is a bugfix and not attempting to provide compatibility +backwards. I don't think the previous format was seeing much use, and it's +trivial to adapt to the new one. + +(cherry picked from commit b570204a97bccfbfce8fc4ffa65306f8a06fe16e) + +Related: RHEL-13199 +--- + src/shared/bootspec.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c +index d3cfb41a12..fe44b5e9d2 100644 +--- a/src/shared/bootspec.c ++++ b/src/shared/bootspec.c +@@ -1408,6 +1408,8 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { + assert(config); + + if (!FLAGS_SET(json_format, JSON_FORMAT_OFF)) { ++ _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; ++ + for (size_t i = 0; i < config->n_entries; i++) { + _cleanup_free_ char *opts = NULL; + const BootEntry *e = config->entries + i; +@@ -1447,9 +1449,13 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { + if (r < 0) + return log_oom(); + +- json_variant_dump(v, json_format, stdout, NULL); ++ r = json_variant_append_array(&array, v); ++ if (r < 0) ++ return log_oom(); + } + ++ json_variant_dump(array, json_format, NULL, NULL); ++ + } else { + for (size_t n = 0; n < config->n_entries; n++) { + r = show_boot_entry( diff --git a/0409-test-replace-readfp-with-read_file.patch b/0409-test-replace-readfp-with-read_file.patch new file mode 100644 index 0000000..a35f451 --- /dev/null +++ b/0409-test-replace-readfp-with-read_file.patch @@ -0,0 +1,28 @@ +From 8fced1b2ed30b9cda338c35946d8dcc3820ac25a Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Wed, 5 Jul 2023 19:43:43 +0200 +Subject: [PATCH] test: replace readfp() with read_file() + +ConfigParser.readfp() has been deprecated since Python 3.2 and was +dropped completely in Python 3.11. + +(cherry picked from commit ba4a1cd8a863f65ff016be72e520c323aa1e1a6f) + +Related: RHEL-13199 +--- + test/sysv-generator-test.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/sysv-generator-test.py b/test/sysv-generator-test.py +index 484b610a02..84237bab61 100755 +--- a/test/sysv-generator-test.py ++++ b/test/sysv-generator-test.py +@@ -80,7 +80,7 @@ class SysvGeneratorTest(unittest.TestCase): + cp = RawConfigParser(dict_type=MultiDict) + cp.optionxform = lambda o: o # don't lower-case option names + with open(service) as f: +- cp.readfp(f) ++ cp.read_file(f) + results[os.path.basename(service)] = cp + + return (err, results) diff --git a/0410-stub-measure-document-and-measure-.uname-UKI-section.patch b/0410-stub-measure-document-and-measure-.uname-UKI-section.patch new file mode 100644 index 0000000..967b063 --- /dev/null +++ b/0410-stub-measure-document-and-measure-.uname-UKI-section.patch @@ -0,0 +1,81 @@ +From fe66c5955044cf2b93fa788ae7bdfe3a07f11449 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Sun, 21 May 2023 14:32:09 +0100 +Subject: [PATCH] stub/measure: document and measure .uname UKI section + +(cherry picked from commit b6f2e6860220aa89550f690b12246c4e8eb6e908) + +Resolves: RHEL-13199 +--- + man/systemd-stub.xml | 3 +++ + src/boot/measure.c | 3 +++ + src/fundamental/tpm-pcr.c | 1 + + src/fundamental/tpm-pcr.h | 1 + + 4 files changed, 8 insertions(+) + +diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml +index 415d663f53..85d30129d6 100644 +--- a/man/systemd-stub.xml ++++ b/man/systemd-stub.xml +@@ -57,6 +57,9 @@ + os-release5 file of + the OS the kernel belongs to, in the .osrel PE section. + ++ Kernel version information, i.e. the output of uname -r for the ++ kernel included in the UKI, in the .uname PE section. ++ + The initrd will be loaded from the .initrd PE section. + + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 0bbd386449..67ab84753e 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -79,6 +79,7 @@ static int help(int argc, char *argv[], void *userdata) { + " --initrd=PATH Path to initrd image file %7$s .initrd\n" + " --splash=PATH Path to splash bitmap file %7$s .splash\n" + " --dtb=PATH Path to Devicetree file %7$s .dtb\n" ++ " --uname=PATH Path to 'uname -r' file %7$s .uname\n" + " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, +@@ -118,6 +119,7 @@ static int parse_argv(int argc, char *argv[]) { + ARG_INITRD, + ARG_SPLASH, + ARG_DTB, ++ ARG_UNAME, + _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ + _ARG_SECTION_LAST, + ARG_PCRPKEY = _ARG_SECTION_LAST, +@@ -139,6 +141,7 @@ static int parse_argv(int argc, char *argv[]) { + { "initrd", required_argument, NULL, ARG_INITRD }, + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, ++ { "uname", required_argument, NULL, ARG_UNAME }, + { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, + { "current", no_argument, NULL, 'c' }, + { "bank", required_argument, NULL, ARG_BANK }, +diff --git a/src/fundamental/tpm-pcr.c b/src/fundamental/tpm-pcr.c +index 7609d83c2e..0685d37b05 100644 +--- a/src/fundamental/tpm-pcr.c ++++ b/src/fundamental/tpm-pcr.c +@@ -11,6 +11,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = { + [UNIFIED_SECTION_INITRD] = ".initrd", + [UNIFIED_SECTION_SPLASH] = ".splash", + [UNIFIED_SECTION_DTB] = ".dtb", ++ [UNIFIED_SECTION_UNAME] = ".uname", + [UNIFIED_SECTION_PCRSIG] = ".pcrsig", + [UNIFIED_SECTION_PCRPKEY] = ".pcrpkey", + NULL, +diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h +index 235d4841b0..24240b82ed 100644 +--- a/src/fundamental/tpm-pcr.h ++++ b/src/fundamental/tpm-pcr.h +@@ -34,6 +34,7 @@ typedef enum UnifiedSection { + UNIFIED_SECTION_INITRD, + UNIFIED_SECTION_SPLASH, + UNIFIED_SECTION_DTB, ++ UNIFIED_SECTION_UNAME, + UNIFIED_SECTION_PCRSIG, + UNIFIED_SECTION_PCRPKEY, + _UNIFIED_SECTION_MAX, diff --git a/0411-boot-measure-.sbat-section.patch b/0411-boot-measure-.sbat-section.patch new file mode 100644 index 0000000..92f13cd --- /dev/null +++ b/0411-boot-measure-.sbat-section.patch @@ -0,0 +1,96 @@ +From 16b3bb1a1bb8a0a42ad6eb56fd33dcb800c8af04 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Thu, 29 Jun 2023 23:41:48 +0100 +Subject: [PATCH] boot: measure .sbat section + +We are now merging .sbat sections from sd-stub and kernel image, so +measure it in PCR11. + +(cherry picked from commit d5f91cf79361cab58e32bf7b76c41ba244add75f) + +Resolves: RHEL-13199 +--- + man/systemd-measure.xml | 8 +++++--- + src/boot/measure.c | 3 +++ + src/fundamental/tpm-pcr.c | 1 + + src/fundamental/tpm-pcr.h | 1 + + 4 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml +index 46fc979654..e08dbcdac9 100644 +--- a/man/systemd-measure.xml ++++ b/man/systemd-measure.xml +@@ -66,9 +66,10 @@ + Pre-calculate the expected values seen in PCR register 11 after boot-up of a unified + kernel image consisting of the components specified with , + , , , +- , , see below. Only +- is mandatory. (Alternatively, specify to use the +- current values of PCR register 11 instead.) ++ , , , ++ see below. Only is mandatory. (Alternatively, ++ specify to use the current values of PCR register 11 instead.) ++ + + + +@@ -104,6 +105,7 @@ + + + ++ + + + When used with the calculate or sign verb, +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 67ab84753e..84a7c357a4 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -80,6 +80,7 @@ static int help(int argc, char *argv[], void *userdata) { + " --splash=PATH Path to splash bitmap file %7$s .splash\n" + " --dtb=PATH Path to Devicetree file %7$s .dtb\n" + " --uname=PATH Path to 'uname -r' file %7$s .uname\n" ++ " --sbat=PATH Path to SBAT file %7$s .sbat\n" + " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, +@@ -120,6 +121,7 @@ static int parse_argv(int argc, char *argv[]) { + ARG_SPLASH, + ARG_DTB, + ARG_UNAME, ++ ARG_SBAT, + _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ + _ARG_SECTION_LAST, + ARG_PCRPKEY = _ARG_SECTION_LAST, +@@ -142,6 +144,7 @@ static int parse_argv(int argc, char *argv[]) { + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, + { "uname", required_argument, NULL, ARG_UNAME }, ++ { "sbat", required_argument, NULL, ARG_SBAT }, + { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, + { "current", no_argument, NULL, 'c' }, + { "bank", required_argument, NULL, ARG_BANK }, +diff --git a/src/fundamental/tpm-pcr.c b/src/fundamental/tpm-pcr.c +index 0685d37b05..2f7e9b428d 100644 +--- a/src/fundamental/tpm-pcr.c ++++ b/src/fundamental/tpm-pcr.c +@@ -12,6 +12,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = { + [UNIFIED_SECTION_SPLASH] = ".splash", + [UNIFIED_SECTION_DTB] = ".dtb", + [UNIFIED_SECTION_UNAME] = ".uname", ++ [UNIFIED_SECTION_SBAT] = ".sbat", + [UNIFIED_SECTION_PCRSIG] = ".pcrsig", + [UNIFIED_SECTION_PCRPKEY] = ".pcrpkey", + NULL, +diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h +index 24240b82ed..794d593825 100644 +--- a/src/fundamental/tpm-pcr.h ++++ b/src/fundamental/tpm-pcr.h +@@ -35,6 +35,7 @@ typedef enum UnifiedSection { + UNIFIED_SECTION_SPLASH, + UNIFIED_SECTION_DTB, + UNIFIED_SECTION_UNAME, ++ UNIFIED_SECTION_SBAT, + UNIFIED_SECTION_PCRSIG, + UNIFIED_SECTION_PCRPKEY, + _UNIFIED_SECTION_MAX, diff --git a/0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch b/0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch new file mode 100644 index 0000000..1091ce0 --- /dev/null +++ b/0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch @@ -0,0 +1,42 @@ +From 3c235c8d26813ae428053c284b67ddfe70d9caed Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Wed, 22 Nov 2023 15:38:47 +0100 +Subject: [PATCH] Revert "test_ukify: no stinky root needed for signing" + +This reverts commit 0d66468243d888dd721ba0072cbad742ab6fc690. + +There was a huge rewrite of the tpm2 code that removed the requirement for +device access, but backporting that would be a huge effort. Let's instead skip +the tests for now. (They pass under root.) + +Related: RHEL-13199 +--- + src/ukify/test/test_ukify.py | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py +index b12c09d4bf..5a42a94799 100755 +--- a/src/ukify/test/test_ukify.py ++++ b/src/ukify/test/test_ukify.py +@@ -661,6 +661,10 @@ def test_pcr_signing(kernel_initrd, tmpdir): + pytest.skip('linux+initrd not found') + if systemd_measure() is None: + pytest.skip('systemd-measure not found') ++ if os.getuid() != 0: ++ pytest.skip('must be root to access tpm2') ++ if subprocess.call(['systemd-creds', 'has-tpm2', '-q']) != 0: ++ pytest.skip('tpm2 is not available') + + ourdir = pathlib.Path(__file__).parent + pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') +@@ -724,6 +728,10 @@ def test_pcr_signing2(kernel_initrd, tmpdir): + pytest.skip('linux+initrd not found') + if systemd_measure() is None: + pytest.skip('systemd-measure not found') ++ if os.getuid() != 0: ++ pytest.skip('must be root to access tpm2') ++ if subprocess.call(['systemd-creds', 'has-tpm2', '-q']) != 0: ++ pytest.skip('tpm2 is not available') + + ourdir = pathlib.Path(__file__).parent + pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') diff --git a/0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch b/0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch new file mode 100644 index 0000000..dde0df5 --- /dev/null +++ b/0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch @@ -0,0 +1,36 @@ +From 2adec0d845e6d00a604ddaa5639759896b78728f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Mon, 28 Aug 2023 18:22:43 +0300 +Subject: [PATCH] ukify: move to /usr/bin and mark as non non-experimental + +The tool is moved into the $PATH and a compat symlink is provided. + +It is fairly widely used now, and realistically we need to keep backwards +compat or people will be very unhappy. + +(cherry picked from commit f65aa477d90ab7fbbc50ba05c55180213d5992e0) + +Related: RHEL-13199 +--- + meson.build | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index b874c2f9b4..e0495ed36a 100644 +--- a/meson.build ++++ b/meson.build +@@ -3861,9 +3861,13 @@ ukify = custom_target( + command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + install : want_ukify, + install_mode : 'rwxr-xr-x', +- install_dir : rootlibexecdir) ++ install_dir : bindir) + if want_ukify + public_programs += ukify ++ ++ meson.add_install_script(sh, '-c', ++ ln_s.format(bindir / 'ukify', ++ rootlibexecdir / 'ukify')) + endif + + if want_tests != 'false' and want_kernel_install diff --git a/0414-kernel-install-Add-uki-layout.patch b/0414-kernel-install-Add-uki-layout.patch new file mode 100644 index 0000000..baefb8a --- /dev/null +++ b/0414-kernel-install-Add-uki-layout.patch @@ -0,0 +1,221 @@ +From 90f26ba66f256fd65c6e7c38ed6f2138fed6b3ed Mon Sep 17 00:00:00 2001 +From: Joerg Behrmann +Date: Wed, 23 Nov 2022 16:43:19 +0100 +Subject: [PATCH] kernel-install: Add uki layout + +Currently the kernel-install man page only documents the bls layout for use +with the boot loader spec type #1. 90-loaderentry.install uses this layout to +generate loader entries and copy the kernel image and initrd to $BOOT. + +This commit documents a second layout "uki" and adds 90-uki-copy.install, +which copies a UKI "uki.efi" from the staging area or any file with the .efi +extension given on the command line to +$BOOT/EFI/Linux/$ENTRY_TOKEN-$KERNEl_VERSION(+$TRIES).efi + +This allows for both locally generated and distro-provided UKIs to be handled +by kernel-install. + +(cherry picked from commit 0ccfd3564b2532a4da6526a9e030362c4a142b77) + +Resolves: RHEL-16354 +--- + man/kernel-install.xml | 38 ++++++++-- + src/kernel-install/90-uki-copy.install | 97 ++++++++++++++++++++++++++ + src/kernel-install/meson.build | 2 + + 3 files changed, 131 insertions(+), 6 deletions(-) + create mode 100755 src/kernel-install/90-uki-copy.install + +diff --git a/man/kernel-install.xml b/man/kernel-install.xml +index b8ea2b16b2..b1822a8847 100644 +--- a/man/kernel-install.xml ++++ b/man/kernel-install.xml +@@ -108,6 +108,14 @@ + is missing), or "Linux KERNEL-VERSION", if unset. + + If $KERNEL_INSTALL_LAYOUT is not "bls", this plugin does nothing. ++ ++ 90-uki-copy.install copies a file ++ uki.efi from $KERNEL_INSTALL_STAGING_AREA or if it does ++ not exist the KERNEL-IMAGE argument, iff it has a ++ .efi extension, to ++ $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION.efi. ++ ++ If $KERNEL_INSTALL_LAYOUT is not "uki", this plugin does nothing. + + + +@@ -132,6 +140,9 @@ + + 90-loaderentry.install removes the file + $BOOT/loader/entries/ENTRY-TOKEN-KERNEL-VERSION.conf. ++ ++ 90-uki-copy.install removes the file ++ $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION.efi. + + + +@@ -213,7 +224,7 @@ + (EFI System Partition) are mounted, and also conceptually referred to as $BOOT. Can + be overridden by setting $BOOT_ROOT (see below). + +- $KERNEL_INSTALL_LAYOUT=bls|other|... is set for the plugins to specify the ++ $KERNEL_INSTALL_LAYOUT=bls|uki|other|... is set for the plugins to specify the + installation layout. Defaults to if + $BOOT/ENTRY-TOKEN exists, or + otherwise. Additional layout names may be defined by convention. If a plugin uses a special layout, +@@ -235,6 +246,18 @@ + Implemented by 90-loaderentry.install. + + ++ ++ uki ++ ++ Standard Boot Loader ++ Specification Type #2 layout, compatible with ++ systemd-boot7: ++ unified kernel images under $BOOT/EFI/Linux as ++ $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION[+TRIES].efi. ++ Implemented by 90-uki-copy.install. ++ ++ + + other + +@@ -312,12 +335,15 @@ + /etc/kernel/tries + + +- Read by 90-loaderentry.install. If this file exists a numeric value is read from +- it and the naming of the generated entry file is slightly altered to include it as +- $BOOT/loader/entries/MACHINE-ID-KERNEL-VERSION+TRIES.conf. This ++ Read by 90-loaderentry.install and ++ 90-uki-copy.install. If this file exists a numeric value is read from it ++ and the naming of the generated entry file or UKI is slightly altered to include it as ++ $BOOT/loader/entries/ENTRY-TOKEN-KERNEL-VERSION+TRIES.conf ++ or ++ $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION+TRIES.conf, respectively. This + is useful for boot loaders such as +- systemd-boot7 which +- implement boot attempt counting with a counter embedded in the entry file name. ++ systemd-boot7 ++ which implement boot attempt counting with a counter embedded in the entry file name. + $KERNEL_INSTALL_CONF_ROOT may be used to override the path. + + +diff --git a/src/kernel-install/90-uki-copy.install b/src/kernel-install/90-uki-copy.install +new file mode 100755 +index 0000000000..d6e3deb723 +--- /dev/null ++++ b/src/kernel-install/90-uki-copy.install +@@ -0,0 +1,97 @@ ++#!/bin/sh ++# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- ++# ex: ts=8 sw=4 sts=4 et filetype=sh ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++# This file is part of systemd. ++# ++# systemd 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. ++# ++# systemd 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 ++# General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public License ++# along with systemd; If not, see . ++ ++set -e ++ ++COMMAND="${1:?}" ++KERNEL_VERSION="${2:?}" ++# shellcheck disable=SC2034 ++ENTRY_DIR_ABS="$3" ++KERNEL_IMAGE="$4" ++ ++[ "$KERNEL_INSTALL_LAYOUT" = "uki" ] || exit 0 ++ ++ENTRY_TOKEN="$KERNEL_INSTALL_ENTRY_TOKEN" ++BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT" ++ ++UKI_DIR="$BOOT_ROOT/EFI/Linux" ++ ++case "$COMMAND" in ++ remove) ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \ ++ echo "Removing $UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION*.efi" ++ exec rm -f \ ++ "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi" \ ++ "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION+"*".efi" ++ ;; ++ add) ++ ;; ++ *) ++ exit 0 ++ ;; ++esac ++ ++if ! [ -d "$UKI_DIR" ]; then ++ echo "Error: entry directory '$UKI_DIR' does not exist" >&2 ++ exit 1 ++fi ++ ++TRIES_FILE="${KERNEL_INSTALL_CONF_ROOT:-/etc/kernel}/tries" ++ ++if [ -f "$TRIES_FILE" ]; then ++ read -r TRIES <"$TRIES_FILE" ++ if ! echo "$TRIES" | grep -q '^[0-9][0-9]*$'; then ++ echo "$TRIES_FILE does not contain an integer." >&2 ++ exit 1 ++ fi ++ UKI_FILE="$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION+$TRIES.efi" ++else ++ UKI_FILE="$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi" ++fi ++ ++# If there is a UKI named uki.efi on the staging area use that, if not use what ++# was passed in as $KERNEL_IMAGE but insist it has a .efi extension ++if [ -f "$KERNEL_INSTALL_STAGING_AREA/uki.efi" ]; then ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_INSTALL_STAGING_AREA/uki.efi" ++ install -m 0644 "$KERNEL_INSTALL_STAGING_AREA/uki.efi" "$UKI_FILE" || { ++ echo "Error: could not copy '$KERNEL_INSTALL_STAGING_AREA/uki.efi' to '$UKI_FILE'." >&2 ++ exit 1 ++ } ++elif [ -n "$KERNEL_IMAGE" ]; then ++ [ -f "$KERNEL_IMAGE" ] || { ++ echo "Error: UKI '$KERNEL_IMAGE' not a file." >&2 ++ exit 1 ++ } ++ [ "$KERNEL_IMAGE" != "${KERNEL_IMAGE%*.efi}.efi" ] && { ++ echo "Error: $KERNEL_IMAGE is missing .efi suffix." >&2 ++ exit 1 ++ } ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_IMAGE" ++ install -m 0644 "$KERNEL_IMAGE" "$UKI_FILE" || { ++ echo "Error: could not copy '$KERNEL_IMAGE' to '$UKI_FILE'." >&2 ++ exit 1 ++ } ++else ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "No UKI available. Nothing to do." ++ exit 0 ++fi ++chown root:root "$UKI_FILE" || : ++ ++exit 0 +diff --git a/src/kernel-install/meson.build b/src/kernel-install/meson.build +index 90a0e3ae49..68a4d43862 100644 +--- a/src/kernel-install/meson.build ++++ b/src/kernel-install/meson.build +@@ -3,6 +3,8 @@ + kernel_install_in = files('kernel-install.in') + loaderentry_install = files('90-loaderentry.install') + ++uki_copy_install = files('90-uki-copy.install') ++ + if want_kernel_install + install_data('50-depmod.install', + loaderentry_install, diff --git a/0415-kernel-install-remove-math-slang-from-man-page.patch b/0415-kernel-install-remove-math-slang-from-man-page.patch new file mode 100644 index 0000000..789d450 --- /dev/null +++ b/0415-kernel-install-remove-math-slang-from-man-page.patch @@ -0,0 +1,25 @@ +From 5d3d683fd54100fea2a355b0ebc8b03d9b6c964b Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Wed, 25 Jan 2023 13:57:09 +0100 +Subject: [PATCH] kernel-install: remove math slang from man page + +(cherry picked from commit 642617f431457382ec2140a52ee08bfbb3e5e1db) + +Related: RHEL-16354 +--- + man/kernel-install.xml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/man/kernel-install.xml b/man/kernel-install.xml +index b1822a8847..1bc6e7aa05 100644 +--- a/man/kernel-install.xml ++++ b/man/kernel-install.xml +@@ -111,7 +111,7 @@ + + 90-uki-copy.install copies a file + uki.efi from $KERNEL_INSTALL_STAGING_AREA or if it does +- not exist the KERNEL-IMAGE argument, iff it has a ++ not exist the KERNEL-IMAGE argument, only if it has a + .efi extension, to + $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION.efi. + diff --git a/0416-kernel-install-handle-uki-installs-automatically.patch b/0416-kernel-install-handle-uki-installs-automatically.patch new file mode 100644 index 0000000..744b1cb --- /dev/null +++ b/0416-kernel-install-handle-uki-installs-automatically.patch @@ -0,0 +1,80 @@ +From 4f3593718196c007838eebf5d9b42f09318bd4e6 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Fri, 20 Jan 2023 09:05:18 +0100 +Subject: [PATCH] kernel-install: handle uki installs automatically + +Detect image type using "bootctl kernel-identify $kernel", +store result in KERNEL_INSTALL_IMAGE_TYPE. + +Extend layout autodetection to check the kernel image type +and pick layout=uki for UKIs. + +Resolves: https://github.com/systemd/systemd/issues/25822 +(cherry picked from commit 3d5f0bfe4e72fdc4d8f8d65f96dc5501dfed8a64) + +Related: RHEL-16354 +--- + man/kernel-install.xml | 17 +++++++++++++---- + src/kernel-install/kernel-install.in | 12 ++++++++++-- + 2 files changed, 23 insertions(+), 6 deletions(-) + +diff --git a/man/kernel-install.xml b/man/kernel-install.xml +index 1bc6e7aa05..4d91b7b20b 100644 +--- a/man/kernel-install.xml ++++ b/man/kernel-install.xml +@@ -224,10 +224,8 @@ + (EFI System Partition) are mounted, and also conceptually referred to as $BOOT. Can + be overridden by setting $BOOT_ROOT (see below). + +- $KERNEL_INSTALL_LAYOUT=bls|uki|other|... is set for the plugins to specify the +- installation layout. Defaults to if +- $BOOT/ENTRY-TOKEN exists, or +- otherwise. Additional layout names may be defined by convention. If a plugin uses a special layout, ++ $KERNEL_INSTALL_LAYOUT=auto|bls|uki|other|... is set for the plugins to specify the ++ installation layout. Additional layout names may be defined by convention. If a plugin uses a special layout, + it's encouraged to declare its own layout name and configure layout= in + install.conf upon initial installation. The following values are currently + understood: +@@ -264,6 +262,17 @@ + Some other layout not understood natively by kernel-install. + + ++ ++ auto ++ ++ Pick the layout automatically. If the kernel is a UKI set layout to ++ . If not default to if ++ $BOOT/loader/entries.srel with content type1 or ++ $BOOT/ENTRY-TOKEN exists, or ++ otherwise. ++ Leaving layout blank has the same effect. This is the default. ++ ++ + + + $KERNEL_INSTALL_INITRD_GENERATOR is set for plugins to select the initrd +diff --git a/src/kernel-install/kernel-install.in b/src/kernel-install/kernel-install.in +index fa2c0d5276..25884fc0e2 100755 +--- a/src/kernel-install/kernel-install.in ++++ b/src/kernel-install/kernel-install.in +@@ -250,10 +250,18 @@ if [ -z "$ENTRY_TOKEN" ]; then + echo "No entry-token candidate matched, using \"$ENTRY_TOKEN\" from machine-id" + fi + +-if [ -z "$layout" ]; then ++export KERNEL_INSTALL_IMAGE_TYPE="" ++if [ -f "$1" ]; then ++ KERNEL_INSTALL_IMAGE_TYPE="$(bootctl kernel-identify "$1" 2>/dev/null || echo "unknown")" ++fi ++ ++if [ "$layout" = "auto" ] || [ -z "$layout" ]; then + # No layout configured by the administrator. Let's try to figure it out + # automatically from metadata already contained in $BOOT_ROOT. +- if [ -e "$BOOT_ROOT/loader/entries.srel" ]; then ++ if [ "$KERNEL_INSTALL_IMAGE_TYPE" = "uki" ]; then ++ layout="uki" ++ log_verbose "Kernel image is UKI, using layout=$layout" ++ elif [ -e "$BOOT_ROOT/loader/entries.srel" ]; then + read -r ENTRIES_SREL <"$BOOT_ROOT/loader/entries.srel" + if [ "$ENTRIES_SREL" = "type1" ]; then + # The loader/entries.srel file clearly indicates that the installed diff --git a/0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch b/0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch new file mode 100644 index 0000000..90d3490 --- /dev/null +++ b/0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch @@ -0,0 +1,31 @@ +From cb2ead4b9fc17554a8694fd213bfb100d9c15678 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Fri, 20 Jan 2023 12:59:33 +0100 +Subject: [PATCH] 90-uki-copy.install: create $BOOT/EFI/Linux directory if + needed + +Do not consider a missing 'Linux' subdirectory an error. +Just create it instead. + +(cherry picked from commit c7314ee7e290b3978e2f2d7726d07656eda071f9) + +Related: RHEL-16354 +--- + src/kernel-install/90-uki-copy.install | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/kernel-install/90-uki-copy.install b/src/kernel-install/90-uki-copy.install +index d6e3deb723..6c71b211d7 100755 +--- a/src/kernel-install/90-uki-copy.install ++++ b/src/kernel-install/90-uki-copy.install +@@ -49,8 +49,8 @@ case "$COMMAND" in + esac + + if ! [ -d "$UKI_DIR" ]; then +- echo "Error: entry directory '$UKI_DIR' does not exist" >&2 +- exit 1 ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "creating $UKI_DIR" ++ mkdir -p "$UKI_DIR" + fi + + TRIES_FILE="${KERNEL_INSTALL_CONF_ROOT:-/etc/kernel}/tries" diff --git a/0418-kernel-install-Log-location-that-uki-is-installed-in.patch b/0418-kernel-install-Log-location-that-uki-is-installed-in.patch new file mode 100644 index 0000000..494e1c4 --- /dev/null +++ b/0418-kernel-install-Log-location-that-uki-is-installed-in.patch @@ -0,0 +1,36 @@ +From d0b5386bde65b8c488d23f16ec4049d1e6378c25 Mon Sep 17 00:00:00 2001 +From: Daan De Meyer +Date: Sun, 5 Nov 2023 13:50:25 +0100 +Subject: [PATCH] kernel-install: Log location that uki is installed in + +Let's log where we install a UKI when running in verbose mode. + +(cherry picked from commit 4f5278eead35bc66cc943a493eab8a8b78174400) + +Related: RHEL-16354 +--- + src/kernel-install/90-uki-copy.install | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/kernel-install/90-uki-copy.install b/src/kernel-install/90-uki-copy.install +index 6c71b211d7..c66c09719c 100755 +--- a/src/kernel-install/90-uki-copy.install ++++ b/src/kernel-install/90-uki-copy.install +@@ -69,7 +69,7 @@ fi + # If there is a UKI named uki.efi on the staging area use that, if not use what + # was passed in as $KERNEL_IMAGE but insist it has a .efi extension + if [ -f "$KERNEL_INSTALL_STAGING_AREA/uki.efi" ]; then +- [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_INSTALL_STAGING_AREA/uki.efi" ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_INSTALL_STAGING_AREA/uki.efi as $UKI_FILE" + install -m 0644 "$KERNEL_INSTALL_STAGING_AREA/uki.efi" "$UKI_FILE" || { + echo "Error: could not copy '$KERNEL_INSTALL_STAGING_AREA/uki.efi' to '$UKI_FILE'." >&2 + exit 1 +@@ -83,7 +83,7 @@ elif [ -n "$KERNEL_IMAGE" ]; then + echo "Error: $KERNEL_IMAGE is missing .efi suffix." >&2 + exit 1 + } +- [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_IMAGE" ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_IMAGE as $UKI_FILE" + install -m 0644 "$KERNEL_IMAGE" "$UKI_FILE" || { + echo "Error: could not copy '$KERNEL_IMAGE' to '$UKI_FILE'." >&2 + exit 1 diff --git a/0419-bootctl-fix-errno-logging.patch b/0419-bootctl-fix-errno-logging.patch new file mode 100644 index 0000000..c868226 --- /dev/null +++ b/0419-bootctl-fix-errno-logging.patch @@ -0,0 +1,25 @@ +From f0ab67eb46103c68a1fc708b45e2fa6b93780efb Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 19 Dec 2022 22:25:28 +0100 +Subject: [PATCH] bootctl: fix errno logging + +(cherry picked from commit e425849e995e448f529d3c106bf1e3de2ca23a35) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 7da48b4ca4..67fcbcc8cd 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -2028,7 +2028,7 @@ static int install_random_seed(const char *esp) { + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing random seed file."); + + if (rename(tmp, path) < 0) +- return log_error_errno(r, "Failed to move random seed file into place: %m"); ++ return log_error_errno(errno, "Failed to move random seed file into place: %m"); + + tmp = mfree(tmp); + diff --git a/0420-bootctl-add-kernel-identity-command.patch b/0420-bootctl-add-kernel-identity-command.patch new file mode 100644 index 0000000..1571df7 --- /dev/null +++ b/0420-bootctl-add-kernel-identity-command.patch @@ -0,0 +1,214 @@ +From ca716a0fdfd434e52384dd74ff4c909aa95b6c81 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Mon, 16 Jan 2023 18:58:21 +0100 +Subject: [PATCH] bootctl: add kernel-identity command + +The command takes a kernel as argument and checks what kind of kernel +the image is. Returns one of uki, pe or unknown. + +(cherry picked from commit 53c368d71ba43da7414ac86c58291a11da05ba84) + +Resolves: RHEL-16354 +--- + man/bootctl.xml | 13 +++++ + meson.build | 5 +- + src/boot/bootctl-uki.c | 109 +++++++++++++++++++++++++++++++++++++++++ + src/boot/bootctl-uki.h | 3 ++ + src/boot/bootctl.c | 4 ++ + 5 files changed, 133 insertions(+), 1 deletion(-) + create mode 100644 src/boot/bootctl-uki.c + create mode 100644 src/boot/bootctl-uki.h + +diff --git a/man/bootctl.xml b/man/bootctl.xml +index dfc56d6125..0f992ec383 100644 +--- a/man/bootctl.xml ++++ b/man/bootctl.xml +@@ -217,6 +217,19 @@ + + + ++ ++ <command>kernel</command> Commands ++ ++ ++ ++ kernel ++ ++ Takes a kernel image as argument. Checks what kind of kernel the image is. Returns ++ one of uki, pe or unknown. ++ ++ ++ ++ + + Options + The following options are understood: +diff --git a/meson.build b/meson.build +index e0495ed36a..936e612a01 100644 +--- a/meson.build ++++ b/meson.build +@@ -2566,7 +2566,10 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1 + + public_programs += executable( + 'bootctl', +- 'src/boot/bootctl.c', ++ ['src/boot/bootctl.c', ++ 'src/boot/bootctl-uki.c', ++ 'src/boot/bootctl-uki.h', ++ ], + include_directories : includes, + link_with : [boot_link_with], + dependencies : [libblkid], +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +new file mode 100644 +index 0000000000..7e8e8a570b +--- /dev/null ++++ b/src/boot/bootctl-uki.c +@@ -0,0 +1,109 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include "bootctl-uki.h" ++#include "fd-util.h" ++#include "parse-util.h" ++#include "pe-header.h" ++ ++#define MAX_SECTIONS 96 ++ ++static const uint8_t dos_file_magic[2] = "MZ"; ++static const uint8_t pe_file_magic[4] = "PE\0\0"; ++ ++static const uint8_t name_osrel[8] = ".osrel"; ++static const uint8_t name_linux[8] = ".linux"; ++static const uint8_t name_initrd[8] = ".initrd"; ++ ++static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { ++ _cleanup_free_ struct PeSectionHeader *sections = NULL; ++ struct DosFileHeader dos; ++ struct PeHeader pe; ++ size_t scount; ++ uint64_t soff, items; ++ int rc; ++ ++ items = fread(&dos, 1, sizeof(dos), uki); ++ if (items != sizeof(dos)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "DOS header read error"); ++ if (memcmp(dos.Magic, dos_file_magic, sizeof(dos_file_magic)) != 0) ++ goto no_sections; ++ ++ rc = fseek(uki, le32toh(dos.ExeHeader), SEEK_SET); ++ if (rc < 0) ++ return log_error_errno(errno, "seek to PE header"); ++ items = fread(&pe, 1, sizeof(pe), uki); ++ if (items != sizeof(pe)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE header read error"); ++ if (memcmp(pe.Magic, pe_file_magic, sizeof(pe_file_magic)) != 0) ++ goto no_sections; ++ ++ soff = le32toh(dos.ExeHeader) + sizeof(pe) + le16toh(pe.FileHeader.SizeOfOptionalHeader); ++ rc = fseek(uki, soff, SEEK_SET); ++ if (rc < 0) ++ return log_error_errno(errno, "seek to PE section headers"); ++ ++ scount = le16toh(pe.FileHeader.NumberOfSections); ++ if (scount > MAX_SECTIONS) ++ goto no_sections; ++ sections = new(struct PeSectionHeader, scount); ++ if (!sections) ++ return log_oom(); ++ items = fread(sections, sizeof(*sections), scount, uki); ++ if (items != scount) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE section header read error"); ++ ++ *ret = TAKE_PTR(sections); ++ *ret_n = scount; ++ return 0; ++ ++no_sections: ++ *ret = NULL; ++ *ret_n = 0; ++ return 0; ++} ++ ++static int find_pe_section(struct PeSectionHeader *sections, size_t scount, ++ const uint8_t *name, size_t namelen, size_t *ret) { ++ for (size_t s = 0; s < scount; s++) { ++ if (memcmp_nn(sections[s].Name, sizeof(sections[s].Name), ++ name, namelen) == 0) { ++ if (ret) ++ *ret = s; ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++static bool is_uki(struct PeSectionHeader *sections, size_t scount) { ++ return (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), NULL) && ++ find_pe_section(sections, scount, name_linux, sizeof(name_linux), NULL) && ++ find_pe_section(sections, scount, name_initrd, sizeof(name_initrd), NULL)); ++} ++ ++int verb_kernel_identify(int argc, char *argv[], void *userdata) { ++ _cleanup_fclose_ FILE *uki = NULL; ++ _cleanup_free_ struct PeSectionHeader *sections = NULL; ++ size_t scount; ++ int rc; ++ ++ uki = fopen(argv[1], "re"); ++ if (!uki) ++ return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]); ++ ++ rc = pe_sections(uki, §ions, &scount); ++ if (rc < 0) ++ return EXIT_FAILURE; ++ ++ if (sections) { ++ if (is_uki(sections, scount)) { ++ puts("uki"); ++ return EXIT_SUCCESS; ++ } ++ puts("pe"); ++ return EXIT_SUCCESS; ++ } ++ ++ puts("unknown"); ++ return EXIT_SUCCESS; ++} +diff --git a/src/boot/bootctl-uki.h b/src/boot/bootctl-uki.h +new file mode 100644 +index 0000000000..3c1fb5bc6a +--- /dev/null ++++ b/src/boot/bootctl-uki.h +@@ -0,0 +1,3 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++int verb_kernel_identify(int argc, char *argv[], void *userdata); +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 67fcbcc8cd..58e4af85f8 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -14,6 +14,7 @@ + + #include "alloc-util.h" + #include "blkid-util.h" ++#include "bootctl-uki.h" + #include "bootspec.h" + #include "chase-symlinks.h" + #include "copy.h" +@@ -1453,6 +1454,8 @@ static int help(int argc, char *argv[], void *userdata) { + " remove Remove systemd-boot from the ESP and EFI variables\n" + " is-installed Test whether systemd-boot is installed in the ESP\n" + " random-seed Initialize random seed in ESP and EFI variables\n" ++ "\n%3$skernel Commands:%4$s\n" ++ " kernel-identify Identify kernel image type.\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" +@@ -2567,6 +2570,7 @@ static int bootctl_main(int argc, char *argv[]) { + { "update", VERB_ANY, 1, 0, verb_install }, + { "remove", VERB_ANY, 1, 0, verb_remove }, + { "is-installed", VERB_ANY, 1, 0, verb_is_installed }, ++ { "kernel-identify", 2, 2, 0, verb_kernel_identify }, + { "list", VERB_ANY, 1, 0, verb_list }, + { "set-default", 2, 2, 0, verb_set_efivar }, + { "set-oneshot", 2, 2, 0, verb_set_efivar }, diff --git a/0421-bootctl-add-kernel-inspect-command.patch b/0421-bootctl-add-kernel-inspect-command.patch new file mode 100644 index 0000000..fdf869b --- /dev/null +++ b/0421-bootctl-add-kernel-inspect-command.patch @@ -0,0 +1,150 @@ +From 3012047a48f67f6cceba50ce326497aa647074ea Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Tue, 17 Jan 2023 22:06:06 +0100 +Subject: [PATCH] bootctl: add kernel-inspect command + +Takes a kernel image as argument. Prints details about the kernel. + +Signed-off-by: Gerd Hoffmann +(cherry picked from commit a05255981ba5b04f1cf54ea656fbce1dfd9c3a68) + +Resolves: RHEL-16354 +--- + man/bootctl.xml | 6 ++++ + src/boot/bootctl-uki.c | 79 ++++++++++++++++++++++++++++++++++++++++++ + src/boot/bootctl-uki.h | 1 + + src/boot/bootctl.c | 1 + + 4 files changed, 87 insertions(+) + +diff --git a/man/bootctl.xml b/man/bootctl.xml +index 0f992ec383..c12fe93214 100644 +--- a/man/bootctl.xml ++++ b/man/bootctl.xml +@@ -227,6 +227,12 @@ + Takes a kernel image as argument. Checks what kind of kernel the image is. Returns + one of uki, pe or unknown. + ++ ++ ++ kernel ++ ++ Takes a kernel image as argument. Prints details about the kernel. ++ + + + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index 7e8e8a570b..3085f703a8 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -13,6 +13,8 @@ static const uint8_t pe_file_magic[4] = "PE\0\0"; + static const uint8_t name_osrel[8] = ".osrel"; + static const uint8_t name_linux[8] = ".linux"; + static const uint8_t name_initrd[8] = ".initrd"; ++static const uint8_t name_cmdline[8] = ".cmdline"; ++static const uint8_t name_uname[8] = ".uname"; + + static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + _cleanup_free_ struct PeSectionHeader *sections = NULL; +@@ -107,3 +109,80 @@ int verb_kernel_identify(int argc, char *argv[], void *userdata) { + puts("unknown"); + return EXIT_SUCCESS; + } ++ ++static int read_pe_section(FILE *uki, const struct PeSectionHeader *section, ++ void **ret, size_t *ret_n) { ++ _cleanup_free_ void *data = NULL; ++ uint32_t size, bytes; ++ uint64_t soff; ++ int rc; ++ ++ soff = le32toh(section->PointerToRawData); ++ size = le32toh(section->VirtualSize); ++ ++ if (size > 16 * 1024) ++ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "PE section too big"); ++ ++ rc = fseek(uki, soff, SEEK_SET); ++ if (rc < 0) ++ return log_error_errno(errno, "seek to PE section"); ++ ++ data = malloc(size+1); ++ if (!data) ++ return log_oom(); ++ ((uint8_t*) data)[size] = 0; /* safety NUL byte */ ++ ++ bytes = fread(data, 1, size, uki); ++ if (bytes != size) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE section read error"); ++ ++ *ret = TAKE_PTR(data); ++ if (ret_n) ++ *ret_n = size; ++ return 0; ++} ++ ++static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scount) { ++ _cleanup_free_ char *cmdline = NULL; ++ _cleanup_free_ char *uname = NULL; ++ size_t idx; ++ ++ if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx)) ++ read_pe_section(uki, sections + idx, (void**)&cmdline, NULL); ++ ++ if (find_pe_section(sections, scount, name_uname, sizeof(name_uname), &idx)) ++ read_pe_section(uki, sections + idx, (void**)&uname, NULL); ++ ++ if (cmdline) ++ printf(" Cmdline: %s\n", cmdline); ++ if (uname) ++ printf(" Version: %s\n", uname); ++} ++ ++int verb_kernel_inspect(int argc, char *argv[], void *userdata) { ++ _cleanup_fclose_ FILE *uki = NULL; ++ _cleanup_free_ struct PeSectionHeader *sections = NULL; ++ size_t scount; ++ int rc; ++ ++ uki = fopen(argv[1], "re"); ++ if (!uki) ++ return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]); ++ ++ rc = pe_sections(uki, §ions, &scount); ++ if (rc < 0) ++ return EXIT_FAILURE; ++ ++ if (sections) { ++ if (is_uki(sections, scount)) { ++ puts("Kernel Type: uki"); ++ inspect_uki(uki, sections, scount); ++ return EXIT_SUCCESS; ++ } ++ puts("Kernel Type: pe"); ++ return EXIT_SUCCESS; ++ } ++ ++ puts("Kernel Type: unknown"); ++ return EXIT_SUCCESS; ++} +diff --git a/src/boot/bootctl-uki.h b/src/boot/bootctl-uki.h +index 3c1fb5bc6a..effb984d80 100644 +--- a/src/boot/bootctl-uki.h ++++ b/src/boot/bootctl-uki.h +@@ -1,3 +1,4 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + + int verb_kernel_identify(int argc, char *argv[], void *userdata); ++int verb_kernel_inspect(int argc, char *argv[], void *userdata); +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 58e4af85f8..b98673cd11 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -2571,6 +2571,7 @@ static int bootctl_main(int argc, char *argv[]) { + { "remove", VERB_ANY, 1, 0, verb_remove }, + { "is-installed", VERB_ANY, 1, 0, verb_is_installed }, + { "kernel-identify", 2, 2, 0, verb_kernel_identify }, ++ { "kernel-inspect", 2, 2, 0, verb_kernel_inspect }, + { "list", VERB_ANY, 1, 0, verb_list }, + { "set-default", 2, 2, 0, verb_set_efivar }, + { "set-oneshot", 2, 2, 0, verb_set_efivar }, diff --git a/0422-bootctl-add-kernel-inspect-to-help-text.patch b/0422-bootctl-add-kernel-inspect-to-help-text.patch new file mode 100644 index 0000000..95218e4 --- /dev/null +++ b/0422-bootctl-add-kernel-inspect-to-help-text.patch @@ -0,0 +1,24 @@ +From c1736cd4c9d0cb7b39333280bd30657e68481436 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Fri, 20 Jan 2023 13:30:48 +0100 +Subject: [PATCH] bootctl: add kernel-inspect to --help text + +(cherry picked from commit 24a3b37f12a1a752e5a1379cadc15031b04c3fba) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index b98673cd11..cc35147376 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1456,6 +1456,7 @@ static int help(int argc, char *argv[], void *userdata) { + " random-seed Initialize random seed in ESP and EFI variables\n" + "\n%3$skernel Commands:%4$s\n" + " kernel-identify Identify kernel image type.\n" ++ " kernel-inspect Prints details about the kernel.\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" diff --git a/0423-bootctl-drop-full-stop-at-end-of-help-texts.patch b/0423-bootctl-drop-full-stop-at-end-of-help-texts.patch new file mode 100644 index 0000000..ba546ee --- /dev/null +++ b/0423-bootctl-drop-full-stop-at-end-of-help-texts.patch @@ -0,0 +1,29 @@ +From d5d960b0b8db73ffe4cff9d156b0883876d722ac Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 20 Jan 2023 18:29:13 +0100 +Subject: [PATCH] bootctl: drop full stop at end of --help texts + +We never do that, don't do so here either. + +(cherry picked from commit 2b197967bf251ecf58b93fed0f51b9d4cd83fda4) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index cc35147376..681c5bd44f 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1455,8 +1455,8 @@ static int help(int argc, char *argv[], void *userdata) { + " is-installed Test whether systemd-boot is installed in the ESP\n" + " random-seed Initialize random seed in ESP and EFI variables\n" + "\n%3$skernel Commands:%4$s\n" +- " kernel-identify Identify kernel image type.\n" +- " kernel-inspect Prints details about the kernel.\n" ++ " kernel-identify Identify kernel image type\n" ++ " kernel-inspect Prints details about the kernel\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" diff --git a/0424-bootctl-change-section-title-for-kernel-image-comman.patch b/0424-bootctl-change-section-title-for-kernel-image-comman.patch new file mode 100644 index 0000000..9743c47 --- /dev/null +++ b/0424-bootctl-change-section-title-for-kernel-image-comman.patch @@ -0,0 +1,44 @@ +From 6ee66a4736bde681956ef8ab601a72a5cb7b19ed Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 20 Jan 2023 18:30:06 +0100 +Subject: [PATCH] bootctl: change section title for kernel image commands + +Let's call them kernel *images*, not just *kernels*. + +(cherry picked from commit 1e7d6cc07211de425bcc5c408c1f4376d6717305) + +Related: RHEL-16354 +--- + man/bootctl.xml | 2 +- + src/boot/bootctl.c | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/man/bootctl.xml b/man/bootctl.xml +index c12fe93214..d82f12d5bb 100644 +--- a/man/bootctl.xml ++++ b/man/bootctl.xml +@@ -218,7 +218,7 @@ + + + +- <command>kernel</command> Commands ++ Kernel Image Commands + + + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 681c5bd44f..4f2a6288fb 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1454,9 +1454,9 @@ static int help(int argc, char *argv[], void *userdata) { + " remove Remove systemd-boot from the ESP and EFI variables\n" + " is-installed Test whether systemd-boot is installed in the ESP\n" + " random-seed Initialize random seed in ESP and EFI variables\n" +- "\n%3$skernel Commands:%4$s\n" ++ "\n%3$sKernel Image Commands:%4$s\n" + " kernel-identify Identify kernel image type\n" +- " kernel-inspect Prints details about the kernel\n" ++ " kernel-inspect Prints details about the kernel image\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" diff --git a/0425-bootctl-remove-space-that-should-not-be-there.patch b/0425-bootctl-remove-space-that-should-not-be-there.patch new file mode 100644 index 0000000..11dbf91 --- /dev/null +++ b/0425-bootctl-remove-space-that-should-not-be-there.patch @@ -0,0 +1,25 @@ +From 2eae3094d018587e2550aaf895a7cbdeaea679bd Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 20 Jan 2023 18:40:57 +0100 +Subject: [PATCH] bootctl: remove space that should not be there + +(cherry picked from commit e684d2d5f85a82ed47eb063809145540df01ae1a) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 4f2a6288fb..3044f67b5a 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1433,7 +1433,7 @@ static int help(int argc, char *argv[], void *userdata) { + if (r < 0) + return log_oom(); + +- printf("%1$s [OPTIONS...] COMMAND ...\n" ++ printf("%1$s [OPTIONS...] COMMAND ...\n" + "\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n" + "\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n" + " status Show status of installed boot loader and EFI variables\n" diff --git a/0426-bootctl-kernel-inspect-print-os-info.patch b/0426-bootctl-kernel-inspect-print-os-info.patch new file mode 100644 index 0000000..bf20bc1 --- /dev/null +++ b/0426-bootctl-kernel-inspect-print-os-info.patch @@ -0,0 +1,73 @@ +From 6204aa43883fdf02d72bd0db6d3cfbfa7c075213 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Fri, 20 Jan 2023 15:40:36 +0100 +Subject: [PATCH] bootctl: kernel-inspect: print os info + +(cherry picked from commit 2d4260482cb8463f4de9502efd26bf8c64262669) + +Related: RHEL-16354 +--- + src/boot/bootctl-uki.c | 29 ++++++++++++++++++++++++++++- + 1 file changed, 28 insertions(+), 1 deletion(-) + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index 3085f703a8..6bdf926350 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -1,6 +1,7 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + + #include "bootctl-uki.h" ++#include "env-file.h" + #include "fd-util.h" + #include "parse-util.h" + #include "pe-header.h" +@@ -142,10 +143,31 @@ static int read_pe_section(FILE *uki, const struct PeSectionHeader *section, + return 0; + } + ++static void inspect_osrel(char *osrel, size_t osrel_size) { ++ _cleanup_fclose_ FILE *s = NULL; ++ _cleanup_free_ char *pname = NULL, *name = NULL; ++ int r; ++ ++ assert(osrel); ++ s = fmemopen(osrel, osrel_size, "r"); ++ if (!s) ++ return (void) log_warning_errno(errno, "Failed to open embedded os-release file, ignoring: %m"); ++ ++ r = parse_env_file(s, NULL, ++ "PRETTY_NAME", &pname, ++ "NAME", &name); ++ if (r < 0) ++ return (void) log_warning_errno(r, "Failed to parse embedded os-release file, ignoring: %m"); ++ ++ if (pname || name) ++ printf(" OS: %s\n", pname ?: name); ++} ++ + static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scount) { + _cleanup_free_ char *cmdline = NULL; + _cleanup_free_ char *uname = NULL; +- size_t idx; ++ _cleanup_free_ char *osrel = NULL; ++ size_t osrel_size, idx; + + if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx)) + read_pe_section(uki, sections + idx, (void**)&cmdline, NULL); +@@ -153,10 +175,15 @@ static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scou + if (find_pe_section(sections, scount, name_uname, sizeof(name_uname), &idx)) + read_pe_section(uki, sections + idx, (void**)&uname, NULL); + ++ if (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), &idx)) ++ read_pe_section(uki, sections + idx, (void**)&osrel, &osrel_size); ++ + if (cmdline) + printf(" Cmdline: %s\n", cmdline); + if (uname) + printf(" Version: %s\n", uname); ++ if (osrel) ++ inspect_osrel(osrel, osrel_size); + } + + int verb_kernel_inspect(int argc, char *argv[], void *userdata) { diff --git a/0427-bootctl-uki-several-coding-style-fixlets.patch b/0427-bootctl-uki-several-coding-style-fixlets.patch new file mode 100644 index 0000000..17116fd --- /dev/null +++ b/0427-bootctl-uki-several-coding-style-fixlets.patch @@ -0,0 +1,207 @@ +From a9c7fe86260ca906c7378503e059eca0ad014947 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 24 Jan 2023 22:59:59 +0900 +Subject: [PATCH] bootctl-uki: several coding style fixlets + +Mostly follow-ups for #26082. + +(cherry picked from commit 5b532e14e3ac1d8f8cef90fd8c5b1ce277458ae7) + +Related: RHEL-16354 +--- + src/boot/bootctl-uki.c | 105 +++++++++++++++++++++++------------------ + 1 file changed, 60 insertions(+), 45 deletions(-) + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index 6bdf926350..3492fa548f 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -23,7 +23,10 @@ static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + struct PeHeader pe; + size_t scount; + uint64_t soff, items; +- int rc; ++ ++ assert(uki); ++ assert(ret); ++ assert(ret_n); + + items = fread(&dos, 1, sizeof(dos), uki); + if (items != sizeof(dos)) +@@ -31,9 +34,9 @@ static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + if (memcmp(dos.Magic, dos_file_magic, sizeof(dos_file_magic)) != 0) + goto no_sections; + +- rc = fseek(uki, le32toh(dos.ExeHeader), SEEK_SET); +- if (rc < 0) ++ if (fseek(uki, le32toh(dos.ExeHeader), SEEK_SET) < 0) + return log_error_errno(errno, "seek to PE header"); ++ + items = fread(&pe, 1, sizeof(pe), uki); + if (items != sizeof(pe)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE header read error"); +@@ -41,8 +44,7 @@ static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + goto no_sections; + + soff = le32toh(dos.ExeHeader) + sizeof(pe) + le16toh(pe.FileHeader.SizeOfOptionalHeader); +- rc = fseek(uki, soff, SEEK_SET); +- if (rc < 0) ++ if (fseek(uki, soff, SEEK_SET) < 0) + return log_error_errno(errno, "seek to PE section headers"); + + scount = le16toh(pe.FileHeader.NumberOfSections); +@@ -65,58 +67,72 @@ no_sections: + return 0; + } + +-static int find_pe_section(struct PeSectionHeader *sections, size_t scount, +- const uint8_t *name, size_t namelen, size_t *ret) { +- for (size_t s = 0; s < scount; s++) { +- if (memcmp_nn(sections[s].Name, sizeof(sections[s].Name), +- name, namelen) == 0) { ++static bool find_pe_section( ++ struct PeSectionHeader *sections, ++ size_t scount, ++ const uint8_t *name, ++ size_t namelen, ++ size_t *ret) { ++ ++ assert(sections || scount == 0); ++ assert(name || namelen == 0); ++ ++ for (size_t s = 0; s < scount; s++) ++ if (memcmp_nn(sections[s].Name, sizeof(sections[s].Name), name, namelen) == 0) { + if (ret) + *ret = s; +- return 1; ++ return true; + } +- } +- return 0; ++ ++ return false; + } + + static bool is_uki(struct PeSectionHeader *sections, size_t scount) { +- return (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), NULL) && ++ assert(sections || scount == 0); ++ ++ return ++ find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), NULL) && + find_pe_section(sections, scount, name_linux, sizeof(name_linux), NULL) && +- find_pe_section(sections, scount, name_initrd, sizeof(name_initrd), NULL)); ++ find_pe_section(sections, scount, name_initrd, sizeof(name_initrd), NULL); + } + + int verb_kernel_identify(int argc, char *argv[], void *userdata) { + _cleanup_fclose_ FILE *uki = NULL; + _cleanup_free_ struct PeSectionHeader *sections = NULL; + size_t scount; +- int rc; ++ int r; + + uki = fopen(argv[1], "re"); + if (!uki) + return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]); + +- rc = pe_sections(uki, §ions, &scount); +- if (rc < 0) +- return EXIT_FAILURE; ++ r = pe_sections(uki, §ions, &scount); ++ if (r < 0) ++ return r; + +- if (sections) { +- if (is_uki(sections, scount)) { +- puts("uki"); +- return EXIT_SUCCESS; +- } ++ if (!sections) ++ puts("unknown"); ++ else if (is_uki(sections, scount)) ++ puts("uki"); ++ else + puts("pe"); +- return EXIT_SUCCESS; +- } + +- puts("unknown"); + return EXIT_SUCCESS; + } + +-static int read_pe_section(FILE *uki, const struct PeSectionHeader *section, +- void **ret, size_t *ret_n) { ++static int read_pe_section( ++ FILE *uki, ++ const struct PeSectionHeader *section, ++ void **ret, ++ size_t *ret_n) { ++ + _cleanup_free_ void *data = NULL; + uint32_t size, bytes; + uint64_t soff; +- int rc; ++ ++ assert(uki); ++ assert(section); ++ assert(ret); + + soff = le32toh(section->PointerToRawData); + size = le32toh(section->VirtualSize); +@@ -124,8 +140,7 @@ static int read_pe_section(FILE *uki, const struct PeSectionHeader *section, + if (size > 16 * 1024) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "PE section too big"); + +- rc = fseek(uki, soff, SEEK_SET); +- if (rc < 0) ++ if (fseek(uki, soff, SEEK_SET) < 0) + return log_error_errno(errno, "seek to PE section"); + + data = malloc(size+1); +@@ -169,6 +184,9 @@ static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scou + _cleanup_free_ char *osrel = NULL; + size_t osrel_size, idx; + ++ assert(uki); ++ assert(sections || scount == 0); ++ + if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx)) + read_pe_section(uki, sections + idx, (void**)&cmdline, NULL); + +@@ -190,26 +208,23 @@ int verb_kernel_inspect(int argc, char *argv[], void *userdata) { + _cleanup_fclose_ FILE *uki = NULL; + _cleanup_free_ struct PeSectionHeader *sections = NULL; + size_t scount; +- int rc; ++ int r; + + uki = fopen(argv[1], "re"); + if (!uki) + return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]); + +- rc = pe_sections(uki, §ions, &scount); +- if (rc < 0) +- return EXIT_FAILURE; ++ r = pe_sections(uki, §ions, &scount); ++ if (r < 0) ++ return r; + +- if (sections) { +- if (is_uki(sections, scount)) { +- puts("Kernel Type: uki"); +- inspect_uki(uki, sections, scount); +- return EXIT_SUCCESS; +- } ++ if (!sections) ++ puts("Kernel Type: unknown"); ++ else if (is_uki(sections, scount)) { ++ puts("Kernel Type: uki"); ++ inspect_uki(uki, sections, scount); ++ } else + puts("Kernel Type: pe"); +- return EXIT_SUCCESS; +- } + +- puts("Kernel Type: unknown"); + return EXIT_SUCCESS; + } diff --git a/0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch b/0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch new file mode 100644 index 0000000..bd3ff4f --- /dev/null +++ b/0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch @@ -0,0 +1,171 @@ +From a83545ba5d0cc1b29f2bb4e2b794ce14b138e3fe Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 23 Jan 2023 12:28:38 +0100 +Subject: [PATCH] tree-wide: unify how we pick OS pretty name to display + +(cherry picked from commit 02b7005e38db756711cd6463bda34e93cf304c3c) + +Related: RHEL-16354 +--- + src/analyze/analyze-plot.c | 3 ++- + src/basic/os-util.c | 9 +++++++++ + src/basic/os-util.h | 2 ++ + src/core/main.c | 2 +- + src/firstboot/firstboot.c | 5 +++-- + src/hostname/hostnamed.c | 7 ++++++- + src/journal-remote/journal-gatewayd.c | 9 ++++++--- + 7 files changed, 29 insertions(+), 8 deletions(-) + +diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c +index 24f4add099..8aca691b3d 100644 +--- a/src/analyze/analyze-plot.c ++++ b/src/analyze/analyze-plot.c +@@ -6,6 +6,7 @@ + #include "bus-error.h" + #include "bus-map-properties.h" + #include "format-table.h" ++#include "os-util.h" + #include "sort-util.h" + #include "version.h" + +@@ -283,7 +284,7 @@ static int produce_plot_as_svg( + svg("%s", pretty_times); + if (host) + svg("%s %s (%s %s %s) %s %s", +- isempty(host->os_pretty_name) ? "Linux" : host->os_pretty_name, ++ os_release_pretty_name(host->os_pretty_name, NULL), + strempty(host->hostname), + strempty(host->kernel_name), + strempty(host->kernel_release), +diff --git a/src/basic/os-util.c b/src/basic/os-util.c +index 8f8bb0881e..66cbfc76a5 100644 +--- a/src/basic/os-util.c ++++ b/src/basic/os-util.c +@@ -370,3 +370,12 @@ int os_release_support_ended(const char *support_end, bool quiet) { + usec_t ts = now(CLOCK_REALTIME); + return DIV_ROUND_UP(ts, USEC_PER_SEC) > (usec_t) eol; + } ++ ++const char *os_release_pretty_name(const char *pretty_name, const char *name) { ++ /* Distills a "pretty" name to show from os-release data. First argument is supposed to be the ++ * PRETTY_NAME= field, the second one the NAME= field. This function is trivial, of course, and ++ * exists mostly to ensure we use the same logic wherever possible. */ ++ ++ return empty_to_null(pretty_name) ?: ++ empty_to_null(name) ?: "Linux"; ++} +diff --git a/src/basic/os-util.h b/src/basic/os-util.h +index d22f5ab8e7..1239f6f6b7 100644 +--- a/src/basic/os-util.h ++++ b/src/basic/os-util.h +@@ -33,3 +33,5 @@ int load_os_release_pairs(const char *root, char ***ret); + int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret); + + int os_release_support_ended(const char *support_end, bool quiet); ++ ++const char *os_release_pretty_name(const char *pretty_name, const char *name); +diff --git a/src/core/main.c b/src/core/main.c +index d3ec526e7e..0e2e5448bb 100644 +--- a/src/core/main.c ++++ b/src/core/main.c +@@ -1339,7 +1339,7 @@ static int os_release_status(void) { + return log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + +- const char *label = empty_to_null(pretty_name) ?: empty_to_null(name) ?: "Linux"; ++ const char *label = os_release_pretty_name(pretty_name, name); + + if (show_status_on(arg_show_status)) { + if (log_get_show_color()) +diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c +index 63db78b52d..10eda1d302 100644 +--- a/src/firstboot/firstboot.c ++++ b/src/firstboot/firstboot.c +@@ -96,7 +96,7 @@ static bool press_any_key(void) { + } + + static void print_welcome(void) { +- _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; ++ _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL; + static bool done = false; + const char *pn, *ac; + int r; +@@ -110,12 +110,13 @@ static void print_welcome(void) { + r = parse_os_release( + arg_root, + "PRETTY_NAME", &pretty_name, ++ "NAME", &os_name, + "ANSI_COLOR", &ansi_color); + if (r < 0) + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + +- pn = isempty(pretty_name) ? "Linux" : pretty_name; ++ pn = os_release_pretty_name(pretty_name, os_name); + ac = isempty(ansi_color) ? "0" : ansi_color; + + if (colors_enabled()) +diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c +index 486b093062..2a675caada 100644 +--- a/src/hostname/hostnamed.c ++++ b/src/hostname/hostnamed.c +@@ -147,6 +147,7 @@ static void context_read_machine_info(Context *c) { + } + + static void context_read_os_release(Context *c) { ++ _cleanup_free_ char *os_name = NULL, *os_pretty_name = NULL; + struct stat current_stat = {}; + int r; + +@@ -163,12 +164,16 @@ static void context_read_os_release(Context *c) { + (UINT64_C(1) << PROP_OS_HOME_URL)); + + r = parse_os_release(NULL, +- "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME], ++ "PRETTY_NAME", &os_pretty_name, ++ "NAME", &os_name, + "CPE_NAME", &c->data[PROP_OS_CPE_NAME], + "HOME_URL", &c->data[PROP_OS_HOME_URL]); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read os-release file, ignoring: %m"); + ++ if (free_and_strdup(&c->data[PROP_OS_PRETTY_NAME], os_release_pretty_name(os_pretty_name, os_name)) < 0) ++ log_oom(); ++ + c->etc_os_release_stat = current_stat; + } + +diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c +index 34def4670e..e848ae5026 100644 +--- a/src/journal-remote/journal-gatewayd.c ++++ b/src/journal-remote/journal-gatewayd.c +@@ -732,7 +732,7 @@ static int request_handler_machine( + _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL; + RequestMeta *m = ASSERT_PTR(connection_cls); + int r; +- _cleanup_free_ char* hostname = NULL, *os_name = NULL; ++ _cleanup_free_ char* hostname = NULL, *pretty_name = NULL, *os_name = NULL; + uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0; + sd_id128_t mid, bid; + _cleanup_free_ char *v = NULL, *json = NULL; +@@ -763,7 +763,10 @@ static int request_handler_machine( + if (r < 0) + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m"); + +- (void) parse_os_release(NULL, "PRETTY_NAME", &os_name); ++ (void) parse_os_release( ++ NULL, ++ "PRETTY_NAME", &pretty_name, ++ "NAME=", &os_name); + (void) get_virtualization(&v); + + r = asprintf(&json, +@@ -778,7 +781,7 @@ static int request_handler_machine( + SD_ID128_FORMAT_VAL(mid), + SD_ID128_FORMAT_VAL(bid), + hostname_cleanup(hostname), +- os_name ? os_name : "Linux", ++ os_release_pretty_name(pretty_name, os_name), + v ? v : "bare", + usage, + cutoff_from, diff --git a/0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch b/0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch new file mode 100644 index 0000000..1397d76 --- /dev/null +++ b/0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch @@ -0,0 +1,102 @@ +From 5374c8fb7c32e79f9dcd24333e5c117bd8963a1a Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 25 Jan 2023 11:05:46 +0900 +Subject: [PATCH] bootctl-uki: several follow-ups for inspect_osrel() + +Follow-ups for #26124 and #26158. + +- use os_release_pretty_name(), +- constify the buffer passed to inspect_osrel(), +- propagate errors in inspect_osrele(), and ignore them in the caller + side, +- and several coding style fixlets. + +(cherry picked from commit 1b7586df976d7b033b4901a099337d83578bb8f1) + +Related: RHEL-16354 +--- + src/boot/bootctl-uki.c | 36 ++++++++++++++++++++---------------- + 1 file changed, 20 insertions(+), 16 deletions(-) + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index 3492fa548f..fd249c43fb 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -3,6 +3,7 @@ + #include "bootctl-uki.h" + #include "env-file.h" + #include "fd-util.h" ++#include "os-util.h" + #include "parse-util.h" + #include "pe-header.h" + +@@ -158,50 +159,53 @@ static int read_pe_section( + return 0; + } + +-static void inspect_osrel(char *osrel, size_t osrel_size) { ++static int inspect_osrel(const void *osrel, size_t osrel_size) { + _cleanup_fclose_ FILE *s = NULL; + _cleanup_free_ char *pname = NULL, *name = NULL; + int r; + +- assert(osrel); +- s = fmemopen(osrel, osrel_size, "r"); ++ assert(osrel || osrel_size == 0); ++ ++ if (!osrel) ++ return 0; ++ ++ s = fmemopen((void*) osrel, osrel_size, "r"); + if (!s) +- return (void) log_warning_errno(errno, "Failed to open embedded os-release file, ignoring: %m"); ++ return log_warning_errno(errno, "Failed to open embedded os-release file, ignoring: %m"); + + r = parse_env_file(s, NULL, + "PRETTY_NAME", &pname, + "NAME", &name); + if (r < 0) +- return (void) log_warning_errno(r, "Failed to parse embedded os-release file, ignoring: %m"); ++ return log_warning_errno(r, "Failed to parse embedded os-release file, ignoring: %m"); + +- if (pname || name) +- printf(" OS: %s\n", pname ?: name); ++ printf(" OS: %s\n", os_release_pretty_name(pname, name)); ++ return 0; + } + + static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scount) { +- _cleanup_free_ char *cmdline = NULL; +- _cleanup_free_ char *uname = NULL; +- _cleanup_free_ char *osrel = NULL; +- size_t osrel_size, idx; ++ _cleanup_free_ char *cmdline = NULL, *uname = NULL; ++ _cleanup_free_ void *osrel = NULL; ++ size_t osrel_size = 0, idx; + + assert(uki); + assert(sections || scount == 0); + + if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx)) +- read_pe_section(uki, sections + idx, (void**)&cmdline, NULL); ++ read_pe_section(uki, sections + idx, (void**) &cmdline, NULL); + + if (find_pe_section(sections, scount, name_uname, sizeof(name_uname), &idx)) +- read_pe_section(uki, sections + idx, (void**)&uname, NULL); ++ read_pe_section(uki, sections + idx, (void**) &uname, NULL); + + if (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), &idx)) +- read_pe_section(uki, sections + idx, (void**)&osrel, &osrel_size); ++ read_pe_section(uki, sections + idx, &osrel, &osrel_size); + + if (cmdline) + printf(" Cmdline: %s\n", cmdline); + if (uname) + printf(" Version: %s\n", uname); +- if (osrel) +- inspect_osrel(osrel, osrel_size); ++ ++ (void) inspect_osrel(osrel, osrel_size); + } + + int verb_kernel_inspect(int argc, char *argv[], void *userdata) { diff --git a/0430-bootctl-Add-missing-m.patch b/0430-bootctl-Add-missing-m.patch new file mode 100644 index 0000000..3775ab1 --- /dev/null +++ b/0430-bootctl-Add-missing-m.patch @@ -0,0 +1,25 @@ +From 47a9030cad80a684b9affcc2c35ef264e7a8b145 Mon Sep 17 00:00:00 2001 +From: Daan De Meyer +Date: Thu, 9 Feb 2023 10:44:35 +0100 +Subject: [PATCH] bootctl: Add missing %m + +(cherry picked from commit 3b42ffe590c5728af50feb138890a44264c4b02e) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 3044f67b5a..a0ca2afec2 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1420,7 +1420,7 @@ static int install_entry_token(void) { + + r = write_string_file("/etc/kernel/entry-token", arg_entry_token, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) +- return log_error_errno(r, "Failed to write entry token '%s' to /etc/kernel/entry-token", arg_entry_token); ++ return log_error_errno(r, "Failed to write entry token '%s' to /etc/kernel/entry-token: %m", arg_entry_token); + + return 0; + } diff --git a/0431-bootctl-tweak-DOS-header-magic-check.patch b/0431-bootctl-tweak-DOS-header-magic-check.patch new file mode 100644 index 0000000..72c6840 --- /dev/null +++ b/0431-bootctl-tweak-DOS-header-magic-check.patch @@ -0,0 +1,41 @@ +From 618e38b8b775f45c0a18975ae33753b92c954092 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Tue, 24 Jan 2023 10:28:25 +0100 +Subject: [PATCH] bootctl: tweak DOS header magic check + +Read the magic first, try reading the full DOS exe header only in case +the magic check succeeds. + +This avoids throwing an header read error on small dummy files as used +by test-kernel-install. + +(cherry picked from commit 78088b8f43717a43661cd2c1627a9860904c4794) + +Related: RHEL-16354 +--- + src/boot/bootctl-uki.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index fd249c43fb..d90a850b1c 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -30,11 +30,16 @@ static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + assert(ret_n); + + items = fread(&dos, 1, sizeof(dos), uki); +- if (items != sizeof(dos)) +- return log_error_errno(SYNTHETIC_ERRNO(EIO), "DOS header read error"); ++ if (items < sizeof(dos.Magic)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "File is smaller than DOS magic (got %"PRIu64" of %zu bytes)", ++ items, sizeof(dos.Magic)); + if (memcmp(dos.Magic, dos_file_magic, sizeof(dos_file_magic)) != 0) + goto no_sections; + ++ if (items != sizeof(dos)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "File is smaller than DOS header (got %"PRIu64" of %zu bytes)", ++ items, sizeof(dos)); ++ + if (fseek(uki, le32toh(dos.ExeHeader), SEEK_SET) < 0) + return log_error_errno(errno, "seek to PE header"); + diff --git a/systemd.spec b/systemd.spec index 301a82c..e15e104 100644 --- a/systemd.spec +++ b/systemd.spec @@ -21,7 +21,7 @@ Name: systemd Url: https://systemd.io Version: 252 -Release: 19%{?dist} +Release: 20%{?dist} # For a breakdown of the licensing, see README License: LGPLv2+ and MIT and GPLv2+ Summary: System and Service Manager @@ -460,6 +460,55 @@ Patch0379: 0379-ci-add-missing-permissions.patch Patch0380: 0380-ci-permissions-write-all.patch Patch0381: 0381-ci-lint-exclude-.in-files-from-ShellCheck-lint.patch Patch0382: 0382-udev-raise-RLIMIT_NOFILE-as-high-as-we-can.patch +Patch0383: 0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch +Patch0384: 0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch +Patch0385: 0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch +Patch0386: 0386-udev-attempt-device-rename-even-if-interface-is-up.patch +Patch0387: 0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch +Patch0388: 0388-test-network-add-a-test-for-renaming-device-to-curre.patch +Patch0389: 0389-udev-align-table.patch +Patch0390: 0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch +Patch0391: 0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch +Patch0392: 0392-udev-move-device_rename-from-device-private.c.patch +Patch0393: 0393-udev-restore-syspath-and-properties-on-failure.patch +Patch0394: 0394-sd-device-introduce-device_get_property_int.patch +Patch0395: 0395-core-device-downgrade-log-level-for-ignored-errors.patch +Patch0396: 0396-core-device-ignore-failed-uevents.patch +Patch0397: 0397-test-add-tests-for-failure-in-renaming-network-inter.patch +Patch0398: 0398-test-modernize-test-netlink.c.patch +Patch0399: 0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch +Patch0400: 0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch +Patch0401: 0401-udev-make-udev_builtin_run-take-UdevEvent.patch +Patch0402: 0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch +Patch0403: 0403-udev-net-generate-new-network-interface-name-only-on.patch +Patch0404: 0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch +Patch0405: 0405-udev-net-assign-alternative-names-only-on-add-uevent.patch +Patch0406: 0406-test-add-tests-for-renaming-network-interface.patch +Patch0407: 0407-Backport-ukify-from-upstream.patch +Patch0408: 0408-bootctl-make-json-output-normal-json.patch +Patch0409: 0409-test-replace-readfp-with-read_file.patch +Patch0410: 0410-stub-measure-document-and-measure-.uname-UKI-section.patch +Patch0411: 0411-boot-measure-.sbat-section.patch +Patch0412: 0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch +Patch0413: 0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch +Patch0414: 0414-kernel-install-Add-uki-layout.patch +Patch0415: 0415-kernel-install-remove-math-slang-from-man-page.patch +Patch0416: 0416-kernel-install-handle-uki-installs-automatically.patch +Patch0417: 0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch +Patch0418: 0418-kernel-install-Log-location-that-uki-is-installed-in.patch +Patch0419: 0419-bootctl-fix-errno-logging.patch +Patch0420: 0420-bootctl-add-kernel-identity-command.patch +Patch0421: 0421-bootctl-add-kernel-inspect-command.patch +Patch0422: 0422-bootctl-add-kernel-inspect-to-help-text.patch +Patch0423: 0423-bootctl-drop-full-stop-at-end-of-help-texts.patch +Patch0424: 0424-bootctl-change-section-title-for-kernel-image-comman.patch +Patch0425: 0425-bootctl-remove-space-that-should-not-be-there.patch +Patch0426: 0426-bootctl-kernel-inspect-print-os-info.patch +Patch0427: 0427-bootctl-uki-several-coding-style-fixlets.patch +Patch0428: 0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch +Patch0429: 0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch +Patch0430: 0430-bootctl-Add-missing-m.patch +Patch0431: 0431-bootctl-tweak-DOS-header-magic-check.patch # Downstream-only patches (9000–9999) @@ -1281,6 +1330,57 @@ getent passwd systemd-oom &>/dev/null || useradd -r -l -g systemd-oom -d / -s /s %files standalone-sysusers -f .file-list-standalone-sysusers %changelog +* Fri Dec 08 2023 systemd maintenance team - 252-20 +- udev/net: allow new link name as an altname before renaming happens (RHEL-5988) +- sd-netlink: do not swap old name and alternative name (RHEL-5988) +- sd-netlink: restore altname on error in rtnl_set_link_name (RHEL-5988) +- udev: attempt device rename even if interface is up (RHEL-5988) +- sd-netlink: add a test for rtnl_set_link_name() (RHEL-5988) +- test-network: add a test for renaming device to current altname (RHEL-5988) +- udev: align table (RHEL-5988) +- sd-device: make device_set_syspath() clear sysname and sysnum (RHEL-5988) +- sd-device: do not directly access entry in sd-device object (RHEL-5988) +- udev: move device_rename() from device-private.c (RHEL-5988) +- udev: restore syspath and properties on failure (RHEL-5988) +- sd-device: introduce device_get_property_int() (RHEL-5988) +- core/device: downgrade log level for ignored errors (RHEL-5988) +- core/device: ignore failed uevents (RHEL-5988) +- test: add tests for failure in renaming network interface (RHEL-5988) +- test: modernize test-netlink.c (RHEL-5988) +- test-netlink: use dummy interface to test assigning new interface name (RHEL-5988) +- udev: use SYNTHETIC_ERRNO() at one more place (RHEL-5988) +- udev: make udev_builtin_run() take UdevEvent* (RHEL-5988) +- udev/net: verify ID_NET_XYZ before trying to assign it as an alternative name (RHEL-5988) +- udev/net: generate new network interface name only on add uevent (RHEL-5988) +- sd-netlink: make rtnl_set_link_name() optionally append alternative names (RHEL-5988) +- udev/net: assign alternative names only on add uevent (RHEL-5988) +- test: add tests for renaming network interface (RHEL-5988) +- Backport ukify from upstream (RHEL-13199) +- bootctl: make --json output normal json (RHEL-13199) +- test: replace readfp() with read_file() (RHEL-13199) +- stub/measure: document and measure .uname UKI section (RHEL-13199) +- boot: measure .sbat section (RHEL-13199) +- Revert "test_ukify: no stinky root needed for signing" (RHEL-13199) +- ukify: move to /usr/bin and mark as non non-experimental (RHEL-13199) +- kernel-install: Add uki layout (RHEL-16354) +- kernel-install: remove math slang from man page (RHEL-16354) +- kernel-install: handle uki installs automatically (RHEL-16354) +- 90-uki-copy.install: create $BOOT/EFI/Linux directory if needed (RHEL-16354) +- kernel-install: Log location that uki is installed in (RHEL-16354) +- bootctl: fix errno logging (RHEL-16354) +- bootctl: add kernel-identity command (RHEL-16354) +- bootctl: add kernel-inspect command (RHEL-16354) +- bootctl: add kernel-inspect to --help text (RHEL-16354) +- bootctl: drop full stop at end of --help texts (RHEL-16354) +- bootctl: change section title for kernel image commands (RHEL-16354) +- bootctl: remove space that should not be there (RHEL-16354) +- bootctl: kernel-inspect: print os info (RHEL-16354) +- bootctl-uki: several coding style fixlets (RHEL-16354) +- tree-wide: unify how we pick OS pretty name to display (RHEL-16354) +- bootctl-uki: several follow-ups for inspect_osrel() (RHEL-16354) +- bootctl: Add missing %m (RHEL-16354) +- bootctl: tweak DOS header magic check (RHEL-16354) + * Mon Nov 13 2023 systemd maintenance team - 252-19 - ci: Extend source-git-automation (RHEL-1086) - netif-naming-scheme: let's also include rhel8 schemes (RHEL-7026)