* Run x86_64 Flatpak apps through FEX * proot: run an execveat with an absolute path like execve * Find x86_64 Flatpak installs and resolve sandbox paths like the sandbox does
12 KiB
proot patches carried by the app
Built by tools/proot/build.sh with the NDK on top of termux/proot 4dba3afb (Termux's
5.1.107-70 package, the proot the Linux runtime ships at opt/android-host/proot; every
file.c:line site in that binary matches this commit). talloc is linked in statically from
Samba's release tarball, so the apk carries one self-contained libproot.so plus
libproot-loader.so, and LinuxRuntime prefers them over the runtime's copy.
Ported from WinNative (feature/proot-enhancements, e7af0f24):
0001-tracee-lookup-by-pid.patch- tracees are hashed by pid, so each ptrace stop finds its tracee without walking one list entry per thread, and terminated tracees are swept only after a termination.0002-canon-resolve-parent-at-once.patch- a clean absolute guest path at least three directories deep under the rootfs binding alone has its parent opened once withO_PATHand taken as canonical when/proc/self/fdnames the path proot would build, replacing anlstatper component. The final component of a call that does not follow it is no longerlstated. Extensions still see the parent's host path, and the fast path stays off while the f2fs workaround is active.0003-clone3-flags-read-guard.patch- a thread created withclone3keeps the flags it inherited whenstruct clone_argscannot be read, instead of being tracked as a fork.
Ported from WinNative (main, 53836ca9, "Fix/performance and vac"):
0004-seccomp-filter-by-argument.patch- the seccomp filter tracesprctlonly forPR_SET_DUMPABLE,setrlimitonly forRLIMIT_STACKandprlimit64only when it sets a newRLIMIT_STACK, the only cases proot acts on; every other call runs without a stop.unameis traced by the core only on x86_64, the one arch it rewrites; kompat still adds it for--kernel-release.0005-seccomp-skip-unneeded-sysexit.patch- a seccomp stop fetches the registers once and looks the filter flags up by syscall number, instead of aPTRACE_GETEVENTMSGand a second register fetch. The flags table is built from proot's list merged with the enabled extensions' (kompat and fake_id0 here), so it is exactly what the filter reports.wait4/waitpidleft to the kernel andaccept/accept4without a sockaddr skip their exit stop, unless an extension replaced the syscall (kompat turningaccept4intoaccept).0006-clone3-exit-signal.patch-clone3flags and exit signal are read as the 64-bit fields ofstruct clone_args, so aclone3fork is told apart from a thread when proot decides how the child is traced.0007-proc-self-thread-group.patch- tracees track their thread group,/proc/selfnames it instead of the calling thread, and/proc/thread-selfresolves to/proc/<tgid>/task/<tid>.0008-fchmodat2-openat2.patch-fchmodat2paths are translated (honouringAT_SYMLINK_NOFOLLOW), and syscall numbers past the end of a table are rejected instead of read. Itsopenat2answer is replaced by0019.0009-tracee-relatives-sweep.patch- tracees count their children, so a terminating thread with no children or ptracees no longer walks every tracee; the per-stop memory collector is emptied instead of freed and reallocated.
Added for Flatpak:
-
0010-new-mount-api-enosys.patch-open_tree,move_mount,fspickandmount_setattranswerENOSYS. They take paths proot never translated, so libglnx'sopen_tree(AT_FDCWD, "/")handed Flatpak a descriptor for the host's root and it tried to create directories there (mkdirat(root): Operation not permitted); unsupported, libglnx falls back toopenat. -
0012-android-hardlink-denial.patch- Android's SELinux policy denies apps hard links, solinkatfails withEACCESand Flatpak could not create its repo (Creating repo: linkat: Permission denied).O_TMPFILEanswersEOPNOTSUPP, so libglnx writes a named temporary file and renames it, and a denied link answersEPERM, on which ostree's checkout copies instead. -
0017-android-execstack-denial.patch- the same policy denies apps executable stacks, so themprotect(PROT_READ | PROT_WRITE | PROT_EXEC | PROT_GROWSDOWN)with whichld.somakes the main stack executable for an object whosePT_GNU_STACKasks for it fails withEACCES, and the program stops beforemain("cannot enable executable stack as shared object requires: Permission denied",libQt5Gui.so.5in ONLYOFFICE's Flatpak). The filter tracesmprotectonly for that exact request, and a denied one answers success: the stack stays non-executable, as Android keeps it anyway, and objects that only carry the flag load. Added by DroidDeck: -
0011-kompat-utsname-only.patch---kernel-release(the guest'sDroidDeckhostname) loads kompat, whose filter trapsfutex,fcntl,epoll_pwait,pselect6,pipe2,eventfd2,socketand more, and which stripsAT_SYSINFO_EHDRon everyexecve, so glibc runs without the vDSO. When the virtual release is not older than the real kernel and the hwcap is left alone, every one of those handlers is a no-op: kompat now traces onlyuname,sethostnameandsetdomainnameand leaves the auxv as the kernel wrote it. On an SD 8 Gen 2 guest this took a futex ping-pong from 467 to 97 us,epoll_pwaitfrom 60 to 0.8 us andfcntlfrom 40-107 to 0.4 us, andclock_gettimeback to the vDSO. -
0012-fake_id0-identity-only.patch--i uid:gid(for Xwayland's setgid/setuid before it runs xkbcomp) loads fake_id0, whose filter traps everyfstat/newfstatat/stat(entry and exit), everysendmsg(all Wayland, X11, Chromium and PulseAudio traffic),socket,getsockopt, theget*idfamily and the chown/chmod family. When the ids given are the ones proot really has and are not 0, every one of those handlers is a no-op; fake_id0 now traces only theset*idfamily and the xattr permission fixups, and leaves set-user-ID bits alone onexecve(Android mounts the app's datanosuid, so the kernel would not honour them either), which keeps the ids fixed. On an x86_64 host build under the same options:fstat41.7 -> 1.3 us,sendmsg18.0 -> 2.5 us. -
0013-seccomp-ioctl-by-request-and-kernel-exit-stops.patch- everyioctlstopped on entry and exit, which is every GPU submit and wait. The filter now traces only the requests enter.c and exit.c act on (TCSETSF, the four termios2 requests,FICLONE) and allows the rest at once, and the ioctl block is emitted first (after WinNative 53836ca9, which traces only the termios2 ones). Thefaccessat2exit stop (termux 5ba8b95, for glibc's ENOSYS fallback on kernels before 5.8) and thestatxone (emulation on kernels before 4.11) are dropped on kernels 5.8 and newer; patch0015keeps it on 4.11–5.7 to return the mount ID added in 5.8. Host build:ioctl31.6 -> 0.5 us;stat/statx/faccessat2about 44 -> 28 us (one stop). -
0015-statx-mnt-id-fdinfo.patch- kernels before 5.8 can completestatxwithout the mount ID that libglnx requires while chasing an open descriptor. PRoot keeps the exit stop on those kernels and fills only a missing ordinarySTATX_MNT_IDforAT_EMPTY_PATHafter pinning the tracee descriptor, matching its device and inode to the successful statx result, and readingmnt_idfrom the pin's fdinfo. A later close/reuse of the tracee fd cannot redirect that read; a concurrent reuse for the same inode through a different bind mount is indistinguishable at the exit stop. It does not synthesizeSTATX_MNT_ID_UNIQUE. -
0016-robust-list-from-seccomp.patch- Android's app policy refusesset_robust_listandget_robust_list. proot still answers the first withENOSYSbut keeps the head each thread offered (cleared onexecve, none in a new task), and answers the second for the calling thread with it. SteamVR'svrclient.soasks for the head through libc'ssyscall(), whichlibblsession.so's robust.c never sees:libopenvr_api.soopens itRTLD_DEEPBIND. Steam aborted on "futex robust_list not initialized by pthreads" as soon as SteamVR was registered.
Prototype (inert unless PROOT_FASTPATH is set; see docs/development/proot-performance.md):
-
0014-fastpath-trampoline.patch- withPROOT_FASTPATH, everySECCOMP_RET_TRACEin the filter is preceded by a check of the caller's address, and a syscall made from the fast path's trampoline page (fastpath/fastpath.c,0xffff00000) runs without a stop: the tracee already translated it. The check comes after the syscall-number dispatch, so untraced syscalls stay constant-ALLOW and keep the kernel's seccomp action cache (checking first cost every syscall the full filter: +0.5 ms an exec).chdir/fchdir, still emulated, also move the kernel's cwd to the host directory, and the first tracee starts at-w's, so relative lookups inside the tracee resolve where proot would. SM8850 (adb shell):stat25.3 -> 0.6 us,open+close25.3 -> 0.9 us, ENOENT 22.9 -> 1.6 us, 8 threadsstating 89k -> 3.6M/s; equivalence suite (bench/equiv.py) byte-identical to proot. -
0019-openat2-emulation.patch-openat2withoutresolveflags runs as a translatedopenatwith the same flag and mode checks as the kernel. Anyresolveflag still answersENOSYS, because PRoot cannot confine symlinks or mounts, so callers keep their own confined fallback. -
0022-fake-root-statx-owners.patch- with0013dropping thestatxexit stop, fake root never rewrote the ownerstatxreturns, so files read as owned by the app's uid while the process was root. Rust'smetadataand GNUstatusestatx, so uutils'test -w(Ubuntu's/usr/bin/test) said root could not write to its own directories and apt's AppStream hook never built Discover's catalog. fake_id0 keeps that exit stop when it fakes ids; identity-only mode is unchanged. -
0023-reused-pid-new-child.patch- when a thread that is not its process's leader callsexecve, the kernel retires that thread's ID without reporting an exit, so PRoot keeps its entry. Once the IDs wrap (32768 on many Android kernels) a new child can get that ID, and PRoot aborted on the assertion that a new child has no state, ending the whole session. Chromium-based programs such as Steam's client do this. PRoot now retires the stale entry and sets the new child up from scratch. -
0024-execveat-absolute-path.patch-execveatwith an absolute path and neitherAT_EMPTY_PATHnorAT_SYMLINK_NOFOLLOWruns asexecve. The kernel ignores the directory descriptor for such a path, and PRoot refused the call unless the descriptor wasAT_FDCWD. FEX starts every child of an emulated program withexecveat(fd, "/proc/self/exe"), so x86 programs in a Flatpak sandbox, which does not carry the session's preloads, could not start other programs.
Old kernel fixes:
0015-lost-fork-events.patch- works around a problem reported on Xiaomi's msm 4.14 kernel: the kernel sometimes reports a new process or thread but gives its ID as zero. Without that ID, PRoot cannot finish setting it up, which can leave the session stuck. The patch searches/procfor the child. If exactly one matches, PRoot uses its existing setup code. If several threads match, it waits for the creating thread'sclonecall to return and uses the child ID returned by the kernel instead. The original clone flags are preserved. When the kernel supplies a valid ID in the notification, normal setup is unchanged. No timers or background polling are added. Completely missing notifications and ambiguous matches for separate processes are not recovered.
Development diagnostics:
0018-syscall-handler-profile.patch-PROOT_PROFILE=1enables per-syscall entry/exit handler counts, wall/CPU time, periodic cumulative JSON totals, and bounded slow-handler samples on stderr.profile.his copied into the source tree bybuild.sh. It adds no ptrace stops; handler timing excludes tracee kernel time and scheduling before handler entry. Seedocs/development/proot-performance.mdfor the capture and library-override workflow.