Compare commits

..

17 Commits

Author SHA1 Message Date
Andrew Gunnerson e105efb6d3 Version 3.2.3
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-06-14 18:26:14 -04:00
Andrew Gunnerson 2a7df104ed CHANGELOG.md: Add entry for PR #304
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-06-14 18:17:06 -04:00
Andrew Gunnerson 8e52a9cf8c Add support for cross-compiling to Android
The precompiled binaries are compiled for aarch64 API 31, which should
work for every device that avbroot supports.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-06-14 17:49:15 -04:00
Andrew Gunnerson f9343aa542 Version 3.2.2
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-06-03 19:50:01 -04:00
Andrew Gunnerson 0391d4e2c3 CHANGELOG.md: Add entry for PR #268
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-06-03 18:50:10 -04:00
Andrew Gunnerson c18800dc44 patch/boot: Add entry name/index to zip error messages
`specified file not found in archive` is a useless error message when
there's no additional context.

Issue: #301

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-06-03 18:50:02 -04:00
Andrew Gunnerson 2db90f83c2 Add support for upcoming Magisk Canary 27003
Magisk no longer puts both magisk64 and magisk32 in the ramdisk.
Instead, it just puts a single binary for the target ABI.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-06-03 18:50:01 -04:00
Andrew Gunnerson 34915e256f Merge pull request #300 from bugreportion/master
README.ru.md: update translation
2024-06-01 10:04:50 -04:00
Ivan Katrovsky 61392eb9d6 README.ru.md: update translation
Signed-off-by: Ivan Katrovsky <notbugreporter@proton.me>
2024-06-01 15:11:29 +03:00
Andrew Gunnerson 029cb4264e README.md: Move supported device list to issue tracker
This makes it a bit easier to keep updated and gather user comments.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-31 18:36:39 -04:00
Andrew Gunnerson 98a7fc811d Merge pull request #298 from bugreportion/master
README.ru.md: update translation
2024-05-31 18:25:32 -04:00
Ivan Katrovsky f4f4ec8e0d README.ru.md: update translation
Signed-off-by: Ivan Katrovsky <notbugreporter@proton.me>
2024-06-01 01:12:47 +03:00
Andrew Gunnerson 99b316f55e CHANGELOG.md: Add entry for PR #297
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-30 00:08:58 -04:00
Andrew Gunnerson 50ee90b61a cli/avb: Allow writing new TOML file when packing an image
Previously, there was no way to easily see the values of the recomputed
fields without unpacking the newly built image. This commit adds a new
`--output-info` option to the `avb pack` subcommand to write/overwrite a
new `avb.toml` file.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-30 00:04:05 -04:00
Andrew Gunnerson 2e8bd9f9d4 CHANGELOG.md: Add entry for PR #296
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-29 23:52:42 -04:00
Andrew Gunnerson 3fa96714c4 avb: Add support for building minimally sized image
Previously, for resizable images, the user had to guess an appropriate
size for the final image that could fit all the AVB metadata. This
commit adds a new `--recompute-size` option to the `avb pack` subcommand
to generate a minimally sized image.

Fixes: #294

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-29 23:42:43 -04:00
Andrew Gunnerson 7a2530a199 README.md: Strongly discourage using any device besides Google Pixels
OnePlus seems to be getting worse and worse with some devices not being
recoverable.

Issue: #290

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-29 21:20:24 -04:00
13 changed files with 362 additions and 141 deletions
+35 -4
View File
@@ -19,6 +19,7 @@ jobs:
# https://github.com/rust-lang/rust/issues/78210
RUSTFLAGS: -C strip=symbols -C target-feature=+crt-static
TARGETS: ${{ join(matrix.artifact.targets, ' ') || matrix.artifact.name }}
ANDROID_API: ${{ matrix.artifact.android_api }}
strategy:
fail-fast: false
matrix:
@@ -33,6 +34,12 @@ jobs:
- aarch64-apple-darwin
- x86_64-apple-darwin
combine: lipo
# ubuntu-latest is not 24.04 yet and 22.04's qemu-user-static segfaults.
- os: ubuntu-24.04
name: aarch64-linux-android31
targets:
- aarch64-linux-android
android_api: '31'
steps:
- name: Check out repository
uses: actions/checkout@v4
@@ -40,6 +47,26 @@ jobs:
# For git describe
fetch-depth: 0
- name: Install qemu-user-static
if: ${{ contains(matrix.artifact.name, 'android') }}
shell: bash
run: |
sudo apt-get -y update
sudo apt-get -y install qemu-user-static
- name: Set Android temporary directory
if: ${{ contains(matrix.artifact.name, 'android') }}
shell: bash
run: |
echo "TMPDIR=/tmp" >> "${GITHUB_ENV}"
- name: Install cargo-android
shell: bash
run: |
cargo install \
--git https://github.com/chenxiaolong/cargo-android \
--tag v0.1.1
- name: Get version
id: get_version
shell: bash
@@ -63,7 +90,8 @@ jobs:
shell: bash
run: |
for target in ${TARGETS}; do
cargo clippy --release --workspace --features static \
cargo android \
clippy --release --workspace --features static \
--target "${target}"
done
@@ -71,7 +99,8 @@ jobs:
shell: bash
run: |
for target in ${TARGETS}; do
cargo build --release --workspace --features static \
cargo android \
build --release --workspace --features static \
--target "${target}"
done
@@ -79,7 +108,8 @@ jobs:
shell: bash
run: |
for target in ${TARGETS}; do
cargo test --release --workspace --features static \
cargo android \
test --release --workspace --features static \
--target "${target}"
done
@@ -87,7 +117,8 @@ jobs:
shell: bash
run: |
for target in ${TARGETS}; do
cargo run --release -p e2e --features static \
cargo android \
run --release -p e2e --features static \
--target "${target}" \
-- test -a -c e2e/e2e.toml
done
+16
View File
@@ -7,6 +7,16 @@
to update the actual links at the bottom of the file.
-->
### Version 3.2.3
* Add prebuilt binary for Android (aarch64) ([PR #304])
### Version 3.2.2
* Add new `--recompute-size` option to `avbroot avb pack` to automatically recompute the image size for resizable images ([Discussion #294], [PR #296])
* Add new `--output-info` option to `avbroot avb pack` to write a new `avb.toml` file containing computed values ([PR #297])
* Add support for upcoming Magisk Canary 27003 ([Issue #301], [PR #268])
### Version 3.2.1
* Increase hash tree and FEC size limits to accommodate partition images up to 8 GiB ([Issue #291], [PR #293])
@@ -181,6 +191,7 @@ Behind-the-scenes changes:
[Discussion #195]: https://github.com/chenxiaolong/avbroot/discussions/195
[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
[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
@@ -199,6 +210,7 @@ Behind-the-scenes changes:
[Issue #278]: https://github.com/chenxiaolong/avbroot/issues/278
[Issue #285]: https://github.com/chenxiaolong/avbroot/issues/285
[Issue #291]: https://github.com/chenxiaolong/avbroot/issues/291
[Issue #301]: https://github.com/chenxiaolong/avbroot/issues/301
[PR #130]: https://github.com/chenxiaolong/avbroot/pull/130
[PR #132]: https://github.com/chenxiaolong/avbroot/pull/132
[PR #133]: https://github.com/chenxiaolong/avbroot/pull/133
@@ -277,6 +289,7 @@ Behind-the-scenes changes:
[PR #256]: https://github.com/chenxiaolong/avbroot/pull/256
[PR #257]: https://github.com/chenxiaolong/avbroot/pull/257
[PR #261]: https://github.com/chenxiaolong/avbroot/pull/261
[PR #268]: https://github.com/chenxiaolong/avbroot/pull/268
[PR #276]: https://github.com/chenxiaolong/avbroot/pull/276
[PR #277]: https://github.com/chenxiaolong/avbroot/pull/277
[PR #279]: https://github.com/chenxiaolong/avbroot/pull/279
@@ -284,3 +297,6 @@ Behind-the-scenes changes:
[PR #288]: https://github.com/chenxiaolong/avbroot/pull/288
[PR #289]: https://github.com/chenxiaolong/avbroot/pull/289
[PR #293]: https://github.com/chenxiaolong/avbroot/pull/293
[PR #296]: https://github.com/chenxiaolong/avbroot/pull/296
[PR #297]: https://github.com/chenxiaolong/avbroot/pull/297
[PR #304]: https://github.com/chenxiaolong/avbroot/pull/304
Generated
+4 -4
View File
@@ -109,7 +109,7 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "avbroot"
version = "3.2.1"
version = "3.2.3"
dependencies = [
"anyhow",
"assert_matches",
@@ -534,7 +534,7 @@ dependencies = [
[[package]]
name = "e2e"
version = "3.2.1"
version = "3.2.3"
dependencies = [
"anyhow",
"avbroot",
@@ -628,7 +628,7 @@ dependencies = [
[[package]]
name = "fuzz"
version = "3.2.1"
version = "3.2.3"
dependencies = [
"avbroot",
"honggfuzz",
@@ -2068,7 +2068,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "3.2.1"
version = "3.2.3"
dependencies = [
"anyhow",
"clap",
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "3.2.1"
version = "3.2.3"
license = "GPL-3.0-only"
edition = "2021"
repository = "https://github.com/chenxiaolong/avbroot"
+4
View File
@@ -32,6 +32,10 @@ This subcommand packs a new AVB image from the `avb.toml` file and, for appended
Note that if the image is an appended image and its hash or hash tree descriptor uses an insecure algorithm, like `sha1`, then it will automatically be promoted to `sha256`.
By default, for appended vbmeta images, the output image size will match the size of the original image that was unpacked. This size is specified by the `image_size` field in `avb.toml`. If the image is resizable (eg. `system`), then passing in `--recompute-size` will cause the `image_size` field to be ignored and the smallest possible output file that fits the raw image and AVB metadata will be built. This avoids wasting space if `raw.img` shrunk or allows the packing to work at all if `raw.img` grew. **Do not use this option for non-resizable images** (eg. `boot`) or else the device won't be able to boot.
When packing an image, several of the fields in `avb.toml` may potentially be recomputed. To write a TOML file containing the new values, use `--output-info <output TOML>`. It is safe to overwrite the existing `avb.toml` if desired.
### Repacking an AVB image
```bash
+12 -2
View File
@@ -15,9 +15,9 @@ Having a good understanding of how AVB and A/B OTAs work is recommended prior to
* `payload.bin` exists
* `META-INF/com/android/metadata` (Android 10-11) or `META-INF/com/android/metadata.pb` (Android 12+) exists
* The device must support using a custom public key for the bootloader's root of trust. This is normally done via the `fastboot flash avb_custom_key` command. All Pixel devices with unlockable bootloaders since the Pixel 2 support this. Other devices may support it as well, but there's no easy way to check without just trying it.
* The device must support using a custom public key for the bootloader's root of trust. This is normally done via the `fastboot flash avb_custom_key` command.
* NOTE: Some OnePlus devices have a broken implementation where a custom public key can be set, but the device won't boot despite having proper signatures. Downgrading the bootloader to the version shipped with Android 11 might potentially help. This problem has been reported across multiple OnePlus models ([#186](https://github.com/chenxiaolong/avbroot/issues/186), [#195](https://github.com/chenxiaolong/avbroot/discussions/195), [#212](https://github.com/chenxiaolong/avbroot/issues/212)).
A list of devices known to work can be found in the issue tracker at [#299](https://github.com/chenxiaolong/avbroot/issues/299).
## Patches
@@ -440,6 +440,16 @@ 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`.
### Android cross-compilation
To cross-compile for Android, install [cargo-android](https://github.com/chenxiaolong/cargo-android) and use the `cargo android` wrapper. To make a release build for aarch64, run:
```bash
cargo android build --release --target aarch64-linux-android
```
It is possible to run the tests if the host is running Linux, qemu-user-static is installed, and the executable is built with `RUSTFLAGS=-C target-feature=+crt-static` and `--features static`.
## 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/).
+2 -2
View File
@@ -13,9 +13,9 @@ avbroot – это программа для модификации OTA-обра
* наличие файла `payload.bin` (обычно находится в корне архива)
* наличие файла `META-INF/com/android/metadata` (Android 10-11) или `META-INF/com/android/metadata.pb` (Android 12+)
* Устройство должно поддерживать установку пользовательского публичного ключа для подтверждения статуса доверия загрузчика. Обычно это делается с помощью команды `fastboot flash avb_custom_key`. Все устройства Pixel с разблокируемым загрузчиком, начиная с Pixel 2, поддерживают эту функцию. Другие устройства тоже могут поддерживать её, но достоверно убедиться в этом можно только проверив лично.
* Устройство должно поддерживать установку пользовательского публичного ключа для подтверждения статуса доверия загрузчика. Обычно это производится с помощью команды `fastboot flash avb_custom_key`.
* ПРИМЕЧАНИЕ: Некоторые девайсы от OnePlus имеют некорректную реализацию, где возможна установка кастомного публичного ключа, но устройство все равно не будет загружаться, несмотря на наличие корректной подписи. Понижение загрузчика до версии, поставляемой вместе с Android 11, потенциально может помочь. Уже сообщалось о наличии этой проблемы на нескольких устройствах OnePlus ([#186,](https://github.com/chenxiaolong/avbroot/issues/186) [#195,](https://github.com/chenxiaolong/avbroot/discussions/195) [#212](https://github.com/chenxiaolong/avbroot/issues/212)).
Список девайсов, на которых проверялась совместимость с указанным выше функционалом, находится здесь: [#299.](https://github.com/chenxiaolong/avbroot/issues/299)
## Патчи
+34 -8
View File
@@ -57,16 +57,22 @@ fn read_avb_image(path: &Path) -> Result<(AvbInfo, BufReader<File>)> {
Ok((info, reader))
}
fn write_avb_image(file: PSeekFile, info: &mut AvbInfo) -> Result<()> {
fn write_avb_image(file: PSeekFile, info: &mut AvbInfo, recompute_size: bool) -> Result<()> {
let mut writer = BufWriter::new(file);
if let Some(f) = &mut info.footer {
avb::write_appended_image(&mut writer, &info.header, f, info.image_size)
.context("Failed to write appended AVB image")?;
info.image_size = if let Some(f) = &mut info.footer {
let image_size = if recompute_size {
None
} else {
Some(info.image_size)
};
avb::write_appended_image(&mut writer, &info.header, f, image_size)
.context("Failed to write appended AVB image")?
} else {
avb::write_root_image(&mut writer, &info.header, 4096)
.context("Failed to write root AVB image")?;
}
.context("Failed to write root AVB image")?
};
writer.flush().context("Failed to flush writes")?;
@@ -625,12 +631,16 @@ fn pack_subcommand(cli: &PackCli, cancel_signal: &AtomicBool) -> Result<()> {
sign_or_clear(&mut info, &orig_header, &cli.key)?;
write_avb_image(file, &mut info)?;
write_avb_image(file, &mut info, cli.recompute_size)?;
// We display the info at the very end after both the header and footer are
// updated so that incorrect/incomplete information isn't shown.
display_info(&cli.display, &info);
if let Some(path) = &cli.output_info {
write_info(path, &info)?;
}
Ok(())
}
@@ -659,7 +669,7 @@ fn repack_subcommand(cli: &RepackCli, cancel_signal: &AtomicBool) -> Result<()>
sign_or_clear(&mut info, &orig_header, &cli.key)?;
write_avb_image(file, &mut info)?;
write_avb_image(file, &mut info, false)?;
// We display the info at the very end after both the header and footer are
// updated so that incorrect/incomplete information isn't shown.
@@ -827,12 +837,28 @@ struct PackCli {
#[arg(long, value_name = "FILE", value_parser, default_value = "avb.toml")]
input_info: PathBuf,
/// Path to output AVB info TOML.
///
/// If specified, the AVB info containing all recomputed fields will be
/// written to this file. This can point to the same file as --input-info.
#[arg(long, value_name = "FILE", value_parser)]
output_info: Option<PathBuf>,
/// Path to input raw image.
///
/// Appended AVB images require a raw image.
#[arg(long, value_name = "FILE", value_parser, default_value = "raw.img")]
input_raw: PathBuf,
/// Recompute image size.
///
/// By default, it is assumed that the image has a fixed size specified by
/// the image_size top-level field in the AVB info TOML. If flag is passed,
/// then that field is ignored and the smallest possible output image will
/// be created.
#[arg(long)]
recompute_size: bool,
#[command(flatten)]
key: KeyGroup,
+89 -89
View File
@@ -1,10 +1,10 @@
/*
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
* SPDX-License-Identifier: GPL-3.0-only
*/
use std::{
cmp, fmt,
fmt,
io::{self, Cursor, Read, Seek, SeekFrom, Write},
ops::Range,
str,
@@ -29,8 +29,8 @@ use crate::{
padding,
},
stream::{
self, CountingReader, FromReader, ReadDiscardExt, ReadSeekReopen, ReadStringExt, ToWriter,
WriteSeekReopen, WriteStringExt, WriteZerosExt,
self, CountingReader, CountingWriter, FromReader, ReadDiscardExt, ReadSeekReopen,
ReadStringExt, ToWriter, WriteSeekReopen, WriteStringExt, WriteZerosExt,
},
util,
};
@@ -124,8 +124,10 @@ pub enum Error {
RsaSign(#[source] rsa::Error),
#[error("Failed to RSA verify signature")]
RsaVerify(#[source] rsa::Error),
#[error("{0} byte image size is too small to fit header or footer")]
ImageSizeTooSmall(u64),
#[error("{0} byte image size is too small to fit header")]
TooSmallForHeader(u64),
#[error("{0} byte image size is too small to fit footer")]
TooSmallForFooter(u64),
#[error("Hash tree error")]
HashTree(#[from] hashtree::Error),
#[error("FEC error")]
@@ -1851,97 +1853,95 @@ pub fn load_image(mut reader: impl Read + Seek) -> Result<(Header, Option<Footer
Ok((header, footer, image_size))
}
/// Write a vbmeta header to the specified writer. If a footer is specified, it
/// will be used as the basis of the newly written footer, with the original
/// image size, vbmeta header offset, and vbmeta header size fields updated
/// appropriately.
///
/// The writer must not have an existing vbmeta header or footer.
fn write_image_internal(
mut writer: impl Write + Seek,
header: &Header,
footer: Option<&mut Footer>,
image_size: Option<u64>,
block_size: u64,
) -> Result<()> {
let eof_image_size = if footer.is_some() {
match header.appended_descriptor()? {
AppendedDescriptorRef::HashTree(d) => d
.image_size
.checked_add(d.tree_size)
.and_then(|s| s.checked_add(d.fec_size))
.ok_or_else(|| Error::FieldOutOfBounds("eof_image_size"))?,
AppendedDescriptorRef::Hash(d) => d.image_size,
}
} else {
0
};
writer.seek(SeekFrom::Start(eof_image_size))?;
// The header must be block-aligned.
let vbmeta_offset = if block_size > 0 {
let padding_size = padding::write_zeros(&mut writer, block_size)?;
eof_image_size
.checked_add(padding_size)
.ok_or_else(|| Error::FieldOutOfBounds("vbmeta_offset"))?
} else {
eof_image_size
};
header.to_writer(&mut writer)?;
let vbmeta_end = writer.stream_position()?;
if let Some(s) = image_size {
let footer_space = if footer.is_some() {
cmp::max(block_size, Footer::SIZE as u64)
} else {
0
};
if s < footer_space || vbmeta_end > s - footer_space {
return Err(Error::ImageSizeTooSmall(s));
}
}
if block_size > 0 {
padding::write_zeros(&mut writer, block_size)?;
}
if let Some(f) = footer {
let footer_offset = image_size.unwrap() - Footer::SIZE as u64;
writer.seek(SeekFrom::Start(footer_offset))?;
let original_image_size = match header.appended_descriptor()? {
AppendedDescriptorRef::HashTree(d) => d.image_size,
AppendedDescriptorRef::Hash(d) => d.image_size,
};
f.original_image_size = original_image_size;
f.vbmeta_offset = vbmeta_offset;
f.vbmeta_size = vbmeta_end - vbmeta_offset;
f.to_writer(&mut writer)?;
}
Ok(())
}
/// Write a vbmeta header to the specified writer. This is meant for writing
/// vbmeta partition images, not appended vbmeta images. The writer must refer
/// to an empty file.
pub fn write_root_image(writer: impl Write + Seek, header: &Header, block_size: u64) -> Result<()> {
write_image_internal(writer, header, None, None, block_size)
/// to an empty file. Returns the size of the new file.
pub fn write_root_image(writer: impl Write, header: &Header, block_size: u64) -> Result<u64> {
let mut counting_writer = CountingWriter::new(writer);
header.to_writer(&mut counting_writer)?;
padding::write_zeros(&mut counting_writer, block_size)?;
Ok(counting_writer.stream_position()?)
}
/// Write a vbmeta header and footer to the specified writer. This is meant for
/// appending vbmeta data to existing partition data, not writing vbmeta images.
/// If `image_size` is specified, then the writer is guaranteed to not grow
/// past that size and an error is returned if the header and footer won't fit.
/// Otherwise, the writer will grow to the necessary size. Returns the size of
/// the new file.
pub fn write_appended_image(
writer: impl Write + Seek,
mut writer: impl Write + Seek,
header: &Header,
footer: &mut Footer,
image_size: u64,
) -> Result<()> {
image_size: Option<u64>,
) -> Result<u64> {
// avbtool hardcodes a 4096 block size for appended non-sparse images.
write_image_internal(writer, header, Some(footer), Some(image_size), 4096)
const BLOCK_SIZE: u64 = 4096;
// Logical image size, excluding the AVB header and footer.
let logical_image_size = match header.appended_descriptor()? {
AppendedDescriptorRef::HashTree(d) => d
.image_size
.checked_add(d.tree_size)
.and_then(|s| s.checked_add(d.fec_size))
.ok_or_else(|| Error::FieldOutOfBounds("logical_image_size"))?,
AppendedDescriptorRef::Hash(d) => d.image_size,
};
writer.seek(SeekFrom::Start(logical_image_size))?;
// The header start offset must be block aligned.
let header_offset = {
let padding_size = padding::write_zeros(&mut writer, BLOCK_SIZE)?;
logical_image_size
.checked_add(padding_size)
.ok_or_else(|| Error::FieldOutOfBounds("header_offset"))?
};
// The header lives at the beginning of the empty space.
let mut header_buf = Cursor::new(Vec::new());
header.to_writer(&mut header_buf)?;
let header_size = header_buf.stream_position()?;
let header_padding = padding::write_zeros(&mut header_buf, BLOCK_SIZE)?;
let header_end_padded = header_offset
.checked_add(header_size)
.and_then(|s| s.checked_add(header_padding))
.ok_or_else(|| Error::FieldOutOfBounds("header_end_padded"))?;
if let Some(s) = image_size {
if header_end_padded > s {
return Err(Error::TooSmallForHeader(s));
}
}
writer.write_all(&header_buf.into_inner())?;
// The footer lives in its own separate block at the end of the empty space.
let footer_end = if let Some(s) = image_size {
if s - header_end_padded < BLOCK_SIZE {
return Err(Error::TooSmallForFooter(s));
}
s
} else {
header_end_padded
.checked_add(BLOCK_SIZE)
.ok_or_else(|| Error::FieldOutOfBounds("footer_end"))?
};
let footer_offset = footer_end - Footer::SIZE as u64;
writer.seek(SeekFrom::Start(footer_offset))?;
footer.original_image_size = match header.appended_descriptor()? {
AppendedDescriptorRef::HashTree(d) => d.image_size,
AppendedDescriptorRef::Hash(d) => d.image_size,
};
footer.vbmeta_offset = header_offset;
footer.vbmeta_size = header_size;
footer.to_writer(&mut writer)?;
Ok(footer_end)
}
+49 -22
View File
@@ -70,7 +70,11 @@ pub enum Error {
#[error("XZ stream error")]
XzStream(#[from] liblzma::stream::Error),
#[error("Zip error")]
Zip(#[from] ZipError),
Zip(#[source] ZipError),
#[error("Zip error for entry name: {0:?}")]
ZipEntryName(String, #[source] ZipError),
#[error("Zip error for entry index #{0}")]
ZipEntryIndex(usize, #[source] ZipError),
#[error("I/O error")]
Io(#[from] io::Error),
#[error("File I/O error")]
@@ -157,6 +161,13 @@ impl MagiskRootPatcher {
const VER_XZ_BACKUP: Range<u32> =
26403..Self::VERS_SUPPORTED[Self::VERS_SUPPORTED.len() - 1].end;
const ZIP_LIBMAGISK: &'static str = "lib/arm64-v8a/libmagisk.so";
const ZIP_LIBMAGISK32: &'static str = "lib/armeabi-v7a/libmagisk32.so";
const ZIP_LIBMAGISK64: &'static str = "lib/arm64-v8a/libmagisk64.so";
const ZIP_MAGISKINIT: &'static str = "lib/arm64-v8a/libmagiskinit.so";
const ZIP_STUB: &'static str = "assets/stub.apk";
const ZIP_UTIL_FUNCTIONS: &'static str = "assets/util_functions.sh";
pub fn new(
path: &Path,
preinit_device: Option<&str>,
@@ -208,8 +219,10 @@ impl MagiskRootPatcher {
fn get_version(path: &Path) -> Result<u32> {
let reader = File::open(path).map_err(|e| Error::File(path.to_owned(), e))?;
let reader = BufReader::new(reader);
let mut zip = ZipArchive::new(reader)?;
let entry = zip.by_name("assets/util_functions.sh")?;
let mut zip = ZipArchive::new(reader).map_err(Error::Zip)?;
let entry = zip
.by_name(Self::ZIP_UTIL_FUNCTIONS)
.map_err(|e| Error::ZipEntryName(Self::ZIP_UTIL_FUNCTIONS.to_owned(), e))?;
let mut entry = BufReader::new(entry);
let mut line = String::new();
@@ -384,7 +397,7 @@ impl BootImagePatch for MagiskRootPatcher {
fn patch(&self, boot_image: &mut BootImage, cancel_signal: &AtomicBool) -> Result<()> {
let zip_reader =
File::open(&self.apk_path).map_err(|e| Error::File(self.apk_path.clone(), e))?;
let mut zip = ZipArchive::new(BufReader::new(zip_reader))?;
let mut zip = ZipArchive::new(BufReader::new(zip_reader)).map_err(Error::Zip)?;
// Load the first ramdisk. If it doesn't exist, we have to generate one
// from scratch.
@@ -413,7 +426,9 @@ impl BootImagePatch for MagiskRootPatcher {
// Add magiskinit.
{
let mut zip_entry = zip.by_name("lib/arm64-v8a/libmagiskinit.so")?;
let mut zip_entry = zip
.by_name(Self::ZIP_MAGISKINIT)
.map_err(|e| Error::ZipEntryName(Self::ZIP_MAGISKINIT.to_owned(), e))?;
let mut data = vec![];
zip_entry.read_to_end(&mut data)?;
@@ -424,28 +439,33 @@ impl BootImagePatch for MagiskRootPatcher {
));
}
// 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",
b"overlay.d/sbin/magisk32.xz",
);
xz_files.insert(
"lib/arm64-v8a/libmagisk64.so",
b"overlay.d/sbin/magisk64.xz",
);
if zip.file_names().any(|n| n == Self::ZIP_LIBMAGISK) {
// Newer Magisk versions only include a single binary for the target
// ABI in the ramdisk. fb5ee86615ed3df830e8538f8b39b1b133caea34.
debug!("Single libmagisk");
xz_files.insert(Self::ZIP_LIBMAGISK, b"overlay.d/sbin/magisk.xz");
} else {
// Older Magisk versions include the 64-bit binary and, optionally,
// the 32-bit binary if the device supports it. We unconditionally
// include the magisk32 because the boot image itself doesn't have
// sufficient information to determine if a device is 64-bit only.
debug!("Split libmagisk32/libmagisk64");
xz_files.insert(Self::ZIP_LIBMAGISK32, b"overlay.d/sbin/magisk32.xz");
xz_files.insert(Self::ZIP_LIBMAGISK64, b"overlay.d/sbin/magisk64.xz");
}
// Add stub apk, which only exists after Magisk commit
// ad0e6511e11ebec65aa9b5b916e1397342850319.
if zip.file_names().any(|n| n == "assets/stub.apk") {
if zip.file_names().any(|n| n == Self::ZIP_STUB) {
debug!("Magisk stub found");
xz_files.insert("assets/stub.apk", b"overlay.d/sbin/stub.xz");
xz_files.insert(Self::ZIP_STUB, b"overlay.d/sbin/stub.xz");
}
for (source, target) in xz_files {
let reader = zip.by_name(source)?;
let reader = zip
.by_name(source)
.map_err(|e| Error::ZipEntryName(source.to_owned(), e))?;
let buf = Self::xz_compress(reader, cancel_signal)?;
entries.push(CpioEntry::new_file(target, 0o644, CpioEntryData::Data(buf)));
@@ -564,10 +584,12 @@ impl OtaCertPatcher {
continue;
};
let mut zip = ZipArchive::new(Cursor::new(&data))?;
let mut zip = ZipArchive::new(Cursor::new(&data)).map_err(Error::Zip)?;
for index in 0..zip.len() {
let zip_entry = zip.by_index(index)?;
let zip_entry = zip
.by_index(index)
.map_err(|e| Error::ZipEntryIndex(index, e))?;
if !zip_entry.name().ends_with(".x509.pem") {
debug!("Skipping invalid entry path: {}", zip_entry.name());
continue;
@@ -1184,7 +1206,12 @@ pub fn patch_boot_images<'a>(
info.header.sign(key)?;
}
avb::write_appended_image(writer, &info.header, &mut info.footer, info.image_size)?;
avb::write_appended_image(
writer,
&info.header,
&mut info.footer,
Some(info.image_size),
)?;
Ok(())
})
+2 -2
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
* SPDX-License-Identifier: GPL-3.0-only
*/
@@ -207,7 +207,7 @@ pub fn patch_system_image(
}
let writer = output.reopen_boxed()?;
avb::write_appended_image(writer, &header, &mut footer, image_size)?;
avb::write_appended_image(writer, &header, &mut footer, Some(image_size))?;
let AppendedDescriptorMut::HashTree(descriptor) = header.appended_descriptor_mut()? else {
return Err(Error::NoHashTreeDescriptor);
+112 -5
View File
@@ -228,7 +228,7 @@ fn round_trip_appended_hash_image() {
assert_eq!(header.verify().unwrap().unwrap(), key.to_public_key());
// Write vbmeta structures.
avb::write_appended_image(&mut writer, &header, &mut footer, image_size).unwrap();
avb::write_appended_image(&mut writer, &header, &mut footer, Some(image_size)).unwrap();
let data = writer.into_inner();
// Verify checksum of the output.
@@ -254,8 +254,8 @@ fn round_trip_appended_hash_image() {
}
#[test]
fn round_trip_appended_hash_tree_image() {
let image_size = 28672;
fn round_trip_appended_hash_tree_image_fixed_size() {
let image_size = 32768;
let raw_data: [u8; 8192] = repeat_array(b"foobar");
let mut header = Header {
required_libavb_version_major: 1,
@@ -334,7 +334,114 @@ fn round_trip_appended_hash_tree_image() {
assert_eq!(header.verify().unwrap().unwrap(), key.to_public_key());
// Write vbmeta structures.
avb::write_appended_image(&mut writer, &header, &mut footer, image_size).unwrap();
avb::write_appended_image(&mut writer, &header, &mut footer, Some(image_size)).unwrap();
let mut data = Vec::new();
writer.rewind().unwrap();
writer.read_to_end(&mut data).unwrap();
// Verify checksum of the output.
assert_eq!(
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
[
0xb5, 0x56, 0x65, 0x81, 0x5a, 0x16, 0x65, 0xa9, 0xa6, 0xc6, 0x9e, 0x41, 0x89, 0x9f,
0xe9, 0xbc, 0xea, 0x59, 0x4d, 0x14, 0x8a, 0x9e, 0x2b, 0x13, 0xa0, 0x3a, 0x8e, 0xd4,
0x59, 0xcd, 0x74, 0xe7, 0x99, 0xbd, 0xa3, 0x58, 0x4b, 0x84, 0xf2, 0x04, 0xe2, 0x12,
0x48, 0xfe, 0x4f, 0x67, 0x1f, 0x2a, 0xaa, 0x22, 0x51, 0x19, 0x83, 0x95, 0xa8, 0x03,
0xf5, 0x87, 0x12, 0x05, 0x8e, 0x14, 0xd9, 0xbd
],
);
// Parse the generated image.
let mut reader = Cursor::new(&data);
let (new_header, new_footer, new_image_size) = avb::load_image(&mut reader).unwrap();
let new_footer = new_footer.unwrap();
assert_eq!(new_header, header);
assert_eq!(new_footer, footer);
assert_eq!(new_image_size, image_size);
}
#[test]
fn round_trip_appended_hash_tree_image_minimum_size() {
let raw_data: [u8; 8192] = repeat_array(b"foobar");
let mut header = Header {
required_libavb_version_major: 1,
required_libavb_version_minor: 0,
algorithm_type: AlgorithmType::Sha256Rsa4096,
hash: vec![], // autogenerated
signature: vec![], // autogenerated
public_key: vec![], // autogenerated
public_key_metadata: vec![
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f,
],
descriptors: vec![
Descriptor::Property(PropertyDescriptor {
key: "foobar".to_owned(),
value: b"Invalid UTF-8: \xFF".to_vec(),
}),
Descriptor::HashTree(HashTreeDescriptor {
dm_verity_version: 1,
image_size: raw_data.len() as u64,
tree_offset: 0, // autogenerated
tree_size: 0, // autogenerated
data_block_size: 4096,
hash_block_size: 4096,
fec_num_roots: 2,
fec_offset: 0, // autogenerated
fec_size: 0, // autogenerated
hash_algorithm: "sha256".to_owned(),
partition_name: "vbmeta_appended_hash_tree".to_owned(),
salt: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef].repeat(4),
root_digest: vec![], // autogenerated
flags: 0,
reserved: repeat_array(&[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
}),
],
rollback_index: 1677974400,
flags: 0,
rollback_index_location: 0,
release_string: repeat_str("MaxLength", 48),
reserved: repeat_array(&[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
};
let mut footer = Footer {
version_major: 1,
version_minor: 0,
original_image_size: 0, // autogenerated
vbmeta_offset: 0, // autogenerated
vbmeta_size: 0, // autogenerated
reserved: repeat_array(&[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
};
let mut writer = SharedCursor::default();
let cancel_signal = AtomicBool::new(false);
// Write the raw partition data.
writer.write_all(&raw_data).unwrap();
// Generate and write the hash tree and FEC data.
match header.appended_descriptor_mut().unwrap() {
AppendedDescriptorMut::HashTree(d) => {
d.update(&writer, &writer, None, &cancel_signal).unwrap();
}
AppendedDescriptorMut::Hash(_) => panic!("Expected hash tree descriptor"),
}
// Verify the hash tree and FEC data.
match header.appended_descriptor_mut().unwrap() {
AppendedDescriptorMut::HashTree(d) => {
d.verify(&writer, &cancel_signal).unwrap();
}
AppendedDescriptorMut::Hash(_) => panic!("Expected hash tree descriptor"),
}
// Sign the header.
let key = get_test_key();
header.sign(&key).unwrap();
assert_eq!(header.verify().unwrap().unwrap(), key.to_public_key());
// Write vbmeta structures.
avb::write_appended_image(&mut writer, &header, &mut footer, None).unwrap();
let mut data = Vec::new();
writer.rewind().unwrap();
writer.read_to_end(&mut data).unwrap();
@@ -358,5 +465,5 @@ fn round_trip_appended_hash_tree_image() {
assert_eq!(new_header, header);
assert_eq!(new_footer, footer);
assert_eq!(new_image_size, image_size);
assert_eq!(new_image_size, 28672);
}
+2 -2
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
* SPDX-FileCopyrightText: 2023 Pascal Roeleven
* SPDX-License-Identifier: GPL-3.0-only
*/
@@ -196,7 +196,7 @@ fn append_avb(
// Give enough free space for changes from patching.
.max(1024 * 1024);
avb::write_appended_image(file, &header, &mut footer, full_image_size)?;
avb::write_appended_image(file, &header, &mut footer, Some(full_image_size))?;
Ok(())
}