#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #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; }