[MSGINA][WINLOGON] Perform thorough memory cleanup after user logging (#8155)

- MSGINA: The `pMprNotifyInfo` and `pProfile` structures returned by
  `WlxLoggedOutSAS()`, as well as all of their pointer fields, are
  allocated by `LocalAlloc()`[^1][^2]. This is what Windows' Winlogon
  expects (and ours too, for interoperability with GINA dlls written
  for Windows), as it then frees them using `LocalFree()`.

- WINLOGON: In `HandleLogon()`, free the cached `MprNotifyInfo` and
  `Profile` buffers (and all their members) obtained from a previous
  call to `WlxLoggedOutSAS()`.

[^1]: https://learn.microsoft.com/en-us/windows/win32/api/winwlx/nf-winwlx-wlxloggedoutsas
[^2]: 3rd-party GINAs rely on this as well. One example can be found at:
      https://www.codeproject.com/Articles/20656/Winlogon-using-Mobile-Disk
This commit is contained in:
Hermès Bélusca-Maïto
2025-06-19 17:12:27 +02:00
parent 64d1301b5a
commit b254ea8274
3 changed files with 83 additions and 25 deletions

View File

@@ -491,6 +491,61 @@ quit:
RevertToSelf();
}
/**
* @brief
* Frees the Profile information structure (WLX_PROFILE_V1_0
* or WLX_PROFILE_V2_0) allocated by the GINA.
**/
static VOID
FreeWlxProfileInfo(
_Inout_ PVOID Profile)
{
PWLX_PROFILE_V2_0 pProfile = (PWLX_PROFILE_V2_0)Profile;
if (pProfile->dwType != WLX_PROFILE_TYPE_V1_0
&& pProfile->dwType != WLX_PROFILE_TYPE_V2_0)
{
ERR("WL: Wrong profile info\n");
return;
}
if (pProfile->pszProfile)
LocalFree(pProfile->pszProfile);
if (pProfile->dwType >= WLX_PROFILE_TYPE_V2_0)
{
if (pProfile->pszPolicy)
LocalFree(pProfile->pszPolicy);
if (pProfile->pszNetworkDefaultUserProfile)
LocalFree(pProfile->pszNetworkDefaultUserProfile);
if (pProfile->pszServerName)
LocalFree(pProfile->pszServerName);
if (pProfile->pszEnvironment)
LocalFree(pProfile->pszEnvironment);
}
}
/**
* @brief
* Frees the MPR information structure allocated by the GINA.
*
* @note
* Currently used only in HandleLogon(), but will also be used
* by WlxChangePasswordNotify(Ex) once implemented.
**/
static VOID
FreeWlxMprInfo(
_Inout_ PWLX_MPR_NOTIFY_INFO MprNotifyInfo)
{
if (MprNotifyInfo->pszUserName)
LocalFree(MprNotifyInfo->pszUserName);
if (MprNotifyInfo->pszDomain)
LocalFree(MprNotifyInfo->pszDomain);
if (MprNotifyInfo->pszPassword)
LocalFree(MprNotifyInfo->pszPassword);
if (MprNotifyInfo->pszOldPassword)
LocalFree(MprNotifyInfo->pszOldPassword);
}
static
BOOL
HandleLogon(
@@ -611,10 +666,13 @@ HandleLogon(
cleanup:
if (Session->Profile)
{
HeapFree(GetProcessHeap(), 0, Session->Profile->pszProfile);
HeapFree(GetProcessHeap(), 0, Session->Profile);
FreeWlxProfileInfo(Session->Profile);
LocalFree(Session->Profile);
Session->Profile = NULL;
}
Session->Profile = NULL;
FreeWlxMprInfo(&Session->MprNotifyInfo);
ZeroMemory(&Session->MprNotifyInfo, sizeof(Session->MprNotifyInfo));
if (!ret && ProfileInfo.hProfile != INVALID_HANDLE_VALUE)
{
UnloadUserProfile(Session->UserToken, ProfileInfo.hProfile);

View File

@@ -254,7 +254,7 @@ typedef struct _WLSESSION
/* Logon informations */
DWORD Options;
WLX_MPR_NOTIFY_INFO MprNotifyInfo;
WLX_PROFILE_V2_0 *Profile;
PWLX_PROFILE_V2_0 Profile;
} WLSESSION, *PWLSESSION;
typedef enum _NOTIFICATION_TYPE

View File

@@ -834,10 +834,10 @@ CreateProfile(
IN PWSTR Domain,
IN PWSTR Password)
{
LPWSTR ProfilePath = NULL;
LPWSTR lpEnvironment = NULL;
TOKEN_STATISTICS Stats;
PWLX_PROFILE_V2_0 pProfile = NULL;
PWSTR pProfilePath = NULL;
PWSTR pEnvironment = NULL;
TOKEN_STATISTICS Stats;
DWORD cbStats, cbSize;
DWORD dwLength;
BOOL bResult;
@@ -862,13 +862,13 @@ CreateProfile(
bResult = GetProfilesDirectoryW(NULL, &cbSize);
if (!bResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
ProfilePath = HeapAlloc(GetProcessHeap(), 0, cbSize * sizeof(WCHAR));
if (!ProfilePath)
pProfilePath = LocalAlloc(LMEM_FIXED, cbSize * sizeof(WCHAR));
if (!pProfilePath)
{
WARN("HeapAlloc() failed\n");
WARN("LocalAlloc() failed\n");
goto cleanup;
}
bResult = GetProfilesDirectoryW(ProfilePath, &cbSize);
bResult = GetProfilesDirectoryW(pProfilePath, &cbSize);
}
if (!bResult)
{
@@ -877,30 +877,30 @@ CreateProfile(
}
/* Allocate memory for profile */
pProfile = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WLX_PROFILE_V2_0));
pProfile = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(*pProfile));
if (!pProfile)
{
WARN("HeapAlloc() failed\n");
goto cleanup;
}
pProfile->dwType = WLX_PROFILE_TYPE_V2_0;
pProfile->pszProfile = ProfilePath;
pProfile->pszProfile = pProfilePath;
cbSize = sizeof(L"LOGONSERVER=\\\\") +
wcslen(pgContext->DomainName) * sizeof(WCHAR) +
sizeof(UNICODE_NULL);
lpEnvironment = HeapAlloc(GetProcessHeap(), 0, cbSize);
if (!lpEnvironment)
pEnvironment = LocalAlloc(LMEM_FIXED, cbSize);
if (!pEnvironment)
{
WARN("HeapAlloc() failed\n");
WARN("LocalAlloc() failed\n");
goto cleanup;
}
StringCbPrintfW(lpEnvironment, cbSize, L"LOGONSERVER=\\\\%ls", pgContext->DomainName);
ASSERT(wcslen(lpEnvironment) == cbSize / sizeof(WCHAR) - 2);
lpEnvironment[cbSize / sizeof(WCHAR) - 1] = UNICODE_NULL;
StringCbPrintfW(pEnvironment, cbSize, L"LOGONSERVER=\\\\%ls", pgContext->DomainName);
ASSERT(wcslen(pEnvironment) == cbSize / sizeof(WCHAR) - 2);
pEnvironment[cbSize / sizeof(WCHAR) - 1] = UNICODE_NULL;
pProfile->pszEnvironment = lpEnvironment;
pProfile->pszEnvironment = pEnvironment;
if (!GetTokenInformation(pgContext->UserToken,
TokenStatistics,
@@ -922,12 +922,12 @@ CreateProfile(
return TRUE;
cleanup:
if (pEnvironment)
LocalFree(pEnvironment);
if (pProfilePath)
LocalFree(pProfilePath);
if (pProfile)
{
HeapFree(GetProcessHeap(), 0, pProfile->pszEnvironment);
}
HeapFree(GetProcessHeap(), 0, pProfile);
HeapFree(GetProcessHeap(), 0, ProfilePath);
LocalFree(pProfile);
return FALSE;
}