PowerShell: Windows Defender has started to slowdown method calls in PowerShell dramatically
Prerequisites
- Write a descriptive title.
- Make sure you are able to repro it on the latest released version
- Search the existing issues.
- Refer to the FAQ.
- Refer to Differences between Windows PowerShell 5.1 and PowerShell.
Steps to reproduce
Run the following test.ps1 file with the latest Windows Defender signature version (e.g. 1.385.1608.0 at time of filing).
for ($i = 0; $i -lt 40000; $i++) {
$path = "C:\"
$fileName = [System.IO.Path]::GetFileName($path)
$fileExtension = [System.IO.Path]::GetExtension($path)
$parent = $path.TrimEnd("\")
$parent = [System.IO.Path]::GetDirectoryName($parent)
}
This script will take ~40ms with realtime protection turned off, vs ~8000ms with realtime protection turned on. The test script executes quickly with realtime protection turned on in PowerShell 5, however.
(Note: to address some pushback I received on reddit, yes, this is nonsense code doing nonsense work. The whole point is to demonstrate that just calling methods at all is taking a huge penalty all of the sudden.)
Furthermore, if I use my own infrastructure for timing code and execute the following:
Invoke-Timed { [void]"".IndexOf("") } -Count 10000
I see 60 microseconds for each call with realtime protection turned on, vs 1 microsecond with realtime protection turned off. That same [void]"".IndexOf("")
body can be placed into the for loop of the test.ps1 file to see the slowdown without my timing infrastructure.
It appears that the latest Windows Defender is significantly impacting method calls in PowerShell 7.
For a full repro, to make sure this isn’t just something wrong with my install:
- Perform a fresh windows install of Windows 11 Pro 22H2 from Win11_22H2_English_x64v1.iso, with windows11.0-kb5023778-x64_204c2f5ab1eb71d80eb470b5af079dd8e56c20e7.msu already slipstreamed in
- Install windows using a local account (so none of my MS account settings are being brought in)
- Execute
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force -Verbose
- Grab the latest winget: ms-windows-store://pdp/?productId=9NBLGGH4NNS1
- Install powershell 7.3.3:
winget install -e Microsoft.PowerShell -a x64 --accept-package-agreements --accept-source-agreements
- Observe that Windows Defender signature version is 1.321.69.0
- Run the test.ps1 file in PowerShell 5 and 7
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew(); powershell.exe -NoProfile -File "test.ps1"; Write-Host "Elapsed: $($stopwatch.ElapsedMilliseconds.ToString("N0"))ms"
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew(); pwsh.exe -NoProfile -File "test.ps1"; Write-Host "Elapsed: $($stopwatch.ElapsedMilliseconds.ToString("N0"))ms"
- Open Windows Defender and check for protection updates
- Observe that the signature version updates to e.g. 1.385.1608.0
- Wait for threat protection service to restart
- Re-execute the test.ps1 file in PowerShell 5 and 7
- Observe that the script remains fast in PowerShell 5, but dramatically slows down in 7
- Turn real-time protection off
- Observe that the fast script execution time is restored in 7
The issue appears to have started somewhere around signature version 1.385.1310.0, around March 27, 2023.
(Edit: reddit links removed. I couldn’t get anyone to try my repro test.ps1 code out, so they were of limited value. I’m still not sure if this issue is affecting anyone but me. It’s been confirmed below that I’m not the only one seeing this.)
While I understand this is probably an issue external to PowerShell 7, I thought the team should be aware and there’s no Windows Defender GitHub repo for me to file an issue against.
Expected behavior
Method calls like [void]"".IndexOf("") are fast, on the order of 1 microsecond, with Windows Defender realtime protection turned on.
Actual behavior
Method calls like [void]"".IndexOf("") are slow, on the order of 60 microseconds, with Windows Defender realtime protection turned on.
Error details
No response
Environment data
Name Value
---- -----
PSVersion 7.3.3
PSEdition Core
GitCommitId 7.3.3
OS Microsoft Windows 10.0.22621
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Visuals
No response
(Note: Latest signature version where I’m still seeing this: 1.389.2214.0)
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 9
- Comments: 30 (7 by maintainers)
We’ll pass this feedback to the Windows Defender team, but I would also suggest sending it via the Windows Feedback tool
The Windows Defender team has identified the issue and has a fix for this performance degradation, and the fix should be pushed by the end of May.
Defender has confirmed that this their issue and they are working on a fix.
I have been messing around with this further. The culprit is clearly the calls to AmsiNativeMethods.AmsiNotifyOperation. This call in and of itself is taking approximately ~40us to respond on each call, which is single-handedly causing the spike in latency. I have a version that I built which is caching the notifications sent to this method if they return the AMSI_RESULT_CLEAN result, as the documentation indicates that this return value should not change. I am caching with an LRU cache to only cache the last 500 calls (configurable) to limit the memory impact, though I am sure some logic between last recently used and most used would make more sense. This almost entirely mitigates the perf impact, and by placing the cache in the AmsiTools namespace, prevents manipulation of the cache in the same manner as the rest of the Amsi settings are protected. As I am unsure as to where the LRU cache implementation would be best located, I do not have a PR ready to submit for this, as I do think this entire topic needs further discussion to ensure a caching mechanism does not significantly increase the exposure of this very important security measure.
I had been chasing down a performance degredation in some of my existing modules/scripts over the last few weeks before I stumbled upon this issue. After doing a bit of digging, my issues seemed to closely align to this scenario so I wanted to take a closer look.
I performed a test using a local release build of 7.3.3 and then of the same tag with the AmsiNativeMethods.AmsiNotifyOperation removed (just setting its hr to 0) from the AmsiUtils.WinReportContent method. I then tested both builds with the following:
The version w/o AMSNofify ran 270x faster than the current release version:
This seems to indicate that indeed, the bottleneck is on the Amsi provider side (my test case was using Windows Defender as well)
I’m hoping we can get some official guidance on whether the remaining impact is considered acceptable and is the new normal for PowerShell 7.3+ going forward.
+1 I’ve been fighting this for a month or so now, and I also notice pwsh.exe hammering the Windows Defender registry key since this issue started.
I also have a test script that reproduces the issue. With RTP disabled, it takes 5 seconds, with RTP enabled, it takes 60 seconds.
I also run the script from https://github.com/PowerShell/PowerShell/issues/19431#issuecomment-1494528756 and see over 100x slowdown in execution time.
The AMSI method invocation logging was introduced in 7.3 and may play a role in this performance degradation. Reassign to the Security WG for evaluation and suggestions for next step.
I have the new version, but I’m still seeing the perf impact.
One of my scripts takes 0m:11s with RTP off (its original speed before all of this), but takes 1m:48s with RTP on. So nearly 10x slower. That is faster than it was when I first reported the issue, however, when it was around 50x slower.
That’s a real script too, btw, not repro code where I’m making useless method calls just to test the overhead.
@User1785604260 you can always close your own issues, but since a resolution label is set, our bot will close it automatically in a few days
@User1785604260,
Yes, for your test: With realtime protection on:
~34000ms
With realtime protection off:~200ms
I noticed the same behavior as I was busy trying to improve the performance of my script which appeared suddenly a lot slower. As these performance tests, I created recently, appeared now about 5 times slower…