#!/bin/sh # runtime-policy.sh [args…] — política de RUNTIME derivada por medición (SDD 17 §3). # # La clausura que harkaq mide en BUILD **no** es la política de runtime: un build lee `zlib.h`, # `make`, el compilador; el binario resultante no lee nada de eso. Son dos clausuras de dos # sujetos distintos. Lo que sí sirve es la TÉCNICA: correr el binario bajo la jaula con política # mínima y dejar que las denegaciones digan qué necesita. Eso ES su política de runtime, derivada # por evidencia y no escrita a mano. # # scripts/harkaq/runtime-policy.sh store/-htop/usr/bin/htop --version # # BASELINE: el `sh` que lanza al binario también deniega cosas (locale, ld.so.cache). Se mide # primero un `true` para conocer ese ruido y RESTARLO — si no, la política del binario se lleva # la basura del lanzador. # # Cómo encaja con la concesión firmada de arje/wawa (PLAN-ATESTACION): # - `ConcesionCapacidad` firma `(blake3(binario), u32)` en 36 bytes canónicos, zero-alloc, Ring 0 # ⇒ es la FRONTERA gruesa. NO se toca: meterle paths rompería todas las firmas y contradice # "capacidad = frontera física, no tabla". # - Esta política es el DETALLE granular (fs, paths), vive en userspace y se aplica con Landlock. # Los dos coexisten: el kernel verifica la frontera; Landlock aplica el detalle. set -eu BIN="${1:?uso: runtime-policy.sh [args…]}" shift || true HUB="$(cd "$(dirname "$0")/../.." && pwd)" cd "$HUB" BIN="$(cd "$(dirname "$BIN")" && pwd)/$(basename "$BIN")" # absoluto POL="$(mktemp)"; BASE="$(mktemp)"; MED="$(mktemp)" trap 'rm -f "$POL" "$BASE" "$MED"' EXIT # Política mínima DERIVADA (no a mano: escribirla a mano se olvida de libc.musl y el sh ni # arranca). El sandbox de harkaq-run monta el HOST ⇒ el cierre es el del sh DEL HOST. scripts/harkaq/harkaq-base-closure.py / /bin/sh 2>/dev/null > "$POL" echo "ro $BIN" >> "$POL" denegados() { # corre $1 bajo la jaula y lista los paths denegados HARKAQ_TIMEOUT=10 scripts/harkaq/harkaq-run.sh "$POL" "$1" 2>/dev/null \ | grep '^{' | head -1 \ | python3 -c 'import json,sys try: v=json.load(sys.stdin) if v["estado"]=="SinEvidencia": print("SIN-EVIDENCIA:"+v.get("motivo","")[:60]); raise SystemExit for d in v["denials"]: print(d["path"]) except Exception: pass' } echo "══ baseline: el ruido del lanzador (sh)" denegados "true" | sort -u > "$BASE" grep -q 'SIN-EVIDENCIA' "$BASE" && { echo " $(cat "$BASE")"; echo " ⇒ sin evidencia fiable: no se afirma ninguna política"; exit 3; } sed 's/^/ − /' "$BASE" echo "══ medición: $(basename "$BIN") $*" denegados "$BIN $*" | sort -u > "$MED" grep -q 'SIN-EVIDENCIA' "$MED" && { echo " $(cat "$MED")"; exit 3; } echo echo "══ POLÍTICA DE RUNTIME de $(basename "$BIN") (medida − baseline):" propios=$(comm -23 "$MED" "$BASE") if [ -z "$propios" ]; then echo " (ninguna) — el binario no tocó NADA fuera de sí mismo." echo " ⇒ política Landlock: sólo \`ro $BIN\`. Es el caso de un estático que no abre ficheros." else echo "$propios" | sed 's/^/ ro /' # CONTACTO con el §3 (clases de servicio, diseño de Fable 5): la medición no sólo lista # paths — propone la CLASE. El path va a Landlock (detalle, userspace); la clase va al bit # del u32 firmado (frontera, Ring 0). La medición ALIMENTA la tabla; la tabla decide el bit. # Ninguno ensancha por su cuenta (D3). clases="" echo "$propios" | grep -qE '/etc/(resolv\.conf|hosts)' && clases="$clases dns" echo "$propios" | grep -qE '/etc/ssl/certs' && clases="$clases tls-certs" echo "$propios" | grep -qE '/usr/share/zoneinfo|/etc/TZ' && clases="$clases tz" echo "$propios" | grep -qE '/dev/log' && clases="$clases syslog" if [ -n "$clases" ]; then echo echo " ── clases de servicio detectadas (§3: van al u32 firmado, NO a Landlock):" for c in $clases; do echo " $c"; done echo " ⇒ declarar por nombre en la ConcesionCapacidad; el resto de arriba es Landlock." fi echo echo " ⇒ los `ro` van a la política Landlock del binario instalado." fi