diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs index 35608783ae8..4b1096c40de 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs @@ -63,7 +63,6 @@ internal class RunspacePoolInternal /// The explicit PSHost implementation. /// /// - /// RunspaceConfiguration is null. /// Host is null. /// /// diff --git a/src/System.Management.Automation/security/SecurityManager.cs b/src/System.Management.Automation/security/SecurityManager.cs index e4c79e2e72e..19dcfdbe13b 100644 --- a/src/System.Management.Automation/security/SecurityManager.cs +++ b/src/System.Management.Automation/security/SecurityManager.cs @@ -48,7 +48,7 @@ namespace Microsoft.PowerShell /// Unrestricted - No files must be signed. If a file originates from the /// internet, Monad provides a warning prompt to alert the user. To /// suppress this warning message, right-click on the file in File Explorer, - /// select "Properties," and then "Unblock." + /// select "Properties," and then "Unblock." Requires Shell. /// Bypass - No files must be signed, and internet origin is not verified /// /// @@ -136,10 +136,6 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r if (!IsSupportedExtension(fi.Extension)) return true; - // Product binaries are always trusted - if (SecuritySupport.IsProductBinary(path)) - return true; - // Get the execution policy _executionPolicy = SecuritySupport.GetExecutionPolicy(_shellId); @@ -189,6 +185,11 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r #endif if (_executionPolicy == ExecutionPolicy.Unrestricted) { + // Product binaries are always trusted + // This avoids signature and security zone checks + if (SecuritySupport.IsProductBinary(path)) + return true; + // We need to give the "Remote File" warning // if the file originated from the internet if (!IsLocalFile(fi.FullName)) diff --git a/test/csharp/test_FileSystemProvider.cs b/test/csharp/test_FileSystemProvider.cs index 5ad95a6daf5..78631251692 100644 --- a/test/csharp/test_FileSystemProvider.cs +++ b/test/csharp/test_FileSystemProvider.cs @@ -37,9 +37,8 @@ private ExecutionContext GetExecutionContext() { CultureInfo currentCulture = CultureInfo.CurrentCulture; PSHost hostInterface = new DefaultHost(currentCulture,currentCulture); - RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create(); InitialSessionState iss = InitialSessionState.CreateDefault2(); - AutomationEngine engine = new AutomationEngine(hostInterface, runspaceConfiguration, iss); + AutomationEngine engine = new AutomationEngine(hostInterface, iss); ExecutionContext executionContext = new ExecutionContext(engine, hostInterface, iss); return executionContext; } diff --git a/test/csharp/test_SessionState.cs b/test/csharp/test_SessionState.cs index 7558c1b6789..eec20153772 100644 --- a/test/csharp/test_SessionState.cs +++ b/test/csharp/test_SessionState.cs @@ -20,9 +20,8 @@ public void TestDrives() { CultureInfo currentCulture = CultureInfo.CurrentCulture; PSHost hostInterface = new DefaultHost(currentCulture,currentCulture); - RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create(); InitialSessionState iss = InitialSessionState.CreateDefault2(); - AutomationEngine engine = new AutomationEngine(hostInterface, runspaceConfiguration, iss); + AutomationEngine engine = new AutomationEngine(hostInterface, iss); ExecutionContext executionContext = new ExecutionContext(engine, hostInterface, iss); SessionStateInternal sessionState = new SessionStateInternal(executionContext); Collection drives = sessionState.Drives(null); diff --git a/test/powershell/Installer/WindowsInstaller.Tests.ps1 b/test/powershell/Installer/WindowsInstaller.Tests.ps1 index 0084671ab66..759869784a5 100644 --- a/test/powershell/Installer/WindowsInstaller.Tests.ps1 +++ b/test/powershell/Installer/WindowsInstaller.Tests.ps1 @@ -9,13 +9,13 @@ Describe "Windows Installer" -Tags "Scenario" { @{ Name = "WMF 5.1"; Url = "https://www.microsoft.com/download/details.aspx?id=54616" } ) } - + It "WiX (Windows Installer XML) file contains pre-requisites link $preRequisitesLink" { $wixProductFile = Join-Path -Path $PSScriptRoot -ChildPath "..\..\..\assets\Product.wxs" (Get-Content $wixProductFile -Raw).Contains($preRequisitesLink) | Should Be $true } - It "Pre-Requisistes link for '' is reachable" -TestCases $linkCheckTestCases -Test { + It "Pre-Requisistes link for '' is reachable: " -TestCases $linkCheckTestCases -Test { param ($Url) # Because an outdated link 'https://www.microsoft.com/download/details.aspx?id=504100000' would still return a 200 reponse (due to a redirection to an error page), it only checks that it returns something diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1 index 406f0fd2d0c..debe15d28aa 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1 @@ -1,3 +1,4 @@ +Import-Module HelpersCommon # # These are general tests that verify non-Windows behavior # @@ -95,6 +96,8 @@ try { $LocalSignatureCorruptedScript = Join-Path -Path $remoteTestDirectory -ChildPath LocalSignatureCorruptedScript.ps1 $LocalSignedScript = Join-Path -Path $remoteTestDirectory -ChildPath LocalSignedScript.ps1 $LocalUnsignedScript = Join-Path -Path $remoteTestDirectory -ChildPath LocalUnsignedScript.ps1 + $PSHomeUnsignedModule = Join-Path -Path $PSHome -ChildPath 'Modules' -AdditionalChildPath 'LocalUnsignedModule', 'LocalUnsignedModule.psm1' + $PSHomeUntrustedModule = Join-Path -Path $PSHome -ChildPath 'Modules' -AdditionalChildPath 'LocalUntrustedModule', 'LocalUntrustedModule.psm1' $TrustedSignatureCorruptedScript = Join-Path -Path $remoteTestDirectory -ChildPath TrustedSignatureCorruptedScript.ps1 $TrustedSignedScript = Join-Path -Path $remoteTestDirectory -ChildPath TrustedSignedScript.ps1 $TrustedUnsignedScript = Join-Path -Path $remoteTestDirectory -ChildPath TrustedUnsignedScript.ps1 @@ -169,6 +172,18 @@ try { AddSignature = $false Corrupted = $false } + @{ + FilePath = $PSHomeUnsignedModule + FileType = $fileType.Local + AddSignature = $false + Corrupted = $false + } + @{ + FilePath = $PSHomeUntrustedModule + FileType = $fileType.Untrusted + AddSignature = $false + Corrupted = $false + } @{ FilePath = $TrustedSignatureCorruptedScript FileType = $fileType.Trusted @@ -230,9 +245,11 @@ try { function createTestFile { param ( + [Parameter(Mandatory)] [string] $FilePath, + [Parameter(Mandatory)] [int] $FileType, @@ -243,7 +260,14 @@ try { $Corrupted ) - $null = New-Item -Path $filePath -ItemType File + $folder = Split-Path -Path $FilePath + # create folder if it doesn't already exist + if(!(Test-Path $folder)) + { + $null = New-Item -Path $folder -ItemType Directory + } + + $null = New-Item -Path $filePath -ItemType File -Force $content = "`"Hello`"" + "`r`n" if($AddSignature) @@ -460,6 +484,34 @@ ZoneId=$FileType #Get Execution Policy $originalExecPolicy = Get-ExecutionPolicy $originalExecutionPolicy = $originalExecPolicy + + $archiveSigned = $false + $archivePath = Get-Module -ListAvailable Microsoft.PowerShell.Archive -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path + if($archivePath) + { + $archiveFolder = Split-Path -Path $archivePath + + # get all the certs used to sign the module + $script:archiveAllCert = Get-ChildItem -File -Path (Join-Path -Path $archiveFolder -ChildPath '*') -Recurse | + Get-AuthenticodeSignature + + # filter only to valid signatures + $script:archiveCert = $script:archiveAllCert | + Where-Object { $_.status -eq 'Valid'} | + Select-Object -Unique -ExpandProperty SignerCertificate + + # if we have valid signatures, add them to trusted publishers so powershell will trust them. + if($script:archiveCert) + { + $store = [System.Security.Cryptography.X509Certificates.X509Store]::new([System.Security.Cryptography.X509Certificates.StoreName]::TrustedPublisher,[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + $archiveCert | ForEach-Object { + $store.Add($_) + } + $store.Close() + $archiveSigned = $true + } + } } } AfterAll { @@ -472,6 +524,15 @@ ZoneId=$FileType } } + Context "Prereq: Validate that 'Microsoft.PowerShell.Archive' is signed" { + It "'Microsoft.PowerShell.Archive' should have a signature" { + $script:archiveAllCert | should not be null + } + It "'Microsoft.PowerShell.Archive' should have a valid signature" { + $script:archiveCert | should not be null + } + } + Context "Validate that 'Restricted' execution policy works on OneCore powershell" { BeforeAll { @@ -598,6 +659,36 @@ ZoneId=$FileType foreach($testScript in $testScripts) { Test-UnrestrictedExecutionPolicy $testScript $expected } + + $error = "UnauthorizedAccess,Microsoft.PowerShell.Commands.ImportModuleCommand" + $testData = @( + @{ + module = $PSHomeUntrustedModule + error = $null + } + @{ + module = $PSHomeUnsignedModule + error = $null + } + @{ + module = "Microsoft.PowerShell.Archive" + error = $null + } + ) + + $TestTypePrefix = "Test 'Unrestricted' execution policy." + It "$TestTypePrefix Importing Module should throw ''" -TestCases $testData { + param([string]$module, [string]$error) + $testScript = {Import-Module -Name $module -Force} + if($error) + { + $testScript | ShouldBeErrorId $error + } + else + { + $testScript | Should Not throw + } + } } Context "Validate that 'ByPass' execution policy works on OneCore powershell" { @@ -813,102 +904,134 @@ ZoneId=$FileType } } - function Test-AllSignedExecutionPolicy { - - param($testScript, $error) - - $TestTypePrefix = "Test 'AllSigned' execution policy." - - It "$TestTypePrefix Running $testScript script should return $error" { + $TestTypePrefix = "Test 'AllSigned' execution policy." - $scriptName = $testScript - - $exception = $null - try - { - & $scriptName - } - catch - { - $exception = $_ - } - $errorType = $null - - if($null -ne $exception) - { - $errorType = $exception.exception.getType() - } - - $result = $errorType + $error = "UnauthorizedAccess,Microsoft.PowerShell.Commands.ImportModuleCommand" + $testData = @( + @{ + module = $PSHomeUntrustedModule + error = $error + } + @{ + module = $PSHomeUnsignedModule + error = $error + } + @{ + module = "Microsoft.PowerShell.Archive" + error = $null + } + ) - $result | Should be $error + It "$TestTypePrefix Importing Module should throw ''" -TestCases $testData { + param([string]$module, [string]$error) + $testScript = {Import-Module -Name $module -Force} + if($error) + { + $testScript | ShouldBeErrorId $error + } + else + { + $testScript | Should Not throw } } - $error = "System.Management.Automation.PSSecurityException" + + $error = "UnauthorizedAccess" + $pendingTestData = @( + # The following files are not signed correctly when generated, so we will skip for now + # filed https://github.com/PowerShell/PowerShell/issues/5559 + @{ + testScript = $MyComputerSignedScript + error = $null + } + @{ + testScript = $UntrustedSignedScript + error = $null + } + @{ + testScript = $TrustedSignedScript + error = $null + } + @{ + testScript = $LocalSignedScript + error = $null + } + @{ + testScript = $IntranetSignedScript + error = $null + } + @{ + testScript = $InternetSignedScript + error = $null + } + ) + It "$TestTypePrefix Running Script should throw ''" -TestCases $pendingTestData -Pending {} + $testData = @( @{ - testScript = $LocalUnsignedScript - expected = $null + testScript = $InternetSignatureCorruptedScript error = $error } @{ - testScript = $LocalSignatureCorruptedScript - expected = $null + testScript = $InternetUnsignedScript error = $error } @{ - testScript = $MyComputerUnsignedScript - expected = $null + testScript = $IntranetSignatureCorruptedScript error = $error } @{ - testScript = $MyComputerSignatureCorruptedScript - expected = $null + testScript = $IntranetSignatureCorruptedScript error = $error } @{ - testScript = $TrustedUnsignedScript - expected = $null + testScript = $IntranetUnsignedScript error = $error } @{ - testScript = $TrustedSignatureCorruptedScript - expected = $null + testScript = $LocalSignatureCorruptedScript error = $error } @{ - testScript = $IntranetUnsignedScript - expected = $null + testScript = $LocalUnsignedScript error = $error } @{ - testScript = $IntranetSignatureCorruptedScript - expected = $null + testScript = $TrustedSignatureCorruptedScript error = $error } @{ - testScript = $InternetUnsignedScript - expected = $null + testScript = $TrustedUnsignedScript error = $error } @{ - testScript = $InternetSignatureCorruptedScript - expected = $null + testScript = $UntrustedSignatureCorruptedScript error = $error } @{ testScript = $UntrustedUnsignedScript - expected = $null error = $error } @{ - testScript = $UntrustedSignatureCorruptedScript - expected = $null + testScript = $MyComputerSignatureCorruptedScript + error = $error + } + @{ + testScript = $MyComputerUnsignedScript error = $error } + ) - foreach($testScript in $testScripts) { - Test-AllSignedExecutionPolicy $testScript $error + It "$TestTypePrefix Running Script should throw ''" -TestCases $testData { + param([string]$testScript, [string]$error) + $testScript | should exist + if($error) + { + {& $testScript} | ShouldBeErrorId $error + } + else + { + {& $testScript} | Should Not throw + } } } }