On 11/5/26 4:33 PM, Jonathan Wiltshire wrote:
Control: tag -1 moreinfo
Hi,
On Thu, Nov 05, 2026 at 12:31:08PM -0600, Mario Limonciello wrote:
[ Changes ]
Upgrade source tree to 1.9.
The d/* changes are:
* d/control: standards version (unstable bumped it)
* d/control: maintainer (my debian.org address)
* d/control: build deps (missing pkgconf)
* d/rules: need new arg for 1.9
* d/changelog: duh
[ Other info ]
I want to do this as a straight rebuild from forky source.
This is because although this vulnerability was flagged; many
others were found earlier this year that weren't issued advisories.
We may still have exposure to issues if we only backport
the fix for this advisory.
To go from 1.7 to 1.9 as a "just for good measure" is quite an ask...
Well I can tell you that there was a lot of changes upstream that were
hypothetical failures that no one "tried" to break yet.
https://github.com/fwupd/fwupd-efi/commit/db830c660735a43501931e7417da8d46ea3aca4c
https://github.com/fwupd/fwupd-efi/commit/f162f8f9b642bb6033af5677b4001c4b9a769838
When binarly tried to break things they had that as a baseline. If we
don't catch up the code to parity then there is a good chance we're
fixing one vulnerability but not the others that were already fixed.
Can you prepare a debdiff between them so at least we can see what the
scope is?
Yes; just attaching it. It's already staged on salsa as well on
debian/trixie branch.
diff -Nru fwupd-efi-1.7/CODE_OF_CONDUCT.md fwupd-efi-1.9/CODE_OF_CONDUCT.md
--- fwupd-efi-1.7/CODE_OF_CONDUCT.md 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/CODE_OF_CONDUCT.md 2026-10-26 04:23:48.000000000 -0500
@@ -34,7 +34,7 @@
## Enforcement
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [email protected]. The project
team will review and investigate all complaints, and will respond in a way that
it deems appropriate to the circumstances. The project team is obligated to
maintain confidentiality with regard to the reporter of an incident. Further
details of specific enforcement policies may be posted separately.
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <[email protected]>. The
project team will review and investigate all complaints, and will respond in a
way that it deems appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an
incident. Further details of specific enforcement policies may be posted
separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
diff -Nru fwupd-efi-1.7/contrib/ci.sh fwupd-efi-1.9/contrib/ci.sh
--- fwupd-efi-1.7/contrib/ci.sh 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/contrib/ci.sh 2026-10-26 04:23:48.000000000 -0500
@@ -7,10 +7,8 @@
# disable the safe directory feature
git config --global safe.directory "*"
-if [ "$OS" = "fedora" ]; then
- sudo dnf install -y \
-
https://kojipkgs.fedoraproject.org//packages/gnu-efi/3.0.18/3.fc40/x86_64/gnu-efi-3.0.18-3.fc40.x86_64.rpm
\
-
https://kojipkgs.fedoraproject.org//packages/gnu-efi/3.0.18/3.fc40/x86_64/gnu-efi-devel-3.0.18-3.fc40.x86_64.rpm
+case "$OS" in
+fedora*)
meson build
VERSION=`meson introspect build --projectinfo | jq -r .version`
RPMVERSION=${VERSION//-/.}
@@ -29,7 +27,8 @@
rpmbuild -ba build/fwupd-efi.spec
mkdir -p dist
cp $HOME/rpmbuild/RPMS/*/*.rpm dist
-elif [ "$OS" = "debian-x86_64" ] || [ "$OS" = "debian-i386" ]; then
+ ;;
+debian*)
export DEBFULLNAME="CI Builder"
export DEBEMAIL="[email protected]"
VERSION=`head meson.build | grep ' version :' | cut -d \' -f2`
@@ -42,7 +41,9 @@
EDITOR=/bin/true dch --create --package fwupd-efi -v $VERSION "CI Build"
debuild --no-lintian --preserve-envvar CI --preserve-envvar CC \
--preserve-envvar QUBES_OPTION
-else
+ ;;
+*)
meson build
ninja -C build
-fi
+ ;;
+esac
diff -Nru fwupd-efi-1.7/contrib/fwupd-efi.spec.in
fwupd-efi-1.9/contrib/fwupd-efi.spec.in
--- fwupd-efi-1.7/contrib/fwupd-efi.spec.in 2024-09-20 08:13:31.000000000
-0500
+++ fwupd-efi-1.9/contrib/fwupd-efi.spec.in 2026-10-26 04:23:48.000000000
-0500
@@ -9,7 +9,7 @@
Release: 0.#BUILD#%{?alphatag}%{?dist}
License: LGPL-2.1-or-later
URL: https://github.com/fwupd/fwupd-efi
-Source0:
https://github.com/fwupd/fwupd-efi/releases/download/%{version}/%{name}-%{version}.tar.xz
+Source0:
https://github.com/fwupd/%{name}/archive/refs/tags/%{name}-%{version}.tar.xz
# these are the only architectures supporting UEFI UpdateCapsule
ExclusiveArch: x86_64 aarch64
@@ -38,6 +38,11 @@
-Defi_sbat_distro_pkgname="%{name}" \
-Defi_sbat_distro_version="%{version}-%{release}" \
-Defi_sbat_distro_url="https://src.fedoraproject.org/rpms/%{name}" \
+ -Dpkgnote="{\"type\":\"rpm\", \
+ \"name\":\"%{name}\", \
+ \"version\":\"%{version}-%{release}\", \
+ \"architecture\":\"%{_arch}\", \
+ \"osCpe\":\"cpe:/o:fwupd:fwupd-efi\"}" \
-Dgenpeimg=disabled
%meson_build
diff -Nru fwupd-efi-1.7/contrib/org.freedesktop.fwupd.efi.metainfo.xml
fwupd-efi-1.9/contrib/org.freedesktop.fwupd.efi.metainfo.xml
--- fwupd-efi-1.7/contrib/org.freedesktop.fwupd.efi.metainfo.xml
2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/contrib/org.freedesktop.fwupd.efi.metainfo.xml
2026-10-26 04:23:48.000000000 -0500
@@ -15,7 +15,7 @@
<p>
The source was imported from the combined fwupd project, and is now
maintained separately to allow fwupd userspace releases and fwupd-efi
- UEFI executable releases to follow a different candence.
+ UEFI executable releases to follow a different cadence.
</p>
</description>
<url type="bugtracker">https://github.com/fwupd/fwupd-efi/issues</url>
@@ -27,6 +27,36 @@
</developer>
<content_rating type="oars-1.0"/>
<releases>
+ <release version="1.9" date="2026-10-26">
+ <description>
+ <p>This release adds the following features:</p>
+ <ul>
+ <li>Add self-test harness with mock EFI headers</li>
+ <li>Add support for .pkgnote</li>
+ <li>Record the version of gnu-efi we built against</li>
+ </ul>
+ <p>This release fixes the following bugs:</p>
+ <ul>
+ <li>Fix block descriptor terminator index when capsules are
skipped</li>
+ <li>Fix device path size limit in update info validation</li>
+ <li>Fix GetVariable call with NULL data size in variable
deletion</li>
+ <li>Fix off-by-one in maximum capsule update count check</li>
+ <li>Fix out-of-bounds write via malicious capsule HeaderSize</li>
+ <li>Keep .dynstr and .dynsym when building the binary</li>
+ <li>Remove support for SHIM_DEBUG to fix BRLY-2026-062</li>
+ </ul>
+ </description>
+ </release>
+ <release version="1.8" date="2025-10-03">
+ <description>
+ <p>This release fixes the following bugs:</p>
+ <ul>
+ <li>Fix build problems with LoongArch and riscv64</li>
+ <li>Use CC_LD configured through meson</li>
+ <li>Use --output-target to define the object output format</li>
+ </ul>
+ </description>
+ </release>
<release version="1.7" date="2024-09-20">
<description>
<p>This release fixes the following bugs:</p>
@@ -85,7 +115,7 @@
<description>
<p>This release fixes the following bugs:</p>
<ul>
- <li>Sleep longer when no updates to process or event of error</li>
+ <li>Sleep longer when no updates to process or in the event of
error</li>
<li>Use the correct ldscript when the local crt0 is used for
SBAT</li>
<li>Use the standard objcopy binary</li>
</ul>
diff -Nru fwupd-efi-1.7/debian/changelog fwupd-efi-1.9/debian/changelog
--- fwupd-efi-1.7/debian/changelog 2024-09-20 23:13:41.000000000 -0500
+++ fwupd-efi-1.9/debian/changelog 2026-11-08 14:28:01.000000000 -0600
@@ -1,3 +1,77 @@
+fwupd-efi (1:1.9-3~deb13u1) trixie; urgency=medium
+
+ * Backport to trixie to fix BRLY-2026-062.
+ (Closes: #1153119)
+
+ -- Mario Limonciello <[email protected]> Sun, 08 Nov 2026 14:28:01 -0600
+
+fwupd-efi (1:1.9-3) unstable; urgency=medium
+
+ * Backport a patch from upstream to help with loong64 FTBFS
+
+ -- Mario Limonciello <[email protected]> Mon, 02 Nov 2026 15:38:45 -0600
+
+fwupd-efi (1:1.9-2) unstable; urgency=medium
+
+ [ Mario Limonciello ]
+ * Backport gcc-fstack-protector.patch from upstream
+
+ [ Fabian Grünbichler ]
+ * pick patch fixing SBAT generation (Closes: #1152861)
+
+ -- Mario Limonciello <[email protected]> Mon, 02 Nov 2026 11:09:20 -0600
+
+fwupd-efi (1:1.9-1) unstable; urgency=medium
+
+ * New upstream version.
+ - Fixes security issue BRLY-2026-062
+ * Rebase and drop upstream patches.
+ * d/control: Update standards version
+
+ -- Mario Limonciello <[email protected]> Mon, 26 Oct 2026 12:04:24 -0500
+
+fwupd-efi (1:1.8-5) unstable; urgency=medium
+
+ * Team upload
+
+ [ Helmut Grohne ]
+ * Fix FTCBFS: (Closes: #1134422)
+ + Build-Depends: pkgconf to discover gnu-efi
+ + Fix detection of the C linker
+
+ -- Bastian Germann <[email protected]> Thu, 23 Apr 2026 13:21:13 +0200
+
+fwupd-efi (1:1.8-4) unstable; urgency=medium
+
+ * Team upload
+ * Prepare cross-compilation (Closes: #1125739)
+
+ -- Bastian Germann <[email protected]> Wed, 21 Jan 2026 00:27:12 +0100
+
+fwupd-efi (1:1.8-3) unstable; urgency=medium
+
+ * Team upload
+ * Revert "Fix FTCBFS: Pass a suitable efi-cc to meson"
+
+ -- Bastian Germann <[email protected]> Thu, 15 Jan 2026 00:12:33 +0100
+
+fwupd-efi (1:1.8-2) unstable; urgency=medium
+
+ * Team upload
+ * Source-only upload (Closes: #1125468)
+
+ [ Helmut Grohne ]
+ * Fix FTCBFS: Pass a suitable efi-cc to meson (Closes: #1008025)
+
+ -- Bastian Germann <[email protected]> Wed, 14 Jan 2026 22:43:19 +0100
+
+fwupd-efi (1:1.8-1) unstable; urgency=medium
+
+ * New upstream version (1.8)
+ * d/control: update my email
+
+ -- Mario Limonciello <[email protected]> Fri, 10 Oct 2025 06:25:20 -0500
+
fwupd-efi (1:1.7-1) unstable; urgency=medium
* New upstream veresion (1.7)
diff -Nru fwupd-efi-1.7/debian/control fwupd-efi-1.9/debian/control
--- fwupd-efi-1.7/debian/control 2024-09-20 23:13:29.000000000 -0500
+++ fwupd-efi-1.9/debian/control 2026-11-02 15:36:48.000000000 -0600
@@ -3,12 +3,13 @@
Maintainer: Debian EFI <[email protected]>
Uploaders: Steve McIntyre <[email protected]>,
Matthias Klumpp <[email protected]>,
- Mario Limonciello <[email protected]>
+ Mario Limonciello <[email protected]>
Build-Depends: debhelper (>= 13),
gnu-efi (>= 3.0.18) [amd64 arm64 armhf i386 riscv64 loong64],
meson,
mingw-w64-tools [amd64 arm64 armhf i386 riscv64 loong64],
-Standards-Version: 4.6.0.1
+ pkgconf,
+Standards-Version: 4.7.4
Section: admin
Homepage: https://github.com/fwupd/fwupd-efi
Vcs-Git: https://salsa.debian.org/efi-team/fwupd-efi.git -b debian/unstable
diff -Nru
fwupd-efi-1.7/debian/patches/Add-efi_sbat_distro_generation-and-fix-mstack-protec.patch
fwupd-efi-1.9/debian/patches/Add-efi_sbat_distro_generation-and-fix-mstack-protec.patch
---
fwupd-efi-1.7/debian/patches/Add-efi_sbat_distro_generation-and-fix-mstack-protec.patch
1969-12-31 18:00:00.000000000 -0600
+++
fwupd-efi-1.9/debian/patches/Add-efi_sbat_distro_generation-and-fix-mstack-protec.patch
2026-11-08 14:27:45.000000000 -0600
@@ -0,0 +1,38 @@
+From 261d4bed75d5284aab588145071a9cacad634890 Mon Sep 17 00:00:00 2001
+From: Mario Limonciello <[email protected]>
+Date: Mon, 2 Nov 2026 14:45:38 -0600
+Subject: [PATCH] Add efi_sbat_distro_generation and fix
+ -mstack-protector-guard=global portability
+
+The EFI build hard-codes -mstack-protector-guard=global which GCC for
+loongarch64 rejects, causing the build to fail. Move this flag into
+cc.get_supported_arguments() so it's only used where the compiler
+accepts it.
+---
+ efi/meson.build | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/efi/meson.build b/efi/meson.build
+index 4acb613..9989d59 100644
+--- a/efi/meson.build
++++ b/efi/meson.build
+@@ -165,7 +165,6 @@ compile_args = ['-Og',
+ '-ffreestanding',
+ '-fno-strict-aliasing',
+ '-fstack-protector-strong',
+- '-mstack-protector-guard=global',
+ '-fno-stack-check',
+ '-fno-merge-all-constants',
+ '-Wsign-compare',
+@@ -178,6 +177,8 @@ compile_args = ['-Og',
+ '-isystem', join_paths(efi_incdir, gnu_efi_path_arch)]
+ # clang only has fno-merge-all-constants
+ compile_args += cc.first_supported_argument('-fno-merge-constants')
++# not supported on all architectures, e.g. loongarch64
++compile_args += cc.get_supported_arguments('-mstack-protector-guard=global')
+ if get_option('werror')
+ compile_args += '-Werror'
+ endif
+--
+2.53.0
+
diff -Nru
fwupd-efi-1.7/debian/patches/Add-efi_sbat_distro_generation-to-set-the-distro-SBA.patch
fwupd-efi-1.9/debian/patches/Add-efi_sbat_distro_generation-to-set-the-distro-SBA.patch
---
fwupd-efi-1.7/debian/patches/Add-efi_sbat_distro_generation-to-set-the-distro-SBA.patch
1969-12-31 18:00:00.000000000 -0600
+++
fwupd-efi-1.9/debian/patches/Add-efi_sbat_distro_generation-to-set-the-distro-SBA.patch
2026-11-08 14:27:45.000000000 -0600
@@ -0,0 +1,91 @@
+From: Egor Ignatov <[email protected]>
+Date: Tue, 27 Oct 2026 12:58:35 +0500
+Subject: Add efi_sbat_distro_generation to set the distro SBAT generation
+
+The distro entry was always passed a generation of 0, which generate_sbat.py
+treated as unset and replaced with the fwupd-efi generation. Bumping
+efi_sbat_fwupd_generation therefore also bumped every distro entry to 2.
+
+SBAT keeps the two generations independent: the distro one only goes up for
+distro-specific fixes and must never go back down, even when the global
+one is bumped.
+
+Distributions that have already shipped binaries with the distro entry at 2
+must set -Defi_sbat_distro_generation=2 to keep the number monotonic.
+
+See https://github.com/rhboot/shim/blob/main/SBAT.md
+---
+ README.md | 13 +++++++++++++
+ efi/generate_sbat.py | 4 ++--
+ efi/meson.build | 2 +-
+ meson_options.txt | 1 +
+ 4 files changed, 17 insertions(+), 3 deletions(-)
+
+diff --git a/README.md b/README.md
+index 343ff57..3121a12 100644
+--- a/README.md
++++ b/README.md
+@@ -74,3 +74,16 @@ Typically, this will be set as part of the packager build
script, e.g.
+ -Defi_sbat_distro_version="%{version}" \
+ -Defi_sbat_distro_url="https://src.fedoraproject.org/rpms/%{name}" \
+ ```
++
++The generation number of the distribution-specific SBAT entry defaults to 1
and
++is independent of the `fwupd-efi` generation. It is only incremented when a
++distribution-specific security fix requires revoking older builds of that
++distribution, and like the global generation it must never be decreased or
++reset, even when the `fwupd-efi` generation is bumped, e.g.
++
++```meson
++ -Defi_sbat_distro_generation=2 \
++```
++
++Distributions that have already shipped signed binaries with a distribution
++generation higher than 1 must set this option to keep the number monotonic.
+diff --git a/efi/generate_sbat.py b/efi/generate_sbat.py
+index b9b80ac..9209802 100755
+--- a/efi/generate_sbat.py
++++ b/efi/generate_sbat.py
+@@ -57,7 +57,7 @@ def _generate_sbat(args):
+ "{0}.{1},{2},{3},{4},{5},{6}\n".format(
+ args.project_name,
+ args.sbat_distro_id,
+- args.sbat_distro_generation or args.sbat_generation,
++ args.sbat_distro_generation,
+ args.sbat_distro_summary or FWUPD_SUMMARY,
+ args.sbat_distro_pkgname,
+ args.sbat_distro_version or args.project_version,
+@@ -99,7 +99,7 @@ if __name__ == "__main__":
+ parser.add_argument("--sbat-distro-id", default=None, help="SBAT
distribution ID")
+ parser.add_argument(
+ "--sbat-distro-generation",
+- default=None,
++ default=1,
+ type=int,
+ help="SBAT distribution generation",
+ )
+diff --git a/efi/meson.build b/efi/meson.build
+index bde4346..2664658 100644
+--- a/efi/meson.build
++++ b/efi/meson.build
+@@ -271,7 +271,7 @@ o_files += custom_target('fwup-sbat.o',
+ '--sbat-version', '1',
+ '--sbat-generation',
'@0@'.format(get_option('efi_sbat_fwupd_generation')),
+ '--sbat-distro-id',
get_option('efi_sbat_distro_id'),
+- '--sbat-distro-generation', '0',
++ '--sbat-distro-generation',
'@0@'.format(get_option('efi_sbat_distro_generation')),
+ '--sbat-distro-summary',
get_option('efi_sbat_distro_summary'),
+ '--sbat-distro-pkgname',
get_option('efi_sbat_distro_pkgname'),
+ '--sbat-distro-version',
get_option('efi_sbat_distro_version'),
+diff --git a/meson_options.txt b/meson_options.txt
+index ec18f8e..8cc0781 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -2,6 +2,7 @@ option('efi-libdir', type : 'string', description : 'path to
the EFI lib directo
+ option('efi-ldsdir', type : 'string', description : 'path to the EFI lds
directory')
+ option('efi-includedir', type : 'string', value : '/usr/include/efi',
description : 'path to the EFI header directory')
+ option('efi_sbat_fwupd_generation', type : 'integer', value : 2, description
: 'SBAT fwupd generation')
++option('efi_sbat_distro_generation', type : 'integer', value : 1, description
: 'SBAT distribution generation')
+ option('efi_sbat_distro_id', type : 'string', value : '', description : 'SBAT
distribution ID, e.g. fedora')
+ option('efi_sbat_distro_summary', type : 'string', value : '', description :
'SBAT distribution summary, e.g. Fedora')
+ option('efi_sbat_distro_pkgname', type : 'string', value : '', description :
'SBAT distribution package name, e.g. fwupd')
diff -Nru fwupd-efi-1.7/debian/patches/cross.patch
fwupd-efi-1.9/debian/patches/cross.patch
--- fwupd-efi-1.7/debian/patches/cross.patch 1969-12-31 18:00:00.000000000
-0600
+++ fwupd-efi-1.9/debian/patches/cross.patch 2026-11-02 15:36:48.000000000
-0600
@@ -0,0 +1,18 @@
+From: Bastian Germann <[email protected]>
+Date: Fri, 17 Apr 2026 13:05:00 +0200
+Subject: fix cross-compilation linker detection
+
+---
+Index: fwupd-efi/meson.build
+===================================================================
+--- fwupd-efi.orig/meson.build
++++ fwupd-efi/meson.build
+@@ -11,7 +11,7 @@ conf = configuration_data()
+ conf.set_quoted('PACKAGE_VERSION', meson.project_version())
+
+ cc = meson.get_compiler('c')
+-cc_ld = find_program(cc.get_linker_id())
++cc_ld = run_command(cc.cmd_array(), '-print-prog-name=ld', check:
true).stdout().strip()
+ objcopy = find_program('objcopy')
+ objcopy_version = run_command(objcopy, '--version', check:
true).stdout().split('\n')[0].split(' ')[-1]
+
diff -Nru fwupd-efi-1.7/debian/patches/gcc-fstack-protector.patch
fwupd-efi-1.9/debian/patches/gcc-fstack-protector.patch
--- fwupd-efi-1.7/debian/patches/gcc-fstack-protector.patch 1969-12-31
18:00:00.000000000 -0600
+++ fwupd-efi-1.9/debian/patches/gcc-fstack-protector.patch 2026-11-08
14:27:45.000000000 -0600
@@ -0,0 +1,319 @@
+From cba330256af9ac0b8664ffbb90bf4bec200c40d5 Mon Sep 17 00:00:00 2001
+From: Peter Jones <[email protected]>
+Date: Fri, 23 Oct 2026 13:24:51 -0400
+Subject: [PATCH] Implement support for `gcc -fstack-protector`
+
+This implements support for building fwupdate.efi with GCC's Stack
+Protector feature, which is also enabled in the meson build.
+
+Signed-off-by: Peter Jones <[email protected]>
+---
+ efi/efi-mock-funcs.h | 7 ++++
+ efi/efi-mock.h | 5 +++
+ efi/fwup-common.h | 19 +++++++++
+ efi/fwup-efi.h | 2 +
+ efi/fwup-fuzz-capsule.c | 1 +
+ efi/fwup-fuzz-update-info.c | 1 +
+ efi/fwup-selftest.c | 13 ++++++
+ efi/fwupdate.c | 19 ++++++---
+ efi/meson.build | 8 +++-
+ efi/stack-protector.c | 82 +++++++++++++++++++++++++++++++++++++
+ 10 files changed, 151 insertions(+), 6 deletions(-)
+ create mode 100644 efi/stack-protector.c
+
+diff --git a/efi/efi-mock-funcs.h b/efi/efi-mock-funcs.h
+index bc29579..676ca14 100644
+--- a/efi/efi-mock-funcs.h
++++ b/efi/efi-mock-funcs.h
+@@ -23,6 +23,13 @@ EFI_RUNTIME_SERVICES *RT = &mock_rt;
+ #include "fwup-common.c"
+ #include "fwupdate.c"
+
++static EFI_STATUS
++mock_exit(EFI_HANDLE image_handle, EFI_STATUS exit_status,
++ UINTN exit_data_size, CHAR16 *exit_data)
++{
++ exit(exit_status);
++}
++
+ static EFI_STATUS
+ mock_stall(UINTN microseconds)
+ {
+diff --git a/efi/efi-mock.h b/efi/efi-mock.h
+index 5c3919c..4801a9e 100644
+--- a/efi/efi-mock.h
++++ b/efi/efi-mock.h
+@@ -213,6 +213,10 @@ typedef struct {
+ UINT64 Signature;
+ } EFI_TABLE_HEADER;
+
++typedef EFI_STATUS (*EFI_EXIT)(EFI_HANDLE image_handle,
++ EFI_STATUS exit_status,
++ UINTN exit_data_size,
++ CHAR16 *exit_data);
+ typedef EFI_STATUS (*EFI_STALL)(UINTN microseconds);
+ typedef EFI_STATUS (*EFI_ALLOCATE_PAGES)(EFI_ALLOCATE_TYPE type,
+ EFI_MEMORY_TYPE mem_type,
+@@ -273,6 +277,7 @@ typedef struct {
+ VOID *OpenProtocolInformation;
+ VOID *ProtocolsPerHandle;
+ VOID *LocateHandleBuffer2;
++ EFI_EXIT Exit;
+ EFI_STALL Stall;
+ } EFI_BOOT_SERVICES;
+
+diff --git a/efi/fwup-common.h b/efi/fwup-common.h
+index e4c8b77..00e5e36 100644
+--- a/efi/fwup-common.h
++++ b/efi/fwup-common.h
+@@ -21,3 +21,22 @@ VOID *fwup_malloc0 (UINTN
size);
+
+ #define fwup_new(struct_type, n)
((struct_type*)fwup_malloc((n)*sizeof(struct_type)))
+ #define fwup_new0(struct_type, n)
((struct_type*)fwup_malloc0((n)*sizeof(struct_type)))
++
++extern uintptr_t __stack_chk_guard;
++extern uintptr_t stack_protector_init(void);
++
++/*
++ * This call must only be called from functions that do not return. That
++ * is, the caller must call BS->Exit() to end the task rather than
++ * returning, or else the stack protector will trigger on the function
++ * postamble.
++ */
++static inline __attribute__((__always_inline__)) void
++update_stack_guard(void)
++{
++ uintptr_t guard;
++
++ guard = stack_protector_init();
++ if (guard)
++ __stack_chk_guard = guard;
++}
+diff --git a/efi/fwup-efi.h b/efi/fwup-efi.h
+index 002e5b3..5ef34d7 100644
+--- a/efi/fwup-efi.h
++++ b/efi/fwup-efi.h
+@@ -15,6 +15,8 @@ static __attribute__((__unused__)) EFI_GUID empty_guid =
+ {0x0,0x0,0x0,{0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0}};
+ static __attribute__((__unused__))EFI_GUID fwupdate_guid =
+ {0x0abba7dc,0xe516,0x4167,{0xbb,0xf5,0x4d,0x9d,0x1c,0x73,0x94,0x16}};
++static __attribute__((__unused__))EFI_GUID rng_protocol_guid =
++ {0x3152bca5,0xeade,0x433d,{0x86,0x2e,0xc0,0x1c,0xdc,0x29,0x1f,0x44}};
+ static __attribute__((__unused__))EFI_GUID ux_capsule_guid =
+ {0x3b8c8162,0x188c,0x46a4,{0xae,0xc9,0xbe,0x43,0xf1,0xd6,0x56,0x97}};
+ static __attribute__((__unused__))EFI_GUID global_variable_guid =
EFI_GLOBAL_VARIABLE;
+diff --git a/efi/fwup-fuzz-capsule.c b/efi/fwup-fuzz-capsule.c
+index 6f4d12e..fd504be 100644
+--- a/efi/fwup-fuzz-capsule.c
++++ b/efi/fwup-fuzz-capsule.c
+@@ -35,6 +35,7 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+ memset(&mock_bs, 0, sizeof(mock_bs));
+ memset(&mock_rt, 0, sizeof(mock_rt));
+
++ mock_bs.Exit = mock_exit;
+ mock_bs.Stall = mock_stall;
+ mock_bs.AllocatePages = mock_allocate_pages;
+ mock_bs.FreePages = mock_free_pages;
+diff --git a/efi/fwup-fuzz-update-info.c b/efi/fwup-fuzz-update-info.c
+index 9a0c0aa..311dafa 100644
+--- a/efi/fwup-fuzz-update-info.c
++++ b/efi/fwup-fuzz-update-info.c
+@@ -39,6 +39,7 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+ memset(&mock_bs, 0, sizeof(mock_bs));
+ memset(&mock_rt, 0, sizeof(mock_rt));
+
++ mock_bs.Exit = mock_exit;
+ mock_bs.Stall = mock_stall;
+ mock_bs.AllocatePages = mock_allocate_pages;
+ mock_bs.FreePages = mock_free_pages;
+diff --git a/efi/fwup-selftest.c b/efi/fwup-selftest.c
+index 2b4af95..1310172 100644
+--- a/efi/fwup-selftest.c
++++ b/efi/fwup-selftest.c
+@@ -129,6 +129,7 @@ mock_init(void)
+ memset(&mock_bs, 0, sizeof(mock_bs));
+ memset(&mock_rt, 0, sizeof(mock_rt));
+
++ mock_bs.Exit = mock_exit;
+ mock_bs.Stall = mock_stall;
+ mock_bs.AllocatePages = mock_allocate_pages;
+ mock_bs.FreePages = mock_free_pages;
+@@ -658,6 +659,18 @@ test_cbd_terminator_position(void)
+ assert(cbd[1].Length == 200);
+ }
+
++/* ================================================================
++ * Stack protector needs some things to be present for the self-test
++ * to link correctly.
++ * ================================================================ */
++
++uintptr_t __stack_chk_guard = (uintptr_t)test_cbd_terminator_position;
++
++uintptr_t stack_protector_init(void)
++{
++ return 0;
++}
++
+ /* ================================================================
+ * main
+ * ================================================================ */
+diff --git a/efi/fwupdate.c b/efi/fwupdate.c
+index a3a6bba..609b113 100644
+--- a/efi/fwupdate.c
++++ b/efi/fwupdate.c
+@@ -555,17 +555,13 @@ fwup_debug_hook(VOID)
+ }
+
+ EFI_STATUS
+-efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
++fwupd_main(EFI_HANDLE image)
+ {
+ EFI_STATUS rc;
+ UINTN i, n_updates = 0;
+ EFI_RESET_TYPE reset_type = EfiResetWarm;
+ _cleanup_free FWUP_UPDATE_TABLE **updates = NULL;
+
+- InitializeLib(image, systab);
+-
+- fwup_debug_hook();
+-
+ /* show the version to screen */
+ fwup_info(L"fwupd-efi version " PACKAGE_VERSION);
+
+@@ -638,6 +634,19 @@ efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
+ if (fwup_debug_get_enabled())
+ fwup_msleep(30 * SECONDS);
+ uefi_call_wrapper(RT->ResetSystem, 4, reset_type, EFI_SUCCESS, 0, NULL);
++ return rc;
++}
+
++EFI_STATUS
++efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
++{
++ EFI_STATUS rc;
++
++ InitializeLib(image, systab);
++ update_stack_guard();
++
++ fwup_debug_hook();
++ rc = fwupd_main(image);
++ uefi_call_wrapper(BS->Exit, 4, image, rc, 0, NULL);
+ return rc;
+ }
+diff --git a/efi/meson.build b/efi/meson.build
+index bde4346..63f716c 100644
+--- a/efi/meson.build
++++ b/efi/meson.build
+@@ -164,7 +164,8 @@ compile_args = ['-Og',
+ '-fshort-wchar',
+ '-ffreestanding',
+ '-fno-strict-aliasing',
+- '-fno-stack-protector',
++ '-fstack-protector-strong',
++ '-mstack-protector-guard=global',
+ '-fno-stack-check',
+ '-fno-merge-all-constants',
+ '-Wsign-compare',
+@@ -257,6 +258,11 @@ o_files += custom_target('fwup-common.o',
+ output : 'fwup-common.o',
+ command : [cc.cmd_array(), '-c', '@INPUT@', '-o',
'@OUTPUT@']
+ + compile_args)
++o_files += custom_target('stack-protector.o',
++ input : 'stack-protector.c',
++ output : 'stack-protector.o',
++ command : [cc.cmd_array(), '-c', '@INPUT@', '-o',
'@OUTPUT@']
++ + compile_args)
+
+ o_files += custom_target('fwup-sbat.o',
+ output : 'fwup-sbat.o',
+diff --git a/efi/stack-protector.c b/efi/stack-protector.c
+new file mode 100644
+index 0000000..a7a495c
+--- /dev/null
++++ b/efi/stack-protector.c
+@@ -0,0 +1,82 @@
++/*
++ * Copyright (C) 2026 Peter Jones <[email protected]>
++ *
++ * SPDX-License-Identifier: LGPL-2.1+
++ */
++
++#include "config.h"
++
++#include <efi.h>
++#include <efilib.h>
++
++#include "fwup-cleanups.h"
++#include "fwup-common.h"
++#include "fwup-debug.h"
++#include "fwup-efi.h"
++
++#define SECONDS 1000000
++
++/*
++ * Before the first call to stack_protector_init(), it's important for the
++ * canary it to have /something nonzero/ and to have some variability.
++ * urandom would be nice, but it's not that important that it's truly
++ * random, just fairly hard to predict and not stable across a bunch of
++ * different machines.
++ *
++ * So we're just assigning it to some address that'll get relocated during
++ * startup. It'll get reset during initialization soon after.
++ */
++uintptr_t __stack_chk_guard = (uintptr_t)stack_protector_init;
++
++void __attribute__((__noreturn__))
++__stack_chk_fail(void)
++{
++ do {
++ fwup_info(L"Stack corruption detected. Shutting down in 5
seconds.\n");
++ fwup_msleep(5 * SECONDS);
++ RT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION, 0,
NULL);
++
++ /*
++ * We should never get here, but if we do, we have to do
++ * /something/.
++ */
++ fwup_msleep(120 * SECONDS);
++ } while (1);
++}
++
++/*
++ * For reasons unknown, when we build for ia32 using:
++ *
++ * x86_64-linux-gnu-gcc -fstack-protector -mstack-protector-guard=global -m32
...
++ *
++ * a few objects wind up referencing __stack_chk_fail_local() instead of
++ * __stack_chk_fail(). I really don't understand why, but... eh.
++ */
++void __attribute__((__noreturn__)) __attribute__((__visibility__ ("hidden")))
++__stack_chk_fail_local (void)
++{
++ __stack_chk_fail ();
++}
++
++uintptr_t
++stack_protector_init(void)
++{
++ EFI_RNG_PROTOCOL *rng = NULL;
++ EFI_STATUS efi_status;
++ uintptr_t guard = __stack_chk_guard;
++
++ efi_status = LibLocateProtocol(&rng_protocol_guid, (VOID *)&rng);
++ if (EFI_ERROR(efi_status)) {
++ fwup_warning(L"Could not find EFI RNG protocol: %r\n",
efi_status);
++ return guard;
++ }
++
++ efi_status = rng->GetRNG(rng, NULL, sizeof(guard)-1, (uint8_t *)&guard);
++ if (EFI_ERROR(efi_status)) {
++ fwup_warning(L"Could not get random data: %r\n", efi_status);
++ return guard;
++ }
++ return guard;
++}
++
++// vim:fenc=utf-8:tw=75:noet
+--
+2.53.0
+
diff -Nru fwupd-efi-1.7/debian/patches/series
fwupd-efi-1.9/debian/patches/series
--- fwupd-efi-1.7/debian/patches/series 1969-12-31 18:00:00.000000000 -0600
+++ fwupd-efi-1.9/debian/patches/series 2026-11-08 14:27:45.000000000 -0600
@@ -0,0 +1,4 @@
+cross.patch
+gcc-fstack-protector.patch
+Add-efi_sbat_distro_generation-to-set-the-distro-SBA.patch
+Add-efi_sbat_distro_generation-and-fix-mstack-protec.patch
diff -Nru fwupd-efi-1.7/debian/rules fwupd-efi-1.9/debian/rules
--- fwupd-efi-1.7/debian/rules 2024-09-20 23:13:29.000000000 -0500
+++ fwupd-efi-1.9/debian/rules 2026-11-08 14:24:42.000000000 -0600
@@ -29,6 +29,8 @@
CONFARGS += -Defi_sbat_distro_summary=Debian
CONFARGS += -Defi_sbat_distro_pkgname=fwupd
CONFARGS += -Defi_sbat_distro_version=$(deb_version)
+# upstream 1.9 was broken and ended up in unstable with SBAT 2
+ CONFARGS += -Defi_sbat_distro_generation=2
CONFARGS += -Defi_sbat_distro_url="https://tracker.debian.org/pkg/fwupd"
endif
diff -Nru fwupd-efi-1.7/.editorconfig fwupd-efi-1.9/.editorconfig
--- fwupd-efi-1.7/.editorconfig 1969-12-31 18:00:00.000000000 -0600
+++ fwupd-efi-1.9/.editorconfig 2026-10-26 04:23:48.000000000 -0500
@@ -0,0 +1,37 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 8
+indent_style = space
+insert_final_newline = true
+max_line_length = 100
+tab_width = 8
+trim_trailing_whitespace = true
+
+[.git/**]
+max_line_length = 72
+
+[*.md]
+max_line_length = 80
+
+[*.{yml,yaml}]
+indent_size = 2
+
+[meson{.build,.options,_options.txt}]
+indent_size = 2
+
+[*.py]
+indent_size = 4
+# default for python black
+max_line_length = 88
+
+[*.{sh,{pre,post}{inst,rm}}]
+indent_size = 4
+
+[*.rs]
+indent_size = 4
+
+[*.{c,h}]
+indent_style = tab
diff -Nru fwupd-efi-1.7/efi/efi-mock-funcs.h fwupd-efi-1.9/efi/efi-mock-funcs.h
--- fwupd-efi-1.7/efi/efi-mock-funcs.h 1969-12-31 18:00:00.000000000 -0600
+++ fwupd-efi-1.9/efi/efi-mock-funcs.h 2026-10-26 04:23:48.000000000 -0500
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 Richard Hughes <[email protected]>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include "efi-mock.h"
+
+#include <stdio.h>
+#include <string.h>
+
+/* BS/RT globals */
+static EFI_BOOT_SERVICES mock_bs;
+static EFI_RUNTIME_SERVICES mock_rt;
+EFI_BOOT_SERVICES *BS = &mock_bs;
+EFI_RUNTIME_SERVICES *RT = &mock_rt;
+
+/* include the real source -- makes static functions visible */
+#include "fwup-debug.c"
+#include "fwup-efi.c"
+#include "fwup-common.c"
+#include "fwupdate.c"
+
+static EFI_STATUS
+mock_stall(UINTN microseconds)
+{
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS
+mock_allocate_pages(EFI_ALLOCATE_TYPE type, EFI_MEMORY_TYPE mem_type,
+ UINTN pages, EFI_PHYSICAL_ADDRESS *memory)
+{
+ VOID *p = calloc(pages, 4096);
+ if (p == NULL)
+ return EFI_OUT_OF_RESOURCES;
+ *memory = (EFI_PHYSICAL_ADDRESS)(UINTN)p;
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS
+mock_free_pages(EFI_PHYSICAL_ADDRESS memory, UINTN pages)
+{
+ free((VOID *)(UINTN)memory);
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS
+mock_get_time(EFI_TIME *time, EFI_TIME_CAPABILITIES *caps)
+{
+ memset(time, 0, sizeof(*time));
+ time->Year = 2024;
+ time->Month = 1;
+ time->Day = 1;
+ if (caps)
+ memset(caps, 0, sizeof(*caps));
+ return EFI_SUCCESS;
+}
+
+static void
+mock_reset_system(EFI_RESET_TYPE type, EFI_STATUS status,
+ UINTN data_size, CHAR16 *data)
+{
+}
diff -Nru fwupd-efi-1.7/efi/efi-mock.h fwupd-efi-1.9/efi/efi-mock.h
--- fwupd-efi-1.7/efi/efi-mock.h 1969-12-31 18:00:00.000000000 -0600
+++ fwupd-efi-1.9/efi/efi-mock.h 2026-10-26 04:23:48.000000000 -0500
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2024 Richard Hughes <[email protected]>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#define FWUP_SELFTEST_BUILD 1
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <wchar.h>
+
+typedef void VOID;
+typedef uint8_t UINT8;
+typedef uint16_t UINT16;
+typedef uint32_t UINT32;
+typedef uint64_t UINT64;
+typedef int16_t INT16;
+typedef int64_t INTN;
+typedef uint64_t UINTN;
+typedef wchar_t CHAR16;
+typedef char CHAR8;
+typedef uint8_t BOOLEAN;
+typedef UINTN EFI_STATUS;
+typedef UINT64 EFI_PHYSICAL_ADDRESS;
+typedef VOID *EFI_HANDLE;
+typedef UINT16 EFI_TPL;
+
+#define TRUE 1
+#define FALSE 0
+#define EFIAPI
+#define IN
+#define OUT
+#define OPTIONAL
+#define CONST const
+#define UNUSED __attribute__((__unused__))
+
+#define uefi_call_wrapper(func, va_num, ...) func(__VA_ARGS__)
+
+#define EFIERR(a) (0x8000000000000000ULL | (a))
+#define EFI_ERROR(a) (((INTN)(a)) < 0)
+#define EFI_SUCCESS 0
+#define EFI_LOAD_ERROR EFIERR(1)
+#define EFI_INVALID_PARAMETER EFIERR(2)
+#define EFI_UNSUPPORTED EFIERR(3)
+#define EFI_BAD_BUFFER_SIZE EFIERR(4)
+#define EFI_BUFFER_TOO_SMALL EFIERR(5)
+#define EFI_NOT_READY EFIERR(6)
+#define EFI_OUT_OF_RESOURCES EFIERR(9)
+#define EFI_NOT_FOUND EFIERR(14)
+
+typedef struct {
+ UINT32 Data1;
+ UINT16 Data2;
+ UINT16 Data3;
+ UINT8 Data4[8];
+} EFI_GUID;
+
+typedef struct _EFI_DEVICE_PATH {
+ UINT8 Type;
+ UINT8 SubType;
+ UINT8 Length[2];
+} EFI_DEVICE_PATH;
+
+typedef struct {
+ UINT16 Year;
+ UINT8 Month;
+ UINT8 Day;
+ UINT8 Hour;
+ UINT8 Minute;
+ UINT8 Second;
+ UINT8 Pad1;
+ UINT32 Nanosecond;
+ INT16 TimeZone;
+ UINT8 Daylight;
+ UINT8 Pad2;
+} EFI_TIME;
+
+typedef struct {
+ UINT32 Resolution;
+ UINT32 Accuracy;
+ BOOLEAN SetsToZero;
+} EFI_TIME_CAPABILITIES;
+
+typedef struct {
+ EFI_GUID CapsuleGuid;
+ UINT32 HeaderSize;
+ UINT32 Flags;
+ UINT32 CapsuleImageSize;
+} EFI_CAPSULE_HEADER;
+
+typedef struct {
+ UINT64 Length;
+ union {
+ EFI_PHYSICAL_ADDRESS DataBlock;
+ EFI_PHYSICAL_ADDRESS ContinuationPointer;
+ } Union;
+} EFI_CAPSULE_BLOCK_DESCRIPTOR;
+
+typedef enum {
+ EfiResetCold,
+ EfiResetWarm,
+ EfiResetShutdown
+} EFI_RESET_TYPE;
+
+typedef enum {
+ AllocateAnyPages,
+ AllocateMaxAddress,
+ AllocateAddress
+} EFI_ALLOCATE_TYPE;
+
+typedef enum {
+ EfiLoaderCode,
+ EfiLoaderData,
+ EfiMaxMemoryType
+} EFI_MEMORY_TYPE;
+
+typedef enum {
+ AllHandles,
+ ByRegisterNotify,
+ ByProtocol
+} EFI_LOCATE_SEARCH_TYPE;
+
+#define CAPSULE_FLAGS_PERSIST_ACROSS_RESET 0x00010000
+#define CAPSULE_FLAGS_INITIATE_RESET 0x00040000
+#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x00000002
+#define EFI_FILE_MODE_READ 0x0000000000000001ULL
+
+#define END_DEVICE_PATH_TYPE 0x7f
+#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff
+#define MEDIA_DEVICE_PATH 0x04
+#define MEDIA_FILEPATH_DP 0x04
+
+#define EFI_VARIABLE_NON_VOLATILE 0x00000001
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
+#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
+#define EFI_VARIABLE_APPEND_WRITE 0x00000040
+
+#define DevicePathType(a) ((a)->Type & 0x7f)
+#define DevicePathSubType(a) ((a)->SubType)
+#define DevicePathNodeLength(a) ((UINTN)((a)->Length[0] |
((a)->Length[1] << 8)))
+#define NextDevicePathNode(a) ((EFI_DEVICE_PATH *)((UINT8 *)(a) +
DevicePathNodeLength(a)))
+#define IsDevicePathEndType(a) (DevicePathType(a) ==
END_DEVICE_PATH_TYPE)
+#define IsDevicePathEnd(a) (IsDevicePathEndType(a) && \
+ (a)->SubType ==
END_ENTIRE_DEVICE_PATH_SUBTYPE)
+#define SetDevicePathEndNode(a) do { \
+ (a)->Type = END_DEVICE_PATH_TYPE; \
+ (a)->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; \
+ (a)->Length[0] = 4; (a)->Length[1] = 0; \
+} while (0)
+
+#define EFI_FIELD_OFFSET(TYPE, Field) ((UINTN)offsetof(TYPE, Field))
+
+#define EFI_GLOBAL_VARIABLE \
+ { 0x8BE4DF61, 0x93CA, 0x11d2, { 0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03,
0x2B, 0x8C } }
+#define SIMPLE_FILE_SYSTEM_PROTOCOL \
+ { 0x964e5b22, 0x6459, 0x11d2, { 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69,
0x72, 0x3b } }
+#define DEVICE_PATH_PROTOCOL \
+ { 0x09576e91, 0x6d3f, 0x11d2, { 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69,
0x72, 0x3b } }
+#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \
+ { 0x9042a9de, 0x23dc, 0x4a38, { 0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80,
0x51, 0x6a } }
+
+typedef struct {
+ UINT32 MaxMode;
+ UINT32 Mode;
+ VOID *Info;
+ UINTN SizeOfInfo;
+ EFI_PHYSICAL_ADDRESS FrameBufferBase;
+ UINTN FrameBufferSize;
+} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
+
+typedef struct {
+ VOID *QueryMode;
+ VOID *SetMode;
+ VOID *Blt;
+ EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
+} EFI_GRAPHICS_OUTPUT_PROTOCOL;
+
+struct _EFI_FILE_HANDLE;
+typedef EFI_STATUS (*EFI_FILE_OPEN)(struct _EFI_FILE_HANDLE *file,
+ struct _EFI_FILE_HANDLE **new_handle,
+ CHAR16 *filename, UINT64 open_mode,
+ UINT64 attributes);
+typedef EFI_STATUS (*EFI_FILE_CLOSE)(struct _EFI_FILE_HANDLE *file);
+typedef EFI_STATUS (*EFI_FILE_READ)(struct _EFI_FILE_HANDLE *file,
+ UINTN *buf_size, VOID *buf);
+
+typedef struct _EFI_FILE_HANDLE {
+ UINT64 Revision;
+ EFI_FILE_OPEN Open;
+ EFI_FILE_CLOSE Close;
+ VOID *Delete;
+ EFI_FILE_READ Read;
+} *EFI_FILE_HANDLE;
+
+typedef struct _EFI_FILE_HANDLE EFI_FILE;
+
+struct _EFI_FILE_IO_INTERFACE;
+typedef EFI_STATUS (*EFI_VOLUME_OPEN)(struct _EFI_FILE_IO_INTERFACE *this,
+ EFI_FILE_HANDLE *root);
+
+typedef struct _EFI_FILE_IO_INTERFACE {
+ UINT64 Revision;
+ EFI_VOLUME_OPEN OpenVolume;
+} EFI_FILE_IO_INTERFACE;
+
+typedef struct {
+ UINT64 Signature;
+} EFI_TABLE_HEADER;
+
+typedef EFI_STATUS (*EFI_STALL)(UINTN microseconds);
+typedef EFI_STATUS (*EFI_ALLOCATE_PAGES)(EFI_ALLOCATE_TYPE type,
+ EFI_MEMORY_TYPE mem_type,
+ UINTN pages,
+ EFI_PHYSICAL_ADDRESS *memory);
+typedef EFI_STATUS (*EFI_FREE_PAGES)(EFI_PHYSICAL_ADDRESS memory, UINTN pages);
+typedef EFI_STATUS (*EFI_LOCATE_DEVICE_PATH)(EFI_GUID *protocol,
+ EFI_DEVICE_PATH **device_path,
+ EFI_HANDLE *device);
+typedef EFI_STATUS (*EFI_HANDLE_PROTOCOL)(EFI_HANDLE handle,
+ EFI_GUID *protocol,
+ VOID **interface);
+typedef EFI_STATUS (*EFI_LOCATE_HANDLE_BUFFER)(EFI_LOCATE_SEARCH_TYPE
search_type,
+ EFI_GUID *protocol,
+ VOID *search_key,
+ UINTN *num_handles,
+ EFI_HANDLE **buffer);
+typedef EFI_STATUS (*EFI_OPEN_PROTOCOL)(EFI_HANDLE handle,
+ EFI_GUID *protocol,
+ VOID **interface,
+ EFI_HANDLE agent,
+ EFI_HANDLE controller,
+ UINT32 attributes);
+typedef EFI_STATUS (*EFI_RAISE_TPL)(EFI_TPL new_tpl);
+
+typedef struct {
+ EFI_TABLE_HEADER Hdr;
+ EFI_RAISE_TPL RaiseTPL;
+ VOID *RestoreTPL;
+ EFI_ALLOCATE_PAGES AllocatePages;
+ EFI_FREE_PAGES FreePages;
+ VOID *GetMemoryMap;
+ VOID *AllocatePool;
+ VOID *FreePool;
+ VOID *CreateEvent;
+ VOID *SetTimer;
+ VOID *WaitForEvent;
+ VOID *SignalEvent;
+ VOID *CloseEvent;
+ VOID *CheckEvent;
+ VOID *InstallProtocolInterface;
+ VOID *ReinstallProtocolInterface;
+ VOID *UninstallProtocolInterface;
+ EFI_HANDLE_PROTOCOL HandleProtocol;
+ VOID *Reserved;
+ VOID *RegisterProtocolNotify;
+ EFI_LOCATE_HANDLE_BUFFER LocateHandleBuffer;
+ VOID *LocateProtocol;
+ VOID *InstallMultipleProtocolInterfaces;
+ VOID *UninstallMultipleProtocolInterfaces;
+ VOID *CalculateCrc32;
+ VOID *CopyMem;
+ VOID *SetMem;
+ VOID *CreateEventEx;
+ EFI_LOCATE_DEVICE_PATH LocateDevicePath;
+ EFI_OPEN_PROTOCOL OpenProtocol;
+ VOID *CloseProtocol;
+ VOID *OpenProtocolInformation;
+ VOID *ProtocolsPerHandle;
+ VOID *LocateHandleBuffer2;
+ EFI_STALL Stall;
+} EFI_BOOT_SERVICES;
+
+typedef EFI_STATUS (*EFI_GET_TIME)(EFI_TIME *time,
+ EFI_TIME_CAPABILITIES *capabilities);
+typedef EFI_STATUS (*EFI_GET_VARIABLE)(CHAR16 *name, EFI_GUID *vendor,
+ UINT32 *attrs, UINTN *data_size,
+ VOID *data);
+typedef EFI_STATUS (*EFI_GET_NEXT_VARIABLE_NAME)(UINTN *name_size,
+ CHAR16 *name,
+ EFI_GUID *vendor);
+typedef EFI_STATUS (*EFI_SET_VARIABLE)(CHAR16 *name, EFI_GUID *vendor,
+ UINT32 attrs, UINTN data_size,
+ VOID *data);
+typedef EFI_STATUS (*EFI_QUERY_CAPSULE_CAPABILITIES)(EFI_CAPSULE_HEADER
**capsules,
+ UINTN count,
+ UINT64 *max_size,
+ EFI_RESET_TYPE
*reset_type);
+typedef EFI_STATUS (*EFI_UPDATE_CAPSULE)(EFI_CAPSULE_HEADER **capsules,
+ UINTN count,
+ EFI_PHYSICAL_ADDRESS sg_list);
+typedef VOID (*EFI_RESET_SYSTEM)(EFI_RESET_TYPE type, EFI_STATUS status,
+ UINTN data_size, CHAR16 *data);
+
+typedef struct {
+ EFI_TABLE_HEADER Hdr;
+ EFI_GET_TIME GetTime;
+ VOID *SetTime;
+ VOID *GetWakeupTime;
+ VOID *SetWakeupTime;
+ VOID *SetVirtualAddressMap;
+ VOID *ConvertPointer;
+ EFI_GET_VARIABLE GetVariable;
+ EFI_GET_NEXT_VARIABLE_NAME GetNextVariableName;
+ EFI_SET_VARIABLE SetVariable;
+ VOID *GetNextHighMonotonicCount;
+ EFI_RESET_SYSTEM ResetSystem;
+ EFI_UPDATE_CAPSULE UpdateCapsule;
+ EFI_QUERY_CAPSULE_CAPABILITIES QueryCapsuleCapabilities;
+} EFI_RUNTIME_SERVICES;
+
+typedef struct {
+ EFI_TABLE_HEADER Hdr;
+ CHAR16 *FirmwareVendor;
+ UINT32 FirmwareRevision;
+ EFI_HANDLE ConsoleInHandle;
+ VOID *ConIn;
+ EFI_HANDLE ConsoleOutHandle;
+ VOID *ConOut;
+ EFI_HANDLE StandardErrorHandle;
+ VOID *StdErr;
+ EFI_RUNTIME_SERVICES *RuntimeServices;
+ EFI_BOOT_SERVICES *BootServices;
+} EFI_SYSTEM_TABLE;
+
+extern EFI_BOOT_SERVICES *BS;
+extern EFI_RUNTIME_SERVICES *RT;
+
+static inline VOID
+CopyMem(VOID *dest, const VOID *src, UINTN len)
+{
+ memcpy(dest, src, len);
+}
+
+static inline INTN
+CompareGuid(const EFI_GUID *a, const EFI_GUID *b)
+{
+ return (INTN)memcmp(a, b, sizeof(EFI_GUID));
+}
+
+static inline VOID *
+AllocatePool(UINTN size)
+{
+ return malloc(size);
+}
+
+static inline VOID *
+AllocateZeroPool(UINTN size)
+{
+ return calloc(1, size);
+}
+
+static inline VOID
+FreePool(VOID *ptr)
+{
+ free(ptr);
+}
+
+static inline CHAR16 *
+StrDuplicate(const CHAR16 *src)
+{
+ if (src == NULL)
+ return NULL;
+ UINTN len = 0;
+ while (src[len] != 0)
+ len++;
+ CHAR16 *dst = malloc((len + 1) * sizeof(CHAR16));
+ if (dst == NULL)
+ return NULL;
+ memcpy(dst, src, (len + 1) * sizeof(CHAR16));
+ return dst;
+}
+
+static inline INTN
+StrCmp(const CHAR16 *a, const CHAR16 *b)
+{
+ while (*a && *a == *b) {
+ a++;
+ b++;
+ }
+ return *a - *b;
+}
+
+static inline UINTN
+StrSize(const CHAR16 *s)
+{
+ UINTN len = 0;
+ while (s[len] != 0)
+ len++;
+ return (len + 1) * sizeof(CHAR16);
+}
+
+static inline UINTN UNUSED
+Print(const CHAR16 *fmt, ...)
+{
+ return 0;
+}
+
+static inline CHAR16 * UNUSED
+VPoolPrint(const CHAR16 *fmt, va_list args)
+{
+ return StrDuplicate(L"<mock>");
+}
+
+static inline CHAR16 * UNUSED
+PoolPrint(const CHAR16 *fmt, ...)
+{
+ return StrDuplicate(L"<mock>");
+}
+
+static inline VOID UNUSED
+InitializeLib(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
+{
+}
+
+static inline EFI_STATUS UNUSED
+LibLocateHandle(EFI_LOCATE_SEARCH_TYPE type, EFI_GUID *protocol,
+ VOID *search_key, UINTN *num_handles, EFI_HANDLE **buffer)
+{
+ *num_handles = 0;
+ *buffer = NULL;
+ return EFI_NOT_FOUND;
+}
+
+static inline BOOLEAN UNUSED
+LibMatchDevicePaths(const EFI_DEVICE_PATH *a, const EFI_DEVICE_PATH *b)
+{
+ return FALSE;
+}
+
+static inline EFI_DEVICE_PATH * UNUSED
+DuplicateDevicePath(const EFI_DEVICE_PATH *dp)
+{
+ return NULL;
+}
+
+static inline CHAR16 * UNUSED
+DevicePathToStr(const EFI_DEVICE_PATH *dp)
+{
+ return L"<mock-dp>";
+}
diff -Nru fwupd-efi-1.7/efi/fwupdate.c fwupd-efi-1.9/efi/fwupdate.c
--- fwupd-efi-1.7/efi/fwupdate.c 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/efi/fwupdate.c 2026-10-26 04:23:48.000000000 -0500
@@ -54,7 +54,7 @@
if (limit < 4)
break;
INTN nodelen = DevicePathNodeLength(dp);
- if (nodelen > limit)
+ if (nodelen < 4 || nodelen > limit)
break;
limit -= nodelen;
ret += nodelen;
@@ -73,7 +73,7 @@
FWUP_UPDATE_INFO *info = NULL;
UINTN info_size = 0;
UINT32 attrs = 0;
- VOID *info_ptr = NULL;
+ _cleanup_free VOID *info_ptr = NULL;
rc = fwup_get_variable(name, &fwupdate_guid, &info_ptr, &info_size,
&attrs);
if (EFI_ERROR(rc))
@@ -81,7 +81,7 @@
info = (FWUP_UPDATE_INFO *)info_ptr;
if (info_size < sizeof(*info)) {
- fwup_warning(L"Update '%s' is is too small", name);
+ fwup_warning(L"Update '%s' is too small", name);
return EFI_INVALID_PARAMETER;
}
@@ -101,7 +101,7 @@
}
is = info_size - is;
- INTN sz = fwup_dp_size(hdr, info_size);
+ INTN sz = fwup_dp_size(hdr, is);
if (sz < 0 || is > (INTN)info_size || is != sz) {
fwup_warning(L"Update '%s' has an invalid file path, "
L"update info size: %d dp size: %d size for dp:
%d",
@@ -109,7 +109,7 @@
return EFI_INVALID_PARAMETER;
}
- info_out->info = info;
+ info_out->info = _steal_pointer(&info_ptr);
info_out->size = info_size;
info_out->attrs = attrs;
info_out->name = StrDuplicate(name);
@@ -160,7 +160,7 @@
StrCmp(variable_name, L"FWUPDATE_DEBUG_LOG") == 0)
continue;
- if (n_updates > FWUP_NUM_CAPSULE_UPDATES_MAX) {
+ if (n_updates >= FWUP_NUM_CAPSULE_UPDATES_MAX) {
fwup_warning(L"Ignoring update %s", variable_name);
continue;
}
@@ -189,7 +189,8 @@
static EFI_STATUS
fwup_search_file(EFI_DEVICE_PATH **file_dp, EFI_FILE_HANDLE *fh)
{
- EFI_DEVICE_PATH *dp, *parent_dp;
+ EFI_DEVICE_PATH *dp;
+ _cleanup_free EFI_DEVICE_PATH *parent_dp = NULL;
EFI_GUID sfsp = SIMPLE_FILE_SYSTEM_PROTOCOL;
EFI_GUID dpp = DEVICE_PATH_PROTOCOL;
UINTN n_handles, count;
@@ -378,11 +379,20 @@
static EFI_STATUS
fwup_check_gop_for_ux_capsule(EFI_HANDLE loaded_image,
- EFI_CAPSULE_HEADER *capsule)
+ EFI_CAPSULE_HEADER *capsule,
+ UINTN fsize)
{
UX_CAPSULE_HEADER *payload_hdr;
EFI_STATUS rc;
+ if (capsule->HeaderSize < sizeof(EFI_CAPSULE_HEADER) ||
+ capsule->CapsuleImageSize < capsule->HeaderSize ||
+ capsule->CapsuleImageSize - capsule->HeaderSize <
sizeof(UX_CAPSULE_HEADER) ||
+ capsule->HeaderSize + sizeof(UX_CAPSULE_HEADER) > fsize) {
+ fwup_warning(L"Invalid capsule header size %u",
capsule->HeaderSize);
+ return EFI_INVALID_PARAMETER;
+ }
+
payload_hdr = (UX_CAPSULE_HEADER *) (((UINT8 *) capsule) +
capsule->HeaderSize);
rc = fwup_get_gop_mode(&payload_hdr->mode, loaded_image);
if (EFI_ERROR(rc))
@@ -394,6 +404,22 @@
}
static EFI_STATUS
+fwup_validate_capsule_size(EFI_CAPSULE_HEADER *capsule, UINTN fsize)
+{
+ if (capsule->CapsuleImageSize < sizeof(EFI_CAPSULE_HEADER) ||
+ capsule->CapsuleImageSize > fsize)
+ return EFI_INVALID_PARAMETER;
+ return EFI_SUCCESS;
+}
+
+static void
+fwup_cbd_set_terminator(EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd, UINTN index)
+{
+ cbd[index].Length = 0;
+ cbd[index].Union.ContinuationPointer = 0;
+}
+
+static EFI_STATUS
fwup_add_update_capsule(FWUP_UPDATE_TABLE *update, EFI_CAPSULE_HEADER
**capsule_out,
EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd_out, EFI_HANDLE
loaded_image)
{
@@ -429,6 +455,11 @@
cbd_len = fsize;
cbd_data = (EFI_PHYSICAL_ADDRESS)(UINTN)fbuf;
capsule = cap_out = (EFI_CAPSULE_HEADER *)fbuf;
+ if (EFI_ERROR(fwup_validate_capsule_size(cap_out, fsize))) {
+ fwup_warning(L"Invalid capsule image size %u for file size %d",
+ cap_out->CapsuleImageSize, fsize);
+ return EFI_INVALID_PARAMETER;
+ }
if (cap_out->Flags == 0 &&
CompareGuid(&update->info->guid, &ux_capsule_guid) != 0) {
#if defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64)
@@ -442,7 +473,7 @@
if (CompareGuid(&update->info->guid, &ux_capsule_guid) == 0) {
fwup_debug(L"Checking GOP for ux capsule");
- rc = fwup_check_gop_for_ux_capsule(loaded_image, capsule);
+ rc = fwup_check_gop_for_ux_capsule(loaded_image, capsule,
fsize);
if (EFI_ERROR(rc))
return EFI_UNSUPPORTED;
}
@@ -506,43 +537,21 @@
return EFI_SUCCESS;
}
-EFI_GUID SHIM_LOCK_GUID =
- {0x605dab50,0xe046,0x4300,{0xab,0xb6,0x3d,0xd8,0x10,0xdd,0x8b,0x23}};
-
static VOID
EFI_OPTNONE
fwup_debug_hook(VOID)
{
- EFI_GUID guid = SHIM_LOCK_GUID;
- UINTN data = 0;
- UINTN data_size = 1;
- EFI_STATUS efi_status;
+ EFI_STATUS rc;
UINT32 attrs;
- register volatile int x = 0;
- extern char _text UNUSED, _data UNUSED;
+ UINT8 data = 0;
+ UINTN data_size = sizeof(data);
- /* shim has done whatever is needed to get a debugger attached */
- efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"SHIM_DEBUG",
- &guid, &attrs, &data_size, &data);
- if (EFI_ERROR(efi_status) || data != 1) {
- efi_status = uefi_call_wrapper(RT->GetVariable, 5,
- L"FWUPDATE_VERBOSE",
- &fwupdate_guid, &attrs,
- &data_size, &data);
- if (EFI_ERROR(efi_status) || data != 1)
- return;
+ rc = uefi_call_wrapper(RT->GetVariable, 5,
+ L"FWUPDATE_VERBOSE",
+ &fwupdate_guid, &attrs,
+ &data_size, &data);
+ if (rc == EFI_SUCCESS && data > 0)
fwup_debug_set_enabled(TRUE);
- return;
- }
-
- fwup_debug_set_enabled(TRUE);
- if (x)
- return;
-
- x = 1;
- fwup_info(L"add-symbol-file "DEBUGDIR
- L"fwupdate.efi.debug %p -s .data %p",
- &_text, &_data);
}
EFI_STATUS
@@ -555,7 +564,6 @@
InitializeLib(image, systab);
- /* if SHIM_DEBUG is set, fwup_info info for our attached debugger */
fwup_debug_hook();
/* show the version to screen */
@@ -610,8 +618,7 @@
n_updates = j;
fwup_debug(L"n_updates: %d", n_updates);
- cbd_data[i].Length = 0;
- cbd_data[i].Union.ContinuationPointer = 0;
+ fwup_cbd_set_terminator(cbd_data, n_updates);
/* step 3: update the state variables */
rc = fwup_set_update_statuses(updates);
diff -Nru fwupd-efi-1.7/efi/fwup-efi.c fwupd-efi-1.9/efi/fwup-efi.c
--- fwupd-efi-1.7/efi/fwup-efi.c 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/efi/fwup-efi.c 2026-10-26 04:23:48.000000000 -0500
@@ -17,9 +17,10 @@
{
EFI_STATUS rc;
UINT32 attrs = 0;
+ UINTN size = 0;
/* get the attrs so we can delete it */
- rc = uefi_call_wrapper(RT->GetVariable, 5, name, guid, &attrs, NULL,
NULL);
+ rc = uefi_call_wrapper(RT->GetVariable, 5, name, guid, &attrs, &size,
NULL);
if (EFI_ERROR(rc)) {
if (rc == EFI_NOT_FOUND) {
fwup_debug(L"Not deleting variable '%s' as not found",
name);
diff -Nru fwupd-efi-1.7/efi/fwup-fuzz-capsule.c
fwupd-efi-1.9/efi/fwup-fuzz-capsule.c
--- fwupd-efi-1.7/efi/fwup-fuzz-capsule.c 1969-12-31 18:00:00.000000000
-0600
+++ fwupd-efi-1.9/efi/fwup-fuzz-capsule.c 2026-10-26 04:23:48.000000000
-0500
@@ -0,0 +1,61 @@
+#include "efi-mock-funcs.h"
+
+static EFI_STATUS
+fuzz_locate_handle_buffer(EFI_LOCATE_SEARCH_TYPE type, EFI_GUID *protocol,
+ VOID *key, UINTN *num, EFI_HANDLE **buf)
+{
+ *num = 0;
+ *buf = NULL;
+ return EFI_NOT_FOUND;
+}
+
+static EFI_STATUS
+fuzz_get_variable(CHAR16 *name, EFI_GUID *vendor, UINT32 *attrs,
+ UINTN *data_size, VOID *data)
+{
+ return EFI_NOT_FOUND;
+}
+
+static EFI_STATUS
+fuzz_get_next_variable_name(UINTN *name_size, CHAR16 *name, EFI_GUID *vendor)
+{
+ return EFI_NOT_FOUND;
+}
+
+static EFI_STATUS
+fuzz_set_variable(CHAR16 *name, EFI_GUID *vendor, UINT32 attrs,
+ UINTN data_size, VOID *data)
+{
+ return EFI_SUCCESS;
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ memset(&mock_bs, 0, sizeof(mock_bs));
+ memset(&mock_rt, 0, sizeof(mock_rt));
+
+ mock_bs.Stall = mock_stall;
+ mock_bs.AllocatePages = mock_allocate_pages;
+ mock_bs.FreePages = mock_free_pages;
+ mock_bs.LocateHandleBuffer = fuzz_locate_handle_buffer;
+
+ mock_rt.GetTime = mock_get_time;
+ mock_rt.GetVariable = fuzz_get_variable;
+ mock_rt.GetNextVariableName = fuzz_get_next_variable_name;
+ mock_rt.SetVariable = fuzz_set_variable;
+ mock_rt.ResetSystem = mock_reset_system;
+
+ if (size < sizeof(EFI_CAPSULE_HEADER))
+ return 0;
+
+ UINT8 *buf = malloc(size);
+ if (buf == NULL)
+ return 0;
+ memcpy(buf, data, size);
+
+ fwup_check_gop_for_ux_capsule(NULL, (EFI_CAPSULE_HEADER *)buf, size);
+
+ free(buf);
+ return 0;
+}
diff -Nru fwupd-efi-1.7/efi/fwup-fuzz-update-info.c
fwupd-efi-1.9/efi/fwup-fuzz-update-info.c
--- fwupd-efi-1.7/efi/fwup-fuzz-update-info.c 1969-12-31 18:00:00.000000000
-0600
+++ fwupd-efi-1.9/efi/fwup-fuzz-update-info.c 2026-10-26 04:23:48.000000000
-0500
@@ -0,0 +1,64 @@
+#include "efi-mock-funcs.h"
+
+static const uint8_t *fuzz_data;
+static size_t fuzz_size;
+
+static EFI_STATUS
+fuzz_get_variable(CHAR16 *name, EFI_GUID *vendor, UINT32 *attrs,
+ UINTN *data_size, VOID *data)
+{
+ if (attrs)
+ *attrs = EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS;
+
+ if (data == NULL || *data_size < fuzz_size) {
+ *data_size = fuzz_size;
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ *data_size = fuzz_size;
+ memcpy(data, fuzz_data, fuzz_size);
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS
+fuzz_get_next_variable_name(UINTN *name_size, CHAR16 *name, EFI_GUID *vendor)
+{
+ return EFI_NOT_FOUND;
+}
+
+static EFI_STATUS
+fuzz_set_variable(CHAR16 *name, EFI_GUID *vendor, UINT32 attrs,
+ UINTN data_size, VOID *data)
+{
+ return EFI_SUCCESS;
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ memset(&mock_bs, 0, sizeof(mock_bs));
+ memset(&mock_rt, 0, sizeof(mock_rt));
+
+ mock_bs.Stall = mock_stall;
+ mock_bs.AllocatePages = mock_allocate_pages;
+ mock_bs.FreePages = mock_free_pages;
+
+ mock_rt.GetTime = mock_get_time;
+ mock_rt.GetVariable = fuzz_get_variable;
+ mock_rt.GetNextVariableName = fuzz_get_next_variable_name;
+ mock_rt.SetVariable = fuzz_set_variable;
+ mock_rt.ResetSystem = mock_reset_system;
+
+ fuzz_data = data;
+ fuzz_size = size;
+
+ FWUP_UPDATE_TABLE out;
+ memset(&out, 0, sizeof(out));
+ EFI_STATUS rc = fwup_populate_update_info(L"fuzz", &out);
+ if (!EFI_ERROR(rc)) {
+ FreePool(out.info);
+ FreePool(out.name);
+ }
+
+ return 0;
+}
diff -Nru fwupd-efi-1.7/efi/fwup-selftest.c fwupd-efi-1.9/efi/fwup-selftest.c
--- fwupd-efi-1.7/efi/fwup-selftest.c 1969-12-31 18:00:00.000000000 -0600
+++ fwupd-efi-1.9/efi/fwup-selftest.c 2026-10-26 04:23:48.000000000 -0500
@@ -0,0 +1,719 @@
+#include "efi-mock-funcs.h"
+
+#include <assert.h>
+
+static unsigned int tests_run;
+static unsigned int tests_passed;
+
+#define RUN_TEST(func) do { \
+ tests_run++; \
+ printf(" " #func "... "); \
+ fflush(stdout); \
+ func(); \
+ tests_passed++; \
+ printf("OK\n"); \
+} while (0)
+
+/*
+ * mock BS->LocateDevicePath -- always fail
+ */
+static EFI_STATUS
+mock_locate_device_path(EFI_GUID *protocol, EFI_DEVICE_PATH **dp,
+ EFI_HANDLE *device)
+{
+ return EFI_NOT_FOUND;
+}
+
+/*
+ * mock BS->HandleProtocol -- always fail
+ */
+static EFI_STATUS
+mock_handle_protocol(EFI_HANDLE handle, EFI_GUID *protocol, VOID **iface)
+{
+ return EFI_UNSUPPORTED;
+}
+
+/*
+ * mock BS->LocateHandleBuffer -- always fail
+ */
+static EFI_STATUS
+mock_locate_handle_buffer(EFI_LOCATE_SEARCH_TYPE type, EFI_GUID *protocol,
+ VOID *key, UINTN *num, EFI_HANDLE **buf)
+{
+ *num = 0;
+ *buf = NULL;
+ return EFI_NOT_FOUND;
+}
+
+/*
+ * mock BS->OpenProtocol -- always fail
+ */
+static EFI_STATUS
+mock_open_protocol(EFI_HANDLE handle, EFI_GUID *protocol, VOID **iface,
+ EFI_HANDLE agent, EFI_HANDLE controller, UINT32 attrs)
+{
+ return EFI_UNSUPPORTED;
+}
+
+/*
+ * mock RT->GetVariable state -- configurable per test
+ */
+static UINT8 *mock_var_data;
+static UINTN mock_var_data_size;
+static UINT32 mock_var_attrs;
+static EFI_STATUS mock_var_status;
+
+static EFI_STATUS
+mock_get_variable(CHAR16 *name, EFI_GUID *vendor, UINT32 *attrs,
+ UINTN *data_size, VOID *data)
+{
+ if (mock_var_status != EFI_SUCCESS)
+ return mock_var_status;
+
+ if (attrs)
+ *attrs = mock_var_attrs;
+
+ if (data == NULL || *data_size < mock_var_data_size) {
+ *data_size = mock_var_data_size;
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ *data_size = mock_var_data_size;
+ memcpy(data, mock_var_data, mock_var_data_size);
+ return EFI_SUCCESS;
+}
+
+/*
+ * mock RT->SetVariable -- record what was called
+ */
+static BOOLEAN mock_set_variable_called;
+static UINTN mock_set_variable_size;
+
+static EFI_STATUS
+mock_set_variable(CHAR16 *name, EFI_GUID *vendor, UINT32 attrs,
+ UINTN data_size, VOID *data)
+{
+ mock_set_variable_called = TRUE;
+ mock_set_variable_size = data_size;
+ return EFI_SUCCESS;
+}
+
+/*
+ * mock RT->GetNextVariableName -- return nothing
+ */
+static EFI_STATUS
+mock_get_next_variable_name(UINTN *name_size, CHAR16 *name, EFI_GUID *vendor)
+{
+ return EFI_NOT_FOUND;
+}
+
+static EFI_STATUS
+mock_query_capsule_caps(EFI_CAPSULE_HEADER **capsules, UINTN count,
+ UINT64 *max_size, EFI_RESET_TYPE *reset_type)
+{
+ *max_size = 0x1000000;
+ *reset_type = EfiResetWarm;
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS
+mock_update_capsule(EFI_CAPSULE_HEADER **capsules, UINTN count,
+ EFI_PHYSICAL_ADDRESS sg_list)
+{
+ return EFI_SUCCESS;
+}
+
+static void
+mock_init(void)
+{
+ memset(&mock_bs, 0, sizeof(mock_bs));
+ memset(&mock_rt, 0, sizeof(mock_rt));
+
+ mock_bs.Stall = mock_stall;
+ mock_bs.AllocatePages = mock_allocate_pages;
+ mock_bs.FreePages = mock_free_pages;
+ mock_bs.LocateDevicePath = mock_locate_device_path;
+ mock_bs.HandleProtocol = mock_handle_protocol;
+ mock_bs.LocateHandleBuffer = mock_locate_handle_buffer;
+ mock_bs.OpenProtocol = mock_open_protocol;
+
+ mock_rt.GetTime = mock_get_time;
+ mock_rt.GetVariable = mock_get_variable;
+ mock_rt.GetNextVariableName = mock_get_next_variable_name;
+ mock_rt.SetVariable = mock_set_variable;
+ mock_rt.ResetSystem = mock_reset_system;
+ mock_rt.QueryCapsuleCapabilities = mock_query_capsule_caps;
+ mock_rt.UpdateCapsule = mock_update_capsule;
+
+ mock_var_data = NULL;
+ mock_var_data_size = 0;
+ mock_var_attrs = 0;
+ mock_var_status = EFI_NOT_FOUND;
+ mock_set_variable_called = FALSE;
+ mock_set_variable_size = 0;
+}
+
+/*
+ * helper: build a device path end node at buf
+ */
+static void
+build_dp_end(UINT8 *buf)
+{
+ EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *)buf;
+ dp->Type = END_DEVICE_PATH_TYPE;
+ dp->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ dp->Length[0] = 4;
+ dp->Length[1] = 0;
+}
+
+/*
+ * helper: build a generic device path node at buf with given type, subtype,
length
+ */
+static void
+build_dp_node(UINT8 *buf, UINT8 type, UINT8 subtype, UINT16 length)
+{
+ EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *)buf;
+ dp->Type = type;
+ dp->SubType = subtype;
+ dp->Length[0] = (UINT8)(length & 0xff);
+ dp->Length[1] = (UINT8)(length >> 8);
+}
+
+/* ================================================================
+ * Group 1: fwup_dp_size
+ * ================================================================ */
+
+static void
+test_dp_size_single_end_node(void)
+{
+ UINT8 buf[4];
+ build_dp_end(buf);
+ INTN sz = fwup_dp_size((EFI_DEVICE_PATH *)buf, 100);
+ assert(sz == 4);
+}
+
+static void
+test_dp_size_multiple_nodes(void)
+{
+ UINT8 buf[18];
+ build_dp_node(buf, 0x01, 0x01, 8);
+ build_dp_node(buf + 8, 0x02, 0x01, 6);
+ build_dp_end(buf + 14);
+ INTN sz = fwup_dp_size((EFI_DEVICE_PATH *)buf, 100);
+ assert(sz == 18);
+}
+
+static void
+test_dp_size_limit_too_small(void)
+{
+ UINT8 buf[4];
+ build_dp_end(buf);
+ INTN sz = fwup_dp_size((EFI_DEVICE_PATH *)buf, 3);
+ assert(sz == -1);
+}
+
+static void
+test_dp_size_node_exceeds_limit(void)
+{
+ UINT8 buf[12];
+ build_dp_node(buf, 0x01, 0x01, 8);
+ build_dp_end(buf + 8);
+ INTN sz = fwup_dp_size((EFI_DEVICE_PATH *)buf, 6);
+ assert(sz == -1);
+}
+
+static void
+test_dp_size_exact_fit(void)
+{
+ UINT8 buf[4];
+ build_dp_end(buf);
+ INTN sz = fwup_dp_size((EFI_DEVICE_PATH *)buf, 4);
+ assert(sz == 4);
+}
+
+static void
+test_dp_size_zero_limit(void)
+{
+ UINT8 buf[4];
+ build_dp_end(buf);
+ INTN sz = fwup_dp_size((EFI_DEVICE_PATH *)buf, 0);
+ assert(sz == -1);
+}
+
+static void
+test_dp_size_zero_length_node(void)
+{
+ UINT8 buf[8];
+ build_dp_node(buf, 0x01, 0x01, 0);
+ build_dp_end(buf + 4);
+ INTN sz = fwup_dp_size((EFI_DEVICE_PATH *)buf, 100);
+ assert(sz == -1);
+}
+
+/* ================================================================
+ * Group 2: fwup_update_ux_capsule_checksum
+ * ================================================================ */
+
+static void
+test_checksum_zeroes(void)
+{
+ UX_CAPSULE_HEADER hdr;
+ memset(&hdr, 0, sizeof(hdr));
+ fwup_update_ux_capsule_checksum(&hdr);
+ assert(hdr.checksum == 0);
+}
+
+static void
+test_checksum_known_values(void)
+{
+ UX_CAPSULE_HEADER hdr;
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.version = 1;
+ hdr.image_type = 2;
+ hdr.mode = 3;
+ hdr.x_offset = 100;
+ hdr.y_offset = 200;
+
+ fwup_update_ux_capsule_checksum(&hdr);
+
+ /* the checksum field should be set to the sum of all other bytes */
+ UINT8 saved = hdr.checksum;
+ assert(saved != 0);
+
+ /* re-compute: checksum algo zeros the field, sums all bytes, stores */
+ hdr.checksum = 0;
+ UINT8 *buf = (UINT8 *)&hdr;
+ UINT8 expected = 0;
+ for (UINTN i = 0; i < sizeof(hdr); i++)
+ expected = (UINT8)(expected + buf[i]);
+ assert(saved == expected);
+ hdr.checksum = saved;
+}
+
+static void
+test_checksum_idempotent(void)
+{
+ UX_CAPSULE_HEADER hdr;
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.version = 5;
+ hdr.mode = 42;
+ hdr.x_offset = 10;
+ hdr.y_offset = 20;
+
+ fwup_update_ux_capsule_checksum(&hdr);
+ UINT8 first = hdr.checksum;
+
+ fwup_update_ux_capsule_checksum(&hdr);
+ assert(hdr.checksum == first);
+}
+
+/* ================================================================
+ * Group 3: fwup_check_gop_for_ux_capsule (HeaderSize bounds)
+ * ================================================================ */
+
+static void
+test_gop_headersize_too_small(void)
+{
+ mock_init();
+ UINT8 buf[256];
+ memset(buf, 0, sizeof(buf));
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)buf;
+ cap->HeaderSize = sizeof(EFI_CAPSULE_HEADER) - 1;
+ cap->CapsuleImageSize = sizeof(buf);
+
+ EFI_STATUS rc = fwup_check_gop_for_ux_capsule(NULL, cap, sizeof(buf));
+ assert(rc == EFI_INVALID_PARAMETER);
+}
+
+static void
+test_gop_capsule_image_size_less_than_header(void)
+{
+ mock_init();
+ UINT8 buf[256];
+ memset(buf, 0, sizeof(buf));
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)buf;
+ cap->HeaderSize = sizeof(EFI_CAPSULE_HEADER);
+ cap->CapsuleImageSize = sizeof(EFI_CAPSULE_HEADER) - 1;
+
+ EFI_STATUS rc = fwup_check_gop_for_ux_capsule(NULL, cap, sizeof(buf));
+ assert(rc == EFI_INVALID_PARAMETER);
+}
+
+static void
+test_gop_ux_payload_too_small(void)
+{
+ mock_init();
+ UINT8 buf[256];
+ memset(buf, 0, sizeof(buf));
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)buf;
+ cap->HeaderSize = sizeof(EFI_CAPSULE_HEADER);
+ cap->CapsuleImageSize = sizeof(EFI_CAPSULE_HEADER) +
sizeof(UX_CAPSULE_HEADER) - 1;
+
+ EFI_STATUS rc = fwup_check_gop_for_ux_capsule(NULL, cap, sizeof(buf));
+ assert(rc == EFI_INVALID_PARAMETER);
+}
+
+static void
+test_gop_fsize_too_small(void)
+{
+ mock_init();
+ UINTN fsize = sizeof(EFI_CAPSULE_HEADER) + sizeof(UX_CAPSULE_HEADER) -
1;
+ _cleanup_free UINT8 *buf = calloc(1, fsize);
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)buf;
+ cap->HeaderSize = sizeof(EFI_CAPSULE_HEADER);
+ cap->CapsuleImageSize = sizeof(EFI_CAPSULE_HEADER) +
sizeof(UX_CAPSULE_HEADER);
+
+ EFI_STATUS rc = fwup_check_gop_for_ux_capsule(NULL, cap, fsize);
+ assert(rc == EFI_INVALID_PARAMETER);
+}
+
+static void
+test_gop_headersize_exact_boundary(void)
+{
+ mock_init();
+ UINTN fsize = sizeof(EFI_CAPSULE_HEADER) + sizeof(UX_CAPSULE_HEADER);
+ _cleanup_free UINT8 *buf = calloc(1, fsize);
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)buf;
+ cap->HeaderSize = sizeof(EFI_CAPSULE_HEADER);
+ cap->CapsuleImageSize = fsize;
+
+ EFI_STATUS rc = fwup_check_gop_for_ux_capsule(NULL, cap, fsize);
+ assert(rc == EFI_UNSUPPORTED);
+}
+
+static void
+test_gop_headersize_one_past(void)
+{
+ mock_init();
+ UINTN fsize = sizeof(EFI_CAPSULE_HEADER) + sizeof(UX_CAPSULE_HEADER);
+ _cleanup_free UINT8 *buf = calloc(1, fsize);
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)buf;
+ cap->HeaderSize = sizeof(EFI_CAPSULE_HEADER) + 1;
+ cap->CapsuleImageSize = fsize;
+
+ EFI_STATUS rc = fwup_check_gop_for_ux_capsule(NULL, cap, fsize);
+ assert(rc == EFI_INVALID_PARAMETER);
+}
+
+/* ================================================================
+ * Group 4: fwup_populate_update_info (variable validation)
+ * ================================================================ */
+
+static void
+test_populate_info_too_small(void)
+{
+ mock_init();
+ FWUP_UPDATE_INFO info;
+ memset(&info, 0, sizeof(info));
+ mock_var_data = (UINT8 *)&info;
+ mock_var_data_size = sizeof(info) - 1;
+ mock_var_status = EFI_SUCCESS;
+
+ FWUP_UPDATE_TABLE out;
+ memset(&out, 0, sizeof(out));
+ EFI_STATUS rc = fwup_populate_update_info(L"test", &out);
+ assert(EFI_ERROR(rc));
+}
+
+static void
+test_populate_info_no_room_for_dp(void)
+{
+ mock_init();
+ FWUP_UPDATE_INFO info;
+ memset(&info, 0, sizeof(info));
+ mock_var_data = (UINT8 *)&info;
+ mock_var_data_size = sizeof(info);
+ mock_var_status = EFI_SUCCESS;
+
+ FWUP_UPDATE_TABLE out;
+ memset(&out, 0, sizeof(out));
+ EFI_STATUS rc = fwup_populate_update_info(L"test", &out);
+ assert(EFI_ERROR(rc));
+}
+
+static void
+test_populate_info_valid(void)
+{
+ mock_init();
+ UINTN dp_offset = EFI_FIELD_OFFSET(FWUP_UPDATE_INFO, dp);
+ /* need total > sizeof(FWUP_UPDATE_INFO) + sizeof(EFI_DEVICE_PATH)
+ * to pass the check at fwupdate.c:88 */
+ UINTN total = sizeof(FWUP_UPDATE_INFO) + sizeof(EFI_DEVICE_PATH) + 4;
+ _cleanup_free UINT8 *buf = calloc(1, total);
+
+ /* set up an 8-byte node + end-node device path at the dp field */
+ UINTN dp_space = total - dp_offset;
+ build_dp_node(buf + dp_offset, 0x01, 0x01, (UINT16)(dp_space - 4));
+ build_dp_end(buf + dp_offset + dp_space - 4);
+
+ mock_var_data = buf;
+ mock_var_data_size = total;
+ mock_var_attrs = EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS;
+ mock_var_status = EFI_SUCCESS;
+
+ FWUP_UPDATE_TABLE out;
+ memset(&out, 0, sizeof(out));
+ EFI_STATUS rc = fwup_populate_update_info(L"test", &out);
+ assert(rc == EFI_SUCCESS);
+ assert(out.info != NULL);
+ assert(out.name != NULL);
+ assert(out.size == total);
+
+ FreePool(out.info);
+ FreePool(out.name);
+}
+
+static void
+test_populate_info_dp_size_mismatch(void)
+{
+ mock_init();
+ UINTN dp_offset = EFI_FIELD_OFFSET(FWUP_UPDATE_INFO, dp);
+ UINTN total = dp_offset + 8; /* 8 bytes: room for a node + end */
+ _cleanup_free UINT8 *buf = calloc(1, total);
+
+ /* put just an end node (4 bytes) but claim 8 bytes of dp space */
+ build_dp_end(buf + dp_offset);
+
+ mock_var_data = buf;
+ mock_var_data_size = total;
+ mock_var_status = EFI_SUCCESS;
+
+ FWUP_UPDATE_TABLE out;
+ memset(&out, 0, sizeof(out));
+ EFI_STATUS rc = fwup_populate_update_info(L"test", &out);
+ /* dp_size returns 4 but remaining space is 8 -- mismatch */
+ assert(EFI_ERROR(rc));
+}
+
+static void
+test_populate_info_variable_not_found(void)
+{
+ mock_init();
+ mock_var_status = EFI_NOT_FOUND;
+
+ FWUP_UPDATE_TABLE out;
+ memset(&out, 0, sizeof(out));
+ EFI_STATUS rc = fwup_populate_update_info(L"test", &out);
+ assert(rc == EFI_NOT_FOUND);
+}
+
+/* ================================================================
+ * Group 5: capsule validation (CapsuleImageSize)
+ * ================================================================ */
+
+static void
+test_capsule_image_size_exceeds_file(void)
+{
+ UINTN fsize = 64;
+ _cleanup_free UINT8 *fbuf = calloc(1, fsize);
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)fbuf;
+ cap->CapsuleImageSize = (UINT32)(fsize + 1);
+
+ assert(EFI_ERROR(fwup_validate_capsule_size(cap, fsize)));
+}
+
+static void
+test_capsule_image_size_too_small(void)
+{
+ UINTN fsize = 128;
+ _cleanup_free UINT8 *fbuf = calloc(1, fsize);
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)fbuf;
+ cap->CapsuleImageSize = sizeof(EFI_CAPSULE_HEADER) - 1;
+
+ assert(EFI_ERROR(fwup_validate_capsule_size(cap, fsize)));
+}
+
+static void
+test_capsule_file_too_small(void)
+{
+ UINTN fsize = sizeof(EFI_CAPSULE_HEADER) - 1;
+ _cleanup_free UINT8 *fbuf = calloc(1, sizeof(EFI_CAPSULE_HEADER));
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)fbuf;
+ cap->CapsuleImageSize = sizeof(EFI_CAPSULE_HEADER);
+
+ assert(EFI_ERROR(fwup_validate_capsule_size(cap, fsize)));
+}
+
+static void
+test_capsule_image_size_valid(void)
+{
+ UINTN fsize = 128;
+ _cleanup_free UINT8 *fbuf = calloc(1, fsize);
+ EFI_CAPSULE_HEADER *cap = (EFI_CAPSULE_HEADER *)fbuf;
+ cap->CapsuleImageSize = (UINT32)fsize;
+
+ assert(!EFI_ERROR(fwup_validate_capsule_size(cap, fsize)));
+}
+
+/* ================================================================
+ * Group 6: fwup_delete_variable (NULL DataSize fix)
+ * ================================================================ */
+
+static void
+test_delete_variable_exists(void)
+{
+ mock_init();
+ /* fwup_delete_variable calls GetVariable with size=0 and data=NULL
+ * to retrieve attrs. For an existing variable with data, GetVariable
+ * returns EFI_BUFFER_TOO_SMALL. The function treats this as an error
+ * and returns it without calling SetVariable. */
+ mock_var_status = EFI_BUFFER_TOO_SMALL;
+
+ mock_set_variable_called = FALSE;
+ EFI_STATUS rc = fwup_delete_variable(L"test", &fwupdate_guid);
+ assert(rc == EFI_BUFFER_TOO_SMALL);
+ assert(mock_set_variable_called == FALSE);
+}
+
+static void
+test_delete_variable_not_found(void)
+{
+ mock_init();
+ mock_var_status = EFI_NOT_FOUND;
+ EFI_STATUS rc = fwup_delete_variable(L"test", &fwupdate_guid);
+ assert(rc == EFI_SUCCESS);
+}
+
+static void
+test_delete_variable_other_error(void)
+{
+ mock_init();
+ mock_var_status = EFI_UNSUPPORTED;
+ EFI_STATUS rc = fwup_delete_variable(L"test", &fwupdate_guid);
+ assert(rc == EFI_UNSUPPORTED);
+}
+
+/* ================================================================
+ * Group 7: fwup_get_variable
+ * ================================================================ */
+
+static void
+test_get_variable_success(void)
+{
+ mock_init();
+ UINT8 data[] = { 0x01, 0x02, 0x03, 0x04 };
+ mock_var_data = data;
+ mock_var_data_size = sizeof(data);
+ mock_var_attrs = EFI_VARIABLE_NON_VOLATILE;
+ mock_var_status = EFI_SUCCESS;
+
+ VOID *buf = NULL;
+ UINTN size = 0;
+ UINT32 attrs = 0;
+ EFI_STATUS rc = fwup_get_variable(L"test", &fwupdate_guid,
+ &buf, &size, &attrs);
+ assert(rc == EFI_SUCCESS);
+ assert(size == sizeof(data));
+ assert(attrs == EFI_VARIABLE_NON_VOLATILE);
+ assert(memcmp(buf, data, sizeof(data)) == 0);
+ FreePool(buf);
+}
+
+static void
+test_get_variable_not_found(void)
+{
+ mock_init();
+ mock_var_status = EFI_NOT_FOUND;
+
+ VOID *buf = NULL;
+ UINTN size = 0;
+ UINT32 attrs = 0;
+ EFI_STATUS rc = fwup_get_variable(L"test", &fwupdate_guid,
+ &buf, &size, &attrs);
+ assert(rc == EFI_NOT_FOUND);
+ assert(buf == NULL);
+}
+
+/* ================================================================
+ * Group 8: block descriptor terminator index fix
+ * ================================================================ */
+
+static void
+test_cbd_terminator_position(void)
+{
+ /* verify that the terminator is placed at position n_updates (j)
+ * not at position i when some capsules fail */
+ UINTN n = 4;
+ _cleanup_free EFI_CAPSULE_BLOCK_DESCRIPTOR *cbd = NULL;
+ cbd = calloc(n + 1, sizeof(EFI_CAPSULE_BLOCK_DESCRIPTOR));
+
+ /* simulate: 4 allocated entries, but only 2 succeeded (j=2) */
+ cbd[0].Length = 100;
+ cbd[0].Union.DataBlock = 0x1000;
+ cbd[1].Length = 200;
+ cbd[1].Union.DataBlock = 0x2000;
+
+ /* poison the slot so we know the write actually happens */
+ cbd[2].Length = 999;
+ cbd[2].Union.ContinuationPointer = 0xDEAD;
+
+ /* the fix places terminator at cbd[n_updates] where n_updates=j=2 */
+ UINTN n_updates = 2;
+ fwup_cbd_set_terminator(cbd, n_updates);
+
+ assert(cbd[2].Length == 0);
+ assert(cbd[2].Union.ContinuationPointer == 0);
+ /* entries 0 and 1 are valid */
+ assert(cbd[0].Length == 100);
+ assert(cbd[1].Length == 200);
+}
+
+/* ================================================================
+ * main
+ * ================================================================ */
+
+int
+main(void)
+{
+ printf("fwupd-efi self tests\n");
+
+ printf("device path size:\n");
+ RUN_TEST(test_dp_size_single_end_node);
+ RUN_TEST(test_dp_size_multiple_nodes);
+ RUN_TEST(test_dp_size_limit_too_small);
+ RUN_TEST(test_dp_size_node_exceeds_limit);
+ RUN_TEST(test_dp_size_exact_fit);
+ RUN_TEST(test_dp_size_zero_limit);
+ RUN_TEST(test_dp_size_zero_length_node);
+
+ printf("UX capsule checksum:\n");
+ RUN_TEST(test_checksum_zeroes);
+ RUN_TEST(test_checksum_known_values);
+ RUN_TEST(test_checksum_idempotent);
+
+ printf("GOP for UX capsule (HeaderSize bounds):\n");
+ RUN_TEST(test_gop_headersize_too_small);
+ RUN_TEST(test_gop_capsule_image_size_less_than_header);
+ RUN_TEST(test_gop_ux_payload_too_small);
+ RUN_TEST(test_gop_fsize_too_small);
+ RUN_TEST(test_gop_headersize_exact_boundary);
+ RUN_TEST(test_gop_headersize_one_past);
+
+ printf("populate update info:\n");
+ RUN_TEST(test_populate_info_too_small);
+ RUN_TEST(test_populate_info_no_room_for_dp);
+ RUN_TEST(test_populate_info_valid);
+ RUN_TEST(test_populate_info_dp_size_mismatch);
+ RUN_TEST(test_populate_info_variable_not_found);
+
+ printf("capsule validation:\n");
+ RUN_TEST(test_capsule_image_size_exceeds_file);
+ RUN_TEST(test_capsule_image_size_too_small);
+ RUN_TEST(test_capsule_file_too_small);
+ RUN_TEST(test_capsule_image_size_valid);
+
+ printf("delete variable:\n");
+ RUN_TEST(test_delete_variable_exists);
+ RUN_TEST(test_delete_variable_not_found);
+ RUN_TEST(test_delete_variable_other_error);
+
+ printf("get variable:\n");
+ RUN_TEST(test_get_variable_success);
+ RUN_TEST(test_get_variable_not_found);
+
+ printf("block descriptor terminator:\n");
+ RUN_TEST(test_cbd_terminator_position);
+
+ printf("\n%u/%u tests passed\n", tests_passed, tests_run);
+ return (tests_passed == tests_run) ? 0 : 1;
+}
diff -Nru fwupd-efi-1.7/efi/generate_binary.py
fwupd-efi-1.9/efi/generate_binary.py
--- fwupd-efi-1.7/efi/generate_binary.py 2024-09-20 08:13:31.000000000
-0500
+++ fwupd-efi-1.9/efi/generate_binary.py 2026-10-26 04:23:48.000000000
-0500
@@ -10,6 +10,8 @@
import subprocess
import sys
import argparse
+import tempfile
+import json
def _run_objcopy(args):
@@ -29,6 +31,10 @@
"-j",
".dynamic",
"-j",
+ ".dynstr",
+ "-j",
+ ".dynsym",
+ "-j",
".rodata",
"-j",
".areloc",
@@ -42,12 +48,8 @@
# Use "binary" instead, and add required symbols manually.
if args.objcopy_manualsymbols:
argv.extend(["-O", "binary"])
- elif args.os == "freebsd":
- # `--target` option is missing and --input-target doesn't recognize
- # "efi-app-*"
- argv.extend(["--output-target", "efi-app-{}".format(args.arch)])
else:
- argv.extend(["--target", "efi-app-{}".format(args.arch)])
+ argv.extend(["--output-target", "efi-app-{}".format(args.arch)])
try:
subprocess.run(argv, check=True)
@@ -68,6 +70,33 @@
sys.exit(1)
+def _run_pkgnote(args):
+
+ if not args.pkgnote:
+ return
+
+ # verify this is actually JSON
+ try:
+ _ = json.loads(args.pkgnote)
+ except json.JSONDecodeError as e:
+ print(f"Not valid JSON: {e}")
+ sys.exit(1)
+
+ # use `objdump -s -j .pkgnote` to verify
+ with tempfile.NamedTemporaryFile() as sfd:
+ sfd.write(args.pkgnote.encode() + b"\0")
+ sfd.flush()
+ argv = [
+ args.objcopy,
+ "--add-section",
+ ".pkgnote={}".format(sfd.name),
+ "--set-section-flags",
+ ".sbat=contents,alloc,load,readonly,data",
+ args.outfile,
+ ]
+ subprocess.run(argv, check=True)
+
+
def _add_nx_pefile(args):
# unnecessary if we have genpeimg
if args.genpeimg:
@@ -92,6 +121,7 @@
"--objcopy", default="objcopy", help="Binary file to use for objcopy"
)
parser.add_argument("--genpeimg", help="Binary file to use for genpeimg")
+ parser.add_argument("--pkgnote", help="Package metadata in JSON format")
parser.add_argument("--arch", default="x86_64", help="EFI architecture")
parser.add_argument("--os", help="OS type")
parser.add_argument(
@@ -103,6 +133,7 @@
parser.add_argument("outfile", help="Output file")
_args = parser.parse_args()
_run_objcopy(_args)
+ _run_pkgnote(_args)
_run_genpeimg(_args)
_add_nx_pefile(_args)
diff -Nru fwupd-efi-1.7/efi/lds/elf_loongarch64_efi.lds
fwupd-efi-1.9/efi/lds/elf_loongarch64_efi.lds
--- fwupd-efi-1.7/efi/lds/elf_loongarch64_efi.lds 2024-09-20
08:13:31.000000000 -0500
+++ fwupd-efi-1.9/efi/lds/elf_loongarch64_efi.lds 2026-10-26
04:23:48.000000000 -0500
@@ -3,37 +3,39 @@
ENTRY(_start)
SECTIONS
{
- .text 0 : {
- *(.text.head)
- . = ALIGN(4096);
+ . = 0;
+ ImageBase = .;
+ /* .hash and/or .gnu.hash MUST come first! */
+ .hash : { *(.hash) }
+ .gnu.hash : { *(.gnu.hash) }
+ . = ALIGN(4096);
+ .eh_frame : { *(.eh_frame) }
+ .eh_frame_hdr : { *(.eh_frame_hdr) }
+ .gcc_except_table : { *(.gcc_except_table*) }
+ . = ALIGN(4096);
+ .text : {
_text = .;
*(.text)
*(.text.*)
*(.gnu.linkonce.t.*)
*(.plt)
. = ALIGN(16);
- _evtext = .;
- . = ALIGN(4096);
_etext = .;
- } =0
- _text_vsize = _evtext - _text;
+ }
_text_size = _etext - _text;
. = ALIGN(4096);
_reloc = .;
.reloc : {
*(.reloc)
- _evreloc = .;
- . = ALIGN(4096);
_ereloc = .;
- } =0
- _reloc_vsize = _evreloc - _reloc;
+ }
_reloc_size = _ereloc - _reloc;
. = ALIGN(65536);
- _data = .;
.dynamic : { *(.dynamic) }
. = ALIGN(4096);
.data :
{
+ _data = .;
*(.sdata)
*(.data)
*(.data1)
@@ -78,13 +80,11 @@
*(.bss)
*(.bss.*)
*(COMMON)
+ *(.rel.local)
. = ALIGN(16);
_bss_end = .;
- _evdata = .;
- . = ALIGN(4096);
_edata = .;
- } =0
- _data_vsize = _evdata - _data;
+ }
_data_size = _edata - _data;
. = ALIGN(4096);
@@ -107,11 +107,8 @@
. = ALIGN(4096);
.rodata : {
*(.rodata*)
- _evrodata = .;
- . = ALIGN(4096);
_erodata = .;
- } =0
- _rodata_vsize = _evrodata - _rodata;
+ }
_rodata_size = _erodata - _rodata;
/*
* Note that _sbat must be the beginning of the data, and _esbat must be the
@@ -129,7 +126,7 @@
_esbat = .;
. = ALIGN(4096);
_epsbat = .;
- } =0
+ }
_sbat_size = _epsbat - _sbat;
_sbat_vsize = _esbat - _sbat;
. = ALIGN(4096);
@@ -137,12 +134,9 @@
{
_sbom = .;
*(.sbom)
- _esbom = .;
- . = ALIGN(4096);
_epsbom = .;
- } =0
+ }
_sbom_size = _epsbom - _sbom;
- _sbom_vsize = _esbom - _sbom;
_image_end = .;
_alldata_size = _image_end - _reloc;
@@ -152,15 +146,6 @@
.dynstr : { *(.dynstr) }
. = ALIGN(4096);
.note.gnu.build-id : { *(.note.gnu.build-id) }
- . = ALIGN(4096);
- .hash : { *(.hash) }
- . = ALIGN(4096);
- .gnu.hash : { *(.gnu.hash) }
- . = ALIGN(4096);
- .eh_frame : { *(.eh_frame) }
- .eh_frame_hdr : { *(.eh_frame_hdr) }
- . = ALIGN(4096);
- .gcc_except_table : { *(.gcc_except_table*) }
/DISCARD/ :
{
*(.rela.reloc)
@@ -168,4 +153,3 @@
}
.comment 0 : { *(.comment) }
}
-
diff -Nru fwupd-efi-1.7/efi/meson.build fwupd-efi-1.9/efi/meson.build
--- fwupd-efi-1.7/efi/meson.build 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/efi/meson.build 2026-10-26 04:23:48.000000000 -0500
@@ -37,11 +37,10 @@
efi_libdir = get_option('efi-libdir')
if efi_libdir == ''
- fs = import('fs')
multi = run_command(cc.cmd_array(), '-print-multi-os-directory', check:
true).stdout().strip()
efi_libdir = join_paths('/usr/lib/', multi)
if not fs.is_dir(join_paths(efi_libdir, 'gnuefi'))
- efi_libdir = '/usr/lib'
+ efi_libdir = gnuefi.get_variable('libdir', default_value: '/usr/lib')
endif
endif
@@ -55,28 +54,23 @@
arch_crt = 'crt0.o'
if efi_ldsdir == ''
efi_ldsdir = join_paths(efi_libdir, 'gnuefi', gnu_efi_path_arch)
- cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds), check:
false)
- if cmd.returncode() != 0
+ if not fs.is_file(efi_ldsdir / arch_lds)
arch_lds = 'elf_@0@@1@_efi.lds'.format(gnu_efi_path_arch, lds_os)
arch_crt = 'crt0-efi-@[email protected]'.format(gnu_efi_path_arch)
efi_ldsdir = join_paths(efi_libdir, 'gnuefi')
- cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds), check:
false)
endif
- if cmd.returncode() != 0
+ if not fs.is_file(efi_ldsdir / arch_lds)
efi_ldsdir = efi_libdir
- cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds), check:
false)
- if cmd.returncode() != 0
+ if not fs.is_file(efi_ldsdir / arch_lds)
error('Cannot find @0@'.format(arch_lds))
endif
endif
else
- cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds), check:
false)
- if cmd.returncode() != 0
+ if not fs.is_file(efi_ldsdir / arch_lds)
arch_lds = 'elf_@0@@1@_efi.lds'.format(gnu_efi_path_arch, lds_os)
arch_crt = 'crt0-efi-@[email protected]'.format(gnu_efi_path_arch)
- cmd = run_command('test', '-f', join_paths(efi_ldsdir, arch_lds), check:
false)
endif
- if cmd.returncode() != 0
+ if not fs.is_file(efi_ldsdir / arch_lds)
error('Cannot find @0@'.format(arch_lds))
endif
endif
@@ -91,10 +85,10 @@
# older objcopy for Aarch64, ARM32 and RISC-V are not EFI capable.
# Use 'binary' instead, and add required symbols manually.
-if host_cpu == 'arm' or host_cpu == 'riscv64' or (host_cpu == 'aarch64' and
(objcopy_version.version_compare ('< 2.38') or coff_header_in_crt0 or
uswid.found()))
- objcopy_manualsymbols = true
- generate_binary_extra = ['--objcopy-manualsymbols']
-elif host_cpu == 'loongarch64' and (objcopy_version.version_compare ('< 2.41')
or coff_header_in_crt0)
+if ( host_cpu == 'arm'
+ or (host_cpu == 'riscv64' and (objcopy_version.version_compare ('<
2.42') or coff_header_in_crt0))
+ or (host_cpu == 'aarch64' and (objcopy_version.version_compare ('<
2.38') or coff_header_in_crt0 or uswid.found()))
+ or (host_cpu == 'loongarch64' and (objcopy_version.version_compare ('<
2.41') or coff_header_in_crt0)) )
objcopy_manualsymbols = true
generate_binary_extra = ['--objcopy-manualsymbols']
else
@@ -197,6 +191,9 @@
'-mno-mmx',
'-mno-red-zone',
'-m32']
+elif host_cpu == 'loongarch64'
+ compile_args += ['-mno-lsx',
+ '-mno-lasx']
# no special cases for aarch64 or arm
endif
@@ -226,7 +223,7 @@
endif
efi_format = ['-O', 'binary']
else
- efi_format = ['--target=efi-app-@0@'.format(gnu_efi_arch)]
+ efi_format = ['--output-target=efi-app-@0@'.format(gnu_efi_arch)]
endif
libgcc_file_name = run_command(cc.cmd_array(), '-print-libgcc-file-name',
check: true).stdout().strip()
@@ -305,16 +302,11 @@
fwupd_so_deps += [o_crt0]
endif
-efi_cc_ldflags = []
-foreach flag : efi_ldflags
- efi_cc_ldflags += ['-Wl,' + flag]
-endforeach
-
so = custom_target('fwup.so',
input : o_files,
output : 'fwup.so',
- command : [cc.cmd_array(), '-nostdlib', '-o', '@OUTPUT@'] +
- efi_cc_ldflags + ['@INPUT@'] +
+ command : [cc_ld, '-nostdlib', '-o', '@OUTPUT@'] +
+ efi_ldflags + ['@INPUT@'] +
['-lefi', '-lgnuefi', libgcc_file_name],
depends: fwupd_so_deps)
@@ -327,6 +319,7 @@
'--arch', gnu_efi_arch,
'--os', host_machine.system(),
'--objcopy', objcopy,
+ '--pkgnote', get_option('pkgnote'),
'--genpeimg', genpeimg.found() ? genpeimg : ''
]
+ generate_binary_extra,
@@ -341,6 +334,8 @@
'-j', '.sdata',
'-j', '.data',
'-j', '.dynamic',
+ '-j', '.dynstr',
+ '-j', '.dynsym',
'-j', '.rodata',
'-j', '.rel*',
'-j', '.rela*',
@@ -352,3 +347,47 @@
['@INPUT@', '@OUTPUT@'],
install : false,
install_dir : debugdir)
+
+selftest = executable('fwup-selftest',
+ 'fwup-selftest.c',
+ c_args: [
+ '-DDEBUGDIR="/mock/"',
+ '-Wno-unused-function',
+ '-Wno-unused-variable',
+ '-Wno-unused-parameter',
+ '-Wno-address-of-packed-member',
+ ],
+ include_directories: include_directories('mock-include', '.'),
+ native: true,
+)
+test('fwup-selftest', selftest)
+
+if get_option('fuzzing')
+ fuzz_c_args = [
+ '-DDEBUGDIR="/mock/"',
+ '-Wno-unused-function',
+ '-Wno-unused-variable',
+ '-Wno-unused-parameter',
+ '-Wno-address-of-packed-member',
+ '-fsanitize=fuzzer,address,undefined',
+ '-fno-sanitize-recover=all',
+ ]
+ fuzz_link_args = ['-fsanitize=fuzzer,address,undefined']
+ fuzz_incdirs = include_directories('mock-include', '.')
+
+ executable('fwup-fuzz-update-info',
+ 'fwup-fuzz-update-info.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ include_directories: fuzz_incdirs,
+ native: true,
+ )
+
+ executable('fwup-fuzz-capsule',
+ 'fwup-fuzz-capsule.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ include_directories: fuzz_incdirs,
+ native: true,
+ )
+endif
diff -Nru fwupd-efi-1.7/efi/mock-include/config.h
fwupd-efi-1.9/efi/mock-include/config.h
--- fwupd-efi-1.7/efi/mock-include/config.h 1969-12-31 18:00:00.000000000
-0600
+++ fwupd-efi-1.9/efi/mock-include/config.h 2026-10-26 04:23:48.000000000
-0500
@@ -0,0 +1,4 @@
+#pragma once
+#ifndef PACKAGE_VERSION
+#define PACKAGE_VERSION "selftest"
+#endif
diff -Nru fwupd-efi-1.7/efi/mock-include/efi.h
fwupd-efi-1.9/efi/mock-include/efi.h
--- fwupd-efi-1.7/efi/mock-include/efi.h 1969-12-31 18:00:00.000000000
-0600
+++ fwupd-efi-1.9/efi/mock-include/efi.h 2026-10-26 04:23:48.000000000
-0500
@@ -0,0 +1,2 @@
+#pragma once
+#include "../efi-mock.h"
diff -Nru fwupd-efi-1.7/efi/mock-include/efilib.h
fwupd-efi-1.9/efi/mock-include/efilib.h
--- fwupd-efi-1.7/efi/mock-include/efilib.h 1969-12-31 18:00:00.000000000
-0600
+++ fwupd-efi-1.9/efi/mock-include/efilib.h 2026-10-26 04:23:48.000000000
-0500
@@ -0,0 +1,2 @@
+#pragma once
+#include "../efi-mock.h"
diff -Nru fwupd-efi-1.7/.github/workflows/main.yml
fwupd-efi-1.9/.github/workflows/main.yml
--- fwupd-efi-1.7/.github/workflows/main.yml 2024-09-20 08:13:31.000000000
-0500
+++ fwupd-efi-1.9/.github/workflows/main.yml 2026-10-26 04:23:48.000000000
-0500
@@ -10,9 +10,9 @@
jobs:
pre-commit:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/[email protected]
- name: Refresh dependencies
run: sudo apt update
- name: Install dependencies
@@ -29,9 +29,13 @@
strategy:
fail-fast: false
matrix:
- os: [fedora, debian-x86_64, arch, debian-i386]
+ os:
+ - arch-amd64
+ - debian-amd64
+ - debian-amd64-i386
+ - fedora-amd64
steps:
- - uses: actions/checkout@v4
+ - uses: actions/[email protected]
- name: Docker login
run: docker login docker.pkg.github.com -u $GITHUB_ACTOR -p
$GITHUB_TOKEN
env:
@@ -39,7 +43,6 @@
- name: Build in container
env:
CI_NETWORK: true
- CI: true
run: |
echo $GITHUB_WORKSPACE
- docker run --privileged -e CI=true -t -v
$GITHUB_WORKSPACE:/github/workspace
docker.pkg.github.com/fwupd/fwupd/fwupd-${{matrix.os}}:latest ./contrib/ci.sh
+ docker run --privileged -e CI=true -t -v
$GITHUB_WORKSPACE:/github/workspace "ghcr.io/fwupd/fwupd/fwupd-${{ matrix.os
}}:latest" ./contrib/ci.sh
diff -Nru fwupd-efi-1.7/.github/workflows/scorecard.yml
fwupd-efi-1.9/.github/workflows/scorecard.yml
--- fwupd-efi-1.7/.github/workflows/scorecard.yml 2024-09-20
08:13:31.000000000 -0500
+++ fwupd-efi-1.9/.github/workflows/scorecard.yml 2026-10-26
04:23:48.000000000 -0500
@@ -32,12 +32,12 @@
steps:
- name: "Checkout code"
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
+ uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a #v4.1.1
with:
persist-credentials: false
- name: "Run analysis"
- uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 #
v2.4.0
+ uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e #
v2.5.0
with:
results_file: results.sarif
results_format: sarif
@@ -59,7 +59,7 @@
# Upload the results as artifacts (optional). Commenting out will
disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
- uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874
# v4.4.0
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
# v7.0.2
with:
name: SARIF file
path: results.sarif
@@ -68,6 +68,6 @@
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code
Scanning dashboard
- name: "Upload to code-scanning"
- uses:
github/codeql-action/upload-sarif@8214744c546c1e5c8f03dde8fab3a7353211988d #
v3.26.7
+ uses:
github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee #
v3.29.5
with:
sarif_file: results.sarif
diff -Nru fwupd-efi-1.7/meson.build fwupd-efi-1.9/meson.build
--- fwupd-efi-1.7/meson.build 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/meson.build 2026-10-26 04:23:48.000000000 -0500
@@ -1,14 +1,17 @@
project('fwupd-efi', 'c',
- version : '1.7',
+ version : '1.9',
license : 'LGPL-2.1+',
- meson_version : '>=0.62.0',
+ meson_version : '>=0.58.2',
default_options : ['warning_level=2', 'c_std=c99'],
)
+fs = import('fs')
+
conf = configuration_data()
conf.set_quoted('PACKAGE_VERSION', meson.project_version())
cc = meson.get_compiler('c')
+cc_ld = find_program(cc.get_linker_id())
objcopy = find_program('objcopy')
objcopy_version = run_command(objcopy, '--version', check:
true).stdout().split('\n')[0].split(' ')[-1]
@@ -28,7 +31,12 @@
efi_app_location = join_paths(libexecdir, 'fwupd', 'efi')
host_cpu = host_machine.cpu_family()
-if host_cpu == 'x86'
+EFI_MACHINE_TYPE_NAME = gnuefi.get_variable('efi_machine_type_name',
default_value: '')
+gnu_efi_arch = gnuefi.get_variable('gnu_efi_arch', default_value: '')
+if EFI_MACHINE_TYPE_NAME != '' and gnu_efi_arch != ''
+ message('pkg-config EFI_MACHINE_TYPE_NAME: ' + EFI_MACHINE_TYPE_NAME)
+ message('pkg-config gnu_efi_arch: ' + gnu_efi_arch)
+elif host_cpu == 'x86'
EFI_MACHINE_TYPE_NAME = 'ia32'
gnu_efi_arch = 'ia32'
elif host_cpu == 'x86_64'
@@ -66,6 +74,9 @@
name : meson.project_name(),
filebase : 'fwupd-efi',
description : 'fwupd-efi is the UEFI binary used with fwupd for installing
UEFI firmware updates',
+ variables : {
+ 'gnuefi': gnuefi.version(),
+ },
)
fwupd_efi_dep = declare_dependency()
@@ -73,7 +84,7 @@
# SBOM support
uswid = find_program('uswid', required: false)
if not uswid.found()
- warning('uswid is unfound, so not embedding uSWID metadata')
+ warning('uswid is not found, so not embedding uSWID metadata')
endif
subdir('efi')
diff -Nru fwupd-efi-1.7/meson_options.txt fwupd-efi-1.9/meson_options.txt
--- fwupd-efi-1.7/meson_options.txt 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/meson_options.txt 2026-10-26 04:23:48.000000000 -0500
@@ -1,11 +1,13 @@
option('efi-libdir', type : 'string', description : 'path to the EFI lib
directory')
option('efi-ldsdir', type : 'string', description : 'path to the EFI lds
directory')
option('efi-includedir', type : 'string', value : '/usr/include/efi',
description : 'path to the EFI header directory')
-option('efi_sbat_fwupd_generation', type : 'integer', value : 1, description :
'SBAT fwupd generation')
+option('efi_sbat_fwupd_generation', type : 'integer', value : 2, description :
'SBAT fwupd generation')
option('efi_sbat_distro_id', type : 'string', value : '', description : 'SBAT
distribution ID, e.g. fedora')
option('efi_sbat_distro_summary', type : 'string', value : '', description :
'SBAT distribution summary, e.g. Fedora')
option('efi_sbat_distro_pkgname', type : 'string', value : '', description :
'SBAT distribution package name, e.g. fwupd')
option('efi_sbat_distro_version', type : 'string', value : '', description :
'SBAT distribution version, e.g. fwupd-1.5.6.fc33')
option('efi_sbat_distro_url', type : 'string', value : '', description : 'SBAT
distribution URL, e.g. https://src.fedoraproject.org/rpms/fwupd')
+option('pkgnote', type : 'string', value : '', description : 'Package metadata
in JSON format')
option('genpeimg', type : 'feature', description : 'Use genpeimg to add NX
support to binaries')
option('python', type : 'string', description : 'the absolute path of the
python3 binary')
+option('fuzzing', type : 'boolean', value : false, description : 'Build fuzz
targets (requires clang -fsanitize=fuzzer)')
diff -Nru fwupd-efi-1.7/.pre-commit-config.yaml
fwupd-efi-1.9/.pre-commit-config.yaml
--- fwupd-efi-1.7/.pre-commit-config.yaml 2024-09-20 08:13:31.000000000
-0500
+++ fwupd-efi-1.9/.pre-commit-config.yaml 2026-10-26 04:23:48.000000000
-0500
@@ -1,11 +1,11 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.1.0
+ rev: v6.0.0
hooks:
- id: no-commit-to-branch
args: [--branch, main]
- id: check-added-large-files
- - id: check-byte-order-marker
+ - id: fix-byte-order-marker
- id: check-executables-have-shebangs
- id: forbid-new-submodules
- id: check-yaml
@@ -21,12 +21,12 @@
- id: mixed-line-ending
args: [--fix=lf]
- repo: https://github.com/codespell-project/codespell
- rev: v2.1.0
+ rev: v2.4.3
hooks:
- id: codespell
args: ['--config', './contrib/codespell.cfg', --write-changes]
- repo: https://github.com/ambv/black
- rev: 22.3.0
+ rev: 26.10.1
hooks:
- id: black
- repo: local
@@ -36,13 +36,8 @@
language: system
entry: shellcheck --severity=error -e SC2068
types: [shell]
- - id: clang-format
- name: clang-format
- language: script
- entry: ./contrib/reformat-code.py
- types: [c]
- repo: https://github.com/igorshubovych/markdownlint-cli
- rev: v0.31.1
+ rev: v0.49.1
hooks:
- id: markdownlint
args: ['--fix', '--ignore', '.github']
diff -Nru fwupd-efi-1.7/README.md fwupd-efi-1.9/README.md
--- fwupd-efi-1.7/README.md 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/README.md 2026-10-26 04:23:48.000000000 -0500
@@ -41,6 +41,24 @@
ninja -C build install
```
+## Fuzzing
+
+Fuzz targets are available for the capsule and update-info parsing code.
+Building them requires `clang` with libFuzzer support:
+
+```bash
+CC=clang meson setup build-fuzz -Dfuzzing=true -Defi-libdir=/usr/lib
-Defi-ldsdir=/usr/lib
+ninja -C build-fuzz
+```
+
+Then run a target with an optional corpus directory:
+
+```bash
+mkdir -p corpus/update-info corpus/capsule
+./build-fuzz/efi/fwup-fuzz-update-info corpus/update-info
+./build-fuzz/efi/fwup-fuzz-capsule corpus/capsule
+```
+
## UEFI SBAT Support
The packager should also specify the SBAT metadata required for the secure boot
diff -Nru fwupd-efi-1.7/RELEASE fwupd-efi-1.9/RELEASE
--- fwupd-efi-1.7/RELEASE 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/RELEASE 2026-10-26 04:23:48.000000000 -0500
@@ -2,30 +2,23 @@
Write release entries:
-git log --format="%s" --cherry-pick --right-only 1.6... | grep -i -v trivial |
grep -v Merge | sort | uniq
+git log --format="%s" --cherry-pick --right-only 1.8... | grep -i -v trivial |
grep -v Merge | sort | uniq
Add any user visible changes into
../contrib/org.freedesktop.fwupd.efi.metainfo.xml
appstream-util appdata-to-news
../contrib/org.freedesktop.fwupd.efi.metainfo.xml > NEWS
2. Commit changes to git:
# MAKE SURE THIS IS CORRECT
-export release_ver="1.7"
+export release_ver="1.9"
git commit -a -m "Release fwupd-efi ${release_ver}" --no-verify
git tag -s -f -m "Release fwupd-efi ${release_ver}" "${release_ver}"
-<wait>
-git push --tags
-git push
-
-3. Generate the tarball:
-
ninja dist
-
-3a. Generate the additional verification metadata
-
gpg -b -a meson-dist/fwupd-efi-${release_ver}.tar.xz
+git push --tags
+git push
-4. Create release and upload tarball to https://github.com/fwupd/fwupd-efi/tags
+4. Create release on https://github.com/fwupd/fwupd-efi/tags
5. Do post release version bump in meson.build
diff -Nru fwupd-efi-1.7/SECURITY.md fwupd-efi-1.9/SECURITY.md
--- fwupd-efi-1.7/SECURITY.md 2024-09-20 08:13:31.000000000 -0500
+++ fwupd-efi-1.9/SECURITY.md 2026-10-26 04:23:48.000000000 -0500
@@ -3,6 +3,89 @@
Due to the nature of what we are doing, fwupd takes security very seriously.
If you have any concerns please let us know.
+## Considerations For Secure Boot
+
+In most secure boot configurations `fwup*.efi` will be signed and run by a
first-stage bootloader
+(e.g. Shim) and it is thus critically important that fwup-efi is not allowed
to run unauthenticated
+or untrusted code.
+
+The following sections contain considerations about what the EFI binary
actually does, which
+hopefully should give enough assurance that using `fwupd.efi` is thus no
threat to the chain of
+trust required by secure boot.
+
+### General Logic Flow
+
+```mermaid
+ flowchart TD
+ 1[Find and validate variables]
+ 2[Build the capsule data]
+ 3[Update the state variables]
+ 4[Apply the capsules]
+ 5[Reset the machine]
+ 1-->2-->3-->4-->5
+```
+
+* Find and validate update state variables:
+
+ * Read the list of EFI variables, and match the ones with the fwupd GUID
(`0abba7dc-e516-4167-bbf5-4d9d1c739416`).
+ * For each matching variable, parse this data as a
+
[`FWUP_UPDATE_INFO`](https://raw.githubusercontent.com/fwupd/fwupd-efi/refs/heads/main/efi/fwup-efi.h)
+ structure, being mindful the
+ variable is big enough to copy, and that the device path is valid.
+ * On any error abort, returning to the EFI firmware.
+
+* Build our data structure and add the capsules to it:
+
+ * Read the capsule file from the ESP, loading it into a dynamically
allocated buffer.
+ * If the capsule is a UX capsule (containing the pre-generated localized
bitmap image of
+ “Installing firmware update…”) then additionally verify the checksum is
valid.
+ * On any error abort, returning to the EFI firmware.
+
+* Update the state variables:
+
+ * Set the `FWUP_UPDATE_INFO.status` field to `ATTEMPTED` and overwrite each
variable found in step 1.
+ * On any error abort, returning to the EFI firmware.
+
+* Apply the capsules:
+
+ * Query the runtime machine capsule capabilities to get the reset kind,
otherwise assume `EfiResetWarm`
+ * Call the runtime “UpdateCapsule” interface with the capsule data as found
in step 2.
+ * On any error abort, returning to the EFI firmware.
+
+* Reset the machine, unless step 4 did this for us automatically.
+
+ * On any error abort, returning to the EFI firmware.
+
+### Unauthenticated Code
+
+There is no additional code launched directly from `fwupd.efi`.
+
+### User Input
+
+There is no user input processed during the execution of `fwupd.efi`.
+
+### Programmatic Input
+
+There are two inputs that are loaded:
+
+* EFI variables with the update state, GUID and paths to the payload in the
ESP – and then
+ overwritten with modified contents.
+* The firmware capsules from the ESP, loaded read only into memory and not
verified directly.
+
+The most important takeaway from this is that `fwupd.efi` does not *execute*
the capsule itself,
+but instead calls the firmware capsule loader runtime interface which tells
the firmware to copy
+the structured data into some part of the protected memory that will survive a
reboot.
+It then reboots the machine, causing the machine to re-initialize the
firmware, which in turn
+verifies the signature of the capsule payload and flashes the contents to
NVRAM.
+The machine then reboots again, re-initializing the firmware and booting to
the OS.
+
+For all vendors correctly implementing UEFI capsule updates, it is not
possible to flash unsigned
+capsules, or capsules signed by the wrong platform key.
+The platform key is per-vendor and sometimes even specific to the machine
type. Writing to efivarfs
+requires root access, and the worst case scenario for a malicious user without
the platform
+certificate would be that they could schedule a capsule and cause the machine
to quickly reboot
+twice when the user next manually restarted – but no firmware update would
happen.
+
## Supported Versions
The `1.3.x`, `1.2.x` and `1.1.x` branches are fully supported by the upstream
authors.