Thursday, August 3, 2023

The Weird and Wonderful World of Proot and Inits

If you want to run Linux on your unrooted Android phone via Termux, there's at least two main methods of doing so: full virtualization using Qemu, or using containers with proot-distro.

What's Proot-distro?

Proot-distro is a wrapper script around the proot utility, which is a user space implementation of chroot(or in other words, a non-root container engine).

Proot's implementation of chroot relies on using ptrace to hijack system calls and fake being root within the proot session. However, while proot can isolate the host file system from the guest, it cannot isolate the process view like other container engines like Docker and podman can.

Limitations of Proot

This means that while you can get away with merely running a shell within the proot session, you can't run any traditional init system that relies on being PID-1 and manages processes on its own. I've tried, and everything from kitchen sink systemd to sysvinit to even Rich Felker's minimal init won't properly run without modification.

Making a !PID-1

So, with these limitations of proot in mind, I decided to try to either modify an existing init or write one from scratch. My first attempt was commenting out the PID check in Felker's init and compiling the result. It worked, but job control got disabled and the proot session hung on exit. Progress, but still not usable. So I instead attempted to write a proof of concept in shell script, since proot simply launches a shell by default. It completely worked. Next I tried further modifying Felker's init by disabling all functionality except passing off execution to /etc/rc. The result was literally a one-line main() function. It worked! So now I have two working starting points to build up an init system with a "proot mode" of operation. And since it seems that there's literally no other projects with this specific aim(podman doesn't count because that's a container engine explicitly designed to run systemd, rather than an init designed to run in proot) that I could find, it's likely I'm a pioneer in this specific area.

...But why?

It's a fair question. What's the value of running an init in a prooted distribution? Isn't it enough that proot handles job control and by default automatically launches a shell?

This Red Hat Developer article advocating for podman actually explains the rationale for running an init inside a container better than I could. But as my attempts to use traditional init systems demonstrate, it's currently not as simple as "apt install systemd", and then restart with "proot-distro login debian --isolated -- systemd". Proot is a different beast than Docker, podman, or systemd-nspawn, and it requires init systems designed to adjust to it or written from the ground up for it.

So right now, I'm going to attempt to rewrite Felker's init where instead of refusing to run if it's not PID-1, operate in "proot mode" and put it up on my GitHub if it works.

Update: Here it is. I stripped Felker's init down to a one-line main function, as a minimal working starting point, and I plan to add a "hybrid" version that runs as a normal init if it's PID-1, else it runs in proot mode. And after that, I'll implement support for running groot as a user service manager.

The Weird and Wonderful World of Proot and Inits

If you want to run Linux on your unrooted Android phone via Termux , there's at least two main methods of doing so: full virtualization ...