Skip to content

Commit 7a84ca7

Browse files
authored
Release: sign a copy of the exe and write it back in place (#508)
Inside tauri build, ssign's in-place signing failed with "atomically replacing ...netscli-gui.exe: Access is denied (os error 5)": the bundler still holds the patched exe open without delete sharing, so a rename over it is refused. This lost v0.3.4 its Windows build. Sign into a temporary directory with -o and copy the bytes back over the original, which needs only write access, retrying for up to ten seconds. Reproduced on a Windows runner with the real signer and the exe held open without FileShare.Delete: the old wrapper fails with the same error, the new one exits 0 and Get-AuthenticodeSignature reports Valid.
1 parent fa30d73 commit 7a84ca7

1 file changed

Lines changed: 39 additions & 1 deletion

File tree

‎scripts/release/sign-windows-pe.ps1‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,51 @@ if (-not $ssign -or -not (Test-Path -LiteralPath $ssign -PathType Leaf)) {
9292
Fail "NETSCLI_SSIGN does not point at ssign.exe ('$ssign')"
9393
}
9494

95+
# Sign a copy, then write it back over the original in place.
96+
#
97+
# ssign signs in place by writing a new file and renaming it over the old
98+
# one, and inside the bundler that rename is refused: "atomically replacing
99+
# ...netscli-gui.exe: Access is denied (os error 5)", which is what lost
100+
# v0.3.4 its Windows build. Something still holds the freshly patched exe
101+
# open without delete sharing, so it cannot be replaced, but it can be
102+
# written. `-o` puts the signed file elsewhere; copying its bytes over the
103+
# original needs only write access, and is retried in case the holder is a
104+
# scanner that lets go after a moment.
105+
$signedDir = Join-Path ([IO.Path]::GetTempPath()) ("sign-windows-pe-" + [guid]::NewGuid())
106+
New-Item -ItemType Directory -Path $signedDir | Out-Null
95107
Log "signing $name"
96-
$output = & $ssign $Path 2>&1 | Out-String
108+
$output = & $ssign -o $signedDir $Path 2>&1 | Out-String
97109
$code = $LASTEXITCODE
98110
Log "ssign exited $code. Output:`n$($output.Trim())"
99111
if ($code -ne 0) {
100112
Fail "ssign exited $code for $name" $code
101113
}
114+
$signed = Join-Path $signedDir $name
115+
if (-not (Test-Path -LiteralPath $signed -PathType Leaf)) {
116+
Fail "ssign reported success but wrote no $signed"
117+
}
118+
119+
$bytes = [IO.File]::ReadAllBytes($signed)
120+
for ($attempt = 1; ; $attempt++) {
121+
try {
122+
$stream = [IO.File]::Open($Path, 'Open', 'Write', 'ReadWrite')
123+
try {
124+
$stream.SetLength(0)
125+
$stream.Write($bytes, 0, $bytes.Length)
126+
} finally {
127+
$stream.Dispose()
128+
}
129+
break
130+
} catch {
131+
if ($attempt -ge 10) {
132+
Fail "could not write the signed bytes back to $name after $attempt tries: $($_.Exception.Message)"
133+
}
134+
Log "write-back attempt $attempt failed ($($_.Exception.Message)); retrying"
135+
Start-Sleep -Seconds 1
136+
}
137+
}
138+
Remove-Item -LiteralPath $signedDir -Recurse -Force
139+
Log "wrote the signed $name back in place"
102140

103141
# Read the signature back through Windows' own verifier rather than trusting
104142
# the exit code. `Valid` means signed, unmodified since, and chaining to a

0 commit comments

Comments
 (0)