Skip to content

qcom-capsule: inject the OEM root cert with qdte-lite, before signing - #3043

Open
Igor Opaniuk (igoropaniuk) wants to merge 5 commits into
qualcomm-linux:masterfrom
igoropaniuk:feat/qdte-lite-capsule-2456
Open

qcom-capsule: inject the OEM root cert with qdte-lite, before signing#3043
Igor Opaniuk (igoropaniuk) wants to merge 5 commits into
qualcomm-linux:masterfrom
igoropaniuk:feat/qdte-lite-capsule-2456

Conversation

@igoropaniuk

@igoropaniuk Igor Opaniuk (igoropaniuk) commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

The OEM capsule root certificate lives in a device tree embedded in a boot config ELF (xbl_config.elf/uefi_dtbs.elf), and getting it there constrains the whole pipeline. Three things must happen in this order:

  1. Inject QcCapsuleRootCert into the config ELF (xbl_config.elf/uefi_dtbs.elf)
  2. sign the result - editing a DTB inside the ELF invalidates any signature it carried (this is what planned to land in [RFC] qcom-firmware-sign: build-time secure-boot signing of Qualcomm firmware #2598)
  3. build the capsule, which the firmware verifies against the certificate from step 1

qcom-capsule.bbclass can't own step 1: by the time it runs, the boot firmware is deployed and whatever signs it has finished. There's no seam left. This series moves injection into its own recipe between the boot firmware and the capsule, which creates one - it stages the injected ELFs and stops, leaving do_compile/do_deploy for a signing step to sit between. That's what makes the secure-boot work in #2598 able to slot in without rewiring anything.

Injection of the root certification (stored in QcCapsuleRootCert property) is done by qdte-lite instead of a fixed dump/set-property/replace sequence. The class no longer needs to know anything about the container:

target=$(qdte-lite --nogui --input_file "${XBL}" --find_property QcCapsuleRootCert)
qcom-capsule-tool bin-to-hex "${CAPSULE_ROOT_CER}" "${ROOT_INC}"
qdte-lite --nogui --input_file "${XBL}" --output_path "${OUT}" \
    --output_file xbl_config.elf --modify "${target}=@list:${ROOT_INC}"

DTB names are assigned during disassembly - from container metadata in one container, from /compatible in another - so asking the tool that assigns them beats configuring them per machine. XBLCONFIG_DTB and XBLCONFIG_DTB_SECTION go away.

Also the capsule flow leaves meta-arm. It lived there only for edk2-basetools (GenFfs, GenFv, GenerateCapsule.py). cbsp-boot-utilities now has its own equivalents, so the dependency and the reason for the dynamic layer are both gone.

Additionally two bugs are fixed:

  • qcom-capsule.bbclass tested for a literal xbl_config.elf, but QCOM_XBL_CONFIG is xbl_config_kvm.elf on kvm machines like iq-9075-evk - injection was silently skipped there, surfacing much later as a capsule the firmware refuses. Now uses QCOM_XBL_CONFIG throughout.
    CAPSULE_ENTRY_dtb[...] flags were unqualified. Varflags take no part in override resolution, so hamoa's SPINOR destinations applied to every machine, and generate_fvupdate() doesn't catch it. Now guarded on MACHINEOVERRIDES.

Can be build with this config (example for RB3Gen2):

kas build ci/rb3gen2-core-kit.yml:ci/capsule.yml:ci/capsule-test-keys.yml

The capsule root certificate lives in a device tree embedded in a boot
config ELF, so putting it there means editing a DTB inside a container
the build has no tool to open. cbsp-boot-utilities can do it, but only
through a fixed dump / set-property / replace sequence that has to be
told which DTB to touch.

qdte-lite opens these containers directly and can be asked what is in
them, which is what the following commits need to find the certificate
without hardcoding per-machine names.

Packaging it is cheap because the fork was made for this: a pyproject
with a console script, child interpreters spawned via sys.executable, and
a --nogui path that never imports a GUI toolkit. Its device-tree layer is
pylibfdt, so oe-core's python3-dtc is the only dependency and the recipe
stays out of any dynamic layer.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
The capsule recipes sat in dynamic-layers/meta-arm for one reason: they
needed edk2-basetools for GenFfs, GenFv and GenerateCapsule.py. That made
capsule generation unavailable unless a consumer also carried meta-arm,
which has nothing else to do with building a capsule.

cbsp-boot-utilities has since grown its own equivalents -- fv_builder.py
for the first two, generate-capsule for the third, byte-identical for
this subset -- so the dependency is gone and with it the only reason for
the recipes to live behind a layer that may not be present.

Bump to pick that up and move them into the normal recipe tree. meta-arm
is still needed for optee and trusted-firmware-a, so the layer stays;
only the capsule pieces leave it.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
…gned

Three things have to happen in one order and cannot be reordered: the
certificate goes into the boot config ELF, that ELF is signed, then the
capsule is built and verified against the certificate. Editing a DTB
inside the ELF invalidates any signature it already carried, so injecting
after signing produces an image the boot ROM rejects; building the
capsule before injection produces one the firmware will not authenticate.

qcom-capsule.bbclass is the wrong place for step one, because by the time
it runs the boot firmware is already deployed and whatever signs it has
already finished. There is no seam left. A separate recipe between the
boot firmware and the capsule creates one: it stages the injected ELFs
and stops, leaving do_compile and do_deploy for a signing step to sit
between.

Injection itself no longer needs the class to know anything about the
container. The DTB names are assigned during disassembly -- from
container metadata in one case, from /compatible in another -- so asking
the tool that assigns them beats configuring them per machine, which is
what XBLCONFIG_DTB and XBLCONFIG_DTB_SECTION were doing and why they go
away. Leaving the DER-to-cells conversion to bin-to-hex keeps the padding
of a trailing partial cell in one place, where the two tools cannot
disagree about it.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
Hamoa and similar SPINOR-boot parts have no xbl_config.elf at all: their
QcCapsuleRootCert lives in uefi_dtbs.elf, shipped xz-compressed. Without
this the injection finds nothing to patch on those machines and the
capsule is rejected at authentication time, with nothing in the build to
suggest why.

The container also holds the property more than once -- in a base DTB and
in a .dtbo overlay, at different node paths -- so anything assuming a
single target would silently patch half of it. --find_property already
reports each as its own line and --modify takes them in one pass, so
handling hamoa needs no machinery beyond staging the file.

It deploys under its own name for the same reason xbl_config does: the
boot firmware recipe owns the unmodified copy, and two recipes cannot
deploy the same filename. image_types_qcom then prefers the cert-bearing
one over the QCOM_UEFI_DTB variant, because a device flashed with the
other will not take an update.

Signed-off-by: Xueqian Nie <xueqian.nie@oss.qualcomm.com>
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
CAPSULE_FLASH_TYPE and CAPSULE_ENTRIES next to these definitions are
machine-qualified; the CAPSULE_ENTRY_dtb[...] flags beside them are not,
and cannot be -- varflags take no part in override resolution, so
CAPSULE_ENTRY_dtb[dest_disk]:iq-x7181-evk does not exist.

They therefore apply on every machine. Any other board that declares a
"dtb" capsule entry inherits hamoa's SPINOR destinations, and nothing
catches it: generate_fvupdate() only checks that an entry has a binary, a
dest_disk and a dest_partition, all of which hamoa's values supply. The
build succeeds and produces a capsule aimed at storage the machine may
not even have.

Guarding on MACHINEOVERRIDES gives the flags the scope the neighbouring
overrides already have. Renaming the entry would also work, but the class
keys the kernel dependency on the literal name "dtb".

Fixes: a314263 ("firmware-qcom-capsule: add iq-x7181-evk capsule entry definitions")
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
@igoropaniuk

Copy link
Copy Markdown
Contributor Author

xueqnie could you please take a look, thanks!

@github-actions

Copy link
Copy Markdown

Test run workflow

Test jobs for commit 0e66a76

qcom-distro
Pass: 262 | Fail: 58 | Total: 342
nodistro
Pass: 10 | Fail: 0 | Total: 10
qcom-distro_linux-qcom-6.18
Pass: 8 | Fail: 0 | Total: 8

@test-reporting-app

Copy link
Copy Markdown

Test Results

   79 files    409 suites   12h 16m 28s ⏱️
  155 tests   106 ✅  0 💤 49 ❌
2 629 runs  2 515 ✅ 22 💤 92 ❌

For more details on these failures, see this check.

Results for commit 0e66a76.

@qcomlnxci

Copy link
Copy Markdown

Test Coral run workflow

Test jobs for commit 0e66a76

  • qcomdistro: multimedia image-prop
    Pass: 35 | Fail: 0 | Others: 1 | Total: 36
  • qcomdistro: multimedia image
    Pass: 9 | Fail: 0 | Total: 9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants