diff --git a/scripts/static-audit.sh b/scripts/static-audit.sh new file mode 100755 index 00000000..d403cd4e --- /dev/null +++ b/scripts/static-audit.sh @@ -0,0 +1,82 @@ +#!/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 `hammer 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}" + +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 + art=$(ls -d "$STORE"/*-"$n" 2>/dev/null | head -1) || true + [ -z "${art:-}" ] && { sin=$((sin + 1)); continue; } + + # El primer ELF *ejecutable* del artefacto. Una `.so` tiene NEEDED por definición y no prueba + # nada; sólo el ejecutable dice si el enlace final fue estático. + bin="" + for b in $(find "$art" -type f -perm -u+x 2>/dev/null | head -40); do + if file -b "$b" 2>/dev/null | grep -q 'ELF.*executable'; then bin="$b"; break; fi + done + [ -z "$bin" ] && { sin=$((sin + 1)); continue; } + + needed=$(readelf -d "$bin" 2>/dev/null | grep -oE 'Shared library: \[[^]]+\]' \ + | sed 's/.*\[//;s/\]//' | tr '\n' ' ') || true + if [ -n "$needed" ]; then + printf ' ✗ %-18s dice static, es DINÁMICO → %s\n' "$n" "$needed" + 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