/* $Id: VBoxNetFltNobj.cpp $ */ /** @file * VBoxNetFltNobj.cpp - Notify Object for Bridged Networking Driver. * * Used to filter Bridged Networking Driver bindings */ /* * Copyright (C) 2011-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include "VBoxNetFltNobj.h" #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ //# define VBOXNETFLTNOTIFY_DEBUG_BIND #ifdef DEBUG # define NonStandardAssert(a) Assert(a) # define NonStandardAssertBreakpoint() AssertFailed() #else # define NonStandardAssert(a) do{}while (0) # define NonStandardAssertBreakpoint() do{}while (0) #endif /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ static HMODULE g_hModSelf = (HMODULE)~(uintptr_t)0; VBoxNetFltNobj::VBoxNetFltNobj() : mpNetCfg(NULL) , mpNetCfgComponent(NULL) , mbInstalling(FALSE) { } VBoxNetFltNobj::~VBoxNetFltNobj() { cleanup(); } void VBoxNetFltNobj::cleanup() { if (mpNetCfg) { mpNetCfg->Release(); mpNetCfg = NULL; } if (mpNetCfgComponent) { mpNetCfgComponent->Release(); mpNetCfgComponent = NULL; } } void VBoxNetFltNobj::init(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling) { cleanup(); NonStandardAssert(pNetCfg); NonStandardAssert(pNetCfgComponent); if (pNetCfg) { pNetCfg->AddRef(); mpNetCfg = pNetCfg; } if (pNetCfgComponent) { pNetCfgComponent->AddRef(); mpNetCfgComponent = pNetCfgComponent; } mbInstalling = bInstalling; } /* INetCfgComponentControl methods */ STDMETHODIMP VBoxNetFltNobj::Initialize(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling) { init(pNetCfgComponent, pNetCfg, bInstalling); return S_OK; } STDMETHODIMP VBoxNetFltNobj::ApplyRegistryChanges() { return S_OK; } STDMETHODIMP VBoxNetFltNobj::ApplyPnpChanges(IN INetCfgPnpReconfigCallback *pCallback) { RT_NOREF1(pCallback); return S_OK; } STDMETHODIMP VBoxNetFltNobj::CancelChanges() { return S_OK; } static HRESULT vboxNetFltWinQueryInstanceKey(IN INetCfgComponent *pComponent, OUT PHKEY phKey) { LPWSTR pwszPnpId; HRESULT hrc = pComponent->GetPnpDevNodeId(&pwszPnpId); if (hrc == S_OK) { WCHAR wszKeyName[MAX_PATH]; RTUtf16Copy(wszKeyName, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Enum\\"); int rc = RTUtf16Cat(wszKeyName, MAX_PATH, pwszPnpId); if (RT_SUCCESS(rc)) { LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszKeyName, 0 /*ulOptions*/, KEY_READ, phKey); if (lrc != ERROR_SUCCESS) { hrc = HRESULT_FROM_WIN32(lrc); NonStandardAssertBreakpoint(); } } else AssertRCStmt(rc, hrc = ERROR_BUFFER_OVERFLOW); CoTaskMemFree(pwszPnpId); } else NonStandardAssertBreakpoint(); return hrc; } static HRESULT vboxNetFltWinQueryDriverKey(IN HKEY InstanceKey, OUT PHKEY phKey) { HRESULT hrc = S_OK; WCHAR wszValue[MAX_PATH]; DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); DWORD dwType = REG_SZ; LSTATUS lrc = RegQueryValueExW(InstanceKey, L"Driver", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); if (lrc == ERROR_SUCCESS) { if (dwType == REG_SZ) { wszValue[RT_ELEMENTS(wszValue) - 1] = '\0'; /* registry strings does not need to be zero terminated. */ WCHAR wszKeyName[MAX_PATH]; RTUtf16Copy(wszKeyName, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Control\\Class\\"); int rc = RTUtf16Cat(wszKeyName, MAX_PATH, wszValue); if (RT_SUCCESS(rc)) { lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszKeyName, 0 /*ulOptions*/, KEY_READ, phKey); if (lrc != ERROR_SUCCESS) { hrc = HRESULT_FROM_WIN32(lrc); NonStandardAssertBreakpoint(); } } else AssertRCStmt(rc, hrc = ERROR_BUFFER_OVERFLOW); } else { hrc = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); NonStandardAssertBreakpoint(); } } else { hrc = HRESULT_FROM_WIN32(lrc); NonStandardAssertBreakpoint(); } return hrc; } static HRESULT vboxNetFltWinQueryDriverKey(IN INetCfgComponent *pComponent, OUT PHKEY phKey) { HKEY hKeyInstance = NULL; HRESULT hrc = vboxNetFltWinQueryInstanceKey(pComponent, &hKeyInstance); if (hrc == S_OK) { hrc = vboxNetFltWinQueryDriverKey(hKeyInstance, phKey); if (hrc != S_OK) NonStandardAssertBreakpoint(); RegCloseKey(hKeyInstance); } else NonStandardAssertBreakpoint(); return hrc; } static HRESULT vboxNetFltWinNotifyCheckNetAdp(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) { *pfShouldBind = false; LPWSTR pwszDevId = NULL; HRESULT hrc = pComponent->GetId(&pwszDevId); if (hrc == S_OK) { /** @todo r=bird: This was _wcsnicmp(pwszDevId, L"sun_VBoxNetAdp", sizeof(L"sun_VBoxNetAdp")/2)) * which includes the terminator, so it translates to a full compare. Goes way back. */ if (RTUtf16ICmpAscii(pwszDevId, "sun_VBoxNetAdp") == 0) *pfShouldBind = false; else hrc = S_FALSE; CoTaskMemFree(pwszDevId); } else NonStandardAssertBreakpoint(); return hrc; } static HRESULT vboxNetFltWinNotifyCheckMsLoop(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) { *pfShouldBind = false; LPWSTR pwszDevId = NULL; HRESULT hrc = pComponent->GetId(&pwszDevId); if (hrc == S_OK) { /** @todo r=bird: This was _wcsnicmp(pwszDevId, L"*msloop", sizeof(L"*msloop")/2) * which includes the terminator, making it a full compare. Goes way back. */ if (RTUtf16ICmpAscii(pwszDevId, "*msloop") == 0) { /* we need to detect the medium the adapter is presenting * to do that we could examine in the registry the *msloop params */ HKEY hKeyDriver; hrc = vboxNetFltWinQueryDriverKey(pComponent, &hKeyDriver); if (hrc == S_OK) { WCHAR wszValue[64]; /* 2 should be enough actually, paranoid check for extra spaces */ DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); DWORD dwType = REG_SZ; LSTATUS lrc = RegQueryValueExW(hKeyDriver, L"Medium", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); if (lrc == ERROR_SUCCESS) { if (dwType == REG_SZ) { wszValue[RT_ELEMENTS(wszValue) - 1] = '\0'; char szUtf8[256]; char *pszUtf8 = szUtf8; RTUtf16ToUtf8Ex(wszValue, RTSTR_MAX, &pszUtf8, sizeof(szUtf8), NULL); pszUtf8 = RTStrStrip(pszUtf8); uint64_t uValue = 0; int rc = RTStrToUInt64Ex(pszUtf8, NULL, 0, &uValue); if (RT_SUCCESS(rc)) { if (uValue == 0) /* 0 is Ethernet */ *pfShouldBind = true; else *pfShouldBind = false; } else { NonStandardAssertBreakpoint(); *pfShouldBind = true; } } else NonStandardAssertBreakpoint(); } else { /** @todo we should check the default medium in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\\Ndi\Params\Medium, REG_SZ "Default" value */ NonStandardAssertBreakpoint(); *pfShouldBind = true; } RegCloseKey(hKeyDriver); } else NonStandardAssertBreakpoint(); } else hrc = S_FALSE; CoTaskMemFree(pwszDevId); } else NonStandardAssertBreakpoint(); return hrc; } static HRESULT vboxNetFltWinNotifyCheckLowerRange(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) { *pfShouldBind = false; HKEY hKeyDriver = NULL; HRESULT hrc = vboxNetFltWinQueryDriverKey(pComponent, &hKeyDriver); if (hrc == S_OK) { HKEY hKeyInterfaces = NULL; LSTATUS lrc = RegOpenKeyExW(hKeyDriver, L"Ndi\\Interfaces", 0 /*ulOptions*/, KEY_READ, &hKeyInterfaces); if (lrc == ERROR_SUCCESS) { WCHAR wszValue[MAX_PATH]; DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); DWORD dwType = REG_SZ; lrc = RegQueryValueExW(hKeyInterfaces, L"LowerRange", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); if (lrc == ERROR_SUCCESS) { if (dwType == REG_SZ) { if (RTUtf16FindAscii(wszValue, "ethernet") >= 0 || RTUtf16FindAscii(wszValue, "wan") >= 0) *pfShouldBind = true; else *pfShouldBind = false; } } else { /* do not set err status to it */ *pfShouldBind = false; NonStandardAssertBreakpoint(); } RegCloseKey(hKeyInterfaces); } else { hrc = HRESULT_FROM_WIN32(lrc); NonStandardAssertBreakpoint(); } RegCloseKey(hKeyDriver); } else NonStandardAssertBreakpoint(); return hrc; } static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) { *pfShouldBind = false; /* filter out only physical adapters */ DWORD fCharacteristics = 0; HRESULT hrc = pComponent->GetCharacteristics(&fCharacteristics); if (hrc != S_OK) { NonStandardAssertBreakpoint(); return hrc; } /* we are not binding to hidden adapters */ if (fCharacteristics & NCF_HIDDEN) return S_OK; hrc = vboxNetFltWinNotifyCheckMsLoop(pComponent, pfShouldBind); if ( hrc == S_OK /* this is a loopback adapter, the pfShouldBind already contains the result */ || hrc != S_FALSE /* error occurred */) return hrc; hrc = vboxNetFltWinNotifyCheckNetAdp(pComponent, pfShouldBind); if ( hrc == S_OK /* this is a VBoxNetAdp adapter, the pfShouldBind already contains the result */ || hrc != S_FALSE /* error occurred */) return hrc; //if (!(fCharacteristics & NCF_PHYSICAL)) //{ // *pfShouldBind = false; /* we are binding to physical adapters only */ // return S_OK; //} return vboxNetFltWinNotifyCheckLowerRange(pComponent, pfShouldBind); } static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgBindingInterface *pIf, OUT bool *pfShouldBind) { INetCfgComponent *pAdapterComponent = NULL; HRESULT hrc = pIf->GetLowerComponent(&pAdapterComponent); if (hrc == S_OK) { hrc = vboxNetFltWinNotifyShouldBind(pAdapterComponent, pfShouldBind); pAdapterComponent->Release(); } else { NonStandardAssertBreakpoint(); *pfShouldBind = false; } return hrc; } static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgBindingPath *pPath, OUT bool *pfShouldBind) { *pfShouldBind = false; IEnumNetCfgBindingInterface *pIEnumBinding = NULL; HRESULT hrc = pPath->EnumBindingInterfaces(&pIEnumBinding); if (hrc == S_OK) { hrc = pIEnumBinding->Reset(); if (hrc == S_OK) { for (;;) { ULONG uCount = 0; INetCfgBindingInterface *pIBinding = NULL; hrc = pIEnumBinding->Next(1, &pIBinding, &uCount); if (hrc == S_OK) { hrc = vboxNetFltWinNotifyShouldBind(pIBinding, pfShouldBind); pIBinding->Release(); if (hrc != S_OK) break; /* break on failure. */ if (!*pfShouldBind) break; } else if (hrc == S_FALSE) { /* no more elements */ hrc = S_OK; break; } else { NonStandardAssertBreakpoint(); /* break on falure */ break; } } } else NonStandardAssertBreakpoint(); pIEnumBinding->Release(); } else NonStandardAssertBreakpoint(); return hrc; } static bool vboxNetFltWinNotifyShouldBind(IN INetCfgBindingPath *pPath) { #ifdef VBOXNETFLTNOTIFY_DEBUG_BIND return VBOXNETFLTNOTIFY_DEBUG_BIND; #else bool fShouldBind; HRESULT hrc = vboxNetFltWinNotifyShouldBind(pPath, &fShouldBind); if (hrc != S_OK) fShouldBind = VBOXNETFLTNOTIFY_ONFAIL_BINDDEFAULT; return fShouldBind; #endif } /* INetCfgComponentNotifyBinding methods */ STDMETHODIMP VBoxNetFltNobj::NotifyBindingPath(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP) { if (!(dwChangeFlag & NCN_ENABLE) || (dwChangeFlag & NCN_REMOVE) || vboxNetFltWinNotifyShouldBind(pNetCfgBP)) return S_OK; return NETCFG_S_DISABLE_QUERY; } STDMETHODIMP VBoxNetFltNobj::QueryBindingPath(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP) { RT_NOREF1(dwChangeFlag); if (vboxNetFltWinNotifyShouldBind(pNetCfgBP)) return S_OK; return NETCFG_S_DISABLE_QUERY; } static ATL::CComModule _Module; BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_VBoxNetFltNobj, VBoxNetFltNobj) END_OBJECT_MAP() extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { g_hModSelf = (HMODULE)hInstance; _Module.Init(ObjectMap, hInstance); DisableThreadLibraryCalls(hInstance); } else if (dwReason == DLL_PROCESS_DETACH) { _Module.Term(); } return TRUE; } STDAPI DllCanUnloadNow(void) { return _Module.GetLockCount() == 0 ? S_OK : S_FALSE; } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) { return _Module.GetClassObject(rclsid, riid, ppv); } /* * ATL::CComModule does not suport server registration/unregistration methods, * so we need to do it manually. Since this is the only place we do registraton * manually, we do it the quick-and-dirty way. */ #ifdef RT_EXCEPTIONS_ENABLED /* Someday we may want to log errors. */ class AdHocRegError { public: AdHocRegError(LSTATUS rc) { RT_NOREF1(rc); }; }; #endif /** * A simple wrapper on Windows registry functions. */ class AdHocRegKey { public: AdHocRegKey(HKEY hKey) : m_hKey(hKey) {}; AdHocRegKey(LPCWSTR pcwszName, HKEY hParent = HKEY_CLASSES_ROOT); ~AdHocRegKey() { RegCloseKey(m_hKey); }; AdHocRegKey *create(LPCWSTR pcwszSubkey); LSTATUS setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue); HKEY getKey(void) { return m_hKey; }; private: HKEY m_hKey; }; AdHocRegKey::AdHocRegKey(LPCWSTR pcwszName, HKEY hParent) : m_hKey(NULL) { LSTATUS rc = RegOpenKeyExW(hParent, pcwszName, 0, KEY_ALL_ACCESS, &m_hKey); if (rc != ERROR_SUCCESS) #ifdef RT_EXCEPTIONS_ENABLED throw AdHocRegError(rc); #else m_hKey = NULL; #endif } AdHocRegKey *AdHocRegKey::create(LPCWSTR pcwszSubkey) { HKEY hSubkey; LSTATUS rc = RegCreateKeyExW(m_hKey, pcwszSubkey, 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/, KEY_ALL_ACCESS, NULL /*pSecAttr*/, &hSubkey, NULL /*pdwDisposition*/); if (rc != ERROR_SUCCESS) #ifdef RT_EXCEPTIONS_ENABLED throw AdHocRegError(rc); #else return NULL; #endif AdHocRegKey *pSubkey = new AdHocRegKey(hSubkey); if (!pSubkey) RegCloseKey(hSubkey); return pSubkey; } LSTATUS AdHocRegKey::setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue) { LSTATUS rc = RegSetValueExW(m_hKey, pcwszName, 0, REG_SZ, (const BYTE *)pcwszValue, (DWORD)((RTUtf16Len(pcwszValue) + 1) * sizeof(WCHAR))); #ifdef RT_EXCEPTIONS_ENABLED if (rc != ERROR_SUCCESS) throw AdHocRegError(rc); #endif return rc; } /** * Auxiliary class that facilitates automatic destruction of AdHocRegKey objects * allocated in heap. No reference counting here! */ class AdHocRegKeyPtr { public: AdHocRegKeyPtr(AdHocRegKey *pKey) : m_pKey(pKey) {}; ~AdHocRegKeyPtr() { if (m_pKey) { delete m_pKey; m_pKey = NULL; } } AdHocRegKey *create(LPCWSTR pcwszSubkey) { return m_pKey ? m_pKey->create(pcwszSubkey) : NULL; }; LSTATUS setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue) { return m_pKey ? m_pKey->setValue(pcwszName, pcwszValue) : ERROR_INVALID_STATE; }; private: AdHocRegKey *m_pKey; /* Prevent copying, since we do not support reference counting */ AdHocRegKeyPtr(const AdHocRegKeyPtr&); AdHocRegKeyPtr& operator=(const AdHocRegKeyPtr&); }; STDAPI DllRegisterServer(void) { /* Get the path to the DLL we're running inside. */ WCHAR wszModule[MAX_PATH + 1]; UINT cwcModule = GetModuleFileNameW(g_hModSelf, wszModule, MAX_PATH); if (cwcModule == 0 || cwcModule > MAX_PATH) return SELFREG_E_CLASS; wszModule[MAX_PATH] = '\0'; /* * Create registry keys and values. When exceptions are disabled, we depend * on setValue() to propagate fail key creation failures. */ #ifdef RT_EXCEPTIONS_ENABLED try #endif { AdHocRegKey keyCLSID(L"CLSID"); AdHocRegKeyPtr pkeyNobjClass(keyCLSID.create(L"{f374d1a0-bf08-4bdc-9cb2-c15ddaeef955}")); LSTATUS lrc = pkeyNobjClass.setValue(NULL, L"VirtualBox Bridged Networking Driver Notify Object v1.1"); if (lrc != ERROR_SUCCESS) return SELFREG_E_CLASS; AdHocRegKeyPtr pkeyNobjSrv(pkeyNobjClass.create(L"InProcServer32")); lrc = pkeyNobjSrv.setValue(NULL, wszModule); if (lrc != ERROR_SUCCESS) return SELFREG_E_CLASS; lrc = pkeyNobjSrv.setValue(L"ThreadingModel", L"Both"); if (lrc != ERROR_SUCCESS) return SELFREG_E_CLASS; } #ifdef RT_EXCEPTIONS_ENABLED catch (AdHocRegError) { return SELFREG_E_CLASS; } #endif #ifdef RT_EXCEPTIONS_ENABLED try #endif { AdHocRegKey keyTypeLib(L"TypeLib"); AdHocRegKeyPtr pkeyNobjLib(keyTypeLib.create(L"{2A0C94D1-40E1-439C-8FE8-24107CAB0840}\\1.1")); LSTATUS lrc = pkeyNobjLib.setValue(NULL, L"VirtualBox Bridged Networking Driver Notify Object v1.1 Type Library"); if (lrc != ERROR_SUCCESS) return SELFREG_E_TYPELIB; AdHocRegKeyPtr pkeyNobjLib0(pkeyNobjLib.create(L"0\\win64")); lrc = pkeyNobjLib0.setValue(NULL, wszModule); if (lrc != ERROR_SUCCESS) return SELFREG_E_TYPELIB; AdHocRegKeyPtr pkeyNobjLibFlags(pkeyNobjLib.create(L"FLAGS")); lrc = pkeyNobjLibFlags.setValue(NULL, L"0"); if (lrc != ERROR_SUCCESS) return SELFREG_E_TYPELIB; if (GetSystemDirectoryW(wszModule, MAX_PATH) == 0) return SELFREG_E_TYPELIB; AdHocRegKeyPtr pkeyNobjLibHelpDir(pkeyNobjLib.create(L"HELPDIR")); lrc = pkeyNobjLibHelpDir.setValue(NULL, wszModule); if (lrc != ERROR_SUCCESS) return SELFREG_E_TYPELIB; } #ifdef RT_EXCEPTIONS_ENABLED catch (AdHocRegError) { return SELFREG_E_TYPELIB; } #endif return S_OK; } STDAPI DllUnregisterServer(void) { static struct { HKEY hKeyRoot; wchar_t const *pwszParentKey; wchar_t const *pwszKeyToDelete; HRESULT hrcFail; } s_aKeys[] = { { HKEY_CLASSES_ROOT, L"TypeLib", L"{2A0C94D1-40E1-439C-8FE8-24107CAB0840}", SELFREG_E_TYPELIB }, { HKEY_CLASSES_ROOT, L"CLSID", L"{f374d1a0-bf08-4bdc-9cb2-c15ddaeef955}", SELFREG_E_CLASS }, }; HRESULT hrc = S_OK; for (size_t i = 0; i < RT_ELEMENTS(s_aKeys); i++) { HKEY hKey = NULL; LSTATUS lrc = RegOpenKeyExW(s_aKeys[i].hKeyRoot, s_aKeys[i].pwszParentKey, 0, KEY_ALL_ACCESS, &hKey); if (lrc == ERROR_SUCCESS) { lrc = RegDeleteTreeW(hKey, s_aKeys[i].pwszKeyToDelete); /* Vista and later */ RegCloseKey(hKey); } if (lrc != ERROR_SUCCESS && lrc != ERROR_FILE_NOT_FOUND && hrc == S_OK) hrc = s_aKeys[i].hrcFail; } return S_OK; }