diff --git a/tools/proot/PATCHES.md b/tools/proot/PATCHES.md index 64b5857dc..aa528b746 100644 --- a/tools/proot/PATCHES.md +++ b/tools/proot/PATCHES.md @@ -19,3 +19,28 @@ Ported from WinNative (`feature/proot-enhancements`, e7af0f24): 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. + +Ported from WinNative (`main`, 53836ca9, "Fix/performance and vac"): + +- `0004-seccomp-filter-by-argument.patch` - the seccomp filter traces `prctl` only for + `PR_SET_DUMPABLE`, `setrlimit` only for `RLIMIT_STACK` and `prlimit64` only when it sets a new + `RLIMIT_STACK`, the only cases proot acts on; every other call runs without a stop. `uname` is + traced by the core only on x86_64, the one arch it rewrites; kompat still adds it for + `--kernel-release`. +- `0005-seccomp-skip-unneeded-sysexit.patch` - a seccomp stop fetches the registers once and + looks the filter flags up by syscall number, instead of a `PTRACE_GETEVENTMSG` and a second + register fetch. The flags table is built from proot's list merged with the enabled extensions' + (kompat and fake_id0 here), so it is exactly what the filter reports. `wait4`/`waitpid` left to + the kernel and `accept`/`accept4` without a sockaddr skip their exit stop, unless an extension + replaced the syscall (kompat turning `accept4` into `accept`). +- `0006-clone3-exit-signal.patch` - `clone3` flags and exit signal are read as the 64-bit fields + of `struct clone_args`, so a `clone3` fork is told apart from a thread when proot decides how the + child is traced. +- `0007-proc-self-thread-group.patch` - tracees track their thread group, `/proc/self` names it + instead of the calling thread, and `/proc/thread-self` resolves to `/proc//task/`. +- `0008-fchmodat2-openat2.patch` - `fchmodat2` paths are translated (honouring + `AT_SYMLINK_NOFOLLOW`), `openat2` answers `ENOSYS` so callers fall back to the translated + `openat`, and syscall numbers past the end of a table are rejected instead of read. +- `0009-tracee-relatives-sweep.patch` - tracees count their children, so a terminating thread + with no children or ptracees no longer walks every tracee; the per-stop memory collector is + emptied instead of freed and reallocated. diff --git a/tools/proot/patches/0004-seccomp-filter-by-argument.patch b/tools/proot/patches/0004-seccomp-filter-by-argument.patch new file mode 100644 index 000000000..302a97eba --- /dev/null +++ b/tools/proot/patches/0004-seccomp-filter-by-argument.patch @@ -0,0 +1,209 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Sun, 27 Sep 2026 07:56:54 -0400 +Subject: [PATCH] seccomp: trace prctl, setrlimit and prlimit64 only for the + arguments PRoot acts on + +--- + src/syscall/seccomp.c | 106 +++++++++++++++++++++++++++++++++++------- + 1 file changed, 89 insertions(+), 17 deletions(-) + +diff --git a/src/syscall/seccomp.c b/src/syscall/seccomp.c +index 6d1ba47..8557d4a 100644 +--- a/src/syscall/seccomp.c ++++ b/src/syscall/seccomp.c +@@ -37,6 +37,7 @@ + #include /* memcpy(3), */ + #include /* offsetof(3), */ + #include /* uint*_t, UINT*_MAX, */ ++#include + #include /* assert(3), */ + + #include "syscall/seccomp.h" +@@ -121,13 +122,86 @@ static int add_trace_syscall(struct sock_fprog *program, word_t syscall, int fla + return 0; + } + ++static int add_trace_syscall_if_arg(struct sock_fprog *program, word_t syscall, ++ size_t arg, uint32_t value, int flag) ++{ ++ const size_t arg_offset = offsetof(struct seccomp_data, args[arg]); ++ ++ #define LENGTH_TRACE_SYSCALL_IF_ARG 5 ++ struct sock_filter statements[LENGTH_TRACE_SYSCALL_IF_ARG] = { ++ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, syscall, 0, 4), ++ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arg_offset), ++ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, value, 0, 1), ++ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE + flag), ++ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) ++ }; ++ ++ DEBUG_FILTER("FILTER: trace if syscall == %ld and arg %zd == %u\n", ++ syscall, arg, value); ++ ++ return add_statements(program, LENGTH_TRACE_SYSCALL_IF_ARG, statements); ++} ++ ++static int add_trace_prlimit_stack(struct sock_fprog *program, word_t syscall, int flag) ++{ ++ const size_t resource_offset = offsetof(struct seccomp_data, args[1]); ++ const size_t limit_offset = offsetof(struct seccomp_data, args[2]); ++ ++ #define LENGTH_TRACE_PRLIMIT_STACK 9 ++ struct sock_filter statements[LENGTH_TRACE_PRLIMIT_STACK] = { ++ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, syscall, 0, 8), ++ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, resource_offset), ++ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, RLIMIT_STACK, 0, 5), ++ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, limit_offset), ++ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 2), ++ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, limit_offset + sizeof(uint32_t)), ++ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), ++ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE + flag), ++ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) ++ }; ++ ++ DEBUG_FILTER("FILTER: trace if syscall == %ld sets RLIMIT_STACK\n", syscall); ++ ++ return add_statements(program, LENGTH_TRACE_PRLIMIT_STACK, statements); ++} ++ ++static size_t trace_length(Sysnum sysnum) ++{ ++ switch (sysnum) { ++ case PR_prctl: ++ case PR_setrlimit: ++ return LENGTH_TRACE_SYSCALL_IF_ARG; ++ case PR_prlimit64: ++ return LENGTH_TRACE_PRLIMIT_STACK; ++ default: ++ return LENGTH_TRACE_SYSCALL; ++ } ++} ++ ++static int add_trace(struct sock_fprog *program, Sysnum sysnum, word_t syscall, int flag) ++{ ++ if (syscall > UINT32_MAX) ++ return -ERANGE; ++ ++ switch (sysnum) { ++ case PR_prctl: ++ return add_trace_syscall_if_arg(program, syscall, 0, PR_SET_DUMPABLE, flag); ++ case PR_setrlimit: ++ return add_trace_syscall_if_arg(program, syscall, 0, RLIMIT_STACK, flag); ++ case PR_prlimit64: ++ return add_trace_prlimit_stack(program, syscall, flag); ++ default: ++ return add_trace_syscall(program, syscall, flag); ++ } ++} ++ + /** + * Append to @program->filter the statements that allow anything (if +- * unfiltered). Note that @nb_traced_syscalls is used to make a +- * sanity check. This function returns -errno if an error occurred, ++ * unfiltered). Note that @section_length is used to make a sanity ++ * check. This function returns -errno if an error occurred, + * otherwise 0. + */ +-static int end_arch_section(struct sock_fprog *program, size_t nb_traced_syscalls) ++static int end_arch_section(struct sock_fprog *program, size_t section_length) + { + int status; + +@@ -143,8 +217,7 @@ static int end_arch_section(struct sock_fprog *program, size_t nb_traced_syscall + return status; + + /* Sanity check, see start_arch_section(). */ +- if ( talloc_array_length(program->filter) - program->len +- != LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL) ++ if (talloc_array_length(program->filter) - program->len != section_length) + return -ERANGE; + + return 0; +@@ -152,16 +225,14 @@ static int end_arch_section(struct sock_fprog *program, size_t nb_traced_syscall + + /** + * Append to @program->filter the statements that check the current +- * @architecture. Note that @nb_traced_syscalls is used to make a ++ * @architecture. Note that @section_length is used to make a + * sanity check. This function returns -errno if an error occurred, + * otherwise 0. + */ +-static int start_arch_section(struct sock_fprog *program, uint32_t arch, size_t nb_traced_syscalls) ++static int start_arch_section(struct sock_fprog *program, uint32_t arch, size_t section_length) + { + const size_t arch_offset = offsetof(struct seccomp_data, arch); + const size_t syscall_offset = offsetof(struct seccomp_data, nr); +- const size_t section_length = LENGTH_END_SECTION + +- nb_traced_syscalls * LENGTH_TRACE_SYSCALL; + int status; + + /* Sanity checks. */ +@@ -191,7 +262,7 @@ static int start_arch_section(struct sock_fprog *program, uint32_t arch, size_t + }; + + DEBUG_FILTER("FILTER: if arch == %ld, up to %zdth statement\n", +- arch, nb_traced_syscalls); ++ arch, section_length); + + status = add_statements(program, LENGTH_START_SECTION, statements); + if (status < 0) +@@ -256,7 +327,7 @@ static int set_seccomp_filters(const FilteredSysnum *sysnums) + size_t nb_archs = sizeof(seccomp_archs) / sizeof(SeccompArch); + + struct sock_fprog program = { .len = 0, .filter = NULL }; +- size_t nb_traced_syscalls; ++ size_t section_length; + size_t i, j, k; + int status; + +@@ -268,19 +339,18 @@ static int set_seccomp_filters(const FilteredSysnum *sysnums) + for (i = 0; i < nb_archs; i++) { + word_t syscall; + +- nb_traced_syscalls = 0; ++ section_length = LENGTH_END_SECTION; + +- /* Pre-compute the number of traced syscalls for this architecture. */ + for (j = 0; j < seccomp_archs[i].nb_abis; j++) { + for (k = 0; sysnums[k].value != PR_void; k++) { + syscall = detranslate_sysnum(seccomp_archs[i].abis[j], sysnums[k].value); + if (syscall != SYSCALL_AVOIDER) +- nb_traced_syscalls++; ++ section_length += trace_length(sysnums[k].value); + } + } + + /* Filter: if handled architecture */ +- status = start_arch_section(&program, seccomp_archs[i].value, nb_traced_syscalls); ++ status = start_arch_section(&program, seccomp_archs[i].value, section_length); + if (status < 0) + goto end; + +@@ -292,14 +362,14 @@ static int set_seccomp_filters(const FilteredSysnum *sysnums) + continue; + + /* Filter: trace if handled syscall */ +- status = add_trace_syscall(&program, syscall, sysnums[k].flags); ++ status = add_trace(&program, sysnums[k].value, syscall, sysnums[k].flags); + if (status < 0) + goto end; + } + } + + /* Filter: allow untraced syscalls for this architecture */ +- status = end_arch_section(&program, nb_traced_syscalls); ++ status = end_arch_section(&program, section_length); + if (status < 0) + goto end; + } +@@ -411,7 +481,9 @@ static FilteredSysnum proot_sysnums[] = { + { PR_truncate64, 0 }, + { PR_umount, 0 }, + { PR_umount2, 0 }, ++#if defined(ARCH_X86_64) + { PR_uname, FILTER_SYSEXIT }, ++#endif + { PR_unlink, 0 }, + { PR_unlinkat, 0 }, + { PR_uselib, 0 }, diff --git a/tools/proot/patches/0005-seccomp-skip-unneeded-sysexit.patch b/tools/proot/patches/0005-seccomp-skip-unneeded-sysexit.patch new file mode 100644 index 000000000..9e4c0bf21 --- /dev/null +++ b/tools/proot/patches/0005-seccomp-skip-unneeded-sysexit.patch @@ -0,0 +1,242 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Sun, 27 Sep 2026 07:57:46 -0400 +Subject: [PATCH] seccomp: answer a stop's flags from its registers and skip + the sysexit stop when nothing is left for it + +--- + src/syscall/enter.c | 3 +++ + src/syscall/seccomp.c | 51 +++++++++++++++++++++++++++++++++++++------ + src/syscall/seccomp.h | 1 + + src/syscall/syscall.c | 17 ++++++++++++--- + src/tracee/event.c | 14 +++++++----- + src/tracee/tracee.h | 2 ++ + 6 files changed, 73 insertions(+), 15 deletions(-) + +diff --git a/src/syscall/enter.c b/src/syscall/enter.c +index 8c3eb2e..b84fdc3 100644 +--- a/src/syscall/enter.c ++++ b/src/syscall/enter.c +@@ -149,6 +149,8 @@ int translate_syscall_enter(Tracee *tracee) + case PR_wait4: + case PR_waitpid: + status = translate_wait_enter(tracee); ++ tracee->sysexit_unneeded = status >= 0 ++ && tracee->as_ptracer.waits_in == WAITS_IN_KERNEL; + break; + + case PR_brk: +@@ -271,6 +273,7 @@ int translate_syscall_enter(Tracee *tracee) + case PR_accept4: + /* Nothing special to do if no sockaddr was specified. */ + if (peek_reg(tracee, ORIGINAL, SYSARG_2) == 0) { ++ tracee->sysexit_unneeded = true; + status = 0; + break; + } +diff --git a/src/syscall/seccomp.c b/src/syscall/seccomp.c +index 8557d4a..cec4330 100644 +--- a/src/syscall/seccomp.c ++++ b/src/syscall/seccomp.c +@@ -37,6 +37,7 @@ + #include /* memcpy(3), */ + #include /* offsetof(3), */ + #include /* uint*_t, UINT*_MAX, */ ++#include + #include + #include /* assert(3), */ + +@@ -542,6 +543,9 @@ static int merge_filtered_sysnums(TALLOC_CTX *context, FilteredSysnum **sysnums, + return 0; + } + ++static int collect_filtered_sysnums(TALLOC_CTX *context, const Tracee *tracee, ++ FilteredSysnum **filtered_sysnums); ++ + /** + * Tell the kernel to trace only syscalls handled by PRoot and its + * extensions. This filter will be enabled for the given @tracee and +@@ -551,14 +555,30 @@ static int merge_filtered_sysnums(TALLOC_CTX *context, FilteredSysnum **sysnums, + int enable_syscall_filtering(const Tracee *tracee) + { + FilteredSysnum *filtered_sysnums = NULL; +- Extension *extension; + int status; + + assert(tracee != NULL && tracee->ctx != NULL); + ++ status = collect_filtered_sysnums(tracee->ctx, tracee, &filtered_sysnums); ++ if (status < 0) ++ return status; ++ ++ status = set_seccomp_filters(filtered_sysnums); ++ if (status < 0) ++ return status; ++ ++ return 0; ++} ++ ++static int collect_filtered_sysnums(TALLOC_CTX *context, const Tracee *tracee, ++ FilteredSysnum **filtered_sysnums) ++{ ++ Extension *extension; ++ int status; ++ + /* Add the sysnums required by PRoot to the list of filtered + * sysnums. TODO: only if path translation is required. */ +- status = merge_filtered_sysnums(tracee->ctx, &filtered_sysnums, proot_sysnums); ++ status = merge_filtered_sysnums(context, filtered_sysnums, proot_sysnums); + if (status < 0) + return status; + +@@ -569,20 +589,37 @@ int enable_syscall_filtering(const Tracee *tracee) + if (extension->filtered_sysnums == NULL) + continue; + +- status = merge_filtered_sysnums(tracee->ctx, &filtered_sysnums, ++ status = merge_filtered_sysnums(context, filtered_sysnums, + extension->filtered_sysnums); + if (status < 0) + return status; + } + } + +- status = set_seccomp_filters(filtered_sysnums); +- if (status < 0) +- return status; +- + return 0; + } + ++word_t filtered_sysnum_flags(const Tracee *tracee, Sysnum sysnum) ++{ ++ static word_t flags[PR_NB_SYSNUM]; ++ static bool initialized = false; ++ FilteredSysnum *filtered_sysnums = NULL; ++ size_t i; ++ ++ if (!initialized) { ++ if (collect_filtered_sysnums(NULL, tracee, &filtered_sysnums) < 0) { ++ TALLOC_FREE(filtered_sysnums); ++ return FILTER_SYSEXIT; ++ } ++ for (i = 0; filtered_sysnums[i].value != PR_void; i++) ++ flags[filtered_sysnums[i].value] |= filtered_sysnums[i].flags; ++ TALLOC_FREE(filtered_sysnums); ++ initialized = true; ++ } ++ ++ return sysnum < PR_NB_SYSNUM ? flags[sysnum] : FILTER_SYSEXIT; ++} ++ + #else + + #include "tracee/tracee.h" +diff --git a/src/syscall/seccomp.h b/src/syscall/seccomp.h +index 3d5ba40..67053b5 100644 +--- a/src/syscall/seccomp.h ++++ b/src/syscall/seccomp.h +@@ -44,5 +44,6 @@ typedef struct { + #define FILTER_SYSEXIT 0x1 + + extern int enable_syscall_filtering(const Tracee *tracee); ++extern word_t filtered_sysnum_flags(const Tracee *tracee, Sysnum sysnum); + + #endif /* SECCOMP_H */ +diff --git a/src/syscall/syscall.c b/src/syscall/syscall.c +index 0761a26..9ab063c 100644 +--- a/src/syscall/syscall.c ++++ b/src/syscall/syscall.c +@@ -107,9 +107,13 @@ void translate_syscall(Tracee *tracee) + + assert(tracee->exe != NULL); + +- status = fetch_regs(tracee); +- if (status < 0) +- return; ++ if (tracee->regs_fresh) ++ tracee->regs_fresh = false; ++ else { ++ status = fetch_regs(tracee); ++ if (status < 0) ++ return; ++ } + + int suppressed_syscall_status = 0; + +@@ -136,9 +140,16 @@ void translate_syscall(Tracee *tracee) + * requested by the tracee, it is not a syscall + * chained by PRoot. */ + if (tracee->chain.syscalls == NULL) { ++ tracee->sysexit_unneeded = false; + save_current_regs(tracee, ORIGINAL); + status = translate_syscall_enter(tracee); + save_current_regs(tracee, MODIFIED); ++ ++ if (status >= 0 && tracee->sysexit_unneeded ++ && tracee->seccomp == ENABLED && !tracee->sysexit_pending ++ && !tracee->restore_original_regs_after_seccomp_event ++ && get_sysnum(tracee, MODIFIED) == get_sysnum(tracee, ORIGINAL)) ++ tracee->restart_how = PTRACE_CONT; + } + else { + if (tracee->chain.sysnum_workaround_state != SYSNUM_WORKAROUND_PROCESS_REPLACED_CALL) { +diff --git a/src/tracee/event.c b/src/tracee/event.c +index ec79ef3..41afacd 100644 +--- a/src/tracee/event.c ++++ b/src/tracee/event.c +@@ -38,6 +38,7 @@ + #include "tracee/event.h" + #include "tracee/seccomp.h" + #include "tracee/mem.h" ++#include "tracee/reg.h" + #include "cli/note.h" + #include "path/path.h" + #include "path/binding.h" +@@ -576,18 +577,20 @@ int handle_tracee_event(Tracee *tracee, int tracee_status) + if (tracee->seccomp != ENABLED) + break; + +- status = ptrace(PTRACE_GETEVENTMSG, tracee->pid, NULL, &flags); ++ status = fetch_regs(tracee); + if (status < 0) + break; ++ tracee->regs_fresh = true; ++ flags = filtered_sysnum_flags(tracee, get_sysnum(tracee, CURRENT)); + + /* Use the common ptrace flow when + * sysexit has to be handled. */ + if ((flags & FILTER_SYSEXIT) != 0 || sysexit_necessary) { +- if (seccomp_after_ptrace_enter) { +- tracee->restart_how = PTRACE_SYSCALL; +- translate_syscall(tracee); +- } + tracee->restart_how = PTRACE_SYSCALL; ++ if (seccomp_after_ptrace_enter) ++ translate_syscall(tracee); ++ else ++ tracee->regs_fresh = false; + break; + } + +@@ -720,6 +723,7 @@ bool restart_tracee(Tracee *tracee, int signal) + + tracee->last_restart_how = tracee->restart_how; + tracee->restart_how = 0; ++ tracee->regs_fresh = false; + tracee->running = true; + + return true; +diff --git a/src/tracee/tracee.h b/src/tracee/tracee.h +index e4be3ff..c444a50 100644 +--- a/src/tracee/tracee.h ++++ b/src/tracee/tracee.h +@@ -161,6 +161,8 @@ typedef struct tracee { + bool _regs_were_changed; + bool restore_original_regs; + bool restore_original_regs_after_seccomp_event; ++ bool regs_fresh; ++ bool sysexit_unneeded; + + /* State for the special handling of SIGSTOP. */ + enum { diff --git a/tools/proot/patches/0006-clone3-exit-signal.patch b/tools/proot/patches/0006-clone3-exit-signal.patch new file mode 100644 index 000000000..8424a63dd --- /dev/null +++ b/tools/proot/patches/0006-clone3-exit-signal.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Sun, 27 Sep 2026 07:58:07 -0400 +Subject: [PATCH] tracee: take a clone3 child's exit signal from struct + clone_args + +--- + src/tracee/tracee.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/src/tracee/tracee.c b/src/tracee/tracee.c +index 7c0c50c..25eeb57 100644 +--- a/src/tracee/tracee.c ++++ b/src/tracee/tracee.c +@@ -424,9 +424,13 @@ int new_child(Tracee *parent, word_t clone_flags) + 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) { +- word_t flags = peek_word(parent, peek_reg(parent, CURRENT, SYSARG_1)); +- if (errno == 0) +- clone_flags = flags; ++ word_t args = peek_reg(parent, CURRENT, SYSARG_1); ++ word_t flags = peek_uint64(parent, args); ++ if (errno == 0) { ++ word_t exit_signal = peek_uint64(parent, args + 4 * sizeof(uint64_t)); ++ if (errno == 0) ++ clone_flags = (flags & ~(word_t) 0xFF) | (exit_signal & 0xFF); ++ } + } + + /* Get the pid of the parent's new child. */ diff --git a/tools/proot/patches/0007-proc-self-thread-group.patch b/tools/proot/patches/0007-proc-self-thread-group.patch new file mode 100644 index 000000000..03c3dbfb5 --- /dev/null +++ b/tools/proot/patches/0007-proc-self-thread-group.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Sun, 27 Sep 2026 07:58:16 -0400 +Subject: [PATCH] proc: name a thread's /proc/self by its thread group and + resolve /proc/thread-self + +--- + src/path/proc.c | 7 +++++-- + src/tracee/tracee.c | 3 +++ + src/tracee/tracee.h | 1 + + 3 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/path/proc.c b/src/path/proc.c +index 1c816df..62650f6 100644 +--- a/src/path/proc.c ++++ b/src/path/proc.c +@@ -58,10 +58,13 @@ Action readlink_proc(const Tracee *tracee, char result[PATH_MAX], + switch (comparison) { + case PATHS_ARE_EQUAL: + /* Substitute "/proc/self" with "/proc/". */ +- if (strcmp(component, "self") != 0) ++ if (strcmp(component, "self") == 0) ++ status = snprintf(result, PATH_MAX, "/proc/%d", tracee->tgid); ++ else if (strcmp(component, "thread-self") == 0) ++ status = snprintf(result, PATH_MAX, "/proc/%d/task/%d", tracee->tgid, tracee->pid); ++ else + return DEFAULT; + +- status = snprintf(result, PATH_MAX, "/proc/%d", tracee->pid); + if (status < 0 || status >= PATH_MAX) + return -EPERM; + +diff --git a/src/tracee/tracee.c b/src/tracee/tracee.c +index 25eeb57..d6e414d 100644 +--- a/src/tracee/tracee.c ++++ b/src/tracee/tracee.c +@@ -234,6 +234,7 @@ static Tracee *new_tracee(pid_t pid) + talloc_set_destructor(tracee, remove_tracee); + + tracee->pid = pid; ++ tracee->tgid = pid; + tracee->vpid = next_vpid++; + + LIST_INSERT_HEAD(&tracees, tracee, link); +@@ -355,6 +356,7 @@ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) + void set_tracee_pid(Tracee *tracee, pid_t pid) + { + tracee->pid = pid; ++ tracee->tgid = pid; + LIST_REMOVE(tracee, bucket_link); + LIST_INSERT_HEAD(bucket_of(pid), tracee, bucket_link); + } +@@ -511,6 +513,7 @@ int new_child(Tracee *parent, word_t clone_flags) + * specificity (ie. when a thread calls execve(2), its pid + * gets replaced by the pid of its thread group leader). */ + child->clone = ((clone_flags & CLONE_THREAD) != 0); ++ child->tgid = child->clone ? parent->tgid : child->pid; + + /* Depending on how the new process is created, it may be + * automatically traced by the parent's tracer. */ +diff --git a/src/tracee/tracee.h b/src/tracee/tracee.h +index c444a50..527f428 100644 +--- a/src/tracee/tracee.h ++++ b/src/tracee/tracee.h +@@ -83,6 +83,7 @@ typedef struct tracee { + + /* Process identifier. */ + pid_t pid; ++ pid_t tgid; + + /* Unique tracee identifier. */ + uint64_t vpid; diff --git a/tools/proot/patches/0008-fchmodat2-openat2.patch b/tools/proot/patches/0008-fchmodat2-openat2.patch new file mode 100644 index 000000000..e900561bb --- /dev/null +++ b/tools/proot/patches/0008-fchmodat2-openat2.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Sun, 27 Sep 2026 07:58:30 -0400 +Subject: [PATCH] syscall: translate fchmodat2, send openat2 callers to openat, + bound sysnum lookups by the table length + +--- + src/syscall/enter.c | 18 ++++++++++++++++++ + src/syscall/seccomp.c | 2 ++ + src/syscall/sysnum.c | 2 +- + src/syscall/sysnums-arm.h | 2 ++ + src/syscall/sysnums-arm64.h | 2 ++ + src/syscall/sysnums.list | 2 ++ + 6 files changed, 27 insertions(+), 1 deletion(-) + +diff --git a/src/syscall/enter.c b/src/syscall/enter.c +index b84fdc3..1d3b3fd 100644 +--- a/src/syscall/enter.c ++++ b/src/syscall/enter.c +@@ -447,6 +447,24 @@ int translate_syscall_enter(Tracee *tracee) + status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); + break; + ++ case PR_fchmodat2: ++ dirfd = peek_reg(tracee, CURRENT, SYSARG_1); ++ flags = peek_reg(tracee, CURRENT, SYSARG_4); ++ ++ status = get_sysarg_path(tracee, path, SYSARG_2); ++ if (status < 0) ++ break; ++ ++ if ((flags & AT_SYMLINK_NOFOLLOW) != 0) ++ status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); ++ else ++ status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); ++ break; ++ ++ case PR_openat2: ++ status = -ENOSYS; ++ break; ++ + case PR_inotify_add_watch: + flags = peek_reg(tracee, CURRENT, SYSARG_3); + +diff --git a/src/syscall/seccomp.c b/src/syscall/seccomp.c +index cec4330..0b381bb 100644 +--- a/src/syscall/seccomp.c ++++ b/src/syscall/seccomp.c +@@ -419,6 +419,7 @@ static FilteredSysnum proot_sysnums[] = { + { PR_faccessat2, FILTER_SYSEXIT }, + { PR_fchdir, FILTER_SYSEXIT }, + { PR_fchmodat, 0 }, ++ { PR_fchmodat2, 0 }, + { PR_fchownat, 0 }, + { PR_fstatat64, 0 }, + { PR_futimesat, 0 }, +@@ -455,6 +456,7 @@ static FilteredSysnum proot_sysnums[] = { + { PR_oldstat, 0 }, + { PR_open, 0 }, + { PR_openat, 0 }, ++ { PR_openat2, 0 }, + { PR_pivot_root, 0 }, + { PR_prctl, 0 }, + { PR_prlimit64, FILTER_SYSEXIT }, +diff --git a/src/syscall/sysnum.c b/src/syscall/sysnum.c +index b5440a6..785593e 100644 +--- a/src/syscall/sysnum.c ++++ b/src/syscall/sysnum.c +@@ -92,7 +92,7 @@ static Sysnum translate_sysnum(Abi abi, word_t sysnum) + index = sysnum - sysnums.offset; + + /* Sanity checks. */ +- if (index > sysnums.length) ++ if (index >= sysnums.length) + return PR_void; + + return sysnums.table[index]; +diff --git a/src/syscall/sysnums-arm.h b/src/syscall/sysnums-arm.h +index 0503fdd..e3f6f6b 100644 +--- a/src/syscall/sysnums-arm.h ++++ b/src/syscall/sysnums-arm.h +@@ -358,4 +358,6 @@ static const Sysnum sysnums_arm[] = { + [ 397 ] = PR_statx, + [ 439 ] = PR_faccessat2, + [ 435 ] = PR_clone3, ++ [ 437 ] = PR_openat2, ++ [ 452 ] = PR_fchmodat2, + }; +diff --git a/src/syscall/sysnums-arm64.h b/src/syscall/sysnums-arm64.h +index 751382f..b257339 100644 +--- a/src/syscall/sysnums-arm64.h ++++ b/src/syscall/sysnums-arm64.h +@@ -279,5 +279,7 @@ static const Sysnum sysnums_arm64[] = { + [ 290 ] = PR_pkey_free, + [ 291 ] = PR_statx, + [ 435 ] = PR_clone3, ++ [ 437 ] = PR_openat2, + [ 439 ] = PR_faccessat2, ++ [ 452 ] = PR_fchmodat2, + }; +diff --git a/src/syscall/sysnums.list b/src/syscall/sysnums.list +index 8170b6e..c221ddd 100644 +--- a/src/syscall/sysnums.list ++++ b/src/syscall/sysnums.list +@@ -72,6 +72,7 @@ SYSNUM(fanotify_mark) + SYSNUM(fchdir) + SYSNUM(fchmod) + SYSNUM(fchmodat) ++SYSNUM(fchmodat2) + SYSNUM(fchown) + SYSNUM(fchown32) + SYSNUM(fchownat) +@@ -221,6 +222,7 @@ SYSNUM(olduname) + SYSNUM(open) + SYSNUM(open_by_handle_at) + SYSNUM(openat) ++SYSNUM(openat2) + SYSNUM(pause) + SYSNUM(pciconfig_iobase) + SYSNUM(pciconfig_read) diff --git a/tools/proot/patches/0009-tracee-relatives-sweep.patch b/tools/proot/patches/0009-tracee-relatives-sweep.patch new file mode 100644 index 000000000..6aea9411e --- /dev/null +++ b/tools/proot/patches/0009-tracee-relatives-sweep.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: maxjivi05 +Date: Sun, 27 Sep 2026 07:58:52 -0400 +Subject: [PATCH] tracee: sweep relatives only for tracees that have some, + reuse the memory collector + +--- + src/path/path.h | 2 +- + src/tracee/tracee.c | 16 ++++++++++------ + src/tracee/tracee.h | 1 + + 3 files changed, 12 insertions(+), 7 deletions(-) + +diff --git a/src/path/path.h b/src/path/path.h +index d455894..7d0e68d 100644 +--- a/src/path/path.h ++++ b/src/path/path.h +@@ -48,9 +48,9 @@ typedef enum { + + /* Path with cached attributes. */ + typedef struct { +- char path[PATH_MAX]; + size_t length; + Side side; ++ char path[PATH_MAX]; + } Path; + + /* Path ending type. */ +diff --git a/src/tracee/tracee.c b/src/tracee/tracee.c +index d6e414d..8bb2929 100644 +--- a/src/tracee/tracee.c ++++ b/src/tracee/tracee.c +@@ -105,12 +105,16 @@ static int remove_tracee(Tracee *tracee) + * span. */ + talloc_report_depth_cb(tracee->life_context, 0, 100, clean_life_span_object, tracee); + +- /* This could be optimize by using a dedicated list of +- * children and ptracees. */ ++ if (tracee->parent != NULL) ++ tracee->parent->nb_children--; ++ ++ if (tracee->nb_children != 0 || tracee->as_ptracer.nb_ptracees != 0) + LIST_FOREACH(relative, &tracees, link) { + /* Its children are now orphan. */ +- if (relative->parent == tracee) ++ if (relative->parent == tracee) { + relative->parent = NULL; ++ tracee->nb_children--; ++ } + + /* Its tracees are now free. */ + if (relative->as_ptracee.ptracer == tracee) { +@@ -342,9 +346,7 @@ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) + + LIST_FOREACH(tracee, bucket_of(pid), bucket_link) { + if (tracee->pid == pid) { +- /* Flush then allocate a new memory collector. */ +- TALLOC_FREE(tracee->ctx); +- tracee->ctx = talloc_new(tracee); ++ talloc_free_children(tracee->ctx); + + return tracee; + } +@@ -506,6 +508,8 @@ int new_child(Tracee *parent, word_t clone_flags) + child->parent = parent->parent; + else + child->parent = parent; ++ if (child->parent != NULL) ++ child->parent->nb_children++; + + /* Remember if this child belongs to the same thread group as + * its parent. This is currently useful for ptrace emulation +diff --git a/src/tracee/tracee.h b/src/tracee/tracee.h +index 527f428..1fca85f 100644 +--- a/src/tracee/tracee.h ++++ b/src/tracee/tracee.h +@@ -101,6 +101,7 @@ typedef struct tracee { + + /* Parent of this tracee, NULL if none. */ + struct tracee *parent; ++ size_t nb_children; + + /* Is it a "clone", i.e has the same parent as its creator. */ + bool clone;