Skip to content

Commit 9de6831

Browse files
authored
Merge pull request DeusData#1695 from astandrik/codex/fix-1691-test-runtime-isolation
fix(test): isolate harness daemon runtimes
2 parents a6f73a6 + 0eb22f0 commit 9de6831

12 files changed

Lines changed: 331 additions & 32 deletions

‎scripts/security-fuzz-random.sh‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ fi
2727

2828
echo "=== Fuzz Testing ($DURATION seconds) ==="
2929

30-
FUZZ_TMPDIR=$(mktemp -d)
31-
trap 'rm -rf "$FUZZ_TMPDIR"' EXIT
30+
# shellcheck source=test-runtime.sh
31+
source "$(dirname "${BASH_SOURCE[0]}")/test-runtime.sh"
32+
cbm_test_runtime_init
33+
FUZZ_TMPDIR="$CBM_TEST_RUNTIME_ROOT"
34+
trap 'cbm_test_runtime_cleanup "$BINARY"' EXIT
3235

3336
CRASHES=0
3437
ITERATIONS=0

‎scripts/security-fuzz.sh‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ PASS=0
2222
TOTAL=0
2323

2424
# Temp directory for input files (avoids pipe/stdin issues with timeout)
25-
FUZZ_TMPDIR=$(mktemp -d)
26-
trap 'rm -rf "$FUZZ_TMPDIR"' EXIT
25+
# shellcheck source=test-runtime.sh
26+
source "$(dirname "${BASH_SOURCE[0]}")/test-runtime.sh"
27+
cbm_test_runtime_init
28+
FUZZ_TMPDIR="$CBM_TEST_RUNTIME_ROOT"
29+
trap 'cbm_test_runtime_cleanup "$BINARY"' EXIT
2730
FUZZ_HOME="$FUZZ_TMPDIR/home"
28-
FUZZ_CACHE="$FUZZ_TMPDIR/cache"
29-
mkdir -p "$FUZZ_HOME" "$FUZZ_CACHE"
31+
mkdir "$FUZZ_HOME"
32+
export HOME="$FUZZ_HOME"
3033

3134
# Helper: send a payload to the MCP server and check it doesn't crash.
3235
# Uses temp file + perl alarm for portable timeout (works on macOS + Linux).
@@ -49,11 +52,9 @@ test_payload() {
4952
# Run with 10s timeout: GNU timeout → perl alarm fallback
5053
local ec=0
5154
if command -v timeout &>/dev/null; then
52-
HOME="$FUZZ_HOME" CBM_CACHE_DIR="$FUZZ_CACHE" \
53-
timeout 10 "$BINARY" < "$tmpinput" > "$tmpoutput" 2>&1 || ec=$?
55+
timeout 10 "$BINARY" < "$tmpinput" > "$tmpoutput" 2>&1 || ec=$?
5456
else
55-
HOME="$FUZZ_HOME" CBM_CACHE_DIR="$FUZZ_CACHE" \
56-
perl -e 'alarm(10); exec @ARGV' -- "$BINARY" \
57+
perl -e 'alarm(10); exec @ARGV' -- "$BINARY" \
5758
< "$tmpinput" > "$tmpoutput" 2>&1 || ec=$?
5859
fi
5960

@@ -88,8 +89,7 @@ test_invalid_index_interactive() {
8889
local tmpoutput="$FUZZ_TMPDIR/output_${TOTAL}.jsonl"
8990
local ec=0
9091

91-
HOME="$FUZZ_HOME" CBM_CACHE_DIR="$FUZZ_CACHE" \
92-
python3 "$(dirname "$0")/test_mcp_interactive.py" "$BINARY" \
92+
python3 "$(dirname "$0")/test_mcp_interactive.py" "$BINARY" \
9393
--scenario invalid-index --repo-path /nonexistent/path/abc123 \
9494
--response-timeout 45 --exit-timeout 15 > "$tmpoutput" 2>&1 || ec=$?
9595

‎scripts/security-install.sh‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# Layer 4: Install output audit — verifies install --dry-run writes only to expected paths.
4+
# Layer 4: Install output audit — runs a sandboxed real install and audits its writes.
55
#
66
# Checks:
77
# 1. All output file paths are in the expected set
@@ -19,8 +19,11 @@ fi
1919

2020
echo "=== Layer 4: Install Output Audit ==="
2121

22-
TMPDIR=$(mktemp -d)
23-
trap 'rm -rf "$TMPDIR"' EXIT
22+
# shellcheck source=test-runtime.sh
23+
source "$(dirname "${BASH_SOURCE[0]}")/test-runtime.sh"
24+
cbm_test_runtime_init
25+
TMPDIR="$CBM_TEST_RUNTIME_ROOT"
26+
trap 'cbm_test_runtime_cleanup "$BINARY"' EXIT
2427

2528
# Set HOME to tmpdir so install writes there instead of real home
2629
export HOME="$TMPDIR/home"

‎scripts/security-network.sh‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ if ! command -v strace &>/dev/null; then
3030
exit 0
3131
fi
3232

33-
TMPDIR=$(mktemp -d)
34-
trap 'rm -rf "$TMPDIR"' EXIT
33+
# shellcheck source=test-runtime.sh
34+
source "$(dirname "${BASH_SOURCE[0]}")/test-runtime.sh"
35+
cbm_test_runtime_init
36+
TMPDIR="$CBM_TEST_RUNTIME_ROOT"
37+
trap 'cbm_test_runtime_cleanup "$BINARY"' EXIT
3538

3639
# Create a minimal test project
3740
mkdir -p "$TMPDIR/project/src"

‎scripts/test-runtime.sh‎

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env bash
2+
3+
# Sourced by test harnesses. Host paths stay separate from paths exported to a
4+
# native Windows product binary.
5+
CBM_TEST_RUNTIME_ROOT=""
6+
CBM_TEST_RUNTIME_DIR_HOST=""
7+
CBM_TEST_CACHE_DIR_HOST=""
8+
_CBM_TEST_RUNTIME_CREATED_ROOT=""
9+
_CBM_TEST_RUNTIME_PRODUCT_RUNTIME=""
10+
_CBM_TEST_RUNTIME_PRODUCT_CACHE=""
11+
12+
_cbm_test_runtime_error() {
13+
echo "test-runtime: $*" >&2
14+
}
15+
16+
_cbm_test_runtime_discard_partial() {
17+
local root="$1" parent="$2" name="${1##*/}"
18+
[[ -n "$root" && "$root" != "$name" && "${root%/*}" == "$parent" && ! -L "$root" ]] || return 0
19+
case "$name" in cbmrt.?*|cbmrt-?*) rm -rf -- "$root" >/dev/null 2>&1 || true ;; esac
20+
}
21+
22+
cbm_test_runtime_init() {
23+
[[ -z "$_CBM_TEST_RUNTIME_CREATED_ROOT" ]] ||
24+
{ _cbm_test_runtime_error "runtime already initialized"; return 1; }
25+
local platform windows=0 parent root="" runtime_host cache_host
26+
local product_runtime product_cache helper_dir helper_native root_native
27+
platform="$(uname -s)" ||
28+
{ _cbm_test_runtime_error "cannot identify the host platform"; return 1; }
29+
case "$platform" in
30+
MINGW*|MSYS*|CYGWIN*) windows=1 ;;
31+
Darwin) parent="/private/tmp" ;;
32+
*) parent="/tmp" ;;
33+
esac
34+
if (( windows )); then
35+
command -v cygpath >/dev/null 2>&1 ||
36+
{ _cbm_test_runtime_error "cygpath is required on Windows"; return 1; }
37+
if [[ -n "${CBM_CI_TEMP_ROOT:-}" ]]; then
38+
parent="$(cygpath -u "$CBM_CI_TEMP_ROOT")" ||
39+
{ _cbm_test_runtime_error "cannot convert CBM_CI_TEMP_ROOT"; return 1; }
40+
[[ -d "$parent" && ! -L "$parent" ]] ||
41+
{ _cbm_test_runtime_error "CBM_CI_TEMP_ROOT is not a real directory"; return 1; }
42+
root="$(mktemp -d "${parent%/}/cbmrt.XXXXXX")" ||
43+
{ _cbm_test_runtime_error "cannot create a private Windows CI root"; return 1; }
44+
else
45+
helper_dir="$(CDPATH= cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || return 1
46+
helper_native="$(cygpath -w "$helper_dir/ci/new-protected-temp-root.ps1")" ||
47+
{ _cbm_test_runtime_error "cannot convert protected-root helper"; return 1; }
48+
root_native="$(MSYS2_ARG_CONV_EXCL='*' powershell.exe -NoProfile \
49+
-ExecutionPolicy Bypass -File "$helper_native" -Prefix 'cbmrt-')" ||
50+
{ _cbm_test_runtime_error "protected Windows root creation failed"; return 1; }
51+
root_native="${root_native//$'\r'/}"
52+
[[ -n "$root_native" && "$root_native" != *$'\n'* ]] ||
53+
{ _cbm_test_runtime_error "protected-root helper returned an invalid path"; return 1; }
54+
root="$(cygpath -u "$root_native")" ||
55+
{ _cbm_test_runtime_error "cannot convert protected Windows root"; return 1; }
56+
parent="${root%/*}"
57+
fi
58+
else
59+
root="$(mktemp -d "${parent%/}/cbmrt.XXXXXX")" ||
60+
{ _cbm_test_runtime_error "cannot create private root under $parent"; return 1; }
61+
chmod 700 "$root" || {
62+
_cbm_test_runtime_discard_partial "$root" "$parent"
63+
_cbm_test_runtime_error "cannot protect runtime root $root"
64+
return 1
65+
}
66+
fi
67+
runtime_host="$root/runtime"
68+
cache_host="$root/cache"
69+
mkdir "$runtime_host" "$cache_host" || {
70+
_cbm_test_runtime_discard_partial "$root" "$parent"
71+
_cbm_test_runtime_error "cannot create runtime/cache directories"
72+
return 1
73+
}
74+
if (( ! windows )); then
75+
chmod 700 "$runtime_host" "$cache_host" || {
76+
_cbm_test_runtime_discard_partial "$root" "$parent"
77+
_cbm_test_runtime_error "cannot protect runtime/cache directories"
78+
return 1
79+
}
80+
product_runtime="$runtime_host"
81+
product_cache="$cache_host"
82+
else
83+
product_runtime="$(cygpath -m "$runtime_host")" || {
84+
_cbm_test_runtime_discard_partial "$root" "$parent"
85+
_cbm_test_runtime_error "cannot convert the runtime directory"
86+
return 1
87+
}
88+
product_cache="$(cygpath -m "$cache_host")" || {
89+
_cbm_test_runtime_discard_partial "$root" "$parent"
90+
_cbm_test_runtime_error "cannot convert the cache directory"
91+
return 1
92+
}
93+
fi
94+
CBM_TEST_RUNTIME_ROOT="$root"
95+
CBM_TEST_RUNTIME_DIR_HOST="$runtime_host"
96+
CBM_TEST_CACHE_DIR_HOST="$cache_host"
97+
_CBM_TEST_RUNTIME_CREATED_ROOT="$root"
98+
_CBM_TEST_RUNTIME_PRODUCT_RUNTIME="$product_runtime"
99+
_CBM_TEST_RUNTIME_PRODUCT_CACHE="$product_cache"
100+
export CBM_RUNTIME_DIR="$product_runtime" CBM_CACHE_DIR="$product_cache"
101+
}
102+
103+
_cbm_test_runtime_daemon() {
104+
CBM_RUNTIME_DIR="$_CBM_TEST_RUNTIME_PRODUCT_RUNTIME" \
105+
CBM_CACHE_DIR="$_CBM_TEST_RUNTIME_PRODUCT_CACHE" "$1" daemon "$2"
106+
}
107+
108+
cbm_test_runtime_cleanup() {
109+
local binary="${1:-}" root="$_CBM_TEST_RUNTIME_CREATED_ROOT"
110+
local name="${_CBM_TEST_RUNTIME_CREATED_ROOT##*/}" runtime_entry="" active=0
111+
[[ -n "$root" ]] || return 0
112+
case "$name" in
113+
cbmrt.?*|cbmrt-?*) ;;
114+
*) _cbm_test_runtime_error "refusing cleanup for unexpected basename: $name"; return 0 ;;
115+
esac
116+
if [[ "$root" != "$CBM_TEST_RUNTIME_ROOT" || -L "$root" ]]; then
117+
_cbm_test_runtime_error "refusing cleanup for an unexpected runtime root: $root"
118+
return 0
119+
fi
120+
if [[ ! -e "$root" ]]; then
121+
_CBM_TEST_RUNTIME_CREATED_ROOT=""
122+
return 0
123+
fi
124+
if [[ ! -d "$root" || "$CBM_TEST_RUNTIME_DIR_HOST" != "$root/runtime" ||
125+
"$CBM_TEST_CACHE_DIR_HOST" != "$root/cache" ||
126+
-L "$CBM_TEST_RUNTIME_DIR_HOST" || -L "$CBM_TEST_CACHE_DIR_HOST" ]]; then
127+
_cbm_test_runtime_error "refusing cleanup after runtime path replacement: $root"
128+
return 0
129+
fi
130+
if [[ -d "$CBM_TEST_RUNTIME_DIR_HOST" ]] &&
131+
! runtime_entry="$(find "$CBM_TEST_RUNTIME_DIR_HOST" -mindepth 1 -print -quit 2>/dev/null)"; then
132+
_cbm_test_runtime_error "cannot inspect runtime state; leaving $root"
133+
return 0
134+
fi
135+
if [[ -n "$runtime_entry" ]]; then
136+
[[ -x "$binary" ]] || {
137+
_cbm_test_runtime_error "cannot check the private daemon; leaving $root"
138+
return 0
139+
}
140+
if _cbm_test_runtime_daemon "$binary" status >/dev/null 2>&1; then
141+
active=1
142+
_cbm_test_runtime_daemon "$binary" stop >/dev/null 2>&1 || true
143+
for _attempt in {1..50}; do
144+
if ! _cbm_test_runtime_daemon "$binary" status >/dev/null 2>&1; then
145+
active=0
146+
break
147+
fi
148+
sleep 0.1
149+
done
150+
fi
151+
fi
152+
if (( active )); then
153+
_cbm_test_runtime_error "private daemon is still active; leaving $root"
154+
return 0
155+
fi
156+
if rm -rf -- "$root"; then
157+
_CBM_TEST_RUNTIME_CREATED_ROOT=""
158+
else
159+
_cbm_test_runtime_error "cleanup failed; leaving $root"
160+
fi
161+
return 0
162+
}

‎scripts/test.sh‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Usage: scripts/test.sh [--suites LIST] [--arch ARCH] [VAR=VAL ...]
1414
1515
The canonical test entry: identical in local CI, PR CI, dry run and release.
1616
DEFAULT (no --suites) is exactly what CI runs: static contract checks
17-
(Step 0a-0s), a CLEAN sanitizer build, every suite via the parallel harness,
17+
(Step 0a-0t), a CLEAN sanitizer build, every suite via the parallel harness,
1818
then the prod-binary regression guards (Steps 4-6).
1919
2020
Modes:
@@ -257,6 +257,9 @@ bash "$ROOT/tests/test_vt_candidate_selection_contract.sh"
257257
echo "=== Step 0s: release gate-chain ordering contract ==="
258258
bash "$ROOT/tests/test_release_gate_chain_contract.sh"
259259

260+
echo "=== Step 0t: test runtime isolation contract (#1691) ==="
261+
bash "$ROOT/tests/test_runtime_isolation_contract.sh"
262+
260263
# Verify compiler supports target arch
261264
verify_compiler "$CC"
262265

‎tests/test_hook_conflict_notice.sh‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@ if ! LC_ALL=C grep -a -q -F 'CBM_TEST_HOOK_CLIENT_BUILD' "${BINARY}"; then
4444
exit 2
4545
fi
4646

47-
tmpdir="$(mktemp -d)"
47+
# shellcheck source=../scripts/test-runtime.sh
48+
source "${ROOT}/scripts/test-runtime.sh"
49+
cbm_test_runtime_init
50+
tmpdir="${CBM_TEST_RUNTIME_ROOT}"
4851
cleanup() {
4952
CBM_CACHE_DIR="${tmpdir}/cache" "${BINARY}" daemon stop >/dev/null 2>&1 || true
50-
rm -rf "${tmpdir}"
53+
cbm_test_runtime_cleanup "${BINARY}"
5154
}
5255
trap cleanup EXIT
5356

‎tests/test_parent_watchdog.sh‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ if [[ ! -x "${BINARY}" ]]; then
2525
exit 2
2626
fi
2727

28-
tmpdir="$(mktemp -d)"
28+
# shellcheck source=../scripts/test-runtime.sh
29+
source "${ROOT}/scripts/test-runtime.sh"
30+
cbm_test_runtime_init
31+
tmpdir="${CBM_TEST_RUNTIME_ROOT}"
2932
wrapper_pid=""
3033
cleanup() {
3134
if [[ -s "${tmpdir}/child.pid" ]]; then
@@ -34,7 +37,7 @@ cleanup() {
3437
[[ -n "${child_pid}" ]] && kill "${child_pid}" 2>/dev/null || true
3538
fi
3639
[[ -n "${wrapper_pid}" ]] && kill "${wrapper_pid}" 2>/dev/null || true
37-
rm -rf "${tmpdir}"
40+
cbm_test_runtime_cleanup "${BINARY}"
3841
}
3942
trap cleanup EXIT
4043

0 commit comments

Comments
 (0)