mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
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.
208 lines
8.5 KiB
C
208 lines
8.5 KiB
C
/*
|
|
* binfmt_misc for the desktop, which proot does not have: an exec of an x86 Linux program, a Windows
|
|
* program, or a program or script on noexec shared storage goes to droiddeck-open --exec, which runs
|
|
* it under FEX, Proton or its interpreter. x86 and Windows programs are caught before the exec: proot
|
|
* loads a static x86 program as a native one, which dies on its first instruction, and execvp runs a
|
|
* Windows program as a shell script. A program on noexec storage also passes access(X_OK), which KIO
|
|
* and Qt ask before they start one. Only in the desktop session.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <elf.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <spawn.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#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
|
|
|
|
typedef int (*spawn_fn)(pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *,
|
|
char *const[], char *const[]);
|
|
|
|
/* BINFMT_TEST: tools/tests builds it for the host, whose own x86 programs stand in for foreign ones. */
|
|
#if defined(__aarch64__) || defined(BINFMT_TEST)
|
|
|
|
static int desktop(void) {
|
|
const char *value = getenv("BL_DESKTOP");
|
|
return value && value[0] == '1' && value[1] == 0;
|
|
}
|
|
|
|
/* 1: x86 program or noexec storage, 2: Windows program, 3: script; 0 for anything else. */
|
|
static int foreign(const char *path, int error) {
|
|
if (!path || !strcmp(path, BINFMT_HANDLER)) return 0;
|
|
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0) return 0;
|
|
struct stat st;
|
|
unsigned char head[20];
|
|
ssize_t n = fstat(fd, &st) == 0 && S_ISREG(st.st_mode) ? read(fd, head, sizeof head) : -1;
|
|
int kind = 0;
|
|
if (n >= 20 && !memcmp(head, ELFMAG, SELFMAG)) {
|
|
unsigned machine = head[EI_DATA] == ELFDATA2MSB ? (unsigned)head[18] << 8 | head[19] : head[18] | (unsigned)head[19] << 8;
|
|
kind = machine == EM_X86_64 || machine == EM_386 || error == EACCES ? 1 : 0;
|
|
} else if (n >= 2 && head[0] == 'M' && head[1] == 'Z') {
|
|
kind = 2;
|
|
} else if (n >= 2 && head[0] == '#' && head[1] == '!' && error == EACCES) {
|
|
kind = 3;
|
|
}
|
|
/* What the kernel refuses for its permissions stays refused; noexec storage has no execute bits to give. */
|
|
struct statvfs vfs;
|
|
int allowed = kind && (((st.st_mode & 0111) && error != EACCES) || (fstatvfs(fd, &vfs) == 0 && (vfs.f_flag & ST_NOEXEC)));
|
|
close(fd);
|
|
return allowed ? kind : 0;
|
|
}
|
|
|
|
static int wanted(const char *path, int error) {
|
|
return (error == ENOEXEC || error == ENOENT || error == EACCES) && desktop() && foreign(path, error);
|
|
}
|
|
|
|
/* The program on PATH as execvp would find it, into [out]. */
|
|
static int lookup(const char *file, char *out, size_t size) {
|
|
if (!file || !*file) return 0;
|
|
if (strchr(file, '/')) return snprintf(out, size, "%s", file) < (int)size;
|
|
const char *paths = getenv("PATH");
|
|
if (!paths) paths = "/bin:/usr/bin";
|
|
for (const char *p = paths;; ) {
|
|
const char *end = strchrnul(p, ':');
|
|
int n = end > p ? snprintf(out, size, "%.*s/%s", (int)(end - p), p, file) : snprintf(out, size, "%s", file);
|
|
struct stat st;
|
|
if (n > 0 && n < (int)size && stat(out, &st) == 0 && S_ISREG(st.st_mode)) return 1;
|
|
if (!*end) return 0;
|
|
p = end + 1;
|
|
}
|
|
}
|
|
|
|
static int count(char *const argv[]) {
|
|
int n = 0;
|
|
while (argv && argv[n]) n++;
|
|
return n;
|
|
}
|
|
|
|
static void handler_args(char **args, const char *path, char *const argv[], int n) {
|
|
args[0] = "droiddeck-open";
|
|
args[1] = "--exec";
|
|
args[2] = (char *)path;
|
|
int i = 3;
|
|
for (int k = 1; k < n; k++) args[i++] = argv[k];
|
|
args[i] = NULL;
|
|
}
|
|
|
|
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[]))bl_next("execve");
|
|
int n = count(argv);
|
|
char *args[n + 4];
|
|
handler_args(args, path, argv, n);
|
|
real(BINFMT_HANDLER, args, envp);
|
|
}
|
|
|
|
static int spawn_handler(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa, const posix_spawnattr_t *attr,
|
|
char *const argv[], char *const envp[], spawn_fn real) {
|
|
int n = count(argv);
|
|
char *args[n + 4];
|
|
handler_args(args, path, argv, n);
|
|
return real(pid, BINFMT_HANDLER, fa, attr, args, envp);
|
|
}
|
|
|
|
/* Before an exec of [path]: an x86 or Windows program goes to the handler instead. */
|
|
__attribute__((visibility("hidden"))) void bl_binfmt_before(const char *path, char *const argv[], char *const envp[]) {
|
|
if (desktop() && foreign(path, ENOEXEC)) hand_off(path, argv, envp);
|
|
}
|
|
|
|
/* After a failed exec of [path]: the handler takes it over, or the exec's own error stands. */
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_retry(const char *path, char *const argv[], char *const envp[], int error) {
|
|
if (wanted(path, error)) hand_off(path, argv, envp);
|
|
errno = error;
|
|
return -1;
|
|
}
|
|
|
|
/* The same around execvp, for the program it finds on PATH. */
|
|
__attribute__((visibility("hidden"))) void bl_binfmt_before_search(const char *file, char *const argv[], char *const envp[]) {
|
|
char path[PATH_MAX];
|
|
if (desktop() && lookup(file, path, sizeof path)) bl_binfmt_before(path, argv, envp);
|
|
}
|
|
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_after_search(const char *file, char *const argv[], char *const envp[], int error) {
|
|
char path[PATH_MAX];
|
|
if (desktop() && lookup(file, path, sizeof path)) return bl_binfmt_retry(path, argv, envp, error);
|
|
errno = error;
|
|
return -1;
|
|
}
|
|
|
|
/* After access(X_OK) of [path] was refused (pathcache.c): a program on noexec storage runs all the same. */
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_runnable(const char *path, int mode) {
|
|
if (mode != X_OK || !desktop()) return 0;
|
|
int runs = foreign(path, EACCES) != 0;
|
|
errno = EACCES;
|
|
return runs;
|
|
}
|
|
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa,
|
|
const posix_spawnattr_t *attr, char *const argv[], char *const envp[],
|
|
spawn_fn real) {
|
|
if (desktop() && foreign(path, ENOEXEC)) return spawn_handler(pid, path, fa, attr, argv, envp, real);
|
|
int error = real(pid, path, fa, attr, argv, envp);
|
|
return error && wanted(path, error) ? spawn_handler(pid, path, fa, attr, argv, envp, real) : error;
|
|
}
|
|
|
|
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[]))bl_next("execvpe");
|
|
bl_binfmt_before_search(file, argv, envp);
|
|
real(file, argv, envp);
|
|
return bl_binfmt_after_search(file, argv, envp, errno);
|
|
}
|
|
|
|
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)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);
|
|
}
|
|
|
|
#else
|
|
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_runnable(const char *path, int mode) {
|
|
(void)path, (void)mode;
|
|
return 0;
|
|
}
|
|
|
|
__attribute__((visibility("hidden"))) void bl_binfmt_before(const char *path, char *const argv[], char *const envp[]) {
|
|
(void)path, (void)argv, (void)envp;
|
|
}
|
|
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_retry(const char *path, char *const argv[], char *const envp[], int error) {
|
|
(void)path, (void)argv, (void)envp;
|
|
errno = error;
|
|
return -1;
|
|
}
|
|
|
|
__attribute__((visibility("hidden"))) void bl_binfmt_before_search(const char *file, char *const argv[], char *const envp[]) {
|
|
(void)file, (void)argv, (void)envp;
|
|
}
|
|
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_after_search(const char *file, char *const argv[], char *const envp[], int error) {
|
|
(void)file, (void)argv, (void)envp;
|
|
errno = error;
|
|
return -1;
|
|
}
|
|
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa,
|
|
const posix_spawnattr_t *attr, char *const argv[], char *const envp[],
|
|
spawn_fn real) {
|
|
return real(pid, path, fa, attr, argv, envp);
|
|
}
|
|
|
|
#endif
|