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 1184Shell 70Rust 62Markdown 501503 Sutichaykuna

fix(instalador): el bucle de la contraseña no terminaba nunca, y llenó 1,5 G de RAM del anfitrión

Los dos `while :;` que piden contraseña reintentaban SIN TOPE, y los `read_secret` —el de la
librería y el que traen los dos instaladores— se comen el fallo del `read` (`|| _sec=""`). Juntos:
con un stdin en EOF —un arnés, un `< /dev/null`, un pipe cerrado— el `read` falla, la variable sale
vacía, el `[ -n ]` no pasa, y el bucle imprime «no coinciden (o vacía) — reintentá» para siempre,
sin dormir ni un milisegundo.

ENCONTRADO EN VIVO, no leyendo el código: la raíz del anfitrión estaba al 96 % y un
`takana-live-install.sh` quedó HUÉRFANO colgado de PID 1 escribiendo **1,6 G en cuatro minutos** de
esas tres líneas. Dos cosas lo hacían invisible: su propio script ya estaba borrado del disco, y
nada en su línea de comandos nombraba el fichero que estaba llenando — se encontró recorriendo
`/proc/*/fd`. Y `/tmp` es **tmpfs**, así que eso no era disco: era **RAM**, en una caja de 7,7 G que
ya tenía 1,8 G en swap.

El tope va en el BUCLE y no sólo en el lector a propósito: los instaladores traen su propio
`read_secret` y la librería no manda sobre él, así que un bucle que se defiende solo es lo único que
protege en los tres casos. `CUENTA_EOF` corta en el primer intento cuando el lector sí es el
nuestro; el tope de `CUENTA_INTENTOS` (3) corta de todas formas.

Control en los cuatro casos, con stdin en EOF y 15 s de techo:

    actual    · lector de la librería    SE COLGÓ   202 KB en 15 s
    actual    · lector del instalador    SE COLGÓ   15,3 MB en 15 s
    arreglado · lector de la librería    terminó    704 bytes  (corta en el primer EOF)
    arreglado · lector del instalador    terminó    998 bytes  (corta por el tope)

Y lo que dice al cortar nombra la causa y la consecuencia, que es lo que faltaría para diagnosticarlo
desde un log: «⚠ no pude leer la contraseña de root (¿stdin sin terminal?) — root queda CERRADO».

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

739b7423ea2ed4e42dbf30b292f4fa27d426db7e · ruwaq Sergio · (7 p'unchaw ñawpaq) · tayta 6f3d75d5f · kay commitpi sach'ata qhaway

Semántico sayk'uy

Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.

1 qillqakuna · +35 −2

diff --git a/scripts/lib/cuenta.sh b/scripts/lib/cuenta.sh
index 29be5e0c..cfad81a5 100644
--- a/scripts/lib/cuenta.sh
+++ b/scripts/lib/cuenta.sh
@@ -60,16 +60,35 @@ CUENTA_ROOT_HASH="${TAKANA_ROOT_HASH:-${HAMMER_ROOT_HASH:-}}"
 command -v bb >/dev/null 2>&1 || bb() { busybox "$@" 2>/dev/null || command "$@"; }
 command -v ask >/dev/null 2>&1 || ask() {
 	_a_v="$1"; printf '%s%s: ' "$2" "$([ -n "${3:-}" ] && printf ' [%s]' "$3")"
-	IFS= read -r _a_r || _a_r=""
+	IFS= read -r _a_r || { _a_r=""; CUENTA_EOF=1; }
 	[ -n "$_a_r" ] || _a_r="${3:-}"
 	eval "$_a_v=\$_a_r"
 }
 command -v read_secret >/dev/null 2>&1 || read_secret() {
 	printf '%s: ' "$2"; stty -echo 2>/dev/null || true
-	IFS= read -r _rs || _rs=""; stty echo 2>/dev/null || true; echo
+	IFS= read -r _rs || { _rs=""; CUENTA_EOF=1; }; stty echo 2>/dev/null || true; echo
 	eval "$1=\$_rs"
 }
 
+# ── ⚠ POR QUÉ LOS BUCLES DE CONTRASEÑA SON ACOTADOS, Y NO ES UN GUSTO (2026-10-15) ─────────────
+# Los dos `while :;` de abajo reintentaban SIN TOPE, y los `read_secret` —el de acá y el que traen
+# los dos instaladores— se comen el fallo del `read` (`|| _sec=""`). Juntos: con un stdin en EOF
+# —un arnés, un `< /dev/null`, un pipe que se cerró— el `read` falla, la variable sale vacía, el
+# `[ -n ]` no pasa, y el bucle imprime «no coinciden (o vacía) — reintentá» para siempre, sin
+# dormir ni un milisegundo.
+#
+# MEDIDO en el anfitrión: un `takana-live-install.sh` quedó huérfano colgado de PID 1 y en CUATRO
+# MINUTOS escribió **1,5 G** de esas tres líneas. Y `/tmp` es **tmpfs**, así que eso no era disco:
+# era RAM, en una caja de 7,7 G que ya tenía 1,8 G en swap. El proceso no se veía: su propio script
+# ya estaba borrado y nada en su línea de comandos nombraba el fichero que estaba llenando.
+#
+# El tope va en el BUCLE y no sólo en el lector a propósito: los instaladores traen su propio
+# `read_secret` y la librería no manda sobre él, así que un bucle que se defiende solo es lo único
+# que protege en los tres casos. `CUENTA_EOF` corta en el primer intento cuando el lector SÍ es el
+# nuestro; el tope corta de todas formas.
+CUENTA_EOF=0
+CUENTA_INTENTOS="${CUENTA_INTENTOS:-3}"
+
 # cuenta_hash <contraseña> — SHA-512 por `cryptpw`. Devuelve vacío si este busybox no lo trae, y el
 # llamador lo dice: un hash vacío escribiría una cuenta SIN CONTRASEÑA, que es peor que no tenerla.
 cuenta_hash() {
@@ -122,9 +141,16 @@ cuenta_preguntar() {
 	if [ -n "$CUENTA_USER" ]; then
 		if [ -z "$CUENTA_HASH" ]; then
 			if [ -n "$(cuenta_hash x)" ]; then
+				_cp_n=0
 				while :; do
+					_cp_n=$((_cp_n+1))
 					read_secret _cp_p1 "Contraseña para $CUENTA_USER"
 					read_secret _cp_p2 "Repetir"
+					if [ "$CUENTA_EOF" = 1 ] || [ "$_cp_n" -ge "$CUENTA_INTENTOS" ]; then
+						[ "$_cp_p1" = "$_cp_p2" ] && [ -n "$_cp_p1" ] || {
+							echo "  ⚠ no pude leer una contraseña para $CUENTA_USER (¿stdin sin terminal?) — su cuenta queda CERRADA"
+							break; }
+					fi
 					[ "$_cp_p1" = "$_cp_p2" ] && [ -n "$_cp_p1" ] || { echo "  no coinciden (o vacía) — reintentá"; continue; }
 					CUENTA_HASH=$(cuenta_hash "$_cp_p1")
 					[ -n "$CUENTA_HASH" ] || { echo "  ⚠ no pude generar el hash"; break; }
@@ -150,9 +176,16 @@ cuenta_preguntar() {
 		echo
 		echo "  Nadie puede escalar todavía ⇒ root necesita contraseña, o la máquina queda inadministrable."
 		if [ -n "$(cuenta_hash x)" ]; then
+			_cp_m=0
 			while :; do
+				_cp_m=$((_cp_m+1))
 				read_secret _cp_r1 "Contraseña de root"
 				read_secret _cp_r2 "Repetir"
+				if [ "$CUENTA_EOF" = 1 ] || [ "$_cp_m" -ge "$CUENTA_INTENTOS" ]; then
+					[ "$_cp_r1" = "$_cp_r2" ] && [ -n "$_cp_r1" ] || {
+						echo "  ⚠ no pude leer la contraseña de root (¿stdin sin terminal?) — root queda CERRADO"
+						break; }
+				fi
 				[ "$_cp_r1" = "$_cp_r2" ] && [ -n "$_cp_r1" ] || { echo "  no coinciden (o vacía) — reintentá"; continue; }
 				CUENTA_ROOT_HASH=$(cuenta_hash "$_cp_r1"); break
 			done