Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for replacing otacerts.zip in the system image #240

Merged
merged 1 commit into from
Dec 28, 2023

Conversation

chenxiaolong
Copy link
Owner

@chenxiaolong chenxiaolong commented Dec 24, 2023

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

@chenxiaolong chenxiaolong self-assigned this Dec 24, 2023
@chenxiaolong chenxiaolong force-pushed the system_otacerts branch 2 times, most recently from b5ee191 to b988365 Compare December 24, 2023 21:14
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>
@chenxiaolong
Copy link
Owner Author

chenxiaolong commented Dec 24, 2023

On newer OnePlus devices, system's /system/etc/security/otacerts.zip is symlinked to a file in /my_engineering. my_engineering is normally mounted via an fstab file in /odm/etc/oplus.fstab and is verified using the public key in /vendor/etc/oplus_avb.pubkey. The public key can't just be replaced because it's also used for other partitions. A potential solution would be to add an entry for my_engineering in the vbmeta partition and then patch the fstab.

However...

From what I can tell, my_engineering exists only on devices that shipped with Android 12 or newer. From previous avbroot bug reports (#186, #195, #212), it doesn't seem like avb_custom_key works anymore past their bootloader version shipped with Android 11. Seems like wasted effort to add support for their new esoteric setup.

@chenxiaolong chenxiaolong merged commit c1095b8 into master Dec 28, 2023
@chenxiaolong chenxiaolong deleted the system_otacerts branch December 28, 2023 22:49
chenxiaolong added a commit that referenced this pull request Dec 28, 2023
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
chenxiaolong added a commit that referenced this pull request Dec 29, 2023
When the original payload extents are all in order and have no gaps, we
can efficiently copy unmodified chunks of the system image from the
original payload into the new payload. Only the chunks containing the
modified regions (`otacerts.zip`, hash tree, FEC data, and AVB metadata)
need to be recompressed. This massively reduces the CPU usage since
usually only <20 MiB need to be recompressed.

If the conditions for the optimized path aren't satisfied (eg. extents
aren't in order or `--replace` is used), then it falls back to splitting
and compressing the entire system image.

This fixes the performance issues introduced in #240. This is probably
the best that we can do given that we now always patch the system image.

Issue: #225

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
chenxiaolong added a commit that referenced this pull request Dec 29, 2023
When the original payload extents are all in order and have no gaps, we
can efficiently copy unmodified chunks of the system image from the
original payload into the new payload. Only the chunks containing the
modified regions (`otacerts.zip`, hash tree, FEC data, and AVB metadata)
need to be recompressed. This massively reduces the CPU usage since
usually only <20 MiB need to be recompressed.

If the conditions for the optimized path aren't satisfied (eg. extents
aren't in order or `--replace` is used), then it falls back to splitting
and compressing the entire system image.

This fixes the performance issues introduced in #240. This is probably
the best that we can do given that we now always patch the system image.

Issue: #225

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
chenxiaolong added a commit that referenced this pull request Dec 29, 2023
When the original payload extents are all in order and have no gaps, we
can efficiently copy unmodified chunks of the system image from the
original payload into the new payload. Only the chunks containing the
modified regions (`otacerts.zip`, hash tree, FEC data, and AVB metadata)
need to be recompressed. This massively reduces the CPU usage since
usually only <20 MiB need to be recompressed.

If the conditions for the optimized path aren't satisfied (eg. extents
aren't in order or `--replace` is used), then it falls back to splitting
and compressing the entire system image.

This fixes the performance issues introduced in #240. This is probably
the best that we can do given that we now always patch the system image.

Issue: #225

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
chenxiaolong added a commit that referenced this pull request Dec 29, 2023
When the original payload extents are all in order and have no gaps, we
can efficiently copy unmodified chunks of the system image from the
original payload into the new payload. Only the chunks containing the
modified regions (`otacerts.zip`, hash tree, FEC data, and AVB metadata)
need to be recompressed. This massively reduces the CPU usage since
usually only <20 MiB need to be recompressed.

If the conditions for the optimized path aren't satisfied (eg. extents
aren't in order or `--replace` is used), then it falls back to splitting
and compressing the entire system image.

This fixes the performance issues introduced in #240. This is probably
the best that we can do given that we now always patch the system image.

Issue: #225

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Investigate better ways to override system.img's otacerts.zip
1 participant