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
147 lines
7.5 KiB
Bash
Executable File
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"
|