40 Commits

Author SHA1 Message Date
Andrew Gunnerson f84df86ef5 Version 3.2.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-24 18:27:38 -04:00
Andrew Gunnerson f479fe1a08 Version 3.2.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-18 21:19:04 -04:00
Andrew Gunnerson d6ac94c430 Update all dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-18 17:25:26 -04:00
Andrew Gunnerson e9ba770a15 Switch to my fork of the bzip2-rs library
This includes a fix for yet another infinite-loop-on-drop bug in the
bzip2::write::BzDecoder implementation. This could be easily triggered
when interrupting an avbroot command while it is extracting a bzip2
compressed payload.bin chunk.

Fixes: #285

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-18 16:46:58 -04:00
Andrew Gunnerson 3940b31acb Version 3.1.3
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-05-02 19:19:43 -04:00
Andrew Gunnerson 46662796a0 Version 3.1.2
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-04-27 17:47:09 -04:00
Andrew Gunnerson 0bad6f8c6f Version 3.1.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-03-05 21:59:41 -05:00
Andrew Gunnerson 10370692d0 Version 3.1.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:51:51 -05:00
Andrew Gunnerson 82fc1336f7 Update all dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:32:59 -05:00
Andrew Gunnerson c3b073cece Add tracing support
Instead of println'ing everything, this commit switches the code base to
using the tracing library. There are now proper log levels and multiple
logging output formats. A bunch of new debug and trace-level messages
have also been added to help with future troubleshooting.

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

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2024-02-03 18:16:57 -05:00
Andrew Gunnerson 0a68966594 Switch from xz2 to liblzma
liblzma is a maintained fork of xz2. We can now get rid of our own fork
of xz2.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-31 16:20:30 -05:00
Andrew Gunnerson a134d7f889 Remove oemunlockonboot module
The module has been split out into another repo [1] so that it can be
versioned separately. It now also supports Magisk's automatic update
mechanism.

Closes: #235

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

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-30 19:16:05 -05:00
Andrew Gunnerson 833ca931e8 Version 3.0.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-29 20:57:22 -05:00
Andrew Gunnerson b5ad9dddf6 Update all dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-29 20:44:12 -05:00
Andrew Gunnerson 3a3582ce4c e2e: Switch to using mock OTAs for testing
This commit replaces the previous approach of patching real OTAs with
patching mock OTAs. The motivation for this change is to make it
possible to test the system partition otacerts.zip patching without
needing to download huge files. Adding the system image to the stripped
OTAs would increase the file size by an order of magnitude.

The mock OTAs are generated from a set of profiles defined in e2e.toml.
The four included profiles are meant to mimic the OTAs used for testing
before:

* pixel_v4_gki     ~= cheetah
* pixel_v4_non_gki ~= bluejay
* pixel_v3         ~= bramble
* pixel_v2         ~= sunfish

There is no equivalent profile for ossi because newer OnePlus devices no
longer support custom signing keys properly.

The mock OTAs are perfectly valid, structure and signature-wise. They
just don't include any real partition data where possible. They are
initially signed with a different set of keys to ensure that the changes
made by the patching process are actually visible.

With how small the mock OTAs are, testing every profile only takes about
two seconds. Thus, the Github Actions workflow was adjusted to just run
e2e in the same job as the build.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-29 16:07:57 -05:00
Andrew Gunnerson 17504a7f81 Add support for replacing otacerts.zip in the system image
Previously, overriding otacerts.zip in the system partition required the
user to flash a Magisk/KernelSU module that would bind mount over the
file during boot. While this worked well enough, it's insufficient for
unrooted setups, which has become more important since unrooting is the
only safe way to use the new OEM repair mode feature. With the stock
otacerts.zip, the OEM's default OTA updater app could run and install an
OS upgrade that's not signed by the user's key.

With this commit, the raw otacerts.zip bytes in the system partition are
directly replaced with a new zip that contains the user's certificate.
This method was inspired by @pascallj's comment in #216 suggesting
intentionally corrupting the otacerts.zip data in the filesystem.

Because avbroot does not have filesystem parsers for ext4/f2fs/erofs, we
rely on a heuristic-based search on the raw filesystem image. The file
is always smaller than one block (which is at least 4096 bytes on all
known devices), so the file data is stored contiguously on disk and in
the case of erofs, won't be compressed. None of the three filesystems
are copy-on-write and thus, have no filesystem-level data checksums. For
the dm-verity layer one level up, avbroot already knows how to recompute
the hash tree and FEC data.

To ensure that there are no false positives, any match that the search
finds must correctly parse as a valid zip and every entry within the zip
must have a filename that ends in .x509.pem. This matches what
update_engine expects from a proper otacerts.zip file.

Since the new approach is doing a raw search and replace, the old and
new files must have the same size. When the new zip is smaller, null
bytes are added to the zip archive comment field to pad to the correct
size. When the new zip is larger, avbroot will attempt the following to
try and make the file size smaller:

1. Enable zip deflate compression
2. Strip the X.509 signature from the certificate
3. Clear out the issuer RDN sequence from the certificate
4. Clear out the subject RDN sequence from the certificate

The latter three changes work because Android never performs any PKI
operations with the certificate. There is no CA certificate chain. The
X.509 certificate file is nothing more than a way to transport an RSA
public key.

avbroot requires the user's key to be RSA 4096. If the original zip had
the same key size, then none of these shrinking methods are needed. If
it contained an RSA 2048 key, then the first two modifications are
usually sufficient. The latter two modifications should only be needed
if the user picked a really long subject value when generating the
certificate.

With these new changes, the OTA patching time will approximately double
on a system with an SSD and modern CPU. This is dominated by the time it
takes to XZ-compress the system partition image. The compression is
already parallelized and scales linearly with the number of cores.
There's likely not much more that can be done to further speed this up.

Finally, these new changes are currently excluded from the e2e tests
because including the system partition in the stripped OTAs would
increase the file size by an order of magnitude. This could potentially
be solved in the future by generating our own small OTAs to use for
testing instead of running against real device OTAs.

Fixes: #225

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-12-24 18:23:03 -05:00
Andrew Gunnerson a7c872be3e Version 2.3.3
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-11-15 18:59:43 -05:00
Andrew Gunnerson 2a293147b1 Version 2.3.2
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-11-14 19:19:15 -05:00
Andrew Gunnerson cb62996b9f Version 2.3.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-11-06 16:59:49 -05:00
Andrew Gunnerson bde8dbfbcd Version 2.3.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-27 18:10:51 -04:00
Andrew Gunnerson 9d63c9759d Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-27 17:44:25 -04:00
Andrew Gunnerson dac4e8d328 Version 2.2.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-04 14:58:01 -04:00
Andrew Gunnerson 09048a00d2 Update ring to 0.17.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-10-03 20:51:16 -04:00
Andrew Gunnerson 4651b755ba Switch to prost for protobuf encoding/decoding
There are several things we had to work around with quick-protobuf, like
forcing no_std mode to use BTreeMaps and avoiding helper functions to
read and write non-size-delimited messages. In addition, the pb-rs code
generator doesn't support adding #[derive]s to enums and the existing
support for adding derives to structs is broken due to incorrect string
concatenation.

Prost doesn't have these limitations and bugs. I originally avoided it
because prost_build required the external `protoc` binary, but now that
the protox library exists, the code generation can be done entirely in
Rust without external tools.

Prost also fully supports adding custom attributes to structs, enums,
and fields. This will be helpful for future payload unpack and pack
commands where the payload manifest would have to be serialized to TOML.

The e2e checksums had to be updated because prost's serialized bytes on
the wire differ from quick-protobuf, despite having the same semantic
meaning. Since all checksums need to be updated anyway, the Magisk apk
and OTA images have all been updated to the latest versions.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-30 22:27:46 -04:00
Andrew Gunnerson e72b55dc57 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-30 19:23:30 -04:00
Andrew Gunnerson 61129e8ec7 Version 2.1.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-28 21:53:15 -04:00
Andrew Gunnerson 26a3bafc3d Use handle-based directory operations instead of path-based
This prevents avbroot writing to locations where it shouldn't due to
manipulation from external processes. For example, replacing the output
directory for `avbroot ota extract` with a symlink.

Regardless of what symlinks or other changes an external process does to
the directory, the cap-std library will use the appropriate `openat2()`
options (or equivalent) to prevent writes outside of the directory.

Fixes: #166

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-28 21:26:35 -04:00
Andrew Gunnerson 5b22d7b603 Add initial honggfuzz fuzzing infrastructure
This initially includes fuzzers for the AVB and boot image parsers. The
initial input corpus are the same test files we use for the round trip
tests.

Issue: #160

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-27 19:15:42 -04:00
Andrew Gunnerson dff2fff028 Version 2.1.0
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-19 23:56:58 -04:00
Andrew Gunnerson 1d08ffa527 Switch to bstr for escaping mostly UTF-8 binary data
bstr is Unicode-aware and won't split multi-byte codepoints into
multiple `\x##`.

This commit also adds an `escape` module for use with serde so we can
serialize mostly UTF-8 binary data into human-readable files, like TOML.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-18 21:10:47 -04:00
Andrew Gunnerson 335c04711e Add support for AVB FEC
This commit adds initial support for the FEC (forward error correction)
used by dm-verity. The new `avbroot fec` commands operate on FEC data
stored in AOSP's standalone FEC file format. It is compatible with
AOSP's `fec` tool.

Issue: #145

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-18 15:57:09 -04:00
Andrew Gunnerson d0d51f562c e2e: Switch to attohttpc for HTTP downloads
We don't use async for anything else, so switching to a synchronous HTTP
library lets us get rid of the entire async ecosystem and removes 47
packages from e2e's dependency tree.

The only downside is that ^C worst case takes 5 seconds (TCP connect and
read timeouts) or whatever the OS's DNS lookup timeout is. That's good
enough for a test suite.

Performance wise, e2e still easily saturates a gigabit internet
connection.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-09 23:34:48 -04:00
Andrew Gunnerson f31c4134e4 Version 2.0.3
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-07 14:01:00 -04:00
Andrew Gunnerson 26142b0271 Upgrade xz to 5.4.4 and enable all encoders and decoders
When using the xz2/static feature, the xz2 crate uses a bundled version
of xz 5.2 and doesn't enable all of the available encoders and decoders.
This prevents certain payload data from being decompressed.

Fixes: #138

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-07 13:40:34 -04:00
Andrew Gunnerson 65979beff4 Version 2.0.2
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-06 23:16:01 -04:00
Andrew Gunnerson 4337170f57 Version 2.0.1
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-09-04 17:11:28 -04:00
Andrew Gunnerson a25d9a9eb4 Update dependencies
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-08-31 20:49:31 -04:00
Andrew Gunnerson 44d62f5147 Add release management tasks
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-08-30 19:23:07 -04:00
Andrew Gunnerson c6ac508a50 Work around upstream bzip2 infinite loop issue
There's an upstream bug that causes an infinite loop in the
`write::BzDecoder` destructor if the decoder is fed invalid data. While
this never happens during normal operation, it is possible to run into
this by running `ota extract` against a `--stripped` OTA file.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-08-29 19:23:50 -04:00
Andrew Gunnerson 8549fa1dfc avbroot 2.0: Rewrite in Rust
Why?
----

It was always my intention to write avbroot in a compiled language.
Python was a stop-gap solution since it was possible to use the various
tools and parsers from AOSP to make the initial prototyping and
implementation easier. However, doing so required a whole lot of hacks
since nearly all of the Python modules we use were intended to be used
as executables, not libraries, and they were definitely not meant to be
used outside of AOSP's code base.

Although the dependencies on AOSP code have been reduced over time,
working on the Python code is still frustrating. The majority of the
modules we use from both the standard library and external dependencies
are lacking type annotations. All of the Python language servers and
type checker tools I've used choked on them. There have been serveral
avbroot bugs in the past that wouldn't have happened with any
statically typed language.

The catalyst for me working on this recently was dealing with some
python-protobuf versions that wouldn't work with AOSP's pregenerated
protobuf bindings. When parsing protobuf messages, it would fail
with obscure runtime type errors. I need my projects to not feel
frustrating or else I'll just get burnt out.

Hence, the Rust rewrite. With fewer hacks this time! avbroot no longer
has any dependencies on external tools like openssl. I'll be providing
precompiled binaries for the three major desktop OS's, built by GitHub
Actions. avbroot will also be versioned now, starting at 2.0.0.

Whats new?
----------

* A new `avbroot ota verify` subcommand has been added to check that all
  OTA and AVB related components have been properly hashed and signed.
  This works for all OTA images, including stock ones.
* A couple new `avbroot avb` subcommands have been added for dumping
  vbmeta header/footer information and verifying AVB signatures. These
  are roughly equivalent to avbtool's `info_image` and `verify_image`
  subcommands, though avbroot is about an order of magnitude faster than
  the latter.
* A new set of `avbroot boot` subcommands have been added for packing
  and unpacking boot images. It supports Android v0-v4 images and vendor
  v3-v4 images. Repacking is lossless even when using deprecated fields,
  like the boot image v4 VTS signature.
* A new `avbroot ramdisk` subcommand has been added for inspecting
  the CPIO structure of ramdisks.
* A new set of `avbroot key` subcommands have been added for generating
  signing keys so that it's no longer necessary to install openssl and
  avbtool (though of course, keys generated by other tools remain fully
  compatible).
* Since avbroot has a ton of CLI options, a new `avbroot completion`
  subcommand has been added for generating tab-completion configs for
  various shells (eg. bash, zsh, fish, powershell).

What was removed?
-----------------

Nothing :) The `patch` and `extract` subcommands have been moved under
`avbroot ota` and the `magisk-info` subcommand has been moved under
`avbroot boot`, but there are compatibility shims in place to keep all
the old commands working.

The command-line interface will remain backwards compatible for as long
as possible, even with new major releases. The Rust API, however, has no
backwards compatibility guarantees. I currently don't intend for
avbroot's "library" components to be used anywhere outside of Custota
and avbroot itself.

Performance
-----------

Due to having better access to low-level APIs (especially `pread` and
`pwrite`), nearly everything that can be multithreaded in avbroot is now
multithreaded. In addition, during the patching operation, everything
is done entirely in memory without temp files and the maximum memory
usage is still about 100MB lower than with the Python implementation.

The new implementation is bottlenecked by how fast a single CPU core can
calculate 3 SHA256 hashes of overlapping regions spanning the majority
of the OTA file. About 90% of the CPU time is spent calculating SHA256
hashes and another 5% or so performing XZ-compression.

Some numbers:

* Patching should take roughly 40%-70% of the time it took before.
* Extracting with `--all` should take roughly 10%-30% of the time it
  took before.

Folks with x86_64 CPUs supporting SHA-NI extensions (eg. Intel 11th gen
and newer) should see even bigger improvements.

Reproducibility
---------------

The new implementation's output files are bit-for-bit identical when the
inputs are the same. However, they do not exactly match what the Python
implementation produced.

* The zip entries, aside from `metadata` and `metadata.pb`, are written
  in sorted order.
* All zip entries are stored without compression.
* All zip entries are stored without additional metadata (eg.
  modification timestamp).
* The OTA certificate, both in the OTA zip and in the recovery ramdisk's
  `otacerts.zip`, goes through deserialization + serialization before
  being written. Text in the certificate file before the header and
  after the footer will be stripped out.
* The protobuf structures (payload header and OTA metadata) are
  serialized differently. Protobuf has more than one way to encode the
  same messages "on the wire". The Rust quick_protobuf library
  serializes messages a bit differently than python-protobuf, but the
  outputs are mutually compatible.
* XZ compression of modified partition images in the payload is now done
  at compression level 0 instead of 6. This reduces the patching time by
  several seconds at the cost of a couple MiB increase in file size.
* Ramdisks are now compressed with standard LZ4 instead of LZ4HC (high
  compression mode). For our use case, the difference is <100 KiB, but
  using standard LZ4 allows us to use a pure-Rust LZ4 library and makes
  the compression step much faster.
* Older ramdisks compressed with gzip are slightly different due to a
  different gzip implementation being used (flate2 vs. zlib). The two
  implementations structure the gzip frames slightly differently, but
  the output is identical when decompressed.
* Magisk's config file in the ramdisk (`.backup/.magisk`) will have the
  `SHA1` field set to all zeros. This allows avbroot to keep track of
  less information during patching for better performance. The field is
  only used for Magisk's uninstall feature, which can't ever be used in
  a locked bootloader setup anyway.

Misc
----

While working on the new `avbroot ota verify` subcommand, I found that
the `ossi` stock image (OnePlus 10 Pro) used in avbroot's tests has an
invalid vbmeta hash for the `odm` partition. I thought it was an avbroot
bug, but AOSP's avbtool reports the same invalid hash too. If that image
actually boots, then I'm not sure AVB can be trusted on those devices...

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-08-29 15:54:53 -04:00