#!/usr/bin/env bash # Normal selectors only; exit 77 means prerequisite/native composition BLOCKED. set -euo pipefail main() { taskset -pc 0,1 $$ ROOT=$(git rev-parse --show-toplevel) ATTEMPT=${1:?unique attempt required} REPORT=${2:?new absolute receipt directory required} TARGET=${3:?absolute private or serialized Cargo target cache required} PREVIOUS=${4:-} [[ "$ATTEMPT" =~ ^[a-z0-9-]+$ && "$REPORT" = /* && ! -e "$REPORT" && "$TARGET" = /* ]] [[ $(id -u) != 0 && $(podman info --format '{{.Host.Security.Rootless}}') == true ]] umask 077 exec 9>/tmp/opencode/release-heavy.lock flock 9 NAME="qualification-c01-normal-$ATTEMPT" engine=(podman) container_extra=() pasta_pid= pasta_start= if [[ -n "$PREVIOUS" ]]; then python3 -B "$ROOT/scripts/tests/package_c01_normal_fixture.py" replay "$PREVIOUS" "$REPORT" else python3 -B "$ROOT/scripts/tests/package_c01_normal_fixture.py" freeze "$ROOT" "$REPORT" fi FROZEN="$REPORT/source" cleanup() { local status=$? trap - EXIT if [[ -n "$pasta_pid" && -n "$pasta_start" ]]; then sudo -n python3 -c 'import os,signal,sys try: fd=os.pidfd_open(int(sys.argv[1])); raw=open("/proc/"+sys.argv[1]+"/stat").read(); fields=raw[raw.rindex(")")+2:].split() if fields[19]==sys.argv[2]: signal.pidfd_send_signal(fd,signal.SIGTERM) os.close(fd) except ProcessLookupError: pass except FileNotFoundError: pass' "$pasta_pid" "$pasta_start" || true wait "$pasta_pid" 2>/dev/null || true fi if "${engine[@]}" container exists "$NAME"; then "${engine[@]}" exec "$NAME" journalctl --no-pager > "$REPORT/journal.log" 2>&1 || true "${engine[@]}" logs "$NAME" > "$REPORT/container.log" 2>&1 || true "${engine[@]}" cp "$NAME:/var/lib/c01-normal" "$REPORT/native-evidence" 2>/dev/null || true "${engine[@]}" cp "$NAME:/var/lib/c01-fixture/receipts" "$REPORT/prerequisite-evidence" 2>/dev/null || true "${engine[@]}" inspect "$NAME" > "$REPORT/container-inspect.json" || true "${engine[@]}" stop --time 10 "$NAME" > "$REPORT/stop.log" 2>&1 || true fi if [[ ${C01_FIXTURE_ROOTFUL:-0} == 1 ]]; then for directory in "$REPORT/native-evidence" "$REPORT/prerequisite-evidence"; do [[ ! -d "$directory" ]] || sudo -n chown -R "$(id -u):$(id -g)" "$directory" done fi python3 -B "$FROZEN/scripts/tests/package_c01_normal_fixture.py" verify "$ROOT" "$REPORT" || status=1 printf '%s\n' "$status" > "$REPORT/exit-code.txt" exit "$status" } trap cleanup EXIT if [[ -z "$PREVIOUS" ]]; then if [[ -n ${C01_FIXTURE_NATIVE_FROM:-} ]]; then python3 -B "$FROZEN/scripts/tests/package_c01_normal_fixture.py" reuse-native "$C01_FIXTURE_NATIVE_FROM" "$REPORT" else # Keep the compiler in the enclosing durable service's control group. Moving # it to a sibling scope could outlive this owner and release its flock early. taskset -c 0,1 \ env CARGO_TARGET_DIR="$TARGET" CARGO_BUILD_JOBS=1 CARGO_INCREMENTAL=0 \ cargo build --locked --offline --manifest-path "$FROZEN/core/Cargo.toml" --bin archipelago --config 'profile.dev.package.archipelago.debug=0' \ > "$REPORT/build.log" 2>&1 python3 -B "$FROZEN/scripts/tests/package_c01_normal_fixture.py" artifact "$TARGET/debug/archipelago" "$REPORT" python3 -B "$FROZEN/scripts/tests/package_c01_tmux_fixture.py" "$REPORT" fi fi IMAGE=$(podman image inspect localhost/archipelago-rootfs:latest --format '{{.Id}}') printf '%s\n' "$IMAGE" > "$REPORT/image-id.txt" if [[ ${C01_FIXTURE_ROOTFUL:-0} == 1 ]]; then # Never select installed rootful container storage or host networking. The # entire fixture subtree belongs to this durable job's delegated cgroup. store="$(dirname "$REPORT")/composed-rootful-store" ls -d "$(dirname "$REPORT")" >/dev/null sudo -n install -d -m 0700 "$store" parent=$(python3 -c 'from pathlib import Path; p=Path(Path("/proc/self/cgroup").read_text().strip().split(":",2)[2]); assert p.name=="supervisor"; print(p.parent)') engine=(sudo -n env "CONTAINERS_CONF_OVERRIDE=$REPORT/containers.conf" podman \ "--root=$store/root" "--runroot=$store/run" \ --storage-driver=overlay --cgroup-manager=cgroupfs --events-backend=file) # The outer generic containers-default MAC profile prevents PID1's generator # sandbox from mounting its own namespace. Product C01 confinement/account/IPC # checks remain unchanged; this outer fixture is not AppArmor acceptance. container_extra=(--cgroup-parent="$parent" --security-opt=apparmor=unconfined) printf '%s\n' '{"realm":"rootful-private-store-network-none","outer_apparmor":"unconfined for nested PID1 sandbox; not product confinement acceptance","cgroup_parent":"'"$parent"'"}' > "$REPORT/realm.json" if ! "${engine[@]}" image exists "$IMAGE"; then archive=${C01_FIXTURE_IMAGE_ARCHIVE:-$REPORT/base-image.tar} if [[ -z ${C01_FIXTURE_IMAGE_ARCHIVE:-} ]]; then podman save --format oci-archive --output "$archive" "$IMAGE" fi "${engine[@]}" load --input "$archive" > "$REPORT/image-load.log" 2>&1 "${engine[@]}" image exists "$IMAGE" fi fi ! "${engine[@]}" container exists "$NAME" CONTAINERS_CONF_OVERRIDE="$REPORT/containers.conf" "${engine[@]}" run --detach --tty --pull=never --name "$NAME" --hostname c01-prerequisite-qualification \ "${container_extra[@]}" \ --network=none --cgroupns=private --systemd=always --security-opt=unmask=ALL \ --cap-add=SYS_ADMIN --cap-add=SYS_PTRACE --cap-add=NET_ADMIN \ --memory=8g --memory-swap=8g --cpus=2 --pids-limit=512 --tmpfs /run --tmpfs /tmp \ --entrypoint /sbin/init "$IMAGE" --unit=basic.target --log-target=console > "$REPORT/container-id.txt" "${engine[@]}" exec "$NAME" install -d /opt/c01-fixture "${engine[@]}" cp "$REPORT/source.tar" "$NAME:/opt/c01-fixture/source.tar" "${engine[@]}" cp "$REPORT/normal-native.tar" "$NAME:/opt/c01-fixture/normal-native.tar" "${engine[@]}" cp "$REPORT/source-sha256.json" "$NAME:/opt/c01-fixture/source-sha256.json" "${engine[@]}" cp "$REPORT/native-artifact-sha256.json" "$NAME:/opt/c01-fixture/native-artifact-sha256.json" "${engine[@]}" cp "$REPORT/tmux-runtime.tar" "$NAME:/opt/c01-fixture/tmux-runtime.tar" "${engine[@]}" exec "$NAME" tar -xf /opt/c01-fixture/source.tar -C /opt/c01-fixture "${engine[@]}" exec "$NAME" chown -R root:root /opt/c01-fixture if [[ ${C01_FIXTURE_NTS:-0} == 1 ]]; then [[ ${C01_FIXTURE_ROOTFUL:-0} == 1 ]] python3 -B "$FROZEN/scripts/tests/prepare_c01_nts_dependencies.py" "$REPORT" for file in chrony-runtime.tar chrony-artifact.json nts-egress.nft nts-addresses.json; do "${engine[@]}" cp "$REPORT/$file" "$NAME:/opt/c01-fixture/$file" done "${engine[@]}" exec "$NAME" chown -R root:root /opt/c01-fixture "${engine[@]}" exec "$NAME" tar -xf /opt/c01-fixture/chrony-runtime.tar -C / "${engine[@]}" exec "$NAME" /usr/sbin/nft -f /opt/c01-fixture/nts-egress.nft # No host bridge/firewall changes. pasta creates its TAP only in this exact # owned fixture namespace and has no inbound port forwards. Initial-userns # root is required to join this rootful netns; SYS_TIME is explicitly removed. pid=$("${engine[@]}" inspect "$NAME" --format '{{.State.Pid}}') sudo -n python3 -c 'import ctypes,fcntl,os,select,signal,subprocess,sys pid=int(sys.argv[1]); pin=os.pidfd_open(pid); ns=os.open(f"/proc/{pid}/ns/net",os.O_RDONLY|os.O_CLOEXEC) group=open(f"/proc/{pid}/cgroup").read().strip().split(":",2)[2] assert group.startswith(sys.argv[2]+"/") and not select.select([pin],[],[],0)[0] assert fcntl.ioctl(ns,0xb703)==0x40000000 and os.fstat(ns).st_ino!=os.stat("/proc/1/ns/net").st_ino libc=ctypes.CDLL(None,use_errno=True) assert libc.unshare(0x20000)==0 and libc.mount(None,b"/",None,16384|(1<<18),None)==0 assert libc.mount(b"tmpfs",b"/tmp",b"tmpfs",2|4|8,b"mode=0755,size=1m")==0 target="/tmp/c01-nts-net" fd=os.open(target,os.O_CREAT|os.O_EXCL|os.O_RDONLY,0o444); os.close(fd) assert libc.mount(f"/proc/self/fd/{ns}".encode(),target.encode(),None,4096,None)==0 assert (os.stat(target).st_dev,os.stat(target).st_ino)==(os.fstat(ns).st_dev,os.fstat(ns).st_ino) class Header(ctypes.Structure): _fields_=[("version",ctypes.c_uint), ("pid",ctypes.c_int)] class Caps(ctypes.Structure): _fields_=[("effective",ctypes.c_uint), ("permitted",ctypes.c_uint), ("inheritable",ctypes.c_uint)] header=Header(0x20080522,0); caps=(Caps*2)() assert libc.capget(ctypes.byref(header),caps)==0 for field in ("effective","permitted","inheritable"): setattr(caps[0],field,getattr(caps[0],field)&~(1<<25)) assert libc.capset(ctypes.byref(header),caps)==0 and libc.prctl(24,25,0,0,0)==0 and libc.prctl(38,1,0,0,0)==0 child=None; stopping=False def stop(*_): global stopping stopping=True if child is not None: child.terminate() signal.signal(signal.SIGTERM,stop) try: # This parent watches the original container pidfd. Do not ask pasta to watch # a namespace pathname after its own filesystem sandbox has hidden that path. child=subprocess.Popen(["/usr/bin/pasta","--foreground","--runas","0:0","--no-netns-quit","--config-net","--no-map-gw","--no-dhcp","--no-dhcpv6","--no-ndp","--no-ra","--no-icmp","-4","-t","none","-u","none","-T","none","-U","none","--ns-ifname","c01-nts-egress","--netns",target]) if stopping: child.terminate() while child.poll() is None: if select.select([pin],[],[],.1)[0]: stop() raise SystemExit(child.wait()) finally: if child is not None and child.poll() is None: child.terminate(); child.wait() os.close(ns); os.close(pin)' \ "$pid" "$parent" > "$REPORT/pasta.log" 2>&1 & pasta_pid=$! pasta_start=$(sudo -n python3 -c 'import sys; raw=open("/proc/"+sys.argv[1]+"/stat").read(); fields=raw[raw.rindex(")")+2:].split(); assert fields[1]==sys.argv[2]; print(fields[19])' "$pasta_pid" "$$") for attempt in {1..40}; do if "${engine[@]}" exec "$NAME" python3 -c 'import json,subprocess; v=json.loads(subprocess.check_output(["ip","-j","-4","address","show","dev","c01-nts-egress"])); assert any(x.get("addr_info") for x in v)' >/dev/null 2>&1; then break; fi sleep .1 done "${engine[@]}" exec "$NAME" python3 -c 'import json,subprocess; v=json.loads(subprocess.check_output(["ip","-j","-4","address","show","dev","c01-nts-egress"])); assert any(x.get("addr_info") for x in v), "fixture NTS interface unavailable"' printf '%s\n' 'Only loopback, established replies, and pinned public NTS TCP4460/NTP UDP123; no DNS/host gateway/inbound forwarding' > "$REPORT/nts-egress-scope.txt" fi "${engine[@]}" exec --env C01_NORMAL_PID1=1 --env "C01_POWER_COMPONENT=${C01_POWER_COMPONENT:-0}" "$NAME" python3 -B \ /opt/c01-fixture/tests/regression/c01-normal-systemd.py 2>&1 | tee "$REPORT/test.log" } # Parse the complete driver before long builds; editing the worktree must not # move Bash's file cursor into a different command after a compiler returns. main "$@"; exit "$?"