mirror of
https://github.com/reactos/reactos.git
synced 2024-12-29 10:35:28 +00:00
8e2fe925f2
As it currently stands the PsImpersonateClient routine does the following approach. If impersonation couldn't be granted to a client the routine will make a copy of the client's access token. As it makes a copy of the said token PsImpersonateClient will reference the copied token after impersonation info have been filled out. In the same code path we are assigning the desired level for impersonation to thread impersonation info. This is wrong for two reasons: - On a copy situation the SeCopyClientToken routine holds a reference as the object has been created. Referencing it at the bottom of the PsImpersonateClient routine will make it that the token is referenced twice and whenever a server stops impersonation the token still has an extra reference count which keeps the token still alive in object database and memory space. - If client impersonation is not possible the thread impersonation info should have been assigned SecurityIdentification level to further indicate that the actual impersonation of the thread is not currently in force but instead we are assigning the impersonation level that is supplied by the caller. For instance if the requested level is SecurityDelegation but impersonation is not possible the level will be assigned that of SecurityDelegation yet the token has an impersonation level of SecurityIdentification. This could lead to erratic behaviors as well as potential impersonation escalation. Fix the aforementioned issues by avoiding a double reference and properly assign the impersonation level to SecurityIdentification if the server is not able to impersonate the target client.
1118 lines
34 KiB
C
1118 lines
34 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/ps/security.c
|
|
* PURPOSE: Process Manager: Process/Thread Security
|
|
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
|
|
* Eric Kohl
|
|
* Thomas Weidenmueller (w3seek@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
PTOKEN PspBootAccessToken;
|
|
|
|
VOID
|
|
NTAPI
|
|
SeAssignPrimaryToken(
|
|
IN PEPROCESS Process,
|
|
IN PTOKEN Token
|
|
);
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
VOID
|
|
NTAPI
|
|
PspDeleteProcessSecurity(IN PEPROCESS Process)
|
|
{
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Process: %p\n", Process);
|
|
|
|
/* Check if we have a token */
|
|
if (Process->Token.Object)
|
|
{
|
|
/* Deassign it */
|
|
SeDeassignPrimaryToken(Process);
|
|
Process->Token.Object = NULL;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PspDeleteThreadSecurity(IN PETHREAD Thread)
|
|
{
|
|
PPS_IMPERSONATION_INFORMATION ImpersonationInfo = Thread->ImpersonationInfo;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Thread: %p\n", Thread);
|
|
|
|
/* Check if we have active impersonation info */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Dereference its token */
|
|
ObDereferenceObject(ImpersonationInfo->Token);
|
|
}
|
|
|
|
/* Check if we have impersonation info */
|
|
if (ImpersonationInfo)
|
|
{
|
|
/* Free it */
|
|
ExFreePool(ImpersonationInfo);
|
|
PspClearCrossThreadFlag(Thread, CT_ACTIVE_IMPERSONATION_INFO_BIT);
|
|
Thread->ImpersonationInfo = NULL;
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
PspInitializeProcessSecurity(IN PEPROCESS Process,
|
|
IN PEPROCESS Parent OPTIONAL)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PTOKEN NewToken, ParentToken;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Process: %p\n", Process);
|
|
|
|
/* If we have a parent, then duplicate the Token */
|
|
if (Parent)
|
|
{
|
|
/* Get the Parent Token */
|
|
ParentToken = PsReferencePrimaryToken(Parent);
|
|
|
|
/* Duplicate it */
|
|
Status = SeSubProcessToken(ParentToken,
|
|
&NewToken,
|
|
TRUE,
|
|
MmGetSessionId(Process));
|
|
|
|
/* Dereference the Parent */
|
|
ObFastDereferenceObject(&Parent->Token, ParentToken);
|
|
|
|
/* Set the new Token */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Initailize the fast reference */
|
|
ObInitializeFastReference(&Process->Token, NewToken);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* No parent, assign the Boot Token */
|
|
ObInitializeFastReference(&Process->Token, NULL);
|
|
SeAssignPrimaryToken(Process, PspBootAccessToken);
|
|
}
|
|
|
|
/* Return to caller */
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
PspWriteTebImpersonationInfo(IN PETHREAD Thread,
|
|
IN PETHREAD CurrentThread)
|
|
{
|
|
PEPROCESS Process;
|
|
PTEB Teb;
|
|
BOOLEAN Attached = FALSE;
|
|
BOOLEAN IsImpersonating;
|
|
KAPC_STATE ApcState;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Thread: %p\n", Thread);
|
|
|
|
/* Sanity check */
|
|
ASSERT(CurrentThread == PsGetCurrentThread());
|
|
|
|
/* Get process and TEB */
|
|
Process = Thread->ThreadsProcess;
|
|
Teb = Thread->Tcb.Teb;
|
|
if (Teb)
|
|
{
|
|
/* Check if we're not in the right process */
|
|
if (Thread->Tcb.ApcState.Process != &Process->Pcb)
|
|
{
|
|
/* Attach to the process */
|
|
KeStackAttachProcess(&Process->Pcb, &ApcState);
|
|
Attached = TRUE;
|
|
}
|
|
|
|
/* Check if we're in a different thread or acquire rundown */
|
|
if ((Thread == CurrentThread) ||
|
|
(ExAcquireRundownProtection(&Thread->RundownProtect)))
|
|
{
|
|
/* Check if the thread is impersonating */
|
|
IsImpersonating = (BOOLEAN)Thread->ActiveImpersonationInfo;
|
|
if (IsImpersonating)
|
|
{
|
|
/* Set TEB data */
|
|
Teb->ImpersonationLocale = -1;
|
|
Teb->IsImpersonating = 1;
|
|
}
|
|
else
|
|
{
|
|
/* Set TEB data */
|
|
Teb->ImpersonationLocale = 0;
|
|
Teb->IsImpersonating = 0;
|
|
}
|
|
}
|
|
|
|
/* Check if we're in a different thread */
|
|
if (Thread != CurrentThread)
|
|
{
|
|
/* Release protection */
|
|
ExReleaseRundownProtection(&Thread->RundownProtect);
|
|
}
|
|
|
|
/* Detach */
|
|
if (Attached) KeUnstackDetachProcess(&ApcState);
|
|
}
|
|
|
|
/* Return to caller */
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
PspAssignPrimaryToken(IN PEPROCESS Process,
|
|
IN HANDLE Token,
|
|
IN PACCESS_TOKEN AccessToken OPTIONAL)
|
|
{
|
|
PACCESS_TOKEN NewToken = AccessToken, OldToken;
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Process: %p Token: %p\n", Process, Token);
|
|
|
|
/* Check if we don't have a pointer */
|
|
if (!AccessToken)
|
|
{
|
|
/* Reference it from the handle */
|
|
Status = ObReferenceObjectByHandle(Token,
|
|
TOKEN_ASSIGN_PRIMARY,
|
|
SeTokenObjectType,
|
|
ExGetPreviousMode(),
|
|
&NewToken,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
}
|
|
|
|
/* Exchange tokens */
|
|
Status = SeExchangePrimaryToken(Process, NewToken, &OldToken);
|
|
|
|
/* Acquire and release the lock */
|
|
PspLockProcessSecurityExclusive(Process);
|
|
PspUnlockProcessSecurityExclusive(Process);
|
|
|
|
/* Dereference Tokens and Return */
|
|
if (NT_SUCCESS(Status)) ObDereferenceObject(OldToken);
|
|
if (!AccessToken) ObDereferenceObject(NewToken);
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
PspSetPrimaryToken(IN PEPROCESS Process,
|
|
IN HANDLE TokenHandle OPTIONAL,
|
|
IN PACCESS_TOKEN Token OPTIONAL)
|
|
{
|
|
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
|
|
BOOLEAN IsChildOrSibling;
|
|
PACCESS_TOKEN NewToken = Token;
|
|
NTSTATUS Status, AccessStatus;
|
|
BOOLEAN Result, SdAllocated;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor = NULL;
|
|
SECURITY_SUBJECT_CONTEXT SubjectContext;
|
|
|
|
PSTRACE(PS_SECURITY_DEBUG, "Process: %p Token: %p\n", Process, Token);
|
|
|
|
/* Reference the token by handle if we don't already have a token object */
|
|
if (!Token)
|
|
{
|
|
Status = ObReferenceObjectByHandle(TokenHandle,
|
|
TOKEN_ASSIGN_PRIMARY,
|
|
SeTokenObjectType,
|
|
PreviousMode,
|
|
(PVOID*)&NewToken,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
}
|
|
|
|
/*
|
|
* Check whether this token is a child or sibling of the current process token.
|
|
* NOTE: On Windows Vista+ both of these checks (together with extra steps)
|
|
* are now performed by a new SeIsTokenAssignableToProcess() helper.
|
|
*/
|
|
Status = SeIsTokenChild(NewToken, &IsChildOrSibling);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Failed, dereference */
|
|
if (!Token) ObDereferenceObject(NewToken);
|
|
return Status;
|
|
}
|
|
if (!IsChildOrSibling)
|
|
{
|
|
Status = SeIsTokenSibling(NewToken, &IsChildOrSibling);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Failed, dereference */
|
|
if (!Token) ObDereferenceObject(NewToken);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
/* Check if this was an independent token */
|
|
if (!IsChildOrSibling)
|
|
{
|
|
/* Make sure we have the privilege to assign a new one */
|
|
if (!SeSinglePrivilegeCheck(SeAssignPrimaryTokenPrivilege,
|
|
PreviousMode))
|
|
{
|
|
/* Failed, dereference */
|
|
if (!Token) ObDereferenceObject(NewToken);
|
|
return STATUS_PRIVILEGE_NOT_HELD;
|
|
}
|
|
}
|
|
|
|
/* Assign the token */
|
|
Status = PspAssignPrimaryToken(Process, NULL, NewToken);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/*
|
|
* We need to completely reverify if the process still has access to
|
|
* itself under this new token.
|
|
*/
|
|
Status = ObGetObjectSecurity(Process,
|
|
&SecurityDescriptor,
|
|
&SdAllocated);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Setup the security context */
|
|
SubjectContext.ProcessAuditId = Process;
|
|
SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process);
|
|
SubjectContext.ClientToken = NULL;
|
|
|
|
/* Do the access check */
|
|
Result = SeAccessCheck(SecurityDescriptor,
|
|
&SubjectContext,
|
|
FALSE,
|
|
MAXIMUM_ALLOWED,
|
|
0,
|
|
NULL,
|
|
&PsProcessType->TypeInfo.GenericMapping,
|
|
PreviousMode,
|
|
&Process->GrantedAccess,
|
|
&AccessStatus);
|
|
|
|
/* Dereference the token and let go the SD */
|
|
ObFastDereferenceObject(&Process->Token,
|
|
SubjectContext.PrimaryToken);
|
|
ObReleaseObjectSecurity(SecurityDescriptor, SdAllocated);
|
|
|
|
/* Remove access if it failed */
|
|
if (!Result) Process->GrantedAccess = 0;
|
|
|
|
/* Setup granted access */
|
|
Process->GrantedAccess |= (PROCESS_VM_OPERATION |
|
|
PROCESS_VM_READ |
|
|
PROCESS_VM_WRITE |
|
|
PROCESS_QUERY_INFORMATION |
|
|
PROCESS_TERMINATE |
|
|
PROCESS_CREATE_THREAD |
|
|
PROCESS_DUP_HANDLE |
|
|
PROCESS_CREATE_PROCESS |
|
|
PROCESS_SET_INFORMATION |
|
|
STANDARD_RIGHTS_ALL |
|
|
PROCESS_SET_QUOTA);
|
|
}
|
|
|
|
/*
|
|
* In case LUID device maps are enable, we may not be using
|
|
* system device map for this process, but a logon LUID based
|
|
* device map. Because we change primary token, this usage is
|
|
* no longer valid, so dereference the process device map
|
|
*/
|
|
if (ObIsLUIDDeviceMapsEnabled()) ObDereferenceDeviceMap(Process);
|
|
}
|
|
|
|
/* Dereference the token */
|
|
if (!Token) ObDereferenceObject(NewToken);
|
|
return Status;
|
|
}
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtOpenProcessToken(IN HANDLE ProcessHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
OUT PHANDLE TokenHandle)
|
|
{
|
|
/* Call the newer API */
|
|
return NtOpenProcessTokenEx(ProcessHandle,
|
|
DesiredAccess,
|
|
0,
|
|
TokenHandle);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtOpenProcessTokenEx(IN HANDLE ProcessHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN ULONG HandleAttributes,
|
|
OUT PHANDLE TokenHandle)
|
|
{
|
|
PACCESS_TOKEN Token;
|
|
HANDLE hToken;
|
|
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG,
|
|
"Process: %p DesiredAccess: %lx\n", ProcessHandle, DesiredAccess);
|
|
|
|
/* Check if caller was user-mode */
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
/* Enter SEH for probing */
|
|
_SEH2_TRY
|
|
{
|
|
/* Probe the token handle */
|
|
ProbeForWriteHandle(TokenHandle);
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
|
|
/* Validate object attributes */
|
|
HandleAttributes = ObpValidateAttributes(HandleAttributes, PreviousMode);
|
|
|
|
/* Open the process token */
|
|
Status = PsOpenTokenOfProcess(ProcessHandle, &Token);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Reference it by handle and dereference the pointer */
|
|
Status = ObOpenObjectByPointer(Token,
|
|
HandleAttributes,
|
|
NULL,
|
|
DesiredAccess,
|
|
SeTokenObjectType,
|
|
PreviousMode,
|
|
&hToken);
|
|
ObDereferenceObject(Token);
|
|
|
|
/* Make sure we got a handle */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Enter SEH for write */
|
|
_SEH2_TRY
|
|
{
|
|
/* Return the handle */
|
|
*TokenHandle = hToken;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Get exception code */
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
}
|
|
|
|
/* Return status */
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PACCESS_TOKEN
|
|
NTAPI
|
|
PsReferencePrimaryToken(PEPROCESS Process)
|
|
{
|
|
PACCESS_TOKEN Token;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Process: %p\n", Process);
|
|
|
|
/* Fast Reference the Token */
|
|
Token = ObFastReferenceObject(&Process->Token);
|
|
|
|
/* Check if we got the Token or if we got locked */
|
|
if (!Token)
|
|
{
|
|
/* Lock the Process */
|
|
PspLockProcessSecurityShared(Process);
|
|
|
|
/* Do a Locked Fast Reference */
|
|
Token = ObFastReferenceObjectLocked(&Process->Token);
|
|
|
|
/* Unlock the Process */
|
|
PspUnlockProcessSecurityShared(Process);
|
|
}
|
|
|
|
/* Return the Token */
|
|
return Token;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
PsOpenTokenOfProcess(IN HANDLE ProcessHandle,
|
|
OUT PACCESS_TOKEN* Token)
|
|
{
|
|
PEPROCESS Process;
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Process: %p\n", ProcessHandle);
|
|
|
|
/* Get the Token */
|
|
Status = ObReferenceObjectByHandle(ProcessHandle,
|
|
PROCESS_QUERY_INFORMATION,
|
|
PsProcessType,
|
|
ExGetPreviousMode(),
|
|
(PVOID*)&Process,
|
|
NULL);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Reference the token and dereference the process */
|
|
*Token = PsReferencePrimaryToken(Process);
|
|
ObDereferenceObject(Process);
|
|
}
|
|
|
|
/* Return */
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
PsAssignImpersonationToken(IN PETHREAD Thread,
|
|
IN HANDLE TokenHandle)
|
|
{
|
|
PACCESS_TOKEN Token;
|
|
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Thread: %p Token: %p\n", Thread, TokenHandle);
|
|
|
|
/* Check if we were given a handle */
|
|
if (!TokenHandle)
|
|
{
|
|
/* Undo impersonation */
|
|
PsRevertThreadToSelf(Thread);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Get the token object */
|
|
Status = ObReferenceObjectByHandle(TokenHandle,
|
|
TOKEN_IMPERSONATE,
|
|
SeTokenObjectType,
|
|
KeGetPreviousMode(),
|
|
(PVOID*)&Token,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) return(Status);
|
|
|
|
/* Make sure it's an impersonation token */
|
|
if (SeTokenType(Token) != TokenImpersonation)
|
|
{
|
|
/* Fail */
|
|
ObDereferenceObject(Token);
|
|
return STATUS_BAD_TOKEN_TYPE;
|
|
}
|
|
|
|
/* Get the impersonation level */
|
|
ImpersonationLevel = SeTokenImpersonationLevel(Token);
|
|
|
|
/* Call the impersonation API */
|
|
Status = PsImpersonateClient(Thread,
|
|
Token,
|
|
FALSE,
|
|
FALSE,
|
|
ImpersonationLevel);
|
|
|
|
/* Dereference the token and return status */
|
|
ObDereferenceObject(Token);
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
PsRevertToSelf(VOID)
|
|
{
|
|
/* Call the per-thread API */
|
|
PAGED_CODE();
|
|
PsRevertThreadToSelf(PsGetCurrentThread());
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
PsRevertThreadToSelf(IN PETHREAD Thread)
|
|
{
|
|
PTOKEN Token = NULL;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Thread: %p\n", Thread);
|
|
|
|
/* Make sure we had impersonation information */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Lock the thread security */
|
|
PspLockThreadSecurityExclusive(Thread);
|
|
|
|
/* Make sure it's still active */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Disable impersonation */
|
|
PspClearCrossThreadFlag(Thread, CT_ACTIVE_IMPERSONATION_INFO_BIT);
|
|
|
|
/* Get the token */
|
|
Token = Thread->ImpersonationInfo->Token;
|
|
}
|
|
|
|
/* Release thread security */
|
|
PspUnlockThreadSecurityExclusive(Thread);
|
|
|
|
/* Check if we had a token */
|
|
if (Token)
|
|
{
|
|
/* Dereference the impersonation token */
|
|
ObDereferenceObject(Token);
|
|
|
|
/* Write impersonation info to the TEB */
|
|
PspWriteTebImpersonationInfo(Thread, PsGetCurrentThread());
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
PsImpersonateClient(IN PETHREAD Thread,
|
|
IN PACCESS_TOKEN Token,
|
|
IN BOOLEAN CopyOnOpen,
|
|
IN BOOLEAN EffectiveOnly,
|
|
IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
|
|
{
|
|
PPS_IMPERSONATION_INFORMATION Impersonation, OldData;
|
|
PTOKEN OldToken = NULL, ProcessToken = NULL;
|
|
BOOLEAN CopiedToken = FALSE;
|
|
PACCESS_TOKEN NewToken, ImpersonationToken;
|
|
PEJOB Job;
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Thread: %p, Token: %p\n", Thread, Token);
|
|
|
|
/* Check if we don't have a token */
|
|
if (!Token)
|
|
{
|
|
/* Make sure we're impersonating */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* We seem to be, lock the thread */
|
|
PspLockThreadSecurityExclusive(Thread);
|
|
|
|
/* Make sure we're still impersonating */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Disable impersonation */
|
|
PspClearCrossThreadFlag(Thread,
|
|
CT_ACTIVE_IMPERSONATION_INFO_BIT);
|
|
|
|
/* Get the token */
|
|
OldToken = Thread->ImpersonationInfo->Token;
|
|
}
|
|
|
|
/* Unlock the process and write TEB information */
|
|
PspUnlockThreadSecurityExclusive(Thread);
|
|
PspWriteTebImpersonationInfo(Thread, PsGetCurrentThread());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Check if we have impersonation info */
|
|
Impersonation = Thread->ImpersonationInfo;
|
|
if (!Impersonation)
|
|
{
|
|
/* We need to allocate a new one */
|
|
Impersonation = ExAllocatePoolWithTag(PagedPool,
|
|
sizeof(*Impersonation),
|
|
TAG_PS_IMPERSONATION);
|
|
if (!Impersonation) return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
/* Update the pointer */
|
|
OldData = InterlockedCompareExchangePointer((PVOID*)&Thread->
|
|
ImpersonationInfo,
|
|
Impersonation,
|
|
NULL);
|
|
if (OldData)
|
|
{
|
|
/* Someone beat us to it, free our copy */
|
|
ExFreePoolWithTag(Impersonation, TAG_PS_IMPERSONATION);
|
|
Impersonation = OldData;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Assign the token we get from the caller first. The reason
|
|
* we have to do that is because we're unsure if we can impersonate
|
|
* in the first place. In the scenario where we cannot then the
|
|
* last resort is to make a copy of the token and assign that newly
|
|
* token to the impersonation information.
|
|
*/
|
|
ImpersonationToken = Token;
|
|
|
|
/* Obtain a token from the process */
|
|
ProcessToken = PsReferencePrimaryToken(Thread->ThreadsProcess);
|
|
if (!ProcessToken)
|
|
{
|
|
/* We can't continue this way without having the process' token... */
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* Make sure we can impersonate */
|
|
if (!SeTokenCanImpersonate(ProcessToken,
|
|
Token,
|
|
ImpersonationLevel))
|
|
{
|
|
/* We can't, make a copy of the token instead */
|
|
Status = SeCopyClientToken(Token,
|
|
SecurityIdentification,
|
|
KernelMode,
|
|
&NewToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* We can't even make a copy of the token? Then bail out... */
|
|
ObFastDereferenceObject(&Thread->ThreadsProcess->Token, ProcessToken);
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* Since we cannot impersonate, assign the newly copied token.
|
|
* SeCopyClientToken already holds a reference to the copied token,
|
|
* let the code path below know that it must not reference it twice.
|
|
*/
|
|
CopiedToken = TRUE;
|
|
ImpersonationLevel = SecurityIdentification;
|
|
ImpersonationToken = NewToken;
|
|
}
|
|
|
|
/* We no longer need the process' token */
|
|
ObFastDereferenceObject(&Thread->ThreadsProcess->Token, ProcessToken);
|
|
|
|
/* Check if this is a job */
|
|
Job = Thread->ThreadsProcess->Job;
|
|
if (Job != NULL)
|
|
{
|
|
/* No admin allowed in this job */
|
|
if ((Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_NO_ADMIN) &&
|
|
SeTokenIsAdmin(ImpersonationToken))
|
|
{
|
|
if (CopiedToken)
|
|
{
|
|
ObDereferenceObject(ImpersonationToken);
|
|
}
|
|
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
/* No restricted tokens allowed in this job */
|
|
if ((Job->SecurityLimitFlags & JOB_OBJECT_SECURITY_RESTRICTED_TOKEN) &&
|
|
SeTokenIsRestricted(ImpersonationToken))
|
|
{
|
|
if (CopiedToken)
|
|
{
|
|
ObDereferenceObject(ImpersonationToken);
|
|
}
|
|
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
/* We don't support job filters yet */
|
|
if (Job->Filter != NULL)
|
|
{
|
|
ASSERT(Job->Filter == NULL);
|
|
}
|
|
}
|
|
|
|
/* Lock thread security */
|
|
PspLockThreadSecurityExclusive(Thread);
|
|
|
|
/* Check if we're impersonating */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Get the token */
|
|
OldToken = Impersonation->Token;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, enable impersonation */
|
|
PspSetCrossThreadFlag(Thread, CT_ACTIVE_IMPERSONATION_INFO_BIT);
|
|
}
|
|
|
|
/* Now fill it out */
|
|
Impersonation->ImpersonationLevel = ImpersonationLevel;
|
|
Impersonation->CopyOnOpen = CopyOnOpen;
|
|
Impersonation->EffectiveOnly = EffectiveOnly;
|
|
Impersonation->Token = ImpersonationToken;
|
|
|
|
/* Do not reference the token again if we copied it */
|
|
if (!CopiedToken)
|
|
{
|
|
ObReferenceObject(ImpersonationToken);
|
|
}
|
|
|
|
/* Unlock the thread */
|
|
PspUnlockThreadSecurityExclusive(Thread);
|
|
|
|
/* Write impersonation info to the TEB */
|
|
PspWriteTebImpersonationInfo(Thread, PsGetCurrentThread());
|
|
}
|
|
|
|
/* Dereference the token and return success */
|
|
if (OldToken) PsDereferenceImpersonationToken(OldToken);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PACCESS_TOKEN
|
|
NTAPI
|
|
PsReferenceEffectiveToken(IN PETHREAD Thread,
|
|
OUT IN PTOKEN_TYPE TokenType,
|
|
OUT PBOOLEAN EffectiveOnly,
|
|
OUT PSECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
|
|
{
|
|
PEPROCESS Process;
|
|
PACCESS_TOKEN Token = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
PSTRACE(PS_SECURITY_DEBUG,
|
|
"Thread: %p, TokenType: %p\n", Thread, TokenType);
|
|
|
|
/* Check if we don't have impersonation info */
|
|
Process = Thread->ThreadsProcess;
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Lock the Process */
|
|
PspLockProcessSecurityShared(Process);
|
|
|
|
/* Make sure impersonation is still active */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Get the token */
|
|
Token = Thread->ImpersonationInfo->Token;
|
|
ObReferenceObject(Token);
|
|
|
|
/* Return data to caller */
|
|
*TokenType = TokenImpersonation;
|
|
*EffectiveOnly = Thread->ImpersonationInfo->EffectiveOnly;
|
|
*ImpersonationLevel = Thread->ImpersonationInfo->ImpersonationLevel;
|
|
|
|
/* Unlock the Process */
|
|
PspUnlockProcessSecurityShared(Process);
|
|
return Token;
|
|
}
|
|
|
|
/* Unlock the Process */
|
|
PspUnlockProcessSecurityShared(Process);
|
|
}
|
|
|
|
/* Fast Reference the Token */
|
|
Token = ObFastReferenceObject(&Process->Token);
|
|
|
|
/* Check if we got the Token or if we got locked */
|
|
if (!Token)
|
|
{
|
|
/* Lock the Process */
|
|
PspLockProcessSecurityShared(Process);
|
|
|
|
/* Do a Locked Fast Reference */
|
|
Token = ObFastReferenceObjectLocked(&Process->Token);
|
|
|
|
/* Unlock the Process */
|
|
PspUnlockProcessSecurityShared(Process);
|
|
}
|
|
|
|
/* Return the token */
|
|
*TokenType = TokenPrimary;
|
|
*EffectiveOnly = FALSE;
|
|
// NOTE: ImpersonationLevel is left untouched on purpose!
|
|
return Token;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PACCESS_TOKEN
|
|
NTAPI
|
|
PsReferenceImpersonationToken(IN PETHREAD Thread,
|
|
OUT PBOOLEAN CopyOnOpen,
|
|
OUT PBOOLEAN EffectiveOnly,
|
|
OUT PSECURITY_IMPERSONATION_LEVEL ImpersonationLevel)
|
|
{
|
|
PTOKEN Token = NULL;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG, "Thread: %p\n", Thread);
|
|
|
|
/* If we don't have impersonation info, just quit */
|
|
if (!Thread->ActiveImpersonationInfo) return NULL;
|
|
|
|
/* Lock the thread */
|
|
PspLockThreadSecurityShared(Thread);
|
|
|
|
/* Make sure we still have active impersonation */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Return data from caller */
|
|
ObReferenceObject(Thread->ImpersonationInfo->Token);
|
|
*ImpersonationLevel = Thread->ImpersonationInfo->ImpersonationLevel;
|
|
*CopyOnOpen = Thread->ImpersonationInfo->CopyOnOpen;
|
|
*EffectiveOnly = Thread->ImpersonationInfo->EffectiveOnly;
|
|
|
|
/* Set the token */
|
|
Token = Thread->ImpersonationInfo->Token;
|
|
}
|
|
|
|
/* Unlock thread and return impersonation token */
|
|
PspUnlockThreadSecurityShared(Thread);
|
|
return Token;
|
|
}
|
|
|
|
#undef PsDereferenceImpersonationToken
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
PsDereferenceImpersonationToken(IN PACCESS_TOKEN ImpersonationToken)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
/* If we got a token, dereference it */
|
|
if (ImpersonationToken) ObDereferenceObject(ImpersonationToken);
|
|
}
|
|
|
|
#undef PsDereferencePrimaryToken
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
PsDereferencePrimaryToken(IN PACCESS_TOKEN PrimaryToken)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
/* Dereference the token*/
|
|
ObDereferenceObject(PrimaryToken);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
PsDisableImpersonation(IN PETHREAD Thread,
|
|
OUT PSE_IMPERSONATION_STATE ImpersonationState)
|
|
{
|
|
PPS_IMPERSONATION_INFORMATION Impersonation = NULL;
|
|
LONG OldFlags;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG,
|
|
"Thread: %p State: %p\n", Thread, ImpersonationState);
|
|
|
|
/* Check if we don't have impersonation */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Lock thread security */
|
|
PspLockThreadSecurityExclusive(Thread);
|
|
|
|
/* Disable impersonation */
|
|
OldFlags = PspClearCrossThreadFlag(Thread,
|
|
CT_ACTIVE_IMPERSONATION_INFO_BIT);
|
|
|
|
/* Make sure nobody disabled it behind our back */
|
|
if (OldFlags & CT_ACTIVE_IMPERSONATION_INFO_BIT)
|
|
{
|
|
/* Copy the old state */
|
|
Impersonation = Thread->ImpersonationInfo;
|
|
ImpersonationState->Token = Impersonation->Token;
|
|
ImpersonationState->CopyOnOpen = Impersonation->CopyOnOpen;
|
|
ImpersonationState->EffectiveOnly = Impersonation->EffectiveOnly;
|
|
ImpersonationState->Level = Impersonation->ImpersonationLevel;
|
|
}
|
|
|
|
/* Unlock thread security */
|
|
PspUnlockThreadSecurityExclusive(Thread);
|
|
|
|
/* If we had impersonation info, return true */
|
|
if (Impersonation) return TRUE;
|
|
}
|
|
|
|
/* Clear everything */
|
|
ImpersonationState->Token = NULL;
|
|
ImpersonationState->CopyOnOpen = FALSE;
|
|
ImpersonationState->EffectiveOnly = FALSE;
|
|
ImpersonationState->Level = SecurityAnonymous;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
PsRestoreImpersonation(IN PETHREAD Thread,
|
|
IN PSE_IMPERSONATION_STATE ImpersonationState)
|
|
{
|
|
PTOKEN Token = NULL;
|
|
PPS_IMPERSONATION_INFORMATION Impersonation;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG,
|
|
"Thread: %p State: %p\n", Thread, ImpersonationState);
|
|
|
|
/* Lock thread security */
|
|
PspLockThreadSecurityExclusive(Thread);
|
|
|
|
/* Get the impersonation info */
|
|
Impersonation = Thread->ImpersonationInfo;
|
|
|
|
/* Check if we're impersonating */
|
|
if (Thread->ActiveImpersonationInfo)
|
|
{
|
|
/* Get the token */
|
|
Token = Impersonation->Token;
|
|
}
|
|
|
|
/* Check if we have an impersonation state */
|
|
if (ImpersonationState)
|
|
{
|
|
/* Fill out the impersonation info */
|
|
Impersonation->ImpersonationLevel = ImpersonationState->Level;
|
|
Impersonation->CopyOnOpen = ImpersonationState->CopyOnOpen;
|
|
Impersonation->EffectiveOnly = ImpersonationState->EffectiveOnly;
|
|
Impersonation->Token = ImpersonationState->Token;
|
|
|
|
/* Enable impersonation */
|
|
PspSetCrossThreadFlag(Thread, CT_ACTIVE_IMPERSONATION_INFO_BIT);
|
|
}
|
|
else
|
|
{
|
|
/* Disable impersonation */
|
|
PspClearCrossThreadFlag(Thread, CT_ACTIVE_IMPERSONATION_INFO_BIT);
|
|
}
|
|
|
|
/* Unlock the thread */
|
|
PspUnlockThreadSecurityExclusive(Thread);
|
|
|
|
/* Dereference the token */
|
|
if (Token) ObDereferenceObject(Token);
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
NtImpersonateThread(IN HANDLE ThreadHandle,
|
|
IN HANDLE ThreadToImpersonateHandle,
|
|
IN PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService)
|
|
{
|
|
SECURITY_QUALITY_OF_SERVICE SafeServiceQoS;
|
|
SECURITY_CLIENT_CONTEXT ClientContext;
|
|
PETHREAD Thread;
|
|
PETHREAD ThreadToImpersonate;
|
|
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_SECURITY_DEBUG,
|
|
"Threads: %p %p\n", ThreadHandle, ThreadToImpersonateHandle);
|
|
|
|
/* Check if call came from user mode */
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
/* Enter SEH for probing */
|
|
_SEH2_TRY
|
|
{
|
|
/* Probe QoS */
|
|
ProbeForRead(SecurityQualityOfService,
|
|
sizeof(SECURITY_QUALITY_OF_SERVICE),
|
|
sizeof(ULONG));
|
|
|
|
/* Capture it */
|
|
SafeServiceQoS = *SecurityQualityOfService;
|
|
SecurityQualityOfService = &SafeServiceQoS;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
|
|
/* Reference the thread */
|
|
Status = ObReferenceObjectByHandle(ThreadHandle,
|
|
THREAD_DIRECT_IMPERSONATION,
|
|
PsThreadType,
|
|
PreviousMode,
|
|
(PVOID*)&Thread,
|
|
NULL);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Reference the impersonating thead */
|
|
Status = ObReferenceObjectByHandle(ThreadToImpersonateHandle,
|
|
THREAD_IMPERSONATE,
|
|
PsThreadType,
|
|
PreviousMode,
|
|
(PVOID*)&ThreadToImpersonate,
|
|
NULL);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Create a client security context */
|
|
Status = SeCreateClientSecurity(ThreadToImpersonate,
|
|
SecurityQualityOfService,
|
|
0,
|
|
&ClientContext);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Do the impersonation */
|
|
SeImpersonateClient(&ClientContext, Thread);
|
|
if (ClientContext.ClientToken)
|
|
{
|
|
/* Dereference the client token if we had one */
|
|
ObDereferenceObject(ClientContext.ClientToken);
|
|
}
|
|
}
|
|
|
|
/* Dereference the thread to impersonate */
|
|
ObDereferenceObject(ThreadToImpersonate);
|
|
}
|
|
|
|
/* Dereference the main thread */
|
|
ObDereferenceObject(Thread);
|
|
}
|
|
|
|
/* Return status */
|
|
return Status;
|
|
}
|
|
/* EOF */
|