DEV Community

Cover image for Running Android on my own hypervisor — what I learned in one month. 11 partitions, 1.7GB sparse images.
Eric-Octavian
Eric-Octavian

Posted on

Running Android on my own hypervisor — what I learned in one month. 11 partitions, 1.7GB sparse images.

I've been building IONA OS — a sovereign operating system written entirely in Rust — for the past 13 years.

This month, I decided to run Android on it.

Not as a virtual machine, not as an emulator, but as a real guest running on my own hypervisor, written from scratch, in Rust, in Ring -1 (EL2).

Here's how I did it.


Why run Android on a custom hypervisor?

IONA OS is a sovereign operating system. It has its own kernel, its own GUI, its own blockchain protocol, its own programming language (Flux), and its own AI running in Ring 0.

But a phone needs apps. And the biggest app ecosystem is Android.

Instead of building a compatibility layer from scratch, I built a hypervisor that can run a real Android system (AOSP) as a guest, completely isolated from IONA OS.

The goal:

  • IONA OS runs in EL1/EL0 (native, unchanged)
  • Android runs in a separate EL1/EL0 guest
  • EL2 (the hypervisor) manages the isolation
  • The user sees a unified UI, but the two systems are completely separate

This gives me:

  • Suveranitate totală — IONA OS remains untouched
  • Compatibilitate completă — all Android apps work
  • Securitate maximă — a compromised Android guest cannot affect IONA

Week 1: The real AOSP diagnostic

Most people would search Stack Overflow. I downloaded the actual AOSP source code from android.googlesource.com and read it.

The error message wasn't about a missing file. It was about a parsing failure.

Problem 1: androidboot.slot_suffix missing

The kernel command line was missing androidboot.slot_suffix. The fstab entries had slotselect, which requires this property.

Fix: Add androidboot.slot_suffix=_a to the kernel command line.

Problem 2: GPT composite disk

super.img was attached as a raw disk without a partition table. Android expects named partitions: boot_a, metadata, super, vbmeta_a, etc.

Fix: Built a real GPT disk with sgdisk:

sgdisk -o /dev/sdb
sgdisk -n 1:2048:+64M -t 1:EF00 /dev/sdb # boot_a
sgdisk -n 2:... -t 2:8300 /dev/sdb # metadata

Then wrote each image with dd at the correct sector offset.

Problem 3: /dev/block/by-name/* symlinks missing

realpath /dev/block/by-name/super failed.

Fix: Read devices.cpp in AOSP. The symlink is only created if androidboot.boot_devices is set correctly. For ARM64 under QEMU, the correct value is 4010000000.pcie, not the PCI address.

Problem 4: Sparse images

super.img and userdata.img were in Android sparse format (magic: 3aff26ed). liblp reported "invalid geometry magic signature".

Fix: Converted with simg2img:

simg2img super.img super.raw
simg2img userdata.img userdata.raw

1.7GB sparse → 8GiB raw each.

Problem 5: AVB (Android Verified Boot)

libfs_avb: vbmeta digest error isn't allowed — AVB was blocking boot.

Fix: Read fs_mgr/libfs_avb/fs_avb.cpp and util.cpp. The standard development property is:

androidboot.verifiedbootstate=orange

This is used by every -userdebug build, not a hack.

Problem 6: APEX module ambiguities

apexd-bootstrap aborted repeatedly, once for each module with two variants installed (gatekeeper, graphics.composer, keymint, camera.provider.hal).

Fix: Read apexd error messages. Each pointed to the exact property:

ro.boot.vendor.apex.gatekeeper
ro.boot.vendor.apex.graphics.composer

Set them correctly.

Problem 7: /data mounted read-only

The fstab required ext4 for /data, but userdata.img was actually formatted as F2FS (verified with file).

Fix: Changed androidboot.hardware=cf.f2fs.hctr2 — the correct fstab variant for F2FS.

Result: __mount(..., type=f2fs)=0: Success.


Week 2: The vsock investigation

SurfaceFlinger and several vendor HALs (light, uwb, ril-daemon, bt_hci) were stuck in a restart loop.

The real cause: reading the Rust source of the lights HAL (lights.rs).

An .unwrap() on creating an AF_VSOCK socket was failing. Without virtio-vsock transport, it fails guaranteed. Real Cuttlefish runs companion processes on the host (casimir, netsimd, wmediumd) for these HALs.

We don't have that. The fix would have been:

We skipped it. The HALs are not critical for a booting Android system.


Week 3: The real gap in the hypervisor

You asked: "repară breșa din kernel".

The dispatch loop in hypervisor_launch_linux_image() didn't know what to do with a real hvc from a guest. Any trap other than a stage-2 fault would "stop and report".

The fix: A new psci module in hypervisor.rs.

Real PSCI emulation:

  • PSCI_VERSION — returns 0x10000 (version 1.0)
  • SYSTEM_OFF — powers off the guest
  • SYSTEM_RESET / RESET2 — resets the guest
  • Everything else — honest NOT_SUPPORTED

All constants verified from real Linux kernel headers (include/uapi/linux/psci.h), not from memory.

Verified organically: The real Android kernel queried PSCI_VERSION through IONA's own EL2 (not QEMU firmware) and received the correct response.

Verified synthetically: New dedicated test: guest_psci_reset_test.

Regression caught before it happened: Our own test hvc calls (hvc #22, #98, etc.) used imm≠0, but real PSCI uses imm=0. Without this distinction, the synthetic boot test would have silently broken.

Verified on both paths (normal EL1 + virtualized EL2): ECDSA/ECDSA-KAT all PASS, no regression.


The final test

I deliberately provoked a known error (removed slot_suffix again) to trigger a real, controlled reboot:

  1. Android crashes: InitFatalReboot: signal 6reboot: Restarting system with command 'bootloader'.
  2. IONA genuinely intercepts that SYSTEM_RESET through its own EL2: PSCI call fid=0x84000009 -> guest power request.
  3. hypervisor_launch_linux_image() returns (for the first time ever with a real guest attached) — the loop no longer blocks indefinitely.
  4. Guest #1 (IONA itself) boots — for the first time in any test run with a real external guest attached: launching real guest: IONA kernel boot continuation at el1_native.

What this means

  • IONA OS can run Android as a guest, isolated in its own EL1/EL0 space.
  • The hypervisor can intercept, emulate, and handle real requests from a real Linux kernel (PSCI, stage-2 faults, etc.).
  • When Android crashes and reboots, IONA survives — it intercepts the reboot, tears down the guest, and resumes its own boot.
  • The isolation works. Android cannot affect IONA OS. The kernel is completely unchanged.

What's next

This is not a finished product yet. But the foundation is solid:

  • IONA OS runs the hypervisor (EL2) — unchanged
  • IONA OS runs the native kernel (EL1) — unchanged
  • Android runs as a guest (EL1) — isolated
  • The hypervisor manages the transition

Next steps:

  • GPU acceleration for the Android guest
  • Audio and input routing
  • Integration into the IONA OS UI (Android apps appearing in the launcher)

The code

You can find the full source code (including the hypervisor, the PSCI emulation, and the AOSP integration) in the IONA OS repository:

github.com/Ionablokchain/Iona-OS

The project is not yet production‑ready — but the foundation is solid.


Final thoughts

Running Android on my own hypervisor was one of the hardest things I've done.

But it's also proof that one person can build sovereign infrastructure — a hypervisor, a kernel, a blockchain, a language, an AI — all from scratch, all working together.

Website: iona.zone

GitHub: github.com/Ionablokchain


13 years of research. Every line written from scratch. And it works.

Top comments (0)