diff --git a/scan/trustabl-scan.sh b/scan/trustabl-scan.sh index caa6314..ee0917f 100644 --- a/scan/trustabl-scan.sh +++ b/scan/trustabl-scan.sh @@ -61,7 +61,14 @@ if [ "$VER" = "latest" ]; then fi if [ -z "$VER" ] || [ "$VER" = "null" ]; then echo "Could not resolve trustabl version. Pin 'VERSION' to a tag, or set GITHUB_TOKEN." - exit 1 + exit 2 +fi +# One path segment only. A slash, '..', or URL metacharacter in a pin or in +# tag_name from /releases/latest would change +# /releases/download/${VER}/${ASSET} and the local -o filename. +if [[ "$VER" == */* || "$VER" == *\\* || "$VER" == *..* || "$VER" == *[[:space:]]* || "$VER" == *:* || "$VER" == *@* ]]; then + echo "Invalid trustabl version. Pin VERSION to a single release tag." + exit 2 fi echo "Trustabl version: $VER" @@ -70,12 +77,12 @@ VNUM="${VER#v}" case "$(uname -s)" in Linux) OS=linux ;; Darwin) OS=darwin ;; - *) echo "Unsupported OS $(uname -s)"; exit 1 ;; + *) echo "Unsupported OS $(uname -s)"; exit 2 ;; esac case "$(uname -m)" in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; - *) echo "Unsupported arch $(uname -m)"; exit 1 ;; + *) echo "Unsupported arch $(uname -m)"; exit 2 ;; esac ASSET="trustabl_${VNUM}_${OS}_${ARCH}.tar.gz" DEST="$(pwd)/.trustabl-bin" diff --git a/test/test-setup-exit.sh b/test/test-setup-exit.sh new file mode 100755 index 0000000..9521054 --- /dev/null +++ b/test/test-setup-exit.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Offline: unresolved or path-breaking VERSION is setup I/O, so exit 2. +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +STUB="$(mktemp -d)" +LOG="$(mktemp)" +trap 'rm -rf "$STUB" "$LOG"' EXIT + +run_scan() { + set +e + PATH="$STUB:$PATH" bash "$ROOT/scan/trustabl-scan.sh" >"$LOG" 2>&1 + rc=$? + set -e +} + +expect_exit_2() { + local why="$1" + [ "$rc" -eq 2 ] || { + echo "expected exit 2 ($why), got $rc" + cat "$LOG" + exit 1 + } +} + +# ---- unresolved latest ---- +cat > "$STUB/curl" <<'EOF' +#!/usr/bin/env bash +echo '{"tag_name":""}' +exit 0 +EOF +chmod +x "$STUB/curl" + +VERSION=latest run_scan +expect_exit_2 "unresolved VERSION" +echo "ok: unresolved VERSION exits 2" + +# ---- pin that would leave /releases/download/${VER}/ ---- +cat > "$STUB/curl" <<'EOF' +#!/usr/bin/env bash +echo "CURL_RAN $*" >&2 +exit 0 +EOF +chmod +x "$STUB/curl" + +VERSION='v1.0.0/../../../tmp/pwn' run_scan +expect_exit_2 "VERSION path breakout" +if grep -q CURL_RAN "$LOG"; then + echo "curl must not run when VERSION leaves the download path" + cat "$LOG" + exit 1 +fi +echo "ok: VERSION with slash exits 2 without fetching" + +# ---- tag_name from /releases/latest that would leave the path ---- +cat > "$STUB/curl" <<'EOF' +#!/usr/bin/env bash +if printf '%s' "$*" | grep -q 'releases/download'; then + echo "DOWNLOAD_RAN $*" >&2 + exit 0 +fi +echo '{"tag_name":"v1.0.0/../evil"}' +exit 0 +EOF +chmod +x "$STUB/curl" + +VERSION=latest run_scan +expect_exit_2 "latest tag_name path breakout" +if grep -q DOWNLOAD_RAN "$LOG"; then + echo "binary fetch must not run when latest tag_name leaves the download path" + cat "$LOG" + exit 1 +fi +echo "ok: latest tag_name with slash exits 2 without downloading"