Home > Back-end >  Memory leak in Microsoft's WMI library. Is there a way to mitigate it?
Memory leak in Microsoft's WMI library. Is there a way to mitigate it?

Time:01-22

When we write 156KB worth of key:value pairs to the WMI using IWbemQualifierSet::Put this method creates 2GB of dangling allocations. I've never encountered a bug like this in a Microsoft library so I'm not really sure what to do about it.

Is there some way we can mitigate it by manipulating the call stack? Or by pre-allocating the memory for the class in some kind of quarantine container to make sure it gets completely freed after the class object is destroyed?

You can skip to "Step 3" in the code to see the leak, it occurs irrespective of data type or format, including using only C types and manually managing the memory.

Update: Even if we create and destroy the class object in the loop, the leak still occurs.

Update: The size of the leak is the same irrespective of input data size. Even if we write strings that are 4000 bytes long, or 1 byte long, the amount of memory leaked is the same.

main.cpp:

// MemorLeakMinimalExample.cpp : This file contains the 'main' function. Program execution begins and ends there.
#include <string>
#include <Windows.h>
#include "wmi_connection.h"

// Memory leak in this method
// Consumes 2GB of RAM to write 32MB of data

int main()
{

    // ------------------------------------------------------
    // Step 1. Connect to WMI -------------------------------

    wmi_connection wmi;
    wmi.connect_to_wmi();


    // ------------------------------------------------------
    // Step 2. Create a new WMI class -----------------------

    IWbemClassObject* pNewClass = NULL;
    VARIANT vVariant;
    VariantInit(&vVariant);

    // Our new class
    HRESULT hres = wmi.pSvc->GetObject(
        0,
        0,
        NULL,
        &pNewClass,
        NULL
    );

    // Set class name
    vVariant.vt = VT_BSTR;
    vVariant.bstrVal = SysAllocString(L"TestClass");
    hres = pNewClass->Put(
        _bstr_t("__CLASS"),
        0,
        &vVariant,
        0
    );

    // Create the "Test" key property for the new class
    BSTR KeyProp = SysAllocString(L"TestProperty");

    hres = pNewClass->Put(
        KeyProp,
        0,
        NULL,
        CIM_STRING);

    //Attach the Key standard qualifier to the test property
    IWbemQualifierSet* pQualSet = NULL;
    vVariant.vt = VT_BOOL;
    vVariant.boolVal = TRUE;
    hres = pNewClass->GetPropertyQualifierSet(KeyProp, &pQualSet);
    hres = pQualSet->Put(L"Key", &vVariant, 0);
    

    // --------------------------------------------------------------------
    // Step 3. Write 20,000 key:value pairs -------------------------------
    
    // Key
    wchar_t key[32] = { 0 };
    wchar_t* pKey = key;
    
    // Value
    VARIANT value;
    VariantInit(&value);
    value.vt = VT_BSTR;

    // Value is always 1234
    value.bstrVal = SysAllocString(L"1234");
    

    printf("Breakpoint\n"); // Inspect memory usage here

    
    for (unsigned long long i = 0; i <= 20000; i  )
    {
        // Key name is formatted "key0", "key1", "key2", ...
        swprintf_s(pKey, 32, L"key%llu", i);

        // Write a key:value pair
        pQualSet->Put(pKey, &value, 0); // <-- memory leak
        
    }
    
    printf("Breakpoint\n"); // Inspect memory usage here

    // --------------------------------------------------------------------
    // // Step 4. Save class for viewing in sys utility Wbemtest.exe ------

    // Uncomment if you want the class written to the wmi database
    // (its exactly the size it should be for our key:value data in)

    /*
    hres = wmi.pSvc->PutClass(pNewClass, WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL);
    if (FAILED(hres))
    {
        printf("Failed to save class changes. Error code = 0x%X \n", hres);
    }
    */

    return 0;
}

wmi_connection.h:

#pragma once
#include <Windows.h>
#include <comdef.h>
#include <wbemcli.h>

class wmi_connection
{
public:
    // uninteresting parts
    int connect_to_wmi()
    {
        // Initialize 
        hres = CoInitializeEx(0, COINIT_MULTITHREADED);
        if (FAILED(hres))
        {
            printf("Failed to initialize COM library. Error code = 0x%llx", (unsigned long long)hres);
            return FALSE;                  // Program has failed.
        }
        // Set general COM security levels 
        hres = CoInitializeSecurity(
            NULL,
            -1,                          // COM authentication
            NULL,                        // Authentication services
            NULL,                        // Reserved
            RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication 
            RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation  
            NULL,                        // Authentication info
            EOAC_NONE,                   // Additional capabilities 
            NULL                         // Reserved
        );

        // Get the class factory for the WbemLocator object
        IClassFactory* pClassFactory = NULL;

        hres = CoGetClassObject(CSLSID_WbemLocator, CLSCTX_INPROC_SERVER, NULL, SIID_IClassFactory, (void**)&pClassFactory);

        // Create an instance of the WbemLocator object
        IUnknown* pUnk = NULL;

        hres = pClassFactory->CreateInstance(NULL, SIID_IUnknown, (void**)&pUnk);

        hres = pUnk->QueryInterface(SIID_IWbemLocator, (void**)&pLoc);

        pUnk->Release();
        pClassFactory->Release();

        // Set security levels on the proxy 
        hres = CoSetProxyBlanket(
            pSvc,                        // Indicates the proxy to set
            RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
            RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
            NULL,                        // Server principal name 
            RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx 
            RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
            NULL,                        // client identity
            EOAC_NONE                    // proxy capabilities 
        );

        // Connect to WMI through the IWbemLocator::ConnectServer method
        hres = pLoc->ConnectServer(
            _bstr_t(L"root\\cimv2"), // Object path of WMI namespace
            NULL,                    // User name
            NULL,                    // User password
            0,                       // Locale
            NULL,                    // Security flags
            0,                       // Authority
            0,                       // Context object 
            &pSvc                    // pointer to IWbemServices proxy
        );

        if (FAILED(hres))
        {
            printf("Could not connect. Error code = 0x%lx\n", hres);
            return 1;
        }

        printf("Connected to root\cimv2 WMI namespace\n");
        return 0;
    }

    IWbemServices* pSvc;

private:
    HRESULT hres = NULL;
    IWbemLocator* pLoc = NULL;
    GUID CSLSID_WbemLocator = { 0x4590f811, 0x1d3a, 0x11d0, 0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 };
    GUID SIID_IClassFactory = { 0x00000001, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
    GUID SIID_IUnknown = { 0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
    GUID SIID_IWbemLocator = { 0xdc12a687, 0x737f, 0x11cf, 0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 };
};

CodePudding user response:

I turned i into 40000000. I found that the memory footprint has indeed been increasing from Task Manager. And show this continuously. enter image description here

I added Sleep(50);in the “for” loop, “Exception thrown….” disappeared. But the memory footprint still was increasing. The process memory increased to 565MB in 6mins. enter image description here

There is a conflict between the “for” loop with Put(pKey, &value, 0);

So, using multithreading is necessary.

I modified the code, it runs well. No effect on the loop and process memory is normal. enter image description here

Here is the code, it still has many flaws, and you are welcome to correct them.

#include <string>
#include <Windows.h>
#include "wmi_connection.h"
wchar_t key[32] = { 0 };
wchar_t* pKey = key;
BSTR KeyProp = SysAllocString(L"TestProperty");
IWbemQualifierSet* pQualSet = NULL;
VARIANT value;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
    wmi_connection wmi;
    wmi.connect_to_wmi();
    IWbemClassObject* pNewClass = NULL;
    VARIANT vVariant;
    VariantInit(&vVariant);
    HRESULT hres = wmi.pSvc->GetObject(
        0,
        0,
        NULL,
        &pNewClass,
        NULL
    );
    vVariant.vt = VT_BSTR;
    vVariant.bstrVal = SysAllocString(L"TestClass");
    hres = pNewClass->Put(
        _bstr_t("__CLASS"),
        0,
        &vVariant,
        0
    );
    hres = pNewClass->Put(
        KeyProp,
        0,
        NULL,
        CIM_STRING);
    vVariant.vt = VT_BOOL;
    vVariant.boolVal = TRUE;
    hres = pNewClass->GetPropertyQualifierSet(KeyProp, &pQualSet);
    hres = pQualSet->Put(L"Key", &vVariant, 0);

    VARIANT value;
    VariantInit(&value);
    value.vt = VT_BSTR;

    value.bstrVal = SysAllocString(L"1234");
    printf("Breakpoint\n");
    pQualSet->Put(pKey, &value, 0);
   
    return 2;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
    for (unsigned long long i = 0; i <= 400000000; i  )

    {
           
        swprintf_s(pKey, 32, L"key%llu", i);
    }
    printf("Breakpoint\n");
    return 0;
}
int main()
{
    wmi_connection wmi;
    wmi.connect_to_wmi();

    IWbemClassObject* pNewClass = NULL;
    VARIANT vVariant;
    VariantInit(&vVariant);

    // Our new class
    HRESULT hres = wmi.pSvc->GetObject(
        0,
        0,
        NULL,
        &pNewClass,
        NULL
    );

    vVariant.vt = VT_BSTR;
    vVariant.bstrVal = SysAllocString(L"TestClass");
    hres = pNewClass->Put(
        _bstr_t("__CLASS"),
        0,
        &vVariant,
        0
    );
    BSTR KeyProp = SysAllocString(L"TestProperty");

    hres = pNewClass->Put(
        KeyProp,
        0,
        NULL,
        CIM_STRING);

    IWbemQualifierSet* pQualSet = NULL;
    vVariant.vt = VT_BOOL;
    vVariant.boolVal = TRUE;
    hres = pNewClass->GetPropertyQualifierSet(KeyProp, &pQualSet);
    hres = pQualSet->Put(L"Key", &vVariant, 0);

    HANDLE hThread[2];
    DWORD dWResult1;
    DWORD dWResult2;
    hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);

    WaitForMultipleObjects(2, hThread, true, INFINITE);
    GetExitCodeThread(hThread[0], &dWResult1);
    GetExitCodeThread(hThread[1], &dWResult2);
    printf("Thread execution completed \n");
    getchar();
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    SysFreeString((BSTR) KeyProp);//add

    printf("Breakpoint\n"); 
    
    return 0;
}
  • Related