microsoft / azure-pipelines-agent

Azure Pipelines Agent 🚀
MIT License
1.7k stars 857 forks source link

[BUG]: ProcessInvokerWrapper does not support Constrained Language Mode #4492

Open timbrigham-oc opened 8 months ago

timbrigham-oc commented 8 months ago

What happened?

While attempting to run any of the common tasks (script, powershell, powershell@2) the following error occurs on WDAC & CLM enabled devices. This makes using the Azure DevOps agent to test on endpoints hardened with this technology impossible. Please note that this is not a problem with a single task. I believe that this does not belong with the team at Azure-Pipelines-Tasks as a result. Using ProcMon while executing a pipeline shows definitively that Agent.Worker.exe is responsible for spawning a PowerShell instance with dynamic, unsigned code via the prompt instead of a script file.

The error received is the same for all tasks:
Cannot invoke method. Method invocation is supported only on core types in this language mode.

On analysis of the logs, the ProcessInvokerWrapper statement from the worker log shows that there is code being executed directly through a PowerShell prompt, included in the bug report.

Since this code is being executed from the PowerShell prompt and not a script file it isn't signable. Not signable dynamic code equals CLM. CLM in use results in "Method invocation is supported only on core types in this language mode."

The method invocation failure results in not being able to test real world hardened devices using a ADO pipeline.

Versions

This has been received on agents from version 2.1.x (historical device which is now decommissioned) until 3.227.2. Occurs with any Windows operating system with WDAC and CLM enabled.

Environment type (Please select at least one enviroment where you face this issue)

Azure DevOps Server type

dev.azure.com (formerly visualstudio.com)

Azure DevOps Server Version (if applicable)

No response

Operation system

Win 10, Win 11, Server 2019, Server 2022

Version controll system

git

Relevant log output

ProcessInvokerWrapper] Starting process:
ProcessInvokerWrapper]   File name: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
ProcessInvokerWrapper]   Arguments: '-NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". ([scriptblock]::Create('if ([Console]::InputEncoding -is [Text.UTF8Encoding] -and [Console]::InputEncoding.GetPreamble().Length -ne 0) { [Console]::InputEncoding = New-Object Text.UTF8Encoding $false } if (!$PSHOME) { $null = Get-Item -LiteralPath ''variable:PSHOME'' } else { Import-Module -Name ([System.IO.Path]::Combine($PSHOME, ''Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1'')) ; Import-Module -Name ([System.IO.Path]::Combine($PSHOME, ''Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1'')) }')) 2>&1 | ForEach-Object { Write-Verbose $_.Exception.Message -Verbose } ; Import-Module -Name 'C:\agent\_work\_tasks\CmdLine_d9bafed4-0b18-4f58-968d-86655b4d2ce9\2.229.0\ps_modules\VstsTaskSdk\VstsTaskSdk.psd1' -ArgumentList @{ NonInteractive = $true } -ErrorAction Stop ; $VerbosePreference = 'SilentlyContinue' ; $DebugPreference = 'SilentlyContinue' ; Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create('. ''C:\agent\_work\_tasks\CmdLine_d9bafed4-0b18-4f58-968d-86655b4d2ce9\2.229.0\cmdline.ps1'''))"'
ProcessInvokerWrapper]   Working directory: 'C:\agent\_work\_tasks\CmdLine_d9bafed4-0b18-4f58-968d-86655b4d2ce9\2.229.0'
ProcessInvokerWrapper]   Require exit code zero: 'True'
ProcessInvokerWrapper]   Encoding web name:  ; code page: ''
ProcessInvokerWrapper]   Force kill process on cancellation: 'False'
ProcessInvokerWrapper]   Redirected STDIN: 'False'
ProcessInvokerWrapper]   Persist current code page: 'True'
ProcessInvokerWrapper]   Keep redirected STDIN open: 'False'
ProcessInvokerWrapper]   High priority process: 'False'
ProcessInvokerWrapper]   ContinueAfterCancelProcessTreeKillAttempt: 'True'
ProcessInvokerWrapper] OOM score adjustment is Linux-only.
ProcessInvokerWrapper] Process started with process id 7540, waiting for process exit.
timbrigham-oc commented 8 months ago

It looks like the arguments for this are call to powershell.exe are built in PowerShell3Handler.cs starting on line 49 (for the current master release), which is then used on a call to StepHone.ExecuteAsync passing this string as an argument.

I propose either writing this set of commands to disk, and making the argument passed to powershell.exe the name of the file being referenced. Ideally this created script should be signed, but as long as it exists on disk on an identifiable path it can be whitelisted for WDAC / CLM enforcement.

A better alternative would be a signed, parameterized PowerShell script that is designed to take the four arguments being passed to build this string.

vmapetr commented 8 months ago

Hi @timbrigham-oc, thanks for reporting! We are working on more prioritized issues at the moment but will get back to this one soon.

timbrigham-oc commented 8 months ago

@vmapetr, I think we can close this ticket. I've submitted a pull request to address this issue.

timbrigham-oc commented 4 months ago

It's been four months since I submitted this pull request and it hasn't been merged. Needs to be in the backlog.