2017-11-23 22:26:10 +00:00
|
|
|
/*++
|
|
|
|
|
|
|
|
Copyright (c) 1989-2000 Microsoft Corporation
|
|
|
|
|
|
|
|
Module Name:
|
|
|
|
|
|
|
|
AcChkSup.c
|
|
|
|
|
|
|
|
Abstract:
|
|
|
|
|
|
|
|
This module implements the FAT access checking routine
|
|
|
|
|
|
|
|
|
|
|
|
--*/
|
|
|
|
|
|
|
|
#include "fatprocs.h"
|
|
|
|
|
|
|
|
//
|
|
|
|
// Our debug trace level
|
|
|
|
//
|
|
|
|
|
|
|
|
#define Dbg (DEBUG_TRACE_ACCHKSUP)
|
|
|
|
|
|
|
|
NTSTATUS
|
|
|
|
FatCreateRestrictEveryoneToken(
|
|
|
|
IN PACCESS_TOKEN Token,
|
|
|
|
OUT PACCESS_TOKEN *RestrictedToken
|
|
|
|
);
|
|
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
|
|
#pragma alloc_text(PAGE, FatCheckFileAccess)
|
|
|
|
#pragma alloc_text(PAGE, FatCheckManageVolumeAccess)
|
|
|
|
#pragma alloc_text(PAGE, FatCreateRestrictEveryoneToken)
|
|
|
|
#pragma alloc_text(PAGE, FatExplicitDeviceAccessGranted)
|
|
|
|
#endif
|
|
|
|
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
BOOLEAN
|
|
|
|
FatCheckFileAccess (
|
|
|
|
PIRP_CONTEXT IrpContext,
|
|
|
|
IN UCHAR DirentAttributes,
|
|
|
|
IN PACCESS_MASK DesiredAccess
|
|
|
|
)
|
|
|
|
|
|
|
|
/*++
|
|
|
|
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
This routine checks if a desired access is allowed to a file represented
|
|
|
|
by the specified DirentAttriubutes.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
DirentAttributes - Supplies the Dirent attributes to check access for
|
|
|
|
|
|
|
|
DesiredAccess - Supplies the desired access mask that we are checking for
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
|
|
BOOLEAN - TRUE if access is allowed and FALSE otherwise
|
|
|
|
|
|
|
|
--*/
|
|
|
|
|
|
|
|
{
|
|
|
|
BOOLEAN Result;
|
|
|
|
|
|
|
|
DebugTrace(+1, Dbg, "FatCheckFileAccess\n", 0);
|
|
|
|
DebugTrace( 0, Dbg, "DirentAttributes = %8lx\n", DirentAttributes);
|
|
|
|
DebugTrace( 0, Dbg, "DesiredAccess = %8lx\n", *DesiredAccess);
|
|
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
|
|
|
|
//
|
|
|
|
// This procedures is programmed like a string of filters each
|
|
|
|
// filter checks to see if some access is allowed, if it is not allowed
|
|
|
|
// the filter return FALSE to the user without further checks otherwise
|
|
|
|
// it moves on to the next filter. The filter check is to check for
|
|
|
|
// desired access flags that are not allowed for a particular dirent
|
|
|
|
//
|
|
|
|
|
|
|
|
Result = TRUE;
|
|
|
|
|
|
|
|
_SEH2_TRY {
|
|
|
|
|
|
|
|
//
|
|
|
|
// Check for Volume ID or Device Dirents, these are not allowed user
|
|
|
|
// access at all
|
|
|
|
//
|
|
|
|
|
|
|
|
if (FlagOn(DirentAttributes, FAT_DIRENT_ATTR_VOLUME_ID) ||
|
|
|
|
FlagOn(DirentAttributes, FAT_DIRENT_ATTR_DEVICE)) {
|
|
|
|
|
|
|
|
DebugTrace(0, Dbg, "Cannot access volume id or device\n", 0);
|
|
|
|
|
|
|
|
try_return( Result = FALSE );
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Check the desired access for the object - we only blackball that
|
|
|
|
// we do not understand. The model of filesystems using ACLs is that
|
|
|
|
// they do not type the ACL to the object the ACL is on. Permissions
|
|
|
|
// are not checked for consistency vs. the object type - dir/file.
|
|
|
|
//
|
|
|
|
|
|
|
|
if (FlagOn(*DesiredAccess, ~(DELETE |
|
|
|
|
READ_CONTROL |
|
|
|
|
WRITE_OWNER |
|
|
|
|
WRITE_DAC |
|
|
|
|
SYNCHRONIZE |
|
|
|
|
ACCESS_SYSTEM_SECURITY |
|
|
|
|
FILE_WRITE_DATA |
|
|
|
|
FILE_READ_EA |
|
|
|
|
FILE_WRITE_EA |
|
|
|
|
FILE_READ_ATTRIBUTES |
|
|
|
|
FILE_WRITE_ATTRIBUTES |
|
|
|
|
FILE_LIST_DIRECTORY |
|
|
|
|
FILE_TRAVERSE |
|
|
|
|
FILE_DELETE_CHILD |
|
|
|
|
FILE_APPEND_DATA |
|
|
|
|
MAXIMUM_ALLOWED))) {
|
|
|
|
|
|
|
|
DebugTrace(0, Dbg, "Cannot open object\n", 0);
|
|
|
|
|
|
|
|
try_return( Result = FALSE );
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Check for a read-only Dirent
|
|
|
|
//
|
|
|
|
|
|
|
|
if (FlagOn(DirentAttributes, FAT_DIRENT_ATTR_READ_ONLY)) {
|
|
|
|
|
|
|
|
//
|
|
|
|
// Check the desired access for a read-only dirent. AccessMask will contain
|
|
|
|
// the flags we're going to allow.
|
|
|
|
//
|
|
|
|
|
|
|
|
ACCESS_MASK AccessMask = DELETE | READ_CONTROL | WRITE_OWNER | WRITE_DAC |
|
|
|
|
SYNCHRONIZE | ACCESS_SYSTEM_SECURITY | FILE_READ_DATA |
|
|
|
|
FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES |
|
|
|
|
FILE_WRITE_ATTRIBUTES | FILE_EXECUTE | FILE_LIST_DIRECTORY |
|
|
|
|
FILE_TRAVERSE;
|
|
|
|
|
|
|
|
//
|
|
|
|
// If this is a subdirectory also allow add file/directory and delete.
|
|
|
|
//
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
if (FlagOn(DirentAttributes, FAT_DIRENT_ATTR_DIRECTORY)) {
|
|
|
|
|
|
|
|
AccessMask |= FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE | FILE_DELETE_CHILD;
|
|
|
|
}
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
if (FlagOn(*DesiredAccess, ~AccessMask)) {
|
|
|
|
|
|
|
|
DebugTrace(0, Dbg, "Cannot open readonly\n", 0);
|
|
|
|
|
|
|
|
try_return( Result = FALSE );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try_exit: NOTHING;
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
|
|
|
|
DebugUnwind( FatCheckFileAccess );
|
|
|
|
|
|
|
|
DebugTrace(-1, Dbg, "FatCheckFileAccess -> %08lx\n", Result);
|
|
|
|
} _SEH2_END;
|
|
|
|
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BOOLEAN
|
|
|
|
FatCheckManageVolumeAccess (
|
|
|
|
_In_ PIRP_CONTEXT IrpContext,
|
|
|
|
_In_ PACCESS_STATE AccessState,
|
|
|
|
_In_ KPROCESSOR_MODE ProcessorMode
|
|
|
|
)
|
|
|
|
|
|
|
|
/*++
|
|
|
|
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
This function checks whether the SID described in the input access state has
|
|
|
|
manage volume privilege.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
AccessState - the access state describing the security context to be checked
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
ProcessorMode - the mode this check should occur against
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
|
|
BOOLEAN - TRUE if privilege is held and FALSE otherwise
|
|
|
|
|
|
|
|
--*/
|
|
|
|
|
|
|
|
{
|
|
|
|
PRIVILEGE_SET PrivilegeSet;
|
|
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
|
|
|
|
PrivilegeSet.PrivilegeCount = 1;
|
|
|
|
PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|
|
|
PrivilegeSet.Privilege[0].Luid = RtlConvertLongToLuid( SE_MANAGE_VOLUME_PRIVILEGE );
|
|
|
|
PrivilegeSet.Privilege[0].Attributes = 0;
|
|
|
|
|
|
|
|
if (SePrivilegeCheck( &PrivilegeSet,
|
|
|
|
&AccessState->SubjectSecurityContext,
|
|
|
|
ProcessorMode )) {
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
NTSTATUS
|
|
|
|
FatExplicitDeviceAccessGranted (
|
|
|
|
IN PIRP_CONTEXT IrpContext,
|
|
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
|
|
IN PACCESS_STATE AccessState,
|
|
|
|
IN KPROCESSOR_MODE ProcessorMode
|
|
|
|
)
|
|
|
|
|
|
|
|
/*++
|
|
|
|
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
This function asks whether the SID described in the input access state has
|
|
|
|
been granted any explicit access to the given device object. It does this
|
|
|
|
by acquiring a token stripped of its ability to acquire access via the
|
|
|
|
Everyone SID and re-doing the access check.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
DeviceObject - the device whose ACL will be checked
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
AccessState - the access state describing the security context to be checked
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
ProcessorMode - the mode this check should occur against
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
|
|
NTSTATUS - Indicating whether explicit access was granted.
|
|
|
|
|
|
|
|
--*/
|
|
|
|
|
|
|
|
{
|
|
|
|
NTSTATUS Status;
|
|
|
|
|
|
|
|
PACCESS_TOKEN OriginalAccessToken;
|
|
|
|
PACCESS_TOKEN RestrictedAccessToken;
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
PACCESS_TOKEN *EffectiveToken;
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
ACCESS_MASK GrantedAccess;
|
|
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
|
|
|
|
//
|
|
|
|
// If the access state indicates that specific access other
|
|
|
|
// than traverse was acquired, either Everyone does have such
|
|
|
|
// access or explicit access was granted. In both cases, we're
|
|
|
|
// happy to let this proceed.
|
|
|
|
//
|
|
|
|
|
|
|
|
if (AccessState->PreviouslyGrantedAccess & (SPECIFIC_RIGHTS_ALL ^
|
|
|
|
FILE_TRAVERSE)) {
|
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// If the manage volume privilege is held, this also permits access.
|
|
|
|
//
|
|
|
|
|
|
|
|
if (FatCheckManageVolumeAccess( IrpContext,
|
|
|
|
AccessState,
|
|
|
|
ProcessorMode )) {
|
|
|
|
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Capture the subject context as a prelude to everything below.
|
|
|
|
//
|
|
|
|
|
|
|
|
SeLockSubjectContext( &AccessState->SubjectSecurityContext );
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
//
|
|
|
|
// Convert the token in the subject context into one which does not
|
|
|
|
// acquire access through the Everyone SID.
|
|
|
|
//
|
|
|
|
// The logic for deciding which token is effective comes from
|
|
|
|
// SeQuerySubjectContextToken; since there is no natural way
|
|
|
|
// of getting a pointer to it, do it by hand.
|
|
|
|
//
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
if (ARGUMENT_PRESENT( AccessState->SubjectSecurityContext.ClientToken )) {
|
|
|
|
EffectiveToken = &AccessState->SubjectSecurityContext.ClientToken;
|
|
|
|
} else {
|
|
|
|
EffectiveToken = &AccessState->SubjectSecurityContext.PrimaryToken;
|
|
|
|
}
|
|
|
|
|
|
|
|
OriginalAccessToken = *EffectiveToken;
|
|
|
|
Status = FatCreateRestrictEveryoneToken( OriginalAccessToken, &RestrictedAccessToken );
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
SeReleaseSubjectContext( &AccessState->SubjectSecurityContext );
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Now see if the resulting context has access to the device through
|
|
|
|
// its explicitly granted access. We swap in our restricted token
|
|
|
|
// for this check as the effective client token.
|
|
|
|
//
|
|
|
|
|
|
|
|
*EffectiveToken = RestrictedAccessToken;
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
#ifdef _MSC_VER
|
|
|
|
#pragma prefast( suppress: 28175, "we're a file system, this is ok to touch" )
|
|
|
|
#endif
|
|
|
|
SeAccessCheck( DeviceObject->SecurityDescriptor,
|
|
|
|
&AccessState->SubjectSecurityContext,
|
|
|
|
FALSE,
|
|
|
|
AccessState->OriginalDesiredAccess,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
IoGetFileObjectGenericMapping(),
|
|
|
|
ProcessorMode,
|
|
|
|
&GrantedAccess,
|
|
|
|
&Status );
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
*EffectiveToken = OriginalAccessToken;
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
//
|
|
|
|
// Cleanup and return.
|
|
|
|
//
|
|
|
|
|
|
|
|
SeUnlockSubjectContext( &AccessState->SubjectSecurityContext );
|
|
|
|
ObDereferenceObject( RestrictedAccessToken );
|
|
|
|
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
2021-06-11 12:29:21 +00:00
|
|
|
|
2017-11-23 22:26:10 +00:00
|
|
|
NTSTATUS
|
|
|
|
FatCreateRestrictEveryoneToken (
|
|
|
|
IN PACCESS_TOKEN Token,
|
|
|
|
OUT PACCESS_TOKEN *RestrictedToken
|
|
|
|
)
|
|
|
|
|
|
|
|
/*++
|
|
|
|
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
This function takes a token as the input and returns a new restricted token
|
|
|
|
from which Everyone sid has been disabled. The resulting token may be used
|
|
|
|
to find out if access is available to a user-sid by explicit means.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
Token - Input token from which Everyone sid needs to be deactivated.
|
|
|
|
|
|
|
|
RestrictedToken - Receives the the new restricted token.
|
|
|
|
This must be released using ObDereferenceObject(*RestrictedToken);
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
|
|
NTSTATUS - Returned by SeFilterToken.
|
|
|
|
|
|
|
|
--*/
|
|
|
|
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// Array of sids to disable.
|
|
|
|
//
|
|
|
|
|
|
|
|
TOKEN_GROUPS SidsToDisable;
|
|
|
|
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
|
|
|
|
//
|
|
|
|
// Restricted token will contain the original sids with one change:
|
|
|
|
// If Everyone sid is present in the token, it will be marked for DenyOnly.
|
|
|
|
//
|
|
|
|
|
|
|
|
*RestrictedToken = NULL;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Put Everyone sid in the array of sids to disable. This will mark it
|
|
|
|
// for SE_GROUP_USE_FOR_DENY_ONLY and it'll only be applicable for Deny aces.
|
|
|
|
//
|
|
|
|
|
|
|
|
SidsToDisable.GroupCount = 1;
|
|
|
|
SidsToDisable.Groups[0].Attributes = 0;
|
|
|
|
SidsToDisable.Groups[0].Sid = SeExports->SeWorldSid;
|
|
|
|
|
|
|
|
Status = SeFilterToken(
|
|
|
|
Token, // Token that needs to be restricted.
|
|
|
|
0, // No flags
|
|
|
|
&SidsToDisable, // Disable everyone sid
|
|
|
|
NULL, // Do not create any restricted sids
|
|
|
|
NULL, // Do not delete any privileges
|
|
|
|
RestrictedToken // Restricted token
|
|
|
|
);
|
|
|
|
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|