Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions mk/tests.mk
Original file line number Diff line number Diff line change
Expand Up @@ -732,37 +732,43 @@ 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=$$!; \
$$tmo "$$secs" $(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); \
Expand Down
59 changes: 25 additions & 34 deletions src/syscall/proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<dir>elfuse-<stem>-<id>" 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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading