diff --git a/.github/workflows/reusable-homebrew-bottle-publish.yml b/.github/workflows/reusable-homebrew-bottle-publish.yml index 380fb475b9..11a3c4e54c 100644 --- a/.github/workflows/reusable-homebrew-bottle-publish.yml +++ b/.github/workflows/reusable-homebrew-bottle-publish.yml @@ -432,6 +432,7 @@ jobs: systemd_run_bin="/usr/bin/systemd-run" systemctl_bin="/usr/bin/systemctl" getent_bin="/usr/bin/getent" + findmnt_bin="/usr/bin/findmnt" pgrep_bin="/usr/bin/pgrep" pkill_bin="/usr/bin/pkill" useradd_bin="/usr/sbin/useradd" @@ -443,7 +444,7 @@ jobs: [ $((8#$sudo_mode & 0022)) -ne 0 ]; then echo "::error::runner sudo boundary is unavailable"; exit 2 fi - for protected_bin in "$systemd_run_bin" "$systemctl_bin" "$getent_bin" "$pgrep_bin" \ + for protected_bin in "$systemd_run_bin" "$systemctl_bin" "$getent_bin" "$findmnt_bin" "$pgrep_bin" \ "$useradd_bin" "$userdel_bin"; do protected_mode="$(stat -c '%a' "$protected_bin" 2>/dev/null || true)" if [ ! -f "$protected_bin" ] || [ -L "$protected_bin" ] || \ @@ -1662,6 +1663,7 @@ jobs: systemd_run_bin="/usr/bin/systemd-run" systemctl_bin="/usr/bin/systemctl" getent_bin="/usr/bin/getent" + findmnt_bin="/usr/bin/findmnt" pgrep_bin="/usr/bin/pgrep" pkill_bin="/usr/bin/pkill" useradd_bin="/usr/sbin/useradd" @@ -1672,7 +1674,7 @@ jobs: [ $((8#$sudo_mode & 0022)) -ne 0 ]; then echo "::error::runner sudo boundary is unavailable"; exit 2 fi - for protected_bin in "$systemd_run_bin" "$systemctl_bin" "$getent_bin" "$pgrep_bin" \ + for protected_bin in "$systemd_run_bin" "$systemctl_bin" "$getent_bin" "$findmnt_bin" "$pgrep_bin" \ "$useradd_bin"; do protected_mode="$(stat -c '%a' "$protected_bin" 2>/dev/null || true)" if [ ! -f "$protected_bin" ] || [ -L "$protected_bin" ] || \ diff --git a/docs/homebrew-publishing.md b/docs/homebrew-publishing.md index 60aa02c2ad..fc91044eaf 100644 --- a/docs/homebrew-publishing.md +++ b/docs/homebrew-publishing.md @@ -299,12 +299,15 @@ only per `(tap, formula)`, so unrelated Formulae retain parallel throughput: `/home/linuxbrew/.linuxbrew` prefix. This preserves the selected prefix and Cellar so ordinary host build-dependency bottles remain usable. Within that read-only build, all Formula-evaluating Homebrew commands run as a distinct - unprivileged user. Kandelo, tap, and patched Homebrew source are recursively - non-writable and non-replaceable by that identity; only a root-provisioned - shared temporary root, Homebrew cache/temp, prefix, and build home are - writable. Dependency lists and install logs used by the workflow identity - live in a separate mode-0700 control directory under the protected output - root; Formula processes cannot preplant or replace those paths. The wrapper + unprivileged user. The original Kandelo and tap checkouts remain hidden from + that identity. Each transient service receives root-created, read-only bind + aliases for those exact trees, and the Kandelo SDK environment points only + at the alias. The patched Homebrew source is recursively non-writable and + non-replaceable; only a root-provisioned shared temporary root, Homebrew + cache/temp, prefix, and build home are writable. Dependency lists and install + logs used by the workflow identity live in a separate mode-0700 control + directory under the protected output root; Formula processes cannot preplant + or replace those paths. The wrapper uses an explicit host `sudo` boundary, a fixed environment allowlist, and a transient systemd service with control-group kill semantics and `NoNewPrivileges=yes` for every Brew invocation. A final diff --git a/scripts/check-homebrew-publish-workflow-trust.rb b/scripts/check-homebrew-publish-workflow-trust.rb index 3068b0c730..4b796a7ffa 100644 --- a/scripts/check-homebrew-publish-workflow-trust.rb +++ b/scripts/check-homebrew-publish-workflow-trust.rb @@ -16,10 +16,10 @@ DOWNLOAD_ACTION = "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" BREW_COMMIT = "34c40c18ffa2029b611b61c73273e32c003d0842" PUBLISHER_PLAN_DIGEST = "75468bd935146ca1c78e1a757a314bb6355bce63f01707c3345936d3f9ccaf07" -PUBLISHER_BUILD_DIGEST = "682e73ef771a9beebb0bbfaea2770d6f1cd7062882b4bb3145958d204e1436b5" +PUBLISHER_BUILD_DIGEST = "aa18e5d12e4e38bd1e631899444fb8f14d56a1eb05f41b00ceb1288774f78dab" PUBLISHER_UPLOAD_DIGEST = "329ff7dfafc8f3c262ae8c817187d875db451da05d7d98a12eafda45cdbf2213" PUBLISHER_INDEX_DIGEST = "2c5988c9b840dcc1e845f40b58e47ed8263a9babccdae08140bea145a4199de3" -PUBLISHER_VERIFY_DIGEST = "73cc22e389c4fb449f32976860178dc40bce686739c6662cb769ca2ad93b7003" +PUBLISHER_VERIFY_DIGEST = "b9da6e384260ac4e6a036a9734ccce78e43cb144866ec91895c03922aa0e78bb" PUBLISHER_FINALIZE_DIGEST = "2e7a24cdcaa63e631cee1a967bff1c41bb0f8f8e57e2039929efda5d069cf9f9" MAINTENANCE_VALIDATE_DIGEST = "9ab856fe40640172500d82b5179a096aa028763bf696aeac865d732298617a22" MAINTENANCE_ROLLBACK_DIGEST = "45ff220697da9604dbe69c82761f285ba2e3e5182ef0819360128b82dd169efc" @@ -771,6 +771,7 @@ def check_publisher(workflow) 'systemd_run_bin="/usr/bin/systemd-run"', 'systemctl_bin="/usr/bin/systemctl"', 'getent_bin="/usr/bin/getent"', + 'findmnt_bin="/usr/bin/findmnt"', 'pgrep_bin="/usr/bin/pgrep"', 'pkill_bin="/usr/bin/pkill"', 'useradd_bin="/usr/sbin/useradd"', @@ -819,8 +820,7 @@ def check_publisher(workflow) 'homebrew_patched_launcher_isolate "$BUILD_USER"', 'homebrew_patched_launcher_teardown "$BUILD_USER"', "homebrew_patched_launcher_verify_isolation", - 'homebrew_assert_tree_not_writable_by_user "$BUILD_USER" "$OUT_DIR"', - 'homebrew_assert_tree_not_replaceable_by_user "$BUILD_USER" "$OUT_DIR"', + '"$WORK_DIR" "$KANDELO_ROOT" "$TAP_ROOT" "$OUT_DIR"', "CI Formula execution requires KANDELO_HOMEBREW_BUILD_USER", 'mktemp -d "$SHARED_TEMP/homebrew-build.XXXXXX"', 'CONTROL_DIR="$(mktemp -d "$OUT_DIR/.control.XXXXXX")"', @@ -932,13 +932,17 @@ def check_publisher(workflow) "systemd-run", "--wait", "--collect", "--pipe", "--property=KillMode=control-group", "--property=SendSIGKILL=yes", "--property=NoNewPrivileges=yes", "--expand-environment=no", + "--property=BindReadOnlyPaths=", "--property=InaccessiblePaths=", '"--uid=$build_user"', '"--gid=$build_group"', 'env_bin="$(command -v env)"', 'printf \' --working-directory="$working_directory" -- %q -i\'', 'printf \'bottle_tag_env=()\\n\'', 'for variable in KANDELO_HOMEBREW_BOTTLE_TAG HOMEBREW_KANDELO_BOTTLE_TAG', 'bottle_tag_env+=("%s=${%s}")', - 'printf \' "${bottle_tag_env[@]}" %q "$@"\\n\' "$protected_brew"', + 'HOMEBREW_KANDELO_ROOT=$source_alias_dir/kandelo', + 'KANDELO_HOMEBREW_KANDELO_ROOT=$source_alias_dir/kandelo', + 'printf \' "${bottle_tag_env[@]}" "$command_path" "$@"\\n\'', + "__kandelo_verify_source_aliases", "/usr/bin/findmnt", '"$sudo_bin" install -o root -g root -m 0555 "$wrapper_source" "$wrapper_path"', "-writable -print -quit", "! -readable -o ! -executable", "-prune", "homebrew_patched_launcher_uid_has_processes", "homebrew_patched_launcher_teardown", diff --git a/scripts/homebrew-bottle-build.sh b/scripts/homebrew-bottle-build.sh index cdb4a22f32..d0fc34ecd9 100755 --- a/scripts/homebrew-bottle-build.sh +++ b/scripts/homebrew-bottle-build.sh @@ -186,9 +186,8 @@ if [ -n "$BUILD_USER" ]; then # loading TypeScript sources. Do that while the workflow identity still owns # the checkout; the isolated build identity receives no source write access. rm -rf "$KANDELO_ROOT/host/dist" - homebrew_patched_launcher_isolate "$BUILD_USER" "$WORK_DIR" "$KANDELO_ROOT" "$TAP_ROOT" - homebrew_assert_tree_not_writable_by_user "$BUILD_USER" "$OUT_DIR" - homebrew_assert_tree_not_replaceable_by_user "$BUILD_USER" "$OUT_DIR" + homebrew_patched_launcher_isolate "$BUILD_USER" \ + "$WORK_DIR" "$KANDELO_ROOT" "$TAP_ROOT" "$OUT_DIR" BREW_BIN="$HOMEBREW_PATCHED_BREW_BIN" elif [ "${GITHUB_ACTIONS:-}" = "true" ]; then echo "homebrew-bottle-build.sh: CI Formula execution requires KANDELO_HOMEBREW_BUILD_USER" >&2 diff --git a/scripts/homebrew-patched-launcher.sh b/scripts/homebrew-patched-launcher.sh index 0423786569..4e15ddbaf1 100644 --- a/scripts/homebrew-patched-launcher.sh +++ b/scripts/homebrew-patched-launcher.sh @@ -9,6 +9,7 @@ HOMEBREW_PATCHED_OVERLAY="" HOMEBREW_PATCHED_LAUNCHER="" HOMEBREW_PATCHED_BREW_BIN="" HOMEBREW_PATCHED_PROTECTED_DIR="" +HOMEBREW_PATCHED_SOURCE_ALIAS_DIR="" HOMEBREW_PATCHED_INTEGRITY_SHA256="" HOMEBREW_PATCHED_SUDO_BIN="" HOMEBREW_PATCHED_SYSTEMD_RUN_BIN="" @@ -50,11 +51,13 @@ homebrew_assert_tree_not_writable_by_user() { echo "homebrew-patched-launcher: privileged host boundary is not initialized" >&2 return 2 } - writable="$("$HOMEBREW_PATCHED_SUDO_BIN" -H -u "$user" -- \ + if ! writable="$("$HOMEBREW_PATCHED_SUDO_BIN" -H -u "$user" -- \ find "$tree" -xdev \ \( -writable -print -quit \) -o \ - \( -type d \( ! -readable -o ! -executable \) -prune \) \ - 2>/dev/null)" + \( -type d \( ! -readable -o ! -executable \) -prune \))"; then + echo "homebrew-patched-launcher: could not inspect protected source as $user: $tree" >&2 + return 2 + fi if [ -n "$writable" ]; then echo "homebrew-patched-launcher: build user can write protected source: $writable" >&2 return 1 @@ -137,6 +140,11 @@ homebrew_patched_launcher_cleanup() { >/dev/null 2>&1 || true HOMEBREW_PATCHED_PROTECTED_DIR="" fi + if [ -n "$HOMEBREW_PATCHED_SOURCE_ALIAS_DIR" ]; then + "$HOMEBREW_PATCHED_SUDO_BIN" rm -rf "$HOMEBREW_PATCHED_SOURCE_ALIAS_DIR" \ + >/dev/null 2>&1 || true + HOMEBREW_PATCHED_SOURCE_ALIAS_DIR="" + fi if [ -n "$HOMEBREW_PATCHED_LAUNCHER" ] && [ -L "$HOMEBREW_PATCHED_LAUNCHER" ]; then rm -f "$HOMEBREW_PATCHED_LAUNCHER" 2>/dev/null || \ "$HOMEBREW_PATCHED_SUDO_BIN" rm -f "$HOMEBREW_PATCHED_LAUNCHER" \ @@ -164,16 +172,17 @@ homebrew_patched_launcher_cleanup() { # to a dedicated user inside a transient systemd service. KillMode=control-group # makes double-forked or session-detached descendants part of the call lifecycle. homebrew_patched_launcher_isolate() { - if [ "$#" -ne 4 ]; then - echo "homebrew_patched_launcher_isolate: expected BUILD_USER WORK_DIR KANDELO_ROOT TAP_ROOT" >&2 + if [ "$#" -ne 5 ]; then + echo "homebrew_patched_launcher_isolate: expected BUILD_USER WORK_DIR KANDELO_ROOT TAP_ROOT OUTPUT_ROOT" >&2 return 2 fi - local build_user="$1" work_dir="$2" kandelo_root="$3" tap_root="$4" - local build_group build_home protected_brew wrapper_source wrapper_path + local build_user="$1" work_dir="$2" kandelo_root="$3" tap_root="$4" output_root="$5" + local build_group build_home protected_brew protected_audit + local wrapper_source wrapper_path audit_source local mutable_root protected_root local sudo_bin sudo_mode env_bin variable value patched_prefix patched_repo local systemd_run_bin systemctl_bin getent_bin pgrep_bin pkill_bin - local build_uid systemd_slice unit_prefix + local build_uid systemd_slice unit_prefix source_alias_dir local -a preserved_variables [ "$(uname -s)" = "Linux" ] || { @@ -218,6 +227,8 @@ homebrew_patched_launcher_isolate() { "$build_user" "$pgrep_bin" /usr/bin/pgrep pgrep homebrew_assert_protected_host_executable \ "$build_user" "$pkill_bin" /usr/bin/pkill pkill /usr/bin/pgrep + homebrew_assert_protected_host_executable \ + "$build_user" /usr/bin/findmnt /usr/bin/findmnt findmnt [ -d /run/systemd/system ] || { echo "homebrew-patched-launcher: systemd is not the active service manager" >&2 return 2 @@ -235,6 +246,19 @@ homebrew_patched_launcher_isolate() { return 2 } + for protected_root in "$kandelo_root" "$tap_root" "$output_root"; do + if [ ! -d "$protected_root" ] || [ -L "$protected_root" ]; then + echo "homebrew-patched-launcher: protected root is not a real directory: $protected_root" >&2 + return 2 + fi + case "$protected_root" in + *:*) + echo "homebrew-patched-launcher: protected root cannot contain ':' for a systemd bind: $protected_root" >&2 + return 2 + ;; + esac + done + for mutable_root in "$work_dir" "$HOMEBREW_CACHE" "$HOMEBREW_TEMP"; do if [ ! -d "$mutable_root" ] || [ -L "$mutable_root" ]; then echo "homebrew-patched-launcher: mutable build root is not a real directory: $mutable_root" >&2 @@ -262,9 +286,43 @@ homebrew_patched_launcher_isolate() { HOMEBREW_PATCHED_PROTECTED_DIR="$HOMEBREW_PATCHED_PREFIX/.kandelo-homebrew-$$-${RANDOM}" "$sudo_bin" install -d -o root -g root -m 0755 "$HOMEBREW_PATCHED_PROTECTED_DIR" + source_alias_dir="$work_dir/source-aliases" + "$sudo_bin" install -d -o root -g root -m 0555 \ + "$source_alias_dir" "$source_alias_dir/kandelo" "$source_alias_dir/tap" + HOMEBREW_PATCHED_SOURCE_ALIAS_DIR="$source_alias_dir" protected_brew="$HOMEBREW_PATCHED_PROTECTED_DIR/brew" "$sudo_bin" ln -s "$HOMEBREW_PATCHED_OVERLAY/bin/brew" "$protected_brew" + audit_source="$work_dir/audit-source-aliases" + protected_audit="$HOMEBREW_PATCHED_PROTECTED_DIR/audit-source-aliases" + { + printf '#!/usr/bin/env bash\nset -euo pipefail\n' + printf 'expected_kandelo=%q\n' "$source_alias_dir/kandelo" + printf 'expected_tap=%q\n' "$source_alias_dir/tap" + printf 'if [ "${HOMEBREW_KANDELO_ROOT:-}" != "$expected_kandelo" ] || ' + printf '[ "${KANDELO_HOMEBREW_KANDELO_ROOT:-}" != "$expected_kandelo" ]; then\n' + printf ' echo "homebrew-patched-launcher: isolated Kandelo root does not use the protected alias" >&2\n' + printf ' exit 2\nfi\n' + printf 'for source_alias in "$expected_kandelo" "$expected_tap"; do\n' + printf ' if [ ! -d "$source_alias" ] || [ ! -r "$source_alias" ] || [ ! -x "$source_alias" ]; then\n' + printf ' echo "homebrew-patched-launcher: protected source alias is inaccessible: $source_alias" >&2\n' + printf ' exit 2\n fi\n' + printf ' mount_options="$(/usr/bin/findmnt --noheadings --output VFS-OPTIONS --target "$source_alias")" || {\n' + printf ' echo "homebrew-patched-launcher: could not inspect protected source mount: $source_alias" >&2\n' + printf ' exit 2\n }\n' + printf ' case ",${mount_options// /}," in\n' + printf ' *,ro,*) ;;\n' + printf ' *) echo "homebrew-patched-launcher: protected source mount is writable: $source_alias" >&2; exit 1 ;;\n' + printf ' esac\ndone\n' + printf 'for hidden_root in %q %q %q; do\n' \ + "$kandelo_root" "$tap_root" "$output_root" + printf ' if [ -e "$hidden_root" ] || [ -r "$hidden_root" ] || [ -x "$hidden_root" ]; then\n' + printf ' echo "homebrew-patched-launcher: original protected root is visible to Formula execution: $hidden_root" >&2\n' + printf ' exit 1\n fi\ndone\n' + } >"$audit_source" + "$sudo_bin" install -o root -g root -m 0555 "$audit_source" "$protected_audit" + rm -f "$audit_source" + wrapper_source="$work_dir/run-isolated-brew" wrapper_path="$HOMEBREW_PATCHED_PROTECTED_DIR/run-brew" systemd_slice="kandelo-homebrew-build-${build_uid}.slice" @@ -274,8 +332,8 @@ homebrew_patched_launcher_isolate() { PATH XDG_CONFIG_HOME HOMEBREW_CACHE HOMEBREW_TEMP HOMEBREW_NO_AUTO_UPDATE HOMEBREW_NO_INSTALL_CLEANUP HOMEBREW_NO_ANALYTICS HOMEBREW_DEVELOPER - KANDELO_HOMEBREW_ARCH KANDELO_HOMEBREW_KANDELO_ROOT - HOMEBREW_KANDELO_ARCH HOMEBREW_KANDELO_ROOT HOMEBREW_KANDELO_NODE + KANDELO_HOMEBREW_ARCH + HOMEBREW_KANDELO_ARCH HOMEBREW_KANDELO_NODE HOMEBREW_KANDELO_LLVM_BIN HOMEBREW_KANDELO_ABI HOMEBREW_KANDELO_NODE_RECEIPT_PATH LLVM_BIN WASM_POSIX_LLVM_DIR @@ -288,9 +346,11 @@ homebrew_patched_launcher_isolate() { printf 'if [ -n "${%s+x}" ]; then bottle_tag_env+=("%s=${%s}"); fi\n' \ "$variable" "$variable" "$variable" done - # The workflow checkout may live below a home directory that the isolated - # build identity cannot traverse. The mutable work root was already - # verified for that identity, so use it as the service working directory. + printf 'command_path=%q\n' "$protected_brew" + printf 'if [ "${1:-}" = __kandelo_verify_source_aliases ]; then\n' + printf ' [ "$#" -eq 1 ] || { echo "homebrew-patched-launcher: source audit accepts no arguments" >&2; exit 2; }\n' + printf ' command_path=%q\n' "$protected_audit" + printf ' shift\nfi\n' printf 'working_directory=%q\n' "$work_dir" printf 'unit=%q-$$-${RANDOM}.service\n' "$unit_prefix" printf 'exec %q -n -- %q --quiet --wait --collect --pipe' \ @@ -300,6 +360,11 @@ homebrew_patched_launcher_isolate() { "--uid=$build_user" "--gid=$build_group" \ "--property=KillMode=control-group" "--property=SendSIGKILL=yes" \ "--property=TimeoutStopSec=10s" "--property=NoNewPrivileges=yes" \ + "--property=BindReadOnlyPaths=$kandelo_root:$source_alias_dir/kandelo" \ + "--property=BindReadOnlyPaths=$tap_root:$source_alias_dir/tap" \ + "--property=InaccessiblePaths=$kandelo_root" \ + "--property=InaccessiblePaths=$tap_root" \ + "--property=InaccessiblePaths=$output_root" \ "--service-type=exec" \ "--expand-environment=no" printf ' --working-directory="$working_directory" -- %q -i' "$env_bin" @@ -311,14 +376,17 @@ homebrew_patched_launcher_isolate() { printf ' %q' "$variable=$value" fi done - printf ' "${bottle_tag_env[@]}" %q "$@"\n' "$protected_brew" + printf ' %q %q' "HOMEBREW_KANDELO_ROOT=$source_alias_dir/kandelo" \ + "KANDELO_HOMEBREW_KANDELO_ROOT=$source_alias_dir/kandelo" + printf ' "${bottle_tag_env[@]}" "$command_path" "$@"\n' } >"$wrapper_source" "$sudo_bin" install -o root -g root -m 0555 "$wrapper_source" "$wrapper_path" rm -f "$wrapper_source" "$sudo_bin" chmod 0555 "$HOMEBREW_PATCHED_PROTECTED_DIR" - for protected_root in \ - "$kandelo_root" "$tap_root" "$HOMEBREW_PATCHED_REPO" "$HOMEBREW_PATCHED_OVERLAY"; do + # The overlay is a Git worktree, so its backing repository must remain + # traversable. Protect the Formula-executing overlay itself instead. + for protected_root in "$source_alias_dir" "$HOMEBREW_PATCHED_OVERLAY"; do homebrew_assert_tree_not_writable_by_user "$build_user" "$protected_root" homebrew_assert_tree_not_replaceable_by_user "$build_user" "$protected_root" done @@ -338,8 +406,18 @@ homebrew_patched_launcher_isolate() { "$sudo_bin" rm -f "$HOMEBREW_PATCHED_LAUNCHER" HOMEBREW_PATCHED_LAUNCHER="$protected_brew" HOMEBREW_PATCHED_BREW_BIN="$wrapper_path" - patched_prefix="$("$HOMEBREW_PATCHED_BREW_BIN" --prefix)" || return - patched_repo="$("$HOMEBREW_PATCHED_BREW_BIN" --repository)" || return + "$HOMEBREW_PATCHED_BREW_BIN" __kandelo_verify_source_aliases || { + echo "homebrew-patched-launcher: isolated source aliases failed verification" >&2 + return 1 + } + if ! patched_prefix="$("$HOMEBREW_PATCHED_BREW_BIN" --prefix)"; then + echo "homebrew-patched-launcher: isolated wrapper could not report the Homebrew prefix" >&2 + return 1 + fi + if ! patched_repo="$("$HOMEBREW_PATCHED_BREW_BIN" --repository)"; then + echo "homebrew-patched-launcher: isolated wrapper could not report the Homebrew repository" >&2 + return 1 + fi [ "$patched_prefix" = "$HOMEBREW_PATCHED_PREFIX" ] || { echo "homebrew-patched-launcher: isolated wrapper changed Homebrew prefix" >&2 return 1 diff --git a/scripts/homebrew-verify-poured-bottle.sh b/scripts/homebrew-verify-poured-bottle.sh index c347124a92..2b1a4d6ccb 100755 --- a/scripts/homebrew-verify-poured-bottle.sh +++ b/scripts/homebrew-verify-poured-bottle.sh @@ -185,9 +185,8 @@ TAPPED_TAP_ROOT="$("$BREW_BIN" --repository "$TAP_NAME")" if [ -n "$BUILD_USER" ]; then rm -rf "$KANDELO_ROOT/host/dist" - homebrew_patched_launcher_isolate "$BUILD_USER" "$WORK_DIR" "$KANDELO_ROOT" "$TAP_ROOT" - homebrew_assert_tree_not_writable_by_user "$BUILD_USER" "$OUT_PARENT" - homebrew_assert_tree_not_replaceable_by_user "$BUILD_USER" "$OUT_PARENT" + homebrew_patched_launcher_isolate "$BUILD_USER" \ + "$WORK_DIR" "$KANDELO_ROOT" "$TAP_ROOT" "$OUT_PARENT" BREW_BIN="$HOMEBREW_PATCHED_BREW_BIN" elif [ "${GITHUB_ACTIONS:-}" = "true" ]; then echo "homebrew-verify-poured-bottle.sh: CI Formula execution requires KANDELO_HOMEBREW_BUILD_USER" >&2 diff --git a/scripts/test-homebrew-patched-launcher.sh b/scripts/test-homebrew-patched-launcher.sh index 9c0897cf6f..02da49fe8b 100755 --- a/scripts/test-homebrew-patched-launcher.sh +++ b/scripts/test-homebrew-patched-launcher.sh @@ -87,6 +87,12 @@ case "${1:-}" in assert-working-directory) [ "$(pwd -P)" = "$2" ] ;; + assert-kandelo-source-alias) + [ "${HOMEBREW_KANDELO_ROOT:-}" = "$2" ] + [ "${KANDELO_HOMEBREW_KANDELO_ROOT:-}" = "$2" ] + [ -r "$2/source-marker" ] + [ ! -e "$3" ] + ;; assert-argv) [ "$#" -eq 6 ] [ "$2" = "" ] @@ -190,6 +196,27 @@ HOMEBREW_PATCHED_SUDO_BIN="" HOMEBREW_PATCHED_PGREP_BIN="" HOMEBREW_PATCHED_BUILD_UID="" +audit_probe_dir="$TMPDIR/audit-probe" +mkdir -p "$audit_probe_dir/tree" +cat >"$audit_probe_dir/sudo" <<'EOF' +#!/usr/bin/env bash +echo "fixture traversal denied" >&2 +exit 13 +EOF +chmod +x "$audit_probe_dir/sudo" +HOMEBREW_PATCHED_SUDO_BIN="$audit_probe_dir/sudo" +set +e +audit_error="$(homebrew_assert_tree_not_writable_by_user \ + fixture-user "$audit_probe_dir/tree" 2>&1)" +audit_status="$?" +set -e +[ "$audit_status" -eq 2 ] || fail "failed source audit did not return its contract error" +[[ "$audit_error" == *"fixture traversal denied"* ]] || + fail "failed source audit suppressed the underlying traversal error" +[[ "$audit_error" == *"could not inspect protected source"* ]] || + fail "failed source audit did not identify the rejected tree" +HOMEBREW_PATCHED_SUDO_BIN="" + if [ "$(uname -s)" = "Linux" ] && [ -x /usr/bin/sudo ] && \ [ -x /usr/bin/systemd-run ] && [ -x /usr/bin/systemctl ] && \ [ -x /usr/bin/getent ] && [ -x /usr/bin/pgrep ] && [ -x /usr/bin/pkill ] && \ @@ -204,15 +231,20 @@ if [ "$(uname -s)" = "Linux" ] && [ -x /usr/bin/sudo ] && \ isolated_work="$ISOLATION_ROOT/work" isolated_cache="$ISOLATION_ROOT/cache" isolated_temp="$ISOLATION_ROOT/temp" - isolated_kandelo="$ISOLATION_ROOT/kandelo" - isolated_tap="$ISOLATION_ROOT/tap" + isolated_source_parent="$ISOLATION_ROOT/private-runner-home" + isolated_kandelo="$isolated_source_parent/kandelo" + isolated_tap="$isolated_source_parent/tap" + isolated_output="$isolated_source_parent/output" isolated_home="/home/$ISOLATION_BUILD_USER" daemon_marker="$isolated_work/detached-process-survived" daemon_started="$isolated_work/detached-process-started" mkdir -p "$isolated_repo/bin" "$isolated_prefix/bin" "$isolated_work" \ - "$isolated_cache" "$isolated_temp" "$isolated_kandelo" "$isolated_tap" + "$isolated_cache" "$isolated_temp" "$isolated_kandelo" "$isolated_tap" \ + "$isolated_output" + printf 'reviewed source\n' >"$isolated_kandelo/source-marker" mkdir "$isolated_kandelo/runner-control" chmod 0700 "$isolated_kandelo/runner-control" + chmod 0700 "$isolated_source_parent" cp "$prefix/bin/brew" "$isolated_repo/bin/brew" chmod +x "$isolated_repo/bin/brew" printf 'unpatched\n' >"$isolated_repo/marker.txt" @@ -239,10 +271,13 @@ if [ "$(uname -s)" = "Linux" ] && [ -x /usr/bin/sudo ] && \ homebrew_patched_launcher_prepare \ "$isolated_prefix/bin/brew" "$patch_file" "$isolated_work" homebrew_patched_launcher_isolate \ - "$ISOLATION_BUILD_USER" "$isolated_work" "$isolated_kandelo" "$isolated_tap" + "$ISOLATION_BUILD_USER" "$isolated_work" "$isolated_kandelo" "$isolated_tap" \ + "$isolated_output" "$HOMEBREW_PATCHED_BREW_BIN" assert-identity \ "$(id -u "$ISOLATION_BUILD_USER")" "$(id -g "$ISOLATION_BUILD_USER")" "$HOMEBREW_PATCHED_BREW_BIN" assert-working-directory "$isolated_work" + "$HOMEBREW_PATCHED_BREW_BIN" assert-kandelo-source-alias \ + "$HOMEBREW_PATCHED_SOURCE_ALIAS_DIR/kandelo" "$isolated_kandelo" "$HOMEBREW_PATCHED_BREW_BIN" assert-argv \ "" "with spaces" '$dollar' '%percent' $'line one\nline two' "$HOMEBREW_PATCHED_BREW_BIN" assert-bottle-tags "" ""