qorpa D9: harkaq enjaula la instancia, y midiendo salieron dos conflictos

Paso 4 del ADR 0015. harkaq-exec entra como último eslabón dentro de bwrap,
igual que en el sandbox del build. Cruza el borde un binario ESTÁTICO, no una
librería, así que D2 sigue en pie: lo único compartido es la ABI del kernel.

Honestidad primero, y está escrita en el código: en el eje del sistema de
ficheros harkaq casi no agrega nada, porque el namespace de montaje de bwrap ya
es una lista blanca. Escribir reglas `ro` que repiten eso sería un sello de
goma, así que la política de una instancia no sellada es UNA línea (`rw /`) y no
finge. Lo que sí aporta: seccomp (bwrap no instala filtro alguno — hoy una
instancia podía io_uring, bpf, ptrace, userfaultfd, keyctl, perf_event_open),
no_new_privs, el canal de evidencia, y `seal_image`, que congela /usr /bin /lib
/opt aunque adentro seas root.

Verificado contra el kernel, no contra el log: Landlock ABI 9, logging
post-exec ON, NoNewPrivs 1, Seccomp 2. Y sellando, `/usr/bin` y `/bin` denegados
mientras /etc y /var siguen escribibles.

DOS CONFLICTOS que sólo se ven midiendo, y ninguno estaba en el ADR:

1. Landlock y los contenedores anidados son INCOMPATIBLES hoy: con un dominio
   activo, `mount` falla con EACCES aunque seccomp lo permita — el kernel no
   admite montajes nuevos bajo un dominio porque escaparían de sus reglas
   por-ruta. ⇒ pressure-vessel no arranca bajo Landlock. Por eso `nesting` pasa
   `--allow-nesting --no-landlock` y lo dice a gritos; seccomp y no_new_privs
   siguen puestos, que es lo que más pesa con un binario ajeno.
2. `root` adentro y anidar se pelean: con --uid 0, un userns anidado no puede
   escribir su uid_map. Sin remapear anida, pero el gestor de paquetes se queja.
   La instancia de juegos y la de paquetes quieren mapeos OPUESTOS, y ahora el
   manifiesto lo declara (`root`, encendido por defecto).

Las dos mitades se curan con lo mismo que el impuesto de apt: un rango real de
subuid con newuidmap + --userns FD. Ése es el ticket que más desbloquea.

En harkaq-exec, dos flags ADITIVOS y apagados por defecto (--allow-nesting,
--no-landlock): el camino del build no cambia ni un byte, que es requisito duro
con 700+ artefactos sellados. La lista de syscalls del anidamiento se separó de
la base y el _Static_assert del techo de salto BPF ahora suma las dos.

3 tests nuevos: que la política sin sellar no finja, que sellando el `rw /` no
sobreviva (uniría derechos por ancestro y anularía el sellado), y que `root` sea
lo único que nace encendido. 43/43.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2
This commit is contained in:
Sergio
2026-09-03 05:02:56 +00:00
co-authored by Claude Opus 5
parent ba09fdb403
commit 5b82474079
3 changed files with 319 additions and 22 deletions
+40 -1
View File
@@ -285,6 +285,42 @@ máquinas, que es lo que ya hace ADR 0013 con las fuentes) hay que mirar licenci
redistribuir un rootfs de Ubuntu o Fedora sin modificar suele estar permitido, pero la política de
marca es cosa aparte y no es una pregunta técnica.
### D9 — Qué aporta harkaq a una instancia, y los dos conflictos que sólo se ven midiendo
**Honestidad primero: en el eje del sistema de ficheros, harkaq casi no agrega nada.** El namespace
de montaje de bwrap ya es una lista blanca —lo que no se bindea no existe adentro, y un `--ro-bind`
es de sólo lectura por el kernel— así que escribir reglas Landlock que repiten eso sería un sello de
goma. Por eso la política de una instancia **no sellada** es una sola línea (`rw /`) y no finge.
Lo que harkaq sí aporta, y pesa mucho más con un binario ajeno y opaco que con nuestros propios
builds:
| aporte | por qué importa acá |
|---|---|
| **seccomp** | bwrap **no instala filtro alguno**: hoy una instancia puede `io_uring`, `bpf`, `ptrace`, `process_vm_readv`, `userfaultfd`, `keyctl`, `perf_event_open`, `init_module`, `kexec`. Ésa es superficie de kernel contra un prebuilt de terceros |
| **`no_new_privs`** | ningún setuid de la imagen ajena escala adentro |
| **canal de evidencia** | con Landlock ABI ≥ 7 el kernel audita las denegaciones ⇒ se puede saber **qué intentó tocar** un binario cerrado. Es la única forma de auditar el montón B (leer ese canal es trabajo aparte: pide el lector con `CAP_AUDIT_READ`) |
| **`seal_image`** | lo único del eje fs que el namespace no da: congelar `/usr`, `/bin`, `/lib`, `/opt` **aunque adentro seas root**, para que un exploit no persista en el `upper` |
**Conflicto 1 — Landlock y los contenedores anidados son incompatibles hoy.** Medido: con un dominio
Landlock activo, `mount` falla con **EACCES aunque seccomp lo permita**; el kernel no admite montajes
nuevos bajo un dominio, porque escaparían de sus reglas por-ruta. Es decir: **pressure-vessel no
arranca bajo Landlock** (D6). Por eso `nesting = true` cuesta la política de ficheros —`harkaq-exec
--allow-nesting --no-landlock`—, y **seccomp y `no_new_privs` siguen puestos**, que es lo que más
pesa. Nunca por defecto y siempre dicho en la línea de arranque: harkaq no afirma una jaula que no
puso.
**Conflicto 2 — `root` adentro y anidar se pelean.** Medido: con el uid remapeado a 0 (`--uid 0`), un
userns anidado **no puede escribir su `uid_map`**. Sin remapear, anida bien pero el gestor de
paquetes se queja. ⇒ la instancia de juegos y la de gestor de paquetes quieren mapeos **opuestos**,
y el manifiesto lo declara (`root`, encendido por defecto). Las dos mitades se curan con lo mismo que
el impuesto de §NO-resuelve 2: **un rango real de subuid** mapeado con `newuidmap` y pasado a bwrap
con `--userns FD`. Ése es hoy el ticket que más desbloquea del ADR entero.
**Y un detalle que ordena las expectativas:** bwrap **tira todas las capabilities**, así que dentro
de una instancia no hay `CAP_SYS_ADMIN` — el anidamiento sólo puede ser por userns sin privilegios,
que es exactamente como lo hace pressure-vessel.
---
## Lo que este ADR admite que NO resuelve
@@ -374,7 +410,10 @@ Se escriben acá para que no se descubran en producción.
`recreate` / `run`. El overlay lo monta bwrap (`--overlay-src` + `--overlay`) dentro de su
propio namespace: sin root y sin montar nada en el host. `run` entra con `--clearenv` y con
`--unshare-net` salvo que se declare `network` — el entorno del host también es una concesión.
4. Concesiones → `PolicySpec` de harkaq. Empezar por **nada** y abrir sólo lo declarado.
4. **HECHO 2026-09-03.** Concesiones → política de harkaq. `harkaq-exec` entra como último
eslabón dentro de bwrap (cruza un binario **estático**, no una librería ⇒ D2 sigue en pie), y
aporta lo que bwrap no da: **seccomp** —hoy una instancia podría `io_uring`, `bpf`, `ptrace`,
`userfaultfd`, `keyctl`, `perf_event_open`—, `no_new_privs`, y el canal de evidencia. Ver D9.
5. Shims + `.desktop` generados (`export`), y clase `ajeno` en `build-state.py`.
6. **Steam de punta a punta**, con verificación explícita del bwrap anidado.
7. Poda (`hammer qorpa prune`) y renglón en SDD 20 sobre licencias.