DEV Community

Ai2th
Ai2th

Posted on

Pockr | Part 2 — Executing Binaries on Android

Executing Binaries on Android — The SELinux Problem

Part 2 of 6 — building Pockr, a single APK that runs Docker on non-rooted Android.
← Part 1: The Idea and Architecture


The First Wall: Permission Denied

The most obvious approach is to bundle the QEMU binary inside the APK and extract it to app storage on first launch, then execute it with ProcessBuilder.

On Android 10+, this silently fails:

Cannot run program ".../files/qemu/qemu-system-aarch64":
error=13, Permission denied
Enter fullscreen mode Exit fullscreen mode

This isn't a file permission issue. chmod +x won't fix it.


Why: SELinux W^X Policy

Android 10 enforces a W^X (Write XOR Execute) policy via SELinux. Any file in getFilesDir() — the app's private data directory — is labelled app_data_file. That label does not allow execve().

Directory SELinux Label Executable?
getFilesDir() app_data_file
getCacheDir() app_data_file
nativeLibraryDir exec_type

The native library directory is the exception — it's specifically labelled to allow execution. This is where Android puts .so files from your APK's jniLibs/.


The Fix: Ship QEMU as a .so File

Android's PackageManager extracts files from jniLibs/<abi>/ to nativeLibraryDir during installation. Those files must end in .so, but they don't have to be shared libraries.

We renamed the QEMU binary:

qemu-system-aarch64  →  libqemu.so
qemu-img             →  libqemu_img.so
Enter fullscreen mode Exit fullscreen mode

Put them in android/app/src/main/jniLibs/arm64-v8a/ and Android extracts them to:

/data/app/~~.../com.example.dockerapp-.../lib/arm64/libqemu.so
Enter fullscreen mode Exit fullscreen mode

This path has exec_type. execve() works.


The Second Problem: Wrong ELF Interpreter

Our first attempt used a self-built QEMU binary compiled on Debian. Android installed it to nativeLibraryDir but silently refused to extract it.

The reason: Android's PackageManager only extracts .so files with the Android linker as interpreter:

# Debian-built QEMU (rejected)
readelf -l qemu | grep interpreter
  [Requesting program interpreter: /lib/ld-linux-aarch64.so.1]  ← glibc

# Termux-built QEMU (accepted)
readelf -l qemu | grep interpreter
  [Requesting program interpreter: /system/bin/linker64]  ← Bionic
Enter fullscreen mode Exit fullscreen mode

Fix: Switch to pre-built QEMU from Termux packages. Termux builds everything against Android's Bionic libc and uses /system/bin/linker64.


One More Catch: useLegacyPackaging

AGP 3.6+ compresses native libraries inside the APK by default (to reduce download size). Compressed .so files are NOT extracted to disk — they're loaded directly from the APK zip.

QEMU is not a shared library. We need it on disk to execute it. Fix in build.gradle:

android {
    packagingOptions {
        jniLibs {
            useLegacyPackaging true  // force extraction to disk
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

Problem Root Cause Fix
Permission denied on exec SELinux W^X on filesDir Ship as .so in jniLibs/
.so not extracted APK compression useLegacyPackaging true
Wrong ELF interpreter glibc binary Use Termux pre-built QEMU

Next: Part 3 — Bundling 50 Termux Libraries Without Breaking the ELF Linker

GitHub: github.com/AI2TH/Pockr



Pockr Series — Docker in Your Pocket

Pockr = Pocket + Docker. A single Android APK that runs real Docker containers in your pocket — no root, no Termux, no PC required.

# Post Topic
📖 Intro What is Pockr? Start here
1 Part 1 The Idea and Architecture
2 Part 2 Executing Binaries — The SELinux Problem
3 Part 3 Bundling 50 Native Libraries
4 Part 4 Docker Without Kernel Modules
5 Part 5 Debugging the VM Restart Loop
6 Part 6 Test Results and What's Next

GitHub: github.com/AI2TH/Pockr

Systems Engineer: Kalvin Nathan
skalvinnathan@gmail.com · LinkedIn

Top comments (0)