Skip to content

Commit e8f9fbe

Browse files
committed
fix(checkver)!: Harden checkver script evaluation
Signed-off-by: Chawye Hsu <su+git@chawyehsu.com>
1 parent a7a7d34 commit e8f9fbe

2 files changed

Lines changed: 58 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- **core:** Give `dark` higher priority when use `Extract-DarkArchive` ([#6637](https://github.com/ScoopInstaller/Scoop/issues/6637))
3737
- **checkver:** Harden github checkver ([#6641](https://github.com/ScoopInstaller/Scoop/issues/6641))
3838
- **scoop-search:** Select latest search result semantically ([#6643](https://github.com/ScoopInstaller/Scoop/issues/6643))
39+
- **checkver:** Harden checkver script evaluation ([#6652](https://github.com/ScoopInstaller/Scoop/issues/6652))
3940

4041
### Code Refactoring
4142

bin/checkver.ps1

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,46 @@ function next($er) {
266266
Write-Host $er -ForegroundColor DarkRed
267267
}
268268

269+
function Invoke-SanitizeScriptBlock {
270+
param(
271+
[Parameter(Mandatory = $true)]
272+
[string] $ScriptBlock
273+
)
274+
275+
$tokens = $null
276+
$errors = $null
277+
$ast = [System.Management.Automation.Language.Parser]::ParseInput($ScriptBlock, [ref]$tokens, [ref]$errors)
278+
279+
if ($errors.Count -gt 0 -or -not $ast) {
280+
$message = if ($errors) { $errors[0].Message } else { 'unknown parse error' }
281+
return $true, $message
282+
}
283+
284+
# Restrict the capability of accessing outer variables via `$using:`
285+
if ($ast.Find({ param($node) $node -is [System.Management.Automation.Language.UsingExpressionAst] }, $true)) {
286+
return $true, "'`$using:' may not be used"
287+
}
288+
289+
return $false, $null
290+
}
291+
292+
function Get-CheckverScriptPrelude() {
293+
return @(
294+
'$ErrorActionPreference = "Stop"',
295+
'$ProgressPreference = "SilentlyContinue"',
296+
# Set allowed env vars, `SystemRoot` is required for irm/iwr
297+
'$allowedEnvs = @("SystemRoot","TEMP","TMP")',
298+
'Get-ChildItem Env: | Where-Object { $allowedEnvs -notcontains $_.Name } | Remove-Item',
299+
# Use `UseBasicParsing` for Invoke-WebRequest by default (WindowsPowerShell)
300+
'$PSDefaultParameterValues["Invoke-WebRequest:UseBasicParsing"] = $true',
301+
# Use ConstrainedLanguage language mode to restrict checkver.script capabilities
302+
'$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"',
303+
# Available variables/functions for checkver.script
304+
# - `$page`: the retrieved webpage content
305+
'$page = $using:page'
306+
)
307+
}
308+
269309
# wait for all to complete
270310
while ($in_progress -gt 0) {
271311
$ev = Wait-Event
@@ -318,12 +358,26 @@ while ($in_progress -gt 0) {
318358
$page = (New-Object System.IO.StreamReader($ms, (Get-Encoding $wc))).ReadToEnd()
319359
}
320360

361+
# checkver.script evaluation
321362
if ($script) {
322-
$page = Invoke-Command ([scriptblock]::Create($script -join "`r`n"))
323-
$source = 'the output of script'
363+
$script = @($script) -join "`n"
364+
$isBadScript, $errorMessage = Invoke-SanitizeScriptBlock $script
365+
if ($isBadScript) {
366+
next "invalid checkver.script: $errorMessage"
367+
continue
368+
}
369+
370+
$prelude = Get-CheckverScriptPrelude
371+
372+
$script = $prelude + $script
373+
$scriptBlock = [scriptblock]::Create($script -join "`n")
374+
375+
$page = (Start-Job -ScriptBlock $scriptBlock | Receive-Job -Wait -AutoRemoveJob | Out-String).Trim()
376+
Write-Verbose "Output from evaluating checkver.script:`r`n$page"
377+
$source = 'the output of checkver.script'
324378
}
325379

326-
if ($null -eq $page) {
380+
if (!$page) {
327381
next "couldn't retrieve content from $source"
328382
continue
329383
}

0 commit comments

Comments
 (0)