Compare commits

..

19 Commits

Author SHA1 Message Date
Andrew Gunnerson 0cdc7172dd Version 3.13.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-12 18:39:59 -04:00
Andrew Gunnerson 1296275418 CHANGELOG.md: Add entry for PR #427
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-12 18:38:43 -04:00
Andrew Gunnerson a62d0a5c91 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-12 18:25:35 -04:00
Andrew Gunnerson de433a2724 README.md: Clarify what --skip-{system,recovery}-ota-cert affects
Discussion: #426

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-03-12 18:07:17 -04:00
Andrew Gunnerson c08af33343 CHANGELOG.md: Add entry for PR #425
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-24 21:17:44 -05:00
Andrew Gunnerson e224884591 format/lp: Relax validation checks to parse on-device super partition
There are two documented behaviors in liblp that are violated with the
on-disk super partition layout after a virtual A/B CoW merge:

1. The partition name includes a `-` due to the `-cow` suffix. This is
   not meant to be a valid character.
2. The extent list is likely to have many gaps and not be sorted. The
   format documentation says that gaps are not allowed.

This commit updates avbroot's LP parser to be less strict so that it can
load real on-device super partitions. The extent allocator for the
`pack` subcommand remains unchanged though, so avbroot will always
produce LP images with sorted, gapless extents.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-24 21:05:28 -05:00
Andrew Gunnerson a525fc4550 CHANGELOG.md: Add entry for PR #424
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:34:30 -05:00
Andrew Gunnerson 8c6b0fdfc5 cli/ota: Discard unmodified system image temp file earlier
This is the same optimization as is currently done for boot images.
There's no reason to keep the temp file around for the entire patching
process if it's unmodified and we're not going to be copying its data
into the payload.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:23:10 -05:00
Andrew Gunnerson 36d4ed19ad CHANGELOG.md: Add entry for PR #423
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:18:12 -05:00
Andrew Gunnerson f9b06b33e6 cli/ota: Fix incorrect unprotected partition warning with --skip-system-ota-cert
The filtering out of partitions was done at the wrong scope, causing
avbroot to warn that extracted-but-unmodified partitions were not
protected by AVB. We never encountered this before because the system
image was always patched and unmodified boot images got filtered out at
an earlier phase during patching.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:00:59 -05:00
Andrew Gunnerson 620c873be5 CHANGELOG.md: Add entry for PR #422
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 21:00:20 -05:00
Andrew Gunnerson e929ecbe44 Replace ring with aws-lc
The author of ring recently announced that the library is no longer
being maintained and fixes for security issues may be significantly
delayed. Big thanks to Brian Smith for creating and maintaining the
library for so long!

This commit replaces ring with aws-lc, a cryptography library maintained
by Amazon AWS. It seems to be well-regarded and is used by high-profile
projects like rustls. It is also API-compatible with ring, so it is
effectively a drop-in replacement.

Unfortunately, we still cannot switch back to the RustCrypto SHA1 and
SHA2 implementations because they are still significantly slower than
ring and aws-lc on systems that do not support the SHA-NI extensions.

https://rustsec.org/advisories/RUSTSEC-2025-0007

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 20:44:19 -05:00
Andrew Gunnerson 31685713ef CHANGELOG.md: Add entry for PR #421
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 19:33:58 -05:00
Andrew Gunnerson 0dab7aa32c Switch to stable bzip2-rs release and use zlib-rs flate2 backend
* There is now a stable release of bzip2-rs with the fix for both the C
  and Rust versions of bzip2 being compiled.

* The zlib-rs deflate implementation is faster than the default
  miniz_oxide. Changing this requires updating the checksums in the e2e
  tests due to slight differences in compression levels between the two
  implementations.

* Temporarily silence RUSTSEC-2025-0007 to avoid blocking CI. The ring
  library is no longer maintained.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-21 19:27:18 -05:00
Ivan ba6c1b1400 README.ru.md: update translation
* https://github.com/chenxiaolong/avbroot/commit/c198646c7c9b3eb8013dca43df6df8502e619e3c
* https://github.com/chenxiaolong/avbroot/commit/d4eb231dd49e7e6c7320135785cde7cd57634a50
* https://github.com/chenxiaolong/avbroot/commit/1484cd47c354197e1179a554f9f001020297aac8
* https://github.com/chenxiaolong/avbroot/commit/84fa6c6bc63b62d1b3b96dcb50b18b13dea0076f

Signed-off-by: Ivan <reddxae@proton.me>
2025-02-11 14:20:11 +03:00
Andrew Gunnerson 1ecbf1144d CHANGELOG.md: Add entry for PR #418
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-09 21:59:13 -05:00
Andrew Gunnerson 84fa6c6bc6 Add option to skip replacing OTA cert in system image
This is analogous to the existing --skip-recovery-ota-cert option,
except for the system image.

Discussion: #417

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-09 21:42:18 -05:00
Andrew Gunnerson 15b7db4631 CHANGELOG.md: Add entry for PR #415
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-04 00:16:38 -05:00
Andrew Gunnerson 1d9c1574da lp: Stop checking for zeroed first block
AOSP says that for non-empty images, the first 4 KiB block is supposed
to be filled with zeros to prevent it from being interpreted as an old
BIOS boot sector. The previous implementation relied on that to
distinguish between empty and non-empty images. However, Samsung decided
to use this region for their own SignerVer02 structure, so the heuristic
doesn't work.

AOSP's liblp tries to parse the input file as an empty image before
falling back to parsing as a normal image. We'll do the same.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2025-02-04 00:13:27 -05:00
28 changed files with 725 additions and 413 deletions
+3 -1
View File
@@ -20,6 +20,8 @@ jobs:
RUSTFLAGS: -C strip=symbols -C target-feature=+crt-static
TARGETS: ${{ join(matrix.artifact.targets, ' ') || matrix.artifact.name }}
ANDROID_API: ${{ matrix.artifact.android_api }}
# https://aws.github.io/aws-lc-rs/requirements/windows.html#use-of-prebuilt-nasm-objects
AWS_LC_SYS_PREBUILT_NASM: 1
strategy:
fail-fast: false
matrix:
@@ -155,7 +157,7 @@ jobs:
run: cp LICENSE README.md target/output/
- name: Archive executable
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: avbroot-${{ steps.get_version.outputs.version }}-${{ matrix.artifact.name }}
path: |
+1 -1
View File
@@ -13,4 +13,4 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run cargo-deny
uses: EmbarkStudios/cargo-deny-action@e2f4ede4a4e60ea15ff31bc0647485d80c66cfba # v2.0.4
uses: EmbarkStudios/cargo-deny-action@34899fc7ba81ca6268d5947a7a16b4649013fea1 # v2.0.11
+22
View File
@@ -7,6 +7,19 @@
to update the actual links at the bottom of the file.
-->
### Version 3.13.0
* Fix parsing Samsung `super.img` files in `avbroot lp` due to Samsung putting their own data structures in a region that's supposed to be filled with zeros ([PR #415])
* Add advanced option to skip replacing the OTA certificate in the system image ([Discussion #417], [PR #418])
* Switch to stable bzip2-rs release and use zlib-rs as the backend for flate2 ([PR #421])
* Switch to the aws-lc cryptography library for SHA1 and SHA2 hashing ([PR #422])
* The ring library is no longer maintained
* Fix incorrect `Partitions aren't protected by AVB: system` warning when using `--skip-system-ota-cert` ([PR #423])
* Discard unneeded temp file sooner when using `--skip-system-ota-cert` ([PR #424])
* Make `avbroot lp`'s parser less strict so that it can load on-device `super` partitions ([PR #425])
* The on-disk layout on virtual A/B devices violates some requirements stated in AOSP's documentation
* Update dependencies ([PR #427])
### Version 3.12.0
* Add new `-p <name>` option to `avbroot ota extract` for extracting specific partitions ([PR #408])
@@ -280,6 +293,7 @@ Behind-the-scenes changes:
[Discussion #235]: https://github.com/chenxiaolong/avbroot/discussions/235
[Discussion #286]: https://github.com/chenxiaolong/avbroot/discussions/286
[Discussion #294]: https://github.com/chenxiaolong/avbroot/discussions/294
[Discussion #417]: https://github.com/chenxiaolong/avbroot/discussions/417
[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
@@ -444,3 +458,11 @@ Behind-the-scenes changes:
[PR #409]: https://github.com/chenxiaolong/avbroot/pull/409
[PR #410]: https://github.com/chenxiaolong/avbroot/pull/410
[PR #411]: https://github.com/chenxiaolong/avbroot/pull/411
[PR #415]: https://github.com/chenxiaolong/avbroot/pull/415
[PR #418]: https://github.com/chenxiaolong/avbroot/pull/418
[PR #421]: https://github.com/chenxiaolong/avbroot/pull/421
[PR #422]: https://github.com/chenxiaolong/avbroot/pull/422
[PR #423]: https://github.com/chenxiaolong/avbroot/pull/423
[PR #424]: https://github.com/chenxiaolong/avbroot/pull/424
[PR #425]: https://github.com/chenxiaolong/avbroot/pull/425
[PR #427]: https://github.com/chenxiaolong/avbroot/pull/427
Generated
+409 -223
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.12.0"
version = "3.13.0"
license = "GPL-3.0-only"
edition = "2021"
repository = "https://github.com/chenxiaolong/avbroot"
+20 -3
View File
@@ -384,11 +384,28 @@ Note that avbroot will validate that the prepatched image is compatible with the
avbroot can be used for just re-signing an OTA by specifying `--rootless` instead of `--magisk`/`--prepatched`. With this option, the patched OTA will not be rooted. The only modification applied is the replacement of the OTA verification certificate so that the OS can be upgraded with future (patched) OTAs.
### Skipping recovery OTA certificate patches
### Skipping OTA certificate patches
avbroot can skip modifying `otacerts.zip` in the recovery image with the `--skip-recovery-ota-cert` option. **Do not do this unless you have a good reason to do so.** (For example, if you've already manually inserted the OTA certificate into a boot image specified with `--prepatched` or `--replace`.) When this option is used with `--rootless` (and `--dsu` is not specified), then no modifications are performed on any boot image besides ensuring they are properly signed.
avbroot can skip modifying `otacerts.zip` with the `--skip-system-ota-cert` and `--skip-recovery-ota-cert` options. **Do not use these unless you have a good reason to do so.**
When manually adding the OTA certificate to a boot image, [verifying the patched OTA](#verifying-otas) afterwards is recommended to ensure that it was properly done.
When `--skip-system-ota-cert` is used, the OTA certificates in the `system` partition will not be modified. This prevents custom OTA updater apps from installing further patched OTAs while booted into Android.
When `--skip-recovery-ota-cert` is used, the OTA certificates in the `vendor_boot` or `recovery` partition will not be modified. **This prevents sideloading further patched OTAs from recovery mode.**
If `--skip-recovery-ota-cert` is used because the OTA certificate was already manually added to the boot image, then [verifying the patched OTA](#verifying-otas) afterwards is recommended to ensure that it was properly done. The verification process is only capable of checking the boot image's copy of the OTA certificates, not the system image's copy of them.
### Skipping all patches
To have avbroot make the absolute minimal changes:
* Specify `--skip-system-ota-cert`
* Specify `--skip-recovery-ota-cert`
* Specify `--rootless`
* Omit `--dsu`
This will re-sign the `vbmeta` partition and the OTA with the custom keys, but leave all other partitions untouched.
**This should only be used for advanced troubleshooting.** Without the OTA certificate patches, the resulting OTA will not be able to install further updates.
### Replacing partitions
+63 -7
View File
@@ -295,6 +295,7 @@ Magisk версии 25211 и новее требует наличие разде
--input /path/to/ota.zip \
--directory . \
--boot-only
--partition <название раздела> # init_boot или boot, в зависимости от устройства
```
2. Теперь нужно пропатчить загрузочный образ с помощью приложения Magisk. Это **ДОЛЖНО** быть сделано именно на целевом устройстве или устройстве той же модели! Имя раздела будет неверным и не подойдет, если пропатчить образ на устройстве иной модели.
@@ -381,11 +382,16 @@ avbroot может подменить используемый загрузоч
avbroot можно использовать для простого переподписания OTA, указав аргумент `--rootless` вместо `--magisk`/`--prepatched`. В таком случае пропатченный OTA не будет рутирован. Единственная модификация, которая будет применена – это замена сертификата проверки OTA, чтобы систему можно было обновлять с помощью будущих пропатченных OTA.
### Пропуск патчинга сертификата OTA в разделе Recovery
### Пропуск патчинга сертификата OTA
avbroot может пропустить изменение файла `otacerts.zip` в разделе Recovery с помощью опции `--skip-recovery-ota-cert`. **Не используйте эту функцию, если на то нет веской причины.** (Например, если вы уже самостоятельно встроили сертификат OTA в загрузочный образ (`boot.img`) и передаете его программе через опции `--prepatched` или `--replace`.) Если эта опция применяется совместно с `--rootless` (и без указания параметра `--dsu`), то в загрузочный образ не будут внесены никакие изменения, кроме обеспечения его корректной подписи.
avbroot может пропускать изменение `otacerts.zip` с помощью аргументов `--skip-system-ota-cert` и `--skip-recovery-ota-cert`. **Не используйте их без веской причины.** (Например, если вы уже самостоятельно встроили сертификат OTA в загрузочный (boot) образ и передаете его программе через опции `--prepatched` или `--replace`.)
В противном случае, на устройстве может не остаться возможности устанавливать дальнейшие обновления.
Если вы вручную добавили сертификат OTA в загрузочный образ, рекомендуем [предварительно проверить пропатченный OTA.](#проверка-ota)
При использовании `--skip-system-ota-cert`, никаких изменений в образ `system` не вносится.
При использовании `--skip-recovery-ota-cert` совместно с `--rootless` и без указания `--dsu`, не вносится никаких изменений в загрузочные образы, кроме обеспечения их корректной подписи.
Если вы вручную добавили сертификат OTA в загрузочный (boot) образ, рекомендуем [предварительно проверить пропатченный OTA.](#проверка-ota)
### Подмена разделов
@@ -441,17 +447,20 @@ Verified boot is disabled by vbmeta's header flags: 0x3
* Использовать незашифрованные приватные ключи. Крайне не рекомендуется.
### Извлечение всей OTA
### Извлечение образов из OTA
Чтобы извлечь все образы, содержащиеся в `payload.bin`, используйте команду:
Чтобы извлечь образы разделов, содержащихся в `payload.bin`, используйте команду:
```bash
avbroot ota extract \
--input /путь/к/ota.zip \
--directory extracted \
--all
--directory extracted
```
По умолчанию извлекаются только те образы, которые потенциально могут быть пропатчены с помощью avbroot. Чтобы извлечь все образы, используйте опцию `--all`. Для извлечения конкретных образов используйте опцию `--partition <название раздела>`, которую можно указать несколько раз.
Эта команда также поддерживает извлечение встроенного сертификата OTA и публичного ключа AVB с помощью опций `--cert-ota` и `--public-key-avb`. Чтобы извлечь только эти компоненты, укажите аргумент `--none`, чтобы пропустить извлечение образов разделов.
### Режим записи ZIP
По умолчанию, avbroot использует потоковую запись для вывода OTA во время патчинга. Это означает, что он вычисляет дайджест sha256 для цифровой подписи одновременно с записью файла. Такой режим приводит к тому, что в ZIP-файле появляются описатели данных, что является частью стандарта ZIP и работает на подавляющем большинстве устройств. Однако некоторые устройства могут иметь некорректно работающие парсеры ZIP-файлов и не смогут правильно прочитать ZIP-файлы OTA, содержащие описатели данных. Если это так, используйте опцию `--zip-mode seekable` при патчинге.
@@ -480,6 +489,53 @@ avbroot поддерживает делегирование всех опера
Обратите внимание, что avbroot проверит подпись, возвращенную внешней программой, на соответствие с публичным ключом. Это гарантирует, что процесс патчинга завершится ошибкой, если был использован неправильный приватный ключ.
### Размер страницы 16 КБ в настройках для разработчиков
На современных устройствах с Android 16 и выше, в настройках для разработчиков может появиться опция переключения на ядро с размером страницы 16 КБ. Однако, эта функция не будет работать в системе, пропатченной с помощью avbroot, поскольку переключение данной настройки осуществляется путём установки инкрементальной OTA:
* `/vendor/boot_otas/boot_ota_16k.zip` — используется для переключения на ядро с размером страницы 16 КБ (в разделе `boot` уже должно быть прошито ядро с размером страницы 4K)
* `/vendor/boot_otas/boot_ota_4k.zip` — используется для переключения на ядро с размером страницы 4 КБ (в разделе `boot` уже должно быть прошито ядро с размером страницы 16K)
Эти файлы (в `boot_otas`) невозможно прошить на системе, пропатченной avbroot, потому что `payload.bin` внутри них подписан ключом производителя. Кроме того, это неполноценные OTA-файлы: у них нет метаданных, характерных для OTA, а сам zip-файл не подписан. Это просто обычный архив, который содержит подписанный `payload.bin`.
Поддержка `boot_otas` не планируется. Это потребует реализации функционала для модификации ФС в инкрементальных OTA и их дальнейшей обработки, что сделать очень непросто.
Если вы всё же хотите завести эту функцию, можно попробовать вручную подписать файлы в `boot_otas` собственным ключом. Поскольку инкрементальные OTA не пересоздаются, раздел `boot` должен оставаться без изменений во время выполнения команды `avbroot ota patch`.
1. Распакуйте `vendor.img` с помощью avbroot и [afsr](https://github.com/chenxiaolong/afsr):
```bash
avbroot avb unpack -i vendor.img
afsr unpack -i raw.img
```
2. Извлеките `payload.bin` из `boot_otas/boot_ota_16k.zip`.
3. Переподпишите `payload.bin` вашим OTA-ключом:
```bash
avbroot payload repack \
-i payload.bin.orig \
-o payload.bin \
-k ota.key \
--output-properties payload_properties.txt
```
4. Создайте новый zip, включающий `payload.bin` и `payload_properties.txt`. Файлы должны быть добавлены без сжатия (например, с помощью `zip -0`).
5. Повторите эту процедуру для `boot_otas/boot_ota_4k.zip`.
6. Соберите `vendor.img` обратно и подпишите его вашим AVB-ключом:
```bash
afsr pack -o raw.img
avbroot avb pack -o vendor.img -k avb.key --recompute-size
```
7. Пропатчите обычный OTA-архив с прошивкой, подменив `vendor` на модифицированный образ:
```bash
avbroot ota patch \
--replace vendor <модифицированный vendor.img> \
<дальше указываются аргументы, как при обычном патчинге>
```
## Сборка из исходного кода
Убедитесь, что у вас установлен [набор инструментов Rust.](https://www.rust-lang.org/ru/) Затем выполните:
+9 -15
View File
@@ -10,9 +10,15 @@ publish = false
[dependencies]
anyhow = "1.0.75"
# We use aws-lc-rs instead of sha2 for sha256 digest computation of large files
# because sha2 is significantly slower on older x86_64 CPUs without the SHA-NI
# instructions. sha2 is still used for signing purposes.
# https://github.com/RustCrypto/hashes/issues/327
aws-lc-rs = { version = "1.0.0", default-features = false, features = ["aws-lc-sys"] }
base64 = "0.22.1"
bitflags = { version = "2.4.1", features = ["serde"] }
bstr = "1.6.2"
bzip2 = { version = "0.5.1", default-features = false, features = ["libbz2-rs-sys"] }
cap-std = "3.0.0"
cap-tempfile = "3.0.0"
clap = { version = "4.4.1", features = ["derive"] }
@@ -22,27 +28,22 @@ const-oid = "0.9.5"
crc32fast = "1.4.2"
ctrlc = "3.4.0"
dlv-list = "0.6.0"
flate2 = "1.0.27"
flate2 = { version = "1.0.29", features = ["zlib-rs"] }
gf256 = { version = "0.3.0", features = ["rs"] }
hex = { version = "0.4.3", features = ["serde"] }
liblzma = "0.3.0"
lz4_flex = "0.11.1"
memchr = "2.6.0"
miniz_oxide = "0.8.0"
num-bigint-dig = "0.8.4"
num-traits = "0.2.16"
passterm = "2.0.3"
phf = { version = "0.11.2", features = ["macros"] }
pkcs8 = { version = "0.10.2", features = ["encryption", "pem"] }
prost = "0.13.1"
# We can't upgrade to 0.9.0 until rsa updates its rand_core dependency.
rand = "0.8.5"
rayon = "1.7.0"
regex = { version = "1.9.4", default-features = false, features = ["perf", "std"] }
# We use ring instead of sha2 for sha256 digest computation of large files
# because sha2 is significantly slower on older x86_64 CPUs without the SHA-NI
# instructions. sha2 is still used for signing purposes.
# https://github.com/RustCrypto/hashes/issues/327
ring = "0.17.0"
rsa = { version = "0.9.2", features = ["sha1", "sha2"] }
serde = { version = "1.0.188", features = ["derive"] }
sha1 = "0.10.5"
@@ -57,13 +58,6 @@ x509-cert = { version = "0.2.4", features = ["builder"] }
zerocopy = { version = "0.8.10", features = ["std"] }
zerocopy-derive = "0.8.5"
# Waiting for next stable release.
[dependencies.bzip2]
git = "https://github.com/trifectatechfoundation/bzip2-rs"
rev = "09a87db73c0517a9715ab3fd96fbe4961d545aee"
default-features = false
features = ["libbz2-rs-sys"]
# https://github.com/zip-rs/zip/pull/383
[dependencies.zip]
git = "https://github.com/chenxiaolong/zip"
@@ -76,7 +70,7 @@ libc = "0.2.158"
rustix = { version = "0.38.9", default-features = false, features = ["process"] }
[build-dependencies]
constcat = "0.5.0"
constcat = "0.6.0"
prost-build = "0.13.1"
protox = "0.7.0"
+2 -2
View File
@@ -599,7 +599,7 @@ pub fn verify_descriptors(
fn compute_digest_recursive(
directory: &Dir,
name: &str,
context: &mut ring::digest::Context,
context: &mut aws_lc_rs::digest::Context,
max_depth: u8,
seen: &mut HashSet<String>,
cancel_signal: &AtomicBool,
@@ -670,7 +670,7 @@ fn compute_digest_recursive(
/// chained partitions more than one level deep are ignored.
pub fn compute_digest(directory: &Dir, name: &str, cancel_signal: &AtomicBool) -> Result<[u8; 32]> {
let mut seen = HashSet::<String>::new();
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
compute_digest_recursive(directory, name, &mut context, 2, &mut seen, cancel_signal)?;
+47 -16
View File
@@ -373,8 +373,9 @@ fn get_vbmeta_patch_order(
.get_mut(vbmeta_name.as_str())
.unwrap()
.insert(partition_name.to_owned());
missing.remove(partition_name);
}
missing.remove(partition_name);
}
}
@@ -592,7 +593,12 @@ fn update_vbmeta_headers(
// Only sign and rewrite the image if we need to. Some vbmeta images may
// have no dependencies and are only being processed to ensure that the
// flags are set to a sane value.
if parent_header != &orig_parent_header {
//
// The root vbmeta image is always signed because it is possible to
// invoke avbroot is a way that no modifications are made (rootless +
// skipping recovery otacerts.zip patch). We still want the result to be
// bootable.
if parent_header != &orig_parent_header || name == "vbmeta" {
parent_header
.set_algo_for_key(key)
.with_context(|| format!("Failed to set signature algorithm: {name}"))?;
@@ -723,6 +729,7 @@ fn patch_ota_payload(
writer: impl Write,
external_images: &HashMap<String, PathBuf>,
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
skip_system_ota_cert: bool,
clear_vbmeta_flags: bool,
key_avb: &RsaSigningKey,
key_ota: &RsaSigningKey,
@@ -777,18 +784,23 @@ fn patch_ota_payload(
cancel_signal,
)?;
// Main patching operation is done. Unmodified boot images no longer need to
// be kept around.
input_files
.retain(|n, f| !(f.state == InputFileState::Extracted && RequiredImages::is_boot(n)));
let (system_target, system_ranges) = patch_system_image(
&required_images,
&mut input_files,
cert_ota,
key_avb,
cancel_signal,
)?;
let system_result = if skip_system_ota_cert {
None
} else {
Some(patch_system_image(
&required_images,
&mut input_files,
cert_ota,
key_avb,
cancel_signal,
)?)
};
input_files
.retain(|n, f| !(f.state == InputFileState::Extracted && RequiredImages::is_system(n)));
let mut vbmeta_headers = load_vbmeta_images(&mut input_files, &vbmeta_images)?;
@@ -822,8 +834,12 @@ fn patch_ota_payload(
&mut header,
// We can only perform the optimization of avoiding
// recompression if the image came from the original payload.
if name == system_target && !external_images.contains_key(&name) {
Some(&system_ranges)
if let Some((system_target, system_ranges)) = &system_result {
if name == *system_target && !external_images.contains_key(&name) {
Some(system_ranges)
} else {
None
}
} else {
None
},
@@ -915,6 +931,7 @@ fn patch_ota_zip(
mut zip_writer: &mut ZipWriter<impl Write>,
external_images: &HashMap<String, PathBuf>,
boot_patchers: &[Box<dyn BootImagePatch + Sync>],
skip_system_ota_cert: bool,
clear_vbmeta_flags: bool,
zip_mode: ZipMode,
key_avb: &RsaSigningKey,
@@ -1037,6 +1054,7 @@ fn patch_ota_zip(
&mut writer,
external_images,
boot_patchers,
skip_system_ota_cert,
clear_vbmeta_flags,
key_avb,
key_ota,
@@ -1183,7 +1201,7 @@ fn verify_partition_hashes(
let mut writer = HashingWriter::new(
io::sink(),
ring::digest::Context::new(&ring::digest::SHA256),
aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256),
);
stream::copy(file, &mut writer, cancel_signal)?;
@@ -1305,6 +1323,10 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
assert!(cli.root.rootless);
};
if cli.skip_system_ota_cert {
warn!("Not inserting OTA cert into system image; sideloading further updates may fail");
}
if cli.skip_recovery_ota_cert {
warn!("Not inserting OTA cert into recovery image; sideloading further updates may fail");
} else {
@@ -1347,6 +1369,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
&mut zip_writer,
&external_images,
&boot_patchers,
cli.skip_system_ota_cert,
cli.clear_vbmeta_flags,
cli.zip_mode,
&key_avb,
@@ -1981,11 +2004,19 @@ pub struct PatchCli {
)]
pub ignore_prepatched_compat: u8,
/// Skip adding OTA certificate to system image.
///
/// DO NOT USE THIS unless you've manually added the certificate to the
/// system image already. Otherwise, installing further updates via a custom
/// OTA updater app while booted into Android will not be possible.
#[arg(long, help_heading = HEADING_OTHER)]
pub skip_system_ota_cert: bool,
/// Skip adding OTA certificate to recovery image.
///
/// DO NOT USE THIS unless you've manually added the certificate to the
/// recovery image already. Otherwise, sideloading further updates will not
/// be possible.
/// recovery image already. Otherwise, sideloading further updates while
/// booted into recovery mode will not be possible.
///
/// When this option is used with --rootless, the boot images in the OTA
/// will not be modified.
+10 -10
View File
@@ -10,10 +10,10 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::{Algorithm, Context};
use bstr::ByteSlice;
use num_bigint_dig::{ModInverse, ToBigInt};
use num_traits::{Pow, ToPrimitive};
use ring::digest::{Algorithm, Context};
use rsa::{traits::PublicKeyParts, BigUint, RsaPublicKey};
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -55,7 +55,7 @@ pub const HEADER_MAX_SIZE: u64 = 64 * 1024;
///
/// ```rust
/// use avbroot::format::hashtree::HashTree;
/// let size = HashTree::new(4096, &ring::digest::SHA256, b"")
/// let size = HashTree::new(4096, &aws_lc_rs::digest::SHA256, b"")
/// .compute_level_offsets(8 * 1024 * 1024 * 1024)
/// .unwrap()
/// .first()
@@ -163,11 +163,11 @@ pub enum Error {
type Result<T> = std::result::Result<T, Error>;
pub(crate) fn ring_algorithm(name: &str, for_verify: bool) -> Result<&'static Algorithm> {
pub(crate) fn digest_algorithm(name: &str, for_verify: bool) -> Result<&'static Algorithm> {
match name {
"sha1" if for_verify => Ok(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY),
"sha256" => Ok(&ring::digest::SHA256),
"sha512" => Ok(&ring::digest::SHA512),
"sha1" if for_verify => Ok(&aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY),
"sha256" => Ok(&aws_lc_rs::digest::SHA256),
"sha512" => Ok(&aws_lc_rs::digest::SHA512),
a => Err(Error::UnsupportedHashAlgorithm(a.to_owned())),
}
}
@@ -534,7 +534,7 @@ impl HashTreeDescriptor {
ranges: Option<&[Range<u64>]>,
cancel_signal: &AtomicBool,
) -> Result<()> {
let algorithm = ring_algorithm(&self.hash_algorithm, false)?;
let algorithm = digest_algorithm(&self.hash_algorithm, false)?;
let hash_tree = HashTree::new(self.data_block_size, algorithm, &self.salt);
let (root_digest, hash_tree_data) = match ranges {
Some(r) => {
@@ -642,7 +642,7 @@ impl HashTreeDescriptor {
) -> Result<()> {
self.check_offsets()?;
let algorithm = ring_algorithm(&self.hash_algorithm, true)?;
let algorithm = digest_algorithm(&self.hash_algorithm, true)?;
util::check_bounds(self.tree_size, ..=HASH_TREE_MAX_SIZE)
.map_err(|e| Error::IntOutOfBounds("HashTree::tree_size", e))?;
@@ -905,8 +905,8 @@ impl HashDescriptor {
reader: impl Read,
for_verify: bool,
cancel_signal: &AtomicBool,
) -> Result<ring::digest::Digest> {
let algorithm = ring_algorithm(&self.hash_algorithm, for_verify)?;
) -> Result<aws_lc_rs::digest::Digest> {
let algorithm = digest_algorithm(&self.hash_algorithm, for_verify)?;
let mut context = Context::new(algorithm);
context.update(&self.salt);
+2 -2
View File
@@ -8,9 +8,9 @@ use std::{
str::{self, Utf8Error},
};
use aws_lc_rs::digest::Context;
use bstr::ByteSlice;
use num_traits::ToPrimitive;
use ring::digest::Context;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use zerocopy::{little_endian, FromBytes, IntoBytes};
@@ -890,7 +890,7 @@ impl BootImageV3Through4 {
/// image was successfully signed. Returns false if there's no vbmeta
/// structure to sign in [`V4Extra::signature`].
pub fn sign(&mut self, key: &RsaSigningKey) -> Result<bool> {
let mut context = Context::new(&ring::digest::SHA256);
let mut context = Context::new(&aws_lc_rs::digest::SHA256);
let image_size;
if let Some(v4) = &self.v4_extra {
+4 -4
View File
@@ -707,7 +707,7 @@ impl FecImage {
let fec_size: u32 =
util::try_cast(self.fec.len()).map_err(|e| Error::IntOutOfBounds("fec_size", e))?;
let digest = ring::digest::digest(&ring::digest::SHA256, &self.fec);
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &self.fec);
let header = RawHeader {
magic: FEC_MAGIC.into(),
@@ -781,7 +781,7 @@ impl<R: Read> FromReader<R> for FecImage {
let data_size = header.data_size.get();
let actual_digest = ring::digest::digest(&ring::digest::SHA256, &fec[..fec_size]);
let actual_digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &fec[..fec_size]);
if header.digest != actual_digest.as_ref() {
return Err(Error::InvalidFecDigest {
expected: hex::encode(header.digest),
@@ -886,7 +886,7 @@ mod tests {
let mut buf = vec![0u8; size];
rand::thread_rng().fill_bytes(&mut buf);
file.write_all(&buf).unwrap();
ring::digest::digest(&ring::digest::SHA256, &buf)
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &buf)
};
let fec = Fec::new(size as u64, block_size, parity).unwrap();
@@ -921,7 +921,7 @@ mod tests {
let mut buf = Vec::new();
file.rewind().unwrap();
file.read_to_end(&mut buf).unwrap();
ring::digest::digest(&ring::digest::SHA256, &buf)
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, &buf)
};
assert_eq!(repaired_digest.as_ref(), orig_digest.as_ref());
+14 -14
View File
@@ -9,12 +9,12 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::{Algorithm, Context};
use bstr::ByteSlice;
use rayon::{
iter::{IndexedParallelIterator, ParallelIterator},
slice::ParallelSliceMut,
};
use ring::digest::{Algorithm, Context};
use thiserror::Error;
use zerocopy::{little_endian, FromBytes, IntoBytes};
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
@@ -416,8 +416,8 @@ impl HashTree {
if hash_tree_data != actual_hash_tree_data {
// These are multiple megabytes, so only report the hashes.
let algorithm = self.salted_context.algorithm();
let expected = ring::digest::digest(algorithm, hash_tree_data);
let actual = ring::digest::digest(algorithm, &actual_hash_tree_data);
let expected = aws_lc_rs::digest::digest(algorithm, hash_tree_data);
let actual = aws_lc_rs::digest::digest(algorithm, &actual_hash_tree_data);
return Err(Error::InvalidHashTree {
expected: hex::encode(expected),
@@ -492,8 +492,8 @@ impl HashTreeImage {
const MAGIC: &'static [u8; 16] = b"avbroot!hashtree";
const VERSION: u16 = 1;
fn ring_algorithm(name: &str) -> Result<&'static Algorithm> {
avb::ring_algorithm(name, false)
fn digest_algorithm(name: &str) -> Result<&'static Algorithm> {
avb::digest_algorithm(name, false)
.map_err(|_| Error::UnsupportedHashAlgorithm(name.to_owned().into_bytes()))
}
@@ -509,8 +509,8 @@ impl HashTreeImage {
.reopen_boxed()
.and_then(|mut f| f.seek(SeekFrom::End(0)))
.map_err(Error::InputReopen)?;
let ring_algorithm = Self::ring_algorithm(algorithm)?;
let hash_tree = HashTree::new(block_size, ring_algorithm, salt);
let digest_algorithm = Self::digest_algorithm(algorithm)?;
let hash_tree = HashTree::new(block_size, digest_algorithm, salt);
let (root_digest, hash_tree_data) = hash_tree.generate(input, image_size, cancel_signal)?;
Ok(Self {
@@ -530,8 +530,8 @@ impl HashTreeImage {
ranges: &[Range<u64>],
cancel_signal: &AtomicBool,
) -> Result<()> {
let ring_algorithm = Self::ring_algorithm(&self.algorithm)?;
let hash_tree = HashTree::new(self.block_size, ring_algorithm, &self.salt);
let digest_algorithm = Self::digest_algorithm(&self.algorithm)?;
let hash_tree = HashTree::new(self.block_size, digest_algorithm, &self.salt);
self.root_digest = hash_tree.update(
input,
@@ -550,8 +550,8 @@ impl HashTreeImage {
input: &(dyn ReadSeekReopen + Sync),
cancel_signal: &AtomicBool,
) -> Result<()> {
let ring_algorithm = Self::ring_algorithm(&self.algorithm)?;
let hash_tree = HashTree::new(self.block_size, ring_algorithm, &self.salt);
let digest_algorithm = Self::digest_algorithm(&self.algorithm)?;
let hash_tree = HashTree::new(self.block_size, digest_algorithm, &self.salt);
hash_tree.verify(
input,
@@ -662,7 +662,7 @@ mod tests {
#[test]
fn calculate_level_ranges() {
let hash_tree = HashTree::new(4096, &ring::digest::SHA256, &[]);
let hash_tree = HashTree::new(4096, &aws_lc_rs::digest::SHA256, &[]);
assert_eq!(
hash_tree.compute_level_offsets(0).unwrap(),
&[] as &[Range<usize>],
@@ -675,7 +675,7 @@ mod tests {
#[test]
fn blocks_for_ranges() {
let hash_tree = HashTree::new(4096, &ring::digest::SHA256, b"Salt");
let hash_tree = HashTree::new(4096, &aws_lc_rs::digest::SHA256, b"Salt");
assert_eq!(
hash_tree.blocks_for_ranges(16384, &[0..16384]).unwrap(),
&[0..4],
@@ -696,7 +696,7 @@ mod tests {
#[test]
fn generate_update_verify() {
let cancel_signal = AtomicBool::new(false);
let hash_tree = HashTree::new(64, &ring::digest::SHA256, b"Salt");
let hash_tree = HashTree::new(64, &aws_lc_rs::digest::SHA256, b"Salt");
let mut input = SharedCursor::new();
// Try input smaller than one block.
+53 -55
View File
@@ -1,8 +1,7 @@
// SPDX-FileCopyrightText: 2024 Andrew Gunnerson
// SPDX-FileCopyrightText: 2024-2025 Andrew Gunnerson
// SPDX-License-Identifier: GPL-3.0-only
use std::{
cmp::Ordering,
fmt,
io::{self, Read, Seek, Write},
mem,
@@ -152,10 +151,8 @@ pub enum Error {
ExtentTypeZeroNotEmpty { index: usize },
#[error("Extent #{index}: Invalid type: {extent_type}")]
ExtentInvalidType { index: usize, extent_type: u32 },
#[error("Extent #{index}: Overlaps previous extent")]
ExtentOverlapsPrevious { index: usize },
#[error("Extent #{index}: Earlier block device index than previous extent")]
ExtentDeviceNotConsecutive { index: usize },
#[error("Extent #{index}: Overlaps another extent: #{other}")]
ExtentOverlapsAnother { index: usize, other: usize },
#[error("Extent #{index}: Block device index too large")]
ExtentDeviceIndexTooLarge { index: usize },
// Partition group errors.
@@ -304,7 +301,7 @@ impl RawGeometry {
let mut copy = *self;
copy.checksum.fill(0);
let digest = ring::digest::digest(&ring::digest::SHA256, copy.as_bytes());
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, copy.as_bytes());
if digest.as_ref() != self.checksum {
return Err(Error::GeometryInvalidDigest {
expected: hex::encode(self.checksum),
@@ -520,7 +517,7 @@ impl RawHeader {
let portion = &copy.as_bytes()[..expected_size];
let digest = ring::digest::digest(&ring::digest::SHA256, portion);
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, portion);
if digest.as_ref() != self.header_checksum {
return Err(Error::HeaderInvalidDigest {
expected: hex::encode(self.header_checksum),
@@ -598,17 +595,15 @@ impl PartitionName {
fn validate(&self) -> Result<()> {
let (prefix, suffix) = self.split();
let mut has_alnum = false;
for b in prefix {
match b {
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' => has_alnum = true,
b'_' => {}
_ => return Err(Error::PartitionNameInvalid(DebugString::new(self))),
}
}
// AOSP liblp's metadata_format.h says "Characters may only be
// alphanumeric or _", but AOSP creates partitions named like
// "system_b-cow".
let prefix_valid = prefix
.iter()
.all(|b| matches!(*b, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-'));
if has_alnum && is_zero(suffix) {
if prefix_valid && is_zero(suffix) {
Ok(())
} else {
Err(Error::PartitionNameInvalid(DebugString::new(self)))
@@ -1010,23 +1005,29 @@ impl RawMetadataSlot {
extent.validate(i, &self.block_devices)?;
}
// Ensure that all extents are in increasing order and not overlapping.
let mut iter = self
// Ensure that all extents are not overlapping. We have to sort here
// because the extents may not be in order when loading the super
// partition on an actual device. Also, AOSP liblp's `metadata_format.h`
// says "Gaps between extents are not allowed", but AOSP frequently
// creates this situation after a virtual A/B CoW merge.
let mut sorted_extents = self
.extents
.iter()
.filter(|e| e.target_type.get() == RawExtent::TARGET_TYPE_LINEAR)
.enumerate();
while let (Some((_, a)), Some((i, b))) = (iter.next(), iter.next()) {
match a.target_source.get().cmp(&b.target_source.get()) {
Ordering::Equal => {
if a.target_data.get() + a.num_sectors.get() > b.target_data.get() {
return Err(Error::ExtentOverlapsPrevious { index: i });
}
}
Ordering::Greater => {
return Err(Error::ExtentDeviceNotConsecutive { index: i });
}
Ordering::Less => {}
.enumerate()
.filter(|(_, e)| e.target_type.get() == RawExtent::TARGET_TYPE_LINEAR)
.collect::<Vec<_>>();
sorted_extents.sort_by_key(|(_, e)| (e.target_source, e.target_data));
for window in sorted_extents.windows(2) {
let ((a_i, a), (b_i, b)) = (window[0], window[1]);
if a.target_source == b.target_source
&& a.target_data.get() + a.num_sectors.get() > b.target_data.get()
{
return Err(Error::ExtentOverlapsAnother {
index: b_i,
other: a_i,
});
}
}
@@ -1060,14 +1061,19 @@ impl RawMetadata {
.read_exact(&mut buf)
.map_err(|e| Error::DataRead("geometry", e))?;
let image_type = if util::is_zero(&buf) {
ImageType::Normal
} else {
ImageType::Empty
};
// For non-empty images, AOSP says the first block is supposed to be
// filled with zeros, but Samsung puts their own SignerVer02 structure
// in there, so we can't rely on that.
let mut geometry = RawGeometry::ref_from_prefix(&buf).unwrap().0;
let geometry = match image_type {
ImageType::Normal => {
let image_type = match geometry.validate() {
Ok(()) => {
// This is an empty image for use with fastboot. These have no
// extra padding at the beginning of the file nor backup copies
// of the geometry and metadata structs.
ImageType::Empty
}
Err(Error::GeometryInvalidMagic(_)) => {
// This is an normal non-empty image, which has extra padding at
// the beginning to avoid having the geometry struct interpreted
// as a boot sector.
@@ -1077,7 +1083,7 @@ impl RawMetadata {
.read_exact(&mut buf)
.map_err(|e| Error::DataRead("geometry_primary", e))?;
let mut geometry = RawGeometry::ref_from_prefix(&buf).unwrap().0;
geometry = RawGeometry::ref_from_prefix(&buf).unwrap().0;
if geometry.validate().is_ok() {
// Skip the backup copy.
@@ -1094,17 +1100,9 @@ impl RawMetadata {
geometry.validate()?;
}
geometry
}
ImageType::Empty => {
// This is an empty image for use with fastboot. These have no
// extra padding at the beginning of the file nor backup copies
// of the geometry and metadata structs.
let geometry = RawGeometry::ref_from_prefix(&buf).unwrap().0;
geometry.validate()?;
geometry
ImageType::Normal
}
Err(e) => return Err(e),
};
Ok((image_type, geometry.to_owned()))
@@ -1181,7 +1179,7 @@ impl RawMetadata {
for slot in &self.slots {
#[cfg(not(fuzzing))]
{
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
context.update(slot.partitions.as_bytes());
context.update(slot.extents.as_bytes());
context.update(slot.groups.as_bytes());
@@ -1851,7 +1849,7 @@ impl TryFrom<&MetadataSlot> for RawMetadataSlot {
raw_slot.header.tables_size = offset.into();
let tables_digest = {
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
let mut context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
context.update(raw_slot.partitions.as_bytes());
context.update(raw_slot.extents.as_bytes());
context.update(raw_slot.groups.as_bytes());
@@ -1863,8 +1861,8 @@ impl TryFrom<&MetadataSlot> for RawMetadataSlot {
.tables_checksum
.copy_from_slice(tables_digest.as_ref());
let header_digest = ring::digest::digest(
&ring::digest::SHA256,
let header_digest = aws_lc_rs::digest::digest(
&aws_lc_rs::digest::SHA256,
&raw_slot.header.as_bytes()[..header_size],
);
raw_slot
@@ -1953,7 +1951,7 @@ impl TryFrom<&Metadata> for RawMetadata {
};
let geometry_digest =
ring::digest::digest(&ring::digest::SHA256, raw_metadata.geometry.as_bytes());
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, raw_metadata.geometry.as_bytes());
raw_metadata
.geometry
.checksum
+9 -6
View File
@@ -10,12 +10,12 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::Context;
use clap::ValueEnum;
use cms::signed_data::SignedData;
use const_oid::{db::rfc5912, ObjectIdentifier};
use memchr::memmem;
use prost::Message;
use ring::digest::Context;
use thiserror::Error;
use x509_cert::{der::Encode, Certificate};
use zip::{result::ZipError, write::FileOptions, CompressionMethod, ZipArchive, ZipWriter};
@@ -686,10 +686,13 @@ pub fn verify_ota(mut reader: impl Read + Seek, cancel_signal: &AtomicBool) -> R
// We support SHA1 for verification only.
let (algorithm, algo) = if signer.digest_alg.oid == rfc5912::ID_SHA_256 {
(&ring::digest::SHA256, SignatureAlgorithm::Sha256WithRsa)
(
&aws_lc_rs::digest::SHA256,
SignatureAlgorithm::Sha256WithRsa,
)
} else {
(
&ring::digest::SHA1_FOR_LEGACY_USE_ONLY,
&aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY,
SignatureAlgorithm::Sha1WithRsa,
)
};
@@ -791,7 +794,7 @@ fn validate_eocd(eocd: &[u8]) -> Result<()> {
fn compute_signature_comment(
key: &RsaSigningKey,
cert: &Certificate,
digest: ring::digest::Digest,
digest: aws_lc_rs::digest::Digest,
) -> Result<Vec<u8>> {
let cms_signature =
crypto::cms_sign_external(key, cert, digest.as_ref()).map_err(Error::CmsSign)?;
@@ -843,7 +846,7 @@ pub struct StreamingSigningWriter<W> {
impl<W: Write> StreamingSigningWriter<W> {
pub fn new(inner: W) -> Self {
Self {
inner: HashingWriter::new(inner, Context::new(&ring::digest::SHA256)),
inner: HashingWriter::new(inner, Context::new(&aws_lc_rs::digest::SHA256)),
queue: Default::default(),
used: 0,
}
@@ -947,7 +950,7 @@ impl<W: Read + Write + Seek> SeekableSigningWriter<W> {
// Compute the digest of everything up until the comment size field.
let mut hashing_writer = HashingWriter::new(
io::sink(),
ring::digest::Context::new(&ring::digest::SHA256),
aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256),
);
self.rewind().map_err(|e| Error::DataRead("raw_data", e))?;
+26 -18
View File
@@ -9,9 +9,11 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::{Context, Digest};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use bzip2::write::BzDecoder;
use flate2::{write::GzEncoder, Compression};
use liblzma::{
stream::{Check, Stream},
write::XzDecoder,
@@ -22,7 +24,6 @@ use rayon::{
iter::{IndexedParallelIterator, IntoParallelRefMutIterator},
prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
};
use ring::digest::{Context, Digest};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use x509_cert::Certificate;
@@ -121,6 +122,8 @@ pub enum Error {
InputOpen(String, #[source] io::Error),
#[error("Failed to open output file for partition: {0}")]
OutputOpen(String, #[source] io::Error),
#[error("Failed to GZ compress partition image chunk")]
GzCompress(#[source] io::Error),
#[error("Failed to initialize XZ encoder")]
XzInit(#[source] liblzma::stream::Error),
#[error("Failed to XZ compress partition image chunk")]
@@ -371,7 +374,7 @@ impl<W: Write> PayloadWriter<W> {
// Get the length of an dummy signature struct since the length fields
// are part of the data to be signed.
let dummy_sig = sign_digest(
ring::digest::digest(&ring::digest::SHA256, b"").as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, b"").as_ref(),
&key,
)?;
let dummy_sig_size = dummy_sig.encoded_len();
@@ -384,9 +387,9 @@ impl<W: Write> PayloadWriter<W> {
let manifest_raw_new = header.manifest.encode_to_vec();
// Excludes signatures (hashes are for signing).
let mut h_partial = Context::new(&ring::digest::SHA256);
let mut h_partial = Context::new(&aws_lc_rs::digest::SHA256);
// Includes signatures (hashes are for properties file).
let mut h_full = Context::new(&ring::digest::SHA256);
let mut h_full = Context::new(&aws_lc_rs::digest::SHA256);
// Write header to output file.
let raw_header = RawHeader {
@@ -597,9 +600,9 @@ pub fn verify_payload(
.ok_or(Error::MissingField("signatures_size"))?;
// Excludes signatures (hashes are for signing).
let mut h_partial = Context::new(&ring::digest::SHA256);
let mut h_partial = Context::new(&aws_lc_rs::digest::SHA256);
// Includes signatures (hashes are for properties file).
let mut h_full = Context::new(&ring::digest::SHA256);
let mut h_full = Context::new(&aws_lc_rs::digest::SHA256);
// Read from the beginning to the metadata signature.
let metadata_size = header.blob_offset - u64::from(header.metadata_signature_size);
@@ -753,7 +756,7 @@ pub fn apply_operation(
writer.seek(SeekFrom::Start(out_offset)).map_err(error_fn)?;
let mut hasher = Context::new(&ring::digest::SHA256);
let mut hasher = Context::new(&aws_lc_rs::digest::SHA256);
match op.r#type() {
// Handle ZERO/DISCARD specially since they don't require access to
@@ -922,7 +925,7 @@ pub fn extract_images<'a>(
fn compress_chunk(raw_data: &[u8], cancel_signal: &AtomicBool) -> Result<(Vec<u8>, Digest)> {
let reader = Cursor::new(raw_data);
let writer = Cursor::new(Vec::new());
let hashing_writer = HashingWriter::new(writer, Context::new(&ring::digest::SHA256));
let hashing_writer = HashingWriter::new(writer, Context::new(&aws_lc_rs::digest::SHA256));
// AOSP's payload_consumer does not support checking CRC during
// decompression. Also, we intentionally pick the lowest compression level
@@ -957,17 +960,20 @@ impl VabcAlgo {
}
}
fn compressed_size(self, mut raw_data: &[u8], block_size: u32) -> u64 {
fn compressed_size(self, mut raw_data: &[u8], block_size: u32) -> Result<u64> {
let mut total = 0;
while !raw_data.is_empty() {
let n = raw_data.len().min(block_size as usize);
// This should match CompressWorker::GetDefaultCompressionLevel() in
// AOSP's libsnapshot.
let compressed = match self {
Self::Lz4 => lz4_flex::block::compress(&raw_data[..n]),
// We use the miniz_oxide backend for flate2, but flate2 doesn't
// expose a nice function for compressing to a vec, so just use
// miniz_oxide directly.
Self::Gzip => miniz_oxide::deflate::compress_to_vec_zlib(&raw_data[..n], 9),
Self::Gzip => {
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
encoder.write_all(raw_data).map_err(Error::GzCompress)?;
encoder.finish().map_err(Error::GzCompress)?
}
};
total += compressed.len().min(n) as u64;
@@ -975,7 +981,7 @@ impl VabcAlgo {
raw_data = &raw_data[n..];
}
total
Ok(total)
}
}
@@ -1029,7 +1035,7 @@ pub fn compress_image(
let chunks_total = file_size.div_ceil(CHUNK_SIZE);
let mut bytes_compressed = 0;
let mut context_uncompressed = Context::new(&ring::digest::SHA256);
let mut context_uncompressed = Context::new(&aws_lc_rs::digest::SHA256);
let mut cow_estimate = 0;
let mut operations = vec![];
@@ -1069,8 +1075,10 @@ pub fn compress_image(
.map(
|(raw_offset, raw_data)| -> Result<(Vec<u8>, InstallOperation, u64)> {
let (data, digest_compressed) = compress_chunk(&raw_data, cancel_signal)?;
let cow_size =
vabc_algo.map_or(0, |a| a.compressed_size(&raw_data, block_size));
let cow_size = vabc_algo
.map(|a| a.compressed_size(&raw_data, block_size))
.transpose()?
.unwrap_or(0);
let extent = Extent {
start_block: Some(raw_offset / u64::from(block_size)),
@@ -1198,7 +1206,7 @@ pub fn compress_modified_image(
let groups_total = operations.len().div_ceil(OPERATION_GROUP);
let mut bytes_compressed = 0;
let mut context_uncompressed = Context::new(&ring::digest::SHA256);
let mut context_uncompressed = Context::new(&aws_lc_rs::digest::SHA256);
let mut modified_operations = vec![];
// Read the file one group at a time. This allows for some parallelization
+2 -2
View File
@@ -14,6 +14,7 @@ use std::{
sync::atomic::AtomicBool,
};
use aws_lc_rs::digest::Context;
use bstr::ByteSlice;
use liblzma::{
stream::{Check, Stream},
@@ -21,7 +22,6 @@ use liblzma::{
};
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use regex::bytes::Regex;
use ring::digest::Context;
use rsa::RsaPublicKey;
use thiserror::Error;
use tracing::{debug, debug_span, trace, warn, Span};
@@ -1180,7 +1180,7 @@ fn save_boot_image(
};
// Write new boot image. We reuse the existing salt for the digest.
let mut context = Context::new(&ring::digest::SHA256);
let mut context = Context::new(&aws_lc_rs::digest::SHA256);
context.update(&descriptor.salt);
let mut hashing_writer = HashingWriter::new(writer, context);
info.boot_image
+4 -4
View File
@@ -10,8 +10,8 @@ use std::{
},
};
use aws_lc_rs::digest::Context;
use num_traits::ToPrimitive;
use ring::digest::Context;
use crate::util;
@@ -637,7 +637,7 @@ mod tests {
sync::atomic::{AtomicBool, Ordering},
};
use ring::digest::Context;
use aws_lc_rs::digest::Context;
use super::{
CountingReader, CountingWriter, HashingReader, HashingWriter, PSeekFile, ReadDiscardExt,
@@ -713,7 +713,7 @@ mod tests {
#[test]
fn hashing_reader() {
let raw_reader = Cursor::new(b"foobar");
let mut reader = HashingReader::new(raw_reader, Context::new(&ring::digest::SHA256));
let mut reader = HashingReader::new(raw_reader, Context::new(&aws_lc_rs::digest::SHA256));
let mut buf = [0u8; 6];
reader.read_exact(&mut buf[..0]).unwrap();
@@ -730,7 +730,7 @@ mod tests {
#[test]
fn hashing_writer() {
let raw_writer = Cursor::new([0u8; 6]);
let mut writer = HashingWriter::new(raw_writer, Context::new(&ring::digest::SHA256));
let mut writer = HashingWriter::new(raw_writer, Context::new(&aws_lc_rs::digest::SHA256));
writer.write_all(b"").unwrap();
writer.write_all(b"foo").unwrap();
+4 -4
View File
@@ -134,7 +134,7 @@ fn round_trip_root_image() {
// Verify checksum of the output.
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
[
0x3b, 0x01, 0xf6, 0x04, 0x04, 0x6e, 0x6f, 0x60, 0x9c, 0xb0, 0x8b, 0x8a, 0x43, 0xf7,
0x91, 0x2e, 0xc4, 0x1b, 0xc0, 0x7f, 0xa1, 0xe4, 0xe6, 0x59, 0x14, 0x08, 0xbe, 0x83,
@@ -233,7 +233,7 @@ fn round_trip_appended_hash_image() {
// Verify checksum of the output.
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
[
0x91, 0x38, 0x61, 0xc0, 0x68, 0x2a, 0x8b, 0xd8, 0x01, 0xa6, 0xe4, 0x4c, 0x1d, 0x27,
0x93, 0x1b, 0xa4, 0x63, 0xd1, 0xbb, 0xf1, 0x64, 0x05, 0xf2, 0xa1, 0xa0, 0xb3, 0x35,
@@ -341,7 +341,7 @@ fn round_trip_appended_hash_tree_image_fixed_size() {
// Verify checksum of the output.
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
[
0x92, 0xdd, 0x4d, 0xc5, 0xb0, 0x5b, 0x4f, 0x65, 0x97, 0x5a, 0x72, 0x66, 0xde, 0x82,
0xc2, 0x2f, 0x33, 0x86, 0x8b, 0x65, 0x67, 0x80, 0x1d, 0xca, 0xd6, 0x2c, 0xfc, 0xca,
@@ -448,7 +448,7 @@ fn round_trip_appended_hash_tree_image_minimum_size() {
// Verify checksum of the output.
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
[
0xcf, 0x6b, 0x90, 0xcf, 0x77, 0x76, 0x62, 0x12, 0xc2, 0x22, 0xe6, 0xd5, 0x5b, 0xab,
0x82, 0xd8, 0x6c, 0x93, 0xa3, 0x35, 0x5b, 0x77, 0xe0, 0x38, 0x12, 0x48, 0x90, 0x0c,
+1 -1
View File
@@ -49,7 +49,7 @@ fn round_trip(image: &BootImage, sha512: &[u8; 64], expected_version: u32) {
let data = writer.into_inner();
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
sha512,
);
+1 -1
View File
@@ -41,7 +41,7 @@ fn generate_archive() -> Vec<u8> {
let data = writer.into_inner();
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
[
0xb0, 0x51, 0xac, 0x28, 0x6f, 0x78, 0xe2, 0xe7, 0x45, 0xa0, 0x52, 0x7c, 0xff, 0x42,
0x30, 0x55, 0xbd, 0x64, 0x7d, 0x4e, 0xb8, 0xe6, 0x95, 0xe5, 0x9b, 0xd1, 0x13, 0xd6,
+1 -1
View File
@@ -17,7 +17,7 @@ fn round_trip(metadata: &Metadata, sha512: &[u8; 64]) {
let data = writer.into_inner();
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
sha512,
);
+1 -1
View File
@@ -39,7 +39,7 @@ fn round_trip(block_size: u32, crc32: u32, test_chunks: &[TestChunk], sha512: &[
let data = writer.into_inner();
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA512, &data).as_ref(),
sha512,
);
+3 -8
View File
@@ -39,13 +39,7 @@ allow = [
"MIT",
"OpenSSL",
"Unicode-3.0",
]
[[licenses.clarify]]
name = "ring"
expression = "MIT AND ISC AND OpenSSL"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 },
"Zlib",
]
[bans]
@@ -63,6 +57,8 @@ include-workspace = true
bypass = [
# Copies of unmodified crashwrangler objects for old macOS versions.
{ name = "honggfuzz", allow-globs = ["honggfuzz/third_party/mac/CrashReport_*.o"] },
# Only used in tests.
{ name = "libloading", allow-globs = ["tests/*.dll"] }
]
[sources]
@@ -70,5 +66,4 @@ unknown-registry = "deny"
unknown-git = "deny"
allow-git = [
"https://github.com/chenxiaolong/zip",
"https://github.com/trifectatechfoundation/bzip2-rs",
]
+1 -1
View File
@@ -9,12 +9,12 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aws-lc-rs = { version = "1.0.0", default-features = false, features = ["aws-lc-sys"] }
anyhow = "1.0.75"
avbroot = { path = "../avbroot" }
clap = { version = "4.4.1", features = ["derive"] }
ctrlc = "3.4.0"
hex = { version = "0.4.3", features = ["serde"] }
ring = "0.17.0"
rsa = { version = "0.9.6", features = ["hazmat"] }
serde = { version = "1.0.188", features = ["derive"] }
tempfile = "3.8.0"
+10 -10
View File
@@ -50,11 +50,11 @@ data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
[profile.pixel_v4_gki.hashes_streaming]
original = "c00f891f941f3dddb28966f7b07f3acea773bee104dace82b37c2d1341f09422"
patched = "ce9d8ee97828d233809742a5d3f23aa27b042675b1935ca9e3df0592c55788fd"
patched = "6c27ffb07f4497af8539f8283e506066af9417580230c3209c9875fc15d5069d"
[profile.pixel_v4_gki.hashes_seekable]
original = "96a6c366b5de1c3b10d4d6cb4ca503c83ac4cd9ca952a965cceb041990ba7022"
patched = "e7b4609ba7a23609211dcae143bc43f091f286fbbb3a9301c02ee25614d35deb"
patched = "e4fc12523ffc312796b92210bc1e3bbb70dd60797a47daae76c8b5852e48b382"
# Google Pixel 6a
# What's unique: boot (boot v4, no ramdisk) + vendor_boot (vendor v4, 2 ramdisks)
@@ -91,11 +91,11 @@ data.ramdisks = [["init", "otacerts", "first_stage", "dsu_key_dir"], ["dlkm"]]
[profile.pixel_v4_non_gki.hashes_streaming]
original = "4d692bc777b568b0626d3c08d2e6f83f1b472db5ad903486daaec6a78d0cc26e"
patched = "e27673e4f30933710c11d51f0e73849068cbe9bc9f54e6076bdd93f9a5c8ea0a"
patched = "6832ded3e98a14edc8c5ea7284fcea0b958fa710ebf222c27116faec8dfefe2e"
[profile.pixel_v4_non_gki.hashes_seekable]
original = "ea27ecd9718c17b63400b2548680bb3cee93ce63b4fc44ff9654ca0d9c5372a8"
patched = "3456b14e014cf565a808a9e834d9105a23539f07b2c460db19c9384aadbc3b93"
patched = "114f8936e917d7e4a71bc1521adb3c8e676de3a738f8c7c505b86464d20bd95c"
# Google Pixel 4a 5G
# What's unique: boot (boot v3) + vendor_boot (vendor v3)
@@ -133,11 +133,11 @@ data.ramdisks = [["otacerts", "first_stage", "dsu_key_dir"]]
[profile.pixel_v3.hashes_streaming]
original = "f432dc7931520feb238474aa707dd5299747562ffe6129f3f763b5f11ac473ab"
patched = "3850a2e73bd783a1ec4a70c59f37d2374e017c20df7ab4b591182b14d187c18e"
patched = "1f28d9210a17e233cd5da4af55b07db764b19eeab991394514170b405240464f"
[profile.pixel_v3.hashes_seekable]
original = "7d29ecc6780953c22052a576b8dc85066c8667a875e918a786a08ff4545b47d1"
patched = "9f6342940b7cfbeb27b0567f006bb35cbee910ef038ec535403c662d5252ca71"
patched = "27b80c7be9c1e527ea26abe3dabde245c580e6f26ec084204278fbfd81a39f83"
# Google Pixel 4a
# What's unique: boot (boot v2)
@@ -168,9 +168,9 @@ data.type = "vbmeta"
data.deps = ["system"]
[profile.pixel_v2.hashes_streaming]
original = "1b45235b58054009cc496f6c3ee11d3dc16ed5c388c861761e26a6fce83103a0"
patched = "193b2dc70dd465d686f35c7b7f74d2cc1b06a55e48cf5c2e4df0f667e03032fc"
original = "4b7e5675f834ac56bf3459628adfe1425c2346a1c224ee6ea0a3be9f996db254"
patched = "4a7ca99808b4e49a2dc77a620d4fb7d8219b974ab5b3d5b5709084942805dc9a"
[profile.pixel_v2.hashes_seekable]
original = "52284308fae10cbaf09ade14e92f3bbe6149751a42bff15432982fcef8d890ab"
patched = "7ad74ac87ddcaf34938017e6149a646041d70926e31ecda93e156e9397467b3b"
original = "66b44b148b35a8a998214e0ae36470b42ffd6d2974e18cd8fa12cd8af0c98540"
patched = "5ddec4bb56dd78a49fbc49e9ad3de7710b0b008b031ff4dfce8fdc775e753ad1"
+2 -2
View File
@@ -69,7 +69,7 @@ fn hash_file(path: &Path, cancel_signal: &AtomicBool) -> Result<[u8; 32]> {
let raw_reader =
File::open(path).with_context(|| format!("Failed to open for reading: {path:?}"))?;
let buf_reader = BufReader::new(raw_reader);
let context = ring::digest::Context::new(&ring::digest::SHA256);
let context = aws_lc_rs::digest::Context::new(&aws_lc_rs::digest::SHA256);
let mut hashing_reader = HashingReader::new(buf_reader, context);
stream::copy(&mut hashing_reader, io::sink(), cancel_signal)?;
@@ -104,7 +104,7 @@ fn append_avb(
cancel_signal: &AtomicBool,
) -> Result<()> {
let image_size = file.seek(SeekFrom::End(0))?;
let salt = ring::digest::digest(&ring::digest::SHA256, b"avbroot");
let salt = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, b"avbroot");
let descriptors = vec![
if hash_tree {
let mut descriptor = HashTreeDescriptor {