Compare commits

...
Author SHA1 Message Date
maxjivi05 8a6877423e AppImage apps: Electron programs such as Heroic open under FEX
Every Electron AppImage hung without a window or crashed at its first page.
Four causes, each enough on its own:

- The session preload's hooks found the real functions with
  dlsym(RTLD_NEXT). Before glibc 2.34 a thread's first dlsym callocs, and
  Chromium's PartitionAlloc calls syscall() from inside its first malloc:
  the allocator waited for itself. On older glibc (the x86 runtime's 2.31)
  next.c now walks the libraries after the preload with no allocation and
  picks each function's default version, as glibc 2.36 does; the old
  lookup handed realpath() and posix_spawn() their compat versions, and
  realpath(path, NULL) failed. arm64 keeps dlsym.
- libpci's sysfs backend exits when /sys/bus/pci/devices is listable but a
  device's files are not (a RedMagic's PCIe root port), taking every
  Chromium GPU process with it. An empty directory is bound over it on
  devices where that happens.
- FEX_OUTPUTLOG=stderr made FEX log to its own duplicate of descriptor 2.
  Chromium dup2()s its IPC socket onto that low number in each child it
  starts, then lists /proc/self/fd, which FEX logs into the socket. Empty,
  FEX writes to descriptor 2 itself.
- The tracer preload served every /proc/<pid>/status as a memfd, which is
  open for writing. Chromium hands each renderer its status as a read-only
  file and checks that it is one. Only a process's own status (pid, tid,
  self, thread-self) is rewritten now.
2026-10-11 00:17:55 -04:00
31 changed files with 472 additions and 112 deletions

No files matched your search

@@ -277,6 +277,12 @@ public final class LinuxRuntime {
bind(cmd, fake.getPath() + ":" + entry[1]);
}
}
// libpci prefers sysfs to procfs, and where the app may list /sys/bus/pci/devices but not
// read the files of a device in it (a RedMagic's PCIe root port) it die()s the same way.
// Every Chromium GPU process exits with it: the Steam client's, an Electron app's.
if (unreadablePciDevice()) {
bind(cmd, new File(root, "etc/droiddeck/empty").getPath() + ":/sys/bus/pci/devices");
}
bindGpuNode(context, cmd);
bindAdrenoStats(cmd, extraBinds);
bindCpuTemps(cmd, root);
@@ -288,6 +294,16 @@ public final class LinuxRuntime {
return specs;
}
/** Whether /sys/bus/pci/devices lists a device whose vendor the app may not read. */
private static boolean unreadablePciDevice() {
File[] devices = new File("/sys/bus/pci/devices").listFiles();
if (devices == null) return false;
for (File device : devices) {
if (!new File(device, "vendor").canRead()) return true;
}
return false;
}
/**
* An app process may not open {@code /dev/dri} - the nodes exist but are labelled
* {@code graphics_device}, which stock policy grants surfaceflinger and not us - yet libdrm and
@@ -428,7 +428,11 @@ def guest_environment(fex, env):
result["FEX_APP_DATA_LOCATION"] = data + "/"
result["FEX_APP_CACHE_LOCATION"] = CACHE_DIR + "/"
result.setdefault("FEX_SILENTLOG", "0")
result.setdefault("FEX_OUTPUTLOG", "stderr")
# Empty: FEX writes to descriptor 2 itself. "stderr" has it write to its own duplicate, a low
# number a program may dup2() its own descriptor onto: Chromium does so in each child it starts
# and lists /proc/self/fd next, which FEX logs, into the child's IPC channel - the browser
# never understood a child, and every Electron program hung without a window.
result.setdefault("FEX_OUTPUTLOG", "")
if fex["portable"]:
result["FEX_PORTABLE"] = "1"
else:
+6 -5
View File
@@ -7,7 +7,6 @@
* and Qt ask before they start one. Only in the desktop session.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
@@ -20,6 +19,8 @@
#include <sys/statvfs.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
#ifndef BINFMT_HANDLER
#define BINFMT_HANDLER "/usr/local/bin/droiddeck-open"
#endif
@@ -96,7 +97,7 @@ static void handler_args(char **args, const char *path, char *const argv[], int
static void hand_off(const char *path, char *const argv[], char *const envp[]) {
static int (*real)(const char *, char *const[], char *const[]);
if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execve");
if (!real) real = (int (*)(const char *, char *const[], char *const[]))bl_next("execve");
int n = count(argv);
char *args[n + 4];
handler_args(args, path, argv, n);
@@ -154,7 +155,7 @@ __attribute__((visibility("hidden"))) int bl_binfmt_spawn(pid_t *pid, const char
int execvpe(const char *file, char *const argv[], char *const envp[]) {
static int (*real)(const char *, char *const[], char *const[]);
if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execvpe");
if (!real) real = (int (*)(const char *, char *const[], char *const[]))bl_next("execvpe");
bl_binfmt_before_search(file, argv, envp);
real(file, argv, envp);
return bl_binfmt_after_search(file, argv, envp, errno);
@@ -163,8 +164,8 @@ int execvpe(const char *file, char *const argv[], char *const envp[]) {
int posix_spawnp(pid_t *pid, const char *file, const posix_spawn_file_actions_t *fa, const posix_spawnattr_t *attr,
char *const argv[], char *const envp[]) {
static spawn_fn real, spawn;
if (!real) real = (spawn_fn)dlsym(RTLD_NEXT, "posix_spawnp");
if (!spawn) spawn = (spawn_fn)dlsym(RTLD_NEXT, "posix_spawn");
if (!real) real = (spawn_fn)bl_next("posix_spawnp");
if (!spawn) spawn = (spawn_fn)bl_next("posix_spawn");
char path[PATH_MAX];
if (!desktop() || !lookup(file, path, sizeof path)) return real(pid, file, fa, attr, argv, envp);
return bl_binfmt_spawn(pid, path, fa, attr, argv, envp, spawn);
+3 -2
View File
@@ -7,7 +7,6 @@
*/
#define _GNU_SOURCE
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
@@ -25,6 +24,8 @@
#include <time.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
#define DIR_TTL_NS 5000000000LL
#define MAX_LISTINGS 16
#define MAX_ENTRIES 100000
@@ -334,7 +335,7 @@ static struct handle *lookup(DIR *d) {
#define REAL(name) static __typeof__(&name) next_##name
#define NEXT(name) \
if (next_##name == NULL) next_##name = (__typeof__(&name))dlsym(RTLD_NEXT, #name)
if (next_##name == NULL) next_##name = (__typeof__(&name))bl_next(#name)
REAL(opendir);
REAL(readdir64);
+3 -2
View File
@@ -6,7 +6,6 @@
* descriptors stands in for the kernel's, and every other device is left to libdrm.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
@@ -17,6 +16,8 @@
#include <sys/syscall.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
#define MAX_HANDLES 4096
/* DRM_IOCTL_GEM_CLOSE from drm.h: _IOW('d', 0x09, struct drm_gem_close { u32 handle, pad; }). */
@@ -50,7 +51,7 @@ static int is_kgsl(int fd) {
}
static void *real(const char *name) {
void *fn = dlsym(RTLD_NEXT, name);
void *fn = bl_next(name);
if (!fn) {
errno = ENOSYS;
}
+14 -13
View File
@@ -2,7 +2,6 @@
* Credit: fsync, the futex-based synchronization in Proton, by Elizabeth Figura and Paul Gofman;
* futex_waitv(2) by André Almeida. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
@@ -18,6 +17,8 @@
#include <time.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
#ifndef SYS_futex_waitv
#define SYS_futex_waitv 449
#endif
@@ -112,12 +113,12 @@ static int fs_on(void) {
}
static long fs_sys(long n, long a, long b, long c, long d, long e, long f) {
if (!fs_real_syscall) fs_real_syscall = (long (*)(long, ...))dlsym(RTLD_NEXT, "syscall");
if (!fs_real_syscall) fs_real_syscall = (long (*)(long, ...))bl_next("syscall");
return fs_real_syscall(n, a, b, c, d, e, f);
}
static void *fs_anon(size_t size) {
if (!fs_real_mmap) fs_real_mmap = (void *(*)(void *, size_t, int, int, int, off_t))dlsym(RTLD_NEXT, "mmap");
if (!fs_real_mmap) fs_real_mmap = (void *(*)(void *, size_t, int, int, int, off_t))bl_next("mmap");
return fs_real_mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
@@ -341,9 +342,9 @@ static void fs_child(void) {
}
__attribute__((constructor)) static void fs_init(void) {
if (!fs_real_mmap) fs_real_mmap = (void *(*)(void *, size_t, int, int, int, off_t))dlsym(RTLD_NEXT, "mmap");
if (!fs_real_mmap64) fs_real_mmap64 = (void *(*)(void *, size_t, int, int, int, off64_t))dlsym(RTLD_NEXT, "mmap64");
if (!fs_real_munmap) fs_real_munmap = (int (*)(void *, size_t))dlsym(RTLD_NEXT, "munmap");
if (!fs_real_mmap) fs_real_mmap = (void *(*)(void *, size_t, int, int, int, off_t))bl_next("mmap");
if (!fs_real_mmap64) fs_real_mmap64 = (void *(*)(void *, size_t, int, int, int, off64_t))bl_next("mmap64");
if (!fs_real_munmap) fs_real_munmap = (int (*)(void *, size_t))bl_next("munmap");
if (fs_on()) pthread_atfork(fs_fork_prepare, fs_fork_parent, fs_child);
}
@@ -571,7 +572,7 @@ static void fs_attach(const char *name, int fd, int create) {
close(rfd);
return;
}
if (!fs_real_mmap) fs_real_mmap = (void *(*)(void *, size_t, int, int, int, off_t))dlsym(RTLD_NEXT, "mmap");
if (!fs_real_mmap) fs_real_mmap = (void *(*)(void *, size_t, int, int, int, off_t))bl_next("mmap");
p = fs_real_mmap(NULL, FS_REGION, PROT_READ | PROT_WRITE, MAP_SHARED, rfd, 0);
close(rfd);
if (p == MAP_FAILED) {
@@ -588,8 +589,8 @@ static void fs_attach(const char *name, int fd, int create) {
int shm_open(const char *name, int oflag, mode_t mode) {
int fd, saved;
if (!fs_real_shm_open) fs_real_shm_open = (int (*)(const char *, int, mode_t))dlsym(RTLD_NEXT, "shm_open");
if (!fs_real_shm_unlink) fs_real_shm_unlink = (int (*)(const char *))dlsym(RTLD_NEXT, "shm_unlink");
if (!fs_real_shm_open) fs_real_shm_open = (int (*)(const char *, int, mode_t))bl_next("shm_open");
if (!fs_real_shm_unlink) fs_real_shm_unlink = (int (*)(const char *))bl_next("shm_unlink");
if (!fs_real_shm_open) {
errno = ENOSYS;
return -1;
@@ -605,7 +606,7 @@ int shm_open(const char *name, int oflag, mode_t mode) {
int shm_unlink(const char *name) {
int r, saved;
if (!fs_real_shm_unlink) fs_real_shm_unlink = (int (*)(const char *))dlsym(RTLD_NEXT, "shm_unlink");
if (!fs_real_shm_unlink) fs_real_shm_unlink = (int (*)(const char *))bl_next("shm_unlink");
if (!fs_real_shm_unlink) {
errno = ENOSYS;
return -1;
@@ -622,7 +623,7 @@ int shm_unlink(const char *name) {
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off) {
void *p;
if (!fs_real_mmap) fs_real_mmap = (void *(*)(void *, size_t, int, int, int, off_t))dlsym(RTLD_NEXT, "mmap");
if (!fs_real_mmap) fs_real_mmap = (void *(*)(void *, size_t, int, int, int, off_t))bl_next("mmap");
p = fs_real_mmap(addr, len, prot, flags, fd, off);
if (p == MAP_FAILED && bl_noexec_retry(prot, flags, fd)) p = fs_real_mmap(addr, len, prot & ~PROT_EXEC, flags, fd, off);
if (p != MAP_FAILED && __builtin_expect(fs_fd >= 0, 0)) fs_track(p, len, flags, fd, off);
@@ -631,7 +632,7 @@ void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off) {
void *mmap64(void *addr, size_t len, int prot, int flags, int fd, off64_t off) {
void *p;
if (!fs_real_mmap64) fs_real_mmap64 = (void *(*)(void *, size_t, int, int, int, off64_t))dlsym(RTLD_NEXT, "mmap64");
if (!fs_real_mmap64) fs_real_mmap64 = (void *(*)(void *, size_t, int, int, int, off64_t))bl_next("mmap64");
p = fs_real_mmap64(addr, len, prot, flags, fd, off);
if (p == MAP_FAILED && bl_noexec_retry(prot, flags, fd)) p = fs_real_mmap64(addr, len, prot & ~PROT_EXEC, flags, fd, off);
if (p != MAP_FAILED && __builtin_expect(fs_fd >= 0, 0)) fs_track(p, len, flags, fd, off);
@@ -642,7 +643,7 @@ int munmap(void *addr, size_t len) {
uintptr_t lo = (uintptr_t)addr, hi = lo + len;
sigset_t old;
int r, saved;
if (!fs_real_munmap) fs_real_munmap = (int (*)(void *, size_t))dlsym(RTLD_NEXT, "munmap");
if (!fs_real_munmap) fs_real_munmap = (int (*)(void *, size_t))bl_next("munmap");
if (__builtin_expect(fs_fd < 0, 1) || !fs_overlaps(lo, hi)) return fs_real_munmap(addr, len);
fs_block(&old);
pthread_mutex_lock(&fs_mx);
+16 -16
View File
@@ -39,6 +39,8 @@
#include <sys/sysmacros.h>
#include <sys/types.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
/* The app opens one ring per configured controller; four is what its launcher offers. Nodes from
* UINPUT_BASE up are the virtual pads made through libfakeinput's /dev/uinput stand-in. */
#define RING_MAX 4
@@ -208,8 +210,6 @@ static struct pad *pad_for_syslink(const char *path) {
return NULL;
}
static void *next_symbol(const char *name) { return dlsym(RTLD_NEXT, name); }
char *bl_cached_realpath(const char *path, char *resolved, char *(*real)(const char *, char *))
__attribute__((visibility("hidden")));
@@ -232,7 +232,7 @@ char *realpath(const char *path, char *resolved) {
memcpy(resolved, pad->syspath, strlen(pad->syspath) + 1);
return resolved;
}
if (real_realpath == NULL) real_realpath = next_symbol("realpath");
if (real_realpath == NULL) real_realpath = bl_next("realpath");
if (real_realpath == NULL) return NULL;
return bl_cached_realpath(path, resolved, real_realpath);
}
@@ -243,7 +243,7 @@ void *udev_device_new_from_syspath(void *udev, const char *syspath) {
struct pad *pad = caller_is_winebus(caller) ? pad_for_syspath(syspath) : NULL;
if (pad != NULL) return pad;
if (real_new == NULL) real_new = next_symbol("udev_device_new_from_syspath");
if (real_new == NULL) real_new = bl_next("udev_device_new_from_syspath");
if (real_new == NULL) return NULL;
return real_new(udev, syspath);
}
@@ -252,7 +252,7 @@ void *udev_device_ref(void *device) {
static void *(*real_ref)(void *);
if (pad_for_device(device) != NULL) return device;
if (real_ref == NULL) real_ref = next_symbol("udev_device_ref");
if (real_ref == NULL) real_ref = bl_next("udev_device_ref");
if (real_ref == NULL) return NULL;
return real_ref(device);
}
@@ -261,7 +261,7 @@ void *udev_device_unref(void *device) {
static void *(*real_unref)(void *);
if (pad_for_device(device) != NULL) return NULL;
if (real_unref == NULL) real_unref = next_symbol("udev_device_unref");
if (real_unref == NULL) real_unref = bl_next("udev_device_unref");
if (real_unref == NULL) return NULL;
return real_unref(device);
}
@@ -271,7 +271,7 @@ const char *udev_device_get_devnode(void *device) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return pad->devnode;
if (real_get == NULL) real_get = next_symbol("udev_device_get_devnode");
if (real_get == NULL) real_get = bl_next("udev_device_get_devnode");
if (real_get == NULL) return NULL;
return real_get(device);
}
@@ -281,7 +281,7 @@ const char *udev_device_get_syspath(void *device) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return pad->syspath;
if (real_get == NULL) real_get = next_symbol("udev_device_get_syspath");
if (real_get == NULL) real_get = bl_next("udev_device_get_syspath");
if (real_get == NULL) return NULL;
return real_get(device);
}
@@ -291,7 +291,7 @@ const char *udev_device_get_sysname(void *device) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return pad->sysname;
if (real_get == NULL) real_get = next_symbol("udev_device_get_sysname");
if (real_get == NULL) real_get = bl_next("udev_device_get_sysname");
if (real_get == NULL) return NULL;
return real_get(device);
}
@@ -301,7 +301,7 @@ const char *udev_device_get_subsystem(void *device) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return "input";
if (real_get == NULL) real_get = next_symbol("udev_device_get_subsystem");
if (real_get == NULL) real_get = bl_next("udev_device_get_subsystem");
if (real_get == NULL) return NULL;
return real_get(device);
}
@@ -311,7 +311,7 @@ const char *udev_device_get_devtype(void *device) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return NULL;
if (real_get == NULL) real_get = next_symbol("udev_device_get_devtype");
if (real_get == NULL) real_get = bl_next("udev_device_get_devtype");
if (real_get == NULL) return NULL;
return real_get(device);
}
@@ -321,7 +321,7 @@ const char *udev_device_get_action(void *device) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return NULL;
if (real_get == NULL) real_get = next_symbol("udev_device_get_action");
if (real_get == NULL) real_get = bl_next("udev_device_get_action");
if (real_get == NULL) return NULL;
return real_get(device);
}
@@ -331,7 +331,7 @@ dev_t udev_device_get_devnum(void *device) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return pad->devnum;
if (real_get == NULL) real_get = next_symbol("udev_device_get_devnum");
if (real_get == NULL) real_get = bl_next("udev_device_get_devnum");
if (real_get == NULL) return makedev(0, 0);
return real_get(device);
}
@@ -349,7 +349,7 @@ void *udev_device_get_parent_with_subsystem_devtype(void *device, const char *su
if (subsystem != NULL && strcmp(subsystem, "input") == 0 && devtype == NULL) return pad;
return NULL;
}
if (real_get == NULL) real_get = next_symbol("udev_device_get_parent_with_subsystem_devtype");
if (real_get == NULL) real_get = bl_next("udev_device_get_parent_with_subsystem_devtype");
if (real_get == NULL) return NULL;
return real_get(device, subsystem, devtype);
}
@@ -359,7 +359,7 @@ const char *udev_device_get_sysattr_value(void *device, const char *attr) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return attr != NULL && strcmp(attr, "uevent") == 0 ? pad->uevent : NULL;
if (real_get == NULL) real_get = next_symbol("udev_device_get_sysattr_value");
if (real_get == NULL) real_get = bl_next("udev_device_get_sysattr_value");
if (real_get == NULL) return NULL;
return real_get(device, attr);
}
@@ -369,7 +369,7 @@ const char *udev_device_get_property_value(void *device, const char *key) {
struct pad *pad = pad_for_device(device);
if (pad != NULL) return key != NULL && strcmp(key, "ID_INPUT_JOYSTICK") == 0 ? "1" : NULL;
if (real_get == NULL) real_get = next_symbol("udev_device_get_property_value");
if (real_get == NULL) real_get = bl_next("udev_device_get_property_value");
if (real_get == NULL) return NULL;
return real_get(device, key);
}
+5 -4
View File
@@ -18,7 +18,6 @@
* becomes a rename that will not replace an existing name, which is all the link promised there.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
@@ -26,6 +25,8 @@
#include <string.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
static int refused(int error) { return error == EPERM || error == EACCES; }
static int moves_instead(void) {
@@ -45,7 +46,7 @@ __attribute__((visibility("hidden"))) int bl_link_lock(const char *oldpath, cons
if (result == 0 || !refused(errno)) return result;
if (moves_instead()) return renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_NOREPLACE);
if (!lock_name(newpath)) return result;
if (real_symlink == NULL) real_symlink = dlsym(RTLD_NEXT, "symlink");
if (real_symlink == NULL) real_symlink = bl_next("symlink");
return real_symlink(oldpath, newpath);
}
@@ -53,14 +54,14 @@ __attribute__((visibility("hidden"))) int bl_link_lock(const char *oldpath, cons
#if __SIZEOF_POINTER__ != 8
int link(const char *oldpath, const char *newpath) {
static int (*real_link)(const char *, const char *);
if (real_link == NULL) real_link = dlsym(RTLD_NEXT, "link");
if (real_link == NULL) real_link = bl_next("link");
return bl_link_lock(oldpath, newpath, real_link(oldpath, newpath));
}
#endif
int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) {
static int (*real_linkat)(int, const char *, int, const char *, int);
if (real_linkat == NULL) real_linkat = dlsym(RTLD_NEXT, "linkat");
if (real_linkat == NULL) real_linkat = bl_next("linkat");
int result = real_linkat(olddirfd, oldpath, newdirfd, newpath, flags);
if (result != 0 && refused(errno) && flags == 0 && moves_instead())
return renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE);
+4 -3
View File
@@ -20,7 +20,6 @@
* answer is "nobody", so it is reported unlocked rather than granted.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
@@ -28,6 +27,8 @@
#include <sys/file.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
/* The filesystem refuses the whole operation, rather than declining this particular lock. */
static int unsupported(int error) {
return error == ENOSYS || error == ENOTSUP || error == EOPNOTSUPP;
@@ -37,7 +38,7 @@ int flock(int fd, int operation) {
static int (*real_flock)(int, int);
int result;
if (real_flock == NULL) real_flock = dlsym(RTLD_NEXT, "flock");
if (real_flock == NULL) real_flock = bl_next("flock");
if (real_flock == NULL) return 0;
result = real_flock(fd, operation);
if (result < 0 && unsupported(errno)) return 0;
@@ -88,7 +89,7 @@ int fcntl(int fd, int cmd, ...) {
arg = va_arg(ap, void *);
va_end(ap);
if (real_fcntl == NULL) real_fcntl = dlsym(RTLD_NEXT, "fcntl");
if (real_fcntl == NULL) real_fcntl = bl_next("fcntl");
if (real_fcntl == NULL) {
errno = ENOSYS;
return -1;
+10 -9
View File
@@ -9,7 +9,6 @@
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <spawn.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <linux/netlink.h>
#include <netinet/in.h>
@@ -24,6 +23,8 @@
#include <time.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
static const char *net_dir(void) {
static char dir[256];
if (!dir[0]) {
@@ -124,7 +125,7 @@ __attribute__((visibility("hidden"))) int bl_udevmon_stand_in(int fd);
int bind(int fd, const struct sockaddr *addr, socklen_t len) {
static bind_fn real;
if (!real) real = (bind_fn)dlsym(RTLD_NEXT, "bind");
if (!real) real = (bind_fn)bl_next("bind");
if (bl_udevmon_stand_in(fd)) {
if (addr == NULL || len < sizeof(struct sockaddr_nl) || addr->sa_family != AF_NETLINK) {
errno = EINVAL;
@@ -151,7 +152,7 @@ int bind(int fd, const struct sockaddr *addr, socklen_t len) {
int listen(int fd, int backlog) {
static listen_fn real;
if (!real) real = (listen_fn)dlsym(RTLD_NEXT, "listen");
if (!real) real = (listen_fn)bl_next("listen");
int ret = real(fd, backlog);
if (ret == 0) record_port(fd, 0);
return ret;
@@ -233,7 +234,7 @@ static int guest_peer(int fd) {
int connect(int fd, const struct sockaddr *addr, socklen_t len) {
static connect_fn real;
if (!real) real = (connect_fn)dlsym(RTLD_NEXT, "connect");
if (!real) real = (connect_fn)bl_next("connect");
bind_before_loopback_connect(fd, addr, len);
unsigned peer = loopback_family(addr, len) ? addr_port(addr, len) : 0;
if (peer) record_port(fd, peer);
@@ -249,7 +250,7 @@ int connect(int fd, const struct sockaddr *addr, socklen_t len) {
int accept4(int fd, struct sockaddr *addr, socklen_t *len, int flags) {
static accept4_fn real;
if (!real) real = (accept4_fn)dlsym(RTLD_NEXT, "accept4");
if (!real) real = (accept4_fn)bl_next("accept4");
for (;;) {
int ret = real(fd, addr, len, flags);
if (ret < 0) return ret;
@@ -327,7 +328,7 @@ static int maybe_answer_lsof(const char *path, char *const argv[]) {
int execve(const char *path, char *const argv[], char *const envp[]) {
static int (*real)(const char *, char *const[], char *const[]);
if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execve");
if (!real) real = (int (*)(const char *, char *const[], char *const[]))bl_next("execve");
maybe_answer_lsof(path, argv);
bl_binfmt_before(path, argv, envp);
real(path, argv, envp);
@@ -353,7 +354,7 @@ static int answer_lsof_via_spawn(const char *path, char *const argv[], pid_t *pi
int execvp(const char *file, char *const argv[]) {
static int (*real)(const char *, char *const[]);
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execvp");
if (!real) real = (int (*)(const char *, char *const[]))bl_next("execvp");
maybe_answer_lsof(file, argv);
bl_binfmt_before_search(file, argv, environ);
real(file, argv);
@@ -362,7 +363,7 @@ int execvp(const char *file, char *const argv[]) {
int execv(const char *path, char *const argv[]) {
static int (*real)(const char *, char *const[]);
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execv");
if (!real) real = (int (*)(const char *, char *const[]))bl_next("execv");
maybe_answer_lsof(path, argv);
bl_binfmt_before(path, argv, environ);
real(path, argv);
@@ -377,6 +378,6 @@ int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *
return answer_lsof_via_spawn(path, argv, pid);
}
static spawn_fn real;
if (!real) real = (spawn_fn)dlsym(RTLD_NEXT, "posix_spawn");
if (!real) real = (spawn_fn)bl_next("posix_spawn");
return bl_binfmt_spawn(pid, path, fa, attr, argv, envp, real);
}
+13 -12
View File
@@ -13,7 +13,6 @@
*/
#define _GNU_SOURCE 1
#include <arpa/inet.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
@@ -32,6 +31,8 @@
#include <time.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
#define LINK_FILE "/etc/droiddeck-net"
#define LO_NAME "lo"
#define LO_INDEX 1
@@ -68,7 +69,7 @@ typedef FILE *(*fopen_fn)(const char *, const char *);
static int read_link(struct link *link) {
static fopen_fn real_fopen;
if (real_fopen == NULL) real_fopen = (fopen_fn) dlsym(RTLD_NEXT, "fopen");
if (real_fopen == NULL) real_fopen = (fopen_fn) bl_next("fopen");
FILE *f = real_fopen ? real_fopen(LINK_FILE, "re") : NULL;
if (f == NULL) return 0;
@@ -222,8 +223,8 @@ static freeifaddrs_fn real_freeifaddrs;
int getifaddrs(struct ifaddrs **out) {
typedef int (*getifaddrs_fn)(struct ifaddrs **);
static getifaddrs_fn real;
if (real == NULL) real = (getifaddrs_fn) dlsym(RTLD_NEXT, "getifaddrs");
if (real_freeifaddrs == NULL) real_freeifaddrs = (freeifaddrs_fn) dlsym(RTLD_NEXT, "freeifaddrs");
if (real == NULL) real = (getifaddrs_fn) bl_next("getifaddrs");
if (real_freeifaddrs == NULL) real_freeifaddrs = (freeifaddrs_fn) bl_next("freeifaddrs");
struct ifaddrs *list = NULL;
int asked = !fresh(&ifaddrs_unusable_at);
int rc = -1, saved = errno;
@@ -273,7 +274,7 @@ void freeifaddrs(struct ifaddrs *list) {
free((struct entry *) list - 1);
return;
}
if (real_freeifaddrs == NULL) real_freeifaddrs = (freeifaddrs_fn) dlsym(RTLD_NEXT, "freeifaddrs");
if (real_freeifaddrs == NULL) real_freeifaddrs = (freeifaddrs_fn) bl_next("freeifaddrs");
real_freeifaddrs(list);
}
@@ -286,7 +287,7 @@ struct name_block {
struct if_nameindex *if_nameindex(void) {
typedef struct if_nameindex *(*if_nameindex_fn)(void);
static if_nameindex_fn real;
if (real == NULL) real = (if_nameindex_fn) dlsym(RTLD_NEXT, "if_nameindex");
if (real == NULL) real = (if_nameindex_fn) bl_next("if_nameindex");
struct if_nameindex *list = NULL;
int asked = !fresh(&nameindex_unusable_at);
int saved = errno;
@@ -323,12 +324,12 @@ void if_freenameindex(struct if_nameindex *list) {
free(list);
return;
}
((if_freenameindex_fn) dlsym(RTLD_NEXT, "if_freenameindex"))(list);
((if_freenameindex_fn) bl_next("if_freenameindex"))(list);
}
unsigned int if_nametoindex(const char *name) {
typedef unsigned int (*if_nametoindex_fn)(const char *);
unsigned int index = ((if_nametoindex_fn) dlsym(RTLD_NEXT, "if_nametoindex"))(name);
unsigned int index = ((if_nametoindex_fn) bl_next("if_nametoindex"))(name);
if (index != 0 || name == NULL) return index;
int saved = errno;
struct link link;
@@ -340,7 +341,7 @@ unsigned int if_nametoindex(const char *name) {
char *if_indextoname(unsigned int index, char name[IF_NAMESIZE]) {
typedef char *(*if_indextoname_fn)(unsigned int, char *);
char *found = ((if_indextoname_fn) dlsym(RTLD_NEXT, "if_indextoname"))(index, name);
char *found = ((if_indextoname_fn) bl_next("if_indextoname"))(index, name);
if (found || name == NULL) return found;
int saved = errno;
struct link link;
@@ -439,7 +440,7 @@ int ioctl(int fd, unsigned long request, ...) {
int rc;
if (bl_ntsync_ioctl(fd, request, arg, &rc)) return rc;
if (bl_drm_gem_close(fd, request, arg, &rc)) return rc;
if (real == NULL) real = (ioctl_fn) dlsym(RTLD_NEXT, "ioctl");
if (real == NULL) real = (ioctl_fn) bl_next("ioctl");
if (!bl_kgsl_poll(real, fd, request, arg, &rc)) rc = real(fd, request, arg);
bl_kgsl_chip_id_fixup(request, arg, rc);
if (arg == NULL || !is_link_query(request)) return rc;
@@ -579,12 +580,12 @@ static FILE *open_stream(fopen_fn real, const char *path, const char *mode) {
FILE *fopen(const char *path, const char *mode) {
static fopen_fn real;
if (real == NULL) real = (fopen_fn) dlsym(RTLD_NEXT, "fopen");
if (real == NULL) real = (fopen_fn) bl_next("fopen");
return open_stream(real, path, mode);
}
FILE *fopen64(const char *path, const char *mode) {
static fopen_fn real;
if (real == NULL) real = (fopen_fn) dlsym(RTLD_NEXT, "fopen64");
if (real == NULL) real = (fopen_fn) bl_next("fopen64");
return open_stream(real, path, mode);
}
+110
View File
@@ -0,0 +1,110 @@
/*
* next.c: the definition a hook passes a call on to, as dlsym(RTLD_NEXT, name) finds it.
*
* Before glibc 2.34 a thread's first dlsym callocs dlerror's state. A hook first called from inside
* an allocator that is still starting up - Chromium's PartitionAlloc calls syscall() from its first
* malloc - re-enters the allocator there, and it waits for itself forever: every Electron and
* Chromium program on Steam's x86 runtime (glibc 2.31) hung at start. There the libraries after this
* one are searched by hand, with nothing allocated; an object or symbol that walk cannot handle is
* left to dlsym.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
#include <stdint.h>
#include <string.h>
/* NEXT_TEST: tools/tests builds the walk on a newer glibc. */
#if __GLIBC_PREREQ(2, 34) && !defined(NEXT_TEST)
__attribute__((visibility("hidden"))) void *bl_next(const char *name) {
return dlsym(RTLD_NEXT, name);
}
#else
struct search {
const char *name;
uint32_t hash;
int passed;
int unsure;
void *found;
};
static uint32_t gnu_hash(const char *name) {
uint32_t h = 5381;
for (; *name; name++) h = h * 33 + (unsigned char)*name;
return h;
}
/* glibc relocates these entries in place, but not every object's (the vDSO's). */
static ElfW(Addr) address(const struct dl_phdr_info *info, ElfW(Addr) value) {
return value < info->dlpi_addr ? info->dlpi_addr + value : value;
}
/* The object's definition, as glibc 2.36 and later pick it: unversioned or the default version. */
static void find(const struct dl_phdr_info *info, const ElfW(Dyn) *dyn, struct search *s) {
const uint32_t *table = NULL;
const ElfW(Sym) *symbols = NULL;
const char *strings = NULL;
const ElfW(Half) *versions = NULL;
for (; dyn->d_tag != DT_NULL; dyn++) {
if (dyn->d_tag == DT_GNU_HASH) table = (const uint32_t *)address(info, dyn->d_un.d_ptr);
else if (dyn->d_tag == DT_SYMTAB) symbols = (const ElfW(Sym) *)address(info, dyn->d_un.d_ptr);
else if (dyn->d_tag == DT_STRTAB) strings = (const char *)address(info, dyn->d_un.d_ptr);
else if (dyn->d_tag == DT_VERSYM) versions = (const ElfW(Half) *)address(info, dyn->d_un.d_ptr);
}
if (!table || !symbols || !strings || !table[0]) {
s->unsure = 1;
return;
}
const uint32_t *buckets = (const uint32_t *)((const ElfW(Addr) *)(table + 4) + table[2]);
const uint32_t *chain = buckets + table[0] - table[1];
const ElfW(Sym) *only = NULL;
int matches = 0, defaults = 0;
for (uint32_t i = buckets[s->hash % table[0]]; i >= table[1] && i != 0; i++) {
const ElfW(Sym) *sym = &symbols[i];
if ((chain[i] | 1) == (s->hash | 1) && sym->st_shndx != SHN_UNDEF && sym->st_value != 0 &&
ELF64_ST_BIND(sym->st_info) != STB_LOCAL && !strcmp(strings + sym->st_name, s->name)) {
if (ELF64_ST_TYPE(sym->st_info) == STT_GNU_IFUNC || ELF64_ST_TYPE(sym->st_info) == STT_TLS) {
s->unsure = 1;
return;
}
if (!versions || (versions[i] & 0x7fff) < 2) {
only = sym;
defaults = 1;
break;
}
matches++;
if (!(versions[i] & 0x8000) && defaults++ == 0) only = sym;
}
if (chain[i] & 1) break;
}
/* Only hidden versions: dlsym's own rules decide. */
if (defaults == 1) s->found = (void *)(info->dlpi_addr + only->st_value);
else if (matches) s->unsure = 1;
}
static int visit(struct dl_phdr_info *info, size_t size, void *data) {
struct search *s = data;
const ElfW(Dyn) *dyn = NULL;
(void)size;
for (int i = 0; i < info->dlpi_phnum; i++)
if (info->dlpi_phdr[i].p_type == PT_DYNAMIC) dyn = (const ElfW(Dyn) *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr);
if (!dyn) return 0;
if (!s->passed) {
s->passed = dyn == _DYNAMIC;
return 0;
}
find(info, dyn, s);
return s->found || s->unsure;
}
__attribute__((visibility("hidden"))) void *bl_next(const char *name) {
struct search s = {name, gnu_hash(name), 0, 0, NULL};
dl_iterate_phdr(visit, &s);
if (s.found || (s.passed && !s.unsure)) return s.found;
return dlsym(RTLD_NEXT, name);
}
#endif
+3 -1
View File
@@ -12,6 +12,8 @@
#include <sys/statvfs.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
/* NOEXEC_TEST: tools/tests builds it for the host. */
#if defined(__aarch64__) || defined(NOEXEC_TEST)
@@ -42,7 +44,7 @@ static int (*real_mprotect)(void *, size_t, int);
/* Looked up before anything runs: Wine calls mprotect from its signal handlers, where dlsym is unsafe. */
__attribute__((constructor)) static void noexec_init(void) {
real_mprotect = (int (*)(void *, size_t, int))dlsym(RTLD_NEXT, "mprotect");
real_mprotect = (int (*)(void *, size_t, int))bl_next("mprotect");
}
int mprotect(void *addr, size_t len, int prot) {
+8 -7
View File
@@ -1,7 +1,6 @@
/* droiddeck-ntsync: a userspace implementation of the /dev/ntsync interface for kernels without the driver.
* Credit: ntsync, the kernel driver and its Wine client, by Elizabeth Figura. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
@@ -21,6 +20,8 @@
#include <time.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
struct ns_sem_args {
uint32_t count;
uint32_t max;
@@ -194,7 +195,7 @@ static int ns_on(void) {
}
static int ns_close_fd(int fd) {
if (!ns_real_close) ns_real_close = (int (*)(int))dlsym(RTLD_NEXT, "close");
if (!ns_real_close) ns_real_close = (int (*)(int))bl_next("close");
return ns_real_close(fd);
}
@@ -1242,7 +1243,7 @@ int bl_udevmon_recvmsg(int fd, struct msghdr *msg, int flags, ssize_t (*real_rec
ssize_t recvmsg(int fd, struct msghdr *msg, int flags) {
static ssize_t (*real)(int, struct msghdr *, int);
if (!real) real = (ssize_t (*)(int, struct msghdr *, int))dlsym(RTLD_NEXT, "recvmsg");
if (!real) real = (ssize_t (*)(int, struct msghdr *, int))bl_next("recvmsg");
/* udevmon.c's stand-in netlink sockets first: their messages are dressed as udevd's. */
ssize_t answered;
if (msg && bl_udevmon_recvmsg(fd, msg, flags, real, &answered)) return answered;
@@ -1266,7 +1267,7 @@ int close(int fd) {
int dup2(int oldfd, int newfd) {
static int (*real)(int, int);
if (!real) real = (int (*)(int, int))dlsym(RTLD_NEXT, "dup2");
if (!real) real = (int (*)(int, int))bl_next("dup2");
if (oldfd != newfd && newfd >= 0) bl_fsync_fds_closed((unsigned int)newfd, (unsigned int)newfd);
uint64_t *t = __atomic_load_n(&ns_fdt, __ATOMIC_ACQUIRE);
if (t && oldfd != newfd && newfd >= 0 && newfd < NS_FDS && __atomic_load_n(&t[newfd], __ATOMIC_ACQUIRE)) ns_forget(newfd);
@@ -1275,7 +1276,7 @@ int dup2(int oldfd, int newfd) {
int dup3(int oldfd, int newfd, int flags) {
static int (*real)(int, int, int);
if (!real) real = (int (*)(int, int, int))dlsym(RTLD_NEXT, "dup3");
if (!real) real = (int (*)(int, int, int))bl_next("dup3");
if (oldfd != newfd && newfd >= 0) bl_fsync_fds_closed((unsigned int)newfd, (unsigned int)newfd);
uint64_t *t = __atomic_load_n(&ns_fdt, __ATOMIC_ACQUIRE);
if (t && oldfd != newfd && newfd >= 0 && newfd < NS_FDS && __atomic_load_n(&t[newfd], __ATOMIC_ACQUIRE)) ns_forget(newfd);
@@ -1291,7 +1292,7 @@ static void ns_forget_range(unsigned int first, unsigned int last) {
int close_range(unsigned int first, unsigned int last, int flags) {
static int (*real)(unsigned int, unsigned int, int);
if (!real) real = (int (*)(unsigned int, unsigned int, int))dlsym(RTLD_NEXT, "close_range");
if (!real) real = (int (*)(unsigned int, unsigned int, int))bl_next("close_range");
if (!(flags & CLOSE_RANGE_CLOEXEC)) {
bl_fsync_fds_closed(first, last);
ns_forget_range(first, last);
@@ -1305,7 +1306,7 @@ int close_range(unsigned int first, unsigned int last, int flags) {
void closefrom(int lowfd) {
static void (*real)(int);
if (!real) real = (void (*)(int))dlsym(RTLD_NEXT, "closefrom");
if (!real) real = (void (*)(int))bl_next("closefrom");
if (lowfd >= 0) {
bl_fsync_fds_closed((unsigned int)lowfd, ~0u);
ns_forget_range((unsigned int)lowfd, ~0u);
+4 -3
View File
@@ -25,7 +25,6 @@
* A process status is served without the container's tracer in it; see tracer.c.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
@@ -33,6 +32,8 @@
#include <string.h>
#include <sys/types.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
int bl_status_without_tracer(const char *path, int flags) __attribute__((visibility("hidden")));
int bl_writable_retry(int dirfd, const char *path, int flags) __attribute__((visibility("hidden")));
int bl_ntsync_open(const char *path, int flags) __attribute__((visibility("hidden")));
@@ -76,7 +77,7 @@ int open(const char *path, int flags, ...) {
}
if ((fd = bl_status_without_tracer(path, flags)) >= 0) return fd;
if ((fd = bl_ntsync_open(path, flags)) != -2) return fd;
if (real_open == NULL) real_open = dlsym(RTLD_NEXT, "open");
if (real_open == NULL) real_open = bl_next("open");
if (real_open == NULL) {
errno = ENOSYS;
return -1;
@@ -102,7 +103,7 @@ int openat(int dirfd, const char *path, int flags, ...) {
}
if ((fd = bl_status_without_tracer(path, flags)) >= 0) return fd;
if ((fd = bl_ntsync_open(path, flags)) != -2) return fd;
if (real_openat == NULL) real_openat = dlsym(RTLD_NEXT, "openat");
if (real_openat == NULL) real_openat = bl_next("openat");
if (real_openat == NULL) {
errno = ENOSYS;
return -1;
+3 -2
View File
@@ -14,7 +14,6 @@
* BL_NO_PATH_CACHE=1 turns it off.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
@@ -24,6 +23,8 @@
#include <time.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
#define PATH_CACHE_SLOTS 256
#define PATH_CACHE_KEY_MAX 512
#define PATH_CACHE_TTL_NS 2000000000LL
@@ -148,7 +149,7 @@ static int cached_access(const char *path, int mode, int flags, int (*check)(con
static int (*next_faccessat(void))(int, const char *, int, int) {
static int (*real)(int, const char *, int, int);
if (real == NULL) real = (int (*)(int, const char *, int, int))dlsym(RTLD_NEXT, "faccessat");
if (real == NULL) real = (int (*)(int, const char *, int, int))bl_next("faccessat");
return real;
}
+3 -2
View File
@@ -7,7 +7,6 @@
* the link back finds the head without depending on libc's layout.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
@@ -19,6 +18,8 @@
#include <sys/uio.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
#ifndef SYS_openat2
#define SYS_openat2 437
#endif
@@ -123,7 +124,7 @@ long syscall(long number, ...) {
return handled;
}
if (!real_syscall) {
real_syscall = (long (*)(long, ...)) dlsym(RTLD_NEXT, "syscall");
real_syscall = (long (*)(long, ...)) bl_next("syscall");
}
if (number == SYS_openat2) {
if (!__atomic_load_n(&openat2_missing, __ATOMIC_RELAXED)) {
+3 -2
View File
@@ -19,13 +19,14 @@
* card. Every other path is passed through untouched.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/statfs.h>
#include <sys/statvfs.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
/* A library root, or its steamapps, resolved to the directory that holds the games. Returns the
* rewritten path in `buf`, or NULL when `path` is not one of ours and should be left alone. */
static const char *content_of(const char *path, char *buf, size_t len) {
@@ -64,7 +65,7 @@ static const char *content_of(const char *path, char *buf, size_t len) {
char buf[4096]; \
const char *content; \
if (!real) \
real = dlsym(RTLD_NEXT, #name); \
real = bl_next(#name); \
if (!real) \
return -1; \
content = content_of(path, buf, sizeof(buf)); \
+11 -10
View File
@@ -9,7 +9,6 @@
* missing entry therefore does not prove the operation did not happen.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
@@ -22,6 +21,8 @@
#include <time.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
static int (*next_fstat)(int, struct stat *);
static int (*next_fallocate)(int, int, off_t, off_t);
static int (*next_posix_fallocate)(int, off_t, off_t);
@@ -93,7 +94,7 @@ static int on_selected_device(int fd, const struct stat *known) {
initialize();
if (!enabled) return 0;
if (known == NULL) {
if (next_fstat == NULL) next_fstat = dlsym(RTLD_NEXT, "fstat");
if (next_fstat == NULL) next_fstat = bl_next("fstat");
if (next_fstat == NULL || next_fstat(fd, &local) != 0) return 0;
known = &local;
}
@@ -181,7 +182,7 @@ static int cannot_reserve(long long device) {
static void note_cannot_reserve(int fd) {
struct stat st;
if (next_fstat == NULL) next_fstat = dlsym(RTLD_NEXT, "fstat");
if (next_fstat == NULL) next_fstat = bl_next("fstat");
if (next_fstat == NULL || next_fstat(fd, &st) != 0 || cannot_reserve((long long)st.st_dev)) return;
unsigned int slot = __atomic_load_n(&unreserved_count, __ATOMIC_RELAXED);
while (slot < 16 && !__atomic_compare_exchange_n(&unreserved_count, &slot, slot + 1, 0, __ATOMIC_ACQ_REL,
@@ -192,7 +193,7 @@ static void note_cannot_reserve(int fd) {
int fallocate(int fd, int mode, off_t offset, off_t length) {
int entry_errno = errno;
if (next_fallocate == NULL) next_fallocate = dlsym(RTLD_NEXT, "fallocate");
if (next_fallocate == NULL) next_fallocate = bl_next("fallocate");
if (next_fallocate == NULL) { errno = ENOSYS; return -1; }
int selected = on_selected_device(fd, NULL);
errno = entry_errno;
@@ -208,7 +209,7 @@ int fallocate(int fd, int mode, off_t offset, off_t length) {
int posix_fallocate(int fd, off_t offset, off_t length) {
int entry_errno = errno;
if (next_posix_fallocate == NULL) next_posix_fallocate = dlsym(RTLD_NEXT, "posix_fallocate");
if (next_posix_fallocate == NULL) next_posix_fallocate = bl_next("posix_fallocate");
if (next_posix_fallocate == NULL) { errno = entry_errno; return ENOSYS; }
int selected = on_selected_device(fd, NULL);
errno = entry_errno;
@@ -229,7 +230,7 @@ int posix_fallocate(int fd, off_t offset, off_t length) {
ssize_t pwrite(int fd, const void *buffer, size_t count, off_t offset) {
int entry_errno = errno;
if (next_pwrite == NULL) next_pwrite = dlsym(RTLD_NEXT, "pwrite");
if (next_pwrite == NULL) next_pwrite = bl_next("pwrite");
if (next_pwrite == NULL) { errno = ENOSYS; return -1; }
initialize();
uint64_t sequence = enabled ? add(&pwrite_seen, 1) : 1;
@@ -254,14 +255,14 @@ ssize_t pwrite(int fd, const void *buffer, size_t count, off_t offset) {
int ftruncate(int fd, off_t length) {
int entry_errno = errno;
if (next_ftruncate == NULL) next_ftruncate = dlsym(RTLD_NEXT, "ftruncate");
if (next_ftruncate == NULL) next_ftruncate = bl_next("ftruncate");
if (next_ftruncate == NULL) { errno = ENOSYS; return -1; }
int selected = on_selected_device(fd, NULL);
uint64_t start = selected ? now_ns() : 0;
struct stat st;
int known = 0;
if (steam_client()) {
if (next_fstat == NULL) next_fstat = dlsym(RTLD_NEXT, "fstat");
if (next_fstat == NULL) next_fstat = bl_next("fstat");
/* staging_file() reads /proc/self/fd, a round trip through proot: skip it for a shrink, which
* is passed through untouched either way. */
known = next_fstat != NULL && next_fstat(fd, &st) == 0 && S_ISREG(st.st_mode) &&
@@ -293,7 +294,7 @@ int ftruncate(int fd, off_t length) {
int fstat(int fd, struct stat *out) {
int entry_errno = errno;
if (next_fstat == NULL) next_fstat = dlsym(RTLD_NEXT, "fstat");
if (next_fstat == NULL) next_fstat = bl_next("fstat");
if (next_fstat == NULL) { errno = ENOSYS; return -1; }
initialize();
errno = entry_errno;
@@ -314,7 +315,7 @@ int fstat(int fd, struct stat *out) {
int fsync(int fd) {
int entry_errno = errno;
if (next_fsync == NULL) next_fsync = dlsym(RTLD_NEXT, "fsync");
if (next_fsync == NULL) next_fsync = bl_next("fsync");
if (next_fsync == NULL) { errno = ENOSYS; return -1; }
int selected = on_selected_device(fd, NULL);
errno = entry_errno;
+6 -4
View File
@@ -18,6 +18,8 @@
#include <string.h>
#include <sys/stat.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
#define STANDIN_DIR "/tmp/droiddeck-tracefs"
#define KGSL_TOTAL "/sys/class/kgsl/kgsl/page_alloc"
@@ -54,10 +56,10 @@ static const char gpu_mem_total_format[] =
static struct tracefs_instance *standin;
struct tracefs_instance *tracefs_instance_create(const char *name) {
struct tracefs_instance *(*real)(const char *) = dlsym(RTLD_NEXT, "tracefs_instance_create");
struct tracefs_instance *(*real)(const char *) = bl_next("tracefs_instance_create");
struct tracefs_instance *instance = real ? real(name) : NULL;
if (instance || !name) return instance;
struct tracefs_instance *(*alloc)(const char *, const char *) = dlsym(RTLD_NEXT, "tracefs_instance_alloc");
struct tracefs_instance *(*alloc)(const char *, const char *) = bl_next("tracefs_instance_alloc");
if (!alloc) return NULL;
char path[PATH_MAX];
mkdir(STANDIN_DIR, 0700);
@@ -69,7 +71,7 @@ struct tracefs_instance *tracefs_instance_create(const char *name) {
struct tep_handle *tracefs_local_events(const char *tracing_dir) {
if (!tracing_dir || strncmp(tracing_dir, STANDIN_DIR "/", sizeof(STANDIN_DIR))) {
struct tep_handle *(*real)(const char *) = dlsym(RTLD_NEXT, "tracefs_local_events");
struct tep_handle *(*real)(const char *) = bl_next("tracefs_local_events");
return real ? real(tracing_dir) : NULL;
}
struct tep_handle *(*tep_alloc)(void) = dlsym(RTLD_DEFAULT, "tep_alloc");
@@ -84,7 +86,7 @@ int tracefs_iterate_raw_events(struct tep_handle *tep, struct tracefs_instance *
if (!standin || instance != standin) {
int (*real)(struct tep_handle *, struct tracefs_instance *, void *, int,
int (*)(struct tep_event *, struct tep_record *, int, void *), void *) =
dlsym(RTLD_NEXT, "tracefs_iterate_raw_events");
bl_next("tracefs_iterate_raw_events");
return real ? real(tep, instance, cpus, cpu_size, callback, context) : -1;
}
struct tep_event *(*find)(struct tep_handle *, int) = dlsym(RTLD_DEFAULT, "tep_find_event");
+14 -2
View File
@@ -34,14 +34,26 @@ static const char *skip_digits(const char *s) {
return end == s ? NULL : end;
}
/* /proc/{self,thread-self,<pid>}[/task/<tid>]/status */
static int is_own(const char *digits, const char *end) {
long id = 0;
for (; digits < end; digits++) {
if (id > 99999999) return 0;
id = id * 10 + (*digits - '0');
}
return id == (long)getpid() || id == syscall(SYS_gettid);
}
/* /proc/{self,thread-self,<own pid or tid>}[/task/<tid>]/status. A program asks whether it is
* traced about itself; another process's status is read as it is. Chromium opens each renderer's
* and hands it over as a read-only file, which it checks: a memfd is open for writing, so Chromium
* and every Electron program aborted as its first page connected. */
static int is_status_path(const char *path) {
const char *rest;
if (path == NULL || strncmp(path, "/proc/", 6) != 0) return 0;
path += 6;
if (strncmp(path, "self/", 5) == 0) rest = path + 4;
else if (strncmp(path, "thread-self/", 12) == 0) rest = path + 11;
else if ((rest = skip_digits(path)) == NULL) return 0;
else if ((rest = skip_digits(path)) == NULL || !is_own(path, rest)) return 0;
if (strncmp(rest, "/task/", 6) == 0 && (rest = skip_digits(rest + 6)) == NULL) return 0;
return strcmp(rest, "/status") == 0;
}
+5 -3
View File
@@ -38,6 +38,8 @@
#include <stdio.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
struct stand_in {
int fd; /* the descriptor handed out, -1 when the slot is free */
int peer; /* the other end, kept open */
@@ -131,7 +133,7 @@ int socket(int domain, int type, int protocol) {
static int (*real_socket)(int, int, int);
int fd;
if (real_socket == NULL) real_socket = dlsym(RTLD_NEXT, "socket");
if (real_socket == NULL) real_socket = bl_next("socket");
if (real_socket == NULL) {
errno = ENOSYS;
return -1;
@@ -168,7 +170,7 @@ int getsockname(int fd, struct sockaddr *addr, socklen_t *len) {
*len = sizeof(nl);
return 0;
}
if (real_getsockname == NULL) real_getsockname = dlsym(RTLD_NEXT, "getsockname");
if (real_getsockname == NULL) real_getsockname = bl_next("getsockname");
if (real_getsockname == NULL) {
errno = ENOSYS;
return -1;
@@ -187,7 +189,7 @@ int setsockopt(int fd, int level, int optname, const void *optval, socklen_t opt
if (level == SOL_SOCKET && (optname == SO_ATTACH_FILTER || optname == SO_DETACH_FILTER))
return 0;
}
if (real_setsockopt == NULL) real_setsockopt = dlsym(RTLD_NEXT, "setsockopt");
if (real_setsockopt == NULL) real_setsockopt = bl_next("setsockopt");
if (real_setsockopt == NULL) {
errno = ENOSYS;
return -1;
+4 -3
View File
@@ -15,11 +15,12 @@
* session, and any other property, is passed straight on.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
/* Xlib's types, without needing its headers in the build image. */
typedef struct _XDisplay Display;
typedef unsigned long Atom;
@@ -41,13 +42,13 @@ static int following(void) {
int XChangeProperty(Display *dpy, Window w, Atom property, Atom type, int format, int mode,
const unsigned char *data, int nelements) {
static change_property_fn real;
if (!real) real = (change_property_fn)dlsym(RTLD_NEXT, "XChangeProperty");
if (!real) real = (change_property_fn)bl_next("XChangeProperty");
if (!real) return 0;
if (following() && format == 32 && nelements == 4 && data) {
static intern_atom_fn intern;
static Display *atom_dpy;
static Atom mode_control;
if (!intern) intern = (intern_atom_fn)dlsym(RTLD_NEXT, "XInternAtom");
if (!intern) intern = (intern_atom_fn)bl_next("XInternAtom");
/* Atoms belong to a connection; the client's is the one that writes this. */
if (intern && atom_dpy != dpy) {
mode_control = intern(dpy, "GAMESCOPE_XWAYLAND_MODE_CONTROL", 1);
+1 -1
View File
@@ -61,7 +61,7 @@ class BinfmtPreloadTest(unittest.TestCase):
cls.library = cls.tmp / "libbinfmt.so"
subprocess.run(["gcc", "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror", "-DBINFMT_TEST",
'-DBINFMT_HANDLER="%s"' % cls.handler, "-o", str(cls.library),
"-pthread"] + [str(PRELOAD / name) for name in ("binfmt.c", "net.c", "udevmon.c", "pathcache.c")] + ["-ldl"],
"-pthread"] + [str(PRELOAD / name) for name in ("binfmt.c", "net.c", "udevmon.c", "pathcache.c", "next.c")] + ["-ldl"],
check=True)
source = cls.tmp / "program.c"
source.write_text(PROGRAM)
+1 -1
View File
@@ -16,7 +16,7 @@ class CdpGuardTest(unittest.TestCase):
def setUpClass(cls):
cls.build = tempfile.TemporaryDirectory()
cls.library = Path(cls.build.name) / "net.so"
subprocess.run(["cc", "-shared", "-fPIC", "-O2", str(SOURCE), str(SOURCE.parent / "binfmt.c"), "-x", "c", "-", "-ldl",
subprocess.run(["cc", "-shared", "-fPIC", "-O2", str(SOURCE), str(SOURCE.parent / "binfmt.c"), str(SOURCE.parent / "next.c"), "-x", "c", "-", "-ldl",
"-o", str(cls.library)],
input=b"int bl_udevmon_stand_in(int fd) { return 0; }", check=True, capture_output=True)
+1 -1
View File
@@ -348,7 +348,7 @@ class LaunchTest(FexTestCase):
self.assertNotIn("FEX_ROOTFS", guest)
self.assertEqual(guest["FEX_TSOENABLED"], "1")
self.assertTrue(guest["FEX_APP_CONFIG_LOCATION"].endswith("/config/"))
self.assertEqual((guest["FEX_SILENTLOG"], guest["FEX_OUTPUTLOG"]), ("0", "stderr"))
self.assertEqual((guest["FEX_SILENTLOG"], guest["FEX_OUTPUTLOG"]), ("0", ""))
guest = FEX["guest_environment"]({"portable": False}, {"LD_PRELOAD": str(arm), "FEX_PORTABLE": "1", "FEX_OUTPUTLOG": "/tmp/fex.log"})
self.assertEqual(guest["FEX_OUTPUTLOG"], "/tmp/fex.log")
self.assertNotIn("LD_PRELOAD", guest)
+1 -1
View File
@@ -319,7 +319,7 @@ class FsyncPreloadTest(unittest.TestCase):
source = base / "fsync-test.c"
source.write_text(PROGRAM)
subprocess.run(["cc", "-O2", "-Wall", "-fPIC", "-shared", "-pthread", "-o", str(cls.lib),
str(PRELOAD / "fsync.c"), str(PRELOAD / "robust.c"), str(PRELOAD / "noexec.c"), "-ldl"], check=True)
str(PRELOAD / "fsync.c"), str(PRELOAD / "robust.c"), str(PRELOAD / "noexec.c"), str(PRELOAD / "next.c"), "-ldl"], check=True)
subprocess.run(["cc", "-O2", "-Wall", "-pthread", "-o", str(cls.exe), str(source)], check=True)
@classmethod
+129
View File
@@ -0,0 +1,129 @@
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
PRELOAD = Path(__file__).resolve().parents[1] / "linuxfs/preload"
PROBE = r"""
#define _GNU_SOURCE
#include <dlfcn.h>
void *bl_next(const char *name) __attribute__((visibility("hidden")));
void *__real_dlsym(void *handle, const char *name);
/* Lookups the walk left to dlsym. */
static int walking;
static long passed_on;
void *__wrap_dlsym(void *handle, const char *name) {
passed_on += walking;
return __real_dlsym(handle, name);
}
void *probe_walk(const char *name) {
walking = 1;
void *found = bl_next(name);
walking = 0;
return found;
}
void *probe_dlsym(const char *name) { return dlsym(RTLD_NEXT, name); }
long probe_passed_on(void) { return passed_on; }
"""
# Counts the allocations a thread makes, as an allocator that is still starting up would see them.
COUNT = r"""
#include <stddef.h>
extern void *__libc_malloc(size_t);
extern void *__libc_calloc(size_t, size_t);
extern void *__libc_realloc(void *, size_t);
static __thread long made;
void *malloc(size_t size) { made++; return __libc_malloc(size); }
void *calloc(size_t count, size_t size) { made++; return __libc_calloc(count, size); }
void *realloc(void *old, size_t size) { made++; return __libc_realloc(old, size); }
void count_reset(void) { made = 0; }
long count_get(void) { return made; }
"""
PROGRAM = r"""
#include <pthread.h>
#include <stdio.h>
void *probe_walk(const char *name);
void *probe_dlsym(const char *name);
long probe_passed_on(void);
void count_reset(void);
long count_get(void);
static void *first_lookup(void *made) {
long before = probe_passed_on();
count_reset();
void *found = probe_walk("openat");
((long *)made)[0] = found ? count_get() : -1;
((long *)made)[1] = probe_passed_on() - before;
return NULL;
}
int main(int argc, char **argv) {
int differ = 0;
for (int i = 1; i < argc; i++) {
void *walked = probe_walk(argv[i]), *found = probe_dlsym(argv[i]);
if (walked != found) {
printf("%s: %p, dlsym %p\n", argv[i], walked, found);
differ = 1;
}
}
long made[2] = {0, 0};
pthread_t thread;
pthread_create(&thread, NULL, first_lookup, made);
pthread_join(thread, NULL);
printf("allocations %ld, left to dlsym %ld\n", made[0], made[1]);
return differ;
}
"""
NAMES = ["open", "openat", "read", "recvmsg", "syscall", "close_range", "fstatat", "stat", "pthread_create",
"dlopen", "malloc", "calloc", "memcpy", "strlen", "environ", "no_such_symbol"]
@unittest.skipUnless(sys.platform.startswith("linux") and shutil.which("cc"), "needs Linux and a C compiler")
class NextTest(unittest.TestCase):
"""bl_next built as on glibc before 2.34, where the walk replaces dlsym(RTLD_NEXT)."""
@classmethod
def setUpClass(cls):
cls.build = tempfile.TemporaryDirectory()
build = Path(cls.build.name)
(build / "probe.c").write_text(PROBE)
(build / "count.c").write_text(COUNT)
(build / "program.c").write_text(PROGRAM)
cls.count = build / "libcount.so"
cls.program = build / "program"
subprocess.run(["cc", "-shared", "-fPIC", "-O2", "-DNEXT_TEST", "-Wl,--wrap=dlsym", "-o", str(build / "libprobe.so"),
str(build / "probe.c"), str(PRELOAD / "next.c"), "-ldl"], check=True)
subprocess.run(["cc", "-shared", "-fPIC", "-O2", "-o", str(cls.count), str(build / "count.c")], check=True)
subprocess.run(["cc", "-O2", "-pthread", "-o", str(cls.program), str(build / "program.c"),
str(build / "libprobe.so"), str(cls.count), "-Wl,-rpath," + str(build)], check=True)
@classmethod
def tearDownClass(cls):
cls.build.cleanup()
def run_program(self):
return subprocess.run([str(self.program)] + NAMES, capture_output=True, text=True)
def test_finds_what_dlsym_finds(self):
result = self.run_program()
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
def test_a_threads_first_lookup_walks_and_allocates_nothing(self):
self.assertIn("allocations 0, left to dlsym 0\n", self.run_program().stdout)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -43,7 +43,7 @@ class NoexecPreloadTest(unittest.TestCase):
cls.tmp = Path(tempfile.mkdtemp())
cls.lib = cls.tmp / "libnoexec.so"
subprocess.run(["cc", "-O2", "-Wall", "-Werror", "-fPIC", "-shared", "-pthread", "-DNOEXEC_TEST", "-o", str(cls.lib),
str(PRELOAD / "noexec.c"), str(PRELOAD / "fsync.c"), str(PRELOAD / "robust.c"), "-ldl"], check=True)
str(PRELOAD / "noexec.c"), str(PRELOAD / "fsync.c"), str(PRELOAD / "robust.c"), str(PRELOAD / "next.c"), "-ldl"], check=True)
source = cls.tmp / "program.c"
source.write_text(PROGRAM)
for name in ("FEX", "game"):
+1 -1
View File
@@ -81,7 +81,7 @@ class StoragePreloadTest(unittest.TestCase):
(build / "program.c").write_text(PROGRAM)
cls.storage = build / "storage.so"
cls.fake = build / "fake.so"
subprocess.run(["cc", "-shared", "-fPIC", "-O2", "-o", str(cls.storage), str(PRELOAD / "storage.c"), "-ldl"], check=True)
subprocess.run(["cc", "-shared", "-fPIC", "-O2", "-o", str(cls.storage), str(PRELOAD / "storage.c"), str(PRELOAD / "next.c"), "-ldl"], check=True)
subprocess.run(["cc", "-shared", "-fPIC", "-O2", "-o", str(cls.fake), str(build / "fake.c")], check=True)
cls.steam = cls.dir / "steamrtarm64/steam"
cls.steam.parent.mkdir()
+68
View File
@@ -0,0 +1,68 @@
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
PRELOAD = Path(__file__).resolve().parents[1] / "linuxfs/preload"
# Each path opened read-only through the preload, from a thread other than the main one: how it was
# served, and its TracerPid line.
PROGRAM = r"""
import fcntl, json, os, sys, threading
seen = {}
def open_all():
for path in sys.argv[1:]:
path = path.replace("PID", str(os.getpid())).replace("TID", str(threading.get_native_id()))
fd = os.open(path, os.O_RDONLY)
try:
text = os.read(fd, 65536).decode()
seen[path] = {"read_only": fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_ACCMODE == os.O_RDONLY,
"file": os.readlink("/proc/self/fd/%d" % fd),
"tracer": next(line for line in text.splitlines() if line.startswith("TracerPid:"))}
finally:
os.close(fd)
thread = threading.Thread(target=open_all)
thread.start()
thread.join()
print(json.dumps(seen))
"""
@unittest.skipUnless(sys.platform.startswith("linux") and shutil.which("cc"), "needs Linux and a C compiler")
class TracerTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.build = tempfile.TemporaryDirectory()
cls.library = Path(cls.build.name) / "libblsession.so"
subprocess.run(["cc", "-shared", "-fPIC", "-O2", "-pthread", "-o", str(cls.library),
*map(str, sorted(PRELOAD.glob("*.c"))), "-ldl"], check=True)
@classmethod
def tearDownClass(cls):
cls.build.cleanup()
def opened(self, *paths):
result = subprocess.run([sys.executable, "-c", PROGRAM, *paths], env=dict(os.environ, LD_PRELOAD=str(self.library)),
check=True, capture_output=True, text=True)
return json.loads(result.stdout)
def test_a_process_reads_its_own_status_without_the_tracer(self):
for path, seen in self.opened("/proc/self/status", "/proc/thread-self/status", "/proc/PID/status",
"/proc/TID/status", "/proc/PID/task/TID/status").items():
self.assertEqual("TracerPid:\t0", seen["tracer"], path)
self.assertTrue(seen["file"].startswith("/memfd:status"), path)
def test_another_process_status_is_the_real_read_only_file(self):
# Chromium hands a renderer its status as a read-only file and checks that it is one.
path = "/proc/%d/status" % os.getpid()
seen = self.opened(path)[path]
self.assertTrue(seen["read_only"])
self.assertEqual(path, seen["file"])
if __name__ == "__main__":
unittest.main()