Skip to content
Open
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
28 changes: 23 additions & 5 deletions scripts/memlab.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ if [ ! -x "$BINARY" ]; then
exit 2
fi

# The profiled process must reach a daemon rendezvous and cache this run owns:
# only CBM_RUNTIME_DIR moves the rendezvous, so a private CBM_CACHE_DIR alone
# joined the operator's account daemon (#1691, #1696). The helper also stops
# that daemon before its cache is removed and leaves the root for diagnosis
# when it will not stop.
# shellcheck source=test-runtime.sh
source "$(dirname "$0")/test-runtime.sh"
cbm_test_runtime_init || exit 1
trap 'cbm_test_runtime_cleanup "$BINARY"' EXIT

WORK=$(mktemp -d 2>/dev/null || mktemp -d -t memlab)
Comment on lines +33 to 37

# Native Windows: the server walks the full ancestor chain of both the binary
Expand Down Expand Up @@ -75,7 +85,12 @@ PROFILE_OUT="$PWD/memlab-${LABEL}.jsonl"
RUN_LOG="$PWD/memlab-${LABEL}.log"
rm -f "$PROFILE_OUT" "$RUN_LOG"

cleanup() { rm -rf "$WORK" 2>/dev/null || true; rm -rf "${WIN_ROOT:-}" 2>/dev/null || true; }
cleanup() {
# The helper probes with $BINARY, whose native-Windows copy lives in $WORK.
cbm_test_runtime_cleanup "$BINARY"
rm -rf "$WORK" 2>/dev/null || true
rm -rf "${WIN_ROOT:-}" 2>/dev/null || true
}
trap cleanup EXIT
Comment on lines +88 to 94

# A fixed corpus: same file count and content on every platform, so a
Expand Down Expand Up @@ -108,12 +123,15 @@ done
echo "=== memlab: binary=$BINARY requests=$REQUESTS label=$LABEL ==="
echo "corpus: $(find "$CORPUS" -name '*.py' | wc -l | tr -d ' ') files"

# The Windows binary needs a native path here; an msys /c/... path is not one.
if command -v cygpath >/dev/null 2>&1 && ! command -v winepath >/dev/null 2>&1; then
# The native Windows binary needs a native path here, under its stamped root;
# an msys /c/... path is not one. Everywhere else the helper's owner-only cache
# (already exported as CBM_CACHE_DIR) is the cache.
if [ -n "${WIN_ROOT:-}" ]; then
mkdir -p "$WORK/cache"
export CBM_CACHE_DIR="$(cygpath -w "$WORK/cache")"
CACHE_HOST="$WORK/cache"
else
export CBM_CACHE_DIR="$WORK/cache"
CACHE_HOST="$CBM_TEST_CACHE_DIR_HOST"
fi
export CBM_MEM_PROFILE=1
export CBM_MEM_PROFILE_OUT="$PROFILE_OUT"
Expand All @@ -139,7 +157,7 @@ python3 "$(dirname "$0")/memlab-drive.py" "$DRIVE_BINARY" "$DRIVE_CORPUS" "$REQU
RC=$?
# With CBM_CACHE_DIR set the process logs to its own file rather than stderr,
# so fold that in or the census series is invisible.
cat "$WORK"/cache/logs/*.log >> "$RUN_LOG" 2>/dev/null || true
cat "$CACHE_HOST"/logs/*.log >> "$RUN_LOG" 2>/dev/null || true
cat "$WORK/server-stderr.log" >> "$RUN_LOG" 2>/dev/null || true
RESPONSES=$(sed -n "s/.*served=\\([0-9]*\\).*/\\1/p" "$WORK/drive.out" | head -1); RESPONSES=${RESPONSES:-0}
CENSUS=$(grep -c "mem.census" "$RUN_LOG" 2>/dev/null | head -1); CENSUS=${CENSUS:-0}
Expand Down
3 changes: 3 additions & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ bash "$ROOT/tests/test_makefile_ts_runtime_dependencies.sh"
echo "=== Step 0g: security fuzz harness self-test ==="
bash "$ROOT/tests/test_security_fuzz_harness.sh"

echo "=== Step 0g2: memlab harness runtime isolation contract (#1696) ==="
bash "$ROOT/tests/test_memlab_runtime_isolation_contract.sh"

echo "=== Step 0h: smoke release-fixture contract ==="
bash "$ROOT/tests/test_smoke_fixture_contract.sh"

Expand Down
92 changes: 92 additions & 0 deletions tests/test_memlab_runtime_isolation_contract.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail

# Runtime-isolation contract for the memlab harness (#1696, follow-up to #1691).
#
# scripts/memlab.sh starts the product over stdio to attribute retained memory
# and then removes its work directory. It used to give the run a private
# CBM_CACHE_DIR only, but only CBM_RUNTIME_DIR moves the daemon rendezvous
# (docs/CONFIGURATION.md), so the profiled process joined the operator's
# account daemon — refused with a cache-root conflict when one was live, or
# left as the account daemon with its cache deleted underneath it otherwise.
#
# memlab launches the product through its Python driver, and the driver hands
# the product the environment it inherited (memlab-drive.py passes no env= to
# Popen). Recording the environment at the driver boundary therefore observes
# exactly what the product receives, on every host — including Windows, whose
# native Python cannot exec a shell fixture — so a python3 shim stands in for
# the driver and no product process is started at all.

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT

fail() {
echo "FAIL: $*" >&2
exit 1
}

normalize_path() {
local path=${1%$'\r'}
if command -v cygpath >/dev/null 2>&1; then
cygpath -u "$path" 2>/dev/null && return 0
fi
printf '%s\n' "${path//\\//}"
}

if grep -q 'env=' "$ROOT/scripts/memlab-drive.py"; then
fail "memlab-drive.py no longer passes the harness environment through unchanged"
Comment on lines +37 to +38
fi

SHIM_DIR="$WORKDIR/bin"
mkdir -p "$SHIM_DIR"
cat > "$SHIM_DIR/python3" <<'EOF'
#!/usr/bin/env bash
printf '%s\t%s\n' "${CBM_CACHE_DIR-}" "${CBM_RUNTIME_DIR-}" >> "$CBM_MEMLAB_ENV_PROBE"
echo "served=0 failed=0"
exit 1
EOF
DUMMY_BINARY="$WORKDIR/dummy-binary"
printf '#!/usr/bin/env bash\nexit 0\n' > "$DUMMY_BINARY"
chmod +x "$SHIM_DIR/python3" "$DUMMY_BINARY"

CALLER_CACHE="$WORKDIR/caller-cache"
CALLER_RUNTIME="$WORKDIR/caller-runtime"
ENV_LOG="$WORKDIR/environment.log"
mkdir -p "$CALLER_CACHE" "$CALLER_RUNTIME" "$WORKDIR/cwd"

# memlab writes its profile and log into $PWD, hence the cwd change. It is
# tracked without an executable bit, so run it through bash.
(
cd "$WORKDIR/cwd"
PATH="$SHIM_DIR:$PATH" \
CBM_CACHE_DIR="$CALLER_CACHE" \
CBM_RUNTIME_DIR="$CALLER_RUNTIME" \
CBM_MEMLAB_ENV_PROBE="$ENV_LOG" \
bash "$ROOT/scripts/memlab.sh" "$DUMMY_BINARY" 1 probe > "$WORKDIR/memlab.out" 2>&1 || true
)

[[ -s "$ENV_LOG" ]] || fail "memlab did not reach its driver"

CALLER_CACHE_NORMALIZED=$(normalize_path "$CALLER_CACHE")
CALLER_RUNTIME_NORMALIZED=$(normalize_path "$CALLER_RUNTIME")
private_root=""
while IFS=$'\t' read -r child_cache_raw child_runtime_raw; do
child_cache=$(normalize_path "$child_cache_raw")
child_runtime=$(normalize_path "$child_runtime_raw")
Comment on lines +73 to +76
if [[ -z "$child_runtime" || "$child_runtime" == "$CALLER_RUNTIME_NORMALIZED" ]]; then
fail "memlab exposed the caller CBM_RUNTIME_DIR to the product"
fi
if [[ -z "$child_cache" || "$child_cache" == "$CALLER_CACHE_NORMALIZED" ]]; then
fail "memlab exposed the caller CBM_CACHE_DIR to the product"
fi
if [[ "${child_runtime%/*}" != "${child_cache%/*}" ||
"${child_runtime##*/}" != "runtime" || "${child_cache##*/}" != "cache" ]]; then
fail "memlab runtime/cache were not isolated beneath one private root"
fi
private_root="${child_runtime%/*}"
done < "$ENV_LOG"

[[ ! -e "$private_root" ]] || fail "memlab left its private root behind: $private_root"

echo "PASS: memlab harness isolates its daemon runtime and cache from the caller"
Loading