Compare commits

..

50 Commits

Author SHA1 Message Date
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
94 changed files with 2371 additions and 538 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# For git describe
fetch-depth: 0
@@ -85,7 +89,7 @@ jobs:
done
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run cargo-deny
uses: EmbarkStudios/cargo-deny-action@f2ba7abc2abebaf185c833c3961145a3c275caad # v2.0.13
uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15
+2 -2
View File
@@ -24,10 +24,10 @@ jobs:
echo "version=${version}" >> "${GITHUB_OUTPUT}"
- name: Check out repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Create release
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
tag_name: v${{ steps.get_version.outputs.version }}
name: Version ${{ steps.get_version.outputs.version }}
+60
View File
@@ -7,6 +7,41 @@
to update the actual links at the bottom of the file.
-->
### 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])
@@ -425,6 +460,10 @@ 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
[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
@@ -626,3 +665,24 @@ Behind-the-scenes changes:
[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
Generated
+455 -317
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.23.1"
version = "3.25.0"
license = "GPL-3.0-only"
edition = "2024"
repository = "https://github.com/chenxiaolong/avbroot"
+63 -5
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.
@@ -337,17 +345,19 @@ Only full payload binaries can be unpacked. Delta payload binaries from incremen
### 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,49 @@ 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 ota unpack -i <input OTA>
```
This subcommand unpacks the OTA metadata to `ota.toml` and the OTA files to the `ota_files` directory.
### Packing an OTA zip
```bash
avbroot ota pack -o <output OTA> -k <OTA private key>
```
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 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 ota repack -i <input OTA> -o <output OTA> -k <OTA private key>
```
This subcommand is logically equivalent to `avbroot ota unpack` followed by `avbroot ota 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 ota 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 `ota` subcommands show this information. This specific subcommand just does so without performing any other operation.)
+1 -1
View File
@@ -237,7 +237,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`.
+1 -1
View File
@@ -235,7 +235,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>`).
+4 -3
View File
@@ -27,9 +27,10 @@ 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.14.2"
lz4_flex = "0.12.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"
@@ -52,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"
+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();
}
+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),
+8 -8
View File
@@ -37,9 +37,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 +78,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 +86,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 +101,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()
@@ -1169,8 +1169,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>,
+22 -14
View File
@@ -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(())
}
@@ -286,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![];
+11 -10
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:?}"))?;
@@ -287,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.
+3 -4
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:?}"))?;
@@ -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.
+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;
+46 -18
View File
@@ -78,6 +78,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 +113,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;
}
@@ -1571,7 +1600,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 +1939,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 +2094,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() {
@@ -2164,7 +2192,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 +2205,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 +2220,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 +2231,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 +2242,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 +2253,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,7 +2272,7 @@ 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>,
@@ -2256,7 +2284,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 +2293,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 +2301,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 +2310,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 +2367,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 +2376,7 @@ pub struct PatchCli {
long,
value_name = "PARTITION",
hide = true,
help_heading = HEADING_OTHER
help_heading = HEADING_OTHER,
)]
pub boot_partition: Option<String>,
}
+9 -8
View File
@@ -52,14 +52,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:?}"))?;
@@ -235,10 +235,12 @@ fn pack_subcommand(
.with_context(|| format!("Failed to copy from replacement image: {name}"))?;
}
let (_, header, properties, _) = payload_writer
let (raw_writer, header, properties, _) = payload_writer
.finish()
.context("Failed to finalize payload")?;
raw_writer.into_inner().context("Failed to flush payload")?;
// Display the header information now that it has been finalized.
display_header(payload_cli, &header);
@@ -363,8 +365,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.
@@ -407,7 +408,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.
@@ -447,7 +448,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)]
@@ -457,7 +458,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,
}
+629
View File
@@ -0,0 +1,629 @@
// 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},
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::{
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(cli: &ZipCli, info: &OtaInfo) {
if !cli.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)?;
if !cli.no_output_files {
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}"))?;
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, &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_path = util::path_join(&cli.input_files, path)?;
let input_size = 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![];
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)?;
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, &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, &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, &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,
}
/// 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,
#[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);
}
}
+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),
+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::{
+103 -36
View File
@@ -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,7 +211,7 @@ 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..30500];
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..30700];
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;
@@ -437,6 +452,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>> {
@@ -451,7 +467,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 {
@@ -619,9 +640,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,
@@ -732,7 +751,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)
}
@@ -745,6 +764,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>> {
@@ -777,7 +797,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),
@@ -816,34 +841,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)
}
@@ -856,6 +896,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>> {
@@ -869,6 +910,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;
@@ -879,10 +926,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;
}
@@ -916,7 +965,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),
@@ -928,7 +982,7 @@ impl BootImagePatch for DsuPubKeyPatcher {
continue;
}
if self.patch_ramdisk(ramdisk, cancel_signal)? {
if self.patch_ramdisk(layout, ramdisk, cancel_signal)? {
return Ok(());
}
}
@@ -1013,6 +1067,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>> {
@@ -1037,7 +1092,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
@@ -1286,10 +1346,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();
@@ -1302,7 +1362,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
@@ -1325,7 +1391,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
@@ -1333,14 +1399,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<_>>>()?;
@@ -1367,8 +1434,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,
);
+5 -2
View File
@@ -54,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"] },
]
+1 -1
View File
@@ -20,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 = "179923431ff94148186d8b0a457636c8a60057d047d55a4fc4da3a162e73eeed"
[profile.pixel_v4_gki.hashes_seekable]
original = "f6615ae355eba38689d24aa535981d09175d4832e7c65c04cdd89aa95d21f09d"
patched = "9195ba963d9897af2f0821ff6051c1efe3fe130055b7936bedf2cd189998fd89"
patched = "b006858b78cddef0e3db1d77454cd945111ce15708121e3a483ff711abedec8b"
# 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 = "da3b9a4d6a58bb5aaadfc63006cb36b7af9c1c5945e81b38587c245bd5ad1f5a"
[profile.pixel_v3.hashes_seekable]
original = "e0e93ee11de56992f3a5f543858c1d11858b4deb76a91a4caf4fb0f3f34be855"
patched = "8730b398367179a0be092a67e4714f09c2267c656fe3e42ec1f8b43bb3a28634"
patched = "71682a4f31a5920784a0e2e38a6ee5fe3c65213436c7bf1ce347b121cd01a367"
# 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 = "132fb4d90e3c19a7a5ee0da2082767228255228ea5fb66811df1ccd5ff600bb5"
[profile.pixel_v2.hashes_seekable]
original = "57d4ac5ab7d362a593f3c02f3d2044b5400c10db0f732741a2d18e025d4231ec"
patched = "40d7674be14e19747e7ead118e0f4faf50880c638dfb4bfc0d0154b0e9202d3f"
patched = "b365eb32f463ac4ba13f7bed4e6535a68483379640dc6e842c591f194db042c2"
+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:?}"))
}
+6 -6
View File
@@ -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)?;
@@ -1203,7 +1203,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 +1336,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
+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
+1 -1
View File
@@ -13,7 +13,7 @@ rust-version.workspace = true
anyhow = "1.0.75"
clap = { version = "4.4.1", features = ["derive"] }
regex = { version = "1.9.4", default-features = false, features = ["perf", "std"] }
toml_edit = "0.23.3"
toml_edit = "0.25.1"
[lints]
workspace = true
+192
View File
@@ -0,0 +1,192 @@
// SPDX-FileCopyrightText: 2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
env,
fs::{self},
path::{Path, PathBuf},
process::Command,
};
use anyhow::{Context, Result, bail};
use crate::WORKSPACE_DIR;
#[cfg(not(target_os = "windows"))]
const EXE_SUFFIX: &str = "";
#[cfg(target_os = "windows")]
const EXE_SUFFIX: &str = ".exe";
fn run(mut command: Command) -> Result<()> {
let status = command
.status()
.with_context(|| format!("Failed to spawn: {command:?}"))?;
if !status.success() {
bail!("Command failed with status: {status}: {command:?}");
}
Ok(())
}
fn build_avbroot() -> Result<PathBuf> {
let mut command = Command::new(env!("CARGO"));
command.arg("build");
command.current_dir(WORKSPACE_DIR);
run(command)?;
let mut executable = Path::new(WORKSPACE_DIR).to_owned();
if let Some(target_dir) = env::var_os("CARGO_TARGET_DIR") {
executable.push(target_dir);
} else {
executable.push("target");
}
executable.push("debug");
executable.push(format!("avbroot{EXE_SUFFIX}"));
Ok(executable)
}
fn generate_avb_image(avbroot: &Path, input_dir: &Path, output_file: &Path) -> Result<()> {
let mut command = Command::new(avbroot);
command.arg("avb");
command.arg("pack");
command.arg("-q");
command.arg("-o");
command.arg(output_file);
command.current_dir(input_dir);
run(command)
}
fn generate_boot_image(avbroot: &Path, input_dir: &Path, output_file: &Path) -> Result<()> {
let vts_signature_dir = input_dir.join("vts_signature");
if vts_signature_dir.exists() {
let mut command = Command::new(avbroot);
command.arg("avb");
command.arg("pack");
command.arg("-q");
command.arg("-o");
command.arg("../vts_signature.img");
command.current_dir(vts_signature_dir);
run(command)?;
}
let mut command = Command::new(avbroot);
command.arg("boot");
command.arg("pack");
command.arg("-q");
command.arg("-o");
command.arg(output_file);
command.current_dir(input_dir);
run(command)
}
fn generate_cpio_image(avbroot: &Path, input_dir: &Path, output_file: &Path) -> Result<()> {
let mut command = Command::new(avbroot);
command.arg("cpio");
command.arg("pack");
command.arg("-q");
command.arg("-o");
command.arg(output_file);
command.current_dir(input_dir);
run(command)
}
fn generate_fec_image(avbroot: &Path, input_dir: &Path, output_file: &Path) -> Result<()> {
let mut command = Command::new(avbroot);
command.arg("fec");
command.arg("generate");
command.arg("-i");
command.arg("input.img");
command.arg("-f");
command.arg(output_file);
command.current_dir(input_dir);
run(command)
}
fn generate_lp_image(avbroot: &Path, input_dir: &Path, output_file: &Path) -> Result<()> {
let mut command = Command::new(avbroot);
command.arg("lp");
command.arg("pack");
command.arg("-q");
command.arg("-o");
command.arg(output_file);
command.current_dir(input_dir);
run(command)
}
fn generate_sparse_image(avbroot: &Path, input_dir: &Path, output_file: &Path) -> Result<()> {
let mut command = Command::new(avbroot);
command.arg("sparse");
command.arg("pack");
command.arg("-q");
command.arg("-i");
command.arg("input.img");
command.arg("-o");
command.arg(output_file);
command.current_dir(input_dir);
run(command)
}
type Generator = fn(&Path, &Path, &Path) -> Result<()>;
fn generate_corpus(
avbroot: &Path,
input_dir: &Path,
output_dir: &Path,
generator: Generator,
suffix: &str,
) -> Result<()> {
fs::create_dir_all(output_dir)
.with_context(|| format!("Failed to create directory: {output_dir:?}"))?;
let parent = fs::read_dir(input_dir)
.with_context(|| format!("Failed to open directory: {input_dir:?}"))?;
for entry in parent {
let entry = entry.with_context(|| format!("Failed to list directory: {input_dir:?}"))?;
let entry_input_dir = entry.path();
let mut entry_output_file = output_dir.join(entry.file_name());
entry_output_file.as_mut_os_string().push(suffix);
println!("{entry_input_dir:?} -> {entry_output_file:?}");
generator(avbroot, &entry_input_dir, &entry_output_file)?;
}
Ok(())
}
pub fn fuzz_corpus_subcommand() -> Result<()> {
let fuzz_dir = Path::new(WORKSPACE_DIR).join("fuzz");
let corpus = fuzz_dir.join("corpus");
let hfuzz_workspace = fuzz_dir.join("hfuzz_workspace");
let avbroot = build_avbroot()?;
let items: [(Generator, _, _); _] = [
(generate_avb_image, "avb", ".img"),
(generate_boot_image, "bootimage", ".img"),
(generate_cpio_image, "cpio", ".cpio"),
(generate_fec_image, "fec", ".fec"),
(generate_lp_image, "lp", ".img"),
(generate_sparse_image, "sparse", ".img"),
];
for (generator, name, suffix) in items {
let input_dir = corpus.join(name);
let mut output_dir = hfuzz_workspace.join(name);
output_dir.push("input");
generate_corpus(&avbroot, &input_dir, &output_dir, generator, suffix)?;
}
Ok(())
}
+5 -1
View File
@@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2023 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2026 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
mod changelog;
mod fuzz_corpus;
mod version;
use anyhow::Result;
@@ -16,6 +17,7 @@ fn main() -> Result<()> {
match cli.command {
Command::SetVersion(c) => version::set_version_subcommand(&c),
Command::FuzzCorpus => fuzz_corpus::fuzz_corpus_subcommand(),
Command::UpdateChangelog => changelog::update_changelog_subcommand(),
}
}
@@ -23,6 +25,8 @@ fn main() -> Result<()> {
#[derive(Debug, Subcommand)]
pub enum Command {
SetVersion(SetVersionCli),
/// Generate initial fuzzing corpus.
FuzzCorpus,
/// Update links in CHANGELOG.md.
UpdateChangelog,
}