#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FP_STUB_ADDR 0xffff00000UL #define FP_STUB_SIZE 4096UL #define MAX_BINDS 256 #define FP_SLOW (-100000L) #define MAX_LINKS 16 typedef long (*stub_fn)(long nr, long a, long b, long c, long d, long e, long f); static stub_fn fp_sys; static int fp_on; struct bind { char *host; size_t hlen; char *guest; size_t glen; int canon; int dropped; }; static struct bind binds[MAX_BINDS]; static int nbinds; static char root[PATH_MAX]; static size_t rootlen; static long long ttl_ns = 2000000000LL; static unsigned long hits, misses; static long sc(long nr, long a, long b, long c, long d, long e) { return fp_sys(nr, a, b, c, d, e, 0); } static void fp_init(void); static pthread_once_t init_once = PTHREAD_ONCE_INIT; static inline int fp_ready(void) { pthread_once(&init_once, fp_init); return fp_on; } static int under(const char *path, const char *dir, size_t dlen) { if (dlen == 1 && dir[0] == '/') return path[0] == '/'; return strncmp(path, dir, dlen) == 0 && (path[dlen] == 0 || path[dlen] == '/'); } static void stats(void) { char line[160]; int n = snprintf(line, sizeof line, "fastpath[%d] %s: %lu fast, %lu to proot\n", getpid(), program_invocation_short_name, hits, misses); if (write(2, line, n) < 0) {} } static const char *miss_log; static long fp_open(int dirfd, const char *path, int flags, mode_t mode); static void missed(const char *call, int dirfd, const char *path) { misses++; if (!miss_log) return; char line[2 * PATH_MAX + 128], base[PATH_MAX] = ""; if (path && path[0] != '/' && dirfd != AT_FDCWD) { char link[40]; snprintf(link, sizeof link, "/proc/self/fd/%d", dirfd); long n = sc(SYS_readlinkat, AT_FDCWD, (long)link, (long)base, sizeof base - 1, 0); base[n > 0 ? n : 0] = 0; } int n = snprintf(line, sizeof line, "%d %s %s %s%s%s\n", getpid(), program_invocation_short_name, call, base, base[0] ? "/" : "", path ? path : "(null)"); long fd = fp_open(AT_FDCWD, miss_log, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0644); if (fd < 0) return; sc(SYS_write, fd, (long)line, n < (int)sizeof line ? n : (int)sizeof line - 1, 0, 0); sc(SYS_close, fd, 0, 0, 0, 0); } static long canonical(char *path, size_t size) { long fd = sc(SYS_openat, AT_FDCWD, (long)path, O_PATH | O_CLOEXEC, 0, 0); if (fd < 0) return fd; char link[40], out[PATH_MAX]; snprintf(link, sizeof link, "/proc/self/fd/%ld", fd); long n = sc(SYS_readlinkat, AT_FDCWD, (long)link, (long)out, sizeof out - 1, 0); sc(SYS_close, fd, 0, 0, 0, 0); if (n <= 0 || out[0] != '/') return -ENOENT; if ((size_t)n >= size) return -ENAMETOOLONG; out[n] = 0; memcpy(path, out, n + 1); return 0; } static long tracer; static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t bind_lock; static pthread_mutex_t long_lock; static void after_fork(void) { pthread_mutex_init(&cache_lock, NULL); pthread_mutex_init(&bind_lock, NULL); pthread_mutex_init(&long_lock, NULL); } static int traced_by_match(const char *key) { static char buf[32768]; char path[48], want[160]; long fd = sc(SYS_openat, AT_FDCWD, (long)"/proc/self/status", O_RDONLY | O_CLOEXEC, 0, 0); if (fd < 0) return 0; long n = sc(SYS_read, fd, (long)buf, 4095, 0, 0); sc(SYS_close, fd, 0, 0, 0, 0); if (n <= 0) return 0; buf[n] = 0; const char *line = strstr(buf, "\nTracerPid:"); if (!line) return 0; tracer = strtol(line + 11, NULL, 10); if (tracer <= 0) return 0; snprintf(path, sizeof path, "/proc/%ld/environ", tracer); if (snprintf(want, sizeof want, "PROOT_FASTPATH=%s", key) >= (int)sizeof want) return 0; fd = sc(SYS_openat, AT_FDCWD, (long)path, O_RDONLY | O_CLOEXEC, 0, 0); if (fd < 0) return 0; size_t have = 0; for (long r; have < sizeof buf - 1 && (r = sc(SYS_read, fd, (long)buf + have, sizeof buf - 1 - have, 0, 0)) > 0;) have += r; sc(SYS_close, fd, 0, 0, 0, 0); buf[have] = 0; int found = 0; for (size_t i = 0; i < have; i += strlen(buf + i) + 1) { if (strcmp(buf + i, want) == 0) found = 1; if (strncmp(buf + i, "PROOT_NO_SECCOMP=", 17) == 0) return 0; } return found; } static long init_sys4(long nr, long a, long b, long c, long d) { static long (*real_syscall)(long, ...); if (!real_syscall) real_syscall = (long (*)(long, ...))dlsym(RTLD_NEXT, "syscall"); return real_syscall(nr, a, b, c, d); } static long init_sys(long nr, long a, long b, long c) { return init_sys4(nr, a, b, c, 0); } static int program_32bit(const char *prog, int scripts) { char head[256]; long fd = init_sys(SYS_openat, AT_FDCWD, (long)prog, O_RDONLY | O_CLOEXEC); if (fd < 0) return 1; long n = init_sys(SYS_read, fd, (long)head, sizeof head - 1); init_sys(SYS_close, fd, 0, 0); if (n < 0) return 1; head[n] = 0; if (n >= 2 && head[0] == '#' && head[1] == '!') { if (scripts <= 0) return 1; char *p = head + 2; while (*p == ' ' || *p == '\t') p++; char *e = p; while (*e && *e != ' ' && *e != '\t' && *e != '\n' && *e != '\r') e++; if (e == p || (!*e && n == (long)sizeof head - 1)) return 1; *e = 0; return program_32bit(p, scripts - 1); } return !(n >= 5 && memcmp(head, "\x7f" "ELF", 4) == 0 && head[4] == 2); } static int fex_runs_32bit(void) { if (strcmp(program_invocation_short_name, "FEX") != 0 && strcmp(program_invocation_short_name, "FEXInterpreter") != 0) return 0; char args[PATH_MAX + 64]; long fd = init_sys(SYS_openat, AT_FDCWD, (long)"/proc/self/cmdline", O_RDONLY | O_CLOEXEC); if (fd < 0) return 1; long n = init_sys(SYS_read, fd, (long)args, sizeof args - 1); init_sys(SYS_close, fd, 0, 0); if (n <= 0) return 1; args[n] = 0; const char *prog = args + strlen(args) + 1; if (prog >= args + n || !*prog) return 1; return program_32bit(prog, 4); } static int in_tracer(struct bind *bd) { char h[PATH_MAX]; if (snprintf(h, sizeof h, "/proc/%ld%s", tracer, bd->host + 10) >= (int)sizeof h) return 0; long e = canonical(h, sizeof h); if (e == -EACCES || e == -EPERM) return 0; memcpy(bd->host, h, strlen(h) + 1); bd->hlen = strlen(bd->host); bd->dropped = e < 0; bd->canon = 1; return 1; } static void fp_init(void) { const char *r = getenv("PROOT_FP_ROOT"), *b = getenv("PROOT_FP_BINDS"), *t = getenv("PROOT_FP_TTL_MS"); const char *key = getenv("PROOT_FP_KEY"); if (!r || !*r || !key || !*key || getenv("PROOT_FP_OFF")) return; if (fex_runs_32bit()) return; void *p = mmap((void *)FP_STUB_ADDR, FP_STUB_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); if (p != (void *)FP_STUB_ADDR) { if (p != MAP_FAILED) { munmap(p, FP_STUB_SIZE); return; } if (errno != EEXIST) return; } else { static const uint32_t code[] = { 0xAA0003E8, 0xAA0103E0, 0xAA0203E1, 0xAA0303E2, 0xAA0403E3, 0xAA0503E4, 0xAA0603E5, 0xD4000001, 0xD65F03C0, }; memcpy(p, code, sizeof code); __builtin___clear_cache((char *)p, (char *)p + sizeof code); if (mprotect(p, FP_STUB_SIZE, PROT_READ | PROT_EXEC) != 0) { munmap(p, FP_STUB_SIZE); return; } } fp_sys = (stub_fn)FP_STUB_ADDR; if (!traced_by_match(key)) { fp_sys = NULL; return; } if (t) ttl_ns = atoll(t) * 1000000LL; snprintf(root, sizeof root, "%s", r); canonical(root, sizeof root); rootlen = strlen(root); if (rootlen <= 1) { fp_sys = NULL; return; } const char *s = b; int unheld = 0; for (; s && *s && nbinds < MAX_BINDS;) { const char *end = strchr(s, '|'); size_t len = end ? (size_t)(end - s) : strlen(s); char spec[2 * PATH_MAX]; if (len > 0 && len < sizeof spec) { memcpy(spec, s, len); spec[len] = 0; char *colon = strchr(spec, ':'); if (colon) *colon = 0; struct bind *bd = &binds[nbinds]; static char hosts[MAX_BINDS][PATH_MAX], guests[MAX_BINDS][512]; const char *g = colon ? colon + 1 : spec; if (strlen(g) >= sizeof guests[0]) { unheld = 1; break; } bd->host = hosts[nbinds]; bd->guest = guests[nbinds]; snprintf(bd->guest, sizeof guests[0], "%s", g); snprintf(bd->host, PATH_MAX, "%.*s", PATH_MAX - 1, spec); bd->hlen = strlen(bd->host); bd->glen = strlen(bd->guest); if (bd->guest[0] == '/' && bd->glen > 1 && bd->hlen > 0) nbinds++; } else if (len > 0) { unheld = 1; break; } s = end ? end + 1 : NULL; } if (unheld || (s && *s)) { fp_sys = NULL; return; } for (int i = 0; i < nbinds; i++) if (under(binds[i].host, "/proc/self", 10) && !in_tracer(&binds[i])) { fp_sys = NULL; return; } pthread_atfork(NULL, NULL, after_fork); fp_on = 1; if (getenv("PROOT_FP_STATS")) atexit(stats); miss_log = getenv("PROOT_FP_LOG"); if (miss_log && miss_log[0] != '/') miss_log = NULL; } static pthread_mutex_t bind_lock = PTHREAD_MUTEX_INITIALIZER; static int held(struct bind *bd) { if (!__atomic_load_n(&bd->canon, __ATOMIC_ACQUIRE)) { if (pthread_mutex_trylock(&bind_lock) != 0) return -1; if (!bd->canon) { bd->dropped = canonical(bd->host, PATH_MAX) < 0; bd->hlen = strlen(bd->host); __atomic_store_n(&bd->canon, 1, __ATOMIC_RELEASE); } pthread_mutex_unlock(&bind_lock); } return !bd->dropped; } static int reserved(const char *g) { return under(g, "/dev/fd", 7) || under(g, "/dev/stdin", 10) || under(g, "/dev/stdout", 11) || under(g, "/dev/stderr", 11); } static int bind_of(const char *g, struct bind **out) { 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)) continue; int h = held(&binds[i]); if (h < 0) return -1; if (h) bd = &binds[i]; } *out = bd; return 0; } static int to_host(const char *g, char *host, struct bind **through) { struct bind *bd; if (bind_of(g, &bd) < 0) return -1; if (through) *through = bd; int n = bd ? snprintf(host, PATH_MAX, "%s%s", bd->host, g + bd->glen) : snprintf(host, PATH_MAX, "%s%s", root, strcmp(g, "/") ? g : ""); return n < PATH_MAX ? 0 : -1; } static int glue_above_binding(const char *g, size_t len) { int glue = 0; for (int i = 0; i < nbinds && glue == 0; i++) if (binds[i].glen > len && under(binds[i].guest, g, len)) glue = held(&binds[i]); return glue; } static int guest_name(const char *h, int k, char *g) { char back[PATH_MAX]; size_t deepest = 0; if (under(h, root, rootlen)) { if (k > 0 || snprintf(g, PATH_MAX, "%s", h[rootlen] ? h + rootlen : "/") >= PATH_MAX) return -1; return reserved(g) || to_host(g, back, NULL) < 0 || strcmp(back, h) != 0 ? -1 : 0; } for (int i = 0; i < nbinds; i++) { int in = held(&binds[i]); if (in < 0) return -1; if (in && under(h, binds[i].host, binds[i].hlen) && binds[i].hlen > deepest) deepest = binds[i].hlen; } for (int i = 0; deepest && i < nbinds; i++) { if (binds[i].dropped || binds[i].hlen != deepest || !under(h, binds[i].host, binds[i].hlen)) continue; if (k-- > 0) continue; if (snprintf(g, PATH_MAX, "%s%s", binds[i].guest, h + binds[i].hlen) >= PATH_MAX) return -1; return reserved(g) || to_host(g, back, NULL) < 0 || strcmp(back, h) != 0 ? -1 : 0; } return -1; } static int to_guest(const char *h, char *g, size_t size) { char other[PATH_MAX]; if (size < PATH_MAX || guest_name(h, 0, g) < 0) return -1; for (int k = 1; guest_name(h, k, other) == 0; k++) if (strcmp(other, g) != 0) return -1; return 0; } static int host_order[MAX_BINDS + 1], norder, ordered; static int contains(const char *a, const char *b) { size_t la = strlen(a); return strlen(b) > la && under(b, a, la); } static const char *side_of(int i, int host) { return host ? (i < 0 ? root : binds[i].host) : (i < 0 ? "/" : binds[i].guest); } static int insort(int *list, int m, int i, int host) { const char *p = side_of(i, host); int prev = -1; for (int j = 0; j < m; j++) { const char *q = side_of(list[j], host); if (!strcmp(p, q) && !host) { list[j] = i; return m; } if (!strcmp(p, q) || contains(p, q)) prev = j; } memmove(list + prev + 2, list + prev + 1, (m - prev - 1) * sizeof *list); list[prev + 1] = i; return m + 1; } static int order_binds(void) { if (__atomic_load_n(&ordered, __ATOMIC_ACQUIRE)) return 0; for (int i = 0; i < nbinds; i++) if (held(&binds[i]) < 0) return -1; if (pthread_mutex_trylock(&bind_lock) != 0) return -1; if (!ordered) { int pending[MAX_BINDS + 1], m = 1; pending[0] = -1; for (int i = 0; i < nbinds; i++) if (!binds[i].dropped) m = insort(pending, m, i, 0); norder = 0; for (int j = m - 1; j >= 0; j--) norder = insort(host_order, norder, pending[j], 1); __atomic_store_n(&ordered, 1, __ATOMIC_RELEASE); } pthread_mutex_unlock(&bind_lock); return 0; } static int host_bind(const char *h) { if (order_binds() < 0) return -2; for (int j = 0; j < norder; j++) if (host_order[j] >= 0 && under(h, binds[host_order[j]].host, binds[host_order[j]].hlen)) return host_order[j]; return -1; } static int guest_text(const char *h, char *g) { if (h[0] == '/' && under(h, root, rootlen)) return to_guest(h, g, PATH_MAX); int i = h[0] == '/' ? host_bind(h) : -1; if (i == -2) return -1; if (i >= 0) return snprintf(g, PATH_MAX, "%s%s", binds[i].guest, h + binds[i].hlen) >= PATH_MAX || reserved(g) ? -1 : 0; memcpy(g, h, strlen(h) + 1); return 0; } static long detranslated(char *text, const char *at) { if (text[0] != '/') return 0; if (!under(at, root, rootlen) && !under(text, root, rootlen)) { int own = host_bind(at), to = host_bind(text); if (to == -1) return 0; if (own == -2 || to == -2) return FP_SLOW; if (own >= 0 && to >= 0 && strcmp(binds[own].host, binds[to].host) == 0) { const struct bind *bd = &binds[to]; if (strcmp(bd->host, bd->guest) == 0) return 0; char g[PATH_MAX]; if (snprintf(g, sizeof g, "%s%s", bd->guest, text + bd->hlen) >= (int)sizeof g) return FP_SLOW; strcpy(text, g); return 1; } } if (!under(text, root, rootlen)) return 0; if (!text[rootlen]) strcpy(text, "/"); else memmove(text, text + rootlen, strlen(text + rootlen) + 1); return 1; } #define SLOTS 512 #define SLOT_PATH 256 struct slot { long long stamp; unsigned hash; unsigned gen; char literal[SLOT_PATH]; char guest[SLOT_PATH]; }; static struct slot cache[SLOTS]; static unsigned generation; static long long now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); return ts.tv_sec * 1000000000LL + ts.tv_nsec; } static unsigned hash_of(const char *s) { unsigned h = 2166136261u; while (*s) h = (h ^ (unsigned char)*s++) * 16777619u; return h; } static void forget(void) { __atomic_add_fetch(&generation, 1, __ATOMIC_RELEASE); } static int cached(const char *literal, unsigned h, char *guest) { if (ttl_ns <= 0 || pthread_mutex_trylock(&cache_lock) != 0) return 0; struct slot *e = &cache[h % SLOTS]; int hit = e->stamp && e->hash == h && e->gen == __atomic_load_n(&generation, __ATOMIC_ACQUIRE) && now_ns() - e->stamp < ttl_ns && strcmp(e->literal, literal) == 0; if (hit) memcpy(guest, e->guest, strlen(e->guest) + 1); pthread_mutex_unlock(&cache_lock); return hit; } static void remember(const char *literal, unsigned h, const char *guest) { if (ttl_ns <= 0 || strlen(literal) >= SLOT_PATH || strlen(guest) >= SLOT_PATH) return; unsigned gen = __atomic_load_n(&generation, __ATOMIC_ACQUIRE); if (pthread_mutex_trylock(&cache_lock) != 0) return; struct slot *e = &cache[h % SLOTS]; e->hash = h; e->gen = gen; strcpy(e->literal, literal); strcpy(e->guest, guest); e->stamp = now_ns(); pthread_mutex_unlock(&cache_lock); } static int binding_below(const char *g) { size_t len = strlen(g); for (int i = 0; i < nbinds; i++) if (binds[i].glen > len && under(binds[i].guest, g, len)) return 1; return 0; } struct res { char guest[PATH_MAX]; char host[PATH_MAX]; struct stat st; int exists; int named; int libc; int moving; int magic; }; static int gone(const char *h, size_t n) { return n > 10 && strcmp(h + n - 10, " (deleted)") == 0; } static long own_cwd(char *g) { char h[PATH_MAX]; long n = sc(SYS_readlinkat, AT_FDCWD, (long)"/proc/self/cwd", (long)h, sizeof h - 1, 0); if (n <= 0 || h[0] != '/') return FP_SLOW; h[n] = 0; if (gone(h, n) || to_guest(h, g, PATH_MAX) < 0) return FP_SLOW; return (long)strlen(g); } static char exe_text[PATH_MAX]; static int exe_len; static long own_exe(char *g) { int n = __atomic_load_n(&exe_len, __ATOMIC_ACQUIRE); if (n > 0) { memcpy(g, exe_text, n + 1); return n; } long t = init_sys4(SYS_readlinkat, AT_FDCWD, (long)"/proc/self/exe", (long)g, PATH_MAX - 1); if (t <= 0 || t >= PATH_MAX - 1) return FP_SLOW; g[t] = 0; static pthread_mutex_t exe_lock = PTHREAD_MUTEX_INITIALIZER; if (pthread_mutex_trylock(&exe_lock) == 0) { if (!exe_len) { memcpy(exe_text, g, t + 1); __atomic_store_n(&exe_len, (int)t, __ATOMIC_RELEASE); } pthread_mutex_unlock(&exe_lock); } return t; } static long proc_readlink(const struct res *r, char *text, size_t size, int *changed) { const char *name = strrchr(r->guest, '/') + 1; size_t blen = name - 1 - r->guest; char base[32]; int pid = blen > 6 ? atoi(r->guest + 6) : 0; int bn = snprintf(base, sizeof base, "/proc/%d", pid); if (pid != 0 && (size_t)bn == blen && strncmp(r->guest, base, blen) == 0 && (!strcmp(name, "exe") || !strcmp(name, "cwd") || !strcmp(name, "root"))) { if (pid != getpid() || !strcmp(name, "root")) return FP_SLOW; *changed = 1; return name[0] == 'e' ? own_exe(text) : own_cwd(text); } char h[PATH_MAX]; long t = sc(SYS_readlinkat, AT_FDCWD, (long)r->host, (long)h, sizeof h - 1, 0); if (t < 0) return t; if (t >= (long)sizeof h - 1) return FP_SLOW; if (size && (size_t)t > size) t = size; h[t] = 0; if (guest_text(h, text) < 0) return FP_SLOW; *changed = strcmp(h, text) != 0; return (long)strlen(text); } static int fd_link(const char *g) { const char *p = g + 6; if (strncmp(g, "/proc/", 6) != 0 || *p < '1' || *p > '9') return 0; while (*p >= '0' && *p <= '9') p++; if (strncmp(p, "/fd/", 4) != 0 || p[4] < '0' || p[4] > '9') return 0; return 1; } static long proc_link(struct res *r, char *text, int final) { int changed; if (!under(r->host, "/proc", 5)) return FP_SLOW; r->moving = 1; if (!strcmp(r->guest, "/proc/self") || !strcmp(r->guest, "/proc/thread-self")) { long t = sc(SYS_readlinkat, AT_FDCWD, (long)r->host, (long)text, PATH_MAX - 1, 0); if (t <= 0 || t >= PATH_MAX - 1) return FP_SLOW; text[t] = 0; return t; } if (!r->libc && final && fd_link(r->guest)) return 0; long t = proc_readlink(r, text, 0, &changed); return t <= 0 ? FP_SLOW : t; } static int pop(char *g) { char *slash = strrchr(g, '/'); if (!slash) return -1; if (slash == g) g[1] = 0; else *slash = 0; return 0; } #define UNSEEN 2 #define MAY_BE_UNSEEN 2 #define GLUE 3 static long walk(struct res *r, const char *text, int follow, int last) { char rest[PATH_MAX]; size_t len = strlen(text); int links = 0, stated = 0; if (len >= sizeof rest) return FP_SLOW; memcpy(rest, text, len + 1); char *c = rest; r->exists = 0; for (;;) { while (*c == '/') c++; if (!*c) break; char *e = strchrnul(c, '/'); size_t n = e - c; char *after = e; while (*after == '/') after++; int final = *after == 0; stated = 0; if (n == 1 && c[0] == '.') { c = e; continue; } if (n == 2 && c[0] == '.' && c[1] == '.') { pop(r->guest); c = e; continue; } size_t glen = strlen(r->guest); if (glen + n + 2 >= PATH_MAX) return FP_SLOW; if (glen > 1) r->guest[glen++] = '/'; memcpy(r->guest + glen, c, n); r->guest[glen + n] = 0; if (reserved(r->guest)) return FP_SLOW; struct bind *through; if (to_host(r->guest, r->host, &through) < 0) return FP_SLOW; long s = sc(SYS_newfstatat, AT_FDCWD, (long)r->host, (long)&r->st, AT_SYMLINK_NOFOLLOW, 0); if (s < 0) { int glue = glue_above_binding(r->guest, glen + n); if (glue < 0) return FP_SLOW; if (glue) { if (final) return GLUE; c = e; continue; } if (!final || !last) return r->libc ? s : -ENOENT; return s == -ENOENT ? 1 : UNSEEN; } if (S_ISLNK(r->st.st_mode) && (!final || follow)) { char target[PATH_MAX]; long t; if (++links > MAX_LINKS || binding_below(r->guest)) return FP_SLOW; if (under(r->guest, "/proc", 5)) { t = proc_link(r, target, final && last); if (t == FP_SLOW) return FP_SLOW; if (t == 0) { if (sc(SYS_newfstatat, AT_FDCWD, (long)r->host, (long)&r->st, 0, 0) < 0) return FP_SLOW; r->magic = 1; r->exists = 1; return 0; } } else { t = sc(SYS_readlinkat, AT_FDCWD, (long)r->host, (long)target, sizeof target - 1, 0); if (t <= 0 || t >= (long)sizeof target - 1) return FP_SLOW; target[t] = 0; if (detranslated(target, r->host) == FP_SLOW) return FP_SLOW; t = strlen(target); } if (target[0] == '/') strcpy(r->guest, "/"); else pop(r->guest); size_t tail = strlen(e); if ((size_t)t + 1 + tail >= sizeof rest) return FP_SLOW; memmove(rest + t + 1, e, tail + 1); memcpy(rest, target, t); rest[t] = '/'; c = rest; continue; } if (!final && !S_ISDIR(r->st.st_mode)) return -ENOTDIR; stated = 1; c = e; } if (!stated) { if (to_host(r->guest, r->host, NULL) < 0) return FP_SLOW; long s = sc(SYS_newfstatat, AT_FDCWD, (long)r->host, (long)&r->st, AT_SYMLINK_NOFOLLOW, 0); if (s < 0 && strcmp(r->guest, "/") != 0 && glue_above_binding(r->guest, strlen(r->guest))) return FP_SLOW; if (s < 0) return s == -ENOENT ? FP_SLOW : s; } r->exists = 1; return 0; } static int base_of(int dirfd, char *h) { char link[40]; if (dirfd == AT_FDCWD) snprintf(link, sizeof link, "/proc/self/cwd"); else snprintf(link, sizeof link, "/proc/self/fd/%d", dirfd); long n = sc(SYS_readlinkat, AT_FDCWD, (long)link, (long)h, PATH_MAX - 1, 0); if (n <= 0 || n >= PATH_MAX - 1) return -1; if (h[0] != '/') return dirfd == AT_FDCWD ? -1 : -ENOTDIR; h[n] = 0; return gone(h, n) ? -1 : 0; } static long bound_literal(const char *literal, struct res *r) { struct bind *bd; if (reserved(literal) || bind_of(literal, &bd) < 0 || !bd || bd->glen != strlen(literal) || bd->glen < 2) return FP_SLOW; if (snprintf(r->host, PATH_MAX, "%s", bd->host) >= PATH_MAX) return FP_SLOW; if (sc(SYS_newfstatat, AT_FDCWD, (long)r->host, (long)&r->st, AT_SYMLINK_NOFOLLOW, 0) < 0 || S_ISLNK(r->st.st_mode)) return FP_SLOW; snprintf(r->guest, PATH_MAX, "%s", literal); r->exists = 1; r->moving = 0; r->magic = 0; return 0; } static long resolve_in(const char *base, const char *path, int follow, struct res *r) { char norm[PATH_MAX]; size_t o = 0; int dotdot = 0; size_t plen = strlen(path), end = plen; while (end > 1 && path[end - 1] == '/') end--; int trailing = (plen > 1 && path[plen - 1] == '/') || (end > 0 && path[end - 1] == '.' && (end == 1 || path[end - 2] == '/')); for (int part = base ? 0 : 1; part < 2; part++) { for (const char *c = part ? path : base; *c;) { while (*c == '/') c++; if (!*c) break; const char *e = strchrnul(c, '/'); size_t len = e - c; c = e; if (len == 1 && e[-1] == '.') continue; if (len == 2 && e[-1] == '.' && e[-2] == '.') dotdot = 1; if (o + len + 2 >= sizeof norm) return FP_SLOW; norm[o++] = '/'; memcpy(norm + o, e - len, len); o += len; } } norm[o] = 0; strcpy(r->guest, "/"); r->moving = 0; r->magic = 0; long w; char *slash = strrchr(norm, '/'); if (o == 0 || dotdot || slash == norm) { w = walk(r, o ? norm : "/", follow || trailing, 1); } else { *slash = 0; unsigned h = hash_of(norm); if (!cached(norm, h, r->guest)) { w = walk(r, norm, 1, 0); if (w == FP_SLOW && !trailing) { *slash = '/'; return bound_literal(norm, r); } if (w < 0) return w; if (w != GLUE && !S_ISDIR(r->st.st_mode)) return -ENOTDIR; if (!r->moving) remember(norm, h, r->guest); } *slash = '/'; w = walk(r, slash + 1, follow || trailing, 1); } if (w < 0) return w; if (w == GLUE || (trailing && w != 1 && (w != 0 || !S_ISDIR(r->st.st_mode)))) return FP_SLOW; return w; } static __attribute__((noinline)) long same_from_other_names(const char *h, const char *path, int follow, long rs, const struct res *r) { char base[PATH_MAX]; struct res other; other.libc = r->libc; for (int k = 1; guest_name(h, k, base) == 0; k++) { if (resolve_in(base, path, follow, &other) != rs) return FP_SLOW; if (rs >= 0 && strcmp(other.host, r->host) != 0) return FP_SLOW; } return rs; } static long resolve_as(int dirfd, const char *path, int how, int libc, struct res *r) { int follow = how & 1; long rs; char h[PATH_MAX], base[PATH_MAX]; if (!path || !path[0] || !fp_ready()) return FP_SLOW; r->named = 1; r->libc = libc; if (path[0] == '/') { rs = resolve_in(NULL, path, follow, r); } else { int b = base_of(dirfd, h); if (b == -ENOTDIR) return b; if (b < 0 || guest_name(h, 0, base) < 0) return FP_SLOW; rs = resolve_in(base, path, follow, r); if (rs != FP_SLOW && guest_name(h, 1, base) == 0) { r->named = 0; rs = same_from_other_names(h, path, follow, rs, r); } } return rs == UNSEEN && !(how & MAY_BE_UNSEEN) ? FP_SLOW : rs; } static long resolve(int dirfd, const char *path, int how, struct res *r) { return resolve_as(dirfd, path, how, 0, r); } static int trailing_slash(const char *path) { size_t len = path ? strlen(path) : 0; return len > 1 && path[len - 1] == '/'; } static int ret(long r) { if (r < 0) { errno = (int)-r; return -1; } return (int)r; } #define REAL(name, type) \ static __typeof__(type) real_##name; \ if (!real_##name) real_##name = (type)dlsym(RTLD_NEXT, #name) #define ANSWER(r, dirfd, path, real_call) \ do { long r_ = (r); if (r_ != FP_SLOW) { hits++; return ret(r_); } missed(__func__, dirfd, path); return real_call; } while (0) static long fp_open(int dirfd, const char *path, int flags, mode_t mode) { struct res r; if ((flags & __O_TMPFILE) == __O_TMPFILE) return FP_SLOW; int follow = !(flags & O_NOFOLLOW) && !((flags & O_CREAT) && (flags & O_EXCL)); if ((flags & O_CREAT) && trailing_slash(path)) return FP_SLOW; long rs = resolve(dirfd, path, follow | MAY_BE_UNSEEN, &r); if (rs < 0) return rs; if (rs == 1 && !(flags & O_CREAT)) return -ENOENT; if (rs == UNSEEN) return sc(SYS_openat, AT_FDCWD, (long)r.host, flags, (flags & O_CREAT) ? mode : 0, 0); return sc(SYS_openat, AT_FDCWD, (long)r.host, flags | (follow && !r.magic ? O_NOFOLLOW : 0), (flags & O_CREAT) ? mode : 0, 0); } static int do_openat(int dirfd, const char *path, int flags, mode_t mode, int (*real)(int, const char *, int, ...)) { long r = fp_open(dirfd, path, flags, mode); if (r != FP_SLOW) { hits++; return ret(r); } missed(__func__, dirfd, path); return real(dirfd, path, flags, mode); } int openat(int dirfd, const char *path, int flags, ...) { REAL(openat, int (*)(int, const char *, int, ...)); mode_t mode = 0; if (flags & (O_CREAT | __O_TMPFILE)) { va_list ap; va_start(ap, flags); mode = va_arg(ap, mode_t); va_end(ap); } return do_openat(dirfd, path, flags, mode, real_openat); } int openat64(int dirfd, const char *path, int flags, ...) __attribute__((alias("openat"))); int open(const char *path, int flags, ...) { REAL(openat, int (*)(int, const char *, int, ...)); mode_t mode = 0; if (flags & (O_CREAT | __O_TMPFILE)) { va_list ap; va_start(ap, flags); mode = va_arg(ap, mode_t); va_end(ap); } return do_openat(AT_FDCWD, path, flags, mode, real_openat); } int open64(const char *path, int flags, ...) __attribute__((alias("open"))); int __open_2(const char *path, int flags) { return open(path, flags); } int __open64_2(const char *path, int flags) { return open(path, flags); } int __openat_2(int d, const char *path, int flags) { return openat(d, path, flags); } int __openat64_2(int d, const char *path, int flags) { return openat(d, path, flags); } int creat(const char *path, mode_t mode) { return open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); } int creat64(const char *path, mode_t mode) __attribute__((alias("creat"))); static long fp_stat(int dirfd, const char *path, int flags, struct stat *st) { struct res r; if ((flags & AT_EMPTY_PATH) && path && !path[0] && fp_ready()) return sc(SYS_newfstatat, dirfd, (long)"", (long)st, flags, 0); if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT)) return FP_SLOW; long rs = resolve(dirfd, path, (flags & AT_SYMLINK_NOFOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r); if (rs < 0) return rs; if (rs == 1) return -ENOENT; if (rs == UNSEEN) return sc(SYS_newfstatat, AT_FDCWD, (long)r.host, (long)st, flags, 0); *st = r.st; return 0; } static int stat_common(int dirfd, const char *path, struct stat *st, int flags, int (*real)(int, const char *, struct stat *, int)) { ANSWER(fp_stat(dirfd, path, flags, st), dirfd, path, real(dirfd, path, st, flags)); } int fstatat(int dirfd, const char *path, struct stat *st, int flags) { REAL(fstatat, int (*)(int, const char *, struct stat *, int)); return stat_common(dirfd, path, st, flags, real_fstatat); } int fstatat64(int d, const char *p, struct stat64 *st, int f) { return fstatat(d, p, (struct stat *)st, f); } int stat(const char *path, struct stat *st) { REAL(fstatat, int (*)(int, const char *, struct stat *, int)); return stat_common(AT_FDCWD, path, st, 0, real_fstatat); } int stat64(const char *p, struct stat64 *st) { return stat(p, (struct stat *)st); } int lstat(const char *path, struct stat *st) { REAL(fstatat, int (*)(int, const char *, struct stat *, int)); return stat_common(AT_FDCWD, path, st, AT_SYMLINK_NOFOLLOW, real_fstatat); } int lstat64(const char *p, struct stat64 *st) { return lstat(p, (struct stat *)st); } int fstat(int fd, struct stat *st) { REAL(fstat, int (*)(int, struct stat *)); if (!fp_ready()) return real_fstat(fd, st); hits++; return ret(sc(SYS_fstat, fd, (long)st, 0, 0, 0)); } int fstat64(int fd, struct stat64 *st) { return fstat(fd, (struct stat *)st); } #define FP_STAT_VER 0 static int xstat_common(int ver, int dirfd, const char *path, struct stat *st, int flags) { REAL(fstatat, int (*)(int, const char *, struct stat *, int)); if (ver != FP_STAT_VER) { errno = EINVAL; return -1; } return stat_common(dirfd, path, st, flags, real_fstatat); } int __xstat(int ver, const char *p, struct stat *st) { return xstat_common(ver, AT_FDCWD, p, st, 0); } int __xstat64(int ver, const char *p, struct stat64 *st) { return xstat_common(ver, AT_FDCWD, p, (struct stat *)st, 0); } int __lxstat(int ver, const char *p, struct stat *st) { return xstat_common(ver, AT_FDCWD, p, st, AT_SYMLINK_NOFOLLOW); } int __lxstat64(int ver, const char *p, struct stat64 *st) { return xstat_common(ver, AT_FDCWD, p, (struct stat *)st, AT_SYMLINK_NOFOLLOW); } int __fxstatat(int ver, int d, const char *p, struct stat *st, int f) { return xstat_common(ver, d, p, st, f); } int __fxstatat64(int ver, int d, const char *p, struct stat64 *st, int f) { return xstat_common(ver, d, p, (struct stat *)st, f); } int __fxstat(int ver, int fd, struct stat *st) { if (ver != FP_STAT_VER) { errno = EINVAL; return -1; } return fstat(fd, st); } int __fxstat64(int ver, int fd, struct stat64 *st) { return __fxstat(ver, fd, (struct stat *)st); } #ifndef STATX__RESERVED #define STATX__RESERVED 0x80000000U #endif static int statx_missing; static unsigned int dev_major(unsigned long long dev) { return ((dev >> 8) & 0xfff) | ((unsigned int)(dev >> 32) & ~0xfffU); } static unsigned int dev_minor(unsigned long long dev) { return (dev & 0xff) | ((unsigned int)(dev >> 12) & ~0xffU); } static void fill_statx(struct statx *out, const struct stat *st) { memset(out, 0, sizeof *out); out->stx_mask = STATX_BASIC_STATS; out->stx_blksize = st->st_blksize; out->stx_nlink = st->st_nlink; out->stx_uid = st->st_uid; out->stx_gid = st->st_gid; out->stx_mode = st->st_mode; out->stx_ino = st->st_ino; out->stx_size = st->st_size; out->stx_blocks = st->st_blocks; out->stx_atime.tv_sec = st->st_atim.tv_sec; out->stx_atime.tv_nsec = st->st_atim.tv_nsec; out->stx_ctime.tv_sec = st->st_ctim.tv_sec; out->stx_ctime.tv_nsec = st->st_ctim.tv_nsec; out->stx_mtime.tv_sec = st->st_mtim.tv_sec; out->stx_mtime.tv_nsec = st->st_mtim.tv_nsec; out->stx_rdev_major = dev_major(st->st_rdev); out->stx_rdev_minor = dev_minor(st->st_rdev); out->stx_dev_major = dev_major(st->st_dev); out->stx_dev_minor = dev_minor(st->st_dev); } static long statx_call(int dirfd, const char *path, int flags, unsigned mask, struct statx *out) { struct stat st; long rs; if ((flags & ~(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE)) || (flags & AT_STATX_SYNC_TYPE) == AT_STATX_SYNC_TYPE) return -EINVAL; if (!statx_missing) { rs = sc(SYS_statx, dirfd, (long)path, flags, mask, (long)out); if (rs != -ENOSYS) return rs; statx_missing = 1; } if (flags & AT_STATX_SYNC_TYPE) return -ENOSYS; rs = sc(SYS_newfstatat, dirfd, (long)path, (long)&st, flags & (AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT), 0); if (rs < 0) return rs; fill_statx(out, &st); return 0; } 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) 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); 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))); } static int effective_is_real(void) { return !getauxval(AT_SECURE) && getuid() == geteuid() && getgid() == getegid(); } 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 ? resolve(dirfd, path, (flags & AT_SYMLINK_NOFOLLOW ? 0 : 1) | MAY_BE_UNSEEN, &r) : FP_SLOW; 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); return real_faccessat(dirfd, path, mode, flags); } int faccessat(int dirfd, const char *path, int mode, int flags) { return access_common(dirfd, path, mode, flags); } int access(const char *path, int mode) { return access_common(AT_FDCWD, path, mode, 0); } int eaccess(const char *path, int mode) { return access_common(AT_FDCWD, path, mode, AT_EACCESS); } int euidaccess(const char *path, int mode) __attribute__((alias("eaccess"))); static long link_text(const struct res *r, char *text, size_t size, int *changed) { int in_proc = under(r->guest, "/proc", 5); *changed = 0; if (in_proc != under(r->host, "/proc", 5)) return FP_SLOW; if (in_proc) return proc_readlink(r, text, size, changed); long t = sc(SYS_readlinkat, AT_FDCWD, (long)r->host, (long)text, PATH_MAX - 1, 0); if (t < 0) return t; if (t >= PATH_MAX - 1) return FP_SLOW; if (size && (size_t)t > size) t = size; text[t] = 0; long d = detranslated(text, r->host); if (d == FP_SLOW) return FP_SLOW; *changed = (int)d; return (long)strlen(text); } 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 < 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); } ssize_t __readlink_chk(const char *path, char *buf, size_t size, size_t room) { REAL(__readlink_chk, ssize_t (*)(const char *, char *, size_t, size_t)); if (size > room) return real___readlink_chk(path, buf, size, room); return readlink_common(AT_FDCWD, path, buf, size); } ssize_t __readlinkat_chk(int dirfd, const char *path, char *buf, size_t size, size_t room) { REAL(__readlinkat_chk, ssize_t (*)(int, const char *, char *, size_t, size_t)); if (size > room) return real___readlinkat_chk(dirfd, path, buf, size, room); return readlink_common(dirfd, path, buf, size); } char *realpath(const char *path, char *resolved) { REAL(realpath, char *(*)(const char *, char *)); struct res r; long rs = resolve_as(AT_FDCWD, path, 1, 1, &r); if (rs == 0 && !r.named) rs = FP_SLOW; if (rs == FP_SLOW) { missed(__func__, AT_FDCWD, path); return real_realpath(path, resolved); } hits++; if (rs == 1) rs = -ENOENT; if (rs < 0) { errno = (int)-rs; return NULL; } size_t n = strlen(r.guest) + 1; if (!resolved) { resolved = malloc(n); if (!resolved) return NULL; } memcpy(resolved, r.guest, n); return resolved; } char *__realpath_chk(const char *path, char *resolved, size_t size) { REAL(__realpath_chk, char *(*)(const char *, char *, size_t)); if (resolved && size < PATH_MAX) return real___realpath_chk(path, resolved, size); return realpath(path, resolved); } char *canonicalize_file_name(const char *path) { return realpath(path, NULL); } char *getcwd(char *buf, size_t size) { REAL(getcwd, char *(*)(char *, size_t)); char g[PATH_MAX]; if (!fp_ready() || own_cwd(g) == FP_SLOW) { missed(__func__, AT_FDCWD, "."); return real_getcwd(buf, size); } hits++; size_t need = strlen(g) + 1; if (buf && size == 0) { errno = EINVAL; return NULL; } if (buf && size < need) { errno = ERANGE; return NULL; } if (!buf) { if (size && size < need) { errno = ERANGE; return NULL; } buf = malloc(size ? size : need); if (!buf) return NULL; } memcpy(buf, g, need); return buf; } char *__getcwd_chk(char *buf, size_t size, size_t room) { REAL(__getcwd_chk, char *(*)(char *, size_t, size_t)); if (size > room) return real___getcwd_chk(buf, size, room); return getcwd(buf, size); } 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); 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); } static int ends_in_dots(const char *path) { size_t n = strlen(path); while (n > 1 && path[n - 1] == '/') n--; size_t b = n; while (b > 0 && path[b - 1] != '/') b--; return (n - b == 1 && path[b] == '.') || (n - b == 2 && path[b] == '.' && path[b + 1] == '.'); } static long fp_unlink(int dirfd, const char *path, int flags) { struct res r; char bare[PATH_MAX]; int slash = trailing_slash(path); if ((flags & ~AT_REMOVEDIR) || !path || ends_in_dots(path) || (slash && !(flags & AT_REMOVEDIR))) return FP_SLOW; if (slash) { size_t n = strlen(path); if (n >= sizeof bare) return FP_SLOW; while (n > 1 && path[n - 1] == '/') n--; memcpy(bare, path, n); bare[n] = 0; path = bare; } long rs = resolve(dirfd, path, MAY_BE_UNSEEN, &r); if (rs == 1) rs = -ENOENT; if (slash && rs == UNSEEN) rs = FP_SLOW; if (slash && rs == 0 && !S_ISDIR(r.st.st_mode)) rs = S_ISLNK(r.st.st_mode) ? FP_SLOW : -ENOTDIR; if (rs == 0 && (flags & AT_REMOVEDIR) && strcmp(r.guest, "/") == 0) rs = FP_SLOW; if (rs == 0 || rs == UNSEEN) { int changes_paths = (flags & AT_REMOVEDIR) || rs == UNSEEN || S_ISLNK(r.st.st_mode); rs = sc(SYS_unlinkat, AT_FDCWD, (long)r.host, flags, 0, 0); if (rs == 0 && changes_paths) forget(); } return rs; } static int unlink_common(int dirfd, const char *path, int flags) { REAL(unlinkat, int (*)(int, const char *, int)); ANSWER(fp_unlink(dirfd, path, flags), dirfd, path, real_unlinkat(dirfd, path, flags)); } int unlinkat(int dirfd, const char *path, int flags) { return unlink_common(dirfd, path, flags); } 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 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; if (rs == 0 && S_ISDIR(from.st.st_mode)) rs = FP_SLOW; if (rs == 0) { long rt = resolve(newdirfd, new, 0, &to); if (rt < 0) rs = rt; else if (rt == 0 && S_ISDIR(to.st.st_mode)) rs = FP_SLOW; else { rs = flags ? sc(SYS_renameat2, AT_FDCWD, (long)from.host, AT_FDCWD, (long)to.host, flags) : sc(SYS_renameat, AT_FDCWD, (long)from.host, AT_FDCWD, (long)to.host, 0); 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); return real_renameat2(olddirfd, old, newdirfd, new, flags); } int renameat2(int olddirfd, const char *old, int newdirfd, const char *new, unsigned flags) { return rename_common(olddirfd, old, newdirfd, new, flags); } 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); } 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(); } 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); } 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); 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 long fp_chown(int dirfd, const char *path, uid_t uid, gid_t gid, int flags) { struct res r; long rs = flags & ~AT_SYMLINK_NOFOLLOW ? 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 = sc(SYS_fchownat, AT_FDCWD, (long)r.host, (long)uid, (long)gid, flags); return rs; } int fchownat(int dirfd, const char *path, uid_t uid, gid_t gid, int flags) { REAL(fchownat, int (*)(int, const char *, uid_t, gid_t, int)); ANSWER(fp_chown(dirfd, path, uid, gid, flags), dirfd, path, real_fchownat(dirfd, path, uid, gid, flags)); } int chown(const char *path, uid_t uid, gid_t gid) { return fchownat(AT_FDCWD, path, uid, gid, 0); } int lchown(const char *path, uid_t uid, gid_t gid) { return fchownat(AT_FDCWD, path, uid, gid, AT_SYMLINK_NOFOLLOW); } 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()) { rs = sc(SYS_utimensat, dirfd, 0, (long)times, flags, 0); } else { rs = flags & ~AT_SYMLINK_NOFOLLOW ? 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 = sc(SYS_utimensat, AT_FDCWD, (long)r.host, (long)times, flags, 0); } 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); } int futimens(int fd, const struct timespec times[2]) { return utimens_common(fd, NULL, times, 0); } static int utimes_common(int dirfd, const char *path, const struct timeval tv[2], int flags) { struct timespec ts[2]; if (tv) { for (int i = 0; i < 2; i++) { if (tv[i].tv_usec < 0 || tv[i].tv_usec >= 1000000) { errno = EINVAL; return -1; } ts[i].tv_sec = tv[i].tv_sec; ts[i].tv_nsec = tv[i].tv_usec * 1000; } } return utimens_common(dirfd, path, tv ? ts : NULL, flags); } int utimes(const char *path, const struct timeval tv[2]) { return utimes_common(AT_FDCWD, path, tv, 0); } int lutimes(const char *path, const struct timeval tv[2]) { return utimes_common(AT_FDCWD, path, tv, AT_SYMLINK_NOFOLLOW); } int futimes(int fd, const struct timeval tv[2]) { return utimes_common(fd, NULL, tv, 0); } int utime(const char *path, const struct utimbuf *times) { if (!times) return utimens_common(AT_FDCWD, path, NULL, 0); struct timespec ts[2] = { { times->actime, 0 }, { times->modtime, 0 } }; return utimens_common(AT_FDCWD, path, ts, 0); } 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); 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 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); 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 *)); return statfs_common(path, buf, real_statfs); } int statfs64(const char *path, struct statfs64 *buf) { REAL(statfs64, int (*)(const char *, struct statfs *)); return statfs_common(path, (struct statfs *)buf, real_statfs64); } #define FP_ST_VALID 0x0020 static int statvfs_common(const char *path, struct statvfs *buf, int (*real)(const char *, struct statvfs *)) { struct res r; struct statfs fs; 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)&fs, 0, 0, 0); if (rs == 0 && !(fs.f_flags & FP_ST_VALID)) rs = FP_SLOW; if (rs == 0) { memset(buf, 0, sizeof *buf); buf->f_bsize = fs.f_bsize; buf->f_frsize = fs.f_frsize ? fs.f_frsize : fs.f_bsize; buf->f_blocks = fs.f_blocks; buf->f_bfree = fs.f_bfree; buf->f_bavail = fs.f_bavail; buf->f_files = fs.f_files; buf->f_ffree = fs.f_ffree; buf->f_favail = fs.f_ffree; _Static_assert(sizeof buf->f_fsid == sizeof fs.f_fsid, "fsid layout"); memcpy(&buf->f_fsid, &fs.f_fsid, sizeof fs.f_fsid); buf->f_flag = fs.f_flags ^ FP_ST_VALID; buf->f_namemax = fs.f_namelen; } ANSWER(rs, AT_FDCWD, path, real(path, buf)); } int statvfs(const char *path, struct statvfs *buf) { REAL(statvfs, int (*)(const char *, struct statvfs *)); return statvfs_common(path, buf, real_statvfs); } int statvfs64(const char *path, struct statvfs64 *buf) { REAL(statvfs64, int (*)(const char *, struct statvfs *)); return statvfs_common(path, (struct statvfs *)buf, real_statvfs64); } 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); } ssize_t getxattr(const char *path, const char *name, void *value, size_t size) { REAL(getxattr, ssize_t (*)(const char *, const char *, void *, size_t)); return getxattr_common(path, name, value, size, 1, real_getxattr); } ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size) { REAL(lgetxattr, ssize_t (*)(const char *, const char *, void *, size_t)); return getxattr_common(path, name, value, size, 0, real_lgetxattr); } 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); } ssize_t listxattr(const char *path, char *list, size_t size) { REAL(listxattr, ssize_t (*)(const char *, char *, size_t)); return listxattr_common(path, list, size, 1, real_listxattr); } ssize_t llistxattr(const char *path, char *list, size_t size) { REAL(llistxattr, ssize_t (*)(const char *, char *, size_t)); return listxattr_common(path, list, size, 0, real_llistxattr); } 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 == 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)); } enum { LONG_SELF, LONG_PEER }; static struct { unsigned long ino; int kind; char name[sizeof ((struct sockaddr_un *)0)->sun_path]; char guest[sizeof ((struct sockaddr_un *)0)->sun_path]; } long_names[64]; static unsigned long_next; static pthread_mutex_t long_lock = PTHREAD_MUTEX_INITIALIZER; static const char *fd_name(const char *path) { if (strncmp(path, "/proc/self/fd/", 14) != 0) return NULL; const char *p = path + 14; if (*p < '0' || *p > '9') return NULL; while (*p >= '0' && *p <= '9') p++; return *p == '/' && p[1] ? p + 1 : NULL; } static unsigned long socket_ino(int fd) { struct stat st; return sc(SYS_newfstatat, fd, (long)"", (long)&st, AT_EMPTY_PATH, 0) == 0 ? (unsigned long)st.st_ino : 0; } static void remember_long(int fd, int kind, const char *name, const char *guest) { unsigned long ino = socket_ino(fd); if (!ino || !name || strlen(name) >= sizeof long_names[0].name || strlen(guest) >= sizeof long_names[0].guest) return; unsigned count = sizeof long_names / sizeof long_names[0], i = count; pthread_mutex_lock(&long_lock); for (unsigned n = 0; n < count && i == count; n++) if (long_names[n].ino == ino && long_names[n].kind == kind) i = n; if (i == count) i = long_next++ % count; long_names[i].ino = ino; long_names[i].kind = kind; snprintf(long_names[i].name, sizeof long_names[i].name, "%s", name); snprintf(long_names[i].guest, sizeof long_names[i].guest, "%s", guest); pthread_mutex_unlock(&long_lock); } static int long_entry(int fd, int kind, const char *name, char *guest, size_t size) { unsigned long ino = socket_ino(fd); int found = -1; if (!ino || !name) return -1; pthread_mutex_lock(&long_lock); for (unsigned n = 0; n < sizeof long_names / sizeof long_names[0] && found < 0; n++) if (long_names[n].ino == ino && long_names[n].kind == kind && strcmp(long_names[n].name, name) == 0 && strlen(long_names[n].guest) < size) { memcpy(guest, long_names[n].guest, strlen(long_names[n].guest) + 1); found = 0; } pthread_mutex_unlock(&long_lock); return found; } static void remember_long_address(int fd, int kind, const struct sockaddr *addr, socklen_t len, const struct sockaddr_un *used) { const struct sockaddr_un *un = (const struct sockaddr_un *)addr; char guest[sizeof un->sun_path + 1]; size_t glen = strnlen(un->sun_path, len - offsetof(struct sockaddr_un, sun_path)); memcpy(guest, un->sun_path, glen); guest[glen] = 0; remember_long(fd, kind, fd_name(used->sun_path), guest); } static long socket_address(const struct sockaddr *addr, socklen_t len, int follow, struct sockaddr_un *out, socklen_t *out_len, long *dir) { const struct sockaddr_un *un = (const struct sockaddr_un *)addr; *dir = -1; size_t room = sizeof un->sun_path; if (!addr || len <= offsetof(struct sockaddr_un, sun_path) || addr->sa_family != AF_UNIX || un->sun_path[0] == 0) return 0; size_t plen = strnlen(un->sun_path, len - offsetof(struct sockaddr_un, sun_path)); char path[sizeof un->sun_path + 1]; if (plen > room) return FP_SLOW; memcpy(path, un->sun_path, plen); path[plen] = 0; if (trailing_slash(path)) return FP_SLOW; struct res r; long rs = resolve(AT_FDCWD, path, follow, &r); if (rs < 0) return rs; if (rs == 1 && follow) return -ENOENT; if (r.magic) return FP_SLOW; size_t hlen = strlen(r.host); memset(out, 0, sizeof *out); out->sun_family = AF_UNIX; if (hlen >= room) { char *slash = strrchr(r.host, '/'); if (!slash || slash == r.host) return FP_SLOW; *slash = 0; *dir = sc(SYS_openat, AT_FDCWD, (long)r.host, O_PATH | O_DIRECTORY | O_CLOEXEC, 0, 0); if (*dir < 0) return FP_SLOW; hlen = snprintf(out->sun_path, room, "/proc/self/fd/%ld/%s", *dir, slash + 1); if (hlen >= room) { sc(SYS_close, *dir, 0, 0, 0, 0); *dir = -1; return FP_SLOW; } } else { memcpy(out->sun_path, r.host, hlen + 1); } *out_len = offsetof(struct sockaddr_un, sun_path) + hlen + 1; return 1; } static int nonblocking(int fd) { long fl = sc(SYS_fcntl, fd, F_GETFL, 0, 0, 0); return fl >= 0 && (fl & O_NONBLOCK); } int connect(int fd, const struct sockaddr *addr, socklen_t len) { REAL(connect, int (*)(int, const struct sockaddr *, socklen_t)); struct sockaddr_un un; socklen_t un_len; long dir = -1; long rs = fp_ready() ? socket_address(addr, len, 1, &un, &un_len, &dir) : FP_SLOW; if (rs == 0 && !(addr && addr->sa_family == AF_UNIX) && !nonblocking(fd)) rs = FP_SLOW; if (rs == 0) rs = sc(SYS_connect, fd, (long)addr, len, 0, 0); else if (rs == 1) rs = sc(SYS_connect, fd, (long)&un, un_len, 0, 0); if (dir >= 0) { sc(SYS_close, dir, 0, 0, 0, 0); if (rs == 0 || rs == -EINPROGRESS) remember_long_address(fd, LONG_PEER, addr, len, &un); } ANSWER(rs, AT_FDCWD, "(connect)", real_connect(fd, addr, len)); } int bind(int fd, const struct sockaddr *addr, socklen_t len) { REAL(bind, int (*)(int, const struct sockaddr *, socklen_t)); struct sockaddr_un un; socklen_t un_len; long dir = -1; long rs = fp_ready() ? socket_address(addr, len, 0, &un, &un_len, &dir) : FP_SLOW; if (rs == 0) rs = sc(SYS_bind, fd, (long)addr, len, 0, 0); else if (rs == 1) rs = sc(SYS_bind, fd, (long)&un, un_len, 0, 0); if (dir >= 0) { sc(SYS_close, dir, 0, 0, 0, 0); if (rs == 0) remember_long_address(fd, LONG_SELF, addr, len, &un); } ANSWER(rs, AT_FDCWD, "(bind)", real_bind(fd, addr, len)); } static void report_address(int fd, int kind, const struct sockaddr_storage *got, socklen_t got_len, struct sockaddr *addr, socklen_t *len) { struct sockaddr_storage shown = *got; socklen_t shown_len = got_len; const struct sockaddr_un *un = (const struct sockaddr_un *)got; if (got_len > offsetof(struct sockaddr_un, sun_path) && un->sun_family == AF_UNIX && un->sun_path[0]) { char g[PATH_MAX], h[sizeof un->sun_path + 1]; size_t hl = strnlen(un->sun_path, got_len - offsetof(struct sockaddr_un, sun_path)); memcpy(h, un->sun_path, hl); h[hl] = 0; struct sockaddr_un *out = (struct sockaddr_un *)&shown; if ((long_entry(fd, kind, fd_name(h), g, sizeof g) == 0 || to_guest(h, g, sizeof g) == 0) && strlen(g) < sizeof out->sun_path) { memset(out->sun_path, 0, sizeof out->sun_path); strcpy(out->sun_path, g); shown_len = offsetof(struct sockaddr_un, sun_path) + strlen(g) + 1; } } memcpy(addr, &shown, *len < shown_len ? *len : shown_len); *len = shown_len; } static void inherit_long_name(int listener, int accepted) { unsigned long ino = socket_ino(listener); char name[sizeof long_names[0].name], guest[sizeof long_names[0].guest]; int found = -1; if (!ino) return; pthread_mutex_lock(&long_lock); for (unsigned n = 0; n < sizeof long_names / sizeof long_names[0] && found < 0; n++) if (long_names[n].ino == ino && long_names[n].kind == LONG_SELF) { snprintf(name, sizeof name, "%s", long_names[n].name); snprintf(guest, sizeof guest, "%s", long_names[n].guest); found = 0; } pthread_mutex_unlock(&long_lock); if (found == 0) remember_long(accepted, LONG_SELF, name, guest); } static int pending(int fd) { struct pollfd p = { .fd = fd, .events = POLLIN }; struct timespec now = { 0, 0 }; return sc(SYS_ppoll, (long)&p, 1, (long)&now, 0, 0) == 1 && (p.revents & POLLIN); } static int accept_common(int fd, struct sockaddr *addr, socklen_t *len, int flags) { REAL(accept4, int (*)(int, struct sockaddr *, socklen_t *, int)); if (!fp_ready() || (addr && !len) || (!nonblocking(fd) && !pending(fd))) { missed(__func__, AT_FDCWD, "(accept)"); return real_accept4(fd, addr, len, flags); } hits++; struct sockaddr_storage got; socklen_t got_len = sizeof got; long r = sc(SYS_accept4, fd, addr ? (long)&got : 0, addr ? (long)&got_len : 0, flags, 0); if (r >= 0) inherit_long_name(fd, (int)r); if (!addr) return ret(r); if (r >= 0) report_address((int)r, LONG_PEER, &got, got_len, addr, len); return ret(r); } int accept4(int fd, struct sockaddr *addr, socklen_t *len, int flags) { return accept_common(fd, addr, len, flags); } int accept(int fd, struct sockaddr *addr, socklen_t *len) { return accept_common(fd, addr, len, 0); } static int name_common(long nr, int fd, struct sockaddr *addr, socklen_t *len, int (*real)(int, struct sockaddr *, socklen_t *)) { if (!fp_ready() || !addr || !len) { missed(__func__, AT_FDCWD, "(name)"); return real(fd, addr, len); } hits++; struct sockaddr_storage got; socklen_t got_len = sizeof got; long r = sc(nr, fd, (long)&got, (long)&got_len, 0, 0); if (r == 0) report_address(fd, nr == SYS_getsockname ? LONG_SELF : LONG_PEER, &got, got_len, addr, len); return ret(r); } int getsockname(int fd, struct sockaddr *addr, socklen_t *len) { REAL(getsockname, int (*)(int, struct sockaddr *, socklen_t *)); return name_common(SYS_getsockname, fd, addr, len, real_getsockname); } int getpeername(int fd, struct sockaddr *addr, socklen_t *len) { REAL(getpeername, int (*)(int, struct sockaddr *, socklen_t *)); return name_common(SYS_getpeername, fd, addr, len, real_getpeername); } static int shm_path(const char *name, char *path) { while (*name == '/') name++; size_t n = strlen(name); if (n == 0 || n > NAME_MAX || strchr(name, '/')) return -1; memcpy(path, "/dev/shm/", 9); memcpy(path + 9, name, n + 1); return 0; } int shm_open(const char *name, int flags, mode_t mode) { REAL(shm_open, int (*)(const char *, int, mode_t)); char path[sizeof "/dev/shm/" + NAME_MAX]; long rs = shm_path(name, path) == 0 ? fp_open(AT_FDCWD, path, flags | O_NOFOLLOW | O_CLOEXEC, mode) : FP_SLOW; if (rs == -EISDIR) rs = -EINVAL; ANSWER(rs, AT_FDCWD, name, real_shm_open(name, flags, mode)); } int shm_unlink(const char *name) { REAL(shm_unlink, int (*)(const char *)); char path[sizeof "/dev/shm/" + NAME_MAX]; long rs = shm_path(name, path) == 0 ? fp_unlink(AT_FDCWD, path, 0) : FP_SLOW; if (rs == -EPERM) rs = -EACCES; ANSWER(rs, AT_FDCWD, name, real_shm_unlink(name)); } DIR *opendir(const char *path) { REAL(opendir, DIR *(*)(const char *)); long fd = fp_open(AT_FDCWD, path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NONBLOCK, 0); if (fd == FP_SLOW) { missed(__func__, AT_FDCWD, path); return real_opendir(path); } hits++; if (fd < 0) { errno = (int)-fd; return NULL; } DIR *d = fdopendir((int)fd); if (!d) close((int)fd); return d; } static FILE *fopen_common(const char *path, const char *m, FILE *(*real)(const char *, const char *)) { int flags; switch (m[0]) { case 'r': flags = strchr(m, '+') ? O_RDWR : O_RDONLY; break; case 'w': flags = (strchr(m, '+') ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC; break; case 'a': flags = (strchr(m, '+') ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND; break; default: return real(path, m); } if (strchr(m, 'e')) flags |= O_CLOEXEC; if (strchr(m, 'x')) flags |= O_EXCL; long fd = fp_open(AT_FDCWD, path, flags, 0666); if (fd == FP_SLOW) { missed(__func__, AT_FDCWD, path); return real(path, m); } hits++; if (fd < 0) { errno = (int)-fd; return NULL; } FILE *f = fdopen((int)fd, m); if (!f) close((int)fd); return f; } FILE *fopen(const char *path, const char *m) { REAL(fopen, FILE *(*)(const char *, const char *)); return fopen_common(path, m, real_fopen); } FILE *fopen64(const char *path, const char *m) { REAL(fopen64, FILE *(*)(const char *, const char *)); return fopen_common(path, m, real_fopen64); } static int online_cpus(void) { long fd = fp_open(AT_FDCWD, "/sys/devices/system/cpu/online", O_RDONLY | O_CLOEXEC, 0); if (fd < 0) return -1; char buf[256]; long n = sc(SYS_read, fd, (long)buf, sizeof buf - 1, 0, 0); sc(SYS_close, fd, 0, 0, 0, 0); if (n <= 0) return -1; buf[n] = 0; int count = 0; for (char *p = buf; *p && *p != '\n';) { char *end; long lo = strtol(p, &end, 10), hi = lo; if (end == p) return -1; if (*end == '-') { p = end + 1; hi = strtol(p, &end, 10); if (end == p || hi < lo) return -1; } count += (int)(hi - lo + 1); p = *end == ',' ? end + 1 : end; } return count > 0 ? count : -1; } int get_nprocs(void) { REAL(get_nprocs, int (*)(void)); int n = fp_ready() ? online_cpus() : -1; if (n > 0) { hits++; return n; } missed(__func__, AT_FDCWD, "(nprocs)"); return real_get_nprocs(); } long sysconf(int name) { REAL(sysconf, long (*)(int)); if (name == _SC_NPROCESSORS_ONLN) return get_nprocs(); return real_sysconf(name); } static struct utsname uts; static int uts_known; static long own_uname(struct utsname *u) { if (!fp_ready()) return FP_SLOW; if (!__atomic_load_n(&uts_known, __ATOMIC_ACQUIRE)) { struct utsname got; if (syscall(SYS_uname, &got) != 0) return FP_SLOW; static pthread_mutex_t uts_lock = PTHREAD_MUTEX_INITIALIZER; if (pthread_mutex_trylock(&uts_lock) == 0) { if (!uts_known) { uts = got; __atomic_store_n(&uts_known, 1, __ATOMIC_RELEASE); } pthread_mutex_unlock(&uts_lock); } *u = got; return 0; } *u = uts; return 0; } int uname(struct utsname *u) { REAL(uname, int (*)(struct utsname *)); if (!u) return real_uname(u); ANSWER(own_uname(u), AT_FDCWD, "(uname)", real_uname(u)); } int gethostname(char *name, size_t len) { REAL(gethostname, int (*)(char *, size_t)); struct utsname u; if (own_uname(&u) == FP_SLOW) { missed(__func__, AT_FDCWD, "(gethostname)"); return real_gethostname(name, len); } hits++; size_t n = strlen(u.nodename) + 1; memcpy(name, u.nodename, len < n ? len : n); if (n > len) { errno = ENAMETOOLONG; return -1; } return 0; } int isatty(int fd) { REAL(isatty, int (*)(int)); struct termios k; if (!fp_ready()) return real_isatty(fd); hits++; long r = sc(SYS_ioctl, fd, TCGETS, (long)&k, 0, 0); if (r < 0) { errno = (int)-r; return 0; } return r == 0; } int memfd_create(const char *name, unsigned flags) { REAL(memfd_create, int (*)(const char *, unsigned)); if (!name || !strncmp(name, "JITCode:", 8) || !strcmp(name, "opcache_lock") || !strncmp(name, "lib/apk/exec/", 13) || !fp_ready()) return real_memfd_create(name, flags); long r = sc(SYS_memfd_create, (long)name, flags, 0, 0, 0); if (r == -ENOSYS) return real_memfd_create(name, flags); hits++; return ret(r); } static long read_small(const char *path, char *buf, size_t size) { long fd = fp_open(AT_FDCWD, path, O_RDONLY | O_CLOEXEC, 0); if (fd < 0) return -1; long have = 0, r = 0; while ((size_t)have < size && (r = sc(SYS_read, fd, (long)buf + have, size - have, 0, 0)) > 0) have += r; sc(SYS_close, fd, 0, 0, 0, 0); return (size_t)have < size && r == 0 ? have : -1; } static int passwd_is_files(char *conf, size_t size) { long n = read_small("/etc/nsswitch.conf", conf, size - 1); if (n < 0) return 0; conf[n] = 0; int found = 0; for (char *line = conf; line;) { char *end = strchr(line, '\n'); if (end) *end = 0; if (strncmp(line, "passwd", 6) == 0 && strchr(" \t:", line[6])) { char *p = line + 6; while (*p == ' ' || *p == '\t') p++; if (*p++ != ':' || found++ || strchr(p, '#')) return 0; while (*p == ' ' || *p == '\t') p++; if (strncmp(p, "files", 5) != 0 || (p[5] && p[5] != ' ' && p[5] != '\t')) return 0; for (p += 5; *p == ' ' || *p == '\t'; p++) {} if (*p == '[') return 0; } else if (strstr(line, "passwd") && line[0] != '#') { return 0; } line = end ? end + 1 : NULL; } return found; } static int passwd_line(char *file, const char *name, uid_t uid, char **f, size_t *longest) { *longest = 0; for (char *line = file; line && *line;) { char *end = strchr(line, '\n'); if (end) *end = 0; size_t len = strlen(line); if (len > *longest) *longest = len; if (line[0] && line[0] != '#') { int k = 0; f[k++] = line; for (char *p = line; *p && k < 8; p++) if (*p == ':') { *p = 0; if (k < 7) f[k] = p + 1; k++; } if (k != 7 || !f[0][0] || strchr("+- \t", f[0][0])) return 0; for (int i = 2; i < 4; i++) { size_t d = strspn(f[i], "0123456789"); if (d == 0 || d > 10 || f[i][d] || strtoul(f[i], NULL, 10) > 0xffffffffUL) return 0; } if (name ? strcmp(f[0], name) == 0 : (uid_t)strtoul(f[2], NULL, 10) == uid) return 1; } line = end ? end + 1 : NULL; } return -1; } static int own_passwd(const char *name, uid_t uid, struct passwd *pw, char *buf, size_t buflen) { char file[16384], *f[7]; size_t longest; if (!fp_ready() || !passwd_is_files(file, sizeof file)) return 0; long n = read_small("/etc/passwd", file, sizeof file - 1); if (n < 0) return 0; file[n] = 0; if (passwd_line(file, name, uid, f, &longest) != 1 || longest + 3 > buflen) return 0; size_t off = 0; char *s[7]; for (int i = 0; i < 7; i++) { size_t len = strlen(f[i]) + 1; s[i] = buf + off; memcpy(s[i], f[i], len); off += len; } pw->pw_name = s[0]; pw->pw_passwd = s[1]; pw->pw_uid = (uid_t)strtoul(s[2], NULL, 10); pw->pw_gid = (gid_t)strtoul(s[3], NULL, 10); pw->pw_gecos = s[4]; pw->pw_dir = s[5]; pw->pw_shell = s[6]; return 1; } static int passwd_r(const char *name, uid_t uid, struct passwd *pw, char *buf, size_t buflen, struct passwd **result, int (*real_name)(const char *, struct passwd *, char *, size_t, struct passwd **), int (*real_uid)(uid_t, struct passwd *, char *, size_t, struct passwd **)) { if (!own_passwd(name, uid, pw, buf, buflen)) { missed(name ? "getpwnam" : "getpwuid", AT_FDCWD, "(passwd)"); return name ? real_name(name, pw, buf, buflen, result) : real_uid(uid, pw, buf, buflen, result); } hits++; *result = pw; errno = 0; return 0; } int getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t buflen, struct passwd **result) { REAL(getpwnam_r, int (*)(const char *, struct passwd *, char *, size_t, struct passwd **)); return passwd_r(name, 0, pw, buf, buflen, result, real_getpwnam_r, NULL); } int getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t buflen, struct passwd **result) { REAL(getpwuid_r, int (*)(uid_t, struct passwd *, char *, size_t, struct passwd **)); return passwd_r(NULL, uid, pw, buf, buflen, result, NULL, real_getpwuid_r); } static struct passwd static_pw; static char static_pw_buf[4096]; static pthread_mutex_t static_pw_lock = PTHREAD_MUTEX_INITIALIZER; static struct passwd *passwd_static(const char *name, uid_t uid) { pthread_mutex_lock(&static_pw_lock); int own = own_passwd(name, uid, &static_pw, static_pw_buf, sizeof static_pw_buf); pthread_mutex_unlock(&static_pw_lock); if (!own) return NULL; hits++; errno = 0; return &static_pw; } struct passwd *getpwnam(const char *name) { REAL(getpwnam, struct passwd *(*)(const char *)); struct passwd *pw = passwd_static(name, 0); if (pw) return pw; missed(__func__, AT_FDCWD, "(passwd)"); return real_getpwnam(name); } struct passwd *getpwuid(uid_t uid) { REAL(getpwuid, struct passwd *(*)(uid_t)); struct passwd *pw = passwd_static(NULL, uid); if (pw) return pw; missed(__func__, AT_FDCWD, "(passwd)"); return real_getpwuid(uid); } #include "svc-patch.h"