mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bde8dbfbcd | |||
| fbafbafe90 | |||
| a14cab71e5 | |||
| c2f4297bb7 | |||
| 9d63c9759d | |||
| 73b893f3ed | |||
| fd7bbf5751 | |||
| 409867a8e5 | |||
| f96a2887df | |||
| 8140e25620 | |||
| ce05477ab1 | |||
| 1c4800c0cb | |||
| d402fdf5f1 | |||
| 4930527598 | |||
| 9b970c6d25 | |||
| e7fb4004cd | |||
| 2773dcddcf | |||
| 36765290fd | |||
| 6d8268e472 | |||
| 2e8e86766b | |||
| 8ae1c54c13 | |||
| 5212d4de51 | |||
| 61bb9d6a81 | |||
| 6d86fbf8a0 | |||
| 621ccc254c | |||
| f41ef844e0 | |||
| 42f8769e48 | |||
| d2187676d1 | |||
| 3de5194c46 | |||
| e0c1d4ad3a | |||
| c94af49009 | |||
| 5ed7dd7dca |
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:?}"),
|
||||
|
||||
@@ -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())?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user