go/cgo: GOTMPDIR fuera del módulo — era la única causa del no-determinismo de gocryptfs

`gocryptfs` era el último no-determinismo vigente del corpus. Ya REPRODUCE.

── La medición, que primero hice mal ──────────────────────────────────────────────────
Comparé los dos ficheros de `store/.divergen/` y salían 1.874.136 bytes distintos, con
`.text` entre ellos y funciones de OpenSSL cambiando de AVX2 a AVX-512. Era el par
EQUIVOCADO: `.divergen/<D>` es el artefacto VIEJO archivado, no una reconstrucción. El
par que define el veredicto es `<D>.r1` (1ª reconstrucción) contra el que queda en el
store (2ª).

Comparado bien, el no-determinismo son **7 bytes, todos en `.debug_str`**: los dígitos
de `go-build598798091` contra `go-build270591055`.

(Lo de AVX no era ruido: era DERIVA real contra el artefacto viejo, sellado antes de que
467a87cd pusiera `-mcpu=baseline` en el CC de cgo. Se trata aparte, abajo.)

── La causa ───────────────────────────────────────────────────────────────────────────
`-trimpath` reescribe rutas, pero cuál gana depende de dónde esté el work dir de Go:
dentro del módulo gana la reescritura del módulo y el componente ALEATORIO sobrevive;
fuera, Go reescribe el work dir entero a `/tmp/go-build` y el número desaparece. El
driver ponía `GOTMPDIR=/src/.gotmp`, que es justo dentro del módulo.

Con CGO off da igual —no hay fuentes C generadas, ninguna ruta del work dir entra en el
DWARF— y por eso las 15 recetas Go puras verificadas reproducen. Con cgo sí entra. Por
eso el cambio va acotado a `cgo = true`: las otras 358 no lo necesitan.

── Medido en los dos sentidos, con un módulo cgo de juguete y cachés limpias ─────────
  · GOTMPDIR dentro  ⇒ `go-build3806217352` en el binario; dos builds DIFIEREN
  · GOTMPDIR fuera   ⇒ ninguna cadena `go-build`; dos builds IDÉNTICOS
Y sobre gocryptfs de verdad: DERIVA primero (las dos reconstrucciones ya coincidían
entre sí y el guardado era el viejo), REPRODUCE en la pasada siguiente. En el binario
sólo queda `/tmp/go-build`, el marcador constante de trimpath.

── Va a `/cache` y no a `/tmp`, y se COMPRUEBA que exista ─────────────────────────────
El sandbox monta `--tmpfs /tmp` (~½ RAM) y un proyecto Go grande lo desborda: ésa era la
razón de poner esto en `/src`. `/cache` es un bind RW a disco (el de la caché de zig),
así que conserva el disco y queda fuera del módulo.

Y no se da por hecho que esté: con `--tmp-overlay /` la raíz es escribible, así que un
`mkdir -p /cache/gotmp` a ciegas TRIUNFARÍA creando el directorio en la capa tmpfs —en
RAM, el desbordamiento que se quería evitar, y en silencio. Si el bind falta, el build
muere diciéndolo.
This commit is contained in:
Sergio
2026-09-14 22:57:47 +00:00
parent 229ecff90a
commit 1728a77a3a
2 changed files with 39 additions and 2 deletions
+36 -2
View File
@@ -984,10 +984,44 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> takana_core::Result<Phases> {
} else {
("CGO_ENABLED=0", "-ldflags=-buildid=")
};
// ── GOTMPDIR: DENTRO del módulo con cgo apagado, FUERA con cgo encendido ──────
// `-trimpath` reescribe rutas, pero cuál gana depende de dónde esté el work dir:
// · si cae DENTRO del módulo, gana la reescritura del módulo y el componente
// ALEATORIO `go-build<N>` SOBREVIVE;
// · si cae FUERA, Go reescribe el work dir entero a `/tmp/go-build` y el número
// desaparece.
// Con CGO off da igual: no hay fuentes C generadas, así que ninguna ruta del work
// dir entra en el DWARF — las 15 recetas Go puras verificadas REPRODUCEN. Con cgo
// sí entra, y ésa era la ÚNICA causa del no-determinismo de `gocryptfs`: dos
// reconstrucciones difieren en SIETE bytes, todos en `.debug_str`, y son los
// dígitos de `go-build598798091` contra `go-build270591055`.
//
// Medido con un módulo cgo de juguete, cachés limpias y en los dos sentidos
// (2026-09-14):
// · GOTMPDIR dentro ⇒ `go-build3806217352` aparece en el binario; dos builds DIFIEREN
// · GOTMPDIR fuera ⇒ ninguna cadena `go-build`; dos builds IDÉNTICOS
//
// ⚠ Va a `/cache`, NO a `/tmp`: el sandbox monta `--tmpfs /tmp` (~½ RAM) y un
// proyecto Go grande lo desborda con sus objetos — es la razón por la que esto
// estaba en `/src` de entrada. `/cache` es un bind RW a disco (el mismo que usa la
// caché de zig), así que conserva la propiedad de estar en disco Y queda fuera del
// módulo.
//
// ⚠ Y se COMPRUEBA que `/cache` exista en vez de dar por hecho que está: con
// `--tmp-overlay /` la raíz es escribible, así que un `mkdir -p /cache/gotmp` a
// ciegas TRIUNFARÍA creando el directorio en la capa tmpfs — o sea en RAM, que es
// exactamente el desbordamiento que se quería evitar, y en silencio. Si no está,
// el build muere diciendo por qué.
let gotmp_setup = if recipe.build.cgo {
"[ -d /cache ] || { echo '!! falta el bind /cache: GOTMPDIR caería en la capa tmpfs (RAM) y un proyecto Go grande la desborda' >&2; exit 1; }; mkdir -p /cache/gotmp"
} else {
"mkdir -p /src/.gotmp"
};
let gotmpdir = if recipe.build.cgo { "/cache/gotmp" } else { "/src/.gotmp" };
Some(format!(
"{cd_prefix}export GOCACHE=/src/.gocache GOTMPDIR=/src/.gotmp GOPATH=/tmp/gopath \
"{cd_prefix}export GOCACHE=/src/.gocache GOTMPDIR={gotmpdir} GOPATH=/tmp/gopath \
GOTOOLCHAIN=local {cgo_env} GOFLAGS=-mod=vendor GOPROXY=off \
GOBIN=/out/usr/bin; mkdir -p /src/.gotmp; \
GOBIN=/out/usr/bin; {gotmp_setup}; \
go install -trimpath {ldflags} {pkg}"
))
}
+3
View File
@@ -236,3 +236,6 @@ libplasma 73ec5fb279ea44e6bcfdf4c58d8703ed1307ae7985e56ce55a5da078ea098b7e 2026-
syntax-highlighting 3db73c19bdbddcb046a80d11ec112bc7dc60134eeec75b26e907cc756d25d081 2026-09-14 reproduce
powerdevil 70ea4b373af5ac3c22cf6adde9ca45153925724ef523000bc1e0e2338e282d15 2026-09-14 reproduce
ktexteditor f13baeb75249c7f768fdd1b4584b5077779e6806ffcb295a6554f50d3f31d0ec 2026-09-14 reproduce
gocryptfs 0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d 2026-09-14 NO-DETERMINISMO
gocryptfs 0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d 2026-09-14 reproduce (tras poner al día un artefacto derivado)
gocryptfs 0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d 2026-09-14 reproduce
1 # repro-verificado.tsv — qué artefactos se comprobaron que REPRODUCEN bit a bit, y cuándo.
236 syntax-highlighting
237 powerdevil
238 ktexteditor
239 gocryptfs
240 gocryptfs
241 gocryptfs