gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 815Rust 56Shell 37Markdown 291229 Definitions

cierres §3: política de runtime derivada por medición (sin tocar la cripto)

El #3 decía "la clausura medida por harkaq en build = permisos del binario
instalado". MEDIDO: es falso en dos ejes, y por eso NO se implementó así.

1. SUJETO: la clausura de BUILD (zlib.h, make, gcc) no es la de RUNTIME. Un build
   lee headers; el binario resultante no. Dos clausuras, dos sujetos.
2. GRANULARIDAD/CRIPTO: lo que la ConcesionCapacidad firma NO es card-core::
   Permissions (struct) sino format::Permisos = u32 bitmask, dentro de
   `mensaje_capacidad` = hash(32)||permisos_le(4) = 36 bytes canónicos, zero-alloc,
   con espejo no_std en wawa-kernel (Ring 0). Meterle paths rompería TODAS las
   firmas y contradice el "capacidad = frontera física, no tabla" que el propio
   plan cita como referencia.

⇒ Camino elegido: la concesión firmada QUEDA INTACTA (frontera gruesa, Ring 0) y
la clausura granular se deriva por MEDICIÓN como política Landlock en userspace,
donde Vec<String> sí cabe. Coexisten: el kernel verifica la frontera, Landlock
aplica el detalle.

runtime-policy.sh: corre el binario bajo la jaula con política mínima DERIVADA
(harkaq-base-closure, no a mano) y resta el BASELINE del lanzador (el sh deniega
locale/ld.so.cache; sin restarlo, la política del binario se lleva esa basura).

Demo real — htop (musl-estático): baseline 3 paths del sh; htop --version no tocó
NADA propio ⇒ su política de runtime es sólo `ro <su binario>`. Medido, no supuesto.
htop tiene 0 NEEDED: para un estático la política NO sale de las libs, sólo de
correrlo — lo que confirma que la técnica de harkaq es la única vía para el #3.

+ FIX del lector (lo cazó el chequeo anti-pérdida `nº ACCESS == denials`): descartaba
los ACCESS previos al canario porque hasta verlo no sabe su domain= — pero el sh
deniega ANTES del probe y esas son suyas. El kernel contaba 4 y el lector recibía 1.
Ahora se bufferizan con su domain y se rescatan retroactivamente al ver el canario.
Sin ese chequeo habría emitido una política con 1 de 4 accesos, perfectamente creíble.
(Primer intento de fix —drenar el socket al ver el deallocated— era plausible y NO
era la causa: el contador siguió en 4 vs 1.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

be52e735ab34d7bc3091c0ed79721c5d30eb2a10 · by sergio · (3 months ago) · parent e9b628372 · browse the tree at this commit

Semantic delta · 2

scripts/harkaq/harkaq-audit.c

2 files · +100 −2

diff --git a/scripts/harkaq/harkaq-audit.c b/scripts/harkaq/harkaq-audit.c
index e6ec0e2d..a45b1c17 100644
--- a/scripts/harkaq/harkaq-audit.c
+++ b/scripts/harkaq/harkaq-audit.c
@@ -192,6 +192,13 @@ int main(int argc, char **argv) {
 
     struct denial denials[MAX_DENIALS];
     int nd = 0, canary_seen = 0, kernel_count = -1;
+    int drenando = 0;   // el dominio ya murió: drenar lo que quede antes de emitir
+    // ACCESS que llegan ANTES de ver el canario: no se pueden filtrar todavía (el `domain=` del
+    // build se conoce al ver el canario), pero pueden ser SUYOS — el `sh` deniega cosas antes de
+    // llegar al probe. Descartarlos perdía registros y el contador del kernel lo cazaba (4 vs 1).
+    // Se bufferizan con su domain y se filtran retroactivamente.
+    static struct { char dom[64]; struct denial d; } previos[MAX_DENIALS];
+    int n_prev = 0;
     char domain[64] = "";
     struct timeval tv = {.tv_sec = 1};
     setsockopt(nl, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
@@ -203,6 +210,9 @@ int main(int argc, char **argv) {
 
     while (time(NULL) < deadline && !parar) {
         ssize_t n = recv(nl, buf, sizeof(buf), 0);
+        // Drenando: el dominio murió y sólo faltan los ACCESS rezagados. Si el socket se agota
+        // (timeout de 1s) o ya cuadramos con el contador del kernel, cerramos.
+        if (drenando && (n <= 0 || (kernel_count >= 0 && nd + 1 >= kernel_count))) goto done;
         if (n <= 0) continue;
         for (struct nlmsghdr *h = (struct nlmsghdr *)buf; NLMSG_OK(h, n); h = NLMSG_NEXT(h, n)) {
             if (h->nlmsg_type != AUDIT_LANDLOCK_ACCESS && h->nlmsg_type != AUDIT_LANDLOCK_DOMAIN)
@@ -225,9 +235,24 @@ int main(int argc, char **argv) {
                 if (!canary_seen && !strcmp(path, canary)) {
                     canary_seen = 1;
                     snprintf(domain, sizeof(domain), "%s", dom);
+                    // Rescatar del buffer los ACCESS previos que resultaron ser de MI dominio.
+                    for (int k = 0; k < n_prev && nd < MAX_DENIALS; k++)
+                        if (!strcmp(previos[k].dom, domain)) denials[nd++] = previos[k].d;
                     continue;  // el canario no es una denegación del build: es el sello del lector
                 }
-                if (!canary_seen || strcmp(dom, domain) != 0) continue;  // de otro build
+                if (!canary_seen) {
+                    // Aún no sé mi domain: guardar por si resulta ser mío.
+                    if (n_prev < MAX_DENIALS) {
+                        snprintf(previos[n_prev].dom, sizeof(previos[n_prev].dom), "%s", dom);
+                        field(rec, "blockers", previos[n_prev].d.blockers, sizeof(previos[n_prev].d.blockers));
+                        snprintf(previos[n_prev].d.path, sizeof(previos[n_prev].d.path), "%s", path);
+                        field(rec, "dev", previos[n_prev].d.dev, sizeof(previos[n_prev].d.dev));
+                        field(rec, "ino", previos[n_prev].d.ino, sizeof(previos[n_prev].d.ino));
+                        n_prev++;
+                    }
+                    continue;
+                }
+                if (strcmp(dom, domain) != 0) continue;  // de otro build
                 if (nd < MAX_DENIALS) {
                     field(rec, "blockers", denials[nd].blockers, sizeof(denials[nd].blockers));
                     snprintf(denials[nd].path, sizeof(denials[nd].path), "%s", path);
@@ -241,7 +266,13 @@ int main(int argc, char **argv) {
                 if (!strcmp(status, "deallocated")) {
                     char cnt[32] = "";
                     if (field(rec, "denials", cnt, sizeof(cnt))) kernel_count = atoi(cnt);
-                    goto done;  // el dominio murió: el build terminó
+                    // NO salir de una: el `deallocated` puede adelantarse a registros ACCESS que
+                    // siguen en el buffer del socket. Con un inquilino instantáneo (`htop
+                    // --version`) el kernel contaba 4 denegaciones y el lector recibía 1 ⇒ el
+                    // chequeo anti-pérdida disparaba SinEvidencia. Se drena hasta cuadrar con el
+                    // contador (o agotar el socket), y recién ahí se cierra.
+                    drenando = 1;
+                    continue;
                 }
             }
         }
diff --git a/scripts/harkaq/runtime-policy.sh b/scripts/harkaq/runtime-policy.sh
new file mode 100755
index 00000000..7019ac7f
--- /dev/null
+++ b/scripts/harkaq/runtime-policy.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+# runtime-policy.sh <binario> [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/<hash>-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 <binario> [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 /'
+    echo
+    echo "   ⇒ pegar en la política Landlock del binario instalado."
+fi