https://git.reactos.org/?p=reactos.git;a=commitdiff;h=e90e918039e60228d1c3e…
commit e90e918039e60228d1c3e3a7e8bc016915dd49ac
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Mar 10 12:03:31 2022 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Fri May 6 10:09:50 2022 +0200
[ADVAPI32] Soft rewrite of CreateProcessAsUserCommon
Refactor the function in such a way that it can jump to a single exit but most
importantly, implement a "rinse and repeat" mechanism where we assign a primary
token to process by disabling impersonation first and retry with impersonation later.
More info can be found in the documention within the code.
---
dll/win32/advapi32/misc/logon.c | 419 +++++++++++++++++++++++++++++++++-------
1 file changed, 352 insertions(+), 67 deletions(-)
diff --git a/dll/win32/advapi32/misc/logon.c b/dll/win32/advapi32/misc/logon.c
index 1262f1b652d..6687a9f7e93 100644
--- a/dll/win32/advapi32/misc/logon.c
+++ b/dll/win32/advapi32/misc/logon.c
@@ -89,24 +89,221 @@ CloseLogonLsaHandle(VOID)
}
+/**
+ * @brief
+ * Sets a primary token to the newly created process.
+ * The primary token that gets assigned to is a token
+ * whose security context is associated with the logged
+ * in user. For futher documentation information, see
+ * Remarks.
+ *
+ * @param[in] ImpersonateAsSelf
+ * If set to TRUE, the function will act on behalf of
+ * the calling process by impersonating its security context.
+ * Generally the caller will disable impersonation and attempt
+ * to act on behalf of the said main process as a first tentative
+ * to acquire the needed privilege in order to assign a token
+ * to the process. If set to FALSE, the function won't act on behalf
+ * of the calling process.
+ *
+ * @param[in] ProcessHandle
+ * A handle to the newly created process. The function will use it
+ * as a mean to assign the primary token to this process.
+ *
+ * @param[in] ThreadHandle
+ * A handle to the newly and primary created thread associated with
+ * the process.
+ *
+ * @param[in] DuplicatedTokenHandle
+ * A handle to a duplicated access token. This token represents as a primary
+ * one, initially duplicated in form as a primary type from an impersonation
+ * type.
+ *
+ * @return
+ * STATUS_SUCCESS is returned if token assignment to process succeeded, otherwise
+ * a failure NTSTATUS code is returned. A potential failure status code is
+ * STATUS_ACCESS_DENIED which means the caller doesn't have enough rights
+ * to grant access for primary token assignment to process.
+ *
+ * @remarks
+ * This function acts like an internal helper for CreateProcessAsUserCommon (and as
+ * such for CreateProcessAsUserW/A as well) as once a process is created, the
+ * function is tasked to assign the security context of the logged in user to
+ * that process. However, the rate of success of inserting the token into the
+ * process ultimately depends on the caller.
+ *
+ * The caller will either succeed or fail at acquiring SE_ASSIGNPRIMARYTOKEN_PRIVILEGE
+ * privilege depending on the security context of the user. If it's allowed, the
caller
+ * would generally acquire such privilege immediately but if not, the caller will
attempt
+ * to do a second try.
+ */
+static
+NTSTATUS
+InsertTokenToProcessCommon(
+ _In_ BOOL ImpersonateAsSelf,
+ _In_ HANDLE ProcessHandle,
+ _In_ HANDLE ThreadHandle,
+ _In_ HANDLE DuplicatedTokenHandle)
+{
+ NTSTATUS Status;
+ PROCESS_ACCESS_TOKEN AccessToken;
+ BOOLEAN PrivilegeSet;
+ BOOLEAN HavePrivilege;
+
+ /*
+ * Assume the SE_ASSIGNPRIMARYTOKEN_PRIVILEGE
+ * privilege hasn't been set.
+ */
+ PrivilegeSet = FALSE;
+
+ /*
+ * The caller asked that we must impersonate as
+ * ourselves, that is, we'll be going to impersonate
+ * the security context of the calling process. If
+ * self impersonation fails then the caller has
+ * to do a "rinse and repeat" approach.
+ */
+ if (ImpersonateAsSelf)
+ {
+ Status = RtlImpersonateSelf(SecurityImpersonation);
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("RtlImpersonateSelf(SecurityImpersonation) failed, Status
0x%08x\n", Status);
+ return Status;
+ }
+ }
+
+ /*
+ * Attempt to acquire the process primary token assignment privilege
+ * in case we actually need it.
+ * The call will either succeed or fail when the caller has (or has not)
+ * enough rights.
+ * The last situation may not be dramatic for us. Indeed it may happen
+ * that the user-provided token is a restricted version of the caller's
+ * primary token (aka. a "child" token), or both tokens inherit (i.e. are
+ * children, and are together "siblings") from a common parent token.
+ * In this case the NT kernel allows us to assign the token to the child
+ * process without the need for the assignment privilege, which is fine.
+ * On the contrary, if the user-provided token is completely arbitrary,
+ * then the NT kernel will enforce the presence of the assignment privilege:
+ * because we failed (by assumption) to assign the privilege, the process
+ * token assignment will fail as required. It is then the job of the
+ * caller to manually acquire the necessary privileges.
+ */
+ Status = RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE,
+ TRUE, TRUE, &PrivilegeSet);
+ HavePrivilege = NT_SUCCESS(Status);
+ if (!HavePrivilege)
+ {
+ ERR("RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE) failed, Status
0x%08lx, "
+ "attempting to continue without it...\n", Status);
+ }
+
+ /*
+ * Assign the duplicated token and thread
+ * handle to the structure so that we'll
+ * use it to assign the primary token
+ * to process.
+ */
+ AccessToken.Token = DuplicatedTokenHandle;
+ AccessToken.Thread = ThreadHandle;
+
+ /* Set the new process token */
+ Status = NtSetInformationProcess(ProcessHandle,
+ ProcessAccessToken,
+ (PVOID)&AccessToken,
+ sizeof(AccessToken));
+
+ /* Restore the privilege */
+ if (HavePrivilege)
+ {
+ RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE,
+ PrivilegeSet, TRUE, &PrivilegeSet);
+ }
+
+ /*
+ * Check again if the caller wanted to impersonate
+ * as self. If that is the case we must revert this
+ * impersonation back.
+ */
+ if (ImpersonateAsSelf)
+ {
+ RevertToSelf();
+ }
+
+ /*
+ * Finally, check if we actually succeeded on assigning
+ * a primary token to the process. If we failed, oh well,
+ * asta la vista baby e arrivederci. The caller has to do
+ * a rinse and repeat approach.
+ */
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to assign primary token to the process (Status 0x%08lx)\n",
Status);
+ return Status;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+/**
+ * @brief
+ * Internal function that serves as a helper for
+ * CreateProcessAsUserW/A routines on creating
+ * a process within the context of the logged in
+ * user.
+ *
+ * @param[in] hToken
+ * A handle to an access token that is associated
+ * with the logged in user. If the caller does not
+ * submit a token, the helper will immediately quit
+ * and return success, and the newly created process
+ * will be created upon using the default security
+ * context.
+ *
+ * @param[in] dwCreationFlags
+ * Bit masks containing the creation process flags.
+ * The function uses this parameter to determine
+ * if the process wasn't created in a suspended way
+ * and if not the function will resume the main thread.
+ *
+ * @param[in,out] lpProcessInformation
+ * A pointer to a structure that contains process creation
+ * information data. Such pointer contains the process
+ * and thread handles and whatnot.
+ *
+ * @return
+ * Returns TRUE if the helper has successfully assigned
+ * the newly created process the user's security context
+ * to that process, otherwise FALSE is returned.
+ *
+ * @remarks
+ * In order for the helper function to assign the primary
+ * token to the process, it has to do a "rinse and repeat"
+ * approach. That is, the helper will stop the impersonation
+ * and attempt to assign the token to process by acting
+ * on behalf of the main process' security context. If that
+ * fails, the function will do a second attempt by doing this
+ * but with impersonation enabled instead.
+ */
static
BOOL
CreateProcessAsUserCommon(
_In_opt_ HANDLE hToken,
_In_ DWORD dwCreationFlags,
- _Out_ LPPROCESS_INFORMATION lpProcessInformation)
+ _Inout_ LPPROCESS_INFORMATION lpProcessInformation)
{
- NTSTATUS Status;
- PROCESS_ACCESS_TOKEN AccessToken;
+ NTSTATUS Status = STATUS_SUCCESS, StatusOnExit;
+ BOOL Success;
+ TOKEN_TYPE Type;
+ ULONG ReturnLength;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE hTokenDup = NULL;
+ HANDLE OriginalImpersonationToken = NULL;
+ HANDLE NullToken = NULL;
if (hToken != NULL)
{
- TOKEN_TYPE Type;
- ULONG ReturnLength;
- OBJECT_ATTRIBUTES ObjectAttributes;
- HANDLE hTokenDup;
- BOOLEAN PrivilegeSet = FALSE, HavePrivilege;
-
/* Check whether the user-provided token is a primary token */
// GetTokenInformation();
Status = NtQueryInformationToken(hToken,
@@ -117,15 +314,59 @@ CreateProcessAsUserCommon(
if (!NT_SUCCESS(Status))
{
ERR("NtQueryInformationToken() failed, Status 0x%08x\n", Status);
+ Success = FALSE;
goto Quit;
}
+
if (Type != TokenPrimary)
{
ERR("Wrong token type for token 0x%p, expected TokenPrimary, got
%ld\n", hToken, Type);
Status = STATUS_BAD_TOKEN_TYPE;
+ Success = FALSE;
goto Quit;
}
+ /*
+ * Open the original token of the calling thread
+ * and halt the impersonation for the moment
+ * being. The opened thread token will be cached
+ * so that we will restore it back when we're done.
+ */
+ Status = NtOpenThreadToken(NtCurrentThread(),
+ TOKEN_QUERY | TOKEN_IMPERSONATE,
+ TRUE,
+ &OriginalImpersonationToken);
+ if (!NT_SUCCESS(Status))
+ {
+ /* We failed? Does this thread have a token at least? */
+ OriginalImpersonationToken = NULL;
+ if (Status != STATUS_NO_TOKEN)
+ {
+ /*
+ * OK so this thread has a token but we
+ * could not open it for whatever reason.
+ * Bail out then.
+ */
+ ERR("Failed to open thread token with 0x%08lx\n", Status);
+ Success = FALSE;
+ goto Quit;
+ }
+ }
+ else
+ {
+ /* We succeeded, stop the impersonation for now */
+ Status = NtSetInformationThread(NtCurrentThread(),
+ ThreadImpersonationToken,
+ &NullToken,
+ sizeof(NullToken));
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to stop impersonation with 0x%08lx\n", Status);
+ Success = FALSE;
+ goto Quit;
+ }
+ }
+
/* Duplicate the token for this new process */
InitializeObjectAttributes(&ObjectAttributes,
NULL,
@@ -141,79 +382,123 @@ CreateProcessAsUserCommon(
if (!NT_SUCCESS(Status))
{
ERR("NtDuplicateToken() failed, Status 0x%08x\n", Status);
- goto Quit;
- }
-
- // FIXME: Do we always need SecurityImpersonation?
- Status = RtlImpersonateSelf(SecurityImpersonation);
- if (!NT_SUCCESS(Status))
- {
- ERR("RtlImpersonateSelf(SecurityImpersonation) failed, Status
0x%08x\n", Status);
- NtClose(hTokenDup);
+ Success = FALSE;
goto Quit;
}
/*
- * Attempt to acquire the process primary token assignment privilege
- * in case we actually need it.
- * The call will either succeed or fail when the caller has (or has not)
- * enough rights.
- * The last situation may not be dramatic for us. Indeed it may happen
- * that the user-provided token is a restricted version of the caller's
- * primary token (aka. a "child" token), or both tokens inherit (i.e.
are
- * children, and are together "siblings") from a common parent token.
- * In this case the NT kernel allows us to assign the token to the child
- * process without the need for the assignment privilege, which is fine.
- * On the contrary, if the user-provided token is completely arbitrary,
- * then the NT kernel will enforce the presence of the assignment privilege:
- * because we failed (by assumption) to assign the privilege, the process
- * token assignment will fail as required. It is then the job of the
- * caller to manually acquire the necessary privileges.
+ * Now it's time to set the primary token into
+ * the process. On the first try, do it by
+ * impersonating the security context of the
+ * calling process (impersonate as self).
*/
- Status = RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE,
- TRUE, TRUE, &PrivilegeSet);
- HavePrivilege = NT_SUCCESS(Status);
- if (!HavePrivilege)
+ Status = InsertTokenToProcessCommon(TRUE,
+ lpProcessInformation->hProcess,
+ lpProcessInformation->hThread,
+ hTokenDup);
+ if (!NT_SUCCESS(Status))
{
- ERR("RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE) failed, Status
0x%08lx, "
- "attempting to continue without it...\n", Status);
- }
-
- AccessToken.Token = hTokenDup;
- AccessToken.Thread = lpProcessInformation->hThread;
+ /*
+ * OK, we failed. Our second (and last try) is to not
+ * impersonate as self but instead we will try by setting
+ * the original impersonation (thread) token and set the
+ * primary token to the process through this way. This is
+ * what we call -- the "rinse and repeat" approach.
+ */
+ Status = NtSetInformationThread(NtCurrentThread(),
+ ThreadImpersonationToken,
+ &OriginalImpersonationToken,
+ sizeof(OriginalImpersonationToken));
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to restore impersonation token for setting process
token, Status 0x%08lx\n", Status);
+ NtClose(hTokenDup);
+ Success = FALSE;
+ goto Quit;
+ }
- /* Set the new process token */
- Status = NtSetInformationProcess(lpProcessInformation->hProcess,
- ProcessAccessToken,
- (PVOID)&AccessToken,
- sizeof(AccessToken));
+ /* Retry again */
+ Status = InsertTokenToProcessCommon(FALSE,
+ lpProcessInformation->hProcess,
+ lpProcessInformation->hThread,
+ hTokenDup);
+ if (!NT_SUCCESS(Status))
+ {
+ /* Even the second try failed, bail out... */
+ ERR("Failed to insert the primary token into process, Status
0x%08lx\n", Status);
+ NtClose(hTokenDup);
+ Success = FALSE;
+ goto Quit;
+ }
- /* Restore the privilege */
- if (HavePrivilege)
- {
- RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE,
- PrivilegeSet, TRUE, &PrivilegeSet);
+ /* All good, now stop impersonation */
+ Status = NtSetInformationThread(NtCurrentThread(),
+ ThreadImpersonationToken,
+ &NullToken,
+ sizeof(NullToken));
+ if (!NT_SUCCESS(Status))
+ {
+ ERR("Failed to unset impersonationg token after setting process
token, Status 0x%08lx\n", Status);
+ NtClose(hTokenDup);
+ Success = FALSE;
+ goto Quit;
+ }
}
- RevertToSelf();
+ /*
+ * FIXME: As we have successfully set up a primary token to
+ * the newly created process, we must set up as well a definite
+ * limit of quota charges for this process on the context of
+ * this user.
+ */
/* Close the duplicated token */
NtClose(hTokenDup);
+ Success = TRUE;
+ }
- /* Check whether NtSetInformationProcess() failed */
- if (!NT_SUCCESS(Status))
- {
- ERR("NtSetInformationProcess() failed, Status 0x%08x\n", Status);
- goto Quit;
- }
+ /*
+ * If the caller did not supply a token then just declare
+ * ourselves as job done. The newly created process will use
+ * the default security context at this point anyway.
+ */
+ TRACE("No token supplied, the process will use default security
context!\n");
+ Success = TRUE;
- if (!NT_SUCCESS(Status))
- {
Quit:
- TerminateProcess(lpProcessInformation->hProcess, Status);
- SetLastError(RtlNtStatusToDosError(Status));
- return FALSE;
- }
+ /*
+ * If we successfully opened the thread token before
+ * and stopped the impersonation then we have to assign
+ * its original token back and close that token we have
+ * referenced it.
+ */
+ if (OriginalImpersonationToken != NULL)
+ {
+ StatusOnExit = NtSetInformationThread(NtCurrentThread(),
+ ThreadImpersonationToken,
+ &OriginalImpersonationToken,
+ sizeof(OriginalImpersonationToken));
+
+ /*
+ * We really must assert ourselves that we successfully
+ * set the original token back, otherwise if we fail
+ * then something is seriously going wrong....
+ * The status code is cached in a separate status
+ * variable because we would not want to tamper
+ * with the original status code that could have been
+ * returned by someone else above in this function code.
+ */
+ ASSERT(NT_SUCCESS(StatusOnExit));
+
+ /* De-reference it */
+ NtClose(OriginalImpersonationToken);
+ }
+
+ /* Terminate the process and set the last error status */
+ if (!NT_SUCCESS(Status))
+ {
+ TerminateProcess(lpProcessInformation->hProcess, Status);
+ SetLastError(RtlNtStatusToDosError(Status));
}
/* Resume the main thread */
@@ -222,7 +507,7 @@ Quit:
ResumeThread(lpProcessInformation->hThread);
}
- return TRUE;
+ return Success;
}