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…
⎇ 84e3737e10e17927380ed3e232e50cebfdd39123
docs/evidencia/tasas-kernel-2026-08-29/bench_jaula.c
Ñawpa willay
Pi imata llamkarqan
ch'uya
tasas: T17/T18 — la jaula cobra por syscall, y el filtro largo no es lo que cobra
1/* bench_jaula.c — lo que cobra la JAULA por syscall, no por proceso (SDD 25 T17/T18).
2 *
3 * SDD 25 midió el sandbox por NAMESPACES (T14: ~1 ms por proceso). Lo que no midió es el grano
4 * fino que harkaq (SDD 16) pone dentro: un filtro seccomp y un ruleset Landlock. Esos dos no
5 * cobran por proceso — cobran por SYSCALL, que es la unidad en la que un build hace millones.
6 *
7 * Dos preguntas, cada una con su control negativo:
8 *
9 * A) seccomp. El filtro de harkaq (`scripts/harkaq/harkaq-exec.c`) es una DENYLIST: una cadena
10 * lineal de `BPF_JEQ` sobre `seccomp_data.nr` y, al final, `RET ALLOW`. O sea que toda syscall
11 * LEGÍTIMA recorre la cadena ENTERA antes de que la dejen pasar: la peor posición posible,
12 * para el caso más frecuente. La pregunta es si eso se paga.
13 *
14 * La hipótesis es que NO, y no por casualidad: desde 5.11 el kernel construye un BITMAP de
15 * acción constante (`seccomp_cache_prepare`). Si para un número de syscall el filtro devuelve
16 * ALLOW sin mirar los argumentos —cosa que `seccomp_is_const_allow()` decide leyendo el
17 * programa BPF entero, instrucción por instrucción— ese número queda marcado y el programa
18 * NO SE EJECUTA nunca más. El filtro de harkaq usa sólo `BPF_LD|W|ABS` sobre `nr`/`arch`,
19 * `BPF_JMP|BPF_JEQ|BPF_K` y `BPF_RET|BPF_K`: es exactamente lo que el analizador sabe seguir.
20 *
21 * El control que lo convierte en medición y no en fe: se instala un GEMELO de la misma
22 * longitud con UNA instrucción de más al principio —una carga de `args[0]`— que hace que
23 * `seccomp_is_const_allow()` se rinda. Mismo largo, misma cadena, sin bitmap. Si el primero
24 * sale plano en N y el segundo crece con N, el bitmap existe y es lo que paga la cuenta.
25 * Si los DOS salen planos, la medición no discrimina y no dice nada.
26 *
27 * B) Landlock. La política de harkaq se deriva FICHERO A FICHERO (`harkaq-policy.sh`, D1),
28 * porque adentro la dep declarada y el rootfs Alpine comparten `/usr`. Una dep como boost son
29 * 15 829 ficheros ⇒ ~17 000 reglas. La pregunta es si un ruleset de ese tamaño encarece cada
30 * `open`, y cuánto cuesta armarlo.
31 *
32 * Control negativo obligatorio en las dos: si la jaula NO quedó puesta (una syscall denegada que
33 * NO da EPERM, un fichero fuera del ruleset que SÍ se abre), el programa sale con 2. Medir una
34 * jaula apagada da números preciosos y falsos — CLAUDE.md §3.
35 *
36 * cc -O2 -o bench_jaula bench_jaula.c && ./bench_jaula [rondas] [dir-temporal]
37 *
38 * No pide root. Cada configuración corre en un hijo propio: ni seccomp ni Landlock se pueden
39 * quitar una vez puestos, así que el proceso que mide es siempre de usar y tirar.
40 */
41#define _GNU_SOURCE
42#include <dirent.h>
43#include <errno.h>
44#include <fcntl.h>
45#include <linux/audit.h>
46#include <linux/filter.h>
47#include <linux/landlock.h>
48#include <linux/seccomp.h>
49#include <stddef.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <sys/mman.h>
54#include <sys/prctl.h>
55#include <sys/stat.h>
56#include <sys/syscall.h>
57#include <sys/utsname.h>
58#include <sys/wait.h>
59#include <time.h>
60#include <unistd.h>
61
62#define ROUNDS_MAX 15
63static int ROUNDS = 9;
64
65static inline double now_ns(void) {
66 struct timespec t;
67 clock_gettime(CLOCK_MONOTONIC, &t); /* vDSO: no cruza al kernel, no se mide a sí mismo */
68 return t.tv_sec * 1e9 + t.tv_nsec;
69}
70static int cmpd(const void *a, const void *b) {
71 double x = *(const double *)a, y = *(const double *)b;
72 return (x > y) - (x < y);
73}
74/* Resultados de los hijos: memoria compartida, porque cada medición vive en su propio proceso. */
75static double *shared;
76static void rep(const char *n, double *r, int c) {
77 qsort(r, c, sizeof(double), cmpd);
78 printf("%-52s %9.1f %9.1f\n", n, r[0], r[c / 2]);
79 fflush(stdout);
80}
81
82/* ------------------------------------------------------------------ A) seccomp */
83
84/* Números de syscall que NO existen: la cadena se recorre entera sin que ninguno case, que es
85 * exactamente lo que le pasa a toda syscall legítima bajo la denylist de harkaq. */
86#define NR_FALSO_BASE 900
87
88/* Arma el filtro con la MISMA forma que `harkaq-exec.c::poner_seccomp`.
89 * `mirar_args` antepone una carga de `seccomp_data.args[0]`, que es lo único que cambia — y lo
90 * único que hace falta para que el analizador de acción constante se rinda. */
91static struct sock_filter *armar(int n, int mirar_args, unsigned short *out_len) {
92 int extra = mirar_args ? 2 : 0;
93 struct sock_filter *f = calloc(n + 6 + extra, sizeof(*f));
94 if (!f) return NULL;
95 int k = 0;
96 const int I_ERRNO = 4 + n + extra, I_KILL = 5 + n + extra;
97
98 if (mirar_args) {
99 /* offsetof(seccomp_data, args) no es `nr` ni `arch` ⇒ seccomp_is_const_allow() devuelve
100 * false y el bitmap queda vacío. El salto nunca se toma: 0xdeadbeef no es un argumento. */
101 f[k++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
102 offsetof(struct seccomp_data, args));
103 f[k] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0xdeadbeefu,
104 I_KILL - k - 1, 0);
105 k++;
106 }
107 f[k++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
108 offsetof(struct seccomp_data, arch));
109 f[k] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0,
110 I_KILL - k - 1);
111 k++;
112 f[k++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
113 offsetof(struct seccomp_data, nr));
114 for (int i = 0; i < n; i++, k++)
115 f[k] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,
116 (unsigned)(NR_FALSO_BASE + i), I_ERRNO - k - 1, 0);
117 f[k++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
118 f[k++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
119 SECCOMP_RET_ERRNO | (EPERM & SECCOMP_RET_DATA));
120 f[k++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS);
121 *out_len = (unsigned short)k;
122 return f;
123}
124
125/* El límite que nadie ve: `jt`/`jf` de la BPF clásica son __u8. Con la cadena de harkaq, el salto
126 * más largo mide N+3, así que a partir de ~252 entradas el desplazamiento DA LA VUELTA y el
127 * filtro deja de decir lo que dice su código fuente — sin error, sin aviso. */
128#define N_MAX_SEGURO 250
129
130static long instalar(int n, int mirar_args) {
131 unsigned short len = 0;
132 struct sock_filter *f = armar(n, mirar_args, &len);
133 if (!f) return -1;
134 struct sock_fprog prog = {.len = len, .filter = f};
135 long r = syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog);
136 free(f);
137 return r;
138}
139
140/* La syscall que se cronometra: `getppid` por número, para no depender de si la libc la cachea. */
141static double t_syscall(long iters) {
142 double t0 = now_ns();
143 for (long i = 0; i < iters; i++) syscall(SYS_getppid);
144 return (now_ns() - t0) / iters;
145}
146
147/* Corre una configuración de seccomp en un hijo. Devuelve 0 si el hijo terminó bien. */
148static int hijo_seccomp(int n, int mirar_args, long iters, int verificar) {
149 pid_t p = fork();
150 if (p < 0) return -1;
151 if (p == 0) {
152 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) _exit(3);
153 if (n >= 0 && instalar(n, mirar_args) < 0) _exit(4);
154 /* Control negativo: con filtro puesto, un número de la denylist TIENE que dar EPERM.
155 * Sin esto, un filtro que el kernel aceptó pero no aplica se mediría como "gratis". */
156 if (verificar && n > 0) {
157 errno = 0;
158 long r = syscall(NR_FALSO_BASE, 0, 0, 0, 0, 0, 0);
159 if (!(r == -1 && errno == EPERM)) _exit(5);
160 }
161 for (int q = 0; q < ROUNDS; q++) shared[q] = t_syscall(iters);
162 _exit(0);
163 }
164 int st = 0;
165 waitpid(p, &st, 0);
166 return (WIFEXITED(st) && WEXITSTATUS(st) == 0) ? 0 : (WIFEXITED(st) ? WEXITSTATUS(st) : -1);
167}
168
169/* Coste de INSTALAR el filtro: el kernel recorre el programa entero una vez POR NÚMERO DE SYSCALL
170 * para armar el bitmap, así que esto crece con N aunque la ejecución no. Es precio por proceso
171 * (por fase de build), no por syscall.
172 *
173 * Una instalación por hijo, y no un lazo de instalaciones apiladas, por dos razones que salieron
174 * midiendo: (1) el kernel limita el total de instrucciones de la cadena (MAX_INSNS_PER_PATH), y
175 * apilar 200 filtros de 64 entradas ya no entra —da EINVAL, que se leería como "el filtro es
176 * ilegal" cuando lo ilegal es la pila—; (2) cada instalación posterior atraviesa los filtros ya
177 * puestos, así que el lazo mide una cosa distinta en cada vuelta. Un solo `seccomp()` por proceso
178 * es además lo que hace harkaq. */
179static int hijo_instalar(int n, int mirar_args) {
180 for (int q = 0; q < ROUNDS; q++) {
181 pid_t p = fork();
182 if (p < 0) return -1;
183 if (p == 0) {
184 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) _exit(3);
185 unsigned short len = 0;
186 struct sock_filter *f = armar(n, mirar_args, &len);
187 if (!f) _exit(4);
188 struct sock_fprog prog = {.len = len, .filter = f};
189 double t0 = now_ns();
190 long r = syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog);
191 double t1 = now_ns();
192 if (r < 0) _exit(4);
193 shared[q] = (t1 - t0) / 1000.0; /* µs */
194 _exit(0);
195 }
196 int st = 0;
197 waitpid(p, &st, 0);
198 if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0))
199 return WIFEXITED(st) ? WEXITSTATUS(st) : -1;
200 }
201 return 0;
202}
203
204/* ------------------------------------------------------------------ B) Landlock */
205
206static int ll_create(const struct landlock_ruleset_attr *a, size_t n, __u32 f) {
207 return syscall(SYS_landlock_create_ruleset, a, n, f);
208}
209static int ll_add(int fd, enum landlock_rule_type t, const void *a, __u32 f) {
210 return syscall(SYS_landlock_add_rule, fd, t, a, f);
211}
212static int ll_self(int fd, __u32 f) { return syscall(SYS_landlock_restrict_self, fd, f); }
213
214/* Mismo juego de accesos que harkaq pide en ABI >= 5, recortado a lo que el ABI vivo soporte. */
215static __u64 accesos_manejados(int abi) {
216 __u64 h = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE |
217 LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR |
218 LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE |
219 LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR |
220 LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK |
221 LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK |
222 LANDLOCK_ACCESS_FS_MAKE_SYM;
223 if (abi >= 2) h |= LANDLOCK_ACCESS_FS_REFER;
224 if (abi >= 3) h |= LANDLOCK_ACCESS_FS_TRUNCATE;
225 if (abi >= 5) h |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
226 return h;
227}
228
229static char base[512];
230static int n_ficheros_creados = 0;
231#define POR_DIR 256
232
233static void ruta_de(int i, char *out, size_t n) {
234 snprintf(out, n, "%s/d%03d/f%03d", base, i / POR_DIR, i % POR_DIR);
235}
236
237/* El árbol de prueba: M ficheros repartidos en directorios de 256, más un fichero TESTIGO que
238 * NUNCA entra al ruleset — es con lo que se comprueba que la jaula está puesta de verdad. */
239static int armar_arbol(int m) {
240 char p[600];
241 for (int d = 0; d <= m / POR_DIR; d++) {
242 snprintf(p, sizeof p, "%s/d%03d", base, d);
243 if (mkdir(p, 0700) < 0 && errno != EEXIST) return -1;
244 }
245 for (int i = n_ficheros_creados; i < m; i++) {
246 ruta_de(i, p, sizeof p);
247 int fd = open(p, O_CREAT | O_WRONLY | O_TRUNC, 0600);
248 if (fd < 0) return -1;
249 if (write(fd, "x", 1) != 1) { close(fd); return -1; }
250 close(fd);
251 }
252 if (m > n_ficheros_creados) n_ficheros_creados = m;
253 snprintf(p, sizeof p, "%s/TESTIGO", base);
254 int fd = open(p, O_CREAT | O_WRONLY | O_TRUNC, 0600);
255 if (fd < 0) return -1;
256 close(fd);
257 return 0;
258}
259
260/* Construye el ruleset de M reglas fichero-a-fichero (más el `list` de cada directorio), tal como
261 * lo hace harkaq. Devuelve el fd del ruleset y, por `us_setup`, lo que costó armarlo. */
262static int construir_ruleset(int m, int abi, double *us_setup) {
263 struct landlock_ruleset_attr attr = {.handled_access_fs = accesos_manejados(abi)};
264 int rs = ll_create(&attr, sizeof attr, 0);
265 if (rs < 0) return -1;
266 char p[600];
267 double t0 = now_ns();
268 for (int d = 0; d <= (m ? (m - 1) / POR_DIR : 0); d++) {
269 snprintf(p, sizeof p, "%s/d%03d", base, d);
270 int dfd = open(p, O_PATH | O_CLOEXEC);
271 if (dfd < 0) { close(rs); return -1; }
272 struct landlock_path_beneath_attr pb = {.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
273 .parent_fd = dfd};
274 int r = ll_add(rs, LANDLOCK_RULE_PATH_BENEATH, &pb, 0);
275 close(dfd);
276 if (r < 0) { close(rs); return -1; }
277 }
278 for (int i = 0; i < m; i++) {
279 ruta_de(i, p, sizeof p);
280 int fd = open(p, O_PATH | O_CLOEXEC);
281 if (fd < 0) { close(rs); return -1; }
282 struct landlock_path_beneath_attr pb = {.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE,
283 .parent_fd = fd};
284 int r = ll_add(rs, LANDLOCK_RULE_PATH_BENEATH, &pb, 0);
285 close(fd); /* la regla se queda con la referencia al inodo: el fd no hay que retenerlo */
286 if (r < 0) { close(rs); return -1; }
287 }
288 *us_setup = (now_ns() - t0) / 1000.0;
289 return rs;
290}
291
292static double t_open(const char *ruta, long iters) {
293 double t0 = now_ns();
294 for (long i = 0; i < iters; i++) {
295 int fd = open(ruta, O_RDONLY | O_CLOEXEC);
296 if (fd >= 0) close(fd);
297 }
298 return (now_ns() - t0) / iters;
299}
300static double t_stat(const char *ruta, long iters) {
301 struct stat st;
302 double t0 = now_ns();
303 for (long i = 0; i < iters; i++) stat(ruta, &st);
304 return (now_ns() - t0) / iters;
305}
306
307/* Qué se cronometra dentro del hijo. `open` sobre un fichero CON regla es el caso frecuente de un
308 * build; `open` DENEGADO se midió esperando que fuera el peor caso —recorrer el árbol hasta la
309 * raíz sin encontrar regla que case— y resultó ser MÁS BARATO que uno concedido, porque falla
310 * antes de montar el descriptor: la denegación no es un riesgo de coste; `stat` está para
311 * responder si Landlock cobra en la syscall que un compilador hace más — Landlock no engancha
312 * `getattr`, y esa línea es lo que lo demuestra en vez de afirmarlo. */
313enum medida { MED_OPEN, MED_OPEN_DENEGADO, MED_STAT };
314
315/* m < 0 ⇒ sin Landlock (el control). Si no, ruleset de m reglas y se mide lo que pida `qué`. */
316static int hijo_landlock_med(int m, int abi, long iters, double *us_setup_out, enum medida que);
317static int hijo_landlock(int m, int abi, long iters, double *us_setup_out) {
318 return hijo_landlock_med(m, abi, iters, us_setup_out, MED_OPEN);
319}
320static int hijo_landlock_med(int m, int abi, long iters, double *us_setup_out, enum medida que) {
321 int tuberia[2];
322 if (pipe(tuberia) < 0) return -1;
323 pid_t p = fork();
324 if (p < 0) return -1;
325 if (p == 0) {
326 close(tuberia[0]);
327 double us = 0;
328 if (m >= 0) {
329 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) _exit(3);
330 int rs = construir_ruleset(m, abi, &us);
331 if (rs < 0) _exit(4);
332 if (ll_self(rs, 0) < 0) _exit(5);
333 close(rs);
334 /* Control negativo: el TESTIGO no está en ninguna regla. Si se puede abrir, la jaula
335 * no está puesta y todo lo de abajo mediría un `open` sin Landlock. */
336 char t[600];
337 snprintf(t, sizeof t, "%s/TESTIGO", base);
338 int fd = open(t, O_RDONLY);
339 if (fd >= 0) { close(fd); _exit(6); }
340 if (errno != EACCES && errno != EPERM) _exit(7);
341 }
342 if (write(tuberia[1], &us, sizeof us) != (ssize_t)sizeof us) _exit(8);
343 close(tuberia[1]);
344 char r[600];
345 if (que == MED_OPEN_DENEGADO)
346 snprintf(r, sizeof r, "%s/TESTIGO", base);
347 else
348 ruta_de(m > 0 ? m / 2 : 0, r, sizeof r);
349 for (int q = 0; q < ROUNDS; q++)
350 shared[q] = (que == MED_STAT) ? t_stat(r, iters) : t_open(r, iters);
351 _exit(0);
352 }
353 close(tuberia[1]);
354 double us = 0;
355 if (read(tuberia[0], &us, sizeof us) != (ssize_t)sizeof us) us = -1;
356 close(tuberia[0]);
357 int st = 0;
358 waitpid(p, &st, 0);
359 if (us_setup_out) *us_setup_out = us;
360 return (WIFEXITED(st) && WEXITSTATUS(st) == 0) ? 0 : (WIFEXITED(st) ? WEXITSTATUS(st) : -1);
361}
362
363/* ------------------------------------------------------------------ main */
364
365static void fatal(const char *que, int code) {
366 fprintf(stderr, "\nABORTA: %s (codigo %d).\n", que, code);
367 fprintf(stderr, "Un banco que mide una jaula apagada da numeros preciosos y falsos.\n");
368 exit(2);
369}
370
371int main(int argc, char **argv) {
372 if (argc > 1) {
373 ROUNDS = atoi(argv[1]);
374 if (ROUNDS < 3 || ROUNDS > ROUNDS_MAX) ROUNDS = 9;
375 }
376 const char *tmpdir = argc > 2 ? argv[2] : "/tmp";
377 snprintf(base, sizeof base, "%s/bench_jaula.%d", tmpdir, (int)getpid());
378 if (mkdir(base, 0700) < 0) { perror("mkdir"); return 1; }
379
380 shared = mmap(NULL, sizeof(double) * ROUNDS_MAX, PROT_READ | PROT_WRITE,
381 MAP_SHARED | MAP_ANONYMOUS, -1, 0);
382 if (shared == MAP_FAILED) { perror("mmap"); return 1; }
383
384 struct utsname u;
385 uname(&u);
386 int abi = ll_create(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
387 printf("bench_jaula — lo que cobra la jaula POR SYSCALL (SDD 25 T17/T18)\n");
388 printf("kernel %s · landlock ABI %d · %d rondas · arbol en %s\n", u.release, abi, ROUNDS, base);
389 if (abi < 1) fatal("este kernel no tiene Landlock: no hay nada que medir", abi);
390
391 double r[ROUNDS_MAX];
392 long ITERS = 300000;
393
394 /* --- A.1 · el filtro en ejecucion: ¿la cadena lineal se paga? ------------------------- */
395 printf("\n--- A.1) seccomp: ns por getppid segun el LARGO del filtro ---\n");
396 printf("%-52s %9s %9s\n", "", "min", "mediana");
397 int rc = hijo_seccomp(-1, 0, ITERS, 0);
398 if (rc) fatal("el hijo sin filtro no termino bien", rc);
399 memcpy(r, shared, sizeof(double) * ROUNDS);
400 rep(" sin filtro (piso)", r, ROUNDS);
401
402 int enes[] = {4, 25, 64, 128, N_MAX_SEGURO};
403 for (unsigned k = 0; k < sizeof enes / sizeof *enes; k++) {
404 char nom[80];
405 rc = hijo_seccomp(enes[k], 0, ITERS, 1);
406 if (rc) fatal("filtro denylist: el hijo no termino bien (5=no dio EPERM)", rc);
407 memcpy(r, shared, sizeof(double) * ROUNDS);
408 snprintf(nom, sizeof nom, " denylist de %3d (forma harkaq, analizable)%s", enes[k],
409 enes[k] == 25 ? " <-- harkaq" : "");
410 rep(nom, r, ROUNDS);
411 }
412 for (unsigned k = 0; k < sizeof enes / sizeof *enes; k++) {
413 char nom[80];
414 rc = hijo_seccomp(enes[k], 1, ITERS, 1);
415 if (rc) fatal("filtro que mira args: el hijo no termino bien", rc);
416 memcpy(r, shared, sizeof(double) * ROUNDS);
417 snprintf(nom, sizeof nom, " gemelo de %3d que MIRA args[0] (sin bitmap)", enes[k]);
418 rep(nom, r, ROUNDS);
419 }
420
421 /* --- A.2 · el precio por proceso: armar el bitmap ------------------------------------- */
422 printf("\n--- A.2) seccomp: us por INSTALAR el filtro (precio por proceso) ---\n");
423 printf("%-52s %9s %9s\n", "", "min", "mediana");
424 for (unsigned k = 0; k < sizeof enes / sizeof *enes; k++) {
425 char nom[80];
426 rc = hijo_instalar(enes[k], 0);
427 if (rc) fatal("instalacion: el hijo no termino bien", rc);
428 memcpy(r, shared, sizeof(double) * ROUNDS);
429 snprintf(nom, sizeof nom, " instalar denylist de %3d (arma el bitmap)", enes[k]);
430 rep(nom, r, ROUNDS);
431 }
432 for (unsigned k = 0; k < sizeof enes / sizeof *enes; k++) {
433 char nom[80];
434 rc = hijo_instalar(enes[k], 1);
435 if (rc) fatal("instalacion del gemelo: el hijo no termino bien", rc);
436 memcpy(r, shared, sizeof(double) * ROUNDS);
437 snprintf(nom, sizeof nom, " instalar gemelo de %3d (sin bitmap que armar)", enes[k]);
438 rep(nom, r, ROUNDS);
439 }
440
441 /* --- B · Landlock ---------------------------------------------------------------------- */
442 printf("\n--- B) landlock: ns por open+close segun el TAMANO del ruleset ---\n");
443 printf("%-52s %9s %9s\n", "", "min", "mediana");
444 long IT_OPEN = 40000;
445 int emes[] = {1, 16, 256, 4096, 16384};
446 if (armar_arbol(emes[sizeof emes / sizeof *emes - 1]) < 0) { perror("armar_arbol"); return 1; }
447
448 rc = hijo_landlock(-1, abi, IT_OPEN, NULL);
449 if (rc) fatal("el hijo sin landlock no termino bien", rc);
450 memcpy(r, shared, sizeof(double) * ROUNDS);
451 rep(" sin landlock (piso)", r, ROUNDS);
452
453 double setup[8];
454 for (unsigned k = 0; k < sizeof emes / sizeof *emes; k++) {
455 char nom[80];
456 rc = hijo_landlock(emes[k], abi, IT_OPEN, &setup[k]);
457 if (rc) fatal("landlock: el hijo no termino bien (6=el TESTIGO se abrio igual)", rc);
458 memcpy(r, shared, sizeof(double) * ROUNDS);
459 snprintf(nom, sizeof nom, " ruleset de %5d reglas fichero-a-fichero%s", emes[k],
460 emes[k] == 16384 ? " <-- ~boost" : "");
461 rep(nom, r, ROUNDS);
462 }
463 /* --- B.1.bis · qué syscalls toca Landlock y cuál es su peor caso ----------------------- */
464 printf("\n--- B.1.bis) landlock con %d reglas: otras dos syscalls ---\n", emes[4]);
465 printf("%-52s %9s %9s\n", "", "min", "mediana");
466 rc = hijo_landlock_med(-1, abi, IT_OPEN, NULL, MED_STAT);
467 if (rc) fatal("stat sin landlock: el hijo no termino bien", rc);
468 memcpy(r, shared, sizeof(double) * ROUNDS);
469 rep(" stat sin landlock (piso)", r, ROUNDS);
470 rc = hijo_landlock_med(emes[4], abi, IT_OPEN, NULL, MED_STAT);
471 if (rc) fatal("stat con landlock: el hijo no termino bien", rc);
472 memcpy(r, shared, sizeof(double) * ROUNDS);
473 rep(" stat con landlock (Landlock NO engancha getattr)", r, ROUNDS);
474 rc = hijo_landlock_med(emes[4], abi, IT_OPEN, NULL, MED_OPEN_DENEGADO);
475 if (rc) fatal("open denegado: el hijo no termino bien", rc);
476 memcpy(r, shared, sizeof(double) * ROUNDS);
477 rep(" open DENEGADO (no monta el fd: sale mas barato)", r, ROUNDS);
478
479 printf("\n--- B.2) landlock: ms por ARMAR el ruleset (precio por proceso) ---\n");
480 for (unsigned k = 0; k < sizeof emes / sizeof *emes; k++)
481 printf(" %5d reglas: %8.2f ms (%6.2f us/regla)\n", emes[k], setup[k] / 1000.0,
482 setup[k] / emes[k]);
483
484 printf("\n(el arbol de prueba queda en %s; borrarlo a mano)\n", base);
485 return 0;
486}