/* * PROJECT: ReactOS Kernel * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Security token implementation support * COPYRIGHT: Copyright David Welch * Copyright 2021-2022 George Bișoc */ /* INCLUDES *******************************************************************/ #include #define NDEBUG #include #include /* GLOBALS ********************************************************************/ POBJECT_TYPE SeTokenObjectType = NULL; TOKEN_SOURCE SeSystemTokenSource = {"*SYSTEM*", {0}}; LUID SeSystemAuthenticationId = SYSTEM_LUID; LUID SeAnonymousAuthenticationId = ANONYMOUS_LOGON_LUID; static GENERIC_MAPPING SepTokenMapping = { TOKEN_READ, TOKEN_WRITE, TOKEN_EXECUTE, TOKEN_ALL_ACCESS }; static const INFORMATION_CLASS_INFO SeTokenInformationClass[] = { /* Class 0 not used, blame MS! */ IQS_NONE, /* TokenUser */ IQS_SAME(TOKEN_USER, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE), /* TokenGroups */ IQS_SAME(TOKEN_GROUPS, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE), /* TokenPrivileges */ IQS_SAME(TOKEN_PRIVILEGES, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE), /* TokenOwner */ IQS_SAME(TOKEN_OWNER, ULONG, ICIF_QUERY | ICIF_SET | ICIF_SIZE_VARIABLE), /* TokenPrimaryGroup */ IQS_SAME(TOKEN_PRIMARY_GROUP, ULONG, ICIF_QUERY | ICIF_SET | ICIF_SIZE_VARIABLE), /* TokenDefaultDacl */ IQS_SAME(TOKEN_DEFAULT_DACL, ULONG, ICIF_QUERY | ICIF_SET | ICIF_SIZE_VARIABLE), /* TokenSource */ IQS_SAME(TOKEN_SOURCE, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE), /* TokenType */ IQS_SAME(TOKEN_TYPE, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE), /* TokenImpersonationLevel */ IQS_SAME(SECURITY_IMPERSONATION_LEVEL, ULONG, ICIF_QUERY), /* TokenStatistics */ IQS_SAME(TOKEN_STATISTICS, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE), /* TokenRestrictedSids */ IQS_SAME(TOKEN_GROUPS, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE), /* TokenSessionId */ IQS_SAME(ULONG, ULONG, ICIF_QUERY | ICIF_SET), /* TokenGroupsAndPrivileges */ IQS_SAME(TOKEN_GROUPS_AND_PRIVILEGES, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE), /* TokenSessionReference */ IQS_SAME(ULONG, ULONG, ICIF_SET), /* TokenSandBoxInert */ IQS_SAME(ULONG, ULONG, ICIF_QUERY), /* TokenAuditPolicy */ IQS_SAME(TOKEN_AUDIT_POLICY_INFORMATION, ULONG, ICIF_SET | ICIF_SET_SIZE_VARIABLE), /* TokenOrigin */ IQS_SAME(TOKEN_ORIGIN, ULONG, ICIF_QUERY | ICIF_SET), }; /* FUNCTIONS *****************************************************************/ /** * @brief * Creates a lock for the token. * * @param[in,out] Token * A token which lock has to be created. * * @return * STATUS_SUCCESS if the pool allocation and resource initialisation have * completed successfully, otherwise STATUS_INSUFFICIENT_RESOURCES on a * pool allocation failure. */ static NTSTATUS SepCreateTokenLock( _Inout_ PTOKEN Token) { PAGED_CODE(); Token->TokenLock = ExAllocatePoolWithTag(NonPagedPool, sizeof(ERESOURCE), TAG_SE_TOKEN_LOCK); if (Token->TokenLock == NULL) { DPRINT1("SepCreateTokenLock(): Failed to allocate memory!\n"); return STATUS_INSUFFICIENT_RESOURCES; } ExInitializeResourceLite(Token->TokenLock); return STATUS_SUCCESS; } /** * @brief * Deletes a lock of a token. * * @param[in,out] Token * A token which contains the lock. * * @return * Nothing. */ static VOID SepDeleteTokenLock( _Inout_ PTOKEN Token) { PAGED_CODE(); ExDeleteResourceLite(Token->TokenLock); ExFreePoolWithTag(Token->TokenLock, TAG_SE_TOKEN_LOCK); } /** * @brief * Compares the elements of SID arrays provided by tokens. * The elements that are being compared for equality are * the SIDs and their attributes. * * @param[in] SidArrayToken1 * SID array from the first token. * * @param[in] CountSidArray1 * SID count array from the first token. * * @param[in] SidArrayToken2 * SID array from the second token. * * @param[in] CountSidArray2 * SID count array from the second token. * * @return * Returns TRUE if the elements match from either arrays, * FALSE otherwise. */ static BOOLEAN SepCompareSidAndAttributesFromTokens( _In_ PSID_AND_ATTRIBUTES SidArrayToken1, _In_ ULONG CountSidArray1, _In_ PSID_AND_ATTRIBUTES SidArrayToken2, _In_ ULONG CountSidArray2) { ULONG FirstCount, SecondCount; PSID_AND_ATTRIBUTES FirstSidArray, SecondSidArray; PAGED_CODE(); /* Bail out if index counters provided are not equal */ if (CountSidArray1 != CountSidArray2) { DPRINT("SepCompareSidAndAttributesFromTokens(): Index counters are not the same!\n"); return FALSE; } /* Loop over the SID arrays and compare them */ for (FirstCount = 0; FirstCount < CountSidArray1; FirstCount++) { for (SecondCount = 0; SecondCount < CountSidArray2; SecondCount++) { FirstSidArray = &SidArrayToken1[FirstCount]; SecondSidArray = &SidArrayToken2[SecondCount]; if (RtlEqualSid(FirstSidArray->Sid, SecondSidArray->Sid) && FirstSidArray->Attributes == SecondSidArray->Attributes) { break; } } /* We've exhausted the array of the second token without finding this one */ if (SecondCount == CountSidArray2) { DPRINT("SepCompareSidAndAttributesFromTokens(): No matching elements could be found in either token!\n"); return FALSE; } } return TRUE; } /** * @brief * Compares the elements of privilege arrays provided by tokens. * The elements that are being compared for equality are * the privileges and their attributes. * * @param[in] PrivArrayToken1 * Privilege array from the first token. * * @param[in] CountPrivArray1 * Privilege count array from the first token. * * @param[in] PrivArrayToken2 * Privilege array from the second token. * * @param[in] CountPrivArray2 * Privilege count array from the second token. * * @return * Returns TRUE if the elements match from either arrays, * FALSE otherwise. */ static BOOLEAN SepComparePrivilegeAndAttributesFromTokens( _In_ PLUID_AND_ATTRIBUTES PrivArrayToken1, _In_ ULONG CountPrivArray1, _In_ PLUID_AND_ATTRIBUTES PrivArrayToken2, _In_ ULONG CountPrivArray2) { ULONG FirstCount, SecondCount; PLUID_AND_ATTRIBUTES FirstPrivArray, SecondPrivArray; PAGED_CODE(); /* Bail out if index counters provided are not equal */ if (CountPrivArray1 != CountPrivArray2) { DPRINT("SepComparePrivilegeAndAttributesFromTokens(): Index counters are not the same!\n"); return FALSE; } /* Loop over the privilege arrays and compare them */ for (FirstCount = 0; FirstCount < CountPrivArray1; FirstCount++) { for (SecondCount = 0; SecondCount < CountPrivArray2; SecondCount++) { FirstPrivArray = &PrivArrayToken1[FirstCount]; SecondPrivArray = &PrivArrayToken2[SecondCount]; if (RtlEqualLuid(&FirstPrivArray->Luid, &SecondPrivArray->Luid) && FirstPrivArray->Attributes == SecondPrivArray->Attributes) { break; } } /* We've exhausted the array of the second token without finding this one */ if (SecondCount == CountPrivArray2) { DPRINT("SepComparePrivilegeAndAttributesFromTokens(): No matching elements could be found in either token!\n"); return FALSE; } } return TRUE; } /** * @brief * Compares tokens if they're equal based on all the following properties. If all * of the said conditions are met then the tokens are deemed as equal. * * - Every SID that is present in either token is also present in the other one. * - Both or none of the tokens are restricted. * - If both tokens are restricted, every SID that is restricted in either token is * also restricted in the other one. * - Every privilege present in either token is also present in the other one. * * @param[in] FirstToken * The first token. * * @param[in] SecondToken * The second token. * * @param[out] Equal * The retrieved value which determines if the tokens are * equal or not. * * @return * Returns STATUS_SUCCESS. */ static NTSTATUS SepCompareTokens( _In_ PTOKEN FirstToken, _In_ PTOKEN SecondToken, _Out_ PBOOLEAN Equal) { BOOLEAN Restricted, IsEqual = FALSE; PAGED_CODE(); ASSERT(FirstToken != SecondToken); /* Lock the tokens */ SepAcquireTokenLockShared(FirstToken); SepAcquireTokenLockShared(SecondToken); /* Check if every SID that is present in either token is also present in the other one */ if (!SepCompareSidAndAttributesFromTokens(FirstToken->UserAndGroups, FirstToken->UserAndGroupCount, SecondToken->UserAndGroups, SecondToken->UserAndGroupCount)) { goto Quit; } /* Is one token restricted but the other isn't? */ Restricted = SeTokenIsRestricted(FirstToken); if (Restricted != SeTokenIsRestricted(SecondToken)) { /* If that's the case then bail out */ goto Quit; } /* * If both tokens are restricted check if every SID * that is restricted in either token is also restricted * in the other one. */ if (Restricted) { if (!SepCompareSidAndAttributesFromTokens(FirstToken->RestrictedSids, FirstToken->RestrictedSidCount, SecondToken->RestrictedSids, SecondToken->RestrictedSidCount)) { goto Quit; } } /* Check if every privilege present in either token is also present in the other one */ if (!SepComparePrivilegeAndAttributesFromTokens(FirstToken->Privileges, FirstToken->PrivilegeCount, SecondToken->Privileges, SecondToken->PrivilegeCount)) { goto Quit; } /* If we're here then the tokens are equal */ IsEqual = TRUE; DPRINT("SepCompareTokens(): Tokens are equal!\n"); Quit: /* Unlock the tokens */ SepReleaseTokenLock(SecondToken); SepReleaseTokenLock(FirstToken); *Equal = IsEqual; return STATUS_SUCCESS; } /** * @brief * Private function that impersonates the system's anonymous logon token. * The major bulk of the impersonation procedure is done here. * * @param[in] Thread * The executive thread object that is to impersonate the client. * * @param[in] PreviousMode * The access processor mode, indicating if the call is executed * in kernel or user mode. * * @return * Returns STATUS_SUCCESS if the impersonation has succeeded. * STATUS_UNSUCCESSFUL is returned if the primary token couldn't be * obtained from the current process to perform additional tasks. * STATUS_ACCESS_DENIED is returned if the process' primary token is * restricted, which for this matter we cannot impersonate onto a * restricted process. Otherwise a failure NTSTATUS code is returned. */ static NTSTATUS SepImpersonateAnonymousToken( _In_ PETHREAD Thread, _In_ KPROCESSOR_MODE PreviousMode) { NTSTATUS Status; PTOKEN TokenToImpersonate, ProcessToken; ULONG IncludeEveryoneValueData; PAGED_CODE(); /* * We must check first which kind of token * shall we assign for the thread to impersonate, * the one with Everyone Group SID or the other * without. Invoke the registry helper to * return the data value for us. */ Status = SepRegQueryHelper(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Lsa", L"EveryoneIncludesAnonymous", REG_DWORD, sizeof(IncludeEveryoneValueData), &IncludeEveryoneValueData); if (!NT_SUCCESS(Status)) { DPRINT1("SepRegQueryHelper(): Failed to query the registry value (Status 0x%lx)\n", Status); return Status; } if (IncludeEveryoneValueData == 0) { DPRINT("SepImpersonateAnonymousToken(): Assigning the token not including the Everyone Group SID...\n"); TokenToImpersonate = SeAnonymousLogonTokenNoEveryone; } else { DPRINT("SepImpersonateAnonymousToken(): Assigning the token including the Everyone Group SID...\n"); TokenToImpersonate = SeAnonymousLogonToken; } /* * Tell the object manager that we're going to use this token * object now by incrementing the reference count. */ Status = ObReferenceObjectByPointer(TokenToImpersonate, TOKEN_IMPERSONATE, SeTokenObjectType, PreviousMode); if (!NT_SUCCESS(Status)) { DPRINT1("SepImpersonateAnonymousToken(): Couldn't be able to use the token, bail out...\n"); return Status; } /* * Reference the primary token of the current process that the anonymous * logon token impersonation procedure is being performed. We'll be going * to use the process' token to figure out if the process is actually * restricted or not. */ ProcessToken = PsReferencePrimaryToken(PsGetCurrentProcess()); if (!ProcessToken) { DPRINT1("SepImpersonateAnonymousToken(): Couldn't be able to get the process' primary token, bail out...\n"); ObDereferenceObject(TokenToImpersonate); return STATUS_UNSUCCESSFUL; } /* Now, is the token from the current process restricted? */ if (SeTokenIsRestricted(ProcessToken)) { DPRINT1("SepImpersonateAnonymousToken(): The process is restricted, can't do anything. Bail out...\n"); PsDereferencePrimaryToken(ProcessToken); ObDereferenceObject(TokenToImpersonate); return STATUS_ACCESS_DENIED; } /* * Finally it's time to impersonate! But first, fast dereference the * process' primary token as we no longer need it. */ ObFastDereferenceObject(&PsGetCurrentProcess()->Token, ProcessToken); Status = PsImpersonateClient(Thread, TokenToImpersonate, TRUE, FALSE, SecurityImpersonation); if (!NT_SUCCESS(Status)) { DPRINT1("SepImpersonateAnonymousToken(): Failed to impersonate, bail out...\n"); ObDereferenceObject(TokenToImpersonate); return Status; } return Status; } /** * @brief * Updates the token's flags based upon the privilege that the token * has been granted. The flag can either be taken out or given to the token * if the attributes of the specified privilege is enabled or not. * * @param[in,out] Token * The token where the flags are to be changed. * * @param[in] Index * The index count which represents the total sum of privileges. The count in question * MUST NOT exceed the expected privileges count of the token. * * @return * Nothing. */ static VOID SepUpdateSinglePrivilegeFlagToken( _Inout_ PTOKEN Token, _In_ ULONG Index) { ULONG TokenFlag; ASSERT(Index < Token->PrivilegeCount); /* The high part of all values we are interested in is 0 */ if (Token->Privileges[Index].Luid.HighPart != 0) { return; } /* Check for certain privileges to update flags */ if (Token->Privileges[Index].Luid.LowPart == SE_CHANGE_NOTIFY_PRIVILEGE) { TokenFlag = TOKEN_HAS_TRAVERSE_PRIVILEGE; } else if (Token->Privileges[Index].Luid.LowPart == SE_BACKUP_PRIVILEGE) { TokenFlag = TOKEN_HAS_BACKUP_PRIVILEGE; } else if (Token->Privileges[Index].Luid.LowPart == SE_RESTORE_PRIVILEGE) { TokenFlag = TOKEN_HAS_RESTORE_PRIVILEGE; } else if (Token->Privileges[Index].Luid.LowPart == SE_IMPERSONATE_PRIVILEGE) { TokenFlag = TOKEN_HAS_IMPERSONATE_PRIVILEGE; } else { /* Nothing to do */ return; } /* Check if the specified privilege is enabled */ if (Token->Privileges[Index].Attributes & SE_PRIVILEGE_ENABLED) { /* It is enabled, so set the flag */ Token->TokenFlags |= TokenFlag; } else { /* Is is disabled, so remove the flag */ Token->TokenFlags &= ~TokenFlag; } } /** * @brief * Updates the token's flags based upon the privilege that the token * has been granted. The function uses the private helper, SepUpdateSinglePrivilegeFlagToken, * in order to update the flags of a token. * * @param[in,out] Token * The token where the flags are to be changed. * * @return * Nothing. */ static VOID SepUpdatePrivilegeFlagsToken( _Inout_ PTOKEN Token) { ULONG i; /* Loop all privileges */ for (i = 0; i < Token->PrivilegeCount; i++) { /* Updates the flags for this privilege */ SepUpdateSinglePrivilegeFlagToken(Token, i); } } /** * @brief * Removes a privilege from the token. * * @param[in,out] Token * The token where the privilege is to be removed. * * @param[in] Index * The index count which represents the number position of the privilege * we want to remove. * * @return * Nothing. */ static VOID SepRemovePrivilegeToken( _Inout_ PTOKEN Token, _In_ ULONG Index) { ULONG MoveCount; ASSERT(Index < Token->PrivilegeCount); /* Calculate the number of trailing privileges */ MoveCount = Token->PrivilegeCount - Index - 1; if (MoveCount != 0) { /* Move them one location ahead */ RtlMoveMemory(&Token->Privileges[Index], &Token->Privileges[Index + 1], MoveCount * sizeof(LUID_AND_ATTRIBUTES)); } /* Update privilege count */ Token->PrivilegeCount--; } /** * @brief * Removes a group from the token. * * @param[in,out] Token * The token where the group is to be removed. * * @param[in] Index * The index count which represents the number position of the group * we want to remove. * * @return * Nothing. */ static VOID SepRemoveUserGroupToken( _Inout_ PTOKEN Token, _In_ ULONG Index) { ULONG MoveCount; ASSERT(Index < Token->UserAndGroupCount); /* Calculate the number of trailing groups */ MoveCount = Token->UserAndGroupCount - Index - 1; if (MoveCount != 0) { /* Time to remove the group by moving one location ahead */ RtlMoveMemory(&Token->UserAndGroups[Index], &Token->UserAndGroups[Index + 1], MoveCount * sizeof(SID_AND_ATTRIBUTES)); } /* Remove one group count */ Token->UserAndGroupCount--; } /** * @unimplemented * @brief * Frees (de-allocates) the proxy data memory block of a token. * * @param[in,out] ProxyData * The proxy data to be freed. * * @return * Nothing. */ VOID NTAPI SepFreeProxyData( _Inout_ PVOID ProxyData) { UNIMPLEMENTED; } /** * @unimplemented * @brief * Copies the proxy data from the source into the destination of a token. * * @param[out] Dest * The destination path where the proxy data is to be copied to. * * @param[in] Src * The source path where the proxy data is be copied from. * * @return * To be added... */ NTSTATUS NTAPI SepCopyProxyData( _Out_ PVOID* Dest, _In_ PVOID Src) { UNIMPLEMENTED; return STATUS_NOT_IMPLEMENTED; } /** * @brief * Replaces the old access token of a process (pointed by the EPROCESS kernel structure) with a * new access token. The new access token must be a primary token for use. * * @param[in] Process * The process instance where its access token is about to be replaced. * * @param[in] NewAccessToken * The new token that it's going to replace the old one. * * @param[out] OldAccessToken * The returned old token that's been replaced, which the caller can do anything. * * @return * Returns STATUS_SUCCESS if the exchange operation between tokens has completed successfully. * STATUS_BAD_TOKEN_TYPE is returned if the new token is not a primary one so that we cannot * exchange it with the old one from the process. STATUS_TOKEN_ALREADY_IN_USE is returned if * both tokens aren't equal which means one of them has different properties (groups, privileges, etc.) * and as such one of them is currently in use. A failure NTSTATUS code is returned otherwise. */ NTSTATUS NTAPI SeExchangePrimaryToken( _In_ PEPROCESS Process, _In_ PACCESS_TOKEN NewAccessToken, _Out_ PACCESS_TOKEN* OldAccessToken) { PTOKEN OldToken; PTOKEN NewToken = (PTOKEN)NewAccessToken; PAGED_CODE(); if (NewToken->TokenType != TokenPrimary) return STATUS_BAD_TOKEN_TYPE; if (NewToken->TokenInUse) { BOOLEAN IsEqual; NTSTATUS Status; /* Maybe we're trying to set the same token */ OldToken = PsReferencePrimaryToken(Process); if (OldToken == NewToken) { /* So it's a nop. */ *OldAccessToken = OldToken; return STATUS_SUCCESS; } Status = SepCompareTokens(OldToken, NewToken, &IsEqual); if (!NT_SUCCESS(Status)) { PsDereferencePrimaryToken(OldToken); *OldAccessToken = NULL; return Status; } if (!IsEqual) { PsDereferencePrimaryToken(OldToken); *OldAccessToken = NULL; return STATUS_TOKEN_ALREADY_IN_USE; } /* Silently return STATUS_SUCCESS but do not set the new token, * as it's already in use elsewhere. */ *OldAccessToken = OldToken; return STATUS_SUCCESS; } /* Lock the new token */ SepAcquireTokenLockExclusive(NewToken); /* Mark new token in use */ NewToken->TokenInUse = TRUE; /* Set the session ID for the new token */ NewToken->SessionId = MmGetSessionId(Process); /* Unlock the new token */ SepReleaseTokenLock(NewToken); /* Reference the new token */ ObReferenceObject(NewToken); /* Replace the old with the new */ OldToken = ObFastReplaceObject(&Process->Token, NewToken); /* Lock the old token */ SepAcquireTokenLockExclusive(OldToken); /* Mark the old token as free */ OldToken->TokenInUse = FALSE; /* Unlock the old token */ SepReleaseTokenLock(OldToken); *OldAccessToken = (PACCESS_TOKEN)OldToken; return STATUS_SUCCESS; } /** * @brief * Removes the primary token of a process. * * @param[in,out] Process * The process instance with the access token to be removed. * * @return * Nothing. */ VOID NTAPI SeDeassignPrimaryToken( _Inout_ PEPROCESS Process) { PTOKEN OldToken; /* Remove the Token */ OldToken = ObFastReplaceObject(&Process->Token, NULL); /* Mark the Old Token as free */ OldToken->TokenInUse = FALSE; /* Dereference the Token */ ObDereferenceObject(OldToken); } /** * @brief * Computes the length size of a SID. * * @param[in] Count * Total count of entries that have SIDs in them (that being PSID_AND_ATTRIBUTES in this context). * * @param[in] Src * Source that points to the attributes and SID entry structure. * * @return * Returns the total length of a SID size. */ static ULONG RtlLengthSidAndAttributes( _In_ ULONG Count, _In_ PSID_AND_ATTRIBUTES Src) { ULONG i; ULONG uLength; PAGED_CODE(); uLength = Count * sizeof(SID_AND_ATTRIBUTES); for (i = 0; i < Count; i++) uLength += RtlLengthSid(Src[i].Sid); return uLength; } /** * @brief * Finds the primary group and default owner entity based on the submitted primary group instance * and an access token. * * @param[in] Token * Access token to begin the search query of primary group and default owner. * * @param[in] PrimaryGroup * A primary group SID to be used for search query, determining if user & groups of a token * and the submitted primary group do match. * * @param[in] DefaultOwner * The default owner. If specified, it's used to determine if the token belongs to the actual user, * that is, being the owner himself. * * @param[out] PrimaryGroupIndex * Returns the primary group index. * * @param[out] DefaultOwnerIndex * Returns the default owner index. * * @return * Returns STATUS_SUCCESS if the find query operation has completed successfully and that at least one * search result is requested by the caller. STATUS_INVALID_PARAMETER is returned if the caller hasn't requested * any search result. STATUS_INVALID_OWNER is returned if the specified default user owner does not match with the other * user from the token. STATUS_INVALID_PRIMARY_GROUP is returned if the specified default primary group does not match with the * other group from the token. */ static NTSTATUS SepFindPrimaryGroupAndDefaultOwner( _In_ PTOKEN Token, _In_ PSID PrimaryGroup, _In_opt_ PSID DefaultOwner, _Out_opt_ PULONG PrimaryGroupIndex, _Out_opt_ PULONG DefaultOwnerIndex) { ULONG i; /* We should return at least a search result */ if (!PrimaryGroupIndex && !DefaultOwnerIndex) return STATUS_INVALID_PARAMETER; if (PrimaryGroupIndex) { /* Initialize with an invalid index */ // Token->PrimaryGroup = NULL; *PrimaryGroupIndex = Token->UserAndGroupCount; } if (DefaultOwnerIndex) { if (DefaultOwner) { /* An owner is specified: check whether this is actually the user */ if (RtlEqualSid(Token->UserAndGroups[0].Sid, DefaultOwner)) { /* * It's the user (first element in array): set it * as the owner and stop the search for it. */ *DefaultOwnerIndex = 0; DefaultOwnerIndex = NULL; } else { /* An owner is specified: initialize with an invalid index */ *DefaultOwnerIndex = Token->UserAndGroupCount; } } else { /* * No owner specified: set the user (first element in array) * as the owner and stop the search for it. */ *DefaultOwnerIndex = 0; DefaultOwnerIndex = NULL; } } /* Validate and set the primary group and default owner indices */ for (i = 0; i < Token->UserAndGroupCount; i++) { /* Stop the search if we have found what we searched for */ if (!PrimaryGroupIndex && !DefaultOwnerIndex) break; if (DefaultOwnerIndex && DefaultOwner && RtlEqualSid(Token->UserAndGroups[i].Sid, DefaultOwner) && (Token->UserAndGroups[i].Attributes & SE_GROUP_OWNER)) { /* Owner is found, stop the search for it */ *DefaultOwnerIndex = i; DefaultOwnerIndex = NULL; } if (PrimaryGroupIndex && RtlEqualSid(Token->UserAndGroups[i].Sid, PrimaryGroup)) { /* Primary group is found, stop the search for it */ // Token->PrimaryGroup = Token->UserAndGroups[i].Sid; *PrimaryGroupIndex = i; PrimaryGroupIndex = NULL; } } if (DefaultOwnerIndex) { if (*DefaultOwnerIndex == Token->UserAndGroupCount) return STATUS_INVALID_OWNER; } if (PrimaryGroupIndex) { if (*PrimaryGroupIndex == Token->UserAndGroupCount) // if (Token->PrimaryGroup == NULL) return STATUS_INVALID_PRIMARY_GROUP; } return STATUS_SUCCESS; } /** * @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 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; Status = ObCreateObject(PreviousMode, SeTokenObjectType, ObjectAttributes, PreviousMode, NULL, TotalSize, 0, 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); 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 */ RtlCopyLuid(&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; /* Lock the source token and copy the mutable fields */ SepAcquireTokenLockExclusive(Token); AccessToken->SessionId = Token->SessionId; RtlCopyLuid(&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; } /* 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; } AccessToken->PrimaryGroup = AccessToken->UserAndGroups[PrimaryGroupIndex].Sid; AccessToken->DefaultOwnerIndex = Token->DefaultOwnerIndex; /* 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--; } } } // // NOTE: So far our dynamic area only contains // the default dacl, so this makes the following // code pretty simple. The day where it stores // other data, the code will require adaptations. // /* Now allocate the TOKEN's dynamic information area and set the data */ AccessToken->DynamicAvailable = 0; // Unused memory in the dynamic area. AccessToken->DynamicPart = NULL; if (Token->DynamicPart && Token->DefaultDacl) { AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool, Token->DefaultDacl->AclSize, TAG_TOKEN_DYNAMIC); if (AccessToken->DynamicPart == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Quit; } EndMem = (PVOID)AccessToken->DynamicPart; AccessToken->DefaultDacl = EndMem; RtlCopyMemory(AccessToken->DefaultDacl, Token->DefaultDacl, Token->DefaultDacl->AclSize); } /* Unlock the source token */ SepReleaseTokenLock(Token); /* Return the token */ *NewAccessToken = AccessToken; Status = STATUS_SUCCESS; Quit: if (!NT_SUCCESS(Status)) { /* Unlock the source token */ SepReleaseTokenLock(Token); /* Dereference the token, the delete procedure will clean it up */ ObDereferenceObject(AccessToken); } return Status; } /** * @brief * Subtracts a token in exchange of duplicating a new one. * * @param[in] ParentToken * The parent access token for duplication. * * @param[out] Token * The new duplicated token. * * @param[in] InUse * Set this to TRUE if the token is about to be used immediately after the call execution * of this function, FALSE otherwise. * * @param[in] SessionId * Session ID for the token to be assigned. * * @return * Returns STATUS_SUCCESS if token subtracting and duplication have completed successfully. * A failure NTSTATUS code is returned otherwise. */ NTSTATUS NTAPI SeSubProcessToken( _In_ PTOKEN ParentToken, _Out_ PTOKEN *Token, _In_ BOOLEAN InUse, _In_ ULONG SessionId) { PTOKEN NewToken; OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS Status; /* Initialize the attributes and duplicate it */ InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); Status = SepDuplicateToken(ParentToken, &ObjectAttributes, FALSE, TokenPrimary, ParentToken->ImpersonationLevel, KernelMode, &NewToken); if (NT_SUCCESS(Status)) { /* Insert it */ Status = ObInsertObject(NewToken, NULL, 0, 0, NULL, NULL); if (NT_SUCCESS(Status)) { /* Set the session ID */ NewToken->SessionId = SessionId; NewToken->TokenInUse = InUse; /* Return the token */ *Token = NewToken; } } /* Return status */ return Status; } /** * @brief * Checks if the token is a child of the other token * of the current process that the calling thread is invoking this function. * * @param[in] Token * An access token to determine if it's a child or not. * * @param[out] IsChild * The returned boolean result. * * @return * Returns STATUS_SUCCESS when the function finishes its operation. STATUS_UNSUCCESSFUL is * returned if primary token of the current calling process couldn't be referenced otherwise. */ NTSTATUS NTAPI SeIsTokenChild( _In_ PTOKEN Token, _Out_ PBOOLEAN IsChild) { PTOKEN ProcessToken; LUID ProcessTokenId, CallerParentId; /* Assume failure */ *IsChild = FALSE; /* Reference the process token */ ProcessToken = PsReferencePrimaryToken(PsGetCurrentProcess()); if (!ProcessToken) return STATUS_UNSUCCESSFUL; /* Get its token ID */ ProcessTokenId = ProcessToken->TokenId; /* Dereference the token */ ObFastDereferenceObject(&PsGetCurrentProcess()->Token, ProcessToken); /* Get our parent token ID */ CallerParentId = Token->ParentTokenId; /* Compare the token IDs */ if (RtlEqualLuid(&CallerParentId, &ProcessTokenId)) *IsChild = TRUE; /* Return success */ return STATUS_SUCCESS; } /** * @brief * Checks if the token is a sibling of the other token of * the current process that the calling thread is invoking this function. * * @param[in] Token * An access token to determine if it's a sibling or not. * * @param[out] IsSibling * The returned boolean result. * * @return * Returns STATUS_SUCCESS when the function finishes its operation. STATUS_UNSUCCESSFUL is * returned if primary token of the current calling process couldn't be referenced otherwise. */ NTSTATUS NTAPI SeIsTokenSibling( _In_ PTOKEN Token, _Out_ PBOOLEAN IsSibling) { PTOKEN ProcessToken; LUID ProcessParentId, ProcessAuthId; LUID CallerParentId, CallerAuthId; /* Assume failure */ *IsSibling = FALSE; /* Reference the process token */ ProcessToken = PsReferencePrimaryToken(PsGetCurrentProcess()); if (!ProcessToken) return STATUS_UNSUCCESSFUL; /* Get its parent and authentication IDs */ ProcessParentId = ProcessToken->ParentTokenId; ProcessAuthId = ProcessToken->AuthenticationId; /* Dereference the token */ ObFastDereferenceObject(&PsGetCurrentProcess()->Token, ProcessToken); /* Get our parent and authentication IDs */ CallerParentId = Token->ParentTokenId; CallerAuthId = Token->AuthenticationId; /* Compare the token IDs */ if (RtlEqualLuid(&CallerParentId, &ProcessParentId) && RtlEqualLuid(&CallerAuthId, &ProcessAuthId)) { *IsSibling = TRUE; } /* Return success */ return STATUS_SUCCESS; } /** * @brief * Copies an existing access token (technically duplicating a new one). * * @param[in] Token * Token to copy. * * @param[in] Level * Impersonation security level to assign to the newly copied token. * * @param[in] PreviousMode * Processor request level mode. * * @param[out] NewToken * The newly copied token. * * @return * Returns STATUS_SUCCESS when token copying has finished successfully. A failure * NTSTATUS code is returned otherwise. */ NTSTATUS NTAPI SeCopyClientToken( _In_ PACCESS_TOKEN Token, _In_ SECURITY_IMPERSONATION_LEVEL Level, _In_ KPROCESSOR_MODE PreviousMode, _Out_ PACCESS_TOKEN* NewToken) { NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; PAGED_CODE(); InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); Status = SepDuplicateToken(Token, &ObjectAttributes, FALSE, TokenImpersonation, Level, PreviousMode, (PTOKEN*)NewToken); return Status; } /** * @brief * Internal function that deals with access token object destruction and deletion. * The function is used solely by the object manager mechanism that handles the life * management of a token object. * * @param[in] ObjectBody * The object body that represents an access token object. * * @return * Nothing. */ VOID NTAPI SepDeleteToken( _In_ PVOID ObjectBody) { NTSTATUS Status; PTOKEN AccessToken = (PTOKEN)ObjectBody; DPRINT("SepDeleteToken()\n"); /* Remove the referenced logon session from token */ if (AccessToken->LogonSession) { Status = SepRmRemoveLogonSessionFromToken(AccessToken); if (!NT_SUCCESS(Status)) { /* Something seriously went wrong */ DPRINT1("SepDeleteToken(): Failed to remove the logon session from token (Status: 0x%lx)\n", Status); return; } } /* Dereference the logon session */ if ((AccessToken->TokenFlags & TOKEN_SESSION_NOT_REFERENCED) == 0) SepRmDereferenceLogonSession(&AccessToken->AuthenticationId); /* Delete the token lock */ if (AccessToken->TokenLock) SepDeleteTokenLock(AccessToken); /* Delete the dynamic information area */ if (AccessToken->DynamicPart) ExFreePoolWithTag(AccessToken->DynamicPart, TAG_TOKEN_DYNAMIC); } /** * @brief * Internal function that initializes critical kernel data for access * token implementation in SRM. * * @return * Nothing. */ CODE_SEG("INIT") VOID NTAPI SepInitializeTokenImplementation(VOID) { UNICODE_STRING Name; OBJECT_TYPE_INITIALIZER ObjectTypeInitializer; DPRINT("Creating Token Object Type\n"); /* Initialize the Token type */ RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer)); RtlInitUnicodeString(&Name, L"Token"); ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer); ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK; ObjectTypeInitializer.SecurityRequired = TRUE; ObjectTypeInitializer.DefaultPagedPoolCharge = sizeof(TOKEN); ObjectTypeInitializer.GenericMapping = SepTokenMapping; ObjectTypeInitializer.PoolType = PagedPool; ObjectTypeInitializer.ValidAccessMask = TOKEN_ALL_ACCESS; ObjectTypeInitializer.UseDefaultObject = TRUE; ObjectTypeInitializer.DeleteProcedure = SepDeleteToken; ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &SeTokenObjectType); } /** * @brief * Assigns a primary access token to a given process. * * @param[in] Process * Process where the token is about to be assigned. * * @param[in] Token * The token to be assigned. * * @return * Nothing. */ VOID NTAPI SeAssignPrimaryToken( _In_ PEPROCESS Process, _In_ PTOKEN Token) { PAGED_CODE(); /* Sanity checks */ ASSERT(Token->TokenType == TokenPrimary); ASSERT(!Token->TokenInUse); /* Clean any previous token */ if (Process->Token.Object) SeDeassignPrimaryToken(Process); /* Set the new token */ ObReferenceObject(Token); Token->TokenInUse = TRUE; ObInitializeFastReference(&Process->Token, Token); } /** * @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 TotalSize; 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; Status = ObCreateObject(PreviousMode, SeTokenObjectType, ObjectAttributes, PreviousMode, NULL, TotalSize, 0, 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); RtlCopyLuid(&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; RtlCopyLuid(&AccessToken->TokenSource.SourceIdentifier, &TokenSource->SourceIdentifier); RtlCopyMemory(AccessToken->TokenSource.SourceName, TokenSource->SourceName, sizeof(TokenSource->SourceName)); AccessToken->ExpirationTime = *ExpirationTime; RtlCopyLuid(&AccessToken->ModifiedId, &ModifiedId); AccessToken->TokenFlags = TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED; /* Copy and reference the logon session */ RtlCopyLuid(&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; } /* 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; } AccessToken->PrimaryGroup = AccessToken->UserAndGroups[PrimaryGroupIndex].Sid; AccessToken->DefaultOwnerIndex = DefaultOwnerIndex; /* Now allocate the TOKEN's dynamic information area and set the data */ AccessToken->DynamicAvailable = 0; // Unused memory in the dynamic area. AccessToken->DynamicPart = NULL; if (DefaultDacl != NULL) { AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool, DefaultDacl->AclSize, TAG_TOKEN_DYNAMIC); if (AccessToken->DynamicPart == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Quit; } EndMem = (PVOID)AccessToken->DynamicPart; AccessToken->DefaultDacl = EndMem; RtlCopyMemory(AccessToken->DefaultDacl, 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 * 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) { PTOKEN AccessToken; NTSTATUS Status; PVOID EndMem; 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 token we get is not garbage */ ASSERT(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; } /* Set up a filtered token object */ Status = ObCreateObject(PreviousMode, SeTokenObjectType, NULL, PreviousMode, NULL, TotalSize, 0, 0, (PVOID*)&AccessToken); if (!NT_SUCCESS(Status)) { DPRINT1("SepPerformTokenFiltering(): Failed to create the filtered token object (Status 0x%lx)\n", Status); 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)) { ObDereferenceObject(AccessToken); return Status; } /* 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 */ RtlCopyLuid(&AccessToken->TokenSource.SourceIdentifier, &Token->TokenSource.SourceIdentifier); RtlCopyMemory(AccessToken->TokenSource.SourceName, Token->TokenSource.SourceName, sizeof(Token->TokenSource.SourceName)); RtlCopyLuid(&AccessToken->AuthenticationId, &Token->AuthenticationId); RtlCopyLuid(&AccessToken->ParentTokenId, &Token->TokenId); RtlCopyLuid(&AccessToken->OriginatingLogonSession, &Token->OriginatingLogonSession); 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; } /* 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; if (PreviousMode != KernelMode) { _SEH2_TRY { RtlCopyMemory(AccessToken->Privileges, Token->Privileges, AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Quit); } _SEH2_END; } else { 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); if (PreviousMode != KernelMode) { _SEH2_TRY { Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount, Token->UserAndGroups, VariableLength, AccessToken->UserAndGroups, EndMem, &EndMem, &VariableLength); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Quit); } _SEH2_END; } else { 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); if (PreviousMode != KernelMode) { _SEH2_TRY { Status = RtlCopySidAndAttributesArray(AccessToken->RestrictedSidCount, Token->RestrictedSids, VariableLength, AccessToken->RestrictedSids, EndMem, &EndMem, &VariableLength); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Quit); } _SEH2_END; } else { 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; } } } /* 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; } /* Assign the primary group and default owner index now */ AccessToken->PrimaryGroup = AccessToken->UserAndGroups[PrimaryGroupIndex].Sid; AccessToken->DefaultOwnerIndex = Token->DefaultOwnerIndex; /* Now allocate the token's dynamic information area and set the data */ AccessToken->DynamicAvailable = 0; AccessToken->DynamicPart = NULL; if (Token->DynamicPart && Token->DefaultDacl) { AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool, Token->DefaultDacl->AclSize, TAG_TOKEN_DYNAMIC); if (AccessToken->DynamicPart == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Quit; } EndMem = (PVOID)AccessToken->DynamicPart; AccessToken->DefaultDacl = EndMem; RtlCopyMemory(AccessToken->DefaultDacl, 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; } } } /* * 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; if (PreviousMode != KernelMode) { _SEH2_TRY { RtlCopyMemory(AccessToken->RestrictedSids, RestrictedSidsIntoToken, AccessToken->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES)); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Quit); } _SEH2_END; } else { 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; } /* We've finally filtered the token, give it to the caller */ *FilteredToken = AccessToken; Status = STATUS_SUCCESS; DPRINT("SepPerformTokenFiltering(): The token has been filtered!\n"); Quit: if (!NT_SUCCESS(Status)) { /* Dereference the token */ ObDereferenceObject(AccessToken); } return Status; } /** * @brief * Creates the system process token. * * @return * Returns the system process token if the operations have * completed successfully. */ CODE_SEG("INIT") PTOKEN NTAPI SepCreateSystemProcessToken(VOID) { LUID_AND_ATTRIBUTES Privileges[25]; ULONG GroupAttributes, OwnerAttributes; SID_AND_ATTRIBUTES Groups[32]; LARGE_INTEGER Expiration; SID_AND_ATTRIBUTES UserSid; ULONG GroupsLength; PSID PrimaryGroup; OBJECT_ATTRIBUTES ObjectAttributes; PSID Owner; ULONG i; PTOKEN Token; NTSTATUS Status; /* Don't ever expire */ Expiration.QuadPart = -1; /* All groups mandatory and enabled */ GroupAttributes = SE_GROUP_ENABLED | SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT; OwnerAttributes = SE_GROUP_ENABLED | SE_GROUP_OWNER | SE_GROUP_ENABLED_BY_DEFAULT; /* User is Local System */ UserSid.Sid = SeLocalSystemSid; UserSid.Attributes = 0; /* Primary group is Local System */ PrimaryGroup = SeLocalSystemSid; /* Owner is Administrators */ Owner = SeAliasAdminsSid; /* Groups are Administrators, World, and Authenticated Users */ Groups[0].Sid = SeAliasAdminsSid; Groups[0].Attributes = OwnerAttributes; Groups[1].Sid = SeWorldSid; Groups[1].Attributes = GroupAttributes; Groups[2].Sid = SeAuthenticatedUsersSid; Groups[2].Attributes = GroupAttributes; GroupsLength = sizeof(SID_AND_ATTRIBUTES) + SeLengthSid(Groups[0].Sid) + SeLengthSid(Groups[1].Sid) + SeLengthSid(Groups[2].Sid); ASSERT(GroupsLength <= sizeof(Groups)); /* Setup the privileges */ i = 0; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeTcbPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeCreateTokenPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeTakeOwnershipPrivilege; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeCreatePagefilePrivilege; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeLockMemoryPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeAssignPrimaryTokenPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeIncreaseQuotaPrivilege; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeIncreaseBasePriorityPrivilege; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeCreatePermanentPrivilege; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeDebugPrivilege; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeAuditPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeSecurityPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeSystemEnvironmentPrivilege; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeChangeNotifyPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeBackupPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeRestorePrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeShutdownPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeLoadDriverPrivilege; Privileges[i].Attributes = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED; Privileges[i++].Luid = SeProfileSingleProcessPrivilege; Privileges[i].Attributes = 0; Privileges[i++].Luid = SeSystemtimePrivilege; ASSERT(i == 20); /* Setup the object attributes */ InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); ASSERT(SeSystemDefaultDacl != NULL); /* Create the token */ Status = SepCreateToken((PHANDLE)&Token, KernelMode, 0, &ObjectAttributes, TokenPrimary, SecurityAnonymous, &SeSystemAuthenticationId, &Expiration, &UserSid, 3, Groups, GroupsLength, 20, Privileges, Owner, PrimaryGroup, SeSystemDefaultDacl, &SeSystemTokenSource, TRUE); ASSERT(Status == STATUS_SUCCESS); /* Return the token */ return Token; } /** * @brief * Creates the anonymous logon token for the system. The difference between this * token and the other one is the inclusion of everyone SID group (being SeWorldSid). * The other token lacks such group. * * @return * Returns the system's anonymous logon token if the operations have * completed successfully. */ CODE_SEG("INIT") PTOKEN SepCreateSystemAnonymousLogonToken(VOID) { SID_AND_ATTRIBUTES Groups[32], UserSid; PSID PrimaryGroup; PTOKEN Token; ULONG GroupsLength; LARGE_INTEGER Expiration; OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS Status; /* The token never expires */ Expiration.QuadPart = -1; /* The user is the anonymous logon */ UserSid.Sid = SeAnonymousLogonSid; UserSid.Attributes = 0; /* The primary group is also the anonymous logon */ PrimaryGroup = SeAnonymousLogonSid; /* The only group for the token is the World */ Groups[0].Sid = SeWorldSid; Groups[0].Attributes = SE_GROUP_ENABLED | SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT; GroupsLength = sizeof(SID_AND_ATTRIBUTES) + SeLengthSid(Groups[0].Sid); ASSERT(GroupsLength <= sizeof(Groups)); /* Initialise the object attributes for the token */ InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); ASSERT(SeSystemAnonymousLogonDacl != NULL); /* Create token */ Status = SepCreateToken((PHANDLE)&Token, KernelMode, 0, &ObjectAttributes, TokenPrimary, SecurityAnonymous, &SeAnonymousAuthenticationId, &Expiration, &UserSid, 1, Groups, GroupsLength, 0, NULL, NULL, PrimaryGroup, SeSystemAnonymousLogonDacl, &SeSystemTokenSource, TRUE); ASSERT(Status == STATUS_SUCCESS); /* Return the anonymous logon token */ return Token; } /** * @brief * Creates the anonymous logon token for the system. This kind of token * doesn't include the everyone SID group (being SeWorldSid). * * @return * Returns the system's anonymous logon token if the operations have * completed successfully. */ CODE_SEG("INIT") PTOKEN SepCreateSystemAnonymousLogonTokenNoEveryone(VOID) { SID_AND_ATTRIBUTES UserSid; PSID PrimaryGroup; PTOKEN Token; LARGE_INTEGER Expiration; OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS Status; /* The token never expires */ Expiration.QuadPart = -1; /* The user is the anonymous logon */ UserSid.Sid = SeAnonymousLogonSid; UserSid.Attributes = 0; /* The primary group is also the anonymous logon */ PrimaryGroup = SeAnonymousLogonSid; /* Initialise the object attributes for the token */ InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); ASSERT(SeSystemAnonymousLogonDacl != NULL); /* Create token */ Status = SepCreateToken((PHANDLE)&Token, KernelMode, 0, &ObjectAttributes, TokenPrimary, SecurityAnonymous, &SeAnonymousAuthenticationId, &Expiration, &UserSid, 0, NULL, 0, 0, NULL, NULL, PrimaryGroup, SeSystemAnonymousLogonDacl, &SeSystemTokenSource, TRUE); ASSERT(Status == STATUS_SUCCESS); /* Return the anonymous (not including everyone) logon token */ return Token; } /* 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. * * @remarks * WARNING -- The caller IS RESPONSIBLE for locking the existing access token * before attempting to do any kind of filtering operation into * the token. The lock MUST BE RELEASED after this kernel routine * has finished doing its work. */ 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 token (Status 0x%lx)\n", Status); return Status; } /* Give it to the caller */ *FilteredToken = AccessToken; return Status; } /** * @brief * Queries information details about the given token to the call. The difference * between NtQueryInformationToken and this routine is that the system call has * user mode buffer data probing and additional protection checks whereas this * routine doesn't have any of these. The routine is used exclusively in kernel * mode. * * @param[in] AccessToken * An access token to be given. * * @param[in] TokenInformationClass * Token information class. * * @param[out] TokenInformation * Buffer with retrieved information. Such information is arbitrary, depending * on the requested information class. * * @return * Returns STATUS_SUCCESS if the operation to query the desired information * has completed successfully. STATUS_INSUFFICIENT_RESOURCES is returned if * pool memory allocation has failed to satisfy an operation. Otherwise * STATUS_INVALID_INFO_CLASS is returned indicating that the information * class provided is not supported by the routine. * * @remarks * Only certain information classes are not implemented in this function and * these are TokenOrigin, TokenGroupsAndPrivileges, TokenRestrictedSids and * TokenSandBoxInert. The following classes are implemented in NtQueryInformationToken * only. */ NTSTATUS NTAPI SeQueryInformationToken( _In_ PACCESS_TOKEN AccessToken, _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, _Outptr_result_buffer_(_Inexpressible_(token-dependent)) PVOID *TokenInformation) { NTSTATUS Status; PTOKEN Token = (PTOKEN)AccessToken; ULONG RequiredLength; union { PSID PSid; ULONG Ulong; } Unused; PAGED_CODE(); /* Lock the token */ SepAcquireTokenLockShared(Token); switch (TokenInformationClass) { case TokenUser: { PTOKEN_USER tu; DPRINT("SeQueryInformationToken(TokenUser)\n"); RequiredLength = sizeof(TOKEN_USER) + RtlLengthSid(Token->UserAndGroups[0].Sid); /* Allocate the output buffer */ tu = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (tu == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } Status = RtlCopySidAndAttributesArray(1, &Token->UserAndGroups[0], RequiredLength - sizeof(TOKEN_USER), &tu->User, (PSID)(tu + 1), &Unused.PSid, &Unused.Ulong); /* Return the structure */ *TokenInformation = tu; Status = STATUS_SUCCESS; break; } case TokenGroups: { PTOKEN_GROUPS tg; ULONG SidLen; PSID Sid; DPRINT("SeQueryInformationToken(TokenGroups)\n"); RequiredLength = sizeof(tg->GroupCount) + RtlLengthSidAndAttributes(Token->UserAndGroupCount - 1, &Token->UserAndGroups[1]); SidLen = RequiredLength - sizeof(tg->GroupCount) - ((Token->UserAndGroupCount - 1) * sizeof(SID_AND_ATTRIBUTES)); /* Allocate the output buffer */ tg = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (tg == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } Sid = (PSID)((ULONG_PTR)tg + sizeof(tg->GroupCount) + ((Token->UserAndGroupCount - 1) * sizeof(SID_AND_ATTRIBUTES))); tg->GroupCount = Token->UserAndGroupCount - 1; Status = RtlCopySidAndAttributesArray(Token->UserAndGroupCount - 1, &Token->UserAndGroups[1], SidLen, &tg->Groups[0], Sid, &Unused.PSid, &Unused.Ulong); /* Return the structure */ *TokenInformation = tg; Status = STATUS_SUCCESS; break; } case TokenPrivileges: { PTOKEN_PRIVILEGES tp; DPRINT("SeQueryInformationToken(TokenPrivileges)\n"); RequiredLength = sizeof(tp->PrivilegeCount) + (Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); /* Allocate the output buffer */ tp = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (tp == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } tp->PrivilegeCount = Token->PrivilegeCount; RtlCopyLuidAndAttributesArray(Token->PrivilegeCount, Token->Privileges, &tp->Privileges[0]); /* Return the structure */ *TokenInformation = tp; Status = STATUS_SUCCESS; break; } case TokenOwner: { PTOKEN_OWNER to; ULONG SidLen; DPRINT("SeQueryInformationToken(TokenOwner)\n"); SidLen = RtlLengthSid(Token->UserAndGroups[Token->DefaultOwnerIndex].Sid); RequiredLength = sizeof(TOKEN_OWNER) + SidLen; /* Allocate the output buffer */ to = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (to == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } to->Owner = (PSID)(to + 1); Status = RtlCopySid(SidLen, to->Owner, Token->UserAndGroups[Token->DefaultOwnerIndex].Sid); /* Return the structure */ *TokenInformation = to; Status = STATUS_SUCCESS; break; } case TokenPrimaryGroup: { PTOKEN_PRIMARY_GROUP tpg; ULONG SidLen; DPRINT("SeQueryInformationToken(TokenPrimaryGroup)\n"); SidLen = RtlLengthSid(Token->PrimaryGroup); RequiredLength = sizeof(TOKEN_PRIMARY_GROUP) + SidLen; /* Allocate the output buffer */ tpg = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (tpg == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } tpg->PrimaryGroup = (PSID)(tpg + 1); Status = RtlCopySid(SidLen, tpg->PrimaryGroup, Token->PrimaryGroup); /* Return the structure */ *TokenInformation = tpg; Status = STATUS_SUCCESS; break; } case TokenDefaultDacl: { PTOKEN_DEFAULT_DACL tdd; DPRINT("SeQueryInformationToken(TokenDefaultDacl)\n"); RequiredLength = sizeof(TOKEN_DEFAULT_DACL); if (Token->DefaultDacl != NULL) RequiredLength += Token->DefaultDacl->AclSize; /* Allocate the output buffer */ tdd = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (tdd == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } if (Token->DefaultDacl != NULL) { tdd->DefaultDacl = (PACL)(tdd + 1); RtlCopyMemory(tdd->DefaultDacl, Token->DefaultDacl, Token->DefaultDacl->AclSize); } else { tdd->DefaultDacl = NULL; } /* Return the structure */ *TokenInformation = tdd; Status = STATUS_SUCCESS; break; } case TokenSource: { PTOKEN_SOURCE ts; DPRINT("SeQueryInformationToken(TokenSource)\n"); RequiredLength = sizeof(TOKEN_SOURCE); /* Allocate the output buffer */ ts = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (ts == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } *ts = Token->TokenSource; /* Return the structure */ *TokenInformation = ts; Status = STATUS_SUCCESS; break; } case TokenType: { PTOKEN_TYPE tt; DPRINT("SeQueryInformationToken(TokenType)\n"); RequiredLength = sizeof(TOKEN_TYPE); /* Allocate the output buffer */ tt = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (tt == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } *tt = Token->TokenType; /* Return the structure */ *TokenInformation = tt; Status = STATUS_SUCCESS; break; } case TokenImpersonationLevel: { PSECURITY_IMPERSONATION_LEVEL sil; DPRINT("SeQueryInformationToken(TokenImpersonationLevel)\n"); RequiredLength = sizeof(SECURITY_IMPERSONATION_LEVEL); /* Fail if the token is not an impersonation token */ if (Token->TokenType != TokenImpersonation) { Status = STATUS_INVALID_INFO_CLASS; break; } /* Allocate the output buffer */ sil = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (sil == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } *sil = Token->ImpersonationLevel; /* Return the structure */ *TokenInformation = sil; Status = STATUS_SUCCESS; break; } case TokenStatistics: { PTOKEN_STATISTICS ts; DPRINT("SeQueryInformationToken(TokenStatistics)\n"); RequiredLength = sizeof(TOKEN_STATISTICS); /* Allocate the output buffer */ ts = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE); if (ts == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; break; } ts->TokenId = Token->TokenId; ts->AuthenticationId = Token->AuthenticationId; ts->ExpirationTime = Token->ExpirationTime; ts->TokenType = Token->TokenType; ts->ImpersonationLevel = Token->ImpersonationLevel; ts->DynamicCharged = Token->DynamicCharged; ts->DynamicAvailable = Token->DynamicAvailable; ts->GroupCount = Token->UserAndGroupCount - 1; ts->PrivilegeCount = Token->PrivilegeCount; ts->ModifiedId = Token->ModifiedId; /* Return the structure */ *TokenInformation = ts; Status = STATUS_SUCCESS; break; } case TokenSessionId: { DPRINT("SeQueryInformationToken(TokenSessionId)\n"); Status = SeQuerySessionIdToken(Token, (PULONG)TokenInformation); break; } default: DPRINT1("SeQueryInformationToken(%d) invalid information class\n", TokenInformationClass); Status = STATUS_INVALID_INFO_CLASS; break; } /* Release the lock of the token */ SepReleaseTokenLock(Token); return Status; } /** * @brief * Queries the session ID of an access token. * * @param[in] Token * A valid access token where the session ID has to be gathered. * * @param[out] pSessionId * The returned pointer to a session ID to the caller. * * @return * Returns STATUS_SUCCESS. */ NTSTATUS NTAPI SeQuerySessionIdToken( _In_ PACCESS_TOKEN Token, _Out_ PULONG pSessionId) { PAGED_CODE(); /* Lock the token */ SepAcquireTokenLockShared(Token); *pSessionId = ((PTOKEN)Token)->SessionId; /* Unlock the token */ SepReleaseTokenLock(Token); return STATUS_SUCCESS; } /** * @brief * Queries the authentication ID of an access token. * * @param[in] Token * A valid access token where the authentication ID has to be gathered. * * @param[out] pSessionId * The returned pointer to an authentication ID to the caller. * * @return * Returns STATUS_SUCCESS. */ NTSTATUS NTAPI SeQueryAuthenticationIdToken( _In_ PACCESS_TOKEN Token, _Out_ PLUID LogonId) { PAGED_CODE(); *LogonId = ((PTOKEN)Token)->AuthenticationId; return STATUS_SUCCESS; } /** * @brief * Gathers the security impersonation level of an access token. * * @param[in] Token * A valid access token where the impersonation level has to be gathered. * * @return * Returns the security impersonation level from a valid token. */ SECURITY_IMPERSONATION_LEVEL NTAPI SeTokenImpersonationLevel( _In_ PACCESS_TOKEN Token) { PAGED_CODE(); return ((PTOKEN)Token)->ImpersonationLevel; } /** * @brief * Gathers the token type of an access token. A token ca be either * a primary token or impersonation token. * * @param[in] Token * A valid access token where the token type has to be gathered. * * @return * Returns the token type from a valid token. */ TOKEN_TYPE NTAPI SeTokenType( _In_ PACCESS_TOKEN Token) { PAGED_CODE(); return ((PTOKEN)Token)->TokenType; } /** * @brief * Determines if a token is either an admin token or not. Such * condition is checked based upon TOKEN_HAS_ADMIN_GROUP flag, * which means if the respective access token belongs to an * administrator group or not. * * @param[in] Token * A valid access token to determine if such token is admin or not. * * @return * Returns TRUE if the token is an admin one, FALSE otherwise. */ BOOLEAN NTAPI SeTokenIsAdmin( _In_ PACCESS_TOKEN Token) { PAGED_CODE(); // NOTE: Win7+ instead really checks the list of groups in the token // (since TOKEN_HAS_ADMIN_GROUP == TOKEN_WRITE_RESTRICTED ...) return (((PTOKEN)Token)->TokenFlags & TOKEN_HAS_ADMIN_GROUP) != 0; } /** * @brief * Determines if a token is restricted or not, based upon the token * flags. * * @param[in] Token * A valid access token to determine if such token is restricted. * * @return * Returns TRUE if the token is restricted, FALSE otherwise. */ BOOLEAN NTAPI SeTokenIsRestricted( _In_ PACCESS_TOKEN Token) { PAGED_CODE(); return (((PTOKEN)Token)->TokenFlags & TOKEN_IS_RESTRICTED) != 0; } /** * @brief * Determines if a token is write restricted, that is, nobody can write anything * to it. * * @param[in] Token * A valid access token to determine if such token is write restricted. * * @return * Returns TRUE if the token is write restricted, FALSE otherwise. * * @remarks * First introduced in NT 5.1 SP2 x86 (5.1.2600.2622), absent in NT 5.2, * then finally re-introduced in Vista+. */ BOOLEAN NTAPI SeTokenIsWriteRestricted( _In_ PACCESS_TOKEN Token) { PAGED_CODE(); // NOTE: NT 5.1 SP2 x86 checks the SE_BACKUP_PRIVILEGES_CHECKED flag // while Vista+ checks the TOKEN_WRITE_RESTRICTED flag as one expects. return (((PTOKEN)Token)->TokenFlags & SE_BACKUP_PRIVILEGES_CHECKED) != 0; } /** * @brief * Ensures that client impersonation can occur by checking if the token * we're going to assign as the impersonation token can be actually impersonated * in the first place. The routine is used primarily by PsImpersonateClient. * * @param[in] ProcessToken * Token from a process. * * @param[in] TokenToImpersonate * Token that we are going to impersonate. * * @param[in] ImpersonationLevel * Security impersonation level grade. * * @return * Returns TRUE if the conditions checked are met for token impersonation, * FALSE otherwise. */ BOOLEAN NTAPI SeTokenCanImpersonate( _In_ PTOKEN ProcessToken, _In_ PTOKEN TokenToImpersonate, _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel) { BOOLEAN CanImpersonate; PAGED_CODE(); /* * SecurityAnonymous and SecurityIdentification levels do not * allow impersonation. */ if (ImpersonationLevel == SecurityAnonymous || ImpersonationLevel == SecurityIdentification) { return FALSE; } /* Time to lock our tokens */ SepAcquireTokenLockShared(ProcessToken); SepAcquireTokenLockShared(TokenToImpersonate); /* What kind of authentication ID does the token have? */ if (RtlEqualLuid(&TokenToImpersonate->AuthenticationId, &SeAnonymousAuthenticationId)) { /* * OK, it looks like the token has an anonymous * authentication. Is that token created by the system? */ if (TokenToImpersonate->TokenSource.SourceName != SeSystemTokenSource.SourceName && !RtlEqualLuid(&TokenToImpersonate->TokenSource.SourceIdentifier, &SeSystemTokenSource.SourceIdentifier)) { /* It isn't, we can't impersonate regular tokens */ DPRINT("SeTokenCanImpersonate(): Token has an anonymous authentication ID, can't impersonate!\n"); CanImpersonate = FALSE; goto Quit; } } /* Are the SID values from both tokens equal? */ if (!RtlEqualSid(ProcessToken->UserAndGroups->Sid, TokenToImpersonate->UserAndGroups->Sid)) { /* They aren't, bail out */ DPRINT("SeTokenCanImpersonate(): Tokens SIDs are not equal!\n"); CanImpersonate = FALSE; goto Quit; } /* * Make sure the tokens aren't diverged in terms of * restrictions, that is, one token is restricted * but the other one isn't. */ if (SeTokenIsRestricted(ProcessToken) != SeTokenIsRestricted(TokenToImpersonate)) { /* * One token is restricted so we cannot * continue further at this point, bail out. */ DPRINT("SeTokenCanImpersonate(): One token is restricted, can't continue!\n"); CanImpersonate = FALSE; goto Quit; } /* If we've reached that far then we can impersonate! */ DPRINT("SeTokenCanImpersonate(): We can impersonate.\n"); CanImpersonate = TRUE; Quit: /* We're done, unlock the tokens now */ SepReleaseTokenLock(ProcessToken); SepReleaseTokenLock(TokenToImpersonate); return CanImpersonate; } /* SYSTEM CALLS ***************************************************************/ /** * @brief * Queries a specific type of information in regard of an access token based upon * the information class. The calling thread must have specific access rights in order * to obtain specific information about the token. * * @param[in] TokenHandle * A handle of a token where information is to be gathered. * * @param[in] TokenInformationClass * Token information class. * * @param[out] TokenInformation * A returned output buffer with token information, which information is arbitrarily upon * the information class chosen. * * @param[in] TokenInformationLength * Length of the token information buffer, in bytes. * * @param[out] ReturnLength * If specified in the call, the function returns the total length size of the token * information buffer.. * * @return * Returns STATUS_SUCCESS if information querying has completed successfully. * STATUS_BUFFER_TOO_SMALL is returned if the information length that represents * the token information buffer is not greater than the required length. * STATUS_INVALID_HANDLE is returned if the token handle is not a valid one. * STATUS_INVALID_INFO_CLASS is returned if the information class is not a valid * one (that is, the class doesn't belong to TOKEN_INFORMATION_CLASS). A failure * NTSTATUS code is returned otherwise. */ _Must_inspect_result_ __kernel_entry NTSTATUS NTAPI NtQueryInformationToken( _In_ HANDLE TokenHandle, _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, _Out_writes_bytes_to_opt_(TokenInformationLength, *ReturnLength) PVOID TokenInformation, _In_ ULONG TokenInformationLength, _Out_ PULONG ReturnLength) { NTSTATUS Status; KPROCESSOR_MODE PreviousMode; PTOKEN Token; ULONG RequiredLength; union { PSID PSid; ULONG Ulong; } Unused; PAGED_CODE(); PreviousMode = ExGetPreviousMode(); /* Check buffers and class validity */ Status = DefaultQueryInfoBufferCheck(TokenInformationClass, SeTokenInformationClass, RTL_NUMBER_OF(SeTokenInformationClass), TokenInformation, TokenInformationLength, ReturnLength, NULL, PreviousMode, TRUE); if (!NT_SUCCESS(Status)) { DPRINT("NtQueryInformationToken() failed, Status: 0x%x\n", Status); return Status; } Status = ObReferenceObjectByHandle(TokenHandle, (TokenInformationClass == TokenSource) ? TOKEN_QUERY_SOURCE : TOKEN_QUERY, SeTokenObjectType, PreviousMode, (PVOID*)&Token, NULL); if (NT_SUCCESS(Status)) { /* Lock the token */ SepAcquireTokenLockShared(Token); switch (TokenInformationClass) { case TokenUser: { PTOKEN_USER tu = (PTOKEN_USER)TokenInformation; DPRINT("NtQueryInformationToken(TokenUser)\n"); RequiredLength = sizeof(TOKEN_USER) + RtlLengthSid(Token->UserAndGroups[0].Sid); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { Status = RtlCopySidAndAttributesArray(1, &Token->UserAndGroups[0], RequiredLength - sizeof(TOKEN_USER), &tu->User, (PSID)(tu + 1), &Unused.PSid, &Unused.Ulong); } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenGroups: { PTOKEN_GROUPS tg = (PTOKEN_GROUPS)TokenInformation; DPRINT("NtQueryInformationToken(TokenGroups)\n"); RequiredLength = sizeof(tg->GroupCount) + RtlLengthSidAndAttributes(Token->UserAndGroupCount - 1, &Token->UserAndGroups[1]); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { ULONG SidLen = RequiredLength - sizeof(tg->GroupCount) - ((Token->UserAndGroupCount - 1) * sizeof(SID_AND_ATTRIBUTES)); PSID Sid = (PSID_AND_ATTRIBUTES)((ULONG_PTR)tg + sizeof(tg->GroupCount) + ((Token->UserAndGroupCount - 1) * sizeof(SID_AND_ATTRIBUTES))); tg->GroupCount = Token->UserAndGroupCount - 1; Status = RtlCopySidAndAttributesArray(Token->UserAndGroupCount - 1, &Token->UserAndGroups[1], SidLen, &tg->Groups[0], Sid, &Unused.PSid, &Unused.Ulong); } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenPrivileges: { PTOKEN_PRIVILEGES tp = (PTOKEN_PRIVILEGES)TokenInformation; DPRINT("NtQueryInformationToken(TokenPrivileges)\n"); RequiredLength = sizeof(tp->PrivilegeCount) + (Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { tp->PrivilegeCount = Token->PrivilegeCount; RtlCopyLuidAndAttributesArray(Token->PrivilegeCount, Token->Privileges, &tp->Privileges[0]); } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenOwner: { PTOKEN_OWNER to = (PTOKEN_OWNER)TokenInformation; ULONG SidLen; DPRINT("NtQueryInformationToken(TokenOwner)\n"); SidLen = RtlLengthSid(Token->UserAndGroups[Token->DefaultOwnerIndex].Sid); RequiredLength = sizeof(TOKEN_OWNER) + SidLen; _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { to->Owner = (PSID)(to + 1); Status = RtlCopySid(SidLen, to->Owner, Token->UserAndGroups[Token->DefaultOwnerIndex].Sid); } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenPrimaryGroup: { PTOKEN_PRIMARY_GROUP tpg = (PTOKEN_PRIMARY_GROUP)TokenInformation; ULONG SidLen; DPRINT("NtQueryInformationToken(TokenPrimaryGroup)\n"); SidLen = RtlLengthSid(Token->PrimaryGroup); RequiredLength = sizeof(TOKEN_PRIMARY_GROUP) + SidLen; _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { tpg->PrimaryGroup = (PSID)(tpg + 1); Status = RtlCopySid(SidLen, tpg->PrimaryGroup, Token->PrimaryGroup); } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenDefaultDacl: { PTOKEN_DEFAULT_DACL tdd = (PTOKEN_DEFAULT_DACL)TokenInformation; DPRINT("NtQueryInformationToken(TokenDefaultDacl)\n"); RequiredLength = sizeof(TOKEN_DEFAULT_DACL); if (Token->DefaultDacl != NULL) RequiredLength += Token->DefaultDacl->AclSize; _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { if (Token->DefaultDacl != NULL) { tdd->DefaultDacl = (PACL)(tdd + 1); RtlCopyMemory(tdd->DefaultDacl, Token->DefaultDacl, Token->DefaultDacl->AclSize); } else { tdd->DefaultDacl = NULL; } } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenSource: { PTOKEN_SOURCE ts = (PTOKEN_SOURCE)TokenInformation; DPRINT("NtQueryInformationToken(TokenSource)\n"); RequiredLength = sizeof(TOKEN_SOURCE); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { *ts = Token->TokenSource; } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenType: { PTOKEN_TYPE tt = (PTOKEN_TYPE)TokenInformation; DPRINT("NtQueryInformationToken(TokenType)\n"); RequiredLength = sizeof(TOKEN_TYPE); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { *tt = Token->TokenType; } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenImpersonationLevel: { PSECURITY_IMPERSONATION_LEVEL sil = (PSECURITY_IMPERSONATION_LEVEL)TokenInformation; DPRINT("NtQueryInformationToken(TokenImpersonationLevel)\n"); /* Fail if the token is not an impersonation token */ if (Token->TokenType != TokenImpersonation) { Status = STATUS_INVALID_INFO_CLASS; break; } RequiredLength = sizeof(SECURITY_IMPERSONATION_LEVEL); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { *sil = Token->ImpersonationLevel; } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenStatistics: { PTOKEN_STATISTICS ts = (PTOKEN_STATISTICS)TokenInformation; DPRINT("NtQueryInformationToken(TokenStatistics)\n"); RequiredLength = sizeof(TOKEN_STATISTICS); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { ts->TokenId = Token->TokenId; ts->AuthenticationId = Token->AuthenticationId; ts->ExpirationTime = Token->ExpirationTime; ts->TokenType = Token->TokenType; ts->ImpersonationLevel = Token->ImpersonationLevel; ts->DynamicCharged = Token->DynamicCharged; ts->DynamicAvailable = Token->DynamicAvailable; ts->GroupCount = Token->UserAndGroupCount - 1; ts->PrivilegeCount = Token->PrivilegeCount; ts->ModifiedId = Token->ModifiedId; } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenOrigin: { PTOKEN_ORIGIN to = (PTOKEN_ORIGIN)TokenInformation; DPRINT("NtQueryInformationToken(TokenOrigin)\n"); RequiredLength = sizeof(TOKEN_ORIGIN); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { RtlCopyLuid(&to->OriginatingLogonSession, &Token->AuthenticationId); } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenGroupsAndPrivileges: DPRINT1("NtQueryInformationToken(TokenGroupsAndPrivileges) not implemented\n"); Status = STATUS_NOT_IMPLEMENTED; break; case TokenRestrictedSids: { PTOKEN_GROUPS tg = (PTOKEN_GROUPS)TokenInformation; DPRINT("NtQueryInformationToken(TokenRestrictedSids)\n"); RequiredLength = sizeof(tg->GroupCount) + RtlLengthSidAndAttributes(Token->RestrictedSidCount, Token->RestrictedSids); _SEH2_TRY { if (TokenInformationLength >= RequiredLength) { ULONG SidLen = RequiredLength - sizeof(tg->GroupCount) - (Token->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES)); PSID Sid = (PSID)((ULONG_PTR)tg + sizeof(tg->GroupCount) + (Token->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES))); tg->GroupCount = Token->RestrictedSidCount; Status = RtlCopySidAndAttributesArray(Token->RestrictedSidCount, Token->RestrictedSids, SidLen, &tg->Groups[0], Sid, &Unused.PSid, &Unused.Ulong); } else { Status = STATUS_BUFFER_TOO_SMALL; } if (ReturnLength != NULL) { *ReturnLength = RequiredLength; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; break; } case TokenSandBoxInert: DPRINT1("NtQueryInformationToken(TokenSandboxInert) not implemented\n"); Status = STATUS_NOT_IMPLEMENTED; break; case TokenSessionId: { ULONG SessionId = 0; DPRINT("NtQueryInformationToken(TokenSessionId)\n"); Status = SeQuerySessionIdToken(Token, &SessionId); if (NT_SUCCESS(Status)) { _SEH2_TRY { /* Buffer size was already verified, no need to check here again */ *(PULONG)TokenInformation = SessionId; if (ReturnLength != NULL) { *ReturnLength = sizeof(ULONG); } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; } break; } default: DPRINT1("NtQueryInformationToken(%d) invalid information class\n", TokenInformationClass); Status = STATUS_INVALID_INFO_CLASS; break; } /* Unlock and dereference the token */ SepReleaseTokenLock(Token); ObDereferenceObject(Token); } return Status; } /** * @unimplemented * @brief * Sets (modifies) some specific information in regard of an access token. The * calling thread must have specific access rights in order to modify token's * information data. * * @param[in] TokenHandle * A handle of a token where information is to be modified. * * @param[in] TokenInformationClass * Token information class. * * @param[in] TokenInformation * An arbitrary pointer to a buffer with token information to set. Such * arbitrary buffer depends on the information class chosen that the caller * wants to modify such information data of a token. * * @param[in] TokenInformationLength * Length of the token information buffer, in bytes. * * @return * Returns STATUS_SUCCESS if information setting has completed successfully. * STATUS_INFO_LENGTH_MISMATCH is returned if the information length of the * buffer is less than the required length. STATUS_INSUFFICIENT_RESOURCES is * returned if memory pool allocation has failed. STATUS_PRIVILEGE_NOT_HELD * is returned if the calling thread hasn't the required privileges to perform * the operation in question. A failure NTSTATUS code is returned otherwise. * * @remarks * The function is partly implemented, mainly TokenOrigin and TokenDefaultDacl. */ _Must_inspect_result_ __kernel_entry NTSTATUS NTAPI NtSetInformationToken( _In_ HANDLE TokenHandle, _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, _In_reads_bytes_(TokenInformationLength) PVOID TokenInformation, _In_ ULONG TokenInformationLength) { NTSTATUS Status; PTOKEN Token; KPROCESSOR_MODE PreviousMode; ULONG NeededAccess = TOKEN_ADJUST_DEFAULT; PAGED_CODE(); PreviousMode = ExGetPreviousMode(); Status = DefaultSetInfoBufferCheck(TokenInformationClass, SeTokenInformationClass, RTL_NUMBER_OF(SeTokenInformationClass), TokenInformation, TokenInformationLength, PreviousMode); if (!NT_SUCCESS(Status)) { /* Invalid buffers */ DPRINT("NtSetInformationToken() failed, Status: 0x%x\n", Status); return Status; } if (TokenInformationClass == TokenSessionId) { NeededAccess |= TOKEN_ADJUST_SESSIONID; } Status = ObReferenceObjectByHandle(TokenHandle, NeededAccess, SeTokenObjectType, PreviousMode, (PVOID*)&Token, NULL); if (NT_SUCCESS(Status)) { switch (TokenInformationClass) { case TokenOwner: { if (TokenInformationLength >= sizeof(TOKEN_OWNER)) { PTOKEN_OWNER to = (PTOKEN_OWNER)TokenInformation; PSID InputSid = NULL, CapturedSid; ULONG DefaultOwnerIndex; _SEH2_TRY { InputSid = to->Owner; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Cleanup); } _SEH2_END; Status = SepCaptureSid(InputSid, PreviousMode, PagedPool, FALSE, &CapturedSid); if (NT_SUCCESS(Status)) { /* Lock the token */ SepAcquireTokenLockExclusive(Token); /* Find the owner amongst the existing token user and groups */ Status = SepFindPrimaryGroupAndDefaultOwner(Token, NULL, CapturedSid, NULL, &DefaultOwnerIndex); if (NT_SUCCESS(Status)) { /* Found it */ Token->DefaultOwnerIndex = DefaultOwnerIndex; ExAllocateLocallyUniqueId(&Token->ModifiedId); } /* Unlock the token */ SepReleaseTokenLock(Token); SepReleaseSid(CapturedSid, PreviousMode, FALSE); } } else { Status = STATUS_INFO_LENGTH_MISMATCH; } break; } case TokenPrimaryGroup: { if (TokenInformationLength >= sizeof(TOKEN_PRIMARY_GROUP)) { PTOKEN_PRIMARY_GROUP tpg = (PTOKEN_PRIMARY_GROUP)TokenInformation; PSID InputSid = NULL, CapturedSid; ULONG PrimaryGroupIndex; _SEH2_TRY { InputSid = tpg->PrimaryGroup; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Cleanup); } _SEH2_END; Status = SepCaptureSid(InputSid, PreviousMode, PagedPool, FALSE, &CapturedSid); if (NT_SUCCESS(Status)) { /* Lock the token */ SepAcquireTokenLockExclusive(Token); /* Find the primary group amongst the existing token user and groups */ Status = SepFindPrimaryGroupAndDefaultOwner(Token, CapturedSid, NULL, &PrimaryGroupIndex, NULL); if (NT_SUCCESS(Status)) { /* Found it */ Token->PrimaryGroup = Token->UserAndGroups[PrimaryGroupIndex].Sid; ExAllocateLocallyUniqueId(&Token->ModifiedId); } /* Unlock the token */ SepReleaseTokenLock(Token); SepReleaseSid(CapturedSid, PreviousMode, FALSE); } } else { Status = STATUS_INFO_LENGTH_MISMATCH; } break; } case TokenDefaultDacl: { if (TokenInformationLength >= sizeof(TOKEN_DEFAULT_DACL)) { PTOKEN_DEFAULT_DACL tdd = (PTOKEN_DEFAULT_DACL)TokenInformation; PACL InputAcl = NULL; _SEH2_TRY { InputAcl = tdd->DefaultDacl; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Cleanup); } _SEH2_END; if (InputAcl != NULL) { PACL CapturedAcl; /* Capture and copy the dacl */ Status = SepCaptureAcl(InputAcl, PreviousMode, PagedPool, TRUE, &CapturedAcl); if (NT_SUCCESS(Status)) { ULONG DynamicLength; /* Lock the token */ SepAcquireTokenLockExclusive(Token); // // NOTE: So far our dynamic area only contains // the default dacl, so this makes the following // code pretty simple. The day where it stores // other data, the code will require adaptations. // DynamicLength = Token->DynamicAvailable; // Add here any other data length present in the dynamic area... if (Token->DefaultDacl) DynamicLength += Token->DefaultDacl->AclSize; /* Reallocate the dynamic area if it is too small */ Status = STATUS_SUCCESS; if ((DynamicLength < CapturedAcl->AclSize) || (Token->DynamicPart == NULL)) { PVOID NewDynamicPart; NewDynamicPart = ExAllocatePoolWithTag(PagedPool, CapturedAcl->AclSize, TAG_TOKEN_DYNAMIC); if (NewDynamicPart == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; } else { if (Token->DynamicPart != NULL) { // RtlCopyMemory(NewDynamicPart, Token->DynamicPart, DynamicLength); ExFreePoolWithTag(Token->DynamicPart, TAG_TOKEN_DYNAMIC); } Token->DynamicPart = NewDynamicPart; Token->DynamicAvailable = 0; } } else { Token->DynamicAvailable = DynamicLength - CapturedAcl->AclSize; } if (NT_SUCCESS(Status)) { /* Set the new dacl */ Token->DefaultDacl = (PVOID)Token->DynamicPart; RtlCopyMemory(Token->DefaultDacl, CapturedAcl, CapturedAcl->AclSize); ExAllocateLocallyUniqueId(&Token->ModifiedId); } /* Unlock the token */ SepReleaseTokenLock(Token); ExFreePoolWithTag(CapturedAcl, TAG_ACL); } } else { /* Lock the token */ SepAcquireTokenLockExclusive(Token); /* Clear the default dacl if present */ if (Token->DefaultDacl != NULL) { Token->DynamicAvailable += Token->DefaultDacl->AclSize; RtlZeroMemory(Token->DefaultDacl, Token->DefaultDacl->AclSize); Token->DefaultDacl = NULL; ExAllocateLocallyUniqueId(&Token->ModifiedId); } /* Unlock the token */ SepReleaseTokenLock(Token); } } else { Status = STATUS_INFO_LENGTH_MISMATCH; } break; } case TokenSessionId: { ULONG SessionId = 0; _SEH2_TRY { /* Buffer size was already verified, no need to check here again */ SessionId = *(PULONG)TokenInformation; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Cleanup); } _SEH2_END; /* Check for TCB privilege */ if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode)) { Status = STATUS_PRIVILEGE_NOT_HELD; break; } /* Lock the token */ SepAcquireTokenLockExclusive(Token); Token->SessionId = SessionId; ExAllocateLocallyUniqueId(&Token->ModifiedId); /* Unlock the token */ SepReleaseTokenLock(Token); break; } case TokenSessionReference: { ULONG SessionReference; _SEH2_TRY { /* Buffer size was already verified, no need to check here again */ SessionReference = *(PULONG)TokenInformation; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Cleanup); } _SEH2_END; /* Check for TCB privilege */ if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode)) { Status = STATUS_PRIVILEGE_NOT_HELD; goto Cleanup; } /* Check if it is 0 */ if (SessionReference == 0) { ULONG OldTokenFlags; /* Lock the token */ SepAcquireTokenLockExclusive(Token); /* Atomically set the flag in the token */ OldTokenFlags = RtlInterlockedSetBits(&Token->TokenFlags, TOKEN_SESSION_NOT_REFERENCED); /* * If the flag was already set, do not dereference again * the logon session. Use SessionReference as an indicator * to know whether to really dereference the session. */ if (OldTokenFlags == Token->TokenFlags) SessionReference = ULONG_MAX; /* * Otherwise if the flag was never set but just for this first time then * remove the referenced logon session data from the token and dereference * the logon session when needed. */ if (SessionReference == 0) { SepRmRemoveLogonSessionFromToken(Token); SepRmDereferenceLogonSession(&Token->AuthenticationId); } /* Unlock the token */ SepReleaseTokenLock(Token); } break; } case TokenAuditPolicy: { PTOKEN_AUDIT_POLICY_INFORMATION PolicyInformation = (PTOKEN_AUDIT_POLICY_INFORMATION)TokenInformation; SEP_AUDIT_POLICY AuditPolicy; ULONG i; _SEH2_TRY { ProbeForRead(PolicyInformation, FIELD_OFFSET(TOKEN_AUDIT_POLICY_INFORMATION, Policies[PolicyInformation->PolicyCount]), sizeof(ULONG)); /* Loop all policies in the structure */ for (i = 0; i < PolicyInformation->PolicyCount; i++) { /* Set the corresponding bits in the packed structure */ switch (PolicyInformation->Policies[i].Category) { case AuditCategorySystem: AuditPolicy.PolicyElements.System = PolicyInformation->Policies[i].Value; break; case AuditCategoryLogon: AuditPolicy.PolicyElements.Logon = PolicyInformation->Policies[i].Value; break; case AuditCategoryObjectAccess: AuditPolicy.PolicyElements.ObjectAccess = PolicyInformation->Policies[i].Value; break; case AuditCategoryPrivilegeUse: AuditPolicy.PolicyElements.PrivilegeUse = PolicyInformation->Policies[i].Value; break; case AuditCategoryDetailedTracking: AuditPolicy.PolicyElements.DetailedTracking = PolicyInformation->Policies[i].Value; break; case AuditCategoryPolicyChange: AuditPolicy.PolicyElements.PolicyChange = PolicyInformation->Policies[i].Value; break; case AuditCategoryAccountManagement: AuditPolicy.PolicyElements.AccountManagement = PolicyInformation->Policies[i].Value; break; case AuditCategoryDirectoryServiceAccess: AuditPolicy.PolicyElements.DirectoryServiceAccess = PolicyInformation->Policies[i].Value; break; case AuditCategoryAccountLogon: AuditPolicy.PolicyElements.AccountLogon = PolicyInformation->Policies[i].Value; break; } } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Cleanup); } _SEH2_END; /* Check for TCB privilege */ if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode)) { Status = STATUS_PRIVILEGE_NOT_HELD; break; } /* Lock the token */ SepAcquireTokenLockExclusive(Token); /* Set the new audit policy */ Token->AuditPolicy = AuditPolicy; ExAllocateLocallyUniqueId(&Token->ModifiedId); /* Unlock the token */ SepReleaseTokenLock(Token); break; } case TokenOrigin: { TOKEN_ORIGIN TokenOrigin; _SEH2_TRY { /* Copy the token origin */ TokenOrigin = *(PTOKEN_ORIGIN)TokenInformation; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Cleanup); } _SEH2_END; /* Check for TCB privilege */ if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode)) { Status = STATUS_PRIVILEGE_NOT_HELD; break; } /* Lock the token */ SepAcquireTokenLockExclusive(Token); /* Check if there is no token origin set yet */ if (RtlIsZeroLuid(&Token->OriginatingLogonSession)) { /* Set the token origin */ Token->OriginatingLogonSession = TokenOrigin.OriginatingLogonSession; ExAllocateLocallyUniqueId(&Token->ModifiedId); } /* Unlock the token */ SepReleaseTokenLock(Token); break; } default: { DPRINT1("Invalid TokenInformationClass: 0x%lx\n", TokenInformationClass); Status = STATUS_INVALID_INFO_CLASS; break; } } Cleanup: ObDereferenceObject(Token); } if (!NT_SUCCESS(Status)) { DPRINT1("NtSetInformationToken failed with Status 0x%lx\n", Status); } 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 * Private routine that iterates over the groups of an * access token to be adjusted as per on request by the * caller, where a group can be enabled or disabled. * * @param[in] Token * Access token where its groups are to be enabled or disabled. * * @param[in] NewState * A list of groups with new state attributes to be assigned to * the token. * * @param[in] NewStateCount * The captured count number of groups in the list. * * @param[in] ApplyChanges * If set to FALSE, the function will only iterate over the token's * groups without performing any kind of modification. If set to TRUE, * the changes will be applied immediately when the function has done * looping the groups. * * @param[in] ResetToDefaultStates * The function will reset the groups in an access token to default * states if set to TRUE. In such scenario the function ignores * NewState outright. Otherwise if set to FALSE, the function will * use NewState to assign the newly attributes to adjust the token's * groups. SE_GROUP_ENABLED_BY_DEFAULT is a flag indicator that is used * for such purpose. * * @param[out] ChangesMade * Returns TRUE if changes to token's groups have been made, otherwise * FALSE is returned. Bear in mind such changes aren't always deterministic. * See remarks for further details. * * @param[out] PreviousGroupsState * If requested by the caller, the function will return the previous state * of groups in an access token prior taking action on adjusting the token. * This is a UM (user mode) pointer and it's prone to raise exceptions * if such pointer address is not valid. * * @param[out] ChangedGroups * Returns the total number of changed groups in an access token. This * argument could also indicate the number of groups to be changed if * the calling thread hasn't chosen to apply the changes yet. A number * of 0 indicates no groups have been or to be changed because the groups' * attributes in a token are the same as the ones from NewState given by * the caller. * * @return * STATUS_SUCCESS is returned if the function has successfully completed * the operation of adjusting groups in a token. STATUS_CANT_DISABLE_MANDATORY * is returned if there was an attempt to disable a mandatory group which is * not possible. STATUS_CANT_ENABLE_DENY_ONLY is returned if there was an attempt * to enable a "use for Deny only" group which is not allowed, that is, a restricted * group. STATUS_NOT_ALL_ASSIGNED is returned if not all the groups are actually * assigned to the token. * * @remarks * Token groups adjusting can be judged to be deterministic or not based on the * NT status code value. That is, STATUS_SUCCESS indicates the function not only * has iterated over the whole groups in a token, it also has applied the changes * thoroughly without impediment and the results perfectly match with the request * desired by the caller. In this situation the condition is deemed deterministic. * In a different situation however, if the status code was STATUS_NOT_ALL_ASSIGNED, * the function would still continue looping the groups in a token and apply the * changes whenever possible where the respective groups actually exist in the * token. This kind of situation is deemed as indeterministic. * For STATUS_CANT_DISABLE_MANDATORY and STATUS_CANT_ENABLE_DENY_ONLY the scenario * is even more indeterministic as the iteration of groups comes to a halt thus * leaving all other possible groups to be adjusted. */ static NTSTATUS SepAdjustGroups( _In_ PTOKEN Token, _In_opt_ PSID_AND_ATTRIBUTES NewState, _In_ ULONG NewStateCount, _In_ BOOLEAN ApplyChanges, _In_ BOOLEAN ResetToDefaultStates, _Out_ PBOOLEAN ChangesMade, _Out_opt_ PTOKEN_GROUPS PreviousGroupsState, _Out_ PULONG ChangedGroups) { ULONG GroupsInToken, GroupsInList; ULONG ChangeCount, GroupsCount, NewAttributes; PAGED_CODE(); /* Ensure that the token we get is not plain garbage */ ASSERT(Token); /* Initialize the counters and begin the work */ *ChangesMade = FALSE; GroupsCount = 0; ChangeCount = 0; /* Begin looping all the groups in the token */ for (GroupsInToken = 0; GroupsInToken < Token->UserAndGroupCount; GroupsInToken++) { /* Does the caller want to reset groups to default states? */ if (ResetToDefaultStates) { /* * SE_GROUP_ENABLED_BY_DEFAULT is a special indicator that informs us * if a certain group has been enabled by default or not. In case * a group is enabled by default but it is not currently enabled then * at that point we must enable it back by default. For now just * assign the respective SE_GROUP_ENABLED attribute as we'll do the * eventual work later. */ if ((Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_ENABLED_BY_DEFAULT) && (Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_ENABLED) == 0) { NewAttributes = Token->UserAndGroups[GroupsInToken].Attributes |= SE_GROUP_ENABLED; } /* * Unlike the case above, a group that hasn't been enabled by * default but it's currently enabled then we must disable * it back. */ if ((Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_ENABLED_BY_DEFAULT) == 0 && (Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_ENABLED)) { NewAttributes = Token->UserAndGroups[GroupsInToken].Attributes & ~SE_GROUP_ENABLED; } } else { /* Loop the provided groups in the list then */ for (GroupsInList = 0; GroupsInList < NewStateCount; GroupsInList++) { /* Does this group exist in the token? */ if (RtlEqualSid(&Token->UserAndGroups[GroupsInToken].Sid, &NewState[GroupsInList].Sid)) { /* * This is the group that we're looking for. * However, it could be that the group is a * mandatory group which we are not allowed * and cannot disable it. */ if ((Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_MANDATORY) && (NewState[GroupsInList].Attributes & SE_GROUP_ENABLED) == 0) { /* It is mandatory, forget about this group */ DPRINT1("SepAdjustGroups(): The SID group is mandatory!\n"); return STATUS_CANT_DISABLE_MANDATORY; } /* * We've to ensure that apart the group mustn't be * mandatory, it mustn't be a restricted group as * well. That is, the group is marked with * SE_GROUP_USE_FOR_DENY_ONLY flag and no one * can enable it because it's for "deny" use only. */ if ((Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_USE_FOR_DENY_ONLY) && (NewState[GroupsInList].Attributes & SE_GROUP_ENABLED)) { /* This group is restricted, forget about it */ DPRINT1("SepAdjustGroups(): The SID group is for use deny only!\n"); return STATUS_CANT_ENABLE_DENY_ONLY; } /* Copy the attributes and stop searching */ NewAttributes = NewState[GroupsInList].Attributes; NewAttributes &= SE_GROUP_ENABLED; NewAttributes = Token->UserAndGroups[GroupsInToken].Attributes & ~SE_GROUP_ENABLED; break; } /* Did we find the specific group we wanted? */ if (GroupsInList == NewStateCount) { /* We didn't, continue with the next token's group */ continue; } } /* Count the group that we found it */ GroupsCount++; /* Does the token have the same attributes as the caller requested them? */ if (Token->UserAndGroups[GroupsInToken].Attributes != NewAttributes) { /* * No, then it's time to make some adjustment to the * token's groups. Does the caller want the previous states * of groups? */ if (PreviousGroupsState != NULL) { PreviousGroupsState->Groups[ChangeCount] = Token->UserAndGroups[GroupsInToken]; } /* Time to apply the changes now? */ if (ApplyChanges) { /* The caller gave us consent, apply and report that we made changes! */ Token->UserAndGroups[GroupsInToken].Attributes = NewAttributes; *ChangesMade = TRUE; } /* Increment the count change */ ChangeCount++; } } } /* Report the number of previous saved groups */ if (PreviousGroupsState != NULL) { PreviousGroupsState->GroupCount = ChangeCount; } /* Report the number of changed groups */ *ChangedGroups = ChangeCount; /* Did we miss some groups? */ if (!ResetToDefaultStates && (GroupsCount < NewStateCount)) { /* * If we're at this stage then we are in a situation * where the adjust changes done to token's groups is * not deterministic as the caller might have wanted * as per NewState parameter. */ DPRINT1("SepAdjustGroups(): The token hasn't all the groups assigned!\n"); return STATUS_NOT_ALL_ASSIGNED; } return STATUS_SUCCESS; } /** * @brief * Changes the list of groups by enabling or disabling them * in an access token. Unlike NtAdjustPrivilegesToken, * this API routine does not remove groups. * * @param[in] TokenHandle * Token handle where the list of groups SID are to be adjusted. * The access token must have TOKEN_ADJUST_GROUPS access right * in order to change the groups in a token. The token must also * have TOKEN_QUERY access right if the caller requests the previous * states of groups list, that is, PreviousState is not NULL. * * @param[in] ResetToDefault * If set to TRUE, the function resets the list of groups to default * enabled and disabled states. NewState is ignored in this case. * Otherwise if the parameter is set to FALSE, the function expects * a new list of groups from NewState to be adjusted within the token. * * @param[in] NewState * A new list of groups SID that the function will use it accordingly to * modify the current list of groups SID of a token. * * @param[in] BufferLength * The length size of the buffer that is pointed by the NewState parameter * argument, in bytes. * * @param[out] PreviousState * If specified, the function will return to the caller the old list of groups * SID. If this parameter is NULL, ReturnLength must also be NULL. * * @param[out] ReturnLength * If specified, the function will return the total size length of the old list * of groups SIDs, in bytes. * * @return * STATUS_SUCCESS is returned if the function has successfully adjusted the * token's groups. STATUS_INVALID_PARAMETER is returned if the caller has * submitted one or more invalid parameters, that is, the caller didn't want * to reset the groups to default state but no NewState argument list has been * provided. STATUS_BUFFER_TOO_SMALL is returned if the buffer length given * by the caller is smaller than the required length size. A failure NTSTATUS * code is returned otherwise. */ NTSTATUS NTAPI NtAdjustGroupsToken( _In_ HANDLE TokenHandle, _In_ BOOLEAN ResetToDefault, _In_ PTOKEN_GROUPS NewState, _In_ ULONG BufferLength, _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_GROUPS PreviousState, _When_(PreviousState != NULL, _Out_) PULONG ReturnLength) { PTOKEN Token; NTSTATUS Status; KPROCESSOR_MODE PreviousMode; ULONG ChangeCount, RequiredLength; ULONG CapturedCount = 0; ULONG CapturedLength = 0; ULONG NewStateSize = 0; PSID_AND_ATTRIBUTES CapturedGroups = NULL; BOOLEAN ChangesMade = FALSE; BOOLEAN LockAndReferenceAcquired = FALSE; PAGED_CODE(); /* * If the caller doesn't want to reset the groups of an * access token to default states then at least we must * expect a list of groups to be adjusted based on NewState * parameter. Otherwise bail out because the caller has * no idea what they're doing. */ if (!ResetToDefault && !NewState) { DPRINT1("NtAdjustGroupsToken(): The caller hasn't provided any list of groups to adjust!\n"); return STATUS_INVALID_PARAMETER; } PreviousMode = ExGetPreviousMode(); if (PreviousMode != KernelMode) { _SEH2_TRY { /* Probe NewState */ if (!ResetToDefault) { /* Probe the header */ ProbeForRead(NewState, sizeof(*NewState), sizeof(ULONG)); CapturedCount = NewState->GroupCount; NewStateSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedCount]); ProbeForRead(NewState, NewStateSize, sizeof(ULONG)); } if (PreviousState != NULL) { ProbeForWrite(PreviousState, BufferLength, sizeof(ULONG)); ProbeForWrite(ReturnLength, sizeof(*ReturnLength), sizeof(ULONG)); } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Return the exception code */ _SEH2_YIELD(return _SEH2_GetExceptionCode()); } _SEH2_END; } else { /* * We're calling directly from the kernel, just retrieve * the number count of captured groups outright. */ if (!ResetToDefault) { CapturedCount = NewState->GroupCount; } } /* Time to capture the NewState list */ if (!ResetToDefault) { _SEH2_TRY { Status = SeCaptureSidAndAttributesArray(NewState->Groups, CapturedCount, PreviousMode, NULL, 0, PagedPool, TRUE, &CapturedGroups, &CapturedLength); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; if (!NT_SUCCESS(Status)) { DPRINT1("NtAdjustGroupsToken(): Failed to capture the NewState list of groups (Status 0x%lx)\n", Status); return Status; } } /* Time to reference the token */ Status = ObReferenceObjectByHandle(TokenHandle, TOKEN_ADJUST_GROUPS | (PreviousState != NULL ? TOKEN_QUERY : 0), SeTokenObjectType, PreviousMode, (PVOID*)&Token, NULL); if (!NT_SUCCESS(Status)) { /* We couldn't reference the access token, bail out */ DPRINT1("NtAdjustGroupsToken(): Failed to reference the token (Status 0x%lx)\n", Status); if (CapturedGroups != NULL) { SeReleaseSidAndAttributesArray(CapturedGroups, PreviousMode, TRUE); } goto Quit; } /* Lock the token */ SepAcquireTokenLockExclusive(Token); LockAndReferenceAcquired = TRUE; /* Count the number of groups to be changed */ Status = SepAdjustGroups(Token, CapturedGroups, CapturedCount, FALSE, ResetToDefault, &ChangesMade, NULL, &ChangeCount); /* Does the caller want the previous state of groups? */ if (PreviousState != NULL) { /* Calculate the required length */ RequiredLength = FIELD_OFFSET(TOKEN_GROUPS, Groups[ChangeCount]); /* Return the required length to the caller */ _SEH2_TRY { *ReturnLength = RequiredLength; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Bail out and return the exception code */ Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Quit); } _SEH2_END; /* The buffer length provided is smaller than the required length, bail out */ if (BufferLength < RequiredLength) { Status = STATUS_BUFFER_TOO_SMALL; goto Quit; } } /* * Now it's time to apply changes. Wrap the code * in SEH as we are returning the old groups state * list to the caller since PreviousState is a * UM pointer. */ _SEH2_TRY { Status = SepAdjustGroups(Token, CapturedGroups, CapturedCount, TRUE, ResetToDefault, &ChangesMade, PreviousState, &ChangeCount); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Bail out and return the exception code */ Status = _SEH2_GetExceptionCode(); /* Force the write as we touched the token still */ ChangesMade = TRUE; _SEH2_YIELD(goto Quit); } _SEH2_END; Quit: /* Allocate a new ID for the token as we made changes */ if (ChangesMade) { ExAllocateLocallyUniqueId(&Token->ModifiedId); } /* Have we successfully acquired the lock and referenced the token before? */ if (LockAndReferenceAcquired) { /* Unlock and dereference the token */ SepReleaseTokenLock(Token); ObDereferenceObject(Token); } /* Release the captured groups */ if (CapturedGroups != NULL) { SeReleaseSidAndAttributesArray(CapturedGroups, PreviousMode, TRUE); } return Status; } /** * @brief * Removes a certain amount of privileges of a token based upon the request * by the caller. * * @param[in,out] Token * Token handle where the privileges are about to be modified. * * @param[in] DisableAllPrivileges * If set to TRUE, the function disables all the privileges. * * @param[in] NewState * A new list of privileges that the function will use it accordingly to * either disable or enable the said privileges and change them. * * @param[in] NewStateCount * The new total number count of privileges. * * @param[out] PreviousState * If specified, the function will return the previous state list of privileges. * * @param[in] ApplyChanges * If set to TRUE, the function will immediatelly apply the changes onto the * token's privileges. * * @param[out] ChangedPrivileges * The returned count number of changed privileges. * * @param[out] ChangesMade * If TRUE, the function has made changes to the token's privileges. FALSE * otherwise. * * @return * Returns STATUS_SUCCESS if the function has successfully changed the list * of privileges. STATUS_NOT_ALL_ASSIGNED is returned if not every privilege * has been changed. */ static NTSTATUS SepAdjustPrivileges( _Inout_ PTOKEN Token, _In_ BOOLEAN DisableAllPrivileges, _In_opt_ PLUID_AND_ATTRIBUTES NewState, _In_ ULONG NewStateCount, _Out_opt_ PTOKEN_PRIVILEGES PreviousState, _In_ BOOLEAN ApplyChanges, _Out_ PULONG ChangedPrivileges, _Out_ PBOOLEAN ChangesMade) { ULONG i, j, PrivilegeCount, ChangeCount, NewAttributes; PAGED_CODE(); /* Count the found privileges and those that need to be changed */ PrivilegeCount = 0; ChangeCount = 0; *ChangesMade = FALSE; /* Loop all privileges in the token */ for (i = 0; i < Token->PrivilegeCount; i++) { /* Shall all of them be disabled? */ if (DisableAllPrivileges) { /* The new attributes are the old ones, but disabled */ NewAttributes = Token->Privileges[i].Attributes & ~SE_PRIVILEGE_ENABLED; } else { /* Otherwise loop all provided privileges */ for (j = 0; j < NewStateCount; j++) { /* Check if this is the LUID we are looking for */ if (RtlEqualLuid(&Token->Privileges[i].Luid, &NewState[j].Luid)) { DPRINT("Found privilege\n"); /* Copy SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_REMOVED */ NewAttributes = NewState[j].Attributes; NewAttributes &= (SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_REMOVED); NewAttributes |= Token->Privileges[i].Attributes & ~SE_PRIVILEGE_ENABLED; /* Stop looking */ break; } } /* Check if we didn't find the privilege */ if (j == NewStateCount) { /* Continue with the token's next privilege */ continue; } } /* We found a privilege, count it */ PrivilegeCount++; /* Does the privilege need to be changed? */ if (Token->Privileges[i].Attributes != NewAttributes) { /* Does the caller want the old privileges? */ if (PreviousState != NULL) { /* Copy the old privilege */ PreviousState->Privileges[ChangeCount] = Token->Privileges[i]; } /* Does the caller want to apply the changes? */ if (ApplyChanges) { /* Shall we remove the privilege? */ if (NewAttributes & SE_PRIVILEGE_REMOVED) { /* Set the token as disabled and update flags for it */ Token->Privileges[i].Attributes &= ~SE_PRIVILEGE_ENABLED; SepUpdateSinglePrivilegeFlagToken(Token, i); /* Remove the privilege */ SepRemovePrivilegeToken(Token, i); *ChangesMade = TRUE; /* Fix the running index and continue with next one */ i--; continue; } /* Set the new attributes and update flags */ Token->Privileges[i].Attributes = NewAttributes; SepUpdateSinglePrivilegeFlagToken(Token, i); *ChangesMade = TRUE; } /* Increment the change count */ ChangeCount++; } } /* Set the number of saved privileges */ if (PreviousState != NULL) PreviousState->PrivilegeCount = ChangeCount; /* Return the number of changed privileges */ *ChangedPrivileges = ChangeCount; /* Check if we missed some */ if (!DisableAllPrivileges && (PrivilegeCount < NewStateCount)) { return STATUS_NOT_ALL_ASSIGNED; } return STATUS_SUCCESS; } /** * @brief * Removes a certain amount of privileges of a token based upon the request * by the caller. * * @param[in,out] Token * Token handle where the privileges are about to be modified. * * @param[in] DisableAllPrivileges * If set to TRUE, the function disables all the privileges. * * @param[in] NewState * A new list of privileges that the function will use it accordingly to * either disable or enable the said privileges and change them. * * @param[in] NewStateCount * The new total number count of privileges. * * @param[out] PreviousState * If specified, the function will return the previous state list of privileges. * * @param[in] ApplyChanges * If set to TRUE, the function will immediatelly apply the changes onto the * token's privileges. * * @param[out] ChangedPrivileges * The returned count number of changed privileges. * * @param[out] ChangesMade * If TRUE, the function has made changes to the token's privileges. FALSE * otherwise. * * @return * Returns STATUS_SUCCESS if the function has successfully changed the list * of privileges. STATUS_NOT_ALL_ASSIGNED is returned if not every privilege * has been changed. */ _Must_inspect_result_ __kernel_entry NTSTATUS NTAPI NtAdjustPrivilegesToken( _In_ HANDLE TokenHandle, _In_ BOOLEAN DisableAllPrivileges, _In_opt_ PTOKEN_PRIVILEGES NewState, _In_ ULONG BufferLength, _Out_writes_bytes_to_opt_(BufferLength,*ReturnLength) PTOKEN_PRIVILEGES PreviousState, _When_(PreviousState!=NULL, _Out_) PULONG ReturnLength) { NTSTATUS Status; KPROCESSOR_MODE PreviousMode; PTOKEN Token; PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL; ULONG CapturedCount = 0; ULONG CapturedLength = 0; ULONG NewStateSize = 0; ULONG ChangeCount; ULONG RequiredLength; BOOLEAN ChangesMade = FALSE; PAGED_CODE(); DPRINT("NtAdjustPrivilegesToken() called\n"); /* Fail, if we do not disable all privileges but NewState is NULL */ if (DisableAllPrivileges == FALSE && NewState == NULL) return STATUS_INVALID_PARAMETER; PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { _SEH2_TRY { /* Probe NewState */ if (DisableAllPrivileges == FALSE) { /* First probe the header */ ProbeForRead(NewState, sizeof(TOKEN_PRIVILEGES), sizeof(ULONG)); CapturedCount = NewState->PrivilegeCount; NewStateSize = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[CapturedCount]); ProbeForRead(NewState, NewStateSize, sizeof(ULONG)); } /* Probe PreviousState and ReturnLength */ if (PreviousState != NULL) { ProbeForWrite(PreviousState, BufferLength, sizeof(ULONG)); ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG)); } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Return the exception code */ _SEH2_YIELD(return _SEH2_GetExceptionCode()); } _SEH2_END; } else { /* This is kernel mode, we trust the caller */ if (DisableAllPrivileges == FALSE) CapturedCount = NewState->PrivilegeCount; } /* Do we need to capture the new state? */ if (DisableAllPrivileges == FALSE) { _SEH2_TRY { /* Capture the new state array of privileges */ Status = SeCaptureLuidAndAttributesArray(NewState->Privileges, CapturedCount, PreviousMode, NULL, 0, PagedPool, TRUE, &CapturedPrivileges, &CapturedLength); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Return the exception code */ Status = _SEH2_GetExceptionCode(); } _SEH2_END; if (!NT_SUCCESS(Status)) return Status; } /* Reference the token */ Status = ObReferenceObjectByHandle(TokenHandle, TOKEN_ADJUST_PRIVILEGES | (PreviousState != NULL ? TOKEN_QUERY : 0), SeTokenObjectType, PreviousMode, (PVOID*)&Token, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to reference token (Status 0x%lx)\n", Status); /* Release the captured privileges */ if (CapturedPrivileges != NULL) { SeReleaseLuidAndAttributesArray(CapturedPrivileges, PreviousMode, TRUE); } return Status; } /* Lock the token */ SepAcquireTokenLockExclusive(Token); /* Count the privileges that need to be changed, do not apply them yet */ Status = SepAdjustPrivileges(Token, DisableAllPrivileges, CapturedPrivileges, CapturedCount, NULL, FALSE, &ChangeCount, &ChangesMade); /* Check if the caller asked for the previous state */ if (PreviousState != NULL) { /* Calculate the required length */ RequiredLength = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[ChangeCount]); /* Try to return the required buffer length */ _SEH2_TRY { *ReturnLength = RequiredLength; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Do cleanup and return the exception code */ Status = _SEH2_GetExceptionCode(); _SEH2_YIELD(goto Cleanup); } _SEH2_END; /* Fail, if the buffer length is smaller than the required length */ if (BufferLength < RequiredLength) { Status = STATUS_BUFFER_TOO_SMALL; goto Cleanup; } } /* Now enter SEH, since we might return the old privileges */ _SEH2_TRY { /* This time apply the changes */ Status = SepAdjustPrivileges(Token, DisableAllPrivileges, CapturedPrivileges, CapturedCount, PreviousState, TRUE, &ChangeCount, &ChangesMade); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Do cleanup and return the exception code */ Status = _SEH2_GetExceptionCode(); ChangesMade = TRUE; // Force write. _SEH2_YIELD(goto Cleanup); } _SEH2_END; Cleanup: /* Touch the token if we made changes */ if (ChangesMade) ExAllocateLocallyUniqueId(&Token->ModifiedId); /* Unlock and dereference the token */ SepReleaseTokenLock(Token); ObDereferenceObject(Token); /* Release the captured privileges */ if (CapturedPrivileges != NULL) { SeReleaseLuidAndAttributesArray(CapturedPrivileges, PreviousMode, TRUE); } DPRINT ("NtAdjustPrivilegesToken() done\n"); return Status; } /** * @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 * Opens a token that is tied to a thread handle. * * @param[out] ThreadHandle * Thread handle where the token is about to be opened. * * @param[in] DesiredAccess * The request access right for the token. * * @param[in] OpenAsSelf * If set to TRUE, the access check will be made with the security context * of the process of the calling thread (opening as self). Otherwise the access * check will be made with the security context of the calling thread instead. * * @param[in] HandleAttributes * Handle attributes for the opened thread token handle. * * @param[out] TokenHandle * The opened token handle returned to the caller for use. * * @return * Returns STATUS_SUCCESS if the function has successfully opened the thread * token. STATUS_CANT_OPEN_ANONYMOUS is returned if a token has SecurityAnonymous * as impersonation level and we cannot open it. A failure NTSTATUS code is returned * otherwise. */ NTSTATUS NTAPI NtOpenThreadTokenEx( _In_ HANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess, _In_ BOOLEAN OpenAsSelf, _In_ ULONG HandleAttributes, _Out_ PHANDLE TokenHandle) { PETHREAD Thread; HANDLE hToken; PTOKEN Token, NewToken = NULL, PrimaryToken; BOOLEAN CopyOnOpen, EffectiveOnly; SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; SE_IMPERSONATION_STATE ImpersonationState; OBJECT_ATTRIBUTES ObjectAttributes; SECURITY_DESCRIPTOR SecurityDescriptor; PACL Dacl = NULL; KPROCESSOR_MODE PreviousMode; NTSTATUS Status; BOOLEAN RestoreImpersonation = FALSE; PAGED_CODE(); PreviousMode = ExGetPreviousMode(); if (PreviousMode != KernelMode) { _SEH2_TRY { 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); /* * At first open the thread token for information access and verify * that the token associated with thread is valid. */ Status = ObReferenceObjectByHandle(ThreadHandle, THREAD_QUERY_INFORMATION, PsThreadType, PreviousMode, (PVOID*)&Thread, NULL); if (!NT_SUCCESS(Status)) { return Status; } Token = PsReferenceImpersonationToken(Thread, &CopyOnOpen, &EffectiveOnly, &ImpersonationLevel); if (Token == NULL) { ObDereferenceObject(Thread); return STATUS_NO_TOKEN; } if (ImpersonationLevel == SecurityAnonymous) { PsDereferenceImpersonationToken(Token); ObDereferenceObject(Thread); return STATUS_CANT_OPEN_ANONYMOUS; } /* * Revert to self if OpenAsSelf is specified. */ if (OpenAsSelf) { RestoreImpersonation = PsDisableImpersonation(PsGetCurrentThread(), &ImpersonationState); } if (CopyOnOpen) { PrimaryToken = PsReferencePrimaryToken(Thread->ThreadsProcess); Status = SepCreateImpersonationTokenDacl(Token, PrimaryToken, &Dacl); ObFastDereferenceObject(&Thread->ThreadsProcess->Token, PrimaryToken); if (NT_SUCCESS(Status)) { if (Dacl) { Status = RtlCreateSecurityDescriptor(&SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION); if (!NT_SUCCESS(Status)) { DPRINT1("NtOpenThreadTokenEx(): Failed to create a security descriptor (Status 0x%lx)\n", Status); } Status = RtlSetDaclSecurityDescriptor(&SecurityDescriptor, TRUE, Dacl, FALSE); if (!NT_SUCCESS(Status)) { DPRINT1("NtOpenThreadTokenEx(): Failed to set a DACL to the security descriptor (Status 0x%lx)\n", Status); } } InitializeObjectAttributes(&ObjectAttributes, NULL, HandleAttributes, NULL, Dacl ? &SecurityDescriptor : NULL); Status = SepDuplicateToken(Token, &ObjectAttributes, EffectiveOnly, TokenImpersonation, ImpersonationLevel, KernelMode, &NewToken); if (!NT_SUCCESS(Status)) { DPRINT1("NtOpenThreadTokenEx(): Failed to duplicate the token (Status 0x%lx)\n", Status); } ObReferenceObject(NewToken); Status = ObInsertObject(NewToken, NULL, DesiredAccess, 0, NULL, &hToken); if (!NT_SUCCESS(Status)) { DPRINT1("NtOpenThreadTokenEx(): Failed to insert the token object (Status 0x%lx)\n", Status); } } else { DPRINT1("NtOpenThreadTokenEx(): Failed to impersonate token from DACL (Status 0x%lx)\n", Status); } } else { Status = ObOpenObjectByPointer(Token, HandleAttributes, NULL, DesiredAccess, SeTokenObjectType, PreviousMode, &hToken); if (!NT_SUCCESS(Status)) { DPRINT1("NtOpenThreadTokenEx(): Failed to open the object (Status 0x%lx)\n", Status); } } if (Dacl) ExFreePoolWithTag(Dacl, TAG_ACL); if (RestoreImpersonation) { PsRestoreImpersonation(PsGetCurrentThread(), &ImpersonationState); } ObDereferenceObject(Token); if (NT_SUCCESS(Status) && CopyOnOpen) { Status = PsImpersonateClient(Thread, NewToken, FALSE, EffectiveOnly, ImpersonationLevel); if (!NT_SUCCESS(Status)) { DPRINT1("NtOpenThreadTokenEx(): Failed to impersonate the client (Status 0x%lx)\n", Status); } } if (NewToken) ObDereferenceObject(NewToken); ObDereferenceObject(Thread); if (NT_SUCCESS(Status)) { _SEH2_TRY { *TokenHandle = hToken; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; } return Status; } /** * @brief * Opens a token that is tied to a thread handle. * * @param[out] ThreadHandle * Thread handle where the token is about to be opened. * * @param[in] DesiredAccess * The request access right for the token. * * @param[in] OpenAsSelf * If set to TRUE, the access check will be made with the security context * of the process of the calling thread (opening as self). Otherwise the access * check will be made with the security context of the calling thread instead. * * @param[out] TokenHandle * The opened token handle returned to the caller for use. * * @return * See NtOpenThreadTokenEx. */ NTSTATUS NTAPI NtOpenThreadToken( _In_ HANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess, _In_ BOOLEAN OpenAsSelf, _Out_ PHANDLE TokenHandle) { return NtOpenThreadTokenEx(ThreadHandle, DesiredAccess, OpenAsSelf, 0, TokenHandle); } /** * @brief * Compares tokens if they're equal or not. * * @param[in] FirstToken * The first token. * * @param[in] SecondToken * The second token. * * @param[out] Equal * The retrieved value which determines if the tokens are * equal or not. * * @return * Returns STATUS_SUCCESS, otherwise it returns a failure NTSTATUS code. */ NTSTATUS NTAPI NtCompareTokens( _In_ HANDLE FirstTokenHandle, _In_ HANDLE SecondTokenHandle, _Out_ PBOOLEAN Equal) { KPROCESSOR_MODE PreviousMode; PTOKEN FirstToken, SecondToken; BOOLEAN IsEqual; NTSTATUS Status; PAGED_CODE(); PreviousMode = ExGetPreviousMode(); if (PreviousMode != KernelMode) { _SEH2_TRY { ProbeForWriteBoolean(Equal); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Return the exception code */ _SEH2_YIELD(return _SEH2_GetExceptionCode()); } _SEH2_END; } Status = ObReferenceObjectByHandle(FirstTokenHandle, TOKEN_QUERY, SeTokenObjectType, PreviousMode, (PVOID*)&FirstToken, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("ObReferenceObjectByHandle() failed (Status 0x%lx)\n", Status); return Status; } Status = ObReferenceObjectByHandle(SecondTokenHandle, TOKEN_QUERY, SeTokenObjectType, PreviousMode, (PVOID*)&SecondToken, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("ObReferenceObjectByHandle() failed (Status 0x%lx)\n", Status); ObDereferenceObject(FirstToken); return Status; } if (FirstToken != SecondToken) { Status = SepCompareTokens(FirstToken, SecondToken, &IsEqual); } else { IsEqual = TRUE; } ObDereferenceObject(SecondToken); ObDereferenceObject(FirstToken); if (NT_SUCCESS(Status)) { _SEH2_TRY { *Equal = IsEqual; } _SEH2_EXCEPT(ExSystemExceptionFilter()) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; } 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 and do the job */ 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; } /* Lock the token */ SepAcquireTokenLockExclusive(Token); /* 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 so that it can filter the token for us */ 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; } /* We got our filtered token, insert it to the handle */ Status = ObInsertObject(FilteredToken, NULL, HandleInfo.GrantedAccess, 0, NULL, &FilteredTokenHandle); if (!NT_SUCCESS(Status)) { DPRINT1("NtFilterToken(): Failed to insert the filtered token object into the handle (Status 0x%lx)\n", Status); goto Quit; } /* And give 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: /* Unlock and dereference the token */ SepReleaseTokenLock(Token); ObDereferenceObject(Token); /* Release all the stuff we've captured */ if (CapturedSids != NULL) { SeReleaseSidAndAttributesArray(CapturedSids, PreviousMode, TRUE); CapturedSids = NULL; } if (CapturedPrivileges != NULL) { SeReleaseLuidAndAttributesArray(CapturedPrivileges, PreviousMode, TRUE); CapturedPrivileges = NULL; } if (CapturedRestrictedSids != NULL) { SeReleaseSidAndAttributesArray(CapturedRestrictedSids, PreviousMode, TRUE); CapturedRestrictedSids = NULL; } return Status; } /** * @brief * Allows the calling thread to impersonate the system's anonymous * logon token. * * @param[in] ThreadHandle * A handle to the thread to start the procedure of logon token * impersonation. The thread must have the THREAD_IMPERSONATE * access right. * * @return * Returns STATUS_SUCCESS if the thread has successfully impersonated the * anonymous logon token, otherwise a failure NTSTATUS code is returned. * * @remarks * By default the system gives the opportunity to the caller to impersonate * the anonymous logon token without including the Everyone Group SID. * In cases where the caller wants to impersonate the token including such * group, the EveryoneIncludesAnonymous registry value setting has to be set * to 1, from HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa registry * path. The calling thread must invoke PsRevertToSelf when impersonation * is no longer needed or RevertToSelf if the calling execution is done * in user mode. */ NTSTATUS NTAPI NtImpersonateAnonymousToken( _In_ HANDLE ThreadHandle) { PETHREAD Thread; KPROCESSOR_MODE PreviousMode; NTSTATUS Status; PAGED_CODE(); PreviousMode = ExGetPreviousMode(); /* Obtain the thread object from the handle */ Status = ObReferenceObjectByHandle(ThreadHandle, THREAD_IMPERSONATE, PsThreadType, PreviousMode, (PVOID*)&Thread, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("NtImpersonateAnonymousToken(): Failed to reference the object (Status 0x%lx)\n", Status); return Status; } /* Call the private routine to impersonate the token */ Status = SepImpersonateAnonymousToken(Thread, PreviousMode); if (!NT_SUCCESS(Status)) { DPRINT1("NtImpersonateAnonymousToken(): Failed to impersonate the token (Status 0x%lx)\n", Status); } ObDereferenceObject(Thread); return Status; } /* EOF */