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.

Reply via email to