mirror of
https://github.com/unslothai/unsloth.git
synced 2026-08-17 21:03:59 +00:00
Windows: verify the publisher of the installers we download and run (#8418)
* Windows: verify the publisher of the installers we download and run Both Windows fallbacks fetch an executable over HTTPS to the temp directory and Start-Process it immediately: the VC++ runtime in studio/setup.ps1 when winget is absent or fails, and the python.org installer in install.ps1. HTTPS vouches for the transfer, not for what arrived, and the process that runs it may be elevated. Neither URL can be pinned to a committed SHA-256 the way install_node_prebuilt.py pins the Node archives. aka.ms/vs/17/release is evergreen and its bytes change with every VS servicing update, and the python.org patch version is resolved at runtime from the directory listing. Verify the publisher instead. Checking the Authenticode status alone would not help, since any code-signing certificate from any trusted CA passes it. The signer subject is checked too, so the chain has to lead back to Microsoft or the Python Software Foundation. Both failure paths are the ones already there. The VC++ throw lands in the existing catch, which prints the same yellow line and falls through to the manual install instructions, and the python.org check returns $null exactly like the download failure two lines above it, so the caller still falls back to uv and astral.sh. * Treat an unreadable signature as a verification failure Get-AuthenticodeSignature can fail on the file itself rather than on its signature: antivirus quarantining the download before we inspect it, or the path becoming unreadable. install.ps1 sets $ErrorActionPreference = "Stop" at the top, so that error was terminating and escaped Install-PythonFromPythonOrg entirely, skipping the $null return the caller relies on for its fallback and leaving the downloaded executable in the temp directory. Confirmed under pwsh: the error propagates out of the function and the cleanup line never runs. Unreadable is unverified, so it now takes the same route as a bad signature: a yellow substep, remove the file, return $null. setup.ps1 already ran its check inside a try with a finally that removes the file, so only the install.ps1 path needed this. Also accept a quoted RDN value in both publisher checks. A subject can arrive as O="Microsoft Corporation", which the unquoted pattern would have rejected. * Tighten the comments on the two signature checks
This commit is contained in:
parent
da1f829bbb
commit
aaf994881b
3 changed files with 43 additions and 1 deletions
18
install.ps1
18
install.ps1
|
|
@ -2223,6 +2223,24 @@ exit 0
|
|||
return $null
|
||||
}
|
||||
|
||||
# Same trust boundary as the VC++ runtime in studio/setup.ps1: $full moves per patch
|
||||
# release, so there is no SHA-256 to pin and the publisher is what we can check.
|
||||
# Inspection itself can fail (antivirus quarantining the download first), and the
|
||||
# script-wide 'Stop' would let that escape the function, skipping the $null fallback
|
||||
# and leaving the executable behind. Unreadable is unverified, so it takes the same
|
||||
# route as a bad signature.
|
||||
$sig = $null
|
||||
try { $sig = Get-AuthenticodeSignature -LiteralPath $dest } catch { $sig = $null }
|
||||
if ($null -eq $sig -or
|
||||
$sig.Status -ne [System.Management.Automation.SignatureStatus]::Valid -or
|
||||
$null -eq $sig.SignerCertificate -or
|
||||
$sig.SignerCertificate.Subject -notmatch '(^|,\s*)O="?Python Software Foundation"?(,|$)') {
|
||||
$sigStatus = if ($null -eq $sig) { "could not be read" } else { $sig.Status }
|
||||
substep "python.org installer is not validly signed by the Python Software Foundation (signature status: $sigStatus); not running it." "Yellow"
|
||||
Remove-Item -LiteralPath $dest -Force -ErrorAction SilentlyContinue
|
||||
return $null
|
||||
}
|
||||
|
||||
# Per-user install => no UAC. PrependPath puts python + py on PATH;
|
||||
# Include_launcher installs py.exe (preferred by Find-CompatiblePython).
|
||||
substep "installing Python $full (silent, per-user)..."
|
||||
|
|
|
|||
|
|
@ -1514,6 +1514,16 @@ function Ensure-VCRedist {
|
|||
} catch { $_prevProtocol = $null }
|
||||
try {
|
||||
Invoke-WebRequest -Uri $url -OutFile $dst -UseBasicParsing -TimeoutSec 300
|
||||
# HTTPS secures the transfer, not the payload, and this runs with the setup
|
||||
# process's privileges. The evergreen URL rules out a SHA-256 pin (the bytes
|
||||
# change with every VS servicing update), so check the publisher. Status alone
|
||||
# is not enough: any trusted CA's code-signing cert passes it.
|
||||
$sig = Get-AuthenticodeSignature -LiteralPath $dst
|
||||
if ($sig.Status -ne [System.Management.Automation.SignatureStatus]::Valid -or
|
||||
$null -eq $sig.SignerCertificate -or
|
||||
$sig.SignerCertificate.Subject -notmatch '(^|,\s*)O="?Microsoft Corporation"?(,|$)') {
|
||||
throw "the downloaded VC++ runtime is not validly signed by Microsoft (signature status: $($sig.Status))"
|
||||
}
|
||||
$p = Start-Process -FilePath $dst -ArgumentList '/quiet', '/norestart' -Wait -PassThru
|
||||
# 3010 = success, reboot required; usable either way.
|
||||
if ($p.ExitCode -notin @(0, 3010)) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
||||
|
||||
"""The direct VC++ runtime download must negotiate TLS 1.2 on legacy protocol defaults."""
|
||||
"""The direct VC++ runtime download must negotiate TLS 1.2 and run only Microsoft's binary."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -27,6 +27,20 @@ def _download_block() -> str:
|
|||
return source[start:end]
|
||||
|
||||
|
||||
def test_the_download_is_verified_as_microsoft_signed_before_it_runs():
|
||||
# No pwsh needed: Get-AuthenticodeSignature is Windows-only, so the ordering of the three
|
||||
# steps in the real block is the thing to hold still. A verification placed after
|
||||
# Start-Process, or one that only checks Status, would still "pass" on a swapped binary.
|
||||
block = _download_block()
|
||||
download = block.index("Invoke-WebRequest")
|
||||
verify = block.index("Get-AuthenticodeSignature", download)
|
||||
execute = block.index("Start-Process", verify)
|
||||
assert download < verify < execute
|
||||
assert "SignatureStatus]::Valid" in block
|
||||
# Loose on the quoting, since an RDN value may arrive quoted, strict on the publisher.
|
||||
assert "Microsoft Corporation" in block
|
||||
|
||||
|
||||
def _script(starting_protocol: str) -> str:
|
||||
# Start from a non-zero set that lacks Tls12. Tls13 is the only such value modern .NET
|
||||
# accepts, and it stands in for the legacy Ssl3/Tls default of Windows PowerShell 5.1.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue