mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c019ccad1c | |||
| 192e737dd3 | |||
| 339267149f | |||
| 21c2536759 | |||
| 185f02c209 | |||
| a8908d6d06 | |||
| 2683781737 | |||
| 44b90936bf | |||
| d7369e73e9 | |||
| bb5c97ea1b | |||
| ac95660e23 | |||
| 2bac85f080 | |||
| fa99a3bb98 | |||
| 0d5bc574b2 | |||
| e34c48c92b | |||
| 8ea08ef98c | |||
| 779b1116e1 | |||
| 821c5fe088 |
@@ -92,7 +92,7 @@ jobs:
|
||||
run: |
|
||||
for target in ${TARGETS}; do
|
||||
cargo android \
|
||||
clippy --release --workspace --features static \
|
||||
clippy --release --workspace \
|
||||
--target "${target}"
|
||||
done
|
||||
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
run: |
|
||||
for target in ${TARGETS}; do
|
||||
cargo android \
|
||||
build --release --workspace --features static \
|
||||
build --release --workspace \
|
||||
--target "${target}"
|
||||
done
|
||||
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
run: |
|
||||
for target in ${TARGETS}; do
|
||||
cargo android \
|
||||
test --release --workspace --features static \
|
||||
test --release --workspace \
|
||||
--target "${target}"
|
||||
done
|
||||
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
run: |
|
||||
for target in ${TARGETS}; do
|
||||
cargo android \
|
||||
run --release -p e2e --features static \
|
||||
run --release -p e2e \
|
||||
--target "${target}" \
|
||||
-- test -a -c e2e/e2e.toml
|
||||
done
|
||||
|
||||
@@ -7,6 +7,20 @@
|
||||
to update the actual links at the bottom of the file.
|
||||
-->
|
||||
|
||||
### Version 3.20.0
|
||||
|
||||
* Switch to using lzma-rust2 for XZ compression and decompression ([PR #483])
|
||||
* Remove cap-std and cap-tempfile dependencies ([PR #484])
|
||||
* Add new `avbroot avb verify-device` command to verify the signatures on the actual device ([Issue #482], [PR #485], [PR #486])
|
||||
* This is only available in the Android build of avbroot.
|
||||
* Update dependencies and fix new Rust 1.89 clippy lints ([PR #487])
|
||||
|
||||
### 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])
|
||||
@@ -386,6 +400,7 @@ Behind-the-scenes changes:
|
||||
[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
|
||||
[Issue #482]: https://github.com/chenxiaolong/avbroot/issues/482
|
||||
[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
|
||||
@@ -558,3 +573,11 @@ Behind-the-scenes changes:
|
||||
[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
|
||||
[PR #483]: https://github.com/chenxiaolong/avbroot/pull/483
|
||||
[PR #484]: https://github.com/chenxiaolong/avbroot/pull/484
|
||||
[PR #485]: https://github.com/chenxiaolong/avbroot/pull/485
|
||||
[PR #486]: https://github.com/chenxiaolong/avbroot/pull/486
|
||||
[PR #487]: https://github.com/chenxiaolong/avbroot/pull/487
|
||||
|
||||
Generated
+147
-247
@@ -28,12 +28,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ambient-authority"
|
||||
version = "0.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.20"
|
||||
@@ -113,7 +107,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "avbroot"
|
||||
version = "3.18.1"
|
||||
version = "3.20.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
@@ -121,8 +115,6 @@ dependencies = [
|
||||
"bitflags",
|
||||
"bstr",
|
||||
"bzip2",
|
||||
"cap-std",
|
||||
"cap-tempfile",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"cms",
|
||||
@@ -135,8 +127,8 @@ dependencies = [
|
||||
"gf256",
|
||||
"hex",
|
||||
"libc",
|
||||
"liblzma",
|
||||
"lz4_flex",
|
||||
"lzma-rust2",
|
||||
"memchr",
|
||||
"num-bigint-dig",
|
||||
"num-traits",
|
||||
@@ -155,6 +147,7 @@ dependencies = [
|
||||
"serde",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"system-properties",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"toml_edit",
|
||||
@@ -185,6 +178,26 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.71.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
@@ -250,49 +263,6 @@ dependencies = [
|
||||
"libbz2-rs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-primitives"
|
||||
version = "3.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a1e394ed14f39f8bc26f59d4c0c010dbe7f0a1b9bafff451b1f98b67c8af62a"
|
||||
dependencies = [
|
||||
"ambient-authority",
|
||||
"fs-set-times",
|
||||
"io-extras",
|
||||
"io-lifetimes",
|
||||
"ipnet",
|
||||
"maybe-owned",
|
||||
"rustix",
|
||||
"rustix-linux-procfs",
|
||||
"windows-sys 0.59.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-std"
|
||||
version = "3.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07c0355ca583dd58f176c3c12489d684163861ede3c9efa6fd8bba314c984189"
|
||||
dependencies = [
|
||||
"cap-primitives",
|
||||
"io-extras",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-tempfile"
|
||||
version = "3.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bdc50d18ee6c3551b30eb7ad5c4628d7c73ed9e1696b63c432a55602d634d7d"
|
||||
dependencies = [
|
||||
"cap-std",
|
||||
"rand",
|
||||
"rustix",
|
||||
"rustix-linux-procfs",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
@@ -304,15 +274,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.31"
|
||||
version = "1.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
||||
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
@@ -335,6 +312,17 @@ dependencies = [
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.43"
|
||||
@@ -359,9 +347,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.55"
|
||||
version = "4.5.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
|
||||
checksum = "67e4efcbb5da11a92e8a609233aa1e8a7d91e38de0be865f016d14700d45a7fd"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
@@ -423,6 +411,21 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@@ -567,7 +570,7 @@ checksum = "ecb08c4819242b1ec89b3d0c6affa229005bef46ae4f7eed8b80768187c10087"
|
||||
|
||||
[[package]]
|
||||
name = "e2e"
|
||||
version = "3.18.1"
|
||||
version = "3.20.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"avbroot",
|
||||
@@ -649,20 +652,9 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fs-set-times"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuzz"
|
||||
version = "3.18.1"
|
||||
version = "3.20.0"
|
||||
dependencies = [
|
||||
"avbroot",
|
||||
"honggfuzz",
|
||||
@@ -725,10 +717,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -793,34 +791,21 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-extras"
|
||||
version = "0.18.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983"
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
@@ -830,26 +815,6 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -872,23 +837,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "liblzma"
|
||||
version = "0.4.2"
|
||||
name = "libloading"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8"
|
||||
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||
dependencies = [
|
||||
"liblzma-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "liblzma-sys"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"cfg-if",
|
||||
"windows-targets 0.53.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -920,18 +875,18 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "logos"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab6f536c1af4c7cc81edf73da1f8029896e7e1e16a219ef09b184e76a296f3db"
|
||||
checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154"
|
||||
dependencies = [
|
||||
"logos-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logos-codegen"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "189bbfd0b61330abea797e5e9276408f2edbe4f822d7ad08685d67419aafb34e"
|
||||
checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c"
|
||||
dependencies = [
|
||||
"beef",
|
||||
"fnv",
|
||||
@@ -945,9 +900,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "logos-derive"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebfe8e1a19049ddbfccbd14ac834b215e11b85b90bab0c2dba7c7b92fb5d5cba"
|
||||
checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470"
|
||||
dependencies = [
|
||||
"logos-codegen",
|
||||
]
|
||||
@@ -962,10 +917,14 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maybe-owned"
|
||||
version = "0.3.4"
|
||||
name = "lzma-rust2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4"
|
||||
checksum = "a82192ab5b40bc95fff6e8a61e099e421f07055778b2f5261297c102137b6081"
|
||||
dependencies = [
|
||||
"crc",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -1004,6 +963,12 @@ dependencies = [
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -1031,6 +996,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@@ -1232,12 +1207,6 @@ dependencies = [
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@@ -1259,9 +1228,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1283,7 +1252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"itertools",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
@@ -1303,7 +1272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
@@ -1487,6 +1456,12 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@@ -1509,22 +1484,6 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix-linux-procfs"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "salsa20"
|
||||
version = "0.10.2"
|
||||
@@ -1701,6 +1660,26 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-properties"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/chenxiaolong/system-properties?tag=v0.2.1#1c7bf60d0912e74034804bb7f088fbad8f0a645e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"libc",
|
||||
"system-properties-bindgen",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-properties-bindgen"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/chenxiaolong/system-properties?tag=v0.2.1#1c7bf60d0912e74034804bb7f088fbad8f0a645e"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
@@ -1902,17 +1881,6 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
@@ -1940,64 +1908,6 @@ dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -2191,16 +2101,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winx"
|
||||
version = "0.36.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
@@ -2226,7 +2126,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "3.18.1"
|
||||
version = "3.20.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.18.1"
|
||||
version = "3.20.0"
|
||||
license = "GPL-3.0-only"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/chenxiaolong/avbroot"
|
||||
|
||||
+12
-1
@@ -58,12 +58,23 @@ This subcommand shows all of the vbmeta header and footer fields. `vbmeta` parti
|
||||
avbroot avb verify -i <root vbmeta image> -p <public key>
|
||||
```
|
||||
|
||||
This subcommand verifies the vbmeta header signature and the hashes for all vbmeta descriptors (including hash tree descriptors). If the vbmeta image has a chain descriptor for another partition, that partition image will be verified as well (recursively). All partitions are expected to be in the same directory as the vbmeta image being verified.
|
||||
This subcommand verifies the vbmeta header signature and the hashes for all vbmeta descriptors (including hash tree descriptors). If the vbmeta image has a chain descriptor for another partition, that partition image will be verified as well (recursively). All images are expected to be in the same directory as the vbmeta image being verified. Missing images are ignored by default because the vbmeta images in some OTAs reference partitions that only exist on a real device. `--fail-if-missing` can be used to override this.
|
||||
|
||||
If `-p` is omitted, the signatures and hashes are checked only for validity, not that they are trusted.
|
||||
|
||||
By default, this command will not write to any file and fails if an image is corrupt or invalid. To attempt to repair corrupted dm-verity images, pass in `--repair`.
|
||||
|
||||
### Verifying AVB hashes and signatures on device
|
||||
|
||||
```bash
|
||||
# Run from a root adb shell:
|
||||
avbroot avb verify-device [-p <public key>]
|
||||
```
|
||||
|
||||
This subcommand is like `avbroot avb verify`, except that it verifies the actual partitions on the device instead of a directory of image files. This is only available in the Android build of avbroot.
|
||||
|
||||
If `-p` is omitted, the signatures are verified against the public key SHA-256 digest reported by the bootloader. This is the same digest shown on screen every time the device boots.
|
||||
|
||||
### Computing vbmeta digest
|
||||
|
||||
```bash
|
||||
|
||||
@@ -213,6 +213,8 @@ If you lose your AVB or OTA signing key, you will no longer be able to sign new
|
||||
init: [libfs_avb]Returning avb_handle with status: Success
|
||||
```
|
||||
|
||||
Alternatively, the Android build of avbroot can also be used to [verify the partitions on the device](./README.extra.md#verifying-avb-hashes-and-signatures-on-device).
|
||||
|
||||
9. Reboot back into fastboot and lock the bootloader. This will trigger a data wipe again.
|
||||
|
||||
```bash
|
||||
@@ -588,8 +590,6 @@ The output binary is written to `target/release/avbroot`.
|
||||
|
||||
Debug builds work too, but they will run significantly slower (in the sha256 computations) due to compiler optimizations being turned off.
|
||||
|
||||
By default, the executable links to the system's bzip2 and liblzma libraries, which are the only external libraries avbroot depends on. To compile and statically link these two libraries, pass in `--features static`.
|
||||
|
||||
### Android cross-compilation
|
||||
|
||||
To cross-compile for Android, install [cargo-android](https://github.com/chenxiaolong/cargo-android) and use the `cargo android` wrapper. To make a release build for aarch64, run:
|
||||
|
||||
+10
-12
@@ -391,17 +391,17 @@ avbroot можно использовать для простого перепо
|
||||
Если вы используете аргумент `--skip-recovery-ota-cert`, потому что уже добавили сертификат OTA в загрузочный образ вручную, рекомендуетcя [проверить пропатченный OTA](#проверка-ota), дабы удостовериться, что замена произведена корректно. Процесс верификации проверяет только копию сертификатов OTA в загрузочном образе, не проверяя копию в образе системы.
|
||||
|
||||
### Пропуск всех патчей
|
||||
|
||||
|
||||
Чтобы внести самый минимум изменений, укажите аргументы:
|
||||
|
||||
|
||||
* `--skip-system-ota-cert`
|
||||
* `--skip-recovery-ota-cert`
|
||||
* `--rootless`
|
||||
* не используйте аргумент `--dsu`.
|
||||
|
||||
|
||||
Так, пользовательскими ключами будут переподписаны лишь образ `vbmeta` и OTA, остальные разделы останутся нетронутыми.
|
||||
|
||||
**Это следует использовать только для устранения неполадок.** Без патчей сертификатов, поверх полученного OTA не получится установить никакие обновления.
|
||||
|
||||
**Это следует использовать только для устранения неполадок.** Без патчей сертификатов, поверх полученного OTA не получится установить никакие обновления.
|
||||
|
||||
### Подмена образов
|
||||
|
||||
@@ -422,15 +422,15 @@ Verified boot is disabled by vbmeta's header flags: 0x3
|
||||
Чтобы принудительно включить AVB (очистив флаги), укажите аргумент `--clear-vbmeta-flags`.
|
||||
|
||||
### Изменение алгоритма CoW сжатия для вирутального A/B
|
||||
|
||||
|
||||
Алгоритм CoW (copy-on-write) сжатия для виртуального A/B можно изменить, используя аргумент `--vabc-algo <алгоритм>`, указав `gz` или `lz4`. Как правило, по умолчанию OTA использует алгоритм, который совместим с изначальной версией Android, на которой поставлялось устройство.
|
||||
|
||||
|
||||
* Девайсы, поставляемые с Android 12, поддерживают `gz` и `brotli` (последний не поддерживается avbroot)
|
||||
* Девайсы, поставляемые с Android 14, поддерживают `lz4`
|
||||
* Девайсы, поставляемые с Android 15, поддерживают `zstd` (не поддерживается avbroot)
|
||||
|
||||
|
||||
Выбор быстрого алгоритма, такого как lz4, может значительно ускорить установку OTA из-под системы (при использованием стороннего приложения для OTA-обновлений). Однако, при установке OTA в режиме Recovery, разницы в скорости не будет.
|
||||
|
||||
|
||||
Обратите внимание, что текущая используемая версия Android должна поддерживать выбранный алгоритм сжатия. В противном случае установка завершится ошибкой. Например, попытка установить OTA-обновление с Android 14, использующее алгоритм lz4, приведет к ошибке, если установка производится из-под Android 13.
|
||||
|
||||
### Использование в неинтерактивном режиме
|
||||
@@ -515,7 +515,7 @@ avbroot поддерживает делегирование всех опера
|
||||
|
||||
На современных устройствах с Android 16 и выше, в настройках для разработчиков может появиться опция переключения на ядро с размером страницы 16 КБ. Однако, эта функция не будет работать в системе, пропатченной с помощью avbroot, поскольку переключение данной настройки осуществляется путём установки инкрементальной OTA:
|
||||
|
||||
* `/vendor/boot_otas/boot_ota_16k.zip` — используется для переключения на ядро с размером страницы 16 КБ (в разделе `boot` уже должно быть прошито ядро с размером страницы 4K)
|
||||
* `/vendor/boot_otas/boot_ota_16k.zip` — используется для переключения на ядро с размером страницы 16 КБ (в разделе `boot` уже должно быть прошито ядро с размером страницы 4K)
|
||||
* `/vendor/boot_otas/boot_ota_4k.zip` — используется для переключения на ядро с размером страницы 4 КБ (в разделе `boot` уже должно быть прошито ядро с размером страницы 16K)
|
||||
|
||||
Эти файлы (в `boot_otas`) невозможно прошить на системе, пропатченной avbroot, потому что `payload.bin` внутри них подписан ключом производителя. Кроме того, это неполноценные OTA-файлы: у них нет метаданных, характерных для OTA, а сам zip-файл не подписан. Это просто обычный архив, который содержит подписанный `payload.bin`.
|
||||
@@ -570,8 +570,6 @@ cargo build --release
|
||||
|
||||
Дебаг-сборки тоже работают, но они будут работать значительно медленнее (в вычислениях sha256), потому что оптимизации компилятора отключены.
|
||||
|
||||
По умолчанию исполняемый файл ссылается на системные библиотеки bzip2 и liblzma, от которых зависит avbroot. Чтобы скомпилировать и статически связать эти две библиотеки, укажите аргумент `--features static`.
|
||||
|
||||
### Кросс-компиляция на Android
|
||||
|
||||
Чтобы использовать кросс-компиляцию на Android, установите [cargo-android](https://github.com/chenxiaolong/cargo-android) и воспользуйтесь оболочкой `cargo android`. Чтобы создать релизную сборку для aarch64, выполните:
|
||||
|
||||
+4
-6
@@ -14,8 +14,6 @@ base64 = "0.22.1"
|
||||
bitflags = { version = "2.4.1", features = ["serde"] }
|
||||
bstr = "1.6.2"
|
||||
bzip2 = "0.6.0"
|
||||
cap-std = "3.0.0"
|
||||
cap-tempfile = "3.0.0"
|
||||
clap = { version = "4.4.1", features = ["derive"] }
|
||||
clap_complete = "4.4.0"
|
||||
cms = { version = "0.2.2", features = ["std"] }
|
||||
@@ -28,8 +26,8 @@ dlv-list = "0.6.0"
|
||||
flate2 = { version = "1.0.29", features = ["zlib-rs"] }
|
||||
gf256 = { version = "0.3.0", features = ["rs"] }
|
||||
hex = { version = "0.4.3", features = ["serde"] }
|
||||
liblzma = "0.4.1"
|
||||
lz4_flex = "0.11.1"
|
||||
lzma-rust2 = "0.8.0"
|
||||
memchr = "2.6.0"
|
||||
num-bigint-dig = "0.8.4"
|
||||
num-traits = "0.2.16"
|
||||
@@ -73,6 +71,9 @@ features = ["deflate"]
|
||||
libc = "0.2.158"
|
||||
rustix = { version = "1.0.3", default-features = false, features = ["process"] }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
system-properties = { git = "https://github.com/chenxiaolong/system-properties", tag = "v0.2.1" }
|
||||
|
||||
[build-dependencies]
|
||||
constcat = "0.6.0"
|
||||
prost-build = "0.14.1"
|
||||
@@ -81,8 +82,5 @@ protox = "0.9.0"
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
|
||||
[features]
|
||||
static = ["liblzma/static"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+239
-85
@@ -1,24 +1,22 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::{OsStr, OsString},
|
||||
fs::{self, File},
|
||||
ffi::OsString,
|
||||
fmt,
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{self, BufReader, BufWriter, Cursor, Seek, SeekFrom, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::atomic::AtomicBool,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use cap_std::{
|
||||
ambient_authority,
|
||||
fs::{Dir, OpenOptions},
|
||||
};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
||||
use rsa::RsaPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use tracing::{Span, debug_span, info, warn};
|
||||
|
||||
use crate::{
|
||||
@@ -298,14 +296,13 @@ fn update_dm_verity_cmdline(info: &mut AvbInfo) -> Result<bool> {
|
||||
};
|
||||
|
||||
for d in &mut info.header.descriptors {
|
||||
if let Descriptor::KernelCmdline(d) = d {
|
||||
if d.flags & KernelCmdlineDescriptor::FLAG_USE_ONLY_IF_HASHTREE_NOT_DISABLED != 0
|
||||
&& d.cmdline.starts_with("dm=")
|
||||
&& d.cmdline != new_cmdline
|
||||
{
|
||||
d.cmdline = new_cmdline;
|
||||
return Ok(true);
|
||||
}
|
||||
if let Descriptor::KernelCmdline(d) = d
|
||||
&& d.flags & KernelCmdlineDescriptor::FLAG_USE_ONLY_IF_HASHTREE_NOT_DISABLED != 0
|
||||
&& d.cmdline.starts_with("dm=")
|
||||
&& d.cmdline != new_cmdline
|
||||
{
|
||||
d.cmdline = new_cmdline;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +328,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
|
||||
@@ -410,22 +407,82 @@ fn display_info(display: &DisplayGroup, info: &AvbInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that the partition name won't cause directory traversals.
|
||||
fn ensure_name_is_safe(name: &str) -> Result<()> {
|
||||
if Path::new(name).file_name() != Some(OsStr::new(name)) {
|
||||
bail!("Unsafe partition name: {name}");
|
||||
#[derive(Debug, Clone)]
|
||||
struct SearchPath {
|
||||
dir: PathBuf,
|
||||
suffix: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for SearchPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?} (suffix: {:?})", self.dir, self.suffix)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ImageOpener {
|
||||
search: Vec<SearchPath>,
|
||||
}
|
||||
|
||||
impl ImageOpener {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn with_dir(dir: impl Into<PathBuf>) -> Self {
|
||||
let mut result = Self::new();
|
||||
result.add_dir(dir, ".img");
|
||||
result
|
||||
}
|
||||
|
||||
pub fn add_dir(&mut self, dir: impl Into<PathBuf>, suffix: impl Into<String>) {
|
||||
self.search.push(SearchPath {
|
||||
dir: dir.into(),
|
||||
suffix: suffix.into(),
|
||||
});
|
||||
}
|
||||
|
||||
fn open(&self, name: &str, options: &OpenOptions) -> io::Result<(PathBuf, File)> {
|
||||
for search in &self.search {
|
||||
let path = util::path_join_single(&search.dir, format!("{name}{}", search.suffix))
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
||||
|
||||
match options.open(&path) {
|
||||
Ok(f) => return Ok((path, f)),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => continue,
|
||||
Err(e) => {
|
||||
return Err(io::Error::new(
|
||||
e.kind(),
|
||||
format!("Failed to open for reading: {path:?}: {e}"),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!(
|
||||
"Failed to find {name:?} image in: {}",
|
||||
util::join(&self.search, ", "),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TrustMethod {
|
||||
Key(RsaPublicKey),
|
||||
KeyDigest([u8; 32]),
|
||||
Anything,
|
||||
}
|
||||
|
||||
/// Recursively verify an image's vbmeta header and all of the chained images.
|
||||
/// `seen` is used to prevent cycles. `descriptors` will contain all of the hash
|
||||
/// and hash tree descriptors that need to be verified.
|
||||
pub fn verify_headers(
|
||||
directory: &Dir,
|
||||
opener: &ImageOpener,
|
||||
name: &str,
|
||||
expected_key: Option<&RsaPublicKey>,
|
||||
trust_method: &TrustMethod,
|
||||
seen: &mut HashSet<String>,
|
||||
descriptors: &mut HashMap<String, Descriptor>,
|
||||
) -> Result<()> {
|
||||
@@ -433,12 +490,7 @@ pub fn verify_headers(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ensure_name_is_safe(name)?;
|
||||
|
||||
let path = format!("{name}.img");
|
||||
let raw_reader = directory
|
||||
.open(&path)
|
||||
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
let (path, raw_reader) = opener.open(name, OpenOptions::new().read(true))?;
|
||||
let (header, _, _) = avb::load_image(BufReader::new(raw_reader))
|
||||
.with_context(|| format!("Failed to load vbmeta structures: {path:?}"))?;
|
||||
|
||||
@@ -450,19 +502,36 @@ pub fn verify_headers(
|
||||
if let Some(k) = &public_key {
|
||||
let prefix = format!("{name} has a signed vbmeta header");
|
||||
|
||||
if let Some(e) = expected_key {
|
||||
if k == e {
|
||||
info!("{prefix}");
|
||||
} else {
|
||||
bail!("{prefix}, but is signed by an untrusted key");
|
||||
match trust_method {
|
||||
TrustMethod::Key(expected) => {
|
||||
if k == expected {
|
||||
info!("{prefix}");
|
||||
} else {
|
||||
bail!("{prefix}, but is signed by an untrusted key");
|
||||
}
|
||||
}
|
||||
TrustMethod::KeyDigest(expected_sha256) => {
|
||||
let encoded = avb::encode_public_key(k)?;
|
||||
let digest = Sha256::digest(&encoded);
|
||||
|
||||
if digest.as_slice() == expected_sha256 {
|
||||
info!("{prefix}");
|
||||
} else {
|
||||
bail!("{prefix}, but is signed by an untrusted key");
|
||||
}
|
||||
}
|
||||
TrustMethod::Anything => {
|
||||
warn!("{prefix}, but parent does not list a trusted key");
|
||||
}
|
||||
} else {
|
||||
warn!("{prefix}, but parent does not list a trusted key");
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
@@ -482,8 +551,9 @@ pub fn verify_headers(
|
||||
let target_key = avb::decode_public_key(&d.public_key).with_context(|| {
|
||||
format!("Failed to decode chained public key for: {target_name}")
|
||||
})?;
|
||||
let target_trust = TrustMethod::Key(target_key);
|
||||
|
||||
verify_headers(directory, target_name, Some(&target_key), seen, descriptors)?;
|
||||
verify_headers(opener, target_name, &target_trust, seen, descriptors)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -541,34 +611,33 @@ fn verify_and_repair(
|
||||
/// Verify hash and hash tree descriptor digests and FEC data against their
|
||||
/// corresponding input files.
|
||||
pub fn verify_descriptors(
|
||||
directory: &Dir,
|
||||
opener: &ImageOpener,
|
||||
descriptors: &HashMap<String, Descriptor>,
|
||||
repair: bool,
|
||||
allow_missing: bool,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<()> {
|
||||
let parent_span = Span::current();
|
||||
|
||||
let mut options = OpenOptions::new();
|
||||
options.read(true);
|
||||
options.write(repair);
|
||||
|
||||
descriptors
|
||||
.par_iter()
|
||||
.map(|(name, descriptor)| {
|
||||
let _span = parent_span.enter();
|
||||
|
||||
let path = format!("{name}.img");
|
||||
let file = match directory
|
||||
.open_with(&path, OpenOptions::new().read(true).write(repair))
|
||||
.map(|f| PSeekFile::new(f.into_std()))
|
||||
{
|
||||
Ok(f) => f,
|
||||
let file = match opener.open(name, &options) {
|
||||
Ok((_, f)) => PSeekFile::new(f),
|
||||
// Some devices, like bluejay, have vbmeta descriptors that
|
||||
// refer to partitions that exist on the device, but not in the
|
||||
// OTA.
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||
warn!("Partition image does not exist: {path:?}");
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound && allow_missing => {
|
||||
warn!("{e}");
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => {
|
||||
Err(e).with_context(|| format!("Failed to open for reading: {path:?}"))?
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
verify_and_repair(
|
||||
@@ -583,7 +652,7 @@ pub fn verify_descriptors(
|
||||
}
|
||||
|
||||
fn compute_digest_recursive(
|
||||
directory: &Dir,
|
||||
directory: &Path,
|
||||
name: &str,
|
||||
context: &mut ring::digest::Context,
|
||||
max_depth: u8,
|
||||
@@ -598,11 +667,8 @@ fn compute_digest_recursive(
|
||||
|
||||
seen.insert(name.to_owned());
|
||||
|
||||
ensure_name_is_safe(name)?;
|
||||
|
||||
let path = format!("{name}.img");
|
||||
let mut raw_reader = directory
|
||||
.open(&path)
|
||||
let path = util::path_join_single(directory, format!("{name}.img"))?;
|
||||
let mut raw_reader = File::open(&path)
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
let (header, footer, _) = avb::load_image(&mut raw_reader)
|
||||
@@ -654,7 +720,11 @@ fn compute_digest_recursive(
|
||||
/// the root vbmeta image, followed by the headers in the immediate chained
|
||||
/// partitions. This digest is not defined to be recursive, so headers of
|
||||
/// chained partitions more than one level deep are ignored.
|
||||
pub fn compute_digest(directory: &Dir, name: &str, cancel_signal: &AtomicBool) -> Result<[u8; 32]> {
|
||||
pub fn compute_digest(
|
||||
directory: &Path,
|
||||
name: &str,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<[u8; 32]> {
|
||||
let mut seen = HashSet::<String>::new();
|
||||
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
|
||||
|
||||
@@ -761,50 +831,40 @@ fn info_subcommand(cli: &InfoCli) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let public_key = if let Some(p) = &cli.public_key {
|
||||
fn verify_internal(
|
||||
public_key_path: Option<&Path>,
|
||||
public_key_digest: Option<[u8; 32]>,
|
||||
opener: &ImageOpener,
|
||||
name: &str,
|
||||
repair: bool,
|
||||
allow_missing: bool,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<()> {
|
||||
let trust_method = if let Some(p) = public_key_path {
|
||||
let data = fs::read(p).with_context(|| format!("Failed to read file: {p:?}"))?;
|
||||
let key = avb::decode_public_key(&data)
|
||||
.with_context(|| format!("Failed to decode public key: {p:?}"))?;
|
||||
|
||||
Some(key)
|
||||
TrustMethod::Key(key)
|
||||
} else if let Some(d) = public_key_digest {
|
||||
TrustMethod::KeyDigest(d)
|
||||
} else {
|
||||
None
|
||||
TrustMethod::Anything
|
||||
};
|
||||
|
||||
let authority = ambient_authority();
|
||||
let parent_path = util::parent_path(&cli.input);
|
||||
let directory = Dir::open_ambient_dir(parent_path, authority)
|
||||
.with_context(|| format!("Failed to open directory: {parent_path:?}"))?;
|
||||
let name = cli
|
||||
.input
|
||||
.file_stem()
|
||||
.with_context(|| format!("Path is not a file: {:?}", cli.input))?
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("Invalid UTF-8: {:?}", cli.input))?;
|
||||
|
||||
let mut seen = HashSet::<String>::new();
|
||||
let mut descriptors = HashMap::<String, Descriptor>::new();
|
||||
|
||||
verify_headers(
|
||||
&directory,
|
||||
name,
|
||||
public_key.as_ref(),
|
||||
&mut seen,
|
||||
&mut descriptors,
|
||||
)?;
|
||||
verify_descriptors(&directory, &descriptors, cli.repair, cancel_signal)?;
|
||||
verify_headers(opener, name, &trust_method, &mut seen, &mut descriptors)?;
|
||||
verify_descriptors(opener, &descriptors, repair, allow_missing, cancel_signal)?;
|
||||
|
||||
info!("Successfully verified all vbmeta signatures and hashes");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn digest_subcommand(cli: &DigestCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let authority = ambient_authority();
|
||||
let parent_path = util::parent_path(&cli.input);
|
||||
let directory = Dir::open_ambient_dir(parent_path, authority)
|
||||
.with_context(|| format!("Failed to open directory: {parent_path:?}"))?;
|
||||
fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let directory = util::parent_path(&cli.input);
|
||||
let name = cli
|
||||
.input
|
||||
.file_stem()
|
||||
@@ -812,7 +872,71 @@ fn digest_subcommand(cli: &DigestCli, cancel_signal: &AtomicBool) -> Result<()>
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("Invalid UTF-8: {:?}", cli.input))?;
|
||||
|
||||
let digest = compute_digest(&directory, name, cancel_signal)?;
|
||||
let opener = ImageOpener::with_dir(directory);
|
||||
|
||||
verify_internal(
|
||||
cli.public_key.as_deref(),
|
||||
None,
|
||||
&opener,
|
||||
name,
|
||||
cli.repair,
|
||||
!cli.fail_if_missing,
|
||||
cancel_signal,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn get_required_property(name: &str) -> Result<String> {
|
||||
system_properties::read(name)
|
||||
.with_context(|| format!("Failed to query property: {name}"))?
|
||||
.ok_or_else(|| anyhow!("Property is not set: {name}"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn verify_device_subcommand(cli: &VerifyDeviceCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let slot_suffix = get_required_property("ro.boot.slot_suffix")?;
|
||||
|
||||
// Use the bootloader's public key digest if no key is specified. This is
|
||||
// what the user flashed for avb_custom_key.
|
||||
let public_key_digest = if cli.public_key.is_none() {
|
||||
let hex_digest = get_required_property("ro.boot.vbmeta.public_key_digest")?;
|
||||
let mut digest = [0u8; 32];
|
||||
|
||||
hex::decode_to_slice(&hex_digest, &mut digest)
|
||||
.with_context(|| format!("Invalid public key digest: {hex_digest}"))?;
|
||||
|
||||
info!("Verifying against bootloader public key digest: {hex_digest}");
|
||||
|
||||
Some(digest)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut opener = ImageOpener::new();
|
||||
opener.add_dir("/dev/block/by-name", &slot_suffix);
|
||||
opener.add_dir("/dev/block/mapper", &slot_suffix);
|
||||
|
||||
verify_internal(
|
||||
cli.public_key.as_deref(),
|
||||
public_key_digest,
|
||||
&opener,
|
||||
&cli.partition,
|
||||
false,
|
||||
false,
|
||||
cancel_signal,
|
||||
)
|
||||
}
|
||||
|
||||
fn digest_subcommand(cli: &DigestCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let directory = util::parent_path(&cli.input);
|
||||
let name = cli
|
||||
.input
|
||||
.file_stem()
|
||||
.with_context(|| format!("Path is not a file: {:?}", cli.input))?
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("Invalid UTF-8: {:?}", cli.input))?;
|
||||
|
||||
let digest = compute_digest(directory, name, cancel_signal)?;
|
||||
|
||||
println!("{}", hex::encode(digest));
|
||||
|
||||
@@ -826,6 +950,8 @@ pub fn avb_main(cli: &AvbCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
AvbCommand::Repack(c) => repack_subcommand(c, cancel_signal),
|
||||
AvbCommand::Info(c) => info_subcommand(c),
|
||||
AvbCommand::Verify(c) => verify_subcommand(c, cancel_signal),
|
||||
#[cfg(target_os = "android")]
|
||||
AvbCommand::VerifyDevice(c) => verify_device_subcommand(c, cancel_signal),
|
||||
AvbCommand::Digest(c) => digest_subcommand(c, cancel_signal),
|
||||
}
|
||||
}
|
||||
@@ -1029,6 +1155,32 @@ struct VerifyCli {
|
||||
/// Only images with hash tree descriptors can contain FEC data.
|
||||
#[arg(short, long)]
|
||||
repair: bool,
|
||||
|
||||
/// Fail if a referenced image is missing.
|
||||
///
|
||||
/// Missing images are ignored by default because some OTAs contain vbmeta
|
||||
/// images referencing partitions that only exist on the real device.
|
||||
#[arg(long)]
|
||||
fail_if_missing: bool,
|
||||
}
|
||||
|
||||
/// Verify vbmeta signatures for the currently booted system.
|
||||
///
|
||||
/// This behaves like the `verify` subcommand, except that it checks the actual
|
||||
/// partitions that this device is currently booted from.
|
||||
#[cfg(target_os = "android")]
|
||||
#[derive(Debug, Parser)]
|
||||
struct VerifyDeviceCli {
|
||||
/// Path to public key in AVB binary format.
|
||||
///
|
||||
/// If this is not specified, the signatures can only be checked for
|
||||
/// validity, not whether they are trusted.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
public_key: Option<PathBuf>,
|
||||
|
||||
/// Partition to recursively verify.
|
||||
#[arg(short = 'P', long, value_name = "NAME", default_value = "vbmeta")]
|
||||
partition: String,
|
||||
}
|
||||
|
||||
/// Compute the vbmeta digest.
|
||||
@@ -1050,6 +1202,8 @@ enum AvbCommand {
|
||||
#[command(alias = "dump")]
|
||||
Info(InfoCli),
|
||||
Verify(VerifyCli),
|
||||
#[cfg(target_os = "android")]
|
||||
VerifyDevice(VerifyDeviceCli),
|
||||
Digest(DigestCli),
|
||||
}
|
||||
|
||||
|
||||
+18
-26
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
@@ -11,7 +11,6 @@ use std::{
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use bstr::ByteSlice;
|
||||
use cap_std::{ambient_authority, fs::Dir};
|
||||
use clap::{Parser, Subcommand};
|
||||
use num_traits::ToPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -34,7 +33,7 @@ fn open_reader(
|
||||
path: &Path,
|
||||
include_trailer: bool,
|
||||
) -> Result<(
|
||||
CpioReader<CompressedReader<BufReader<File>>>,
|
||||
CpioReader<CompressedReader<'_, BufReader<File>>>,
|
||||
CompressedFormat,
|
||||
)> {
|
||||
let file =
|
||||
@@ -50,7 +49,7 @@ fn open_reader(
|
||||
fn open_writer(
|
||||
path: &Path,
|
||||
format: CompressedFormat,
|
||||
) -> Result<CpioWriter<CompressedWriter<BufWriter<File>>>> {
|
||||
) -> Result<CpioWriter<CompressedWriter<'_, BufWriter<File>>>> {
|
||||
let file =
|
||||
File::create(path).with_context(|| format!("Failed to open cpio for writing: {path:?}"))?;
|
||||
let writer = CompressedWriter::new(BufWriter::new(file), format)
|
||||
@@ -91,17 +90,17 @@ fn write_info(path: &Path, info: &CpioInfo) -> Result<()> {
|
||||
|
||||
/// Open reader to the corresponding file inside the tree if the entry is a
|
||||
/// regular file. Unsafe paths will result in an error.
|
||||
fn open_tree_file(tree: &Dir, entry: &CpioEntry) -> Result<Option<(BufReader<File>, u32)>> {
|
||||
fn open_tree_file(tree: &Path, entry: &CpioEntry) -> Result<Option<(BufReader<File>, u32)>> {
|
||||
if entry.file_type == CpioEntryType::Regular {
|
||||
let path = entry
|
||||
let sub_path = entry
|
||||
.path
|
||||
.as_bstr()
|
||||
.to_path()
|
||||
.with_context(|| format!("Invalid entry path: {:?}", entry.path.as_bstr()))?;
|
||||
let path = util::path_join(tree, sub_path)?;
|
||||
|
||||
let mut reader = tree
|
||||
.open(path)
|
||||
.map(|f| BufReader::new(f.into_std()))
|
||||
let mut reader = File::open(&path)
|
||||
.map(BufReader::new)
|
||||
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
|
||||
let file_size = reader
|
||||
@@ -122,21 +121,21 @@ fn open_tree_file(tree: &Dir, entry: &CpioEntry) -> Result<Option<(BufReader<Fil
|
||||
/// Open writer to the corresponding file inside the tree if the entry is a
|
||||
/// regular file. Intermediate directories are automatically created as needed.
|
||||
/// Unsafe paths will result in an error.
|
||||
fn create_tree_file(tree: &Dir, entry: &CpioEntry) -> Result<Option<BufWriter<File>>> {
|
||||
fn create_tree_file(tree: &Path, entry: &CpioEntry) -> Result<Option<BufWriter<File>>> {
|
||||
if entry.file_type == CpioEntryType::Regular {
|
||||
let path = entry
|
||||
let sub_path = entry
|
||||
.path
|
||||
.as_bstr()
|
||||
.to_path()
|
||||
.with_context(|| format!("Invalid entry path: {:?}", entry.path.as_bstr()))?;
|
||||
let parent = util::parent_path(path);
|
||||
let path = util::path_join(tree, sub_path)?;
|
||||
let parent = util::parent_path(&path);
|
||||
|
||||
tree.create_dir_all(parent)
|
||||
fs::create_dir_all(parent)
|
||||
.with_context(|| format!("Failed to create directory: {parent:?}"))?;
|
||||
|
||||
let writer = tree
|
||||
.create(path)
|
||||
.map(|f| BufWriter::new(f.into_std()))
|
||||
let writer = File::create(&path)
|
||||
.map(BufWriter::new)
|
||||
.with_context(|| format!("Failed to open for writing: {path:?}"))?;
|
||||
|
||||
Ok(Some(writer))
|
||||
@@ -171,16 +170,13 @@ fn unpack_subcommand(
|
||||
|
||||
display_format(cpio_cli, format);
|
||||
|
||||
let authority = ambient_authority();
|
||||
Dir::create_ambient_dir_all(&cli.output_tree, authority)
|
||||
fs::create_dir_all(&cli.output_tree)
|
||||
.with_context(|| format!("Failed to create directory: {:?}", cli.output_tree))?;
|
||||
let tree = Dir::open_ambient_dir(&cli.output_tree, authority)
|
||||
.with_context(|| format!("Failed to open directory: {:?}", cli.output_tree))?;
|
||||
|
||||
while let Some(entry) = reader.next_entry().context("Failed to read cpio entry")? {
|
||||
display_entry(cpio_cli, &entry);
|
||||
|
||||
if let Some(mut writer) = create_tree_file(&tree, &entry)? {
|
||||
if let Some(mut writer) = create_tree_file(&cli.output_tree, &entry)? {
|
||||
let file_size = entry.data.size()?;
|
||||
|
||||
stream::copy_n(&mut reader, &mut writer, file_size.into(), cancel_signal)
|
||||
@@ -209,12 +205,8 @@ fn pack_subcommand(cpio_cli: &CpioCli, cli: &PackCli, cancel_signal: &AtomicBool
|
||||
|
||||
cpio::assign_inodes(&mut info.entries, true)?;
|
||||
|
||||
let authority = ambient_authority();
|
||||
let tree = Dir::open_ambient_dir(&cli.input_tree, authority)
|
||||
.with_context(|| format!("Failed to open directory: {:?}", cli.input_tree))?;
|
||||
|
||||
for entry in &mut info.entries {
|
||||
let out = open_tree_file(&tree, entry)?;
|
||||
let out = open_tree_file(&cli.input_tree, entry)?;
|
||||
|
||||
if let Some((_, file_size)) = &out {
|
||||
entry.data = CpioEntryData::Size(*file_size);
|
||||
|
||||
+11
-29
@@ -1,8 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2024 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2024-2025 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs::{self, File},
|
||||
io::{Seek, SeekFrom},
|
||||
path::{Path, PathBuf},
|
||||
@@ -10,7 +9,6 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use cap_std::{ambient_authority, fs::Dir};
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use rayon::iter::{
|
||||
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator,
|
||||
@@ -19,6 +17,7 @@ use rayon::iter::{
|
||||
use crate::{
|
||||
format::lp::{Extent, ExtentType, ImageType, Metadata, SECTOR_SIZE},
|
||||
stream::{self, FromReader, PSeekFile, Reopen, ToWriter},
|
||||
util,
|
||||
};
|
||||
|
||||
fn open_lp_inputs(paths: &[impl AsRef<Path>]) -> Result<(Vec<PSeekFile>, Metadata)> {
|
||||
@@ -190,11 +189,8 @@ fn unpack_subcommand(lp_cli: &LpCli, cli: &UnpackCli, cancel_signal: &AtomicBool
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let authority = ambient_authority();
|
||||
Dir::create_ambient_dir_all(&cli.output_images, authority)
|
||||
fs::create_dir_all(&cli.output_images)
|
||||
.with_context(|| format!("Failed to create directory: {:?}", cli.output_images))?;
|
||||
let directory = Dir::open_ambient_dir(&cli.output_images, authority)
|
||||
.with_context(|| format!("Failed to open directory: {:?}", cli.output_images))?;
|
||||
|
||||
let slot = &metadata.slots[0];
|
||||
|
||||
@@ -216,11 +212,11 @@ fn unpack_subcommand(lp_cli: &LpCli, cli: &UnpackCli, cancel_signal: &AtomicBool
|
||||
|
||||
for partition in &group.partitions {
|
||||
// A partition name with unsafe characters fails during parsing.
|
||||
let path = format!("{}.img", partition.name);
|
||||
let path =
|
||||
util::path_join_single(&cli.output_images, format!("{}.img", partition.name))?;
|
||||
|
||||
let file = directory
|
||||
.create(&path)
|
||||
.map(|f| PSeekFile::new(f.into_std()))
|
||||
let file = File::create(&path)
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to open for writing: {path:?}"))?;
|
||||
|
||||
file.set_len(partition.size()?)
|
||||
@@ -303,35 +299,21 @@ fn pack_subcommand(lp_cli: &LpCli, cli: &PackCli, cancel_signal: &AtomicBool) ->
|
||||
}
|
||||
}
|
||||
|
||||
for group in &slot.groups {
|
||||
for partition in &group.partitions {
|
||||
let name = &partition.name;
|
||||
|
||||
if Path::new(name).file_name() != Some(OsStr::new(name)) {
|
||||
bail!("Unsafe partition name: {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Preopen all image input files.
|
||||
let mut paths = vec![];
|
||||
let mut files = vec![];
|
||||
|
||||
if metadata.image_type == ImageType::Normal {
|
||||
let authority = ambient_authority();
|
||||
let directory = Dir::open_ambient_dir(&cli.input_images, authority)
|
||||
.with_context(|| format!("Failed to open directory: {:?}", cli.input_images))?;
|
||||
|
||||
for group in &mut slot.groups {
|
||||
let mut group_paths = vec![];
|
||||
let mut group_files = vec![];
|
||||
|
||||
for partition in &mut group.partitions {
|
||||
let path = format!("{}.img", partition.name);
|
||||
let path =
|
||||
util::path_join_single(&cli.input_images, format!("{}.img", partition.name))?;
|
||||
|
||||
let mut file = directory
|
||||
.open(&path)
|
||||
.map(|f| PSeekFile::new(f.into_std()))
|
||||
let mut file = File::open(&path)
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
|
||||
let size = file
|
||||
|
||||
+72
-69
@@ -15,18 +15,19 @@ use std::{
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use bitflags::bitflags;
|
||||
use cap_std::{ambient_authority, fs::Dir};
|
||||
use cap_tempfile::TempDir;
|
||||
use clap::{ArgAction, Args, Parser, Subcommand, value_parser};
|
||||
use rayon::{iter::IntoParallelRefIterator, prelude::ParallelIterator};
|
||||
use tempfile::NamedTempFile;
|
||||
use tempfile::{NamedTempFile, TempDir};
|
||||
use topological_sort::TopologicalSort;
|
||||
use tracing::{debug_span, error, info, warn};
|
||||
use x509_cert::Certificate;
|
||||
use zip::{CompressionMethod, DateTime, ZipArchive, write::SimpleFileOptions};
|
||||
|
||||
use crate::{
|
||||
cli,
|
||||
cli::{
|
||||
self,
|
||||
avb::{ImageOpener, TrustMethod},
|
||||
},
|
||||
crypto::{self, PassphraseSource, RsaSigningKey},
|
||||
format::{
|
||||
avb::{self, Descriptor, Header},
|
||||
@@ -900,10 +901,10 @@ fn patch_ota_payload(
|
||||
if !skip_system_ota_cert {
|
||||
required_flags |= RequiredFlags::SYSTEM;
|
||||
}
|
||||
if let Some(vabc_algo) = vabc_algo_override {
|
||||
if set_vabc_algo(&mut header, vabc_algo)? {
|
||||
required_flags |= RequiredFlags::ALL_COW;
|
||||
}
|
||||
if let Some(vabc_algo) = vabc_algo_override
|
||||
&& set_vabc_algo(&mut header, vabc_algo)?
|
||||
{
|
||||
required_flags |= RequiredFlags::ALL_COW;
|
||||
}
|
||||
|
||||
let all_partitions = header
|
||||
@@ -1051,23 +1052,23 @@ fn patch_ota_payload(
|
||||
// Try to copy from our replacement image. The compressed chunks are
|
||||
// laid out sequentially and data_offset is set to the offset within
|
||||
// that file.
|
||||
if let Some((input_file, modified_operations)) = compressed_files.get_mut(&name) {
|
||||
if util::ranges_contains(modified_operations, &oi) {
|
||||
input_file
|
||||
.file
|
||||
.seek(SeekFrom::Start(data_offset))
|
||||
.with_context(|| format!("Failed to seek image: {name}"))?;
|
||||
if let Some((input_file, modified_operations)) = compressed_files.get_mut(&name)
|
||||
&& util::ranges_contains(modified_operations, &oi)
|
||||
{
|
||||
input_file
|
||||
.file
|
||||
.seek(SeekFrom::Start(data_offset))
|
||||
.with_context(|| format!("Failed to seek image: {name}"))?;
|
||||
|
||||
stream::copy_n(
|
||||
&mut input_file.file,
|
||||
&mut payload_writer,
|
||||
data_length,
|
||||
cancel_signal,
|
||||
)
|
||||
.with_context(|| format!("Failed to copy from replacement image: {name}"))?;
|
||||
stream::copy_n(
|
||||
&mut input_file.file,
|
||||
&mut payload_writer,
|
||||
data_length,
|
||||
cancel_signal,
|
||||
)
|
||||
.with_context(|| format!("Failed to copy from replacement image: {name}"))?;
|
||||
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, copy from the original payload.
|
||||
@@ -1293,29 +1294,22 @@ fn patch_ota_zip(
|
||||
|
||||
pub fn extract_payload(
|
||||
raw_reader: &PSeekFile,
|
||||
directory: &Dir,
|
||||
directory: &Path,
|
||||
payload_offset: u64,
|
||||
payload_size: u64,
|
||||
header: &PayloadHeader,
|
||||
images: &BTreeSet<String>,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<()> {
|
||||
for name in images {
|
||||
if Path::new(name).file_name() != Some(OsStr::new(name)) {
|
||||
bail!("Unsafe partition name: {name}");
|
||||
}
|
||||
}
|
||||
|
||||
info!("Extracting from the payload: {}", util::join(images, ", "));
|
||||
|
||||
// Pre-open all output files.
|
||||
let output_files = images
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let path = format!("{name}.img");
|
||||
let file = directory
|
||||
.create(&path)
|
||||
.map(|f| PSeekFile::new(f.into_std()))
|
||||
let path = util::path_join_single(directory, format!("{name}.img"))?;
|
||||
let file = File::create(&path)
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to open for writing: {path:?}"))?;
|
||||
Ok((name.as_str(), file))
|
||||
})
|
||||
@@ -1345,7 +1339,7 @@ pub fn extract_payload(
|
||||
}
|
||||
|
||||
fn verify_partition_hashes(
|
||||
directory: &Dir,
|
||||
directory: &Path,
|
||||
header: &PayloadHeader,
|
||||
images: &BTreeSet<String>,
|
||||
cancel_signal: &AtomicBool,
|
||||
@@ -1365,9 +1359,8 @@ fn verify_partition_hashes(
|
||||
.and_then(|info| info.hash.as_ref())
|
||||
.ok_or_else(|| anyhow!("Hash not found for partition: {name}"))?;
|
||||
|
||||
let path = format!("{name}.img");
|
||||
let file = directory
|
||||
.open(&path)
|
||||
let path = util::path_join_single(directory, format!("{name}.img"))?;
|
||||
let file = File::open(&path)
|
||||
.with_context(|| format!("Failed to open for reading: {path:?}"))?;
|
||||
|
||||
let mut writer = HashingWriter::new(
|
||||
@@ -1702,15 +1695,12 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let authority = ambient_authority();
|
||||
Dir::create_ambient_dir_all(&cli.directory, authority)
|
||||
fs::create_dir_all(&cli.directory)
|
||||
.with_context(|| format!("Failed to create directory: {:?}", cli.directory))?;
|
||||
let directory = Dir::open_ambient_dir(&cli.directory, authority)
|
||||
.with_context(|| format!("Failed to open directory: {:?}", cli.directory))?;
|
||||
|
||||
extract_payload(
|
||||
&raw_reader,
|
||||
&directory,
|
||||
&cli.directory,
|
||||
payload_offset,
|
||||
payload_size,
|
||||
&header,
|
||||
@@ -1742,9 +1732,9 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
|
||||
.and_then(|p| p.device.first())
|
||||
.ok_or_else(|| anyhow!("Device codename not found in OTA metadata"))?;
|
||||
|
||||
directory
|
||||
.write(ANDROID_INFO, format!("require board={device}\n"))
|
||||
.with_context(|| format!("Failed to write file: {ANDROID_INFO}"))?;
|
||||
let android_info_path = util::path_join_single(&cli.directory, ANDROID_INFO)?;
|
||||
fs::write(&android_info_path, format!("require board={device}\n"))
|
||||
.with_context(|| format!("Failed to write file: {android_info_path:?}"))?;
|
||||
|
||||
// Find out which images can be flashed with fastboot. The bootloader
|
||||
// (and potentially modem) partitions need to be flashed as a whole and
|
||||
@@ -1753,9 +1743,9 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
|
||||
let mut flashable_images = BTreeSet::new();
|
||||
|
||||
for name in &unique_images {
|
||||
let file = directory
|
||||
.open(format!("{name}.img"))
|
||||
.with_context(|| format!("Failed to open image for reading: {name}"))?;
|
||||
let path = util::path_join_single(&cli.directory, format!("{name}.img"))?;
|
||||
let file = File::open(&path)
|
||||
.with_context(|| format!("Failed to open image for reading: {path:?}"))?;
|
||||
|
||||
match avb::load_image(file) {
|
||||
Ok(_) => {
|
||||
@@ -1763,7 +1753,7 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
|
||||
}
|
||||
// Treat images without AVB metadata as bootloader partitions.
|
||||
Err(avb::Error::InvalidHeaderMagic(_)) => continue,
|
||||
Err(e) => return Err(e).with_context(|| format!("Failed to load image: {name}")),
|
||||
Err(e) => return Err(e).with_context(|| format!("Failed to load image: {path:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1851,9 +1841,9 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
|
||||
fastboot_info.push_str("if-wipe erase userdata\n");
|
||||
fastboot_info.push_str("if-wipe erase metadata\n");
|
||||
|
||||
directory
|
||||
.write(FASTBOOT_INFO, fastboot_info)
|
||||
.with_context(|| format!("Failed to write file: {FASTBOOT_INFO}"))?;
|
||||
let fastboot_info_path = util::path_join_single(&cli.directory, FASTBOOT_INFO)?;
|
||||
fs::write(&fastboot_info_path, fastboot_info)
|
||||
.with_context(|| format!("Failed to write file: {fastboot_info_path:?}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1934,8 +1924,7 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
|
||||
|
||||
info!("Extracting partition images to temporary directory");
|
||||
|
||||
let authority = ambient_authority();
|
||||
let temp_dir = TempDir::new(authority).context("Failed to create temporary directory")?;
|
||||
let temp_dir = TempDir::new().context("Failed to create temporary directory")?;
|
||||
let raw_reader = reader.into_inner();
|
||||
let unique_images = header
|
||||
.manifest
|
||||
@@ -1947,7 +1936,7 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
|
||||
|
||||
extract_payload(
|
||||
&raw_reader,
|
||||
&temp_dir,
|
||||
temp_dir.path(),
|
||||
pf_payload.offset,
|
||||
pf_payload.size,
|
||||
&header,
|
||||
@@ -1957,29 +1946,31 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
|
||||
|
||||
info!("Verifying partition hashes");
|
||||
|
||||
if let Err(e) = verify_partition_hashes(&temp_dir, &header, &unique_images, cancel_signal) {
|
||||
if let Err(e) = verify_partition_hashes(temp_dir.path(), &header, &unique_images, cancel_signal)
|
||||
{
|
||||
fail_later!("{e:?}");
|
||||
}
|
||||
|
||||
info!("Verifying AVB signatures");
|
||||
|
||||
let public_key = if let Some(p) = &cli.public_key_avb {
|
||||
let trust_method = if let Some(p) = &cli.public_key_avb {
|
||||
let data = fs::read(p).with_context(|| format!("Failed to read file: {p:?}"))?;
|
||||
let key = avb::decode_public_key(&data)
|
||||
.with_context(|| format!("Failed to decode public key: {p:?}"))?;
|
||||
|
||||
Some(key)
|
||||
TrustMethod::Key(key)
|
||||
} else {
|
||||
None
|
||||
TrustMethod::Anything
|
||||
};
|
||||
|
||||
let opener = ImageOpener::with_dir(temp_dir.path());
|
||||
let mut seen = HashSet::<String>::new();
|
||||
let mut descriptors = HashMap::<String, Descriptor>::new();
|
||||
|
||||
if let Err(e) = cli::avb::verify_headers(
|
||||
&temp_dir,
|
||||
&opener,
|
||||
"vbmeta",
|
||||
public_key.as_ref(),
|
||||
&trust_method,
|
||||
&mut seen,
|
||||
&mut descriptors,
|
||||
)
|
||||
@@ -1988,8 +1979,14 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
|
||||
fail_later!("{e:?}");
|
||||
}
|
||||
|
||||
if let Err(e) = cli::avb::verify_descriptors(&temp_dir, &descriptors, false, cancel_signal)
|
||||
.context("Failed to verify images against AVB descriptors")
|
||||
if let Err(e) = cli::avb::verify_descriptors(
|
||||
&opener,
|
||||
&descriptors,
|
||||
false,
|
||||
!cli.fail_if_missing,
|
||||
cancel_signal,
|
||||
)
|
||||
.context("Failed to verify images against AVB descriptors")
|
||||
{
|
||||
fail_later!("{e:?}");
|
||||
}
|
||||
@@ -2003,11 +2000,10 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
|
||||
.map(|(name, _)| name.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let boot_images = boot::load_boot_images(&boot_image_names, |name| {
|
||||
Ok(Box::new(
|
||||
temp_dir
|
||||
.open(format!("{name}.img"))
|
||||
.map(|f| PSeekFile::new(f.into_std()))?,
|
||||
))
|
||||
let path = util::path_join_single(temp_dir.path(), format!("{name}.img"))
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
||||
|
||||
Ok(Box::new(File::open(path).map(PSeekFile::new)?))
|
||||
})
|
||||
.context("Failed to load all boot images")?;
|
||||
let targets = OtaCertPatcher::new(ota_cert.clone())
|
||||
@@ -2387,6 +2383,13 @@ pub struct VerifyCli {
|
||||
/// same key.
|
||||
#[arg(long, help_heading = HEADING_OTHER)]
|
||||
pub skip_recovery_ota_cert: bool,
|
||||
|
||||
/// Fail if a referenced image is missing.
|
||||
///
|
||||
/// Missing images are ignored by default because some OTAs contain vbmeta
|
||||
/// images referencing partitions that only exist on the real device.
|
||||
#[arg(long)]
|
||||
fail_if_missing: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2024 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2024-2025 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{OsStr, OsString},
|
||||
ffi::OsString,
|
||||
fs::{self, File},
|
||||
io::{BufReader, BufWriter, Seek, SeekFrom},
|
||||
path::{Path, PathBuf},
|
||||
@@ -11,7 +11,6 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use cap_std::{ambient_authority, fs::Dir};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use tracing::info;
|
||||
|
||||
@@ -20,6 +19,7 @@ use crate::{
|
||||
crypto::{self, PassphraseSource, RsaSigningKey},
|
||||
format::payload::{PayloadHeader, PayloadWriter},
|
||||
stream::{self, FromReader, PSeekFile},
|
||||
util,
|
||||
};
|
||||
|
||||
fn open_reader(path: &Path, allow_delta: bool) -> Result<(BufReader<File>, PayloadHeader)> {
|
||||
@@ -113,15 +113,12 @@ fn unpack_subcommand(
|
||||
|
||||
write_info(&cli.output_info, &header)?;
|
||||
|
||||
let authority = ambient_authority();
|
||||
Dir::create_ambient_dir_all(&cli.output_images, authority)
|
||||
fs::create_dir_all(&cli.output_images)
|
||||
.with_context(|| format!("Failed to create directory: {:?}", cli.output_images))?;
|
||||
let directory = Dir::open_ambient_dir(&cli.output_images, authority)
|
||||
.with_context(|| format!("Failed to open directory: {:?}", cli.output_images))?;
|
||||
|
||||
ota::extract_payload(
|
||||
&PSeekFile::new(reader.into_inner()),
|
||||
&directory,
|
||||
&cli.output_images,
|
||||
0,
|
||||
payload_size,
|
||||
&header,
|
||||
@@ -147,28 +144,16 @@ fn pack_subcommand(
|
||||
|
||||
let mut header = read_info(&cli.input_info)?;
|
||||
|
||||
let authority = ambient_authority();
|
||||
let directory = Dir::open_ambient_dir(&cli.input_images, authority)
|
||||
.with_context(|| format!("Failed to open directory: {:?}", cli.input_images))?;
|
||||
|
||||
for p in &header.manifest.partitions {
|
||||
let name = &p.partition_name;
|
||||
|
||||
if Path::new(name).file_name() != Some(OsStr::new(name)) {
|
||||
bail!("Unsafe partition name: {name}");
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-open all of the image files.
|
||||
let input_files = header
|
||||
.manifest
|
||||
.partitions
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let path = format!("{}.img", p.partition_name);
|
||||
let file = directory
|
||||
.open(&path)
|
||||
.map(|f| PSeekFile::new(f.into_std()))
|
||||
let path =
|
||||
util::path_join_single(&cli.input_images, format!("{}.img", p.partition_name))?;
|
||||
let file = File::open(&path)
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to open file: {path:?}"))?;
|
||||
|
||||
Ok((p.partition_name.clone(), file))
|
||||
|
||||
@@ -160,10 +160,10 @@ impl PassphraseSource {
|
||||
Err(e) => {
|
||||
#[cfg(unix)]
|
||||
if let PromptError::IOError(io_e) = e {
|
||||
if let Some(errno) = io_e.raw_os_error() {
|
||||
if errno == libc::ENXIO || errno == libc::ENOTTY {
|
||||
return Err(Error::NotInteractive(io_e));
|
||||
}
|
||||
if let Some(errno) = io_e.raw_os_error()
|
||||
&& (errno == libc::ENXIO || errno == libc::ENOTTY)
|
||||
{
|
||||
return Err(Error::NotInteractive(io_e));
|
||||
}
|
||||
|
||||
return Err(Error::PassphrasePrompt(PromptError::IOError(io_e)));
|
||||
|
||||
+14
-10
@@ -1657,7 +1657,7 @@ impl Header {
|
||||
|
||||
/// Get the first hash or hash tree descriptor if there is only one. This is
|
||||
/// the case for appended AVB images.
|
||||
pub fn appended_descriptor(&self) -> Result<AppendedDescriptorRef> {
|
||||
pub fn appended_descriptor(&self) -> Result<AppendedDescriptorRef<'_>> {
|
||||
let mut result = None;
|
||||
|
||||
for descriptor in &self.descriptors {
|
||||
@@ -1683,7 +1683,7 @@ impl Header {
|
||||
|
||||
/// Get the first hash or hash tree descriptor if there is only one. This is
|
||||
/// the case for appended AVB images.
|
||||
pub fn appended_descriptor_mut(&mut self) -> Result<AppendedDescriptorMut> {
|
||||
pub fn appended_descriptor_mut(&mut self) -> Result<AppendedDescriptorMut<'_>> {
|
||||
let mut result = None;
|
||||
|
||||
for descriptor in &mut self.descriptors {
|
||||
@@ -1758,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();
|
||||
@@ -2174,10 +2178,10 @@ pub fn write_appended_image(
|
||||
.and_then(|s| s.checked_add(header_padding))
|
||||
.ok_or(Error::IntOverflow("Appended::header_end_padded"))?;
|
||||
|
||||
if let Some(s) = image_size {
|
||||
if header_end_padded > s {
|
||||
return Err(Error::TooSmallForHeader(s));
|
||||
}
|
||||
if let Some(s) = image_size
|
||||
&& header_end_padded > s
|
||||
{
|
||||
return Err(Error::TooSmallForHeader(s));
|
||||
}
|
||||
|
||||
writer
|
||||
|
||||
@@ -387,14 +387,13 @@ impl<R: Read> FromReader<R> for BootImageV0Through2 {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(v1) = &v1_data {
|
||||
if reader
|
||||
if let Some(v1) = &v1_data
|
||||
&& reader
|
||||
.stream_position()
|
||||
.map_err(|e| Error::DataRead("Boot::V1::header_size", e))?
|
||||
!= u64::from(v1.header_size)
|
||||
{
|
||||
return Err(Error::InvalidHeaderSize(v1.header_size));
|
||||
}
|
||||
{
|
||||
return Err(Error::InvalidHeaderSize(v1.header_size));
|
||||
}
|
||||
|
||||
padding::read_discard(&mut reader, page_size.into())
|
||||
@@ -870,14 +869,12 @@ impl BootImageV3Through4 {
|
||||
padding::write_zeros(&mut writer, PAGE_SIZE.into())
|
||||
.map_err(|e| Error::DataWrite("Boot::V3::ramdisk_padding", e))?;
|
||||
|
||||
if !skip_v4_sig {
|
||||
if let Some(sig) = v4_signature {
|
||||
writer
|
||||
.write_all(&sig)
|
||||
.map_err(|e| Error::DataWrite("Boot::V4::signature", e))?;
|
||||
padding::write_zeros(&mut writer, PAGE_SIZE.into())
|
||||
.map_err(|e| Error::DataWrite("Boot::V4::signature_padding", e))?;
|
||||
}
|
||||
if !skip_v4_sig && let Some(sig) = v4_signature {
|
||||
writer
|
||||
.write_all(&sig)
|
||||
.map_err(|e| Error::DataWrite("Boot::V4::signature", e))?;
|
||||
padding::write_zeros(&mut writer, PAGE_SIZE.into())
|
||||
.map_err(|e| Error::DataWrite("Boot::V4::signature_padding", e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::io::{self, Read, Seek, Write};
|
||||
|
||||
use flate2::{Compression, read::GzDecoder, write::GzEncoder};
|
||||
use liblzma::{
|
||||
read::XzDecoder,
|
||||
stream::{Check, Stream},
|
||||
write::XzEncoder,
|
||||
};
|
||||
use lz4_flex::frame::FrameDecoder;
|
||||
use lzma_rust2::{CheckType, XZOptions, XZReader, XZWriter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -28,7 +24,7 @@ pub enum Error {
|
||||
#[error("Failed to initialize legacy LZ4 encoder")]
|
||||
Lz4Init(#[source] io::Error),
|
||||
#[error("Failed to initialize XZ encoder")]
|
||||
XzInit(#[source] liblzma::stream::Error),
|
||||
XzInit(#[source] io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -114,14 +110,14 @@ pub enum CompressedFormat {
|
||||
Xz,
|
||||
}
|
||||
|
||||
pub enum CompressedReader<R: Read> {
|
||||
pub enum CompressedReader<'reader, R: Read> {
|
||||
None(R),
|
||||
Gzip(GzDecoder<R>),
|
||||
Lz4(FrameDecoder<R>),
|
||||
Xz(XzDecoder<R>),
|
||||
Xz(XZReader<'reader, R>),
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> CompressedReader<R> {
|
||||
impl<'reader, R: Read + Seek + 'reader> CompressedReader<'reader, R> {
|
||||
pub fn new(mut reader: R, raw_if_unknown: bool) -> Result<Self> {
|
||||
let magic = reader.read_array_exact::<6>().map_err(Error::AutoDetect)?;
|
||||
|
||||
@@ -132,7 +128,7 @@ impl<R: Read + Seek> CompressedReader<R> {
|
||||
} else if &magic[0..4] == LZ4_LEGACY_MAGIC {
|
||||
Ok(Self::Lz4(FrameDecoder::new(reader)))
|
||||
} else if &magic == XZ_MAGIC {
|
||||
Ok(Self::Xz(XzDecoder::new(reader)))
|
||||
Ok(Self::Xz(XZReader::new(reader, false)))
|
||||
} else if raw_if_unknown {
|
||||
Ok(Self::None(reader))
|
||||
} else {
|
||||
@@ -159,7 +155,7 @@ impl<R: Read + Seek> CompressedReader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for CompressedReader<R> {
|
||||
impl<'reader, R: Read + 'reader> Read for CompressedReader<'reader, R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
Self::None(r) => r.read(buf),
|
||||
@@ -170,14 +166,15 @@ impl<R: Read> Read for CompressedReader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum CompressedWriter<W: Write> {
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum CompressedWriter<'writer, W: Write> {
|
||||
None(W),
|
||||
Gzip(GzEncoder<W>),
|
||||
Lz4Legacy(Lz4LegacyEncoder<W>),
|
||||
Xz(XzEncoder<W>),
|
||||
Xz(XZWriter<'writer, W>),
|
||||
}
|
||||
|
||||
impl<W: Write> CompressedWriter<W> {
|
||||
impl<'writer, W: Write + 'writer> CompressedWriter<'writer, W> {
|
||||
pub fn new(writer: W, format: CompressedFormat) -> Result<Self> {
|
||||
match format {
|
||||
CompressedFormat::None => Ok(Self::None(writer)),
|
||||
@@ -190,8 +187,11 @@ impl<W: Write> CompressedWriter<W> {
|
||||
}
|
||||
CompressedFormat::Xz => {
|
||||
// Some kernels are compiled without support for the default CRC64.
|
||||
let stream = Stream::new_easy_encoder(6, Check::Crc32).map_err(Error::XzInit)?;
|
||||
Ok(Self::Xz(XzEncoder::new_stream(writer, stream)))
|
||||
let mut options = XZOptions::with_preset(6);
|
||||
options.set_check_sum_type(CheckType::Crc32);
|
||||
|
||||
let xz_writer = XZWriter::new(writer, options).map_err(Error::XzInit)?;
|
||||
Ok(Self::Xz(xz_writer))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,7 +215,7 @@ impl<W: Write> CompressedWriter<W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Write for CompressedWriter<W> {
|
||||
impl<'writer, W: Write + 'writer> Write for CompressedWriter<'writer, W> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
Self::None(w) => w.write(buf),
|
||||
|
||||
@@ -15,11 +15,7 @@ use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use bzip2::write::BzDecoder;
|
||||
use flate2::{Compression, write::GzEncoder};
|
||||
use liblzma::{
|
||||
stream::{Check, Stream},
|
||||
write::XzDecoder,
|
||||
write::XzEncoder,
|
||||
};
|
||||
use lzma_rust2::{CheckType, XZOptions, XZReader, XZWriter};
|
||||
use num_traits::CheckedAdd;
|
||||
use prost::Message;
|
||||
use rayon::{
|
||||
@@ -40,8 +36,8 @@ use crate::{
|
||||
install_operation::Type, signatures::Signature,
|
||||
},
|
||||
stream::{
|
||||
self, CountingReader, FromReader, HashingWriter, ReadDiscardExt, ReadFixedSizeExt,
|
||||
ReadSeekReopen, WriteSeek, WriteSeekReopen,
|
||||
self, CountingReader, FromReader, HashingReader, HashingWriter, ReadDiscardExt,
|
||||
ReadFixedSizeExt, ReadSeekReopen, WriteSeek, WriteSeekReopen,
|
||||
},
|
||||
util::{self, OutOfBoundsError},
|
||||
};
|
||||
@@ -139,7 +135,7 @@ pub enum Error {
|
||||
#[error("Failed to GZ compress partition image chunk")]
|
||||
GzCompress(#[source] io::Error),
|
||||
#[error("Failed to initialize XZ encoder")]
|
||||
XzInit(#[source] liblzma::stream::Error),
|
||||
XzInit(#[source] io::Error),
|
||||
#[error("Failed to XZ compress partition image chunk")]
|
||||
XzCompress(#[source] io::Error),
|
||||
#[error("Failed to read uncompressed input partition image chunk")]
|
||||
@@ -818,16 +814,14 @@ pub fn apply_operation(
|
||||
.map_err(error_fn)?;
|
||||
}
|
||||
Type::ReplaceXz => {
|
||||
let mut decoder = XzDecoder::new(&mut writer);
|
||||
stream::copy_n_inspect(
|
||||
&mut reader,
|
||||
&mut decoder,
|
||||
data_length,
|
||||
|data| hasher.update(data),
|
||||
cancel_signal,
|
||||
)
|
||||
.and_then(|()| decoder.finish())
|
||||
.map_err(error_fn)?;
|
||||
// lzma_rust2 does not have a Write API, so we limit the
|
||||
// reader and read till EOF.
|
||||
let limited_reader = (&mut reader).take(data_length);
|
||||
let hashing_reader = HashingReader::new(limited_reader, hasher);
|
||||
let mut decoder = XZReader::new(hashing_reader, false);
|
||||
stream::copy(&mut decoder, &mut writer, cancel_signal).map_err(error_fn)?;
|
||||
|
||||
(_, hasher) = decoder.into_inner().finish();
|
||||
}
|
||||
_ => return Err(Error::UnsupportedOperation(op.r#type())),
|
||||
}
|
||||
@@ -947,8 +941,9 @@ fn compress_chunk(raw_data: &[u8], cancel_signal: &AtomicBool) -> Result<(Vec<u8
|
||||
// decompression. Also, we intentionally pick the lowest compression level
|
||||
// since we primarily care about squishing zeros. The non-zero portions of
|
||||
// boot images are usually already-compressed kernels and ramdisks.
|
||||
let stream = Stream::new_easy_encoder(0, Check::None).map_err(Error::XzInit)?;
|
||||
let mut xz_writer = XzEncoder::new_stream(hashing_writer, stream);
|
||||
let mut options = XZOptions::with_preset(0);
|
||||
options.set_check_sum_type(CheckType::None);
|
||||
let mut xz_writer = XZWriter::new(hashing_writer, options).map_err(Error::XzInit)?;
|
||||
|
||||
stream::copy_n(reader, &mut xz_writer, raw_data.len() as u64, cancel_signal)
|
||||
.map_err(Error::XzCompress)?;
|
||||
|
||||
@@ -522,11 +522,11 @@ impl ChunkList {
|
||||
// entire list every time.
|
||||
let mut insert_before = self.chunks.front_index();
|
||||
|
||||
if let Some(last_used) = self.last_used {
|
||||
if chunk.bounds.start >= self.chunks.get(last_used).unwrap().bounds.start {
|
||||
// The new chunk starts after the last used chunk.
|
||||
insert_before = Some(last_used);
|
||||
}
|
||||
if let Some(last_used) = self.last_used
|
||||
&& chunk.bounds.start >= self.chunks.get(last_used).unwrap().bounds.start
|
||||
{
|
||||
// The new chunk starts after the last used chunk.
|
||||
insert_before = Some(last_used);
|
||||
}
|
||||
|
||||
while let Some(index) = insert_before {
|
||||
|
||||
+28
-29
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2022-2025 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
@@ -15,10 +15,7 @@ use std::{
|
||||
};
|
||||
|
||||
use bstr::ByteSlice;
|
||||
use liblzma::{
|
||||
stream::{Check, Stream},
|
||||
write::XzEncoder,
|
||||
};
|
||||
use lzma_rust2::{CheckType, XZOptions, XZWriter};
|
||||
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||
use regex::bytes::Regex;
|
||||
use ring::digest::Context;
|
||||
@@ -38,6 +35,7 @@ use crate::{
|
||||
},
|
||||
patch::otacert::{self, OtaCertBuildFlags},
|
||||
stream::{self, FromReader, HashingWriter, ReadSeek, SectionReader, ToWriter, WriteSeek},
|
||||
util,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -83,7 +81,7 @@ pub enum Error {
|
||||
#[error("Failed to generate replacement otacerts zip")]
|
||||
OtaCertZip(#[source] otacert::Error),
|
||||
#[error("Failed to initialize XZ encoder")]
|
||||
XzInit(#[source] liblzma::stream::Error),
|
||||
XzInit(#[source] io::Error),
|
||||
#[error("Failed to XZ compress entry: {:?}", .0.as_bstr())]
|
||||
XzCompress(Vec<u8>, #[source] io::Error),
|
||||
#[error("Failed to open zip file: {0:?}")]
|
||||
@@ -288,9 +286,11 @@ impl MagiskRootPatcher {
|
||||
}
|
||||
|
||||
fn xz_compress(name: &[u8], reader: impl Read, cancel_signal: &AtomicBool) -> Result<Vec<u8>> {
|
||||
let stream = Stream::new_easy_encoder(9, Check::Crc32).map_err(Error::XzInit)?;
|
||||
let mut options = XZOptions::with_preset(9);
|
||||
options.set_check_sum_type(CheckType::None);
|
||||
|
||||
let raw_writer = Cursor::new(Vec::new());
|
||||
let mut writer = XzEncoder::new_stream(raw_writer, stream);
|
||||
let mut writer = XZWriter::new(raw_writer, options).map_err(Error::XzInit)?;
|
||||
|
||||
let raw_writer = stream::copy(reader, &mut writer, cancel_signal)
|
||||
.and_then(|_| writer.finish())
|
||||
@@ -378,14 +378,12 @@ impl MagiskRootPatcher {
|
||||
|
||||
let mut new_data = None;
|
||||
|
||||
if xz_compress {
|
||||
if let CpioEntryData::Data(data) = &old_entry.data {
|
||||
new_path.extend(b".xz");
|
||||
if xz_compress && let CpioEntryData::Data(data) = &old_entry.data {
|
||||
new_path.extend(b".xz");
|
||||
|
||||
let reader = Cursor::new(data);
|
||||
let buf = Self::xz_compress(&new_path, reader, cancel_signal)?;
|
||||
new_data = Some(CpioEntryData::Data(buf));
|
||||
}
|
||||
let reader = Cursor::new(data);
|
||||
let buf = Self::xz_compress(&new_path, reader, cancel_signal)?;
|
||||
new_data = Some(CpioEntryData::Data(buf));
|
||||
}
|
||||
|
||||
new_entries.push(CpioEntry {
|
||||
@@ -544,10 +542,10 @@ impl BootImagePatch for MagiskRootPatcher {
|
||||
|
||||
magisk_config.push_str("RECOVERYMODE=false\n");
|
||||
|
||||
if Self::VER_PREINIT_DEVICE.contains(&self.version) {
|
||||
if let Some(device) = &self.preinit_device {
|
||||
writeln!(&mut magisk_config, "PREINITDEVICE={device}").unwrap();
|
||||
}
|
||||
if Self::VER_PREINIT_DEVICE.contains(&self.version)
|
||||
&& let Some(device) = &self.preinit_device
|
||||
{
|
||||
writeln!(&mut magisk_config, "PREINITDEVICE={device}").unwrap();
|
||||
}
|
||||
|
||||
// Magisk normally saves the original SHA1 digest in its config file. It
|
||||
@@ -930,16 +928,17 @@ impl PrepatchedImagePatcher {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let kmi_version = captures
|
||||
.iter()
|
||||
// Capture #0 is the entire match.
|
||||
.skip(1)
|
||||
.flatten()
|
||||
.map(|c| c.as_bytes())
|
||||
// Our regex only matches ASCII bytes.
|
||||
.map(|c| std::str::from_utf8(c).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
.join("-");
|
||||
let kmi_version = util::join(
|
||||
captures
|
||||
.iter()
|
||||
// Capture #0 is the entire match.
|
||||
.skip(1)
|
||||
.flatten()
|
||||
.map(|c| c.as_bytes())
|
||||
// Our regex only matches ASCII bytes.
|
||||
.map(|c| std::str::from_utf8(c).unwrap()),
|
||||
"-",
|
||||
);
|
||||
|
||||
Ok(Some(kmi_version))
|
||||
}
|
||||
|
||||
@@ -95,10 +95,10 @@ pub fn create_zip(cert: &Certificate, flags: OtaCertBuildFlags) -> Result<Vec<u8
|
||||
modified.signature =
|
||||
BitString::from_bytes(&[]).expect("Empty ASN.1 bit string was invalid");
|
||||
}
|
||||
if flags.contains(OtaCertBuildFlags::REMOVE_EXTENSIONS) {
|
||||
if let Some(extensions) = &mut modified.tbs_certificate.extensions {
|
||||
extensions.clear();
|
||||
}
|
||||
if flags.contains(OtaCertBuildFlags::REMOVE_EXTENSIONS)
|
||||
&& let Some(extensions) = &mut modified.tbs_certificate.extensions
|
||||
{
|
||||
extensions.clear();
|
||||
}
|
||||
if flags.contains(OtaCertBuildFlags::REMOVE_ISSUER) {
|
||||
modified.tbs_certificate.issuer.0.clear();
|
||||
|
||||
+18
-12
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023 Andrew Gunnerson
|
||||
// SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
@@ -603,12 +603,14 @@ pub fn copy_n(
|
||||
copy_n_inspect(reader, writer, size, |_| {}, cancel_signal)
|
||||
}
|
||||
|
||||
/// Copy data from `reader` to `writer` until `reader` reaches EOF. If `writer`
|
||||
/// reaches EOF before `reader` does, an error is returned. The operation is
|
||||
/// cancelled on the next loop iteration if `cancel_signal` is set to `true`.
|
||||
pub fn copy(
|
||||
/// Copy data from `reader` to `writer` until `reader` reaches EOF, invoking
|
||||
/// `inspect` after every buffer read iteration. If `writer` reaches EOF before
|
||||
/// `reader` does, an error is returned. The operation is cancelled on the next
|
||||
/// loop iteration if `cancel_signal` is set to `true`.
|
||||
pub fn copy_inspect(
|
||||
mut reader: impl Read,
|
||||
mut writer: impl Write,
|
||||
mut inspect: impl FnMut(&[u8]),
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> io::Result<u64> {
|
||||
let mut buf = [0u8; 16384];
|
||||
@@ -622,6 +624,8 @@ pub fn copy(
|
||||
break;
|
||||
}
|
||||
|
||||
inspect(&buf[..n]);
|
||||
|
||||
writer.write_all(&buf[..n])?;
|
||||
|
||||
copied += n as u64;
|
||||
@@ -630,6 +634,11 @@ pub fn copy(
|
||||
Ok(copied)
|
||||
}
|
||||
|
||||
/// Copy data from `reader` to `writer` until `reader` reaches EOF.
|
||||
pub fn copy(reader: impl Read, writer: impl Write, cancel_signal: &AtomicBool) -> io::Result<u64> {
|
||||
copy_inspect(reader, writer, |_| {}, cancel_signal)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
@@ -822,21 +831,19 @@ mod tests {
|
||||
let mut reader = Cursor::new(b"foobar");
|
||||
let mut writer = Cursor::new([0u8; 6]);
|
||||
|
||||
super::copy_n_inspect(&mut reader, &mut writer, 6, |_| {}, &cancel_signal).unwrap();
|
||||
super::copy_n(&mut reader, &mut writer, 6, &cancel_signal).unwrap();
|
||||
assert_eq!(writer.get_ref(), b"foobar");
|
||||
|
||||
// Reader early EOF.
|
||||
reader.seek(SeekFrom::Start(3)).unwrap();
|
||||
writer.rewind().unwrap();
|
||||
let err =
|
||||
super::copy_n_inspect(&mut reader, &mut writer, 6, |_| {}, &cancel_signal).unwrap_err();
|
||||
let err = super::copy_n(&mut reader, &mut writer, 6, &cancel_signal).unwrap_err();
|
||||
assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
|
||||
|
||||
// Writer early EOF.
|
||||
reader.rewind().unwrap();
|
||||
writer.seek(SeekFrom::Start(3)).unwrap();
|
||||
let err =
|
||||
super::copy_n_inspect(&mut reader, &mut writer, 6, |_| {}, &cancel_signal).unwrap_err();
|
||||
let err = super::copy_n(&mut reader, &mut writer, 6, &cancel_signal).unwrap_err();
|
||||
assert_eq!(err.kind(), io::ErrorKind::WriteZero);
|
||||
|
||||
reader.rewind().unwrap();
|
||||
@@ -860,8 +867,7 @@ mod tests {
|
||||
reader.rewind().unwrap();
|
||||
writer.rewind().unwrap();
|
||||
cancel_signal.store(true, Ordering::SeqCst);
|
||||
let err =
|
||||
super::copy_n_inspect(&mut reader, &mut writer, 6, |_| {}, &cancel_signal).unwrap_err();
|
||||
let err = super::copy_n(&mut reader, &mut writer, 6, &cancel_signal).unwrap_err();
|
||||
assert_eq!(err.kind(), io::ErrorKind::Interrupted);
|
||||
let err = super::copy(&mut reader, &mut writer, &cancel_signal).unwrap_err();
|
||||
assert_eq!(err.kind(), io::ErrorKind::Interrupted);
|
||||
|
||||
+73
-10
@@ -8,7 +8,7 @@ use std::{
|
||||
ops::{
|
||||
Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
|
||||
},
|
||||
path::Path,
|
||||
path::{Component, Path, PathBuf},
|
||||
};
|
||||
|
||||
use num_traits::{NumCast, PrimInt};
|
||||
@@ -304,15 +304,76 @@ pub fn is_zero(mut buf: &[u8]) -> bool {
|
||||
/// Get the non-empty parent of a path. If the path has no parent in the string,
|
||||
/// then `.` is returned. This does not perform any filesystem operations.
|
||||
pub fn parent_path(path: &Path) -> &Path {
|
||||
if let Some(parent) = path.parent() {
|
||||
if !parent.as_os_str().is_empty() {
|
||||
return parent;
|
||||
}
|
||||
if let Some(parent) = path.parent()
|
||||
&& !parent.as_os_str().is_empty()
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
|
||||
Path::new(".")
|
||||
}
|
||||
|
||||
/// Path safety-related errors.
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum PathSafetyError {
|
||||
#[error("Path contains multiple components: {0:?}")]
|
||||
NotSingle(PathBuf),
|
||||
#[error("Path contains '..': {0:?}")]
|
||||
HasDotDot(PathBuf),
|
||||
}
|
||||
|
||||
/// Join `parent` with `child`, disallowing unsafe child paths. Absolute child
|
||||
/// paths are converted into relative paths and `..` components result in an
|
||||
/// error.
|
||||
pub fn path_join(
|
||||
parent: impl AsRef<Path>,
|
||||
child: impl AsRef<Path>,
|
||||
) -> Result<PathBuf, PathSafetyError> {
|
||||
fn inner(parent: &Path, child: &Path) -> Result<PathBuf, PathSafetyError> {
|
||||
let mut result = parent.to_owned();
|
||||
|
||||
for component in child.components() {
|
||||
match component {
|
||||
// Make absolute paths relative.
|
||||
Component::Prefix(_) | Component::RootDir | Component::CurDir => continue,
|
||||
// Unsafe path. We block this even if it wouldn't escape the parent.
|
||||
Component::ParentDir => {
|
||||
return Err(PathSafetyError::HasDotDot(child.to_path_buf()));
|
||||
}
|
||||
Component::Normal(os_str) => result.push(os_str),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
inner(parent.as_ref(), child.as_ref())
|
||||
}
|
||||
|
||||
/// Ensure that the path has no directory components.
|
||||
pub fn path_ensure_single(name: impl AsRef<Path>) -> Result<(), PathSafetyError> {
|
||||
fn inner(name: &Path) -> Result<(), PathSafetyError> {
|
||||
// Path::file_name() already checks for `.` and `..`.
|
||||
if name.file_name() != Some(name.as_os_str()) {
|
||||
return Err(PathSafetyError::NotSingle(name.to_path_buf()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
inner(name.as_ref())
|
||||
}
|
||||
|
||||
/// Like [`path_join`], but ensures that the child path contains no directory
|
||||
/// components with [`path_ensure_single`].
|
||||
pub fn path_join_single(
|
||||
parent: impl AsRef<Path>,
|
||||
child: impl AsRef<Path>,
|
||||
) -> Result<PathBuf, PathSafetyError> {
|
||||
path_ensure_single(child.as_ref())?;
|
||||
path_join(parent.as_ref(), child.as_ref())
|
||||
}
|
||||
|
||||
/// Sort and merge overlapping intervals.
|
||||
pub fn merge_overlapping<T>(sections: &[Range<T>]) -> Vec<Range<T>>
|
||||
where
|
||||
@@ -326,11 +387,11 @@ where
|
||||
for section in sections {
|
||||
if section.start >= section.end {
|
||||
continue;
|
||||
} else if let Some(last) = result.last_mut() {
|
||||
if section.start <= last.end {
|
||||
last.end = last.end.max(section.end);
|
||||
continue;
|
||||
}
|
||||
} else if let Some(last) = result.last_mut()
|
||||
&& section.start <= last.end
|
||||
{
|
||||
last.end = last.end.max(section.end);
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push(section);
|
||||
@@ -379,6 +440,7 @@ where
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Join arbitrary displayable items with a separator.
|
||||
pub fn join(into_iter: impl IntoIterator<Item = impl Display>, sep: &str) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
@@ -395,6 +457,7 @@ pub fn join(into_iter: impl IntoIterator<Item = impl Display>, sep: &str) -> Str
|
||||
result
|
||||
}
|
||||
|
||||
/// Sort arbitrary sequence of sortable items.
|
||||
pub fn sort<T: Ord>(iter: impl Iterator<Item = T>) -> Vec<T> {
|
||||
let mut items = iter.collect::<Vec<_>>();
|
||||
items.sort();
|
||||
|
||||
@@ -63,11 +63,14 @@ include-workspace = true
|
||||
bypass = [
|
||||
# Copies of unmodified crashwrangler objects for old macOS versions.
|
||||
{ name = "honggfuzz", allow-globs = ["honggfuzz/third_party/mac/CrashReport_*.o"] },
|
||||
# Only used in tests.
|
||||
{ name = "libloading", allow-globs = ["tests/nagisa*.dll"] },
|
||||
]
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
allow-git = [
|
||||
"https://github.com/chenxiaolong/system-properties",
|
||||
"https://github.com/chenxiaolong/zip2",
|
||||
]
|
||||
|
||||
@@ -32,8 +32,5 @@ git = "https://github.com/chenxiaolong/zip2"
|
||||
rev = "59685f4dadbfee8cb3ea74c8fbb402b60d8137e8"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
static = ["avbroot/static"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+16
-16
@@ -51,12 +51,12 @@ data.version = "vendor_v4"
|
||||
data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
|
||||
|
||||
[profile.pixel_v4_gki.hashes_streaming]
|
||||
original = "ef6261cd9ebea90f036e52a46160a400c5b8f6ef24ed2469c4a1e9689987aa06"
|
||||
patched = "37fd353a766a7b9a339fbf51fa79c703e94640dc6a2c6310d79357aaefcc7ca1"
|
||||
original = "4e283ad1e3450795a32f46445bf626f6af983f52bd0a3484b49f8740c1029653"
|
||||
patched = "837861bf64e9387380e02d740b21c94e60c41bba4161d85ab44fc5ce86f19631"
|
||||
|
||||
[profile.pixel_v4_gki.hashes_seekable]
|
||||
original = "8a2c717607c10dfa5483d6f9a9f37b3d978acaf8d2ea18e36544af267943e750"
|
||||
patched = "2c4734c9e1d028ee6aaf02bb416e5e173857faffd2ca067366790655147b3afa"
|
||||
original = "0ab2403a2634f00063c44f9a477a672205922ede189442e18850dd8efceb5d6f"
|
||||
patched = "2d94841c3be6cc1739f4c7ad5d9db048301b3f93f8ccd510e2c825bf13ecff90"
|
||||
|
||||
# Google Pixel 6a
|
||||
# What's unique: boot (boot v4, no ramdisk) + vendor_boot (vendor v4, 2 ramdisks)
|
||||
@@ -93,12 +93,12 @@ data.version = "vendor_v4"
|
||||
data.ramdisks = [["init", "otacerts", "first_stage", "dsu_key_dir"], ["dlkm"]]
|
||||
|
||||
[profile.pixel_v4_non_gki.hashes_streaming]
|
||||
original = "630220ef813a2b4743d1941179cc9705da86ad4805f1c52341dcb38fbce3d29e"
|
||||
patched = "b725e91751fe58aed20495aecbf9b4bdc14d2799cd88dcbd58f3a3b02b3af15b"
|
||||
original = "c3a978b7225632875d3b1e87e852494b1a69019f5ba4099cf3981503623facca"
|
||||
patched = "cf0ef7429c4657875018d10d7302aaca371b21c5968af234aabd941304f0035b"
|
||||
|
||||
[profile.pixel_v4_non_gki.hashes_seekable]
|
||||
original = "1afbe6867ded345d941098ee7c7fcf94a3df52c50ff96ab8f3a67b2ab957259a"
|
||||
patched = "4357b977249006b101002c961916f962787315a80b8608494c6a1f0cf09cecd1"
|
||||
original = "cc11ee5a5be66bf34dcb6834a9a635016fa9f82dd0d9a2fb1029aa4f218d1d2e"
|
||||
patched = "e2fb7d3ce2c697372fb342ae5cd7bb606cd764f06a2aa8a2d6d7bd2c88456780"
|
||||
|
||||
# Google Pixel 4a 5G
|
||||
# What's unique: boot (boot v3) + vendor_boot (vendor v3)
|
||||
@@ -136,12 +136,12 @@ data.version = "vendor_v3"
|
||||
data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
|
||||
|
||||
[profile.pixel_v3.hashes_streaming]
|
||||
original = "9b65037343d45211e0f9706929cba34643a9c54274d1b39740c43f45974984e0"
|
||||
patched = "fb23ab9616968b38b96d1e5e6a503154f89aebc1741e89a9e9dfd2c4d9946b05"
|
||||
original = "d12d92c051bcc832cc9ceef1d46c415af80a09808a984db4c42a88d65d644a8b"
|
||||
patched = "408496eb2cca51c0563eceaa3a9def95a5c42acff938850a8577aaa047331284"
|
||||
|
||||
[profile.pixel_v3.hashes_seekable]
|
||||
original = "e581934887dd93b8a9d9c3aa5dec1d48aa7e01bf01ac507e8c5fb256b59cbe7d"
|
||||
patched = "669a826abc6d67e7e0b1def724aa7b470461087255c65663d195df1426a355f0"
|
||||
original = "63505cfd7c2c9d948a5ee150cf53f448f11c83cdce3919fd43231640bf002812"
|
||||
patched = "b246497de1588d41b919ba002b37dff13cb565682aa3e086e543b25ead7d7aaf"
|
||||
|
||||
# Google Pixel 4a
|
||||
# What's unique: boot (boot v2)
|
||||
@@ -169,9 +169,9 @@ data.type = "vbmeta"
|
||||
data.deps = ["system"]
|
||||
|
||||
[profile.pixel_v2.hashes_streaming]
|
||||
original = "f10ee15c900a474cc6bbefa705f272cef42636ea096e75563d2d78f6c4327fd1"
|
||||
patched = "6929f65909037f5550a53982b71e96bdf69ab876bc5e86702c469ed601be8a9a"
|
||||
original = "c303728f4ee9c42ef990bb576bd0688635b263a67127d2a986caf574c9eedd63"
|
||||
patched = "e907db408767f81223e3ae53dd8a44c6f59f4702a016093cd6983499c9391ff0"
|
||||
|
||||
[profile.pixel_v2.hashes_seekable]
|
||||
original = "4e863d251b9ff6eaa1511f9c03e9bdb8919650b2e0eaf23e33892a639edafcaf"
|
||||
patched = "9a103222e73df70a097281525546d25c850df2ae7a2ba715aa5dfbbba3f7972b"
|
||||
original = "951b1f70bef7736b9e06f3215357fcebf8a4925872cd31691198fdda9d7c04ce"
|
||||
patched = "2b1bfe46f41942be88e09b40000745c68ed133900d7970f57d15019683a1ba87"
|
||||
|
||||
+2
-5
@@ -47,6 +47,7 @@ use avbroot::{
|
||||
},
|
||||
},
|
||||
stream::{self, CountingWriter, FromReader, HashingReader, PSeekFile, Reopen, ToWriter},
|
||||
util,
|
||||
};
|
||||
use clap::Parser;
|
||||
use rsa::{BigUint, rand_core::OsRng, traits::PublicKeyParts};
|
||||
@@ -1222,10 +1223,6 @@ fn test_subcommand(cli: &TestCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
];
|
||||
|
||||
for name in profiles {
|
||||
if Path::new(name).file_name() != Some(OsStr::new(name)) {
|
||||
bail!("Unsafe profile name: {name}");
|
||||
}
|
||||
|
||||
let profile = &config.profile[name];
|
||||
|
||||
for (zip_mode, hashes) in [
|
||||
@@ -1235,7 +1232,7 @@ fn test_subcommand(cli: &TestCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let _span = info_span!("profile", name, %zip_mode).entered();
|
||||
|
||||
// Can't use NamedTempFile because avbroot does atomic replaces.
|
||||
let mut profile_dir = work_dir.join(name);
|
||||
let mut profile_dir = util::path_join_single(work_dir, name)?;
|
||||
profile_dir.push(zip_mode.to_string());
|
||||
let out_original = profile_dir.join("ota.zip");
|
||||
let out_magisk = profile_dir.join("ota_magisk.zip");
|
||||
|
||||
Reference in New Issue
Block a user