From 9591e69b70418b5cf7c96418fffe1d65d2d6986a Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 1 Sep 2026 16:59:22 +0000 Subject: [PATCH] harkaq: el salto del check de arquitectura ya no se apoya en UB (SDD 25 T17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `f[k++] = BPF_JUMP(..., I_KILL - k - 1 + 1)` leía y modificaba `k` en la misma expresión, sin punto de secuencia: UB en C11. Andaba porque gcc y clang leen `k` después del incremento y ese `+1` era la compensación exacta, pero si algún compilador leyera antes el salto caería una instrucción más allá del final. El índice se fija ahora en `I_ARCH` antes de usarlo. El código generado es IDÉNTICO —mismo objdump del objeto a -O1 y a -O2, la única línea que difiere es el nombre del fichero—, que es lo que prueba que es la misma cuenta escrita sin UB y no un arreglo que además mueve el salto. Comprobado además que la jaula sigue puesta: ptrace desde dentro da EPERM y un ejecutable fuera de la clausura no arranca. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih --- docs/25-tasas-del-kernel.md | 25 ++++++++++++++++--------- scripts/harkaq/harkaq-exec.c | 10 ++++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/25-tasas-del-kernel.md b/docs/25-tasas-del-kernel.md index 94091ead..da6a43ac 100644 --- a/docs/25-tasas-del-kernel.md +++ b/docs/25-tasas-del-kernel.md @@ -325,12 +325,17 @@ por `fork`/`exec`, así que lo paga el primer proceso de la fase y lo cobran los mide N+3, así que la denylist tiene un **techo de ~252 entradas**: pasado eso el desplazamiento da la vuelta y el filtro deja de decir lo que dice su código fuente. Hoy son 25 y no hay problema; el problema es que crecer la lista es justo lo que uno hace sin pensarlo. -2. **`f[k++] = BPF_JUMP(..., I_KILL - k - 1 + 1)` modifica y lee `k` en la misma expresión** — sin - punto de secuencia, o sea UB en C11. Funciona porque gcc y clang leen `k` *después* del - incremento, y el `+1` final es exactamente la compensación de eso (comprobado con los dos, `-O0` - y `-O2`, en esta máquina). Si algún compilador leyera antes, el salto caería una instrucción - más allá del final: el verificador clásico lo rechazaría y harkaq saldría por su rama de - `RECHAZO`. Falla ruidoso, no silencioso — pero está apoyado en UB. +2. **`f[k++] = BPF_JUMP(..., I_KILL - k - 1 + 1)` modificaba y leía `k` en la misma expresión** — + sin punto de secuencia, o sea UB en C11. Funcionaba porque gcc y clang leen `k` *después* del + incremento, y el `+1` final era exactamente la compensación de eso. Si algún compilador leyera + antes, el salto caería una instrucción más allá del final: el verificador clásico lo rechazaría + y harkaq saldría por su rama de `RECHAZO` — falla ruidoso, no silencioso, pero apoyado en UB. + + **CERRADO el 2026-09-01**: el índice se fija en `I_ARCH` antes de usarlo. **El código generado es + idéntico** —mismo `objdump` del objeto a `-O1` y a `-O2`, la única línea que difiere es el nombre + del fichero— y el binario parcheado sigue poniendo la jaula: `ptrace` desde dentro da `EPERM` y + un ejecutable fuera de la clausura no arranca. Que el desensamblado no cambie es lo que prueba + que era exactamente la misma cuenta escrita sin UB, y no un arreglo que además mueve el salto. ### T18 · Landlock: el precio es ESTAR enjaulado, no cuántas reglas hay @@ -828,9 +833,11 @@ Su precio, concreto: algún día hace falta (filtrar `ioctl` por petición, `clone` por flags), el precio es la cadena entera en cada llamada de esa syscall — 0,158 ns por instrucción, medido. Se decide con ese número delante, no con la intuición de que «un filtro es un filtro». - 3. **La denylist tiene un techo de ~252 entradas** por el `__u8` de los saltos de la BPF clásica, - y `poner_seccomp()` no lo comprueba. Hoy son 25. Un `_Static_assert` cuesta nada y evita que - crecer la lista rompa el filtro en silencio — la misma forma de fallo que CLAUDE.md §3. + 3. **La denylist tiene un techo de ~252 entradas** por el `__u8` de los saltos de la BPF clásica. + Hoy son 25. **HECHO**: un `_Static_assert` en `harkaq-exec.c` lo cierra, para que crecer la + lista no rompa el filtro en silencio — la misma forma de fallo que CLAUDE.md §3. Y **el UB del + punto (2) de T17 también está cerrado**, con el desensamblado como prueba de que no cambia + nada más. Y la política **fichero a fichero** de D1 queda confirmada por el lado del coste: cambiarla por reglas de directorio ahorraría 225 ns por `open` de los 698, o sea el 32% de lo barato, a cambio diff --git a/scripts/harkaq/harkaq-exec.c b/scripts/harkaq/harkaq-exec.c index 886daa86..0dc33cc7 100644 --- a/scripts/harkaq/harkaq-exec.c +++ b/scripts/harkaq/harkaq-exec.c @@ -156,8 +156,14 @@ static int poner_seccomp(void) { // el error clásico de los filtros seccomp escritos a mano. f[k++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)); - f[k++] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, - I_KILL - k - 1 + 1); + // El desplazamiento se cuenta desde ESTA instrucción, así que hace falta su índice. Fijarlo en + // una variable y no escribir `f[k++] = ... (I_KILL - k - 1 + 1)`: eso lee y modifica `k` en la + // misma expresión, sin punto de secuencia — UB en C11. Andaba (gcc y clang leen `k` después del + // incremento, y ese `+1` era justo la compensación), pero andaba apoyado en UB. Comprobado que + // el código generado no cambia, a `-O1` y `-O2`. Ver SDD 25 T17. + const int I_ARCH = k++; + f[I_ARCH] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, + I_KILL - I_ARCH - 1); f[k++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)); for (int i = 0; i < n; i++, k++)