[MSGINA] CreateProfile(): Fix initialization of some WLX_PROFILE_V2_0 members (#8321)

The following members of the returned `WLX_PROFILE_V2_0` structure:
`pszProfile`, `pszPolicy`, `pszNetworkDefaultUserProfile`, and
`pszServerName`, have a specific meaning and are used when logging
to (NT4, Active Directory...) domains.
See the added code comments for details.

In particular, `pszProfile` specifies the path to a *roaming* user
profile[^1] on a domain server, if any. It **DOES NOT** specify the local
`"C:\Documents and Settings"` path (got via `GetProfilesDirectoryW()`).

Since we don't really support user login on domains, set these pointers to NULL.

----

Enabling UserEnv debug logging[^2] on Windows 2003, one can observe such
following traces, for a `TestUser` roaming user profile:
```
USERENV(148.14c) 20:24:59:821 LoadUserProfile: Entering, hToken = <0x8bc>, lpProfileInfo = 0x6e5d8
USERENV(148.14c) 20:24:59:875 LoadUserProfile: lpProfileInfo->dwFlags = <0x0>
USERENV(148.14c) 20:24:59:912 LoadUserProfile: lpProfileInfo->lpUserName = <TestUser>
USERENV(148.14c) 20:24:59:966 LoadUserProfile: lpProfileInfo->lpProfilePath = <C:\Documents and Settings\TestUser_Roaming>
USERENV(148.14c) 20:25:00:021 LoadUserProfile: lpProfileInfo->lpDefaultPath = <\\PC-H\netlogon\Default User>
USERENV(148.14c) 20:25:00:075 LoadUserProfile: NULL server name
...
USERENV(148.14c) 20:25:06:177 CopyProfileDirectoryEx: Found hive file NTUSER.DAT
USERENV(148.14c) 20:25:06:395 ReconcileFile: C:\Documents and Settings\TestUser_Roaming\NTUSER.DAT ==> C:\Documents and Settings\TestUser\NTUSER.DAT  [OK]
...
```
The `lpProfilePath` specifies a roaming profile directory (`"TestUser_Roaming"`)
for a user named named `TestUser`, and UserEnv proceeds to image the roaming
profile into the directory (`"TestUser"`) in the local computer.

However, when the user has a local profile, the `lpProfilePath` is set to
NULL in this case, and one observes instead:
```
USERENV(148.14c) 20:21:22:644 LoadUserProfile: NULL central profile path
```

----

[^1]: The following links explain this, also demonstrating UserEnv debug logging:
    - https://web.archive.org/web/20130319204738/http://blogs.technet.com/b/askds/archive/2008/11/11/understanding-how-to-read-a-userenv-log-part-2.aspx
    - https://web.archive.org/web/20150405040409/http://blogs.technet.com/b/ad/archive/2007/08/20/tracking-user-environment-creation.aspx

[^2]: For more details, see:
  https://www.betaarchive.com/wiki/index.php?title=Microsoft_KB_Archive/221833
  (archived from: http://support.microsoft.com/kb/221833)
  UserEnv debug logging is achieved by adding a `REG_DWORD` value named
  `UserEnvDebugLevel` in the following registry sub-key:
  `HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon`
  with a non-zero value.
  To view the output in a debugger (e.g. WinDbg when kernel-debugging
  Windows), set the value to `0x00020002`.
This commit is contained in:
Hermès Bélusca-Maïto
2025-08-10 19:21:46 +02:00
parent 96249c12d9
commit aa67222595

View File

@@ -835,19 +835,21 @@ CreateProfile(
IN PWSTR Password)
{
PWLX_PROFILE_V2_0 pProfile = NULL;
PWSTR pProfilePath = NULL;
PWSTR pEnvironment = NULL;
TOKEN_STATISTICS Stats;
DWORD cbStats, cbSize;
DWORD dwLength;
BOOL bResult;
#if 0
BOOL bIsDomainLogon;
WCHAR ComputerName[MAX_COMPUTERNAME_LENGTH+1];
#endif
/* Store the logon time in the context */
GetLocalTime(&pgContext->LogonTime);
/* Store user and domain in the context */
wcscpy(pgContext->UserName, UserName);
if (Domain == NULL || wcslen(Domain) == 0)
if (Domain == NULL || !Domain[0])
{
dwLength = _countof(pgContext->DomainName);
GetComputerNameW(pgContext->DomainName, &dwLength);
@@ -856,25 +858,17 @@ CreateProfile(
{
wcscpy(pgContext->DomainName, Domain);
}
/* From now on we use in UserName and Domain the captured values from pgContext */
UserName = pgContext->UserName;
Domain = pgContext->DomainName;
/* Get profile path */
cbSize = 0;
bResult = GetProfilesDirectoryW(NULL, &cbSize);
if (!bResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
pProfilePath = LocalAlloc(LMEM_FIXED, cbSize * sizeof(WCHAR));
if (!pProfilePath)
{
WARN("LocalAlloc() failed\n");
goto cleanup;
}
bResult = GetProfilesDirectoryW(pProfilePath, &cbSize);
}
if (!bResult)
{
WARN("GetUserProfileDirectoryW() failed\n");
goto cleanup;
}
#if 0
/* Determine whether this is really a domain logon, by verifying
* that the specified domain is different from the local computer */
dwLength = _countof(ComputerName);
GetComputerNameW(ComputerName, &dwLength);
bIsDomainLogon = (_wcsicmp(ComputerName, Domain) != 0);
#endif
/* Allocate memory for profile */
pProfile = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(*pProfile));
@@ -884,10 +878,61 @@ CreateProfile(
goto cleanup;
}
pProfile->dwType = WLX_PROFILE_TYPE_V2_0;
pProfile->pszProfile = pProfilePath;
/*
* TODO: For domain logon support:
*
* - pszProfile: Specifies the path to a *roaming* user profile on a
* domain server, if any. It is then used to create a local image
* (copy) of the profile on the local computer.
* ** This data should be retrieved from the LsaLogonUser() call
* made by MyLogonUser()! **
*
* - pszPolicy (for domain logon): Path to a policy file.
* Windows' msgina.dll hardcodes it as:
* "\\<domain_controller>\netlogon\ntconfig.pol"
*
* - pszNetworkDefaultUserProfile (for domain logon): Path to the
* default user profile. Windows' msgina.dll hardcodes it as:
* "\\<domain_controller>\netlogon\Default User"
*
* - pszServerName (for domain logon): Name ("domain_controller") of
* the server (local computer; Active Directory domain controller...)
* that validated the logon.
* ** This data should be retrieved from the LsaLogonUser() call
* made by MyLogonUser()! **
*
* NOTES:
* - The paths use the domain controllers' "netlogon" share.
* - These strings are LocalAlloc'd here, and LocalFree'd by Winlogon.
*/
pProfile->pszProfile = NULL;
pProfile->pszPolicy = NULL;
pProfile->pszNetworkDefaultUserProfile = NULL;
pProfile->pszServerName = NULL;
#if 0
if (bIsDomainLogon)
{
PWSTR pServerName;
cbSize = sizeof(L"\\\\") + wcslen(Domain) * sizeof(WCHAR);
pServerName = LocalAlloc(LMEM_FIXED, cbSize);
if (!pServerName)
WARN("HeapAlloc() failed\n"); // Consider this optional, so no failure.
else
StringCbPrintfW(pServerName, cbSize, L"\\\\%ws", Domain); // See LogonServer below.
pProfile->pszServerName = pServerName;
}
#endif
/* Build the minimal environment string block */
// FIXME: LogonServer is the name of the server that processed the logon
// request ("domain_controller"). It can be different from the selected
// user's logon domain.
// See e.g.:
// - https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-msv1_0_interactive_profile
// - https://learn.microsoft.com/en-us/windows/win32/api/winwlx/ns-winwlx-wlx_consoleswitch_credentials_info_v1_0
cbSize = sizeof(L"LOGONSERVER=\\\\") +
wcslen(pgContext->DomainName) * sizeof(WCHAR) +
wcslen(Domain) * sizeof(WCHAR) +
sizeof(UNICODE_NULL);
pEnvironment = LocalAlloc(LMEM_FIXED, cbSize);
if (!pEnvironment)
@@ -896,19 +941,20 @@ CreateProfile(
goto cleanup;
}
StringCbPrintfW(pEnvironment, cbSize, L"LOGONSERVER=\\\\%ls", pgContext->DomainName);
StringCbPrintfW(pEnvironment, cbSize, L"LOGONSERVER=\\\\%ws", Domain);
ASSERT(wcslen(pEnvironment) == cbSize / sizeof(WCHAR) - 2);
pEnvironment[cbSize / sizeof(WCHAR) - 1] = UNICODE_NULL;
pProfile->pszEnvironment = pEnvironment;
/* Return the other info */
if (!GetTokenInformation(pgContext->UserToken,
TokenStatistics,
&Stats,
sizeof(Stats),
&cbStats))
{
WARN("Couldn't get Authentication id from user token!\n");
WARN("Couldn't get Authentication Id from user token!\n");
goto cleanup;
}
@@ -924,8 +970,6 @@ CreateProfile(
cleanup:
if (pEnvironment)
LocalFree(pEnvironment);
if (pProfilePath)
LocalFree(pProfilePath);
if (pProfile)
LocalFree(pProfile);
return FALSE;