Compare commits

...

19 Commits

Author SHA1 Message Date
Andrew Gunnerson 2bac85f080 Version 3.19.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 23:23:18 -04:00
Andrew Gunnerson fa99a3bb98 CHANGELOG.md: Add entry for PR #479
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 23:20:12 -04:00
Andrew Gunnerson 0d5bc574b2 cli/ota: Re-sign signed image when header verification fails
Previously, unless forced, `avbroot avb pack` would only re-sign an
image if the packing process changed the header (eg. root digest).
However, this isn't sufficient when packing an image after the user
modifies avb.toml manually. This is especially the case when packing a
vbmeta image, which never triggered the old check because it does not
contain a raw image.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 23:14:06 -04:00
Andrew Gunnerson e34c48c92b CHANGELOG.md: Add entry for PR #478
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 23:13:47 -04:00
Andrew Gunnerson 8ea08ef98c cli/avb: Warn when verifying image with insecure flags
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 23:10:24 -04:00
Andrew Gunnerson 779b1116e1 CHANGELOG.md: Add entry for PR #477
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 23:09:24 -04:00
Andrew Gunnerson 821c5fe088 format/avb: Fix verifying unsigned AVB images
Previously, Header::verify() tried to always decode the public_key
field, even if the header was unsigned. This prevented verifying
unsigned images with `avbroot avb verify`. Verifying unsigned images
referenced by signed images was unaffected.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 23:04:01 -04:00
Andrew Gunnerson 6a1da333eb Version 3.18.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 01:53:44 -04:00
Andrew Gunnerson 256483d248 CHANGELOG.md: Add entry for PR #476
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 01:53:25 -04:00
Andrew Gunnerson 2314c371a8 sparse: Fix u32 overflow when unpacking files with large holes
CHUNK_TYPE_RAW is the only chunk type that's guaranteed to not overflow
a u32 when its number of blocks is multiplied by the block size.
CHUNK_TYPE_FILL and CHUNK_TYPE_DONT_CARE require a u64.

Issue: #472

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-07 01:36:22 -04:00
Andrew Gunnerson 59cf37faaf Version 3.18.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-06 21:41:13 -04:00
Andrew Gunnerson 36269acd7b CHANGELOG.md: Add entry for PR #475
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-06 21:37:45 -04:00
Andrew Gunnerson 6aacc5a76c Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-06 21:33:54 -04:00
Andrew Gunnerson bd4ebde403 CHANGELOG.md: Add entry for PR #474
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-06 21:30:23 -04:00
Andrew Gunnerson ffdae0bf88 Bump Magisk version upper bound to 30300
There are no breaking changes.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-06 21:28:33 -04:00
Andrew Gunnerson 8282f8087c CHANGELOG.md: Add entry for PR #473
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-06 21:27:46 -04:00
Andrew Gunnerson 6b11cd8af2 Allow using SHA-1 as AVB hash algorithm
Previously, we automatically promoted SHA-1 to SHA-256 because SHA-1 is
insecure, but there are devices that don't support SHA-256. It's safer
to just keep using the original hash algorithm.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-06 21:17:21 -04:00
Andrew Gunnerson f610f3b794 CHANGELOG.md: Add entry for PR #470
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-08-06 21:14:26 -04:00
Andrew Gunnerson b8d022d52c format/ota: Make property files verification more lenient
Previously, we computed the expected property files string (based on
AOSP's rules) and checked if the string in the OTA metadata was a
byte-for-byte match. This would fail for OTAs with strings that differ
from how AOSP generates them. This could be additional files or just
different ordering of the entries.

This commit changes the approach to just verify that the property file
entries is a valid subset of the zip file entries. We no longer try to
compute the expected value.

This does not change what avbroot generates when patching an OTA. Newly
generated property files strings always follow AOSP's rules.

Fixes: #469

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-07-22 20:49:05 -04:00
21 changed files with 376 additions and 233 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ jobs:
done
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
key: ${{ matrix.artifact.name }}
+1 -1
View File
@@ -13,4 +13,4 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run cargo-deny
uses: EmbarkStudios/cargo-deny-action@34899fc7ba81ca6268d5947a7a16b4649013fea1 # v2.0.11
uses: EmbarkStudios/cargo-deny-action@30f817c6f72275c6d54dc744fbca09ebc958599f # v2.0.12
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Create release
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
with:
tag_name: v${{ steps.get_version.outputs.version }}
name: Version ${{ steps.get_version.outputs.version }}
+30
View File
@@ -7,6 +7,26 @@
to update the actual links at the bottom of the file.
-->
### Version 3.19.0
* Allow verifying hashes of unsigned images with `avbroot avb verify` ([PR #477])
* Warn when verifying image containing insecure flags field with `avbroot avb verify` ([PR #478])
* Force re-signing of signed images in `avbroot avb pack` when fields were changed externally ([PR #479])
### Version 3.18.1
* Fix output file corruption in `avbroot sparse unpack` when unpacking a sparse file with holes larger than 2^32 ([Issue #472], [PR #476])
### Version 3.18.0
* Make OTA metadata property file field validation more lenient ([Issue #469], [PR #470])
* Fixes `avbroot ota verify` for stock OTAs that include extra zip file entries in the metadata
* Remove automatic promotion of insecure SHA-1 AVB hash algorithm to SHA-256 ([Issue #366], [Issue #469], [PR #473])
* There are insecure devices that don't support SHA-256 and won't boot with it.
* The original feature was a bandaid for OnePlus devices to make them a tiny bit more secure. They used SHA-256 for every partition except `system`. However, OnePlus no longer supports custom AVB keys anyway, so this feature is going away.
* Add support for Magisk 30200 ([PR #474])
* Update dependencies ([PR #475])
### Version 3.17.2
* Add support for Magisk 30100 ([PR #468])
@@ -370,6 +390,8 @@ Behind-the-scenes changes:
[Issue #433]: https://github.com/chenxiaolong/avbroot/issues/433
[Issue #441]: https://github.com/chenxiaolong/avbroot/issues/441
[Issue #451]: https://github.com/chenxiaolong/avbroot/issues/451
[Issue #469]: https://github.com/chenxiaolong/avbroot/issues/469
[Issue #472]: https://github.com/chenxiaolong/avbroot/issues/472
[PR #130]: https://github.com/chenxiaolong/avbroot/pull/130
[PR #132]: https://github.com/chenxiaolong/avbroot/pull/132
[PR #133]: https://github.com/chenxiaolong/avbroot/pull/133
@@ -537,3 +559,11 @@ Behind-the-scenes changes:
[PR #464]: https://github.com/chenxiaolong/avbroot/pull/464
[PR #467]: https://github.com/chenxiaolong/avbroot/pull/467
[PR #468]: https://github.com/chenxiaolong/avbroot/pull/468
[PR #470]: https://github.com/chenxiaolong/avbroot/pull/470
[PR #473]: https://github.com/chenxiaolong/avbroot/pull/473
[PR #474]: https://github.com/chenxiaolong/avbroot/pull/474
[PR #475]: https://github.com/chenxiaolong/avbroot/pull/475
[PR #476]: https://github.com/chenxiaolong/avbroot/pull/476
[PR #477]: https://github.com/chenxiaolong/avbroot/pull/477
[PR #478]: https://github.com/chenxiaolong/avbroot/pull/478
[PR #479]: https://github.com/chenxiaolong/avbroot/pull/479
Generated
+90 -73
View File
@@ -36,9 +36,9 @@ checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b"
[[package]]
name = "anstream"
version = "0.6.19"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -66,22 +66,22 @@ dependencies = [
[[package]]
name = "anstyle-query"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -113,7 +113,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "avbroot"
version = "3.17.2"
version = "3.19.0"
dependencies = [
"anyhow",
"assert_matches",
@@ -225,9 +225,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.18.1"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
@@ -304,9 +304,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.27"
version = "1.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
dependencies = [
"jobserver",
"libc",
@@ -337,9 +337,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.40"
version = "4.5.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -347,9 +347,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.40"
version = "4.5.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
dependencies = [
"anstream",
"anstyle",
@@ -359,23 +359,23 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.54"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677"
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -425,9 +425,9 @@ dependencies = [
[[package]]
name = "crc32fast"
version = "1.4.2"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
@@ -533,7 +533,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -544,7 +544,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -567,7 +567,7 @@ checksum = "ecb08c4819242b1ec89b3d0c6affa229005bef46ae4f7eed8b80768187c10087"
[[package]]
name = "e2e"
version = "3.17.2"
version = "3.19.0"
dependencies = [
"anyhow",
"avbroot",
@@ -662,7 +662,7 @@ dependencies = [
[[package]]
name = "fuzz"
version = "3.17.2"
version = "3.19.0"
dependencies = [
"avbroot",
"honggfuzz",
@@ -775,9 +775,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.9.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown",
@@ -861,9 +861,9 @@ dependencies = [
[[package]]
name = "libbz2-rs-sys"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775bf80d5878ab7c2b1080b5351a48b2f737d9f6f8b383574eebcc22be0dfccb"
checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
[[package]]
name = "libc"
@@ -940,7 +940,7 @@ dependencies = [
"quote",
"regex-syntax",
"rustc_version",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -975,9 +975,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "memmap2"
version = "0.9.5"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
dependencies = [
"libc",
]
@@ -1001,7 +1001,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -1176,7 +1176,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -1249,12 +1249,12 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.2.34"
version = "0.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2"
dependencies = [
"proc-macro2",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -1292,7 +1292,7 @@ dependencies = [
"prost",
"prost-types",
"regex",
"syn 2.0.103",
"syn 2.0.104",
"tempfile",
]
@@ -1306,14 +1306,14 @@ dependencies = [
"itertools",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
name = "prost-reflect"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e6b545c556471033fc1099868fca468bff8cf034f7bb9153b445f24d00aa28d"
checksum = "3c9ae1e4084d9737646934f5ac36a8d77f9a82f962f43e267a382fde4f2a903d"
dependencies = [
"logos",
"miette",
@@ -1498,15 +1498,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1568,14 +1568,14 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
dependencies = [
"serde",
]
@@ -1692,9 +1692,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.103"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
@@ -1731,7 +1731,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -1761,37 +1761,47 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
checksum = "17d3b47e6b7a040216ae5302712c94d1cf88c95b47efa80e2c59ce96c878267e"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
name = "toml_parser"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
[[package]]
name = "topological-sort"
@@ -1818,7 +1828,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -1952,7 +1962,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
"wasm-bindgen-shared",
]
@@ -1974,7 +1984,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2010,6 +2020,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -2034,7 +2050,7 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.2",
"windows-targets 0.53.3",
]
[[package]]
@@ -2055,10 +2071,11 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.53.2"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
@@ -2167,9 +2184,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
dependencies = [
"memchr",
]
@@ -2209,7 +2226,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "3.17.2"
version = "3.19.0"
dependencies = [
"anyhow",
"clap",
@@ -2234,7 +2251,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@@ -2254,7 +2271,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.17.2"
version = "3.19.0"
license = "GPL-3.0-only"
edition = "2024"
repository = "https://github.com/chenxiaolong/avbroot"
-2
View File
@@ -30,8 +30,6 @@ This subcommand packs a new AVB image from the `avb.toml` file and, for appended
* To force an image to be signed, use `--key <path> --force`.
* To force an image to be unsigned, use `--force` without specifying `--key`.
Note that if the image is an appended image and its hash or hash tree descriptor uses an insecure algorithm, like `sha1`, then it will automatically be promoted to `sha256`.
By default, for appended vbmeta images, the output image size will match the size of the original image that was unpacked. This size is specified by the `image_size` field in `avb.toml`. If the image is resizable (eg. `system`), then passing in `--recompute-size` will cause the `image_size` field to be ignored and the smallest possible output file that fits the raw image and AVB metadata will be built. This avoids wasting space if `raw.img` shrunk or allows the packing to work at all if `raw.img` grew. **Do not use this option for non-resizable images** (eg. `boot`) or else the device won't be able to boot.
When packing an image, several of the fields in `avb.toml` may potentially be recomputed. To write a TOML file containing the new values, use `--output-info <output TOML>`. It is safe to overwrite the existing `avb.toml` if desired.
+1 -1
View File
@@ -52,7 +52,7 @@ sha1 = "0.10.5"
sha2 = "0.10.7"
tempfile = "3.8.0"
thiserror = "2.0.3"
toml_edit = { version = "0.22.9", features = ["serde"] }
toml_edit = { version = "0.23.3", features = ["serde"] }
topological-sort = "0.2.2"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
+5 -16
View File
@@ -95,18 +95,6 @@ fn write_info(path: &Path, info: &AvbInfo) -> Result<()> {
Ok(())
}
/// Packing with insecure algorithms is intentionally not supported, so promote
/// to a secure algorithm if needed.
fn promote_insecure_hash_algorithm(algorithm: &mut String) {
const INSECURE_ALGORITHMS: &[&str] = &["sha1"];
const NEW_ALGORITHM: &str = "sha256";
if INSECURE_ALGORITHMS.contains(&algorithm.as_str()) {
warn!("Changing insecure hash algorithm {algorithm} to {NEW_ALGORITHM}");
NEW_ALGORITHM.clone_into(algorithm);
}
}
/// Copy `size` bytes from `reader` into a new file `path` that's opened as
/// both readable and writable.
fn write_raw(
@@ -194,13 +182,11 @@ fn write_raw_and_update(
match info.header.appended_descriptor_mut()? {
AppendedDescriptorMut::HashTree(d) => {
promote_insecure_hash_algorithm(&mut d.hash_algorithm);
d.image_size = image_size;
d.update(&raw_file, &raw_file, None, cancel_signal)
.context("Failed to update hash tree descriptor")?;
}
AppendedDescriptorMut::Hash(d) => {
promote_insecure_hash_algorithm(&mut d.hash_algorithm);
d.image_size = image_size;
raw_file.rewind()?;
d.update(&mut raw_file, cancel_signal)
@@ -345,7 +331,7 @@ fn sign_or_clear(info: &mut AvbInfo, orig_header: &Header, key_group: &KeyGroup)
} else {
SignAction::Clear
}
} else if originally_signed && &info.header != orig_header {
} else if originally_signed && (&info.header != orig_header || info.header.verify().is_err()) {
SignAction::Sign
} else {
// If the original image was signed, we can preserve the existing
@@ -477,6 +463,10 @@ pub fn verify_headers(
info!("{name} has an unsigned vbmeta header");
}
if header.flags != 0 {
warn!("{name} has insecure flags: {:#x}", header.flags);
}
for descriptor in &header.descriptors {
let Some(target_name) = descriptor.partition_name() else {
continue;
@@ -745,7 +735,6 @@ 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()? {
promote_insecure_hash_algorithm(&mut d.hash_algorithm);
d.update(&file, &file, None, cancel_signal)?;
}
+17 -34
View File
@@ -5,7 +5,6 @@ use std::{
borrow::Cow,
collections::{BTreeSet, HashMap, HashSet},
ffi::{OsStr, OsString},
fmt::Display,
fs::{self, File},
io::{self, BufReader, BufWriter, Read, Seek, SeekFrom, Write},
ops::Range,
@@ -53,28 +52,6 @@ use crate::{
util,
};
fn joined(into_iter: impl IntoIterator<Item = impl Display>) -> String {
use std::fmt::Write;
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> {
let mut items = iter.collect::<Vec<_>>();
items.sort();
items
}
bitflags! {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -226,7 +203,7 @@ fn patch_boot_images(
info!(
"Candidate boot images: {}",
joined(sorted(boot_partitions.iter())),
util::join(util::sort(boot_partitions.iter()), ", "),
);
boot::patch_boot_images(
@@ -249,7 +226,7 @@ fn patch_boot_images(
.with_context(|| {
format!(
"Failed to patch boot images: {}",
joined(sorted(boot_partitions.iter())),
util::join(util::sort(boot_partitions.iter()), ", "),
)
})?;
@@ -369,7 +346,7 @@ fn ensure_partitions_protected(
if !missing.is_empty() {
bail!(
"Found critical partitions that are not protected by AVB: {}",
joined(missing),
util::join(missing, ", "),
);
}
@@ -412,7 +389,10 @@ fn get_vbmeta_patch_order(
}
if !missing.is_empty() {
warn!("Partitions aren't protected by AVB: {}", joined(missing));
warn!(
"Partitions aren't protected by AVB: {}",
util::join(missing, ", "),
);
}
// Ensure that there's only a single root of trust. Otherwise, there could
@@ -428,7 +408,10 @@ fn get_vbmeta_patch_order(
// For zero roots, let TopologicalSort report the cycle.
if roots.len() > 1 {
bail!("Found multiple root vbmeta images: {}", joined(roots));
bail!(
"Found multiple root vbmeta images: {}",
util::join(roots, ", "),
);
}
// Compute the patching order. This only includes vbmeta images. All vbmeta
@@ -672,7 +655,7 @@ fn update_vbmeta_headers(
) -> Result<()> {
info!(
"Patching vbmeta images: {}",
joined(order.iter().map(|(n, _)| n)),
util::join(order.iter().map(|(n, _)| n), ", "),
);
for (name, deps) in order {
@@ -1142,7 +1125,7 @@ fn patch_ota_zip(
}
if !missing.is_empty() {
bail!("Missing entries in OTA zip: {}", joined(missing));
bail!("Missing entries in OTA zip: {}", util::join(missing, ", "));
} else if !paths.contains(ota::PATH_METADATA) && !paths.contains(ota::PATH_METADATA_PB) {
bail!(
"Neither legacy nor protobuf OTA metadata files exist: {:?}, {:?}",
@@ -1274,7 +1257,7 @@ fn patch_ota_zip(
let size = writer.stream_position()?;
entries.push(ZipEntry {
name: path.clone(),
path: path.clone(),
offset,
size,
});
@@ -1323,7 +1306,7 @@ pub fn extract_payload(
}
}
info!("Extracting from the payload: {}", joined(images));
info!("Extracting from the payload: {}", util::join(images, ", "));
// Pre-open all output files.
let output_files = images
@@ -1676,7 +1659,7 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
.collect::<Vec<_>>();
if !missing_images.is_empty() {
bail!("Invalid partitions: {}", joined(missing_images));
bail!("Invalid partitions: {}", util::join(missing_images, ", "));
}
unique_images.extend(cli.extract.partition.iter().cloned());
@@ -1936,7 +1919,7 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
.with_context(|| format!("Failed to parse property files: {}", ota::PF_NAME))?;
let pf_payload = pfs
.iter()
.find(|pf| pf.name == ota::PATH_PAYLOAD)
.find(|pf| pf.name() == ota::PATH_PAYLOAD)
.ok_or_else(|| anyhow!("Missing property files entry: {}", ota::PATH_PAYLOAD))?;
let section_reader = SectionReader::new(&mut reader, pf_payload.offset, pf_payload.size)
+4 -4
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2024-2025 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -317,11 +317,11 @@ fn unpack_subcommand(
})?;
}
ChunkData::Hole => {
// This cannot overflow.
let to_skip = chunk.bounds.len() * metadata.header.block_size;
// Unlike ChunkData::Data, this can overflow a u32.
let to_skip = i64::from(chunk.bounds.len()) * i64::from(metadata.header.block_size);
writer
.seek(SeekFrom::Current(to_skip.into()))
.seek(SeekFrom::Current(to_skip))
.with_context(|| format!("Failed to seek file: {:?}", cli.output))?;
}
ChunkData::Crc32(_) => {}
+15 -12
View File
@@ -163,9 +163,9 @@ pub enum Error {
type Result<T> = std::result::Result<T, Error>;
pub(crate) fn digest_algorithm(name: &str, for_verify: bool) -> Result<&'static Algorithm> {
pub(crate) fn digest_algorithm(name: &str) -> Result<&'static Algorithm> {
match name {
"sha1" if for_verify => Ok(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY),
"sha1" => 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())),
@@ -534,7 +534,7 @@ impl HashTreeDescriptor {
ranges: Option<&[Range<u64>]>,
cancel_signal: &AtomicBool,
) -> Result<()> {
let algorithm = digest_algorithm(&self.hash_algorithm, false)?;
let algorithm = digest_algorithm(&self.hash_algorithm)?;
let hash_tree = HashTree::new(self.data_block_size, algorithm, &self.salt);
let (root_digest, hash_tree_data) = match ranges {
Some(r) => {
@@ -642,7 +642,7 @@ impl HashTreeDescriptor {
) -> Result<()> {
self.check_offsets()?;
let algorithm = digest_algorithm(&self.hash_algorithm, true)?;
let algorithm = digest_algorithm(&self.hash_algorithm)?;
util::check_bounds(self.tree_size, ..=HASH_TREE_MAX_SIZE)
.map_err(|e| Error::IntOutOfBounds("HashTree::tree_size", e))?;
@@ -903,10 +903,9 @@ impl HashDescriptor {
fn calculate(
&self,
reader: impl Read,
for_verify: bool,
cancel_signal: &AtomicBool,
) -> Result<ring::digest::Digest> {
let algorithm = digest_algorithm(&self.hash_algorithm, for_verify)?;
let algorithm = digest_algorithm(&self.hash_algorithm)?;
let mut context = Context::new(algorithm);
context.update(&self.salt);
@@ -924,14 +923,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, false, cancel_signal)?;
let digest = self.calculate(reader, 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, true, cancel_signal)?;
let digest = self.calculate(reader, cancel_signal)?;
if self.root_digest != digest.as_ref() {
return Err(Error::InvalidRootDigest {
@@ -1759,16 +1758,20 @@ impl Header {
/// and return the public key. If the header is not signed, then `None` is
/// returned.
pub fn verify(&self) -> Result<Option<RsaPublicKey>> {
// Reconstruct the public key.
let public_key = decode_public_key(&self.public_key)?;
if self.public_key.len() != self.algorithm_type.public_key_len() {
return Err(Error::IncorrectKeySize(
public_key.size(),
self.public_key.len(),
self.algorithm_type,
));
}
if self.algorithm_type == AlgorithmType::None {
return Ok(None);
}
// Reconstruct the public key.
let public_key = decode_public_key(&self.public_key)?;
let mut without_auth_writer = Cursor::new(Vec::new());
self.to_writer_internal(&mut without_auth_writer, true)?;
let without_auth = without_auth_writer.into_inner();
+1 -1
View File
@@ -493,7 +493,7 @@ impl HashTreeImage {
const VERSION: u16 = 1;
fn digest_algorithm(name: &str) -> Result<&'static Algorithm> {
avb::digest_algorithm(name, false)
avb::digest_algorithm(name)
.map_err(|_| Error::UnsupportedHashAlgorithm(name.to_owned().into_bytes()))
}
+173 -62
View File
@@ -2,11 +2,13 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::{
cmp::Ordering,
collections::BTreeMap,
fmt,
fmt::{self, Write as _},
io::{self, Cursor, Read, Seek, SeekFrom, Write},
iter,
path::Path,
str::FromStr,
sync::atomic::AtomicBool,
};
@@ -28,6 +30,7 @@ use crate::{
},
protobuf::build::tools::releasetools::{OtaMetadata, ota_metadata::OtaType},
stream::{self, FromReader, HashingReader, HashingWriter, ReadFixedSizeExt},
util,
};
pub const PATH_METADATA: &str = "META-INF/com/android/metadata";
@@ -73,8 +76,12 @@ pub enum Error {
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("Mismatched {key:?} entry offsets: zip only: {zip_only:?}, prop only: {prop_only:?}")]
MismatchedPropertyFiles {
key: String,
zip_only: String,
prop_only: String,
},
#[error("Property files {value:?} exceed {reserved} byte reserved space")]
InsufficientReservedSpace { value: String, reserved: usize },
#[error("Invalid property file entry: {0:?}")]
@@ -283,16 +290,48 @@ fn serialize_metadata(metadata: &OtaMetadata) -> (String, Vec<u8>) {
#[derive(Clone, Debug)]
pub struct ZipEntry {
pub name: String,
pub path: String,
pub offset: u64,
pub size: u64,
}
/// Parse OTA property files string.
pub fn parse_property_files(data: &str) -> Result<Vec<ZipEntry>> {
let mut result = vec![];
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PropEntry {
name: String,
pub offset: u64,
pub size: u64,
}
for entry in data.trim_end().split(',') {
impl PropEntry {
pub fn new(path: &str, offset: u64, size: u64) -> Self {
Self {
name: property_file_name(path).to_owned(),
offset,
size,
}
}
pub fn name(&self) -> &str {
&self.name
}
}
impl From<&ZipEntry> for PropEntry {
fn from(entry: &ZipEntry) -> Self {
Self::new(&entry.path, entry.offset, entry.size)
}
}
impl fmt::Display for PropEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.name, self.offset, self.size)
}
}
impl FromStr for PropEntry {
type Err = Error;
fn from_str(entry: &str) -> Result<Self> {
let mut pieces = entry.split(':');
let name = pieces
@@ -312,17 +351,31 @@ pub fn parse_property_files(data: &str) -> Result<Vec<ZipEntry>> {
return Err(Error::InvalidPropertyFileEntry(entry.to_owned()));
}
result.push(ZipEntry { name, offset, size });
Ok(Self { name, offset, size })
}
}
/// Parse OTA property files string.
pub fn parse_property_files(data: &str) -> Result<Vec<PropEntry>> {
let mut result = vec![];
for entry in data.trim_end().split(',') {
result.push(entry.parse()?);
}
Ok(result)
}
/// Get the filename for use in property files entries.
fn property_file_name(path: &str) -> &str {
path.rsplit_once('/').map_or(path, |p| p.1)
}
/// Compute the property files entries listing the offsets and sizes to every
/// zip entry.
fn compute_property_files(
pf_name: &str,
entries: &[ZipEntry],
entries: &[PropEntry],
max_length: Option<usize>,
want_pb: bool,
) -> Result<String> {
@@ -335,24 +388,26 @@ fn compute_property_files(
// writes, so we reserve an additional byte to allow offsets <100 GB.
const RESERVATION_SIZE: usize = 16;
let compute = |path: &'static str| -> Result<String> {
let mut buf = String::new();
let mut append = |path: &'static str| -> Result<()> {
let name = property_file_name(path);
let entry = entries
.iter()
.find(|e| e.name == path)
.find(|e| e.name == name)
.ok_or(Error::MissingZipEntry(path))?;
let name = path.rsplit_once('/').map_or(path, |p| p.1);
Ok(format!("{name}:{}:{}", entry.offset, entry.size))
let _ = write!(&mut buf, "{entry},");
Ok(())
};
let mut tokens = vec![];
if pf_name == PF_NAME {
tokens.push(compute(NAME_PAYLOAD_METADATA)?);
append(NAME_PAYLOAD_METADATA)?;
}
for path in [PATH_PAYLOAD, PATH_PROPERTIES] {
tokens.push(compute(path)?);
append(path)?;
}
for path in [
@@ -361,44 +416,51 @@ fn compute_property_files(
"care_map.txt",
"compatibility.zip",
] {
if let Ok(token) = compute(path) {
tokens.push(token);
}
// These are optional.
let _ = append(path);
}
if max_length.is_none() {
tokens.push(format!("metadata:{}", " ".repeat(RESERVATION_SIZE)));
buf.push_str(property_file_name(PATH_METADATA));
buf.push(':');
buf.extend(iter::repeat_n(' ', RESERVATION_SIZE));
buf.push(',');
if want_pb {
tokens.push(format!("metadata.pb:{}", " ".repeat(RESERVATION_SIZE)));
buf.push_str(property_file_name(PATH_METADATA_PB));
buf.push(':');
buf.extend(iter::repeat_n(' ', RESERVATION_SIZE));
buf.push(',');
}
} else {
tokens.push(compute(PATH_METADATA)?);
append(PATH_METADATA)?;
if want_pb {
tokens.push(compute(PATH_METADATA_PB)?);
append(PATH_METADATA_PB)?;
}
}
let mut joined = tokens.join(",");
// Strip final trailing comma.
buf.pop();
if let Some(l) = max_length {
if joined.len() > l {
if buf.len() > l {
return Err(Error::InsufficientReservedSpace {
value: joined,
value: buf,
reserved: l,
});
}
let remain = l - joined.len();
joined.extend(iter::repeat_n(' ', remain));
let remain = l - buf.len();
buf.extend(iter::repeat_n(' ', remain));
}
Ok(joined)
Ok(buf)
}
// Add fake payload_metadata.bin entry, covering the header + header signature
// regions of the payload.
fn add_payload_metadata_entry(
entries: &mut Vec<ZipEntry>,
entries: &mut Vec<PropEntry>,
payload_metadata_size: u64,
) -> Result<()> {
let payload_offset = entries
@@ -406,11 +468,11 @@ fn add_payload_metadata_entry(
.find(|e| e.name == PATH_PAYLOAD)
.ok_or(Error::MissingZipEntry(PATH_PAYLOAD))?
.offset;
entries.push(ZipEntry {
name: NAME_PAYLOAD_METADATA.to_owned(),
offset: payload_offset,
size: payload_metadata_size,
});
entries.push(PropEntry::new(
NAME_PAYLOAD_METADATA,
payload_offset,
payload_metadata_size,
));
Ok(())
}
@@ -449,8 +511,8 @@ pub fn add_metadata(
.last_modified_time(DateTime::default())
.compression_method(CompressionMethod::Stored);
let mut zip_entries = zip_entries.to_owned();
add_payload_metadata_entry(&mut zip_entries, payload_metadata_size)?;
let mut prop_entries = zip_entries.iter().map(PropEntry::from).collect();
add_payload_metadata_entry(&mut prop_entries, payload_metadata_size)?;
// Compute initial property files with reserved space as placeholders to
// store the self-referential metadata entries later.
@@ -458,7 +520,7 @@ pub fn add_metadata(
for pf in [PF_NAME, PF_STREAMING_NAME] {
metadata.property_files.insert(
pf.to_owned(),
compute_property_files(pf, &zip_entries, None, true)?,
compute_property_files(pf, &prop_entries, None, true)?,
);
}
@@ -485,23 +547,23 @@ pub fn add_metadata(
.write_all(&modern_raw)
.map_err(|e| Error::ZipEntryWrite(PATH_METADATA_PB, e))?;
zip_entries.push(ZipEntry {
name: PATH_METADATA.to_owned(),
offset: next_offset + legacy_offset,
size: legacy_raw.len() as u64,
});
zip_entries.push(ZipEntry {
name: PATH_METADATA_PB.to_owned(),
offset: next_offset + modern_offset,
size: modern_raw.len() as u64,
});
prop_entries.push(PropEntry::new(
PATH_METADATA,
next_offset + legacy_offset,
legacy_raw.len() as u64,
));
prop_entries.push(PropEntry::new(
PATH_METADATA_PB,
next_offset + modern_offset,
modern_raw.len() as u64,
));
(next_offset + legacy_offset, next_offset + modern_offset)
};
// Compute the final property files using the offsets of the fake entries.
for (key, value) in &mut metadata.property_files {
*value = compute_property_files(key, &zip_entries, Some(value.len()), true)?;
*value = compute_property_files(key, &prop_entries, Some(value.len()), true)?;
}
// Add the final metadata files to the real zip.
@@ -542,24 +604,73 @@ pub fn verify_metadata(
let entry = zip_reader
.by_index(i)
.map_err(|e| Error::ZipIndexOpen(i, e))?;
zip_entries.push(ZipEntry {
name: entry.name().to_owned(),
offset: entry.data_start(),
size: entry.size(),
});
if entry.compression() != CompressionMethod::Stored {
continue;
}
zip_entries.push(PropEntry::new(
entry.name(),
entry.data_start(),
entry.size(),
));
}
add_payload_metadata_entry(&mut zip_entries, payload_metadata_size)?;
let metadata_pb = zip_entries.iter().find(|e| e.name == PATH_METADATA_PB);
zip_entries.sort_by(|a, b| a.name.cmp(&b.name));
for (key, value) in &metadata.property_files {
let new_value =
compute_property_files(key, &zip_entries, Some(value.len()), metadata_pb.is_some())?;
if *value != new_value {
let mut prop_entries = parse_property_files(value)?;
prop_entries.sort_by(|a, b| a.name.cmp(&b.name));
// Check that this is a subset of the actual entries.
let mut zip_iter = zip_entries.iter().peekable();
let mut prop_iter = prop_entries.iter().peekable();
let mut zip_only = vec![];
let mut prop_only = vec![];
loop {
match (zip_iter.peek(), prop_iter.peek()) {
(Some(&zip), Some(&prop)) => match zip.name.cmp(&prop.name) {
Ordering::Less => {
// Exists in zip, but not in property files.
zip_iter.next();
}
Ordering::Equal => {
// If the zip had multiple files with the same filename,
// but in different directories, this will fail.
if zip != prop {
zip_only.push(zip);
prop_only.push(prop);
}
zip_iter.next();
prop_iter.next();
}
Ordering::Greater => {
// Exists in property files, but not in zip.
prop_only.push(prop);
prop_iter.next();
}
},
(Some(_), None) => {
// Exists in zip, but not in property files.
zip_iter.next();
}
(None, Some(prop)) => {
// Exists in property files, but not in zip.
prop_only.push(prop);
prop_iter.next();
}
(None, None) => break,
}
}
if !zip_only.is_empty() || !prop_only.is_empty() {
return Err(Error::MismatchedPropertyFiles {
expected: value.clone(),
actual: new_value,
key: key.clone(),
zip_only: util::join(zip_only.into_iter().map(|e| e.to_string()), ","),
prop_only: util::join(prop_only.into_iter().map(|e| e.to_string()), ","),
});
}
}
+4 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2024-2025 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
@@ -374,6 +374,9 @@ impl fmt::Debug for ChunkData {
/// metadata they contain.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Chunk {
/// When [`Self::data`] is [`ChunkData::Data`], this is guaranteed to not
/// exceed the bounds of [`u32`] when multiplied by [`Header::block_size`].
/// For other types of data, a 64-bit signed or unsigned integer is needed.
pub bounds: ChunkBounds,
pub data: ChunkData,
}
+1 -1
View File
@@ -194,7 +194,7 @@ impl MagiskRootPatcher {
// replaced by PREINITDEVICE
// - Versions newer than the latest supported version are assumed to support
// the same features as the latest version
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..30200];
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..30300];
const VER_PREINIT_DEVICE: RangeFrom<u32> = 25211..;
const VER_RANDOM_SEED: Range<u32> = 25211..26103;
const VER_PATCH_VBMETA: Range<u32> = Self::VERS_SUPPORTED[0].start..26202;
+3 -17
View File
@@ -190,23 +190,9 @@ pub fn patch_system_image(
return Err(Error::OldZipNotFound);
}
let update_ranges = if descriptor.hash_algorithm == "sha1" {
// Promote to a secure algorithm. SHA1 is allowed for verification only.
// The entire hash tree and FEC data will need to be recomputed.
let new_algorithm = "sha256".to_owned();
debug!(
"Changing insecure hash algorithm {} to {new_algorithm}",
descriptor.hash_algorithm,
);
descriptor.hash_algorithm = new_algorithm;
None
} else {
// Only need to update the hash tree and FEC data corresponding to the
// modified regions.
Some(modified_ranges.as_slice())
};
// Only need to update the hash tree and FEC data corresponding to the
// modified regions.
let update_ranges = Some(modified_ranges.as_slice());
descriptor
.update(input, output, update_ranges, cancel_signal)
+25 -2
View File
@@ -1,9 +1,10 @@
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
cmp::Ordering,
fmt, mem,
fmt::{self, Display},
mem,
ops::{
Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
},
@@ -378,6 +379,28 @@ where
.is_ok()
}
pub fn join(into_iter: impl IntoIterator<Item = impl Display>, sep: &str) -> String {
use std::fmt::Write;
let mut result = String::new();
for (i, item) in into_iter.into_iter().enumerate() {
if i > 0 {
result.push_str(sep);
}
write!(result, "{item}").expect("Failed to allocate");
}
result
}
pub fn sort<T: Ord>(iter: impl Iterator<Item = T>) -> Vec<T> {
let mut items = iter.collect::<Vec<_>>();
items.sort();
items
}
#[cfg(test)]
mod tests {
use super::*;
+1 -1
View File
@@ -18,7 +18,7 @@ ring = "0.17.14"
rsa = { version = "0.9.6", features = ["hazmat"] }
serde = { version = "1.0.188", features = ["derive"] }
tempfile = "3.8.0"
toml_edit = { version = "0.22.9", features = ["serde"] }
toml_edit = { version = "0.23.3", features = ["serde"] }
topological-sort = "0.2.2"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
+1 -1
View File
@@ -797,7 +797,7 @@ fn create_ota(
let size = writer.stream_position()?;
entries.push(ZipEntry {
name: path.to_owned(),
path: path.to_owned(),
offset,
size,
});
+1 -1
View File
@@ -12,7 +12,7 @@ publish = false
anyhow = "1.0.75"
clap = { version = "4.4.1", features = ["derive"] }
regex = { version = "1.9.4", default-features = false, features = ["perf", "std"] }
toml_edit = "0.22.9"
toml_edit = "0.23.3"
[lints]
workspace = true