diff --git a/scripts/validation/opcua-lifecycle-memory/late-slope/container.sh b/scripts/validation/opcua-lifecycle-memory/late-slope/container.sh index 8cb84478..d8520aae 100644 --- a/scripts/validation/opcua-lifecycle-memory/late-slope/container.sh +++ b/scripts/validation/opcua-lifecycle-memory/late-slope/container.sh @@ -1,5 +1,18 @@ #!/usr/bin/env bash # Execute the immutable self-contained apphost through the cached Linux VSTest controller. +verify_mapped_coreclr() { + awk -v identity="$2" ' + $6 ~ /\/libcoreclr.so$/ { + if (NF != 6 || ($5 " " $6) != identity) bad = 1 + else matched = 1 + } + END { exit (matched && !bad) ? 0 : 1 } + ' "$1" +} + +# The offline controls source only the mapping check above. +if [[ ${BASH_SOURCE[0]} != "$0" ]]; then return 0; fi + set -euo pipefail umask 077 app=ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests @@ -51,8 +64,8 @@ while ((SECONDS < proof_deadline)) && kill -0 "$vstest_pid" 2>/dev/null; do [[ $pid =~ ^[1-9][0-9]*$ ]] executable=$(readlink "/proc/$pid/exe") [[ $executable == "/app/$app" ]] - runtime=$(awk '$6 ~ /\/libcoreclr.so$/ {print $6}' "/proc/$pid/maps" | sort -u) - [[ $runtime == /app/libcoreclr.so ]] + runtime=/app/libcoreclr.so + verify_mapped_coreclr "/proc/$pid/maps" "$(stat -c '%i %n' "$runtime")" runtime_hash=$(sha256sum "$runtime") printf 'targetPid=%s\nactualExecutable=%s\nmappedCoreClr=%s\ncoreClrSha256=%s\n' \ "$pid" "$executable" "$runtime" "${runtime_hash%% *}" > /task/target-runtime.txt diff --git a/scripts/validation/opcua-lifecycle-memory/late-slope/test_offline.py b/scripts/validation/opcua-lifecycle-memory/late-slope/test_offline.py index f8d270e3..505d58ec 100644 --- a/scripts/validation/opcua-lifecycle-memory/late-slope/test_offline.py +++ b/scripts/validation/opcua-lifecycle-memory/late-slope/test_offline.py @@ -4,6 +4,7 @@ import json import os from pathlib import Path +import subprocess import sys import tempfile import unittest @@ -299,6 +300,20 @@ class ReceiptControls(unittest.TestCase): class BundleControls(unittest.TestCase): + def test_mapped_runtime_requires_the_mounted_inode_and_live_file(self): + script = Path(__file__).with_name("container.sh") + with tempfile.TemporaryDirectory() as temporary: + maps = Path(temporary) / "maps" + live = "1000-2000 r-xp 00000000 00:01 42 /app/libcoreclr.so" + for line, expected in ((live, 0), (live + " (deleted)", 1), (live.replace(" 42 ", " 43 "), 1), + (live + "\n" + live.replace("/app/", "/different/"), 1)): + with self.subTest(line=line): + maps.write_text(line + "\n", encoding="ascii") + result = subprocess.run(["bash", "-c", 'source "$1"\nverify_mapped_coreclr "$2" "$3"', + "map-control", str(script), str(maps), "42 /app/libcoreclr.so"], + capture_output=True, text=True, timeout=5) + self.assertEqual(result.returncode, expected, result.stderr) + def test_remote_override_fails_before_asking_docker(self): with patch.dict(os.environ, {"DOCKER_HOST": "tcp://example.invalid:2375"}), patch("bundle.subprocess.check_output") as docker: with self.assertRaisesRegex(ValueError, "override"):