NT4/private/ntos/se/tokendup.c

946 lines
25 KiB
C
Raw Normal View History

2001-01-01 00:00:00 +01:00
/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
tokendup.c
Abstract:
This module implements the token duplication service.
Author:
Jim Kelly (JimK) 5-April-1990
Environment:
Kernel mode only.
Revision History:
--*/
//#ifndef TOKEN_DEBUG
//#define TOKEN_DEBUG
//#endif
#include "sep.h"
#include "seopaque.h"
#include "tokenp.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,NtDuplicateToken)
#pragma alloc_text(PAGE,SepDuplicateToken)
#pragma alloc_text(PAGE,SepMakeTokenEffectiveOnly)
#pragma alloc_text(PAGE,SeCopyClientToken)
#endif
NTSTATUS
NtDuplicateToken(
IN HANDLE ExistingTokenHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN BOOLEAN EffectiveOnly,
IN TOKEN_TYPE TokenType,
OUT PHANDLE NewTokenHandle
)
/*++
Routine Description:
Create a new token that is a duplicate of an existing token.
Arguments:
ExistingTokenHandle - Is a handle to a token already open for
TOKEN_DUPLICATE access.
DesiredAccess - Is an access mask indicating which access types
are desired to the newly created token. If specified as zero,
the granted access mask of the existing token handle
is used as the desired access mask for the new token.
ObjectAttributes - Points to the standard object attributes data
structure. Refer to the NT Object Management
Specification for a description of this data structure.
If the new token type is TokenImpersonation, then this
parameter may be used to specify the impersonation level
of the new token. If no value is provided, and the source
token is an impersonation token, then the impersonation level
of the source will become that of the target as well. If the
source token is a primary token, then an impersonation level
must be explicitly provided.
If the token being duplicated is an impersonation token, and
an impersonation level is explicitly provided for the target,
then the value provided must not be greater than that of the
source token. For example, an Identification level token can
not be duplicated to produce a Delegation level token.
EffectiveOnly - Is a boolean flag indicating whether the entire
source token should be duplicated into the target token or
just the effective (currently enabled) part of the token.
This provides a means for a caller of a protected subsystem
to limit which privileges and optional groups are made
available to the protected subsystem. A value of TRUE
indicates only the currently enabled parts of the source
token are to be duplicated. Otherwise, the entire source
token is duplicated.
TokenType - Indicates which type of object the new object is to
be created as (primary or impersonation). If you are duplicating
an Impersonation token to produce a Primary token, then
the Impersonation token must have an impersonation level of
either DELEGATE or IMPERSONATE.
NewTokenHandle - Receives the handle of the newly created token.
Return Value:
STATUS_SUCCESS - Indicates the operation was successful.
STATUS_INVALID_PARAMETER - Indicates one or more of the parameter values
was invalid. This value is returned if the target token is not
an impersonation token.
STATUS_BAD_IMPERSONATION_LEVEL - Indicates the impersonation level
requested for the duplicate token is not compatible with the
level of the source token. The duplicate token may not be assigned
a level greater than that of the source token.
--*/
{
PTOKEN Token;
PTOKEN NewToken;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status;
SECURITY_ADVANCED_QUALITY_OF_SERVICE SecurityQos;
BOOLEAN SecurityQosPresent = FALSE;
HANDLE LocalHandle;
OBJECT_HANDLE_INFORMATION HandleInformation;
ACCESS_MASK EffectiveDesiredAccess;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
//
// Probe parameters
//
if (PreviousMode != KernelMode) {
try {
//
// Make sure the TokenType is valid
//
if ( (TokenType < TokenPrimary) && (TokenType > TokenImpersonation) ) {
return(STATUS_INVALID_PARAMETER);
}
//
// Make sure we can write the handle
//
ProbeForWriteHandle(NewTokenHandle);
} except(EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
} // end_try
} //end_if
Status = SeCaptureSecurityQos(
ObjectAttributes,
PreviousMode,
&SecurityQosPresent,
&SecurityQos
);
if (!NT_SUCCESS(Status)) {
return Status;
}
//
// Check the handle's access to the existing token and get
// a pointer to that token. Pick up the default desired
// access mask from the handle while we're at it.
//
Status = ObReferenceObjectByHandle(
ExistingTokenHandle, // Handle
TOKEN_DUPLICATE, // DesiredAccess
SepTokenObjectType, // ObjectType
PreviousMode, // AccessMode
(PVOID *)&Token, // Object
&HandleInformation // GrantedAccess
);
if ( !NT_SUCCESS(Status) ) {
if (SecurityQosPresent) {
SeFreeCapturedSecurityQos( &SecurityQos );
}
return Status;
}
#ifdef TOKEN_DEBUG
////////////////////////////////////////////////////////////////////////////
//
// Debug
SepAcquireTokenReadLock( Token );
DbgPrint("\n");
DbgPrint("\n");
DbgPrint("Token being duplicated: \n");
SepDumpToken( Token );
SepReleaseTokenReadLock( Token );
// Debug
//
////////////////////////////////////////////////////////////////////////////
#endif //TOKEN_DEBUG
//
// Check to see if an alternate desired access mask was provided.
//
if (ARGUMENT_PRESENT(DesiredAccess)) {
EffectiveDesiredAccess = DesiredAccess;
} else {
EffectiveDesiredAccess = HandleInformation.GrantedAccess;
}
//
// If no impersonation level was specified, pick one up from
// the source token.
//
if ( !SecurityQosPresent ) {
SecurityQos.ImpersonationLevel = Token->ImpersonationLevel;
}
if (Token->TokenType == TokenImpersonation) {
//
// Make sure a legitimate transformation is being requested:
//
// (1) The impersonation level of a target duplicate must not
// exceed that of the source token.
//
//
ASSERT( SecurityDelegation > SecurityImpersonation );
ASSERT( SecurityImpersonation > SecurityIdentification );
ASSERT( SecurityIdentification > SecurityAnonymous );
if ( (SecurityQos.ImpersonationLevel > Token->ImpersonationLevel) ) {
ObDereferenceObject( (PVOID)Token );
if (SecurityQosPresent) {
SeFreeCapturedSecurityQos( &SecurityQos );
}
return STATUS_BAD_IMPERSONATION_LEVEL;
}
}
//
// If we are producing a Primary token from an impersonation
// token, then specify an impersonation level of at least
// Impersonate.
//
if ( (Token->TokenType == TokenImpersonation) &&
(TokenType == TokenPrimary) &&
(Token->ImpersonationLevel < SecurityImpersonation)
) {
ObDereferenceObject( (PVOID)Token );
if (SecurityQosPresent) {
SeFreeCapturedSecurityQos( &SecurityQos );
}
return STATUS_BAD_IMPERSONATION_LEVEL;
}
//
// Duplicate the existing token
//
NewToken = NULL;
Status = SepDuplicateToken(
Token,
ObjectAttributes,
EffectiveOnly,
TokenType,
SecurityQos.ImpersonationLevel,
PreviousMode,
&NewToken
);
if (NT_SUCCESS(Status)) {
//
// Insert the new token
//
Status = ObInsertObject( NewToken,
NULL,
EffectiveDesiredAccess,
0,
(PVOID *)NULL,
&LocalHandle
);
if (!NT_SUCCESS( Status )) {
DbgPrint( "SE: ObInsertObject failed (%x) for token at %x\n", Status, NewToken );
}
} else
if (NewToken != NULL) {
DbgPrint( "SE: SepDuplicateToken failed (%x) but allocated token at %x\n", Status, NewToken );
}
//
// We no longer need our reference to the source token
//
ObDereferenceObject( (PVOID)Token );
if (SecurityQosPresent) {
SeFreeCapturedSecurityQos( &SecurityQos );
}
// BUGWARNING Probably need to audit here
//
// Return the new handle
//
if (NT_SUCCESS(Status)) {
try { *NewTokenHandle = LocalHandle; }
except(EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); }
}
return Status;
}
NTSTATUS
SepDuplicateToken(
IN PTOKEN ExistingToken,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN BOOLEAN EffectiveOnly,
IN TOKEN_TYPE TokenType,
IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel OPTIONAL,
IN KPROCESSOR_MODE RequestorMode,
OUT PTOKEN *DuplicateToken
)
/*++
Routine Description:
This routine does the bulk of the work to actually duplicate
a token. This routine assumes all access validation and argument
probing (except the ObjectAttributes) has been performed.
THE CALLER IS RESPONSIBLE FOR CHECKING SUBJECT RIGHTS TO CREATE THE
TYPE OF TOKEN BEING CREATED.
This routine acquires a read lock on the token being duplicated.
Arguments:
ExistingToken - Points to the token to be duplicated.
ObjectAttributes - Points to the standard object attributes data
structure. Refer to the NT Object Management
Specification for a description of this data structure.
The security Quality Of Service of the object attributes are ignored.
This information must be specified using parameters to this
routine.
EffectiveOnly - Is a boolean flag indicating whether the entire
source token should be duplicated into the target token or
just the effective (currently enabled) part of the token.
This provides a means for a caller of a protected subsystem
to limit which privileges and optional groups are made
available to the protected subsystem. A value of TRUE
indicates only the currently enabled parts of the source
token are to be duplicated. Otherwise, the entire source
token is duplicated.
TokenType - Indicates the type of token to make the duplicate token.
ImpersonationLevel - This value specifies the impersonation level
to assign to the duplicate token. If the TokenType of the
duplicate is not TokenImpersonation then this parameter is
ignored. Otherwise, it is must be provided.
RequestorMode - Mode of client requesting the token be duplicated.
DuplicateToken - Receives a pointer to the duplicate token.
The token has not yet been inserted into any object table.
No exceptions are expected when tring to set this OUT value.
Return Value:
STATUS_SUCCESS - The service successfully completed the requested
operation.
--*/
{
NTSTATUS Status;
PTOKEN NewToken;
PULONG DynamicPart;
ULONG PagedPoolSize;
ULONG NonPagedPoolSize;
ULONG TokenBodyLength;
ULONG FieldOffset;
ULONG Index;
PSECURITY_TOKEN_PROXY_DATA NewProxyData;
PSECURITY_TOKEN_AUDIT_DATA NewAuditData;
PAGED_CODE();
ASSERT( sizeof(SECURITY_IMPERSONATION_LEVEL) <= sizeof(ULONG) );
if ( TokenType == TokenImpersonation ) {
ASSERT( SecurityDelegation > SecurityImpersonation );
ASSERT( SecurityImpersonation > SecurityIdentification );
ASSERT( SecurityIdentification > SecurityAnonymous );
if ( (ImpersonationLevel > SecurityDelegation) ||
(ImpersonationLevel < SecurityAnonymous) ) {
return STATUS_BAD_IMPERSONATION_LEVEL;
}
}
//
// Increment the reference count for this logon session
// This can not fail, since there is already a token in this logon
// session.
//
Status = SepReferenceLogonSession( &(ExistingToken->AuthenticationId) );
ASSERT( NT_SUCCESS(Status) );
//
// Note that the size of the dynamic portion of a token can not change
// once established.
//
//
// Allocate the dynamic portion
//
DynamicPart = (PULONG)ExAllocatePoolWithTag(
PagedPool,
ExistingToken->DynamicCharged,
'dTeS'
);
if (DynamicPart == NULL) {
SepDeReferenceLogonSession( &(ExistingToken->AuthenticationId) );
return( STATUS_INSUFFICIENT_RESOURCES );
}
if (ARGUMENT_PRESENT(ExistingToken->ProxyData)) {
Status = SepCopyProxyData(
&NewProxyData,
ExistingToken->ProxyData
);
if (!NT_SUCCESS(Status)) {
SepDeReferenceLogonSession( &(ExistingToken->AuthenticationId) );
ExFreePool( DynamicPart );
return( Status );
}
} else {
NewProxyData = NULL;
}
if (ARGUMENT_PRESENT( ExistingToken->AuditData )) {
NewAuditData = ExAllocatePool( PagedPool, sizeof( SECURITY_TOKEN_AUDIT_DATA ));
if (NewAuditData == NULL) {
SepFreeProxyData( NewProxyData );
SepDeReferenceLogonSession( &(ExistingToken->AuthenticationId) );
ExFreePool( DynamicPart );
return( STATUS_INSUFFICIENT_RESOURCES );
} else {
*NewAuditData = *(ExistingToken->AuditData);
}
} else {
NewAuditData = NULL;
}
//
// Create a new object
//
TokenBodyLength = (ULONG)sizeof(TOKEN) +
ExistingToken->VariableLength;
NonPagedPoolSize = TokenBodyLength;
PagedPoolSize = ExistingToken->DynamicCharged;
Status = ObCreateObject(
RequestorMode, // ProbeMode
SepTokenObjectType, // ObjectType
ObjectAttributes, // ObjectAttributes
RequestorMode, // OwnershipMode
NULL, // ParseContext
TokenBodyLength, // ObjectBodySize
PagedPoolSize, // PagedPoolCharge
NonPagedPoolSize, // NonPagedPoolCharge
(PVOID *)&NewToken // Return pointer to object
);
if (!NT_SUCCESS(Status)) {
SepDeReferenceLogonSession( &(ExistingToken->AuthenticationId) );
ExFreePool( DynamicPart );
SepFreeProxyData( NewProxyData );
if (NewAuditData != NULL) {
ExFreePool( NewAuditData );
}
return Status;
}
//
// acquire exclusive access to the source token
//
SepAcquireTokenReadLock( ExistingToken );
//
// Main Body initialization
//
//
// The following fields are unchanged from the source token.
// Although some may change if EffectiveOnly has been specified.
//
NewToken->AuthenticationId = ExistingToken->AuthenticationId;
NewToken->ModifiedId = ExistingToken->ModifiedId;
NewToken->ExpirationTime = ExistingToken->ExpirationTime;
NewToken->TokenSource = ExistingToken->TokenSource;
NewToken->DynamicCharged = ExistingToken->DynamicCharged;
NewToken->DynamicAvailable = ExistingToken->DynamicAvailable;
NewToken->DefaultOwnerIndex = ExistingToken->DefaultOwnerIndex;
NewToken->UserAndGroupCount = ExistingToken->UserAndGroupCount;
NewToken->PrivilegeCount = ExistingToken->PrivilegeCount;
NewToken->VariableLength = ExistingToken->VariableLength;
NewToken->TokenFlags = ExistingToken->TokenFlags;
NewToken->ProxyData = NewProxyData;
NewToken->AuditData = NewAuditData;
//
// The following fields differ in the new token.
//
ExAllocateLocallyUniqueId( &(NewToken->TokenId) );
NewToken->TokenInUse = FALSE;
NewToken->TokenType = TokenType;
NewToken->ImpersonationLevel = ImpersonationLevel;
//
// Copy and initialize the variable part.
// The variable part is assumed to be position independent.
//
RtlMoveMemory( (PVOID)&(NewToken->VariablePart),
(PVOID)&(ExistingToken->VariablePart),
ExistingToken->VariableLength
);
//
// Set the address of the UserAndGroups array.
//
ASSERT( ARGUMENT_PRESENT(ExistingToken->UserAndGroups ) );
ASSERT( (ULONG)(ExistingToken->UserAndGroups) >=
(ULONG)(&(ExistingToken->VariablePart)) );
FieldOffset = (ULONG)(ExistingToken->UserAndGroups) -
(ULONG)(&(ExistingToken->VariablePart));
NewToken->UserAndGroups =
(PSID_AND_ATTRIBUTES)(FieldOffset + (ULONG)(&(NewToken->VariablePart)) );
//
// Now go through and change the address of each SID pointer
// for the user and groups
//
Index = 0;
while (Index < ExistingToken->UserAndGroupCount) {
FieldOffset = (ULONG)(ExistingToken->UserAndGroups[Index].Sid) -
(ULONG)(&(ExistingToken->VariablePart));
NewToken->UserAndGroups[Index].Sid =
(PSID)( FieldOffset + (ULONG)(&(NewToken->VariablePart)) );
Index += 1;
}
//
// If present, set the address of the privileges
//
if (ExistingToken->PrivilegeCount > 0) {
ASSERT( ARGUMENT_PRESENT(ExistingToken->Privileges ) );
ASSERT( (ULONG)(ExistingToken->Privileges) >=
(ULONG)(&(ExistingToken->VariablePart)) );
FieldOffset = (ULONG)(ExistingToken->Privileges) -
(ULONG)(&(ExistingToken->VariablePart));
NewToken->Privileges = (PLUID_AND_ATTRIBUTES)(
FieldOffset +
(ULONG)(&(NewToken->VariablePart))
);
} else {
NewToken->Privileges = NULL;
}
//
// Copy and initialize the dynamic part.
// The dynamic part is assumed to be position independent.
//
RtlMoveMemory( (PVOID)DynamicPart,
(PVOID)(ExistingToken->DynamicPart),
ExistingToken->DynamicCharged
);
NewToken->DynamicPart = DynamicPart;
//
// If present, set the address of the default Dacl
//
if (ARGUMENT_PRESENT(ExistingToken->DefaultDacl)) {
ASSERT( (ULONG)(ExistingToken->DefaultDacl) >=
(ULONG)(ExistingToken->DynamicPart) );
FieldOffset = (ULONG)(ExistingToken->DefaultDacl) -
(ULONG)(ExistingToken->DynamicPart);
NewToken->DefaultDacl = (PACL)(FieldOffset + (ULONG)DynamicPart);
} else {
NewToken->DefaultDacl = NULL;
}
//
// Set the address of the primary group
//
ASSERT(ARGUMENT_PRESENT(ExistingToken->PrimaryGroup));
ASSERT( (ULONG)(ExistingToken->PrimaryGroup) >=
(ULONG)(ExistingToken->DynamicPart) );
FieldOffset = (ULONG)(ExistingToken->PrimaryGroup) -
(ULONG)(ExistingToken->DynamicPart);
NewToken->PrimaryGroup = (PACL)(FieldOffset + (ULONG)(DynamicPart));
//
// For the time being, take the easy way to generating an "EffectiveOnly"
// duplicate. That is, use the same space required of the original, just
// eliminate any IDs or privileges not active.
//
// Ultimately, if duplication becomes a common operation, then it will be
// worthwhile to recalculate the actual space needed and copy only the
// effective IDs/privileges into the new token.
//
if (EffectiveOnly) {
SepMakeTokenEffectiveOnly( NewToken );
}
#ifdef TOKEN_DEBUG
////////////////////////////////////////////////////////////////////////////
//
// Debug
DbgPrint("\n");
DbgPrint("\n");
DbgPrint("\n");
DbgPrint("Duplicate token:\n");
SepDumpToken( NewToken );
// Debug
//
////////////////////////////////////////////////////////////////////////////
#endif //TOKEN_DEBUG
//
// Release the source token.
//
SepReleaseTokenReadLock( ExistingToken );
(*DuplicateToken) = NewToken;
return Status;
}
VOID
SepMakeTokenEffectiveOnly(
IN PTOKEN Token
)
/*++
Routine Description:
This routine eliminates all but the effective groups and privileges from
a token. It does this by moving elements of the SID and privileges arrays
to overwrite lapsed IDs/privileges, and then reducing the array element
counts. This results in wasted memory within the token object.
One side effect of this routine is that a token that initially had a
default owner ID corresponding to a lapsed group will be changed so
that the default owner ID is the user ID.
THIS ROUTINE MUST BE CALLED ONLY AS PART OF TOKEN CREATION (FOR TOKENS
WHICH HAVE NOT YET BEEN INSERTED INTO AN OBJECT TABLE.) THIS ROUTINE
MODIFIES READ ONLY TOKEN FIELDS.
Note that since we are operating on a token that is not yet visible
to the user, we do not bother acquiring a read lock on the token
being modified.
Arguments:
Token - Points to the token to be made effective only.
Return Value:
None.
--*/
{
ULONG Index;
ULONG ElementCount;
PAGED_CODE();
//
// Walk the privilege array, discarding any lapsed privileges
//
ElementCount = Token->PrivilegeCount;
Index = 0;
while (Index < ElementCount) {
//
// If this privilege is not enabled, replace it with the one at
// the end of the array and reduce the size of the array by one.
// Otherwise, move on to the next entry in the array.
//
if ( !(SepTokenPrivilegeAttributes(Token,Index) & SE_PRIVILEGE_ENABLED)
) {
(Token->Privileges)[Index] =
(Token->Privileges)[ElementCount - 1];
ElementCount -= 1;
} else {
Index += 1;
}
} // endwhile
Token->PrivilegeCount = ElementCount;
//
// Walk the UserAndGroups array (except for the first entry, which is
// the user - and can't be disabled) discarding any lapsed groups.
//
ElementCount = Token->UserAndGroupCount;
ASSERT( ElementCount >= 1 ); // Must be at least a user ID
Index = 1; // Start at the first group, not the user ID.
while (Index < ElementCount) {
//
// If this group is not enabled, replace it with the one at
// the end of the array and reduce the size of the array by one.
//
if ( !(SepTokenGroupAttributes(Token, Index) & SE_GROUP_ENABLED) ){
(Token->UserAndGroups)[Index] =
(Token->UserAndGroups)[ElementCount - 1];
ElementCount -= 1;
} else {
Index += 1;
}
} // endwhile
Token->UserAndGroupCount = ElementCount;
return;
}
NTSTATUS
SeCopyClientToken(
IN PACCESS_TOKEN ClientToken,
IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
IN KPROCESSOR_MODE RequestorMode,
OUT PACCESS_TOKEN *DuplicateToken
)
/*++
Routine Description:
This routine copies a client's token as part of establishing a client
context for impersonation.
The result will be an impersonation token.
No handles to the new token are established.
The token will be an exact duplicate of the source token. It is the
caller's responsibility to ensure an effective only copy of the token
is produced when the token is opened, if necessary.
Arguments:
ClientToken - Points to the token to be duplicated. This may be either
a primary or impersonation token.
ImpersonationLevel - The impersonation level to be assigned to the new
token.
RequestorMode - Mode to be assigned as the owner mode of the new token.
DuplicateToken - Receives a pointer to the duplicate token.
The token has not yet been inserted into any object table.
No exceptions are expected when tring to set this OUT value.
Return Value:
STATUS_SUCCESS - The service successfully completed the requested
operation.
--*/
{
NTSTATUS Status;
OBJECT_ATTRIBUTES ObjectAttributes;
PTOKEN NewToken;
PAGED_CODE();
InitializeObjectAttributes(
&ObjectAttributes,
NULL,
0,
NULL,
NULL
);
Status = SepDuplicateToken(
(PTOKEN)ClientToken, // ExistingToken
&ObjectAttributes, // ObjectAttributes
FALSE, // EffectiveOnly
TokenImpersonation, // TokenType (target)
ImpersonationLevel, // ImpersonationLevel
RequestorMode, // RequestorMode
&NewToken // DuplicateToken
);
(*DuplicateToken) = (PACCESS_TOKEN)NewToken;
return Status;
}