Compare commits

..

14 Commits

Author SHA1 Message Date
Andrew Gunnerson 10370692d0 Version 3.1.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:51:51 -05:00
Andrew Gunnerson 73b6e9b177 CHANGELOG.md: Add entry for PR #256
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:51:15 -05:00
Andrew Gunnerson 8d25e28ea3 Fix clippy warnings and formatting
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:36:20 -05:00
Andrew Gunnerson 82fc1336f7 Update all dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:32:59 -05:00
Andrew Gunnerson 953c0c1c5b CHANGELOG.md: Add entry for PR #251
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:23:35 -05:00
Andrew Gunnerson c3b073cece Add tracing support
Instead of println'ing everything, this commit switches the code base to
using the tracing library. There are now proper log levels and multiple
logging output formats. A bunch of new debug and trace-level messages
have also been added to help with future troubleshooting.

By default, the output is kept nice and short. Spans won't be shown
unless the log level is set to debug or lower.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:16:57 -05:00
Andrew Gunnerson 565efc5ee5 CHANGELOG.md: Add entry for PR #255
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 14:58:39 -05:00
Andrew Gunnerson 6ef8e548a3 Add support for Magisk v27.0
Upstream Magisk now xz-compresses files in modifies in the ramdisk. This
commit also implements the same in avbroot's MagiskRootPatcher.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 14:35:57 -05:00
Andrew Gunnerson 80549346ea README.md: Temporarily suggest manual procedure for initially flashing system.img
We'll switch to using `fastboot flashall` in the future once that has
been implemented and tested.

Fixes: #252

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-01-31 17:39:13 -05:00
Andrew Gunnerson 275b18b2a5 CHANGELOG.md: Add entry for PR #247
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-31 16:29:40 -05:00
Andrew Gunnerson 0a68966594 Switch from xz2 to liblzma
liblzma is a maintained fork of xz2. We can now get rid of our own fork
of xz2.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-31 16:20:30 -05:00
Andrew Gunnerson 9ef5629556 CHANGELOG.md: Add entry for PR #246
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-30 19:25:49 -05:00
Andrew Gunnerson 1db09eb896 Merge pull request #246 from chenxiaolong/remove_modules
Remove oemunlockonboot module
2023-12-30 19:24:06 -05:00
Andrew Gunnerson a134d7f889 Remove oemunlockonboot module
The module has been split out into another repo [1] so that it can be
versioned separately. It now also supports Magisk's automatic update
mechanism.

Closes: #235

[1] https://github.com/chenxiaolong/OEMUnlockOnBoot

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-30 19:16:05 -05:00
33 changed files with 693 additions and 847 deletions
-35
View File
@@ -1,35 +0,0 @@
---
name: Modules
on:
push:
branches:
- master
pull_request:
jobs:
build:
name: Build modules
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
with:
# For git describe
fetch-depth: 0
- name: Get version
id: get_version
shell: bash
run: |
echo -n 'version=' >> "${GITHUB_OUTPUT}"
git describe --always \
| sed -E "s/^v//g;s/([^-]*-g)/r\1/;s/-/./g" \
>> "${GITHUB_OUTPUT}"
- name: Build modules
run: cargo xtask modules -a
- name: Archive artifacts
uses: actions/upload-artifact@v3
with:
name: avbroot-modules-${{ steps.get_version.outputs.version }}
path: modules/dist/
+20 -1
View File
@@ -7,6 +7,20 @@
to update the actual links at the bottom of the file.
-->
### Version 3.1.0
* The `OEMUnlockOnBoot` module has been split out to a separate repo ([Discussion #235], [PR #246])
* https://github.com/chenxiaolong/OEMUnlockOnBoot
* The new module supports the automatic update mechanism within Magisk/KernelSU
* Add support for Magisk v27.0 ([PR #255])
* Switch to using a proper logging library ([PR #251])
* Folks who want to see the juicy details during patching can use `--log-level debug` or `--log-level trace`
Behind-the-scenes changes:
* Switch from xz2 to liblzma (maintained fork of xz2) ([PR #247])
* Update all dependencies ([PR #256])
### Version 3.0.0
Happy New Year! This release brings two major changes:
@@ -140,6 +154,7 @@ Behind-the-scenes changes:
<!-- Do not manually edit the lines below. Use `cargo xtask update-changelog` to regenerate. -->
[Discussion #195]: https://github.com/chenxiaolong/avbroot/discussions/195
[Discussion #235]: https://github.com/chenxiaolong/avbroot/discussions/235
[Issue #138]: https://github.com/chenxiaolong/avbroot/issues/138
[Issue #144]: https://github.com/chenxiaolong/avbroot/issues/144
[Issue #145]: https://github.com/chenxiaolong/avbroot/issues/145
@@ -207,7 +222,6 @@ Behind-the-scenes changes:
[PR #211]: https://github.com/chenxiaolong/avbroot/pull/211
[PR #214]: https://github.com/chenxiaolong/avbroot/pull/214
[PR #219]: https://github.com/chenxiaolong/avbroot/pull/219
[PR #220]: https://github.com/chenxiaolong/avbroot/pull/220
[PR #221]: https://github.com/chenxiaolong/avbroot/pull/221
[PR #224]: https://github.com/chenxiaolong/avbroot/pull/224
[PR #226]: https://github.com/chenxiaolong/avbroot/pull/226
@@ -225,3 +239,8 @@ Behind-the-scenes changes:
[PR #243]: https://github.com/chenxiaolong/avbroot/pull/243
[PR #244]: https://github.com/chenxiaolong/avbroot/pull/244
[PR #245]: https://github.com/chenxiaolong/avbroot/pull/245
[PR #246]: https://github.com/chenxiaolong/avbroot/pull/246
[PR #247]: https://github.com/chenxiaolong/avbroot/pull/247
[PR #251]: https://github.com/chenxiaolong/avbroot/pull/251
[PR #255]: https://github.com/chenxiaolong/avbroot/pull/255
[PR #256]: https://github.com/chenxiaolong/avbroot/pull/256
Generated
+246 -170
View File
@@ -36,9 +36,9 @@ checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b"
[[package]]
name = "anstream"
version = "0.6.5"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -50,9 +50,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
[[package]]
name = "anstyle-parse"
@@ -84,9 +84,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.77"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
[[package]]
name = "arbitrary"
@@ -108,12 +108,12 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "avbroot"
version = "3.0.0"
version = "3.1.0"
dependencies = [
"anyhow",
"assert_matches",
"base64",
"bitflags 2.4.1",
"bitflags 2.4.2",
"bstr",
"byteorder",
"bzip2",
@@ -127,6 +127,7 @@ dependencies = [
"flate2",
"gf256",
"hex",
"liblzma",
"lz4_flex",
"memchr",
"num-bigint-dig",
@@ -150,16 +151,17 @@ dependencies = [
"thiserror",
"toml_edit",
"topological-sort",
"tracing",
"tracing-subscriber",
"x509-cert",
"xz2",
"zip",
]
[[package]]
name = "base64"
version = "0.21.5"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64ct"
@@ -181,9 +183,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "block-buffer"
@@ -247,9 +249,9 @@ dependencies = [
[[package]]
name = "cap-primitives"
version = "2.0.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf30c373a3bee22c292b1b6a7a26736a38376840f1af3d2d806455edf8c3899"
checksum = "90a0b44fc796b1a84535a63753d50ba3972c4db55c7255c186f79140e63d56d0"
dependencies = [
"ambient-authority",
"fs-set-times",
@@ -258,15 +260,15 @@ dependencies = [
"ipnet",
"maybe-owned",
"rustix",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
"winx",
]
[[package]]
name = "cap-std"
version = "2.0.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84bade423fa6403efeebeafe568fdb230e8c590a275fba2ba978dd112efcf6e9"
checksum = "266626ce180cf9709f317d0bf9754e3a5006359d87f4bf792f06c9c5f1b63c0f"
dependencies = [
"cap-primitives",
"io-extras",
@@ -276,9 +278,9 @@ dependencies = [
[[package]]
name = "cap-tempfile"
version = "2.0.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b9e3348a3510c4619b4c7a7bcdef09a71221da18f266bda3ed6b9aea2c509e2"
checksum = "8748c8ae8186b7362da62c7cb8edf7aabae095c86175e988d76387a842574e16"
dependencies = [
"cap-std",
"rand",
@@ -301,6 +303,7 @@ version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"jobserver",
"libc",
]
@@ -322,9 +325,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.12"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
dependencies = [
"clap_builder",
"clap_derive",
@@ -332,9 +335,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.12"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
dependencies = [
"anstream",
"anstyle",
@@ -344,9 +347,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.4.5"
version = "4.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a51919c5608a32e34ea1d6be321ad070065e17613e168c5b6977024290f2630b"
checksum = "abb745187d7f4d76267b37485a65e0149edd0e91a4cfcdd3f27524ad86cee9f3"
dependencies = [
"clap",
]
@@ -360,7 +363,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@@ -371,9 +374,9 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "cms"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01b1b34bce0eaafd63b374fa6b58178d72c0b6670e92db786bdd3cde9e37a1f1"
checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730"
dependencies = [
"const-oid",
"der",
@@ -395,9 +398,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "cpufeatures"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
@@ -413,34 +416,28 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.4"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.17"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.18"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
dependencies = [
"cfg-if",
]
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crypto-common"
@@ -518,7 +515,7 @@ checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@@ -535,7 +532,7 @@ dependencies = [
[[package]]
name = "e2e"
version = "3.0.0"
version = "3.1.0"
dependencies = [
"anyhow",
"avbroot",
@@ -548,6 +545,8 @@ dependencies = [
"tempfile",
"toml_edit",
"topological-sort",
"tracing",
"tracing-subscriber",
"x509-cert",
"zip",
]
@@ -627,7 +626,7 @@ dependencies = [
[[package]]
name = "fuzz"
version = "3.0.0"
version = "3.1.0"
dependencies = [
"avbroot",
"honggfuzz",
@@ -645,9 +644,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
@@ -728,12 +727,6 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "human-sort"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219"
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -742,9 +735,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.1.0"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
dependencies = [
"equivalent",
"hashbrown",
@@ -797,6 +790,15 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobserver"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -808,9 +810,29 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.151"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "liblzma"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b864ea21f326142b902d10658834cc1c9e758c96a579f4b0b1aa655edb0b351c"
dependencies = [
"liblzma-sys",
]
[[package]]
name = "liblzma-sys"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be9aaba5f9c8f8f615d41570909338b6284fbb1813dc057ecc68563d98a65097"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "libm"
@@ -820,9 +842,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
@@ -850,7 +872,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex-syntax 0.6.29",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@@ -864,23 +886,13 @@ dependencies = [
[[package]]
name = "lz4_flex"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8"
checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15"
dependencies = [
"twox-hash",
]
[[package]]
name = "lzma-sys"
version = "0.1.20"
source = "git+https://github.com/chenxiaolong/xz2-rs?rev=fe2050b9c3395db15d8610f1dabb505440c1a556#fe2050b9c3395db15d8610f1dabb505440c1a556"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "maybe-owned"
version = "0.3.4"
@@ -922,14 +934,14 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
@@ -946,11 +958,21 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.2",
"cfg-if",
"libc",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@@ -1006,6 +1028,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "pbkdf2"
version = "0.12.2"
@@ -1065,7 +1093,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@@ -1077,6 +1105,12 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pkcs1"
version = "0.7.5"
@@ -1117,9 +1151,9 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.28"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
[[package]]
name = "ppv-lite86"
@@ -1129,19 +1163,19 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "prettyplease"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
dependencies = [
"proc-macro2",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
name = "proc-macro2"
version = "1.0.71"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@@ -1173,7 +1207,7 @@ dependencies = [
"prost",
"prost-types",
"regex",
"syn 2.0.43",
"syn 2.0.48",
"tempfile",
"which",
]
@@ -1188,7 +1222,7 @@ dependencies = [
"itertools",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@@ -1242,9 +1276,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@@ -1281,9 +1315,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
dependencies = [
"either",
"rayon-core",
@@ -1291,9 +1325,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@@ -1310,9 +1344,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.2"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
@@ -1322,9 +1356,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.3"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
@@ -1411,11 +1445,11 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.28"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.2",
"errno",
"itoa",
"libc",
@@ -1433,15 +1467,6 @@ dependencies = [
"cipher",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scrypt"
version = "0.11.0"
@@ -1455,28 +1480,28 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.20"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.193"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.193"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@@ -1510,6 +1535,15 @@ dependencies = [
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "signature"
version = "2.2.0"
@@ -1528,9 +1562,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "smallvec"
version = "1.11.2"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "spin"
@@ -1585,9 +1619,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.43"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@@ -1609,29 +1643,39 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.52"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.52"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tls_codec"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38a1d5fcfa859f0ec2b5e111dc903890bd7dac7f34713232bf9aa4fd7cad7b2"
checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a"
dependencies = [
"tls_codec_derive",
"zeroize",
@@ -1639,13 +1683,13 @@ dependencies = [
[[package]]
name = "tls_codec_derive"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e00e3e7a54e0f1c8834ce72ed49c8487fbd3f801d8cfe1a0ad0640382f8e15"
checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
@@ -1659,9 +1703,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.21.0"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [
"indexmap",
"serde",
@@ -1676,6 +1720,63 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d"
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
name = "twox-hash"
version = "1.6.3"
@@ -1718,29 +1819,25 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.6.1"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
dependencies = [
"getrandom",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -1775,15 +1872,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -1924,9 +2012,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.31"
version = "0.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c"
checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5"
dependencies = [
"memchr",
]
@@ -1937,7 +2025,7 @@ version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.2",
"windows-sys 0.52.0",
]
@@ -1957,24 +2045,12 @@ dependencies = [
[[package]]
name = "xtask"
version = "3.0.0"
version = "3.1.0"
dependencies = [
"anyhow",
"clap",
"human-sort",
"regex",
"tempfile",
"toml_edit",
"walkdir",
"zip",
]
[[package]]
name = "xz2"
version = "0.1.7"
source = "git+https://github.com/chenxiaolong/xz2-rs?rev=fe2050b9c3395db15d8610f1dabb505440c1a556#fe2050b9c3395db15d8610f1dabb505440c1a556"
dependencies = [
"lzma-sys",
]
[[package]]
@@ -1994,7 +2070,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.48",
]
[[package]]
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.0.0"
version = "3.1.0"
license = "GPL-3.0-only"
edition = "2021"
repository = "https://github.com/chenxiaolong/avbroot"
+10 -32
View File
@@ -33,11 +33,7 @@ avbroot applies the following patches to the partition images:
Repeat: **_ALWAYS leave `OEM unlocking` enabled if rooted._**
* Any operation that causes an improperly-signed boot image to be flashed will result in the device being unbootable and unrecoverable without unlocking the bootloader again (and thus, triggering a data wipe). This includes:
* Performing a unpatched A/B OTA update while booted into Android via the OS' default updater. This can be blocked via a Magisk/KernelSU module (see: [Blocking A/B OTA Updates](#blocking-ab-ota-updates)).
* The `Direct install` method for updating Magisk. Magisk updates **must** be done by repatching the OTA, not via the app.
* Any operation that causes an improperly-signed boot image to be flashed will result in the device being unbootable and unrecoverable without unlocking the bootloader again (and thus, triggering a data wipe). This includes the `Direct install` method for updating Magisk. Magisk updates **must** be done by repatching the OTA, not via the app.
If the boot image is ever modified, **do not reboot**. [Open an issue](https://github.com/chenxiaolong/avbroot/issues/new) for support and be very clear about what steps were done that lead to the situation. If Android is still running and root access works, it might be possible to recover without wiping and starting over.
@@ -148,15 +144,17 @@ If you lose your AVB or OTA signing key, you will no longer be able to sign new
4. Flash the partition images that were extracted.
This can be done by manually running `fastboot flash <partition> extracted/<partition>.img` for each image in the `extracted/` directory or by using the following script:
For each partition inside `extracted/`, except for `system`, run:
```bash
for image in extracted/*.img; do
partition=$(basename "${image}")
partition=${partition%.img}
fastboot flash <partition> extracted/<partition>.img
```
fastboot flash "${partition}" "${image}"
done
Then, reboot into recovery's fastbootd mode and flash `system`:
```bash
fastboot reboot fastboot
fastboot flash system extracted/system.img
```
5. Set up the custom AVB public key in the bootloader.
@@ -184,12 +182,10 @@ If you lose your AVB or OTA signing key, you will no longer be able to sign new
Remember: **Do not uncheck `OEM unlocking`!**
**WARNING**: If you are flashing CalyxOS, the setup wizard will [automatically turn off the `OEM unlocking` switch](https://github.com/CalyxOS/platform_packages_apps_SetupWizard/blob/7d2df25cedcbff83ddb608e628f9d97b38259c26/src/org/lineageos/setupwizard/SetupWizardApp.java#L135-L140). Make sure to manually reenable it again from Android's developer settings. Consider using [avbroot's `oemunlockonboot` Magisk module](#oemunlockonboot-enable-oem-unlocking-on-every-boot) to automatically ensure OEM unlocking is enabled on every boot.
**WARNING**: If you are flashing CalyxOS, the setup wizard will [automatically turn off the `OEM unlocking` switch](https://github.com/CalyxOS/platform_packages_apps_SetupWizard/blob/7d2df25cedcbff83ddb608e628f9d97b38259c26/src/org/lineageos/setupwizard/SetupWizardApp.java#L135-L140). Make sure to manually reenable it again from Android's developer settings. Consider using the [`OEMUnlockOnBoot` module](https://github.com/chenxiaolong/OEMUnlockOnBoot) to automatically ensure OEM unlocking is enabled on every boot.
8. That's it! To install future OS, Magisk, or KernelSU updates, see the [next section](#updates).
For extra safety, consider flashing [avbroot's Magisk/KernelSU modules](#avbroot-modules).
## Updates
Updates to Android, Magisk, and KernelSU are all done the same way by patching (or repatching) the OTA.
@@ -251,16 +247,6 @@ To safely use Repair Mode:
Because the unrooting and rooting are done by flashing OTAs, the device's data will not be wiped.
## avbroot modules
avbroot's Magisk/KernelSU modules can be downloaded from the [releases page](https://github.com/chenxiaolong/avbroot/releases).
### `oemunlockonboot`: Enable OEM unlocking on every boot
To help reduce the risk of OEM unlocking being accidentally disabled (or intentionally disabled as part of some OS' initial setup wizard), this module will attempt to enable the OEM unlocking option on every boot.
The logs for this module can be found at `/data/local/tmp/avbroot_oem_unlock.log`.
## Magisk preinit device
Magisk versions 25211 and newer require a writable partition for storing custom SELinux rules that need to be accessed during early boot stages. This can only be determined on a real device, so avbroot requires the partition to be explicitly specified via `--magisk-preinit-device <name>`. To find the partition name:
@@ -437,14 +423,6 @@ Debug builds work too, but they will run significantly slower (in the sha256 com
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`.
To build avbroot's modules from source, run:
```bash
cargo xtask modules -a
```
This requires Java and the Android SDK to be installed. The `ANDROID_HOME` environment variable must be set to the Android SDK path.
## Verifying digital signatures
First, save the public key to a file listing the keys to be trusted. This is the same key listed in [the author's profile](https://github.com/chenxiaolong/).
+6 -11
View File
@@ -14,8 +14,8 @@ base64 = "0.21.3"
bitflags = "2.4.1"
bstr = "1.6.2"
byteorder = "1.4.3"
cap-std = "2.0.0"
cap-tempfile = "2.0.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"] }
@@ -24,6 +24,7 @@ ctrlc = "3.4.0"
flate2 = "1.0.27"
gf256 = { version = "0.3.0", features = ["rs"] }
hex = { version = "0.4.3", features = ["serde"] }
liblzma = "0.2.1"
lz4_flex = "0.11.1"
memchr = "2.6.0"
num-bigint-dig = "0.8.4"
@@ -48,6 +49,8 @@ tempfile = "3.8.0"
thiserror = "1.0.47"
toml_edit = { version = "0.21.0", features = ["serde"] }
topological-sort = "0.2.2"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
x509-cert = { version = "0.2.4", features = ["builder"] }
# There's an upstream bug that causes an infinite loop in the write::BzDecoder
@@ -59,14 +62,6 @@ x509-cert = { version = "0.2.4", features = ["builder"] }
git = "https://github.com/jongiddy/bzip2-rs"
rev = "2aefcb4d3634de1df226c73d93f758d65228bb8c"
# The upstream xz2 crate uses an old version of liblzma when compiling with the
# `static` feature and doesn't enable all of the encoders and decoders. This
# causes certain payload data to fail to decompress.
# https://github.com/chenxiaolong/avbroot/issues/138
[dependencies.xz2]
git = "https://github.com/chenxiaolong/xz2-rs"
rev = "fe2050b9c3395db15d8610f1dabb505440c1a556"
# https://github.com/zip-rs/zip/pull/383
[dependencies.zip]
git = "https://github.com/chenxiaolong/zip"
@@ -85,4 +80,4 @@ protox = "0.5.0"
assert_matches = "1.5.0"
[features]
static = ["bzip2/static", "xz2/static"]
static = ["bzip2/static", "liblzma/static"]
+118 -3
View File
@@ -3,10 +3,17 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
use std::sync::atomic::AtomicBool;
use std::{
fmt,
io::{self, IsTerminal},
sync::atomic::{AtomicBool, Ordering},
time::Instant,
};
use anyhow::Result;
use clap::{Parser, Subcommand};
use clap::{Parser, Subcommand, ValueEnum};
use tracing::{debug, Level};
use tracing_subscriber::fmt::{format::Writer, time::FormatTime};
use crate::cli::{avb, boot, completion, cpio, fec, hashtree, key, ota};
@@ -29,16 +36,124 @@ pub enum Command {
MagiskInfo(boot::MagiskInfoCli),
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl LogLevel {
fn as_level(self) -> Level {
match self {
Self::Trace => Level::TRACE,
Self::Debug => Level::DEBUG,
Self::Info => Level::INFO,
Self::Warn => Level::WARN,
Self::Error => Level::ERROR,
}
}
}
impl Default for LogLevel {
fn default() -> Self {
Self::Info
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_possible_value().ok_or(fmt::Error)?.get_name())
}
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum LogFormat {
Short,
Medium,
Long,
}
impl Default for LogFormat {
fn default() -> Self {
Self::Short
}
}
impl fmt::Display for LogFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_possible_value().ok_or(fmt::Error)?.get_name())
}
}
#[derive(Debug, Parser)]
#[command(version)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
/// Lowest log message severity to output.
#[arg(long, global = true, value_name = "LEVEL", default_value_t)]
pub log_level: LogLevel,
/// Output format for log messages.
#[arg(long, global = true, value_name = "FORMAT", default_value_t)]
pub log_format: LogFormat,
}
pub fn main(cancel_signal: &AtomicBool) -> Result<()> {
#[derive(Debug, Clone, Copy)]
pub struct ShortUptime {
epoch: Instant,
}
impl Default for ShortUptime {
fn default() -> Self {
Self {
epoch: Instant::now(),
}
}
}
impl FormatTime for ShortUptime {
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
let e = self.epoch.elapsed();
write!(w, "{:3}.{:03}s", e.as_secs(), e.subsec_millis())
}
}
pub fn init_logging(log_level: LogLevel, log_format: LogFormat) {
let builder = tracing_subscriber::fmt()
.with_writer(io::stderr)
.with_ansi(io::stderr().is_terminal())
.with_max_level(log_level.as_level());
match log_format {
LogFormat::Short => {
let format = tracing_subscriber::fmt::format()
.with_timer(ShortUptime::default())
.with_target(false);
builder.event_format(format).init();
}
LogFormat::Medium => {
builder.with_timer(ShortUptime::default()).init();
}
LogFormat::Long => {
builder.pretty().init();
}
}
}
pub fn main(logging_initialized: &AtomicBool, cancel_signal: &AtomicBool) -> Result<()> {
let cli = Cli::parse();
init_logging(cli.log_level, cli.log_format);
logging_initialized.store(true, Ordering::SeqCst);
debug!(?cli);
match cli.command {
Command::Avb(c) => avb::avb_main(&c, cancel_signal),
Command::Boot(c) => boot::boot_main(&c),
+24 -19
View File
@@ -22,9 +22,9 @@ use clap::{Args, Parser, Subcommand};
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use rsa::RsaPublicKey;
use serde::{Deserialize, Serialize};
use tracing::{debug_span, info, warn, Span};
use crate::{
cli::{status, warning},
crypto::{self, PassphraseSource},
format::avb::{
self, AlgorithmType, AppendedDescriptorMut, AppendedDescriptorRef, Descriptor, Footer,
@@ -99,7 +99,7 @@ fn promote_insecure_hash_algorithm(algorithm: &str) -> &str {
const NEW_ALGORITHM: &str = "sha256";
if INSECURE_ALGORITHMS.contains(&algorithm) {
warning!("Changing insecure hash algorithm {algorithm} to {NEW_ALGORITHM}");
warn!("Changing insecure hash algorithm {algorithm} to {NEW_ALGORITHM}");
NEW_ALGORITHM
} else {
algorithm
@@ -166,7 +166,7 @@ fn write_raw_and_verify(
if let Err(e) = result {
if ignore_invalid {
warning!("{e:?}");
warn!("{e:?}");
} else {
return Err(e);
}
@@ -358,16 +358,16 @@ fn sign_or_clear(info: &mut AvbInfo, orig_header: &Header, key_group: &KeyGroup)
match sign_action {
SignAction::None => {
if originally_signed {
status!("Preserving original AVB header signature");
info!("Preserving original AVB header signature");
} else {
status!("Leaving AVB header unsigned");
info!("Leaving AVB header unsigned");
}
}
SignAction::Sign => {
if originally_signed {
status!("Replacing AVB header signature");
info!("Replacing AVB header signature");
} else {
status!("Signing AVB header");
info!("Signing AVB header");
}
let Some(key_path) = &key_group.key else {
@@ -389,9 +389,9 @@ fn sign_or_clear(info: &mut AvbInfo, orig_header: &Header, key_group: &KeyGroup)
}
SignAction::Clear => {
if originally_signed {
status!("Clearing AVB header signature");
info!("Clearing AVB header signature");
} else {
status!("Leaving AVB header unsigned");
info!("Leaving AVB header unsigned");
}
info.header.algorithm_type = AlgorithmType::None;
@@ -451,15 +451,15 @@ pub fn verify_headers(
if let Some(e) = expected_key {
if k == e {
status!("{prefix}");
info!("{prefix}");
} else {
bail!("{prefix}, but is signed by an untrusted key");
}
} else {
warning!("{prefix}, but parent does not list a trusted key");
warn!("{prefix}, but parent does not list a trusted key");
}
} else {
status!("{name} has an unsigned vbmeta header");
info!("{name} has an unsigned vbmeta header");
}
for descriptor in &header.descriptors {
@@ -502,6 +502,7 @@ fn verify_and_repair(
repair: bool,
cancel_signal: &AtomicBool,
) -> Result<()> {
let _span = debug_span!("image", name = name.unwrap_or_default()).entered();
let suffix = match name {
Some(n) => format!(" for: {n}"),
None => String::new(),
@@ -509,18 +510,18 @@ fn verify_and_repair(
match descriptor {
AppendedDescriptorRef::HashTree(d) => {
status!("Verifying hash tree descriptor{suffix}");
info!("Verifying hash tree descriptor{suffix}");
match d.verify(&file, cancel_signal) {
Err(e @ avb::Error::HashTree(_)) if repair => {
warning!("Failed to verify hash tree descriptor{suffix}: {e}");
warning!("Attempting to repair using FEC data{suffix}");
warn!("Failed to verify hash tree descriptor{suffix}: {e}");
warn!("Attempting to repair using FEC data{suffix}");
d.repair(&file, &file, cancel_signal)
.with_context(|| format!("Failed to repair data{suffix}"))?;
d.verify(&file, cancel_signal).map(|_| {
status!("Successfully repaired data{suffix}");
info!("Successfully repaired data{suffix}");
})
}
ret => ret,
@@ -528,7 +529,7 @@ fn verify_and_repair(
.with_context(|| format!("Failed to verify hash tree descriptor{suffix}"))?;
}
AppendedDescriptorRef::Hash(d) => {
status!("Verifying hash descriptor{suffix}");
info!("Verifying hash descriptor{suffix}");
file.rewind()?;
d.verify(file, cancel_signal)
@@ -547,9 +548,13 @@ pub fn verify_descriptors(
repair: bool,
cancel_signal: &AtomicBool,
) -> Result<()> {
let parent_span = Span::current();
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))
@@ -560,7 +565,7 @@ pub fn verify_descriptors(
// refer to partitions that exist on the device, but not in the
// OTA.
Err(e) if e.kind() == io::ErrorKind::NotFound => {
warning!("Partition image does not exist: {path:?}");
warn!("Partition image does not exist: {path:?}");
return Ok(());
}
Err(e) => {
@@ -706,7 +711,7 @@ fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<()>
)?;
verify_descriptors(&directory, &descriptors, cli.repair, cancel_signal)?;
status!("Successfully verified all vbmeta signatures and hashes");
info!("Successfully verified all vbmeta signatures and hashes");
Ok(())
}
-15
View File
@@ -12,18 +12,3 @@ pub mod fec;
pub mod hashtree;
pub mod key;
pub mod ota;
macro_rules! status {
($($arg:tt)*) => {
eprintln!("\x1b[1m[*] {}\x1b[0m", format!($($arg)*))
}
}
macro_rules! warning {
($($arg:tt)*) => {
eprintln!("\x1b[1;31m[WARNING] {}\x1b[0m", format!($($arg)*))
}
}
pub(crate) use status;
pub(crate) use warning;
+41 -37
View File
@@ -13,7 +13,6 @@ use std::{
ops::Range,
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Mutex},
time::Instant,
};
use anyhow::{anyhow, bail, Context, Result};
@@ -24,11 +23,12 @@ use rayon::{iter::IntoParallelRefIterator, prelude::ParallelIterator};
use rsa::RsaPrivateKey;
use tempfile::NamedTempFile;
use topological_sort::TopologicalSort;
use tracing::{debug_span, info, warn};
use x509_cert::Certificate;
use zip::{write::FileOptions, CompressionMethod, ZipArchive, ZipWriter};
use crate::{
cli::{self, status, warning},
cli,
crypto::{self, PassphraseSource},
format::{
avb::Header,
@@ -149,8 +149,10 @@ fn open_input_files(
.collect::<HashSet<_>>();
for name in all_images {
let _span = debug_span!("image", name).entered();
if let Some(path) = external_images.get(name) {
status!("Opening external image: {name}: {path:?}");
info!("Opening external image: {name}: {path:?}");
let file = File::open(path)
.map(PSeekFile::new)
@@ -163,7 +165,7 @@ fn open_input_files(
},
);
} else {
status!("Extracting from original payload: {name}");
info!("Extracting from original payload: {name}");
let file = tempfile::tempfile()
.map(PSeekFile::new)
@@ -207,7 +209,7 @@ fn patch_boot_images<'a, 'b: 'a>(
let boot_partitions = required_images.iter_boot().collect::<Vec<_>>();
status!(
info!(
"Patching boot images: {}",
joined(sorted(boot_partitions.iter())),
);
@@ -252,7 +254,9 @@ fn patch_system_image<'a, 'b: 'a>(
bail!("No system partition found");
};
status!("Patching system image: {target}");
let _span = debug_span!("image", name = target).entered();
info!("Patching system image: {target}");
let input_file = input_files.get_mut(target).unwrap();
@@ -280,7 +284,7 @@ fn patch_system_image<'a, 'b: 'a>(
input_file.state = InputFileState::Modified;
status!("Patched otacerts.zip offsets in {target}: {ranges:?}");
info!("Patched otacerts.zip offsets in {target}: {ranges:?}");
ranges.extend(other_ranges);
@@ -384,7 +388,7 @@ fn get_vbmeta_patch_order(
}
if !missing.is_empty() {
warning!("Partitions aren't protected by AVB: {:?}", joined(missing));
warn!("Partitions aren't protected by AVB: {:?}", joined(missing));
}
// Ensure that there's only a single root of trust. Otherwise, there could
@@ -635,6 +639,8 @@ fn compress_image(
ranges: Option<&[Range<u64>]>,
cancel_signal: &AtomicBool,
) -> Result<Vec<Range<usize>>> {
let _span = debug_span!("image", name).entered();
file.rewind()?;
let writer = tempfile::tempfile()
@@ -650,7 +656,7 @@ fn compress_image(
.unwrap();
if let Some(r) = ranges {
status!("Compressing partial image: {name}: {r:?}");
info!("Compressing partial image: {name}: {r:?}");
match payload::compress_modified_image(
&*file,
@@ -668,13 +674,13 @@ fn compress_image(
// If we can't take advantage of the optimization, we can still
// compress the whole image.
Err(payload::Error::ExtentsNotInOrder) => {
warning!("Cannot use optimization for {name}: extents not in order");
warn!("Cannot use optimization for {name}: extents not in order");
}
Err(e) => return Err(e.into()),
}
}
status!("Compressing full image: {name}");
info!("Compressing full image: {name}");
// Otherwise, compress the entire image.
let (partition_info, operations) =
@@ -771,7 +777,7 @@ fn patch_ota_payload(
let mut vbmeta_order = get_vbmeta_patch_order(&mut input_files, &vbmeta_headers)?;
status!(
info!(
"Patching vbmeta images: {}",
joined(vbmeta_order.iter().map(|(n, _)| n)),
);
@@ -810,7 +816,7 @@ fn patch_ota_payload(
})
.collect::<Result<HashMap<_, _>>>()?;
status!("Generating new OTA payload");
info!("Generating new OTA payload");
let mut payload_writer = PayloadWriter::new(writer, header_locked.clone(), key_ota.clone())
.context("Failed to write payload header")?;
@@ -926,6 +932,8 @@ fn patch_ota_zip(
let mut last_entry_used_zip64 = false;
for path in &paths {
let _span = debug_span!("zip", entry = path).entered();
let mut reader = zip_reader
.by_name(path)
.with_context(|| format!("Failed to open zip entry: {path}"))?;
@@ -984,13 +992,13 @@ fn patch_ota_zip(
match path.as_str() {
ota::PATH_OTACERT => {
// Use the user's certificate
status!("Replacing zip entry: {path}");
info!("Replacing zip entry: {path}");
crypto::write_pem_cert(&mut writer, cert_ota)
.with_context(|| format!("Failed to write entry: {path}"))?;
}
ota::PATH_PAYLOAD => {
status!("Patching zip entry: {path}");
info!("Patching zip entry: {path}");
if reader.compression() != CompressionMethod::Stored {
bail!("{path} is not stored uncompressed");
@@ -1022,7 +1030,7 @@ fn patch_ota_zip(
payload_metadata_size = Some(m);
}
ota::PATH_PROPERTIES => {
status!("Patching zip entry: {path}");
info!("Patching zip entry: {path}");
// payload.bin is guaranteed to be patched first.
writer
@@ -1030,7 +1038,7 @@ fn patch_ota_zip(
.with_context(|| format!("Failed to write payload properties: {path}"))?;
}
_ => {
status!("Copying zip entry: {path}");
info!("Copying zip entry: {path}");
stream::copy(&mut reader, &mut writer, cancel_signal)
.with_context(|| format!("Failed to copy zip entry: {path}"))?;
@@ -1049,7 +1057,7 @@ fn patch_ota_zip(
last_entry_used_zip64 = use_zip64;
}
status!("Generating new OTA metadata");
info!("Generating new OTA metadata");
let data_descriptor_size = if last_entry_used_zip64 { 24 } else { 16 };
let metadata = ota::add_metadata(
@@ -1080,7 +1088,7 @@ fn extract_ota_zip(
}
}
status!("Extracting from the payload: {}", joined(images));
info!("Extracting from the payload: {}", joined(images));
// Pre-open all output files.
let output_files = images
@@ -1113,6 +1121,8 @@ fn extract_ota_zip(
)
.context("Failed to extract images from payload")?;
info!("Successfully extracted OTA");
Ok(())
}
@@ -1166,7 +1176,7 @@ fn verify_partition_hashes(
pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()> {
if cli.boot_partition.is_some() {
warning!("Ignoring --boot-partition: deprecated and no longer needed");
warn!("Ignoring --boot-partition: deprecated and no longer needed");
}
let output = cli.output.as_ref().map_or_else(
@@ -1222,7 +1232,6 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
cli.magisk_preinit_device.as_deref(),
cli.magisk_random_seed,
cli.ignore_magisk_warnings,
move |s| warning!("{s}"),
)
.context("Failed to create Magisk boot image patcher")?,
);
@@ -1232,9 +1241,6 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
let patcher: Box<dyn BootImagePatch + Sync> = Box::new(PrepatchedImagePatcher::new(
prepatched,
cli.ignore_prepatched_compat + 1,
move |s| {
warning!("{s}");
},
));
Some(patcher)
@@ -1243,8 +1249,6 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
None
};
let start = Instant::now();
let raw_reader = File::open(&cli.input)
.map(PSeekFile::new)
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
@@ -1292,7 +1296,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
temp_writer.flush().context("Failed to flush output zip")?;
// We do a lot of low-level hackery. Reopen and verify offsets.
status!("Verifying metadata offsets");
info!("Verifying metadata offsets");
temp_writer.rewind().context("Failed to seek output zip")?;
ota::verify_metadata(
BufReader::new(&mut temp_writer),
@@ -1301,7 +1305,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
)
.context("Failed to verify OTA metadata offsets")?;
status!("Completed after {:.1}s", start.elapsed().as_secs_f64());
info!("Successfully patched OTA");
// NamedTempFile forces 600 permissions on temp files because it's the safe
// option for a shared /tmp. Since we're writing to the output file's
@@ -1334,7 +1338,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Result<()> {
if cli.boot_partition.is_some() {
warning!("Ignoring --boot-partition: deprecated and no longer needed");
warn!("Ignoring --boot-partition: deprecated and no longer needed");
}
let raw_reader = File::open(&cli.input)
@@ -1408,7 +1412,7 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
.with_context(|| format!("Failed to open for reading: {:?}", cli.input))?;
let mut reader = BufReader::new(raw_reader);
status!("Verifying whole-file signature");
info!("Verifying whole-file signature");
let embedded_cert = ota::verify_ota(&mut reader, cancel_signal)?;
@@ -1426,13 +1430,13 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
bail!("OTA has a valid signature, but was not signed with: {p:?}");
}
} else {
warning!("Whole-file signature is valid, but its trust is unknown");
warn!("Whole-file signature is valid, but its trust is unknown");
}
ota::verify_metadata(&mut reader, &metadata, header.blob_offset)
.context("Failed to verify OTA metadata offsets")?;
status!("Verifying payload");
info!("Verifying payload");
let pfs_raw = metadata
.property_files
@@ -1450,7 +1454,7 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
payload::verify_payload(section_reader, &ota_cert, &properties, cancel_signal)?;
status!("Extracting partition images to temporary directory");
info!("Extracting partition images to temporary directory");
let authority = ambient_authority();
let temp_dir = TempDir::new(authority).context("Failed to create temporary directory")?;
@@ -1473,11 +1477,11 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
cancel_signal,
)?;
status!("Verifying partition hashes");
info!("Verifying partition hashes");
verify_partition_hashes(&temp_dir, &header, &unique_images, cancel_signal)?;
status!("Checking ramdisk's otacerts.zip");
info!("Checking ramdisk's otacerts.zip");
{
let required_images = RequiredImages::new(&header.manifest);
@@ -1509,7 +1513,7 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
}
}
status!("Verifying AVB signatures");
info!("Verifying AVB signatures");
let public_key = if let Some(p) = &cli.public_key_avb {
let data = fs::read(p).with_context(|| format!("Failed to read file: {p:?}"))?;
@@ -1533,7 +1537,7 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
)?;
cli::avb::verify_descriptors(&temp_dir, &descriptors, false, cancel_signal)?;
status!("Signatures are all valid!");
info!("Signatures are all valid!");
Ok(())
}
+5 -5
View File
@@ -7,14 +7,14 @@ use std::io::{self, Read, Seek, Write};
use byteorder::{LittleEndian, WriteBytesExt};
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use lz4_flex::frame::FrameDecoder;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use xz2::{
use liblzma::{
read::XzDecoder,
stream::{Check, Stream},
write::XzEncoder,
};
use lz4_flex::frame::FrameDecoder;
use serde::{Deserialize, Serialize};
use thiserror::Error;
static GZIP_MAGIC: &[u8; 2] = b"\x1f\x8b";
static LZ4_LEGACY_MAGIC: &[u8; 4] = b"\x02\x21\x4c\x18";
@@ -25,7 +25,7 @@ pub enum Error {
#[error("Unknown compression format")]
UnknownFormat,
#[error("XZ stream error")]
XzStream(#[from] xz2::stream::Error),
XzStream(#[from] liblzma::stream::Error),
#[error("I/O error")]
Io(#[from] io::Error),
}
+2 -2
View File
@@ -807,12 +807,12 @@ mod tests {
let size = 2 * 253 * 4096;
let fec = Fec::new(size, 4096, 2).unwrap();
assert_eq!(fec.rounds_for_ranges(&[0..0]).unwrap(), HashSet::new(),);
assert_eq!(fec.rounds_for_ranges(&[0..0]).unwrap(), HashSet::new());
assert_eq!(
fec.rounds_for_ranges(&[0..size]).unwrap(),
HashSet::from([0, 1]),
);
assert_eq!(fec.rounds_for_ranges(&[0..1]).unwrap(), HashSet::from([0]),);
assert_eq!(fec.rounds_for_ranges(&[0..1]).unwrap(), HashSet::from([0]));
assert_eq!(
fec.rounds_for_ranges(&[4095..4096]).unwrap(),
HashSet::from([0]),
+4 -4
View File
@@ -326,7 +326,7 @@ impl<'a> HashTree<'a> {
cancel_signal: &AtomicBool,
) -> Result<(Vec<u8>, Vec<u8>)> {
let offsets = self.compute_level_offsets(image_size)?;
let hash_tree_size = offsets.get(0).map(|r| r.end).unwrap_or(0);
let hash_tree_size = offsets.first().map(|r| r.end).unwrap_or(0);
let mut hash_tree_data = vec![0u8; hash_tree_size];
let root_digest = self.calculate(
@@ -352,7 +352,7 @@ impl<'a> HashTree<'a> {
cancel_signal: &AtomicBool,
) -> Result<Vec<u8>> {
let offsets = self.compute_level_offsets(image_size)?;
let hash_tree_size = offsets.get(0).map(|r| r.end).unwrap_or(0);
let hash_tree_size = offsets.first().map(|r| r.end).unwrap_or(0);
if hash_tree_data.len() != hash_tree_size {
return Err(Error::InvalidHashTreeSize {
input: image_size,
@@ -381,7 +381,7 @@ impl<'a> HashTree<'a> {
cancel_signal: &AtomicBool,
) -> Result<()> {
let offsets = self.compute_level_offsets(image_size)?;
let hash_tree_size = offsets.get(0).map(|r| r.end).unwrap_or(0);
let hash_tree_size = offsets.first().map(|r| r.end).unwrap_or(0);
if hash_tree_data.len() != hash_tree_size {
return Err(Error::InvalidHashTreeSize {
input: image_size,
@@ -637,7 +637,7 @@ mod tests {
hash_tree.blocks_for_ranges(16384, &[0..16384]).unwrap(),
&[0..4],
);
assert_eq!(hash_tree.blocks_for_ranges(16384, &[0..0]).unwrap(), &[],);
assert_eq!(hash_tree.blocks_for_ranges(16384, &[0..0]).unwrap(), &[]);
assert_eq!(
hash_tree
.blocks_for_ranges(16384, &[12287..12289, 0..1, 5000..5001])
+6 -6
View File
@@ -14,6 +14,11 @@ use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use byteorder::{BigEndian, ReadBytesExt};
use bzip2::write::BzDecoder;
use liblzma::{
stream::{Check, Stream},
write::XzDecoder,
write::XzEncoder,
};
use num_traits::ToPrimitive;
use prost::Message;
use rayon::{
@@ -25,11 +30,6 @@ use rsa::{traits::PublicKeyParts, Pkcs1v15Sign, RsaPrivateKey};
use sha2::Sha256;
use thiserror::Error;
use x509_cert::Certificate;
use xz2::{
stream::{Check, Stream},
write::XzDecoder,
write::XzEncoder,
};
use crate::{
crypto,
@@ -99,7 +99,7 @@ pub enum Error {
#[error("Failed to decode protobuf message")]
ProtobufDecode(#[from] prost::DecodeError),
#[error("XZ stream error")]
XzStream(#[from] xz2::stream::Error),
XzStream(#[from] liblzma::stream::Error),
#[error("RSA error")]
Rsa(#[from] rsa::Error),
#[error("I/O error")]
+15 -3
View File
@@ -8,9 +8,11 @@ use std::sync::{
Arc,
};
use anyhow::Result;
use tracing::error;
fn main() -> Result<()> {
static LOGGING_INITIALIZED: AtomicBool = AtomicBool::new(false);
fn main() {
// Set up a cancel signal so we can properly clean up any temporary files.
let cancel_signal = Arc::new(AtomicBool::new(false));
{
@@ -22,5 +24,15 @@ fn main() -> Result<()> {
.expect("Failed to set signal handler");
}
avbroot::cli::args::main(&cancel_signal)
match avbroot::cli::args::main(&LOGGING_INITIALIZED, &cancel_signal) {
Ok(_) => {}
Err(e) => {
if LOGGING_INITIALIZED.load(Ordering::SeqCst) {
error!("{e:?}");
} else {
eprintln!("{e:?}");
}
std::process::exit(1);
}
}
}
+134 -40
View File
@@ -16,16 +16,17 @@ use std::{
};
use bstr::ByteSlice;
use liblzma::{
stream::{Check, Stream},
write::XzEncoder,
};
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use regex::bytes::Regex;
use ring::digest::Context;
use rsa::RsaPrivateKey;
use thiserror::Error;
use tracing::{debug, debug_span, trace, warn, Span};
use x509_cert::Certificate;
use xz2::{
stream::{Check, Stream},
write::XzEncoder,
};
use zip::{result::ZipError, ZipArchive};
use crate::{
@@ -67,7 +68,7 @@ pub enum Error {
#[error("OTA certificate error")]
OtaCert(#[from] otacert::Error),
#[error("XZ stream error")]
XzStream(#[from] xz2::stream::Error),
XzStream(#[from] liblzma::stream::Error),
#[error("Zip error")]
Zip(#[from] ZipError),
#[error("I/O error")]
@@ -86,6 +87,12 @@ fn load_ramdisk(
let mut reader = CompressedReader::new(raw_reader, false)?;
let entries = cpio::load(&mut reader, false, cancel_signal)?;
trace!(
"Loaded {:?} ramdisk with {} entries",
reader.format(),
entries.len(),
);
Ok((entries, reader.format()))
}
@@ -98,6 +105,8 @@ fn save_ramdisk(
let mut writer = CompressedWriter::new(raw_writer, format)?;
cpio::save(&mut writer, entries, false, cancel_signal)?;
trace!("Wrote {format:?} ramdisk with {} entries", entries.len());
let raw_writer = writer.finish()?;
Ok(raw_writer.into_inner())
}
@@ -140,20 +149,24 @@ impl MagiskRootPatcher {
// RULESDEVICE config option, which stored the writable block device as an
// rdev major/minor pair, which was not consistent across reboots and was
// replaced by PREINITDEVICE
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..26500];
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..27100];
const VER_PREINIT_DEVICE: Range<u32> =
25211..Self::VERS_SUPPORTED[Self::VERS_SUPPORTED.len() - 1].end;
const VER_RANDOM_SEED: Range<u32> = 25211..26103;
const VER_PATCH_VBMETA: Range<u32> = Self::VERS_SUPPORTED[0].start..26202;
const VER_XZ_BACKUP: Range<u32> =
26403..Self::VERS_SUPPORTED[Self::VERS_SUPPORTED.len() - 1].end;
pub fn new(
path: &Path,
preinit_device: Option<&str>,
random_seed: Option<u64>,
ignore_compatibility: bool,
warning_fn: impl Fn(&str) + Send + 'static,
) -> Result<Self> {
let version = Self::get_version(path)?;
debug!("Found Magisk version: {version}");
if !Self::VERS_SUPPORTED.iter().any(|v| v.contains(&version)) {
let msg = format!(
"Unsupported Magisk version {} (supported: {:?})",
@@ -162,7 +175,7 @@ impl MagiskRootPatcher {
);
if ignore_compatibility {
warning_fn(&msg);
warn!("{msg}");
} else {
return Err(Error::Validation(msg));
}
@@ -176,7 +189,7 @@ impl MagiskRootPatcher {
);
if ignore_compatibility {
warning_fn(&msg);
warn!("{msg}");
} else {
return Err(Error::Validation(msg));
}
@@ -208,6 +221,8 @@ impl MagiskRootPatcher {
}
if let Some(suffix) = line.trim_end().strip_prefix("MAGISK_VER_CODE=") {
trace!("Magisk version code line: {line:?}");
let version = suffix
.parse()
.map_err(|e| Error::ParseMagiskVersion(suffix.to_owned(), e))?;
@@ -216,6 +231,18 @@ impl MagiskRootPatcher {
}
}
fn xz_compress(reader: impl Read, cancel_signal: &AtomicBool) -> Result<Vec<u8>> {
let stream = Stream::new_easy_encoder(9, Check::Crc32)?;
let raw_writer = Cursor::new(Vec::new());
let mut writer = XzEncoder::new_stream(raw_writer, stream);
stream::copy(reader, &mut writer, cancel_signal)?;
let raw_writer = writer.finish()?;
Ok(raw_writer.into_inner())
}
/// Compare old and new ramdisk entry lists, creating the Magisk `.backup/`
/// directory structure. `.backup/.rmlist` will contain a sorted list of
/// NULL-terminated strings, listing which files were newly added or
@@ -223,7 +250,12 @@ impl MagiskRootPatcher {
/// entries as `.backup/<path>`.
///
/// Both lists and entries within the lists may be mutated.
fn apply_magisk_backup(old_entries: &mut [CpioEntry], new_entries: &mut Vec<CpioEntry>) {
fn apply_magisk_backup(
old_entries: &mut [CpioEntry],
new_entries: &mut Vec<CpioEntry>,
xz_compress: bool,
cancel_signal: &AtomicBool,
) -> Result<()> {
cpio::sort(old_entries);
cpio::sort(new_entries);
@@ -268,11 +300,54 @@ impl MagiskRootPatcher {
new_entries.push(CpioEntry::new_directory(b".backup", 0));
debug!(
"Removed entries: {:?}",
rm_list
.split(|b| *b == 0)
.filter(|e| !e.is_empty())
.map(|p| p.as_bstr().to_string())
.collect::<Vec<_>>(),
);
debug!(
"Added/changed entries: {:?}",
to_back_up
.iter()
.map(|e| e.path.as_bstr().to_string())
.collect::<Vec<_>>(),
);
for old_entry in to_back_up {
let mut new_entry = old_entry.clone();
new_entry.path = b".backup/".to_vec();
new_entry.path.extend(&old_entry.path);
new_entries.push(new_entry);
let mut new_path = b".backup/".to_vec();
new_path.extend(&old_entry.path);
let mut new_data = None;
if xz_compress {
if let CpioEntryData::Data(data) = &old_entry.data {
new_path.extend(b".xz");
let reader = Cursor::new(data);
let buf = Self::xz_compress(reader, cancel_signal)?;
new_data = Some(CpioEntryData::Data(buf));
}
}
new_entries.push(CpioEntry {
path: new_path,
data: new_data.unwrap_or_else(|| old_entry.data.clone()),
inode: old_entry.inode,
file_type: old_entry.file_type,
file_mode: old_entry.file_mode,
uid: old_entry.uid,
gid: old_entry.gid,
nlink: old_entry.nlink,
mtime: old_entry.mtime,
dev_maj: old_entry.dev_maj,
dev_min: old_entry.dev_min,
rdev_maj: old_entry.rdev_maj,
rdev_min: old_entry.rdev_min,
crc32: old_entry.crc32,
});
}
new_entries.push(CpioEntry::new_file(
@@ -280,6 +355,8 @@ impl MagiskRootPatcher {
0,
CpioEntryData::Data(rm_list),
));
Ok(())
}
}
@@ -347,7 +424,9 @@ impl BootImagePatch for MagiskRootPatcher {
));
}
// Add xz-compressed magisk32 and magisk64.
// Add xz-compressed magisk32 and magisk64. We currently unconditionally
// include magisk32 because the boot image itself doesn't contain
// sufficient information to determine if a device is 64-bit only.
let mut xz_files = HashMap::<&str, &[u8]>::new();
xz_files.insert(
"lib/armeabi-v7a/libmagisk32.so",
@@ -361,34 +440,34 @@ impl BootImagePatch for MagiskRootPatcher {
// Add stub apk, which only exists after Magisk commit
// ad0e6511e11ebec65aa9b5b916e1397342850319.
if zip.file_names().any(|n| n == "assets/stub.apk") {
debug!("Magisk stub found");
xz_files.insert("assets/stub.apk", b"overlay.d/sbin/stub.xz");
}
for (source, target) in xz_files {
let reader = zip.by_name(source)?;
let raw_writer = Cursor::new(vec![]);
let stream = Stream::new_easy_encoder(9, Check::Crc32)?;
let mut writer = XzEncoder::new_stream(raw_writer, stream);
let buf = Self::xz_compress(reader, cancel_signal)?;
stream::copy(reader, &mut writer, cancel_signal)?;
let raw_writer = writer.finish()?;
entries.push(CpioEntry::new_file(
target,
0o644,
CpioEntryData::Data(raw_writer.into_inner()),
));
entries.push(CpioEntry::new_file(target, 0o644, CpioEntryData::Data(buf)));
}
// Create Magisk .backup directory structure.
Self::apply_magisk_backup(&mut old_entries, &mut entries);
Self::apply_magisk_backup(
&mut old_entries,
&mut entries,
Self::VER_XZ_BACKUP.contains(&self.version),
cancel_signal,
)?;
// Create Magisk config.
let mut magisk_config = String::new();
magisk_config.push_str("KEEPVERITY=true\n");
magisk_config.push_str("KEEPFORCEENCRYPT=true\n");
magisk_config.push_str("PATCHVBMETAFLAG=false\n");
if Self::VER_PATCH_VBMETA.contains(&self.version) {
magisk_config.push_str("PATCHVBMETAFLAG=false\n");
}
magisk_config.push_str("RECOVERYMODE=false\n");
if Self::VER_PREINIT_DEVICE.contains(&self.version) {
@@ -408,6 +487,8 @@ impl BootImagePatch for MagiskRootPatcher {
magisk_config.push_str(&format!("RANDOMSEED={:#x}\n", self.random_seed));
}
trace!("Magisk config: {magisk_config:?}");
entries.push(CpioEntry::new_file(
b".backup/.magisk",
0,
@@ -488,6 +569,7 @@ impl OtaCertPatcher {
for index in 0..zip.len() {
let zip_entry = zip.by_index(index)?;
if !zip_entry.name().ends_with(".x509.pem") {
debug!("Skipping invalid entry path: {}", zip_entry.name());
continue;
}
@@ -564,6 +646,7 @@ impl BootImagePatch for OtaCertPatcher {
};
let new_zip = otacert::create_zip(&self.cert, OtaCertBuildFlags::empty())?;
trace!("Generated new {} byte otacerts.zip", new_zip.len());
for ramdisk in ramdisks {
if ramdisk.is_empty() {
@@ -593,7 +676,6 @@ impl BootImagePatch for OtaCertPatcher {
pub struct PrepatchedImagePatcher {
prepatched: PathBuf,
fatal_level: u8,
warning_fn: Box<dyn Fn(&str) + Send + Sync>,
}
impl PrepatchedImagePatcher {
@@ -604,15 +686,10 @@ impl PrepatchedImagePatcher {
const VERSION_REGEX: &'static str =
r"Linux version ([0-9]+\.[0-9]+).[0-9]+-(android[0-9]+)-([0-9]+)-";
pub fn new(
prepatched: &Path,
fatal_level: u8,
warning_fn: impl Fn(&str) + Send + Sync + 'static,
) -> Self {
pub fn new(prepatched: &Path, fatal_level: u8) -> Self {
Self {
prepatched: prepatched.to_owned(),
fatal_level,
warning_fn: Box::new(warning_fn),
}
}
@@ -829,7 +906,7 @@ impl BootImagePatch for PrepatchedImagePatcher {
msg.push_str(warning);
}
(self.warning_fn)(&msg);
warn!("{msg}");
}
if !errors.is_empty() {
@@ -853,9 +930,12 @@ pub fn load_boot_images<'a>(
names: &[&'a str],
open_input: impl Fn(&str) -> io::Result<Box<dyn ReadSeek>> + Sync,
) -> Result<HashMap<&'a str, BootImageInfo>> {
let parent_span = Span::current();
names
.par_iter()
.map(|name| {
let _span = debug_span!(parent: &parent_span, "image", name).entered();
let mut reader = open_input(name)?;
let (header, footer, image_size) = avb::load_image(&mut reader)?;
@@ -873,6 +953,8 @@ pub fn load_boot_images<'a>(
boot_image,
};
trace!("Loaded {image_size} byte boot image: {name}");
Ok((*name, info))
})
.collect()
@@ -891,6 +973,8 @@ pub fn patch_boot_images<'a>(
patchers: &[Box<dyn BootImagePatch + Sync>],
cancel_signal: &AtomicBool,
) -> Result<HashSet<&'a str>> {
let parent_span = Span::current();
// Preparse all images. Some patchers need to inspect every candidate.
let mut images = load_boot_images(names, open_input)?;
@@ -898,16 +982,21 @@ pub fn patch_boot_images<'a>(
let all_targets = patchers
.par_iter()
.map(|p| {
let _span =
debug_span!(parent: &parent_span, "patcher", name = p.patcher_name()).entered();
p.find_targets(&images, cancel_signal).and_then(|targets| {
if targets.is_empty() {
Err(Error::NoTargets(p.patcher_name()))
} else {
debug!("Found patcher targets: {targets:?}");
Ok(targets)
}
})
})
.collect::<Result<Vec<_>>>()?;
debug!("All patcher targets: {all_targets:?}");
// Regroup data so we can parallelize by target.
let mut groups = HashMap::<&str, (BootImageInfo, Vec<&Box<dyn BootImagePatch + Sync>>)>::new();
for (patcher, targets) in patchers.iter().zip(all_targets.into_iter()) {
@@ -927,15 +1016,19 @@ pub fn patch_boot_images<'a>(
groups
.par_iter_mut()
.try_for_each(|(_, (info, patchers))| -> Result<()> {
patchers
.iter()
.try_for_each(|p| p.patch(&mut info.boot_image, cancel_signal))
patchers.iter().try_for_each(|p| {
let _span =
debug_span!(parent: &parent_span, "patcher", name = p.patcher_name()).entered();
p.patch(&mut info.boot_image, cancel_signal)
})
})?;
// Resign and write new images.
groups
.par_iter_mut()
.map(|(name, (info, _))| {
let _span = debug_span!(parent: &parent_span, "image", name).entered();
let AppendedDescriptorMut::Hash(descriptor) = info.header.appended_descriptor_mut()?
else {
return Err(Error::NoHashDescriptor);
@@ -955,6 +1048,7 @@ pub fn patch_boot_images<'a>(
descriptor.root_digest = context.finish().as_ref().to_vec();
if !info.header.public_key.is_empty() {
debug!("Signing boot image");
info.header.set_algo_for_key(key)?;
info.header.sign(key)?;
}
+5
View File
@@ -7,6 +7,7 @@ use std::{borrow::Cow, cmp::Ordering, io::Cursor};
use bitflags::bitflags;
use thiserror::Error;
use tracing::trace;
use x509_cert::{der::asn1::BitString, Certificate};
use zip::{result::ZipError, write::FileOptions, CompressionMethod, ZipWriter};
@@ -134,8 +135,12 @@ pub fn create_zip_with_size(cert: &Certificate, size: usize) -> Result<Vec<u8>>
] {
flags |= additional_flag;
trace!("Attempting to create {size} byte otacerts.zip: {flags:?}");
let mut data = create_zip(cert, flags)?;
if data.len() <= size {
trace!("Padding {} byte otacerts.zip to {size}", data.len());
pad_zip(&mut data, size)?;
return Ok(data);
}
+24 -1
View File
@@ -13,6 +13,7 @@ use memchr::memmem;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use rsa::RsaPrivateKey;
use thiserror::Error;
use tracing::{debug, debug_span, trace, Span};
use x509_cert::Certificate;
use zip::ZipArchive;
@@ -50,6 +51,7 @@ type Result<T> = std::result::Result<T, Error>;
fn find_zip_bounds(data: &[u8], eocd_offset: usize) -> Option<Range<usize>> {
let eocd = &data[eocd_offset..];
if eocd.len() < 22 {
trace!("Buffer is too small to contain EOCD");
return None;
}
@@ -60,14 +62,18 @@ fn find_zip_bounds(data: &[u8], eocd_offset: usize) -> Option<Range<usize>> {
let start = eocd_offset.checked_sub(cd_size)?.checked_sub(cd_offset)?;
let end = eocd_offset.checked_add(22)?.checked_add(comment_size)?;
if end > data.len() {
trace!("End of zip is out of bounds");
return None;
}
trace!("Found zip bounds: {:?}", start..end);
let reader = SectionReader::new(Cursor::new(data), start as u64, (end - start) as u64).ok()?;
let mut zip_reader = ZipArchive::new(reader).ok()?;
if zip_reader.is_empty() {
// otacerts.zip files contain at least one cert.
trace!("Zip is empty");
return None;
}
@@ -76,10 +82,13 @@ fn find_zip_bounds(data: &[u8], eocd_offset: usize) -> Option<Range<usize>> {
if !entry.name().ends_with(".x509.pem") {
// otacerts.zip files only contain files named this way.
trace!("Excluded due to invalid name: {:?}", entry.name());
return None;
}
}
debug!("Found otacerts.zip candidate");
// There's one or more entries and every one is named *.x509.pem.
Some(start..end)
}
@@ -110,6 +119,8 @@ pub fn patch_system_image(
// chunk boundaries.
const CHUNK_SIZE: u64 = 2 * 1024 * 1024;
let parent_span = Span::current();
let (mut header, footer, image_size) = avb::load_image(input.reopen_boxed()?)?;
let Some(mut footer) = footer else {
return Err(Error::NoFooter);
@@ -119,6 +130,7 @@ pub fn patch_system_image(
};
let num_chunks = util::div_ceil(footer.original_image_size, CHUNK_SIZE);
trace!("Parallel heuristics search for otacerts.zip with {num_chunks} chunks");
let modified_ranges = (0..num_chunks)
.into_par_iter()
@@ -137,6 +149,9 @@ pub fn patch_system_image(
let mut ranges = Vec::<Range<u64>>::new();
for eocd_offset_rel in memmem::find_iter(&buf, ota::ZIP_EOCD_MAGIC) {
let _span = debug_span!(parent: &parent_span, "otacerts", offset, eocd_offset_rel)
.entered();
let Some(bounds_rel) = find_zip_bounds(&buf, eocd_offset_rel) else {
continue;
};
@@ -168,7 +183,14 @@ pub fn patch_system_image(
let update_ranges = if descriptor.hash_algorithm == "sha1" {
// Promote to a secure algorithm. SHA1 is allowed for verification only.
// The entire hash tree and FEC data will need to be recomputed.
descriptor.hash_algorithm = "sha256".to_owned();
let new_algorithm = "sha256".to_owned();
debug!(
"Changing insecure hash algorithm {} to {new_algorithm}",
descriptor.hash_algorithm,
);
descriptor.hash_algorithm = new_algorithm;
None
} else {
// Only need to update the hash tree and FEC data corresponding to the
@@ -179,6 +201,7 @@ pub fn patch_system_image(
descriptor.update(input, output, update_ranges, cancel_signal)?;
if !header.public_key.is_empty() {
debug!("Signing system image");
header.set_algo_for_key(key)?;
header.sign(key)?;
}
+2 -1
View File
@@ -65,13 +65,14 @@ include-workspace = true
bypass = [
# Copies of unmodified crashwrangler objects for old macOS versions.
{ name = "honggfuzz", allow-globs = ["honggfuzz/third_party/mac/CrashReport_*.o"] },
# Test files for liblzma's test suite
{ name = "liblzma-sys", allow-globs = ["xz/tests/compress_prepared_bcj_*"] },
]
[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-git = [
"https://github.com/chenxiaolong/xz2-rs",
"https://github.com/chenxiaolong/zip",
"https://github.com/jongiddy/bzip2-rs",
]
+2
View File
@@ -20,6 +20,8 @@ serde = { version = "1.0.188", features = ["derive"] }
tempfile = "3.8.0"
toml_edit = { version = "0.21.0", features = ["serde"] }
topological-sort = "0.2.2"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
x509-cert = "0.2.5"
# https://github.com/zip-rs/zip/pull/383
+4 -4
View File
@@ -47,7 +47,7 @@ data.ramdisks = ["otacerts"]
[profile.pixel_v4_gki.hashes]
original = "f9477a35e3b60a495e49431c61e3897f11775f453a6a9897ead568357c963618"
patched = "eb045799a514300727357a5ec471f9b04b276991daf4fd72f17f840b2a7dd1b8"
patched = "d3436650b4e0c60688dafb1472fa5fe95e67d19a8fad2da4850ffaa739d44574"
# Google Pixel 6a
# What's unique: boot (boot v4, no ramdisk) + vendor_boot (vendor v4, 2 ramdisks)
@@ -81,7 +81,7 @@ data.ramdisks = ["init_and_otacerts", "dlkm"]
[profile.pixel_v4_non_gki.hashes]
original = "021b4510bc244f5f686fbff89eb2058ec9c96a2949c2fe8caa7750a78d593225"
patched = "5d1a36e2eb18d9d905ab397d4eaf2d7e7c94da6e7573afe3d4e4367841171fe3"
patched = "0f90ae4c26a54a735d48e13e98bea73b6d1c026b0e95fa5b4b26179a1d6bdc86"
# Google Pixel 4a 5G
# What's unique: boot (boot v3) + vendor_boot (vendor v3)
@@ -116,7 +116,7 @@ data.ramdisks = ["otacerts"]
[profile.pixel_v3.hashes]
original = "12221a69ff32e137d5f19b61f576fc6b33f0973c4a81da7722c640554ff4bc4e"
patched = "e2049c6eba6990fc5ce30130af470c134a5ccc42947dcdd398f737fbca7ae44a"
patched = "0a92969bbd7cb30071a0799eb20028546d1cec3e6ec1ca4d7e1fe7776f1399fc"
# Google Pixel 4a
# What's unique: boot (boot v2)
@@ -145,4 +145,4 @@ data.deps = ["system"]
[profile.pixel_v2.hashes]
original = "8b38d2d999b5b6e240e894f669e9e2643b3764c108d53bb7b02447da725e7c18"
patched = "16f56e3d02c08bb646d8d0694ce77a6edb02a613bbcff14b10ead4a448c3dc00"
patched = "85b411947145e89cdc7f71b7109a12586f4dbddce3021f0f96172fc465c189d6"
+9
View File
@@ -5,6 +5,7 @@
use std::path::PathBuf;
use avbroot::cli::args::{LogFormat, LogLevel};
use clap::{Args, Parser, Subcommand};
#[derive(Debug, Args)]
@@ -66,4 +67,12 @@ pub enum Command {
pub struct Cli {
#[command(subcommand)]
pub command: Command,
/// Lowest log message severity to output.
#[arg(long, global = true, value_name = "LEVEL", default_value_t)]
pub log_level: LogLevel,
/// Output format for log messages.
#[arg(long, global = true, value_name = "FORMAT", default_value_t = LogFormat::Medium)]
pub log_format: LogFormat,
}
+12 -6
View File
@@ -51,6 +51,7 @@ use clap::Parser;
use rsa::RsaPrivateKey;
use tempfile::{NamedTempFile, TempDir};
use topological_sort::TopologicalSort;
use tracing::{info, info_span};
use x509_cert::Certificate;
use zip::{write::FileOptions, CompressionMethod, ZipWriter};
@@ -63,7 +64,7 @@ use crate::{
};
fn hash_file(path: &Path, cancel_signal: &AtomicBool) -> Result<[u8; 32]> {
println!("Calculating hash of {path:?}");
info!("Calculating hash: {path:?}");
let raw_reader =
File::open(path).with_context(|| format!("Failed to open for reading: {path:?}"))?;
@@ -817,11 +818,12 @@ fn create_fake_magisk(output: &Path) -> Result<()> {
"lib/x86_64/libmagiskinit.so",
] {
zip_writer.start_file(path, FileOptions::default())?;
write!(zip_writer, "dummy contents for {path}")?;
}
// avbroot looks for the version number in this file.
zip_writer.start_file("assets/util_functions.sh", FileOptions::default())?;
zip_writer.write_all(b"MAGISK_VER_CODE=26400\n")?;
zip_writer.write_all(b"MAGISK_VER_CODE=27000\n")?;
Ok(())
}
@@ -947,7 +949,7 @@ fn patch_image(
keys: &KeySet,
cancel_signal: &AtomicBool,
) -> Result<()> {
println!("Patching {input_file:?}");
info!("Patching OTA: {input_file:?} -> {output_file:?}");
// We're intentionally using the CLI interface.
let mut args: Vec<&OsStr> = vec![
@@ -976,7 +978,7 @@ fn patch_image(
}
fn extract_image(input_file: &Path, output_dir: &Path, cancel_signal: &AtomicBool) -> Result<()> {
println!("Extracting AVB partitions from {input_file:?}");
info!("Extracting AVB partitions: {input_file:?} -> {output_dir:?}");
let cli = ExtractCli::try_parse_from([
OsStr::new("extract"),
@@ -991,7 +993,7 @@ fn extract_image(input_file: &Path, output_dir: &Path, cancel_signal: &AtomicBoo
}
fn verify_image(input_file: &Path, keys: &KeySet, cancel_signal: &AtomicBool) -> Result<()> {
println!("Verifying signatures in {input_file:?}");
info!("Verifying signatures: {input_file:?}");
let cli = VerifyCli::try_parse_from([
OsStr::new("verify"),
@@ -1064,11 +1066,13 @@ fn test_subcommand(cli: &TestCli, cancel_signal: &AtomicBool) -> Result<()> {
];
for name in profiles {
let _span = info_span!("profile", name).entered();
if Path::new(name).file_name() != Some(OsStr::new(name)) {
bail!("Unsafe profile name: {name}");
}
println!("Generating OTA from profile: {name}");
info!("Generating OTA");
let profile = &config.profile[name];
@@ -1169,6 +1173,8 @@ fn main() -> Result<()> {
let cli = Cli::parse();
avbroot::cli::args::init_logging(cli.log_level, cli.log_format);
match cli.command {
Command::Test(c) => test_subcommand(&c, &cancel_signal),
Command::List(c) => list_subcommand(&c),
-33
View File
@@ -1,33 +0,0 @@
#!/sbin/sh
#################
# Initialization
#################
umask 022
# echo before loading util_functions
ui_print() { echo "$1"; }
require_new_magisk() {
ui_print "*******************************"
ui_print " Please install Magisk v20.4+! "
ui_print "*******************************"
exit 1
}
#########################
# Load util_functions.sh
#########################
OUTFD=$2
ZIPFILE=$3
mount /data 2>/dev/null
[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk
. /data/adb/magisk/util_functions.sh
[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk
install_module
exit 0
-1
View File
@@ -1 +0,0 @@
#MAGISK
-83
View File
@@ -1,83 +0,0 @@
import android.annotation.SuppressLint;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Process;
import android.system.ErrnoException;
import java.lang.reflect.Method;
@SuppressLint({"DiscouragedPrivateApi", "PrivateApi", "SoonBlockedPrivateApi"})
public class Main {
private static final int GET_SERVICE_ATTEMPTS = 30;
@SuppressWarnings("SameParameterValue")
private static IInterface getService(Class<?> interfaceClass, String serviceName) throws Exception {
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getDeclaredMethod("getService", String.class);
Class<?> stub = Class.forName(interfaceClass.getCanonicalName() + "$Stub");
Method asInterface = stub.getDeclaredMethod("asInterface", IBinder.class);
// ServiceManager.waitForService() tries to start the service, which we want to avoid to be
// 100% sure we're not disrupting the boot flow.
for (int attempt = 1; attempt <= GET_SERVICE_ATTEMPTS; ++attempt) {
IBinder iBinder = (IBinder) getService.invoke(null, serviceName);
if (iBinder != null) {
return (IInterface) asInterface.invoke(null, iBinder);
}
if (attempt < GET_SERVICE_ATTEMPTS) {
Thread.sleep(1000);
}
}
throw new IllegalStateException(
"Service " + serviceName + " not found after " + GET_SERVICE_ATTEMPTS + " attempts");
}
@SuppressWarnings("ConstantConditions")
private static void unlock() throws Exception {
Class<?> iOemLockService = Class.forName("android.service.oemlock.IOemLockService");
IInterface iFace = getService(iOemLockService, "oem_lock");
Method setOemUnlockAllowedByUser = iOemLockService.getDeclaredMethod("setOemUnlockAllowedByUser", boolean.class);
Method isOemUnlockAllowedByUser = iOemLockService.getDeclaredMethod("isOemUnlockAllowedByUser");
Boolean unlockAllowed = (Boolean) isOemUnlockAllowedByUser.invoke(iFace);
if (unlockAllowed) {
System.out.println("OEM unlocking already enabled");
return;
}
System.out.println("Enabling OEM unlocking");
setOemUnlockAllowedByUser.invoke(iFace, true);
}
@SuppressWarnings({"ConstantConditions", "JavaReflectionMemberAccess"})
private static void switchToSystemUid() throws Exception {
if (Process.myUid() != Process.SYSTEM_UID) {
Method setUid = Process.class.getDeclaredMethod("setUid", int.class);
int errno = (int) setUid.invoke(null, Process.SYSTEM_UID);
if (errno != 0) {
throw new Exception("Failed to switch to SYSTEM (" + Process.SYSTEM_UID + ") user",
new ErrnoException("setuid", errno));
}
if (Process.myUid() != Process.SYSTEM_UID) {
throw new IllegalStateException("UID didn't actually change: " +
Process.myUid() + " != " + Process.SYSTEM_UID);
}
}
}
public static void main(String[] args) {
try {
switchToSystemUid();
unlock();
} catch (Exception e) {
System.err.println("Failed to enable OEM unlocking");
e.printStackTrace();
System.exit(1);
}
}
}
-6
View File
@@ -1,6 +0,0 @@
id=com.chiller3.avbroot.oemunlockonboot
name=oemunlockonboot
version=v3.0.0
versionCode=196608
author=chenxiaolong
description=Enable OEM unlocking on every boot
-20
View File
@@ -1,20 +0,0 @@
exec >/data/local/tmp/avbroot_oem_unlock.log 2>&1
mod_dir=${0%/*}
header() {
echo "----- ${*} -----"
}
header Environment
echo "Timestamp: $(date)"
echo "Script: ${0}"
echo "UID/GID/Context: $(id)"
header Enable OEM unlocking
CLASSPATH="${mod_dir}/classes.dex" app_process / Main &
pid=${!}
wait "${pid}"
echo "Exit status: ${?}"
echo "Logcat:"
logcat -d --pid "${pid}"
-9
View File
@@ -11,14 +11,5 @@ publish = false
[dependencies]
anyhow = "1.0.75"
clap = { version = "4.4.1", features = ["derive"] }
human-sort = "0.2.2"
regex = { version = "1.9.4", default-features = false, features = ["perf", "std"] }
tempfile = "3.8.0"
toml_edit = "0.21.0"
walkdir = "2.3.3"
# https://github.com/zip-rs/zip/pull/383
[dependencies.zip]
git = "https://github.com/chenxiaolong/zip"
rev = "989101f9384b9e94e36e6e9e0f51908fdf98bde6"
default-features = false
+1 -12
View File
@@ -8,7 +8,7 @@ To update the project version, run:
cargo xtask set-version -V <version>
```
This will update the main [`Cargo.toml`](../Cargo.toml) and each module's `module.prop` file.
This will update the main [`Cargo.toml`](../Cargo.toml) file.
## Updating changelog links
@@ -19,14 +19,3 @@ Then, run the following command to generate the appropriate link references:
```bash
cargo xtask update-changelog
```
## Build modules
To build avbroot's companion modules, run:
```bash
cargo xtask modules -a
# or -m <module1> -m <module2> ...
```
See the main [`README.md`](../README.md#avbroot-magisk-modules) for more details.
+1 -4
View File
@@ -4,13 +4,12 @@
*/
mod changelog;
mod module;
mod version;
use anyhow::Result;
use clap::{Parser, Subcommand};
use crate::{module::ModulesCli, version::SetVersionCli};
use crate::version::SetVersionCli;
const WORKSPACE_DIR: &str = env!("CARGO_WORKSPACE_DIR");
@@ -19,7 +18,6 @@ fn main() -> Result<()> {
match cli.command {
Command::SetVersion(c) => version::set_version_subcommand(&c),
Command::Modules(c) => module::modules_subcommand(&c),
Command::UpdateChangelog => changelog::update_changelog_subcommand(),
}
}
@@ -27,7 +25,6 @@ fn main() -> Result<()> {
#[derive(Debug, Subcommand)]
pub enum Command {
SetVersion(SetVersionCli),
Modules(ModulesCli),
/// Update links in CHANGELOG.md.
UpdateChangelog,
}
-231
View File
@@ -1,231 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
* SPDX-License-Identifier: GPL-3.0-only
*/
use std::{
collections::HashMap,
env,
ffi::OsStr,
fmt,
fs::{self, File},
io::{self, BufWriter, Write},
path::{Path, PathBuf},
process::Command,
};
use anyhow::{anyhow, bail, Result};
use clap::{Args, Parser, ValueEnum};
use tempfile::TempDir;
use walkdir::WalkDir;
use zip::{write::FileOptions, ZipWriter};
use crate::WORKSPACE_DIR;
#[cfg(unix)]
const D8: &str = "d8";
#[cfg(windows)]
const D8: &str = "d8.bat";
fn newest_child_by_name(directory: &Path) -> Result<PathBuf> {
let mut children = vec![];
for entry in fs::read_dir(directory)? {
let entry = entry?;
let path = entry
.path()
.into_os_string()
.into_string()
.map_err(|e| anyhow!("Non-UTF-8 path: {e:?}"))?;
children.push(path);
}
children.sort_by(|a, b| human_sort::compare(a, b));
children
.pop()
.map(PathBuf::from)
.ok_or_else(|| anyhow!("{directory:?} has no children"))
}
fn build_dex(writer: &mut dyn Write, sources: &[&Path]) -> Result<()> {
let sdk = env::var_os("ANDROID_HOME")
.map(PathBuf::from)
.ok_or_else(|| anyhow!("ANDROID_HOME must be set to the Android SDK path"))?;
let build_tools = newest_child_by_name(&sdk.join("build-tools"))?;
let platform = newest_child_by_name(&sdk.join("platforms"))?;
let d8 = build_tools.join(D8);
let android_jar = platform.join("android.jar");
let temp_dir = TempDir::new()?;
let mut process = Command::new("javac")
.args(["-source", "1.8"])
.args(["-target", "1.8"])
.arg("-cp")
.arg(android_jar)
.arg("-d")
.arg(temp_dir.path())
.args(sources)
.spawn()?;
let status = process.wait()?;
if !status.success() {
bail!("javac failed with {status}");
}
let mut class_files = vec![];
for entry in WalkDir::new(temp_dir.path()) {
let entry = entry?;
if entry.path().extension() == Some(OsStr::new("class")) {
class_files.push(entry.into_path());
}
}
let mut process = Command::new(d8)
.arg("--output")
.arg(temp_dir.path())
.args(class_files)
.spawn()?;
let status = process.wait()?;
if !status.success() {
bail!("d8 failed with {status}");
}
let mut reader = File::open(temp_dir.path().join("classes.dex"))?;
io::copy(&mut reader, writer)?;
Ok(())
}
fn parse_props(data: &str) -> Result<HashMap<String, String>> {
let mut result = HashMap::new();
for line in data.split('\n') {
if line.is_empty() {
continue;
}
let Some((k, v)) = line.split_once('=') else {
bail!("Malformed line: {line:?}");
};
result.insert(k.trim().to_owned(), v.trim().to_owned());
}
Ok(result)
}
fn start_module(
dist_dir: &Path,
common_dir: &Path,
module_dir: &Path,
) -> Result<(PathBuf, ZipWriter<BufWriter<File>>)> {
let module_prop_raw = fs::read_to_string(module_dir.join("module.prop"))?;
let module_prop = parse_props(&module_prop_raw)?;
let name = module_prop["name"].as_str();
let version = module_prop["version"].as_str();
let version = version.strip_prefix('v').unwrap_or(version);
let zip_path = dist_dir.join(format!("{name}-{version}.zip"));
let raw_writer = File::create(&zip_path)?;
let mut zip_writer = ZipWriter::new(BufWriter::new(raw_writer));
zip_writer.start_file(
"META-INF/com/google/android/update-binary",
FileOptions::default(),
)?;
io::copy(
&mut File::open(common_dir.join("update-binary"))?,
&mut zip_writer,
)?;
zip_writer.start_file(
"META-INF/com/google/android/updater-script",
FileOptions::default(),
)?;
io::copy(
&mut File::open(common_dir.join("updater-script"))?,
&mut zip_writer,
)?;
zip_writer.start_file("module.prop", FileOptions::default())?;
zip_writer.write_all(module_prop_raw.as_bytes())?;
Ok((zip_path, zip_writer))
}
pub fn modules_subcommand(cli: &ModulesCli) -> Result<()> {
let modules_dir = Path::new(WORKSPACE_DIR).join("modules");
let common_dir = modules_dir.join("common");
let dist_dir = modules_dir.join("dist");
fs::create_dir_all(&dist_dir)?;
let modules = if cli.module.all {
Module::value_variants()
} else {
&cli.module.module
};
for module in modules {
let module_dir = modules_dir.join(module.to_string());
let (path, mut writer) = start_module(&dist_dir, &common_dir, &module_dir)?;
match module {
Module::OemUnlockOnBoot => {
writer.start_file("classes.dex", FileOptions::default())?;
build_dex(&mut writer, &[&module_dir.join("Main.java")])?;
writer.start_file("service.sh", FileOptions::default())?;
let mut reader = File::open(module_dir.join("service.sh"))?;
io::copy(&mut reader, &mut writer)?;
}
}
writer.finish()?;
let path = path.canonicalize()?;
println!("Built module: {path:?}");
}
Ok(())
}
#[derive(Clone, Copy, Debug, ValueEnum)]
#[value(rename_all = "lower")]
enum Module {
OemUnlockOnBoot,
}
impl fmt::Display for Module {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_possible_value()
.expect("no values are skipped")
.get_name()
.fmt(f)
}
}
#[derive(Debug, Args)]
#[group(required = true, multiple = false)]
pub struct ModuleGroup {
/// Name of module.
#[arg(short, long)]
module: Vec<Module>,
/// Build all modules.
#[arg(short, long, conflicts_with = "module")]
all: bool,
}
/// Build companion modules.
#[derive(Debug, Parser)]
pub struct ModulesCli {
#[command(flatten)]
module: ModuleGroup,
}
+1 -52
View File
@@ -27,43 +27,6 @@ fn update_cargo_version(version: &str) -> Result<()> {
Ok(())
}
fn update_module_version(path: &Path, version: &str) -> Result<()> {
let mut version_code = 0;
// 8 bits per version component.
for piece in version.split('.') {
let piece: u32 = piece.parse()?;
version_code <<= 8;
version_code |= piece;
}
let raw_reader = File::open(path)?;
let mut reader = BufReader::new(raw_reader);
let mut result = String::new();
let mut line = String::new();
loop {
line.clear();
let n = reader.read_line(&mut line)?;
if n == 0 {
break;
}
if line.starts_with("version=") {
result.push_str(&format!("version=v{version}\n"));
} else if line.starts_with("versionCode=") {
result.push_str(&format!("versionCode={version_code}\n"));
} else {
result.push_str(&line);
}
}
fs::write(path, &result)?;
Ok(())
}
fn update_changelog_version(version: &str) -> Result<()> {
let path = Path::new(WORKSPACE_DIR).join("CHANGELOG.md");
let raw_reader = File::open(&path)?;
@@ -109,26 +72,12 @@ fn update_changelog_version(version: &str) -> Result<()> {
pub fn set_version_subcommand(cli: &SetVersionCli) -> Result<()> {
update_cargo_version(&cli.version)?;
let modules_dir = Path::new(WORKSPACE_DIR).join("modules");
for entry in fs::read_dir(modules_dir)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
let module_prop = entry.path().join("module.prop");
if module_prop.exists() {
update_module_version(&module_prop, &cli.version)?;
}
}
}
update_changelog_version(&cli.version)?;
Ok(())
}
/// Set the version number in all Cargo.toml files and in the module metadata.
/// Set the version number in all Cargo.toml files.
#[derive(Debug, Parser)]
pub struct SetVersionCli {
/// Version number.