diff --git a/ntoskrnl/include/internal/se.h b/ntoskrnl/include/internal/se.h index 034203d9313..db2dd0b58a3 100644 --- a/ntoskrnl/include/internal/se.h +++ b/ntoskrnl/include/internal/se.h @@ -1,5 +1,16 @@ +/* + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Internal header for the Security Manager + * COPYRIGHT: Copyright Eric Kohl + * Copyright 2022 George Bișoc + */ + #pragma once +// +// Internal ACE type structures +// typedef struct _KNOWN_ACE { ACE_HEADER Header; @@ -24,6 +35,9 @@ typedef struct _KNOWN_COMPOUND_ACE ULONG SidStart; } KNOWN_COMPOUND_ACE, *PKNOWN_COMPOUND_ACE; +// +// Access Check Rights +// typedef struct _ACCESS_CHECK_RIGHTS { ACCESS_MASK RemainingAccessRights; @@ -37,6 +51,9 @@ typedef enum _ACCESS_CHECK_RIGHT_TYPE AccessCheckRegular } ACCESS_CHECK_RIGHT_TYPE; +// +// Token Audit Policy Information structure +// typedef struct _TOKEN_AUDIT_POLICY_INFORMATION { ULONG PolicyCount; @@ -47,10 +64,16 @@ typedef struct _TOKEN_AUDIT_POLICY_INFORMATION } Policies[1]; } TOKEN_AUDIT_POLICY_INFORMATION, *PTOKEN_AUDIT_POLICY_INFORMATION; +// +// Token creation method defines (for debugging purposes) +// #define TOKEN_CREATE_METHOD 0xCUL #define TOKEN_DUPLICATE_METHOD 0xDUL #define TOKEN_FILTER_METHOD 0xFUL +// +// Security descriptor internal helpers +// FORCEINLINE PSID SepGetGroupFromDescriptor( @@ -137,14 +160,18 @@ SepGetSaclFromDescriptor( #ifndef RTL_H -/* SID Authorities */ +// +// SID Authorities +// extern SID_IDENTIFIER_AUTHORITY SeNullSidAuthority; extern SID_IDENTIFIER_AUTHORITY SeWorldSidAuthority; extern SID_IDENTIFIER_AUTHORITY SeLocalSidAuthority; extern SID_IDENTIFIER_AUTHORITY SeCreatorSidAuthority; extern SID_IDENTIFIER_AUTHORITY SeNtSidAuthority; -/* SIDs */ +// +// SIDs +// extern PSID SeNullSid; extern PSID SeWorldSid; extern PSID SeLocalSid; @@ -177,7 +204,9 @@ extern PSID SeAnonymousLogonSid; extern PSID SeLocalServiceSid; extern PSID SeNetworkServiceSid; -/* Privileges */ +// +// Privileges +// extern const LUID SeCreateTokenPrivilege; extern const LUID SeAssignPrimaryTokenPrivilege; extern const LUID SeLockMemoryPrivilege; @@ -213,14 +242,18 @@ extern const LUID SeIncreaseWorkingSetPrivilege; extern const LUID SeTimeZonePrivilege; extern const LUID SeCreateSymbolicLinkPrivilege; -/* DACLs */ +// +// DACLs +// extern PACL SePublicDefaultUnrestrictedDacl; extern PACL SePublicOpenDacl; extern PACL SePublicOpenUnrestrictedDacl; extern PACL SeUnrestrictedDacl; extern PACL SeSystemAnonymousLogonDacl; -/* SDs */ +// +// SDs +// extern PSECURITY_DESCRIPTOR SePublicDefaultSd; extern PSECURITY_DESCRIPTOR SePublicDefaultUnrestrictedSd; extern PSECURITY_DESCRIPTOR SePublicOpenSd; @@ -229,11 +262,16 @@ extern PSECURITY_DESCRIPTOR SeSystemDefaultSd; extern PSECURITY_DESCRIPTOR SeUnrestrictedSd; extern PSECURITY_DESCRIPTOR SeSystemAnonymousLogonSd; -/* Anonymous Logon Tokens */ +// +// Anonymous Logon Tokens +// extern PTOKEN SeAnonymousLogonToken; extern PTOKEN SeAnonymousLogonTokenNoEveryone; +// +// Token lock management macros +// #define SepAcquireTokenLockExclusive(Token) \ { \ KeEnterCriticalRegion(); \ @@ -254,128 +292,6 @@ extern PTOKEN SeAnonymousLogonTokenNoEveryone; // // Token Functions // -BOOLEAN -NTAPI -SepTokenIsOwner( - _In_ PACCESS_TOKEN _Token, - _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, - _In_ BOOLEAN TokenLocked); - -BOOLEAN -NTAPI -SepSidInToken( - _In_ PACCESS_TOKEN _Token, - _In_ PSID Sid); - -BOOLEAN -NTAPI -SepSidInTokenEx( - _In_ PACCESS_TOKEN _Token, - _In_ PSID PrincipalSelfSid, - _In_ PSID _Sid, - _In_ BOOLEAN Deny, - _In_ BOOLEAN Restricted); - -BOOLEAN -NTAPI -SeTokenCanImpersonate( - _In_ PTOKEN ProcessToken, - _In_ PTOKEN TokenToImpersonate, - _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel); - -/* Functions */ -CODE_SEG("INIT") -BOOLEAN -NTAPI -SeInitSystem(VOID); - -CODE_SEG("INIT") -VOID -NTAPI -SepInitPrivileges(VOID); - -CODE_SEG("INIT") -BOOLEAN -NTAPI -SepInitSecurityIDs(VOID); - -CODE_SEG("INIT") -BOOLEAN -NTAPI -SepInitDACLs(VOID); - -CODE_SEG("INIT") -BOOLEAN -NTAPI -SepInitSDs(VOID); - -BOOLEAN -NTAPI -SeRmInitPhase0(VOID); - -BOOLEAN -NTAPI -SeRmInitPhase1(VOID); - -VOID -NTAPI -SeDeassignPrimaryToken( - _Inout_ PEPROCESS Process); - -NTSTATUS -NTAPI -SeSubProcessToken( - _In_ PTOKEN Parent, - _Out_ PTOKEN *Token, - _In_ BOOLEAN InUse, - _In_ ULONG SessionId); - -NTSTATUS -NTAPI -SeInitializeProcessAuditName( - _In_ PFILE_OBJECT FileObject, - _In_ BOOLEAN DoAudit, - _Out_ POBJECT_NAME_INFORMATION *AuditInfo); - -NTSTATUS -NTAPI -SeCreateAccessStateEx( - _In_ PETHREAD Thread, - _In_ PEPROCESS Process, - _In_ OUT PACCESS_STATE AccessState, - _In_ PAUX_ACCESS_DATA AuxData, - _In_ ACCESS_MASK Access, - _In_ PGENERIC_MAPPING GenericMapping); - -NTSTATUS -NTAPI -SeIsTokenChild( - _In_ PTOKEN Token, - _Out_ PBOOLEAN IsChild); - -NTSTATUS -NTAPI -SeIsTokenSibling( - _In_ PTOKEN Token, - _Out_ PBOOLEAN IsSibling); - -NTSTATUS -NTAPI -SepCreateImpersonationTokenDacl( - _In_ PTOKEN Token, - _In_ PTOKEN PrimaryToken, - _Out_ PACL* Dacl); - -NTSTATUS -NTAPI -SepRmInsertLogonSessionIntoToken( - _Inout_ PTOKEN Token); - -NTSTATUS -NTAPI -SepRmRemoveLogonSessionFromToken( - _Inout_ PTOKEN Token); - CODE_SEG("INIT") VOID NTAPI @@ -394,20 +310,123 @@ CODE_SEG("INIT") PTOKEN SepCreateSystemAnonymousLogonTokenNoEveryone(VOID); +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 +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); + BOOLEAN NTAPI -SeDetailedAuditingWithToken( - _In_ PTOKEN Token); +SepTokenIsOwner( + _In_ PACCESS_TOKEN _Token, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ BOOLEAN TokenLocked); + +NTSTATUS +SepCreateTokenLock( + _Inout_ PTOKEN Token); + +VOID +SepDeleteTokenLock( + _Inout_ PTOKEN Token); + +VOID +SepUpdatePrivilegeFlagsToken( + _Inout_ PTOKEN Token); + +NTSTATUS +SepFindPrimaryGroupAndDefaultOwner( + _In_ PTOKEN Token, + _In_ PSID PrimaryGroup, + _In_opt_ PSID DefaultOwner, + _Out_opt_ PULONG PrimaryGroupIndex, + _Out_opt_ PULONG DefaultOwnerIndex); + +VOID +SepUpdateSinglePrivilegeFlagToken( + _Inout_ PTOKEN Token, + _In_ ULONG Index); + +VOID +SepUpdatePrivilegeFlagsToken( + _Inout_ PTOKEN Token); + +VOID +SepRemovePrivilegeToken( + _Inout_ PTOKEN Token, + _In_ ULONG Index); + +VOID +SepRemoveUserGroupToken( + _Inout_ PTOKEN Token, + _In_ ULONG Index); + +BOOLEAN +NTAPI +SeTokenCanImpersonate( + _In_ PTOKEN ProcessToken, + _In_ PTOKEN TokenToImpersonate, + _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel); VOID NTAPI -SeAuditProcessExit( - _In_ PEPROCESS Process); +SeGetTokenControlInformation( + _In_ PACCESS_TOKEN _Token, + _Out_ PTOKEN_CONTROL TokenControl); VOID NTAPI -SeAuditProcessCreate( - _In_ PEPROCESS Process); +SeDeassignPrimaryToken( + _Inout_ PEPROCESS Process); + +NTSTATUS +NTAPI +SeSubProcessToken( + _In_ PTOKEN Parent, + _Out_ PTOKEN *Token, + _In_ BOOLEAN InUse, + _In_ ULONG SessionId); + +NTSTATUS +NTAPI +SeIsTokenChild( + _In_ PTOKEN Token, + _Out_ PBOOLEAN IsChild); + +NTSTATUS +NTAPI +SeIsTokenSibling( + _In_ PTOKEN Token, + _Out_ PBOOLEAN IsSibling); NTSTATUS NTAPI @@ -416,32 +435,58 @@ SeExchangePrimaryToken( _In_ PACCESS_TOKEN NewAccessToken, _Out_ PACCESS_TOKEN* OldAccessToken); -VOID +NTSTATUS NTAPI -SeCaptureSubjectContextEx( - _In_ PETHREAD Thread, - _In_ PEPROCESS Process, - _Out_ PSECURITY_SUBJECT_CONTEXT SubjectContext); +SeCopyClientToken( + _In_ PACCESS_TOKEN Token, + _In_ SECURITY_IMPERSONATION_LEVEL Level, + _In_ KPROCESSOR_MODE PreviousMode, + _Out_ PACCESS_TOKEN* NewToken); + +ULONG +RtlLengthSidAndAttributes( + _In_ ULONG Count, + _In_ PSID_AND_ATTRIBUTES Src); + +// +// Security Manager (SeMgr) functions +// +CODE_SEG("INIT") +BOOLEAN +NTAPI +SeInitSystem(VOID); NTSTATUS NTAPI -SeCaptureLuidAndAttributesArray( - _In_ PLUID_AND_ATTRIBUTES Src, - _In_ ULONG PrivilegeCount, - _In_ KPROCESSOR_MODE PreviousMode, - _In_ PLUID_AND_ATTRIBUTES AllocatedMem, - _In_ ULONG AllocatedLength, +SeDefaultObjectMethod( + _In_ PVOID Object, + _In_ SECURITY_OPERATION_CODE OperationType, + _In_ PSECURITY_INFORMATION SecurityInformation, + _Inout_opt_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Inout_opt_ PULONG ReturnLength, + _Inout_opt_ PSECURITY_DESCRIPTOR *OldSecurityDescriptor, _In_ POOL_TYPE PoolType, - _In_ BOOLEAN CaptureIfKernel, - _Out_ PLUID_AND_ATTRIBUTES* Dest, - _Inout_ PULONG Length); + _In_ PGENERIC_MAPPING GenericMapping); VOID NTAPI -SeReleaseLuidAndAttributesArray( - _In_ PLUID_AND_ATTRIBUTES Privilege, - _In_ KPROCESSOR_MODE PreviousMode, - _In_ BOOLEAN CaptureIfKernel); +SeQuerySecurityAccessMask( + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_ PACCESS_MASK DesiredAccess); + +VOID +NTAPI +SeSetSecurityAccessMask( + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_ PACCESS_MASK DesiredAccess); + +// +// Privilege functions +// +CODE_SEG("INIT") +VOID +NTAPI +SepInitPrivileges(VOID); BOOLEAN NTAPI @@ -462,6 +507,12 @@ SePrivilegePolicyCheck( _Out_opt_ PPRIVILEGE_SET *OutPrivilegeSet, _In_ KPROCESSOR_MODE PreviousMode); +BOOLEAN +NTAPI +SeCheckAuditPrivilege( + _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext, + _In_ KPROCESSOR_MODE PreviousMode); + BOOLEAN NTAPI SeCheckPrivilegedObject( @@ -472,32 +523,32 @@ SeCheckPrivilegedObject( NTSTATUS NTAPI -SepDuplicateToken( - _In_ PTOKEN Token, - _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, - _In_ BOOLEAN EffectiveOnly, - _In_ TOKEN_TYPE TokenType, - _In_ SECURITY_IMPERSONATION_LEVEL Level, +SeCaptureLuidAndAttributesArray( + _In_ PLUID_AND_ATTRIBUTES Src, + _In_ ULONG PrivilegeCount, _In_ KPROCESSOR_MODE PreviousMode, - _Out_ PTOKEN* NewAccessToken); - -NTSTATUS -NTAPI -SepCaptureSecurityQualityOfService( - _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, - _In_ KPROCESSOR_MODE AccessMode, + _In_ PLUID_AND_ATTRIBUTES AllocatedMem, + _In_ ULONG AllocatedLength, _In_ POOL_TYPE PoolType, _In_ BOOLEAN CaptureIfKernel, - _Out_ PSECURITY_QUALITY_OF_SERVICE *CapturedSecurityQualityOfService, - _Out_ PBOOLEAN Present); + _Out_ PLUID_AND_ATTRIBUTES* Dest, + _Inout_ PULONG Length); VOID NTAPI -SepReleaseSecurityQualityOfService( - _In_opt_ PSECURITY_QUALITY_OF_SERVICE CapturedSecurityQualityOfService, - _In_ KPROCESSOR_MODE AccessMode, +SeReleaseLuidAndAttributesArray( + _In_ PLUID_AND_ATTRIBUTES Privilege, + _In_ KPROCESSOR_MODE PreviousMode, _In_ BOOLEAN CaptureIfKernel); +// +// SID functions +// +CODE_SEG("INIT") +BOOLEAN +NTAPI +SepInitSecurityIDs(VOID); + NTSTATUS NTAPI SepCaptureSid( @@ -514,6 +565,21 @@ SepReleaseSid( _In_ KPROCESSOR_MODE AccessMode, _In_ BOOLEAN CaptureIfKernel); +BOOLEAN +NTAPI +SepSidInToken( + _In_ PACCESS_TOKEN _Token, + _In_ PSID Sid); + +BOOLEAN +NTAPI +SepSidInTokenEx( + _In_ PACCESS_TOKEN _Token, + _In_ PSID PrincipalSelfSid, + _In_ PSID _Sid, + _In_ BOOLEAN Deny, + _In_ BOOLEAN Restricted); + PSID NTAPI SepGetSidFromAce( @@ -540,11 +606,20 @@ SeReleaseSidAndAttributesArray( _In_ KPROCESSOR_MODE AccessMode, _In_ BOOLEAN CaptureIfKernel); +// +// ACL functions +// +CODE_SEG("INIT") +BOOLEAN +NTAPI +SepInitDACLs(VOID); + NTSTATUS NTAPI -SeComputeQuotaInformationSize( - _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, - _Out_ PULONG QuotaInfoSize); +SepCreateImpersonationTokenDacl( + _In_ PTOKEN Token, + _In_ PTOKEN PrimaryToken, + _Out_ PACL* Dacl); NTSTATUS NTAPI @@ -588,17 +663,13 @@ SepSelectAcl( _In_ BOOLEAN IsDirectoryObject, _In_ PGENERIC_MAPPING GenericMapping); -NTSTATUS +// +// SD functions +// +CODE_SEG("INIT") +BOOLEAN NTAPI -SeDefaultObjectMethod( - _In_ PVOID Object, - _In_ SECURITY_OPERATION_CODE OperationType, - _In_ PSECURITY_INFORMATION SecurityInformation, - _Inout_opt_ PSECURITY_DESCRIPTOR SecurityDescriptor, - _Inout_opt_ PULONG ReturnLength, - _Inout_opt_ PSECURITY_DESCRIPTOR *OldSecurityDescriptor, - _In_ POOL_TYPE PoolType, - _In_ PGENERIC_MAPPING GenericMapping); +SepInitSDs(VOID); NTSTATUS NTAPI @@ -609,54 +680,30 @@ SeSetWorldSecurityDescriptor( NTSTATUS NTAPI -SeCopyClientToken( - _In_ PACCESS_TOKEN Token, - _In_ SECURITY_IMPERSONATION_LEVEL Level, - _In_ KPROCESSOR_MODE PreviousMode, - _Out_ PACCESS_TOKEN* NewToken); +SeComputeQuotaInformationSize( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Out_ PULONG QuotaInfoSize); + +// +// Security Reference Monitor (SeRm) functions +// +BOOLEAN +NTAPI +SeRmInitPhase0(VOID); + +BOOLEAN +NTAPI +SeRmInitPhase1(VOID); NTSTATUS NTAPI -SepRegQueryHelper( - _In_ PCWSTR KeyName, - _In_ PCWSTR ValueName, - _In_ ULONG ValueType, - _In_ ULONG DataLength, - _Out_ PVOID ValueData); +SepRmInsertLogonSessionIntoToken( + _Inout_ PTOKEN Token); -VOID +NTSTATUS NTAPI -SeQuerySecurityAccessMask( - _In_ SECURITY_INFORMATION SecurityInformation, - _Out_ PACCESS_MASK DesiredAccess); - -VOID -NTAPI -SeSetSecurityAccessMask( - _In_ SECURITY_INFORMATION SecurityInformation, - _Out_ PACCESS_MASK DesiredAccess); - -BOOLEAN -NTAPI -SeFastTraverseCheck( - _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, - _In_ PACCESS_STATE AccessState, - _In_ ACCESS_MASK DesiredAccess, - _In_ KPROCESSOR_MODE AccessMode); - -BOOLEAN -NTAPI -SeCheckAuditPrivilege( - _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext, - _In_ KPROCESSOR_MODE PreviousMode); - -VOID -NTAPI -SePrivilegedServiceAuditAlarm( - _In_opt_ PUNICODE_STRING ServiceName, - _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext, - _In_ PPRIVILEGE_SET PrivilegeSet, - _In_ BOOLEAN AccessGranted); +SepRmRemoveLogonSessionFromToken( + _Inout_ PTOKEN Token); NTSTATUS SepRmReferenceLogonSession( @@ -666,12 +713,123 @@ NTSTATUS SepRmDereferenceLogonSession( _Inout_ PLUID LogonLuid); +NTSTATUS +NTAPI +SepRegQueryHelper( + _In_ PCWSTR KeyName, + _In_ PCWSTR ValueName, + _In_ ULONG ValueType, + _In_ ULONG DataLength, + _Out_ PVOID ValueData); + NTSTATUS NTAPI SeGetLogonIdDeviceMap( _In_ PLUID LogonId, _Out_ PDEVICE_MAP *DeviceMap); +// +// Audit functions +// +NTSTATUS +NTAPI +SeInitializeProcessAuditName( + _In_ PFILE_OBJECT FileObject, + _In_ BOOLEAN DoAudit, + _Out_ POBJECT_NAME_INFORMATION *AuditInfo); + +BOOLEAN +NTAPI +SeDetailedAuditingWithToken( + _In_ PTOKEN Token); + +VOID +NTAPI +SeAuditProcessExit( + _In_ PEPROCESS Process); + +VOID +NTAPI +SeAuditProcessCreate( + _In_ PEPROCESS Process); + +VOID +NTAPI +SePrivilegedServiceAuditAlarm( + _In_opt_ PUNICODE_STRING ServiceName, + _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext, + _In_ PPRIVILEGE_SET PrivilegeSet, + _In_ BOOLEAN AccessGranted); + +// +// Subject functions +// +VOID +NTAPI +SeCaptureSubjectContextEx( + _In_ PETHREAD Thread, + _In_ PEPROCESS Process, + _Out_ PSECURITY_SUBJECT_CONTEXT SubjectContext); + +// +// Security Quality of Service (SQoS) functions +// +NTSTATUS +NTAPI +SepCaptureSecurityQualityOfService( + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ KPROCESSOR_MODE AccessMode, + _In_ POOL_TYPE PoolType, + _In_ BOOLEAN CaptureIfKernel, + _Out_ PSECURITY_QUALITY_OF_SERVICE *CapturedSecurityQualityOfService, + _Out_ PBOOLEAN Present); + +VOID +NTAPI +SepReleaseSecurityQualityOfService( + _In_opt_ PSECURITY_QUALITY_OF_SERVICE CapturedSecurityQualityOfService, + _In_ KPROCESSOR_MODE AccessMode, + _In_ BOOLEAN CaptureIfKernel); + +// +// Object type list functions +// +NTSTATUS +SeCaptureObjectTypeList( + _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ KPROCESSOR_MODE PreviousMode, + _Out_ POBJECT_TYPE_LIST *CapturedObjectTypeList); + +VOID +SeReleaseObjectTypeList( + _In_ _Post_invalid_ POBJECT_TYPE_LIST CapturedObjectTypeList, + _In_ KPROCESSOR_MODE PreviousMode); + +// +// Access state functions +// +NTSTATUS +NTAPI +SeCreateAccessStateEx( + _In_ PETHREAD Thread, + _In_ PEPROCESS Process, + _In_ OUT PACCESS_STATE AccessState, + _In_ PAUX_ACCESS_DATA AuxData, + _In_ ACCESS_MASK Access, + _In_ PGENERIC_MAPPING GenericMapping); + +// +// Access check functions +// +BOOLEAN +NTAPI +SeFastTraverseCheck( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ PACCESS_STATE AccessState, + _In_ ACCESS_MASK DesiredAccess, + _In_ KPROCESSOR_MODE AccessMode); + #endif /* EOF */ diff --git a/ntoskrnl/ntos.cmake b/ntoskrnl/ntos.cmake index 2284563e436..af8217bc480 100644 --- a/ntoskrnl/ntos.cmake +++ b/ntoskrnl/ntos.cmake @@ -275,13 +275,19 @@ list(APPEND SOURCE ${REACTOS_SOURCE_DIR}/ntoskrnl/se/accesschk.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/acl.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/audit.c + ${REACTOS_SOURCE_DIR}/ntoskrnl/se/client.c + ${REACTOS_SOURCE_DIR}/ntoskrnl/se/objtype.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/priv.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/sd.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/semgr.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/sid.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/sqos.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/srm.c + ${REACTOS_SOURCE_DIR}/ntoskrnl/se/subject.c ${REACTOS_SOURCE_DIR}/ntoskrnl/se/token.c + ${REACTOS_SOURCE_DIR}/ntoskrnl/se/tokenadj.c + ${REACTOS_SOURCE_DIR}/ntoskrnl/se/tokencls.c + ${REACTOS_SOURCE_DIR}/ntoskrnl/se/tokenlif.c ${REACTOS_SOURCE_DIR}/ntoskrnl/vf/driver.c ${REACTOS_SOURCE_DIR}/ntoskrnl/wmi/guidobj.c ${REACTOS_SOURCE_DIR}/ntoskrnl/wmi/smbios.c diff --git a/ntoskrnl/se/access.c b/ntoskrnl/se/access.c index 98b00a70d1e..77100336509 100644 --- a/ntoskrnl/se/access.c +++ b/ntoskrnl/se/access.c @@ -11,529 +11,8 @@ #define NDEBUG #include -/* GLOBALS ********************************************************************/ - -ERESOURCE SepSubjectContextLock; - -/* PRIVATE FUNCTIONS **********************************************************/ - -/** - * @brief - * Checks if a SID is present in a token. - * - * @param[in] _Token - * A valid token object. - * - * @param[in] PrincipalSelfSid - * A principal self SID. - * - * @param[in] _Sid - * A regular SID. - * - * @param[in] Deny - * If set to TRUE, the caller expected that a SID in a token - * must be a deny-only SID, that is, access checks are performed - * only for deny-only ACEs of the said SID. - * - * @param[in] Restricted - * If set to TRUE, the caller expects that a SID in a token is - * restricted (by the general definition, a token is restricted). - * - * @return - * Returns TRUE if the specified SID in the call is present in the token, - * FALSE otherwise. - */ -BOOLEAN -NTAPI -SepSidInTokenEx( - _In_ PACCESS_TOKEN _Token, - _In_ PSID PrincipalSelfSid, - _In_ PSID _Sid, - _In_ BOOLEAN Deny, - _In_ BOOLEAN Restricted) -{ - ULONG SidIndex; - PTOKEN Token = (PTOKEN)_Token; - PISID TokenSid, Sid = (PISID)_Sid; - PSID_AND_ATTRIBUTES SidAndAttributes; - ULONG SidCount, SidLength; - USHORT SidMetadata; - PAGED_CODE(); - - /* Check if a principal SID was given, and this is our current SID already */ - if ((PrincipalSelfSid) && (RtlEqualSid(SePrincipalSelfSid, Sid))) - { - /* Just use the principal SID in this case */ - Sid = PrincipalSelfSid; - } - - /* Check if this is a restricted token or not */ - if (Restricted) - { - /* Use the restricted SIDs and count */ - SidAndAttributes = Token->RestrictedSids; - SidCount = Token->RestrictedSidCount; - } - else - { - /* Use the normal SIDs and count */ - SidAndAttributes = Token->UserAndGroups; - SidCount = Token->UserAndGroupCount; - } - - /* Do checks here by hand instead of the usual 4 function calls */ - SidLength = FIELD_OFFSET(SID, - SubAuthority[Sid->SubAuthorityCount]); - SidMetadata = *(PUSHORT)&Sid->Revision; - - /* Loop every SID */ - for (SidIndex = 0; SidIndex < SidCount; SidIndex++) - { - TokenSid = (PISID)SidAndAttributes->Sid; -#if SE_SID_DEBUG - UNICODE_STRING sidString; - RtlConvertSidToUnicodeString(&sidString, TokenSid, TRUE); - DPRINT1("SID in Token: %wZ\n", &sidString); - RtlFreeUnicodeString(&sidString); -#endif - /* Check if the SID metadata matches */ - if (*(PUSHORT)&TokenSid->Revision == SidMetadata) - { - /* Check if the SID data matches */ - if (RtlEqualMemory(Sid, TokenSid, SidLength)) - { - /* - * Check if the group is enabled, or used for deny only. - * Otherwise we have to check if this is the first user. - * We understand that by looking if this SID is not - * restricted, this is the first element we are iterating - * and that it doesn't have SE_GROUP_USE_FOR_DENY_ONLY - * attribute. - */ - if ((!Restricted && (SidIndex == 0) && !(SidAndAttributes->Attributes & SE_GROUP_USE_FOR_DENY_ONLY)) || - (SidAndAttributes->Attributes & SE_GROUP_ENABLED) || - ((Deny) && (SidAndAttributes->Attributes & SE_GROUP_USE_FOR_DENY_ONLY))) - { - /* SID is present */ - return TRUE; - } - else - { - /* SID is not present */ - return FALSE; - } - } - } - - /* Move to the next SID */ - SidAndAttributes++; - } - - /* SID is not present */ - return FALSE; -} - -/** - * @brief - * Checks if a SID is present in a token. - * - * @param[in] _Token - * A valid token object. - * - * @param[in] _Sid - * A regular SID. - * - * @return - * Returns TRUE if the specified SID in the call is present in the token, - * FALSE otherwise. - */ -BOOLEAN -NTAPI -SepSidInToken( - _In_ PACCESS_TOKEN _Token, - _In_ PSID Sid) -{ - /* Call extended API */ - return SepSidInTokenEx(_Token, NULL, Sid, FALSE, FALSE); -} - -/** - * @brief - * Checks if a token belongs to the main user, being the owner. - * - * @param[in] _Token - * A valid token object. - * - * @param[in] SecurityDescriptor - * A security descriptor where the owner is to be found. - * - * @param[in] TokenLocked - * If set to TRUE, the token has been already locked and there's - * no need to lock it again. Otherwise the function will acquire - * the lock. - * - * @return - * Returns TRUE if the token belongs to a owner, FALSE otherwise. - */ -BOOLEAN -NTAPI -SepTokenIsOwner( - _In_ PACCESS_TOKEN _Token, - _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, - _In_ BOOLEAN TokenLocked) -{ - PSID Sid; - BOOLEAN Result; - PTOKEN Token = _Token; - - /* Get the owner SID */ - Sid = SepGetOwnerFromDescriptor(SecurityDescriptor); - ASSERT(Sid != NULL); - - /* Lock the token if needed */ - if (!TokenLocked) SepAcquireTokenLockShared(Token); - - /* Check if the owner SID is found, handling restricted case as well */ - Result = SepSidInToken(Token, Sid); - if ((Result) && (Token->TokenFlags & TOKEN_IS_RESTRICTED)) - { - Result = SepSidInTokenEx(Token, NULL, Sid, FALSE, TRUE); - } - - /* Release the lock if we had acquired it */ - if (!TokenLocked) SepReleaseTokenLock(Token); - - /* Return the result */ - return Result; -} - -/** - * @brief - * Retrieves token control information. - * - * @param[in] _Token - * A valid token object. - * - * @param[out] SecurityDescriptor - * The returned token control information. - * - * @return - * Nothing. - */ -VOID -NTAPI -SeGetTokenControlInformation( - _In_ PACCESS_TOKEN _Token, - _Out_ PTOKEN_CONTROL TokenControl) -{ - PTOKEN Token = _Token; - PAGED_CODE(); - - /* Capture the main fields */ - TokenControl->AuthenticationId = Token->AuthenticationId; - TokenControl->TokenId = Token->TokenId; - TokenControl->TokenSource = Token->TokenSource; - - /* Lock the token */ - SepAcquireTokenLockShared(Token); - - /* Capture the modified ID */ - TokenControl->ModifiedId = Token->ModifiedId; - - /* Unlock it */ - SepReleaseTokenLock(Token); -} - -/** - * @brief - * Creates a client security context based upon an access token. - * - * @param[in] Token - * A valid token object. - * - * @param[in] ClientSecurityQos - * The Quality of Service (QoS) of a client security context. - * - * @param[in] ServerIsRemote - * If the client is a remote server (TRUE), the function will retrieve the - * control information of an access token, that is, we're doing delegation - * and that the server isn't local. - * - * @param[in] TokenType - * Type of token. - * - * @param[in] ThreadEffectiveOnly - * If set to TRUE, the client wants that the current thread wants to modify - * (enable or disable) privileges and groups. - * - * @param[in] ImpersonationLevel - * Security impersonation level filled in the QoS context. - * - * @param[out] ClientContext - * The returned security client context. - * - * @return - * Returns STATUS_SUCCESS if client security creation has completed successfully. - * STATUS_INVALID_PARAMETER is returned if one or more of the parameters are bogus. - * STATUS_BAD_IMPERSONATION_LEVEL is returned if the current impersonation level - * within QoS context doesn't meet with the conditions required. A failure - * NTSTATUS code is returned otherwise. - */ -NTSTATUS -NTAPI -SepCreateClientSecurity( - _In_ PACCESS_TOKEN Token, - _In_ PSECURITY_QUALITY_OF_SERVICE ClientSecurityQos, - _In_ BOOLEAN ServerIsRemote, - _In_ TOKEN_TYPE TokenType, - _In_ BOOLEAN ThreadEffectiveOnly, - _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, - _Out_ PSECURITY_CLIENT_CONTEXT ClientContext) -{ - NTSTATUS Status; - PACCESS_TOKEN NewToken; - PAGED_CODE(); - - /* Check for bogus impersonation level */ - if (!VALID_IMPERSONATION_LEVEL(ClientSecurityQos->ImpersonationLevel)) - { - /* Fail the call */ - return STATUS_INVALID_PARAMETER; - } - - /* Check what kind of token this is */ - if (TokenType != TokenImpersonation) - { - /* On a primary token, if we do direct access, copy the flag from the QOS */ - ClientContext->DirectAccessEffectiveOnly = ClientSecurityQos->EffectiveOnly; - } - else - { - /* This is an impersonation token, is the level ok? */ - if (ClientSecurityQos->ImpersonationLevel > ImpersonationLevel) - { - /* Nope, fail */ - return STATUS_BAD_IMPERSONATION_LEVEL; - } - - /* Is the level too low, or are we doing something other than delegation remotely */ - if ((ImpersonationLevel == SecurityAnonymous) || - (ImpersonationLevel == SecurityIdentification) || - ((ServerIsRemote) && (ImpersonationLevel != SecurityDelegation))) - { - /* Fail the call */ - return STATUS_BAD_IMPERSONATION_LEVEL; - } - - /* Pick either the thread setting or the QOS setting */ - ClientContext->DirectAccessEffectiveOnly = - ((ThreadEffectiveOnly) || (ClientSecurityQos->EffectiveOnly)) ? TRUE : FALSE; - } - - /* Is this static tracking */ - if (ClientSecurityQos->ContextTrackingMode == SECURITY_STATIC_TRACKING) - { - /* Do not use direct access and make a copy */ - ClientContext->DirectlyAccessClientToken = FALSE; - Status = SeCopyClientToken(Token, - ClientSecurityQos->ImpersonationLevel, - KernelMode, - &NewToken); - if (!NT_SUCCESS(Status)) - return Status; - } - else - { - /* Use direct access and check if this is local */ - ClientContext->DirectlyAccessClientToken = TRUE; - if (ServerIsRemote) - { - /* We are doing delegation, so make a copy of the control data */ - SeGetTokenControlInformation(Token, - &ClientContext->ClientTokenControl); - } - - /* Keep the same token */ - NewToken = Token; - } - - /* Fill out the context and return success */ - ClientContext->SecurityQos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); - ClientContext->SecurityQos.ImpersonationLevel = ClientSecurityQos->ImpersonationLevel; - ClientContext->SecurityQos.ContextTrackingMode = ClientSecurityQos->ContextTrackingMode; - ClientContext->SecurityQos.EffectiveOnly = ClientSecurityQos->EffectiveOnly; - ClientContext->ServerIsRemote = ServerIsRemote; - ClientContext->ClientToken = NewToken; - return STATUS_SUCCESS; -} - /* PUBLIC FUNCTIONS ***********************************************************/ -/** - * @brief - * An extended function that captures the security subject context based upon - * the specified thread and process. - * - * @param[in] Thread - * A thread where the calling thread's token is to be referenced for - * the security context. - * - * @param[in] Process - * A process where the main process' token is to be referenced for - * the security context. - * - * @param[out] SubjectContext - * The returned security subject context. - * - * @return - * Nothing. - */ -VOID -NTAPI -SeCaptureSubjectContextEx( - _In_ PETHREAD Thread, - _In_ PEPROCESS Process, - _Out_ PSECURITY_SUBJECT_CONTEXT SubjectContext) -{ - BOOLEAN CopyOnOpen, EffectiveOnly; - - PAGED_CODE(); - - /* Save the unique ID */ - SubjectContext->ProcessAuditId = Process->UniqueProcessId; - - /* Check if we have a thread */ - if (!Thread) - { - /* We don't, so no token */ - SubjectContext->ClientToken = NULL; - } - else - { - /* Get the impersonation token */ - SubjectContext->ClientToken = PsReferenceImpersonationToken(Thread, - &CopyOnOpen, - &EffectiveOnly, - &SubjectContext->ImpersonationLevel); - } - - /* Get the primary token */ - SubjectContext->PrimaryToken = PsReferencePrimaryToken(Process); -} - -/** - * @brief - * Captures the security subject context of the calling thread and calling - * process. - * - * @param[out] SubjectContext - * The returned security subject context. - * - * @return - * Nothing. - */ -VOID -NTAPI -SeCaptureSubjectContext( - _Out_ PSECURITY_SUBJECT_CONTEXT SubjectContext) -{ - /* Call the extended API */ - SeCaptureSubjectContextEx(PsGetCurrentThread(), - PsGetCurrentProcess(), - SubjectContext); -} - -/** - * @brief - * Locks both the referenced primary and client access tokens of a - * security subject context. - * - * @param[in] SubjectContext - * A valid security context with both referenced tokens. - * - * @return - * Nothing. - */ -VOID -NTAPI -SeLockSubjectContext( - _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext) -{ - PTOKEN PrimaryToken, ClientToken; - PAGED_CODE(); - - /* Read both tokens */ - PrimaryToken = SubjectContext->PrimaryToken; - ClientToken = SubjectContext->ClientToken; - - /* Always lock the primary */ - SepAcquireTokenLockShared(PrimaryToken); - - /* Lock the impersonation one if it's there */ - if (!ClientToken) return; - SepAcquireTokenLockShared(ClientToken); -} - -/** - * @brief - * Unlocks both the referenced primary and client access tokens of a - * security subject context. - * - * @param[in] SubjectContext - * A valid security context with both referenced tokens. - * - * @return - * Nothing. - */ -VOID -NTAPI -SeUnlockSubjectContext( - _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext) -{ - PTOKEN PrimaryToken, ClientToken; - PAGED_CODE(); - - /* Read both tokens */ - PrimaryToken = SubjectContext->PrimaryToken; - ClientToken = SubjectContext->ClientToken; - - /* Unlock the impersonation one if it's there */ - if (ClientToken) - { - SepReleaseTokenLock(ClientToken); - } - - /* Always unlock the primary one */ - SepReleaseTokenLock(PrimaryToken); -} - -/** - * @brief - * Releases both the primary and client tokens of a security - * subject context. - * - * @param[in] SubjectContext - * The captured security context. - * - * @return - * Nothing. - */ -VOID -NTAPI -SeReleaseSubjectContext( - _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext) -{ - PAGED_CODE(); - - /* Drop reference on the primary */ - ObFastDereferenceObject(&PsGetCurrentProcess()->Token, SubjectContext->PrimaryToken); - SubjectContext->PrimaryToken = NULL; - - /* Drop reference on the impersonation, if there was one */ - PsDereferenceImpersonationToken(SubjectContext->ClientToken); - SubjectContext->ClientToken = NULL; -} - /** * @brief * An extended function that creates an access state. @@ -721,194 +200,4 @@ SeSetAccessStateGenericMapping( ((PAUX_ACCESS_DATA)AccessState->AuxData)->GenericMapping = *GenericMapping; } -/** - * @brief - * Creates a client security context. - * - * @param[in] Thread - * Thread object of the client where impersonation has to begin. - * - * @param[in] Qos - * Quality of service to specify what kind of impersonation to be done. - * - * @param[in] RemoteClient - * If set to TRUE, the client that we're going to impersonate is remote. - * - * @param[out] ClientContext - * The returned security client context. - * - * @return - * See SepCreateClientSecurity. - */ -NTSTATUS -NTAPI -SeCreateClientSecurity( - _In_ PETHREAD Thread, - _In_ PSECURITY_QUALITY_OF_SERVICE Qos, - _In_ BOOLEAN RemoteClient, - _Out_ PSECURITY_CLIENT_CONTEXT ClientContext) -{ - TOKEN_TYPE TokenType; - BOOLEAN ThreadEffectiveOnly; - SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; - PACCESS_TOKEN Token; - NTSTATUS Status; - PAGED_CODE(); - - /* Reference the correct token */ - Token = PsReferenceEffectiveToken(Thread, - &TokenType, - &ThreadEffectiveOnly, - &ImpersonationLevel); - - /* Create client security from it */ - Status = SepCreateClientSecurity(Token, - Qos, - RemoteClient, - TokenType, - ThreadEffectiveOnly, - ImpersonationLevel, - ClientContext); - - /* Check if we failed or static tracking was used */ - if (!(NT_SUCCESS(Status)) || (Qos->ContextTrackingMode == SECURITY_STATIC_TRACKING)) - { - /* Dereference our copy since it's not being used */ - ObDereferenceObject(Token); - } - - /* Return status */ - return Status; -} - -/** - * @brief - * Creates a client security context based upon the captured security - * subject context. - * - * @param[in] SubjectContext - * The captured subject context where client security is to be created - * from. - * - * @param[in] ClientSecurityQos - * Quality of service to specify what kind of impersonation to be done. - * - * @param[in] ServerIsRemote - * If set to TRUE, the client that we're going to impersonate is remote. - * - * @param[out] ClientContext - * The returned security client context. - * - * @return - * See SepCreateClientSecurity. - */ -NTSTATUS -NTAPI -SeCreateClientSecurityFromSubjectContext( - _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext, - _In_ PSECURITY_QUALITY_OF_SERVICE ClientSecurityQos, - _In_ BOOLEAN ServerIsRemote, - _Out_ PSECURITY_CLIENT_CONTEXT ClientContext) -{ - PACCESS_TOKEN Token; - NTSTATUS Status; - PAGED_CODE(); - - /* Get the right token and reference it */ - Token = SeQuerySubjectContextToken(SubjectContext); - ObReferenceObject(Token); - - /* Create the context */ - Status = SepCreateClientSecurity(Token, - ClientSecurityQos, - ServerIsRemote, - SubjectContext->ClientToken ? - TokenImpersonation : TokenPrimary, - FALSE, - SubjectContext->ImpersonationLevel, - ClientContext); - - /* Check if we failed or static tracking was used */ - if (!(NT_SUCCESS(Status)) || - (ClientSecurityQos->ContextTrackingMode == SECURITY_STATIC_TRACKING)) - { - /* Dereference our copy since it's not being used */ - ObDereferenceObject(Token); - } - - /* Return status */ - return Status; -} - -/** - * @brief - * Extended function that impersonates a client. - * - * @param[in] ClientContext - * A valid client context. - * - * @param[in] ServerThread - * The thread where impersonation is to be done. - * - * @return - * STATUS_SUCCESS is returned if the calling thread successfully impersonates - * the client. A failure NTSTATUS code is returned otherwise. - */ -NTSTATUS -NTAPI -SeImpersonateClientEx( - _In_ PSECURITY_CLIENT_CONTEXT ClientContext, - _In_opt_ PETHREAD ServerThread) -{ - BOOLEAN EffectiveOnly; - PAGED_CODE(); - - /* Check if direct access is requested */ - if (!ClientContext->DirectlyAccessClientToken) - { - /* No, so get the flag from QOS */ - EffectiveOnly = ClientContext->SecurityQos.EffectiveOnly; - } - else - { - /* Yes, so see if direct access should be effective only */ - EffectiveOnly = ClientContext->DirectAccessEffectiveOnly; - } - - /* Use the current thread if one was not passed */ - if (!ServerThread) ServerThread = PsGetCurrentThread(); - - /* Call the lower layer routine */ - return PsImpersonateClient(ServerThread, - ClientContext->ClientToken, - TRUE, - EffectiveOnly, - ClientContext->SecurityQos.ImpersonationLevel); -} - -/** - * @brief - * Impersonates a client user. - * - * @param[in] ClientContext - * A valid client context. - * - * @param[in] ServerThread - * The thread where impersonation is to be done. - * * - * @return - * Nothing. - */ -VOID -NTAPI -SeImpersonateClient( - _In_ PSECURITY_CLIENT_CONTEXT ClientContext, - _In_opt_ PETHREAD ServerThread) -{ - PAGED_CODE(); - - /* Call the new API */ - SeImpersonateClientEx(ClientContext, ServerThread); -} - /* EOF */ diff --git a/ntoskrnl/se/audit.c b/ntoskrnl/se/audit.c index 0d993a1761d..a201e214907 100644 --- a/ntoskrnl/se/audit.c +++ b/ntoskrnl/se/audit.c @@ -16,7 +16,7 @@ UNICODE_STRING SeSubsystemName = RTL_CONSTANT_STRING(L"Security"); -/* PRIVATE FUNCTIONS***********************************************************/ +/* PRIVATE FUNCTIONS ***********************************************************/ /** * @unimplemented @@ -411,107 +411,6 @@ SePrivilegedServiceAuditAlarm( } -/** - * @brief - * Captures a list of object types. - * - * @param[in] ObjectTypeList - * An existing list of object types. - * - * @param[in] ObjectTypeListLength - * The length size of the list. - * - * @param[in] PreviousMode - * Processor access level mode. - * - * @param[out] CapturedObjectTypeList - * The captured list of object types. - * - * @return - * Returns STATUS_SUCCESS if the list of object types has been captured - * successfully. STATUS_INVALID_PARAMETER is returned if the caller hasn't - * supplied a buffer list of object types. STATUS_INSUFFICIENT_RESOURCES - * is returned if pool memory allocation for the captured list has failed. - */ -static -NTSTATUS -SeCaptureObjectTypeList( - _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, - _In_ ULONG ObjectTypeListLength, - _In_ KPROCESSOR_MODE PreviousMode, - _Out_ POBJECT_TYPE_LIST *CapturedObjectTypeList) -{ - SIZE_T Size; - - if (PreviousMode == KernelMode) - { - return STATUS_NOT_IMPLEMENTED; - } - - if (ObjectTypeListLength == 0) - { - *CapturedObjectTypeList = NULL; - return STATUS_SUCCESS; - } - - if (ObjectTypeList == NULL) - { - return STATUS_INVALID_PARAMETER; - } - - /* Calculate the list size and check for integer overflow */ - Size = ObjectTypeListLength * sizeof(OBJECT_TYPE_LIST); - if (Size == 0) - { - return STATUS_INVALID_PARAMETER; - } - - /* Allocate a new list */ - *CapturedObjectTypeList = ExAllocatePoolWithTag(PagedPool, Size, TAG_SEPA); - if (*CapturedObjectTypeList == NULL) - { - return STATUS_INSUFFICIENT_RESOURCES; - } - - _SEH2_TRY - { - ProbeForRead(ObjectTypeList, Size, sizeof(ULONG)); - RtlCopyMemory(*CapturedObjectTypeList, ObjectTypeList, Size); - } - _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) - { - ExFreePoolWithTag(*CapturedObjectTypeList, TAG_SEPA); - *CapturedObjectTypeList = NULL; - _SEH2_YIELD(return _SEH2_GetExceptionCode()); - } - _SEH2_END; - - return STATUS_SUCCESS; -} - -/** - * @brief - * Releases a buffer list of object types. - * - * @param[in] CapturedObjectTypeList - * A list of object types to free. - * - * @param[in] PreviousMode - * Processor access level mode. - * - * @return - * Nothing. - */ -static -VOID -SeReleaseObjectTypeList( - _In_ _Post_invalid_ POBJECT_TYPE_LIST CapturedObjectTypeList, - _In_ KPROCESSOR_MODE PreviousMode) -{ - if ((PreviousMode != KernelMode) && (CapturedObjectTypeList != NULL)) - ExFreePoolWithTag(CapturedObjectTypeList, TAG_SEPA); -} - /** * @unimplemented * @brief diff --git a/ntoskrnl/se/client.c b/ntoskrnl/se/client.c new file mode 100644 index 00000000000..773428009a9 --- /dev/null +++ b/ntoskrnl/se/client.c @@ -0,0 +1,331 @@ +/* + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Security client support routines + * COPYRIGHT: Copyright Alex Ionescu + */ + +/* INCLUDES *******************************************************************/ + +#include +#define NDEBUG +#include + +/* PRIVATE FUNCTIONS **********************************************************/ + +/** + * @brief + * Creates a client security context based upon an access token. + * + * @param[in] Token + * A valid token object. + * + * @param[in] ClientSecurityQos + * The Quality of Service (QoS) of a client security context. + * + * @param[in] ServerIsRemote + * If the client is a remote server (TRUE), the function will retrieve the + * control information of an access token, that is, we're doing delegation + * and that the server isn't local. + * + * @param[in] TokenType + * Type of token. + * + * @param[in] ThreadEffectiveOnly + * If set to TRUE, the client wants that the current thread wants to modify + * (enable or disable) privileges and groups. + * + * @param[in] ImpersonationLevel + * Security impersonation level filled in the QoS context. + * + * @param[out] ClientContext + * The returned security client context. + * + * @return + * Returns STATUS_SUCCESS if client security creation has completed successfully. + * STATUS_INVALID_PARAMETER is returned if one or more of the parameters are bogus. + * STATUS_BAD_IMPERSONATION_LEVEL is returned if the current impersonation level + * within QoS context doesn't meet with the conditions required. A failure + * NTSTATUS code is returned otherwise. + */ +NTSTATUS +NTAPI +SepCreateClientSecurity( + _In_ PACCESS_TOKEN Token, + _In_ PSECURITY_QUALITY_OF_SERVICE ClientSecurityQos, + _In_ BOOLEAN ServerIsRemote, + _In_ TOKEN_TYPE TokenType, + _In_ BOOLEAN ThreadEffectiveOnly, + _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, + _Out_ PSECURITY_CLIENT_CONTEXT ClientContext) +{ + NTSTATUS Status; + PACCESS_TOKEN NewToken; + PAGED_CODE(); + + /* Check for bogus impersonation level */ + if (!VALID_IMPERSONATION_LEVEL(ClientSecurityQos->ImpersonationLevel)) + { + /* Fail the call */ + return STATUS_INVALID_PARAMETER; + } + + /* Check what kind of token this is */ + if (TokenType != TokenImpersonation) + { + /* On a primary token, if we do direct access, copy the flag from the QOS */ + ClientContext->DirectAccessEffectiveOnly = ClientSecurityQos->EffectiveOnly; + } + else + { + /* This is an impersonation token, is the level ok? */ + if (ClientSecurityQos->ImpersonationLevel > ImpersonationLevel) + { + /* Nope, fail */ + return STATUS_BAD_IMPERSONATION_LEVEL; + } + + /* Is the level too low, or are we doing something other than delegation remotely */ + if ((ImpersonationLevel == SecurityAnonymous) || + (ImpersonationLevel == SecurityIdentification) || + ((ServerIsRemote) && (ImpersonationLevel != SecurityDelegation))) + { + /* Fail the call */ + return STATUS_BAD_IMPERSONATION_LEVEL; + } + + /* Pick either the thread setting or the QOS setting */ + ClientContext->DirectAccessEffectiveOnly = + ((ThreadEffectiveOnly) || (ClientSecurityQos->EffectiveOnly)) ? TRUE : FALSE; + } + + /* Is this static tracking */ + if (ClientSecurityQos->ContextTrackingMode == SECURITY_STATIC_TRACKING) + { + /* Do not use direct access and make a copy */ + ClientContext->DirectlyAccessClientToken = FALSE; + Status = SeCopyClientToken(Token, + ClientSecurityQos->ImpersonationLevel, + KernelMode, + &NewToken); + if (!NT_SUCCESS(Status)) + return Status; + } + else + { + /* Use direct access and check if this is local */ + ClientContext->DirectlyAccessClientToken = TRUE; + if (ServerIsRemote) + { + /* We are doing delegation, so make a copy of the control data */ + SeGetTokenControlInformation(Token, + &ClientContext->ClientTokenControl); + } + + /* Keep the same token */ + NewToken = Token; + } + + /* Fill out the context and return success */ + ClientContext->SecurityQos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); + ClientContext->SecurityQos.ImpersonationLevel = ClientSecurityQos->ImpersonationLevel; + ClientContext->SecurityQos.ContextTrackingMode = ClientSecurityQos->ContextTrackingMode; + ClientContext->SecurityQos.EffectiveOnly = ClientSecurityQos->EffectiveOnly; + ClientContext->ServerIsRemote = ServerIsRemote; + ClientContext->ClientToken = NewToken; + return STATUS_SUCCESS; +} + +/* PUBLIC FUNCTIONS ***********************************************************/ + +/** + * @brief + * Creates a client security context. + * + * @param[in] Thread + * Thread object of the client where impersonation has to begin. + * + * @param[in] Qos + * Quality of service to specify what kind of impersonation to be done. + * + * @param[in] RemoteClient + * If set to TRUE, the client that we're going to impersonate is remote. + * + * @param[out] ClientContext + * The returned security client context. + * + * @return + * See SepCreateClientSecurity. + */ +NTSTATUS +NTAPI +SeCreateClientSecurity( + _In_ PETHREAD Thread, + _In_ PSECURITY_QUALITY_OF_SERVICE Qos, + _In_ BOOLEAN RemoteClient, + _Out_ PSECURITY_CLIENT_CONTEXT ClientContext) +{ + TOKEN_TYPE TokenType; + BOOLEAN ThreadEffectiveOnly; + SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; + PACCESS_TOKEN Token; + NTSTATUS Status; + PAGED_CODE(); + + /* Reference the correct token */ + Token = PsReferenceEffectiveToken(Thread, + &TokenType, + &ThreadEffectiveOnly, + &ImpersonationLevel); + + /* Create client security from it */ + Status = SepCreateClientSecurity(Token, + Qos, + RemoteClient, + TokenType, + ThreadEffectiveOnly, + ImpersonationLevel, + ClientContext); + + /* Check if we failed or static tracking was used */ + if (!(NT_SUCCESS(Status)) || (Qos->ContextTrackingMode == SECURITY_STATIC_TRACKING)) + { + /* Dereference our copy since it's not being used */ + ObDereferenceObject(Token); + } + + /* Return status */ + return Status; +} + +/** + * @brief + * Creates a client security context based upon the captured security + * subject context. + * + * @param[in] SubjectContext + * The captured subject context where client security is to be created + * from. + * + * @param[in] ClientSecurityQos + * Quality of service to specify what kind of impersonation to be done. + * + * @param[in] ServerIsRemote + * If set to TRUE, the client that we're going to impersonate is remote. + * + * @param[out] ClientContext + * The returned security client context. + * + * @return + * See SepCreateClientSecurity. + */ +NTSTATUS +NTAPI +SeCreateClientSecurityFromSubjectContext( + _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext, + _In_ PSECURITY_QUALITY_OF_SERVICE ClientSecurityQos, + _In_ BOOLEAN ServerIsRemote, + _Out_ PSECURITY_CLIENT_CONTEXT ClientContext) +{ + PACCESS_TOKEN Token; + NTSTATUS Status; + PAGED_CODE(); + + /* Get the right token and reference it */ + Token = SeQuerySubjectContextToken(SubjectContext); + ObReferenceObject(Token); + + /* Create the context */ + Status = SepCreateClientSecurity(Token, + ClientSecurityQos, + ServerIsRemote, + SubjectContext->ClientToken ? + TokenImpersonation : TokenPrimary, + FALSE, + SubjectContext->ImpersonationLevel, + ClientContext); + + /* Check if we failed or static tracking was used */ + if (!(NT_SUCCESS(Status)) || + (ClientSecurityQos->ContextTrackingMode == SECURITY_STATIC_TRACKING)) + { + /* Dereference our copy since it's not being used */ + ObDereferenceObject(Token); + } + + /* Return status */ + return Status; +} + +/** + * @brief + * Extended function that impersonates a client. + * + * @param[in] ClientContext + * A valid client context. + * + * @param[in] ServerThread + * The thread where impersonation is to be done. + * + * @return + * STATUS_SUCCESS is returned if the calling thread successfully impersonates + * the client. A failure NTSTATUS code is returned otherwise. + */ +NTSTATUS +NTAPI +SeImpersonateClientEx( + _In_ PSECURITY_CLIENT_CONTEXT ClientContext, + _In_opt_ PETHREAD ServerThread) +{ + BOOLEAN EffectiveOnly; + PAGED_CODE(); + + /* Check if direct access is requested */ + if (!ClientContext->DirectlyAccessClientToken) + { + /* No, so get the flag from QOS */ + EffectiveOnly = ClientContext->SecurityQos.EffectiveOnly; + } + else + { + /* Yes, so see if direct access should be effective only */ + EffectiveOnly = ClientContext->DirectAccessEffectiveOnly; + } + + /* Use the current thread if one was not passed */ + if (!ServerThread) ServerThread = PsGetCurrentThread(); + + /* Call the lower layer routine */ + return PsImpersonateClient(ServerThread, + ClientContext->ClientToken, + TRUE, + EffectiveOnly, + ClientContext->SecurityQos.ImpersonationLevel); +} + +/** + * @brief + * Impersonates a client user. + * + * @param[in] ClientContext + * A valid client context. + * + * @param[in] ServerThread + * The thread where impersonation is to be done. + * * + * @return + * Nothing. + */ +VOID +NTAPI +SeImpersonateClient( + _In_ PSECURITY_CLIENT_CONTEXT ClientContext, + _In_opt_ PETHREAD ServerThread) +{ + PAGED_CODE(); + + /* Call the new API */ + SeImpersonateClientEx(ClientContext, ServerThread); +} + +/* EOF */ diff --git a/ntoskrnl/se/objtype.c b/ntoskrnl/se/objtype.c new file mode 100644 index 00000000000..f4648d1a8ec --- /dev/null +++ b/ntoskrnl/se/objtype.c @@ -0,0 +1,115 @@ +/* + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Security object type list support routines + * COPYRIGHT: Copyright Timo Kreuzer + */ + +/* INCLUDES *******************************************************************/ + +#include +#define NDEBUG +#include + +/* PRIVATE FUNCTIONS ***********************************************************/ + +/** + * @brief + * Captures a list of object types. + * + * @param[in] ObjectTypeList + * An existing list of object types. + * + * @param[in] ObjectTypeListLength + * The length size of the list. + * + * @param[in] PreviousMode + * Processor access level mode. + * + * @param[out] CapturedObjectTypeList + * The captured list of object types. + * + * @return + * Returns STATUS_SUCCESS if the list of object types has been captured + * successfully. STATUS_INVALID_PARAMETER is returned if the caller hasn't + * supplied a buffer list of object types. STATUS_INSUFFICIENT_RESOURCES + * is returned if pool memory allocation for the captured list has failed. + */ +NTSTATUS +SeCaptureObjectTypeList( + _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ KPROCESSOR_MODE PreviousMode, + _Out_ POBJECT_TYPE_LIST *CapturedObjectTypeList) +{ + SIZE_T Size; + + if (PreviousMode == KernelMode) + { + return STATUS_NOT_IMPLEMENTED; + } + + if (ObjectTypeListLength == 0) + { + *CapturedObjectTypeList = NULL; + return STATUS_SUCCESS; + } + + if (ObjectTypeList == NULL) + { + return STATUS_INVALID_PARAMETER; + } + + /* Calculate the list size and check for integer overflow */ + Size = ObjectTypeListLength * sizeof(OBJECT_TYPE_LIST); + if (Size == 0) + { + return STATUS_INVALID_PARAMETER; + } + + /* Allocate a new list */ + *CapturedObjectTypeList = ExAllocatePoolWithTag(PagedPool, Size, TAG_SEPA); + if (*CapturedObjectTypeList == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + _SEH2_TRY + { + ProbeForRead(ObjectTypeList, Size, sizeof(ULONG)); + RtlCopyMemory(*CapturedObjectTypeList, ObjectTypeList, Size); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + ExFreePoolWithTag(*CapturedObjectTypeList, TAG_SEPA); + *CapturedObjectTypeList = NULL; + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } + _SEH2_END; + + return STATUS_SUCCESS; +} + +/** + * @brief + * Releases a buffer list of object types. + * + * @param[in] CapturedObjectTypeList + * A list of object types to free. + * + * @param[in] PreviousMode + * Processor access level mode. + * + * @return + * Nothing. + */ +VOID +SeReleaseObjectTypeList( + _In_ _Post_invalid_ POBJECT_TYPE_LIST CapturedObjectTypeList, + _In_ KPROCESSOR_MODE PreviousMode) +{ + if ((PreviousMode != KernelMode) && (CapturedObjectTypeList != NULL)) + ExFreePoolWithTag(CapturedObjectTypeList, TAG_SEPA); +} + +/* EOF */ diff --git a/ntoskrnl/se/sid.c b/ntoskrnl/se/sid.c index 13aeba2662b..3d4d8931a15 100644 --- a/ntoskrnl/se/sid.c +++ b/ntoskrnl/se/sid.c @@ -412,6 +412,146 @@ SepReleaseSid( } } +/** + * @brief + * Checks if a SID is present in a token. + * + * @param[in] _Token + * A valid token object. + * + * @param[in] PrincipalSelfSid + * A principal self SID. + * + * @param[in] _Sid + * A regular SID. + * + * @param[in] Deny + * If set to TRUE, the caller expected that a SID in a token + * must be a deny-only SID, that is, access checks are performed + * only for deny-only ACEs of the said SID. + * + * @param[in] Restricted + * If set to TRUE, the caller expects that a SID in a token is + * restricted (by the general definition, a token is restricted). + * + * @return + * Returns TRUE if the specified SID in the call is present in the token, + * FALSE otherwise. + */ +BOOLEAN +NTAPI +SepSidInTokenEx( + _In_ PACCESS_TOKEN _Token, + _In_ PSID PrincipalSelfSid, + _In_ PSID _Sid, + _In_ BOOLEAN Deny, + _In_ BOOLEAN Restricted) +{ + ULONG SidIndex; + PTOKEN Token = (PTOKEN)_Token; + PISID TokenSid, Sid = (PISID)_Sid; + PSID_AND_ATTRIBUTES SidAndAttributes; + ULONG SidCount, SidLength; + USHORT SidMetadata; + PAGED_CODE(); + + /* Check if a principal SID was given, and this is our current SID already */ + if ((PrincipalSelfSid) && (RtlEqualSid(SePrincipalSelfSid, Sid))) + { + /* Just use the principal SID in this case */ + Sid = PrincipalSelfSid; + } + + /* Check if this is a restricted token or not */ + if (Restricted) + { + /* Use the restricted SIDs and count */ + SidAndAttributes = Token->RestrictedSids; + SidCount = Token->RestrictedSidCount; + } + else + { + /* Use the normal SIDs and count */ + SidAndAttributes = Token->UserAndGroups; + SidCount = Token->UserAndGroupCount; + } + + /* Do checks here by hand instead of the usual 4 function calls */ + SidLength = FIELD_OFFSET(SID, + SubAuthority[Sid->SubAuthorityCount]); + SidMetadata = *(PUSHORT)&Sid->Revision; + + /* Loop every SID */ + for (SidIndex = 0; SidIndex < SidCount; SidIndex++) + { + TokenSid = (PISID)SidAndAttributes->Sid; +#if SE_SID_DEBUG + UNICODE_STRING sidString; + RtlConvertSidToUnicodeString(&sidString, TokenSid, TRUE); + DPRINT1("SID in Token: %wZ\n", &sidString); + RtlFreeUnicodeString(&sidString); +#endif + /* Check if the SID metadata matches */ + if (*(PUSHORT)&TokenSid->Revision == SidMetadata) + { + /* Check if the SID data matches */ + if (RtlEqualMemory(Sid, TokenSid, SidLength)) + { + /* + * Check if the group is enabled, or used for deny only. + * Otherwise we have to check if this is the first user. + * We understand that by looking if this SID is not + * restricted, this is the first element we are iterating + * and that it doesn't have SE_GROUP_USE_FOR_DENY_ONLY + * attribute. + */ + if ((!Restricted && (SidIndex == 0) && !(SidAndAttributes->Attributes & SE_GROUP_USE_FOR_DENY_ONLY)) || + (SidAndAttributes->Attributes & SE_GROUP_ENABLED) || + ((Deny) && (SidAndAttributes->Attributes & SE_GROUP_USE_FOR_DENY_ONLY))) + { + /* SID is present */ + return TRUE; + } + else + { + /* SID is not present */ + return FALSE; + } + } + } + + /* Move to the next SID */ + SidAndAttributes++; + } + + /* SID is not present */ + return FALSE; +} + +/** + * @brief + * Checks if a SID is present in a token. + * + * @param[in] _Token + * A valid token object. + * + * @param[in] _Sid + * A regular SID. + * + * @return + * Returns TRUE if the specified SID in the call is present in the token, + * FALSE otherwise. + */ +BOOLEAN +NTAPI +SepSidInToken( + _In_ PACCESS_TOKEN _Token, + _In_ PSID Sid) +{ + /* Call extended API */ + return SepSidInTokenEx(_Token, NULL, Sid, FALSE, FALSE); +} + /** * @brief * Captures a security identifier from a diff --git a/ntoskrnl/se/subject.c b/ntoskrnl/se/subject.c new file mode 100644 index 00000000000..51a9e132028 --- /dev/null +++ b/ntoskrnl/se/subject.c @@ -0,0 +1,185 @@ +/* + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Security subject context support routines + * COPYRIGHT: Copyright Alex Ionescu + */ + +/* INCLUDES *******************************************************************/ + +#include +#define NDEBUG +#include + +/* GLOBALS ********************************************************************/ + +ERESOURCE SepSubjectContextLock; + +/* PUBLIC FUNCTIONS ***********************************************************/ + +/** + * @brief + * An extended function that captures the security subject context based upon + * the specified thread and process. + * + * @param[in] Thread + * A thread where the calling thread's token is to be referenced for + * the security context. + * + * @param[in] Process + * A process where the main process' token is to be referenced for + * the security context. + * + * @param[out] SubjectContext + * The returned security subject context. + * + * @return + * Nothing. + */ +VOID +NTAPI +SeCaptureSubjectContextEx( + _In_ PETHREAD Thread, + _In_ PEPROCESS Process, + _Out_ PSECURITY_SUBJECT_CONTEXT SubjectContext) +{ + BOOLEAN CopyOnOpen, EffectiveOnly; + + PAGED_CODE(); + + /* Save the unique ID */ + SubjectContext->ProcessAuditId = Process->UniqueProcessId; + + /* Check if we have a thread */ + if (!Thread) + { + /* We don't, so no token */ + SubjectContext->ClientToken = NULL; + } + else + { + /* Get the impersonation token */ + SubjectContext->ClientToken = PsReferenceImpersonationToken(Thread, + &CopyOnOpen, + &EffectiveOnly, + &SubjectContext->ImpersonationLevel); + } + + /* Get the primary token */ + SubjectContext->PrimaryToken = PsReferencePrimaryToken(Process); +} + +/** + * @brief + * Captures the security subject context of the calling thread and calling + * process. + * + * @param[out] SubjectContext + * The returned security subject context. + * + * @return + * Nothing. + */ +VOID +NTAPI +SeCaptureSubjectContext( + _Out_ PSECURITY_SUBJECT_CONTEXT SubjectContext) +{ + /* Call the extended API */ + SeCaptureSubjectContextEx(PsGetCurrentThread(), + PsGetCurrentProcess(), + SubjectContext); +} + +/** + * @brief + * Locks both the referenced primary and client access tokens of a + * security subject context. + * + * @param[in] SubjectContext + * A valid security context with both referenced tokens. + * + * @return + * Nothing. + */ +VOID +NTAPI +SeLockSubjectContext( + _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext) +{ + PTOKEN PrimaryToken, ClientToken; + PAGED_CODE(); + + /* Read both tokens */ + PrimaryToken = SubjectContext->PrimaryToken; + ClientToken = SubjectContext->ClientToken; + + /* Always lock the primary */ + SepAcquireTokenLockShared(PrimaryToken); + + /* Lock the impersonation one if it's there */ + if (!ClientToken) return; + SepAcquireTokenLockShared(ClientToken); +} + +/** + * @brief + * Unlocks both the referenced primary and client access tokens of a + * security subject context. + * + * @param[in] SubjectContext + * A valid security context with both referenced tokens. + * + * @return + * Nothing. + */ +VOID +NTAPI +SeUnlockSubjectContext( + _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext) +{ + PTOKEN PrimaryToken, ClientToken; + PAGED_CODE(); + + /* Read both tokens */ + PrimaryToken = SubjectContext->PrimaryToken; + ClientToken = SubjectContext->ClientToken; + + /* Unlock the impersonation one if it's there */ + if (ClientToken) + { + SepReleaseTokenLock(ClientToken); + } + + /* Always unlock the primary one */ + SepReleaseTokenLock(PrimaryToken); +} + +/** + * @brief + * Releases both the primary and client tokens of a security + * subject context. + * + * @param[in] SubjectContext + * The captured security context. + * + * @return + * Nothing. + */ +VOID +NTAPI +SeReleaseSubjectContext( + _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext) +{ + PAGED_CODE(); + + /* Drop reference on the primary */ + ObFastDereferenceObject(&PsGetCurrentProcess()->Token, SubjectContext->PrimaryToken); + SubjectContext->PrimaryToken = NULL; + + /* Drop reference on the impersonation, if there was one */ + PsDereferenceImpersonationToken(SubjectContext->ClientToken); + SubjectContext->ClientToken = NULL; +} + +/* EOF */ diff --git a/ntoskrnl/se/token.c b/ntoskrnl/se/token.c index e773358b356..83b962f1ba9 100644 --- a/ntoskrnl/se/token.c +++ b/ntoskrnl/se/token.c @@ -1,7 +1,7 @@ /* * PROJECT: ReactOS Kernel * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) - * PURPOSE: Security token implementation support + * PURPOSE: Security access token implementation base support routines * COPYRIGHT: Copyright David Welch * Copyright 2021-2022 George Bișoc */ @@ -12,8 +12,6 @@ #define NDEBUG #include -#include - /* GLOBALS ********************************************************************/ POBJECT_TYPE SeTokenObjectType = NULL; @@ -29,48 +27,7 @@ static GENERIC_MAPPING SepTokenMapping = { 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 *****************************************************************/ +/* PRIVATE FUNCTIONS *****************************************************************/ /** * @brief @@ -84,7 +41,6 @@ static const INFORMATION_CLASS_INFO SeTokenInformationClass[] = { * completed successfully, otherwise STATUS_INSUFFICIENT_RESOURCES on a * pool allocation failure. */ -static NTSTATUS SepCreateTokenLock( _Inout_ PTOKEN Token) @@ -114,7 +70,6 @@ SepCreateTokenLock( * @return * Nothing. */ -static VOID SepDeleteTokenLock( _Inout_ PTOKEN Token) @@ -483,7 +438,6 @@ SepImpersonateAnonymousToken( * @return * Nothing. */ -static VOID SepUpdateSinglePrivilegeFlagToken( _Inout_ PTOKEN Token, @@ -534,6 +488,56 @@ SepUpdateSinglePrivilegeFlagToken( } } +/** + * @brief + * Checks if a token belongs to the main user, being the owner. + * + * @param[in] _Token + * A valid token object. + * + * @param[in] SecurityDescriptor + * A security descriptor where the owner is to be found. + * + * @param[in] TokenLocked + * If set to TRUE, the token has been already locked and there's + * no need to lock it again. Otherwise the function will acquire + * the lock. + * + * @return + * Returns TRUE if the token belongs to a owner, FALSE otherwise. + */ +BOOLEAN +NTAPI +SepTokenIsOwner( + _In_ PACCESS_TOKEN _Token, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ BOOLEAN TokenLocked) +{ + PSID Sid; + BOOLEAN Result; + PTOKEN Token = _Token; + + /* Get the owner SID */ + Sid = SepGetOwnerFromDescriptor(SecurityDescriptor); + ASSERT(Sid != NULL); + + /* Lock the token if needed */ + if (!TokenLocked) SepAcquireTokenLockShared(Token); + + /* Check if the owner SID is found, handling restricted case as well */ + Result = SepSidInToken(Token, Sid); + if ((Result) && (Token->TokenFlags & TOKEN_IS_RESTRICTED)) + { + Result = SepSidInTokenEx(Token, NULL, Sid, FALSE, TRUE); + } + + /* Release the lock if we had acquired it */ + if (!TokenLocked) SepReleaseTokenLock(Token); + + /* Return the result */ + return Result; +} + /** * @brief * Updates the token's flags based upon the privilege that the token @@ -546,7 +550,6 @@ SepUpdateSinglePrivilegeFlagToken( * @return * Nothing. */ -static VOID SepUpdatePrivilegeFlagsToken( _Inout_ PTOKEN Token) @@ -575,7 +578,6 @@ SepUpdatePrivilegeFlagsToken( * @return * Nothing. */ -static VOID SepRemovePrivilegeToken( _Inout_ PTOKEN Token, @@ -612,7 +614,6 @@ SepRemovePrivilegeToken( * @return * Nothing. */ -static VOID SepRemoveUserGroupToken( _Inout_ PTOKEN Token, @@ -819,7 +820,7 @@ SeDeassignPrimaryToken( * @return * Returns the total length of a SID size. */ -static ULONG +ULONG RtlLengthSidAndAttributes( _In_ ULONG Count, _In_ PSID_AND_ATTRIBUTES Src) @@ -865,7 +866,7 @@ RtlLengthSidAndAttributes( * 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 +NTSTATUS SepFindPrimaryGroupAndDefaultOwner( _In_ PTOKEN Token, _In_ PSID PrimaryGroup, @@ -959,349 +960,6 @@ SepFindPrimaryGroupAndDefaultOwner( 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 */ - 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 */ - SepAcquireTokenLockShared(Token); - - AccessToken->SessionId = Token->SessionId; - AccessToken->ModifiedId = Token->ModifiedId; - - AccessToken->TokenFlags = Token->TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED; - - /* Reference the logon session */ - Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId); - if (!NT_SUCCESS(Status)) - { - /* No logon session could be found, bail out */ - DPRINT1("SepRmReferenceLogonSession() failed (Status 0x%lx)\n", Status); - /* Set the flag for proper cleanup by the delete procedure */ - AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED; - goto Quit; - } - - /* Insert the referenced logon session into the token */ - Status = SepRmInsertLogonSessionIntoToken(AccessToken); - if (!NT_SUCCESS(Status)) - { - /* Failed to insert the logon session into the token, bail out */ - DPRINT1("SepRmInsertLogonSessionIntoToken() failed (Status 0x%lx)\n", Status); - goto Quit; - } - - /* Fill in token debug information */ -#if DBG - RtlCopyMemory(AccessToken->ImageFileName, - PsGetCurrentProcess()->ImageFileName, - min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName))); - - AccessToken->ProcessCid = PsGetCurrentProcessId(); - AccessToken->ThreadCid = PsGetCurrentThreadId(); - AccessToken->CreateMethod = TOKEN_DUPLICATE_METHOD; -#endif - - /* Assign the data that reside in the token's variable information area */ - AccessToken->VariableLength = VariableLength; - EndMem = (PVOID)&AccessToken->VariablePart; - - /* Copy the privileges */ - AccessToken->PrivilegeCount = 0; - AccessToken->Privileges = NULL; - if (Token->Privileges && (Token->PrivilegeCount > 0)) - { - ULONG PrivilegesLength = Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES); - PrivilegesLength = ALIGN_UP_BY(PrivilegesLength, sizeof(PVOID)); - - ASSERT(VariableLength >= PrivilegesLength); - - AccessToken->PrivilegeCount = Token->PrivilegeCount; - AccessToken->Privileges = EndMem; - EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength); - VariableLength -= PrivilegesLength; - - RtlCopyMemory(AccessToken->Privileges, - Token->Privileges, - AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); - } - - /* Copy the user and groups */ - AccessToken->UserAndGroupCount = 0; - AccessToken->UserAndGroups = NULL; - if (Token->UserAndGroups && (Token->UserAndGroupCount > 0)) - { - AccessToken->UserAndGroupCount = Token->UserAndGroupCount; - AccessToken->UserAndGroups = EndMem; - EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount]; - VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups); - - Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount, - Token->UserAndGroups, - VariableLength, - AccessToken->UserAndGroups, - EndMem, - &EndMem, - &VariableLength); - if (!NT_SUCCESS(Status)) - { - DPRINT1("RtlCopySidAndAttributesArray(UserAndGroups) failed (Status 0x%lx)\n", Status); - goto Quit; - } - } - - /* Find the token primary group */ - Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken, - Token->PrimaryGroup, - NULL, - &PrimaryGroupIndex, - NULL); - if (!NT_SUCCESS(Status)) - { - DPRINT1("SepFindPrimaryGroupAndDefaultOwner failed (Status 0x%lx)\n", Status); - goto Quit; - } - - 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); - } - - /* Return the token to the caller */ - *NewAccessToken = AccessToken; - Status = STATUS_SUCCESS; - -Quit: - if (!NT_SUCCESS(Status)) - { - /* Dereference the token, the delete procedure will clean it up */ - ObDereferenceObject(AccessToken); - } - - /* Unlock the source token */ - SepReleaseTokenLock(Token); - - return Status; -} - /** * @brief * Subtracts a token in exchange of duplicating a new one. @@ -1638,954 +1296,39 @@ SeAssignPrimaryToken( /** * @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. + * Retrieves token control information. * - * @param[out] TokenHandle - * Valid token handle that's ready for use after token creation and object insertion. + * @param[in] _Token + * A valid token object. * - * @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. + * @param[out] SecurityDescriptor + * The returned token control information. * * @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. + * Nothing. */ -NTSTATUS +VOID 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) +SeGetTokenControlInformation( + _In_ PACCESS_TOKEN _Token, + _Out_ PTOKEN_CONTROL TokenControl) { - 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; - + PTOKEN Token = _Token; 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); - } + /* Capture the main fields */ + TokenControl->AuthenticationId = Token->AuthenticationId; + TokenControl->TokenId = Token->TokenId; + TokenControl->TokenSource = Token->TokenSource; - /* 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); - - AccessToken->TokenId = TokenId; - AccessToken->TokenType = TokenType; - AccessToken->ImpersonationLevel = ImpersonationLevel; - - /* Initialise the lock for the access token */ - Status = SepCreateTokenLock(AccessToken); - if (!NT_SUCCESS(Status)) - goto Quit; - - AccessToken->TokenSource.SourceIdentifier = TokenSource->SourceIdentifier; - RtlCopyMemory(AccessToken->TokenSource.SourceName, - TokenSource->SourceName, - sizeof(TokenSource->SourceName)); - - AccessToken->ExpirationTime = *ExpirationTime; - AccessToken->ModifiedId = ModifiedId; - - AccessToken->TokenFlags = TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED; - - /* Copy and reference the logon session */ - AccessToken->AuthenticationId = *AuthenticationId; - Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId); - if (!NT_SUCCESS(Status)) - { - /* No logon session could be found, bail out */ - DPRINT1("SepRmReferenceLogonSession() failed (Status 0x%lx)\n", Status); - /* Set the flag for proper cleanup by the delete procedure */ - AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED; - goto Quit; - } - - /* Insert the referenced logon session into the token */ - Status = SepRmInsertLogonSessionIntoToken(AccessToken); - if (!NT_SUCCESS(Status)) - { - /* Failed to insert the logon session into the token, bail out */ - DPRINT1("SepRmInsertLogonSessionIntoToken() failed (Status 0x%lx)\n", Status); - goto Quit; - } - - /* Fill in token debug information */ -#if DBG - /* - * We must determine ourselves that the current - * process is not the initial CPU one. The initial - * process is not a "real" process, that is, the - * Process Manager has not yet been initialized and - * as a matter of fact we are creating a token before - * any process gets created by Ps. If it turns out - * that the current process is the initial CPU process - * where token creation execution takes place, don't - * do anything. - */ - if (PsGetCurrentProcess() != &KiInitialProcess) - { - RtlCopyMemory(AccessToken->ImageFileName, - PsGetCurrentProcess()->ImageFileName, - min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName))); - - AccessToken->ProcessCid = PsGetCurrentProcessId(); - AccessToken->ThreadCid = PsGetCurrentThreadId(); - } - - AccessToken->CreateMethod = TOKEN_CREATE_METHOD; -#endif - - /* Assign the data that reside in the token's variable information area */ - AccessToken->VariableLength = VariableLength; - EndMem = (PVOID)&AccessToken->VariablePart; - - /* Copy the privileges */ - AccessToken->PrivilegeCount = PrivilegeCount; - AccessToken->Privileges = NULL; - if (PrivilegeCount > 0) - { - AccessToken->Privileges = EndMem; - EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength); - VariableLength -= PrivilegesLength; - - if (PreviousMode != KernelMode) - { - _SEH2_TRY - { - RtlCopyMemory(AccessToken->Privileges, - Privileges, - PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); - } - _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) - { - Status = _SEH2_GetExceptionCode(); - } - _SEH2_END; - } - else - { - RtlCopyMemory(AccessToken->Privileges, - Privileges, - PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); - } - - if (!NT_SUCCESS(Status)) - goto Quit; - } - - /* Update the privilege flags */ - SepUpdatePrivilegeFlagsToken(AccessToken); - - /* Copy the user and groups */ - AccessToken->UserAndGroupCount = 1 + GroupCount; - AccessToken->UserAndGroups = EndMem; - EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount]; - VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups); - - Status = RtlCopySidAndAttributesArray(1, - User, - VariableLength, - &AccessToken->UserAndGroups[0], - EndMem, - &EndMem, - &VariableLength); - if (!NT_SUCCESS(Status)) - goto Quit; - - Status = RtlCopySidAndAttributesArray(GroupCount, - Groups, - VariableLength, - &AccessToken->UserAndGroups[1], - EndMem, - &EndMem, - &VariableLength); - if (!NT_SUCCESS(Status)) - goto Quit; - - /* Find the token primary group and default owner */ - Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken, - PrimaryGroup, - Owner, - &PrimaryGroupIndex, - &DefaultOwnerIndex); - if (!NT_SUCCESS(Status)) - { - DPRINT1("SepFindPrimaryGroupAndDefaultOwner failed (Status 0x%lx)\n", Status); - goto Quit; - } - - 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) -{ - NTSTATUS Status; - PTOKEN AccessToken; - 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 source token is valid, and lock it */ - ASSERT(Token); + /* Lock the token */ SepAcquireTokenLockShared(Token); - /* Assume the caller doesn't want privileges disabled */ - WantPrivilegesDisabled = FALSE; + /* Capture the modified ID */ + TokenControl->ModifiedId = Token->ModifiedId; - /* 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); - - /* Unlock the source token and bail out */ - SepReleaseTokenLock(Token); - return Status; - } - - /* Initialize the token and begin filling stuff to it */ - RtlZeroMemory(AccessToken, TotalSize); - - /* Set up a lock for the new token */ - Status = SepCreateTokenLock(AccessToken); - if (!NT_SUCCESS(Status)) - goto Quit; - - /* Allocate new IDs for the token */ - ExAllocateLocallyUniqueId(&AccessToken->TokenId); - ExAllocateLocallyUniqueId(&AccessToken->ModifiedId); - - /* Copy the type and impersonation level from the token */ - AccessToken->TokenType = Token->TokenType; - AccessToken->ImpersonationLevel = Token->ImpersonationLevel; - - /* Copy the immutable fields */ - AccessToken->TokenSource.SourceIdentifier = Token->TokenSource.SourceIdentifier; - RtlCopyMemory(AccessToken->TokenSource.SourceName, - Token->TokenSource.SourceName, - sizeof(Token->TokenSource.SourceName)); - - AccessToken->AuthenticationId = Token->AuthenticationId; - AccessToken->ParentTokenId = Token->TokenId; - AccessToken->OriginatingLogonSession = Token->OriginatingLogonSession; - - AccessToken->ExpirationTime = Token->ExpirationTime; - - /* Copy the mutable fields */ - AccessToken->SessionId = Token->SessionId; - AccessToken->TokenFlags = Token->TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED; - - /* Reference the logon session */ - Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId); - if (!NT_SUCCESS(Status)) - { - /* We failed, bail out*/ - DPRINT1("SepPerformTokenFiltering(): Failed to reference the logon session (Status 0x%lx)\n", Status); - AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED; - goto Quit; - } - - /* Insert the referenced logon session into the token */ - Status = SepRmInsertLogonSessionIntoToken(AccessToken); - if (!NT_SUCCESS(Status)) - { - /* Failed to insert the logon session into the token, bail out */ - DPRINT1("SepPerformTokenFiltering(): Failed to insert the logon session into token (Status 0x%lx)\n", Status); - goto Quit; - } - - /* Fill in token debug information */ -#if DBG - RtlCopyMemory(AccessToken->ImageFileName, - PsGetCurrentProcess()->ImageFileName, - min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName))); - - AccessToken->ProcessCid = PsGetCurrentProcessId(); - AccessToken->ThreadCid = PsGetCurrentThreadId(); - AccessToken->CreateMethod = TOKEN_FILTER_METHOD; -#endif - - /* Assign the data that reside in the token's variable information area */ - AccessToken->VariableLength = VariableLength; - EndMem = (PVOID)&AccessToken->VariablePart; - - /* Copy the privileges from the existing token */ - AccessToken->PrivilegeCount = 0; - AccessToken->Privileges = NULL; - if (Token->Privileges && (Token->PrivilegeCount > 0)) - { - PrivilegesLength = Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES); - PrivilegesLength = ALIGN_UP_BY(PrivilegesLength, sizeof(PVOID)); - - /* - * Ensure that the token can actually hold all - * the privileges from the existing token. - * Otherwise something's seriously wrong and - * we've to guard ourselves. - */ - ASSERT(VariableLength >= PrivilegesLength); - - AccessToken->PrivilegeCount = Token->PrivilegeCount; - AccessToken->Privileges = EndMem; - EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength); - VariableLength -= PrivilegesLength; - - RtlCopyMemory(AccessToken->Privileges, - Token->Privileges, - AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); - } - - /* Copy the user and groups */ - AccessToken->UserAndGroupCount = 0; - AccessToken->UserAndGroups = NULL; - if (Token->UserAndGroups && (Token->UserAndGroupCount > 0)) - { - AccessToken->UserAndGroupCount = Token->UserAndGroupCount; - AccessToken->UserAndGroups = EndMem; - EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount]; - VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups); - - Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount, - Token->UserAndGroups, - VariableLength, - AccessToken->UserAndGroups, - EndMem, - &EndMem, - &VariableLength); - if (!NT_SUCCESS(Status)) - { - DPRINT1("SepPerformTokenFiltering(): Failed to copy the groups into token (Status 0x%lx)\n", Status); - goto Quit; - } - } - - /* Copy the restricted SIDs */ - AccessToken->RestrictedSidCount = 0; - AccessToken->RestrictedSids = NULL; - if (Token->RestrictedSids && (Token->RestrictedSidCount > 0)) - { - AccessToken->RestrictedSidCount = Token->RestrictedSidCount; - AccessToken->RestrictedSids = EndMem; - EndMem = &AccessToken->RestrictedSids[AccessToken->RestrictedSidCount]; - VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->RestrictedSids); - - Status = RtlCopySidAndAttributesArray(AccessToken->RestrictedSidCount, - Token->RestrictedSids, - VariableLength, - AccessToken->RestrictedSids, - EndMem, - &EndMem, - &VariableLength); - if (!NT_SUCCESS(Status)) - { - DPRINT1("SepPerformTokenFiltering(): Failed to copy the restricted SIDs into token (Status 0x%lx)\n", Status); - goto Quit; - } - } - - /* - * Insert the restricted SIDs into the token on - * the request by the caller. - */ - if (RestrictedSidsIntoToken != NULL) - { - for (RestrictedSidsInList = 0; RestrictedSidsInList < RestrictedSidsCount; RestrictedSidsInList++) - { - /* Did the caller assign attributes to the restricted SIDs? */ - if (RestrictedSidsIntoToken[RestrictedSidsInList].Attributes != 0) - { - /* There mustn't be any attributes, bail out */ - DPRINT1("SepPerformTokenFiltering(): There mustn't be any attributes to restricted SIDs!\n"); - Status = STATUS_INVALID_PARAMETER; - goto Quit; - } - } - - /* - * Ensure that the token can hold the restricted SIDs - * (the variable length is calculated at the beginning - * of the routine call). - */ - ASSERT(VariableLength >= RestrictedSidsLength); - - /* - * Now let's begin inserting the restricted SIDs into the filtered - * access token from the list the caller gave us. - */ - AccessToken->RestrictedSidCount = RestrictedSidsCount; - AccessToken->RestrictedSids = EndMem; - EndMem = (PVOID)((ULONG_PTR)EndMem + RestrictedSidsLength); - VariableLength -= RestrictedSidsLength; - - RtlCopyMemory(AccessToken->RestrictedSids, - RestrictedSidsIntoToken, - AccessToken->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES)); - - /* - * As we've copied the restricted SIDs into - * the token, we must assign them the following - * combination of attributes SE_GROUP_ENABLED, - * SE_GROUP_ENABLED_BY_DEFAULT and SE_GROUP_MANDATORY. - * With such attributes we estabilish that restricting - * SIDs into the token are enabled for access checks. - */ - for (RestrictedSidsInToken = 0; RestrictedSidsInToken < AccessToken->RestrictedSidCount; RestrictedSidsInToken++) - { - AccessToken->RestrictedSids[RestrictedSidsInToken].Attributes |= (SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY); - } - - /* - * As we added restricted SIDs into the token, mark - * it as restricted. - */ - AccessToken->TokenFlags |= TOKEN_IS_RESTRICTED; - } - - /* Search for the primary group */ - Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken, - Token->PrimaryGroup, - NULL, - &PrimaryGroupIndex, - NULL); - if (!NT_SUCCESS(Status)) - { - DPRINT1("SepPerformTokenFiltering(): Failed searching for the primary group (Status 0x%lx)\n", Status); - goto Quit; - } - - /* 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; - } - } - } - - /* We've finally filtered the token, return it to the caller */ - *FilteredToken = AccessToken; - Status = STATUS_SUCCESS; - DPRINT("SepPerformTokenFiltering(): The token has been filtered!\n"); - -Quit: - if (!NT_SUCCESS(Status)) - { - /* Dereference the created token */ - ObDereferenceObject(AccessToken); - } - - /* Unlock the source token */ + /* Unlock it */ SepReleaseTokenLock(Token); - - return Status; } /** @@ -2868,477 +1611,6 @@ SepCreateSystemAnonymousLogonTokenNoEveryone(VOID) /* PUBLIC FUNCTIONS ***********************************************************/ -/** - * @brief - * Filters an access token from an existing token, making it more restricted - * than the previous one. - * - * @param[in] ExistingToken - * An existing token for filtering. - * - * @param[in] Flags - * Privilege flag options. This parameter argument influences how the token - * is filtered. Such parameter can be 0. See NtFilterToken syscall for - * more information. - * - * @param[in] SidsToDisable - * Array of SIDs to disable. Such parameter can be NULL. - * - * @param[in] PrivilegesToDelete - * Array of privileges to delete. If DISABLE_MAX_PRIVILEGE flag is specified - * in the Flags parameter, PrivilegesToDelete is ignored. - * - * @param[in] RestrictedSids - * An array of restricted SIDs for the new filtered token. Such parameter - * can be NULL. - * - * @param[out] FilteredToken - * The newly filtered token, returned to the caller. - * - * @return - * Returns STATUS_SUCCESS if the function has successfully completed its - * operations and that the access token has been filtered. STATUS_INVALID_PARAMETER - * is returned if one or more of the parameter are not valid. A failure NTSTATUS code - * is returned otherwise. - */ -NTSTATUS -NTAPI -SeFilterToken( - _In_ PACCESS_TOKEN ExistingToken, - _In_ ULONG Flags, - _In_opt_ PTOKEN_GROUPS SidsToDisable, - _In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete, - _In_opt_ PTOKEN_GROUPS RestrictedSids, - _Out_ PACCESS_TOKEN *FilteredToken) -{ - NTSTATUS Status; - PTOKEN AccessToken; - ULONG PrivilegesCount = 0; - ULONG SidsCount = 0; - ULONG RestrictedSidsCount = 0; - - PAGED_CODE(); - - /* Begin copying the counters */ - if (SidsToDisable != NULL) - { - SidsCount = SidsToDisable->GroupCount; - } - - if (PrivilegesToDelete != NULL) - { - PrivilegesCount = PrivilegesToDelete->PrivilegeCount; - } - - if (RestrictedSids != NULL) - { - RestrictedSidsCount = RestrictedSids->GroupCount; - } - - /* Call the internal API */ - Status = SepPerformTokenFiltering(ExistingToken, - PrivilegesToDelete->Privileges, - SidsToDisable->Groups, - RestrictedSids->Groups, - PrivilegesCount, - SidsCount, - RestrictedSidsCount, - Flags, - KernelMode, - &AccessToken); - if (!NT_SUCCESS(Status)) - { - DPRINT1("SeFilterToken(): Failed to filter the token (Status 0x%lx)\n", Status); - return Status; - } - - /* Insert the filtered token */ - Status = ObInsertObject(AccessToken, - NULL, - 0, - 0, - NULL, - NULL); - if (!NT_SUCCESS(Status)) - { - DPRINT1("SeFilterToken(): Failed to insert the filtered token (Status 0x%lx)\n", Status); - return Status; - } - - /* Return it to the caller */ - *FilteredToken = AccessToken; - return Status; -} - -/** - * @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. @@ -3613,2555 +1885,6 @@ Quit: /* 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) - { - 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, validate, 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 valid */ - 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; - - 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); - } - - return Status; - } - - /* Lock the token */ - SepAcquireTokenLockExclusive(Token); - - /* 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); - - /* 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. @@ -6505,286 +2228,6 @@ NtCompareTokens( return Status; } -/** - * @brief - * Creates an access token in a restricted form - * from the original existing token, that is, such - * action is called filtering. - * - * @param[in] ExistingTokenHandle - * A handle to an access token which is to be filtered. - * - * @param[in] Flags - * Privilege flag options. This parameter argument influences how the - * token's privileges are filtered. For further details see remarks. - * - * @param[in] SidsToDisable - * Array of SIDs to disable. The action of doing so assigns the - * SE_GROUP_USE_FOR_DENY_ONLY attribute to the respective group - * SID and takes away SE_GROUP_ENABLED and SE_GROUP_ENABLED_BY_DEFAULT. - * This parameter can be NULL. This can be a UM pointer. - * - * @param[in] PrivilegesToDelete - * Array of privileges to delete. The function will walk within this - * array to determine if the specified privileges do exist in the - * access token. Any missing privileges gets ignored. This parameter - * can be NULL. This can be a UM pointer. - * - * @param[in] RestrictedSids - * An array list of restricted groups SID to be added in the access - * token. A token that is already restricted the newly added restricted - * SIDs are redundant information in addition to the existing restricted - * SIDs in the token. This parameter can be NULL. This can be a UM pointer. - * - * @param[out] NewTokenHandle - * A new handle to the restricted (filtered) access token. This can be a - * UM pointer. - * - * @return - * Returns STATUS_SUCCESS if the routine has successfully filtered the - * access token. STATUS_INVALID_PARAMETER is returned if one or more - * parameters are not valid (see SepPerformTokenFiltering routine call - * for more information). A failure NTSTATUS code is returned otherwise. - * - * @remarks - * The Flags parameter determines the final outcome of how the privileges - * in an access token are filtered. This parameter can take these supported - * values (these can be combined): - * - * 0 -- Filter the token's privileges in the usual way. The function expects - * that the caller MUST PROVIDE a valid array list of privileges to be - * deleted (that is, PrivilegesToDelete MUSTN'T BE NULL). - * - * DISABLE_MAX_PRIVILEGE -- Disables (deletes) all the privileges except SeChangeNotifyPrivilege - * in the new access token. Bear in mind if this flag is specified - * the routine ignores PrivilegesToDelete. - * - * SANDBOX_INERT -- Stores the TOKEN_SANDBOX_INERT token flag within the access token. - * - * LUA_TOKEN -- The newly filtered access token is a LUA token. This flag is not - * supported in Windows Server 2003. - * - * WRITE_RESTRICTED -- The newly filtered token has the restricted SIDs that are - * considered only when evaluating write access onto the token. - * This value is not supported in Windows Server 2003. - */ -NTSTATUS -NTAPI -NtFilterToken( - _In_ HANDLE ExistingTokenHandle, - _In_ ULONG Flags, - _In_opt_ PTOKEN_GROUPS SidsToDisable, - _In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete, - _In_opt_ PTOKEN_GROUPS RestrictedSids, - _Out_ PHANDLE NewTokenHandle) -{ - PTOKEN Token, FilteredToken; - HANDLE FilteredTokenHandle; - NTSTATUS Status; - KPROCESSOR_MODE PreviousMode; - OBJECT_HANDLE_INFORMATION HandleInfo; - ULONG ResultLength; - ULONG CapturedSidsCount = 0; - ULONG CapturedPrivilegesCount = 0; - ULONG CapturedRestrictedSidsCount = 0; - ULONG ProbeSize = 0; - PSID_AND_ATTRIBUTES CapturedSids = NULL; - PSID_AND_ATTRIBUTES CapturedRestrictedSids = NULL; - PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL; - - PAGED_CODE(); - - PreviousMode = ExGetPreviousMode(); - - _SEH2_TRY - { - /* Probe SidsToDisable */ - if (SidsToDisable != NULL) - { - /* Probe the header */ - ProbeForRead(SidsToDisable, sizeof(*SidsToDisable), sizeof(ULONG)); - - CapturedSidsCount = SidsToDisable->GroupCount; - ProbeSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedSidsCount]); - - ProbeForRead(SidsToDisable, ProbeSize, sizeof(ULONG)); - } - - /* Probe PrivilegesToDelete */ - if (PrivilegesToDelete != NULL) - { - /* Probe the header */ - ProbeForRead(PrivilegesToDelete, sizeof(*PrivilegesToDelete), sizeof(ULONG)); - - CapturedPrivilegesCount = PrivilegesToDelete->PrivilegeCount; - ProbeSize = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[CapturedPrivilegesCount]); - - ProbeForRead(PrivilegesToDelete, ProbeSize, sizeof(ULONG)); - } - - /* Probe RestrictedSids */ - if (RestrictedSids != NULL) - { - /* Probe the header */ - ProbeForRead(RestrictedSids, sizeof(*RestrictedSids), sizeof(ULONG)); - - CapturedRestrictedSidsCount = RestrictedSids->GroupCount; - ProbeSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedRestrictedSidsCount]); - - ProbeForRead(RestrictedSids, ProbeSize, sizeof(ULONG)); - } - - /* Probe the handle */ - ProbeForWriteHandle(NewTokenHandle); - } - _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) - { - /* Return the exception code */ - _SEH2_YIELD(return _SEH2_GetExceptionCode()); - } - _SEH2_END; - - /* Reference the token */ - Status = ObReferenceObjectByHandle(ExistingTokenHandle, - TOKEN_DUPLICATE, - SeTokenObjectType, - PreviousMode, - (PVOID*)&Token, - &HandleInfo); - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtFilterToken(): Failed to reference the token (Status 0x%lx)\n", Status); - return Status; - } - - /* Capture the group SIDs */ - if (SidsToDisable != NULL) - { - Status = SeCaptureSidAndAttributesArray(SidsToDisable->Groups, - CapturedSidsCount, - PreviousMode, - NULL, - 0, - PagedPool, - TRUE, - &CapturedSids, - &ResultLength); - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtFilterToken(): Failed to capture the SIDs (Status 0x%lx)\n", Status); - goto Quit; - } - } - - /* Capture the privileges */ - if (PrivilegesToDelete != NULL) - { - Status = SeCaptureLuidAndAttributesArray(PrivilegesToDelete->Privileges, - CapturedPrivilegesCount, - PreviousMode, - NULL, - 0, - PagedPool, - TRUE, - &CapturedPrivileges, - &ResultLength); - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtFilterToken(): Failed to capture the privileges (Status 0x%lx)\n", Status); - goto Quit; - } - } - - /* Capture the restricted SIDs */ - if (RestrictedSids != NULL) - { - Status = SeCaptureSidAndAttributesArray(RestrictedSids->Groups, - CapturedRestrictedSidsCount, - PreviousMode, - NULL, - 0, - PagedPool, - TRUE, - &CapturedRestrictedSids, - &ResultLength); - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtFilterToken(): Failed to capture the restricted SIDs (Status 0x%lx)\n", Status); - goto Quit; - } - } - - /* Call the internal API */ - Status = SepPerformTokenFiltering(Token, - CapturedPrivileges, - CapturedSids, - CapturedRestrictedSids, - CapturedPrivilegesCount, - CapturedSidsCount, - CapturedRestrictedSidsCount, - Flags, - PreviousMode, - &FilteredToken); - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtFilterToken(): Failed to filter the token (Status 0x%lx)\n", Status); - goto Quit; - } - - /* Insert the filtered token and retrieve a handle to it */ - Status = ObInsertObject(FilteredToken, - NULL, - HandleInfo.GrantedAccess, - 0, - NULL, - &FilteredTokenHandle); - if (!NT_SUCCESS(Status)) - { - DPRINT1("NtFilterToken(): Failed to insert the filtered token (Status 0x%lx)\n", Status); - goto Quit; - } - - /* And return it to the caller once we're done */ - _SEH2_TRY - { - *NewTokenHandle = FilteredTokenHandle; - } - _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) - { - Status = _SEH2_GetExceptionCode(); - _SEH2_YIELD(goto Quit); - } - _SEH2_END; - -Quit: - /* Dereference the token */ - ObDereferenceObject(Token); - - /* Release all the captured data */ - if (CapturedSids != NULL) - { - SeReleaseSidAndAttributesArray(CapturedSids, - PreviousMode, - TRUE); - } - - if (CapturedPrivileges != NULL) - { - SeReleaseLuidAndAttributesArray(CapturedPrivileges, - PreviousMode, - TRUE); - } - - if (CapturedRestrictedSids != NULL) - { - SeReleaseSidAndAttributesArray(CapturedRestrictedSids, - PreviousMode, - TRUE); - } - - return Status; -} - /** * @brief * Allows the calling thread to impersonate the system's anonymous diff --git a/ntoskrnl/se/tokenadj.c b/ntoskrnl/se/tokenadj.c new file mode 100644 index 00000000000..eb036a32d32 --- /dev/null +++ b/ntoskrnl/se/tokenadj.c @@ -0,0 +1,910 @@ +/* + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Access token ajusting Groups/Privileges support routines + * COPYRIGHT: Copyright David Welch + * Copyright 2021-2022 George Bișoc + */ + +/* INCLUDES *******************************************************************/ + +#include +#define NDEBUG +#include + +/* PRIVATE FUNCTIONS *********************************************************/ + +/** + * @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 + * 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 valid */ + 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; +} + +/* SYSTEM CALLS ***************************************************************/ + +/** + * @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 + * 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; + + 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); + } + + return Status; + } + + /* Lock the token */ + SepAcquireTokenLockExclusive(Token); + + /* 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); + + /* Unlock and dereference the token */ + SepReleaseTokenLock(Token); + ObDereferenceObject(Token); + + /* Release the captured groups */ + if (CapturedGroups != NULL) + { + SeReleaseSidAndAttributesArray(CapturedGroups, + PreviousMode, + TRUE); + } + + return Status; +} + +/* EOF */ diff --git a/ntoskrnl/se/tokencls.c b/ntoskrnl/se/tokencls.c new file mode 100644 index 00000000000..70b1ecafa79 --- /dev/null +++ b/ntoskrnl/se/tokencls.c @@ -0,0 +1,1588 @@ +/* + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Access token Query/Set information classes implementation + * COPYRIGHT: Copyright David Welch + * Copyright 2021-2022 George Bișoc + */ + +/* INCLUDES *******************************************************************/ + +#include +#define NDEBUG +#include + +#include + +/* INFORMATION CLASSES ********************************************************/ + +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), +}; + +/* PUBLIC FUNCTIONS *****************************************************************/ + +/** + * @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; +} + +/* 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) + { + 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, validate, 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; +} + +/* EOF */ diff --git a/ntoskrnl/se/tokenlif.c b/ntoskrnl/se/tokenlif.c new file mode 100644 index 00000000000..c3900c2dce5 --- /dev/null +++ b/ntoskrnl/se/tokenlif.c @@ -0,0 +1,2201 @@ +/* + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Access token lifetime management implementation (Creation/Duplication/Filtering) + * COPYRIGHT: Copyright David Welch + * Copyright 2021-2022 George Bișoc + */ + +/* INCLUDES *******************************************************************/ + +#include +#define NDEBUG +#include + +/* PRIVATE FUNCTIONS *********************************************************/ + +/** + * @brief + * Internal function responsible for access token object creation in the kernel. + * A fully created token objected is inserted into the token handle, thus the handle + * becoming a valid handle to an access token object and ready for use. + * + * @param[out] TokenHandle + * Valid token handle that's ready for use after token creation and object insertion. + * + * @param[in] PreviousMode + * Processor request level mode. + * + * @param[in] DesiredAccess + * Desired access right for the token object to be granted. This kind of access right + * impacts how the token can be used and who. + * + * @param[in] ObjectAttributes + * Object attributes for the token to be created. + * + * @param[in] TokenType + * Type of token to assign upon creation. + * + * @param[in] ImpersonationLevel + * Security impersonation level of token to assign upon creation. + * + * @param[in] AuthenticationId + * Authentication ID that represents the authentication information of the token. + * + * @param[in] ExpirationTime + * Expiration time of the token to assign. A value of -1 means that the token never + * expires and its life depends upon the amount of references this token object has. + * + * @param[in] User + * User entry to assign to the token. + * + * @param[in] GroupCount + * The total number of groups count for the token. + * + * @param[in] Groups + * The group entries for the token. + * + * @param[in] GroupsLength + * The length size of the groups array, pointed by the Groups parameter. + * + * @param[in] PrivilegeCount + * The total number of priivleges that the newly created token has. + * + * @param[in] Privileges + * The privileges for the token. + * + * @param[in] Owner + * The main user (or also owner) that represents the token that we create. + * + * @param[in] PrimaryGroup + * The main group that represents the token that we create. + * + * @param[in] DefaultDacl + * A discretionary access control list for the token. + * + * @param[in] TokenSource + * Source (or the origin) of the access token that creates it. + * + * @param[in] SystemToken + * If set to TRUE, the newly created token is a system token and only in charge + * by the internal system. The function directly returns a pointer to the + * created token object for system kernel use. Otherwise if set to FALSE, the + * function inserts the object to a handle making it a regular access token. + * + * @return + * Returns STATUS_SUCCESS if token creation has completed successfully. + * STATUS_INSUFFICIENT_RESOURCES is returned if the dynamic area of memory of the + * token hasn't been allocated because of lack of memory resources. A failure + * NTSTATUS code is returned otherwise. + */ +NTSTATUS +NTAPI +SepCreateToken( + _Out_ PHANDLE TokenHandle, + _In_ KPROCESSOR_MODE PreviousMode, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ TOKEN_TYPE TokenType, + _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, + _In_ PLUID AuthenticationId, + _In_ PLARGE_INTEGER ExpirationTime, + _In_ PSID_AND_ATTRIBUTES User, + _In_ ULONG GroupCount, + _In_ PSID_AND_ATTRIBUTES Groups, + _In_ ULONG GroupsLength, + _In_ ULONG PrivilegeCount, + _In_ PLUID_AND_ATTRIBUTES Privileges, + _In_opt_ PSID Owner, + _In_ PSID PrimaryGroup, + _In_opt_ PACL DefaultDacl, + _In_ PTOKEN_SOURCE TokenSource, + _In_ BOOLEAN SystemToken) +{ + NTSTATUS Status; + PTOKEN AccessToken; + ULONG TokenFlags = 0; + ULONG PrimaryGroupIndex, DefaultOwnerIndex; + LUID TokenId; + LUID ModifiedId; + PVOID EndMem; + ULONG PrivilegesLength; + ULONG UserGroupsLength; + ULONG VariableLength; + ULONG 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); + + AccessToken->TokenId = TokenId; + AccessToken->TokenType = TokenType; + AccessToken->ImpersonationLevel = ImpersonationLevel; + + /* Initialise the lock for the access token */ + Status = SepCreateTokenLock(AccessToken); + if (!NT_SUCCESS(Status)) + goto Quit; + + AccessToken->TokenSource.SourceIdentifier = TokenSource->SourceIdentifier; + RtlCopyMemory(AccessToken->TokenSource.SourceName, + TokenSource->SourceName, + sizeof(TokenSource->SourceName)); + + AccessToken->ExpirationTime = *ExpirationTime; + AccessToken->ModifiedId = ModifiedId; + + AccessToken->TokenFlags = TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED; + + /* Copy and reference the logon session */ + AccessToken->AuthenticationId = *AuthenticationId; + Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId); + if (!NT_SUCCESS(Status)) + { + /* No logon session could be found, bail out */ + DPRINT1("SepRmReferenceLogonSession() failed (Status 0x%lx)\n", Status); + /* Set the flag for proper cleanup by the delete procedure */ + AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED; + goto Quit; + } + + /* Insert the referenced logon session into the token */ + Status = SepRmInsertLogonSessionIntoToken(AccessToken); + if (!NT_SUCCESS(Status)) + { + /* Failed to insert the logon session into the token, bail out */ + DPRINT1("SepRmInsertLogonSessionIntoToken() failed (Status 0x%lx)\n", Status); + goto Quit; + } + + /* Fill in token debug information */ +#if DBG + /* + * We must determine ourselves that the current + * process is not the initial CPU one. The initial + * process is not a "real" process, that is, the + * Process Manager has not yet been initialized and + * as a matter of fact we are creating a token before + * any process gets created by Ps. If it turns out + * that the current process is the initial CPU process + * where token creation execution takes place, don't + * do anything. + */ + if (PsGetCurrentProcess() != &KiInitialProcess) + { + RtlCopyMemory(AccessToken->ImageFileName, + PsGetCurrentProcess()->ImageFileName, + min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName))); + + AccessToken->ProcessCid = PsGetCurrentProcessId(); + AccessToken->ThreadCid = PsGetCurrentThreadId(); + } + + AccessToken->CreateMethod = TOKEN_CREATE_METHOD; +#endif + + /* Assign the data that reside in the token's variable information area */ + AccessToken->VariableLength = VariableLength; + EndMem = (PVOID)&AccessToken->VariablePart; + + /* Copy the privileges */ + AccessToken->PrivilegeCount = PrivilegeCount; + AccessToken->Privileges = NULL; + if (PrivilegeCount > 0) + { + AccessToken->Privileges = EndMem; + EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength); + VariableLength -= PrivilegesLength; + + if (PreviousMode != KernelMode) + { + _SEH2_TRY + { + RtlCopyMemory(AccessToken->Privileges, + Privileges, + PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + } + else + { + RtlCopyMemory(AccessToken->Privileges, + Privileges, + PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); + } + + if (!NT_SUCCESS(Status)) + goto Quit; + } + + /* Update the privilege flags */ + SepUpdatePrivilegeFlagsToken(AccessToken); + + /* Copy the user and groups */ + AccessToken->UserAndGroupCount = 1 + GroupCount; + AccessToken->UserAndGroups = EndMem; + EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount]; + VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups); + + Status = RtlCopySidAndAttributesArray(1, + User, + VariableLength, + &AccessToken->UserAndGroups[0], + EndMem, + &EndMem, + &VariableLength); + if (!NT_SUCCESS(Status)) + goto Quit; + + Status = RtlCopySidAndAttributesArray(GroupCount, + Groups, + VariableLength, + &AccessToken->UserAndGroups[1], + EndMem, + &EndMem, + &VariableLength); + if (!NT_SUCCESS(Status)) + goto Quit; + + /* Find the token primary group and default owner */ + Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken, + PrimaryGroup, + Owner, + &PrimaryGroupIndex, + &DefaultOwnerIndex); + if (!NT_SUCCESS(Status)) + { + DPRINT1("SepFindPrimaryGroupAndDefaultOwner failed (Status 0x%lx)\n", Status); + goto Quit; + } + + 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 + * 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 */ + 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 */ + SepAcquireTokenLockShared(Token); + + AccessToken->SessionId = Token->SessionId; + AccessToken->ModifiedId = Token->ModifiedId; + + AccessToken->TokenFlags = Token->TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED; + + /* Reference the logon session */ + Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId); + if (!NT_SUCCESS(Status)) + { + /* No logon session could be found, bail out */ + DPRINT1("SepRmReferenceLogonSession() failed (Status 0x%lx)\n", Status); + /* Set the flag for proper cleanup by the delete procedure */ + AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED; + goto Quit; + } + + /* Insert the referenced logon session into the token */ + Status = SepRmInsertLogonSessionIntoToken(AccessToken); + if (!NT_SUCCESS(Status)) + { + /* Failed to insert the logon session into the token, bail out */ + DPRINT1("SepRmInsertLogonSessionIntoToken() failed (Status 0x%lx)\n", Status); + goto Quit; + } + + /* Fill in token debug information */ +#if DBG + RtlCopyMemory(AccessToken->ImageFileName, + PsGetCurrentProcess()->ImageFileName, + min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName))); + + AccessToken->ProcessCid = PsGetCurrentProcessId(); + AccessToken->ThreadCid = PsGetCurrentThreadId(); + AccessToken->CreateMethod = TOKEN_DUPLICATE_METHOD; +#endif + + /* Assign the data that reside in the token's variable information area */ + AccessToken->VariableLength = VariableLength; + EndMem = (PVOID)&AccessToken->VariablePart; + + /* Copy the privileges */ + AccessToken->PrivilegeCount = 0; + AccessToken->Privileges = NULL; + if (Token->Privileges && (Token->PrivilegeCount > 0)) + { + ULONG PrivilegesLength = Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES); + PrivilegesLength = ALIGN_UP_BY(PrivilegesLength, sizeof(PVOID)); + + ASSERT(VariableLength >= PrivilegesLength); + + AccessToken->PrivilegeCount = Token->PrivilegeCount; + AccessToken->Privileges = EndMem; + EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength); + VariableLength -= PrivilegesLength; + + RtlCopyMemory(AccessToken->Privileges, + Token->Privileges, + AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); + } + + /* Copy the user and groups */ + AccessToken->UserAndGroupCount = 0; + AccessToken->UserAndGroups = NULL; + if (Token->UserAndGroups && (Token->UserAndGroupCount > 0)) + { + AccessToken->UserAndGroupCount = Token->UserAndGroupCount; + AccessToken->UserAndGroups = EndMem; + EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount]; + VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups); + + Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount, + Token->UserAndGroups, + VariableLength, + AccessToken->UserAndGroups, + EndMem, + &EndMem, + &VariableLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("RtlCopySidAndAttributesArray(UserAndGroups) failed (Status 0x%lx)\n", Status); + goto Quit; + } + } + + /* Find the token primary group */ + Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken, + Token->PrimaryGroup, + NULL, + &PrimaryGroupIndex, + NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("SepFindPrimaryGroupAndDefaultOwner failed (Status 0x%lx)\n", Status); + goto Quit; + } + + 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); + } + + /* Return the token to the caller */ + *NewAccessToken = AccessToken; + Status = STATUS_SUCCESS; + +Quit: + if (!NT_SUCCESS(Status)) + { + /* Dereference the token, the delete procedure will clean it up */ + ObDereferenceObject(AccessToken); + } + + /* Unlock the source token */ + SepReleaseTokenLock(Token); + + return Status; +} + +/** + * @brief + * Private helper function responsible for creating a restricted access + * token, that is, a filtered token from privileges and groups and with + * restricted SIDs added into the token on demand by the caller. + * + * @param[in] Token + * An existing and valid access token. + * + * @param[in] PrivilegesToBeDeleted + * A list of privileges to be deleted within the token that's going + * to be filtered. This parameter is ignored if the caller wants to disable + * all the privileges by specifying DISABLE_MAX_PRIVILEGE in the flags + * parameter. + * + * @param[in] SidsToBeDisabled + * A list of group SIDs to be disabled within the token. This parameter + * can be NULL. + * + * @param[in] RestrictedSidsIntoToken + * A list of restricted SIDs to be added into the token. This parameter + * can be NULL. + * + * @param[in] PrivilegesCount + * The privilege count of the privileges list. + * + * @param[in] RegularGroupsSidCount + * The SIDs count of the group SIDs list. + * + * @param[in] RestrictedSidsCount + * The restricted SIDs count of restricted SIDs list. + * + * @param[in] PrivilegeFlags + * Influences how the privileges should be filtered in an access + * token. See NtFilterToken syscall for more information. + * + * @param[in] PreviousMode + * Processor level access mode. + * + * @param[out] FilteredToken + * The filtered token, returned to the caller. + * + * @return + * Returns STATUS_SUCCESS if token token filtering has completed successfully. + * STATUS_INVALID_PARAMETER is returned if one or more of the parameters + * do not meet the conditions imposed by the function. A failure NTSTATUS + * code is returned otherwise. + * + * @remarks + * The final outcome of privileges and/or SIDs filtering is not always + * deterministic. That is, any privileges or SIDs that aren't present + * in the access token are ignored and the function continues with the + * next privilege or SID to find for filtering. For a fully deterministic + * outcome the caller is responsible for querying the information details + * of privileges and SIDs present in the token and then afterwards use + * such obtained information to do any kind of filtering to the token. + */ +static +NTSTATUS +SepPerformTokenFiltering( + _In_ PTOKEN Token, + _In_opt_ PLUID_AND_ATTRIBUTES PrivilegesToBeDeleted, + _In_opt_ PSID_AND_ATTRIBUTES SidsToBeDisabled, + _In_opt_ PSID_AND_ATTRIBUTES RestrictedSidsIntoToken, + _When_(PrivilegesToBeDeleted != NULL, _In_) ULONG PrivilegesCount, + _When_(SidsToBeDisabled != NULL, _In_) ULONG RegularGroupsSidCount, + _When_(RestrictedSidsIntoToken != NULL, _In_) ULONG RestrictedSidsCount, + _In_ ULONG PrivilegeFlags, + _In_ KPROCESSOR_MODE PreviousMode, + _Out_ PTOKEN *FilteredToken) +{ + NTSTATUS Status; + PTOKEN AccessToken; + PVOID EndMem; + ULONG RestrictedSidsLength; + ULONG PrivilegesLength; + ULONG PrimaryGroupIndex; + ULONG RestrictedSidsInList; + ULONG RestrictedSidsInToken; + ULONG VariableLength, TotalSize; + ULONG PrivsInToken, PrivsInList; + ULONG GroupsInToken, GroupsInList; + BOOLEAN WantPrivilegesDisabled; + BOOLEAN FoundPrivilege; + BOOLEAN FoundGroup; + + PAGED_CODE(); + + /* Ensure that the source token is valid, and lock it */ + ASSERT(Token); + SepAcquireTokenLockShared(Token); + + /* Assume the caller doesn't want privileges disabled */ + WantPrivilegesDisabled = FALSE; + + /* Assume we haven't found anything */ + FoundPrivilege = FALSE; + FoundGroup = FALSE; + + /* + * Take the size that we need for filtered token + * allocation based upon the existing access token + * we've been given. + */ + VariableLength = Token->VariableLength; + + if (RestrictedSidsIntoToken != NULL) + { + /* + * If the caller provided a list of restricted SIDs + * to be added onto the filtered access token then + * we must compute the size which is the total space + * of the current token and the length of the restricted + * SIDs for the filtered token. + */ + RestrictedSidsLength = RestrictedSidsCount * sizeof(SID_AND_ATTRIBUTES); + RestrictedSidsLength += RtlLengthSidAndAttributes(RestrictedSidsCount, RestrictedSidsIntoToken); + RestrictedSidsLength = ALIGN_UP_BY(RestrictedSidsLength, sizeof(PVOID)); + + /* + * The variable length of the token is not just + * the actual space length of the existing token + * but also the sum of the restricted SIDs length. + */ + VariableLength += RestrictedSidsLength; + TotalSize = FIELD_OFFSET(TOKEN, VariablePart) + VariableLength + RestrictedSidsLength; + } + else + { + /* Otherwise the size is of the actual current token */ + TotalSize = FIELD_OFFSET(TOKEN, VariablePart) + VariableLength; + } + + /* 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); + + /* Unlock the source token and bail out */ + SepReleaseTokenLock(Token); + return Status; + } + + /* Initialize the token and begin filling stuff to it */ + RtlZeroMemory(AccessToken, TotalSize); + + /* Set up a lock for the new token */ + Status = SepCreateTokenLock(AccessToken); + if (!NT_SUCCESS(Status)) + goto Quit; + + /* Allocate new IDs for the token */ + ExAllocateLocallyUniqueId(&AccessToken->TokenId); + ExAllocateLocallyUniqueId(&AccessToken->ModifiedId); + + /* Copy the type and impersonation level from the token */ + AccessToken->TokenType = Token->TokenType; + AccessToken->ImpersonationLevel = Token->ImpersonationLevel; + + /* Copy the immutable fields */ + AccessToken->TokenSource.SourceIdentifier = Token->TokenSource.SourceIdentifier; + RtlCopyMemory(AccessToken->TokenSource.SourceName, + Token->TokenSource.SourceName, + sizeof(Token->TokenSource.SourceName)); + + AccessToken->AuthenticationId = Token->AuthenticationId; + AccessToken->ParentTokenId = Token->TokenId; + AccessToken->OriginatingLogonSession = Token->OriginatingLogonSession; + + AccessToken->ExpirationTime = Token->ExpirationTime; + + /* Copy the mutable fields */ + AccessToken->SessionId = Token->SessionId; + AccessToken->TokenFlags = Token->TokenFlags & ~TOKEN_SESSION_NOT_REFERENCED; + + /* Reference the logon session */ + Status = SepRmReferenceLogonSession(&AccessToken->AuthenticationId); + if (!NT_SUCCESS(Status)) + { + /* We failed, bail out*/ + DPRINT1("SepPerformTokenFiltering(): Failed to reference the logon session (Status 0x%lx)\n", Status); + AccessToken->TokenFlags |= TOKEN_SESSION_NOT_REFERENCED; + goto Quit; + } + + /* Insert the referenced logon session into the token */ + Status = SepRmInsertLogonSessionIntoToken(AccessToken); + if (!NT_SUCCESS(Status)) + { + /* Failed to insert the logon session into the token, bail out */ + DPRINT1("SepPerformTokenFiltering(): Failed to insert the logon session into token (Status 0x%lx)\n", Status); + goto Quit; + } + + /* Fill in token debug information */ +#if DBG + RtlCopyMemory(AccessToken->ImageFileName, + PsGetCurrentProcess()->ImageFileName, + min(sizeof(AccessToken->ImageFileName), sizeof(PsGetCurrentProcess()->ImageFileName))); + + AccessToken->ProcessCid = PsGetCurrentProcessId(); + AccessToken->ThreadCid = PsGetCurrentThreadId(); + AccessToken->CreateMethod = TOKEN_FILTER_METHOD; +#endif + + /* Assign the data that reside in the token's variable information area */ + AccessToken->VariableLength = VariableLength; + EndMem = (PVOID)&AccessToken->VariablePart; + + /* Copy the privileges from the existing token */ + AccessToken->PrivilegeCount = 0; + AccessToken->Privileges = NULL; + if (Token->Privileges && (Token->PrivilegeCount > 0)) + { + PrivilegesLength = Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES); + PrivilegesLength = ALIGN_UP_BY(PrivilegesLength, sizeof(PVOID)); + + /* + * Ensure that the token can actually hold all + * the privileges from the existing token. + * Otherwise something's seriously wrong and + * we've to guard ourselves. + */ + ASSERT(VariableLength >= PrivilegesLength); + + AccessToken->PrivilegeCount = Token->PrivilegeCount; + AccessToken->Privileges = EndMem; + EndMem = (PVOID)((ULONG_PTR)EndMem + PrivilegesLength); + VariableLength -= PrivilegesLength; + + RtlCopyMemory(AccessToken->Privileges, + Token->Privileges, + AccessToken->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES)); + } + + /* Copy the user and groups */ + AccessToken->UserAndGroupCount = 0; + AccessToken->UserAndGroups = NULL; + if (Token->UserAndGroups && (Token->UserAndGroupCount > 0)) + { + AccessToken->UserAndGroupCount = Token->UserAndGroupCount; + AccessToken->UserAndGroups = EndMem; + EndMem = &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount]; + VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->UserAndGroups); + + Status = RtlCopySidAndAttributesArray(AccessToken->UserAndGroupCount, + Token->UserAndGroups, + VariableLength, + AccessToken->UserAndGroups, + EndMem, + &EndMem, + &VariableLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("SepPerformTokenFiltering(): Failed to copy the groups into token (Status 0x%lx)\n", Status); + goto Quit; + } + } + + /* Copy the restricted SIDs */ + AccessToken->RestrictedSidCount = 0; + AccessToken->RestrictedSids = NULL; + if (Token->RestrictedSids && (Token->RestrictedSidCount > 0)) + { + AccessToken->RestrictedSidCount = Token->RestrictedSidCount; + AccessToken->RestrictedSids = EndMem; + EndMem = &AccessToken->RestrictedSids[AccessToken->RestrictedSidCount]; + VariableLength -= ((ULONG_PTR)EndMem - (ULONG_PTR)AccessToken->RestrictedSids); + + Status = RtlCopySidAndAttributesArray(AccessToken->RestrictedSidCount, + Token->RestrictedSids, + VariableLength, + AccessToken->RestrictedSids, + EndMem, + &EndMem, + &VariableLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("SepPerformTokenFiltering(): Failed to copy the restricted SIDs into token (Status 0x%lx)\n", Status); + goto Quit; + } + } + + /* + * Insert the restricted SIDs into the token on + * the request by the caller. + */ + if (RestrictedSidsIntoToken != NULL) + { + for (RestrictedSidsInList = 0; RestrictedSidsInList < RestrictedSidsCount; RestrictedSidsInList++) + { + /* Did the caller assign attributes to the restricted SIDs? */ + if (RestrictedSidsIntoToken[RestrictedSidsInList].Attributes != 0) + { + /* There mustn't be any attributes, bail out */ + DPRINT1("SepPerformTokenFiltering(): There mustn't be any attributes to restricted SIDs!\n"); + Status = STATUS_INVALID_PARAMETER; + goto Quit; + } + } + + /* + * Ensure that the token can hold the restricted SIDs + * (the variable length is calculated at the beginning + * of the routine call). + */ + ASSERT(VariableLength >= RestrictedSidsLength); + + /* + * Now let's begin inserting the restricted SIDs into the filtered + * access token from the list the caller gave us. + */ + AccessToken->RestrictedSidCount = RestrictedSidsCount; + AccessToken->RestrictedSids = EndMem; + EndMem = (PVOID)((ULONG_PTR)EndMem + RestrictedSidsLength); + VariableLength -= RestrictedSidsLength; + + RtlCopyMemory(AccessToken->RestrictedSids, + RestrictedSidsIntoToken, + AccessToken->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES)); + + /* + * As we've copied the restricted SIDs into + * the token, we must assign them the following + * combination of attributes SE_GROUP_ENABLED, + * SE_GROUP_ENABLED_BY_DEFAULT and SE_GROUP_MANDATORY. + * With such attributes we estabilish that restricting + * SIDs into the token are enabled for access checks. + */ + for (RestrictedSidsInToken = 0; RestrictedSidsInToken < AccessToken->RestrictedSidCount; RestrictedSidsInToken++) + { + AccessToken->RestrictedSids[RestrictedSidsInToken].Attributes |= (SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY); + } + + /* + * As we added restricted SIDs into the token, mark + * it as restricted. + */ + AccessToken->TokenFlags |= TOKEN_IS_RESTRICTED; + } + + /* Search for the primary group */ + Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken, + Token->PrimaryGroup, + NULL, + &PrimaryGroupIndex, + NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("SepPerformTokenFiltering(): Failed searching for the primary group (Status 0x%lx)\n", Status); + goto Quit; + } + + /* 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; + } + } + } + + /* We've finally filtered the token, return it to the caller */ + *FilteredToken = AccessToken; + Status = STATUS_SUCCESS; + DPRINT("SepPerformTokenFiltering(): The token has been filtered!\n"); + +Quit: + if (!NT_SUCCESS(Status)) + { + /* Dereference the created token */ + ObDereferenceObject(AccessToken); + } + + /* Unlock the source token */ + SepReleaseTokenLock(Token); + + return Status; +} + +/* PUBLIC FUNCTIONS ***********************************************************/ + +/** + * @brief + * Filters an access token from an existing token, making it more restricted + * than the previous one. + * + * @param[in] ExistingToken + * An existing token for filtering. + * + * @param[in] Flags + * Privilege flag options. This parameter argument influences how the token + * is filtered. Such parameter can be 0. See NtFilterToken syscall for + * more information. + * + * @param[in] SidsToDisable + * Array of SIDs to disable. Such parameter can be NULL. + * + * @param[in] PrivilegesToDelete + * Array of privileges to delete. If DISABLE_MAX_PRIVILEGE flag is specified + * in the Flags parameter, PrivilegesToDelete is ignored. + * + * @param[in] RestrictedSids + * An array of restricted SIDs for the new filtered token. Such parameter + * can be NULL. + * + * @param[out] FilteredToken + * The newly filtered token, returned to the caller. + * + * @return + * Returns STATUS_SUCCESS if the function has successfully completed its + * operations and that the access token has been filtered. STATUS_INVALID_PARAMETER + * is returned if one or more of the parameter are not valid. A failure NTSTATUS code + * is returned otherwise. + */ +NTSTATUS +NTAPI +SeFilterToken( + _In_ PACCESS_TOKEN ExistingToken, + _In_ ULONG Flags, + _In_opt_ PTOKEN_GROUPS SidsToDisable, + _In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete, + _In_opt_ PTOKEN_GROUPS RestrictedSids, + _Out_ PACCESS_TOKEN *FilteredToken) +{ + NTSTATUS Status; + PTOKEN AccessToken; + ULONG PrivilegesCount = 0; + ULONG SidsCount = 0; + ULONG RestrictedSidsCount = 0; + + PAGED_CODE(); + + /* Begin copying the counters */ + if (SidsToDisable != NULL) + { + SidsCount = SidsToDisable->GroupCount; + } + + if (PrivilegesToDelete != NULL) + { + PrivilegesCount = PrivilegesToDelete->PrivilegeCount; + } + + if (RestrictedSids != NULL) + { + RestrictedSidsCount = RestrictedSids->GroupCount; + } + + /* Call the internal API */ + Status = SepPerformTokenFiltering(ExistingToken, + PrivilegesToDelete->Privileges, + SidsToDisable->Groups, + RestrictedSids->Groups, + PrivilegesCount, + SidsCount, + RestrictedSidsCount, + Flags, + KernelMode, + &AccessToken); + if (!NT_SUCCESS(Status)) + { + DPRINT1("SeFilterToken(): Failed to filter the token (Status 0x%lx)\n", Status); + return Status; + } + + /* Insert the filtered token */ + Status = ObInsertObject(AccessToken, + NULL, + 0, + 0, + NULL, + NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("SeFilterToken(): Failed to insert the filtered token (Status 0x%lx)\n", Status); + return Status; + } + + /* Return it to the caller */ + *FilteredToken = AccessToken; + return Status; +} + +/* SYSTEM CALLS ***************************************************************/ + +/** + * @brief + * Creates an access token. + * + * @param[out] TokenHandle + * The returned created token handle to the caller. + * + * @param[in] DesiredAccess + * The desired access rights for the token that we're creating. + * + * @param[in] ObjectAttributes + * The object attributes for the token object that we're creating. + * + * @param[in] TokenType + * The type of token to assign for the newly created token. + * + * @param[in] AuthenticationId + * Authentication ID that represents the token's identity. + * + * @param[in] ExpirationTime + * Expiration time for the token. If set to -1, the token never expires. + * + * @param[in] TokenUser + * The main user entity for the token to assign. + * + * @param[in] TokenGroups + * Group list of SIDs for the token to assign. + * + * @param[in] TokenPrivileges + * Privileges for the token. + * + * @param[in] TokenOwner + * The main user that owns the newly created token. + * + * @param[in] TokenPrimaryGroup + * The primary group that represents as the main group of the token. + * + * @param[in] TokenDefaultDacl + * Discretionary access control list for the token. This limits on how + * the token can be used, accessed and used by whom. + * + * @param[in] TokenSource + * The source origin of the token who creates it. + * + * @return + * Returns STATUS_SUCCESS if the function has successfully created the token. + * A failure NTSTATUS code is returned otherwise. + */ +__kernel_entry +NTSTATUS +NTAPI +NtCreateToken( + _Out_ PHANDLE TokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ TOKEN_TYPE TokenType, + _In_ PLUID AuthenticationId, + _In_ PLARGE_INTEGER ExpirationTime, + _In_ PTOKEN_USER TokenUser, + _In_ PTOKEN_GROUPS TokenGroups, + _In_ PTOKEN_PRIVILEGES TokenPrivileges, + _In_opt_ PTOKEN_OWNER TokenOwner, + _In_ PTOKEN_PRIMARY_GROUP TokenPrimaryGroup, + _In_opt_ PTOKEN_DEFAULT_DACL TokenDefaultDacl, + _In_ PTOKEN_SOURCE TokenSource) +{ + HANDLE hToken; + KPROCESSOR_MODE PreviousMode; + ULONG PrivilegeCount, GroupCount; + PSID OwnerSid, PrimaryGroupSid; + PACL DefaultDacl; + LARGE_INTEGER LocalExpirationTime = {{0, 0}}; + LUID LocalAuthenticationId; + TOKEN_SOURCE LocalTokenSource; + SECURITY_QUALITY_OF_SERVICE LocalSecurityQos; + PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL; + PSID_AND_ATTRIBUTES CapturedUser = NULL; + PSID_AND_ATTRIBUTES CapturedGroups = NULL; + PSID CapturedOwnerSid = NULL; + PSID CapturedPrimaryGroupSid = NULL; + PACL CapturedDefaultDacl = NULL; + ULONG PrivilegesLength, UserLength, GroupsLength; + NTSTATUS Status; + + PAGED_CODE(); + + PreviousMode = ExGetPreviousMode(); + + if (PreviousMode != KernelMode) + { + _SEH2_TRY + { + ProbeForWriteHandle(TokenHandle); + + if (ObjectAttributes != NULL) + { + ProbeForRead(ObjectAttributes, + sizeof(OBJECT_ATTRIBUTES), + sizeof(ULONG)); + LocalSecurityQos = *(SECURITY_QUALITY_OF_SERVICE*)ObjectAttributes->SecurityQualityOfService; + } + + ProbeForRead(AuthenticationId, + sizeof(LUID), + sizeof(ULONG)); + LocalAuthenticationId = *AuthenticationId; + + LocalExpirationTime = ProbeForReadLargeInteger(ExpirationTime); + + ProbeForRead(TokenUser, + sizeof(TOKEN_USER), + sizeof(ULONG)); + + ProbeForRead(TokenGroups, + sizeof(TOKEN_GROUPS), + sizeof(ULONG)); + GroupCount = TokenGroups->GroupCount; + + ProbeForRead(TokenPrivileges, + sizeof(TOKEN_PRIVILEGES), + sizeof(ULONG)); + PrivilegeCount = TokenPrivileges->PrivilegeCount; + + if (TokenOwner != NULL) + { + ProbeForRead(TokenOwner, + sizeof(TOKEN_OWNER), + sizeof(ULONG)); + OwnerSid = TokenOwner->Owner; + } + else + { + OwnerSid = NULL; + } + + ProbeForRead(TokenPrimaryGroup, + sizeof(TOKEN_PRIMARY_GROUP), + sizeof(ULONG)); + PrimaryGroupSid = TokenPrimaryGroup->PrimaryGroup; + + if (TokenDefaultDacl != NULL) + { + ProbeForRead(TokenDefaultDacl, + sizeof(TOKEN_DEFAULT_DACL), + sizeof(ULONG)); + DefaultDacl = TokenDefaultDacl->DefaultDacl; + } + else + { + DefaultDacl = NULL; + } + + ProbeForRead(TokenSource, + sizeof(TOKEN_SOURCE), + sizeof(ULONG)); + LocalTokenSource = *TokenSource; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + /* Return the exception code */ + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } + _SEH2_END; + } + else + { + if (ObjectAttributes != NULL) + LocalSecurityQos = *(SECURITY_QUALITY_OF_SERVICE*)ObjectAttributes->SecurityQualityOfService; + LocalAuthenticationId = *AuthenticationId; + LocalExpirationTime = *ExpirationTime; + GroupCount = TokenGroups->GroupCount; + PrivilegeCount = TokenPrivileges->PrivilegeCount; + OwnerSid = TokenOwner ? TokenOwner->Owner : NULL; + PrimaryGroupSid = TokenPrimaryGroup->PrimaryGroup; + DefaultDacl = TokenDefaultDacl ? TokenDefaultDacl->DefaultDacl : NULL; + LocalTokenSource = *TokenSource; + } + + /* Check token type */ + if ((TokenType < TokenPrimary) || + (TokenType > TokenImpersonation)) + { + return STATUS_BAD_TOKEN_TYPE; + } + + /* Check for token creation privilege */ + if (!SeSinglePrivilegeCheck(SeCreateTokenPrivilege, PreviousMode)) + { + return STATUS_PRIVILEGE_NOT_HELD; + } + + /* Capture the user SID and attributes */ + Status = SeCaptureSidAndAttributesArray(&TokenUser->User, + 1, + PreviousMode, + NULL, + 0, + PagedPool, + FALSE, + &CapturedUser, + &UserLength); + if (!NT_SUCCESS(Status)) + { + goto Cleanup; + } + + /* Capture the groups SID and attributes array */ + Status = SeCaptureSidAndAttributesArray(&TokenGroups->Groups[0], + GroupCount, + PreviousMode, + NULL, + 0, + PagedPool, + FALSE, + &CapturedGroups, + &GroupsLength); + if (!NT_SUCCESS(Status)) + { + goto Cleanup; + } + + /* Capture privileges */ + Status = SeCaptureLuidAndAttributesArray(&TokenPrivileges->Privileges[0], + PrivilegeCount, + PreviousMode, + NULL, + 0, + PagedPool, + FALSE, + &CapturedPrivileges, + &PrivilegesLength); + if (!NT_SUCCESS(Status)) + { + goto Cleanup; + } + + /* Capture the token owner SID */ + if (TokenOwner != NULL) + { + Status = SepCaptureSid(OwnerSid, + PreviousMode, + PagedPool, + FALSE, + &CapturedOwnerSid); + if (!NT_SUCCESS(Status)) + { + goto Cleanup; + } + } + + /* Capture the token primary group SID */ + Status = SepCaptureSid(PrimaryGroupSid, + PreviousMode, + PagedPool, + FALSE, + &CapturedPrimaryGroupSid); + if (!NT_SUCCESS(Status)) + { + goto Cleanup; + } + + /* Capture DefaultDacl */ + if (DefaultDacl != NULL) + { + Status = SepCaptureAcl(DefaultDacl, + PreviousMode, + NonPagedPool, + FALSE, + &CapturedDefaultDacl); + if (!NT_SUCCESS(Status)) + { + goto Cleanup; + } + } + + /* Call the internal function */ + Status = SepCreateToken(&hToken, + PreviousMode, + DesiredAccess, + ObjectAttributes, + TokenType, + LocalSecurityQos.ImpersonationLevel, + &LocalAuthenticationId, + &LocalExpirationTime, + CapturedUser, + GroupCount, + CapturedGroups, + GroupsLength, + PrivilegeCount, + CapturedPrivileges, + CapturedOwnerSid, + CapturedPrimaryGroupSid, + CapturedDefaultDacl, + &LocalTokenSource, + FALSE); + if (NT_SUCCESS(Status)) + { + _SEH2_TRY + { + *TokenHandle = hToken; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + } + +Cleanup: + + /* Release what we captured */ + SeReleaseSidAndAttributesArray(CapturedUser, PreviousMode, FALSE); + SeReleaseSidAndAttributesArray(CapturedGroups, PreviousMode, FALSE); + SeReleaseLuidAndAttributesArray(CapturedPrivileges, PreviousMode, FALSE); + SepReleaseSid(CapturedOwnerSid, PreviousMode, FALSE); + SepReleaseSid(CapturedPrimaryGroupSid, PreviousMode, FALSE); + SepReleaseAcl(CapturedDefaultDacl, PreviousMode, FALSE); + + return Status; +} + +/** + * @brief + * Duplicates a token. + * + * @param[in] ExistingTokenHandle + * An existing token to duplicate. + * + * @param[in] DesiredAccess + * The desired access rights for the new duplicated token. + * + * @param[in] ObjectAttributes + * Object attributes for the new duplicated token. + * + * @param[in] EffectiveOnly + * If set to TRUE, the function removes all the disabled privileges and groups + * of the token to duplicate. + * + * @param[in] TokenType + * Type of token to assign to the duplicated token. + * + * @param[out] NewTokenHandle + * The returned duplicated token handle. + * + * @return + * STATUS_SUCCESS is returned if token duplication has completed successfully. + * STATUS_BAD_IMPERSONATION_LEVEL is returned if the caller erroneously wants + * to raise the impersonation level even though the conditions do not permit + * it. A failure NTSTATUS code is returned otherwise. + * + * @remarks + * Some sources claim 4th param is ImpersonationLevel, but on W2K + * this is certainly NOT true, although I can't say for sure that EffectiveOnly + * is correct either. -Gunnar + * This is true. EffectiveOnly overrides SQOS.EffectiveOnly. - IAI + * NOTE for readers: http://hex.pp.ua/nt/NtDuplicateToken.php is therefore + * wrong in that regard, while MSDN documentation is correct. + */ +_Must_inspect_result_ +__kernel_entry +NTSTATUS +NTAPI +NtDuplicateToken( + _In_ HANDLE ExistingTokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ BOOLEAN EffectiveOnly, + _In_ TOKEN_TYPE TokenType, + _Out_ PHANDLE NewTokenHandle) +{ + KPROCESSOR_MODE PreviousMode; + HANDLE hToken; + PTOKEN Token; + PTOKEN NewToken; + PSECURITY_QUALITY_OF_SERVICE CapturedSecurityQualityOfService; + BOOLEAN QoSPresent; + OBJECT_HANDLE_INFORMATION HandleInformation; + NTSTATUS Status; + + PAGED_CODE(); + + if (TokenType != TokenImpersonation && + TokenType != TokenPrimary) + { + return STATUS_INVALID_PARAMETER; + } + + PreviousMode = KeGetPreviousMode(); + + if (PreviousMode != KernelMode) + { + _SEH2_TRY + { + ProbeForWriteHandle(NewTokenHandle); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + /* Return the exception code */ + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } + _SEH2_END; + } + + Status = SepCaptureSecurityQualityOfService(ObjectAttributes, + PreviousMode, + PagedPool, + FALSE, + &CapturedSecurityQualityOfService, + &QoSPresent); + if (!NT_SUCCESS(Status)) + { + DPRINT1("NtDuplicateToken() failed to capture QoS! Status: 0x%x\n", Status); + return Status; + } + + Status = ObReferenceObjectByHandle(ExistingTokenHandle, + TOKEN_DUPLICATE, + SeTokenObjectType, + PreviousMode, + (PVOID*)&Token, + &HandleInformation); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Failed to reference token (Status 0x%lx)\n", Status); + SepReleaseSecurityQualityOfService(CapturedSecurityQualityOfService, + PreviousMode, + FALSE); + return Status; + } + + /* + * Fail, if the original token is an impersonation token and the caller + * tries to raise the impersonation level of the new token above the + * impersonation level of the original token. + */ + if (Token->TokenType == TokenImpersonation) + { + if (QoSPresent && + CapturedSecurityQualityOfService->ImpersonationLevel >Token->ImpersonationLevel) + { + ObDereferenceObject(Token); + SepReleaseSecurityQualityOfService(CapturedSecurityQualityOfService, + PreviousMode, + FALSE); + return STATUS_BAD_IMPERSONATION_LEVEL; + } + } + + /* + * Fail, if a primary token is to be created from an impersonation token + * and and the impersonation level of the impersonation token is below SecurityImpersonation. + */ + if (Token->TokenType == TokenImpersonation && + TokenType == TokenPrimary && + Token->ImpersonationLevel < SecurityImpersonation) + { + ObDereferenceObject(Token); + SepReleaseSecurityQualityOfService(CapturedSecurityQualityOfService, + PreviousMode, + FALSE); + return STATUS_BAD_IMPERSONATION_LEVEL; + } + + Status = SepDuplicateToken(Token, + ObjectAttributes, + EffectiveOnly, + TokenType, + (QoSPresent ? CapturedSecurityQualityOfService->ImpersonationLevel : SecurityAnonymous), + PreviousMode, + &NewToken); + + ObDereferenceObject(Token); + + if (NT_SUCCESS(Status)) + { + Status = ObInsertObject(NewToken, + NULL, + (DesiredAccess ? DesiredAccess : HandleInformation.GrantedAccess), + 0, + NULL, + &hToken); + if (NT_SUCCESS(Status)) + { + _SEH2_TRY + { + *NewTokenHandle = hToken; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + } + } + + /* Free the captured structure */ + SepReleaseSecurityQualityOfService(CapturedSecurityQualityOfService, + PreviousMode, + FALSE); + + return Status; +} + +/** + * @brief + * Creates an access token in a restricted form + * from the original existing token, that is, such + * action is called filtering. + * + * @param[in] ExistingTokenHandle + * A handle to an access token which is to be filtered. + * + * @param[in] Flags + * Privilege flag options. This parameter argument influences how the + * token's privileges are filtered. For further details see remarks. + * + * @param[in] SidsToDisable + * Array of SIDs to disable. The action of doing so assigns the + * SE_GROUP_USE_FOR_DENY_ONLY attribute to the respective group + * SID and takes away SE_GROUP_ENABLED and SE_GROUP_ENABLED_BY_DEFAULT. + * This parameter can be NULL. This can be a UM pointer. + * + * @param[in] PrivilegesToDelete + * Array of privileges to delete. The function will walk within this + * array to determine if the specified privileges do exist in the + * access token. Any missing privileges gets ignored. This parameter + * can be NULL. This can be a UM pointer. + * + * @param[in] RestrictedSids + * An array list of restricted groups SID to be added in the access + * token. A token that is already restricted the newly added restricted + * SIDs are redundant information in addition to the existing restricted + * SIDs in the token. This parameter can be NULL. This can be a UM pointer. + * + * @param[out] NewTokenHandle + * A new handle to the restricted (filtered) access token. This can be a + * UM pointer. + * + * @return + * Returns STATUS_SUCCESS if the routine has successfully filtered the + * access token. STATUS_INVALID_PARAMETER is returned if one or more + * parameters are not valid (see SepPerformTokenFiltering routine call + * for more information). A failure NTSTATUS code is returned otherwise. + * + * @remarks + * The Flags parameter determines the final outcome of how the privileges + * in an access token are filtered. This parameter can take these supported + * values (these can be combined): + * + * 0 -- Filter the token's privileges in the usual way. The function expects + * that the caller MUST PROVIDE a valid array list of privileges to be + * deleted (that is, PrivilegesToDelete MUSTN'T BE NULL). + * + * DISABLE_MAX_PRIVILEGE -- Disables (deletes) all the privileges except SeChangeNotifyPrivilege + * in the new access token. Bear in mind if this flag is specified + * the routine ignores PrivilegesToDelete. + * + * SANDBOX_INERT -- Stores the TOKEN_SANDBOX_INERT token flag within the access token. + * + * LUA_TOKEN -- The newly filtered access token is a LUA token. This flag is not + * supported in Windows Server 2003. + * + * WRITE_RESTRICTED -- The newly filtered token has the restricted SIDs that are + * considered only when evaluating write access onto the token. + * This value is not supported in Windows Server 2003. + */ +NTSTATUS +NTAPI +NtFilterToken( + _In_ HANDLE ExistingTokenHandle, + _In_ ULONG Flags, + _In_opt_ PTOKEN_GROUPS SidsToDisable, + _In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete, + _In_opt_ PTOKEN_GROUPS RestrictedSids, + _Out_ PHANDLE NewTokenHandle) +{ + PTOKEN Token, FilteredToken; + HANDLE FilteredTokenHandle; + NTSTATUS Status; + KPROCESSOR_MODE PreviousMode; + OBJECT_HANDLE_INFORMATION HandleInfo; + ULONG ResultLength; + ULONG CapturedSidsCount = 0; + ULONG CapturedPrivilegesCount = 0; + ULONG CapturedRestrictedSidsCount = 0; + ULONG ProbeSize = 0; + PSID_AND_ATTRIBUTES CapturedSids = NULL; + PSID_AND_ATTRIBUTES CapturedRestrictedSids = NULL; + PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL; + + PAGED_CODE(); + + PreviousMode = ExGetPreviousMode(); + + _SEH2_TRY + { + /* Probe SidsToDisable */ + if (SidsToDisable != NULL) + { + /* Probe the header */ + ProbeForRead(SidsToDisable, sizeof(*SidsToDisable), sizeof(ULONG)); + + CapturedSidsCount = SidsToDisable->GroupCount; + ProbeSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedSidsCount]); + + ProbeForRead(SidsToDisable, ProbeSize, sizeof(ULONG)); + } + + /* Probe PrivilegesToDelete */ + if (PrivilegesToDelete != NULL) + { + /* Probe the header */ + ProbeForRead(PrivilegesToDelete, sizeof(*PrivilegesToDelete), sizeof(ULONG)); + + CapturedPrivilegesCount = PrivilegesToDelete->PrivilegeCount; + ProbeSize = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[CapturedPrivilegesCount]); + + ProbeForRead(PrivilegesToDelete, ProbeSize, sizeof(ULONG)); + } + + /* Probe RestrictedSids */ + if (RestrictedSids != NULL) + { + /* Probe the header */ + ProbeForRead(RestrictedSids, sizeof(*RestrictedSids), sizeof(ULONG)); + + CapturedRestrictedSidsCount = RestrictedSids->GroupCount; + ProbeSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedRestrictedSidsCount]); + + ProbeForRead(RestrictedSids, ProbeSize, sizeof(ULONG)); + } + + /* Probe the handle */ + ProbeForWriteHandle(NewTokenHandle); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + /* Return the exception code */ + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } + _SEH2_END; + + /* Reference the token */ + Status = ObReferenceObjectByHandle(ExistingTokenHandle, + TOKEN_DUPLICATE, + SeTokenObjectType, + PreviousMode, + (PVOID*)&Token, + &HandleInfo); + if (!NT_SUCCESS(Status)) + { + DPRINT1("NtFilterToken(): Failed to reference the token (Status 0x%lx)\n", Status); + return Status; + } + + /* Capture the group SIDs */ + if (SidsToDisable != NULL) + { + Status = SeCaptureSidAndAttributesArray(SidsToDisable->Groups, + CapturedSidsCount, + PreviousMode, + NULL, + 0, + PagedPool, + TRUE, + &CapturedSids, + &ResultLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("NtFilterToken(): Failed to capture the SIDs (Status 0x%lx)\n", Status); + goto Quit; + } + } + + /* Capture the privileges */ + if (PrivilegesToDelete != NULL) + { + Status = SeCaptureLuidAndAttributesArray(PrivilegesToDelete->Privileges, + CapturedPrivilegesCount, + PreviousMode, + NULL, + 0, + PagedPool, + TRUE, + &CapturedPrivileges, + &ResultLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("NtFilterToken(): Failed to capture the privileges (Status 0x%lx)\n", Status); + goto Quit; + } + } + + /* Capture the restricted SIDs */ + if (RestrictedSids != NULL) + { + Status = SeCaptureSidAndAttributesArray(RestrictedSids->Groups, + CapturedRestrictedSidsCount, + PreviousMode, + NULL, + 0, + PagedPool, + TRUE, + &CapturedRestrictedSids, + &ResultLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("NtFilterToken(): Failed to capture the restricted SIDs (Status 0x%lx)\n", Status); + goto Quit; + } + } + + /* Call the internal API */ + Status = SepPerformTokenFiltering(Token, + CapturedPrivileges, + CapturedSids, + CapturedRestrictedSids, + CapturedPrivilegesCount, + CapturedSidsCount, + CapturedRestrictedSidsCount, + Flags, + PreviousMode, + &FilteredToken); + if (!NT_SUCCESS(Status)) + { + DPRINT1("NtFilterToken(): Failed to filter the token (Status 0x%lx)\n", Status); + goto Quit; + } + + /* Insert the filtered token and retrieve a handle to it */ + Status = ObInsertObject(FilteredToken, + NULL, + HandleInfo.GrantedAccess, + 0, + NULL, + &FilteredTokenHandle); + if (!NT_SUCCESS(Status)) + { + DPRINT1("NtFilterToken(): Failed to insert the filtered token (Status 0x%lx)\n", Status); + goto Quit; + } + + /* And return it to the caller once we're done */ + _SEH2_TRY + { + *NewTokenHandle = FilteredTokenHandle; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + Status = _SEH2_GetExceptionCode(); + _SEH2_YIELD(goto Quit); + } + _SEH2_END; + +Quit: + /* Dereference the token */ + ObDereferenceObject(Token); + + /* Release all the captured data */ + if (CapturedSids != NULL) + { + SeReleaseSidAndAttributesArray(CapturedSids, + PreviousMode, + TRUE); + } + + if (CapturedPrivileges != NULL) + { + SeReleaseLuidAndAttributesArray(CapturedPrivileges, + PreviousMode, + TRUE); + } + + if (CapturedRestrictedSids != NULL) + { + SeReleaseSidAndAttributesArray(CapturedRestrictedSids, + PreviousMode, + TRUE); + } + + return Status; +} + +/* EOF */