2018-11-14 01:16:29 +01:00
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
##
## ----------
## Test Note:
## ----------
## Since these tests change session and system state (constrained language and system lockdown)
## they will all use try/finally blocks instead of Pester AfterEach/AfterAll to ensure session
## and system state is restored.
## Pester AfterEach, AfterAll is not reliable when the session is constrained language or locked down.
##
Import-Module HelpersSecurity
try
{
$defaultParamValues = $PSDefaultParameterValues . Clone ( )
$PSDefaultParameterValues [ " it:Skip " ] = ! $IsWindows
Describe " Trusted module on locked down machine should not expose private functions to script debugger command processing " -Tags 'CI' , 'RequireAdminOnWindows' {
BeforeAll {
# Debugger test type definition
$debuggerTestTypeDef = @ '
using System ;
using System . Management . Automation ;
using System . Management . Automation . Runspaces ;
namespace TestRunner
{
public class DebuggerTester
{
private Runspace _runspace ;
private readonly string _privateFnName ;
[ Flags ]
public enum TestResults
{
NoResult = 0x0 ,
DebuggerStopHandled = 0x1 ,
PrivateFnFound = 0x2
} ;
public TestResults TestResult
{
private set ;
get ;
}
public Exception ScriptException
{
private set ;
get ;
}
public DebuggerTester ( Runspace runspace , string privateFnName )
{
if ( runspace . Debugger = = null )
{
throw new PSArgumentException ( " The provided runspace script debugger cannot be null for test. " ) ;
}
_runspace = runspace ;
_privateFnName = privateFnName ;
_runspace . Debugger . DebuggerStop + = ( sender , args ) = >
{
try
{
/ / Within the debugger stop handler , make sure trusted private function s are not accessible .
string commandText = string . Format ( @ " Get-Command "" {0} "" " , _privateFnName ) ;
PSCommand command = new PSCommand ( ) ;
command . AddCommand ( new Command ( commandText , true ) ) ;
PSDataCollection < PSObject > output = new PSDataCollection < PSObject > ( ) ;
_runspace . Debugger . ProcessCommand ( command , output ) ;
if ( ( output . Count > 0 ) & & ( output [ 0 ] . BaseObject is CommandInfo ) )
{
TestResult | = TestResults . PrivateFnFound ;
}
}
catch ( Exception e )
{
ScriptException = e ;
System . Console . WriteLine ( e . Message ) ;
}
TestResult | = TestResults . DebuggerStopHandled ;
} ;
}
}
}
' @
$modulePath = Join-Path $TestDrive Modules
if ( Test-Path -Path $modulePath )
{
try { Remove-Item -Path $modulePath -Recurse -Force -ErrorAction SilentlyContinue } catch { }
}
# Trusted module
$trustedModuleName = " TrustedModule_System32 "
$trustedModuleDirectory = Join-Path $modulePath $trustedModuleName
New-Item -ItemType Directory -Path $trustedModuleDirectory -Force -ErrorAction SilentlyContinue
$trustedModuleFilePath = Join-Path $trustedModuleDirectory " $( $trustedModuleName ) .psm1 "
$trustedManifestFilePath = Join-Path $trustedModuleDirectory " $( $trustedModuleName ) .psd1 "
@ '
function PublicFn {
Write-Output PrivateFn " PublicFn "
}
function PrivateFn {
param ( [ string ] $msg )
Write-Output $msg
}
' @ > $trustedModuleFilePath
$modManifest = " @{ ModuleVersion = '1.0' " + ( " ; RootModule = '{0}' " -f $trustedModuleFilePath ) + " ; FunctionsToExport = 'PublicFn' } "
$modManifest > $trustedManifestFilePath
# Create test runspace
[ runspace ] $runspace = [ runspacefactory ] :: CreateRunspace ( )
$runspace . Open ( )
# Create debugger test object
Add-Type -TypeDefinition $debuggerTestTypeDef
}
AfterAll {
if ( $runspace -ne $null ) { $runspace . Dispose ( ) }
}
It " Verifies that private trusted module function is not available in script debugger " {
# Run debugger access test
$debuggerTester = [ TestRunner.DebuggerTester ] :: new ( $runspace , " PrivateFn " )
# Script to invoke the script debugger so that $debuggerTester can handle
# the debugger stop event and test for access of private functions within the
# script debugger command processor.
$script = @ '
Import-Module -Name HelpersSecurity
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$ExecutionContext . SessionState . LanguageMode = " ConstrainedLanguage "
2018-12-06 03:29:50 +01:00
Import-Module -Name { 0 } -Force
2018-11-14 01:16:29 +01:00
Set-PSBreakpoint -Command PublicFn
PublicFn
2018-12-06 03:29:50 +01:00
' @ -f $trustedManifestFilePath
2018-11-14 01:16:29 +01:00
[ powershell ] $ps = [ powershell ] :: Create ( )
$ps . Runspace = $runspace
try
{
$ps . AddScript ( $script ) . BeginInvoke ( )
# Wait for debugger test result for up to ten seconds
$count = 0
while ( ( $debuggerTester . TestResult -eq 0 ) -and ( $count + + -lt 40 ) )
{
Start-Sleep -Milliseconds 250
}
}
finally
{
# Revert lockdown
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
# Verify that PrivateFn function name is not accessible
$debuggerTester . TestResult | Should -Match " DebuggerStopHandled "
$debuggerTester . TestResult | Should -Not -Match " PrivateFnFound "
$debuggerTester . ScriptException | Should -BeNullOrEmpty
}
}
Describe " Cross language debugger get-item commands should not have access to FullLanguage trusted functions through provider " -Tags 'Feature' , 'RequireAdminOnWindows' {
BeforeAll {
# Trusted module that will always run in FullLanguage mode
$scriptModuleName = " ImportTrustedModuleForTestA_System32 "
$moduleFilePath = Join-Path $TestDrive ( $scriptModuleName + " .psm1 " )
$script = @ '
function PublicFn { " PublicFn " ; PrivateFn }
function PrivateFn { " PrivateFn " }
Export-ModuleMember -Function PublicFn
' @
$script > $moduleFilePath
# Import and run module function script
$scriptIM = @ '
Import-Module -Name { 0 } -Force
$null = Set-PSBreakpoint -command PublicFn
PublicFn
' @ -f $moduleFilePath
# Debugger stop event handler object.
$type = @ '
using System ;
using System . Collections . Generic ;
using System . Collections . ObjectModel ;
using System . Management . Automation ;
using System . Management . Automation . Runspaces ;
public class DebuggerStopEventHandler
{
private Runspace _runspace ;
public object GetItemResult
{
get ;
internal set ;
}
public object GetChildItemResult
{
get ;
internal set ;
}
public object CopyItemResult
{
get ;
internal set ;
}
public object Function VariableResult
{
get ;
internal set ;
}
public object RenameItemResult
{
get ;
internal set ;
}
public DebuggerStopEventHandler ( Runspace runspace )
{
_runspace = runspace ;
_runspace . Debugger . DebuggerStop + = ( sender , args ) = >
{
var debugger = sender as Debugger ;
PSDataCollection < PSObject > output = new PSDataCollection < PSObject > ( ) ;
PSCommand command = new PSCommand ( ) ;
command . AddScript ( @ " Get-Item -Path function:\PrivateFn 2>&1 " ) ;
debugger . ProcessCommand ( command , output ) ;
GetItemResult = ( output . Count > 0 ) ? ( output [ 0 ] . BaseObject ) : null ;
command . Clear ( ) ;
output . Clear ( ) ;
command . AddScript ( @ " Get-ChildItem -Path function:\PrivateFn 2>&1 " ) ;
debugger . ProcessCommand ( command , output ) ;
GetChildItemResult = ( output . Count > 0 ) ? ( output [ 0 ] . BaseObject ) : null ;
command . Clear ( ) ;
output . Clear ( ) ;
command . AddScript ( @ " Copy-Item -Path function:\PrivateFn -Destination function:\MyPrivateFn 2>&1 " ) ;
debugger . ProcessCommand ( command , output ) ;
CopyItemResult = ( output . Count > 0 ) ? ( output [ 0 ] . BaseObject ) : null ;
command . Clear ( ) ;
output . Clear ( ) ;
command . AddScript ( @ " $ {function:\PrivateFn} " ) ;
debugger . ProcessCommand ( command , output ) ;
Function VariableResult = ( output . Count > 0 ) ? ( output [ 0 ] . BaseObject ) : null ;
command . Clear ( ) ;
output . Clear ( ) ;
command . AddScript ( @ " Rename-Item -Path function:\PrivateFn -NewName function:\MyPrivateFn -Passthru 2>&1 " ) ;
debugger . ProcessCommand ( command , output ) ;
RenameItemResult = ( output . Count > 0 ) ? ( output [ 0 ] . BaseObject ) : null ;
} ;
}
public void Reset ( ) { GetItemResult = null ; GetChildItemResult = null ; CopyItemResult = null ; }
}
' @
try { Add-Type -TypeDefinition $type } catch { }
# Create runspace and debugger event handler
[ runspace ] $rs = [ runspacefactory ] :: CreateRunspace ( $host )
$rs . Open ( )
$rs . Debugger . SetDebugMode ( @ ( 'LocalScript' , 'RemoteScript' ) )
$debuggerStopHandler = [ DebuggerStopEventHandler ] :: New ( $rs )
# Create PowerShell to run module script
[ powershell ] $ps = [ powershell ] :: Create ( )
$ps . Runspace = $rs
$ps . AddScript ( $scriptIM )
}
AfterAll {
if ( $rs -ne $null ) { $rs . Dispose ( ) }
if ( $ps -ne $null ) { $ps . Dispose ( ) }
}
It " Verifies that same language mode trusted public functions *are* accessible from debugger through Get-Item, Get-ChildItem, Copy-Item, Rename-Item, Variable " {
# Test
$results = $ps . Invoke ( )
# Results. Only PublicFn is returned since PrivateFn is renamed.
$results [ 0 ] | Should Be " PublicFn "
# Expected Get-Item function:\PrivateFn returns FunctionInfo object
( $debuggerStopHandler . GetItemResult -is [ System.Management.Automation.FunctionInfo ] ) | Should Be $true
# Expected Get-ChildItem function:\PrivateFn returns FunctionInfo object
( $debuggerStopHandler . GetChildItemResult -is [ System.Management.Automation.FunctionInfo ] ) | Should Be $true
# Expected Copy-Item function:\PrivateFn succeeds with no error output
$debuggerStopHandler . CopyItemResult | Should Be $null
# Expected function variable succeeds
( $debuggerStopHandler . FunctionVariableResult -is [ scriptblock ] ) | Should Be $true
# Expected Rename-Item function:\PrivateFn returns FunctionInfo object
( $debuggerStopHandler . RenameItemResult -is [ System.Management.Automation.FunctionInfo ] ) | Should Be $true
}
It " Verifies that cross language mode trusted public functions *are not* accessible through Get-Item, Get-ChildItem, Copy-Item, Rename-Item, Variable " {
# Test
$debuggerStopHandler . Reset ( )
try
{
$rs . LanguageMode = " ConstrainedLanguage "
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$results = $ps . Invoke ( )
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
# Results
$results [ 0 ] | Should Be " PublicFn "
$results [ 1 ] | Should Be " PrivateFn "
# Expected Get-Item function:\PrivateFn returns error
$debuggerStopHandler . GetItemResult . FullyQualifiedErrorId | Should Be " PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand "
# Expected Get-ChildItem function:\PrivateFn returns error
$debuggerStopHandler . GetChildItemResult . FullyQualifiedErrorId | Should Be " PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand "
# Expected Copy-Item fails with error output
$debuggerStopHandler . CopyItemResult . FullyQualifiedErrorId | Should Be " PathNotFound,Microsoft.PowerShell.Commands.CopyItemCommand "
# Expected function variable fails
$debuggerStopHandler . FunctionVariableResult | Should Be $null
# Expected Rename-Item function:\PrivateFn fails with error
$debuggerStopHandler . RenameItemResult . FullyQualifiedErrorId | Should Be " PathNotFound,Microsoft.PowerShell.Commands.RenameItemCommand "
}
}
Describe " Cross language debugger Action scripts should not have access to FullLanguage trusted functions through provider " -Tags 'Feature' , 'RequireAdminOnWindows' {
BeforeAll {
# Trusted script that will always run in FullLanguage mode
$scriptFileName = " TrustedScriptForTestB_System32 "
$scriptFilePath = Join-Path $TestDrive ( $scriptFileName + " .ps1 " )
$script = @ '
function PublicFn { PrivateFn -typeDef 'public class Hello { public new static void ToString() { System.Console.WriteLine("Hello!"); } }' ; [ Hello ] :: ToString ( ) ; }
function PrivateFn { param ( [ string ] $typeDef ) Add-Type -TypeDefinition $typeDef }
PublicFn
" Complete "
' @
$script > $scriptFilePath
}
AfterAll {
Get-PSBreakpoint | Remove-PSBreakpoint
}
It " Verifies that debugger stop Action scriptblock cannot access PrivateFn " {
try
{
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
$ExecutionContext . SessionState . LanguageMode = " ConstrainedLanguage "
# Set breakpoint on script
Set-PSBreakpoint -Script $scriptFilePath -Line 4 -Action {
& ( Get-Item -Path function : \ PrivateFn ) -typeDef @ '
public class Foo {
public new static void ToString ( ) {
System . Console . WriteLine ( " pwnd! " ) ;
}
}
' @
}
# Run script
& $scriptFilePath
}
finally
{
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
}
try
{
# Verify that Action scriptblock did not create Foo type using PrivateFn
[ Foo ] :: ToString ( )
throw " No Exception! "
}
catch
{
$_ . FullyQualifiedErrorId | Should Be " TypeNotFound "
}
}
}
}
finally
{
if ( $defaultParamValues -ne $null )
{
$Global:PSDefaultParameterValues = $defaultParamValues
}
}