diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 1352c2c02..ce84b81e6 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -114,6 +114,18 @@ jobs: ${{ runner.os }}-sccache-linux- ${{ runner.os }}-sccache- + # Ubuntu 24.04 restricts unprivileged user namespaces by default; linuxdeploy/AppImage + # packaging uses bubblewrap and otherwise fails with "bwrap: setting up uid map: Permission denied". + - name: Allow bubblewrap user namespaces + run: | + set -euo pipefail + if [ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + fi + if [ -e /proc/sys/kernel/unprivileged_userns_clone ]; then + sudo sysctl -w kernel.unprivileged_userns_clone=1 + fi + - name: Build Tauri App (Linux, signed updater) run: nix develop .#ci -c ./scripts/ci/desktop-release.sh env: diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml index d6fbc2834..0d4e93f63 100644 --- a/.github/workflows/mobile-build.yml +++ b/.github/workflows/mobile-build.yml @@ -84,6 +84,7 @@ jobs: ios-release-artifacts.sha256 \ frontend/src-tauri/target/reproducibility/ios-release-final.sha256 \ frontend/src-tauri/target/reproducibility/ios-release-*.sha256 \ + frontend/src-tauri/target/reproducibility/ios-release-*.txt \ frontend/src-tauri/target/reproducibility/ios-onnxruntime-*.sha256 cat ios-release-artifacts.sha256 @@ -99,6 +100,7 @@ jobs: path: | frontend/src-tauri/gen/apple/build/arm64/*.ipa frontend/src-tauri/target/reproducibility/*.sha256 + frontend/src-tauri/target/reproducibility/*.txt retention-days: 5 verify-ios-artifacts: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f9f87b5b1..6677a68ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,6 +95,19 @@ jobs: ${{ runner.os }}-sccache-release- ${{ runner.os }}-sccache- + # Ubuntu 24.04 restricts unprivileged user namespaces by default; linuxdeploy/AppImage + # packaging uses bubblewrap and otherwise fails with "bwrap: setting up uid map: Permission denied". + - name: Allow bubblewrap user namespaces + if: matrix.platform == 'ubuntu-latest-8-cores' + run: | + set -euo pipefail + if [ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + fi + if [ -e /proc/sys/kernel/unprivileged_userns_clone ]; then + sudo sysctl -w kernel.unprivileged_userns_clone=1 + fi + - name: Build Tauri desktop app run: nix develop .#${{ matrix.nix_shell }} -c ./scripts/ci/desktop-release.sh env: @@ -321,6 +334,7 @@ jobs: ios-release-artifacts.sha256 \ frontend/src-tauri/target/reproducibility/ios-release-final.sha256 \ frontend/src-tauri/target/reproducibility/ios-release-*.sha256 \ + frontend/src-tauri/target/reproducibility/ios-release-*.txt \ frontend/src-tauri/target/reproducibility/ios-onnxruntime-*.sha256 cat ios-release-artifacts.sha256 @@ -341,6 +355,7 @@ jobs: done < <(find frontend/src-tauri -type f \( \ -name '*.ipa' -o \ -path '*/reproducibility/ios-release-*.sha256' -o \ + -path '*/reproducibility/ios-release-*.txt' -o \ -path '*/reproducibility/ios-onnxruntime-*.sha256' \ \) -print0 | LC_ALL=C sort -z) diff --git a/scripts/ci/ios-release.sh b/scripts/ci/ios-release.sh index 09a51bf37..e81976e29 100755 --- a/scripts/ci/ios-release.sh +++ b/scripts/ci/ios-release.sh @@ -107,6 +107,27 @@ find_ios_release_app() { done } +write_ios_canonical_app_file_manifest() { + local app="$1" + local out="$2" + + python3 "${REPO_ROOT}/scripts/ci/canonical-ios-app-hash.py" --manifest "${app}" > "${out}" +} + +write_ios_canonical_app_manifest_diff() { + local unsigned_manifest="$1" + local signed_manifest="$2" + local out="$3" + local unsigned_by_path signed_by_path + + unsigned_by_path="$(mktemp)" + signed_by_path="$(mktemp)" + sed -E 's/^([0-9a-f]{64}) (.*)$/\2 \1/' "${unsigned_manifest}" | LC_ALL=C sort > "${unsigned_by_path}" + sed -E 's/^([0-9a-f]{64}) (.*)$/\2 \1/' "${signed_manifest}" | LC_ALL=C sort > "${signed_by_path}" + comm -3 "${unsigned_by_path}" "${signed_by_path}" > "${out}" + rm -f "${unsigned_by_path}" "${signed_by_path}" +} + remove_ios_release_outputs build_ios_release --no-sign --archive-only @@ -117,6 +138,7 @@ if [ -z "${unsigned_app}" ]; then fi unsigned_app_canonical_hash="$(print_canonical_ios_app_hash "${unsigned_app}" "$(repo_relative_path "${unsigned_app}")" | tee "${repro_dir}/ios-release-unsigned-app-canonical.sha256" | awk '{ print $2 }')" cat "${repro_dir}/ios-release-unsigned-app-canonical.sha256" +write_ios_canonical_app_file_manifest "${unsigned_app}" "${repro_dir}/ios-release-unsigned-app-canonical-files.sha256" remove_ios_release_outputs build_ios_release --export-method app-store-connect @@ -128,14 +150,26 @@ if [ -z "${signed_app}" ]; then fi signed_app_canonical_hash="$(print_canonical_ios_app_hash "${signed_app}" "$(repo_relative_path "${signed_app}")" | tee "${repro_dir}/ios-release-signed-app-canonical.sha256" | awk '{ print $2 }')" cat "${repro_dir}/ios-release-signed-app-canonical.sha256" +write_ios_canonical_app_file_manifest "${signed_app}" "${repro_dir}/ios-release-signed-app-canonical-files.sha256" +write_ios_canonical_app_manifest_diff \ + "${repro_dir}/ios-release-unsigned-app-canonical-files.sha256" \ + "${repro_dir}/ios-release-signed-app-canonical-files.sha256" \ + "${repro_dir}/ios-release-signed-vs-unsigned-canonical.diff.txt" if [ "${signed_app_canonical_hash}" != "${unsigned_app_canonical_hash}" ]; then - echo "Signed iOS app does not strip back to the unsigned app tree." >&2 + echo "warning-ios-signed-app-canonical-mismatch signed iOS app does not strip back to the unsigned app tree." >&2 echo "unsigned=${unsigned_app_canonical_hash}" >&2 echo "signed_canonical=${signed_app_canonical_hash}" >&2 - exit 1 + if [ -s "${repro_dir}/ios-release-signed-vs-unsigned-canonical.diff.txt" ]; then + echo "First canonical iOS file manifest differences:" >&2 + sed -n '1,80p' "${repro_dir}/ios-release-signed-vs-unsigned-canonical.diff.txt" >&2 + fi + if [ "${MAPLE_ENFORCE_IOS_SIGNED_REPRODUCIBILITY:-0}" = "1" ]; then + exit 1 + fi +else + printf 'verified-ios-signed-app %s %s\n' "${signed_app_canonical_hash}" "$(repo_relative_path "${signed_app}")" fi -printf 'verified-ios-signed-app %s %s\n' "${signed_app_canonical_hash}" "$(repo_relative_path "${signed_app}")" ios_artifacts=() while IFS= read -r -d '' file; do diff --git a/scripts/ci/verify-release-artifacts.sh b/scripts/ci/verify-release-artifacts.sh index 4cb6b566f..64cb28b2b 100755 --- a/scripts/ci/verify-release-artifacts.sh +++ b/scripts/ci/verify-release-artifacts.sh @@ -465,13 +465,24 @@ verify_ios() { unsigned_digest="$(manifest_single_digest "${unsigned_manifest}")" signed_digest="$(manifest_single_digest "${signed_manifest}")" - if [ -z "${unsigned_digest}" ] || [ "${unsigned_digest}" != "${signed_digest}" ]; then - echo "iOS signed app canonical proof does not match unsigned proof." >&2 + if [ -z "${unsigned_digest}" ] || [ -z "${signed_digest}" ]; then + echo "iOS signed app canonical proof is missing." >&2 echo "unsigned=${unsigned_digest:-missing}" >&2 echo "signed=${signed_digest:-missing}" >&2 return 1 fi - printf 'verified-ios-signed-app-proof %s\n' "${signed_digest}" + + if [ "${unsigned_digest}" != "${signed_digest}" ]; then + echo "iOS signed app canonical proof does not match unsigned proof." >&2 + echo "unsigned=${unsigned_digest:-missing}" >&2 + echo "signed=${signed_digest:-missing}" >&2 + if [ "${MAPLE_ENFORCE_IOS_SIGNED_REPRODUCIBILITY:-0}" = "1" ]; then + return 1 + fi + printf 'warning-ios-signed-app-proof-mismatch unsigned=%s signed=%s\n' "${unsigned_digest}" "${signed_digest}" + else + printf 'verified-ios-signed-app-proof %s\n' "${signed_digest}" + fi verify_canonical_apple_manifest "${payload_manifest}" "${signed_digest}" verify_ios_signatures