diff --git a/Build/lldb-mi/lldb-mi-sign.template.yml b/Build/lldb-mi/lldb-mi-sign.template.yml index f6e2f256e..7d6cd0348 100644 --- a/Build/lldb-mi/lldb-mi-sign.template.yml +++ b/Build/lldb-mi/lldb-mi-sign.template.yml @@ -3,7 +3,8 @@ parameters: archs: ["x86_64", "arm64"] jobs: -- job: +- job: Sign + displayName: Sign LLDB-MI pool: name: VSEngSS-MicroBuild2022-1ES steps: @@ -27,4 +28,102 @@ jobs: inputs: targetPath: '$(Pipeline.Workspace)\Artifacts\lldb-mi_${{ arch }}.zip' artifactName: 'lldb-mi_${{ arch }}_zip' + +- job: VerifySignatures + displayName: Verify LLDB-MI signatures + dependsOn: Sign + pool: + vmImage: macOS-latest + steps: + - ${{ each arch in parameters.archs }}: + - task: DownloadPipelineArtifact@1 + displayName: 'Downloading lldb-mi_${{ arch }}_zip' + inputs: + targetPath: '$(Pipeline.Workspace)/signed_lldb-mi_${{ arch }}' + artifactName: 'lldb-mi_${{ arch }}_zip' + + - task: Bash@3 + displayName: 'Verify lldb-mi_${{ arch }} signature' + inputs: + targetType: inline + script: | + set -euo pipefail + + arch='${{ arch }}' + artifact_dir='$(Pipeline.Workspace)/signed_lldb-mi_${{ arch }}' + zip_path="${artifact_dir}/lldb-mi_${arch}.zip" + extract_dir="${artifact_dir}/extracted" + expected_binary="${extract_dir}/debugAdapters/lldb-mi_${arch}/bin/lldb-mi" + expected_entitlements='$(Build.SourcesDirectory)/Build/lldb-mi/debugger-entitlements.plist' + + rm -rf "$extract_dir" + mkdir -p "$extract_dir" + unzip -q "$zip_path" -d "$extract_dir" + + binary_count="$(find "$extract_dir" -type f -name lldb-mi | wc -l | tr -d '[:space:]')" + if [[ "$binary_count" != 1 || ! -f "$expected_binary" ]]; then + echo "##[error]Expected exactly one lldb-mi binary at $expected_binary." + find "$extract_dir" -name lldb-mi -print + exit 1 + fi + + actual_archs="$(lipo -archs "$expected_binary")" + if [[ "$actual_archs" != "$arch" ]]; then + echo "##[error]Expected architecture $arch, found $actual_archs." + exit 1 + fi + + codesign --verify --strict --verbose=4 "$expected_binary" + signature_details="$(codesign --display --verbose=4 "$expected_binary" 2>&1)" + printf '%s\n' "$signature_details" + + if printf '%s\n' "$signature_details" | grep -q '^Signature=adhoc$'; then + echo '##[error]The final lldb-mi binary still has an ad-hoc signature.' + exit 1 + fi + + authority_count="$(printf '%s\n' "$signature_details" | grep -c '^Authority=' || true)" + if (( authority_count < 2 )); then + echo "##[error]Expected a signing authority chain, found $authority_count authority entries." + exit 1 + fi + + actual_entitlements="${artifact_dir}/actual-entitlements-${arch}.plist" + codesign --display --entitlements "$actual_entitlements" --xml "$expected_binary" + python3 - "$expected_entitlements" "$actual_entitlements" <<'PY' + import plistlib + import sys + + with open(sys.argv[1], "rb") as expected_file: + expected = plistlib.load(expected_file) + with open(sys.argv[2], "rb") as actual_file: + actual = plistlib.load(actual_file) + + if actual != expected: + print(f"##[error]Expected entitlements: {expected}") + print(f"##[error]Actual entitlements: {actual}") + sys.exit(1) + PY + + tampered_binary="${artifact_dir}/tampered-lldb-mi-${arch}" + cp "$expected_binary" "$tampered_binary" + python3 - "$tampered_binary" <<'PY' + import os + import sys + + tamper_offset = 16384 + with open(sys.argv[1], "r+b") as binary_file: + if os.fstat(binary_file.fileno()).st_size <= tamper_offset: + print("##[error]The lldb-mi binary is too small for the tamper check.") + sys.exit(1) + binary_file.seek(tamper_offset) + original = binary_file.read(1) + binary_file.seek(tamper_offset) + binary_file.write(bytes([original[0] ^ 1])) + PY + + if codesign --verify --strict --verbose=4 "$tampered_binary"; then + echo '##[error]codesign accepted a modified lldb-mi binary.' + exit 1 + fi ... \ No newline at end of file diff --git a/Build/lldb-mi/lldb-mi.template.yml b/Build/lldb-mi/lldb-mi.template.yml index 539ed5836..066939ad5 100644 --- a/Build/lldb-mi/lldb-mi.template.yml +++ b/Build/lldb-mi/lldb-mi.template.yml @@ -27,11 +27,34 @@ jobs: - job: LLDB_MI_${{ parameters.llvm_arch }} timeoutInMinutes: 360 pool: - ${{if eq(parameters['llvm_arch'], 'arm64')}}: - name: cpptoolsMacM1pool + name: Azure Pipelines + ${{ if eq(parameters['llvm_arch'], 'arm64') }}: + vmImage: macOS-15-arm64 ${{ else }}: - vmImage: macOS-latest + vmImage: macOS-15 steps: + - task: UsePythonVersion@0 + displayName: 'Use Python 3.11' + inputs: + versionSpec: '3.11' + ${{ if eq(parameters['llvm_arch'], 'arm64') }}: + architecture: arm64 + ${{ else }}: + architecture: x64 + + - task: CmdLine@2 + displayName: 'Verify Python 3.11' + inputs: + script: | + set -euo pipefail + python_executable="$(command -v python3)" + python_architecture="$("$python_executable" -c 'import platform; print(platform.machine())')" + if [[ "$python_architecture" != '${{ parameters.llvm_arch }}' ]]; then + echo "##[error]Expected Python architecture ${{ parameters.llvm_arch }}, found $python_architecture." + exit 1 + fi + "$python_executable" -c "import distutils.sysconfig; print(distutils.sysconfig.get_python_lib(True, False, ''))" + - task: CmdLine@2 displayName: 'Install Dependencies' inputs: @@ -53,6 +76,9 @@ jobs: $1 } + python_executable="$(command -v python3)" + python_home="$("$python_executable" -c 'import sys; print(sys.prefix)')" + log_and_exec_cmd "sudo rm -rf /Library/Developer/CommandLineTools" log_and_exec_cmd "sudo xcode-select --switch /Applications/XCode.app" @@ -62,6 +88,11 @@ jobs: log_and_exec_cmd "git clone ${{ parameters.llvm_repo }} llvm-project" log_and_exec_cmd "cd llvm-project" log_and_exec_cmd "git checkout ${{ parameters.llvm_commit }}" + if ! log_and_exec_cmd "git cherry-pick --no-commit 81fc5f7909a4ef5a8d4b5da2a10f77f7cb01ba63 73e15b5edb4fa4a77e68c299a6e3b21e610d351f f0a25fe0b746f56295d5c02116ba28d2f965c175" + then + echo "##[error] applying LLVM compatibility fixes failed" + exit 1 + fi log_and_exec_cmd "./lldb/scripts/macos-setup-codesign.sh" @@ -70,7 +101,7 @@ jobs: log_and_exec_cmd "mkdir $(Build.StagingDirectory)/buildspace/llvm-build" log_and_exec_cmd "cd $(Build.StagingDirectory)/buildspace/llvm-build" - log_and_exec_cmd "cmake -DLLVM_ENABLE_PROJECTS=clang;lldb -DCMAKE_BUILD_TYPE=${{ parameters.llvm_build_type }} -DCMAKE_INSTALL_PREFIX=$(Build.StagingDirectory)/buildspace/llvm-inst/ -DCMAKE_OSX_ARCHITECTURES=${{ parameters.llvm_arch }} ${{ parameters.llvm_additional_parameters }} -GNinja $(Build.StagingDirectory)/llvm-project/llvm" + log_and_exec_cmd "cmake -DLLVM_ENABLE_PROJECTS=clang;lldb -DCMAKE_BUILD_TYPE=${{ parameters.llvm_build_type }} -DCMAKE_INSTALL_PREFIX=$(Build.StagingDirectory)/buildspace/llvm-inst/ -DCMAKE_OSX_ARCHITECTURES=${{ parameters.llvm_arch }} -DPYTHON_HOME=$python_home -DPython3_EXECUTABLE=$python_executable ${{ parameters.llvm_additional_parameters }} -GNinja $(Build.StagingDirectory)/llvm-project/llvm" if [[ $? -ne 0 ]] then echo "##[error] cmake llvm failed" @@ -112,7 +143,11 @@ jobs: # Create a separate build directory for building lldb-mi. log_and_exec_cmd "mkdir build" log_and_exec_cmd "cd build" - log_and_exec_cmd "cmake -DCMAKE_PREFIX_PATH=$(Build.StagingDirectory)/buildspace/llvm-inst/ -DCMAKE_OSX_ARCHITECTURES=${{ parameters.llvm_arch }} ${{ parameters.lldb_mi_additional_parameters }} -GNinja .." + if ! log_and_exec_cmd "cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_PREFIX_PATH=$(Build.StagingDirectory)/buildspace/llvm-inst/ -DCMAKE_OSX_ARCHITECTURES=${{ parameters.llvm_arch }} ${{ parameters.lldb_mi_additional_parameters }} -GNinja .." + then + echo "##[error] cmake lldb-mi failed" + exit 1 + fi log_and_exec_cmd "ninja" if [[ $? -ne 0 ]] then