From 23bc802daebd92676e7c78128338bb18b9163576 Mon Sep 17 00:00:00 2001 From: nicolas Date: Mon, 14 Sep 2026 00:13:23 +0200 Subject: [PATCH] fix(ci): inject LD_PRELOAD interceptor to redirect all x86_64 ELF executions to QEMU --- .gitea/workflows/build-apk.yaml | 123 +++++++++++++++++++++++-------- .github/workflows/build-apk.yaml | 123 +++++++++++++++++++++++-------- android/gradle.properties | 2 +- 3 files changed, 187 insertions(+), 61 deletions(-) diff --git a/.gitea/workflows/build-apk.yaml b/.gitea/workflows/build-apk.yaml index 62980b6..0febd82 100644 --- a/.gitea/workflows/build-apk.yaml +++ b/.gitea/workflows/build-apk.yaml @@ -34,36 +34,9 @@ jobs: # On Linux ARM64, Flutter engine and Android AAPT2 need x86_64 emulation via QEMU. if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then echo "Setting up ARM64 Flutter environment & QEMU binfmt emulation..." - apt-get update -qq && apt-get install -y -qq qemu-user-static binfmt-support libc6-amd64-cross libstdc++6-amd64-cross + apt-get update -qq && apt-get install -y -qq qemu-user-static binfmt-support libc6-amd64-cross libstdc++6-amd64-cross gcc - # Mount binfmt_misc pseudo-filesystem (supported under --privileged) - mkdir -p /proc/sys/fs/binfmt_misc - if ! grep -qs '/proc/sys/fs/binfmt_misc' /proc/mounts; then - mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc 2>/dev/null || true - fi - - # Cleanly unregister any conflicting or broken host binfmt handlers (e.g. host FEX) - if [ -f /proc/sys/fs/binfmt_misc/register ]; then - echo "=== Active binfmt_misc handlers before cleanup ===" - ls -la /proc/sys/fs/binfmt_misc/ || true - for entry in /proc/sys/fs/binfmt_misc/*; do - [ -f "$entry" ] || continue - name="$(basename "$entry")" - [ "$name" = "register" ] && continue - [ "$name" = "status" ] && continue - if grep -qi "fex" "$entry" 2>/dev/null || [ "$name" = "FEX-x86_64" ] || [ "$name" = "fex-x86_64" ] || [ "$name" = "x86_64" ] || [ "$name" = "qemu-x86_64" ]; then - echo "Unregistering conflicting binfmt handler: $name" - echo -1 > "$entry" 2>/dev/null || true - fi - done - - # Register QEMU x86_64 emulator directly with OCF flags (Fix-binary) - echo ':qemu-x86_64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-x86_64-static:OCF' > /proc/sys/fs/binfmt_misc/register 2>/dev/null || true - echo "=== Active binfmt_misc handlers after registration ===" - ls -la /proc/sys/fs/binfmt_misc/ || true - fi - - # Setup x86_64 dynamic libraries & prefix for QEMU emulation of AAPT2 and tools + # Setup x86_64 dynamic libraries & prefix for QEMU emulation mkdir -p /etc/qemu-binfmt ln -sf /usr/x86_64-linux-gnu /etc/qemu-binfmt/x86_64 mkdir -p /lib64 /usr/lib64 @@ -74,9 +47,99 @@ jobs: export QEMU_LD_PREFIX=/usr/x86_64-linux-gnu echo "QEMU_LD_PREFIX=/usr/x86_64-linux-gnu" >> "$GITHUB_ENV" + # Create transparent execve/posix_spawn interceptor to route ALL x86_64 binaries to QEMU + cat << 'EOF' > /tmp/qemu_intercept.c +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static int is_x86_64_elf(const char *path) { + if (!path) return 0; + int fd = open(path, O_RDONLY); + if (fd < 0) return 0; + unsigned char header[20]; + ssize_t n = read(fd, header, sizeof(header)); + close(fd); + if (n < 20) return 0; + if (header[0] != 0x7f || header[1] != 'E' || header[2] != 'L' || header[3] != 'F') return 0; + if (header[4] != 2 || header[5] != 1) return 0; + uint16_t machine = (uint16_t)(header[18] | (header[19] << 8)); + return machine == 62; +} + +static char **build_qemu_argv(const char *pathname, char *const argv[]) { + int argc = 0; + while (argv && argv[argc]) argc++; + char **new_argv = malloc((argc + 4) * sizeof(char *)); + new_argv[0] = "/usr/bin/qemu-x86_64-static"; + new_argv[1] = "-L"; + new_argv[2] = "/usr/x86_64-linux-gnu"; + new_argv[3] = (char *)pathname; + for (int i = 1; i < argc; i++) { + new_argv[i + 3] = argv[i]; + } + new_argv[argc + 3] = NULL; + return new_argv; +} + +int execve(const char *pathname, char *const argv[], char *const envp[]) { + static int (*real_execve)(const char *, char *const [], char *const []) = NULL; + if (!real_execve) real_execve = dlsym(RTLD_NEXT, "execve"); + if (pathname && is_x86_64_elf(pathname)) { + char **new_argv = build_qemu_argv(pathname, argv); + return real_execve("/usr/bin/qemu-x86_64-static", new_argv, envp); + } + return real_execve(pathname, argv, envp); +} + +int execv(const char *pathname, char *const argv[]) { + extern char **environ; + return execve(pathname, argv, environ); +} + +int execvp(const char *file, char *const argv[]) { + extern char **environ; + return execve(file, argv, environ); +} + +int execvpe(const char *file, char *const argv[], char *const envp[]) { + return execve(file, argv, envp); +} + +int posix_spawn(pid_t *pid, const char *path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], char *const envp[]) { + static int (*real_posix_spawn)(pid_t *, const char *, const posix_spawn_file_actions_t *, + const posix_spawnattr_t *, char *const [], char *const []) = NULL; + if (!real_posix_spawn) real_posix_spawn = dlsym(RTLD_NEXT, "posix_spawn"); + if (path && is_x86_64_elf(path)) { + char **new_argv = build_qemu_argv(path, argv); + return real_posix_spawn(pid, "/usr/bin/qemu-x86_64-static", file_actions, attrp, new_argv, envp); + } + return real_posix_spawn(pid, path, file_actions, attrp, argv, envp); +} + +int posix_spawnp(pid_t *pid, const char *file, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], char *const envp[]) { + return posix_spawn(pid, file, file_actions, attrp, argv, envp); +} +EOF + gcc -O2 -shared -fPIC -o /usr/local/lib/libqemu_intercept.so /tmp/qemu_intercept.c -ldl + echo "/usr/local/lib/libqemu_intercept.so" > /etc/ld.so.preload + echo "Installed transparent QEMU execution interceptor into /etc/ld.so.preload" + QEMU_BIN=$(command -v qemu-x86_64-static || command -v qemu-x86_64) - # Dummy fallbacks for FEX binaries/sockets to eliminate unexpected errors + # Dummy fallbacks for FEX binaries/sockets for fex in /usr/bin/FEXInterpreter /usr/bin/FEX /usr/local/bin/FEXInterpreter /usr/bin/FEXServer /usr/local/bin/FEXServer; do printf '#!/bin/sh\nexit 0\n' > "$fex" chmod +x "$fex" 2>/dev/null || true diff --git a/.github/workflows/build-apk.yaml b/.github/workflows/build-apk.yaml index 62980b6..0febd82 100644 --- a/.github/workflows/build-apk.yaml +++ b/.github/workflows/build-apk.yaml @@ -34,36 +34,9 @@ jobs: # On Linux ARM64, Flutter engine and Android AAPT2 need x86_64 emulation via QEMU. if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then echo "Setting up ARM64 Flutter environment & QEMU binfmt emulation..." - apt-get update -qq && apt-get install -y -qq qemu-user-static binfmt-support libc6-amd64-cross libstdc++6-amd64-cross + apt-get update -qq && apt-get install -y -qq qemu-user-static binfmt-support libc6-amd64-cross libstdc++6-amd64-cross gcc - # Mount binfmt_misc pseudo-filesystem (supported under --privileged) - mkdir -p /proc/sys/fs/binfmt_misc - if ! grep -qs '/proc/sys/fs/binfmt_misc' /proc/mounts; then - mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc 2>/dev/null || true - fi - - # Cleanly unregister any conflicting or broken host binfmt handlers (e.g. host FEX) - if [ -f /proc/sys/fs/binfmt_misc/register ]; then - echo "=== Active binfmt_misc handlers before cleanup ===" - ls -la /proc/sys/fs/binfmt_misc/ || true - for entry in /proc/sys/fs/binfmt_misc/*; do - [ -f "$entry" ] || continue - name="$(basename "$entry")" - [ "$name" = "register" ] && continue - [ "$name" = "status" ] && continue - if grep -qi "fex" "$entry" 2>/dev/null || [ "$name" = "FEX-x86_64" ] || [ "$name" = "fex-x86_64" ] || [ "$name" = "x86_64" ] || [ "$name" = "qemu-x86_64" ]; then - echo "Unregistering conflicting binfmt handler: $name" - echo -1 > "$entry" 2>/dev/null || true - fi - done - - # Register QEMU x86_64 emulator directly with OCF flags (Fix-binary) - echo ':qemu-x86_64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-x86_64-static:OCF' > /proc/sys/fs/binfmt_misc/register 2>/dev/null || true - echo "=== Active binfmt_misc handlers after registration ===" - ls -la /proc/sys/fs/binfmt_misc/ || true - fi - - # Setup x86_64 dynamic libraries & prefix for QEMU emulation of AAPT2 and tools + # Setup x86_64 dynamic libraries & prefix for QEMU emulation mkdir -p /etc/qemu-binfmt ln -sf /usr/x86_64-linux-gnu /etc/qemu-binfmt/x86_64 mkdir -p /lib64 /usr/lib64 @@ -74,9 +47,99 @@ jobs: export QEMU_LD_PREFIX=/usr/x86_64-linux-gnu echo "QEMU_LD_PREFIX=/usr/x86_64-linux-gnu" >> "$GITHUB_ENV" + # Create transparent execve/posix_spawn interceptor to route ALL x86_64 binaries to QEMU + cat << 'EOF' > /tmp/qemu_intercept.c +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static int is_x86_64_elf(const char *path) { + if (!path) return 0; + int fd = open(path, O_RDONLY); + if (fd < 0) return 0; + unsigned char header[20]; + ssize_t n = read(fd, header, sizeof(header)); + close(fd); + if (n < 20) return 0; + if (header[0] != 0x7f || header[1] != 'E' || header[2] != 'L' || header[3] != 'F') return 0; + if (header[4] != 2 || header[5] != 1) return 0; + uint16_t machine = (uint16_t)(header[18] | (header[19] << 8)); + return machine == 62; +} + +static char **build_qemu_argv(const char *pathname, char *const argv[]) { + int argc = 0; + while (argv && argv[argc]) argc++; + char **new_argv = malloc((argc + 4) * sizeof(char *)); + new_argv[0] = "/usr/bin/qemu-x86_64-static"; + new_argv[1] = "-L"; + new_argv[2] = "/usr/x86_64-linux-gnu"; + new_argv[3] = (char *)pathname; + for (int i = 1; i < argc; i++) { + new_argv[i + 3] = argv[i]; + } + new_argv[argc + 3] = NULL; + return new_argv; +} + +int execve(const char *pathname, char *const argv[], char *const envp[]) { + static int (*real_execve)(const char *, char *const [], char *const []) = NULL; + if (!real_execve) real_execve = dlsym(RTLD_NEXT, "execve"); + if (pathname && is_x86_64_elf(pathname)) { + char **new_argv = build_qemu_argv(pathname, argv); + return real_execve("/usr/bin/qemu-x86_64-static", new_argv, envp); + } + return real_execve(pathname, argv, envp); +} + +int execv(const char *pathname, char *const argv[]) { + extern char **environ; + return execve(pathname, argv, environ); +} + +int execvp(const char *file, char *const argv[]) { + extern char **environ; + return execve(file, argv, environ); +} + +int execvpe(const char *file, char *const argv[], char *const envp[]) { + return execve(file, argv, envp); +} + +int posix_spawn(pid_t *pid, const char *path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], char *const envp[]) { + static int (*real_posix_spawn)(pid_t *, const char *, const posix_spawn_file_actions_t *, + const posix_spawnattr_t *, char *const [], char *const []) = NULL; + if (!real_posix_spawn) real_posix_spawn = dlsym(RTLD_NEXT, "posix_spawn"); + if (path && is_x86_64_elf(path)) { + char **new_argv = build_qemu_argv(path, argv); + return real_posix_spawn(pid, "/usr/bin/qemu-x86_64-static", file_actions, attrp, new_argv, envp); + } + return real_posix_spawn(pid, path, file_actions, attrp, argv, envp); +} + +int posix_spawnp(pid_t *pid, const char *file, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], char *const envp[]) { + return posix_spawn(pid, file, file_actions, attrp, argv, envp); +} +EOF + gcc -O2 -shared -fPIC -o /usr/local/lib/libqemu_intercept.so /tmp/qemu_intercept.c -ldl + echo "/usr/local/lib/libqemu_intercept.so" > /etc/ld.so.preload + echo "Installed transparent QEMU execution interceptor into /etc/ld.so.preload" + QEMU_BIN=$(command -v qemu-x86_64-static || command -v qemu-x86_64) - # Dummy fallbacks for FEX binaries/sockets to eliminate unexpected errors + # Dummy fallbacks for FEX binaries/sockets for fex in /usr/bin/FEXInterpreter /usr/bin/FEX /usr/local/bin/FEXInterpreter /usr/bin/FEXServer /usr/local/bin/FEXServer; do printf '#!/bin/sh\nexit 0\n' > "$fex" chmod +x "$fex" 2>/dev/null || true diff --git a/android/gradle.properties b/android/gradle.properties index 94adc3a..5d7af41 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx1536M -Djdk.lang.Process.launchMechanism=vfork android.useAndroidX=true android.enableJetifier=true