/*++ Copyright (c) 1989 Microsoft Corporation Module Name: security.c Abstract: This module implements the security related portions of the process structure. Author: Mark Lucovsky (markl) 25-Apr-1989 Jim Kelly (JimK) 2-August-1990 Neill Clift (NeillC) 14-Aug-2000 Revamped for fast referencing the primary token and holding the security lock only over critical portions. Revision History: --*/ #include "psp.h" #ifdef ALLOC_PRAGMA NTSTATUS PsOpenTokenOfJobObject( IN HANDLE JobObject, OUT PACCESS_TOKEN * Token ); #pragma alloc_text(PAGE, PsReferencePrimaryToken) #pragma alloc_text(PAGE, PsReferenceImpersonationToken) #pragma alloc_text(PAGE, PsReferenceEffectiveToken) #pragma alloc_text(PAGE, PsOpenTokenOfThread) #pragma alloc_text(PAGE, PsOpenTokenOfProcess) #pragma alloc_text(PAGE, PsOpenTokenOfJobObject) #pragma alloc_text(PAGE, PsImpersonateClient) #pragma alloc_text(PAGE, PsDisableImpersonation) #pragma alloc_text(PAGE, PsRestoreImpersonation) #pragma alloc_text(PAGE, PsRevertToSelf) #pragma alloc_text(PAGE, PsRevertThreadToSelf) #pragma alloc_text(PAGE, PspInitializeProcessSecurity) #pragma alloc_text(PAGE, PspDeleteProcessSecurity) #pragma alloc_text(PAGE, PspAssignPrimaryToken) #pragma alloc_text(PAGE, PspInitializeThreadSecurity) #pragma alloc_text(PAGE, PspDeleteThreadSecurity) #pragma alloc_text(PAGE, PsAssignImpersonationToken) #pragma alloc_text(PAGE, PspWriteTebImpersonationInfo) #endif //ALLOC_PRAGMA PACCESS_TOKEN PsReferencePrimaryToken( IN PEPROCESS Process ) /*++ Routine Description: This function returns a pointer to the primary token of a process. The reference count of that primary token is incremented to protect the pointer returned. When the pointer is no longer needed, it should be freed using PsDereferencePrimaryToken(). Arguments: Process - Supplies the address of the process whose primary token is to be referenced. Return Value: A pointer to the specified process's primary token. --*/ { PACCESS_TOKEN Token; PETHREAD CurrentThread; PAGED_CODE(); ASSERT( Process->Pcb.Header.Type == ProcessObject ); Token = ObFastReferenceObject (&Process->Token); if (Token == NULL) { CurrentThread = PsGetCurrentThread (); PspLockProcessSecurityShared (Process, CurrentThread); Token = ObFastReferenceObjectLocked (&Process->Token); PspUnlockProcessSecurityShared (Process, CurrentThread); } return Token; } PACCESS_TOKEN PsReferenceImpersonationToken( IN PETHREAD Thread, OUT PBOOLEAN CopyOnOpen, OUT PBOOLEAN EffectiveOnly, OUT PSECURITY_IMPERSONATION_LEVEL ImpersonationLevel ) /*++ Routine Description: This function returns a pointer to the impersonation token of a thread. The reference count of that impersonation token is incremented to protect the pointer returned. If the thread is not currently impersonating a client, then a null pointer is returned. If the thread is impersonating a client, then information about the means of impersonation are also returned (ImpersonationLevel). If a non-null value is returned, then PsDereferenceImpersonationToken() must be called to decrement the token's reference count when the pointer is no longer needed. Arguments: Thread - Supplies the address of the thread whose impersonation token is to be referenced. CopyOnOpen - The current value of the Thread->ImpersonationInfo->CopyOnOpen field. EffectiveOnly - The current value of the Thread->ImpersonationInfo->EffectiveOnly field. ImpersonationLevel - The current value of the Thread->ImpersonationInfo->ImpersonationLevel field. Return Value: A pointer to the specified thread's impersonation token. If the thread is not currently impersonating a client, then NULL is returned. --*/ { PACCESS_TOKEN Token; PETHREAD CurrentThread; PPS_IMPERSONATION_INFORMATION ImpersonationInfo; PAGED_CODE(); ASSERT (Thread->Tcb.Header.Type == ThreadObject); // // before going through the lock overhead just look to see if it is // null. There is no race. Grabbing the lock is not needed until // we decide to use the token at which point we re check to see it // it is null. // This check saves about 300 instructions. // if (!PS_IS_THREAD_IMPERSONATING (Thread)) { return NULL; } // // Lock the process security fields. // CurrentThread = PsGetCurrentThread (); PspLockThreadSecurityShared (Thread, CurrentThread); // // Grab impersonation info block. // ImpersonationInfo = Thread->ImpersonationInfo; if (PS_IS_THREAD_IMPERSONATING (Thread)) { // // Return the thread's impersonation level, etc. // Token = ImpersonationInfo->Token; // // Increment the reference count of the token to protect our // pointer. // ObReferenceObject (Token); (*ImpersonationLevel) = ImpersonationInfo->ImpersonationLevel; (*CopyOnOpen) = ImpersonationInfo->CopyOnOpen; (*EffectiveOnly) = ImpersonationInfo->EffectiveOnly; } else { Token = NULL; } // // Release the security fields. // PspUnlockThreadSecurityShared (Thread, CurrentThread); return Token; } PACCESS_TOKEN PsReferenceEffectiveToken( IN PETHREAD Thread, OUT PTOKEN_TYPE TokenType, OUT PBOOLEAN EffectiveOnly, OUT PSECURITY_IMPERSONATION_LEVEL ImpersonationLevel ) /*++ Routine Description: This function returns a pointer to the effective token of a thread. The effective token of a thread is the thread's impersonation token if it has one. Otherwise, it is the primary token of the thread's process. The reference count of the effective token is incremented to protect the pointer returned. If the thread is impersonating a client, then the impersonation level is also returned. Either PsDereferenceImpersonationToken() (for an impersonation token) or PsDereferencePrimaryToken() (for a primary token) must be called to decrement the token's reference count when the pointer is no longer needed. Arguments: Thread - Supplies the address of the thread whose effective token is to be referenced. TokenType - Receives the type of the effective token. If the thread is currently impersonating a client, then this will be TokenImpersonation. Othwerwise, it will be TokenPrimary. EffectiveOnly - If the token type is TokenImpersonation, then this receives the value of the client thread's Thread->Client->EffectiveOnly field. Otherwise, it is set to FALSE. ImpersonationLevel - The current value of the Thread->Client->ImpersonationLevel field for an impersonation token and is not set for a primary token. Return Value: A pointer to the specified thread's effective token. --*/ { PACCESS_TOKEN Token; PEPROCESS Process; PETHREAD CurrentThread; PPS_IMPERSONATION_INFORMATION ImpersonationInfo; PAGED_CODE(); ASSERT (Thread->Tcb.Header.Type == ThreadObject); Process = THREAD_TO_PROCESS(Thread); // // Grab the current impersonation token pointer value // Token = NULL; if (PS_IS_THREAD_IMPERSONATING (Thread)) { // // Lock the process security fields. // CurrentThread = PsGetCurrentThread (); PspLockThreadSecurityShared (Thread, CurrentThread); if (PS_IS_THREAD_IMPERSONATING (Thread)) { // // Grab impersonation info block. // ImpersonationInfo = Thread->ImpersonationInfo; Token = ImpersonationInfo->Token; // // Return the thread's impersonation level, etc. // (*TokenType) = TokenImpersonation; (*EffectiveOnly) = ImpersonationInfo->EffectiveOnly; (*ImpersonationLevel) = ImpersonationInfo->ImpersonationLevel; // // Increment the reference count of the token to protect our // pointer. // ObReferenceObject (Token); // // Release the security fields. // PspUnlockThreadSecurityShared (Thread, CurrentThread); return Token; } // // Release the security fields. // PspUnlockThreadSecurityShared (Thread, CurrentThread); } // // Get the thread's primary token if it wasn't impersonating a client. // Token = ObFastReferenceObject (&Process->Token); if (Token == NULL) { // // Fast ref failed. We go the slow way with a lock // CurrentThread = PsGetCurrentThread (); PspLockProcessSecurityShared (Process,CurrentThread); Token = ObFastReferenceObjectLocked (&Process->Token); PspUnlockProcessSecurityShared (Process,CurrentThread); } // // Only the TokenType and CopyOnOpen OUT parameters are // returned for a primary token. // (*TokenType) = TokenPrimary; (*EffectiveOnly) = FALSE; return Token; } NTSTATUS PsOpenTokenOfThread( IN HANDLE ThreadHandle, IN BOOLEAN OpenAsSelf, OUT PACCESS_TOKEN *Token, OUT PBOOLEAN CopyOnOpen, OUT PBOOLEAN EffectiveOnly, OUT PSECURITY_IMPERSONATION_LEVEL ImpersonationLevel ) /*++ Routine Description: This function does the thread specific processing of an NtOpenThreadToken() service. The service validates that the handle has appropriate access to reference the thread. If so, it goes on to increment the reference count of the token object to prevent it from going away while the rest of the NtOpenThreadToken() request is processed. NOTE: If this call completes successfully, the caller is responsible for decrementing the reference count of the target token. This must be done using PsDereferenceImpersonationToken(). Arguments: ThreadHandle - Supplies a handle to a thread object. OpenAsSelf - Is a boolean value indicating whether the access should be made using the calling thread's current security context, which may be that of a client (if impersonating), or using the caller's process-level security context. A value of FALSE indicates the caller's current context should be used un-modified. A value of TRUE indicates the request should be fulfilled using the process level security context. Token - If successful, receives a pointer to the thread's token object. CopyOnOpen - The current value of the Thread->Client->CopyOnOpen field. EffectiveOnly - The current value of the Thread->Client->EffectiveOnly field. ImpersonationLevel - The current value of the Thread->Client->ImpersonationLevel field. Return Value: STATUS_SUCCESS - Indicates the call completed successfully. STATUS_NO_TOKEN - Indicates the referenced thread is not currently impersonating a client. STATUS_CANT_OPEN_ANONYMOUS - Indicates the client requested anonymous impersonation level. An anonymous token can not be openned. status may also be any value returned by an attemp the reference the thread object for THREAD_QUERY_INFORMATION access. --*/ { NTSTATUS Status; PETHREAD Thread; KPROCESSOR_MODE PreviousMode; PAGED_CODE(); PreviousMode = KeGetPreviousMode(); UNREFERENCED_PARAMETER (OpenAsSelf); // // Make sure the handle grants the appropriate access to the specified // thread. // Status = ObReferenceObjectByHandle (ThreadHandle, THREAD_QUERY_INFORMATION, PsThreadType, PreviousMode, &Thread, NULL); if (!NT_SUCCESS (Status)) { return Status; } // // Reference the impersonation token, if there is one // (*Token) = PsReferenceImpersonationToken (Thread, CopyOnOpen, EffectiveOnly, ImpersonationLevel); // // dereference the target thread. // ObDereferenceObject (Thread); // // Make sure there is a token // if (*Token == NULL) { return STATUS_NO_TOKEN; } // // Make sure the ImpersonationLevel is high enough to allow // the token to be openned. // if ((*ImpersonationLevel) <= SecurityAnonymous) { PsDereferenceImpersonationToken (*Token); (*Token) = NULL; return STATUS_CANT_OPEN_ANONYMOUS; } return STATUS_SUCCESS; } NTSTATUS PsOpenTokenOfProcess( IN HANDLE ProcessHandle, OUT PACCESS_TOKEN *Token ) /*++ Routine Description: This function does the process specific processing of an NtOpenProcessToken() service. The service validates that the handle has appropriate access to referenced process. If so, it goes on to reference the primary token object to prevent it from going away while the rest of the NtOpenProcessToken() request is processed. NOTE: If this call completes successfully, the caller is responsible for decrementing the reference count of the target token. This must be done using the PsDereferencePrimaryToken() API. Arguments: ProcessHandle - Supplies a handle to a process object whose primary token is to be opened. Token - If successful, receives a pointer to the process's token object. Return Value: STATUS_SUCCESS - Indicates the call completed successfully. status may also be any value returned by an attemp the reference the process object for PROCESS_QUERY_INFORMATION access. --*/ { NTSTATUS Status; PEPROCESS Process; KPROCESSOR_MODE PreviousMode; PAGED_CODE(); PreviousMode = KeGetPreviousMode(); // // Make sure the handle grants the appropriate access to the specified // process. // Status = ObReferenceObjectByHandle (ProcessHandle, PROCESS_QUERY_INFORMATION, PsProcessType, PreviousMode, &Process, NULL); if (!NT_SUCCESS (Status)) { return Status; } // // Reference the primary token // (This takes care of gaining exlusive access to the process // security fields for us) // (*Token) = PsReferencePrimaryToken (Process); // // Done with the process object // ObDereferenceObject (Process); return STATUS_SUCCESS; } NTSTATUS PsOpenTokenOfJobObject( IN HANDLE JobObject, OUT PACCESS_TOKEN * Token ) /*++ Routine Description: This function does the ps/job specific work for NtOpenJobObjectToken. Arguments: JobObject - Supplies a handle to a job object whose limit token token is to be opened. Token - If successful, receives a pointer to the process's token object. Return Value: STATUS_SUCCESS - Indicates the call completed successfully. STATUS_NO_TOKEN - indicates the job object does not have a token --*/ { NTSTATUS Status; PEJOB Job; KPROCESSOR_MODE PreviousMode; PAGED_CODE(); PreviousMode = KeGetPreviousMode(); Status = ObReferenceObjectByHandle (JobObject, JOB_OBJECT_QUERY, PsJobType, PreviousMode, &Job, NULL); if (NT_SUCCESS (Status)) { if (Job->Token != NULL) { ObReferenceObject (Job->Token); *Token = Job->Token; } else { Status = STATUS_NO_TOKEN; } } return Status; } NTSTATUS PsImpersonateClient( IN PETHREAD Thread, IN PACCESS_TOKEN Token, IN BOOLEAN CopyOnOpen, IN BOOLEAN EffectiveOnly, IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel ) /*++ Routine Description: This routine sets up the specified thread so that it is impersonating the specified client. This will result in the reference count of the token representing the client being incremented to reflect the new reference. If the thread is currently impersonating a client, that token will be dereferenced. Arguments: Thread - points to the thread which is going to impersonate a client. Token - Points to the token to be assigned as the impersonation token. This does NOT have to be a TokenImpersonation type token. This allows direct reference of client process's primary tokens. CopyOnOpen - If TRUE, indicates the token is considered to be private by the assigner and should be copied if opened. For example, a session layer may be using a token to represent a client's context. If the session is trying to synchronize the context of the client, then user mode code should not be given direct access to the session layer's token. Basically, session layers should always specify TRUE for this, while tokens assigned by the server itself (handle based) should specify FALSE. EffectiveOnly - Is a boolean value to be assigned as the Thread->ImpersonationInfo->EffectiveOnly field value for the impersonation. A value of FALSE indicates the server is allowed to enable currently disabled groups and privileges. ImpersonationLevel - Is the impersonation level that the server is allowed to access the token with. Return Value: STATUS_SUCCESS - Indicates the call completed successfully. --*/ { PPS_IMPERSONATION_INFORMATION NewClient, FreeClient; PACCESS_TOKEN OldToken; PACCESS_TOKEN NewerToken=NULL; PACCESS_TOKEN ProcessToken ; NTSTATUS Status; PPS_JOB_TOKEN_FILTER Filter; PEPROCESS Process; PETHREAD CurrentThread; PEJOB Job; PPS_IMPERSONATION_INFORMATION ImpersonationInfo; BOOLEAN DontReference = FALSE ; BOOLEAN NewTokenCreated = FALSE ; PAGED_CODE(); ASSERT (Thread->Tcb.Header.Type == ThreadObject); Process = THREAD_TO_PROCESS (Thread); if (!ARGUMENT_PRESENT(Token)) { OldToken = NULL; if (PS_IS_THREAD_IMPERSONATING (Thread)) { // // Lock the process security fields // CurrentThread = PsGetCurrentThread (); PspLockThreadSecurityExclusive (Thread, CurrentThread); if (PS_IS_THREAD_IMPERSONATING (Thread)) { // // Grab impersonation info block. // ImpersonationInfo = Thread->ImpersonationInfo; // // This is a request to revert to self. // Clean up any client information. // OldToken = ImpersonationInfo->Token; PS_CLEAR_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING); } // // Release the security fields // PspUnlockThreadSecurityExclusive (Thread, CurrentThread); PspWriteTebImpersonationInfo (Thread, CurrentThread); } } else { // // Allocate and set up the Client block. We do this without holding the Process // security lock so we reduce contention. Only one thread will manage to assign this. // The client block once created never goes away until the last dereference of // the process. We can touch this without locks // NewClient = Thread->ImpersonationInfo; if (NewClient == NULL) { NewClient = ExAllocatePoolWithTag (PagedPool, sizeof (PS_IMPERSONATION_INFORMATION), 'mIsP'|PROTECTED_POOL); if (NewClient == NULL) { return STATUS_NO_MEMORY; } FreeClient = InterlockedCompareExchangePointer (&Thread->ImpersonationInfo, NewClient, NULL); // // We got beaten by another thread. Free our context and use the new one // if (FreeClient != NULL) { ExFreePoolWithTag (NewClient, 'mIsP'|PROTECTED_POOL); NewClient = FreeClient; } } // // Check process token for rules on impersonation // ProcessToken = PsReferencePrimaryToken( Process ); if ( ProcessToken ) { Status = SeTokenCanImpersonate( ProcessToken, Token, ImpersonationLevel ); PsDereferencePrimaryTokenEx( Process, ProcessToken ); if ( !NT_SUCCESS( Status ) ) { Status = SeCopyClientToken( Token, SecurityIdentification, KernelMode, &NewerToken ); if ( !NT_SUCCESS(Status)) { return Status ; } // // We have a substitute token. Change Token to be this new // one, but do not add a reference later. Right now, there // is exactly one reference, so it will go away when the // thread stops impersonating. Note that we still need to // do the job filters below, hence the switch. // Token = NewerToken ; NewerToken = NULL ; DontReference = TRUE ; NewTokenCreated = TRUE ; ImpersonationLevel = SecurityIdentification ; } } // // Check if we're allowed to impersonate based on the job // restrictions: // Job = Process->Job; if (Job != NULL) { if ((Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_NO_ADMIN) && (SeTokenIsAdmin (Token))) { if ( NewTokenCreated ) { ObDereferenceObject( Token ); } return STATUS_ACCESS_DENIED; } else if ((Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_RESTRICTED_TOKEN) && (!SeTokenIsRestricted (Token))) { if ( NewTokenCreated ) { ObDereferenceObject( Token ); } return STATUS_ACCESS_DENIED; } else { Filter = Job->Filter; if (Filter != NULL) { // // Filter installed. Need to create a restricted token // dynamically. // Status = SeFastFilterToken (Token, KernelMode, 0, Filter->CapturedGroupCount, Filter->CapturedGroups, Filter->CapturedPrivilegeCount, Filter->CapturedPrivileges, Filter->CapturedSidCount, Filter->CapturedSids, Filter->CapturedSidsLength, &NewerToken); if (NT_SUCCESS (Status)) { // // If we created a filtered token then we don't need to add an extra token reference // as this is a new token with a single reference we just created. // if ( NewTokenCreated ) { ObDereferenceObject( Token ); } Token = NewerToken; } else { if ( NewTokenCreated ) { ObDereferenceObject( Token ); } return Status; } } else { if ( !DontReference) { ObReferenceObject (Token); } } } } else { if ( !DontReference) { ObReferenceObject (Token); } } // // Lock the process security fields // CurrentThread = PsGetCurrentThread (); PspLockThreadSecurityExclusive (Thread, CurrentThread); // // If we are already impersonating someone, // use the already allocated block. This avoids // an alloc and a free. // if (PS_IS_THREAD_IMPERSONATING (Thread)) { // // capture the old token pointer. // We'll dereference it after unlocking the security fields. // OldToken = NewClient->Token; } else { OldToken = NULL; PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING); } NewClient->ImpersonationLevel = ImpersonationLevel; NewClient->EffectiveOnly = EffectiveOnly; NewClient->CopyOnOpen = CopyOnOpen; NewClient->Token = Token; // // Release the security fields // PspUnlockThreadSecurityExclusive (Thread, CurrentThread); PspWriteTebImpersonationInfo (Thread, CurrentThread); } // // Free the old client token, if necessary. // if (OldToken != NULL) { PsDereferenceImpersonationToken (OldToken); } return STATUS_SUCCESS; } BOOLEAN PsDisableImpersonation( IN PETHREAD Thread, IN PSE_IMPERSONATION_STATE ImpersonationState ) /*++ Routine Description: This routine temporarily disables the impersonation of a thread. The impersonation state is saved for quick replacement later. The impersonation token is left referenced and a pointer to it is held in the IMPERSONATION_STATE data structure. PsRestoreImpersonation() must be used after this routine is called. Arguments: Thread - points to the thread whose impersonation (if any) is to be temporarily disabled. ImpersonationState - receives the current impersonation information, including a pointer to the impersonation token. Return Value: TRUE - Indicates the impersonation state has been saved and the impersonation has been temporarily disabled. FALSE - Indicates the specified thread was not impersonating a client. No action has been taken. --*/ { PPS_IMPERSONATION_INFORMATION OldClient; PETHREAD CurrentThread; PAGED_CODE(); ASSERT (Thread->Tcb.Header.Type == ThreadObject); // // Capture the impersonation information (if there is any). // The vast majority of cases this function is called we are not impersonating. Skip acquiring // the lock in this case. // OldClient = NULL; if (PS_IS_THREAD_IMPERSONATING (Thread)) { // // Lock the process security fields // CurrentThread = PsGetCurrentThread (); PspLockThreadSecurityExclusive (Thread, CurrentThread); // // Test and clear the impersonation bit. If we are still impersonating then capture the info. // if (PS_TEST_CLEAR_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING)& PS_CROSS_THREAD_FLAGS_IMPERSONATING) { OldClient = Thread->ImpersonationInfo; ImpersonationState->Level = OldClient->ImpersonationLevel; ImpersonationState->EffectiveOnly = OldClient->EffectiveOnly; ImpersonationState->CopyOnOpen = OldClient->CopyOnOpen; ImpersonationState->Token = OldClient->Token; } // // Release the security fields // PspUnlockThreadSecurityExclusive (Thread, CurrentThread); } if (OldClient != NULL) { return TRUE; } else { // // Not impersonating. Just make up some values. // The NULL for the token indicates we aren't impersonating. // ImpersonationState->Level = SecurityAnonymous; ImpersonationState->EffectiveOnly = FALSE; ImpersonationState->CopyOnOpen = FALSE; ImpersonationState->Token = NULL; return FALSE; } } VOID PsRestoreImpersonation( IN PETHREAD Thread, IN PSE_IMPERSONATION_STATE ImpersonationState ) /*++ Routine Description: This routine restores an impersonation that has been temporarily disabled using PsDisableImpersonation(). Notice that if this routine finds the thread is already impersonating (again), then restoring the temporarily disabled impersonation will cause the current impersonation to be abandoned. Arguments: Thread - points to the thread whose impersonation is to be restored. ImpersontionState - receives the current impersontion information, including a pointer ot the impersonation token. Return Value: TRUE - Indicates the impersonation state has been saved and the impersonation has been temporarily disabled. FALSE - Indicates the specified thread was not impersonating a client. No action has been taken. --*/ { PETHREAD CurrentThread; PACCESS_TOKEN OldToken; PPS_IMPERSONATION_INFORMATION ImpInfo; PAGED_CODE(); ASSERT (Thread->Tcb.Header.Type == ThreadObject); OldToken = NULL; // // Lock the process security fields // CurrentThread = PsGetCurrentThread (); PspLockThreadSecurityExclusive (Thread, CurrentThread); ImpInfo = Thread->ImpersonationInfo; // // If the thread is currently impersonating then we must revert this // if (PS_IS_THREAD_IMPERSONATING (Thread)) { OldToken = ImpInfo->Token; } // // Restore the previous impersonation token if there was one // if (ImpersonationState->Token) { ImpInfo->ImpersonationLevel = ImpersonationState->Level; ImpInfo->EffectiveOnly = ImpersonationState->EffectiveOnly; ImpInfo->CopyOnOpen = ImpersonationState->CopyOnOpen; ImpInfo->Token = ImpersonationState->Token; PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING); } else { PS_CLEAR_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING); } // // Release the security fields // PspUnlockThreadSecurityExclusive (Thread, CurrentThread); if (OldToken != NULL) { ObDereferenceObject (OldToken); } return; } VOID PsRevertToSelf( ) /*++ Routine Description: This routine causes the calling thread to discontinue impersonating a client. If the thread is not currently impersonating a client, no action is taken. Arguments: None. Return Value: None. --*/ { PETHREAD Thread; PEPROCESS Process; PACCESS_TOKEN OldToken; PAGED_CODE(); Thread = PsGetCurrentThread (); Process = THREAD_TO_PROCESS (Thread); // // Lock the process security fields // PspLockThreadSecurityExclusive (Thread, Thread); // // See if the thread is impersonating a client // and dereference that token if so. // if (PS_IS_THREAD_IMPERSONATING (Thread)) { PS_CLEAR_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING); OldToken = Thread->ImpersonationInfo->Token; } else { OldToken = NULL; } // // Release the security fields // PspUnlockThreadSecurityExclusive (Thread, Thread); // // Free the old client info... // if (OldToken != NULL) { ObDereferenceObject (OldToken); PspWriteTebImpersonationInfo (Thread, Thread); } return; } VOID PsRevertThreadToSelf ( IN PETHREAD Thread ) /*++ Routine Description: This routine causes the specified thread to discontinue impersonating a client. If the thread is not currently impersonating a client, no action is taken. Arguments: Thread - Thread to remove impersonation from Return Value: None. --*/ { PETHREAD CurrentThread; PACCESS_TOKEN OldToken; PPS_IMPERSONATION_INFORMATION ImpersonationInfo; PAGED_CODE(); ASSERT (Thread->Tcb.Header.Type == ThreadObject); if (PS_IS_THREAD_IMPERSONATING (Thread)) { CurrentThread = PsGetCurrentThread (); // // Lock the process security fields // PspLockThreadSecurityExclusive (Thread, CurrentThread); // // See if the thread is impersonating a client // and dereference that token if so. // if (PS_IS_THREAD_IMPERSONATING (Thread)) { // // Grab impersonation info block. // ImpersonationInfo = Thread->ImpersonationInfo; PS_CLEAR_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING); OldToken = ImpersonationInfo->Token; } else { OldToken = NULL; } // // Release the security fields // PspUnlockThreadSecurityExclusive (Thread, CurrentThread); // // Free the old client info... // if (OldToken != NULL) { ObDereferenceObject (OldToken); PspWriteTebImpersonationInfo (Thread, CurrentThread); } } return; } NTSTATUS PspInitializeProcessSecurity( IN PEPROCESS Parent OPTIONAL, IN PEPROCESS Child ) /*++ Routine Description: This function initializes a new process's security fields, including the assignment of a new primary token. The child process is assumed to not yet have been inserted into an object table. NOTE: IT IS EXPECTED THAT THIS SERVICE WILL BE CALLED WITH A NULL PARENT PROCESS POINTER EXACTLY ONCE - FOR THE INITIAL SYSTEM PROCESS. Arguments: Parent - An optional pointer to the process being used as the parent of the new process. If this value is NULL, then the process is assumed to be the initial system process, and the boot token is assigned rather than a duplicate of the parent process's primary token. Child - Supplies the address of the process being initialized. This process does not yet require security field contention protection. In particular, the security fields may be accessed without first acquiring the process security fields lock. Return Value: --*/ { NTSTATUS Status; PACCESS_TOKEN ParentToken, NewToken; PAGED_CODE(); // // Assign the primary token // if (ARGUMENT_PRESENT (Parent)) { // // create the primary token // This is a duplicate of the parent's token. // ParentToken = PsReferencePrimaryToken (Parent); Status = SeSubProcessToken (ParentToken, &NewToken, TRUE, MmGetSessionId (Child)); PsDereferencePrimaryTokenEx (Parent, ParentToken); if (NT_SUCCESS(Status)) { ObInitializeFastReference (&Child->Token, NewToken); } } else { // // Reference and assign the boot token // // The use of a single boot access token assumes there is // exactly one parentless process in the system - the initial // process. If this ever changes, this code will need to change // to match the new condition (so that a token doesn't end up // being shared by multiple processes. // ObInitializeFastReference (&Child->Token, NULL); SeAssignPrimaryToken (Child, PspBootAccessToken); Status = STATUS_SUCCESS; } return Status; } VOID PspDeleteProcessSecurity( IN PEPROCESS Process ) /*++ Routine Description: This function cleans up a process's security fields as part of process deletion. It is assumed no other references to the process can occur during or after a call to this routine. This enables us to reference the process security fields without acquiring the lock protecting those fields. NOTE: It may be desirable to add auditing capability to this routine at some point. Arguments: Process - A pointer to the process being deleted. Return Value: None. --*/ { PAGED_CODE(); // // If we are deleting a process that didn't successfully complete // process initialization, then there may be no token associated // with it yet. // if (!ExFastRefObjectNull (Process->Token)) { SeDeassignPrimaryToken (Process); } return; } NTSTATUS PspAssignPrimaryToken( IN PEPROCESS Process, IN HANDLE Token OPTIONAL, IN PACCESS_TOKEN TokenPointer OPTIONAL ) /*++ Routine Description: This function performs the security portions of primary token assignment. It is expected that the proper access to the process and thread objects, as well as necessary privilege, has already been established. A primary token can only be replaced if the process has no threads, or has one thread. This is because the thread objects point to the primary token and must have those pointers updated when the primary token is changed. This is only expected to be necessary at logon time, when the process is in its infancy and either has zero threads or maybe one inactive thread. If the assignment is successful, the old token is dereferenced and the new one is referenced. Arguments: Process - A pointer to the process whose primary token is being replaced. Token - The handle value of the token to be assigned as the primary token. Return Value: STATUS_SUCCESS - Indicates the primary token has been successfully replaced. STATUS_BAD_TOKEN_TYPE - Indicates the token is not of type TokenPrimary. STATUS_TOKEN_IN_USE - Indicates the token is already in use by another process. Other status may be returned when attempting to reference the token object. --*/ { NTSTATUS Status; PACCESS_TOKEN NewToken, OldToken; KPROCESSOR_MODE PreviousMode; PETHREAD CurrentThread; PAGED_CODE(); CurrentThread = PsGetCurrentThread (); if (TokenPointer == NULL) { PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb); // // Reference the specified token, and make sure it can be assigned // as a primary token. // Status = ObReferenceObjectByHandle (Token, TOKEN_ASSIGN_PRIMARY, SeTokenObjectType, PreviousMode, &NewToken, NULL); if (!NT_SUCCESS (Status)) { return Status; } } else { NewToken = TokenPointer; } // // This routine makes sure the NewToken is suitable for assignment // as a primary token. // Status = SeExchangePrimaryToken (Process, NewToken, &OldToken); // // Acquire and release the process security lock to force any slow // referencers out of the slow path. // PspLockProcessSecurityExclusive (Process, CurrentThread); PspUnlockProcessSecurityExclusive (Process, CurrentThread); // // Free the old token (we don't need it). // This can't be done while the security fields are locked. // if (NT_SUCCESS (Status)) { ObDereferenceObject (OldToken); } // // Undo the handle reference // if (TokenPointer == NULL) { ObDereferenceObject (NewToken); } return Status; } VOID PspInitializeThreadSecurity( IN PEPROCESS Process, IN PETHREAD Thread ) /*++ Routine Description: This function initializes a new thread's security fields. Arguments: Process - Points to the process the thread belongs to. Thread - Points to the thread object being initialized. Return Value: None. --*/ { PAGED_CODE(); UNREFERENCED_PARAMETER (Process); // // Initially not impersonating anyone. This is not currently called as we zero out the entire thread at create time anyway // Thread->ImpersonationInfo = NULL; PS_CLEAR_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING); return; } VOID PspDeleteThreadSecurity( IN PETHREAD Thread ) /*++ Routine Description: This function cleans up a thread's security fields as part of thread deletion. It is assumed no other references to the thread can occur during or after a call to this routine, so no locking is necessary to access the thread security fields. Arguments: Thread - A pointer to the thread being deleted. Return Value: None. --*/ { PPS_IMPERSONATION_INFORMATION ImpersonationInfo; PAGED_CODE(); ImpersonationInfo = Thread->ImpersonationInfo; // // clean-up client information, if there is any. // if (PS_IS_THREAD_IMPERSONATING (Thread)) { ObDereferenceObject (ImpersonationInfo->Token); } if (ImpersonationInfo != NULL) { ExFreePoolWithTag (ImpersonationInfo, 'mIsP'|PROTECTED_POOL); PS_CLEAR_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_IMPERSONATING); Thread->ImpersonationInfo = NULL; } return; } NTSTATUS PspWriteTebImpersonationInfo ( IN PETHREAD Thread, IN PETHREAD CurrentThread ) /*++ Routine Description: This function updates the thread TEB fields to reflect the impersonation status of the thread. Arguments: Thread - A pointer to the thread whose impersonation token has been changed CurrentThread - The current thread Return Value: NTSTATUS - Status of operation --*/ { PTEB Teb; BOOLEAN AttachedToProcess = FALSE; PEPROCESS ThreadProcess; BOOLEAN Impersonating; KAPC_STATE ApcState; PAGED_CODE(); ASSERT (CurrentThread == PsGetCurrentThread ()); ThreadProcess = THREAD_TO_PROCESS (Thread); Teb = Thread->Tcb.Teb; if (Teb != NULL) { if (PsGetCurrentProcessByThread (CurrentThread) != ThreadProcess) { KeStackAttachProcess (&ThreadProcess->Pcb, &ApcState); AttachedToProcess = TRUE; } // // We are doing a cross thread TEB reference here. Protect against the TEB being freed and used by // somebody else. // if (Thread == CurrentThread || ExAcquireRundownProtection (&Thread->RundownProtect)) { while (1) { Impersonating = (BOOLEAN) PS_IS_THREAD_IMPERSONATING (Thread); // // The TEB may still raise an exception in low memory conditions so we need try/except here // try { if (Impersonating) { Teb->ImpersonationLocale = (LCID)-1; Teb->IsImpersonating = 1; } else { Teb->ImpersonationLocale = (LCID) 0; Teb->IsImpersonating = 0; } } except (EXCEPTION_EXECUTE_HANDLER) { } KeMemoryBarrier (); if (Impersonating == (BOOLEAN) PS_IS_THREAD_IMPERSONATING (Thread)) { break; } } if (Thread != CurrentThread) { ExReleaseRundownProtection (&Thread->RundownProtect); } } if (AttachedToProcess) { KeUnstackDetachProcess (&ApcState); } } return STATUS_SUCCESS; } NTSTATUS PsAssignImpersonationToken( IN PETHREAD Thread, IN HANDLE Token ) /*++ Routine Description: This function performs the security portions of establishing an impersonation token. This routine is expected to be used only in the case where the subject has asked for impersonation explicitly providing an impersonation token. Other services are provided for use by communication session layers that need to establish an impersonation on a server's behalf. It is expected that the proper access to the thread object has already been established. The following rules apply: 1) The caller must have TOKEN_IMPERSONATE access to the token for any action to be taken. 2) If the token may NOT be used for impersonation (e.g., not an impersonation token) no action is taken. 3) Otherwise, any existing impersonation token is dereferenced and the new token is established as the impersonation token. Arguments: Thread - A pointer to the thread whose impersonation token is being set. Token - The handle value of the token to be assigned as the impersonation token. If this value is NULL, then current impersonation (if any) is terminated and no new impersonation is established. Return Value: STATUS_SUCCESS - Indicates the primary token has been successfully replaced. STATUS_BAD_TOKEN_TYPE - Indicates the token is not of type TokenImpersonation. Other status may be returned when attempting to reference the token object. --*/ { NTSTATUS Status; PACCESS_TOKEN NewToken; KPROCESSOR_MODE PreviousMode; SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; PETHREAD CurrentThread; PAGED_CODE(); CurrentThread = PsGetCurrentThread (); if (!ARGUMENT_PRESENT (Token)) { PsRevertThreadToSelf (Thread); Status = STATUS_SUCCESS; } else { PreviousMode = KeGetPreviousModeByThread (&CurrentThread->Tcb); // // Reference the specified token for TOKEN_IMPERSONATE access // Status = ObReferenceObjectByHandle (Token, TOKEN_IMPERSONATE, SeTokenObjectType, PreviousMode, &NewToken, NULL); if (!NT_SUCCESS (Status)) { return Status; } // // Make sure the token is an impersonation token. // if (SeTokenType (NewToken) != TokenImpersonation) { ObDereferenceObject (NewToken); return STATUS_BAD_TOKEN_TYPE; } ImpersonationLevel = SeTokenImpersonationLevel (NewToken); // // The rest can be done by PsImpersonateClient. // // PsImpersonateClient will reference the passed token // on success. // Status = PsImpersonateClient (Thread, NewToken, FALSE, // CopyOnOpen FALSE, // EffectiveOnly ImpersonationLevel); // // Dereference the passed token. // // ObDereferenceObject (NewToken); } return Status; } #undef PsDereferencePrimaryToken #pragma alloc_text(PAGE, PsDereferencePrimaryToken) VOID PsDereferencePrimaryToken( IN PACCESS_TOKEN PrimaryToken ) /*++ Routine Description: Returns the reference obtained via PsReferencePrimaryToken Arguments: Returns the reference Return Value: None. --*/ { PAGED_CODE(); ObDereferenceObject (PrimaryToken); } #undef PsDereferenceImpersonationToken #pragma alloc_text(PAGE, PsDereferenceImpersonationToken) VOID PsDereferenceImpersonationToken( IN PACCESS_TOKEN ImpersonationToken ) /*++ Routine Description: Returns the reference obtained via PsReferenceImpersonationToken Arguments: Returns the reference Return Value: None. --*/ { PAGED_CODE(); if (ImpersonationToken != NULL) { ObDereferenceObject (ImpersonationToken); } }