diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81068f159..c2d479537 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -240,6 +240,21 @@ jobs: tar --use-compress-program=unzstd -tf app/src/main/assets/pulseaudio.tzst | grep -c '\.so$' rm -rf pa-bundle sink-out + - name: Cache proot + id: proot + uses: actions/cache@v4 + with: + path: | + app/src/main/jniLibs/arm64-v8a/libproot.so + app/src/main/jniLibs/arm64-v8a/libproot-loader.so + key: proot-${{ env.NDK_VERSION }}-${{ hashFiles('tools/proot/**') }} + - name: Build proot + if: steps.proot.outputs.cache-hit != 'true' + run: | + set -euo pipefail + export NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}" + tools/proot/build.sh app/src/main/jniLibs/arm64-v8a + - name: Check the preloads run: | set -euo pipefail diff --git a/.gitignore b/.gitignore index 1fa2e10c1..244bb5f33 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ captures/ # Built in CI from tools/linuxfs and app/src/main/cpp/fakeinput.cpp, never committed, so the two # copies cannot drift. app/src/main/assets/linuxfs/ +app/src/main/jniLibs/arm64-v8a/libproot.so +app/src/main/jniLibs/arm64-v8a/libproot-loader.so # Kotlin compiler session files (build-tool state, never source). .kotlin/ diff --git a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java index 838c970b0..b0d9adb89 100644 --- a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java +++ b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java @@ -58,30 +58,22 @@ public final class LinuxRuntime { /** Where the runtime carries the host-side proot; see tools/linuxfs/prebuilt/proot/README.md. */ private static final String HOST_DIR = "opt/android-host"; - /** - * proot, preferred from the installed runtime and falling back to the copy in the apk. - * - *

It is an Android binary rather than part of the rootfs - it is what creates the rootfs - - * but it travels in the runtime tarball so that reinstalling the app cannot replace the one - * binary everything else depends on, and so a device with no working packaged proot can still - * run the runtime. - */ public static File prootBinary(Context context) { - File shipped = new File(rootDir(context), HOST_DIR + "/proot"); - if (shipped.isFile()) return shipped; - return new File(context.getApplicationInfo().nativeLibraryDir, "libproot.so"); + File packaged = new File(context.getApplicationInfo().nativeLibraryDir, "libproot.so"); + if (packaged.isFile()) return packaged; + return new File(rootDir(context), HOST_DIR + "/proot"); } public static File prootLoader(Context context) { - File shipped = new File(rootDir(context), HOST_DIR + "/loader"); - if (shipped.isFile()) return shipped; - return new File(context.getApplicationInfo().nativeLibraryDir, "libproot-loader.so"); + File packaged = new File(context.getApplicationInfo().nativeLibraryDir, "libproot-loader.so"); + if (packaged.isFile()) return packaged; + return new File(rootDir(context), HOST_DIR + "/loader"); } /** proot links against libtalloc, which sits beside it; empty when the apk copy is in use. */ public static String prootLibraryPath(Context context) { File dir = new File(rootDir(context), HOST_DIR); - return new File(dir, "proot").isFile() ? dir.getPath() : ""; + return dir.equals(prootBinary(context).getParentFile()) ? dir.getPath() : ""; } /** The rootfs is present with gamescope and the session script the launcher hands control to. */ diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index ec15c5c2c..5fa6f3c5d 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -982,7 +982,7 @@ class SessionService : Service() { private const val EXTRA_AUXILIARY_PID = "auxiliaryPid" /** Command lines that can only belong to a session of ours. */ private val STRAGGLERS = listOf("bannerlator-session", "gamescope", "Xwayland", "steamrtarm64", - "steamwebhelper", "linuxfs/opt/android-host/proot", "pulseaudio/libpulseaudio.so") + "steamwebhelper", "linuxfs/opt/android-host/proot", "/libproot.so", "pulseaudio/libpulseaudio.so") /** How long proot gets to run its own cleanup before it is killed outright. */ private const val GRACE_MS = 1200L private const val NO_PAD_SWITCH = "Download/droiddeck-no-pad" diff --git a/tools/build_local.sh b/tools/build_local.sh index cd831a8df..3df403703 100755 --- a/tools/build_local.sh +++ b/tools/build_local.sh @@ -135,6 +135,7 @@ fi export NDK="${sdk_dir}/ndk/${ndk_version}" sink_output="${staging_dir}/sink-out" "${repo_root}/tools/aaudio-sink/build.sh" "${pa_source}" "${sink_output}" +"${repo_root}/tools/proot/build.sh" "${repo_root}/app/src/main/jniLibs/arm64-v8a" cp -p "${bundle_asset}" "${bundle_backup}" bundle_dir="${staging_dir}/pulseaudio-bundle" diff --git a/tools/proot/PATCHES.md b/tools/proot/PATCHES.md new file mode 100644 index 000000000..64b5857dc --- /dev/null +++ b/tools/proot/PATCHES.md @@ -0,0 +1,21 @@ +# proot patches carried by the app + +Built by `tools/proot/build.sh` with the NDK on top of termux/proot `4dba3afb` (Termux's +5.1.107-70 package, the proot the Linux runtime ships at `opt/android-host/proot`; every +`file.c:line` site in that binary matches this commit). talloc is linked in statically from +Samba's release tarball, so the apk carries one self-contained `libproot.so` plus +`libproot-loader.so`, and `LinuxRuntime` prefers them over the runtime's copy. + +Ported from WinNative (`feature/proot-enhancements`, e7af0f24): + +- `0001-tracee-lookup-by-pid.patch` - tracees are hashed by pid, so each ptrace stop finds its + tracee without walking one list entry per thread, and terminated tracees are swept only after a + termination. +- `0002-canon-resolve-parent-at-once.patch` - a clean absolute guest path at least three + directories deep under the rootfs binding alone has its parent opened once with `O_PATH` and + taken as canonical when `/proc/self/fd` names the path proot would build, replacing an `lstat` + per component. The final component of a call that does not follow it is no longer `lstat`ed. + Extensions still see the parent's host path, and the fast path stays off while the f2fs + workaround is active. +- `0003-clone3-flags-read-guard.patch` - a thread created with `clone3` keeps the flags it + inherited when `struct clone_args` cannot be read, instead of being tracked as a fork. diff --git a/tools/proot/build.sh b/tools/proot/build.sh new file mode 100755 index 000000000..897e2e017 --- /dev/null +++ b/tools/proot/build.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail +OUTDIR=$(mkdir -p "$1" && cd "$1" && pwd) +: "${NDK:?set NDK to the Android NDK root}" +API=26 +HERE=$(cd "$(dirname "$0")" && pwd) +. "$HERE/source.env" +case "$(uname -s):$(uname -m)" in + Darwin:*) NDK_HOST=darwin-x86_64 ;; + Linux:x86_64) NDK_HOST=linux-x86_64 ;; + Linux:aarch64|Linux:arm64) NDK_HOST=linux-aarch64 ;; + *) echo "Unsupported build host: $(uname -s) $(uname -m)" >&2; exit 1 ;; +esac +TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/$NDK_HOST/bin" +if [[ ! -d "$TOOLCHAIN" && -d "$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" ]]; then + TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" +fi +CC="$TOOLCHAIN/aarch64-linux-android${API}-clang" +if [[ ! -x "$CC" ]]; then + echo "Android NDK toolchain not found at $TOOLCHAIN" >&2 + exit 1 +fi +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT +fetch() { + curl -fsSL --retry 3 -o "$WORK/$1" "$2" + echo "$3 $WORK/$1" | shasum -a 256 -c - + mkdir -p "$WORK/${1%%.*}" + tar -xzf "$WORK/$1" -C "$WORK/${1%%.*}" --strip-components=1 +} +fetch proot.tar.gz "https://github.com/termux/proot/archive/${PROOT_COMMIT}.tar.gz" "$PROOT_SHA256" +fetch talloc.tar.gz "https://www.samba.org/ftp/talloc/talloc-${TALLOC_VERSION}.tar.gz" "$TALLOC_SHA256" +for patch in "$HERE"/patches/*.patch; do + patch -d "$WORK/proot" -p1 --forward --quiet < "$patch" +done +mkdir -p "$WORK/talloc/config" +cp "$HERE/talloc-config.h" "$WORK/talloc/config/config.h" +"$CC" -c -O2 -fPIC -ffile-prefix-map="$WORK"=. -D__STDC_WANT_LIB_EXT1__=1 -DHAVE_CONFIG_H \ + -I"$WORK/talloc/config" -I"$WORK/talloc" -I"$WORK/talloc/lib/replace" \ + -o "$WORK/talloc.o" "$WORK/talloc/talloc.c" +"$TOOLCHAIN/llvm-ar" rcs "$WORK/libtalloc.a" "$WORK/talloc.o" +mkdir -p "$WORK/bin" +ln -s "$TOOLCHAIN/llvm-readelf" "$WORK/bin/readelf" +PATH="$WORK/bin:$PATH" make -C "$WORK/proot/src" -j"$(getconf _NPROCESSORS_ONLN)" \ + CC="$CC" STRIP="$TOOLCHAIN/llvm-strip" OBJCOPY="$TOOLCHAIN/llvm-objcopy" OBJDUMP="$TOOLCHAIN/llvm-objdump" \ + CPPFLAGS="-D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I. -I$WORK/talloc -DARG_MAX=131072 -Wno-error=implicit-function-declaration" \ + LDFLAGS="$WORK/libtalloc.a -Wl,-z,noexecstack" \ + PROOT_UNBUNDLE_LOADER=/nonexistent proot loader +install -m755 "$WORK/proot/src/proot" "$OUTDIR/libproot.so" +install -m755 "$WORK/proot/src/loader/loader" "$OUTDIR/libproot-loader.so" +"$TOOLCHAIN/llvm-strip" --strip-unneeded "$OUTDIR/libproot.so" +grep -q '^#define HAVE_SECCOMP_FILTER' "$WORK/proot/src/build.h" || { echo "ERROR: proot was built without seccomp acceleration"; exit 1; } +grep -q '^#define HAVE_PROCESS_VM' "$WORK/proot/src/build.h" || { echo "ERROR: proot was built without process_vm"; exit 1; } +NEEDED=$("$TOOLCHAIN/llvm-readelf" -d "$OUTDIR/libproot.so" | sed -n 's/.*NEEDED.*\[\(.*\)\]/\1/p' | tr '\n' ' ') +echo "libproot.so NEEDED: $NEEDED" +for lib in $NEEDED; do + case $lib in libc.so|libdl.so|libm.so) ;; + *) echo "ERROR: unexpected dependency $lib"; exit 1 ;; + esac +done +ls -l "$OUTDIR/libproot.so" "$OUTDIR/libproot-loader.so" diff --git a/tools/proot/patches/0001-tracee-lookup-by-pid.patch b/tools/proot/patches/0001-tracee-lookup-by-pid.patch new file mode 100644 index 000000000..3b181ced6 --- /dev/null +++ b/tools/proot/patches/0001-tracee-lookup-by-pid.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Wed, 23 Sep 2026 21:44:45 -0400 +Subject: [PATCH] tracee: find tracees by pid, and sweep only after a + termination + +--- + src/cli/cli.c | 2 +- + src/tracee/event.c | 2 +- + src/tracee/tracee.c | 26 +++++++++++++++++++++++++- + src/tracee/tracee.h | 2 ++ + 4 files changed, 29 insertions(+), 3 deletions(-) + +diff --git a/src/cli/cli.c b/src/cli/cli.c +index bbc6400..ec50712 100644 +--- a/src/cli/cli.c ++++ b/src/cli/cli.c +@@ -465,7 +465,7 @@ int main(int argc, char *const argv[]) + tracee = get_tracee(NULL, 0, true); + if (tracee == NULL) + goto error; +- tracee->pid = getpid(); ++ set_tracee_pid(tracee, getpid()); + + /* Set verboseness from env variable, may be overriden by option */ + { +diff --git a/src/tracee/event.c b/src/tracee/event.c +index 756aa7e..ec79ef3 100644 +--- a/src/tracee/event.c ++++ b/src/tracee/event.c +@@ -106,7 +106,7 @@ int launch_process(Tracee *tracee, char *const argv[]) + + default: /* parent */ + /* We know the pid of the first tracee now. */ +- tracee->pid = pid; ++ set_tracee_pid(tracee, pid); + return 0; + } + +diff --git a/src/tracee/tracee.c b/src/tracee/tracee.c +index a97757d..90894a0 100644 +--- a/src/tracee/tracee.c ++++ b/src/tracee/tracee.c +@@ -48,6 +48,16 @@ + + static Tracees tracees; + ++#define TRACEE_BUCKETS 1024 ++static Tracees buckets[TRACEE_BUCKETS]; ++ ++static bool terminations_pending = false; ++ ++static Tracees *bucket_of(pid_t pid) ++{ ++ return &buckets[(unsigned int) pid % TRACEE_BUCKETS]; ++} ++ + + /** + * Remove @zombie from its parent's list of zombies. Note: this is a +@@ -89,6 +99,7 @@ static int remove_tracee(Tracee *tracee) + int event; + + LIST_REMOVE(tracee, link); ++ LIST_REMOVE(tracee, bucket_link); + + /* Clean objects that are linked to this tracee's life + * span. */ +@@ -226,6 +237,7 @@ static Tracee *new_tracee(pid_t pid) + tracee->vpid = next_vpid++; + + LIST_INSERT_HEAD(&tracees, tracee, link); ++ LIST_INSERT_HEAD(bucket_of(pid), tracee, bucket_link); + + tracee->life_context = talloc_new(tracee); + +@@ -327,7 +339,7 @@ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) + if (current_tracee != NULL && current_tracee->pid == pid) + return (Tracee *)current_tracee; + +- LIST_FOREACH(tracee, &tracees, link) { ++ LIST_FOREACH(tracee, bucket_of(pid), bucket_link) { + if (tracee->pid == pid) { + /* Flush then allocate a new memory collector. */ + TALLOC_FREE(tracee->ctx); +@@ -340,12 +352,20 @@ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) + return (create ? new_tracee(pid) : NULL); + } + ++void set_tracee_pid(Tracee *tracee, pid_t pid) ++{ ++ tracee->pid = pid; ++ LIST_REMOVE(tracee, bucket_link); ++ LIST_INSERT_HEAD(bucket_of(pid), tracee, bucket_link); ++} ++ + /** + * Mark tracee as terminated and optionally take action. + */ + void terminate_tracee(Tracee *tracee) + { + tracee->terminated = true; ++ terminations_pending = true; + + /* Case where the terminated tracee is marked + to kill all tracees on exit. +@@ -363,6 +383,10 @@ void free_terminated_tracees() + { + Tracee *next; + ++ if (!terminations_pending) ++ return; ++ terminations_pending = false; ++ + /* Items can't be deleted when using LIST_FOREACH. */ + next = tracees.lh_first; + while (next != NULL) { +diff --git a/src/tracee/tracee.h b/src/tracee/tracee.h +index 3dcbc7b..e4be3ff 100644 +--- a/src/tracee/tracee.h ++++ b/src/tracee/tracee.h +@@ -79,6 +79,7 @@ typedef struct tracee { + + /* Link for the list of all tracees. */ + LIST_ENTRY(tracee) link; ++ LIST_ENTRY(tracee) bucket_link; + + /* Process identifier. */ + pid_t pid; +@@ -300,6 +301,7 @@ typedef struct tracee { + #define TRACEE(a) talloc_get_type_abort(talloc_parent(talloc_parent(a)), Tracee) + + extern Tracee *get_tracee(const Tracee *tracee, pid_t pid, bool create); ++extern void set_tracee_pid(Tracee *tracee, pid_t pid); + extern Tracee *get_stopped_ptracee(const Tracee *ptracer, pid_t pid, + bool only_with_pevent, word_t wait_options); + extern bool has_ptracees(const Tracee *ptracer, pid_t pid, word_t wait_options); diff --git a/tools/proot/patches/0002-canon-resolve-parent-at-once.patch b/tools/proot/patches/0002-canon-resolve-parent-at-once.patch new file mode 100644 index 000000000..8965ba61f --- /dev/null +++ b/tools/proot/patches/0002-canon-resolve-parent-at-once.patch @@ -0,0 +1,198 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Wed, 23 Sep 2026 21:44:45 -0400 +Subject: [PATCH] canon: resolve a deep parent directory at once, and skip the + lstat of a final component that is not followed + +--- + src/path/canon.c | 94 +++++++++++++++++++++++++++++++++++++++++++-- + src/path/f2fs-bug.c | 7 +++- + src/path/f2fs-bug.h | 1 + + 3 files changed, 97 insertions(+), 5 deletions(-) + +diff --git a/src/path/canon.c b/src/path/canon.c +index 6c5d4c0..cabd0c0 100644 +--- a/src/path/canon.c ++++ b/src/path/canon.c +@@ -24,6 +24,7 @@ + #include /* PATH_MAX, */ + #include /* MAXSYMLINKS, */ + #include /* E*, */ ++#include + #include /* lstat(2), S_ISREG(), */ + #include /* access(2), lstat(2), */ + #include /* string(3), */ +@@ -133,7 +134,8 @@ static inline Finality next_component(char component[NAME_MAX], const char **cur + * (returned value is 1), otherwise it returns -errno (-ENOENT or + * -ENOTDIR). + */ +-static inline int substitute_binding_stat(Tracee *tracee, Finality finality, unsigned int recursion_level, ++static inline int substitute_binding_stat(Tracee *tracee, Finality finality, bool deref_final, ++ unsigned int recursion_level, + const char guest_path[PATH_MAX], char host_path[PATH_MAX]) + { + struct stat statl; +@@ -152,6 +154,9 @@ static inline int substitute_binding_stat(Tracee *tracee, Finality finality, uns + return status; + } + ++ if (finality == FINAL_NORMAL && !deref_final && tracee->glue_type == 0) ++ return 0; ++ + statl.st_mode = 0; + if (should_skip_file_access_due_to_f2fs_bug(tracee, host_path)) { + status = -ENOENT; +@@ -186,6 +191,87 @@ static inline int substitute_binding_stat(Tracee *tracee, Finality finality, uns + return (S_ISLNK(statl.st_mode) ? 1 : 0); + } + ++#define RESOLVE_AT_ONCE_MIN_DEPTH 3 ++ ++static size_t resolve_parent_at_once(Tracee *tracee, const char *user_path, ++ char guest_path[PATH_MAX]) ++{ ++ char parent[PATH_MAX]; ++ char expected[PATH_MAX]; ++ char resolved[PATH_MAX]; ++ char link[32]; ++ const Binding *binding; ++ const char *last; ++ const char *cursor; ++ size_t parent_length; ++ size_t prefix_length; ++ size_t expected_length; ++ ssize_t length; ++ int depth; ++ int fd; ++ ++ if (tracee->glue_type != 0 || f2fs_bug_workaround_active(tracee)) ++ return 0; ++ ++ last = strrchr(user_path, '/'); ++ if (last == NULL || last == user_path || last[1] == '\0' ++ || strcmp(last + 1, ".") == 0 || strcmp(last + 1, "..") == 0) ++ return 0; ++ ++ parent_length = last - user_path; ++ if (parent_length >= PATH_MAX) ++ return 0; ++ ++ depth = 0; ++ for (cursor = user_path; cursor < last; ) { ++ const char *end; ++ ++ end = memchr(cursor + 1, '/', last - cursor); ++ if (end == NULL) ++ end = last; ++ if (end == cursor + 1 ++ || (end == cursor + 2 && cursor[1] == '.') ++ || (end == cursor + 3 && cursor[1] == '.' && cursor[2] == '.')) ++ return 0; ++ depth++; ++ cursor = end; ++ } ++ if (depth < RESOLVE_AT_ONCE_MIN_DEPTH) ++ return 0; ++ ++ memcpy(parent, user_path, parent_length); ++ parent[parent_length] = '\0'; ++ ++ binding = get_binding(tracee, GUEST, parent); ++ if (binding == NULL || binding->guest.length != 1) ++ return 0; ++ ++ prefix_length = (binding->host.length == 1 ? 0 : binding->host.length); ++ expected_length = prefix_length + parent_length; ++ if (expected_length >= PATH_MAX) ++ return 0; ++ memcpy(expected, binding->host.path, prefix_length); ++ strcpy(expected + prefix_length, parent); ++ ++ fd = open(expected, O_PATH | O_DIRECTORY | O_CLOEXEC); ++ if (fd < 0) ++ return 0; ++ ++ snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); ++ length = readlink(link, resolved, sizeof(resolved)); ++ close(fd); ++ ++ if (length < 0 || (size_t) length != expected_length ++ || memcmp(resolved, expected, expected_length) != 0) ++ return 0; ++ ++ if (notify_extensions(tracee, HOST_PATH, (intptr_t) expected, false) < 0) ++ return 0; ++ ++ strcpy(guest_path, parent); ++ return last + 1 - user_path; ++} ++ + /** + * Copy in @guest_path the canonicalization (see `man 3 realpath`) of + * @user_path regarding to @tracee->root. The path to canonicalize +@@ -227,6 +313,8 @@ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final, + + /* Canonicalize recursely 'user_path' into 'guest_path'. */ + cursor = user_path; ++ if (user_path[0] == '/') ++ cursor += resolve_parent_at_once(tracee, user_path, guest_path); + finality = NOT_FINAL; + while (!IS_FINAL(finality)) { + Comparison comparison; +@@ -260,7 +348,7 @@ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final, + * symlink. For this latter case, we check that the + * symlink points to a directory once it is + * canonicalized, at the end of this loop. */ +- status = substitute_binding_stat(tracee, finality, recursion_level, scratch_path, host_path); ++ status = substitute_binding_stat(tracee, finality, deref_final, recursion_level, scratch_path, host_path); + if (status < 0) + return status; + +@@ -338,7 +426,7 @@ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final, + + /* Check that a non-final canonicalized/dereferenced + * symlink exists and is a directory. */ +- status = substitute_binding_stat(tracee, finality, recursion_level, guest_path, host_path); ++ status = substitute_binding_stat(tracee, finality, deref_final, recursion_level, guest_path, host_path); + if (status < 0) + return status; + +diff --git a/src/path/f2fs-bug.c b/src/path/f2fs-bug.c +index 4a5ee45..e4bddb9 100644 +--- a/src/path/f2fs-bug.c ++++ b/src/path/f2fs-bug.c +@@ -113,7 +113,7 @@ end: + return result; + } + +-bool should_skip_file_access_due_to_f2fs_bug(const Tracee *tracee, const char *path) { ++bool f2fs_bug_workaround_active(const Tracee *tracee) { + /* On first call check if workaround should be enabled */ + static bool f2fs_bug_probed; + static bool f2fs_bug_detected; +@@ -130,9 +130,12 @@ bool should_skip_file_access_due_to_f2fs_bug(const Tracee *tracee, const char *p + } + f2fs_bug_probed = true; + } ++ return f2fs_bug_detected; ++} + ++bool should_skip_file_access_due_to_f2fs_bug(const Tracee *tracee, const char *path) { + /* If workaround is not active don't skip access to file */ +- if (!f2fs_bug_detected) { ++ if (!f2fs_bug_workaround_active(tracee)) { + return false; + } + +diff --git a/src/path/f2fs-bug.h b/src/path/f2fs-bug.h +index b8eabdf..360f149 100644 +--- a/src/path/f2fs-bug.h ++++ b/src/path/f2fs-bug.h +@@ -4,6 +4,7 @@ + #include /* bool, true, false, */ + #include "tracee/tracee.h" /* Tracee, */ + ++bool f2fs_bug_workaround_active(const Tracee *tracee); + bool should_skip_file_access_due_to_f2fs_bug(const Tracee *tracee, const char *path); + + #endif /* F2FS_BUG_H */ diff --git a/tools/proot/patches/0003-clone3-flags-read-guard.patch b/tools/proot/patches/0003-clone3-flags-read-guard.patch new file mode 100644 index 000000000..773297951 --- /dev/null +++ b/tools/proot/patches/0003-clone3-flags-read-guard.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Wed, 23 Sep 2026 21:44:45 -0400 +Subject: [PATCH] tracee: keep a thread's inherited clone flags when clone3's + arguments cannot be read + +--- + src/tracee/tracee.c | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/src/tracee/tracee.c b/src/tracee/tracee.c +index 90894a0..7c0c50c 100644 +--- a/src/tracee/tracee.c ++++ b/src/tracee/tracee.c +@@ -423,10 +423,11 @@ int new_child(Tracee *parent, word_t clone_flags) + status = fetch_regs(parent); + if (status >= 0 && get_sysnum(parent, CURRENT) == PR_clone) + clone_flags = peek_reg(parent, CURRENT, SYSARG_1); +- else if (status >= 0 && get_sysnum(parent, CURRENT) == PR_clone3) +- // Look at the first word of the clone_args structure, which +- // contains the usual clone flags. +- clone_flags = peek_word(parent, peek_reg(parent, CURRENT, SYSARG_1)); ++ else if (status >= 0 && get_sysnum(parent, CURRENT) == PR_clone3) { ++ word_t flags = peek_word(parent, peek_reg(parent, CURRENT, SYSARG_1)); ++ if (errno == 0) ++ clone_flags = flags; ++ } + + /* Get the pid of the parent's new child. */ + status = ptrace(PTRACE_GETEVENTMSG, parent->pid, NULL, &pid); diff --git a/tools/proot/source.env b/tools/proot/source.env new file mode 100644 index 000000000..1d705b893 --- /dev/null +++ b/tools/proot/source.env @@ -0,0 +1,4 @@ +PROOT_COMMIT=4dba3afbf3a63af89b4d9c1a59bf2bda10f4d10f +PROOT_SHA256=de94f648547d1b62431073b82dc99d56ef0ddcef6da022e0dfffe2bf661cc34e +TALLOC_VERSION=2.4.3 +TALLOC_SHA256=dc46c40b9f46bb34dd97fe41f548b0e8b247b77a918576733c528e83abd854dd diff --git a/tools/proot/talloc-config.h b/tools/proot/talloc-config.h new file mode 100644 index 000000000..ef7a805f1 --- /dev/null +++ b/tools/proot/talloc-config.h @@ -0,0 +1,34 @@ +#define TALLOC_BUILD_VERSION_MAJOR 2 +#define TALLOC_BUILD_VERSION_MINOR 4 +#define TALLOC_BUILD_VERSION_RELEASE 3 +#define HAVE_STDBOOL_H 1 +#define HAVE_BOOL 1 +#define HAVE_STDINT_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_DLFCN_H 1 +#define HAVE_STRNLEN 1 +#define HAVE_STRNDUP 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_USLEEP 1 +#define HAVE_VA_COPY 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_C99_VSNPRINTF 1 +#define HAVE_INTPTR_T 1 +#define HAVE_UINTPTR_T 1 +#define HAVE_PTRDIFF_T 1 +#define HAVE_SIG_ATOMIC_T_TYPE 1 +#define HAVE_ERRNO_DECL 1 +#define HAVE_MEMMOVE 1 +#define HAVE_CONSTRUCTOR_ATTRIBUTE 1 +#define HAVE_DESTRUCTOR_ATTRIBUTE 1 +#define HAVE_VISIBILITY_ATTR 1 +#define HAVE_FALLTHROUGH_ATTRIBUTE 1 +#define HAVE_SYS_AUXV_H 1 +#define HAVE_GETAUXVAL 1