igneum/tools/ci/glibc-ceiling-check.sh
igneum-labs 3720c40bce glibc ceilings per artefact class: hive and rig 2.31, seed and linux 2.35, native unchecked; proven in ubuntu:20.04 and 22.04 on the box
Main's order of 7 October 2026 after RunPod's Ubuntu 22.04 canaries (glibc 2.35) refused the box's GLIBC_2.38 binaries and HiveOS
turned out Ubuntu 20.04 based (2.31). The table lives once, in tools/ci/glibc-ceiling-check.sh (--class, --ceiling-of; self-test
covers the table, an unknown class and a 2.34 need against hive); tools/build-remote.sh --ship hive|rig|seed|linux (default seed)
and tools/workers-remote.sh --class (default rig) build with zig at the class's glibc and check against it. provision.sh installs
docker.io (user build in the docker group) for the proof. Proof: fork 3bfe346f at class hive, igneumd and igneum-miner need
GLIBC_2.30; the workers at class rig need GLIBC_2.17; all four run in ubuntu:20.04 (ldd 2.31) and ubuntu:22.04 (ldd 2.35) and
print their version or usage lines (the OpenCL worker its own no-libOpenCL message, the binary running in a GPU-less container).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 00:35:18 +00:00

54 lines
5.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# The glibc ceiling of a shipped Linux binary (main, 7 October 2026: a seed took 14 restarts and three minutes down on a binary
# built natively on Ubuntu 24.04, glibc 2.39, while Debian 12 seeds have 2.36 and HiveOS rigs less). Rule: anything that ships to
# a seed or a rig needs at most GLIBC_2.36; tools/build-remote.sh --ship and tools/workers-remote.sh build with zig for that and run
# this on every artefact they fetch. Reads the versioned symbol needs (`objdump -T`, or `nm -D` where objdump is absent) and compares
# the highest GLIBC_x.y with the ceiling.
#
# Classes (main, 7 October 2026, after RunPod's Ubuntu 22.04 canaries refused GLIBC_2.38 binaries and a 2.36 HiveOS package would
# not run on a real rig): the ceiling is per artefact class, in this one table, read with --class:
# hive, rig 2.31 HiveOS images are Ubuntu 20.04 based; the HiveOS package and anything for a rig
# seed, linux 2.35 Debian 12 seeds (2.36) and Ubuntu 22.04 hosts and containers (2.35); generic Linux
# native none the box and Ubuntu 24.04 hosts; a native build, not checked
#
# tools/ci/glibc-ceiling-check.sh <elf> [ceiling, default 2.36] exit 0 when the need is at or under the ceiling
# tools/ci/glibc-ceiling-check.sh --class <class> <elf> the ceiling from the class table above
# tools/ci/glibc-ceiling-check.sh --ceiling-of <class> print the class's ceiling (the build tools read it here)
# tools/ci/glibc-ceiling-check.sh --symbols <file> [ceiling] the same from a saved `objdump -T` text (the self-test, CI without ELF tools)
# tools/ci/glibc-ceiling-check.sh --self-test fires on a 2.38 need against 2.36, passes 2.34 against 2.36 and 2.36 against 2.36
set -euo pipefail
class_ceiling() { case "$1" in hive|rig) echo 2.31 ;; seed|linux) echo 2.35 ;; native) echo native ;; *) echo "glibc-ceiling: unknown class '$1' (hive, rig, seed, linux, native)" >&2; return 2 ;; esac; }
ceiling_of() { grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sed 's/GLIBC_//' | sort -t. -k1,1n -k2,2n | tail -1; }
le() { [ "$(printf '%s\n%s\n' "$1" "$2" | sort -t. -k1,1n -k2,2n | tail -1)" = "$2" ]; } # $1 <= $2 as glibc versions
judge() { # <need> <ceiling> <label>
local need="$1" max="$2" label="$3"
[ -n "$need" ] || { echo "glibc-ceiling: $label needs no versioned glibc symbol (static, or not an ELF): ok"; return 0; }
if le "$need" "$max"; then echo "glibc-ceiling: $label needs GLIBC_$need, ceiling $max: ok"; else echo "glibc-ceiling: $label needs GLIBC_$need, OVER the ceiling $max (a Debian 12 seed or a HiveOS rig refuses it)" >&2; return 1; fi
}
case "${1:-}" in
--self-test)
t=$(mktemp -d); trap 'rm -rf "$t"' EXIT
printf '0000 DF *UND* 0000 GLIBC_2.34 pthread_create\n0000 DF *UND* 0000 GLIBC_2.38 strlcpy\n0000 DF *UND* 0000 GLIBC_2.2.5 malloc\n' > "$t/high.txt"
printf '0000 DF *UND* 0000 GLIBC_2.34 pthread_create\n0000 DF *UND* 0000 GLIBC_2.2.5 malloc\n' > "$t/low.txt"
printf '0000 DF *UND* 0000 GLIBC_2.36 arc4random\n' > "$t/edge.txt"
"$0" --symbols "$t/high.txt" 2.36 >/dev/null 2>&1 && { echo "glibc-ceiling self-test: a 2.38 need PASSED against 2.36 (blind)"; exit 1; }
"$0" --symbols "$t/low.txt" 2.36 >/dev/null || { echo "glibc-ceiling self-test: a 2.34 need FAILED against 2.36"; exit 1; }
"$0" --symbols "$t/edge.txt" 2.36 >/dev/null || { echo "glibc-ceiling self-test: a 2.36 need FAILED against 2.36 (the ceiling is inclusive)"; exit 1; }
"$0" --symbols "$t/high.txt" 2.39 >/dev/null || { echo "glibc-ceiling self-test: a 2.38 need FAILED against 2.39"; exit 1; }
[ "$("$0" --ceiling-of hive)" = 2.31 ] && [ "$("$0" --ceiling-of rig)" = 2.31 ] && [ "$("$0" --ceiling-of seed)" = 2.35 ] && [ "$("$0" --ceiling-of linux)" = 2.35 ] && [ "$("$0" --ceiling-of native)" = native ] || { echo "glibc-ceiling self-test: the class table is wrong"; exit 1; }
"$0" --ceiling-of pods >/dev/null 2>&1 && { echo "glibc-ceiling self-test: an unknown class PASSED"; exit 1; }
"$0" --symbols "$t/low.txt" "$("$0" --ceiling-of hive)" >/dev/null 2>&1 && { echo "glibc-ceiling self-test: a 2.34 need PASSED the hive class (2.31)"; exit 1; }
echo "glibc-ceiling self-test: fires on 2.38 against 2.36 and 2.34 against hive (2.31); passes 2.34 and 2.36 against 2.36, 2.38 against 2.39; class table hive/rig 2.31, seed/linux 2.35, native unchecked"; exit 0 ;;
--symbols) judge "$(ceiling_of < "$2")" "${3:-2.36}" "$2" ;;
--ceiling-of) class_ceiling "$2" ;;
--class) c=$(class_ceiling "$2") || exit 2; [ "$c" = native ] && { echo "glibc-ceiling: class native, $(basename "$3") not checked"; exit 0; }; exec "$0" "$3" "$c" ;;
"") echo "usage: glibc-ceiling-check.sh <elf> [ceiling] | --symbols <file> [ceiling] | --self-test" >&2; exit 2 ;;
*)
f="$1"; max="${2:-2.36}"
[ -f "$f" ] || { echo "glibc-ceiling: no file $f" >&2; exit 2; }
if command -v objdump >/dev/null 2>&1 && objdump -T "$f" >/dev/null 2>&1; then need=$(objdump -T "$f" | ceiling_of)
elif command -v nm >/dev/null 2>&1; then need=$(nm -D "$f" 2>/dev/null | ceiling_of)
elif command -v strings >/dev/null 2>&1; then need=$(strings "$f" | ceiling_of) # the Mac: no ELF objdump; the version strings are in .dynstr
else echo "glibc-ceiling: no objdump, nm or strings to read $f" >&2; exit 2; fi
judge "$need" "$max" "$(basename "$f")" ;;
esac