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
67 changes: 67 additions & 0 deletions tests/test-push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,73 @@ assert_fail "reject port above 65535" parse_server_info "root@host:65536"
printf '%s\n' "$original_comm" > "/proc/$proc_pid/comm"
)

# proc identity helpers must stay total for absent pids: the same reads run under
# both errexit modes, inside `if !` and `||` callers, and must leave the caller's
# errexit setting untouched afterwards.
proc_reader_flow_case() {
local mode="$1" e_before="" e_after="" missing_pid="" candidate="" rc=0 expected_start="" expected_uid="" live_pid="$BASHPID"
e_before=off; case $- in *e*) e_before=on ;; esac
expected_start=$(awk '{print $22}' "/proc/$live_pid/stat") || return 1
expected_uid=$(id -u) || return 1
read_process_stat "$live_pid" || return 1
[[ "$PROC_START" == "$expected_start" ]] || return 1
read_process_uid "$live_pid" || return 1
[[ "$PROC_UID" == "$expected_uid" ]] || return 1
printf 'live-read %s\n' "$mode"

# /proc/0 normally does not exist; fall back to the highest addressable pids
# so the case never depends on a fixed pid.
for candidate in 0 4194303 4194302 4194301 999999; do
[[ -e "/proc/$candidate" ]] || { missing_pid="$candidate"; break; }
done
[[ -n "$missing_pid" ]] || return 1

if ! read_process_stat "$missing_pid"; then
printf 'if-stat %s\n' "$mode"
else
return 1
fi
if ! read_process_uid "$missing_pid"; then
printf 'if-uid %s\n' "$mode"
else
return 1
fi
read_process_stat "$missing_pid" || rc=$?
[[ "$rc" -ne 0 ]] || { printf 'stat-absent-succeeded %s\n' "$mode"; return 1; }
printf 'or-stat %s\n' "$mode"
rc=0
read_process_uid "$missing_pid" || rc=$?
[[ "$rc" -ne 0 ]] || { printf 'uid-absent-succeeded %s\n' "$mode"; return 1; }
printf 'or-uid %s\n' "$mode"

e_after=off; case $- in *e*) e_after=on ;; esac
printf 'alive %s errexit-before=%s errexit-after=%s\n' "$mode" "$e_before" "$e_after"
}

for proc_reader_mode in errexit no-errexit; do
proc_reader_output=""
if [[ "$proc_reader_mode" == errexit ]]; then
proc_reader_output=$(set -e; proc_reader_flow_case "$proc_reader_mode") \
|| fail "proc readers aborted the calling shell under $proc_reader_mode"
else
proc_reader_output=$(set +e; proc_reader_flow_case "$proc_reader_mode") \
|| fail "proc readers aborted the calling shell under $proc_reader_mode"
fi
[[ "$proc_reader_output" == *"live-read $proc_reader_mode"* ]] \
|| fail "proc readers rejected the live pid under $proc_reader_mode: $proc_reader_output"
pass "proc readers match kernel identity for live pid under $proc_reader_mode"
for proc_reader_marker in if-stat if-uid or-stat or-uid; do
[[ "$proc_reader_output" == *"$proc_reader_marker $proc_reader_mode"* ]] \
|| fail "proc readers lost control flow after $proc_reader_marker under $proc_reader_mode: $proc_reader_output"
done
pass "proc readers reject absent pid without ending if/|| callers under $proc_reader_mode"
proc_reader_errexit=off
[[ "$proc_reader_mode" != errexit ]] || proc_reader_errexit=on
[[ "$proc_reader_output" == *"alive $proc_reader_mode errexit-before=$proc_reader_errexit errexit-after=$proc_reader_errexit"* ]] \
|| fail "proc readers changed caller errexit state under $proc_reader_mode: $proc_reader_output"
pass "proc readers preserve caller errexit state under $proc_reader_mode"
done

# Runtime initialization publishes only a verified 0700 directory and supports idempotent cleanup.
(
trap - EXIT HUP INT TERM
Expand Down
39 changes: 24 additions & 15 deletions tools/push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -874,13 +874,22 @@ initialize_runtime() {
}

read_process_stat() {
local pid="$1" stat_line="" stat_rest="" stat_fd
local pid="$1" stat_line="" stat_rest="" read_status=0 had_errexit=false
local -a fields=()

[[ "$pid" =~ ^[0-9]+$ ]] || return 1
{ exec {stat_fd}<"/proc/$pid/stat"; } 2>/dev/null || return 1
IFS= read -r stat_line <&"$stat_fd" || [[ -n "$stat_line" ]] || { exec {stat_fd}<&- || true; return 1; }
exec {stat_fd}<&- || true
# 整文件读取:read 内建在同一次 /proc 扫描中可能把上一个 status 读取
# 遗留的缓冲字节交给这里的 stat 读取(fd 本身正确、pos=0),导致存活
# worker 的身份校验被误判;命令替换只规避这条已观察到的跨读取路径,
# 不据此推断其他版本或实现的行为。
# Bash 5.2 下 $(<file) 的打开失败可能绕过 if/|| 直接终止 shell,故读取
# 期间临时关闭 errexit,读完立即按原状态恢复。
case $- in *e*) had_errexit=true ;; esac
set +e
{ stat_line=$(< "/proc/$pid/stat"); } 2>/dev/null || read_status=1
[[ "$had_errexit" == false ]] || set -e
(( read_status == 0 )) || return 1
[[ -n "$stat_line" ]] || return 1
[[ "$stat_line" == "$pid ("* && "$stat_line" == *") "* ]] || return 1
stat_rest=${stat_line##*) }
read -r -a fields <<< "$stat_rest"
Expand All @@ -893,17 +902,17 @@ read_process_stat() {
}

read_process_uid() {
local pid="$1" uid_line="" status_key status_value status_fd
{ exec {status_fd}<"/proc/$pid/status"; } 2>/dev/null || return 1
while read -r status_key status_value _ <&"$status_fd"; do
if [[ "$status_key" == Uid: ]]; then
uid_line=$status_value
break
fi
done
exec {status_fd}<&- || true
[[ "$uid_line" =~ ^[0-9]+$ ]] || return 1
PROC_UID=$uid_line
local pid="$1" status_text="" read_status=0 had_errexit=false
# 同 read_process_stat:整文件读取,规避已观察到的 read 内建跨读取缓冲
# 路径;$(<file) 打开失败可能绕过 if/|| 终止 shell(Bash 5.2),故读取
# 期间临时关闭 errexit,读完按原状态恢复。
case $- in *e*) had_errexit=true ;; esac
set +e
{ status_text=$(< "/proc/$pid/status"); } 2>/dev/null || read_status=1
[[ "$had_errexit" == false ]] || set -e
(( read_status == 0 )) || return 1
[[ "$status_text" =~ (^|$'\n')Uid:[[:space:]]+([0-9]+) ]] || return 1
PROC_UID=${BASH_REMATCH[2]}
}

read_process_record() {
Expand Down