#!/bin/sh # Q2 — ¿qué es el "runtime base" implícito? (SDD 16 §8) # # La pregunta de diseño que decide si la métrica de la Fase 2 mide deuda real de de-Alpinización # o mide ruido. Ninguna receta declara /bin/sh, la libc o /opt/zig, y ninguna debería. Pero si el # runtime base se define de más, la política concede Alpine entero y la evidencia no vale nada. # # Método: NO adivinarlo. Correr un build real con `política = clausura declarada` y dejar que # las denegaciones lo nombren. Lo que aparezca es, por definición, lo que todo build necesita y # nadie declara. # # Replica el sandbox real (`Sandbox::bwrap_args`): overlay del rootfs Alpine + las deps apiladas, # `--tmp-overlay /`, /proc, /dev, /tmp, zig en /opt/zig, fuentes en /src, salida en /out. set -eu cd "$(dirname "$0")/../.." BIN="${HARKAQ_BIN:-$HOME/.cache/harkaq}" ROOTFS=.dev-fs/alpine ZIG=.dev-fs/tools/zig # MODE=base → build SIN ninguna dep declarada. Por definición, TODA denegación es runtime base: # no hay clausura que pueda explicarla. Aísla la respuesta a Q2 sin juicio de valor. # MODE=zlib → el mismo build contra la dep zlib del store. Lo que sobre por encima del runtime # base ES la deuda de de-Alpinización, sin ambigüedad. MODE="${MODE:-zlib}" [ -d "$ROOTFS" ] || { echo "falta $ROOTFS"; exit 1; } [ -d "$ZIG" ] || { echo "falta $ZIG"; exit 1; } WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT mkdir -p "$WORK/src" "$WORK/out" if [ "$MODE" = configure ]; then # Una fuente autotools REAL. `configure` sondea el sistema entero (cientos de tests con # sed/grep/awk/rm...), así que es la prueba de si el runtime base de 5 paths medido contra # un `zig cc` mínimo aguanta, o si es POR FORMA DE BUILD. Es la pregunta que decide si la # métrica de <5% de la Fase 2 es alcanzable. DEPS="" SRCDIR="${Q2_SRC:-work/sources/libgpg-error-7a85413f2bc354f4}" [ -d "$SRCDIR" ] || { echo "falta $SRCDIR"; exit 1; } cp -a "$SRCDIR"/. "$WORK/src/" elif [ "$MODE" = base ]; then DEPS="" LDFLAGS="" cat > "$WORK/src/hello.c" <<'EOF' #include int main(void) { printf("hola\n"); return 0; } EOF else DEPS=$(ls -d store/*-zlib | head -1) LDFLAGS="-lz" cat > "$WORK/src/hello.c" <<'EOF' #include #include int main(void) { printf("zlib %s\n", zlibVersion()); return 0; } EOF fi # La clausura DECLARADA (D1) + las superficies que el sandbox define por contrato, no la receta: # /src (fuentes), /out (DESTDIR), /tmp (HOME). Nada de Alpine. POLICY="$WORK/policy" # Sin deps (MODE=base) la clausura declarada es VACÍA: la política son sólo las # superficies de contrato del sandbox. Todo lo demás que el build toque, se ve. : > "$POLICY" [ -n "$DEPS" ] && scripts/harkaq/harkaq-policy.sh $DEPS > "$POLICY" { echo "rw /src" echo "rw /out" echo "rw /tmp" echo "ro /opt/zig" # BASE_EXTRA: lo que las corridas previas demostraron que hace falta. Empieza vacío y se # llena SÓLO con lo que el kernel denunció — nunca por corazonada. Cada línea de acá es una # respuesta medida a Q2, no una conjetura. [ -n "${BASE_EXTRA:-}" ] && printf '%s\n' "$BASE_EXTRA" # Denegaciones ESPERADAS (§4.2): se deniegan a propósito y se declaran acá para que el # clasificador no las cuente como deuda. harkaq-exec las ignora (empiezan con #). [ -n "${EXPECT:-}" ] && printf '# expect %s\n' $EXPECT } >> "$POLICY" echo "── MODE=$MODE deps=[${DEPS:-ninguna}]" >&2 echo "── política: $(grep -c '^ro ' "$POLICY") ro, $(grep -c '^list ' "$POLICY") list, $(grep -c '^rw ' "$POLICY") rw" >&2 NONCE="q2-$$" # El canario va en la RAÍZ, no en /tmp: la política concede `rw /tmp` (es el HOME del build), así # que un canario ahí caería DENTRO de la clausura, se leería sin problema y no generaría ninguna # denegación ⇒ SinEvidencia eterno. El canario tiene que vivir donde la política NO alcanza, por # construcción. En el sandbox real la raíz es escribible (`--tmp-overlay /`) y bwrap puede crear # ahí el punto de montaje. (Lo cazó el propio D9: el lector se negó a certificar.) CANARY="/harkaq-canary-$NONCE" CSRC=$(mktemp); echo canario > "$CSRC" FIFO=$(mktemp -u); mkfifo "$FIFO" VERDICT=$(mktemp) trap 'rm -rf "$WORK" "$CSRC" "$FIFO" "$VERDICT"' EXIT "$BIN/harkaq-audit" --canary "$CANARY" --timeout "${HARKAQ_TIMEOUT:-60}" --ready-fd 3 \ 3>"$FIFO" >"$VERDICT" & AUDIT_PID=$! read -r _ < "$FIFO" || true # El probe del canario usa SÓLO builtins del shell: `read` + redirección. Con `cat` dependía de # /bin/cat (otro applet de busybox) y, si no estaba en la clausura, el probe moría sin probar # nada — el canario tiene que ser lo más barato e independiente del sistema. if [ "$MODE" = configure ]; then BUILD='cd /src && ./configure --prefix=/usr >/tmp/conf.log 2>&1; echo "configure rc=$?"' else BUILD='cd /src && zig cc -mcpu=baseline hello.c '"$LDFLAGS"' -o /out/hello && echo BUILD-OK' fi CMD="read _ < $CANARY || true; $BUILD" set +e bwrap --overlay-src "$ROOTFS" $(for d in $DEPS; do printf -- "--overlay-src %s " "$d"; done) --tmp-overlay / \ --proc /proc --dev /dev --tmpfs /tmp \ --ro-bind "$ZIG" /opt/zig \ --bind "$WORK/src" /src --bind "$WORK/out" /out \ --ro-bind "$CSRC" "$CANARY" \ --ro-bind "$BIN/harkaq-exec" /tmp/harkaq-exec \ --ro-bind "$POLICY" /tmp/harkaq.policy \ --chdir /src --unshare-all --die-with-parent \ --setenv PATH /opt/zig:/usr/bin:/bin \ --setenv HOME /tmp \ --setenv CC "zig cc -mcpu=baseline" \ --setenv CXX "zig c++ -mcpu=baseline" \ --setenv AR "zig ar" \ --setenv SOURCE_DATE_EPOCH 1 --setenv LC_ALL C --setenv LANG C --setenv TZ UTC \ /tmp/harkaq-exec --policy /tmp/harkaq.policy -- /bin/sh -c "$CMD" RC=$? set -e sleep 2 kill -TERM "$AUDIT_PID" 2>/dev/null || true wait "$AUDIT_PID" 2>/dev/null || true echo "── build rc=$RC" >&2 scripts/harkaq/harkaq-verdict.py "$VERDICT" "$POLICY" --human