main
20
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9395fbfc5c |
piloto trace END-TO-END: build real de zlib trazado en store desechable — el hash REPRODUCE bit a bit el sellado (b3:2623a403), poda filosa (make 1/8 ficheros, busybox 1/2), taxonomía de ruido medida (+/cache al normalizador); harkaq-trace-build.sh orquesta (tracer por fase vía /proc/pid/root)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
91496007ca |
harkaq-trace: la traza POSITIVA de la clausura (plan-freebsd T1.1-T1.2, prior art filemon/META MODE)
- harkaq-trace.c: fanotify FAN_MARK_FILESYSTEM — agnóstico de mount-ns, el tracer en el HOST ve los open() de dentro del bwrap (ptrace lo deniega el seccomp D4; audit sólo emite denegaciones). Crudo y tonto a propósito (reparto Q1c); D9: sin fanotify ⇒ exit 3 SinEvidencia, jamás traza vacía. - harkaq-trace-norm.py: canoniza (drop /proc /sys /dev /tmp /out /src, dedupe, orden estable — la lección nº1 de META MODE) + atribución por dep con --resumen = la señal de PODA (T1.4): dep declarada con 0 toques = grasa. - VALIDADO: modo --fs como root (worker, 5s, sin tocar colas): capturó exactamente zlib.h + 3×libz.a de un cat/head; laptop sin privilegio ⇒ SinEvidencia honesto. Piloto completo (traza de un hammer build real + resumen de poda) pendiente de un rebuild natural en el worker. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
83d40af35c |
harkaq: el clasificador nunca propone runtime base como declarable (fix de raíz de busybox)
El bug que declaró `busybox` en 29 recetas y rompió binutils: harkaq-suggest veía `/bin/busybox` denegado, encontraba `recipes/busybox.toml` en el store y decía "declarar dep: busybox" — sin mirar que la política YA lo concede (`ro /bin/busybox`: el sandbox corre `sh -c` y /bin/sh→busybox ⇒ es CONTRATO, no dep). Fix: harkaq-suggest ahora lee los paths CONCEDIDOS de la política (`ro`/`rw`/`list`), no sólo los `# expect`. Si un path está concedido ⇒ categoría RUNTIME BASE: no se declara, aunque el store lo provea. Las 4 clasificaciones verificadas tras el cambio: /bin/busybox → RUNTIME BASE (ya concedido; NO declarar) ← el fix /usr/bin/make → DECLARABLE (declarar dep: make) /usr/lib/libstdc++.so → IRREDUCIBLE (nadie lo provee) /usr/bin/gcc en broot → DEUDA DE COMPILADOR (compiler=gcc) /usr/bin/gcc en zlib → sin deuda (sonda esperada; zlib compila con zig) Con esto puesto, busybox JAMÁS se habría declarado. Cierra el TODO que dejó el revert: la campaña automática ya no puede cimentar contrato como dependencia. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d99ed58850 |
harkaq: revertir busybox (era runtime base, rompía builds) + coordinar el §3 con Fable 5
DOS ERRORES MÍOS, uno de dirección y otro técnico, los dos en la cosecha automática:
1. DIRECCIÓN: declaró `busybox` como dep en 29 recetas — cuando la Etapa C lo está
ELIMINANDO (USERLAND_COMPONENTS=[uutils,findutils,…], ya cerrada; joyas-reusables
§5 lo confirma: "uutils… Ubuntu 25.10 los envía como default"). Estaba cimentando
la deuda que el roadmap borra.
2. TÉCNICO: busybox YA está en el runtime base de harkaq (`ro /bin/busybox`, `ro
/bin/sh` — el sandbox corre `sh -c` y /bin/sh→/bin/busybox). Es CONTRATO, no dep.
Declararlo apila el busybox de hammer sobre el de Alpine y ROMPE el build:
binutils daba "cannot run C compiled programs" en las DOS máquinas. Verificado:
sin busybox declarado, binutils construye (b3:f4507dcd…). Y la cadena se explica:
binutils roto ⇒ zlib (que lo declara) tampoco construía.
Auditoría de la cosecha (diff real, no la línea completa del +): añadió sólo 5 deps
distintas — make ×73, busybox ×29, perl ×8, pkgconf ×5, binutils ×1. Sólo busybox
estaba mal; las otras 4 son deps reales medidas. busybox revertido de 30 recetas
(queda sólo en busybox.toml, pre-existente).
Es el mismo error que ya me habían señalado con otro disfraz: MEDIR BIEN Y ACCIONAR
MAL. harkaq midió correcto (el build toca /bin/busybox: es el shell); la acción
correcta no era declararlo sino reconocerlo como contrato.
+ COORDINACIÓN del §3 con Fable 5 (mismo diseño, tareas repartidas):
- Su lección casper queda CONFIRMADA y REFORZADA: la clausura de build no sólo le
FALTAN las clases del mundo (offline) — también le SOBRA casi todo (headers, gcc).
Medido: htop (estático, 0 NEEDED) no toca NADA al correr ⇒ política = su binario.
- Su "la clase viaja como campo de la ConcesionCapacidad, sin formato nuevo" se
cumple LITERALMENTE: lo firmado es format::Permisos = u32 bitmask en 36 bytes
canónicos (Ring 0) ⇒ las clases SON los bits. La cripto no se toca.
- Diseño unificado: frontera (clases, u32, declaradas) + detalle (paths, Landlock,
medidos). D3 rige en ambos.
- Reparto: clases→Fable 5; medición/harness→Opus. CONTACTO: runtime-policy.sh ahora
emite la CLASE detectada (/etc/resolv.conf→dns, /etc/ssl/certs→tls-certs, …), no
sólo el path: la medición alimenta la tabla, la tabla decide el bit.
- Consumidor esperando: plan-jaula-juegos F1 (Steam que no puede leer ~/.ssh).
+ juez.sh (§10): nombre único por corrida (con uno fijo pega en caché ⇒ falso
"sin evidencia"). Fue el juez quien destapó todo esto en su primera corrida real.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
be52e735ab |
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> |
||
|
|
c6ff858f23 |
harkaq: cpp/getent en las sondas del provisioning (coherente con base.policy)
Faltaba persistir en harkaq-farm-setup.sh las 2 sondas que añadí a base.policy al revisar needs-review (cpp=frontend gcc, getent=NSS musl). Sin esto, un worker nuevo derivaría una base.policy sin ellas y volvería a marcar irreducibles falsos (dhcpcd/strace). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
71ec92345f |
harkaq: el clasificador distingue gcc-compilador de gcc-sonda — matar-gcc son 47 recetas
El límite del modelo que mtdev destapó, cerrado. harkaq clasificaba /usr/bin/gcc
como "sonda esperada" SIEMPRE, así que marcaba `Hermetico` a las recetas que
compilan CON el gcc de Alpine (compiler=gcc = CC=gcc, musl+GNU ld). Estuvo ciego
a la deuda de compilador todo el barrido.
Fix en harkaq-suggest: 5º arg = la receta medida. Si usa compiler=gcc, los
frontends de gcc (gcc/cpp/c89/c99/g++/cc/ld/as) dejan de ser "esperada" y pasan a
DEUDA DE COMPILADOR (matar-gcc). Para compiler=zig-cc (default, 714 recetas) gcc
sigue siendo sonda de configure — esperada, como antes. Verificado: mtdev(gcc) →
deuda de compilador; zlib(zig) → sin deuda.
EL HALLAZGO: matar-gcc no es "{kernel, cmake}" como creía la memoria del proyecto.
Son 47 recetas (36 C puro + 11 Rust con sys-crate C) que dependen del gcc de
Alpine. harkaq lo reveló sólo cuando aprendió a leer el compiler= de la receta —
antes gcc-como-sonda-esperada ocultaba una deuda mayor que toda la que sí detectó.
Reporte: tandas/needs-review-harkaq/deuda-compilador-matar-gcc.md.
Cada una usa gcc porque zig-cc la miscompila (documentado en su receta). Acción
por receta: migrar a zig cuando zig mejore, o aceptar el escape como deuda
conocida. Ahora es una deuda NOMBRADA y contada, no invisible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
ee51e5d596 |
harkaq: el kernel de hammer ya trae Landlock — ABI 7 verificado BOOTEANDO (Fase 1 precond. 1)
recipes/linux.toml: -e SECURITY -e SECURITY_LANDLOCK -e AUDIT. Era lo único que
faltaba para que harkaq corra DENTRO de hammer (VM/metal) y no sólo en el laptop
y la granja. El CONFIG_LSM del defconfig ya lista `landlock` de primero ⇒ bastó
encenderlo, sin tocar la cadena de LSMs.
AUDIT se fija explícito aunque ya viniera =y por defconfig: sin él el kernel no
emite un solo registro y harkaq certificaría TODO como hermético en silencio
(§3.4). Es la precondición de la que cuelga la evidencia; no se deja al azar de
un default.
VERIFICADO BOOTEANDO, no leyendo el .config — que dice lo que se compiló, no lo
que el kernel hace al arrancar. Misma disciplina de §3.1 (el ABI se consulta por
syscall, jamás por versión) llevada a la verificación: el único que sabe si
Landlock está vivo es el kernel vivo. scripts/harkaq/vm-abi-probe.c es un /init
de initramfs mínimo que pregunta y apaga:
===== HARKAQ EN EL KERNEL DE HAMMER =====
LANDLOCK ABI = 7
audit de denegaciones (>=7): SI
=========================================
Kernel nuevo: b3:f2583d61… (el hash cambia, como se esperaba; el viejo
34755ff2… decía "# CONFIG_SECURITY_LANDLOCK is not set").
Con esto las 4 precondiciones de la Fase 1 están cerradas y harkaq corre en las
tres máquinas del proyecto: laptop (ABI 10), granja (ABI 7 tras el bump de la
golden) y el kernel propio de hammer (ABI 7).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
caa23f9ab7 |
harkaq: seccomp implementado (D4) — denylist, no allowlist; el hash no se mueve
D4 declaraba seccomp "obligatorio, no opcional" y harkaq-exec tenía CERO
seccomp: el documento afirmaba algo que el código no hacía. Cerrado.
CORRECCIÓN DELIBERADA A D4: pedía ALLOWLIST por syscall, se implementó DENYLIST.
Un allowlist para builds ARBITRARIOS (compiladores, make, shells, linkers, perl)
es un blanco móvil que cada herramienta nueva rompe — el riesgo del §7 ("los
falsos positivos matan proyectos de sandboxing") aplicado a las syscalls, y con
peor final: un build que muere por una syscall legítima no da un diagnóstico
útil, da un misterio. Lo PELIGROSO sí es enumerable y estable: no hay build
honesto que cargue módulos, haga kexec o attachee un ptrace.
Denegadas: io_uring_*, ptrace, process_vm_{readv,writev}, bpf, userfaultfd,
keyctl/add_key/request_key, {init,finit,delete}_module, kexec_*,
perf_event_open, mount/umount2, open_tree/move_mount/fs*, setns.
pivot_root NO: bwrap lo usa ANTES de llegar a harkaq-exec.
EPERM y no KILL: matar deja un cadáver sin explicación; EPERM deja al build
fallar donde corresponde y al log decir por qué. Se instala DESPUÉS de
no_new_privs y de Landlock, justo antes del exec, y se hereda por fork/exec como
el dominio (D5). Si el kernel lo rechaza NO se corre el build (D7: media jaula
creyéndose entera es peor que ninguna).
Chequeo de arquitectura antes del número de syscall: los nros son POR ARCH y sin
el check un binario i386 podría colar otra syscall con el mismo número — el error
clásico de los filtros seccomp a mano.
COMPROBADO: ptrace → EPERM bajo la jaula (control positivo), y un build REAL de
zlib sale Hermetico ×3 con el hash del artefacto IDÉNTICO al de antes de seccomp
(b3:adc5c251…). Añadir media jaula no movió un byte — como debe ser: esto recorta
superficie de escape, no cambia el build.
El --seccomp <fd> de bwrap quedó SIN USAR: harkaq-exec ya corre dentro y con
no_new_privs puesto, así que instala el filtro él mismo — una pieza menos de
plumbing y el filtro queda al lado de la política que lo justifica.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
13233ee4e9 |
harkaq: primer barrido en la granja — la deuda irreducible de la muestra es perl (§4.10)
harkaq-farm-setup.sh: provisiona un worker para barrer (idempotente; gatea por
ABI>=7 y aborta si no llega — con la golden vieja el barrido daría SinEvidencia
en TODO). Deriva el runtime base DEL ROOTFS DEL WORKER, no copia el del laptop:
es por-rootfs (§4.3) y los sonames dependen de la versión de Alpine.
Barrido: 24 recetas, 14 con veredicto. Primer número crudo: 4 irreducibles (29%).
ERA FALSO, y por dos motivos propios:
1. GAP DE POLÍTICA. Los `list` salían sólo de los ancestros de la clausura ⇒ una
receta SIN deps (bash) no generaba ni uno y todo escaneo del árbol caía como
denegación (/usr/lib). Listar NO es leer: Landlock separa READ_DIR de
READ_FILE ⇒ la estructura del árbol es CONTRATO. Se puede `ls /usr/lib` sin
leer un solo fichero no declarado; la deuda es leer lo ajeno, no saber que
existe. Idem /var/tmp: con --tmp-overlay / es scratch descartable, como /tmp.
2. La heurística del catálogo es por NOMBRE y un fichero no se llama como su
paquete: /usr/bin/ranlib lo trae `binutils`, /usr/bin/diff lo trae
`diffutils`. El único que sabe la verdad es el STORE (conoce la lista de
ficheros de cada artefacto) — y el worker tiene 103 artefactos contra los
cientos del hub.
⇒ EL WORKER MIDE, EL HUB CLASIFICA. Misma separación que lector/clasificador: el
que tiene el privilegio —o los datos— hace lo mínimo. Reclasificado en el hub:
bash ranlib,/usr/lib,/var/tmp → nada (gap de política)
doas /usr/bin/diff → nada (diffutils lo provee)
ca-certificates /usr/bin/perl → /usr/bin/perl
curl /usr/bin/perl → /usr/bin/perl
RESULTADO: la deuda irreducible de toda la muestra es UN path — /usr/bin/perl
(2 de 14 = 14%). No hay recipes/perl.toml ni artefacto: deuda genuina y nombrada.
Todo lo demás era declarable (make ×10, ranlib→binutils, diff→diffutils).
Sigue por encima del <5% del §7, pero el perfil es el que importa: NO hay cola
larga de sorpresas. La deuda de un catálogo entero se resume en "declarar make" y
"no tenemos perl".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
cba59e829b |
harkaq: la granja VPS ya puede ser el compilador continuo — golden bumpeada a 6.17 (ABI 7)
El VPS va a ser el compilador permanente ⇒ harkaq TIENE que correr ahí o el
frente queda como decoración de laptop. Verificado de punta a punta en un worker
efímero desde la golden, no en el papel:
antes: kernel 6.8.0-134 → Landlock ABI 4 → sin audit → SinEvidencia siempre
después: kernel 6.17.0-40 → Landlock ABI 7 → AUDIT ✓
`apt install linux-image-generic-hwe-24.04` (archivo estándar de Ubuntu 24.04, sin
PPA ni cambiar distro), reboot, y la cadena COMPLETA de evidencia corre igual que
en el laptop:
same-exec 2 registros · new-exec 0 (el ciego) · new-exec-logon 4
domain=14e9f83c2 blockers=fs.read_file path="/etc/passwd" dev="sda1" ino=133259
El store del catálogo (103 artefactos) sobrevivió el bump intacto.
NUEVA GOLDEN: snapshot 408909310 "hammer-golden-harkaq-6.17-2026-07-15".
farm-up.sh pasa a usarla por defecto. La vieja (405120842) queda como fallback.
+ harkaq-uapi.h — EL HALLAZGO QUE IMPORTA para un compilador continuo: **el kernel
y los headers envejecen por separado**. El worker corre 6.17 pero su
linux-libc-dev es 6.8 y NO define NADA de lo necesario: ni los flags de log de ABI
7/8, ni AUDIT_LANDLOCK_ACCESS/DOMAIN (¡los tipos de registro!), ni IOCTL_DEV de
ABI 5. El kernel puede; el compilador no sabe pedírselo.
Es el mismo error de §3.1 al revés: allá, deducir el ABI de la versión del kernel;
acá, de la versión de los headers. NINGUNA de las dos dice la verdad — la única
fuente es el syscall en runtime. Estas constantes son números de contrato de UAPI,
estables por definición, seguros de fijar con #ifndef.
Sin esto harkaq sólo compila en distros con headers al día, que es justo lo que un
compilador continuo NO puede exigir. Y el modo de falla habría sido el peor: sin
AUDIT_LANDLOCK_ACCESS el lector filtraría por un número que no conoce y vería CERO
denegaciones — el falso `Hermetico` de D9, esta vez por headers viejos.
Nota: ABI 7 da el audit (lo que el proyecto necesita); TSYNC (ABI 8) pide 7.0 y no
está — es robustez opcional de D5, no un bloqueo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
993060630d |
harkaq: el bucle CERRADO — de Impuro a Hermetico sobre una receta real (§4.7)
Diagnosticar no vale nada si no se puede accionar. El bucle completo sobre recipes/zlib.toml, cada paso guiado SÓLO por lo que dijo harkaq: zlib tal cual → Impuro: /usr/bin/make → "declarar dep: make" + make → Impuro: /usr/bin/ranlib → "declarar dep: binutils" + binutils → Impuro: liblto_plugin.so → irreducible ⇒ clasificar final → Hermetico ×3 fases, artefacto b3:adc5c251… SELLADO Cada capa que se pela destapa la siguiente, y TODAS convergen en el gcc de Alpine. El último hallazgo es el que no se encuentra a mano: los binutils DE HAMMER —ya declarados como dep— cargan el plugin LTO del gcc DE ALPINE (/usr/libexec/gcc/x86_64-alpine-linux-musl/15.2.0/liblto_plugin.so). No es un fallo de declaración de la receta: es un agujero de soberanía DENTRO de un artefacto que hammer construye. Va a denegación esperada por la misma razón que /usr/bin/gcc (§4.2): el build completa sin él ⇒ es una SONDA, no una necesidad, y bloquearla es DESEABLE — impide que los binutils de hammer usen el plugin de Alpine por detrás. D3 otra vez: no se calla, se clasifica. Detalle que confirma el modelo: el hash del artefacto es IDÉNTICO antes y después de clasificar la sonda (b3:adc5c251… las dos veces). Clasificar cambia el VEREDICTO, no el BUILD. Es el principio de H1 sosteniéndose solo: la evidencia es comportamiento, no identidad (recipe.rs:228, y por eso Evidence está fuera de hash_inputs). Y eso es lo que `Hermetico` significa ahora, con todo el peso: el kernel certificó que ese build no usó NADA fuera de su clausura declarada, y el canario prueba que el certificado no es el silencio de un lector roto. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d0c9cdfa33 |
harkaq: deuda declarable vs irreducible (§4.5) + primer barrido de Fase 2 (§4.6)
La métrica del §7 (<5% de recetas necesitan excepción) medía otra cosa. zlib
salió Impuro por /usr/bin/make y parecía runtime base: NO lo es. recipes/make.toml
existe, store/fbad44ac…-make está sellado y SWAP_MAKE es uno de los swaps del
selfhost-verify ⇒ a zlib le falta una LÍNEA en [deps], no una excepción.
declarable el store ya provee el path ⇒ arreglo mecánico → NO cuenta
irreducible nadie lo provee ⇒ receta nueva o runtime base → SÍ cuenta
harkaq-suggest.py cruza cada path de deuda contra el store (el mapeo de §4.1 al
revés: el path relativo dentro del artefacto ES el path del sandbox):
/usr/bin/make → declarar dep: make
/usr/lib/libz.so.1.3.2 → irreducible (la zlib de hammer da libz.a estático,
no .so ⇒ NO se arregla declarando)
El diagnóstico deja de ser "algo pasó" y pasa a ser "hacé esto".
PRIMER BARRIDO (6 recetas C, fuentes cacheadas):
Hermetico ............... 0
sólo deuda DECLARABLE ... 5 ← y la deuda es EL MISMO path en las 5: /usr/bin/make
con deuda IRREDUCIBLE ... 1 (brotli)
Lo que importa: la deuda de 5/6 es UN SOLO path y se arregla con una línea. No
hay cola larga de excepciones — que era el riesgo de muerte del §7.
Y el caso irreducible es la frontera CONOCIDA: brotli (C++) pide
/usr/lib/libstdc++.so.6.0.34 y /usr/lib/libgcc_s.so.1 — el runtime C++ del gcc de
Alpine, que hammer no construye. Es exactamente lo que la campaña "matar gcc" ya
tenía identificado. TERCERA vez que harkaq llega por su cuenta a una lista que
otro frente ya tenía: los swaps del selfhost-verify (§4.3), los binutils (§4.4)
y ahora el runtime C++.
Honestidad: 6 recetas no son 750 y son las fáciles. 1/6 = 17%, muy por encima del
<5% — pero el único caso es un problema conocido, nombrado y compartido con otros
dos frentes, no una cola de sorpresas. El barrido grande es trabajo de granja.
Bug del barrido encontrado por el propio barrido: copiar la receta a otro
directorio rompe la resolución de deps.build (son relativas al dir de la receta)
⇒ brotli fallaba con "no pude cargar la dep 'cmake'" y se descartaba EN SILENCIO
— o sea que se descartaban justo las recetas CON deps, las interesantes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
ed614e6898 |
harkaq: el runtime base DERIVADO (§4.3) + el diagnóstico sobre un configure real (§4.4)
harkaq-base-closure.py: deriva el cierre dinámico del runtime base. Es D1
aplicado a la base — mantenerla a mano sería el "alguien mantiene un perfil de
permisos" que D1 dice que mata a todos los sandboxes. No usa ldd (resolvería
contra el HOST, no contra el rootfs Alpine): lee los DT_NEEDED del ELF y
resuelve dentro del rootfs por las rutas de musl. Emite symlink Y destino: el
kernel denuncia el fichero real (libz.so.1.3.2) pero el build abre por el nombre
corto (libz.so.1).
VALIDACIÓN: el cierre derivado de {sh,busybox,bash,coreutils,env} reproduce
EXACTAMENTE las 7 librerías que las rondas 2-3 habían descubierto a mano, en una
sola pasada, y además caza los symlinks y libc.musl-x86_64.so.1 que se habían
escapado. El método es computable, no adivinado.
§4.4 — con el entorno del Sandbox real replicado (CC="zig cc -mcpu=baseline",
AR, SOURCE_DATE_EPOCH, LC_ALL=C), configure llega mucho más lejos: 205
denegaciones del kernel, y clasificadas:
esperadas (170): /usr/bin/gcc, /usr/bin/ldd ← sondas de compilador
DEUDA (34) en 8 paths:
/usr/bin/{ld,nm,objdump,strip} ← binutils de ALPINE
/usr/bin/{make,getconf}
/usr/lib/gcc/x86_64-alpine-linux-musl ← libdir del gcc de ALPINE
/opt ← bug de política
Es la tesis del §0 hecha dato: un configure que se creía hermético va a buscar
los binutils y el libdir de gcc de Alpine. Y la clasificación es lo que lo hace
legible — sin ella son 205 denegaciones planas y el hallazgo queda enterrado;
con ella son 8 paths accionables. Coincide con los swaps del selfhost-verify y
con la campaña "matar gcc" (recipes/binutils.toml existe justo porque zig provee
as/ld/ar pero el resto se toma de Alpine).
Bug de política encontrado por el propio experimento: /opt sale como deuda
porque la política concede `ro /opt/zig` pero no deja LISTAR el padre. Regla
general: todo directorio concedido necesita `list` en sus ancestros, o el escaneo
del padre es un falso positivo. harkaq-policy.sh ya lo hace para la clausura de
las deps; falta para las superficies de contrato.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
77317a7291 |
harkaq: Verdict clasificado (base|esperada|deuda) + el runtime base es por forma de build
harkaq-verdict.py: clasifica las denegaciones crudas. Va DELIBERADAMENTE fuera del lector — éste corre con CAP_AUDIT_READ y clasificar es POLÍTICA, no privilegio (mismo argumento que Q1c). De yapa: se itera sin recompilar el binario capabilitado y sin perder el setcap. Las expectativas viajan en la propia política como `# expect <path>` (una sola fuente de verdad; harkaq-exec las ignora como comentario). SinEvidencia manda sobre todo: si el lector no es confiable no se clasifica nada — reinterpretarlo sería el falso Hermetico que el canario existe para impedir. MODE=base rc=0 Hermetico esperadas: /usr/bin/gcc deuda: ninguna ✓ MODE=zlib rc=1 Impuro DEUDA: /usr/lib/libz.so.1.3.2 §4.3 — ¿aguantan los 5 paths en otra forma de build? Medido con un configure de autotools REAL (libgpg-error, MODE=configure): ronda 1: /bin/bash, /bin/coreutils ronda 2: libacl, libattr, libcrypto, libreadline, libutmps ronda 3: libncursesw, libskarnet ronda 4: /usr/bin/c89, /usr/bin/c99, /usr/bin/ldd, /usr/bin/make NO: el runtime base es POR FORMA DE BUILD (~16 entradas para autotools, no 5). Pero converge en 3 rondas (34 denegaciones → 5) y se queda chico ⇒ la métrica de <5% de la Fase 2 sigue siendo plausible. Hallazgo: **el runtime base no es una lista de binarios, es su CIERRE de .so** (bash arrastra libreadline+libncursesw; coreutils arrastra libacl+libattr). Es computable con ldd, no adivinable ⇒ harkaq-policy debe derivarlo igual que deriva la clausura de las deps. Misma idea, otro origen. Y el residuo es todo señal, nada ruido: c89/c99/ldd son sondas de compilador de Alpine (→ esperadas, como gcc) y /usr/bin/make es una decisión de diseño real — hoy los builds de hammer usan el make de Alpine sin declararlo. Es EXACTAMENTE lo que los swaps del selfhost-verify reemplazan uno a uno: la lista de deuda de harkaq y la lista de swaps del bootstrap son la MISMA lista, descubierta por dos caminos independientes. Que coincidan es la mejor validación externa del método. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
61171bfe7f |
harkaq: Q2 RESUELTA — el runtime base son 5 paths, y aparece una 3ª categoría
Método (lo importante): NO se adivina, se mide. MODE=base corre SIN ninguna dep
declarada ⇒ sin clausura que pueda explicarlas, toda denegación es runtime base
por definición. Aísla la respuesta sin juicio de valor.
MODE=base, hello.c mínimo con zig cc: 18 denegaciones, 4 paths distintos
(contador del kernel 19 = 18 + canario ✓):
8× /dev/urandom 6× /usr/lib/os-release 3× /dev/null 1× /usr/bin/env
Y el build salió rc=0: no son cosas que el build NECESITE, son cosas que INTENTA
— pero salen en todos los builds y sin tratarlas entierran la deuda real.
La partición cierra en TRES categorías, todas medidas (§4.2):
- Contrato del sandbox: /src /out /tmp /dev/null(rw) /dev/urandom /proc
/opt/zig — los monta BWRAP, no son Alpine ⇒ no son deuda. Ésta era la línea
divisoria que faltaba.
- Runtime base Alpine: /bin/sh /bin/busybox /lib/ld-musl /usr/bin/env
/usr/lib/os-release. CINCO. ⇒ el resto de Alpine es deuda genuina, y eso
hace viable todo el planteo.
- Denegación ESPERADA (no estaba en el diseño): /usr/bin/gcc. zig cc lo sondea
3× y el build sale rc=0 igual ⇒ NO es ruido a permitir, es la jaula haciendo
su trabajo: concederla dejaría a zig invocar el gcc de Alpine por detrás,
justo lo que la campaña "matar gcc" persigue a mano. Es D3 literal ("una
denegación esperada no se calla, se CLASIFICA") y confirma que no tener
`quiet` era correcto: silenciarla habría borrado el hallazgo.
El contraste con el mismo runtime base:
MODE=base rc=0 Impuro [ /usr/bin/gcc ] ← esperada
MODE=zlib rc=1 Impuro [ /usr/lib/libz.so.1.3.2 ] ← DEUDA PURA, sin ruido
Consecuencia de diseño para el Verdict: las denegaciones tienen que venir
CLASIFICADAS (base|esperada|deuda), no en lista plana. `Hermetico` debe
significar "cero deuda", no "cero denegaciones" — si no, ningún build real lo
alcanzaría jamás.
Gotchas medidos: /dev/null es destino de ESCRITURA (rw, no ro) y el piso duro es
/bin/sh→busybox→ld-musl (sin eso ni el canario corre ⇒ sólo SinEvidencia).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
24c6ab455e |
harkaq: §4 corregido (política POR FICHERO) + Q2 con método y primeros datos reales
CORRECCIÓN ESTRUCTURAL del §4 (otra herencia del marco nix). El Draft 2 decía
`fs_ro: [store paths de la clausura]`. Falso: esos paths NO existen dentro del
sandbox. `Sandbox::bwrap_args` apila las deps con --overlay-src y las FUNDE en
el mismo /usr que el rootfs Alpine, a propósito, para que el compilador las
encuentre sin plumbing de flags. Medido:
/usr/include/zlib.h (dep DECLARADA) dev=63 ino=13641973
/usr/include/stdio.h (Alpine, NO declarado) dev=63 ino=4196788
/usr/include (el directorio) dev=61 ino=15 ← overlayfs, uno
Una regla sobre /usr concede las dos ⇒ la evidencia no valdría nada. Ni `dev`
distingue. ⇒ La clausura se enumera FICHERO A FICHERO (computable: en el host
sabemos qué aporta cada dep). Dos consecuencias: `list` ≠ `ro` (pkgconf NECESITA
escanear /usr/lib/pkgconfig, pero listar no es leer; Landlock separa READ_DIR de
READ_FILE) y máscara por tipo (derechos de-sólo-dir sobre un fichero ⇒ EINVAL y
la regla entera se cae).
Piezas nuevas: harkaq-policy.sh (deriva la clausura, D1) + q2-runtime-base.sh
(responde Q2 midiendo, no adivinando) + harkaq-exec con `list`/máscara por tipo.
Q2, primeros datos con un build REAL en el sandbox REAL contra la dep zlib:
estado: Impuro | canario: True | contador kernel: 3
fs.read_file /usr/bin/env
fs.read_file /usr/lib/libz.so.1.3.2 ← la zlib de ALPINE!
El build se linkaba contra la zlib de Alpine en vez de la dep declarada (que
aporta libz.a). Sin harkaq eso pasa en verde y el artefacto queda dependiendo de
Alpine. ES EXACTAMENTE EL BUG DEL §0, cazado en un build real.
Piso duro del runtime base: /bin/sh → /bin/busybox → /lib/ld-musl (sin eso ni el
canario corre ⇒ el mínimo es "lo que hace falta para que el canario pueda correr").
Dos rastrillos: el canario no puede depender de un binario (`cat` moría en
/bin/cat ⇒ ahora `read _ < $CANARY`, sólo builtins) ni vivir en /tmp (la política
concede `rw /tmp` ⇒ el canario caía DENTRO de la clausura y no denegaba nada).
El segundo lo cazó D9 MISMO: el lector se negó a certificar en vez de decir
Hermetico. El canario funcionando como se diseñó, sobre un bug de quien lo diseñó.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
4eecba06f4 |
harkaq: Fase 1 — la cadena corre de punta a punta (Hermetico / Impuro sobre kernel real)
Tres piezas nuevas, las del §4 del SDD:
- harkaq-audit.c lector de evidencia; HOST junto a hammerd, CAP_AUDIT_READ;
espera el canario (que revela el domain=), junta las
denegaciones de ESE dominio, cruza contra el contador del
kernel al liberarse, y emite el Verdict JSON de 3 estados.
- harkaq-exec.c aplica la política y execea el builder; DENTRO de bwrap,
estático (rootfs musl). Negocia el ABI por syscall (D7:
<min rechaza, <7 avisa SIN EVIDENCIA) y pone
LOG_NEW_EXEC_ON + TSYNC.
- harkaq-run.sh encadena lector+bwrap+exec, pone el canario con nonce y
espera a que el lector confirme que escucha (--ready-fd)
antes de arrancar el build: sin esa barrera el canario se
emitiría sin nadie escuchando y daría SinEvidencia por una
carrera, no por un problema real.
El hito, con comandos sintéticos sobre el kernel real (ABI 10):
limpio {"estado":"Hermetico","canario_visto":true,"contador_kernel":1,"denials":[]}
impuro {"estado":"Impuro","canario_visto":true,"contador_kernel":2,
"denials":[{"blockers":"fs.read_file","path":"…/README.md",…}]}
Canario visto y contador coherente en ambos ⇒ los 3 estados de D9 funcionan.
Q1c contestada en la práctica: el privilegio vive en un helper chico con
`setcap cap_audit_read,cap_audit_control`, NO en hammerd entero.
Sutileza medida: lo que DAC ya bloquea es INVISIBLE para harkaq (el hook del
LSM no llega a correr). Descubierto porque el escenario "impuro" con
/root/.bashrc (drwx------) dio Hermetico: DAC lo bloqueó antes que Landlock.
No rompe D2 (si DAC lo bloqueó, el build tampoco lo usó ⇒ Hermetico es cierto)
pero acota el diagnóstico, y al escribir tests hay que elegir paths que DAC
permita o el control no controla nada.
Falta para cerrar Fase 1: harkaq-policy derivando de la clausura real + el
contraste receta-de-Alpinizada vs. receta-que-no. El rebuild del kernel
(SECURITY_LANDLOCK) NO bloquea el hito: sólo hace falta para que harkaq corra
DENTRO de hammer; el laptop ya está en ABI 10.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
6419f2a952 |
harkaq: Q1b CERRADA — la evidencia cruza el userns y el canario es la clave primaria
Banco: scripts/harkaq/q1b-attrib.c. Dos builds CONCURRENTES en bwrap --unshare-all (los ns de Sandbox::bwrap_args), sin privilegio (uid 1000), canarios con nonce distinto, lector en el host como root. (a) ✅ Los registros CRUZAN el userns/pidns/mountns (6 registros llegaron al lector del host) y cruzan con builds SIN privilegio, que es como corren de verdad. ⇒ harkaq-audit vive en el host junto a hammerd; la arquitectura del §4 se sostiene. (b) ✅ El canario con nonce ATRIBUYE: AAAA=1 BBBB=1, cero cruces con 2 builds en paralelo. ⇒ la granja puede construir en paralelo sin contaminarse la evidencia entre builds. Tres hallazgos colaterales que valen más que el veredicto: - El contador quedó VALIDADO: dos `deallocated denials=1`, uno por dominio, cuadrando con 1 ACCESS cada uno ⇒ el chequeo anti-pérdida (nº ACCESS == denials) está medido, no supuesto. - La identidad sobrevive a los ns: pid=8498 uid=1000 son del HOST (adentro del pidns la víctima es pid 1). - dev+ino identifican el OBJETO, no el BUILD: los 2 canarios dan el mismo ino=4457713 (ambos bindean /etc/hostname). Atribuir por dev/ino habría fundido dos builds leyendo el mismo fichero no declarado — el caso más común que harkaq va a ver. La clave es domain=, y el canario la revela. ⇒ D9 hace TRES trabajos con un mecanismo: honestidad (§3.4), clave primaria (§3.5) y, vía el contador, detección de pérdida. Nota de método: esta corrida también falló 2 veces más, y las 2 el banco afirmó algo falso con total confianza. (1) `cat $canario >/dev/null` — /dev no estaba en la clausura ⇒ sh fallaba en el REDIRECT y cat no corría; el rc=1 no era del canario. (2) Como root, bwrap no pudo ni ejecutar el binario y el veredicto imprimió "NADA cruzó ⇒ replantear la arquitectura" a partir de cero estímulo (causa: --unshare-all mapea sólo uid 0→0; CAP_DAC_OVERRIDE en userns sólo vale sobre uids MAPEADOS ⇒ root-en-userns no atraviesa un home drwx------). Fix: drop_privs() + gate de estímulo (exit 4 si una víctima no sale con status 0). Van 3 veces en una sesión que un cero fue el instrumento y no el kernel, con quien escribía el banco mirando de frente ese riesgo. harkaq sin canario no es un riesgo de configuración: es el comportamiento por defecto. Quedan Q1c (privilegio de harkaq-audit: mejor un helper acotado que hammerd entero) y Q1d (qué ruta reporta exe= con --bind /src). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ad0948cfb0 |
harkaq: Q1 CERRADA — la evidencia llega; + D9 (canario deliberado) y el banco de pruebas
Q1 era el gate del proyecto (¿llegan los registros AUDIT_LANDLOCK_* a un lector?). Medido en el laptop (ABI 10) con scripts/harkaq/q1-audit.c: ✅ VIABLE. El multicast AUDIT_NLGRP_READLOG entrega blockers=fs.read_file path="/etc/passwd" dev="nvme1n1p6" ino=4458369 + exe=/comm= del que lo intentó. Es el diagnóstico de de-Alpinización que el SDD promete, saliendo del kernel sin instrumentar nada. Dos precondiciones que el Draft 2 no tenía, ambas medidas: - audit_enabled=1 NO viene de fábrica (sin audit=1 en cmdline, sin auditd) ⇒ cero registros de cualquier escenario. AUDIT_SET o audit=1 horneado. - LOG_NEW_EXEC_ON es OBLIGATORIO: same-exec=2 registros, new-exec=0, new-exec-logon=4. harkaq restringe y DESPUÉS execea el builder ⇒ sin el flag certificaría herméticos TODOS los builds, denials=[], sin un error. D9 (nueva, no negociable): el canario lo fabrica harkaq. Se investigó si el registro `deallocated denials=N` servía de verificación cruzada gratis: NO. Medido con ventana de 6s, un dominio con flags default y denegación post-exec no emite NADA (ni allocated, ni access, ni el contador); y el `allocated` es perezoso (llega pegado al primer denial logueado) ⇒ su ausencia tampoco prueba nada. Un build hermético y un lector ciego son bit-idénticos: denials=[]. El Verdict pasa a 3 estados (Hermetico / Impuro / SinEvidencia). El contador SÍ sirve para lo otro: nº ACCESS == denials caza registros perdidos por backlog (riesgo real con la granja en paralelo). Nota de método (§3.4): la 1ª corrida dio 0 en los 3 escenarios y "confirmaba" la hipótesis. Era el instrumento roto (AUDIT_GET con NLM_F_ACK: el ACK llegaba antes que la respuesta → enabled=-1 → nunca se encendía el audit). Se cazó sólo porque el CONTROL (same-exec) también dio 0, y eso era imposible. Es el modo de falla de harkaq en vivo sobre su propio banco. El test ahora aborta (exit 4) si no confirma audit_enabled=1. Nuevas Q1b (¿el lector ve a través del userns de bwrap? ¿cómo se atribuye un registro a SU build con la granja en paralelo?) y Q1c (privilegio de harkaq-audit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |