Files
takana/scripts/static-audit.sh
T
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

119 lines
6.2 KiB
Bash
Executable File

#!/bin/sh
# static-audit.sh [receta…] — ¿`link = "static"` es VERDAD, o sólo una declaración?
#
# scripts/static-audit.sh # audita todo el store
# scripts/static-audit.sh curl expat # sólo ésas
#
# EL HALLAZGO (2026-07-17, destapado migrando procps-ng): `link = "static"` se cumple *casi*
# siempre — pero en todo paquete que enlaza con **libtool** se ignora EN SILENCIO. libtool lee el
# `-static` del lab como "preferí los `.a` de libtool", no como flag al linker. El binario sale
# dinámico y la receta jura que es estático. Con gcc pasaba igual: no es una regresión de zig, es
# un agujero que nadie había medido.
#
# Por qué importa, más allá de la pureza:
#
# 1. NO CORREN. El curl sellado, ejecutado en el host: `Error relocating /lib/libz.so.1:
# __snprintf_chk: symbol not found`. Su `NEEDED: libc.so` es el soname de la musl que zig
# bundlea; en el host `/lib/libc.so` son 255 bytes de *linker script*, no una biblioteca.
# El artefacto sólo funciona dentro del sandbox, donde ese nombre resuelve a otra cosa.
# 2. ARRASTRAN GCC. helix, yazi, git-absorb, cargo-audit y tuc traen `libgcc_s.so.1` de Alpine
# DENTRO del binario final. Es deuda de matar-gcc que ningún `compiler = "gcc"` declara:
# invisible para el frente entero hasta esta medición.
# 3. Un NEEDED es una entrada NO declarada — lo mismo que harkaq (SDD 16) mide en build, pero en
# RUNTIME. Un binario que dice "no dependo de nada" y depende, rompe el cono de `affected.py`
# (SDD 17 §4): un CVE en zlib no alcanzaría a un curl que declara enlace estático.
#
# EL FIX, por receta: `LDFLAGS="-all-static -no-pie"` en `compile` **y** en `install` (libtool
# RELINKEA al instalar, así que sólo en compile no basta), nunca en configure. Patrón ya usado en
# jq/parted/shadow/procps-ng.
#
# Por qué esto es un script y no un gate duro dentro de `takana build`: hacerlo fallar hoy rompe
# 28 recetas selladas de golpe, varias del sistema base (curl, util-linux, sudo). Primero se
# arreglan con evidencia, después se cierra la puerta. Mientras tanto esto es la evidencia.
#
# Exit: 0 si nadie miente, 1 si alguno miente (sirve de gate en CI cuando la lista llegue a cero).
set -eu
HUB="$(cd "$(dirname "$0")/.." && pwd)"
cd "$HUB"
STORE="${STORE:-$PWD/store}"
HAMMER="${TAKANA:-${HAMMER:-$HUB/target/release/takana}}"
if [ $# -gt 0 ]; then
recetas=""
for n in "$@"; do recetas="$recetas recipes/$n.toml"; done
else
recetas="$(grep -lE '^[[:space:]]*link[[:space:]]*=[[:space:]]*"static"' recipes/*.toml)"
fi
mienten=0; honestos=0; sin=0
for f in $recetas; do
n=$(basename "$f" .toml)
grep -qE '^[[:space:]]*link[[:space:]]*=[[:space:]]*"static"' "$f" || continue
# EL ARTEFACTO VIGENTE, por hash — NO el más reciente por mtime. `takana hash` calcula el
# ArtifactHash de la receta de HOY sin construir (~2ms, puro sobre las recetas). El store guarda
# TODOS los sellados históricos de una receta (expat tenía 5); auditar el más reciente por mtime
# sobre-reportaba (acusaba a dbus/libnl, ya sanas, por un sellado anterior a sus flags). Ahora
# auditamos EXACTAMENTE el sellado que corresponde a la receta actual, o ninguno.
#
# Fallback a `ls -dt` si `takana` no está compilado: el audit sigue corriendo, sólo vuelve a
# poder sobre-reportar (como antes de que existiera `takana hash`).
if [ -x "$HAMMER" ]; then
h=$("$HAMMER" --store "$STORE" hash "$f" 2>/dev/null) || h=""
if [ -n "$h" ] && [ -d "$STORE/${h#b3:}-$n" ]; then
art="$STORE/${h#b3:}-$n"
else
# No hay sellado para la receta VIGENTE ⇒ no hay nada honesto que auditar (rancio no
# cuenta). Es el "sin artefacto" legítimo: la receta cambió y falta rebuild.
sin=$((sin + 1)); continue
fi
else
art=$(ls -dt "$STORE"/*-"$n" 2>/dev/null | head -1) || true
[ -z "${art:-}" ] && { sin=$((sin + 1)); continue; }
fi
# TODOS los ELF *ejecutables* del artefacto, no el primero. Una `.so` tiene NEEDED por
# definición y no prueba nada; sólo los ejecutables dicen si el enlace final fue estático.
#
# ⚠ Antes esto miraba UNO y cortaba (`break`). Con dwarves se vio lo que eso deja pasar: sus
# DIEZ ejecutables eran dinámicos y el audit reportaba uno — acertó de casualidad, porque el
# primero también lo era. El caso que se escapaba en silencio es el inverso: primer binario
# estático y el resto no, que se leía como receta honesta. Es «no comprobar no es aprobar»
# aplicado al propio guardián (2026-08-31).
vistos=0; needed=""; primer_malo=""; malos=0
for b in $(find "$art" -type f -perm -u+x 2>/dev/null); do
file -b "$b" 2>/dev/null | grep -q 'ELF.*executable' || continue
vistos=$((vistos + 1))
nb=$(readelf -d "$b" 2>/dev/null | grep -oE 'Shared library: \[[^]]+\]' \
| sed 's/.*\[//;s/\]//' | tr '\n' ' ') || true
if [ -n "$nb" ]; then
malos=$((malos + 1))
[ -z "$primer_malo" ] && { primer_malo="${b#$art/}"; needed="$nb"; }
fi
done
[ "$vistos" -eq 0 ] && { sin=$((sin + 1)); continue; }
if [ -n "$needed" ]; then
if [ "$malos" -gt 1 ]; then
printf ' ✗ %-18s dice static, %d de %d ejecutables DINÁMICOS (p.ej. %s) → %s\n' \
"$n" "$malos" "$vistos" "$primer_malo" "$needed"
else
printf ' ✗ %-18s dice static, es DINÁMICO (%s) → %s\n' "$n" "$primer_malo" "$needed"
fi
case "$needed" in *libgcc_s*) printf ' %-18s ↑ arrastra libgcc_s de Alpine: deuda de matar-gcc no declarada\n' "" ;; esac
case "$needed" in *libc.so*) printf ' %-18s ↑ `libc.so` es la musl de zig: en el host son 255B de linker script ⇒ NO CORRE fuera del sandbox\n' "" ;; esac
mienten=$((mienten + 1))
else
honestos=$((honestos + 1))
fi
done
echo
echo "══ estáticos de verdad: $honestos | MIENTEN: $mienten | sin artefacto o sin ELF: $sin"
[ "$mienten" -gt 0 ] && {
echo " fix: LDFLAGS=\"-all-static -no-pie\" en compile Y en install (libtool relinkea al instalar)"
exit 1
}
echo " ✅ toda receta que declara link=static lo cumple"
exit 0