mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3847 lines
109 KiB
3847 lines
109 KiB
/*++
|
|
|
|
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_
|
|
|
|
|