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…
⎇ 7cce81611c922bc05a8400df9c8b543127ea3cf9
scripts/sonda-llavero-hermanos.c
Historia
Quién tocó qué
crudo
el llavero entre HERMANOS, medido: sí — pero sólo si el ancestro abrió sesión
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}