Bug#1143444: trixie-pu: package efivar/38-3.1+deb13u1

Christopher Obbard <[email protected]> Sun, 02 Aug 2026 00:02:25 +0100
Newsgroups gmane.linux.debian.devel.release
Message-ID <178562534517.1879187.12296125853693658882.reportbug__11895.8122025774$1785625542$gmane$org@nox>
This is a multi-part MIME message sent by reportbug.


--===============1356331198776715103==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected], [email protected], [email protected], [email protected]
Control: affects -1 + src:efivar
User: [email protected]
Usertags: pu

Dear Stable Release Managers,

I would like to update efivar in trixie to backport a single upstream
patch which enables persistent EFI variable storage on platforms that
lack dedicated firmware-managed variable storage (e.g. ARM64 devices
such as the Qualcomm RB3 Gen 2 Development Kit).

This patch was originally proposed to trixie-backports upload (see
Bug#1142580) which was accepted. In that bug, both Chris Hofstaedtler
and Steve McIntyre suggested this is something which should be fixed in
trixie proper rather than only in backports, so I am following up with
their request. The backport (39-2~bpo13+1) has been in trixie-backports
since 2026-07-22.

[ Reason ]
Embedded and non-x86 platforms often have no firmware-managed store for
EFI variables. The vast majority of these devices (anything booting via
U-Boot) instead keep their EFI variables in a file on the ESP.

To make SetVariable-at-runtime work, U-Boot advertises SetVariableRT in
the RTPROP table and backs it with a memory store so the kernel can read
and write variables through efivarfs as normal. Those writes only land
in RAM and are silently lost on the next reboot. To let the OS persist
them, U-Boot also exposes two read-only variables in the
b2ac5fc9-92b7-4acd-aeac-11e818c3130c namespace:

  RTStorageVolatile - the name of the variable store file, relative to
                      the ESP.
  VarToFile         - a binary dump of the variables that should be
                      preserved.

Upstream commit 68daa04654ac ("efivarfs: Update a file variable store
On SetVariable RT", rhboot/efivar#267) teaches the efivarfs backend to
look for those two variables and, when present, writes the variable
store back out to the file on the ESP after a variable is set or
deleted.

This is not a regression on bookworm as the functionality has never
worked there. It is missing functionality that became much more
visible in trixie, which is the first Debian release to support this
class of hardware in any real way.

The equivalent change is already in unstable and testing via efivar
39-2; see Bug#1102494.

[ Impact ]
Without this update, on affected hardware every EFI variable write from
userspace is silently discarded after a reboot. In practice that means:

 - efibootmgr changes (e.g. boot order or new boot entries) do not
   survive a reboot, so the system cannot be reconfigured to boot
   anything other than what the firmware/U-Boot environment already
   points at.
 - bootctl, grub-install, shim-signed and the d-i/installer bootloader
   steps all appear to succeed and then has no effect.
 - UEFI capsule updates (therefore fwupd firmware updates) cannot work,
   since they depend on SetVariable at runtime persisting.
 - the failures are silent; no errors are reported;, the variable reads
   back correctly for the rest of the boot but resets to default after
   a reboot.

The only workaround available to users today is to manually copy the
variable store to the ESP after every change, e.g.

  dd if=/sys/firmware/efi/efivars/VarToFile-b2ac5fc9-92b7-4acd-aeac-11e818c3130c \
     of=/boot/efi/ubootefi.var skip=4 bs=1

which is not something we can expect users to so; and nothing in
Debian does this automatically.

If the update is not approved, trixie remains effectively unusable for
boot management and firmware updates on this hardware for the lifetime
of the release and the only supported answer is "install the package
from trixie-backports".

[ Tests ]
efivar has no upstream test suite covering the efivarfs backend so
testing has been manual.

 - The identical patch has been in unstable and testing since efivar
   39-2 (uploaded 2025-08-19) and in trixie-backports since
   39-2~bpo13+1 (2026-07-22). No regressions have been reported
   against it in that time.
 - Manually tested on various Qualcomm ARM64 devices running trixie:
   creating and reordering boot entries with efibootmgr now persists
   across a reboot where previously the changes were lost.
 - Manually tested on amd64 with normal firmware-managed variable
   storage to confirm there is no behaviour change: RTStorageVolatile
   does not exist there so the new code returns immediately and
   efibootmgr/fwupd behave exactly as before.
 - The package builds on all release architectures and the salsa-ci
   pipeline (including lintian, build and reproducibility jobs) passes.

[ Risks ]
The change is small and self-contained: it touches only src/efivarfs.c,
adding 153 lines and removing 4 (the removal is purely moving an
existing macro definition earlier in the file so the new code can use
it). No public API, ABI or packaging changes are involved.

The main mitigating factor is that all of the new code is gated on
reading the RTStorageVolatile variable from the
b2ac5fc9-92b7-4acd-aeac-11e818c3130c namespace. On any system that does
not use a file-backed variable store that variable does not exist,
get_esp_filename() returns an error and efivarfs_set_variable() and
efivarfs_del_variable() behave exactly as they do today. The blast
radius is therefore limited to the platforms the change is intended to
fix.

For completeness, the risks I am aware of:

 - On affected platforms, every variable set/delete now additionally
   rewrites the whole variable store file on the ESP. This is more I/O
   than before and the write is not atomic so an badly-timed power loss
   during a variable write could leave a truncated store file. This is
   also part of the upstream design and is the same behaviour already
   shipping in testing/unstable and trixie-backports.
 - The new write_file() helper calls exit(1) on some error paths rather
   than returning an error to the caller, which is not ideal for a
   library. Again, this is only reachable on platforms that have the
   U-Boot variables and is unchanged from the version in
   testing/unstable.

The alternative to taking this upsate is leaving the fix in
trixie-backports only; which is what prompted the discussion in
#1142580.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
The proposed 38-3.1+deb13u1 contains exactly three things:

 1. debian/patches/0003-efivarfs-Update-a-file-variable-store-On-SetVariable.patch
    New patch. A byte-for-byte copy of the patch shipped in unstable
    and testing as
    debian/patches/0002-efivarfs-Update-a-file-variable-store-On-SetVariable.patch
    in efivar 39-2, which is upstream commit 68daa04654ac. It applies
    to 38 unchanged. It:
      - defines the RTStorageVolatile name and the
        b2ac5fc9-92b7-4acd-aeac-11e818c3130c GUID, and a list of the
        usual ESP mount points (/boot/efi/, /boot/, /efi/);
      - adds get_esp_filename() to read the store filename out of
        RTStorageVolatile, and get_esp_filepath() to locate that file
        under one of the ESP mount points;
      - adds write_file(), which copies the contents of the VarToFile
        variable (skipping its 4-byte size prefix) over that file;
      - moves the existing make_efivarfs_path() macro earlier in the
        file so the above can use it;
      - calls the new efi_update_var_file() at the end of
        efivarfs_set_variable() and efivarfs_del_variable().
    All of this is a no-op when RTStorageVolatile is absent.

 2. debian/patches/series
    Adds the one line for the new patch.

 3. debian/changelog
    The new 38-3.1+deb13u1 entry.

There are no other changes: no build system, packaging, symbols or
maintainer script changes. The full debdiff against 38-3.1 in trixie is
attached.

[ Other info ]
The packaging change is prepared on the `trixie` branch of the efivar
Salsa repository at https://salsa.debian.org/efi-team/efivar and I have
discussed the intent with the maintainers in #1142580. I am happy to make
any changes you would like before uploading and to follow this up for the
lifetime of trixie.

Some background on why I care about this: I work at Qualcomm and part
of my job is making Debian better on Qualcomm hardware. We maintain
downstream customised Debian image builds for Qualcomm hardware
(https://github.com/qualcomm-linux/qcom-deb-images) and are working to
reduce the delta against Debian and other upstream projects over the
long term. This change is one of those deltas: with it, stock trixie
can manage its own boot configuration on these machines.

Related bugs and links:
  #1142580 - efivar: intent to maintain a backport of efivar for
             trixie-backports (where the point release was suggested)
  #1102494 - the underlying issue, fixed in unstable by efivar 39-2
  https://github.com/rhboot/efivar/pull/267 - the upstream PR


Cheers!

Christopher Obbard

--===============1356331198776715103==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="efivar_38-3.1+deb13u1.debdiff"

diff -Nru efivar-38/debian/changelog efivar-38/debian/changelog
--- efivar-38/debian/changelog	2024-02-28 02:57:40.000000000 +0000
+++ efivar-38/debian/changelog	2026-08-01 23:20:32.000000000 +0100
@@ -1,3 +1,9 @@
+efivar (38-3.1+deb13u1) trixie; urgency=medium
+
+  * d/patches: backport efivarfs SetVariable at runtime support
+
+ -- Christopher Obbard <[email protected]>  Sat, 01 Aug 2026 23:20:32 +0100
+
 efivar (38-3.1) unstable; urgency=medium
 
   * Non-maintainer upload.
diff -Nru efivar-38/debian/patches/0003-efivarfs-Update-a-file-variable-store-On-SetVariable.patch efivar-38/debian/patches/0003-efivarfs-Update-a-file-variable-store-On-SetVariable.patch
--- efivar-38/debian/patches/0003-efivarfs-Update-a-file-variable-store-On-SetVariable.patch	1970-01-01 01:00:00.000000000 +0100
+++ efivar-38/debian/patches/0003-efivarfs-Update-a-file-variable-store-On-SetVariable.patch	2026-08-01 23:11:02.000000000 +0100
@@ -0,0 +1,240 @@
+From 68daa04654acbe1bbaa17ebfc23c371b39e69c6b Mon Sep 17 00:00:00 2001
+From: Ilias Apalodimas <[email protected]>
+Date: Wed, 18 Jun 2025 22:37:04 +0300
+Subject: [PATCH] efivarfs: Update a file variable store On SetVariable RT
+
+Embedded boards have hardware limitations when storing and managing EFI
+variables. Some hardware comes with an eMMC & an RPMB partition which they
+use to store the EFI variables securely. However, the vast majority of
+boards (using U-Boot), stores the EFI variables in a file in the ESP.
+
+This has a few limitations
+- UEFI secure boot cannot be enabled as it can be very easily
+  overridden
+- SetVariable at runtime is impossible to support
+
+Distros and capsule updates on-disk do rely on the that service though
+and U-Boot does implement a workaround.
+
+U-Boot enables SetVariableRT in the RTPROP table and creates a memory backend,
+so the linux kernel can naturally read and write variables via the efivarfs
+filesystem. Those reads and writes end up in memory though. So they are visible
+while the OS is live and are lost in the event of a reboot.
+
+At the same time it also creates two EFI RO variables.
+RTStorageVolatile -- Holds the filename  the variables are stored relative to
+                     the ESP
+VarToFile -- Holds a binary dump of all the EFI variables that should be
+             preserved (BS, NV, RT).
+
+By using these two variables we can persist the changes after reboots by
+doing
+dd if=/sys/firmware/efi/efivars/VarToFile-b2ac5fc9-92b7-4acd-aeac-11e818c3130c of=/boot/efi/ubootefi.var skip=4 bs=1
+
+So let's plug this functionality into the efivafs backend and enable it
+automatically if those variables are detected.
+
+Signed-off-by: Ilias Apalodimas <[email protected]>
+---
+ src/efivarfs.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 153 insertions(+), 4 deletions(-)
+
+diff --git a/src/efivarfs.c b/src/efivarfs.c
+index 034d6c1..2dea252 100644
+--- a/src/efivarfs.c
++++ b/src/efivarfs.c
+@@ -28,6 +28,24 @@
+ #  define EFIVARFS_MAGIC 0xde5e81e4
+ #endif
+ 
++/*
++ * RTStorageVolatile-b2ac5fc9-92b7-4acd-aeac-11e818c3130c holds the name of
++ * the file we need to update relative to the ESP
++ */
++#define NAME_RTSV	"RTStorageVolatile"
++/*
++ * Namespace of the special EFI variables pointing to the file and data we
++ * need to update
++ */
++#define GUID_FILE_STORE_VARS \
++	EFI_GUID(0xB2AC5FC9,0x92B7,0x4ACD,0xAEAC,0x11,0xE8,0x18,0xC3,0x13,0x0C)
++
++static const char *esp_paths[] = {
++	"/boot/efi/",
++	"/boot/",
++	"/efi/"
++};
++
+ static char const default_efivarfs_path[] = "/sys/firmware/efi/efivars/";
+ static char *efivarfs_path;
+ 
+@@ -64,6 +82,137 @@ fini_efivarfs_path(void)
+ 	}
+ }
+ 
++static int
++get_esp_filepath(const char *filename, char *filepath, size_t sz)
++{
++	size_t num_paths = sizeof(esp_paths) / sizeof(esp_paths[0]);
++	size_t rc;
++
++	for (size_t i = 0; i < num_paths; ++i) {
++		struct stat buffer;
++
++		rc = snprintf(filepath, sz, "%s%s", esp_paths[i], filename);
++		if (rc >= sz) {
++			fprintf(stderr, "Error: Filepath too big. Max allowed %ld\n", sz);
++			return -1;
++		}
++		if (!stat(filepath, &buffer))
++			return 0;
++	}
++
++	return -1;
++}
++
++static int
++get_esp_filename(char *filename, size_t sz)
++{
++	size_t size;
++	uint32_t attr;
++	uint8_t *data = NULL;
++	int rc = 0;
++
++	rc = efi_get_variable(GUID_FILE_STORE_VARS, NAME_RTSV, &data, &size, &attr);
++	if (rc < 0)
++		/*
++		 * Return an error here so we can bail out and not try to
++		 * write the file
++		 */
++		return rc;
++
++	if (size > sz) {
++		fprintf(stderr, "Error: Filename too big. Max allowed %ld\n", sz);
++		free(data);
++		return -1;
++	}
++
++	memcpy(filename, data, sz);
++	free(data);
++
++	return 0;
++}
++
++#define make_efivarfs_path(str, guid, name) ({				\
++		asprintf(str, "%s%s-" GUID_FORMAT, get_efivarfs_path(),	\
++			name, GUID_FORMAT_ARGS(&(guid)));		\
++	})
++
++static void
++write_file(const char *filepath) {
++	size_t bytes_read;
++	unsigned char buffer[1024];
++	FILE *output_file = NULL;
++	FILE *var2file = NULL;
++	bool fail = false;
++	char *path;
++	int rc;
++
++	rc = make_efivarfs_path(&path, GUID_FILE_STORE_VARS, "VarToFile");
++	if (rc < 0) {
++		efi_error("make_efivarfs_path failed");
++		exit(1);
++	}
++
++	var2file = fopen(path, "rb");
++	if (!var2file) {
++		fprintf(stderr, "Error: Could not open file '%s'\n", path);
++		goto err;
++	}
++
++	output_file = fopen(filepath, "wb");
++	if (!output_file) {
++		fprintf(stderr, "Error: Could not open file '%s'\n", filepath);
++		goto err;
++	}
++
++	if (fread(buffer, 1, 4, var2file) < 4) {
++		fprintf(stderr, "Error: Could not skip first 4 bytes or '%s' file is too small\n", filepath);
++		fail = true;
++		goto err;
++	}
++
++	while ((bytes_read = fread(buffer, 1, sizeof(buffer), var2file)) > 0) {
++		size_t total_written = 0;
++		while (total_written < bytes_read) {
++			size_t written = fwrite(buffer + total_written, 1, bytes_read - total_written, output_file);
++			if (!written) {
++				fprintf(stderr, "Error: Could not write data to ESP '%s' file\n", filepath);
++				fail = true;
++				goto err;
++			}
++			total_written += written;
++		}
++	}
++
++err:
++	if (path)
++		free(path);
++	if (var2file)
++		fclose(var2file);
++	if (output_file)
++		fclose(output_file);
++
++	if (fail)
++		exit(1);
++}
++
++static void
++efi_update_var_file(void)
++{
++	int rc = 0;
++	char filename[PATH_MAX / 4] = { 0 };
++	char filepath[PATH_MAX] = { 0 };
++
++	rc = get_esp_filename(filename, sizeof(filename));
++	if (rc < 0)
++		return;
++
++	rc = get_esp_filepath(filename, filepath, sizeof(filepath));
++	if (!rc)
++		write_file(filepath);
++	else
++		fprintf(stderr, "Error: '%s' file not found in ESP partition. EFI variable changes won't persist reboots\n", filename);
++}
++
+ static int
+ efivarfs_probe(void)
+ {
+@@ -94,10 +243,6 @@ efivarfs_probe(void)
+ 	return 0;
+ }
+ 
+-#define make_efivarfs_path(str, guid, name) ({				\
+-		asprintf(str, "%s%s-" GUID_FORMAT, get_efivarfs_path(),	\
+-			name, GUID_FORMAT_ARGS(&(guid)));		\
+-	})
+ 
+ static int
+ efivarfs_set_fd_immutable(int fd, int immutable)
+@@ -312,6 +457,8 @@ efivarfs_del_variable(efi_guid_t guid, const char *name)
+ 	if (rc < 0)
+ 		efi_error("unlink failed");
+ 
++	efi_update_var_file();
++
+ 	__typeof__(errno) errno_value = errno;
+ 	free(path);
+ 	errno = errno_value;
+@@ -442,6 +589,8 @@ efivarfs_set_variable(efi_guid_t guid, const char *name, const uint8_t *data,
+ 		goto err;
+ 	}
+ 
++	efi_update_var_file();
++
+ 	/* we're done */
+ 	ret = 0;
+ 
+-- 
+2.43.0
+
diff -Nru efivar-38/debian/patches/series efivar-38/debian/patches/series
--- efivar-38/debian/patches/series	2023-11-29 14:23:32.000000000 +0000
+++ efivar-38/debian/patches/series	2026-08-01 23:11:02.000000000 +0100
@@ -1,2 +1,3 @@
 0001-linux-handle-non-ACPI-systems-in-device_get.patch
 0002_no_host_march.patch
+0003-efivarfs-Update-a-file-variable-store-On-SetVariable.patch

--===============1356331198776715103==--