This second security post will describe how to leverage the update feature available in our KitKat 4.4.3 release.
Android devices in the field can receive and install over-the-air (OTA) updates to the system and application software. Devices have a special recovery partition with the software needed to unpack a downloaded update package and apply it to the rest of the system.
The goal of this post to for customers to understand how to generate and apply OTA updates as well as knowing the security behind this process. It will describe the structure of these packages and the tools provided to build them.
For the impatient
We’ve updated our KitKat 4.4.3 releases to support OTA, and will be releasing updates through the normal Over-The-Air process. This feature will be present if you load a version dated 2015-05-11 or newer.
Please refer to our KitKat 4.4.3 release blog post to download the Android image for your board.
OTA system updates
A typical OTA update contains the following steps:
- Device performs regular check in with OTA servers and is notified of the availability of an update, including the URL of the update package and a description string to show the user.
- Update downloads to a cache or data partition, and its cryptographic signature is verified against the certificates in /system/etc/security/otacerts.zip. User is prompted to install the update.
- Device reboots into recovery mode, in which the kernel and system in the recovery partition are booted instead of the kernel in the boot partition.
- Recovery binary is started by init. It finds command-line arguments in /cache/recovery/command that point it to the downloaded package.
- Recovery verifies the cryptographic signature of the package against the public keys in /res/keys (part of the RAM disk contained in the recovery partition).
- Data is pulled from the package and used to update the boot, system, and/or vendor partitions as necessary. One of the new files left on the system partition contains the contents of the new recovery partition.
- Device reboots normally.
- The newly updated boot partition is loaded, and it mounts and starts executing binaries in the newly updated system partition.
- As part of normal startup, the system checks the contents of the recovery partition against the desired contents (which were previously stored as a file in /system). They are different, so the recovery partition is reflashed with the desired contents. (On subsequent boots, the recovery partition already contains the new contents, so no reflash is necessary.)
The system update is complete!
It is assumed below that you are in possession of our KitKat 4.4.3 release source tree under your ~/myandroid folder.
A full update is one where the entire final state of the device (system, boot, and recovery partitions) is contained in the package. The Android build system includes a default target for OTA package generation:
~/myandroid$ . build/envsetup.sh && lunch nitrogen6x-eng ~/myandroid$ make dist
The output package will be located under out/dist/nitrogen6x-ota-.zip.
Our previous security post explains the signing process and keys, please refer to it if you wish to specify which keys to use for the update package.
An incremental update contains a set of binary patches to be applied to the data already on the device. This can result in considerably smaller update packages:
- Files that have not changed don’t need to be included.
- Files that have changed are often very similar to their previous versions, so the package need only contain an encoding of the differences between the two files.
Note: incremental hasn’t been tested yet and might require some work due to our Boundary-specific modifications.
Here is the procedure to generate such package:
~/myandroid$ ./build/tools/releasetools/ota_from_target_files -i out/dist/nitrogen6x-ota-.zip out/dist/nitrogen6x-ota-.zip incremental_ota_update.zip
Signing an existing update package
It can happen that you need to apply an update which hasn’t been signed for your platform. As explained in our previous security post, a simple command allows you to re-sign an existing package:
~/myandroid$ java -jar out/host/linux-x86/framework/signapk.jar -w .x509.pem .pk8 package.zip package-signed.zip
Creating a package from scratch
At this point you might be wondering what’s inside an OTA package and if you can make one manually, this section will try to answer those questions.
What’s inside an OTA package?
The content of an update package will be as follows:
update/ ├── boot/ │ └── ... ├── file_contexts ├── META-INF │ ├── CERT.RSA │ ├── CERT.SF │ ├── com │ │ ├── android │ │ │ ├── metadata │ │ │ └── otacert │ │ └── google │ │ └── android │ │ ├── update-binary │ │ └── updater-script │ └── MANIFEST.MF ├── recovery/ │ └── ... └── system └── ...
Here is a descriptions of the different parts:
- file_contexts: sepolicy contexts used if selinux is enabled in recovery console
- system/ or boot/ or other files
- files to be used / copied / extracted during the update
How can I make my own?
Sometimes you might need to add only one file to an existing image and don’t want to go into the hassle of creating an incremental update for it. This section will describe how to make a minimalistic update which will add a ota_test.log file to /system/.
All you need to create a package are update-binary and updater-script. The former is an output of the build while the latter can be easily made by hand.
First prepare the script that will define you update, create an updater-script file with the following content:
ui_print("***********************************************"); ui_print(" Boundary testing update package"); ui_print("***********************************************"); ui_print("Mounting system partition..."); mount("ext4", "EMMC", "$BD5", "/system"); show_progress(0.200000, 10); ui_print("Extracting system partition..."); package_extract_dir("system", "/system"); show_progress(0.100000, 0); unmount("/system"); ui_print("Done!");
Then you can create the package:
~/myandroid$ mkdir -p update/META-INF/com/google/android/ ~/myandroid$ cp out/target/product/nitrogen6x/system/bin/updater update/META-INF/com/google/android/update-binary ~/myandroid$ cp updater-script update/META-INF/com/google/android/ ~/myandroid$ mkdir update/system ~/myandroid$ echo "the update worked!" > update/system/ota_test.log
This minimalistic package can now be signed:
~/myandroid$ cd update/ && zip -rq ../update_unsigned.zip * && cd - ~/myandroid$ java -jar out/host/linux-x86/framework/signapk.jar -w device/fsl/common/security/testkey.x509.pem device/fsl/common/security/testkey.pk8 update_unsigned.zip update_signed.zip
The package is ready to be applied! See next sections to know the different options.
Applying system updates over-the-air
This section relies on the FSLOta application provided by Freescale to fetch system updates from a distant server. This app has been included into our release with its default configuration that needs to be tweaked for your needs.
- The OTA application settings are located in the file /system/etc/ota.conf which contains:
- Update server address
- Update server HTTP port
- In order to have Boundary’s servers by default, please edit ota.conf as follows:
~/$ adb remount ~/$ adb shell root@nitrogen6x:/ # busybox vi /system/ota.conf server=boundarydevices.com.commondatastorage.googleapis.com port=80
- From there you can check if an OTA update is available under Settings > About tablet > Additional system updates
- Behind the scene, the application will first look for a build.prop file at:
- In the case of a nitrogen6x build using Boundary’s server setup:
- Then the app will check on the ro.build.date.utc on the remote build.prop against the actual one on the target.
- If the number is higher on the remote server, the app offers to download the update which be named:
- machine_type being the output of /sys/devices/soc0/machine
- Here is the update name for nitrogen6x on our servers:
Feel free to modify FSLOta application to fit your needs as well as creating your own update server (installing lighttpd for example). This application is clearly target full system update only, a more flexible version check might need to be implemented to manage incremental updates.
Also, as an example, you could have the application starting at every boot, to do so you would need to listen to the BOOT_COMPLETED Intent.
Another option is that have a periodic check triggered by scheduling repeating alarms.
Applying the update manually
Here are the steps to apply an update using adb sideload which requires the Android buttons board for Nitrogen6x, BD-SL and Nitrogen6_max. Unfortunately, this procedure won’t work on Nit6xlite boards because of the lack of buttons.
- Reboot the device into recovery console
~/$ adb shell 'reboot recovery'
- Press VOL_UP and SEARCH (should be labelled POWER) at the same time to get to the recovery console menu.
- From the menu, you can navigate with VOL_DN and VOL_UP, selection is done by pressing on SEARCH.
- Select “apply update from ADB“.
- On you host machine, you can now upload the update package.
~/$ adb sideload .zip
Another option is to execute what the OTA app would have done when receiving an update which requires no user interaction (no buttons needed):
~/$ adb push update.zip /cache/ ~/$ adb shell 'mkdir /cache/recovery/' ~/$ adb shell 'echo "--update_package=/cache/update.zip" > /cache/recovery/command' ~/$ adb reboot recovery
One last option would be to use Fastboot but it isn’t part of our standard u-boot yet.
As always please tell us how this works for you. Contact us if you have any trouble or ask a question by posting a reply below.