From f4a152cf77a5ff7514a345450d766b09fb2ce445 Mon Sep 17 00:00:00 2001 From: Simon de Haan Date: Mon, 1 Jun 2026 15:07:33 +0200 Subject: [PATCH 1/2] Pinning build_deps.sh against the SHA of the release, not blindly trusting the tagged release as that's mutable. --- README.md | 8 +++++++- build_deps.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index fd460ae..13b951a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,13 @@ ## [Zstd][1] binding for Erlang -This binding is based on zstd v1.5.7. In case you want to modify the `zstd` version you can change `ZSTD_TAG` from `build_deps.sh` +This binding is based on zstd v1.5.7. The build is pinned to an immutable commit SHA (not just the tag) to protect against supply-chain attacks where a mutable tag is re-pointed at a malicious commit. In case you want to modify the `zstd` version, update **both** `ZSTD_TAG` and `ZSTD_SHA` in `build_deps.sh`. You can find the commit a tag resolves to with: + +```sh +git ls-remote https://github.com/facebook/zstd.git 'v1.5.7^{}' +``` + +`build_deps.sh` checks out `ZSTD_SHA` directly and will refuse to build if the tag no longer resolves to the pinned commit. ## API diff --git a/build_deps.sh b/build_deps.sh index 90751cc..0a83723 100755 --- a/build_deps.sh +++ b/build_deps.sh @@ -12,7 +12,14 @@ CPUS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)" ZSTD_REPO="https://github.com/facebook/zstd.git" ZSTD_BRANCH="release" +# Tags are mutable: upstream could re-point v1.5.7 at a malicious commit and we +# would build it unknowingly. The pinned commit SHA below is the source of +# truth for what we actually build; ZSTD_TAG is kept only for readability and +# is verified against the SHA before building (see checkout_lib). +# ZSTD_SHA is the commit that v1.5.7 currently resolves to: +# git ls-remote https://github.com/facebook/zstd.git 'v1.5.7^{}' ZSTD_TAG="v1.5.7" +ZSTD_SHA="f8745da6ff1ad1e7bab384bd1f9d742439278e99" ZSTD_DIR="zstd" ZSTD_SUCCESS_FILE="lib/libzstd.a" @@ -27,10 +34,11 @@ fail_check() { checkout_lib() { local repo_url="$1" - local tag="$2" - local branch="$3" - local dir_name="$4" - local success_file="$5" + local sha="$2" + local tag="$3" + local branch="$4" + local dir_name="$5" + local success_file="$6" local full_path="$DEPS_DIR/$dir_name/$success_file" if [ -f "$full_path" ]; then @@ -39,7 +47,7 @@ checkout_lib() { return fi - echo "📦 Cloning $repo_url (branch: $branch, tag: $tag)" + echo "📦 Cloning $repo_url (branch: $branch, pinned commit: $sha)" mkdir -p "$DEPS_DIR" pushd "$DEPS_DIR" > /dev/null @@ -49,7 +57,42 @@ checkout_lib() { fi pushd "$dir_name" > /dev/null - fail_check git checkout "$tag" + + # Make sure the exact pinned commit is present locally. Cloning the branch + # normally fetches it (it is reachable from the release tag), but fetch it + # explicitly as a fallback so we never silently fall back to whatever the + # branch currently happens to point at. + if ! git cat-file -e "${sha}^{commit}" 2>/dev/null; then + fail_check git fetch origin "$sha" + fi + + # Supply-chain safety: tags are mutable. If the human-readable tag is + # present, confirm it still resolves to the commit we pinned. A mismatch + # means upstream moved the tag (re-tagged) and we must refuse to build. + local tag_sha + tag_sha="$(git rev-list -n 1 "$tag" 2>/dev/null || true)" + if [ -n "$tag_sha" ] && [ "$tag_sha" != "$sha" ]; then + echo "❌ Supply-chain check failed for $dir_name" >&2 + echo " Tag '$tag' now resolves to $tag_sha" >&2 + echo " but the build is pinned to $sha" >&2 + echo " The upstream tag appears to have been moved. Refusing to build." >&2 + echo " If this change is expected, update ZSTD_SHA in build_deps.sh." >&2 + exit 1 + fi + + # Check out the pinned commit by SHA (detached HEAD) rather than the tag, + # so the build is reproducible and independent of tag mutations. + fail_check git checkout --quiet "$sha" + + # Belt and braces: confirm HEAD really is the pinned commit. + local head_sha + head_sha="$(git rev-parse HEAD)" + if [ "$head_sha" != "$sha" ]; then + echo "❌ Expected HEAD to be $sha but got $head_sha" >&2 + exit 1 + fi + echo "🔒 Checked out pinned commit $sha ($tag)" + build_library "$dir_name" popd > /dev/null popd > /dev/null @@ -84,4 +127,4 @@ echo " ➤ OS Type : $OS" echo " ➤ OS Name : $KERNEL" echo " ➤ CPU Cores : $CPUS" -checkout_lib "$ZSTD_REPO" "$ZSTD_TAG" "$ZSTD_BRANCH" "$ZSTD_DIR" "$ZSTD_SUCCESS_FILE" +checkout_lib "$ZSTD_REPO" "$ZSTD_SHA" "$ZSTD_TAG" "$ZSTD_BRANCH" "$ZSTD_DIR" "$ZSTD_SUCCESS_FILE" From a6f9e86ed1bf99020ad181105130a23f1d984f3a Mon Sep 17 00:00:00 2001 From: Simon de Haan Date: Tue, 14 Jul 2026 12:58:51 +0000 Subject: [PATCH 2/2] Simplify SHA pinning per upstream review (silviucpp/ezstd#26) Adopt the maintainer's minimal structure: drop ZSTD_BRANCH and ZSTD_TAG for a single immutable ZSTD_SHA, clone with --no-tags so no mutable tag refs land on disk, and check out the pinned commit by SHA. Removes the extra tag-verification/HEAD checks the maintainer asked to drop. Pin stays on the real v1.5.7 commit f8745da6ff1ad1e7bab384bd1f9d742439278e99 (the upstream comment's 794ea1b0... is the v1.5.7 merge's first parent, i.e. v1.5.6). A plain clone fetches all branches, so the release commit is present for checkout without needing --branch. --- README.md | 4 +--- build_deps.sh | 63 ++++++++++----------------------------------------- 2 files changed, 13 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 13b951a..1381434 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,12 @@ ## [Zstd][1] binding for Erlang -This binding is based on zstd v1.5.7. The build is pinned to an immutable commit SHA (not just the tag) to protect against supply-chain attacks where a mutable tag is re-pointed at a malicious commit. In case you want to modify the `zstd` version, update **both** `ZSTD_TAG` and `ZSTD_SHA` in `build_deps.sh`. You can find the commit a tag resolves to with: +This binding is based on zstd v1.5.7. The build is pinned to an immutable commit SHA (`ZSTD_SHA` in `build_deps.sh`) rather than a mutable git tag, to protect against supply-chain attacks where a tag is re-pointed at a different commit upstream. To change the `zstd` version, update `ZSTD_SHA` (the `# vX.Y.Z` comment next to it is just for humans). You can find the commit a tag resolves to with: ```sh git ls-remote https://github.com/facebook/zstd.git 'v1.5.7^{}' ``` -`build_deps.sh` checks out `ZSTD_SHA` directly and will refuse to build if the tag no longer resolves to the pinned commit. - ## API ### Compress and decompress diff --git a/build_deps.sh b/build_deps.sh index 0a83723..d3d630a 100755 --- a/build_deps.sh +++ b/build_deps.sh @@ -11,15 +11,11 @@ CPUS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)" # https://github.com/facebook/zstd.git ZSTD_REPO="https://github.com/facebook/zstd.git" -ZSTD_BRANCH="release" -# Tags are mutable: upstream could re-point v1.5.7 at a malicious commit and we -# would build it unknowingly. The pinned commit SHA below is the source of -# truth for what we actually build; ZSTD_TAG is kept only for readability and -# is verified against the SHA before building (see checkout_lib). -# ZSTD_SHA is the commit that v1.5.7 currently resolves to: +# Pin to an immutable commit SHA instead of a mutable tag: tags can be +# re-pointed at a different commit upstream, so trusting the tag alone is a +# supply-chain risk. Resolve a tag to its commit with: # git ls-remote https://github.com/facebook/zstd.git 'v1.5.7^{}' -ZSTD_TAG="v1.5.7" -ZSTD_SHA="f8745da6ff1ad1e7bab384bd1f9d742439278e99" +ZSTD_SHA="f8745da6ff1ad1e7bab384bd1f9d742439278e99" # v1.5.7 ZSTD_DIR="zstd" ZSTD_SUCCESS_FILE="lib/libzstd.a" @@ -35,10 +31,8 @@ fail_check() { checkout_lib() { local repo_url="$1" local sha="$2" - local tag="$3" - local branch="$4" - local dir_name="$5" - local success_file="$6" + local dir_name="$3" + local success_file="$4" local full_path="$DEPS_DIR/$dir_name/$success_file" if [ -f "$full_path" ]; then @@ -47,52 +41,19 @@ checkout_lib() { return fi - echo "📦 Cloning $repo_url (branch: $branch, pinned commit: $sha)" + echo "📦 Cloning $repo_url (pinned commit: $sha)" mkdir -p "$DEPS_DIR" pushd "$DEPS_DIR" > /dev/null if [ ! -d "$dir_name" ]; then - fail_check git clone --branch "$branch" "$repo_url" "$dir_name" + # --no-tags keeps mutable tag refs off disk; we check out an immutable + # commit by SHA, so tags are never consulted or trusted. + fail_check git clone --no-tags "$repo_url" "$dir_name" fi pushd "$dir_name" > /dev/null - - # Make sure the exact pinned commit is present locally. Cloning the branch - # normally fetches it (it is reachable from the release tag), but fetch it - # explicitly as a fallback so we never silently fall back to whatever the - # branch currently happens to point at. - if ! git cat-file -e "${sha}^{commit}" 2>/dev/null; then - fail_check git fetch origin "$sha" - fi - - # Supply-chain safety: tags are mutable. If the human-readable tag is - # present, confirm it still resolves to the commit we pinned. A mismatch - # means upstream moved the tag (re-tagged) and we must refuse to build. - local tag_sha - tag_sha="$(git rev-list -n 1 "$tag" 2>/dev/null || true)" - if [ -n "$tag_sha" ] && [ "$tag_sha" != "$sha" ]; then - echo "❌ Supply-chain check failed for $dir_name" >&2 - echo " Tag '$tag' now resolves to $tag_sha" >&2 - echo " but the build is pinned to $sha" >&2 - echo " The upstream tag appears to have been moved. Refusing to build." >&2 - echo " If this change is expected, update ZSTD_SHA in build_deps.sh." >&2 - exit 1 - fi - - # Check out the pinned commit by SHA (detached HEAD) rather than the tag, - # so the build is reproducible and independent of tag mutations. - fail_check git checkout --quiet "$sha" - - # Belt and braces: confirm HEAD really is the pinned commit. - local head_sha - head_sha="$(git rev-parse HEAD)" - if [ "$head_sha" != "$sha" ]; then - echo "❌ Expected HEAD to be $sha but got $head_sha" >&2 - exit 1 - fi - echo "🔒 Checked out pinned commit $sha ($tag)" - + fail_check git checkout "$sha" build_library "$dir_name" popd > /dev/null popd > /dev/null @@ -127,4 +88,4 @@ echo " ➤ OS Type : $OS" echo " ➤ OS Name : $KERNEL" echo " ➤ CPU Cores : $CPUS" -checkout_lib "$ZSTD_REPO" "$ZSTD_SHA" "$ZSTD_TAG" "$ZSTD_BRANCH" "$ZSTD_DIR" "$ZSTD_SUCCESS_FILE" +checkout_lib "$ZSTD_REPO" "$ZSTD_SHA" "$ZSTD_DIR" "$ZSTD_SUCCESS_FILE"