Skip to content

Commit 6b82e9e

Browse files
authored
Merge branch 'main' into test/core-coverage-20261026
2 parents 99ad88b + 1869326 commit 6b82e9e

39 files changed

Lines changed: 1645 additions & 170 deletions

‎.github/PULL_REQUEST_TEMPLATE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
## Description
22

3-
<!-- Every PR should have a corresponding issue that describes and motivates the work done in the PR -->
3+
<!-- Link the relevant issue, if any. -->
44
closes <!-- Link issue here -->
55

66
<!-- Provide a standalone description of changes in this PR. -->
77

88
<!-- Note: The pull request title will be included in the CHANGELOG. -->
99

1010
## Checklist
11-
<!-- TODO: - [ ] I am familiar with the [Contributing Guidelines](). -->
11+
- [ ] I have read the [Contributing Guidelines](https://github.com/NVIDIA/cuda-python/blob/main/CONTRIBUTING.md#coordinate-before-implementing).
1212
- [ ] New or existing tests cover these changes.
1313
- [ ] The documentation is up to date with these changes.

‎.github/actionlint.yaml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,15 @@ paths:
3232
".github/workflows/test-wheel-windows.yml":
3333
ignore:
3434
- 'specifying action "\$/[^"]+" in invalid format because ref is missing'
35+
".github/workflows/build-wheel.yml":
36+
ignore:
37+
- 'specifying action "\$/[^"]+" in invalid format because ref is missing'
38+
".github/workflows/pre-commit.yml":
39+
ignore:
40+
- 'specifying action "\$/[^"]+" in invalid format because ref is missing'
41+
".github/workflows/test-sdist-windows.yml":
42+
ignore:
43+
- 'specifying action "\$/[^"]+" in invalid format because ref is missing'
44+
".github/workflows/coverage.yml":
45+
ignore:
46+
- 'specifying action "\$/[^"]+" in invalid format because ref is missing'
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Temporary stopgap for the eMudhra/emSign CA key compromise (CVE-2026-111943)
6+
# on GitHub-hosted runners until GitHub ships fixed images.
7+
#
8+
# On Linux: upgrades ca-certificates via apt so the compromised emSign ECC Root
9+
# CA - G3 anchor is removed from /etc/ssl/certs/ca-certificates.crt. Ubuntu's
10+
# bundle does not include AAA Certificate Services, so the Sectigo cross-signs
11+
# do not chain to a trusted root there.
12+
# On Windows: adds the eight compromised intermediates and their eight Sectigo
13+
# cross-signs (impacted-intermediates.json) to Cert:\LocalMachine\Disallowed so
14+
# schannel-based clients (git, gh, PowerShell/.NET) reject chains through them,
15+
# including chains to AAA Certificate Services, which Windows still trusts.
16+
#
17+
# This action is a no-op on self-hosted runners (runner.environment != 'github-hosted')
18+
# because self-hosted runner operators own and update their own host images.
19+
#
20+
# Removal criteria and implementation notes: security/emudhra-plan2.md
21+
# Tracked by: actions/runner-images#15018
22+
23+
name: Harden OS certificate trust (eMudhra/emSign CA stopgap)
24+
description: >
25+
Temporary per-job stopgap for CVE-2026-111943. Upgrades ca-certificates on
26+
GitHub-hosted Linux runners and adds the compromised eMudhra intermediates
27+
and cross-signs to the Disallowed store on GitHub-hosted Windows runners.
28+
No-op on self-hosted.
29+
30+
runs:
31+
using: composite
32+
steps:
33+
34+
# -------------------------------------------------------------------------
35+
# Windows path: add the compromised intermediates to LocalMachine\Disallowed.
36+
# Takes effect immediately for all schannel-based clients in subsequent steps
37+
# (git, gh, PowerShell/.NET, WinHTTP). Must precede actions/checkout.
38+
# -------------------------------------------------------------------------
39+
- name: Distrust compromised eMudhra intermediates (Windows, GitHub-hosted)
40+
if: ${{ runner.os == 'Windows' && runner.environment == 'github-hosted' }}
41+
shell: pwsh
42+
env:
43+
ACTION_PATH: ${{ github.action_path }}
44+
run: |
45+
$ErrorActionPreference = 'Stop'
46+
. (Join-Path $env:ACTION_PATH 'emudhra_intermediates.ps1')
47+
48+
$certs = @(Get-EmudhraIntermediates)
49+
try {
50+
# Adding is idempotent, so no early exit when the image is already fixed.
51+
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('Disallowed', 'LocalMachine')
52+
$store.Open('ReadWrite')
53+
try {
54+
foreach ($cert in $certs) { $store.Add($cert) }
55+
} finally {
56+
$store.Close()
57+
}
58+
59+
$notDistrusted = @($certs | Where-Object { -not (Test-ExplicitlyDistrusted $_) } | ForEach-Object { $_.EmudhraLabel })
60+
if ($notDistrusted.Count -gt 0) {
61+
Write-Error "Not explicitly distrusted after adding to LocalMachine\Disallowed: $($notDistrusted -join '; ')"
62+
exit 1
63+
}
64+
Write-Host "Distrusted $($certs.Count) compromised eMudhra intermediates and cross-signs."
65+
} finally {
66+
$certs | ForEach-Object { $_.Dispose() }
67+
}
68+
69+
# -------------------------------------------------------------------------
70+
# Linux path: upgrade ca-certificates via apt.
71+
# The Ubuntu archive GPG check does not depend on the trust store being
72+
# fixed, so this bootstrap is safe. Retries guard against transient mirror
73+
# failures. Must precede actions/checkout.
74+
# -------------------------------------------------------------------------
75+
- name: Upgrade ca-certificates (Linux, GitHub-hosted)
76+
if: ${{ runner.os == 'Linux' && runner.environment == 'github-hosted' }}
77+
shell: bash
78+
run: |
79+
set -euo pipefail
80+
EMSIGN_G3_FP="86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B"
81+
CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt"
82+
VERSION_ID="$(. /etc/os-release && echo "${VERSION_ID}")"
83+
84+
# Fail closed if openssl is missing or the bundle cannot be parsed
85+
# (mirrors ci/tools/runner_trust_probe.sh).
86+
if ! command -v openssl >/dev/null 2>&1; then
87+
echo "::error::openssl not found; cannot verify CA bundle"
88+
exit 1
89+
fi
90+
if [[ ! -f "${CA_BUNDLE}" ]]; then
91+
echo "::error::CA bundle not found: ${CA_BUNDLE}"
92+
exit 1
93+
fi
94+
95+
fingerprints_in_bundle() {
96+
awk '/-----BEGIN CERTIFICATE-----/{cert=""} {cert=cert $0 "\n"}
97+
/-----END CERTIFICATE-----/{print cert | "openssl x509 -noout -fingerprint -sha256 2>/dev/null"; close("openssl x509 -noout -fingerprint -sha256 2>/dev/null")}' \
98+
"${CA_BUNDLE}"
99+
}
100+
101+
fps="$(fingerprints_in_bundle)"
102+
cert_count="$(grep -c . <<< "${fps}" || true)"
103+
if (( cert_count == 0 )); then
104+
echo "::error::No certificates parsed from ${CA_BUNDLE}; openssl may have failed"
105+
exit 1
106+
fi
107+
108+
if grep -qiF "${EMSIGN_G3_FP}" <<< "${fps}"; then
109+
before_state="not fixed"
110+
else
111+
before_state="already fixed"
112+
fi
113+
before_ver="$(dpkg-query -W -f='${Version}' ca-certificates 2>/dev/null || echo unknown)"
114+
echo "::notice::emSign ECC Root CA - G3 on ${VERSION_ID}: ${before_state} (ca-certificates ${before_ver})"
115+
116+
if [ "${before_state}" = "already fixed" ]; then
117+
exit 0
118+
fi
119+
120+
for attempt in 1 2 3; do
121+
if sudo apt-get update -q && sudo apt-get install --only-upgrade -y ca-certificates; then
122+
break
123+
fi
124+
if [ "${attempt}" -lt 3 ]; then
125+
echo "apt-get attempt ${attempt} failed; retrying in 10s..."
126+
sleep 10
127+
else
128+
echo "::error::apt-get upgrade failed after ${attempt} attempts"
129+
exit 1
130+
fi
131+
done
132+
133+
after_ver="$(dpkg-query -W -f='${Version}' ca-certificates 2>/dev/null || echo unknown)"
134+
after_fps="$(fingerprints_in_bundle)"
135+
after_count="$(grep -c . <<< "${after_fps}" || true)"
136+
if (( after_count == 0 )); then
137+
echo "::error::No certificates parsed after upgrade; cannot verify fix"
138+
exit 1
139+
fi
140+
if grep -qiF "${EMSIGN_G3_FP}" <<< "${after_fps}"; then
141+
echo "::error::emSign ECC Root CA - G3 still present after upgrade (ca-certificates ${after_ver} on ${VERSION_ID})"
142+
exit 1
143+
fi
144+
echo "ca-certificates upgraded to ${after_ver}; emSign ECC Root CA - G3 absent."
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Helpers for the compromised eMudhra intermediates and their Sectigo
6+
# cross-signs listed in impacted-intermediates.json. Dot-sourced by this
7+
# action's Windows step and by ci/tools/runner_trust_probe.ps1.
8+
#
9+
# impacted-intermediates.json is the disclosure's file, unmodified (SHA-256
10+
# f67b3abe9b4879f4e91874ed041beccd71257f1db41c588c724ccf12aa90c59e). The set
11+
# is closed: new certificates for these keys would have to be issued by
12+
# another CA, and all existing ones are revoked.
13+
14+
$EmudhraIntermediatesPath = Join-Path $PSScriptRoot 'impacted-intermediates.json'
15+
$EmudhraIntermediatesCount = 16
16+
17+
# Returns the certificates as X509Certificate2 objects; callers dispose them.
18+
function Get-EmudhraIntermediates {
19+
$entries = @(Get-Content -LiteralPath $EmudhraIntermediatesPath -Raw | ConvertFrom-Json)
20+
if ($entries.Count -ne $EmudhraIntermediatesCount) {
21+
throw "Expected $EmudhraIntermediatesCount certificates in $EmudhraIntermediatesPath, found $($entries.Count)."
22+
}
23+
foreach ($entry in $entries) {
24+
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPem($entry.pem)
25+
if ($cert.GetCertHashString('SHA256') -ne $entry.sha256.ToUpperInvariant()) {
26+
$cert.Dispose()
27+
throw "SHA-256 mismatch for $($entry.name) (issued by $($entry.issued_by))."
28+
}
29+
Add-Member -InputObject $cert -NotePropertyName EmudhraLabel -NotePropertyValue "$($entry.name) (issued by $($entry.issued_by))"
30+
$cert
31+
}
32+
}
33+
34+
# True if Windows chain building reports the certificate as explicitly
35+
# distrusted. This covers both a local LocalMachine\Disallowed entry and
36+
# Microsoft's automatically updated disallowed list. Revocation checking and
37+
# AIA downloads are off so the result reflects the local trust state only.
38+
function Test-ExplicitlyDistrusted([System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert) {
39+
$explicitDistrust = [System.Security.Cryptography.X509Certificates.X509ChainStatusFlags]::ExplicitDistrust
40+
$chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
41+
try {
42+
$chain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck
43+
$chain.ChainPolicy.DisableCertificateDownloads = $true
44+
[void]$chain.Build($Cert)
45+
$statuses = @($chain.ChainStatus) + @($chain.ChainElements | ForEach-Object { $_.ChainElementStatus })
46+
return [bool]($statuses | Where-Object { $_.Status.HasFlag($explicitDistrust) })
47+
} finally {
48+
$chain.Dispose()
49+
}
50+
}

0 commit comments

Comments
 (0)