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)