Files
takana/scripts/test-atuq-codecs.sh
SergioandClaude Opus 5 b221f06c01 arneses: los CINCO sandboxes de Gecko apagados, no un subconjunto a ojo
Cada arnés headless tenía su propio subconjunto de MOZ_DISABLE_*_SANDBOX, elegido en su
momento sin medir: banco 1 de 5, perfilar 4, codecs 3, ruteo 3, nativo 2, inicio 2. Con
cualquier subconjunto incompleto el sandbox de Firefox sigue intentando montar su
user-namespace dentro del de bwrap, falla con 'uid_map: EPERM' y deja un ayudante
'Sandbox Forked' muerto de SIGSEGV por cada intento — en el 100% de las corridas, después
de escribir el PNG y por eso invisible.

Medido: 2 cadáveres por corrida con sólo CONTENT apagado, 0 con los cinco. Verificado sobre
el arnés ya editado: segv=0 EPERM=0 screenshot=sí.

Acá no se pierde seguridad: bwrap ya es la jaula, el sandbox de Gecko es redundante y lo
único que hace dentro es fallar.

⚠ Cambia las condiciones de medición del banco de PGO: las cifras de docs/26 se tomaron con
el arnés viejo. Los cocientes del PGO comparan dos firefox bajo el mismo arnés y deberían
aguantar; la línea base absoluta de arranque no tiene por qué. Anotado en la cabecera.

No se tocan scripts/wlr/dunst-headless.sh (otro agente trabajando en él) ni los cazadores,
que conservan el entorno viejo a propósito para poder reproducir la condición original.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
2026-09-08 16:11:28 +00:00

147 lines
7.5 KiB
Bash
Executable File

#!/bin/sh
# test-atuq-codecs.sh — ¿qué formatos de vídeo y audio REPRODUCE atuq de verdad?
#
# scripts/test-atuq-codecs.sh # sale 0 si los seis casos reproducen
# scripts/test-atuq-codecs.sh --negative-control # rompe el enlace de ffvpx a propósito
#
# ── POR QUÉ EXISTE, Y POR QUÉ NO LO CUBRE NINGÚN OTRO GUARDIÁN ────────────────────────────────
# `vigia-sonames.py` mira la clausura de un perfil; `test-atuq-rootfs.py` mira los NEEDED del
# rootfs hidratado y, con `--dlopen`, adivina el tercer escalón por cadenas. Ninguno de los tres
# puede responder la única pregunta que le importa a quien usa el navegador: **¿se ve el vídeo?**
# Un códec puede fallar con TODOS los ficheros en su sitio —pasó, y es lo que originó este script:
# el proceso RDD perdía `/usr/lib/atuq` del `LD_LIBRARY_PATH`, no cargaba el ffvpx bundleado, se
# caía al ffmpeg del sistema, y AV1 —que ese ffmpeg sólo decodifica por hardware— se quedaba en
# `readyState=1` para siempre. Sin un error. Sin un NEEDED faltante. Sin una cadena ausente.
#
# ── CÓMO SE MIDE: EL PROPIO MOTOR DICE EL VEREDICTO ───────────────────────────────────────────
# La página abre cada muestra, la deja correr 6 s y mira `currentTime`. Un decodificador que se
# crea pero no entrega cuadros deja el tiempo en 0.00, que es exactamente el fallo que buscamos:
# «se creó» no es «decodifica». El resultado sale por `dump()` —con
# `browser.dom.window.dump.enabled`— al stdout del proceso, así que **no hace falta ni red ni
# servidor**: se lee del log. Headless a propósito: esto tiene que correr en el worker y en cron.
#
# ── LAS MUESTRAS VAN VERSIONADAS, NO SE GENERAN ───────────────────────────────────────────────
# `scripts/fixtures/codecs/` son cinco ficheros de ~200 K en total, generados una vez con ffmpeg.
# Generarlos en cada corrida ataría el guardián al ffmpeg del HOST —otra versión, otros bytes, otro
# resultado— y lo volvería inejecutable donde no haya uno. Un fixture congelado es el control.
#
# ── EL CONTROL NEGATIVO NO ES ADORNO ──────────────────────────────────────────────────────────
# Un guardián que nunca falló y uno que no puede fallar se ven idénticos desde afuera. Con
# `--negative-control` se saltea el lanzador y se corre el binario con el appdir UNA sola vez en
# `LD_LIBRARY_PATH`, que es la forma exacta del bug: se EXIGE que AV1 falle. Si en ese modo AV1
# pasa, el que está roto es este script.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
RFS="${RFS:-/mnt/cosecha/escritorios/atuq-rootfs}"
ALPINE="$ROOT/.dev-fs/alpine"
CONTROL=0
[ "${1:-}" = "--negative-control" ] && CONTROL=1
command -v bwrap >/dev/null || { echo "✗ falta bwrap" >&2; exit 1; }
# El rootfs lo arma `atuq-nested.sh`, que es donde vive LA LISTA DE RAÍCES. Acá no se copia: se
# llama. Dos listas es cómo se desincronizan.
ROOTFS_ONLY=1 RFS="$RFS" "$ROOT/scripts/atuq-nested.sh"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
cp scripts/fixtures/codecs/* "$TMP/"
mkdir -p "$TMP/perfil"
cat > "$TMP/perfil/user.js" <<'JS'
// `dump()` es el canal de salida del veredicto: sin esto la página no puede hablarle al log.
user_pref("browser.dom.window.dump.enabled", true);
user_pref("devtools.console.stdout.content", true);
// Sin esto un `<video autoplay muted>` puede quedarse bloqueado y el guardián reportaría un
// fallo de códec que no existe. Se apaga la política, no el códec.
user_pref("media.autoplay.default", 0);
user_pref("media.autoplay.blocking_policy", 0);
JS
cat > "$TMP/matriz.html" <<'HTML'
<!doctype html><meta charset="utf-8"><title>matriz de códecs</title>
<body style="background:#111;color:#eee;font:15px monospace">
<h2>¿qué reproduce atuq?</h2><table id="t"></table>
<script>
const CASOS = [
["H264+AAC", "h264-aac.mp4"],
["VP9+Opus", "vp9-opus.webm"],
["AV1", "av1.mp4"],
["MP3", "mp3.mp3"],
["FLAC", "flac.flac"],
];
const t = document.getElementById('t'), res = [];
function probar(i){
if (i >= CASOS.length) {
const s = 'CODEC-RESULT ' + res.join(' ');
if (window.dump) dump(s + '\n');
console.log(s);
return;
}
const [nom, f] = CASOS[i];
const v = document.createElement(/\.(mp3|flac)$/.test(f) ? 'audio' : 'video');
v.src = f; v.muted = true; v.autoplay = true; v.playsInline = true;
document.body.appendChild(v);
let err = '';
v.addEventListener('error', () => { err = 'error'; });
v.play().catch(() => { err = 'play-rechazado'; });
// 6 s: una muestra dura 3 s, así que un decodificador sano llega al final con margen. Y el
// veredicto NO es «hubo decodificador» sino «el tiempo avanzó», que es lo que ve el usuario.
setTimeout(() => {
const bien = v.currentTime > 0.3 && !err;
const fila = t.insertRow();
fila.insertCell().textContent = nom;
fila.insertCell().textContent = bien ? 'REPRODUCE' : 'NO';
fila.insertCell().textContent = 't=' + v.currentTime.toFixed(2) + ' ready=' + v.readyState + ' ' + err;
res.push(nom + '=' + (bien ? 1 : 0) + '@' + v.currentTime.toFixed(1));
v.pause(); v.remove();
probar(i + 1);
}, 6000);
}
probar(0);
</script>
HTML
# El sandbox de contenido/RDD/utility se apaga DENTRO de bwrap y no por gusto: en una jaula sin
# userns anidado cada hijo muere en bucle y no habría decodificación que medir. Es entorno de
# prueba, no una preferencia del artefacto — la imagen real corre con su sandbox puesto.
if [ "$CONTROL" = "1" ]; then
# La forma exacta del bug: sin lanzador y con el appdir UNA vez. Gecko se come el primer
# elemento al lanzar el RDD ⇒ ffvpx no carga ⇒ AV1 no reproduce.
BIN=/usr/lib/atuq/atuq
LDP=/usr/lib/atuq:/usr/lib:/lib
else
BIN=/usr/bin/atuq
LDP=""
fi
bwrap --overlay-src "$ALPINE" --overlay-src "$RFS" --tmp-overlay / \
--dev-bind /dev /dev --proc /proc --bind /sys /sys --tmpfs /tmp --tmpfs /run \
--bind "$TMP" /salida --uid 0 --gid 0 \
--setenv PATH /usr/bin:/bin --setenv LD_LIBRARY_PATH "$LDP" \
--setenv HOME /root --setenv XDG_RUNTIME_DIR /run --setenv MOZ_HEADLESS 1 \
--setenv MOZ_DISABLE_CONTENT_SANDBOX 1 \
--setenv MOZ_DISABLE_GMP_SANDBOX 1 \
--setenv MOZ_DISABLE_RDD_SANDBOX 1 \
--setenv MOZ_DISABLE_SOCKET_PROCESS_SANDBOX 1 \
--setenv MOZ_DISABLE_UTILITY_SANDBOX 1 \
sh -c "mkdir -p /root; timeout 90 $BIN --no-remote --headless --profile /salida/perfil \
file:///salida/matriz.html > /salida/atuq.log 2>&1; true"
LINEA="$(grep -a '^CODEC-RESULT' "$TMP/atuq.log" | head -1 || true)"
[ -n "$LINEA" ] || { echo "✗ el navegador no llegó a dar veredicto — mirá $TMP/atuq.log" >&2
cp "$TMP/atuq.log" work/atuq-codecs.log 2>/dev/null || true; exit 1; }
echo "$LINEA" | tr ' ' '\n' | grep -v CODEC-RESULT | while read -r caso; do
printf ' %-14s %s\n' "${caso%%=*}" "$(echo "$caso" | sed 's/.*=//')"
done
MALOS="$(echo "$LINEA" | tr ' ' '\n' | grep '=0@' | sed 's/=.*//' | tr '\n' ' ')"
if [ "$CONTROL" = "1" ]; then
case " $MALOS " in
*" AV1 "*) echo "✓ control negativo: AV1 falla como debe (el guardián SÍ lo vería)"; exit 0 ;;
*) echo "✗ control negativo: AV1 pasó sin ffvpx — este script no puede ver el fallo" >&2; exit 1 ;;
esac
fi
[ -z "$MALOS" ] || { echo "✗ no reproducen: $MALOS" >&2; exit 1; }
echo "✓ los cinco formatos reproducen"