/*++ Copyright (c) 1991 Microsoft Corporation Module Name: SecurSup.c Abstract: This module implements the Ntfs Security Support routines Author: Gary Kimura [GaryKi] 27-Dec-1991 Revision History: --*/ #include "NtfsProc.h" #define Dbg (DEBUG_TRACE_SECURSUP) #define DbgAcl (DEBUG_TRACE_SECURSUP | DEBUG_TRACE_ACLINDEX) // // Define a tag for general pool allocations from this module // #undef MODULE_POOL_TAG #define MODULE_POOL_TAG ('SFtN') UNICODE_STRING FileString = CONSTANT_UNICODE_STRING( L"File" ); // // Local procedure prototypes // PSHARED_SECURITY NtfsCacheSharedSecurityByDescriptor ( IN PIRP_CONTEXT IrpContext, PSECURITY_DESCRIPTOR SecurityDescriptor, ULONG SecurityDescriptorLength, IN BOOLEAN RaiseIfInvalid ); VOID NtfsStoreSecurityDescriptor ( PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN BOOLEAN LogIt ); PSHARED_SECURITY FindCachedSharedSecurityByHashUnsafe ( IN PVCB Vcb, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN ULONG SecurityDescriptorLength, IN ULONG Hash ); VOID AddCachedSharedSecurityUnsafe ( IN PVCB Vcb, PSHARED_SECURITY SharedSecurity ); BOOLEAN MapSecurityIdToSecurityDescriptorHeaderUnsafe ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN SECURITY_ID SecurityId, OUT PSECURITY_DESCRIPTOR_HEADER *SecurityDescriptorHeader, OUT PBCB *Bcb ); NTSTATUS NtOfsMatchSecurityHash ( IN PINDEX_ROW IndexRow, IN OUT PVOID MatchData ); VOID NtOfsLookupSecurityDescriptorInIndex ( PIRP_CONTEXT IrpContext, IN OUT PSHARED_SECURITY SharedSecurity ); PSHARED_SECURITY GetSharedSecurityFromDescriptorUnsafe ( IN PIRP_CONTEXT IrpContext, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN ULONG SecurityDescriptorLength, IN BOOLEAN RaiseIfInvalid ); #ifdef NTFS_CACHE_RIGHTS // // Local procedure prototypes for access rights cache // VOID NtfsAddCachedRights ( IN PVCB Vcb, IN PSHARED_SECURITY SharedSecurity, IN ACCESS_MASK Rights, IN PLUID TokenId, IN PLUID ModifiedId ); INLINE ACCESS_MASK NtfsGetCachedRightsWorld ( IN PCACHED_ACCESS_RIGHTS CachedRights ) { return CachedRights->EveryoneRights; } INLINE VOID NtfsSetCachedRightsWorld ( IN PSHARED_SECURITY SharedSecurity ) { SeGetWorldRights( &SharedSecurity->SecurityDescriptor, IoGetFileObjectGenericMapping(), &SharedSecurity->CachedRights.EveryoneRights ); // // Make certain that MAXIMUM_ALLOWED is not in the rights. // ClearFlag( SharedSecurity->CachedRights.EveryoneRights, MAXIMUM_ALLOWED ); return; } #endif #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NtfsAssignSecurity) #pragma alloc_text(PAGE, NtfsCacheSharedSecurityByDescriptor) #pragma alloc_text(PAGE, NtfsModifySecurity) #pragma alloc_text(PAGE, NtfsQuerySecurity) #pragma alloc_text(PAGE, NtfsAccessCheck) #pragma alloc_text(PAGE, NtfsCheckFileForDelete) #pragma alloc_text(PAGE, NtfsCheckIndexForAddOrDelete) #pragma alloc_text(PAGE, GetSharedSecurityFromDescriptorUnsafe) #pragma alloc_text(PAGE, NtfsSetFcbSecurityFromDescriptor) #pragma alloc_text(PAGE, NtfsNotifyTraverseCheck) #pragma alloc_text(PAGE, NtfsInitializeSecurity) #pragma alloc_text(PAGE, NtfsCacheSharedSecurityBySecurityId) #pragma alloc_text(PAGE, FindCachedSharedSecurityByHashUnsafe) #pragma alloc_text(PAGE, AddCachedSharedSecurityUnsafe) #pragma alloc_text(PAGE, NtOfsPurgeSecurityCache) #pragma alloc_text(PAGE, MapSecurityIdToSecurityDescriptorHeaderUnsafe) #pragma alloc_text(PAGE, NtfsLoadSecurityDescriptor) #pragma alloc_text(PAGE, NtOfsMatchSecurityHash) #pragma alloc_text(PAGE, NtOfsLookupSecurityDescriptorInIndex) #pragma alloc_text(PAGE, GetSecurityIdFromSecurityDescriptorUnsafe) #pragma alloc_text(PAGE, NtfsStoreSecurityDescriptor) #pragma alloc_text(PAGE, NtfsCacheSharedSecurityForCreate) #pragma alloc_text(PAGE, NtOfsCollateSecurityHash) #pragma alloc_text(PAGE, NtfsCanAdministerVolume) #endif #ifdef NTFS_CACHE_RIGHTS #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NtfsGetCachedRightsById) #pragma alloc_text(PAGE, NtfsGetCachedRights) #pragma alloc_text(PAGE, NtfsAddCachedRights) #endif #endif VOID NtfsAssignSecurity ( IN PIRP_CONTEXT IrpContext, IN PFCB ParentFcb, IN PIRP Irp, IN PFCB NewFcb, IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN PBCB FileRecordBcb, IN LONGLONG FileOffset, IN OUT PBOOLEAN LogIt ) /*++ Routine Description: LEGACY NOTE - this routine disappears when all volumes go to Cairo. This routine constructs and assigns a new security descriptor to the specified file/directory. The new security descriptor is placed both on the fcb and on the disk. This will only be called in the context of an open/create operation. It currently MUST NOT be called to store a security descriptor for an existing file, because it instructs NtfsStoreSecurityDescriptor to not log the change. If this is a large security descriptor then it is possible that AllocateClusters may be called twice within the call to AddAllocation when the attribute is created. If so then the second call will always log the changes. In that case we need to log all of the operations to create this security attribute and also we must log the current state of the file record. It is possible that our caller has already started logging operations against this log record. In that case we always log the security changes. Arguments: ParentFcb - Supplies the directory under which the new fcb exists Irp - Supplies the Irp being processed NewFcb - Supplies the fcb that is being assigned a new security descriptor FileRecord - Supplies the file record for this operation. Used if we have to log against the file record. FileRecordBcb - Bcb for the file record above. FileOffset - File offset in the Mft for this file record. LogIt - On entry this indicates whether our caller wants this operation logged. On exit we return TRUE if we logged the security change. Return Value: None. --*/ { PSECURITY_DESCRIPTOR SecurityDescriptor; NTSTATUS Status; BOOLEAN IsDirectory; PACCESS_STATE AccessState; PIO_STACK_LOCATION IrpSp; ULONG SecurityDescLength; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( ParentFcb ); ASSERT_IRP( Irp ); ASSERT_FCB( NewFcb ); PAGED_CODE(); if (NewFcb->Vcb->SecurityDescriptorStream != NULL) { return; } DebugTrace( +1, Dbg, ("NtfsAssignSecurity...\n") ); // // First decide if we are creating a file or a directory // IrpSp = IoGetCurrentIrpStackLocation(Irp); if (FlagOn(IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE)) { IsDirectory = TRUE; } else { IsDirectory = FALSE; } // // Extract the parts of the Irp that we need to do our assignment // AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState; // // Check if we need to load the security descriptor for the parent. // if (ParentFcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, ParentFcb ); } ASSERT( ParentFcb->SharedSecurity != NULL ); // // Create a new security descriptor for the file and raise if there is // an error // if (!NT_SUCCESS( Status = SeAssignSecurity( &ParentFcb->SharedSecurity->SecurityDescriptor, AccessState->SecurityDescriptor, &SecurityDescriptor, IsDirectory, &AccessState->SubjectSecurityContext, IoGetFileObjectGenericMapping(), PagedPool ))) { NtfsRaiseStatus( IrpContext, Status, NULL, NULL ); } // // Load the security descriptor into the Fcb // SecurityDescLength = RtlLengthSecurityDescriptor( SecurityDescriptor ); try { // // Make sure the length is non-zero. // if (SecurityDescLength == 0) { NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL ); } ASSERT( SeValidSecurityDescriptor( SecurityDescLength, SecurityDescriptor )); NtfsSetFcbSecurityFromDescriptor( IrpContext, NewFcb, SecurityDescriptor, SecurityDescLength, TRUE ); } finally { // // Free the security descriptor created by Se // SeDeassignSecurity( &SecurityDescriptor ); } // // If the security descriptor is large enough that it may cause us to // start logging in the StoreSecurity call below then make sure everything // is logged. // if (!(*LogIt) && (SecurityDescLength > BytesFromClusters( NewFcb->Vcb, MAXIMUM_RUNS_AT_ONCE ))) { // // Log the current state of the file record. // FileRecord->Lsn = NtfsWriteLog( IrpContext, NewFcb->Vcb->MftScb, FileRecordBcb, InitializeFileRecordSegment, FileRecord, FileRecord->FirstFreeByte, Noop, NULL, 0, FileOffset, 0, 0, NewFcb->Vcb->BytesPerFileRecordSegment ); *LogIt = TRUE; } // // Write out the new security descriptor // NtfsStoreSecurityDescriptor( IrpContext, NewFcb, *LogIt ); // // And return to our caller // DebugTrace( -1, Dbg, ("NtfsAssignSecurity -> VOID\n") ); return; } PSHARED_SECURITY NtfsCacheSharedSecurityByDescriptor ( IN PIRP_CONTEXT IrpContext, PSECURITY_DESCRIPTOR SecurityDescriptor, ULONG SecurityDescriptorLength, IN BOOLEAN RaiseIfInvalid ) /*++ Routine Description: This routine finds or constructs a security id and SHARED_SECURITY from a specific file or directory. Arguments: IrpContext - Context of the call SecurityDescriptor - the actual security descriptor being stored SecurityDescriptorLength - length of security descriptor RaiseIfInvalid - raise status if sd is invalid Return Value: Referenced shared security. --*/ { PSHARED_SECURITY SharedSecurity = NULL; SECURITY_ID SecurityId; ULONG FcbSecurityAcquired; ULONG OwnerCount; ASSERT_IRP_CONTEXT( IrpContext ); PAGED_CODE(); // // LEGACY NOTE - this goes away when all volumes become NT 5.0 // if (IrpContext->Vcb->SecurityDescriptorStream == NULL) { return NULL; } DebugTrace( +1, DbgAcl, ("NtfsCacheSharedSecurityByDescriptor...\n") ); // // Serialize access to the security cache and use a try/finally to make // sure we release it // NtfsAcquireFcbSecurity( IrpContext->Vcb ); FcbSecurityAcquired = TRUE; // // Capture our owner count on the mft - so we can release it if we acquired it later on // growing the file record for the security stream // OwnerCount = NtfsIsSharedScb( IrpContext->Vcb->MftScb ); try { // // We have a security descriptor. Create a shared security descriptor. // SharedSecurity = GetSharedSecurityFromDescriptorUnsafe( IrpContext, SecurityDescriptor, SecurityDescriptorLength, RaiseIfInvalid ); // // Make sure the shared security doesn't go away // SharedSecurity->ReferenceCount += 1; DebugTrace( 0, DbgAcl, ("NtfsCacheSharedSecurityByDescriptor bumping refcount %08x\n", SharedSecurity )); // // If we found a shared security descriptor with no Id assigned, then // we must assign it. Since it is known that no Id was assigned we // must also add it into the cache. // if (SharedSecurity->Header.HashKey.SecurityId == SECURITY_ID_INVALID) { // // Find unique SecurityId for descriptor and set SecurityId in Fcb. // SecurityId = GetSecurityIdFromSecurityDescriptorUnsafe( IrpContext, SharedSecurity ); ASSERT( SharedSecurity->Header.HashKey.SecurityId == SecurityId ); SharedSecurity->Header.HashKey.SecurityId = SecurityId; DebugTrace( 0, DbgAcl, ("NtfsCacheSharedSecurityByDescriptor setting security Id to new %08x\n", SecurityId )); // // We need to drop the FcbSecurity before performing the checkpoint, to avoid // deadlocks, but this is ok since we have incremented the reference count on // our SharedSecurity. // NtfsReleaseFcbSecurity( IrpContext->Vcb ); FcbSecurityAcquired = FALSE; // // Checkpoint the current transaction so that we can safely add this // shared security to the cache. Once this call is complete, we are // guaranteed that the security index modifications make it out to // disk before the newly allocated security ID does. // NtfsCheckpointCurrentTransaction( IrpContext ); // // Release the security descriptor and mft if owned // NtfsReleaseExclusiveScbIfOwned( IrpContext, IrpContext->Vcb->SecurityDescriptorStream ); // // Check if the mft has been acquired during the call before releasing it // if (NtfsIsSharedScb( IrpContext->Vcb->MftScb ) != OwnerCount) { NtfsReleaseScb( IrpContext, IrpContext->Vcb->MftScb ); } // // Cache this shared security for faster access. // NtfsAcquireFcbSecurity( IrpContext->Vcb ); FcbSecurityAcquired = TRUE; AddCachedSharedSecurityUnsafe( IrpContext->Vcb, SharedSecurity ); } } finally { if (AbnormalTermination( )) { if (SharedSecurity != NULL) { if (!FcbSecurityAcquired) { NtfsAcquireFcbSecurity( IrpContext->Vcb ); RemoveReferenceSharedSecurityUnsafe( &SharedSecurity ); FcbSecurityAcquired = TRUE; } } } if (FcbSecurityAcquired) { NtfsReleaseFcbSecurity( IrpContext->Vcb ); } } // // And return to our caller // DebugTrace( -1, DbgAcl, ( "NtfsCacheSharedSecurityByDescriptor -> %08x\n", SharedSecurity ) ); return SharedSecurity; } NTSTATUS NtfsModifySecurity ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PSECURITY_INFORMATION SecurityInformation, OUT PSECURITY_DESCRIPTOR SecurityDescriptor ) /*++ Routine Description: This routine modifies an existing security descriptor for a file/directory. Arguments: Fcb - Supplies the Fcb whose security is being modified SecurityInformation - Supplies the security information structure passed to the file system by the I/O system. SecurityDescriptor - Supplies the security information structure passed to the file system by the I/O system. Return Value: NTSTATUS - Returns an appropriate status value for the function results --*/ { NTSTATUS Status; PSECURITY_DESCRIPTOR DescriptorPtr; ULONG DescriptorLength; PSCB Scb; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( Fcb ); PAGED_CODE(); DebugTrace( +1, DbgAcl, ("NtfsModifySecurity...\n") ); // // First check if we need to load the security descriptor for the file // if (Fcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, Fcb ); } ASSERT( Fcb->SharedSecurity != NULL); DescriptorPtr = &Fcb->SharedSecurity->SecurityDescriptor; // // Do the modify operation. SeSetSecurityDescriptorInfo no longer // frees the passed security descriptor. // if (!NT_SUCCESS( Status = SeSetSecurityDescriptorInfo( NULL, SecurityInformation, SecurityDescriptor, &DescriptorPtr, PagedPool, IoGetFileObjectGenericMapping() ))) { NtfsRaiseStatus( IrpContext, Status, NULL, NULL ); } DescriptorLength = RtlLengthSecurityDescriptor( DescriptorPtr ); try { // // Check for a zero length. // if (DescriptorLength == 0) { NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL ); } // // LEGACY NOTE - remove this test when all volumes go to NT 5 // if (Fcb->Vcb->SecurityDescriptorStream != NULL) { PSHARED_SECURITY SharedSecurity; PSHARED_SECURITY OldSharedSecurity = NULL; SECURITY_ID OldSecurityId; ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext; // // Cache security descriptor // // // After the SeSetSecurityDescriptorInfo we should have a valid sd // ASSERT( SeValidSecurityDescriptor( DescriptorLength, DescriptorPtr )); SharedSecurity = NtfsCacheSharedSecurityByDescriptor( IrpContext, DescriptorPtr, DescriptorLength, TRUE ); NtfsInitializeAttributeContext( &AttributeContext ); try { // // Move Quota to new owner as described in descriptor. // NtfsMoveQuotaOwner( IrpContext, Fcb, DescriptorPtr ); // // Set in new shared security // OldSharedSecurity = Fcb->SharedSecurity; OldSecurityId = Fcb->SecurityId; Fcb->SharedSecurity = SharedSecurity; Fcb->SecurityId = SharedSecurity->Header.HashKey.SecurityId; DebugTrace( 0, DbgAcl, ("NtfsModifySecurity setting Fcb securityId to %08x\n", Fcb->SecurityId )); // // We are called to replace an existing security descriptor. In the // event that we have a downlevel $STANDARD_INFORMATION attribute, we // must convert it to large form so that the security ID is stored. // if (!FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO) ) { DebugTrace( 0, DbgAcl, ("Growing standard information\n") ); NtfsGrowStandardInformation( IrpContext, Fcb ); } // // Despite having a large $STANDARD_INFORMATION, we may have // a security descriptor present. This occurs if the SecurityId // is invalid // if (OldSecurityId == SECURITY_ID_INVALID) { // // Read in the security descriptor attribute. If it // doesn't exist then we're done, otherwise simply delete the // attribute // if (NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $SECURITY_DESCRIPTOR, &AttributeContext )) { UNICODE_STRING NoName = CONSTANT_UNICODE_STRING( L"" ); DebugTrace( 0, DbgAcl, ("Delete existing Security Descriptor\n") ); NtfsDeleteAttributeRecord( IrpContext, Fcb, DELETE_LOG_OPERATION | DELETE_RELEASE_FILE_RECORD | DELETE_RELEASE_ALLOCATION, &AttributeContext ); // // If the $SECURITY_DESCRIPTOR was non resident, the above // delete call created one for us under the covers. We // need to mark it as deleted otherwise, we detect the // volume as being corrupt. // Scb = NtfsCreateScb( IrpContext, Fcb, $SECURITY_DESCRIPTOR, &NoName, TRUE, NULL ); if (Scb != NULL) { ASSERT_EXCLUSIVE_SCB( Scb ); SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ); } } } // // The security descriptor in the FCB is now changed and may not // reflect what is $STANDARD_INFORMATION. The caller is responsible // for making this update. // } finally { NtfsCleanupAttributeContext( IrpContext, &AttributeContext ); if (AbnormalTermination()) { if (OldSharedSecurity != NULL) { // // Put back the security the way we found it // Fcb->SharedSecurity = OldSharedSecurity; Fcb->SecurityId = OldSecurityId; DebugTrace( 0, DbgAcl, ("NtfsModifySecurity resetting Fcb->SecurityId to %08x\n", Fcb->SecurityId )); } OldSharedSecurity = SharedSecurity; } // // release old security descriptor (or new one if // NtfsMoveQuotaOwner raises // ASSERT( OldSharedSecurity != NULL ); NtfsAcquireFcbSecurity( Fcb->Vcb ); RemoveReferenceSharedSecurityUnsafe( &OldSharedSecurity ); NtfsReleaseFcbSecurity( Fcb->Vcb ); } } else { // LEGACY NOTE - delete this clause when all volumes go to NT 5 // // Update the move the quota to the new owner if necessary. // NtfsMoveQuotaOwner( IrpContext, Fcb, DescriptorPtr ); // // Load the security descriptor into the Fcb // NtfsAcquireFcbSecurity( Fcb->Vcb ); RemoveReferenceSharedSecurityUnsafe( &Fcb->SharedSecurity ); NtfsReleaseFcbSecurity( Fcb->Vcb ); NtfsSetFcbSecurityFromDescriptor( IrpContext, Fcb, DescriptorPtr, DescriptorLength, TRUE ); // // Now we need to store the new security descriptor on disk // NtfsStoreSecurityDescriptor( IrpContext, Fcb, TRUE ); } } finally { SeDeassignSecurity( &DescriptorPtr ); } // // Remember that we modified the security on the file. // SetFlag( Fcb->InfoFlags, FCB_INFO_MODIFIED_SECURITY ); // // And return to our caller // DebugTrace( -1, DbgAcl, ("NtfsModifySecurity -> %08lx\n", Status) ); return Status; } NTSTATUS NtfsQuerySecurity ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PSECURITY_INFORMATION SecurityInformation, OUT PSECURITY_DESCRIPTOR SecurityDescriptor, IN OUT PULONG SecurityDescriptorLength ) /*++ Routine Description: This routine is used to query the contents of an existing security descriptor for a file/directory. Arguments: Fcb - Supplies the file/directory being queried SecurityInformation - Supplies the security information structure passed to the file system by the I/O system. SecurityDescriptor - Supplies the security information structure passed to the file system by the I/O system. SecurityDescriptorLength - Supplies the length of the input security descriptor buffer in bytes. Return Value: NTSTATUS - Returns an appropriate status value for the function results --*/ { NTSTATUS Status; PSECURITY_DESCRIPTOR LocalPointer; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( Fcb ); PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsQuerySecurity...\n") ); // // First check if we need to load the security descriptor for the file // if (Fcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, Fcb ); } LocalPointer = &Fcb->SharedSecurity->SecurityDescriptor; // // Now with the security descriptor loaded do the query operation but // protect ourselves with a exception handler just in case the caller's // buffer isn't valid // try { Status = SeQuerySecurityDescriptorInfo( SecurityInformation, SecurityDescriptor, SecurityDescriptorLength, &LocalPointer ); } except(EXCEPTION_EXECUTE_HANDLER) { ExRaiseStatus( STATUS_INVALID_USER_BUFFER ); } // // And return to our caller // DebugTrace( -1, Dbg, ("NtfsQuerySecurity -> %08lx\n", Status) ); return Status; } #define NTFS_SE_CONTROL (((SE_DACL_PRESENT | SE_SELF_RELATIVE) << 16) | SECURITY_DESCRIPTOR_REVISION1) #define NTFS_DEFAULT_ACCESS_MASK 0x001f01ff ULONG NtfsWorldAclFile[] = { 0x00000000, // Null Sacl 0x00000014, // Dacl 0x001c0002, // Acl header 0x00000001, // One ACE 0x00140000, // ACE Header NTFS_DEFAULT_ACCESS_MASK, 0x00000101, // World Sid 0x01000000, 0x00000000 }; ULONG NtfsWorldAclDir[] = { 0x00000000, // Null Sacl 0x00000014, // Dacl 0x00300002, // Acl header 0x00000002, // Two ACEs 0x00140000, // ACE Header NTFS_DEFAULT_ACCESS_MASK, 0x00000101, // World Sid 0x01000000, 0x00000000, 0x00140b00, // ACE Header NTFS_DEFAULT_ACCESS_MASK, 0x00000101, // World Sid 0x01000000, 0x00000000 }; VOID NtfsAccessCheck ( PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PFCB ParentFcb OPTIONAL, IN PIRP Irp, IN ACCESS_MASK DesiredAccess, IN BOOLEAN CheckOnly ) /*++ Routine Description: This routine does a general access check for the indicated desired access. This will only be called in the context of an open/create operation. If access is granted then control is returned to the caller otherwise this function will do the proper Nt security calls to log the attempt and then raise an access denied status. Arguments: Fcb - Supplies the file/directory being examined ParentFcb - Optionally supplies the parent of the Fcb being examined Irp - Supplies the Irp being processed DesiredAccess - Supplies a mask of the access being requested CheckOnly - Indicates if this operation is to check the desired access only and not accumulate the access granted here. In this case we are guaranteed that we have passed in a hard-wired desired access and MAXIMUM_ALLOWED will not be one of them. Return Value: None. --*/ { NTSTATUS Status; NTSTATUS AccessStatus; NTSTATUS AccessStatusError; PACCESS_STATE AccessState; PIO_STACK_LOCATION IrpSp; #ifdef NTFS_CACHE_RIGHTS ACCESS_MASK TmpDesiredAccess; #endif KPROCESSOR_MODE EffectiveMode; BOOLEAN AccessGranted; ACCESS_MASK GrantedAccess; PISECURITY_DESCRIPTOR SecurityDescriptor; PPRIVILEGE_SET Privileges; PUNICODE_STRING FileName; PUNICODE_STRING RelatedFileName; PUNICODE_STRING PartialFileName; UNICODE_STRING FullFileName; PUNICODE_STRING DeviceObjectName; USHORT DeviceObjectNameLength; ULONG FullFileNameLength; ATTRIBUTE_ENUMERATION_CONTEXT Context; BOOLEAN CleanupAttrContext = FALSE; BOOLEAN LeadingSlash; BOOLEAN RelatedFileNamePresent; BOOLEAN PartialFileNamePresent; BOOLEAN MaximumRequested; BOOLEAN MaximumDeleteAcquired; BOOLEAN MaximumReadAttrAcquired; BOOLEAN PerformAccessValidation; BOOLEAN PerformDeleteAudit; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( Fcb ); ASSERT_IRP( Irp ); PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsAccessCheck...\n") ); // // First extract the parts of the Irp that we need to do our checking // IrpSp = IoGetCurrentIrpStackLocation(Irp); AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState; // // Check if we need to load the security descriptor for the file // if (Fcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, Fcb ); } ASSERT( Fcb->SharedSecurity != NULL ); SecurityDescriptor = (PISECURITY_DESCRIPTOR) Fcb->SharedSecurity->SecurityDescriptor; // // Check to see if auditing is enabled and if this is the default world ACL. // if ((*((PULONG) SecurityDescriptor) == NTFS_SE_CONTROL) && !SeAuditingFileEvents( TRUE, SecurityDescriptor )) { // // Directories and files have different default ACLs. // if (((Fcb->Info.FileAttributes & DUP_FILE_NAME_INDEX_PRESENT) && RtlEqualMemory( &SecurityDescriptor->Sacl, NtfsWorldAclDir, sizeof( NtfsWorldAclDir ))) || RtlEqualMemory( &SecurityDescriptor->Sacl, NtfsWorldAclFile, sizeof(NtfsWorldAclFile))) { if (FlagOn( DesiredAccess, MAXIMUM_ALLOWED )) { GrantedAccess = NTFS_DEFAULT_ACCESS_MASK; } else { GrantedAccess = DesiredAccess & NTFS_DEFAULT_ACCESS_MASK; } if (!CheckOnly) { SetFlag( AccessState->PreviouslyGrantedAccess, GrantedAccess ); ClearFlag( AccessState->RemainingDesiredAccess, GrantedAccess | MAXIMUM_ALLOWED ); } DebugTrace( -1, Dbg, ("NtfsAccessCheck -> DefaultWorldAcl\n") ); return; } } Privileges = NULL; FileName = NULL; RelatedFileName = NULL; PartialFileName = NULL; DeviceObjectName = NULL; MaximumRequested = FALSE; MaximumDeleteAcquired = FALSE; MaximumReadAttrAcquired = FALSE; PerformAccessValidation = TRUE; PerformDeleteAudit = FALSE; // // Check to see if we need to perform access validation // ClearFlag( DesiredAccess, AccessState->PreviouslyGrantedAccess ); #ifdef NTFS_CACHE_RIGHTS // // Get any cached knowledge about rights that all callers are known to // have for this security descriptor. // GrantedAccess = NtfsGetCachedRightsWorld( &Fcb->SharedSecurity->CachedRights ); if (!CheckOnly) { SetFlag( AccessState->PreviouslyGrantedAccess, FlagOn( DesiredAccess, GrantedAccess )); } ClearFlag( DesiredAccess, GrantedAccess ); #endif if (DesiredAccess == 0) { // // Nothing to check, skip AVR and go straight to auditing // PerformAccessValidation = FALSE; AccessGranted = TRUE; } // // Remember the case where MAXIMUM_ALLOWED was requested. // if (FlagOn( DesiredAccess, MAXIMUM_ALLOWED )) { MaximumRequested = TRUE; } if (FlagOn(IrpSp->Parameters.Create.SecurityContext->FullCreateOptions,FILE_DELETE_ON_CLOSE)) { PerformDeleteAudit = TRUE; } // // SL_FORCE_ACCESS_CHECK causes us to use an effective RequestorMode // of UserMode. // EffectiveMode = (KPROCESSOR_MODE)(FlagOn( IrpSp->Flags, SL_FORCE_ACCESS_CHECK ) ? UserMode : Irp->RequestorMode); // // Lock the user context, do the access check and then unlock the context // SeLockSubjectContext( &AccessState->SubjectSecurityContext ); // // Use a try-finally to facilitate cleanup. // try { if (PerformAccessValidation) { #ifdef NTFS_CACHE_RIGHTS BOOLEAN EntryCached = FALSE; // // Check the cached information only if the effective // RequestorMode is UserMode. if (EffectiveMode == UserMode) { // // Add in any cached knowledge about rights that this caller // is known to have for this security descriptor. // (VOID)NtfsGetCachedRights( Fcb->Vcb, &AccessState->SubjectSecurityContext, Fcb->SharedSecurity, &GrantedAccess, &EntryCached, NULL, NULL ); // // Make certain that GrantedAccess has no rights not // originally requested. // ClearFlag( GrantedAccess, ~DesiredAccess ); TmpDesiredAccess = DesiredAccess; ClearFlag( TmpDesiredAccess, GrantedAccess ); if (EntryCached) { ClearFlag( TmpDesiredAccess, MAXIMUM_ALLOWED ); } // // If all rights are available, then access is granted. // if (TmpDesiredAccess == 0) { AccessGranted = TRUE; AccessStatus = STATUS_SUCCESS; // // Otherwise, we don't know. // } else { AccessGranted = FALSE; } } else { AccessGranted = FALSE; } #endif // // We need to take the slow path. // #ifdef NTFS_CACHE_RIGHTS if (!AccessGranted) { #endif // // Get the rights information. // AccessGranted = SeAccessCheck( &Fcb->SharedSecurity->SecurityDescriptor, &AccessState->SubjectSecurityContext, TRUE, // Tokens are locked DesiredAccess, 0, &Privileges, IoGetFileObjectGenericMapping(), EffectiveMode, &GrantedAccess, &AccessStatus ); if (Privileges != NULL) { Status = SeAppendPrivileges( AccessState, Privileges ); SeFreePrivileges( Privileges ); Privileges = NULL; } #ifdef NTFS_CACHE_RIGHTS } #endif if (AccessGranted) { ClearFlag( DesiredAccess, GrantedAccess | MAXIMUM_ALLOWED ); if (!CheckOnly) { SetFlag( AccessState->PreviouslyGrantedAccess, GrantedAccess ); // // Remember the case where MAXIMUM_ALLOWED was requested and we // got everything requested from the file. // if (MaximumRequested) { // // Check whether we got DELETE and READ_ATTRIBUTES. Otherwise // we will query the parent. // if (FlagOn( AccessState->PreviouslyGrantedAccess, DELETE )) { MaximumDeleteAcquired = TRUE; } if (FlagOn( AccessState->PreviouslyGrantedAccess, FILE_READ_ATTRIBUTES )) { MaximumReadAttrAcquired = TRUE; } } ClearFlag( AccessState->RemainingDesiredAccess, (GrantedAccess | MAXIMUM_ALLOWED) ); } } else { AccessStatusError = AccessStatus; } // // Check if the access is not granted and if we were given a parent fcb, and // if the desired access was asking for delete or file read attributes. If so // then we need to do some extra work to decide if the caller does get access // based on the parent directories security descriptor. We also do the same // work if MAXIMUM_ALLOWED was requested and we didn't get DELETE or // FILE_READ_ATTRIBUTES. // if ((ParentFcb != NULL) && ((!AccessGranted && FlagOn( DesiredAccess, DELETE | FILE_READ_ATTRIBUTES )) || (MaximumRequested && (!MaximumDeleteAcquired || !MaximumReadAttrAcquired)))) { BOOLEAN DeleteAccessGranted = TRUE; BOOLEAN ReadAttributesAccessGranted = TRUE; ACCESS_MASK DeleteChildGrantedAccess = 0; ACCESS_MASK ListDirectoryGrantedAccess = 0; // // Before we proceed load in the parent security descriptor. // Acquire the parent shared while doing this to protect the // security descriptor. // SeUnlockSubjectContext( &AccessState->SubjectSecurityContext ); NtfsAcquireResourceShared( IrpContext, ParentFcb, TRUE ); SeLockSubjectContext( &AccessState->SubjectSecurityContext ); try { if (ParentFcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, ParentFcb ); } ASSERT( ParentFcb->SharedSecurity != NULL); // // Now if the user is asking for delete access then check if the parent // will granted delete access to the child, and if so then we munge the // desired access // #ifdef NTFS_CACHE_RIGHTS // // Check the cached information only if the effective // RequestorMode is UserMode. // if (EffectiveMode == UserMode) { // // Acquire in any cached knowledge about rights that // this caller is known to have for this security // descriptor. // (VOID)NtfsGetCachedRights( ParentFcb->Vcb, &AccessState->SubjectSecurityContext, ParentFcb->SharedSecurity, &GrantedAccess, NULL, NULL, NULL ); // // Add in the results of the parent directory access. // if (FlagOn( GrantedAccess, FILE_DELETE_CHILD) ) { SetFlag( DeleteChildGrantedAccess, DELETE ); ClearFlag( DesiredAccess, DELETE ); MaximumDeleteAcquired = TRUE; } if (FlagOn( GrantedAccess, FILE_LIST_DIRECTORY) ) { SetFlag( ListDirectoryGrantedAccess, FILE_READ_ATTRIBUTES ); ClearFlag( DesiredAccess, FILE_READ_ATTRIBUTES ); MaximumReadAttrAcquired = TRUE; } } #endif if (FlagOn( DesiredAccess, DELETE ) || (MaximumRequested && !MaximumDeleteAcquired)) { DeleteAccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor, &AccessState->SubjectSecurityContext, TRUE, // Tokens are locked FILE_DELETE_CHILD, 0, &Privileges, IoGetFileObjectGenericMapping(), EffectiveMode, &DeleteChildGrantedAccess, &AccessStatus ); if (Privileges != NULL) { SeFreePrivileges( Privileges ); Privileges = NULL; } if (DeleteAccessGranted) { SetFlag( DeleteChildGrantedAccess, DELETE ); ClearFlag( DeleteChildGrantedAccess, FILE_DELETE_CHILD ); ClearFlag( DesiredAccess, DELETE ); } else { AccessStatusError = AccessStatus; } } // // Do the same test for read attributes and munge the desired access // as appropriate // if (FlagOn(DesiredAccess, FILE_READ_ATTRIBUTES) || (MaximumRequested && !MaximumReadAttrAcquired)) { ReadAttributesAccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor, &AccessState->SubjectSecurityContext, TRUE, // Tokens are locked FILE_LIST_DIRECTORY, 0, &Privileges, IoGetFileObjectGenericMapping(), EffectiveMode, &ListDirectoryGrantedAccess, &AccessStatus ); if (Privileges != NULL) { SeFreePrivileges( Privileges ); Privileges = NULL; } if (ReadAttributesAccessGranted) { SetFlag( ListDirectoryGrantedAccess, FILE_READ_ATTRIBUTES ); ClearFlag( ListDirectoryGrantedAccess, FILE_LIST_DIRECTORY ); ClearFlag( DesiredAccess, FILE_READ_ATTRIBUTES ); } else { AccessStatusError = AccessStatus; } } } finally { NtfsReleaseResource( IrpContext, ParentFcb ); } if (DesiredAccess == 0) { // // If we got either the delete or list directory access then // grant access. // if (ListDirectoryGrantedAccess != 0 || DeleteChildGrantedAccess != 0) { AccessGranted = TRUE; } } else { // // Now the desired access has been munged by removing everything the parent // has granted so now do the check on the child again // AccessGranted = SeAccessCheck( &Fcb->SharedSecurity->SecurityDescriptor, &AccessState->SubjectSecurityContext, TRUE, // Tokens are locked DesiredAccess, 0, &Privileges, IoGetFileObjectGenericMapping(), EffectiveMode, &GrantedAccess, &AccessStatus ); if (Privileges != NULL) { Status = SeAppendPrivileges( AccessState, Privileges ); SeFreePrivileges( Privileges ); Privileges = NULL; } // // Suppose that we asked for MAXIMUM_ALLOWED and no access was allowed // on the file. In that case the call above would fail. It's possible // that we were given DELETE or READ_ATTR permission from the // parent directory. If we have granted any access and the only remaining // desired access is MAXIMUM_ALLOWED then grant this access. // if (!AccessGranted) { AccessStatusError = AccessStatus; if (DesiredAccess == MAXIMUM_ALLOWED && (ListDirectoryGrantedAccess != 0 || DeleteChildGrantedAccess != 0)) { GrantedAccess = 0; AccessGranted = TRUE; } } } // // If we are given access this time then by definition one of the earlier // parent checks had to have succeeded, otherwise we would have failed again // and we can update the access state // if (!CheckOnly && AccessGranted) { SetFlag( AccessState->PreviouslyGrantedAccess, (GrantedAccess | DeleteChildGrantedAccess | ListDirectoryGrantedAccess) ); ClearFlag( AccessState->RemainingDesiredAccess, (GrantedAccess | MAXIMUM_ALLOWED | DeleteChildGrantedAccess | ListDirectoryGrantedAccess) ); } } } // // Now call a routine that will do the proper open audit/alarm work // // **** We need to expand the audit alarm code to deal with // create and traverse alarms. // // // First we take a shortcut and see if we should bother setting up // and making the audit call. // // // NOTE: Calling SeAuditingFileEvents below disables per-user auditing functionality. // To make per-user auditing work again, it is necessary to change the call below to // be SeAuditingFileOrGlobalEvents, which also takes the subject context. // // The reason for calling SeAuditingFileEvents here is because per-user auditing is // not currently exposed to users, and this routine imposes less of a performance // penalty than does calling SeAuditingFileOrGlobalEvents. // if (SeAuditingFileEvents( AccessGranted, &Fcb->SharedSecurity->SecurityDescriptor )) { BOOLEAN Found; PFILE_NAME FileNameAttr; UNICODE_STRING FileRecordName; NtfsInitializeAttributeContext( &Context ); CleanupAttrContext = TRUE; // // Construct the file name. The file name // consists of: // // The device name out of the Vcb + // // The contents of the filename in the File Object + // // The contents of the Related File Object if it // is present and the name in the File Object // does not start with a '\' // // // Obtain the file name. // PartialFileName = &IrpSp->FileObject->FileName; PartialFileNamePresent = (PartialFileName->Length != 0); if (!PartialFileNamePresent && FlagOn(IrpSp->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID) || (IrpSp->FileObject->RelatedFileObject != NULL && IrpSp->FileObject->RelatedFileObject->FsContext2 != NULL && FlagOn(((PCCB) IrpSp->FileObject->RelatedFileObject->FsContext2)->Flags, CCB_FLAG_OPEN_BY_FILE_ID))) { // // If this file is open by id or the relative file object is // then get the first file name out of the file record. // Found = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $FILE_NAME, &Context ); while (Found) { FileNameAttr = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &Context )); if (FileNameAttr->Flags != FILE_NAME_DOS) { FileRecordName.Length = FileNameAttr->FileNameLength * sizeof(WCHAR); FileRecordName.MaximumLength = FileRecordName.Length; FileRecordName.Buffer = FileNameAttr->FileName; PartialFileNamePresent = TRUE; PartialFileName = &FileRecordName; break; } Found = NtfsLookupNextAttributeByCode( IrpContext, Fcb, $FILE_NAME, &Context ); } } // // Obtain the device name. // DeviceObjectName = &Fcb->Vcb->DeviceName; DeviceObjectNameLength = DeviceObjectName->Length; // // Compute how much space we need for the final name string // FullFileNameLength = (ULONG)DeviceObjectNameLength + PartialFileName->Length + sizeof( UNICODE_NULL ) + sizeof((WCHAR)'\\'); if ((FullFileNameLength & 0xffff0000L) != 0) { NtfsRaiseStatus( IrpContext, STATUS_OBJECT_NAME_INVALID, NULL, NULL ); } FullFileName.MaximumLength = DeviceObjectNameLength + PartialFileName->Length + sizeof( UNICODE_NULL ) + sizeof((WCHAR)'\\'); // // If the partial file name starts with a '\', then don't use // whatever may be in the related file name. // if (PartialFileNamePresent && ((WCHAR)(PartialFileName->Buffer[0]) == L'\\' || PartialFileName == &FileRecordName)) { LeadingSlash = TRUE; } else { // // Since PartialFileName either doesn't exist or doesn't // start with a '\', examine the RelatedFileName to see // if it exists. // LeadingSlash = FALSE; if (IrpSp->FileObject->RelatedFileObject != NULL) { RelatedFileName = &IrpSp->FileObject->RelatedFileObject->FileName; } if (RelatedFileNamePresent = ((RelatedFileName != NULL) && (RelatedFileName->Length != 0))) { FullFileName.MaximumLength += RelatedFileName->Length; } } FullFileName.Buffer = NtfsAllocatePool(PagedPool, FullFileName.MaximumLength ); NtfsCleanupAttributeContext( IrpContext, &Context ); CleanupAttrContext = FALSE; RtlCopyUnicodeString( &FullFileName, DeviceObjectName ); // // RelatedFileNamePresent is not initialized if LeadingSlash == TRUE, // but in that case we won't even examine it. // if (!LeadingSlash && RelatedFileNamePresent) { Status = RtlAppendUnicodeStringToString( &FullFileName, RelatedFileName ); ASSERTMSG("RtlAppendUnicodeStringToString of RelatedFileName", NT_SUCCESS( Status )); // // RelatedFileName may simply be '\'. Don't append another // '\' in this case. // if (RelatedFileName->Length != sizeof( WCHAR )) { FullFileName.Buffer[ (FullFileName.Length / sizeof( WCHAR )) ] = L'\\'; FullFileName.Length += sizeof(WCHAR); } } if (PartialFileNamePresent) { Status = RtlAppendUnicodeStringToString( &FullFileName, PartialFileName ); // // This should not fail // ASSERTMSG("RtlAppendUnicodeStringToString of PartialFileName failed", NT_SUCCESS( Status )); } if (PerformDeleteAudit) { SeOpenObjectForDeleteAuditAlarm( &FileString, NULL, &FullFileName, &Fcb->SharedSecurity->SecurityDescriptor, AccessState, FALSE, AccessGranted, EffectiveMode, &AccessState->GenerateOnClose ); } else { SeOpenObjectAuditAlarm( &FileString, NULL, &FullFileName, &Fcb->SharedSecurity->SecurityDescriptor, AccessState, FALSE, AccessGranted, EffectiveMode, &AccessState->GenerateOnClose ); } NtfsFreePool( FullFileName.Buffer ); } } finally { if (CleanupAttrContext) { NtfsCleanupAttributeContext( IrpContext, &Context ); } SeUnlockSubjectContext( &AccessState->SubjectSecurityContext ); } // // If access is not granted then we will raise // if (!AccessGranted) { DebugTrace( -1, Dbg, ("NtfsAccessCheck -> Access Denied\n") ); NtfsRaiseStatus( IrpContext, AccessStatusError, NULL, NULL ); } // // And return to our caller // DebugTrace( -1, Dbg, ("NtfsAccessCheck -> VOID\n") ); return; } NTSTATUS NtfsCheckFileForDelete ( IN PIRP_CONTEXT IrpContext, IN PSCB ParentScb, IN PFCB ThisFcb, IN BOOLEAN FcbExisted, IN PINDEX_ENTRY IndexEntry ) /*++ Routine Description: This routine checks that the caller has permission to delete the target file of a rename or set link operation. Arguments: ParentScb - This is the parent directory for this file. ThisFcb - This is the Fcb for the link being removed. FcbExisted - Indicates if this Fcb was just created. IndexEntry - This is the index entry on the disk for this file. Return Value: NTSTATUS - Indicating whether access was granted or the reason access was denied. --*/ { UNICODE_STRING LastComponentFileName; PFILE_NAME IndexFileName; PLCB ThisLcb; PFCB ParentFcb = ParentScb->Fcb; PSCB NextScb = NULL; BOOLEAN LcbExisted = FALSE; BOOLEAN AccessGranted; ACCESS_MASK GrantedAccess; NTSTATUS Status = STATUS_SUCCESS; BOOLEAN UnlockSubjectContext = FALSE; PPRIVILEGE_SET Privileges = NULL; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsCheckFileForDelete: Entered\n") ); ThisLcb = NULL; IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry ); // // If the unclean count is non-zero, we exit with an error. // if (ThisFcb->CleanupCount != 0) { DebugTrace( 0, Dbg, ("Cleanup count of target is non-zero\n") ); return STATUS_ACCESS_DENIED; } // // We look at the index entry to see if the file is either a directory // or a read-only file. We can't delete this for a target directory open. // if (IsDirectory( &ThisFcb->Info ) || IsReadOnly( &ThisFcb->Info )) { DebugTrace( -1, Dbg, ("NtfsCheckFileForDelete: Read only or directory\n") ); return STATUS_ACCESS_DENIED; } // // We want to scan through all of the Scb for data streams on this file // and look for image sections. We must be able to remove the image section // in order to delete the file. Otherwise we can get the case where an // active image (with no handle) could be deleted and subsequent faults // through the image section will return zeroes. // if (ThisFcb->LinkCount == 1) { BOOLEAN DecrementScb = FALSE; // // We will increment the Scb count to prevent this Scb from going away // if the flush call below generates a close. Use a try-finally to // restore the count. // try { while ((NextScb = NtfsGetNextChildScb( ThisFcb, NextScb )) != NULL) { InterlockedIncrement( &NextScb->CloseCount ); DecrementScb = TRUE; if (NtfsIsTypeCodeUserData( NextScb->AttributeTypeCode ) && !FlagOn( NextScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ) && (NextScb->NonpagedScb->SegmentObject.ImageSectionObject != NULL)) { if (!MmFlushImageSection( &NextScb->NonpagedScb->SegmentObject, MmFlushForDelete )) { Status = STATUS_ACCESS_DENIED; leave; } } InterlockedDecrement( &NextScb->CloseCount ); DecrementScb = FALSE; } } finally { if (DecrementScb) { InterlockedDecrement( &NextScb->CloseCount ); } } if (Status != STATUS_SUCCESS) { return Status; } } // // We need to check if the link to this file has been deleted. We // first check if we definitely know if the link is deleted by // looking at the file name flags and the Fcb flags. // If that result is uncertain, we need to create an Lcb and // check the Lcb flags. // if (FcbExisted) { if (FlagOn( IndexFileName->Flags, FILE_NAME_NTFS | FILE_NAME_DOS )) { if (FlagOn( ThisFcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED )) { DebugTrace( -1, Dbg, ("NtfsCheckFileForDelete: Link is going away\n") ); return STATUS_DELETE_PENDING; } // // This is a Posix link. We need to create the link to test it // for deletion. // } else { LastComponentFileName.MaximumLength = LastComponentFileName.Length = IndexFileName->FileNameLength * sizeof( WCHAR ); LastComponentFileName.Buffer = (PWCHAR) IndexFileName->FileName; ThisLcb = NtfsCreateLcb( IrpContext, ParentScb, ThisFcb, LastComponentFileName, IndexFileName->Flags, &LcbExisted ); // // If no Lcb was returned, there's no way that the Lcb has been // marked for deletion already. // if ((ThisLcb != NULL) && (FlagOn( ThisLcb->LcbState, LCB_STATE_DELETE_ON_CLOSE ))) { DebugTrace( -1, Dbg, ("NtfsCheckFileForDelete: Link is going away\n") ); return STATUS_DELETE_PENDING; } } } // // Finally call the security package to check for delete access. // We check for delete access on the target Fcb. If this succeeds, we // are done. Otherwise we will check for delete child access on the // the parent. Either is sufficient to perform the delete. // // // Check if we need to load the security descriptor for the file // if (ThisFcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, ThisFcb ); } ASSERT( ThisFcb->SharedSecurity != NULL ); #ifdef NTFS_CACHE_RIGHTS // // Get any cached knowledge about rights that all callers are known to // have for this security descriptor. // GrantedAccess = NtfsGetCachedRightsWorld( &ThisFcb->SharedSecurity->CachedRights ); if (FlagOn( GrantedAccess, DELETE )) { return STATUS_SUCCESS; } #endif // // Use a try-finally to facilitate cleanup. // try { // // Lock the user context, do the access check and then unlock the context // SeLockSubjectContext( IrpContext->Union.SubjectContext ); UnlockSubjectContext = TRUE; #ifdef NTFS_CACHE_RIGHTS // // Acquire any cached knowledge about rights that this caller // is known to have for this security descriptor. // (VOID)NtfsGetCachedRights( ThisFcb->Vcb, IrpContext->Union.SubjectContext, ThisFcb->SharedSecurity, &GrantedAccess, NULL, NULL, NULL ); if (FlagOn( GrantedAccess, DELETE )) { AccessGranted = TRUE; Status = STATUS_SUCCESS; } else { #endif AccessGranted = SeAccessCheck( &ThisFcb->SharedSecurity->SecurityDescriptor, IrpContext->Union.SubjectContext, TRUE, // Tokens are locked DELETE, 0, &Privileges, IoGetFileObjectGenericMapping(), UserMode, &GrantedAccess, &Status ); #ifdef NTFS_CACHE_RIGHTS } #endif // // Check if the access is not granted and if we were given a parent fcb, and // if the desired access was asking for delete or file read attributes. If so // then we need to do some extra work to decide if the caller does get access // based on the parent directories security descriptor // if (!AccessGranted) { // // Before we proceed load in the parent security descriptor // if (ParentFcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, ParentFcb ); } ASSERT( ParentFcb->SharedSecurity != NULL); // // Now if the user is asking for delete access then check if the parent // will granted delete access to the child, and if so then we munge the // desired access // #ifdef NTFS_CACHE_RIGHTS // // Add in any cached knowledge about rights that this caller // is known to have for this security descriptor. // (VOID)NtfsGetCachedRights( ParentFcb->Vcb, IrpContext->Union.SubjectContext, ParentFcb->SharedSecurity, &GrantedAccess, NULL, NULL, NULL ); if (FlagOn( GrantedAccess, FILE_DELETE_CHILD )) { AccessGranted = TRUE; Status = STATUS_SUCCESS; } else { #endif AccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor, IrpContext->Union.SubjectContext, TRUE, // Tokens are locked FILE_DELETE_CHILD, 0, &Privileges, IoGetFileObjectGenericMapping(), UserMode, &GrantedAccess, &Status ); #ifdef NTFS_CACHE_RIGHTS } #endif } } finally { DebugUnwind( NtfsCheckFileForDelete ); if (UnlockSubjectContext) { SeUnlockSubjectContext( IrpContext->Union.SubjectContext ); } DebugTrace( -1, Dbg, ("NtfsCheckFileForDelete: Exit\n") ); } return Status; } VOID NtfsCheckIndexForAddOrDelete ( IN PIRP_CONTEXT IrpContext, IN PFCB ParentFcb, IN ACCESS_MASK DesiredAccess, IN ULONG CreatePrivileges ) /*++ Routine Description: This routine checks if a caller has permission to remove or add a link within a directory. Arguments: ParentFcb - This is the parent directory for the add or delete operation. DesiredAccess - Indicates the type of operation. We could be adding or removing and entry in the index. CreatePriveleges - Backup and restore priveleges captured at create time. Return Value: None - This routine raises on error. --*/ { BOOLEAN AccessGranted; ACCESS_MASK GrantedAccess; NTSTATUS Status; BOOLEAN UnlockSubjectContext = FALSE; PPRIVILEGE_SET Privileges = NULL; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsCheckIndexForAddOrDelete: Entered\n") ); // // Use a try-finally to facilitate cleanup. // try { // // If we have restore privelege then we can add either a file or directory. // if (FlagOn( CreatePrivileges, TOKEN_HAS_RESTORE_PRIVILEGE )) { ClearFlag( DesiredAccess, DELETE | FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE ); } // // Do a security check if there is more being asked for. // if (DesiredAccess != 0) { // // Finally call the security package to check for delete access. // We check for delete access on the target Fcb. If this succeeds, we // are done. Otherwise we will check for delete child access on the // the parent. Either is sufficient to perform the delete. // // // Check if we need to load the security descriptor for the file // if (ParentFcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, ParentFcb ); } ASSERT( ParentFcb->SharedSecurity != NULL ); #ifdef NTFS_CACHE_RIGHTS // // Get any cached knowledge about rights that all callers are known to // have for this security descriptor. // GrantedAccess = NtfsGetCachedRightsWorld( &ParentFcb->SharedSecurity->CachedRights ); ClearFlag( DesiredAccess, GrantedAccess ); } if (DesiredAccess != 0) { // // Finally call the security package to check for delete access. // We check for delete access on the target Fcb. If this succeeds, we // are done. Otherwise we will check for delete child access on the // the parent. Either is sufficient to perform the delete. // #endif // // Capture and lock the user context, do the access check and then unlock the context // SeLockSubjectContext( IrpContext->Union.SubjectContext ); UnlockSubjectContext = TRUE; #ifdef NTFS_CACHE_RIGHTS // // Acquire any cached knowledge about rights that this caller // is known to have for this security descriptor. // (VOID)NtfsGetCachedRights( ParentFcb->Vcb, IrpContext->Union.SubjectContext, ParentFcb->SharedSecurity, &GrantedAccess, NULL, NULL, NULL ); if (FlagOn( GrantedAccess, DELETE )) { AccessGranted = TRUE; Status = STATUS_SUCCESS; } else { #endif AccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor, IrpContext->Union.SubjectContext, TRUE, // Tokens are locked DesiredAccess, 0, &Privileges, IoGetFileObjectGenericMapping(), UserMode, &GrantedAccess, &Status ); // // If access is not granted then we will raise // if (!AccessGranted) { DebugTrace( 0, Dbg, ("Access Denied\n") ); NtfsRaiseStatus( IrpContext, Status, NULL, NULL ); } #ifdef NTFS_CACHE_RIGHTS } #endif } } finally { DebugUnwind( NtfsCheckIndexForAddOrDelete ); if (UnlockSubjectContext) { SeUnlockSubjectContext( IrpContext->Union.SubjectContext ); } DebugTrace( -1, Dbg, ("NtfsCheckIndexForAddOrDelete: Exit\n") ); } return; } PSHARED_SECURITY GetSharedSecurityFromDescriptorUnsafe ( IN PIRP_CONTEXT IrpContext, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN ULONG SecurityDescriptorLength, IN BOOLEAN RaiseIfInvalid ) /*++ Routine Description: This routine is called to create or find a shared security structure given an security descriptor. We check the parent if present to determine if we have a matching security descriptor and reference the existing one if so. This routine must be called while holding the Vcb so we can safely access the parent structure. Arguments: IrpContext - context of call SecurityId - Id (if known) of security descriptor. SecurityDescriptor - Security Descriptor for this file. SecurityDescriptorLength - Length of security descriptor for this file RaiseIfInvalid - raise if the sd is invalid rather than supplying a default used during a create as opposed to an open Return Value: PSHARED_SECURITY if found, NULL otherwise. --*/ { ULONG Hash = 0; PSHARED_SECURITY SharedSecurity; PAGED_CODE(); // // Make sure the security descriptor we just read in is valid // if ((SecurityDescriptorLength == 0) || !SeValidSecurityDescriptor( SecurityDescriptorLength, SecurityDescriptor )) { if (RaiseIfInvalid) { NtfsRaiseStatus( IrpContext, STATUS_INVALID_SECURITY_DESCR, NULL, NULL ); } SecurityDescriptor = NtfsData.DefaultDescriptor; SecurityDescriptorLength = NtfsData.DefaultDescriptorLength; if (!SeValidSecurityDescriptor( SecurityDescriptorLength, SecurityDescriptor )) { NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL ); } } // // Hash security descriptor. This hash must be position independent to // allow for multiple instances of the same descriptor. It is assumed // that the bits within the security descriptor are all position // independent, i.e, no pointers, all offsets. // // For speed in the hash, we consider the security descriptor as an array // of ULONGs. The fragment at the end that is ignored should not affect // the collision nature of this hash. // { PULONG Rover = (PULONG)SecurityDescriptor; ULONG Count = SecurityDescriptorLength / 4; while (Count--) { Hash = ((Hash << 3) | (Hash >> (32-3))) + *Rover++; } } DebugTrace( 0, DbgAcl, ("Hash is %08x\n", Hash) ); // // try to find it by hash // SharedSecurity = FindCachedSharedSecurityByHashUnsafe( IrpContext->Vcb, SecurityDescriptor, SecurityDescriptorLength, Hash ); // // If we can't find an existing descriptor allocate new pool and copy // security descriptor into it. // if (SharedSecurity == NULL) { SharedSecurity = NtfsAllocatePool( PagedPool, FIELD_OFFSET( SHARED_SECURITY, SecurityDescriptor ) + SecurityDescriptorLength ); SharedSecurity->ReferenceCount = 0; // // Initialize security index data in shared security // // // Set the security id in the shared structure. If it is not // invalid, also cache this shared security structure // SharedSecurity->Header.HashKey.SecurityId = SECURITY_ID_INVALID; SharedSecurity->Header.HashKey.Hash = Hash; SetSharedSecurityLength(SharedSecurity, SecurityDescriptorLength); SharedSecurity->Header.Offset = (ULONGLONG) 0xFFFFFFFFFFFFFFFFi64; RtlCopyMemory( &SharedSecurity->SecurityDescriptor, SecurityDescriptor, SecurityDescriptorLength ); #ifdef NTFS_CACHE_RIGHTS // // Initialize the cached rights. // RtlZeroMemory( &SharedSecurity->CachedRights, sizeof( CACHED_ACCESS_RIGHTS )); #endif } DebugTrace( 0, DbgAcl, ("GetSharedSecurityFromDescriptorUnsafe found %08x with Id %08x\n", SharedSecurity, SharedSecurity->Header.HashKey.SecurityId )); return SharedSecurity; } VOID NtfsSetFcbSecurityFromDescriptor ( IN PIRP_CONTEXT IrpContext, IN OUT PFCB Fcb, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN ULONG SecurityDescriptorLength, IN BOOLEAN RaiseIfInvalid ) /*++ Routine Description: This routine is called to fill in the shared security structure in an Fcb. We check the parent if present to determine if we have a matching security descriptor and reference the existing one if so. This routine must be called while holding the Vcb so we can safely access the parent structure. Arguments: IrpContext - context of call Fcb - Supplies the fcb for the file being operated on SecurityDescriptor - Security Descriptor for this file. SecurityDescriptorLength - Length of security descriptor for this file Return Value: None. --*/ { PSHARED_SECURITY SharedSecurity; PAGED_CODE( ); NtfsAcquireFcbSecurity( Fcb->Vcb ); try { SharedSecurity = GetSharedSecurityFromDescriptorUnsafe( IrpContext, SecurityDescriptor, SecurityDescriptorLength, RaiseIfInvalid ); SharedSecurity->ReferenceCount += 1; DebugTrace( +1, DbgAcl, ("NtfsSetFcbSecurityFromDescriptor bumping refcount %08x\n", SharedSecurity )); ASSERT( Fcb->SharedSecurity == NULL ); Fcb->SharedSecurity = SharedSecurity; AddCachedSharedSecurityUnsafe( IrpContext->Vcb, SharedSecurity ); } finally { NtfsReleaseFcbSecurity( Fcb->Vcb ); } return; } BOOLEAN NtfsNotifyTraverseCheck ( IN PCCB Ccb, IN PFCB Fcb, IN PSECURITY_SUBJECT_CONTEXT SubjectContext ) /*++ Routine Description: This routine is the callback routine provided to the dir notify package to check that a caller who is watching a tree has traverse access to the directory which has the change. This routine is only called when traverse access checking was turned on for the open used to perform the watch. Arguments: Ccb - This is the Ccb associated with the directory which is being watched. Fcb - This is the Fcb for the directory which contains the file being modified. We want to walk up the tree from this point and check that the caller has traverse access across that directory. If not specified then there is no work to do. SubjectContext - This is the subject context captured at the time the dir notify call was made. Return Value: BOOLEAN - TRUE if the caller has traverse access to the file which was changed. FALSE otherwise. --*/ { TOP_LEVEL_CONTEXT TopLevelContext; PTOP_LEVEL_CONTEXT ThreadTopLevelContext; PFCB TopFcb; IRP_CONTEXT LocalIrpContext; IRP LocalIrp; PIRP_CONTEXT IrpContext; BOOLEAN AccessGranted; ACCESS_MASK GrantedAccess; NTSTATUS Status = STATUS_SUCCESS; #ifdef NTFS_CACHE_RIGHTS NTSTATUS TokenInfoStatus = STATUS_UNSUCCESSFUL; #endif PPRIVILEGE_SET Privileges = NULL; PAGED_CODE(); // // If we have no Fcb then we can return immediately. // if (Fcb == NULL) { return TRUE; } IrpContext = &LocalIrpContext; NtfsInitializeIrpContext( NULL, TRUE, &IrpContext ); IrpContext->OriginatingIrp = &LocalIrp; IrpContext->Vcb = Fcb->Vcb; // // Make sure we don't get any pop-ups // ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, TRUE, FALSE ); ASSERT( ThreadTopLevelContext == &TopLevelContext ); NtfsUpdateIrpContextWithTopLevel( IrpContext, &TopLevelContext ); TopFcb = Ccb->Lcb->Fcb; // // Use a try-except to catch all of the errors. // try { // // Always lock the subject context. // SeLockSubjectContext( SubjectContext ); // // Use a try-finally to perform local cleanup. // try { // // We look while walking up the tree. // do { #ifdef NTFS_CACHE_RIGHTS LUID ModifiedId; LUID TokenId; #endif PLCB ParentLcb; // // Since this is a directory it can have only one parent. So // we can use any Lcb to walk upwards. // ParentLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink, LCB, FcbLinks ); Fcb = ParentLcb->Scb->Fcb; // // Check if we need to load the security descriptor for the file // if (Fcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, Fcb ); } #ifdef NTFS_CACHE_RIGHTS // // Acquire any cached knowledge about rights that this caller // is known to have for this security descriptor. // // Note that we can trust that TokenId and ModifiedId won't // change inside this block of code because we have locked // the subject context above. // if (TokenInfoStatus != STATUS_SUCCESS) { // // We have not previously acquired the Id information. // TokenInfoStatus = NtfsGetCachedRights( Fcb->Vcb, SubjectContext, Fcb->SharedSecurity, &GrantedAccess, NULL, &TokenId, &ModifiedId ); } else { NtfsGetCachedRightsById( Fcb->Vcb, &TokenId, &ModifiedId, SubjectContext, Fcb->SharedSecurity, NULL, &GrantedAccess ); } if (FlagOn( GrantedAccess, FILE_TRAVERSE )) { AccessGranted = TRUE; } else { #endif AccessGranted = SeAccessCheck( &Fcb->SharedSecurity->SecurityDescriptor, SubjectContext, TRUE, // Tokens are locked FILE_TRAVERSE, 0, &Privileges, IoGetFileObjectGenericMapping(), UserMode, &GrantedAccess, &Status ); #ifdef NTFS_CACHE_RIGHTS } #endif } while (AccessGranted && (Fcb != TopFcb)); } finally { SeUnlockSubjectContext( SubjectContext ); } } except (NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) { NOTHING; } NtfsCleanupIrpContext( IrpContext, TRUE ); ASSERT( IoGetTopLevelIrp() != (PIRP) &TopLevelContext ); return AccessGranted; } VOID NtfsInitializeSecurity ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PFCB Fcb ) /*++ Routine Description: This routine is called to initialize the security indexes and descriptor stream. Arguments: IrpContext - context of call Vcb - Supplies the volume being initialized Fcb - Supplies the file containing the seurity indexes and descriptor stream. Return Value: None. --*/ { UNICODE_STRING SecurityIdIndexName = CONSTANT_UNICODE_STRING( L"$SII" ); UNICODE_STRING SecurityDescriptorHashIndexName = CONSTANT_UNICODE_STRING( L"$SDH" ); UNICODE_STRING SecurityDescriptorStreamName = CONSTANT_UNICODE_STRING( L"$SDS" ); MAP_HANDLE Map; NTSTATUS Status; PAGED_CODE( ); // // Open/Create the security descriptor stream // NtOfsCreateAttribute( IrpContext, Fcb, SecurityDescriptorStreamName, CREATE_OR_OPEN, TRUE, &Vcb->SecurityDescriptorStream ); NtfsAcquireSharedScb( IrpContext, Vcb->SecurityDescriptorStream ); // // Load the run information for the Security data stream. // Note this call must be done after the stream is nonresident. // if (!FlagOn( Vcb->SecurityDescriptorStream->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) { NtfsPreloadAllocation( IrpContext, Vcb->SecurityDescriptorStream, 0, MAXLONGLONG ); } // // Open the Security descriptor indexes and storage. // NtOfsCreateIndex( IrpContext, Fcb, SecurityIdIndexName, CREATE_OR_OPEN, 0, COLLATION_NTOFS_ULONG, NtOfsCollateUlong, NULL, &Vcb->SecurityIdIndex ); NtOfsCreateIndex( IrpContext, Fcb, SecurityDescriptorHashIndexName, CREATE_OR_OPEN, 0, COLLATION_NTOFS_SECURITY_HASH, NtOfsCollateSecurityHash, NULL, &Vcb->SecurityDescriptorHashIndex ); // // Retrieve the next security Id to allocate // try { SECURITY_ID LastSecurityId = 0xFFFFFFFF; INDEX_KEY LastKey; INDEX_ROW LastRow; LastKey.KeyLength = sizeof( SECURITY_ID ); LastKey.Key = &LastSecurityId; Map.Bcb = NULL; Status = NtOfsFindLastRecord( IrpContext, Vcb->SecurityIdIndex, &LastKey, &LastRow, &Map ); // // If we've found the last key, set the next Id to allocate to be // one greater than this last key. // if (Status == STATUS_SUCCESS) { ASSERT( LastRow.KeyPart.KeyLength == sizeof( SECURITY_ID ) ); if (LastRow.KeyPart.KeyLength != sizeof( SECURITY_ID )) { NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL ); } DebugTrace( 0, DbgAcl, ("Found last security Id in index\n") ); Vcb->NextSecurityId = *(SECURITY_ID *)LastRow.KeyPart.Key + 1; // // If the index is empty, then set the next Id to be the beginning of the // user range. // } else if (Status == STATUS_NO_MATCH) { DebugTrace( 0, DbgAcl, ("Security Id index is empty\n") ); Vcb->NextSecurityId = SECURITY_ID_FIRST; } else { NtfsRaiseStatus( IrpContext, Status, NULL, NULL ); } DebugTrace( 0, DbgAcl, ("NextSecurityId is %x\n", Vcb->NextSecurityId) ); } finally { NtOfsReleaseMap( IrpContext, &Map ); } } PSHARED_SECURITY NtfsCacheSharedSecurityBySecurityId ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN SECURITY_ID SecurityId ) /*++ Routine Description: This routine maps looks up a shared security structure given the security Id by looking in the per-Vcb cache or loads it if not present. Arguments: IrpContext - Context of call Vcb - Volume where security Id is cached SecurityId - security Id for descriptor that is being retrieved Return Value: Referenced PSHARED_SECURITY of found descriptor. --*/ { PSHARED_SECURITY *Bucket; PSHARED_SECURITY SharedSecurity; PBCB Bcb; PSECURITY_DESCRIPTOR_HEADER SecurityDescriptorHeader; PAGED_CODE( ); NtfsAcquireFcbSecurity( Vcb ); // // Probe the cache by Id // Bucket = Vcb->SecurityCacheById[SecurityId % VCB_SECURITY_CACHE_BY_ID_SIZE]; // // We get a match under the following conditions. // // - There is a corresponding entry in the SecurityID array // - This entry points to an entry in the SecurityHash array // - The entry in the SecurityHash array has the correct SecurityID // if ((Bucket != NULL) && ((SharedSecurity = *Bucket) != NULL) && (SharedSecurity->Header.HashKey.SecurityId == SecurityId)) { DebugTrace( 0, DbgAcl, ("Found cached security descriptor %x %x\n", SharedSecurity, SharedSecurity->Header.HashKey.SecurityId) ); DebugTrace( 0, DbgAcl, ("NtfsCacheSharedSecurityBySecurityId bumping refcount %08x\n", SharedSecurity )); // // We found the correct shared security. Make sure it cannot go // away on us. // SharedSecurity->ReferenceCount += 1; NtfsReleaseFcbSecurity( Vcb ); return SharedSecurity; } // // If we get here we didn't find a matching descriptor. Throw away // the incorrect security descriptor we may have found through the // SecurityID array. // SharedSecurity = NULL; NtfsReleaseFcbSecurity( Vcb ); // // If we don't have a security index, then return the default security descriptor. // This should only happen in cases of corrupted volumes or security indices. // if (Vcb->SecurityDescriptorStream == NULL) { DebugTrace( 0, 0, ("No security index present in Vcb, using default descriptor\n") ); return NULL; } // // We don't have the descriptor in the cache and have to load it from disk. // Bcb = NULL; DebugTrace( 0, DbgAcl, ("Looking up security descriptor %x\n", SecurityId) ); // // Lock down the security stream // NtfsAcquireSharedScb( IrpContext, Vcb->SecurityDescriptorStream ); // // Reacquire the security mutex // NtfsAcquireFcbSecurity( Vcb ); try { // // Consult the Vcb index to map to the security descriptor // if (!MapSecurityIdToSecurityDescriptorHeaderUnsafe( IrpContext, Vcb, SecurityId, &SecurityDescriptorHeader, &Bcb )) { // // We couldn't find the Id. We generate a security descriptor from // the default one. // leave; } DebugTrace( 0, DbgAcl, ("Found it at %16I64X\n", SecurityDescriptorHeader->Offset) ); // // Look up the security descriptor by hash (just in case) // SharedSecurity = FindCachedSharedSecurityByHashUnsafe( Vcb, (PSECURITY_DESCRIPTOR) ( SecurityDescriptorHeader + 1 ), GETSECURITYDESCRIPTORLENGTH( SecurityDescriptorHeader ), SecurityDescriptorHeader->HashKey.Hash ); // // If not found // if (SharedSecurity == NULL) { DebugTrace( 0, DbgAcl, ("Not in hash table, creating new SHARED SECURITY\n") ); SharedSecurity = NtfsAllocatePool( PagedPool, FIELD_OFFSET( SHARED_SECURITY, Header ) + SecurityDescriptorHeader->Length ); SharedSecurity->ReferenceCount = 0; RtlCopyMemory( &SharedSecurity->Header, SecurityDescriptorHeader, SecurityDescriptorHeader->Length ); #ifdef NTFS_CACHE_RIGHTS // // Initialize the cached rights. // RtlZeroMemory( &SharedSecurity->CachedRights, sizeof( CACHED_ACCESS_RIGHTS )); #endif } else { DebugTrace( 0, DbgAcl, ("Found in hash table %x, promoting header\n", SharedSecurity) ); // // We found the descriptor by hash. Perform some consistency checks // #if DBG if (SharedSecurity->Header.HashKey.SecurityId != SECURITY_ID_INVALID && SharedSecurity->Header.HashKey.SecurityId != SecurityId ) DebugTrace( 0, 0, ("Duplicate hash entry found %x %x\n", SecurityId, SharedSecurity->Header.HashKey.SecurityId )); #endif SharedSecurity->Header = *SecurityDescriptorHeader; } // // reference the security descriptor // SharedSecurity->ReferenceCount += 1; // // Regardless of whether we found it by hash (since the earlier Id probe missed) // or created it anew. Let's put it back into the cache // AddCachedSharedSecurityUnsafe( Vcb, SharedSecurity ); } finally { NtfsUnpinBcb( IrpContext, &Bcb ); NtfsReleaseScb( IrpContext, Vcb->SecurityDescriptorStream ); // // Release access to security cache // NtfsReleaseFcbSecurity( Vcb ); } // // if we did not generate a shared security, then build one from // the default security descriptor // if (SharedSecurity == NULL) { DebugTrace( 0, 0, ("Security Id %x not found, using default\n", SecurityId) ); SharedSecurity = NtfsCacheSharedSecurityByDescriptor( IrpContext, NtfsData.DefaultDescriptor, NtfsData.DefaultDescriptorLength, FALSE ); } return SharedSecurity; } // // Local Support routine // PSHARED_SECURITY FindCachedSharedSecurityByHashUnsafe ( IN PVCB Vcb, IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN ULONG SecurityDescriptorLength, IN ULONG Hash ) /*++ Routine Description: This routine maps looks up a shared security structure given the Hash by looking in the per-Vcb cache. This routine assumes exclusive access to the security cache. Arguments: Vcb - Volume where security Id is cached SecurityDescriptor - Security descriptor being retrieved SecurityDescriptorLength - length of descriptor. Hash - Hash for descriptor that is being retrieved Return Value: PSHARED_SECURITY of found shared descriptor. Otherwise, NULL is returned. --*/ { PSHARED_SECURITY SharedSecurity; PAGED_CODE( ); // // Hash the hash into the per-volume table SharedSecurity = Vcb->SecurityCacheByHash[Hash % VCB_SECURITY_CACHE_BY_HASH_SIZE]; // // If there is no shared descriptor there, then no match // if (SharedSecurity == NULL) { return NULL; } // // if the hash doesn't match then no descriptor found // if (SharedSecurity->Header.HashKey.Hash != Hash) { return NULL; } // // If the lengths don't match then no descriptor found // if (GetSharedSecurityLength( SharedSecurity ) != SecurityDescriptorLength) { return NULL; } // // If the security descriptor bits don't compare then no match // if (!RtlEqualMemory( SharedSecurity->SecurityDescriptor, SecurityDescriptor, SecurityDescriptorLength) ) { return NULL; } // // The shared security was found // return SharedSecurity; } // // Local Support routine // VOID AddCachedSharedSecurityUnsafe ( IN PVCB Vcb, PSHARED_SECURITY SharedSecurity ) /*++ Routine Description: This routine adds shared security to the Vcb Cache. This routine assumes exclusive access to the security cache. The shared security being added may have a ref count of one and may already be in the table. Arguments: Vcb - Volume where security Id is cached SharedSecurity - descriptor to be added to the cache Return Value: None. --*/ { PSHARED_SECURITY *Bucket; PSHARED_SECURITY Old; PAGED_CODE( ); // // Is there an item already in the hash bucket? // Bucket = &Vcb->SecurityCacheByHash[SharedSecurity->Header.HashKey.Hash % VCB_SECURITY_CACHE_BY_HASH_SIZE]; Old = *Bucket; // // Place it into the bucket and reference it // *Bucket = SharedSecurity; SharedSecurity->ReferenceCount += 1; DebugTrace( 0, DbgAcl, ("AddCachedSharedSecurityUnsafe bumping refcount %08x\n", SharedSecurity )); // // Set up hash to point to bucket // Vcb->SecurityCacheById[SharedSecurity->Header.HashKey.SecurityId % VCB_SECURITY_CACHE_BY_ID_SIZE] = Bucket; // // Handle removing the old value from the bucket. We do this after advancing // the ReferenceCount above in the case where the item is already in the bucket. // if (Old != NULL) { // // Remove and dereference the item in the bucket // RemoveReferenceSharedSecurityUnsafe( &Old ); } return; } VOID NtOfsPurgeSecurityCache ( IN PVCB Vcb ) /*++ Routine Description: This routine removes all shared security from the per-Vcb cache. Arguments: Vcb - Volume where descriptors are cached Return Value: None. --*/ { ULONG i; PAGED_CODE( ); // // Serialize access to the security cache // NtfsAcquireFcbSecurity( Vcb ); // // Walk through the cache looking for cached security // for (i = 0; i < VCB_SECURITY_CACHE_BY_ID_SIZE; i++) { if (Vcb->SecurityCacheByHash[i] != NULL) { // // Remove the reference to the security // PSHARED_SECURITY SharedSecurity = Vcb->SecurityCacheByHash[i]; Vcb->SecurityCacheByHash[i] = NULL; RemoveReferenceSharedSecurityUnsafe( &SharedSecurity ); } } // // Release access to the cache // NtfsReleaseFcbSecurity( Vcb ); } // // Local Support routine // BOOLEAN MapSecurityIdToSecurityDescriptorHeaderUnsafe ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN SECURITY_ID SecurityId, OUT PSECURITY_DESCRIPTOR_HEADER *SecurityDescriptorHeader, OUT PBCB *Bcb ) /*++ Routine Description: This routine maps from a security Id to the descriptor bits stored in the security descriptor stream using the security Id index. Arguments: IrpContext - Context of the call Vcb - Volume where descriptor is stored SecurityId - security Id for descriptor that is being retrieved SecurityDescriptorHeader - returned security descriptor pointer Bcb - returned mapping control structure Return Value: True if the descriptor header was successfully mapped. --*/ { SECURITY_DESCRIPTOR_HEADER Header; NTSTATUS Status; MAP_HANDLE Map; INDEX_ROW Row; INDEX_KEY Key; PSECURITY_DESCRIPTOR SecurityDescriptor; PAGED_CODE( ); DebugTrace( 0, DbgAcl, ("Mapping security ID %08x\n", SecurityId) ); // // Lookup descriptor stream position information. // The format of the key is simply the ULONG SecurityId // Key.KeyLength = sizeof( SecurityId ); Key.Key = &SecurityId; Status = NtOfsFindRecord( IrpContext, Vcb->SecurityIdIndex, &Key, &Row, &Map, NULL ); DebugTrace( 0, DbgAcl, ("Security Id lookup status = %08x\n", Status) ); // // If the security Id is not found, we let the called decide if the volume // needs fixing or whether a default descriptor should be used. // if (Status == STATUS_NO_MATCH) { return FALSE; } // // Save security descriptor offset and length information // Header = *(PSECURITY_DESCRIPTOR_HEADER)Row.DataPart.Data; ASSERT( Header.HashKey.SecurityId == SecurityId ); // // Release mapping information // NtOfsReleaseMap( IrpContext, &Map ); // // Make sure that the data is the correct size. This is a true failure case // where we must fix the disk up. We can just return false because caller // will then use a default sd and chkdsk will replace with the same default // when it next verifies the disk // ASSERT( Row.DataPart.DataLength == sizeof( SECURITY_DESCRIPTOR_HEADER ) ); if (Row.DataPart.DataLength != sizeof( SECURITY_DESCRIPTOR_HEADER )) { DebugTrace( 0, DbgAcl, ("SecurityId data doesn't have the correct length\n") ); return FALSE; } // // Don't try to map clearly invalid sections of the sds stream // if (Header.Offset > (ULONGLONG)(Vcb->SecurityDescriptorStream->Header.FileSize.QuadPart) || Header.Offset + Header.Length > (ULONGLONG)(Vcb->SecurityDescriptorStream->Header.FileSize.QuadPart)) { DebugTrace( 0, DbgAcl, ("SecurityId data doesn't have a correct position\n") ); return FALSE; } // // Map security descriptor // DebugTrace( 0, DbgAcl, ("Mapping security descriptor stream at %I64x, len %x\n", Header.Offset, Header.Length) ); NtfsMapStream( IrpContext, Vcb->SecurityDescriptorStream, Header.Offset, Header.Length, Bcb, SecurityDescriptorHeader ); // // Sanity check the found descriptor // if (RtlCompareMemory( &Header, *SecurityDescriptorHeader, sizeof( Header )) != sizeof( Header )) { DebugTrace( 0, DbgAcl, ("Index data does not match stream header\n") ); return FALSE; } // // Now actually verify the descriptor is valid. If length is too small (even 0) // SeValidSecurityDescriptor will safely return false so we don't need to test this // before calling // SecurityDescriptor = (PSECURITY_DESCRIPTOR) Add2Ptr( (*SecurityDescriptorHeader), sizeof( SECURITY_DESCRIPTOR_HEADER ) ); if (!SeValidSecurityDescriptor( GETSECURITYDESCRIPTORLENGTH( *SecurityDescriptorHeader ), SecurityDescriptor )) { DebugTrace( 0, DbgAcl, ("SecurityId data is not valid\n") ); return FALSE; } #if DBG { ULONG SecurityDescLength; SecurityDescLength = RtlLengthSecurityDescriptor( SecurityDescriptor ); ASSERT( SecurityDescLength == GETSECURITYDESCRIPTORLENGTH( *SecurityDescriptorHeader ) ); } #endif return TRUE; } VOID NtfsLoadSecurityDescriptor ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb ) /*++ Routine Description: This routine loads the shared security descriptor into the fcb for the file from disk using either the SecurityId or the $Security_Descriptor Arguments: Fcb - Supplies the fcb for the file being operated on Return Value: None. --*/ { PAGED_CODE(); ASSERTMSG("Must only be called with a null value here", Fcb->SharedSecurity == NULL); DebugTrace( +1, DbgAcl, ("NtfsLoadSecurityDescriptor...\n") ); // // If the file has a valid SecurityId then retrieve the security descriptor // from the security descriptor index // if ((Fcb->SecurityId != SECURITY_ID_INVALID) && (Fcb->Vcb->SecurityDescriptorStream != NULL)) { ASSERT( Fcb->SharedSecurity == NULL ); Fcb->SharedSecurity = NtfsCacheSharedSecurityBySecurityId( IrpContext, Fcb->Vcb, Fcb->SecurityId ); ASSERT( Fcb->SharedSecurity != NULL ); } else { PBCB Bcb = NULL; PSECURITY_DESCRIPTOR SecurityDescriptor; ULONG SecurityDescriptorLength; ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext; PATTRIBUTE_RECORD_HEADER Attribute; try { // // Read in the security descriptor attribute, and if it is not present // then there then the file is not protected. In that case we will // use the default descriptor. // NtfsInitializeAttributeContext( &AttributeContext ); if (!NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $SECURITY_DESCRIPTOR, &AttributeContext )) { DebugTrace( 0, DbgAcl, ("Security Descriptor attribute does not exist\n") ); SecurityDescriptor = NtfsData.DefaultDescriptor; SecurityDescriptorLength = NtfsData.DefaultDescriptorLength; } else { // // There must be a security descriptor with a non-zero length; only // applies for non-resident descriptors with valid data length. // Attribute = NtfsFoundAttribute( &AttributeContext ); if (NtfsIsAttributeResident( Attribute ) ? (Attribute->Form.Resident.ValueLength == 0) : (Attribute->Form.Nonresident.ValidDataLength == 0)) { SecurityDescriptor = NtfsData.DefaultDescriptor; SecurityDescriptorLength = NtfsData.DefaultDescriptorLength; } else { NtfsMapAttributeValue( IrpContext, Fcb, (PVOID *)&SecurityDescriptor, &SecurityDescriptorLength, &Bcb, &AttributeContext ); } } NtfsSetFcbSecurityFromDescriptor( IrpContext, Fcb, SecurityDescriptor, SecurityDescriptorLength, FALSE ); } finally { DebugUnwind( NtfsLoadSecurityDescriptor ); // // Cleanup our attribute enumeration context and the Bcb // NtfsCleanupAttributeContext( IrpContext, &AttributeContext ); NtfsUnpinBcb( IrpContext, &Bcb ); } } // // And return to our caller // DebugTrace( -1, DbgAcl, ("NtfsLoadSecurityDescriptor -> VOID\n") ); return; } // // Local Support routine // NTSTATUS NtOfsMatchSecurityHash ( IN PINDEX_ROW IndexRow, IN OUT PVOID MatchData ) /*++ Routine Description: Test whether an index row is worthy of returning based on its contents as a row in the SecurityDescriptorHashIndex. Arguments: IndexRow - row that is being tested MatchData - a PVOID that is the hash function we look for. Returns: STATUS_SUCCESS if the IndexRow matches STATUS_NO_MATCH if the IndexRow does not match, but the enumeration should continue STATUS_NO_MORE_MATCHES if the IndexRow does not match, and the enumeration should terminate --*/ { ASSERT(IndexRow->KeyPart.KeyLength == sizeof( SECURITY_HASH_KEY ) ); PAGED_CODE( ); if (((PSECURITY_HASH_KEY)IndexRow->KeyPart.Key)->Hash == (ULONG)((ULONG_PTR) MatchData)) { return STATUS_SUCCESS; } else { return STATUS_NO_MORE_MATCHES; } } // // Local Support routine // VOID NtOfsLookupSecurityDescriptorInIndex ( PIRP_CONTEXT IrpContext, IN OUT PSHARED_SECURITY SharedSecurity ) /*++ Routine Description: Look up the security descriptor in the index. If found, return the security ID. Arguments: IrpContext - context of the call SharedSecurity - shared security for a file Return Value: None. --*/ { PAGED_CODE( ); DebugTrace( +1, DbgAcl, ("NtOfsLookupSecurityDescriptorInIndex...\n") ); // // For each matching hash record in the index, see if the actual security // security descriptor matches. // { INDEX_KEY IndexKey; INDEX_ROW FoundRow; PSECURITY_DESCRIPTOR_HEADER Header; UCHAR HashDescriptorHeader[2 * (sizeof( SECURITY_DESCRIPTOR_HEADER ) + sizeof( ULONG ))]; PINDEX_KEY Key = &IndexKey; PREAD_CONTEXT ReadContext = NULL; ULONG FoundCount = 0; PBCB Bcb = NULL; IndexKey.KeyLength = sizeof( SharedSecurity->Header.HashKey ); IndexKey.Key = &SharedSecurity->Header.HashKey.Hash; try { // // We keep reading hash records until we find a hash. // while (SharedSecurity->Header.HashKey.SecurityId == SECURITY_ID_INVALID) { // // Read next matching SecurityHashIndex record // FoundCount = 1; NtOfsReadRecords( IrpContext, IrpContext->Vcb->SecurityDescriptorHashIndex, &ReadContext, Key, NtOfsMatchSecurityHash, ULongToPtr( SharedSecurity->Header.HashKey.Hash ), &FoundCount, &FoundRow, sizeof( HashDescriptorHeader ), &HashDescriptorHeader[0]); // // Set next read to read sequentially rather than explicitly // seek. // Key = NULL; // // If there were no more records found, then go and establish a // a new security Id. // if (FoundCount == 0) { break; } // // Examine the row to see if the descriptors are // the same. Verify the cache contents. // ASSERT( FoundRow.DataPart.DataLength == sizeof( SECURITY_DESCRIPTOR_HEADER ) ); if (FoundRow.DataPart.DataLength != sizeof( SECURITY_DESCRIPTOR_HEADER )) { DebugTrace( 0, DbgAcl, ("Found row has a bad size\n") ); NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL ); } Header = (PSECURITY_DESCRIPTOR_HEADER)FoundRow.DataPart.Data; // // If the length of the security descriptor in the stream is NOT // the same as the current security descriptor, then a match is // not possible // if (SharedSecurity->Header.Length != Header->Length) { DebugTrace( 0, DbgAcl, ("Descriptor has wrong length\n") ); continue; } // // Map security descriptor given descriptor stream position. // try { PSECURITY_DESCRIPTOR_HEADER TestHeader; NtfsMapStream( IrpContext, IrpContext->Vcb->SecurityDescriptorStream, Header->Offset, Header->Length, &Bcb, &TestHeader ); // // Make sure index data matches stream data // ASSERT( (TestHeader->HashKey.Hash == Header->HashKey.Hash) && (TestHeader->HashKey.SecurityId == Header->HashKey.SecurityId) && (TestHeader->Length == Header->Length) ); // // Compare byte-for-byte the security descriptors. We do not // perform any rearranging of descriptors into canonical forms. // if (RtlEqualMemory( SharedSecurity->SecurityDescriptor, TestHeader + 1, GetSharedSecurityLength( SharedSecurity )) ) { // // We have a match. Save the found header // SharedSecurity->Header = *TestHeader; DebugTrace( 0, DbgAcl, ("Reusing indexed security Id %x\n", TestHeader->HashKey.SecurityId) ); leave; } DebugTrace( 0, 0, ("Descriptors different in bits %x\n", TestHeader->HashKey.SecurityId)); } finally { NtfsUnpinBcb( IrpContext, &Bcb ); } } } finally { if (ReadContext != NULL) { NtOfsFreeReadContext( ReadContext ); } } } DebugTrace( -1, DbgAcl, ("NtOfsLookupSecurityDescriptorInIndex...Done\n") ); return; } // // Local Support routine // SECURITY_ID GetSecurityIdFromSecurityDescriptorUnsafe ( PIRP_CONTEXT IrpContext, IN OUT PSHARED_SECURITY SharedSecurity ) /*++ Routine Description: Return the security Id associated with a given security descriptor. If there is an existing Id, return it. If no Id exists, create one. This assumes security mutex is already acquired Arguments: IrpContext - context of the call SharedSecurity - Shared security used by file Return Value: SECURITY_ID corresponding to the unique instantiation of the security descriptor on the volume. --*/ { SECURITY_ID SavedSecurityId; LONGLONG NextDescriptorOffset, PaddedNextDescriptorOffset; PAGED_CODE( ); DebugTrace( +1, DbgAcl, ("GetSecurityIdFromSecurityDescriptorUnsafe...\n") ); // // Drop the security mutex since we are going to acquire / extend the descriptor stream // and the mutex is basically an end resource. Inc ref. count to keep // shared security around // SharedSecurity->ReferenceCount += 1; NtfsReleaseFcbSecurity( IrpContext->Vcb ); // // Find descriptor in indexes/stream // try { // // Make sure the data structures don't change underneath us // NtfsAcquireSharedScb( IrpContext, IrpContext->Vcb->SecurityDescriptorStream ); // // Save next Security Id. This is used if we fail to find the security // descriptor in the descriptor stream. // SavedSecurityId = IrpContext->Vcb->NextSecurityId; NtOfsLookupSecurityDescriptorInIndex( IrpContext, SharedSecurity ); // // If we've found the security descriptor in the stream we're done. // if (SharedSecurity->Header.HashKey.SecurityId != SECURITY_ID_INVALID) { leave; } // // The security descriptor is not found. Reacquire the security // stream exclusive since we are about to modify it. // NtfsReleaseScb( IrpContext, IrpContext->Vcb->SecurityDescriptorStream ); NtfsAcquireExclusiveScb( IrpContext, IrpContext->Vcb->SecurityDescriptorStream ); // // During the short interval above, we did not own the security stream. // It is possible that another thread has gotten in and created this // descriptor. Therefore, we must probe the indexes again. // // Rather than perform this expensive test *always*, we saved the next // security id to be allocated above. Now that we've obtained the stream // exclusive we can check to see if the saved one is the same as the next // one. If so, then we need to probe the indexes. Otherwise // we know that no modifications have taken place. // if (SavedSecurityId != IrpContext->Vcb->NextSecurityId) { DebugTrace( 0, DbgAcl, ("SecurityId changed, rescanning\n") ); // // The descriptor cache has been edited. We must search again // NtOfsLookupSecurityDescriptorInIndex( IrpContext, SharedSecurity ); // // If the Id was found this time, simply return it // if (SharedSecurity->Header.HashKey.SecurityId != SECURITY_ID_INVALID) { leave; } } // // Allocate security id. This does not need to be logged since we only // increment this and initialize this from the max key in the index at // mount time. // SharedSecurity->Header.HashKey.SecurityId = IrpContext->Vcb->NextSecurityId++; // // Determine allocation location in descriptor stream. The alignment // requirements for security descriptors within the stream are: // // DWORD alignment // Not spanning a VACB_MAPPING_GRANULARITY boundary // // // Get current EOF for descriptor stream. This includes the replicated // region. Remove the replicated region (& ~VACB_MAPPING_GRANULARITY) // #if DBG { LONGLONG Tmp = NtOfsQueryLength( IrpContext->Vcb->SecurityDescriptorStream ); ASSERT( Tmp == 0 || (Tmp & VACB_MAPPING_GRANULARITY) ); } #endif NextDescriptorOffset = NtOfsQueryLength( IrpContext->Vcb->SecurityDescriptorStream ) & ~VACB_MAPPING_GRANULARITY; // // Align to 16 byte boundary. // PaddedNextDescriptorOffset = SharedSecurity->Header.Offset = (NextDescriptorOffset + 0xF) & 0xFFFFFFFFFFFFFFF0i64; DebugTrace( 0, DbgAcl, ("Allocating SecurityId %x at %016I64x\n", SharedSecurity->Header.HashKey.SecurityId, SharedSecurity->Header.Offset) ); // // Make sure we don't span a VACB_MAPPING_GRANULARITY boundary and // have enough room for a completely-zero header. // if ( // // Offset in window // (SharedSecurity->Header.Offset & (VACB_MAPPING_GRANULARITY - 1)) // // Plus size security stream entry // + SharedSecurity->Header.Length // // Plus size of one empty header // + sizeof( SharedSecurity->Header ) // // goes into the next block // > VACB_MAPPING_GRANULARITY) { // // We are about to span the mapping granularity of the cache manager // so we want to place this into the next cache window. However, // the following window is where the replicated descriptors are // stored. We must advance to the window beyond that. // SharedSecurity->Header.Offset = // // Round down to previous VACB_MAPPING GRANULARITY // (SharedSecurity->Header.Offset & ~(VACB_MAPPING_GRANULARITY - 1)) // // Move past this window and replicated window // + 2 * VACB_MAPPING_GRANULARITY; // // The next descriptor offset is used for zeroing out the padding // PaddedNextDescriptorOffset = SharedSecurity->Header.Offset - VACB_MAPPING_GRANULARITY; } // // Grow security stream to make room for new descriptor and header. This // takes into account the replicated copy of the descriptor. // NtOfsSetLength( IrpContext, IrpContext->Vcb->SecurityDescriptorStream, (SharedSecurity->Header.Offset + SharedSecurity->Header.Length + VACB_MAPPING_GRANULARITY) ); // // Zero out any alignment padding since Chkdsk verifies the replication by // doing 256K memcmp's. // NtOfsPutData( IrpContext, IrpContext->Vcb->SecurityDescriptorStream, NextDescriptorOffset + VACB_MAPPING_GRANULARITY, (ULONG)(PaddedNextDescriptorOffset - NextDescriptorOffset), NULL ); NtOfsPutData( IrpContext, IrpContext->Vcb->SecurityDescriptorStream, NextDescriptorOffset, (ULONG)(PaddedNextDescriptorOffset - NextDescriptorOffset), NULL ); // // Put the new descriptor into the stream in both the "normal" // place and in the replicated place. // NtOfsPutData( IrpContext, IrpContext->Vcb->SecurityDescriptorStream, SharedSecurity->Header.Offset, SharedSecurity->Header.Length, &SharedSecurity->Header ); NtOfsPutData( IrpContext, IrpContext->Vcb->SecurityDescriptorStream, SharedSecurity->Header.Offset + VACB_MAPPING_GRANULARITY, SharedSecurity->Header.Length, &SharedSecurity->Header ); // // add id->data map // { INDEX_ROW Row; Row.KeyPart.KeyLength = sizeof( SharedSecurity->Header.HashKey.SecurityId ); Row.KeyPart.Key = &SharedSecurity->Header.HashKey.SecurityId; Row.DataPart.DataLength = sizeof( SharedSecurity->Header ); Row.DataPart.Data = &SharedSecurity->Header; NtOfsAddRecords( IrpContext, IrpContext->Vcb->SecurityIdIndex, 1, &Row, FALSE ); } // // add hash|id->data map // { INDEX_ROW Row; Row.KeyPart.KeyLength = sizeof( SharedSecurity->Header.HashKey ); Row.KeyPart.Key = &SharedSecurity->Header.HashKey; Row.DataPart.DataLength = sizeof( SharedSecurity->Header ); Row.DataPart.Data = &SharedSecurity->Header; NtOfsAddRecords( IrpContext, IrpContext->Vcb->SecurityDescriptorHashIndex, 1, &Row, FALSE ); } } finally { NtfsReleaseScb( IrpContext, IrpContext->Vcb->SecurityDescriptorStream ); // // Reacquire fcb security mutex and deref count // NtfsAcquireFcbSecurity( IrpContext->Vcb ); SharedSecurity->ReferenceCount -= 1; } DebugTrace( -1, DbgAcl, ("GetSecurityIdFromSecurityDescriptorUnsafe returns %08x\n", SharedSecurity->Header.HashKey.SecurityId) ); return SharedSecurity->Header.HashKey.SecurityId; } VOID NtfsStoreSecurityDescriptor ( PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN BOOLEAN LogIt ) /*++ Routine Description: LEGACY NOTE - this routine disappears when all volumes become NT 5 This routine stores a new security descriptor already stored in the fcb from memory onto the disk. Arguments: Fcb - Supplies the fcb for the file being operated on LogIt - Supplies whether or not the creation of a new security descriptor should/ be logged or not. Modifications are always logged. This parameter must only be specified as FALSE for a file which is currently being created. Return Value: None. Note: This will dirty the standard information in the FCB but will not update it on disk. The caller needs to bring these into sync. --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext; ATTRIBUTE_ENUMERATION_CONTEXT StdInfoContext; BOOLEAN CleanupStdInfoContext = FALSE; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsStoreSecurityDescriptor...\n") ); ASSERT_EXCLUSIVE_FCB( Fcb ); // // Initialize the attribute and find the security attribute // NtfsInitializeAttributeContext( &AttributeContext ); try { ASSERT( Fcb->Vcb->SecurityDescriptorStream == NULL); // // Check if the attribute is first being modified or deleted, a null // value means that we are deleting the security descriptor // if (Fcb->SharedSecurity == NULL) { DebugTrace( 0, Dbg, ("Security Descriptor is null\n") ); // // If it already doesn't exist then we're done, otherwise simply // delete the attribute // if (NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $SECURITY_DESCRIPTOR, &AttributeContext )) { DebugTrace( 0, Dbg, ("Delete existing Security Descriptor\n") ); NtfsDeleteAttributeRecord( IrpContext, Fcb, DELETE_LOG_OPERATION | DELETE_RELEASE_FILE_RECORD | DELETE_RELEASE_ALLOCATION, &AttributeContext ); } leave; } // // At this point we are modifying the security descriptor so read in the // security descriptor, if it does not exist then we will need to create // one. // if (!NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $SECURITY_DESCRIPTOR, &AttributeContext )) { DebugTrace( 0, Dbg, ("Create a new Security Descriptor\n") ); NtfsCleanupAttributeContext( IrpContext, &AttributeContext ); NtfsInitializeAttributeContext( &AttributeContext ); NtfsCreateAttributeWithValue( IrpContext, Fcb, $SECURITY_DESCRIPTOR, NULL, // attribute name &Fcb->SharedSecurity->SecurityDescriptor, GetSharedSecurityLength(Fcb->SharedSecurity), 0, // attribute flags NULL, // where indexed LogIt, // logit &AttributeContext ); // // We may be modifying the security descriptor of an NT 5.0 volume. // We want to store a SecurityID in the standard information field so // that if we reboot on 5.0 NTFS will know where to find the most // recent security descriptor. // if (FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) { LARGE_STANDARD_INFORMATION StandardInformation; // // Initialize the context structure. // NtfsInitializeAttributeContext( &StdInfoContext ); CleanupStdInfoContext = TRUE; // // Locate the standard information, it must be there. // if (!NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $STANDARD_INFORMATION, &StdInfoContext )) { DebugTrace( 0, Dbg, ("Can't find standard information\n") ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } ASSERT( NtfsFoundAttribute( &StdInfoContext )->Form.Resident.ValueLength >= sizeof( LARGE_STANDARD_INFORMATION )); // // Copy the existing standard information to our buffer. // RtlCopyMemory( &StandardInformation, NtfsAttributeValue( NtfsFoundAttribute( &StdInfoContext )), sizeof( LARGE_STANDARD_INFORMATION )); StandardInformation.SecurityId = SECURITY_ID_INVALID; StandardInformation.OwnerId = 0; // // Call to change the attribute value. // NtfsChangeAttributeValue( IrpContext, Fcb, 0, &StandardInformation, sizeof( LARGE_STANDARD_INFORMATION ), FALSE, FALSE, FALSE, FALSE, &StdInfoContext ); } } else { DebugTrace( 0, Dbg, ("Change an existing Security Descriptor\n") ); NtfsChangeAttributeValue( IrpContext, Fcb, 0, // Value offset &Fcb->SharedSecurity->SecurityDescriptor, GetSharedSecurityLength( Fcb->SharedSecurity ), TRUE, // logit TRUE, FALSE, FALSE, &AttributeContext ); } } finally { DebugUnwind( NtfsStoreSecurityDescriptor ); // // Cleanup our attribute enumeration context // NtfsCleanupAttributeContext( IrpContext, &AttributeContext ); if (CleanupStdInfoContext) { NtfsCleanupAttributeContext( IrpContext, &StdInfoContext ); } } // // And return to our caller // DebugTrace( -1, Dbg, ("NtfsStoreSecurityDescriptor -> VOID\n") ); return; } PSHARED_SECURITY NtfsCacheSharedSecurityForCreate ( IN PIRP_CONTEXT IrpContext, IN PFCB ParentFcb ) /*++ Routine Description: This routine finds or constructs a security id and SHARED_SECURITY from a specific file or directory. Arguments: IrpContext - Context of the call ParentFcb - Supplies the directory under which the new fcb exists Return Value: Referenced shared security. --*/ { PSECURITY_DESCRIPTOR SecurityDescriptor; PSHARED_SECURITY SharedSecurity; NTSTATUS Status; BOOLEAN IsDirectory; PACCESS_STATE AccessState; PIO_STACK_LOCATION IrpSp; ULONG SecurityDescLength; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( ParentFcb ); PAGED_CODE(); DebugTrace( +1, DbgAcl, ("NtfsCacheSharedSecurityForCreate...\n") ); // // First decide if we are creating a file or a directory // IrpSp = IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp); if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE )) { IsDirectory = TRUE; } else { IsDirectory = FALSE; } // // Extract the parts of the Irp that we need to do our assignment // AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState; // // Check if we need to load the security descriptor for the parent. // if (ParentFcb->SharedSecurity == NULL) { NtfsLoadSecurityDescriptor( IrpContext, ParentFcb ); } ASSERT( ParentFcb->SharedSecurity != NULL ); // // Create a new security descriptor for the file and raise if there is // an error // if (!NT_SUCCESS( Status = SeAssignSecurity( &ParentFcb->SharedSecurity->SecurityDescriptor, AccessState->SecurityDescriptor, &SecurityDescriptor, IsDirectory, &AccessState->SubjectSecurityContext, IoGetFileObjectGenericMapping(), PagedPool ))) { NtfsRaiseStatus( IrpContext, Status, NULL, NULL ); } SecurityDescLength = RtlLengthSecurityDescriptor( SecurityDescriptor ); ASSERT( SeValidSecurityDescriptor( SecurityDescLength, SecurityDescriptor )); try { // // Make sure the length is non-zero. // if (SecurityDescLength == 0) { NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL ); } // // We have a security descriptor. Create a shared security descriptor. // SharedSecurity = NtfsCacheSharedSecurityByDescriptor( IrpContext, SecurityDescriptor, SecurityDescLength, TRUE ); } finally { // // Free the security descriptor created by Se // SeDeassignSecurity( &SecurityDescriptor ); } // // And return to our caller // DebugTrace( -1, DbgAcl, ("NtfsCacheSharedSecurityForCreate -> VOID\n") ); return SharedSecurity; } /*++ Routine Descriptions: Collation routines for security hash index. Collation occurs by Hash first, then security Id Arguments: Key1 - First key to compare. Key2 - Second key to compare. CollationData - Optional data to support the collation. Return Value: LessThan, EqualTo, or Greater than, for how Key1 compares with Key2. --*/ FSRTL_COMPARISON_RESULT NtOfsCollateSecurityHash ( IN PINDEX_KEY Key1, IN PINDEX_KEY Key2, IN PVOID CollationData ) { PSECURITY_HASH_KEY HashKey1 = (PSECURITY_HASH_KEY) Key1->Key; PSECURITY_HASH_KEY HashKey2 = (PSECURITY_HASH_KEY) Key2->Key; UNREFERENCED_PARAMETER(CollationData); PAGED_CODE( ); ASSERT( Key1->KeyLength == sizeof( SECURITY_HASH_KEY ) ); ASSERT( Key2->KeyLength == sizeof( SECURITY_HASH_KEY ) ); if (HashKey1->Hash < HashKey2->Hash) { return LessThan; } else if (HashKey1->Hash > HashKey2->Hash) { return GreaterThan; } else if (HashKey1->SecurityId < HashKey2->SecurityId) { return LessThan; } else if (HashKey1->SecurityId > HashKey2->SecurityId) { return GreaterThan; } else { return EqualTo; } } BOOLEAN NtfsCanAdministerVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp, IN PFCB Fcb, IN PSECURITY_DESCRIPTOR TestSecurityDescriptor OPTIONAL, IN PULONG TestDesiredAccess OPTIONAL ) /*++ Routine Descriptions: For volume open irps test if the user has enough access to administer the volume This means retesting the original requested access Arguments: Irp - The create irp Fcb - The fcb to be tested - this should always be the volumedasd fcb TestSecurityDescriptor - If specified then use then apply this descriptor for the test. TestDesiredAccess - If specified then this is the access to apply. Return Value: TRUE if the user can administer the volume --*/ { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); BOOLEAN ManageAccessGranted; ULONG ManageDesiredAccess; ULONG ManageGrantedAccess; NTSTATUS ManageAccessStatus; PPRIVILEGE_SET Privileges = NULL; PACCESS_STATE AccessState; KPROCESSOR_MODE EffectiveMode; PAGED_CODE(); ASSERT( IrpContext->MajorFunction == IRP_MJ_CREATE ); ASSERT( Fcb == Fcb->Vcb->VolumeDasdScb->Fcb ); AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState; ManageDesiredAccess = AccessState->OriginalDesiredAccess; if (ARGUMENT_PRESENT( TestDesiredAccess )) { ManageDesiredAccess = *TestDesiredAccess; } // // SL_FORCE_ACCESS_CHECK causes us to use an effective RequestorMode // of UserMode. // EffectiveMode = (KPROCESSOR_MODE)(FlagOn( IrpSp->Flags, SL_FORCE_ACCESS_CHECK ) ? UserMode : Irp->RequestorMode); // // Lock the user context, do the access check and then unlock the context // SeLockSubjectContext( &AccessState->SubjectSecurityContext ); try { ManageAccessGranted = SeAccessCheck( (ARGUMENT_PRESENT( TestSecurityDescriptor ) ? TestSecurityDescriptor : &Fcb->SharedSecurity->SecurityDescriptor), &AccessState->SubjectSecurityContext, TRUE, // Tokens are locked ManageDesiredAccess, 0, &Privileges, IoGetFileObjectGenericMapping(), EffectiveMode, &ManageGrantedAccess, &ManageAccessStatus ); } finally { SeUnlockSubjectContext( &AccessState->SubjectSecurityContext ); } if (Privileges != NULL) { SeFreePrivileges( Privileges ); } return ManageAccessGranted; UNREFERENCED_PARAMETER( IrpContext ); } #ifdef NTFS_CACHE_RIGHTS VOID NtfsGetCachedRightsById ( IN PVCB Vcb, IN PLUID TokenId, IN PLUID ModifiedId, IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext, IN PSHARED_SECURITY SharedSecurity, OUT PBOOLEAN EntryCached OPTIONAL, OUT PACCESS_MASK Rights ) /*++ Routine Descriptions: This call returns the access rights held by the effective ACCESS_TOKEN for the given security information, if available. Arguments: Vcb - Volume where security Id is cached TokenId - The effective token's id. ModifiedId - The effective token's modification id. SubjectSecurityContext - A pointer to the subject's captured and locked security context SharedSecurity - Shared security used by file EntryCached - If the token-specific rights are cached at all, TRUE is optionally returned here, otherwise FALSE is returned. Rights - The access rights are returned here. If an entry is not found in the cache for the effective token, only the world rights are returned. Return Value: None. --*/ { UCHAR Index; BOOLEAN AccessGranted; BOOLEAN LockHeld = FALSE; BOOLEAN IsCached = FALSE; NTSTATUS AccessStatus = STATUS_UNSUCCESSFUL; ACCESS_MASK GrantedAccess; PCACHED_ACCESS_RIGHTS CachedRights; PAGED_CODE( ); NtfsAcquireFcbSecurity( Vcb ); LockHeld = TRUE; try { CachedRights = &SharedSecurity->CachedRights; *Rights = CachedRights->EveryoneRights; // // Search the list for the given TokenId. // It is assumed that a specific TokenId will only appear // once in the cache. // for (Index = 0; Index < CachedRights->Count; Index += 1) { // // Check for a match on TokenId and ModifiedId. // if (RtlEqualLuid( &CachedRights->TokenRights[Index].TokenId, TokenId )) { if (RtlEqualLuid( &CachedRights->TokenRights[Index].ModifiedId, ModifiedId )) { // // We have a match. // SetFlag( *Rights, CachedRights->TokenRights[Index].Rights ); IsCached = TRUE; } break; } } // // If the entry is not cached, get the maximum rights. // Note that it is assumed that this call will not return // rights that require privileges, even if they are currently // enabled. This is the behavior when only MAXIMUM_ALLOWED // is requested. // if (!IsCached) { // // Drop our lock across this call. // NtfsReleaseFcbSecurity( Vcb ); LockHeld = FALSE; AccessGranted = SeAccessCheck( &SharedSecurity->SecurityDescriptor, SubjectSecurityContext, TRUE, // Tokens are locked MAXIMUM_ALLOWED, 0, NULL, IoGetFileObjectGenericMapping(), UserMode, &GrantedAccess, &AccessStatus ); if (AccessGranted) { // // Update the cached knowledge about rights that this // caller is known to have for this security descriptor. // NtfsAddCachedRights( Vcb, SharedSecurity, GrantedAccess, TokenId, ModifiedId ); IsCached = TRUE; } } } finally { if (LockHeld) { NtfsReleaseFcbSecurity( Vcb ); } } if (ARGUMENT_PRESENT( EntryCached )) { *EntryCached = IsCached; } return; } NTSTATUS NtfsGetCachedRights ( IN PVCB Vcb, IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext, IN PSHARED_SECURITY SharedSecurity, OUT PACCESS_MASK Rights, OUT PBOOLEAN EntryCached OPTIONAL, OUT PLUID TokenId OPTIONAL, OUT PLUID ModifiedId OPTIONAL ) /*++ Routine Descriptions: This call returns the access rights known to be held by the effective ACCESS_TOKEN for the given security information. It is assumed that the subject context is locked. Arguments: Vcb - Volume where security Id is cached SubjectSecurityContext - A pointer to the subject's captured and locked security context SharedSecurity - Shared security used by file Rights - The access rights are returned here. If an entry is not found in the cache for the effective token, only the world rights are returned. EntryCached - If the token-specific rights are cached at all, TRUE is optionally returned here, otherwise FALSE is returned. TokenId - The effective token's id is optionally returned here. ModifiedId - The effective token's modification id is optionally returned here. Return Value: NTSTATUS - Returns STATUS_SUCCESS if and only if we have obtained at least the TokenId and ModifiedId information. --*/ { NTSTATUS Status; PACCESS_TOKEN EToken; PTOKEN_STATISTICS Info = NULL; PAGED_CODE( ); DebugTrace( +1, Dbg, ("NtfsGetCachedRights...\n") ); // // First obtain the effective token's id and modification id. // EToken = SeQuerySubjectContextToken( SubjectSecurityContext ); Status = SeQueryInformationToken( EToken, TokenStatistics, &Info ); // // If we have the TokenId and ModifiedId, get the cached rights. // if (Status == STATUS_SUCCESS) { NtfsGetCachedRightsById( Vcb, &Info->TokenId, &Info->ModifiedId, SubjectSecurityContext, SharedSecurity, EntryCached, Rights ); // // Return the Tokenid and ModifiedId to the caller. // if (ARGUMENT_PRESENT( TokenId )) { RtlCopyLuid( TokenId, &Info->TokenId ); } if (ARGUMENT_PRESENT( ModifiedId )) { RtlCopyLuid( ModifiedId, &Info->ModifiedId ); } } else { // // Just return the rights everyone is known to have. // *Rights = SharedSecurity->CachedRights.EveryoneRights; if (ARGUMENT_PRESENT( EntryCached )) { *EntryCached = FALSE; } } if (Info != NULL) { ExFreePool( Info ); } DebugTrace( -1, Dbg, ("NtfsGetCachedRights -> %08lx, Rights=%08lx\n", Status, *Rights) ); return Status; } VOID NtfsAddCachedRights ( IN PVCB Vcb, IN PSHARED_SECURITY SharedSecurity, IN ACCESS_MASK Rights, IN PLUID TokenId, IN PLUID ModifiedId ) /*++ Routine Descriptions: This call caches the access rights held by the effective ACCESS_TOKEN for the given security information. It is assumed that the subject context is locked. Arguments: Vcb - Volume where security Id is cached SharedSecurity - Shared security used by file Rights - The access rights. TokenId - The effective token's id. ModifiedId - The effective token's modification id. Return Value: None. --*/ { BOOLEAN GetEveryoneRights = FALSE; UCHAR Index; PCACHED_ACCESS_RIGHTS CachedRights; PAGED_CODE( ); DebugTrace( +1, Dbg, ("NtfsAddCachedRights...\n") ); // // Make certain that MAXIMUM_ALLOWED is not in the rights. // ClearFlag( Rights, MAXIMUM_ALLOWED ); // // Acquire the security mutex // NtfsAcquireFcbSecurity( Vcb ); try { // // Search the list for the given TokenId. // It is assumed that a specific TokenId will only appear // once in the cache. // for (Index = 0, CachedRights = &SharedSecurity->CachedRights; Index < CachedRights->Count; Index += 1) { // // Check for a match on TokenId and ModifiedId. // if (RtlEqualLuid( &CachedRights->TokenRights[Index].TokenId, TokenId )) { // // Replace ModifiedId if it doesn't match. That will // happen when the token's enabled groups or privileges // have changed since the last time we cached information // for it. // if (!RtlEqualLuid( &CachedRights->TokenRights[Index].ModifiedId, ModifiedId )) { RtlCopyLuid( &CachedRights->TokenRights[Index].ModifiedId, ModifiedId ); } // // We have a match. Set the rights. // CachedRights->TokenRights[Index].Rights = Rights; // // Remember the next entry to use. // CachedRights->NextInsert = Index + 1; break; } } // // If the entry was not found above, add the new entry into the cache. // if (Index == CachedRights->Count) { if ((CachedRights->Count >= 1) && !CachedRights->HaveEveryoneRights) { // // Once we add the second TokenId to the cache, we have a // good indicator that having the world rights could be // useful. // GetEveryoneRights = TRUE; // // Set the indicator that we have the rights now so that // there is no need in the acquisition routine to acquire // the security mutex. This will prevent multiple threads // from attempting to acquire the everyone rights. // // Note that until we actually acquire the rights information // caller will assume that the rights are 0 and go through // the normal per-token access check path. // CachedRights->HaveEveryoneRights = TRUE; } Index = CachedRights->NextInsert; // // We will just replace the first entry in the list. // if (Index == NTFS_MAX_CACHED_RIGHTS) { Index = 0; } ASSERT( Index < NTFS_MAX_CACHED_RIGHTS ); // // Copy in the information. // CachedRights->TokenRights[Index].Rights = Rights; RtlCopyLuid( &CachedRights->TokenRights[Index].TokenId, TokenId ); RtlCopyLuid( &CachedRights->TokenRights[Index].ModifiedId, ModifiedId ); if (Index == CachedRights->Count) { // // Bump the count of entries. // CachedRights->Count += 1; } // // Remember the next entry to use. // CachedRights->NextInsert = Index + 1; } } finally { NtfsReleaseFcbSecurity( Vcb ); } if (GetEveryoneRights) { NtfsSetCachedRightsWorld( SharedSecurity ); } DebugTrace( -1, Dbg, ("NtfsAddCachedRights -> VOID\n") ); return; } #endif