AtlatestRepositorycore-channel
core-channel / tree / seed / scriptsbuild-seed.sh
1
#!/bin/sh2
set -eu4
seed=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)5
repo=$(CDPATH= cd -- "$seed/.." && pwd)6
work=/tmp/sigil-channel/seed-build7
rm -rf "$work"8
mkdir -p "$work"9
export ZIG_GLOBAL_CACHE_DIR="$work/zig-global-cache"10
export ZIG_LOCAL_CACHE_DIR="$work/zig-local-cache"11
export KCONFIG_NOTIMESTAMP=112
export SOURCE_DATE_EPOCH=013
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)}14
cc="$repo/toolchain/bin/x86_64-linux-musl-cc"15
ar="$repo/toolchain/bin/x86_64-linux-musl-ar"16
ranlib="$repo/toolchain/bin/x86_64-linux-musl-ranlib"17
copy_strip="$repo/toolchain/bin/copy-strip"18
map_flags="-g0 -ffile-prefix-map=$work=. -fdebug-prefix-map=$work=. -ffile-prefix-map=$repo=. -fdebug-prefix-map=$repo=."20
"$seed/scripts/verify-sources.sh"21
rm -rf "$seed/out"22
mkdir -p "$seed/out/bin"24
tar -C "$work" -xf "$seed/sources/make-4.4.1.tar.lz"25
(26
cd "$work/make-4.4.1"27
# make_cv_sys_gnu_glob=no: configure's "checking if system libc has working28
# GNU glob" answers YES against musl, and that is a FALSE POSITIVE. The check29
# compiles and RUNS a glob program, which musl passes; but musl's `glob_t` has30
# none of the GNU `gl_opendir`/`gl_readdir`/`gl_closedir`/`gl_lstat`/`gl_stat`31
# members that make's `dir_setup_glob()` then assigns unconditionally, so the32
# build dies with five "no member named" errors in src/dir.c.33
#34
# This is an autoconf cache variable, i.e. the documented escape hatch, and it35
# forces make's own bundled gnulib glob instead. No source patch.36
#37
# THIS BREAK IS PRE-EXISTING, not something the seed re-pin introduced.38
# Measured 2026-07-31 by restoring build-seed.sh, verify-sources.sh and39
# MANIFEST.sources to their pristine `main` versions in a separate copy: the40
# identical five errors. Without this line the seed cannot be rebuilt at all41
# on this host, which is why `gate-crosspath.sh` could not have been passing.42
make_cv_sys_gnu_glob=no CC="$cc" AR="$ar" RANLIB="$ranlib" LD="$cc" CFLAGS="$map_flags" ./configure --disable-nls --without-guile --disable-dependency-tracking LDFLAGS="-static -s"43
CC="$cc" CFLAGS="$map_flags" ./build.sh44
cp make "$work/bootstrap-make"45
)46
make="$work/bootstrap-make"48
tar -C "$work" -xf "$seed/sources/busybox-1.37.0.tar.bz2"49
(50
cd "$work/busybox-1.37.0"51
# Zig 0.16's linker rejects GNU ld's diagnostic-only --warn-common flag.52
sed -i 's/-Wl,--warn-common //g; /-Wl,--warn-common \\/d; s@echo "-Wl,-Map,$EXE.map -Wl,--verbose"@echo ""@' scripts/trylink53
"$make" defconfig HOSTCC="$cc"54
sed -i 's/^# CONFIG_STATIC is not set$/CONFIG_STATIC=y/' .config55
sed -i 's/^CONFIG_TC=y$/# CONFIG_TC is not set/' .config56
sed -i 's/^CONFIG_EXTRA_LDFLAGS=""$/CONFIG_EXTRA_LDFLAGS="-s"/' .config57
"$make" -j"$jobs" CC="$cc" HOSTCC="$cc" AR="$ar" RANLIB="$ranlib" STRIP="$copy_strip" KCFLAGS="$map_flags" HOSTCFLAGS="$map_flags"58
cp busybox "$seed/out/bin/busybox"59
)61
(62
cd "$work/make-4.4.1"63
cp make "$seed/out/bin/make"64
)66
tar -C "$work" -xf "$seed/sources/pkgconf-2.4.3.tar.xz"67
(68
cd "$work/pkgconf-2.4.3"69
# t-79ca, ROOT CAUSE. `p = value + (strlen(value) - 1)` computes a pointer70
# BEFORE the start of the buffer whenever a .pc file carries an empty71
# dependency-list field (`Requires:` with nothing after it): strlen is 0, the72
# subtraction wraps to SIZE_MAX, and the addition underflows. That is73
# undefined behaviour, and Zig's runtime safety TRAPS it -- SIGABRT, exit 134,74
# "addition of unsigned offset ... overflowed". gcc and clang without75
# sanitizers wrap around and land on the key's NUL terminator, which is why76
# every other distribution's pkgconf appears to work.77
#78
# MEASURED, so nobody re-derives it:79
# * identical line in 1.9.5 (parser.c:88) and 2.4.3 (parser.c:95). There is80
# NO pkgconf release that fixes this -- a version bump is not a fix.81
# * same 2.4.3 source built with host gcc: EXIT 0, correct cflags.82
# * with this line fixed and built by zig: EXIT 0, correct cflags, and a83
# genuinely absent package still reports absent (EXIT 1). Both directions.84
#85
# DO NOT "FIX" THIS BY ADDING -O2. `zig cc -O2` selects ReleaseFast, which86
# turns the safety check OFF. The symptom disappears and a live pointer87
# underflow stays in a signed build environment, silently. This script builds88
# without -O2 on purpose, and that is precisely why the defect was ever89
# visible.90
#91
# Population bound for this audit, WITH its limit: 27 .c/.h files under92
# libpkgconf/ and cli/ were scanned; 2 matched the `ptr + strlen(x) - 1`93
# shape; 1 is unguarded (this one) and the other (cli/main.c) is explicitly94
# guarded by an empty-string check immediately above it. That bound covers95
# ONLY that syntactic shape -- other pointer underflows in pkgconf are not96
# ruled out.97
sed -i 's@p = value + (strlen(value) - 1);@p = value + strlen(value); if (p > value) p--;@g' \98
libpkgconf/parser.c99
# `sed -i` exits 0 when it matches nothing. Verify the edit landed, in both100
# directions, or a future pkgconf bump silently reintroduces the abort.101
#102
# SABOTAGE-TESTED 2026-07-31, each grep separately, with a positive control:103
# real 2.4.3 parser.c -> EXIT 0, fix applied (control)104
# variable renamed upstream (sed no-ops) -> EXIT 1, "did not apply"105
# two occurrences on ONE line, `s///` -> EXIT 1, "still present"106
# The `g` above was added BECAUSE of that third case: without it sed replaces107
# only the first occurrence per line. With `g` the second grep no longer has108
# a trigger I can construct, so it is now a belt against a future edit to this109
# sed rather than a check with a demonstrated red. Saying which is which110
# matters more than the grep does.111
grep -q 'if (p > value) p--;' libpkgconf/parser.c || {112
echo "build-seed: REFUSING: pkgconf parser.c underflow fix did not apply" >&2113
exit 1114
}115
grep -q 'strlen(value) - 1' libpkgconf/parser.c && {116
echo "build-seed: REFUSING: the unfixed pkgconf underflow is still present" >&2117
exit 1118
}119
CC="$cc" AR="$ar" RANLIB="$ranlib" LD="$cc" CFLAGS="$map_flags" ./configure \120
--prefix=/usr \121
--with-pkg-config-dir=/usr/lib/pkgconfig:/usr/share/pkgconfig \122
--with-system-libdir=/usr/lib \123
--with-system-includedir=/usr/include \124
--disable-shared --enable-static --disable-dependency-tracking LDFLAGS="-static -s"125
"$make" -j"$jobs"126
cp pkgconf "$seed/out/bin/pkgconf"127
)129
# GNU patch, replacing the busybox applet (§8 Q4, decided by David 2026-07-31).130
#131
# The deciding factor was TIMING ASYMMETRY: the seed re-pin invalidates every132
# identity exactly once, so adding a tool the builder legitimately needs costs133
# nothing now and costs a second flag-day later.134
#135
# The load-bearing behavioural difference, MEASURED both ways with the builder's136
# own `patch -p1 -N -i FILE` invocation:137
# garbage / no-hunk file : busybox EXIT 0, silent, applies nothing138
# GNU EXIT 2, "Only garbage was found in the139
# patch input."140
# wrong context : both EXIT 1 (busybox "Hunk 1 FAILED", GNU141
# "Hunk #1 FAILED at 1" + a .rej file)142
# applies cleanly : both EXIT 0, file changed (positive control)143
#144
# So the recorded sha256 on a patch input moves from being the ONLY guard145
# against a corrupted patch to being defence-in-depth. That was the goal.146
#147
# What is NOT true, and the plan said it was: GNU's `--forward` does not exit 0148
# on a re-apply. Both implementations exit 1 there (GNU: "Reversed (or149
# previously applied) patch detected! Skipping patch"). Neither corrupts the150
# file. §8 Q4's supporting argument 2 is falsified; argument 1 above is what151
# the decision rests on.152
tar -C "$work" -xf "$seed/sources/patch-2.8.tar.xz"153
(154
cd "$work/patch-2.8"155
CC="$cc" AR="$ar" RANLIB="$ranlib" LD="$cc" CFLAGS="$map_flags" ./configure \156
--disable-nls --disable-dependency-tracking LDFLAGS="-static -s"157
"$make" -j"$jobs"158
cp src/patch "$seed/out/bin/patch"159
)161
ln -s pkgconf "$seed/out/bin/pkg-config"162
# APPLET TRIM. Network clients and servers have no business in a signed build163
# environment: nothing in a build phase legitimately fetches or listens, every164
# input arrives through the store, and the builder runs in a network namespace165
# with loopback only. With netns in place (P1.1) this is defence-in-depth, not166
# the thing keeping the network out -- say that rather than overselling it.167
#168
# `patch` is excluded for a different reason: GNU patch above owns that name.169
trim="wget nc telnetd httpd sendmail ftpd tftp"170
"$seed/out/bin/busybox" --list | while IFS= read -r applet; do171
case "$applet" in busybox|make|pkgconf|pkg-config|patch|'['|'[[') continue ;; esac172
case " $trim " in *" $applet "*) continue ;; esac173
ln -s busybox "$seed/out/bin/$applet"174
done175
# A trim that trims nothing is the same silent-success shape as a sed that176
# matches nothing. Verify, in BOTH directions -- what must be gone is gone, and177
# what must remain remains. A one-directional check would pass just as happily178
# on a seed containing nothing at all.179
#180
# Written as `if`, not `[ ... ] && { ... }`: a refusal must not depend on the181
# subtle `set -e` semantics of a failing AND-OR list.182
for applet in $trim; do183
if [ -e "$seed/out/bin/$applet" ]; then184
echo "build-seed: REFUSING: trimmed applet $applet is still present" >&2185
exit 1186
fi187
done188
for applet in sh tar patch pkgconf make; do189
if [ ! -e "$seed/out/bin/$applet" ]; then190
echo "build-seed: REFUSING: required tool $applet is missing from the seed" >&2191
exit 1192
fi193
done194
# The builder assembles its sandbox with these; a seed that lost one would look195
# to the builder like a host that forbids user namespaces (see external.sgl196
# require-sandbox-tools!), so refuse here where the cause is visible.197
for applet in unshare mount chroot ip mkdir ln touch env; do198
if [ ! -e "$seed/out/bin/$applet" ]; then199
echo "build-seed: REFUSING: sandbox tool $applet is missing from the seed" >&2200
exit 1201
fi202
done204
"$seed/scripts/package-seed.sh"205
"$seed/out/bin/busybox" echo "seed binaries built for static musl target"