Lilac: The Device Policy Story

An epic writeup of how I created lilac, the ChromeOS device policy editor.

Posted on April 4, 2023

Some background

If you aren't coming from the lilac github repo, you'll definitely want to read through this section. Check the repo out after reading this post too!

Lilac is a device policy editor for ChromeOS that exploits a flag called --disable-policy-key-verification in Chrome. It basically gobbles up a device policy blob, changes the policies, and spits it out with a brand new signature and changed values, using the boringssl library along the way. Most of the signature code was taken from the Chrome equivalents, just because at that point I was thinking that this wasn't going to work out and to maximize the chances of it working, I took the signature code directly from Chrome.

How to actually compile it

For all the people out there who tried (and failed) at compiling lilac, here's a less cryptic guide:

  • Install protobuf
    • yay -S protobuf for Arch and Arch-based distro users like me
    • All other distros: You have to figure it out, there have been issues with compiling on other distros for some reason
  • Compile boringssl (included in the repo)
    • cd boringssl
    • mkdir build; cd build
    • cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/lilac/boringssl/install -DBUILD_SHARED_LIBS=on
    • make
    • make install
  • Compile the protobuf files
    • protoc proto/* -Iproto --cpp_out=. in the lilac dir
  • Run make and hope for the best

To actually run it, you may need to export LD_LIBRARY_PATH="boringssl/install/lib" if you get undefined symbol errors.

Discovering the flag

One night a few months ago I was trying to see if I could set device policies with Pollen. That obviously failed, but I was looking at Code Search too and I saw a list of all the policy sources. One of those interested me the most: POLICY_SOURCE_COMMAND_LINE. While jumping around source files trying to figure out how to set a policy with this source, I saw the golden flag: --disable-policy-key-verification. I moved on at first, trying to see if I could set policies via the command line. Unfortunately setting policies via command line was only possible on Android, but I decided to mess around with a policy editor Rory McNamara made for an old root escalation to see if I could get something to work.

Wait, I see the Guest Mode button!

After looking through the protobuf bindings some more, I was able to (kind of) morph the old Rory policyeditor into one that supposedly sets the guest mode policy (see in the source tree here). I didnt know it at the time, but this broken code basically erased all policies from the policy blob. I continued on by overwriting the policy blobs in /var/lib/devicesettings with the patched one, intercepting /opt/google/chrome/chrome to add --disable-policy-key-verification, and restarting. As Chrome loaded, there was a short period of time where I couldn't move the mouse or input on the keyboard, but could see the guest mode button sitting there. And then, of course, I got kicked to OOBE because the policy signature verification failed (according to logs). At first I was puzzled, as --disable-policy-key-verification was supposed to fix that.

How the policy blob signature system works

But after diving back into the Chromium code (the Mercury Workshop members sometimes joke about how much I use Code Search), it actually disables the verification of the signature of the signature of the policy blob. This may sound weird at first, but I think this is actually a decent system. The policy blob gets sent with a signature to make sure it wasn't tampered with, but the policy blobs must come from Google so the signature of the policy blob is signed by Google and sent along with the policy blob. At this point, I didn't feel like learning how to create my own signature with a different library, so I looked in the Chromium code again, this time in where the --disable-policy-key-verification flag was meant to be used: tests. I dived through the code until I got to the actual signing implementation. Then I condensed it all down into one function (see in lilac source).

Still doesn't work... Wait a minute.

I then once again replaced the policy files and restarted, and got kicked back to OOBE again. Back to Code Search I went and this time looked at how the policies are loaded. I soon realized my mistake: I forgot to export owner.key to overwrite the owner.key stored along with the policy files. After a quick fix (it's fitting that the commit is named "oops"), I was up and running with a fancy new login screen background and guest mode enabled. But of course, anything in C doesn't come without segfaults. Oh, and I found out that my code was broken a while later.

A ChromeOS VM whose policy has been patched with lilac A ChromeOS VM whose policy has been patched with lilac


  • lilac: r58playz
  • moral support to r58playz while they slowly went insane while reading chromiumOS code: all of Mercury Workshop and thenamedhuman from TN