Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/System.Management.Automation/security/SecurityManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
/// </summary>
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Import-Module HelpersCommon
#
# These are general tests that verify non-Windows behavior
#
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -230,9 +245,11 @@ try {
function createTestFile
{
param (
[Parameter(Mandatory)]
[string]
$FilePath,

[Parameter(Mandatory)]
[int]
$FileType,

Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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> Module should throw '<error>'" -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" {
Expand Down Expand Up @@ -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> Module should throw '<error>'" -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 <testScript> Script should throw '<error>'" -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) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, $testScripts was not defined in this scope, so nothing was being run.

Test-AllSignedExecutionPolicy $testScript $error
It "$TestTypePrefix Running <testScript> Script should throw '<error>'" -TestCases $testData {
param([string]$testScript, [string]$error)
$testScript | should exist
if($error)
{
{& $testScript} | ShouldBeErrorId $error
}
else
{
{& $testScript} | Should Not throw
}
}
}
}
Expand Down