perf(proot): expand and cache syscall relays

This commit is contained in:
Kurt Himebauch committed 2026-10-28 07:17:53 -04:00
1 parent 785d1bf1da
commit c62b9b5d1b
9 files changed
+719 -81

No files matched your search

@@ -1,6 +1,7 @@
package com.droiddeck.launcher.runtime
import android.content.Context
import android.os.Build
import android.util.Log
import com.droiddeck.launcher.session.SessionPrefs
import java.io.File
@@ -37,9 +38,10 @@ object ProotFastPath {
fun hostEnv(key: String): Pair<String, String> = "PROOT_FASTPATH" to key
fun guestEnv(root: File, binds: List<String>, key: String): List<String> = listOf(
fun guestEnv(root: File, binds: List<String>, key: String, sdk: Int = Build.VERSION.SDK_INT): List<String> = listOf(
"PROOT_FP_ROOT=" + root.path,
"PROOT_FP_BINDS=" + binds.joinToString("|"),
"PROOT_FP_KEY=$key",
"PROOT_FP_ANDROID_API=$sdk",
)
}
@@ -10,6 +10,12 @@ import java.io.File
class ProotFastPathTest {
private val root = File("/data/data/com.droiddeck.launcher/files/linuxfs")
@Test fun guestEnvironmentCarriesTheHostAndroidApi() {
for (sdk in listOf(33, 35, 36)) {
assertTrue(ProotFastPath.guestEnv(root, emptyList(), "test", sdk).contains("PROOT_FP_ANDROID_API=$sdk"))
}
}
@Test fun aSessionsUsualBindsGetAKey() {
val binds = (1..87).map { "/host/$it:/guest/$it" }
assertNotNull(ProotFastPath.key(root, binds))
+58 -6
View File
@@ -287,12 +287,64 @@ The installed PRoot pack emitted no profiling start/counter records despite the
queued profiling setting; the profiler report had `hasCounters: false`. That is a
measurement gap, not evidence of zero remaining traps.
Further validation includes signal/cancellation/unwind behavior,
additional glibc builds and workloads, startup cost and Steam/CEF tree RSS; obtain
matching patch-0018 tracer measurements; and resolve the existing SD socket mismatch.
Additional syscall families, Android-blocked syscall emulation and build-id caching
remain follow-ups. In particular this implementation does not remove startup loader
traps before the preload constructor, nor patch raw calls in arbitrary guest ELFs.
### Expanded relay coverage
The subsequent revision keeps patching enabled by default and shares the existing
wrapper translators with 20 path syscall numbers: openat, newfstatat, statx,
readlinkat, faccessat, mkdirat, unlinkat, renameat, renameat2, symlinkat, fchmodat,
utimensat, truncate, statfs, getxattr, lgetxattr, listxattr, llistxattr,
inotify_add_watch and getcwd. This is an allowlist of safely recognized sites, not
a claim that every glibc implementation contains every site. Unsupported paths,
flags, instructions and inaccessible pointers still use the original PRoot route.
Input/output copies use process_vm_readv/writev rather than dereferencing arbitrary
guest pointers. The device matrix checks error priority, protected buffers and a
NUL-terminated path ending at a page boundary.
The app forwards its Android SDK as `PROOT_FP_ANDROID_API`. With a known SDK,
set_robust_list retains the head in TLS and returns ENOSYS, matching patch 0016;
self get_robust_list uses that head. Uncaptured heads and other TIDs fall back to
PRoot. This does not implement kernel robust-mutex owner-death recovery. faccessat2
returns ENOSYS for glibc's fallback. clone3 returns ENOSYS only before API 35;
it is never executed from the C relay, since successful clone3 would change the
child's stack. The [Android 14 allowlist](https://android.googlesource.com/platform/bionic/+/refs/heads/android14-release/libc/SECCOMP_ALLOWLIST_COMMON.TXT)
omits clone3 and the [Android 15 allowlist](https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/libc/SECCOMP_ALLOWLIST_COMMON.TXT)
includes it. Unknown platforms leave these sites untouched. Existing droiddeck-fsync
handling of futex_waitv is unchanged.
Build-ID tables in `/tmp/.droiddeck-svc-v1-<api>-<build-id>` cache relative site
offsets. Every hit revalidates executable-segment bounds, alignment, the current
SVC opcode and the recognized syscall number. Missing, malformed, truncated,
overly permissive or symlinked tables trigger a rescan; writes use a private
temporary file and atomic rename. Images without a readable GNU build-ID still
scan normally. `PROOT_FP_SVC_CACHE=0` disables caching for comparison. Changes to
the accepted instruction/syscall set must bump the cache version.
On the Thor's glibc 2.43 / Android 13, 49 sites patch across libc and the loader.
Warm cached discovery took 60–63 microseconds in two samples versus roughly
0.6–0.7 milliseconds for the earlier uncached scan; patch publication took about
0.28 milliseconds. These are constructor phases, not whole Steam startup times.
Five alternating expanded-library pairs measured median internal stat at 39.5→4.9
microseconds and open/close at 41.2→6.4 microseconds, with 10,000→0 voluntary
context switches over both loops. The earlier SD/download tables above describe
the initial 14-site revision and must not be attributed to this expanded build.
The expanded ABI matrix passes raw return values, errno, all x1–x30 and q0–q31,
NZCV/FPCR/FPSR, four concurrent threads with distinct robust heads, fork, 100
signals on an alternate stack, deferred cancellation and 32 thread create/join
cycles. Constructor patching still refuses a process with multiple live threads.
Host tests exercise corrupted cache entries, invalid offsets/opcodes, mismatched
API/version, unsafe file modes, symlinks and truncation. The Android unit test
checks API forwarding. The existing preload/agent selection passed 79 tests with
two skips before the cache regression test was added.
Internal fts/realpath/NSS/dlopen and the normalized first equivalence suite still
match PRoot at both cache TTLs. The write suite still matches wrapper-only output
and reproduces the same existing SD Unix-socket mismatch described above.
No in-process RET_TRAP filter is installed, so the proposed SIGSYS mask/handler
machinery is not applicable. Startup loader traps before the constructor and
raw calls in arbitrary guest ELFs remain outside the patch scope. Additional
glibc builds, Android versions, unwind behavior, Steam/CEF tree RSS and matching
patch-0018 tracer measurements remain coverage gaps.
Build the tools with the same ARM64 glibc toolchain as the fast path:
+184 -2
View File
@@ -1,6 +1,8 @@
#include "../fastpath/fastpath.c"
#include <assert.h>
#include <sys/wait.h>
#include <linux/futex.h>
#include <signal.h>
#if defined(__aarch64__)
struct snapshot { long x[32]; unsigned char q[32][16]; unsigned long nzcv, fpcr, fpsr; };
@@ -37,14 +39,13 @@ __asm__(
"add sp, sp, #1072\nret\n.size svc_register_probe, .-svc_register_probe\n"
);
static void check(const long *args, long want) {
static long probe(const long *args) {
struct snapshot out = {0};
unsigned long fpcr, fpsr;
__asm__ volatile("mrs %0, fpcr\nmrs %1, fpsr" : "=r"(fpcr), "=r"(fpsr));
errno = 123;
svc_register_probe(&out, args);
assert(errno == 123);
assert(out.x[0] == want);
for (int i = 1; i <= 30; i++) {
long expected = i <= 5 ? args[i] : i == 8 ? args[6] : i;
if (out.x[i] != expected) { fprintf(stderr, "x%d: %lx != %lx\n", i, out.x[i], expected); abort(); }
@@ -53,11 +54,127 @@ static void check(const long *args, long want) {
for (int j = 0; j < 16; j++) assert(out.q[i][j] == 0x5a);
assert(out.nzcv == 0xa0000000UL);
assert(out.fpcr == fpcr && out.fpsr == fpsr);
return out.x[0];
}
static void check(const long *args, long want) {
long got = probe(args);
if (got != want) { fprintf(stderr, "syscall %ld: got %ld, expected %ld\n", args[6], got, want); abort(); }
}
static long raw(long nr, long a, long b, long c, long d, long e, long f) {
long args[] = {a, b, c, d, e, f, nr};
return probe(args);
}
static void fault(long nr, long a, long b, long c, long d, long e, long f) {
long args[] = {a, b, c, d, e, f, 0, 0, nr};
long want = svc_original(args);
long call[] = {a, b, c, d, e, f, nr};
check(call, want);
}
static void path_checks(void) {
char work[] = "/tmp/proot-expanded-check-XXXXXX";
assert(mkdtemp(work));
int cwd = open(".", O_RDONLY | O_DIRECTORY);
assert(cwd >= 0 && chdir(work) == 0);
assert(raw(SYS_mkdirat, AT_FDCWD, (long)"dir", 0700, 0, 0, 0) == 0);
long fd = raw(SYS_openat, AT_FDCWD, (long)"dir/file", O_CREAT | O_RDWR | O_EXCL, 0600, 0, 0);
assert(fd >= 0 && write((int)fd, "hello", 5) == 5);
struct stat st;
struct statx sx;
assert(raw(SYS_newfstatat, AT_FDCWD, (long)"dir/file", (long)&st, 0, 0, 0) == 0 && st.st_size == 5);
assert(raw(SYS_statx, AT_FDCWD, (long)"dir/file", 0, STATX_BASIC_STATS, (long)&sx, 0) == 0 && sx.stx_size == 5);
assert(raw(SYS_faccessat, AT_FDCWD, (long)"dir/file", R_OK, 0, 0, 0) == 0);
assert(raw(SYS_symlinkat, (long)"file", AT_FDCWD, (long)"dir/link", 0, 0, 0) == 0);
char text[PATH_MAX];
memset(text, '#', sizeof text);
assert(raw(SYS_readlinkat, AT_FDCWD, (long)"dir/link", (long)text, sizeof text, 0, 0) == 4);
assert(!memcmp(text, "file#", 5));
assert(raw(SYS_fchmodat, AT_FDCWD, (long)"dir/file", 0640, 0, 0, 0) == 0);
struct timespec times[] = {{1000, 0}, {2000, 0}};
assert(raw(SYS_truncate, (long)"dir/file", 11, 0, 0, 0, 0) == 0);
assert(raw(SYS_utimensat, AT_FDCWD, (long)"dir/file", (long)times, 0, 0, 0) == 0);
assert(raw(SYS_newfstatat, AT_FDCWD, (long)"dir/file", (long)&st, 0, 0, 0) == 0);
assert(st.st_size == 11 && (st.st_mode & 0777) == 0640 && st.st_mtime == 2000);
assert(raw(SYS_renameat, AT_FDCWD, (long)"dir/file", AT_FDCWD, (long)"dir/moved", 0, 0) == 0);
assert(raw(SYS_renameat2, AT_FDCWD, (long)"dir/moved", AT_FDCWD, (long)"dir/file", RENAME_NOREPLACE, 0) == 0);
struct statfs fs;
assert(raw(SYS_statfs, (long)"dir/file", (long)&fs, 0, 0, 0, 0) == 0);
long x = raw(SYS_getxattr, (long)"dir/file", (long)"user.proot-missing", (long)text, sizeof text, 0, 0);
assert(x == -ENODATA || x == -EOPNOTSUPP);
x = raw(SYS_lgetxattr, (long)"dir/link", (long)"user.proot-missing", (long)text, sizeof text, 0, 0);
assert(x == -ENODATA || x == -EOPNOTSUPP);
assert(raw(SYS_listxattr, (long)"dir/file", (long)text, sizeof text, 0, 0, 0) >= 0);
assert(raw(SYS_llistxattr, (long)"dir/link", (long)text, sizeof text, 0, 0, 0) >= 0);
int in = inotify_init1(IN_CLOEXEC);
assert(in >= 0 && raw(SYS_inotify_add_watch, in, (long)"dir", IN_CREATE, 0, 0, 0) >= 0);
close(in);
long n = raw(SYS_getcwd, (long)text, sizeof text, 0, 0, 0, 0);
assert(n == (long)strlen(work) + 1 && !strcmp(text, work));
size_t page = sysconf(_SC_PAGESIZE);
char *guard = mmap(NULL, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(guard != MAP_FAILED && mprotect(guard + page, page, PROT_NONE) == 0);
strcpy(guard + page - 2, ".");
assert(raw(SYS_newfstatat, AT_FDCWD, (long)(guard + page - 2), (long)&st, 0, 0, 0) == 0);
long bad = (long)(guard + page);
fault(SYS_openat, AT_FDCWD, bad, O_RDONLY, 0, 0, 0);
fault(SYS_newfstatat, AT_FDCWD, (long)"dir/file", bad, 0, 0, 0);
fault(SYS_statx, AT_FDCWD, (long)"dir/file", 0, STATX_BASIC_STATS, bad, 0);
fault(SYS_readlinkat, AT_FDCWD, (long)"dir/link", bad, 16, 0, 0);
fault(SYS_getcwd, bad, PATH_MAX, 0, 0, 0, 0);
fault(SYS_utimensat, AT_FDCWD, (long)"missing", bad, 0, 0, 0);
fault(SYS_inotify_add_watch, -1, (long)"missing", IN_CREATE, 0, 0, 0);
fault(SYS_renameat2, AT_FDCWD, (long)"dir/file", AT_FDCWD, (long)"dir/nope", 0x80000000, 0);
assert(munmap(guard, page * 2) == 0);
close((int)fd);
assert(raw(SYS_unlinkat, AT_FDCWD, (long)"dir/link", 0, 0, 0, 0) == 0);
assert(raw(SYS_unlinkat, AT_FDCWD, (long)"dir/file", 0, 0, 0, 0) == 0);
assert(raw(SYS_unlinkat, AT_FDCWD, (long)"dir", AT_REMOVEDIR, 0, 0, 0) == 0);
assert(fchdir(cwd) == 0 && close(cwd) == 0 && rmdir(work) == 0);
puts("PASS: path families, dirfds, flags, symlinks, protected pointers, page boundary");
}
static void robust_checks(void) {
if (!svc_android_api) return;
long original = 0;
size_t size = 0;
assert(raw(SYS_get_robust_list, 0, (long)&original, (long)&size, 0, 0, 0) == 0);
struct robust_list_head head = {0};
assert(raw(SYS_set_robust_list, (long)&head, sizeof head, 0, 0, 0, 0) == -ENOSYS);
long got = 0;
assert(raw(SYS_get_robust_list, gettid(), (long)&got, (long)&size, 0, 0, 0) == 0);
assert(got == (long)&head && size == sizeof head);
assert(raw(SYS_get_robust_list, 0, 1, (long)&size, 0, 0, 0) == -EFAULT);
assert(raw(SYS_set_robust_list, original, sizeof head, 0, 0, 0, 0) == -ENOSYS);
assert(raw(SYS_faccessat2, AT_FDCWD, (long)"/usr/lib/libc.so.6", R_OK, 0, 0, 0) == -ENOSYS);
if (svc_android_api < 35) assert(raw(SYS_clone3, 0, 0, 0, 0, 0, 0) == -ENOSYS);
int api = svc_android_api;
svc_android_api = 35;
assert(!svc_supported(SYS_clone3));
svc_android_api = 0;
assert(!svc_supported(SYS_clone3) && !svc_supported(SYS_set_robust_list));
svc_android_api = api;
puts("PASS: robust-list TLS, errors, faccessat2, clone3 platform guard");
}
static void *thread_check(void *unused) {
(void)unused;
struct stat st;
if (svc_android_api) {
long head;
size_t size;
assert(raw(SYS_get_robust_list, 0, (long)&head, (long)&size, 0, 0, 0) == 0 && head && size == 3 * sizeof(long));
struct robust_list_head private = {0};
assert(raw(SYS_set_robust_list, (long)&private, sizeof private, 0, 0, 0, 0) == -ENOSYS);
for (int i = 0; i < 10; i++) {
long got;
sched_yield();
assert(raw(SYS_get_robust_list, 0, (long)&got, (long)&size, 0, 0, 0) == 0 && got == (long)&private);
}
assert(raw(SYS_set_robust_list, head, sizeof private, 0, 0, 0, 0) == -ENOSYS);
}
const long args[] = {AT_FDCWD, (long)"/usr/lib/libc.so.6", (long)&st, 0, 0, 0, SYS_newfstatat};
for (int i = 0; i < 100; i++) check(args, 0);
return NULL;
@@ -71,8 +188,70 @@ static void *thread_probe(void *unused) {
return NULL;
}
static volatile sig_atomic_t signals_seen;
static void on_signal(int signal) {
(void)signal;
int saved = errno;
struct stat st;
assert(raw(SYS_newfstatat, AT_FDCWD, (long)"/usr/lib/libc.so.6", (long)&st, 0, 0, 0) == 0);
signals_seen++;
errno = saved;
}
struct signal_sender { pthread_t target; int done; };
static void *send_signals(void *arg) {
struct signal_sender *sender = arg;
const struct timespec delay = {0, 100000};
for (int i = 0; i < 100; i++) {
assert(pthread_kill(sender->target, SIGUSR1) == 0);
nanosleep(&delay, NULL);
}
__atomic_store_n(&sender->done, 1, __ATOMIC_RELEASE);
return NULL;
}
static void *cancel_worker(void *unused) {
(void)unused;
struct stat st;
for (;;) {
assert(raw(SYS_newfstatat, AT_FDCWD, (long)"/usr/lib/libc.so.6", (long)&st, 0, 0, 0) == 0);
pthread_testcancel();
}
}
static void signal_checks(void) {
stack_t old_stack, stack = {.ss_sp = malloc(128 * 1024), .ss_size = 128 * 1024};
assert(stack.ss_sp && sigaltstack(&stack, &old_stack) == 0);
struct sigaction old, action = {.sa_handler = on_signal, .sa_flags = SA_ONSTACK};
sigemptyset(&action.sa_mask);
assert(sigaction(SIGUSR1, &action, &old) == 0);
sigset_t deliver, old_mask;
sigemptyset(&deliver);
sigaddset(&deliver, SIGUSR1);
assert(pthread_sigmask(SIG_UNBLOCK, &deliver, &old_mask) == 0);
struct signal_sender sender = {.target = pthread_self()};
pthread_t thread;
assert(pthread_create(&thread, NULL, send_signals, &sender) == 0);
struct stat st;
for (int i = 0; i < 2000 || !__atomic_load_n(&sender.done, __ATOMIC_ACQUIRE); i++)
assert(raw(SYS_newfstatat, AT_FDCWD, (long)"/usr/lib/libc.so.6", (long)&st, 0, 0, 0) == 0);
assert(pthread_join(thread, NULL) == 0 && signals_seen > 0);
assert(sigaction(SIGUSR1, &old, NULL) == 0 && sigaltstack(&old_stack, NULL) == 0);
assert(pthread_sigmask(SIG_SETMASK, &old_mask, NULL) == 0);
free(stack.ss_sp);
void *result;
assert(pthread_create(&thread, NULL, cancel_worker, NULL) == 0);
assert(pthread_cancel(thread) == 0 && pthread_join(thread, &result) == 0 && result == PTHREAD_CANCELED);
for (int i = 0; i < 32; i++) {
assert(pthread_create(&thread, NULL, thread_check, NULL) == 0);
assert(pthread_join(thread, NULL) == 0);
}
puts("PASS: signal reentry on alternate stack, deferred cancellation, thread churn");
}
int main(void) {
assert(fp_ready());
printf("platform: android %d, patched %u sites\n", svc_android_api, svc_sites);
assert(svc_single_thread());
size_t page = sysconf(_SC_PAGESIZE);
uint32_t *relay = svc_relay_page((uintptr_t)&svc_check_site, page), branch;
@@ -100,6 +279,8 @@ int main(void) {
check(args, 0);
args[3] = 0x40000000;
check(args, -EINVAL);
path_checks();
robust_checks();
pthread_t threads[4];
assert(pthread_barrier_init(&barrier, NULL, 5) == 0);
for (int i = 0; i < 4; i++) assert(pthread_create(&threads[i], NULL, thread_probe, NULL) == 0);
@@ -113,6 +294,7 @@ int main(void) {
if (!pid) { thread_check(NULL); _exit(0); }
int status;
assert(waitpid(pid, &status, 0) == pid && WIFEXITED(status) && WEXITSTATUS(status) == 0);
signal_checks();
puts("PASS: raw results, errno, x1-x30, q0-q31, NZCV, FPCR/FPSR, 4 threads, fork");
return 0;
}
+90 -54
View File
@@ -931,29 +931,26 @@ static long statx_call(int dirfd, const char *path, int flags, unsigned mask, st
return 0;
}
int statx(int dirfd, const char *path, int flags, unsigned mask, struct statx *out) {
REAL(statx, int (*)(int, const char *, int, unsigned, struct statx *));
static long fp_statx(int dirfd, const char *path, int flags, unsigned mask, struct statx *out) {
struct res r;
long rs;
if (mask & STATX__RESERVED) { errno = EINVAL; return -1; }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull-compare"
if (!out) { errno = EFAULT; return -1; }
if (!path) {
if (!(flags & AT_EMPTY_PATH)) { errno = EFAULT; return -1; }
return real_statx ? real_statx(dirfd, path, flags, mask, out) : ret(sc(SYS_statx, dirfd, (long)path, flags, mask, (long)out));
}
#pragma GCC diagnostic pop
if ((flags & AT_EMPTY_PATH) && !path[0] && fp_ready()) {
hits++;
return ret(statx_call(dirfd, "", flags, mask, out));
}
if (mask & STATX__RESERVED) return -EINVAL;
if (!out) return -EFAULT;
if (!path) return flags & AT_EMPTY_PATH ? FP_SLOW : -EFAULT;
if ((flags & AT_EMPTY_PATH) && !path[0] && fp_ready())
return statx_call(dirfd, "", flags, mask, out);
rs = (flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE)) ? FP_SLOW
: resolve(dirfd, path, (flags & AT_SYMLINK_NOFOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r);
if (rs == 1) rs = -ENOENT;
if (rs == 0 || rs == UNSEEN)
rs = statx_call(AT_FDCWD, r.host, flags | (rs == 0 && !r.magic ? AT_SYMLINK_NOFOLLOW : 0), mask, out);
ANSWER(rs, dirfd, path, real_statx(dirfd, path, flags, mask, out));
return rs;
}
int statx(int dirfd, const char *path, int flags, unsigned mask, struct statx *out) {
REAL(statx, int (*)(int, const char *, int, unsigned, struct statx *));
ANSWER(fp_statx(dirfd, path, flags, mask, out), dirfd, path,
real_statx ? real_statx(dirfd, path, flags, mask, out) : ret(sc(SYS_statx, dirfd, (long)path, flags, mask, (long)out)));
}
@@ -961,8 +958,7 @@ static int effective_is_real(void) {
return !getauxval(AT_SECURE) && getuid() == geteuid() && getgid() == getegid();
}
static int access_common(int dirfd, const char *path, int mode, int flags) {
REAL(faccessat, int (*)(int, const char *, int, int));
static long fp_access(int dirfd, const char *path, int mode, int flags) {
struct res r;
if ((flags & AT_EACCESS) && effective_is_real()) flags &= ~AT_EACCESS;
long rs = (flags & ~AT_SYMLINK_NOFOLLOW) == 0
@@ -970,6 +966,12 @@ static int access_common(int dirfd, const char *path, int mode, int flags) {
if (rs == 1) rs = -ENOENT;
if (rs == 0 && S_ISLNK(r.st.st_mode)) rs = FP_SLOW;
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_faccessat, AT_FDCWD, (long)r.host, mode, 0, 0);
return rs;
}
static int access_common(int dirfd, const char *path, int mode, int flags) {
REAL(faccessat, int (*)(int, const char *, int, int));
long rs = fp_access(dirfd, path, mode, flags);
if (rs != FP_SLOW) { hits++; return ret(rs); }
missed(__func__, dirfd, path);
if (flags == 0) return (int)syscall(SYS_faccessat, dirfd, path, mode);
@@ -997,34 +999,36 @@ static long link_text(const struct res *r, char *text, size_t size, int *changed
return (long)strlen(text);
}
static ssize_t readlink_common(int dirfd, const char *path, char *buf, size_t size) {
REAL(readlinkat, ssize_t (*)(int, const char *, char *, size_t));
static long fp_readlink(int dirfd, const char *path, char *buf, size_t size, size_t *written) {
struct res r;
char text[PATH_MAX];
int changed;
long rs = size ? resolve(dirfd, path, 0, &r) : FP_SLOW;
if (rs == 1) rs = -ENOENT;
if (rs == 0) rs = link_text(&r, text, size, &changed);
if (rs == FP_SLOW) {
missed(__func__, dirfd, path);
return real_readlinkat(dirfd, path, buf, size);
}
hits++;
if (rs < 0) return ret(rs);
if (rs < 0) return rs;
size_t n = rs;
if (!changed) {
n = n < size ? n : size;
memcpy(buf, text, n);
if (written) *written = n;
return n;
}
size_t max = size < PATH_MAX ? size : PATH_MAX;
if (n + 1 < max) {
memcpy(buf, text, n + 1);
if (written) *written = n + 1;
return n;
}
memcpy(buf, text, max);
if (written) *written = max;
return max;
}
static ssize_t readlink_common(int dirfd, const char *path, char *buf, size_t size) {
REAL(readlinkat, ssize_t (*)(int, const char *, char *, size_t));
ANSWER(fp_readlink(dirfd, path, buf, size, NULL), dirfd, path, real_readlinkat(dirfd, path, buf, size));
}
ssize_t readlinkat(int dirfd, const char *path, char *buf, size_t size) { return readlink_common(dirfd, path, buf, size); }
ssize_t readlink(const char *path, char *buf, size_t size) { return readlink_common(AT_FDCWD, path, buf, size); }
@@ -1086,12 +1090,15 @@ char *__getcwd_chk(char *buf, size_t size, size_t room) {
}
int mkdirat(int dirfd, const char *path, mode_t mode) {
REAL(mkdirat, int (*)(int, const char *, mode_t));
static long fp_mkdir(int dirfd, const char *path, mode_t mode) {
struct res r;
long rs = resolve(dirfd, path, MAY_BE_UNSEEN, &r);
if (rs == 0 || rs == 1 || rs == UNSEEN) rs = sc(SYS_mkdirat, AT_FDCWD, (long)r.host, mode, 0, 0);
ANSWER(rs, dirfd, path, real_mkdirat(dirfd, path, mode));
return rs;
}
int mkdirat(int dirfd, const char *path, mode_t mode) {
REAL(mkdirat, int (*)(int, const char *, mode_t));
ANSWER(fp_mkdir(dirfd, path, mode), dirfd, path, real_mkdirat(dirfd, path, mode));
}
int mkdir(const char *path, mode_t mode) { return mkdirat(AT_FDCWD, path, mode); }
@@ -1137,8 +1144,7 @@ int unlinkat(int dirfd, const char *path, int flags) { return unlink_common(dirf
int unlink(const char *path) { return unlink_common(AT_FDCWD, path, 0); }
int rmdir(const char *path) { return unlink_common(AT_FDCWD, path, AT_REMOVEDIR); }
static int rename_common(int olddirfd, const char *old, int newdirfd, const char *new, unsigned flags) {
REAL(renameat2, int (*)(int, const char *, int, const char *, unsigned));
static long fp_rename(int olddirfd, const char *old, int newdirfd, const char *new, unsigned flags) {
struct res from, to;
long rs = flags & ~RENAME_NOREPLACE || trailing_slash(old) || trailing_slash(new) ? FP_SLOW : resolve(olddirfd, old, 0, &from);
if (rs == 1) rs = -ENOENT;
@@ -1153,6 +1159,11 @@ static int rename_common(int olddirfd, const char *old, int newdirfd, const char
if (rs == 0) forget();
}
}
return rs;
}
static int rename_common(int olddirfd, const char *old, int newdirfd, const char *new, unsigned flags) {
REAL(renameat2, int (*)(int, const char *, int, const char *, unsigned));
long rs = fp_rename(olddirfd, old, newdirfd, new, flags);
if (rs != FP_SLOW) { hits++; return ret(rs); }
missed(__func__, olddirfd, old);
if (!flags) return (int)syscall(SYS_renameat, olddirfd, old, newdirfd, new);
@@ -1164,30 +1175,35 @@ int renameat2(int olddirfd, const char *old, int newdirfd, const char *new, unsi
int renameat(int olddirfd, const char *old, int newdirfd, const char *new) { return rename_common(olddirfd, old, newdirfd, new, 0); }
int rename(const char *old, const char *new) { return rename_common(AT_FDCWD, old, AT_FDCWD, new, 0); }
int symlinkat(const char *target, int dirfd, const char *path) {
REAL(symlinkat, int (*)(const char *, int, const char *));
static long fp_symlink(const char *target, int dirfd, const char *path) {
struct res r;
long rs = trailing_slash(path) ? FP_SLOW : resolve(dirfd, path, MAY_BE_UNSEEN, &r);
if (rs == 0 || rs == 1 || rs == UNSEEN) {
rs = sc(SYS_symlinkat, (long)target, AT_FDCWD, (long)r.host, 0, 0);
if (rs == 0) forget();
}
ANSWER(rs, dirfd, path, real_symlinkat(target, dirfd, path));
return rs;
}
int symlinkat(const char *target, int dirfd, const char *path) {
REAL(symlinkat, int (*)(const char *, int, const char *));
ANSWER(fp_symlink(target, dirfd, path), dirfd, path, real_symlinkat(target, dirfd, path));
}
int symlink(const char *target, const char *path) { return symlinkat(target, AT_FDCWD, path); }
int fchmodat(int dirfd, const char *path, mode_t mode, int flags) {
REAL(fchmodat, int (*)(int, const char *, mode_t, int));
static long fp_chmod(int dirfd, const char *path, mode_t mode, int flags) {
struct res r;
long rs = flags ? FP_SLOW : resolve(dirfd, path, 1 | MAY_BE_UNSEEN, &r);
if (rs == 1) rs = -ENOENT;
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_fchmodat, AT_FDCWD, (long)r.host, mode, 0, 0);
ANSWER(rs, dirfd, path, real_fchmodat(dirfd, path, mode, flags));
return rs;
}
int fchmodat(int dirfd, const char *path, mode_t mode, int flags) {
REAL(fchmodat, int (*)(int, const char *, mode_t, int));
ANSWER(fp_chmod(dirfd, path, mode, flags), dirfd, path, real_fchmodat(dirfd, path, mode, flags));
}
int chmod(const char *path, mode_t mode) { return fchmodat(AT_FDCWD, path, mode, 0); }
static int utimens_common(int dirfd, const char *path, const struct timespec times[2], int flags) {
REAL(utimensat, int (*)(int, const char *, const struct timespec[2], int));
static long fp_utimens(int dirfd, const char *path, const struct timespec times[2], int flags) {
struct res r;
long rs;
if (!path && fp_ready()) {
@@ -1197,7 +1213,11 @@ static int utimens_common(int dirfd, const char *path, const struct timespec tim
if (rs == 1) rs = -ENOENT;
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_utimensat, AT_FDCWD, (long)r.host, (long)times, flags, 0);
}
ANSWER(rs, dirfd, path, real_utimensat(dirfd, path, times, flags));
return rs;
}
static int utimens_common(int dirfd, const char *path, const struct timespec times[2], int flags) {
REAL(utimensat, int (*)(int, const char *, const struct timespec[2], int));
ANSWER(fp_utimens(dirfd, path, times, flags), dirfd, path, real_utimensat(dirfd, path, times, flags));
}
int utimensat(int dirfd, const char *path, const struct timespec times[2], int flags) {
return utimens_common(dirfd, path, times, flags);
@@ -1224,23 +1244,29 @@ int utime(const char *path, const struct utimbuf *times) {
return utimens_common(AT_FDCWD, path, ts, 0);
}
int truncate(const char *path, off_t length) {
REAL(truncate, int (*)(const char *, off_t));
static long fp_truncate(const char *path, off_t length) {
struct res r;
long rs = resolve(AT_FDCWD, path, 1 | MAY_BE_UNSEEN, &r);
if (rs == 1) rs = -ENOENT;
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_truncate, (long)r.host, length, 0, 0, 0);
ANSWER(rs, AT_FDCWD, path, real_truncate(path, length));
return rs;
}
int truncate(const char *path, off_t length) {
REAL(truncate, int (*)(const char *, off_t));
ANSWER(fp_truncate(path, length), AT_FDCWD, path, real_truncate(path, length));
}
int truncate64(const char *path, off_t length) __attribute__((alias("truncate")));
static int statfs_common(const char *path, struct statfs *buf, int (*real)(const char *, struct statfs *)) {
static long fp_statfs(const char *path, struct statfs *buf) {
struct res r;
long rs = resolve(AT_FDCWD, path, 1, &r);
if (rs == 1) rs = -ENOENT;
if (rs == 0 && under(r.guest, "/dev/shm", 8)) rs = FP_SLOW;
if (rs == 0) rs = sc(SYS_statfs, (long)r.host, (long)buf, 0, 0, 0);
ANSWER(rs, AT_FDCWD, path, real(path, buf));
return rs;
}
static int statfs_common(const char *path, struct statfs *buf, int (*real)(const char *, struct statfs *)) {
ANSWER(fp_statfs(path, buf), AT_FDCWD, path, real(path, buf));
}
int statfs(const char *path, struct statfs *buf) {
REAL(statfs, int (*)(const char *, struct statfs *));
@@ -1288,12 +1314,16 @@ int statvfs64(const char *path, struct statvfs64 *buf) {
return statvfs_common(path, (struct statvfs *)buf, real_statvfs64);
}
static ssize_t getxattr_common(const char *path, const char *name, void *value, size_t size, int follow,
ssize_t (*real)(const char *, const char *, void *, size_t)) {
static long fp_getxattr(const char *path, const char *name, void *value, size_t size, int follow) {
struct res r;
long rs = resolve(AT_FDCWD, path, follow | MAY_BE_UNSEEN, &r);
if (rs == 1) rs = -ENOENT;
if (rs == 0 || rs == UNSEEN) rs = sc(follow ? SYS_getxattr : SYS_lgetxattr, (long)r.host, (long)name, (long)value, size, 0);
return rs;
}
static ssize_t getxattr_common(const char *path, const char *name, void *value, size_t size, int follow,
ssize_t (*real)(const char *, const char *, void *, size_t)) {
long rs = fp_getxattr(path, name, value, size, follow);
if (rs != FP_SLOW) { hits++; return rs < 0 ? ret(rs) : rs; }
missed(__func__, AT_FDCWD, path);
return real(path, name, value, size);
@@ -1307,12 +1337,16 @@ ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size)
return getxattr_common(path, name, value, size, 0, real_lgetxattr);
}
static ssize_t listxattr_common(const char *path, char *list, size_t size, int follow,
ssize_t (*real)(const char *, char *, size_t)) {
static long fp_listxattr(const char *path, char *list, size_t size, int follow) {
struct res r;
long rs = resolve(AT_FDCWD, path, follow | MAY_BE_UNSEEN, &r);
if (rs == 1) rs = -ENOENT;
if (rs == 0 || rs == UNSEEN) rs = sc(follow ? SYS_listxattr : SYS_llistxattr, (long)r.host, (long)list, size, 0, 0);
return rs;
}
static ssize_t listxattr_common(const char *path, char *list, size_t size, int follow,
ssize_t (*real)(const char *, char *, size_t)) {
long rs = fp_listxattr(path, list, size, follow);
if (rs != FP_SLOW) { hits++; return rs < 0 ? ret(rs) : rs; }
missed(__func__, AT_FDCWD, path);
return real(path, list, size);
@@ -1326,13 +1360,15 @@ ssize_t llistxattr(const char *path, char *list, size_t size) {
return listxattr_common(path, list, size, 0, real_llistxattr);
}
int inotify_add_watch(int fd, const char *path, uint32_t mask) {
REAL(inotify_add_watch, int (*)(int, const char *, uint32_t));
static long fp_inotify(int fd, const char *path, uint32_t mask) {
struct res r;
long rs = resolve(AT_FDCWD, path, (mask & IN_DONT_FOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r);
if (rs == 1) rs = -ENOENT;
if (rs == 0 || rs == UNSEEN) rs = sc(SYS_inotify_add_watch, fd, (long)r.host, mask, 0, 0);
ANSWER(rs, AT_FDCWD, path, real_inotify_add_watch(fd, path, mask));
if (rs == 0 || rs == 1 || rs == UNSEEN) rs = sc(SYS_inotify_add_watch, fd, (long)r.host, mask, 0, 0);
return rs;
}
int inotify_add_watch(int fd, const char *path, uint32_t mask) {
REAL(inotify_add_watch, int (*)(int, const char *, uint32_t));
ANSWER(fp_inotify(fd, path, mask), AT_FDCWD, path, real_inotify_add_watch(fd, path, mask));
}
+90
View File
@@ -0,0 +1,90 @@
struct svc_cached_sites {
uint64_t magic;
uint32_t api, count;
uint64_t offset[256];
};
static unsigned svc_cache_hits, svc_cache_misses;
static int svc_segment(const struct dl_phdr_info *info, uint64_t at, size_t length, unsigned flags) {
for (int i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *p = &info->dlpi_phdr[i];
if (p->p_type == PT_LOAD && (p->p_flags & flags) == flags &&
at >= p->p_vaddr && at - p->p_vaddr <= p->p_memsz &&
length <= p->p_memsz - (at - p->p_vaddr)) return i + 1;
}
return 0;
}
static int svc_cache_name(const struct dl_phdr_info *info, char *path, size_t size) {
const char *enabled = getenv("PROOT_FP_SVC_CACHE");
if (enabled && strcmp(enabled, "1")) return 0;
for (int i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *p = &info->dlpi_phdr[i];
if (p->p_type != PT_NOTE || !svc_segment(info, p->p_vaddr, p->p_memsz, PF_R)) continue;
const unsigned char *note = (const unsigned char *)(info->dlpi_addr + p->p_vaddr);
for (size_t at = 0; p->p_memsz - at >= sizeof(ElfW(Nhdr));) {
ElfW(Nhdr) h;
memcpy(&h, note + at, sizeof h);
at += sizeof h;
size_t name = ((size_t)h.n_namesz + 3) & ~(size_t)3;
size_t desc = ((size_t)h.n_descsz + 3) & ~(size_t)3;
if (name > p->p_memsz - at || desc > p->p_memsz - at - name) break;
if (h.n_type == NT_GNU_BUILD_ID && h.n_namesz == 4 && !memcmp(note + at, "GNU", 4) &&
h.n_descsz && h.n_descsz <= 64) {
char id[129];
static const char hex[] = "0123456789abcdef";
for (size_t j = 0; j < h.n_descsz; j++) {
unsigned char b = note[at + name + j];
id[2 * j] = hex[b >> 4];
id[2 * j + 1] = hex[b & 15];
}
id[2 * h.n_descsz] = 0;
int n = snprintf(path, size, "/tmp/.droiddeck-svc-v1-%d-%s", svc_android_api, id);
return n > 0 && (size_t)n < size;
}
at += name + desc;
}
}
return 0;
}
static int svc_cache_read(const struct dl_phdr_info *info, const char *path) {
long fd = fp_open(AT_FDCWD, path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK, 0);
if (fd < 0) return 0;
struct stat st;
struct svc_cached_sites table;
int valid = sc(SYS_fstat, fd, (long)&st, 0, 0, 0) == 0 && S_ISREG(st.st_mode) &&
st.st_uid == (uid_t)sc(SYS_getuid, 0, 0, 0, 0, 0) && !(st.st_mode & 0077) &&
st.st_size == sizeof table && sc(SYS_read, fd, (long)&table, sizeof table, 0, 0) == sizeof table;
sc(SYS_close, fd, 0, 0, 0, 0);
if (!valid || table.magic != 0x314356534444ULL || table.api != (unsigned)svc_android_api ||
!table.count || table.count > 256 || table.count > 256 - svc_planned) return 0;
for (unsigned i = 0; i < table.count; i++) {
uint64_t offset = table.offset[i];
int segment = svc_segment(info, offset, 4, PF_R | PF_X);
if ((offset & 3) || (i && offset <= table.offset[i - 1]) || !segment) return 0;
const ElfW(Phdr) *p = &info->dlpi_phdr[segment - 1];
const uint32_t *text = (const uint32_t *)(info->dlpi_addr + p->p_vaddr);
size_t at = (offset - p->p_vaddr) / 4;
if (text[at] != 0xd4000001U || !svc_supported(svc_site_number(text, at))) return 0;
}
for (unsigned i = 0; i < table.count; i++)
svc_plan[svc_planned++].site = (uint32_t *)(info->dlpi_addr + table.offset[i]);
return 1;
}
static void svc_cache_write(const struct dl_phdr_info *info, const char *path, size_t first) {
if (svc_planned == first || svc_planned == 256) return;
struct svc_cached_sites table = {.magic = 0x314356534444ULL, .api = svc_android_api, .count = svc_planned - first};
for (unsigned i = 0; i < table.count; i++)
table.offset[i] = (uintptr_t)svc_plan[first + i].site - info->dlpi_addr;
char temporary[256];
int n = snprintf(temporary, sizeof temporary, "%s.part-%d", path, getpid());
if (n <= 0 || n >= (int)sizeof temporary) return;
long fd = fp_open(AT_FDCWD, temporary, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
if (fd < 0) return;
long written = sc(SYS_write, fd, (long)&table, sizeof table, 0, 0);
sc(SYS_close, fd, 0, 0, 0, 0);
if (written == sizeof table) fp_rename(AT_FDCWD, temporary, AT_FDCWD, path, 0);
fp_unlink(AT_FDCWD, temporary, 0);
}
+139
View File
@@ -0,0 +1,139 @@
static int svc_android_api;
static __thread struct { long head, tid; } svc_robust __attribute__((tls_model("initial-exec")));
static int svc_host_android_api(void) {
const char *value = getenv("PROOT_FP_ANDROID_API");
if (!value || !*value) return 0;
char *end;
long api = strtol(value, &end, 10);
return !*end && api > 0 && api < 1000 ? (int)api : 0;
}
static long svc_memory(void *local, long remote, size_t size, int write) {
if (!size) return 0;
struct iovec here = {local, size}, there = {(void *)remote, size};
return sc(write ? SYS_process_vm_writev : SYS_process_vm_readv, getpid(),
(long)&here, 1, (long)&there, 1);
}
static int svc_path(long pointer, char *path) {
long n = svc_memory(path, pointer, PATH_MAX, 0);
return n > 0 && memchr(path, 0, (size_t)n) != NULL;
}
static long svc_output(long result, void *value, long out, size_t size) {
if (result < 0) return result;
return svc_memory(value, out, size, 1) == (long)size ? result : FP_SLOW;
}
static int svc_supported(int nr) {
switch (nr) {
case SYS_openat: case SYS_newfstatat: case SYS_statx: case SYS_readlinkat:
case SYS_faccessat: case SYS_mkdirat: case SYS_unlinkat: case SYS_renameat:
case SYS_renameat2: case SYS_symlinkat: case SYS_fchmodat: case SYS_utimensat:
case SYS_truncate: case SYS_statfs: case SYS_getxattr: case SYS_lgetxattr:
case SYS_listxattr: case SYS_llistxattr: case SYS_inotify_add_watch: case SYS_getcwd:
return 1;
case SYS_set_robust_list: case SYS_get_robust_list: case SYS_faccessat2:
return svc_android_api > 0;
case SYS_clone3:
return svc_android_api > 0 && svc_android_api < 35;
default: return 0;
}
}
static long svc_blocked(const long *r) {
if (!svc_android_api) return FP_SLOW;
if (r[8] == SYS_faccessat2 || (r[8] == SYS_clone3 && svc_android_api < 35)) return -ENOSYS;
if (r[8] == SYS_set_robust_list) {
svc_robust.head = r[0];
svc_robust.tid = sc(SYS_gettid, 0, 0, 0, 0, 0);
return -ENOSYS;
}
if (r[8] == SYS_get_robust_list) {
long tid = sc(SYS_gettid, 0, 0, 0, 0, 0);
if ((r[0] && r[0] != tid) || svc_robust.tid != tid || !svc_robust.head) return FP_SLOW;
size_t size = 3 * sizeof(long);
if (svc_memory(&svc_robust.head, r[1], sizeof(long), 1) != sizeof(long) ||
svc_memory(&size, r[2], sizeof size, 1) != sizeof size) return -EFAULT;
return 0;
}
return FP_SLOW;
}
static long svc_path_call(const long *r) {
char path[PATH_MAX], other[PATH_MAX];
struct timespec times[2];
const struct timespec *when = (const struct timespec *)r[2];
int first = 1, second = -1;
switch (r[8]) {
case SYS_getcwd: {
if (own_cwd(path) == FP_SLOW) return FP_SLOW;
size_t n = strlen(path) + 1;
if ((unsigned long)r[1] < n) return -ERANGE;
return svc_output(n, path, r[0], n);
}
case SYS_truncate: case SYS_statfs: case SYS_getxattr: case SYS_lgetxattr:
case SYS_listxattr: case SYS_llistxattr: first = 0; break;
case SYS_renameat: case SYS_renameat2: second = 3; break;
case SYS_symlinkat: first = 0; second = 2; break;
case SYS_utimensat:
if (when) {
if (svc_memory(times, r[2], sizeof times, 0) != sizeof times) return FP_SLOW;
for (int i = 0; i < 2; i++)
if (times[i].tv_nsec != UTIME_NOW && times[i].tv_nsec != UTIME_OMIT &&
(times[i].tv_nsec < 0 || times[i].tv_nsec >= 1000000000L)) return FP_SLOW;
when = times;
}
if (!r[1]) return fp_utimens((int)r[0], NULL, when, (int)r[3]);
break;
case SYS_inotify_add_watch: {
char link[64], target[64];
snprintf(link, sizeof link, "/proc/self/fd/%d", (int)r[0]);
long n = sc(SYS_readlinkat, AT_FDCWD, (long)link, (long)target, sizeof target - 1, 0);
if (n <= 0) return FP_SLOW;
target[n] = 0;
if (strcmp(target, "anon_inode:inotify")) return FP_SLOW;
break;
}
default:
if (!svc_supported((int)r[8])) return FP_SLOW;
}
if (!svc_path(r[first], path) || (second >= 0 && !svc_path(r[second], other))) return FP_SLOW;
switch (r[8]) {
case SYS_openat: return fp_open((int)r[0], path, (int)r[2], (mode_t)r[3]);
case SYS_newfstatat: {
struct stat st;
return svc_output(fp_stat((int)r[0], path, (int)r[3], &st), &st, r[2], sizeof st);
}
case SYS_statx: {
struct statx st;
return svc_output(fp_statx((int)r[0], path, (int)r[2], (unsigned)r[3], &st), &st, r[4], sizeof st);
}
case SYS_readlinkat: {
char text[PATH_MAX];
size_t written = 0;
long result = fp_readlink((int)r[0], path, text, (size_t)r[3], &written);
return svc_output(result, text, r[2], written);
}
case SYS_faccessat: return fp_access((int)r[0], path, (int)r[2], 0);
case SYS_mkdirat: return fp_mkdir((int)r[0], path, (mode_t)r[2]);
case SYS_unlinkat: return fp_unlink((int)r[0], path, (int)r[2]);
case SYS_renameat: case SYS_renameat2:
return fp_rename((int)r[0], path, (int)r[2], other, r[8] == SYS_renameat2 ? (unsigned)r[4] : 0);
case SYS_symlinkat: return fp_symlink(path, (int)r[1], other);
case SYS_fchmodat: return fp_chmod((int)r[0], path, (mode_t)r[2], 0);
case SYS_utimensat: return fp_utimens((int)r[0], path, when, (int)r[3]);
case SYS_truncate: return fp_truncate(path, (off_t)r[1]);
case SYS_statfs: {
struct statfs st;
return svc_output(fp_statfs(path, &st), &st, r[1], sizeof st);
}
case SYS_getxattr: case SYS_lgetxattr:
return fp_getxattr(path, (const char *)r[1], (void *)r[2], (size_t)r[3], r[8] == SYS_getxattr);
case SYS_listxattr: case SYS_llistxattr:
return fp_listxattr(path, (char *)r[1], (size_t)r[2], r[8] == SYS_listxattr);
case SYS_inotify_add_watch: return fp_inotify((int)r[0], path, (uint32_t)r[2]);
default: return FP_SLOW;
}
}
+46 -18
View File
@@ -7,17 +7,23 @@ static inline int svc_number(uint32_t insn) {
static inline int svc_argument_instruction(uint32_t insn) {
if (insn == 0xd503201fU) return 1;
if ((insn & 0xffc003e0U) == 0x290003e0U ||
(insn & 0xffc003e0U) == 0xa90003e0U) return 1;
if ((insn & 0xffc00000U) == 0x29000000U ||
(insn & 0xffc00000U) == 0xa9000000U ||
(insn & 0xffc00000U) == 0xb9000000U ||
(insn & 0xffc00000U) == 0xf9000000U) return 1;
if ((insn & 31) == 8) return 0;
return (insn & 0xffe0ffe0U) == 0xaa0003e0U ||
(insn & 0xffe0ffe0U) == 0x2a0003e0U ||
(insn & 0xfffffc00U) == 0x93407c00U ||
(insn & 0x7f800000U) == 0x52800000U ||
(insn & 0x7f800000U) == 0x12800000U ||
(insn & 0xffc003e0U) == 0xb94003e0U ||
(insn & 0xffc003e0U) == 0xf94003e0U ||
(insn & 0xffc003e0U) == 0xb98003e0U;
(insn & 0xffc00000U) == 0xb9400000U ||
(insn & 0xffc00000U) == 0xf9400000U ||
(insn & 0xffc00000U) == 0xb9800000U ||
(insn & 0xff000000U) == 0x91000000U ||
(insn & 0xff000000U) == 0xd1000000U ||
(insn & 0xff000000U) == 0x11000000U ||
(insn & 0xff000000U) == 0x51000000U;
}
static inline int svc_site_number(const uint32_t *text, size_t at) {
@@ -40,10 +46,14 @@ static inline int svc_branch(uintptr_t from, uintptr_t to, uint32_t *out) {
#if defined(__aarch64__) && !defined(FP_SVC_ENCODINGS_ONLY)
#include <asm/hwcap.h>
#include <link.h>
#include <sys/uio.h>
static __thread int svc_busy __attribute__((tls_model("initial-exec")));
static unsigned svc_sites, svc_pages;
static unsigned long svc_fast, svc_slow;
static unsigned long long svc_scan_ns, svc_patch_ns;
#include "svc-dispatch.h"
static long svc_original(const long *r) {
register long x0 __asm__("x0") = r[0];
@@ -60,20 +70,16 @@ static long svc_original(const long *r) {
__attribute__((used, visibility("hidden")))
long fp_svc_dispatch(const long *r) {
int saved_errno = errno;
long result = FP_SLOW;
if (!svc_busy) {
long result = svc_blocked(r);
if (result == FP_SLOW && !svc_busy) {
svc_busy = 1;
if (r[8] == SYS_openat)
result = fp_open((int)r[0], (const char *)r[1], (int)r[2], (mode_t)r[3]);
else if (r[8] == SYS_newfstatat && r[2])
result = fp_stat((int)r[0], (const char *)r[1], (int)r[3], (struct stat *)r[2]);
if (result != FP_SLOW) __atomic_fetch_add(&svc_fast, 1, __ATOMIC_RELAXED);
result = svc_path_call(r);
svc_busy = 0;
}
if (result == FP_SLOW) {
__atomic_fetch_add(&svc_slow, 1, __ATOMIC_RELAXED);
result = svc_original(r);
}
} else __atomic_fetch_add(&svc_fast, 1, __ATOMIC_RELAXED);
errno = saved_errno;
return result;
}
@@ -141,12 +147,19 @@ struct svc_site { uint32_t *site; uint32_t branch; };
static struct svc_site svc_plan[256];
static size_t svc_planned;
#include "svc-cache.h"
static int svc_object(struct dl_phdr_info *info, size_t size, void *unused) {
(void)size;
(void)unused;
const char *name = strrchr(info->dlpi_name, '/');
name = name ? name + 1 : info->dlpi_name;
if (strcmp(name, "libc.so.6") && strcmp(name, "ld-linux-aarch64.so.1")) return 0;
char cache[224];
int cached = svc_cache_name(info, cache, sizeof cache);
if (cached && svc_cache_read(info, cache)) { svc_cache_hits++; return 0; }
if (cached) svc_cache_misses++;
size_t first = svc_planned;
for (int i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *ph = &info->dlpi_phdr[i];
if (ph->p_type != PT_LOAD || !(ph->p_flags & PF_X)) continue;
@@ -154,10 +167,11 @@ static int svc_object(struct dl_phdr_info *info, size_t size, void *unused) {
for (size_t j = 1; j < ph->p_memsz / 4 && svc_planned < 256; j++) {
if (text[j] != 0xd4000001U) continue;
int nr = svc_site_number(text, j);
if (nr != SYS_openat && nr != SYS_newfstatat) continue;
if (!svc_supported(nr)) continue;
svc_plan[svc_planned++].site = &text[j];
}
}
if (cached) svc_cache_write(info, cache, first);
return 0;
}
@@ -199,12 +213,20 @@ done:
}
static void svc_stats(void) {
char line[160];
int n = snprintf(line, sizeof line, "fastpath-svc[%d]: %u sites, %u relay pages, %lu fast, %lu to proot\n",
getpid(), svc_sites, svc_pages, svc_fast, svc_slow);
if (write(2, line, n) < 0) {}
char line[256];
int n = snprintf(line, sizeof line, "fastpath-svc[%d]: %u sites, %u relay pages, %lu fast, %lu to proot; scan %llu us, patch %llu us, android %d, cache %u hit %u miss\n",
getpid(), svc_sites, svc_pages, svc_fast, svc_slow, svc_scan_ns / 1000, svc_patch_ns / 1000, svc_android_api, svc_cache_hits, svc_cache_misses);
if (n > 0 && write(2, line, n < (int)sizeof line ? n : (int)sizeof line - 1) < 0) {}
}
static unsigned long long svc_clock(void) {
struct timespec t;
if (sc(SYS_clock_gettime, CLOCK_MONOTONIC, (long)&t, 0, 0, 0) < 0) return 0;
return (unsigned long long)t.tv_sec * 1000000000ULL + t.tv_nsec;
}
static void svc_child(void) { svc_busy = 0; }
__attribute__((constructor)) static void svc_patch_init(void) {
const char *enabled = getenv("PROOT_FP_PATCH_SVC");
if ((enabled && strcmp(enabled, "1")) || !fp_ready()) return;
@@ -213,11 +235,16 @@ __attribute__((constructor)) static void svc_patch_init(void) {
if (getauxval(AT_HWCAP2) & HWCAP2_SME) return;
#endif
if (!svc_single_thread()) return;
if (pthread_atfork(NULL, NULL, svc_child)) return;
svc_android_api = svc_host_android_api();
size_t page = (size_t)sysconf(_SC_PAGESIZE);
if (page < 4096 || (page & (page - 1))) return;
int mem = (int)sc(SYS_openat, AT_FDCWD, (long)"/proc/self/mem", O_RDWR | O_CLOEXEC, 0, 0);
if (mem < 0) return;
unsigned long long start = svc_clock();
dl_iterate_phdr(svc_object, NULL);
svc_scan_ns = svc_clock() - start;
start = svc_clock();
for (size_t first = 0; first < svc_planned;) {
uint32_t *relay = svc_relay_page((uintptr_t)svc_plan[first].site, page);
if (!relay) { first++; continue; }
@@ -245,6 +272,7 @@ __attribute__((constructor)) static void svc_patch_init(void) {
first = end;
}
sc(SYS_close, mem, 0, 0, 0, 0);
svc_patch_ns = svc_clock() - start;
if (getenv("PROOT_FP_STATS")) atexit(svc_stats);
}
#endif
+103
View File
@@ -33,6 +33,9 @@ int main(void) {
const uint32_t branch[] = {0xd2800708, 0x14000001, 0xd4000001};
const uint32_t call[] = {0xd2800708, 0x94000001, 0xd4000001};
const uint32_t shift[] = {0xd2800708, 0xd2a00708, 0xd4000001};
const uint32_t robust[] = {0xd2800c68, 0xf9000260, 0xa9000261, 0xf9400260, 0x9100e260, 0xd4000001};
const uint32_t writeback[] = {0xd2800708, 0xa9bf07e0, 0xd4000001};
const uint32_t added[] = {0xd2800708, 0x91000408, 0xd4000001};
assert(svc_site_number(create, 3) == 56);
assert(svc_site_number(create_at, 3) == 56);
assert(svc_site_number(xstat, 4) == 79);
@@ -41,6 +44,9 @@ int main(void) {
assert(svc_site_number(branch, 2) == -1);
assert(svc_site_number(call, 2) == -1);
assert(svc_site_number(shift, 2) == -1);
assert(svc_site_number(robust, 5) == 99);
assert(svc_site_number(writeback, 2) == -1);
assert(svc_site_number(added, 2) == -1);
uint32_t out;
uintptr_t pc = 0x10000000;
assert(svc_branch(pc, pc + 4, &out) && out == 0x14000001);
@@ -60,3 +66,100 @@ int main(void) {
output = work / "check"
subprocess.run(["cc", "-O2", "-Wall", "-Wextra", "-Werror", str(program), "-o", str(output)], check=True)
subprocess.run([str(output)], check=True, timeout=10)
def test_cache_revalidates_image_offsets_and_untrusted_files(self):
if shutil.which("cc") is None:
self.skipTest("cc is not available")
source = r'''
#define _GNU_SOURCE
#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <link.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#define FP_SVC_ENCODINGS_ONLY
#include "HEADER"
static int svc_android_api = 33;
static struct { uint32_t *site; } svc_plan[256];
static size_t svc_planned;
static int svc_supported(int nr) { return nr == 56 || nr == 79; }
#define sc(n,a,b,c,d,e) syscall(n,a,b,c,d,e)
static long fp_open(int d, const char *p, int f, mode_t m) { return openat(d,p,f,m); }
static long fp_rename(int d, const char *p, int e, const char *q, int f) { (void)f; return renameat(d,p,e,q); }
static long fp_unlink(int d, const char *p, int f) { return unlinkat(d,p,f); }
#include "CACHE"
static void store(const char *path, struct svc_cached_sites *table) {
int fd = open(path, O_WRONLY | O_TRUNC);
assert(fd >= 0 && write(fd, table, sizeof *table) == sizeof *table && close(fd) == 0);
}
int main(void) {
uint32_t image[32] = {0xd2800708, 0xd4000001, 0xd28009e8, 0xd4000001};
ElfW(Phdr) ph[] = {{.p_type=PT_LOAD, .p_flags=PF_R|PF_X, .p_memsz=sizeof image},
{.p_type=PT_NOTE, .p_vaddr=64, .p_memsz=24}};
struct dl_phdr_info info = {.dlpi_addr=(uintptr_t)image, .dlpi_phdr=ph, .dlpi_phnum=2};
ElfW(Nhdr) note = {.n_type=NT_GNU_BUILD_ID, .n_namesz=4, .n_descsz=8};
memcpy((char *)image+64, &note, sizeof note);
memcpy((char *)image+76, "GNU", 4);
uint64_t id = getpid();
memcpy((char *)image+80, &id, 8);
char path[256], other[256];
assert(svc_cache_name(&info, path, sizeof path));
svc_android_api = 35;
assert(svc_cache_name(&info, other, sizeof other) && strcmp(path, other));
svc_android_api = 33;
setenv("PROOT_FP_SVC_CACHE", "0", 1);
assert(!svc_cache_name(&info, other, sizeof other));
unsetenv("PROOT_FP_SVC_CACHE");
ph[1].p_memsz = sizeof image;
assert(!svc_cache_name(&info, other, sizeof other));
ph[1].p_memsz = 24;
svc_plan[0].site = image+1; svc_plan[1].site = image+3; svc_planned=2;
svc_cache_write(&info, path, 0);
svc_planned=0;
assert(svc_cache_read(&info, path) && svc_planned==2 && svc_plan[1].site==image+3);
struct svc_cached_sites valid, table;
int fd = open(path, O_RDONLY);
assert(fd >= 0 && read(fd, &valid, sizeof valid)==sizeof valid && close(fd)==0);
#define REJECT(change) do { table=valid; change; store(path,&table); svc_planned=0; \
assert(!svc_cache_read(&info,path) && svc_planned==0); } while(0)
REJECT(table.magic++);
REJECT(table.api=35);
REJECT(table.count=0);
REJECT(table.count=257);
REJECT(table.offset[0]=3);
REJECT(table.offset[0]=sizeof image);
REJECT(table.offset[0]=UINT64_MAX-3);
REJECT(table.offset[1]=table.offset[0]);
REJECT(table.offset[0]=12; table.offset[1]=4);
store(path,&valid);
image[1]=0x14000001;
assert(!svc_cache_read(&info,path));
image[1]=0xd4000001; image[0]=0xd2800008;
assert(!svc_cache_read(&info,path));
image[0]=0xd2800708;
assert(chmod(path,0644)==0 && !svc_cache_read(&info,path));
assert(chmod(path,0600)==0 && truncate(path,8)==0 && !svc_cache_read(&info,path));
assert(unlink(path)==0 && symlink("/dev/zero",path)==0 && !svc_cache_read(&info,path));
assert(unlink(path)==0 && mkdir(path,0700)==0 && !svc_cache_read(&info,path));
assert(rmdir(path)==0);
svc_planned=0;
svc_cache_write(&info,path,0);
assert(access(path,F_OK)<0);
(void)svc_cache_hits; (void)svc_cache_misses;
return 0;
}
'''
with tempfile.TemporaryDirectory() as directory:
work = Path(directory)
program = work / "cache.c"
program.write_text(source.replace('"HEADER"', '"' + HEADER.as_posix() + '"')
.replace('"CACHE"', '"' + HEADER.with_name("svc-cache.h").as_posix() + '"'))
output = work / "cache"
subprocess.run(["cc", "-O2", "-Wall", "-Wextra", "-Werror", str(program), "-o", str(output)], check=True)
subprocess.run([str(output)], check=True, timeout=10)