gioser

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 1471Shell 173Rust 105Markdown 943545 Definiciones

⎇ 32627251b1adcc995958bd60e72226040bff6125 scripts/sonda-llavero-hermanos.c C · 216 líneas · 10.4 KB Historia Quién tocó qué crudo

el llavero entre HERMANOS, medido: sí — pero sólo si el ancestro abrió sesión 40e9f4a12 · Sergio ·

1/* ¿Un proceso HERMANO ve la seed que otro sembró en el llavero de SESIÓN? (SDD 26 §7.terdecies)
2 *
3 *     scripts/sonda-llavero-hermanos.sh                      las dos etapas
4 *     scripts/sonda-llavero-hermanos.sh --negative-control   + el llavero de sesión NUEVO
5 *
6 * ── LA PREGUNTA, Y POR QUÉ NO ES ACADÉMICA ────────────────────────────────────────────────
7 * En las cuatro imágenes de escritorio la bóveda se abre así: la persona corre `agora-cli
8 * identity unlock`, que GUARDA la seed bajo `pacha_llavero::SEED_IDENTIDAD`; después abre
9 * `boveda` desde el lanzador, y esa app LEE la misma clave para descifrar la base. Son dos
10 * procesos distintos y ninguno es hijo del otro: HERMANOS, colgados los dos de la sesión.
11 *
12 * El §7.terdecies cerró dejando esto como pendiente y, textualmente, «sin medir — y /proc/keys
13 * como root no la mide, sólo dice que la clave existe». Es exacto: ver la clave en /proc/keys
14 * prueba que `add_key` funcionó, no que el hermano pueda ENCONTRARLA. Las dos preguntas se
15 * separaron de la peor manera en ese mismo capítulo: en el LXC del worker `add_key` funciona y
16 * `keyctl(KEYCTL_SEARCH)` da ENOSYS — ahí se puede sembrar y no se puede cosechar.
17 *
18 * Si los hermanos no compartieran llavero, el flujo del escritorio sería imposible POR
19 * CONSTRUCCIÓN —igual que el caso `servidor`— y la bóveda diría `locked:true` para siempre sin
20 * que nada falle.
21 *
22 * ── POR QUÉ EN C Y NO EN PYTHON ────────────────────────────────────────────────────────────
23 * El primer intento fue una sonda en Python con `ctypes`. No corre donde hace falta: el python
24 * del anfitrión musl no trae `_ctypes` (`libffi.so.8` no está) y se cae al importar, antes de
25 * medir nada. `keyutils`/`keyctl(1)` tampoco están en el corpus. Queda el syscall pelado, que es
26 * además lo que hace `pacha-llavero`.
27 *
28 * ── LOS TRES CONTROLES ─────────────────────────────────────────────────────────────────────
29 * 1. INSTRUMENTO: si `KEYCTL_SEARCH` da ENOSYS, un «no lo encontró» no prueba nada y se dice.
30 * 2. POSITIVO: el sembrador se busca su propia clave antes de salir. Si él no la ve, lo roto es
31 *    la sonda y no el llavero.
32 * 3. NEGATIVO: un tercer proceso hace `KEYCTL_JOIN_SESSION_KEYRING` —se va a una sesión nueva y
33 *    anónima— y busca lo mismo: TIENE que no encontrarlo. Sin esto, una sonda que encuentra
34 *    siempre se ve idéntica a una que mide.
35 *
36 * Los roles corren como `exec` APARTE, no como forks: un fork heredaría por definición y la
37 * sonda contestaría que sí sin haber medido nada. Lo que se reproduce es el escritorio: dos
38 * `exec` independientes de la misma sesión.
39 */
40#define _GNU_SOURCE
41#include <errno.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <sys/syscall.h>
46#include <sys/wait.h>
47#include <unistd.h>
48
49#define KEY_SPEC_SESSION_KEYRING (-3)
50#define KEYCTL_JOIN_SESSION_KEYRING 1
51#define KEYCTL_SEARCH 10
52
53static long sembrar(const char *clave) {
54    static const char valor[] = "la seed de mentira";
55    return syscall(SYS_add_key, "user", clave, valor, sizeof valor - 1,
56                   KEY_SPEC_SESSION_KEYRING);
57}
58
59static long buscar(const char *clave) {
60    return syscall(SYS_keyctl, KEYCTL_SEARCH, KEY_SPEC_SESSION_KEYRING, "user", clave, 0L);
61}
62
63static long sesion_nueva(void) {
64    return syscall(SYS_keyctl, KEYCTL_JOIN_SESSION_KEYRING, (const char *)NULL);
65}
66
67static int rol_sembrador(const char *clave) {
68    long id = sembrar(clave);
69    if (id < 0) { printf("ADD_KEY_FALLO %s\n", strerror(errno)); return 1; }
70    long yo = buscar(clave);
71    if (yo >= 0) printf("SEMBRADA id=%ld yo_la_veo=si\n", id);
72    else         printf("SEMBRADA id=%ld yo_la_veo=NO(%s)\n", id, strerror(errno));
73    return 0;
74}
75
76static int rol_cosechador(const char *clave, int nueva) {
77    if (nueva) {
78        long s = sesion_nueva();
79        if (s < 0) { printf("JOIN_FALLO %s\n", strerror(errno)); return 1; }
80        printf("SESION_NUEVA id=%ld ", s);
81    }
82    long id = buscar(clave);
83    if (id >= 0) printf("ENCONTRADA id=%ld\n", id);
84    else         printf("NO_ENCONTRADA %s\n", strerror(errno));
85    return 0;
86}
87
88/* Lanza un rol como proceso APARTE y devuelve su primera línea en `salida`. */
89static int hermano(const char *yo, const char *rol, const char *clave, const char *extra,
90                   char *salida, size_t n) {
91    int tubo[2];
92    if (pipe(tubo) != 0) return -1;
93    pid_t p = fork();
94    if (p < 0) return -1;
95    if (p == 0) {
96        close(tubo[0]);
97        dup2(tubo[1], 1);
98        close(tubo[1]);
99        if (extra) execl(yo, yo, rol, clave, extra, (char *)NULL);
100        else       execl(yo, yo, rol, clave, (char *)NULL);
101        _exit(127);
102    }
103    close(tubo[1]);
104    ssize_t k = read(tubo[0], salida, n - 1);
105    salida[k > 0 ? k : 0] = 0;
106    char *nl = strchr(salida, '\n');
107    if (nl) *nl = 0;
108    close(tubo[0]);
109    int st = 0;
110    waitpid(p, &st, 0);
111    return 0;
112}
113
114static int escenario(const char *yo, const char *nombre, int con_sesion, int control) {
115    char clave[128], linea[512];
116    snprintf(clave, sizeof clave, "takana:sonda-hermanos:%d:%s", (int)getpid(),
117             con_sesion ? "con" : "sin");
118
119    printf("\n── ESCENARIO %s ──\n", nombre);
120    if (con_sesion) {
121        long s = sesion_nueva();
122        if (s < 0) {
123            printf("   ✗ no pude crear la sesión del ancestro (%s): este escenario no se midió\n",
124                   strerror(errno));
125            return 1;
126        }
127        printf("   el ancestro se une a un llavero de sesión propio (id=%ld) y RECIÉN AHÍ\n"
128               "   lanza a los dos hermanos, que lo heredan los dos.\n", s);
129    } else {
130        printf("   el ancestro NO tiene llavero de sesión propio: cada proceso que pide @s\n"
131               "   hace que el kernel le cree uno SUYO, al vuelo.\n");
132    }
133
134    if (hermano(yo, "sembrador", clave, NULL, linea, sizeof linea) != 0) {
135        fprintf(stderr, "no pude lanzar al sembrador\n");
136        return 1;
137    }
138    printf("   sembrador  : %s\n", linea);
139    if (strstr(linea, "ADD_KEY_FALLO")) {
140        printf("   ✗ no se pudo ni SEMBRAR: esta caja no tiene llavero de kernel usable. La\n"
141               "     pregunta queda SIN CONTESTAR acá — que no es contestada que no.\n");
142        return 1;
143    }
144    if (strstr(linea, "yo_la_veo=NO")) {
145        if (strstr(linea, "Function not implemented"))
146            printf("   ⚠ CONTROL DEL INSTRUMENTO EN ROJO: KEYCTL_SEARCH da ENOSYS acá (es el caso\n"
147                   "     medido del LXC del worker: se puede sembrar y no cosechar) ⇒ un «no lo\n"
148                   "     encontró» no probaría nada.\n");
149        else
150            printf("   ✗ CONTROL POSITIVO EN ROJO: el sembrador no ve su propia clave. Lo roto es\n"
151                   "     la sonda, no el llavero.\n");
152        return 1;
153    }
154
155    if (hermano(yo, "cosechador", clave, NULL, linea, sizeof linea) != 0) return 1;
156    printf("   hermano    : %s\n", linea);
157    int lo_ve = strstr(linea, "ENCONTRADA") && !strstr(linea, "NO_ENCONTRADA");
158
159    if (control && lo_ve) {
160        /* El control negativo SÓLO significa algo donde el hermano SÍ encontró: si arriba salió
161         * «no», un «no» del control es el mismo «no» y no separa medir de no encontrar nunca. */
162        char otra[512];
163        if (hermano(yo, "cosechador", clave, "--sesion-nueva", otra, sizeof otra) != 0) return 1;
164        printf("   control(-) : %s\n", otra);
165        if (strstr(otra, "JOIN_FALLO")) {
166            printf("   ⚠ el control negativo no se pudo montar: sin él, el «sí» de arriba no se\n"
167                   "     distingue de una sonda que encuentra siempre.\n");
168            return 1;
169        }
170        if (!strstr(otra, "NO_ENCONTRADA")) {
171            printf("   ✗ el control negativo la encontró IGUAL: la sonda no mide el llavero de\n"
172                   "     SESIÓN sino otra cosa (¿el de usuario?)\n");
173            return 1;
174        }
175        printf("   ✓ control negativo: en una sesión NUEVA la clave no está ⇒ esto mide el llavero\n");
176    }
177    return lo_ve ? 0 : 2;
178}
179
180int main(int argc, char **argv) {
181    if (argc >= 3 && !strcmp(argv[1], "sembrador"))  return rol_sembrador(argv[2]);
182    if (argc >= 3 && !strcmp(argv[1], "cosechador"))
183        return rol_cosechador(argv[2], argc > 3 && !strcmp(argv[3], "--sesion-nueva"));
184
185    int control = argc > 1 && !strcmp(argv[1], "--negative-control");
186    printf("── ¿un hermano ve la seed del llavero de SESIÓN? ──\n   uid : %d\n", (int)getuid());
187
188    /* Los dos escenarios se corren SIEMPRE y juntos, porque el interesante es el CONTRASTE: el
189     * primero solo, en rojo, se lee como «los hermanos no comparten nunca», que es falso. */
190    int sin_ses = escenario(argv[0], "A · el ancestro NO tiene sesión propia", 0, control);
191    int con_ses = escenario(argv[0], "B · el ancestro SÍ tiene sesión (lo que hace un login)", 1,
192                            control);
193
194    printf("\n── LO QUE SE MIDIÓ ──\n");
195    if (sin_ses == 0 && con_ses == 0) {
196        printf("✓ el hermano ve la seed en los DOS escenarios: la herencia no depende de que\n"
197               "  alguien abra sesión. El flujo del escritorio es posible.\n");
198        return 0;
199    }
200    if (con_ses == 0 && sin_ses == 2) {
201        printf("✓ el hermano ve la seed SÓLO si el ancestro común abrió sesión (B), y no la ve si\n"
202               "  cada proceso se fabrica la suya al vuelo (A).\n"
203               "⇒ el flujo del escritorio es posible, pero NO gratis: `agora-cli unlock` y `boveda`\n"
204               "  tienen que colgar de un ancestro con llavero de SESIÓN. Si arje lanza cada app\n"
205               "  sin eso, la seed muere con el proceso que la sembró y la bóveda contesta\n"
206               "  locked:true para siempre, sin un solo error — que es la forma de fallo del §7.\n");
207        return 0;
208    }
209    if (con_ses == 2) {
210        printf("✗ ni con sesión propia el hermano ve la seed: el flujo del escritorio sería\n"
211               "  imposible por construcción, igual que el caso `servidor` del §7.terdecies.\n");
212        return 1;
213    }
214    printf("⚠ alguno de los dos escenarios no se pudo medir acá (ver arriba): sin contestar.\n");
215    return 1;
216}