Skip to content

Add support for MBN header v7 #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,17 @@ The `-v`/`--version` option must be set correctly depending on the chipset:
- **`-v3` (default):** MSM8916, MSM8939, MSM8953
- MSM8909, MDM9607 (they usually accept unsigned ELF images as well)
- **`-v5`:** MSM8998, SDM845
- **`-v6`:** SM8150
- **`-v6`:** SM8150, IPQ9574, IPQ5332
- In case of problems, try using `-v5` instead. Sometimes both seem to be supported.
- **`-v7`:** IPQ5424

The list of chipsets is not complete, most other Qualcomm chipsets likely use one of the already supported
versions above or an older/newer version that is not supported yet by [qtestsign].

## Supported firmware
Qualcomm's own signing tool is proprietary and not publicly available. [qtestsign] was created to allow
building open-source firmware projects without access to Qualcomm's tool, e.g.:
- `aboot`: [U-Boot] bootloader (for DragonBoard 410c)
- `aboot`: [U-Boot] bootloader (for DragonBoard 410c, QCA SoCs etc..)
- `aboot`: [Qualcomm's fork of LK (Little Kernel)], used as bootloader on older platforms
- `abl`: [Qualcomm's Android Bootloader for UEFI], used on newer platforms
- `hyp`: [qhypstub], [tfalkstub]
Expand All @@ -82,7 +83,10 @@ from Qualcomm's whitepaper _"Secure Boot and Image Authentication"_ (both
[v1.0](https://www.qualcomm.com/media/documents/files/secure-boot-and-image-authentication-technical-overview-v1-0.pdf) and
[v2.0](https://www.qualcomm.com/media/documents/files/secure-boot-and-image-authentication-technical-overview-v2-0.pdf)).
Some implementation details (e.g. the exact MBN header format) are adapted from [signlk] and [coreboot]
(`util/qualcomm/mbn_tools.py`) available under a `BSD-3-Clause` license.
(`util/qualcomm/mbn_tools.py`, `util/cbfstool/platform_fixups.c`) available under a `BSD-3-Clause` license.

For QCA chipsets, Qualcomm maintains a similar set of tools at https://git.codelinaro.org/clo/qsdk/oss/system/tools/meta
available under a `ISC` license.

[qtestsign]: https://github.com/msm8916-mainline/qtestsign
[cryptography]: https://cryptography.io
Expand Down
97 changes: 89 additions & 8 deletions fw/hashseg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copyright (C) 2021-2023 Stephan Gerhold (GPL-2.0-only)
# MBN header format adapted from:
# - signlk: https://git.linaro.org/landing-teams/working/qualcomm/signlk.git
# - coreboot (util/qualcomm/mbn_tools.py)
# - coreboot (util/qualcomm/mbn_tools.py, util/cbfstool/platform_fixups.c)
# Copyright (c) 2016, 2018, The Linux Foundation. All rights reserved. (BSD-3-Clause)
# See also:
# - https://www.qualcomm.com/media/documents/files/secure-boot-and-image-authentication-technical-overview-v1-0.pdf
Expand Down Expand Up @@ -43,8 +43,20 @@
# the ELF header (including all program headers). It is a placeholder so that
# each hash covers the data of exactly one program header.

PHDR_FLAGS_HDR_PLACEHOLDER = 0x7000000 # placeholder for hash over ELF header
PHDR_FLAGS_HASH_SEGMENT = 0x2200000 # hash table segment
# For definitions of the ELF PHDR flags used by Qualcomm, see:
# https://github.com/coreboot/coreboot/blob/812d0e2f626dfea7e7deb960a8dc08ff0e026bc1/util/qualcomm/mbn_tools.py#L108-L189
PHDR_FLAGS_SEGMENT_TYPE_MASK = 0x07000000
PHDR_FLAGS_SEGMENT_TYPE_SHIFT = 0x18
PHDR_FLAGS_SEGMENT_TYPE_HASH = (0x2 << PHDR_FLAGS_SEGMENT_TYPE_SHIFT)
PHDR_FLAGS_SEGMENT_TYPE_HDR = (0x7 << PHDR_FLAGS_SEGMENT_TYPE_SHIFT)

PDHR_FLAGS_ACCESS_TYPE_MASK = 0x00E00000
PHDR_FLAGS_ACCESS_TYPE_SHIFT = 0x15
PHDR_FLAGS_ACCESS_TYPE_RO = (0x1 << PHDR_FLAGS_ACCESS_TYPE_SHIFT)

# Flags we use for placeholder for hash over ELF header and hash segment
PHDR_FLAGS_HDR_PLACEHOLDER = PHDR_FLAGS_SEGMENT_TYPE_HDR
PHDR_FLAGS_HASH_SEGMENT = (PHDR_FLAGS_SEGMENT_TYPE_HASH | PHDR_FLAGS_ACCESS_TYPE_RO)

EXTRA_PHDRS = 2 # header placeholder + hash segment

Expand All @@ -55,7 +67,11 @@

# According to the v2.0 PDF the metadata is 128 bytes long, but this does not
# seem to work. All official firmware seems to use 120 bytes instead.
METADATA_SIZE = 120
MBN_V6_METADATA_SIZE = 120

# See OEM Metadata 2.0 definition in coreboot source code:
# https://github.com/coreboot/coreboot/blob/812d0e2f626dfea7e7deb960a8dc08ff0e026bc1/util/qualcomm/mbn_tools.py#L506-L691
MBN_V7_OEM_2_0_METADATA_SIZE = 224


def _align(i: int, alignment: int) -> int:
Expand Down Expand Up @@ -191,17 +207,74 @@ def pack(self):
+ self.signature + self.cert_chain


@dataclass
# Information from MBNv7 definition in Coreboot source code:
# https://github.com/coreboot/coreboot/blob/812d0e2f626dfea7e7deb960a8dc08ff0e026bc1/util/qualcomm/mbn_tools.py#L506-L691
class HashSegmentV7(_HashSegment):
version: int = 7 # Header version number

common_metadata_size: int = 24 # Size of "common metadata" below
metadata_size_qcom: int = 0 # Size of metadata from Qualcomm
metadata_size: int = 0 # Size of metadata from OEM
hash_size: int = 0 # Size of hashes for all program segments
signature_size_qcom: int = 0 # Size of signature from Qualcomm
cert_chain_size_qcom: int = 0 # Size of certificate chain from Qualcomm
signature_size: int = 0 # Size of attestation signature
cert_chain_size: int = 0 # Size of certificate chain

# Common metadata, placed directly after MBNv7 header
common_metadata_major_version: int = 0
common_metadata_minor_version: int = 0
software_id: int = 0 # Type of software image, mandatory
secondary_software_id: int = 0
hash_table_algorithm: int = 3 # SHA384
measurement_register_target: int = 0

metadata_qcom = b''
metadata = b''
signature_qcom = b''
cert_chain_qcom = b''

FORMAT = Struct('<16L')
Hash = hashlib.sha384

def update(self, dest_addr: int):
super().update(dest_addr)
self.metadata_size_qcom = len(self.metadata_qcom)
self.metadata_size = len(self.metadata)
self.signature_size_qcom = len(self.signature_qcom)
self.cert_chain_size_qcom = len(self.cert_chain_qcom)
# self.common_metadata_size is already included as part of the header
self.total_size += self.metadata_size_qcom + self.metadata_size
self.total_size += self.signature_size_qcom + self.cert_chain_size_qcom

def check(self):
super().check()
assert len(self.metadata_qcom) == self.metadata_size_qcom
assert len(self.metadata) == self.metadata_size
assert len(self.signature_qcom) == self.signature_size_qcom
assert len(self.cert_chain_qcom) == self.cert_chain_size_qcom

def pack(self):
return self.pack_header() \
+ self.metadata_qcom + self.metadata \
+ b''.join(self.hashes) \
+ self.signature_qcom + self.cert_chain_qcom \
+ self.signature + self.cert_chain

HashSegment = {
3: HashSegmentV3,
5: HashSegmentV5,
6: HashSegmentV6,
7: HashSegmentV7,
}


def drop(elff: elf.Elf):
# Drop existing hash segments
elff.phdrs = [phdr for phdr in elff.phdrs if phdr.p_type != 0 or phdr.p_flags not in
[PHDR_FLAGS_HASH_SEGMENT, PHDR_FLAGS_HDR_PLACEHOLDER]]
elff.phdrs = [phdr for phdr in elff.phdrs if phdr.p_type != 0
or (phdr.p_flags & PHDR_FLAGS_SEGMENT_TYPE_MASK) not in
[PHDR_FLAGS_SEGMENT_TYPE_HASH, PHDR_FLAGS_SEGMENT_TYPE_HDR]]


def generate(elff: elf.Elf, version: int, sw_id: int):
Expand All @@ -210,9 +283,16 @@ def generate(elff: elf.Elf, version: int, sw_id: int):

hash_seg = HashSegment[version]()

if version >= 6:
if version == 6:
# TODO: Figure out metadata format and fill this with useful data
hash_seg.metadata = b'\0' * METADATA_SIZE
hash_seg.metadata = b'\0' * MBN_V6_METADATA_SIZE

# Software ID is mandatory for MBN v7
if version == 7:
hash_seg.software_id = sw_id
# The format is documented in Coreboot util/qualcomm/mbn_tools.py
# (see class Boot_Hdr), but for simplicity we just keep this empty.
hash_seg.metadata = b'\0' * MBN_V7_OEM_2_0_METADATA_SIZE

# Generate hash for all existing segments with data
digest_size = hash_seg.Hash().digest_size
Expand Down Expand Up @@ -279,3 +359,4 @@ def generate(elff: elf.Elf, version: int, sw_id: int):

# And finally, assemble the hash segment
hash_phdr.data = hash_seg.pack()
assert len(hash_phdr.data) == hash_phdr.p_filesz
5 changes: 4 additions & 1 deletion qtestsign.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"aop": 0x21,
"qup": 0x24,
"xbl-config": 0x25,
"cdsp-dtb": 0x52,
"adsp-dtb": 0x53,
"av1": 0x69,
}


Expand All @@ -58,7 +61,7 @@ def _sign_elf(b: bytes, out: Path, version: int, sw_id: int):
""")
parser.add_argument('type', choices=FW_SW_ID.keys(), help="Firmware type (for SW_ID)")
parser.add_argument('elf', type=argparse.FileType('rb'), help="ELF image to sign")
parser.add_argument('-v', '--version', type=int, choices=[3, 5, 6], default=3,
parser.add_argument('-v', '--version', type=int, choices=[3, 5, 6, 7], default=3,
help="MBN header version. Must be set correctly depending on the target chipset. "
"See README for details.")
parser.add_argument('-o', '--output', type=Path, help="Output file")
Expand Down