1463 lines
43 KiB
C
1463 lines
43 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
tokenadj.c
|
||
|
||
Abstract:
|
||
|
||
This module implements the services that perform individual adjustments
|
||
on token objects.
|
||
|
||
Author:
|
||
|
||
Jim Kelly (JimK) 15-June-1990
|
||
|
||
Environment:
|
||
|
||
Kernel mode only.
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "pch.h"
|
||
|
||
#pragma hdrstop
|
||
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE,NtAdjustPrivilegesToken)
|
||
#pragma alloc_text(PAGE,NtAdjustGroupsToken)
|
||
#pragma alloc_text(PAGE,SepAdjustPrivileges)
|
||
#pragma alloc_text(PAGE,SepAdjustGroups)
|
||
#endif
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Token Object Routines & Methods //
|
||
// //
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
NTSTATUS
|
||
NtAdjustPrivilegesToken (
|
||
IN HANDLE TokenHandle,
|
||
IN BOOLEAN DisableAllPrivileges,
|
||
IN PTOKEN_PRIVILEGES NewState OPTIONAL,
|
||
IN ULONG BufferLength OPTIONAL,
|
||
OUT PTOKEN_PRIVILEGES PreviousState OPTIONAL,
|
||
OUT PULONG ReturnLength
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
|
||
Routine Description:
|
||
|
||
This routine is used to disable or enable privileges in the
|
||
specified token. The absence of some of the privileges listed to
|
||
be changed won't effect the successful modification of the
|
||
privileges that are in the token. The previous enabled/disabled
|
||
state of changed privileges may optionally be capture (for
|
||
resetting later).
|
||
|
||
TOKEN_ADJUST_PRIVILEGES access is required to enable or disable
|
||
privileges in a token.
|
||
|
||
|
||
Arguments:
|
||
|
||
TokenHandle - Provides a handle to the token to operate on.
|
||
|
||
DisableAllPrivileges - This boolean parameter may be
|
||
used to disable all privileges assigned to the token. If
|
||
this parameter is specified as TRUE, then the NewState parameter is
|
||
ignored.
|
||
|
||
NewState - This (optional) parameter points to a TOKEN_PRIVILEGES
|
||
data structure containing the privileges whose states are to
|
||
be adjusted (disabled or enabled). Only the Enabled flag of
|
||
the attributes associated with each privilege is used. It
|
||
provides the new value that is to be assigned to the
|
||
privilege in the token.
|
||
|
||
BufferLength - This optional parameter indicates the length (in
|
||
bytes) of the PreviousState buffer. This value must be
|
||
provided if the PreviousState parameter is provided.
|
||
|
||
PreviousState - This (optional) parameter points to a buffer to
|
||
receive the state of any privileges actually changed by this
|
||
request. This information is formated as a TOKEN_PRIVILEGES
|
||
data structure which may be passed as the NewState parameter
|
||
in a subsequent call to this routine to restore the original
|
||
state of those privilges. TOKEN_QUERY access is needed to
|
||
use this parameter.
|
||
|
||
If this buffer does not contain enough space to receive the
|
||
complete list of modified privileges, then no privilege
|
||
states are changed and STATUS_BUFFER_TOO_SMALL is returned.
|
||
In this case, the ReturnLength OUT parameter will
|
||
contain the actual number of bytes needed to hold the
|
||
information.
|
||
|
||
ReturnLength - Indicates the actual number of bytes needed to
|
||
contain the previous privilege state information.
|
||
This parameter is ignored if the PreviousState argument is not
|
||
passed.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The service successfully completed the requested
|
||
operation.
|
||
|
||
STATUS_NOT_ALL_ASSIGNED - This NT_SUCCESS severity return status
|
||
indicates that not all the specified privileges are currently
|
||
assigned to the caller. All specified privileges that are
|
||
currently assigned have been successfully adjusted.
|
||
|
||
STATUS_BUFFER_TOO_SMALL - Indicates the optional buffer provided
|
||
to receive the previous states of changed privileges wasn't
|
||
large enough to receive that information. No changes to
|
||
privilege states has been made. The number of bytes needed
|
||
to hold the state change information is returned via the
|
||
ReturnLength parameter.
|
||
|
||
STATUS_INVALID_PARAMETER - Indicates neither the DisableAllPrivileges
|
||
parameter was specified as true, nor was an explicit NewState
|
||
provided.
|
||
|
||
--*/
|
||
|
||
{
|
||
KPROCESSOR_MODE PreviousMode;
|
||
NTSTATUS Status;
|
||
|
||
PTOKEN Token;
|
||
|
||
ACCESS_MASK DesiredAccess;
|
||
|
||
ULONG CapturedPrivilegeCount = 0;
|
||
PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL;
|
||
ULONG CapturedPrivilegesLength = 0;
|
||
|
||
ULONG LocalReturnLength = 0;
|
||
ULONG ChangeCount = 0;
|
||
BOOLEAN ChangesMade = FALSE;
|
||
|
||
ULONG ParameterLength = 0;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// The semantics of the PreviousState parameter leads to a two-pass
|
||
// approach to adjusting privileges. The first pass simply checks
|
||
// to see which privileges will change and counts them. This allows
|
||
// the amount of space needed to be calculated and returned. If
|
||
// the caller's PreviousState return buffer is not large enough, then
|
||
// an error is returned without making any modifications. Otherwise,
|
||
// a second pass is made to actually make the changes.
|
||
//
|
||
//
|
||
|
||
if (!DisableAllPrivileges && !ARGUMENT_PRESENT(NewState)) {
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
//
|
||
// Get previous processor mode and probe parameters if necessary.
|
||
//
|
||
|
||
PreviousMode = KeGetPreviousMode();
|
||
if (PreviousMode != KernelMode) {
|
||
try {
|
||
|
||
//
|
||
// Make sure we can see all of the new state
|
||
//
|
||
|
||
if (!DisableAllPrivileges) {
|
||
|
||
ProbeForReadSmallStructure(
|
||
NewState,
|
||
sizeof(TOKEN_PRIVILEGES),
|
||
sizeof(ULONG)
|
||
);
|
||
|
||
CapturedPrivilegeCount = NewState->PrivilegeCount;
|
||
ParameterLength = (ULONG)sizeof(TOKEN_PRIVILEGES) +
|
||
( (CapturedPrivilegeCount - ANYSIZE_ARRAY) *
|
||
(ULONG)sizeof(LUID_AND_ATTRIBUTES) );
|
||
|
||
ProbeForRead(
|
||
NewState,
|
||
ParameterLength,
|
||
sizeof(ULONG)
|
||
);
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Check the PreviousState buffer for writeability
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
ProbeForWrite(
|
||
PreviousState,
|
||
BufferLength,
|
||
sizeof(ULONG)
|
||
);
|
||
|
||
ProbeForWriteUlong(ReturnLength);
|
||
}
|
||
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
return GetExceptionCode();
|
||
}
|
||
|
||
} else {
|
||
|
||
if (!DisableAllPrivileges) {
|
||
|
||
CapturedPrivilegeCount = NewState->PrivilegeCount;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Capture NewState if passed.
|
||
//
|
||
|
||
if (!DisableAllPrivileges) {
|
||
|
||
try {
|
||
|
||
|
||
Status = SeCaptureLuidAndAttributesArray(
|
||
(NewState->Privileges),
|
||
CapturedPrivilegeCount,
|
||
PreviousMode,
|
||
NULL, 0,
|
||
PagedPool,
|
||
TRUE,
|
||
&CapturedPrivileges,
|
||
&CapturedPrivilegesLength
|
||
);
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
return GetExceptionCode();
|
||
|
||
}
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Reference the token object and validate the caller's right
|
||
// to adjust the privileges.
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
DesiredAccess = (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY);
|
||
} else {
|
||
DesiredAccess = TOKEN_ADJUST_PRIVILEGES;
|
||
}
|
||
|
||
Status = ObReferenceObjectByHandle(
|
||
TokenHandle, // Handle
|
||
DesiredAccess, // DesiredAccess
|
||
SeTokenObjectType, // ObjectType
|
||
PreviousMode, // AccessMode
|
||
(PVOID *)&Token, // Object
|
||
NULL // GrantedAccess
|
||
);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
if (CapturedPrivileges != NULL) {
|
||
SeReleaseLuidAndAttributesArray(
|
||
CapturedPrivileges,
|
||
PreviousMode,
|
||
TRUE
|
||
);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Gain exclusive access to the token.
|
||
//
|
||
|
||
SepAcquireTokenWriteLock( Token );
|
||
|
||
//
|
||
// First pass through the privileges list - just count the changes
|
||
//
|
||
|
||
|
||
Status = SepAdjustPrivileges(
|
||
Token,
|
||
FALSE, // Don't make changes this pass
|
||
DisableAllPrivileges,
|
||
CapturedPrivilegeCount,
|
||
CapturedPrivileges,
|
||
PreviousState,
|
||
&LocalReturnLength,
|
||
&ChangeCount,
|
||
&ChangesMade
|
||
);
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
try {
|
||
|
||
(*ReturnLength) = LocalReturnLength;
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
SepReleaseTokenWriteLock( Token, FALSE );
|
||
ObDereferenceObject( Token );
|
||
|
||
if (CapturedPrivileges != NULL) {
|
||
SeReleaseLuidAndAttributesArray(
|
||
CapturedPrivileges,
|
||
PreviousMode,
|
||
TRUE
|
||
);
|
||
}
|
||
|
||
return GetExceptionCode();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Make sure there is enough room to return any requested
|
||
// information.
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
if (LocalReturnLength > BufferLength) {
|
||
|
||
SepReleaseTokenWriteLock( Token, FALSE );
|
||
ObDereferenceObject( Token );
|
||
|
||
if (CapturedPrivileges != NULL) {
|
||
SeReleaseLuidAndAttributesArray(
|
||
CapturedPrivileges,
|
||
PreviousMode,
|
||
TRUE
|
||
);
|
||
}
|
||
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Second pass through the privileges list - Make the changes.
|
||
//
|
||
// Note that the internal routine attempts to write the previous
|
||
// state directly to the caller's buffer - and so may get an exception.
|
||
//
|
||
|
||
try {
|
||
|
||
Status = SepAdjustPrivileges(
|
||
Token,
|
||
TRUE, // Make the changes this pass
|
||
DisableAllPrivileges,
|
||
CapturedPrivilegeCount,
|
||
CapturedPrivileges,
|
||
PreviousState,
|
||
&LocalReturnLength,
|
||
&ChangeCount,
|
||
&ChangesMade
|
||
);
|
||
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
PreviousState->PrivilegeCount = ChangeCount;
|
||
}
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
SepReleaseTokenWriteLock( Token, TRUE );
|
||
ObDereferenceObject( Token );
|
||
if (CapturedPrivileges != NULL) {
|
||
SeReleaseLuidAndAttributesArray(
|
||
CapturedPrivileges,
|
||
PreviousMode,
|
||
TRUE
|
||
);
|
||
}
|
||
return GetExceptionCode();
|
||
|
||
}
|
||
|
||
|
||
SepReleaseTokenWriteLock( Token, ChangesMade );
|
||
ObDereferenceObject( Token );
|
||
if (CapturedPrivileges != NULL) {
|
||
SeReleaseLuidAndAttributesArray(
|
||
CapturedPrivileges,
|
||
PreviousMode,
|
||
TRUE
|
||
);
|
||
}
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtAdjustGroupsToken (
|
||
IN HANDLE TokenHandle,
|
||
IN BOOLEAN ResetToDefault,
|
||
IN PTOKEN_GROUPS NewState OPTIONAL,
|
||
IN ULONG BufferLength OPTIONAL,
|
||
OUT PTOKEN_GROUPS PreviousState OPTIONAL,
|
||
OUT PULONG ReturnLength
|
||
)
|
||
|
||
/*++
|
||
|
||
|
||
Routine Description:
|
||
|
||
This routine is used to disable or enable groups in the specified
|
||
token. The absence of some of the groups listed to be changed
|
||
won't effect the successful modification of the groups that are in
|
||
the token. The previous enabled/disabled state of changed groups
|
||
may optionally be capture (for resetting later).
|
||
|
||
TOKEN_ADJUST_GROUPS access is required to enable or disable groups
|
||
in a token
|
||
|
||
Note that mandatory groups can not be disabled. An attempt
|
||
disable any mandatory groups will cause the call to fail, leaving
|
||
the state of all groups unchanged.
|
||
|
||
|
||
Arguments:
|
||
|
||
TokenHandle - Provides a handle to the token to operate on.
|
||
|
||
ResetToDefault - The parameter indicates whether all the groups
|
||
in the token are to be reset to their default enabled/disabled
|
||
state.
|
||
|
||
NewState - This parameter points to a TOKEN_GROUPS data structure
|
||
containing the groups whose states are to be adjusted
|
||
(disabled or enabled). Only the Enabled flag of the
|
||
attributes associated with each group is used. It provides
|
||
the new value that is to be assigned to the group in the
|
||
token. If the ResetToDefault argument is specified as TRUE,
|
||
then this argument is ignored. Otherwise, it must be passed.
|
||
|
||
BufferLength - This optional parameter indicates the length (in
|
||
bytes) of the PreviousState buffer. This value must be
|
||
provided if the PreviousState parameter is provided.
|
||
|
||
PreviousState - This (optional) parameter points to a buffer to
|
||
receive the state of any groups actually changed by this
|
||
request. This information is formated as a TOKEN_GROUPS data
|
||
structure which may be passed as the NewState parameter in a
|
||
subsequent call to NtAdjustGroups to restore the original state
|
||
of those groups. TOKEN_QUERY access is needed to use this
|
||
parameter.
|
||
|
||
If this buffer does not contain enough space to receive the
|
||
complete list of modified groups, then no group states are
|
||
changed and STATUS_BUFFER_TOO_SMALL is returned. In this
|
||
case, the ReturnLength return parameter will contain the
|
||
actual number of bytes needed to hold the information.
|
||
|
||
ReturnLength - Indicates the actual number of bytes needed to
|
||
contain the previous group state information.
|
||
This parameter is ignored if the PreviousState argument is not
|
||
passed.
|
||
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The service successfully completed the requested
|
||
operation.
|
||
|
||
STATUS_NOT_ALL_ASSIGNED - This NT_SUCCESS severity return status
|
||
indicates that not all the specified groups are currently
|
||
assigned to the caller. All specified groups that are
|
||
currently assigned have been successfully adjusted.
|
||
|
||
STATUS_CANT_DISABLE_MANDATORY - Indicates an attempt was made to
|
||
disable a mandatory group. The states of all groups remains
|
||
unchanged.
|
||
|
||
STATUS_BUFFER_TOO_SMALL - Indicates the optional buffer provided
|
||
to receive the previous states of changed group wasn't large
|
||
enough to receive that information. No changes to group
|
||
states has been made. The number of bytes needed to hold the
|
||
state change information is returned via the ReturnLength
|
||
parameter.
|
||
|
||
STATUS_INVALID_PARAMETER - Indicates neither the ResetToDefault
|
||
parameter was specified as true, nor was an explicit NewState
|
||
provided.
|
||
|
||
--*/
|
||
{
|
||
|
||
KPROCESSOR_MODE PreviousMode;
|
||
NTSTATUS Status;
|
||
|
||
PTOKEN Token;
|
||
|
||
ACCESS_MASK DesiredAccess;
|
||
|
||
ULONG CapturedGroupCount = 0;
|
||
PSID_AND_ATTRIBUTES CapturedGroups = NULL;
|
||
ULONG CapturedGroupsLength = 0;
|
||
|
||
ULONG LocalReturnLength;
|
||
ULONG ChangeCount;
|
||
BOOLEAN ChangesMade;
|
||
PSID SidBuffer = NULL;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// The semantics of the PreviousState parameter and the
|
||
// STATUS_CANT_DISABLE_MANDATORY completion status leads to a two-pass
|
||
// approach to adjusting groups. The first pass simply checks
|
||
// to see which groups will change and counts them. This allows
|
||
// the amount of space needed to be calculated and returned. If
|
||
// the caller's PreviousState return buffer is not large enough, or
|
||
// one of the specified groups is a mandatory group, then an error
|
||
// is returned without making any modifications. Otherwise, a second
|
||
// pass is made to actually make the changes.
|
||
//
|
||
|
||
if (!ResetToDefault && !ARGUMENT_PRESENT(NewState)) {
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
//
|
||
// Get previous processor mode and probe parameters if necessary.
|
||
//
|
||
|
||
PreviousMode = KeGetPreviousMode();
|
||
if (PreviousMode != KernelMode) {
|
||
try {
|
||
|
||
if (!ResetToDefault) {
|
||
ProbeForReadSmallStructure(
|
||
NewState,
|
||
sizeof(TOKEN_GROUPS),
|
||
sizeof(ULONG)
|
||
);
|
||
}
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
ProbeForWrite(
|
||
PreviousState,
|
||
BufferLength,
|
||
sizeof(ULONG)
|
||
);
|
||
|
||
//
|
||
// This parameter is only used if PreviousState
|
||
// is present
|
||
//
|
||
|
||
ProbeForWriteUlong(ReturnLength);
|
||
|
||
}
|
||
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
return GetExceptionCode();
|
||
}
|
||
}
|
||
|
||
//
|
||
// Capture NewState.
|
||
//
|
||
|
||
if (!ResetToDefault) {
|
||
|
||
try {
|
||
|
||
CapturedGroupCount = NewState->GroupCount;
|
||
Status = SeCaptureSidAndAttributesArray(
|
||
&(NewState->Groups[0]),
|
||
CapturedGroupCount,
|
||
PreviousMode,
|
||
NULL, 0,
|
||
PagedPool,
|
||
TRUE,
|
||
&CapturedGroups,
|
||
&CapturedGroupsLength
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
return GetExceptionCode();
|
||
|
||
} // endtry
|
||
} // endif !ResetToDefault
|
||
|
||
|
||
//
|
||
// Reference the token object and validate the caller's right
|
||
// to adjust the groups.
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
DesiredAccess = (TOKEN_ADJUST_GROUPS | TOKEN_QUERY);
|
||
} else {
|
||
DesiredAccess = TOKEN_ADJUST_GROUPS;
|
||
}
|
||
|
||
Status = ObReferenceObjectByHandle(
|
||
TokenHandle, // Handle
|
||
DesiredAccess, // DesiredAccess
|
||
SeTokenObjectType, // ObjectType
|
||
PreviousMode, // AccessMode
|
||
(PVOID *)&Token, // Object
|
||
NULL // GrantedAccess
|
||
);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
if (ARGUMENT_PRESENT(CapturedGroups)) {
|
||
SeReleaseSidAndAttributesArray( CapturedGroups, PreviousMode, TRUE );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Gain exclusive access to the token.
|
||
//
|
||
|
||
SepAcquireTokenWriteLock( Token );
|
||
|
||
//
|
||
// First pass through the groups list.
|
||
//
|
||
// This pass is always necessary for groups to make sure the caller
|
||
// isn't trying to do anything illegal to mandatory groups.
|
||
//
|
||
|
||
Status = SepAdjustGroups(
|
||
Token,
|
||
FALSE, // Don't make changes this pass
|
||
ResetToDefault,
|
||
CapturedGroupCount,
|
||
CapturedGroups,
|
||
PreviousState,
|
||
NULL, // Not returning SIDs this call
|
||
&LocalReturnLength,
|
||
&ChangeCount,
|
||
&ChangesMade
|
||
);
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
try {
|
||
|
||
(*ReturnLength) = LocalReturnLength;
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
SepReleaseTokenWriteLock( Token, FALSE );
|
||
ObDereferenceObject( Token );
|
||
|
||
if (ARGUMENT_PRESENT(CapturedGroups)) {
|
||
SeReleaseSidAndAttributesArray(
|
||
CapturedGroups,
|
||
PreviousMode,
|
||
TRUE
|
||
);
|
||
}
|
||
|
||
return GetExceptionCode();
|
||
}
|
||
}
|
||
|
||
//
|
||
// Make sure we didn't encounter an error
|
||
//
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
SepReleaseTokenWriteLock( Token, FALSE );
|
||
ObDereferenceObject( Token );
|
||
|
||
if (ARGUMENT_PRESENT(CapturedGroups)) {
|
||
SeReleaseSidAndAttributesArray(
|
||
CapturedGroups,
|
||
PreviousMode,
|
||
TRUE
|
||
);
|
||
}
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
//
|
||
// Make sure there is enough room to return requested information.
|
||
// Also go on to calculate where the SID values go.
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
if (LocalReturnLength > BufferLength) {
|
||
|
||
SepReleaseTokenWriteLock( Token, FALSE );
|
||
ObDereferenceObject( Token );
|
||
|
||
if (ARGUMENT_PRESENT(CapturedGroups)) {
|
||
SeReleaseSidAndAttributesArray(
|
||
CapturedGroups,
|
||
PreviousMode,
|
||
TRUE
|
||
);
|
||
}
|
||
|
||
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
//
|
||
// Calculate where the SIDs can be placed in the PreviousState
|
||
// buffer.
|
||
//
|
||
|
||
SidBuffer = (PSID)(LongAlignPtr(
|
||
(PCHAR)PreviousState + (ULONG)sizeof(TOKEN_GROUPS) +
|
||
(ChangeCount * (ULONG)sizeof(SID_AND_ATTRIBUTES)) -
|
||
(ANYSIZE_ARRAY * (ULONG)sizeof(SID_AND_ATTRIBUTES))
|
||
) );
|
||
|
||
}
|
||
|
||
//
|
||
// Second pass through the groups list.
|
||
//
|
||
|
||
try {
|
||
|
||
Status = SepAdjustGroups(
|
||
Token,
|
||
TRUE, // Make changes in this pass
|
||
ResetToDefault,
|
||
CapturedGroupCount,
|
||
CapturedGroups,
|
||
PreviousState,
|
||
SidBuffer,
|
||
&LocalReturnLength,
|
||
&ChangeCount,
|
||
&ChangesMade
|
||
);
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
PreviousState->GroupCount = ChangeCount;
|
||
}
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
//SepFreeToken( Token, TRUE );
|
||
SepReleaseTokenWriteLock( Token, TRUE );
|
||
ObDereferenceObject( Token );
|
||
if (ARGUMENT_PRESENT(CapturedGroups)) {
|
||
SeReleaseSidAndAttributesArray( CapturedGroups, PreviousMode, TRUE );
|
||
}
|
||
return GetExceptionCode();
|
||
|
||
}
|
||
|
||
//SepFreeToken( Token, ChangesMade );
|
||
SepReleaseTokenWriteLock( Token, ChangesMade );
|
||
ObDereferenceObject( Token );
|
||
|
||
if (ARGUMENT_PRESENT(CapturedGroups)) {
|
||
SeReleaseSidAndAttributesArray( CapturedGroups, PreviousMode, TRUE );
|
||
}
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
NTSTATUS
|
||
SepAdjustPrivileges(
|
||
IN PTOKEN Token,
|
||
IN BOOLEAN MakeChanges,
|
||
IN BOOLEAN DisableAllPrivileges,
|
||
IN ULONG PrivilegeCount OPTIONAL,
|
||
IN PLUID_AND_ATTRIBUTES NewState OPTIONAL,
|
||
OUT PTOKEN_PRIVILEGES PreviousState OPTIONAL,
|
||
OUT PULONG ReturnLength,
|
||
OUT PULONG ChangeCount,
|
||
OUT PBOOLEAN ChangesMade
|
||
)
|
||
|
||
/*++
|
||
|
||
|
||
Routine Description:
|
||
|
||
This routine is used to walk the privileges array in a token as a
|
||
result of a request to adjust privileges.
|
||
|
||
If the MakeChanges parameter is FALSE, this routine simply determines
|
||
what changes are needed and how much space is necessary to save the
|
||
current state of changed privileges.
|
||
|
||
If the MakeChanges parameter is TRUE, this routine will not only
|
||
calculate the space necessary to save the current state, but will
|
||
actually make the changes.
|
||
|
||
This routine makes the following assumptions:
|
||
|
||
1) The token is locked for exclusive access.
|
||
|
||
2) The PrivilegeCount and NewState parameters (if passed) are captured
|
||
and accesses to them will not result in access violations.
|
||
|
||
4) Any access violations encountered may leave the request
|
||
partially completed. It is the calling routine's responsibility
|
||
to catch exceptions.
|
||
|
||
5) The calling routine is responsible for inrementing the token's
|
||
ModifiedId field.
|
||
|
||
Arguments:
|
||
|
||
Token - Pointer to the token to act upon.
|
||
|
||
MakeChanges - A boolean value indicating whether the changes should
|
||
actually be made, or just evaluated. A value of TRUE indicates
|
||
the changes should be made.
|
||
|
||
DisableAllPrivilegs - A boolean value indicating whether all privileges
|
||
are to be disabled, or only select, specified privileges. A value
|
||
of TRUE indicates all privileges are to be disabled.
|
||
|
||
PrivilegeCount - This parameter is required only if the NewState parameter
|
||
is used. In that case, this parameter indicates how many entries are
|
||
in the NewState parameter.
|
||
|
||
NewState - This parameter is ignored if the DisableAllPrivileges
|
||
argument is TRUE. If the DisableAllPrivileges argument is FALSE,
|
||
then this parameter must be provided and specifies the new state
|
||
to set privileges to (enabled or disabled).
|
||
|
||
PreviousState - This (optional) parameter points to a buffer to
|
||
receive the state of any privileges actually changed by this
|
||
request. This information is formated as a TOKEN_PRIVILEGES data
|
||
structure which may be passed as the NewState parameter in a
|
||
subsequent call to NtAdjustPrivileges to restore the original state
|
||
of those privileges. It is the caller's responsibility to make
|
||
sure this buffer is large enough to receive all the state
|
||
information.
|
||
|
||
ReturnLength - Points to a buffer to receive the number of bytes needed
|
||
to retrieve the previous state information of changed privileges.
|
||
This parameter is ignored if the PreviousState argument is not
|
||
passed.
|
||
|
||
ChangeCount - Points to a ULONG to receive the number of privileges
|
||
which were adjusted (or would be adjusted, if changes are made).
|
||
|
||
ChangesMade - Points to a boolean flag which is to receive an indication
|
||
as to whether any changes were made as a result of this call. This
|
||
is expected to be used to decide whether or not to increment the
|
||
token's ModifiedId field.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - Call completed sccessfully.
|
||
|
||
STATUS_NOT_ALL_ASSIGNED - Indicates not all the specified adjustments
|
||
have been made (or could be made, if update wasn't requested).
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS CompletionStatus = STATUS_SUCCESS;
|
||
|
||
ULONG OldIndex;
|
||
ULONG NewIndex;
|
||
BOOLEAN Found;
|
||
ULONG MatchCount = 0;
|
||
|
||
LUID_AND_ATTRIBUTES CurrentPrivilege;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Walk through the privileges array to determine which need to be
|
||
// adjusted.
|
||
//
|
||
|
||
OldIndex = 0;
|
||
(*ChangeCount) = 0;
|
||
|
||
while (OldIndex < Token->PrivilegeCount) {
|
||
|
||
CurrentPrivilege = (Token->Privileges)[OldIndex];
|
||
|
||
if (DisableAllPrivileges) {
|
||
|
||
if (SepTokenPrivilegeAttributes(Token,OldIndex) &
|
||
SE_PRIVILEGE_ENABLED ) {
|
||
|
||
//
|
||
// Change, if necessary (saving previous state if
|
||
// appropriate).
|
||
//
|
||
|
||
if (MakeChanges) {
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
PreviousState->Privileges[(*ChangeCount)] =
|
||
CurrentPrivilege;
|
||
}
|
||
|
||
SepTokenPrivilegeAttributes(Token,OldIndex) &=
|
||
~SE_PRIVILEGE_ENABLED;
|
||
|
||
|
||
|
||
} //endif make changes
|
||
|
||
//
|
||
// increment the number of changes
|
||
//
|
||
|
||
(*ChangeCount) += 1;
|
||
|
||
} // endif privilege enabled
|
||
|
||
} else {
|
||
|
||
//
|
||
// Selective adjustments - this is a little trickier
|
||
// Compare the current privilege to each of those in
|
||
// the NewState array. If a match is found, then adjust
|
||
// the current privilege appropriately.
|
||
//
|
||
|
||
NewIndex = 0;
|
||
Found = FALSE;
|
||
|
||
while ( (NewIndex < PrivilegeCount) && !Found) {
|
||
|
||
//
|
||
// Look for a comparison
|
||
//
|
||
|
||
if (RtlEqualLuid(&CurrentPrivilege.Luid,&NewState[NewIndex].Luid)) {
|
||
|
||
Found = TRUE;
|
||
MatchCount += 1;
|
||
|
||
if ( (SepArrayPrivilegeAttributes( NewState, NewIndex ) &
|
||
SE_PRIVILEGE_ENABLED)
|
||
!=
|
||
(SepTokenPrivilegeAttributes(Token,OldIndex) &
|
||
SE_PRIVILEGE_ENABLED) ) {
|
||
|
||
//
|
||
// Change, if necessary (saving previous state if
|
||
// appropriate).
|
||
//
|
||
|
||
if (MakeChanges) {
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
PreviousState->Privileges[(*ChangeCount)] =
|
||
CurrentPrivilege;
|
||
}
|
||
|
||
SepTokenPrivilegeAttributes(Token,OldIndex) &=
|
||
~(SepTokenPrivilegeAttributes(Token,OldIndex)
|
||
& SE_PRIVILEGE_ENABLED);
|
||
SepTokenPrivilegeAttributes(Token,OldIndex) |=
|
||
(SepArrayPrivilegeAttributes(NewState,NewIndex)
|
||
& SE_PRIVILEGE_ENABLED);
|
||
|
||
//
|
||
// if this is SeChangeNotifyPrivilege, then
|
||
// change its corresponding bit in TokenFlags
|
||
//
|
||
|
||
if (RtlEqualLuid(&CurrentPrivilege.Luid,
|
||
&SeChangeNotifyPrivilege)) {
|
||
Token->TokenFlags ^= TOKEN_HAS_TRAVERSE_PRIVILEGE;
|
||
}
|
||
|
||
|
||
|
||
} //endif make changes
|
||
|
||
//
|
||
// increment the number of changes
|
||
//
|
||
|
||
(*ChangeCount) += 1;
|
||
|
||
|
||
} // endif change needed
|
||
|
||
} // endif found
|
||
|
||
NewIndex += 1;
|
||
|
||
} // endwhile searching NewState
|
||
|
||
} // endelse
|
||
|
||
OldIndex += 1;
|
||
|
||
} // endwhile privileges in token
|
||
|
||
//
|
||
// If we disabled all privileges, then clear the TokenFlags flag
|
||
// corresponding to the SeChangeNotifyPrivilege privilege.
|
||
//
|
||
|
||
|
||
if (DisableAllPrivileges) {
|
||
Token->TokenFlags &= ~TOKEN_HAS_TRAVERSE_PRIVILEGE;
|
||
}
|
||
|
||
//
|
||
// Set completion status appropriately if some not assigned
|
||
//
|
||
|
||
if (!DisableAllPrivileges) {
|
||
|
||
if (MatchCount < PrivilegeCount) {
|
||
CompletionStatus = STATUS_NOT_ALL_ASSIGNED;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Indicate whether changes were made
|
||
//
|
||
|
||
if ((*ChangeCount) > 0 && MakeChanges) {
|
||
(*ChangesMade) = TRUE;
|
||
} else {
|
||
(*ChangesMade) = FALSE;
|
||
}
|
||
|
||
//
|
||
// Calculate the space needed to return previous state information
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
(*ReturnLength) = (ULONG)sizeof(TOKEN_PRIVILEGES) +
|
||
((*ChangeCount) * (ULONG)sizeof(LUID_AND_ATTRIBUTES)) -
|
||
(ANYSIZE_ARRAY * (ULONG)sizeof(LUID_AND_ATTRIBUTES));
|
||
}
|
||
|
||
return CompletionStatus;
|
||
}
|
||
|
||
NTSTATUS
|
||
SepAdjustGroups(
|
||
IN PTOKEN Token,
|
||
IN BOOLEAN MakeChanges,
|
||
IN BOOLEAN ResetToDefault,
|
||
IN ULONG GroupCount,
|
||
IN PSID_AND_ATTRIBUTES NewState OPTIONAL,
|
||
OUT PTOKEN_GROUPS PreviousState OPTIONAL,
|
||
OUT PSID SidBuffer OPTIONAL,
|
||
OUT PULONG ReturnLength,
|
||
OUT PULONG ChangeCount,
|
||
OUT PBOOLEAN ChangesMade
|
||
)
|
||
|
||
/*++
|
||
|
||
|
||
Routine Description:
|
||
|
||
This routine is used to walk the groups array in a token as a
|
||
result of a request to adjust groups.
|
||
|
||
If the MakeChanges parameter is FALSE, this routine simply determines
|
||
what changes are needed and how much space is necessary to save the
|
||
current state of changed groups.
|
||
|
||
If the MakeChanges parameter is TRUE, this routine will not only
|
||
calculate the space necessary to save the current state, but will
|
||
actually make the changes.
|
||
|
||
This routine makes the following assumptions:
|
||
|
||
1) The token is locked for exclusive access.
|
||
|
||
2) The NewState parameter is captured and accesses
|
||
to it will not result in access violations.
|
||
|
||
4) Any access violations encountered may leave the request
|
||
partially completed. It is the calling routine's responsibility
|
||
to catch exceptions.
|
||
|
||
5) The calling routine is responsible for inrementing the token's
|
||
ModifiedId field.
|
||
|
||
Arguments:
|
||
|
||
Token - Pointer to the token to act upon.
|
||
|
||
MakeChanges - A boolean value indicating whether the changes should
|
||
actually be made, or just evaluated. A value of TRUE indicates
|
||
the changes should be made.
|
||
|
||
ResetToDefault - Indicates that the groups are to be reset to their
|
||
default enabled/disabled state.
|
||
|
||
GroupCount - This parameter is required only if the NewState parameter
|
||
is used. In that case, this parameter indicates how many entries are
|
||
in the NewState parameter.
|
||
|
||
NewState - This parameter points to a SID_AND_ATTRIBUTES array
|
||
containing the groups whose states are to be adjusted
|
||
(disabled or enabled). Only the Enabled flag of the
|
||
attributes associated with each group is used. It provides
|
||
the new value that is to be assigned to the group in the
|
||
token. If the ResetToDefault argument is specified as TRUE,
|
||
then this argument is ignored. Otherwise, it must be passed.
|
||
|
||
PreviousState - This (optional) parameter points to a buffer to
|
||
receive the state of any groups actually changed by this
|
||
request. This information is formated as a TOKEN_GROUPS data
|
||
structure which may be passed as the NewState parameter in a
|
||
subsequent call to NtAdjustGroups to restore the original state
|
||
of those groups. It is the caller's responsibility to make
|
||
sure this buffer is large enough to receive all the state
|
||
information.
|
||
|
||
SidBuffer - Pointer to buffer to receive the SID values corresponding
|
||
to the groups returned in the PreviousState argument.
|
||
|
||
ReturnLength - Points to a buffer to receive the number of bytes needed
|
||
to retrieve the previous state information of changed privileges.
|
||
This parameter is ignored if the PreviousState argument is not
|
||
passed.
|
||
|
||
ChangeCount - Points to a ULONG to receive the number of groups
|
||
which were adjusted (or would be adjusted, if changes are made).
|
||
|
||
ChangesMade - Points to a boolean flag which is to receive an indication
|
||
as to whether any changes were made as a result of this call. This
|
||
is expected to be used to decide whether or not to increment the
|
||
token's ModifiedId field.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - Call completed sccessfully.
|
||
|
||
STATUS_NOT_ALL_ASSIGNED - Indicates not all the specified adjustments
|
||
have been made (or could be made, if update wasn't requested).
|
||
|
||
STATUS_CANT_DISABLE_MANDATORY - Not all adjustments were made (or could
|
||
be made, if update not requested) because an attempt was made to
|
||
disable a mandatory group. The state of the groups is left
|
||
in an underterministic state if update was requested.
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS CompletionStatus = STATUS_SUCCESS;
|
||
|
||
ULONG OldIndex;
|
||
ULONG NewIndex;
|
||
ULONG SidLength;
|
||
ULONG LocalReturnLength = 0;
|
||
PSID NextSid;
|
||
BOOLEAN Found;
|
||
ULONG MatchCount = 0;
|
||
BOOLEAN EnableGroup;
|
||
BOOLEAN DisableGroup;
|
||
ULONG TokenGroupAttributes;
|
||
|
||
SID_AND_ATTRIBUTES CurrentGroup;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// NextSid is used to copy group SID values if asked for previous state.
|
||
//
|
||
|
||
NextSid = SidBuffer;
|
||
|
||
|
||
//
|
||
// Walk through the groups array to determine which need to be
|
||
// adjusted.
|
||
//
|
||
|
||
OldIndex = 1; // Don't evaluate the 0th entry (user ID)
|
||
(*ChangeCount) = 0;
|
||
|
||
while (OldIndex < Token->UserAndGroupCount) {
|
||
|
||
CurrentGroup = Token->UserAndGroups[OldIndex];
|
||
|
||
if (ResetToDefault) {
|
||
|
||
TokenGroupAttributes = SepTokenGroupAttributes(Token,OldIndex);
|
||
|
||
//
|
||
// If the group is enabled by default and currently disabled,
|
||
// then we must enable it.
|
||
//
|
||
|
||
EnableGroup = (BOOLEAN)( (TokenGroupAttributes & SE_GROUP_ENABLED_BY_DEFAULT)
|
||
&& !(TokenGroupAttributes & SE_GROUP_ENABLED));
|
||
|
||
//
|
||
// If the group is disabled by default and currently enabled,
|
||
// then we must disable it.
|
||
//
|
||
|
||
DisableGroup = (BOOLEAN)( !(TokenGroupAttributes & SE_GROUP_ENABLED_BY_DEFAULT)
|
||
&& (TokenGroupAttributes & SE_GROUP_ENABLED));
|
||
|
||
//
|
||
// Blow up if it's a mandatory group that is not both
|
||
// enabled by default and enabled (SepCreateToken should
|
||
// make sure that this never happens).
|
||
//
|
||
|
||
ASSERT(!(TokenGroupAttributes & SE_GROUP_MANDATORY)
|
||
|| ((TokenGroupAttributes & (SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED))
|
||
== (SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED)));
|
||
|
||
if ( EnableGroup || DisableGroup ) {
|
||
|
||
SidLength = SeLengthSid( CurrentGroup.Sid );
|
||
SidLength = (ULONG)LongAlignSize(SidLength);
|
||
LocalReturnLength += SidLength;
|
||
|
||
//
|
||
// Change, if necessary (saving previous state if
|
||
// appropriate).
|
||
//
|
||
|
||
if (MakeChanges) {
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
(*(PreviousState)).Groups[(*ChangeCount)].Attributes =
|
||
CurrentGroup.Attributes;
|
||
|
||
(*(PreviousState)).Groups[(*ChangeCount)].Sid =
|
||
NextSid;
|
||
|
||
RtlCopySid( SidLength, NextSid, CurrentGroup.Sid );
|
||
NextSid = (PSID)((ULONG_PTR)NextSid + SidLength);
|
||
}
|
||
|
||
if (EnableGroup) {
|
||
SepTokenGroupAttributes(Token,OldIndex) |= SE_GROUP_ENABLED;
|
||
} else {
|
||
SepTokenGroupAttributes(Token,OldIndex) &= ~SE_GROUP_ENABLED;
|
||
}
|
||
|
||
|
||
|
||
} //endif make changes
|
||
|
||
//
|
||
// increment the number of changes
|
||
//
|
||
|
||
(*ChangeCount) += 1;
|
||
|
||
} // endif group enabled
|
||
|
||
} else {
|
||
|
||
//
|
||
// Selective adjustments - this is a little trickier
|
||
// Compare the current group to each of those in
|
||
// the NewState array. If a match is found, then adjust
|
||
// the current group appropriately.
|
||
//
|
||
|
||
NewIndex = 0;
|
||
Found = FALSE;
|
||
|
||
while ( (NewIndex < GroupCount) && !Found) {
|
||
|
||
//
|
||
// Look for a comparison
|
||
//
|
||
|
||
if (RtlEqualSid(
|
||
CurrentGroup.Sid,
|
||
NewState[NewIndex].Sid
|
||
) ) {
|
||
|
||
Found = TRUE;
|
||
MatchCount += 1;
|
||
|
||
|
||
//
|
||
// See if it needs to be changed
|
||
//
|
||
|
||
if ( (SepArrayGroupAttributes( NewState, NewIndex ) &
|
||
SE_GROUP_ENABLED ) !=
|
||
(SepTokenGroupAttributes(Token,OldIndex) &
|
||
SE_GROUP_ENABLED ) ) {
|
||
|
||
//
|
||
// Make sure group is not mandatory
|
||
//
|
||
|
||
if (SepTokenGroupAttributes(Token,OldIndex) &
|
||
SE_GROUP_MANDATORY ) {
|
||
return STATUS_CANT_DISABLE_MANDATORY;
|
||
}
|
||
|
||
//
|
||
// Make sure group is not deny-only
|
||
//
|
||
|
||
|
||
if (SepTokenGroupAttributes(Token,OldIndex) &
|
||
SE_GROUP_USE_FOR_DENY_ONLY ) {
|
||
return STATUS_CANT_ENABLE_DENY_ONLY;
|
||
}
|
||
|
||
SidLength = SeLengthSid( CurrentGroup.Sid );
|
||
SidLength = (ULONG)LongAlignSize(SidLength);
|
||
LocalReturnLength += SidLength;
|
||
|
||
//
|
||
// Change, if necessary (saving previous state if
|
||
// appropriate).
|
||
//
|
||
|
||
if (MakeChanges) {
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
PreviousState->Groups[(*ChangeCount)].Attributes =
|
||
CurrentGroup.Attributes;
|
||
|
||
PreviousState->Groups[(*ChangeCount)].Sid =
|
||
NextSid;
|
||
|
||
RtlCopySid( SidLength, NextSid, CurrentGroup.Sid );
|
||
|
||
NextSid = (PSID)((ULONG_PTR)NextSid + SidLength);
|
||
}
|
||
|
||
SepTokenGroupAttributes(Token,OldIndex) &=
|
||
~(SepTokenGroupAttributes(Token,OldIndex)
|
||
& SE_GROUP_ENABLED);
|
||
SepTokenGroupAttributes(Token,OldIndex) |=
|
||
(SepArrayGroupAttributes(NewState,NewIndex)
|
||
& SE_GROUP_ENABLED);
|
||
|
||
|
||
|
||
} //endif make changes
|
||
|
||
//
|
||
// increment the number of changes
|
||
//
|
||
|
||
(*ChangeCount) += 1;
|
||
|
||
|
||
} // endif change needed
|
||
|
||
} // endif found
|
||
|
||
NewIndex += 1;
|
||
|
||
} // endwhile searching NewState
|
||
|
||
} // endelse
|
||
|
||
OldIndex += 1;
|
||
|
||
} // endwhile more groups in token
|
||
|
||
//
|
||
// Set completion status appropriately if some not assigned
|
||
//
|
||
|
||
if (!ResetToDefault) {
|
||
|
||
if (MatchCount < GroupCount) {
|
||
CompletionStatus = STATUS_NOT_ALL_ASSIGNED;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Indicate whether changes were made
|
||
//
|
||
|
||
if ((*ChangeCount) > 0 && MakeChanges) {
|
||
(*ChangesMade) = TRUE;
|
||
} else {
|
||
(*ChangesMade) = FALSE;
|
||
}
|
||
|
||
//
|
||
// Calculate the space needed to return previous state information
|
||
// (The SID lengths have already been added up in LocalReturnLength).
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(PreviousState)) {
|
||
|
||
(*ReturnLength) = LocalReturnLength +
|
||
(ULONG)sizeof(TOKEN_GROUPS) +
|
||
((*ChangeCount) * (ULONG)sizeof(SID_AND_ATTRIBUTES)) -
|
||
(ANYSIZE_ARRAY * (ULONG)sizeof(SID_AND_ATTRIBUTES));
|
||
}
|
||
|
||
return CompletionStatus;
|
||
}
|