mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-03 07:58:25 +08:00
The opt-in glibc-static supervisor variant from #2682 is gone: the Nix release builds (#2977) removed it from CI and the supervisor Dockerfile, and the RFC 0012 sandbox split (#2942) removed it from the staging script. The split also moved the binary that runs inside workload images to openshell-sandbox; the supervisor now runs from its own image and is dynamically linked. A few places still describe the old model: - skills/debug-openshell-cluster told operators to check that supervisor_image contains a static /openshell-supervisor. Ask instead for a supervisor from the matching release whose loader and shared libraries are available inside that image, and give a `--version` check that shows both. The static requirement for /openshell-sandbox is unchanged. - verify-static-binary.sh justified its check with the supervisor and the glibc-static variant. Describe the property it enforces instead, with the musl sandbox runtime as the main example. - stage-prebuilt-binaries.sh kept an unreachable gnu-static case in target_triple. No behavior change: resolve_component only ever selects gnu or musl. Signed-off-by: Emilien Macchi <emacchi@redhat.com>
215 lines
8.9 KiB
Bash
Executable File
215 lines
8.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
set -euo pipefail
|
|
|
|
# Verify a binary is a genuine, complete, fully static executable.
|
|
#
|
|
# Callers pass binaries that must run without a dynamic loader, such as the
|
|
# musl sandbox runtime (openshell-sandbox). It is executed from inside
|
|
# arbitrary workload images (Docker extraction, Podman image volumes, the
|
|
# Kubernetes copy-self path), so any dynamic linkage breaks it on musl-based
|
|
# images and on images whose glibc is older than the build host's. Other
|
|
# callers include the release prover and e2e fixtures.
|
|
#
|
|
# This check exists because the failure is silent: `zig cc` accepts `-static`
|
|
# for `*-linux-gnu` targets and emits a dynamically linked binary anyway, so a
|
|
# toolchain change can quietly downgrade linkage without failing the build.
|
|
#
|
|
# The verifier must also fail closed on malformed input; readelf can exit 0 on a
|
|
# damaged ELF, which naive parsing would read as "no interpreter, no
|
|
# dependencies". The checks below therefore require, for each binary:
|
|
# * an executable ELF: ET_EXEC (classic static) or ET_DYN (static-PIE);
|
|
# * a fully readable ELF header, program header table, and dynamic section
|
|
# (readelf must exit 0 AND emit no diagnostics — see readelf_strict);
|
|
# * at least one PT_LOAD segment, every PT_LOAD contained within the file
|
|
# (catches truncation that readelf does not otherwise report);
|
|
# * no PT_INTERP and no DT_NEEDED (the actual static-linkage properties);
|
|
# * for ET_DYN, the DF_1_PIE flag, which a static-PIE executable sets and a
|
|
# shared library does not.
|
|
#
|
|
# Accepts both classic static and static-PIE binaries. static-PIE keeps a
|
|
# PT_DYNAMIC segment for self-relocation, so linkage is judged by the absence of
|
|
# PT_INTERP and DT_NEEDED, not by the absence of a dynamic section.
|
|
|
|
usage() {
|
|
echo "Usage: verify-static-binary.sh <binary> [binary ...]" >&2
|
|
}
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
usage
|
|
exit 2
|
|
fi
|
|
|
|
# Resolve a readelf-compatible inspector. macOS ships none of these; Homebrew
|
|
# binutils provides `greadelf` and LLVM provides `llvm-readelf`, both of which
|
|
# emit GNU-style output this script parses. Prefer GNU readelf, then greadelf,
|
|
# then llvm-readelf.
|
|
READELF=""
|
|
for candidate in readelf greadelf llvm-readelf; do
|
|
if command -v "$candidate" >/dev/null 2>&1; then
|
|
READELF=$candidate
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ -z $READELF ]]; then
|
|
host_os=""
|
|
command -v uname >/dev/null 2>&1 && host_os=$(uname -s 2>/dev/null || true)
|
|
# Skip only on a host positively identified as non-Linux — e.g. a macOS dev
|
|
# cross-building a Linux musl binary via cargo-zigbuild, where mise installs
|
|
# no binutils. Linux (including CI), or any host whose OS cannot be determined,
|
|
# fails closed so a missing inspector never silently passes. Static linkage is
|
|
# still enforced in CI, which runs on Linux.
|
|
if [[ "$host_os" == "Linux" || -z $host_os ]]; then
|
|
echo "error: readelf (or greadelf/llvm-readelf) is required to inspect binary linkage" >&2
|
|
exit 2
|
|
fi
|
|
echo "warning: no readelf/greadelf/llvm-readelf found on ${host_os}; skipping static-linkage verification." >&2
|
|
echo " install GNU binutils (greadelf) or LLVM (llvm-readelf) to verify locally; CI enforces it on Linux." >&2
|
|
exit 0
|
|
fi
|
|
|
|
# Explicit template: BSD/macOS mktemp requires one, GNU mktemp accepts it.
|
|
readelf_err=$(mktemp "${TMPDIR:-/tmp}/verify-static-binary.XXXXXXXX")
|
|
trap 'rm -f "$readelf_err"' EXIT
|
|
|
|
# Run readelf and print its stdout. Fails (returns non-zero) if readelf exits
|
|
# non-zero OR writes anything to stderr, leaving the diagnostics in
|
|
# $readelf_err. readelf reports a truncated or corrupt ELF on stderr while still
|
|
# exiting 0, so the stderr check — not the exit code — is what makes a damaged
|
|
# file fail closed instead of reading as "no PT_INTERP, no DT_NEEDED".
|
|
readelf_strict() {
|
|
local out
|
|
out=$("$READELF" "$@" 2>"$readelf_err") || return 1
|
|
[[ -s "$readelf_err" ]] && return 1
|
|
printf '%s\n' "$out"
|
|
return 0
|
|
}
|
|
|
|
failed=0
|
|
|
|
for binary in "$@"; do
|
|
if [[ ! -f $binary ]]; then
|
|
echo "error: binary not found: $binary" >&2
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
echo "==> Inspecting $binary"
|
|
|
|
# llvm-readelf rejects the `--` end-of-options marker that GNU readelf accepts,
|
|
# so make a leading-dash path safe for either tool by prefixing "./" instead.
|
|
case "$binary" in
|
|
-*) scan_path="./$binary" ;;
|
|
*) scan_path="$binary" ;;
|
|
esac
|
|
|
|
if command -v file >/dev/null 2>&1; then
|
|
file "$scan_path" || true
|
|
fi
|
|
|
|
# The ELF header and the full program header table must be readable. A
|
|
# truncated or non-ELF file makes readelf emit a diagnostic, which fails here
|
|
# instead of being misread as a static binary.
|
|
if ! headers=$(readelf_strict --wide --file-header --program-headers "$scan_path"); then
|
|
echo "error: $binary: unable to read a complete ELF (truncated, malformed, or not an ELF)" >&2
|
|
sed 's/^/ /' "$readelf_err" >&2 || true
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
if grep -Eq '^[[:space:]]*Type:[[:space:]]+EXEC' <<<"$headers"; then
|
|
elf_type=EXEC
|
|
elif grep -Eq '^[[:space:]]*Type:[[:space:]]+DYN' <<<"$headers"; then
|
|
elf_type=DYN
|
|
else
|
|
echo "error: $binary is not an executable ELF (expected ET_EXEC or ET_DYN)" >&2
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
# Every runnable ELF has at least one PT_LOAD segment. Its absence means the
|
|
# program header table was truncated or the input is not a program image.
|
|
if ! grep -qw 'LOAD' <<<"$headers"; then
|
|
echo "error: $binary has no PT_LOAD segment; it is truncated or not an executable" >&2
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
# Every PT_LOAD must lie within the file. readelf can exit 0 with empty stderr
|
|
# on a file whose section headers were stripped even though a LOAD segment runs
|
|
# past EOF, so validate p_offset + p_filesz <= file size explicitly rather than
|
|
# trusting readelf to notice the truncation.
|
|
# wc -c is portable (GNU stat -c / BSD stat -f differ); arithmetic strips any
|
|
# leading whitespace BSD wc prints. The redirect also tolerates a '-' path.
|
|
file_size=$(( $(wc -c < "$binary") ))
|
|
load_past_eof=0
|
|
while read -r ph_type ph_off _ph_va _ph_pa ph_fsize _ph_rest; do
|
|
[[ "$ph_type" == "LOAD" ]] || continue
|
|
# ph_off and ph_fsize are hex (e.g. 0x6a8440); bash arithmetic parses 0x.
|
|
if (( ph_off + ph_fsize > file_size )); then
|
|
echo "error: $binary: PT_LOAD at ${ph_off} (filesz ${ph_fsize}) extends past end of file (${file_size} bytes); it is truncated" >&2
|
|
load_past_eof=1
|
|
fi
|
|
done <<<"$headers"
|
|
if (( load_past_eof )); then
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
if grep -qw 'INTERP' <<<"$headers"; then
|
|
echo "error: $binary has a program interpreter (PT_INTERP); it is dynamically linked" >&2
|
|
grep -w -A1 'INTERP' <<<"$headers" >&2 || true
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
# The dynamic section must also be fully readable. A classic static binary has
|
|
# none (readelf says so on stdout and exits cleanly, with no stderr); a
|
|
# static-PIE has one without any DT_NEEDED entries.
|
|
if ! dynamic=$(readelf_strict --wide --dynamic "$scan_path"); then
|
|
echo "error: $binary: unable to read the ELF dynamic section (truncated or malformed)" >&2
|
|
sed 's/^/ /' "$readelf_err" >&2 || true
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
# Anchor the dynamic table to PT_DYNAMIC. GNU readelf --dynamic reads the
|
|
# SHT_DYNAMIC *section*, whose file offset can be pointed away from the real
|
|
# PT_DYNAMIC *segment* to hide DT_NEEDED entries (llvm-readelf warns on this;
|
|
# GNU does not). Require the section offset readelf used to match the
|
|
# PT_DYNAMIC segment offset from the program headers; fail closed on any
|
|
# disagreement. Compare numerically so 0x0b1da8 and 0xb1da8 are equal.
|
|
dyn_seg_off=$(awk '$1 == "DYNAMIC" { print $2; exit }' <<<"$headers")
|
|
dyn_sec_off=$(grep -oE 'Dynamic section at offset 0x[0-9a-fA-F]+' <<<"$dynamic" | grep -oE '0x[0-9a-fA-F]+' | head -1 || true)
|
|
if [[ -n $dyn_seg_off || -n $dyn_sec_off ]]; then
|
|
if [[ -z $dyn_seg_off || -z $dyn_sec_off ]] || (( dyn_seg_off != dyn_sec_off )); then
|
|
echo "error: $binary: dynamic table location mismatch (PT_DYNAMIC ${dyn_seg_off:-none}, section ${dyn_sec_off:-none}); malformed or tampered" >&2
|
|
failed=1
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
if grep -qw 'NEEDED' <<<"$dynamic"; then
|
|
echo "error: $binary depends on shared libraries (DT_NEEDED); it is dynamically linked" >&2
|
|
grep -w 'NEEDED' <<<"$dynamic" >&2 || true
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
# An ET_DYN static-PIE executable sets DT_FLAGS_1 DF_1_PIE; a shared library
|
|
# (also ET_DYN, and possibly without PT_INTERP/DT_NEEDED) does not. Require the
|
|
# flag so a .so cannot pass as a static executable.
|
|
if [[ "$elf_type" == "DYN" ]] && ! grep -E '\(FLAGS_1\)' <<<"$dynamic" | grep -qw 'PIE'; then
|
|
echo "error: $binary is an ET_DYN object without DF_1_PIE; it looks like a shared library, not a static-PIE executable" >&2
|
|
failed=1
|
|
continue
|
|
fi
|
|
|
|
echo "statically linked: no PT_INTERP, no DT_NEEDED"
|
|
done
|
|
|
|
exit "$failed"
|