mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
perf(proot): expand and cache syscall relays
This commit is contained in:
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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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, ¬e, 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)
|
||||
Reference in new issue
Block a user