/* ¿Un proceso HERMANO ve la seed que otro sembró en el llavero de SESIÓN? (SDD 26 §7.terdecies) * * scripts/sonda-llavero-hermanos.sh las dos etapas * scripts/sonda-llavero-hermanos.sh --negative-control + el llavero de sesión NUEVO * * ── LA PREGUNTA, Y POR QUÉ NO ES ACADÉMICA ──────────────────────────────────────────────── * En las cuatro imágenes de escritorio la bóveda se abre así: la persona corre `agora-cli * identity unlock`, que GUARDA la seed bajo `pacha_llavero::SEED_IDENTIDAD`; después abre * `boveda` desde el lanzador, y esa app LEE la misma clave para descifrar la base. Son dos * procesos distintos y ninguno es hijo del otro: HERMANOS, colgados los dos de la sesión. * * El §7.terdecies cerró dejando esto como pendiente y, textualmente, «sin medir — y /proc/keys * como root no la mide, sólo dice que la clave existe». Es exacto: ver la clave en /proc/keys * prueba que `add_key` funcionó, no que el hermano pueda ENCONTRARLA. Las dos preguntas se * separaron de la peor manera en ese mismo capítulo: en el LXC del worker `add_key` funciona y * `keyctl(KEYCTL_SEARCH)` da ENOSYS — ahí se puede sembrar y no se puede cosechar. * * Si los hermanos no compartieran llavero, el flujo del escritorio sería imposible POR * CONSTRUCCIÓN —igual que el caso `servidor`— y la bóveda diría `locked:true` para siempre sin * que nada falle. * * ── POR QUÉ EN C Y NO EN PYTHON ──────────────────────────────────────────────────────────── * El primer intento fue una sonda en Python con `ctypes`. No corre donde hace falta: el python * del anfitrión musl no trae `_ctypes` (`libffi.so.8` no está) y se cae al importar, antes de * medir nada. `keyutils`/`keyctl(1)` tampoco están en el corpus. Queda el syscall pelado, que es * además lo que hace `pacha-llavero`. * * ── LOS TRES CONTROLES ───────────────────────────────────────────────────────────────────── * 1. INSTRUMENTO: si `KEYCTL_SEARCH` da ENOSYS, un «no lo encontró» no prueba nada y se dice. * 2. POSITIVO: el sembrador se busca su propia clave antes de salir. Si él no la ve, lo roto es * la sonda y no el llavero. * 3. NEGATIVO: un tercer proceso hace `KEYCTL_JOIN_SESSION_KEYRING` —se va a una sesión nueva y * anónima— y busca lo mismo: TIENE que no encontrarlo. Sin esto, una sonda que encuentra * siempre se ve idéntica a una que mide. * * Los roles corren como `exec` APARTE, no como forks: un fork heredaría por definición y la * sonda contestaría que sí sin haber medido nada. Lo que se reproduce es el escritorio: dos * `exec` independientes de la misma sesión. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #define KEY_SPEC_SESSION_KEYRING (-3) #define KEYCTL_JOIN_SESSION_KEYRING 1 #define KEYCTL_SEARCH 10 static long sembrar(const char *clave) { static const char valor[] = "la seed de mentira"; return syscall(SYS_add_key, "user", clave, valor, sizeof valor - 1, KEY_SPEC_SESSION_KEYRING); } static long buscar(const char *clave) { return syscall(SYS_keyctl, KEYCTL_SEARCH, KEY_SPEC_SESSION_KEYRING, "user", clave, 0L); } static long sesion_nueva(void) { return syscall(SYS_keyctl, KEYCTL_JOIN_SESSION_KEYRING, (const char *)NULL); } static int rol_sembrador(const char *clave) { long id = sembrar(clave); if (id < 0) { printf("ADD_KEY_FALLO %s\n", strerror(errno)); return 1; } long yo = buscar(clave); if (yo >= 0) printf("SEMBRADA id=%ld yo_la_veo=si\n", id); else printf("SEMBRADA id=%ld yo_la_veo=NO(%s)\n", id, strerror(errno)); return 0; } static int rol_cosechador(const char *clave, int nueva) { if (nueva) { long s = sesion_nueva(); if (s < 0) { printf("JOIN_FALLO %s\n", strerror(errno)); return 1; } printf("SESION_NUEVA id=%ld ", s); } long id = buscar(clave); if (id >= 0) printf("ENCONTRADA id=%ld\n", id); else printf("NO_ENCONTRADA %s\n", strerror(errno)); return 0; } /* Lanza un rol como proceso APARTE y devuelve su primera línea en `salida`. */ static int hermano(const char *yo, const char *rol, const char *clave, const char *extra, char *salida, size_t n) { int tubo[2]; if (pipe(tubo) != 0) return -1; pid_t p = fork(); if (p < 0) return -1; if (p == 0) { close(tubo[0]); dup2(tubo[1], 1); close(tubo[1]); if (extra) execl(yo, yo, rol, clave, extra, (char *)NULL); else execl(yo, yo, rol, clave, (char *)NULL); _exit(127); } close(tubo[1]); ssize_t k = read(tubo[0], salida, n - 1); salida[k > 0 ? k : 0] = 0; char *nl = strchr(salida, '\n'); if (nl) *nl = 0; close(tubo[0]); int st = 0; waitpid(p, &st, 0); return 0; } static int escenario(const char *yo, const char *nombre, int con_sesion, int control) { char clave[128], linea[512]; snprintf(clave, sizeof clave, "takana:sonda-hermanos:%d:%s", (int)getpid(), con_sesion ? "con" : "sin"); printf("\n── ESCENARIO %s ──\n", nombre); if (con_sesion) { long s = sesion_nueva(); if (s < 0) { printf(" ✗ no pude crear la sesión del ancestro (%s): este escenario no se midió\n", strerror(errno)); return 1; } printf(" el ancestro se une a un llavero de sesión propio (id=%ld) y RECIÉN AHÍ\n" " lanza a los dos hermanos, que lo heredan los dos.\n", s); } else { printf(" el ancestro NO tiene llavero de sesión propio: cada proceso que pide @s\n" " hace que el kernel le cree uno SUYO, al vuelo.\n"); } if (hermano(yo, "sembrador", clave, NULL, linea, sizeof linea) != 0) { fprintf(stderr, "no pude lanzar al sembrador\n"); return 1; } printf(" sembrador : %s\n", linea); if (strstr(linea, "ADD_KEY_FALLO")) { printf(" ✗ no se pudo ni SEMBRAR: esta caja no tiene llavero de kernel usable. La\n" " pregunta queda SIN CONTESTAR acá — que no es contestada que no.\n"); return 1; } if (strstr(linea, "yo_la_veo=NO")) { if (strstr(linea, "Function not implemented")) printf(" ⚠ CONTROL DEL INSTRUMENTO EN ROJO: KEYCTL_SEARCH da ENOSYS acá (es el caso\n" " medido del LXC del worker: se puede sembrar y no cosechar) ⇒ un «no lo\n" " encontró» no probaría nada.\n"); else printf(" ✗ CONTROL POSITIVO EN ROJO: el sembrador no ve su propia clave. Lo roto es\n" " la sonda, no el llavero.\n"); return 1; } if (hermano(yo, "cosechador", clave, NULL, linea, sizeof linea) != 0) return 1; printf(" hermano : %s\n", linea); int lo_ve = strstr(linea, "ENCONTRADA") && !strstr(linea, "NO_ENCONTRADA"); if (control && lo_ve) { /* El control negativo SÓLO significa algo donde el hermano SÍ encontró: si arriba salió * «no», un «no» del control es el mismo «no» y no separa medir de no encontrar nunca. */ char otra[512]; if (hermano(yo, "cosechador", clave, "--sesion-nueva", otra, sizeof otra) != 0) return 1; printf(" control(-) : %s\n", otra); if (strstr(otra, "JOIN_FALLO")) { printf(" ⚠ el control negativo no se pudo montar: sin él, el «sí» de arriba no se\n" " distingue de una sonda que encuentra siempre.\n"); return 1; } if (!strstr(otra, "NO_ENCONTRADA")) { printf(" ✗ el control negativo la encontró IGUAL: la sonda no mide el llavero de\n" " SESIÓN sino otra cosa (¿el de usuario?)\n"); return 1; } printf(" ✓ control negativo: en una sesión NUEVA la clave no está ⇒ esto mide el llavero\n"); } return lo_ve ? 0 : 2; } int main(int argc, char **argv) { if (argc >= 3 && !strcmp(argv[1], "sembrador")) return rol_sembrador(argv[2]); if (argc >= 3 && !strcmp(argv[1], "cosechador")) return rol_cosechador(argv[2], argc > 3 && !strcmp(argv[3], "--sesion-nueva")); int control = argc > 1 && !strcmp(argv[1], "--negative-control"); printf("── ¿un hermano ve la seed del llavero de SESIÓN? ──\n uid : %d\n", (int)getuid()); /* Los dos escenarios se corren SIEMPRE y juntos, porque el interesante es el CONTRASTE: el * primero solo, en rojo, se lee como «los hermanos no comparten nunca», que es falso. */ int sin_ses = escenario(argv[0], "A · el ancestro NO tiene sesión propia", 0, control); int con_ses = escenario(argv[0], "B · el ancestro SÍ tiene sesión (lo que hace un login)", 1, control); printf("\n── LO QUE SE MIDIÓ ──\n"); if (sin_ses == 0 && con_ses == 0) { printf("✓ el hermano ve la seed en los DOS escenarios: la herencia no depende de que\n" " alguien abra sesión. El flujo del escritorio es posible.\n"); return 0; } if (con_ses == 0 && sin_ses == 2) { printf("✓ el hermano ve la seed SÓLO si el ancestro común abrió sesión (B), y no la ve si\n" " cada proceso se fabrica la suya al vuelo (A).\n" "⇒ el flujo del escritorio es posible, pero NO gratis: `agora-cli unlock` y `boveda`\n" " tienen que colgar de un ancestro con llavero de SESIÓN. Si arje lanza cada app\n" " sin eso, la seed muere con el proceso que la sembró y la bóveda contesta\n" " locked:true para siempre, sin un solo error — que es la forma de fallo del §7.\n"); return 0; } if (con_ses == 2) { printf("✗ ni con sesión propia el hermano ve la seed: el flujo del escritorio sería\n" " imposible por construcción, igual que el caso `servidor` del §7.terdecies.\n"); return 1; } printf("⚠ alguno de los dos escenarios no se pudo medir acá (ver arriba): sin contestar.\n"); return 1; }