Compare commits

...

32 Commits

Author SHA1 Message Date
Andrew Gunnerson bde8dbfbcd Version 2.3.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-27 18:10:51 -04:00
Andrew Gunnerson fbafbafe90 CHANGELOG.md: Fix typo: Reword -> Rework
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-27 18:03:51 -04:00
Andrew Gunnerson a14cab71e5 CHANGELOG.md: Add entry for PR #197
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-27 18:01:17 -04:00
Andrew Gunnerson c2f4297bb7 Merge pull request #197 from chenxiaolong/deps
Update dependencies
2023-10-27 17:59:15 -04:00
Andrew Gunnerson 9d63c9759d Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-27 17:44:25 -04:00
Andrew Gunnerson 73b893f3ed changelog.txt: Add entry for PR #196
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-27 17:42:00 -04:00
Andrew Gunnerson fd7bbf5751 Merge pull request #196 from chenxiaolong/legacy
ota: Add support for legacy OTA metadata
2023-10-27 17:36:03 -04:00
Andrew Gunnerson 409867a8e5 ota: Add support for legacy OTA metadata
Android 11 OTAs use the same `payload.bin` format, but lack the
`metadata.pb` protobuf representation of the OTA metadata. This commit
adds support for parsing the legacy plain-text `metadata` format. Like
before, the output files will still contain both the legacy and
protobuf representations.

Note that the legacy format allowed OEMs to specify arbitrary key/value
pairs. These will be discarded during patching because they cannot be
represented in the protobuf format, which is used in avbroot's internal
representation.

Issue: #195

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-27 15:15:13 -04:00
Andrew Gunnerson f96a2887df README.md: Add instructions for reverting to stock firmware
Fixes: #194

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-17 15:47:27 -04:00
Andrew Gunnerson 8140e25620 Merge pull request #193 from pascallj/patch-1
Update README.md to use new options
2023-10-15 15:55:52 -04:00
Pascal Roeleven ce05477ab1 Update README.md to use new options
During the migration to avbroot 2.0, these options have been renamed. Reflect this in the Readme.

Signed-off-by: Pascal Roeleven <dev@pascalroeleven.nl>
2023-10-15 21:36:35 +02:00
Andrew Gunnerson 1c4800c0cb CHANGELOG.md: Add entry for PR #191
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-14 17:05:53 -04:00
Andrew Gunnerson d402fdf5f1 Merge pull request #191 from chenxiaolong/sign
boot: Avoid setting signature algorithm when image is indirectly signed
2023-10-14 17:04:29 -04:00
Andrew Gunnerson 4930527598 boot: Avoid setting signature algorithm when image is indirectly signed
Previously, the AVB `algorithm_type` field was unconditionally being set
to a value that is compatible with the AVB private key. However, for
indirectly-signed boot images, the value should be set to `None`. Pixel
bootloaders accept the incorrect value, but other devices' bootloaders
might not.

Issue: #186

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-13 18:46:52 -04:00
Andrew Gunnerson 9b970c6d25 CHANGELOG.md: Add entry for PR #190
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-13 18:04:44 -04:00
Andrew Gunnerson e7fb4004cd Merge pull request #190 from chenxiaolong/sha1
avb: Allow computing insecure SHA1 hashes for verification
2023-10-13 18:03:04 -04:00
Andrew Gunnerson 2773dcddcf avb: Allow computing insecure SHA1 hashes for verification
Some partitions, like system_ext in the Pixel Experience build for
`avicii`, use SHA1 for the dm-verity hash tree. This is technically
valid, so allow the `avb verify` command to compute these hashes. SHA1
will still be rejected when creating AVB images.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-13 17:33:45 -04:00
Andrew Gunnerson 36765290fd CHANGELOG.md: Add entry for PR #189
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-13 17:32:33 -04:00
Andrew Gunnerson 6d8268e472 Merge pull request #189 from chenxiaolong/reopen
Move reopen functionality to a new trait
2023-10-13 17:31:15 -04:00
Andrew Gunnerson 2e8e86766b Move reopen functionality to a new trait
This is still not the ideal API, but it makes the code quite a bit more
readable since we no longer have to pass around closures everywhere that
multithreaded reads and writes to the same file are needed.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-11 22:09:45 -04:00
Andrew Gunnerson 8ae1c54c13 CHANGELOG.md: Add entry for PR #188
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-11 20:47:03 -04:00
Andrew Gunnerson 5212d4de51 Merge pull request #188 from chenxiaolong/avb_check
Add more checks to ensure AVB is actually providing security
2023-10-11 20:43:07 -04:00
Andrew Gunnerson 61bb9d6a81 Add more checks to ensure AVB is actually providing security
avbroot currently has a check to ensure that AVB signature verification
isn't completely disabled by non-zero vbmeta header flags. This is done
to ensure that the user isn't given a false sense of security when the
OS is built in an insecure way. This commit makes a few changes better
reject insecure OTAs.

The first change is extending the non-zero flags check to all vbmeta
images. Previously, only vbmeta images that needed to be updated as a
result of patching/re-signing were checked.

The second change is ensuring that there are no critical partitions
present in `payload.bin`, but missing from the vbmeta descriptors. For
example, if the vbmeta descriptor for `system.img` is missing, the
patching process will fail with a fatal error that cannot be bypassed.

Unfortunately, it's not feasible to check every partition because there
is no known device where every partition is protected by AVB. Google's
Tensor-based devices are the best and only have a single partition not
protected by AVB: `modem`. Qualcomm-based devices have many partitions
not protected by AVB. OnePlus devices have many partitions that are
checked directly (against the appended header) and thus, aren't listed
in any vbmeta image.

Due to this, only partitions that AOSP understands are checked. OEM-
specific partitions (which, security-wise, may be just as important) are
not checked.

The third change is ensuring that there's only a single root of trust
among the vbmeta images. This way, it's impossible to have, for example,
a `vbmeta_unused` image that contains all the descriptors and an empty
`vbmeta` image that's actually read by the bootloader.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-11 20:28:32 -04:00
Andrew Gunnerson 6d86fbf8a0 CHANGELOG.md: Add entry for PR #184
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-08 20:56:35 -04:00
Andrew Gunnerson 621ccc254c Merge pull request #184 from chenxiaolong/patch
Group `ota patch --help` options into sections
2023-10-08 20:55:19 -04:00
Andrew Gunnerson f41ef844e0 Group ota patch --help options into sections
There are a ton of options and simply displaying them in a giant list is
not very readable.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-07 17:04:38 -04:00
Andrew Gunnerson 42f8769e48 CHANGELOG.md: Add entry for PR #183
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-07 16:38:46 -04:00
Andrew Gunnerson d2187676d1 Merge pull request #183 from chenxiaolong/avb
avb.rs: Fix missing help text for --ignore-invalid
2023-10-07 16:36:52 -04:00
Andrew Gunnerson 3de5194c46 avb.rs: Fix missing help text for --ignore-invalid
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-06 21:05:52 -04:00
Andrew Gunnerson e0c1d4ad3a CHANGELOG.md: Add entry for PR #182
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-04 17:53:39 -04:00
Andrew Gunnerson c94af49009 Merge pull request #182 from chenxiaolong/payload
payload: Bump maximum manifest size to 4 MiB
2023-10-04 17:51:51 -04:00
Andrew Gunnerson 5ed7dd7dca payload: Bump maximum manifest size to 4 MiB
The Android 13 -> 14 incremental OTA has a 1.7 MiB manifest.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-04 15:49:20 -04:00
22 changed files with 733 additions and 419 deletions
+26
View File
@@ -7,6 +7,22 @@
to update the actual links at the bottom of the file.
-->
### Version 2.3.0
* Fix missing `--help` text for `avbroot avb unpack`'s `--ignore-invalid` option ([PR #183])
* Group `avbroot ota patch --help` output into more readable sections ([PR #184])
* Add more checks to ensure that the OTA has a secure AVB setup ([PR #188])
* OTAs with blatantly insecure or missing AVB configuration are now more likely to be rejected by avbroot to avoid providing a false sense of security.
* Allow `avbroot avb verify` and `avbroot ota verify` to work for dm-verity partitions that use insecure SHA1 hashes ([PR #190])
* Add support for legacy Android 11 OTAs ([Discussion #195], [PR #196])
Behind-the-scenes changes:
* Bump maximum payload manifest size to 4 MiB ([PR #182])
* Rework file handle reopen functionality to use traits instead of callbacks ([PR #189])
* Don't set signature algorithm field for indirectly signed boot images ([PR #191])
* Update dependencies ([PR #197])
### Version 2.2.0
It's Android 14 release day! All versions of avbroot, including the old Python version, are compatible with Android 14 OTAs.
@@ -76,6 +92,7 @@ Behind-the-scenes changes:
* Initial Rust release. The old Python implementation can be found in the `python` branch. ([PR #130])
<!-- Do not manually edit the lines below. Use `cargo xtask update-changelog` to regenerate. -->
[Discussion #195]: https://github.com/chenxiaolong/avbroot/discussions/195
[Issue #138]: https://github.com/chenxiaolong/avbroot/issues/138
[Issue #144]: https://github.com/chenxiaolong/avbroot/issues/144
[Issue #145]: https://github.com/chenxiaolong/avbroot/issues/145
@@ -117,3 +134,12 @@ Behind-the-scenes changes:
[PR #176]: https://github.com/chenxiaolong/avbroot/pull/176
[PR #178]: https://github.com/chenxiaolong/avbroot/pull/178
[PR #181]: https://github.com/chenxiaolong/avbroot/pull/181
[PR #182]: https://github.com/chenxiaolong/avbroot/pull/182
[PR #183]: https://github.com/chenxiaolong/avbroot/pull/183
[PR #184]: https://github.com/chenxiaolong/avbroot/pull/184
[PR #188]: https://github.com/chenxiaolong/avbroot/pull/188
[PR #189]: https://github.com/chenxiaolong/avbroot/pull/189
[PR #190]: https://github.com/chenxiaolong/avbroot/pull/190
[PR #191]: https://github.com/chenxiaolong/avbroot/pull/191
[PR #196]: https://github.com/chenxiaolong/avbroot/pull/196
[PR #197]: https://github.com/chenxiaolong/avbroot/pull/197
Generated
+101 -114
View File
@@ -21,9 +21,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.1"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
@@ -90,9 +90,9 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "arbitrary"
version = "1.3.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e"
checksum = "a2e1373abdaa212b704512ec2bd8b26bd0b7d5c3f70117411a5d9a451383c859"
[[package]]
name = "assert_matches"
@@ -121,7 +121,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "avbroot"
version = "2.2.0"
version = "2.3.0"
dependencies = [
"anyhow",
"assert_matches",
@@ -169,9 +169,9 @@ dependencies = [
[[package]]
name = "base64"
version = "0.21.4"
version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
[[package]]
name = "base64ct"
@@ -193,9 +193,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "block-buffer"
@@ -217,9 +217,9 @@ dependencies = [
[[package]]
name = "bstr"
version = "1.6.2"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019"
dependencies = [
"memchr",
"regex-automata",
@@ -228,9 +228,9 @@ dependencies = [
[[package]]
name = "byteorder"
version = "1.4.3"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
@@ -334,9 +334,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.6"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
dependencies = [
"clap_builder",
"clap_derive",
@@ -344,9 +344,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.6"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
dependencies = [
"anstream",
"anstyle",
@@ -356,30 +356,30 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.4.3"
version = "4.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ae8ba90b9d8b007efe66e55e48fb936272f5ca00349b5b0e89877520d35ea7"
checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.4.2"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
name = "clap_lex"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "cms"
@@ -423,9 +423,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpufeatures"
version = "0.2.9"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
dependencies = [
"libc",
]
@@ -548,7 +548,7 @@ checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
@@ -565,7 +565,7 @@ dependencies = [
[[package]]
name = "e2e"
version = "2.2.0"
version = "2.3.0"
dependencies = [
"anyhow",
"attohttpc",
@@ -594,25 +594,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480"
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "evalexpr"
version = "6.6.0"
@@ -639,9 +628,9 @@ checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779"
[[package]]
name = "flate2"
version = "1.0.27"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -690,7 +679,7 @@ dependencies = [
[[package]]
name = "fuzz"
version = "2.2.0"
version = "2.3.0"
dependencies = [
"avbroot",
"honggfuzz",
@@ -742,9 +731,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.14.1"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
[[package]]
name = "heck"
@@ -862,9 +851,9 @@ checksum = "bffb4def18c48926ccac55c1223e02865ce1a821751a95920448662696e7472c"
[[package]]
name = "ipnet"
version = "2.8.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "itertools"
@@ -892,21 +881,21 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.148"
version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "libm"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "linux-raw-sys"
version = "0.4.8"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "log"
@@ -934,7 +923,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex-syntax 0.6.29",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
@@ -1015,7 +1004,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
@@ -1057,7 +1046,7 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"cfg-if",
"libc",
]
@@ -1103,9 +1092,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
"libm",
@@ -1123,7 +1112,7 @@ version = "0.10.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"cfg-if",
"foreign-types",
"libc",
@@ -1140,7 +1129,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
@@ -1226,7 +1215,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
@@ -1295,14 +1284,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
dependencies = [
"proc-macro2",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
name = "proc-macro2"
version = "1.0.67"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
"unicode-ident",
]
@@ -1334,7 +1323,7 @@ dependencies = [
"prost",
"prost-types",
"regex",
"syn 2.0.37",
"syn 2.0.38",
"tempfile",
"which",
]
@@ -1349,7 +1338,7 @@ dependencies = [
"itertools",
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
@@ -1462,34 +1451,34 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.3.5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.9.6"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax 0.7.5",
"regex-syntax 0.8.2",
]
[[package]]
name = "regex-automata"
version = "0.3.9"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.5",
"regex-syntax 0.8.2",
]
[[package]]
@@ -1500,15 +1489,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.5"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "ring"
version = "0.17.0"
version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb9d44f9bf6b635117787f72416783eb7e4227aaf255e5ce739563d817176a7e"
checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
dependencies = [
"cc",
"getrandom",
@@ -1531,16 +1520,14 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.9.2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8"
checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d"
dependencies = [
"byteorder",
"const-oid",
"digest",
"num-bigint-dig",
"num-integer",
"num-iter",
"num-traits",
"pkcs1",
"pkcs8",
@@ -1574,11 +1561,11 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.15"
version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"errno",
"itoa",
"libc",
@@ -1656,35 +1643,35 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.19"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
[[package]]
name = "serde"
version = "1.0.188"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.188"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
name = "serde_spanned"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
dependencies = [
"serde",
]
@@ -1786,9 +1773,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.37"
version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
"proc-macro2",
"quote",
@@ -1797,9 +1784,9 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.8.0"
version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [
"cfg-if",
"fastrand",
@@ -1810,22 +1797,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.49"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.49"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.38",
]
[[package]]
@@ -1845,18 +1832,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml_datetime"
version = "0.6.3"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.20.2"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [
"indexmap",
"serde",
@@ -1939,9 +1926,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.4.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
dependencies = [
"getrandom",
]
@@ -2085,9 +2072,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "winnow"
version = "0.5.15"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c"
dependencies = [
"memchr",
]
@@ -2098,7 +2085,7 @@ version = "0.36.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357bb8e2932df531f83b052264b050b81ba0df90ee5a59b2d1d3949f344f81e5"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"windows-sys",
]
@@ -2117,7 +2104,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "2.2.0"
version = "2.3.0"
dependencies = [
"anyhow",
"clap",
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "2.2.0"
version = "2.3.0"
license = "GPL-3.0-only"
edition = "2021"
repository = "https://github.com/chenxiaolong/avbroot"
+18 -5
View File
@@ -16,11 +16,10 @@ avbroot applies two patches to the boot images:
## Warnings and Caveats
* The device must use (non-legacy-SAR) A/B partitioning. This is the case on newer Pixel and OnePlus devices. To check if a device uses this partitioning sceme, open the OTA zip file and check that:
* The device must use modern (non-legacy-SAR) A/B partitioning. This is the case on newer Pixel and OnePlus devices. To check if a device uses this partitioning scheme, open the OTA zip file and check that:
* `payload.bin` exists
* `META-INF/com/android/metadata.pb` exists
* `META-INF/com/android/metadata` contains the line: `ota-type=AB`
* `META-INF/com/android/metadata` (Android 11) or `META-INF/com/android/metadata.pb` (Android 12+) exists
* The device must support using a custom public key for the bootloader's root of trust. This is normally done via the `fastboot flash avb_custom_key` command. All Pixel devices with unlockable bootloaders support this, as well as most OnePlus devices. Other devices may support it as well, but there's no easy way to check without just trying it.
@@ -79,8 +78,8 @@ If you lose your AVB or OTA signing key, you will no longer be able to sign new
```bash
avbroot ota patch \
--input /path/to/ota.zip \
--privkey-avb /path/to/avb.key \
--privkey-ota /path/to/ota.key \
--key-avb /path/to/avb.key \
--key-ota /path/to/ota.key \
--cert-ota /path/to/ota.crt \
--magisk /path/to/magisk.apk
```
@@ -139,6 +138,20 @@ To update Android or Magisk:
4. Reboot.
## Reverting to stock firmware
To stop using avbroot and revert to the stock firmware:
1. Unlock the bootloader. This will trigger a data wipe.
2. Erase the custom AVB public key.
```bash
fastboot erase avb_custom_key
```
3. Flash the stock firmware.
## avbroot Magisk modules
avbroot's Magisk modules can be found on the [releases page](https://github.com/chenxiaolong/avbroot/releases) or they can be built locally by running:
+1 -2
View File
@@ -764,8 +764,6 @@ pub fn patch_boot(
patcher.patch(&mut boot_image, cancel_signal)?;
}
header.set_algo_for_key(key)?;
let mut descriptor_iter = header.descriptors.iter_mut().filter_map(|d| {
if let Descriptor::Hash(h) = d {
Some(h)
@@ -794,6 +792,7 @@ pub fn patch_boot(
}
if !header.public_key.is_empty() {
header.set_algo_for_key(key)?;
header.sign(key)?;
}
+12 -25
View File
@@ -30,7 +30,7 @@ use crate::{
self, AlgorithmType, AppendedDescriptorMut, AppendedDescriptorRef, Descriptor, Footer,
Header,
},
stream::{self, PSeekFile},
stream::{self, PSeekFile, Reopen},
util,
};
@@ -145,7 +145,7 @@ fn write_raw_and_verify(
let raw_file = write_raw(path, reader, copy_size, cancel_signal)?;
let result = verify_and_repair(None, raw_file.reopen(), descriptor, true, cancel_signal);
let result = verify_and_repair(None, raw_file.reopen()?, descriptor, true, cancel_signal);
// Chop off the old hash tree and FEC data.
raw_file.set_len(f.original_image_size)?;
@@ -180,12 +180,8 @@ fn write_raw_and_update(
match info.header.appended_descriptor_mut()? {
AppendedDescriptorMut::HashTree(d) => {
d.image_size = image_size;
d.update(
|| Ok(Box::new(raw_file.reopen())),
|| Ok(Box::new(raw_file.reopen())),
cancel_signal,
)
.context("Failed to update hash tree descriptor")?;
d.update(&raw_file, &raw_file, cancel_signal)
.context("Failed to update hash tree descriptor")?;
}
AppendedDescriptorMut::Hash(d) => {
d.image_size = image_size;
@@ -384,7 +380,7 @@ fn verify_and_repair(
AppendedDescriptorRef::HashTree(d) => {
status!("Verifying hash tree descriptor{suffix}");
match d.verify(|| Ok(Box::new(file.reopen())), cancel_signal) {
match d.verify(&file, cancel_signal) {
Err(
e @ avb::Error::InvalidRootDigest { .. }
| e @ avb::Error::InvalidHashTree { .. },
@@ -392,17 +388,12 @@ fn verify_and_repair(
warning!("Failed to verify hash tree descriptor{suffix}: {e}");
warning!("Attempting to repair using FEC data{suffix}");
d.repair(
|| Ok(Box::new(file.reopen())),
|| Ok(Box::new(file.reopen())),
cancel_signal,
)
.with_context(|| format!("Failed to repair data{suffix}"))?;
d.repair(&file, &file, cancel_signal)
.with_context(|| format!("Failed to repair data{suffix}"))?;
d.verify(|| Ok(Box::new(file.reopen())), cancel_signal)
.map(|_| {
status!("Successfully repaired data{suffix}");
})
d.verify(&file, cancel_signal).map(|_| {
status!("Successfully repaired data{suffix}");
})
}
ret => ret,
}
@@ -518,11 +509,7 @@ fn repack_subcommand(cli: &RepackCli, cancel_signal: &AtomicBool) -> Result<()>
// Write new hash tree and FEC data instead of copying the original.
// THere could have been errors in the original FEC data itself.
if let AppendedDescriptorMut::HashTree(d) = info.header.appended_descriptor_mut()? {
d.update(
|| Ok(Box::new(file.reopen())),
|| Ok(Box::new(file.reopen())),
cancel_signal,
)?;
d.update(&file, &file, cancel_signal)?;
}
file
@@ -664,7 +651,7 @@ struct UnpackCli {
#[arg(long, value_name = "FILE", value_parser, default_value = "raw.img")]
output_raw: PathBuf,
// Ignore invalid digests or FEC data.
/// Ignore invalid digests or FEC data.
#[arg(long)]
ignore_invalid: bool,
+4 -8
View File
@@ -53,7 +53,7 @@ fn write_fec(path: &Path, fec: &FecImage) -> Result<()> {
fn generate_subcommand(cli: &GenerateCli, cancel_signal: &AtomicBool) -> Result<()> {
let input = open_input(&cli.input, false)?;
let fec = FecImage::generate(|| Ok(Box::new(input.reopen())), cli.parity, cancel_signal)
let fec = FecImage::generate(&input, cli.parity, cancel_signal)
.context("Failed to generate FEC data")?;
write_fec(&cli.fec, &fec)?;
@@ -65,7 +65,7 @@ fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<()>
let input = open_input(&cli.input, false)?;
let fec = read_fec(&cli.fec)?;
fec.verify(|| Ok(Box::new(input.reopen())), cancel_signal)
fec.verify(&input, cancel_signal)
.context("Failed to verify data")?;
Ok(())
@@ -78,12 +78,8 @@ fn repair_subcommand(cli: &RepairCli, cancel_signal: &AtomicBool) -> Result<()>
// The separate buffered readers and writers are safe because the function
// guarantees that every thread touches disjoint offsets and every offset is
// read and written at most once.
fec.repair(
|| Ok(Box::new(input.reopen())),
|| Ok(Box::new(input.reopen())),
cancel_signal,
)
.context("Failed to repair file")?;
fec.repair(&input, &input, cancel_signal)
.context("Failed to repair file")?;
Ok(())
}
+297 -127
View File
@@ -9,7 +9,7 @@ use std::{
ffi::{OsStr, OsString},
fmt::Display,
fs::{self, File},
io::{self, BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write},
io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write},
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Mutex},
time::Instant,
@@ -43,8 +43,8 @@ use crate::{
build::tools::releasetools::OtaMetadata, chromeos_update_engine::DeltaArchiveManifest,
},
stream::{
self, CountingWriter, FromReader, HolePunchingWriter, PSeekFile, ReadSeek, SectionReader,
ToWriter,
self, CountingWriter, FromReader, HolePunchingWriter, PSeekFile, ReadSeek, ReadSeekReopen,
Reopen, SectionReader, ToWriter,
},
util,
};
@@ -59,12 +59,19 @@ static PARTITION_PRIORITIES: phf::Map<&'static str, &[&'static str]> = phf_map!
};
fn joined(into_iter: impl IntoIterator<Item = impl Display>) -> String {
let items = into_iter
.into_iter()
.map(|i| i.to_string())
.collect::<Vec<_>>();
use std::fmt::Write;
items.join(", ")
let mut result = String::new();
for (i, item) in into_iter.into_iter().enumerate() {
if i > 0 {
result.push_str(", ");
}
write!(result, "{item}").expect("Failed to allocate");
}
result
}
fn sorted<T: Ord>(iter: impl Iterator<Item = T>) -> Vec<T> {
@@ -142,7 +149,7 @@ pub fn get_required_images(
/// in `external_images`, the real file on the filesystem is opened. Otherwise,
/// the image is extracted from the payload.
fn open_input_streams(
open_payload: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
payload: &(dyn ReadSeekReopen + Sync),
required_images: &HashMap<String, String>,
external_images: &HashMap<String, PathBuf>,
header: &PayloadHeader,
@@ -167,9 +174,8 @@ fn open_input_streams(
} else {
status!("Extracting from original payload: {name}");
let stream =
payload::extract_image_to_memory(&open_payload, header, name, cancel_signal)
.with_context(|| format!("Failed to extract from original payload: {name}"))?;
let stream = payload::extract_image_to_memory(payload, header, name, cancel_signal)
.with_context(|| format!("Failed to extract from original payload: {name}"))?;
input_streams.insert(name.clone(), Box::new(stream));
}
}
@@ -236,16 +242,14 @@ fn patch_boot_images(
Ok(())
}
/// From the set of input images (modified partitions + all vbmeta partitions)
/// and determine the order to patch the vbmeta images so that it can be done in
/// a single pass.
fn get_vbmeta_patch_order(
/// Load the specified vbmeta image headers. If an image has a vbmeta footer,
/// then an error is returned because the vbmeta patching logic only ever writes
/// root vbmeta images.
fn load_vbmeta_images(
images: &mut HashMap<String, Box<dyn ReadSeek + Send>>,
vbmeta_images: &HashSet<String>,
) -> Result<Vec<(String, Header, HashSet<String>)>> {
let mut dep_graph = HashMap::<&str, HashSet<String>>::new();
let mut headers = HashMap::<&str, Header>::new();
let mut missing = images.keys().cloned().collect::<BTreeSet<_>>();
) -> Result<HashMap<String, Header>> {
let mut result = HashMap::new();
for name in vbmeta_images {
let reader = images.get_mut(name).unwrap();
@@ -253,9 +257,83 @@ fn get_vbmeta_patch_order(
.with_context(|| format!("Failed to load vbmeta image: {name}"))?;
if let Some(f) = footer {
warning!("{name} is a vbmeta partition, but has a footer: {f:?}");
bail!("{name} is a vbmeta partition, but has a footer: {f:?}");
}
result.insert(name.clone(), header);
}
Ok(result)
}
/// Check if a partition is critical to AVB's chain of trust. This is not
/// foolproof and uses a heuristic based on AOSP's boot process. OEM-specific
/// partitions may be equally important, but it's infeasible to list them all.
fn is_critical_to_avb(name: &str) -> bool {
name.ends_with("boot")
|| name.starts_with("odm")
|| name.starts_with("system")
|| name.starts_with("vbmeta")
|| name.starts_with("vendor")
|| name == "dtbo"
|| name == "product"
|| name == "pvmfw"
|| name == "recovery"
}
/// Check that all critical partitions within the payload are protected by a
/// vbmeta image in `vbmeta_headers`.
fn ensure_partitions_protected(
manifest: &DeltaArchiveManifest,
vbmeta_headers: &HashMap<String, Header>,
) -> Result<()> {
let critical_partitions = manifest
.partitions
.iter()
.map(|p| &p.partition_name)
.filter(|n| is_critical_to_avb(n))
.cloned()
.collect::<BTreeSet<_>>();
// vbmeta partitions first.
let mut avb_partitions = vbmeta_headers.keys().cloned().collect::<BTreeSet<_>>();
// Then, everything referred to by the descriptors.
for header in vbmeta_headers.values() {
let partition_names = header
.descriptors
.iter()
.filter_map(|d| d.partition_name())
.map(|n| n.to_owned());
avb_partitions.extend(partition_names);
}
let missing = critical_partitions
.difference(&avb_partitions)
.collect::<Vec<_>>();
if !missing.is_empty() {
bail!(
"Found critical partitions that are not protected by AVB: {}",
joined(missing),
);
}
Ok(())
}
/// From the set of input images (modified partitions + all vbmeta partitions),
/// determine the order to patch the vbmeta images so that it can be done in a
/// single pass.
fn get_vbmeta_patch_order(
images: &mut HashMap<String, Box<dyn ReadSeek + Send>>,
vbmeta_headers: &HashMap<String, Header>,
) -> Result<Vec<(String, HashSet<String>)>> {
let mut dep_graph = HashMap::<&str, HashSet<String>>::new();
let mut missing = images.keys().cloned().collect::<BTreeSet<_>>();
for (name, header) in vbmeta_headers {
dep_graph.insert(name, HashSet::new());
missing.remove(name);
@@ -273,34 +351,31 @@ fn get_vbmeta_patch_order(
missing.remove(partition_name);
}
}
headers.insert(name, header);
}
if !missing.is_empty() {
warning!("Partitions aren't protected by AVB: {:?}", joined(missing));
}
// Prune vbmeta images we don't need.
loop {
let unneeded = dep_graph
.iter()
.find(|(_, d)| d.is_empty())
.map(|(&n, _)| n.to_owned());
match unneeded {
Some(name) => {
dep_graph.remove(name.as_str());
headers.remove(name.as_str());
// Ensure that there's only a single root of trust. Otherwise, there could
// be eg. a `vbmeta_unused` containing all the relevant descriptors, but is
// never loaded by the bootloader.
let mut roots = BTreeSet::new();
for deps in dep_graph.values_mut() {
deps.remove(name.as_str());
}
}
None => break,
for name in vbmeta_headers.keys() {
if !dep_graph.values().any(|d| d.contains(name)) {
roots.insert(name.as_str());
}
}
// Compute the patching order. This only includes vbmeta images.
// For zero roots, let TopologicalSort report the cycle.
if roots.len() > 1 {
bail!("Found multiple root vbmeta images: {}", joined(roots));
}
// Compute the patching order. This only includes vbmeta images. All vbmeta
// images are included (even those that have no dependencies) so that
// update_vbmeta_headers() can check and update the flags field if needed.
let mut topo = TopologicalSort::<String>::new();
let mut order = vec![];
@@ -313,13 +388,9 @@ fn get_vbmeta_patch_order(
while !topo.is_empty() {
match topo.pop() {
Some(item) => {
// Only include vbmeta images that we need to modify.
if headers.contains_key(item.as_str()) {
order.push((
item.clone(),
headers.remove(item.as_str()).unwrap(),
dep_graph.remove(item.as_str()).unwrap(),
));
// Only include vbmeta images.
if dep_graph.contains_key(item.as_str()) {
order.push((item.clone(), dep_graph.remove(item.as_str()).unwrap()));
}
}
None => bail!("vbmeta dependency graph has cycle: {topo:?}"),
@@ -329,26 +400,43 @@ fn get_vbmeta_patch_order(
Ok(order)
}
/// Update vbmeta descriptors based on the footers from the specified images and
/// then re-sign the vbmeta images.
fn update_vbmeta_descriptors(
/// Update vbmeta headers.
///
/// * If [`Header::flags`] is non-zero, then an error is returned because the
/// value renders AVB useless. If `clear_vbmeta_flags` is set to true, then
/// the value is set to 0 instead.
/// * [`Header::descriptors`] is updated for each dependency listed in `order`.
/// * [`Header::algorithm_type`] is updated with an algorithm type that matches
/// `key`. This is not a factor when determining if a header is changed.
///
/// If changes were made to a vbmeta header, then the image in `images` will be
/// replaced with a new in-memory reader containing the new image. Otherwise,
/// the image is removed from `images` entirely to avoid needing to repack it.
fn update_vbmeta_headers(
images: &mut HashMap<String, Box<dyn ReadSeek + Send>>,
order: &mut [(String, Header, HashSet<String>)],
headers: &mut HashMap<String, Header>,
order: &mut [(String, HashSet<String>)],
clear_vbmeta_flags: bool,
key: &RsaPrivateKey,
block_size: u64,
) -> Result<()> {
for (name, parent_header, deps) in order {
let mut unchanged = HashSet::new();
for (name, deps) in order {
let parent_header = headers.get_mut(name).unwrap();
let orig_parent_header = parent_header.clone();
if parent_header.flags != 0 {
if clear_vbmeta_flags {
parent_header.flags = 0;
} else {
bail!("{name} header flags disable AVB {:#x}", parent_header.flags);
bail!(
"Verified boot is disabled by {name}'s header flags: {:#x}",
parent_header.flags,
);
}
}
parent_header.set_algo_for_key(key)?;
for dep in deps.iter() {
// This can't fail since the descriptor must have existed for the
// dependency to exist.
@@ -396,19 +484,32 @@ fn update_vbmeta_descriptors(
}
}
parent_header
.sign(key)
.with_context(|| format!("Failed to sign vbmeta header for image: {name}"))?;
// Only sign and rewrite the image if we need to. Some vbmeta images may
// have no dependencies and are only being processed to ensure that the
// flags are set to a sane value.
if parent_header != &orig_parent_header {
parent_header.set_algo_for_key(key)?;
parent_header
.sign(key)
.with_context(|| format!("Failed to sign vbmeta header for image: {name}"))?;
let mut writer = Cursor::new(Vec::new());
parent_header
.to_writer(&mut writer)
.with_context(|| format!("Failed to write vbmeta image: {name}"))?;
let mut writer = Cursor::new(Vec::new());
parent_header
.to_writer(&mut writer)
.with_context(|| format!("Failed to write vbmeta image: {name}"))?;
padding::write_zeros(&mut writer, block_size)
.with_context(|| format!("Failed to write vbmeta padding: {name}"))?;
padding::write_zeros(&mut writer, block_size)
.with_context(|| format!("Failed to write vbmeta padding: {name}"))?;
*images.get_mut(name).unwrap() = Box::new(writer);
*images.get_mut(name).unwrap() = Box::new(writer);
} else {
unchanged.insert(name.as_str());
}
}
// No need to package a replacement image if it's unchanged.
for name in unchanged {
images.remove(name);
}
Ok(())
@@ -445,7 +546,7 @@ fn compress_image(
#[allow(clippy::too_many_arguments)]
fn patch_ota_payload(
open_payload: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
payload: &(dyn ReadSeekReopen + Sync),
writer: impl Write,
external_images: &HashMap<String, PathBuf>,
boot_partition: &str,
@@ -456,8 +557,8 @@ fn patch_ota_payload(
cert_ota: &Certificate,
cancel_signal: &AtomicBool,
) -> Result<(String, u64)> {
let header =
PayloadHeader::from_reader(open_payload()?).context("Failed to load OTA payload header")?;
let header = PayloadHeader::from_reader(payload.reopen_boxed()?)
.context("Failed to load OTA payload header")?;
if !header.is_full_ota() {
bail!("Payload is a delta OTA, not a full OTA");
}
@@ -499,7 +600,7 @@ fn patch_ota_payload(
// from the old payload). The values will be replaced later if the images
// need to be patched (eg. boot or vbmeta image).
let mut input_streams = open_input_streams(
&open_payload,
payload,
&required_images,
external_images,
&header_locked,
@@ -515,11 +616,15 @@ fn patch_ota_payload(
cancel_signal,
)?;
let mut vbmeta_order = get_vbmeta_patch_order(&mut input_streams, &vbmeta_images)?;
let mut vbmeta_headers = load_vbmeta_images(&mut input_streams, &vbmeta_images)?;
ensure_partitions_protected(&header_locked.manifest, &vbmeta_headers)?;
let mut vbmeta_order = get_vbmeta_patch_order(&mut input_streams, &vbmeta_headers)?;
status!(
"Patching vbmeta images: {}",
joined(vbmeta_order.iter().map(|(n, _, _)| n)),
joined(vbmeta_order.iter().map(|(n, _)| n)),
);
// Get rid of input readers for vbmeta partitions we don't need to modify.
@@ -530,8 +635,9 @@ fn patch_ota_payload(
}
}
update_vbmeta_descriptors(
update_vbmeta_headers(
&mut input_streams,
&mut vbmeta_headers,
&mut vbmeta_order,
clear_vbmeta_flags,
key_avb,
@@ -559,7 +665,7 @@ fn patch_ota_payload(
let header_locked = header.lock().unwrap();
let mut payload_writer = PayloadWriter::new(writer, header_locked.clone(), key_ota.clone())
.context("Failed to write payload header")?;
let mut orig_payload_reader = open_payload().context("Failed to open payload")?;
let mut orig_payload_reader = payload.reopen_boxed().context("Failed to open payload")?;
while payload_writer
.begin_next_operation()
@@ -628,12 +734,7 @@ fn patch_ota_zip(
cert_ota: &Certificate,
cancel_signal: &AtomicBool,
) -> Result<(OtaMetadata, u64)> {
let mut missing = BTreeSet::from([
ota::PATH_METADATA_PB,
ota::PATH_OTACERT,
ota::PATH_PAYLOAD,
ota::PATH_PROPERTIES,
]);
let mut missing = BTreeSet::from([ota::PATH_OTACERT, ota::PATH_PAYLOAD, ota::PATH_PROPERTIES]);
// Keep in sorted order for reproducibility and to guarantee that the
// payload is processed before its properties file.
@@ -648,9 +749,15 @@ fn patch_ota_zip(
if !missing.is_empty() {
bail!("Missing entries in OTA zip: {:?}", joined(missing));
} else if !paths.contains(ota::PATH_METADATA) && !paths.contains(ota::PATH_METADATA_PB) {
bail!(
"Neither legacy nor protobuf OTA metadata files exist: {:?}, {:?}",
ota::PATH_METADATA,
ota::PATH_METADATA_PB,
)
}
let mut metadata_pb_raw = None;
let mut metadata = None;
let mut properties = None;
let mut payload_metadata_size = None;
let mut entries = vec![];
@@ -671,19 +778,33 @@ fn patch_ota_zip(
.compression_method(CompressionMethod::Stored)
.large_file(use_zip64);
// Processed at the end after all other entries are written.
match path.as_str() {
// Convert legacy metadata from Android 11 to the modern protobuf
// structure. Note that although we can read legacy-only OTAs, we
// always produce both the legacy and protobuf representations in
// the output.
ota::PATH_METADATA => {
// Ignore because the plain-text legacy metadata file is
// regenerated from the new protobuf metadata.
let mut buf = String::new();
reader
.read_to_string(&mut buf)
.with_context(|| format!("Failed to read OTA metadata: {path}"))?;
metadata = Some(
ota::parse_legacy_metadata(&buf)
.with_context(|| format!("Failed to parse OTA metadata: {path}"))?,
);
continue;
}
// This takes precedence due to sorted iteration order.
ota::PATH_METADATA_PB => {
// Processed at the end after all other entries are written.
let mut buf = vec![];
reader
.read_to_end(&mut buf)
.with_context(|| format!("Failed to read OTA metadata: {path}"))?;
metadata_pb_raw = Some(buf);
metadata = Some(
ota::parse_protobuf_metadata(&buf)
.with_context(|| format!("Failed to parse OTA metadata: {path}"))?,
);
continue;
}
_ => {}
@@ -713,19 +834,16 @@ fn patch_ota_zip(
bail!("{path} is not stored uncompressed");
}
let payload_offset = reader.data_start();
let payload_size = reader.size();
// The zip library doesn't provide us with a seekable reader, so
// we make our own from the underlying file.
let payload_reader = SectionReader::new(
BufReader::new(raw_reader.reopen()?),
reader.data_start(),
reader.size(),
)?;
let (p, m) = patch_ota_payload(
|| {
// The zip library doesn't provide us with a seekable
// reader, so we make our own from the underlying file.
Ok(Box::new(SectionReader::new(
BufReader::new(raw_reader.reopen()),
payload_offset,
payload_size,
)?))
},
&payload_reader,
&mut writer,
external_images,
boot_partition,
@@ -778,7 +896,7 @@ fn patch_ota_zip(
zip_writer,
// Offset where next entry would begin.
entries.last().map(|e| e.offset + e.size).unwrap() + data_descriptor_size,
&metadata_pb_raw.unwrap(),
&metadata.unwrap(),
payload_metadata_size.unwrap(),
)
.context("Failed to write new OTA metadata")?;
@@ -816,18 +934,18 @@ fn extract_ota_zip(
})
.collect::<Result<HashMap<_, _>>>()?;
let payload_reader = SectionReader::new(
BufReader::new(raw_reader.reopen()?),
payload_offset,
payload_size,
)?;
// Extract the images. Each time we're asked to open a new file, we just
// clone the relevant PSeekFile. We only ever have one actual kernel file
// descriptor for each file.
payload::extract_images(
|| {
Ok(Box::new(SectionReader::new(
BufReader::new(raw_reader.reopen()),
payload_offset,
payload_size,
)?))
},
|name| Ok(Box::new(BufWriter::new(output_files[name].reopen()))),
&payload_reader,
|name| Ok(Box::new(BufWriter::new(output_files[name].reopen()?))),
header,
images.iter().map(|n| n.as_str()),
cancel_signal,
@@ -913,7 +1031,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
let raw_reader = File::open(&cli.input)
.map(PSeekFile::new)
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
let mut zip_reader = ZipArchive::new(BufReader::new(raw_reader.reopen()))
let mut zip_reader = ZipArchive::new(BufReader::new(raw_reader.reopen()?))
.with_context(|| format!("Failed to read zip: {:?}", cli.input))?;
// Open the output file for reading too, so we can verify offsets later.
@@ -1002,7 +1120,7 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
let raw_reader = File::open(&cli.input)
.map(PSeekFile::new)
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
let mut zip = ZipArchive::new(BufReader::new(raw_reader.reopen()))
let mut zip = ZipArchive::new(BufReader::new(raw_reader.reopen()?))
.with_context(|| format!("Failed to read zip: {:?}", cli.input))?;
let payload_entry = zip
.by_name(ota::PATH_PAYLOAD)
@@ -1012,7 +1130,7 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
// Open the payload data directly.
let mut payload_reader = SectionReader::new(
BufReader::new(raw_reader.reopen()),
BufReader::new(raw_reader.reopen()?),
payload_offset,
payload_size,
)
@@ -1195,19 +1313,25 @@ pub fn ota_main(cli: &OtaCli, cancel_signal: &AtomicBool) -> Result<()> {
// inside a group: https://github.com/clap-rs/clap/issues/4707. Even if that
// were fixed, the former option's error message is much more user friendly.
const HEADING_PATH: &str = "Path options";
const HEADING_KEY: &str = "Key options";
const HEADING_MAGISK: &str = "Magisk patch options";
const HEADING_PREPATCHED: &str = "Prepatched boot image options";
const HEADING_OTHER: &str = "Other patch options";
#[derive(Debug, Args)]
#[group(required = true, multiple = false)]
pub struct RootGroup {
/// Path to Magisk APK.
#[arg(long, value_name = "FILE", value_parser)]
#[arg(long, value_name = "FILE", value_parser, help_heading = HEADING_MAGISK)]
pub magisk: Option<PathBuf>,
/// Path to prepatched boot image.
#[arg(long, value_name = "FILE", value_parser)]
#[arg(long, value_name = "FILE", value_parser, help_heading = HEADING_PREPATCHED)]
pub prepatched: Option<PathBuf>,
/// Skip applying root patch.
#[arg(long)]
#[arg(long, help_heading = HEADING_OTHER)]
pub rootless: bool,
}
@@ -1215,23 +1339,35 @@ pub struct RootGroup {
#[derive(Debug, Parser)]
pub struct PatchCli {
/// Patch to original OTA zip.
#[arg(short, long, value_name = "FILE", value_parser)]
#[arg(short, long, value_name = "FILE", value_parser, help_heading = HEADING_PATH)]
pub input: PathBuf,
/// Path to new OTA zip.
#[arg(short, long, value_name = "FILE", value_parser)]
#[arg(short, long, value_name = "FILE", value_parser, help_heading = HEADING_PATH)]
pub output: Option<PathBuf>,
/// Private key for signing vbmeta images.
#[arg(long, alias = "privkey-avb", value_name = "FILE", value_parser)]
#[arg(
long,
alias = "privkey-avb",
value_name = "FILE",
value_parser,
help_heading = HEADING_KEY
)]
pub key_avb: PathBuf,
/// Private key for signing the OTA.
#[arg(long, alias = "privkey-ota", value_name = "FILE", value_parser)]
#[arg(
long,
alias = "privkey-ota",
value_name = "FILE",
value_parser,
help_heading = HEADING_KEY
)]
pub key_ota: PathBuf,
/// Certificate for OTA signing key.
#[arg(long, value_name = "FILE", value_parser)]
#[arg(long, value_name = "FILE", value_parser, help_heading = HEADING_KEY)]
pub cert_ota: PathBuf,
/// Environment variable containing AVB private key passphrase.
@@ -1240,7 +1376,8 @@ pub struct PatchCli {
alias = "passphrase-avb-env-var",
value_name = "ENV_VAR",
value_parser,
group = "pass_avb"
group = "pass_avb",
help_heading = HEADING_KEY
)]
pub pass_avb_env_var: Option<OsString>,
@@ -1250,7 +1387,8 @@ pub struct PatchCli {
alias = "passphrase-avb-file",
value_name = "FILE",
value_parser,
group = "pass_avb"
group = "pass_avb",
help_heading = HEADING_KEY
)]
pub pass_avb_file: Option<PathBuf>,
@@ -1260,7 +1398,8 @@ pub struct PatchCli {
alias = "passphrase-ota-env-var",
value_name = "ENV_VAR",
value_parser,
group = "pass_ota"
group = "pass_ota",
help_heading = HEADING_KEY
)]
pub pass_ota_env_var: Option<OsString>,
@@ -1270,39 +1409,70 @@ pub struct PatchCli {
alias = "passphrase-ota-file",
value_name = "FILE",
value_parser,
group = "pass_ota"
group = "pass_ota",
help_heading = HEADING_KEY
)]
pub pass_ota_file: Option<PathBuf>,
/// Use partition image from a file instead of the original payload.
#[arg(long, value_names = ["PARTITION", "FILE"], value_parser = value_parser!(OsString), num_args = 2)]
#[arg(
long,
value_names = ["PARTITION", "FILE"],
value_parser = value_parser!(OsString),
num_args = 2,
help_heading = HEADING_PATH,
)]
pub replace: Vec<OsString>,
#[command(flatten)]
pub root: RootGroup,
/// Magisk preinit block device.
#[arg(long, value_name = "PARTITION", conflicts_with_all = ["prepatched", "rootless"])]
/// Magisk preinit block device (version >=25211 only).
#[arg(
long,
value_name = "PARTITION",
conflicts_with_all = ["prepatched", "rootless"],
help_heading = HEADING_MAGISK
)]
pub magisk_preinit_device: Option<String>,
/// Magisk random seed.
#[arg(long, value_name = "NUMBER", conflicts_with_all = ["prepatched", "rootless"])]
/// Magisk random seed (version >=25211, <26103 only).
#[arg(
long,
value_name = "NUMBER",
conflicts_with_all = ["prepatched", "rootless"],
help_heading = HEADING_MAGISK
)]
pub magisk_random_seed: Option<u64>,
/// Ignore Magisk compatibility/version warnings.
#[arg(long, conflicts_with_all = ["prepatched", "rootless"])]
#[arg(
long,
conflicts_with_all = ["prepatched", "rootless"],
help_heading = HEADING_MAGISK
)]
pub ignore_magisk_warnings: bool,
/// Ignore compatibility issues with prepatched boot images.
#[arg(long, action = ArgAction::Count, conflicts_with_all = ["magisk", "rootless"])]
#[arg(
long,
action = ArgAction::Count,
conflicts_with_all = ["magisk", "rootless"],
help_heading = HEADING_PREPATCHED
)]
pub ignore_prepatched_compat: u8,
/// Forcibly clear vbmeta flags if they disable AVB.
#[arg(long)]
#[arg(long, help_heading = HEADING_OTHER)]
pub clear_vbmeta_flags: bool,
/// Boot partition name.
#[arg(long, value_name = "PARTITION", default_value = "@gki_ramdisk")]
#[arg(
long,
value_name = "PARTITION",
default_value = "@gki_ramdisk",
help_heading = HEADING_OTHER
)]
pub boot_partition: String,
}
+28 -26
View File
@@ -28,8 +28,8 @@ use crate::{
padding,
},
stream::{
self, CountingReader, FromReader, ReadDiscardExt, ReadSeek, ReadStringExt, ToWriter,
WriteSeek, WriteStringExt, WriteZerosExt,
self, CountingReader, FromReader, ReadDiscardExt, ReadSeekReopen, ReadStringExt, ToWriter,
WriteSeekReopen, WriteStringExt, WriteZerosExt,
},
util,
};
@@ -120,8 +120,9 @@ pub enum Error {
type Result<T> = std::result::Result<T, Error>;
fn ring_algorithm(name: &str) -> Result<&'static Algorithm> {
fn ring_algorithm(name: &str, for_verify: bool) -> Result<&'static Algorithm> {
match name {
"sha1" if for_verify => Ok(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY),
"sha256" => Ok(&ring::digest::SHA256),
"sha512" => Ok(&ring::digest::SHA512),
a => Err(Error::UnsupportedHashAlgorithm(a.to_owned())),
@@ -427,7 +428,7 @@ impl HashTreeDescriptor {
///
/// NOTE: The result is **not** padded to the block size.
fn hash_one_level_parallel(
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
image_size: u64,
block_size: u32,
algorithm: &'static Algorithm,
@@ -449,7 +450,7 @@ impl HashTreeDescriptor {
let start = c * chunk_size;
let size = chunk_size.min(image_size - start);
let mut reader = open_input()?;
let mut reader = input.reopen_boxed()?;
reader.seek(SeekFrom::Start(start))?;
Self::hash_one_level(reader, size, block_size, algorithm, salt, cancel_signal)
@@ -461,7 +462,7 @@ impl HashTreeDescriptor {
/// Calculate the hash tree for the given input in parallel.
fn calculate_hash_tree(
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
image_size: u64,
block_size: u32,
algorithm: &'static Algorithm,
@@ -470,7 +471,7 @@ impl HashTreeDescriptor {
) -> io::Result<(Vec<u8>, Vec<u8>)> {
// Small files are hashed directly, exactly like a hash descriptor.
if image_size <= u64::from(block_size) {
let mut reader = open_input()?;
let mut reader = input.reopen_boxed()?;
let mut buf = vec![0u8; block_size as usize];
reader.read_exact(&mut buf)?;
@@ -500,7 +501,7 @@ impl HashTreeDescriptor {
} else {
// Initially read from file.
Self::hash_one_level_parallel(
&open_input,
input,
level_size,
block_size,
algorithm,
@@ -590,13 +591,13 @@ impl HashTreeDescriptor {
/// original state by truncating it to [`Self::image_size`].
pub fn update(
&mut self,
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
open_output: impl Fn() -> io::Result<Box<dyn WriteSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
output: &(dyn WriteSeekReopen + Sync),
cancel_signal: &AtomicBool,
) -> Result<()> {
let algorithm = ring_algorithm(&self.hash_algorithm)?;
let algorithm = ring_algorithm(&self.hash_algorithm, false)?;
let (root_digest, hash_tree) = Self::calculate_hash_tree(
&open_input,
input,
self.image_size,
self.data_block_size,
algorithm,
@@ -610,7 +611,7 @@ impl HashTreeDescriptor {
let tree_size = hash_tree.len() as u64;
let mut writer = open_output()?;
let mut writer = output.reopen_boxed()?;
writer.seek(SeekFrom::Start(self.image_size))?;
writer
.write_all(&hash_tree)
@@ -633,7 +634,7 @@ impl HashTreeDescriptor {
// The FEC covers the hash tree as well.
let fec = Fec::new(self.image_size + tree_size, self.data_block_size, parity)?;
let fec_data = fec.generate(open_input, cancel_signal)?;
let fec_data = fec.generate(input, cancel_signal)?;
let fec_size = fec_data
.len()
.to_u64()
@@ -660,19 +661,19 @@ impl HashTreeDescriptor {
/// handles to the same file.
pub fn verify(
&self,
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
cancel_signal: &AtomicBool,
) -> Result<()> {
self.check_offsets()?;
let algorithm = ring_algorithm(&self.hash_algorithm)?;
let algorithm = ring_algorithm(&self.hash_algorithm, true)?;
if self.tree_size > HASH_TREE_MAX_SIZE {
return Err(Error::FieldOutOfBounds("tree_size"));
}
let (actual_root_digest, actual_hash_tree) = Self::calculate_hash_tree(
&open_input,
input,
self.image_size,
self.data_block_size,
algorithm,
@@ -687,7 +688,7 @@ impl HashTreeDescriptor {
});
}
let mut reader = open_input()?;
let mut reader = input.reopen_boxed()?;
reader.seek(SeekFrom::Start(self.tree_offset))?;
let mut hash_tree = vec![0u8; self.tree_size as usize];
@@ -716,7 +717,7 @@ impl HashTreeDescriptor {
.read_exact(&mut fec_data)
.map_err(|e| Error::ReadFieldError("fec_data", e))?;
fec.verify(open_input, &fec_data, cancel_signal)?;
fec.verify(input, &fec_data, cancel_signal)?;
}
Ok(())
@@ -732,8 +733,8 @@ impl HashTreeDescriptor {
/// actually valid.
pub fn repair(
&self,
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
open_output: impl Fn() -> io::Result<Box<dyn WriteSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
output: &(dyn WriteSeekReopen + Sync),
cancel_signal: &AtomicBool,
) -> Result<()> {
self.check_offsets()?;
@@ -743,7 +744,7 @@ impl HashTreeDescriptor {
return Err(Error::FecMissing);
}
let mut reader = open_input()?;
let mut reader = input.reopen_boxed()?;
reader.seek(SeekFrom::Start(self.fec_offset))?;
let (fec, fec_size) = self.get_fec()?;
@@ -754,7 +755,7 @@ impl HashTreeDescriptor {
.read_exact(&mut fec_data)
.map_err(|e| Error::ReadFieldError("fec_data", e))?;
fec.repair(open_input, open_output, &fec_data, cancel_signal)?;
fec.repair(input, output, &fec_data, cancel_signal)?;
Ok(())
}
@@ -911,9 +912,10 @@ impl HashDescriptor {
fn calculate(
&self,
reader: impl Read,
for_verify: bool,
cancel_signal: &AtomicBool,
) -> Result<ring::digest::Digest> {
let algorithm = ring_algorithm(&self.hash_algorithm)?;
let algorithm = ring_algorithm(&self.hash_algorithm, for_verify)?;
let mut context = Context::new(algorithm);
context.update(&self.salt);
@@ -930,14 +932,14 @@ impl HashDescriptor {
/// Update the root hash from the input reader's contents.
pub fn update(&mut self, reader: impl Read, cancel_signal: &AtomicBool) -> Result<()> {
let digest = self.calculate(reader, cancel_signal)?;
let digest = self.calculate(reader, false, cancel_signal)?;
self.root_digest = digest.as_ref().to_vec();
Ok(())
}
/// Verify the root hash against the input reader.
pub fn verify(&self, reader: impl Read, cancel_signal: &AtomicBool) -> Result<()> {
let digest = self.calculate(reader, cancel_signal)?;
let digest = self.calculate(reader, true, cancel_signal)?;
if self.root_digest != digest.as_ref() {
return Err(Error::InvalidRootDigest {
+22 -31
View File
@@ -19,7 +19,7 @@ use thiserror::Error;
use crate::{
format::verityrs,
stream::{self, FromReader, ReadSeek, ToWriter, WriteSeek, WriteZerosExt},
stream::{self, FromReader, ReadSeekReopen, ToWriter, WriteSeekReopen, WriteZerosExt},
util::NumBytes,
};
@@ -419,7 +419,7 @@ impl Fec {
/// This function is multithreaded and uses rayon's global thread pool.
pub fn generate(
&self,
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
cancel_signal: &AtomicBool,
) -> Result<Vec<u8>> {
let fec_size = self.fec_size();
@@ -430,7 +430,7 @@ impl Fec {
.map(|(round, buf)| -> Result<()> {
stream::check_cancel(cancel_signal)?;
let reader = open_input()?;
let reader = input.reopen_boxed()?;
self.generate_one_round(reader, round as u64, buf)
})
.collect::<Result<()>>()?;
@@ -445,7 +445,7 @@ impl Fec {
/// This function is multithreaded and uses rayon's global thread pool.
pub fn verify(
&self,
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
fec: &[u8],
cancel_signal: &AtomicBool,
) -> Result<()> {
@@ -463,7 +463,7 @@ impl Fec {
.map(|(round, buf)| -> Result<()> {
stream::check_cancel(cancel_signal)?;
let reader = open_input()?;
let reader = input.reopen_boxed()?;
self.verify_one_round(reader, round as u64, buf)
})
.collect::<Result<()>>()?;
@@ -484,8 +484,8 @@ impl Fec {
/// This function is multithreaded and uses rayon's global thread pool.
pub fn repair(
&self,
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
open_output: impl Fn() -> io::Result<Box<dyn WriteSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
output: &(dyn WriteSeekReopen + Sync),
fec: &[u8],
cancel_signal: &AtomicBool,
) -> Result<u64> {
@@ -504,8 +504,8 @@ impl Fec {
.map(|(round, buf)| -> Result<u64> {
stream::check_cancel(cancel_signal)?;
let reader = open_input()?;
let writer = open_output()?;
let reader = input.reopen_boxed()?;
let writer = output.reopen_boxed()?;
self.repair_one_round(reader, writer, round as u64, buf)
})
.collect::<Result<Vec<u64>>>()?
@@ -542,16 +542,16 @@ impl FecImage {
/// Generate FEC data for a file. `parity` is the number of parity bytes per
/// 255-byte Reed-Solomon codeword.
pub fn generate(
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
parity: u8,
cancel_signal: &AtomicBool,
) -> Result<Self> {
let data_size = {
let mut file = open_input()?;
let mut file = input.reopen_boxed()?;
file.seek(SeekFrom::End(0))?
};
let fec = Fec::new(data_size, FEC_BLOCK_SIZE as u32, parity)?;
let fec_data = fec.generate(open_input, cancel_signal)?;
let fec_data = fec.generate(input, cancel_signal)?;
Ok(Self {
fec: fec_data,
@@ -564,11 +564,11 @@ impl FecImage {
/// [`Self::repair()`] if performing a repair is not necessary.
pub fn verify(
&self,
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
cancel_signal: &AtomicBool,
) -> Result<()> {
let fec = Fec::new(self.data_size, FEC_BLOCK_SIZE as u32, self.parity)?;
fec.verify(open_input, &self.fec, cancel_signal)
fec.verify(input, &self.fec, cancel_signal)
}
/// Repair a file using this instance's FEC data. The maximum correctable
@@ -588,12 +588,12 @@ impl FecImage {
/// that multiple threads will always read and write disjoint file offsets.
pub fn repair(
&self,
open_input: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
open_output: impl Fn() -> io::Result<Box<dyn WriteSeek>> + Sync,
input: &(dyn ReadSeekReopen + Sync),
output: &(dyn WriteSeekReopen + Sync),
cancel_signal: &AtomicBool,
) -> Result<u64> {
let fec = Fec::new(self.data_size, FEC_BLOCK_SIZE as u32, self.parity)?;
fec.repair(open_input, open_output, &self.fec, cancel_signal)
fec.repair(input, output, &self.fec, cancel_signal)
}
/// Build one instance of the FEC header. The caller is responsible for
@@ -764,18 +764,15 @@ mod tests {
let num_codewords = fec.rounds as usize * block_size as usize;
// Generate FEC data.
let fec_data = fec
.generate(|| Ok(Box::new(file.reopen())), &cancel_signal)
.unwrap();
let fec_data = fec.generate(&file, &cancel_signal).unwrap();
// Verify that there are no errors.
fec.verify(|| Ok(Box::new(file.reopen())), &fec_data, &cancel_signal)
.unwrap();
fec.verify(&file, &fec_data, &cancel_signal).unwrap();
// Verify that errors are detected.
corrupt_byte(&mut file, 0);
assert_matches!(
fec.verify(|| Ok(Box::new(file.reopen())), &fec_data, &cancel_signal,),
fec.verify(&file, &fec_data, &cancel_signal),
Err(Error::HasErrors)
);
@@ -785,13 +782,7 @@ mod tests {
}
// Verify that all the single-byte errors can be fixed.
fec.repair(
|| Ok(Box::new(file.reopen())),
|| Ok(Box::new(file.reopen())),
&fec_data,
&cancel_signal,
)
.unwrap();
fec.repair(&file, &file, &fec_data, &cancel_signal).unwrap();
let repaired_digest = {
let mut buf = Vec::new();
@@ -826,7 +817,7 @@ mod tests {
file.write_all(&buf).unwrap();
}
let image = FecImage::generate(|| Ok(Box::new(file.reopen())), 2, &cancel_signal).unwrap();
let image = FecImage::generate(&file, 2, &cancel_signal).unwrap();
let mut fec_file = Cursor::new(Vec::new());
image.to_writer(&mut fec_file).unwrap();
+117 -21
View File
@@ -44,6 +44,8 @@ const ZIP_EOCD_MAGIC: &[u8; 4] = b"PK\x05\x06";
const COMMENT_MESSAGE: &[u8] = b"signed by avbroot\0";
const LEGACY_SEP: &str = "|";
#[derive(Debug, Error)]
pub enum Error {
#[error("Cannot find OTA signature footer magic")]
@@ -64,6 +66,10 @@ pub enum Error {
UnsupportedDigestAlgorithm(ObjectIdentifier),
#[error("Unsupported signature algorithm: {0}")]
UnsupportedSignatureAlgorithm(ObjectIdentifier),
#[error("Invalid legacy metadata line: {0:?}")]
InvalidLegacyMetadataLine(String),
#[error("Unsupported legacy metadata field: {key:?} = {value:?}")]
UnsupportedLegacyMetadataField { key: String, value: String },
#[error("Expected entry offsets {expected:?}, but have {actual:?}")]
MismatchedPropertyFiles { expected: String, actual: String },
#[error("Property files {0:?} exceed {1} byte reserved space")]
@@ -92,21 +98,111 @@ pub enum Error {
type Result<T> = std::result::Result<T, Error>;
pub fn parse_protobuf_metadata(data: &[u8]) -> Result<OtaMetadata> {
Ok(OtaMetadata::decode(data)?)
}
/// Synthesize protobuf structure from legacy plain-text metadata.
pub fn parse_legacy_metadata(data: &str) -> Result<OtaMetadata> {
let mut metadata = OtaMetadata::default();
for line in data.split('\n') {
if line.is_empty() {
continue;
}
let (key, value) = line
.split_once('=')
.ok_or_else(|| Error::InvalidLegacyMetadataLine(line.to_owned()))?;
let unsupported = || Error::UnsupportedLegacyMetadataField {
key: key.to_owned(),
value: value.to_owned(),
};
// Booleans are represented by the presence or absence of `<key>=yes`.
let parse_yes = || match value {
"yes" => Ok(true),
_ => Err(unsupported()),
};
let parse_list = || {
value
.split(LEGACY_SEP)
.map(|s| s.to_owned())
.collect::<Vec<_>>()
};
match key {
"ota-type" => {
match OtaType::from_str_name(value).ok_or_else(unsupported)? {
t @ (OtaType::Ab | OtaType::Block) => metadata.set_type(t),
// Not allowed by AOSP in the legacy format.
_ => return Err(unsupported()),
}
}
"ota-wipe" => metadata.wipe = parse_yes()?,
"ota-retrofit-dynamic-partitions" => {
metadata.retrofit_dynamic_partitions = parse_yes()?
}
"ota-downgrade" => metadata.downgrade = parse_yes()?,
"ota-required-cache" => {
metadata.required_cache = value.parse().map_err(|_| unsupported())?;
}
"post-build" => {
let p = metadata.postcondition.get_or_insert_with(Default::default);
p.build = parse_list();
}
"post-build-incremental" => {
let p = metadata.postcondition.get_or_insert_with(Default::default);
p.build_incremental = value.to_owned();
}
"post-sdk-level" => {
let p = metadata.postcondition.get_or_insert_with(Default::default);
p.sdk_level = value.to_owned();
}
"post-security-patch-level" => {
let p = metadata.postcondition.get_or_insert_with(Default::default);
p.security_patch_level = value.to_owned();
}
"post-timestamp" => {
let p = metadata.postcondition.get_or_insert_with(Default::default);
p.timestamp = value.parse().map_err(|_| unsupported())?;
}
"pre-device" => {
let p = metadata.precondition.get_or_insert_with(Default::default);
p.device = parse_list();
}
"pre-build" => {
let p = metadata.precondition.get_or_insert_with(Default::default);
p.build = parse_list();
}
"pre-build-incremental" => {
let p = metadata.precondition.get_or_insert_with(Default::default);
p.build_incremental = value.to_owned();
}
"spl-downgrade" => metadata.spl_downgrade = parse_yes()?,
k if k.ends_with("-property-files") => {
metadata
.property_files
.insert(key.to_owned(), value.to_owned());
}
_ => {
// Ignore. Some OEMs insert values that aren't defined in AOSP.
}
}
}
Ok(metadata)
}
/// Generate the legacy plain-text and modern protobuf serializations of the
/// given metadata instance.
fn serialize_metadata(metadata: &OtaMetadata) -> Result<(String, Vec<u8>)> {
const SEP: &str = "|";
use std::fmt::Write;
let mut pairs = BTreeMap::<String, String>::new();
match metadata.r#type() {
OtaType::Ab => {
pairs.insert("ota-type".to_owned(), "AB".to_owned());
}
OtaType::Block => {
pairs.insert("ota-type".to_owned(), "BLOCK".to_owned());
}
_ => {}
// Other types are not allowed by AOSP in the legacy format.
if let t @ (OtaType::Ab | OtaType::Block) = metadata.r#type() {
pairs.insert("ota-type".to_owned(), t.as_str_name().to_owned());
}
if metadata.wipe {
pairs.insert("ota-wipe".to_owned(), "yes".to_owned());
@@ -127,7 +223,7 @@ fn serialize_metadata(metadata: &OtaMetadata) -> Result<(String, Vec<u8>)> {
);
if let Some(p) = &metadata.postcondition {
pairs.insert("post-build".to_owned(), p.build.join(SEP));
pairs.insert("post-build".to_owned(), p.build.join(LEGACY_SEP));
pairs.insert(
"post-build-incremental".to_owned(),
p.build_incremental.clone(),
@@ -141,9 +237,9 @@ fn serialize_metadata(metadata: &OtaMetadata) -> Result<(String, Vec<u8>)> {
}
if let Some(p) = &metadata.precondition {
pairs.insert("pre-device".to_owned(), p.device.join(SEP));
pairs.insert("pre-device".to_owned(), p.device.join(LEGACY_SEP));
if !p.build.is_empty() {
pairs.insert("pre-build".to_owned(), p.build.join(SEP));
pairs.insert("pre-build".to_owned(), p.build.join(LEGACY_SEP));
pairs.insert(
"pre-build-incremental".to_owned(),
p.build_incremental.clone(),
@@ -157,10 +253,10 @@ fn serialize_metadata(metadata: &OtaMetadata) -> Result<(String, Vec<u8>)> {
pairs.extend(metadata.property_files.clone());
let legacy_metadata = pairs
.into_iter()
.map(|(k, v)| format!("{k}={v}\n"))
.collect::<String>();
let legacy_metadata = pairs.into_iter().fold(String::new(), |mut output, (k, v)| {
let _ = writeln!(output, "{k}={v}");
output
});
let modern_metadata = metadata.encode_to_vec();
Ok((legacy_metadata, modern_metadata))
@@ -286,9 +382,9 @@ fn add_payload_metadata_entry(
/// Add metadata files to the output OTA zip. `zip_entries` is the list of
/// [`ZipEntry`] already written to `zip_writer`. `next_offset` is the current
/// file offset (where the next zip entry's local header begins).
/// `metadata_pb_raw` is the serialized OTA metadata protobuf message from the
/// original OTA. `payload_metadata_size` is the size of the new payload's
/// metadata and metadata signature regions.
/// `metadata` is the OTA metadata protobuf message from the original OTA.
/// `payload_metadata_size` is the size of the new payload's metadata and
/// metadata signature regions.
///
/// The zip file's backing file position MUST BE set to where the central
/// directory would start.
@@ -296,10 +392,10 @@ pub fn add_metadata(
zip_entries: &[ZipEntry],
zip_writer: &mut ZipWriter<impl Write>,
next_offset: u64,
metadata_pb_raw: &[u8],
metadata: &OtaMetadata,
payload_metadata_size: u64,
) -> Result<OtaMetadata> {
let mut metadata = OtaMetadata::decode(metadata_pb_raw)?;
let mut metadata = metadata.clone();
let options = FileOptions::default().compression_method(CompressionMethod::Stored);
let mut zip_entries = zip_entries.to_owned();
+8 -8
View File
@@ -34,15 +34,15 @@ use crate::{
InstallOperation, PartitionInfo, PartitionUpdate, Signatures,
},
stream::{
self, CountingReader, CountingWriter, FromReader, HashingWriter, ReadDiscardExt, ReadSeek,
SharedCursor, WriteSeek,
self, CountingReader, CountingWriter, FromReader, HashingWriter, ReadDiscardExt,
ReadSeekReopen, Reopen, SharedCursor, WriteSeek,
},
};
const OTA_MAGIC: &[u8; 4] = b"CrAU";
const OTA_HEADER_SIZE: usize = OTA_MAGIC.len() + 8 + 8 + 4;
const MANIFEST_MAX_SIZE: usize = 1024 * 1024;
const MANIFEST_MAX_SIZE: usize = 4 * 1024 * 1024;
#[derive(Debug, Error)]
pub enum Error {
@@ -871,7 +871,7 @@ pub fn apply_operation(
/// multithreaded and uses rayon's global thread pool. `open_payload` will be
/// called from multiple threads.
pub fn extract_image_to_memory(
open_payload: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
payload: &(dyn ReadSeekReopen + Sync),
header: &PayloadHeader,
partition_name: &str,
cancel_signal: &AtomicBool,
@@ -888,8 +888,8 @@ pub fn extract_image_to_memory(
.operations
.par_iter()
.map(|op| -> Result<()> {
let reader = open_payload()?;
let writer = stream.reopen();
let reader = payload.reopen_boxed()?;
let writer = stream.reopen()?;
apply_operation(
reader,
@@ -911,7 +911,7 @@ pub fn extract_image_to_memory(
/// is done multithreaded and uses rayon's global thread pool. `open_payload`
/// and `open_output` will be called from multiple threads.
pub fn extract_images<'a>(
open_payload: impl Fn() -> io::Result<Box<dyn ReadSeek>> + Sync,
payload: &(dyn ReadSeekReopen + Sync),
open_output: impl Fn(&str) -> io::Result<Box<dyn WriteSeek>> + Sync,
header: &PayloadHeader,
partition_names: impl IntoIterator<Item = &'a str>,
@@ -938,7 +938,7 @@ pub fn extract_images<'a>(
operations
.into_par_iter()
.map(|(name, op)| -> Result<()> {
let reader = open_payload()?;
let reader = payload.reopen_boxed()?;
let writer = open_output(name)?;
apply_operation(
+70 -17
View File
@@ -5,7 +5,7 @@
use std::{
fs::File,
io::{self, Cursor, Read, Seek, SeekFrom, Write},
io::{self, BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, RwLock,
@@ -30,6 +30,28 @@ pub trait WriteSeek: Write + Seek {}
impl<W: Write + Seek> WriteSeek for W {}
/// A trait for seekable and reopenable readers.
pub trait ReadSeekReopen: ReadSeek {
fn reopen_boxed(&self) -> io::Result<Box<dyn ReadSeek>>;
}
impl<R: ReadSeek + Reopen + 'static> ReadSeekReopen for R {
fn reopen_boxed(&self) -> io::Result<Box<dyn ReadSeek>> {
Ok(Box::new(self.reopen()?))
}
}
/// A trait for seekable and reopenable writers.
pub trait WriteSeekReopen: WriteSeek {
fn reopen_boxed(&self) -> io::Result<Box<dyn WriteSeek>>;
}
impl<W: WriteSeek + Reopen + 'static> WriteSeekReopen for W {
fn reopen_boxed(&self) -> io::Result<Box<dyn WriteSeek>> {
Ok(Box::new(self.reopen()?))
}
}
/// Common function for reading a structure from a reader.
pub trait FromReader<R: Read>: Sized {
type Error;
@@ -168,6 +190,25 @@ impl<W: Write> WriteStringExt for W {
}
}
/// Extensions for file-like types to reopen themselves.
pub trait Reopen: Sized {
/// Open a new handle to the same file. The new handle is independently
/// seekable and the file offset is initially set to 0.
fn reopen(&self) -> io::Result<Self>;
}
impl<R: Read + Reopen> Reopen for BufReader<R> {
fn reopen(&self) -> io::Result<Self> {
Ok(BufReader::new(self.get_ref().reopen()?))
}
}
impl<W: Write + Reopen> Reopen for BufWriter<W> {
fn reopen(&self) -> io::Result<Self> {
Ok(BufWriter::new(self.get_ref().reopen()?))
}
}
/// A reader wrapper that implements [`Seek`], but only for reporting the
/// current file position.
pub struct CountingReader<R: Read> {
@@ -325,6 +366,14 @@ impl<R: Read + Seek> SectionReader<R> {
}
}
impl<R: Read + Seek + Reopen> Reopen for SectionReader<R> {
fn reopen(&self) -> io::Result<Self> {
let inner = self.inner.reopen()?;
Self::new(inner, self.start, self.size)
}
}
impl<R: Read + Seek> Read for SectionReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let to_read = self.size.saturating_sub(self.pos).min(buf.len() as u64) as usize;
@@ -417,13 +466,6 @@ impl PSeekFile {
}
}
pub fn reopen(&self) -> Self {
Self {
file: self.file.clone(),
offset: 0,
}
}
pub fn set_len(&self, size: u64) -> io::Result<()> {
let file_locked = self.file.read().unwrap();
file_locked.set_len(size)
@@ -458,6 +500,15 @@ impl PSeekFile {
}
}
impl Reopen for PSeekFile {
fn reopen(&self) -> io::Result<Self> {
Ok(Self {
file: self.file.clone(),
offset: 0,
})
}
}
impl Read for PSeekFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.read_at(buf)?;
@@ -530,12 +581,14 @@ impl SharedCursor {
..Default::default()
}
}
}
pub fn reopen(&self) -> Self {
Self {
impl Reopen for SharedCursor {
fn reopen(&self) -> io::Result<Self> {
Ok(Self {
inner: self.inner.clone(),
offset: 0,
}
})
}
}
@@ -668,8 +721,8 @@ mod tests {
use super::{
CountingReader, CountingWriter, HashingReader, HashingWriter, HolePunchingWriter,
PSeekFile, ReadDiscardExt, ReadStringExt, SectionReader, SharedCursor, WriteStringExt,
WriteZerosExt,
PSeekFile, ReadDiscardExt, ReadStringExt, Reopen, SectionReader, SharedCursor,
WriteStringExt, WriteZerosExt,
};
const FOOBAR_SHA256: [u8; 32] = [
@@ -848,8 +901,8 @@ mod tests {
fn pseek_file() {
let raw_file = tempfile::tempfile().unwrap();
let mut a = PSeekFile::new(raw_file);
let mut b = a.reopen();
let mut c = b.reopen();
let mut b = a.reopen().unwrap();
let mut c = b.reopen().unwrap();
b.write_all(b"foobar").unwrap();
c.write_all(b"hello").unwrap();
@@ -868,8 +921,8 @@ mod tests {
#[test]
fn shared_cursor() {
let mut a = SharedCursor::default();
let mut b = a.reopen();
let mut c = b.reopen();
let mut b = a.reopen().unwrap();
let mut c = b.reopen().unwrap();
b.write_all(b"foobar").unwrap();
c.write_all(b"hello").unwrap();
+2 -10
View File
@@ -138,10 +138,7 @@ fn round_trip_appended_hash_tree_image() {
// Verify the hash tree and FEC data.
match header.appended_descriptor().unwrap() {
AppendedDescriptorRef::HashTree(d) => {
d.verify(|| Ok(Box::new(reader.reopen())), &cancel_signal)
.unwrap();
}
AppendedDescriptorRef::HashTree(d) => d.verify(&reader, &cancel_signal).unwrap(),
AppendedDescriptorRef::Hash(_) => panic!("Expected hash tree descriptor"),
}
@@ -166,12 +163,7 @@ fn round_trip_appended_hash_tree_image() {
d.fec_offset = 0;
d.fec_size = 0;
d.update(
|| Ok(Box::new(writer.reopen())),
|| Ok(Box::new(writer.reopen())),
&cancel_signal,
)
.unwrap();
d.update(&writer, &writer, &cancel_signal).unwrap();
}
AppendedDescriptorMut::Hash(_) => panic!("Expected hash tree descriptor"),
}
+12 -12
View File
@@ -15,13 +15,13 @@ sections = [
]
hash.original.full = "6b881553f012d582080642d660e1cf5c9e6fe41e9f1c6ab12ae87fab7894e307"
hash.original.stripped = "9befd7887a125ebd8e9ae0555469dababe6bc04b0aa41aa2562036782a6d87e0"
hash.patched.full = "6a08ae5c08e42b29ffc476511b6c60de8b77ba3f06c469a0a640e84cb95b9db9"
hash.patched.stripped = "479b4e203d432148baaf4eaa69205c2291ec10e8541c9a78e6d9b799e994f1e3"
hash.patched.full = "91e15447ade648c10bce599e75569ce55edc18798b11a704586acf3b51ae7971"
hash.patched.stripped = "84b069ac7f20115ac0b19d7e319bb86d00b9f3f64e0c7a51612f99d6cae297bb"
hash.avb_images."init_boot.img" = "3bedb41be98c46241f11219021dfbb799a6d5c89e6e00d45a66f9a5b42e7dfbc"
hash.avb_images."vbmeta.img" = "e8e6e898ca73807edb43af0a0e86d4a94b14256def89581970287a9b1bf7a3ee"
hash.avb_images."vbmeta_system.img" = "dbb63e08f26f46ccda501d99058d513ff71e3d6302c14d587442b666ff08862a"
hash.avb_images."vbmeta_vendor.img" = "6ffa0a10e72c3371653be80de1380832b4d7f8bbf38a2bd861d44a4097a57117"
hash.avb_images."vendor_boot.img" = "8f9407435e1ea532e55be418d58e1b3a4f8e0794e3cf4e562e0a0414058c6108"
hash.avb_images."vendor_boot.img" = "dd58e8d46dd26198edf14f72d17a3315ff4c2aeb65b98dbd06369ca2a6e365a3"
# Google Pixel 6a
# What's unique: boot (boot v4, no ramdisk) + vendor_boot (vendor v4, 2 ramdisks)
@@ -36,13 +36,13 @@ sections = [
]
hash.original.full = "1f1f0abe67a6f6f47287be6dafec2c12628de6a715b82ca7beddaf67ad22aca5"
hash.original.stripped = "38b15f5efdc7e056bc799859ba72ef9a73e93c61292c59f85fb4b9c31acc5f82"
hash.patched.full = "20f3eb522c45f8185c8c121ef2f6a18214345b85f3021d05841e1b002d348b3e"
hash.patched.stripped = "105f4305f886a79c03fcabd296a44ef498e2b9e602970c0ef6d8599b74a704b0"
hash.patched.full = "65f7e29591fb48ad9c7c3233df4d76bb6986aef96b94c06b73213477bc7486d8"
hash.patched.stripped = "cf77b5e307ce4d2a62e742cd3a300964de6c693880cbfa90dcd0bfec66bd1976"
hash.avb_images."boot.img" = "a19cb4d4fcc7f3e7d3046c3d19e2f243fb02513ca848ff92ad70d1ada55c4e65"
hash.avb_images."vbmeta.img" = "2d817e35f7b6cdc2edce58ef249a966fc677085b70a4aced4c829320aeef0be2"
hash.avb_images."vbmeta_system.img" = "98a050f0d53a016fbb78147b1b4a9bca3fde615aa4da34bf62c2e07a395104b5"
hash.avb_images."vbmeta_vendor.img" = "fac530f47f237e76f3c7c3cdfe96308170dd8e8f0b227d81114a489c69ba763c"
hash.avb_images."vendor_boot.img" = "6b66d8ea772dafc85e8745e88c9e05ff0401d062010c4d1ae0914b93cf3510aa"
hash.avb_images."vendor_boot.img" = "b3c596360f38cd0d6212341571acf3c2d977921f8bf163d7da27978a353da9f7"
# Google Pixel 4a 5G
# What's unique: boot (boot v3) + vendor_boot (vendor v3)
@@ -57,12 +57,12 @@ sections = [
]
hash.original.full = "6d107ffac1cd3da2c972112acc75957ed725e5c13d57ca724d9bcca5404fcebd"
hash.original.stripped = "5b889bdab3bb12ddcd3c243a56e1c58bedada8831069f49d56fe5098fb141e35"
hash.patched.full = "c2866d8959fea55884de6281403e7daf435cded62477181354eb76332446af28"
hash.patched.stripped = "a9ccefe44818560144f073bef8ae1c2c840cf8fff9dafb0e589bd5fd40cf0fea"
hash.patched.full = "8725e03798539070d7075a07c80fb1403652a2b446d5c460d636a32b7368c9a2"
hash.patched.stripped = "c4b5cfa84dc8c15f3ac9661d768062546fa36ca82cdeb6dd943347be0422903c"
hash.avb_images."boot.img" = "8e7278a2e8ae44ffc5475717eb0e1aa56bfb7650aef34375b0fe92f790835f95"
hash.avb_images."vbmeta.img" = "b036132b867f52a86eef79716261f35b1eb50e843dc4fe42f72cce67b24ae2db"
hash.avb_images."vbmeta_system.img" = "9a7c6fd654e7a92aeffbdbd55ea0d87eee36f4c235e1b505423ad8a13a751a00"
hash.avb_images."vendor_boot.img" = "e774aa770fd9c0d19a509f212309f20b08da411f8830a7383646a65571437933"
hash.avb_images."vendor_boot.img" = "6e83d22371af4a26aef2c64cc4235f83b03978b91aef69c34eec1985e9942139"
# Google Pixel 4a
# What's unique: boot (boot v2)
@@ -76,9 +76,9 @@ sections = [
]
hash.original.full = "01fd34b206152a3559039161c9874ab03df37da4268b86a9e0be899de5fc0af7"
hash.original.stripped = "cc311b5bd46e06cfdefbade794d33aa9bc3ceda4ad4f38bfe9f0dfc17033d207"
hash.patched.full = "b788d2d86008a3923734a40fb89ca98533116e51e10f35c0447beb956b236bf5"
hash.patched.stripped = "b174f530d63bfe753ab9435a910b01a1abb2e08a8628b50101f876ed2313e536"
hash.avb_images."boot.img" = "7cdb8e4a9b79d9c2b4d5a513c219082c0609cf6cab8cf07e122dcaaa2bd1ed63"
hash.patched.full = "f2ac798b31a94dc251ca4ce370ebfb3073170d4a34431829df9ed0742149ffe7"
hash.patched.stripped = "4387a5ba30c925f56c67eeaf6512757d5c1e07dfd2a8a99be14e06db8ba2dde7"
hash.avb_images."boot.img" = "506a955080b6cfa2039ef85923e8a4e717ef6c1dc478538599c7ba26ee21e525"
hash.avb_images."vbmeta.img" = "3679c7224e3e3e0793b4d1a031e116098460a6b4c5f3f88d5a3a0a4c65b21582"
hash.avb_images."vbmeta_system.img" = "1d3efa00fd1d44a594c7317072468fa95c23d83d2759d6d6e757783ceeabc594"
+3 -2
View File
@@ -18,7 +18,7 @@ use std::{
};
use anyhow::{anyhow, bail, Context, Result};
use avbroot::stream::PSeekFile;
use avbroot::stream::{PSeekFile, Reopen};
use serde::{Deserialize, Serialize};
/// Minimum download chunk size per task.
@@ -309,7 +309,8 @@ fn download_ranges(
}
if let Some(thread_range) = remaining.pop_front() {
let file_cloned = file.reopen();
// PSeekFile's reopen can't fail.
let file_cloned = file.reopen().unwrap();
let thread_range_cloned = thread_range.clone();
let tx_cloned = tx.clone();
+3 -3
View File
@@ -27,7 +27,7 @@ use avbroot::{
cli::ota::{ExtractCli, PatchCli, VerifyCli},
format::{ota, payload::PayloadHeader},
protobuf::chromeos_update_engine::install_operation::Type,
stream::{self, FromReader, HashingReader, PSeekFile, SectionReader},
stream::{self, FromReader, HashingReader, PSeekFile, Reopen, SectionReader},
};
use clap::Parser;
use tempfile::TempDir;
@@ -98,7 +98,7 @@ fn strip_image(
let mut raw_reader = File::open(input)
.map(PSeekFile::new)
.with_context(|| format!("Failed to open for reading: {input:?}"))?;
let mut zip_reader = ZipArchive::new(BufReader::new(raw_reader.reopen()))
let mut zip_reader = ZipArchive::new(BufReader::new(raw_reader.reopen()?))
.with_context(|| format!("Failed to read zip: {input:?}"))?;
let payload_entry = zip_reader
.by_name(ota::PATH_PAYLOAD)
@@ -108,7 +108,7 @@ fn strip_image(
// Open the payload data directly.
let mut payload_reader = SectionReader::new(
BufReader::new(raw_reader.reopen()),
BufReader::new(raw_reader.reopen()?),
payload_offset,
payload_size,
)?;
+1 -1
View File
@@ -23,7 +23,7 @@ mod fuzz {
input.write_zeros_exact(fec.data_size).unwrap();
}
let _ = fec.verify(|| Ok(Box::new(input.reopen())), &cancel_signal);
let _ = fec.verify(&input, &cancel_signal);
}
});
}
+2 -2
View File
@@ -1,6 +1,6 @@
id=com.chiller3.avbroot.clearotacerts
name=clearotacerts
version=v2.2.0
versionCode=131584
version=v2.3.0
versionCode=131840
author=chenxiaolong
description=Block A/B OTAs by clearing verification certificates
+2 -2
View File
@@ -1,6 +1,6 @@
id=com.chiller3.avbroot.oemunlockonboot
name=oemunlockonboot
version=v2.2.0
versionCode=131584
version=v2.3.0
versionCode=131840
author=chenxiaolong
description=Enable OEM unlocking on every boot
+2 -1
View File
@@ -50,7 +50,7 @@ fn check_brackets(line: &str) -> Result<()> {
fn update_changelog_links(path: &Path, base_url: &str) -> Result<()> {
let re_standalone_link = Regex::new(r"\[([^\]]+)\]($|[^\(\[])")?;
let re_auto_link = Regex::new(r"^(Issue|PR) #([0-9]+)?$")?;
let re_auto_link = Regex::new(r"^(Discussion|Issue|PR) #([0-9]+)?$")?;
let mut links = BTreeMap::<LinkRef, String>::new();
let raw_reader = File::open(path)?;
@@ -82,6 +82,7 @@ fn update_changelog_links(path: &Path, base_url: &str) -> Result<()> {
let number: u32 = captures.get(2).unwrap().as_str().parse()?;
let link = match link_type {
"Discussion" => format!("{base_url}/discussions/{number}"),
"Issue" => format!("{base_url}/issues/{number}"),
"PR" => format!("{base_url}/pull/{number}"),
t => bail!("Unknown link type in {link_ref:?}: {t:?}"),
+1 -1
View File
@@ -20,7 +20,7 @@ fn update_cargo_version(version: &str) -> Result<()> {
let data = fs::read_to_string(&path)?;
let mut document: Document = data.parse()?;
document["workspace"]["package"]["version"] = value(version.clone());
document["workspace"]["package"]["version"] = value(version);
fs::write(path, document.to_string())?;