mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
340 lines
11 KiB
C
340 lines
11 KiB
C
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <link.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <time.h>
|
|
|
|
#define FP_STUB_ADDR 0xffff00000UL
|
|
#define FP_STUB_SIZE 4096UL
|
|
#define MAX_BINDS 256
|
|
#define PATH 4096
|
|
#define VERIFIED 32
|
|
#define TTL_NS 2000000000LL
|
|
|
|
typedef long (*stub_fn)(long, long, long, long, long, long, long);
|
|
static stub_fn sys;
|
|
|
|
static long sc(long nr, long a, long b, long c, long d) { return sys(nr, a, b, c, d, 0, 0); }
|
|
|
|
static long raw(long nr, long a, long b, long c, long d) {
|
|
register long x8 __asm__("x8") = nr;
|
|
register long x0 __asm__("x0") = a;
|
|
register long x1 __asm__("x1") = b;
|
|
register long x2 __asm__("x2") = c;
|
|
register long x3 __asm__("x3") = d;
|
|
register long x4 __asm__("x4") = -1;
|
|
register long x5 __asm__("x5") = 0;
|
|
__asm__ volatile("svc #0" : "+r"(x0) : "r"(x8), "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5) : "memory");
|
|
return x0;
|
|
}
|
|
|
|
static void sync_code(char *b, char *e) {
|
|
for (char *p = b; p < e; p += 4) __asm__ volatile("dc cvau, %0" : : "r"(p) : "memory");
|
|
__asm__ volatile("dsb ish" : : : "memory");
|
|
for (char *p = b; p < e; p += 4) __asm__ volatile("ic ivau, %0" : : "r"(p) : "memory");
|
|
__asm__ volatile("dsb ish\n\tisb" : : : "memory");
|
|
}
|
|
|
|
static size_t len_of(const char *s) {
|
|
size_t n = 0;
|
|
while (s[n]) n++;
|
|
return n;
|
|
}
|
|
|
|
static int same(const char *a, const char *b, size_t n) {
|
|
for (size_t i = 0; i < n; i++)
|
|
if (a[i] != b[i]) return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void copy(char *d, const char *s, size_t n) {
|
|
for (size_t i = 0; i < n; i++) d[i] = s[i];
|
|
}
|
|
|
|
static int under(const char *path, const char *dir, size_t dlen) {
|
|
return same(path, dir, dlen) && (path[dlen] == 0 || path[dlen] == '/');
|
|
}
|
|
|
|
|
|
static char env[65536], tracer_env[32768];
|
|
static size_t env_len;
|
|
static const char *root;
|
|
static size_t rootlen;
|
|
static char root_canon[PATH];
|
|
struct bind { const char *host; size_t hlen; const char *guest; size_t glen; int state; char canon[PATH]; };
|
|
static struct bind binds[MAX_BINDS];
|
|
static int nbinds;
|
|
static int ready;
|
|
|
|
static long slurp(const char *path, char *buf, size_t size) {
|
|
long fd = sc(SYS_openat, AT_FDCWD, (long)path, O_RDONLY | O_CLOEXEC, 0);
|
|
if (fd < 0) return -1;
|
|
size_t have = 0;
|
|
for (long r; have < size - 1 && (r = sc(SYS_read, fd, (long)buf + have, size - 1 - have, 0)) > 0;) have += r;
|
|
sc(SYS_close, fd, 0, 0, 0);
|
|
buf[have] = 0;
|
|
return (long)have;
|
|
}
|
|
|
|
static const char *var(const char *buf, size_t len, const char *name) {
|
|
size_t n = len_of(name);
|
|
for (size_t i = 0; i < len; i += len_of(buf + i) + 1)
|
|
if (same(buf + i, name, n) && buf[i + n] == '=') return buf + i + n + 1;
|
|
return NULL;
|
|
}
|
|
|
|
static int canonical(const char *path, char *out) {
|
|
long fd = sc(SYS_openat, AT_FDCWD, (long)path, O_PATH | O_CLOEXEC, 0);
|
|
if (fd < 0) return 0;
|
|
char link[40] = "/proc/self/fd/";
|
|
char digits[20];
|
|
int nd = 0;
|
|
for (long v = fd; nd == 0 || v; v /= 10) digits[nd++] = '0' + v % 10;
|
|
for (int i = 0; i < nd; i++) link[14 + i] = digits[nd - 1 - i];
|
|
link[14 + nd] = 0;
|
|
long n = sc(SYS_readlinkat, AT_FDCWD, (long)link, (long)out, PATH - 1);
|
|
sc(SYS_close, fd, 0, 0, 0);
|
|
if (n <= 0 || out[0] != '/') return 0;
|
|
out[n] = 0;
|
|
return 1;
|
|
}
|
|
|
|
static int traced_by_match(const char *key) {
|
|
char status[4096], path[48] = "/proc/";
|
|
long n = slurp("/proc/self/status", status, sizeof status);
|
|
const char *line = NULL;
|
|
for (long i = 0; n > 0 && i < n; i++)
|
|
if (status[i] == '\n' && same(status + i + 1, "TracerPid:", 10)) { line = status + i + 11; break; }
|
|
if (!line) return 0;
|
|
while (*line == ' ' || *line == '\t') line++;
|
|
size_t d = 0;
|
|
while (line[d] >= '0' && line[d] <= '9' && d < 16) d++;
|
|
if (d == 0 || (d == 1 && line[0] == '0')) return 0;
|
|
copy(path + 6, line, d);
|
|
copy(path + 6 + d, "/environ", 9);
|
|
long have = slurp(path, tracer_env, sizeof tracer_env);
|
|
if (have <= 0 || var(tracer_env, have, "PROOT_NO_SECCOMP")) return 0;
|
|
const char *theirs = var(tracer_env, have, "PROOT_FASTPATH");
|
|
return theirs && len_of(theirs) == len_of(key) && same(theirs, key, len_of(key));
|
|
}
|
|
|
|
static void setup(void) {
|
|
long p = raw(SYS_mmap, FP_STUB_ADDR, FP_STUB_SIZE, PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE);
|
|
if (p != (long)FP_STUB_ADDR) {
|
|
if (p > 0) raw(SYS_munmap, p, FP_STUB_SIZE, 0, 0);
|
|
return;
|
|
}
|
|
static const uint32_t code[] = {
|
|
0xAA0003E8, 0xAA0103E0, 0xAA0203E1, 0xAA0303E2, 0xAA0403E3, 0xAA0503E4, 0xAA0603E5,
|
|
0xD4000001, 0xD65F03C0,
|
|
};
|
|
copy((char *)p, (const char *)code, sizeof code);
|
|
sync_code((char *)p, (char *)p + sizeof code);
|
|
if (raw(SYS_mprotect, FP_STUB_ADDR, FP_STUB_SIZE, PROT_READ | PROT_EXEC, 0) != 0) {
|
|
raw(SYS_munmap, FP_STUB_ADDR, FP_STUB_SIZE, 0, 0);
|
|
return;
|
|
}
|
|
sys = (stub_fn)FP_STUB_ADDR;
|
|
long n = slurp("/proc/self/environ", env, sizeof env);
|
|
if (n <= 0 || (size_t)n >= sizeof env - 1) return;
|
|
env_len = n;
|
|
const char *key = var(env, env_len, "PROOT_FP_KEY"), *b = var(env, env_len, "PROOT_FP_BINDS");
|
|
root = var(env, env_len, "PROOT_FP_ROOT");
|
|
if (!key || !*key || !root || !*root || var(env, env_len, "PROOT_FP_OFF") || !traced_by_match(key)) return;
|
|
if (!canonical(root, root_canon)) return;
|
|
root = root_canon;
|
|
rootlen = len_of(root);
|
|
if (rootlen <= 1) return;
|
|
for (char *s = (char *)b; s && *s;) {
|
|
if (nbinds == MAX_BINDS) return;
|
|
char *end = s;
|
|
while (*end && *end != '|') end++;
|
|
char *colon = s;
|
|
while (colon < end && *colon != ':') colon++;
|
|
struct bind *bd = &binds[nbinds];
|
|
bd->host = s;
|
|
bd->hlen = colon - s;
|
|
bd->guest = colon < end ? colon + 1 : s;
|
|
bd->glen = colon < end ? (size_t)(end - colon - 1) : bd->hlen;
|
|
if (bd->guest[0] == '/' && bd->glen > 1 && bd->hlen > 0) nbinds++;
|
|
int last = *end == 0;
|
|
*end = 0;
|
|
if (colon < end) *colon = 0;
|
|
if (last) break;
|
|
s = end + 1;
|
|
}
|
|
ready = 1;
|
|
}
|
|
|
|
|
|
static const char *bind_host(struct bind *bd) {
|
|
if (!bd->state) {
|
|
bd->state = under(bd->host, "/proc/self", 10) || !canonical(bd->host, bd->canon) ? 2 : 1;
|
|
}
|
|
return bd->state == 1 ? bd->canon : NULL;
|
|
}
|
|
|
|
static long long now(void) {
|
|
struct timespec ts;
|
|
if (sc(SYS_clock_gettime, CLOCK_MONOTONIC_COARSE, (long)&ts, 0, 0) != 0) return 0;
|
|
return ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
|
}
|
|
|
|
static int to_host(const char *g, char *host, int *bound) {
|
|
size_t n = len_of(g);
|
|
struct bind *bd = NULL;
|
|
for (int i = 0; i < nbinds; i++)
|
|
if (under(g, binds[i].guest, binds[i].glen) && (!bd || binds[i].glen >= bd->glen)) bd = &binds[i];
|
|
const char *base = root, *rest = g;
|
|
if (bd) {
|
|
base = bind_host(bd);
|
|
if (!base) return 0;
|
|
rest = g + bd->glen;
|
|
} else if (n == 1) {
|
|
rest = "";
|
|
}
|
|
size_t blen = len_of(base), rlen = len_of(rest);
|
|
if (blen + rlen >= PATH) return 0;
|
|
copy(host, base, blen);
|
|
copy(host + blen, rest, rlen + 1);
|
|
*bound = bd != NULL;
|
|
return 1;
|
|
}
|
|
|
|
static int special(const char *g) {
|
|
return under(g, "/proc", 5) || under(g, "/dev", 4) || under(g, "/sys", 4);
|
|
}
|
|
|
|
static int above_binding(const char *g) {
|
|
size_t n = len_of(g);
|
|
for (int i = 0; i < nbinds; i++)
|
|
if (binds[i].glen > n && under(binds[i].guest, g, n)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int canonical_dir(const char *g, char *out) {
|
|
char path[PATH], next[PATH], host[PATH], text[PATH];
|
|
char st_buf[256] __attribute__((aligned(16)));
|
|
struct stat *st = (struct stat *)st_buf;
|
|
size_t glen = len_of(g), olen = 0;
|
|
int links = 0, bound;
|
|
if (glen >= PATH) return 0;
|
|
copy(path, g, glen + 1);
|
|
out[0] = '/';
|
|
out[1] = 0;
|
|
for (char *c = path;;) {
|
|
while (*c == '/') c++;
|
|
if (!*c) break;
|
|
char *e = c;
|
|
while (*e && *e != '/') e++;
|
|
size_t n = e - c;
|
|
if (n == 1 && c[0] == '.') { c = e; continue; }
|
|
if (n == 2 && c[0] == '.' && c[1] == '.') {
|
|
while (olen > 0 && out[olen] != '/') olen--;
|
|
out[olen ? olen : 1] = 0;
|
|
c = e;
|
|
continue;
|
|
}
|
|
if (olen + n + 2 >= PATH) return 0;
|
|
out[olen] = '/';
|
|
copy(out + olen + 1, c, n);
|
|
out[olen + 1 + n] = 0;
|
|
if (!to_host(out, host, &bound)) return 0;
|
|
long r = sc(SYS_newfstatat, AT_FDCWD, (long)host, (long)st, AT_SYMLINK_NOFOLLOW);
|
|
if (r == -ENOENT && !special(out) && !above_binding(out)) return -1;
|
|
if (r != 0) return 0;
|
|
if (S_ISDIR(st->st_mode)) {
|
|
olen += 1 + n;
|
|
c = e;
|
|
continue;
|
|
}
|
|
if (!S_ISLNK(st->st_mode) || bound || ++links > 16) return 0;
|
|
long t = sc(SYS_readlinkat, AT_FDCWD, (long)host, (long)text, PATH - 1);
|
|
if (t <= 0 || t >= PATH - 1) return 0;
|
|
text[t] = 0;
|
|
const char *guest_text = text;
|
|
if (under(text, root, rootlen)) guest_text = text[rootlen] ? text + rootlen : "/";
|
|
size_t tl = len_of(guest_text), rest = len_of(e);
|
|
if (tl + rest + 1 >= PATH) return 0;
|
|
copy(next, guest_text, tl);
|
|
copy(next + tl, e, rest + 1);
|
|
copy(path, next, tl + rest + 1);
|
|
c = path;
|
|
if (guest_text[0] == '/') olen = 0;
|
|
out[olen ? olen : 1] = 0;
|
|
}
|
|
if (olen == 0) out[1] = 0;
|
|
return 1;
|
|
}
|
|
|
|
static struct { char dir[PATH]; char canon[PATH]; long long at; } known[VERIFIED];
|
|
static int next_slot;
|
|
|
|
static int known_dir(const char *g, char *out) {
|
|
size_t n = len_of(g);
|
|
long long t = now();
|
|
for (int i = 0; i < VERIFIED; i++)
|
|
if (known[i].at && t - known[i].at < TTL_NS && same(known[i].dir, g, n + 1)) {
|
|
copy(out, known[i].canon, len_of(known[i].canon) + 1);
|
|
return 1;
|
|
}
|
|
int r = canonical_dir(g, out);
|
|
if (r != 1) return r;
|
|
copy(known[next_slot].dir, g, n + 1);
|
|
copy(known[next_slot].canon, out, len_of(out) + 1);
|
|
known[next_slot].at = t;
|
|
next_slot = (next_slot + 1) % VERIFIED;
|
|
return 1;
|
|
}
|
|
|
|
static int missing(const char *g) {
|
|
char dir[PATH], cand[PATH], host[PATH];
|
|
size_t n = len_of(g);
|
|
if (g[0] != '/' || n < 2 || n >= PATH - 1) return 0;
|
|
size_t slash = n;
|
|
while (slash > 0 && g[slash - 1] != '/') slash--;
|
|
const char *name = g + slash;
|
|
size_t nl = n - slash;
|
|
if (nl == 0 || (nl == 1 && name[0] == '.') || (nl == 2 && name[0] == '.' && name[1] == '.')) return 0;
|
|
copy(dir, g, slash);
|
|
dir[slash > 1 ? slash - 1 : 1] = 0;
|
|
int known_as = known_dir(dir, cand);
|
|
if (known_as != 1) return known_as < 0;
|
|
size_t cl = len_of(cand);
|
|
if (cl + nl + 2 >= PATH) return 0;
|
|
if (cl > 1) cand[cl++] = '/';
|
|
copy(cand + cl, name, nl + 1);
|
|
int bound;
|
|
if (special(cand) || above_binding(cand) || !to_host(cand, host, &bound))
|
|
return 0;
|
|
char st[256] __attribute__((aligned(16)));
|
|
return sc(SYS_newfstatat, AT_FDCWD, (long)host, (long)st, AT_SYMLINK_NOFOLLOW) == -ENOENT;
|
|
}
|
|
|
|
unsigned int la_version(unsigned int version) {
|
|
(void)version;
|
|
setup();
|
|
return LAV_CURRENT;
|
|
}
|
|
|
|
static int tried;
|
|
|
|
char *la_objsearch(const char *name, uintptr_t *cookie, unsigned int flag) {
|
|
(void)cookie;
|
|
if (flag == LA_SER_ORIG) {
|
|
tried = 0;
|
|
return (char *)name;
|
|
}
|
|
if (!ready || !tried || flag == LA_SER_DEFAULT || !missing(name)) {
|
|
tried = 1;
|
|
return (char *)name;
|
|
}
|
|
return NULL;
|
|
}
|