diff --git a/mk/tests.mk b/mk/tests.mk index 49c2674d..f27195e0 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -732,18 +732,22 @@ test-absock-cleanup: $(ELFUSE_BIN) $(BUILD_DIR)/test-absock-cleanup # pid to another elfuse process. The recipe plants such a record, host pid of # a live unrelated elfuse run with a start time it does not have, and the # family's kill(99, 0) must still fail with ESRCH. +# Concurrent runners share the directory and a stale registry can carry a +# reused pid, so select the registry that is new since the recipe started and +# holds records for the family root and one of its children. ## registry ignores a reused host pid, and is not named after one test-registry-stale-pid: $(ELFUSE_BIN) $(BUILD_DIR)/test-registry-stale-pid @tmp=$$(mktemp -d); xpid=; fpid=; \ trap 'kill $$xpid $$fpid 2>/dev/null; rm -rf "$$tmp"' EXIT; \ fail() { printf "FAIL: %s\n" "$$1"; exit 1; }; \ + regs() { (cd "$$dir" && ls) | grep '^elfuse-procs-' | sort; }; \ printf " %-30s " "stale record on reused pid"; \ tmo=$$(command -v timeout 2>/dev/null \ || command -v gtimeout 2>/dev/null || true); \ [ -n "$$tmo" ] || { printf "SKIP (timeout(1) missing)\n"; exit 0; }; \ secs=$${TEST_TIMEOUT:-10}; \ dir=$$(getconf DARWIN_USER_TEMP_DIR); \ - ls "$$dir" | grep '^elfuse-procs-' | sort > "$$tmp/before" || true; \ + regs > "$$tmp/before" || true; \ mkfifo "$$tmp/go"; \ $$tmo "$$secs" $(ELFUSE_BIN) $(BUILD_DIR)/test-registry-stale-pid hold & \ xpid=$$!; \ @@ -751,18 +755,20 @@ test-registry-stale-pid: $(ELFUSE_BIN) $(BUILD_DIR)/test-registry-stale-pid < "$$tmp/go" > "$$tmp/out" & \ fpid=$$!; \ exec 4> "$$tmp/go"; \ - for i in $$(seq 1 50); do \ + for i in $$(seq 1 $$((secs > 0 ? secs * 10 : 50))); do \ grep -q READY "$$tmp/out" && break; \ sleep 0.1; \ done; \ grep -q READY "$$tmp/out" || fail "family never reported READY"; \ - new=$$(ls "$$dir" | grep '^elfuse-procs-' | sort \ - | comm -13 "$$tmp/before" -); \ - [ "$$(printf '%s\n' "$$new" | grep -c .)" = 1 ] \ - || fail "expected one new registry in $$dir, saw '$$new'"; \ - reg="$$dir$$new"; \ root=$$(pgrep -P "$$fpid" | head -1); \ [ -n "$$root" ] || fail "family root process not found"; \ + kids=$$(pgrep -P "$$root" | paste -sd'|' -); \ + [ -n "$$kids" ] || fail "family child process not found"; \ + new=$$(regs | comm -13 "$$tmp/before" - | (cd "$$dir" \ + && xargs grep -ls "^$$root " | xargs grep -lsE "^($$kids) ")); \ + [ "$$(printf '%s\n' "$$new" | grep -c .)" = 1 ] \ + || fail "expected one registry for host pids $$root/$$kids, saw '$$new'"; \ + reg="$$dir$$new"; \ [ "$$new" != "elfuse-procs-$$root" ] \ || fail "family id is the root pid $$root, which macOS recycles"; \ holder=$$(pgrep -P "$$xpid" | head -1); \ diff --git a/src/syscall/proc.c b/src/syscall/proc.c index c127a543..2e680e20 100644 --- a/src/syscall/proc.c +++ b/src/syscall/proc.c @@ -400,17 +400,34 @@ int rseq_try_abort(guest_t *g, return result; } -static bool process_pid_sequence_path(char *out, size_t out_size) +/* Build "elfuse--" in the per-user private temp directory macOS + * provisions (mode 0700, owned by the invoking uid) rather than world-writable + * /tmp, so another local user cannot pre-plant a symlink or forge a file's + * contents. confstr returns the same directory for every process of this uid, + * so every family member agrees on the path. Callers open the result O_NOFOLLOW + * for defense in depth. + * + * Returns false (fail closed) if the directory cannot be resolved or the path + * does not fit. + */ +static bool private_tmp_path(char *out, + size_t out_size, + const char *stem, + unsigned long long id) { char dir[PATH_MAX]; size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, dir, sizeof(dir)); if (n == 0 || n > sizeof(dir)) return false; - int len = snprintf(out, out_size, "%selfuse-pidseq-%llu", dir, - (unsigned long long) absock_get_namespace_id()); + int len = snprintf(out, out_size, "%selfuse-%s-%llu", dir, stem, id); return len > 0 && (size_t) len < out_size; } +static bool process_pid_sequence_path(char *out, size_t out_size) +{ + return private_tmp_path(out, out_size, "pidseq", absock_get_namespace_id()); +} + int64_t proc_alloc_pid(void) { static _Atomic bool owner_sequence_reset; @@ -587,36 +604,16 @@ pid_t proc_guest_to_host_pid(int64_t gpid) return result; } -/* Build the path to the cross-process signal-transport file for @host_pid. - * - * Files live in the per-user private temp directory macOS provisions (mode - * 0700, owned by the invoking uid) rather than world-writable /tmp, so another - * local user cannot pre-plant a symlink to redirect the write or inject signal - * numbers into the guest. confstr returns the same directory for every process - * of this uid, so sender and receiver agree on the path. Callers open the - * result O_NOFOLLOW for defense in depth. - * - * Returns false (fail closed) if the private directory cannot be resolved. - */ +/* Cross-process signal-transport file for @host_pid, a live process. */ static bool signal_transport_path(char *out, size_t out_size, pid_t host_pid) { - char dir[PATH_MAX]; - size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, dir, sizeof(dir)); - if (n == 0 || n > sizeof(dir)) - return false; - int len = snprintf(out, out_size, "%selfuse-sig-%ld", dir, (long) host_pid); - return len > 0 && (size_t) len < out_size; + return private_tmp_path(out, out_size, "sig", + (unsigned long long) host_pid); } static bool process_registry_path(char *out, size_t out_size) { - char dir[PATH_MAX]; - size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, dir, sizeof(dir)); - if (n == 0 || n > sizeof(dir)) - return false; - int len = snprintf(out, out_size, "%selfuse-procs-%llu", dir, - (unsigned long long) absock_get_namespace_id()); - return len > 0 && (size_t) len < out_size; + return private_tmp_path(out, out_size, "procs", absock_get_namespace_id()); } /* The process-group registry above intentionally contains only live host @@ -654,13 +651,7 @@ typedef struct { static bool lifecycle_registry_path(char *out, size_t out_size) { - char dir[PATH_MAX]; - size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, dir, sizeof(dir)); - if (n == 0 || n > sizeof(dir)) - return false; - int len = snprintf(out, out_size, "%selfuse-life-%llu", dir, - (unsigned long long) absock_get_namespace_id()); - return len > 0 && (size_t) len < out_size; + return private_tmp_path(out, out_size, "life", absock_get_namespace_id()); } static bool lifecycle_registry_size(uint32_t capacity, size_t *size_out)