findings X-1: pin every dependency tarball fetch to a SHA256 and enforce it
security-audit-v3 #15. download_file already had a verify branch; no caller used it and every *_SHA256 in versions.sh was a commented placeholder, so a tampered mirror tarball flowed straight into configure/make and the shipped WASM. - versions.sh: 13 pins (cross-checked against Homebrew/Buildroot/nixpkgs/ FreeBSD/vcpkg/boost.org/curl PGP; glm .zip is TOFU), boost/curl/libgit2 versions moved beside their pins. - all 13 download_file call sites pass "${NAME_SHA256}". - download_file refuses an empty or malformed pin (PCBJAM_ALLOW_UNPINNED=1 to bootstrap a new dep); file_sha256 prefers sha256sum, falls back to shasum. - scripts/deps/check-pins.sh: static 3-arg check + offline file:// enforcement test; runs in wasm-build.yml before the deps cache, on cache hits too. Expect one cold --build-deps run: the deps-cache key hashes versions.sh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GcsgJZ77bhZatLAVU8R84H
This commit is contained in:
parent
5ce919daa6
commit
f6b0aaf122
16 changed files with 157 additions and 22 deletions
|
|
@ -48,7 +48,7 @@ if [ ! -d "${BOOST_DIR}" ]; then
|
|||
BOOST_ARCHIVE="boost_${BOOST_VERSION_UNDERSCORE}.tar.gz"
|
||||
|
||||
# Download and extract
|
||||
download_file "${BOOST_URL}" "${BOOST_ARCHIVE}"
|
||||
download_file "${BOOST_URL}" "${BOOST_ARCHIVE}" "${BOOST_SHA256}"
|
||||
tar -xzf "${BOOST_ARCHIVE}"
|
||||
rm "${BOOST_ARCHIVE}"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ if [ ! -d "${CAIRO_DIR}" ]; then
|
|||
cd "${DEPS_ROOT}"
|
||||
|
||||
CAIRO_URL="https://cairographics.org/releases/cairo-${CAIRO_VERSION}.tar.xz"
|
||||
download_file "${CAIRO_URL}" "cairo-${CAIRO_VERSION}.tar.xz"
|
||||
download_file "${CAIRO_URL}" "cairo-${CAIRO_VERSION}.tar.xz" "${CAIRO_SHA256}"
|
||||
tar -xJf "cairo-${CAIRO_VERSION}.tar.xz"
|
||||
rm "cairo-${CAIRO_VERSION}.tar.xz"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ if [ ! -d "${CURL_DIR}" ]; then
|
|||
|
||||
# Download from curl official releases
|
||||
CURL_URL="https://curl.se/download/curl-${CURL_VERSION}.tar.gz"
|
||||
download_file "${CURL_URL}" "curl-${CURL_VERSION}.tar.gz"
|
||||
download_file "${CURL_URL}" "curl-${CURL_VERSION}.tar.gz" "${CURL_SHA256}"
|
||||
tar -xzf "curl-${CURL_VERSION}.tar.gz"
|
||||
rm "curl-${CURL_VERSION}.tar.gz"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ if [ ! -d "${FREETYPE_DIR}" ]; then
|
|||
cd "${DEPS_ROOT}"
|
||||
|
||||
FREETYPE_URL="https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
download_file "${FREETYPE_URL}" "freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
download_file "${FREETYPE_URL}" "freetype-${FREETYPE_VERSION}.tar.xz" "${FREETYPE_SHA256}"
|
||||
tar -xJf "freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
rm "freetype-${FREETYPE_VERSION}.tar.xz"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ if [ ! -d "${GLM_DIR}" ]; then
|
|||
cd "${DEPS_ROOT}"
|
||||
|
||||
GLM_URL="https://github.com/g-truc/glm/releases/download/${GLM_VERSION}/glm-${GLM_VERSION}.zip"
|
||||
download_file "${GLM_URL}" "glm-${GLM_VERSION}.zip"
|
||||
download_file "${GLM_URL}" "glm-${GLM_VERSION}.zip" "${GLM_SHA256}"
|
||||
unzip -q "glm-${GLM_VERSION}.zip"
|
||||
mv glm "glm-${GLM_VERSION}"
|
||||
rm "glm-${GLM_VERSION}.zip"
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ if [ ! -d "${HARFBUZZ_DIR}" ]; then
|
|||
cd "${DEPS_ROOT}"
|
||||
|
||||
HARFBUZZ_URL="https://github.com/harfbuzz/harfbuzz/releases/download/${HARFBUZZ_VERSION}/harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
download_file "${HARFBUZZ_URL}" "harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
download_file "${HARFBUZZ_URL}" "harfbuzz-${HARFBUZZ_VERSION}.tar.xz" "${HARFBUZZ_SHA256}"
|
||||
tar -xJf "harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
rm "harfbuzz-${HARFBUZZ_VERSION}.tar.xz"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ if [ ! -d "${LIBGIT2_DIR}" ]; then
|
|||
cd "${DEPS_ROOT}"
|
||||
|
||||
LIBGIT2_URL="https://github.com/libgit2/libgit2/archive/refs/tags/v${LIBGIT2_VERSION}.tar.gz"
|
||||
download_file "${LIBGIT2_URL}" "libgit2-${LIBGIT2_VERSION}.tar.gz"
|
||||
download_file "${LIBGIT2_URL}" "libgit2-${LIBGIT2_VERSION}.tar.gz" "${LIBGIT2_SHA256}"
|
||||
tar -xzf "libgit2-${LIBGIT2_VERSION}.tar.gz"
|
||||
rm "libgit2-${LIBGIT2_VERSION}.tar.gz"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ if [ ! -d "${NGSPICE_DIR}" ]; then
|
|||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
|
||||
download_file "${NGSPICE_URL}" "ngspice-${NGSPICE_VERSION}.tar.gz"
|
||||
download_file "${NGSPICE_URL}" "ngspice-${NGSPICE_VERSION}.tar.gz" "${NGSPICE_SHA256}"
|
||||
tar -xzf "ngspice-${NGSPICE_VERSION}.tar.gz"
|
||||
rm "ngspice-${NGSPICE_VERSION}.tar.gz"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ if [ ! -d "${OCC_DIR}" ]; then
|
|||
|
||||
# OpenCASCADE releases are on GitHub
|
||||
OCC_URL="https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V${OCC_VERSION//./_}.tar.gz"
|
||||
download_file "${OCC_URL}" "opencascade-${OCC_VERSION}.tar.gz"
|
||||
download_file "${OCC_URL}" "opencascade-${OCC_VERSION}.tar.gz" "${OCC_SHA256}"
|
||||
tar -xzf "opencascade-${OCC_VERSION}.tar.gz"
|
||||
# Directory name in tarball is OCCT-7_8_0 (without V prefix)
|
||||
mv "OCCT-${OCC_VERSION//./_}" "opencascade-${OCC_VERSION}"
|
||||
|
|
@ -60,7 +60,7 @@ if [ ! -d "${RAPIDJSON_DIR}" ]; then
|
|||
log_info "Downloading RapidJSON ${RAPIDJSON_VERSION} (master snapshot ${RAPIDJSON_COMMIT:0:12})..."
|
||||
mkdir -p "${DEPS_ROOT}"
|
||||
cd "${DEPS_ROOT}"
|
||||
download_file "${RAPIDJSON_URL}" "rapidjson-${RAPIDJSON_VERSION}.tar.gz"
|
||||
download_file "${RAPIDJSON_URL}" "rapidjson-${RAPIDJSON_VERSION}.tar.gz" "${RAPIDJSON_SHA256}"
|
||||
tar -xzf "rapidjson-${RAPIDJSON_VERSION}.tar.gz"
|
||||
mv "rapidjson-${RAPIDJSON_COMMIT}" "rapidjson-${RAPIDJSON_VERSION}"
|
||||
rm "rapidjson-${RAPIDJSON_VERSION}.tar.gz"
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ if [ ! -d "${PIXMAN_DIR}" ]; then
|
|||
cd "${DEPS_ROOT}"
|
||||
|
||||
PIXMAN_URL="https://cairographics.org/releases/pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
download_file "${PIXMAN_URL}" "pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
download_file "${PIXMAN_URL}" "pixman-${PIXMAN_VERSION}.tar.gz" "${PIXMAN_SHA256}"
|
||||
tar -xzf "pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
rm "pixman-${PIXMAN_VERSION}.tar.gz"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ if [ ! -d "${PROTOBUF_DIR}" ]; then
|
|||
# Protobuf 3.21.x uses tag format v21.12 (major version 3 is implicit)
|
||||
PROTOBUF_TAG_VERSION="${PROTOBUF_VERSION#3.}" # Strip leading "3." -> "21.12"
|
||||
PROTOBUF_URL="https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_TAG_VERSION}/protobuf-cpp-${PROTOBUF_VERSION}.tar.gz"
|
||||
download_file "${PROTOBUF_URL}" "protobuf-${PROTOBUF_VERSION}.tar.gz"
|
||||
download_file "${PROTOBUF_URL}" "protobuf-${PROTOBUF_VERSION}.tar.gz" "${PROTOBUF_SHA256}"
|
||||
tar -xzf "protobuf-${PROTOBUF_VERSION}.tar.gz"
|
||||
rm "protobuf-${PROTOBUF_VERSION}.tar.gz"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ if [ ! -d "${ZSTD_DIR}" ]; then
|
|||
cd "${DEPS_ROOT}"
|
||||
|
||||
ZSTD_URL="https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION}/zstd-${ZSTD_VERSION}.tar.gz"
|
||||
download_file "${ZSTD_URL}" "zstd-${ZSTD_VERSION}.tar.gz"
|
||||
download_file "${ZSTD_URL}" "zstd-${ZSTD_VERSION}.tar.gz" "${ZSTD_SHA256}"
|
||||
tar -xzf "zstd-${ZSTD_VERSION}.tar.gz"
|
||||
rm "zstd-${ZSTD_VERSION}.tar.gz"
|
||||
fi
|
||||
|
|
|
|||
82
scripts/deps/check-pins.sh
Executable file
82
scripts/deps/check-pins.sh
Executable file
|
|
@ -0,0 +1,82 @@
|
|||
#!/bin/bash
|
||||
# Oracle for X-1 (security-audit-v3 #15): every dependency tarball fetch is
|
||||
# pinned to a SHA256, and download_file actually enforces the pin.
|
||||
#
|
||||
# 1. static — every download_file call in scripts/deps/*.sh passes a third
|
||||
# argument that resolves to a 64-hex value from versions.sh.
|
||||
# 2. dynamic — download_file against a file:// URL (fully offline): a wrong
|
||||
# pin fails and removes the file, the right pin succeeds, and an
|
||||
# empty pin is refused unless PCBJAM_ALLOW_UNPINNED=1.
|
||||
#
|
||||
# Runs in seconds with no docker; wired into wasm-build.yml before the deps
|
||||
# step and usable locally: scripts/deps/check-pins.sh
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||
|
||||
fail=0
|
||||
ok() { echo " ok $*"; }
|
||||
bad() { echo " FAIL $*"; fail=1; }
|
||||
|
||||
echo "[check-pins] static: download_file call sites"
|
||||
while IFS= read -r line; do
|
||||
file="${line%%:*}"; rest="${line#*:}"; lineno="${rest%%:*}"; call="${rest#*:}"
|
||||
# third argument must be a "${NAME_SHA256}" reference
|
||||
if [[ "$call" =~ download_file[[:space:]]+\"[^\"]*\"[[:space:]]+\"[^\"]*\"[[:space:]]+\"\$\{([A-Z0-9_]+_SHA256)\}\" ]]; then
|
||||
var="${BASH_REMATCH[1]}"
|
||||
val="${!var:-}"
|
||||
if printf '%s' "$val" | grep -Eq '^[0-9a-f]{64}$'; then
|
||||
ok "$(basename "$file"):$lineno -> $var"
|
||||
else
|
||||
bad "$(basename "$file"):$lineno -> $var is not a 64-hex pin in versions.sh ('$val')"
|
||||
fi
|
||||
else
|
||||
bad "$(basename "$file"):$lineno has no \"\${NAME_SHA256}\" third argument: $call"
|
||||
fi
|
||||
done < <(grep -n '^[[:space:]]*download_file ' "${SCRIPT_DIR}"/build-*.sh)
|
||||
|
||||
echo "[check-pins] dynamic: download_file enforces the pin (file:// URL)"
|
||||
tmp="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
printf 'pcbjam pin oracle\n' > "$tmp/src.txt"
|
||||
tar -czf "$tmp/src.tar.gz" -C "$tmp" src.txt
|
||||
good="$(file_sha256 "$tmp/src.tar.gz")"
|
||||
wrong="$(printf '%064d' 1)"
|
||||
url="file://$tmp/src.tar.gz"
|
||||
|
||||
if download_file "$url" "$tmp/d1.tar.gz" "$wrong" >/dev/null 2>&1; then
|
||||
bad "wrong pin was accepted"
|
||||
elif [ -e "$tmp/d1.tar.gz" ]; then
|
||||
bad "wrong pin: mismatched file left on disk"
|
||||
else
|
||||
ok "wrong pin rejected and file removed"
|
||||
fi
|
||||
|
||||
if download_file "$url" "$tmp/d2.tar.gz" "$good" >/dev/null 2>&1 && [ -f "$tmp/d2.tar.gz" ]; then
|
||||
ok "right pin accepted"
|
||||
else
|
||||
bad "right pin rejected"
|
||||
fi
|
||||
|
||||
if PCBJAM_ALLOW_UNPINNED=0 download_file "$url" "$tmp/d3.tar.gz" >/dev/null 2>&1; then
|
||||
bad "empty pin accepted without PCBJAM_ALLOW_UNPINNED=1"
|
||||
else
|
||||
ok "empty pin refused"
|
||||
fi
|
||||
|
||||
if PCBJAM_ALLOW_UNPINNED=1 download_file "$url" "$tmp/d4.tar.gz" >/dev/null 2>&1; then
|
||||
ok "empty pin allowed with PCBJAM_ALLOW_UNPINNED=1 (bootstrap)"
|
||||
else
|
||||
bad "bootstrap escape hatch broken"
|
||||
fi
|
||||
|
||||
if download_file "$url" "$tmp/d5.tar.gz" "not-a-hash" >/dev/null 2>&1; then
|
||||
bad "malformed pin accepted"
|
||||
else
|
||||
ok "malformed pin refused"
|
||||
fi
|
||||
|
||||
if [ $fail -ne 0 ]; then echo "[check-pins] FAILED"; exit 1; fi
|
||||
echo "[check-pins] all good"
|
||||
Loading…
Reference in a new issue