tawasuyu

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 1574Shell 204Rust 111Markdown 993763 Sutichaykuna

⎇ 91496007ca9d3e711993441c23170bd1470e1665 scripts/harkaq/harkaq-trace.c C · 184 siq'ikuna · 7.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

harkaq-trace: la traza POSITIVA de la clausura (plan-freebsd T1.1-T1.2, prior art filemon/META MODE) 91496007c · sergio ·

1// harkaq-trace — la traza POSITIVA de la clausura (plan-freebsd T1.1; prior art: el
2// filemon/META MODE de FreeBSD, 12 años de builds midiendo "qué tocó este target").
3//
4// harkaq-audit lee lo DENEGADO (evidencia negativa bajo política=clausura). Esto lee lo
5// ABIERTO (evidencia positiva). Juntas cierran el bucle en las dos direcciones que el SDD 16
6// sólo cierra en una: la deuda (usó sin declarar → audit) y la GRASA (declaró y jamás tocó →
7// esta traza). La grasa hoy es invisible y se hereda a la política runtime del cierre §3.
8//
9//   harkaq-trace [--fs <path>] [--dir <path>]... [--prefix <pfx>]... [-o <out>] -- <cmd>...
10//
11//   --fs <path>    marca el FILESYSTEM que contiene <path> (FAN_MARK_FILESYSTEM; pide
12//                  CAP_SYS_ADMIN — el modo del piloto en el worker, que corre como root).
13//                  La marca de filesystem es AGNÓSTICA de mount-namespaces: el tracer vive
14//                  en el HOST y ve los open() que el build hace DENTRO del bwrap sin cruzar
15//                  ningún ns — la razón por la que fanotify le gana a ptrace acá (ptrace
16//                  además lo deniega el seccomp D4 del propio sandbox) y a audit (que sólo
17//                  emite DENEGACIONES, nunca concesiones usadas).
18//   --dir <path>   marca un directorio suelto, sólo hijos directos (modo self-test sin
19//                  privilegios — suficiente para probar el ciclo, NO para un build real).
20//   --prefix <p>   filtra la salida a paths bajo <p> (repetible). En el worker: el store —
21//                  exactamente la pregunta "¿qué parte de la clausura concedida se usó?".
22//
23// Salida: un path por línea, CRUDO y con duplicados. Clasificar, deduplicar y canonizar es
24// del normalizador (harkaq-trace-norm.py) — el mismo reparto que harkaq-audit/harkaq-verdict:
25// el proceso con privilegio se mantiene lo más tonto posible (argumento Q1c).
26//
27// D9 (canario): si fanotify no arranca (sin privilegio en --fs, kernel sin soporte), el exit
28// es 3 = SinEvidencia. Jamás una traza vacía que se lea como "el build no usó nada".
29
30#define _GNU_SOURCE
31#include <errno.h>
32#include <fcntl.h>
33#include <limits.h>
34#include <poll.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <sys/fanotify.h>
39#include <sys/wait.h>
40#include <unistd.h>
41
42#define MAX_MARKS 32
43#define MAX_PREFIJOS 32
44
45static const char *marcas_fs[MAX_MARKS];
46static const char *marcas_dir[MAX_MARKS];
47static const char *prefijos[MAX_PREFIJOS];
48static int n_fs = 0, n_dir = 0, n_pref = 0;
49
50static int pasa_filtro(const char *path) {
51    if (n_pref == 0)
52        return 1;
53    for (int i = 0; i < n_pref; i++)
54        if (strncmp(path, prefijos[i], strlen(prefijos[i])) == 0)
55            return 1;
56    return 0;
57}
58
59int main(int argc, char **argv) {
60    const char *salida = NULL;
61    int i = 1;
62    for (; i < argc; i++) {
63        if (strcmp(argv[i], "--fs") == 0 && i + 1 < argc && n_fs < MAX_MARKS)
64            marcas_fs[n_fs++] = argv[++i];
65        else if (strcmp(argv[i], "--dir") == 0 && i + 1 < argc && n_dir < MAX_MARKS)
66            marcas_dir[n_dir++] = argv[++i];
67        else if (strcmp(argv[i], "--prefix") == 0 && i + 1 < argc && n_pref < MAX_PREFIJOS)
68            prefijos[n_pref++] = argv[++i];
69        else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc)
70            salida = argv[++i];
71        else if (strcmp(argv[i], "--") == 0) {
72            i++;
73            break;
74        } else {
75            fprintf(stderr, "harkaq-trace: arg desconocido: %s\n", argv[i]);
76            return 2;
77        }
78    }
79    if (i >= argc || (n_fs + n_dir) == 0) {
80        fprintf(stderr,
81                "uso: harkaq-trace [--fs p] [--dir p]... [--prefix p]... [-o out] -- cmd...\n");
82        return 2;
83    }
84    char **cmd = &argv[i];
85
86    FILE *out = stdout;
87    if (salida) {
88        out = fopen(salida, "w");
89        if (!out) {
90            perror("harkaq-trace: fopen salida");
91            return 2;
92        }
93    }
94
95    // FAN_CLASS_NOTIF: sólo observamos, no autorizamos — la autorización es de Landlock
96    // (harkaq-exec). FAN_CLOEXEC: el hijo no hereda el grupo (se auto-trazaría el exec).
97    int fan = fanotify_init(FAN_CLASS_NOTIF | FAN_CLOEXEC, O_RDONLY | O_CLOEXEC);
98    if (fan < 0) {
99        // D9: sin grupo fanotify no hay evidencia. 3 = SinEvidencia, mismo código que audit.
100        fprintf(stderr, "harkaq-trace: SinEvidencia — fanotify_init: %s\n", strerror(errno));
101        return 3;
102    }
103    for (int m = 0; m < n_fs; m++)
104        if (fanotify_mark(fan, FAN_MARK_ADD | FAN_MARK_FILESYSTEM, FAN_OPEN, AT_FDCWD,
105                          marcas_fs[m]) < 0) {
106            fprintf(stderr, "harkaq-trace: SinEvidencia — mark fs %s: %s\n", marcas_fs[m],
107                    strerror(errno));
108            return 3;
109        }
110    for (int m = 0; m < n_dir; m++)
111        if (fanotify_mark(fan, FAN_MARK_ADD, FAN_OPEN | FAN_EVENT_ON_CHILD, AT_FDCWD,
112                          marcas_dir[m]) < 0) {
113            fprintf(stderr, "harkaq-trace: SinEvidencia — mark dir %s: %s\n", marcas_dir[m],
114                    strerror(errno));
115            return 3;
116        }
117
118    pid_t hijo = fork();
119    if (hijo < 0) {
120        perror("harkaq-trace: fork");
121        return 2;
122    }
123    if (hijo == 0) {
124        execvp(cmd[0], cmd);
125        perror("harkaq-trace: execvp");
126        _exit(127);
127    }
128
129    // Bucle: drenar eventos mientras el hijo vive, y un último drenado tras su muerte
130    // (la cola puede traer eventos rezagados del propio build).
131    int rc_hijo = -1;
132    int vivo = 1;
133    char buf[16384], path[PATH_MAX + 1], link[64];
134    while (1) {
135        if (vivo) {
136            int st;
137            pid_t w = waitpid(hijo, &st, WNOHANG);
138            if (w == hijo) {
139                vivo = 0;
140                rc_hijo = WIFEXITED(st) ? WEXITSTATUS(st) : 128 + WTERMSIG(st);
141            }
142        }
143        struct pollfd p = {.fd = fan, .events = POLLIN};
144        int pr = poll(&p, 1, vivo ? 200 : 150);
145        if (pr < 0 && errno != EINTR) {
146            perror("harkaq-trace: poll");
147            break;
148        }
149        if (pr <= 0) {
150            if (!vivo)
151                break; // hijo muerto y cola seca: terminamos
152            continue;
153        }
154        ssize_t n = read(fan, buf, sizeof(buf));
155        if (n <= 0)
156            continue;
157        for (struct fanotify_event_metadata *ev = (void *)buf; FAN_EVENT_OK(ev, n);
158             ev = FAN_EVENT_NEXT(ev, n)) {
159            if (ev->vers != FANOTIFY_METADATA_VERSION) {
160                fprintf(stderr, "harkaq-trace: version fanotify no casa\n");
161                return 3;
162            }
163            if (ev->fd < 0)
164                continue;
165            // El propio tracer (y su fichero de salida) generan eventos: fuera.
166            if (ev->pid == getpid()) {
167                close(ev->fd);
168                continue;
169            }
170            snprintf(link, sizeof(link), "/proc/self/fd/%d", ev->fd);
171            ssize_t l = readlink(link, path, PATH_MAX);
172            close(ev->fd);
173            if (l <= 0)
174                continue;
175            path[l] = '\0';
176            if (pasa_filtro(path))
177                fprintf(out, "%s\n", path);
178        }
179    }
180    if (out != stdout)
181        fclose(out);
182    // El rc del build viaja intacto: la traza es un side-channel, no un juez.
183    return rc_hijo < 0 ? 2 : rc_hijo;
184}