mirror of
https://github.com/reactos/reactos.git
synced 2024-12-30 19:14:31 +00:00
8b75dce45a
This fixes the copyright file header at the top of the file, reflecting the Coding Style rules. No code changes!
2286 lines
80 KiB
C
2286 lines
80 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
|
* PURPOSE: Access token lifetime management implementation (Creation/Duplication/Filtering)
|
|
* COPYRIGHT: Copyright David Welch <welch@cwcom.net>
|
|
* Copyright 2021-2022 George Bișoc <george.bisoc@reactos.org>
|
|
*/
|
|
|
|
/* INCLUDES *******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* DEFINES ********************************************************************/
|
|
|
|
#define SE_TOKEN_DYNAMIC_SLIM 500
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
/**
|
|
* @brief
|
|
* Internal function responsible for access token object creation in the kernel.
|
|
* A fully created token objected is inserted into the token handle, thus the handle
|
|
* becoming a valid handle to an access token object and ready for use.
|
|
*
|
|
* @param[out] TokenHandle
|
|
* Valid token handle that's ready for use after token creation and object insertion.
|
|
*
|
|
* @param[in] PreviousMode
|
|
* Processor request level mode.
|
|
*
|
|
* @param[in] DesiredAccess
|
|
* Desired access right for the token object to be granted. This kind of access right
|
|
* impacts how the token can be used and who.
|
|
*
|
|
* @param[in] ObjectAttributes
|
|
* Object attributes for the token to be created.
|
|
*
|
|
* @param[in] TokenType
|
|
* Type of token to assign upon creation.
|
|
*
|
|
* @param[in] ImpersonationLevel
|
|
* Security impersonation level of token to assign upon creation.
|
|
*
|
|
* @param[in] AuthenticationId
|
|
* Authentication ID that represents the authentication information of the token.
|
|
*
|
|
* @param[in] ExpirationTime
|
|
* Expiration time of the token to assign. A value of -1 means that the token never
|
|
* expires and its life depends upon the amount of references this token object has.
|
|
*
|
|
* @param[in] User
|
|
* User entry to assign to the token.
|
|
*
|
|
* @param[in] GroupCount
|
|
* The total number of groups count for the token.
|
|
*
|
|
* @param[in] Groups
|
|
* The group entries for the token.
|
|
*
|
|
* @param[in] GroupsLength
|
|
* The length size of the groups array, pointed by the Groups parameter.
|
|
*
|
|
* @param[in] PrivilegeCount
|
|
* The total number of priivleges that the newly created token has.
|
|
*
|
|
* @param[in] Privileges
|
|
* The privileges for the token.
|
|
*
|
|
* @param[in] Owner
|
|
* The main user (or also owner) that represents the token that we create.
|
|
*
|
|
* @param[in] PrimaryGroup
|
|
* The main group that represents the token that we create.
|
|
*
|
|
* @param[in] DefaultDacl
|
|
* A discretionary access control list for the token.
|
|
*
|
|
* @param[in] TokenSource
|
|
* Source (or the origin) of the access token that creates it.
|
|
*
|
|
* @param[in] SystemToken
|
|
* If set to TRUE, the newly created token is a system token and only in charge
|
|
* by the internal system. The function directly returns a pointer to the
|
|
* created token object for system kernel use. Otherwise if set to FALSE, the
|
|
* function inserts the object to a handle making it a regular access token.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if token creation has completed successfully.
|
|
* STATUS_INSUFFICIENT_RESOURCES is returned if the dynamic area of memory of the
|
|
* token hasn't been allocated because of lack of memory resources. A failure
|
|
* NTSTATUS code is returned otherwise.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
SepCreateToken(
|
|
_Out_ PHANDLE TokenHandle,
|
|
_In_ KPROCESSOR_MODE PreviousMode,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
|
|
_In_ TOKEN_TYPE TokenType,
|
|
_In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
|
|
_In_ PLUID AuthenticationId,
|
|
_In_ PLARGE_INTEGER ExpirationTime,
|
|
_In_ PSID_AND_ATTRIBUTES User,
|
|
_In_ ULONG GroupCount,
|
|
_In_ PSID_AND_ATTRIBUTES Groups,
|
|
_In_ ULONG GroupsLength,
|
|
_In_ ULONG PrivilegeCount,
|
|
_In_ PLUID_AND_ATTRIBUTES Privileges,
|
|
_In_opt_ PSID Owner,
|
|
_In_ PSID PrimaryGroup,
|
|
_In_opt_ PACL DefaultDacl,
|
|
_In_ PTOKEN_SOURCE TokenSource,
|
|
_In_ BOOLEAN SystemToken)
|
|
{
|
|
NTSTATUS Status;
|
|
PTOKEN AccessToken;
|
|
ULONG TokenFlags = 0;
|
|
ULONG PrimaryGroupIndex, DefaultOwnerIndex;
|
|
LUID TokenId;
|
|
LUID ModifiedId;
|
|
PVOID EndMem;
|
|
ULONG PrivilegesLength;
|
|
ULONG UserGroupsLength;
|
|
ULONG VariableLength;
|
|
ULONG DynamicPartSize, TotalSize;
|
|
ULONG TokenPagedCharges;
|
|
ULONG i;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Loop all groups */
|
|
for (i = 0; i < GroupCount; i++)
|
|
{
|
|
/* Check for mandatory groups */
|
|
if (Groups[i].Attributes & SE_GROUP_MANDATORY)
|
|
{
|
|
/* Force them to be enabled */
|
|
Groups[i].Attributes |= (SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT);
|
|
}
|
|
|
|
/* Check of the group is an admin group */
|
|
if (RtlEqualSid(SeAliasAdminsSid, Groups[i].Sid))
|
|
{
|
|
/* Remember this so we can optimize queries later */
|
|
TokenFlags |= TOKEN_HAS_ADMIN_GROUP;
|
|
}
|
|
}
|
|
|
|
/* Allocate unique IDs for the token */
|
|
ExAllocateLocallyUniqueId(&TokenId);
|
|
ExAllocateLocallyUniqueId(&ModifiedId);
|
|
|
|
/* Compute how much size we need to allocate for the token */
|
|
|
|
/* Privileges size */
|
|
PrivilegesLength = PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES);
|
|
PrivilegesLength = ALIGN_UP_BY(PrivilegesLength, sizeof(PVOID));
|
|
|
|
/* User and groups size */
|
|
UserGroupsLength = (1 + GroupCount) * sizeof(SID_AND_ATTRIBUTES);
|
|
UserGroupsLength += RtlLengthSid(User->Sid);
|
|
for (i = 0; i < GroupCount; i++)
|
|
{
|
|
UserGroupsLength += RtlLengthSid(Groups[i].Sid);
|
|
}
|
|
UserGroupsLength = ALIGN_UP_BY(UserGroupsLength, sizeof(PVOID));
|
|
|
|
/* Add the additional groups array length */
|
|
UserGroupsLength += ALIGN_UP_BY(GroupsLength, sizeof(PVOID));
|
|
|
|
VariableLength = PrivilegesLength + UserGroupsLength;
|
|
TotalSize = FIELD_OFFSET(TOKEN, VariablePart) + VariableLength;
|
|
|
|
/*
|
|
* A token is considered slim if it has the default dynamic
|
|
* contents, or in other words, the primary group and ACL.
|
|
* We judge if such contents are default by checking their
|
|
* total size if it's over the range. On Windows this range
|
|
* is 0x1F4 (aka 500). If the size of the whole dynamic contents
|
|
* is over that range then the token is considered fat and
|
|
* the token will be charged the whole of its token body length
|
|
* plus the dynamic size.
|
|
*/
|
|
DynamicPartSize = DefaultDacl ? DefaultDacl->AclSize : 0;
|
|
DynamicPartSize += RtlLengthSid(PrimaryGroup);
|
|
if (DynamicPartSize > SE_TOKEN_DYNAMIC_SLIM)
|
|
{
|
|
TokenPagedCharges = DynamicPartSize + TotalSize;
|
|
}
|
|
else
|
|
{
|
|
TokenPagedCharges = SE_TOKEN_DYNAMIC_SLIM + TotalSize;
|
|
}
|
|
|
|
Status = ObCreateObject(PreviousMode,
|
|
SeTokenObjectType,
|
|
ObjectAttributes,
|
|
PreviousMode,
|
|
NULL,
|
|
TotalSize,
|
|
TokenPagedCharges,
|
|
0,
|
|
(PVOID*)&AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ObCreateObject() failed (Status 0x%lx)\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
/* Zero out the buffer and initialize the token */
|
|
RtlZeroMemory(AccessToken, TotalSize);
|
|
|
|
AccessToken->TokenId = TokenId;
|
|
AccessToken->TokenType = TokenType;
|
|
AccessToken->ImpersonationLevel = ImpersonationLevel;
|
|
|
|
/* Initialise the lock for the access token */
|
|
Status = SepCreateTokenLock(AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
goto Quit;
|
|
|
|
AccessToken->TokenSource.SourceIdentifier = TokenSource->SourceIdentifier;
|
|
RtlCopyMemory(AccessToken->TokenSource.SourceName,
|
|
TokenSource->SourceName,
|
|
sizeof(TokenSource->SourceName));
|
|
|
|
AccessToken->ExpirationTime = *ExpirationTime;
|
|
AccessToken->ModifiedId = ModifiedId;
|
|
AccessToken->DynamicCharged = TokenPagedCharges - TotalSize;
|
|
|
|
AccessToken->TokenFlags = TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED;
|
|
|
|
/* Copy and reference the logon session */
|
|
AccessToken->AuthenticationId = *AuthenticationId;
|
|
Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* No logon session could be found, bail out */
|
|
DPRINT1("SepRmReferenceLogonSession() failed (Status 0x%lx)\n", Status);
|
|
/* Set the flag for proper cleanup by the delete procedure */
|
|
AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED;
|
|
goto Quit;
|
|
}
|
|
|
|
/* Insert the referenced logon session into the token */
|
|
Status = SepRmInsertLogonSessionIntoToken(AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Failed to insert the logon session into the token, bail out */
|
|
DPRINT1("SepRmInsertLogonSessionIntoToken() failed (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
|
|
/* Fill in token debug information */
|
|
#if DBG
|
|
/*
|
|
* We must determine ourselves that the current
|
|
* process is not the initial CPU one. The initial
|
|
* process is not a "real" process, that is, the
|
|
* Process Manager has not yet been initialized and
|
|
* as a matter of fact we are creating a token before
|
|
* any process gets created by Ps. If it turns out
|
|
* that the current process is the initial CPU process
|
|
* where token creation execution takes place, don't
|
|
* do anything.
|
|
*/
|
|
if (PsGetCurrentProcess() != &KiInitialProcess)
|
|
{
|
|
RtlCopyMemory(AccessToken->ImageFileName,
|
|
PsGetCurrentProcess()->ImageFileName,
|
|
min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName)));
|
|
|
|
AccessToken->ProcessCid = PsGetCurrentProcessId();
|
|
AccessToken->ThreadCid = PsGetCurrentThreadId();
|
|
}
|
|
|
|
AccessToken->CreateMethod = TOKEN_CREATE_METHOD;
|
|
#endif
|
|
|
|
/* Assign the data that reside in the token's variable information area */
|
|
AccessToken->VariableLength = VariableLength;
|
|
EndMem = (PVOID)&AccessToken->VariablePart;
|
|
|
|
/* Copy the privileges */
|
|
AccessToken->PrivilegeCount = PrivilegeCount;
|
|
AccessToken->Privileges = NULL;
|
|
if (PrivilegeCount > 0)
|
|
{
|
|
AccessToken->Privileges = EndMem;
|
|
EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength);
|
|
VariableLength -= PrivilegesLength;
|
|
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
_SEH2_TRY
|
|
{
|
|
RtlCopyMemory(AccessToken->Privileges,
|
|
Privileges,
|
|
PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
else
|
|
{
|
|
RtlCopyMemory(AccessToken->Privileges,
|
|
Privileges,
|
|
PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
goto Quit;
|
|
}
|
|
|
|
/* Update the privilege flags */
|
|
SepUpdatePrivilegeFlagsToken(AccessToken);
|
|
|
|
/* Copy the user and groups */
|
|
AccessToken->UserAndGroupCount = 1 + GroupCount;
|
|
AccessToken->UserAndGroups = EndMem;
|
|
EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount];
|
|
VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups);
|
|
|
|
Status = RtlCopySidAndAttributesArray(1,
|
|
User,
|
|
VariableLength,
|
|
&AccessToken->UserAndGroups[0],
|
|
EndMem,
|
|
&EndMem,
|
|
&VariableLength);
|
|
if (!NT_SUCCESS(Status))
|
|
goto Quit;
|
|
|
|
Status = RtlCopySidAndAttributesArray(GroupCount,
|
|
Groups,
|
|
VariableLength,
|
|
&AccessToken->UserAndGroups[1],
|
|
EndMem,
|
|
&EndMem,
|
|
&VariableLength);
|
|
if (!NT_SUCCESS(Status))
|
|
goto Quit;
|
|
|
|
/* Find the token primary group and default owner */
|
|
Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken,
|
|
PrimaryGroup,
|
|
Owner,
|
|
&PrimaryGroupIndex,
|
|
&DefaultOwnerIndex);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SepFindPrimaryGroupAndDefaultOwner failed (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
|
|
/*
|
|
* Now allocate the token's dynamic information area
|
|
* and set the data. The dynamic part consists of two
|
|
* contents, the primary group SID and the default DACL
|
|
* of the token, in this strict order.
|
|
*/
|
|
AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool,
|
|
DynamicPartSize,
|
|
TAG_TOKEN_DYNAMIC);
|
|
if (AccessToken->DynamicPart == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Quit;
|
|
}
|
|
|
|
/* Unused memory in the dynamic area */
|
|
AccessToken->DynamicAvailable = 0;
|
|
|
|
/*
|
|
* Assign the primary group to the token
|
|
* and put it in the dynamic part as well.
|
|
*/
|
|
EndMem = (PVOID)AccessToken->DynamicPart;
|
|
AccessToken->PrimaryGroup = EndMem;
|
|
RtlCopySid(RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid),
|
|
EndMem,
|
|
AccessToken->UserAndGroups[PrimaryGroupIndex].Sid);
|
|
AccessToken->DefaultOwnerIndex = DefaultOwnerIndex;
|
|
EndMem = (PVOID)((ULONG_PTR)EndMem + RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid));
|
|
|
|
/*
|
|
* We have assigned a primary group and put it in the
|
|
* dynamic part, now it's time to copy the provided
|
|
* default DACL (if it's provided to begin with) into
|
|
* the DACL field of the token and put it at the end
|
|
* tail of the dynamic part too.
|
|
*/
|
|
if (DefaultDacl != NULL)
|
|
{
|
|
AccessToken->DefaultDacl = EndMem;
|
|
|
|
RtlCopyMemory(EndMem,
|
|
DefaultDacl,
|
|
DefaultDacl->AclSize);
|
|
}
|
|
|
|
/* Insert the token only if it's not the system token, otherwise return it directly */
|
|
if (!SystemToken)
|
|
{
|
|
Status = ObInsertObject(AccessToken,
|
|
NULL,
|
|
DesiredAccess,
|
|
0,
|
|
NULL,
|
|
TokenHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ObInsertObject() failed (Status 0x%lx)\n", Status);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Return pointer instead of handle */
|
|
*TokenHandle = (HANDLE)AccessToken;
|
|
}
|
|
|
|
Quit:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Dereference the token, the delete procedure will clean it up */
|
|
ObDereferenceObject(AccessToken);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Duplicates an access token, from an existing valid token.
|
|
*
|
|
* @param[in] Token
|
|
* Access token to duplicate.
|
|
*
|
|
* @param[in] ObjectAttributes
|
|
* Object attributes for the new token.
|
|
*
|
|
* @param[in] EffectiveOnly
|
|
* If set to TRUE, the function removes all the disabled privileges and groups of the token
|
|
* to duplicate.
|
|
*
|
|
* @param[in] TokenType
|
|
* Type of token.
|
|
*
|
|
* @param[in] Level
|
|
* Security impersonation level of a token.
|
|
*
|
|
* @param[in] PreviousMode
|
|
* The processor request level mode.
|
|
*
|
|
* @param[out] NewAccessToken
|
|
* The duplicated token.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if the token has been duplicated. STATUS_INSUFFICIENT_RESOURCES is returned
|
|
* if memory pool allocation of the dynamic part of the token for duplication has failed due to the lack
|
|
* of memory resources. A failure NTSTATUS code is returned otherwise.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
SepDuplicateToken(
|
|
_In_ PTOKEN Token,
|
|
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
|
|
_In_ BOOLEAN EffectiveOnly,
|
|
_In_ TOKEN_TYPE TokenType,
|
|
_In_ SECURITY_IMPERSONATION_LEVEL Level,
|
|
_In_ KPROCESSOR_MODE PreviousMode,
|
|
_Out_ PTOKEN* NewAccessToken)
|
|
{
|
|
NTSTATUS Status;
|
|
PTOKEN AccessToken;
|
|
PVOID EndMem;
|
|
ULONG PrimaryGroupIndex;
|
|
ULONG VariableLength;
|
|
ULONG DynamicPartSize, TotalSize;
|
|
ULONG PrivilegesIndex, GroupsIndex;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Compute how much size we need to allocate for the token */
|
|
VariableLength = Token->VariableLength;
|
|
TotalSize = FIELD_OFFSET(TOKEN, VariablePart) + VariableLength;
|
|
|
|
/*
|
|
* Compute how much size we need to allocate
|
|
* the dynamic part of the newly duplicated
|
|
* token.
|
|
*/
|
|
DynamicPartSize = Token->DefaultDacl ? Token->DefaultDacl->AclSize : 0;
|
|
DynamicPartSize += RtlLengthSid(Token->PrimaryGroup);
|
|
|
|
Status = ObCreateObject(PreviousMode,
|
|
SeTokenObjectType,
|
|
ObjectAttributes,
|
|
PreviousMode,
|
|
NULL,
|
|
TotalSize,
|
|
Token->DynamicCharged,
|
|
TotalSize,
|
|
(PVOID*)&AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ObCreateObject() failed (Status 0x%lx)\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
/* Zero out the buffer and initialize the token */
|
|
RtlZeroMemory(AccessToken, TotalSize);
|
|
|
|
ExAllocateLocallyUniqueId(&AccessToken->TokenId);
|
|
|
|
AccessToken->TokenType = TokenType;
|
|
AccessToken->ImpersonationLevel = Level;
|
|
|
|
/* Initialise the lock for the access token */
|
|
Status = SepCreateTokenLock(AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
ObDereferenceObject(AccessToken);
|
|
return Status;
|
|
}
|
|
|
|
/* Copy the immutable fields */
|
|
AccessToken->TokenSource.SourceIdentifier = Token->TokenSource.SourceIdentifier;
|
|
RtlCopyMemory(AccessToken->TokenSource.SourceName,
|
|
Token->TokenSource.SourceName,
|
|
sizeof(Token->TokenSource.SourceName));
|
|
|
|
AccessToken->AuthenticationId = Token->AuthenticationId;
|
|
AccessToken->ParentTokenId = Token->ParentTokenId;
|
|
AccessToken->ExpirationTime = Token->ExpirationTime;
|
|
AccessToken->OriginatingLogonSession = Token->OriginatingLogonSession;
|
|
AccessToken->DynamicCharged = Token->DynamicCharged;
|
|
|
|
/* Lock the source token and copy the mutable fields */
|
|
SepAcquireTokenLockShared(Token);
|
|
|
|
AccessToken->SessionId = Token->SessionId;
|
|
AccessToken->ModifiedId = Token->ModifiedId;
|
|
|
|
AccessToken->TokenFlags = Token->TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED;
|
|
|
|
/* Reference the logon session */
|
|
Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* No logon session could be found, bail out */
|
|
DPRINT1("SepRmReferenceLogonSession() failed (Status 0x%lx)\n", Status);
|
|
/* Set the flag for proper cleanup by the delete procedure */
|
|
AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED;
|
|
goto Quit;
|
|
}
|
|
|
|
/* Insert the referenced logon session into the token */
|
|
Status = SepRmInsertLogonSessionIntoToken(AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Failed to insert the logon session into the token, bail out */
|
|
DPRINT1("SepRmInsertLogonSessionIntoToken() failed (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
|
|
/* Fill in token debug information */
|
|
#if DBG
|
|
RtlCopyMemory(AccessToken->ImageFileName,
|
|
PsGetCurrentProcess()->ImageFileName,
|
|
min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName)));
|
|
|
|
AccessToken->ProcessCid = PsGetCurrentProcessId();
|
|
AccessToken->ThreadCid = PsGetCurrentThreadId();
|
|
AccessToken->CreateMethod = TOKEN_DUPLICATE_METHOD;
|
|
#endif
|
|
|
|
/* Assign the data that reside in the token's variable information area */
|
|
AccessToken->VariableLength = VariableLength;
|
|
EndMem = (PVOID)&AccessToken->VariablePart;
|
|
|
|
/* Copy the privileges */
|
|
AccessToken->PrivilegeCount = 0;
|
|
AccessToken->Privileges = NULL;
|
|
if (Token->Privileges && (Token->PrivilegeCount > 0))
|
|
{
|
|
ULONG PrivilegesLength = Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES);
|
|
PrivilegesLength = ALIGN_UP_BY(PrivilegesLength, sizeof(PVOID));
|
|
|
|
ASSERT(VariableLength >= PrivilegesLength);
|
|
|
|
AccessToken->PrivilegeCount = Token->PrivilegeCount;
|
|
AccessToken->Privileges = EndMem;
|
|
EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength);
|
|
VariableLength -= PrivilegesLength;
|
|
|
|
RtlCopyMemory(AccessToken->Privileges,
|
|
Token->Privileges,
|
|
AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
|
|
}
|
|
|
|
/* Copy the user and groups */
|
|
AccessToken->UserAndGroupCount = 0;
|
|
AccessToken->UserAndGroups = NULL;
|
|
if (Token->UserAndGroups && (Token->UserAndGroupCount > 0))
|
|
{
|
|
AccessToken->UserAndGroupCount = Token->UserAndGroupCount;
|
|
AccessToken->UserAndGroups = EndMem;
|
|
EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount];
|
|
VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups);
|
|
|
|
Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount,
|
|
Token->UserAndGroups,
|
|
VariableLength,
|
|
AccessToken->UserAndGroups,
|
|
EndMem,
|
|
&EndMem,
|
|
&VariableLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("RtlCopySidAndAttributesArray(UserAndGroups) failed (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
/* Find the token primary group */
|
|
Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken,
|
|
Token->PrimaryGroup,
|
|
NULL,
|
|
&PrimaryGroupIndex,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SepFindPrimaryGroupAndDefaultOwner failed (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
|
|
/* Copy the restricted SIDs */
|
|
AccessToken->RestrictedSidCount = 0;
|
|
AccessToken->RestrictedSids = NULL;
|
|
if (Token->RestrictedSids && (Token->RestrictedSidCount > 0))
|
|
{
|
|
AccessToken->RestrictedSidCount = Token->RestrictedSidCount;
|
|
AccessToken->RestrictedSids = EndMem;
|
|
EndMem = &AccessToken->RestrictedSids[AccessToken->RestrictedSidCount];
|
|
VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->RestrictedSids);
|
|
|
|
Status = RtlCopySidAndAttributesArray(AccessToken->RestrictedSidCount,
|
|
Token->RestrictedSids,
|
|
VariableLength,
|
|
AccessToken->RestrictedSids,
|
|
EndMem,
|
|
&EndMem,
|
|
&VariableLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("RtlCopySidAndAttributesArray(RestrictedSids) failed (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Filter the token by removing the disabled privileges
|
|
* and groups if the caller wants to duplicate an access
|
|
* token as effective only.
|
|
*/
|
|
if (EffectiveOnly)
|
|
{
|
|
/* Begin querying the groups and search for disabled ones */
|
|
for (GroupsIndex = 0; GroupsIndex < AccessToken->UserAndGroupCount; GroupsIndex++)
|
|
{
|
|
/*
|
|
* A group or user is considered disabled if its attributes is either
|
|
* 0 or SE_GROUP_ENABLED is not included in the attributes flags list.
|
|
* That is because a certain user and/or group can have several attributes
|
|
* that bear no influence on whether a user/group is enabled or not
|
|
* (SE_GROUP_ENABLED_BY_DEFAULT for example which is a mere indicator
|
|
* that the group has just been enabled by default). A mandatory
|
|
* group (that is, the group has SE_GROUP_MANDATORY attribute)
|
|
* by standards it's always enabled and no one can disable it.
|
|
*/
|
|
if (AccessToken->UserAndGroups[GroupsIndex].Attributes == 0 ||
|
|
(AccessToken->UserAndGroups[GroupsIndex].Attributes & SE_GROUP_ENABLED) == 0)
|
|
{
|
|
/*
|
|
* If this group is an administrators group
|
|
* and the token belongs to such group,
|
|
* we've to take away TOKEN_HAS_ADMIN_GROUP
|
|
* for the fact that's not enabled and as
|
|
* such the token no longer belongs to
|
|
* this group.
|
|
*/
|
|
if (RtlEqualSid(SeAliasAdminsSid,
|
|
&AccessToken->UserAndGroups[GroupsIndex].Sid))
|
|
{
|
|
AccessToken->TokenFlags &= ~TOKEN_HAS_ADMIN_GROUP;
|
|
}
|
|
|
|
/*
|
|
* A group is not enabled, it's time to remove
|
|
* from the token and update the groups index
|
|
* accordingly and continue with the next group.
|
|
*/
|
|
SepRemoveUserGroupToken(AccessToken, GroupsIndex);
|
|
GroupsIndex--;
|
|
}
|
|
}
|
|
|
|
/* Begin querying the privileges and search for disabled ones */
|
|
for (PrivilegesIndex = 0; PrivilegesIndex < AccessToken->PrivilegeCount; PrivilegesIndex++)
|
|
{
|
|
/*
|
|
* A privilege is considered disabled if its attributes is either
|
|
* 0 or SE_PRIVILEGE_ENABLED is not included in the attributes flags list.
|
|
* That is because a certain privilege can have several attributes
|
|
* that bear no influence on whether a privilege is enabled or not
|
|
* (SE_PRIVILEGE_ENABLED_BY_DEFAULT for example which is a mere indicator
|
|
* that the privilege has just been enabled by default).
|
|
*/
|
|
if (AccessToken->Privileges[PrivilegesIndex].Attributes == 0 ||
|
|
(AccessToken->Privileges[PrivilegesIndex].Attributes & SE_PRIVILEGE_ENABLED) == 0)
|
|
{
|
|
/*
|
|
* A privilege is not enabled, therefor it's time
|
|
* to strip it from the token and continue with the next
|
|
* privilege. Of course we must also want to update the
|
|
* privileges index accordingly.
|
|
*/
|
|
SepRemovePrivilegeToken(AccessToken, PrivilegesIndex);
|
|
PrivilegesIndex--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now allocate the token's dynamic information area and set the data */
|
|
AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool,
|
|
DynamicPartSize,
|
|
TAG_TOKEN_DYNAMIC);
|
|
if (AccessToken->DynamicPart == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Quit;
|
|
}
|
|
|
|
/* Unused memory in the dynamic area */
|
|
AccessToken->DynamicAvailable = 0;
|
|
|
|
/*
|
|
* Assign the primary group to the token
|
|
* and put it in the dynamic part as well.
|
|
*/
|
|
EndMem = (PVOID)AccessToken->DynamicPart;
|
|
AccessToken->PrimaryGroup = EndMem;
|
|
RtlCopySid(RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid),
|
|
EndMem,
|
|
AccessToken->UserAndGroups[PrimaryGroupIndex].Sid);
|
|
AccessToken->DefaultOwnerIndex = Token->DefaultOwnerIndex;
|
|
EndMem = (PVOID)((ULONG_PTR)EndMem + RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid));
|
|
|
|
/*
|
|
* The existing token has a default DACL only
|
|
* if it has an allocated dynamic part.
|
|
*/
|
|
if (Token->DynamicPart && Token->DefaultDacl)
|
|
{
|
|
AccessToken->DefaultDacl = EndMem;
|
|
|
|
RtlCopyMemory(EndMem,
|
|
Token->DefaultDacl,
|
|
Token->DefaultDacl->AclSize);
|
|
}
|
|
|
|
/* Return the token to the caller */
|
|
*NewAccessToken = AccessToken;
|
|
Status = STATUS_SUCCESS;
|
|
|
|
Quit:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Dereference the token, the delete procedure will clean it up */
|
|
ObDereferenceObject(AccessToken);
|
|
}
|
|
|
|
/* Unlock the source token */
|
|
SepReleaseTokenLock(Token);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Private helper function responsible for creating a restricted access
|
|
* token, that is, a filtered token from privileges and groups and with
|
|
* restricted SIDs added into the token on demand by the caller.
|
|
*
|
|
* @param[in] Token
|
|
* An existing and valid access token.
|
|
*
|
|
* @param[in] PrivilegesToBeDeleted
|
|
* A list of privileges to be deleted within the token that's going
|
|
* to be filtered. This parameter is ignored if the caller wants to disable
|
|
* all the privileges by specifying DISABLE_MAX_PRIVILEGE in the flags
|
|
* parameter.
|
|
*
|
|
* @param[in] SidsToBeDisabled
|
|
* A list of group SIDs to be disabled within the token. This parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[in] RestrictedSidsIntoToken
|
|
* A list of restricted SIDs to be added into the token. This parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[in] PrivilegesCount
|
|
* The privilege count of the privileges list.
|
|
*
|
|
* @param[in] RegularGroupsSidCount
|
|
* The SIDs count of the group SIDs list.
|
|
*
|
|
* @param[in] RestrictedSidsCount
|
|
* The restricted SIDs count of restricted SIDs list.
|
|
*
|
|
* @param[in] PrivilegeFlags
|
|
* Influences how the privileges should be filtered in an access
|
|
* token. See NtFilterToken syscall for more information.
|
|
*
|
|
* @param[in] PreviousMode
|
|
* Processor level access mode.
|
|
*
|
|
* @param[out] FilteredToken
|
|
* The filtered token, returned to the caller.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if token token filtering has completed successfully.
|
|
* STATUS_INVALID_PARAMETER is returned if one or more of the parameters
|
|
* do not meet the conditions imposed by the function. A failure NTSTATUS
|
|
* code is returned otherwise.
|
|
*
|
|
* @remarks
|
|
* The final outcome of privileges and/or SIDs filtering is not always
|
|
* deterministic. That is, any privileges or SIDs that aren't present
|
|
* in the access token are ignored and the function continues with the
|
|
* next privilege or SID to find for filtering. For a fully deterministic
|
|
* outcome the caller is responsible for querying the information details
|
|
* of privileges and SIDs present in the token and then afterwards use
|
|
* such obtained information to do any kind of filtering to the token.
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
SepPerformTokenFiltering(
|
|
_In_ PTOKEN Token,
|
|
_In_opt_ PLUID_AND_ATTRIBUTES PrivilegesToBeDeleted,
|
|
_In_opt_ PSID_AND_ATTRIBUTES SidsToBeDisabled,
|
|
_In_opt_ PSID_AND_ATTRIBUTES RestrictedSidsIntoToken,
|
|
_When_(PrivilegesToBeDeleted != NULL, _In_) ULONG PrivilegesCount,
|
|
_When_(SidsToBeDisabled != NULL, _In_) ULONG RegularGroupsSidCount,
|
|
_When_(RestrictedSidsIntoToken != NULL, _In_) ULONG RestrictedSidsCount,
|
|
_In_ ULONG PrivilegeFlags,
|
|
_In_ KPROCESSOR_MODE PreviousMode,
|
|
_Out_ PTOKEN *FilteredToken)
|
|
{
|
|
NTSTATUS Status;
|
|
PTOKEN AccessToken;
|
|
PVOID EndMem;
|
|
ULONG DynamicPartSize;
|
|
ULONG RestrictedSidsLength;
|
|
ULONG PrivilegesLength;
|
|
ULONG PrimaryGroupIndex;
|
|
ULONG RestrictedSidsInList;
|
|
ULONG RestrictedSidsInToken;
|
|
ULONG VariableLength, TotalSize;
|
|
ULONG PrivsInToken, PrivsInList;
|
|
ULONG GroupsInToken, GroupsInList;
|
|
BOOLEAN WantPrivilegesDisabled;
|
|
BOOLEAN FoundPrivilege;
|
|
BOOLEAN FoundGroup;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Ensure that the source token is valid, and lock it */
|
|
ASSERT(Token);
|
|
SepAcquireTokenLockShared(Token);
|
|
|
|
/* Assume the caller doesn't want privileges disabled */
|
|
WantPrivilegesDisabled = FALSE;
|
|
|
|
/* Assume we haven't found anything */
|
|
FoundPrivilege = FALSE;
|
|
FoundGroup = FALSE;
|
|
|
|
/*
|
|
* Take the size that we need for filtered token
|
|
* allocation based upon the existing access token
|
|
* we've been given.
|
|
*/
|
|
VariableLength = Token->VariableLength;
|
|
|
|
if (RestrictedSidsIntoToken != NULL)
|
|
{
|
|
/*
|
|
* If the caller provided a list of restricted SIDs
|
|
* to be added onto the filtered access token then
|
|
* we must compute the size which is the total space
|
|
* of the current token and the length of the restricted
|
|
* SIDs for the filtered token.
|
|
*/
|
|
RestrictedSidsLength = RestrictedSidsCount * sizeof(SID_AND_ATTRIBUTES);
|
|
RestrictedSidsLength += RtlLengthSidAndAttributes(RestrictedSidsCount, RestrictedSidsIntoToken);
|
|
RestrictedSidsLength = ALIGN_UP_BY(RestrictedSidsLength, sizeof(PVOID));
|
|
|
|
/*
|
|
* The variable length of the token is not just
|
|
* the actual space length of the existing token
|
|
* but also the sum of the restricted SIDs length.
|
|
*/
|
|
VariableLength += RestrictedSidsLength;
|
|
TotalSize = FIELD_OFFSET(TOKEN, VariablePart) + VariableLength + RestrictedSidsLength;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise the size is of the actual current token */
|
|
TotalSize = FIELD_OFFSET(TOKEN, VariablePart) + VariableLength;
|
|
}
|
|
|
|
/*
|
|
* Compute how much size we need to allocate
|
|
* the dynamic part of the newly duplicated
|
|
* token.
|
|
*/
|
|
DynamicPartSize = Token->DefaultDacl ? Token->DefaultDacl->AclSize : 0;
|
|
DynamicPartSize += RtlLengthSid(Token->PrimaryGroup);
|
|
|
|
/* Set up a filtered token object */
|
|
Status = ObCreateObject(PreviousMode,
|
|
SeTokenObjectType,
|
|
NULL,
|
|
PreviousMode,
|
|
NULL,
|
|
TotalSize,
|
|
Token->DynamicCharged,
|
|
TotalSize,
|
|
(PVOID*)&AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SepPerformTokenFiltering(): Failed to create the filtered token object (Status 0x%lx)\n", Status);
|
|
|
|
/* Unlock the source token and bail out */
|
|
SepReleaseTokenLock(Token);
|
|
return Status;
|
|
}
|
|
|
|
/* Initialize the token and begin filling stuff to it */
|
|
RtlZeroMemory(AccessToken, TotalSize);
|
|
|
|
/* Set up a lock for the new token */
|
|
Status = SepCreateTokenLock(AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
goto Quit;
|
|
|
|
/* Allocate new IDs for the token */
|
|
ExAllocateLocallyUniqueId(&AccessToken->TokenId);
|
|
ExAllocateLocallyUniqueId(&AccessToken->ModifiedId);
|
|
|
|
/* Copy the type and impersonation level from the token */
|
|
AccessToken->TokenType = Token->TokenType;
|
|
AccessToken->ImpersonationLevel = Token->ImpersonationLevel;
|
|
|
|
/* Copy the immutable fields */
|
|
AccessToken->TokenSource.SourceIdentifier = Token->TokenSource.SourceIdentifier;
|
|
RtlCopyMemory(AccessToken->TokenSource.SourceName,
|
|
Token->TokenSource.SourceName,
|
|
sizeof(Token->TokenSource.SourceName));
|
|
|
|
AccessToken->AuthenticationId = Token->AuthenticationId;
|
|
AccessToken->ParentTokenId = Token->TokenId;
|
|
AccessToken->OriginatingLogonSession = Token->OriginatingLogonSession;
|
|
AccessToken->DynamicCharged = Token->DynamicCharged;
|
|
|
|
AccessToken->ExpirationTime = Token->ExpirationTime;
|
|
|
|
/* Copy the mutable fields */
|
|
AccessToken->SessionId = Token->SessionId;
|
|
AccessToken->TokenFlags = Token->TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED;
|
|
|
|
/* Reference the logon session */
|
|
Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* We failed, bail out*/
|
|
DPRINT1("SepPerformTokenFiltering(): Failed to reference the logon session (Status 0x%lx)\n", Status);
|
|
AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED;
|
|
goto Quit;
|
|
}
|
|
|
|
/* Insert the referenced logon session into the token */
|
|
Status = SepRmInsertLogonSessionIntoToken(AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Failed to insert the logon session into the token, bail out */
|
|
DPRINT1("SepPerformTokenFiltering(): Failed to insert the logon session into token (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
|
|
/* Fill in token debug information */
|
|
#if DBG
|
|
RtlCopyMemory(AccessToken->ImageFileName,
|
|
PsGetCurrentProcess()->ImageFileName,
|
|
min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName)));
|
|
|
|
AccessToken->ProcessCid = PsGetCurrentProcessId();
|
|
AccessToken->ThreadCid = PsGetCurrentThreadId();
|
|
AccessToken->CreateMethod = TOKEN_FILTER_METHOD;
|
|
#endif
|
|
|
|
/* Assign the data that reside in the token's variable information area */
|
|
AccessToken->VariableLength = VariableLength;
|
|
EndMem = (PVOID)&AccessToken->VariablePart;
|
|
|
|
/* Copy the privileges from the existing token */
|
|
AccessToken->PrivilegeCount = 0;
|
|
AccessToken->Privileges = NULL;
|
|
if (Token->Privileges && (Token->PrivilegeCount > 0))
|
|
{
|
|
PrivilegesLength = Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES);
|
|
PrivilegesLength = ALIGN_UP_BY(PrivilegesLength, sizeof(PVOID));
|
|
|
|
/*
|
|
* Ensure that the token can actually hold all
|
|
* the privileges from the existing token.
|
|
* Otherwise something's seriously wrong and
|
|
* we've to guard ourselves.
|
|
*/
|
|
ASSERT(VariableLength >= PrivilegesLength);
|
|
|
|
AccessToken->PrivilegeCount = Token->PrivilegeCount;
|
|
AccessToken->Privileges = EndMem;
|
|
EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength);
|
|
VariableLength -= PrivilegesLength;
|
|
|
|
RtlCopyMemory(AccessToken->Privileges,
|
|
Token->Privileges,
|
|
AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
|
|
}
|
|
|
|
/* Copy the user and groups */
|
|
AccessToken->UserAndGroupCount = 0;
|
|
AccessToken->UserAndGroups = NULL;
|
|
if (Token->UserAndGroups && (Token->UserAndGroupCount > 0))
|
|
{
|
|
AccessToken->UserAndGroupCount = Token->UserAndGroupCount;
|
|
AccessToken->UserAndGroups = EndMem;
|
|
EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount];
|
|
VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups);
|
|
|
|
Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount,
|
|
Token->UserAndGroups,
|
|
VariableLength,
|
|
AccessToken->UserAndGroups,
|
|
EndMem,
|
|
&EndMem,
|
|
&VariableLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SepPerformTokenFiltering(): Failed to copy the groups into token (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
/* Copy the restricted SIDs */
|
|
AccessToken->RestrictedSidCount = 0;
|
|
AccessToken->RestrictedSids = NULL;
|
|
if (Token->RestrictedSids && (Token->RestrictedSidCount > 0))
|
|
{
|
|
AccessToken->RestrictedSidCount = Token->RestrictedSidCount;
|
|
AccessToken->RestrictedSids = EndMem;
|
|
EndMem = &AccessToken->RestrictedSids[AccessToken->RestrictedSidCount];
|
|
VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->RestrictedSids);
|
|
|
|
Status = RtlCopySidAndAttributesArray(AccessToken->RestrictedSidCount,
|
|
Token->RestrictedSids,
|
|
VariableLength,
|
|
AccessToken->RestrictedSids,
|
|
EndMem,
|
|
&EndMem,
|
|
&VariableLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SepPerformTokenFiltering(): Failed to copy the restricted SIDs into token (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Insert the restricted SIDs into the token on
|
|
* the request by the caller.
|
|
*/
|
|
if (RestrictedSidsIntoToken != NULL)
|
|
{
|
|
for (RestrictedSidsInList = 0; RestrictedSidsInList < RestrictedSidsCount; RestrictedSidsInList++)
|
|
{
|
|
/* Did the caller assign attributes to the restricted SIDs? */
|
|
if (RestrictedSidsIntoToken[RestrictedSidsInList].Attributes != 0)
|
|
{
|
|
/* There mustn't be any attributes, bail out */
|
|
DPRINT1("SepPerformTokenFiltering(): There mustn't be any attributes to restricted SIDs!\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Ensure that the token can hold the restricted SIDs
|
|
* (the variable length is calculated at the beginning
|
|
* of the routine call).
|
|
*/
|
|
ASSERT(VariableLength >= RestrictedSidsLength);
|
|
|
|
/*
|
|
* Now let's begin inserting the restricted SIDs into the filtered
|
|
* access token from the list the caller gave us.
|
|
*/
|
|
AccessToken->RestrictedSidCount = RestrictedSidsCount;
|
|
AccessToken->RestrictedSids = EndMem;
|
|
EndMem = (PVOID)((ULONG_PTR)EndMem + RestrictedSidsLength);
|
|
VariableLength -= RestrictedSidsLength;
|
|
|
|
RtlCopyMemory(AccessToken->RestrictedSids,
|
|
RestrictedSidsIntoToken,
|
|
AccessToken->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES));
|
|
|
|
/*
|
|
* As we've copied the restricted SIDs into
|
|
* the token, we must assign them the following
|
|
* combination of attributes SE_GROUP_ENABLED,
|
|
* SE_GROUP_ENABLED_BY_DEFAULT and SE_GROUP_MANDATORY.
|
|
* With such attributes we estabilish that restricting
|
|
* SIDs into the token are enabled for access checks.
|
|
*/
|
|
for (RestrictedSidsInToken = 0; RestrictedSidsInToken < AccessToken->RestrictedSidCount; RestrictedSidsInToken++)
|
|
{
|
|
AccessToken->RestrictedSids[RestrictedSidsInToken].Attributes |= (SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY);
|
|
}
|
|
|
|
/*
|
|
* As we added restricted SIDs into the token, mark
|
|
* it as restricted.
|
|
*/
|
|
AccessToken->TokenFlags |= TOKEN_IS_RESTRICTED;
|
|
}
|
|
|
|
/* Search for the primary group */
|
|
Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken,
|
|
Token->PrimaryGroup,
|
|
NULL,
|
|
&PrimaryGroupIndex,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SepPerformTokenFiltering(): Failed searching for the primary group (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
|
|
/* Now allocate the token's dynamic information area and set the data */
|
|
AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool,
|
|
DynamicPartSize,
|
|
TAG_TOKEN_DYNAMIC);
|
|
if (AccessToken->DynamicPart == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Quit;
|
|
}
|
|
|
|
/* Unused memory in the dynamic area */
|
|
AccessToken->DynamicAvailable = 0;
|
|
|
|
/*
|
|
* Assign the primary group to the token
|
|
* and put it in the dynamic part as well.
|
|
*/
|
|
EndMem = (PVOID)AccessToken->DynamicPart;
|
|
AccessToken->PrimaryGroup = EndMem;
|
|
RtlCopySid(RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid),
|
|
EndMem,
|
|
AccessToken->UserAndGroups[PrimaryGroupIndex].Sid);
|
|
AccessToken->DefaultOwnerIndex = Token->DefaultOwnerIndex;
|
|
EndMem = (PVOID)((ULONG_PTR)EndMem + RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid));
|
|
|
|
/*
|
|
* The existing token has a default DACL only
|
|
* if it has an allocated dynamic part.
|
|
*/
|
|
if (Token->DynamicPart && Token->DefaultDacl)
|
|
{
|
|
AccessToken->DefaultDacl = EndMem;
|
|
|
|
RtlCopyMemory(EndMem,
|
|
Token->DefaultDacl,
|
|
Token->DefaultDacl->AclSize);
|
|
}
|
|
|
|
/*
|
|
* Now figure out what does the caller
|
|
* want with the privileges.
|
|
*/
|
|
if (PrivilegeFlags & DISABLE_MAX_PRIVILEGE)
|
|
{
|
|
/*
|
|
* The caller wants them disabled, cache this request
|
|
* for later operations.
|
|
*/
|
|
WantPrivilegesDisabled = TRUE;
|
|
}
|
|
|
|
if (PrivilegeFlags & SANDBOX_INERT)
|
|
{
|
|
/* The caller wants an inert token, store the TOKEN_SANDBOX_INERT flag now */
|
|
AccessToken->TokenFlags |= TOKEN_SANDBOX_INERT;
|
|
}
|
|
|
|
/*
|
|
* Now it's time to filter the token's privileges.
|
|
* Loop all the privileges in the token.
|
|
*/
|
|
for (PrivsInToken = 0; PrivsInToken < AccessToken->PrivilegeCount; PrivsInToken++)
|
|
{
|
|
if (WantPrivilegesDisabled)
|
|
{
|
|
/*
|
|
* We got the acknowledgement that the caller wants
|
|
* to disable all the privileges so let's just do it.
|
|
* However, as per the general documentation is stated
|
|
* that only SE_CHANGE_NOTIFY_PRIVILEGE must be kept
|
|
* therefore in that case we must skip this privilege.
|
|
*/
|
|
if (AccessToken->Privileges[PrivsInToken].Luid.LowPart == SE_CHANGE_NOTIFY_PRIVILEGE)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* The act of disabling privileges actually means
|
|
* "deleting" them from the access token entirely.
|
|
* First we must disable them so that we can update
|
|
* token flags accordingly.
|
|
*/
|
|
AccessToken->Privileges[PrivsInToken].Attributes &= ~SE_PRIVILEGE_ENABLED;
|
|
SepUpdateSinglePrivilegeFlagToken(AccessToken, PrivsInToken);
|
|
|
|
/* Remove the privileges now */
|
|
SepRemovePrivilegeToken(AccessToken, PrivsInToken);
|
|
PrivsInToken--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PrivilegesToBeDeleted != NULL)
|
|
{
|
|
/* Loop the privileges we've got to delete */
|
|
for (PrivsInList = 0; PrivsInList < PrivilegesCount; PrivsInList++)
|
|
{
|
|
/* Does this privilege exist in the token? */
|
|
if (RtlEqualLuid(&AccessToken->Privileges[PrivsInToken].Luid,
|
|
&PrivilegesToBeDeleted[PrivsInList].Luid))
|
|
{
|
|
/* Mark that we found it */
|
|
FoundPrivilege = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Did we find the privilege? */
|
|
if (PrivsInList == PrivilegesCount)
|
|
{
|
|
/* We didn't, continue with next one */
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we have found the target privilege in the token
|
|
* based on the privileges list given by the caller
|
|
* then begin deleting it.
|
|
*/
|
|
if (FoundPrivilege)
|
|
{
|
|
/* Disable the privilege and update the flags */
|
|
AccessToken->Privileges[PrivsInToken].Attributes &= ~SE_PRIVILEGE_ENABLED;
|
|
SepUpdateSinglePrivilegeFlagToken(AccessToken, PrivsInToken);
|
|
|
|
/* Delete the privilege */
|
|
SepRemovePrivilegeToken(AccessToken, PrivsInToken);
|
|
|
|
/*
|
|
* Adjust the index and reset the FoundPrivilege indicator
|
|
* so that we can continue with the next privilege to delete.
|
|
*/
|
|
PrivsInToken--;
|
|
FoundPrivilege = FALSE;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Loop the group SIDs that we want to disable as
|
|
* per on the request by the caller.
|
|
*/
|
|
if (SidsToBeDisabled != NULL)
|
|
{
|
|
for (GroupsInToken = 0; GroupsInToken < AccessToken->UserAndGroupCount; GroupsInToken++)
|
|
{
|
|
for (GroupsInList = 0; GroupsInList < RegularGroupsSidCount; GroupsInList++)
|
|
{
|
|
/* Does this group SID exist in the token? */
|
|
if (RtlEqualSid(&AccessToken->UserAndGroups[GroupsInToken].Sid,
|
|
&SidsToBeDisabled[GroupsInList].Sid))
|
|
{
|
|
/* Mark that we found it */
|
|
FoundGroup = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Did we find the group? */
|
|
if (GroupsInList == RegularGroupsSidCount)
|
|
{
|
|
/* We didn't, continue with next one */
|
|
continue;
|
|
}
|
|
|
|
/* If we have found the group, disable it */
|
|
if (FoundGroup)
|
|
{
|
|
/*
|
|
* If the acess token belongs to the administrators
|
|
* group and this is the target group, we must take
|
|
* away TOKEN_HAS_ADMIN_GROUP flag from the token.
|
|
*/
|
|
if (RtlEqualSid(SeAliasAdminsSid,
|
|
&AccessToken->UserAndGroups[GroupsInToken].Sid))
|
|
{
|
|
AccessToken->TokenFlags &= ~TOKEN_HAS_ADMIN_GROUP;
|
|
}
|
|
|
|
/*
|
|
* If the target group that we have found it is the
|
|
* owner then from now on it no longer is but the user.
|
|
* Therefore assign the default owner index as the user.
|
|
*/
|
|
if (AccessToken->DefaultOwnerIndex == GroupsInToken)
|
|
{
|
|
AccessToken->DefaultOwnerIndex = 0;
|
|
}
|
|
|
|
/*
|
|
* The principle of disabling a group SID is by
|
|
* taking away SE_GROUP_ENABLED_BY_DEFAULT and
|
|
* SE_GROUP_ENABLED attributes and assign
|
|
* SE_GROUP_USE_FOR_DENY_ONLY. This renders
|
|
* SID a "Deny only" SID.
|
|
*/
|
|
AccessToken->UserAndGroups[GroupsInToken].Attributes &= ~(SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT);
|
|
AccessToken->UserAndGroups[GroupsInToken].Attributes |= SE_GROUP_USE_FOR_DENY_ONLY;
|
|
|
|
/* Adjust the index and continue with the next group */
|
|
GroupsInToken--;
|
|
FoundGroup = FALSE;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We've finally filtered the token, return it to the caller */
|
|
*FilteredToken = AccessToken;
|
|
Status = STATUS_SUCCESS;
|
|
DPRINT("SepPerformTokenFiltering(): The token has been filtered!\n");
|
|
|
|
Quit:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Dereference the created token */
|
|
ObDereferenceObject(AccessToken);
|
|
}
|
|
|
|
/* Unlock the source token */
|
|
SepReleaseTokenLock(Token);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS ***********************************************************/
|
|
|
|
/**
|
|
* @brief
|
|
* Filters an access token from an existing token, making it more restricted
|
|
* than the previous one.
|
|
*
|
|
* @param[in] ExistingToken
|
|
* An existing token for filtering.
|
|
*
|
|
* @param[in] Flags
|
|
* Privilege flag options. This parameter argument influences how the token
|
|
* is filtered. Such parameter can be 0. See NtFilterToken syscall for
|
|
* more information.
|
|
*
|
|
* @param[in] SidsToDisable
|
|
* Array of SIDs to disable. Such parameter can be NULL.
|
|
*
|
|
* @param[in] PrivilegesToDelete
|
|
* Array of privileges to delete. If DISABLE_MAX_PRIVILEGE flag is specified
|
|
* in the Flags parameter, PrivilegesToDelete is ignored.
|
|
*
|
|
* @param[in] RestrictedSids
|
|
* An array of restricted SIDs for the new filtered token. Such parameter
|
|
* can be NULL.
|
|
*
|
|
* @param[out] FilteredToken
|
|
* The newly filtered token, returned to the caller.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if the function has successfully completed its
|
|
* operations and that the access token has been filtered. STATUS_INVALID_PARAMETER
|
|
* is returned if one or more of the parameter are not valid. A failure NTSTATUS code
|
|
* is returned otherwise.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
SeFilterToken(
|
|
_In_ PACCESS_TOKEN ExistingToken,
|
|
_In_ ULONG Flags,
|
|
_In_opt_ PTOKEN_GROUPS SidsToDisable,
|
|
_In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete,
|
|
_In_opt_ PTOKEN_GROUPS RestrictedSids,
|
|
_Out_ PACCESS_TOKEN *FilteredToken)
|
|
{
|
|
NTSTATUS Status;
|
|
PTOKEN AccessToken;
|
|
ULONG PrivilegesCount = 0;
|
|
ULONG SidsCount = 0;
|
|
ULONG RestrictedSidsCount = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Begin copying the counters */
|
|
if (SidsToDisable != NULL)
|
|
{
|
|
SidsCount = SidsToDisable->GroupCount;
|
|
}
|
|
|
|
if (PrivilegesToDelete != NULL)
|
|
{
|
|
PrivilegesCount = PrivilegesToDelete->PrivilegeCount;
|
|
}
|
|
|
|
if (RestrictedSids != NULL)
|
|
{
|
|
RestrictedSidsCount = RestrictedSids->GroupCount;
|
|
}
|
|
|
|
/* Call the internal API */
|
|
Status = SepPerformTokenFiltering(ExistingToken,
|
|
PrivilegesToDelete->Privileges,
|
|
SidsToDisable->Groups,
|
|
RestrictedSids->Groups,
|
|
PrivilegesCount,
|
|
SidsCount,
|
|
RestrictedSidsCount,
|
|
Flags,
|
|
KernelMode,
|
|
&AccessToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SeFilterToken(): Failed to filter the token (Status 0x%lx)\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
/* Insert the filtered token */
|
|
Status = ObInsertObject(AccessToken,
|
|
NULL,
|
|
0,
|
|
0,
|
|
NULL,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SeFilterToken(): Failed to insert the filtered token (Status 0x%lx)\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
/* Return it to the caller */
|
|
*FilteredToken = AccessToken;
|
|
return Status;
|
|
}
|
|
|
|
/* SYSTEM CALLS ***************************************************************/
|
|
|
|
/**
|
|
* @brief
|
|
* Creates an access token.
|
|
*
|
|
* @param[out] TokenHandle
|
|
* The returned created token handle to the caller.
|
|
*
|
|
* @param[in] DesiredAccess
|
|
* The desired access rights for the token that we're creating.
|
|
*
|
|
* @param[in] ObjectAttributes
|
|
* The object attributes for the token object that we're creating.
|
|
*
|
|
* @param[in] TokenType
|
|
* The type of token to assign for the newly created token.
|
|
*
|
|
* @param[in] AuthenticationId
|
|
* Authentication ID that represents the token's identity.
|
|
*
|
|
* @param[in] ExpirationTime
|
|
* Expiration time for the token. If set to -1, the token never expires.
|
|
*
|
|
* @param[in] TokenUser
|
|
* The main user entity for the token to assign.
|
|
*
|
|
* @param[in] TokenGroups
|
|
* Group list of SIDs for the token to assign.
|
|
*
|
|
* @param[in] TokenPrivileges
|
|
* Privileges for the token.
|
|
*
|
|
* @param[in] TokenOwner
|
|
* The main user that owns the newly created token.
|
|
*
|
|
* @param[in] TokenPrimaryGroup
|
|
* The primary group that represents as the main group of the token.
|
|
*
|
|
* @param[in] TokenDefaultDacl
|
|
* Discretionary access control list for the token. This limits on how
|
|
* the token can be used, accessed and used by whom.
|
|
*
|
|
* @param[in] TokenSource
|
|
* The source origin of the token who creates it.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if the function has successfully created the token.
|
|
* A failure NTSTATUS code is returned otherwise.
|
|
*/
|
|
__kernel_entry
|
|
NTSTATUS
|
|
NTAPI
|
|
NtCreateToken(
|
|
_Out_ PHANDLE TokenHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
|
|
_In_ TOKEN_TYPE TokenType,
|
|
_In_ PLUID AuthenticationId,
|
|
_In_ PLARGE_INTEGER ExpirationTime,
|
|
_In_ PTOKEN_USER TokenUser,
|
|
_In_ PTOKEN_GROUPS TokenGroups,
|
|
_In_ PTOKEN_PRIVILEGES TokenPrivileges,
|
|
_In_opt_ PTOKEN_OWNER TokenOwner,
|
|
_In_ PTOKEN_PRIMARY_GROUP TokenPrimaryGroup,
|
|
_In_opt_ PTOKEN_DEFAULT_DACL TokenDefaultDacl,
|
|
_In_ PTOKEN_SOURCE TokenSource)
|
|
{
|
|
HANDLE hToken;
|
|
KPROCESSOR_MODE PreviousMode;
|
|
ULONG PrivilegeCount, GroupCount;
|
|
PSID OwnerSid, PrimaryGroupSid;
|
|
PACL DefaultDacl;
|
|
LARGE_INTEGER LocalExpirationTime = {{0, 0}};
|
|
LUID LocalAuthenticationId;
|
|
TOKEN_SOURCE LocalTokenSource;
|
|
SECURITY_QUALITY_OF_SERVICE LocalSecurityQos;
|
|
PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL;
|
|
PSID_AND_ATTRIBUTES CapturedUser = NULL;
|
|
PSID_AND_ATTRIBUTES CapturedGroups = NULL;
|
|
PSID CapturedOwnerSid = NULL;
|
|
PSID CapturedPrimaryGroupSid = NULL;
|
|
PACL CapturedDefaultDacl = NULL;
|
|
ULONG PrivilegesLength, UserLength, GroupsLength;
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
PreviousMode = ExGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
_SEH2_TRY
|
|
{
|
|
ProbeForWriteHandle(TokenHandle);
|
|
|
|
if (ObjectAttributes != NULL)
|
|
{
|
|
ProbeForRead(ObjectAttributes,
|
|
sizeof(OBJECT_ATTRIBUTES),
|
|
sizeof(ULONG));
|
|
LocalSecurityQos = *(SECURITY_QUALITY_OF_SERVICE*)ObjectAttributes->SecurityQualityOfService;
|
|
}
|
|
|
|
ProbeForRead(AuthenticationId,
|
|
sizeof(LUID),
|
|
sizeof(ULONG));
|
|
LocalAuthenticationId = *AuthenticationId;
|
|
|
|
LocalExpirationTime = ProbeForReadLargeInteger(ExpirationTime);
|
|
|
|
ProbeForRead(TokenUser,
|
|
sizeof(TOKEN_USER),
|
|
sizeof(ULONG));
|
|
|
|
ProbeForRead(TokenGroups,
|
|
sizeof(TOKEN_GROUPS),
|
|
sizeof(ULONG));
|
|
GroupCount = TokenGroups->GroupCount;
|
|
|
|
ProbeForRead(TokenPrivileges,
|
|
sizeof(TOKEN_PRIVILEGES),
|
|
sizeof(ULONG));
|
|
PrivilegeCount = TokenPrivileges->PrivilegeCount;
|
|
|
|
if (TokenOwner != NULL)
|
|
{
|
|
ProbeForRead(TokenOwner,
|
|
sizeof(TOKEN_OWNER),
|
|
sizeof(ULONG));
|
|
OwnerSid = TokenOwner->Owner;
|
|
}
|
|
else
|
|
{
|
|
OwnerSid = NULL;
|
|
}
|
|
|
|
ProbeForRead(TokenPrimaryGroup,
|
|
sizeof(TOKEN_PRIMARY_GROUP),
|
|
sizeof(ULONG));
|
|
PrimaryGroupSid = TokenPrimaryGroup->PrimaryGroup;
|
|
|
|
if (TokenDefaultDacl != NULL)
|
|
{
|
|
ProbeForRead(TokenDefaultDacl,
|
|
sizeof(TOKEN_DEFAULT_DACL),
|
|
sizeof(ULONG));
|
|
DefaultDacl = TokenDefaultDacl->DefaultDacl;
|
|
}
|
|
else
|
|
{
|
|
DefaultDacl = NULL;
|
|
}
|
|
|
|
ProbeForRead(TokenSource,
|
|
sizeof(TOKEN_SOURCE),
|
|
sizeof(ULONG));
|
|
LocalTokenSource = *TokenSource;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
else
|
|
{
|
|
if (ObjectAttributes != NULL)
|
|
LocalSecurityQos = *(SECURITY_QUALITY_OF_SERVICE*)ObjectAttributes->SecurityQualityOfService;
|
|
LocalAuthenticationId = *AuthenticationId;
|
|
LocalExpirationTime = *ExpirationTime;
|
|
GroupCount = TokenGroups->GroupCount;
|
|
PrivilegeCount = TokenPrivileges->PrivilegeCount;
|
|
OwnerSid = TokenOwner ? TokenOwner->Owner : NULL;
|
|
PrimaryGroupSid = TokenPrimaryGroup->PrimaryGroup;
|
|
DefaultDacl = TokenDefaultDacl ? TokenDefaultDacl->DefaultDacl : NULL;
|
|
LocalTokenSource = *TokenSource;
|
|
}
|
|
|
|
/* Check token type */
|
|
if ((TokenType < TokenPrimary) ||
|
|
(TokenType > TokenImpersonation))
|
|
{
|
|
return STATUS_BAD_TOKEN_TYPE;
|
|
}
|
|
|
|
/* Check for token creation privilege */
|
|
if (!SeSinglePrivilegeCheck(SeCreateTokenPrivilege, PreviousMode))
|
|
{
|
|
return STATUS_PRIVILEGE_NOT_HELD;
|
|
}
|
|
|
|
/* Capture the user SID and attributes */
|
|
Status = SeCaptureSidAndAttributesArray(&TokenUser->User,
|
|
1,
|
|
PreviousMode,
|
|
NULL,
|
|
0,
|
|
PagedPool,
|
|
FALSE,
|
|
&CapturedUser,
|
|
&UserLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Capture the groups SID and attributes array */
|
|
Status = SeCaptureSidAndAttributesArray(&TokenGroups->Groups[0],
|
|
GroupCount,
|
|
PreviousMode,
|
|
NULL,
|
|
0,
|
|
PagedPool,
|
|
FALSE,
|
|
&CapturedGroups,
|
|
&GroupsLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Capture privileges */
|
|
Status = SeCaptureLuidAndAttributesArray(&TokenPrivileges->Privileges[0],
|
|
PrivilegeCount,
|
|
PreviousMode,
|
|
NULL,
|
|
0,
|
|
PagedPool,
|
|
FALSE,
|
|
&CapturedPrivileges,
|
|
&PrivilegesLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Capture the token owner SID */
|
|
if (TokenOwner != NULL)
|
|
{
|
|
Status = SepCaptureSid(OwnerSid,
|
|
PreviousMode,
|
|
PagedPool,
|
|
FALSE,
|
|
&CapturedOwnerSid);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
/* Capture the token primary group SID */
|
|
Status = SepCaptureSid(PrimaryGroupSid,
|
|
PreviousMode,
|
|
PagedPool,
|
|
FALSE,
|
|
&CapturedPrimaryGroupSid);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Capture DefaultDacl */
|
|
if (DefaultDacl != NULL)
|
|
{
|
|
Status = SepCaptureAcl(DefaultDacl,
|
|
PreviousMode,
|
|
NonPagedPool,
|
|
FALSE,
|
|
&CapturedDefaultDacl);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
/* Call the internal function */
|
|
Status = SepCreateToken(&hToken,
|
|
PreviousMode,
|
|
DesiredAccess,
|
|
ObjectAttributes,
|
|
TokenType,
|
|
LocalSecurityQos.ImpersonationLevel,
|
|
&LocalAuthenticationId,
|
|
&LocalExpirationTime,
|
|
CapturedUser,
|
|
GroupCount,
|
|
CapturedGroups,
|
|
GroupsLength,
|
|
PrivilegeCount,
|
|
CapturedPrivileges,
|
|
CapturedOwnerSid,
|
|
CapturedPrimaryGroupSid,
|
|
CapturedDefaultDacl,
|
|
&LocalTokenSource,
|
|
FALSE);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
_SEH2_TRY
|
|
{
|
|
*TokenHandle = hToken;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
/* Release what we captured */
|
|
SeReleaseSidAndAttributesArray(CapturedUser, PreviousMode, FALSE);
|
|
SeReleaseSidAndAttributesArray(CapturedGroups, PreviousMode, FALSE);
|
|
SeReleaseLuidAndAttributesArray(CapturedPrivileges, PreviousMode, FALSE);
|
|
SepReleaseSid(CapturedOwnerSid, PreviousMode, FALSE);
|
|
SepReleaseSid(CapturedPrimaryGroupSid, PreviousMode, FALSE);
|
|
SepReleaseAcl(CapturedDefaultDacl, PreviousMode, FALSE);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Duplicates a token.
|
|
*
|
|
* @param[in] ExistingTokenHandle
|
|
* An existing token to duplicate.
|
|
*
|
|
* @param[in] DesiredAccess
|
|
* The desired access rights for the new duplicated token.
|
|
*
|
|
* @param[in] ObjectAttributes
|
|
* Object attributes for the new duplicated token.
|
|
*
|
|
* @param[in] EffectiveOnly
|
|
* If set to TRUE, the function removes all the disabled privileges and groups
|
|
* of the token to duplicate.
|
|
*
|
|
* @param[in] TokenType
|
|
* Type of token to assign to the duplicated token.
|
|
*
|
|
* @param[out] NewTokenHandle
|
|
* The returned duplicated token handle.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS is returned if token duplication has completed successfully.
|
|
* STATUS_BAD_IMPERSONATION_LEVEL is returned if the caller erroneously wants
|
|
* to raise the impersonation level even though the conditions do not permit
|
|
* it. A failure NTSTATUS code is returned otherwise.
|
|
*
|
|
* @remarks
|
|
* Some sources claim 4th param is ImpersonationLevel, but on W2K
|
|
* this is certainly NOT true, although I can't say for sure that EffectiveOnly
|
|
* is correct either. -Gunnar
|
|
* This is true. EffectiveOnly overrides SQOS.EffectiveOnly. - IAI
|
|
* NOTE for readers: http://hex.pp.ua/nt/NtDuplicateToken.php is therefore
|
|
* wrong in that regard, while MSDN documentation is correct.
|
|
*/
|
|
_Must_inspect_result_
|
|
__kernel_entry
|
|
NTSTATUS
|
|
NTAPI
|
|
NtDuplicateToken(
|
|
_In_ HANDLE ExistingTokenHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
|
|
_In_ BOOLEAN EffectiveOnly,
|
|
_In_ TOKEN_TYPE TokenType,
|
|
_Out_ PHANDLE NewTokenHandle)
|
|
{
|
|
KPROCESSOR_MODE PreviousMode;
|
|
HANDLE hToken;
|
|
PTOKEN Token;
|
|
PTOKEN NewToken;
|
|
PSECURITY_QUALITY_OF_SERVICE CapturedSecurityQualityOfService;
|
|
BOOLEAN QoSPresent;
|
|
OBJECT_HANDLE_INFORMATION HandleInformation;
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (TokenType != TokenImpersonation &&
|
|
TokenType != TokenPrimary)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
_SEH2_TRY
|
|
{
|
|
ProbeForWriteHandle(NewTokenHandle);
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
|
|
Status = SepCaptureSecurityQualityOfService(ObjectAttributes,
|
|
PreviousMode,
|
|
PagedPool,
|
|
FALSE,
|
|
&CapturedSecurityQualityOfService,
|
|
&QoSPresent);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("NtDuplicateToken() failed to capture QoS! Status: 0x%x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = ObReferenceObjectByHandle(ExistingTokenHandle,
|
|
TOKEN_DUPLICATE,
|
|
SeTokenObjectType,
|
|
PreviousMode,
|
|
(PVOID*)&Token,
|
|
&HandleInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to reference token (Status 0x%lx)\n", Status);
|
|
SepReleaseSecurityQualityOfService(CapturedSecurityQualityOfService,
|
|
PreviousMode,
|
|
FALSE);
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* Fail, if the original token is an impersonation token and the caller
|
|
* tries to raise the impersonation level of the new token above the
|
|
* impersonation level of the original token.
|
|
*/
|
|
if (Token->TokenType == TokenImpersonation)
|
|
{
|
|
if (QoSPresent &&
|
|
CapturedSecurityQualityOfService->ImpersonationLevel >Token->ImpersonationLevel)
|
|
{
|
|
ObDereferenceObject(Token);
|
|
SepReleaseSecurityQualityOfService(CapturedSecurityQualityOfService,
|
|
PreviousMode,
|
|
FALSE);
|
|
return STATUS_BAD_IMPERSONATION_LEVEL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Fail, if a primary token is to be created from an impersonation token
|
|
* and and the impersonation level of the impersonation token is below SecurityImpersonation.
|
|
*/
|
|
if (Token->TokenType == TokenImpersonation &&
|
|
TokenType == TokenPrimary &&
|
|
Token->ImpersonationLevel < SecurityImpersonation)
|
|
{
|
|
ObDereferenceObject(Token);
|
|
SepReleaseSecurityQualityOfService(CapturedSecurityQualityOfService,
|
|
PreviousMode,
|
|
FALSE);
|
|
return STATUS_BAD_IMPERSONATION_LEVEL;
|
|
}
|
|
|
|
Status = SepDuplicateToken(Token,
|
|
ObjectAttributes,
|
|
EffectiveOnly,
|
|
TokenType,
|
|
(QoSPresent ? CapturedSecurityQualityOfService->ImpersonationLevel : SecurityAnonymous),
|
|
PreviousMode,
|
|
&NewToken);
|
|
|
|
ObDereferenceObject(Token);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = ObInsertObject(NewToken,
|
|
NULL,
|
|
(DesiredAccess ? DesiredAccess : HandleInformation.GrantedAccess),
|
|
0,
|
|
NULL,
|
|
&hToken);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
_SEH2_TRY
|
|
{
|
|
*NewTokenHandle = hToken;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
}
|
|
|
|
/* Free the captured structure */
|
|
SepReleaseSecurityQualityOfService(CapturedSecurityQualityOfService,
|
|
PreviousMode,
|
|
FALSE);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Creates an access token in a restricted form
|
|
* from the original existing token, that is, such
|
|
* action is called filtering.
|
|
*
|
|
* @param[in] ExistingTokenHandle
|
|
* A handle to an access token which is to be filtered.
|
|
*
|
|
* @param[in] Flags
|
|
* Privilege flag options. This parameter argument influences how the
|
|
* token's privileges are filtered. For further details see remarks.
|
|
*
|
|
* @param[in] SidsToDisable
|
|
* Array of SIDs to disable. The action of doing so assigns the
|
|
* SE_GROUP_USE_FOR_DENY_ONLY attribute to the respective group
|
|
* SID and takes away SE_GROUP_ENABLED and SE_GROUP_ENABLED_BY_DEFAULT.
|
|
* This parameter can be NULL. This can be a UM pointer.
|
|
*
|
|
* @param[in] PrivilegesToDelete
|
|
* Array of privileges to delete. The function will walk within this
|
|
* array to determine if the specified privileges do exist in the
|
|
* access token. Any missing privileges gets ignored. This parameter
|
|
* can be NULL. This can be a UM pointer.
|
|
*
|
|
* @param[in] RestrictedSids
|
|
* An array list of restricted groups SID to be added in the access
|
|
* token. A token that is already restricted the newly added restricted
|
|
* SIDs are redundant information in addition to the existing restricted
|
|
* SIDs in the token. This parameter can be NULL. This can be a UM pointer.
|
|
*
|
|
* @param[out] NewTokenHandle
|
|
* A new handle to the restricted (filtered) access token. This can be a
|
|
* UM pointer.
|
|
*
|
|
* @return
|
|
* Returns STATUS_SUCCESS if the routine has successfully filtered the
|
|
* access token. STATUS_INVALID_PARAMETER is returned if one or more
|
|
* parameters are not valid (see SepPerformTokenFiltering routine call
|
|
* for more information). A failure NTSTATUS code is returned otherwise.
|
|
*
|
|
* @remarks
|
|
* The Flags parameter determines the final outcome of how the privileges
|
|
* in an access token are filtered. This parameter can take these supported
|
|
* values (these can be combined):
|
|
*
|
|
* 0 -- Filter the token's privileges in the usual way. The function expects
|
|
* that the caller MUST PROVIDE a valid array list of privileges to be
|
|
* deleted (that is, PrivilegesToDelete MUSTN'T BE NULL).
|
|
*
|
|
* DISABLE_MAX_PRIVILEGE -- Disables (deletes) all the privileges except SeChangeNotifyPrivilege
|
|
* in the new access token. Bear in mind if this flag is specified
|
|
* the routine ignores PrivilegesToDelete.
|
|
*
|
|
* SANDBOX_INERT -- Stores the TOKEN_SANDBOX_INERT token flag within the access token.
|
|
*
|
|
* LUA_TOKEN -- The newly filtered access token is a LUA token. This flag is not
|
|
* supported in Windows Server 2003.
|
|
*
|
|
* WRITE_RESTRICTED -- The newly filtered token has the restricted SIDs that are
|
|
* considered only when evaluating write access onto the token.
|
|
* This value is not supported in Windows Server 2003.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtFilterToken(
|
|
_In_ HANDLE ExistingTokenHandle,
|
|
_In_ ULONG Flags,
|
|
_In_opt_ PTOKEN_GROUPS SidsToDisable,
|
|
_In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete,
|
|
_In_opt_ PTOKEN_GROUPS RestrictedSids,
|
|
_Out_ PHANDLE NewTokenHandle)
|
|
{
|
|
PTOKEN Token, FilteredToken;
|
|
HANDLE FilteredTokenHandle;
|
|
NTSTATUS Status;
|
|
KPROCESSOR_MODE PreviousMode;
|
|
OBJECT_HANDLE_INFORMATION HandleInfo;
|
|
ULONG ResultLength;
|
|
ULONG CapturedSidsCount = 0;
|
|
ULONG CapturedPrivilegesCount = 0;
|
|
ULONG CapturedRestrictedSidsCount = 0;
|
|
ULONG ProbeSize = 0;
|
|
PSID_AND_ATTRIBUTES CapturedSids = NULL;
|
|
PSID_AND_ATTRIBUTES CapturedRestrictedSids = NULL;
|
|
PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
PreviousMode = ExGetPreviousMode();
|
|
|
|
_SEH2_TRY
|
|
{
|
|
/* Probe SidsToDisable */
|
|
if (SidsToDisable != NULL)
|
|
{
|
|
/* Probe the header */
|
|
ProbeForRead(SidsToDisable, sizeof(*SidsToDisable), sizeof(ULONG));
|
|
|
|
CapturedSidsCount = SidsToDisable->GroupCount;
|
|
ProbeSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedSidsCount]);
|
|
|
|
ProbeForRead(SidsToDisable, ProbeSize, sizeof(ULONG));
|
|
}
|
|
|
|
/* Probe PrivilegesToDelete */
|
|
if (PrivilegesToDelete != NULL)
|
|
{
|
|
/* Probe the header */
|
|
ProbeForRead(PrivilegesToDelete, sizeof(*PrivilegesToDelete), sizeof(ULONG));
|
|
|
|
CapturedPrivilegesCount = PrivilegesToDelete->PrivilegeCount;
|
|
ProbeSize = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[CapturedPrivilegesCount]);
|
|
|
|
ProbeForRead(PrivilegesToDelete, ProbeSize, sizeof(ULONG));
|
|
}
|
|
|
|
/* Probe RestrictedSids */
|
|
if (RestrictedSids != NULL)
|
|
{
|
|
/* Probe the header */
|
|
ProbeForRead(RestrictedSids, sizeof(*RestrictedSids), sizeof(ULONG));
|
|
|
|
CapturedRestrictedSidsCount = RestrictedSids->GroupCount;
|
|
ProbeSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedRestrictedSidsCount]);
|
|
|
|
ProbeForRead(RestrictedSids, ProbeSize, sizeof(ULONG));
|
|
}
|
|
|
|
/* Probe the handle */
|
|
ProbeForWriteHandle(NewTokenHandle);
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
|
|
/* Reference the token */
|
|
Status = ObReferenceObjectByHandle(ExistingTokenHandle,
|
|
TOKEN_DUPLICATE,
|
|
SeTokenObjectType,
|
|
PreviousMode,
|
|
(PVOID*)&Token,
|
|
&HandleInfo);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("NtFilterToken(): Failed to reference the token (Status 0x%lx)\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
/* Capture the group SIDs */
|
|
if (SidsToDisable != NULL)
|
|
{
|
|
Status = SeCaptureSidAndAttributesArray(SidsToDisable->Groups,
|
|
CapturedSidsCount,
|
|
PreviousMode,
|
|
NULL,
|
|
0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedSids,
|
|
&ResultLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("NtFilterToken(): Failed to capture the SIDs (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
/* Capture the privileges */
|
|
if (PrivilegesToDelete != NULL)
|
|
{
|
|
Status = SeCaptureLuidAndAttributesArray(PrivilegesToDelete->Privileges,
|
|
CapturedPrivilegesCount,
|
|
PreviousMode,
|
|
NULL,
|
|
0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedPrivileges,
|
|
&ResultLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("NtFilterToken(): Failed to capture the privileges (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
/* Capture the restricted SIDs */
|
|
if (RestrictedSids != NULL)
|
|
{
|
|
Status = SeCaptureSidAndAttributesArray(RestrictedSids->Groups,
|
|
CapturedRestrictedSidsCount,
|
|
PreviousMode,
|
|
NULL,
|
|
0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedRestrictedSids,
|
|
&ResultLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("NtFilterToken(): Failed to capture the restricted SIDs (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
}
|
|
|
|
/* Call the internal API */
|
|
Status = SepPerformTokenFiltering(Token,
|
|
CapturedPrivileges,
|
|
CapturedSids,
|
|
CapturedRestrictedSids,
|
|
CapturedPrivilegesCount,
|
|
CapturedSidsCount,
|
|
CapturedRestrictedSidsCount,
|
|
Flags,
|
|
PreviousMode,
|
|
&FilteredToken);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("NtFilterToken(): Failed to filter the token (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
|
|
/* Insert the filtered token and retrieve a handle to it */
|
|
Status = ObInsertObject(FilteredToken,
|
|
NULL,
|
|
HandleInfo.GrantedAccess,
|
|
0,
|
|
NULL,
|
|
&FilteredTokenHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("NtFilterToken(): Failed to insert the filtered token (Status 0x%lx)\n", Status);
|
|
goto Quit;
|
|
}
|
|
|
|
/* And return it to the caller once we're done */
|
|
_SEH2_TRY
|
|
{
|
|
*NewTokenHandle = FilteredTokenHandle;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
Status = _SEH2_GetExceptionCode();
|
|
_SEH2_YIELD(goto Quit);
|
|
}
|
|
_SEH2_END;
|
|
|
|
Quit:
|
|
/* Dereference the token */
|
|
ObDereferenceObject(Token);
|
|
|
|
/* Release all the captured data */
|
|
if (CapturedSids != NULL)
|
|
{
|
|
SeReleaseSidAndAttributesArray(CapturedSids,
|
|
PreviousMode,
|
|
TRUE);
|
|
}
|
|
|
|
if (CapturedPrivileges != NULL)
|
|
{
|
|
SeReleaseLuidAndAttributesArray(CapturedPrivileges,
|
|
PreviousMode,
|
|
TRUE);
|
|
}
|
|
|
|
if (CapturedRestrictedSids != NULL)
|
|
{
|
|
SeReleaseSidAndAttributesArray(CapturedRestrictedSids,
|
|
PreviousMode,
|
|
TRUE);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/* EOF */
|