Skip to content

Commit 64b948d

Browse files
committed
fix(software): stop shelling the 7-Zip uninstall string through cmd
The NanaZip swap read 7-Zip's QuietUninstallString from HKLM and interpolated it straight into cmd /c, executing registry-sourced text as admin. Parse it with Get-AtlasParsedUninstallString (accepting only the documented "<quoted exe>" [args] shape) and launch the executable directly; an unrecognized string logs a warning and keeps the existing 7-Zip install rather than running anything.
1 parent 78e7860 commit 64b948d

2 files changed

Lines changed: 66 additions & 1 deletion

File tree

playbook/Executables/AtlasModules/Scripts/Modules/Atlas.Software/Domain/Installers.ps1

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,25 @@ function Install-AtlasNanaZip {
311311
}
312312
}
313313

314+
function Get-AtlasParsedUninstallString {
315+
# Parse a registry QuietUninstallString into an executable + argument string
316+
# WITHOUT ever handing it to a shell. Accept only the documented 7-Zip shape:
317+
# "<quoted exe path>" [args]. Anything unquoted or otherwise malformed (which
318+
# includes metacharacter-bearing strings like `C:\x.exe & calc`) returns $null
319+
# so callers can decline safely rather than execute registry-sourced text.
320+
param([Parameter(Mandatory = $true)][string]$UninstallString)
321+
322+
if ($UninstallString -match '^"([^"]+)"\s*(.*)$') {
323+
$uninstallArgs = if ($Matches[2]) { $Matches[2] } else { '/S' }
324+
return @{
325+
FilePath = $Matches[1]
326+
ArgumentList = $uninstallArgs
327+
}
328+
}
329+
330+
return $null
331+
}
332+
314333
function Install-AtlasArchiveTool {
315334
param([Parameter(Mandatory = $true)][string]$TempDir)
316335

@@ -334,7 +353,15 @@ NanaZip is a fork of 7-Zip with an updated user interface and extra features.
334353
if ((Read-MessageBox -Title 'Installing NanaZip - Atlas' -Body $message -Icon Question) -eq 'Yes') {
335354
$sevenZipUninstall = (Get-ItemProperty -Path $sevenZipRegistry -Name 'QuietUninstallString' -ErrorAction SilentlyContinue).QuietUninstallString
336355
if ($sevenZipUninstall) {
337-
Start-AtlasSoftwareInstaller -FilePath 'cmd.exe' -ArgumentList ("/c $sevenZipUninstall") -Description '7-Zip removal'
356+
# QuietUninstallString is machine-writable data; never feed it to a shell.
357+
$parsedUninstall = Get-AtlasParsedUninstallString -UninstallString $sevenZipUninstall
358+
if ($parsedUninstall) {
359+
Start-AtlasSoftwareInstaller -FilePath $parsedUninstall.FilePath -ArgumentList $parsedUninstall.ArgumentList -Description '7-Zip removal'
360+
}
361+
else {
362+
Write-AtlasLog -Level Warning -Message "Unrecognized 7-Zip QuietUninstallString format; keeping the existing 7-Zip installation."
363+
return
364+
}
338365
}
339366
Install-AtlasNanaZip -TempDir $TempDir -Assets $assets
340367
}

tests/Atlas.Software.Tests.ps1

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,41 @@ Describe 'Install-AtlasArchiveTool asset selection' {
399399
}
400400
}
401401
}
402+
403+
Describe 'the 7-Zip uninstall-string parser' {
404+
# Get-AtlasParsedUninstallString accepts only the documented, quoted 7-Zip shape
405+
# and never hands registry-sourced text to a shell; it is module-private, hence
406+
# InModuleScope.
407+
408+
It 'parses a quoted exe path with trailing arguments' {
409+
InModuleScope Atlas.Software {
410+
$parsed = Get-AtlasParsedUninstallString -UninstallString '"C:\Program Files\7-Zip\Uninstall.exe" /S'
411+
412+
$parsed | Should -Not -BeNullOrEmpty
413+
$parsed.FilePath | Should -Be 'C:\Program Files\7-Zip\Uninstall.exe'
414+
$parsed.ArgumentList | Should -Be '/S'
415+
}
416+
}
417+
418+
It 'defaults the arguments to /S when only a quoted path is present' {
419+
InModuleScope Atlas.Software {
420+
$parsed = Get-AtlasParsedUninstallString -UninstallString '"C:\Program Files\7-Zip\Uninstall.exe"'
421+
422+
$parsed | Should -Not -BeNullOrEmpty
423+
$parsed.FilePath | Should -Be 'C:\Program Files\7-Zip\Uninstall.exe'
424+
$parsed.ArgumentList | Should -Be '/S'
425+
}
426+
}
427+
428+
It 'rejects an unquoted, metacharacter-bearing string' {
429+
InModuleScope Atlas.Software {
430+
Get-AtlasParsedUninstallString -UninstallString 'C:\x.exe & calc' | Should -BeNullOrEmpty
431+
}
432+
}
433+
434+
It 'rejects an empty-quotes-free string' {
435+
InModuleScope Atlas.Software {
436+
Get-AtlasParsedUninstallString -UninstallString 'C:\Program Files\7-Zip\Uninstall.exe /S' | Should -BeNullOrEmpty
437+
}
438+
}
439+
}

0 commit comments

Comments
 (0)