Introduction

As an advocate for online privacy, I’m quite particular about the operating system on my devices. So, when I purchased a used Pixel 5a from eBay, I installed GrapheneOS right away. However, what I soon discovered was that the phone wasn’t as usable as I had hoped – at least, not as a traditional telephone. Living in South Korea, I faced an additional challenge: Google Pixel phones aren’t officially released here, which meant that the carrier didn’t support this device. In fact, when I first inserted my SIM card, the phone couldn’t even detect the phone number. Further research revealed that I needed to patch my phone for VoLTE support to use it as a functional telephone in unsupported countries.

After searching online, I found a Korean forum that offered a patch for my specific device. What it does is basically copying files from an EFS partition of a device that functions normally to the Pixel device using EfsTools. But there was a catch: it required root access – something I hadn’t planned on doing since I wasn’t comfortable unlocking the bootloader or rooting my phone. (Having an unlocked bootloader is a massive security risk. Explained in #) I spent quite some time searching for an alternative solution, which ultimately led me to avbroot, a program that allowed me to re-lock the bootloader with a rooted system.

In this post, I’ll share my experiences in trying to get my phone working properly with a locked bootloader and an unrooted system, as well as the workaround.

Disclaimer

I am not an expert. In fact, I have minimal knowledge about how the Android operating system actually works. The primary purpose of this post is to serve as a reference for myself in the future.

Failed Attempts

As I have mentioned above, the main problem I face was to unroot and lock the bootloader after patching the phone. To have a locked bootloader with a rooted system in the first place, you need an .zip file of an OTA for the OS, and patch it with Magisk using avbroot. If done properly, you can flash the patched OTA, lock the bootloader, and you have a rooted system with a locked bootloader. But how do you unroot the system after patching the EFS partition?

I first took the most naive approach: simply uninstalling through Magisk app. Obviously, this didn’t work and the locked bootloader didn’t let me boot my phone. When you uninstall through Magisk app, what I believe is being done is that the app runs the uninstaller script for cleanup. This script flashes the stock boot image, so AVB will fail and the device will be unbootable.

Workaround

After some more stupid trials (too stupid to even write them down), I finally figured out the way. The steps I took were:

  1. Fresh install GrapheneOS following their installation guide.
  2. Obtain the latest OTA file from the official release of GrapheneOS.
  3. Patch the OTA with Magisk app following the Usage section of avbroot.
  4. Flash the patched OTA by following the Initial setup section of avbroot.
  5. Lock the bootloader.
  6. Boot the device, complete the setup, and enable eSIM if you intend to have it enabled too.
  7. Patch the EFS partition.
  8. Generate another patch with avbroot using the same OTA, but this time with --rootless option.
  9. Reboot into recovery. If the screen says No command with a dead android, hold down the power button and press the volume up key once while holding.
  10. Select the Apply update from ADB option.
  11. Sideload the patched rootless OTA with adb sideload /path/to/file.patched.

In fact, the unrooting process was already explained in the Warning and caveats section of avbroot. All my wasted time trying stupid things where I could have just read the […] manual…

Caveats

The biggest risk of this approach is that (again very well described in the documents) ‘any operation that causes an improperly-signed boot image to be flashed will result in the device being unbootable and unrecoverable without unlocking the bootloader again.’ This includes the default System Updater for GrapheneOS as the signature of the official OTA now does not match the one used in avbroot. Since the Graphene’s System Update is very eager to update the system for security reasons, to prevent the device from rendering itself unbootable I had to disable the app from settings so that it cannot attempt installing an unpatched image.

The developer of avbroot, chenxiaolong, actually has another project called Custota, which solves this very problem. I should take a look at this sooner or later; thanks so much to chenxiaolong for creating these useful projects!

Trivia

Somewhat unrelated, but I found that zygisk was not available in GrapheneOS using vanilla Magisk. The reason why this is broken (not only about zygisk but about overall Magisk root approach) is well explained in this issue. Without zygisk, it is basically almost impossible to avoid any root detection nowadays, as most root hiding solutions such as Zygisk Assistant require zygisk to run properly. To get zygisk working on Magisk, it only needs a little addition to native/src/core/zygisk/gen_jni_hooks.py and then run the code to generate the proper jni_hooks.hpp for GrapheneOS, as shown in this PR.

Since all I needed, in the end, was a one-time root access for ADB shell only, it might have been an option to build a userdebug GrapheneOS from source with ro.adb.secure=1 option, as stated in this issue. But I didn’t end up trying this approach because A) of my hardware limitations, I couldn’t manage to build Graphene from source, and B) further discussion hints that a build with this option cannot disable ADB.