diff --git a/man/systemd-bless-boot.service.xml b/man/systemd-bless-boot.service.xml
index 715ff6f844..69ed69856a 100644
--- a/man/systemd-bless-boot.service.xml
+++ b/man/systemd-bless-boot.service.xml
@@ -45,6 +45,9 @@
left" numeric boot counters from the filename, which indicates to future invocations of the boot loader
that the entry has completed booting successfully at least once. (This service will hence rename the boot
loader entry file or unified kernel image file on the first successful boot.)
+
+ Note that if the system was booted via kexec and LUO is available and enabled, this service will
+ skip marking the boot entry as good or bad, since kexec bypasses the EFI boot loader entirely.
diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c
index c831b5a3c2..24a8df3c7c 100644
--- a/src/bless-boot/bless-boot.c
+++ b/src/bless-boot/bless-boot.c
@@ -3,6 +3,7 @@
#include
#include "sd-json.h"
+#include "sd-varlink.h"
#include "alloc-util.h"
#include "build.h"
@@ -20,6 +21,7 @@
#include "string-util.h"
#include "strv.h"
#include "sync-util.h"
+#include "varlink-util.h"
#include "verbs.h"
#include "virt.h"
@@ -443,6 +445,8 @@ VERB_FULL(verb_set, "indeterminate", NULL, VERB_ANY, 1, 0, STATUS_INDETERMINATE,
"Undo any marking as good or bad");
static int verb_set(int argc, char *argv[], uintptr_t data, void *userdata) {
_cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
+ _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
+ sd_json_variant *reply = NULL;
const char *target = NULL, *source1 = NULL, *source2 = NULL; /* avoid false maybe-uninitialized warning */
uint64_t left, done;
Status status = data;
@@ -454,6 +458,35 @@ static int verb_set(int argc, char *argv[], uintptr_t data, void *userdata) {
if (r < 0)
return r;
+ /* kexec doesn't go through the EFI loaders, so we shouldn't try to mark an image as good/bad.
+ * E.g.: booting the kernel+initrd might work, but the EFI stub might not, and we wouldn't notice */
+ r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.Manager");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to service manager: %m");
+
+ r = varlink_call_and_log(link, "io.systemd.Manager.Describe", /* parameters= */ NULL, &reply);
+ if (r < 0)
+ return r;
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "KExecCount", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, 0, 0 },
+ {},
+ };
+ uint64_t kexec_count = 0;
+
+ r = sd_json_dispatch(
+ sd_json_variant_by_key(reply, "runtime"),
+ dispatch_table,
+ SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS,
+ &kexec_count);
+ if (r < 0)
+ return r;
+
+ if (kexec_count > 0) {
+ log_info("System was booted via kexec, skipping boot entry marking.");
+ return 0;
+ }
+
r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix);
if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */
return log_error_errno(r, "Not booted with boot counting in effect.");
diff --git a/test/units/TEST-91-LIVEUPDATE.sh b/test/units/TEST-91-LIVEUPDATE.sh
index 910018b52a..9b30f3f7f6 100755
--- a/test/units/TEST-91-LIVEUPDATE.sh
+++ b/test/units/TEST-91-LIVEUPDATE.sh
@@ -80,6 +80,13 @@ if grep -qw luo_nboot=1 /proc/cmdline; then
assert_eq "$(busctl -j get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager KExecCount | jq -r '.data')" "1"
+ # Check that bless-boot does not fail after a kexec
+ if [[ -d /sys/firmware/efi ]]; then
+ for verb in good bad indeterminate; do
+ /usr/lib/systemd/systemd-bless-boot --path=/nonexistent "$verb"
+ done
+ fi
+
# The previous boot's shutdown timestamps were preserved across the kexec via LUO and are now exposed
# as the PreviousShutdown* properties (the live Shutdown* ones describe this boot, which is not shutting
# down, so they are unset here).