fix(installer): verify checksums on FreeBSD without coreutils

FreeBSD base ships sha256(1) but not GNU sha256sum or Perl shasum, so a
fresh pfSense/FreeBSD host without coreutils failed the download checksum
and refused to install. Route every installer checksum through
installer_file_sha256 and fall back through sha256sum, sha256 -q, shasum and
openssl dgst. install-mcp.sh gets the same sha256 branch. Add a focused test
that hides sha256sum/shasum and asserts the FreeBSD fallback returns the
digest.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot] 2026-09-20 02:43:57 +01:00
parent 838ca6f26d
commit 84b2787ba6
3 changed files with 54 additions and 6 deletions

View file

@ -146,10 +146,13 @@ Build from source: go install github.com/rcourtman/pulse-go-rewrite/cmd/pulse-mc
local sha_cmd
if command -v sha256sum >/dev/null 2>&1; then
sha_cmd="sha256sum"
elif command -v sha256 >/dev/null 2>&1; then
# FreeBSD base ships sha256(1) rather than GNU sha256sum.
sha_cmd="sha256 -q"
elif command -v shasum >/dev/null 2>&1; then
sha_cmd="shasum -a 256"
else
err "no sha256 tool found (sha256sum or shasum); refusing unverified install"
err "no sha256 tool found (sha256sum, sha256 or shasum); refusing unverified install"
fi
checksums_url="${base}/checksums.txt"

View file

@ -3075,8 +3075,20 @@ trusted_private_lifecycle_regular_file() {
}
installer_file_sha256() {
sha256sum "$1" 2>/dev/null | awk '{print $1}' ||
shasum -a 256 "$1" 2>/dev/null | awk '{print $1}'
# FreeBSD base ships neither GNU sha256sum nor Perl's shasum; it provides
# sha256(1) instead, which prints only the digest with -q. macOS and Linux
# provide sha256sum or shasum. Fall back through whichever exists so a
# pfSense/FreeBSD install without coreutils can still verify downloads.
local file="$1"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$file" 2>/dev/null | awk '{print $1}'
elif command -v sha256 >/dev/null 2>&1; then
sha256 -q "$file" 2>/dev/null | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$file" 2>/dev/null | awk '{print $1}'
elif command -v openssl >/dev/null 2>&1; then
openssl dgst -sha256 "$file" 2>/dev/null | awk '{print $NF}'
fi
}
sync_lifecycle_path() {
@ -6216,7 +6228,7 @@ download_verified_privileged_helper() {
if has_pinned_installer_signature_key && [[ -z "$helper_signature" ]]; then
fail "Typed privileged helper download omitted its signature; refusing install." "$EXIT_SIGNATURE_FAILED"
fi
helper_actual_sha=$(sha256sum "$TMP_HELPER_BIN" 2>/dev/null | awk '{print $1}' || shasum -a 256 "$TMP_HELPER_BIN" 2>/dev/null | awk '{print $1}')
helper_actual_sha=$(installer_file_sha256 "$TMP_HELPER_BIN")
if [[ -z "$helper_actual_sha" ]]; then
fail "Could not compute typed privileged helper checksum." "$EXIT_CHECKSUM_FAILED"
fi
@ -6262,7 +6274,7 @@ download_verified_action_runner() {
if has_pinned_installer_signature_key && [[ -z "$runner_signature" ]]; then
fail "Typed action runner download omitted its signature; refusing install." "$EXIT_SIGNATURE_FAILED"
fi
runner_actual_sha=$(sha256sum "$TMP_ACTION_RUNNER_BIN" 2>/dev/null | awk '{print $1}' || shasum -a 256 "$TMP_ACTION_RUNNER_BIN" 2>/dev/null | awk '{print $1}')
runner_actual_sha=$(installer_file_sha256 "$TMP_ACTION_RUNNER_BIN")
if [[ -z "$runner_actual_sha" || "$runner_actual_sha" != "$runner_expected_sha" ]]; then
fail "Typed action runner checksum verification failed." "$EXIT_CHECKSUM_FAILED"
fi
@ -6300,7 +6312,7 @@ if has_pinned_installer_signature_key && [[ -z "$SSH_SIGNATURE_HEADER" ]]; then
fail "Server did not provide SSH signature header; refusing signed install." "$EXIT_SIGNATURE_FAILED"
fi
ACTUAL_SHA=$(sha256sum "$TMP_BIN" 2>/dev/null | awk '{print $1}' || shasum -a 256 "$TMP_BIN" 2>/dev/null | awk '{print $1}')
ACTUAL_SHA=$(installer_file_sha256 "$TMP_BIN")
if [[ -z "$ACTUAL_SHA" ]]; then
fail "Could not compute binary checksum." "$EXIT_CHECKSUM_FAILED"
fi

View file

@ -4009,6 +4009,39 @@ func TestInstallSHSavedInstallerTamperAndUntrustedStateFailClosed(t *testing.T)
}
}
func TestInstallSHInstallerFileSHA256FallsBackToFreeBSDsha256(t *testing.T) {
contents := []byte("pulse-freebsd-checksum-fixture")
file := filepath.Join(t.TempDir(), "payload.bin")
if err := os.WriteFile(file, contents, 0600); err != nil {
t.Fatal(err)
}
want := fmt.Sprintf("%x", sha256.Sum256(contents))
// FreeBSD base provides sha256(1) but neither GNU sha256sum nor Perl's
// shasum. Hide those two and confirm the helper still returns the digest.
script := `
set -euo pipefail
command() {
if [[ "$1" == "-v" && ( "$2" == "sha256sum" || "$2" == "shasum" ) ]]; then
return 1
fi
builtin command "$@"
}
sha256() {
printf '%s\n' "` + want + `"
}
` + extractInstallShellFunction(t, "installer_file_sha256") + `
installer_file_sha256 "` + file + `"
`
out, err := exec.Command("bash", "-c", script).CombinedOutput()
if err != nil {
t.Fatalf("bash: %v\n%s", err, out)
}
if got := strings.TrimSpace(string(out)); got != want {
t.Fatalf("FreeBSD sha256 fallback = %q, want %q", got, want)
}
}
func TestInstallSHPrivilegedHelperStateRemovalRequiresExactLifecycleAuthority(t *testing.T) {
for _, tc := range []struct {
name string