/*++ Copyright (c) 1989 Microsoft Corporation Module Name: Subject.c Abstract: This Module implements services related to subject security context. These services are part of the services provided by the Reference Monitor component. FOR PERFORMANCE SAKE, THIS MODULE IS AWARE OF INTERNAL TOKEN OBJECT FORMATS. Author: Jim Kelly (JimK) 2-Aug-1990 Environment: Kernel Mode Revision History: --*/ #include "pch.h" #pragma hdrstop #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,SeCaptureSubjectContext) #pragma alloc_text(PAGE,SeCaptureSubjectContextEx) #pragma alloc_text(PAGE,SeLockSubjectContext) #pragma alloc_text(PAGE,SeUnlockSubjectContext) #pragma alloc_text(PAGE,SeReleaseSubjectContext) #pragma alloc_text(PAGE,SepGetDefaultsSubjectContext) #pragma alloc_text(PAGE,SepIdAssignableAsGroup) #pragma alloc_text(PAGE,SepValidOwnerSubjectContext) //#pragma alloc_text(PAGE,SeQueryAuthenticationIdSubjectContext) #endif VOID SeCaptureSubjectContext ( OUT PSECURITY_SUBJECT_CONTEXT SubjectContext ) /*++ Routine Description: This routine takes a snapshot of the calling thread's security context (locking tokens as necessary to do so). This function is intended to support the object manager and other components that utilize the reference monitor's access validation, privilege test, and audit generation services. A subject's security context should be captured before initiating access validation and should be released after audit messages are generated. This is necessary to provide a consistent security context to all those services. After calling access validation, privilege test, and audit generation services, the captured context should be released as soon as possible using the SeReleaseSubjectContext() service. Arguments: SubjectContext - Points to a SECURITY_SUBJECT_CONTEXT data structure to be filled in with a snapshot of the calling thread's security profile. Return Value: none. --*/ { SeCaptureSubjectContextEx (PsGetCurrentThread (), PsGetCurrentProcess (), SubjectContext); } VOID SeCaptureSubjectContextEx ( IN PETHREAD Thread, IN PEPROCESS Process, OUT PSECURITY_SUBJECT_CONTEXT SubjectContext ) /*++ Routine Description: This routine takes a snapshot of the calling thread's security context (locking tokens as necessary to do so). This function is intended to support the object manager and other components that utilize the reference monitor's access validation, privilege test, and audit generation services. A subject's security context should be captured before initiating access validation and should be released after audit messages are generated. This is necessary to provide a consistent security context to all those services. After calling access validation, privilege test, and audit generation services, the captured context should be released as soon as possible using the SeReleaseSubjectContext() service. Arguments: Thread - Thread to capture the thread token from. If NULL we don't capture an impersonation token. Process - Process to capture primary token from. SubjectContext - Points to a SECURITY_SUBJECT_CONTEXT data structure to be filled in with a snapshot of the calling thread's security profile. Return Value: none. --*/ { //PVOID Objects[2]; BOOLEAN IgnoreCopyOnOpen; BOOLEAN IgnoreEffectiveOnly; PAGED_CODE(); SubjectContext->ProcessAuditId = PsProcessAuditId( Process ); // // Get pointers to primary and impersonation tokens // if (Thread == NULL) { SubjectContext->ClientToken = NULL; } else { SubjectContext->ClientToken = PsReferenceImpersonationToken( Thread, &IgnoreCopyOnOpen, &IgnoreEffectiveOnly, &(SubjectContext->ImpersonationLevel) ); } SubjectContext->PrimaryToken = PsReferencePrimaryToken(Process); #if DBG || TOKEN_LEAK_MONITOR if (SubjectContext->PrimaryToken) { InterlockedIncrement(&((PTOKEN)(SubjectContext->PrimaryToken))->CaptureCount); if (SubjectContext->PrimaryToken == SepTokenLeakToken) { DbgBreakPoint(); } } if (SubjectContext->ClientToken) { InterlockedIncrement(&((PTOKEN)(SubjectContext->ClientToken))->CaptureCount); if (SubjectContext->ClientToken == SepTokenLeakToken) { DbgBreakPoint(); } } #endif return; } VOID SeLockSubjectContext( IN PSECURITY_SUBJECT_CONTEXT SubjectContext ) /*++ Routine Description: Acquires READ LOCKS on the primary and impersonation tokens in the passed SubjectContext. This call must be undone by a call to SeUnlockSubjectContext(). No one outside of the SE component should need to acquire a write lock to a token. Therefore there is no public interface to do this. Arguments: SubjectContext - Points to a SECURITY_SUBJECT_CONTEXT data structure which points to a primary token and an optional impersonation token. Return Value: None --*/ { PAGED_CODE(); SepAcquireTokenReadLock((PTOKEN)(SubjectContext->PrimaryToken)); if (ARGUMENT_PRESENT(SubjectContext->ClientToken)) { SepAcquireTokenReadLock((PTOKEN)(SubjectContext->ClientToken)); } return; } VOID SeUnlockSubjectContext( IN PSECURITY_SUBJECT_CONTEXT SubjectContext ) /*++ Routine Description: Releases the read locks on the token(s) in the passed SubjectContext. Arguments: SubjectContext - Points to a SECURITY_SUBJECT_CONTEXT data structure which points to a primary token and an optional impersonation token. Return Value: None --*/ { PAGED_CODE(); SepReleaseTokenReadLock((PTOKEN)(SubjectContext->PrimaryToken)); if (ARGUMENT_PRESENT(SubjectContext->ClientToken)) { SepReleaseTokenReadLock((PTOKEN)(SubjectContext->ClientToken)); } } VOID SeReleaseSubjectContext ( IN PSECURITY_SUBJECT_CONTEXT SubjectContext ) /*++ Routine Description: This routine releases a subject security context previously captured by SeCaptureSubjectContext(). Arguments: SubjectContext - Points to a SECURITY_SUBJECT_CONTEXT data structure containing a subject's previously captured security context. Return Value: none. --*/ { PAGED_CODE(); #if DBG || TOKEN_LEAK_MONITOR if (SubjectContext->PrimaryToken) { InterlockedDecrement(&((PTOKEN)(SubjectContext->PrimaryToken))->CaptureCount); if (SubjectContext->PrimaryToken == SepTokenLeakToken) { DbgBreakPoint(); } } if (SubjectContext->ClientToken) { InterlockedDecrement(&((PTOKEN)(SubjectContext->ClientToken))->CaptureCount); if (SubjectContext->ClientToken == SepTokenLeakToken) { DbgBreakPoint(); } } #endif PsDereferencePrimaryTokenEx( PsGetCurrentProcess(), SubjectContext->PrimaryToken ); SubjectContext->PrimaryToken = NULL; PsDereferenceImpersonationToken( SubjectContext->ClientToken ); SubjectContext->ClientToken = NULL; return; } VOID SepGetDefaultsSubjectContext( IN PSECURITY_SUBJECT_CONTEXT SubjectContext, OUT PSID *Owner, OUT PSID *Group, OUT PSID *ServerOwner, OUT PSID *ServerGroup, OUT PACL *Dacl ) /*++ Routine Description: This routine retrieves pointers to the default owner, primary group, and, if present, discretionary ACL of the provided subject security context. Arguments: SubjectContext - Points to the subject security context whose default values are to be retrieved. Owner - Receives a pointer to the subject's default owner SID. This value will always be returned as a non-zero pointer. That is, a subject's security context must contain a owner SID. Group - Receives a pointer to the subject's default primary group SID. This value will always be returned as a non-zero pointer. That is, a subject's security context must contain a primary group. Dacl - Receives a pointer to the subject's default discretionary ACL, if one is define for the subject. Note that a subject security context does not have to include a default discretionary ACL. In this case, this value will be returned as NULL. Return Value: none. --*/ { PTOKEN EffectiveToken; PTOKEN PrimaryToken; PAGED_CODE(); if (ARGUMENT_PRESENT(SubjectContext->ClientToken)) { EffectiveToken = (PTOKEN)SubjectContext->ClientToken; } else { EffectiveToken = (PTOKEN)SubjectContext->PrimaryToken; } (*Owner) = EffectiveToken->UserAndGroups[EffectiveToken->DefaultOwnerIndex].Sid; (*Group) = EffectiveToken->PrimaryGroup; (*Dacl) = EffectiveToken->DefaultDacl; PrimaryToken = (PTOKEN)SubjectContext->PrimaryToken; *ServerOwner = PrimaryToken->UserAndGroups[PrimaryToken->DefaultOwnerIndex].Sid; *ServerGroup = PrimaryToken->PrimaryGroup; return; } BOOLEAN SepIdAssignableAsGroup( IN PACCESS_TOKEN AToken, IN PSID Group ) /*++ Routine Description: This routine checks to see whether the provided SID is one that may be assigned to be the default primary group in a token. The current criteria is that the passed SID be a group in the token, with no other restrictions. Arguments: Token - Points to the token to be examined. Group - Points to the SID to be checked. Return Value: TRUE - SID passed by be assigned as the default primary group in a token. FALSE - Passed SID may not be so assigned. --*/ { ULONG Index; BOOLEAN Found = FALSE; PTOKEN Token; PAGED_CODE(); Token = (PTOKEN)AToken; // // Let's make it invalid to assign a NULL primary group, // but we may need to revisit this. // if (Group == NULL) { return( FALSE ); } Index = 0; SepAcquireTokenReadLock( Token ); // // Walk through the list of user and group IDs looking // for a match to the specified SID. // while (Index < Token->UserAndGroupCount) { Found = RtlEqualSid( Group, Token->UserAndGroups[Index].Sid ); if ( Found ) { break; } Index += 1; } SepReleaseTokenReadLock( Token ); return Found; } BOOLEAN SepValidOwnerSubjectContext( IN PSECURITY_SUBJECT_CONTEXT SubjectContext, IN PSID Owner, IN BOOLEAN ServerObject ) /*++ Routine Description: This routine checks to see whether the provided SID is one the subject is authorized to assign as the owner of objects. It will also check to see if the caller has SeRestorePrivilege, if so, the request is granted. Arguments: SubjectContext - Points to the subject's security context. Owner - Points to the SID to be checked. Return Value: none. --*/ { ULONG Index; BOOLEAN Found; PTOKEN EffectiveToken; BOOLEAN Rc = FALSE; PAGED_CODE(); // // It is invalid to assign a NULL owner, regardless of // whether you have SeRestorePrivilege or not. // if (Owner == NULL) { return( FALSE ); } // // Allowable owners come from the primary if it's a server object. // if (!ServerObject && ARGUMENT_PRESENT(SubjectContext->ClientToken)) { EffectiveToken = (PTOKEN)SubjectContext->ClientToken; } else { EffectiveToken = (PTOKEN)SubjectContext->PrimaryToken; } // // If we're impersonating, make sure we're at TokenImpersonation // or above. This prevents someone from setting the owner of an // object when impersonating at less Identify or Anonymous. // if (EffectiveToken->TokenType == TokenImpersonation) { if (EffectiveToken->ImpersonationLevel < SecurityImpersonation) { return( FALSE ); } } Index = 0; SepAcquireTokenReadLock( EffectiveToken ); // // Walk through the list of user and group IDs looking // for a match to the specified SID. If one is found, // make sure it may be assigned as an owner. // // This code is similar to that performed to set the default // owner of a token (NtSetInformationToken). // while (Index < EffectiveToken->UserAndGroupCount) { Found = RtlEqualSid( Owner, EffectiveToken->UserAndGroups[Index].Sid ); if ( Found ) { // // We may return success if the Sid is one that may be assigned // as an owner, or if the caller has SeRestorePrivilege // if ( SepIdAssignableAsOwner(EffectiveToken,Index) ) { SepReleaseTokenReadLock( EffectiveToken ); Rc = TRUE; goto exit; } else { // // Rc is already set to FALSE, just exit. // SepReleaseTokenReadLock( EffectiveToken ); goto exit; } //endif assignable } //endif Found Index += 1; } //endwhile SepReleaseTokenReadLock( EffectiveToken ); exit: // // If we are going to fail this call, check for Restore privilege, // and succeed if he has it. // // // We should really have gotten PreviousMode from the caller, but we // didn't, so hard wire it to be user-mode here. // if ( Rc == FALSE ) { Rc = SeSinglePrivilegeCheck( SeRestorePrivilege, UserMode ); } return Rc; } #if 0 NTSTATUS SeQueryAuthenticationIdSubjectContext( IN PSECURITY_SUBJECT_CONTEXT SubjectContext, OUT PLUID AuthenticationId ) /*++ Routine Description: This routine returns the authentication ID for the effective token in a subject context Parameters: SubjectContext - The subject context to get the ID from AuthenticationId - Receives the authentication ID from the token Return Value: Errors from SeQueryAuthenticationidToken. --*/ { NTSTATUS Status; PAGED_CODE(); SeLockSubjectContext( SubjectContext ); Status = SeQueryAuthenticationIdToken( EffectiveToken(SubjectContext), AuthenticationId ); SeUnlockSubjectContext( SubjectContext ); return( Status ); } #endif