GetRef's memory consumption (garbage collection) changed with KB4525236
Asked Answered
H

3

14

We experience out-of-memory issues after installing KB4525236 on our Windows 2016 Servers/Windows 10 Clients. This security fix seems to have changed the moment when memory is garbage collected when calling a function through GetRef.

Pré KB4525236

Each instance created in a function called through GetRef got garbage collected as soon as the instance variable was set to nothing

Post KB4525236

Each instance created in a function called through GetRef remains in memory and is garbage collected only when the entire function completes. When creating instances in a loop, this can quickly add up and lead to an out-of-memory, especially in a 32-bit process.

Questions

  • we can not find anything relevant online so we would like to get confirmation from others experiencing the same issue.
    EDIT scratch that: this is the same issue but with no solution as of yet
    (vbscript.dll class_terminate bug since KB4524570 (November 12, 2019) Windows 10 1903)
  • if anyone can verify and knows a workable solution, that would be awesome.

POC

following script running on a device with KB4525236 installed shows the difference in garbage collecting when

  • called directly: the second instance gets created only after the first instance is destroyed (this is our desired behavior)
  • called through GetRef: the second instance gets created before the first instance is destroyed so whe have two instances using memory.

save as: KB4525236.vbs
run as: wscript KB4525236.vbs

Dim Name, Log

Class IDummyInstance
  Dim FName
  Sub Class_Initialize
    FName = Name
    Log = Log & "Initialize " & FName & VbNewLine
  End Sub
  Sub Class_Terminate
    Log = Log & "Terminate " & FName & vbNewLine
  End Sub
End Class

Sub CreateDestroyTwoInstances
  Dim DummyInstance
  Name = "First Instance"
  Set DummyInstance = New IDummyInstance
  Set DummyInstance = Nothing
  Name = "Second Instance"
  Set DummyInstance = New IDummyInstance
  Set DummyInstance = Nothing
End Sub

Log = "(1) Direct Call :" & VbNewLine
Call CreateDestroyTwoInstances

Log = VbNewLine & Log & "(2) GetRef Call :" & vbNewLine
Set GetRefCall = GetRef ("CreateDestroyTwoInstances")
Call GetRefCall

MsgBox Log
Heptangular answered 6/2, 2020 at 7:28 Comment(12)
If you are worried about memory consumption of the GetRef() why not just set the GetRefCall object reference to Nothing to manually clear the memory instead of relying on VBScript's garbage collection?Wollastonite
@Lankymart - the problem is that the instances created in GetRef() don't get garbage collected until GetRef() ends. That's different to what it was. We have functions called through GetRef() creating 1000's of instances and they keep cumulating memory until GetRef() ends while in the past, they were freed while executing the loop in GetRef().Heptangular
Thanks for clarifying, I'm not sure what you are going to be able to do about that tbh. Imagine if anyone knows, it will be @eric-lippert as they worked on the original team that built VBScript.Wollastonite
@LankyMart - Eric Lippert did work on the garbage collector but according to a post here on SO, that's more then 10 years ago. I'd love to get his take on this though, fingers crossed ;)Heptangular
I have the behaviour you describe on Windows 7 without KB4525236 or KB4524570 (apparently there is another KB that does it to Windows 7). Still, there is no garbage collection in VBScript, the objects must be destroyed when their reference count drops to zero. If that does not happen, it is an engine bug rather than a different way of GC functioning.Courtney
This is the case even without explicit variables. Two With New IDummyInstance : End With blocks still produce "Initialize First Instance, Initialize Second Instance, Terminate First Instance, Terminate Second Instance". This is very wrong, it should be reported. Apart from the memory consumption thing, it completely breaks this.Courtney
@Courtney - As far as our tests go, memory does get freed but the moment when is changed so one could debate this being an engine bug. If not changed, this does force us to rewrite our code and take special care not to create many objects in a tight loop through a GetRef call. I wouldn't know where to start to report this to Microsoft apart from the visibility it get's here on SO and here.Heptangular
@LievenKeersmaekers It is an engine bug because when is a part of the contract with COM reference counting. It is not a memory leak bug, but it is a bug. You can't even take special care as far as I can see, because the only thing that fixes it is avoiding GetRef. Although I would test what happens when you call a helper sub from CreateDestroyTwoInstances that creates, uses and destroys an IDummyInstance without returning it to CreateDestroyTwoInstances. Which I did, and nope, same behaviour.Courtney
@Courtney - Do you happen to have a channel to report this? Nothing gets to me faster than trying to figure out where to report issues. This support page for example leads to this support page which effectively leads to nothing.Heptangular
@LievenKeersmaekers No, I don't, and it would appear nobody does. Try the feedback hub app I guess.Courtney
@Gserg - thank you. At least, this made my day ;)Heptangular
@Courtney - also from that blog, it appears that the way to go is through answers.microsoft.com. "Apparently, this is also recommended as a way to get in touch with developers". I had already linked to answers.microsoft.com in my question. After reading the blog and in the interest of keeping my sanity, I'll leave it at that.Heptangular
S
4

Great question. I came to it via a Microsoft forums post

https://answers.microsoft.com/en-us/windows/forum/all/vbscriptdll-classterminate-bug-since-kb4524570/1b34d9b4-91ce-4d61-a05c-1bfa0ec96344?auth=1

The asker of this SO had added a link to it to that above Microsoft thread.

That Microsoft thread was referenced in the release notes for KB5005101 (for Windows 10 21H1 - there will be similar KBs for other Windows 10 versions). Link to the Microsoft release notes: https://support.microsoft.com/en-us/topic/september-1-2021-kb5005101-os-builds-19041-1202-19042-1202-and-19043-1202-preview-82a50f27-a56f-4212-96ce-1554e8058dc1

Microsoft claim to have fixed this issue Addresses a memory leak that occurs when you use nested classes within VBScript. In the release notes, the VBScript word on the end is a hyperlink to that Microsoft forum thread. Rather subtle...

Anyway, whilst I'm not a frequent user of VBScript, as a long-time COM user, and interested reader of this sort of issue anyway, I thought I'd share what should eventually be the definitive answer once everyone gets their monthly patches. Those release notes only go back to Windows 10 2004, so 1909 and earlier may be out of luck, as may Windows 7 users affected by this.

Skeie answered 14/9, 2021 at 2:13 Comment(0)
H
5

Since I don't have a solution or an official source explaining the issue I was waiting the bounty to expire.

I've come up with an unpleasant workaround that can help until the bug is fixed.

The workaround is not to use any local variable to hold object instances in procedures that might be executed through GetRef.

Instead of implicit or explicit variables, using a local (or global if there's no recursion) dictionary object to hold object instances and calling them through that dictionary works.

Sub CreateDestroyTwoInstances
  Dim Refs
  Set Refs = CreateObject("Scripting.Dictionary")
  Name = "First Instance"
  Refs.Add "DummyInstance", New IDummyInstance
  ' Call Refs("DummyInstance").DoSomething()
  Refs.Remove "DummyInstance"
  Name = "Second Instance"
  Refs.Add "DummyInstance", New IDummyInstance
  ' Call Refs("DummyInstance").DoSomething()
  Refs.Remove "DummyInstance"
End Sub

It seems to be worth using if you have a script that is not too complicated.

Hammett answered 18/2, 2020 at 0:18 Comment(5)
Just tested it and I can confirm it works on my machine. I am going to mark this as the solution. It's the best yet until Microsoft provides a fix (assuming they acknowledge this being a bug).Heptangular
Just be aware that Scripting.Dictionary is notably slow!Gilgamesh
@Gilgamesh Of course it is, that was a workaround anyway. The bug is fixed by Microsoft and no need to use Scripting.Dictionary for such a job anymore.Hammett
Sadly, the bug is fixed in Windows 10 and Server versions, but not in previous versions (eg. Windows Server 2012 R2). I can confirm that Win 2012 R2 is still affected, even when fully updated at the time of writing this comment.Gilgamesh
@Gilgamesh Really? I didn't know as our entire stack is built on 2016.Hammett
S
4

There is a roundabout way to getting VBScript to terminate objects 'now' rather than later.

By piecing the following observations together:

  • If you call a member function of a class instance, then object "clean up" is deferred to the end of the function.

  • If you pass a parameter by reference, you can modify the value (e.g. set to Nothing).

You might end up with something like this:

Class Disposal
   Public Sub Dispose(ByRef oRef)
      Set oRef = Nothing
   End Sub
End Class

' A global instance ready for use.
Dim GD: Set GD = New Disposal

To use the Disposal, you would replace:

Set x = Nothing

with:

GD.Dispose x

Here is a rough explanation of what happens when passing objects to 'dispose of':

  • Dispose takes a reference to an object and sets it to Nothing (self explanatory).
  • At the end of the function, VBScript 'sees' there are no more references to the object.
  • VBScript terminates the object.

Or at least, that's what I think it's doing...

Here is the solution applied to the POC:

Class Disposal
   Public Sub Dispose(ByRef oRef)
      Set oRef = Nothing
   End Sub
End Class

' A global instance ready for use.
Dim GD: Set GD = New Disposal

Dim Name, Log

Class IDummyInstance
  Dim FName
  Sub Class_Initialize
    FName = Name
    Log = Log & "Initialize " & FName & VbNewLine
  End Sub
  Sub Class_Terminate
    Log = Log & "Terminate " & FName & vbNewLine
  End Sub
End Class

Sub CreateDestroyTwoInstances
  Dim DummyInstance
  Name = "First Instance"
  Set DummyInstance = New IDummyInstance
  GD.Dispose DummyInstance 'Set DummyInstance = Nothing
  Name = "Second Instance"
  Set DummyInstance = New IDummyInstance
  GD.Dispose DummyInstance 'Set DummyInstance = Nothing
End Sub

Log = "(1) Direct Call :" & VbNewLine
Call CreateDestroyTwoInstances

Log = VbNewLine & Log & "(2) GetRef Call :" & vbNewLine
Set GetRefCall = GetRef ("CreateDestroyTwoInstances")
Call GetRefCall

MsgBox Log

Hopefully, this helps out those still stuck with VBScript in 2021 and beyond...

Soulless answered 4/8, 2021 at 17:46 Comment(2)
Brilliant! I have created a Sub Dispose(byRef variable) a long time ago. It is a Sub, not a Class. Can I, by chance, consider that it will work the same way that your Class example?Gilgamesh
Following-up my previous comment : Using a Sub Dispose(byRef variable) does NOT fixes the issue. So the Class workaround proposed here IS the way to go! (tested successfully)Gilgamesh
S
4

Great question. I came to it via a Microsoft forums post

https://answers.microsoft.com/en-us/windows/forum/all/vbscriptdll-classterminate-bug-since-kb4524570/1b34d9b4-91ce-4d61-a05c-1bfa0ec96344?auth=1

The asker of this SO had added a link to it to that above Microsoft thread.

That Microsoft thread was referenced in the release notes for KB5005101 (for Windows 10 21H1 - there will be similar KBs for other Windows 10 versions). Link to the Microsoft release notes: https://support.microsoft.com/en-us/topic/september-1-2021-kb5005101-os-builds-19041-1202-19042-1202-and-19043-1202-preview-82a50f27-a56f-4212-96ce-1554e8058dc1

Microsoft claim to have fixed this issue Addresses a memory leak that occurs when you use nested classes within VBScript. In the release notes, the VBScript word on the end is a hyperlink to that Microsoft forum thread. Rather subtle...

Anyway, whilst I'm not a frequent user of VBScript, as a long-time COM user, and interested reader of this sort of issue anyway, I thought I'd share what should eventually be the definitive answer once everyone gets their monthly patches. Those release notes only go back to Windows 10 2004, so 1909 and earlier may be out of luck, as may Windows 7 users affected by this.

Skeie answered 14/9, 2021 at 2:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.