NT4/private/ntos/cntfs/secursup.c
2020-09-30 17:12:29 +02:00

3848 lines
109 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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
//
VOID
NtfsLoadSecurityDescriptor (
PIRP_CONTEXT IrpContext,
IN PFCB Fcb,
IN PFCB ParentFcb OPTIONAL
);
VOID
NtfsStoreSecurityDescriptor (
PIRP_CONTEXT IrpContext,
IN PFCB Fcb,
IN BOOLEAN LogIt
);
#ifdef _CAIRO_
PSHARED_SECURITY
NtOfsFindCachedSharedSecurityBySecurityId (
IN PVCB Vcb,
IN SECURITY_ID SecurityId
);
PSHARED_SECURITY
NtOfsFindCachedSharedSecurityByHash (
IN PVCB Vcb,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN ULONG SecurityDescriptorLength,
IN ULONG Hash
);
VOID
NtOfsAddCachedSharedSecurity (
IN PVCB Vcb,
PSHARED_SECURITY SharedSecurity
);
VOID
NtOfsMapSecurityIdToSecurityDescriptor (
IN PIRP_CONTEXT IrpContext,
IN PVCB Vcb,
IN SECURITY_ID SecurityId,
OUT PSECURITY_DESCRIPTOR *SecurityDescriptor,
OUT PULONG SecurityDescriptorLength,
OUT PBCB *Bcb
);
NTSTATUS
NtOfsMatchSecurityHash (
IN PINDEX_ROW IndexRow,
IN OUT PVOID MatchData
);
VOID
NtOfsLookupSecurityDescriptorInIndex (
PIRP_CONTEXT IrpContext,
IN OUT PSHARED_SECURITY SharedSecurity
);
SECURITY_ID
NtOfsGetSecurityIdFromSecurityDescriptor (
PIRP_CONTEXT IrpContext,
IN OUT PSHARED_SECURITY SharedSecurity
);
#endif // _CAIRO_
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, NtfsAccessCheck)
#pragma alloc_text(PAGE, NtfsAssignSecurity)
#pragma alloc_text(PAGE, NtfsCheckFileForDelete)
#pragma alloc_text(PAGE, NtfsCheckIndexForAddOrDelete)
#pragma alloc_text(PAGE, NtfsDereferenceSharedSecurity)
#pragma alloc_text(PAGE, NtfsLoadSecurityDescriptor)
#pragma alloc_text(PAGE, NtfsModifySecurity)
#pragma alloc_text(PAGE, NtfsNotifyTraverseCheck)
#pragma alloc_text(PAGE, NtfsQuerySecurity)
#pragma alloc_text(PAGE, NtfsStoreSecurityDescriptor)
#ifdef _CAIRO_
#pragma alloc_text(PAGE, NtfsInitializeSecurity)
#pragma alloc_text(PAGE, NtfsLoadSecurityDescriptorById)
#pragma alloc_text(PAGE, NtOfsFindCachedSharedSecurityBySecurityId)
#pragma alloc_text(PAGE, NtOfsFindCachedSharedSecurityByHash)
#pragma alloc_text(PAGE, NtOfsAddCachedSharedSecurity)
#pragma alloc_text(PAGE, NtOfsPurgeSecurityCache)
#pragma alloc_text(PAGE, NtOfsMapSecurityIdToSecurityDescriptor)
#pragma alloc_text(PAGE, NtOfsMatchSecurityHash)
#pragma alloc_text(PAGE, NtOfsLookupSecurityDescriptorInIndex)
#pragma alloc_text(PAGE, NtOfsGetSecurityIdFromSecurityDescriptor)
#pragma alloc_text(PAGE, NtOfsCollateSecurityHash)
#endif // _CAIRO_
#endif
VOID
NtfsAssignSecurity (
IN PIRP_CONTEXT IrpContext,
IN PFCB ParentFcb,
IN PIRP Irp,
IN PFCB NewFcb,
IN PFILE_RECORD_SEGMENT_HEADER FileRecord, // BUGBUG delete
IN PBCB FileRecordBcb, // BUGBUG delete
IN LONGLONG FileOffset, // BUGBUG delete
IN OUT PBOOLEAN LogIt // BUGBUG delete
)
/*++
Routine Description:
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();
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, NULL );
}
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 );
//
// Make sure the length is non-zero.
//
if (SecurityDescLength == 0) {
SeDeassignSecurity( &SecurityDescriptor );
NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
}
ASSERT( SeValidSecurityDescriptor( SecurityDescLength, SecurityDescriptor ));
NtfsUpdateFcbSecurity( IrpContext,
NewFcb,
ParentFcb,
#ifdef _CAIRO_
SECURITY_ID_INVALID,
#endif // _CAIRO_
SecurityDescriptor,
SecurityDescLength );
//
// Free the security descriptor created by Se
//
if (!NT_SUCCESS( Status = SeDeassignSecurity( &SecurityDescriptor ))) {
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
}
//
// BUGBUG begin section to delete when all volumes are cairo
//
#ifdef _CAIRO_
if (NewFcb->Vcb->SecurityDescriptorStream == NULL) {
#endif
//
// 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;
}
#ifdef _CAIRO_
}
#endif // _CAIRO_
//
// BUGBUG end section to delete when all volumes are cairo
//
//
// Write out the new security descriptor
//
NtfsStoreSecurityDescriptor( IrpContext, NewFcb, *LogIt );
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsAssignSecurity -> VOID\n") );
return;
}
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;
ASSERT_IRP_CONTEXT( IrpContext );
ASSERT_FCB( Fcb );
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsModifySecurity...\n") );
//
// First check if we need to load the security descriptor for the file
//
if (Fcb->SharedSecurity == NULL) {
NtfsLoadSecurityDescriptor( IrpContext, Fcb, NULL );
}
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 );
//
// Check for a zero length.
//
if (DescriptorLength == 0) {
SeDeassignSecurity( &DescriptorPtr );
NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
}
//
// Update the move the quota to the new owner if necessary.
//
NtfsMoveQuotaOwner( IrpContext, Fcb, DescriptorPtr );
NtfsAcquireFcbSecurity( Fcb->Vcb );
NtfsDereferenceSharedSecurity( Fcb );
NtfsReleaseFcbSecurity( Fcb->Vcb );
//
// Load the security descriptor into the Fcb
//
NtfsUpdateFcbSecurity( IrpContext,
Fcb,
NULL,
#ifdef _CAIRO_
SECURITY_ID_INVALID,
#endif // _CAIRO_
DescriptorPtr,
DescriptorLength );
//
// Free the security descriptor created by Se
//
if (!NT_SUCCESS( Status = SeDeassignSecurity( &DescriptorPtr ))) {
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
}
//
// Now we need to store the new security descriptor on disk
//
NtfsStoreSecurityDescriptor( IrpContext, Fcb, TRUE );
//
// Remember that we modified the security on the file.
//
SetFlag( Fcb->InfoFlags, FCB_INFO_MODIFIED_SECURITY );
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("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, NULL );
}
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;
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;
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, ParentFcb );
}
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) );
}
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 );
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;
}
//
// Lock the user context, do the access check and then unlock the context
//
SeLockSubjectContext( &AccessState->SubjectSecurityContext );
if (PerformAccessValidation) {
AccessGranted = SeAccessCheck( &Fcb->SharedSecurity->SecurityDescriptor,
&AccessState->SubjectSecurityContext,
TRUE, // Tokens are locked
DesiredAccess,
0,
&Privileges,
IoGetFileObjectGenericMapping(),
(KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
UserMode : Irp->RequestorMode),
&GrantedAccess,
&AccessStatus );
if (Privileges != NULL) {
Status = SeAppendPrivileges( AccessState, Privileges );
SeFreePrivileges( Privileges );
}
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
//
if (ParentFcb->SharedSecurity == NULL) {
NtfsLoadSecurityDescriptor( IrpContext, ParentFcb, NULL );
}
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
//
if (FlagOn( DesiredAccess, DELETE )
|| (MaximumRequested && !MaximumDeleteAcquired)) {
DeleteAccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor,
&AccessState->SubjectSecurityContext,
TRUE, // Tokens are locked
FILE_DELETE_CHILD,
0,
&Privileges,
IoGetFileObjectGenericMapping(),
(KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
UserMode : Irp->RequestorMode),
&DeleteChildGrantedAccess,
&AccessStatus );
if (Privileges != NULL) { SeFreePrivileges( Privileges ); }
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(),
(KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
UserMode : Irp->RequestorMode),
&ListDirectoryGrantedAccess,
&AccessStatus );
if (Privileges != NULL) { SeFreePrivileges( Privileges ); }
if (ReadAttributesAccessGranted) {
SetFlag( ListDirectoryGrantedAccess, FILE_READ_ATTRIBUTES );
ClearFlag( ListDirectoryGrantedAccess, FILE_LIST_DIRECTORY );
ClearFlag( DesiredAccess, FILE_READ_ATTRIBUTES );
} else {
AccessStatusError = AccessStatus;
}
}
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(),
(KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
UserMode : Irp->RequestorMode),
&GrantedAccess,
&AccessStatus );
if (Privileges != NULL) {
Status = SeAppendPrivileges( AccessState, Privileges );
SeFreePrivileges( Privileges );
}
//
// 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;
ATTRIBUTE_ENUMERATION_CONTEXT Context;
PFILE_NAME FileNameAttr;
UNICODE_STRING FileRecordName;
NtfsInitializeAttributeContext( &Context );
try {
//
// 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
//
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 );
} finally {
NtfsCleanupAttributeContext( &Context );
if (AbnormalTermination()) {
SeUnlockSubjectContext( &AccessState->SubjectSecurityContext );
}
}
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,
(KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
UserMode : Irp->RequestorMode),
&AccessState->GenerateOnClose );
} else {
SeOpenObjectAuditAlarm( &FileString,
NULL,
&FullFileName,
&Fcb->SharedSecurity->SecurityDescriptor,
AccessState,
FALSE,
AccessGranted,
(KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
UserMode : Irp->RequestorMode),
&AccessState->GenerateOnClose );
}
NtfsFreePool( FullFileName.Buffer );
}
SeUnlockSubjectContext( &AccessState->SubjectSecurityContext );
//
// If access is not granted then we will raise
//
if (!AccessGranted) {
DebugTrace( 0, Dbg, ("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;
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, ("Unclean 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 (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, ParentFcb );
}
ASSERT( ThisFcb->SharedSecurity != NULL );
//
// 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;
AccessGranted = SeAccessCheck( &ThisFcb->SharedSecurity->SecurityDescriptor,
IrpContext->Union.SubjectContext,
TRUE, // Tokens are locked
DELETE,
0,
&Privileges,
IoGetFileObjectGenericMapping(),
UserMode,
&GrantedAccess,
&Status );
//
// 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, NULL );
}
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
//
AccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor,
IrpContext->Union.SubjectContext,
TRUE, // Tokens are locked
FILE_DELETE_CHILD,
0,
&Privileges,
IoGetFileObjectGenericMapping(),
UserMode,
&GrantedAccess,
&Status );
}
} 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
)
/*++
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.
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 {
//
// 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, NULL );
}
ASSERT( ParentFcb->SharedSecurity != NULL );
//
// Capture and lock the user context, do the access check and then unlock the context
//
SeLockSubjectContext( IrpContext->Union.SubjectContext );
UnlockSubjectContext = TRUE;
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 );
}
} finally {
DebugUnwind( NtfsCheckIndexForAddOrDelete );
if (UnlockSubjectContext) {
SeUnlockSubjectContext( IrpContext->Union.SubjectContext );
}
DebugTrace( +1, Dbg, ("NtfsCheckIndexForAddOrDelete: Exit\n") );
}
return;
}
VOID
NtfsUpdateFcbSecurity (
IN PIRP_CONTEXT IrpContext,
IN OUT PFCB Fcb,
IN PFCB ParentFcb OPTIONAL,
#ifdef _CAIRO_
IN SECURITY_ID SecurityId,
#endif // _CAIRO_
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN ULONG SecurityDescriptorLength
)
/*++
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:
Fcb - Supplies the fcb for the file being operated on
ParentFcb - Optionally supplies a parent Fcb to examine for a
match. If not present, we will follow the Lcb chain in the target
Fcb.
SecurityDescriptor - Security Descriptor for this file.
SecurityDescriptorLength - Length of security descriptor for this file
Return Value:
None.
--*/
{
PSHARED_SECURITY SharedSecurity = NULL;
PLCB ParentLcb;
PFCB LastParent = NULL;
#ifdef _CAIRO_
ULONG Hash = 0;
#endif // _CAIRO_
PAGED_CODE();
//
// Only continue with the load if the length is greater than zero
//
if (SecurityDescriptorLength == 0) {
return;
}
//
// Make sure the security descriptor we just read in is valid
//
if (!SeValidSecurityDescriptor( SecurityDescriptorLength, SecurityDescriptor )) {
SecurityDescriptor = NtfsData.DefaultDescriptor;
SecurityDescriptorLength = NtfsData.DefaultDescriptorLength;
if (!SeValidSecurityDescriptor( SecurityDescriptorLength, SecurityDescriptor )) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
}
}
#ifdef _CAIRO_
//
// 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, Dbg, ("Hash is %08x\n", Hash) );
}
#endif // _CAIRO_
//
// Acquire the security event and use a try-finally to insure we release it.
//
NtfsAcquireFcbSecurity( Fcb->Vcb );
try {
//
// BUGBUG - since we have a cache based on a hash of security ID's, can
// we just skip this walk altogether?
//
//
// If we have a parent then check if this is a matching descriptor.
//
if (!ARGUMENT_PRESENT( ParentFcb )
&& !IsListEmpty( &Fcb->LcbQueue )) {
ParentLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
LCB,
FcbLinks );
if (ParentLcb != Fcb->Vcb->RootLcb) {
ParentFcb = ParentLcb->Scb->Fcb;
}
}
if (ParentFcb != NULL) {
while (TRUE) {
PSHARED_SECURITY NextSharedSecurity;
//
// If the target Fcb is an Index then use the security descriptor for
// our parent. Otherwise use the descriptor for a file on the drive.
//
if (FlagOn( Fcb->Info.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT )) {
NextSharedSecurity = ParentFcb->SharedSecurity;
} else {
NextSharedSecurity = ParentFcb->ChildSharedSecurity;
}
if (NextSharedSecurity != NULL) {
if (GetSharedSecurityLength(NextSharedSecurity) == SecurityDescriptorLength
#ifdef _CAIRO_
&& NextSharedSecurity->Header.HashKey.Hash == Hash
#endif // _CAIRO_
&& RtlEqualMemory( &NextSharedSecurity->SecurityDescriptor,
SecurityDescriptor,
SecurityDescriptorLength )) {
SharedSecurity = NextSharedSecurity;
}
break;
}
LastParent = ParentFcb;
if (!IsListEmpty( &ParentFcb->LcbQueue )) {
ParentLcb = CONTAINING_RECORD( ParentFcb->LcbQueue.Flink,
LCB,
FcbLinks );
if (ParentLcb != Fcb->Vcb->RootLcb) {
ParentFcb = ParentLcb->Scb->Fcb;
} else {
break;
}
} else {
break;
}
}
}
#ifdef _CAIRO_
//
// If we havent't found the security descriptor by walking up the tree then
// try to find it by hash
//
SharedSecurity =
NtOfsFindCachedSharedSecurityByHash( Fcb->Vcb,
SecurityDescriptor,
SecurityDescriptorLength,
Hash );
#endif
//
// 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 );
//
// If this is a file and we have a Parent Fcb without a child
// descriptor we will store this one with that directory.
//
if (!FlagOn( Fcb->Info.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT )
&& LastParent != NULL) {
SharedSecurity->ParentFcb = LastParent;
ASSERT( LastParent->ChildSharedSecurity == NULL );
LastParent->ChildSharedSecurity = SharedSecurity;
LastParent->ChildSharedSecurity->ReferenceCount = 1;
} else {
SharedSecurity->ParentFcb = NULL;
SharedSecurity->ReferenceCount = 0;
}
#ifdef _CAIRO_
//
// 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 = SecurityId;
SharedSecurity->Header.HashKey.Hash = Hash;
if (SecurityId != SECURITY_ID_INVALID) {
NtOfsAddCachedSharedSecurity( Fcb->Vcb, SharedSecurity );
}
SetSharedSecurityLength(SharedSecurity, SecurityDescriptorLength);
SharedSecurity->Header.Offset = (ULONGLONG) 0xFFFFFFFFFFFFFFFFi64;
#else // _CAIRO_
SetSharedSecurityLength(SharedSecurity, SecurityDescriptorLength);
#endif // _CAIRO_
RtlCopyMemory( &SharedSecurity->SecurityDescriptor,
SecurityDescriptor,
SecurityDescriptorLength );
}
Fcb->SharedSecurity = SharedSecurity;
Fcb->SharedSecurity->ReferenceCount++;
Fcb->CreateSecurityCount++;
} finally {
DebugUnwind( NtfsUpdateFcbSecurity );
NtfsReleaseFcbSecurity( Fcb->Vcb );
}
return;
}
_inline
VOID
NtfsRemoveReferenceSharedSecurity (
IN OUT PSHARED_SECURITY SharedSecurity
)
/*++
Routine Description:
This routine is called to manage the reference count on a shared security
descriptor. If the reference count goes to zero, the shared security is
freed.
Arguments:
SharedSecurity - security that is being dereferenced.
Return Value:
None.
--*/
{
//
// Note that there will be one less reference shortly
//
SharedSecurity->ReferenceCount--;
//
// If there is another reference to this shared security *AND* this
// shared security is being shared as a parent's child FCB then
// decouple it from the parent.
//
if (SharedSecurity->ReferenceCount == 1 && SharedSecurity->ParentFcb != NULL) {
//
// Verify that the parent's child matches this shared security
//
ASSERT( SharedSecurity->ParentFcb->ChildSharedSecurity == SharedSecurity );
//
// Remove reference from parent fcb
//
SharedSecurity->ParentFcb->ChildSharedSecurity = NULL;
SharedSecurity->ReferenceCount--;
SharedSecurity->ParentFcb = NULL;
}
if (SharedSecurity->ReferenceCount == 0) {
NtfsFreePool( SharedSecurity );
}
}
VOID
NtfsDereferenceSharedSecurity (
IN OUT PFCB Fcb
)
/*++
Routine Description:
This routine is called to dereference the shared security structure in
an Fcb and deallocate it if possible.
Arguments:
Fcb - Supplies the fcb for the file being operated on.
Return Value:
None.
--*/
{
PSHARED_SECURITY SharedSecurity;
PAGED_CODE();
//
// Remove the reference and capture the shared security if we are to free it.
//
SharedSecurity = Fcb->SharedSecurity;
Fcb->SharedSecurity = NULL;
NtfsRemoveReferenceSharedSecurity( SharedSecurity );
}
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;
PPRIVILEGE_SET Privileges = NULL;
PAGED_CODE();
//
// If we have no Fcb then we can return immediately.
//
if (Fcb == NULL) {
return TRUE;
}
RtlZeroMemory( &LocalIrpContext, sizeof(LocalIrpContext) );
RtlZeroMemory( &LocalIrp, sizeof(LocalIrp) );
IrpContext = &LocalIrpContext;
IrpContext->NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
IrpContext->NodeByteSize = sizeof(IRP_CONTEXT);
IrpContext->OriginatingIrp = &LocalIrp;
SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT);
InitializeListHead( &IrpContext->ExclusiveFcbList );
IrpContext->Vcb = Fcb->Vcb;
//
// Make sure we don't get any pop-ups
//
ThreadTopLevelContext = NtfsSetTopLevelIrp( &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 {
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, NULL );
}
AccessGranted = SeAccessCheck( &Fcb->SharedSecurity->SecurityDescriptor,
SubjectContext,
TRUE, // Tokens are locked
FILE_TRAVERSE,
0,
&Privileges,
IoGetFileObjectGenericMapping(),
UserMode,
&GrantedAccess,
&Status );
} while ( AccessGranted && Fcb != TopFcb );
} finally {
SeUnlockSubjectContext( SubjectContext );
}
} except (NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
NOTHING;
}
NtfsRestoreTopLevelIrp( &TopLevelContext );
return AccessGranted;
}
#ifdef _CAIRO_
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"$SecurityIdIndex" );
UNICODE_STRING SecurityDescriptorHashIndexName =
CONSTANT_UNICODE_STRING( L"$SecurityDescriptorHashIndex" );
UNICODE_STRING SecurityDescriptorStreamName =
CONSTANT_UNICODE_STRING( L"$SecurityDescriptorStream" );
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.
// BUGBUG: At present, these attributes are stored as part of the
// QuotaTable file record.
//
NtOfsCreateIndex( IrpContext,
Fcb,
SecurityIdIndexName,
CREATE_OR_OPEN,
0,
NtOfsCollateUlong,
NULL,
&Vcb->SecurityIdIndex );
NtOfsCreateIndex( IrpContext,
Fcb,
SecurityDescriptorHashIndexName,
CREATE_OR_OPEN,
0,
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, Dbg, ("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, Dbg, ("Security Id index is empty\n") );
Vcb->NextSecurityId = SECURITY_ID_FIRST;
} else {
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
}
DebugTrace( 0, Dbg, ("NextSecurityId is %x\n", Vcb->NextSecurityId) );
} finally {
NtOfsReleaseMap( IrpContext, &Map );
}
}
#endif // _CAIRO_
//
// Local Support routine
//
#ifdef _CAIRO_
PSHARED_SECURITY
NtOfsFindCachedSharedSecurityBySecurityId (
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. This routine assumes exclusive access to the
security cache.
Arguments:
Vcb - Volume where security Id is cached
SecurityId - security Id for descriptor that is being retrieved
Return Value:
PSHARED_SECURITY of found descriptor. Otherwise, NULL is returned.
--*/
{
PSHARED_SECURITY SharedSecurity;
PAGED_CODE( );
SharedSecurity = Vcb->SecurityCacheById[SecurityId % VCB_SECURITY_CACHE_BY_ID_SIZE];
//
// If there is no security descriptor there then no match was found
//
if (SharedSecurity == NULL) {
return NULL;
}
//
// If the security Id's don't match then no descriptor was found
//
if (SharedSecurity->Header.HashKey.SecurityId != SecurityId) {
return NULL;
}
//
// The shared security was found
//
return SharedSecurity;
}
#endif // _CAIRO_
//
// Local Support routine
//
#ifdef _CAIRO_
PSHARED_SECURITY
NtOfsFindCachedSharedSecurityByHash (
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 || *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;
}
#endif // _CAIRO_
//
// Local Support routine
//
#ifdef _CAIRO_
void
NtOfsAddCachedSharedSecurity (
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->SecurityCacheById[SharedSecurity->Header.HashKey.SecurityId % VCB_SECURITY_CACHE_BY_ID_SIZE];
Old = *Bucket;
//
// Place it into the bucket and reference it
//
*Bucket = SharedSecurity;
SharedSecurity->ReferenceCount ++;
//
// Set up hash to point to bucket
//
Vcb->SecurityCacheByHash[SharedSecurity->Header.HashKey.Hash % VCB_SECURITY_CACHE_BY_HASH_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
//
// *Bucket = NULL;
NtfsRemoveReferenceSharedSecurity( Old );
}
}
#endif // _CAIRO_
#ifdef _CAIRO_
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->SecurityCacheById[i] != NULL) {
//
// Remove the reference to the security
//
PSHARED_SECURITY SharedSecurity = Vcb->SecurityCacheById[i];
Vcb->SecurityCacheById[i] = NULL;
NtfsRemoveReferenceSharedSecurity( SharedSecurity );
}
}
//
// Release access to the cache
//
NtfsReleaseFcbSecurity( Vcb );
}
#endif // _CAIRO_
//
// Local Support routine
//
#ifdef _CAIRO_
VOID
NtOfsMapSecurityIdToSecurityDescriptor (
IN PIRP_CONTEXT IrpContext,
IN PVCB Vcb,
IN SECURITY_ID SecurityId,
OUT PSECURITY_DESCRIPTOR *SecurityDescriptor,
OUT PULONG SecurityDescriptorLength,
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
SecurityDescriptor - returned security descriptor pointer
SecurityDescriptorLength - returned length of security descriptor
Bcb - returned mapping control structure
Return Value:
None.
--*/
{
SECURITY_DESCRIPTOR_HEADER Header;
NTSTATUS Status;
MAP_HANDLE Map;
INDEX_ROW Row;
INDEX_KEY Key;
PAGED_CODE( );
DebugTrace( 0, Dbg, ("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, Dbg, ("Security Id lookup status = %08x\n", Status) );
//
// If the security Id is not found, then this volume is corrupt.
// We raise the error which will force CHKDSK to be run to rebuild
// the mapping index.
//
if (Status == STATUS_NO_MATCH) {
DebugTrace( 0, Dbg, ("SecurityId is not found in index\n") );
NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
}
//
// 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
//
ASSERT( Row.DataPart.DataLength == sizeof( SECURITY_DESCRIPTOR_HEADER ) );
if (Row.DataPart.DataLength != sizeof( SECURITY_DESCRIPTOR_HEADER )) {
DebugTrace( 0, Dbg, ("SecurityId data doesn't have the correct length\n") );
NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
}
//
// Map security descriptor
//
DebugTrace( 0, Dbg, ("Mapping security descriptor stream at %I64x, len %x\n",
Header.Offset, Header.Length) );
NtfsMapStream(
IrpContext,
Vcb->SecurityDescriptorStream,
Header.Offset,
Header.Length,
Bcb,
SecurityDescriptor );
//
// Set return values
//
*SecurityDescriptor =
(PSECURITY_DESCRIPTOR) Add2Ptr( *SecurityDescriptor,
sizeof( SECURITY_DESCRIPTOR_HEADER ) );
*SecurityDescriptorLength =
GETSECURITYDESCRIPTORLENGTH( &Header );
}
VOID
NtfsLoadSecurityDescriptorById (
IN PIRP_CONTEXT IrpContext,
IN PFCB Fcb,
IN PFCB ParentFcb OPTIONAL
)
/*++
Routine Description:
This routine finds or creates the shared security for the specified
Fcb by looking in the volume cache or index
Arguments:
IrpContext - Context of call
Fcb - File whose security is to be loaded
ParentFcb - FCB of parent when searching upward to find already-cached
descriptor
Return Value:
None.
--*/
{
PSHARED_SECURITY SharedSecurity;
PAGED_CODE( );
//
// Serialize access to the security cache
//
NtfsAcquireFcbSecurity( Fcb->Vcb );
//
// First, consult the Vcb cache of security Ids
//
SharedSecurity = NtOfsFindCachedSharedSecurityBySecurityId( Fcb->Vcb, Fcb->SecurityId );
//
// If we found one, store it in the Fcb and we're done
//
if (SharedSecurity != NULL) {
Fcb->SharedSecurity = SharedSecurity;
Fcb->SharedSecurity->ReferenceCount++;
Fcb->CreateSecurityCount += 1;
DebugTrace( 0, DbgAcl, ("Found cached security descriptor %x %x\n",
SharedSecurity, SharedSecurity->Header.HashKey.SecurityId) );
//
// Release access to security cache
//
NtfsReleaseFcbSecurity( Fcb->Vcb );
} else {
PBCB Bcb = NULL;
PSECURITY_DESCRIPTOR SecurityDescriptor;
ULONG SecurityDescriptorLength;
//
// Release access to security cache
//
NtfsReleaseFcbSecurity( Fcb->Vcb );
DebugTrace( 0, Dbg, ("Looking up security descriptor %x\n", Fcb->SecurityId) );
//
// Lock down the security stream
//
NtfsAcquireSharedScb( IrpContext, Fcb->Vcb->SecurityDescriptorStream );
try {
//
// Consult the Vcb index to map to the security descriptor
//
NtOfsMapSecurityIdToSecurityDescriptor( IrpContext,
Fcb->Vcb,
Fcb->SecurityId,
&SecurityDescriptor,
&SecurityDescriptorLength,
&Bcb );
//
// Generate the shared security from the security Id and descriptor
//
NtfsUpdateFcbSecurity( IrpContext,
Fcb,
ParentFcb,
Fcb->SecurityId,
SecurityDescriptor,
SecurityDescriptorLength );
} finally {
NtfsUnpinBcb( &Bcb );
NtfsReleaseScb( IrpContext, Fcb->Vcb->SecurityDescriptorStream );
}
}
}
#endif // _CAIRO_
//
// Local Support routine
//
VOID
NtfsLoadSecurityDescriptor (
IN PIRP_CONTEXT IrpContext,
IN PFCB Fcb,
IN PFCB ParentFcb OPTIONAL
)
/*++
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, Dbg, ("NtfsLoadSecurityDescriptor...\n") );
#ifdef _CAIRO_
//
// If the file has a valid SecurityId then retrieve the security descriptor
// from the security descriptor index
//
if (Fcb->SecurityId != SECURITY_ID_INVALID) {
NtfsLoadSecurityDescriptorById( IrpContext, Fcb, ParentFcb );
} else
#endif // _CAIRO_
{
PBCB Bcb = NULL;
PSHARED_SECURITY SharedSecurity;
PSECURITY_DESCRIPTOR SecurityDescriptor;
ULONG SecurityDescriptorLength;
ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
PATTRIBUTE_RECORD_HEADER Attribute;
try {
//
// Read in the security descriptor attribute, and it is 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, Dbg, ("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 );
}
}
NtfsUpdateFcbSecurity( IrpContext,
Fcb,
ParentFcb,
#ifdef _CAIRO_
SECURITY_ID_INVALID,
#endif // _CAIRO_
SecurityDescriptor,
SecurityDescriptorLength );
} finally {
DebugUnwind( NtfsLoadSecurityDescriptor );
//
// Cleanup our attribute enumeration context and the Bcb
//
NtfsCleanupAttributeContext( &AttributeContext );
NtfsUnpinBcb( &Bcb );
}
}
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsLoadSecurityDescriptor -> VOID\n") );
return;
}
//
// Local Support routine
//
#ifdef _CAIRO_
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) MatchData) {
return STATUS_SUCCESS;
} else {
return STATUS_NO_MORE_MATCHES;
}
}
#endif // _CAIRO_
//
// Local Support routine
//
#ifdef _CAIRO_
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, Dbg, ("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,
(PVOID)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, Dbg, ("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) {
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) );
}
} finally {
NtfsUnpinBcb( &Bcb );
}
}
} finally {
if (ReadContext != NULL) {
NtOfsFreeReadContext( ReadContext );
}
}
}
}
#endif // _CAIRO_
//
// Local Support routine
//
#ifdef _CAIRO_
SECURITY_ID
NtOfsGetSecurityIdFromSecurityDescriptor (
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.
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;
PAGED_CODE( );
DebugTrace( +1, Dbg, ("NtOfsGetSecurityIdFromSecurityDescriptor...\n") );
//
// 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;
//
// Find descriptor in indexes/stream
//
try {
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
//
SharedSecurity->Header.Offset =
IrpContext->Vcb->SecurityDescriptorStream->Header.FileSize.QuadPart;
//
// Align to big boundary
//
SharedSecurity->Header.Offset =
(SharedSecurity->Header.Offset + 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
//
if ((SharedSecurity->Header.Offset & (VACB_MAPPING_GRANULARITY - 1)) +
SharedSecurity->Header.Length >= VACB_MAPPING_GRANULARITY) {
SharedSecurity->Header.Offset =
(SharedSecurity->Header.Offset + VACB_MAPPING_GRANULARITY - 1) &
~(VACB_MAPPING_GRANULARITY - 1);
}
//
// Grow security stream to make room for new descriptor and header
//
NtOfsSetLength( IrpContext, IrpContext->Vcb->SecurityDescriptorStream,
SharedSecurity->Header.Offset +
SharedSecurity->Header.Length);
//
// Put the new descriptor into the stream
//
NtOfsPutData( IrpContext, IrpContext->Vcb->SecurityDescriptorStream,
SharedSecurity->Header.Offset,
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 );
}
DebugTrace(-1, Dbg, ("NtOfsGetSecurityIdFromSecurityDescriptor returns %08x\n",
SharedSecurity->Header.HashKey.SecurityId));
return SharedSecurity->Header.HashKey.SecurityId;
}
#endif // _CAIRO_
//
// Local Support routine
//
VOID
NtfsStoreSecurityDescriptor (
PIRP_CONTEXT IrpContext,
IN PFCB Fcb,
IN BOOLEAN LogIt
)
/*++
Routine Description:
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.
--*/
{
ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
ATTRIBUTE_ENUMERATION_CONTEXT StdInfoContext;
BOOLEAN CleanupStdInfoContext = FALSE;
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsStoreSecurityDescriptor...\n") );
//
// Initialize the attribute and find the security attribute
//
NtfsInitializeAttributeContext( &AttributeContext );
try {
#ifdef _CAIRO_
//
// BUGBUG - remove the following IF statement when all volumes get security
// descriptor streams.
//
if (Fcb->Vcb->SecurityDescriptorStream != NULL) {
//
// If the shared security pointer is null, then we are deleting the
// security descriptor altogether. If so, and we have a security
// attribute, indicated by NOT having large standard info, then we
// must delete the security attribute.
//
if (Fcb->SharedSecurity == NULL) {
if (!FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) {
DebugTrace( 0, Dbg, ("Security Descriptor is null\n") );
//
// Read in the security descriptor attribute 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,
TRUE,
FALSE,
&AttributeContext );
}
}
leave;
}
//
// 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 before we store the ACL efficiently.
//
if (!FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO) ) {
DebugTrace( 0, Dbg, ("Growing standard information\n") );
NtfsGrowStandardInformation( IrpContext, Fcb );
DebugTrace( 0, Dbg, ("Security Descriptor is null\n") );
//
// Read in the security descriptor attribute 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,
TRUE,
FALSE,
&AttributeContext );
}
}
//
// If the shared security descriptor already has an ID assigned, then
// use it
//
if (Fcb->SharedSecurity->Header.HashKey.SecurityId != SECURITY_ID_INVALID) {
Fcb->SecurityId = Fcb->SharedSecurity->Header.HashKey.SecurityId;
DebugTrace( 0, DbgAcl, ("Reusing cached security Id %x\n", Fcb->SecurityId) );
} else {
//
// Find unique SecurityId for descriptor and set SecurityId in Fcb.
//
Fcb->SecurityId = NtOfsGetSecurityIdFromSecurityDescriptor( IrpContext,
Fcb->SharedSecurity );
//
// By serializing allocation of Id's, we have a tiny race in here
// where two threads could be setting the same security Id into
// the shared security.
//
ASSERT( Fcb->SharedSecurity->Header.HashKey.SecurityId == SECURITY_ID_INVALID ||
Fcb->SharedSecurity->Header.HashKey.SecurityId == Fcb->SecurityId );
Fcb->SharedSecurity->Header.HashKey.SecurityId = Fcb->SecurityId;
//
// Serialize access to the security cache
//
NtfsAcquireFcbSecurity( Fcb->Vcb );
//
// Cache this shared security for faster access
//
NtOfsAddCachedSharedSecurity( Fcb->Vcb, Fcb->SharedSecurity );
//
// Release access to security cache
//
NtfsReleaseFcbSecurity( Fcb->Vcb );
}
//
// We've changed the standard information for this file. We now must
// update the disk to make sure things are consistent.
//
leave;
}
#endif // _CAIRO_
//
// 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,
TRUE,
FALSE,
&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( &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( &AttributeContext );
if (CleanupStdInfoContext) {
NtfsCleanupAttributeContext( &StdInfoContext );
}
}
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsStoreSecurityDescriptor -> VOID\n") );
return;
}
/*++
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.
--*/
#ifdef _CAIRO_
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;
}
}
#endif // _CAIRO_