diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3d8bccda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +aosp/* linguist-vendored +external/* linguist-vendored +avb/* linguist-vendored diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..c0604c24 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,98 @@ +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ rc, master, bin ] + pull_request: + branches: [ ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + linux: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: apt + run: sudo apt install device-tree-compiler p7zip-full android-sdk-libsparse-utils erofs-utils + + # Runs a single command using the runners shell + - name: Unit Test + run: ./gradlew check && ./gradlew clean || true + + # Runs a set of commands using the runners shell + - name: Integration Test + run: | + ./integrationTest.py + + macos: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: brew + run: brew install dtc + + - name: Unit Test + run: ./gradlew check && ./gradlew clean || true + + # Runs a set of commands using the runners shell + - name: Integration Test + run: ./integrationTest.py + + windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: choco + run: choco install openssl dtc-msys2 zip vim + + - name: Unit Test + run: ./gradlew.bat check && ./gradlew.bat clean || true + + # Runs a set of commands using the runners shell + - name: Integration Test + run: python integrationTest.py + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8d8680c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.gradle +build/ +__pycache__ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..188ddb97 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "src/integrationTest/resources"] + path = src/integrationTest/resources + url = https://github.com/cfig/android_image_res +[submodule "src/integrationTest/resources_2"] + path = src/integrationTest/resources_2 + url = https://github.com/cfig/android_image_res2.git +[submodule "src/integrationTest/resources_3"] + path = src/integrationTest/resources_3 + url = https://github.com/cfig/android_image_res3.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..852e4da7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: java +os: + - linux + - osx +dist: focal +osx_image: xcode12.2 +addons: + apt: + packages: + - xz-utils + - libblkid-dev + - liblz4-tool + - device-tree-compiler + - python3 + - python-all +before_install: + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update ; fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install lz4 dtc gradle; fi +script: + - ./gradlew check + - ./gradlew clean + - ./integrationTest.py diff --git a/README.md b/README.md new file mode 100644 index 00000000..679c0d60 --- /dev/null +++ b/README.md @@ -0,0 +1,364 @@ +# Android_boot_image_editor +[![CI](https://github.com/cfig/Android_boot_image_editor/actions/workflows/main.yml/badge.svg)](https://github.com/cfig/Android_boot_image_editor/actions/workflows/main.yml) +[![License](http://img.shields.io/:license-apache-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0.html) + +A tool for reverse engineering Android ROM images. + +## Requirements +Make sure you have [JDK11+](https://www.oracle.com/java/technologies/downloads/#java17) and [Python3](https://www.python.org/downloads/). + +* Linux / WSL: `sudo apt install git device-tree-compiler lz4 xz-utils zlib1g-dev openjdk-17-jdk gcc g++ python3 python-is-python3 p7zip-full android-sdk-libsparse-utils erofs-utils` + +* Mac: `brew install lz4 xz dtc` + +* Windows: Install openssl and device-tree compiler with [chocolate](https://chocolatey.org/install) +`choco install openssl dtc-msys2 zip vim` + +## Getting Started +Put your boot.img to current directory, then start gradle 'unpack' task: + +```bash +cp boot.img +./gradlew unpack +``` + +Your get the flattened kernel and /root filesystem under **./build/unzip\_boot**: + + build/unzip_boot/ + ├── boot.json (boot image info) + ├── boot.avb.json (AVB only) + ├── kernel + ├── second (2nd bootloader, if exists) + ├── dtb (dtb, if exists) + ├── dtbo (dtbo, if exists) + └── root (extracted initramfs) + +Then you can edit the actual file contents, like rootfs or kernel. +Now, pack the boot.img again + + ./gradlew pack + +You get the repacked boot.img at $(CURDIR): + + boot.img.signed + +Well done you did it! The last step is to star this repo :smile + + +### live demo + +

+ +

+ +## Supported ROM image types + +| Image Type | file names | platforms | note | +| --------------- |----------------------------------------------------------------|-------------|-------------------------| +| boot | boot.img, init_boot.img, boot-debug.img, boot-test-harness.img | all | | +|vendor boot | vendor_boot.img, vendor_boot-debug.img, vendor_kernel_boot.img | all | | +| recovery | recovery.img, recovery-two-step.img | all | | +| vbmeta | vbmeta.img, vbmeta_system.img etc. | all | | +| dtbo | dtbo.img | linux & mac | | +| dtb | *.dtb | linux & mac | | +| sparse images | system.img, vendor.img, product.img etc. | linux | | +| OTA payload | payload.bin | all | Windows git-bash | + +Please note that the boot.img MUST follows AOSP verified boot flow, either [Boot image signature](https://source.android.com/security/verifiedboot/verified-boot#signature_format) in VBoot 1.0 or [AVB HASH footer](https://android.googlesource.com/platform/external/avb/+/master/README.md#The-VBMeta-struct) (a.k.a. AVB) in VBoot 2.0. + +## compatible devices + +| Device Model | Manufacturer | Compatible | Android Version | Note | +|--------------------------------|--------------|----------------------|--------------------------|------| +| Pixel 7 (panther) | Google | Y | 13 (TQ2A.230505.002)
2023)| | +| ADT-3 (adt3) | Askey/Google | Y | 12 (spp2.210219.010) | amlogic inside,
Android TV | +| Pixel 3 (blueline) | Google | Y | 12 (spp2.210219.008,
2021)| | +| Pixel 3 (blueline) | Google | Y | 11 (RP1A.200720.009,
2020)| [more ...](doc/additional_tricks.md#pixel-3-blueline) | +| Pixel 3 (blueline) | Google | Y | Q preview (qpp2.190228.023,
2019)| [more ...](doc/additional_tricks.md#pixel-3-blueline) | +| Redmi K30 4G (phoenix[n]) | XiaoMi | Y | 10 | [verified](https://github.com/cfig/Android_boot_image_editor/issues/17#issuecomment-817169307) by @eebssk1 | +| TS10 | Topway | Y | 10 | car headunit, @mariodantas | +| Pixel XL (marlin) | HTC | Y | 9.0.0 (PPR2.180905.006,
Sep 2018)| [more ...](doc/additional_tricks.md#pixel-xl-marlin) | +| K3 (CPH1955) | OPPO | Y for recovery.img
N for boot.img | Pie | [more](doc/additional_tricks.md#k3-cph1955) | +| Z18 (NX606J) | ZTE | Y | 8.1.0 | [more...](doc/additional_tricks.md#nx606j) | +| Nexus 9 (volantis/flounder) | HTC | Y(with some tricks) | 7.1.1 (N9F27M, Oct 2017) | [tricks](doc/additional_tricks.md#tricks-for-nexus-9volantis)| +| Nexus 5x (bullhead) | LG | Y | 6.0.0_r12 (MDA89E) | | +| Moto X (2013) T-Mobile | Motorola | N | | | +| X7 (PD1602_A_3.12.8) | VIVO | N | ? | [Issue 35](https://github.com/cfig/Android_boot_image_editor/issues/35) | +| Realme GT Neo 3 | Realme | N | 12 | [Issue 105](https://github.com/cfig/Android_boot_image_editor/issues/105) | + +## more examples +
+ working with recovery.img + +Please remember to clean the work directory first. + +```bash +rm *.img +cp recovery.img +./gradlew unpack +./gradlew pack +``` + +
+ +
+ working with vbmeta.img + + +```bash +rm *.img +cp vbmeta.img +./gradlew unpack +./gradlew pack +``` + +
+ +
+ clean workspace +When you finished current work and need to clean the workspace for next image, it's a good idea to call the `clear` command: + +```bash +./gradlew clear +``` + +
+ +
+ working with boot.img and vbmeta.img + +If your vbmeta.img contains hash of boot.img, you MUST update vbmeta image together. + +```bash +rm *.img +cp boot.img +cp vbmeta.img +./gradlew unpack +./gradlew pack +``` +Your boot.img.signed and vbmeta.img.signd will be updated together, then you can flash them to your device. + +
+ +
+ working with vendor_boot.img + vbmeta.img (Pixel 5 etc.) +Most devices include hash descriptor of vendor_boot.img in vbmeta.img, so if you need to modify vendor_boot.img, you need to update vbmeta.img together. + +```bash +rm *.img +cp vendor_boot.img +cp vbmeta.img +./gradlew unpack +./gradlew pack +./gradlew flash +``` + +Please note that to use 'gradle flash', your host machine must be connectted to your DUT with adb, and you already 'adb root'. + +
+ +
+ How to edit device tree blob(dtb) inside vendor_boot.img + +If you want to edit the device-tree blob in place: + +```bash +cp vendor_boot.img +cp vbmeta.img +./gradlew unpack +==> now you can edit build/unzip_boot/dtb.dts directly +./gradlew pack +``` + +During unpack stage, dtb will be dumped to file `build/unzip_boot/dtb`, dts will be decompiled to `build/unzip_boot/dtb.dts`. +You can edit `dtb.dts` directly, and it will be compiled to dtb duing repack stage. + +If you just want to replace the dtb with the one that is compiled outside this tool, please + +```bash +cp vendor_boot.img +cp vbmeta.img +./gradlew unpack +rm build/unzip_boot/dtb.dts +cp build/unzip_boot/dtb +./gradlew pack +``` + +
+ +
+ + How to pull device tree blob(dtb) from a rooted device + +If you have a rooted device and want to pull /proc/device-tree +```bash +touch fake.dtb +./gradlew pull +``` +This tool will copy `dtc` to the target device via `adb`, and dump the dtb and dts file. Eventually you should get something like this +``` ++--------+------------------------------+ +| What | Where | ++--------+------------------------------+ +| source | /proc/device-tree | ++--------+------------------------------+ +| DTB | panther.dtb | ++--------+------------------------------+ +| DTS | build/unzip_boot/panther.dts | ++--------+------------------------------+ + +``` + +
+ +
+ + How to work edit device tree blob(dtb) file + +If you have a dtb file and want to edit its content +```bash +cp . +./gradlew unpack +``` +This tool will decompile it and put the decompiled source to build/unzip_boot. + +``` + Unpack Summary of panther.dtb ++------+------------------------------+ +| What | Where | ++------+------------------------------+ +| DTB | panther.dtb | ++------+------------------------------+ +| DTS | build/unzip_boot/panther.dts | ++------+------------------------------+ +``` + +
+ +
+ working with system.img + +```bash +cp system.img +./gradlew unpack +``` +You get `system.img.unsparse`, that's a plain ext4 filesystem data. + +
+ +
+ How to disable AVB verification + +The idea is to set flag=2 in main vbmeta. + +```bash +rm *.img +cp vbmeta.img +./gradlew unpack +vim -u NONE -N build/unzip_boot/vbmeta.avb.json -c ":19s/0/2/g" -c ":wq" +./gradlew pack +``` +Then flash vbmeta.img.signed to your device. + +
+ +
+ + How to merge init_boot.img into boot.img + +* unpack init_boot.img and copy out "build/unzip_boot/root". +* clear workspace by `gradle clear`, then unpack boot.img +* copy back the "build/unzip_boot/root" +* edit build/unzip_boot/boot.json +- change `ramdisk.size` to 1 +- change `ramdisk.file` from "build/unzip_boot/ramdisk.img" to "build/unzip_boot/ramdisk.img.lz4" + +
+ +
+ + work with payload.bin + +- extract everything + +Usage: +``` + gradle unpack +``` + +- extract only 1 specified partition +Usage: +``` + gradle unpack -Dpart= +``` +Example: +``` + gradle unpack -Dpart=boot + gradle unpack -Dpart=system +``` + +Note: + "build/payload/" will be deleted before each "unpack" task + +
+ + +
+ + work with apex images + +AOSP already has tools like apexer, deapexer, sign_apex.py, these should suffice the needs on .apex and .capex. +Refer to Issue https://github.com/cfig/Android_boot_image_editor/issues/120 + +- For those who may be interested in apex generation flow, there is a graph here +![image](doc/apexer_generate_flow.png) + +
+ +
+ How to work with vendor_dlkm.img + +```bash +cp vendor_dlkm.img +cp vbmeta.img +./gradlew unpack +# replace your .ko +./gradlew pack +``` +Then flash `vbmeta.img.signed` and `vendor_dlkm.img.signed` to the device. + +
+ +## boot.img layout +Read [boot layout](doc/layout.md) of Android boot.img and vendor\_boot.img. +Read [misc layout](doc/misc_image_layout.md) of misc\.img + +## References and Acknowledgement +
+ more ... + +Android version list https://source.android.com/source/build-numbers.html
+Android build-numbers https://source.android.com/setup/start/build-numbers + +cpio & fs\_config
+https://android.googlesource.com/platform/system/core
+https://www.kernel.org/doc/Documentation/early-userspace/buffer-format.txt
+AVB
+https://android.googlesource.com/platform/external/avb/
+boot\_signer
+https://android.googlesource.com/platform/system/extras
+mkbootimg
+https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/
+boot header definition
+https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h
+kernel info extractor
+https://android.googlesource.com/platform/build/+/refs/heads/master/tools/extract_kernel.py
+mkdtboimg
+https://android.googlesource.com/platform/system/libufdt/
+libsparse
+https://android.googlesource.com/platform/system/core/+/refs/heads/master/libsparse/
+Android Nexus/Pixle factory images
+https://developers.google.cn/android/images
+ +
+ diff --git a/aosp/apksigner/build/libs/apksigner-1.0.jar b/aosp/apksigner/build/libs/apksigner-1.0.jar new file mode 100644 index 00000000..619993d2 Binary files /dev/null and b/aosp/apksigner/build/libs/apksigner-1.0.jar differ diff --git a/aosp/avb/avbtool.diff b/aosp/avb/avbtool.diff new file mode 100644 index 00000000..faaa663c --- /dev/null +++ b/aosp/avb/avbtool.diff @@ -0,0 +1,20 @@ +diff --git a/avb/avbtool b/avb/avbtool +index 8732024..5f62948 100755 +--- a/avb/avbtool ++++ b/avb/avbtool +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python ++#!/usr/bin/env python2.7 + + # Copyright 2016, The Android Open Source Project + # +@@ -2159,7 +2159,8 @@ class Avb(object): + expected_chain_partitions_map[partition_name] = (rollback_index_location, pk_blob) + + image_dir = os.path.dirname(image_filename) +- image_ext = os.path.splitext(image_filename)[1] ++ #image_ext = os.path.splitext(image_filename)[1] ++ image_ext = image_filename[image_filename.index('.'):] + + key_blob = None + if key_path: diff --git a/aosp/avb/avbtool.v1.1.py b/aosp/avb/avbtool.v1.1.py new file mode 100755 index 00000000..c53c18e6 --- /dev/null +++ b/aosp/avb/avbtool.v1.1.py @@ -0,0 +1,5766 @@ +#!/usr/bin/env python2 + +# Copyright 2016, The Android Open Source Project +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +"""Command-line tool for working with Android Verified Boot images.""" + +from __future__ import print_function + +import argparse +import binascii +import bisect +import hashlib +import math +import os +import struct +import subprocess +import sys +import tempfile +import time + +# Keep in sync with libavb/avb_version.h. +AVB_VERSION_MAJOR = 1 +AVB_VERSION_MINOR = 1 +AVB_VERSION_SUB = 0 + +# Keep in sync with libavb/avb_footer.h. +AVB_FOOTER_VERSION_MAJOR = 1 +AVB_FOOTER_VERSION_MINOR = 0 + +AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = 1 + + +class AvbError(Exception): + """Application-specific errors. + + These errors represent issues for which a stack-trace should not be + presented. + + Attributes: + message: Error message. + """ + + def __init__(self, message): + Exception.__init__(self, message) + + +class Algorithm(object): + """Contains details about an algorithm. + + See the avb_vbmeta_image.h file for more details about algorithms. + + The constant |ALGORITHMS| is a dictionary from human-readable + names (e.g 'SHA256_RSA2048') to instances of this class. + + Attributes: + algorithm_type: Integer code corresponding to |AvbAlgorithmType|. + hash_name: Empty or a name from |hashlib.algorithms|. + hash_num_bytes: Number of bytes used to store the hash. + signature_num_bytes: Number of bytes used to store the signature. + public_key_num_bytes: Number of bytes used to store the public key. + padding: Padding used for signature, if any. + """ + + def __init__(self, algorithm_type, hash_name, hash_num_bytes, + signature_num_bytes, public_key_num_bytes, padding): + self.algorithm_type = algorithm_type + self.hash_name = hash_name + self.hash_num_bytes = hash_num_bytes + self.signature_num_bytes = signature_num_bytes + self.public_key_num_bytes = public_key_num_bytes + self.padding = padding + + +# This must be kept in sync with the avb_crypto.h file. +# +# The PKC1-v1.5 padding is a blob of binary DER of ASN.1 and is +# obtained from section 5.2.2 of RFC 4880. +ALGORITHMS = { + 'NONE': Algorithm( + algorithm_type=0, # AVB_ALGORITHM_TYPE_NONE + hash_name='', + hash_num_bytes=0, + signature_num_bytes=0, + public_key_num_bytes=0, + padding=[]), + 'SHA256_RSA2048': Algorithm( + algorithm_type=1, # AVB_ALGORITHM_TYPE_SHA256_RSA2048 + hash_name='sha256', + hash_num_bytes=32, + signature_num_bytes=256, + public_key_num_bytes=8 + 2*2048//8, + padding=[ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*202 + [0x00] + [ + # ASN.1 header + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20, + ]), + 'SHA256_RSA4096': Algorithm( + algorithm_type=2, # AVB_ALGORITHM_TYPE_SHA256_RSA4096 + hash_name='sha256', + hash_num_bytes=32, + signature_num_bytes=512, + public_key_num_bytes=8 + 2*4096//8, + padding=[ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*458 + [0x00] + [ + # ASN.1 header + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20, + ]), + 'SHA256_RSA8192': Algorithm( + algorithm_type=3, # AVB_ALGORITHM_TYPE_SHA256_RSA8192 + hash_name='sha256', + hash_num_bytes=32, + signature_num_bytes=1024, + public_key_num_bytes=8 + 2*8192//8, + padding=[ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*970 + [0x00] + [ + # ASN.1 header + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20, + ]), + 'SHA512_RSA2048': Algorithm( + algorithm_type=4, # AVB_ALGORITHM_TYPE_SHA512_RSA2048 + hash_name='sha512', + hash_num_bytes=64, + signature_num_bytes=256, + public_key_num_bytes=8 + 2*2048//8, + padding=[ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*170 + [0x00] + [ + # ASN.1 header + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 + ]), + 'SHA512_RSA4096': Algorithm( + algorithm_type=5, # AVB_ALGORITHM_TYPE_SHA512_RSA4096 + hash_name='sha512', + hash_num_bytes=64, + signature_num_bytes=512, + public_key_num_bytes=8 + 2*4096//8, + padding=[ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*426 + [0x00] + [ + # ASN.1 header + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 + ]), + 'SHA512_RSA8192': Algorithm( + algorithm_type=6, # AVB_ALGORITHM_TYPE_SHA512_RSA8192 + hash_name='sha512', + hash_num_bytes=64, + signature_num_bytes=1024, + public_key_num_bytes=8 + 2*8192//8, + padding=[ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*938 + [0x00] + [ + # ASN.1 header + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 + ]), +} + + +def get_release_string(): + """Calculates the release string to use in the VBMeta struct.""" + # Keep in sync with libavb/avb_version.c:avb_version_string(). + return 'avbtool {}.{}.{}'.format(AVB_VERSION_MAJOR, + AVB_VERSION_MINOR, + AVB_VERSION_SUB) + + +def round_to_multiple(number, size): + """Rounds a number up to nearest multiple of another number. + + Arguments: + number: The number to round up. + size: The multiple to round up to. + + Returns: + If |number| is a multiple of |size|, returns |number|, otherwise + returns |number| + |size|. + """ + remainder = number % size + if remainder == 0: + return number + return number + size - remainder + + +def round_to_pow2(number): + """Rounds a number up to the next power of 2. + + Arguments: + number: The number to round up. + + Returns: + If |number| is already a power of 2 then |number| is + returned. Otherwise the smallest power of 2 greater than |number| + is returned. + """ + return 2**((number - 1).bit_length()) + + +def encode_long(num_bits, value): + """Encodes a long to a bytearray() using a given amount of bits. + + This number is written big-endian, e.g. with the most significant + bit first. + + This is the reverse of decode_long(). + + Arguments: + num_bits: The number of bits to write, e.g. 2048. + value: The value to write. + + Returns: + A bytearray() with the encoded long. + """ + ret = bytearray() + for bit_pos in range(num_bits, 0, -8): + octet = (value >> (bit_pos - 8)) & 0xff + ret.extend(struct.pack('!B', octet)) + return ret + + +def decode_long(blob): + """Decodes a long from a bytearray() using a given amount of bits. + + This number is expected to be in big-endian, e.g. with the most + significant bit first. + + This is the reverse of encode_long(). + + Arguments: + blob: A bytearray() with the encoded long. + + Returns: + The decoded value. + """ + ret = 0 + for b in bytearray(blob): + ret *= 256 + ret += b + return ret + + +def egcd(a, b): + """Calculate greatest common divisor of two numbers. + + This implementation uses a recursive version of the extended + Euclidian algorithm. + + Arguments: + a: First number. + b: Second number. + + Returns: + A tuple (gcd, x, y) that where |gcd| is the greatest common + divisor of |a| and |b| and |a|*|x| + |b|*|y| = |gcd|. + """ + if a == 0: + return (b, 0, 1) + g, y, x = egcd(b % a, a) + return (g, x - (b // a) * y, y) + + +def modinv(a, m): + """Calculate modular multiplicative inverse of |a| modulo |m|. + + This calculates the number |x| such that |a| * |x| == 1 (modulo + |m|). This number only exists if |a| and |m| are co-prime - |None| + is returned if this isn't true. + + Arguments: + a: The number to calculate a modular inverse of. + m: The modulo to use. + + Returns: + The modular multiplicative inverse of |a| and |m| or |None| if + these numbers are not co-prime. + """ + gcd, x, _ = egcd(a, m) + if gcd != 1: + return None # modular inverse does not exist + return x % m + + +def parse_number(string): + """Parse a string as a number. + + This is just a short-hand for int(string, 0) suitable for use in the + |type| parameter of |ArgumentParser|'s add_argument() function. An + improvement to just using type=int is that this function supports + numbers in other bases, e.g. "0x1234". + + Arguments: + string: The string to parse. + + Returns: + The parsed integer. + + Raises: + ValueError: If the number could not be parsed. + """ + return int(string, 0) + + +class RSAPublicKey(object): + """Data structure used for a RSA public key. + + Attributes: + exponent: The key exponent. + modulus: The key modulus. + num_bits: The key size. + """ + + MODULUS_PREFIX = 'modulus=' + + def __init__(self, key_path): + """Loads and parses an RSA key from either a private or public key file. + + Arguments: + key_path: The path to a key file. + + Raises: + AvbError: If RSA key parameters could not be read from file. + """ + # We used to have something as simple as this: + # + # key = Crypto.PublicKey.RSA.importKey(open(key_path).read()) + # self.exponent = key.e + # self.modulus = key.n + # self.num_bits = key.size() + 1 + # + # but unfortunately PyCrypto is not available in the builder. So + # instead just parse openssl(1) output to get this + # information. It's ugly but... + args = ['openssl', 'rsa', '-in', key_path, '-modulus', '-noout'] + p = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate() + if p.wait() != 0: + # Could be just a public key is passed, try that. + args.append('-pubin') + p = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate() + if p.wait() != 0: + raise AvbError('Error getting public key: {}'.format(perr)) + + if not pout.lower().startswith(self.MODULUS_PREFIX): + raise AvbError('Unexpected modulus output') + + modulus_hexstr = pout[len(self.MODULUS_PREFIX):] + + # The exponent is assumed to always be 65537 and the number of + # bits can be derived from the modulus by rounding up to the + # nearest power of 2. + self.modulus = int(modulus_hexstr, 16) + self.num_bits = round_to_pow2(int(math.ceil(math.log(self.modulus, 2)))) + self.exponent = 65537 + + +# TODO(danielaustin): Should this be moved into the RSAPublicKey class? +def rsa_key_read_pem_bytes(key_path): + """Reads the bytes out of the passed in PEM file. + + Arguments: + key_path: A string containing the path to the PEM file. + + Returns: + A bytearray containing the bytes in the PEM file. + + Raises: + AvbError: If openssl cannot decode the PEM file. + """ + # Use openssl to decode the PEM file. + args = ['openssl', 'rsa', '-in', key_path, '-pubout', '-outform', 'DER'] + p = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate() + retcode = p.wait() + if retcode != 0: + raise AvbError('Error decoding: {}'.format(perr)) + return bytearray(pout) + + +def encode_rsa_key(key_path): + """Encodes a public RSA key in |AvbRSAPublicKeyHeader| format. + + This creates a |AvbRSAPublicKeyHeader| as well as the two large + numbers (|key_num_bits| bits long) following it. + + Arguments: + key_path: The path to a key file. + + Returns: + A bytearray() with the |AvbRSAPublicKeyHeader|. + + Raises: + AvbError: If given RSA key exponent is not 65537. + """ + key = RSAPublicKey(key_path) + if key.exponent != 65537: + raise AvbError('Only RSA keys with exponent 65537 are supported.') + ret = bytearray() + # Calculate n0inv = -1/n[0] (mod 2^32) + b = 2L**32 # pylint: disable=long-suffix + n0inv = b - modinv(key.modulus, b) + # Calculate rr = r^2 (mod N), where r = 2^(# of key bits) + r = 2L**key.modulus.bit_length() # pylint: disable=long-suffix + rrmodn = r * r % key.modulus + ret.extend(struct.pack('!II', key.num_bits, n0inv)) + ret.extend(encode_long(key.num_bits, key.modulus)) + ret.extend(encode_long(key.num_bits, rrmodn)) + return ret + + +def lookup_algorithm_by_type(alg_type): + """Looks up algorithm by type. + + Arguments: + alg_type: The integer representing the type. + + Returns: + A tuple with the algorithm name and an |Algorithm| instance. + + Raises: + Exception: If the algorithm cannot be found + """ + for alg_name in ALGORITHMS: + alg_data = ALGORITHMS[alg_name] + if alg_data.algorithm_type == alg_type: + return (alg_name, alg_data) + raise AvbError('Unknown algorithm type {}'.format(alg_type)) + + +def lookup_hash_size_by_type(alg_type): + """Looks up hash size by type. + + Arguments: + alg_type: The integer representing the type. + + Returns: + The corresponding hash size. + + Raises: + AvbError: If the algorithm cannot be found. + """ + for alg_name in ALGORITHMS: + alg_data = ALGORITHMS[alg_name] + if alg_data.algorithm_type == alg_type: + return alg_data.hash_num_bytes + raise AvbError('Unsupported algorithm type {}'.format(alg_type)) + + +def raw_sign(signing_helper, signing_helper_with_files, + algorithm_name, signature_num_bytes, key_path, + raw_data_to_sign): + """Computes a raw RSA signature using |signing_helper| or openssl. + + Arguments: + signing_helper: Program which signs a hash and returns the signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + algorithm_name: The algorithm name as per the ALGORITHMS dict. + signature_num_bytes: Number of bytes used to store the signature. + key_path: Path to the private key file. Must be PEM format. + raw_data_to_sign: Data to sign (bytearray or str expected). + + Returns: + A bytearray containing the signature. + + Raises: + Exception: If an error occurs. + """ + p = None + if signing_helper_with_files is not None: + signing_file = tempfile.NamedTemporaryFile() + signing_file.write(str(raw_data_to_sign)) + signing_file.flush() + p = subprocess.Popen([ + signing_helper_with_files, algorithm_name, key_path, signing_file.name]) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error signing') + signing_file.seek(0) + signature = bytearray(signing_file.read()) + else: + if signing_helper is not None: + p = subprocess.Popen( + [signing_helper, algorithm_name, key_path], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + else: + p = subprocess.Popen( + ['openssl', 'rsautl', '-sign', '-inkey', key_path, '-raw'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate(str(raw_data_to_sign)) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error signing: {}'.format(perr)) + signature = bytearray(pout) + if len(signature) != signature_num_bytes: + raise AvbError('Error signing: Invalid length of signature') + return signature + + +def verify_vbmeta_signature(vbmeta_header, vbmeta_blob): + """Checks that signature in a vbmeta blob was made by the embedded public key. + + Arguments: + vbmeta_header: A AvbVBMetaHeader. + vbmeta_blob: The whole vbmeta blob, including the header. + + Returns: + True if the signature is valid and corresponds to the embedded + public key. Also returns True if the vbmeta blob is not signed. + + Raises: + AvbError: If there errors calling out to openssl command during + signature verification. + """ + (_, alg) = lookup_algorithm_by_type(vbmeta_header.algorithm_type) + if not alg.hash_name: + return True + header_blob = vbmeta_blob[0:256] + auth_offset = 256 + aux_offset = auth_offset + vbmeta_header.authentication_data_block_size + aux_size = vbmeta_header.auxiliary_data_block_size + aux_blob = vbmeta_blob[aux_offset:aux_offset + aux_size] + pubkey_offset = aux_offset + vbmeta_header.public_key_offset + pubkey_size = vbmeta_header.public_key_size + pubkey_blob = vbmeta_blob[pubkey_offset:pubkey_offset + pubkey_size] + + digest_offset = auth_offset + vbmeta_header.hash_offset + digest_size = vbmeta_header.hash_size + digest_blob = vbmeta_blob[digest_offset:digest_offset + digest_size] + + sig_offset = auth_offset + vbmeta_header.signature_offset + sig_size = vbmeta_header.signature_size + sig_blob = vbmeta_blob[sig_offset:sig_offset + sig_size] + + # Now that we've got the stored digest, public key, and signature + # all we need to do is to verify. This is the exactly the same + # steps as performed in the avb_vbmeta_image_verify() function in + # libavb/avb_vbmeta_image.c. + + ha = hashlib.new(alg.hash_name) + ha.update(header_blob) + ha.update(aux_blob) + computed_digest = ha.digest() + + if computed_digest != digest_blob: + return False + + padding_and_digest = bytearray(alg.padding) + padding_and_digest.extend(computed_digest) + + (num_bits,) = struct.unpack('!I', pubkey_blob[0:4]) + modulus_blob = pubkey_blob[8:8 + num_bits//8] + modulus = decode_long(modulus_blob) + exponent = 65537 + + # We used to have this: + # + # import Crypto.PublicKey.RSA + # key = Crypto.PublicKey.RSA.construct((modulus, long(exponent))) + # if not key.verify(decode_long(padding_and_digest), + # (decode_long(sig_blob), None)): + # return False + # return True + # + # but since 'avbtool verify_image' is used on the builders we don't want + # to rely on Crypto.PublicKey.RSA. Instead just use openssl(1) to verify. + asn1_str = ('asn1=SEQUENCE:pubkeyinfo\n' + '\n' + '[pubkeyinfo]\n' + 'algorithm=SEQUENCE:rsa_alg\n' + 'pubkey=BITWRAP,SEQUENCE:rsapubkey\n' + '\n' + '[rsa_alg]\n' + 'algorithm=OID:rsaEncryption\n' + 'parameter=NULL\n' + '\n' + '[rsapubkey]\n' + 'n=INTEGER:%s\n' + 'e=INTEGER:%s\n' % (hex(modulus).rstrip('L'), + hex(exponent).rstrip('L'))) + asn1_tmpfile = tempfile.NamedTemporaryFile() + asn1_tmpfile.write(asn1_str) + asn1_tmpfile.flush() + der_tmpfile = tempfile.NamedTemporaryFile() + p = subprocess.Popen( + ['openssl', 'asn1parse', '-genconf', asn1_tmpfile.name, '-out', + der_tmpfile.name, '-noout']) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error generating DER file') + + p = subprocess.Popen( + ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', der_tmpfile.name, + '-keyform', 'DER', '-raw'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate(str(sig_blob)) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error verifying data: {}'.format(perr)) + recovered_data = bytearray(pout) + if recovered_data != padding_and_digest: + sys.stderr.write('Signature not correct\n') + return False + return True + + +class ImageChunk(object): + """Data structure used for representing chunks in Android sparse files. + + Attributes: + chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE. + chunk_offset: Offset in the sparse file where this chunk begins. + output_offset: Offset in de-sparsified file where output begins. + output_size: Number of bytes in output. + input_offset: Offset in sparse file for data if TYPE_RAW otherwise None. + fill_data: Blob with data to fill if TYPE_FILL otherwise None. + """ + + FORMAT = '<2H2I' + TYPE_RAW = 0xcac1 + TYPE_FILL = 0xcac2 + TYPE_DONT_CARE = 0xcac3 + TYPE_CRC32 = 0xcac4 + + def __init__(self, chunk_type, chunk_offset, output_offset, output_size, + input_offset, fill_data): + """Initializes an ImageChunk object. + + Arguments: + chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE. + chunk_offset: Offset in the sparse file where this chunk begins. + output_offset: Offset in de-sparsified file. + output_size: Number of bytes in output. + input_offset: Offset in sparse file if TYPE_RAW otherwise None. + fill_data: Blob with data to fill if TYPE_FILL otherwise None. + + Raises: + ValueError: If data is not well-formed. + """ + self.chunk_type = chunk_type + self.chunk_offset = chunk_offset + self.output_offset = output_offset + self.output_size = output_size + self.input_offset = input_offset + self.fill_data = fill_data + # Check invariants. + if self.chunk_type == self.TYPE_RAW: + if self.fill_data is not None: + raise ValueError('RAW chunk cannot have fill_data set.') + if not self.input_offset: + raise ValueError('RAW chunk must have input_offset set.') + elif self.chunk_type == self.TYPE_FILL: + if self.fill_data is None: + raise ValueError('FILL chunk must have fill_data set.') + if self.input_offset: + raise ValueError('FILL chunk cannot have input_offset set.') + elif self.chunk_type == self.TYPE_DONT_CARE: + if self.fill_data is not None: + raise ValueError('DONT_CARE chunk cannot have fill_data set.') + if self.input_offset: + raise ValueError('DONT_CARE chunk cannot have input_offset set.') + else: + raise ValueError('Invalid chunk type') + + +class ImageHandler(object): + """Abstraction for image I/O with support for Android sparse images. + + This class provides an interface for working with image files that + may be using the Android Sparse Image format. When an instance is + constructed, we test whether it's an Android sparse file. If so, + operations will be on the sparse file by interpreting the sparse + format, otherwise they will be directly on the file. Either way the + operations do the same. + + For reading, this interface mimics a file object - it has seek(), + tell(), and read() methods. For writing, only truncation + (truncate()) and appending is supported (append_raw() and + append_dont_care()). Additionally, data can only be written in units + of the block size. + + Attributes: + filename: Name of file. + is_sparse: Whether the file being operated on is sparse. + block_size: The block size, typically 4096. + image_size: The size of the unsparsified file. + """ + # See system/core/libsparse/sparse_format.h for details. + MAGIC = 0xed26ff3a + HEADER_FORMAT = ' 0: + raise ValueError('There were {} bytes of extra data at the end of the ' + 'file.'.format(junk_len)) + + # Assign |image_size|. + self.image_size = output_offset + + # This is used when bisecting in read() to find the initial slice. + self._chunk_output_offsets = [i.output_offset for i in self._chunks] + + self.is_sparse = True + + def _update_chunks_and_blocks(self): + """Helper function to update the image header. + + The the |total_chunks| and |total_blocks| fields in the header + will be set to value of the |_num_total_blocks| and + |_num_total_chunks| attributes. + + """ + self._image.seek(self.NUM_CHUNKS_AND_BLOCKS_OFFSET, os.SEEK_SET) + self._image.write(struct.pack(self.NUM_CHUNKS_AND_BLOCKS_FORMAT, + self._num_total_blocks, + self._num_total_chunks)) + + def append_dont_care(self, num_bytes): + """Appends a DONT_CARE chunk to the sparse file. + + The given number of bytes must be a multiple of the block size. + + Arguments: + num_bytes: Size in number of bytes of the DONT_CARE chunk. + """ + assert num_bytes % self.block_size == 0 + + if not self.is_sparse: + self._image.seek(0, os.SEEK_END) + # This is more efficient that writing NUL bytes since it'll add + # a hole on file systems that support sparse files (native + # sparse, not Android sparse). + self._image.truncate(self._image.tell() + num_bytes) + self._read_header() + return + + self._num_total_chunks += 1 + self._num_total_blocks += num_bytes // self.block_size + self._update_chunks_and_blocks() + + self._image.seek(self._sparse_end, os.SEEK_SET) + self._image.write(struct.pack(ImageChunk.FORMAT, + ImageChunk.TYPE_DONT_CARE, + 0, # Reserved + num_bytes // self.block_size, + struct.calcsize(ImageChunk.FORMAT))) + self._read_header() + + def append_raw(self, data): + """Appends a RAW chunk to the sparse file. + + The length of the given data must be a multiple of the block size. + + Arguments: + data: Data to append. + """ + assert len(data) % self.block_size == 0 + + if not self.is_sparse: + self._image.seek(0, os.SEEK_END) + self._image.write(data) + self._read_header() + return + + self._num_total_chunks += 1 + self._num_total_blocks += len(data) // self.block_size + self._update_chunks_and_blocks() + + self._image.seek(self._sparse_end, os.SEEK_SET) + self._image.write(struct.pack(ImageChunk.FORMAT, + ImageChunk.TYPE_RAW, + 0, # Reserved + len(data) // self.block_size, + len(data) + + struct.calcsize(ImageChunk.FORMAT))) + self._image.write(data) + self._read_header() + + def append_fill(self, fill_data, size): + """Appends a fill chunk to the sparse file. + + The total length of the fill data must be a multiple of the block size. + + Arguments: + fill_data: Fill data to append - must be four bytes. + size: Number of chunk - must be a multiple of four and the block size. + """ + assert len(fill_data) == 4 + assert size % 4 == 0 + assert size % self.block_size == 0 + + if not self.is_sparse: + self._image.seek(0, os.SEEK_END) + self._image.write(fill_data * (size//4)) + self._read_header() + return + + self._num_total_chunks += 1 + self._num_total_blocks += size // self.block_size + self._update_chunks_and_blocks() + + self._image.seek(self._sparse_end, os.SEEK_SET) + self._image.write(struct.pack(ImageChunk.FORMAT, + ImageChunk.TYPE_FILL, + 0, # Reserved + size // self.block_size, + 4 + struct.calcsize(ImageChunk.FORMAT))) + self._image.write(fill_data) + self._read_header() + + def seek(self, offset): + """Sets the cursor position for reading from unsparsified file. + + Arguments: + offset: Offset to seek to from the beginning of the file. + + Raises: + RuntimeError: If the given offset is negative. + """ + if offset < 0: + raise RuntimeError('Seeking with negative offset: %d' % offset) + self._file_pos = offset + + def read(self, size): + """Reads data from the unsparsified file. + + This method may return fewer than |size| bytes of data if the end + of the file was encountered. + + The file cursor for reading is advanced by the number of bytes + read. + + Arguments: + size: Number of bytes to read. + + Returns: + The data. + + """ + if not self.is_sparse: + self._image.seek(self._file_pos) + data = self._image.read(size) + self._file_pos += len(data) + return data + + # Iterate over all chunks. + chunk_idx = bisect.bisect_right(self._chunk_output_offsets, + self._file_pos) - 1 + data = bytearray() + to_go = size + while to_go > 0: + chunk = self._chunks[chunk_idx] + chunk_pos_offset = self._file_pos - chunk.output_offset + chunk_pos_to_go = min(chunk.output_size - chunk_pos_offset, to_go) + + if chunk.chunk_type == ImageChunk.TYPE_RAW: + self._image.seek(chunk.input_offset + chunk_pos_offset) + data.extend(self._image.read(chunk_pos_to_go)) + elif chunk.chunk_type == ImageChunk.TYPE_FILL: + all_data = chunk.fill_data*(chunk_pos_to_go // len(chunk.fill_data) + 2) + offset_mod = chunk_pos_offset % len(chunk.fill_data) + data.extend(all_data[offset_mod:(offset_mod + chunk_pos_to_go)]) + else: + assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE + data.extend('\0' * chunk_pos_to_go) + + to_go -= chunk_pos_to_go + self._file_pos += chunk_pos_to_go + chunk_idx += 1 + # Generate partial read in case of EOF. + if chunk_idx >= len(self._chunks): + break + + return data + + def tell(self): + """Returns the file cursor position for reading from unsparsified file. + + Returns: + The file cursor position for reading. + """ + return self._file_pos + + def truncate(self, size): + """Truncates the unsparsified file. + + Arguments: + size: Desired size of unsparsified file. + + Raises: + ValueError: If desired size isn't a multiple of the block size. + """ + if not self.is_sparse: + self._image.truncate(size) + self._read_header() + return + + if size % self.block_size != 0: + raise ValueError('Cannot truncate to a size which is not a multiple ' + 'of the block size') + + if size == self.image_size: + # Trivial where there's nothing to do. + return + elif size < self.image_size: + chunk_idx = bisect.bisect_right(self._chunk_output_offsets, size) - 1 + chunk = self._chunks[chunk_idx] + if chunk.output_offset != size: + # Truncation in the middle of a trunk - need to keep the chunk + # and modify it. + chunk_idx_for_update = chunk_idx + 1 + num_to_keep = size - chunk.output_offset + assert num_to_keep % self.block_size == 0 + if chunk.chunk_type == ImageChunk.TYPE_RAW: + truncate_at = (chunk.chunk_offset + + struct.calcsize(ImageChunk.FORMAT) + num_to_keep) + data_sz = num_to_keep + elif chunk.chunk_type == ImageChunk.TYPE_FILL: + truncate_at = (chunk.chunk_offset + + struct.calcsize(ImageChunk.FORMAT) + 4) + data_sz = 4 + else: + assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE + truncate_at = chunk.chunk_offset + struct.calcsize(ImageChunk.FORMAT) + data_sz = 0 + chunk_sz = num_to_keep // self.block_size + total_sz = data_sz + struct.calcsize(ImageChunk.FORMAT) + self._image.seek(chunk.chunk_offset) + self._image.write(struct.pack(ImageChunk.FORMAT, + chunk.chunk_type, + 0, # Reserved + chunk_sz, + total_sz)) + chunk.output_size = num_to_keep + else: + # Truncation at trunk boundary. + truncate_at = chunk.chunk_offset + chunk_idx_for_update = chunk_idx + + self._num_total_chunks = chunk_idx_for_update + self._num_total_blocks = 0 + for i in range(0, chunk_idx_for_update): + self._num_total_blocks += self._chunks[i].output_size // self.block_size + self._update_chunks_and_blocks() + self._image.truncate(truncate_at) + + # We've modified the file so re-read all data. + self._read_header() + else: + # Truncating to grow - just add a DONT_CARE section. + self.append_dont_care(size - self.image_size) + + +class AvbDescriptor(object): + """Class for AVB descriptor. + + See the |AvbDescriptor| C struct for more information. + + Attributes: + tag: The tag identifying what kind of descriptor this is. + data: The data in the descriptor. + """ + + SIZE = 16 + FORMAT_STRING = ('!QQ') # tag, num_bytes_following (descriptor header) + + def __init__(self, data): + """Initializes a new property descriptor. + + Arguments: + data: If not None, must be a bytearray(). + + Raises: + LookupError: If the given descriptor is malformed. + """ + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (self.tag, num_bytes_following) = ( + struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])) + self.data = data[self.SIZE:self.SIZE + num_bytes_following] + else: + self.tag = None + self.data = None + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Unknown descriptor:\n') + o.write(' Tag: {}\n'.format(self.tag)) + if len(self.data) < 256: + o.write(' Data: {} ({} bytes)\n'.format( + repr(str(self.data)), len(self.data))) + else: + o.write(' Data: {} bytes\n'.format(len(self.data))) + + def encode(self): + """Serializes the descriptor. + + Returns: + A bytearray() with the descriptor data. + """ + num_bytes_following = len(self.data) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.tag, nbf_with_padding) + padding = struct.pack(str(padding_size) + 'x') + ret = desc + self.data + padding + return bytearray(ret) + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + # Deletes unused parameters to prevent pylint warning unused-argument. + del image_dir, image_ext, expected_chain_partitions_map + del image_containing_descriptor, accept_zeroed_hashtree + + # Nothing to do. + return True + + +class AvbPropertyDescriptor(AvbDescriptor): + """A class for property descriptors. + + See the |AvbPropertyDescriptor| C struct for more information. + + Attributes: + key: The key. + value: The key. + """ + + TAG = 0 + SIZE = 32 + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'Q' # key size (bytes) + 'Q') # value size (bytes) + + def __init__(self, data=None): + """Initializes a new property descriptor. + + Arguments: + data: If not None, must be a bytearray of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + AvbDescriptor.__init__(self, None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, key_size, + value_size) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]) + expected_size = round_to_multiple( + self.SIZE - 16 + key_size + 1 + value_size + 1, 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a property ' + 'descriptor.') + self.key = data[self.SIZE:(self.SIZE + key_size)] + self.value = data[(self.SIZE + key_size + 1):(self.SIZE + key_size + 1 + + value_size)] + else: + self.key = '' + self.value = '' + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + if len(self.value) < 256: + o.write(' Prop: {} -> {}\n'.format(self.key, repr(str(self.value)))) + else: + o.write(' Prop: {} -> ({} bytes)\n'.format(self.key, len(self.value))) + + def encode(self): + """Serializes the descriptor. + + Returns: + A bytearray() with the descriptor data. + """ + num_bytes_following = self.SIZE + len(self.key) + len(self.value) + 2 - 16 + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + len(self.key), len(self.value)) + padding = struct.pack(str(padding_size) + 'x') + ret = desc + self.key + '\0' + self.value + '\0' + padding + return bytearray(ret) + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + # Nothing to do. + return True + + +class AvbHashtreeDescriptor(AvbDescriptor): + """A class for hashtree descriptors. + + See the |AvbHashtreeDescriptor| C struct for more information. + + Attributes: + dm_verity_version: dm-verity version used. + image_size: Size of the image, after rounding up to |block_size|. + tree_offset: Offset of the hash tree in the file. + tree_size: Size of the tree. + data_block_size: Data block size + hash_block_size: Hash block size + fec_num_roots: Number of roots used for FEC (0 if FEC is not used). + fec_offset: Offset of FEC data (0 if FEC is not used). + fec_size: Size of FEC data (0 if FEC is not used). + hash_algorithm: Hash algorithm used. + partition_name: Partition name. + salt: Salt used. + root_digest: Root digest. + flags: Descriptor flags (see avb_hashtree_descriptor.h). + """ + + TAG = 1 + RESERVED = 60 + SIZE = 120 + RESERVED + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'L' # dm-verity version used + 'Q' # image size (bytes) + 'Q' # tree offset (bytes) + 'Q' # tree size (bytes) + 'L' # data block size (bytes) + 'L' # hash block size (bytes) + 'L' # FEC number of roots + 'Q' # FEC offset (bytes) + 'Q' # FEC size (bytes) + '32s' # hash algorithm used + 'L' # partition name (bytes) + 'L' # salt length (bytes) + 'L' # root digest length (bytes) + 'L' + # flags + str(RESERVED) + 's') # reserved + + def __init__(self, data=None): + """Initializes a new hashtree descriptor. + + Arguments: + data: If not None, must be a bytearray of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + AvbDescriptor.__init__(self, None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, self.dm_verity_version, self.image_size, + self.tree_offset, self.tree_size, self.data_block_size, + self.hash_block_size, self.fec_num_roots, self.fec_offset, self.fec_size, + self.hash_algorithm, partition_name_len, salt_len, + root_digest_len, self.flags, _) = struct.unpack(self.FORMAT_STRING, + data[0:self.SIZE]) + expected_size = round_to_multiple( + self.SIZE - 16 + partition_name_len + salt_len + root_digest_len, 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a hashtree ' + 'descriptor.') + # Nuke NUL-bytes at the end. + self.hash_algorithm = self.hash_algorithm.split('\0', 1)[0] + o = 0 + self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o + + partition_name_len)]) + # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8. + self.partition_name.decode('utf-8') + o += partition_name_len + self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)] + o += salt_len + self.root_digest = data[(self.SIZE + o):(self.SIZE + o + root_digest_len)] + if root_digest_len != len(hashlib.new(name=self.hash_algorithm).digest()): + if root_digest_len != 0: + raise LookupError('root_digest_len doesn\'t match hash algorithm') + + else: + self.dm_verity_version = 0 + self.image_size = 0 + self.tree_offset = 0 + self.tree_size = 0 + self.data_block_size = 0 + self.hash_block_size = 0 + self.fec_num_roots = 0 + self.fec_offset = 0 + self.fec_size = 0 + self.hash_algorithm = '' + self.partition_name = '' + self.salt = bytearray() + self.root_digest = bytearray() + self.flags = 0 + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Hashtree descriptor:\n') + o.write(' Version of dm-verity: {}\n'.format(self.dm_verity_version)) + o.write(' Image Size: {} bytes\n'.format(self.image_size)) + o.write(' Tree Offset: {}\n'.format(self.tree_offset)) + o.write(' Tree Size: {} bytes\n'.format(self.tree_size)) + o.write(' Data Block Size: {} bytes\n'.format( + self.data_block_size)) + o.write(' Hash Block Size: {} bytes\n'.format( + self.hash_block_size)) + o.write(' FEC num roots: {}\n'.format(self.fec_num_roots)) + o.write(' FEC offset: {}\n'.format(self.fec_offset)) + o.write(' FEC size: {} bytes\n'.format(self.fec_size)) + o.write(' Hash Algorithm: {}\n'.format(self.hash_algorithm)) + o.write(' Partition Name: {}\n'.format(self.partition_name)) + o.write(' Salt: {}\n'.format(str(self.salt).encode( + 'hex'))) + o.write(' Root Digest: {}\n'.format(str( + self.root_digest).encode('hex'))) + o.write(' Flags: {}\n'.format(self.flags)) + + def encode(self): + """Serializes the descriptor. + + Returns: + A bytearray() with the descriptor data. + """ + encoded_name = self.partition_name.encode('utf-8') + num_bytes_following = (self.SIZE + len(encoded_name) + len(self.salt) + + len(self.root_digest) - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + self.dm_verity_version, self.image_size, + self.tree_offset, self.tree_size, self.data_block_size, + self.hash_block_size, self.fec_num_roots, + self.fec_offset, self.fec_size, self.hash_algorithm, + len(encoded_name), len(self.salt), len(self.root_digest), + self.flags, self.RESERVED*'\0') + padding = struct.pack(str(padding_size) + 'x') + ret = desc + encoded_name + self.salt + self.root_digest + padding + return bytearray(ret) + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + if not self.partition_name: + image_filename = image_containing_descriptor.filename + image = image_containing_descriptor + else: + image_filename = os.path.join(image_dir, self.partition_name + image_ext) + image = ImageHandler(image_filename) + # Generate the hashtree and checks that it matches what's in the file. + digest_size = len(hashlib.new(name=self.hash_algorithm).digest()) + digest_padding = round_to_pow2(digest_size) - digest_size + (hash_level_offsets, tree_size) = calc_hash_level_offsets( + self.image_size, self.data_block_size, digest_size + digest_padding) + root_digest, hash_tree = generate_hash_tree(image, self.image_size, + self.data_block_size, + self.hash_algorithm, self.salt, + digest_padding, + hash_level_offsets, + tree_size) + # The root digest must match unless it is not embedded in the descriptor. + if self.root_digest and root_digest != self.root_digest: + sys.stderr.write('hashtree of {} does not match descriptor\n'. + format(image_filename)) + return False + # ... also check that the on-disk hashtree matches + image.seek(self.tree_offset) + hash_tree_ondisk = image.read(self.tree_size) + is_zeroed = (self.tree_size == 0) or (hash_tree_ondisk[0:8] == 'ZeRoHaSH') + if is_zeroed and accept_zeroed_hashtree: + print('{}: skipping verification since hashtree is zeroed and ' + '--accept_zeroed_hashtree was given' + .format(self.partition_name)) + else: + if hash_tree != hash_tree_ondisk: + sys.stderr.write('hashtree of {} contains invalid data\n'. + format(image_filename)) + return False + print('{}: Successfully verified {} hashtree of {} for image of {} bytes' + .format(self.partition_name, self.hash_algorithm, image.filename, + self.image_size)) + # TODO(zeuthen): we could also verify that the FEC stored in the image is + # correct but this a) currently requires the 'fec' binary; and b) takes a + # long time; and c) is not strictly needed for verification purposes as + # we've already verified the root hash. + return True + + +class AvbHashDescriptor(AvbDescriptor): + """A class for hash descriptors. + + See the |AvbHashDescriptor| C struct for more information. + + Attributes: + image_size: Image size, in bytes. + hash_algorithm: Hash algorithm used. + partition_name: Partition name. + salt: Salt used. + digest: The hash value of salt and data combined. + flags: The descriptor flags (see avb_hash_descriptor.h). + """ + + TAG = 2 + RESERVED = 60 + SIZE = 72 + RESERVED + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'Q' # image size (bytes) + '32s' # hash algorithm used + 'L' # partition name (bytes) + 'L' # salt length (bytes) + 'L' # digest length (bytes) + 'L' + # flags + str(RESERVED) + 's') # reserved + + def __init__(self, data=None): + """Initializes a new hash descriptor. + + Arguments: + data: If not None, must be a bytearray of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + AvbDescriptor.__init__(self, None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, self.image_size, self.hash_algorithm, + partition_name_len, salt_len, + digest_len, self.flags, _) = struct.unpack(self.FORMAT_STRING, + data[0:self.SIZE]) + expected_size = round_to_multiple( + self.SIZE - 16 + partition_name_len + salt_len + digest_len, 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a hash ' 'descriptor.') + # Nuke NUL-bytes at the end. + self.hash_algorithm = self.hash_algorithm.split('\0', 1)[0] + o = 0 + self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o + + partition_name_len)]) + # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8. + self.partition_name.decode('utf-8') + o += partition_name_len + self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)] + o += salt_len + self.digest = data[(self.SIZE + o):(self.SIZE + o + digest_len)] + if digest_len != len(hashlib.new(name=self.hash_algorithm).digest()): + if digest_len != 0: + raise LookupError('digest_len doesn\'t match hash algorithm') + + else: + self.image_size = 0 + self.hash_algorithm = '' + self.partition_name = '' + self.salt = bytearray() + self.digest = bytearray() + self.flags = 0 + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Hash descriptor:\n') + o.write(' Image Size: {} bytes\n'.format(self.image_size)) + o.write(' Hash Algorithm: {}\n'.format(self.hash_algorithm)) + o.write(' Partition Name: {}\n'.format(self.partition_name)) + o.write(' Salt: {}\n'.format(str(self.salt).encode( + 'hex'))) + o.write(' Digest: {}\n'.format(str(self.digest).encode( + 'hex'))) + o.write(' Flags: {}\n'.format(self.flags)) + + def encode(self): + """Serializes the descriptor. + + Returns: + A bytearray() with the descriptor data. + """ + encoded_name = self.partition_name.encode('utf-8') + num_bytes_following = ( + self.SIZE + len(encoded_name) + len(self.salt) + len(self.digest) - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + self.image_size, self.hash_algorithm, len(encoded_name), + len(self.salt), len(self.digest), self.flags, + self.RESERVED*'\0') + padding = struct.pack(str(padding_size) + 'x') + ret = desc + encoded_name + self.salt + self.digest + padding + return bytearray(ret) + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + if not self.partition_name: + image_filename = image_containing_descriptor.filename + image = image_containing_descriptor + else: + image_filename = os.path.join(image_dir, self.partition_name + image_ext) + image = ImageHandler(image_filename) + data = image.read(self.image_size) + ha = hashlib.new(self.hash_algorithm) + ha.update(self.salt) + ha.update(data) + digest = ha.digest() + # The digest must match unless there is no digest in the descriptor. + if self.digest and digest != self.digest: + sys.stderr.write('{} digest of {} does not match digest in descriptor\n'. + format(self.hash_algorithm, image_filename)) + return False + print('{}: Successfully verified {} hash of {} for image of {} bytes' + .format(self.partition_name, self.hash_algorithm, image.filename, + self.image_size)) + return True + + +class AvbKernelCmdlineDescriptor(AvbDescriptor): + """A class for kernel command-line descriptors. + + See the |AvbKernelCmdlineDescriptor| C struct for more information. + + Attributes: + flags: Flags. + kernel_cmdline: The kernel command-line. + """ + + TAG = 3 + SIZE = 24 + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'L' # flags + 'L') # cmdline length (bytes) + + FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED = (1 << 0) + FLAGS_USE_ONLY_IF_HASHTREE_DISABLED = (1 << 1) + + def __init__(self, data=None): + """Initializes a new kernel cmdline descriptor. + + Arguments: + data: If not None, must be a bytearray of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + AvbDescriptor.__init__(self, None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, self.flags, kernel_cmdline_length) = ( + struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])) + expected_size = round_to_multiple(self.SIZE - 16 + kernel_cmdline_length, + 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a kernel cmdline ' + 'descriptor.') + # Nuke NUL-bytes at the end. + self.kernel_cmdline = str(data[self.SIZE:(self.SIZE + + kernel_cmdline_length)]) + # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8. + self.kernel_cmdline.decode('utf-8') + else: + self.flags = 0 + self.kernel_cmdline = '' + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Kernel Cmdline descriptor:\n') + o.write(' Flags: {}\n'.format(self.flags)) + o.write(' Kernel Cmdline: {}\n'.format(repr( + self.kernel_cmdline))) + + def encode(self): + """Serializes the descriptor. + + Returns: + A bytearray() with the descriptor data. + """ + encoded_str = self.kernel_cmdline.encode('utf-8') + num_bytes_following = (self.SIZE + len(encoded_str) - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + self.flags, len(encoded_str)) + padding = struct.pack(str(padding_size) + 'x') + ret = desc + encoded_str + padding + return bytearray(ret) + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + # Nothing to verify. + return True + + +class AvbChainPartitionDescriptor(AvbDescriptor): + """A class for chained partition descriptors. + + See the |AvbChainPartitionDescriptor| C struct for more information. + + Attributes: + rollback_index_location: The rollback index location to use. + partition_name: Partition name. + public_key: Bytes for the public key. + """ + + TAG = 4 + RESERVED = 64 + SIZE = 28 + RESERVED + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'L' # rollback_index_location + 'L' # partition_name_size (bytes) + 'L' + # public_key_size (bytes) + str(RESERVED) + 's') # reserved + + def __init__(self, data=None): + """Initializes a new chain partition descriptor. + + Arguments: + data: If not None, must be a bytearray of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + AvbDescriptor.__init__(self, None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, self.rollback_index_location, + partition_name_len, + public_key_len, _) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]) + expected_size = round_to_multiple( + self.SIZE - 16 + partition_name_len + public_key_len, 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a chain partition ' + 'descriptor.') + o = 0 + self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o + + partition_name_len)]) + # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8. + self.partition_name.decode('utf-8') + o += partition_name_len + self.public_key = data[(self.SIZE + o):(self.SIZE + o + public_key_len)] + + else: + self.rollback_index_location = 0 + self.partition_name = '' + self.public_key = bytearray() + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Chain Partition descriptor:\n') + o.write(' Partition Name: {}\n'.format(self.partition_name)) + o.write(' Rollback Index Location: {}\n'.format( + self.rollback_index_location)) + # Just show the SHA1 of the key, for size reasons. + hexdig = hashlib.sha1(self.public_key).hexdigest() + o.write(' Public key (sha1): {}\n'.format(hexdig)) + + def encode(self): + """Serializes the descriptor. + + Returns: + A bytearray() with the descriptor data. + """ + encoded_name = self.partition_name.encode('utf-8') + num_bytes_following = ( + self.SIZE + len(encoded_name) + len(self.public_key) - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + self.rollback_index_location, len(encoded_name), + len(self.public_key), self.RESERVED*'\0') + padding = struct.pack(str(padding_size) + 'x') + ret = desc + encoded_name + self.public_key + padding + return bytearray(ret) + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + value = expected_chain_partitions_map.get(self.partition_name) + if not value: + sys.stderr.write('No expected chain partition for partition {}. Use ' + '--expected_chain_partition to specify expected ' + 'contents or --follow_chain_partitions.\n'. + format(self.partition_name)) + return False + rollback_index_location, pk_blob = value + + if self.rollback_index_location != rollback_index_location: + sys.stderr.write('Expected rollback_index_location {} does not ' + 'match {} in descriptor for partition {}\n'. + format(rollback_index_location, + self.rollback_index_location, + self.partition_name)) + return False + + if self.public_key != pk_blob: + sys.stderr.write('Expected public key blob does not match public ' + 'key blob in descriptor for partition {}\n'. + format(self.partition_name)) + return False + + print('{}: Successfully verified chain partition descriptor matches ' + 'expected data'.format(self.partition_name)) + + return True + +DESCRIPTOR_CLASSES = [ + AvbPropertyDescriptor, AvbHashtreeDescriptor, AvbHashDescriptor, + AvbKernelCmdlineDescriptor, AvbChainPartitionDescriptor +] + + +def parse_descriptors(data): + """Parses a blob of data into descriptors. + + Arguments: + data: A bytearray() with encoded descriptors. + + Returns: + A list of instances of objects derived from AvbDescriptor. For + unknown descriptors, the class AvbDescriptor is used. + """ + o = 0 + ret = [] + while o < len(data): + tag, nb_following = struct.unpack('!2Q', data[o:o + 16]) + if tag < len(DESCRIPTOR_CLASSES): + c = DESCRIPTOR_CLASSES[tag] + else: + c = AvbDescriptor + ret.append(c(bytearray(data[o:o + 16 + nb_following]))) + o += 16 + nb_following + return ret + + +class AvbFooter(object): + """A class for parsing and writing footers. + + Footers are stored at the end of partitions and point to where the + AvbVBMeta blob is located. They also contain the original size of + the image before AVB information was added. + + Attributes: + magic: Magic for identifying the footer, see |MAGIC|. + version_major: The major version of avbtool that wrote the footer. + version_minor: The minor version of avbtool that wrote the footer. + original_image_size: Original image size. + vbmeta_offset: Offset of where the AvbVBMeta blob is stored. + vbmeta_size: Size of the AvbVBMeta blob. + """ + + MAGIC = 'AVBf' + SIZE = 64 + RESERVED = 28 + FOOTER_VERSION_MAJOR = AVB_FOOTER_VERSION_MAJOR + FOOTER_VERSION_MINOR = AVB_FOOTER_VERSION_MINOR + FORMAT_STRING = ('!4s2L' # magic, 2 x version. + 'Q' # Original image size. + 'Q' # Offset of VBMeta blob. + 'Q' + # Size of VBMeta blob. + str(RESERVED) + 'x') # padding for reserved bytes + + def __init__(self, data=None): + """Initializes a new footer object. + + Arguments: + data: If not None, must be a bytearray of size 4096. + + Raises: + LookupError: If the given footer is malformed. + struct.error: If the given data has no footer. + """ + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (self.magic, self.version_major, self.version_minor, + self.original_image_size, self.vbmeta_offset, + self.vbmeta_size) = struct.unpack(self.FORMAT_STRING, data) + if self.magic != self.MAGIC: + raise LookupError('Given data does not look like a AVB footer.') + else: + self.magic = self.MAGIC + self.version_major = self.FOOTER_VERSION_MAJOR + self.version_minor = self.FOOTER_VERSION_MINOR + self.original_image_size = 0 + self.vbmeta_offset = 0 + self.vbmeta_size = 0 + + def encode(self): + """Gets a string representing the binary encoding of the footer. + + Returns: + A bytearray() with a binary representation of the footer. + """ + return struct.pack(self.FORMAT_STRING, self.magic, self.version_major, + self.version_minor, self.original_image_size, + self.vbmeta_offset, self.vbmeta_size) + + +# Android Firmware Transparency Log Data Structures + + +class AvbIcpHeader(object): + """A class for the transparency log inclusion proof header. + + Attributes: + magic: Magic for identifying the ICP header. + required_icp_version_major: The major version of AVB that wrote the entry. + required_icp_version_minor: The minor version of AVB that wrote the entry. + algorithm: Hash algorithm used. ID is defined in ALGORITHMS. + icp_count: Number of inclusion proofs represented in this structure. + """ + + SIZE = 18 # The size of the structure, in bytes + MAGIC = 'AFTL' + FORMAT_STRING = ('!4s2L' # magic, major & minor version + 'L' # algorithm type for transparency log + 'H') # number of inclusion proof entries + + def __init__(self, data=None): + """Initializes a new transparency header object. + + Arguments: + data: If not None, must be a bytearray of size == 18. + + Raises: + AvbError: If invalid structure for AvbIcpHeader. + """ + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (self.magic, self.required_icp_version_major, + self.required_icp_version_minor, self.algorithm, + self.icp_count) = struct.unpack(self.FORMAT_STRING, data) + else: + self.magic = self.MAGIC + self.required_icp_version_major = AVB_VERSION_MAJOR + self.required_icp_version_minor = AVB_VERSION_MINOR + self.algorithm = 0 + self.icp_count = 0 + if not self.is_valid(): + raise AvbError('Invalid structure for AvbIcpHeader') + + def save(self, output): + """Serializes the transparency header (18) to disk. + + Arguments: + output: The object to write the header to. + + Raises: + AvbError if invalid structure for AvbIcpHeader. + """ + output.write(self.encode()) + + def encode(self): + """Serializes the header (18) to a bytearray(). + + Returns: + A bytearray() with the encoded header. + + Raises: + AvbError: If invalid structure for AvbIcpHeader. + """ + if not self.is_valid(): + raise AvbError('Invalid structure for AvbIcpHeader') + return struct.pack(self.FORMAT_STRING, self.magic, + self.required_icp_version_major, + self.required_icp_version_minor, + self.algorithm, self.icp_count) + + def is_valid(self): + """Ensures that values in an AvbIcpHeader structure are sane. + + Returns: + True if the values in the AvbIcpHeader are sane, False otherwise. + """ + if self.magic != AvbIcpHeader.MAGIC: + sys.stderr.write( + 'ICP Header: magic value mismatch: {}\n'.format(self.magic)) + return False + + if self.required_icp_version_major > AVB_VERSION_MAJOR: + sys.stderr.write('ICP header: major version mismatch: {}\n'.format( + self.required_icp_version_major)) + return False + + if self.required_icp_version_minor > AVB_VERSION_MINOR: + sys.stderr.write('ICP header: minor version mismatch: {}\n'.format( + self.required_icp_version_minor)) + return False + + if self.algorithm < 0 or self.algorithm >= len(ALGORITHMS): + sys.stderr.write( + 'ICP header: algorithm identifier out of range: {}\n'.format( + self.algorithm)) + return False + + if self.icp_count < 0: + sys.stderr.write( + 'ICP header: ICP entry count out of range: {}\n'.format( + self.icp_count)) + return False + return True + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Major version: {}\n'.format( + self.required_icp_version_major)) + o.write(' Minor version: {}\n'.format( + self.required_icp_version_minor)) + o.write(' Algorithm: {}\n'.format( + lookup_algorithm_by_type(self.algorithm)[0])) + o.write(' ICP entries count: {}\n'.format( + self.icp_count)) + + +def check_signature(log_root, log_root_sig, + transparency_log_pub_key): + """Validates the signature provided by the transparency log. + + Arguments: + log_root: The transparency log_root data structure. + log_root_sig: The signature of the transparency log_root data structure. + transparency_log_pub_key: The trusted public key of the transparency log. + + Returns: + True if the signature check passes, otherwise False. + """ + + logsig_tmp = tempfile.NamedTemporaryFile() + logsig_tmp.write(log_root_sig) + logsig_tmp.flush() + logroot_tmp = tempfile.NamedTemporaryFile() + logroot_tmp.write(log_root) + logroot_tmp.flush() + + p = subprocess.Popen(['openssl', 'dgst', '-sha256', '-verify', + transparency_log_pub_key, + '-signature', logsig_tmp.name, logroot_tmp.name], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + (_, openssl_err) = p.communicate() + retcode = p.wait() + if not retcode: + return True + sys.stderr.write('openssl status {}'.format(openssl_err)) + return False + + +class AvbIcpSignedRootBlob(object): + """A class for the components required to validate the incusion proof. + + This class contains the signed tree root components required to verify + an inclusion proof given a list of hashes. + + Attributes: + leaf_hash: The hash of the leaf corresponding with this log entry. + tree_size: The size of the Merkle tree. + log_root: The transparency log_root data structure. + root_hash: The calculated root hash of the Merkle tree. + log_root_sig: The signed root hash. Used to verify the ICP. + """ + # TODO(danielaustin): Match hash and signature size to algorithm value. + SIZE = 645 + FORMAT_STRING = ('!32s' # The leaf hash corresponding to this vbmeta. + 'Q' # The Merkle tree size + '61s' # The log_root structure that is signed + '32s' # The Merkle tree root hash. + '512s') # The log_root signed with the transparency log key. + + def __init__(self, data=None): + """Initializes a new signed_root_blob structure. + + Arguments: + data: If not None, must be a bytearray of size |SIZE|. + + Raises: + AvbError: If data does not represent a well-formed AvbIcpSignedRootBlob. + """ + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (self.leaf_hash, self.tree_size, self.log_root, + self.root_hash, self.log_root_sig) = struct.unpack( + self.FORMAT_STRING, data) + else: + self.leaf_hash = bytearray() + self.tree_size = 0 + self.log_root = bytearray() + self.root_hash = bytearray() + self.log_root_sig = '' + + if not self.is_valid(): + raise AvbError('Invalid structure for AvbIcpSignedBlob') + + def translate_afi_response(self, afi_response): + """Translates an AddFirmwareImageResponse message to AvbIcpSignedRootBlob. + + Arguments: + afi_response: An AddFirmwareImageResponse proto message. + + Raises: + AvbError: If unsupported hash size is detected. + """ + # Do the hash calculation + self.leaf_hash = rfc6962_hash_leaf(afi_response.vbmeta_leaf) + self.log_root = afi_response.vbmeta_proof.sth.log_root + self.log_root_sig = str(afi_response.vbmeta_proof.sth.log_root_signature) + # Partial format string to extract the tree_size and root_hash from + # the log_root. THis structure is defined: + # https://github.com/google/trillian/blob/master/trillian.proto#L255 + + # TODO(danielaustin): Make this into a class. + partial_log_format_string = ('!H' # Version + 'Q' # tree_size + 'B' # hash_size, verify this is 32 for now + '32s') # The root_hash + + (log_root_version, self.tree_size, root_hash_size, + self.root_hash) = struct.unpack(partial_log_format_string, + self.log_root[0:43]) + if log_root_version != 1: + raise AvbError('Unsupported log root version: {}'.format( + log_root_version)) + if len(self.root_hash) != root_hash_size: + raise AvbError('Unsupported hash size.') + + def encode(self): + """Serializes the AvbSignedRootBlob structure (584) to a bytearray. + + Returns: + A bytearray with the AvbSignedRootBlob. + + Raises: + AvbError: If data does not represent a well-formed AvbIcpSignedRootBlob. + """ + if not self.is_valid(): + raise AvbError('Invalid structure for AvbIcpSignedRootBlob') + + return struct.pack(self.FORMAT_STRING, + str(self.leaf_hash), + self.tree_size, + str(self.log_root), + str(self.root_hash), + str(self.log_root_sig)) + + def is_valid(self): + """Ensures that values in the AvbIcpSignedRootBlob are sane. + + Returns: + True if the values in the AvbIcpSignedRootBlob are sane, False otherwise. + """ + # TODO(danielaustin): match these up with algorithm instead of defaults. + # All structures being of size 0 is valid + if (not self.leaf_hash and self.tree_size == 0 and + not self.root_hash and not self.log_root_sig): + return True + if len(self.leaf_hash) != 32: + sys.stderr.write('AvbIcpSignedRootBlob: Bad leaf_hash size {}'.format( + len(self.leaf_hash))) + return False + if self.tree_size < 0: + sys.stderr.write('AvbIcpSignedRootBlob: Bad tree_size value {}'.format( + self.tree_size)) + return False + if len(self.root_hash) != 32: + sys.stderr.write('AvbIcpSignedRootBlob: Bad root_hash size {}'.format( + len(self.root_hash))) + return False + if len(self.log_root_sig) != 512: + sys.stderr.write('AvbIcpSignedRootBlob: Bad log_root_sig size {}'.format( + len(self.log_root_sig))) + return False + return True + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Leaf hash: {}\n'.format( + binascii.hexlify(self.leaf_hash))) + o.write(' Tree size: {}\n'.format( + self.tree_size)) + o.write(' Log root: {}\n'.format( + binascii.hexlify(self.log_root))) + o.write(' Root hash: {}\n'.format( + binascii.hexlify(self.root_hash))) + + +class AvbIcpEntry(object): + """A class for the transparency log inclusion proof entries. + + The data that represents each of the components of the ICP entry are stored + immediately following the ICP entry header. The format is log_url, + SignedLogRoot, and inclusion proof hashes. + + Attributes: + log_url_size: Length of the string representing the transparency log URL. + leaf_index: Leaf index in the transparency log representing this entry. + signed_root_blob_size: Size of the SignedLogRoot for the transparency log; + treat as an opaque blob for now. + proof_hash_count: Number of hashes comprising the inclusion proof. + proof_size: The total size of the inclusion proof, in bytes. + next_entry: 1 if there is a next entry, 0 otherwise. + log_url: The URL for the transparency log that generated this inclusion + proof. + signed_root_blob: The data comprising the signed tree head structure. + proofs: The hashes comprising the inclusion proof. + + """ + SIZE = 22 # The size of the structure, in bytes + FORMAT_STRING = ('!L' # transparency log server url size + 'Q' # leaf index + 'L' # signed tree root blob size + 'B' # number of hashes in the inclusion proof + 'L' # size of the inclusion proof in bytes + 'B') # next entry marker + # These are used to capture the log_url, signed_root_blob, + # and the proofs elements for the encode & save function. + + def __init__(self, data=None): + """Initializes a new ICP entry object. + + Arguments: + data: If not None, must be a bytearray of size >= 22. + + Raises: + AvbError: If data does not represent a well-formed AvbIcpEntry. + """ + # Assert the header structure is of a sane size. + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + # Deserialize the header from the data blob. + (self.log_url_size, self.leaf_index, self.signed_root_blob_size, + self.proof_hash_count, self.proof_size, self.next_entry) = struct.unpack( + self.FORMAT_STRING, data[0:self.SIZE]) + if len(data) >= self.SIZE: + # There's more data. Ensure the data entry size is valid. + if len(data) != self.get_expected_size(): + if self.next_entry == 0: + raise AvbError('ICP entry size is not valid {}/{}.' + .format(len(data), self.get_expected_size())) + # Deserialize ICP entry components from the data blob. + expected_format_string = '{}s{}s{}s'.format( + self.log_url_size, + AvbIcpSignedRootBlob.SIZE, + self.proof_size) + + (self.log_url, signed_root_blob_bytes, proof_bytes) = struct.unpack( + expected_format_string, data[self.SIZE:self.get_expected_size()]) + self.signed_root_blob = AvbIcpSignedRootBlob(signed_root_blob_bytes) + self.proofs = [] + if self.proof_hash_count > 0: + proof_idx = 0 + hash_size = self.proof_size // self.proof_hash_count + for _ in range(self.proof_hash_count): + proof = proof_bytes[proof_idx:(proof_idx+hash_size)] + self.proofs.append(proof) + proof_idx += hash_size + else: + self.log_url_size = 0 + self.leaf_index = 0 + self.signed_root_blob_size = 0 + self.proof_hash_count = 0 + self.proof_size = 0 + self.next_entry = 0 + self.log_url = '' + self.signed_root_blob = AvbIcpSignedRootBlob() + self.proofs = [] + if not self.is_valid(): + raise AvbError('Invalid structure for AvbIcpEntry') + + def set_log_url(self, log_url): + """Sets the log_url and log_url_size elements in the AvbIcpEntry. + + Arguments: + log_url: The string representing the transparency log URL. + """ + self.log_url = log_url + self.log_url_size = len(log_url) + + def set_signed_root_blob(self, signed_root_blob): + """Sets signed_root_blob and signed_root_blob_size. + + Arguments: + signed_root_blob: An AvbIcpSignedRootBlob containing the SignedLogRoot + for the transparency log. + """ + self.signed_root_blob = signed_root_blob + self.signed_root_blob_size = signed_root_blob.SIZE + + def set_proofs(self, proofs): + """Sets the proof_hash_count, proofs, and proof_size. + + Arguments: + proofs: A bytearray of concatenated hashes comprising the inclusion proof. + """ + self.proof_hash_count = 0 + self.proofs = proofs + proof_size = 0 + for proof in proofs: + proof_size += len(proof) + self.proof_hash_count += 1 + self.proof_size = proof_size + + def verify_icp(self, transparency_log_pub_key): + """Verifies the contained inclusion proof given the public log key. + + Arguments: + transparency_log_pub_key: The trusted public key for the log. + + Returns: + True if the calculated signature matches AvbIcpEntry's. False otherwise. + """ + calc_root = root_from_icp(self.leaf_index, self.signed_root_blob.tree_size, + self.proofs, self.signed_root_blob.leaf_hash) + if (calc_root == self.signed_root_blob.root_hash) and check_signature( + self.signed_root_blob.log_root, self.signed_root_blob.log_root_sig, + transparency_log_pub_key): + return True + return False + + def save(self, output): + """Serializes the transparency header (22) and data to disk. + + Arguments: + output: The object to write the header to. + + Raises: + AvbError: If invalid entry structure. + """ + output.write(self.encode()) + + def encode(self): + """Serializes the header (22) and data to a bytearray(). + + Returns: + A bytearray() with the encoded header. + + Raises: + AvbError: If invalid entry structure. + """ + proof_bytes = bytearray() + if not self.is_valid(): + raise AvbError('Invalid AvbIcpEntry structure') + expected_format_string = '{}{}s{}s{}s'.format( + self.FORMAT_STRING, self.log_url_size, + self.signed_root_blob.SIZE, + self.proof_size) + + for proof in self.proofs: + proof_bytes.extend(proof) + + return struct.pack(expected_format_string, + self.log_url_size, self.leaf_index, + self.signed_root_blob_size, self.proof_hash_count, + self.proof_size, self.next_entry, self.log_url, + self.signed_root_blob.encode(), + str(proof_bytes)) + + # TODO(danielaustin): Add unit test. + def translate_response(self, transparency_log, afi_response): + """Takes an AddFirmwareInfoResponse object and translates to an AvbIcpEntry. + + Arguments: + transparency_log: String representing the transparency log URL. + afi_response: The AddFirmwareResponse object to translate. + """ + self.set_log_url(transparency_log) + self.leaf_index = afi_response.vbmeta_proof.proof.leaf_index + self.signed_root_blob = AvbIcpSignedRootBlob() + self.signed_root_blob.translate_afi_response(afi_response) + self.signed_root_blob_size = self.signed_root_blob.SIZE + # Calculate the number of hashes. + proof_hashes = afi_response.vbmeta_proof.proof.hashes + self.set_proofs(proof_hashes) + + def get_expected_size(self): + """Gets the expected size of the full entry out of the header. + + Returns: + The expected size of the AvbIcpEntry from the header. + """ + return (self.SIZE + self.log_url_size + + self.signed_root_blob_size + self.proof_size) + + def is_valid(self): + """Ensures that values in an AvbIcpEntry structure are sane. + + Returns: + True if the values in the AvbIcpEntry are sane, False otherwise. + """ + if ((self.log_url and self.log_url_size != len(self.log_url)) + or (not self.log_url and self.log_url_size != 0)): + sys.stderr.write('ICP entry: invalid URL size: {}\n' + .format(self.log_url_size)) + return False + + if self.leaf_index < 0: + sys.stderr.write('ICP entry: leaf index out of range: ' + '{}\n'.format(self.leaf_index)) + return False + + if not self.signed_root_blob or not self.signed_root_blob.is_valid(): + sys.stderr.write('ICP entry: invalid AvbIcpSignedRootBlob\n') + return False + + if (self.signed_root_blob_size != 0) and ( + self.signed_root_blob_size != self.signed_root_blob.SIZE): + sys.stderr.write('ICP entry: invalid signed root blob size: ' + '{}, should be {}\n'.format( + self.signed_root_blob_size, + self.signed_root_blob.SIZE)) + return False + + if self.proof_hash_count < 0: + sys.stderr.write('ICP entry: invalid proof count: {}\n'.format( + self.proof_hash_count)) + return False + + proof_size = 0 + if self.proofs: + for proof in self.proofs: + proof_size += len(proof) + if self.proof_size != proof_size: + sys.stderr.write('ICP entry: invalid transparency log proof size: ') + sys.stderr.write('{}, calculated {}\n'.format(self.proof_size, + proof_size)) + return False + elif self.proof_size != 0: + sys.stderr.write('ICP entry: invalid transparency log proof size ' + '(should be 0): {}'.format(self.proof_size)) + return False + if self.next_entry != 0 and self.next_entry != 1: + sys.stderr.write('ICP entry: invalid next entry value: {}\n'.format( + self.next_entry)) + return False + return True + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Transparency Log: {}\n'.format(self.log_url)) + o.write(' Leaf index: {}\n'.format(self.leaf_index)) + o.write(' Next entry: {}\n'.format(self.next_entry)) + o.write(' ICP hashes: ') + for i, proof_hash in enumerate(self.proofs): + if i != 0: + o.write(' ' * 24) + o.write('{}\n'.format(binascii.hexlify(proof_hash))) + + +class AvbIcpBlob(object): + """A class for the transparency log inclusion proof blob. + + This encapsulates an AFTL ICP section with all information required to + validate an inclusion proof. + + Attributes: + icp_header: A header for the section. + icp_entries: A list of AvbIcpEntry objects representing the inclusion + proofs. + """ + + def __init__(self, data=None): + """Initializes a new AvbIcpBlob section. + + Arguments: + data: If not None, must be a bytearray representing an AvbIcpBlob. + + Raises: + AvbError: If the data does not represent a well-formed AvbIcpBlob. + """ + if data: + icp_header_bytes = data[0:AvbIcpHeader.SIZE] + self.icp_header = AvbIcpHeader(icp_header_bytes) + if not self.icp_header.is_valid(): + raise AvbError('Invalid ICP header.') + icp_count = self.icp_header.icp_count + algorithm_id = self.icp_header.algorithm + # TODO(danielaustin): make use of proof_hash_size. + # pylint: disable=unused-variable + proof_hash_size = lookup_hash_size_by_type(algorithm_id) + + # Jump past the header for entry deserialization. + icp_index = AvbIcpHeader.SIZE + # Validate each entry. + self.icp_entries = [] + # Add_icp_entry updates entries and header, so set header count to + # compensate. + self.icp_header.icp_count = 0 + for i in range(icp_count): + # Get the entry header from the ICP blob. + cur_icp_entry = AvbIcpEntry(data[icp_index:]) + cur_icp_entry_size = cur_icp_entry.get_expected_size() + # Now validate the entry structure. + if not cur_icp_entry.is_valid(): + raise AvbError('Validation of ICP entry failed.') + self.add_icp_entry(cur_icp_entry) + # Check if there is a next entry. + if cur_icp_entry.next_entry == 0: + if i != icp_count - 1: + raise AvbError('ICP entry count mismatch') + break + icp_index += cur_icp_entry_size + else: + self.icp_header = AvbIcpHeader() + self.icp_entries = [] + if not self.is_valid(): + raise AvbError('Malformed ICP blob') + + def set_algorithm(self, algorithm_id): + """Sets algorithm to be used by the inclusion proofs in AvbIcpBlob.""" + self.icp_header.algorithm = algorithm_id + + def add_icp_entry(self, avb_icp_entry): + """Adds a new AvbIcpEntry to the AvbIcpBlob, updating fields as necessary. + + Arguments: + avb_icp_entry: An AvbIcpEntry structure. + """ + + # Set the next entry field to denote that a new ICP entry will follow. + if self.icp_entries: + self.icp_entries[-1].next_entry = 1 + self.icp_entries.append(avb_icp_entry) + self.icp_header.icp_count += 1 + + def save(self, output): + """Serializes the AvbIcpBlob to disk. + + Arguments: + output: The object to write the blob to. + + Raises: + AvbError: If invalid blob structure. + """ + output.write(self.encode()) + + def encode(self): + """Serialize the AvbIcpBlob to a bytearray(). + + Returns: + A bytearray() with the encoded header. + + Raises: + AvbError: If invalid blob structure. + """ + # The header and entries are guaranteed to be valid when encode is called. + # Check the entire structure as a whole. + if not self.is_valid(): + raise AvbError('Invalid AvbIcpBlob structure.') + + icp_blob = bytearray() + icp_blob.extend(self.icp_header.encode()) + for icp_entry in self.icp_entries: + icp_blob.extend(icp_entry.encode()) + return icp_blob + + def is_valid(self): + """Ensures that values in the AvbIcpBlob are sane. + + Returns: + True if the values in the AvbIcpBlob are sane, False otherwise. + """ + if not self.icp_header.is_valid(): + return False + + if self.icp_header.icp_count != len(self.icp_entries): + return False + + for icp_entry in self.icp_entries: + if not icp_entry.is_valid(): + return False + return True + + +# AFTL Merkle Tree Functionality +# TODO(danielaustin): Encapsulate this behavior in a class. +def rfc6962_hash_leaf(leaf): + """RFC6962 hashing function for hashing leaves of a Merkle tree. + + Arguments: + leaf: A bytearray containing the Merkle tree leaf to be hashed. + + Returns: + A bytearray containing the RFC6962 SHA256 hash of the leaf. + """ + hasher = hashlib.sha256() + # RFC6962 states a '0' byte should be prepended to the data. + # This is done in conjunction with the '1' byte for non-leaf + # nodes for 2nd preimage attack resistance. + hasher.update(b'\x00') + hasher.update(leaf) + return hasher.digest() + + +def rfc6962_hash_children(l, r): + """Calculates the inner Merkle tree node hash of child nodes l and r. + + Arguments: + l: A bytearray containing the left child node to be hashed. + r: A bytearray containing the right child node to be hashed. + + Returns: + A bytearray containing the RFC6962 SHA256 hash of 1|l|r. + """ + hasher = hashlib.sha256() + # RFC6962 states a '1' byte should be prepended to the concatenated data. + # This is done in conjunction with the '0' byte for leaf + # nodes for 2nd preimage attack resistance. + hasher.update(b'\x01') + hasher.update(l) + hasher.update(r) + return hasher.digest() + + +def chain_border_right(seed, proof): + """Computes a subtree hash along the left-side tree border. + + Arguments: + seed: A bytearray containing the starting hash. + proof: A list of bytearrays representing the hashes in the inclusion proof. + + Returns: + A bytearray containing the left-side subtree hash. + """ + for h in proof: + seed = rfc6962_hash_children(h, seed) + return seed + + +def chain_inner(seed, proof, leaf_index): + """Computes a subtree hash on or below the tree's right border. + + Arguments: + seed: A bytearray containing the starting hash. + proof: A list of bytearrays representing the hashes in the inclusion proof. + leaf_index: The current leaf index. + + Returns: + A bytearray containing the subtree hash. + """ + for i, h in enumerate(proof): + if leaf_index >> i & 1 == 0: + seed = rfc6962_hash_children(seed, h) + else: + seed = rfc6962_hash_children(h, seed) + return seed + + +def root_from_icp(leaf_index, tree_size, proof, leaf_hash): + """Calculates the expected Merkle tree root hash. + + Arguments: + leaf_index: The current leaf index. + tree_size: The number of nodes in the Merkle tree. + proof: A list of bytearrays containing the inclusion proof. + leaf_hash: A bytearray containing the initial leaf hash. + + Returns: + A bytearray containing the calculated Merkle tree root hash. + + Raises: + AvbError: If invalid parameters are passed in. + """ + if leaf_index < 0: + raise AvbError('Invalid leaf_index value: {}'.format(leaf_index)) + if tree_size < 0: + raise AvbError('Invalid tree_size value: {}'.format(tree_size)) + if leaf_index >= tree_size: + err_str = 'leaf_index cannot be equal or larger than tree_size: {}, {}' + raise AvbError(err_str.format(leaf_index, tree_size)) + + # Calculate the point to split the proof into two parts. + # The split is where the paths to leaves diverge. + inner = (leaf_index ^ (tree_size - 1)).bit_length() + result = chain_inner(leaf_hash, proof[:inner], leaf_index) + result = chain_border_right(result, proof[inner:]) + return result + + +class AvbVBMetaHeader(object): + """A class for parsing and writing AVB vbmeta images. + + The attributes correspond to the |AvbVBMetaImageHeader| struct defined in + avb_vbmeta_image.h. + + Attributes: + magic: Four bytes equal to "AVB0" (AVB_MAGIC). + required_libavb_version_major: The major version of libavb required for this + header. + required_libavb_version_minor: The minor version of libavb required for this + header. + authentication_data_block_size: The size of the signature block. + auxiliary_data_block_size: The size of the auxiliary data block. + algorithm_type: The verification algorithm used, see |AvbAlgorithmType| + enum. + hash_offset: Offset into the "Authentication data" block of hash data. + hash_size: Length of the hash data. + signature_offset: Offset into the "Authentication data" block of signature + data. + signature_size: Length of the signature data. + public_key_offset: Offset into the "Auxiliary data" block of public key + data. + public_key_size: Length of the public key data. + public_key_metadata_offset: Offset into the "Auxiliary data" block of public + key metadata. + public_key_metadata_size: Length of the public key metadata. Must be set to + zero if there is no public key metadata. + descriptors_offset: Offset into the "Auxiliary data" block of descriptor + data. + descriptors_size: Length of descriptor data. + rollback_index: The rollback index which can be used to prevent rollback to + older versions. + flags: Flags from the AvbVBMetaImageFlags enumeration. This must be set to + zero if the vbmeta image is not a top-level image. + release_string: The release string from avbtool, e.g. "avbtool 1.0.0" or + "avbtool 1.0.0 xyz_board Git-234abde89". Is guaranteed to be NUL + terminated. Applications must not make assumptions about how this + string is formatted. + """ + + SIZE = 256 + + # Keep in sync with |reserved0| and |reserved| field of + # |AvbVBMetaImageHeader|. + RESERVED0 = 4 + RESERVED = 80 + + # Keep in sync with |AvbVBMetaImageHeader|. + FORMAT_STRING = ('!4s2L' # magic, 2 x version + '2Q' # 2 x block size + 'L' # algorithm type + '2Q' # offset, size (hash) + '2Q' # offset, size (signature) + '2Q' # offset, size (public key) + '2Q' # offset, size (public key metadata) + '2Q' # offset, size (descriptors) + 'Q' # rollback_index + 'L' + # flags + str(RESERVED0) + 'x' + # padding for reserved bytes + '47sx' + # NUL-terminated release string + str(RESERVED) + 'x') # padding for reserved bytes + + def __init__(self, data=None): + """Initializes a new header object. + + Arguments: + data: If not None, must be a bytearray of size 8192. + + Raises: + Exception: If the given data is malformed. + """ + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (self.magic, self.required_libavb_version_major, + self.required_libavb_version_minor, + self.authentication_data_block_size, self.auxiliary_data_block_size, + self.algorithm_type, self.hash_offset, self.hash_size, + self.signature_offset, self.signature_size, self.public_key_offset, + self.public_key_size, self.public_key_metadata_offset, + self.public_key_metadata_size, self.descriptors_offset, + self.descriptors_size, + self.rollback_index, + self.flags, + self.release_string) = struct.unpack(self.FORMAT_STRING, data) + # Nuke NUL-bytes at the end of the string. + if self.magic != 'AVB0': + raise AvbError('Given image does not look like a vbmeta image.') + else: + self.magic = 'AVB0' + # Start by just requiring version 1.0. Code that adds features + # in a future version can use bump_required_libavb_version_minor() to + # bump the minor. + self.required_libavb_version_major = AVB_VERSION_MAJOR + self.required_libavb_version_minor = 0 + self.authentication_data_block_size = 0 + self.auxiliary_data_block_size = 0 + self.algorithm_type = 0 + self.hash_offset = 0 + self.hash_size = 0 + self.signature_offset = 0 + self.signature_size = 0 + self.public_key_offset = 0 + self.public_key_size = 0 + self.public_key_metadata_offset = 0 + self.public_key_metadata_size = 0 + self.descriptors_offset = 0 + self.descriptors_size = 0 + self.rollback_index = 0 + self.flags = 0 + self.release_string = get_release_string() + + def bump_required_libavb_version_minor(self, minor): + """Function to bump required_libavb_version_minor. + + Call this when writing data that requires a specific libavb + version to parse it. + + Arguments: + minor: The minor version of libavb that has support for the feature. + """ + self.required_libavb_version_minor = ( + max(self.required_libavb_version_minor, minor)) + + def save(self, output): + """Serializes the header (256 bytes) to disk. + + Arguments: + output: The object to write the output to. + """ + output.write(struct.pack( + self.FORMAT_STRING, self.magic, self.required_libavb_version_major, + self.required_libavb_version_minor, self.authentication_data_block_size, + self.auxiliary_data_block_size, self.algorithm_type, self.hash_offset, + self.hash_size, self.signature_offset, self.signature_size, + self.public_key_offset, self.public_key_size, + self.public_key_metadata_offset, self.public_key_metadata_size, + self.descriptors_offset, self.descriptors_size, self.rollback_index, + self.flags, self.release_string)) + + def encode(self): + """Serializes the header (256) to a bytearray(). + + Returns: + A bytearray() with the encoded header. + """ + return struct.pack(self.FORMAT_STRING, self.magic, + self.required_libavb_version_major, + self.required_libavb_version_minor, + self.authentication_data_block_size, + self.auxiliary_data_block_size, self.algorithm_type, + self.hash_offset, self.hash_size, self.signature_offset, + self.signature_size, self.public_key_offset, + self.public_key_size, self.public_key_metadata_offset, + self.public_key_metadata_size, self.descriptors_offset, + self.descriptors_size, self.rollback_index, self.flags, + self.release_string) + + +class Avb(object): + """Business logic for avbtool command-line tool.""" + + # Keep in sync with avb_ab_flow.h. + AB_FORMAT_NO_CRC = '!4sBB2xBBBxBBBx12x' + AB_MAGIC = '\0AB0' + AB_MAJOR_VERSION = 1 + AB_MINOR_VERSION = 0 + AB_MISC_METADATA_OFFSET = 2048 + + # Constants for maximum metadata size. These are used to give + # meaningful errors if the value passed in via --partition_size is + # too small and when --calc_max_image_size is used. We use + # conservative figures. + MAX_VBMETA_SIZE = 64 * 1024 + MAX_FOOTER_SIZE = 4096 + + def extract_vbmeta_image(self, output, image_filename, padding_size): + """Implements the 'extract_vbmeta_image' command. + + Arguments: + output: Write vbmeta struct to this file. + image_filename: File to extract vbmeta data from (with a footer). + padding_size: If not 0, pads output so size is a multiple of the number. + + Raises: + AvbError: If there's no footer in the image. + """ + image = ImageHandler(image_filename) + + (footer, _, _, _) = self._parse_image(image) + + if not footer: + raise AvbError('Given image does not have a footer.') + + image.seek(footer.vbmeta_offset) + vbmeta_blob = image.read(footer.vbmeta_size) + output.write(vbmeta_blob) + + if padding_size > 0: + padded_size = round_to_multiple(len(vbmeta_blob), padding_size) + padding_needed = padded_size - len(vbmeta_blob) + output.write('\0' * padding_needed) + + def erase_footer(self, image_filename, keep_hashtree): + """Implements the 'erase_footer' command. + + Arguments: + image_filename: File to erase a footer from. + keep_hashtree: If True, keep the hashtree and FEC around. + + Raises: + AvbError: If there's no footer in the image. + """ + + image = ImageHandler(image_filename) + + (footer, _, descriptors, _) = self._parse_image(image) + + if not footer: + raise AvbError('Given image does not have a footer.') + + new_image_size = None + if not keep_hashtree: + new_image_size = footer.original_image_size + else: + # If requested to keep the hashtree, search for a hashtree + # descriptor to figure out the location and size of the hashtree + # and FEC. + for desc in descriptors: + if isinstance(desc, AvbHashtreeDescriptor): + # The hashtree is always just following the main data so the + # new size is easily derived. + new_image_size = desc.tree_offset + desc.tree_size + # If the image has FEC codes, also keep those. + if desc.fec_offset > 0: + fec_end = desc.fec_offset + desc.fec_size + new_image_size = max(new_image_size, fec_end) + break + if not new_image_size: + raise AvbError('Requested to keep hashtree but no hashtree ' + 'descriptor was found.') + + # And cut... + image.truncate(new_image_size) + + def zero_hashtree(self, image_filename): + """Implements the 'zero_hashtree' command. + + Arguments: + image_filename: File to zero hashtree and FEC data from. + + Raises: + AvbError: If there's no footer in the image. + """ + + image = ImageHandler(image_filename) + + (footer, _, descriptors, _) = self._parse_image(image) + + if not footer: + raise AvbError('Given image does not have a footer.') + + # Search for a hashtree descriptor to figure out the location and + # size of the hashtree and FEC. + ht_desc = None + for desc in descriptors: + if isinstance(desc, AvbHashtreeDescriptor): + ht_desc = desc + break + + if not ht_desc: + raise AvbError('No hashtree descriptor was found.') + + zero_ht_start_offset = ht_desc.tree_offset + zero_ht_num_bytes = ht_desc.tree_size + zero_fec_start_offset = None + zero_fec_num_bytes = 0 + if ht_desc.fec_offset > 0: + if ht_desc.fec_offset != ht_desc.tree_offset + ht_desc.tree_size: + raise AvbError('Hash-tree and FEC data must be adjacent.') + zero_fec_start_offset = ht_desc.fec_offset + zero_fec_num_bytes = ht_desc.fec_size + zero_end_offset = (zero_ht_start_offset + zero_ht_num_bytes + + zero_fec_num_bytes) + image.seek(zero_end_offset) + data = image.read(image.image_size - zero_end_offset) + + # Write zeroes all over hashtree and FEC, except for the first eight bytes + # where a magic marker - ZeroHaSH - is placed. Place these markers in the + # beginning of both hashtree and FEC. (That way, in the future we can add + # options to 'avbtool zero_hashtree' so as to zero out only either/or.) + # + # Applications can use these markers to detect that the hashtree and/or + # FEC needs to be recomputed. + image.truncate(zero_ht_start_offset) + data_zeroed_firstblock = 'ZeRoHaSH' + '\0'*(image.block_size - 8) + image.append_raw(data_zeroed_firstblock) + image.append_fill('\0\0\0\0', zero_ht_num_bytes - image.block_size) + if zero_fec_start_offset: + image.append_raw(data_zeroed_firstblock) + image.append_fill('\0\0\0\0', zero_fec_num_bytes - image.block_size) + image.append_raw(data) + + def resize_image(self, image_filename, partition_size): + """Implements the 'resize_image' command. + + Arguments: + image_filename: File with footer to resize. + partition_size: The new size of the image. + + Raises: + AvbError: If there's no footer in the image. + """ + + image = ImageHandler(image_filename) + + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + + (footer, _, _, _) = self._parse_image(image) + + if not footer: + raise AvbError('Given image does not have a footer.') + + # The vbmeta blob is always at the end of the data so resizing an + # image amounts to just moving the footer around. + + vbmeta_end_offset = footer.vbmeta_offset + footer.vbmeta_size + if vbmeta_end_offset % image.block_size != 0: + vbmeta_end_offset += image.block_size - (vbmeta_end_offset + % image.block_size) + + if partition_size < vbmeta_end_offset + 1*image.block_size: + raise AvbError('Requested size of {} is too small for an image ' + 'of size {}.' + .format(partition_size, + vbmeta_end_offset + 1*image.block_size)) + + # Cut at the end of the vbmeta blob and insert a DONT_CARE chunk + # with enough bytes such that the final Footer block is at the end + # of partition_size. + image.truncate(vbmeta_end_offset) + image.append_dont_care(partition_size - vbmeta_end_offset - + 1*image.block_size) + + # Just reuse the same footer - only difference is that we're + # writing it in a different place. + footer_blob = footer.encode() + footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) + + footer_blob) + image.append_raw(footer_blob_with_padding) + + def set_ab_metadata(self, misc_image, slot_data): + """Implements the 'set_ab_metadata' command. + + The |slot_data| argument must be of the form 'A_priority:A_tries_remaining: + A_successful_boot:B_priority:B_tries_remaining:B_successful_boot'. + + Arguments: + misc_image: The misc image to write to. + slot_data: Slot data as a string + + Raises: + AvbError: If slot data is malformed. + """ + tokens = slot_data.split(':') + if len(tokens) != 6: + raise AvbError('Malformed slot data "{}".'.format(slot_data)) + a_priority = int(tokens[0]) + a_tries_remaining = int(tokens[1]) + a_success = True if int(tokens[2]) != 0 else False + b_priority = int(tokens[3]) + b_tries_remaining = int(tokens[4]) + b_success = True if int(tokens[5]) != 0 else False + + ab_data_no_crc = struct.pack(self.AB_FORMAT_NO_CRC, + self.AB_MAGIC, + self.AB_MAJOR_VERSION, self.AB_MINOR_VERSION, + a_priority, a_tries_remaining, a_success, + b_priority, b_tries_remaining, b_success) + # Force CRC to be unsigned, see https://bugs.python.org/issue4903 for why. + crc_value = binascii.crc32(ab_data_no_crc) & 0xffffffff + ab_data = ab_data_no_crc + struct.pack('!I', crc_value) + misc_image.seek(self.AB_MISC_METADATA_OFFSET) + misc_image.write(ab_data) + + def info_image(self, image_filename, output): + """Implements the 'info_image' command. + + Arguments: + image_filename: Image file to get information from (file object). + output: Output file to write human-readable information to (file object). + """ + + image = ImageHandler(image_filename) + + o = output + + (footer, header, descriptors, image_size) = self._parse_image(image) + + if footer: + o.write('Footer version: {}.{}\n'.format(footer.version_major, + footer.version_minor)) + o.write('Image size: {} bytes\n'.format(image_size)) + o.write('Original image size: {} bytes\n'.format( + footer.original_image_size)) + o.write('VBMeta offset: {}\n'.format(footer.vbmeta_offset)) + o.write('VBMeta size: {} bytes\n'.format(footer.vbmeta_size)) + o.write('--\n') + + (alg_name, _) = lookup_algorithm_by_type(header.algorithm_type) + + o.write('Minimum libavb version: {}.{}{}\n'.format( + header.required_libavb_version_major, + header.required_libavb_version_minor, + ' (Sparse)' if image.is_sparse else '')) + o.write('Header Block: {} bytes\n'.format(AvbVBMetaHeader.SIZE)) + o.write('Authentication Block: {} bytes\n'.format( + header.authentication_data_block_size)) + o.write('Auxiliary Block: {} bytes\n'.format( + header.auxiliary_data_block_size)) + o.write('Algorithm: {}\n'.format(alg_name)) + o.write('Rollback Index: {}\n'.format(header.rollback_index)) + o.write('Flags: {}\n'.format(header.flags)) + o.write('Release String: \'{}\'\n'.format( + header.release_string.rstrip('\0'))) + + # Print descriptors. + num_printed = 0 + o.write('Descriptors:\n') + for desc in descriptors: + desc.print_desc(o) + num_printed += 1 + if num_printed == 0: + o.write(' (none)\n') + + def info_image_icp(self, image_filename, output): + """Implements the 'info_image_icp' command. + + Arguments: + image_filename: Image file to get information from. + output: Output file to write human-readable information to (file object). + """ + image = ImageHandler(image_filename) + o = output + (footer, header, _, _) = self._parse_image(image) + + offset = 0 + if footer: + offset = footer.vbmeta_offset + image.seek(offset + + header.SIZE + + header.authentication_data_block_size + + header.auxiliary_data_block_size) + + # TODO(jpm): Fix up AvbIcp* records so the length of data to be read + # can be determined more easily. + icp_bytes = image.read(100000) + if not icp_bytes or len(icp_bytes) < 4 or icp_bytes[0:4] != AvbIcpHeader.MAGIC: + sys.stderr.write('Image does not contain AFTL inclusion proofs.\n') + return + + icp_blob = AvbIcpBlob(icp_bytes) + o.write('Android Firmware Transparency Descriptor:\n') + o.write(' Header:\n') + icp_blob.icp_header.print_desc(o) + for i, icp_entry in enumerate(icp_blob.icp_entries): + o.write(' Entry #{}:\n'.format(i + 1)) + icp_entry.print_desc(o) + o.write(' Signed Root Blob:\n') + icp_entry.signed_root_blob.print_desc(o) + + def verify_image(self, image_filename, key_path, expected_chain_partitions, + follow_chain_partitions, accept_zeroed_hashtree): + """Implements the 'verify_image' command. + + Arguments: + image_filename: Image file to get information from (file object). + key_path: None or check that embedded public key matches key at given + path. + expected_chain_partitions: List of chain partitions to check or None. + follow_chain_partitions: + If True, will follows chain partitions even when not specified with + the --expected_chain_partition option + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Raises: + AvbError: If verification of the image fails. + """ + expected_chain_partitions_map = {} + if expected_chain_partitions: + for cp in expected_chain_partitions: + cp_tokens = cp.split(':') + if len(cp_tokens) != 3: + raise AvbError('Malformed chained partition "{}".'.format(cp)) + partition_name = cp_tokens[0] + rollback_index_location = int(cp_tokens[1]) + file_path = cp_tokens[2] + pk_blob = open(file_path).read() + expected_chain_partitions_map[partition_name] = ( + rollback_index_location, pk_blob) + + image_dir = os.path.dirname(image_filename) + #image_ext = os.path.splitext(image_filename)[1] + image_ext = image_filename[image_filename.index('.'):] + + key_blob = None + if key_path: + print('Verifying image {} using key at {}'.format(image_filename, + key_path)) + key_blob = encode_rsa_key(key_path) + else: + print('Verifying image {} using embedded public key'.format( + image_filename)) + + image = ImageHandler(image_filename) + (footer, header, descriptors, _) = self._parse_image(image) + offset = 0 + if footer: + offset = footer.vbmeta_offset + + image.seek(offset) + vbmeta_blob = image.read(header.SIZE + + header.authentication_data_block_size + + header.auxiliary_data_block_size) + + alg_name, _ = lookup_algorithm_by_type(header.algorithm_type) + if not verify_vbmeta_signature(header, vbmeta_blob): + raise AvbError('Signature check failed for {} vbmeta struct {}' + .format(alg_name, image_filename)) + + if key_blob: + # The embedded public key is in the auxiliary block at an offset. + key_offset = AvbVBMetaHeader.SIZE + key_offset += header.authentication_data_block_size + key_offset += header.public_key_offset + key_blob_in_vbmeta = vbmeta_blob[key_offset:key_offset + + header.public_key_size] + if key_blob != key_blob_in_vbmeta: + raise AvbError('Embedded public key does not match given key.') + + if footer: + print('vbmeta: Successfully verified footer and {} vbmeta struct in {}' + .format(alg_name, image.filename)) + else: + print('vbmeta: Successfully verified {} vbmeta struct in {}' + .format(alg_name, image.filename)) + + for desc in descriptors: + if (isinstance(desc, AvbChainPartitionDescriptor) + and follow_chain_partitions + and expected_chain_partitions_map.get(desc.partition_name) is None): + # In this case we're processing a chain descriptor but don't have a + # --expect_chain_partition ... however --follow_chain_partitions was + # specified so we shouldn't error out in desc.verify(). + print('{}: Chained but ROLLBACK_SLOT (which is {}) ' + 'and KEY (which has sha1 {}) not specified' + .format(desc.partition_name, desc.rollback_index_location, + hashlib.sha1(desc.public_key).hexdigest())) + elif not desc.verify(image_dir, image_ext, expected_chain_partitions_map, + image, accept_zeroed_hashtree): + raise AvbError('Error verifying descriptor.') + # Honor --follow_chain_partitions - add '--' to make the output more + # readable. + if (isinstance(desc, AvbChainPartitionDescriptor) + and follow_chain_partitions): + print('--') + chained_image_filename = os.path.join(image_dir, + desc.partition_name + image_ext) + self.verify_image(chained_image_filename, key_path, None, False, + accept_zeroed_hashtree) + + def calculate_vbmeta_digest(self, image_filename, hash_algorithm, output): + """Implements the 'calculate_vbmeta_digest' command. + + Arguments: + image_filename: Image file to get information from (file object). + hash_algorithm: Hash algorithm used. + output: Output file to write human-readable information to (file object). + """ + + image_dir = os.path.dirname(image_filename) + image_ext = os.path.splitext(image_filename)[1] + + image = ImageHandler(image_filename) + (footer, header, descriptors, _) = self._parse_image(image) + offset = 0 + if footer: + offset = footer.vbmeta_offset + size = (header.SIZE + header.authentication_data_block_size + + header.auxiliary_data_block_size) + image.seek(offset) + vbmeta_blob = image.read(size) + + hasher = hashlib.new(name=hash_algorithm) + hasher.update(vbmeta_blob) + + for desc in descriptors: + if isinstance(desc, AvbChainPartitionDescriptor): + ch_image_filename = os.path.join(image_dir, + desc.partition_name + image_ext) + ch_image = ImageHandler(ch_image_filename) + (ch_footer, ch_header, _, _) = self._parse_image(ch_image) + ch_offset = 0 + ch_size = (ch_header.SIZE + ch_header.authentication_data_block_size + + ch_header.auxiliary_data_block_size) + if ch_footer: + ch_offset = ch_footer.vbmeta_offset + ch_image.seek(ch_offset) + ch_vbmeta_blob = ch_image.read(ch_size) + hasher.update(ch_vbmeta_blob) + + digest = hasher.digest() + output.write('{}\n'.format(binascii.hexlify(digest))) + + def calculate_kernel_cmdline(self, image_filename, hashtree_disabled, output): + """Implements the 'calculate_kernel_cmdline' command. + + Arguments: + image_filename: Image file to get information from (file object). + hashtree_disabled: If True, returns the cmdline for hashtree disabled. + output: Output file to write human-readable information to (file object). + """ + + image = ImageHandler(image_filename) + _, _, descriptors, _ = self._parse_image(image) + + image_dir = os.path.dirname(image_filename) + image_ext = os.path.splitext(image_filename)[1] + + cmdline_descriptors = [] + for desc in descriptors: + if isinstance(desc, AvbChainPartitionDescriptor): + ch_image_filename = os.path.join(image_dir, + desc.partition_name + image_ext) + ch_image = ImageHandler(ch_image_filename) + _, _, ch_descriptors, _ = self._parse_image(ch_image) + for ch_desc in ch_descriptors: + if isinstance(ch_desc, AvbKernelCmdlineDescriptor): + cmdline_descriptors.append(ch_desc) + elif isinstance(desc, AvbKernelCmdlineDescriptor): + cmdline_descriptors.append(desc) + + kernel_cmdline_snippets = [] + for desc in cmdline_descriptors: + use_cmdline = True + if ((desc.flags & + AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED) + != 0): + if hashtree_disabled: + use_cmdline = False + if (desc.flags & + AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_DISABLED) != 0: + if not hashtree_disabled: + use_cmdline = False + if use_cmdline: + kernel_cmdline_snippets.append(desc.kernel_cmdline) + output.write(' '.join(kernel_cmdline_snippets)) + + def _parse_image(self, image): + """Gets information about an image. + + The image can either be a vbmeta or an image with a footer. + + Arguments: + image: An ImageHandler (vbmeta or footer) with a hashtree descriptor. + + Returns: + A tuple where the first argument is a AvbFooter (None if there + is no footer on the image), the second argument is a + AvbVBMetaHeader, the third argument is a list of + AvbDescriptor-derived instances, and the fourth argument is the + size of |image|. + """ + assert isinstance(image, ImageHandler) + footer = None + image.seek(image.image_size - AvbFooter.SIZE) + try: + footer = AvbFooter(image.read(AvbFooter.SIZE)) + except (LookupError, struct.error): + # Nope, just seek back to the start. + image.seek(0) + + vbmeta_offset = 0 + if footer: + vbmeta_offset = footer.vbmeta_offset + + image.seek(vbmeta_offset) + h = AvbVBMetaHeader(image.read(AvbVBMetaHeader.SIZE)) + + auth_block_offset = vbmeta_offset + AvbVBMetaHeader.SIZE + aux_block_offset = auth_block_offset + h.authentication_data_block_size + desc_start_offset = aux_block_offset + h.descriptors_offset + image.seek(desc_start_offset) + descriptors = parse_descriptors(image.read(h.descriptors_size)) + + return footer, h, descriptors, image.image_size + + def _load_vbmeta_blob(self, image): + """Gets the vbmeta struct and associated sections. + + The image can either be a vbmeta.img or an image with a footer. + + Arguments: + image: An ImageHandler (vbmeta or footer). + + Returns: + A blob with the vbmeta struct and other sections. + """ + assert isinstance(image, ImageHandler) + footer = None + image.seek(image.image_size - AvbFooter.SIZE) + try: + footer = AvbFooter(image.read(AvbFooter.SIZE)) + except (LookupError, struct.error): + # Nope, just seek back to the start. + image.seek(0) + + vbmeta_offset = 0 + if footer: + vbmeta_offset = footer.vbmeta_offset + + image.seek(vbmeta_offset) + h = AvbVBMetaHeader(image.read(AvbVBMetaHeader.SIZE)) + + image.seek(vbmeta_offset) + data_size = AvbVBMetaHeader.SIZE + data_size += h.authentication_data_block_size + data_size += h.auxiliary_data_block_size + return image.read(data_size) + + def _get_cmdline_descriptors_for_hashtree_descriptor(self, ht): + """Generate kernel cmdline descriptors for dm-verity. + + Arguments: + ht: A AvbHashtreeDescriptor + + Returns: + A list with two AvbKernelCmdlineDescriptor with dm-verity kernel cmdline + instructions. There is one for when hashtree is not disabled and one for + when it is. + + """ + + c = 'dm="1 vroot none ro 1,' + c += '0' # start + c += ' {}'.format((ht.image_size // 512)) # size (# sectors) + c += ' verity {}'.format(ht.dm_verity_version) # type and version + c += ' PARTUUID=$(ANDROID_SYSTEM_PARTUUID)' # data_dev + c += ' PARTUUID=$(ANDROID_SYSTEM_PARTUUID)' # hash_dev + c += ' {}'.format(ht.data_block_size) # data_block + c += ' {}'.format(ht.hash_block_size) # hash_block + c += ' {}'.format(ht.image_size // ht.data_block_size) # #blocks + c += ' {}'.format(ht.image_size // ht.data_block_size) # hash_offset + c += ' {}'.format(ht.hash_algorithm) # hash_alg + c += ' {}'.format(str(ht.root_digest).encode('hex')) # root_digest + c += ' {}'.format(str(ht.salt).encode('hex')) # salt + if ht.fec_num_roots > 0: + c += ' 10' # number of optional args + c += ' $(ANDROID_VERITY_MODE)' + c += ' ignore_zero_blocks' + c += ' use_fec_from_device PARTUUID=$(ANDROID_SYSTEM_PARTUUID)' + c += ' fec_roots {}'.format(ht.fec_num_roots) + # Note that fec_blocks is the size that FEC covers, *not* the + # size of the FEC data. Since we use FEC for everything up until + # the FEC data, it's the same as the offset. + c += ' fec_blocks {}'.format(ht.fec_offset // ht.data_block_size) + c += ' fec_start {}'.format(ht.fec_offset // ht.data_block_size) + else: + c += ' 2' # number of optional args + c += ' $(ANDROID_VERITY_MODE)' + c += ' ignore_zero_blocks' + c += '" root=/dev/dm-0' + + # Now that we have the command-line, generate the descriptor. + desc = AvbKernelCmdlineDescriptor() + desc.kernel_cmdline = c + desc.flags = ( + AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED) + + # The descriptor for when hashtree verification is disabled is a lot + # simpler - we just set the root to the partition. + desc_no_ht = AvbKernelCmdlineDescriptor() + desc_no_ht.kernel_cmdline = 'root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)' + desc_no_ht.flags = ( + AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_DISABLED) + + return [desc, desc_no_ht] + + def _get_cmdline_descriptors_for_dm_verity(self, image): + """Generate kernel cmdline descriptors for dm-verity. + + Arguments: + image: An ImageHandler (vbmeta or footer) with a hashtree descriptor. + + Returns: + A list with two AvbKernelCmdlineDescriptor with dm-verity kernel cmdline + instructions. There is one for when hashtree is not disabled and one for + when it is. + + Raises: + AvbError: If |image| doesn't have a hashtree descriptor. + + """ + + (_, _, descriptors, _) = self._parse_image(image) + + ht = None + for desc in descriptors: + if isinstance(desc, AvbHashtreeDescriptor): + ht = desc + break + + if not ht: + raise AvbError('No hashtree descriptor in given image') + + return self._get_cmdline_descriptors_for_hashtree_descriptor(ht) + + # TODO(danielaustin): Add unit tests. + def request_inclusion_proof(self, transparency_log, vbmeta_blob, + version_inc, manufacturer_key_path): + """Packages and sends a request to the specified transparency log. + + Arguments: + transparency_log: String containing the URL of a transparency log server. + vbmeta_blob: A bytearray with the vbmeta blob. + version_inc: Subcomponent of the build fingerprint. + manufacturer_key_path: Path to key used to sign messages sent to the + transparency log servers. + + Returns: + An AvbIcpEntry with the inclusion proof for the log entry. + + Raises: + AvbError: If grpc or the proto modules cannot be loaded, if there is an + error communicating with the log or if the manufacturer_key_path + cannot be decoded. + """ + # Import grpc and proto.api_pb2_grpc now to avoid global dependencies. + try: + import grpc + import proto.api_pb2_grpc + except ImportError as e: + err_str = 'grpc can be installed with python pip install grpcio.\n' + raise AvbError('Failed to import module: ({}).\n{}'.format(e, err_str)) + + # Set up the gRPC channel with the transparency log. + sys.stdout.write('Preparing to request inclusion proof from {}. This could ' + 'take ~30 seconds for the process to complete.\n'.format( + transparency_log)) + channel = grpc.insecure_channel(transparency_log) + stub = proto.api_pb2_grpc.AFTLogStub(channel) + + # Calculate the hash of the vbmeta image. + hasher = hashlib.sha256() + hasher.update(vbmeta_blob) + vbmeta_hash = hasher.digest() + # Extract the key data from the PEM file. + manufacturer_key_data = rsa_key_read_pem_bytes(manufacturer_key_path) + # Calculate the hash of the manufacturer key data. + hasher = hashlib.sha256() + hasher.update(manufacturer_key_data) + m_key_hash = hasher.digest() + # Create an AddFirmwareInfoRequest protobuf for transmission to the + # transparency log. + fw_info = proto.aftl_pb2.FirmwareInfo(vbmeta_hash=vbmeta_hash, + version_incremental=version_inc, + manufacturer_key_hash=m_key_hash) + # TODO(danielaustin): Sign the message with the manufacturer key. + sfw_info = proto.aftl_pb2.SignedFirmwareInfo(info=fw_info) + request = proto.api_pb2.AddFirmwareInfoRequest(vbmeta=bytes( + str(vbmeta_blob)), fw_info=sfw_info) + # Attempt to transmit to the transparency log. + try: + # TODO(danielaustin): Set a reasonable timeout deadline here. + sys.stdout.write('ICP is about to be requested from transparency log ' + 'with domain {}.\n'.format(transparency_log)) + response = stub.AddFirmwareInfo(request) + except grpc.RpcError as e: + raise AvbError('Error: grpc failure ({})'.format(e)) + # Return an AvbIcpEntry representing this response. + icp_entry = AvbIcpEntry() + icp_entry.translate_response(transparency_log, response) + return icp_entry + + def make_vbmeta_image(self, output, chain_partitions, algorithm_name, + key_path, public_key_metadata_path, rollback_index, + flags, props, props_from_file, kernel_cmdlines, + setup_rootfs_from_kernel, + include_descriptors_from_image, + signing_helper, + signing_helper_with_files, + release_string, + append_to_release_string, + print_required_libavb_version, + padding_size): + """Implements the 'make_vbmeta_image' command. + + Arguments: + output: File to write the image to. + chain_partitions: List of partitions to chain or None. + algorithm_name: Name of algorithm to use. + key_path: Path to key to use or None. + public_key_metadata_path: Path to public key metadata or None. + rollback_index: The rollback index to use. + flags: Flags value to use in the image. + props: Properties to insert (list of strings of the form 'key:value'). + props_from_file: Properties to insert (list of strings 'key:'). + kernel_cmdlines: Kernel cmdlines to insert (list of strings). + setup_rootfs_from_kernel: None or file to generate from. + include_descriptors_from_image: List of file objects with descriptors. + signing_helper: Program which signs a hash and return signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + release_string: None or avbtool release string to use instead of default. + append_to_release_string: None or string to append. + print_required_libavb_version: True to only print required libavb version. + padding_size: If not 0, pads output so size is a multiple of the number. + + Raises: + AvbError: If a chained partition is malformed. + """ + + # If we're asked to calculate minimum required libavb version, we're done. + if print_required_libavb_version: + if include_descriptors_from_image: + # Use the bump logic in AvbVBMetaHeader to calculate the max required + # version of all included descriptors. + tmp_header = AvbVBMetaHeader() + for image in include_descriptors_from_image: + (_, image_header, _, _) = self._parse_image(ImageHandler(image.name)) + tmp_header.bump_required_libavb_version_minor( + image_header.required_libavb_version_minor) + print('1.{}'.format(tmp_header.required_libavb_version_minor)) + else: + # Descriptors aside, all vbmeta features are supported in 1.0. + print('1.0') + return + + if not output: + raise AvbError('No output file given') + + descriptors = [] + ht_desc_to_setup = None + vbmeta_blob = self._generate_vbmeta_blob( + algorithm_name, key_path, public_key_metadata_path, descriptors, + chain_partitions, rollback_index, flags, props, props_from_file, + kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, + include_descriptors_from_image, signing_helper, + signing_helper_with_files, release_string, + append_to_release_string, 0) + + # Write entire vbmeta blob (header, authentication, auxiliary). + output.seek(0) + output.write(vbmeta_blob) + + if padding_size > 0: + padded_size = round_to_multiple(len(vbmeta_blob), padding_size) + padding_needed = padded_size - len(vbmeta_blob) + output.write('\0' * padding_needed) + + def make_icp_from_vbmeta(self, vbmeta_image_path, output, algorithm, + signing_helper, signing_helper_with_files, + version_incremental, transparency_log_servers, + transparency_log_pub_keys, manufacturer_key, + padding_size): + """Generates a vbmeta image with inclusion proof given a vbmeta image. + + This blob (struct AvbIcpBlob) contains the information required to + validate an inclusion proof for a specific vbmeta image. It consists + of a header (struct AvbIcpHeader) and zero or more entry structures + (struct AvbIcpEntry) that contain the vbmeta leaf hash, tree size, + root hash, inclusion proof hashes, and the signature for the root hash. + + The vbmeta image, its hash, the version_incremental part of the build + fingerprint, and the hash of the manufacturer key are sent to the + transparency log, with the message signed by the manufacturer key. + An inclusion proof is calculated and returned. This inclusion proof is + then packaged in a AvbIcpBlob structure. The existing vbmeta data is + copied to a new file, appended with the AvbIcpBlob data, and written to + output. Validation of the inclusion proof does not require + communication with the transparency log. + + Arguments: + vbmeta_image_path: Path to a vbmeta image file. + output: File to write the results to. + algorithm: The algorithm ID for signing and hashing (see ALGORITHMS). This + will be used for hash and signature size calculation and padding. + signing_helper: Program which signs a hash and returns a signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + version_incremental: A string representing the subcomponent of the + build fingerprint used to identify the vbmeta in the transparency log. + transparency_log_servers: List of strings containing URLs of transparency + log servers where inclusion proofs are requested from. + transparency_log_pub_keys: List of paths to PEM files containing trusted + public keys that correspond with the transparency_logs. There must be + the same number of keys as log servers and they must be in the same + order, that is, transparency_log_pub_keys[n] corresponds to + transparency_log_servers[n]. + manufacturer_key: Path to PEM file containting the key file used to sign + messages sent to the transparency log servers. + padding_size: If not 0, pads output so size is a multiple of the number. + + Returns: + True if the inclusion proofs could be fetched from the transparency log + servers and could be successfully validated, False otherwise. + + Raises: + AvbError: If any parameters are invalid, communication with the log + fails or the structures are malformed. + """ + # TODO(danielaustin): Determine the best way to handle chained vbmeta + # structures. Currently, we only put the main one in the transparency + # log. + + # Validates command line parameters. + if not vbmeta_image_path: + raise AvbError('No vbmeta image path found.') + if not transparency_log_servers: + raise AvbError('No transparency log servers given.') + if not transparency_log_pub_keys: + raise AvbError('No transparency log public keys given.') + if len(transparency_log_servers) != len(transparency_log_pub_keys): + raise AvbError('Transparency log count and public key count mismatch: ' + '{} servers and {} public keys'.format( + len(transparency_log_servers), + len(transparency_log_pub_keys))) + if not manufacturer_key: + raise AvbError('No manufacturer key path given.') + + # TODO(danielaustin): add support for signing_helper and + # signing_helper_with_files + if signing_helper is not None or signing_helper_with_files is not None: + raise AvbError('signing_helper support not yet implemented for ICP.') + + try: + algorithm_id = ALGORITHMS[algorithm].algorithm_type + except KeyError: + raise AvbError('Unknown algorithm with name {}'.format(algorithm)) + + # Retrieves vbmeta structure from given partition image. + image = ImageHandler(vbmeta_image_path) + (footer, header, _, _) = self._parse_image(image) + offset = 0 + if footer: + offset = footer.vbmeta_offset + image.seek(offset) + vbmeta_blob = image.read(header.SIZE + + header.authentication_data_block_size + + header.auxiliary_data_block_size) + + # Fetches inclusion proofs for vbmeta structure from all transparency logs. + icp_entries = [] + for i, transparency_log in enumerate(transparency_log_servers): + try: + icp_entry = self.request_inclusion_proof(transparency_log, vbmeta_blob, + version_incremental, + manufacturer_key) + if not icp_entry.verify_icp(transparency_log_pub_keys[i]): + sys.stderr.write('The ICP from {} could not be verified\n'.format( + transparency_log)) + icp_entries.append(icp_entry) + except AvbError as e: + sys.stderr.write('AvbError: {}'.format(e)) + # The inclusion proof request failed. + # Continue and see if another will succeed. + continue + if not icp_entries: + sys.stderr.write('No inclusion proofs could be validated from any log.\n') + return False + + # Prepares the inclusion proof blob to be appended to the vbmeta image. + icp_blob = AvbIcpBlob() + icp_blob.set_algorithm(algorithm_id) + for icp_entry in icp_entries: + icp_blob.add_icp_entry(icp_entry) + if not icp_blob.is_valid(): + sys.stderr.write('Resulting AvbIcpBlob structure is malformed\n.') + return False + + # Write the original vbmeta blob, followed by the AvbIcpBlob. + if footer: # Checks if it is a chained partition. + # TODO(danielaustin): Add support for chained partitions like system.img + # using similar functionality as implemented in append_vbmeta_image(). + sys.stderr.write('Image has a footer and ICP for this format is not ' + 'implemented.') + return False + + # Writes vbmeta image with inclusion proof into a new vbmeta image. + output.seek(0) + output.write(vbmeta_blob) + encoded_icp_blob = icp_blob.encode() + output.write(encoded_icp_blob) + + if padding_size > 0: + blob_size = len(vbmeta_blob) + len(encoded_icp_blob) + padded_size = round_to_multiple(blob_size, padding_size) + padding_needed = padded_size - blob_size + output.write('\0' * padding_needed) + + return True + + def _generate_vbmeta_blob(self, algorithm_name, key_path, + public_key_metadata_path, descriptors, + chain_partitions, + rollback_index, flags, props, props_from_file, + kernel_cmdlines, + setup_rootfs_from_kernel, + ht_desc_to_setup, + include_descriptors_from_image, signing_helper, + signing_helper_with_files, + release_string, append_to_release_string, + required_libavb_version_minor): + """Generates a VBMeta blob. + + This blob contains the header (struct AvbVBMetaHeader), the + authentication data block (which contains the hash and signature + for the header and auxiliary block), and the auxiliary block + (which contains descriptors, the public key used, and other data). + + The |key| parameter can |None| only if the |algorithm_name| is + 'NONE'. + + Arguments: + algorithm_name: The algorithm name as per the ALGORITHMS dict. + key_path: The path to the .pem file used to sign the blob. + public_key_metadata_path: Path to public key metadata or None. + descriptors: A list of descriptors to insert or None. + chain_partitions: List of partitions to chain or None. + rollback_index: The rollback index to use. + flags: Flags to use in the image. + props: Properties to insert (List of strings of the form 'key:value'). + props_from_file: Properties to insert (List of strings 'key:'). + kernel_cmdlines: Kernel cmdlines to insert (list of strings). + setup_rootfs_from_kernel: None or file to generate + dm-verity kernel cmdline from. + ht_desc_to_setup: If not None, an AvbHashtreeDescriptor to + generate dm-verity kernel cmdline descriptors from. + include_descriptors_from_image: List of file objects for which + to insert descriptors from. + signing_helper: Program which signs a hash and return signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + release_string: None or avbtool release string. + append_to_release_string: None or string to append. + required_libavb_version_minor: Use at least this required minor version. + + Returns: + A bytearray() with the VBMeta blob. + + Raises: + Exception: If the |algorithm_name| is not found, if no key has + been given and the given algorithm requires one, or the key is + of the wrong size. + + """ + try: + alg = ALGORITHMS[algorithm_name] + except KeyError: + raise AvbError('Unknown algorithm with name {}'.format(algorithm_name)) + + if not descriptors: + descriptors = [] + + h = AvbVBMetaHeader() + h.bump_required_libavb_version_minor(required_libavb_version_minor) + + # Insert chained partition descriptors, if any + if chain_partitions: + used_locations = {} + for cp in chain_partitions: + cp_tokens = cp.split(':') + if len(cp_tokens) != 3: + raise AvbError('Malformed chained partition "{}".'.format(cp)) + partition_name = cp_tokens[0] + rollback_index_location = int(cp_tokens[1]) + file_path = cp_tokens[2] + # Check that the same rollback location isn't being used by + # multiple chained partitions. + if used_locations.get(rollback_index_location): + raise AvbError('Rollback Index Location {} is already in use.'.format( + rollback_index_location)) + used_locations[rollback_index_location] = True + desc = AvbChainPartitionDescriptor() + desc.partition_name = partition_name + desc.rollback_index_location = rollback_index_location + if desc.rollback_index_location < 1: + raise AvbError('Rollback index location must be 1 or larger.') + desc.public_key = open(file_path, 'rb').read() + descriptors.append(desc) + + # Descriptors. + encoded_descriptors = bytearray() + for desc in descriptors: + encoded_descriptors.extend(desc.encode()) + + # Add properties. + if props: + for prop in props: + idx = prop.find(':') + if idx == -1: + raise AvbError('Malformed property "{}".'.format(prop)) + # pylint: disable=redefined-variable-type + desc = AvbPropertyDescriptor() + desc.key = prop[0:idx] + desc.value = prop[(idx + 1):] + encoded_descriptors.extend(desc.encode()) + if props_from_file: + for prop in props_from_file: + idx = prop.find(':') + if idx == -1: + raise AvbError('Malformed property "{}".'.format(prop)) + desc = AvbPropertyDescriptor() + desc.key = prop[0:idx] + desc.value = prop[(idx + 1):] + file_path = prop[(idx + 1):] + desc.value = open(file_path, 'rb').read() + encoded_descriptors.extend(desc.encode()) + + # Add AvbKernelCmdline descriptor for dm-verity from an image, if requested. + if setup_rootfs_from_kernel: + image_handler = ImageHandler( + setup_rootfs_from_kernel.name) + cmdline_desc = self._get_cmdline_descriptors_for_dm_verity(image_handler) + encoded_descriptors.extend(cmdline_desc[0].encode()) + encoded_descriptors.extend(cmdline_desc[1].encode()) + + # Add AvbKernelCmdline descriptor for dm-verity from desc, if requested. + if ht_desc_to_setup: + cmdline_desc = self._get_cmdline_descriptors_for_hashtree_descriptor( + ht_desc_to_setup) + encoded_descriptors.extend(cmdline_desc[0].encode()) + encoded_descriptors.extend(cmdline_desc[1].encode()) + + # Add kernel command-lines. + if kernel_cmdlines: + for i in kernel_cmdlines: + desc = AvbKernelCmdlineDescriptor() + desc.kernel_cmdline = i + encoded_descriptors.extend(desc.encode()) + + # Add descriptors from other images. + if include_descriptors_from_image: + descriptors_dict = dict() + for image in include_descriptors_from_image: + image_handler = ImageHandler(image.name) + (_, image_vbmeta_header, image_descriptors, _) = self._parse_image( + image_handler) + # Bump the required libavb version to support all included descriptors. + h.bump_required_libavb_version_minor( + image_vbmeta_header.required_libavb_version_minor) + for desc in image_descriptors: + # The --include_descriptors_from_image option is used in some setups + # with images A and B where both A and B contain a descriptor + # for a partition with the same name. Since it's not meaningful + # to include both descriptors, only include the last seen descriptor. + # See bug 76386656 for details. + if hasattr(desc, 'partition_name'): + key = type(desc).__name__ + '_' + desc.partition_name + descriptors_dict[key] = desc.encode() + else: + encoded_descriptors.extend(desc.encode()) + for key in sorted(descriptors_dict): + encoded_descriptors.extend(descriptors_dict[key]) + + # Load public key metadata blob, if requested. + pkmd_blob = [] + if public_key_metadata_path: + with open(public_key_metadata_path) as f: + pkmd_blob = f.read() + + key = None + encoded_key = bytearray() + if alg.public_key_num_bytes > 0: + if not key_path: + raise AvbError('Key is required for algorithm {}'.format( + algorithm_name)) + encoded_key = encode_rsa_key(key_path) + if len(encoded_key) != alg.public_key_num_bytes: + raise AvbError('Key is wrong size for algorithm {}'.format( + algorithm_name)) + + # Override release string, if requested. + # pylint: disable=unicode-builtin + if isinstance(release_string, (str, unicode)): + h.release_string = release_string + + # Append to release string, if requested. Also insert a space before. + if isinstance(append_to_release_string, (str, unicode)): + h.release_string += ' ' + append_to_release_string + + # For the Auxiliary data block, descriptors are stored at offset 0, + # followed by the public key, followed by the public key metadata blob. + h.auxiliary_data_block_size = round_to_multiple( + len(encoded_descriptors) + len(encoded_key) + len(pkmd_blob), 64) + h.descriptors_offset = 0 + h.descriptors_size = len(encoded_descriptors) + h.public_key_offset = h.descriptors_size + h.public_key_size = len(encoded_key) + h.public_key_metadata_offset = h.public_key_offset + h.public_key_size + h.public_key_metadata_size = len(pkmd_blob) + + # For the Authentication data block, the hash is first and then + # the signature. + h.authentication_data_block_size = round_to_multiple( + alg.hash_num_bytes + alg.signature_num_bytes, 64) + h.algorithm_type = alg.algorithm_type + h.hash_offset = 0 + h.hash_size = alg.hash_num_bytes + # Signature offset and size - it's stored right after the hash + # (in Authentication data block). + h.signature_offset = alg.hash_num_bytes + h.signature_size = alg.signature_num_bytes + + h.rollback_index = rollback_index + h.flags = flags + + # Generate Header data block. + header_data_blob = h.encode() + + # Generate Auxiliary data block. + aux_data_blob = bytearray() + aux_data_blob.extend(encoded_descriptors) + aux_data_blob.extend(encoded_key) + aux_data_blob.extend(pkmd_blob) + padding_bytes = h.auxiliary_data_block_size - len(aux_data_blob) + aux_data_blob.extend('\0' * padding_bytes) + + # Calculate the hash. + binary_hash = bytearray() + binary_signature = bytearray() + if algorithm_name != 'NONE': + ha = hashlib.new(alg.hash_name) + ha.update(header_data_blob) + ha.update(aux_data_blob) + binary_hash.extend(ha.digest()) + + # Calculate the signature. + padding_and_hash = str(bytearray(alg.padding)) + binary_hash + binary_signature.extend(raw_sign(signing_helper, + signing_helper_with_files, + algorithm_name, + alg.signature_num_bytes, key_path, + padding_and_hash)) + + # Generate Authentication data block. + auth_data_blob = bytearray() + auth_data_blob.extend(binary_hash) + auth_data_blob.extend(binary_signature) + padding_bytes = h.authentication_data_block_size - len(auth_data_blob) + auth_data_blob.extend('\0' * padding_bytes) + + return header_data_blob + auth_data_blob + aux_data_blob + + def extract_public_key(self, key_path, output): + """Implements the 'extract_public_key' command. + + Arguments: + key_path: The path to a RSA private key file. + output: The file to write to. + """ + output.write(encode_rsa_key(key_path)) + + def append_vbmeta_image(self, image_filename, vbmeta_image_filename, + partition_size): + """Implementation of the append_vbmeta_image command. + + Arguments: + image_filename: File to add the footer to. + vbmeta_image_filename: File to get vbmeta struct from. + partition_size: Size of partition. + + Raises: + AvbError: If an argument is incorrect. + """ + image = ImageHandler(image_filename) + + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + + # If there's already a footer, truncate the image to its original + # size. This way 'avbtool append_vbmeta_image' is idempotent. + if image.image_size >= AvbFooter.SIZE: + image.seek(image.image_size - AvbFooter.SIZE) + try: + footer = AvbFooter(image.read(AvbFooter.SIZE)) + # Existing footer found. Just truncate. + original_image_size = footer.original_image_size + image.truncate(footer.original_image_size) + except (LookupError, struct.error): + original_image_size = image.image_size + else: + # Image size is too small to possibly contain a footer. + original_image_size = image.image_size + + # If anything goes wrong from here-on, restore the image back to + # its original size. + try: + vbmeta_image_handler = ImageHandler(vbmeta_image_filename) + vbmeta_blob = self._load_vbmeta_blob(vbmeta_image_handler) + + # If the image isn't sparse, its size might not be a multiple of + # the block size. This will screw up padding later so just grow it. + if image.image_size % image.block_size != 0: + assert not image.is_sparse + padding_needed = image.block_size - (image.image_size%image.block_size) + image.truncate(image.image_size + padding_needed) + + # The append_raw() method requires content with size being a + # multiple of |block_size| so add padding as needed. Also record + # where this is written to since we'll need to put that in the + # footer. + vbmeta_offset = image.image_size + padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) - + len(vbmeta_blob)) + vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed + + # Append vbmeta blob and footer + image.append_raw(vbmeta_blob_with_padding) + vbmeta_end_offset = vbmeta_offset + len(vbmeta_blob_with_padding) + + # Now insert a DONT_CARE chunk with enough bytes such that the + # final Footer block is at the end of partition_size.. + image.append_dont_care(partition_size - vbmeta_end_offset - + 1*image.block_size) + + # Generate the Footer that tells where the VBMeta footer + # is. Also put enough padding in the front of the footer since + # we'll write out an entire block. + footer = AvbFooter() + footer.original_image_size = original_image_size + footer.vbmeta_offset = vbmeta_offset + footer.vbmeta_size = len(vbmeta_blob) + footer_blob = footer.encode() + footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) + + footer_blob) + image.append_raw(footer_blob_with_padding) + + except: + # Truncate back to original size, then re-raise + image.truncate(original_image_size) + raise + + def add_hash_footer(self, image_filename, partition_size, partition_name, + hash_algorithm, salt, chain_partitions, algorithm_name, + key_path, + public_key_metadata_path, rollback_index, flags, props, + props_from_file, kernel_cmdlines, + setup_rootfs_from_kernel, + include_descriptors_from_image, calc_max_image_size, + signing_helper, signing_helper_with_files, + release_string, append_to_release_string, + output_vbmeta_image, do_not_append_vbmeta_image, + print_required_libavb_version, use_persistent_digest, + do_not_use_ab): + """Implementation of the add_hash_footer on unsparse images. + + Arguments: + image_filename: File to add the footer to. + partition_size: Size of partition. + partition_name: Name of partition (without A/B suffix). + hash_algorithm: Hash algorithm to use. + salt: Salt to use as a hexadecimal string or None to use /dev/urandom. + chain_partitions: List of partitions to chain. + algorithm_name: Name of algorithm to use. + key_path: Path to key to use or None. + public_key_metadata_path: Path to public key metadata or None. + rollback_index: Rollback index. + flags: Flags value to use in the image. + props: Properties to insert (List of strings of the form 'key:value'). + props_from_file: Properties to insert (List of strings 'key:'). + kernel_cmdlines: Kernel cmdlines to insert (list of strings). + setup_rootfs_from_kernel: None or file to generate + dm-verity kernel cmdline from. + include_descriptors_from_image: List of file objects for which + to insert descriptors from. + calc_max_image_size: Don't store the footer - instead calculate the + maximum image size leaving enough room for metadata with the + given |partition_size|. + signing_helper: Program which signs a hash and return signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + release_string: None or avbtool release string. + append_to_release_string: None or string to append. + output_vbmeta_image: If not None, also write vbmeta struct to this file. + do_not_append_vbmeta_image: If True, don't append vbmeta struct. + print_required_libavb_version: True to only print required libavb version. + use_persistent_digest: Use a persistent digest on device. + do_not_use_ab: This partition does not use A/B. + + Raises: + AvbError: If an argument is incorrect. + """ + + required_libavb_version_minor = 0 + if use_persistent_digest or do_not_use_ab: + required_libavb_version_minor = 1 + + # If we're asked to calculate minimum required libavb version, we're done. + if print_required_libavb_version: + print('1.{}'.format(required_libavb_version_minor)) + return + + # First, calculate the maximum image size such that an image + # this size + metadata (footer + vbmeta struct) fits in + # |partition_size|. + max_metadata_size = self.MAX_VBMETA_SIZE + self.MAX_FOOTER_SIZE + if partition_size < max_metadata_size: + raise AvbError('Parition size of {} is too small. ' + 'Needs to be at least {}'.format( + partition_size, max_metadata_size)) + max_image_size = partition_size - max_metadata_size + + # If we're asked to only calculate the maximum image size, we're done. + if calc_max_image_size: + print('{}'.format(max_image_size)) + return + + image = ImageHandler(image_filename) + + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + + # If there's already a footer, truncate the image to its original + # size. This way 'avbtool add_hash_footer' is idempotent (modulo + # salts). + if image.image_size >= AvbFooter.SIZE: + image.seek(image.image_size - AvbFooter.SIZE) + try: + footer = AvbFooter(image.read(AvbFooter.SIZE)) + # Existing footer found. Just truncate. + original_image_size = footer.original_image_size + image.truncate(footer.original_image_size) + except (LookupError, struct.error): + original_image_size = image.image_size + else: + # Image size is too small to possibly contain a footer. + original_image_size = image.image_size + + # If anything goes wrong from here-on, restore the image back to + # its original size. + try: + # If image size exceeds the maximum image size, fail. + if image.image_size > max_image_size: + raise AvbError('Image size of {} exceeds maximum image ' + 'size of {} in order to fit in a partition ' + 'size of {}.'.format(image.image_size, max_image_size, + partition_size)) + + digest_size = len(hashlib.new(name=hash_algorithm).digest()) + if salt: + salt = binascii.unhexlify(salt) + elif salt is None and not use_persistent_digest: + # If salt is not explicitly specified, choose a hash that's the same + # size as the hash size. Don't populate a random salt if this + # descriptor is being created to use a persistent digest on device. + hash_size = digest_size + salt = open('/dev/urandom').read(hash_size) + else: + salt = '' + + hasher = hashlib.new(name=hash_algorithm, string=salt) + # TODO(zeuthen): might want to read this in chunks to avoid + # memory pressure, then again, this is only supposed to be used + # on kernel/initramfs partitions. Possible optimization. + image.seek(0) + hasher.update(image.read(image.image_size)) + digest = hasher.digest() + + h_desc = AvbHashDescriptor() + h_desc.image_size = image.image_size + h_desc.hash_algorithm = hash_algorithm + h_desc.partition_name = partition_name + h_desc.salt = salt + h_desc.flags = 0 + if do_not_use_ab: + h_desc.flags |= 1 # AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB + if not use_persistent_digest: + h_desc.digest = digest + + # Generate the VBMeta footer. + ht_desc_to_setup = None + vbmeta_blob = self._generate_vbmeta_blob( + algorithm_name, key_path, public_key_metadata_path, [h_desc], + chain_partitions, rollback_index, flags, props, props_from_file, + kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, + include_descriptors_from_image, signing_helper, + signing_helper_with_files, release_string, + append_to_release_string, required_libavb_version_minor) + + # Write vbmeta blob, if requested. + if output_vbmeta_image: + output_vbmeta_image.write(vbmeta_blob) + + # Append vbmeta blob and footer, unless requested not to. + if not do_not_append_vbmeta_image: + # If the image isn't sparse, its size might not be a multiple of + # the block size. This will screw up padding later so just grow it. + if image.image_size % image.block_size != 0: + assert not image.is_sparse + padding_needed = image.block_size - ( + image.image_size % image.block_size) + image.truncate(image.image_size + padding_needed) + + # The append_raw() method requires content with size being a + # multiple of |block_size| so add padding as needed. Also record + # where this is written to since we'll need to put that in the + # footer. + vbmeta_offset = image.image_size + padding_needed = ( + round_to_multiple(len(vbmeta_blob), image.block_size) - + len(vbmeta_blob)) + vbmeta_blob_with_padding = vbmeta_blob + '\0' * padding_needed + + image.append_raw(vbmeta_blob_with_padding) + vbmeta_end_offset = vbmeta_offset + len(vbmeta_blob_with_padding) + + # Now insert a DONT_CARE chunk with enough bytes such that the + # final Footer block is at the end of partition_size.. + image.append_dont_care(partition_size - vbmeta_end_offset - + 1*image.block_size) + + # Generate the Footer that tells where the VBMeta footer + # is. Also put enough padding in the front of the footer since + # we'll write out an entire block. + footer = AvbFooter() + footer.original_image_size = original_image_size + footer.vbmeta_offset = vbmeta_offset + footer.vbmeta_size = len(vbmeta_blob) + footer_blob = footer.encode() + footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) + + footer_blob) + image.append_raw(footer_blob_with_padding) + + except: + # Truncate back to original size, then re-raise + image.truncate(original_image_size) + raise + + def add_hashtree_footer(self, image_filename, partition_size, partition_name, + generate_fec, fec_num_roots, hash_algorithm, + block_size, salt, chain_partitions, algorithm_name, + key_path, + public_key_metadata_path, rollback_index, flags, + props, props_from_file, kernel_cmdlines, + setup_rootfs_from_kernel, + setup_as_rootfs_from_kernel, + include_descriptors_from_image, + calc_max_image_size, signing_helper, + signing_helper_with_files, + release_string, append_to_release_string, + output_vbmeta_image, do_not_append_vbmeta_image, + print_required_libavb_version, + use_persistent_root_digest, do_not_use_ab, + no_hashtree): + """Implements the 'add_hashtree_footer' command. + + See https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for + more information about dm-verity and these hashes. + + Arguments: + image_filename: File to add the footer to. + partition_size: Size of partition or 0 to put it right at the end. + partition_name: Name of partition (without A/B suffix). + generate_fec: If True, generate FEC codes. + fec_num_roots: Number of roots for FEC. + hash_algorithm: Hash algorithm to use. + block_size: Block size to use. + salt: Salt to use as a hexadecimal string or None to use /dev/urandom. + chain_partitions: List of partitions to chain. + algorithm_name: Name of algorithm to use. + key_path: Path to key to use or None. + public_key_metadata_path: Path to public key metadata or None. + rollback_index: Rollback index. + flags: Flags value to use in the image. + props: Properties to insert (List of strings of the form 'key:value'). + props_from_file: Properties to insert (List of strings 'key:'). + kernel_cmdlines: Kernel cmdlines to insert (list of strings). + setup_rootfs_from_kernel: None or file to generate + dm-verity kernel cmdline from. + setup_as_rootfs_from_kernel: If True, generate dm-verity kernel + cmdline to set up rootfs. + include_descriptors_from_image: List of file objects for which + to insert descriptors from. + calc_max_image_size: Don't store the hashtree or footer - instead + calculate the maximum image size leaving enough room for hashtree + and metadata with the given |partition_size|. + signing_helper: Program which signs a hash and return signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + release_string: None or avbtool release string. + append_to_release_string: None or string to append. + output_vbmeta_image: If not None, also write vbmeta struct to this file. + do_not_append_vbmeta_image: If True, don't append vbmeta struct. + print_required_libavb_version: True to only print required libavb version. + use_persistent_root_digest: Use a persistent root digest on device. + do_not_use_ab: The partition does not use A/B. + no_hashtree: Do not append hashtree. Set size in descriptor as zero. + + Raises: + AvbError: If an argument is incorrect. + """ + + required_libavb_version_minor = 0 + if use_persistent_root_digest or do_not_use_ab: + required_libavb_version_minor = 1 + + # If we're asked to calculate minimum required libavb version, we're done. + if print_required_libavb_version: + print('1.{}'.format(required_libavb_version_minor)) + return + + digest_size = len(hashlib.new(name=hash_algorithm).digest()) + digest_padding = round_to_pow2(digest_size) - digest_size + + # If |partition_size| is given (e.g. not 0), calculate the maximum image + # size such that an image this size + the hashtree + metadata (footer + + # vbmeta struct) fits in |partition_size|. We use very conservative figures + # for metadata. + if partition_size > 0: + max_tree_size = 0 + max_fec_size = 0 + if not no_hashtree: + (_, max_tree_size) = calc_hash_level_offsets( + partition_size, block_size, digest_size + digest_padding) + if generate_fec: + max_fec_size = calc_fec_data_size(partition_size, fec_num_roots) + max_metadata_size = (max_fec_size + max_tree_size + + self.MAX_VBMETA_SIZE + + self.MAX_FOOTER_SIZE) + max_image_size = partition_size - max_metadata_size + else: + max_image_size = 0 + + # If we're asked to only calculate the maximum image size, we're done. + if calc_max_image_size: + print('{}'.format(max_image_size)) + return + + image = ImageHandler(image_filename) + + if partition_size > 0: + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + elif image.image_size % image.block_size != 0: + raise AvbError('File size of {} is not a multiple of the image ' + 'block size {}.'.format(image.image_size, + image.block_size)) + + # If there's already a footer, truncate the image to its original + # size. This way 'avbtool add_hashtree_footer' is idempotent + # (modulo salts). + if image.image_size >= AvbFooter.SIZE: + image.seek(image.image_size - AvbFooter.SIZE) + try: + footer = AvbFooter(image.read(AvbFooter.SIZE)) + # Existing footer found. Just truncate. + original_image_size = footer.original_image_size + image.truncate(footer.original_image_size) + except (LookupError, struct.error): + original_image_size = image.image_size + else: + # Image size is too small to possibly contain a footer. + original_image_size = image.image_size + + # If anything goes wrong from here-on, restore the image back to + # its original size. + try: + # Ensure image is multiple of block_size. + rounded_image_size = round_to_multiple(image.image_size, block_size) + if rounded_image_size > image.image_size: + image.append_raw('\0' * (rounded_image_size - image.image_size)) + + # If image size exceeds the maximum image size, fail. + if partition_size > 0: + if image.image_size > max_image_size: + raise AvbError('Image size of {} exceeds maximum image ' + 'size of {} in order to fit in a partition ' + 'size of {}.'.format(image.image_size, max_image_size, + partition_size)) + + if salt: + salt = binascii.unhexlify(salt) + elif salt is None and not use_persistent_root_digest: + # If salt is not explicitly specified, choose a hash that's the same + # size as the hash size. Don't populate a random salt if this + # descriptor is being created to use a persistent digest on device. + hash_size = digest_size + salt = open('/dev/urandom').read(hash_size) + else: + salt = '' + + # Hashes are stored upside down so we need to calculate hash + # offsets in advance. + (hash_level_offsets, tree_size) = calc_hash_level_offsets( + image.image_size, block_size, digest_size + digest_padding) + + # If the image isn't sparse, its size might not be a multiple of + # the block size. This will screw up padding later so just grow it. + if image.image_size % image.block_size != 0: + assert not image.is_sparse + padding_needed = image.block_size - (image.image_size%image.block_size) + image.truncate(image.image_size + padding_needed) + + # Generate the tree and add padding as needed. + tree_offset = image.image_size + root_digest, hash_tree = generate_hash_tree(image, image.image_size, + block_size, + hash_algorithm, salt, + digest_padding, + hash_level_offsets, + tree_size) + + # Generate HashtreeDescriptor with details about the tree we + # just generated. + if no_hashtree: + tree_size = 0 + hash_tree = bytearray() + ht_desc = AvbHashtreeDescriptor() + ht_desc.dm_verity_version = 1 + ht_desc.image_size = image.image_size + ht_desc.tree_offset = tree_offset + ht_desc.tree_size = tree_size + ht_desc.data_block_size = block_size + ht_desc.hash_block_size = block_size + ht_desc.hash_algorithm = hash_algorithm + ht_desc.partition_name = partition_name + ht_desc.salt = salt + if do_not_use_ab: + ht_desc.flags |= 1 # AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB + if not use_persistent_root_digest: + ht_desc.root_digest = root_digest + + # Write the hash tree + padding_needed = (round_to_multiple(len(hash_tree), image.block_size) - + len(hash_tree)) + hash_tree_with_padding = hash_tree + '\0'*padding_needed + image.append_raw(hash_tree_with_padding) + len_hashtree_and_fec = len(hash_tree_with_padding) + + # Generate FEC codes, if requested. + if generate_fec: + if no_hashtree: + fec_data = bytearray() + else: + fec_data = generate_fec_data(image_filename, fec_num_roots) + padding_needed = (round_to_multiple(len(fec_data), image.block_size) - + len(fec_data)) + fec_data_with_padding = fec_data + '\0'*padding_needed + fec_offset = image.image_size + image.append_raw(fec_data_with_padding) + len_hashtree_and_fec += len(fec_data_with_padding) + # Update the hashtree descriptor. + ht_desc.fec_num_roots = fec_num_roots + ht_desc.fec_offset = fec_offset + ht_desc.fec_size = len(fec_data) + + ht_desc_to_setup = None + if setup_as_rootfs_from_kernel: + ht_desc_to_setup = ht_desc + + # Generate the VBMeta footer and add padding as needed. + vbmeta_offset = tree_offset + len_hashtree_and_fec + vbmeta_blob = self._generate_vbmeta_blob( + algorithm_name, key_path, public_key_metadata_path, [ht_desc], + chain_partitions, rollback_index, flags, props, props_from_file, + kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, + include_descriptors_from_image, signing_helper, + signing_helper_with_files, release_string, + append_to_release_string, required_libavb_version_minor) + padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) - + len(vbmeta_blob)) + vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed + + # Write vbmeta blob, if requested. + if output_vbmeta_image: + output_vbmeta_image.write(vbmeta_blob) + + # Append vbmeta blob and footer, unless requested not to. + if not do_not_append_vbmeta_image: + image.append_raw(vbmeta_blob_with_padding) + + # Now insert a DONT_CARE chunk with enough bytes such that the + # final Footer block is at the end of partition_size.. + if partition_size > 0: + image.append_dont_care(partition_size - image.image_size - + 1*image.block_size) + + # Generate the Footer that tells where the VBMeta footer + # is. Also put enough padding in the front of the footer since + # we'll write out an entire block. + footer = AvbFooter() + footer.original_image_size = original_image_size + footer.vbmeta_offset = vbmeta_offset + footer.vbmeta_size = len(vbmeta_blob) + footer_blob = footer.encode() + footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) + + footer_blob) + image.append_raw(footer_blob_with_padding) + + except: + # Truncate back to original size, then re-raise. + image.truncate(original_image_size) + raise + + def make_atx_certificate(self, output, authority_key_path, subject_key_path, + subject_key_version, subject, + is_intermediate_authority, usage, signing_helper, + signing_helper_with_files): + """Implements the 'make_atx_certificate' command. + + Android Things certificates are required for Android Things public key + metadata. They chain the vbmeta signing key for a particular product back to + a fused, permanent root key. These certificates are fixed-length and fixed- + format with the explicit goal of not parsing ASN.1 in bootloader code. + + Arguments: + output: Certificate will be written to this file on success. + authority_key_path: A PEM file path with the authority private key. + If None, then a certificate will be created without a + signature. The signature can be created out-of-band + and appended. + subject_key_path: Path to a PEM or DER subject public key. + subject_key_version: A 64-bit version value. If this is None, the number + of seconds since the epoch is used. + subject: A subject identifier. For Product Signing Key certificates this + should be the same Product ID found in the permanent attributes. + is_intermediate_authority: True if the certificate is for an intermediate + authority. + usage: If not empty, overrides the cert usage with a hash of this value. + signing_helper: Program which signs a hash and returns the signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + """ + signed_data = bytearray() + signed_data.extend(struct.pack(' block_size: + num_blocks = (size + block_size - 1) // block_size + level_size = round_to_multiple(num_blocks * digest_size, block_size) + + level_sizes.append(level_size) + tree_size += level_size + num_levels += 1 + + size = level_size + + for n in range(0, num_levels): + offset = 0 + for m in range(n + 1, num_levels): + offset += level_sizes[m] + level_offsets.append(offset) + + return level_offsets, tree_size + + +# See system/extras/libfec/include/fec/io.h for these definitions. +FEC_FOOTER_FORMAT = ' block_size: + level_output = '' + remaining = hash_src_size + while remaining > 0: + hasher = hashlib.new(name=hash_alg_name, string=salt) + # Only read from the file for the first level - for subsequent + # levels, access the array we're building. + if level_num == 0: + image.seek(hash_src_offset + hash_src_size - remaining) + data = image.read(min(remaining, block_size)) + else: + offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining + data = hash_ret[offset:offset + block_size] + hasher.update(data) + + remaining -= len(data) + if len(data) < block_size: + hasher.update('\0' * (block_size - len(data))) + level_output += hasher.digest() + if digest_padding > 0: + level_output += '\0' * digest_padding + + padding_needed = (round_to_multiple( + len(level_output), block_size) - len(level_output)) + level_output += '\0' * padding_needed + + # Copy level-output into resulting tree. + offset = hash_level_offsets[level_num] + hash_ret[offset:offset + len(level_output)] = level_output + + # Continue on to the next level. + hash_src_size = len(level_output) + level_num += 1 + + hasher = hashlib.new(name=hash_alg_name, string=salt) + hasher.update(level_output) + return hasher.digest(), hash_ret + + +class AvbTool(object): + """Object for avbtool command-line tool.""" + + def __init__(self): + """Initializer method.""" + self.avb = Avb() + + def _add_common_args(self, sub_parser): + """Adds arguments used by several sub-commands. + + Arguments: + sub_parser: The parser to add arguments to. + """ + sub_parser.add_argument('--algorithm', + help='Algorithm to use (default: NONE)', + metavar='ALGORITHM', + default='NONE') + sub_parser.add_argument('--key', + help='Path to RSA private key file', + metavar='KEY', + required=False) + sub_parser.add_argument('--signing_helper', + help='Path to helper used for signing', + metavar='APP', + default=None, + required=False) + sub_parser.add_argument('--signing_helper_with_files', + help='Path to helper used for signing using files', + metavar='APP', + default=None, + required=False) + sub_parser.add_argument('--public_key_metadata', + help='Path to public key metadata file', + metavar='KEY_METADATA', + required=False) + sub_parser.add_argument('--rollback_index', + help='Rollback Index', + type=parse_number, + default=0) + # This is used internally for unit tests. Do not include in --help output. + sub_parser.add_argument('--internal_release_string', + help=argparse.SUPPRESS) + sub_parser.add_argument('--append_to_release_string', + help='Text to append to release string', + metavar='STR') + sub_parser.add_argument('--prop', + help='Add property', + metavar='KEY:VALUE', + action='append') + sub_parser.add_argument('--prop_from_file', + help='Add property from file', + metavar='KEY:PATH', + action='append') + sub_parser.add_argument('--kernel_cmdline', + help='Add kernel cmdline', + metavar='CMDLINE', + action='append') + # TODO(zeuthen): the --setup_rootfs_from_kernel option used to be called + # --generate_dm_verity_cmdline_from_hashtree. Remove support for the latter + # at some future point. + sub_parser.add_argument('--setup_rootfs_from_kernel', + '--generate_dm_verity_cmdline_from_hashtree', + metavar='IMAGE', + help='Adds kernel cmdline to set up IMAGE', + type=argparse.FileType('rb')) + sub_parser.add_argument('--include_descriptors_from_image', + help='Include descriptors from image', + metavar='IMAGE', + action='append', + type=argparse.FileType('rb')) + sub_parser.add_argument('--print_required_libavb_version', + help=('Don\'t store the footer - ' + 'instead calculate the required libavb ' + 'version for the given options.'), + action='store_true') + # These are only allowed from top-level vbmeta and boot-in-lieu-of-vbmeta. + sub_parser.add_argument('--chain_partition', + help='Allow signed integrity-data for partition', + metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH', + action='append') + sub_parser.add_argument('--flags', + help='VBMeta flags', + type=parse_number, + default=0) + sub_parser.add_argument('--set_hashtree_disabled_flag', + help='Set the HASHTREE_DISABLED flag', + action='store_true') + + def _add_common_footer_args(self, sub_parser): + """Adds arguments used by add_*_footer sub-commands. + + Arguments: + sub_parser: The parser to add arguments to. + """ + sub_parser.add_argument('--use_persistent_digest', + help='Use a persistent digest on device instead of ' + 'storing the digest in the descriptor. This ' + 'cannot be used with A/B so must be combined ' + 'with --do_not_use_ab when an A/B suffix is ' + 'expected at runtime.', + action='store_true') + sub_parser.add_argument('--do_not_use_ab', + help='The partition does not use A/B even when an ' + 'A/B suffix is present. This must not be used ' + 'for vbmeta or chained partitions.', + action='store_true') + + def _fixup_common_args(self, args): + """Common fixups needed by subcommands. + + Arguments: + args: Arguments to modify. + + Returns: + The modified arguments. + """ + if args.set_hashtree_disabled_flag: + args.flags |= AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED + return args + + def run(self, argv): + """Command-line processor. + + Arguments: + argv: Pass sys.argv from main. + """ + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(title='subcommands') + + sub_parser = subparsers.add_parser('version', + help='Prints version of avbtool.') + sub_parser.set_defaults(func=self.version) + + sub_parser = subparsers.add_parser('extract_public_key', + help='Extract public key.') + sub_parser.add_argument('--key', + help='Path to RSA private key file', + required=True) + sub_parser.add_argument('--output', + help='Output file name', + type=argparse.FileType('wb'), + required=True) + sub_parser.set_defaults(func=self.extract_public_key) + + sub_parser = subparsers.add_parser('make_vbmeta_image', + help='Makes a vbmeta image.') + sub_parser.add_argument('--output', + help='Output file name', + type=argparse.FileType('wb')) + sub_parser.add_argument('--padding_size', + metavar='NUMBER', + help='If non-zero, pads output with NUL bytes so ' + 'its size is a multiple of NUMBER ' + '(default: 0)', + type=parse_number, + default=0) + self._add_common_args(sub_parser) + sub_parser.set_defaults(func=self.make_vbmeta_image) + + sub_parser = subparsers.add_parser('make_icp_from_vbmeta', + help='Makes an ICP enhanced vbmeta image' + ' from an existing vbmeta image.') + sub_parser.add_argument('--output', + help='Output file name.', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--vbmeta_image_path', + help='Path to a generate vbmeta image file.') + sub_parser.add_argument('--version_incremental', help='Current build ID.') + sub_parser.add_argument('--manufacturer_key', + help='Path to the PEM file containing the ' + 'manufacturer key for use with the log.') + sub_parser.add_argument('--transparency_log_servers', + help='List of transparency log servers in ' + 'host:port format. This must not be None and must ' + 'be the same size as transparency_log_pub_keys. ' + 'Also, transparency_log_servers[n] must correspond ' + 'to transparency_log_pub_keys[n] for all values n.', + nargs='*') + sub_parser.add_argument('--transparency_log_pub_keys', + help='Paths to PEM files containing transparency ' + 'log server key(s). This must not be None and must ' + 'be the same size as transparency_log_servers. ' + 'Also, transparency_log_pub_keys[n] must ' + 'correspond to transparency_log_servers[n] for all ' + 'values n.', nargs='*') + sub_parser.add_argument('--padding_size', + metavar='NUMBER', + help='If non-zero, pads output with NUL bytes so ' + 'its size is a multiple of NUMBER ' + '(default: 0)', + type=parse_number, + default=0) + self._add_common_args(sub_parser) + sub_parser.set_defaults(func=self.make_icp_from_vbmeta) + + sub_parser = subparsers.add_parser('add_hash_footer', + help='Add hashes and footer to image.') + sub_parser.add_argument('--image', + help='Image to add hashes to', + type=argparse.FileType('rab+')) + sub_parser.add_argument('--partition_size', + help='Partition size', + type=parse_number) + sub_parser.add_argument('--partition_name', + help='Partition name', + default=None) + sub_parser.add_argument('--hash_algorithm', + help='Hash algorithm to use (default: sha256)', + default='sha256') + sub_parser.add_argument('--salt', + help='Salt in hex (default: /dev/urandom)') + sub_parser.add_argument('--calc_max_image_size', + help=('Don\'t store the footer - ' + 'instead calculate the maximum image size ' + 'leaving enough room for metadata with ' + 'the given partition size.'), + action='store_true') + sub_parser.add_argument('--output_vbmeta_image', + help='Also write vbmeta struct to file', + type=argparse.FileType('wb')) + sub_parser.add_argument('--do_not_append_vbmeta_image', + help=('Do not append vbmeta struct or footer ' + 'to the image'), + action='store_true') + self._add_common_args(sub_parser) + self._add_common_footer_args(sub_parser) + sub_parser.set_defaults(func=self.add_hash_footer) + + sub_parser = subparsers.add_parser('append_vbmeta_image', + help='Append vbmeta image to image.') + sub_parser.add_argument('--image', + help='Image to append vbmeta blob to', + type=argparse.FileType('rab+')) + sub_parser.add_argument('--partition_size', + help='Partition size', + type=parse_number, + required=True) + sub_parser.add_argument('--vbmeta_image', + help='Image with vbmeta blob to append', + type=argparse.FileType('rb')) + sub_parser.set_defaults(func=self.append_vbmeta_image) + + sub_parser = subparsers.add_parser( + 'add_hashtree_footer', + help='Add hashtree and footer to image.') + sub_parser.add_argument('--image', + help='Image to add hashtree to', + type=argparse.FileType('rab+')) + sub_parser.add_argument('--partition_size', + help='Partition size', + default=0, + type=parse_number) + sub_parser.add_argument('--partition_name', + help='Partition name', + default='') + sub_parser.add_argument('--hash_algorithm', + help='Hash algorithm to use (default: sha1)', + default='sha1') + sub_parser.add_argument('--salt', + help='Salt in hex (default: /dev/urandom)') + sub_parser.add_argument('--block_size', + help='Block size (default: 4096)', + type=parse_number, + default=4096) + # TODO(zeuthen): The --generate_fec option was removed when we + # moved to generating FEC by default. To avoid breaking existing + # users needing to transition we simply just print a warning below + # in add_hashtree_footer(). Remove this option and the warning at + # some point in the future. + sub_parser.add_argument('--generate_fec', + help=argparse.SUPPRESS, + action='store_true') + sub_parser.add_argument( + '--do_not_generate_fec', + help='Do not generate forward-error-correction codes', + action='store_true') + sub_parser.add_argument('--fec_num_roots', + help='Number of roots for FEC (default: 2)', + type=parse_number, + default=2) + sub_parser.add_argument('--calc_max_image_size', + help=('Don\'t store the hashtree or footer - ' + 'instead calculate the maximum image size ' + 'leaving enough room for hashtree ' + 'and metadata with the given partition ' + 'size.'), + action='store_true') + sub_parser.add_argument('--output_vbmeta_image', + help='Also write vbmeta struct to file', + type=argparse.FileType('wb')) + sub_parser.add_argument('--do_not_append_vbmeta_image', + help=('Do not append vbmeta struct or footer ' + 'to the image'), + action='store_true') + # This is different from --setup_rootfs_from_kernel insofar that + # it doesn't take an IMAGE, the generated cmdline will be for the + # hashtree we're adding. + sub_parser.add_argument('--setup_as_rootfs_from_kernel', + action='store_true', + help='Adds kernel cmdline for setting up rootfs') + sub_parser.add_argument('--no_hashtree', + action='store_true', + help='Do not append hashtree') + self._add_common_args(sub_parser) + self._add_common_footer_args(sub_parser) + sub_parser.set_defaults(func=self.add_hashtree_footer) + + sub_parser = subparsers.add_parser('erase_footer', + help='Erase footer from an image.') + sub_parser.add_argument('--image', + help='Image with a footer', + type=argparse.FileType('rwb+'), + required=True) + sub_parser.add_argument('--keep_hashtree', + help='Keep the hashtree and FEC in the image', + action='store_true') + sub_parser.set_defaults(func=self.erase_footer) + + sub_parser = subparsers.add_parser('zero_hashtree', + help='Zero out hashtree and FEC data.') + sub_parser.add_argument('--image', + help='Image with a footer', + type=argparse.FileType('rwb+'), + required=True) + sub_parser.set_defaults(func=self.zero_hashtree) + + sub_parser = subparsers.add_parser( + 'extract_vbmeta_image', + help='Extracts vbmeta from an image with a footer.') + sub_parser.add_argument('--image', + help='Image with footer', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--output', + help='Output file name', + type=argparse.FileType('wb')) + sub_parser.add_argument('--padding_size', + metavar='NUMBER', + help='If non-zero, pads output with NUL bytes so ' + 'its size is a multiple of NUMBER ' + '(default: 0)', + type=parse_number, + default=0) + sub_parser.set_defaults(func=self.extract_vbmeta_image) + + sub_parser = subparsers.add_parser('resize_image', + help='Resize image with a footer.') + sub_parser.add_argument('--image', + help='Image with a footer', + type=argparse.FileType('rwb+'), + required=True) + sub_parser.add_argument('--partition_size', + help='New partition size', + type=parse_number) + sub_parser.set_defaults(func=self.resize_image) + + sub_parser = subparsers.add_parser( + 'info_image', + help='Show information about vbmeta or footer.') + sub_parser.add_argument('--image', + help='Image to show information about', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--output', + help='Write info to file', + type=argparse.FileType('wt'), + default=sys.stdout) + sub_parser.set_defaults(func=self.info_image) + + sub_parser = subparsers.add_parser( + 'info_image_icp', + help='Show information about AFTL ICPs in vbmeta or footer.') + sub_parser.add_argument('--image', + help='Image to show information about', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--output', + help='Write info to file', + type=argparse.FileType('wt'), + default=sys.stdout) + sub_parser.set_defaults(func=self.info_image_icp) + + sub_parser = subparsers.add_parser( + 'verify_image', + help='Verify an image.') + sub_parser.add_argument('--image', + help='Image to verify', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--key', + help='Check embedded public key matches KEY', + metavar='KEY', + required=False) + sub_parser.add_argument('--expected_chain_partition', + help='Expected chain partition', + metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH', + action='append') + sub_parser.add_argument( + '--follow_chain_partitions', + help=('Follows chain partitions even when not ' + 'specified with the --expected_chain_partition option'), + action='store_true') + sub_parser.add_argument( + '--accept_zeroed_hashtree', + help=('Accept images where the hashtree or FEC data is zeroed out'), + action='store_true') + sub_parser.set_defaults(func=self.verify_image) + + sub_parser = subparsers.add_parser( + 'calculate_vbmeta_digest', + help='Calculate vbmeta digest.') + sub_parser.add_argument('--image', + help='Image to calculate digest for', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--hash_algorithm', + help='Hash algorithm to use (default: sha256)', + default='sha256') + sub_parser.add_argument('--output', + help='Write hex digest to file (default: stdout)', + type=argparse.FileType('wt'), + default=sys.stdout) + sub_parser.set_defaults(func=self.calculate_vbmeta_digest) + + sub_parser = subparsers.add_parser( + 'calculate_kernel_cmdline', + help='Calculate kernel cmdline.') + sub_parser.add_argument('--image', + help='Image to calculate kernel cmdline for', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--hashtree_disabled', + help='Return the cmdline for hashtree disabled', + action='store_true') + sub_parser.add_argument('--output', + help='Write cmdline to file (default: stdout)', + type=argparse.FileType('wt'), + default=sys.stdout) + sub_parser.set_defaults(func=self.calculate_kernel_cmdline) + + sub_parser = subparsers.add_parser('set_ab_metadata', + help='Set A/B metadata.') + sub_parser.add_argument('--misc_image', + help=('The misc image to modify. If the image does ' + 'not exist, it will be created.'), + type=argparse.FileType('r+b'), + required=True) + sub_parser.add_argument('--slot_data', + help=('Slot data of the form "priority", ' + '"tries_remaining", "sucessful_boot" for ' + 'slot A followed by the same for slot B, ' + 'separated by colons. The default value ' + 'is 15:7:0:14:7:0.'), + default='15:7:0:14:7:0') + sub_parser.set_defaults(func=self.set_ab_metadata) + + sub_parser = subparsers.add_parser( + 'make_atx_certificate', + help='Create an Android Things eXtension (ATX) certificate.') + sub_parser.add_argument('--output', + help='Write certificate to file', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--subject', + help=('Path to subject file'), + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--subject_key', + help=('Path to subject RSA public key file'), + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--subject_key_version', + help=('Version of the subject key'), + type=parse_number, + required=False) + sub_parser.add_argument('--subject_is_intermediate_authority', + help=('Generate an intermediate authority ' + 'certificate'), + action='store_true') + sub_parser.add_argument('--usage', + help=('Override usage with a hash of the provided ' + 'string'), + required=False) + sub_parser.add_argument('--authority_key', + help='Path to authority RSA private key file', + required=False) + sub_parser.add_argument('--signing_helper', + help='Path to helper used for signing', + metavar='APP', + default=None, + required=False) + sub_parser.add_argument('--signing_helper_with_files', + help='Path to helper used for signing using files', + metavar='APP', + default=None, + required=False) + sub_parser.set_defaults(func=self.make_atx_certificate) + + sub_parser = subparsers.add_parser( + 'make_atx_permanent_attributes', + help='Create Android Things eXtension (ATX) permanent attributes.') + sub_parser.add_argument('--output', + help='Write attributes to file', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--root_authority_key', + help='Path to authority RSA public key file', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--product_id', + help=('Path to Product ID file'), + type=argparse.FileType('rb'), + required=True) + sub_parser.set_defaults(func=self.make_atx_permanent_attributes) + + sub_parser = subparsers.add_parser( + 'make_atx_metadata', + help='Create Android Things eXtension (ATX) metadata.') + sub_parser.add_argument('--output', + help='Write metadata to file', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--intermediate_key_certificate', + help='Path to intermediate key certificate file', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--product_key_certificate', + help='Path to product key certificate file', + type=argparse.FileType('rb'), + required=True) + sub_parser.set_defaults(func=self.make_atx_metadata) + + sub_parser = subparsers.add_parser( + 'make_atx_unlock_credential', + help='Create an Android Things eXtension (ATX) unlock credential.') + sub_parser.add_argument('--output', + help='Write credential to file', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--intermediate_key_certificate', + help='Path to intermediate key certificate file', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--unlock_key_certificate', + help='Path to unlock key certificate file', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--challenge', + help='Path to the challenge to sign (optional). If ' + 'this is not provided the challenge signature ' + 'field is omitted and can be concatenated ' + 'later.', + required=False) + sub_parser.add_argument('--unlock_key', + help='Path to unlock key (optional). Must be ' + 'provided if using --challenge.', + required=False) + sub_parser.add_argument('--signing_helper', + help='Path to helper used for signing', + metavar='APP', + default=None, + required=False) + sub_parser.add_argument('--signing_helper_with_files', + help='Path to helper used for signing using files', + metavar='APP', + default=None, + required=False) + sub_parser.set_defaults(func=self.make_atx_unlock_credential) + + args = parser.parse_args(argv[1:]) + try: + args.func(args) + except AvbError as e: + sys.stderr.write('{}: {}\n'.format(argv[0], str(e))) + sys.exit(1) + + def version(self, _): + """Implements the 'version' sub-command.""" + print(get_release_string()) + + def extract_public_key(self, args): + """Implements the 'extract_public_key' sub-command.""" + self.avb.extract_public_key(args.key, args.output) + + def make_vbmeta_image(self, args): + """Implements the 'make_vbmeta_image' sub-command.""" + args = self._fixup_common_args(args) + self.avb.make_vbmeta_image(args.output, args.chain_partition, + args.algorithm, args.key, + args.public_key_metadata, args.rollback_index, + args.flags, args.prop, args.prop_from_file, + args.kernel_cmdline, + args.setup_rootfs_from_kernel, + args.include_descriptors_from_image, + args.signing_helper, + args.signing_helper_with_files, + args.internal_release_string, + args.append_to_release_string, + args.print_required_libavb_version, + args.padding_size) + + def make_icp_from_vbmeta(self, args): + """Implements the 'make_icp_from_vbmeta' sub-command.""" + args = self._fixup_common_args(args) + self.avb.make_icp_from_vbmeta(args.vbmeta_image_path, + args.output, args.algorithm, + args.signing_helper, + args.signing_helper_with_files, + args.version_incremental, + args.transparency_log_servers, + args.transparency_log_pub_keys, + args.manufacturer_key, + args.padding_size) + + def append_vbmeta_image(self, args): + """Implements the 'append_vbmeta_image' sub-command.""" + self.avb.append_vbmeta_image(args.image.name, args.vbmeta_image.name, + args.partition_size) + + def add_hash_footer(self, args): + """Implements the 'add_hash_footer' sub-command.""" + args = self._fixup_common_args(args) + self.avb.add_hash_footer(args.image.name if args.image else None, + args.partition_size, + args.partition_name, args.hash_algorithm, + args.salt, args.chain_partition, args.algorithm, + args.key, + args.public_key_metadata, args.rollback_index, + args.flags, args.prop, args.prop_from_file, + args.kernel_cmdline, + args.setup_rootfs_from_kernel, + args.include_descriptors_from_image, + args.calc_max_image_size, + args.signing_helper, + args.signing_helper_with_files, + args.internal_release_string, + args.append_to_release_string, + args.output_vbmeta_image, + args.do_not_append_vbmeta_image, + args.print_required_libavb_version, + args.use_persistent_digest, + args.do_not_use_ab) + + def add_hashtree_footer(self, args): + """Implements the 'add_hashtree_footer' sub-command.""" + args = self._fixup_common_args(args) + # TODO(zeuthen): Remove when removing support for the + # '--generate_fec' option above. + if args.generate_fec: + sys.stderr.write('The --generate_fec option is deprecated since FEC ' + 'is now generated by default. Use the option ' + '--do_not_generate_fec to not generate FEC.\n') + self.avb.add_hashtree_footer( + args.image.name if args.image else None, + args.partition_size, + args.partition_name, + not args.do_not_generate_fec, args.fec_num_roots, + args.hash_algorithm, args.block_size, + args.salt, args.chain_partition, args.algorithm, + args.key, args.public_key_metadata, + args.rollback_index, args.flags, args.prop, + args.prop_from_file, + args.kernel_cmdline, + args.setup_rootfs_from_kernel, + args.setup_as_rootfs_from_kernel, + args.include_descriptors_from_image, + args.calc_max_image_size, + args.signing_helper, + args.signing_helper_with_files, + args.internal_release_string, + args.append_to_release_string, + args.output_vbmeta_image, + args.do_not_append_vbmeta_image, + args.print_required_libavb_version, + args.use_persistent_digest, + args.do_not_use_ab, + args.no_hashtree) + + def erase_footer(self, args): + """Implements the 'erase_footer' sub-command.""" + self.avb.erase_footer(args.image.name, args.keep_hashtree) + + def zero_hashtree(self, args): + """Implements the 'zero_hashtree' sub-command.""" + self.avb.zero_hashtree(args.image.name) + + def extract_vbmeta_image(self, args): + """Implements the 'extract_vbmeta_image' sub-command.""" + self.avb.extract_vbmeta_image(args.output, args.image.name, + args.padding_size) + + def resize_image(self, args): + """Implements the 'resize_image' sub-command.""" + self.avb.resize_image(args.image.name, args.partition_size) + + def set_ab_metadata(self, args): + """Implements the 'set_ab_metadata' sub-command.""" + self.avb.set_ab_metadata(args.misc_image, args.slot_data) + + def info_image(self, args): + """Implements the 'info_image' sub-command.""" + self.avb.info_image(args.image.name, args.output) + + def info_image_icp(self, args): + """Implements the 'info_image_icp' sub-command.""" + self.avb.info_image_icp(args.image.name, args.output) + + def verify_image(self, args): + """Implements the 'verify_image' sub-command.""" + self.avb.verify_image(args.image.name, args.key, + args.expected_chain_partition, + args.follow_chain_partitions, + args.accept_zeroed_hashtree) + + def calculate_vbmeta_digest(self, args): + """Implements the 'calculate_vbmeta_digest' sub-command.""" + self.avb.calculate_vbmeta_digest(args.image.name, args.hash_algorithm, + args.output) + + def calculate_kernel_cmdline(self, args): + """Implements the 'calculate_kernel_cmdline' sub-command.""" + self.avb.calculate_kernel_cmdline(args.image.name, args.hashtree_disabled, + args.output) + + def make_atx_certificate(self, args): + """Implements the 'make_atx_certificate' sub-command.""" + self.avb.make_atx_certificate(args.output, args.authority_key, + args.subject_key.name, + args.subject_key_version, + args.subject.read(), + args.subject_is_intermediate_authority, + args.usage, + args.signing_helper, + args.signing_helper_with_files) + + def make_atx_permanent_attributes(self, args): + """Implements the 'make_atx_permanent_attributes' sub-command.""" + self.avb.make_atx_permanent_attributes(args.output, + args.root_authority_key.name, + args.product_id.read()) + + def make_atx_metadata(self, args): + """Implements the 'make_atx_metadata' sub-command.""" + self.avb.make_atx_metadata(args.output, + args.intermediate_key_certificate.read(), + args.product_key_certificate.read()) + + def make_atx_unlock_credential(self, args): + """Implements the 'make_atx_unlock_credential' sub-command.""" + self.avb.make_atx_unlock_credential( + args.output, + args.intermediate_key_certificate.read(), + args.unlock_key_certificate.read(), + args.challenge, + args.unlock_key, + args.signing_helper, + args.signing_helper_with_files) + + +if __name__ == '__main__': + tool = AvbTool() + tool.run(sys.argv) diff --git a/aosp/avb/avbtool.v1.2.py b/aosp/avb/avbtool.v1.2.py new file mode 100755 index 00000000..67416d4c --- /dev/null +++ b/aosp/avb/avbtool.v1.2.py @@ -0,0 +1,4935 @@ +#!/usr/bin/env python3 + +# Copyright 2016, The Android Open Source Project +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +"""Command-line tool for working with Android Verified Boot images.""" + +import argparse +import binascii +import bisect +import hashlib +import json +import math +import os +import struct +import subprocess +import sys +import tempfile +import time + +# Keep in sync with libavb/avb_version.h. +AVB_VERSION_MAJOR = 1 +AVB_VERSION_MINOR = 2 +AVB_VERSION_SUB = 0 + +# Keep in sync with libavb/avb_footer.h. +AVB_FOOTER_VERSION_MAJOR = 1 +AVB_FOOTER_VERSION_MINOR = 0 + +AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = 1 + +# Configuration for enabling logging of calls to avbtool. +AVB_INVOCATION_LOGFILE = os.environ.get('AVB_INVOCATION_LOGFILE') + + +class AvbError(Exception): + """Application-specific errors. + + These errors represent issues for which a stack-trace should not be + presented. + + Attributes: + message: Error message. + """ + + def __init__(self, message): + Exception.__init__(self, message) + + +class Algorithm(object): + """Contains details about an algorithm. + + See the avb_vbmeta_image.h file for more details about algorithms. + + The constant |ALGORITHMS| is a dictionary from human-readable + names (e.g 'SHA256_RSA2048') to instances of this class. + + Attributes: + algorithm_type: Integer code corresponding to |AvbAlgorithmType|. + hash_name: Empty or a name from |hashlib.algorithms|. + hash_num_bytes: Number of bytes used to store the hash. + signature_num_bytes: Number of bytes used to store the signature. + public_key_num_bytes: Number of bytes used to store the public key. + padding: Padding used for signature as bytes, if any. + """ + + def __init__(self, algorithm_type, hash_name, hash_num_bytes, + signature_num_bytes, public_key_num_bytes, padding): + self.algorithm_type = algorithm_type + self.hash_name = hash_name + self.hash_num_bytes = hash_num_bytes + self.signature_num_bytes = signature_num_bytes + self.public_key_num_bytes = public_key_num_bytes + self.padding = padding + + +# This must be kept in sync with the avb_crypto.h file. +# +# The PKC1-v1.5 padding is a blob of binary DER of ASN.1 and is +# obtained from section 5.2.2 of RFC 4880. +ALGORITHMS = { + 'NONE': Algorithm( + algorithm_type=0, # AVB_ALGORITHM_TYPE_NONE + hash_name='', + hash_num_bytes=0, + signature_num_bytes=0, + public_key_num_bytes=0, + padding=b''), + 'SHA256_RSA2048': Algorithm( + algorithm_type=1, # AVB_ALGORITHM_TYPE_SHA256_RSA2048 + hash_name='sha256', + hash_num_bytes=32, + signature_num_bytes=256, + public_key_num_bytes=8 + 2*2048//8, + padding=bytes(bytearray([ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*202 + [0x00] + [ + # ASN.1 header + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20, + ]))), + 'SHA256_RSA4096': Algorithm( + algorithm_type=2, # AVB_ALGORITHM_TYPE_SHA256_RSA4096 + hash_name='sha256', + hash_num_bytes=32, + signature_num_bytes=512, + public_key_num_bytes=8 + 2*4096//8, + padding=bytes(bytearray([ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*458 + [0x00] + [ + # ASN.1 header + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20, + ]))), + 'SHA256_RSA8192': Algorithm( + algorithm_type=3, # AVB_ALGORITHM_TYPE_SHA256_RSA8192 + hash_name='sha256', + hash_num_bytes=32, + signature_num_bytes=1024, + public_key_num_bytes=8 + 2*8192//8, + padding=bytes(bytearray([ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*970 + [0x00] + [ + # ASN.1 header + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20, + ]))), + 'SHA512_RSA2048': Algorithm( + algorithm_type=4, # AVB_ALGORITHM_TYPE_SHA512_RSA2048 + hash_name='sha512', + hash_num_bytes=64, + signature_num_bytes=256, + public_key_num_bytes=8 + 2*2048//8, + padding=bytes(bytearray([ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*170 + [0x00] + [ + # ASN.1 header + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 + ]))), + 'SHA512_RSA4096': Algorithm( + algorithm_type=5, # AVB_ALGORITHM_TYPE_SHA512_RSA4096 + hash_name='sha512', + hash_num_bytes=64, + signature_num_bytes=512, + public_key_num_bytes=8 + 2*4096//8, + padding=bytes(bytearray([ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*426 + [0x00] + [ + # ASN.1 header + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 + ]))), + 'SHA512_RSA8192': Algorithm( + algorithm_type=6, # AVB_ALGORITHM_TYPE_SHA512_RSA8192 + hash_name='sha512', + hash_num_bytes=64, + signature_num_bytes=1024, + public_key_num_bytes=8 + 2*8192//8, + padding=bytes(bytearray([ + # PKCS1-v1_5 padding + 0x00, 0x01] + [0xff]*938 + [0x00] + [ + # ASN.1 header + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 + ]))), +} + + +def get_release_string(): + """Calculates the release string to use in the VBMeta struct.""" + # Keep in sync with libavb/avb_version.c:avb_version_string(). + return 'avbtool {}.{}.{}'.format(AVB_VERSION_MAJOR, + AVB_VERSION_MINOR, + AVB_VERSION_SUB) + + +def round_to_multiple(number, size): + """Rounds a number up to nearest multiple of another number. + + Arguments: + number: The number to round up. + size: The multiple to round up to. + + Returns: + If |number| is a multiple of |size|, returns |number|, otherwise + returns |number| + |size|. + """ + remainder = number % size + if remainder == 0: + return number + return number + size - remainder + + +def round_to_pow2(number): + """Rounds a number up to the next power of 2. + + Arguments: + number: The number to round up. + + Returns: + If |number| is already a power of 2 then |number| is + returned. Otherwise the smallest power of 2 greater than |number| + is returned. + """ + return 2**((number - 1).bit_length()) + + +def encode_long(num_bits, value): + """Encodes a long to a bytearray() using a given amount of bits. + + This number is written big-endian, e.g. with the most significant + bit first. + + This is the reverse of decode_long(). + + Arguments: + num_bits: The number of bits to write, e.g. 2048. + value: The value to write. + + Returns: + A bytearray() with the encoded long. + """ + ret = bytearray() + for bit_pos in range(num_bits, 0, -8): + octet = (value >> (bit_pos - 8)) & 0xff + ret.extend(struct.pack('!B', octet)) + return ret + + +def decode_long(blob): + """Decodes a long from a bytearray() using a given amount of bits. + + This number is expected to be in big-endian, e.g. with the most + significant bit first. + + This is the reverse of encode_long(). + + Arguments: + blob: A bytearray() with the encoded long. + + Returns: + The decoded value. + """ + ret = 0 + for b in bytearray(blob): + ret *= 256 + ret += b + return ret + + +def egcd(a, b): + """Calculate greatest common divisor of two numbers. + + This implementation uses a recursive version of the extended + Euclidian algorithm. + + Arguments: + a: First number. + b: Second number. + + Returns: + A tuple (gcd, x, y) that where |gcd| is the greatest common + divisor of |a| and |b| and |a|*|x| + |b|*|y| = |gcd|. + """ + if a == 0: + return (b, 0, 1) + g, y, x = egcd(b % a, a) + return (g, x - (b // a) * y, y) + + +def modinv(a, m): + """Calculate modular multiplicative inverse of |a| modulo |m|. + + This calculates the number |x| such that |a| * |x| == 1 (modulo + |m|). This number only exists if |a| and |m| are co-prime - |None| + is returned if this isn't true. + + Arguments: + a: The number to calculate a modular inverse of. + m: The modulo to use. + + Returns: + The modular multiplicative inverse of |a| and |m| or |None| if + these numbers are not co-prime. + """ + gcd, x, _ = egcd(a, m) + if gcd != 1: + return None # modular inverse does not exist + return x % m + + +def parse_number(string): + """Parse a string as a number. + + This is just a short-hand for int(string, 0) suitable for use in the + |type| parameter of |ArgumentParser|'s add_argument() function. An + improvement to just using type=int is that this function supports + numbers in other bases, e.g. "0x1234". + + Arguments: + string: The string to parse. + + Returns: + The parsed integer. + + Raises: + ValueError: If the number could not be parsed. + """ + return int(string, 0) + + +class RSAPublicKey(object): + """Data structure used for a RSA public key. + + Attributes: + exponent: The key exponent. + modulus: The key modulus. + num_bits: The key size. + key_path: The path to a key file. + """ + + MODULUS_PREFIX = b'modulus=' + + def __init__(self, key_path): + """Loads and parses an RSA key from either a private or public key file. + + Arguments: + key_path: The path to a key file. + + Raises: + AvbError: If RSA key parameters could not be read from file. + """ + # We used to have something as simple as this: + # + # key = Crypto.PublicKey.RSA.importKey(open(key_path).read()) + # self.exponent = key.e + # self.modulus = key.n + # self.num_bits = key.size() + 1 + # + # but unfortunately PyCrypto is not available in the builder. So + # instead just parse openssl(1) output to get this + # information. It's ugly but... + args = ['openssl', 'rsa', '-in', key_path, '-modulus', '-noout'] + p = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate() + if p.wait() != 0: + # Could be just a public key is passed, try that. + args.append('-pubin') + p = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate() + if p.wait() != 0: + raise AvbError('Error getting public key: {}'.format(perr)) + + if not pout.lower().startswith(self.MODULUS_PREFIX): + raise AvbError('Unexpected modulus output') + + modulus_hexstr = pout[len(self.MODULUS_PREFIX):] + + # The exponent is assumed to always be 65537 and the number of + # bits can be derived from the modulus by rounding up to the + # nearest power of 2. + self.key_path = key_path + self.modulus = int(modulus_hexstr, 16) + self.num_bits = round_to_pow2(int(math.ceil(math.log(self.modulus, 2)))) + self.exponent = 65537 + + def encode(self): + """Encodes the public RSA key in |AvbRSAPublicKeyHeader| format. + + This creates a |AvbRSAPublicKeyHeader| as well as the two large + numbers (|key_num_bits| bits long) following it. + + Returns: + The |AvbRSAPublicKeyHeader| followed by two large numbers as bytes. + + Raises: + AvbError: If given RSA key exponent is not 65537. + """ + if self.exponent != 65537: + raise AvbError('Only RSA keys with exponent 65537 are supported.') + ret = bytearray() + # Calculate n0inv = -1/n[0] (mod 2^32) + b = 2 ** 32 + n0inv = b - modinv(self.modulus, b) + # Calculate rr = r^2 (mod N), where r = 2^(# of key bits) + r = 2 ** self.modulus.bit_length() + rrmodn = r * r % self.modulus + ret.extend(struct.pack('!II', self.num_bits, n0inv)) + ret.extend(encode_long(self.num_bits, self.modulus)) + ret.extend(encode_long(self.num_bits, rrmodn)) + return bytes(ret) + + def sign(self, algorithm_name, data_to_sign, signing_helper=None, + signing_helper_with_files=None): + """Sign given data using |signing_helper| or openssl. + + openssl is used if neither the parameters signing_helper nor + signing_helper_with_files are given. + + Arguments: + algorithm_name: The algorithm name as per the ALGORITHMS dict. + data_to_sign: Data to sign as bytes or bytearray. + signing_helper: Program which signs a hash and returns the signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + + Returns: + The signature as bytes. + + Raises: + AvbError: If an error occurred during signing. + """ + # Checks requested algorithm for validity. + algorithm = ALGORITHMS.get(algorithm_name) + if not algorithm: + raise AvbError('Algorithm with name {} is not supported.' + .format(algorithm_name)) + + if self.num_bits != (algorithm.signature_num_bytes * 8): + raise AvbError('Key size of key ({} bits) does not match key size ' + '({} bits) of given algorithm {}.' + .format(self.num_bits, algorithm.signature_num_bytes * 8, + algorithm_name)) + + # Hashes the data. + hasher = hashlib.new(algorithm.hash_name) + hasher.update(data_to_sign) + digest = hasher.digest() + + # Calculates the signature. + padding_and_hash = algorithm.padding + digest + p = None + if signing_helper_with_files is not None: + with tempfile.NamedTemporaryFile() as signing_file: + signing_file.write(padding_and_hash) + signing_file.flush() + p = subprocess.Popen([signing_helper_with_files, algorithm_name, + self.key_path, signing_file.name]) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error signing') + signing_file.seek(0) + signature = signing_file.read() + else: + if signing_helper is not None: + p = subprocess.Popen( + [signing_helper, algorithm_name, self.key_path], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + else: + p = subprocess.Popen( + ['openssl', 'rsautl', '-sign', '-inkey', self.key_path, '-raw'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate(padding_and_hash) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error signing: {}'.format(perr)) + signature = pout + if len(signature) != algorithm.signature_num_bytes: + raise AvbError('Error signing: Invalid length of signature') + return signature + + +def lookup_algorithm_by_type(alg_type): + """Looks up algorithm by type. + + Arguments: + alg_type: The integer representing the type. + + Returns: + A tuple with the algorithm name and an |Algorithm| instance. + + Raises: + Exception: If the algorithm cannot be found + """ + for alg_name in ALGORITHMS: + alg_data = ALGORITHMS[alg_name] + if alg_data.algorithm_type == alg_type: + return (alg_name, alg_data) + raise AvbError('Unknown algorithm type {}'.format(alg_type)) + + +def lookup_hash_size_by_type(alg_type): + """Looks up hash size by type. + + Arguments: + alg_type: The integer representing the type. + + Returns: + The corresponding hash size. + + Raises: + AvbError: If the algorithm cannot be found. + """ + for alg_name in ALGORITHMS: + alg_data = ALGORITHMS[alg_name] + if alg_data.algorithm_type == alg_type: + return alg_data.hash_num_bytes + raise AvbError('Unsupported algorithm type {}'.format(alg_type)) + + +def verify_vbmeta_signature(vbmeta_header, vbmeta_blob): + """Checks that signature in a vbmeta blob was made by the embedded public key. + + Arguments: + vbmeta_header: A AvbVBMetaHeader. + vbmeta_blob: The whole vbmeta blob, including the header as bytes or + bytearray. + + Returns: + True if the signature is valid and corresponds to the embedded + public key. Also returns True if the vbmeta blob is not signed. + + Raises: + AvbError: If there errors calling out to openssl command during + signature verification. + """ + (_, alg) = lookup_algorithm_by_type(vbmeta_header.algorithm_type) + if not alg.hash_name: + return True + header_blob = vbmeta_blob[0:256] + auth_offset = 256 + aux_offset = auth_offset + vbmeta_header.authentication_data_block_size + aux_size = vbmeta_header.auxiliary_data_block_size + aux_blob = vbmeta_blob[aux_offset:aux_offset + aux_size] + pubkey_offset = aux_offset + vbmeta_header.public_key_offset + pubkey_size = vbmeta_header.public_key_size + pubkey_blob = vbmeta_blob[pubkey_offset:pubkey_offset + pubkey_size] + + digest_offset = auth_offset + vbmeta_header.hash_offset + digest_size = vbmeta_header.hash_size + digest_blob = vbmeta_blob[digest_offset:digest_offset + digest_size] + + sig_offset = auth_offset + vbmeta_header.signature_offset + sig_size = vbmeta_header.signature_size + sig_blob = vbmeta_blob[sig_offset:sig_offset + sig_size] + + # Now that we've got the stored digest, public key, and signature + # all we need to do is to verify. This is the exactly the same + # steps as performed in the avb_vbmeta_image_verify() function in + # libavb/avb_vbmeta_image.c. + + ha = hashlib.new(alg.hash_name) + ha.update(header_blob) + ha.update(aux_blob) + computed_digest = ha.digest() + + if computed_digest != digest_blob: + return False + + padding_and_digest = alg.padding + computed_digest + + (num_bits,) = struct.unpack('!I', pubkey_blob[0:4]) + modulus_blob = pubkey_blob[8:8 + num_bits//8] + modulus = decode_long(modulus_blob) + exponent = 65537 + + # We used to have this: + # + # import Crypto.PublicKey.RSA + # key = Crypto.PublicKey.RSA.construct((modulus, long(exponent))) + # if not key.verify(decode_long(padding_and_digest), + # (decode_long(sig_blob), None)): + # return False + # return True + # + # but since 'avbtool verify_image' is used on the builders we don't want + # to rely on Crypto.PublicKey.RSA. Instead just use openssl(1) to verify. + asn1_str = ('asn1=SEQUENCE:pubkeyinfo\n' + '\n' + '[pubkeyinfo]\n' + 'algorithm=SEQUENCE:rsa_alg\n' + 'pubkey=BITWRAP,SEQUENCE:rsapubkey\n' + '\n' + '[rsa_alg]\n' + 'algorithm=OID:rsaEncryption\n' + 'parameter=NULL\n' + '\n' + '[rsapubkey]\n' + 'n=INTEGER:{}\n' + 'e=INTEGER:{}\n').format(hex(modulus).rstrip('L'), + hex(exponent).rstrip('L')) + + with tempfile.NamedTemporaryFile() as asn1_tmpfile: + asn1_tmpfile.write(asn1_str.encode('ascii')) + asn1_tmpfile.flush() + + with tempfile.NamedTemporaryFile() as der_tmpfile: + p = subprocess.Popen( + ['openssl', 'asn1parse', '-genconf', asn1_tmpfile.name, '-out', + der_tmpfile.name, '-noout']) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error generating DER file') + + p = subprocess.Popen( + ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', der_tmpfile.name, + '-keyform', 'DER', '-raw'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (pout, perr) = p.communicate(sig_blob) + retcode = p.wait() + if retcode != 0: + raise AvbError('Error verifying data: {}'.format(perr)) + if pout != padding_and_digest: + sys.stderr.write('Signature not correct\n') + return False + return True + + +def create_avb_hashtree_hasher(algorithm, salt): + """Create the hasher for AVB hashtree based on the input algorithm.""" + + if algorithm.lower() == 'blake2b-256': + return hashlib.new('blake2b', salt, digest_size=32) + + return hashlib.new(algorithm, salt) + + +class ImageChunk(object): + """Data structure used for representing chunks in Android sparse files. + + Attributes: + chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE. + chunk_offset: Offset in the sparse file where this chunk begins. + output_offset: Offset in de-sparsified file where output begins. + output_size: Number of bytes in output. + input_offset: Offset in sparse file for data if TYPE_RAW otherwise None. + fill_data: Blob with data to fill if TYPE_FILL otherwise None. + """ + + FORMAT = '<2H2I' + TYPE_RAW = 0xcac1 + TYPE_FILL = 0xcac2 + TYPE_DONT_CARE = 0xcac3 + TYPE_CRC32 = 0xcac4 + + def __init__(self, chunk_type, chunk_offset, output_offset, output_size, + input_offset, fill_data): + """Initializes an ImageChunk object. + + Arguments: + chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE. + chunk_offset: Offset in the sparse file where this chunk begins. + output_offset: Offset in de-sparsified file. + output_size: Number of bytes in output. + input_offset: Offset in sparse file if TYPE_RAW otherwise None. + fill_data: Blob as bytes with data to fill if TYPE_FILL otherwise None. + + Raises: + ValueError: If given chunk parameters are invalid. + """ + self.chunk_type = chunk_type + self.chunk_offset = chunk_offset + self.output_offset = output_offset + self.output_size = output_size + self.input_offset = input_offset + self.fill_data = fill_data + # Check invariants. + if self.chunk_type == self.TYPE_RAW: + if self.fill_data is not None: + raise ValueError('RAW chunk cannot have fill_data set.') + if not self.input_offset: + raise ValueError('RAW chunk must have input_offset set.') + elif self.chunk_type == self.TYPE_FILL: + if self.fill_data is None: + raise ValueError('FILL chunk must have fill_data set.') + if self.input_offset: + raise ValueError('FILL chunk cannot have input_offset set.') + elif self.chunk_type == self.TYPE_DONT_CARE: + if self.fill_data is not None: + raise ValueError('DONT_CARE chunk cannot have fill_data set.') + if self.input_offset: + raise ValueError('DONT_CARE chunk cannot have input_offset set.') + else: + raise ValueError('Invalid chunk type') + + +class ImageHandler(object): + """Abstraction for image I/O with support for Android sparse images. + + This class provides an interface for working with image files that + may be using the Android Sparse Image format. When an instance is + constructed, we test whether it's an Android sparse file. If so, + operations will be on the sparse file by interpreting the sparse + format, otherwise they will be directly on the file. Either way the + operations do the same. + + For reading, this interface mimics a file object - it has seek(), + tell(), and read() methods. For writing, only truncation + (truncate()) and appending is supported (append_raw() and + append_dont_care()). Additionally, data can only be written in units + of the block size. + + Attributes: + filename: Name of file. + is_sparse: Whether the file being operated on is sparse. + block_size: The block size, typically 4096. + image_size: The size of the unsparsified file. + """ + # See system/core/libsparse/sparse_format.h for details. + MAGIC = 0xed26ff3a + HEADER_FORMAT = ' 0: + raise ValueError('There were {} bytes of extra data at the end of the ' + 'file.'.format(junk_len)) + + # Assign |image_size|. + self.image_size = output_offset + + # This is used when bisecting in read() to find the initial slice. + self._chunk_output_offsets = [i.output_offset for i in self._chunks] + + self.is_sparse = True + + def _update_chunks_and_blocks(self): + """Helper function to update the image header. + + The the |total_chunks| and |total_blocks| fields in the header + will be set to value of the |_num_total_blocks| and + |_num_total_chunks| attributes. + + """ + self._image.seek(self.NUM_CHUNKS_AND_BLOCKS_OFFSET, os.SEEK_SET) + self._image.write(struct.pack(self.NUM_CHUNKS_AND_BLOCKS_FORMAT, + self._num_total_blocks, + self._num_total_chunks)) + + def append_dont_care(self, num_bytes): + """Appends a DONT_CARE chunk to the sparse file. + + The given number of bytes must be a multiple of the block size. + + Arguments: + num_bytes: Size in number of bytes of the DONT_CARE chunk. + + Raises: + OSError: If ImageHandler was initialized in read-only mode. + """ + assert num_bytes % self.block_size == 0 + + if self._read_only: + raise OSError('ImageHandler is in read-only mode.') + + if not self.is_sparse: + self._image.seek(0, os.SEEK_END) + # This is more efficient that writing NUL bytes since it'll add + # a hole on file systems that support sparse files (native + # sparse, not Android sparse). + self._image.truncate(self._image.tell() + num_bytes) + self._read_header() + return + + self._num_total_chunks += 1 + self._num_total_blocks += num_bytes // self.block_size + self._update_chunks_and_blocks() + + self._image.seek(self._sparse_end, os.SEEK_SET) + self._image.write(struct.pack(ImageChunk.FORMAT, + ImageChunk.TYPE_DONT_CARE, + 0, # Reserved + num_bytes // self.block_size, + struct.calcsize(ImageChunk.FORMAT))) + self._read_header() + + def append_raw(self, data, multiple_block_size=True): + """Appends a RAW chunk to the sparse file. + + The length of the given data must be a multiple of the block size, + unless |multiple_block_size| is False. + + Arguments: + data: Data to append as bytes. + multiple_block_size: whether to check the length of the + data is a multiple of the block size. + + Raises: + OSError: If ImageHandler was initialized in read-only mode. + """ + if multiple_block_size: + assert len(data) % self.block_size == 0 + + if self._read_only: + raise OSError('ImageHandler is in read-only mode.') + + if not self.is_sparse: + self._image.seek(0, os.SEEK_END) + self._image.write(data) + self._read_header() + return + + self._num_total_chunks += 1 + self._num_total_blocks += len(data) // self.block_size + self._update_chunks_and_blocks() + + self._image.seek(self._sparse_end, os.SEEK_SET) + self._image.write(struct.pack(ImageChunk.FORMAT, + ImageChunk.TYPE_RAW, + 0, # Reserved + len(data) // self.block_size, + len(data) + + struct.calcsize(ImageChunk.FORMAT))) + self._image.write(data) + self._read_header() + + def append_fill(self, fill_data, size): + """Appends a fill chunk to the sparse file. + + The total length of the fill data must be a multiple of the block size. + + Arguments: + fill_data: Fill data to append - must be four bytes. + size: Number of chunk - must be a multiple of four and the block size. + + Raises: + OSError: If ImageHandler was initialized in read-only mode. + """ + assert len(fill_data) == 4 + assert size % 4 == 0 + assert size % self.block_size == 0 + + if self._read_only: + raise OSError('ImageHandler is in read-only mode.') + + if not self.is_sparse: + self._image.seek(0, os.SEEK_END) + self._image.write(fill_data * (size//4)) + self._read_header() + return + + self._num_total_chunks += 1 + self._num_total_blocks += size // self.block_size + self._update_chunks_and_blocks() + + self._image.seek(self._sparse_end, os.SEEK_SET) + self._image.write(struct.pack(ImageChunk.FORMAT, + ImageChunk.TYPE_FILL, + 0, # Reserved + size // self.block_size, + 4 + struct.calcsize(ImageChunk.FORMAT))) + self._image.write(fill_data) + self._read_header() + + def seek(self, offset): + """Sets the cursor position for reading from unsparsified file. + + Arguments: + offset: Offset to seek to from the beginning of the file. + + Raises: + RuntimeError: If the given offset is negative. + """ + if offset < 0: + raise RuntimeError('Seeking with negative offset: {}'.format(offset)) + self._file_pos = offset + + def read(self, size): + """Reads data from the unsparsified file. + + This method may return fewer than |size| bytes of data if the end + of the file was encountered. + + The file cursor for reading is advanced by the number of bytes + read. + + Arguments: + size: Number of bytes to read. + + Returns: + The data as bytes. + """ + if not self.is_sparse: + self._image.seek(self._file_pos) + data = self._image.read(size) + self._file_pos += len(data) + return data + + # Iterate over all chunks. + chunk_idx = bisect.bisect_right(self._chunk_output_offsets, + self._file_pos) - 1 + data = bytearray() + to_go = size + while to_go > 0: + chunk = self._chunks[chunk_idx] + chunk_pos_offset = self._file_pos - chunk.output_offset + chunk_pos_to_go = min(chunk.output_size - chunk_pos_offset, to_go) + + if chunk.chunk_type == ImageChunk.TYPE_RAW: + self._image.seek(chunk.input_offset + chunk_pos_offset) + data.extend(self._image.read(chunk_pos_to_go)) + elif chunk.chunk_type == ImageChunk.TYPE_FILL: + all_data = chunk.fill_data*(chunk_pos_to_go // len(chunk.fill_data) + 2) + offset_mod = chunk_pos_offset % len(chunk.fill_data) + data.extend(all_data[offset_mod:(offset_mod + chunk_pos_to_go)]) + else: + assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE + data.extend(b'\0' * chunk_pos_to_go) + + to_go -= chunk_pos_to_go + self._file_pos += chunk_pos_to_go + chunk_idx += 1 + # Generate partial read in case of EOF. + if chunk_idx >= len(self._chunks): + break + + return bytes(data) + + def tell(self): + """Returns the file cursor position for reading from unsparsified file. + + Returns: + The file cursor position for reading. + """ + return self._file_pos + + def truncate(self, size): + """Truncates the unsparsified file. + + Arguments: + size: Desired size of unsparsified file. + + Raises: + ValueError: If desired size isn't a multiple of the block size. + OSError: If ImageHandler was initialized in read-only mode. + """ + if self._read_only: + raise OSError('ImageHandler is in read-only mode.') + + if not self.is_sparse: + self._image.truncate(size) + self._read_header() + return + + if size % self.block_size != 0: + raise ValueError('Cannot truncate to a size which is not a multiple ' + 'of the block size') + + if size == self.image_size: + # Trivial where there's nothing to do. + return + + if size < self.image_size: + chunk_idx = bisect.bisect_right(self._chunk_output_offsets, size) - 1 + chunk = self._chunks[chunk_idx] + if chunk.output_offset != size: + # Truncation in the middle of a trunk - need to keep the chunk + # and modify it. + chunk_idx_for_update = chunk_idx + 1 + num_to_keep = size - chunk.output_offset + assert num_to_keep % self.block_size == 0 + if chunk.chunk_type == ImageChunk.TYPE_RAW: + truncate_at = (chunk.chunk_offset + + struct.calcsize(ImageChunk.FORMAT) + num_to_keep) + data_sz = num_to_keep + elif chunk.chunk_type == ImageChunk.TYPE_FILL: + truncate_at = (chunk.chunk_offset + + struct.calcsize(ImageChunk.FORMAT) + 4) + data_sz = 4 + else: + assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE + truncate_at = chunk.chunk_offset + struct.calcsize(ImageChunk.FORMAT) + data_sz = 0 + chunk_sz = num_to_keep // self.block_size + total_sz = data_sz + struct.calcsize(ImageChunk.FORMAT) + self._image.seek(chunk.chunk_offset) + self._image.write(struct.pack(ImageChunk.FORMAT, + chunk.chunk_type, + 0, # Reserved + chunk_sz, + total_sz)) + chunk.output_size = num_to_keep + else: + # Truncation at trunk boundary. + truncate_at = chunk.chunk_offset + chunk_idx_for_update = chunk_idx + + self._num_total_chunks = chunk_idx_for_update + self._num_total_blocks = 0 + for i in range(0, chunk_idx_for_update): + self._num_total_blocks += self._chunks[i].output_size // self.block_size + self._update_chunks_and_blocks() + self._image.truncate(truncate_at) + + # We've modified the file so re-read all data. + self._read_header() + else: + # Truncating to grow - just add a DONT_CARE section. + self.append_dont_care(size - self.image_size) + + +class AvbDescriptor(object): + """Class for AVB descriptor. + + See the |AvbDescriptor| C struct for more information. + + Attributes: + tag: The tag identifying what kind of descriptor this is. + data: The data in the descriptor. + """ + + SIZE = 16 + FORMAT_STRING = ('!QQ') # tag, num_bytes_following (descriptor header) + + def __init__(self, data): + """Initializes a new property descriptor. + + Arguments: + data: If not None, must be a bytearray(). + + Raises: + LookupError: If the given descriptor is malformed. + """ + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (self.tag, num_bytes_following) = ( + struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])) + self.data = data[self.SIZE:self.SIZE + num_bytes_following] + else: + self.tag = None + self.data = None + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Unknown descriptor:\n') + o.write(' Tag: {}\n'.format(self.tag)) + if len(self.data) < 256: + o.write(' Data: {} ({} bytes)\n'.format( + repr(str(self.data)), len(self.data))) + else: + o.write(' Data: {} bytes\n'.format(len(self.data))) + + def encode(self): + """Serializes the descriptor. + + Returns: + A bytearray() with the descriptor data. + """ + num_bytes_following = len(self.data) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.tag, nbf_with_padding) + padding = struct.pack(str(padding_size) + 'x') + ret = desc + self.data + padding + return bytearray(ret) + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + # Deletes unused parameters to prevent pylint warning unused-argument. + del image_dir, image_ext, expected_chain_partitions_map + del image_containing_descriptor, accept_zeroed_hashtree + + # Nothing to do. + return True + + +class AvbPropertyDescriptor(AvbDescriptor): + """A class for property descriptors. + + See the |AvbPropertyDescriptor| C struct for more information. + + Attributes: + key: The key as string. + value: The value as bytes. + """ + + TAG = 0 + SIZE = 32 + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'Q' # key size (bytes) + 'Q') # value size (bytes) + + def __init__(self, data=None): + """Initializes a new property descriptor. + + Arguments: + data: If not None, must be as bytes of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + super().__init__(None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, key_size, + value_size) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]) + expected_size = round_to_multiple( + self.SIZE - 16 + key_size + 1 + value_size + 1, 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a property ' + 'descriptor.') + try: + self.key = data[self.SIZE:(self.SIZE + key_size)].decode('utf-8') + except UnicodeDecodeError as e: + raise LookupError('Key cannot be decoded as UTF-8: {}.' + .format(e)) from e + self.value = data[(self.SIZE + key_size + 1):(self.SIZE + key_size + 1 + + value_size)] + else: + self.key = '' + self.value = b'' + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + # Go forward with python 3, bytes are represented with the 'b' prefix, + # e.g. b'foobar'. Thus, we trim off the 'b' to keep the print output + # the same between python 2 and python 3. + printable_value = repr(self.value) + if printable_value.startswith('b\''): + printable_value = printable_value[1:] + + if len(self.value) < 256: + o.write(' Prop: {} -> {}\n'.format(self.key, printable_value)) + else: + o.write(' Prop: {} -> ({} bytes)\n'.format(self.key, len(self.value))) + + def encode(self): + """Serializes the descriptor. + + Returns: + The descriptor data as bytes. + """ + key_encoded = self.key.encode('utf-8') + num_bytes_following = ( + self.SIZE + len(key_encoded) + len(self.value) + 2 - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + len(key_encoded), len(self.value)) + ret = (desc + key_encoded + b'\0' + self.value + b'\0' + + padding_size * b'\0') + return ret + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + # Nothing to do. + return True + + +class AvbHashtreeDescriptor(AvbDescriptor): + """A class for hashtree descriptors. + + See the |AvbHashtreeDescriptor| C struct for more information. + + Attributes: + dm_verity_version: dm-verity version used. + image_size: Size of the image, after rounding up to |block_size|. + tree_offset: Offset of the hash tree in the file. + tree_size: Size of the tree. + data_block_size: Data block size. + hash_block_size: Hash block size. + fec_num_roots: Number of roots used for FEC (0 if FEC is not used). + fec_offset: Offset of FEC data (0 if FEC is not used). + fec_size: Size of FEC data (0 if FEC is not used). + hash_algorithm: Hash algorithm used as string. + partition_name: Partition name as string. + salt: Salt used as bytes. + root_digest: Root digest as bytes. + flags: Descriptor flags (see avb_hashtree_descriptor.h). + """ + + TAG = 1 + RESERVED = 60 + SIZE = 120 + RESERVED + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'L' # dm-verity version used + 'Q' # image size (bytes) + 'Q' # tree offset (bytes) + 'Q' # tree size (bytes) + 'L' # data block size (bytes) + 'L' # hash block size (bytes) + 'L' # FEC number of roots + 'Q' # FEC offset (bytes) + 'Q' # FEC size (bytes) + '32s' # hash algorithm used + 'L' # partition name (bytes) + 'L' # salt length (bytes) + 'L' # root digest length (bytes) + 'L' + # flags + str(RESERVED) + 's') # reserved + + FLAGS_DO_NOT_USE_AB = (1 << 0) + FLAGS_CHECK_AT_MOST_ONCE = (1 << 1) + + def __init__(self, data=None): + """Initializes a new hashtree descriptor. + + Arguments: + data: If not None, must be bytes of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + super().__init__(None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, self.dm_verity_version, self.image_size, + self.tree_offset, self.tree_size, self.data_block_size, + self.hash_block_size, self.fec_num_roots, self.fec_offset, self.fec_size, + self.hash_algorithm, partition_name_len, salt_len, + root_digest_len, self.flags, _) = struct.unpack(self.FORMAT_STRING, + data[0:self.SIZE]) + expected_size = round_to_multiple( + self.SIZE - 16 + partition_name_len + salt_len + root_digest_len, 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a hashtree ' + 'descriptor.') + # Nuke NUL-bytes at the end. + self.hash_algorithm = self.hash_algorithm.rstrip(b'\0').decode('ascii') + o = 0 + try: + self.partition_name = data[ + (self.SIZE + o):(self.SIZE + o + partition_name_len) + ].decode('utf-8') + except UnicodeDecodeError as e: + raise LookupError('Partition name cannot be decoded as UTF-8: {}.' + .format(e)) from e + o += partition_name_len + self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)] + o += salt_len + self.root_digest = data[(self.SIZE + o):(self.SIZE + o + root_digest_len)] + + if root_digest_len != self._hashtree_digest_size(): + if root_digest_len != 0: + raise LookupError('root_digest_len doesn\'t match hash algorithm') + + else: + self.dm_verity_version = 0 + self.image_size = 0 + self.tree_offset = 0 + self.tree_size = 0 + self.data_block_size = 0 + self.hash_block_size = 0 + self.fec_num_roots = 0 + self.fec_offset = 0 + self.fec_size = 0 + self.hash_algorithm = '' + self.partition_name = '' + self.salt = b'' + self.root_digest = b'' + self.flags = 0 + + def _hashtree_digest_size(self): + return len(create_avb_hashtree_hasher(self.hash_algorithm, b'').digest()) + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Hashtree descriptor:\n') + o.write(' Version of dm-verity: {}\n'.format(self.dm_verity_version)) + o.write(' Image Size: {} bytes\n'.format(self.image_size)) + o.write(' Tree Offset: {}\n'.format(self.tree_offset)) + o.write(' Tree Size: {} bytes\n'.format(self.tree_size)) + o.write(' Data Block Size: {} bytes\n'.format( + self.data_block_size)) + o.write(' Hash Block Size: {} bytes\n'.format( + self.hash_block_size)) + o.write(' FEC num roots: {}\n'.format(self.fec_num_roots)) + o.write(' FEC offset: {}\n'.format(self.fec_offset)) + o.write(' FEC size: {} bytes\n'.format(self.fec_size)) + o.write(' Hash Algorithm: {}\n'.format(self.hash_algorithm)) + o.write(' Partition Name: {}\n'.format(self.partition_name)) + o.write(' Salt: {}\n'.format(self.salt.hex())) + o.write(' Root Digest: {}\n'.format(self.root_digest.hex())) + o.write(' Flags: {}\n'.format(self.flags)) + + def encode(self): + """Serializes the descriptor. + + Returns: + The descriptor data as bytes. + """ + hash_algorithm_encoded = self.hash_algorithm.encode('ascii') + partition_name_encoded = self.partition_name.encode('utf-8') + num_bytes_following = (self.SIZE + len(partition_name_encoded) + + len(self.salt) + len(self.root_digest) - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + self.dm_verity_version, self.image_size, + self.tree_offset, self.tree_size, self.data_block_size, + self.hash_block_size, self.fec_num_roots, + self.fec_offset, self.fec_size, hash_algorithm_encoded, + len(partition_name_encoded), len(self.salt), + len(self.root_digest), self.flags, self.RESERVED * b'\0') + ret = (desc + partition_name_encoded + self.salt + self.root_digest + + padding_size * b'\0') + return ret + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + if not self.partition_name: + image_filename = image_containing_descriptor.filename + image = image_containing_descriptor + else: + image_filename = os.path.join(image_dir, self.partition_name + image_ext) + image = ImageHandler(image_filename, read_only=True) + # Generate the hashtree and checks that it matches what's in the file. + digest_size = self._hashtree_digest_size() + digest_padding = round_to_pow2(digest_size) - digest_size + (hash_level_offsets, tree_size) = calc_hash_level_offsets( + self.image_size, self.data_block_size, digest_size + digest_padding) + root_digest, hash_tree = generate_hash_tree(image, self.image_size, + self.data_block_size, + self.hash_algorithm, self.salt, + digest_padding, + hash_level_offsets, + tree_size) + # The root digest must match unless it is not embedded in the descriptor. + if self.root_digest and root_digest != self.root_digest: + sys.stderr.write('hashtree of {} does not match descriptor\n'. + format(image_filename)) + return False + # ... also check that the on-disk hashtree matches + image.seek(self.tree_offset) + hash_tree_ondisk = image.read(self.tree_size) + is_zeroed = (self.tree_size == 0) or (hash_tree_ondisk[0:8] == b'ZeRoHaSH') + if is_zeroed and accept_zeroed_hashtree: + print('{}: skipping verification since hashtree is zeroed and ' + '--accept_zeroed_hashtree was given' + .format(self.partition_name)) + else: + if hash_tree != hash_tree_ondisk: + sys.stderr.write('hashtree of {} contains invalid data\n'. + format(image_filename)) + return False + print('{}: Successfully verified {} hashtree of {} for image of {} bytes' + .format(self.partition_name, self.hash_algorithm, image.filename, + self.image_size)) + # TODO(zeuthen): we could also verify that the FEC stored in the image is + # correct but this a) currently requires the 'fec' binary; and b) takes a + # long time; and c) is not strictly needed for verification purposes as + # we've already verified the root hash. + return True + + +class AvbHashDescriptor(AvbDescriptor): + """A class for hash descriptors. + + See the |AvbHashDescriptor| C struct for more information. + + Attributes: + image_size: Image size, in bytes. + hash_algorithm: Hash algorithm used as string. + partition_name: Partition name as string. + salt: Salt used as bytes. + digest: The hash value of salt and data combined as bytes. + flags: The descriptor flags (see avb_hash_descriptor.h). + """ + + TAG = 2 + RESERVED = 60 + SIZE = 72 + RESERVED + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'Q' # image size (bytes) + '32s' # hash algorithm used + 'L' # partition name (bytes) + 'L' # salt length (bytes) + 'L' # digest length (bytes) + 'L' + # flags + str(RESERVED) + 's') # reserved + + def __init__(self, data=None): + """Initializes a new hash descriptor. + + Arguments: + data: If not None, must be bytes of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + super().__init__(None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, self.image_size, self.hash_algorithm, + partition_name_len, salt_len, + digest_len, self.flags, _) = struct.unpack(self.FORMAT_STRING, + data[0:self.SIZE]) + expected_size = round_to_multiple( + self.SIZE - 16 + partition_name_len + salt_len + digest_len, 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a hash descriptor.') + # Nuke NUL-bytes at the end. + self.hash_algorithm = self.hash_algorithm.rstrip(b'\0').decode('ascii') + o = 0 + try: + self.partition_name = data[ + (self.SIZE + o):(self.SIZE + o + partition_name_len) + ].decode('utf-8') + except UnicodeDecodeError as e: + raise LookupError('Partition name cannot be decoded as UTF-8: {}.' + .format(e)) from e + o += partition_name_len + self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)] + o += salt_len + self.digest = data[(self.SIZE + o):(self.SIZE + o + digest_len)] + if digest_len != len(hashlib.new(self.hash_algorithm).digest()): + if digest_len != 0: + raise LookupError('digest_len doesn\'t match hash algorithm') + + else: + self.image_size = 0 + self.hash_algorithm = '' + self.partition_name = '' + self.salt = b'' + self.digest = b'' + self.flags = 0 + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Hash descriptor:\n') + o.write(' Image Size: {} bytes\n'.format(self.image_size)) + o.write(' Hash Algorithm: {}\n'.format(self.hash_algorithm)) + o.write(' Partition Name: {}\n'.format(self.partition_name)) + o.write(' Salt: {}\n'.format(self.salt.hex())) + o.write(' Digest: {}\n'.format(self.digest.hex())) + o.write(' Flags: {}\n'.format(self.flags)) + + def encode(self): + """Serializes the descriptor. + + Returns: + The descriptor data as bytes. + """ + hash_algorithm_encoded = self.hash_algorithm.encode('ascii') + partition_name_encoded = self.partition_name.encode('utf-8') + num_bytes_following = (self.SIZE + len(partition_name_encoded) + + len(self.salt) + len(self.digest) - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + self.image_size, hash_algorithm_encoded, + len(partition_name_encoded), len(self.salt), + len(self.digest), self.flags, self.RESERVED * b'\0') + ret = (desc + partition_name_encoded + self.salt + self.digest + + padding_size * b'\0') + return ret + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + if not self.partition_name: + image_filename = image_containing_descriptor.filename + image = image_containing_descriptor + else: + image_filename = os.path.join(image_dir, self.partition_name + image_ext) + image = ImageHandler(image_filename, read_only=True) + data = image.read(self.image_size) + ha = hashlib.new(self.hash_algorithm) + ha.update(self.salt) + ha.update(data) + digest = ha.digest() + # The digest must match unless there is no digest in the descriptor. + if self.digest and digest != self.digest: + sys.stderr.write('{} digest of {} does not match digest in descriptor\n'. + format(self.hash_algorithm, image_filename)) + return False + print('{}: Successfully verified {} hash of {} for image of {} bytes' + .format(self.partition_name, self.hash_algorithm, image.filename, + self.image_size)) + return True + + +class AvbKernelCmdlineDescriptor(AvbDescriptor): + """A class for kernel command-line descriptors. + + See the |AvbKernelCmdlineDescriptor| C struct for more information. + + Attributes: + flags: Flags. + kernel_cmdline: The kernel command-line as string. + """ + + TAG = 3 + SIZE = 24 + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'L' # flags + 'L') # cmdline length (bytes) + + FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED = (1 << 0) + FLAGS_USE_ONLY_IF_HASHTREE_DISABLED = (1 << 1) + + def __init__(self, data=None): + """Initializes a new kernel cmdline descriptor. + + Arguments: + data: If not None, must be bytes of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + super().__init__(None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, self.flags, kernel_cmdline_length) = ( + struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])) + expected_size = round_to_multiple(self.SIZE - 16 + kernel_cmdline_length, + 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a kernel cmdline ' + 'descriptor.') + # Nuke NUL-bytes at the end. + try: + self.kernel_cmdline = data[ + self.SIZE:(self.SIZE + kernel_cmdline_length)].decode('utf-8') + except UnicodeDecodeError as e: + raise LookupError('Kernel command-line cannot be decoded as UTF-8: {}.' + .format(e)) from e + else: + self.flags = 0 + self.kernel_cmdline = '' + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Kernel Cmdline descriptor:\n') + o.write(' Flags: {}\n'.format(self.flags)) + o.write(' Kernel Cmdline: \'{}\'\n'.format(self.kernel_cmdline)) + + def encode(self): + """Serializes the descriptor. + + Returns: + The descriptor data as bytes. + """ + kernel_cmd_encoded = self.kernel_cmdline.encode('utf-8') + num_bytes_following = (self.SIZE + len(kernel_cmd_encoded) - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + self.flags, len(kernel_cmd_encoded)) + ret = desc + kernel_cmd_encoded + padding_size * b'\0' + return ret + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + # Nothing to verify. + return True + + +class AvbChainPartitionDescriptor(AvbDescriptor): + """A class for chained partition descriptors. + + See the |AvbChainPartitionDescriptor| C struct for more information. + + Attributes: + rollback_index_location: The rollback index location to use. + partition_name: Partition name as string. + public_key: The public key as bytes. + """ + + TAG = 4 + RESERVED = 64 + SIZE = 28 + RESERVED + FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) + 'L' # rollback_index_location + 'L' # partition_name_size (bytes) + 'L' + # public_key_size (bytes) + str(RESERVED) + 's') # reserved + + def __init__(self, data=None): + """Initializes a new chain partition descriptor. + + Arguments: + data: If not None, must be a bytearray of size |SIZE|. + + Raises: + LookupError: If the given descriptor is malformed. + """ + AvbDescriptor.__init__(self, None) + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (tag, num_bytes_following, self.rollback_index_location, + partition_name_len, + public_key_len, _) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]) + expected_size = round_to_multiple( + self.SIZE - 16 + partition_name_len + public_key_len, 8) + if tag != self.TAG or num_bytes_following != expected_size: + raise LookupError('Given data does not look like a chain partition ' + 'descriptor.') + o = 0 + try: + self.partition_name = data[ + (self.SIZE + o):(self.SIZE + o + partition_name_len) + ].decode('utf-8') + except UnicodeDecodeError as e: + raise LookupError('Partition name cannot be decoded as UTF-8: {}.' + .format(e)) from e + o += partition_name_len + self.public_key = data[(self.SIZE + o):(self.SIZE + o + public_key_len)] + + else: + self.rollback_index_location = 0 + self.partition_name = '' + self.public_key = b'' + + def print_desc(self, o): + """Print the descriptor. + + Arguments: + o: The object to write the output to. + """ + o.write(' Chain Partition descriptor:\n') + o.write(' Partition Name: {}\n'.format(self.partition_name)) + o.write(' Rollback Index Location: {}\n'.format( + self.rollback_index_location)) + # Just show the SHA1 of the key, for size reasons. + pubkey_digest = hashlib.sha1(self.public_key).hexdigest() + o.write(' Public key (sha1): {}\n'.format(pubkey_digest)) + + def encode(self): + """Serializes the descriptor. + + Returns: + The descriptor data as bytes. + """ + partition_name_encoded = self.partition_name.encode('utf-8') + num_bytes_following = ( + self.SIZE + len(partition_name_encoded) + len(self.public_key) - 16) + nbf_with_padding = round_to_multiple(num_bytes_following, 8) + padding_size = nbf_with_padding - num_bytes_following + desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, + self.rollback_index_location, + len(partition_name_encoded), len(self.public_key), + self.RESERVED * b'\0') + ret = desc + partition_name_encoded + self.public_key + padding_size * b'\0' + return ret + + def verify(self, image_dir, image_ext, expected_chain_partitions_map, + image_containing_descriptor, accept_zeroed_hashtree): + """Verifies contents of the descriptor - used in verify_image sub-command. + + Arguments: + image_dir: The directory of the file being verified. + image_ext: The extension of the file being verified (e.g. '.img'). + expected_chain_partitions_map: A map from partition name to the + tuple (rollback_index_location, key_blob). + image_containing_descriptor: The image the descriptor is in. + accept_zeroed_hashtree: If True, don't fail if hashtree or FEC data is + zeroed out. + + Returns: + True if the descriptor verifies, False otherwise. + """ + value = expected_chain_partitions_map.get(self.partition_name) + if not value: + sys.stderr.write('No expected chain partition for partition {}. Use ' + '--expected_chain_partition to specify expected ' + 'contents or --follow_chain_partitions.\n'. + format(self.partition_name)) + return False + rollback_index_location, pk_blob = value + + if self.rollback_index_location != rollback_index_location: + sys.stderr.write('Expected rollback_index_location {} does not ' + 'match {} in descriptor for partition {}\n'. + format(rollback_index_location, + self.rollback_index_location, + self.partition_name)) + return False + + if self.public_key != pk_blob: + sys.stderr.write('Expected public key blob does not match public ' + 'key blob in descriptor for partition {}\n'. + format(self.partition_name)) + return False + + print('{}: Successfully verified chain partition descriptor matches ' + 'expected data'.format(self.partition_name)) + + return True + +DESCRIPTOR_CLASSES = [ + AvbPropertyDescriptor, AvbHashtreeDescriptor, AvbHashDescriptor, + AvbKernelCmdlineDescriptor, AvbChainPartitionDescriptor +] + + +def parse_descriptors(data): + """Parses a blob of data into descriptors. + + Arguments: + data: Encoded descriptors as bytes. + + Returns: + A list of instances of objects derived from AvbDescriptor. For + unknown descriptors, the class AvbDescriptor is used. + """ + o = 0 + ret = [] + while o < len(data): + tag, nb_following = struct.unpack('!2Q', data[o:o + 16]) + if tag < len(DESCRIPTOR_CLASSES): + clazz = DESCRIPTOR_CLASSES[tag] + else: + clazz = AvbDescriptor + ret.append(clazz(data[o:o + 16 + nb_following])) + o += 16 + nb_following + return ret + + +class AvbFooter(object): + """A class for parsing and writing footers. + + Footers are stored at the end of partitions and point to where the + AvbVBMeta blob is located. They also contain the original size of + the image before AVB information was added. + + Attributes: + magic: Magic for identifying the footer, see |MAGIC|. + version_major: The major version of avbtool that wrote the footer. + version_minor: The minor version of avbtool that wrote the footer. + original_image_size: Original image size. + vbmeta_offset: Offset of where the AvbVBMeta blob is stored. + vbmeta_size: Size of the AvbVBMeta blob. + """ + + MAGIC = b'AVBf' + SIZE = 64 + RESERVED = 28 + FOOTER_VERSION_MAJOR = AVB_FOOTER_VERSION_MAJOR + FOOTER_VERSION_MINOR = AVB_FOOTER_VERSION_MINOR + FORMAT_STRING = ('!4s2L' # magic, 2 x version. + 'Q' # Original image size. + 'Q' # Offset of VBMeta blob. + 'Q' + # Size of VBMeta blob. + str(RESERVED) + 'x') # padding for reserved bytes + + def __init__(self, data=None): + """Initializes a new footer object. + + Arguments: + data: If not None, must be bytes of size 4096. + + Raises: + LookupError: If the given footer is malformed. + struct.error: If the given data has no footer. + """ + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (self.magic, self.version_major, self.version_minor, + self.original_image_size, self.vbmeta_offset, + self.vbmeta_size) = struct.unpack(self.FORMAT_STRING, data) + if self.magic != self.MAGIC: + raise LookupError('Given data does not look like a AVB footer.') + else: + self.magic = self.MAGIC + self.version_major = self.FOOTER_VERSION_MAJOR + self.version_minor = self.FOOTER_VERSION_MINOR + self.original_image_size = 0 + self.vbmeta_offset = 0 + self.vbmeta_size = 0 + + def encode(self): + """Serializes the footer. + + Returns: + The footer as bytes. + """ + return struct.pack(self.FORMAT_STRING, self.magic, self.version_major, + self.version_minor, self.original_image_size, + self.vbmeta_offset, self.vbmeta_size) + + +class AvbVBMetaHeader(object): + """A class for parsing and writing AVB vbmeta images. + + The attributes correspond to the |AvbVBMetaImageHeader| struct defined in + avb_vbmeta_image.h. + + Attributes: + magic: Four bytes equal to "AVB0" (AVB_MAGIC). + required_libavb_version_major: The major version of libavb required for this + header. + required_libavb_version_minor: The minor version of libavb required for this + header. + authentication_data_block_size: The size of the signature block. + auxiliary_data_block_size: The size of the auxiliary data block. + algorithm_type: The verification algorithm used, see |AvbAlgorithmType| + enum. + hash_offset: Offset into the "Authentication data" block of hash data. + hash_size: Length of the hash data. + signature_offset: Offset into the "Authentication data" block of signature + data. + signature_size: Length of the signature data. + public_key_offset: Offset into the "Auxiliary data" block of public key + data. + public_key_size: Length of the public key data. + public_key_metadata_offset: Offset into the "Auxiliary data" block of public + key metadata. + public_key_metadata_size: Length of the public key metadata. Must be set to + zero if there is no public key metadata. + descriptors_offset: Offset into the "Auxiliary data" block of descriptor + data. + descriptors_size: Length of descriptor data. + rollback_index: The rollback index which can be used to prevent rollback to + older versions. + flags: Flags from the AvbVBMetaImageFlags enumeration. This must be set to + zero if the vbmeta image is not a top-level image. + rollback_index_location: The location of the rollback index defined in this + header. Only valid for the main vbmeta. For chained partitions, the + rollback index location must be specified in the + AvbChainPartitionDescriptor and this value must be set to 0. + release_string: The release string from avbtool, e.g. "avbtool 1.0.0" or + "avbtool 1.0.0 xyz_board Git-234abde89". Is guaranteed to be NUL + terminated. Applications must not make assumptions about how this + string is formatted. + """ + MAGIC = b'AVB0' + SIZE = 256 + + # Keep in sync with |reserved| field of |AvbVBMetaImageHeader|. + RESERVED = 80 + + # Keep in sync with |AvbVBMetaImageHeader|. + FORMAT_STRING = ('!4s2L' # magic, 2 x version + '2Q' # 2 x block size + 'L' # algorithm type + '2Q' # offset, size (hash) + '2Q' # offset, size (signature) + '2Q' # offset, size (public key) + '2Q' # offset, size (public key metadata) + '2Q' # offset, size (descriptors) + 'Q' # rollback_index + 'L' # flags + 'L' # rollback_index_location + '47sx' + # NUL-terminated release string + str(RESERVED) + 'x') # padding for reserved bytes + + def __init__(self, data=None): + """Initializes a new header object. + + Arguments: + data: If not None, must be a bytearray of size 8192. + + Raises: + Exception: If the given data is malformed. + """ + assert struct.calcsize(self.FORMAT_STRING) == self.SIZE + + if data: + (self.magic, self.required_libavb_version_major, + self.required_libavb_version_minor, + self.authentication_data_block_size, self.auxiliary_data_block_size, + self.algorithm_type, self.hash_offset, self.hash_size, + self.signature_offset, self.signature_size, self.public_key_offset, + self.public_key_size, self.public_key_metadata_offset, + self.public_key_metadata_size, self.descriptors_offset, + self.descriptors_size, + self.rollback_index, + self.flags, + self.rollback_index_location, + release_string) = struct.unpack(self.FORMAT_STRING, data) + # Nuke NUL-bytes at the end of the string. + if self.magic != self.MAGIC: + raise AvbError('Given image does not look like a vbmeta image.') + self.release_string = release_string.rstrip(b'\0').decode('utf-8') + else: + self.magic = self.MAGIC + # Start by just requiring version 1.0. Code that adds features + # in a future version can use bump_required_libavb_version_minor() to + # bump the minor. + self.required_libavb_version_major = AVB_VERSION_MAJOR + self.required_libavb_version_minor = 0 + self.authentication_data_block_size = 0 + self.auxiliary_data_block_size = 0 + self.algorithm_type = 0 + self.hash_offset = 0 + self.hash_size = 0 + self.signature_offset = 0 + self.signature_size = 0 + self.public_key_offset = 0 + self.public_key_size = 0 + self.public_key_metadata_offset = 0 + self.public_key_metadata_size = 0 + self.descriptors_offset = 0 + self.descriptors_size = 0 + self.rollback_index = 0 + self.flags = 0 + self.rollback_index_location = 0 + self.release_string = get_release_string() + + def bump_required_libavb_version_minor(self, minor): + """Function to bump required_libavb_version_minor. + + Call this when writing data that requires a specific libavb + version to parse it. + + Arguments: + minor: The minor version of libavb that has support for the feature. + """ + self.required_libavb_version_minor = ( + max(self.required_libavb_version_minor, minor)) + + def encode(self): + """Serializes the header. + + Returns: + The header as bytes. + """ + release_string_encoded = self.release_string.encode('utf-8') + return struct.pack(self.FORMAT_STRING, self.magic, + self.required_libavb_version_major, + self.required_libavb_version_minor, + self.authentication_data_block_size, + self.auxiliary_data_block_size, self.algorithm_type, + self.hash_offset, self.hash_size, self.signature_offset, + self.signature_size, self.public_key_offset, + self.public_key_size, self.public_key_metadata_offset, + self.public_key_metadata_size, self.descriptors_offset, + self.descriptors_size, self.rollback_index, self.flags, + self.rollback_index_location, release_string_encoded) + + +class Avb(object): + """Business logic for avbtool command-line tool.""" + + # Keep in sync with avb_ab_flow.h. + AB_FORMAT_NO_CRC = '!4sBB2xBBBxBBBx12x' + AB_MAGIC = b'\0AB0' + AB_MAJOR_VERSION = 1 + AB_MINOR_VERSION = 0 + AB_MISC_METADATA_OFFSET = 2048 + + # Constants for maximum metadata size. These are used to give + # meaningful errors if the value passed in via --partition_size is + # too small and when --calc_max_image_size is used. We use + # conservative figures. + MAX_VBMETA_SIZE = 64 * 1024 + MAX_FOOTER_SIZE = 4096 + + def generate_test_image(self, output, image_size, start_byte): + """Generates a test image for testing avbtool with known content. + + The content has following pattern: 0x00 0x01 0x02 .. 0xff 0x00 0x01 ..). + + Arguments: + output: Write test image to this file. + image_size: The size of the requested file in bytes. + start_byte: The integer value of the start byte to use for pattern + generation. + """ + pattern = bytearray([x & 0xFF for x in range(start_byte, start_byte + 256)]) + buf = bytearray() + c = int(math.ceil(image_size / 256.0)) + for _ in range(0, c): + buf.extend(pattern) + output.write(buf[0:image_size]) + + def extract_vbmeta_image(self, output, image_filename, padding_size): + """Implements the 'extract_vbmeta_image' command. + + Arguments: + output: Write vbmeta struct to this file. + image_filename: File to extract vbmeta data from (with a footer). + padding_size: If not 0, pads output so size is a multiple of the number. + + Raises: + AvbError: If there's no footer in the image. + """ + image = ImageHandler(image_filename, read_only=True) + (footer, _, _, _) = self._parse_image(image) + if not footer: + raise AvbError('Given image does not have a footer.') + + image.seek(footer.vbmeta_offset) + vbmeta_blob = image.read(footer.vbmeta_size) + output.write(vbmeta_blob) + + if padding_size > 0: + padded_size = round_to_multiple(len(vbmeta_blob), padding_size) + padding_needed = padded_size - len(vbmeta_blob) + output.write(b'\0' * padding_needed) + + def erase_footer(self, image_filename, keep_hashtree): + """Implements the 'erase_footer' command. + + Arguments: + image_filename: File to erase a footer from. + keep_hashtree: If True, keep the hashtree and FEC around. + + Raises: + AvbError: If there's no footer in the image. + """ + image = ImageHandler(image_filename) + (footer, _, descriptors, _) = self._parse_image(image) + if not footer: + raise AvbError('Given image does not have a footer.') + + new_image_size = None + if not keep_hashtree: + new_image_size = footer.original_image_size + else: + # If requested to keep the hashtree, search for a hashtree + # descriptor to figure out the location and size of the hashtree + # and FEC. + for desc in descriptors: + if isinstance(desc, AvbHashtreeDescriptor): + # The hashtree is always just following the main data so the + # new size is easily derived. + new_image_size = desc.tree_offset + desc.tree_size + # If the image has FEC codes, also keep those. + if desc.fec_offset > 0: + fec_end = desc.fec_offset + desc.fec_size + new_image_size = max(new_image_size, fec_end) + break + if not new_image_size: + raise AvbError('Requested to keep hashtree but no hashtree ' + 'descriptor was found.') + + # And cut... + image.truncate(new_image_size) + + def zero_hashtree(self, image_filename): + """Implements the 'zero_hashtree' command. + + Arguments: + image_filename: File to zero hashtree and FEC data from. + + Raises: + AvbError: If there's no footer in the image. + """ + image = ImageHandler(image_filename) + (footer, _, descriptors, _) = self._parse_image(image) + if not footer: + raise AvbError('Given image does not have a footer.') + + # Search for a hashtree descriptor to figure out the location and + # size of the hashtree and FEC. + ht_desc = None + for desc in descriptors: + if isinstance(desc, AvbHashtreeDescriptor): + ht_desc = desc + break + + if not ht_desc: + raise AvbError('No hashtree descriptor was found.') + + zero_ht_start_offset = ht_desc.tree_offset + zero_ht_num_bytes = ht_desc.tree_size + zero_fec_start_offset = None + zero_fec_num_bytes = 0 + if ht_desc.fec_offset > 0: + if ht_desc.fec_offset != ht_desc.tree_offset + ht_desc.tree_size: + raise AvbError('Hash-tree and FEC data must be adjacent.') + zero_fec_start_offset = ht_desc.fec_offset + zero_fec_num_bytes = ht_desc.fec_size + zero_end_offset = (zero_ht_start_offset + zero_ht_num_bytes + + zero_fec_num_bytes) + image.seek(zero_end_offset) + data = image.read(image.image_size - zero_end_offset) + + # Write zeroes all over hashtree and FEC, except for the first eight bytes + # where a magic marker - ZeroHaSH - is placed. Place these markers in the + # beginning of both hashtree and FEC. (That way, in the future we can add + # options to 'avbtool zero_hashtree' so as to zero out only either/or.) + # + # Applications can use these markers to detect that the hashtree and/or + # FEC needs to be recomputed. + image.truncate(zero_ht_start_offset) + data_zeroed_firstblock = b'ZeRoHaSH' + b'\0' * (image.block_size - 8) + image.append_raw(data_zeroed_firstblock) + image.append_fill(b'\0\0\0\0', zero_ht_num_bytes - image.block_size) + if zero_fec_start_offset: + image.append_raw(data_zeroed_firstblock) + image.append_fill(b'\0\0\0\0', zero_fec_num_bytes - image.block_size) + image.append_raw(data) + + def resize_image(self, image_filename, partition_size): + """Implements the 'resize_image' command. + + Arguments: + image_filename: File with footer to resize. + partition_size: The new size of the image. + + Raises: + AvbError: If there's no footer in the image. + """ + + image = ImageHandler(image_filename) + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + (footer, _, _, _) = self._parse_image(image) + if not footer: + raise AvbError('Given image does not have a footer.') + + # The vbmeta blob is always at the end of the data so resizing an + # image amounts to just moving the footer around. + vbmeta_end_offset = footer.vbmeta_offset + footer.vbmeta_size + if vbmeta_end_offset % image.block_size != 0: + vbmeta_end_offset += image.block_size - (vbmeta_end_offset + % image.block_size) + + if partition_size < vbmeta_end_offset + 1 * image.block_size: + raise AvbError('Requested size of {} is too small for an image ' + 'of size {}.' + .format(partition_size, + vbmeta_end_offset + 1 * image.block_size)) + + # Cut at the end of the vbmeta blob and insert a DONT_CARE chunk + # with enough bytes such that the final Footer block is at the end + # of partition_size. + image.truncate(vbmeta_end_offset) + image.append_dont_care(partition_size - vbmeta_end_offset - + 1 * image.block_size) + + # Just reuse the same footer - only difference is that we're + # writing it in a different place. + footer_blob = footer.encode() + footer_blob_with_padding = (b'\0' * (image.block_size - AvbFooter.SIZE) + + footer_blob) + image.append_raw(footer_blob_with_padding) + + def set_ab_metadata(self, misc_image, slot_data): + """Implements the 'set_ab_metadata' command. + + The |slot_data| argument must be of the form 'A_priority:A_tries_remaining: + A_successful_boot:B_priority:B_tries_remaining:B_successful_boot'. + + Arguments: + misc_image: The misc image to write to. + slot_data: Slot data as a string + + Raises: + AvbError: If slot data is malformed. + """ + tokens = slot_data.split(':') + if len(tokens) != 6: + raise AvbError('Malformed slot data "{}".'.format(slot_data)) + a_priority = int(tokens[0]) + a_tries_remaining = int(tokens[1]) + a_success = int(tokens[2]) != 0 + b_priority = int(tokens[3]) + b_tries_remaining = int(tokens[4]) + b_success = int(tokens[5]) != 0 + + ab_data_no_crc = struct.pack(self.AB_FORMAT_NO_CRC, + self.AB_MAGIC, + self.AB_MAJOR_VERSION, self.AB_MINOR_VERSION, + a_priority, a_tries_remaining, a_success, + b_priority, b_tries_remaining, b_success) + # Force CRC to be unsigned, see https://bugs.python.org/issue4903 for why. + crc_value = binascii.crc32(ab_data_no_crc) & 0xffffffff + ab_data = ab_data_no_crc + struct.pack('!I', crc_value) + misc_image.seek(self.AB_MISC_METADATA_OFFSET) + misc_image.write(ab_data) + + def info_image(self, image_filename, output, atx): + """Implements the 'info_image' command. + + Arguments: + image_filename: Image file to get information from (file object). + output: Output file to write human-readable information to (file object). + atx: If True, show information about Android Things eXtension (ATX). + """ + image = ImageHandler(image_filename, read_only=True) + o = output + (footer, header, descriptors, image_size) = self._parse_image(image) + + # To show the SHA1 of the public key. + vbmeta_blob = self._load_vbmeta_blob(image) + key_offset = (header.SIZE + + header.authentication_data_block_size + + header.public_key_offset) + key_blob = vbmeta_blob[key_offset:key_offset + header.public_key_size] + + if footer: + o.write('Footer version: {}.{}\n'.format(footer.version_major, + footer.version_minor)) + o.write('Image size: {} bytes\n'.format(image_size)) + o.write('Original image size: {} bytes\n'.format( + footer.original_image_size)) + o.write('VBMeta offset: {}\n'.format(footer.vbmeta_offset)) + o.write('VBMeta size: {} bytes\n'.format(footer.vbmeta_size)) + o.write('--\n') + + (alg_name, _) = lookup_algorithm_by_type(header.algorithm_type) + + o.write('Minimum libavb version: {}.{}{}\n'.format( + header.required_libavb_version_major, + header.required_libavb_version_minor, + ' (Sparse)' if image.is_sparse else '')) + o.write('Header Block: {} bytes\n'.format(AvbVBMetaHeader.SIZE)) + o.write('Authentication Block: {} bytes\n'.format( + header.authentication_data_block_size)) + o.write('Auxiliary Block: {} bytes\n'.format( + header.auxiliary_data_block_size)) + if key_blob: + hexdig = hashlib.sha1(key_blob).hexdigest() + o.write('Public key (sha1): {}\n'.format(hexdig)) + o.write('Algorithm: {}\n'.format(alg_name)) + o.write('Rollback Index: {}\n'.format(header.rollback_index)) + o.write('Flags: {}\n'.format(header.flags)) + o.write('Rollback Index Location: {}\n'.format( + header.rollback_index_location)) + o.write('Release String: \'{}\'\n'.format(header.release_string)) + + # Print descriptors. + num_printed = 0 + o.write('Descriptors:\n') + for desc in descriptors: + desc.print_desc(o) + num_printed += 1 + if num_printed == 0: + o.write(' (none)\n') + + if atx and header.public_key_metadata_size: + o.write('Android Things eXtension (ATX):\n') + key_metadata_offset = (header.SIZE + + header.authentication_data_block_size + + header.public_key_metadata_offset) + key_metadata_blob = vbmeta_blob[key_metadata_offset: key_metadata_offset + + header.public_key_metadata_size] + version, pik, psk = struct.unpack(' 0: + if ht.flags & AvbHashtreeDescriptor.FLAGS_CHECK_AT_MOST_ONCE: + c += ' 11' # number of optional args + c += ' check_at_most_once' + else: + c += ' 10' # number of optional args + c += ' $(ANDROID_VERITY_MODE)' + c += ' ignore_zero_blocks' + c += ' use_fec_from_device PARTUUID=$(ANDROID_SYSTEM_PARTUUID)' + c += ' fec_roots {}'.format(ht.fec_num_roots) + # Note that fec_blocks is the size that FEC covers, *not* the + # size of the FEC data. Since we use FEC for everything up until + # the FEC data, it's the same as the offset. + c += ' fec_blocks {}'.format(ht.fec_offset // ht.data_block_size) + c += ' fec_start {}'.format(ht.fec_offset // ht.data_block_size) + else: + if ht.flags & AvbHashtreeDescriptor.FLAGS_CHECK_AT_MOST_ONCE: + c += ' 3' # number of optional args + c += ' check_at_most_once' + else: + c += ' 2' # number of optional args + c += ' $(ANDROID_VERITY_MODE)' + c += ' ignore_zero_blocks' + c += '" root=/dev/dm-0' + + # Now that we have the command-line, generate the descriptor. + desc = AvbKernelCmdlineDescriptor() + desc.kernel_cmdline = c + desc.flags = ( + AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED) + + # The descriptor for when hashtree verification is disabled is a lot + # simpler - we just set the root to the partition. + desc_no_ht = AvbKernelCmdlineDescriptor() + desc_no_ht.kernel_cmdline = 'root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)' + desc_no_ht.flags = ( + AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_DISABLED) + + return [desc, desc_no_ht] + + def _get_cmdline_descriptors_for_dm_verity(self, image): + """Generate kernel cmdline descriptors for dm-verity. + + Arguments: + image: An ImageHandler (vbmeta or footer) with a hashtree descriptor. + + Returns: + A list with two AvbKernelCmdlineDescriptor with dm-verity kernel cmdline + instructions. There is one for when hashtree is not disabled and one for + when it is. + + Raises: + AvbError: If |image| doesn't have a hashtree descriptor. + + """ + (_, _, descriptors, _) = self._parse_image(image) + + ht = None + for desc in descriptors: + if isinstance(desc, AvbHashtreeDescriptor): + ht = desc + break + + if not ht: + raise AvbError('No hashtree descriptor in given image') + + return self._get_cmdline_descriptors_for_hashtree_descriptor(ht) + + def make_vbmeta_image(self, output, chain_partitions, algorithm_name, + key_path, public_key_metadata_path, rollback_index, + flags, rollback_index_location, + props, props_from_file, kernel_cmdlines, + setup_rootfs_from_kernel, + include_descriptors_from_image, + signing_helper, + signing_helper_with_files, + release_string, + append_to_release_string, + print_required_libavb_version, + padding_size): + """Implements the 'make_vbmeta_image' command. + + Arguments: + output: File to write the image to. + chain_partitions: List of partitions to chain or None. + algorithm_name: Name of algorithm to use. + key_path: Path to key to use or None. + public_key_metadata_path: Path to public key metadata or None. + rollback_index: The rollback index to use. + flags: Flags value to use in the image. + rollback_index_location: Location of the main vbmeta rollback index. + props: Properties to insert (list of strings of the form 'key:value'). + props_from_file: Properties to insert (list of strings 'key:'). + kernel_cmdlines: Kernel cmdlines to insert (list of strings). + setup_rootfs_from_kernel: None or file to generate from. + include_descriptors_from_image: List of file objects with descriptors. + signing_helper: Program which signs a hash and return signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + release_string: None or avbtool release string to use instead of default. + append_to_release_string: None or string to append. + print_required_libavb_version: True to only print required libavb version. + padding_size: If not 0, pads output so size is a multiple of the number. + + Raises: + AvbError: If a chained partition is malformed. + """ + # If we're asked to calculate minimum required libavb version, we're done. + tmp_header = AvbVBMetaHeader() + if rollback_index_location > 0: + tmp_header.bump_required_libavb_version_minor(2) + if include_descriptors_from_image: + # Use the bump logic in AvbVBMetaHeader to calculate the max required + # version of all included descriptors. + for image in include_descriptors_from_image: + (_, image_header, _, _) = self._parse_image(ImageHandler( + image.name, read_only=True)) + tmp_header.bump_required_libavb_version_minor( + image_header.required_libavb_version_minor) + + if print_required_libavb_version: + print('1.{}'.format(tmp_header.required_libavb_version_minor)) + return + + if not output: + raise AvbError('No output file given') + + descriptors = [] + ht_desc_to_setup = None + vbmeta_blob = self._generate_vbmeta_blob( + algorithm_name, key_path, public_key_metadata_path, descriptors, + chain_partitions, rollback_index, flags, rollback_index_location, + props, props_from_file, + kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, + include_descriptors_from_image, signing_helper, + signing_helper_with_files, release_string, + append_to_release_string, tmp_header.required_libavb_version_minor) + + # Write entire vbmeta blob (header, authentication, auxiliary). + output.seek(0) + output.write(vbmeta_blob) + + if padding_size > 0: + padded_size = round_to_multiple(len(vbmeta_blob), padding_size) + padding_needed = padded_size - len(vbmeta_blob) + output.write(b'\0' * padding_needed) + + def _generate_vbmeta_blob(self, algorithm_name, key_path, + public_key_metadata_path, descriptors, + chain_partitions, + rollback_index, flags, rollback_index_location, + props, props_from_file, + kernel_cmdlines, + setup_rootfs_from_kernel, + ht_desc_to_setup, + include_descriptors_from_image, signing_helper, + signing_helper_with_files, + release_string, append_to_release_string, + required_libavb_version_minor): + """Generates a VBMeta blob. + + This blob contains the header (struct AvbVBMetaHeader), the + authentication data block (which contains the hash and signature + for the header and auxiliary block), and the auxiliary block + (which contains descriptors, the public key used, and other data). + + The |key| parameter can |None| only if the |algorithm_name| is + 'NONE'. + + Arguments: + algorithm_name: The algorithm name as per the ALGORITHMS dict. + key_path: The path to the .pem file used to sign the blob. + public_key_metadata_path: Path to public key metadata or None. + descriptors: A list of descriptors to insert or None. + chain_partitions: List of partitions to chain or None. + rollback_index: The rollback index to use. + flags: Flags to use in the image. + rollback_index_location: Location of the main vbmeta rollback index. + props: Properties to insert (List of strings of the form 'key:value'). + props_from_file: Properties to insert (List of strings 'key:'). + kernel_cmdlines: Kernel cmdlines to insert (list of strings). + setup_rootfs_from_kernel: None or file to generate + dm-verity kernel cmdline from. + ht_desc_to_setup: If not None, an AvbHashtreeDescriptor to + generate dm-verity kernel cmdline descriptors from. + include_descriptors_from_image: List of file objects for which + to insert descriptors from. + signing_helper: Program which signs a hash and return signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + release_string: None or avbtool release string. + append_to_release_string: None or string to append. + required_libavb_version_minor: Use at least this required minor version. + + Returns: + The VBMeta blob as bytes. + + Raises: + Exception: If the |algorithm_name| is not found, if no key has + been given and the given algorithm requires one, or the key is + of the wrong size. + """ + try: + alg = ALGORITHMS[algorithm_name] + except KeyError as e: + raise AvbError('Unknown algorithm with name {}' + .format(algorithm_name)) from e + + if not descriptors: + descriptors = [] + + h = AvbVBMetaHeader() + h.bump_required_libavb_version_minor(required_libavb_version_minor) + + # Insert chained partition descriptors, if any + if chain_partitions: + used_locations = {rollback_index_location: True} + for cp in chain_partitions: + cp_tokens = cp.split(':') + if len(cp_tokens) != 3: + raise AvbError('Malformed chained partition "{}".'.format(cp)) + partition_name = cp_tokens[0] + chained_rollback_index_location = int(cp_tokens[1]) + file_path = cp_tokens[2] + # Check that the same rollback location isn't being used by + # multiple chained partitions. + if used_locations.get(chained_rollback_index_location): + raise AvbError('Rollback Index Location {} is already in use.'.format( + chained_rollback_index_location)) + used_locations[chained_rollback_index_location] = True + desc = AvbChainPartitionDescriptor() + desc.partition_name = partition_name + desc.rollback_index_location = chained_rollback_index_location + if desc.rollback_index_location < 1: + raise AvbError('Rollback index location must be 1 or larger.') + with open(file_path, 'rb') as f: + desc.public_key = f.read() + descriptors.append(desc) + + # Descriptors. + encoded_descriptors = bytearray() + for desc in descriptors: + encoded_descriptors.extend(desc.encode()) + + # Add properties. + if props: + for prop in props: + idx = prop.find(':') + if idx == -1: + raise AvbError('Malformed property "{}".'.format(prop)) + # pylint: disable=redefined-variable-type + desc = AvbPropertyDescriptor() + desc.key = prop[0:idx] + desc.value = prop[(idx + 1):].encode('utf-8') + encoded_descriptors.extend(desc.encode()) + if props_from_file: + for prop in props_from_file: + idx = prop.find(':') + if idx == -1: + raise AvbError('Malformed property "{}".'.format(prop)) + desc = AvbPropertyDescriptor() + desc.key = prop[0:idx] + file_path = prop[(idx + 1):] + with open(file_path, 'rb') as f: + # pylint: disable=attribute-defined-outside-init + desc.value = f.read() + encoded_descriptors.extend(desc.encode()) + + # Add AvbKernelCmdline descriptor for dm-verity from an image, if requested. + if setup_rootfs_from_kernel: + image_handler = ImageHandler( + setup_rootfs_from_kernel.name) + cmdline_desc = self._get_cmdline_descriptors_for_dm_verity(image_handler) + encoded_descriptors.extend(cmdline_desc[0].encode()) + encoded_descriptors.extend(cmdline_desc[1].encode()) + + # Add AvbKernelCmdline descriptor for dm-verity from desc, if requested. + if ht_desc_to_setup: + cmdline_desc = self._get_cmdline_descriptors_for_hashtree_descriptor( + ht_desc_to_setup) + encoded_descriptors.extend(cmdline_desc[0].encode()) + encoded_descriptors.extend(cmdline_desc[1].encode()) + + # Add kernel command-lines. + if kernel_cmdlines: + for i in kernel_cmdlines: + desc = AvbKernelCmdlineDescriptor() + desc.kernel_cmdline = i + encoded_descriptors.extend(desc.encode()) + + # Add descriptors from other images. + if include_descriptors_from_image: + descriptors_dict = dict() + for image in include_descriptors_from_image: + image_handler = ImageHandler(image.name, read_only=True) + (_, image_vbmeta_header, image_descriptors, _) = self._parse_image( + image_handler) + # Bump the required libavb version to support all included descriptors. + h.bump_required_libavb_version_minor( + image_vbmeta_header.required_libavb_version_minor) + for desc in image_descriptors: + # The --include_descriptors_from_image option is used in some setups + # with images A and B where both A and B contain a descriptor + # for a partition with the same name. Since it's not meaningful + # to include both descriptors, only include the last seen descriptor. + # See bug 76386656 for details. + if hasattr(desc, 'partition_name'): + key = type(desc).__name__ + '_' + desc.partition_name + descriptors_dict[key] = desc.encode() + else: + encoded_descriptors.extend(desc.encode()) + for key in sorted(descriptors_dict): + encoded_descriptors.extend(descriptors_dict[key]) + + # Load public key metadata blob, if requested. + pkmd_blob = b'' + if public_key_metadata_path: + with open(public_key_metadata_path, 'rb') as f: + pkmd_blob = f.read() + + key = None + encoded_key = b'' + if alg.public_key_num_bytes > 0: + if not key_path: + raise AvbError('Key is required for algorithm {}'.format( + algorithm_name)) + encoded_key = RSAPublicKey(key_path).encode() + if len(encoded_key) != alg.public_key_num_bytes: + raise AvbError('Key is wrong size for algorithm {}'.format( + algorithm_name)) + + # Override release string, if requested. + if isinstance(release_string, str): + h.release_string = release_string + + # Append to release string, if requested. Also insert a space before. + if isinstance(append_to_release_string, str): + h.release_string += ' ' + append_to_release_string + + # For the Auxiliary data block, descriptors are stored at offset 0, + # followed by the public key, followed by the public key metadata blob. + h.auxiliary_data_block_size = round_to_multiple( + len(encoded_descriptors) + len(encoded_key) + len(pkmd_blob), 64) + h.descriptors_offset = 0 + h.descriptors_size = len(encoded_descriptors) + h.public_key_offset = h.descriptors_size + h.public_key_size = len(encoded_key) + h.public_key_metadata_offset = h.public_key_offset + h.public_key_size + h.public_key_metadata_size = len(pkmd_blob) + + # For the Authentication data block, the hash is first and then + # the signature. + h.authentication_data_block_size = round_to_multiple( + alg.hash_num_bytes + alg.signature_num_bytes, 64) + h.algorithm_type = alg.algorithm_type + h.hash_offset = 0 + h.hash_size = alg.hash_num_bytes + # Signature offset and size - it's stored right after the hash + # (in Authentication data block). + h.signature_offset = alg.hash_num_bytes + h.signature_size = alg.signature_num_bytes + + h.rollback_index = rollback_index + h.flags = flags + h.rollback_index_location = rollback_index_location + + # Generate Header data block. + header_data_blob = h.encode() + + # Generate Auxiliary data block. + aux_data_blob = bytearray() + aux_data_blob.extend(encoded_descriptors) + aux_data_blob.extend(encoded_key) + aux_data_blob.extend(pkmd_blob) + padding_bytes = h.auxiliary_data_block_size - len(aux_data_blob) + aux_data_blob.extend(b'\0' * padding_bytes) + + # Calculate the hash. + binary_hash = b'' + binary_signature = b'' + if algorithm_name != 'NONE': + ha = hashlib.new(alg.hash_name) + ha.update(header_data_blob) + ha.update(aux_data_blob) + binary_hash = ha.digest() + + # Calculate the signature. + rsa_key = RSAPublicKey(key_path) + data_to_sign = header_data_blob + bytes(aux_data_blob) + binary_signature = rsa_key.sign(algorithm_name, data_to_sign, + signing_helper, signing_helper_with_files) + + # Generate Authentication data block. + auth_data_blob = bytearray() + auth_data_blob.extend(binary_hash) + auth_data_blob.extend(binary_signature) + padding_bytes = h.authentication_data_block_size - len(auth_data_blob) + auth_data_blob.extend(b'\0' * padding_bytes) + + return header_data_blob + bytes(auth_data_blob) + bytes(aux_data_blob) + + def extract_public_key(self, key_path, output): + """Implements the 'extract_public_key' command. + + Arguments: + key_path: The path to a RSA private key file. + output: The file to write to. + + Raises: + AvbError: If the public key could not be extracted. + """ + output.write(RSAPublicKey(key_path).encode()) + + def append_vbmeta_image(self, image_filename, vbmeta_image_filename, + partition_size): + """Implementation of the append_vbmeta_image command. + + Arguments: + image_filename: File to add the footer to. + vbmeta_image_filename: File to get vbmeta struct from. + partition_size: Size of partition. + + Raises: + AvbError: If an argument is incorrect or if appending VBMeta image fialed. + """ + image = ImageHandler(image_filename) + + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + + # If there's already a footer, truncate the image to its original + # size. This way 'avbtool append_vbmeta_image' is idempotent. + if image.image_size >= AvbFooter.SIZE: + image.seek(image.image_size - AvbFooter.SIZE) + try: + footer = AvbFooter(image.read(AvbFooter.SIZE)) + # Existing footer found. Just truncate. + original_image_size = footer.original_image_size + image.truncate(footer.original_image_size) + except (LookupError, struct.error): + original_image_size = image.image_size + else: + # Image size is too small to possibly contain a footer. + original_image_size = image.image_size + + # If anything goes wrong from here-on, restore the image back to + # its original size. + try: + vbmeta_image_handler = ImageHandler(vbmeta_image_filename) + vbmeta_blob = self._load_vbmeta_blob(vbmeta_image_handler) + + # If the image isn't sparse, its size might not be a multiple of + # the block size. This will screw up padding later so just grow it. + if image.image_size % image.block_size != 0: + assert not image.is_sparse + padding_needed = image.block_size - (image.image_size%image.block_size) + image.truncate(image.image_size + padding_needed) + + # The append_raw() method requires content with size being a + # multiple of |block_size| so add padding as needed. Also record + # where this is written to since we'll need to put that in the + # footer. + vbmeta_offset = image.image_size + padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) - + len(vbmeta_blob)) + vbmeta_blob_with_padding = vbmeta_blob + b'\0' * padding_needed + + # Append vbmeta blob and footer + image.append_raw(vbmeta_blob_with_padding) + vbmeta_end_offset = vbmeta_offset + len(vbmeta_blob_with_padding) + + # Now insert a DONT_CARE chunk with enough bytes such that the + # final Footer block is at the end of partition_size.. + image.append_dont_care(partition_size - vbmeta_end_offset - + 1 * image.block_size) + + # Generate the Footer that tells where the VBMeta footer + # is. Also put enough padding in the front of the footer since + # we'll write out an entire block. + footer = AvbFooter() + footer.original_image_size = original_image_size + footer.vbmeta_offset = vbmeta_offset + footer.vbmeta_size = len(vbmeta_blob) + footer_blob = footer.encode() + footer_blob_with_padding = (b'\0' * (image.block_size - AvbFooter.SIZE) + + footer_blob) + image.append_raw(footer_blob_with_padding) + + except Exception as e: + # Truncate back to original size, then re-raise. + image.truncate(original_image_size) + raise AvbError('Appending VBMeta image failed: {}.'.format(e)) from e + + def add_hash_footer(self, image_filename, partition_size, + dynamic_partition_size, partition_name, + hash_algorithm, salt, chain_partitions, algorithm_name, + key_path, + public_key_metadata_path, rollback_index, flags, + rollback_index_location, props, + props_from_file, kernel_cmdlines, + setup_rootfs_from_kernel, + include_descriptors_from_image, calc_max_image_size, + signing_helper, signing_helper_with_files, + release_string, append_to_release_string, + output_vbmeta_image, do_not_append_vbmeta_image, + print_required_libavb_version, use_persistent_digest, + do_not_use_ab): + """Implementation of the add_hash_footer on unsparse images. + + Arguments: + image_filename: File to add the footer to. + partition_size: Size of partition. + dynamic_partition_size: Calculate partition size based on image size. + partition_name: Name of partition (without A/B suffix). + hash_algorithm: Hash algorithm to use. + salt: Salt to use as a hexadecimal string or None to use /dev/urandom. + chain_partitions: List of partitions to chain. + algorithm_name: Name of algorithm to use. + key_path: Path to key to use or None. + public_key_metadata_path: Path to public key metadata or None. + rollback_index: Rollback index. + flags: Flags value to use in the image. + rollback_index_location: Location of the main vbmeta rollback index. + props: Properties to insert (List of strings of the form 'key:value'). + props_from_file: Properties to insert (List of strings 'key:'). + kernel_cmdlines: Kernel cmdlines to insert (list of strings). + setup_rootfs_from_kernel: None or file to generate + dm-verity kernel cmdline from. + include_descriptors_from_image: List of file objects for which + to insert descriptors from. + calc_max_image_size: Don't store the footer - instead calculate the + maximum image size leaving enough room for metadata with the + given |partition_size|. + signing_helper: Program which signs a hash and return signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + release_string: None or avbtool release string. + append_to_release_string: None or string to append. + output_vbmeta_image: If not None, also write vbmeta struct to this file. + do_not_append_vbmeta_image: If True, don't append vbmeta struct. + print_required_libavb_version: True to only print required libavb version. + use_persistent_digest: Use a persistent digest on device. + do_not_use_ab: This partition does not use A/B. + + Raises: + AvbError: If an argument is incorrect of if adding of hash_footer failed. + """ + if not partition_size and not dynamic_partition_size: + raise AvbError('--dynamic_partition_size required when not specifying a ' + 'partition size') + + if dynamic_partition_size and calc_max_image_size: + raise AvbError('--calc_max_image_size not supported with ' + '--dynamic_partition_size') + + required_libavb_version_minor = 0 + if use_persistent_digest or do_not_use_ab: + required_libavb_version_minor = 1 + if rollback_index_location > 0: + required_libavb_version_minor = 2 + + # If we're asked to calculate minimum required libavb version, we're done. + if print_required_libavb_version: + print('1.{}'.format(required_libavb_version_minor)) + return + + # First, calculate the maximum image size such that an image + # this size + metadata (footer + vbmeta struct) fits in + # |partition_size|. + max_metadata_size = self.MAX_VBMETA_SIZE + self.MAX_FOOTER_SIZE + if not dynamic_partition_size and partition_size < max_metadata_size: + raise AvbError('Parition size of {} is too small. ' + 'Needs to be at least {}'.format( + partition_size, max_metadata_size)) + + # If we're asked to only calculate the maximum image size, we're done. + if calc_max_image_size: + print('{}'.format(partition_size - max_metadata_size)) + return + + image = ImageHandler(image_filename) + + # If there's already a footer, truncate the image to its original + # size. This way 'avbtool add_hash_footer' is idempotent (modulo + # salts). + if image.image_size >= AvbFooter.SIZE: + image.seek(image.image_size - AvbFooter.SIZE) + try: + footer = AvbFooter(image.read(AvbFooter.SIZE)) + # Existing footer found. Just truncate. + original_image_size = footer.original_image_size + image.truncate(footer.original_image_size) + except (LookupError, struct.error): + original_image_size = image.image_size + else: + # Image size is too small to possibly contain a footer. + original_image_size = image.image_size + + if dynamic_partition_size: + partition_size = round_to_multiple( + original_image_size + max_metadata_size, image.block_size) + + max_image_size = partition_size - max_metadata_size + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + + # If anything goes wrong from here-on, restore the image back to + # its original size. + try: + # If image size exceeds the maximum image size, fail. + if image.image_size > max_image_size: + raise AvbError('Image size of {} exceeds maximum image ' + 'size of {} in order to fit in a partition ' + 'size of {}.'.format(image.image_size, max_image_size, + partition_size)) + + digest_size = len(hashlib.new(hash_algorithm).digest()) + if salt: + salt = binascii.unhexlify(salt) + elif salt is None and not use_persistent_digest: + # If salt is not explicitly specified, choose a hash that's the same + # size as the hash size. Don't populate a random salt if this + # descriptor is being created to use a persistent digest on device. + hash_size = digest_size + with open('/dev/urandom', 'rb') as f: + salt = f.read(hash_size) + else: + salt = b'' + + hasher = hashlib.new(hash_algorithm, salt) + # TODO(zeuthen): might want to read this in chunks to avoid + # memory pressure, then again, this is only supposed to be used + # on kernel/initramfs partitions. Possible optimization. + image.seek(0) + hasher.update(image.read(image.image_size)) + digest = hasher.digest() + + h_desc = AvbHashDescriptor() + h_desc.image_size = image.image_size + h_desc.hash_algorithm = hash_algorithm + h_desc.partition_name = partition_name + h_desc.salt = salt + h_desc.flags = 0 + if do_not_use_ab: + h_desc.flags |= 1 # AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB + if not use_persistent_digest: + h_desc.digest = digest + + # Generate the VBMeta footer. + ht_desc_to_setup = None + vbmeta_blob = self._generate_vbmeta_blob( + algorithm_name, key_path, public_key_metadata_path, [h_desc], + chain_partitions, rollback_index, flags, rollback_index_location, + props, props_from_file, + kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, + include_descriptors_from_image, signing_helper, + signing_helper_with_files, release_string, + append_to_release_string, required_libavb_version_minor) + + # Write vbmeta blob, if requested. + if output_vbmeta_image: + output_vbmeta_image.write(vbmeta_blob) + + # Append vbmeta blob and footer, unless requested not to. + if not do_not_append_vbmeta_image: + # If the image isn't sparse, its size might not be a multiple of + # the block size. This will screw up padding later so just grow it. + if image.image_size % image.block_size != 0: + assert not image.is_sparse + padding_needed = image.block_size - ( + image.image_size % image.block_size) + image.truncate(image.image_size + padding_needed) + + # The append_raw() method requires content with size being a + # multiple of |block_size| so add padding as needed. Also record + # where this is written to since we'll need to put that in the + # footer. + vbmeta_offset = image.image_size + padding_needed = ( + round_to_multiple(len(vbmeta_blob), image.block_size) - + len(vbmeta_blob)) + vbmeta_blob_with_padding = vbmeta_blob + b'\0' * padding_needed + + image.append_raw(vbmeta_blob_with_padding) + vbmeta_end_offset = vbmeta_offset + len(vbmeta_blob_with_padding) + + # Now insert a DONT_CARE chunk with enough bytes such that the + # final Footer block is at the end of partition_size.. + image.append_dont_care(partition_size - vbmeta_end_offset - + 1 * image.block_size) + + # Generate the Footer that tells where the VBMeta footer + # is. Also put enough padding in the front of the footer since + # we'll write out an entire block. + footer = AvbFooter() + footer.original_image_size = original_image_size + footer.vbmeta_offset = vbmeta_offset + footer.vbmeta_size = len(vbmeta_blob) + footer_blob = footer.encode() + footer_blob_with_padding = ( + b'\0' * (image.block_size - AvbFooter.SIZE) + footer_blob) + image.append_raw(footer_blob_with_padding) + except Exception as e: + # Truncate back to original size, then re-raise. + image.truncate(original_image_size) + raise AvbError('Adding hash_footer failed: {}.'.format(e)) from e + + def add_hashtree_footer(self, image_filename, partition_size, partition_name, + generate_fec, fec_num_roots, hash_algorithm, + block_size, salt, chain_partitions, algorithm_name, + key_path, + public_key_metadata_path, rollback_index, flags, + rollback_index_location, + props, props_from_file, kernel_cmdlines, + setup_rootfs_from_kernel, + setup_as_rootfs_from_kernel, + include_descriptors_from_image, + calc_max_image_size, signing_helper, + signing_helper_with_files, + release_string, append_to_release_string, + output_vbmeta_image, do_not_append_vbmeta_image, + print_required_libavb_version, + use_persistent_root_digest, do_not_use_ab, + no_hashtree, check_at_most_once): + """Implements the 'add_hashtree_footer' command. + + See https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for + more information about dm-verity and these hashes. + + Arguments: + image_filename: File to add the footer to. + partition_size: Size of partition or 0 to put it right at the end. + partition_name: Name of partition (without A/B suffix). + generate_fec: If True, generate FEC codes. + fec_num_roots: Number of roots for FEC. + hash_algorithm: Hash algorithm to use. + block_size: Block size to use. + salt: Salt to use as a hexadecimal string or None to use /dev/urandom. + chain_partitions: List of partitions to chain. + algorithm_name: Name of algorithm to use. + key_path: Path to key to use or None. + public_key_metadata_path: Path to public key metadata or None. + rollback_index: Rollback index. + flags: Flags value to use in the image. + rollback_index_location: Location of the main vbmeta rollback index. + props: Properties to insert (List of strings of the form 'key:value'). + props_from_file: Properties to insert (List of strings 'key:'). + kernel_cmdlines: Kernel cmdlines to insert (list of strings). + setup_rootfs_from_kernel: None or file to generate + dm-verity kernel cmdline from. + setup_as_rootfs_from_kernel: If True, generate dm-verity kernel + cmdline to set up rootfs. + include_descriptors_from_image: List of file objects for which + to insert descriptors from. + calc_max_image_size: Don't store the hashtree or footer - instead + calculate the maximum image size leaving enough room for hashtree + and metadata with the given |partition_size|. + signing_helper: Program which signs a hash and return signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + release_string: None or avbtool release string. + append_to_release_string: None or string to append. + output_vbmeta_image: If not None, also write vbmeta struct to this file. + do_not_append_vbmeta_image: If True, don't append vbmeta struct. + print_required_libavb_version: True to only print required libavb version. + use_persistent_root_digest: Use a persistent root digest on device. + do_not_use_ab: The partition does not use A/B. + no_hashtree: Do not append hashtree. Set size in descriptor as zero. + check_at_most_once: Set to verify data blocks only the first time they + are read from the data device. + + Raises: + AvbError: If an argument is incorrect or adding the hashtree footer + failed. + """ + required_libavb_version_minor = 0 + if use_persistent_root_digest or do_not_use_ab or check_at_most_once: + required_libavb_version_minor = 1 + if rollback_index_location > 0: + required_libavb_version_minor = 2 + + # If we're asked to calculate minimum required libavb version, we're done. + if print_required_libavb_version: + print('1.{}'.format(required_libavb_version_minor)) + return + + digest_size = len(create_avb_hashtree_hasher(hash_algorithm, b'') + .digest()) + digest_padding = round_to_pow2(digest_size) - digest_size + + # If |partition_size| is given (e.g. not 0), calculate the maximum image + # size such that an image this size + the hashtree + metadata (footer + + # vbmeta struct) fits in |partition_size|. We use very conservative figures + # for metadata. + if partition_size > 0: + max_tree_size = 0 + max_fec_size = 0 + if not no_hashtree: + (_, max_tree_size) = calc_hash_level_offsets( + partition_size, block_size, digest_size + digest_padding) + if generate_fec: + max_fec_size = calc_fec_data_size(partition_size, fec_num_roots) + max_metadata_size = (max_fec_size + max_tree_size + + self.MAX_VBMETA_SIZE + + self.MAX_FOOTER_SIZE) + max_image_size = partition_size - max_metadata_size + else: + max_image_size = 0 + + # If we're asked to only calculate the maximum image size, we're done. + if calc_max_image_size: + print('{}'.format(max_image_size)) + return + + image = ImageHandler(image_filename) + + if partition_size > 0: + if partition_size % image.block_size != 0: + raise AvbError('Partition size of {} is not a multiple of the image ' + 'block size {}.'.format(partition_size, + image.block_size)) + elif image.image_size % image.block_size != 0: + raise AvbError('File size of {} is not a multiple of the image ' + 'block size {}.'.format(image.image_size, + image.block_size)) + + # If there's already a footer, truncate the image to its original + # size. This way 'avbtool add_hashtree_footer' is idempotent + # (modulo salts). + if image.image_size >= AvbFooter.SIZE: + image.seek(image.image_size - AvbFooter.SIZE) + try: + footer = AvbFooter(image.read(AvbFooter.SIZE)) + # Existing footer found. Just truncate. + original_image_size = footer.original_image_size + image.truncate(footer.original_image_size) + except (LookupError, struct.error): + original_image_size = image.image_size + else: + # Image size is too small to possibly contain a footer. + original_image_size = image.image_size + + # If anything goes wrong from here-on, restore the image back to + # its original size. + try: + # Ensure image is multiple of block_size. + rounded_image_size = round_to_multiple(image.image_size, block_size) + if rounded_image_size > image.image_size: + # If we need to round up the image size, it means the length of the + # data to append is not a multiple of block size. + # Setting multiple_block_size to false, so append_raw() will not + # require it. + image.append_raw(b'\0' * (rounded_image_size - image.image_size), + multiple_block_size=False) + + # If image size exceeds the maximum image size, fail. + if partition_size > 0: + if image.image_size > max_image_size: + raise AvbError('Image size of {} exceeds maximum image ' + 'size of {} in order to fit in a partition ' + 'size of {}.'.format(image.image_size, max_image_size, + partition_size)) + + if salt: + salt = binascii.unhexlify(salt) + elif salt is None and not use_persistent_root_digest: + # If salt is not explicitly specified, choose a hash that's the same + # size as the hash size. Don't populate a random salt if this + # descriptor is being created to use a persistent digest on device. + hash_size = digest_size + with open('/dev/urandom', 'rb') as f: + salt = f.read(hash_size) + else: + salt = b'' + + # Hashes are stored upside down so we need to calculate hash + # offsets in advance. + (hash_level_offsets, tree_size) = calc_hash_level_offsets( + image.image_size, block_size, digest_size + digest_padding) + + # If the image isn't sparse, its size might not be a multiple of + # the block size. This will screw up padding later so just grow it. + if image.image_size % image.block_size != 0: + assert not image.is_sparse + padding_needed = image.block_size - (image.image_size%image.block_size) + image.truncate(image.image_size + padding_needed) + + # Generate the tree and add padding as needed. + tree_offset = image.image_size + root_digest, hash_tree = generate_hash_tree(image, image.image_size, + block_size, + hash_algorithm, salt, + digest_padding, + hash_level_offsets, + tree_size) + + # Generate HashtreeDescriptor with details about the tree we + # just generated. + if no_hashtree: + tree_size = 0 + hash_tree = b'' + ht_desc = AvbHashtreeDescriptor() + ht_desc.dm_verity_version = 1 + ht_desc.image_size = image.image_size + ht_desc.tree_offset = tree_offset + ht_desc.tree_size = tree_size + ht_desc.data_block_size = block_size + ht_desc.hash_block_size = block_size + ht_desc.hash_algorithm = hash_algorithm + ht_desc.partition_name = partition_name + ht_desc.salt = salt + if do_not_use_ab: + ht_desc.flags |= AvbHashtreeDescriptor.FLAGS_DO_NOT_USE_AB + if not use_persistent_root_digest: + ht_desc.root_digest = root_digest + if check_at_most_once: + ht_desc.flags |= AvbHashtreeDescriptor.FLAGS_CHECK_AT_MOST_ONCE + + # Write the hash tree + padding_needed = (round_to_multiple(len(hash_tree), image.block_size) - + len(hash_tree)) + hash_tree_with_padding = hash_tree + b'\0' * padding_needed + if len(hash_tree_with_padding) > 0: + image.append_raw(hash_tree_with_padding) + len_hashtree_and_fec = len(hash_tree_with_padding) + + # Generate FEC codes, if requested. + if generate_fec: + if no_hashtree: + fec_data = b'' + else: + fec_data = generate_fec_data(image_filename, fec_num_roots) + padding_needed = (round_to_multiple(len(fec_data), image.block_size) - + len(fec_data)) + fec_data_with_padding = fec_data + b'\0' * padding_needed + fec_offset = image.image_size + image.append_raw(fec_data_with_padding) + len_hashtree_and_fec += len(fec_data_with_padding) + # Update the hashtree descriptor. + ht_desc.fec_num_roots = fec_num_roots + ht_desc.fec_offset = fec_offset + ht_desc.fec_size = len(fec_data) + + ht_desc_to_setup = None + if setup_as_rootfs_from_kernel: + ht_desc_to_setup = ht_desc + + # Generate the VBMeta footer and add padding as needed. + vbmeta_offset = tree_offset + len_hashtree_and_fec + vbmeta_blob = self._generate_vbmeta_blob( + algorithm_name, key_path, public_key_metadata_path, [ht_desc], + chain_partitions, rollback_index, flags, rollback_index_location, + props, props_from_file, + kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, + include_descriptors_from_image, signing_helper, + signing_helper_with_files, release_string, + append_to_release_string, required_libavb_version_minor) + padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) - + len(vbmeta_blob)) + vbmeta_blob_with_padding = vbmeta_blob + b'\0' * padding_needed + + # Write vbmeta blob, if requested. + if output_vbmeta_image: + output_vbmeta_image.write(vbmeta_blob) + + # Append vbmeta blob and footer, unless requested not to. + if not do_not_append_vbmeta_image: + image.append_raw(vbmeta_blob_with_padding) + + # Now insert a DONT_CARE chunk with enough bytes such that the + # final Footer block is at the end of partition_size.. + if partition_size > 0: + image.append_dont_care(partition_size - image.image_size - + 1 * image.block_size) + + # Generate the Footer that tells where the VBMeta footer + # is. Also put enough padding in the front of the footer since + # we'll write out an entire block. + footer = AvbFooter() + footer.original_image_size = original_image_size + footer.vbmeta_offset = vbmeta_offset + footer.vbmeta_size = len(vbmeta_blob) + footer_blob = footer.encode() + footer_blob_with_padding = ( + b'\0' * (image.block_size - AvbFooter.SIZE) + footer_blob) + image.append_raw(footer_blob_with_padding) + + except Exception as e: + # Truncate back to original size, then re-raise. + image.truncate(original_image_size) + raise AvbError('Adding hashtree_footer failed: {}.'.format(e)) from e + + def make_atx_certificate(self, output, authority_key_path, subject_key_path, + subject_key_version, subject, + is_intermediate_authority, usage, signing_helper, + signing_helper_with_files): + """Implements the 'make_atx_certificate' command. + + Android Things certificates are required for Android Things public key + metadata. They chain the vbmeta signing key for a particular product back to + a fused, permanent root key. These certificates are fixed-length and fixed- + format with the explicit goal of not parsing ASN.1 in bootloader code. + + Arguments: + output: Certificate will be written to this file on success. + authority_key_path: A PEM file path with the authority private key. + If None, then a certificate will be created without a + signature. The signature can be created out-of-band + and appended. + subject_key_path: Path to a PEM or DER subject public key. + subject_key_version: A 64-bit version value. If this is None, the number + of seconds since the epoch is used. + subject: A subject identifier. For Product Signing Key certificates this + should be the same Product ID found in the permanent attributes. + is_intermediate_authority: True if the certificate is for an intermediate + authority. + usage: If not empty, overrides the cert usage with a hash of this value. + signing_helper: Program which signs a hash and returns the signature. + signing_helper_with_files: Same as signing_helper but uses files instead. + + Raises: + AvbError: If there an error during signing. + """ + signed_data = bytearray() + signed_data.extend(struct.pack(' block_size: + num_blocks = (size + block_size - 1) // block_size + level_size = round_to_multiple(num_blocks * digest_size, block_size) + + level_sizes.append(level_size) + tree_size += level_size + num_levels += 1 + + size = level_size + + for n in range(0, num_levels): + offset = 0 + for m in range(n + 1, num_levels): + offset += level_sizes[m] + level_offsets.append(offset) + + return level_offsets, tree_size + + +# See system/extras/libfec/include/fec/io.h for these definitions. +FEC_FOOTER_FORMAT = ' block_size: + level_output_list = [] + remaining = hash_src_size + while remaining > 0: + hasher = create_avb_hashtree_hasher(hash_alg_name, salt) + # Only read from the file for the first level - for subsequent + # levels, access the array we're building. + if level_num == 0: + image.seek(hash_src_offset + hash_src_size - remaining) + data = image.read(min(remaining, block_size)) + else: + offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining + data = hash_ret[offset:offset + block_size] + hasher.update(data) + + remaining -= len(data) + if len(data) < block_size: + hasher.update(b'\0' * (block_size - len(data))) + level_output_list.append(hasher.digest()) + if digest_padding > 0: + level_output_list.append(b'\0' * digest_padding) + + level_output = b''.join(level_output_list) + + padding_needed = (round_to_multiple( + len(level_output), block_size) - len(level_output)) + level_output += b'\0' * padding_needed + + # Copy level-output into resulting tree. + offset = hash_level_offsets[level_num] + hash_ret[offset:offset + len(level_output)] = level_output + + # Continue on to the next level. + hash_src_size = len(level_output) + level_num += 1 + + hasher = create_avb_hashtree_hasher(hash_alg_name, salt) + hasher.update(level_output) + return hasher.digest(), bytes(hash_ret) + + +class AvbTool(object): + """Object for avbtool command-line tool.""" + + def __init__(self): + """Initializer method.""" + self.avb = Avb() + + def _add_common_args(self, sub_parser): + """Adds arguments used by several sub-commands. + + Arguments: + sub_parser: The parser to add arguments to. + """ + sub_parser.add_argument('--algorithm', + help='Algorithm to use (default: NONE)', + metavar='ALGORITHM', + default='NONE') + sub_parser.add_argument('--key', + help='Path to RSA private key file', + metavar='KEY', + required=False) + sub_parser.add_argument('--signing_helper', + help='Path to helper used for signing', + metavar='APP', + default=None, + required=False) + sub_parser.add_argument('--signing_helper_with_files', + help='Path to helper used for signing using files', + metavar='APP', + default=None, + required=False) + sub_parser.add_argument('--public_key_metadata', + help='Path to public key metadata file', + metavar='KEY_METADATA', + required=False) + sub_parser.add_argument('--rollback_index', + help='Rollback Index', + type=parse_number, + default=0) + sub_parser.add_argument('--rollback_index_location', + help='Location of main vbmeta Rollback Index', + type=parse_number, + default=0) + # This is used internally for unit tests. Do not include in --help output. + sub_parser.add_argument('--internal_release_string', + help=argparse.SUPPRESS) + sub_parser.add_argument('--append_to_release_string', + help='Text to append to release string', + metavar='STR') + sub_parser.add_argument('--prop', + help='Add property', + metavar='KEY:VALUE', + action='append') + sub_parser.add_argument('--prop_from_file', + help='Add property from file', + metavar='KEY:PATH', + action='append') + sub_parser.add_argument('--kernel_cmdline', + help='Add kernel cmdline', + metavar='CMDLINE', + action='append') + # TODO(zeuthen): the --setup_rootfs_from_kernel option used to be called + # --generate_dm_verity_cmdline_from_hashtree. Remove support for the latter + # at some future point. + sub_parser.add_argument('--setup_rootfs_from_kernel', + '--generate_dm_verity_cmdline_from_hashtree', + metavar='IMAGE', + help='Adds kernel cmdline to set up IMAGE', + type=argparse.FileType('rb')) + sub_parser.add_argument('--include_descriptors_from_image', + help='Include descriptors from image', + metavar='IMAGE', + action='append', + type=argparse.FileType('rb')) + sub_parser.add_argument('--print_required_libavb_version', + help=('Don\'t store the footer - ' + 'instead calculate the required libavb ' + 'version for the given options.'), + action='store_true') + # These are only allowed from top-level vbmeta and boot-in-lieu-of-vbmeta. + sub_parser.add_argument('--chain_partition', + help='Allow signed integrity-data for partition', + metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH', + action='append') + sub_parser.add_argument('--flags', + help='VBMeta flags', + type=parse_number, + default=0) + sub_parser.add_argument('--set_hashtree_disabled_flag', + help='Set the HASHTREE_DISABLED flag', + action='store_true') + + def _add_common_footer_args(self, sub_parser): + """Adds arguments used by add_*_footer sub-commands. + + Arguments: + sub_parser: The parser to add arguments to. + """ + sub_parser.add_argument('--use_persistent_digest', + help='Use a persistent digest on device instead of ' + 'storing the digest in the descriptor. This ' + 'cannot be used with A/B so must be combined ' + 'with --do_not_use_ab when an A/B suffix is ' + 'expected at runtime.', + action='store_true') + sub_parser.add_argument('--do_not_use_ab', + help='The partition does not use A/B even when an ' + 'A/B suffix is present. This must not be used ' + 'for vbmeta or chained partitions.', + action='store_true') + + def _fixup_common_args(self, args): + """Common fixups needed by subcommands. + + Arguments: + args: Arguments to modify. + + Returns: + The modified arguments. + """ + if args.set_hashtree_disabled_flag: + args.flags |= AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED + return args + + def run(self, argv): + """Command-line processor. + + Arguments: + argv: Pass sys.argv from main. + """ + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(title='subcommands') + + sub_parser = subparsers.add_parser( + 'generate_test_image', + help=('Generates a test image with a known pattern for testing: ' + '0x00 0x01 0x02 ... 0xff 0x00 0x01 ...')) + sub_parser.add_argument('--image_size', + help='Size of image to generate.', + type=parse_number, + required=True) + sub_parser.add_argument('--start_byte', + help='Integer for the start byte of the pattern.', + type=parse_number, + default=0) + sub_parser.add_argument('--output', + help='Output file name.', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.set_defaults(func=self.generate_test_image) + + sub_parser = subparsers.add_parser('version', + help='Prints version of avbtool.') + sub_parser.set_defaults(func=self.version) + + sub_parser = subparsers.add_parser('extract_public_key', + help='Extract public key.') + sub_parser.add_argument('--key', + help='Path to RSA private key file', + required=True) + sub_parser.add_argument('--output', + help='Output file name', + type=argparse.FileType('wb'), + required=True) + sub_parser.set_defaults(func=self.extract_public_key) + + sub_parser = subparsers.add_parser('make_vbmeta_image', + help='Makes a vbmeta image.') + sub_parser.add_argument('--output', + help='Output file name', + type=argparse.FileType('wb')) + sub_parser.add_argument('--padding_size', + metavar='NUMBER', + help='If non-zero, pads output with NUL bytes so ' + 'its size is a multiple of NUMBER ' + '(default: 0)', + type=parse_number, + default=0) + self._add_common_args(sub_parser) + sub_parser.set_defaults(func=self.make_vbmeta_image) + + sub_parser = subparsers.add_parser('add_hash_footer', + help='Add hashes and footer to image.') + sub_parser.add_argument('--image', + help='Image to add hashes to', + type=argparse.FileType('rb+')) + sub_parser.add_argument('--partition_size', + help='Partition size', + type=parse_number) + sub_parser.add_argument('--dynamic_partition_size', + help='Calculate partition size based on image size', + action='store_true') + sub_parser.add_argument('--partition_name', + help='Partition name', + default=None) + sub_parser.add_argument('--hash_algorithm', + help='Hash algorithm to use (default: sha256)', + default='sha256') + sub_parser.add_argument('--salt', + help='Salt in hex (default: /dev/urandom)') + sub_parser.add_argument('--calc_max_image_size', + help=('Don\'t store the footer - ' + 'instead calculate the maximum image size ' + 'leaving enough room for metadata with ' + 'the given partition size.'), + action='store_true') + sub_parser.add_argument('--output_vbmeta_image', + help='Also write vbmeta struct to file', + type=argparse.FileType('wb')) + sub_parser.add_argument('--do_not_append_vbmeta_image', + help=('Do not append vbmeta struct or footer ' + 'to the image'), + action='store_true') + self._add_common_args(sub_parser) + self._add_common_footer_args(sub_parser) + sub_parser.set_defaults(func=self.add_hash_footer) + + sub_parser = subparsers.add_parser('append_vbmeta_image', + help='Append vbmeta image to image.') + sub_parser.add_argument('--image', + help='Image to append vbmeta blob to', + type=argparse.FileType('rb+')) + sub_parser.add_argument('--partition_size', + help='Partition size', + type=parse_number, + required=True) + sub_parser.add_argument('--vbmeta_image', + help='Image with vbmeta blob to append', + type=argparse.FileType('rb')) + sub_parser.set_defaults(func=self.append_vbmeta_image) + + sub_parser = subparsers.add_parser( + 'add_hashtree_footer', + help='Add hashtree and footer to image.') + sub_parser.add_argument('--image', + help='Image to add hashtree to', + type=argparse.FileType('rb+')) + sub_parser.add_argument('--partition_size', + help='Partition size', + default=0, + type=parse_number) + sub_parser.add_argument('--partition_name', + help='Partition name', + default='') + sub_parser.add_argument('--hash_algorithm', + help='Hash algorithm to use (default: sha1)', + default='sha1') + sub_parser.add_argument('--salt', + help='Salt in hex (default: /dev/urandom)') + sub_parser.add_argument('--block_size', + help='Block size (default: 4096)', + type=parse_number, + default=4096) + # TODO(zeuthen): The --generate_fec option was removed when we + # moved to generating FEC by default. To avoid breaking existing + # users needing to transition we simply just print a warning below + # in add_hashtree_footer(). Remove this option and the warning at + # some point in the future. + sub_parser.add_argument('--generate_fec', + help=argparse.SUPPRESS, + action='store_true') + sub_parser.add_argument( + '--do_not_generate_fec', + help='Do not generate forward-error-correction codes', + action='store_true') + sub_parser.add_argument('--fec_num_roots', + help='Number of roots for FEC (default: 2)', + type=parse_number, + default=2) + sub_parser.add_argument('--calc_max_image_size', + help=('Don\'t store the hashtree or footer - ' + 'instead calculate the maximum image size ' + 'leaving enough room for hashtree ' + 'and metadata with the given partition ' + 'size.'), + action='store_true') + sub_parser.add_argument('--output_vbmeta_image', + help='Also write vbmeta struct to file', + type=argparse.FileType('wb')) + sub_parser.add_argument('--do_not_append_vbmeta_image', + help=('Do not append vbmeta struct or footer ' + 'to the image'), + action='store_true') + # This is different from --setup_rootfs_from_kernel insofar that + # it doesn't take an IMAGE, the generated cmdline will be for the + # hashtree we're adding. + sub_parser.add_argument('--setup_as_rootfs_from_kernel', + action='store_true', + help='Adds kernel cmdline for setting up rootfs') + sub_parser.add_argument('--no_hashtree', + action='store_true', + help='Do not append hashtree') + sub_parser.add_argument('--check_at_most_once', + action='store_true', + help='Set to verify data block only once') + self._add_common_args(sub_parser) + self._add_common_footer_args(sub_parser) + sub_parser.set_defaults(func=self.add_hashtree_footer) + + sub_parser = subparsers.add_parser('erase_footer', + help='Erase footer from an image.') + sub_parser.add_argument('--image', + help='Image with a footer', + type=argparse.FileType('rb+'), + required=True) + sub_parser.add_argument('--keep_hashtree', + help='Keep the hashtree and FEC in the image', + action='store_true') + sub_parser.set_defaults(func=self.erase_footer) + + sub_parser = subparsers.add_parser('zero_hashtree', + help='Zero out hashtree and FEC data.') + sub_parser.add_argument('--image', + help='Image with a footer', + type=argparse.FileType('rb+'), + required=True) + sub_parser.set_defaults(func=self.zero_hashtree) + + sub_parser = subparsers.add_parser( + 'extract_vbmeta_image', + help='Extracts vbmeta from an image with a footer.') + sub_parser.add_argument('--image', + help='Image with footer', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--output', + help='Output file name', + type=argparse.FileType('wb')) + sub_parser.add_argument('--padding_size', + metavar='NUMBER', + help='If non-zero, pads output with NUL bytes so ' + 'its size is a multiple of NUMBER ' + '(default: 0)', + type=parse_number, + default=0) + sub_parser.set_defaults(func=self.extract_vbmeta_image) + + sub_parser = subparsers.add_parser('resize_image', + help='Resize image with a footer.') + sub_parser.add_argument('--image', + help='Image with a footer', + type=argparse.FileType('rb+'), + required=True) + sub_parser.add_argument('--partition_size', + help='New partition size', + type=parse_number) + sub_parser.set_defaults(func=self.resize_image) + + sub_parser = subparsers.add_parser( + 'info_image', + help='Show information about vbmeta or footer.') + sub_parser.add_argument('--image', + help='Image to show information about', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--output', + help='Write info to file', + type=argparse.FileType('wt'), + default=sys.stdout) + sub_parser.add_argument('--atx', + help=('Show information about Android Things ' + 'eXtension (ATX).'), + action='store_true') + sub_parser.set_defaults(func=self.info_image) + + sub_parser = subparsers.add_parser( + 'verify_image', + help='Verify an image.') + sub_parser.add_argument('--image', + help='Image to verify', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--key', + help='Check embedded public key matches KEY', + metavar='KEY', + required=False) + sub_parser.add_argument('--expected_chain_partition', + help='Expected chain partition', + metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH', + action='append') + sub_parser.add_argument( + '--follow_chain_partitions', + help=('Follows chain partitions even when not ' + 'specified with the --expected_chain_partition option'), + action='store_true') + sub_parser.add_argument( + '--accept_zeroed_hashtree', + help=('Accept images where the hashtree or FEC data is zeroed out'), + action='store_true') + sub_parser.set_defaults(func=self.verify_image) + + sub_parser = subparsers.add_parser( + 'print_partition_digests', + help='Prints partition digests.') + sub_parser.add_argument('--image', + help='Image to print partition digests from', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--output', + help='Write info to file', + type=argparse.FileType('wt'), + default=sys.stdout) + sub_parser.add_argument('--json', + help=('Print output as JSON'), + action='store_true') + sub_parser.set_defaults(func=self.print_partition_digests) + + sub_parser = subparsers.add_parser( + 'calculate_vbmeta_digest', + help='Calculate vbmeta digest.') + sub_parser.add_argument('--image', + help='Image to calculate digest for', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--hash_algorithm', + help='Hash algorithm to use (default: sha256)', + default='sha256') + sub_parser.add_argument('--output', + help='Write hex digest to file (default: stdout)', + type=argparse.FileType('wt'), + default=sys.stdout) + sub_parser.set_defaults(func=self.calculate_vbmeta_digest) + + sub_parser = subparsers.add_parser( + 'calculate_kernel_cmdline', + help='Calculate kernel cmdline.') + sub_parser.add_argument('--image', + help='Image to calculate kernel cmdline for', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--hashtree_disabled', + help='Return the cmdline for hashtree disabled', + action='store_true') + sub_parser.add_argument('--output', + help='Write cmdline to file (default: stdout)', + type=argparse.FileType('wt'), + default=sys.stdout) + sub_parser.set_defaults(func=self.calculate_kernel_cmdline) + + sub_parser = subparsers.add_parser('set_ab_metadata', + help='Set A/B metadata.') + sub_parser.add_argument('--misc_image', + help=('The misc image to modify. If the image does ' + 'not exist, it will be created.'), + type=argparse.FileType('r+b'), + required=True) + sub_parser.add_argument('--slot_data', + help=('Slot data of the form "priority", ' + '"tries_remaining", "sucessful_boot" for ' + 'slot A followed by the same for slot B, ' + 'separated by colons. The default value ' + 'is 15:7:0:14:7:0.'), + default='15:7:0:14:7:0') + sub_parser.set_defaults(func=self.set_ab_metadata) + + sub_parser = subparsers.add_parser( + 'make_atx_certificate', + help='Create an Android Things eXtension (ATX) certificate.') + sub_parser.add_argument('--output', + help='Write certificate to file', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--subject', + help=('Path to subject file'), + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--subject_key', + help=('Path to subject RSA public key file'), + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--subject_key_version', + help=('Version of the subject key'), + type=parse_number, + required=False) + sub_parser.add_argument('--subject_is_intermediate_authority', + help=('Generate an intermediate authority ' + 'certificate'), + action='store_true') + sub_parser.add_argument('--usage', + help=('Override usage with a hash of the provided ' + 'string'), + required=False) + sub_parser.add_argument('--authority_key', + help='Path to authority RSA private key file', + required=False) + sub_parser.add_argument('--signing_helper', + help='Path to helper used for signing', + metavar='APP', + default=None, + required=False) + sub_parser.add_argument('--signing_helper_with_files', + help='Path to helper used for signing using files', + metavar='APP', + default=None, + required=False) + sub_parser.set_defaults(func=self.make_atx_certificate) + + sub_parser = subparsers.add_parser( + 'make_atx_permanent_attributes', + help='Create Android Things eXtension (ATX) permanent attributes.') + sub_parser.add_argument('--output', + help='Write attributes to file', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--root_authority_key', + help='Path to authority RSA public key file', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--product_id', + help=('Path to Product ID file'), + type=argparse.FileType('rb'), + required=True) + sub_parser.set_defaults(func=self.make_atx_permanent_attributes) + + sub_parser = subparsers.add_parser( + 'make_atx_metadata', + help='Create Android Things eXtension (ATX) metadata.') + sub_parser.add_argument('--output', + help='Write metadata to file', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--intermediate_key_certificate', + help='Path to intermediate key certificate file', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--product_key_certificate', + help='Path to product key certificate file', + type=argparse.FileType('rb'), + required=True) + sub_parser.set_defaults(func=self.make_atx_metadata) + + sub_parser = subparsers.add_parser( + 'make_atx_unlock_credential', + help='Create an Android Things eXtension (ATX) unlock credential.') + sub_parser.add_argument('--output', + help='Write credential to file', + type=argparse.FileType('wb'), + default=sys.stdout) + sub_parser.add_argument('--intermediate_key_certificate', + help='Path to intermediate key certificate file', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--unlock_key_certificate', + help='Path to unlock key certificate file', + type=argparse.FileType('rb'), + required=True) + sub_parser.add_argument('--challenge', + help='Path to the challenge to sign (optional). If ' + 'this is not provided the challenge signature ' + 'field is omitted and can be concatenated ' + 'later.', + required=False) + sub_parser.add_argument('--unlock_key', + help='Path to unlock key (optional). Must be ' + 'provided if using --challenge.', + required=False) + sub_parser.add_argument('--signing_helper', + help='Path to helper used for signing', + metavar='APP', + default=None, + required=False) + sub_parser.add_argument('--signing_helper_with_files', + help='Path to helper used for signing using files', + metavar='APP', + default=None, + required=False) + sub_parser.set_defaults(func=self.make_atx_unlock_credential) + + args = parser.parse_args(argv[1:]) + try: + args.func(args) + except AttributeError: + # This error gets raised when the command line tool is called without any + # arguments. It mimics the original Python 2 behavior. + parser.print_usage() + print('avbtool: error: too few arguments') + sys.exit(2) + except AvbError as e: + sys.stderr.write('{}: {}\n'.format(argv[0], str(e))) + sys.exit(1) + + def version(self, _): + """Implements the 'version' sub-command.""" + print(get_release_string()) + + def generate_test_image(self, args): + """Implements the 'generate_test_image' sub-command.""" + self.avb.generate_test_image(args.output, args.image_size, args.start_byte) + + def extract_public_key(self, args): + """Implements the 'extract_public_key' sub-command.""" + self.avb.extract_public_key(args.key, args.output) + + def make_vbmeta_image(self, args): + """Implements the 'make_vbmeta_image' sub-command.""" + args = self._fixup_common_args(args) + self.avb.make_vbmeta_image(args.output, args.chain_partition, + args.algorithm, args.key, + args.public_key_metadata, args.rollback_index, + args.flags, args.rollback_index_location, + args.prop, args.prop_from_file, + args.kernel_cmdline, + args.setup_rootfs_from_kernel, + args.include_descriptors_from_image, + args.signing_helper, + args.signing_helper_with_files, + args.internal_release_string, + args.append_to_release_string, + args.print_required_libavb_version, + args.padding_size) + + def append_vbmeta_image(self, args): + """Implements the 'append_vbmeta_image' sub-command.""" + self.avb.append_vbmeta_image(args.image.name, args.vbmeta_image.name, + args.partition_size) + + def add_hash_footer(self, args): + """Implements the 'add_hash_footer' sub-command.""" + args = self._fixup_common_args(args) + self.avb.add_hash_footer(args.image.name if args.image else None, + args.partition_size, args.dynamic_partition_size, + args.partition_name, args.hash_algorithm, + args.salt, args.chain_partition, args.algorithm, + args.key, + args.public_key_metadata, args.rollback_index, + args.flags, args.rollback_index_location, + args.prop, args.prop_from_file, + args.kernel_cmdline, + args.setup_rootfs_from_kernel, + args.include_descriptors_from_image, + args.calc_max_image_size, + args.signing_helper, + args.signing_helper_with_files, + args.internal_release_string, + args.append_to_release_string, + args.output_vbmeta_image, + args.do_not_append_vbmeta_image, + args.print_required_libavb_version, + args.use_persistent_digest, + args.do_not_use_ab) + + def add_hashtree_footer(self, args): + """Implements the 'add_hashtree_footer' sub-command.""" + args = self._fixup_common_args(args) + # TODO(zeuthen): Remove when removing support for the + # '--generate_fec' option above. + if args.generate_fec: + sys.stderr.write('The --generate_fec option is deprecated since FEC ' + 'is now generated by default. Use the option ' + '--do_not_generate_fec to not generate FEC.\n') + self.avb.add_hashtree_footer( + args.image.name if args.image else None, + args.partition_size, + args.partition_name, + not args.do_not_generate_fec, args.fec_num_roots, + args.hash_algorithm, args.block_size, + args.salt, args.chain_partition, args.algorithm, + args.key, args.public_key_metadata, + args.rollback_index, args.flags, + args.rollback_index_location, args.prop, + args.prop_from_file, + args.kernel_cmdline, + args.setup_rootfs_from_kernel, + args.setup_as_rootfs_from_kernel, + args.include_descriptors_from_image, + args.calc_max_image_size, + args.signing_helper, + args.signing_helper_with_files, + args.internal_release_string, + args.append_to_release_string, + args.output_vbmeta_image, + args.do_not_append_vbmeta_image, + args.print_required_libavb_version, + args.use_persistent_digest, + args.do_not_use_ab, + args.no_hashtree, + args.check_at_most_once) + + def erase_footer(self, args): + """Implements the 'erase_footer' sub-command.""" + self.avb.erase_footer(args.image.name, args.keep_hashtree) + + def zero_hashtree(self, args): + """Implements the 'zero_hashtree' sub-command.""" + self.avb.zero_hashtree(args.image.name) + + def extract_vbmeta_image(self, args): + """Implements the 'extract_vbmeta_image' sub-command.""" + self.avb.extract_vbmeta_image(args.output, args.image.name, + args.padding_size) + + def resize_image(self, args): + """Implements the 'resize_image' sub-command.""" + self.avb.resize_image(args.image.name, args.partition_size) + + def set_ab_metadata(self, args): + """Implements the 'set_ab_metadata' sub-command.""" + self.avb.set_ab_metadata(args.misc_image, args.slot_data) + + def info_image(self, args): + """Implements the 'info_image' sub-command.""" + self.avb.info_image(args.image.name, args.output, args.atx) + + def verify_image(self, args): + """Implements the 'verify_image' sub-command.""" + self.avb.verify_image(args.image.name, args.key, + args.expected_chain_partition, + args.follow_chain_partitions, + args.accept_zeroed_hashtree) + + def print_partition_digests(self, args): + """Implements the 'print_partition_digests' sub-command.""" + self.avb.print_partition_digests(args.image.name, args.output, args.json) + + def calculate_vbmeta_digest(self, args): + """Implements the 'calculate_vbmeta_digest' sub-command.""" + self.avb.calculate_vbmeta_digest(args.image.name, args.hash_algorithm, + args.output) + + def calculate_kernel_cmdline(self, args): + """Implements the 'calculate_kernel_cmdline' sub-command.""" + self.avb.calculate_kernel_cmdline(args.image.name, args.hashtree_disabled, + args.output) + + def make_atx_certificate(self, args): + """Implements the 'make_atx_certificate' sub-command.""" + self.avb.make_atx_certificate(args.output, args.authority_key, + args.subject_key.name, + args.subject_key_version, + args.subject.read(), + args.subject_is_intermediate_authority, + args.usage, + args.signing_helper, + args.signing_helper_with_files) + + def make_atx_permanent_attributes(self, args): + """Implements the 'make_atx_permanent_attributes' sub-command.""" + self.avb.make_atx_permanent_attributes(args.output, + args.root_authority_key.name, + args.product_id.read()) + + def make_atx_metadata(self, args): + """Implements the 'make_atx_metadata' sub-command.""" + self.avb.make_atx_metadata(args.output, + args.intermediate_key_certificate.read(), + args.product_key_certificate.read()) + + def make_atx_unlock_credential(self, args): + """Implements the 'make_atx_unlock_credential' sub-command.""" + self.avb.make_atx_unlock_credential( + args.output, + args.intermediate_key_certificate.read(), + args.unlock_key_certificate.read(), + args.challenge, + args.unlock_key, + args.signing_helper, + args.signing_helper_with_files) + + +if __name__ == '__main__': + if AVB_INVOCATION_LOGFILE: + with open(AVB_INVOCATION_LOGFILE, 'a') as log: + log.write(' '.join(sys.argv)) + log.write('\n') + + tool = AvbTool() + tool.run(sys.argv) diff --git a/aosp/avb/data/testkey_atx_pik.pem b/aosp/avb/data/testkey_atx_pik.pem new file mode 100644 index 00000000..5f34cb9f --- /dev/null +++ b/aosp/avb/data/testkey_atx_pik.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDoTSKaxoiUJhZ9 +Wb4fT59s9ZZsyIjewnmAhS/+gPTeOY7L+cpo+iDSeTAOOAw++ZcQZiGeQLwLO6DK +Aq2ujqi8ycwMx49aZsMcAKwyIMl2SoMZOgducS27z+Mzv4+FBoLV52eYVr+Idiat +b2ogciK8TEviBF7URyQa6wsdGrY4HwG7kEBL1NiZngkl24U+HNHlzn9L3/C6pg39 +g5KIQyNNjIFzTqGnr1Q0lCNLCR/UP8NtQ0koUL26MeDJk8qANTkI3ihpw27CFmWy +8R9VK7EDkAgs/C7dqBlcVJvVptu9Gih7Yltfsqeq23W87J/JvxE+NSU/iipITHZe +1IdC2dhntAysKa5r+kikyO6uh9fN7siBzNBlTBRHtEaaNcOxaGMHf87J/SktamMT +pvy+NIVe7bdjtDyyd4FVo5dS65QTBt//E5FKUrxShCP7ww2L65xbldXdBjE4JSyQ +gpVrvDMne5+Ro0If1GYnfjgwsIWq/AAm7pZ8bzZOw0HtgjJbCI4HmkJG32wXAIzA +L00S6nICiq4Q3oWnf1mNIDTM6hrcCJxAqS3t1JSqpM5EkTOFg0SZ3y83vcogxr2a +NqP/LI+02FQRirgBwRWdngUbc7mbXG5jhuYirnl52ofzhzwWrGe/iKGzhzZWnjNx +logXB9N2n7E14D0LzmLmE8sALnBr+wIDAQABAoICAHHI3otbdajDYz6xB4xErv1q +6eyM6WXbcjI+irypo8d5//TvfHKhGhSeIajFUVJDZPg2Xn8qjDEgWui4GSSoYgRe +/+C+mvwX27fKqI3BO40CgGaJ4vv42gLlmA9P5FevUcS+nSKkUxrfbKCFM0GDRnpf +EMg7hcuKsSeyO3ZdECY7IdkilivOrO3J2AdAGGrNKQ7cb8PVhA+YqL+lg8/UuuUQ +TpQRTuNY4PEEIqltZbbMKMhLLfleWyBpsAZsqsLTzwUF9Fuhy42r9NKKguNwDDuH +gEmwmLAf15Q/KTmkint85ZlSGxmr466v4mLQsI/cU/DZjU4rOfzB3zUh+sMG+5UZ +OB1aCHdMIxTMBfSUqehPNP7Fe59+OmwYezhMkfy7jJat81OobUvyqLwR/Nx5qNvs +Ry5JOJaLPIouee9Dc3Wp1uEetgAfXj+0CmWyQCzdz5NkwX0nIssXr+9eUmlwgBO+ +FdY72z8Nf84h74aZqcN4ysqU14YeIjv95U5TFQJ7Dj72DDir33t61etCIDceUpxG +y0gnGmnh7xZvVmP8rfnuXhbo4hNwxsXfReKQnP51V4yHIp2AtCReGwexRjGB6iky +K3v2XHKJnRIUFd5y7vhEfo/jj/e1GU9P6FFznJLhET5RS07JDhWhZRyvwcoa+muv +lxcFjysQb7z7dFGJSELBAoIBAQD7/Onpp9M5DniJnS8vVcFSpndpqd1M1f6ZBPQy +Owh9LOVSdJoBsaMuXStilKGxA2BqS4Ty3fww6urIjqYYL0R4q9+dSYAWBX/cGx+/ +yypaPAMA/Vjv/0dPM9pY/lwyeIYUXYjU3sNewK3fgfvxc6piAAVwNfHCegbdHhNR +gCmeeszhmSnbdFZXM9EyzTXpDVb3IWE9mhQ9oYNDxQgQplMcrb30z3AEAV9ZjwGU ++/elaBvvebPrl6un9drAO2qVXMinjCXJTiWaJdlNZkTeYCeGqXHt4xeZJUGeWdVa +aI3s1MPPxxzXi4FlwND8sinnRdPh4w0ZjA9RlkumwCJMBgFhAoIBAQDr//ri23dX +6qX7dmZvdrF9E5ksPZxPZBmBZ0ymSCgxWwHd6DC74EMv406BuccloBxIRW9YAe0/ +ZMNFvHYz/LPYa8eRFTcG34SSkMjWCPo0ywQFjAe0zpCJMpeqJ3BccZ51l5HjU4IH +8BMW83LOE3f47D0W6Sy98T2eTvJPnJ/lqck6rDvWY/G8b5rwchrScIo6+gV5fPbm +QX1fgyUrOrp5dfgDuAGfuEi/5ZoZKUNAX9KNREBV5XGqR+GPe3dxFwB50zA0aEQM +NUUbb85vSD/JoABTgyw33B+ZXnVbQ3cH9/s/TfOtg+DcJCNv4FOY/CVF+jEAQI1a +eiXrjYCjT/7bAoIBAQD69hoogOJWstjNhWRW6jtNi0jmTSx/t6iG0W47EJwVvr7t +bf9rrHTuWhA5b/nRB6HgezH/h6IBPhVyhM9aysiQvexJA8izJer/VWw7YaXelGIR +fEA1VbK5aNUPSNSd13cBlV4PU4SUO6VvMk+vWxjX3VmNNcx/eXSYh7mJs/C/S2H9 +VMNhMu5CjvLMe6AzaPuxyObFqUx7TP4kYnjzzBJ+P3Mt++J9urgxw8E5lfBAJf2c +dUMBYd6tuqeQSByQgQW+CFAhutisOwG+mhoAtxbmgJ9c4ozAE2DUync4QWUH96bE +qnNJIEFRC8WXxgEBuoOZNr33MYyYHu1dN4Fw4ZJBAoIBAHX9r3PIgiyEdqP4mFJW +J3r/V2+VBhdzVoUqHlpsbRvwAkjuE597Clxg6xlUxsp6+Gjxvi9kFzfAqxislvR6 +/XfQuyBAWRiom+GjXaYVKvNGJSaY0imFtSHDF6zMtxKhA7aLJzdewv4w+3pYESgS +98KOaiSQQ/xbJpGFqwQ+rHronmElR21y3qN1sSNSCTL5bUL7Sc1puw934rTkHqs0 +W5Lqdit1zeoK+uRmaNr3lFYVEnHqk7feVSvrcEyKUDdMZsKB4fHxx0PLRlfnWHCu +0KV/x2n5hRwQhAPsBASzAEoNo6IM2S4BZ2To9Ia0w1cTiZco9WLI42M++sKAJraA +Ak0CggEBALwQ3FKc/dc9j3lSx43RH29rOZDXACJdA7vwWRucmdXoTlKxtNEB+Q6A +ycWsHsrgrfa1IqsXJu+uezDB3TlCq+YjElxUnZJopPJQJISRtaJYcMQ3GpU+eS+L +6IhGPhOuzQ7HxgKyxFVrNi7SVpODKS305rZUI1BoY5zFuZtRjx6uDV4R4jIVZczV +y9mXACViqGQBxRCIYV2W5iCPIzG02mzK62QilS+dqCGhKY4oTxfNzOy5huvtGXhK +Y5bwRFG1RPZj8TAyp0WVUIXgyOyt6AgqUZpQW6LWgWxcJecCpPM2ch4adNyusNgi +WM8yjCL8U3+kfP4L6uIgmAuVcmbrX68= +-----END PRIVATE KEY----- diff --git a/aosp/avb/data/testkey_atx_prk.pem b/aosp/avb/data/testkey_atx_prk.pem new file mode 100644 index 00000000..17a8ccef --- /dev/null +++ b/aosp/avb/data/testkey_atx_prk.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDDKUwjFhCsMsE8 +1cWrodnnEz9+0eZhXaOhYNpXS7LmD+FQv0f/Ca/NSS2CM3ah/ihfiWKzwPERrxUJ +J9vrBgGi+LfXnOSIOoYFAiBpsjZMPiUD7fwMaxsKBJzOf4OCYNlSfsQ1exzmZJwX +7IHnnAyLS35IvgCYqCAQTJvRFlsl6U5h2nxjgI+krHTuqAasJtVxb6pzIJx/zXPU +qaB+WrVhsIiw3dtredFanlRJVcaJdnrGeJncyQBdIPX8jzlG8wKWDZv7vNXPWk/E +uAvQ8xk8dATVlCwZFWS/U2eXe57G4PspW5CtBIrYW99pCeSl6dkPxP+ut0QSrq0D +l7ja12A3FfK52xD24iZIfj4+w2fTpgL3vGDtRd837/nql183tOu0kWw5Te1SFTlH +WWLeMlXh1BVYfVJBEnjunw3IXjSR+edMHucvkH+7+Jk+yXmrAdskOeO0yVJz22VC +pS5DVqAzjBq3oe1c0BSTjSN4k8s6Ax+7xnvNUU6qFAHpAycT4rL4Nsbjw3+1dCBe +F6olB5tg2oOYtVWuG3rBH0ly4stqEXffP8CfjzPHEBeM/NW3X16y43su3Mc02zGw +3F0UmLYaKtS0BCzwaByRYCilOwGYth5uqjWJx5SqnvARUg8ooT3TF7UI2HpB+Qfi +hzbNhj55mXNQITAA0vOIYDJZWC9VkwIDAQABAoICAGLztHvxuyBkV6ANz7z3QnGs +S7x0lrf1b3rqjapHYnzNvnNfmRSSB8YxfVaP223FXRkboqT7hf9uY18SC6p9kCTe +sPAsx57e9YbijiapSmib9c6pQuvU/o+0yQDWnIplqqruRXPMaVnT12U18KPLdnzG +GVCurcS5MhCxlyuLh0mQosJwsjMNQ0e+fUdogIGW6xAqCyV2eoM+W6WPICYQvfi7 +NoNe6drzmBe3QYsxZ4nZPeUVzwWoEHwnszGMOQFlQ4FrgxQ/qbUJ2Hooyyz1pW0G +clEMYNOTClML6fFfp6C1AAP3PkLLk86hvoQWlQ0epYUeYHloyKYkVOsGaZ7kJw7D +I7GPINDTd1Bz15Pk7fHxUC655rcmq52KOfp8zmigF6/BSeIE/B7mo5Sf/yLRSrBy +O4h2MzuKuNi/TRTNIAWcIpCJZbo2NeAQgdSvNrQHBgiHsHoysSJi2T2Kx5jrXbeP +xWAxWAjWLAhOE6pS15GnQ3aAcNzx2HV116D8zv2Ma4WnL5RKNQS9IhB1uUcBf2yw +hLUyDTa9LoK2gAbTDIFYmVxIIHMszfH11PcjXSEIKmw/7yHww6deiu3rAMguiEe2 +X0sCw7gb1CHGiU/vwWUeFVvcFeZ2Dw4l+8p/5IUo5a3s9lI6XTXcgojA48BD0mjz +uQFxg2ukHX1swZ7xT6N5AoIBAQD432gmQW1+7DLB1f/cCipJAd/vHCkHQcHEIrFe +ltzfHcbEUnCHlcWisprYc0l3fPileOlrWh/oZLWf52TyJYPBPJV5yb66I3Xec6iP +pDLm9GTwxL3tJI1j+a0Ksx+RjPAK5dC0iGUpC8rKYP7uJUGN+g5zVphvmIVfM5jI +ouIIPwrzZi+VJXIGzmM78JujtN1Bfr7enu0eavcCRHFiHj8eeg8kTNw611xCG4EC +8WDIKdn8fRCEUVUBDQga90S9UftlYl39FT5YgRDqC3VperSMua32WqeCTb1zV/sa +/CM2JMYHUx73iuyWVN0Ew/anAjboP4CwOBr/axpQFjBo+IZFAoIBAQDIwBv+nPuN +fZbHVtA3ASjFnhJauG+M4BHbcb1Liv71snEaQp0b3zoF3RDUc0bKsNYJt1q5xmDc +wex2fuZvyfmMzViSAbtp5MAGRWFWOm4B0PibOzlOh2X6N0NC/+4eYF309iEUg57k +TGZJNFLQfeHY6iBFWidqyaXZF3Mp0l2aEasAmrpA6NAiQLvJTmXMiM62mYT42QAR +FyfLmMamfFYHoBXpE7qZS+bCAVb5II7pxJZh2tuXFx7Y3qX7hRTeNwaw3m/tsbWf +riImcfGda+OqOc2A3QOBqG2Y+kGvJoUWdDkOp6Rd6lTy1SAnouV7eDkvLi1li1CV +9eyhxXXKi7X3AoIBAQCvLfqOqxFa/QHBZVQjW9hl6XbqRYUvwX6WA+Gb9k9kkf4e +pPTmy2sBWf0bDROSkxomx9RuJ1M8bt9VvjhVJkj21SFWR8cEGP/X5MuqyGa4ISGI +RMR3z3ni/JVsaad3+Z/h2+CrozKp0M4e5GWt1fWt7W0MjNDiBJck6xnJaLX4HgAk +UjJ+Jox78/zv7S5w7leryX6rD21TMvHJ28l/ylCdsEdGQv/mPz+GnPuTybpZSvRR +AOuGaAWQps6kxJbTOIjf1XzZL4HiJH92bzhnVeMPB4hHV4p/cx8+uJhdZ0uVyg7G +iyDKGDTuoK+usg3Fgw6JLmH0KJoAXjB4XRYYXY+NAoIBAH8CA/Qfb4tB9L3jL9JM ++nWkn2okG/cd4E5c9G0x4EKkBaieknWK0lPZXAd9c0FThecZyN2WI7wnOKpzeOkq ++KZbWHjvfZnuborJJF8Ako61nkPfwU7snNkkU3q1Hvq671bGzYEEEOfRajlQUEC2 +E8g/v/EAq8WFFFd33ZWNEUkjenPkcIgWg2/YUrZ20jMILvgZwqYJ7F/jrXrDCpNU +QL2MS7BtmfXYroL5hAQT1DcT+Cyq4ZkCuLJuksbBmMVKPQJziI7yir0e115JYpq9 +IomVDm5D5i8G12gclKfyj+r31w1thLEiS2Ji8ngBOHzYQB2YcoI3FOH7eB2VJwPh +RlsCggEBALjp2BdFCGhPdqFmLHuYGExH7O98i9Mjhrtqe3G3pNJU5dQNcWCvW5t+ +CptoEznzJfj/BpqjMWcvEQsTRA0WE6FtT6IcPIc0IEZjzWirPe4pb8lb7snV0fC9 +ivFb0PmVsIXs5PQLETnmGO6oNJ8d+18ACSSPt+5K6Dpdfl01gKKE3t0dVcYHy/j/ +u90CPuAuErEXLd5KmaAjYsekQntr2S9nlPiTpW6aVTiXVAwDxwzEofYbzCisoCD0 +yEx3t5D4k2yyAVii3PmC+dFztbsrhGJW8RZqOTPmlMWqlzbmsXIo4oocKcyvwMN4 +MSKHJYIcbmEACtr9tALUhZo5SkjMLrQ= +-----END PRIVATE KEY----- diff --git a/aosp/avb/data/testkey_atx_psk.pem b/aosp/avb/data/testkey_atx_psk.pem new file mode 100644 index 00000000..71bfebf9 --- /dev/null +++ b/aosp/avb/data/testkey_atx_psk.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCy2mHwjC3ylCFF +dzNkZPKOiZzrUiPawe8OFcTJOySMv0/P4fILDiZuhC1VLwMHquR6kQhvyP9ffeHl +W3JHliiIFOcS71bxlO1aqVYFt3qF8A/qFVruLjpwh47+WEPfWnCEeX2i8RGpUFh/ +oc2Ey1Dv+BuoHpNUFm2HsJgTe/R1EXadmGfTthvaVfWXb9nW/67YkMEWbH5MZ6BY +bPXVpA+AN0IoQm+emSj2Ipve0TTGxgX3VQDzA8aTYimAVB+HuOq6p/UxiJu5GCyI +AhqLsVVHd1qnxCn6eGKcaRACfy6n0mWLn/UXgi2h5hhI3TFGpFSU5ZXB6TfDfYNe +uzE8njYZloWVrbSy0IascNK9JnQy9/ySy8l5Fw67QByNz9DDCRJJLE78e1ss73Dr +pjOafwtE6P+koRZe6SbE8NS2v1qiSSoCiBt6lgjOIMR9+KMp7vk8oLEfBBTMc4JR +4hiZkxPW6j8PYTuiJDs8h7ItZ5awisabbE5URvDc5DscWSE8KbGMa8iXHMJK1Eam +uko4EIQ/fod5BQ1vpJeS4vPAMqeKMufBHJUcuBkUi0au5Ux8lSNkU0591RfFfmUL +AodIqtXX39yHMN7SQqQAfAibdcDWmujN9F/Js4OHCWuhj1q6VUhSEEEqtDeX01sH +xlghCFg8lNO5GeDG5SDyGsdxY+iObQIDAQABAoICAEQwEkr9hr8HTrAHRCawffFt +8c+d32GVsqhyEDaQP90RS0J8aCVi3bAg4I+rfsI7myRHiynjPcmQWsFw3d8BFq7b +GUYUzdcI6n04Nj2zuBi8b7TVM3e/VDR22kOKL0ZGWsOG9ilbM1qT8UmnzI0mXtM+ +inzMO2tBqbyjzTcQeSDw6YIoCt2ifnf9ccastCbOEEEs3xDHiFdk4rMTx54OEILX +jnd+7MNQrVc51qdap35pHPkxBU2hUOH7+MqeR+8cxxEm28poxMYKu1+XPbuoflTi +4kM3/LErmJz9SUdKaeU9x801zOGLlg41hWiyPAksubqS1Ue6vLHhdmZ1g84Sm5j+ +lABJXRBnc1YG/nyJZlFvMhp7gCXVDYQ4nUT4y2Ozd7ip9BweBzVoXQSMvjWfQvvb +fzqJS0dNKqMQQU78x+B1g8iNgL9PCCkEeFN1YZwkJQMfQLdv+fgP9GEHlE/zz1Sv +lZNpve0CARWZUg0Nbw1w9fsbeezJpCntZMs4wVMS0Vyn9TmFAEielILmmW/tkUyR +WpZI7l4AYX4Rzx+nnVVwrSO9UDqp+2aiOODXpL3RboFi8qbblZWvNy7/mSKSKxwv +E7Z7a8qMcaFsOy7LKSsbheZNWyyhQlfe3yNeYKMDj3rFmooNpJd0hU2kqt1fZKQK +SIyutcEHQFP/p1LP1X9ZAoIBAQDdNg0o3FdYySCYTc5N6T+SUai8FyOlUhXvh7wn +m28QmC8YBafjUAPXQYJYKiKtyb1/34FSgaBS9kv5nNL5HYxr15HW7aVrFUV3kSnT +2+yuRrzHiN4B0ZuHlaRHd/fSmuOADM8b/s+CaYeWSWq0qE54uDRSa541OSENxIAq +9f83rNcg8JF/eYaJQ4bAHzDdjCPS+nSJFtfVmswyoLFZBEeBdyR7zyXPBcLKPSvc +4BfBbnrrh+boUvw6hgijDopAQVvyzuDECuA7+0Nsx/7M4p0154kxqgmP/ixuamrF +0Wdx/VOOeXMZ7pEN8hInr4XJ3QEWcxHDl7Yj2Pk8jzduCBQfAoIBAQDO+v9Ax3RH +E2kw+ce6k1K/u37Foku4EgAMuAHPtlEX0HjbC7t67p8mdqFfOLe3Gka8EFvwpmbr +af1fWijErCLxv+JtVsQuv2cr4aFBjs+F/NRaNtY4fS5732O7kGML8y1qtLXGi6qS +bdsdTjhjA2s65tU/lE7g6vJEhAFUtTA9PPZxFQfIv6mFtW0yvdgZcdi3wH7s/NAX +SVkgxPnBwUpLnJ+NvK6dRt6dxr1d5cp2ghWhryt3FOUVl60e0dbJrykOedl/rWF6 +C3fCQnyWQx8Yzq1Lk/CTfzHln9YIDkyYT0DD6ccNJ2OBECsP0+zMKMfPUBYu2vep +w3Yh826Hm+vzAoIBAEKBOI2bSOtZdGI1qhuET2d3A2qg7keKmSutPCUQNuDfT/FB +6gqOCMmTWVOWP1zONRmXoXKjpAatI4RE4KyidJALfD4Irl22RG9BBjk6ejqe66x1 +eoFDeiXWGFCgQbJgfJsHvtBk2BAWF/xX0CvGGelzP8+zqRnJNiXEeN/xmywq23Z8 +vNF9QLRNx9pExlUlB7QrNhPs+TCv3EowQ4FGpxTGNALA8VX/HmPc5i3+dUXjKDNd +ZU9de5VArKIRAgF1ZOZnye1Gc8m0rb2rlvAUBT2qgXWb8EoJGWSMu9MDNL1xcsh3 +vOID9joiF9E0lN1ugyAzshiCqPC4D55kVD7RUPMCggEBAIzg/GHcIEHMbXm/WXmd +kuIbvTLJv53+6ne9usXlQxbhd5EoUChhSIQGlNnaIfmH8gNJYzrOGBk94A5JsJwE +yhgf0f834norHw8YGQklKgz5xJPO5Uo3si7wItLkePYGQ7BwZZVJNQVLrqsotWp3 +RkImIZmP2YxvfgyyiLFeTgIwf1ECznSON9VhYnz6CJ9xBOA1Lm8huIVREFAkohaF ++Iq0hUkU1wkH1rgvMG872+2DpzOQphX8a9yhi10B2J8YEOrgdvDXUxSdv5rCZEhm +UUEyU3OwszvBhHXVr/l1uh6lOuDeOvSyDaEoHxc72N4xF6b8zMyBj7bF6p87MM0u +jI8CggEBAIzDR/EGdJEQJrm4ZFbBrWyxYzKQLCTQ3IpKVVzmaSEy6iXegsTfFbVz +TgHMWh0lUBnokoFSQrPZ5fVng1+AfZOPRToBYLwjVM7mprsmnrjGevJK9VzejOrc +O9NLOlnBL7lNUjDLl2vfzI3O9kwE0OKgfu/kjHNW4WtssQwexsu8kMLbd5sC1txI +G4uHedf6AMwty1m4jLC0MAu40N/CGbyQMfQmAG1ozinFuKTUMgN5f3TjdKMdWx6u +35hLzEgfvrNcu2/Awt6sdmfKPesbKrSBN/5I3NXfFbaI/4aUkjMKqTr2frDt6ZDJ +PqPOQDQM7f0JPuFGA9kFRaKUQygQckw= +-----END PRIVATE KEY----- diff --git a/aosp/avb/data/testkey_rsa2048.pem b/aosp/avb/data/testkey_rsa2048.pem new file mode 100644 index 00000000..867dcff5 --- /dev/null +++ b/aosp/avb/data/testkey_rsa2048.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh +4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ +gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt +DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM +uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct +YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn +SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd +jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp +z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN +mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT +o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG +zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9 +5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp +BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX +vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu +i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2 +iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW +mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY +b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy +oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A +lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF +nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT +PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A +vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow +GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL +-----END RSA PRIVATE KEY----- diff --git a/aosp/avb/data/testkey_rsa2048.pk8 b/aosp/avb/data/testkey_rsa2048.pk8 new file mode 100644 index 00000000..46b15b42 Binary files /dev/null and b/aosp/avb/data/testkey_rsa2048.pk8 differ diff --git a/aosp/avb/data/testkey_rsa4096.pem b/aosp/avb/data/testkey_rsa4096.pem new file mode 100644 index 00000000..26db5c3c --- /dev/null +++ b/aosp/avb/data/testkey_rsa4096.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKQIBAAKCAgEA2ASv49OEbH4NiT3CjNMSVeliyfEPXswWcqtEfCxlSpS1FisA +uwbvEwdTTPlkuSh6G4SYiNhnpCP5p0vcSg/3OhiuVKgV/rCtrDXaO60nvK/o0y83 +NNZRK2xaJ9eWBq9ruIDK+jC0sYWzTaqqwxY0Grjnx/r5CXerl5PrRK7PILzwgBHb +IwxHcblt1ntgR4cWVpO3wiqasEwBDDDYk4fw7W6LvjBb9qav3YB8RV6PkZNeRP64 +ggfuecq/MXNiWOPNxLzCER2hSr/+J32h9jWjXsrcVy8+8Mldhmr4r2an7c247aFf +upuFGtUJrpROO8/LXMl5gPfMpkqoatjTMRH59gJjKhot0RpmGxZBvb33TcBK5SdJ +X39Y4yct5clmDlI4Fjj7FutTP+b96aJeJVnYeUX/A0wmogBajsJRoRX5e/RcgZsY +RzXYLQXprQ81dBWjjovMJ9p8XeT6BNMFC7o6sklFL0fHDUE/l4BNP8G1u3Bfpzev +SCISRS71D4eS4oQB+RIPFBUkzomZ7rnEF3BwFeq+xmwfYrP0LRaH+1YeRauuMuRe +ke1TZl697a3mEjkNg8noa2wtpe7EWmaujJfXDWxJx/XEkjGLCe4z2qk3tkkY+A5g +Rcgzke8gVxC+eC2DJtbKYfkv4L8FMFJaEhwAp13MfC7FlYujO/BDLl7dANsCAwEA +AQKCAgAWoL8P/WsktjuSwb5sY/vKtgzcHH1Ar942GsysuTXPDy686LpF3R8T/jNy +n7k2UBAia8xSoWCR6BbRuHeV5oA+PLGeOpE7QaSfonB+yc+cy0x3Or3ssfqEsu/q +toGHp75/8DXS6WE0K04x94u1rdC9b9sPrrGBlWCLGzqM0kbuJfyHXdd3n2SofAUO +b5QRSgxD+2tHUpEroHqHnWJCaf4J0QegX45yktlfOYNK/PHLDQXV8ly/ejc32M4Y +Tv7hUtOOJTuq8VCg9OWZm2Zo1QuM9XEJTPCp5l3+o5vzO6yhk2gotDvD32CdA+3k +tLJRP54M1Sn+IXb1gGKN9rKAtGJbenWIPlNObhQgkbwG89Qd+5rfMXsiPv1Hl1tK ++tqwjD82/H3/ElaaMnwHCpeoGSp95OblAoBjzjMP2KsbvKSdL8O/rf1c3uOw9+DF +cth0SA8y3ZzI11gJtb2QMGUrCny5n4sPGGbc3x38NdLhwbkPKZy60OiT4g2kNpdY +dIitmAML2otttiF4AJM6AraPk8YVzkPLTksoL3azPBya5lIoDI2H3QvTtSvpXkXP +yKchsDSWYbdqfplqC/X0Djp2/Zd8jpN5I6+1aSmpTmbwx/JTllY1N89FRZLIdxoh +2k81LPiXhE6uRbjioJUlbnEWIpY2y2N2Clmxpjh0/IcXd1XImQKCAQEA7Zai+yjj +8xit24aO9Tf3mZBXBjSaDodjC2KS1yCcAIXp6S7aH0wZipyZpQjys3zaBQyMRYFG +bQqIfVAa6inWyDoofbAJHMu5BVcHFBPZvSS5YhDjc8XZ5dqSCxzIz9opIqAbm+b4 +aEV/3A3Jki5Dy8y/5j21GAK4Y4mqQOYzne7bDGi3Hyu041MGM4qfIcIkS5N1eHW4 +sDZJh6+K5tuxN5TX3nDZSpm9luNH8mLGgKAZ15b1LqXAtM5ycoBY9Hv082suPPom +O+r0ybdRX6nDSH8+11y2KiP2kdVIUHCGkwlqgrux5YZyjCZPwOvEPhzSoOS+vBiF +UVXA8idnxNLk1QKCAQEA6MIihDSXx+350fWqhQ/3Qc6gA/t2C15JwJ9+uFWA+gjd +c/hn5HcmnmBJN4R04nLG/aU9SQur87a4mnC/Mp9JIARjHlZ/WNT4U0sJyPEVRg5U +Z9VajAucWwi0JyJYCO1EMMy68Jp8qlTriK/L7nbD86JJ5ASxjojiN/0psK/Pk60F +Rr+shKPi3jRQ1BDjDtAxOfo4ctf/nFbUM4bY0FNPQMP7WesoSKU0NBCRR6d0d2tq +YflMjIQHx+N74P5jEdSCHTVGQm+dj47pUt3lLPLWc0bX1G/GekwXP4NUsR/70Hsi +bwxkNnK2TSGzkt2rcOnutP125rJu6WpV7SNrq9rm7wKCAQAfMROcnbWviKHqnDPQ +hdR/2K9UJTvEhInASOS2UZWpi+s1rez9BuSjigOx4wbaAZ4t44PW7C3uyt84dHfU +HkIQb3I5bg8ENMrJpK9NN33ykwuzkDwMSwFcZ+Gci97hSubzoMl/IkeiiN1MapL4 +GhLUgsD+3UMVL+Y9SymK8637IgyoCGdiND6/SXsa8SwLJo3VTjqx4eKpX7cvlSBL +RrRxc50TmwUsAhsd4CDl9YnSATLjVvJBeYlfM2tbFPaYwl1aR8v+PWkfnK0efm60 +fHki33HEnGteBPKuGq4vwVYpn6bYGwQz+f6335/A2DMfZHFSpjVURHPcRcHbCMla +0cUxAoIBAQC25eYNkO478mo+bBbEXJlkoqLmvjAyGrNFo48F9lpVH6Y0vNuWkXJN +PUgLUhAu6RYotjGENqG17rz8zt/PPY9Ok2P3sOx8t00y1mIn/hlDZXs55FM0fOMu +PZaiscAPs7HDzvyOmDah+fzi+ZD8H2M3DS2W+YE0iaeJa2vZJS2t02W0BGXiDI33 +IZDqMyLYvwwPjOnShJydEzXID4xLl0tNjzLxo3GSNA7jYqlmbtV8CXIc7rMSL6WV +ktIDKKJcnmpn3TcKeX6MEjaSIT82pNOS3fY3PmXuL+CMzfw8+u77Eecq78fHaTiL +P5JGM93F6mzi19EY0tmInUBMCWtQLcENAoIBAQCg0KaOkb8T36qzPrtgbfou0E2D +ufdpL1ugmD4edOFKQB5fDFQhLnSEVSJq3KUg4kWsXapQdsBd6kLdxS+K6MQrLBzr +4tf0c7UCF1AzWk6wXMExZ8mRb2RkGZYQB2DdyhFB3TPmnq9CW8JCq+6kxg/wkU4s +vM4JXzgcqVoSf42QJl+B9waeWhg0BTWx01lal4ds88HvEKmE0ik5GwiDbr7EvDDw +E6UbZtQcIoSTIIZDgYqVFfR2DAho3wXJRsOXh433lEJ8X7cCDzrngFbQnlKrpwML +Xgm0SIUc+Nf5poMM3rfLFK77t/ob4w+5PwRKcoSniyAxrHd6bwykYA8Vuydv +-----END RSA PRIVATE KEY----- diff --git a/aosp/avb/data/testkey_rsa4096.pk8 b/aosp/avb/data/testkey_rsa4096.pk8 new file mode 100644 index 00000000..1bdd26b5 Binary files /dev/null and b/aosp/avb/data/testkey_rsa4096.pk8 differ diff --git a/aosp/avb/data/testkey_rsa4096_pub.bin b/aosp/avb/data/testkey_rsa4096_pub.bin new file mode 100644 index 00000000..f2e8fbde Binary files /dev/null and b/aosp/avb/data/testkey_rsa4096_pub.bin differ diff --git a/aosp/avb/data/testkey_rsa4096_pub.pem b/aosp/avb/data/testkey_rsa4096_pub.pem new file mode 100644 index 00000000..efd71446 --- /dev/null +++ b/aosp/avb/data/testkey_rsa4096_pub.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2ASv49OEbH4NiT3CjNMS +VeliyfEPXswWcqtEfCxlSpS1FisAuwbvEwdTTPlkuSh6G4SYiNhnpCP5p0vcSg/3 +OhiuVKgV/rCtrDXaO60nvK/o0y83NNZRK2xaJ9eWBq9ruIDK+jC0sYWzTaqqwxY0 +Grjnx/r5CXerl5PrRK7PILzwgBHbIwxHcblt1ntgR4cWVpO3wiqasEwBDDDYk4fw +7W6LvjBb9qav3YB8RV6PkZNeRP64ggfuecq/MXNiWOPNxLzCER2hSr/+J32h9jWj +XsrcVy8+8Mldhmr4r2an7c247aFfupuFGtUJrpROO8/LXMl5gPfMpkqoatjTMRH5 +9gJjKhot0RpmGxZBvb33TcBK5SdJX39Y4yct5clmDlI4Fjj7FutTP+b96aJeJVnY +eUX/A0wmogBajsJRoRX5e/RcgZsYRzXYLQXprQ81dBWjjovMJ9p8XeT6BNMFC7o6 +sklFL0fHDUE/l4BNP8G1u3BfpzevSCISRS71D4eS4oQB+RIPFBUkzomZ7rnEF3Bw +Feq+xmwfYrP0LRaH+1YeRauuMuReke1TZl697a3mEjkNg8noa2wtpe7EWmaujJfX +DWxJx/XEkjGLCe4z2qk3tkkY+A5gRcgzke8gVxC+eC2DJtbKYfkv4L8FMFJaEhwA +p13MfC7FlYujO/BDLl7dANsCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/aosp/avb/data/testkey_rsa8192.pem b/aosp/avb/data/testkey_rsa8192.pem new file mode 100644 index 00000000..a383428a --- /dev/null +++ b/aosp/avb/data/testkey_rsa8192.pem @@ -0,0 +1,99 @@ +-----BEGIN RSA PRIVATE KEY----- +MIISKgIBAAKCBAEA0D3T+dISsmCHm797wsX0vVfqUWDJ/3mvDYozlCabDhnGLlSE +pAQbf1Z8Ts+OM4pVRHOJUJL0WebNdmPPGjsyWQz6zZE96lQZL3avCEXqYVQR66V5 +3wdK/ohaMSRnGyEMBrqkVVbF3gCr+/irxD3YK+VowO2WKs/6GrMdqTA8Y5CTF/Je +ptwsSg5MMjr6UaK4qDcrej3hkgBVGvRV3cj1snK6Br8HuYdFnpGGTS0d7UJlHFgl +trGHU/CBO923hkHgJaWEjC0giSGjhKKtLzrVcpDV2y/lWQP9T/T4djEAIaHqQ++P +SdOSR6psIGR6hVgSigt7HCnE7nW711/rfV5Ur9EiVpB040mDImKZcy8//TMnXydN +1KYTVd/34fdpzMpSw5iblErbwOLXVTUmOztYnpl41feHSv/jPesHstPlfklIF2vo +GZEohf9scQvcuM7wEBfC/aTA9K39zMmkBbcvSZjLyhmcSZWMPPOZyIcl3zY53QhW +QC/abmIcBfI1S4+r7mC4i2Jn++oEvuGNVGr2SY2Z0ZZxXGL1HI/08D/3+Tcumrcn +4YjPK/DMFi0F+e+1x41lipuf+cx/2qRNQX/m02STrLYdM6e0g33KvlnFdi2b752y +/OIaMwxDaJvunMh6EMDWKM1AHbY/ioAoK7eS26HeJLEDllqO4+SWP37c8lMvSEWy +1GiErR0HcsOj/QwWGPFseoVroMiA2sUQ0Ic/tgVjCTlXg+12XpUnouIweCi8KcL/ +ad2zJkju9hBhJLBQ/2GnivJi3lFgF4Gd//TSJ6rgWuXFfMKt/9z2Sz35ohEX4yA0 +flqlCeLInFEoevbz+XT9aRfDe65MZ79yw3TfP9CrV74hf1RRzveD4zpi3F+hcY2i +JWsH7gROZeCm6fAX5Trecd3hOxJOfA4N4rvSSCq6BwCvebT8FY25Z/VF7cQrHYDS +ij5w6lqhMzXHeUEY90Ga9AK4XzaWwGgezq+R7Zs00YSKqFv9qYNKdR7tz3cjijWf +9q/3R1uh6EQKTMZKo4SEClJiGyjOBvmPK09jMFZTJv00hDxagDPZBl7XpLDJ5/Ln +1uppvLCNWWY1zeJfaElMyq3/PqKZLidF9rVoA1SIwk2lpdUvPote2oFiwCZoXlwZ +J2ncjmXgQNs76/8unDJA0rj4JPqccw4M5GxQ7okbgm3F4rmzriCuv8BeMSCkr2ry +0mY3UhpohX4wCMq0G4x5sEUAz9FVVPZKjxnYBmLDzrJAR+4+G7gZsct01XDJYgDd +JVYInFP22/cIre8VrFWYtHbgOFdNqUiVq58de6PdZG/E+uaWmEThSlRrgEjTxupi +OXfgdKW/20j1qAtjOlqFwsY094Q5rqULQ6wPxQIDAQABAoIEAQChmkmlhrRBv42d +fYUiyxK52b8ath0saJdDz6tlXmxYDgJxM9/XlORt9oTzeDknoEO5olu+rrx4BBgQ +tzYiaiwRVXRREVTWQ7tjzRvaNL/GFkLt93XTccpuKwyrNE/bitLVagRbwcI+HZFa +MknCOihHMHoRto8h3FKAY94xzSAgODMek1WG8jhgpCXXmVNnBPt+d4oDDIDAGAfz +qgf03J5nhIb+80KgZOzPOKnbvJaL6EmlLHbgB3c42dzAw7hHtVmofYGWcvLb2MIY +DVKO435/sQx1U/8NDH6JjVdACZjLgObXH9K3/Tt46DWPEcrPLmD8xhoc6gFM+Qr0 +AhkzKoBYDNk0CljbhdIBXjktXU6wRQFZ45uP2e4JZ4zrzGBLr/t4lTavZ0SQtLld +A6kOsGh+dCWFDtnshxYnl/xad/yR+3a5zmDJbo/fJTBXrlf1B4rfQkFtK20etOPQ +B++FC/rjh3Mm/Kb/p9Gz/2upZdArH97ZvD2LBFfj77lFmAhqAi3wCRlN+ekuYxaZ +t1pBV9yXig8Dyldg1d7X8pOn2kyrF3rQUDDf4pa7x9vpnbkUlEUifoV9gnYsmdni +qDzYBtTv2g6MKqwQySXaIUW0YOBPbOellWEwxJqGYQ7y4IfVHfM0iyHnehk2tZcr ++XazLnwGe+Bz4vcguFhJXLyIu//lAOhZtbk6r1QJEUuxaOOQX3wzyceE6nkDsgmr +P5dj3Zpd7fS2VV2vyGHIFnBJ88LRxreVvgr6Q28UT27SB82zMb7mRZTVE2zeuubT +5D2D1XbZ0wBo6WiK6eRRrDQ2Haeetkj/uoRy6PWXwnAaTmmIrrXwLqaoJh/U1e+D +tfsDLWd6IxLjfXvGglrHsrtAz0oprpixUTeVhgTrGk9IQRd5rvxuGUYhFujVaYI6 ++QUf+33AFdtncb8y9C9jZmgx8AKbJk+e73SLhB5JVos+WteU7b8d/Mim5mALjnO6 +Z1n/uimsT79sSDqy3XSymtKWXo/22UlrvGCpoEuELPMb6dSFWR7vwrsvhFngY4/K +UnitnvxboEflQnaIQ4IfRLRzZsX+sC5Esqw9U5tHt4oI+91Dv3KbdbcERgV73K6B +ZQgC4lkAQquFXiZ5AICkxjiMyZwTtU9KJ7xv17Xu6oywF/3AtbVGETW1D+3maHsD +y3DASWojyqZdLj+WGzKQRa+swgCDAYKeek2fIAXFSdF63zxJ2RxOJ4GijSaoh+mr +4HVvcpDaTj+A8T1+QdByM4s98gu4GD7kVtVQGBZdWjutyHvh0hWv1gtVmbhQ/413 +gDMFFDzHIjLTYGYes4hHL22169jVR9sZ1eQxwvTIg3N4pD5cFm0rRuZZTS+oJToF +G27aBFihAoICAQDyVB62ZDnbxQthk+zITKIzRUrJbLoXrUcANcSHfaN7inF87Ova +ze7ejT9DNSEhbtfZFJ1G6diOYoSw+2MzFXv0gEkLKY0dETydKgHEu6nVq5eivMgv +D4hc9YkJMHDSlmv2FDkpL3AXCAmnW9rKp+ddttBZECnmlPEpHLoj6xgBw3pNa1Xs +IcLVfdugH86Hexj6o0oKgYfcqrX8UUHtUI2/XQqgFrIj8ksjf1fFVWJRJFWmBXqp +nMEsYarzATeM1kQ/kDeT1ZUpoGPQt02/XqXT4B5A3ATiEtpM2u+l48xtogWWg2Ry +G9l938StAmhUiW1m7GnKE6EIFvQY85WvbzxOR0JYVUSr7MrasF6nnQlhYxFuIJoJ +2h/KJQao5GCTvG4+GtbJJm4c2nyZgwyhizMsdgsdcls79aXiMkrZZkamLVUZWOtE +3pA/oBuz2qnO9HwjbH1HGOccq0TXfmpFScEV3CQGYJdno6Fy7cbmupaL4U9agQ4e +w+ygL18nq5HV++LStFnVrgs5YijjskfRdE9GUMVDh5pCsd9Y23Fymaad4O/2SRCC +YkSsyH5OvyDOLpoyUJ6g6Q+45Hqm/3lG4YjNpzFUiMcnp7+3xU35qC0LK8xEfeei +Ms1mTVEiHNIp6xH/TqRdX73WD7+YuKZSLIfRG7dgrirU6w+mhhvxD51uHQKCAgEA +2/1mBCR5qm3/0Lt++RQbeyE3tiw40UeyQqucG/+VvY77sSLkI/Lx8iwRlywXcLBn ++A4TvgukmAdWzCs8ndgKNxPA+gfohvBsMOGN9KOB1Ug5vvg2J2kiI64vwYCwzhdZ +NTUUmL+GMFHUqSsWYg6i7iBFcZmznr4W2T3bBxyTMZki7JStB86e35KXrzc2/W/b ++/p5U2HCSazDHI5mMyuClHc6GmUSVJ7f7LHjL94jviNqobp0Vj603tScHISmNrZw +TBavkvZGYXsoWKvqavk7jBB9QzaBL+unaFRslg5jTaiKnISj44Us1fjFKu84xifL +nJaEzjDPt7PBxko7LPgEY7wF39nM9VpoetI7bwR6NwDLSX8UU97MGd+HY+MO1Wi1 +pd2Lapwrx/EK7Oxz335VRK4Je0aZna4j2TyQdMJac9fsGPXv4ZsLfDLj/wD6l1j+ +lLLbBv3ImdSj32LBbhsgF4iCGeXO8HpPO+Q/h9XVsnY52Um2XdNMn03PCGm6ZvtM +7DXiS+lPF90HjolJVHZTBNtdVRrLr53zLuWEfqT4FeKrDaxdtiXkxLjrB+5/VYu7 +ntyk01ZQ63VNfEwS1irmKl9+qZkTHk3HHV9jNV5RzWViwmJI7Wpr1YzBwmcKCB1O +oGUADDs8QpnkCz0xkMVtYwHj9qKZlqfbHzrFDUUcF8kCggIAdYvUcgjf//ju8mA8 +5VQ3AcPE6TvycPW+kR2DvW12VcDsF/sc1UA7dHzziPhGn98SmNxlBjb8suSbFPZ8 +QhVT0WBBDkcTilwIGPx9ax7U3S6lGW2VdS6FqQH5fRmgQKZyrCVXLOEz8BgYBrSJ +xu/3TQAWxH0QtibdbGHg8Pdi58gYlWFRhn9B8Slh1aRYHGPb1AhNLBd0/ddY+5G2 +9xSyDXdmZg1cUA+B3zAwNSqbzFxhp2zU+V1uXsbpk4KtnYV6CZM9QlrCRjTk9iNU +dVXF/qaiRjfzrm4SsmEpCkEbsrp7F22Y1bkooORglMOsNAWNqfVXw4wN+syXj1ro +6vZ8PERYrFyAOR1dsQMIhymnmTPjCpaJ4emKrhWTy20sY71thHakZWJc22YoNpbZ +E6tgIVsJPTlxg/4+fyCCKj5wWr92nhsB1KBZPGO/zFhvMlJpvQ0tH8W2pbN2a0mI +5x9FqALm/qjwCHfZItSwPM+ZozSht3cOkGHdcD5KXAXfcfsDJc4SHZKVIzq4NusN +504R/jvD1GP8sglyG7omp75ckgzAmakLdxOP2HhQvIX9tcXpSirNJ6Sl2bwKuuMF +wxo3r/o/9Y97e4LlfpEYp9eqMdcG+NpR993IwK0UhAWS9H5wdnWBSUHd5e4xtDUt +iILNRuO46g7R/AIhz1cSSraWWQkCggIBAMhhPP5C9yt9PIm1b0eTwCBctnFSQIKo +KsA9rll2ab+bMLk9jc8M6MLszy0CtWso09sHf4YY9tifvrkEHRethEh8zscwUuYu +sm2n1fTixk0ul6LSVgl54uXbMJayENn4PIKRkew8cA8tSma43497w37hmD+MgCb1 +ALzqcco9hfmkgkI6fo1g8Ce3UEECKy2YKSmREdgYcK9JFQO61W6AkFWJcDxAmfzI +JjFkKwsb7TSw79zWiEdSoM9jm7sCPKATd6Bm/ZAAkUUTuEFkfobn9Ax1rJN/Xxb2 +MKuAUtQv0NYY0gEVdG62jItuKLId6nncH8PG+rsRjPLIYpWqYdJpKx5pUnR+4AkQ +S6CsRASwcF4PdBvDDBIFG6XpjFo4pPdQhDzL2sTF8b8SWSBLlJQbb7G6UNqgCSau +SusCFpazvU5NfDmUMuctob2EYVaSXq9jGaj6bTUmDwXHwWilfIk9XfLxnYfXYrJ6 +xhdIpXGmHhuLQtAgK2O1JtLoPc9s9qP8/SkfP7xjjG6xHsP/WvL7QE1pPs9ZM/UI +C01JNHFi9LKCn8o5mbZjN8jUowi7ffK+76wZUG1L7zM5ytWQOYwo0TQBfc8fpmFw ++RBRJX2kJyDO27ExczoGOKjwqEDaODIB9+9zcCK0BgSoRibSm4ZBvoxzWWD65Kls +xdPhZUHcFGW5AoICAQC8iG27aD8aRUt94Oek66gFOJx84QVZehWPqtZjWyVenDuc +T8dink8oejGjcK2UJuQDa83azv90ocVqE0n0ronYyszt9Ib1jlYC+CK1Ar9TYGFg +WU5OWEDyCzCpqW/w/aG68U8qhKm0MvkLJR+G6evan9TwEhFEVAm3iWllNXs9x29s +BucwyMMC23zsimxYlS7dA4DtyvVA+zL1omLpSWHbU/qtuI3HV1NeJzsy+gC4mwPh +j52tdl669fyWLzHzBRLeq6dVOedjnCo+jlU3dL20DEk9SaW08D1CPuZekV1jVPMw +JoaDcIRh4KLtQ0BYZ7UJeFUTsx1CS/+UqzqYSPOi57a5kvr0Y8YwRnSB8dHVFttX +JTv83wTQXHPFSBgfnHNe7lsRTfIQfuIkr2bpiU7h85UQ7LsqcI6YHaC07URcsGFF +FrLWGh91qzAd1diSHla2RnY3n8PPuMnCkguNhLUrYdmyMol7FfWFa9lwplsuTzBq +B6yj8iaiE3LL+Q/eulJ7S6QPfAI2bU0UJO23Y4koeoIibEEDMSCQ6KYZ2NClRRRT +ga5fS1YfkDFEcHUQ1/KIkdYHGBKBjoKGExzi8+CgiSySVSYDZl6wIOhLjH2OZ3ol +ldPN7iNAHirrxg9v8QO6OQlpLUk5Lhp/1dSlZ6sy3UjFqvax3tw6ZjrL88YP5g== +-----END RSA PRIVATE KEY----- diff --git a/aosp/avb/data/testkey_rsa8192.pk8 b/aosp/avb/data/testkey_rsa8192.pk8 new file mode 100644 index 00000000..3890c1b7 Binary files /dev/null and b/aosp/avb/data/testkey_rsa8192.pk8 differ diff --git a/aosp/avb/test/vts-testcase/security/avb/data/Android.bp b/aosp/avb/test/vts-testcase/security/avb/data/Android.bp new file mode 100644 index 00000000..9c75677e --- /dev/null +++ b/aosp/avb/test/vts-testcase/security/avb/data/Android.bp @@ -0,0 +1,45 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +filegroup { + name: "q-gsi_avbpubkey", + srcs: [ + "q-gsi.avbpubkey", + ], +} + +filegroup { + name: "r-gsi_avbpubkey", + srcs: [ + "r-gsi.avbpubkey", + ], +} + +filegroup { + name: "s-gsi_avbpubkey", + srcs: [ + "s-gsi.avbpubkey", + ], +} + +filegroup { + name: "t-gsi_avbpubkey", + srcs: [ + "t-gsi.avbpubkey", + ], +} + +filegroup { + name: "qcar-gsi_avbpubkey", + srcs: [ + "qcar-gsi.avbpubkey", + ], +} + +filegroup { + name: "kernel_version_matrix.textproto", + srcs: [ + "kernel_version_matrix.textproto" + ], +} diff --git a/aosp/avb/test/vts-testcase/security/avb/data/kernel_version_matrix.textproto b/aosp/avb/test/vts-testcase/security/avb/data/kernel_version_matrix.textproto new file mode 100644 index 00000000..05b27f86 --- /dev/null +++ b/aosp/avb/test/vts-testcase/security/avb/data/kernel_version_matrix.textproto @@ -0,0 +1,91 @@ +# Copyright (C) 2023 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# https://source.android.com/docs/core/architecture/kernel/android-common#compatibility-matrix +release_requirements: [ + { + # Android 13 + # This is the minimum SDK level where we start enforcing the matrix. + key: 33 + value { + upgrade: [ + # -q, -stable are dropped from non-GKI kernel release formats because the kernel branch + # cannot be looked up anywhere. + "android-4.9", + "android-4.14", + "android-4.19", + "android11-5.4", + "android12-5.4", + "android12-5.10", + "android13-5.10", + "android13-5.15" + ] + launch: [ + "android11-5.4", + "android12-5.4", + "android12-5.10", + "android13-5.10", + "android13-5.15" + ] + launch_grf: [ + # -q, -stable are dropped from non-GKI kernel release formats because the kernel branch + # cannot be looked up anywhere. + "android-4.19", + "android11-5.4", + "android12-5.4", + "android12-5.10", + "android13-5.10", + "android13-5.15" + ] + } + }, + { + # Android 14 + key: 34 + value { + upgrade: [ + # -q, -stable are dropped from non-GKI kernel release formats because the kernel branch + # cannot be looked up anywhere. + "android-4.14", + "android-4.19", + "android11-5.4", + "android12-5.4", + "android12-5.10", + "android13-5.10", + "android13-5.15", + "android14-5.15", + "android14-6.1" + ] + launch: [ + "android12-5.10", + "android13-5.10", + "android13-5.15", + "android14-5.15", + "android14-6.1" + ] + launch_grf: [ + # -q, -stable are dropped from non-GKI kernel release formats because the kernel branch + # cannot be looked up anywhere. + "android-4.19", + "android11-5.4", + "android12-5.4", + "android12-5.10", + "android13-5.10", + "android13-5.15", + "android14-5.15", + "android14-6.1" + ] + } + } +] diff --git a/aosp/avb/test/vts-testcase/security/avb/data/q-gsi.avbpubkey b/aosp/avb/test/vts-testcase/security/avb/data/q-gsi.avbpubkey new file mode 100644 index 00000000..5ed7543b Binary files /dev/null and b/aosp/avb/test/vts-testcase/security/avb/data/q-gsi.avbpubkey differ diff --git a/aosp/avb/test/vts-testcase/security/avb/data/qcar-gsi.avbpubkey b/aosp/avb/test/vts-testcase/security/avb/data/qcar-gsi.avbpubkey new file mode 100644 index 00000000..ce56646d Binary files /dev/null and b/aosp/avb/test/vts-testcase/security/avb/data/qcar-gsi.avbpubkey differ diff --git a/aosp/avb/test/vts-testcase/security/avb/data/r-gsi.avbpubkey b/aosp/avb/test/vts-testcase/security/avb/data/r-gsi.avbpubkey new file mode 100644 index 00000000..2609b303 Binary files /dev/null and b/aosp/avb/test/vts-testcase/security/avb/data/r-gsi.avbpubkey differ diff --git a/aosp/avb/test/vts-testcase/security/avb/data/s-gsi.avbpubkey b/aosp/avb/test/vts-testcase/security/avb/data/s-gsi.avbpubkey new file mode 100644 index 00000000..9065fb83 Binary files /dev/null and b/aosp/avb/test/vts-testcase/security/avb/data/s-gsi.avbpubkey differ diff --git a/aosp/avb/test/vts-testcase/security/avb/data/t-gsi.avbpubkey b/aosp/avb/test/vts-testcase/security/avb/data/t-gsi.avbpubkey new file mode 100644 index 00000000..de5991e6 Binary files /dev/null and b/aosp/avb/test/vts-testcase/security/avb/data/t-gsi.avbpubkey differ diff --git a/aosp/boot_signer/build/libs/boot_signer.jar b/aosp/boot_signer/build/libs/boot_signer.jar new file mode 100644 index 00000000..c260967c Binary files /dev/null and b/aosp/boot_signer/build/libs/boot_signer.jar differ diff --git a/aosp/dispol/Makefile b/aosp/dispol/Makefile new file mode 100644 index 00000000..0987b9a9 --- /dev/null +++ b/aosp/dispol/Makefile @@ -0,0 +1,24 @@ +.PHONY: all checkpolicy libsepol prepare +nothing: + @echo "Nothing to do" + +checkpolicy: export CFLAGS := -g -Wall -Werror -Wshadow -pipe -fno-strict-aliasing -I$(CURDIR)/libsepol-3.2/include +checkpolicy: export LIBSEPOLA := $(CURDIR)/libsepol-3.2/src/libsepol.a +checkpolicy: export LDLIBS_LIBSEPOLA := -l:libsepol.a -L$(CURDIR)/libsepol-3.2/src +checkpolicy: libsepol + make -C checkpolicy-3.2 -j + cp checkpolicy-3.2/test/dispol . + +libsepol: + make -C libsepol-3.2 -j + +prepare: + rm -fr libsepol-3.2 checkpolicy-3.2 + wget https://github.com/SELinuxProject/selinux/releases/download/3.2/libsepol-3.2.tar.gz + wget https://github.com/SELinuxProject/selinux/releases/download/3.2/checkpolicy-3.2.tar.gz + tar xaf checkpolicy-3.2.tar.gz + tar xaf libsepol-3.2.tar.gz + +all: checkpolicy libsepol prepare +# vim:ft=make +# diff --git a/aosp/dispol/README.md b/aosp/dispol/README.md new file mode 100644 index 00000000..9a75fbf9 --- /dev/null +++ b/aosp/dispol/README.md @@ -0,0 +1,7 @@ +# dispol +decompile binary selinux policy file +Android sepolicy file is `/sys/fs/selinux/policy` +``` +make all +./dispol +``` diff --git a/aosp/dracut/README.md b/aosp/dracut/README.md new file mode 100644 index 00000000..20164f6b --- /dev/null +++ b/aosp/dracut/README.md @@ -0,0 +1 @@ +https://github.com/dracutdevs/dracut/tree/master/skipcpio diff --git a/aosp/dracut/skipcpio.c b/aosp/dracut/skipcpio.c new file mode 100644 index 00000000..30699816 --- /dev/null +++ b/aosp/dracut/skipcpio.c @@ -0,0 +1,122 @@ +/* dracut-install.c -- install files and executables + + Copyright (C) 2012 Harald Hoyer + Copyright (C) 2012 Red Hat, Inc. All rights reserved. + + This program is free software: you can redistribute it and/or modify + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see . +*/ + +#define PROGRAM_VERSION_STRING "1" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + +#define CPIO_END "TRAILER!!!" +#define CPIO_ENDLEN (sizeof(CPIO_END)-1) + +static char buf[CPIO_ENDLEN * 2 + 1]; + +int main(int argc, char **argv) +{ + FILE *f; + size_t s; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + f = fopen(argv[1], "r"); + + if (f == NULL) { + fprintf(stderr, "Cannot open file '%s'\n", argv[1]); + exit(1); + } + + s = fread(buf, 6, 1, f); + if (s <= 0) { + fprintf(stderr, "Read error from file '%s'\n", argv[1]); + fclose(f); + exit(1); + } + fseek(f, 0, SEEK_SET); + + /* check, if this is a cpio archive */ + if (buf[0] == '0' && buf[1] == '7' && buf[2] == '0' && buf[3] == '7' && buf[4] == '0' && buf[5] == '1') { + long pos = 0; + + /* Search for CPIO_END */ + do { + char *h; + fseek(f, pos, SEEK_SET); + buf[sizeof(buf) - 1] = 0; + s = fread(buf, CPIO_ENDLEN, 2, f); + if (s <= 0) + break; + + h = strstr(buf, CPIO_END); + if (h) { + pos = (h - buf) + pos + CPIO_ENDLEN; + fseek(f, pos, SEEK_SET); + break; + } + pos += CPIO_ENDLEN; + } while (!feof(f)); + + if (feof(f)) { + /* CPIO_END not found, just cat the whole file */ + fseek(f, 0, SEEK_SET); + } else { + /* skip zeros */ + while (!feof(f)) { + size_t i; + + buf[sizeof(buf) - 1] = 0; + s = fread(buf, 1, sizeof(buf) - 1, f); + if (s <= 0) + break; + + for (i = 0; (i < s) && (buf[i] == 0); i++) ; + + if (buf[i] != 0) { + pos += i; + fseek(f, pos, SEEK_SET); + break; + } + + pos += s; + } + } + } + /* cat out the rest */ + while (!feof(f)) { + s = fread(buf, 1, sizeof(buf), f); + if (s <= 0) + break; + + s = fwrite(buf, 1, s, stdout); + if (s <= 0) + break; + } + fclose(f); + + return EXIT_SUCCESS; +} diff --git a/aosp/libsparse/append2simg/build.gradle.kts b/aosp/libsparse/append2simg/build.gradle.kts new file mode 100644 index 00000000..54840282 --- /dev/null +++ b/aosp/libsparse/append2simg/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `cpp-application` +} + +application { + targetMachines.set(listOf(machines.linux.x86_64, machines.macOS.x86_64)) + dependencies { + implementation(project(":aosp:libsparse:sparse")) + } +} + +tasks.withType(LinkExecutable::class.java).configureEach { + linkerArgs.add("-lz") +} + +tasks.withType(CppCompile::class.java).configureEach { + compilerArgs.add("-std=c++17") +} diff --git a/aosp/libsparse/append2simg/src/main/cpp/append2simg.cpp b/aosp/libsparse/append2simg/src/main/cpp/append2simg.cpp new file mode 100644 index 00000000..99f43398 --- /dev/null +++ b/aosp/libsparse/append2simg/src/main/cpp/append2simg.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE64_SOURCE 1 + +#include +#include +#include +#include +#include +#include + +#include +#include "backed_block.h" +#include "sparse_file.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#define lseek64 lseek +#endif +#if defined(__APPLE__) && defined(__MACH__) +#define lseek64 lseek +#define off64_t off_t +#endif + +void usage() { + fprintf(stderr, "Usage: append2simg \n"); +} + +int main(int argc, char* argv[]) { + int output; + int output_block; + char* output_path; + struct sparse_file* sparse_output; + + int input; + char* input_path; + off64_t input_len; + + int tmp_fd; + char* tmp_path; + + int ret; + + if (argc == 3) { + output_path = argv[1]; + input_path = argv[2]; + } else { + usage(); + exit(-1); + } + + ret = asprintf(&tmp_path, "%s.append2simg", output_path); + if (ret < 0) { + fprintf(stderr, "Couldn't allocate filename\n"); + exit(-1); + } + + output = open(output_path, O_RDWR | O_BINARY); + if (output < 0) { + fprintf(stderr, "Couldn't open output file (%s)\n", strerror(errno)); + exit(-1); + } + + sparse_output = sparse_file_import_auto(output, false, true); + if (!sparse_output) { + fprintf(stderr, "Couldn't import output file\n"); + exit(-1); + } + + input = open(input_path, O_RDONLY | O_BINARY); + if (input < 0) { + fprintf(stderr, "Couldn't open input file (%s)\n", strerror(errno)); + exit(-1); + } + + input_len = lseek64(input, 0, SEEK_END); + if (input_len < 0) { + fprintf(stderr, "Couldn't get input file length (%s)\n", strerror(errno)); + exit(-1); + } else if (input_len % sparse_output->block_size) { + fprintf(stderr, "Input file is not a multiple of the output file's block size"); + exit(-1); + } + lseek64(input, 0, SEEK_SET); + + output_block = sparse_output->len / sparse_output->block_size; + if (sparse_file_add_fd(sparse_output, input, 0, input_len, output_block) < 0) { + fprintf(stderr, "Couldn't add input file\n"); + exit(-1); + } + sparse_output->len += input_len; + + tmp_fd = open(tmp_path, O_WRONLY | O_CREAT | O_BINARY, 0664); + if (tmp_fd < 0) { + fprintf(stderr, "Couldn't open temporary file (%s)\n", strerror(errno)); + exit(-1); + } + + lseek64(output, 0, SEEK_SET); + if (sparse_file_write(sparse_output, tmp_fd, false, true, false) < 0) { + fprintf(stderr, "Failed to write sparse file\n"); + exit(-1); + } + + sparse_file_destroy(sparse_output); + close(tmp_fd); + close(output); + close(input); + + ret = rename(tmp_path, output_path); + if (ret < 0) { + fprintf(stderr, "Failed to rename temporary file (%s)\n", strerror(errno)); + exit(-1); + } + + free(tmp_path); + + exit(0); +} diff --git a/aosp/libsparse/base/build.gradle.kts b/aosp/libsparse/base/build.gradle.kts new file mode 100644 index 00000000..92a1fe46 --- /dev/null +++ b/aosp/libsparse/base/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `cpp-library` +} + +library { + targetMachines.set(listOf(machines.linux.x86_64, machines.macOS.x86_64)) + linkage.set(listOf(Linkage.STATIC)) +} + +extensions.configure { + source.from(file("src/main/cpp")) + privateHeaders.from(file("src/main/headers")) + publicHeaders.from(file("src/main/public")) +} + +tasks.withType(CppCompile::class.java).configureEach { + compilerArgs.add("-std=c++17") +} diff --git a/aosp/libsparse/base/src/main/cpp/mapped_file.cpp b/aosp/libsparse/base/src/main/cpp/mapped_file.cpp new file mode 100644 index 00000000..fff34538 --- /dev/null +++ b/aosp/libsparse/base/src/main/cpp/mapped_file.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android-base/mapped_file.h" + +#include + +#include + +namespace android { +namespace base { + +static constexpr char kEmptyBuffer[] = {'0'}; + +static off64_t InitPageSize() { +#if defined(_WIN32) + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwAllocationGranularity; +#else + return sysconf(_SC_PAGE_SIZE); +#endif +} + +std::unique_ptr MappedFile::FromFd(borrowed_fd fd, off64_t offset, size_t length, + int prot) { +#if defined(_WIN32) + return FromOsHandle(reinterpret_cast(_get_osfhandle(fd.get())), offset, length, prot); +#else + return FromOsHandle(fd.get(), offset, length, prot); +#endif +} + +std::unique_ptr MappedFile::FromOsHandle(os_handle h, off64_t offset, size_t length, + int prot) { + static const off64_t page_size = InitPageSize(); + size_t slop = offset % page_size; + off64_t file_offset = offset - slop; + off64_t file_length = length + slop; + +#if defined(_WIN32) + HANDLE handle = CreateFileMappingW( + h, nullptr, (prot & PROT_WRITE) ? PAGE_READWRITE : PAGE_READONLY, 0, 0, nullptr); + if (handle == nullptr) { + // http://b/119818070 "app crashes when reading asset of zero length". + // Return a MappedFile that's only valid for reading the size. + if (length == 0 && ::GetLastError() == ERROR_FILE_INVALID) { + return std::unique_ptr( + new MappedFile(const_cast(kEmptyBuffer), 0, 0, nullptr)); + } + return nullptr; + } + void* base = MapViewOfFile(handle, (prot & PROT_WRITE) ? FILE_MAP_ALL_ACCESS : FILE_MAP_READ, 0, + file_offset, file_length); + if (base == nullptr) { + CloseHandle(handle); + return nullptr; + } + return std::unique_ptr( + new MappedFile(static_cast(base), length, slop, handle)); +#else + void* base = mmap(nullptr, file_length, prot, MAP_SHARED, h, file_offset); + if (base == MAP_FAILED) { + // http://b/119818070 "app crashes when reading asset of zero length". + // mmap fails with EINVAL for a zero length region. + if (errno == EINVAL && length == 0) { + return std::unique_ptr(new MappedFile(const_cast(kEmptyBuffer), 0, 0)); + } + return nullptr; + } + return std::unique_ptr(new MappedFile(static_cast(base), length, slop)); +#endif +} + +MappedFile::MappedFile(MappedFile&& other) + : base_(std::exchange(other.base_, nullptr)), + size_(std::exchange(other.size_, 0)), + offset_(std::exchange(other.offset_, 0)) +#ifdef _WIN32 + , + handle_(std::exchange(other.handle_, nullptr)) +#endif +{ +} + +MappedFile& MappedFile::operator=(MappedFile&& other) { + Close(); + base_ = std::exchange(other.base_, nullptr); + size_ = std::exchange(other.size_, 0); + offset_ = std::exchange(other.offset_, 0); +#ifdef _WIN32 + handle_ = std::exchange(other.handle_, nullptr); +#endif + return *this; +} + +MappedFile::~MappedFile() { + Close(); +} + +void MappedFile::Close() { +#if defined(_WIN32) + if (base_ != nullptr && size_ != 0) UnmapViewOfFile(base_); + if (handle_ != nullptr) CloseHandle(handle_); + handle_ = nullptr; +#else + if (base_ != nullptr && size_ != 0) munmap(base_, size_ + offset_); +#endif + + base_ = nullptr; + offset_ = size_ = 0; +} + +} // namespace base +} // namespace android diff --git a/aosp/libsparse/base/src/main/cpp/stringprintf.cpp b/aosp/libsparse/base/src/main/cpp/stringprintf.cpp new file mode 100644 index 00000000..d12c9a2c --- /dev/null +++ b/aosp/libsparse/base/src/main/cpp/stringprintf.cpp @@ -0,0 +1,85 @@ +/* + * ===================================================================================== + * + * Filename: stringprintf.cpp + * + * Description: + * + * Version: 1.0 + * Created: 08/14/2019 11:09:23 AM + * Revision: none + * Compiler: gcc + * + * Author: YOUR NAME (), + * Organization: + * + * ===================================================================================== + */ + +#include +#include +#include + +namespace android { +namespace base { + +void StringAppendV(std::string* dst, const char* format, va_list ap) { + // First try with a small fixed size buffer + char space[1024]; + + // It's possible for methods that use a va_list to invalidate + // the data in it upon use. The fix is to make a copy + // of the structure before using it and use that copy instead. + va_list backup_ap; + va_copy(backup_ap, ap); + int result = vsnprintf(space, sizeof(space), format, backup_ap); + va_end(backup_ap); + + if (result < static_cast(sizeof(space))) { + if (result >= 0) { + // Normal case -- everything fit. + dst->append(space, result); + return; + } + + if (result < 0) { + // Just an error. + return; + } + } + + // Increase the buffer size to the size requested by vsnprintf, + // plus one for the closing \0. + int length = result + 1; + char* buf = new char[length]; + + // Restore the va_list before we use it again + va_copy(backup_ap, ap); + result = vsnprintf(buf, length, format, backup_ap); + va_end(backup_ap); + + if (result >= 0 && result < length) { + // It fit + dst->append(buf, result); + } + delete[] buf; +} + +std::string StringPrintf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string result; + StringAppendV(&result, fmt, ap); + va_end(ap); + return result; +} + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +} // namespace base +} // namespace android diff --git a/aosp/libsparse/base/src/main/public/android-base/macros.h b/aosp/libsparse/base/src/main/public/android-base/macros.h new file mode 100644 index 00000000..546b2ec1 --- /dev/null +++ b/aosp/libsparse/base/src/main/public/android-base/macros.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include // for size_t +#include // for TEMP_FAILURE_RETRY + +#include + +// bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't. +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(exp) \ + ({ \ + decltype(exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ + }) +#endif + +// A macro to disallow the copy constructor and operator= functions +// This must be placed in the private: declarations for a class. +// +// For disallowing only assign or copy, delete the relevant operator or +// constructor, for example: +// void operator=(const TypeName&) = delete; +// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken +// semantically, one should either use disallow both or neither. Try to +// avoid these in new code. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char(&ArraySizeHelper(T(&array)[N]))[N]; // NOLINT(readability/casting) + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +#define SIZEOF_MEMBER(t, f) sizeof(std::declval().f) + +// Changing this definition will cause you a lot of pain. A majority of +// vendor code defines LIKELY and UNLIKELY this way, and includes +// this header through an indirect path. +#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) +#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) + +#define WARN_UNUSED __attribute__((warn_unused_result)) + +// A deprecated function to call to create a false use of the parameter, for +// example: +// int foo(int x) { UNUSED(x); return 10; } +// to avoid compiler warnings. Going forward we prefer ATTRIBUTE_UNUSED. +template +void UNUSED(const T&...) { +} + +// An attribute to place on a parameter to a function, for example: +// int foo(int x ATTRIBUTE_UNUSED) { return 10; } +// to avoid compiler warnings. +#define ATTRIBUTE_UNUSED __attribute__((__unused__)) + +// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through +// between switch labels: +// switch (x) { +// case 40: +// case 41: +// if (truth_is_out_there) { +// ++x; +// FALLTHROUGH_INTENDED; // Use instead of/along with annotations in +// // comments. +// } else { +// return x; +// } +// case 42: +// ... +// +// As shown in the example above, the FALLTHROUGH_INTENDED macro should be +// followed by a semicolon. It is designed to mimic control-flow statements +// like 'break;', so it can be placed in most places where 'break;' can, but +// only if there are no statements on the execution path between it and the +// next switch label. +// +// When compiled with clang, the FALLTHROUGH_INTENDED macro is expanded to +// [[clang::fallthrough]] attribute, which is analysed when performing switch +// labels fall-through diagnostic ('-Wimplicit-fallthrough'). See clang +// documentation on language extensions for details: +// http://clang.llvm.org/docs/LanguageExtensions.html#clang__fallthrough +// +// When used with unsupported compilers, the FALLTHROUGH_INTENDED macro has no +// effect on diagnostics. +// +// In either case this macro has no effect on runtime behavior and performance +// of code. +#ifndef FALLTHROUGH_INTENDED +#define FALLTHROUGH_INTENDED [[clang::fallthrough]] // NOLINT +#endif + +// Current ABI string +#if defined(__arm__) +#define ABI_STRING "arm" +#elif defined(__aarch64__) +#define ABI_STRING "arm64" +#elif defined(__i386__) +#define ABI_STRING "x86" +#elif defined(__x86_64__) +#define ABI_STRING "x86_64" +#endif diff --git a/aosp/libsparse/base/src/main/public/android-base/mapped_file.h b/aosp/libsparse/base/src/main/public/android-base/mapped_file.h new file mode 100644 index 00000000..8c37f430 --- /dev/null +++ b/aosp/libsparse/base/src/main/public/android-base/mapped_file.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +#include "android-base/macros.h" +#include "android-base/off64_t.h" +#include "android-base/unique_fd.h" + +#if defined(_WIN32) +#include +#define PROT_READ 1 +#define PROT_WRITE 2 +using os_handle = HANDLE; +#else +#include +using os_handle = int; +#endif + +namespace android { +namespace base { + +/** + * A region of a file mapped into memory (for grepping: also known as MmapFile or file mapping). + */ +class MappedFile { + public: + /** + * Creates a new mapping of the file pointed to by `fd`. Unlike the underlying OS primitives, + * `offset` does not need to be page-aligned. If `PROT_WRITE` is set in `prot`, the mapping + * will be writable, otherwise it will be read-only. Mappings are always `MAP_SHARED`. + */ + static std::unique_ptr FromFd(borrowed_fd fd, off64_t offset, size_t length, + int prot); + + /** + * Same thing, but using the raw OS file handle instead of a CRT wrapper. + */ + static std::unique_ptr FromOsHandle(os_handle h, off64_t offset, size_t length, + int prot); + + /** + * Removes the mapping. + */ + ~MappedFile(); + + /** + * Not copyable but movable. + */ + MappedFile(MappedFile&& other); + MappedFile& operator=(MappedFile&& other); + + char* data() const { return base_ + offset_; } + size_t size() const { return size_; } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(MappedFile); + + void Close(); + + char* base_; + size_t size_; + + size_t offset_; + +#if defined(_WIN32) + MappedFile(char* base, size_t size, size_t offset, HANDLE handle) + : base_(base), size_(size), offset_(offset), handle_(handle) {} + HANDLE handle_; +#else + MappedFile(char* base, size_t size, size_t offset) : base_(base), size_(size), offset_(offset) {} +#endif +}; + +} // namespace base +} // namespace android diff --git a/aosp/libsparse/base/src/main/public/android-base/off64_t.h b/aosp/libsparse/base/src/main/public/android-base/off64_t.h new file mode 100644 index 00000000..e6b71b81 --- /dev/null +++ b/aosp/libsparse/base/src/main/public/android-base/off64_t.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#if defined(__APPLE__) +/** Mac OS has always had a 64-bit off_t, so it doesn't have off64_t. */ +typedef off_t off64_t; +#endif diff --git a/aosp/libsparse/base/src/main/public/android-base/stringprintf.h b/aosp/libsparse/base/src/main/public/android-base/stringprintf.h new file mode 100644 index 00000000..93c56afd --- /dev/null +++ b/aosp/libsparse/base/src/main/public/android-base/stringprintf.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace android { +namespace base { + +// These printf-like functions are implemented in terms of vsnprintf, so they +// use the same attribute for compile-time format string checking. + +// Returns a string corresponding to printf-like formatting of the arguments. +std::string StringPrintf(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendF(std::string* dst, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendV(std::string* dst, const char* format, va_list ap) + __attribute__((__format__(__printf__, 2, 0))); + +} // namespace base +} // namespace android diff --git a/aosp/libsparse/base/src/main/public/android-base/unique_fd.h b/aosp/libsparse/base/src/main/public/android-base/unique_fd.h new file mode 100644 index 00000000..9ceb5dbd --- /dev/null +++ b/aosp/libsparse/base/src/main/public/android-base/unique_fd.h @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#if !defined(_WIN32) +#include +#endif + +#include +#include +#include + +// DO NOT INCLUDE OTHER LIBBASE HEADERS! +// This file gets used in libbinder, and libbinder is used everywhere. +// Including other headers from libbase frequently results in inclusion of +// android-base/macros.h, which causes macro collisions. + +// Container for a file descriptor that automatically closes the descriptor as +// it goes out of scope. +// +// unique_fd ufd(open("/some/path", "r")); +// if (ufd.get() == -1) return error; +// +// // Do something useful, possibly including 'return'. +// +// return 0; // Descriptor is closed for you. +// +// unique_fd is also known as ScopedFd/ScopedFD/scoped_fd; mentioned here to help +// you find this class if you're searching for one of those names. + +#if defined(__BIONIC__) +#include +#endif + +namespace android { +namespace base { + +struct DefaultCloser { +#if defined(__BIONIC__) + static void Tag(int fd, void* old_addr, void* new_addr) { + if (android_fdsan_exchange_owner_tag) { + uint64_t old_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD, + reinterpret_cast(old_addr)); + uint64_t new_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD, + reinterpret_cast(new_addr)); + android_fdsan_exchange_owner_tag(fd, old_tag, new_tag); + } + } + static void Close(int fd, void* addr) { + if (android_fdsan_close_with_tag) { + uint64_t tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD, + reinterpret_cast(addr)); + android_fdsan_close_with_tag(fd, tag); + } else { + close(fd); + } + } +#else + static void Close(int fd) { + // Even if close(2) fails with EINTR, the fd will have been closed. + // Using TEMP_FAILURE_RETRY will either lead to EBADF or closing someone + // else's fd. + // http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html + ::close(fd); + } +#endif +}; + +template +class unique_fd_impl final { + public: + unique_fd_impl() {} + + explicit unique_fd_impl(int fd) { reset(fd); } + ~unique_fd_impl() { reset(); } + + unique_fd_impl(const unique_fd_impl&) = delete; + void operator=(const unique_fd_impl&) = delete; + unique_fd_impl(unique_fd_impl&& other) noexcept { reset(other.release()); } + unique_fd_impl& operator=(unique_fd_impl&& s) noexcept { + int fd = s.fd_; + s.fd_ = -1; + reset(fd, &s); + return *this; + } + + [[clang::reinitializes]] void reset(int new_value = -1) { reset(new_value, nullptr); } + + int get() const { return fd_; } + +#if !defined(ANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION) + // unique_fd's operator int is dangerous, but we have way too much code that + // depends on it, so make this opt-in at first. + operator int() const { return get(); } // NOLINT +#endif + + bool operator>=(int rhs) const { return get() >= rhs; } + bool operator<(int rhs) const { return get() < rhs; } + bool operator==(int rhs) const { return get() == rhs; } + bool operator!=(int rhs) const { return get() != rhs; } + bool operator==(const unique_fd_impl& rhs) const { return get() == rhs.get(); } + bool operator!=(const unique_fd_impl& rhs) const { return get() != rhs.get(); } + + // Catch bogus error checks (i.e.: "!fd" instead of "fd != -1"). + bool operator!() const = delete; + + bool ok() const { return get() >= 0; } + + int release() __attribute__((warn_unused_result)) { + tag(fd_, this, nullptr); + int ret = fd_; + fd_ = -1; + return ret; + } + + private: + void reset(int new_value, void* previous_tag) { + int previous_errno = errno; + + if (fd_ != -1) { + close(fd_, this); + } + + fd_ = new_value; + if (new_value != -1) { + tag(new_value, previous_tag, this); + } + + errno = previous_errno; + } + + int fd_ = -1; + + // Template magic to use Closer::Tag if available, and do nothing if not. + // If Closer::Tag exists, this implementation is preferred, because int is a better match. + // If not, this implementation is SFINAEd away, and the no-op below is the only one that exists. + template + static auto tag(int fd, void* old_tag, void* new_tag) + -> decltype(T::Tag(fd, old_tag, new_tag), void()) { + T::Tag(fd, old_tag, new_tag); + } + + template + static void tag(long, void*, void*) { + // No-op. + } + + // Same as above, to select between Closer::Close(int) and Closer::Close(int, void*). + template + static auto close(int fd, void* tag_value) -> decltype(T::Close(fd, tag_value), void()) { + T::Close(fd, tag_value); + } + + template + static auto close(int fd, void*) -> decltype(T::Close(fd), void()) { + T::Close(fd); + } +}; + +using unique_fd = unique_fd_impl; + +#if !defined(_WIN32) + +// Inline functions, so that they can be used header-only. +template +inline bool Pipe(unique_fd_impl* read, unique_fd_impl* write, + int flags = O_CLOEXEC) { + int pipefd[2]; + +#if defined(__linux__) + if (pipe2(pipefd, flags) != 0) { + return false; + } +#else // defined(__APPLE__) + if (flags & ~(O_CLOEXEC | O_NONBLOCK)) { + return false; + } + if (pipe(pipefd) != 0) { + return false; + } + + if (flags & O_CLOEXEC) { + if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) { + close(pipefd[0]); + close(pipefd[1]); + return false; + } + } + if (flags & O_NONBLOCK) { + if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 || fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) { + close(pipefd[0]); + close(pipefd[1]); + return false; + } + } +#endif + + read->reset(pipefd[0]); + write->reset(pipefd[1]); + return true; +} + +template +inline bool Socketpair(int domain, int type, int protocol, unique_fd_impl* left, + unique_fd_impl* right) { + int sockfd[2]; + if (socketpair(domain, type, protocol, sockfd) != 0) { + return false; + } + left->reset(sockfd[0]); + right->reset(sockfd[1]); + return true; +} + +template +inline bool Socketpair(int type, unique_fd_impl* left, unique_fd_impl* right) { + return Socketpair(AF_UNIX, type, 0, left, right); +} + +// Using fdopen with unique_fd correctly is more annoying than it should be, +// because fdopen doesn't close the file descriptor received upon failure. +inline FILE* Fdopen(unique_fd&& ufd, const char* mode) { + int fd = ufd.release(); + FILE* file = fdopen(fd, mode); + if (!file) { + close(fd); + } + return file; +} + +// Using fdopendir with unique_fd correctly is more annoying than it should be, +// because fdopen doesn't close the file descriptor received upon failure. +inline DIR* Fdopendir(unique_fd&& ufd) { + int fd = ufd.release(); + DIR* dir = fdopendir(fd); + if (dir == nullptr) { + close(fd); + } + return dir; +} + +#endif // !defined(_WIN32) + +// A wrapper type that can be implicitly constructed from either int or unique_fd. +struct borrowed_fd { + /* implicit */ borrowed_fd(int fd) : fd_(fd) {} // NOLINT + template + /* implicit */ borrowed_fd(const unique_fd_impl& ufd) : fd_(ufd.get()) {} // NOLINT + + int get() const { return fd_; } + + bool operator>=(int rhs) const { return get() >= rhs; } + bool operator<(int rhs) const { return get() < rhs; } + bool operator==(int rhs) const { return get() == rhs; } + bool operator!=(int rhs) const { return get() != rhs; } + + private: + int fd_ = -1; +}; +} // namespace base +} // namespace android + +template +int close(const android::base::unique_fd_impl&) + __attribute__((__unavailable__("close called on unique_fd"))); + +template +FILE* fdopen(const android::base::unique_fd_impl&, const char* mode) + __attribute__((__unavailable__("fdopen takes ownership of the fd passed in; either dup the " + "unique_fd, or use android::base::Fdopen to pass ownership"))); + +template +DIR* fdopendir(const android::base::unique_fd_impl&) __attribute__(( + __unavailable__("fdopendir takes ownership of the fd passed in; either dup the " + "unique_fd, or use android::base::Fdopendir to pass ownership"))); diff --git a/aosp/libsparse/base/src/test/cpp/hello_test.cpp b/aosp/libsparse/base/src/test/cpp/hello_test.cpp new file mode 100644 index 00000000..971ca235 --- /dev/null +++ b/aosp/libsparse/base/src/test/cpp/hello_test.cpp @@ -0,0 +1,9 @@ +/* + * This C++ source file was generated by the Gradle 'init' task. + */ + +#include + +int main() { + return 0; +} diff --git a/aosp/libsparse/img2simg/build.gradle.kts b/aosp/libsparse/img2simg/build.gradle.kts new file mode 100644 index 00000000..54840282 --- /dev/null +++ b/aosp/libsparse/img2simg/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `cpp-application` +} + +application { + targetMachines.set(listOf(machines.linux.x86_64, machines.macOS.x86_64)) + dependencies { + implementation(project(":aosp:libsparse:sparse")) + } +} + +tasks.withType(LinkExecutable::class.java).configureEach { + linkerArgs.add("-lz") +} + +tasks.withType(CppCompile::class.java).configureEach { + compilerArgs.add("-std=c++17") +} diff --git a/aosp/libsparse/img2simg/src/main/cpp/img2simg.cpp b/aosp/libsparse/img2simg/src/main/cpp/img2simg.cpp new file mode 100644 index 00000000..4c2c6ca6 --- /dev/null +++ b/aosp/libsparse/img2simg/src/main/cpp/img2simg.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE64_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#define lseek64 lseek +#define off64_t off_t +#endif + +void usage() { + fprintf(stderr, "Usage: img2simg []\n"); +} + +int main(int argc, char* argv[]) { + int in; + int out; + int ret; + struct sparse_file* s; + unsigned int block_size = 4096; + off64_t len; + + if (argc < 3 || argc > 4) { + usage(); + exit(-1); + } + + if (argc == 4) { + block_size = atoi(argv[3]); + } + + if (block_size < 1024 || block_size % 4 != 0) { + usage(); + exit(-1); + } + + if (strcmp(argv[1], "-") == 0) { + in = STDIN_FILENO; + } else { + in = open(argv[1], O_RDONLY | O_BINARY); + if (in < 0) { + fprintf(stderr, "Cannot open input file %s\n", argv[1]); + exit(-1); + } + } + + if (strcmp(argv[2], "-") == 0) { + out = STDOUT_FILENO; + } else { + out = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664); + if (out < 0) { + fprintf(stderr, "Cannot open output file %s\n", argv[2]); + exit(-1); + } + } + + len = lseek64(in, 0, SEEK_END); + lseek64(in, 0, SEEK_SET); + + s = sparse_file_new(block_size, len); + if (!s) { + fprintf(stderr, "Failed to create sparse file\n"); + exit(-1); + } + + sparse_file_verbose(s); + ret = sparse_file_read(s, in, false, false); + if (ret) { + fprintf(stderr, "Failed to read file\n"); + exit(-1); + } + + ret = sparse_file_write(s, out, false, true, false); + if (ret) { + fprintf(stderr, "Failed to write sparse file\n"); + exit(-1); + } + + close(in); + close(out); + + exit(0); +} diff --git a/aosp/libsparse/simg2img/build.gradle.kts b/aosp/libsparse/simg2img/build.gradle.kts new file mode 100644 index 00000000..3cf339ce --- /dev/null +++ b/aosp/libsparse/simg2img/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + `cpp-application` +} + +application { + targetMachines.set(listOf(machines.linux.x86_64, machines.macOS.x86_64)) + dependencies { + implementation(project(":aosp:libsparse:sparse")) + } +} + +tasks.withType(LinkExecutable::class.java).configureEach { + linkerArgs.add("-lz") +} + +tasks.register("hello") { +} + +tasks.withType(CppCompile::class.java).configureEach { + compilerArgs.add("-std=c++17") +} diff --git a/aosp/libsparse/simg2img/src/main/cpp/simg2img.cpp b/aosp/libsparse/simg2img/src/main/cpp/simg2img.cpp new file mode 100644 index 00000000..8ba5f698 --- /dev/null +++ b/aosp/libsparse/simg2img/src/main/cpp/simg2img.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +void usage() { + fprintf(stderr, "Usage: simg2img \n"); +} + +int main(int argc, char* argv[]) { + int in; + int out; + int i; + struct sparse_file* s; + + if (argc < 3) { + usage(); + exit(-1); + } + + out = open(argv[argc - 1], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664); + if (out < 0) { + fprintf(stderr, "Cannot open output file %s\n", argv[argc - 1]); + exit(-1); + } + + for (i = 1; i < argc - 1; i++) { + if (strcmp(argv[i], "-") == 0) { + in = STDIN_FILENO; + } else { + in = open(argv[i], O_RDONLY | O_BINARY); + if (in < 0) { + fprintf(stderr, "Cannot open input file %s\n", argv[i]); + exit(-1); + } + } + + s = sparse_file_import(in, true, false); + if (!s) { + fprintf(stderr, "Failed to read sparse file\n"); + exit(-1); + } + + if (lseek(out, 0, SEEK_SET) == -1) { + perror("lseek failed"); + exit(EXIT_FAILURE); + } + + if (sparse_file_write(s, out, false, false, false) < 0) { + fprintf(stderr, "Cannot write output file\n"); + exit(-1); + } + sparse_file_destroy(s); + close(in); + } + + close(out); + + exit(0); +} diff --git a/aosp/libsparse/simg2simg/build.gradle.kts b/aosp/libsparse/simg2simg/build.gradle.kts new file mode 100644 index 00000000..54840282 --- /dev/null +++ b/aosp/libsparse/simg2simg/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `cpp-application` +} + +application { + targetMachines.set(listOf(machines.linux.x86_64, machines.macOS.x86_64)) + dependencies { + implementation(project(":aosp:libsparse:sparse")) + } +} + +tasks.withType(LinkExecutable::class.java).configureEach { + linkerArgs.add("-lz") +} + +tasks.withType(CppCompile::class.java).configureEach { + compilerArgs.add("-std=c++17") +} diff --git a/aosp/libsparse/simg2simg/src/main/cpp/simg2simg.cpp b/aosp/libsparse/simg2simg/src/main/cpp/simg2simg.cpp new file mode 100644 index 00000000..41f9ebf9 --- /dev/null +++ b/aosp/libsparse/simg2simg/src/main/cpp/simg2simg.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE64_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +void usage() { + fprintf(stderr, "Usage: simg2simg \n"); +} + +int main(int argc, char* argv[]) { + int in; + int out; + int i; + int ret; + struct sparse_file* s; + int64_t max_size; + struct sparse_file** out_s; + int files; + char filename[4096]; + + if (argc != 4) { + usage(); + exit(-1); + } + + max_size = atoll(argv[3]); + + in = open(argv[1], O_RDONLY | O_BINARY); + if (in < 0) { + fprintf(stderr, "Cannot open input file %s\n", argv[1]); + exit(-1); + } + + s = sparse_file_import(in, true, false); + if (!s) { + fprintf(stderr, "Failed to import sparse file\n"); + exit(-1); + } + + files = sparse_file_resparse(s, max_size, nullptr, 0); + if (files < 0) { + fprintf(stderr, "Failed to resparse\n"); + exit(-1); + } + + out_s = (sparse_file**) calloc(sizeof(struct sparse_file*), files); + if (!out_s) { + fprintf(stderr, "Failed to allocate sparse file array\n"); + exit(-1); + } + + files = sparse_file_resparse(s, max_size, out_s, files); + if (files < 0) { + fprintf(stderr, "Failed to resparse\n"); + exit(-1); + } + + for (i = 0; i < files; i++) { + ret = snprintf(filename, sizeof(filename), "%s.%d", argv[2], i); + if (ret >= (int)sizeof(filename)) { + fprintf(stderr, "Filename too long\n"); + exit(-1); + } + + out = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664); + if (out < 0) { + fprintf(stderr, "Cannot open output file %s\n", argv[2]); + exit(-1); + } + + ret = sparse_file_write(out_s[i], out, false, true, false); + if (ret) { + fprintf(stderr, "Failed to write sparse file\n"); + exit(-1); + } + close(out); + } + + close(in); + + exit(0); +} diff --git a/aosp/libsparse/sparse/build.gradle.kts b/aosp/libsparse/sparse/build.gradle.kts new file mode 100644 index 00000000..ae504c2c --- /dev/null +++ b/aosp/libsparse/sparse/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + `cpp-library` +} + +library { + targetMachines.set(listOf(machines.linux.x86_64, machines.macOS.x86_64)) + linkage.set(listOf(Linkage.STATIC)) + dependencies { + implementation(project(":aosp:libsparse:base")) + } +} + +tasks.withType(CppCompile::class.java).configureEach { + macros.put("NDEBUG", null) + compilerArgs.add("-Wall") + compilerArgs.add("-std=c++17") +} + +tasks.withType(LinkSharedLibrary::class.java).configureEach { + linkerArgs.add("-lz") +} + +tasks.withType(CreateStaticLibrary::class.java).configureEach { +} diff --git a/aosp/libsparse/sparse/src/main/cpp/Android.bp b/aosp/libsparse/sparse/src/main/cpp/Android.bp new file mode 100644 index 00000000..2ec47541 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/Android.bp @@ -0,0 +1,84 @@ +// Copyright 2010 The Android Open Source Project + +cc_library { + name: "libsparse", + host_supported: true, + recovery_available: true, + unique_host_soname: true, + srcs: [ + "backed_block.cpp", + "output_file.cpp", + "sparse.cpp", + "sparse_crc32.cpp", + "sparse_err.cpp", + "sparse_read.cpp", + ], + cflags: ["-Werror"], + local_include_dirs: ["include"], + export_include_dirs: ["include"], + shared_libs: [ + "libz", + "libbase", + ], + target: { + windows: { + enabled: true, + }, + }, +} + +cc_binary { + name: "simg2img", + host_supported: true, + srcs: [ + "simg2img.cpp", + "sparse_crc32.cpp", + ], + static_libs: [ + "libsparse", + "libz", + "libbase", + ], + + cflags: ["-Werror"], +} + +cc_binary { + name: "img2simg", + host_supported: true, + srcs: ["img2simg.cpp"], + static_libs: [ + "libsparse", + "libz", + "libbase", + ], + + cflags: ["-Werror"], +} + +cc_binary_host { + name: "append2simg", + srcs: ["append2simg.cpp"], + static_libs: [ + "libsparse", + "libz", + "libbase", + ], + + cflags: ["-Werror"], +} + +python_binary_host { + name: "simg_dump.py", + main: "simg_dump.py", + srcs: ["simg_dump.py"], + version: { + py2: { + embedded_launcher: true, + enabled: true, + }, + py3: { + enabled: false, + }, + }, +} diff --git a/aosp/libsparse/sparse/src/main/cpp/backed_block.cpp b/aosp/libsparse/sparse/src/main/cpp/backed_block.cpp new file mode 100644 index 00000000..f3d8022a --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/backed_block.cpp @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "backed_block.h" +#include "sparse_defs.h" + +struct backed_block { + unsigned int block; + unsigned int len; + enum backed_block_type type; + union { + struct { + void* data; + } data; + struct { + char* filename; + int64_t offset; + } file; + struct { + int fd; + int64_t offset; + } fd; + struct { + uint32_t val; + } fill; + }; + struct backed_block* next; +}; + +struct backed_block_list { + struct backed_block* data_blocks; + struct backed_block* last_used; + unsigned int block_size; +}; + +struct backed_block* backed_block_iter_new(struct backed_block_list* bbl) { + return bbl->data_blocks; +} + +struct backed_block* backed_block_iter_next(struct backed_block* bb) { + return bb->next; +} + +unsigned int backed_block_len(struct backed_block* bb) { + return bb->len; +} + +unsigned int backed_block_block(struct backed_block* bb) { + return bb->block; +} + +void* backed_block_data(struct backed_block* bb) { + assert(bb->type == BACKED_BLOCK_DATA); + return bb->data.data; +} + +const char* backed_block_filename(struct backed_block* bb) { + assert(bb->type == BACKED_BLOCK_FILE); + return bb->file.filename; +} + +int backed_block_fd(struct backed_block* bb) { + assert(bb->type == BACKED_BLOCK_FD); + return bb->fd.fd; +} + +int64_t backed_block_file_offset(struct backed_block* bb) { + assert(bb->type == BACKED_BLOCK_FILE || bb->type == BACKED_BLOCK_FD); + if (bb->type == BACKED_BLOCK_FILE) { + return bb->file.offset; + } else { /* bb->type == BACKED_BLOCK_FD */ + return bb->fd.offset; + } +} + +uint32_t backed_block_fill_val(struct backed_block* bb) { + assert(bb->type == BACKED_BLOCK_FILL); + return bb->fill.val; +} + +enum backed_block_type backed_block_type(struct backed_block* bb) { + return bb->type; +} + +void backed_block_destroy(struct backed_block* bb) { + if (bb->type == BACKED_BLOCK_FILE) { + free(bb->file.filename); + } + + free(bb); +} + +struct backed_block_list* backed_block_list_new(unsigned int block_size) { + struct backed_block_list* b = + reinterpret_cast(calloc(sizeof(struct backed_block_list), 1)); + b->block_size = block_size; + return b; +} + +void backed_block_list_destroy(struct backed_block_list* bbl) { + if (bbl->data_blocks) { + struct backed_block* bb = bbl->data_blocks; + while (bb) { + struct backed_block* next = bb->next; + backed_block_destroy(bb); + bb = next; + } + } + + free(bbl); +} + +void backed_block_list_move(struct backed_block_list* from, struct backed_block_list* to, + struct backed_block* start, struct backed_block* end) { + struct backed_block* bb; + + if (start == nullptr) { + start = from->data_blocks; + } + + if (!end) { + for (end = start; end && end->next; end = end->next) + ; + } + + if (start == nullptr || end == nullptr) { + return; + } + + from->last_used = nullptr; + to->last_used = nullptr; + if (from->data_blocks == start) { + from->data_blocks = end->next; + } else { + for (bb = from->data_blocks; bb; bb = bb->next) { + if (bb->next == start) { + bb->next = end->next; + break; + } + } + } + + if (!to->data_blocks) { + to->data_blocks = start; + end->next = nullptr; + } else { + for (bb = to->data_blocks; bb; bb = bb->next) { + if (!bb->next || bb->next->block > start->block) { + end->next = bb->next; + bb->next = start; + break; + } + } + } +} + +/* may free b */ +static int merge_bb(struct backed_block_list* bbl, struct backed_block* a, struct backed_block* b) { + unsigned int block_len; + + /* Block doesn't exist (possible if one block is the last block) */ + if (!a || !b) { + return -EINVAL; + } + + assert(a->block < b->block); + + /* Blocks are of different types */ + if (a->type != b->type) { + return -EINVAL; + } + + /* Blocks are not adjacent */ + block_len = a->len / bbl->block_size; /* rounds down */ + if (a->block + block_len != b->block) { + return -EINVAL; + } + + switch (a->type) { + case BACKED_BLOCK_DATA: + /* Don't support merging data for now */ + return -EINVAL; + case BACKED_BLOCK_FILL: + if (a->fill.val != b->fill.val) { + return -EINVAL; + } + break; + case BACKED_BLOCK_FILE: + /* Already make sure b->type is BACKED_BLOCK_FILE */ + if (strcmp(a->file.filename, b->file.filename) || a->file.offset + a->len != b->file.offset) { + return -EINVAL; + } + break; + case BACKED_BLOCK_FD: + if (a->fd.fd != b->fd.fd || a->fd.offset + a->len != b->fd.offset) { + return -EINVAL; + } + break; + } + + /* Blocks are compatible and adjacent, with a before b. Merge b into a, + * and free b */ + a->len += b->len; + a->next = b->next; + + backed_block_destroy(b); + + return 0; +} + +static int queue_bb(struct backed_block_list* bbl, struct backed_block* new_bb) { + struct backed_block* bb; + + if (bbl->data_blocks == nullptr) { + bbl->data_blocks = new_bb; + return 0; + } + + if (bbl->data_blocks->block > new_bb->block) { + new_bb->next = bbl->data_blocks; + bbl->data_blocks = new_bb; + return 0; + } + + /* Optimization: blocks are mostly queued in sequence, so save the + pointer to the last bb that was added, and start searching from + there if the next block number is higher */ + if (bbl->last_used && new_bb->block > bbl->last_used->block) + bb = bbl->last_used; + else + bb = bbl->data_blocks; + bbl->last_used = new_bb; + + for (; bb->next && bb->next->block < new_bb->block; bb = bb->next) + ; + + if (bb->next == nullptr) { + bb->next = new_bb; + } else { + new_bb->next = bb->next; + bb->next = new_bb; + } + + merge_bb(bbl, new_bb, new_bb->next); + if (!merge_bb(bbl, bb, new_bb)) { + /* new_bb destroyed, point to retained as last_used */ + bbl->last_used = bb; + } + + return 0; +} + +/* Queues a fill block of memory to be written to the specified data blocks */ +int backed_block_add_fill(struct backed_block_list* bbl, unsigned int fill_val, unsigned int len, + unsigned int block) { + struct backed_block* bb = reinterpret_cast(calloc(1, sizeof(struct backed_block))); + if (bb == nullptr) { + return -ENOMEM; + } + + bb->block = block; + bb->len = len; + bb->type = BACKED_BLOCK_FILL; + bb->fill.val = fill_val; + bb->next = nullptr; + + return queue_bb(bbl, bb); +} + +/* Queues a block of memory to be written to the specified data blocks */ +int backed_block_add_data(struct backed_block_list* bbl, void* data, unsigned int len, + unsigned int block) { + struct backed_block* bb = reinterpret_cast(calloc(1, sizeof(struct backed_block))); + if (bb == nullptr) { + return -ENOMEM; + } + + bb->block = block; + bb->len = len; + bb->type = BACKED_BLOCK_DATA; + bb->data.data = data; + bb->next = nullptr; + + return queue_bb(bbl, bb); +} + +/* Queues a chunk of a file on disk to be written to the specified data blocks */ +int backed_block_add_file(struct backed_block_list* bbl, const char* filename, int64_t offset, + unsigned int len, unsigned int block) { + struct backed_block* bb = reinterpret_cast(calloc(1, sizeof(struct backed_block))); + if (bb == nullptr) { + return -ENOMEM; + } + + bb->block = block; + bb->len = len; + bb->type = BACKED_BLOCK_FILE; + bb->file.filename = strdup(filename); + bb->file.offset = offset; + bb->next = nullptr; + + return queue_bb(bbl, bb); +} + +/* Queues a chunk of a fd to be written to the specified data blocks */ +int backed_block_add_fd(struct backed_block_list* bbl, int fd, int64_t offset, unsigned int len, + unsigned int block) { + struct backed_block* bb = reinterpret_cast(calloc(1, sizeof(struct backed_block))); + if (bb == nullptr) { + return -ENOMEM; + } + + bb->block = block; + bb->len = len; + bb->type = BACKED_BLOCK_FD; + bb->fd.fd = fd; + bb->fd.offset = offset; + bb->next = nullptr; + + return queue_bb(bbl, bb); +} + +int backed_block_split(struct backed_block_list* bbl, struct backed_block* bb, + unsigned int max_len) { + struct backed_block* new_bb; + + max_len = ALIGN_DOWN(max_len, bbl->block_size); + + if (bb->len <= max_len) { + return 0; + } + + new_bb = reinterpret_cast(malloc(sizeof(struct backed_block))); + if (new_bb == nullptr) { + return -ENOMEM; + } + + *new_bb = *bb; + + new_bb->len = bb->len - max_len; + new_bb->block = bb->block + max_len / bbl->block_size; + new_bb->next = bb->next; + bb->next = new_bb; + bb->len = max_len; + + switch (bb->type) { + case BACKED_BLOCK_DATA: + new_bb->data.data = (char*)bb->data.data + max_len; + break; + case BACKED_BLOCK_FILE: + new_bb->file.offset += max_len; + break; + case BACKED_BLOCK_FD: + new_bb->fd.offset += max_len; + break; + case BACKED_BLOCK_FILL: + break; + } + + return 0; +} diff --git a/aosp/libsparse/sparse/src/main/cpp/defs.h b/aosp/libsparse/sparse/src/main/cpp/defs.h new file mode 100644 index 00000000..28e5cab0 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/defs.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBSPARSE_DEFS_H_ + +#ifndef __unused +#define __unused __attribute__((__unused__)) +#endif + +#endif /* _LIBSPARSE_DEFS_H_ */ diff --git a/aosp/libsparse/sparse/src/main/cpp/output_file.cpp b/aosp/libsparse/sparse/src/main/cpp/output_file.cpp new file mode 100644 index 00000000..b883c13f --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/output_file.cpp @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE64_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "output_file.h" +#include "sparse_crc32.h" +#include "sparse_format.h" + +#include + +#ifndef _WIN32 +#define O_BINARY 0 +#else +#define ftruncate64 ftruncate +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#define lseek64 lseek +#define ftruncate64 ftruncate +#define off64_t off_t +#endif + +#define SPARSE_HEADER_MAJOR_VER 1 +#define SPARSE_HEADER_MINOR_VER 0 +#define SPARSE_HEADER_LEN (sizeof(sparse_header_t)) +#define CHUNK_HEADER_LEN (sizeof(chunk_header_t)) + +#define container_of(inner, outer_t, elem) ((outer_t*)((char*)(inner)-offsetof(outer_t, elem))) + +struct output_file_ops { + int (*open)(struct output_file*, int fd); + int (*skip)(struct output_file*, int64_t); + int (*pad)(struct output_file*, int64_t); + int (*write)(struct output_file*, void*, size_t); + void (*close)(struct output_file*); +}; + +struct sparse_file_ops { + int (*write_data_chunk)(struct output_file* out, unsigned int len, void* data); + int (*write_fill_chunk)(struct output_file* out, unsigned int len, uint32_t fill_val); + int (*write_skip_chunk)(struct output_file* out, int64_t len); + int (*write_end_chunk)(struct output_file* out); +}; + +struct output_file { + int64_t cur_out_ptr; + unsigned int chunk_cnt; + uint32_t crc32; + struct output_file_ops* ops; + struct sparse_file_ops* sparse_ops; + int use_crc; + unsigned int block_size; + int64_t len; + char* zero_buf; + uint32_t* fill_buf; + char* buf; +}; + +struct output_file_gz { + struct output_file out; + gzFile gz_fd; +}; + +#define to_output_file_gz(_o) container_of((_o), struct output_file_gz, out) + +struct output_file_normal { + struct output_file out; + int fd; +}; + +#define to_output_file_normal(_o) container_of((_o), struct output_file_normal, out) + +struct output_file_callback { + struct output_file out; + void* priv; + int (*write)(void* priv, const void* buf, size_t len); +}; + +#define to_output_file_callback(_o) container_of((_o), struct output_file_callback, out) + +static int file_open(struct output_file* out, int fd) { + struct output_file_normal* outn = to_output_file_normal(out); + + outn->fd = fd; + return 0; +} + +static int file_skip(struct output_file* out, int64_t cnt) { + off64_t ret; + struct output_file_normal* outn = to_output_file_normal(out); + + ret = lseek64(outn->fd, cnt, SEEK_CUR); + if (ret < 0) { + error_errno("lseek64"); + return -1; + } + return 0; +} + +static int file_pad(struct output_file* out, int64_t len) { + int ret; + struct output_file_normal* outn = to_output_file_normal(out); + + ret = ftruncate64(outn->fd, len); + if (ret < 0) { + return -errno; + } + + return 0; +} + +static int file_write(struct output_file* out, void* data, size_t len) { + ssize_t ret; + struct output_file_normal* outn = to_output_file_normal(out); + + while (len > 0) { + ret = write(outn->fd, data, len); + if (ret < 0) { + if (errno == EINTR) { + continue; + } + error_errno("write"); + return -1; + } + + data = (char*)data + ret; + len -= ret; + } + + return 0; +} + +static void file_close(struct output_file* out) { + struct output_file_normal* outn = to_output_file_normal(out); + + free(outn); +} + +static struct output_file_ops file_ops = { + .open = file_open, + .skip = file_skip, + .pad = file_pad, + .write = file_write, + .close = file_close, +}; + +static int gz_file_open(struct output_file* out, int fd) { + struct output_file_gz* outgz = to_output_file_gz(out); + + outgz->gz_fd = gzdopen(fd, "wb9"); + if (!outgz->gz_fd) { + error_errno("gzopen"); + return -errno; + } + + return 0; +} + +static int gz_file_skip(struct output_file* out, int64_t cnt) { + off64_t ret; + struct output_file_gz* outgz = to_output_file_gz(out); + + ret = gzseek(outgz->gz_fd, cnt, SEEK_CUR); + if (ret < 0) { + error_errno("gzseek"); + return -1; + } + return 0; +} + +static int gz_file_pad(struct output_file* out, int64_t len) { + off64_t ret; + struct output_file_gz* outgz = to_output_file_gz(out); + + ret = gztell(outgz->gz_fd); + if (ret < 0) { + return -1; + } + + if (ret >= len) { + return 0; + } + + ret = gzseek(outgz->gz_fd, len - 1, SEEK_SET); + if (ret < 0) { + return -1; + } + + gzwrite(outgz->gz_fd, "", 1); + + return 0; +} + +static int gz_file_write(struct output_file* out, void* data, size_t len) { + int ret; + struct output_file_gz* outgz = to_output_file_gz(out); + + while (len > 0) { + ret = gzwrite(outgz->gz_fd, data, std::min(len, (unsigned int)INT_MAX)); + if (ret == 0) { + error("gzwrite %s", gzerror(outgz->gz_fd, nullptr)); + return -1; + } + len -= ret; + data = (char*)data + ret; + } + + return 0; +} + +static void gz_file_close(struct output_file* out) { + struct output_file_gz* outgz = to_output_file_gz(out); + + gzclose(outgz->gz_fd); + free(outgz); +} + +static struct output_file_ops gz_file_ops = { + .open = gz_file_open, + .skip = gz_file_skip, + .pad = gz_file_pad, + .write = gz_file_write, + .close = gz_file_close, +}; + +static int callback_file_open(struct output_file* out __unused, int fd __unused) { + return 0; +} + +static int callback_file_skip(struct output_file* out, int64_t off) { + struct output_file_callback* outc = to_output_file_callback(out); + int to_write; + int ret; + + while (off > 0) { + to_write = std::min(off, (int64_t)INT_MAX); + ret = outc->write(outc->priv, nullptr, to_write); + if (ret < 0) { + return ret; + } + off -= to_write; + } + + return 0; +} + +static int callback_file_pad(struct output_file* out __unused, int64_t len __unused) { + return -1; +} + +static int callback_file_write(struct output_file* out, void* data, size_t len) { + struct output_file_callback* outc = to_output_file_callback(out); + + return outc->write(outc->priv, data, len); +} + +static void callback_file_close(struct output_file* out) { + struct output_file_callback* outc = to_output_file_callback(out); + + free(outc); +} + +static struct output_file_ops callback_file_ops = { + .open = callback_file_open, + .skip = callback_file_skip, + .pad = callback_file_pad, + .write = callback_file_write, + .close = callback_file_close, +}; + +int read_all(int fd, void* buf, size_t len) { + size_t total = 0; + int ret; + char* ptr = reinterpret_cast(buf); + + while (total < len) { + ret = read(fd, ptr, len - total); + + if (ret < 0) return -errno; + + if (ret == 0) return -EINVAL; + + ptr += ret; + total += ret; + } + + return 0; +} + +static int write_sparse_skip_chunk(struct output_file* out, int64_t skip_len) { + chunk_header_t chunk_header; + int ret; + + if (skip_len % out->block_size) { + error("don't care size %" PRIi64 " is not a multiple of the block size %u", skip_len, + out->block_size); + return -1; + } + + /* We are skipping data, so emit a don't care chunk. */ + chunk_header.chunk_type = CHUNK_TYPE_DONT_CARE; + chunk_header.reserved1 = 0; + chunk_header.chunk_sz = skip_len / out->block_size; + chunk_header.total_sz = CHUNK_HEADER_LEN; + ret = out->ops->write(out, &chunk_header, sizeof(chunk_header)); + if (ret < 0) return -1; + + out->cur_out_ptr += skip_len; + out->chunk_cnt++; + + return 0; +} + +static int write_sparse_fill_chunk(struct output_file* out, unsigned int len, uint32_t fill_val) { + chunk_header_t chunk_header; + int rnd_up_len, count; + int ret; + + /* Round up the fill length to a multiple of the block size */ + rnd_up_len = ALIGN(len, out->block_size); + + /* Finally we can safely emit a chunk of data */ + chunk_header.chunk_type = CHUNK_TYPE_FILL; + chunk_header.reserved1 = 0; + chunk_header.chunk_sz = rnd_up_len / out->block_size; + chunk_header.total_sz = CHUNK_HEADER_LEN + sizeof(fill_val); + ret = out->ops->write(out, &chunk_header, sizeof(chunk_header)); + + if (ret < 0) return -1; + ret = out->ops->write(out, &fill_val, sizeof(fill_val)); + if (ret < 0) return -1; + + if (out->use_crc) { + count = out->block_size / sizeof(uint32_t); + while (count--) out->crc32 = sparse_crc32(out->crc32, &fill_val, sizeof(uint32_t)); + } + + out->cur_out_ptr += rnd_up_len; + out->chunk_cnt++; + + return 0; +} + +static int write_sparse_data_chunk(struct output_file* out, unsigned int len, void* data) { + chunk_header_t chunk_header; + int rnd_up_len, zero_len; + int ret; + + /* Round up the data length to a multiple of the block size */ + rnd_up_len = ALIGN(len, out->block_size); + zero_len = rnd_up_len - len; + + /* Finally we can safely emit a chunk of data */ + chunk_header.chunk_type = CHUNK_TYPE_RAW; + chunk_header.reserved1 = 0; + chunk_header.chunk_sz = rnd_up_len / out->block_size; + chunk_header.total_sz = CHUNK_HEADER_LEN + rnd_up_len; + ret = out->ops->write(out, &chunk_header, sizeof(chunk_header)); + + if (ret < 0) return -1; + ret = out->ops->write(out, data, len); + if (ret < 0) return -1; + if (zero_len) { + ret = out->ops->write(out, out->zero_buf, zero_len); + if (ret < 0) return -1; + } + + if (out->use_crc) { + out->crc32 = sparse_crc32(out->crc32, data, len); + if (zero_len) out->crc32 = sparse_crc32(out->crc32, out->zero_buf, zero_len); + } + + out->cur_out_ptr += rnd_up_len; + out->chunk_cnt++; + + return 0; +} + +int write_sparse_end_chunk(struct output_file* out) { + chunk_header_t chunk_header; + int ret; + + if (out->use_crc) { + chunk_header.chunk_type = CHUNK_TYPE_CRC32; + chunk_header.reserved1 = 0; + chunk_header.chunk_sz = 0; + chunk_header.total_sz = CHUNK_HEADER_LEN + 4; + + ret = out->ops->write(out, &chunk_header, sizeof(chunk_header)); + if (ret < 0) { + return ret; + } + out->ops->write(out, &out->crc32, 4); + if (ret < 0) { + return ret; + } + + out->chunk_cnt++; + } + + return 0; +} + +static struct sparse_file_ops sparse_file_ops = { + .write_data_chunk = write_sparse_data_chunk, + .write_fill_chunk = write_sparse_fill_chunk, + .write_skip_chunk = write_sparse_skip_chunk, + .write_end_chunk = write_sparse_end_chunk, +}; + +static int write_normal_data_chunk(struct output_file* out, unsigned int len, void* data) { + int ret; + unsigned int rnd_up_len = ALIGN(len, out->block_size); + + ret = out->ops->write(out, data, len); + if (ret < 0) { + return ret; + } + + if (rnd_up_len > len) { + ret = out->ops->skip(out, rnd_up_len - len); + } + + return ret; +} + +static int write_normal_fill_chunk(struct output_file* out, unsigned int len, uint32_t fill_val) { + int ret; + unsigned int i; + unsigned int write_len; + + /* Initialize fill_buf with the fill_val */ + for (i = 0; i < out->block_size / sizeof(uint32_t); i++) { + out->fill_buf[i] = fill_val; + } + + while (len) { + write_len = std::min(len, out->block_size); + ret = out->ops->write(out, out->fill_buf, write_len); + if (ret < 0) { + return ret; + } + + len -= write_len; + } + + return 0; +} + +static int write_normal_skip_chunk(struct output_file* out, int64_t len) { + return out->ops->skip(out, len); +} + +int write_normal_end_chunk(struct output_file* out) { + return out->ops->pad(out, out->len); +} + +static struct sparse_file_ops normal_file_ops = { + .write_data_chunk = write_normal_data_chunk, + .write_fill_chunk = write_normal_fill_chunk, + .write_skip_chunk = write_normal_skip_chunk, + .write_end_chunk = write_normal_end_chunk, +}; + +void output_file_close(struct output_file* out) { + out->sparse_ops->write_end_chunk(out); + free(out->zero_buf); + free(out->fill_buf); + out->zero_buf = nullptr; + out->fill_buf = nullptr; + out->ops->close(out); +} + +static int output_file_init(struct output_file* out, int block_size, int64_t len, bool sparse, + int chunks, bool crc) { + int ret; + + out->len = len; + out->block_size = block_size; + out->cur_out_ptr = 0LL; + out->chunk_cnt = 0; + out->crc32 = 0; + out->use_crc = crc; + + out->zero_buf = reinterpret_cast(calloc(block_size, 1)); + if (!out->zero_buf) { + error_errno("malloc zero_buf"); + return -ENOMEM; + } + + out->fill_buf = reinterpret_cast(calloc(block_size, 1)); + if (!out->fill_buf) { + error_errno("malloc fill_buf"); + ret = -ENOMEM; + goto err_fill_buf; + } + + if (sparse) { + out->sparse_ops = &sparse_file_ops; + } else { + out->sparse_ops = &normal_file_ops; + } + + if (sparse) { + sparse_header_t sparse_header = { + .magic = SPARSE_HEADER_MAGIC, + .major_version = SPARSE_HEADER_MAJOR_VER, + .minor_version = SPARSE_HEADER_MINOR_VER, + .file_hdr_sz = SPARSE_HEADER_LEN, + .chunk_hdr_sz = CHUNK_HEADER_LEN, + .blk_sz = out->block_size, + .total_blks = static_cast(DIV_ROUND_UP(out->len, out->block_size)), + .total_chunks = static_cast(chunks), + .image_checksum = 0}; + + if (out->use_crc) { + sparse_header.total_chunks++; + } + + ret = out->ops->write(out, &sparse_header, sizeof(sparse_header)); + if (ret < 0) { + goto err_write; + } + } + + return 0; + +err_write: + free(out->fill_buf); +err_fill_buf: + free(out->zero_buf); + return ret; +} + +static struct output_file* output_file_new_gz(void) { + struct output_file_gz* outgz = + reinterpret_cast(calloc(1, sizeof(struct output_file_gz))); + if (!outgz) { + error_errno("malloc struct outgz"); + return nullptr; + } + + outgz->out.ops = &gz_file_ops; + + return &outgz->out; +} + +static struct output_file* output_file_new_normal(void) { + struct output_file_normal* outn = + reinterpret_cast(calloc(1, sizeof(struct output_file_normal))); + if (!outn) { + error_errno("malloc struct outn"); + return nullptr; + } + + outn->out.ops = &file_ops; + + return &outn->out; +} + +struct output_file* output_file_open_callback(int (*write)(void*, const void*, size_t), void* priv, + unsigned int block_size, int64_t len, int gz __unused, + int sparse, int chunks, int crc) { + int ret; + struct output_file_callback* outc; + + outc = + reinterpret_cast(calloc(1, sizeof(struct output_file_callback))); + if (!outc) { + error_errno("malloc struct outc"); + return nullptr; + } + + outc->out.ops = &callback_file_ops; + outc->priv = priv; + outc->write = write; + + ret = output_file_init(&outc->out, block_size, len, sparse, chunks, crc); + if (ret < 0) { + free(outc); + return nullptr; + } + + return &outc->out; +} + +struct output_file* output_file_open_fd(int fd, unsigned int block_size, int64_t len, int gz, + int sparse, int chunks, int crc) { + int ret; + struct output_file* out; + + if (gz) { + out = output_file_new_gz(); + } else { + out = output_file_new_normal(); + } + if (!out) { + return nullptr; + } + + out->ops->open(out, fd); + + ret = output_file_init(out, block_size, len, sparse, chunks, crc); + if (ret < 0) { + free(out); + return nullptr; + } + + return out; +} + +/* Write a contiguous region of data blocks from a memory buffer */ +int write_data_chunk(struct output_file* out, unsigned int len, void* data) { + return out->sparse_ops->write_data_chunk(out, len, data); +} + +/* Write a contiguous region of data blocks with a fill value */ +int write_fill_chunk(struct output_file* out, unsigned int len, uint32_t fill_val) { + return out->sparse_ops->write_fill_chunk(out, len, fill_val); +} + +int write_fd_chunk(struct output_file* out, unsigned int len, int fd, int64_t offset) { + auto m = android::base::MappedFile::FromFd(fd, offset, len, PROT_READ); + if (!m) return -errno; + + return out->sparse_ops->write_data_chunk(out, m->size(), m->data()); +} + +/* Write a contiguous region of data blocks from a file */ +int write_file_chunk(struct output_file* out, unsigned int len, const char* file, int64_t offset) { + int ret; + + int file_fd = open(file, O_RDONLY | O_BINARY); + if (file_fd < 0) { + return -errno; + } + + ret = write_fd_chunk(out, len, file_fd, offset); + + close(file_fd); + + return ret; +} + +int write_skip_chunk(struct output_file* out, int64_t len) { + return out->sparse_ops->write_skip_chunk(out, len); +} diff --git a/aosp/libsparse/sparse/src/main/cpp/output_file.h b/aosp/libsparse/sparse/src/main/cpp/output_file.h new file mode 100644 index 00000000..278430b6 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/output_file.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _OUTPUT_FILE_H_ +#define _OUTPUT_FILE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct output_file; + +struct output_file* output_file_open_fd(int fd, unsigned int block_size, int64_t len, int gz, + int sparse, int chunks, int crc); +struct output_file* output_file_open_callback(int (*write)(void*, const void*, size_t), void* priv, + unsigned int block_size, int64_t len, int gz, + int sparse, int chunks, int crc); +int write_data_chunk(struct output_file* out, unsigned int len, void* data); +int write_fill_chunk(struct output_file* out, unsigned int len, uint32_t fill_val); +int write_file_chunk(struct output_file* out, unsigned int len, const char* file, int64_t offset); +int write_fd_chunk(struct output_file* out, unsigned int len, int fd, int64_t offset); +int write_skip_chunk(struct output_file* out, int64_t len); +void output_file_close(struct output_file* out); + +int read_all(int fd, void* buf, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/aosp/libsparse/sparse/src/main/cpp/simg_dump.py b/aosp/libsparse/sparse/src/main/cpp/simg_dump.py new file mode 100755 index 00000000..82a03ad9 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/simg_dump.py @@ -0,0 +1,221 @@ +#! /usr/bin/env python + +# Copyright (C) 2012 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function +import csv +import getopt +import hashlib +import posixpath +import signal +import struct +import sys + + +def usage(argv0): + print(""" +Usage: %s [-v] [-s] [-c ] sparse_image_file ... + -v verbose output + -s show sha1sum of data blocks + -c save .csv file of blocks +""" % (argv0)) + sys.exit(2) + + +def main(): + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + me = posixpath.basename(sys.argv[0]) + + # Parse the command line + verbose = 0 # -v + showhash = 0 # -s + csvfilename = None # -c + try: + opts, args = getopt.getopt(sys.argv[1:], + "vsc:", + ["verbose", "showhash", "csvfile"]) + except getopt.GetoptError, e: + print(e) + usage(me) + for o, a in opts: + if o in ("-v", "--verbose"): + verbose += 1 + elif o in ("-s", "--showhash"): + showhash = True + elif o in ("-c", "--csvfile"): + csvfilename = a + else: + print("Unrecognized option \"%s\"" % (o)) + usage(me) + + if not args: + print("No sparse_image_file specified") + usage(me) + + if csvfilename: + csvfile = open(csvfilename, "wb") + csvwriter = csv.writer(csvfile) + + output = verbose or csvfilename or showhash + + for path in args: + FH = open(path, "rb") + header_bin = FH.read(28) + header = struct.unpack(" 0: + print(" input_bytes output_blocks") + print("chunk offset number offset number") + + if csvfilename: + csvwriter.writerow(["chunk", "input offset", "input bytes", + "output offset", "output blocks", "type", "hash"]) + + offset = 0 + for i in xrange(1, total_chunks + 1): + header_bin = FH.read(12) + header = struct.unpack("<2H2I", header_bin) + chunk_type = header[0] + chunk_sz = header[2] + total_sz = header[3] + data_sz = total_sz - 12 + curhash = "" + curtype = "" + curpos = FH.tell() + + if verbose > 0: + print("%4u %10u %10u %7u %7u" % (i, curpos, data_sz, offset, chunk_sz), + end=" ") + + if chunk_type == 0xCAC1: + if data_sz != (chunk_sz * blk_sz): + print("Raw chunk input size (%u) does not match output size (%u)" + % (data_sz, chunk_sz * blk_sz)) + break + else: + curtype = "Raw data" + data = FH.read(data_sz) + if showhash: + h = hashlib.sha1() + h.update(data) + curhash = h.hexdigest() + elif chunk_type == 0xCAC2: + if data_sz != 4: + print("Fill chunk should have 4 bytes of fill, but this has %u" + % (data_sz)) + break + else: + fill_bin = FH.read(4) + fill = struct.unpack(" 0: + print("%-18s" % (curtype), end=" ") + + if verbose > 1: + header = struct.unpack("<12B", header_bin) + print(" (%02X%02X %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X)" + % (header[0], header[1], header[2], header[3], + header[4], header[5], header[6], header[7], + header[8], header[9], header[10], header[11]), end=" ") + + print(curhash) + + if csvfilename: + csvwriter.writerow([i, curpos, data_sz, offset, chunk_sz, curtype, + curhash]) + + offset += chunk_sz + + if verbose > 0: + print(" %10u %7u End" % (FH.tell(), offset)) + + if total_blks != offset: + print("The header said we should have %u output blocks, but we saw %u" + % (total_blks, offset)) + + junk_len = len(FH.read()) + if junk_len: + print("There were %u bytes of extra data at the end of the file." + % (junk_len)) + + if csvfilename: + csvfile.close() + + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/aosp/libsparse/sparse/src/main/cpp/sparse.cpp b/aosp/libsparse/sparse/src/main/cpp/sparse.cpp new file mode 100644 index 00000000..8622b4c3 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/sparse.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "defs.h" +#include "sparse_file.h" + +#include "backed_block.h" +#include "output_file.h" +#include "sparse_defs.h" +#include "sparse_format.h" + +struct sparse_file* sparse_file_new(unsigned int block_size, int64_t len) { + struct sparse_file* s = reinterpret_cast(calloc(sizeof(struct sparse_file), 1)); + if (!s) { + return nullptr; + } + + s->backed_block_list = backed_block_list_new(block_size); + if (!s->backed_block_list) { + free(s); + return nullptr; + } + + s->block_size = block_size; + s->len = len; + + return s; +} + +void sparse_file_destroy(struct sparse_file* s) { + backed_block_list_destroy(s->backed_block_list); + free(s); +} + +int sparse_file_add_data(struct sparse_file* s, void* data, unsigned int len, unsigned int block) { + return backed_block_add_data(s->backed_block_list, data, len, block); +} + +int sparse_file_add_fill(struct sparse_file* s, uint32_t fill_val, unsigned int len, + unsigned int block) { + return backed_block_add_fill(s->backed_block_list, fill_val, len, block); +} + +int sparse_file_add_file(struct sparse_file* s, const char* filename, int64_t file_offset, + unsigned int len, unsigned int block) { + return backed_block_add_file(s->backed_block_list, filename, file_offset, len, block); +} + +int sparse_file_add_fd(struct sparse_file* s, int fd, int64_t file_offset, unsigned int len, + unsigned int block) { + return backed_block_add_fd(s->backed_block_list, fd, file_offset, len, block); +} +unsigned int sparse_count_chunks(struct sparse_file* s) { + struct backed_block* bb; + unsigned int last_block = 0; + unsigned int chunks = 0; + + for (bb = backed_block_iter_new(s->backed_block_list); bb; bb = backed_block_iter_next(bb)) { + if (backed_block_block(bb) > last_block) { + /* If there is a gap between chunks, add a skip chunk */ + chunks++; + } + chunks++; + last_block = backed_block_block(bb) + DIV_ROUND_UP(backed_block_len(bb), s->block_size); + } + if (last_block < DIV_ROUND_UP(s->len, s->block_size)) { + chunks++; + } + + return chunks; +} + +static int sparse_file_write_block(struct output_file* out, struct backed_block* bb) { + int ret = -EINVAL; + + switch (backed_block_type(bb)) { + case BACKED_BLOCK_DATA: + ret = write_data_chunk(out, backed_block_len(bb), backed_block_data(bb)); + break; + case BACKED_BLOCK_FILE: + ret = write_file_chunk(out, backed_block_len(bb), backed_block_filename(bb), + backed_block_file_offset(bb)); + break; + case BACKED_BLOCK_FD: + ret = write_fd_chunk(out, backed_block_len(bb), backed_block_fd(bb), + backed_block_file_offset(bb)); + break; + case BACKED_BLOCK_FILL: + ret = write_fill_chunk(out, backed_block_len(bb), backed_block_fill_val(bb)); + break; + } + + return ret; +} + +static int write_all_blocks(struct sparse_file* s, struct output_file* out) { + struct backed_block* bb; + unsigned int last_block = 0; + int64_t pad; + int ret = 0; + + for (bb = backed_block_iter_new(s->backed_block_list); bb; bb = backed_block_iter_next(bb)) { + if (backed_block_block(bb) > last_block) { + unsigned int blocks = backed_block_block(bb) - last_block; + write_skip_chunk(out, (int64_t)blocks * s->block_size); + } + ret = sparse_file_write_block(out, bb); + if (ret) return ret; + last_block = backed_block_block(bb) + DIV_ROUND_UP(backed_block_len(bb), s->block_size); + } + + pad = s->len - (int64_t)last_block * s->block_size; + assert(pad >= 0); + if (pad > 0) { + write_skip_chunk(out, pad); + } + + return 0; +} + +/* + * This is a workaround for 32-bit Windows: Limit the block size to 64 MB before + * fastboot executable binary for windows 64-bit is released (b/156057250). + */ +#define MAX_BACKED_BLOCK_SIZE ((unsigned int) (64UL << 20)) + +int sparse_file_write(struct sparse_file* s, int fd, bool gz, bool sparse, bool crc) { + struct backed_block* bb; + int ret; + int chunks; + struct output_file* out; + + for (bb = backed_block_iter_new(s->backed_block_list); bb; bb = backed_block_iter_next(bb)) { + ret = backed_block_split(s->backed_block_list, bb, MAX_BACKED_BLOCK_SIZE); + if (ret) return ret; + } + + chunks = sparse_count_chunks(s); + out = output_file_open_fd(fd, s->block_size, s->len, gz, sparse, chunks, crc); + + if (!out) return -ENOMEM; + + ret = write_all_blocks(s, out); + + output_file_close(out); + + return ret; +} + +int sparse_file_callback(struct sparse_file* s, bool sparse, bool crc, + int (*write)(void* priv, const void* data, size_t len), void* priv) { + int ret; + int chunks; + struct output_file* out; + + chunks = sparse_count_chunks(s); + out = output_file_open_callback(write, priv, s->block_size, s->len, false, sparse, chunks, crc); + + if (!out) return -ENOMEM; + + ret = write_all_blocks(s, out); + + output_file_close(out); + + return ret; +} + +struct chunk_data { + void* priv; + unsigned int block; + unsigned int nr_blocks; + int (*write)(void* priv, const void* data, size_t len, unsigned int block, unsigned int nr_blocks); +}; + +static int foreach_chunk_write(void* priv, const void* data, size_t len) { + struct chunk_data* chk = reinterpret_cast(priv); + + return chk->write(chk->priv, data, len, chk->block, chk->nr_blocks); +} + +int sparse_file_foreach_chunk(struct sparse_file* s, bool sparse, bool crc, + int (*write)(void* priv, const void* data, size_t len, + unsigned int block, unsigned int nr_blocks), + void* priv) { + int ret = 0; + int chunks; + struct chunk_data chk; + struct output_file* out; + struct backed_block* bb; + + chk.priv = priv; + chk.write = write; + chk.block = chk.nr_blocks = 0; + chunks = sparse_count_chunks(s); + out = output_file_open_callback(foreach_chunk_write, &chk, s->block_size, s->len, false, sparse, + chunks, crc); + + if (!out) return -ENOMEM; + + for (bb = backed_block_iter_new(s->backed_block_list); bb; bb = backed_block_iter_next(bb)) { + chk.block = backed_block_block(bb); + chk.nr_blocks = (backed_block_len(bb) - 1) / s->block_size + 1; + ret = sparse_file_write_block(out, bb); + if (ret) return ret; + } + + output_file_close(out); + + return ret; +} + +static int out_counter_write(void* priv, const void* data __unused, size_t len) { + int64_t* count = reinterpret_cast(priv); + *count += len; + return 0; +} + +int64_t sparse_file_len(struct sparse_file* s, bool sparse, bool crc) { + int ret; + int chunks = sparse_count_chunks(s); + int64_t count = 0; + struct output_file* out; + + out = output_file_open_callback(out_counter_write, &count, s->block_size, s->len, false, sparse, + chunks, crc); + if (!out) { + return -1; + } + + ret = write_all_blocks(s, out); + + output_file_close(out); + + if (ret < 0) { + return -1; + } + + return count; +} + +unsigned int sparse_file_block_size(struct sparse_file* s) { + return s->block_size; +} + +static struct backed_block* move_chunks_up_to_len(struct sparse_file* from, struct sparse_file* to, + unsigned int len) { + int64_t count = 0; + struct output_file* out_counter; + struct backed_block* last_bb = nullptr; + struct backed_block* bb; + struct backed_block* start; + unsigned int last_block = 0; + int64_t file_len = 0; + int ret; + + /* + * overhead is sparse file header, the potential end skip + * chunk and crc chunk. + */ + int overhead = sizeof(sparse_header_t) + 2 * sizeof(chunk_header_t) + sizeof(uint32_t); + len -= overhead; + + start = backed_block_iter_new(from->backed_block_list); + out_counter = output_file_open_callback(out_counter_write, &count, to->block_size, to->len, false, + true, 0, false); + if (!out_counter) { + return nullptr; + } + + for (bb = start; bb; bb = backed_block_iter_next(bb)) { + count = 0; + if (backed_block_block(bb) > last_block) count += sizeof(chunk_header_t); + last_block = backed_block_block(bb) + DIV_ROUND_UP(backed_block_len(bb), to->block_size); + + /* will call out_counter_write to update count */ + ret = sparse_file_write_block(out_counter, bb); + if (ret) { + bb = nullptr; + goto out; + } + if (file_len + count > len) { + /* + * If the remaining available size is more than 1/8th of the + * requested size, split the chunk. Results in sparse files that + * are at least 7/8ths of the requested size + */ + file_len += sizeof(chunk_header_t); + if (!last_bb || (len - file_len > (len / 8))) { + backed_block_split(from->backed_block_list, bb, len - file_len); + last_bb = bb; + } + goto move; + } + file_len += count; + last_bb = bb; + } + +move: + backed_block_list_move(from->backed_block_list, to->backed_block_list, start, last_bb); + +out: + output_file_close(out_counter); + + return bb; +} + +int sparse_file_resparse(struct sparse_file* in_s, unsigned int max_len, struct sparse_file** out_s, + int out_s_count) { + struct backed_block* bb; + struct sparse_file* s; + struct sparse_file* tmp; + int c = 0; + + tmp = sparse_file_new(in_s->block_size, in_s->len); + if (!tmp) { + return -ENOMEM; + } + + do { + s = sparse_file_new(in_s->block_size, in_s->len); + + bb = move_chunks_up_to_len(in_s, s, max_len); + + if (c < out_s_count) { + out_s[c] = s; + } else { + backed_block_list_move(s->backed_block_list, tmp->backed_block_list, nullptr, nullptr); + sparse_file_destroy(s); + } + c++; + } while (bb); + + backed_block_list_move(tmp->backed_block_list, in_s->backed_block_list, nullptr, nullptr); + + sparse_file_destroy(tmp); + + return c; +} + +void sparse_file_verbose(struct sparse_file* s) { + s->verbose = true; +} diff --git a/aosp/libsparse/sparse/src/main/cpp/sparse_crc32.cpp b/aosp/libsparse/sparse/src/main/cpp/sparse_crc32.cpp new file mode 100644 index 00000000..267322c0 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/sparse_crc32.cpp @@ -0,0 +1,97 @@ +/*- + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +/* + * First, the polynomial itself and its table of feedback terms. The + * polynomial is + * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 + * + * Note that we take it "backwards" and put the highest-order term in + * the lowest-order bit. The X^32 term is "implied"; the LSB is the + * X^31 term, etc. The X^0 term (usually shown as "+1") results in + * the MSB being 1 + * + * Note that the usual hardware shift register implementation, which + * is what we're using (we're merely optimizing it by doing eight-bit + * chunks at a time) shifts bits into the lowest-order term. In our + * implementation, that means shifting towards the right. Why do we + * do it this way? Because the calculated CRC must be transmitted in + * order from highest-order term to lowest-order term. UARTs transmit + * characters in order from LSB to MSB. By storing the CRC this way + * we hand it to the UART in the order low-byte to high-byte; the UART + * sends each low-bit to hight-bit; and the result is transmission bit + * by bit from highest- to lowest-order term without requiring any bit + * shuffling on our part. Reception works similarly + * + * The feedback terms table consists of 256, 32-bit entries. Notes + * + * The table can be generated at runtime if desired; code to do so + * is shown later. It might not be obvious, but the feedback + * terms simply represent the results of eight shift/xor opera + * tions for all combinations of data and CRC register values + * + * The values must be right-shifted by eight bits by the "updcrc + * logic; the shift must be unsigned (bring in zeroes). On some + * hardware you could probably optimize the shift in assembler by + * using byte-swap instructions + * polynomial $edb88320 + * + * + * CRC32 code derived from work by Gary S. Brown. + */ + +/* Code taken from FreeBSD 8 */ +#include +#include + +static uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + +/* + * A function that calculates the CRC-32 based on the table above is + * given below for documentation purposes. An equivalent implementation + * of this function that's actually used in the kernel can be found + * in sys/libkern.h, where it can be inlined. + */ + +uint32_t sparse_crc32(uint32_t crc_in, const void* buf, size_t size) { + const uint8_t* p = reinterpret_cast(buf); + uint32_t crc; + + crc = crc_in ^ ~0U; + while (size--) crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + return crc ^ ~0U; +} diff --git a/aosp/libsparse/sparse/src/main/cpp/sparse_crc32.h b/aosp/libsparse/sparse/src/main/cpp/sparse_crc32.h new file mode 100644 index 00000000..2702c4fa --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/sparse_crc32.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBSPARSE_SPARSE_CRC32_H_ +#define _LIBSPARSE_SPARSE_CRC32_H_ + +#include + +uint32_t sparse_crc32(uint32_t crc, const void* buf, size_t size); + +#endif diff --git a/aosp/libsparse/sparse/src/main/cpp/sparse_defs.h b/aosp/libsparse/sparse/src/main/cpp/sparse_defs.h new file mode 100644 index 00000000..9137805f --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/sparse_defs.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBSPARSE_SPARSE_DEFS_ +#define _LIBSPARSE_SPARSE_DEFS_ + +#include +#include + +#define __le64 u64 +#define __le32 u32 +#define __le16 u16 + +#define __be64 u64 +#define __be32 u32 +#define __be16 u16 + +#define __u64 u64 +#define __u32 u32 +#define __u16 u16 +#define __u8 u8 + +typedef unsigned long long u64; +typedef signed long long s64; +typedef unsigned int u32; +typedef unsigned short int u16; +typedef unsigned char u8; + +#define DIV_ROUND_UP(x, y) (((x) + (y)-1) / (y)) +#define ALIGN(x, y) ((y)*DIV_ROUND_UP((x), (y))) +#define ALIGN_DOWN(x, y) ((y) * ((x) / (y))) + +#define error(fmt, args...) \ + do { \ + fprintf(stderr, "error: %s: " fmt "\n", __func__, ##args); \ + } while (0) +#define error_errno(s, args...) error(s ": %s", ##args, strerror(errno)) + +#endif diff --git a/aosp/libsparse/sparse/src/main/cpp/sparse_err.cpp b/aosp/libsparse/sparse/src/main/cpp/sparse_err.cpp new file mode 100644 index 00000000..6886d31d --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/sparse_err.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +void sparse_default_print(const char* fmt, ...) { + va_list argp; + + va_start(argp, fmt); + vfprintf(stderr, fmt, argp); + va_end(argp); +} + +void (*sparse_print_error)(const char* fmt, ...) = sparse_default_print; +void (*sparse_print_verbose)(const char* fmt, ...) = sparse_default_print; diff --git a/aosp/libsparse/sparse/src/main/cpp/sparse_format.h b/aosp/libsparse/sparse/src/main/cpp/sparse_format.h new file mode 100644 index 00000000..a8a721e6 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/sparse_format.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBSPARSE_SPARSE_FORMAT_H_ +#define _LIBSPARSE_SPARSE_FORMAT_H_ +#include "sparse_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sparse_header { + __le32 magic; /* 0xed26ff3a */ + __le16 major_version; /* (0x1) - reject images with higher major versions */ + __le16 minor_version; /* (0x0) - allow images with higer minor versions */ + __le16 file_hdr_sz; /* 28 bytes for first revision of the file format */ + __le16 chunk_hdr_sz; /* 12 bytes for first revision of the file format */ + __le32 blk_sz; /* block size in bytes, must be a multiple of 4 (4096) */ + __le32 total_blks; /* total blocks in the non-sparse output image */ + __le32 total_chunks; /* total chunks in the sparse input image */ + __le32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" */ + /* as 0. Standard 802.3 polynomial, use a Public Domain */ + /* table implementation */ +} sparse_header_t; + +#define SPARSE_HEADER_MAGIC 0xed26ff3a + +#define CHUNK_TYPE_RAW 0xCAC1 +#define CHUNK_TYPE_FILL 0xCAC2 +#define CHUNK_TYPE_DONT_CARE 0xCAC3 +#define CHUNK_TYPE_CRC32 0xCAC4 + +typedef struct chunk_header { + __le16 chunk_type; /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */ + __le16 reserved1; + __le32 chunk_sz; /* in blocks in output image */ + __le32 total_sz; /* in bytes of chunk input file including chunk header and data */ +} chunk_header_t; + +/* Following a Raw or Fill or CRC32 chunk is data. + * For a Raw chunk, it's the data in chunk_sz * blk_sz. + * For a Fill chunk, it's 4 bytes of the fill data. + * For a CRC32 chunk, it's 4 bytes of CRC32 + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/aosp/libsparse/sparse/src/main/cpp/sparse_read.cpp b/aosp/libsparse/sparse/src/main/cpp/sparse_read.cpp new file mode 100644 index 00000000..c4c18235 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/cpp/sparse_read.cpp @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE64_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "android-base/stringprintf.h" +#include "defs.h" +#include "output_file.h" +#include "sparse_crc32.h" +#include "sparse_file.h" +#include "sparse_format.h" + +#if defined(__APPLE__) && defined(__MACH__) +#define lseek64 lseek +#define off64_t off_t +#endif + +#define SPARSE_HEADER_MAJOR_VER 1 +#define SPARSE_HEADER_LEN (sizeof(sparse_header_t)) +#define CHUNK_HEADER_LEN (sizeof(chunk_header_t)) + +static constexpr int64_t COPY_BUF_SIZE = 1024 * 1024; +static char* copybuf; + +static std::string ErrorString(int err) { + if (err == -EOVERFLOW) return "EOF while reading file"; + if (err == -EINVAL) return "Invalid sparse file format"; + if (err == -ENOMEM) return "Failed allocation while reading file"; + return android::base::StringPrintf("Unknown error %d", err); +} + +class SparseFileSource { + public: + /* Seeks the source ahead by the given offset. */ + virtual void Seek(int64_t offset) = 0; + + /* Return the current offset. */ + virtual int64_t GetOffset() = 0; + + /* Set the current offset. Return 0 if successful. */ + virtual int SetOffset(int64_t offset) = 0; + + /* Adds the given length from the current offset of the source to the file at the given block. + * Return 0 if successful. */ + virtual int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) = 0; + + /* Get data of fixed size from the current offset and seek len bytes. Return 0 if successful. */ + virtual int ReadValue(void* ptr, int len) = 0; + + /* Find the crc32 of the next len bytes and seek ahead len bytes. Return 0 if successful. */ + virtual int GetCrc32(uint32_t* crc32, int64_t len) = 0; + + virtual ~SparseFileSource(){}; +}; + +class SparseFileFdSource : public SparseFileSource { + private: + int fd; + + public: + SparseFileFdSource(int fd) : fd(fd) {} + ~SparseFileFdSource() override {} + + void Seek(int64_t off) override { lseek64(fd, off, SEEK_CUR); } + + int64_t GetOffset() override { return lseek64(fd, 0, SEEK_CUR); } + + int SetOffset(int64_t offset) override { + return lseek64(fd, offset, SEEK_SET) == offset ? 0 : -errno; + } + + int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) override { + return sparse_file_add_fd(s, fd, GetOffset(), len, block); + } + + int ReadValue(void* ptr, int len) override { return read_all(fd, ptr, len); } + + int GetCrc32(uint32_t* crc32, int64_t len) override { + int chunk; + int ret; + while (len) { + chunk = std::min(len, COPY_BUF_SIZE); + ret = read_all(fd, copybuf, chunk); + if (ret < 0) { + return ret; + } + *crc32 = sparse_crc32(*crc32, copybuf, chunk); + len -= chunk; + } + return 0; + } +}; + +class SparseFileBufSource : public SparseFileSource { + private: + char* buf; + int64_t offset; + + public: + SparseFileBufSource(char* buf) : buf(buf), offset(0) {} + ~SparseFileBufSource() override {} + + void Seek(int64_t off) override { + buf += off; + offset += off; + } + + int64_t GetOffset() override { return offset; } + + int SetOffset(int64_t off) override { + buf += off - offset; + offset = off; + return 0; + } + + int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) override { + return sparse_file_add_data(s, buf, len, block); + } + + int ReadValue(void* ptr, int len) override { + memcpy(ptr, buf, len); + Seek(len); + return 0; + } + + int GetCrc32(uint32_t* crc32, int64_t len) override { + *crc32 = sparse_crc32(*crc32, buf, len); + Seek(len); + return 0; + } +}; + +static void verbose_error(bool verbose, int err, const char* fmt, ...) { + if (!verbose) return; + + std::string msg = ErrorString(err); + if (fmt) { + msg += " at "; + va_list argp; + va_start(argp, fmt); + android::base::StringAppendV(&msg, fmt, argp); + va_end(argp); + } + sparse_print_verbose("%s\n", msg.c_str()); +} + +static int process_raw_chunk(struct sparse_file* s, unsigned int chunk_size, + SparseFileSource* source, unsigned int blocks, unsigned int block, + uint32_t* crc32) { + int ret; + int64_t len = blocks * s->block_size; + + if (chunk_size % s->block_size != 0) { + return -EINVAL; + } + + if (chunk_size / s->block_size != blocks) { + return -EINVAL; + } + + ret = source->AddToSparseFile(s, len, block); + if (ret < 0) { + return ret; + } + + if (crc32) { + ret = source->GetCrc32(crc32, len); + if (ret < 0) { + return ret; + } + } else { + source->Seek(len); + } + + return 0; +} + +static int process_fill_chunk(struct sparse_file* s, unsigned int chunk_size, + SparseFileSource* source, unsigned int blocks, unsigned int block, + uint32_t* crc32) { + int ret; + int chunk; + int64_t len = (int64_t)blocks * s->block_size; + uint32_t fill_val; + uint32_t* fillbuf; + unsigned int i; + + if (chunk_size != sizeof(fill_val)) { + return -EINVAL; + } + + ret = source->ReadValue(&fill_val, sizeof(fill_val)); + if (ret < 0) { + return ret; + } + + ret = sparse_file_add_fill(s, fill_val, len, block); + if (ret < 0) { + return ret; + } + + if (crc32) { + /* Fill copy_buf with the fill value */ + fillbuf = (uint32_t*)copybuf; + for (i = 0; i < (COPY_BUF_SIZE / sizeof(fill_val)); i++) { + fillbuf[i] = fill_val; + } + + while (len) { + chunk = std::min(len, COPY_BUF_SIZE); + *crc32 = sparse_crc32(*crc32, copybuf, chunk); + len -= chunk; + } + } + + return 0; +} + +static int process_skip_chunk(struct sparse_file* s, unsigned int chunk_size, + SparseFileSource* source __unused, unsigned int blocks, + unsigned int block __unused, uint32_t* crc32) { + if (chunk_size != 0) { + return -EINVAL; + } + + if (crc32) { + int64_t len = (int64_t)blocks * s->block_size; + memset(copybuf, 0, COPY_BUF_SIZE); + + while (len) { + int chunk = std::min(len, COPY_BUF_SIZE); + *crc32 = sparse_crc32(*crc32, copybuf, chunk); + len -= chunk; + } + } + + return 0; +} + +static int process_crc32_chunk(SparseFileSource* source, unsigned int chunk_size, uint32_t* crc32) { + uint32_t file_crc32; + + if (chunk_size != sizeof(file_crc32)) { + return -EINVAL; + } + + int ret = source->ReadValue(&file_crc32, sizeof(file_crc32)); + if (ret < 0) { + return ret; + } + + if (crc32 != nullptr && file_crc32 != *crc32) { + return -EINVAL; + } + + return 0; +} + +static int process_chunk(struct sparse_file* s, SparseFileSource* source, unsigned int chunk_hdr_sz, + chunk_header_t* chunk_header, unsigned int cur_block, uint32_t* crc_ptr) { + int ret; + unsigned int chunk_data_size; + int64_t offset = source->GetOffset(); + + chunk_data_size = chunk_header->total_sz - chunk_hdr_sz; + + switch (chunk_header->chunk_type) { + case CHUNK_TYPE_RAW: + ret = + process_raw_chunk(s, chunk_data_size, source, chunk_header->chunk_sz, cur_block, crc_ptr); + if (ret < 0) { + verbose_error(s->verbose, ret, "data block at %" PRId64, offset); + return ret; + } + return chunk_header->chunk_sz; + case CHUNK_TYPE_FILL: + ret = process_fill_chunk(s, chunk_data_size, source, chunk_header->chunk_sz, cur_block, + crc_ptr); + if (ret < 0) { + verbose_error(s->verbose, ret, "fill block at %" PRId64, offset); + return ret; + } + return chunk_header->chunk_sz; + case CHUNK_TYPE_DONT_CARE: + ret = process_skip_chunk(s, chunk_data_size, source, chunk_header->chunk_sz, cur_block, + crc_ptr); + if (chunk_data_size != 0) { + if (ret < 0) { + verbose_error(s->verbose, ret, "skip block at %" PRId64, offset); + return ret; + } + } + return chunk_header->chunk_sz; + case CHUNK_TYPE_CRC32: + ret = process_crc32_chunk(source, chunk_data_size, crc_ptr); + if (ret < 0) { + verbose_error(s->verbose, -EINVAL, "crc block at %" PRId64, offset); + return ret; + } + return 0; + default: + verbose_error(s->verbose, -EINVAL, "unknown block %04X at %" PRId64, chunk_header->chunk_type, + offset); + } + + return 0; +} + +static int sparse_file_read_sparse(struct sparse_file* s, SparseFileSource* source, bool crc) { + int ret; + unsigned int i; + sparse_header_t sparse_header; + chunk_header_t chunk_header; + uint32_t crc32 = 0; + uint32_t* crc_ptr = nullptr; + unsigned int cur_block = 0; + + if (!copybuf) { + copybuf = (char*)malloc(COPY_BUF_SIZE); + } + + if (!copybuf) { + return -ENOMEM; + } + + if (crc) { + crc_ptr = &crc32; + } + + ret = source->ReadValue(&sparse_header, sizeof(sparse_header)); + if (ret < 0) { + return ret; + } + + if (sparse_header.magic != SPARSE_HEADER_MAGIC) { + return -EINVAL; + } + + if (sparse_header.major_version != SPARSE_HEADER_MAJOR_VER) { + return -EINVAL; + } + + if (sparse_header.file_hdr_sz < SPARSE_HEADER_LEN) { + return -EINVAL; + } + + if (sparse_header.chunk_hdr_sz < sizeof(chunk_header)) { + return -EINVAL; + } + + if (sparse_header.file_hdr_sz > SPARSE_HEADER_LEN) { + /* Skip the remaining bytes in a header that is longer than + * we expected. + */ + source->Seek(sparse_header.file_hdr_sz - SPARSE_HEADER_LEN); + } + + for (i = 0; i < sparse_header.total_chunks; i++) { + ret = source->ReadValue(&chunk_header, sizeof(chunk_header)); + if (ret < 0) { + return ret; + } + + if (sparse_header.chunk_hdr_sz > CHUNK_HEADER_LEN) { + /* Skip the remaining bytes in a header that is longer than + * we expected. + */ + source->Seek(sparse_header.chunk_hdr_sz - CHUNK_HEADER_LEN); + } + + ret = process_chunk(s, source, sparse_header.chunk_hdr_sz, &chunk_header, cur_block, crc_ptr); + if (ret < 0) { + return ret; + } + + cur_block += ret; + } + + if (sparse_header.total_blks != cur_block) { + return -EINVAL; + } + + return 0; +} + +static int sparse_file_read_normal(struct sparse_file* s, int fd) { + int ret; + uint32_t* buf = (uint32_t*)malloc(s->block_size); + unsigned int block = 0; + int64_t remain = s->len; + int64_t offset = 0; + unsigned int to_read; + unsigned int i; + bool sparse_block; + + if (!buf) { + return -ENOMEM; + } + + while (remain > 0) { + to_read = std::min(remain, (int64_t)(s->block_size)); + ret = read_all(fd, buf, to_read); + if (ret < 0) { + error("failed to read sparse file"); + free(buf); + return ret; + } + + if (to_read == s->block_size) { + sparse_block = true; + for (i = 1; i < s->block_size / sizeof(uint32_t); i++) { + if (buf[0] != buf[i]) { + sparse_block = false; + break; + } + } + } else { + sparse_block = false; + } + + if (sparse_block) { + /* TODO: add flag to use skip instead of fill for buf[0] == 0 */ + sparse_file_add_fill(s, buf[0], to_read, block); + } else { + sparse_file_add_fd(s, fd, offset, to_read, block); + } + + remain -= to_read; + offset += to_read; + block++; + } + + free(buf); + return 0; +} + +int sparse_file_read(struct sparse_file* s, int fd, bool sparse, bool crc) { + if (crc && !sparse) { + return -EINVAL; + } + + if (sparse) { + SparseFileFdSource source(fd); + return sparse_file_read_sparse(s, &source, crc); + } else { + return sparse_file_read_normal(s, fd); + } +} + +int sparse_file_read_buf(struct sparse_file* s, char* buf, bool crc) { + SparseFileBufSource source(buf); + return sparse_file_read_sparse(s, &source, crc); +} + +static struct sparse_file* sparse_file_import_source(SparseFileSource* source, bool verbose, + bool crc) { + int ret; + sparse_header_t sparse_header; + int64_t len; + struct sparse_file* s; + + ret = source->ReadValue(&sparse_header, sizeof(sparse_header)); + if (ret < 0) { + verbose_error(verbose, ret, "header"); + return nullptr; + } + + if (sparse_header.magic != SPARSE_HEADER_MAGIC) { + verbose_error(verbose, -EINVAL, "header magic"); + return nullptr; + } + + if (sparse_header.major_version != SPARSE_HEADER_MAJOR_VER) { + verbose_error(verbose, -EINVAL, "header major version"); + return nullptr; + } + + if (sparse_header.file_hdr_sz < SPARSE_HEADER_LEN) { + return nullptr; + } + + if (sparse_header.chunk_hdr_sz < sizeof(chunk_header_t)) { + return nullptr; + } + + len = (int64_t)sparse_header.total_blks * sparse_header.blk_sz; + s = sparse_file_new(sparse_header.blk_sz, len); + if (!s) { + verbose_error(verbose, -EINVAL, nullptr); + return nullptr; + } + + ret = source->SetOffset(0); + if (ret < 0) { + verbose_error(verbose, ret, "seeking"); + sparse_file_destroy(s); + return nullptr; + } + + s->verbose = verbose; + + ret = sparse_file_read_sparse(s, source, crc); + if (ret < 0) { + sparse_file_destroy(s); + return nullptr; + } + + return s; +} + +struct sparse_file* sparse_file_import(int fd, bool verbose, bool crc) { + SparseFileFdSource source(fd); + return sparse_file_import_source(&source, verbose, crc); +} + +struct sparse_file* sparse_file_import_buf(char* buf, bool verbose, bool crc) { + SparseFileBufSource source(buf); + return sparse_file_import_source(&source, verbose, crc); +} + +struct sparse_file* sparse_file_import_auto(int fd, bool crc, bool verbose) { + struct sparse_file* s; + int64_t len; + int ret; + + s = sparse_file_import(fd, verbose, crc); + if (s) { + return s; + } + + len = lseek64(fd, 0, SEEK_END); + if (len < 0) { + return nullptr; + } + + lseek64(fd, 0, SEEK_SET); + + s = sparse_file_new(4096, len); + if (!s) { + return nullptr; + } + + ret = sparse_file_read_normal(s, fd); + if (ret < 0) { + sparse_file_destroy(s); + return nullptr; + } + + return s; +} diff --git a/aosp/libsparse/sparse/src/main/public/backed_block.h b/aosp/libsparse/sparse/src/main/public/backed_block.h new file mode 100644 index 00000000..3a754606 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/public/backed_block.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _BACKED_BLOCK_H_ +#define _BACKED_BLOCK_H_ + +#include + +struct backed_block_list; +struct backed_block; + +enum backed_block_type { + BACKED_BLOCK_DATA, + BACKED_BLOCK_FILE, + BACKED_BLOCK_FD, + BACKED_BLOCK_FILL, +}; + +int backed_block_add_data(struct backed_block_list* bbl, void* data, unsigned int len, + unsigned int block); +int backed_block_add_fill(struct backed_block_list* bbl, unsigned int fill_val, unsigned int len, + unsigned int block); +int backed_block_add_file(struct backed_block_list* bbl, const char* filename, int64_t offset, + unsigned int len, unsigned int block); +int backed_block_add_fd(struct backed_block_list* bbl, int fd, int64_t offset, unsigned int len, + unsigned int block); + +struct backed_block* backed_block_iter_new(struct backed_block_list* bbl); +struct backed_block* backed_block_iter_next(struct backed_block* bb); +unsigned int backed_block_len(struct backed_block* bb); +unsigned int backed_block_block(struct backed_block* bb); +void* backed_block_data(struct backed_block* bb); +const char* backed_block_filename(struct backed_block* bb); +int backed_block_fd(struct backed_block* bb); +int64_t backed_block_file_offset(struct backed_block* bb); +uint32_t backed_block_fill_val(struct backed_block* bb); +enum backed_block_type backed_block_type(struct backed_block* bb); +int backed_block_split(struct backed_block_list* bbl, struct backed_block* bb, unsigned int max_len); + +struct backed_block* backed_block_iter_new(struct backed_block_list* bbl); +struct backed_block* backed_block_iter_next(struct backed_block* bb); + +struct backed_block_list* backed_block_list_new(unsigned int block_size); +void backed_block_list_destroy(struct backed_block_list* bbl); + +void backed_block_list_move(struct backed_block_list* from, struct backed_block_list* to, + struct backed_block* start, struct backed_block* end); + +#endif diff --git a/aosp/libsparse/sparse/src/main/public/sparse/sparse.h b/aosp/libsparse/sparse/src/main/public/sparse/sparse.h new file mode 100644 index 00000000..3d5fb0c5 --- /dev/null +++ b/aosp/libsparse/sparse/src/main/public/sparse/sparse.h @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBSPARSE_SPARSE_H_ +#define _LIBSPARSE_SPARSE_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct sparse_file; + +// The callbacks in sparse_file_callback() and sparse_file_foreach_chunk() take +// size_t as the length type (was `int` in past). This allows clients to keep +// their codes compatibile with both versions as needed. +#define SPARSE_CALLBACK_USES_SIZE_T + +/** + * sparse_file_new - create a new sparse file cookie + * + * @block_size - minimum size of a chunk + * @len - size of the expanded sparse file. + * + * Creates a new sparse_file cookie that can be used to associate data + * blocks. Can later be written to a file with a variety of options. + * block_size specifies the minimum size of a chunk in the file. The maximum + * size of the file is 2**32 * block_size (16TB for 4k block size). + * + * Returns the sparse file cookie, or NULL on error. + */ +struct sparse_file *sparse_file_new(unsigned int block_size, int64_t len); + +/** + * sparse_file_destroy - destroy a sparse file cookie + * + * @s - sparse file cookie + * + * Destroys a sparse file cookie. After destroy, all memory passed in to + * sparse_file_add_data can be freed by the caller + */ +void sparse_file_destroy(struct sparse_file *s); + +/** + * sparse_file_add_data - associate a data chunk with a sparse file + * + * @s - sparse file cookie + * @data - pointer to data block + * @len - length of the data block + * @block - offset in blocks into the sparse file to place the data chunk + * + * Associates a data chunk with a sparse file cookie. The region + * [block * block_size : block * block_size + len) must not already be used in + * the sparse file. If len is not a multiple of the block size the data + * will be padded with zeros. + * + * The data pointer must remain valid until the sparse file is closed or the + * data block is removed from the sparse file. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_add_data(struct sparse_file *s, + void *data, unsigned int len, unsigned int block); + +/** + * sparse_file_add_fill - associate a fill chunk with a sparse file + * + * @s - sparse file cookie + * @fill_val - 32 bit fill data + * @len - length of the fill block + * @block - offset in blocks into the sparse file to place the fill chunk + * + * Associates a chunk filled with fill_val with a sparse file cookie. + * The region [block * block_size : block * block_size + len) must not already + * be used in the sparse file. If len is not a multiple of the block size the + * data will be padded with zeros. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_add_fill(struct sparse_file *s, + uint32_t fill_val, unsigned int len, unsigned int block); + +/** + * sparse_file_add_file - associate a chunk of a file with a sparse file + * + * @s - sparse file cookie + * @filename - filename of the file to be copied + * @file_offset - offset into the copied file + * @len - length of the copied block + * @block - offset in blocks into the sparse file to place the file chunk + * + * Associates a chunk of an existing file with a sparse file cookie. + * The region [block * block_size : block * block_size + len) must not already + * be used in the sparse file. If len is not a multiple of the block size the + * data will be padded with zeros. + * + * Allows adding large amounts of data to a sparse file without needing to keep + * it all mapped. File size is limited by available virtual address space, + * exceptionally large files may need to be added in multiple chunks. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_add_file(struct sparse_file *s, + const char *filename, int64_t file_offset, unsigned int len, + unsigned int block); + +/** + * sparse_file_add_file - associate a chunk of a file with a sparse file + * + * @s - sparse file cookie + * @filename - filename of the file to be copied + * @file_offset - offset into the copied file + * @len - length of the copied block + * @block - offset in blocks into the sparse file to place the file chunk + * + * Associates a chunk of an existing fd with a sparse file cookie. + * The region [block * block_size : block * block_size + len) must not already + * be used in the sparse file. If len is not a multiple of the block size the + * data will be padded with zeros. + * + * Allows adding large amounts of data to a sparse file without needing to keep + * it all mapped. File size is limited by available virtual address space, + * exceptionally large files may need to be added in multiple chunks. + * + * The fd must remain open until the sparse file is closed or the fd block is + * removed from the sparse file. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_add_fd(struct sparse_file *s, + int fd, int64_t file_offset, unsigned int len, unsigned int block); + +/** + * sparse_file_write - write a sparse file to a file + * + * @s - sparse file cookie + * @fd - file descriptor to write to + * @gz - write a gzipped file + * @sparse - write in the Android sparse file format + * @crc - append a crc chunk + * + * Writes a sparse file to a file. If gz is true, the data will be passed + * through zlib. If sparse is true, the file will be written in the Android + * sparse file format. If sparse is false, the file will be written by seeking + * over unused chunks, producing a smaller file if the filesystem supports + * sparse files. If crc is true, the crc of the expanded data will be + * calculated and appended in a crc chunk. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_write(struct sparse_file *s, int fd, bool gz, bool sparse, + bool crc); + +/** + * sparse_file_len - return the length of a sparse file if written to disk + * + * @s - sparse file cookie + * @sparse - write in the Android sparse file format + * @crc - append a crc chunk + * + * Returns the size a sparse file would be on disk if it were written in the + * specified format. If sparse is true, this is the size of the data in the + * sparse format. If sparse is false, this is the size of the normal + * non-sparse file. + */ +int64_t sparse_file_len(struct sparse_file *s, bool sparse, bool crc); + +/** + * sparse_file_block_size + * + * @s - sparse file cookie + */ +unsigned int sparse_file_block_size(struct sparse_file *s); + +/** + * sparse_file_callback - call a callback for blocks in sparse file + * + * @s - sparse file cookie + * @sparse - write in the Android sparse file format + * @crc - append a crc chunk + * @write - function to call for each block + * @priv - value that will be passed as the first argument to write + * + * Writes a sparse file by calling a callback function. If sparse is true, the + * file will be written in the Android sparse file format. If crc is true, the + * crc of the expanded data will be calculated and appended in a crc chunk. + * The callback 'write' will be called with data and length for each data, + * and with data==NULL to skip over a region (only used for non-sparse format). + * The callback should return negative on error, 0 on success. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_callback(struct sparse_file *s, bool sparse, bool crc, + int (*write)(void *priv, const void *data, size_t len), void *priv); + +/** + * sparse_file_foreach_chunk - call a callback for data blocks in sparse file + * + * @s - sparse file cookie + * @sparse - write in the Android sparse file format + * @crc - append a crc chunk + * @write - function to call for each block + * @priv - value that will be passed as the first argument to write + * + * The function has the same behavior as 'sparse_file_callback', except it only + * iterates on blocks that contain data. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_foreach_chunk(struct sparse_file *s, bool sparse, bool crc, + int (*write)(void *priv, const void *data, size_t len, unsigned int block, + unsigned int nr_blocks), + void *priv); +/** + * sparse_file_read - read a file into a sparse file cookie + * + * @s - sparse file cookie + * @fd - file descriptor to read from + * @sparse - read a file in the Android sparse file format + * @crc - verify the crc of a file in the Android sparse file format + * + * Reads a file into a sparse file cookie. If sparse is true, the file is + * assumed to be in the Android sparse file format. If sparse is false, the + * file will be sparsed by looking for block aligned chunks of all zeros or + * another 32 bit value. If crc is true, the crc of the sparse file will be + * verified. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_read(struct sparse_file *s, int fd, bool sparse, bool crc); + +/** + * sparse_file_read_buf - read a buffer into a sparse file cookie + * + * @s - sparse file cookie + * @buf - buffer to read from + * @crc - verify the crc of a file in the Android sparse file format + * + * Reads a buffer into a sparse file cookie. The buffer must remain + * valid until the sparse file cookie is freed. If crc is true, the + * crc of the sparse file will be verified. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_read_buf(struct sparse_file *s, char *buf, bool crc); + +/** + * sparse_file_import - import an existing sparse file + * + * @fd - file descriptor to read from + * @verbose - print verbose errors while reading the sparse file + * @crc - verify the crc of a file in the Android sparse file format + * + * Reads an existing sparse file into a sparse file cookie, recreating the same + * sparse cookie that was used to write it. If verbose is true, prints verbose + * errors when the sparse file is formatted incorrectly. + * + * Returns a new sparse file cookie on success, NULL on error. + */ +struct sparse_file *sparse_file_import(int fd, bool verbose, bool crc); + +/** + * sparse_file_import_buf - import an existing sparse file from a buffer + * + * @buf - buffer to read from + * @verbose - print verbose errors while reading the sparse file + * @crc - verify the crc of a file in the Android sparse file format + * + * Reads existing sparse file data into a sparse file cookie, recreating the same + * sparse cookie that was used to write it. If verbose is true, prints verbose + * errors when the sparse file is formatted incorrectly. + * + * Returns a new sparse file cookie on success, NULL on error. + */ +struct sparse_file *sparse_file_import_buf(char* buf, bool verbose, bool crc); + +/** + * sparse_file_import_auto - import an existing sparse or normal file + * + * @fd - file descriptor to read from + * @crc - verify the crc of a file in the Android sparse file format + * @verbose - whether to use verbose logging + * + * Reads an existing sparse or normal file into a sparse file cookie. + * Attempts to determine if the file is sparse or not by looking for the sparse + * file magic number in the first 4 bytes. If the file is not sparse, the file + * will be sparsed by looking for block aligned chunks of all zeros or another + * 32 bit value. If crc is true, the crc of the sparse file will be verified. + * + * Returns a new sparse file cookie on success, NULL on error. + */ +struct sparse_file *sparse_file_import_auto(int fd, bool crc, bool verbose); + +/** sparse_file_resparse - rechunk an existing sparse file into smaller files + * + * @in_s - sparse file cookie of the existing sparse file + * @max_len - maximum file size + * @out_s - array of sparse file cookies + * @out_s_count - size of out_s array + * + * Splits chunks of an existing sparse file into smaller sparse files such that + * each sparse file is less than max_len. Returns the number of sparse_files + * that would have been written to out_s if out_s were big enough. + */ +int sparse_file_resparse(struct sparse_file *in_s, unsigned int max_len, + struct sparse_file **out_s, int out_s_count); + +/** + * sparse_file_verbose - set a sparse file cookie to print verbose errors + * + * @s - sparse file cookie + * + * Print verbose sparse file errors whenever using the sparse file cookie. + */ +void sparse_file_verbose(struct sparse_file *s); + +/** + * sparse_print_verbose - function called to print verbose errors + * + * By default, verbose errors will print to standard error. + * sparse_print_verbose may be overridden to log verbose errors somewhere else. + * + */ +extern void (*sparse_print_verbose)(const char *fmt, ...); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/aosp/libsparse/sparse/src/main/public/sparse_file.h b/aosp/libsparse/sparse/src/main/public/sparse_file.h new file mode 100644 index 00000000..e565f63c --- /dev/null +++ b/aosp/libsparse/sparse/src/main/public/sparse_file.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBSPARSE_SPARSE_FILE_H_ +#define _LIBSPARSE_SPARSE_FILE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct sparse_file { + unsigned int block_size; + int64_t len; + bool verbose; + + struct backed_block_list* backed_block_list; + struct output_file* out; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSPARSE_SPARSE_FILE_H_ */ diff --git a/aosp/libxbc/COPYING b/aosp/libxbc/COPYING new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/aosp/libxbc/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/aosp/libxbc/libxbc.c b/aosp/libxbc/libxbc.c new file mode 100644 index 00000000..d80ac119 --- /dev/null +++ b/aosp/libxbc/libxbc.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "libxbc.h" + +#define BOOTCONFIG_MAGIC "#BOOTCONFIG\n" +#define BOOTCONFIG_MAGIC_SIZE 12 +#define BOOTCONFIG_SIZE_SIZE 4 +#define BOOTCONFIG_CHECKSUM_SIZE 4 +#define BOOTCONFIG_TRAILER_SIZE BOOTCONFIG_MAGIC_SIZE + \ + BOOTCONFIG_SIZE_SIZE + \ + BOOTCONFIG_CHECKSUM_SIZE +/* + * Simple checksum for a buffer. + * + * @param addr pointer to the start of the buffer. + * @param size size of the buffer in bytes. + * @return check sum result. + */ +static uint32_t checksum(const unsigned char* const buffer, uint32_t size) { + uint32_t sum = 0; + for (uint32_t i = 0; i < size; i++) { + sum += buffer[i]; + } + return sum; +} + +/* + * Check if the bootconfig trailer is present within the bootconfig section. + * + * @param bootconfig_end_addr address of the end of the bootconfig section. If + * the trailer is present, it will be directly preceding this address. + * @return true if the trailer is present, false if not. + */ +static bool isTrailerPresent(uint64_t bootconfig_end_addr) { + return !strncmp((char*)(bootconfig_end_addr - BOOTCONFIG_MAGIC_SIZE), + BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_SIZE); +} + +/* + * Add a string of boot config parameters to memory appended by the trailer. + */ +int32_t addBootConfigParameters(const char* params, uint32_t params_size, + uint64_t bootconfig_start_addr, uint32_t bootconfig_size) { + if (!params || !bootconfig_start_addr) { + return -1; + } + if (params_size == 0) { + return 0; + } + int32_t applied_bytes = 0; + int32_t new_size = 0; + uint64_t end = bootconfig_start_addr + bootconfig_size; + + if (isTrailerPresent(end)) { + end -= BOOTCONFIG_TRAILER_SIZE; + applied_bytes -= BOOTCONFIG_TRAILER_SIZE; + memcpy(&new_size, (void *)end, BOOTCONFIG_SIZE_SIZE); + } else { + new_size = bootconfig_size; + } + + // params + memcpy((void*)end, params, params_size); + + applied_bytes += params_size; + applied_bytes += addBootConfigTrailer(bootconfig_start_addr, + bootconfig_size + applied_bytes); + + return applied_bytes; +} + +/* + * Add boot config trailer. + */ +int32_t addBootConfigTrailer(uint64_t bootconfig_start_addr, + uint32_t bootconfig_size) { + if (!bootconfig_start_addr) { + return -1; + } + if (bootconfig_size == 0) { + return 0; + } + uint64_t end = bootconfig_start_addr + bootconfig_size; + + if (isTrailerPresent(end)) { + // no need to overwrite the current trailers + return 0; + } + + // size + memcpy((void *)(end), &bootconfig_size, BOOTCONFIG_SIZE_SIZE); + + // checksum + uint32_t sum = + checksum((unsigned char*)bootconfig_start_addr, bootconfig_size); + memcpy((void *)(end + BOOTCONFIG_SIZE_SIZE), &sum, + BOOTCONFIG_CHECKSUM_SIZE); + + // magic + memcpy((void *)(end + BOOTCONFIG_SIZE_SIZE + BOOTCONFIG_CHECKSUM_SIZE), + BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_SIZE); + + return BOOTCONFIG_TRAILER_SIZE; +} diff --git a/aosp/libxbc/libxbc.h b/aosp/libxbc/libxbc.h new file mode 100644 index 00000000..bd10adf8 --- /dev/null +++ b/aosp/libxbc/libxbc.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBXBC_H_ +#define LIBXBC_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// memcpy and strncmp +//#include + +/* + * Add a string of boot config parameters to memory appended by the trailer. + * This memory needs to be immediately following the end of the ramdisks. + * The new boot config trailer will be written to the end of the entire + * parameter section(previous + new). The trailer contains a 4 byte size of the + * parameters, followed by a 4 byte checksum of the parameters, followed by a 12 + * byte magic string. + * + * @param params pointer to string of boot config parameters + * @param params_size size of params string in bytes + * @param bootconfig_start_addr address that the boot config section is starting + * at in memory. + * @param bootconfig_size size of the current bootconfig section in bytes. + * @return number of bytes added to the boot config section. -1 for error. + */ +int addBootConfigParameters(const char *params, uint32_t params_size, + uint64_t bootconfig_start_addr, + uint32_t bootconfig_size); + +/* + * Add the boot config trailer to the end of the boot config parameter section. + * This can be used after the vendor bootconfig section has been placed into + * memory if there are no additional parameters that need to be added. + * The new boot config trailer will be written to the end of the entire + * parameter section at (bootconfig_start_addr + bootconfig_size). + * The trailer contains a 4 byte size of the parameters, followed by a 4 byte + * checksum of the parameters, followed by a 12 byte magic string. + * + * @param bootconfig_start_addr address that the boot config section is starting + * at in memory. + * @param bootconfig_size size of the current bootconfig section in bytes. + * @return number of bytes added to the boot config section. -1 for error. + */ +int addBootConfigTrailer(uint64_t bootconfig_start_addr, + uint32_t bootconfig_size); +#ifdef __cplusplus +} +#endif + +#endif /* LIBXBC_H_ */ diff --git a/aosp/libxbc/main.cpp b/aosp/libxbc/main.cpp new file mode 100644 index 00000000..952054d7 --- /dev/null +++ b/aosp/libxbc/main.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static_assert(sizeof(uint64_t) == 8, "uint64_t 8 bytes"); +static_assert(sizeof(void*) == 8, "void* 8 bytes"); + +void dumpBuf(void* buf, size_t bufSize, const char* outFile) { + int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC; + printf("dumping buffer to %s...\n", outFile); + auto fd = open(outFile, flags, 0644); + if (fd == -1) { + printf("fail to open file %s(%s)\n", outFile, strerror(errno)); + exit(1); + } + size_t bytesWrite = write(fd, (char*)buf, bufSize); + if (bytesWrite != bufSize) { + printf("write failed. exp=%zu, act=%ld\n", bufSize, bytesWrite); + exit(2); + } + close(fd); +} + +int main(int, char**) { + size_t bufSize = 256; + uint64_t buf = reinterpret_cast(malloc(bufSize)); + uint32_t bootConfigSize = 0; + if (!buf) { + printf("malloc failed\n"); + exit(1); + } + + { // param1 + char* ANDROID_BOOT_PARAM = (char*)"androidboot.xx=yy\n"; + auto ret = addBootConfigParameters(ANDROID_BOOT_PARAM, strlen(ANDROID_BOOT_PARAM), buf, + bootConfigSize); + if (ret <= 0) { + printf("fail to apply boot config params\n"); + exit(1); + } else { + printf("addBootConfigParameters() ret = %d\n", ret); + bootConfigSize += ret; + } + dumpBuf((void*)buf, bufSize, "tmp.1"); + } + + { // param2 + char* param2 = (char*)"k1=v1\nk2=v2\nk3=v3\n"; + auto ret = addBootConfigParameters(param2, strlen(param2), buf, bootConfigSize); + if (ret <= 0) { + printf("fail to apply boot config param2\n"); + exit(1); + } else { + printf("addBootConfigParameters() ret = %d\n", ret); + bootConfigSize += ret; + } + dumpBuf((void*)buf, bufSize, "tmp.2"); + } + + { // param3 + char* param3 = + (char*)"vendorboot_k1=vendorboot_v1\nvendorboot_k2=vendorboot_v2\nvendorboot_k3=" + "vendorboot_v3\n"; + auto ret = addBootConfigParameters(param3, strlen(param3), buf, bootConfigSize); + if (ret <= 0) { + printf("fail to apply boot config param3\n"); + exit(1); + } else { + printf("addBootConfigParameters() ret = %d\n", ret); + bootConfigSize += ret; + } + dumpBuf((void*)buf, bufSize, "tmp.3"); + dumpBuf((void*)buf, bootConfigSize, "tmp.final"); + } + + free((void*)buf); + return 0; +} diff --git a/aosp/libxbc/meson.build b/aosp/libxbc/meson.build new file mode 100644 index 00000000..a7fac1f7 --- /dev/null +++ b/aosp/libxbc/meson.build @@ -0,0 +1,8 @@ +project('libxbc', 'c', 'cpp', + version : '0.1', + default_options : ['warning_level=3']) + +executable('xbc', + 'libxbc.c', + 'main.cpp', + install : true) diff --git a/aosp/make/target/product/gsi/testkey_rsa2048.pem b/aosp/make/target/product/gsi/testkey_rsa2048.pem new file mode 100644 index 00000000..64de31cf --- /dev/null +++ b/aosp/make/target/product/gsi/testkey_rsa2048.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA3fDgwU4JKVRHhAfofi/g8daTNplB2mTJCX9fIMy9FnZDXNij +1zijRQ8HKbt3bAGImQvb3GxSV4M5eIdiLDUF7RsUpE7K+s939i/AaTtcuyqimQbJ +QjP9emTsgngHzuKWMg1mwlRZYDfdv62zIQmZcbM9a0CZE36hAYvEBiDB8qT4ob++ +godGAx3rpF2Wi7mhIYDINvkCw8/16Qi9CZgvOUrEolt3mz8Sps41z9j7YAsPbAa8 +fg7dUu61s6NkZEykl4G67loOaf7h+SyP//LpFZ0gV+STZ+EMGofL0SXb8A+hdIYE +QxsnKUYo8e+GaQg92FLxVZqcfyG3AZuMB04R1QIDAQABAoIBAQDGj3/1UaSepjlJ +ZW3an2lH1Cpm2ZxyEGNQLPVluead1vaTdXq3zYM9AKHu8zp3lbOpAVQVk4/jnZJo +Q+9QD6waonTIP3oYBE+WIMirHSHsjctkzw52PV9VBkAWxd5ueIfZheXejGpdy/2H +RJcTQqxWbf7QGr4ZE9xmLq4UsW/zbXwy8qGEp9eMQIIaWBua43FkqmWYLSnVFVJI +Gl8mfVJctLNSZHhS3tKiV8up6NxZlDjO8o7kYVFCkv0xJ9yzQNBc3P2MEmvfZ06D +QnimHBqSxr0M9X6hqP43CnqtCbpsHS8A12Dm4l6fkXfkrAY0UNrEaCSDb8aN7TEc +7bc1MB4NAoGBAPK7xSuvQE9CH05Iy+G6mEQTtNmpfcQosqhi6dF60h4bqlkeGzUu +gF/PKHwwffHAxQSv4V831P3A/IoJFa9IFkg218mYPNfzpj4vJA4aNCDp+SYZAIYm +h6hMOmuByI97wds2yCBGt4mP0eow5B3A1b3UQeqW6LVSuobZ22QVlSk/AoGBAOoS +L82yda9hUa7vuXtqTraf9EGjSXhyjoPqWxa+a1ooI9l24f7mokS5Iof+a/SLfPUj +pwj8eOeOZksjAaWJIdrRb3TaYLaqhDkWQeV5N5XxYbn3+TvVJQyR+OSBfGoEpVP/ +IS6fusvpT3eULJDax10By+gDcoLT5M1FNs4rBIvrAoGBAM8yJP5DHDwLjzl9vjsy +0iLaR3e8zBQTQV2nATvFAXKd3u0vW74rsX0XEdHgesFP8V0s3M4wlGj+wRL66j2y +5QJDfjMg9l7IJlHSX46CI5ks33X7xYy9evLYDs4R/Kct1q5OtsmGU8jisSadETus +jUb61kFvC7krovjVIgbuvWJ1AoGAVikzp4gVgeVU6AwePqu3JcpjYvX0SX4Br9VI +imq1oY49BAOa1PWYratoZp7kpjPiX2osRkaJStNEHExagtCjwaRuXpk0GIlT+p+S +yiGAsJUV4BrDh57B8IqbD6IKZgwnv2+ei0cIv562PdIxRXEDCd1rbZA3SqktA9KC +hgmXttkCgYBPU1lqRpwoHP9lpOBTDa6/Xi6WaDEWrG/tUF/wMlvrZ4hEVMDJRs1d +9JCXBxL/O4TMvpmyVKBZW15iZOcLM3EpiZ00UD+ChcAaFstup+oYKrs8gL9hgyTd +cvWMxGQm13KwSj2CLzEQpPAN5xG14njXaee5ksshxkzBz9z3MVWiiw== +-----END RSA PRIVATE KEY----- diff --git a/aosp/make/tools/extract_kernel.py b/aosp/make/tools/extract_kernel.py new file mode 100755 index 00000000..44fbcdfd --- /dev/null +++ b/aosp/make/tools/extract_kernel.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +A tool to extract kernel information from a kernel image. +""" + +import argparse +import subprocess +import sys +import re + +CONFIG_PREFIX = b'IKCFG_ST' +GZIP_HEADER = b'\037\213\010' +COMPRESSION_ALGO = ( + (["gzip", "-d"], GZIP_HEADER), + (["xz", "-d"], b'\3757zXZ\000'), + (["bzip2", "-d"], b'BZh'), + (["lz4", "-d", "-l"], b'\002\041\114\030'), + + # These are not supported in the build system yet. + # (["unlzma"], b'\135\0\0\0'), + # (["lzop", "-d"], b'\211\114\132'), +) + +# "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@" +# LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n"; +LINUX_BANNER_PREFIX = b'Linux version ' +LINUX_BANNER_REGEX = LINUX_BANNER_PREFIX.decode() + \ + r'(?P(?P[0-9]+[.][0-9]+[.][0-9]+).*) \(.*@.*\) \((?P.*)\) .*\n' + + +def get_from_release(input_bytes, start_idx, key): + null_idx = input_bytes.find(b'\x00', start_idx) + if null_idx < 0: + return None + try: + linux_banner = input_bytes[start_idx:null_idx].decode() + except UnicodeDecodeError: + return None + mo = re.match(LINUX_BANNER_REGEX, linux_banner) + if mo: + return mo.group(key) + return None + + +def dump_from_release(input_bytes, key): + """ + Helper of dump_version and dump_release + """ + idx = 0 + while True: + idx = input_bytes.find(LINUX_BANNER_PREFIX, idx) + if idx < 0: + return None + + value = get_from_release(input_bytes, idx, key) + if value: + return value.encode() + + idx += len(LINUX_BANNER_PREFIX) + + +def dump_version(input_bytes): + """ + Dump kernel version, w.x.y, from input_bytes. Search for the string + "Linux version " and do pattern matching after it. See LINUX_BANNER_REGEX. + """ + return dump_from_release(input_bytes, "version") + + +def dump_compiler(input_bytes): + """ + Dump kernel version, w.x.y, from input_bytes. Search for the string + "Linux version " and do pattern matching after it. See LINUX_BANNER_REGEX. + """ + return dump_from_release(input_bytes, "compiler") + + +def dump_release(input_bytes): + """ + Dump kernel release, w.x.y-..., from input_bytes. Search for the string + "Linux version " and do pattern matching after it. See LINUX_BANNER_REGEX. + """ + return dump_from_release(input_bytes, "release") + + +def dump_configs(input_bytes): + """ + Dump kernel configuration from input_bytes. This can be done when + CONFIG_IKCONFIG is enabled, which is a requirement on Treble devices. + + The kernel configuration is archived in GZip format right after the magic + string 'IKCFG_ST' in the built kernel. + """ + + # Search for magic string + GZip header + idx = input_bytes.find(CONFIG_PREFIX + GZIP_HEADER) + if idx < 0: + return None + + # Seek to the start of the archive + idx += len(CONFIG_PREFIX) + + sp = subprocess.Popen(["gzip", "-d", "-c"], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + o, _ = sp.communicate(input=input_bytes[idx:]) + if sp.returncode == 1: # error + return None + + # success or trailing garbage warning + assert sp.returncode in (0, 2), sp.returncode + + return o + + +def try_decompress_bytes(cmd, input_bytes): + sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + o, _ = sp.communicate(input=input_bytes) + # ignore errors + return o + + +def try_decompress(cmd, search_bytes, input_bytes): + idx = 0 + while True: + idx = input_bytes.find(search_bytes, idx) + if idx < 0: + return + + yield try_decompress_bytes(cmd, input_bytes[idx:]) + idx += 1 + + +def decompress_dump(func, input_bytes): + """ + Run func(input_bytes) first; and if that fails (returns value evaluates to + False), then try different decompression algorithm before running func. + """ + o = func(input_bytes) + if o: + return o + for cmd, search_bytes in COMPRESSION_ALGO: + for decompressed in try_decompress(cmd, search_bytes, input_bytes): + if decompressed: + o = decompress_dump(func, decompressed) + if o: + return o + # Force decompress the whole file even if header doesn't match + decompressed = try_decompress_bytes(cmd, input_bytes) + if decompressed: + o = decompress_dump(func, decompressed) + if o: + return o + + +def dump_to_file(f, dump_fn, input_bytes, desc): + """ + Call decompress_dump(dump_fn, input_bytes) and write to f. If it fails, return + False; otherwise return True. + """ + if f is not None: + o = decompress_dump(dump_fn, input_bytes) + if o: + f.write(o) + else: + sys.stderr.write( + "Cannot extract kernel {}".format(desc)) + return False + return True + +def to_bytes_io(b): + """ + Make b, which is either sys.stdout or sys.stdin, receive bytes as arguments. + """ + return b.buffer if sys.version_info.major == 3 else b + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter, + description=__doc__ + + "\nThese algorithms are tried when decompressing the image:\n " + + " ".join(tup[0][0] for tup in COMPRESSION_ALGO)) + parser.add_argument('--input', + help='Input kernel image. If not specified, use stdin', + metavar='FILE', + type=argparse.FileType('rb'), + default=to_bytes_io(sys.stdin)) + parser.add_argument('--output-configs', + help='If specified, write configs. Use stdout if no file ' + 'is specified.', + metavar='FILE', + nargs='?', + type=argparse.FileType('wb'), + const=to_bytes_io(sys.stdout)) + parser.add_argument('--output-version', + help='If specified, write version. Use stdout if no file ' + 'is specified.', + metavar='FILE', + nargs='?', + type=argparse.FileType('wb'), + const=to_bytes_io(sys.stdout)) + parser.add_argument('--output-release', + help='If specified, write kernel release. Use stdout if ' + 'no file is specified.', + metavar='FILE', + nargs='?', + type=argparse.FileType('wb'), + const=to_bytes_io(sys.stdout)) + parser.add_argument('--output-compiler', + help='If specified, write the compiler information. Use stdout if no file ' + 'is specified.', + metavar='FILE', + nargs='?', + type=argparse.FileType('wb'), + const=to_bytes_io(sys.stdout)) + parser.add_argument('--tools', + help='Decompression tools to use. If not specified, PATH ' + 'is searched.', + metavar='ALGORITHM:EXECUTABLE', + nargs='*') + args = parser.parse_args() + + tools = {pair[0]: pair[1] + for pair in (token.split(':') for token in args.tools or [])} + for cmd, _ in COMPRESSION_ALGO: + if cmd[0] in tools: + cmd[0] = tools[cmd[0]] + + input_bytes = args.input.read() + + ret = 0 + if not dump_to_file(args.output_configs, dump_configs, input_bytes, + "configs in {}".format(args.input.name)): + ret = 1 + if not dump_to_file(args.output_version, dump_version, input_bytes, + "version in {}".format(args.input.name)): + ret = 1 + if not dump_to_file(args.output_release, dump_release, input_bytes, + "kernel release in {}".format(args.input.name)): + ret = 1 + + if not dump_to_file(args.output_compiler, dump_compiler, input_bytes, + "kernel compiler in {}".format(args.input.name)): + ret = 1 + + return ret + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/aosp/plugged/bin/e2fsdroid b/aosp/plugged/bin/e2fsdroid new file mode 100755 index 00000000..f6fb3157 Binary files /dev/null and b/aosp/plugged/bin/e2fsdroid differ diff --git a/aosp/plugged/bin/fec b/aosp/plugged/bin/fec new file mode 100755 index 00000000..e0ce1603 Binary files /dev/null and b/aosp/plugged/bin/fec differ diff --git a/aosp/plugged/bin/mkfs.erofs b/aosp/plugged/bin/mkfs.erofs new file mode 100755 index 00000000..3f021fb7 Binary files /dev/null and b/aosp/plugged/bin/mkfs.erofs differ diff --git a/aosp/plugged/bin/sefcontext_compile b/aosp/plugged/bin/sefcontext_compile new file mode 100755 index 00000000..747dea61 Binary files /dev/null and b/aosp/plugged/bin/sefcontext_compile differ diff --git a/aosp/plugged/lib/libc++.so b/aosp/plugged/lib/libc++.so new file mode 100755 index 00000000..e1ff4292 Binary files /dev/null and b/aosp/plugged/lib/libc++.so differ diff --git a/aosp/plugged/res/file_contexts.concat b/aosp/plugged/res/file_contexts.concat new file mode 100644 index 00000000..df6f637a --- /dev/null +++ b/aosp/plugged/res/file_contexts.concat @@ -0,0 +1,1404 @@ +#line 1 "out/target/product/panther/obj/ETC/file_contexts.bin_intermediates/file_contexts.local.tmp" +#line 1 "system/sepolicy/private/file_contexts" +########################################### +# Root +/ u:object_r:rootfs:s0 + +# Data files +/adb_keys u:object_r:adb_keys_file:s0 +/build\.prop u:object_r:rootfs:s0 +/default\.prop u:object_r:rootfs:s0 +/fstab\..* u:object_r:rootfs:s0 +/init\..* u:object_r:rootfs:s0 +/res(/.*)? u:object_r:rootfs:s0 +/selinux_version u:object_r:rootfs:s0 +/ueventd\..* u:object_r:rootfs:s0 +/verity_key u:object_r:rootfs:s0 + +# Executables +/init u:object_r:init_exec:s0 +/sbin(/.*)? u:object_r:rootfs:s0 + +# For kernel modules +/lib(/.*)? u:object_r:rootfs:s0 +/system_dlkm(/.*)? u:object_r:system_dlkm_file:s0 + +# Empty directories +/lost\+found u:object_r:rootfs:s0 +/acct u:object_r:cgroup:s0 +/config u:object_r:rootfs:s0 +/data_mirror u:object_r:mirror_data_file:s0 +/debug_ramdisk u:object_r:tmpfs:s0 +/mnt u:object_r:tmpfs:s0 +/proc u:object_r:rootfs:s0 +/second_stage_resources u:object_r:tmpfs:s0 +/sys u:object_r:sysfs:s0 +/apex u:object_r:apex_mnt_dir:s0 + +# Postinstall directories +/postinstall u:object_r:postinstall_mnt_dir:s0 +/postinstall/apex u:object_r:postinstall_apex_mnt_dir:s0 + +/apex/(\.(bootstrap|default)-)?apex-info-list.xml u:object_r:apex_info_file:s0 + +# Symlinks +/bin u:object_r:rootfs:s0 +/bugreports u:object_r:rootfs:s0 +/charger u:object_r:rootfs:s0 +/d u:object_r:rootfs:s0 +/etc u:object_r:rootfs:s0 +/sdcard u:object_r:rootfs:s0 + +# SELinux policy files +/vendor_file_contexts u:object_r:file_contexts_file:s0 +/plat_file_contexts u:object_r:file_contexts_file:s0 +/product_file_contexts u:object_r:file_contexts_file:s0 +/mapping_sepolicy\.cil u:object_r:sepolicy_file:s0 +/plat_sepolicy\.cil u:object_r:sepolicy_file:s0 +/plat_property_contexts u:object_r:property_contexts_file:s0 +/product_property_contexts u:object_r:property_contexts_file:s0 +/vendor_property_contexts u:object_r:property_contexts_file:s0 +/seapp_contexts u:object_r:seapp_contexts_file:s0 +/vendor_seapp_contexts u:object_r:seapp_contexts_file:s0 +/plat_seapp_contexts u:object_r:seapp_contexts_file:s0 +/sepolicy u:object_r:sepolicy_file:s0 +/plat_service_contexts u:object_r:service_contexts_file:s0 +/plat_hwservice_contexts u:object_r:hwservice_contexts_file:s0 +/plat_keystore2_key_contexts u:object_r:keystore2_key_contexts_file:s0 +/vendor_service_contexts u:object_r:vendor_service_contexts_file:s0 +/vendor_hwservice_contexts u:object_r:hwservice_contexts_file:s0 +/vndservice_contexts u:object_r:vndservice_contexts_file:s0 + +########################## +# Devices +# +/dev(/.*)? u:object_r:device:s0 +/dev/adf[0-9]* u:object_r:graphics_device:s0 +/dev/adf-interface[0-9]*\.[0-9]* u:object_r:graphics_device:s0 +/dev/adf-overlay-engine[0-9]*\.[0-9]* u:object_r:graphics_device:s0 +/dev/ashmem u:object_r:ashmem_device:s0 +/dev/ashmem(.*)? u:object_r:ashmem_libcutils_device:s0 +/dev/audio.* u:object_r:audio_device:s0 +/dev/binder u:object_r:binder_device:s0 +/dev/block(/.*)? u:object_r:block_device:s0 +/dev/block/by-name/zoned_device u:object_r:zoned_block_device:s0 +/dev/block/dm-[0-9]+ u:object_r:dm_device:s0 +/dev/block/ublkb[0-9]+ u:object_r:ublk_block_device:s0 +/dev/block/loop[0-9]* u:object_r:loop_device:s0 +/dev/block/vd[a-z][0-9]* u:object_r:vd_device:s0 +/dev/block/vold/.+ u:object_r:vold_device:s0 +/dev/block/ram[0-9]* u:object_r:ram_device:s0 +/dev/block/zram[0-9]* u:object_r:ram_device:s0 +/dev/boringssl/selftest(/.*)? u:object_r:boringssl_self_test_marker:s0 +/dev/bus/usb(.*)? u:object_r:usb_device:s0 +/dev/console u:object_r:console_device:s0 +/dev/cpu_variant:.* u:object_r:dev_cpu_variant:s0 +/dev/dma_heap(/.*)? u:object_r:dmabuf_heap_device:s0 +/dev/dma_heap/system u:object_r:dmabuf_system_heap_device:s0 +/dev/dma_heap/system-uncached u:object_r:dmabuf_system_heap_device:s0 +/dev/dma_heap/system-secure(.*) u:object_r:dmabuf_system_secure_heap_device:s0 +/dev/dm-user(/.*)? u:object_r:dm_user_device:s0 +/dev/ublk-control u:object_r:ublk_control_device:s0 +/dev/device-mapper u:object_r:dm_device:s0 +/dev/eac u:object_r:audio_device:s0 +/dev/event-log-tags u:object_r:runtime_event_log_tags_file:s0 +/dev/cgroup_info(/.*)? u:object_r:cgroup_rc_file:s0 +/dev/fscklogs(/.*)? u:object_r:fscklogs:s0 +/dev/fuse u:object_r:fuse_device:s0 +/dev/gnss[0-9]+ u:object_r:gnss_device:s0 +/dev/graphics(/.*)? u:object_r:graphics_device:s0 +/dev/hw_random u:object_r:hw_random_device:s0 +/dev/hwbinder u:object_r:hwbinder_device:s0 +/dev/input(/.*)? u:object_r:input_device:s0 +/dev/iio:device[0-9]+ u:object_r:iio_device:s0 +/dev/ion u:object_r:ion_device:s0 +/dev/keychord u:object_r:keychord_device:s0 +/dev/loop-control u:object_r:loop_control_device:s0 +/dev/modem.* u:object_r:radio_device:s0 +/dev/mtp_usb u:object_r:mtp_device:s0 +/dev/pmsg0 u:object_r:pmsg_device:s0 +/dev/pn544 u:object_r:nfc_device:s0 +/dev/port u:object_r:port_device:s0 +/dev/ppp u:object_r:ppp_device:s0 +/dev/ptmx u:object_r:ptmx_device:s0 +/dev/pvrsrvkm u:object_r:gpu_device:s0 +/dev/kmsg u:object_r:kmsg_device:s0 +/dev/kmsg_debug u:object_r:kmsg_debug_device:s0 +/dev/kvm u:object_r:kvm_device:s0 +/dev/null u:object_r:null_device:s0 +/dev/nvhdcp1 u:object_r:video_device:s0 +/dev/random u:object_r:random_device:s0 +/dev/rpmsg-omx[0-9] u:object_r:rpmsg_device:s0 +/dev/rproc_user u:object_r:rpmsg_device:s0 +/dev/rtc[0-9] u:object_r:rtc_device:s0 +/dev/snd(/.*)? u:object_r:audio_device:s0 +/dev/socket(/.*)? u:object_r:socket_device:s0 +/dev/socket/adbd u:object_r:adbd_socket:s0 +/dev/socket/dnsproxyd u:object_r:dnsproxyd_socket:s0 +/dev/socket/dumpstate u:object_r:dumpstate_socket:s0 +/dev/socket/fwmarkd u:object_r:fwmarkd_socket:s0 +/dev/socket/lmkd u:object_r:lmkd_socket:s0 +/dev/socket/logd u:object_r:logd_socket:s0 +/dev/socket/logdr u:object_r:logdr_socket:s0 +/dev/socket/logdw u:object_r:logdw_socket:s0 +/dev/socket/statsdw u:object_r:statsdw_socket:s0 +/dev/socket/mdns u:object_r:mdns_socket:s0 +/dev/socket/mdnsd u:object_r:mdnsd_socket:s0 +/dev/socket/mtpd u:object_r:mtpd_socket:s0 +/dev/socket/pdx/system/buffer_hub u:object_r:pdx_bufferhub_dir:s0 +/dev/socket/pdx/system/buffer_hub/client u:object_r:pdx_bufferhub_client_endpoint_socket:s0 +/dev/socket/pdx/system/performance u:object_r:pdx_performance_dir:s0 +/dev/socket/pdx/system/performance/client u:object_r:pdx_performance_client_endpoint_socket:s0 +/dev/socket/pdx/system/vr/display u:object_r:pdx_display_dir:s0 +/dev/socket/pdx/system/vr/display/client u:object_r:pdx_display_client_endpoint_socket:s0 +/dev/socket/pdx/system/vr/display/manager u:object_r:pdx_display_manager_endpoint_socket:s0 +/dev/socket/pdx/system/vr/display/screenshot u:object_r:pdx_display_screenshot_endpoint_socket:s0 +/dev/socket/pdx/system/vr/display/vsync u:object_r:pdx_display_vsync_endpoint_socket:s0 +/dev/socket/prng_seeder u:object_r:prng_seeder_socket:s0 +/dev/socket/property_service u:object_r:property_socket:s0 +/dev/socket/property_service_for_system u:object_r:property_socket:s0 +/dev/socket/racoon u:object_r:racoon_socket:s0 +/dev/socket/recovery u:object_r:recovery_socket:s0 +/dev/socket/rild u:object_r:rild_socket:s0 +/dev/socket/rild-debug u:object_r:rild_debug_socket:s0 +/dev/socket/snapuserd u:object_r:snapuserd_socket:s0 +/dev/socket/snapuserd_proxy u:object_r:snapuserd_proxy_socket:s0 +/dev/socket/tombstoned_crash u:object_r:tombstoned_crash_socket:s0 +/dev/socket/tombstoned_java_trace u:object_r:tombstoned_java_trace_socket:s0 +/dev/socket/tombstoned_intercept u:object_r:tombstoned_intercept_socket:s0 +/dev/socket/traced_consumer u:object_r:traced_consumer_socket:s0 +/dev/socket/traced_perf u:object_r:traced_perf_socket:s0 +/dev/socket/traced_producer u:object_r:traced_producer_socket:s0 +/dev/socket/heapprofd u:object_r:heapprofd_socket:s0 +/dev/socket/uncrypt u:object_r:uncrypt_socket:s0 +/dev/socket/wpa_eth[0-9] u:object_r:wpa_socket:s0 +/dev/socket/wpa_wlan[0-9] u:object_r:wpa_socket:s0 +/dev/socket/zygote u:object_r:zygote_socket:s0 +/dev/socket/zygote_secondary u:object_r:zygote_socket:s0 +/dev/socket/usap_pool_primary u:object_r:zygote_socket:s0 +/dev/socket/usap_pool_secondary u:object_r:zygote_socket:s0 +/dev/spdif_out.* u:object_r:audio_device:s0 +/dev/sys/block/by-name/rootdisk(/.*)? u:object_r:rootdisk_sysdev:s0 +/dev/sys/block/by-name/userdata(/.*)? u:object_r:userdata_sysdev:s0 +/dev/sys/fs/by-name/userdata(/.*)? u:object_r:userdata_sysdev:s0 +/dev/tty u:object_r:owntty_device:s0 +/dev/tty[0-9]* u:object_r:tty_device:s0 +/dev/ttyS[0-9]* u:object_r:serial_device:s0 +/dev/ttyUSB[0-9]* u:object_r:usb_serial_device:s0 +/dev/ttyACM[0-9]* u:object_r:usb_serial_device:s0 +/dev/tun u:object_r:tun_device:s0 +/dev/uhid u:object_r:uhid_device:s0 +/dev/uinput u:object_r:uhid_device:s0 +/dev/uio[0-9]* u:object_r:uio_device:s0 +/dev/urandom u:object_r:random_device:s0 +/dev/usb_accessory u:object_r:usbaccessory_device:s0 +/dev/v4l-touch[0-9]* u:object_r:input_device:s0 +/dev/vhost-vsock u:object_r:kvm_device:s0 +/dev/video[0-9]* u:object_r:video_device:s0 +/dev/vndbinder u:object_r:vndbinder_device:s0 +/dev/watchdog u:object_r:watchdog_device:s0 +/dev/xt_qtaguid u:object_r:qtaguid_device:s0 +/dev/zero u:object_r:zero_device:s0 +/dev/__properties__ u:object_r:properties_device:s0 +/dev/__properties__/property_info u:object_r:property_info:s0 +############################# +# Linker configuration +# +/linkerconfig(/.*)? u:object_r:linkerconfig_file:s0 + +# Apex sepoolicy files. +/dev/selinux/apex_file_contexts u:object_r:file_contexts_file:s0 +/dev/selinux/apex_seapp_contexts u:object_r:seapp_contexts_file:s0 +/dev/selinux/apex_service_contexts u:object_r:service_contexts_file:s0 +/dev/selinux/apex_property_contexts u:object_r:property_contexts_file:s0 +/dev/selinux/apex_hwservice_contexts u:object_r:hwservice_contexts_file:s0 +/dev/selinux/apex_mac_permissions\.xml u:object_r:mac_perms_file:s0 + +############################# +# System files +# +/system(/.*)? u:object_r:system_file:s0 +/system/apex/com.android.art u:object_r:art_apex_dir:s0 +/system/lib(64)?(/.*)? u:object_r:system_lib_file:s0 +/system/lib(64)?/bootstrap(/.*)? u:object_r:system_bootstrap_lib_file:s0 +/system/bin/mm_events u:object_r:mm_events_exec:s0 +/system/bin/atrace u:object_r:atrace_exec:s0 +/system/bin/auditctl u:object_r:auditctl_exec:s0 +/system/bin/bcc u:object_r:rs_exec:s0 +/system/bin/blank_screen u:object_r:blank_screen_exec:s0 +/system/bin/boringssl_self_test(32|64) u:object_r:boringssl_self_test_exec:s0 +/system/bin/prng_seeder u:object_r:prng_seeder_exec:s0 +/system/bin/charger u:object_r:charger_exec:s0 +/system/bin/e2fsdroid u:object_r:e2fs_exec:s0 +/system/bin/mke2fs u:object_r:e2fs_exec:s0 +/system/bin/e2fsck -- u:object_r:fsck_exec:s0 +/system/bin/extra_free_kbytes\.sh u:object_r:extra_free_kbytes_exec:s0 +/system/bin/fsck\.exfat -- u:object_r:fsck_exec:s0 +/system/bin/fsck\.f2fs -- u:object_r:fsck_exec:s0 +/system/bin/ntfsfix -- u:object_r:fsck_exec:s0 +/system/bin/ntfs-3g -- u:object_r:fuseblkd_untrusted_exec:s0 +/system/bin/ntfs-3g-compart -- u:object_r:fuseblkd_exec:s0 +/system/bin/init u:object_r:init_exec:s0 +# TODO(/123600489): merge mini-keyctl into toybox +/system/bin/mini-keyctl -- u:object_r:toolbox_exec:s0 +/system/bin/fsverity_init u:object_r:fsverity_init_exec:s0 +/system/bin/sload_f2fs -- u:object_r:e2fs_exec:s0 +/system/bin/make_f2fs -- u:object_r:e2fs_exec:s0 +/system/bin/fsck_msdos -- u:object_r:fsck_exec:s0 +/system/bin/tcpdump -- u:object_r:tcpdump_exec:s0 +/system/bin/tune2fs -- u:object_r:fsck_exec:s0 +/system/bin/resize2fs -- u:object_r:fsck_exec:s0 +/system/bin/toolbox -- u:object_r:toolbox_exec:s0 +/system/bin/toybox -- u:object_r:toolbox_exec:s0 +/system/bin/ld\.mc u:object_r:rs_exec:s0 +/system/bin/logcat -- u:object_r:logcat_exec:s0 +/system/bin/logcatd -- u:object_r:logcat_exec:s0 +/system/bin/sh -- u:object_r:shell_exec:s0 +/system/bin/run-as -- u:object_r:runas_exec:s0 +/system/bin/bootanimation u:object_r:bootanim_exec:s0 +/system/bin/bootstat u:object_r:bootstat_exec:s0 +/system/bin/app_process32 u:object_r:zygote_exec:s0 +/system/bin/app_process64 u:object_r:zygote_exec:s0 +/system/bin/servicemanager u:object_r:servicemanager_exec:s0 +/system/bin/hwservicemanager u:object_r:hwservicemanager_exec:s0 +/system/bin/surfaceflinger u:object_r:surfaceflinger_exec:s0 +/system/bin/gpuservice u:object_r:gpuservice_exec:s0 +/system/bin/bufferhubd u:object_r:bufferhubd_exec:s0 +/system/bin/performanced u:object_r:performanced_exec:s0 +/system/bin/drmserver u:object_r:drmserver_exec:s0 +/system/bin/drmserver32 u:object_r:drmserver_exec:s0 +/system/bin/drmserver64 u:object_r:drmserver_exec:s0 +/system/bin/dumpstate u:object_r:dumpstate_exec:s0 +/system/bin/incident u:object_r:incident_exec:s0 +/system/bin/incidentd u:object_r:incidentd_exec:s0 +/system/bin/incident_helper u:object_r:incident_helper_exec:s0 +/system/bin/iw u:object_r:iw_exec:s0 +/system/bin/netutils-wrapper-1\.0 u:object_r:netutils_wrapper_exec:s0 +/system/bin/vold u:object_r:vold_exec:s0 +/system/bin/netd u:object_r:netd_exec:s0 +/system/bin/wificond u:object_r:wificond_exec:s0 +/system/bin/audioserver u:object_r:audioserver_exec:s0 +/system/bin/mediadrmserver u:object_r:mediadrmserver_exec:s0 +/system/bin/mediaserver u:object_r:mediaserver_exec:s0 +/system/bin/mediaserver32 u:object_r:mediaserver_exec:s0 +/system/bin/mediaserver64 u:object_r:mediaserver_exec:s0 +/system/bin/mediametrics u:object_r:mediametrics_exec:s0 +/system/bin/cameraserver u:object_r:cameraserver_exec:s0 +/system/bin/mediaextractor u:object_r:mediaextractor_exec:s0 +/system/bin/mediaswcodec u:object_r:mediaswcodec_exec:s0 +/system/bin/mediatranscoding u:object_r:mediatranscoding_exec:s0 +/system/bin/mediatuner u:object_r:mediatuner_exec:s0 +/system/bin/mdnsd u:object_r:mdnsd_exec:s0 +/system/bin/installd u:object_r:installd_exec:s0 +/system/bin/otapreopt_chroot u:object_r:otapreopt_chroot_exec:s0 +/system/bin/otapreopt_slot u:object_r:otapreopt_slot_exec:s0 +/system/bin/credstore u:object_r:credstore_exec:s0 +/system/bin/keystore u:object_r:keystore_exec:s0 +/system/bin/keystore2 u:object_r:keystore_exec:s0 +/system/bin/fingerprintd u:object_r:fingerprintd_exec:s0 +/system/bin/gatekeeperd u:object_r:gatekeeperd_exec:s0 +/system/bin/tombstoned u:object_r:tombstoned_exec:s0 +/system/bin/recovery-persist u:object_r:recovery_persist_exec:s0 +/system/bin/recovery-refresh u:object_r:recovery_refresh_exec:s0 +/system/bin/sdcard u:object_r:sdcardd_exec:s0 +/system/bin/snapshotctl u:object_r:snapshotctl_exec:s0 +/system/bin/remount u:object_r:remount_exec:s0 +/system/bin/dhcpcd u:object_r:dhcp_exec:s0 +/system/bin/dhcpcd-6\.8\.2 u:object_r:dhcp_exec:s0 +/system/bin/dmesgd u:object_r:dmesgd_exec:s0 +/system/bin/mtpd u:object_r:mtp_exec:s0 +/system/bin/pppd u:object_r:ppp_exec:s0 +/system/bin/racoon u:object_r:racoon_exec:s0 +/system/xbin/su u:object_r:su_exec:s0 +/system/bin/dnsmasq u:object_r:dnsmasq_exec:s0 +/system/bin/linker(64)? u:object_r:system_linker_exec:s0 +/system/bin/linkerconfig u:object_r:linkerconfig_exec:s0 +/system/bin/bootstrap/linker(64)? u:object_r:system_linker_exec:s0 +/system/bin/bootstrap/linkerconfig u:object_r:linkerconfig_exec:s0 +/system/bin/llkd u:object_r:llkd_exec:s0 +/system/bin/lmkd u:object_r:lmkd_exec:s0 +/system/bin/usbd u:object_r:usbd_exec:s0 +/system/bin/inputflinger u:object_r:inputflinger_exec:s0 +/system/bin/logd u:object_r:logd_exec:s0 +/system/bin/lpdumpd u:object_r:lpdumpd_exec:s0 +/system/bin/rss_hwm_reset u:object_r:rss_hwm_reset_exec:s0 +/system/bin/perfetto u:object_r:perfetto_exec:s0 +/system/bin/mtectrl u:object_r:mtectrl_exec:s0 +/system/bin/traced u:object_r:traced_exec:s0 +/system/bin/traced_perf u:object_r:traced_perf_exec:s0 +/system/bin/traced_probes u:object_r:traced_probes_exec:s0 +/system/bin/heapprofd u:object_r:heapprofd_exec:s0 +/system/bin/uncrypt u:object_r:uncrypt_exec:s0 +/system/bin/update_verifier u:object_r:update_verifier_exec:s0 +/system/bin/logwrapper u:object_r:system_file:s0 +/system/bin/vdc u:object_r:vdc_exec:s0 +/system/bin/cppreopts\.sh u:object_r:cppreopts_exec:s0 +/system/bin/preloads_copy\.sh u:object_r:preloads_copy_exec:s0 +/system/bin/preopt2cachename u:object_r:preopt2cachename_exec:s0 +/system/bin/viewcompiler u:object_r:viewcompiler_exec:s0 +/system/bin/sgdisk u:object_r:sgdisk_exec:s0 +/system/bin/blkid u:object_r:blkid_exec:s0 +/system/bin/flags_health_check -- u:object_r:flags_health_check_exec:s0 +/system/bin/idmap2(d)? u:object_r:idmap_exec:s0 +/system/bin/update_engine u:object_r:update_engine_exec:s0 +/system/bin/profcollectd u:object_r:profcollectd_exec:s0 +/system/bin/profcollectctl u:object_r:profcollectd_exec:s0 +/system/bin/storaged u:object_r:storaged_exec:s0 +/system/bin/virtual_touchpad u:object_r:virtual_touchpad_exec:s0 +/system/bin/hw/android\.frameworks\.bufferhub@1\.0-service u:object_r:fwk_bufferhub_exec:s0 +/system/bin/hw/android\.hidl\.allocator@1\.0-service u:object_r:hal_allocator_default_exec:s0 +/system/bin/hw/android\.system\.suspend-service u:object_r:system_suspend_exec:s0 +/system/etc/cgroups\.json u:object_r:cgroup_desc_file:s0 +/system/etc/task_profiles/cgroups_[0-9]+\.json u:object_r:cgroup_desc_api_file:s0 +/system/etc/event-log-tags u:object_r:system_event_log_tags_file:s0 +/system/etc/group u:object_r:system_group_file:s0 +/system/etc/ld\.config.* u:object_r:system_linker_config_file:s0 +/system/etc/passwd u:object_r:system_passwd_file:s0 +/system/etc/seccomp_policy(/.*)? u:object_r:system_seccomp_policy_file:s0 +/system/etc/security/cacerts(/.*)? u:object_r:system_security_cacerts_file:s0 +/system/etc/selinux/mapping/[0-9]+\.[0-9]+\.cil u:object_r:sepolicy_file:s0 +/system/etc/selinux/plat_mac_permissions\.xml u:object_r:mac_perms_file:s0 +/system/etc/selinux/plat_property_contexts u:object_r:property_contexts_file:s0 +/system/etc/selinux/plat_service_contexts u:object_r:service_contexts_file:s0 +/system/etc/selinux/plat_hwservice_contexts u:object_r:hwservice_contexts_file:s0 +/system/etc/selinux/plat_keystore2_key_contexts u:object_r:keystore2_key_contexts_file:s0 +/system/etc/selinux/plat_file_contexts u:object_r:file_contexts_file:s0 +/system/etc/selinux/plat_seapp_contexts u:object_r:seapp_contexts_file:s0 +/system/etc/selinux/plat_sepolicy\.cil u:object_r:sepolicy_file:s0 +/system/etc/selinux/plat_and_mapping_sepolicy\.cil\.sha256 u:object_r:sepolicy_file:s0 +/system/etc/task_profiles\.json u:object_r:task_profiles_file:s0 +/system/etc/task_profiles/task_profiles_[0-9]+\.json u:object_r:task_profiles_api_file:s0 +/system/usr/share/zoneinfo(/.*)? u:object_r:system_zoneinfo_file:s0 +/system/bin/adbd u:object_r:adbd_exec:s0 +/system/bin/vold_prepare_subdirs u:object_r:vold_prepare_subdirs_exec:s0 +/system/bin/stats u:object_r:stats_exec:s0 +/system/bin/statsd u:object_r:statsd_exec:s0 +/system/bin/bpfloader u:object_r:bpfloader_exec:s0 +/system/bin/btfloader u:object_r:bpfloader_exec:s0 +/system/bin/watchdogd u:object_r:watchdogd_exec:s0 +/system/bin/apexd u:object_r:apexd_exec:s0 +/system/bin/gsid u:object_r:gsid_exec:s0 +/system/bin/simpleperf u:object_r:simpleperf_exec:s0 +/system/bin/simpleperf_app_runner u:object_r:simpleperf_app_runner_exec:s0 +/system/bin/migrate_legacy_obb_data u:object_r:migrate_legacy_obb_data_exec:s0 +/system/bin/android\.frameworks\.automotive\.display@1\.0-service u:object_r:automotive_display_service_exec:s0 +/system/bin/snapuserd u:object_r:snapuserd_exec:s0 +/system/bin/odsign u:object_r:odsign_exec:s0 +/system/bin/vehicle_binding_util u:object_r:vehicle_binding_util_exec:s0 +/system/bin/cardisplayproxyd u:object_r:automotive_display_service_exec:s0 +/system/bin/evsmanagerd u:object_r:evsmanagerd_exec:s0 +/system/bin/android\.automotive\.evs\.manager@1\.[0-9]+ u:object_r:evsmanagerd_exec:s0 + +############################# +# Vendor files +# +/(vendor|system/vendor)(/.*)? u:object_r:vendor_file:s0 +/(vendor|system/vendor)/bin/sh u:object_r:vendor_shell_exec:s0 +/(vendor|system/vendor)/bin/toybox_vendor u:object_r:vendor_toolbox_exec:s0 +/(vendor|system/vendor)/bin/toolbox u:object_r:vendor_toolbox_exec:s0 +/(vendor|system/vendor)/etc(/.*)? u:object_r:vendor_configs_file:s0 +/(vendor|system/vendor)/etc/cgroups\.json u:object_r:vendor_cgroup_desc_file:s0 +/(vendor|system/vendor)/etc/task_profiles\.json u:object_r:vendor_task_profiles_file:s0 + +/(vendor|system/vendor)/lib(64)?/egl(/.*)? u:object_r:same_process_hal_file:s0 + +/(vendor|system/vendor)/lib(64)?/vndk-sp(/.*)? u:object_r:vndk_sp_file:s0 + +/(vendor|system/vendor)/manifest\.xml u:object_r:vendor_configs_file:s0 +/(vendor|system/vendor)/compatibility_matrix\.xml u:object_r:vendor_configs_file:s0 +/(vendor|system/vendor)/etc/vintf(/.*)? u:object_r:vendor_configs_file:s0 +/(vendor|system/vendor)/app(/.*)? u:object_r:vendor_app_file:s0 +/(vendor|system/vendor)/priv-app(/.*)? u:object_r:vendor_app_file:s0 +/(vendor|system/vendor)/overlay(/.*)? u:object_r:vendor_overlay_file:s0 +/(vendor|system/vendor)/framework(/.*)? u:object_r:vendor_framework_file:s0 + +/(vendor|system/vendor)/apex(/[^/]+){0,2} u:object_r:vendor_apex_file:s0 +/(vendor|system/vendor)/bin/misc_writer u:object_r:vendor_misc_writer_exec:s0 +/(vendor|system/vendor)/bin/boringssl_self_test(32|64) u:object_r:vendor_boringssl_self_test_exec:s0 + +# HAL location +/(vendor|system/vendor)/lib(64)?/hw u:object_r:vendor_hal_file:s0 + +/(vendor|system/vendor)/etc/selinux/vendor_service_contexts u:object_r:vendor_service_contexts_file:s0 + +############################# +# OEM and ODM files +# +/(odm|vendor/odm)(/.*)? u:object_r:vendor_file:s0 +/(odm|vendor/odm)/lib(64)?/egl(/.*)? u:object_r:same_process_hal_file:s0 +/(odm|vendor/odm)/lib(64)?/hw u:object_r:vendor_hal_file:s0 +/(odm|vendor/odm)/lib(64)?/vndk-sp(/.*)? u:object_r:vndk_sp_file:s0 +/(odm|vendor/odm)/bin/sh u:object_r:vendor_shell_exec:s0 +/(odm|vendor/odm)/etc(/.*)? u:object_r:vendor_configs_file:s0 +/(odm|vendor/odm)/app(/.*)? u:object_r:vendor_app_file:s0 +/(odm|vendor/odm)/priv-app(/.*)? u:object_r:vendor_app_file:s0 +/(odm|vendor/odm)/overlay(/.*)? u:object_r:vendor_overlay_file:s0 +/(odm|vendor/odm)/framework(/.*)? u:object_r:vendor_framework_file:s0 + +# secure-element service: vendor uuid mapping config file +/(odm|vendor/odm|vendor|system/vendor)/etc/hal_uuid_map_(.*)?\.xml u:object_r:vendor_uuid_mapping_config_file:s0 + + +# Input configuration +/(odm|vendor/odm|vendor|system/vendor)/usr/keylayout(/.*)?\.kl u:object_r:vendor_keylayout_file:s0 +/(odm|vendor/odm|vendor|system/vendor)/usr/keychars(/.*)?\.kcm u:object_r:vendor_keychars_file:s0 +/(odm|vendor/odm|vendor|system/vendor)/usr/idc(/.*)?\.idc u:object_r:vendor_idc_file:s0 + +/oem(/.*)? u:object_r:oemfs:s0 +/oem/overlay(/.*)? u:object_r:vendor_overlay_file:s0 + +# The precompiled monolithic sepolicy will be under /odm only when +# BOARD_USES_ODMIMAGE is true: a separate odm.img is built. +/odm/etc/selinux/precompiled_sepolicy u:object_r:sepolicy_file:s0 +/odm/etc/selinux/precompiled_sepolicy\.plat_and_mapping\.sha256 u:object_r:sepolicy_file:s0 + +/(odm|vendor/odm)/etc/selinux/odm_sepolicy\.cil u:object_r:sepolicy_file:s0 +/(odm|vendor/odm)/etc/selinux/odm_file_contexts u:object_r:file_contexts_file:s0 +/(odm|vendor/odm)/etc/selinux/odm_seapp_contexts u:object_r:seapp_contexts_file:s0 +/(odm|vendor/odm)/etc/selinux/odm_property_contexts u:object_r:property_contexts_file:s0 +/(odm|vendor/odm)/etc/selinux/odm_service_contexts u:object_r:vendor_service_contexts_file:s0 +/(odm|vendor/odm)/etc/selinux/odm_hwservice_contexts u:object_r:hwservice_contexts_file:s0 +/(odm|vendor/odm)/etc/selinux/odm_keystore2_key_contexts u:object_r:keystore2_key_contexts_file:s0 +/(odm|vendor/odm)/etc/selinux/odm_mac_permissions\.xml u:object_r:mac_perms_file:s0 + +############################# +# Product files +# +/(product|system/product)(/.*)? u:object_r:system_file:s0 +/(product|system/product)/etc/group u:object_r:system_group_file:s0 +/(product|system/product)/etc/passwd u:object_r:system_passwd_file:s0 +/(product|system/product)/overlay(/.*)? u:object_r:system_file:s0 + +/(product|system/product)/etc/selinux/product_file_contexts u:object_r:file_contexts_file:s0 +/(product|system/product)/etc/selinux/product_hwservice_contexts u:object_r:hwservice_contexts_file:s0 +/(product|system/product)/etc/selinux/product_keystore2_key_contexts u:object_r:keystore2_key_contexts_file:s0 +/(product|system/product)/etc/selinux/product_property_contexts u:object_r:property_contexts_file:s0 +/(product|system/product)/etc/selinux/product_seapp_contexts u:object_r:seapp_contexts_file:s0 +/(product|system/product)/etc/selinux/product_service_contexts u:object_r:service_contexts_file:s0 +/(product|system/product)/etc/selinux/product_mac_permissions\.xml u:object_r:mac_perms_file:s0 + +/(product|system/product)/lib(64)?(/.*)? u:object_r:system_lib_file:s0 + +############################# +# SystemExt files +# +/(system_ext|system/system_ext)(/.*)? u:object_r:system_file:s0 +/(system_ext|system/system_ext)/etc/group u:object_r:system_group_file:s0 +/(system_ext|system/system_ext)/etc/passwd u:object_r:system_passwd_file:s0 +/(system_ext|system/system_ext)/overlay(/.*)? u:object_r:vendor_overlay_file:s0 + +/(system_ext|system/system_ext)/etc/selinux/system_ext_file_contexts u:object_r:file_contexts_file:s0 +/(system_ext|system/system_ext)/etc/selinux/system_ext_hwservice_contexts u:object_r:hwservice_contexts_file:s0 +/(system_ext|system/system_ext)/etc/selinux/system_ext_keystore2_key_contexts u:object_r:keystore2_key_contexts_file:s0 +/(system_ext|system/system_ext)/etc/selinux/system_ext_property_contexts u:object_r:property_contexts_file:s0 +/(system_ext|system/system_ext)/etc/selinux/system_ext_seapp_contexts u:object_r:seapp_contexts_file:s0 +/(system_ext|system/system_ext)/etc/selinux/system_ext_service_contexts u:object_r:service_contexts_file:s0 +/(system_ext|system/system_ext)/etc/selinux/system_ext_mac_permissions\.xml u:object_r:mac_perms_file:s0 +/(system_ext|system/system_ext)/etc/selinux/userdebug_plat_sepolicy\.cil u:object_r:sepolicy_file:s0 + +/(system_ext|system/system_ext)/bin/aidl_lazy_test_server u:object_r:aidl_lazy_test_server_exec:s0 +/(system_ext|system/system_ext)/bin/aidl_lazy_cb_test_server u:object_r:aidl_lazy_test_server_exec:s0 +/(system_ext|system/system_ext)/bin/hidl_lazy_test_server u:object_r:hidl_lazy_test_server_exec:s0 +/(system_ext|system/system_ext)/bin/hidl_lazy_cb_test_server u:object_r:hidl_lazy_test_server_exec:s0 + +/(system_ext|system/system_ext)/bin/canhalconfigurator(-aidl)? u:object_r:canhalconfigurator_exec:s0 + +/(system_ext|system/system_ext)/lib(64)?(/.*)? u:object_r:system_lib_file:s0 + +############################# +# VendorDlkm files +# This includes VENDOR Dynamically Loadable Kernel Modules and other misc files. +# +/(vendor_dlkm|vendor/vendor_dlkm|system/vendor/vendor_dlkm)(/.*)? u:object_r:vendor_file:s0 +/(vendor_dlkm|vendor/vendor_dlkm|system/vendor/vendor_dlkm)/etc(/.*)? u:object_r:vendor_configs_file:s0 + +############################# +# OdmDlkm files +# This includes ODM Dynamically Loadable Kernel Modules and other misc files. +# +/(odm_dlkm|vendor/odm_dlkm|system/vendor/odm_dlkm)(/.*)? u:object_r:vendor_file:s0 +/(odm_dlkm|vendor/odm_dlkm|system/vendor/odm_dlkm)/etc(/.*)? u:object_r:vendor_configs_file:s0 + +############################# +# Vendor files from /(product|system/product)/vendor_overlay +# +# NOTE: For additional vendor file contexts for vendor overlay files, +# use device specific file_contexts. +# +/(product|system/product)/vendor_overlay/[0-9]+/.* u:object_r:vendor_file:s0 + +############################# +# Data files +# +# NOTE: When modifying existing label rules, changes may also need to +# propagate to the "Expanded data files" section. +# +/data u:object_r:system_data_root_file:s0 +/data/(.*)? u:object_r:system_data_file:s0 +/data/system/environ(/.*)? u:object_r:environ_system_data_file:s0 +/data/system/packages\.list u:object_r:packages_list_file:s0 +/data/system/game_mode_intervention\.list u:object_r:game_mode_intervention_list_file:s0 +/data/unencrypted(/.*)? u:object_r:unencrypted_data_file:s0 +/data/backup(/.*)? u:object_r:backup_data_file:s0 +/data/secure/backup(/.*)? u:object_r:backup_data_file:s0 +/data/system/ndebugsocket u:object_r:system_ndebug_socket:s0 +/data/system/unsolzygotesocket u:object_r:system_unsolzygote_socket:s0 +/data/drm(/.*)? u:object_r:drm_data_file:s0 +/data/resource-cache(/.*)? u:object_r:resourcecache_data_file:s0 +/data/dalvik-cache(/.*)? u:object_r:dalvikcache_data_file:s0 +/data/ota(/.*)? u:object_r:ota_data_file:s0 +/data/ota_package(/.*)? u:object_r:ota_package_file:s0 +/data/adb(/.*)? u:object_r:adb_data_file:s0 +/data/anr(/.*)? u:object_r:anr_data_file:s0 +/data/apex(/.*)? u:object_r:apex_data_file:s0 +/data/apex/active/(.*)? u:object_r:staging_data_file:s0 +/data/apex/backup/(.*)? u:object_r:staging_data_file:s0 +/data/apex/decompressed/(.*)? u:object_r:staging_data_file:s0 +/data/apex/ota_reserved(/.*)? u:object_r:apex_ota_reserved_file:s0 +/data/app(/.*)? u:object_r:apk_data_file:s0 +# Traditional /data/app/[packageName]-[randomString]/base.apk location +/data/app/[^/]+/oat(/.*)? u:object_r:dalvikcache_data_file:s0 +# /data/app/[randomStringA]/[packageName]-[randomStringB]/base.apk layout +/data/app/[^/]+/[^/]+/oat(/.*)? u:object_r:dalvikcache_data_file:s0 +/data/app/vmdl[^/]+\.tmp(/.*)? u:object_r:apk_tmp_file:s0 +/data/app/vmdl[^/]+\.tmp/oat(/.*)? u:object_r:dalvikcache_data_file:s0 +/data/app-private(/.*)? u:object_r:apk_private_data_file:s0 +/data/app-private/vmdl.*\.tmp(/.*)? u:object_r:apk_private_tmp_file:s0 +/data/gsi(/.*)? u:object_r:gsi_data_file:s0 +/data/gsi_persistent_data u:object_r:gsi_persistent_data_file:s0 +/data/gsi/ota(/.*)? u:object_r:ota_image_data_file:s0 +/data/tombstones(/.*)? u:object_r:tombstone_data_file:s0 +/data/vendor/tombstones/wifi(/.*)? u:object_r:tombstone_wifi_data_file:s0 +/data/local/tests(/.*)? u:object_r:shell_test_data_file:s0 +/data/local/tmp(/.*)? u:object_r:shell_data_file:s0 +/data/local/tmp/ltp(/.*)? u:object_r:nativetest_data_file:s0 +/data/local/traces(/.*)? u:object_r:trace_data_file:s0 +/data/media u:object_r:media_userdir_file:s0 +/data/media/.* u:object_r:media_rw_data_file:s0 +/data/mediadrm(/.*)? u:object_r:media_data_file:s0 +/data/nativetest(/.*)? u:object_r:nativetest_data_file:s0 +/data/nativetest64(/.*)? u:object_r:nativetest_data_file:s0 +# This directory was removed after Q Beta 2, but we need to preserve labels for upgrading devices. +/data/pkg_staging(/.*)? u:object_r:staging_data_file:s0 +/data/property(/.*)? u:object_r:property_data_file:s0 +/data/preloads(/.*)? u:object_r:preloads_data_file:s0 +/data/preloads/media(/.*)? u:object_r:preloads_media_file:s0 +/data/preloads/demo(/.*)? u:object_r:preloads_media_file:s0 +/data/server_configurable_flags(/.*)? u:object_r:server_configurable_flags_data_file:s0 +/data/app-staging(/.*)? u:object_r:staging_data_file:s0 +# Ensure we have the same labels as /data/app or /data/apex/active +# to avoid restorecon conflicts +/data/rollback/\d+/[^/]+/.*\.apk u:object_r:apk_data_file:s0 +/data/rollback/\d+/[^/]+/.*\.apex u:object_r:staging_data_file:s0 +/data/fonts/files(/.*)? u:object_r:font_data_file:s0 +/data/misc_ce u:object_r:system_userdir_file:s0 +/data/misc_de u:object_r:system_userdir_file:s0 +/data/system_ce u:object_r:system_userdir_file:s0 +/data/system_de u:object_r:system_userdir_file:s0 +/data/user u:object_r:system_userdir_file:s0 +/data/user_de u:object_r:system_userdir_file:s0 + +# Misc data +/data/misc/adb(/.*)? u:object_r:adb_keys_file:s0 +/data/misc/a11ytrace(/.*)? u:object_r:accessibility_trace_data_file:s0 +/data/misc/apexdata(/.*)? u:object_r:apex_module_data_file:s0 +/data/misc/apexdata/com\.android\.art(/.*)? u:object_r:apex_art_data_file:s0 +/data/misc/apexdata/com\.android\.compos(/.*)? u:object_r:apex_compos_data_file:s0 +/data/misc/apexdata/com\.android\.permission(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc/apexdata/com\.android\.scheduling(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc/apexdata/com\.android\.tethering(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc/apexdata/com\.android\.uwb(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc/apexdata/com\.android\.wifi(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc/apexrollback(/.*)? u:object_r:apex_rollback_data_file:s0 +/data/misc/apns(/.*)? u:object_r:radio_data_file:s0 +/data/misc/appcompat(/.*)? u:object_r:appcompat_data_file:s0 +/data/misc/audio(/.*)? u:object_r:audio_data_file:s0 +/data/misc/audioserver(/.*)? u:object_r:audioserver_data_file:s0 +/data/misc/audiohal(/.*)? u:object_r:audiohal_data_file:s0 +/data/misc/bootstat(/.*)? u:object_r:bootstat_data_file:s0 +/data/misc/boottrace(/.*)? u:object_r:boottrace_data_file:s0 +/data/misc/bluetooth(/.*)? u:object_r:bluetooth_data_file:s0 +/data/misc/bluetooth/logs(/.*)? u:object_r:bluetooth_logs_data_file:s0 +/data/misc/bluedroid(/.*)? u:object_r:bluetooth_data_file:s0 +/data/misc/bluedroid/\.a2dp_ctrl u:object_r:bluetooth_socket:s0 +/data/misc/bluedroid/\.a2dp_data u:object_r:bluetooth_socket:s0 +/data/misc/camera(/.*)? u:object_r:camera_data_file:s0 +/data/misc/carrierid(/.*)? u:object_r:radio_data_file:s0 +/data/misc/dhcp(/.*)? u:object_r:dhcp_data_file:s0 +/data/misc/dhcp-6\.8\.2(/.*)? u:object_r:dhcp_data_file:s0 +/data/misc/dmesgd(/.*)? u:object_r:dmesgd_data_file:s0 +/data/misc/emergencynumberdb(/.*)? u:object_r:emergency_data_file:s0 +/data/misc/gatekeeper(/.*)? u:object_r:gatekeeper_data_file:s0 +/data/misc/incidents(/.*)? u:object_r:incident_data_file:s0 +/data/misc/installd(/.*)? u:object_r:install_data_file:s0 +/data/misc/keychain(/.*)? u:object_r:keychain_data_file:s0 +/data/misc/credstore(/.*)? u:object_r:credstore_data_file:s0 +/data/misc/keystore(/.*)? u:object_r:keystore_data_file:s0 +/data/misc/logd(/.*)? u:object_r:misc_logd_file:s0 +/data/misc/media(/.*)? u:object_r:media_data_file:s0 +/data/misc/net(/.*)? u:object_r:net_data_file:s0 +/data/misc/network_watchlist(/.*)? u:object_r:network_watchlist_data_file:s0 +/data/misc/nfc/logs(/.*)? u:object_r:nfc_logs_data_file:s0 +/data/misc/odrefresh(/.*)? u:object_r:odrefresh_data_file:s0 +/data/misc/odsign(/.*)? u:object_r:odsign_data_file:s0 +/data/misc/odsign/metrics(/.*)? u:object_r:odsign_metrics_file:s0 +/data/misc/perfetto-traces/bugreport(.*)? u:object_r:perfetto_traces_bugreport_data_file:s0 +/data/misc/perfetto-traces(/.*)? u:object_r:perfetto_traces_data_file:s0 +/data/misc/perfetto-configs(/.*)? u:object_r:perfetto_configs_data_file:s0 +/data/misc/prereboot(/.*)? u:object_r:prereboot_data_file:s0 +/data/misc/profcollectd(/.*)? u:object_r:profcollectd_data_file:s0 +/data/misc/radio(/.*)? u:object_r:radio_core_data_file:s0 +/data/misc/recovery(/.*)? u:object_r:recovery_data_file:s0 +/data/misc/shared_relro(/.*)? u:object_r:shared_relro_file:s0 +/data/misc/sms(/.*)? u:object_r:radio_data_file:s0 +/data/misc/snapshotctl_log(/.*)? u:object_r:snapshotctl_log_data_file:s0 +/data/misc/stats-active-metric(/.*)? u:object_r:stats_data_file:s0 +/data/misc/stats-data(/.*)? u:object_r:stats_data_file:s0 +/data/misc/stats-service(/.*)? u:object_r:stats_config_data_file:s0 +/data/misc/stats-metadata(/.*)? u:object_r:stats_data_file:s0 +/data/misc/systemkeys(/.*)? u:object_r:systemkeys_data_file:s0 +/data/misc/textclassifier(/.*)? u:object_r:textclassifier_data_file:s0 +/data/misc/train-info(/.*)? u:object_r:stats_data_file:s0 +/data/misc/user(/.*)? u:object_r:misc_user_data_file:s0 +/data/misc/virtualizationservice(/.*)? u:object_r:virtualizationservice_data_file:s0 +/data/misc/vpn(/.*)? u:object_r:vpn_data_file:s0 +/data/misc/wifi(/.*)? u:object_r:wifi_data_file:s0 +/data/misc_ce/[0-9]+/wifi(/.*)? u:object_r:wifi_data_file:s0 +/data/misc/wifi/sockets(/.*)? u:object_r:wpa_socket:s0 +/data/misc/wifi/sockets/wpa_ctrl.* u:object_r:system_wpa_socket:s0 +/data/misc/vold(/.*)? u:object_r:vold_data_file:s0 +/data/misc/update_engine(/.*)? u:object_r:update_engine_data_file:s0 +/data/misc/update_engine_log(/.*)? u:object_r:update_engine_log_data_file:s0 +/data/system/dropbox(/.*)? u:object_r:dropbox_data_file:s0 +/data/system/heapdump(/.*)? u:object_r:heapdump_data_file:s0 +/data/misc/trace(/.*)? u:object_r:method_trace_data_file:s0 +/data/misc/wmtrace(/.*)? u:object_r:wm_trace_data_file:s0 +# TODO(calin) label profile reference differently so that only +# profman run as a special user can write to them +/data/misc/profiles/cur(/[0-9]+)? u:object_r:user_profile_root_file:s0 +/data/misc/profiles/cur/[0-9]+/.* u:object_r:user_profile_data_file:s0 +/data/misc/profiles/ref(/.*)? u:object_r:user_profile_data_file:s0 +/data/misc/profman(/.*)? u:object_r:profman_dump_data_file:s0 +/data/vendor(/.*)? u:object_r:vendor_data_file:s0 +/data/vendor_ce u:object_r:vendor_userdir_file:s0 +/data/vendor_ce/.* u:object_r:vendor_data_file:s0 +/data/vendor_de u:object_r:vendor_userdir_file:s0 +/data/vendor_de/.* u:object_r:vendor_data_file:s0 + +# storaged proto files +/data/misc_de/[0-9]+/storaged(/.*)? u:object_r:storaged_data_file:s0 +/data/misc_ce/[0-9]+/storaged(/.*)? u:object_r:storaged_data_file:s0 + +# checkin data files +/data/misc_ce/[0-9]+/checkin(/.*)? u:object_r:checkin_data_file:s0 + +# Fingerprint data +/data/system/users/[0-9]+/fpdata(/.*)? u:object_r:fingerprintd_data_file:s0 + +# Fingerprint vendor data file +/data/vendor_de/[0-9]+/fpdata(/.*)? u:object_r:fingerprint_vendor_data_file:s0 + +# Face vendor data file +/data/vendor_de/[0-9]+/facedata(/.*)? u:object_r:face_vendor_data_file:s0 +/data/vendor_ce/[0-9]+/facedata(/.*)? u:object_r:face_vendor_data_file:s0 + +# Iris vendor data file +/data/vendor_de/[0-9]+/irisdata(/.*)? u:object_r:iris_vendor_data_file:s0 + +# Bootchart data +/data/bootchart(/.*)? u:object_r:bootchart_data_file:s0 + +# Sandbox sdk data (managed by installd) +/data/misc_de/[0-9]+/sdksandbox u:object_r:sdk_sandbox_system_data_file:s0 +/data/misc_ce/[0-9]+/sdksandbox u:object_r:sdk_sandbox_system_data_file:s0 + +# App data snapshots (managed by installd). +/data/misc_de/[0-9]+/rollback(/.*)? u:object_r:rollback_data_file:s0 +/data/misc_ce/[0-9]+/rollback(/.*)? u:object_r:rollback_data_file:s0 + +# Apex data directories +/data/misc_de/[0-9]+/apexdata(/.*)? u:object_r:apex_module_data_file:s0 +/data/misc_ce/[0-9]+/apexdata(/.*)? u:object_r:apex_module_data_file:s0 +/data/misc_ce/[0-9]+/apexdata/com\.android\.appsearch(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc_de/[0-9]+/apexdata/com\.android\.permission(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc_ce/[0-9]+/apexdata/com\.android\.permission(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc_de/[0-9]+/apexdata/com\.android\.wifi(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc_ce/[0-9]+/apexdata/com\.android\.wifi(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc_de/[0-9]+/apexdata/com\.android\.uwb(/.*)? u:object_r:apex_system_server_data_file:s0 +/data/misc_ce/[0-9]+/apexdata/com\.android\.uwb(/.*)? u:object_r:apex_system_server_data_file:s0 + +# Apex rollback directories +/data/misc_de/[0-9]+/apexrollback(/.*)? u:object_r:apex_rollback_data_file:s0 +/data/misc_ce/[0-9]+/apexrollback(/.*)? u:object_r:apex_rollback_data_file:s0 + +# Incremental directories +/data/incremental(/.*)? u:object_r:apk_data_file:s0 +/data/incremental/MT_[^/]+/mount/.pending_reads u:object_r:incremental_control_file:s0 +/data/incremental/MT_[^/]+/mount/.log u:object_r:incremental_control_file:s0 +/data/incremental/MT_[^/]+/mount/.blocks_written u:object_r:incremental_control_file:s0 + +# Boot animation data +/data/bootanim(/.*)? u:object_r:bootanim_data_file:s0 +############################# +# Expanded data files +# +/mnt/expand u:object_r:mnt_expand_file:s0 +# CAREFUL: the two system_data_file patterns below can't be replaced with one +# pattern "/mnt/expand/[^/]+(/.*)?", since SELinux would prioritize that over +# "/mnt/expand/[^/]+/user". This is because when a path is matched by two +# patterns that contain regex meta-characters, SELinux just chooses the longer +# pattern (or the later pattern if the patterns are the same length), rather +# than the pattern containing fewer regex meta-characters. Splitting the +# pattern into "/mnt/expand/[^/]+" and "/mnt/expand/[^/]+/.*" works around this +# problem, except for 1-character filenames which we aren't using. +/mnt/expand/[^/]+ u:object_r:system_data_file:s0 +/mnt/expand/[^/]+/.* u:object_r:system_data_file:s0 +/mnt/expand/[^/]+/app(/.*)? u:object_r:apk_data_file:s0 +/mnt/expand/[^/]+/app/[^/]+/oat(/.*)? u:object_r:dalvikcache_data_file:s0 +# /mnt/expand/..../app/[randomStringA]/[packageName]-[randomStringB]/base.apk layout +/mnt/expand/[^/]+/app/[^/]+/[^/]+/oat(/.*)? u:object_r:dalvikcache_data_file:s0 +/mnt/expand/[^/]+/app/vmdl[^/]+\.tmp(/.*)? u:object_r:apk_tmp_file:s0 +/mnt/expand/[^/]+/app/vmdl[^/]+\.tmp/oat(/.*)? u:object_r:dalvikcache_data_file:s0 +/mnt/expand/[^/]+/local/tmp(/.*)? u:object_r:shell_data_file:s0 +/mnt/expand/[^/]+/media u:object_r:media_userdir_file:s0 +/mnt/expand/[^/]+/media/.* u:object_r:media_rw_data_file:s0 +/mnt/expand/[^/]+/misc/vold(/.*)? u:object_r:vold_data_file:s0 +/mnt/expand/[^/]+/misc_ce u:object_r:system_userdir_file:s0 +/mnt/expand/[^/]+/misc_de u:object_r:system_userdir_file:s0 +/mnt/expand/[^/]+/user u:object_r:system_userdir_file:s0 +/mnt/expand/[^/]+/user_de u:object_r:system_userdir_file:s0 + +# coredump directory for userdebug/eng devices +/cores(/.*)? u:object_r:coredump_file:s0 + +# Wallpaper files +/data/system/users/[0-9]+/wallpaper_lock_orig u:object_r:wallpaper_file:s0 +/data/system/users/[0-9]+/wallpaper_lock u:object_r:wallpaper_file:s0 +/data/system/users/[0-9]+/wallpaper_orig u:object_r:wallpaper_file:s0 +/data/system/users/[0-9]+/wallpaper u:object_r:wallpaper_file:s0 + +# Ringtone files +/data/system_de/[0-9]+/ringtones(/.*)? u:object_r:ringtone_file:s0 + +# ShortcutManager icons, e.g. +# /data/system_ce/0/shortcut_service/bitmaps/com.example.app/1457472879282.png +/data/system_ce/[0-9]+/shortcut_service/bitmaps(/.*)? u:object_r:shortcut_manager_icons:s0 + +# User icon files +/data/system/users/[0-9]+/photo\.png u:object_r:icon_file:s0 + +# Shutdown-checkpoints files +/data/system/shutdown-checkpoints(/.*)? u:object_r:shutdown_checkpoints_system_data_file:s0 + +# vold per-user data +/data/misc_de/[0-9]+/vold(/.*)? u:object_r:vold_data_file:s0 +/data/misc_ce/[0-9]+/vold(/.*)? u:object_r:vold_data_file:s0 + +# Backup service persistent per-user bookkeeping +/data/system_ce/[0-9]+/backup(/.*)? u:object_r:backup_data_file:s0 +# Backup service temporary per-user data for inter-change with apps +/data/system_ce/[0-9]+/backup_stage(/.*)? u:object_r:backup_data_file:s0 + +############################# +# efs files +# +/efs(/.*)? u:object_r:efs_file:s0 + +############################# +# Cache files +# +/cache(/.*)? u:object_r:cache_file:s0 +/cache/recovery(/.*)? u:object_r:cache_recovery_file:s0 +# General backup/restore interchange with apps +/cache/backup_stage(/.*)? u:object_r:cache_backup_file:s0 +# LocalTransport (backup) uses this subtree +/cache/backup(/.*)? u:object_r:cache_private_backup_file:s0 + +############################# +# Overlayfs support directories +# +/cache/overlay(/.*)? u:object_r:overlayfs_file:s0 +/mnt/scratch(/.*)? u:object_r:overlayfs_file:s0 + +/data/cache(/.*)? u:object_r:cache_file:s0 +/data/cache/recovery(/.*)? u:object_r:cache_recovery_file:s0 +# General backup/restore interchange with apps +/data/cache/backup_stage(/.*)? u:object_r:cache_backup_file:s0 +# LocalTransport (backup) uses this subtree +/data/cache/backup(/.*)? u:object_r:cache_private_backup_file:s0 + +############################# +# Metadata files +# +/metadata(/.*)? u:object_r:metadata_file:s0 +/metadata/apex(/.*)? u:object_r:apex_metadata_file:s0 +/metadata/vold(/.*)? u:object_r:vold_metadata_file:s0 +/metadata/gsi(/.*)? u:object_r:gsi_metadata_file:s0 +/metadata/gsi/dsu/active u:object_r:gsi_public_metadata_file:s0 +/metadata/gsi/dsu/booted u:object_r:gsi_public_metadata_file:s0 +/metadata/gsi/dsu/lp_names u:object_r:gsi_public_metadata_file:s0 +/metadata/gsi/dsu/[^/]+/metadata_encryption_dir u:object_r:gsi_public_metadata_file:s0 +/metadata/gsi/ota(/.*)? u:object_r:ota_metadata_file:s0 +/metadata/password_slots(/.*)? u:object_r:password_slot_metadata_file:s0 +/metadata/ota(/.*)? u:object_r:ota_metadata_file:s0 +/metadata/bootstat(/.*)? u:object_r:metadata_bootstat_file:s0 +/metadata/sepolicy(/.*)? u:object_r:sepolicy_metadata_file:s0 +/metadata/staged-install(/.*)? u:object_r:staged_install_file:s0 +/metadata/userspacereboot(/.*)? u:object_r:userspace_reboot_metadata_file:s0 +/metadata/watchdog(/.*)? u:object_r:watchdog_metadata_file:s0 +/metadata/repair-mode(/.*)? u:object_r:repair_mode_metadata_file:s0 + +############################# +# asec containers +/mnt/asec(/.*)? u:object_r:asec_apk_file:s0 +/mnt/asec/[^/]+/[^/]+\.zip u:object_r:asec_public_file:s0 +/mnt/asec/[^/]+/lib(/.*)? u:object_r:asec_public_file:s0 +/data/app-asec(/.*)? u:object_r:asec_image_file:s0 + +############################# +# external storage +/mnt/media_rw(/.*)? u:object_r:mnt_media_rw_file:s0 +/mnt/user(/.*)? u:object_r:mnt_user_file:s0 +/mnt/pass_through(/.*)? u:object_r:mnt_pass_through_file:s0 +/mnt/sdcard u:object_r:mnt_sdcard_file:s0 +/mnt/runtime(/.*)? u:object_r:storage_file:s0 +/storage(/.*)? u:object_r:storage_file:s0 + +############################# +# mount point for read-write vendor partitions +/mnt/vendor(/.*)? u:object_r:mnt_vendor_file:s0 + +############################# +# mount point for read-write product partitions +/mnt/product(/.*)? u:object_r:mnt_product_file:s0 + +############################# +# /postinstall file contexts +/(system|product)/bin/check_dynamic_partitions u:object_r:postinstall_exec:s0 +/(system|product)/bin/otapreopt_script u:object_r:postinstall_exec:s0 +/(system|product)/bin/otapreopt u:object_r:postinstall_dexopt_exec:s0 +#line 1 "device/google/gs-common/gs_watchdogd/sepolicy/file_contexts" +# Platform watchdogd +/system_ext/bin/gs_watchdogd u:object_r:gs_watchdogd_exec:s0 + +# Devices +/dev/watchdog[0-9] u:object_r:watchdog_device:s0 +#line 1 "system/sepolicy/private/file_contexts_overlayfs" +############################# +# Overlayfs support directories for userdebug/eng devices +# +/cache/overlay/(system|product)/upper u:object_r:system_file:s0 +/cache/overlay/(vendor|odm)/upper u:object_r:vendor_file:s0 +/cache/overlay/oem/upper u:object_r:vendor_file:s0 +/mnt/scratch/overlay/(system|product)/upper u:object_r:system_file:s0 +/mnt/scratch/overlay/(vendor|odm)/upper u:object_r:vendor_file:s0 +/mnt/scratch/overlay/oem/upper u:object_r:vendor_file:s0 +#line 1 "out/target/product/panther/obj/ETC/file_contexts.bin_intermediates/file_contexts.device.sorted.tmp" +/(vendor|system/vendor)/bin/hw/rild u:object_r:rild_exec:s0 +/(vendor|system/vendor)/bin/hw/hostapd u:object_r:hal_wifi_hostapd_default_exec:s0 +/(vendor|system/vendor)/bin/hw/wpa_supplicant u:object_r:hal_wifi_supplicant_default_exec:s0 +/(vendor|system/vendor)/bin/vndservicemanager u:object_r:vndservicemanager_exec:s0 +/(vendor|system/vendor)/bin/install-recovery\.sh u:object_r:vendor_install_recovery_exec:s0 +/(vendor|system/vendor)/lib(64)?/libhwbinder.so u:object_r:same_process_hal_file:s0 +/(vendor|system/vendor)/lib(64)?/libhidltransport.so u:object_r:same_process_hal_file:s0 +/(vendor|system/vendor)/lib(64)?/hw/gralloc\.default\.so u:object_r:same_process_hal_file:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.uwb-service u:object_r:hal_uwb_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.wifi-service u:object_r:hal_wifi_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.ir@1\.0-service u:object_r:hal_ir_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.vr@1\.0-service u:object_r:hal_vr_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.drm@1\.0-service u:object_r:hal_drm_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.nfc@1\.0-service u:object_r:hal_nfc_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.nfc@1\.1-service u:object_r:hal_nfc_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.nfc@1\.2-service u:object_r:hal_nfc_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.usb@1\.0-service u:object_r:hal_usb_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.wifi@1\.0-service u:object_r:hal_wifi_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.light@2\.0-service u:object_r:hal_light_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.power@1\.0-service u:object_r:hal_power_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.wifi-service-lazy u:object_r:hal_wifi_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.atrace@1\.0-service u:object_r:hal_atrace_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.gatekeeper-service u:object_r:hal_gatekeeper_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.health@1\.0-service u:object_r:hal_health_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.health@2\.0-service u:object_r:hal_health_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.health@2\.1-service u:object_r:hal_health_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.ir-service\.example u:object_r:hal_ir_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.lowpan@1\.0-service u:object_r:hal_lowpan_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.evs(.*)? u:object_r:hal_evs_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.cas@1\.[0-2]-service u:object_r:hal_cas_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.cas-service\.example u:object_r:hal_cas_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tv\.hdmi.cec-service u:object_r:hal_tv_hdmi_cec_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.usb-service\.example u:object_r:hal_usb_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.boot-service.default u:object_r:hal_bootctl_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.drm@1\.0-service-lazy u:object_r:hal_drm_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.gnss-service.example u:object_r:hal_gnss_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.memtrack@1\.0-service u:object_r:hal_memtrack_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.radio-service\.compat u:object_r:hal_radio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tv\.hdmi.earc-service u:object_r:hal_tv_hdmi_earc_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tv\.input@1\.0-service u:object_r:hal_tv_input_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.vibrator@1\.0-service u:object_r:hal_vibrator_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.audio(@2\.0-|\.)service u:object_r:hal_audio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.boot@1\.[0-9]+-service u:object_r:hal_bootctl_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.keymaster@3\.0-service u:object_r:hal_keymaster_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.keymaster@4\.0-service u:object_r:hal_keymaster_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.keymaster@4\.1-service u:object_r:hal_keymaster_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.media\.omx@1\.0-service u:object_r:mediacodec_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.power-service\.example u:object_r:hal_power_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.radio@1\.2-sap-service u:object_r:hal_radio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tv\.cec@1\.[01]-service u:object_r:hal_tv_cec_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.wifi@1\.0-service-lazy u:object_r:hal_wifi_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.can-service u:object_r:hal_can_socketcan_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.gatekeeper@1\.0-service u:object_r:hal_gatekeeper_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.health-service\.example u:object_r:hal_health_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.light@2\.0-service-lazy u:object_r:hal_light_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.lights-service\.example u:object_r:hal_light_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.thermal@1\.[01]-service u:object_r:hal_thermal_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.usb\.gadget@1\.1-service u:object_r:hal_usb_gadget_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.input\.processor-service u:object_r:hal_input_processor_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.power\.stats@1\.0-service u:object_r:hal_power_stats_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.radio@1\.2-radio-service u:object_r:hal_radio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.sensors-service\.example u:object_r:hal_sensors_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.thermal-service\.example u:object_r:hal_thermal_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tv\.tuner@1\.[01]-service u:object_r:hal_tv_tuner_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.cas@1\.[0-2]-service-lazy u:object_r:hal_cas_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.cas-service\.example-lazy u:object_r:hal_cas_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.gnss@[0-9]\.[0-9]-service u:object_r:hal_gnss_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.identity-service.example u:object_r:hal_identity_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.memtrack-service.example u:object_r:hal_memtrack_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.radio\.config@1\.0-service u:object_r:hal_radio_config_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.security\.keymint-service u:object_r:hal_keymint_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tv\.input-service\.example u:object_r:hal_tv_input_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.vibrator-service.example u:object_r:hal_vibrator_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.audio@7\.0-service\.example u:object_r:hal_audio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.bluetooth-service.default u:object_r:hal_bluetooth_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.dumpstate-service\.example u:object_r:hal_dumpstate_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.power.stats-service\.pixel u:object_r:hal_power_stats_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.audio\.service-aidl\.example u:object_r:hal_audio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.can@1\.0-service u:object_r:hal_can_socketcan_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.bluetooth@1\.[0-9]+-service u:object_r:hal_bluetooth_default_exec:s0 +/(vendor|sustem/vendor)/bin/hw/android\.hardware\.confirmationui@1\.0-service u:object_r:hal_confirmationui_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.contexthub-service\.example u:object_r:hal_contexthub_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.graphics\.allocator-service u:object_r:hal_graphics_allocator_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.health\.storage@1\.0-service u:object_r:hal_health_storage_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.secure_element@1\.0-service u:object_r:hal_secure_element_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tv\.hdmi.connection-service u:object_r:hal_tv_hdmi_connection_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.usb\.gadget-service\.example u:object_r:hal_usb_gadget_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.contexthub@1\.[0-9]+-service u:object_r:hal_contexthub_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.power.stats-service\.example u:object_r:hal_power_stats_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.sensors-service(\.multihal)? u:object_r:hal_sensors_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.broadcastradio@\d+\.\d+-service u:object_r:hal_broadcastradio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.configstore@1\.[0-9]+-service u:object_r:hal_configstore_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.drm-service(-lazy)?\.clearkey u:object_r:hal_drm_clearkey_aidl_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.input\.classifier@1\.0-service u:object_r:hal_input_classifier_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.rebootescrow-service\.default u:object_r:hal_rebootescrow_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.bluetooth-service\.bcmbtlinux u:object_r:hal_bluetooth_btlinux_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.graphics\.allocator-V1-service u:object_r:hal_graphics_allocator_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tetheroffload-service\.example u:object_r:hal_tetheroffload_default_exec:s0 +/(vendor|system/vendor)/lib(64)?/hw/android\.hardware\.renderscript@1\.0-impl\.so u:object_r:same_process_hal_file:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.ivn@V1-(.*)-service u:object_r:hal_ivn_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.broadcastradio-service.default u:object_r:hal_broadcastradio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.graphics\.allocator@2\.0-service u:object_r:hal_graphics_allocator_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.graphics\.allocator@3\.0-service u:object_r:hal_graphics_allocator_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.graphics\.allocator@4\.0-service u:object_r:hal_graphics_allocator_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.health\.storage-service\.default u:object_r:hal_health_storage_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.secure_element-service.example u:object_r:hal_secure_element_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.biometrics\.face-service\.example u:object_r:hal_face_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.tv\.tuner-service\.example(-lazy)? u:object_r:hal_tv_tuner_default_exec:s0 +/(vendor|system/vendor)/lib(64)?/hw/android\.hardware\.graphics\.mapper@2\.0-impl\.so u:object_r:same_process_hal_file:s0 +/(vendor|system/vendor)/lib(64)?/hw/android\.hardware\.graphics\.mapper@3\.0-impl\.so u:object_r:same_process_hal_file:s0 +/(vendor|system/vendor)/lib(64)?/hw/android\.hardware\.graphics\.mapper@4\.0-impl\.so u:object_r:same_process_hal_file:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.audio\.effect\.service-aidl\.example u:object_r:hal_audio_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.dumpstate@1\.[0-1]-service\.example u:object_r:hal_dumpstate_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.bluetooth@1\.[0-9]+-service\.btlinux u:object_r:hal_bluetooth_btlinux_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.biometrics\.fingerprint@2\.1-service u:object_r:hal_fingerprint_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.graphics\.composer3-service\.example u:object_r:hal_graphics_composer_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.audiocontrol@1\.0-service u:object_r:hal_audiocontrol_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.audiocontrol@2\.0-service u:object_r:hal_audiocontrol_default_exec:s0 +/(vendor|system/vendor)/lib(64)?/hw/android\.hardware\.graphics\.mapper@2\.0-impl-2\.1\.so u:object_r:same_process_hal_file:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.graphics\.composer@[0-9]\.[0-9]-service u:object_r:hal_graphics_composer_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.biometrics\.fingerprint-service\.example u:object_r:hal_fingerprint_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.camera\.provider(@2\.[0-9]+|-V1)-service u:object_r:hal_camera_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.audiocontrol-service.example u:object_r:hal_audiocontrol_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.remoteaccess@V1-(.*)-service u:object_r:hal_remoteaccess_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.sensors@[0-9]\.[0-9]-service(\.multihal)? u:object_r:hal_sensors_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.biometrics\.face@1\.[0-9]+-service\.example u:object_r:hal_face_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.camera\.provider(@2\.[0-9]+|-V1)-service_64 u:object_r:hal_camera_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.biometrics\.fingerprint@2\.2-service\.example u:object_r:hal_fingerprint_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.camera\.provider(@2\.[0-9]+|-V1)-service-lazy u:object_r:hal_camera_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.camera\.provider(@2\.[0-9]+|-V1)-service-lazy_64 u:object_r:hal_camera_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.camera\.provider(@2\.[0-9]+|-V1)-external-service u:object_r:hal_camera_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.vehicle@V1-(default|emulator)-service u:object_r:hal_vehicle_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.camera\.provider(@2\.[0-9]+|-V1)-external-service-lazy u:object_r:hal_camera_default_exec:s0 +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.vehicle@2\.0-((default|emulator)-)*(service|protocan-service) u:object_r:hal_vehicle_default_exec:s0 +/persist(/.*)? u:object_r:persist_file:s0 +/data/nfc(/.*)? u:object_r:nfc_data_file:s0 +/dev/sscd_.* u:object_r:sscoredump_device:s0 +/dev/ttyGS[0-3] u:object_r:serial_device:s0 +/dev/hidraw[0-9]* u:object_r:hidraw_device:s0 +/vendor/lib(64)?/libdrm\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/libgxp\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/libOpenCL\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/libion_google\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/hw/vulkan\.mali\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/libOpenCL-pixel\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/lib_aion_buffer\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/hw/gralloc\.gs201\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/libGralloc4Wrapper\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/gxp_metrics_logger\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/libgpudataproducer\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/arm\.graphics-V1-ndk\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/vendor-pixelatoms-cpp\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/pixel-power-ext-V1-ndk\.so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/libprotobuf-cpp-lite-(\d+\.){2,3}so u:object_r:same_process_hal_file:s0 +/vendor/lib(64)?/android\.frameworks\.stats-V1-ndk\.so u:object_r:same_process_hal_file:s0 +/dev/oem_ipc[0-7] u:object_r:radio_device:s0 +/dev/thermal(/.*)? u:object_r:thermal_link_device:s0 +/dev/acd-com.google.usf u:object_r:aoc_device:s0 +/dev/acd-com.google.chre u:object_r:aoc_device:s0 +/dev/acd-com.google.umfw_stat u:object_r:aoc_device:s0 +/dev/acd-com.google.usf.non_wake_up u:object_r:aoc_device:s0 +/dev/acd-com.google.chre.non_wake_up u:object_r:aoc_device:s0 +/dev/umts_wfc[01] u:object_r:pktrouter_device:s0 +/data/per_boot(/.*)? u:object_r:per_boot_file:s0 +/vendor/etc/aoc(/.*)? u:object_r:aoc_audio_file:s0 +/data/vendor/mm(/.*)? u:object_r:mm_logd_vendor_data_file:s0 +/data/vendor/ss(/.*)? u:object_r:tee_data_file:s0 +/mnt/vendor/efs(/.*)? u:object_r:modem_efs_file:s0 +/data/vendor/gps(/.*)? u:object_r:vendor_gps_file:s0 +/persist/haptics(/.*)? u:object_r:persist_haptics_file:s0 +/vendor/firmware(/.*)? u:object_r:vendor_fw_file:s0 +/data/vendor/log(/.*)? u:object_r:vendor_log_file:s0 +/data/vendor/uwb(/.*)? u:object_r:uwb_data_vendor:s0 +/data/vendor/slog(/.*)? u:object_r:vendor_slog_file:s0 +/data/vendor/rild(/.*)? u:object_r:rild_vendor_data_file:s0 +/data/vendor/misc(/.*)? u:object_r:vendor_misc_data_file:s0 +/dev/acd-audio_tap[0-9]* u:object_r:aoc_device:s0 +/data/vendor/radio(/.*)? u:object_r:radio_vendor_data_file:s0 +/data/vendor/audio(/.*)? u:object_r:audio_vendor_data_file:s0 +/data/vendor/media(/.*)? u:object_r:vendor_media_data_file:s0 +/data/vendor/camera(/.*)? u:object_r:vendor_camera_data_file:s0 +/mnt/vendor/persist(/.*)? u:object_r:persist_file:s0 +/mnt/vendor/ramdump(/.*)? u:object_r:ramdump_vendor_mnt_file:s0 +/data/vendor/log/hwc(/.*)? u:object_r:vendor_hwc_log_file:s0 +/data/vendor/edgetpu(/.*)? u:object_r:edgetpu_vendor_service_data_file:s0 +/data/vendor/ramdump(/.*)? u:object_r:ramdump_vendor_data_file:s0 +/data/vendor/ssrdump(/.*)? u:object_r:sscoredump_vendor_data_crashinfo_file:s0 +/data/vendor/wifi/wpa(/.*)? u:object_r:wpa_data_file:s0 +/data/vendor/mediadrm(/.*)? u:object_r:mediadrm_vendor_data_file:s0 +/data/vendor/log/rfsd(/.*)? u:object_r:vendor_rfsd_log_file:s0 +/mnt/vendor/modem_img(/.*)? u:object_r:modem_img_file:s0 +/mnt/vendor/persist/ss(/.*)? u:object_r:persist_ss_file:s0 +/mnt/vendor/efs_backup(/.*)? u:object_r:modem_efs_file:s0 +/data/vendor/bluetooth(/.*)? u:object_r:vendor_bt_data_file:s0 +/sys/devices/platform/[0-9]+\.ufs/pixel/enable_pixel_ufs_logging u:object_r:sysfs_scsi_devices_0000:s0 +/mnt/vendor/persist/aoc(/.*)? u:object_r:persist_aoc_file:s0 +/data/vendor/mitigation(/.*)? u:object_r:mitigation_vendor_data_file:s0 +/data/vendor/modem_stat(/.*)? u:object_r:modem_stat_data_file:s0 +/data/vendor/powerstats(/.*)? u:object_r:powerstats_vendor_data_file:s0 +/mnt/vendor/persist/uwb(/.*)? u:object_r:persist_uwb_file:s0 +/vendor_dlkm/lib/modules/.*\.ko u:object_r:vendor_kernel_modules:s0 +/data/vendor/wifi/hostapd(/.*)? u:object_r:hostapd_data_file:s0 +/mnt/vendor/persist/audio(/.*)? u:object_r:persist_audio_file:s0 +/dev/sys/block/bootdevice(/.*)? u:object_r:bootdevice_sysdev:s0 +/mnt/vendor/persist/modem(/.*)? u:object_r:persist_modem_file:s0 +/data/vendor/firmware/wifi(/.*)? u:object_r:updated_wifi_firmware_data_file:s0 +/data/vendor/sensors/debug(/.*)? u:object_r:sensor_debug_data_file:s0 +/mnt/vendor/persist/camera(/.*)? u:object_r:persist_camera_file:s0 +/mnt/vendor/modem_userdata(/.*)? u:object_r:modem_userdata_file:s0 +/data/vendor/tcpdump_logger(/.*)? u:object_r:tcpdump_vendor_data_file:s0 +/data/vendor/wifi/wlan_logs(/.*)? u:object_r:wifi_logging_data_file:s0 +/mnt/vendor/persist/haptics(/.*)? u:object_r:persist_haptics_file:s0 +/mnt/vendor/persist/battery(/.*)? u:object_r:persist_battery_file:s0 +/mnt/vendor/persist/display(/.*)? u:object_r:persist_display_file:s0 +/data/vendor/ssrdump/coredump(/.*)? u:object_r:sscoredump_vendor_data_coredump_file:s0 +/data/vendor/sensors/registry(/.*)? u:object_r:sensor_reg_data_file:s0 +/mnt/vendor/persist/sensors/registry(/.*)? u:object_r:persist_sensor_reg_file:s0 +/vendor/bin/hw/android\.hardware\.gnss@[0-9]\.[0-9]-service-brcm u:object_r:hal_gnss_default_exec:s0 +/data/vendor/hal_neuralnetworks_darwinn(/.*)? u:object_r:hal_neuralnetworks_darwinn_data_file:s0 +/dev/block/platform/14700000\.ufs/by-name/abl_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/bl1_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/bl2_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/gsa_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/pbl_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/bl31_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/boot_[ab] u:object_r:boot_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/dtbo_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/ldfw_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/tzsw_[ab] u:object_r:custom_ab_block_device:s0 +/vendor/lib64/com\.google\.edgetpu_app_service-V[1-2]-ndk\.so u:object_r:same_process_hal_file:s0 +/dev/block/platform/14700000\.ufs/by-name/modem_[ab] u:object_r:modem_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/pvmfw_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/vbmeta_[ab] u:object_r:custom_ab_block_device:s0 +/vendor/lib64/com\.google\.edgetpu_vendor_service-V[1-2]-ndk\.so u:object_r:same_process_hal_file:s0 +/dev/block/platform/14700000\.ufs/by-name/init_boot_[ab] u:object_r:boot_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/dram_train_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/vendor_boot_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/vbmeta_system_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/vbmeta_vendor_[ab] u:object_r:custom_ab_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/vendor_kernel_boot_[ab] u:object_r:custom_ab_block_device:s0 +/dev/aoc u:object_r:aoc_device:s0 +/dev/g2d u:object_r:graphics_device:s0 +/dev/gxp u:object_r:gxp_device:s0 +/dev/sg1 u:object_r:sg_device:s0 +/dev/gsc0 u:object_r:citadel_device:s0 +/dev/amcs u:object_r:amcs_device:s0 +/dev/wbrc u:object_r:wb_coexistence_dev:s0 +/dev/dit2 u:object_r:vendor_toe_device:s0 +/dev/mali0 u:object_r:gpu_device:s0 +/dev/ttyBCM u:object_r:vendor_gnss_device:s0 +/dev/fimg2d u:object_r:graphics_device:s0 +/dev/ttySAC0 u:object_r:tty_device:s0 +/dev/janeiro u:object_r:edgetpu_device:s0 +/dev/st21nfc u:object_r:nfc_device:s0 +/dev/st54spi u:object_r:st54spi_device:s0 +/dev/st33spi u:object_r:st33spi_device:s0 +/dev/ttySAC16 u:object_r:hci_attach_dev:s0 +/dev/bigocean u:object_r:video_device:s0 +/dev/lwis-csi u:object_r:lwis_device:s0 +/dev/lwis-dpm u:object_r:lwis_device:s0 +/dev/lwis-ipp u:object_r:lwis_device:s0 +/dev/lwis-itp u:object_r:lwis_device:s0 +/dev/lwis-pdp u:object_r:lwis_device:s0 +/dev/lwis-slc u:object_r:lwis_device:s0 +/dev/lwis-top u:object_r:lwis_device:s0 +/dev/oem_test u:object_r:radio_test_device:s0 +/dev/umts_dm0 u:object_r:radio_device:s0 +/dev/ttySAC18 u:object_r:hci_attach_dev:s0 +/dev/acd-debug u:object_r:aoc_device:s0 +/vendor/bin/cp u:object_r:vendor_toolbox_exec:s0 +/dev/goodix_fp u:object_r:fingerprint_device:s0 +/dev/watchdog0 u:object_r:watchdog_device:s0 +/dev/lwis-g3aa u:object_r:lwis_device:s0 +/dev/lwis-gdc0 u:object_r:lwis_device:s0 +/dev/lwis-gdc1 u:object_r:lwis_device:s0 +/dev/lwis-mcsc u:object_r:lwis_device:s0 +/dev/lwis-scsc u:object_r:lwis_device:s0 +/dev/lwis-votf u:object_r:lwis_device:s0 +/dev/dri/card0 u:object_r:graphics_device:s0 +/dev/umts_ipc0 u:object_r:radio_device:s0 +/dev/umts_ipc1 u:object_r:radio_device:s0 +/dev/umts_rfs0 u:object_r:radio_device:s0 +/dev/block/sda u:object_r:sda_block_device:s0 +/dev/tui-driver u:object_r:tui_device:s0 +/vendor/bin/awk u:object_r:vendor_toolbox_exec:s0 +/dev/socket/pps u:object_r:pps_socket:s0 +/vendor/bin/dmd u:object_r:dmd_exec:s0 +/vendor/bin/vcd u:object_r:vcd_exec:s0 +/vendor/bin/cbd u:object_r:cbd_exec:s0 +/dev/umts_boot0 u:object_r:radio_device:s0 +/dev/acd-logging u:object_r:aoc_device:s0 +/vendor/bin/aocd u:object_r:aocd_exec:s0 +/dev/trusty-log0 u:object_r:trusty_log_device:s0 +/dev/bbd_control u:object_r:vendor_gnss_device:s0 +/vendor/bin/grep u:object_r:vendor_toolbox_exec:s0 +/vendor/bin/sced u:object_r:sced_exec:s0 +/vendor/bin/chre u:object_r:chre_exec:s0 +/vendor/bin/rfsd u:object_r:rfsd_exec:s0 +/dev/bbd_pwrstat u:object_r:power_stats_device:s0 +/dev/umts_router u:object_r:radio_device:s0 +/dev/socket/chre u:object_r:chre_socket:s0 +/dev/logbuffer_bd u:object_r:logbuffer_device:s0 +/vendor/bin/hw/scd u:object_r:scd_exec:s0 +/vendor/bin/hw/lhd u:object_r:lhd_exec:s0 +/dev/snd/pcmC0D24p u:object_r:vibrator_snd_device:s0 +/dev/snd/pcmC1D24p u:object_r:vibrator_snd_device:s0 +/dev/logbuffer_ttf u:object_r:logbuffer_device:s0 +/dev/logbuffer_rtx u:object_r:logbuffer_device:s0 +/dev/logbuffer_cpm u:object_r:logbuffer_device:s0 +/dev/maxfg_history u:object_r:battery_history_device:s0 +/dev/acd-model_data u:object_r:aoc_device:s0 +/vendor/bin/aocdump u:object_r:aocdump_exec:s0 +/vendor/bin/hw/gpsd u:object_r:gpsd_exec:s0 +/vendor/bin/mm_logd u:object_r:init-mm-logging-sh_exec:s0 +/vendor/bin/dumpsys u:object_r:vendor_dumpsys:s0 +/dev/logbuffer_ssoc u:object_r:logbuffer_device:s0 +/dev/logbuffer_maxq u:object_r:logbuffer_device:s0 +/dev/logbuffer_tcpm u:object_r:logbuffer_device:s0 +/dev/acd-hotword_pcm u:object_r:aoc_device:s0 +/dev/acd-ambient_pcm u:object_r:aoc_device:s0 +/vendor/bin/sendhint u:object_r:sendhint_vendor_exec:s0 +/dev/cpu_dma_latency u:object_r:latency_device:s0 +/vendor/bin/bipchmgr u:object_r:bipchmgr_exec:s0 +/dev/logbuffer_usbpd u:object_r:logbuffer_device:s0 +/dev/logbuffer_maxfg u:object_r:logbuffer_device:s0 +/dev/logbuffer_pcie0 u:object_r:logbuffer_device:s0 +/dev/logbuffer_pcie1 u:object_r:logbuffer_device:s0 +/dev/lwis-act-jotnar u:object_r:lwis_device:s0 +/dev/lwis-gtnr-align u:object_r:lwis_device:s0 +/dev/lwis-gtnr-merge u:object_r:lwis_device:s0 +/dev/lwis-ois-jotnar u:object_r:lwis_device:s0 +/dev/trusty-ipc-dev0 u:object_r:tee_device:s0 +/dev/battery_history u:object_r:battery_history_device:s0 +/dev/logbuffer_btlpm u:object_r:logbuffer_device:s0 +/dev/logbuffer_tty18 u:object_r:logbuffer_device:s0 +/dev/acd-audio_rtp_tx u:object_r:aoc_device:s0 +/dev/acd-audio_rtp_rx u:object_r:aoc_device:s0 +/vendor/bin/insmod\.sh u:object_r:insmod-sh_exec:s0 +/vendor/bin/usf_stats u:object_r:vendor_usf_stats:s0 +/dev/acd-audio_bulk_tx u:object_r:aoc_device:s0 +/dev/acd-audio_bulk_rx u:object_r:aoc_device:s0 +/dev/acd-sound_trigger u:object_r:aoc_device:s0 +/dev/acd-mel_processor u:object_r:aoc_device:s0 +/vendor/bin/sscoredump u:object_r:sscoredump_exec:s0 +/vendor/bin/rlsservice u:object_r:rlsservice_exec:s0 +/dev/stmvl53l1_ranging u:object_r:rls_device:s0 +/dev/logbuffer_pca9468 u:object_r:logbuffer_device:s0 +/dev/lwis-flash-lm3644 u:object_r:lwis_device:s0 +/dev/lwis-ois-gargoyle u:object_r:lwis_device:s0 +/dev/lwis-sensor-buraq u:object_r:lwis_device:s0 +/vendor/bin/hw/citadeld u:object_r:citadeld_exec:s0 +/dev/logbuffer_wireless u:object_r:logbuffer_device:s0 +/dev/lwis-eeprom-jotnar u:object_r:lwis_device:s0 +/dev/lwis-sensor-kraken u:object_r:lwis_device:s0 +/dev/lwis-sensor-nagual u:object_r:lwis_device:s0 +/vendor/bin/usf_reg_edit u:object_r:vendor_usf_reg_edit:s0 +/vendor/bin/wifi_sniffer u:object_r:wifi_sniffer_exec:s0 +/vendor/bin/thermal_logd u:object_r:init-thermal-logging-sh_exec:s0 +/dev/lwis-act-slenderman u:object_r:lwis_device:s0 +/dev/lwis-sensor-lamassu u:object_r:lwis_device:s0 +/vendor/bin/dump/dump_soc u:object_r:dump_soc_exec:s0 +/dev/acd-audio_dcdoff_ref u:object_r:aoc_device:s0 +/vendor/bin/dump/dump_aoc u:object_r:dump_aoc_exec:s0 +/vendor/bin/dump/dump_gxp u:object_r:dump_gxp_exec:s0 +/vendor/bin/dump/dump_gps u:object_r:dump_gps_exec:s0 +/vendor/bin/modem_svc_sit u:object_r:modem_svc_sit_exec:s0 +/vendor/bin/storageproxyd u:object_r:tee_exec:s0 +/vendor/bin/init\.radio\.sh u:object_r:init_radio_exec:s0 +/dev/logbuffer_maxfg_base u:object_r:logbuffer_device:s0 +/dev/logbuffer_maxfg_flip u:object_r:logbuffer_device:s0 +/dev/lwis-eeprom-gargoyle u:object_r:lwis_device:s0 +/dev/lwis-sensor-dokkaebi u:object_r:lwis_device:s0 +/dev/lwis-sensor-sandworm u:object_r:lwis_device:s0 +/vendor/bin/dump/dump_perf u:object_r:dump_perf_exec:s0 +/vendor/bin/umfw_stat_tool u:object_r:vendor_umfw_stat_tool:s0 +/vendor/bin/wfc-pkt-router u:object_r:pktrouter_exec:s0 +/vendor/bin/toolbox_vendor u:object_r:vendor_toolbox_exec:s0 +/vendor/bin/wifi_perf_diag u:object_r:wifi_perf_diag_exec:s0 +/vendor/bin/tcpdump_logger u:object_r:tcpdump_logger_exec:s0 +/vendor/bin/hw/rild_exynos u:object_r:rild_exec:s0 +/dev/dma_heap/faimg-secure u:object_r:faceauth_heap_device:s0 +/dev/acd-audio_input_tuning u:object_r:aoc_device:s0 +/vendor/bin/dump/dump_radio u:object_r:dump_radio_exec:s0 +/vendor/bin/hw/init_citadel u:object_r:init_citadel_exec:s0 +/vendor/bin/init\.display\.sh u:object_r:init-display-sh_exec:s0 +/vendor/bin/trusty_metricsd u:object_r:trusty_metricsd_exec:s0 +/dev/dma_heap/faprev-secure u:object_r:faceauth_heap_device:s0 +/dev/dma_heap/vframe-secure u:object_r:dmabuf_system_secure_heap_device:s0 +/dev/logbuffer_pca9468_tcpm u:object_r:logbuffer_device:s0 +/dev/acd-audio_output_tuning u:object_r:aoc_device:s0 +/dev/acd-audio_input_bulk_tx u:object_r:aoc_device:s0 +/dev/acd-audio_input_bulk_rx u:object_r:aoc_device:s0 +/dev/acd-audio_ap_offload_rx u:object_r:aoc_device:s0 +/dev/acd-audio_ap_offload_tx u:object_r:aoc_device:s0 +/vendor/bin/dump/dump_camera u:object_r:dump_camera_exec:s0 +/vendor/bin/dump/dump_gsc\.sh u:object_r:dump_gsc_exec:s0 +/vendor/bin/CitadelProvision u:object_r:citadel_provision_exec:s0 +/vendor/bin/thermal_controld u:object_r:pixel-thermal-control-sh_exec:s0 +/vendor/bin/thermal_symlinks u:object_r:init-thermal-symlinks-sh_exec:s0 +/vendor/bin/trusty_apploader u:object_r:trusty_apploader_exec:s0 +/dev/dma_heap/famodel-secure u:object_r:faceauth_heap_device:s0 +/dev/dma_heap/vscaler-secure u:object_r:vscaler_heap_device:s0 +/dev/dma_heap/vstream-secure u:object_r:dmabuf_system_secure_heap_device:s0 +/dev/logbuffer_maxfg_monitor u:object_r:logbuffer_device:s0 +/dev/lwis-eeprom-smaug-buraq u:object_r:lwis_device:s0 +/vendor/bin/dump/dump_ramdump u:object_r:dump_ramdump_exec:s0 +/vendor/bin/dump/dump_devfreq u:object_r:dump_devfreq_exec:s0 +/dev/acd-hotword_notification u:object_r:aoc_device:s0 +/vendor/bin/dump/dump_display u:object_r:dump_display_exec:s0 +/vendor/bin/securedpud\.slider u:object_r:securedpud_slider_exec:s0 +/vendor/bin/dump/dump_sensors u:object_r:dump_sensors_exec:s0 +/vendor/bin/pixelstats-vendor u:object_r:pixelstats_vendor_exec:s0 +/vendor/bin/init\.uwb\.calib\.sh u:object_r:vendor_uwb_init_exec:s0 +/dev/dma_heap/farawimg-secure u:object_r:faceauth_heap_device:s0 +/dev/logbuffer_pogo_transport u:object_r:logbuffer_device:s0 +/vendor/bin/dump/dump_modem\.sh u:object_r:dump_modem_exec:s0 +/vendor/bin/dump/dump_modemlog u:object_r:dump_modemlog_exec:s0 +/vendor/bin/hw/citadel_updater u:object_r:citadel_updater:s0 +/vendor/bin/dump/dump_memory\.sh u:object_r:dump_memory_exec:s0 +/vendor/bin/dump/dump_umfw_stat u:object_r:dump_umfw_stat_exec:s0 +/vendor/bin/dump/dump_trusty\.sh u:object_r:dump_trusty_exec:s0 +/sys/module/cl_dsp_core/version u:object_r:sysfs_vibrator:s0 +/dev/lwis-eeprom-smaug-dokkaebi u:object_r:lwis_device:s0 +/dev/lwis-eeprom-smaug-sandworm u:object_r:lwis_device:s0 +/vendor/bin/dump/dump_thermal\.sh u:object_r:dump_thermal_exec:s0 +/vendor/bin/dump/dump_storage\.sh u:object_r:dump_storage_exec:s0 +/vendor/lib64/libedgetpu_util\.so u:object_r:same_process_hal_file:s0 +/sys/module/cs40l26_core/version u:object_r:sysfs_vibrator:s0 +/dev/dma_heap/sensor_direct_heap u:object_r:sensor_direct_heap_device:s0 +/vendor/bin/hw/battery_mitigation u:object_r:battery_mitigation_exec:s0 +/vendor/bin/modem_logging_control u:object_r:modem_logging_control_exec:s0 +/dev/dma_heap/faceauth_tpu-secure u:object_r:faceauth_heap_device:s0 +/dev/logbuffer_maxfg_base_monitor u:object_r:logbuffer_device:s0 +/dev/logbuffer_maxfg_flip_monitor u:object_r:logbuffer_device:s0 +/dev/lwis-act-slenderman-sandworm u:object_r:lwis_device:s0 +/vendor/bin/hw/com\.google\.edgetpu.dba-service u:object_r:edgetpu_dba_server_exec:s0 +/vendor/lib64/libmetrics_logger\.so u:object_r:same_process_hal_file:s0 +/vendor/bin/dump/dump_focaltech\.sh u:object_r:dump_focaltech_exec:s0 +/system_ext/bin/convert_to_ext4\.sh u:object_r:convert-to-ext4-sh_exec:s0 +/vendor/bin/ufs_firmware_update\.sh u:object_r:ufs_firmware_update_exec:s0 +/vendor/bin/dump/dump_pixel_metrics u:object_r:dump_pixel_metrics_exec:s0 +/vendor/bin/dump/dump_power_gs201\.sh u:object_r:dump_power_gs201_exec:s0 +/vendor/bin/rebalance_interrupts-vendor u:object_r:rebalance_interrupts_vendor_exec:s0 +/vendor/lib64/libedgetpu_client\.google\.so u:object_r:same_process_hal_file:s0 +/vendor/bin/dump/dump_display_userdebug\.sh u:object_r:dump_display_userdebug_exec:s0 +/vendor/bin/hw/android\.hardware\.usb-service u:object_r:hal_usb_impl_exec:s0 +/dev/block/platform/14700000\.ufs/by-name/efs u:object_r:efs_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/frp u:object_r:frp_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/misc u:object_r:misc_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/fips u:object_r:fips_block_device:s0 +/vendor/bin/hw/android\.hardware\.nfc-service\.st u:object_r:hal_nfc_default_exec:s0 +/dev/block/platform/14700000\.ufs/by-name/super u:object_r:super_block_device:s0 +/vendor/bin/hw/disable_contaminant_detection\.sh u:object_r:disable-contaminant-detection-sh_exec:s0 +/vendor/bin/init\.camera\.set-interrupts-ownership u:object_r:init-camera-set-interrupts-ownership_exec:s0 +/dev/block/platform/14700000\.ufs/by-name/devinfo u:object_r:devinfo_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/persist u:object_r:persist_block_device:s0 +/vendor/bin/hw/android\.hardware\.dumpstate-service u:object_r:hal_dumpstate_default_exec:s0 +/vendor/bin/hw/vendor\.google\.radioext@1\.0-service u:object_r:hal_radioext_default_exec:s0 +/vendor/bin/hw/android\.hardware\.qorvo\.uwb\.service u:object_r:hal_uwb_vendor_default_exec:s0 +/dev/block/platform/14700000\.ufs/by-name/metadata u:object_r:metadata_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/mfg_data u:object_r:mfg_data_block_device:s0 +/dev/block/platform/14700000\.ufs/by-name/userdata u:object_r:userdata_block_device:s0 +/vendor/bin/hw/android\.hardware\.usb\.gadget-service u:object_r:hal_usb_gadget_impl_exec:s0 +/vendor/bin/hw/android\.hardware\.atrace@1\.0-service.pixel u:object_r:hal_atrace_default_exec:s0 +/vendor/bin/hw/google\.hardware\.media\.c2@1\.0-service u:object_r:mediacodec_google_exec:s0 +/dev/block/platform/14700000\.ufs/by-name/efs_backup u:object_r:efs_block_device:s0 +/vendor/bin/hw/android\.hardware\.drm-service\.widevine u:object_r:hal_drm_widevine_exec:s0 +/vendor/bin/hw/android\.hardware\.drm-service\.clearkey u:object_r:hal_drm_clearkey_exec:s0 +/vendor/bin/hw/vendor\.google\.wifi_ext-service-vendor u:object_r:hal_wifi_ext_exec:s0 +/vendor/bin/hw/samsung\.hardware\.media\.c2@1\.0-service u:object_r:mediacodec_samsung_exec:s0 +/vendor/bin/hw/android\.hardware\.health-service\.gs201 u:object_r:hal_health_default_exec:s0 +/vendor/bin/hw/android\.hardware\.thermal-service\.pixel u:object_r:hal_thermal_default_exec:s0 +/vendor/bin/hw/android\.hardware\.weaver-service\.citadel u:object_r:hal_weaver_citadel_exec:s0 +/vendor/bin/hw/android\.hardware\.boot@1\.2-service-gs201 u:object_r:hal_bootctl_default_exec:s0 +/vendor/bin/hw/android\.hardware\.memtrack-service\.pixel u:object_r:hal_memtrack_default_exec:s0 +/vendor/bin/hw/android\.hardware\.oemlock-service\.citadel u:object_r:hal_oemlock_citadel_exec:s0 +/dev/block/platform/14700000\.ufs/by-name/modem_userdata u:object_r:modem_userdata_block_device:s0 +/vendor/bin/hw/vendor\.google\.wifi_ext@1\.0-service-vendor u:object_r:hal_wifi_ext_exec:s0 +/vendor/bin/hw/android\.hardware\.vibrator-service\.cs40l26 u:object_r:hal_vibrator_default_exec:s0 +/vendor/bin/hw/vendor\.google\.wifi_ext-service-vendor-lazy u:object_r:hal_wifi_ext_exec:s0 +/vendor/bin/hw/android\.hardware\.thermal@2\.0-service\.pixel u:object_r:hal_thermal_default_exec:s0 +/vendor/bin/hw/android\.hardware\.gatekeeper-service\.trusty u:object_r:hal_gatekeeper_default_exec:s0 +/vendor/bin/hw/android\.hardware\.weaver@1\.0-service\.citadel u:object_r:hal_weaver_citadel_exec:s0 +/vendor/bin/hw/android\.hardware\.authsecret-service\.citadel u:object_r:hal_authsecret_citadel_exec:s0 +/vendor/bin/hw/android\.hardware\.contexthub-service\.generic u:object_r:hal_contexthub_default_exec:s0 +/vendor/bin/hw/android\.hardware\.composer\.hwc3-service\.pixel u:object_r:hal_graphics_composer_default_exec:s0 +/vendor/bin/hw/android\.hardware\.identity@1\.0-service\.citadel u:object_r:hal_identity_citadel_exec:s0 +/vendor/bin/hw/vendor\.google\.wifi_ext@1\.0-service-vendor-lazy u:object_r:hal_wifi_ext_exec:s0 +/vendor/bin/hw/android\.hardware\.vibrator-service\.cs40l26-dual u:object_r:hal_vibrator_default_exec:s0 +/vendor/bin/hw/android\.hardware\.gatekeeper@1\.0-service\.trusty u:object_r:hal_gatekeeper_default_exec:s0 +/vendor/bin/hw/vendor\.google\.audiometricext@1\.0-service-vendor u:object_r:hal_audiometricext_default_exec:s0 +/vendor/bin/hw/android\.hardware\.power-service\.pixel-libperfmgr u:object_r:hal_power_default_exec:s0 +/vendor/bin/hw/android\.hardware\.secure_element@1\.2-service-gto u:object_r:hal_secure_element_st54spi_exec:s0 +/vendor/bin/hw/vendor\.google\.edgetpu_vendor_service@1\.0-service u:object_r:edgetpu_vendor_server_exec:s0 +/vendor/bin/hw/android\.hardware\.security\.keymint-service\.trusty u:object_r:hal_keymint_default_exec:s0 +/vendor/bin/hw/android\.hardware\.secure_element@1\.2-uicc-service u:object_r:hal_secure_element_uicc_exec:s0 +/vendor/bin/hw/android\.hardware\.gxp\.logging@service-gxp-logging u:object_r:gxp_logging_exec:s0 +/vendor/bin/hw/android\.hardware\.security\.keymint-service\.citadel u:object_r:hal_keymint_citadel_exec:s0 +/system_ext/bin/hw/vendor\.google\.edgetpu_app_service@1\.0-service u:object_r:edgetpu_app_server_exec:s0 +/vendor/bin/hw/vendor\.google\.wireless_charger@1\.3-service-vendor u:object_r:hal_wlc_exec:s0 +/vendor/bin/hw/android\.hardware\.neuralnetworks@service-darwinn-aidl u:object_r:hal_neuralnetworks_darwinn_exec:s0 +/vendor/bin/hw/android\.hardware\.secure_element@1\.2-service-gto-ese2 u:object_r:hal_secure_element_st33spi_exec:s0 +/vendor/bin/hw/android\.hardware\.confirmationui-service\.trusty\.vendor u:object_r:hal_confirmationui_default_exec:s0 +/vendor/bin/hw/android\.hardware\.security\.keymint-service\.rust\.trusty u:object_r:hal_keymint_default_exec:s0 +/vendor/bin/hw/android\.hardware\.biometrics\.fingerprint-service\.goodix u:object_r:hal_fingerprint_default_exec:s0 +/vendor/bin/hw/android\.hardware\.biometrics\.fingerprint@2\.1-service\.fpc u:object_r:fingerprint_factory_service_exec:s0 +/vendor/bin/hw/android\.hardware\.edgetpu\.logging@service-edgetpu-logging u:object_r:edgetpu_logging_exec:s0 +/vendor/bin/hw/android\.hardware\.biometrics\.fingerprint@2\.1-service\.goodix u:object_r:hal_fingerprint_default_exec:s0 diff --git a/aosp/security/README b/aosp/security/README new file mode 100644 index 00000000..15f2e93e --- /dev/null +++ b/aosp/security/README @@ -0,0 +1,38 @@ +For detailed information on key types and image signing, please see: + +https://source.android.com/devices/tech/ota/sign_builds.html + +The test keys in this directory are used in development only and should +NEVER be used to sign packages in publicly released images (as that would +open a major security hole). + +key generation +-------------- + +The following commands were used to generate the test key pairs: + + development/tools/make_key testkey '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com' + development/tools/make_key platform '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com' + development/tools/make_key shared '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com' + development/tools/make_key media '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com' + +signing using the openssl commandline (for boot/system images) +-------------------------------------------------------------- + +1. convert pk8 format key to pem format + % openssl pkcs8 -inform DER -nocrypt -in testkey.pk8 -out testkey.pem + +2. create a signature using the pem format key + % openssl dgst -binary -sha1 -sign testkey.pem FILE > FILE.sig + +extracting public keys for embedding +------------------------------------ + +dumpkey.jar is a Java tool that takes an x.509 certificate in PEM format as +input and prints a C structure to standard output: + + $ java -jar out/host/linux-x86/framework/dumpkey.jar build/target/product/security/testkey.x509.pem + {64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}} + +This is called by build/core/Makefile to incorporate the OTA signing keys +into the recovery image. diff --git a/aosp/security/media.pk8 b/aosp/security/media.pk8 new file mode 100644 index 00000000..a6db9ba1 Binary files /dev/null and b/aosp/security/media.pk8 differ diff --git a/aosp/security/media.x509.pem b/aosp/security/media.x509.pem new file mode 100644 index 00000000..98cd443d --- /dev/null +++ b/aosp/security/media.x509.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJAPK5jmEjVyxOMA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0wODA0MTUyMzQwNTdaFw0zNTA5MDEyMzQwNTdaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBAK4lDFoW75f8KGmsZRsyF8w2ug6GlkFo1YoE +n0DOhYZxI6P/tPbZScM88to6BcI+rKpX2AOImxdZvPWefG8hiQriUIW37VaqYmwJ +ie+czTY2LKDo0blgP9TYModnkmzMCQxot3Wuf/MJNMw2nvKFWiZn3wxmf9DHz12O +umVYBnNzA7tiRybquu37cvB+16dqs8uaOBxLfc2AmxQNiR8AITvkAfWNagamHq3D +qcLxxlZyhbCa4JNCpm+kIer5Ot91c6AowzHXBgGrOvfMhAM+znx3KjpbhrDb6dd3 +w6SKqYAe3O4ngVifRNnkETl5YAV2qZQQuoEJElna2YxsaP94S48CAQOjgfwwgfkw +HQYDVR0OBBYEFMopPKqLwO0+VC7vQgWiv/K1fk11MIHJBgNVHSMEgcEwgb6AFMop +PKqLwO0+VC7vQgWiv/K1fk11oYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAPK5jmEjVyxOMAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAITelRbV5KhyF6c9qEhwSPUzc6X3 +M/OQ1hvfPMnlJRYlv8qnwxWcriddFyqa4eh21UWBJ6xUL2gpDdUQwAKdj1Hg7hVr +e3tazbOUJBuOx4t05cQsXK+uFWyvW9GZojonUk2gct6743hGSlM2MLDk0P+34I7L +cB+ttjecdEZ/bgDG7YiFlTgHkgOHVgB4csjjAHr0I6V6LKs6KChptkxLe9X8GH0K +fiQVll1ark4Hpt91G0p16Xk8kYphK4HNC2KK7gFo3ETkexDTWTJghJ1q321yfcJE +RMIh0/nsw2jK0HmZ8rgQW8HyDTjUEGbMFBHCV6lupDSfV0ZWVQfk6AIKGoE= +-----END CERTIFICATE----- diff --git a/aosp/security/platform.pk8 b/aosp/security/platform.pk8 new file mode 100644 index 00000000..e27a3933 Binary files /dev/null and b/aosp/security/platform.pk8 differ diff --git a/aosp/security/platform.x509.pem b/aosp/security/platform.x509.pem new file mode 100644 index 00000000..087f02e6 --- /dev/null +++ b/aosp/security/platform.x509.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJALOZgIbQVs/6MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0wODA0MTUyMjQwNTBaFw0zNTA5MDEyMjQwNTBaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBAJx4BZKsDV04HN6qZezIpgBuNkgMbXIHsSAR +vlCGOqvitV0Amt9xRtbyICKAx81Ne9smJDuKgGwms0sTdSOkkmgiSQTcAUk+fArP +GgXIdPabA3tgMJ2QdNJCgOFrrSqHNDYZUer3KkgtCbIEsYdeEqyYwap3PWgAuer9 +5W1Yvtjo2hb5o2AJnDeoNKbf7be2tEoEngeiafzPLFSW8s821k35CjuNjzSjuqtM +9TNxqydxmzulh1StDFP8FOHbRdUeI0+76TybpO35zlQmE1DsU1YHv2mi/0qgfbX3 +6iANCabBtJ4hQC+J7RGQiTqrWpGA8VLoL4WkV1PPX8GQccXuyCcCAQOjgfwwgfkw +HQYDVR0OBBYEFE/koLPdnLop9x1yh8Tnw48ghsKZMIHJBgNVHSMEgcEwgb6AFE/k +oLPdnLop9x1yh8Tnw48ghsKZoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJALOZgIbQVs/6MAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAFclUbjZOh9z3g9tRp+G2tZwFAAp +PIigzXzXeLc9r8wZf6t25iEuVsHHYc/EL9cz3lLFCuCIFM78CjtaGkNGBU2Cnx2C +tCsgSL+ItdFJKe+F9g7dEtctVWV+IuPoXQTIMdYT0Zk4u4mCJH+jISVroS0dao+S +6h2xw3Mxe6DAN/DRr/ZFrvIkl5+6bnoUvAJccbmBOM7z3fwFlhfPJIRc97QNY4L3 +J17XOElatuWTG5QhdlxJG3L7aOCA29tYwgKdNHyLMozkPvaosVUz7fvpib1qSN1L +IC7alMarjdW4OZID2q4u1EYjLk/pvZYTlMYwDlE448/Shebk5INTjLixs1c= +-----END CERTIFICATE----- diff --git a/aosp/security/shared.pk8 b/aosp/security/shared.pk8 new file mode 100644 index 00000000..cf99acd5 Binary files /dev/null and b/aosp/security/shared.pk8 differ diff --git a/aosp/security/shared.x509.pem b/aosp/security/shared.x509.pem new file mode 100644 index 00000000..7f886a84 --- /dev/null +++ b/aosp/security/shared.x509.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJAPKnM5a9OHZ6MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0wODA3MjMyMTU3NTlaFw0zNTEyMDkyMTU3NTlaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBAMjC2/0JSi30XD/xoy7SGAXscvxY0BeXG9D2 +tSwmLXCBnRkZZ+FY39Oix/Gz4OgM5UXXnShIIgIR64bw/YMS03tCDBE3UMyUYYro +cvSIZGO9xGJ8qgwEg8hkk+NRVXEXAzi/3MTNat3RwKLzX1zyTtPkBDo+WOKwXmZM +zeEry2dzX9bfEknDaeYlQrwKRynlORf1w4/6UtF7c8nHN5jdsY7UgVkIdVR+Zr/F +2spMJabrlg7ZaSNwnaMCumRstJazJehsXIsuejN3srvkx88zJUKRFj9okVKsCIVQ +yDxQj0v1rfCu1aLcoFg/mrCtF2UNt+6ksj/bRYhVR9D+q3IYOIkCAQOjgfwwgfkw +HQYDVR0OBBYEFMtMfizbs/CtqY2reZaNFy6dux7RMIHJBgNVHSMEgcEwgb6AFMtM +fizbs/CtqY2reZaNFy6dux7RoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAPKnM5a9OHZ6MAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAECo0JaZeVnpF6NsRCRra6wrrgVD +fs2JeUEY94NHIDUtHG+KObCGmUL02mWYH6opUdM5cRKewZIdeVZxxSfW4knyUoKf +r1tZExAxHi3gllANVorUEUplbcNKjG9hBFOvwep5ktukqns/hUOm41wHKN53/pfu +rIN3H9DskPjkRJQ07gtgRXg+cMei5GAkkmDgA892CNw1Kkye9wbe9LJgUOl4ri// +16MyN4cBSRXrPMh0/MeprpMId8XIx9HC4qjuhjyJGA0YVc7bpADnukPMyqckPTl+ +fA6Ojk19T5K2u+rUnAzwGAae3coufi+0Zo2J2715UNDNJUGA+h6q/CpVb4Q= +-----END CERTIFICATE----- diff --git a/aosp/security/testkey.pk8 b/aosp/security/testkey.pk8 new file mode 100644 index 00000000..586c1bd5 Binary files /dev/null and b/aosp/security/testkey.pk8 differ diff --git a/aosp/security/testkey.x509.pem b/aosp/security/testkey.x509.pem new file mode 100644 index 00000000..e242d83e --- /dev/null +++ b/aosp/security/testkey.x509.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g +VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE +AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G +A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI +hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM +qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 +wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy +4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU +RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s +zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw +HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ +AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH +QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG +CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud +EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa +J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y +LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe ++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX +31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr +sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= +-----END CERTIFICATE----- diff --git a/aosp/security/verity.pk8 b/aosp/security/verity.pk8 new file mode 100644 index 00000000..bebf216c Binary files /dev/null and b/aosp/security/verity.pk8 differ diff --git a/aosp/security/verity.x509.pem b/aosp/security/verity.x509.pem new file mode 100644 index 00000000..86399c3c --- /dev/null +++ b/aosp/security/verity.x509.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID/TCCAuWgAwIBAgIJAJcPmDkJqolJMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g +VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE +AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0xNDExMDYxOTA3NDBaFw00MjAzMjQxOTA3NDBaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G +A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAOjreE0vTVSRenuzO9vnaWfk0eQzYab0gqpi +6xAzi6dmD+ugoEKJmbPiuE5Dwf21isZ9uhUUu0dQM46dK4ocKxMRrcnmGxydFn6o +fs3ODJMXOkv2gKXL/FdbEPdDbxzdu8z3yk+W67udM/fW7WbaQ3DO0knu+izKak/3 +T41c5uoXmQ81UNtAzRGzGchNVXMmWuTGOkg6U+0I2Td7K8yvUMWhAWPPpKLtVH9r +AL5TzjYNR92izdKcz3AjRsI3CTjtpiVABGeX0TcjRSuZB7K9EK56HV+OFNS6I1NP +jdD7FIShyGlqqZdUOkAUZYanbpgeT5N7QL6uuqcGpoTOkalu6kkCAwEAAaNQME4w +HQYDVR0OBBYEFH5DM/m7oArf4O3peeKO0ZIEkrQPMB8GA1UdIwQYMBaAFH5DM/m7 +oArf4O3peeKO0ZIEkrQPMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AHO3NSvDE5jFvMehGGtS8BnFYdFKRIglDMc4niWSzhzOVYRH4WajxdtBWc5fx0ix +NF/+hVKVhP6AIOQa+++sk+HIi7RvioPPbhjcsVlZe7cUEGrLSSveGouQyc+j0+m6 +JF84kszIl5GGNMTnx0XRPO+g8t6h5LWfnVydgZfpGRRg+WHewk1U2HlvTjIceb0N +dcoJ8WKJAFWdcuE7VIm4w+vF/DYX/A2Oyzr2+QRhmYSv1cusgAeC1tvH4ap+J1Lg +UnOu5Kh/FqPLLSwNVQp4Bu7b9QFfqK8Moj84bj88NqRGZgDyqzuTrFxn6FW7dmyA +yttuAJAEAymk1mipd9+zp38= +-----END CERTIFICATE----- diff --git a/aosp/security/verity_key b/aosp/security/verity_key new file mode 100644 index 00000000..31982d95 Binary files /dev/null and b/aosp/security/verity_key differ diff --git a/aosp/system/extras/.clang-format b/aosp/system/extras/.clang-format new file mode 120000 index 00000000..9b45e0ae --- /dev/null +++ b/aosp/system/extras/.clang-format @@ -0,0 +1 @@ +.clang-format-4 \ No newline at end of file diff --git a/aosp/system/extras/.clang-format-2 b/aosp/system/extras/.clang-format-2 new file mode 120000 index 00000000..7ab20d4f --- /dev/null +++ b/aosp/system/extras/.clang-format-2 @@ -0,0 +1 @@ +../../build/soong/scripts/system-clang-format-2 \ No newline at end of file diff --git a/aosp/system/extras/.clang-format-4 b/aosp/system/extras/.clang-format-4 new file mode 120000 index 00000000..ddcf5a29 --- /dev/null +++ b/aosp/system/extras/.clang-format-4 @@ -0,0 +1 @@ +../../build/soong/scripts/system-clang-format \ No newline at end of file diff --git a/aosp/system/extras/.clang-format-none b/aosp/system/extras/.clang-format-none new file mode 100644 index 00000000..1ea7b50e --- /dev/null +++ b/aosp/system/extras/.clang-format-none @@ -0,0 +1,7 @@ +# This clang-format configuration may be included in subdirectories to disable +# any warning. + +DisableFormat: true + +# This extra settings is required because of https://reviews.llvm.org/D67843. +SortIncludes: false diff --git a/aosp/system/extras/ext4_utils/mke2fs.conf b/aosp/system/extras/ext4_utils/mke2fs.conf new file mode 100644 index 00000000..8ea960da --- /dev/null +++ b/aosp/system/extras/ext4_utils/mke2fs.conf @@ -0,0 +1,53 @@ +[defaults] + base_features = sparse_super,large_file,filetype,dir_index,ext_attr + default_mntopts = acl,user_xattr + enable_periodic_fsck = 0 + blocksize = 4096 + inode_size = 256 + inode_ratio = 16384 + reserved_ratio = 1.0 + +[fs_types] + ext3 = { + features = has_journal + } + ext4 = { + features = has_journal,extent,huge_file,dir_nlink,extra_isize,uninit_bg + inode_size = 256 + } + ext4dev = { + features = has_journal,extent,huge_file,flex_bg,inline_data,64bit,dir_nlink,extra_isize + inode_size = 256 + options = test_fs=1 + } + small = { + blocksize = 1024 + inode_size = 128 + inode_ratio = 4096 + } + floppy = { + blocksize = 1024 + inode_size = 128 + inode_ratio = 8192 + } + big = { + inode_ratio = 32768 + } + huge = { + inode_ratio = 65536 + } + news = { + inode_ratio = 4096 + } + largefile = { + inode_ratio = 1048576 + blocksize = -1 + } + largefile4 = { + inode_ratio = 4194304 + blocksize = -1 + } + hurd = { + blocksize = 4096 + inode_size = 128 + } diff --git a/aosp/system/extras/ext4_utils/mkuserimg_mke2fs.py b/aosp/system/extras/ext4_utils/mkuserimg_mke2fs.py new file mode 100644 index 00000000..41ae8cd7 --- /dev/null +++ b/aosp/system/extras/ext4_utils/mkuserimg_mke2fs.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import logging +import os +import pkgutil +import subprocess +import sys +import tempfile + + +def RunCommand(cmd, env): + """Runs the given command. + + Args: + cmd: the command represented as a list of strings. + env: a dictionary of additional environment variables. + Returns: + A tuple of the output and the exit code. + """ + env_copy = os.environ.copy() + env_copy.update(env) + + cmd[0] = FindProgram(cmd[0]) + + logging.info("Env: %s", env) + logging.info("Running: " + " ".join(cmd)) + + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=env_copy, text=True) + output, _ = p.communicate() + + return output, p.returncode + +def FindProgram(prog_name): + """Finds the path to prog_name. + + Args: + prog_name: the program name to find. + Returns: + path to the progName if found. The program is searched in the same directory + where this script is located at. If not found, progName is returned. + """ + exec_dir = os.path.dirname(os.path.realpath(sys.argv[0])) + prog_path = os.path.join(exec_dir, prog_name) + if os.path.exists(prog_path): + return prog_path + else: + return prog_name + +def ParseArguments(argv): + """Parses the input arguments to the program.""" + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument("src_dir", help="The source directory for user image.") + parser.add_argument("output_file", help="The path of the output image file.") + parser.add_argument("ext_variant", choices=["ext2", "ext4"], + help="Variant of the extended filesystem.") + parser.add_argument("mount_point", help="The mount point for user image.") + parser.add_argument("fs_size", help="Size of the file system.") + parser.add_argument("file_contexts", nargs='?', + help="The selinux file context.") + + parser.add_argument("--android_sparse", "-s", action="store_true", + help="Outputs an android sparse image (mke2fs).") + parser.add_argument("--journal_size", "-j", + help="Journal size (mke2fs).") + parser.add_argument("--timestamp", "-T", + help="Fake timetamp for the output image.") + parser.add_argument("--fs_config", "-C", + help="Path to the fs config file (e2fsdroid).") + parser.add_argument("--product_out", "-D", + help="Path to the directory with device specific fs" + " config files (e2fsdroid).") + parser.add_argument("--block_list_file", "-B", + help="Path to the block list file (e2fsdroid).") + parser.add_argument("--base_alloc_file_in", "-d", + help="Path to the input base fs file (e2fsdroid).") + parser.add_argument("--base_alloc_file_out", "-A", + help="Path to the output base fs file (e2fsdroid).") + parser.add_argument("--label", "-L", + help="The mount point (mke2fs).") + parser.add_argument("--inodes", "-i", + help="The extfs inodes count (mke2fs).") + parser.add_argument("--inode_size", "-I", + help="The extfs inode size (mke2fs).") + parser.add_argument("--reserved_percent", "-M", + help="The reserved blocks percentage (mke2fs).") + parser.add_argument("--flash_erase_block_size", "-e", + help="The flash erase block size (mke2fs).") + parser.add_argument("--flash_logical_block_size", "-o", + help="The flash logical block size (mke2fs).") + parser.add_argument("--mke2fs_uuid", "-U", + help="The mke2fs uuid (mke2fs) .") + parser.add_argument("--mke2fs_hash_seed", "-S", + help="The mke2fs hash seed (mke2fs).") + parser.add_argument("--share_dup_blocks", "-c", action="store_true", + help="ext4 share dup blocks (e2fsdroid).") + + args, remainder = parser.parse_known_args(argv) + # The current argparse doesn't handle intermixed arguments well. Checks + # manually whether the file_contexts exists as the last argument. + # TODO(xunchang) use parse_intermixed_args() when we switch to python 3.7. + if len(remainder) == 1 and remainder[0] == argv[-1]: + args.file_contexts = remainder[0] + elif remainder: + parser.print_usage() + sys.exit(1) + + return args + + +def ConstructE2fsCommands(args): + """Builds the mke2fs & e2fsdroid command based on the input arguments. + + Args: + args: The result of ArgumentParser after parsing the command line arguments. + Returns: + A tuple of two lists that serve as the command for mke2fs and e2fsdroid. + """ + + BLOCKSIZE = 4096 + + e2fsdroid_opts = [] + mke2fs_extended_opts = [] + mke2fs_opts = [] + + if args.android_sparse: + mke2fs_extended_opts.append("android_sparse") + else: + e2fsdroid_opts.append("-e") + if args.timestamp: + e2fsdroid_opts += ["-T", args.timestamp] + if args.fs_config: + e2fsdroid_opts += ["-C", args.fs_config] + if args.product_out: + e2fsdroid_opts += ["-p", args.product_out] + if args.block_list_file: + e2fsdroid_opts += ["-B", args.block_list_file] + if args.base_alloc_file_in: + e2fsdroid_opts += ["-d", args.base_alloc_file_in] + if args.base_alloc_file_out: + e2fsdroid_opts += ["-D", args.base_alloc_file_out] + if args.share_dup_blocks: + e2fsdroid_opts.append("-s") + if args.file_contexts: + e2fsdroid_opts += ["-S", args.file_contexts] + + if args.flash_erase_block_size: + mke2fs_extended_opts.append("stripe_width={}".format( + int(args.flash_erase_block_size) // BLOCKSIZE)) + if args.flash_logical_block_size: + # stride should be the max of 8kb and the logical block size + stride = max(int(args.flash_logical_block_size), 8192) + mke2fs_extended_opts.append("stride={}".format(stride // BLOCKSIZE)) + if args.mke2fs_hash_seed: + mke2fs_extended_opts.append("hash_seed=" + args.mke2fs_hash_seed) + + if args.journal_size: + if args.journal_size == "0": + mke2fs_opts += ["-O", "^has_journal"] + else: + mke2fs_opts += ["-J", "size=" + args.journal_size] + if args.label: + mke2fs_opts += ["-L", args.label] + if args.inodes: + mke2fs_opts += ["-N", args.inodes] + if args.inode_size: + mke2fs_opts += ["-I", args.inode_size] + if args.mount_point: + mke2fs_opts += ["-M", args.mount_point] + if args.reserved_percent: + mke2fs_opts += ["-m", args.reserved_percent] + if args.mke2fs_uuid: + mke2fs_opts += ["-U", args.mke2fs_uuid] + if mke2fs_extended_opts: + mke2fs_opts += ["-E", ','.join(mke2fs_extended_opts)] + + # Round down the filesystem length to be a multiple of the block size + blocks = int(args.fs_size) // BLOCKSIZE + mke2fs_cmd = (["mke2fs"] + mke2fs_opts + + ["-t", args.ext_variant, "-b", str(BLOCKSIZE), args.output_file, + str(blocks)]) + + e2fsdroid_cmd = (["e2fsdroid"] + e2fsdroid_opts + + ["-f", args.src_dir, "-a", args.mount_point, + args.output_file]) + + return mke2fs_cmd, e2fsdroid_cmd + + +def main(argv): + logging_format = '%(asctime)s %(filename)s %(levelname)s: %(message)s' + logging.basicConfig(level=logging.INFO, format=logging_format, + datefmt='%H:%M:%S') + + args = ParseArguments(argv) + if not os.path.isdir(args.src_dir): + logging.error("Can not find directory %s", args.src_dir) + sys.exit(2) + if not args.mount_point: + logging.error("Mount point is required") + sys.exit(2) + if args.mount_point[0] != '/': + args.mount_point = '/' + args.mount_point + if not args.fs_size: + logging.error("Size of the filesystem is required") + sys.exit(2) + + mke2fs_cmd, e2fsdroid_cmd = ConstructE2fsCommands(args) + + # truncate output file since mke2fs will keep verity section in existing file + with open(args.output_file, 'w') as output: + output.truncate() + + # run mke2fs + with tempfile.NamedTemporaryFile() as conf_file: + conf_data = pkgutil.get_data('mkuserimg_mke2fs', 'mke2fs.conf') + conf_file.write(conf_data) + conf_file.flush() + mke2fs_env = {"MKE2FS_CONFIG" : conf_file.name} + + if args.timestamp: + mke2fs_env["E2FSPROGS_FAKE_TIME"] = args.timestamp + + output, ret = RunCommand(mke2fs_cmd, mke2fs_env) + print(output) + if ret != 0: + logging.error("Failed to run mke2fs: " + output) + sys.exit(4) + + # run e2fsdroid + e2fsdroid_env = {} + if args.timestamp: + e2fsdroid_env["E2FSPROGS_FAKE_TIME"] = args.timestamp + + output, ret = RunCommand(e2fsdroid_cmd, e2fsdroid_env) + # The build script is parsing the raw output of e2fsdroid; keep the pattern + # unchanged for now. + print(output) + if ret != 0: + logging.error("Failed to run e2fsdroid_cmd: " + output) + os.remove(args.output_file) + sys.exit(4) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/aosp/system/extras/f2fs_utils/Android.bp b/aosp/system/extras/f2fs_utils/Android.bp new file mode 100644 index 00000000..e27e28ca --- /dev/null +++ b/aosp/system/extras/f2fs_utils/Android.bp @@ -0,0 +1,63 @@ +// Copyright 2017 The Android Open Source Project + +package { + default_applicable_licenses: ["system_extras_f2fs_utils_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "system_extras_f2fs_utils_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} + +cc_library_shared { + name: "libf2fs_sparseblock", + cflags: ["-Werror"], + + srcs: ["f2fs_sparseblock.c"], + + shared_libs: [ + "liblog", + "libcutils", + ], + + include_dirs: [ + "external/f2fs-tools/include", + "bionic/libc", + ], + + export_include_dirs: ["."], +} + +cc_binary { + name: "f2fs_sparseblock", + cflags: ["-Werror"], + + srcs: ["f2fs_sparseblock.c"], + + shared_libs: [ + "liblog", + "libcutils", + ], + + include_dirs: [ + "external/f2fs-tools/include", + "bionic/libc", + ], +} + +sh_binary_host { + name: "mkf2fsuserimg", + src: "mkf2fsuserimg.sh", + required: [ + "make_f2fs", + "sload_f2fs", + ], +} diff --git a/aosp/system/extras/f2fs_utils/MODULE_LICENSE_APACHE2 b/aosp/system/extras/f2fs_utils/MODULE_LICENSE_APACHE2 new file mode 100644 index 00000000..e69de29b diff --git a/aosp/system/extras/f2fs_utils/NOTICE b/aosp/system/extras/f2fs_utils/NOTICE new file mode 100644 index 00000000..5d142934 --- /dev/null +++ b/aosp/system/extras/f2fs_utils/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2010, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/aosp/system/extras/f2fs_utils/OWNERS b/aosp/system/extras/f2fs_utils/OWNERS new file mode 100644 index 00000000..1c863c58 --- /dev/null +++ b/aosp/system/extras/f2fs_utils/OWNERS @@ -0,0 +1,3 @@ +jaegeuk@google.com +daehojeong@google.com +drosen@google.com diff --git a/aosp/system/extras/f2fs_utils/f2fs_sparseblock.c b/aosp/system/extras/f2fs_utils/f2fs_sparseblock.c new file mode 100644 index 00000000..234ea7e8 --- /dev/null +++ b/aosp/system/extras/f2fs_utils/f2fs_sparseblock.c @@ -0,0 +1,610 @@ +#define _LARGEFILE64_SOURCE + +#define LOG_TAG "f2fs_sparseblock" + +#include "f2fs_sparseblock.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define D_DISP_u32(ptr, member) \ + do { \ + SLOGV("%-30s" \ + "\t\t[0x%#08x : %u]\n", \ + #member, le32_to_cpu((ptr)->member), le32_to_cpu((ptr)->member)); \ + } while (0); + +#define D_DISP_u64(ptr, member) \ + do { \ + SLOGV("%-30s" \ + "\t\t[0x%#016" PRIx64 " : %" PRIu64 "]\n", \ + #member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member)); \ + } while (0); + +#define segno_in_journal(jnl, i) ((jnl)->sit_j.entries[i].segno) + +#define sit_in_journal(jnl, i) ((jnl)->sit_j.entries[i].se) + +static void dbg_print_raw_sb_info(struct f2fs_super_block* sb) { + SLOGV("\n"); + SLOGV("+--------------------------------------------------------+\n"); + SLOGV("| Super block |\n"); + SLOGV("+--------------------------------------------------------+\n"); + + D_DISP_u32(sb, magic); + D_DISP_u32(sb, major_ver); + D_DISP_u32(sb, minor_ver); + D_DISP_u32(sb, log_sectorsize); + D_DISP_u32(sb, log_sectors_per_block); + + D_DISP_u32(sb, log_blocksize); + D_DISP_u32(sb, log_blocks_per_seg); + D_DISP_u32(sb, segs_per_sec); + D_DISP_u32(sb, secs_per_zone); + D_DISP_u32(sb, checksum_offset); + D_DISP_u64(sb, block_count); + + D_DISP_u32(sb, section_count); + D_DISP_u32(sb, segment_count); + D_DISP_u32(sb, segment_count_ckpt); + D_DISP_u32(sb, segment_count_sit); + D_DISP_u32(sb, segment_count_nat); + + D_DISP_u32(sb, segment_count_ssa); + D_DISP_u32(sb, segment_count_main); + D_DISP_u32(sb, segment0_blkaddr); + + D_DISP_u32(sb, cp_blkaddr); + D_DISP_u32(sb, sit_blkaddr); + D_DISP_u32(sb, nat_blkaddr); + D_DISP_u32(sb, ssa_blkaddr); + D_DISP_u32(sb, main_blkaddr); + + D_DISP_u32(sb, root_ino); + D_DISP_u32(sb, node_ino); + D_DISP_u32(sb, meta_ino); + D_DISP_u32(sb, cp_payload); + SLOGV("\n"); +} +static void dbg_print_raw_ckpt_struct(struct f2fs_checkpoint* cp) { + SLOGV("\n"); + SLOGV("+--------------------------------------------------------+\n"); + SLOGV("| Checkpoint |\n"); + SLOGV("+--------------------------------------------------------+\n"); + + D_DISP_u64(cp, checkpoint_ver); + D_DISP_u64(cp, user_block_count); + D_DISP_u64(cp, valid_block_count); + D_DISP_u32(cp, rsvd_segment_count); + D_DISP_u32(cp, overprov_segment_count); + D_DISP_u32(cp, free_segment_count); + + D_DISP_u32(cp, alloc_type[CURSEG_HOT_NODE]); + D_DISP_u32(cp, alloc_type[CURSEG_WARM_NODE]); + D_DISP_u32(cp, alloc_type[CURSEG_COLD_NODE]); + D_DISP_u32(cp, cur_node_segno[0]); + D_DISP_u32(cp, cur_node_segno[1]); + D_DISP_u32(cp, cur_node_segno[2]); + + D_DISP_u32(cp, cur_node_blkoff[0]); + D_DISP_u32(cp, cur_node_blkoff[1]); + D_DISP_u32(cp, cur_node_blkoff[2]); + + D_DISP_u32(cp, alloc_type[CURSEG_HOT_DATA]); + D_DISP_u32(cp, alloc_type[CURSEG_WARM_DATA]); + D_DISP_u32(cp, alloc_type[CURSEG_COLD_DATA]); + D_DISP_u32(cp, cur_data_segno[0]); + D_DISP_u32(cp, cur_data_segno[1]); + D_DISP_u32(cp, cur_data_segno[2]); + + D_DISP_u32(cp, cur_data_blkoff[0]); + D_DISP_u32(cp, cur_data_blkoff[1]); + D_DISP_u32(cp, cur_data_blkoff[2]); + + D_DISP_u32(cp, ckpt_flags); + D_DISP_u32(cp, cp_pack_total_block_count); + D_DISP_u32(cp, cp_pack_start_sum); + D_DISP_u32(cp, valid_node_count); + D_DISP_u32(cp, valid_inode_count); + D_DISP_u32(cp, next_free_nid); + D_DISP_u32(cp, sit_ver_bitmap_bytesize); + D_DISP_u32(cp, nat_ver_bitmap_bytesize); + D_DISP_u32(cp, checksum_offset); + D_DISP_u64(cp, elapsed_time); + + D_DISP_u32(cp, sit_nat_version_bitmap[0]); + SLOGV("\n\n"); +} + +static void dbg_print_info_struct(struct f2fs_info* info) { + SLOGV("\n"); + SLOGV("+--------------------------------------------------------+\n"); + SLOGV("| F2FS_INFO |\n"); + SLOGV("+--------------------------------------------------------+\n"); + SLOGV("blocks_per_segment: %" PRIu64, info->blocks_per_segment); + SLOGV("block_size: %d", info->block_size); + SLOGV("sit_bmp loc: %p", info->sit_bmp); + SLOGV("sit_bmp_size: %d", info->sit_bmp_size); + SLOGV("blocks_per_sit: %" PRIu64, info->blocks_per_sit); + SLOGV("sit_blocks loc: %p", info->sit_blocks); + SLOGV("sit_sums loc: %p", info->sit_sums); + SLOGV("sit_sums num: %d", le16_to_cpu(info->sit_sums->journal.n_sits)); + unsigned int i; + for (i = 0; i < (le16_to_cpu(info->sit_sums->journal.n_sits)); i++) { + SLOGV("entry %d in journal entries is for segment %d", i, + le32_to_cpu(segno_in_journal(&info->sit_sums->journal, i))); + } + + SLOGV("cp_blkaddr: %" PRIu64, info->cp_blkaddr); + SLOGV("cp_valid_cp_blkaddr: %" PRIu64, info->cp_valid_cp_blkaddr); + SLOGV("sit_blkaddr: %" PRIu64, info->sit_blkaddr); + SLOGV("nat_blkaddr: %" PRIu64, info->nat_blkaddr); + SLOGV("ssa_blkaddr: %" PRIu64, info->ssa_blkaddr); + SLOGV("main_blkaddr: %" PRIu64, info->main_blkaddr); + SLOGV("total_user_used: %" PRIu64, info->total_user_used); + SLOGV("total_blocks: %" PRIu64, info->total_blocks); + SLOGV("\n\n"); +} + +/* read blocks */ +static int read_structure(int fd, unsigned long long start, void* buf, ssize_t len) { + off64_t ret; + + ret = lseek64(fd, start, SEEK_SET); + if (ret < 0) { + SLOGE("failed to seek\n"); + return ret; + } + + ret = read(fd, buf, len); + if (ret < 0) { + SLOGE("failed to read\n"); + return ret; + } + if (ret != len) { + SLOGE("failed to read all\n"); + return -1; + } + return 0; +} + +static int read_structure_blk(int fd, unsigned long long start_blk, void* buf, size_t len) { + return read_structure(fd, F2FS_BLKSIZE * start_blk, buf, F2FS_BLKSIZE * len); +} + +static int read_f2fs_sb(int fd, struct f2fs_super_block* sb) { + int rc; + rc = read_structure(fd, F2FS_SUPER_OFFSET, sb, sizeof(*sb)); + if (le32_to_cpu(sb->magic) != F2FS_SUPER_MAGIC) { + SLOGE("Not a valid F2FS super block. Magic:%#08x != %#08x", le32_to_cpu(sb->magic), + F2FS_SUPER_MAGIC); + return -1; + } + return 0; +} + +unsigned int get_f2fs_filesystem_size_sec(char* dev) { + int fd; + if ((fd = open(dev, O_RDONLY)) < 0) { + SLOGE("Cannot open device to get filesystem size "); + return 0; + } + struct f2fs_super_block sb; + if (read_f2fs_sb(fd, &sb)) return 0; + return (unsigned int)(le64_to_cpu(sb.block_count) * F2FS_BLKSIZE / DEFAULT_SECTOR_SIZE); +} + +static struct f2fs_checkpoint* validate_checkpoint(block_t cp_addr, unsigned long long* version, + int fd) { + unsigned char *cp_block_1, *cp_block_2; + struct f2fs_checkpoint* cp_block; + uint64_t cp1_version = 0, cp2_version = 0; + + cp_block_1 = malloc(F2FS_BLKSIZE); + if (!cp_block_1) return NULL; + + /* Read the 1st cp block in this CP pack */ + if (read_structure_blk(fd, cp_addr, cp_block_1, 1)) goto invalid_cp1; + + /* get the version number */ + cp_block = (struct f2fs_checkpoint*)cp_block_1; + + cp1_version = le64_to_cpu(cp_block->checkpoint_ver); + + cp_block_2 = malloc(F2FS_BLKSIZE); + if (!cp_block_2) { + goto invalid_cp1; + } + /* Read the 2nd cp block in this CP pack */ + cp_addr += le32_to_cpu(cp_block->cp_pack_total_block_count) - 1; + if (read_structure_blk(fd, cp_addr, cp_block_2, 1)) { + goto invalid_cp2; + } + + cp_block = (struct f2fs_checkpoint*)cp_block_2; + + cp2_version = le64_to_cpu(cp_block->checkpoint_ver); + + if (cp2_version == cp1_version) { + *version = cp2_version; + free(cp_block_2); + return (struct f2fs_checkpoint*)cp_block_1; + } + + /* There must be something wrong with this checkpoint */ +invalid_cp2: + free(cp_block_2); +invalid_cp1: + free(cp_block_1); + return NULL; +} + +int get_valid_checkpoint_info(int fd, struct f2fs_super_block* sb, struct f2fs_checkpoint** cp, + struct f2fs_info* info) { + struct f2fs_checkpoint *cp1, *cp2, *cur_cp; + unsigned long blk_size; + unsigned long long cp1_version = 0, cp2_version = 0; + unsigned long long cp1_start_blk_no; + unsigned long long cp2_start_blk_no; + + blk_size = 1U << le32_to_cpu(sb->log_blocksize); + + /* + * Find valid cp by reading both packs and finding most recent one. + */ + cp1_start_blk_no = le32_to_cpu(sb->cp_blkaddr); + cp1 = validate_checkpoint(cp1_start_blk_no, &cp1_version, fd); + + /* The second checkpoint pack should start at the next segment */ + cp2_start_blk_no = cp1_start_blk_no + (1 << le32_to_cpu(sb->log_blocks_per_seg)); + cp2 = validate_checkpoint(cp2_start_blk_no, &cp2_version, fd); + + if (cp1 && cp2) { + if (ver_after(cp2_version, cp1_version)) { + cur_cp = cp2; + info->cp_valid_cp_blkaddr = cp2_start_blk_no; + free(cp1); + } else { + cur_cp = cp1; + info->cp_valid_cp_blkaddr = cp1_start_blk_no; + free(cp2); + } + } else if (cp1) { + cur_cp = cp1; + info->cp_valid_cp_blkaddr = cp1_start_blk_no; + } else if (cp2) { + cur_cp = cp2; + info->cp_valid_cp_blkaddr = cp2_start_blk_no; + } else { + goto fail_no_cp; + } + + *cp = cur_cp; + + return 0; + +fail_no_cp: + SLOGE("Valid Checkpoint not found!!"); + return -EINVAL; +} + +static int gather_sit_info(int fd, struct f2fs_info* info) { + uint64_t num_segments = + (info->total_blocks - info->main_blkaddr + info->blocks_per_segment - 1) / + info->blocks_per_segment; + uint64_t num_sit_blocks = (num_segments + SIT_ENTRY_PER_BLOCK - 1) / SIT_ENTRY_PER_BLOCK; + uint64_t sit_block; + + info->sit_blocks = malloc(num_sit_blocks * sizeof(struct f2fs_sit_block)); + if (!info->sit_blocks) return -1; + + for (sit_block = 0; sit_block < num_sit_blocks; sit_block++) { + off64_t address = info->sit_blkaddr + sit_block; + + if (f2fs_test_bit(sit_block, info->sit_bmp)) address += info->blocks_per_sit; + + SLOGV("Reading cache block starting at block %" PRIu64, address); + if (read_structure(fd, address * F2FS_BLKSIZE, &info->sit_blocks[sit_block], + sizeof(struct f2fs_sit_block))) { + SLOGE("Could not read sit block at block %" PRIu64, address); + free(info->sit_blocks); + info->sit_blocks = NULL; + return -1; + } + } + return 0; +} + +static inline int is_set_ckpt_flags(struct f2fs_checkpoint* cp, unsigned int f) { + unsigned int ckpt_flags = le32_to_cpu(cp->ckpt_flags); + return !!(ckpt_flags & f); +} + +static inline uint64_t sum_blk_addr(struct f2fs_checkpoint* cp, struct f2fs_info* info, int base, + int type) { + return info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_total_block_count) - (base + 1) + + type; +} + +static int get_sit_summary(int fd, struct f2fs_info* info, struct f2fs_checkpoint* cp) { + char buffer[F2FS_BLKSIZE]; + + info->sit_sums = calloc(1, sizeof(struct f2fs_summary_block)); + if (!info->sit_sums) return -1; + + /* CURSEG_COLD_DATA where the journaled SIT entries are. */ + if (is_set_ckpt_flags(cp, CP_COMPACT_SUM_FLAG)) { + if (read_structure_blk(fd, info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_start_sum), + buffer, 1)) + return -1; + memcpy(&info->sit_sums->journal.n_sits, &buffer[SUM_JOURNAL_SIZE], SUM_JOURNAL_SIZE); + } else { + uint64_t blk_addr; + if (is_set_ckpt_flags(cp, CP_UMOUNT_FLAG)) + blk_addr = sum_blk_addr(cp, info, NR_CURSEG_TYPE, CURSEG_COLD_DATA); + else + blk_addr = sum_blk_addr(cp, info, NR_CURSEG_DATA_TYPE, CURSEG_COLD_DATA); + + if (read_structure_blk(fd, blk_addr, buffer, 1)) return -1; + + memcpy(info->sit_sums, buffer, sizeof(struct f2fs_summary_block)); + } + return 0; +} + +struct f2fs_info* generate_f2fs_info(int fd) { + struct f2fs_super_block* sb = NULL; + struct f2fs_checkpoint* cp = NULL; + struct f2fs_info* info; + + info = calloc(1, sizeof(*info)); + if (!info) { + SLOGE("Out of memory!"); + return NULL; + } + + sb = malloc(sizeof(*sb)); + if (!sb) { + SLOGE("Out of memory!"); + free(info); + return NULL; + } + if (read_f2fs_sb(fd, sb)) { + SLOGE("Failed to read superblock"); + free(info); + free(sb); + return NULL; + } + dbg_print_raw_sb_info(sb); + + info->cp_blkaddr = le32_to_cpu(sb->cp_blkaddr); + info->sit_blkaddr = le32_to_cpu(sb->sit_blkaddr); + info->nat_blkaddr = le32_to_cpu(sb->nat_blkaddr); + info->ssa_blkaddr = le32_to_cpu(sb->ssa_blkaddr); + info->main_blkaddr = le32_to_cpu(sb->main_blkaddr); + info->block_size = F2FS_BLKSIZE; + info->total_blocks = sb->block_count; + info->blocks_per_sit = (le32_to_cpu(sb->segment_count_sit) >> 1) + << le32_to_cpu(sb->log_blocks_per_seg); + info->blocks_per_segment = 1U << le32_to_cpu(sb->log_blocks_per_seg); + + if (get_valid_checkpoint_info(fd, sb, &cp, info)) goto error; + dbg_print_raw_ckpt_struct(cp); + + info->total_user_used = le32_to_cpu(cp->valid_block_count); + + u32 bmp_size = le32_to_cpu(cp->sit_ver_bitmap_bytesize); + + /* get sit validity bitmap */ + info->sit_bmp = malloc(bmp_size); + if (!info->sit_bmp) { + SLOGE("Out of memory!"); + goto error; + } + + info->sit_bmp_size = bmp_size; + if (read_structure(fd, + info->cp_valid_cp_blkaddr * F2FS_BLKSIZE + + offsetof(struct f2fs_checkpoint, sit_nat_version_bitmap), + info->sit_bmp, bmp_size)) { + SLOGE("Error getting SIT validity bitmap"); + goto error; + } + + if (gather_sit_info(fd, info)) { + SLOGE("Error getting SIT information"); + goto error; + } + if (get_sit_summary(fd, info, cp)) { + SLOGE("Error getting SIT entries in summary area"); + goto error; + } + dbg_print_info_struct(info); + return info; +error: + free(sb); + free(cp); + free_f2fs_info(info); + return NULL; +} + +void free_f2fs_info(struct f2fs_info* info) { + if (info) { + free(info->sit_blocks); + info->sit_blocks = NULL; + + free(info->sit_bmp); + info->sit_bmp = NULL; + + free(info->sit_sums); + info->sit_sums = NULL; + } + free(info); +} + +uint64_t get_num_blocks_used(struct f2fs_info* info) { + return info->main_blkaddr + info->total_user_used; +} + +int f2fs_test_bit(unsigned int nr, const char* p) { + int mask; + char* addr = (char*)p; + + addr += (nr >> 3); + mask = 1 << (7 - (nr & 0x07)); + return (mask & *addr) != 0; +} + +int run_on_used_blocks(uint64_t startblock, struct f2fs_info* info, + int (*func)(uint64_t pos, void* data), void* data) { + struct f2fs_sit_entry* sit_entry; + uint64_t sit_block_num_cur = 0, segnum = 0, block_offset; + uint64_t block; + unsigned int used, found, i; + + block = startblock; + while (block < info->total_blocks) { + /* TODO: Save only relevant portions of metadata */ + if (block < info->main_blkaddr) { + if (func(block, data)) { + SLOGI("func error"); + return -1; + } + } else { + /* Main Section */ + segnum = (block - info->main_blkaddr) / info->blocks_per_segment; + + /* check the SIT entries in the journal */ + found = 0; + for (i = 0; i < le16_to_cpu(info->sit_sums->journal.n_sits); i++) { + if (le32_to_cpu(segno_in_journal(&info->sit_sums->journal, i)) == segnum) { + sit_entry = &sit_in_journal(&info->sit_sums->journal, i); + found = 1; + break; + } + } + + /* get SIT entry from SIT section */ + if (!found) { + sit_block_num_cur = segnum / SIT_ENTRY_PER_BLOCK; + sit_entry = + &info->sit_blocks[sit_block_num_cur].entries[segnum % SIT_ENTRY_PER_BLOCK]; + } + + block_offset = (block - info->main_blkaddr) % info->blocks_per_segment; + + if (block_offset == 0 && GET_SIT_VBLOCKS(sit_entry) == 0) { + block += info->blocks_per_segment; + continue; + } + + used = f2fs_test_bit(block_offset, (char*)sit_entry->valid_map); + if (used) + if (func(block, data)) return -1; + } + + block++; + } + return 0; +} + +struct privdata { + int count; + int infd; + int outfd; + char* buf; + char* zbuf; + int done; + struct f2fs_info* info; +}; + +/* + * This is a simple test program. It performs a block to block copy of a + * filesystem, replacing blocks identified as unused with 0's. + */ + +int copy_used(uint64_t pos, void* data) { + struct privdata* d = data; + char* buf; + int pdone = (pos * 100) / d->info->total_blocks; + if (pdone > d->done) { + d->done = pdone; + printf("Done with %d percent\n", d->done); + } + + d->count++; + buf = d->buf; + if (read_structure_blk(d->infd, (unsigned long long)pos, d->buf, 1)) { + printf("Error reading!!!\n"); + return -1; + } + + off64_t ret; + ret = lseek64(d->outfd, pos * F2FS_BLKSIZE, SEEK_SET); + if (ret < 0) { + SLOGE("failed to seek\n"); + return ret; + } + + ret = write(d->outfd, d->buf, F2FS_BLKSIZE); + if (ret < 0) { + SLOGE("failed to write\n"); + return ret; + } + if (ret != F2FS_BLKSIZE) { + SLOGE("failed to read all\n"); + return -1; + } + return 0; +} + +int main(int argc, char** argv) { + if (argc != 3) printf("Usage: %s fs_file_in fs_file_out\n", argv[0]); + char* in = argv[1]; + char* out = argv[2]; + int infd, outfd; + + if ((infd = open(in, O_RDONLY)) < 0) { + SLOGE("Cannot open device"); + return 0; + } + if ((outfd = open(out, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) < 0) { + SLOGE("Cannot open output"); + return 0; + } + + struct privdata d; + d.infd = infd; + d.outfd = outfd; + d.count = 0; + struct f2fs_info* info = generate_f2fs_info(infd); + if (!info) { + printf("Failed to generate info!"); + return -1; + } + char* buf = malloc(F2FS_BLKSIZE); + char* zbuf = calloc(1, F2FS_BLKSIZE); + d.buf = buf; + d.zbuf = zbuf; + d.done = 0; + d.info = info; + int expected_count = get_num_blocks_used(info); + run_on_used_blocks(0, info, ©_used, &d); + printf("Copied %d blocks. Expected to copy %d\n", d.count, expected_count); + ftruncate64(outfd, info->total_blocks * F2FS_BLKSIZE); + free_f2fs_info(info); + free(buf); + free(zbuf); + close(infd); + close(outfd); + return 0; +} diff --git a/aosp/system/extras/f2fs_utils/f2fs_sparseblock.h b/aosp/system/extras/f2fs_utils/f2fs_sparseblock.h new file mode 100644 index 00000000..e047ed28 --- /dev/null +++ b/aosp/system/extras/f2fs_utils/f2fs_sparseblock.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef F2FS_UTILS_F2F2_UTILS_H_ +#define F2FS_UTILS_F2F2_UTILS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ver_after(a, b) \ + (typecheck(unsigned long long, a) && typecheck(unsigned long long, b) && \ + ((long long)((a) - (b)) > 0)) + +#define ver_equal(a, b) \ + (typecheck(unsigned long long, a) && typecheck(unsigned long long, b) && \ + ((long long)((a) - (b)) == 0)) + +struct f2fs_sit_block; +struct f2fs_summary_block; + +struct f2fs_info { + uint64_t blocks_per_segment; + uint32_t block_size; + + char* sit_bmp; + uint32_t sit_bmp_size; + uint64_t blocks_per_sit; + struct f2fs_sit_block* sit_blocks; + struct f2fs_summary_block* sit_sums; + + uint64_t cp_blkaddr; + uint64_t cp_valid_cp_blkaddr; + + uint64_t sit_blkaddr; + + uint64_t nat_blkaddr; + + uint64_t ssa_blkaddr; + + uint64_t main_blkaddr; + + uint64_t total_user_used; + uint64_t total_blocks; +}; + +uint64_t get_num_blocks_used(struct f2fs_info* info); +struct f2fs_info* generate_f2fs_info(int fd); +void free_f2fs_info(struct f2fs_info* info); +unsigned int get_f2fs_filesystem_size_sec(char* dev); +int run_on_used_blocks(uint64_t startblock, struct f2fs_info* info, + int (*func)(uint64_t pos, void* data), void* data); + +#ifdef __cplusplus +} +#endif + +#endif // F2FS_UTILS_F2F2_UTILS_H_ diff --git a/aosp/system/extras/f2fs_utils/mkf2fsuserimg.sh b/aosp/system/extras/f2fs_utils/mkf2fsuserimg.sh new file mode 100755 index 00000000..e95a0c51 --- /dev/null +++ b/aosp/system/extras/f2fs_utils/mkf2fsuserimg.sh @@ -0,0 +1,193 @@ +#!/bin/bash +# +# To call this script, make sure make_f2fs is somewhere in PATH + +function usage() { +cat< [sload compression sub-options]] +: number of the sload compression args, e.g. -a LZ4 counts as 2 + when sload compression args are not given, must be 0, + and the default flags will be used. +Note: must conserve the option order +EOT +} + +echo "in mkf2fsuserimg.sh PATH=$PATH" + +MKFS_OPTS="" +SLOAD_OPTS="" +BLOCK_MAP_FILE="" +BLOCK_MAP_OPT="" + +if [ $# -lt 2 ]; then + usage + exit 1 +fi + +OUTPUT_FILE=$1 +SIZE=$2 +shift; shift + +SPARSE_IMG="false" +if [[ "$1" == "-S" ]]; then + MKFS_OPTS+=" -S $SIZE" + SLOAD_OPTS+=" -S" + BLOCK_MAP_OPT+=" -S -M" + SPARSE_IMG="true" + shift +fi + +if [[ "$1" == "-C" ]]; then + SLOAD_OPTS+=" -C $2" + shift; shift +fi +if [[ "$1" == "-f" ]]; then + SLOAD_OPTS+=" -f $2" + shift; shift +fi +if [[ "$1" == "-D" ]]; then + SLOAD_OPTS+=" -p $2" + shift; shift +fi +if [[ "$1" == "-s" ]]; then + SLOAD_OPTS+=" -s $2" + shift; shift +fi +if [[ "$1" == "-t" ]]; then + MOUNT_POINT=$2 + shift; shift +fi + +if [ -z $MOUNT_POINT ]; then + echo "Mount point is required" + exit 2 +fi + +if [[ ${MOUNT_POINT:0:1} != "/" ]]; then + MOUNT_POINT="/"$MOUNT_POINT +fi + +SLOAD_OPTS+=" -t $MOUNT_POINT" + +if [[ "$1" == "-T" ]]; then + SLOAD_OPTS+=" -T $2" + shift; shift +fi + +if [[ "$1" == "-B" ]]; then + BLOCK_MAP_FILE="$2" + shift; shift +fi + +if [[ "$1" == "-L" ]]; then + MKFS_OPTS+=" -l $2" + shift; shift +fi + +if [[ "$1" == "--prjquota" ]]; then + MKFS_OPTS+=" -O project_quota,extra_attr" + shift; +fi +if [[ "$1" == "--casefold" ]]; then + MKFS_OPTS+=" -O casefold -C utf8" + shift; +fi + +if [[ "$1" == "--compression" ]]; then + COMPRESS_SUPPORT=1 + MKFS_OPTS+=" -O compression,extra_attr" + shift; +fi +if [[ "$1" == "--readonly" ]]; then + MKFS_OPTS+=" -O ro" + READONLY=1 + shift; +fi + +if [[ "$1" == "--sldc" ]]; then + if [ -z "$COMPRESS_SUPPORT" ]; then + echo "--sldc needs --compression flag" + exit 3 + fi + SLOAD_OPTS+=" -c" + shift + SLDC_NUM_ARGS=$1 + case $SLDC_NUM_ARGS in + ''|*[!0-9]*) + echo "--sldc needs a number" + exit 3 ;; + esac + shift + while [ $SLDC_NUM_ARGS -gt 0 ]; do + SLOAD_OPTS+=" $1" + shift + (( SLDC_NUM_ARGS-- )) + done +fi + +if [ -z $SIZE ]; then + echo "Need size of filesystem" + exit 2 +fi + +function _truncate() +{ + if [ "$SPARSE_IMG" = "true" ]; then + return + fi + + TRUNCATE_CMD="truncate -s $SIZE $OUTPUT_FILE" + echo $TRUNCATE_CMD + $TRUNCATE_CMD + if [ $? -ne 0 ]; then + exit 3 + fi +} + +function _build() +{ + MAKE_F2FS_CMD="make_f2fs -g android $MKFS_OPTS $OUTPUT_FILE" + echo $MAKE_F2FS_CMD + $MAKE_F2FS_CMD + if [ $? -ne 0 ]; then + if [ "$SPARSE_IMG" = "false" ]; then + rm -f $OUTPUT_FILE + fi + exit 4 + fi + + SLOAD_F2FS_CMD="sload_f2fs $SLOAD_OPTS $OUTPUT_FILE" + echo $SLOAD_F2FS_CMD + SLOAD_LOG=`$SLOAD_F2FS_CMD` + # allow 1: Filesystem errors corrected + ret=$? + if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then + rm -f $OUTPUT_FILE + exit 4 + fi + MB_SIZE=`echo "$SLOAD_LOG" | grep "Max image size" | awk '{print $5}'` + SIZE=$(((MB_SIZE + 6) * 1024 * 1024)) +} + +_truncate +_build + +# readonly can reduce the image +if [ "$READONLY" ]; then + if [ "$SPARSE_IMG" = "true" ]; then + MKFS_OPTS+=" -S $SIZE" + rm -f $OUTPUT_FILE && touch $OUTPUT_FILE + fi + _truncate + _build + # build block map + if [ "$BLOCK_MAP_FILE" ]; then + fsck.f2fs $BLOCK_MAP_OPT $OUTPUT_FILE > $BLOCK_MAP_FILE + fi +fi +exit 0 diff --git a/aosp/system/libufdt/utils/src/mkdtboimg.py b/aosp/system/libufdt/utils/src/mkdtboimg.py new file mode 100755 index 00000000..2634c76a --- /dev/null +++ b/aosp/system/libufdt/utils/src/mkdtboimg.py @@ -0,0 +1,1052 @@ +#! /usr/bin/env python3 +# Copyright 2017, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +"""Tool for packing multiple DTB/DTBO files into a single image""" + +import argparse +import fnmatch +import os +import struct +import zlib +from array import array +from collections import namedtuple +from sys import stdout + +class CompressionFormat(object): + """Enum representing DT compression format for a DT entry. + """ + NO_COMPRESSION = 0x00 + ZLIB_COMPRESSION = 0x01 + GZIP_COMPRESSION = 0x02 + +class DtEntry(object): + """Provides individual DT image file arguments to be added to a DTBO. + + Attributes: + REQUIRED_KEYS_V0: 'keys' needed to be present in the dictionary passed to instantiate + an object of this class when a DTBO header of version 0 is used. + REQUIRED_KEYS_V1: 'keys' needed to be present in the dictionary passed to instantiate + an object of this class when a DTBO header of version 1 is used. + COMPRESSION_FORMAT_MASK: Mask to retrieve compression info for DT entry from flags field + when a DTBO header of version 1 is used. + """ + COMPRESSION_FORMAT_MASK = 0x0f + REQUIRED_KEYS_V0 = ('dt_file', 'dt_size', 'dt_offset', 'id', 'rev', + 'custom0', 'custom1', 'custom2', 'custom3') + REQUIRED_KEYS_V1 = ('dt_file', 'dt_size', 'dt_offset', 'id', 'rev', + 'flags', 'custom0', 'custom1', 'custom2') + + @staticmethod + def __get_number_or_prop(arg): + """Converts string to integer or reads the property from DT image. + + Args: + arg: String containing the argument provided on the command line. + + Returns: + An integer property read from DT file or argument string + converted to integer + """ + + if not arg or arg[0] == '+' or arg[0] == '-': + raise ValueError('Invalid argument passed to DTImage') + if arg[0] == '/': + # TODO(b/XXX): Use pylibfdt to get property value from DT + raise ValueError('Invalid argument passed to DTImage') + else: + base = 10 + if arg.startswith('0x') or arg.startswith('0X'): + base = 16 + elif arg.startswith('0'): + base = 8 + return int(arg, base) + + def __init__(self, **kwargs): + """Constructor for DtEntry object. + + Initializes attributes from dictionary object that contains + values keyed with names equivalent to the class's attributes. + + Args: + kwargs: Dictionary object containing values to instantiate + class members with. Expected keys in dictionary are from + the tuple (_REQUIRED_KEYS) + """ + + self.__version = kwargs['version'] + required_keys = None + if self.__version == 0: + required_keys = self.REQUIRED_KEYS_V0 + elif self.__version == 1: + required_keys = self.REQUIRED_KEYS_V1 + + missing_keys = set(required_keys) - set(kwargs) + if missing_keys: + raise ValueError('Missing keys in DtEntry constructor: %r' % + sorted(missing_keys)) + + self.__dt_file = kwargs['dt_file'] + self.__dt_offset = kwargs['dt_offset'] + self.__dt_size = kwargs['dt_size'] + self.__id = self.__get_number_or_prop(kwargs['id']) + self.__rev = self.__get_number_or_prop(kwargs['rev']) + if self.__version == 1: + self.__flags = self.__get_number_or_prop(kwargs['flags']) + self.__custom0 = self.__get_number_or_prop(kwargs['custom0']) + self.__custom1 = self.__get_number_or_prop(kwargs['custom1']) + self.__custom2 = self.__get_number_or_prop(kwargs['custom2']) + if self.__version == 0: + self.__custom3 = self.__get_number_or_prop(kwargs['custom3']) + + def __str__(self): + sb = [] + sb.append('{key:>20} = {value:d}'.format(key='dt_size', + value=self.__dt_size)) + sb.append('{key:>20} = {value:d}'.format(key='dt_offset', + value=self.__dt_offset)) + sb.append('{key:>20} = {value:08x}'.format(key='id', + value=self.__id)) + sb.append('{key:>20} = {value:08x}'.format(key='rev', + value=self.__rev)) + if self.__version == 1: + sb.append('{key:>20} = {value:08x}'.format(key='flags', + value=self.__flags)) + sb.append('{key:>20} = {value:08x}'.format(key='custom[0]', + value=self.__custom0)) + sb.append('{key:>20} = {value:08x}'.format(key='custom[1]', + value=self.__custom1)) + sb.append('{key:>20} = {value:08x}'.format(key='custom[2]', + value=self.__custom2)) + if self.__version == 0: + sb.append('{key:>20} = {value:08x}'.format(key='custom[3]', + value=self.__custom3)) + return '\n'.join(sb) + + def compression_info(self): + """CompressionFormat: compression format for DT image file. + + Args: + version: Version of DTBO header, compression is only + supported from version 1. + """ + if self.__version == 0: + return CompressionFormat.NO_COMPRESSION + return self.flags & self.COMPRESSION_FORMAT_MASK + + @property + def dt_file(self): + """file: File handle to the DT image file.""" + return self.__dt_file + + @property + def size(self): + """int: size in bytes of the DT image file.""" + return self.__dt_size + + @size.setter + def size(self, value): + self.__dt_size = value + + @property + def dt_offset(self): + """int: offset in DTBO file for this DT image.""" + return self.__dt_offset + + @dt_offset.setter + def dt_offset(self, value): + self.__dt_offset = value + + @property + def image_id(self): + """int: DT entry _id for this DT image.""" + return self.__id + + @property + def rev(self): + """int: DT entry _rev for this DT image.""" + return self.__rev + + @property + def flags(self): + """int: DT entry _flags for this DT image.""" + return self.__flags + + @property + def custom0(self): + """int: DT entry _custom0 for this DT image.""" + return self.__custom0 + + @property + def custom1(self): + """int: DT entry _custom1 for this DT image.""" + return self.__custom1 + + @property + def custom2(self): + """int: DT entry custom2 for this DT image.""" + return self.__custom2 + + @property + def custom3(self): + """int: DT entry custom3 for this DT image.""" + return self.__custom3 + +class Dtbo(object): + """ + Provides parser, reader, writer for dumping and creating Device Tree Blob + Overlay (DTBO) images. + + Attributes: + _DTBO_MAGIC: Device tree table header magic. + _ACPIO_MAGIC: Advanced Configuration and Power Interface table header + magic. + _DT_TABLE_HEADER_SIZE: Size of Device tree table header. + _DT_TABLE_HEADER_INTS: Number of integers in DT table header. + _DT_ENTRY_HEADER_SIZE: Size of Device tree entry header within a DTBO. + _DT_ENTRY_HEADER_INTS: Number of integers in DT entry header. + _GZIP_COMPRESSION_WBITS: Argument 'wbits' for gzip compression + _ZLIB_DECOMPRESSION_WBITS: Argument 'wbits' for zlib/gzip compression + """ + + _DTBO_MAGIC = 0xd7b7ab1e + _ACPIO_MAGIC = 0x41435049 + _DT_TABLE_HEADER_SIZE = struct.calcsize('>8I') + _DT_TABLE_HEADER_INTS = 8 + _DT_ENTRY_HEADER_SIZE = struct.calcsize('>8I') + _DT_ENTRY_HEADER_INTS = 8 + _GZIP_COMPRESSION_WBITS = 31 + _ZLIB_DECOMPRESSION_WBITS = 47 + + def _update_dt_table_header(self): + """Converts header entries into binary data for DTBO header. + + Packs the current Device tree table header attribute values in + metadata buffer. + """ + struct.pack_into('>8I', self.__metadata, 0, self.magic, + self.total_size, self.header_size, + self.dt_entry_size, self.dt_entry_count, + self.dt_entries_offset, self.page_size, + self.version) + + def _update_dt_entry_header(self, dt_entry, metadata_offset): + """Converts each DT entry header entry into binary data for DTBO file. + + Packs the current device tree table entry attribute into + metadata buffer as device tree entry header. + + Args: + dt_entry: DtEntry object for the header to be packed. + metadata_offset: Offset into metadata buffer to begin writing. + dtbo_offset: Offset where the DT image file for this dt_entry can + be found in the resulting DTBO image. + """ + if self.version == 0: + struct.pack_into('>8I', self.__metadata, metadata_offset, dt_entry.size, + dt_entry.dt_offset, dt_entry.image_id, dt_entry.rev, + dt_entry.custom0, dt_entry.custom1, dt_entry.custom2, + dt_entry.custom3) + elif self.version == 1: + struct.pack_into('>8I', self.__metadata, metadata_offset, dt_entry.size, + dt_entry.dt_offset, dt_entry.image_id, dt_entry.rev, + dt_entry.flags, dt_entry.custom0, dt_entry.custom1, + dt_entry.custom2) + + + def _update_metadata(self): + """Updates the DTBO metadata. + + Initialize the internal metadata buffer and fill it with all Device + Tree table entries and update the DTBO header. + """ + + self.__metadata = array('b', b' ' * self.__metadata_size) + metadata_offset = self.header_size + for dt_entry in self.__dt_entries: + self._update_dt_entry_header(dt_entry, metadata_offset) + metadata_offset += self.dt_entry_size + self._update_dt_table_header() + + def _read_dtbo_header(self, buf): + """Reads DTBO file header into metadata buffer. + + Unpack and read the DTBO table header from given buffer. The + buffer size must exactly be equal to _DT_TABLE_HEADER_SIZE. + + Args: + buf: Bytebuffer read directly from the file of size + _DT_TABLE_HEADER_SIZE. + """ + (self.magic, self.total_size, self.header_size, + self.dt_entry_size, self.dt_entry_count, self.dt_entries_offset, + self.page_size, self.version) = struct.unpack_from('>8I', buf, 0) + + # verify the header + if self.magic != self._DTBO_MAGIC and self.magic != self._ACPIO_MAGIC: + raise ValueError('Invalid magic number 0x%x in DTBO/ACPIO file' % + (self.magic)) + + if self.header_size != self._DT_TABLE_HEADER_SIZE: + raise ValueError('Invalid header size (%d) in DTBO/ACPIO file' % + (self.header_size)) + + if self.dt_entry_size != self._DT_ENTRY_HEADER_SIZE: + raise ValueError('Invalid DT entry header size (%d) in DTBO/ACPIO file' % + (self.dt_entry_size)) + + def _read_dt_entries_from_metadata(self): + """Reads individual DT entry headers from metadata buffer. + + Unpack and read the DTBO DT entry headers from the internal buffer. + The buffer size must exactly be equal to _DT_TABLE_HEADER_SIZE + + (_DT_ENTRY_HEADER_SIZE * dt_entry_count). The method raises exception + if DT entries have already been set for this object. + """ + + if self.__dt_entries: + raise ValueError('DTBO DT entries can be added only once') + + offset = self.dt_entries_offset // 4 + params = {} + params['version'] = self.version + params['dt_file'] = None + for i in range(0, self.dt_entry_count): + dt_table_entry = self.__metadata[offset:offset + self._DT_ENTRY_HEADER_INTS] + params['dt_size'] = dt_table_entry[0] + params['dt_offset'] = dt_table_entry[1] + for j in range(2, self._DT_ENTRY_HEADER_INTS): + required_keys = None + if self.version == 0: + required_keys = DtEntry.REQUIRED_KEYS_V0 + elif self.version == 1: + required_keys = DtEntry.REQUIRED_KEYS_V1 + params[required_keys[j + 1]] = str(dt_table_entry[j]) + dt_entry = DtEntry(**params) + self.__dt_entries.append(dt_entry) + offset += self._DT_ENTRY_HEADER_INTS + + def _read_dtbo_image(self): + """Parse the input file and instantiate this object.""" + + # First check if we have enough to read the header + file_size = os.fstat(self.__file.fileno()).st_size + if file_size < self._DT_TABLE_HEADER_SIZE: + raise ValueError('Invalid DTBO file') + + self.__file.seek(0) + buf = self.__file.read(self._DT_TABLE_HEADER_SIZE) + self._read_dtbo_header(buf) + + self.__metadata_size = (self.header_size + + self.dt_entry_count * self.dt_entry_size) + if file_size < self.__metadata_size: + raise ValueError('Invalid or truncated DTBO file of size %d expected %d' % + file_size, self.__metadata_size) + + num_ints = (self._DT_TABLE_HEADER_INTS + + self.dt_entry_count * self._DT_ENTRY_HEADER_INTS) + if self.dt_entries_offset > self._DT_TABLE_HEADER_SIZE: + num_ints += (self.dt_entries_offset - self._DT_TABLE_HEADER_SIZE) / 4 + format_str = '>' + str(num_ints) + 'I' + self.__file.seek(0) + self.__metadata = struct.unpack(format_str, + self.__file.read(self.__metadata_size)) + self._read_dt_entries_from_metadata() + + def _find_dt_entry_with_same_file(self, dt_entry): + """Finds DT Entry that has identical backing DT file. + + Args: + dt_entry: DtEntry object whose 'dtfile' we find for existence in the + current 'dt_entries'. + Returns: + If a match by file path is found, the corresponding DtEntry object + from internal list is returned. If not, 'None' is returned. + """ + + dt_entry_path = os.path.realpath(dt_entry.dt_file.name) + for entry in self.__dt_entries: + entry_path = os.path.realpath(entry.dt_file.name) + if entry_path == dt_entry_path: + return entry + return None + + def __init__(self, file_handle, dt_type='dtb', page_size=None, version=0): + """Constructor for Dtbo Object + + Args: + file_handle: The Dtbo File handle corresponding to this object. + The file handle can be used to write to (in case of 'create') + or read from (in case of 'dump') + """ + + self.__file = file_handle + self.__dt_entries = [] + self.__metadata = None + self.__metadata_size = 0 + + # if page_size is given, assume the object is being instantiated to + # create a DTBO file + if page_size: + if dt_type == 'acpi': + self.magic = self._ACPIO_MAGIC + else: + self.magic = self._DTBO_MAGIC + self.total_size = self._DT_TABLE_HEADER_SIZE + self.header_size = self._DT_TABLE_HEADER_SIZE + self.dt_entry_size = self._DT_ENTRY_HEADER_SIZE + self.dt_entry_count = 0 + self.dt_entries_offset = self._DT_TABLE_HEADER_SIZE + self.page_size = page_size + self.version = version + self.__metadata_size = self._DT_TABLE_HEADER_SIZE + else: + self._read_dtbo_image() + + def __str__(self): + sb = [] + sb.append('dt_table_header:') + _keys = ('magic', 'total_size', 'header_size', 'dt_entry_size', + 'dt_entry_count', 'dt_entries_offset', 'page_size', 'version') + for key in _keys: + if key == 'magic': + sb.append('{key:>20} = {value:08x}'.format(key=key, + value=self.__dict__[key])) + else: + sb.append('{key:>20} = {value:d}'.format(key=key, + value=self.__dict__[key])) + count = 0 + for dt_entry in self.__dt_entries: + sb.append('dt_table_entry[{0:d}]:'.format(count)) + sb.append(str(dt_entry)) + count = count + 1 + return '\n'.join(sb) + + @property + def dt_entries(self): + """Returns a list of DtEntry objects found in DTBO file.""" + return self.__dt_entries + + def compress_dt_entry(self, compression_format, dt_entry_file): + """Compresses a DT entry. + + Args: + compression_format: Compression format for DT Entry + dt_entry_file: File handle to read DT entry from. + + Returns: + Compressed DT entry and its length. + + Raises: + ValueError if unrecognized compression format is found. + """ + compress_zlib = zlib.compressobj() # zlib + compress_gzip = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, + zlib.DEFLATED, self._GZIP_COMPRESSION_WBITS) # gzip + compression_obj_dict = { + CompressionFormat.NO_COMPRESSION: None, + CompressionFormat.ZLIB_COMPRESSION: compress_zlib, + CompressionFormat.GZIP_COMPRESSION: compress_gzip, + } + + if compression_format not in compression_obj_dict: + ValueError("Bad compression format %d" % compression_format) + + if compression_format is CompressionFormat.NO_COMPRESSION: + dt_entry = dt_entry_file.read() + else: + compression_object = compression_obj_dict[compression_format] + dt_entry_file.seek(0) + dt_entry = compression_object.compress(dt_entry_file.read()) + dt_entry += compression_object.flush() + return dt_entry, len(dt_entry) + + def add_dt_entries(self, dt_entries): + """Adds DT image files to the DTBO object. + + Adds a list of Dtentry Objects to the DTBO image. The changes are not + committed to the output file until commit() is called. + + Args: + dt_entries: List of DtEntry object to be added. + + Returns: + A buffer containing all DT entries. + + Raises: + ValueError: if the list of DT entries is empty or if a list of DT entries + has already been added to the DTBO. + """ + if not dt_entries: + raise ValueError('Attempted to add empty list of DT entries') + + if self.__dt_entries: + raise ValueError('DTBO DT entries can be added only once') + + dt_entry_count = len(dt_entries) + dt_offset = (self.header_size + + dt_entry_count * self.dt_entry_size) + + dt_entry_buf = b"" + for dt_entry in dt_entries: + if not isinstance(dt_entry, DtEntry): + raise ValueError('Adding invalid DT entry object to DTBO') + entry = self._find_dt_entry_with_same_file(dt_entry) + dt_entry_compression_info = dt_entry.compression_info() + if entry and (entry.compression_info() == dt_entry_compression_info): + dt_entry.dt_offset = entry.dt_offset + dt_entry.size = entry.size + else: + dt_entry.dt_offset = dt_offset + compressed_entry, dt_entry.size = self.compress_dt_entry(dt_entry_compression_info, + dt_entry.dt_file) + dt_entry_buf += compressed_entry + dt_offset += dt_entry.size + self.total_size += dt_entry.size + self.__dt_entries.append(dt_entry) + self.dt_entry_count += 1 + self.__metadata_size += self.dt_entry_size + self.total_size += self.dt_entry_size + + return dt_entry_buf + + def extract_dt_file(self, idx, fout, decompress): + """Extract DT Image files embedded in the DTBO file. + + Extracts Device Tree blob image file at given index into a file handle. + + Args: + idx: Index of the DT entry in the DTBO file. + fout: File handle where the DTB at index idx to be extracted into. + decompress: If a DT entry is compressed, decompress it before writing + it to the file handle. + + Raises: + ValueError: if invalid DT entry index or compression format is detected. + """ + if idx > self.dt_entry_count: + raise ValueError('Invalid index %d of DtEntry' % idx) + + size = self.dt_entries[idx].size + offset = self.dt_entries[idx].dt_offset + self.__file.seek(offset, 0) + fout.seek(0) + compression_format = self.dt_entries[idx].compression_info() + if decompress and compression_format: + if (compression_format == CompressionFormat.ZLIB_COMPRESSION or + compression_format == CompressionFormat.GZIP_COMPRESSION): + fout.write(zlib.decompress(self.__file.read(size), self._ZLIB_DECOMPRESSION_WBITS)) + else: + raise ValueError("Unknown compression format detected") + else: + fout.write(self.__file.read(size)) + + def commit(self, dt_entry_buf): + """Write out staged changes to the DTBO object to create a DTBO file. + + Writes a fully instantiated Dtbo Object into the output file using the + file handle present in '_file'. No checks are performed on the object + except for existence of output file handle on the object before writing + out the file. + + Args: + dt_entry_buf: Buffer containing all DT entries. + """ + if not self.__file: + raise ValueError('No file given to write to.') + + if not self.__dt_entries: + raise ValueError('No DT image files to embed into DTBO image given.') + + self._update_metadata() + + self.__file.seek(0) + self.__file.write(self.__metadata) + self.__file.write(dt_entry_buf) + self.__file.flush() + + +def parse_dt_entry(global_args, arglist): + """Parse arguments for single DT entry file. + + Parses command line arguments for single DT image file while + creating a Device tree blob overlay (DTBO). + + Args: + global_args: Dtbo object containing global default values + for DtEntry attributes. + arglist: Command line argument list for this DtEntry. + + Returns: + A Namespace object containing all values to instantiate DtEntry object. + """ + + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('dt_file', nargs='?', + type=argparse.FileType('rb'), + default=None) + parser.add_argument('--id', type=str, dest='id', action='store', + default=global_args.global_id) + parser.add_argument('--rev', type=str, dest='rev', + action='store', default=global_args.global_rev) + parser.add_argument('--flags', type=str, dest='flags', + action='store', + default=global_args.global_flags) + parser.add_argument('--custom0', type=str, dest='custom0', + action='store', + default=global_args.global_custom0) + parser.add_argument('--custom1', type=str, dest='custom1', + action='store', + default=global_args.global_custom1) + parser.add_argument('--custom2', type=str, dest='custom2', + action='store', + default=global_args.global_custom2) + parser.add_argument('--custom3', type=str, dest='custom3', + action='store', + default=global_args.global_custom3) + return parser.parse_args(arglist) + + +def parse_dt_entries(global_args, arg_list): + """Parse all DT entries from command line. + + Parse all DT image files and their corresponding attribute from + command line + + Args: + global_args: Argument containing default global values for _id, + _rev and customX. + arg_list: The remainder of the command line after global options + DTBO creation have been parsed. + + Returns: + A List of DtEntry objects created after parsing the command line + given in argument. + """ + dt_entries = [] + img_file_idx = [] + idx = 0 + # find all positional arguments (i.e. DT image file paths) + for arg in arg_list: + if not arg.startswith("--"): + img_file_idx.append(idx) + idx = idx + 1 + + if not img_file_idx: + raise ValueError('Input DT images must be provided') + + total_images = len(img_file_idx) + for idx in range(total_images): + start_idx = img_file_idx[idx] + if idx == total_images - 1: + argv = arg_list[start_idx:] + else: + end_idx = img_file_idx[idx + 1] + argv = arg_list[start_idx:end_idx] + args = parse_dt_entry(global_args, argv) + params = vars(args) + params['version'] = global_args.version + params['dt_offset'] = 0 + params['dt_size'] = os.fstat(params['dt_file'].fileno()).st_size + dt_entries.append(DtEntry(**params)) + + return dt_entries + +def parse_config_option(line, is_global, dt_keys, global_key_types): + """Parses a single line from the configuration file. + + Args: + line: String containing the key=value line from the file. + is_global: Boolean indicating if we should parse global or DT entry + specific option. + dt_keys: Tuple containing all valid DT entry and global option strings + in configuration file. + global_key_types: A dict of global options and their corresponding types. It + contains all exclusive valid global option strings in configuration + file that are not repeated in dt entry options. + + Returns: + Returns a tuple for parsed key and value for the option. Also, checks + the key to make sure its valid. + """ + + if line.find('=') == -1: + raise ValueError('Invalid line (%s) in configuration file' % line) + + key, value = (x.strip() for x in line.split('=')) + if is_global and key in global_key_types: + if global_key_types[key] is int: + value = int(value) + elif key not in dt_keys: + raise ValueError('Invalid option (%s) in configuration file' % key) + + return key, value + +def parse_config_file(fin, dt_keys, global_key_types): + """Parses the configuration file for creating DTBO image. + + Args: + fin: File handle for configuration file + is_global: Boolean indicating if we should parse global or DT entry + specific option. + dt_keys: Tuple containing all valid DT entry and global option strings + in configuration file. + global_key_types: A dict of global options and their corresponding types. It + contains all exclusive valid global option strings in configuration + file that are not repeated in dt entry options. + + Returns: + global_args, dt_args: Tuple of a dictionary with global arguments + and a list of dictionaries for all DT entry specific arguments the + following format. + global_args: + {'id' : , 'rev' : ...} + dt_args: + [{'filename' : 'dt_file_name', 'id' : , + 'rev' : ...}, + {'filename' : 'dt_file_name2', 'id' : , + 'rev' : ...}, ... + ] + """ + + # set all global defaults + global_args = dict((k, '0') for k in dt_keys) + global_args['dt_type'] = 'dtb' + global_args['page_size'] = 2048 + global_args['version'] = 0 + + dt_args = [] + found_dt_entry = False + count = -1 + for line in fin: + line = line.rstrip() + if line.lstrip().startswith('#'): + continue + comment_idx = line.find('#') + line = line if comment_idx == -1 else line[0:comment_idx] + if not line or line.isspace(): + continue + if line.startswith((' ', '\t')) and not found_dt_entry: + # This is a global argument + key, value = parse_config_option(line, True, dt_keys, global_key_types) + global_args[key] = value + elif line.find('=') != -1: + key, value = parse_config_option(line, False, dt_keys, global_key_types) + dt_args[-1][key] = value + else: + found_dt_entry = True + count += 1 + dt_args.append({}) + dt_args[-1]['filename'] = line.strip() + return global_args, dt_args + +def parse_create_args(arg_list): + """Parse command line arguments for 'create' sub-command. + + Args: + arg_list: All command line arguments except the outfile file name. + + Returns: + The list of remainder of the command line arguments after parsing + for 'create'. + """ + + image_arg_index = 0 + for arg in arg_list: + if not arg.startswith("--"): + break + image_arg_index = image_arg_index + 1 + + argv = arg_list[0:image_arg_index] + remainder = arg_list[image_arg_index:] + parser = argparse.ArgumentParser(prog='create', add_help=False) + parser.add_argument('--dt_type', type=str, dest='dt_type', + action='store', default='dtb') + parser.add_argument('--page_size', type=int, dest='page_size', + action='store', default=2048) + parser.add_argument('--version', type=int, dest='version', + action='store', default=0) + parser.add_argument('--id', type=str, dest='global_id', + action='store', default='0') + parser.add_argument('--rev', type=str, dest='global_rev', + action='store', default='0') + parser.add_argument('--flags', type=str, dest='global_flags', + action='store', default='0') + parser.add_argument('--custom0', type=str, dest='global_custom0', + action='store', default='0') + parser.add_argument('--custom1', type=str, dest='global_custom1', + action='store', default='0') + parser.add_argument('--custom2', type=str, dest='global_custom2', + action='store', default='0') + parser.add_argument('--custom3', type=str, dest='global_custom3', + action='store', default='0') + args = parser.parse_args(argv) + return args, remainder + +def parse_dump_cmd_args(arglist): + """Parse command line arguments for 'dump' sub-command. + + Args: + arglist: List of all command line arguments including the outfile + file name if exists. + + Returns: + A namespace object of parsed arguments. + """ + + parser = argparse.ArgumentParser(prog='dump') + parser.add_argument('--output', '-o', nargs='?', + type=argparse.FileType('w'), + dest='outfile', + default=stdout) + parser.add_argument('--dtb', '-b', nargs='?', type=str, + dest='dtfilename') + parser.add_argument('--decompress', action='store_true', dest='decompress') + return parser.parse_args(arglist) + +def parse_config_create_cmd_args(arglist): + """Parse command line arguments for 'cfg_create subcommand. + + Args: + arglist: A list of all command line arguments including the + mandatory input configuration file name. + + Returns: + A Namespace object of parsed arguments. + """ + parser = argparse.ArgumentParser(prog='cfg_create') + parser.add_argument('conf_file', nargs='?', + type=argparse.FileType('r'), + default=None) + cwd = os.getcwd() + parser.add_argument('--dtb-dir', '-d', nargs='?', type=str, + dest='dtbdir', default=cwd) + return parser.parse_args(arglist) + +def create_dtbo_image(fout, argv): + """Create Device Tree Blob Overlay image using provided arguments. + + Args: + fout: Output file handle to write to. + argv: list of command line arguments. + """ + + global_args, remainder = parse_create_args(argv) + if not remainder: + raise ValueError('List of dtimages to add to DTBO not provided') + dt_entries = parse_dt_entries(global_args, remainder) + dtbo = Dtbo(fout, global_args.dt_type, global_args.page_size, global_args.version) + dt_entry_buf = dtbo.add_dt_entries(dt_entries) + dtbo.commit(dt_entry_buf) + fout.close() + +def dump_dtbo_image(fin, argv): + """Dump DTBO file. + + Dump Device Tree Blob Overlay metadata as output and the device + tree image files embedded in the DTBO image into file(s) provided + as arguments + + Args: + fin: Input DTBO image files. + argv: list of command line arguments. + """ + dtbo = Dtbo(fin) + args = parse_dump_cmd_args(argv) + if args.dtfilename: + num_entries = len(dtbo.dt_entries) + for idx in range(0, num_entries): + with open(args.dtfilename + '.{:d}'.format(idx), 'wb') as fout: + dtbo.extract_dt_file(idx, fout, args.decompress) + args.outfile.write(str(dtbo) + '\n') + args.outfile.close() + +def create_dtbo_image_from_config(fout, argv): + """Create DTBO file from a configuration file. + + Args: + fout: Output file handle to write to. + argv: list of command line arguments. + """ + args = parse_config_create_cmd_args(argv) + if not args.conf_file: + raise ValueError('Configuration file must be provided') + + _DT_KEYS = ('id', 'rev', 'flags', 'custom0', 'custom1', 'custom2', 'custom3') + _GLOBAL_KEY_TYPES = {'dt_type': str, 'page_size': int, 'version': int} + + global_args, dt_args = parse_config_file(args.conf_file, + _DT_KEYS, _GLOBAL_KEY_TYPES) + version = global_args['version'] + + params = {} + params['version'] = version + dt_entries = [] + for dt_arg in dt_args: + filepath = dt_arg['filename'] + if not os.path.isabs(filepath): + for root, dirnames, filenames in os.walk(args.dtbdir): + for filename in fnmatch.filter(filenames, os.path.basename(filepath)): + filepath = os.path.join(root, filename) + params['dt_file'] = open(filepath, 'rb') + params['dt_offset'] = 0 + params['dt_size'] = os.fstat(params['dt_file'].fileno()).st_size + for key in _DT_KEYS: + if key not in dt_arg: + params[key] = global_args[key] + else: + params[key] = dt_arg[key] + dt_entries.append(DtEntry(**params)) + + # Create and write DTBO file + dtbo = Dtbo(fout, global_args['dt_type'], global_args['page_size'], version) + dt_entry_buf = dtbo.add_dt_entries(dt_entries) + dtbo.commit(dt_entry_buf) + fout.close() + +def print_default_usage(progname): + """Prints program's default help string. + + Args: + progname: This program's name. + """ + sb = [] + sb.append(' ' + progname + ' help all') + sb.append(' ' + progname + ' help \n') + sb.append(' commands:') + sb.append(' help, dump, create, cfg_create') + print('\n'.join(sb)) + +def print_dump_usage(progname): + """Prints usage for 'dump' sub-command. + + Args: + progname: This program's name. + """ + sb = [] + sb.append(' ' + progname + ' dump (