https://git.reactos.org/?p=reactos.git;a=commitdiff;h=e90e918039e60228d1c3e3a7e8bc016915dd49ac

commit e90e918039e60228d1c3e3a7e8bc016915dd49ac
Author:     George Bișoc <[email protected]>
AuthorDate: Thu Mar 10 12:03:31 2022 +0100
Commit:     George Bișoc <[email protected]>
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;
 }
 
 

Reply via email to