refactor: fold is_party_alive into party_conn_state
One liveness primitive instead of two. The PID argument was a vestige (format-checked, never probed, since the kill -0 removal); dropping it means a roster with a garbage SERVER_PID can no longer mask a live socket in host/moderation/role resolution.
This commit is contained in:
parent
be3fb049d2
commit
6b94255245
5 changed files with 51 additions and 88 deletions
53
party
53
party
|
|
@ -312,34 +312,6 @@ roster_list() {
|
||||||
# Liveness
|
# Liveness
|
||||||
# --------
|
# --------
|
||||||
|
|
||||||
# Liveness check: the socket is a live tmux server we can speak the tmux
|
|
||||||
# protocol to. `tmux -S list-clients` exits 0 only against a real tmux
|
|
||||||
# server bound to that exact socket path, which simultaneously rules out
|
|
||||||
# stale roster pointers (server gone), AF_UNIX impostors (a same-group
|
|
||||||
# user planting a hand-rolled listener via `nc -lU`), and PID-reuse
|
|
||||||
# leftovers, none of those answer the tmux handshake. Stronger than any
|
|
||||||
# PID check, and it works regardless of who owns the server process.
|
|
||||||
#
|
|
||||||
# Earlier revisions also gated on `kill -0 $pid` as a cheap pre-filter.
|
|
||||||
# That broke cross-user discovery on every non-Linux POSIX target: per
|
|
||||||
# POSIX, kill(2) signal 0 may return EPERM when the caller lacks
|
|
||||||
# send-permission, and illumos, all the BSDs, and macOS honor that.
|
|
||||||
# Linux is the outlier that returns 0 for "exists, even if not
|
|
||||||
# signalable," and Linux ABI environments (LX-branded zones) inherit
|
|
||||||
# that behavior, which is why the bug hid in the test matrix until a
|
|
||||||
# native illumos run surfaced it. The PID arg is kept in the function
|
|
||||||
# signature for caller compatibility and as a numeric-validation guard,
|
|
||||||
# but is no longer probed. Local var names are prefixed to avoid
|
|
||||||
# clobbering caller-side `pid` / `sock` (POSIX sh has no real locals).
|
|
||||||
is_party_alive() {
|
|
||||||
_pa_pid="$1"
|
|
||||||
_pa_sock="$2"
|
|
||||||
case "$_pa_pid" in
|
|
||||||
'' | *[!0-9]* ) return 1 ;;
|
|
||||||
esac
|
|
||||||
"$PARTY_TMUX" -S "$_pa_sock" list-clients >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Classify the caller's relationship to a party socket. Prints exactly
|
# Classify the caller's relationship to a party socket. Prints exactly
|
||||||
# one of:
|
# one of:
|
||||||
# ok : live tmux server, the caller is authorized.
|
# ok : live tmux server, the caller is authorized.
|
||||||
|
|
@ -360,6 +332,20 @@ is_party_alive() {
|
||||||
# to a hidden party, never to a false "ok".
|
# to a hidden party, never to a false "ok".
|
||||||
# dead : nothing speaking the tmux protocol at that socket
|
# dead : nothing speaking the tmux protocol at that socket
|
||||||
# (stale roster, killed server, AF_UNIX impostor).
|
# (stale roster, killed server, AF_UNIX impostor).
|
||||||
|
#
|
||||||
|
# This probe is the ONLY liveness primitive. `tmux -S <sock>
|
||||||
|
# list-clients` exits 0 only against a real tmux server bound to that
|
||||||
|
# exact socket path, which rules out stale roster pointers, AF_UNIX
|
||||||
|
# impostors (a same-group user planting a hand-rolled listener via
|
||||||
|
# `nc -lU`), and PID-reuse leftovers in one shot. Earlier revisions had
|
||||||
|
# a separate is_party_alive() that also format-checked the roster's
|
||||||
|
# SERVER_PID, a vestige of a `kill -0` pre-filter that broke cross-user
|
||||||
|
# discovery on every non-Linux POSIX target: per POSIX, kill(2) signal
|
||||||
|
# 0 may return EPERM when the caller lacks send-permission, and
|
||||||
|
# illumos, all the BSDs, and macOS honor that. Linux (and LX-branded
|
||||||
|
# zones) return 0 for "exists, even if not signalable", which is why
|
||||||
|
# the bug hid in the test matrix until a native illumos run surfaced
|
||||||
|
# it. SERVER_PID stays in the roster as display-only metadata.
|
||||||
party_conn_state() {
|
party_conn_state() {
|
||||||
_cs_err=$("$PARTY_TMUX" -S "$1" list-clients 2>&1 >/dev/null) \
|
_cs_err=$("$PARTY_TMUX" -S "$1" list-clients 2>&1 >/dev/null) \
|
||||||
&& _cs_rc=0 || _cs_rc=$?
|
&& _cs_rc=0 || _cs_rc=$?
|
||||||
|
|
@ -712,8 +698,9 @@ EOF
|
||||||
fi
|
fi
|
||||||
if [ -d "$party_dir" ] && [ -f "$rec" ]; then
|
if [ -d "$party_dir" ] && [ -f "$rec" ]; then
|
||||||
roster_read "$rec" 2>/dev/null || true
|
roster_read "$rec" 2>/dev/null || true
|
||||||
if is_party_alive "${RR_SERVER_PID:-}" "${RR_SOCKET:-}"; then
|
if [ -n "${RR_SOCKET:-}" ] \
|
||||||
echo "party: '$name' is already running (pid $RR_SERVER_PID)" >&2
|
&& [ "$(party_conn_state "$RR_SOCKET")" != dead ]; then
|
||||||
|
echo "party: '$name' is already running" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -1078,7 +1065,7 @@ resolve_authoritative_party() {
|
||||||
[ -L "$d" ] && continue
|
[ -L "$d" ] && continue
|
||||||
[ -O "$d" ] || continue
|
[ -O "$d" ] || continue
|
||||||
roster_read "$rec" || continue
|
roster_read "$rec" || continue
|
||||||
is_party_alive "$RR_SERVER_PID" "$RR_SOCKET" || continue
|
[ "$(party_conn_state "$RR_SOCKET")" != dead ] || continue
|
||||||
[ "$RR_HOST_USER" = "$USER" ] || continue
|
[ "$RR_HOST_USER" = "$USER" ] || continue
|
||||||
[ -z "$target" ] || [ "$target" = "$RR_PARTY_NAME" ] || continue
|
[ -z "$target" ] || [ "$target" = "$RR_PARTY_NAME" ] || continue
|
||||||
set -- "$@" "$rec"
|
set -- "$@" "$rec"
|
||||||
|
|
@ -1391,7 +1378,7 @@ EOF
|
||||||
tmux_sock="${TMUX%%,*}"
|
tmux_sock="${TMUX%%,*}"
|
||||||
for rec in $(roster_list); do
|
for rec in $(roster_list); do
|
||||||
roster_read "$rec" || continue
|
roster_read "$rec" || continue
|
||||||
is_party_alive "$RR_SERVER_PID" "$RR_SOCKET" || continue
|
[ "$(party_conn_state "$RR_SOCKET")" != dead ] || continue
|
||||||
[ "$RR_SOCKET" = "$tmux_sock" ] || continue
|
[ "$RR_SOCKET" = "$tmux_sock" ] || continue
|
||||||
sock="$RR_SOCKET"; host_sess="$RR_PARTY_NAME"; break
|
sock="$RR_SOCKET"; host_sess="$RR_PARTY_NAME"; break
|
||||||
done
|
done
|
||||||
|
|
@ -1399,7 +1386,7 @@ EOF
|
||||||
if [ -z "$sock" ]; then
|
if [ -z "$sock" ]; then
|
||||||
for rec in $(roster_list); do
|
for rec in $(roster_list); do
|
||||||
roster_read "$rec" || continue
|
roster_read "$rec" || continue
|
||||||
is_party_alive "$RR_SERVER_PID" "$RR_SOCKET" || continue
|
[ "$(party_conn_state "$RR_SOCKET")" != dead ] || continue
|
||||||
"$PARTY_TMUX" -S "$RR_SOCKET" list-clients -F '#{client_user}' 2>/dev/null \
|
"$PARTY_TMUX" -S "$RR_SOCKET" list-clients -F '#{client_user}' 2>/dev/null \
|
||||||
| grep -qx "$USER" || continue
|
| grep -qx "$USER" || continue
|
||||||
sock="$RR_SOCKET"; host_sess="$RR_PARTY_NAME"; break
|
sock="$RR_SOCKET"; host_sess="$RR_PARTY_NAME"; break
|
||||||
|
|
|
||||||
|
|
@ -82,32 +82,22 @@ setup() {
|
||||||
# liveness
|
# liveness
|
||||||
# ========
|
# ========
|
||||||
#
|
#
|
||||||
# is_party_alive must not probe the PID with kill -0. POSIX kill(2) is
|
# party_conn_state must not probe PIDs with kill -0. POSIX kill(2) is
|
||||||
# allowed to return EPERM when the caller can't signal the target, and
|
# allowed to return EPERM when the caller can't signal the target, and
|
||||||
# illumos/BSD/macOS honor that — so kill -0 against another user's PID
|
# illumos/BSD/macOS honor that, so kill -0 against another user's PID
|
||||||
# returns nonzero even when the process exists. Linux and Linux-ABI
|
# returns nonzero even when the process exists. Linux and Linux-ABI
|
||||||
# zones (LX-branded) are the outliers that return 0; that's why the
|
# zones (LX-branded) are the outliers that return 0; that's why the
|
||||||
# bug hid in the test matrix until a native illumos run surfaced it.
|
# bug hid in the test matrix until a native illumos run surfaced it.
|
||||||
# Discovery now relies on `tmux -S list-clients` alone, which is
|
# Liveness rides on `tmux -S list-clients` alone, which is strictly
|
||||||
# strictly stronger (filters PID-reuse, dead servers, AF_UNIX impostors).
|
# stronger (filters PID-reuse, dead servers, AF_UNIX impostors).
|
||||||
|
|
||||||
@test "is_party_alive does not reach for kill -0 (cross-user discovery)" {
|
@test "party_conn_state does not reach for kill -0 (cross-user discovery)" {
|
||||||
body=$(declare -f is_party_alive)
|
body=$(declare -f party_conn_state)
|
||||||
[[ "$body" != *"kill "* ]] || { echo "$body"; false; }
|
[[ "$body" != *"kill "* ]] || { echo "$body"; false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "is_party_alive rejects non-numeric PID without invoking tmux" {
|
@test "party_conn_state answers dead when no tmux server is at the socket" {
|
||||||
! PARTY_TMUX=/nonexistent/should-not-be-called \
|
[ "$(party_conn_state "$PARTY_TMP/no-such-sock")" = dead ]
|
||||||
is_party_alive "" /tmp/should-not-matter
|
|
||||||
! PARTY_TMUX=/nonexistent/should-not-be-called \
|
|
||||||
is_party_alive "abc" /tmp/should-not-matter
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "is_party_alive returns nonzero when tmux server is absent" {
|
|
||||||
# Numeric PID, but no live tmux server at the socket path: tmux call
|
|
||||||
# fails the handshake and the function returns nonzero. Uses PID 1
|
|
||||||
# to also confirm a foreign-uid PID is not preventing the result.
|
|
||||||
! is_party_alive 1 "$PARTY_TMP/no-such-sock"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "validate_socket_dir_parent refuses whitespace in PARTY_SOCKET_DIR" {
|
@test "validate_socket_dir_parent refuses whitespace in PARTY_SOCKET_DIR" {
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helper: write a $PARTY_TMUX stub that exits with the given code on
|
# Helper: write a $PARTY_TMUX stub that exits with the given code on
|
||||||
# every invocation. Used to test is_party_alive's tmux probe in isolation.
|
# every invocation. Used to test party_conn_state's probe in isolation.
|
||||||
_stub_tmux() {
|
_stub_tmux() {
|
||||||
cat > "$PARTY_TMP/tmux-stub" <<EOF
|
cat > "$PARTY_TMP/tmux-stub" <<EOF
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
@ -71,42 +71,28 @@ EOF
|
||||||
PARTY_TMUX="$PARTY_TMP/tmux-stub"
|
PARTY_TMUX="$PARTY_TMP/tmux-stub"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "is_party_alive: tmux probe ok → true (regardless of PID liveness)" {
|
@test "party_conn_state: silent rc-0 probe answers ok" {
|
||||||
# Liveness is decided by the tmux probe alone; the PID arg is kept for
|
|
||||||
# caller-API compatibility and numeric-validation, but is not signal-
|
|
||||||
# probed (POSIX kill(2) returns EPERM cross-user on illumos/BSD/macOS,
|
|
||||||
# which broke cross-user discovery in earlier revisions). With a stub
|
|
||||||
# tmux returning 0, both a live and a never-existed PID must pass.
|
|
||||||
_stub_tmux 0
|
_stub_tmux 0
|
||||||
( sleep 30 ) &
|
[ "$(party_conn_state /dev/null)" = ok ]
|
||||||
pid=$!
|
|
||||||
is_party_alive "$pid" /dev/null
|
|
||||||
kill "$pid" 2>/dev/null
|
|
||||||
wait "$pid" 2>/dev/null || true
|
|
||||||
# Same PID, now reaped — tmux stub still says yes, so still alive.
|
|
||||||
is_party_alive "$pid" /dev/null
|
|
||||||
# And a PID we never owned (init/launchd, root) — likewise alive.
|
|
||||||
is_party_alive 1 /dev/null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "is_party_alive: empty/non-numeric PID → false" {
|
@test "party_conn_state: failing probe answers dead" {
|
||||||
_stub_tmux 0
|
# PID-reuse / planted nc -lU socket scenario: nothing at the socket
|
||||||
! is_party_alive "" /dev/null
|
# speaks the tmux protocol, so the probe fails and the party is dead,
|
||||||
! is_party_alive "abc" /dev/null
|
# regardless of any PID recorded in the roster.
|
||||||
}
|
|
||||||
|
|
||||||
@test "is_party_alive: tmux probe fails → false" {
|
|
||||||
# PID-reuse / planted nc -lU socket scenario: the socket isn't a real
|
|
||||||
# tmux server, so the tmux handshake fails. is_party_alive rejects
|
|
||||||
# regardless of PID state (regression seen cross-user on illumos
|
|
||||||
# native).
|
|
||||||
_stub_tmux 1
|
_stub_tmux 1
|
||||||
( sleep 30 ) &
|
[ "$(party_conn_state /dev/null)" = dead ]
|
||||||
pid=$!
|
}
|
||||||
! is_party_alive "$pid" /dev/null
|
|
||||||
kill "$pid" 2>/dev/null
|
@test "party_conn_state: rc 0 with stderr output answers unauthorized" {
|
||||||
wait "$pid" 2>/dev/null || true
|
cat > "$PARTY_TMP/tmux-stub" <<'EOF'
|
||||||
! is_party_alive 1 /dev/null
|
#!/bin/sh
|
||||||
|
echo "access not allowed" >&2
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x "$PARTY_TMP/tmux-stub"
|
||||||
|
PARTY_TMUX="$PARTY_TMP/tmux-stub"
|
||||||
|
[ "$(party_conn_state /dev/null)" = unauthorized ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "roster_list returns full record paths under PARTY_SOCKET_DIR" {
|
@test "roster_list returns full record paths under PARTY_SOCKET_DIR" {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ EOF
|
||||||
[[ "$output" == *"no parties"* ]]
|
[[ "$output" == *"no parties"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cross-user discovery regression. Earlier revisions of is_party_alive
|
# Cross-user discovery regression. Earlier revisions of party_conn_state
|
||||||
# probed the PID with `kill -0`, which returns EPERM under POSIX semantics
|
# probed the PID with `kill -0`, which returns EPERM under POSIX semantics
|
||||||
# when the caller can't signal the target — illumos, all BSDs, and macOS
|
# when the caller can't signal the target — illumos, all BSDs, and macOS
|
||||||
# honor that, so guests couldn't discover parties hosted by other users
|
# honor that, so guests couldn't discover parties hosted by other users
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ setup() {
|
||||||
# default 'party' group existing on the host.
|
# default 'party' group existing on the host.
|
||||||
export TMUX_PARTY_GROUP="$(id -gn)"
|
export TMUX_PARTY_GROUP="$(id -gn)"
|
||||||
|
|
||||||
# Spawn a long-lived sleeper so cmd_list's is_party_alive check sees a
|
# Spawn a long-lived sleeper so cmd_list's party_conn_state check sees a
|
||||||
# live PID for the "fake server". setup_party_sandbox provides PARTY_TMP.
|
# live PID for the "fake server". setup_party_sandbox provides PARTY_TMP.
|
||||||
( exec sleep 60 ) &
|
( exec sleep 60 ) &
|
||||||
export FAKE_SERVER_PID=$!
|
export FAKE_SERVER_PID=$!
|
||||||
|
|
@ -23,7 +23,7 @@ setup() {
|
||||||
# Recorder: appends every invocation to TMUX_LOG, fakes the few tmux
|
# Recorder: appends every invocation to TMUX_LOG, fakes the few tmux
|
||||||
# subcommands cmd_host depends on (creating a placeholder socket file,
|
# subcommands cmd_host depends on (creating a placeholder socket file,
|
||||||
# answering display-message -p '#{pid}' with the sleeper's PID so
|
# answering display-message -p '#{pid}' with the sleeper's PID so
|
||||||
# is_party_alive sees it as live), and returns 0 for the rest.
|
# party_conn_state sees it as live), and returns 0 for the rest.
|
||||||
export TMUX_LOG="$PARTY_TMP/tmux.log"
|
export TMUX_LOG="$PARTY_TMP/tmux.log"
|
||||||
: > "$TMUX_LOG"
|
: > "$TMUX_LOG"
|
||||||
cat > "$PARTY_TMP/tmux-stub" <<'STUB'
|
cat > "$PARTY_TMP/tmux-stub" <<'STUB'
|
||||||
|
|
@ -711,7 +711,7 @@ STUB
|
||||||
# Parity with test 76 ('cmd_list skips parties...'), but for the join
|
# Parity with test 76 ('cmd_list skips parties...'), but for the join
|
||||||
# resolver. A roster can have a live SERVER_PID (PID reuse, init's pid 1)
|
# resolver. A roster can have a live SERVER_PID (PID reuse, init's pid 1)
|
||||||
# and an [ -S ]-passing socket (planted via `nc -lU`) yet not be a real
|
# and an [ -S ]-passing socket (planted via `nc -lU`) yet not be a real
|
||||||
# tmux server. is_party_alive must probe list-clients on every resolver,
|
# tmux server. party_conn_state must probe list-clients on every resolver,
|
||||||
# not just cmd_list — otherwise `party join
|
# not just cmd_list — otherwise `party join
|
||||||
# imposter` would resolve to the imposter and try to attach.
|
# imposter` would resolve to the imposter and try to attach.
|
||||||
legit_dir="$PARTY_SOCKET_DIR/party-$USER:legit2.d"
|
legit_dir="$PARTY_SOCKET_DIR/party-$USER:legit2.d"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue