Compare commits

..

118 Commits

Author SHA1 Message Date
Andrew Gunnerson 4e18f2b478 CHANGELOG.md: Add entry for PR #620
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-07-02 21:54:50 -04:00
Andrew Gunnerson 710c57ec77 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-07-02 21:14:10 -04:00
Ivan 9ca679b64a README.ru.md: Update translation
* https://github.com/chenxiaolong/avbroot/commit/1b4789a3371a09a7a6bb7c4aa5ca7fccd493bd04
* https://github.com/chenxiaolong/avbroot/commit/22a0196cae45a5d6f2a7274d9edafa483cea515e

Signed-off-by: Ivan <reddxae@proton.me>
2026-07-03 02:12:41 +03:00
Andrew Gunnerson 2700d8212c CHANGELOG.md: Add entry for PR #616
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-27 18:57:29 -04:00
Andrew Gunnerson 8a71c1d8be format/avb: Increase memory limits to support 16GiB images
Samsung ships an 8.2GiB system.img in the SM-S948U1 stock firmware. This
allows avbroot to verify the signature of that.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-27 18:39:39 -04:00
Andrew Gunnerson 6344a927eb Version 3.31.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-22 19:24:37 -04:00
Andrew Gunnerson 29704910e4 CHANGELOG.md: Add entry for PR #613
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-22 19:21:01 -04:00
Andrew Gunnerson ac84e056a1 Update protobufs for Android 17 and update dependencies
retrofit_dynamic_partitions is gone from the payload manifest, but this
wasn't used by devices supporting avbroot anyway.

This fixes the disable_ublk OTA metadata field being lost when patching
an Android 17 OTA that uses it.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-22 19:15:56 -04:00
Andrew Gunnerson f95ef1f8c1 Version 3.30.2
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-13 19:33:05 -04:00
Andrew Gunnerson e5a373b59b CHANGELOG.md: Add entry for PR #611
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-13 19:19:43 -04:00
rushiiMachine ddb8611e27 fix: only compress regular files during Magisk backup
Creation of Magisk .backup/ files should only xz-compress real files,
and ignore other types such as symlinks. This fixes the case where
/init is a symlink, and avbroot incorrectly puts the backup symlink at
.backup/init.xz with the target path being compressed binary data.

Fixes: #610
2026-06-10 22:10:18 -07:00
Andrew Gunnerson 02b172b1c9 Version 3.30.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-06 16:44:50 -04:00
Andrew Gunnerson 8cbcb44e23 CHANGELOG.md: Add entry for PR #608
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-06 16:42:45 -04:00
Andrew Gunnerson 144e635257 cli/payload: Skip recow for non-VABC payloads when packing
Setting the CoW size for non-VABC payloads is invalid and results in the
recow process failing with:

    ERROR Partition has CoW estimate, but VABC is disabled: <name>

Fixes: #607

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-06 16:33:19 -04:00
Andrew Gunnerson 0c17db4fc4 Version 3.30.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-04 22:58:58 -04:00
Andrew Gunnerson a06b7ade86 CHANGELOG.md: Add entry for PR #606
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-04 22:57:50 -04:00
Andrew Gunnerson 22a0196cae ota: Add subcommand to list all partitions
Closes: #603

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-04 22:34:45 -04:00
Andrew Gunnerson f12e2633ce CHANGELOG.md: Add entry for PR #605
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-04 22:34:29 -04:00
Andrew Gunnerson 4fc694039e Update dependencies
The e2e checksums had to be updated due to bug fixes in the lzma-rust2
encoder:

https://github.com/hasenbanck/lzma-rust2/compare/v0.16.2...v0.16.4

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-04 22:28:40 -04:00
Andrew Gunnerson 1b4789a337 README.md: Document how keys correspond to AOSP keys
Fixes: #604

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-06-04 21:25:39 -04:00
Ivan 6387b8ae4d README.ru.md: Update translation
* https://github.com/chenxiaolong/avbroot/commit/6f8de8b2c4cde899e30c8d1744f8c19b5a742f6d
* https://github.com/chenxiaolong/avbroot/commit/fce0bfabab05ccea25a90fb9b014b0a4dd3b760b
* https://github.com/chenxiaolong/avbroot/commit/f82db7aafcb3f8a0229df1ef86c91d8631b90e0b

also noticed that I had missed https://github.com/chenxiaolong/avbroot/commit/d7439e15aedb77948e7079fc1e23c2f44e6636ec a while back. added that, too.

Signed-off-by: Ivan <reddxae@proton.me>
2026-05-28 16:50:48 +03:00
Andrew Gunnerson f82db7aafc README.md: Update minimum fastboot version to 35
https://github.com/chenxiaolong/avbroot/pull/316#issuecomment-4526748687

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-05-26 09:31:57 -04:00
Andrew Gunnerson fce0bfabab README.md: Clarify that license is GPLv3 only
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-04-25 15:32:19 -04:00
Andrew Gunnerson 07b6901d8e Version 3.29.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-04-23 11:36:35 -04:00
Andrew Gunnerson b2b3d98005 CHANGELOG.md: Add entry for PR #594
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-04-23 11:26:51 -04:00
Andrew Gunnerson 21c1dc86ce format/payload: Allow manifests up to 16 MiB
The memory limit prevented some larger incremental OTAs from being
loaded.

Fixes: #593

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-04-23 11:24:57 -04:00
Andrew Gunnerson 462084ac18 Version 3.29.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 01:15:32 -04:00
Andrew Gunnerson 4c3e1efd99 CHANGELOG.md: Add entry for PR #587
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 01:14:41 -04:00
Andrew Gunnerson 9b7f27e384 cli/payload: Add support for unpacking skeleton images
This is useful for getting properly sized (but empty) image files when
unpacking delta payloads.

Discussion: #582

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 01:03:37 -04:00
Andrew Gunnerson 2120827755 CHANGELOG.md: Add entry for PR #586
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 00:56:58 -04:00
Andrew Gunnerson 8b33645026 cli/zip: Add support for packing/unpacking the payload at the same time
This is more efficient since there's no need to waste space on an extra
copy of payload.bin when we can directly read it from the zip file or
directly write to the zip file.

Discussion: #582

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-28 00:22:02 -04:00
Andrew Gunnerson eafc6ad3b4 Version 3.28.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-25 18:48:32 -04:00
Andrew Gunnerson 188d256d03 CHANGELOG.md: Add entry for PR #584
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-25 18:41:08 -04:00
Andrew Gunnerson c1836d01a4 cli/payload: Allow unpacking only metadata from delta payloads
The images are the only parts that can't be extracted. Having the
unpacked metadata is useful because it allows for manually building a
full payload using the metadata from the delta payload.

Discussion: #582

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-25 18:40:07 -04:00
Andrew Gunnerson e8532870b1 Version 3.27.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-20 00:01:47 -04:00
Andrew Gunnerson b854654cab CHANGELOG.md: Add entry for PR #580
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-20 00:00:40 -04:00
Andrew Gunnerson f68a492131 format/compression: Add support for LZMA
Fixes: #578

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-19 21:04:09 -04:00
Andrew Gunnerson c22a5d6eca CHANGELOG.md: Add entry for PR #579
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-19 20:06:42 -04:00
Andrew Gunnerson 2631228e8d Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-03-19 19:33:15 -04:00
Andrew Gunnerson f2b5d9646e Version 3.26.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-23 22:27:43 -05:00
Andrew Gunnerson 4991c5192e CHANGELOG.md: Add entry for PR #573
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-23 22:14:38 -05:00
Andrew Gunnerson 8ef9f89cdd Bump Magisk version upper bound to 30800
There are no breaking changes.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-23 22:12:09 -05:00
Andrew Gunnerson 43cd7eac78 Version 3.26.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-21 16:44:08 -05:00
Andrew Gunnerson faf5a908ec CHANGELOG.md: Add entry for PR #571
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-21 16:39:19 -05:00
Andrew Gunnerson 97cad8f508 cli/avb: Warn when AVB header is signed with AOSP test keys
This will log a warning message, but will not return a fatal error.

Issue: #569

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-21 16:33:56 -05:00
Andrew Gunnerson dd73dcbd56 CHANGELOG.md: Add entry for PR #570
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-21 16:32:15 -05:00
Andrew Gunnerson 6f8de8b2c4 Add option to re-sign unmodified partitions during patching
This is useful when the OTA contains partition images that are signed
with the AOSP test keys.

Issue: #569

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-21 15:32:44 -05:00
Andrew Gunnerson 1be81d87b9 README.extra.md: Fix avbroot ota -> avbroot zip
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-20 23:14:17 -05:00
Andrew Gunnerson 5df2ffa435 Version 3.25.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-16 00:17:39 -05:00
Andrew Gunnerson 16c1c2a4d3 CHANGELOG.md: Add entry for PR #565
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-16 00:15:12 -05:00
Andrew Gunnerson 1155a14b00 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-16 00:06:42 -05:00
Andrew Gunnerson 806870664a CHANGELOG.md: Add entry for PR #564
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-16 00:01:09 -05:00
Andrew Gunnerson 4717407a43 Add missing flush() and remove no-op flush()
* flush() or into_inner() is now called for all BufWriters
* flush() is no longer called for Files, which is a no-op operation

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-15 23:55:53 -05:00
Andrew Gunnerson ff4a2a6db8 CHANGELOG.md: Add entry for PR #563
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-15 23:37:50 -05:00
Andrew Gunnerson 49bd389bf3 cli/payload: Fix short option conflict for output and output_properties
These were both requesting -o. --output-properties will now be -O
instead.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-15 23:37:02 -05:00
Andrew Gunnerson 5afe76bf2b CHANGELOG.md: Add entry for PR #562
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-15 23:28:32 -05:00
Andrew Gunnerson 0c8357924c Add support for unpacking and packing raw OTA zips
This provides the last bit of machinery needed to build an OTA from
scratch, outside of the usual `avbroot ota patch` mechanism. The new
subcommands exist under `avbroot zip` instead of `avbroot ota` to reduce
the chance of someone accidentally using them.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-15 22:37:18 -05:00
Andrew Gunnerson ef9e563010 CHANGELOG.md: Add entry for PR #561
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-15 15:31:16 -05:00
Andrew Gunnerson 2e53147f87 cli/{cpio,lp,payload}: Remove outdated mention of openat-style sandboxing
This was previously removed in d7369e73e9
because it was preventing other legitimate use cases.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-15 15:27:33 -05:00
Andrew Gunnerson 69903c4382 Version 3.24.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-03 18:19:05 -05:00
Andrew Gunnerson 9987d21cef CHANGELOG.md: Add entry for PR #559
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-03 18:16:25 -05:00
Andrew Gunnerson 9802bba5f7 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-02-03 18:09:44 -05:00
Andrew Gunnerson 1b30328b01 CHANGELOG.md: Add entry for PR #555
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-24 18:55:42 -05:00
Andrew Gunnerson efb25e15dd cli/avb: Fix help text for avbroot avb verify-device -p
Issue: #554

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-24 18:52:32 -05:00
Ivan 2e5754e16c README.ru.md: Update translation
Sync with https://github.com/chenxiaolong/avbroot/commit/2033e481632fb7b81df296f00d1d75ab1759adae

Signed-off-by: Ivan <reddxae@proton.me>
2026-01-10 15:43:36 +03:00
Andrew Gunnerson cd4da8d147 Version 3.24.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 23:32:00 -05:00
Andrew Gunnerson 9c48d0ddc5 CHANGELOG.md: Add entry for PR #550
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 23:31:25 -05:00
Andrew Gunnerson ee70fdbcae deny.toml: Ignore compiled honggfuzz executable and object files
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 23:10:50 -05:00
Andrew Gunnerson f8f1afd6b3 CHANGELOG.md: Add entry for PR #549
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 23:07:24 -05:00
Andrew Gunnerson f2b98623cb Update dependencies
Also, switch e2e from toml_edit to toml since we don't need any editing
capabilities.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 21:10:38 -05:00
Andrew Gunnerson 63e21c5aa9 Merge pull request #534 from lefuglyduck/patch-1
Update README.md
2026-01-09 20:27:00 -05:00
Andrew Gunnerson a78a122763 CHANGELOG.md: Add entry for PR #548
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 20:25:41 -05:00
Andrew Gunnerson cd7316b429 Move fuzzing corpus generation to xtask
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 20:15:33 -05:00
Andrew Gunnerson 68d5bd6990 CHANGELOG.md: Add entry for PR #547
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 19:52:31 -05:00
Andrew Gunnerson 2a4c422943 Update toml to support serialization of large u64 values
Upstream bug report: https://github.com/toml-rs/toml/issues/1085
Upstream fix: https://github.com/toml-rs/toml/pull/1086

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 19:49:44 -05:00
Andrew Gunnerson 94b12bf727 CHANGELOG.md: Add entry for PR #539
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 19:36:30 -05:00
Andrew Gunnerson f966714310 cli/ota: Ignore vbmeta partitions filled with zeros
For whatever reason, some OTAs seem to include empty unused vbmeta
partitions. To avoid needing to extract the partitions to check the
contents, we'll just filter out small vbmeta partitions (>=4 KiB and
<=64 KiB) where the partition checksum specified in the payload matches
the SHA-256 digest of an array of zeros of the same size.

Fixes: #537

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 19:35:15 -05:00
Andrew Gunnerson 3eb08f48d1 CHANGELOG.md: Add entry for PR #545
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 01:22:06 -05:00
Andrew Gunnerson dbb5bc4767 format/lp: Fix potential panic caused by empty image extent indices
7017bc66dd relaxed the parser logic to
allow empty images to contain extents. However, the validation check for
extent indices didn't run because it still had a conditional for normal
images. This could cause a panic during parsing due to an out of bounds
read of the extents array.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 01:20:30 -05:00
Andrew Gunnerson 903d6dcd13 CHANGELOG.md: Add entry for PR #544
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 00:55:52 -05:00
Andrew Gunnerson ddcef31b96 fuzz: Fix initial corpus
e22f9f5676 removed all the old binary test
files, leaving broken symlinks in hfuzz_workspace. This commit adds the
source files for generating them from scratch.

This commit also replaces toml_edit with toml since the former cannot
deserialize u64 values.

Fixes: #532

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-09 00:53:18 -05:00
Andrew Gunnerson cbd419f8e9 CHANGELOG.md: Add entry for PR #543
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-08 19:54:20 -05:00
Andrew Gunnerson 9214a832cd patch/boot: Add support for DSU on dedicated recovery devices
On devices with dedicated recovery partitions, first_stage_ramdisk
exists in the ramdisks, but is unused. The first stage switch root
operation is just skipped. To properly add a public key for DSU on these
devices, the top level /avb directory needs to be used.

Fixes: #536

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-08 19:21:55 -05:00
Andrew Gunnerson dc9244f014 CHANGELOG.md: Add entry for PR #541
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-07 20:28:21 -05:00
Andrew Gunnerson 7017bc66dd format/lp: Allow empty images to list extents
Android's DSU mechanism uses this in /metadata/gsi/dsu/dsu/lp_metadata.
The file is an empty image that references the userdata block device and
the extents point to preallocated space for the partition images in
/data/gsi/dsu/dsu.

This commit also relaxes the metadata_max_size limit to 512 KiB since
these DSU LP images set the field to 256 KiB:

https://android.googlesource.com/platform/system/core/+/refs/tags/android-16.0.0_r4/fs_mgr/libfiemap/metadata.cpp#35

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-07 20:26:14 -05:00
Andrew Gunnerson bff45fc53c CHANGELOG.md: Add entry for PR #540
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-07 20:13:44 -05:00
Andrew Gunnerson 0e5ba0f0e2 patch/boot: Always normalize ramdisk archives
Previously, some of the boot image patches did not sort the cpio entries
and (re)assign inode numbers. In practice, this makes no difference, but
it is still not the intended behavior.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2026-01-07 20:10:37 -05:00
lefuglyduck 2033e48163 Update README.md
Added adb command to reboot into recovery. 

Signed-off-by: lefuglyduck <31975903+lefuglyduck@users.noreply.github.com>
2025-12-11 07:02:04 -08:00
Andrew Gunnerson 237f59fd9b Version 3.23.3
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-12-01 21:02:12 -05:00
Andrew Gunnerson b0b00a4c54 CHANGELOG.md: Add entry for PR #530
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-12-01 20:58:15 -05:00
Andrew Gunnerson b9a70efb15 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-12-01 20:54:47 -05:00
Andrew Gunnerson aeef2936d8 CHANGELOG.md: Add entry for PR #529
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-12-01 20:49:24 -05:00
Andrew Gunnerson 3adab80893 Bump Magisk version upper bound to 30700
There are no breaking changes.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-12-01 20:48:16 -05:00
Andrew Gunnerson 4069429361 Version 3.23.2
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-11-04 20:55:17 -05:00
Andrew Gunnerson 74193c1c9f CHANGELOG.md: Add entry for PR #524
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-11-04 20:54:42 -05:00
Andrew Gunnerson 0f8cef2daa ci.yml: Add aarch64-pc-windows-msvc target
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-11-04 18:10:05 -05:00
Andrew Gunnerson 8731df4504 CHANGELOG.md: Add entry for PR #523
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-11-04 18:08:35 -05:00
Andreas Papon fda11b4262 Add aarch64-unknown-linux-gnu target. 2025-11-04 20:08:05 +01:00
Andrew Gunnerson f0970aa80c Version 3.23.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-10-02 19:37:41 -04:00
Andrew Gunnerson 4f0db5a7c4 CHANGELOG.md: Add entry for PR #521
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-10-02 19:28:22 -04:00
Andrew Gunnerson 57374e2e41 Bump Magisk version upper bound to 30500
There are no breaking changes.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-10-02 19:27:19 -04:00
Andrew Gunnerson f4e5c55fe9 Version 3.23.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-09-30 20:39:13 -04:00
Andrew Gunnerson 850ce86388 CHANGELOG.md: Add entry for PR #520
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-09-30 20:16:31 -04:00
Andrew Gunnerson c16636a68c Update dependencies
The only breaking changes are a few types from lzma-rust2 being renamed.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-09-30 20:15:20 -04:00
Andrew Gunnerson 26812cd43a CHANGELOG.md: Add entry for PR #519
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-09-30 20:08:55 -04:00
Andrew Gunnerson 135cd320ce Bump Magisk version upper bound to 30400
30300 introduced a new RECOVERYMODE config option, which we
unconditionally disable because we never patch init_boot.cpio within
the vendor_boot image.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-09-30 20:07:34 -04:00
Andrew Gunnerson 919f621833 CHANGELOG.md: Add entry for PR #517
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-09-21 19:03:34 -04:00
Andrew Gunnerson 2c1d86cd96 deny.toml: Remove outdated override for ring license
Issue: #516

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-09-21 18:52:15 -04:00
Andrew Gunnerson 91074ae4c8 Update dependencies and fix clippy 1.90 lints
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-09-21 18:51:25 -04:00
Andrew Gunnerson a5c628b66f Version 3.22.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 23:47:22 -04:00
Andrew Gunnerson 156c96d3d4 CHANGELOG.md: Add entry for PR #512
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 23:46:31 -04:00
Andrew Gunnerson 2460d1807d Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 23:24:42 -04:00
Andrew Gunnerson 728d333951 CHANGELOG.md: Add entry for PR #511
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 23:11:10 -04:00
Andrew Gunnerson 39661a994d Cargo.toml: Specify minimum supported toolchain version
Fixes: #506

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 23:08:58 -04:00
Andrew Gunnerson d6b19b69f7 CHANGELOG.md: Add entry for PR #510
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 22:58:44 -04:00
Andrew Gunnerson 96806cba36 cli: Allow skipping unpacking of images
This is useful if the file format metadata is the only thing that the
user needs.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 22:54:44 -04:00
Andrew Gunnerson 5c474a1e98 CHANGELOG.md: Add entry for PR #509
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 22:40:38 -04:00
Andrew Gunnerson 4cf546e5ca cli/payload: Compute CoW size estimates during packing
This was overlooked during the initial implementation. Payload images
using VABC, but without CoW size estimates, are unflashable outside of
recovery.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-27 22:06:44 -04:00
104 changed files with 3425 additions and 887 deletions
+7 -3
View File
@@ -28,8 +28,12 @@ jobs:
artifact:
- os: ubuntu-latest
name: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
name: aarch64-unknown-linux-gnu
- os: windows-latest
name: x86_64-pc-windows-msvc
- os: windows-11-arm
name: aarch64-pc-windows-msvc
- os: macos-latest
name: universal-apple-darwin
targets:
@@ -43,7 +47,7 @@ jobs:
android_api: '31'
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# For git describe
fetch-depth: 0
@@ -85,7 +89,7 @@ jobs:
done
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: ${{ matrix.artifact.name }}
@@ -156,7 +160,7 @@ jobs:
run: cp LICENSE README.md target/output/
- name: Archive executable
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: avbroot-${{ steps.get_version.outputs.version }}-${{ matrix.artifact.name }}
path: |
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Run cargo-deny
uses: EmbarkStudios/cargo-deny-action@30f817c6f72275c6d54dc744fbca09ebc958599f # v2.0.12
uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2.0.20
+2 -2
View File
@@ -24,10 +24,10 @@ jobs:
echo "version=${version}" >> "${GITHUB_OUTPUT}"
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Create release
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
tag_name: v${{ steps.get_version.outputs.version }}
name: Version ${{ steps.get_version.outputs.version }}
+156
View File
@@ -7,6 +7,106 @@
to update the actual links at the bottom of the file.
-->
### Unreleased
* Increase memory limits in AVB parser to allow verifying images larger than 8 GIB (limit is now 16 GiB) ([PR #616])
* Update dependencies ([PR #620])
### Version 3.31.0
* Update OTA metadata and payload manifest protobufs from AOSP `android-17.0.0_r1` release ([PR #613])
### Version 3.30.2
* Fix the symlink target path being incorrectly XZ-compressed when the Magisk patcher replaces a symlink with a regular file ([Issue #610], [PR #611])
### Version 3.30.1
* Fix `avbroot payload pack` and `avbroot zip pack --payload` for non-VABC devices ([Issue #607], [PR #608])
### Version 3.30.0
* Add `avbroot ota list` subcommand to list all partitions in an OTA ([Issue #603], [PR #606])
* Update dependencies ([PR #605])
### Version 3.29.1
* Increase memory limit for payload manifest size to 16 MiB ([Issue #593], [PR #594])
### Version 3.29.0
* Add support for unpacking/packing payloads at the same time as the OTA in `avbroot zip {unpack,pack}` ([Discussion #582], [PR #586])
* Add new `--skeleton` option to `avbroot payload unpack` ([Discussion #582], [PR #587])
### Version 3.28.0
* Add support for unpacking metadata from delta payloads in `avbroot payload unpack` ([Discussion #582], [PR #584])
### Version 3.27.0
* Add support for LZMA compression for ramdisks ([Issue #578], [PR #580])
* Update dependencies ([PR #579])
### Version 3.26.1
* Add support for Magisk 30700 ([PR #573])
### Version 3.26.0
* Add new `--re-sign` option to `avbroot ota patch` for re-signing partitions that would otherwise be unmodified ([Issue #569], [PR #570])
* Warn when a partition is signed with an AOSP test key in `avbroot ota verify` ([Issue #569], [PR #571])
### Version 3.25.0
* Remove outdated mentions of guarantees provided by cap-std in `--help` text, which was removed in version 3.20.0 ([PR #561])
* Add new commands for packing and unpacking raw OTA zips ([PR #562])
* For folks who are familiar with how OTAs are built, this means it's now possible create an OTA from scratch without using `avbroot ota patch`.
* Fix `-o` being used as the short option for both `--output` and `--output-properties` in `avbroot payload pack` and `avbroot payload repack` ([PR #563])
* The short option for `--output-properties` is now uppercase `-O`.
* Add missing file flush operations and remove them where they are no-ops ([PR #564])
* Update dependencies ([PR #565])
### Version 3.24.1
* Update `avbroot avb verify-device --help` to mention that `ro.boot.vbmeta.public_key_digest` is used when `-p` is not specified ([Issue #554], [PR #555])
* Update dependencies ([PR #559])
### Version 3.24.0
* Always sort ramdisk cpio entries and ensure they have inode numbers assigned ([PR #540])
* Relax LP image parser's validation to allow parsing the `/metadata/gsi/dsu/dsu/lp_metadata` image used by DSU ([PR #541], [PR #545])
* Add support for devices with separate recovery partitions when using `--dsu` ([Issue #536], [PR #543])
* Fix broken symlinks in fuzzing corpus ([Issue #532], [PR #544], [PR #548])
* Ignore unused vbmeta partitions that are filled with zeros ([Issue #537], [PR #539])
* Update dependencies ([PR #547], [PR #549])
* Fix `cargo deny` failing after running fuzzers with `cargo hfuzz` ([PR #550])
### Version 3.23.3
* Add support for Magisk 30600 ([PR #529])
* Update dependencies ([PR #530])
### Version 3.23.2
* Add prebuilt binaries for aarch64 GNU/Linux ([PR #523])
* Add prebuilt binaries for aarch64 Windows ([PR #524])
### Version 3.23.1
* Add support for Magisk 30400 ([PR #521])
### Version 3.23.0
* Update dependencies and fix Rust 1.90 clippy lints ([PR #517], [PR #520])
* Add support for Magisk 30300 ([PR #519])
### Version 3.22.0
* Fix CoW size estimates not being set when running `avbroot payload pack` ([PR #509])
* Add support for only unpacking metadata in `avbroot {avb,boot,cpio,lp,payload} unpack` ([PR #510])
* Specify minimum supported compiler version for building from source ([PR #511])
* Update dependencies ([PR #512])
### Version 3.21.0
* Switch to using rawzip for zip parsing and writing ([PR #489])
@@ -376,6 +476,7 @@ Behind-the-scenes changes:
[Discussion #294]: https://github.com/chenxiaolong/avbroot/discussions/294
[Discussion #417]: https://github.com/chenxiaolong/avbroot/discussions/417
[Discussion #426]: https://github.com/chenxiaolong/avbroot/discussions/426
[Discussion #582]: https://github.com/chenxiaolong/avbroot/discussions/582
[Issue #138]: https://github.com/chenxiaolong/avbroot/issues/138
[Issue #144]: https://github.com/chenxiaolong/avbroot/issues/144
[Issue #145]: https://github.com/chenxiaolong/avbroot/issues/145
@@ -409,6 +510,16 @@ Behind-the-scenes changes:
[Issue #472]: https://github.com/chenxiaolong/avbroot/issues/472
[Issue #482]: https://github.com/chenxiaolong/avbroot/issues/482
[Issue #493]: https://github.com/chenxiaolong/avbroot/issues/493
[Issue #532]: https://github.com/chenxiaolong/avbroot/issues/532
[Issue #536]: https://github.com/chenxiaolong/avbroot/issues/536
[Issue #537]: https://github.com/chenxiaolong/avbroot/issues/537
[Issue #554]: https://github.com/chenxiaolong/avbroot/issues/554
[Issue #569]: https://github.com/chenxiaolong/avbroot/issues/569
[Issue #578]: https://github.com/chenxiaolong/avbroot/issues/578
[Issue #593]: https://github.com/chenxiaolong/avbroot/issues/593
[Issue #603]: https://github.com/chenxiaolong/avbroot/issues/603
[Issue #607]: https://github.com/chenxiaolong/avbroot/issues/607
[Issue #610]: https://github.com/chenxiaolong/avbroot/issues/610
[PR #130]: https://github.com/chenxiaolong/avbroot/pull/130
[PR #132]: https://github.com/chenxiaolong/avbroot/pull/132
[PR #133]: https://github.com/chenxiaolong/avbroot/pull/133
@@ -602,3 +713,48 @@ Behind-the-scenes changes:
[PR #503]: https://github.com/chenxiaolong/avbroot/pull/503
[PR #504]: https://github.com/chenxiaolong/avbroot/pull/504
[PR #505]: https://github.com/chenxiaolong/avbroot/pull/505
[PR #509]: https://github.com/chenxiaolong/avbroot/pull/509
[PR #510]: https://github.com/chenxiaolong/avbroot/pull/510
[PR #511]: https://github.com/chenxiaolong/avbroot/pull/511
[PR #512]: https://github.com/chenxiaolong/avbroot/pull/512
[PR #517]: https://github.com/chenxiaolong/avbroot/pull/517
[PR #519]: https://github.com/chenxiaolong/avbroot/pull/519
[PR #520]: https://github.com/chenxiaolong/avbroot/pull/520
[PR #521]: https://github.com/chenxiaolong/avbroot/pull/521
[PR #523]: https://github.com/chenxiaolong/avbroot/pull/523
[PR #524]: https://github.com/chenxiaolong/avbroot/pull/524
[PR #529]: https://github.com/chenxiaolong/avbroot/pull/529
[PR #530]: https://github.com/chenxiaolong/avbroot/pull/530
[PR #539]: https://github.com/chenxiaolong/avbroot/pull/539
[PR #540]: https://github.com/chenxiaolong/avbroot/pull/540
[PR #541]: https://github.com/chenxiaolong/avbroot/pull/541
[PR #543]: https://github.com/chenxiaolong/avbroot/pull/543
[PR #544]: https://github.com/chenxiaolong/avbroot/pull/544
[PR #545]: https://github.com/chenxiaolong/avbroot/pull/545
[PR #547]: https://github.com/chenxiaolong/avbroot/pull/547
[PR #548]: https://github.com/chenxiaolong/avbroot/pull/548
[PR #549]: https://github.com/chenxiaolong/avbroot/pull/549
[PR #550]: https://github.com/chenxiaolong/avbroot/pull/550
[PR #555]: https://github.com/chenxiaolong/avbroot/pull/555
[PR #559]: https://github.com/chenxiaolong/avbroot/pull/559
[PR #561]: https://github.com/chenxiaolong/avbroot/pull/561
[PR #562]: https://github.com/chenxiaolong/avbroot/pull/562
[PR #563]: https://github.com/chenxiaolong/avbroot/pull/563
[PR #564]: https://github.com/chenxiaolong/avbroot/pull/564
[PR #565]: https://github.com/chenxiaolong/avbroot/pull/565
[PR #570]: https://github.com/chenxiaolong/avbroot/pull/570
[PR #571]: https://github.com/chenxiaolong/avbroot/pull/571
[PR #573]: https://github.com/chenxiaolong/avbroot/pull/573
[PR #579]: https://github.com/chenxiaolong/avbroot/pull/579
[PR #580]: https://github.com/chenxiaolong/avbroot/pull/580
[PR #584]: https://github.com/chenxiaolong/avbroot/pull/584
[PR #586]: https://github.com/chenxiaolong/avbroot/pull/586
[PR #587]: https://github.com/chenxiaolong/avbroot/pull/587
[PR #594]: https://github.com/chenxiaolong/avbroot/pull/594
[PR #605]: https://github.com/chenxiaolong/avbroot/pull/605
[PR #606]: https://github.com/chenxiaolong/avbroot/pull/606
[PR #608]: https://github.com/chenxiaolong/avbroot/pull/608
[PR #611]: https://github.com/chenxiaolong/avbroot/pull/611
[PR #613]: https://github.com/chenxiaolong/avbroot/pull/613
[PR #616]: https://github.com/chenxiaolong/avbroot/pull/616
[PR #620]: https://github.com/chenxiaolong/avbroot/pull/620
Generated
+486 -449
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -4,10 +4,12 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.21.0"
version = "3.31.0"
license = "GPL-3.0-only"
edition = "2024"
repository = "https://github.com/chenxiaolong/avbroot"
publish = false
rust-version = "1.88.0"
[workspace.lints.clippy]
cast_lossless = "deny"
+68 -6
View File
@@ -19,7 +19,7 @@ The vbmeta descriptor digests are validated during unpacking. For dm-verity imag
### Packing an AVB image
```bash
avbroot avb pack -o <output AVB image> [--key <AVB private key>]
avbroot avb pack -o <output AVB image> [-k <AVB private key>]
```
This subcommand packs a new AVB image from the `avb.toml` file and, for appended vbmeta images, the `raw.img` file.
@@ -52,6 +52,8 @@ avbroot avb info -i <image>
This subcommand shows all of the vbmeta header and footer fields. `vbmeta` partition images will only have a header, while partitions with actual data (eg. boot images) will have both a header and a footer.
(The `unpack`, `pack`, and `repack` subcommands also show this information. This specific subcommand just does so without performing any other operation.)
### Verifying AVB hashes and signatures
```bash
@@ -117,7 +119,9 @@ This subcommand repacks a boot image without writing the individual components t
avbroot boot info -i <input boot image>
```
All of the `boot` subcommands show the boot image information. This specific subcommand just does it without performing any other operation. To show avbroot's internal representation of the information, pass in `-d`.
This subcommand shows the information contained in the boot image's header. To show avbroot's internal representation of the information, pass in `-d`.
(All of the `boot` subcommands show this information. This specific subcommand just does so without performing any other operation.)
## `avbroot cpio`
@@ -159,7 +163,9 @@ This is almost equivalent to running `avbroot cpio unpack` followed by `avbroot
avbroot cpio info -i <input cpio archive>
```
All of the `cpio` subcommands show details about all the entries in the archive. This specific subcommand just does it without performing any other operation.
This subcommand shows details about every entry in the archive.
(All of the `cpio` subcommands show this information. This specific subcommand just does so without performing any other operation.)
## `avbroot fec`
@@ -320,6 +326,8 @@ avbroot lp info -i <first LP image>
This subcommand shows the LP image metadata, including all metadata slots. If there are multiple images, only the first one is needed because it is the only one that stores the metadata.
(All of the `lp` subcommands show this information. This specific subcommand just does so without performing any other operation.)
## `avbroot payload`
This set of commands is for working with payload binary files (`payload.bin`). The `unpack` and `pack` commands can only work with full payloads because they require the complete data to be available, but the `repack` and `info` commands also work with delta payloads.
@@ -332,22 +340,24 @@ avbroot payload unpack -i <input payload>
This subcommand unpacks the payload header information to `payload.toml` and the partition images to the `payload_images` directory.
Only full payload binaries can be unpacked. Delta payload binaries from incremental OTAs are not supported.
Data can only be unpacked from full payload binaries. For delta payloads, either use `--no-output-images` to skip image extraction or use `--skeleton` to create properly sized sparse files with no data.
### Packing a payload binary
```bash
avbroot payload pack -o <output payload> --key <OTA private key>
avbroot payload pack -o <output payload> -k <OTA private key> [-O <output properties>]
```
This subcommand packs a new payload binary from the `payload.toml` file and `payload_images` directory. Any `.img` files in the `payload_images` directory that don't have a corresponding entry in `payload.toml` are silently ignored.
When replacing the payload binary in an OTA, it is not sufficient to only update `payload.bin`. The checksums in `payload_properties.txt` need to be updated as well. Use `--output-properties` to generate a new properties file.
Packing a payload binary requires compressing all of the partition images, which is very CPU intensive. If re-signing an existing payload binary without making any other modifications is all that's needed, use the `repack` subcommand instead.
### Repacking a payload binary
```bash
avbroot payload repack -i <input payload> -o <output payload> --key <OTA private key>
avbroot payload repack -i <input payload> -o <output payload> -k <OTA private key> [-O <output properties>]
```
This subcommand is logically equivalent to `avbroot payload unpack` followed by `avbroot payload pack`, except significantly more efficient. Instead of decompressing and recompressing all partition images, the raw data is directly copied from the input payload binary.
@@ -362,6 +372,8 @@ avbroot payload info -i <payload>
This subcommand shows all of the payload header fields (which will likely be extremely long).
(All of the `payload` subcommands show this information. This specific subcommand just does so without performing any other operation.)
## `avbroot sparse`
This set of commands is for working with Android sparse images. All features of the file format are supported, including hole chunks and CRC32 checksums.
@@ -405,3 +417,53 @@ avbroot sparse info -i <input sparse image>
```
This subcommand shows the sparse image metadata, including the header and all chunks.
(All of the `sparse` subcommands show this information. This specific subcommand just does so without performing any other operation.)
## `avbroot zip`
This set of commands is for working with raw OTA zip files. They are intentionally placed outside of `avbroot ota` to make them less discoverable by accident. These commands are useful for creating OTA zip files without going through the normal `avbroot ota patch` mechanism.
**WARNING**: Make sure to run `avbroot ota verify` on OTA files before installing them. These pack commands are low level operations that only check that the file structure of the OTA itself is valid, not the contents contained within.
### Unpacking an OTA zip
```bash
avbroot zip unpack -i <input OTA> [--payload]
```
This subcommand unpacks the OTA metadata to `ota.toml` and the OTA files to the `ota_files` directory.
When `--payload` is specified, the payload and OTA zip will be unpacked at the same time, which is significantly more efficient that unpack the payload and OTA zip separately. The payload unpacking process writes to the same `payload.toml` and `payload_images` outputs as `avbroot payload unpack`.
### Packing an OTA zip
```bash
avbroot zip pack -o <output OTA> -k <OTA private key> [--payload]
```
This subcommand packs a new OTA zip from the `ota.toml` file and `ota_files` directory. Any files in the `ota_files` directory that don't have a corresponding entry in `ota.toml` are silently ignored.
When `--payload` is specified, the payload and OTA zip will be packed at the same time, which is significantly more efficient than packing the payload and OTA zip separately. The payload packing process reads the same `payload.toml` and `payload_images` inputs as `avbroot payload pack`.
When packing an OTA zip, the `metadata.property_files` field in `ota.toml` may potentially be recomputed. To write a TOML file containing the new values, use `--output-info <output TOML>`. It is safe to overwrite the existing `ota.toml` if desired.
### Repacking an OTA zip
```bash
avbroot zip repack -i <input OTA> -o <output OTA> -k <OTA private key>
```
This subcommand is logically equivalent to `avbroot zip unpack` followed by `avbroot zip pack`, except more efficient.
**WARNING**: This is generally not a useful command. Resigning the OTA zip without also resigning the payload binary inside results in an invalid OTA.
### Showing OTA metadata
```bash
avbroot zip info -i <input OTA>
```
This subcommand shows all of the OTA metadata fields. If both the modern protobuf metadata and the legacy plain text metadata exist, the protobuf metadata takes precedence.
(All of the `zip` subcommands show this information. This specific subcommand just does so without performing any other operation.)
+34 -3
View File
@@ -134,9 +134,26 @@ The commands above are provided for convenience. avbroot is compatible with any
If you lose your AVB or OTA signing key, you will no longer be able to sign new OTA zips. You will have to generate new signing keys and unlock your bootloader again (triggering a data wipe). Follow the [Usage section](#usage) as if doing an initial setup.
### AOSP keys
If you're building Android from source, this is how avbroot's keys correspond to AOSP's keys:
| avbroot | AOSP |
|----------------|-----------------------|
| `avb.key` | `avb.pem` |
| `avb_pkmd.bin` | `avb_pkmd.bin` |
| `ota.key` | `releasekey.pk8` |
| `ota.crt` | `releasekey.x509.pem` |
To use an AOSP `releasekey.pk8` key with avbroot, it must be converted from DER to PEM first:
```bash
openssl pkcs8 -topk8 -scrypt -in releasekey.pk8 -out ota.key -outform PEM
```
## Initial setup
1. Make sure that the version of fastboot is 34 or newer. Older versions have bugs that prevent the `fastboot flashall` command (required later) from working properly.
1. Make sure that the version of fastboot is 35 or newer. Older versions have bugs that prevent the `fastboot flashall` command (required later) from working properly.
```bash
fastboot --version
@@ -237,7 +254,7 @@ Updates to Android, Magisk, and KernelSU are all done the same way: by patching
2. If Magisk or KernelSU is being updated, first install their new `.apk`. If you happen to open the app, make sure it **does not** flash the boot image. Cancel the boot image update prompts if needed.
3. Reboot to recovery mode. If the screen is stuck at a `No command` message, press the volume up button once while holding down the power button.
3. Reboot to recovery mode: `adb reboot recovery`. If the screen is stuck at a `No command` message, press the volume up button once while holding down the power button.
4. Sideload the patched OTA with `adb sideload`.
@@ -421,6 +438,10 @@ The only behavior this changes is where the partition is read from. When using `
This has no impact on what patches are applied. For example, when using Magisk, the root patch is applied to the boot partition, no matter if the partition came from the original `payload.bin` or from `--replace`.
### Re-signing partitions
avbroot will automatically re-sign any partitions in the OTA that it modifies. However, partitions that are otherwise unmodified can also be re-signed with `--re-sign <partition name>`. This is useful, for example, when the OTA contains partitions signed with the public AOSP test key.
### Booting signed GSIs
Android's [Dynamic System Updates (DSU)](https://developer.android.com/topic/dsu) feature uses a different root of trust than the regular system. Instead of using the bootloader's `avb_custom_key`, it obtains the trusted keys from the `first_stage_ramdisk/avb/*.avbpubkey` files inside the `init_boot` or `vendor_boot` ramdisk. These files are encoded in the same binary format as `avb_pkmd.bin`.
@@ -499,6 +520,16 @@ By default, this only extracts the images that could potentially be patched by a
This command also supports extracting the embedded OTA certificate and AVB public key using the `--cert-ota` and `--public-key-avb` options. To extract only these components, pass in `--none` to skip extracting partition images.
### Listing partitions in OTA
To list all partitions in an OTA, run:
```bash
avbroot ota list --input ota.zip
```
The output format is one partition per line with no formatting.
### Zip write mode
By default, avbroot uses streaming writes for the output OTA during patching. This means it computes the sha256 digest for the digital signature as the file is being written. This mode causes the zip file to contain data descriptors, which is part of the zip standard and works on the vast majority of devices. However, some devices may have broken zip file parsers and fail to properly read OTA zip files containing data descriptors. If this is the case, pass in `--zip-mode seekable` when patching.
@@ -610,4 +641,4 @@ Contributions are welcome! However, I'm unlikely to accept changes for supportin
## License
avbroot is licensed under GPLv3. Please see [`LICENSE`](./LICENSE) for the full license text.
avbroot is licensed under GPL-3.0-only. Please see [`LICENSE`](./LICENSE) for the full license text.
+40 -3
View File
@@ -132,9 +132,26 @@ avbroot совместим с любым стандартным 4096-битны
Если вы потеряете ключ(-и) подписи AVB или OTA, вы больше не сможете подписывать новые OTA-архивы. Придется генерировать новые ключи подписи и разблокировать загрузчик (что приведет к стиранию всех данных). В таком случае возвращайтесь к инструкции в разделе [использования.](#использование)
### Ключи AOSP
Если вы собираете Android из исходного кода, вот как ключи avbroot соотносятся с ключами AOSP:
| avbroot | AOSP |
|----------------|-----------------------|
| `avb.key` | `avb.pem` |
| `avb_pkmd.bin` | `avb_pkmd.bin` |
| `ota.key` | `releasekey.pk8` |
| `ota.crt` | `releasekey.x509.pem` |
Для использования ключа AOSP `releasekey.pk8` в avbroot, его необходимо предварительно преобразовать из формата DER в PEM:
```bash
openssl pkcs8 -topk8 -scrypt -in releasekey.pk8 -out ota.key -outform PEM
```
## Первоначальная настройка
1. Убедитесь, что вы используете утилиту fastboot версии 34 или новее. Предыдущие версии содержат баги, что не позволяют команде `fastboot flashall` (которая понадобится по ходу инструкции) работать правильно.
1. Убедитесь, что вы используете утилиту fastboot версии 35 или новее. Предыдущие версии содержат баги, что не позволяют команде `fastboot flashall` (которая понадобится по ходу инструкции) работать правильно.
```bash
fastboot --version
@@ -235,7 +252,7 @@ avbroot совместим с любым стандартным 4096-битны
2. Если обновляется Magisk или KernelSU, сначала установите их новый `.apk`. Если вы случайно открыли приложение, убедитесь, что оно **не начало** прошивать загрузочный образ. Если в самом приложении появится предложение обновить загрузочный образ, отклоните его.
3. Перезагрузитесь в режим Recovery. Если устройство повисло на сплеше с сообщением "No command", удерживайте кнопку питания, а затем нажмите кнопку увеличения громкости один раз.
3. Перезагрузитесь в режим Recovery: `adb reboot recovery`. Если устройство повисло на сплеше с сообщением "No command", удерживайте кнопку питания, а затем один раз нажмите кнопку увеличения громкости.
4. Обновитесь (Apply update from adb → `adb sideload <ota.zip.patched>`).
@@ -419,6 +436,16 @@ avbroot поддерживает подмену целых образов в OTA
Это не влияет на ход применения пачтей. Например, при использовании Magisk, патч получения root-прав применяется к загрузочному образу одинаково, независимо от того, был ли он получен из оригинального `payload.bin` или это файл, указанный через `--replace`.
### Переподписание разделов
avbroot автоматически переподписывает все разделы в OTA, которые он изменяет. Однако и те разделы, что не были затронуты, также можно переподписать с помощью параметра `--re-sign <имя раздела>`. Это полезно, например, когда OTA содержит разделы, подписанные публичным тестовым ключом AOSP (test key).
### Загрузка подписанных GSI
Функция [динамических обновлений системы (DSU)](https://developer.android.com/topic/dsu?hl=ru) в Android использует корень доверия, отличный от используемого обычной системой. Вместо `avb_custom_key` в загрузчике, она получает доверенные ключи из файлов `first_stage_ramdisk/avb/*.avbpubkey` в ramdisk'е разделов `init_boot` или `vendor_boot`. Эти файлы закодированы в том же двоичном формате, что и файл `avb_pkmd.bin`.
avbroot может добавить пользовательский публичный ключ AVB в эту директорию с помощью параметра `--dsu` при патчинге OTA. Это позволяет запускать [общие образы системы (GSI)](https://developer.android.com/topic/generic-system-image?hl=ru), подписанные пользовательским ключом AVB.
### Очистка флагов vbmeta
Некоторые сборки Android-прошивок могут поставляться с образом `vbmeta`, в котором флаги установлены таким образом, что AVB фактически отключен. Если avbroot сталкивается с такими образом, процесс патчинга завершается ошибкой с сообщением следующего типа:
@@ -491,6 +518,16 @@ avbroot ota extract \
Эта команда также поддерживает извлечение встроенного сертификата OTA и публичного ключа AVB с помощью опций `--cert-ota` и `--public-key-avb`. Чтобы извлечь только эти компоненты, укажите аргумент `--none`, чтобы пропустить извлечение образов разделов.
### Вывод списка разделов из OTA
Чтобы вывести список всех разделов, содержащихся в OTA, используйте команду:
```bash
avbroot ota list --input ota.zip
```
Формат вывода: один раздел на строку без дополнительного форматирования.
### Режим записи ZIP
По умолчанию, avbroot использует потоковую запись для вывода OTA во время патчинга. Это означает, что он вычисляет дайджест sha256 для цифровой подписи одновременно с записью файла. Такой режим приводит к тому, что в ZIP-файле появляются описатели данных, что является частью стандарта ZIP и работает на подавляющем большинстве устройств. Однако некоторые устройства могут иметь некорректно работающие парсеры ZIP-файлов и не смогут правильно прочитать ZIP-файлы OTA, содержащие описатели данных. Если это так, используйте опцию `--zip-mode seekable` при патчинге.
@@ -598,5 +635,5 @@ cargo android build --release --target aarch64-linux-android
## Лицензия
avbroot распространяется по лицензии GPLv3. Полный текст лицензии см. в [`LICENSE`.](./LICENSE)
avbroot распространяется по лицензии GPL-3.0-only. Полный текст лицензии см. в [`LICENSE`.](./LICENSE)
+8 -6
View File
@@ -4,7 +4,8 @@ version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
publish = false
publish.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -26,13 +27,14 @@ dlv-list = "0.6.0"
flate2 = { version = "1.0.29", features = ["zlib-rs"] }
gf256 = { version = "0.3.0", features = ["rs"] }
hex = { version = "0.4.3", features = ["serde"] }
lz4_flex = "0.11.1"
lzma-rust2 = "0.10.0"
lz4_flex = "0.13.0"
lzma-rust2 = "0.16.1"
memchr = "2.6.0"
# We can't upgrade to 0.9.0 until rsa updates its dependency.
num-bigint-dig = "0.8.4"
num-traits = "0.2.16"
passterm = "2.0.3"
phf = { version = "0.12.1", features = ["macros"] }
phf = { version = "0.14.0", features = ["macros"] }
pkcs8 = { version = "0.10.2", features = ["encryption", "pem"] }
prost = "0.14.1"
# We can't upgrade to 0.9.0 until rsa updates its rand_core dependency.
@@ -51,7 +53,7 @@ sha1 = "0.10.5"
sha2 = "0.10.7"
tempfile = "3.8.0"
thiserror = "2.0.3"
toml_edit = { version = "0.23.3", features = ["serde"] }
toml = { version = "1.0.1", features = ["serde"] }
topological-sort = "0.2.2"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
@@ -64,7 +66,7 @@ libc = "0.2.158"
rustix = { version = "1.0.3", default-features = false, features = ["process"] }
[target.'cfg(target_os = "android")'.dependencies]
system-properties = { git = "https://github.com/chenxiaolong/system-properties", tag = "v0.2.1" }
system-properties = { git = "https://github.com/chenxiaolong/system-properties", tag = "v0.3.0" }
[build-dependencies]
constcat = "0.6.0"
+28 -13
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{env, ffi::OsStr, fs, io, path::Path};
@@ -35,6 +35,10 @@ fn main() {
const CUE_PU: &str = ".chromeos_update_engine.PartitionUpdate";
const CUE_VABCFS: &str = ".chromeos_update_engine.VABCFeatureSet";
const BTR_DS: &str = ".build.tools.releasetools.DeviceState";
const BTR_OM: &str = ".build.tools.releasetools.OtaMetadata";
const BTR_PS: &str = ".build.tools.releasetools.PartitionState";
const DERIVE_SERDE: &str = "#[derive(serde::Deserialize, serde::Serialize)]";
const SERDE_DEFAULT: &str = "#[serde(default)]";
const SERDE_SKIP: &str = "#[serde(skip)]";
@@ -45,19 +49,25 @@ fn main() {
prost_build::Config::new()
.btree_map(["."])
// Allow deserializing and serializing the types we care about.
.type_attribute(CUE_AI, DERIVE_SERDE)
.type_attribute(CUE_DAM, DERIVE_SERDE)
.type_attribute(CUE_DPG, DERIVE_SERDE)
.type_attribute(CUE_DPM, DERIVE_SERDE)
.type_attribute(CUE_PU, DERIVE_SERDE)
.type_attribute(CUE_VABCFS, DERIVE_SERDE)
.message_attribute(CUE_AI, DERIVE_SERDE)
.message_attribute(CUE_DAM, DERIVE_SERDE)
.message_attribute(CUE_DPG, DERIVE_SERDE)
.message_attribute(CUE_DPM, DERIVE_SERDE)
.message_attribute(CUE_PU, DERIVE_SERDE)
.message_attribute(CUE_VABCFS, DERIVE_SERDE)
.message_attribute(BTR_DS, DERIVE_SERDE)
.message_attribute(BTR_OM, DERIVE_SERDE)
.message_attribute(BTR_PS, DERIVE_SERDE)
// Allow default-initializing all fields.
.type_attribute(CUE_AI, SERDE_DEFAULT)
.type_attribute(CUE_DAM, SERDE_DEFAULT)
.type_attribute(CUE_DPG, SERDE_DEFAULT)
.type_attribute(CUE_DPM, SERDE_DEFAULT)
.type_attribute(CUE_PU, SERDE_DEFAULT)
.type_attribute(CUE_VABCFS, SERDE_DEFAULT)
.message_attribute(CUE_AI, SERDE_DEFAULT)
.message_attribute(CUE_DAM, SERDE_DEFAULT)
.message_attribute(CUE_DPG, SERDE_DEFAULT)
.message_attribute(CUE_DPM, SERDE_DEFAULT)
.message_attribute(CUE_PU, SERDE_DEFAULT)
.message_attribute(CUE_VABCFS, SERDE_DEFAULT)
.message_attribute(BTR_DS, SERDE_DEFAULT)
.message_attribute(BTR_OM, SERDE_DEFAULT)
.message_attribute(BTR_PS, SERDE_DEFAULT)
// Don't serialize fields that define the structure of the payload
// binary and that we recompute during packing.
.field_attribute(c!(CUE_DAM, ".signatures_offset"), SERDE_SKIP)
@@ -84,6 +94,11 @@ fn main() {
.field_attribute(c!(CUE_DAM, ".partitions"), SERDE_SKIP_IF_VEC_EMPTY)
.field_attribute(c!(CUE_DPG, ".partition_names"), SERDE_SKIP_IF_VEC_EMPTY)
.field_attribute(c!(CUE_DPM, ".groups"), SERDE_SKIP_IF_VEC_EMPTY)
// Serialize enums as their enum types, not their underlying reprs.
.field_attribute(
c!(BTR_OM, ".type"),
"#[serde(with = \"crate::protobuf::ota_type\")]",
)
.compile_fds(file_descriptors)
.unwrap();
}
+4 -3
View File
@@ -104,9 +104,10 @@ message OtaMetadata {
// The expected device state after the update.
DeviceState postcondition = 6;
// True if the ota that updates a device to support dynamic partitions, where
// the source build doesn't support it.
bool retrofit_dynamic_partitions = 7;
// This was previously a boolean flag called retrofit_dynamic_partitions, but
// is no longer used or supported.
reserved 7;
// The required size of the cache partition, only valid for non-A/B update.
int64 required_cache = 8;
+9
View File
@@ -82,6 +82,8 @@
// - REPLACE_XZ: Replace the dst_extents with the contents of the attached
// xz file after decompression. The xz file should only use crc32 or no crc at
// all to be compatible with xz-embedded.
// - REPLACE_ZSTD: Replace the dst_extents with the contents of the attached
// zstd file after decompression.
// - PUFFDIFF: Read the data in src_extents in the old partition, perform
// puffpatch with the attached data and write the new data to dst_extents in
// the new partition.
@@ -174,6 +176,9 @@ message InstallOperation {
// On minor version 9 or newer, these operations are supported:
LZ4DIFF_BSDIFF = 12;
LZ4DIFF_PUFFDIFF = 13;
// On minor version 10 or newer, these operations are supported:
REPLACE_ZSTD = 14;
}
required Type type = 1;
@@ -376,6 +381,10 @@ message DynamicPartitionMetadata {
// Max bytes to be compressed at once during ota. Options: 4k, 8k, 16k, 32k,
// 64k, 128k
optional uint64 compression_factor = 7;
// Whether to disable UBLK for OTA. This will force dm-user as OTA backend
// choice even if device was configured for UBLK based snapshots.
optional bool disable_ublk = 8;
}
// Definition has been duplicated from
+6 -9
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -13,7 +13,7 @@ use clap::{Parser, Subcommand, ValueEnum};
use tracing::{Level, debug};
use tracing_subscriber::fmt::{format::Writer, time::FormatTime};
use crate::cli::{avb, boot, completion, cpio, fec, hashtree, key, lp, ota, payload, sparse};
use crate::cli::{avb, boot, completion, cpio, fec, hashtree, key, lp, ota, payload, sparse, zip};
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
@@ -29,6 +29,7 @@ pub enum Command {
Ota(ota::OtaCli),
Payload(payload::PayloadCli),
Sparse(sparse::SparseCli),
Zip(zip::ZipCli),
/// (Deprecated: Use `avbroot ota patch` instead.)
#[command(hide = true)]
Patch(ota::PatchCli),
@@ -40,19 +41,14 @@ pub enum Command {
MagiskInfo(boot::MagiskInfoCli),
}
#[derive(Debug, Clone, Copy, ValueEnum)]
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
pub enum LogFormat {
#[default]
Short,
Medium,
Long,
}
impl Default for LogFormat {
fn default() -> Self {
Self::Short
}
}
impl fmt::Display for LogFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_possible_value().ok_or(fmt::Error)?.get_name())
@@ -137,6 +133,7 @@ pub fn main(logging_initialized: &AtomicBool, cancel_signal: &AtomicBool) -> Res
Command::Ota(c) => ota::ota_main(&c, cancel_signal),
Command::Payload(c) => payload::payload_main(&c, cancel_signal),
Command::Sparse(c) => sparse::sparse_main(&c, cancel_signal),
Command::Zip(c) => zip::zip_main(&c, cancel_signal),
// Deprecated aliases.
Command::Patch(c) => ota::patch_subcommand(&c, cancel_signal),
Command::Extract(c) => ota::extract_subcommand(&c, cancel_signal),
+66 -12
View File
@@ -29,6 +29,53 @@ use crate::{
util,
};
/// SHA-256 digests of the AOSP test keys encoded in the AVB binary format. This
/// excludes 8192-bit keys since RustCrypto doesn't support them anyway.
const PUBLIC_PUBKEYS: [[u8; 32]; 7] = [
// testkey_cert_pik.pem
[
0xce, 0x46, 0x4c, 0x0f, 0xef, 0xc0, 0x23, 0x76, 0x0a, 0xdc, 0x3f, 0xe0, 0x20, 0x7c, 0x28,
0x2d, 0x33, 0x7e, 0x1b, 0x91, 0xe3, 0xdf, 0x7a, 0xb4, 0xfe, 0xe4, 0x3f, 0xcd, 0x62, 0x0d,
0xb4, 0x6d,
],
// testkey_cert_prk.pem
[
0x86, 0xb0, 0x4a, 0x9d, 0x62, 0x80, 0x7f, 0xda, 0x80, 0xcb, 0xf4, 0x45, 0x11, 0x8e, 0x8a,
0x4a, 0x73, 0xaa, 0x59, 0x37, 0x28, 0x3b, 0x49, 0xe0, 0x18, 0xe4, 0x32, 0xdd, 0x8e, 0x3a,
0x76, 0xa1,
],
// testkey_cert_psk.pem
[
0x7e, 0xc0, 0x2e, 0xe1, 0xbe, 0x69, 0x63, 0x66, 0xf3, 0xfa, 0x91, 0x24, 0x0a, 0x8e, 0xc6,
0x81, 0x25, 0xc4, 0x14, 0x5d, 0x69, 0x8f, 0x59, 0x7a, 0xa2, 0xb3, 0x46, 0x4b, 0x59, 0xca,
0x7f, 0xc3,
],
// testkey_cert_puk.pem
[
0xc6, 0x08, 0xff, 0xa5, 0xe7, 0xee, 0x92, 0x98, 0x4f, 0x44, 0x86, 0xe0, 0x30, 0x88, 0x44,
0x0c, 0x36, 0x0c, 0xf3, 0x15, 0xec, 0x6a, 0x5f, 0x52, 0xae, 0xf7, 0x90, 0xcb, 0x30, 0xef,
0xce, 0xc9,
],
// testkey_rsa2048.pem
[
0x22, 0xde, 0x39, 0x94, 0x53, 0x21, 0x96, 0xf6, 0x1c, 0x03, 0x9e, 0x90, 0x26, 0x0d, 0x78,
0xa9, 0x3a, 0x4c, 0x57, 0x36, 0x2c, 0x7e, 0x78, 0x9b, 0xe9, 0x28, 0x03, 0x6e, 0x80, 0xb7,
0x7c, 0x8c,
],
// testkey_rsa2048_2.pem
[
0x2b, 0xed, 0x47, 0x45, 0x1b, 0xc6, 0x98, 0xe9, 0xe8, 0x2d, 0x92, 0xa6, 0x66, 0x8b, 0xd0,
0x3a, 0xb6, 0xcf, 0x8d, 0xd1, 0xa1, 0x44, 0x34, 0x1c, 0xb7, 0xf4, 0x26, 0xf2, 0x0b, 0x28,
0x79, 0xcf,
],
// testkey_rsa4096.pem
[
0x77, 0x28, 0xe3, 0x0f, 0x50, 0xbf, 0xa5, 0xce, 0xa1, 0x65, 0xf4, 0x73, 0x17, 0x5a, 0x08,
0x80, 0x3f, 0x6a, 0x83, 0x46, 0x64, 0x2b, 0x5a, 0xa1, 0x09, 0x13, 0xe9, 0xd9, 0xe6, 0xde,
0xfe, 0xf6,
],
];
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
struct AvbInfo {
header: Header,
@@ -37,9 +84,9 @@ struct AvbInfo {
}
fn read_avb_image(path: &Path) -> Result<(AvbInfo, BufReader<File>)> {
let file = File::open(path)
let mut reader = File::open(path)
.map(BufReader::new)
.with_context(|| format!("Failed to open AVB image for reading: {path:?}"))?;
let mut reader = BufReader::new(file);
let (header, footer, image_size) = avb::load_image(&mut reader)
.with_context(|| format!("Failed to load AVB image: {path:?}"))?;
@@ -78,7 +125,7 @@ fn write_avb_image(file: &File, info: &mut AvbInfo, recompute_size: bool) -> Res
fn read_info(path: &Path) -> Result<AvbInfo> {
let data = fs::read_to_string(path)
.with_context(|| format!("Failed to read AVB info TOML: {path:?}"))?;
let info = toml_edit::de::from_str(&data)
let info = toml::de::from_str(&data)
.with_context(|| format!("Failed to parse AVB info TOML: {path:?}"))?;
Ok(info)
@@ -86,7 +133,7 @@ fn read_info(path: &Path) -> Result<AvbInfo> {
/// Write AVB information to TOML file.
fn write_info(path: &Path, info: &AvbInfo) -> Result<()> {
let data = toml_edit::ser::to_string_pretty(info)
let data = toml::ser::to_string_pretty(info)
.with_context(|| format!("Failed to serialize AVB info TOML: {path:?}"))?;
fs::write(path, data).with_context(|| format!("Failed to write AVB info TOML: {path:?}"))?;
@@ -101,14 +148,14 @@ fn write_raw(
size: u64,
cancel_signal: &AtomicBool,
) -> Result<File> {
let file = fs::OpenOptions::new()
let mut writer = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.map(BufWriter::new)
.with_context(|| format!("Failed to open raw image for writing: {path:?}"))?;
let mut writer = BufWriter::new(file);
reader
.rewind()
@@ -500,6 +547,8 @@ pub fn verify_headers(
if let Some(k) = &public_key {
let prefix = format!("{name} has a signed vbmeta header");
let encoded = avb::encode_public_key(k)?;
let digest = Sha256::digest(&encoded);
match trust_method {
TrustMethod::Key(expected) => {
@@ -510,9 +559,6 @@ pub fn verify_headers(
}
}
TrustMethod::KeyDigest(expected_sha256) => {
let encoded = avb::encode_public_key(k)?;
let digest = Sha256::digest(&encoded);
if digest.as_slice() == expected_sha256 {
info!("{prefix}");
} else {
@@ -523,6 +569,10 @@ pub fn verify_headers(
warn!("{prefix}, but parent does not list a trusted key");
}
}
if PUBLIC_PUBKEYS.iter().any(|p| p == digest.as_slice()) {
warn!("{name} is signed with public AOSP test key");
}
} else {
info!("{name} has an unsigned vbmeta header");
}
@@ -735,7 +785,7 @@ fn unpack_subcommand(cli: &UnpackCli, cancel_signal: &AtomicBool) -> Result<()>
write_info(&cli.output_info, &info)?;
if info.footer.is_some() {
if info.footer.is_some() && !cli.no_output_raw {
write_raw_and_verify(
&cli.output_raw,
&mut reader,
@@ -1025,6 +1075,10 @@ struct UnpackCli {
#[arg(long, value_name = "FILE", value_parser, default_value = "raw.img")]
output_raw: PathBuf,
/// Do not output raw image.
#[arg(long, conflicts_with = "output_raw")]
no_output_raw: bool,
/// Ignore invalid digests or FEC data.
#[arg(long)]
ignore_invalid: bool,
@@ -1165,8 +1219,8 @@ struct VerifyCli {
struct VerifyDeviceCli {
/// Path to public key in AVB binary format.
///
/// If this is not specified, the signatures can only be checked for
/// validity, not whether they are trusted.
/// If this is not specified, then the signature is verified against the
/// ro.boot.vbmeta.public_key_digest value provided by the bootloader.
#[arg(short, long, value_name = "FILE", value_parser)]
public_key: Option<PathBuf>,
+75 -25
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -16,8 +16,9 @@ use crate::{
};
fn read_image(path: &Path) -> Result<BootImage> {
let file = File::open(path).with_context(|| format!("Failed to open for reading: {path:?}"))?;
let reader = BufReader::new(file);
let reader = File::open(path)
.map(BufReader::new)
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
let image = BootImage::from_reader(reader)
.with_context(|| format!("Failed to read boot image: {path:?}"))?;
@@ -25,9 +26,9 @@ fn read_image(path: &Path) -> Result<BootImage> {
}
fn write_image(path: &Path, image: &BootImage) -> Result<()> {
let file =
File::create(path).with_context(|| format!("Failed to open for writing: {path:?}"))?;
let mut writer = BufWriter::new(file);
let mut writer = File::create(path)
.map(BufWriter::new)
.with_context(|| format!("Failed to open for writing: {path:?}"))?;
image
.to_writer(&mut writer)
.with_context(|| format!("Failed to write boot image: {path:?}"))?;
@@ -41,14 +42,14 @@ fn write_image(path: &Path, image: &BootImage) -> Result<()> {
fn read_header(path: &Path) -> Result<BootImage> {
let data = fs::read_to_string(path)
.with_context(|| format!("Failed to read header TOML: {path:?}"))?;
let image = toml_edit::de::from_str(&data)
let image = toml::de::from_str(&data)
.with_context(|| format!("Failed to parse header TOML: {path:?}"))?;
Ok(image)
}
fn write_header(path: &Path, image: &BootImage) -> Result<()> {
let data = toml_edit::ser::to_string_pretty(image)
let data = toml::ser::to_string_pretty(image)
.with_context(|| format!("Failed to serialize header TOML: {path:?}"))?;
fs::write(path, data).with_context(|| format!("Failed to write header TOML: {path:?}"))?;
@@ -76,12 +77,12 @@ fn read_text_if_exists(path: &Path) -> Result<Option<String>> {
}
fn read_avb_header_if_exists(path: &Path) -> Result<Option<Header>> {
let file = match File::open(path) {
Ok(f) => f,
let reader = match File::open(path) {
Ok(f) => BufReader::new(f),
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(e) => Err(e).with_context(|| format!("Failed to open for reading: {path:?}"))?,
};
let header = Header::from_reader(BufReader::new(file))
let header = Header::from_reader(reader)
.with_context(|| format!("Failed to read vbmeta header: {path:?}"))?;
Ok(Some(header))
@@ -105,9 +106,15 @@ fn write_text_if_not_empty(path: &Path, text: &str) -> Result<()> {
}
fn write_avb_header(path: &Path, header: &Header) -> Result<()> {
let file =
File::create(path).with_context(|| format!("Failed to open for writing: {path:?}"))?;
header.to_writer(BufWriter::new(file))?;
let mut writer = File::create(path)
.map(BufWriter::new)
.with_context(|| format!("Failed to open for writing: {path:?}"))?;
header
.to_writer(&mut writer)
.with_context(|| format!("Failed to write AVB header: {path:?}"))?;
writer
.flush()
.with_context(|| format!("Failed to flush AVB header: {path:?}"))?;
Ok(())
}
@@ -164,30 +171,44 @@ fn unpack_subcommand(boot_cli: &BootCli, cli: &UnpackCli) -> Result<()> {
}
}
if let Some(data) = kernel {
if let Some(data) = kernel
&& !cli.no_output_kernel
{
write_data_if_not_empty(&cli.output_kernel, data)?;
}
if let Some(data) = second {
if let Some(data) = second
&& !cli.no_output_second
{
write_data_if_not_empty(&cli.output_second, data)?;
}
if let Some(data) = recovery_dtbo {
if let Some(data) = recovery_dtbo
&& !cli.no_output_recovery_dtbo
{
write_data_if_not_empty(&cli.output_recovery_dtbo, data)?;
}
if let Some(data) = dtb {
if let Some(data) = dtb
&& !cli.no_output_dtb
{
write_data_if_not_empty(&cli.output_dtb, data)?;
}
if let Some(header) = vts_signature {
if let Some(header) = vts_signature
&& !cli.no_output_vts_signature
{
write_avb_header(&cli.output_vts_signature, header)?;
}
if let Some(text) = bootconfig {
if let Some(text) = bootconfig
&& !cli.no_output_bootconfig
{
write_text_if_not_empty(&cli.output_bootconfig, text)?;
}
for (i, data) in ramdisks.iter().enumerate() {
let mut path = cli.output_ramdisk_prefix.as_os_str().to_owned();
path.push(i.to_string());
if !cli.no_output_ramdisk {
for (i, data) in ramdisks.iter().enumerate() {
let mut path = cli.output_ramdisk_prefix.as_os_str().to_owned();
path.push(i.to_string());
write_data_if_not_empty(Path::new(&path), data)?;
write_data_if_not_empty(Path::new(&path), data)?;
}
}
Ok(())
@@ -272,8 +293,9 @@ fn info_subcommand(boot_cli: &BootCli, cli: &InfoCli) -> Result<()> {
pub fn magisk_info_subcommand(cli: &MagiskInfoCli) -> Result<()> {
let raw_reader = File::open(&cli.image)
.map(BufReader::new)
.with_context(|| format!("Failed to open for reading: {:?}", cli.image))?;
let boot_image = BootImage::from_reader(BufReader::new(raw_reader))
let boot_image = BootImage::from_reader(raw_reader)
.with_context(|| format!("Failed to load boot image: {:?}", cli.image))?;
let mut ramdisks = vec![];
@@ -339,6 +361,10 @@ struct UnpackCli {
#[arg(long, value_name = "FILE", value_parser, default_value = "kernel.img")]
output_kernel: PathBuf,
/// Do not output kernel image.
#[arg(long, conflicts_with = "output_kernel")]
no_output_kernel: bool,
/// Path prefix for output ramdisk images.
#[arg(
long,
@@ -348,10 +374,18 @@ struct UnpackCli {
)]
output_ramdisk_prefix: PathBuf,
/// Do not output ramdisk images.
#[arg(long, conflicts_with = "output_ramdisk_prefix")]
no_output_ramdisk: bool,
/// Path to output second stage bootloader image.
#[arg(long, value_name = "FILE", value_parser, default_value = "second.img")]
output_second: PathBuf,
/// Do not output second stage bootloader image.
#[arg(long, conflicts_with = "output_second")]
no_output_second: bool,
/// Path to output recovery dtbo/acpio image.
#[arg(
long,
@@ -361,10 +395,18 @@ struct UnpackCli {
)]
output_recovery_dtbo: PathBuf,
/// Do not output recovery dtbo/acpio image.
#[arg(long, conflicts_with = "output_recovery_dtbo")]
no_output_recovery_dtbo: bool,
/// Path to output device tree blob image.
#[arg(long, value_name = "FILE", value_parser, default_value = "dtb.img")]
output_dtb: PathBuf,
/// Do not output device tree blob image.
#[arg(long, conflicts_with = "output_dtb")]
no_output_dtb: bool,
/// Path to output VTS signature.
#[arg(
long,
@@ -374,6 +416,10 @@ struct UnpackCli {
)]
output_vts_signature: PathBuf,
/// Do not output VTS signature.
#[arg(long, conflicts_with = "output_vts_signature")]
no_output_vts_signature: bool,
/// Path to output bootconfig text.
#[arg(
long,
@@ -382,6 +428,10 @@ struct UnpackCli {
default_value = "bootconfig.txt"
)]
output_bootconfig: PathBuf,
/// Do not output bootconfig text.
#[arg(long, conflicts_with = "output_bootconfig")]
no_output_bootconfig: bool,
}
/// Pack a boot image.
+28 -21
View File
@@ -36,9 +36,10 @@ fn open_reader(
CpioReader<CompressedReader<BufReader<File>>>,
CompressedFormat,
)> {
let file =
File::open(path).with_context(|| format!("Failed to open cpio for reading: {path:?}"))?;
let reader = CompressedReader::new(BufReader::new(file), true)
let raw_reader = File::open(path)
.map(BufReader::new)
.with_context(|| format!("Failed to open cpio for reading: {path:?}"))?;
let reader = CompressedReader::new(raw_reader, true)
.with_context(|| format!("Failed to open decompressor: {path:?}"))?;
let format = reader.format();
let cpio_reader = CpioReader::new(reader, include_trailer);
@@ -50,9 +51,10 @@ fn open_writer(
path: &Path,
format: CompressedFormat,
) -> Result<CpioWriter<CompressedWriter<BufWriter<File>>>> {
let file =
File::create(path).with_context(|| format!("Failed to open cpio for writing: {path:?}"))?;
let writer = CompressedWriter::new(BufWriter::new(file), format)
let raw_writer = File::create(path)
.map(BufWriter::new)
.with_context(|| format!("Failed to open cpio for writing: {path:?}"))?;
let writer = CompressedWriter::new(raw_writer, format)
.with_context(|| format!("Failed to open compressor: {path:?}"))?;
let cpio_writer = CpioWriter::new(writer, false);
@@ -73,7 +75,7 @@ fn flush_writer(writer: CpioWriter<CompressedWriter<BufWriter<File>>>) -> Result
fn read_info(path: &Path) -> Result<CpioInfo> {
let data = fs::read_to_string(path)
.with_context(|| format!("Failed to read cpio info TOML: {path:?}"))?;
let info = toml_edit::de::from_str(&data)
let info = toml::de::from_str(&data)
.with_context(|| format!("Failed to parse cpio info TOML: {path:?}"))?;
Ok(info)
@@ -81,7 +83,7 @@ fn read_info(path: &Path) -> Result<CpioInfo> {
/// Write cpio information to TOML file.
fn write_info(path: &Path, info: &CpioInfo) -> Result<()> {
let data = toml_edit::ser::to_string_pretty(info)
let data = toml::ser::to_string_pretty(info)
.with_context(|| format!("Failed to serialize cpio info TOML: {path:?}"))?;
fs::write(path, data).with_context(|| format!("Failed to write cpio info TOML: {path:?}"))?;
@@ -170,22 +172,24 @@ fn unpack_subcommand(
display_format(cpio_cli, format);
fs::create_dir_all(&cli.output_tree)
.with_context(|| format!("Failed to create directory: {:?}", cli.output_tree))?;
if !cli.no_output_tree {
fs::create_dir_all(&cli.output_tree)
.with_context(|| format!("Failed to create directory: {:?}", cli.output_tree))?;
while let Some(entry) = reader.next_entry().context("Failed to read cpio entry")? {
display_entry(cpio_cli, &entry);
while let Some(entry) = reader.next_entry().context("Failed to read cpio entry")? {
display_entry(cpio_cli, &entry);
if let Some(mut writer) = create_tree_file(&cli.output_tree, &entry)? {
let file_size = entry.data.size()?;
if let Some(mut writer) = create_tree_file(&cli.output_tree, &entry)? {
let file_size = entry.data.size()?;
stream::copy_n(&mut reader, &mut writer, file_size.into(), cancel_signal)
.context("Failed to copy data")?;
stream::copy_n(&mut reader, &mut writer, file_size.into(), cancel_signal)
.context("Failed to copy data")?;
writer.into_inner().context("Failed to flush data")?;
writer.into_inner().context("Failed to flush data")?;
}
info.entries.push(entry);
}
info.entries.push(entry);
}
write_info(&cli.output_info, &info)?;
@@ -285,8 +289,7 @@ pub fn cpio_main(cli: &CpioCli, cancel_signal: &AtomicBool) -> Result<()> {
/// TOML file, like the UID/GID, permissions, and symlink targets.
///
/// If any paths inside the cpio archive are unsafe, the extraction process will
/// fail and exit. Extracted files are never written outside of the tree
/// directory, even if an external process tries to interfere.
/// fail and exit.
#[derive(Debug, Parser)]
struct UnpackCli {
/// Path to input cpio file.
@@ -300,6 +303,10 @@ struct UnpackCli {
/// Path to output files directory.
#[arg(long, value_name = "DIR", value_parser, default_value = "cpio_tree")]
output_tree: PathBuf,
/// Do not output files.
#[arg(long, conflicts_with = "output_tree")]
no_output_tree: bool,
}
/// Pack a cpio archive.
+8 -5
View File
@@ -50,14 +50,14 @@ fn open_lp_outputs(paths: &[impl AsRef<Path>]) -> Result<Vec<File>> {
fn read_info(path: &Path) -> Result<Metadata> {
let data = fs::read_to_string(path)
.with_context(|| format!("Failed to read metadata info TOML: {path:?}"))?;
let info = toml_edit::de::from_str(&data)
let info = toml::de::from_str(&data)
.with_context(|| format!("Failed to parse metadata info TOML: {path:?}"))?;
Ok(info)
}
fn write_info(path: &Path, metadata: &Metadata) -> Result<()> {
let data = toml_edit::ser::to_string_pretty(metadata)
let data = toml::ser::to_string_pretty(metadata)
.with_context(|| format!("Failed to serialize metadata info TOML: {path:?}"))?;
fs::write(path, data)
.with_context(|| format!("Failed to write metadata info TOML: {path:?}"))?;
@@ -179,7 +179,7 @@ fn unpack_subcommand(lp_cli: &LpCli, cli: &UnpackCli, cancel_signal: &AtomicBool
write_info(&cli.output_info, &metadata)?;
// For empty images, there's no data to unpack.
if metadata.image_type == ImageType::Empty {
if metadata.image_type == ImageType::Empty || cli.no_output_images {
return Ok(());
}
@@ -515,8 +515,7 @@ pub fn lp_main(cli: &LpCli, cancel_signal: &AtomicBool) -> Result<()> {
/// directory. For empty images, the output images directory is unused.
///
/// If any partition names are unsafe to use in a path, the extraction process
/// will fail and exit. Extracted files are never written outside of the tree
/// directory, even if an external process tries to interfere.
/// will fail and exit.
#[derive(Debug, Parser)]
struct UnpackCli {
/// Path to input LP images.
@@ -535,6 +534,10 @@ struct UnpackCli {
#[arg(long, value_name = "DIR", value_parser, default_value = "lp_images")]
output_images: PathBuf,
/// Do not output images.
#[arg(long, conflicts_with = "output_images")]
no_output_images: bool,
/// The LP metadata slot to use.
///
/// This slot is the only slot where data extents are copied from. Any data
+2 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
pub mod args;
@@ -13,3 +13,4 @@ pub mod lp;
pub mod ota;
pub mod payload;
pub mod sparse;
pub mod zip;
+250 -35
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022-2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2022-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -23,7 +23,7 @@ use rawzip::{
use rayon::{iter::IntoParallelRefIterator, prelude::ParallelIterator};
use tempfile::{NamedTempFile, TempDir};
use topological_sort::TopologicalSort;
use tracing::{debug_span, error, info, warn};
use tracing::{Span, debug_span, error, info, warn};
use x509_cert::Certificate;
use crate::{
@@ -35,7 +35,6 @@ use crate::{
format::{
avb::{self, Descriptor, Header},
ota::{self, SigningWriter, ZipEntry, ZipMode},
padding,
payload::{self, CowVersion, PayloadHeader, PayloadWriter, VabcAlgo, VabcParams},
zip::{
self, ReaderAtWrapper, ZipArchiveReadAtExt, ZipEntriesSafeExt, ZipFileHeaderRecordExt,
@@ -53,7 +52,7 @@ use crate::{
},
stream::{
self, FromReader, HashingWriter, MutexFile, ReadAt, ReadSeek, SectionReader,
SectionReaderAt, ToWriter, UserPosFile, WriteAt, WriteSeek,
SectionReaderAt, UserPosFile, WriteAt, WriteSeek,
},
util,
};
@@ -78,6 +77,23 @@ bitflags! {
}
}
fn is_zero_sha256(mut size: u64, digest: &[u8]) -> bool {
if digest.len() != ring::digest::SHA256_OUTPUT_LEN {
return false;
}
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
while size > 0 {
let n = size.min(util::ZEROS.len() as u64);
context.update(&util::ZEROS[..n as usize]);
size -= n;
}
let zero_digest = context.finish();
zero_digest.as_ref() == digest
}
/// Get the images required for patching. If [`RequiredFlags::SYSTEM`] is
/// specified, then the system image is included. If [`RequiredFlags::ALL_COW`]
/// is specified, then all images with CoW size estimates are included.
@@ -96,6 +112,18 @@ pub fn get_required_images(
} else if required_flags.contains(RequiredFlags::SYSTEM) && name == "system" {
flags |= PartitionFlags::SYSTEM;
} else if name.starts_with("vbmeta") {
let Some(pi) = &partition.new_partition_info else {
continue;
};
// Some devices seem to ship with empty unused vbmeta partitions.
// Use the SHA-256 checksum to skip them so we don't have to extract
// them to check.
let size = pi.size();
if (4096..=65536).contains(&size) && is_zero_sha256(size, pi.hash()) {
continue;
}
flags |= PartitionFlags::VBMETA;
}
@@ -126,19 +154,23 @@ enum InputFileState {
Modified,
}
#[derive(Clone)]
struct InputFile {
file: Arc<File>,
state: InputFileState,
}
/// Open all input files listed in `required_images`. If an image has a path
/// in `external_images`, that file is opened. Otherwise, the image is extracted
/// from the payload into a temporary file (that is unnamed if supported by the
/// operating system).
/// Open all input files listed in `required_images` and `re_sign_images`. If an
/// image has a path in `external_images`, that file is opened. Otherwise, the
/// image is extracted from the payload into a temporary file (that is unnamed
/// if supported by the operating system). Images that are destined to be
/// re-signed do not get copied to a temporary file until later when it can be
/// determined that it wasn't already modified and re-signed due to patching.
fn open_input_files(
payload: &(dyn ReadAt + Sync),
required_images: &HashMap<String, PartitionFlags>,
external_images: &HashMap<String, PathBuf>,
re_sign_images: &HashSet<String>,
header: &PayloadHeader,
cancel_signal: &AtomicBool,
) -> Result<HashMap<String, InputFile>> {
@@ -149,6 +181,7 @@ fn open_input_files(
let all_images = required_images
.keys()
.chain(external_images.keys())
.chain(re_sign_images.iter())
.collect::<HashSet<_>>();
for name in all_images {
@@ -296,6 +329,102 @@ fn patch_system_image<'a>(
Ok((target, ranges))
}
/// Re-sign the images listed in `re_sign_images` if they aren't already
/// modified by the patching process. If the original image is signed, then it
/// will be re-signed with `key_avb`. Otherwise, it is left untouched. If the
/// original image is external, then it will be copied to a temporary file.
fn re_sign_unmodified_images(
re_sign_images: &HashSet<String>,
input_files: &mut HashMap<String, InputFile>,
key_avb: &RsaSigningKey,
block_size: u64,
cancel_signal: &AtomicBool,
) -> Result<()> {
let parent_span = Span::current();
let input_files = Mutex::new(input_files);
re_sign_images
.par_iter()
.try_for_each(|name| -> Result<()> {
let _span = debug_span!(parent: &parent_span, "image", name).entered();
let mut input_file = input_files.lock().unwrap()[name].clone();
if input_file.state == InputFileState::Modified {
// Already re-signed by some other patching operation.
return Ok(());
}
let (mut header, footer, image_size) = avb::load_image(&mut input_file.file)
.with_context(|| format!("Failed to load AVB metadata: {name}"))?;
let orig_header = header.clone();
if !header.public_key.is_empty() {
header
.set_algo_for_key(key_avb)
.with_context(|| format!("Failed to set signature algorithm: {name}"))?;
header
.sign(key_avb)
.with_context(|| format!("Failed to sign AVB metadata: {name}"))?;
}
if header == orig_header {
return Ok(());
}
if input_file.state == InputFileState::External {
info!("Copying external image for re-signing: {name}");
// Need to make a copy so that the user's original file doesn't
// get modified.
let mut temp_file = tempfile::tempfile()
.with_context(|| format!("Failed to create temp file for: {name}"))?;
input_file
.file
.rewind()
.with_context(|| format!("Failed to rewind file: {name}"))?;
stream::copy(&mut input_file.file.clone(), &mut temp_file, cancel_signal)
.with_context(|| format!("Failed to copy file: {name}"))?;
input_file.file = Arc::new(temp_file);
}
info!("Re-signing image: {name}");
let original_image_size = footer
.as_ref()
.map(|f| f.original_image_size)
.unwrap_or_default();
input_file
.file
.rewind()
.with_context(|| format!("Failed to rewind file: {name}"))?;
input_file
.file
.set_len(original_image_size)
.with_context(|| format!("Failed to truncate file: {name}"))?;
if let Some(mut footer) = footer {
avb::write_appended_image(
&mut input_file.file,
&header,
&mut footer,
Some(image_size),
)
} else {
avb::write_root_image(&mut input_file.file, &header, block_size)
}
.with_context(|| format!("Failed to write AVB metadata: {name}"))?;
input_file.state = InputFileState::Modified;
*input_files.lock().unwrap().get_mut(name).unwrap() = input_file;
Ok(())
})
}
/// Load the specified vbmeta image headers. If an image has a vbmeta footer,
/// then an error is returned because the vbmeta patching logic only ever writes
/// root vbmeta images.
@@ -694,7 +823,7 @@ fn update_vbmeta_headers(
//
// The root vbmeta image is always signed because it is possible to
// invoke avbroot is a way that no modifications are made (rootless +
// skipping recovery otacerts.zip patch). We still want the result to be
// skipping both otacerts.zip patches). We still want the result to be
// bootable.
if parent_header != &orig_parent_header || name == "vbmeta" {
parent_header
@@ -706,13 +835,9 @@ fn update_vbmeta_headers(
let mut writer = tempfile::tempfile()
.with_context(|| format!("Failed to create temp file for: {name}"))?;
parent_header
.to_writer(&mut writer)
avb::write_root_image(&mut writer, parent_header, block_size)
.with_context(|| format!("Failed to write vbmeta image: {name}"))?;
padding::write_zeros(&mut writer, block_size)
.with_context(|| format!("Failed to write vbmeta padding: {name}"))?;
let input_file = images.get_mut(name).unwrap();
input_file.file = Arc::new(writer);
input_file.state = InputFileState::Modified;
@@ -830,7 +955,7 @@ pub fn compress_image(
/// Recompute the CoW estimate for an image and update the OTA manifest
/// partition entry appropriately. The input file is not modified.
fn recow_image(
pub fn recow_image(
name: &str,
file: &File,
header: &mut PayloadHeader,
@@ -880,6 +1005,7 @@ fn patch_ota_payload(
payload: &(dyn ReadAt + Sync),
writer: impl Write,
external_images: &HashMap<String, PathBuf>,
re_sign_images: &HashSet<String>,
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
skip_system_ota_cert: bool,
clear_vbmeta_flags: bool,
@@ -920,6 +1046,12 @@ fn patch_ota_payload(
}
}
for name in re_sign_images {
if !all_partitions.contains(name.as_str()) {
bail!("Cannot re-sign non-existent {name} partition");
}
}
let required_images = get_required_images(&header.manifest, required_flags);
let vbmeta_images = required_images
.iter()
@@ -941,6 +1073,7 @@ fn patch_ota_payload(
payload,
&required_images,
external_images,
re_sign_images,
&header,
cancel_signal,
)?;
@@ -965,6 +1098,14 @@ fn patch_ota_payload(
)?)
};
re_sign_unmodified_images(
re_sign_images,
&mut input_files,
key_avb,
header.manifest.block_size().into(),
cancel_signal,
)?;
let mut vbmeta_headers = load_vbmeta_images(&mut input_files, &vbmeta_images)?;
ensure_partitions_protected(&required_images, &vbmeta_headers)?;
@@ -1087,11 +1228,11 @@ fn patch_ota_payload(
.with_context(|| format!("Failed to copy from original payload: {name}"))?;
}
let (_, _, properties, metadata_size) = payload_writer
let (_, header, properties) = payload_writer
.finish()
.context("Failed to finalize payload")?;
Ok((properties, metadata_size))
Ok((properties, header.blob_offset))
}
#[allow(clippy::too_many_arguments)]
@@ -1100,6 +1241,7 @@ fn patch_ota_zip(
zip_reader: &ZipArchive<ReaderAtWrapper<&File>>,
zip_writer: &mut ZipArchiveWriter<impl Write>,
external_images: &HashMap<String, PathBuf>,
re_sign_images: &HashSet<String>,
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
skip_system_ota_cert: bool,
clear_vbmeta_flags: bool,
@@ -1266,6 +1408,7 @@ fn patch_ota_zip(
&payload_reader,
&mut data_writer,
external_images,
re_sign_images,
boot_patchers,
skip_system_ota_cert,
clear_vbmeta_flags,
@@ -1490,6 +1633,8 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
external_images.insert(name.to_owned(), path.to_owned());
}
let re_sign_images = cli.re_sign.iter().cloned().collect::<HashSet<_>>();
let mut boot_patchers = Vec::<Box<dyn BootImagePatch + Sync>>::new();
if let Some(magisk) = &cli.root.magisk {
@@ -1553,6 +1698,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
&zip_reader,
&mut zip_writer,
&external_images,
&re_sign_images,
&boot_patchers,
cli.skip_system_ota_cert,
cli.clear_vbmeta_flags,
@@ -1571,7 +1717,6 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
let mut temp_writer = signing_writer
.finish(&key_ota, &cert_ota, cancel_signal)
.context("Failed to sign output zip")?;
temp_writer.flush().context("Failed to flush output zip")?;
// We do a lot of low-level hackery. Reopen and verify offsets.
info!("Verifying metadata offsets");
@@ -1911,9 +2056,9 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
};
}
let raw_reader = File::open(&cli.input)
let mut reader = File::open(&cli.input)
.map(BufReader::new)
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
let mut reader = BufReader::new(raw_reader);
info!("Verifying whole-file signature");
@@ -2066,10 +2211,10 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
}
}
let boot_images = boot::load_boot_images(&boot_image_names, &Opener(temp_dir.path()))
let (boot_images, layout) = boot::load_boot_images(&boot_image_names, &Opener(temp_dir.path()))
.context("Failed to load all boot images")?;
let targets = OtaCertPatcher::new(ota_cert.clone())
.find_targets(&boot_images, cancel_signal)
.find_targets(layout, &boot_images, cancel_signal)
.context("Failed to find boot image containing otacerts.zip")?;
if targets.is_empty() {
@@ -2109,11 +2254,58 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
Ok(())
}
pub fn list_subcommand(cli: &ListCli) -> Result<()> {
let raw_reader = File::open(&cli.input)
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
let mut buffer = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let zip = ZipArchive::from_read_at(&raw_reader, &mut buffer)
.with_context(|| format!("Failed to read zip: {:?}", cli.input))?;
let mut entries = zip.entries_safe(&mut buffer);
while let Some((cd_entry, _)) =
entries.next_entry().context("Failed to list zip entries")?
{
let path = cd_entry
.file_path_utf8()
.context("Zip contains non-UTF-8 paths")?;
if path != ota::PATH_PAYLOAD {
continue;
}
let entry_reader = zip
.get_entry(cd_entry.wayfinder())
.and_then(|e| zip::verifying_reader(&e, cd_entry.compression_method()))
.with_context(|| format!("Failed to open zip entry: {path}"))?;
let header = PayloadHeader::from_reader(entry_reader)
.context("Failed to load OTA payload header")?;
for partition in header.manifest.partitions {
let name = &partition.partition_name;
util::path_ensure_single(name)?;
// Ensure the output cannot be misparsed since we use newlines as a
// separator.
if name.contains('\n') {
bail!("Partition name contains newline: {name:?}");
}
println!("{name}");
}
return Ok(());
}
bail!("Zip entry not found: {}", ota::PATH_PAYLOAD);
}
pub fn ota_main(cli: &OtaCli, cancel_signal: &AtomicBool) -> Result<()> {
match &cli.command {
OtaCommand::Patch(c) => patch_subcommand(c, cancel_signal),
OtaCommand::Extract(c) => extract_subcommand(c, cancel_signal),
OtaCommand::Verify(c) => verify_subcommand(c, cancel_signal),
OtaCommand::List(c) => list_subcommand(c),
}
}
@@ -2164,7 +2356,7 @@ pub struct PatchCli {
alias = "privkey-avb",
value_name = "FILE",
value_parser,
help_heading = HEADING_KEY
help_heading = HEADING_KEY,
)]
pub key_avb: PathBuf,
@@ -2177,7 +2369,7 @@ pub struct PatchCli {
alias = "privkey-ota",
value_name = "FILE",
value_parser,
help_heading = HEADING_KEY
help_heading = HEADING_KEY,
)]
pub key_ota: PathBuf,
@@ -2192,7 +2384,7 @@ pub struct PatchCli {
value_name = "ENV_VAR",
value_parser,
group = "pass_avb",
help_heading = HEADING_KEY
help_heading = HEADING_KEY,
)]
pub pass_avb_env_var: Option<OsString>,
@@ -2203,7 +2395,7 @@ pub struct PatchCli {
value_name = "FILE",
value_parser,
group = "pass_avb",
help_heading = HEADING_KEY
help_heading = HEADING_KEY,
)]
pub pass_avb_file: Option<PathBuf>,
@@ -2214,7 +2406,7 @@ pub struct PatchCli {
value_name = "ENV_VAR",
value_parser,
group = "pass_ota",
help_heading = HEADING_KEY
help_heading = HEADING_KEY,
)]
pub pass_ota_env_var: Option<OsString>,
@@ -2225,7 +2417,7 @@ pub struct PatchCli {
value_name = "FILE",
value_parser,
group = "pass_ota",
help_heading = HEADING_KEY
help_heading = HEADING_KEY,
)]
pub pass_ota_file: Option<PathBuf>,
@@ -2244,10 +2436,23 @@ pub struct PatchCli {
value_names = ["PARTITION", "FILE"],
value_parser = value_parser!(OsString),
num_args = 2,
help_heading = HEADING_PATH
help_heading = HEADING_PATH,
)]
pub replace: Vec<OsString>,
/// Re-sign unmodified partition image.
///
/// Modified partition images are always re-signed. This option forces the
/// specified image to be re-signed, even if it was unmodified. When used
/// with --replace, a copy of the replacement image will be re-signed, not
/// the original file.
///
/// This option only has an effect if the specified partition image is
/// originally signed. If it is unsigned because the AVB descriptors are
/// embedded in another signed vbmeta image, then this option is a no-op.
#[arg(long, value_names = ["PARTITION"], help_heading = HEADING_PATH)]
pub re_sign: Vec<String>,
#[command(flatten)]
pub root: RootGroup,
@@ -2256,7 +2461,7 @@ pub struct PatchCli {
long,
value_name = "PARTITION",
conflicts_with_all = ["prepatched", "rootless"],
help_heading = HEADING_MAGISK
help_heading = HEADING_MAGISK,
)]
pub magisk_preinit_device: Option<String>,
@@ -2265,7 +2470,7 @@ pub struct PatchCli {
long,
value_name = "NUMBER",
conflicts_with_all = ["prepatched", "rootless"],
help_heading = HEADING_MAGISK
help_heading = HEADING_MAGISK,
)]
pub magisk_random_seed: Option<u64>,
@@ -2273,7 +2478,7 @@ pub struct PatchCli {
#[arg(
long,
conflicts_with_all = ["prepatched", "rootless"],
help_heading = HEADING_MAGISK
help_heading = HEADING_MAGISK,
)]
pub ignore_magisk_warnings: bool,
@@ -2282,7 +2487,7 @@ pub struct PatchCli {
long,
action = ArgAction::Count,
conflicts_with_all = ["magisk", "rootless"],
help_heading = HEADING_PREPATCHED
help_heading = HEADING_PREPATCHED,
)]
pub ignore_prepatched_compat: u8,
@@ -2339,7 +2544,7 @@ pub struct PatchCli {
long,
value_name = "MODE",
default_value_t = ZipMode::Streaming,
help_heading = HEADING_OTHER
help_heading = HEADING_OTHER,
)]
pub zip_mode: ZipMode,
@@ -2348,7 +2553,7 @@ pub struct PatchCli {
long,
value_name = "PARTITION",
hide = true,
help_heading = HEADING_OTHER
help_heading = HEADING_OTHER,
)]
pub boot_partition: Option<String>,
}
@@ -2454,12 +2659,22 @@ pub struct VerifyCli {
fail_if_missing: bool,
}
/// List partitions contained in an OTA.
#[derive(Debug, Parser)]
pub struct ListCli {
/// Path to OTA zip.
#[arg(short, long, value_name = "FILE", value_parser)]
pub input: PathBuf,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
enum OtaCommand {
Patch(PatchCli),
Extract(ExtractCli),
Verify(VerifyCli),
List(ListCli),
}
/// Patch or extract OTA images.
+171 -60
View File
@@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: 2024-2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2024-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
collections::HashMap,
ffi::OsString,
fs::{self, File},
io::{BufReader, BufWriter, Seek, SeekFrom},
io::{BufReader, BufWriter, Seek, SeekFrom, Write},
path::{Path, PathBuf},
sync::{Arc, atomic::AtomicBool},
};
@@ -18,31 +18,34 @@ use crate::{
cli::ota,
crypto::{self, PassphraseSource, RsaSigningKey},
format::payload::{PayloadHeader, PayloadWriter},
stream::{self, FromReader},
stream::{self, FromReader, SectionReader},
util,
};
fn open_reader(path: &Path, allow_delta: bool) -> Result<(BufReader<File>, PayloadHeader)> {
let mut reader = File::open(path)
.map(BufReader::new)
.with_context(|| format!("Failed to open payload for reading: {path:?}"))?;
fn open_raw_reader(path: &Path) -> Result<File> {
File::open(path).with_context(|| format!("Failed to open payload for reading: {path:?}"))
}
fn open_reader(path: &Path) -> Result<(BufReader<File>, PayloadHeader)> {
let mut reader = open_raw_reader(path).map(BufReader::new)?;
let header = PayloadHeader::from_reader(&mut reader)
.with_context(|| format!("Failed to read payload header: {path:?}"))?;
if !allow_delta && !header.is_full_ota() {
bail!("Payload is a delta OTA, not a full OTA");
}
Ok((reader, header))
}
fn open_raw_writer(path: &Path) -> Result<BufWriter<File>> {
File::create(path)
.map(BufWriter::new)
.with_context(|| format!("Failed to open payload for writing: {path:?}"))
}
fn open_writer(
path: &Path,
header: PayloadHeader,
key: RsaSigningKey,
) -> Result<PayloadWriter<BufWriter<File>>> {
let writer = File::create(path)
.map(BufWriter::new)
.with_context(|| format!("Failed to open payload for writing: {path:?}"))?;
let writer = open_raw_writer(path)?;
let payload_writer = PayloadWriter::new(writer, header, key)
.with_context(|| format!("Failed to write payload header: {path:?}"))?;
@@ -52,14 +55,14 @@ fn open_writer(
fn read_info(path: &Path) -> Result<PayloadHeader> {
let data = fs::read_to_string(path)
.with_context(|| format!("Failed to read payload info TOML: {path:?}"))?;
let info = toml_edit::de::from_str(&data)
let info = toml::de::from_str(&data)
.with_context(|| format!("Failed to parse payload info TOML: {path:?}"))?;
Ok(info)
}
fn write_info(path: &Path, manifest: &PayloadHeader) -> Result<()> {
let data = toml_edit::ser::to_string_pretty(manifest)
let data = toml::ser::to_string_pretty(manifest)
.with_context(|| format!("Failed to serialize payload info TOML: {path:?}"))?;
fs::write(path, data)
.with_context(|| format!("Failed to write payload info TOML: {path:?}"))?;
@@ -67,8 +70,8 @@ fn write_info(path: &Path, manifest: &PayloadHeader) -> Result<()> {
Ok(())
}
fn display_header(cli: &PayloadCli, header: &PayloadHeader) {
if !cli.quiet {
fn display_header(quiet: bool, header: &PayloadHeader) {
if !quiet {
println!("{header:#?}");
}
}
@@ -99,50 +102,105 @@ fn load_key(group: &KeyGroup) -> Result<RsaSigningKey> {
Ok(signing_key)
}
#[allow(clippy::too_many_arguments)]
pub fn unpack_payload(
quiet: bool,
output_info: &Path,
output_images: &Path,
no_output_images: bool,
skeleton: bool,
reader: &File,
payload_offset: u64,
payload_size: u64,
cancel_signal: &AtomicBool,
) -> Result<()> {
let section_reader = SectionReader::new(reader, payload_offset, payload_size)
.context("Failed to directly open payload section")?;
let header =
PayloadHeader::from_reader(section_reader).context("Failed to read payload header")?;
display_header(quiet, &header);
write_info(output_info, &header)?;
if !no_output_images {
fs::create_dir_all(output_images)
.with_context(|| format!("Failed to create directory: {output_images:?}"))?;
if skeleton {
for partition in &header.manifest.partitions {
let name = &partition.partition_name;
let path = util::path_join_single(output_images, format!("{name}.img"))?;
let size = partition
.new_partition_info
.as_ref()
.and_then(|info| info.size)
.ok_or_else(|| anyhow!("Size not found for partition: {name}"))?;
let file = File::create(&path)
.with_context(|| format!("Failed to create file: {path:?}"))?;
file.set_len(size)
.with_context(|| format!("Failed to truncate file: {path:?}"))?;
}
} else {
if !header.is_full_ota() {
bail!("Cannot extract images from a delta payload");
}
ota::extract_payload(
reader,
output_images,
payload_offset,
payload_size,
&header,
&header
.manifest
.partitions
.iter()
.map(|p| &p.partition_name)
.cloned()
.collect(),
cancel_signal,
)?;
}
}
Ok(())
}
fn unpack_subcommand(
payload_cli: &PayloadCli,
cli: &UnpackCli,
cancel_signal: &AtomicBool,
) -> Result<()> {
let (mut reader, header) = open_reader(&cli.input, false)?;
let mut reader = open_raw_reader(&cli.input)?;
let payload_size = reader
.seek(SeekFrom::End(0))
.with_context(|| format!("Failed to get file size: {:?}", cli.input))?;
display_header(payload_cli, &header);
write_info(&cli.output_info, &header)?;
fs::create_dir_all(&cli.output_images)
.with_context(|| format!("Failed to create directory: {:?}", cli.output_images))?;
ota::extract_payload(
&reader.into_inner(),
unpack_payload(
payload_cli.quiet,
&cli.output_info,
&cli.output_images,
cli.no_output_images,
cli.skeleton,
&reader,
0,
payload_size,
&header,
&header
.manifest
.partitions
.iter()
.map(|p| &p.partition_name)
.cloned()
.collect(),
cancel_signal,
)?;
Ok(())
)
}
fn pack_subcommand(
payload_cli: &PayloadCli,
cli: &PackCli,
pub fn pack_payload(
quiet: bool,
input_info: &Path,
input_images: &Path,
writer: &mut dyn Write,
signing_key: &RsaSigningKey,
cancel_signal: &AtomicBool,
) -> Result<()> {
let signing_key = load_key(&cli.key)?;
let mut header = read_info(&cli.input_info)?;
) -> Result<(String, u64)> {
let mut header = read_info(input_info)?;
// Pre-open all of the image files.
let input_files = header
@@ -150,8 +208,7 @@ fn pack_subcommand(
.partitions
.iter()
.map(|p| {
let path =
util::path_join_single(&cli.input_images, format!("{}.img", p.partition_name))?;
let path = util::path_join_single(input_images, format!("{}.img", p.partition_name))?;
let file = File::open(&path)
.map(Arc::new)
.with_context(|| format!("Failed to open file: {path:?}"))?;
@@ -160,6 +217,29 @@ fn pack_subcommand(
})
.collect::<Result<HashMap<_, _>>>()?;
for (name, input_file) in &input_files {
let Some(dpm) = &header.manifest.dynamic_partition_metadata else {
break;
};
if !dpm.vabc_enabled() {
break;
}
if !dpm.groups.iter().any(|g| g.partition_names.contains(name)) {
continue;
}
header
.manifest
.partitions
.iter_mut()
.find(|p| p.partition_name == *name)
.unwrap()
.estimate_cow_size = Some(0);
ota::recow_image(name, input_file, &mut header, cancel_signal)?;
}
// Compress the images and compute the list of install operations for
// insertion into the payload header. The compressed data is stored in new
// temp files and the original input files are dropped.
@@ -177,7 +257,8 @@ fn pack_subcommand(
// Now we can write the actual payload. With everything precomputed, this is
// mostly just a simple copy.
let mut payload_writer = open_writer(&cli.output, header.clone(), signing_key)?;
let mut payload_writer = PayloadWriter::new(writer, header.clone(), signing_key.clone())
.context("Failed to write payload header")?;
while payload_writer
.begin_next_operation()
@@ -213,12 +294,35 @@ fn pack_subcommand(
.with_context(|| format!("Failed to copy from replacement image: {name}"))?;
}
let (_, header, properties, _) = payload_writer
let (_, header, properties) = payload_writer
.finish()
.context("Failed to finalize payload")?;
// Display the header information now that it has been finalized.
display_header(payload_cli, &header);
display_header(quiet, &header);
Ok((properties, header.blob_offset))
}
fn pack_subcommand(
payload_cli: &PayloadCli,
cli: &PackCli,
cancel_signal: &AtomicBool,
) -> Result<()> {
let signing_key = load_key(&cli.key)?;
let mut writer = open_raw_writer(&cli.output)?;
let (properties, _) = pack_payload(
payload_cli.quiet,
&cli.input_info,
&cli.input_images,
&mut writer,
&signing_key,
cancel_signal,
)?;
writer.into_inner().context("Failed to flush payload")?;
// Optionally, write payload_properties.txt.
if let Some(path) = &cli.output_properties {
@@ -236,7 +340,7 @@ fn repack_subcommand(
) -> Result<()> {
let signing_key = load_key(&cli.key)?;
let (mut reader, header) = open_reader(&cli.input, true)?;
let (mut reader, header) = open_reader(&cli.input)?;
info!("Generating new OTA payload");
@@ -275,12 +379,12 @@ fn repack_subcommand(
.with_context(|| format!("Failed to copy from original payload: {name}"))?;
}
let (_, header, properties, _) = payload_writer
let (_, header, properties) = payload_writer
.finish()
.context("Failed to finalize payload")?;
// Display the header information now that it has been finalized.
display_header(payload_cli, &header);
display_header(payload_cli.quiet, &header);
// Optionally, write payload_properties.txt.
if let Some(path) = &cli.output_properties {
@@ -292,9 +396,9 @@ fn repack_subcommand(
}
fn info_subcommand(payload_cli: &PayloadCli, cli: &InfoCli) -> Result<()> {
let (_, header) = open_reader(&cli.input, true)?;
let (_, header) = open_reader(&cli.input)?;
display_header(payload_cli, &header);
display_header(payload_cli.quiet, &header);
Ok(())
}
@@ -341,8 +445,7 @@ struct KeyGroup {
/// directory. The payload header metadata is written to the info TOML file.
///
/// If any partition names are unsafe to use in a path, the extraction process
/// will fail and exit. Extracted files are never written outside of the tree
/// directory, even if an external process tries to interfere.
/// will fail and exit.
#[derive(Debug, Parser)]
struct UnpackCli {
/// Path to input payload binary.
@@ -366,6 +469,14 @@ struct UnpackCli {
default_value = "payload_images"
)]
output_images: PathBuf,
/// Do not output images.
#[arg(long, conflicts_with_all = ["output_images", "skeleton"])]
no_output_images: bool,
/// Only create empty sparse files for output images.
#[arg(long)]
skeleton: bool,
}
/// Pack a payload binary.
@@ -381,7 +492,7 @@ struct PackCli {
output: PathBuf,
/// Path to output payload properties file.
#[arg(short, long, value_name = "FILE", value_parser)]
#[arg(short = 'O', long, value_name = "FILE", value_parser)]
output_properties: Option<PathBuf>,
/// Path to input info TOML.
@@ -421,7 +532,7 @@ struct RepackCli {
output: PathBuf,
/// Path to output payload properties file.
#[arg(short, long, value_name = "FILE", value_parser)]
#[arg(short = 'O', long, value_name = "FILE", value_parser)]
output_properties: Option<PathBuf>,
#[command(flatten)]
@@ -431,7 +542,7 @@ struct RepackCli {
/// Display payload information.
#[derive(Debug, Parser)]
struct InfoCli {
/// Path to input payload file.
/// Path to input payload binary.
#[arg(short, long, value_name = "FILE", value_parser)]
input: PathBuf,
}
+751
View File
@@ -0,0 +1,751 @@
// SPDX-FileCopyrightText: 2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
collections::BTreeMap,
ffi::OsString,
fs::{self, File, OpenOptions},
io::{BufReader, Seek, Write},
path::{Path, PathBuf},
sync::atomic::AtomicBool,
};
use anyhow::{Context, Result, anyhow};
use clap::{Args, Parser, Subcommand};
use rawzip::{
CompressionMethod, RECOMMENDED_BUFFER_SIZE, ZipArchive, ZipArchiveEntryWayfinder,
ZipArchiveWriter, ZipDataWriter, ZipEntryWriter, extra_fields::ExtraFieldId,
};
use serde::{Deserialize, Serialize};
use tracing::warn;
use x509_cert::Certificate;
use crate::{
cli::payload,
crypto::{self, PassphraseSource, RsaSigningKey},
format::{
ota::{self, SigningWriter, ZipEntry, ZipMode},
payload::PayloadHeader,
zip::{
self, ReaderAtWrapper, ZipArchiveReadAtExt, ZipEntriesSafeExt, ZipFileHeaderRecordExt,
},
},
protobuf::build::tools::releasetools::OtaMetadata,
stream::{self, FromReader},
util,
};
#[derive(Clone, Debug, Deserialize, Serialize)]
struct OtaInfo {
metadata: OtaMetadata,
files: Vec<String>,
}
struct InputEntry {
compression_method: CompressionMethod,
is_zip64: bool,
wayfinder: ZipArchiveEntryWayfinder,
}
fn is_excluded_path(path: &str) -> bool {
path == ota::PATH_METADATA || path == ota::PATH_METADATA_PB || path == ota::PATH_OTACERT
}
#[allow(clippy::type_complexity)]
fn open_reader(
path: &Path,
) -> Result<(
ZipArchive<ReaderAtWrapper<File>>,
OtaMetadata,
BTreeMap<String, InputEntry>,
)> {
let mut reader =
File::open(path).with_context(|| format!("Failed to open OTA for reading: {path:?}"))?;
let (metadata, _, _, _) = ota::parse_zip_ota_info(&mut reader)
.with_context(|| format!("Failed to parse OTA metadata: {path:?}"))?;
let mut buffer = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let zip_reader =
ZipArchive::from_read_at(reader, &mut buffer).context("Failed to read OTA zip")?;
let mut entries = zip_reader.entries_safe(&mut buffer);
let mut input_entries = BTreeMap::new();
while let Some((cd_entry, _)) = entries.next_entry().context("Failed to list zip entries")? {
if cd_entry.is_dir() {
continue;
}
let path = cd_entry
.file_path_utf8()
.context("Zip contains non-UTF-8 paths")?;
if is_excluded_path(path) {
// OTA metadata is never copied from the input file.
continue;
}
input_entries.insert(
path.to_owned(),
InputEntry {
compression_method: cd_entry.compression_method(),
// We only check for the sizes here instead of the presence of
// the ZIP64 extra field. The central header's extra fields may
// have ZIP64 only for the local header offset.
is_zip64: cd_entry.compressed_size_hint() >= 0xffffffff
|| cd_entry.uncompressed_size_hint() >= 0xffffffff,
wayfinder: cd_entry.wayfinder(),
},
);
}
Ok((zip_reader, metadata, input_entries))
}
fn open_writer(path: &Path, zip_mode: ZipMode) -> Result<ZipArchiveWriter<SigningWriter<File>>> {
let writer = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.with_context(|| format!("Failed to open OTA for writing: {path:?}"))?;
let signing_writer = match zip_mode {
ZipMode::Streaming => SigningWriter::new_streaming(writer),
ZipMode::Seekable => SigningWriter::new_seekable(writer),
};
let zip_writer = ZipArchiveWriter::new(signing_writer);
Ok(zip_writer)
}
fn read_info(path: &Path) -> Result<OtaInfo> {
let data = fs::read_to_string(path)
.with_context(|| format!("Failed to read OTA info TOML: {path:?}"))?;
let info = toml::de::from_str(&data)
.with_context(|| format!("Failed to parse OTA info TOML: {path:?}"))?;
Ok(info)
}
fn write_info(path: &Path, info: &OtaInfo) -> Result<()> {
let data = toml::ser::to_string_pretty(info)
.with_context(|| format!("Failed to serialize OTA info TOML: {path:?}"))?;
fs::write(path, data).with_context(|| format!("Failed to write OTA info TOML: {path:?}"))?;
Ok(())
}
fn display_info(quiet: bool, info: &OtaInfo) {
if !quiet {
println!("{info:#?}");
}
}
fn load_key(group: &KeyGroup) -> Result<(RsaSigningKey, Certificate)> {
let source = PassphraseSource::new(
&group.key,
group.pass_file.as_deref(),
group.pass_env_var.as_deref(),
);
let signing_key = if let Some(helper) = &group.signing_helper {
let public_key = crypto::read_pem_public_key_file(&group.key)
.with_context(|| format!("Failed to load key: {:?}", group.key))?;
RsaSigningKey::External {
program: helper.clone(),
public_key_file: group.key.clone(),
public_key,
passphrase_source: source,
}
} else {
let private_key = crypto::read_pem_key_file(&group.key, &source)
.with_context(|| format!("Failed to load key: {:?}", group.key))?;
RsaSigningKey::Internal(private_key)
};
let cert = crypto::read_pem_cert_file(&group.cert)
.with_context(|| format!("Failed to load certificate: {:?}", group.cert))?;
Ok((signing_key, cert))
}
fn start_entry<'a>(
zip_writer: &'a mut ZipArchiveWriter<SigningWriter<File>>,
path: &str,
zip_mode: ZipMode,
is_zip64: bool,
) -> Result<(u64, ZipDataWriter<ZipEntryWriter<'a, SigningWriter<File>>>)> {
let mut builder = zip_writer
.new_file(path)
.compression_method(CompressionMethod::Store);
if zip_mode == ZipMode::Seekable && is_zip64 {
// We need to reserve space for the ZIP64 extra field when doing the
// post-processing to convert a streaming zip to a seekable one.
builder = builder.extra_field(
ExtraFieldId::ANDROID_ZIP_ALIGNMENT,
&[0u8; 16],
rawzip::Header::LOCAL,
)?;
}
let (entry_writer, data_config) = builder
.start()
.with_context(|| format!("Failed to begin new zip entry: {path}"))?;
let offset = entry_writer.stream_offset();
let data_writer = data_config.wrap(entry_writer);
Ok((offset, data_writer))
}
fn finalize_entry(
path: &str,
offset: u64,
data_writer: ZipDataWriter<ZipEntryWriter<SigningWriter<File>>>,
metadata_entries: &mut Vec<ZipEntry>,
) -> Result<()> {
let size = data_writer
.finish()
.and_then(|(w, d)| w.finish(d))
.with_context(|| format!("Failed to finalize zip entry: {path}"))?;
metadata_entries.push(ZipEntry {
path: path.to_owned(),
offset,
size,
});
Ok(())
}
fn add_otacert_entry(
zip_writer: &mut ZipArchiveWriter<SigningWriter<File>>,
zip_mode: ZipMode,
cert: &Certificate,
metadata_entries: &mut Vec<ZipEntry>,
) -> Result<()> {
let (offset, mut data_writer) = start_entry(zip_writer, ota::PATH_OTACERT, zip_mode, false)?;
crypto::write_pem_cert(Path::new(ota::PATH_OTACERT), &mut data_writer, cert)
.with_context(|| format!("Failed to write entry: {}", ota::PATH_OTACERT))?;
finalize_entry(ota::PATH_OTACERT, offset, data_writer, metadata_entries)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn finalize_ota(
key: &RsaSigningKey,
cert: &Certificate,
mut zip_writer: ZipArchiveWriter<SigningWriter<File>>,
zip_mode: ZipMode,
metadata_entries: &mut Vec<ZipEntry>,
payload_metadata_size: Option<u64>,
metadata: &mut OtaMetadata,
cancel_signal: &AtomicBool,
) -> Result<File> {
add_otacert_entry(&mut zip_writer, zip_mode, cert, metadata_entries)?;
let payload_metadata_size =
payload_metadata_size.ok_or_else(|| anyhow!("Missing payload metadata size"))?;
let metadata_offset = zip_writer.stream_offset();
*metadata = ota::add_metadata(
metadata_entries,
&mut zip_writer,
// Offset where next entry would begin.
metadata_offset,
metadata,
payload_metadata_size,
)
.context("Failed to write new OTA metadata")?;
let signing_writer = zip_writer
.finish()
.context("Failed to finalize output zip")?;
let mut raw_writer = signing_writer
.finish(key, cert, cancel_signal)
.context("Failed to sign output zip")?;
raw_writer.rewind().context("Failed to seek output zip")?;
ota::verify_metadata(
BufReader::new(&mut raw_writer),
metadata,
payload_metadata_size,
)
.context("Failed to verify OTA metadata offsets")?;
Ok(raw_writer)
}
fn unpack_subcommand(zip_cli: &ZipCli, cli: &UnpackCli, cancel_signal: &AtomicBool) -> Result<()> {
let (zip_reader, metadata, input_entries) = open_reader(&cli.input)?;
for (path, input_entry) in &input_entries {
let entry = zip_reader
.get_entry(input_entry.wayfinder)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
if cli.payload && path == ota::PATH_PAYLOAD {
let (payload_offset, payload_size) = {
let range = entry.compressed_data_range();
(range.0, range.1 - range.0)
};
payload::unpack_payload(
zip_cli.quiet,
&cli.output_payload_info,
&cli.output_payload_images,
cli.no_output_payload_images,
cli.payload_skeleton,
&zip_reader.get_ref().0,
payload_offset,
payload_size,
cancel_signal,
)?;
} else if cli.payload && path == ota::PATH_PROPERTIES {
// We intentionally ignore this since it's recomputed when
// packing with --payload.
} else if !cli.no_output_files {
let mut entry_reader = zip::verifying_reader(&entry, input_entry.compression_method)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
let output_path = util::path_join(&cli.output_files, path)?;
let output_parent = output_path.parent().expect("No parent path");
fs::create_dir_all(output_parent)
.with_context(|| format!("Failed to create directory: {output_parent:?}"))?;
let mut output_file = File::create(&output_path)
.with_context(|| format!("Failed to open for writing: {output_path:?}"))?;
stream::copy(&mut entry_reader, &mut output_file, cancel_signal)
.with_context(|| format!("Failed to extract zip entry: {path}"))?;
}
}
let info = OtaInfo {
metadata,
files: input_entries.into_keys().collect(),
};
display_info(zip_cli.quiet, &info);
write_info(&cli.output_info, &info)?;
Ok(())
}
fn pack_subcommand(zip_cli: &ZipCli, cli: &PackCli, cancel_signal: &AtomicBool) -> Result<()> {
let (signing_key, cert) = load_key(&cli.key)?;
let mut info = read_info(&cli.input_info)?;
let mut zip_writer = open_writer(&cli.output, cli.zip_mode.zip_mode)?;
let mut input_entries = BTreeMap::new();
for path in &info.files {
if is_excluded_path(path) {
warn!("Ignoring input file: {path}");
continue;
}
let input_size =
if cli.payload && (path == ota::PATH_PAYLOAD || path == ota::PATH_PROPERTIES) {
0
} else {
let input_path = util::path_join(&cli.input_files, path)?;
fs::metadata(&input_path)
.map(|m| m.len())
.with_context(|| format!("Failed to get file size: {input_path:?}"))?
};
input_entries.insert(path, input_size >= 0xffffffff);
}
let mut payload_metadata_size = None;
let mut metadata_entries = vec![];
// Only used when packing the zip and payload at the same time.
let mut payload_properties = None;
for (&path, &is_zip64) in &input_entries {
let (offset, mut data_writer) =
start_entry(&mut zip_writer, path, cli.zip_mode.zip_mode, is_zip64)?;
if path == ota::PATH_PAYLOAD && cli.payload {
// Pack the payload directly into the zip.
let (properties, metadata_size) = payload::pack_payload(
zip_cli.quiet,
&cli.input_payload_info,
&cli.input_payload_images,
&mut data_writer,
&signing_key,
cancel_signal,
)?;
payload_metadata_size = Some(metadata_size);
payload_properties = Some(properties);
} else if path == ota::PATH_PROPERTIES && cli.payload {
// We process files in order, so this would only happen if the file
// list did not contain the payload.
let properties = payload_properties
.as_ref()
.ok_or_else(|| anyhow!("Payload has not been processed yet"))?;
data_writer
.write_all(properties.as_bytes())
.with_context(|| format!("Failed to write zip entry: {path}"))?;
} else {
let input_path = util::path_join(&cli.input_files, path)?;
let mut input_file = File::open(&input_path)
.with_context(|| format!("Failed to open for reading: {input_path:?}"))?;
if path == ota::PATH_PAYLOAD {
let header = PayloadHeader::from_reader(&mut input_file)
.with_context(|| format!("Failed to read payload header: {input_path:?}"))?;
payload_metadata_size = Some(header.blob_offset);
input_file
.rewind()
.with_context(|| format!("Failed to seek file: {input_path:?}"))?;
}
stream::copy(&mut input_file, &mut data_writer, cancel_signal)
.with_context(|| format!("Failed to copy zip entry: {path}"))?;
}
finalize_entry(path, offset, data_writer, &mut metadata_entries)?;
}
finalize_ota(
&signing_key,
&cert,
zip_writer,
cli.zip_mode.zip_mode,
&mut metadata_entries,
payload_metadata_size,
&mut info.metadata,
cancel_signal,
)?;
drop(input_entries);
info.files.retain(|p| !is_excluded_path(p));
info.files.sort();
display_info(zip_cli.quiet, &info);
if let Some(path) = &cli.output_info {
write_info(path, &info)?;
}
Ok(())
}
fn repack_subcommand(zip_cli: &ZipCli, cli: &RepackCli, cancel_signal: &AtomicBool) -> Result<()> {
let (signing_key, cert) = load_key(&cli.key)?;
let (zip_reader, mut metadata, input_entries) = open_reader(&cli.input)?;
let mut zip_writer = open_writer(&cli.output, cli.zip_mode.zip_mode)?;
let mut payload_metadata_size = None;
let mut metadata_entries = vec![];
for (path, input_entry) in &input_entries {
let (offset, mut data_writer) = start_entry(
&mut zip_writer,
path,
cli.zip_mode.zip_mode,
input_entry.is_zip64,
)?;
let entry = zip_reader
.get_entry(input_entry.wayfinder)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
if path == ota::PATH_PAYLOAD {
let entry_reader = zip::verifying_reader(&entry, input_entry.compression_method)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
let header = PayloadHeader::from_reader(entry_reader)
.with_context(|| format!("Failed to read payload header: {path:?}"))?;
payload_metadata_size = Some(header.blob_offset);
}
let mut entry_reader = zip::verifying_reader(&entry, input_entry.compression_method)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
stream::copy(&mut entry_reader, &mut data_writer, cancel_signal)
.with_context(|| format!("Failed to copy zip entry: {path}"))?;
finalize_entry(path, offset, data_writer, &mut metadata_entries)?;
}
finalize_ota(
&signing_key,
&cert,
zip_writer,
cli.zip_mode.zip_mode,
&mut metadata_entries,
payload_metadata_size,
&mut metadata,
cancel_signal,
)?;
let info = OtaInfo {
metadata,
files: input_entries.into_keys().collect(),
};
display_info(zip_cli.quiet, &info);
Ok(())
}
fn info_subcommand(zip_cli: &ZipCli, cli: &InfoCli) -> Result<()> {
let (_, metadata, input_entries) = open_reader(&cli.input)?;
let info = OtaInfo {
metadata,
files: input_entries.into_keys().collect(),
};
display_info(zip_cli.quiet, &info);
Ok(())
}
pub fn zip_main(cli: &ZipCli, cancel_signal: &AtomicBool) -> Result<()> {
match &cli.command {
ZipCommand::Unpack(c) => unpack_subcommand(cli, c, cancel_signal),
ZipCommand::Pack(c) => pack_subcommand(cli, c, cancel_signal),
ZipCommand::Repack(c) => repack_subcommand(cli, c, cancel_signal),
ZipCommand::Info(c) => info_subcommand(cli, c),
}
}
#[derive(Debug, Args)]
struct KeyGroup {
/// Path to signing key.
///
/// This should normally be a private key. However, if --signing-helper is
/// used, then it should be a public key instead.
#[arg(short, long, value_name = "FILE", value_parser)]
key: PathBuf,
/// Certificate for signing key.
#[arg(short, long, value_name = "FILE", value_parser)]
cert: PathBuf,
/// Environment variable containing private key passphrase.
#[arg(long, value_name = "ENV_VAR", value_parser, group = "pass")]
pass_env_var: Option<OsString>,
/// File containing private key passphrase.
#[arg(long, value_name = "FILE", value_parser, group = "pass")]
pass_file: Option<PathBuf>,
/// External program for signing.
///
/// If this option is specified, then --key must refer to a public key. The
/// program will be invoked as:
///
/// <program> <algo> <public key> [file <pass file>|env <pass env>]
#[arg(long, value_name = "PROGRAM", value_parser)]
signing_helper: Option<PathBuf>,
}
#[derive(Debug, Args)]
struct ZipModeGroup {
/// Zip creation mode for the output OTA zip.
///
/// The streaming mode produces zip files that contain data descriptors.
/// The zip file is hashed as it is being written. This mode is the default
/// and works with the vast majority of devices.
///
/// The seekable mode produces zip files that do not use data descriptors.
/// The zip file is reread and hashed after it has been fully written. The
/// output file is more likely to be compatible with devices that have
/// broken zip file parsers.
#[arg(
long,
value_name = "MODE",
default_value_t = ZipMode::Streaming,
)]
zip_mode: ZipMode,
}
/// Unpack an OTA zip.
#[derive(Debug, Parser)]
struct UnpackCli {
/// Path to input OTA zip.
#[arg(short, long, value_name = "FILE", value_parser)]
input: PathBuf,
/// Path to output info TOML.
#[arg(long, value_name = "FILE", value_parser, default_value = "ota.toml")]
output_info: PathBuf,
/// Path to output files directory.
#[arg(long, value_name = "DIR", value_parser, default_value = "ota_files")]
output_files: PathBuf,
/// Do not output files.
#[arg(long, conflicts_with = "output_files")]
no_output_files: bool,
/// Also unpack the payload in the OTA.
///
/// Unpacking the zip and payload at the same time is more efficient than
/// unpacking them separately.
#[arg(long)]
payload: bool,
/// Path to output payload info TOML.
#[arg(
long,
value_name = "FILE",
value_parser,
default_value = "payload.toml",
requires = "payload"
)]
output_payload_info: PathBuf,
/// Path to output payload images directory.
#[arg(
long,
value_name = "DIR",
value_parser,
default_value = "payload_images",
requires = "payload"
)]
output_payload_images: PathBuf,
/// Do not output payload images.
#[arg(
long,
conflicts_with_all = ["output_payload_images", "payload_skeleton"],
requires = "payload"
)]
no_output_payload_images: bool,
/// Only create empty sparse files for output payload images.
#[arg(long, requires = "payload")]
payload_skeleton: bool,
}
/// Pack an OTA zip.
///
/// The new OTA zip will *only* contain files listed in the info TOML file.
/// Extra files in the input files directory that aren't listed will be silently
/// ignored.
///
/// WARNING: This subcommand is for raw file manipulation and does not perform
/// any validation of the contents of the payload binary. For normal usage, use
/// `avbroot ota patch` instead.
#[derive(Debug, Parser)]
struct PackCli {
/// Path to output OTA zip.
#[arg(short, long, value_name = "FILE", value_parser)]
output: PathBuf,
/// Path to input info TOML.
#[arg(long, value_name = "FILE", value_parser, default_value = "ota.toml")]
input_info: PathBuf,
/// Path to output info TOML.
///
/// If specified, the OTA info containing all recomputed fields will be
/// written to this file. This can point to the same file as --input-info.
#[arg(long, value_name = "FILE", value_parser)]
output_info: Option<PathBuf>,
/// Path to input files directory.
#[arg(long, value_name = "DIR", value_parser, default_value = "ota_files")]
input_files: PathBuf,
/// Also pack the payload in the OTA.
///
/// Packing the zip and payload at the same time is more efficient than
/// packing them separately.
#[arg(long)]
payload: bool,
/// Path to input payload info TOML.
#[arg(
long,
value_name = "FILE",
value_parser,
default_value = "payload.toml",
requires = "payload"
)]
input_payload_info: PathBuf,
/// Path to input payload images directory.
#[arg(
long,
value_name = "DIR",
value_parser,
default_value = "payload_images",
requires = "payload"
)]
input_payload_images: PathBuf,
#[command(flatten)]
key: KeyGroup,
#[command(flatten)]
zip_mode: ZipModeGroup,
}
/// Repack an OTA zip.
///
/// This command is equivalent to running `unpack` and `pack`, except without
/// storing the unpacked data to disk first.
#[derive(Debug, Parser)]
struct RepackCli {
/// Path to input OTA zip.
#[arg(short, long, value_name = "FILE", value_parser)]
input: PathBuf,
/// Path to output OTA zip.
#[arg(short, long, value_name = "FILE", value_parser)]
output: PathBuf,
#[command(flatten)]
key: KeyGroup,
#[command(flatten)]
zip_mode: ZipModeGroup,
}
/// Display OTA zip information.
#[derive(Debug, Parser)]
struct InfoCli {
/// Path to input OTA zip.
#[arg(short, long, value_name = "FILE", value_parser)]
input: PathBuf,
}
#[derive(Debug, Subcommand)]
enum ZipCommand {
Unpack(UnpackCli),
Pack(PackCli),
Repack(RepackCli),
Info(InfoCli),
}
/// Pack, unpack, and inspect OTA zips.
#[derive(Debug, Parser)]
pub struct ZipCli {
#[command(subcommand)]
command: ZipCommand,
/// Don't print OTA metadata information.
#[arg(short, long, global = true)]
quiet: bool,
}
+2 -2
View File
@@ -151,11 +151,11 @@ mod tests {
contents.extend("💩".as_bytes());
let data = TestData { contents };
let serialized = toml_edit::ser::to_string(&data).unwrap();
let serialized = toml::ser::to_string(&data).unwrap();
assert_eq!(serialized, "contents = 'foo\\xFFbar💩'\n");
let new_data: TestData = toml_edit::de::from_str(&serialized).unwrap();
let new_data: TestData = toml::de::from_str(&serialized).unwrap();
assert_eq!(data.contents, new_data.contents);
}
}
+8 -6
View File
@@ -51,29 +51,31 @@ pub const FOOTER_MAGIC: [u8; 4] = *b"AVBf";
pub const HEADER_MAX_SIZE: u64 = 64 * 1024;
/// Maximum hash tree size. The current limit equals the hash tree size for an
/// 8GiB image using SHA256 digests and a block size of 4096. This is equal to:
/// 16GiB image using SHA256 digests and a block size of 4096. This is equal to:
///
/// ```rust
/// use avbroot::format::hashtree::HashTree;
/// let size = HashTree::new(4096, &ring::digest::SHA256, b"")
/// .compute_level_offsets(8 * 1024 * 1024 * 1024)
/// .compute_level_offsets(16 * 1024 * 1024 * 1024)
/// .unwrap()
/// .first()
/// .map(|r| r.end)
/// .unwrap_or(0);
/// assert_eq!(size, 135_278_592);
/// ```
pub const HASH_TREE_MAX_SIZE: u64 = 67_637_248;
pub const HASH_TREE_MAX_SIZE: u64 = 135_278_592;
/// Maximum FEC data size. The current limit equals the FEC data size for an
/// 8GiB image using 2 parity bytes per codeword. This is equal to:
/// 16GiB image using 2 parity bytes per codeword. This is equal to:
///
/// ```rust
/// use avbroot::format::fec::Fec;
/// let size = Fec::new(8 * 1024 * 1024 * 1024, 4096, 2)
/// let size = Fec::new(16 * 1024 * 1024 * 1024, 4096, 2)
/// .unwrap()
/// .fec_size();
/// assert_eq!(size, 135_815_168);
/// ```
pub const FEC_DATA_MAX_SIZE: u64 = 67_911_680;
pub const FEC_DATA_MAX_SIZE: u64 = 135_815_168;
#[derive(Debug, Error)]
pub enum Error {
+135 -24
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::io::{self, Read, Seek, Write};
@@ -9,7 +9,7 @@ use flate2::{
write::{DeflateEncoder, GzEncoder},
};
use lz4_flex::frame::FrameDecoder;
use lzma_rust2::{CheckType, XZOptions, XZReader, XZWriter};
use lzma_rust2::{CheckType, LzmaOptions, LzmaReader, LzmaWriter, XzOptions, XzReader, XzWriter};
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -18,6 +18,14 @@ use crate::stream::ReadFixedSizeExt;
static GZIP_MAGIC: &[u8; 2] = b"\x1f\x8b";
static LZ4_LEGACY_MAGIC: &[u8; 4] = b"\x02\x21\x4c\x18";
static XZ_MAGIC: &[u8; 6] = b"\xfd\x37\x7a\x58\x5a\x00";
// This is not actually a magic value, but is good enough. The first byte
// encodes (lc=3, lp=0, pb=2) and the next two bytes are two of the U32LE bytes
// encoding the dictionary size. If this heuristic is good enough for libmagic,
// then it's good enough for us too.
// https://nigeltao.github.io/blog/2024/xz-lzma-part-5-xz.html
static LZMA_MAGIC: &[u8; 3] = b"\x5d\x00\x00";
const LZMA_MAX_MEMORY_KIB: u32 = 16 * 1024;
#[derive(Debug, Error)]
pub enum Error {
@@ -26,9 +34,13 @@ pub enum Error {
#[error("I/O error when autodetecting compression format")]
AutoDetect(#[source] io::Error),
#[error("Failed to initialize legacy LZ4 encoder")]
Lz4Init(#[source] io::Error),
Lz4EncodeInit(#[source] io::Error),
#[error("Failed to initialize XZ encoder")]
XzInit(#[source] io::Error),
XzEncodeInit(#[source] io::Error),
#[error("Failed to initialize LZMA decoder")]
LzmaDecodeInit(#[source] io::Error),
#[error("Failed to initialize LZMA encoder")]
LzmaEncodeInit(#[source] io::Error),
}
type Result<T> = std::result::Result<T, Error>;
@@ -113,6 +125,7 @@ pub enum CompressedFormat {
Gzip,
Lz4Legacy,
Xz,
Lzma,
}
pub enum CompressedReader<R: Read> {
@@ -121,18 +134,23 @@ pub enum CompressedReader<R: Read> {
Deflate(DeflateDecoder<R>),
Gzip(GzDecoder<R>),
Lz4(FrameDecoder<R>),
/// Boxed because the [`XZReader`] is nearly 4 KiB.
Xz(Box<XZReader<R>>),
/// Boxed because the [`XzReader`] is nearly 4 KiB.
Xz(Box<XzReader<R>>),
Lzma(Box<LzmaReader<R>>),
}
impl<R: Read> CompressedReader<R> {
pub fn with_format(reader: R, format: CompressedFormat) -> Self {
pub fn with_format(reader: R, format: CompressedFormat) -> Result<Self> {
match format {
CompressedFormat::None => Self::None(reader),
CompressedFormat::Deflate => Self::Deflate(DeflateDecoder::new(reader)),
CompressedFormat::Gzip => Self::Gzip(GzDecoder::new(reader)),
CompressedFormat::Lz4Legacy => Self::Lz4(FrameDecoder::new(reader)),
CompressedFormat::Xz => Self::Xz(Box::new(XZReader::new(reader, false))),
CompressedFormat::None => Ok(Self::None(reader)),
CompressedFormat::Deflate => Ok(Self::Deflate(DeflateDecoder::new(reader))),
CompressedFormat::Gzip => Ok(Self::Gzip(GzDecoder::new(reader))),
CompressedFormat::Lz4Legacy => Ok(Self::Lz4(FrameDecoder::new(reader))),
CompressedFormat::Xz => Ok(Self::Xz(Box::new(XzReader::new(reader, false)))),
CompressedFormat::Lzma => Ok(Self::Lzma(Box::new(
LzmaReader::new_mem_limit(reader, LZMA_MAX_MEMORY_KIB, None)
.map_err(Error::LzmaDecodeInit)?,
))),
}
}
@@ -143,6 +161,7 @@ impl<R: Read> CompressedReader<R> {
Self::Gzip(_) => CompressedFormat::Gzip,
Self::Lz4(_) => CompressedFormat::Lz4Legacy,
Self::Xz(_) => CompressedFormat::Xz,
Self::Lzma(_) => CompressedFormat::Lzma,
}
}
@@ -153,6 +172,7 @@ impl<R: Read> CompressedReader<R> {
Self::Gzip(r) => r.into_inner(),
Self::Lz4(r) => r.into_inner(),
Self::Xz(r) => r.into_inner(),
Self::Lzma(r) => r.into_inner(),
}
}
}
@@ -163,17 +183,21 @@ impl<R: Read + Seek> CompressedReader<R> {
reader.rewind().map_err(Error::AutoDetect)?;
if &magic[0..2] == GZIP_MAGIC {
Ok(Self::Gzip(GzDecoder::new(reader)))
let format = if &magic[0..2] == GZIP_MAGIC {
CompressedFormat::Gzip
} else if &magic[0..4] == LZ4_LEGACY_MAGIC {
Ok(Self::Lz4(FrameDecoder::new(reader)))
CompressedFormat::Lz4Legacy
} else if &magic == XZ_MAGIC {
Ok(Self::Xz(Box::new(XZReader::new(reader, false))))
CompressedFormat::Xz
} else if &magic[0..3] == LZMA_MAGIC {
CompressedFormat::Lzma
} else if raw_if_unknown {
Ok(Self::None(reader))
CompressedFormat::None
} else {
Err(Error::UnknownFormat)
}
return Err(Error::UnknownFormat);
};
Self::with_format(reader, format)
}
}
@@ -185,6 +209,7 @@ impl<R: Read> Read for CompressedReader<R> {
Self::Gzip(r) => r.read(buf),
Self::Lz4(r) => r.read(buf),
Self::Xz(r) => r.read(buf),
Self::Lzma(r) => r.read(buf),
}
}
}
@@ -195,7 +220,8 @@ pub enum CompressedWriter<W: Write> {
Deflate(DeflateEncoder<W>),
Gzip(GzEncoder<W>),
Lz4Legacy(Lz4LegacyEncoder<W>),
Xz(XZWriter<W>),
Xz(Box<XzWriter<W>>),
Lzma(Box<LzmaWriter<W>>),
}
impl<W: Write> CompressedWriter<W> {
@@ -210,16 +236,25 @@ impl<W: Write> CompressedWriter<W> {
Ok(Self::Gzip(GzEncoder::new(writer, Compression::default())))
}
CompressedFormat::Lz4Legacy => {
let encoder = Lz4LegacyEncoder::new(writer).map_err(Error::Lz4Init)?;
let encoder = Lz4LegacyEncoder::new(writer).map_err(Error::Lz4EncodeInit)?;
Ok(Self::Lz4Legacy(encoder))
}
CompressedFormat::Xz => {
// Some kernels are compiled without support for the default CRC64.
let mut options = XZOptions::with_preset(6);
let mut options = XzOptions::with_preset(6);
options.set_check_sum_type(CheckType::Crc32);
let xz_writer = XZWriter::new(writer, options).map_err(Error::XzInit)?;
Ok(Self::Xz(xz_writer))
let xz_writer = XzWriter::new(writer, options).map_err(Error::XzEncodeInit)?;
Ok(Self::Xz(Box::new(xz_writer)))
}
CompressedFormat::Lzma => {
// Defaults are already (lc=3, lp=0, pb=2) and has preset 6.
let options = LzmaOptions::default();
// We only support streaming output.
let lzma_writer = LzmaWriter::new_use_header(writer, &options, None)
.map_err(Error::LzmaEncodeInit)?;
Ok(Self::Lzma(Box::new(lzma_writer)))
}
}
}
@@ -231,6 +266,7 @@ impl<W: Write> CompressedWriter<W> {
Self::Gzip(_) => CompressedFormat::Gzip,
Self::Lz4Legacy(_) => CompressedFormat::Lz4Legacy,
Self::Xz(_) => CompressedFormat::Xz,
Self::Lzma(_) => CompressedFormat::Lzma,
}
}
@@ -241,6 +277,7 @@ impl<W: Write> CompressedWriter<W> {
Self::Gzip(w) => w.finish(),
Self::Lz4Legacy(w) => w.finish(),
Self::Xz(w) => w.finish(),
Self::Lzma(w) => w.finish(),
}
}
}
@@ -253,6 +290,7 @@ impl<W: Write> Write for CompressedWriter<W> {
Self::Gzip(w) => w.write(buf),
Self::Lz4Legacy(w) => w.write(buf),
Self::Xz(w) => w.write(buf),
Self::Lzma(w) => w.write(buf),
}
}
@@ -263,6 +301,79 @@ impl<W: Write> Write for CompressedWriter<W> {
Self::Gzip(w) => w.flush(),
Self::Lz4Legacy(w) => w.flush(),
Self::Xz(w) => w.flush(),
Self::Lzma(w) => w.flush(),
}
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use rand::Rng;
use super::*;
fn round_trip(format: CompressedFormat, can_autodetect: bool) {
let mut data = vec![0u8; 1024 * 1024];
rand::thread_rng().fill(data.as_mut_slice());
let mut writer = CompressedWriter::new(Cursor::new(Vec::new()), format).unwrap();
writer.write_all(&data).unwrap();
let mut compressed = writer.finish().unwrap();
compressed.rewind().unwrap();
let mut reader = if can_autodetect {
CompressedReader::new(compressed, true).unwrap()
} else {
CompressedReader::with_format(compressed, format).unwrap()
};
assert_eq!(reader.format(), format);
let mut buf = [0u8; 8192];
let mut total_read = 0;
loop {
let n = reader.read(&mut buf).unwrap();
if n == 0 {
break;
}
assert_eq!(&buf[..n], &data[total_read..][..n]);
total_read += n;
}
assert_eq!(total_read, data.len());
}
#[test]
fn round_trip_none() {
round_trip(CompressedFormat::None, true);
}
#[test]
fn round_trip_deflate() {
round_trip(CompressedFormat::Deflate, false);
}
#[test]
fn round_trip_gzip() {
round_trip(CompressedFormat::Gzip, true);
}
#[test]
fn round_trip_lz4_legacy() {
round_trip(CompressedFormat::Lz4Legacy, true);
}
#[test]
fn round_trip_xz() {
round_trip(CompressedFormat::Xz, true);
}
#[test]
fn round_trip_lzma() {
round_trip(CompressedFormat::Lzma, true);
}
}
+57 -2
View File
@@ -775,8 +775,8 @@ pub fn assign_inodes(entries: &mut [CpioEntry], missing_only: bool) -> Result<()
// (dev maj, dev min) -> (inode set, last assigned inode)
let mut inodes: HashMap<(u32, u32), (HashSet<u32>, u32)> = HashMap::new();
if missing_only {
for entry in &mut *entries {
for entry in &mut *entries {
if missing_only {
if entry.inode != 0 {
let key = (entry.dev_maj, entry.dev_min);
let (set, last) = inodes.entry(key).or_default();
@@ -784,6 +784,8 @@ pub fn assign_inodes(entries: &mut [CpioEntry], missing_only: bool) -> Result<()
set.insert(entry.inode);
*last = (*last).max(entry.inode);
}
} else {
entry.inode = 0;
}
}
@@ -835,3 +837,56 @@ pub fn save(
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assign_inodes_missing() {
let mut entries = [
CpioEntry::new_directory(b"a", 0o755),
CpioEntry::new_directory(b"b", 0o755),
CpioEntry::new_directory(b"c", 0o755),
CpioEntry::new_directory(b"d", 0o755),
CpioEntry::new_directory(b"e", 0o755),
];
entries[0].inode = 999;
entries[2].inode = 500;
entries[3].dev_maj = 1;
entries[3].dev_min = 1;
assign_inodes(&mut entries, true).unwrap();
assert_eq!(entries[0].inode, 999);
assert_eq!(entries[1].inode, 1000);
assert_eq!(entries[2].inode, 500);
assert_eq!(entries[3].inode, 300000);
assert_eq!(entries[4].inode, 1001);
}
#[test]
fn test_assign_inodes_reassign() {
let mut entries = [
CpioEntry::new_directory(b"a", 0o755),
CpioEntry::new_directory(b"b", 0o755),
CpioEntry::new_directory(b"c", 0o755),
CpioEntry::new_directory(b"d", 0o755),
CpioEntry::new_directory(b"e", 0o755),
];
entries[0].dev_maj = 1;
entries[0].dev_min = 1;
entries[0].inode = 1000;
entries[1].inode = 1000;
entries[2].inode = 500;
entries[3].dev_maj = 1;
entries[3].dev_min = 1;
assign_inodes(&mut entries, false).unwrap();
assert_eq!(entries[0].inode, 300000);
assert_eq!(entries[1].inode, 300000);
assert_eq!(entries[2].inode, 300001);
assert_eq!(entries[3].inode, 300001);
}
}
+16 -34
View File
@@ -54,7 +54,7 @@ const PARTITION_RESERVED_BYTES: u32 = 4096;
/// Maximum allowed size of [`RawGeometry::metadata_max_size`] to prevent the
/// memory usage from blowing up.
const METADATA_MAX_SIZE: u32 = 128 * 1024;
const METADATA_MAX_SIZE: u32 = 512 * 1024;
#[derive(Debug, Error)]
pub enum Error {
@@ -118,8 +118,6 @@ pub enum Error {
},
#[error("Partition {name:?}: Extent indices too large")]
PartitionExtentIndicesTooLarge { name: DebugString },
#[error("Partition {name:?}: Extent indices set on empty image")]
PartitionExtentIndicesEmptyImage { name: DebugString },
#[error("Partition {name:?}: Extent index too large")]
PartitionExtentIndexTooLarge { name: DebugString },
#[error("Partition {name:?}: Extent count too large")]
@@ -674,7 +672,6 @@ impl RawPartition {
/// [`RawPartitionGroup::validate`] do not.
fn validate(
&self,
image_type: ImageType,
header: &RawHeader,
extents: &[RawExtent],
groups: &[RawPartitionGroup],
@@ -695,26 +692,15 @@ impl RawPartition {
});
}
match image_type {
ImageType::Normal => {
if self
.first_extent_index
.get()
.checked_add(self.num_extents.get())
.is_none_or(|n| n as usize > extents.len())
{
return Err(Error::PartitionExtentIndicesTooLarge {
name: DebugString::new(self.name),
});
}
}
ImageType::Empty => {
if self.first_extent_index.get() != 0 || self.num_extents.get() != 0 {
return Err(Error::PartitionExtentIndicesEmptyImage {
name: DebugString::new(self.name),
});
}
}
if self
.first_extent_index
.get()
.checked_add(self.num_extents.get())
.is_none_or(|n| n as usize > extents.len())
{
return Err(Error::PartitionExtentIndicesTooLarge {
name: DebugString::new(self.name),
});
}
if self.group_index.get() as usize >= groups.len() {
@@ -971,7 +957,7 @@ struct RawMetadataSlot {
impl RawMetadataSlot {
/// Ensure that all fields are semantically valid and can be used without
/// further checks.
fn validate(&self, image_type: ImageType, geometry: &RawGeometry) -> Result<()> {
fn validate(&self, geometry: &RawGeometry) -> Result<()> {
self.header.validate(geometry)?;
for (len, descriptor, name) in [
@@ -1032,7 +1018,7 @@ impl RawMetadataSlot {
}
for partition in &self.partitions {
partition.validate(image_type, &self.header, &self.extents, &self.groups)?;
partition.validate(&self.header, &self.extents, &self.groups)?;
}
for (i, group) in self.groups.iter().enumerate() {
@@ -1111,11 +1097,7 @@ impl RawMetadata {
/// Read a single [`RawMetadataSlot`], including the header and all tables.
/// This does not read the extra padding between the end of the last table
/// and [`RawGeometry::metadata_max_size`].
fn read_metadata(
mut reader: impl Read,
image_type: ImageType,
geometry: &RawGeometry,
) -> Result<RawMetadataSlot> {
fn read_metadata(mut reader: impl Read, geometry: &RawGeometry) -> Result<RawMetadataSlot> {
let mut header = RawHeader::new_zeroed();
reader
@@ -1155,7 +1137,7 @@ impl RawMetadata {
block_devices: block_devices.to_vec(),
};
slot.validate(image_type, geometry)?;
slot.validate(geometry)?;
Ok(slot)
}
@@ -1194,7 +1176,7 @@ impl RawMetadata {
}
}
slot.validate(self.image_type, &self.geometry)?;
slot.validate(&self.geometry)?;
}
Ok(())
@@ -1229,7 +1211,7 @@ impl<R: Read> FromReader<R> for RawMetadata {
.stream_position()
.map_err(|e| Error::DataRead("orig_offset", e))?;
match Self::read_metadata(&mut reader, image_type, &geometry) {
match Self::read_metadata(&mut reader, &geometry) {
Ok(m) => *slot = Some(m),
Err(e @ Error::DataRead(_, _)) => return Err(e),
Err(e) => last_err = Some(e),
-9
View File
@@ -172,9 +172,6 @@ pub fn parse_legacy_metadata(data: &str) -> Result<OtaMetadata> {
}
}
"ota-wipe" => metadata.wipe = parse_yes()?,
"ota-retrofit-dynamic-partitions" => {
metadata.retrofit_dynamic_partitions = parse_yes()?;
}
"ota-downgrade" => metadata.downgrade = parse_yes()?,
"ota-required-cache" => {
metadata.required_cache = value.parse().map_err(|_| unsupported())?;
@@ -240,12 +237,6 @@ fn serialize_metadata(metadata: &OtaMetadata) -> (String, Vec<u8>) {
if metadata.wipe {
pairs.insert("ota-wipe".to_owned(), "yes".to_owned());
}
if metadata.retrofit_dynamic_partitions {
pairs.insert(
"ota-retrofit-dynamic-partitions".to_owned(),
"yes".to_owned(),
);
}
if metadata.downgrade {
pairs.insert("ota-downgrade".to_owned(), "yes".to_owned());
}
+15 -13
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022-2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2022-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -15,7 +15,7 @@ use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use bzip2::write::BzDecoder;
use flate2::{Compression, write::GzEncoder};
use lzma_rust2::{CheckType, XZOptions, XZReader, XZWriter};
use lzma_rust2::{CheckType, XzOptions, XzReader, XzWriter};
use num_traits::CheckedAdd;
use prost::Message;
use rayon::{
@@ -45,7 +45,7 @@ use crate::{
const PAYLOAD_MAGIC: &[u8; 4] = b"CrAU";
const PAYLOAD_VERSION: u64 = 2;
const MANIFEST_MAX_SIZE: usize = 4 * 1024 * 1024;
const MANIFEST_MAX_SIZE: usize = 16 * 1024 * 1024;
/// Size of each extent. This matches what AOSP's delta_generator does. We also
/// require this to be a multiple of the block size and a multiple of the
@@ -436,11 +436,11 @@ impl<W: Write> PayloadWriter<W> {
/// Finalize the payload. If this function is not called, the payload will
/// be left in an incomplete state. Returns the original writer, the final
/// header, the contents that should be written for `payload_properties.txt`
/// and the length of the header + manifest + manifest signature sections
/// (for constructing the `payload_metadata.bin` OTA metadata property files
/// entry).
pub fn finish(mut self) -> Result<(W, PayloadHeader, String, u64)> {
/// header and the data that should be written for `payload_properties.txt`.
/// [`PayloadHeader::blob_offset`] indicates the length of the header +
/// manifest + manifest signature sections (for constructing the
/// `payload_metadata.bin` OTA metadata property files entry).
pub fn finish(mut self) -> Result<(W, PayloadHeader, String)> {
// Append payload signature.
let payload_partial_hash = self.h_partial.clone().finish();
let payload_sig = sign_digest(payload_partial_hash.as_ref(), &self.key)?;
@@ -465,7 +465,9 @@ impl<W: Write> PayloadWriter<W> {
self.metadata_size as u64,
);
Ok((self.inner, self.header, properties, metadata_with_sig_size))
self.header.blob_offset = metadata_with_sig_size;
Ok((self.inner, self.header, properties))
}
/// Prepare for writing the next source data blob corresponding to an
@@ -814,7 +816,7 @@ pub fn apply_operation(
// reader and read till EOF.
let limited_reader = (&mut *reader).take(data_length);
let hashing_reader = HashingReader::new(limited_reader, hasher);
let mut decoder = XZReader::new(hashing_reader, false);
let mut decoder = XzReader::new(hashing_reader, false);
stream::copy(&mut decoder, &mut *writer, cancel_signal)
.map_err(error_fn)?;
@@ -922,9 +924,9 @@ fn compress_chunk(raw_data: &[u8], cancel_signal: &AtomicBool) -> Result<(Vec<u8
// decompression. Also, we intentionally pick the lowest compression level
// since we primarily care about squishing zeros. The non-zero portions of
// boot images are usually already-compressed kernels and ramdisks.
let mut options = XZOptions::with_preset(0);
let mut options = XzOptions::with_preset(0);
options.set_check_sum_type(CheckType::None);
let mut xz_writer = XZWriter::new(hashing_writer, options).map_err(Error::XzInit)?;
let mut xz_writer = XzWriter::new(hashing_writer, options).map_err(Error::XzInit)?;
stream::copy_n(reader, &mut xz_writer, raw_data.len() as u64, cancel_signal)
.map_err(Error::XzCompress)?;
@@ -1457,7 +1459,7 @@ pub fn compress_image(
.collect::<io::Result<Vec<_>>>()
.map_err(Error::ChunkWrite)?;
operations.extend(group_operations.into_iter());
operations.extend(group_operations);
}
let digest_uncompressed = context_uncompressed.finish();
+5 -5
View File
@@ -520,14 +520,14 @@ impl ChunkList {
// Find the chunk to insert before. We save the last used index to
// optimize for sequential insertion and avoid needing to search the
// entire list every time.
let mut insert_before = self.chunks.front_index();
if let Some(last_used) = self.last_used
let mut insert_before = if let Some(last_used) = self.last_used
&& chunk.bounds.start >= self.chunks.get(last_used).unwrap().bounds.start
{
// The new chunk starts after the last used chunk.
insert_before = Some(last_used);
}
Some(last_used)
} else {
self.chunks.front_index()
};
while let Some(index) = insert_before {
if self.chunks.get(index).unwrap().bounds.start >= chunk.bounds.start {
+12 -27
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2025-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -249,7 +249,8 @@ pub fn compressed_reader<'archive, R: ReaderAt>(
) -> Result<CompressedReader<ZipReader<&'archive R>>, rawzip::Error> {
let format = compression_method_to_format(compression_method)?;
Ok(CompressedReader::with_format(entry.reader(), format))
CompressedReader::with_format(entry.reader(), format)
.map_err(|e| rawzip::ErrorKind::InvalidInput { msg: e.to_string() }.into())
}
pub fn compressed_slice_reader<'archive>(
@@ -259,7 +260,8 @@ pub fn compressed_slice_reader<'archive>(
let format = compression_method_to_format(compression_method)?;
let raw_reader = Cursor::new(entry.data());
Ok(CompressedReader::with_format(raw_reader, format))
CompressedReader::with_format(raw_reader, format)
.map_err(|e| rawzip::ErrorKind::InvalidInput { msg: e.to_string() }.into())
}
pub fn verifying_reader<'archive, R: ReaderAt>(
@@ -286,7 +288,12 @@ pub fn compressed_writer<W: Write>(
match CompressedWriter::new(writer, format) {
Ok(w) => Ok(w),
Err(Error::Lz4Init(e) | Error::XzInit(e)) => Err(e.into()),
Err(
Error::Lz4EncodeInit(e)
| Error::XzEncodeInit(e)
| Error::LzmaDecodeInit(e)
| Error::LzmaEncodeInit(e),
) => Err(e.into()),
Err(Error::UnknownFormat | Error::AutoDetect(_)) => unreachable!(),
}
}
@@ -306,7 +313,7 @@ pub trait ZipArchiveReadAtExt {
impl ZipArchiveReadAtExt for ZipArchive<()> {}
pub struct ReaderAtWrapper<R: ReadAt>(R);
pub struct ReaderAtWrapper<R: ReadAt>(pub R);
impl<R: ReadAt> ReaderAt for ReaderAtWrapper<R> {
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
@@ -330,28 +337,6 @@ struct ZipLocalHeader {
extra_field_len: little_endian::U16,
}
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(C, packed)]
struct ZipCentralHeader {
pub signature: little_endian::U32,
pub version_made_by: little_endian::U16,
pub version_needed: little_endian::U16,
pub flags: little_endian::U16,
pub compression_method: little_endian::U16,
pub last_mod_time: little_endian::U16,
pub last_mod_date: little_endian::U16,
pub crc32: little_endian::U32,
pub compressed_size: little_endian::U32,
pub uncompressed_size: little_endian::U32,
pub file_name_len: little_endian::U16,
pub extra_field_len: little_endian::U16,
pub file_comment_len: little_endian::U16,
pub disk_number_start: little_endian::U16,
pub internal_file_attrs: little_endian::U16,
pub external_file_attrs: little_endian::U32,
pub local_header_offset: little_endian::U32,
}
/// Convert a streaming zip into a non-streaming one. If any entry uses ZIP64,
/// the local header must contain an [`ExtraFieldId::ANDROID_ZIP_ALIGNMENT`]
/// extra field with sufficient size (16 bytes to be safe). This is used as
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
//! Hack to format an integer as an octal string because toml_edit can't output
//! Hack to format an integer as an octal string because toml can't output
//! octal-formatted integers and many other toml parsers can't parse it either.
use std::{
+124 -46
View File
@@ -16,7 +16,7 @@ use std::{
};
use bstr::ByteSlice;
use lzma_rust2::{CheckType, XZOptions, XZWriter};
use lzma_rust2::{CheckType, XzOptions, XzWriter};
use rawzip::{RECOMMENDED_BUFFER_SIZE, ZipArchive};
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use regex::bytes::Regex;
@@ -32,7 +32,7 @@ use crate::{
avb::{self, AppendedDescriptorMut, Footer, Header},
bootimage::{self, BootImage, BootImageExt, RamdiskMeta},
compression::{self, CompressedFormat, CompressedReader, CompressedWriter},
cpio::{self, CpioEntry, CpioEntryData},
cpio::{self, CpioEntry, CpioEntryData, CpioEntryType},
zip::{self, ZipEntriesSafeExt, ZipFileHeaderRecordExt, ZipSliceEntriesSafeExt},
},
patch::otacert::{self, OtaCertBuildFlags},
@@ -139,10 +139,13 @@ fn load_ramdisk(
}
fn save_ramdisk(
entries: &[CpioEntry],
entries: &mut [CpioEntry],
format: CompressedFormat,
cancel_signal: &AtomicBool,
) -> Result<Vec<u8>> {
cpio::sort(entries);
cpio::assign_inodes(entries, false).map_err(Error::RamdiskSaveCpio)?;
let raw_writer = Cursor::new(vec![]);
let mut writer =
CompressedWriter::new(raw_writer, format).map_err(Error::RamdiskSaveCompression)?;
@@ -156,6 +159,12 @@ fn save_ramdisk(
Ok(raw_writer.into_inner())
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct BootLayoutInfo {
pub has_recovery: bool,
}
#[derive(Clone, Eq, PartialEq)]
pub struct BootImageInfo {
pub header: Header,
pub footer: Footer,
@@ -171,11 +180,17 @@ pub trait BootImagePatch {
/// can be inspected, but during patching, only the boot image is available.
fn find_targets<'a>(
&self,
layout: BootLayoutInfo,
boot_images: &HashMap<&'a str, BootImageInfo>,
cancel_signal: &AtomicBool,
) -> TargetsResult<Vec<&'a str>>;
fn patch(&self, boot_image: &mut BootImage, cancel_signal: &AtomicBool) -> Result<()>;
fn patch(
&self,
layout: BootLayoutInfo,
boot_image: &mut BootImage,
cancel_signal: &AtomicBool,
) -> Result<()>;
}
/// Root a boot image with Magisk.
@@ -196,11 +211,12 @@ impl MagiskRootPatcher {
// replaced by PREINITDEVICE
// - Versions newer than the latest supported version are assumed to support
// the same features as the latest version
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..30300];
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..30800];
const VER_PREINIT_DEVICE: RangeFrom<u32> = 25211..;
const VER_RANDOM_SEED: Range<u32> = 25211..26103;
const VER_PATCH_VBMETA: Range<u32> = Self::VERS_SUPPORTED[0].start..26202;
const VER_XZ_BACKUP: RangeFrom<u32> = 26403..;
const VER_VENDOR_BOOT: RangeFrom<u32> = 30300..;
const ZIP_INIT_LD: &'static str = "lib/arm64-v8a/libinit-ld.so";
const ZIP_LIBMAGISK: &'static str = "lib/arm64-v8a/libmagisk.so";
@@ -301,11 +317,11 @@ impl MagiskRootPatcher {
}
fn xz_compress(name: &[u8], reader: impl Read, cancel_signal: &AtomicBool) -> Result<Vec<u8>> {
let mut options = XZOptions::with_preset(9);
let mut options = XzOptions::with_preset(9);
options.set_check_sum_type(CheckType::None);
let raw_writer = Cursor::new(Vec::new());
let mut writer = XZWriter::new(raw_writer, options).map_err(Error::XzInit)?;
let mut writer = XzWriter::new(raw_writer, options).map_err(Error::XzInit)?;
let raw_writer = stream::copy(reader, &mut writer, cancel_signal)
.and_then(|_| writer.finish())
@@ -391,15 +407,18 @@ impl MagiskRootPatcher {
let mut new_path = b".backup/".to_vec();
new_path.extend(&old_entry.path);
let mut new_data = None;
if xz_compress && let CpioEntryData::Data(data) = &old_entry.data {
let new_data = if xz_compress
&& old_entry.file_type == CpioEntryType::Regular
&& let CpioEntryData::Data(data) = &old_entry.data
{
new_path.extend(b".xz");
let reader = Cursor::new(data);
let buf = Self::xz_compress(&new_path, reader, cancel_signal)?;
new_data = Some(CpioEntryData::Data(buf));
}
Some(CpioEntryData::Data(buf))
} else {
None
};
new_entries.push(CpioEntry {
path: new_path,
@@ -436,6 +455,7 @@ impl BootImagePatch for MagiskRootPatcher {
fn find_targets<'a>(
&self,
_layout: BootLayoutInfo,
boot_images: &HashMap<&'a str, BootImageInfo>,
_cancel_signal: &AtomicBool,
) -> TargetsResult<Vec<&'a str>> {
@@ -450,7 +470,12 @@ impl BootImagePatch for MagiskRootPatcher {
Ok(targets)
}
fn patch(&self, boot_image: &mut BootImage, cancel_signal: &AtomicBool) -> Result<()> {
fn patch(
&self,
_layout: BootLayoutInfo,
boot_image: &mut BootImage,
cancel_signal: &AtomicBool,
) -> Result<()> {
// Load the first ramdisk. If it doesn't exist, we have to generate one
// from scratch.
let ramdisk = match boot_image {
@@ -586,6 +611,13 @@ impl BootImagePatch for MagiskRootPatcher {
magisk_config.push_str("RECOVERYMODE=false\n");
// We never install Magisk into the init_boot.cpio within the
// vendor_boot ramdisk. This config option was introduced in commit
// 742913ebcb10cf819a54699497359535047874f7.
if Self::VER_VENDOR_BOOT.contains(&self.version) {
magisk_config.push_str("VENDORBOOT=false\n");
}
if Self::VER_PREINIT_DEVICE.contains(&self.version)
&& let Some(device) = &self.preinit_device
{
@@ -611,9 +643,7 @@ impl BootImagePatch for MagiskRootPatcher {
));
// Repack ramdisk.
cpio::sort(&mut entries);
cpio::assign_inodes(&mut entries, false).map_err(Error::RamdiskSaveCpio)?;
let new_ramdisk = save_ramdisk(&entries, ramdisk_format, cancel_signal)?;
let new_ramdisk = save_ramdisk(&mut entries, ramdisk_format, cancel_signal)?;
match boot_image {
BootImage::V0Through2(b) => b.ramdisk = new_ramdisk,
@@ -724,7 +754,7 @@ impl OtaCertPatcher {
entry.data = CpioEntryData::Data(zip.to_vec());
// Repack ramdisk.
*ramdisk = save_ramdisk(&entries, ramdisk_format, cancel_signal)?;
*ramdisk = save_ramdisk(&mut entries, ramdisk_format, cancel_signal)?;
Ok(true)
}
@@ -737,6 +767,7 @@ impl BootImagePatch for OtaCertPatcher {
fn find_targets<'a>(
&self,
_layout: BootLayoutInfo,
boot_images: &HashMap<&'a str, BootImageInfo>,
cancel_signal: &AtomicBool,
) -> TargetsResult<Vec<&'a str>> {
@@ -769,7 +800,12 @@ impl BootImagePatch for OtaCertPatcher {
Ok(targets)
}
fn patch(&self, boot_image: &mut BootImage, cancel_signal: &AtomicBool) -> Result<()> {
fn patch(
&self,
_layout: BootLayoutInfo,
boot_image: &mut BootImage,
cancel_signal: &AtomicBool,
) -> Result<()> {
let ramdisks = match boot_image {
BootImage::V0Through2(b) => slice::from_mut(&mut b.ramdisk),
BootImage::V3Through4(b) => slice::from_mut(&mut b.ramdisk),
@@ -808,34 +844,49 @@ impl DsuPubKeyPatcher {
const FIRST_STAGE_PATH: &'static [u8] = b"first_stage_ramdisk";
const DSU_KEYS_PATH: &'static [u8] = b"first_stage_ramdisk/avb";
const AVBROOT_KEY_PATH: &'static [u8] = b"first_stage_ramdisk/avb/avbroot.avbpubkey";
const RECOVERY_DSU_KEYS_PATH: &'static [u8] = b"avb";
const RECOVERY_AVBROOT_KEY_PATH: &'static [u8] = b"avb/avbroot.avbpubkey";
pub fn new(key: RsaPublicKey) -> Self {
Self { key }
}
fn patch_ramdisk(&self, ramdisk: &mut Vec<u8>, cancel_signal: &AtomicBool) -> Result<bool> {
fn patch_ramdisk(
&self,
layout: BootLayoutInfo,
ramdisk: &mut Vec<u8>,
cancel_signal: &AtomicBool,
) -> Result<bool> {
let (mut entries, ramdisk_format) = load_ramdisk(ramdisk, cancel_signal)?;
if !entries.iter_mut().any(|e| e.path == Self::FIRST_STAGE_PATH) {
return Ok(false);
}
if !entries.iter().any(|e| e.path == Self::DSU_KEYS_PATH) {
entries.push(CpioEntry::new_directory(Self::DSU_KEYS_PATH, 0o755));
let (dsu_keys_path, avbroot_key_path) = if layout.has_recovery {
(
Self::RECOVERY_DSU_KEYS_PATH,
Self::RECOVERY_AVBROOT_KEY_PATH,
)
} else {
(Self::DSU_KEYS_PATH, Self::AVBROOT_KEY_PATH)
};
debug!("DSU key path: {:?}", avbroot_key_path.as_bstr());
if !entries.iter().any(|e| e.path == dsu_keys_path) {
entries.push(CpioEntry::new_directory(dsu_keys_path, 0o755));
}
let binary_key = avb::encode_public_key(&self.key).map_err(Error::AvbEncodeKey)?;
let data = CpioEntryData::Data(binary_key);
if let Some(e) = entries
.iter_mut()
.find(|e| e.path == Self::AVBROOT_KEY_PATH)
{
if let Some(e) = entries.iter_mut().find(|e| e.path == avbroot_key_path) {
e.data = data;
} else {
entries.push(CpioEntry::new_file(Self::AVBROOT_KEY_PATH, 0o644, data));
entries.push(CpioEntry::new_file(avbroot_key_path, 0o644, data));
}
*ramdisk = save_ramdisk(&entries, ramdisk_format, cancel_signal)?;
*ramdisk = save_ramdisk(&mut entries, ramdisk_format, cancel_signal)?;
Ok(true)
}
@@ -848,6 +899,7 @@ impl BootImagePatch for DsuPubKeyPatcher {
fn find_targets<'a>(
&self,
layout: BootLayoutInfo,
boot_images: &HashMap<&'a str, BootImageInfo>,
cancel_signal: &AtomicBool,
) -> TargetsResult<Vec<&'a str>> {
@@ -861,6 +913,12 @@ impl BootImagePatch for DsuPubKeyPatcher {
BootImage::VendorV3Through4(b) => &b.ramdisks,
};
let dsu_keys_path = if layout.has_recovery {
Self::RECOVERY_DSU_KEYS_PATH
} else {
Self::DSU_KEYS_PATH
};
for ramdisk in ramdisks {
if ramdisk.is_empty() {
continue;
@@ -871,10 +929,12 @@ impl BootImagePatch for DsuPubKeyPatcher {
let mut found = false;
for entry in entries {
if entry.path == Self::DSU_KEYS_PATH {
if entry.path == dsu_keys_path {
dsu_keys_targets.push(name);
found = true;
} else if entry.path == Self::FIRST_STAGE_PATH {
// This exists even on devices with recovery partitions.
// init just doesn't switch root to it.
first_stage_targets.push(name);
found = true;
}
@@ -908,7 +968,12 @@ impl BootImagePatch for DsuPubKeyPatcher {
}
}
fn patch(&self, boot_image: &mut BootImage, cancel_signal: &AtomicBool) -> Result<()> {
fn patch(
&self,
layout: BootLayoutInfo,
boot_image: &mut BootImage,
cancel_signal: &AtomicBool,
) -> Result<()> {
let ramdisks = match boot_image {
BootImage::V0Through2(b) => slice::from_mut(&mut b.ramdisk),
BootImage::V3Through4(b) => slice::from_mut(&mut b.ramdisk),
@@ -920,7 +985,7 @@ impl BootImagePatch for DsuPubKeyPatcher {
continue;
}
if self.patch_ramdisk(ramdisk, cancel_signal)? {
if self.patch_ramdisk(layout, ramdisk, cancel_signal)? {
return Ok(());
}
}
@@ -1005,6 +1070,7 @@ impl BootImagePatch for PrepatchedImagePatcher {
fn find_targets<'a>(
&self,
_layout: BootLayoutInfo,
boot_images: &HashMap<&'a str, BootImageInfo>,
_cancel_signal: &AtomicBool,
) -> TargetsResult<Vec<&'a str>> {
@@ -1029,7 +1095,12 @@ impl BootImagePatch for PrepatchedImagePatcher {
Ok(targets)
}
fn patch(&self, boot_image: &mut BootImage, _cancel_signal: &AtomicBool) -> Result<()> {
fn patch(
&self,
_layout: BootLayoutInfo,
boot_image: &mut BootImage,
_cancel_signal: &AtomicBool,
) -> Result<()> {
let prepatched_image = self.load_prepatched_image()?;
// Level 0: Warnings that don't affect booting
@@ -1278,10 +1349,10 @@ pub trait BootImageOpener {
pub fn load_boot_images<'a>(
names: &[&'a str],
opener: &(dyn BootImageOpener + Sync),
) -> TargetsResult<HashMap<&'a str, BootImageInfo>> {
) -> TargetsResult<(HashMap<&'a str, BootImageInfo>, BootLayoutInfo)> {
let parent_span = Span::current();
names
let images = names
.par_iter()
.map(|&name| {
let _span = debug_span!(parent: &parent_span, "image", name).entered();
@@ -1294,7 +1365,13 @@ pub fn load_boot_images<'a>(
Ok((name, info))
})
.collect()
.collect::<TargetsResult<HashMap<_, _>>>()?;
let layout = BootLayoutInfo {
has_recovery: images.contains_key("recovery"),
};
Ok((images, layout))
}
/// Apply applicable patches to the list of specified boot images. For each
@@ -1317,7 +1394,7 @@ pub fn patch_boot_images<'a>(
}
// Preparse all images. Some patchers need to inspect every candidate.
let mut images = load_boot_images(names, opener)?;
let (mut images, layout) = load_boot_images(names, opener)?;
// Find the targets that each patcher wants to patch.
let all_targets = patchers
@@ -1325,14 +1402,15 @@ pub fn patch_boot_images<'a>(
.map(|p| {
let _span =
debug_span!(parent: &parent_span, "patcher", name = p.patcher_name()).entered();
p.find_targets(&images, cancel_signal).and_then(|targets| {
if targets.is_empty() {
Err(TargetsError::NoTargets(p.patcher_name()))
} else {
debug!("Found patcher targets: {targets:?}");
Ok(targets)
}
})
p.find_targets(layout, &images, cancel_signal)
.and_then(|targets| {
if targets.is_empty() {
Err(TargetsError::NoTargets(p.patcher_name()))
} else {
debug!("Found patcher targets: {targets:?}");
Ok(targets)
}
})
})
.collect::<TargetsResult<Vec<_>>>()?;
@@ -1340,7 +1418,7 @@ pub fn patch_boot_images<'a>(
// Regroup data so we can parallelize by target.
let mut groups = HashMap::<&str, (BootImageInfo, Vec<&Box<dyn BootImagePatch + Sync>>)>::new();
for (patcher, targets) in patchers.iter().zip(all_targets.into_iter()) {
for (patcher, targets) in patchers.iter().zip(all_targets) {
for target in targets {
groups
.entry(target)
@@ -1359,8 +1437,8 @@ pub fn patch_boot_images<'a>(
.try_for_each(|(&name, (info, patchers))| -> TargetsResult<()> {
patchers.iter().try_for_each(|p| {
let _span =
debug_span!(parent: &parent_span, "patcher", name = p.patcher_name()).entered();
p.patch(&mut info.boot_image, cancel_signal)
debug_span!(parent: &parent_span, "patcher", name = p.patcher_name(), image = name).entered();
p.patch(layout, &mut info.boot_image, cancel_signal)
.map_err(|e| TargetsError::Patch(name.to_owned(), e))
})
})?;
+82
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
#![allow(clippy::all)]
#![allow(clippy::nursery)]
#![allow(clippy::pedantic)]
@@ -13,3 +16,82 @@ pub mod build {
pub mod chromeos_update_engine {
include!(concat!(env!("OUT_DIR"), "/chromeos_update_engine.rs"));
}
/// Implement [`serde::Serialize`] and [`serde::Deserialize`] for prost enum
/// type and create a bridge to use with `#[serde(with = "...")]` since enum
/// fields are stored as their underlying repr. `$type` must be a fully
/// qualified type.
macro_rules! serde_derive_prost_enum {
($type:ty, $module:ident $(,)?) => {
impl serde::Serialize for $type {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str_name().serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for $type {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct EnumVisitor;
impl serde::de::Visitor<'_> for EnumVisitor {
type Value = $type;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a string containing a valid enum variant")
}
fn visit_str<R>(self, v: &str) -> Result<Self::Value, R>
where
R: serde::de::Error,
{
match <$type>::from_str_name(v) {
Some(e) => Ok(e),
None => Err(serde::de::Error::custom(format!(
"invalid enum variant: {v}",
))),
}
}
}
deserializer.deserialize_any(EnumVisitor)
}
}
mod $module {
pub fn serialize<S, T>(data: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: Copy + std::fmt::Display,
$type: TryFrom<T>,
<$type as TryFrom<T>>::Error: std::fmt::Display,
{
let value = <$type>::try_from(*data).map_err(|e| {
serde::ser::Error::custom(format!("invalid enum value: {data}: {e}"))
})?;
serde::Serialize::serialize(&value, serializer)
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'de>,
T: From<$type>,
{
let value: $type = serde::Deserialize::deserialize(deserializer)?;
Ok(value.into())
}
}
};
}
serde_derive_prost_enum!(
crate::protobuf::build::tools::releasetools::ota_metadata::OtaType,
ota_type,
);
+6 -10
View File
@@ -34,20 +34,13 @@ allow = [
"Apache-2.0 WITH LLVM-exception",
"BSD-3-Clause",
"bzip2-1.0.6",
"GPL-3.0",
"GPL-3.0-only",
"ISC",
"MIT",
"Unicode-3.0",
"Zlib",
]
[[licenses.clarify]]
name = "ring"
expression = "MIT AND ISC AND OpenSSL"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 },
]
[bans]
multiple-versions = "warn"
multiple-versions-include-dev = true
@@ -61,8 +54,11 @@ executables = "deny"
include-dependencies = true
include-workspace = true
bypass = [
# Copies of unmodified crashwrangler objects for old macOS versions.
{ name = "honggfuzz", allow-globs = ["honggfuzz/third_party/mac/CrashReport_*.o"] },
# honggfuzz/third_party/mac/CrashReport_*.o are copies of unmodified
# crashwrangler objects for old macOS versions. Unfortunately, when running
# cargo hfuzz, the compiled hongfuzz binary and corresponding object files
# are written to the source directory too.
{ name = "honggfuzz", allow-globs = ["honggfuzz/*.o", "honggfuzz/honggfuzz"] },
# Only used in tests.
{ name = "libloading", allow-globs = ["tests/nagisa*.dll"] },
]
+3 -2
View File
@@ -4,7 +4,8 @@ version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
publish = false
publish.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -19,7 +20,7 @@ ring = "0.17.14"
rsa = { version = "0.9.6", features = ["hazmat"] }
serde = { version = "1.0.188", features = ["derive"] }
tempfile = "3.8.0"
toml_edit = { version = "0.23.3", features = ["serde"] }
toml = { version = "1.0.1", features = ["serde"] }
topological-sort = "0.2.2"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
+8 -8
View File
@@ -53,11 +53,11 @@ data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
[profile.pixel_v4_gki.hashes_streaming]
original = "ea96196191e3a4133db4aff45d47aa3468514e29e0a724faac8081cbf4adf808"
patched = "357a448d1a7505b2308ce1c2d19b063e606c60e7349784913a48cdc3f6d50aa4"
patched = "6b88761a7927ea4a3fdd953cbb7ffb78361e51648f5ab7a34b0dffad2bc163d5"
[profile.pixel_v4_gki.hashes_seekable]
original = "f6615ae355eba38689d24aa535981d09175d4832e7c65c04cdd89aa95d21f09d"
patched = "9195ba963d9897af2f0821ff6051c1efe3fe130055b7936bedf2cd189998fd89"
patched = "e9c0b85a0f9384d3e022f3c83a4653511cd03bfbfa26e660b35cf52986dec888"
# Google Pixel 6a
# What's unique: boot (boot v4, no ramdisk) + vendor_boot (vendor v4, 2 ramdisks)
@@ -97,11 +97,11 @@ data.ramdisks = [["init", "otacerts", "first_stage", "dsu_key_dir"], ["dlkm"]]
[profile.pixel_v4_non_gki.hashes_streaming]
original = "cb2a2e406d2b4c68c8a38f819256a10ada0ed5a4732822bec35bdf7bcdc458eb"
patched = "12f15d29fceeb18af14c9a805de3aedb7f270fe8ec316d9d0fe37fb0c667eacd"
patched = "45cbe96e01b8b2e3ba5e4a2c990e7ff08bee285316a4a7a314aab3ffddf27d0d"
[profile.pixel_v4_non_gki.hashes_seekable]
original = "9450c212c34fe55453b52345c7cc22f72397dc80d9ddc792eafc20b461c2afc9"
patched = "5018c579df9608f9dfe3add2afd8b176de61ba267376cf3a3e422ecc9d3dd112"
patched = "111fdfae91977d80cd141f6c3dc69f3af45bb7fcdf3e2e442bc3f0ad5fa77faa"
# Google Pixel 4a 5G
# What's unique: boot (boot v3) + vendor_boot (vendor v3)
@@ -141,11 +141,11 @@ data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
[profile.pixel_v3.hashes_streaming]
original = "4b864b906a13ec98b1d6c3440a5eca7404ec63d073080e80a3b84afabcbd2fa2"
patched = "a9077fe32dee8eb7a0369c1334d8f3378ae8044814ae04da700f33294b12a4d8"
patched = "cdb9f92e357d8fdf43640704cf7c73917f026e0f759991871ccbab4bfd8c303b"
[profile.pixel_v3.hashes_seekable]
original = "e0e93ee11de56992f3a5f543858c1d11858b4deb76a91a4caf4fb0f3f34be855"
patched = "8730b398367179a0be092a67e4714f09c2267c656fe3e42ec1f8b43bb3a28634"
patched = "bd57432a97feb62c5e4116f0d5bc3b5db61dd46ffa6e436403d197f091a19a55"
# Google Pixel 4a
# What's unique: boot (boot v2)
@@ -174,8 +174,8 @@ data.deps = ["system"]
[profile.pixel_v2.hashes_streaming]
original = "abc0ad7f80020018101437c2494d6564a877df663d208a123267fc100734c175"
patched = "b1876455f5be9d5b6eafc598a017ad10e5e76339137f8b29777097ab19cdc3b0"
patched = "deea7ebf412d413743f14792fbf5605dea1a2a099114b33b9e05d464d90e1228"
[profile.pixel_v2.hashes_seekable]
original = "57d4ac5ab7d362a593f3c02f3d2044b5400c10db0f732741a2d18e025d4231ec"
patched = "40d7674be14e19747e7ead118e0f4faf50880c638dfb4bfc0d0154b0e9202d3f"
patched = "71f49a27c801b837a219927430b41ba37126084d88d141485263a011d2fb9d70"
+2 -7
View File
@@ -6,7 +6,6 @@ use std::{collections::BTreeMap, fs, path::Path};
use anyhow::{Context, Result};
use avbroot::format::payload::{CowVersion, VabcAlgo};
use serde::{Deserialize, Serialize};
use toml_edit::DocumentMut;
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct Sha256Hash(
@@ -130,12 +129,8 @@ pub struct Config {
pub profile: BTreeMap<String, Profile>,
}
pub fn load_config(path: &Path) -> Result<(Config, DocumentMut)> {
pub fn load_config(path: &Path) -> Result<Config> {
let contents =
fs::read_to_string(path).with_context(|| format!("Failed to read config: {path:?}"))?;
let config: Config = toml_edit::de::from_str(&contents)
.with_context(|| format!("Failed to parse config: {path:?}"))?;
let document: DocumentMut = contents.parse().unwrap();
Ok((config, document))
toml::de::from_str(&contents).with_context(|| format!("Failed to parse config: {path:?}"))
}
+12 -10
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023 Pascal Roeleven
// SPDX-License-Identifier: GPL-3.0-only
@@ -68,11 +68,11 @@ use crate::{
fn hash_file(path: &Path, cancel_signal: &AtomicBool) -> Result<[u8; 32]> {
info!("Calculating hash: {path:?}");
let raw_reader =
File::open(path).with_context(|| format!("Failed to open for reading: {path:?}"))?;
let buf_reader = BufReader::new(raw_reader);
let raw_reader = File::open(path)
.map(BufReader::new)
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
let context = ring::digest::Context::new(&ring::digest::SHA256);
let mut hashing_reader = HashingReader::new(buf_reader, context);
let mut hashing_reader = HashingReader::new(raw_reader, context);
stream::copy(&mut hashing_reader, io::sink(), cancel_signal)?;
@@ -664,6 +664,7 @@ fn create_payload(
CowVersion::V2 => v.force_compression_factor.then_some(64 * 1024),
CowVersion::V3 { compression_factor } => Some(compression_factor.into()),
}),
disable_ublk: None,
}),
partial_update: None,
apex_info: vec![],
@@ -704,11 +705,11 @@ fn create_payload(
.with_context(|| format!("Failed to copy from image: {name}"))?;
}
let (_, _, properties, metadata_size) = payload_writer
let (_, header, properties) = payload_writer
.finish()
.context("Failed to finalize payload")?;
Ok((properties, metadata_size))
Ok((properties, header.blob_offset))
}
#[allow(clippy::too_many_arguments)]
@@ -820,7 +821,6 @@ fn create_ota(
security_patch_level: ota_info.security_patch_level.clone(),
partition_state: vec![],
}),
retrofit_dynamic_partitions: false,
required_cache: 0,
spl_downgrade: false,
};
@@ -1067,6 +1067,8 @@ fn patch_image(
OsStr::new("--replace"),
OsStr::new("system"),
system_image_file.as_os_str(),
OsStr::new("--re-sign"),
OsStr::new("boot"),
OsStr::new("--key-avb"),
avb_key_file.as_os_str(),
OsStr::new("--pass-avb-file"),
@@ -1203,7 +1205,7 @@ fn filter_profiles<'a>(config: &'a Config, cli: &'a ProfileGroup) -> Result<BTre
}
fn test_subcommand(cli: &TestCli, cancel_signal: &AtomicBool) -> Result<()> {
let (config, _) = config::load_config(&cli.config.config)?;
let config = config::load_config(&cli.config.config)?;
let profiles = filter_profiles(&config, &cli.profile)?;
if profiles.is_empty() {
@@ -1336,7 +1338,7 @@ fn test_subcommand(cli: &TestCli, cancel_signal: &AtomicBool) -> Result<()> {
}
fn list_subcommand(cli: &ListCli) -> Result<()> {
let (config, _) = config::load_config(&cli.config.config)?;
let config = config::load_config(&cli.config.config)?;
for profile in config.profile.keys() {
println!("{profile}");
+1
View File
@@ -1,4 +1,5 @@
/hfuzz_target/
/hfuzz_workspace/
HONGGFUZZ.REPORT.TXT
*.honggfuzz.cov
*.fuzz
+2 -1
View File
@@ -4,7 +4,8 @@ version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
publish = false
publish.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+9 -3
View File
@@ -10,7 +10,13 @@ While avbroot's parsers are all memory-safe, it is still possible for panics or
cargo install honggfuzz
```
2. Pick a fuzz target to run. A fuzz target is the name of the source file in [`src/bin/`](./src/bin) without the `.rs` extension.
2. [Optional] Generate sample files to use as the initial fuzzing corpus.
```bash
cargo xtask fuzz-corpus
```
3. Pick a fuzz target to run. A fuzz target is the name of the source file in [`src/bin/`](./src/bin) without the `.rs` extension.
The list of targets can be queried programmatically with:
@@ -18,7 +24,7 @@ While avbroot's parsers are all memory-safe, it is still possible for panics or
cargo read-manifest | jq -r '.targets[].name'
```
3. Run the fuzzer.
4. Run the fuzzer.
```bash
cargo hfuzz run <fuzz target>
@@ -41,7 +47,7 @@ While avbroot's parsers are all memory-safe, it is still possible for panics or
When a crash occurs, the `Crashes` counter will increment and the input data that triggered the crash will be written to `hfuzz_workspace/<fuzz target>/*.fuzz`. New files are only written for unique crashes.
4. If a crash occurs, run the following command to trigger the crash in a debugger.
5. If a crash occurs, run the following command to trigger the crash in a debugger.
```bash
cargo hfuzz run-debug <fuzz target> \
@@ -0,0 +1,38 @@
image_size = 12288
[header]
required_libavb_version_major = 1
required_libavb_version_minor = 0
algorithm_type = "Sha256Rsa4096"
hash = "3035539ec16eb6e6645a9022dd62379bf448cc53178b91fb68507eb6565b5478"
signature = "25f59424238d43e8e247583f276d45caeed1396e5d6298c4f4f5db4bdb74453ece1783143900157b35bdd99fe29123fe98f0876663b1c985a1426bdfbdf0b31b710ebe54cbb759d4eb1aa6ecc6964c5bccd364f1a9359e53d79797ec0bdc8e453a9a6c559e95ab4d8309422ad182bdc210b3d815b277b2fc8c827ff5849997ca9cf4fcb9fae69902ba865478401c2a4976f07499f27e8f8e3e609679dd32b495c27dfe9dae4de6eb7e9a86abe30da6f0e5454f85cf170eb3ea75250d697770d0028c78c0be12280b7599019ac03a05612384f81263821b8d3cbd902e4c2b6442afed2fb7800dfc4be999f77274afa4d97512147b8ccf791e59a91e75deebd4469538d71bc9272e4c7fbbc657f3d269ec269a617314a8ceb4a4920f9654fd549c8def8a3d5a54c38871cb822af8733a575f69a187717debca30884872206f04cb98aade78c765007e2d9b024185f153ec769de1684e98b0ce41fd0dbefd9aca814887ee0917f067708c66f1e5586c578f3a4f4148f71e5ce7dd84f150b99b1547e3ca6c041fafd085bb987f16a6056a4c28875d151872bda5e316b08bb43cfb2db78759b50e4cf016f0b0b0118e03ee25508da5ce9eee77f97fdeab0831a55c43760d1ba0b4cd0c8570840549f356f32c0abf8666daf478a4cad9b84cda52430137ad12ee15425a0bf948e362c6f2da0f4ff3c57683e0b8760a014e1dbd936c11"
public_key = "0000100080af29ebacee1fc0a795d91674aaeb4f55f4d36ba6f899ddb543be5d1cc878d45d776fe6211a330c0843322334b690dbbb9dc13fa47642abc75889dbba79c82a9dba19c64d4c099f36240775834e9d86013f433a1aaab0bed1f2212dc53cf2d967ded53209c1e5eb5b563d00d307c021b9d2d084e80cdd20a695225ce31e7fb407ccebdec5cda2eb44ed079df3edf31751e77ce43ae9681bc7f6cc4e58945366bb655550e07dd215eb516815e9457feef9301b772f92291fd5758782f20734b9ac75e7469f42ffd4055ae43a8301a37e56dfda4ede5e639fec57ab7268d8787190268952a27e6b8e3ffc503759af317da4a076fd36fe6499f15de27d81dca566f500838d13a442b01ecd1ffdb655ea2ef779bab13cd8eab649402cc1a3f81444ed24717a7c3fa65ac2b8d3625450aae15b75d3dc37e962f516799cb555063eeb262bb51f9af465fc992e07f3aa66917ce086abd9a60716f04fe155efe7901e4132a4dcdcdcf7db23e4eade6b3664bee87a7fd4c6b3247beeb0553263845d6547efda705f58cab911d9a98fb7cdd807bfdbf3dbf1f81ac3e837ba75ea5512be7246bfe768aa1edf23c469db87df980bbb939fd1f28948340882350e4c58f0450329f8088da6b908b4c4c78689eb18b3c06b683b12e518bde5cd57e7fb43792df94c38f643675e5c92b1c0a95f45256969a4bc6ea1db6682745d423cc676f97f57cf2d493d850bfba9362a858faa355e2bd0c9b1a46f6581c9c0751399788bd37fc5e597a402a3bc05bb5959677a48ce4aa9fa6ed5add36a7e11117152610b0e8f86692669825206eb5d297d7f77d114bc867736b6d6eb4309329569d78dfb910cb66132c5a3f415bd1ededfb61fd58c958db4c7a2c17578f537d24b5405e545d1618ae58be3525c9659d6baf2634255e7ca26fcfde1cbc4401e8ca062babede5f5b3f2ad5cbecd3d9332098f402666a9ef85643e3f4ab214a67a5ca91f31f810eab89df57d2f9ef35e9a5f46a99228af059cdd1f0685f1b9405beb0e44fe7f590b50feea1554e8bdaf7be7fd22e6f050a7751731cd7f8737b5acb92378da9d59068106da25b30882abad4629dff6ca9c4c0cf5337bf2d2a9e8c6182dcd6d7c681da01df6c11d0b335cf1f982156393c4e572df540cee2c53a2c146dec9644be4e81cce20df4cb136b3a08fad9a186be892d6653330d11cac1e96d308fd0d2896c8a141edfbdcac827cdf97ac581f493721c9aa1305667df312277c8ab6700c3a978e6f9d00b389faaeaa99b5e023b72c5f5ce885fa2ba816ffc735599fad1e3876d91a501efbbf1315f42a503ddd7a26a784a3f290a86f43dd580c03fc4125716d6b534e35daf1156fcaec37ac8bc9e881d5ec1715632ff82b4dc4e3aabf48c5db6f3608ce2b8bb48bceae621b07f231254dd6f1f7b4584e7102969f523252a6839dca065"
public_key_metadata = "000102030405060708090a0b0c0d0e0f"
rollback_index = 1677974400
flags = 0
rollback_index_location = 0
release_string = "MaxLengthMaxLengthMaxLengthMaxLengthMaxLengthMa"
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
[[header.descriptors]]
type = "Property"
key = "foobar"
value = 'Invalid\x20UTF-8:\x20\xFF'
[[header.descriptors]]
type = "Hash"
image_size = 6
hash_algorithm = "sha256"
partition_name = "vbmeta_appended_hash"
salt = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
root_digest = "2454ef9d58f39d544eb2d92294131c0b3b1473a276418d0b1362693fdafa685b"
flags = 0
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567"
[footer]
version_major = 1
version_minor = 0
original_image_size = 6
vbmeta_offset = 4096
vbmeta_size = 2176
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef01234567"
@@ -0,0 +1 @@
foobar
@@ -0,0 +1,46 @@
image_size = 28672
[header]
required_libavb_version_major = 1
required_libavb_version_minor = 0
algorithm_type = "Sha256Rsa4096"
hash = "2bf01b15b31a729ca8053bc1303b0a4a638ee4819cc3106fd36ff996b703e249"
signature = "8c4f1daf4174f4347e623b57e314d4ac7717014c7476aa2f54ab3a71f3143fb366398dd2a165e092085ce34ed1412a7bf9ce8008f2061164edc3e42ef8b7f2a7e38ebc1f05059b0b829fa82cceef91aad67ffb843c79a9c6944f0f08d7523111a8c25dbfeab9aedb0cbd682f906d33a044be072fa22657024d4d056823310f38b65cad5e293b26142798a3a49c880eb4646bfbf612294f90792b652532d5e9bb5ca79699f968ec811a3aeb038d6033ac82a571f098659294db36666da4380c8a74eb7af8dfe32e5136301dad0e49d71e726d15c0cf659850eaa878133302835fcbe624122aee63b72a960d1c1432561afa55f07a9e57c6791c813131a7c0f6a0e1036036bc613e38cff14a1353b496416f06b1a3c224d8cb51218d251c8720624c936541519167e150dc2ac92091110fae8a08bac660b6ea4ef12f0c710bc7deada678a4e8d00b4803980e40c1899e9a5208b55d06bc519004e2bbfb15c9bec614c496de1f7fe103d9173067084f0170b9f6837f179689c3a4aa11b94e54aa3a49c76838ed5d31fece91a95fc089af72833e453c64c58a8270ec868ae7a4154717f3c98a940e51a197c9ebed4188ff1969faf4d9e59618713af785b538c4f41568dd4ba9b6dff68ab18222b4195938c1e500640218d1d4f6ef014c76deab8e18782e5fff6988ef6e630ff38dcc6709de7548cc1f56d817a0dbfe5df95e6a0f53"
public_key = "0000100080af29ebacee1fc0a795d91674aaeb4f55f4d36ba6f899ddb543be5d1cc878d45d776fe6211a330c0843322334b690dbbb9dc13fa47642abc75889dbba79c82a9dba19c64d4c099f36240775834e9d86013f433a1aaab0bed1f2212dc53cf2d967ded53209c1e5eb5b563d00d307c021b9d2d084e80cdd20a695225ce31e7fb407ccebdec5cda2eb44ed079df3edf31751e77ce43ae9681bc7f6cc4e58945366bb655550e07dd215eb516815e9457feef9301b772f92291fd5758782f20734b9ac75e7469f42ffd4055ae43a8301a37e56dfda4ede5e639fec57ab7268d8787190268952a27e6b8e3ffc503759af317da4a076fd36fe6499f15de27d81dca566f500838d13a442b01ecd1ffdb655ea2ef779bab13cd8eab649402cc1a3f81444ed24717a7c3fa65ac2b8d3625450aae15b75d3dc37e962f516799cb555063eeb262bb51f9af465fc992e07f3aa66917ce086abd9a60716f04fe155efe7901e4132a4dcdcdcf7db23e4eade6b3664bee87a7fd4c6b3247beeb0553263845d6547efda705f58cab911d9a98fb7cdd807bfdbf3dbf1f81ac3e837ba75ea5512be7246bfe768aa1edf23c469db87df980bbb939fd1f28948340882350e4c58f0450329f8088da6b908b4c4c78689eb18b3c06b683b12e518bde5cd57e7fb43792df94c38f643675e5c92b1c0a95f45256969a4bc6ea1db6682745d423cc676f97f57cf2d493d850bfba9362a858faa355e2bd0c9b1a46f6581c9c0751399788bd37fc5e597a402a3bc05bb5959677a48ce4aa9fa6ed5add36a7e11117152610b0e8f86692669825206eb5d297d7f77d114bc867736b6d6eb4309329569d78dfb910cb66132c5a3f415bd1ededfb61fd58c958db4c7a2c17578f537d24b5405e545d1618ae58be3525c9659d6baf2634255e7ca26fcfde1cbc4401e8ca062babede5f5b3f2ad5cbecd3d9332098f402666a9ef85643e3f4ab214a67a5ca91f31f810eab89df57d2f9ef35e9a5f46a99228af059cdd1f0685f1b9405beb0e44fe7f590b50feea1554e8bdaf7be7fd22e6f050a7751731cd7f8737b5acb92378da9d59068106da25b30882abad4629dff6ca9c4c0cf5337bf2d2a9e8c6182dcd6d7c681da01df6c11d0b335cf1f982156393c4e572df540cee2c53a2c146dec9644be4e81cce20df4cb136b3a08fad9a186be892d6653330d11cac1e96d308fd0d2896c8a141edfbdcac827cdf97ac581f493721c9aa1305667df312277c8ab6700c3a978e6f9d00b389faaeaa99b5e023b72c5f5ce885fa2ba816ffc735599fad1e3876d91a501efbbf1315f42a503ddd7a26a784a3f290a86f43dd580c03fc4125716d6b534e35daf1156fcaec37ac8bc9e881d5ec1715632ff82b4dc4e3aabf48c5db6f3608ce2b8bb48bceae621b07f231254dd6f1f7b4584e7102969f523252a6839dca065"
public_key_metadata = "000102030405060708090a0b0c0d0e0f"
rollback_index = 1677974400
flags = 0
rollback_index_location = 0
release_string = "MaxLengthMaxLengthMaxLengthMaxLengthMaxLengthMa"
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
[[header.descriptors]]
type = "Property"
key = "foobar"
value = 'Invalid\x20UTF-8:\x20\xFF'
[[header.descriptors]]
type = "HashTree"
dm_verity_version = 1
image_size = 8192
tree_offset = 8192
tree_size = 4096
data_block_size = 4096
hash_block_size = 4096
fec_num_roots = 2
fec_offset = 12288
fec_size = 8192
hash_algorithm = "sha256"
partition_name = "vbmeta_appended_hash_tree"
salt = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
root_digest = "bfb9f01152b3315096319d03697b4013d1674063418deec55920686ad13537a8"
flags = 0
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567"
[footer]
version_major = 1
version_minor = 0
original_image_size = 8192
vbmeta_offset = 20480
vbmeta_size = 2240
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef01234567"
File diff suppressed because one or more lines are too long
+61
View File
@@ -0,0 +1,61 @@
image_size = 4096
[header]
required_libavb_version_major = 1
required_libavb_version_minor = 0
algorithm_type = "Sha256Rsa4096"
hash = "892d9bd80a0b8b5e5daed1cdedfc252e3fe5573ffa812fa485c2eaec17632f3b"
signature = "752e7688a4de06eab55d37b56c4d7158e6e0477916f5217904edb85c946e4bfa4c0fb5d8aed0d1a0cc084c528cc303209075caa1067bdba7c0cc970d024c022ffe61feabc0eaf45755938d2b9b248ae07d369388b92c23c0f0f31a4a5ac1a4e92ebd2b24fff7953448a8f6827363fd3d32055977d708f72c9224b1a6616b59e79cdb20a4c17959081904d394fdfa27573f0b428fd7179ae3607c93e99a07badd6908e3f76893a346267126798503807d4a58904262afca7544349a796b04f658511724207c5a645c2976faec055f44c44ea21d63c7b2af21a8ffb7f4007249e8f81f8e2746e20965ced023b952bc48cb9c5ae11587b771f47383cfc1f5d021b1024128fc02a5a2f458d131a08975470d9373a9b70336d647943ed9e04bdaa0bbf19bfb087e0e6454cd178152eb826244bbfad20315dc20ae9a889ea99ac0045f438726bb882bf54c87fecfb3056aac7f2d649523b8cbcc8dd5a54917740e440ad7ffdaefde7e98b557798054d94653005213f377ce560fdec934435b2049b09e613906c12da713b97d31966238e7708a47501c0c91c6a9cfb4911fef54acacfa3e83f29a542d8a7d2c0f7b3b92815502ca9307e40c009415e575aab7f3b998a48d4a0a6d59adf3c8bd253620a9cbf53ea5fdb84bce5e21aca3d8d749406770725fc22f925e9a53f1c58eddc5e4dbd68d8537e7dda5035c20c54a5045434c6505"
public_key = "0000100080af29ebacee1fc0a795d91674aaeb4f55f4d36ba6f899ddb543be5d1cc878d45d776fe6211a330c0843322334b690dbbb9dc13fa47642abc75889dbba79c82a9dba19c64d4c099f36240775834e9d86013f433a1aaab0bed1f2212dc53cf2d967ded53209c1e5eb5b563d00d307c021b9d2d084e80cdd20a695225ce31e7fb407ccebdec5cda2eb44ed079df3edf31751e77ce43ae9681bc7f6cc4e58945366bb655550e07dd215eb516815e9457feef9301b772f92291fd5758782f20734b9ac75e7469f42ffd4055ae43a8301a37e56dfda4ede5e639fec57ab7268d8787190268952a27e6b8e3ffc503759af317da4a076fd36fe6499f15de27d81dca566f500838d13a442b01ecd1ffdb655ea2ef779bab13cd8eab649402cc1a3f81444ed24717a7c3fa65ac2b8d3625450aae15b75d3dc37e962f516799cb555063eeb262bb51f9af465fc992e07f3aa66917ce086abd9a60716f04fe155efe7901e4132a4dcdcdcf7db23e4eade6b3664bee87a7fd4c6b3247beeb0553263845d6547efda705f58cab911d9a98fb7cdd807bfdbf3dbf1f81ac3e837ba75ea5512be7246bfe768aa1edf23c469db87df980bbb939fd1f28948340882350e4c58f0450329f8088da6b908b4c4c78689eb18b3c06b683b12e518bde5cd57e7fb43792df94c38f643675e5c92b1c0a95f45256969a4bc6ea1db6682745d423cc676f97f57cf2d493d850bfba9362a858faa355e2bd0c9b1a46f6581c9c0751399788bd37fc5e597a402a3bc05bb5959677a48ce4aa9fa6ed5add36a7e11117152610b0e8f86692669825206eb5d297d7f77d114bc867736b6d6eb4309329569d78dfb910cb66132c5a3f415bd1ededfb61fd58c958db4c7a2c17578f537d24b5405e545d1618ae58be3525c9659d6baf2634255e7ca26fcfde1cbc4401e8ca062babede5f5b3f2ad5cbecd3d9332098f402666a9ef85643e3f4ab214a67a5ca91f31f810eab89df57d2f9ef35e9a5f46a99228af059cdd1f0685f1b9405beb0e44fe7f590b50feea1554e8bdaf7be7fd22e6f050a7751731cd7f8737b5acb92378da9d59068106da25b30882abad4629dff6ca9c4c0cf5337bf2d2a9e8c6182dcd6d7c681da01df6c11d0b335cf1f982156393c4e572df540cee2c53a2c146dec9644be4e81cce20df4cb136b3a08fad9a186be892d6653330d11cac1e96d308fd0d2896c8a141edfbdcac827cdf97ac581f493721c9aa1305667df312277c8ab6700c3a978e6f9d00b389faaeaa99b5e023b72c5f5ce885fa2ba816ffc735599fad1e3876d91a501efbbf1315f42a503ddd7a26a784a3f290a86f43dd580c03fc4125716d6b534e35daf1156fcaec37ac8bc9e881d5ec1715632ff82b4dc4e3aabf48c5db6f3608ce2b8bb48bceae621b07f231254dd6f1f7b4584e7102969f523252a6839dca065"
public_key_metadata = "000102030405060708090a0b0c0d0e0f"
rollback_index = 1677974400
flags = 0
rollback_index_location = 0
release_string = "MaxLengthMaxLengthMaxLengthMaxLengthMaxLengthMa"
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
[[header.descriptors]]
type = "Property"
key = "foobar"
value = 'Invalid\x20UTF-8:\x20\xFF'
[[header.descriptors]]
type = "HashTree"
dm_verity_version = 1
image_size = 4096
tree_offset = 0
tree_size = 2048
data_block_size = 4096
hash_block_size = 4096
fec_num_roots = 1
fec_offset = 2048
fec_size = 2048
hash_algorithm = "sha512"
partition_name = "hashtreed_partition"
salt = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
root_digest = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
flags = 0
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567"
[[header.descriptors]]
type = "Hash"
image_size = 6
hash_algorithm = "sha256"
partition_name = "hashed_partition"
salt = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
root_digest = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
flags = 0
reserved = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567"
[[header.descriptors]]
type = "KernelCmdline"
flags = 1
cmdline = "foobar"
[[header.descriptors]]
type = "ChainPartition"
rollback_index_location = 1
partition_name = "chained_partition"
public_key = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
flags = 4275878552
reserved = "76543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
+20
View File
@@ -0,0 +1,20 @@
type = "V0Through2"
kernel_addr = 19088743
ramdisk_addr = 2309737967
second_addr = 38177486
tags_addr = 324508639
page_size = 4096
os_version = 1985229328
name = "NameNameNameName"
cmdline = "CmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineC"
id = [
1122867,
1146447479,
2291772091,
3437096703,
4293844428,
3148519816,
2003195204,
857870592,
]
extra_cmdline = "ExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtr"
+1
View File
@@ -0,0 +1 @@
kernel data
@@ -0,0 +1 @@
ramdisk data
+1
View File
@@ -0,0 +1 @@
second data
+23
View File
@@ -0,0 +1,23 @@
type = "V0Through2"
kernel_addr = 19088743
ramdisk_addr = 2309737967
second_addr = 38177486
tags_addr = 324508639
page_size = 4096
os_version = 1985229328
name = "NameNameNameName"
cmdline = "CmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineC"
id = [
1122867,
1146447479,
2291772091,
3437096703,
4293844428,
3148519816,
2003195204,
857870592,
]
extra_cmdline = "ExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtr"
[v1_extra]
recovery_dtbo_offset = 81985529216486895
+1
View File
@@ -0,0 +1 @@
kernel data
@@ -0,0 +1 @@
ramdisk data
@@ -0,0 +1 @@
recovery_dtbo data
+1
View File
@@ -0,0 +1 @@
second data
+26
View File
@@ -0,0 +1,26 @@
type = "V0Through2"
kernel_addr = 19088743
ramdisk_addr = 2309737967
second_addr = 38177486
tags_addr = 324508639
page_size = 4096
os_version = 1985229328
name = "NameNameNameName"
cmdline = "CmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineC"
id = [
1122867,
1146447479,
2291772091,
3437096703,
4293844428,
3148519816,
2003195204,
857870592,
]
extra_cmdline = "ExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtraCmdlineExtr"
[v1_extra]
recovery_dtbo_offset = 81985529216486895
[v2_extra]
dtb_addr = 18364758544493064720
+1
View File
@@ -0,0 +1 @@
dtb data
+1
View File
@@ -0,0 +1 @@
kernel data
@@ -0,0 +1 @@
ramdisk data
@@ -0,0 +1 @@
recovery_dtbo data
+1
View File
@@ -0,0 +1 @@
second data
+9
View File
@@ -0,0 +1,9 @@
type = "V3Through4"
os_version = 19088743
reserved = [
1122867,
1146447479,
2291772091,
3437096703,
]
cmdline = "CmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmd"
+1
View File
@@ -0,0 +1 @@
kernel data
@@ -0,0 +1 @@
ramdisk data
+11
View File
@@ -0,0 +1,11 @@
type = "V3Through4"
os_version = 19088743
reserved = [
1122867,
1146447479,
2291772091,
3437096703,
]
cmdline = "CmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmd"
[v4_extra]
+1
View File
@@ -0,0 +1 @@
kernel data
@@ -0,0 +1 @@
ramdisk data
@@ -0,0 +1,11 @@
type = "V3Through4"
os_version = 19088743
reserved = [
1122867,
1146447479,
2291772091,
3437096703,
]
cmdline = "CmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmd"
[v4_extra]
@@ -0,0 +1 @@
kernel data
@@ -0,0 +1 @@
ramdisk data
@@ -0,0 +1,25 @@
image_size = 2048
[header]
required_libavb_version_major = 1
required_libavb_version_minor = 0
algorithm_type = "Sha256Rsa4096"
hash = "73b770b8199f1e709ce1cc37ee7f699faa99bf7e88c6eff429a6eda1feb6a1f9"
signature = "58c16f72718891a11f8d1ad92b8053c20d701ac3cc997ee7889d45c999ee91b05795d59d6feac44c316fe15f92dd06e9ae3bc570d8f414191bdaab7aae4c37822cb2f6d34fdcccc65a853d2dc72ecec311db000e27fbdc7bc767b80c1a0b4e88e193ce4f878ce15cac2a4b361294eddc193d36e148768b4c6b0de4001cf2e12fe2b8be84d5217a4c3ea12c0e040900a112f6e519363635a3b3108191b5858c90b62a4d459a6d66218978e002ed07478472ff34269ca575a53eb396e3e13972f115827c741b82e5988eb6f59f953e2ee5ad9fc6ab5d86754f282011e4a511c8ce4ed9d8b077a2b5f1a3e8943c222debaab02e02b6e461f98820a8f90a470ea222b625831e280a8d9bdf96bde33bf830bc2e39a62d52103f3b8988025da86f55a5f075ea362e45c0f92279216dfdf47ead516dfd5d9da546fd5a93e325a1706c0241f71a4d4cd5abec836fd2be0812b9716ce126498a67b0d6610adc94bb9cf9b137af57b1f413155428a4bc0318aff369cdcae69008f883b48d3ba6210c8f90cb473c8101c2381ad1ab2d2b17cdf3a8e65d118beb2283b140630e3f4800159d208c34abac3d9493e321948205e1a6df529a36ec47dac50b06a71aa2350f87738022ad8175c514dfb6acca04b9bac4a002ff587639cab9f1d94e68f13739d7d72b30010db4cf0e62ea796d6207e3b9264b9ec980076c6be7dbd9973e6278225be0"
public_key = "0000100080af29ebacee1fc0a795d91674aaeb4f55f4d36ba6f899ddb543be5d1cc878d45d776fe6211a330c0843322334b690dbbb9dc13fa47642abc75889dbba79c82a9dba19c64d4c099f36240775834e9d86013f433a1aaab0bed1f2212dc53cf2d967ded53209c1e5eb5b563d00d307c021b9d2d084e80cdd20a695225ce31e7fb407ccebdec5cda2eb44ed079df3edf31751e77ce43ae9681bc7f6cc4e58945366bb655550e07dd215eb516815e9457feef9301b772f92291fd5758782f20734b9ac75e7469f42ffd4055ae43a8301a37e56dfda4ede5e639fec57ab7268d8787190268952a27e6b8e3ffc503759af317da4a076fd36fe6499f15de27d81dca566f500838d13a442b01ecd1ffdb655ea2ef779bab13cd8eab649402cc1a3f81444ed24717a7c3fa65ac2b8d3625450aae15b75d3dc37e962f516799cb555063eeb262bb51f9af465fc992e07f3aa66917ce086abd9a60716f04fe155efe7901e4132a4dcdcdcf7db23e4eade6b3664bee87a7fd4c6b3247beeb0553263845d6547efda705f58cab911d9a98fb7cdd807bfdbf3dbf1f81ac3e837ba75ea5512be7246bfe768aa1edf23c469db87df980bbb939fd1f28948340882350e4c58f0450329f8088da6b908b4c4c78689eb18b3c06b683b12e518bde5cd57e7fb43792df94c38f643675e5c92b1c0a95f45256969a4bc6ea1db6682745d423cc676f97f57cf2d493d850bfba9362a858faa355e2bd0c9b1a46f6581c9c0751399788bd37fc5e597a402a3bc05bb5959677a48ce4aa9fa6ed5add36a7e11117152610b0e8f86692669825206eb5d297d7f77d114bc867736b6d6eb4309329569d78dfb910cb66132c5a3f415bd1ededfb61fd58c958db4c7a2c17578f537d24b5405e545d1618ae58be3525c9659d6baf2634255e7ca26fcfde1cbc4401e8ca062babede5f5b3f2ad5cbecd3d9332098f402666a9ef85643e3f4ab214a67a5ca91f31f810eab89df57d2f9ef35e9a5f46a99228af059cdd1f0685f1b9405beb0e44fe7f590b50feea1554e8bdaf7be7fd22e6f050a7751731cd7f8737b5acb92378da9d59068106da25b30882abad4629dff6ca9c4c0cf5337bf2d2a9e8c6182dcd6d7c681da01df6c11d0b335cf1f982156393c4e572df540cee2c53a2c146dec9644be4e81cce20df4cb136b3a08fad9a186be892d6653330d11cac1e96d308fd0d2896c8a141edfbdcac827cdf97ac581f493721c9aa1305667df312277c8ab6700c3a978e6f9d00b389faaeaa99b5e023b72c5f5ce885fa2ba816ffc735599fad1e3876d91a501efbbf1315f42a503ddd7a26a784a3f290a86f43dd580c03fc4125716d6b534e35daf1156fcaec37ac8bc9e881d5ec1715632ff82b4dc4e3aabf48c5db6f3608ce2b8bb48bceae621b07f231254dd6f1f7b4584e7102969f523252a6839dca065"
public_key_metadata = ""
rollback_index = 0
flags = 0
rollback_index_location = 0
release_string = "avbtool 1.2.0"
reserved = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
[[header.descriptors]]
type = "Hash"
image_size = 12288
hash_algorithm = "sha256"
partition_name = "boot"
salt = "6430306466303064"
root_digest = "abd5483e11e7940cb9bf387587a4a1659981a1b83962b7c1faf1b0cd6307d449"
flags = 0
reserved = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
@@ -0,0 +1,8 @@
type = "VendorV3Through4"
page_size = 4096
kernel_addr = 19088743
ramdisk_addr = 2309737967
cmdline = "CmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdl"
tags_addr = 4275878552
name = "NameNameNameName"
dtb_addr = 1985229328
+1
View File
@@ -0,0 +1 @@
dtb data
@@ -0,0 +1 @@
ramdisk data
+96
View File
@@ -0,0 +1,96 @@
type = "VendorV3Through4"
page_size = 2048
kernel_addr = 19088743
ramdisk_addr = 2309737967
cmdline = "CmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdlineCmdl"
tags_addr = 4275878552
name = "NameNameNameName"
dtb_addr = 1985229328
[[v4_extra.ramdisk_metas]]
ramdisk_type = 0
ramdisk_name = "NoneNoneNoneNoneNoneNoneNoneNone"
board_id = [
1122867,
1146447479,
2291772091,
3437096703,
4293844428,
3148519816,
2003195204,
857870592,
4491468,
290822621,
577153774,
863484927,
4290475827,
4004144674,
3717813521,
3431482368,
]
[[v4_extra.ramdisk_metas]]
ramdisk_type = 1
ramdisk_name = "PlatformPlatformPlatformPlatform"
board_id = [
1122867,
1146447479,
2291772091,
3437096703,
4293844428,
3148519816,
2003195204,
857870592,
4491468,
290822621,
577153774,
863484927,
4290475827,
4004144674,
3717813521,
3431482368,
]
[[v4_extra.ramdisk_metas]]
ramdisk_type = 2
ramdisk_name = "RecoveryRecoveryRecoveryRecovery"
board_id = [
1122867,
1146447479,
2291772091,
3437096703,
4293844428,
3148519816,
2003195204,
857870592,
4491468,
290822621,
577153774,
863484927,
4290475827,
4004144674,
3717813521,
3431482368,
]
[[v4_extra.ramdisk_metas]]
ramdisk_type = 3
ramdisk_name = "DlkmDlkmDlkmDlkmDlkmDlkmDlkmDlkm"
board_id = [
1122867,
1146447479,
2291772091,
3437096703,
4293844428,
3148519816,
2003195204,
857870592,
4491468,
290822621,
577153774,
863484927,
4290475827,
4004144674,
3717813521,
3431482368,
]
@@ -0,0 +1 @@
bootconfig data
+1
View File
@@ -0,0 +1 @@
dtb data
@@ -0,0 +1 @@
ramdisk 0 data
@@ -0,0 +1 @@
ramdisk 1 data
@@ -0,0 +1 @@
ramdisk 2 data
@@ -0,0 +1 @@
ramdisk 3 data
+35
View File
@@ -0,0 +1,35 @@
format = "None"
[[entries]]
path = "symlink"
data = "target"
file_type = "Symlink"
file_mode = "777"
nlink = 1
[[entries]]
path = "directory"
file_type = "Directory"
file_mode = "755"
nlink = 1
[[entries]]
path = "file"
file_type = "Regular"
file_mode = "644"
nlink = 1
[[entries]]
path = "reserved"
inode = 12345
file_type = "Reserved"
file_mode = "4777"
uid = 12345678
gid = 87654321
nlink = 2
mtime = 1700000000
dev_maj = 2222
dev_min = 3333
rdev_maj = 4444
rdev_min = 5555
crc32 = 4275878409
+1
View File
@@ -0,0 +1 @@
foobar
+1
View File
@@ -0,0 +1 @@
datadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadata
+80
View File
@@ -0,0 +1,80 @@
image_type = "Empty"
metadata_max_size = 65536
metadata_slot_count = 3
logical_block_size = 4096
[[slots]]
major_version = 10
minor_version = 2
flags = "VIRTUAL_AB_DEVICE"
[[slots.groups]]
name = "default"
flags = ""
partitions = []
[[slots.groups]]
name = "google_dynamic_partitions_a"
flags = ""
maximum_size = 8527020032
[[slots.groups.partitions]]
name = "system_a"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "system_dlkm_a"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "system_ext_a"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "product_a"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "vendor_a"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "vendor_dlkm_a"
attributes = "READONLY"
[[slots.groups]]
name = "google_dynamic_partitions_b"
flags = ""
maximum_size = 8527020032
[[slots.groups.partitions]]
name = "system_b"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "system_dlkm_b"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "system_ext_b"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "product_b"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "vendor_b"
attributes = "READONLY"
[[slots.groups.partitions]]
name = "vendor_dlkm_b"
attributes = "READONLY"
[[slots.block_devices]]
first_logical_sector = 2048
alignment = 1048576
alignment_offset = 0
size = 8531214336
partition_name = "super"
flags = ""
+1
View File
@@ -0,0 +1 @@
datadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadatadata
@@ -1 +0,0 @@
../../../../avbroot/tests/data/vbmeta_appended_hash.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/vbmeta_appended_hash_tree.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/vbmeta_root.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/boot_v0.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/boot_v1.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/boot_v2.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/boot_v3.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/boot_v4.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/boot_v4_vts.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/vendor_v3.img
@@ -1 +0,0 @@
../../../../avbroot/tests/data/vendor_v4.img
Binary file not shown.
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More