2362 lines
72 KiB
C
2362 lines
72 KiB
C
/*++
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
token.c
|
|
|
|
Abstract:
|
|
This module implements the initialization, open, duplicate and other services of the executive token object.
|
|
|
|
Author:
|
|
Jim Kelly (JimK) 5-April-1990
|
|
|
|
Environment:
|
|
Kernel mode only.
|
|
|
|
Revision History:
|
|
v15: robertre
|
|
updated ACL_REVISION
|
|
--*/
|
|
|
|
|
|
#include "sep.h"
|
|
#include "sertlp.h"
|
|
#include <zwapi.h>
|
|
#include "tokenp.h"
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(INIT,SepTokenInitialization)
|
|
#pragma alloc_text(INIT,SeMakeSystemToken)
|
|
#pragma alloc_text(INIT,SeMakeAnonymousToken)
|
|
#pragma alloc_text(PAGE,SeTokenType)
|
|
#pragma alloc_text(PAGE,SeTokenIsAdmin)
|
|
#pragma alloc_text(PAGE,SepCreateToken)
|
|
#pragma alloc_text(PAGE,SeTokenImpersonationLevel)
|
|
#pragma alloc_text(PAGE,SeAssignPrimaryToken)
|
|
#pragma alloc_text(PAGE,SeDeassignPrimaryToken)
|
|
#pragma alloc_text(PAGE,SeExchangePrimaryToken)
|
|
#pragma alloc_text(PAGE,SeGetTokenControlInformation)
|
|
#pragma alloc_text(PAGE,SeSubProcessToken)
|
|
#pragma alloc_text(PAGE,NtCreateToken)
|
|
#pragma alloc_text(PAGE,SepTokenDeleteMethod)
|
|
#pragma alloc_text(PAGE,SepIdAssignableAsOwner)
|
|
#pragma alloc_text(PAGE,SeIsChildToken)
|
|
#pragma alloc_text(PAGE,SeIsChildTokenByPointer)
|
|
#pragma alloc_text(PAGE,NtImpersonateAnonymousToken)
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Global Variables //
|
|
|
|
|
|
|
|
|
|
// Generic mapping of access types
|
|
|
|
|
|
GENERIC_MAPPING SepTokenMapping = {TOKEN_READ,
|
|
TOKEN_WRITE,
|
|
TOKEN_EXECUTE,
|
|
TOKEN_ALL_ACCESS
|
|
};
|
|
|
|
|
|
// Address of token object type descriptor.
|
|
|
|
|
|
POBJECT_TYPE SepTokenObjectType;
|
|
|
|
|
|
|
|
// Used to track whether or not a system token has been created or not.
|
|
|
|
|
|
#if DBG
|
|
BOOLEAN SystemTokenCreated = FALSE;
|
|
#endif //DBG
|
|
|
|
|
|
|
|
// Token lock
|
|
|
|
|
|
ERESOURCE SepTokenLock;
|
|
|
|
|
|
|
|
|
|
|
|
// Used to control the active token diagnostic support provided
|
|
|
|
|
|
#ifdef TOKEN_DIAGNOSTICS_ENABLED
|
|
ULONG TokenGlobalFlag = 0;
|
|
#endif // TOKEN_DIAGNOSTICS_ENABLED
|
|
|
|
|
|
// Token Object Routines & Methods //
|
|
|
|
|
|
TOKEN_TYPE SeTokenType(IN PACCESS_TOKEN Token)
|
|
/*++
|
|
Routine Description:
|
|
This function returns the type of an instance of a token (TokenPrimary, or TokenImpersonation).
|
|
Arguments:
|
|
Token - Points to the token whose type is to be returned.
|
|
Return Value:
|
|
The token's type.
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
return (((PTOKEN)Token)->TokenType);
|
|
}
|
|
|
|
|
|
NTKERNELAPI BOOLEAN SeTokenIsAdmin(IN PACCESS_TOKEN Token)
|
|
/*++
|
|
Routine Description:
|
|
Returns if the token is a member of the local admin group.
|
|
Arguments:
|
|
Token - Points to the token.
|
|
Return Value:
|
|
TRUE - Token contains the local admin group
|
|
FALSE - no admin.
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
return ((((PTOKEN)Token)->TokenFlags & TOKEN_HAS_ADMIN_GROUP) != 0);
|
|
}
|
|
|
|
|
|
NTKERNELAPI BOOLEAN SeTokenIsRestricted(IN PACCESS_TOKEN Token)
|
|
/*++
|
|
Routine Description:
|
|
Returns if the token is a restricted token.
|
|
Arguments:
|
|
Token - Points to the token.
|
|
Return Value:
|
|
TRUE - Token contains restricted sids
|
|
FALSE - no admin.
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
return ((((PTOKEN)Token)->TokenFlags & TOKEN_IS_RESTRICTED) != 0);
|
|
}
|
|
|
|
|
|
SECURITY_IMPERSONATION_LEVEL SeTokenImpersonationLevel(IN PACCESS_TOKEN Token)
|
|
/*++
|
|
Routine Description:
|
|
This function returns the impersonation level of a token. The token is assumed to be a TokenImpersonation type token.
|
|
Arguments:
|
|
Token - Points to the token whose impersonation level is to be returned.
|
|
Return Value:
|
|
The token's impersonation level.
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
return ((PTOKEN)Token)->ImpersonationLevel;
|
|
}
|
|
|
|
|
|
VOID
|
|
SeAssignPrimaryToken(
|
|
IN PEPROCESS Process,
|
|
IN PACCESS_TOKEN Token
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
This function establishes a primary token for a process.
|
|
Arguments:
|
|
Token - Points to the new primary token.
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
NTSTATUS
|
|
Status;
|
|
|
|
PTOKEN
|
|
NewToken = (PTOKEN)Token;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(NewToken->TokenType == TokenPrimary);
|
|
ASSERT(!NewToken->TokenInUse);
|
|
|
|
|
|
|
|
// Dereference the old token if there is one.
|
|
|
|
// Processes typically already have a token that must be
|
|
// dereferenced. There are two cases where this may not
|
|
// be the situation. First, during phase 0 system initialization,
|
|
// the initial system process starts out without a token. Second,
|
|
// if an error occurs during process creation, we may be cleaning
|
|
// up a process that hasn't yet had a primary token assigned.
|
|
|
|
if (Process->Token != NULL) {
|
|
SeDeassignPrimaryToken(Process);
|
|
}
|
|
|
|
Process->Token = Token;
|
|
NewToken->TokenInUse = TRUE;
|
|
ObReferenceObject(NewToken);
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
SeDeassignPrimaryToken(
|
|
IN PEPROCESS Process
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
This function causes a process reference to a token to be
|
|
dropped.
|
|
|
|
Arguments:
|
|
Process - Points to the process whose primary token is no longer needed.
|
|
This is probably only the case at process deletion or when
|
|
a primary token is being replaced.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
PTOKEN
|
|
OldToken = (PTOKEN)(Process->Token);
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(OldToken->TokenType == TokenPrimary);
|
|
ASSERT(OldToken->TokenInUse);
|
|
|
|
OldToken->TokenInUse = FALSE;
|
|
ObDereferenceObject(OldToken);
|
|
return;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SeExchangePrimaryToken(
|
|
IN PEPROCESS Process,
|
|
IN PACCESS_TOKEN NewAccessToken,
|
|
OUT PACCESS_TOKEN * OldAccessToken
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
This function is used to perform the portions of changing a primary
|
|
token that reference the internals of token structures.
|
|
|
|
The new token is checked to make sure it is not already in use.
|
|
|
|
The
|
|
|
|
|
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
!!!!!!!! WARNING WARNING WARNING !!!!!!!!
|
|
!!!!!!!! !!!!!!!!
|
|
!!!!!!!! THIS ROUTINE MUST BE CALLED WITH THE GOBAL !!!!!!!!
|
|
!!!!!!!! PROCESS SECURITY FIELDS LOCK HELD !!!!!!!!
|
|
!!!!!!!! !!!!!!!!
|
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
Arguments:
|
|
|
|
Process - Points to the process whose primary token is being exchanged.
|
|
|
|
NewAccessToken - Points to the process's new primary token.
|
|
|
|
OldAccessToken - Receives a pointer to the process's current token.
|
|
The caller is responsible for dereferencing this token when
|
|
it is no longer needed. This can't be done while the process
|
|
security locks are held.
|
|
|
|
Return Value:
|
|
STATUS_SUCCESS - Everything has been updated.
|
|
STATUS_TOKEN_ALREADY_IN_USE - A primary token can only be used by a
|
|
single process. That is, each process must have its own primary
|
|
token. The token passed to be assigned as the primary token is
|
|
already in use as a primary token.
|
|
|
|
STATUS_BAD_TOKEN_TYPE - The new token is not a primary token.
|
|
--*/
|
|
{
|
|
NTSTATUS
|
|
Status;
|
|
|
|
PTOKEN
|
|
OldToken = (PTOKEN)(Process->Token);
|
|
|
|
PTOKEN
|
|
NewToken = (PTOKEN)NewAccessToken;
|
|
|
|
PAGED_CODE();
|
|
|
|
// We need to access the security fields of both tokens.
|
|
// Access to these fields is guarded by the global Process Security
|
|
// fields lock.
|
|
ASSERT(OldToken->TokenType == TokenPrimary);
|
|
ASSERT(OldToken->TokenInUse);
|
|
|
|
// Make sure the new token is a primary token...
|
|
if (NewToken->TokenType != TokenPrimary) {
|
|
return(STATUS_BAD_TOKEN_TYPE);
|
|
}
|
|
|
|
// and that it is not already in use...
|
|
if (NewToken->TokenInUse) {
|
|
return(STATUS_TOKEN_ALREADY_IN_USE);
|
|
}
|
|
|
|
|
|
// Ensure SessionId consistent for hydra
|
|
NewToken->SessionId = OldToken->SessionId;
|
|
|
|
// Switch the tokens
|
|
Process->Token = NewAccessToken;
|
|
NewToken->TokenInUse = TRUE;
|
|
ObReferenceObject(NewToken);
|
|
|
|
// Mark the token as "NOT USED"
|
|
OldToken->TokenInUse = FALSE;
|
|
|
|
// Return the pointer to the old token. The caller
|
|
// is responsible for dereferencing it if they don't need it.
|
|
(*OldAccessToken) = OldToken;
|
|
|
|
return (STATUS_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID SeGetTokenControlInformation(IN PACCESS_TOKEN Token, OUT PTOKEN_CONTROL TokenControl)
|
|
/*++
|
|
Routine Description:
|
|
This routine is provided for communication session layers, or any other executive component that needs to keep track of whether a caller's security context has changed between calls.
|
|
Communication session layers will need to check this, for some security quality of service modes, to determine whether or not a server's security context needs to be updated to reflect
|
|
changes in the client's security context.
|
|
|
|
This routine will also be useful to communications subsystems that need to retrieve client' authentication information from the local security authority in order to perform a remote authentication.
|
|
Parameters:
|
|
Token - Points to the token whose information is to be retrieved.
|
|
TokenControl - Points to the buffer to receive the token control information.
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
// acquire exclusive access to the token
|
|
SepAcquireTokenReadLock((PTOKEN)Token);
|
|
|
|
// Grab the data and run
|
|
TokenControl->TokenId = ((TOKEN *)Token)->TokenId;
|
|
TokenControl->AuthenticationId = ((TOKEN *)Token)->AuthenticationId;
|
|
TokenControl->ModifiedId = ((TOKEN *)Token)->ModifiedId;
|
|
TokenControl->TokenSource = ((TOKEN *)Token)->TokenSource;
|
|
|
|
SepReleaseTokenReadLock((PTOKEN)Token);
|
|
}
|
|
|
|
|
|
PACCESS_TOKEN SeMakeSystemToken()
|
|
/*++
|
|
Routine Description:
|
|
This routine is provided for use by executive components DURING SYSTEM INITIALIZATION ONLY. It creates a token for use by system components.
|
|
|
|
A system token has the following characteristics:
|
|
|
|
- It has LOCAL_SYSTEM as its user ID
|
|
|
|
- It has the following groups with the corresponding
|
|
attributes:
|
|
|
|
ADMINS_ALIAS EnabledByDefault |
|
|
Enabled |
|
|
Owner
|
|
|
|
WORLD EnabledByDefault |
|
|
Enabled |
|
|
Mandatory
|
|
|
|
ADMINISTRATORS (alias) Owner (disabled)
|
|
|
|
AUTHENTICATED_USER
|
|
EnabledByDefault |
|
|
Enabled |
|
|
Mandatory
|
|
|
|
- It has LOCAL_SYSTEM as its primary group.
|
|
- It has the privileges shown in comments below.
|
|
- It has protection that provides TOKEN_ALL_ACCESS to the LOCAL_SYSTEM ID.
|
|
- It has a default ACL that grants GENERIC_ALL access to LOCAL_SYSTEM and GENERIC_EXECUTE to WORLD.
|
|
Parameters:
|
|
None.
|
|
Return Value:
|
|
Pointer to a system token.
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
PVOID Token;
|
|
SID_AND_ATTRIBUTES UserId;
|
|
TOKEN_PRIMARY_GROUP PrimaryGroup;
|
|
PSID_AND_ATTRIBUTES GroupIds;
|
|
ULONG GroupIdsLength;
|
|
LUID_AND_ATTRIBUTES Privileges[30];
|
|
PACL TokenAcl;
|
|
PSID Owner;
|
|
ULONG NormalGroupAttributes;
|
|
ULONG OwnerGroupAttributes;
|
|
ULONG Length;
|
|
OBJECT_ATTRIBUTES TokenObjectAttributes;
|
|
PSECURITY_DESCRIPTOR TokenSecurityDescriptor;
|
|
ULONG BufferLength;
|
|
PVOID Buffer;
|
|
ULONG_PTR GroupIdsBuffer[128 * sizeof(ULONG) / sizeof(ULONG_PTR)];
|
|
TIME_FIELDS TimeFields;
|
|
LARGE_INTEGER NoExpiration;
|
|
|
|
PAGED_CODE();
|
|
|
|
// Make sure only one system token gets created.
|
|
#if DBG
|
|
ASSERT(!SystemTokenCreated);
|
|
SystemTokenCreated = TRUE;
|
|
#endif //DBG
|
|
|
|
// Set up expiration times
|
|
TimeFields.Year = 3000;
|
|
TimeFields.Month = 1;
|
|
TimeFields.Day = 1;
|
|
TimeFields.Hour = 1;
|
|
TimeFields.Minute = 1;
|
|
TimeFields.Second = 1;
|
|
TimeFields.Milliseconds = 1;
|
|
TimeFields.Weekday = 1;
|
|
|
|
RtlTimeFieldsToTime(&TimeFields, &NoExpiration);
|
|
|
|
// // The amount of memory used in the following is gross overkill, but
|
|
// // it is freed up immediately after creating the token.
|
|
|
|
// GroupIds = (PSID_AND_ATTRIBUTES)ExAllocatePool( NonPagedPool, 512 );
|
|
|
|
GroupIds = (PSID_AND_ATTRIBUTES)GroupIdsBuffer;
|
|
|
|
// Set up the attributes to be assigned to groups
|
|
NormalGroupAttributes = (SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED);
|
|
OwnerGroupAttributes = (SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED | SE_GROUP_OWNER);
|
|
|
|
// Set up the user ID
|
|
UserId.Sid = SeLocalSystemSid;
|
|
UserId.Attributes = 0;
|
|
|
|
// Set up the groups
|
|
GroupIds->Sid = SeAliasAdminsSid;
|
|
(GroupIds + 1)->Sid = SeWorldSid;
|
|
(GroupIds + 2)->Sid = SeAuthenticatedUsersSid;
|
|
|
|
GroupIds->Attributes = OwnerGroupAttributes;
|
|
(GroupIds + 1)->Attributes = NormalGroupAttributes;
|
|
(GroupIds + 2)->Attributes = NormalGroupAttributes;
|
|
|
|
GroupIdsLength = (ULONG)LongAlignSize(SeLengthSid(GroupIds->Sid)) + (ULONG)LongAlignSize(SeLengthSid((GroupIds + 1)->Sid)) + (ULONG)LongAlignSize(SeLengthSid((GroupIds + 2)->Sid)) + sizeof(SID_AND_ATTRIBUTES);
|
|
|
|
ASSERT(GroupIdsLength <= 128 * sizeof(ULONG));
|
|
|
|
// Privileges
|
|
|
|
// The privileges in the system token are as follows:
|
|
|
|
// Privilege Name Attributes
|
|
// -------------- ----------
|
|
|
|
// SeTcbPrivilege enabled/enabled by default
|
|
// SeCreateTokenPrivilege DISabled/NOT enabled by default
|
|
// SeTakeOwnershipPrivilege DISabled/NOT enabled by default
|
|
// SeCreatePagefilePrivilege enabled/enabled by default
|
|
// SeLockMemoryPrivilege enabled/enabled by default
|
|
// SeAssignPrimaryTokenPrivilege DISabled/NOT enabled by default
|
|
// SeIncreaseQuotaPrivilege DISabled/NOT enabled by default
|
|
// SeIncreaseBasePriorityPrivilege enabled/enabled by default
|
|
// SeCreatePermanentPrivilege enabled/enabled by default
|
|
// SeDebugPrivilege enabled/enabled by default
|
|
// SeAuditPrivilege enabled/enabled by default
|
|
// SeSecurityPrivilege DISabled/NOT enabled by default
|
|
// SeSystemEnvironmentPrivilege DISabled/NOT enabled by default
|
|
// SeChangeNotifyPrivilege enabled/enabled by default
|
|
// SeBackupPrivilege DISabled/NOT enabled by default
|
|
// SeRestorePrivilege DISabled/NOT enabled by default
|
|
// SeShutdownPrivilege DISabled/NOT enabled by default
|
|
// SeLoadDriverPrivilege DISabled/NOT enabled by default
|
|
// SeProfileSingleProcessPrivilege enabled/enabled by default
|
|
// SeSystemtimePrivilege DISabled/NOT enabled by default
|
|
// SeUndockPrivilege DISabled/NOT enabled by default
|
|
|
|
// The following privileges are not present, and should never be present in
|
|
// the local system token:
|
|
|
|
// SeRemoteShutdownPrivilege no one can come in as local system
|
|
// SeSyncAgentPrivilege only users specified by the admin can
|
|
// be sync agents
|
|
// SeEnableDelegationPrivilege only users specified by the admin can
|
|
// enable delegation on accounts.
|
|
|
|
Privileges[0].Luid = SeTcbPrivilege;
|
|
Privileges[0].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[1].Luid = SeCreateTokenPrivilege;
|
|
Privileges[1].Attributes = 0; // Only the LSA should enable this.
|
|
|
|
Privileges[2].Luid = SeTakeOwnershipPrivilege;
|
|
Privileges[2].Attributes = 0;
|
|
|
|
Privileges[3].Luid = SeCreatePagefilePrivilege;
|
|
Privileges[3].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[4].Luid = SeLockMemoryPrivilege;
|
|
Privileges[4].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[5].Luid = SeAssignPrimaryTokenPrivilege;
|
|
Privileges[5].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[6].Luid = SeIncreaseQuotaPrivilege;
|
|
Privileges[6].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[7].Luid = SeIncreaseBasePriorityPrivilege;
|
|
Privileges[7].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[8].Luid = SeCreatePermanentPrivilege;
|
|
Privileges[8].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[9].Luid = SeDebugPrivilege;
|
|
Privileges[9].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[10].Luid = SeAuditPrivilege;
|
|
Privileges[10].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[11].Luid = SeSecurityPrivilege;
|
|
Privileges[11].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[12].Luid = SeSystemEnvironmentPrivilege;
|
|
Privileges[12].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[13].Luid = SeChangeNotifyPrivilege;
|
|
Privileges[13].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[14].Luid = SeBackupPrivilege;
|
|
Privileges[14].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[15].Luid = SeRestorePrivilege;
|
|
Privileges[15].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[16].Luid = SeShutdownPrivilege;
|
|
Privileges[16].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[17].Luid = SeLoadDriverPrivilege;
|
|
Privileges[17].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[18].Luid = SeProfileSingleProcessPrivilege;
|
|
Privileges[18].Attributes =
|
|
(SE_PRIVILEGE_ENABLED_BY_DEFAULT | // Enabled by default
|
|
SE_PRIVILEGE_ENABLED); // Enabled
|
|
|
|
Privileges[19].Luid = SeSystemtimePrivilege;
|
|
Privileges[19].Attributes = 0; // disabled, not enabled by default
|
|
|
|
Privileges[20].Luid = SeUndockPrivilege;
|
|
Privileges[20].Attributes = 0; // disabled, not enabled by default
|
|
|
|
//BEFORE ADDING ANOTHER PRIVILEGE ^^ HERE ^^ CHECK THE ARRAY BOUND
|
|
//ALSO INCREMENT THE PRIVILEGE COUNT IN THE SepCreateToken() call
|
|
|
|
|
|
// Establish the primary group and default owner
|
|
PrimaryGroup.PrimaryGroup = SeLocalSystemSid; // Primary group
|
|
Owner = SeAliasAdminsSid; // Default owner
|
|
|
|
// Set up an ACL to protect token as well ...
|
|
// give system full reign of terror. This includes user-mode components
|
|
// running as part of the system.
|
|
Length = (ULONG)sizeof(ACL) + ((ULONG)sizeof(ACCESS_ALLOWED_ACE) - sizeof(ULONG)) + SeLengthSid(SeLocalSystemSid);
|
|
|
|
TokenAcl = (PACL)ExAllocatePoolWithTag(PagedPool, Length, 'cAeS');
|
|
if (TokenAcl == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Status = RtlCreateAcl(TokenAcl, Length, ACL_REVISION2);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = RtlAddAccessAllowedAce(TokenAcl, ACL_REVISION2, TOKEN_ALL_ACCESS, SeLocalSystemSid);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
TokenSecurityDescriptor = (PSECURITY_DESCRIPTOR)ExAllocatePoolWithTag(PagedPool, sizeof(SECURITY_DESCRIPTOR), 'dSeS');
|
|
if (TokenSecurityDescriptor == NULL) {
|
|
ExFreePool(TokenAcl);
|
|
return NULL;
|
|
}
|
|
|
|
Status = RtlCreateSecurityDescriptor(TokenSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = RtlSetDaclSecurityDescriptor(TokenSecurityDescriptor, TRUE, TokenAcl, FALSE);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = RtlSetOwnerSecurityDescriptor(
|
|
TokenSecurityDescriptor,
|
|
SeAliasAdminsSid,
|
|
FALSE // Owner defaulted
|
|
);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = RtlSetGroupSecurityDescriptor(
|
|
TokenSecurityDescriptor,
|
|
SeAliasAdminsSid,
|
|
FALSE // Group defaulted
|
|
);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
// Create the system token
|
|
|
|
#ifdef TOKEN_DEBUG
|
|
// Debug
|
|
DbgPrint("\n Creating system token...\n");
|
|
// Debug
|
|
#endif //TOKEN_DEBUG
|
|
|
|
InitializeObjectAttributes(&TokenObjectAttributes, NULL, 0, NULL, TokenSecurityDescriptor);
|
|
|
|
ASSERT(SeSystemDefaultDacl != NULL);
|
|
Status = SepCreateToken(
|
|
(PHANDLE)&Token,
|
|
KernelMode,
|
|
0, // No handle created for system token
|
|
&TokenObjectAttributes,
|
|
TokenPrimary,
|
|
(SECURITY_IMPERSONATION_LEVEL)0,
|
|
&SeSystemAuthenticationId,
|
|
&NoExpiration,
|
|
&UserId,
|
|
3, // GroupCount
|
|
GroupIds,
|
|
GroupIdsLength,
|
|
21, // privileges
|
|
Privileges,
|
|
sizeof(Privileges),
|
|
Owner,
|
|
PrimaryGroup.PrimaryGroup,
|
|
SeSystemDefaultDacl,
|
|
&SeSystemTokenSource,
|
|
TRUE, // System token
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
// Assign the security descriptor here, since we don't do it
|
|
// in SepCreateToken for the System Token.
|
|
BufferLength = Length + sizeof(SECURITY_DESCRIPTOR_RELATIVE) + 2 * SeLengthSid(SeAliasAdminsSid);
|
|
|
|
Buffer = (PSECURITY_DESCRIPTOR)ExAllocatePoolWithTag(PagedPool, BufferLength, 'dSeS');
|
|
if (Buffer) {
|
|
Status = RtlAbsoluteToSelfRelativeSD(TokenSecurityDescriptor, Buffer, &BufferLength);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
Status = ObAssignObjectSecurityDescriptor(Token, Buffer, PagedPool);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
} else {
|
|
ObDereferenceObject(Token);
|
|
Token = NULL;
|
|
}
|
|
|
|
// We can free the old one now.
|
|
ExFreePool(TokenAcl);
|
|
ExFreePool(TokenSecurityDescriptor);
|
|
|
|
return (PACCESS_TOKEN)Token;
|
|
}
|
|
|
|
|
|
PACCESS_TOKEN SeMakeAnonymousLogonToken(VOID)
|
|
/*++
|
|
Routine Description:
|
|
This routine is provided for use by executive components DURING SYSTEM INITIALIZATION ONLY.
|
|
It creates a token for use by system components.
|
|
|
|
A system token has the following characteristics:
|
|
|
|
- It has ANONYMOUS_LOGON as its user ID
|
|
|
|
- It has the following groups with the corresponding
|
|
attributes:
|
|
|
|
WORLD EnabledByDefault |
|
|
Enabled |
|
|
Mandatory
|
|
|
|
- It has WORLD as its primary group.
|
|
- It has no privileges/
|
|
- It has protection that provides TOKEN_ALL_ACCESS to the WORLD ID.
|
|
- It has a default ACL that grants GENERIC_ALL access to WORLD.
|
|
Parameters:
|
|
None.
|
|
Return Value:
|
|
Pointer to a system token.
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
PVOID Token;
|
|
SID_AND_ATTRIBUTES UserId;
|
|
PSID_AND_ATTRIBUTES GroupIds;
|
|
TOKEN_PRIMARY_GROUP PrimaryGroup;
|
|
ULONG GroupIdsLength;
|
|
PACL TokenAcl;
|
|
PSID Owner;
|
|
ULONG NormalGroupAttributes;
|
|
ULONG Length;
|
|
OBJECT_ATTRIBUTES TokenObjectAttributes;
|
|
PSECURITY_DESCRIPTOR TokenSecurityDescriptor;
|
|
ULONG BufferLength;
|
|
PVOID Buffer;
|
|
ULONG_PTR GroupIdsBuffer[128 * sizeof(ULONG) / sizeof(ULONG_PTR)];
|
|
TIME_FIELDS TimeFields;
|
|
LARGE_INTEGER NoExpiration;
|
|
|
|
PAGED_CODE();
|
|
|
|
// Set up expiration times
|
|
TimeFields.Year = 3000;
|
|
TimeFields.Month = 1;
|
|
TimeFields.Day = 1;
|
|
TimeFields.Hour = 1;
|
|
TimeFields.Minute = 1;
|
|
TimeFields.Second = 1;
|
|
TimeFields.Milliseconds = 1;
|
|
TimeFields.Weekday = 1;
|
|
|
|
RtlTimeFieldsToTime(&TimeFields, &NoExpiration);
|
|
|
|
GroupIds = (PSID_AND_ATTRIBUTES)GroupIdsBuffer;
|
|
|
|
// Set up the attributes to be assigned to groups
|
|
NormalGroupAttributes = (SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED);
|
|
|
|
// Set up the user ID
|
|
UserId.Sid = SeAnonymousLogonSid;
|
|
UserId.Attributes = 0;
|
|
|
|
// Set up the groups
|
|
GroupIds->Sid = SeWorldSid;
|
|
GroupIds->Attributes = NormalGroupAttributes;
|
|
|
|
GroupIdsLength = (ULONG)LongAlignSize(SeLengthSid(GroupIds->Sid)) + sizeof(SID_AND_ATTRIBUTES);
|
|
|
|
ASSERT(GroupIdsLength <= 128 * sizeof(ULONG));
|
|
|
|
// Privileges
|
|
|
|
// Establish the primary group and default owner
|
|
PrimaryGroup.PrimaryGroup = SeAnonymousLogonSid; // Primary group
|
|
|
|
// Set up an ACL to protect token as well ...
|
|
// give system full reign of terror. This includes user-mode components
|
|
// running as part of the system.
|
|
Length = (ULONG)sizeof(ACL) + (ULONG)sizeof(ACCESS_ALLOWED_ACE) + SeLengthSid(SeWorldSid) + 8; // The 8 is just for good measure
|
|
ASSERT(Length < 200);
|
|
|
|
TokenAcl = (PACL)ExAllocatePoolWithTag(PagedPool, 200, 'cAeS');
|
|
if (!TokenAcl) {
|
|
return NULL;
|
|
}
|
|
|
|
Status = RtlCreateAcl(TokenAcl, Length, ACL_REVISION2);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = RtlAddAccessAllowedAce(TokenAcl, ACL_REVISION2, TOKEN_ALL_ACCESS, SeWorldSid);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
TokenSecurityDescriptor = (PSECURITY_DESCRIPTOR)ExAllocatePoolWithTag(PagedPool, SECURITY_DESCRIPTOR_MIN_LENGTH, 'dSeS');
|
|
if (!TokenSecurityDescriptor) {
|
|
ExFreePool(TokenAcl);
|
|
return NULL;
|
|
}
|
|
|
|
Status = RtlCreateSecurityDescriptor(TokenSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = RtlSetDaclSecurityDescriptor(TokenSecurityDescriptor, TRUE, TokenAcl, FALSE);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = RtlSetOwnerSecurityDescriptor(
|
|
TokenSecurityDescriptor,
|
|
SeWorldSid,
|
|
FALSE // Owner defaulted
|
|
);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = RtlSetGroupSecurityDescriptor(
|
|
TokenSecurityDescriptor,
|
|
SeWorldSid,
|
|
FALSE // Group defaulted
|
|
);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
// Create the system token
|
|
|
|
#ifdef TOKEN_DEBUG
|
|
// Debug
|
|
DbgPrint("\n Creating system token...\n");
|
|
// Debug
|
|
#endif //TOKEN_DEBUG
|
|
|
|
InitializeObjectAttributes(&TokenObjectAttributes, NULL, 0, NULL, TokenSecurityDescriptor);
|
|
|
|
Status = SepCreateToken(
|
|
(PHANDLE)&Token,
|
|
KernelMode,
|
|
0, // No handle created for system token
|
|
&TokenObjectAttributes,
|
|
TokenPrimary,
|
|
(SECURITY_IMPERSONATION_LEVEL)0,
|
|
&SeAnonymousAuthenticationId,
|
|
&NoExpiration,
|
|
&UserId,
|
|
1, // GroupCount
|
|
GroupIds,
|
|
GroupIdsLength,
|
|
0, // no privileges
|
|
NULL, // no Privileges,
|
|
0, // no privileges
|
|
NULL,
|
|
PrimaryGroup.PrimaryGroup,
|
|
TokenAcl,
|
|
&SeSystemTokenSource,
|
|
TRUE, // System token
|
|
NULL,
|
|
NULL
|
|
);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
// Assign the security descriptor here, since we don't do it
|
|
// in SepCreateToken for the System Token.
|
|
|
|
|
|
BufferLength = Length + sizeof(SECURITY_DESCRIPTOR) + 2 * SeLengthSid(SeAliasAdminsSid);
|
|
Buffer = (PSECURITY_DESCRIPTOR)ExAllocatePoolWithTag(PagedPool, BufferLength, 'dSeS');
|
|
if (Buffer) {
|
|
Status = RtlAbsoluteToSelfRelativeSD(TokenSecurityDescriptor, Buffer, &BufferLength);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Status = ObAssignObjectSecurityDescriptor(Token, Buffer, PagedPool);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
} else {
|
|
ObDereferenceObject(Token);
|
|
Token = NULL;
|
|
}
|
|
|
|
// We can free the old one now.
|
|
ExFreePool(TokenAcl);
|
|
ExFreePool(TokenSecurityDescriptor);
|
|
return (PACCESS_TOKEN)Token;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SeSubProcessToken(
|
|
IN PEPROCESS ParentProcess,
|
|
OUT PACCESS_TOKEN * ChildToken
|
|
)
|
|
|
|
/*++
|
|
Routine Description:
|
|
This routine makes a token for a sub-process that is a duplicate
|
|
of the parent process's token.
|
|
|
|
Parameters:
|
|
ParentProcess - Pointer to the parent process object. This is used
|
|
to locate the parent process's primary token, and for logging
|
|
purposes.
|
|
ChildToken - Receives a pointer to the child process's token.
|
|
Return Value:
|
|
STATUS_SUCCESS - Indicates the sub-process's token has been created
|
|
successfully.
|
|
Other status values may be returned from memory allocation or object
|
|
creation services used and typically indicate insufficient resources
|
|
or quota on the requestor's part.
|
|
--*/
|
|
{
|
|
// NOTE: THIS ROUTINE CAN BE MADE MUCH MORE EFFICIENT.
|
|
// IT IS DONE IN A BRUTE FORCE FASHION FOR THE LARGE_INTEGER
|
|
// BEING TO GET THINGS UP AND RUNNING.
|
|
|
|
// THE PERFORMANCE OF THIS ROUTINE DIRECTLY IMPACTS
|
|
// THE PERFORMANCE OF SUB-PROCESS CREATION.
|
|
|
|
KPROCESSOR_MODE PreviousMode;
|
|
PTOKEN ParentToken;
|
|
PTOKEN NewToken;
|
|
HANDLE NewTokenHandle;
|
|
OBJECT_ATTRIBUTES PrimaryTokenAttributes;
|
|
|
|
PTOKEN InsertedToken;
|
|
|
|
NTSTATUS Status;
|
|
NTSTATUS IgnoreStatus;
|
|
|
|
PAGED_CODE();
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
InitializeObjectAttributes(
|
|
&PrimaryTokenAttributes,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
#ifdef TOKEN_DEBUG
|
|
DbgPrint("\nCreating sub-process token...\n");
|
|
DbgPrint("Parent token address = 0x%lx\n", ParentProcess->Token);
|
|
#endif //TOKEN_DEBUG
|
|
|
|
ParentToken = (PTOKEN)PsReferencePrimaryToken(ParentProcess);
|
|
|
|
Status = SepDuplicateToken(
|
|
ParentToken, // ExistingToken
|
|
&PrimaryTokenAttributes, // ObjectAttributes
|
|
FALSE, // EffectiveOnly
|
|
TokenPrimary, // TokenType
|
|
(SECURITY_IMPERSONATION_LEVEL)0, // ImpersonationLevel
|
|
KernelMode, // RequestorMode
|
|
&NewToken // NewToken
|
|
);
|
|
|
|
PsDereferencePrimaryToken((PACCESS_TOKEN)ParentToken);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
// Insert the new token object, up its ref count, and then
|
|
// delete the new handle.
|
|
Status = ObInsertObject(
|
|
NewToken,
|
|
NULL,
|
|
0,
|
|
1, // ObjectPointerBias
|
|
(PVOID *)&InsertedToken,
|
|
&NewTokenHandle
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
*ChildToken = InsertedToken;
|
|
InsertedToken->TokenInUse = TRUE;
|
|
IgnoreStatus = ZwClose(NewTokenHandle);
|
|
|
|
// At this point, either the token has a reference
|
|
// outstanding (and no handles), or the reference
|
|
// failed. If the reference failed, the status will
|
|
// be returned indicating why.
|
|
} else {
|
|
// ObInsertObject dereferences the passed object if it
|
|
// fails, so we don't have to do any cleanup on NewToken
|
|
// here.
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
SepTokenInitialization(VOID)
|
|
/*++
|
|
Routine Description:
|
|
|
|
This function creates the token object type descriptor at system
|
|
initialization and stores the address of the object type descriptor
|
|
in global storage. It also created token related global variables.
|
|
|
|
Furthermore, some number of pseudo tokens are created during system
|
|
initialization. These tokens are tracked down and replaced with
|
|
real tokens.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
A value of TRUE is returned if the object type descriptor is
|
|
successfully initialized. Otherwise a value of FALSE is returned.
|
|
--*/
|
|
{
|
|
OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
|
|
NTSTATUS Status;
|
|
UNICODE_STRING TypeName;
|
|
|
|
PAGED_CODE();
|
|
|
|
// Initialize string descriptor.
|
|
RtlInitUnicodeString(&TypeName, L"Token");
|
|
|
|
// Create the global token lock
|
|
ExInitializeResource(&SepTokenLock);
|
|
|
|
#if 0
|
|
BUG, BUG Need to get system default ACL to protect token object
|
|
#endif
|
|
|
|
// Create object type descriptor.
|
|
|
|
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
|
|
ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
|
|
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
|
|
ObjectTypeInitializer.GenericMapping = SepTokenMapping;
|
|
ObjectTypeInitializer.SecurityRequired = TRUE;
|
|
ObjectTypeInitializer.UseDefaultObject = TRUE;
|
|
ObjectTypeInitializer.PoolType = PagedPool;
|
|
ObjectTypeInitializer.ValidAccessMask = TOKEN_ALL_ACCESS;
|
|
ObjectTypeInitializer.DeleteProcedure = SepTokenDeleteMethod;
|
|
|
|
Status = ObCreateObjectType(&TypeName,
|
|
&ObjectTypeInitializer,
|
|
(PSECURITY_DESCRIPTOR)NULL, // BUG, BUG assign real protection
|
|
&SepTokenObjectType
|
|
);
|
|
|
|
|
|
#if 0
|
|
BUG, BUG Now track down all pseudo tokens used during system initialization
|
|
BUG, BUG and replace them with real ones.
|
|
#endif
|
|
|
|
|
|
// If the object type descriptor was successfully created, then
|
|
// return a value of TRUE. Otherwise return a value of FALSE.
|
|
return (BOOLEAN)NT_SUCCESS(Status);
|
|
}
|
|
|
|
|
|
// Temporary, for Debug only //
|
|
|
|
|
|
#ifdef TOKEN_DEBUG
|
|
VOID
|
|
SepDumpToken(
|
|
IN PTOKEN T
|
|
)
|
|
{
|
|
ULONG Index;
|
|
|
|
// Dump a token
|
|
|
|
DbgPrint("\n");
|
|
|
|
DbgPrint(" address: 0x%lx \n", ((ULONG)T));
|
|
|
|
DbgPrint(" TokenId: (0x%lx, 0x%lx) \n",
|
|
T->TokenId.HighPart, T->TokenId.LowPart);
|
|
|
|
if ((T->AuthenticationId.Data[0] == SeSystemAuthenticationId.Data[0]) &&
|
|
(T->AuthenticationId.Data[1] == SeSystemAuthenticationId.Data[1]) &&
|
|
(T->AuthenticationId.Data[2] == SeSystemAuthenticationId.Data[2]) &&
|
|
(T->AuthenticationId.Data[3] == SeSystemAuthenticationId.Data[3])) {
|
|
|
|
DbgPrint(" AuthenticationId: SeSystemAuthenticationId \n");
|
|
|
|
} else {
|
|
|
|
DbgPrint(" AuthenticationId: (0x%lx, 0x%lx, 0x%lx, 0x%lx) \n",
|
|
T->AuthenticationId.Data[0],
|
|
T->AuthenticationId.Data[1],
|
|
T->AuthenticationId.Data[2],
|
|
T->AuthenticationId.Data[3]);
|
|
}
|
|
|
|
DbgPrint(" ExpirationTime: 0x%lx, 0x%lx \n",
|
|
T->ExpirationTime.HighPart,
|
|
T->ExpirationTime.LowPart);
|
|
|
|
if (T->TokenType == TokenPrimary) {
|
|
DbgPrint(" TokenType: Primary \n");
|
|
} else {
|
|
if (T->TokenType == TokenImpersonation) {
|
|
DbgPrint(" TokenType: Impersonation \n");
|
|
} else {
|
|
DbgPrint(" TokenType: (Unknown type, value = 0x%lx) \n",
|
|
((ULONG)T - TokenType));
|
|
}
|
|
}
|
|
|
|
DbgPrint(" ImpersonationLevel: 0x%lx \n",
|
|
((ULONG)T->ImpersonationLevel));
|
|
|
|
DbgPrint(" TokenSource: (not yet provided) \n");
|
|
DbgPrint(" DynamicCharged: 0x%lx \n", T->DynamicCharged);
|
|
DbgPrint(" UserAndGroupCount: 0x%lx \n", T->UserAndGroupCount);
|
|
DbgPrint(" PrivilegeCount: 0x%lx \n", T->PrivilegeCount);
|
|
DbgPrint(" VariableLength: 0x%lx \n", T->VariableLength);
|
|
|
|
|
|
DbgPrint(" ModifiedId: (0x%lx, 0x%lx) \n",
|
|
T->ModifiedId.HighPart,
|
|
T->ModifiedId.LowPart);
|
|
DbgPrint(" DynamicAvailable: 0x%lx \n", T->DynamicAvailable);
|
|
DbgPrint(" DefaultOwnerIndex: 0x%lx \n", T->DefaultOwnerIndex);
|
|
|
|
|
|
DbgPrint(" Address of DynamicPart: 0x%lx \n",
|
|
(*(PULONG)((PVOID)(&(T->DynamicPart)))));
|
|
DbgPrint(" Address of Default DACL: 0x%lx \n",
|
|
(*(PULONG)((PVOID)(&(T->DefaultDacl)))));
|
|
|
|
DbgPrint(" Address Of Variable Part: 0x%lx \n",
|
|
&(T->VariablePart));
|
|
|
|
DbgPrint("\n");
|
|
DbgPrint(" PrimaryGroup:\n");
|
|
DbgPrint(" Address: 0x%lx \n",
|
|
(*(PULONG)((PVOID)(&(T->PrimaryGroup)))));
|
|
DbgPrint(" Length: 0x%lx \n",
|
|
SeLengthSid((T->PrimaryGroup)));
|
|
DbgPrint("\n");
|
|
DbgPrint(" UserAndGroups: 0x%lx \n",
|
|
(*(PULONG)((PVOID)(&(T->UserAndGroups)))));
|
|
DbgPrint(" User ID - \n");
|
|
DbgPrint(" Address: 0x%lx \n",
|
|
(*(PULONG)((PVOID)(&(T->UserAndGroups[0].Sid)))));
|
|
DbgPrint(" Attributes: 0x%lx \n",
|
|
(T->UserAndGroups[0].Attributes));
|
|
DbgPrint(" Length: 0x%lx \n",
|
|
SeLengthSid((T->UserAndGroups[0].Sid)));
|
|
Index = 1;
|
|
while (Index < T->UserAndGroupCount) {
|
|
DbgPrint(" Group 0x%lx - \n", Index);
|
|
DbgPrint(" Address: 0x%lx \n",
|
|
(*(PULONG)((PVOID)(&(T->UserAndGroups[Index].Sid)))));
|
|
DbgPrint(" Attributes: 0x%lx \n",
|
|
(T->UserAndGroups[Index].Attributes));
|
|
DbgPrint(" Length: 0x%lx \n",
|
|
SeLengthSid((T->UserAndGroups[Index].Sid)));
|
|
Index += 1;
|
|
}
|
|
|
|
Index = 0;
|
|
while (Index < T->RestrictedSidCount) {
|
|
DbgPrint(" Sid 0x%lx - \n", Index);
|
|
DbgPrint(" Address: 0x%lx \n",
|
|
(*(PULONG)((PVOID)(&(T->RestrictedSids[Index].Sid)))));
|
|
DbgPrint(" Attributes: 0x%lx \n",
|
|
(T->RestrictedSids[Index].Attributes));
|
|
DbgPrint(" Length: 0x%lx \n",
|
|
SeLengthSid((T->RestrictedSids[Index].Sid)));
|
|
Index += 1;
|
|
}
|
|
|
|
|
|
DbgPrint("\n");
|
|
DbgPrint(" Privileges: 0x%lx\n",
|
|
(*(PULONG)((PVOID)(&(T->Privileges)))));
|
|
Index = 0;
|
|
while (Index < T->PrivilegeCount) {
|
|
DbgPrint(" Privilege 0x%lx - \n", Index);
|
|
DbgPrint(" Address: 0x%lx \n",
|
|
(&(T->Privileges[Index])));
|
|
DbgPrint(" LUID: (0x%lx, 0x%lx) \n",
|
|
T->Privileges[Index].Luid.HighPart,
|
|
T->Privileges[Index].Luid.LowPart);
|
|
DbgPrint(" Attributes: 0x%lx \n",
|
|
T->Privileges[Index].Attributes);
|
|
|
|
Index += 1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif //TOKEN_DEBUG
|
|
|
|
|
|
NTSTATUS
|
|
NtCreateToken(
|
|
OUT PHANDLE TokenHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
|
|
IN TOKEN_TYPE TokenType,
|
|
IN PLUID AuthenticationId,
|
|
IN PLARGE_INTEGER ExpirationTime,
|
|
IN PTOKEN_USER User,
|
|
IN PTOKEN_GROUPS Groups,
|
|
IN PTOKEN_PRIVILEGES Privileges,
|
|
IN PTOKEN_OWNER Owner OPTIONAL,
|
|
IN PTOKEN_PRIMARY_GROUP PrimaryGroup,
|
|
IN PTOKEN_DEFAULT_DACL DefaultDacl OPTIONAL,
|
|
IN PTOKEN_SOURCE TokenSource
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create a token object and return a handle opened for access to
|
|
that token. This API requires SeCreateTokenPrivilege privilege.
|
|
|
|
Arguments:
|
|
|
|
TokenHandle - Receives the handle of the newly created token.
|
|
|
|
DesiredAccess - Is an access mask indicating which access types
|
|
the handle is to provide to the new object.
|
|
|
|
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 token type is TokenImpersonation, then this parameter
|
|
must specify the impersonation level of the token.
|
|
|
|
TokenType - Type of token to be created. Privilege is required
|
|
to create any type of token.
|
|
|
|
AuthenticationId - Points to a LUID (or LUID) providing a unique
|
|
identifier associated with the authentication. This is used
|
|
within security only, for audit purposes.
|
|
|
|
ExpirationTime - Time at which the token becomes invalid. If this
|
|
value is specified as zero, then the token has no expiration
|
|
time.
|
|
|
|
User - Is the user SID to place in the token.
|
|
|
|
Groups - Are the group SIDs to place in the token.
|
|
|
|
Privileges - Are the privileges to place in the token.
|
|
|
|
Owner - (Optionally) identifies an identifier that is to be used
|
|
as the default owner for the token. If not provided, the
|
|
user ID is made the default owner.
|
|
|
|
PrimaryGroup - Identifies which of the group IDs is to be the
|
|
primary group of the token.
|
|
|
|
DefaultDacl - (optionally) establishes an ACL to be used as the
|
|
default discretionary access protection for the token.
|
|
|
|
TokenSource - Identifies the token source name string and
|
|
identifier to be assigned to the token.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Indicates the operation was successful.
|
|
|
|
STATUS_INVALID_OWNER - Indicates the ID provided to be assigned
|
|
as the default owner of the token does not have an attribute
|
|
indicating it may be assigned as an owner.
|
|
|
|
STATUS_INVALID_PRIMARY_GROUP - Indicates the group ID provided
|
|
via the PrimaryGroup parameter was not among those assigned
|
|
to the token in the Groups parameter.
|
|
|
|
STATUS_BAD_IMPERSONATION_LEVEL - Indicates no impersonation level
|
|
was provided when attempting to create a token of type
|
|
TokenImpersonation.
|
|
|
|
--*/
|
|
{
|
|
KPROCESSOR_MODE PreviousMode;
|
|
NTSTATUS Status;
|
|
ULONG Ignore;
|
|
|
|
HANDLE LocalHandle;
|
|
|
|
BOOLEAN SecurityQosPresent = FALSE;
|
|
SECURITY_ADVANCED_QUALITY_OF_SERVICE CapturedSecurityQos;
|
|
|
|
LUID CapturedAuthenticationId;
|
|
LARGE_INTEGER CapturedExpirationTime;
|
|
|
|
PSID_AND_ATTRIBUTES CapturedUser = NULL;
|
|
ULONG CapturedUserLength;
|
|
|
|
ULONG CapturedGroupCount;
|
|
PSID_AND_ATTRIBUTES CapturedGroups = NULL;
|
|
ULONG CapturedGroupsLength;
|
|
|
|
ULONG CapturedPrivilegeCount;
|
|
PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL;
|
|
ULONG CapturedPrivilegesLength;
|
|
|
|
PSID CapturedOwner = NULL;
|
|
|
|
PSID CapturedPrimaryGroup = NULL;
|
|
|
|
PACL CapturedDefaultDacl = NULL;
|
|
|
|
TOKEN_SOURCE CapturedTokenSource;
|
|
|
|
PVOID CapturedAddress;
|
|
|
|
PAGED_CODE();
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode) {
|
|
// Probe everything necessary for input to the capture subroutines.
|
|
try {
|
|
ProbeForWriteHandle(TokenHandle);
|
|
|
|
ProbeForRead(ExpirationTime, sizeof(LARGE_INTEGER), sizeof(ULONG));
|
|
ProbeForRead(Groups, sizeof(TOKEN_GROUPS), sizeof(ULONG));
|
|
ProbeForRead(Privileges, sizeof(TOKEN_PRIVILEGES), sizeof(ULONG));
|
|
ProbeForRead(TokenSource, sizeof(TOKEN_SOURCE), sizeof(ULONG));
|
|
|
|
|
|
if (ARGUMENT_PRESENT(Owner)) {
|
|
ProbeForRead(Owner, sizeof(TOKEN_OWNER), sizeof(ULONG));
|
|
}
|
|
|
|
|
|
ProbeForRead(
|
|
PrimaryGroup,
|
|
sizeof(TOKEN_PRIMARY_GROUP),
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
|
|
if (ARGUMENT_PRESENT(DefaultDacl)) {
|
|
ProbeForRead(
|
|
DefaultDacl,
|
|
sizeof(TOKEN_DEFAULT_DACL),
|
|
sizeof(ULONG)
|
|
);
|
|
}
|
|
|
|
ProbeForRead(
|
|
AuthenticationId,
|
|
sizeof(LUID),
|
|
sizeof(ULONG)
|
|
);
|
|
} except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
return GetExceptionCode();
|
|
} // end_try
|
|
} //end_if
|
|
|
|
|
|
// Capture the security quality of service.
|
|
// This capture routine necessarily does some probing of its own.
|
|
|
|
|
|
Status = SeCaptureSecurityQos(
|
|
ObjectAttributes,
|
|
PreviousMode,
|
|
&SecurityQosPresent,
|
|
&CapturedSecurityQos
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
if (TokenType == TokenImpersonation) {
|
|
if (!SecurityQosPresent) {
|
|
return STATUS_BAD_IMPERSONATION_LEVEL;
|
|
} // endif
|
|
} // endif
|
|
|
|
|
|
// Capture the rest of the arguments.
|
|
// These arguments have already been probed.
|
|
|
|
try {
|
|
Status = STATUS_SUCCESS;
|
|
|
|
// Capture and validate AuthenticationID
|
|
RtlCopyLuid(&CapturedAuthenticationId, AuthenticationId);
|
|
|
|
// Capture ExpirationTime
|
|
CapturedExpirationTime = (*ExpirationTime);
|
|
|
|
// Capture User
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = SeCaptureSidAndAttributesArray(
|
|
&(User->User),
|
|
1,
|
|
PreviousMode,
|
|
NULL, 0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedUser,
|
|
&CapturedUserLength
|
|
);
|
|
}
|
|
|
|
// Capture Groups
|
|
if (NT_SUCCESS(Status)) {
|
|
CapturedGroupCount = Groups->GroupCount;
|
|
Status = SeCaptureSidAndAttributesArray(
|
|
(Groups->Groups),
|
|
CapturedGroupCount,
|
|
PreviousMode,
|
|
NULL, 0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedGroups,
|
|
&CapturedGroupsLength
|
|
);
|
|
}
|
|
|
|
// Capture Privileges
|
|
if (NT_SUCCESS(Status)) {
|
|
CapturedPrivilegeCount = Privileges->PrivilegeCount;
|
|
Status = SeCaptureLuidAndAttributesArray(
|
|
(Privileges->Privileges),
|
|
CapturedPrivilegeCount,
|
|
PreviousMode,
|
|
NULL, 0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedPrivileges,
|
|
&CapturedPrivilegesLength
|
|
);
|
|
}
|
|
|
|
// Capture Owner
|
|
if (ARGUMENT_PRESENT(Owner) && NT_SUCCESS(Status)) {
|
|
CapturedAddress = Owner->Owner;
|
|
Status = SeCaptureSid(
|
|
(PSID)CapturedAddress,
|
|
PreviousMode,
|
|
NULL, 0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedOwner
|
|
);
|
|
}
|
|
|
|
// Capture PrimaryGroup
|
|
if (NT_SUCCESS(Status)) {
|
|
CapturedAddress = PrimaryGroup->PrimaryGroup;
|
|
Status = SeCaptureSid(
|
|
(PSID)CapturedAddress,
|
|
PreviousMode,
|
|
NULL, 0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedPrimaryGroup
|
|
);
|
|
}
|
|
|
|
// Capture DefaultDacl
|
|
if (ARGUMENT_PRESENT(DefaultDacl) && NT_SUCCESS(Status)) {
|
|
CapturedAddress = DefaultDacl->DefaultDacl;
|
|
if (CapturedAddress != NULL) {
|
|
Status = SeCaptureAcl(
|
|
(PACL)CapturedAddress,
|
|
PreviousMode,
|
|
NULL, 0,
|
|
NonPagedPool,
|
|
TRUE,
|
|
&CapturedDefaultDacl,
|
|
&Ignore
|
|
);
|
|
}
|
|
}
|
|
|
|
// Capture TokenSource
|
|
CapturedTokenSource = (*TokenSource);
|
|
} except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
|
|
if (CapturedUser != NULL) {
|
|
SeReleaseSidAndAttributesArray(
|
|
CapturedUser,
|
|
PreviousMode,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
if (CapturedGroups != NULL) {
|
|
SeReleaseSidAndAttributesArray(
|
|
CapturedGroups,
|
|
PreviousMode,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
if (CapturedPrivileges != NULL) {
|
|
SeReleaseLuidAndAttributesArray(
|
|
CapturedPrivileges,
|
|
PreviousMode,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
if (CapturedOwner != NULL) {
|
|
SeReleaseSid(CapturedOwner, PreviousMode, TRUE);
|
|
}
|
|
|
|
if (CapturedPrimaryGroup != NULL) {
|
|
SeReleaseSid(CapturedPrimaryGroup, PreviousMode, TRUE);
|
|
}
|
|
|
|
if (CapturedDefaultDacl != NULL) {
|
|
SeReleaseAcl(CapturedDefaultDacl, PreviousMode, TRUE);
|
|
}
|
|
|
|
if (SecurityQosPresent == TRUE) {
|
|
SeFreeCapturedSecurityQos(&CapturedSecurityQos);
|
|
}
|
|
|
|
return GetExceptionCode();
|
|
} // end_try{}
|
|
|
|
// Create the token
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = SepCreateToken(
|
|
&LocalHandle,
|
|
PreviousMode,
|
|
DesiredAccess,
|
|
ObjectAttributes,
|
|
TokenType,
|
|
CapturedSecurityQos.ImpersonationLevel,
|
|
&CapturedAuthenticationId,
|
|
&CapturedExpirationTime,
|
|
CapturedUser,
|
|
CapturedGroupCount,
|
|
CapturedGroups,
|
|
CapturedGroupsLength,
|
|
CapturedPrivilegeCount,
|
|
CapturedPrivileges,
|
|
CapturedPrivilegesLength,
|
|
CapturedOwner,
|
|
CapturedPrimaryGroup,
|
|
CapturedDefaultDacl,
|
|
&CapturedTokenSource,
|
|
FALSE, // Not a system token
|
|
SecurityQosPresent ? CapturedSecurityQos.ProxyData : NULL,
|
|
SecurityQosPresent ? CapturedSecurityQos.AuditData : NULL
|
|
);
|
|
}
|
|
|
|
// Clean up the temporary capture buffers
|
|
|
|
if (CapturedUser != NULL) {
|
|
SeReleaseSidAndAttributesArray(CapturedUser, PreviousMode, TRUE);
|
|
}
|
|
if (CapturedGroups != NULL) {
|
|
SeReleaseSidAndAttributesArray(CapturedGroups, PreviousMode, TRUE);
|
|
}
|
|
|
|
if (CapturedPrivileges != NULL) {
|
|
SeReleaseLuidAndAttributesArray(CapturedPrivileges, PreviousMode, TRUE);
|
|
}
|
|
|
|
if (CapturedOwner != NULL) {
|
|
SeReleaseSid(CapturedOwner, PreviousMode, TRUE);
|
|
}
|
|
|
|
if (CapturedPrimaryGroup != NULL) {
|
|
SeReleaseSid(CapturedPrimaryGroup, PreviousMode, TRUE);
|
|
}
|
|
|
|
if (CapturedDefaultDacl != NULL) {
|
|
SeReleaseAcl(CapturedDefaultDacl, PreviousMode, TRUE);
|
|
}
|
|
|
|
if (SecurityQosPresent == TRUE) {
|
|
SeFreeCapturedSecurityQos(&CapturedSecurityQos);
|
|
}
|
|
|
|
// Return the handle to this new token
|
|
if (NT_SUCCESS(Status)) {
|
|
try { *TokenHandle = LocalHandle; }
|
|
except(EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); }
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
// Token Private Routines //
|
|
|
|
|
|
VOID
|
|
SepTokenDeleteMethod(
|
|
IN PVOID Token
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
This function is the token object type-specific delete method.
|
|
It is needed to ensure that all memory allocated for the token
|
|
gets deallocated.
|
|
|
|
Arguments:
|
|
Token - Points to the token object being deleted.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
// De-reference the logon session referenced by this token object
|
|
SepDeReferenceLogonSession(&(((TOKEN *)Token)->AuthenticationId));
|
|
|
|
// If the token has an associated Dynamic part, deallocate it.
|
|
if (ARGUMENT_PRESENT(((TOKEN *)Token)->DynamicPart)) {
|
|
ExFreePool(((TOKEN *)Token)->DynamicPart);
|
|
}
|
|
|
|
|
|
// Free the Proxy and Global audit structures if present.
|
|
|
|
if (ARGUMENT_PRESENT(((TOKEN *)Token)->ProxyData)) {
|
|
SepFreeProxyData(((TOKEN *)Token)->ProxyData);
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(((TOKEN *)Token)->AuditData)) {
|
|
ExFreePool((((TOKEN *)Token)->AuditData));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SepCreateToken(
|
|
OUT PHANDLE TokenHandle,
|
|
IN KPROCESSOR_MODE RequestorMode,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
|
|
IN TOKEN_TYPE TokenType,
|
|
IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel OPTIONAL,
|
|
IN PLUID AuthenticationId,
|
|
IN PLARGE_INTEGER ExpirationTime,
|
|
IN PSID_AND_ATTRIBUTES User,
|
|
IN ULONG GroupCount,
|
|
IN PSID_AND_ATTRIBUTES Groups,
|
|
IN ULONG GroupsLength,
|
|
IN ULONG PrivilegeCount,
|
|
IN PLUID_AND_ATTRIBUTES Privileges,
|
|
IN ULONG PrivilegesLength,
|
|
IN PSID Owner OPTIONAL,
|
|
IN PSID PrimaryGroup,
|
|
IN PACL DefaultDacl OPTIONAL,
|
|
IN PTOKEN_SOURCE TokenSource,
|
|
IN BOOLEAN SystemToken,
|
|
IN PSECURITY_TOKEN_PROXY_DATA ProxyData OPTIONAL,
|
|
IN PSECURITY_TOKEN_AUDIT_DATA AuditData OPTIONAL
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Create a token object and return a handle opened for access to
|
|
that token. This API implements the bulk of the work needed
|
|
for NtCreateToken.
|
|
|
|
All parameters except DesiredAccess and ObjectAttributes are assumed
|
|
to have been probed and captured.
|
|
|
|
The output parameter (TokenHandle) is expected to be returned to a
|
|
safe address, rather than to a user mode address that may cause an
|
|
exception.
|
|
|
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
NOTE: This routine is also used to create the initial system token.
|
|
In that case, the SystemToken parameter is TRUE and no handle
|
|
is established to the token. Instead, a pointer to the token
|
|
is returned via the TokenHandle parameter.
|
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
|
|
Arguments:
|
|
|
|
TokenHandle - Receives the handle of the newly created token. If the
|
|
SystemToken parameter is specified is true, then this parameter
|
|
receives a pointer to the token instead of a handle to the token.
|
|
|
|
RequestorMode - The mode of the caller on whose behalf the token
|
|
is being created.
|
|
|
|
DesiredAccess - Is an access mask indicating which access types
|
|
the handle is to provide to the new object.
|
|
|
|
ObjectAttributes - Points to the standard object attributes data
|
|
structure. Refer to the NT Object Management
|
|
Specification for a description of this data structure.
|
|
|
|
TokenType - Type of token to be created. Privilege is required
|
|
to create any type of token.
|
|
|
|
ImpersonationLevel - If the token type is TokenImpersonation, then
|
|
this parameter is used to specify the impersonation level of
|
|
the token.
|
|
|
|
AuthenticationId - Points to a LUID (or LUID) providing a unique
|
|
identifier associated with the authentication. This is used
|
|
within security only, for audit purposes.
|
|
|
|
ExpirationTime - Time at which the token becomes invalid. If this
|
|
value is specified as zero, then the token has no expiration
|
|
time.
|
|
|
|
User - Is the user SID to place in the token.
|
|
|
|
GroupCount - Indicates the number of groups in the 'Groups' parameter.
|
|
This value may be zero, in which case the 'Groups' parameter is
|
|
ignored.
|
|
|
|
Groups - Are the group SIDs, and their corresponding attributes,
|
|
to place in the token.
|
|
|
|
GroupsLength - Indicates the length, in bytes, of the array of groups
|
|
to place in the token.
|
|
|
|
PrivilegeCount - Indicates the number of privileges in the 'Privileges'
|
|
parameter. This value may be zero, in which case the 'Privileges'
|
|
parameter is ignored.
|
|
|
|
Privileges - Are the privilege LUIDs, and their corresponding attributes,
|
|
to place in the token.
|
|
|
|
PrivilegesLength - Indicates the length, in bytes, of the array of
|
|
privileges to place in the token.
|
|
|
|
Owner - (Optionally) identifies an identifier that is to be used
|
|
as the default owner for the token. If not provided, the
|
|
user ID is made the default owner.
|
|
|
|
PrimaryGroup - Identifies which of the group IDs is to be the
|
|
primary group of the token.
|
|
|
|
DefaultDacl - (optionally) establishes an ACL to be used as the
|
|
default discretionary access protection for the token.
|
|
|
|
TokenSource - Identifies the token source name string and
|
|
identifier to be assigned to the token.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Indicates the operation was successful.
|
|
|
|
STATUS_INVALID_OWNER - Indicates the ID provided to be assigned
|
|
as the default owner of the token does not have an attribute
|
|
indicating it may be assigned as an owner.
|
|
|
|
STATUS_INVALID_PRIMARY_GROUP - Indicates the group ID provided
|
|
via the PrimaryGroup parameter was not among those assigned
|
|
to the token in the Groups parameter.
|
|
|
|
STATUS_INVALID_PARAMETER - Indicates that a required parameter,
|
|
such as User or PrimaryGroup, was not provided with a legitimate
|
|
value.
|
|
--*/
|
|
{
|
|
PTOKEN Token;
|
|
NTSTATUS Status;
|
|
|
|
ULONG PagedPoolSize;
|
|
|
|
ULONG PrimaryGroupLength;
|
|
|
|
ULONG TokenBodyLength;
|
|
ULONG VariableLength;
|
|
|
|
ULONG DefaultOwnerIndex;
|
|
PUCHAR Where;
|
|
ULONG ComputedPrivLength;
|
|
|
|
//ULONG_PTR NextFree;
|
|
PSID NextSidFree;
|
|
|
|
ULONG DynamicLength = TOKEN_DEFAULT_DYNAMIC_CHARGE;
|
|
ULONG DynamicLengthUsed;
|
|
|
|
ULONG SubAuthorityCount;
|
|
ULONG GroupIndex;
|
|
ULONG PrivilegeIndex;
|
|
BOOLEAN OwnerFound;
|
|
|
|
UCHAR TokenFlags = 0;
|
|
|
|
ACCESS_STATE AccessState;
|
|
AUX_ACCESS_DATA AuxData;
|
|
LUID NewModifiedId;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(sizeof(SECURITY_IMPERSONATION_LEVEL) <= sizeof(ULONG));
|
|
|
|
|
|
// Make sure the Enabled and Enabled-by-default bits are set on every
|
|
// mandatory group.
|
|
|
|
// Also, check to see if the local administrators alias is present.
|
|
// if so, turn on the flag so that we can do restrictions later
|
|
|
|
|
|
for (GroupIndex = 0; GroupIndex < GroupCount; GroupIndex++) {
|
|
if (Groups[GroupIndex].Attributes & SE_GROUP_MANDATORY) {
|
|
Groups[GroupIndex].Attributes |= (SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT);
|
|
}
|
|
if (RtlEqualSid(SeAliasAdminsSid, Groups[GroupIndex].Sid)) {
|
|
TokenFlags |= TOKEN_HAS_ADMIN_GROUP;
|
|
}
|
|
}
|
|
|
|
// Check to see if the token being created is going to be granted
|
|
// SeChangeNotifyPrivilege. If so, set a flag in the TokenFlags field
|
|
// so we can find this out quickly.
|
|
|
|
for (PrivilegeIndex = 0; PrivilegeIndex < PrivilegeCount; PrivilegeIndex++) {
|
|
if (((RtlEqualLuid(&Privileges[PrivilegeIndex].Luid, &SeChangeNotifyPrivilege))
|
|
&&
|
|
(Privileges[PrivilegeIndex].Attributes & SE_PRIVILEGE_ENABLED))) {
|
|
TokenFlags |= TOKEN_HAS_TRAVERSE_PRIVILEGE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get a ModifiedId to use
|
|
ExAllocateLocallyUniqueId(&NewModifiedId);
|
|
|
|
// Validate the owner ID, if provided and establish the default
|
|
// owner index.
|
|
|
|
if (!ARGUMENT_PRESENT(Owner)) {
|
|
DefaultOwnerIndex = 0;
|
|
} else {
|
|
if (RtlEqualSid(Owner, User->Sid)) {
|
|
DefaultOwnerIndex = 0;
|
|
} else {
|
|
GroupIndex = 0;
|
|
OwnerFound = FALSE;
|
|
|
|
while ((GroupIndex < GroupCount) && (!OwnerFound)) {
|
|
if (RtlEqualSid(Owner, (Groups[GroupIndex].Sid))) {
|
|
// Found a match - make sure it is assignable as owner.
|
|
if (SepArrayGroupAttributes(Groups, GroupIndex) &
|
|
SE_GROUP_OWNER) {
|
|
DefaultOwnerIndex = GroupIndex + 1;
|
|
OwnerFound = TRUE;
|
|
} else {
|
|
return STATUS_INVALID_OWNER;
|
|
} // endif Owner attribute set
|
|
} // endif owner = group
|
|
|
|
GroupIndex += 1;
|
|
} // endwhile
|
|
|
|
if (!OwnerFound) {
|
|
return STATUS_INVALID_OWNER;
|
|
} // endif !OwnerFound
|
|
} // endif owner = user
|
|
} // endif owner specified
|
|
|
|
// Increment the reference count for this logon session
|
|
// (fail if there is no corresponding logon session.)
|
|
Status = SepReferenceLogonSession(AuthenticationId);
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
// Calculate the length needed for the variable portion of the token
|
|
// This includes the User ID, Group IDs, and Privileges
|
|
|
|
// Align the privilege chunk by pointer alignment so that the SIDs will
|
|
// be correctly aligned. Align the Groups Length so that the SID_AND_ATTR
|
|
// array (which is
|
|
|
|
ComputedPrivLength = PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES);
|
|
ComputedPrivLength = ALIGN_UP(ComputedPrivLength, PVOID);
|
|
GroupsLength = ALIGN_UP(GroupsLength, PVOID);
|
|
|
|
VariableLength = GroupsLength + ComputedPrivLength +
|
|
ALIGN_UP((GroupCount * sizeof(SID_AND_ATTRIBUTES)), PVOID);
|
|
|
|
SubAuthorityCount = ((SID *)(User->Sid))->SubAuthorityCount;
|
|
VariableLength += sizeof(SID_AND_ATTRIBUTES) +
|
|
(ULONG)LongAlignSize(RtlLengthRequiredSid(SubAuthorityCount));
|
|
|
|
// Calculate the length needed for the dynamic portion of the token
|
|
// This includes the default Dacl and the primary group.
|
|
|
|
SubAuthorityCount = ((SID *)PrimaryGroup)->SubAuthorityCount;
|
|
DynamicLengthUsed = (ULONG)LongAlignSize(RtlLengthRequiredSid(SubAuthorityCount));
|
|
|
|
if (ARGUMENT_PRESENT(DefaultDacl)) {
|
|
DynamicLengthUsed += (ULONG)LongAlignSize(DefaultDacl->AclSize);
|
|
}
|
|
|
|
if (DynamicLengthUsed > DynamicLength) {
|
|
DynamicLength = DynamicLengthUsed;
|
|
}
|
|
|
|
// Now create the token body
|
|
TokenBodyLength = sizeof(TOKEN) + VariableLength;
|
|
PagedPoolSize = TokenBodyLength + DynamicLength;
|
|
|
|
Status = ObCreateObject(
|
|
RequestorMode, // ProbeMode
|
|
SepTokenObjectType, // ObjectType
|
|
ObjectAttributes, // ObjectAttributes
|
|
UserMode, // OwnershipMode
|
|
NULL, // ParseContext
|
|
TokenBodyLength, // ObjectBodySize
|
|
PagedPoolSize, // PagedPoolCharge
|
|
0, // NonPagedPoolCharge
|
|
(PVOID *)&Token // Return pointer to object
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
SepDeReferenceLogonSession(AuthenticationId);
|
|
return Status;
|
|
}
|
|
|
|
// After this point, we rely on token deletion to clean up the referenced
|
|
// logon session if the creation fails.
|
|
|
|
// Main Body initialization
|
|
|
|
ExAllocateLocallyUniqueId(&(Token->TokenId));
|
|
Token->ParentTokenId = RtlConvertLongToLuid(0);
|
|
Token->AuthenticationId = (*AuthenticationId);
|
|
Token->TokenInUse = FALSE;
|
|
Token->ModifiedId = NewModifiedId;
|
|
Token->ExpirationTime = (*ExpirationTime);
|
|
Token->TokenType = TokenType;
|
|
Token->ImpersonationLevel = ImpersonationLevel;
|
|
Token->TokenSource = (*TokenSource);
|
|
|
|
Token->TokenFlags = TokenFlags;
|
|
Token->SessionId = 0;
|
|
|
|
Token->DynamicCharged = DynamicLength;
|
|
Token->DynamicAvailable = DynamicLength - DynamicLengthUsed;
|
|
|
|
Token->DefaultOwnerIndex = DefaultOwnerIndex;
|
|
Token->DefaultDacl = NULL;
|
|
|
|
Token->VariableLength = VariableLength;
|
|
|
|
// Ensure SepTokenDeleteMethod knows the buffers aren't allocated yet.
|
|
Token->ProxyData = NULL;
|
|
Token->AuditData = NULL;
|
|
Token->DynamicPart = NULL;
|
|
|
|
if (ARGUMENT_PRESENT(ProxyData)) {
|
|
Status = SepCopyProxyData(
|
|
&Token->ProxyData,
|
|
ProxyData
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ObDereferenceObject(Token);
|
|
return(STATUS_NO_MEMORY);
|
|
}
|
|
} else {
|
|
Token->ProxyData = NULL;
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(AuditData)) {
|
|
Token->AuditData = ExAllocatePool(PagedPool, sizeof(SECURITY_TOKEN_AUDIT_DATA));
|
|
if (Token->AuditData == NULL) {
|
|
ObDereferenceObject(Token);
|
|
return(STATUS_NO_MEMORY);
|
|
}
|
|
|
|
*(Token->AuditData) = *AuditData;
|
|
} else {
|
|
Token->AuditData = NULL;
|
|
}
|
|
|
|
|
|
// Variable part initialization
|
|
// Data is in the following order:
|
|
|
|
// Privileges array
|
|
// User (SID_AND_ATTRIBUTES)
|
|
// Groups (SID_AND_ATTRIBUTES)
|
|
// Restricted Sids (SID_AND_ATTRIBUTES)
|
|
// SIDs
|
|
|
|
|
|
Where = (PUCHAR)&Token->VariablePart;
|
|
|
|
Token->Privileges = (PLUID_AND_ATTRIBUTES)Where;
|
|
Token->PrivilegeCount = PrivilegeCount;
|
|
|
|
RtlCopyMemory(
|
|
Where,
|
|
Privileges,
|
|
PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
|
|
|
|
ASSERT(ComputedPrivLength >= PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
|
|
|
|
Where += ComputedPrivLength;
|
|
VariableLength -= ComputedPrivLength;
|
|
|
|
ASSERT((((ULONG_PTR)Where) & (sizeof(PVOID) - 1)) == 0);
|
|
|
|
// Now, copy the sid and attributes arrays.
|
|
NextSidFree = (PSID)(Where + (sizeof(SID_AND_ATTRIBUTES) * (GroupCount + 1)));
|
|
|
|
Token->UserAndGroups = (PSID_AND_ATTRIBUTES)Where;
|
|
Token->UserAndGroupCount = GroupCount + 1;
|
|
|
|
ASSERT(VariableLength >= ((GroupCount + 1) * (ULONG)sizeof(SID_AND_ATTRIBUTES)));
|
|
|
|
VariableLength -= ((GroupCount + 1) * (ULONG)sizeof(SID_AND_ATTRIBUTES));
|
|
Status = RtlCopySidAndAttributesArray(
|
|
1,
|
|
User,
|
|
VariableLength,
|
|
(PSID_AND_ATTRIBUTES)Where,
|
|
NextSidFree,
|
|
&NextSidFree,
|
|
&VariableLength
|
|
);
|
|
|
|
Where += sizeof(SID_AND_ATTRIBUTES);
|
|
|
|
ASSERT((((ULONG_PTR)Where) & (sizeof(PVOID) - 1)) == 0);
|
|
|
|
Status = RtlCopySidAndAttributesArray(
|
|
GroupCount,
|
|
Groups,
|
|
VariableLength,
|
|
(PSID_AND_ATTRIBUTES)Where,
|
|
NextSidFree,
|
|
&NextSidFree,
|
|
&VariableLength
|
|
);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
Token->RestrictedSids = NULL;
|
|
Token->RestrictedSidCount = 0;
|
|
|
|
// Dynamic part initialization
|
|
// Data is in the following order:
|
|
|
|
// PrimaryGroup (SID)
|
|
// Default Discreationary Acl (ACL)
|
|
|
|
|
|
Token->DynamicPart = (PULONG)ExAllocatePoolWithTag(PagedPool, DynamicLength, 'dTeS');
|
|
|
|
|
|
// The attempt to allocate the DynamicPart of the token may have
|
|
// failed. Dereference the created object and exit with an error.
|
|
|
|
|
|
if (Token->DynamicPart == NULL) {
|
|
ObDereferenceObject(Token);
|
|
return(STATUS_NO_MEMORY);
|
|
}
|
|
|
|
Where = (PUCHAR)Token->DynamicPart;
|
|
|
|
Token->PrimaryGroup = (PSID)Where;
|
|
PrimaryGroupLength = RtlLengthRequiredSid(((SID *)PrimaryGroup)->SubAuthorityCount);
|
|
RtlCopySid(PrimaryGroupLength, (PSID)Where, PrimaryGroup);
|
|
Where += (ULONG)LongAlignSize(PrimaryGroupLength);
|
|
|
|
if (ARGUMENT_PRESENT(DefaultDacl)) {
|
|
Token->DefaultDacl = (PACL)Where;
|
|
|
|
RtlCopyMemory((PVOID)Where,
|
|
(PVOID)DefaultDacl,
|
|
DefaultDacl->AclSize
|
|
);
|
|
}
|
|
|
|
#ifdef TOKEN_DEBUG
|
|
// Debug
|
|
SepDumpToken(Token);
|
|
// Debug
|
|
#endif //TOKEN_DEBUG
|
|
|
|
// Insert the token unless it is a system token.
|
|
if (!SystemToken) {
|
|
Status = SeCreateAccessState(
|
|
&AccessState,
|
|
&AuxData,
|
|
DesiredAccess,
|
|
&SepTokenObjectType->TypeInfo.GenericMapping
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
BOOLEAN PrivilegeHeld;
|
|
|
|
PrivilegeHeld = SeSinglePrivilegeCheck(
|
|
SeCreateTokenPrivilege,
|
|
KeGetPreviousMode()
|
|
);
|
|
|
|
if (PrivilegeHeld) {
|
|
Status = ObInsertObject(Token,
|
|
&AccessState,
|
|
0,
|
|
0,
|
|
(PVOID *)NULL,
|
|
TokenHandle
|
|
);
|
|
} else {
|
|
Status = STATUS_PRIVILEGE_NOT_HELD;
|
|
ObDereferenceObject(Token);
|
|
}
|
|
|
|
SeDeleteAccessState(&AccessState);
|
|
} else {
|
|
ObDereferenceObject(Token);
|
|
}
|
|
} else {
|
|
ASSERT(NT_SUCCESS(Status));
|
|
ObDeleteCapturedInsertInfo(Token);
|
|
|
|
// Return pointer instead of handle.
|
|
(*TokenHandle) = (HANDLE)Token;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
SepIdAssignableAsOwner(
|
|
IN PTOKEN Token,
|
|
IN ULONG Index
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
This routine returns a boolean value indicating whether the user
|
|
or group ID in the specified token with the specified index is
|
|
assignable as the owner of an object.
|
|
|
|
If the index is 0, which is always the USER ID, then the ID is
|
|
assignable as owner. Otherwise, the ID is that of a group, and
|
|
it must have the "Owner" attribute set to be assignable.
|
|
Arguments:
|
|
Token - Pointer to a locked Token to use.
|
|
Index - Index into the Token's UserAndGroupsArray. This value
|
|
is assumed to be valid.
|
|
Return Value:
|
|
TRUE - Indicates the index corresponds to an ID that may be assigned
|
|
as the owner of objects.
|
|
FALSE - Indicates the index does not correspond to an ID that may be
|
|
assigned as the owner of objects.
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (Index == 0) {
|
|
return TRUE;
|
|
} else {
|
|
return (BOOLEAN)
|
|
((SepTokenGroupAttributes(Token, Index) & SE_GROUP_OWNER)
|
|
!= 0
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS SeIsChildToken(IN HANDLE Token, OUT PBOOLEAN IsChild)
|
|
/*++
|
|
Routine Description:
|
|
This routine returns TRUE if the supplied token is a child of the caller's
|
|
process token. This is done by comparing the ParentTokenId field of the
|
|
supplied token to the TokenId field of the token from the current subject context.
|
|
Arguments:
|
|
Token - Token to check for childhood
|
|
IsChild - Contains results of comparison.
|
|
TRUE - The supplied token is a child of the caller's token
|
|
FALSE- The supplied token is not a child of the caller's token
|
|
Returns:
|
|
Status codes from any NT services called.
|
|
--*/
|
|
{
|
|
PTOKEN CallerToken;
|
|
PTOKEN SuppliedToken;
|
|
LUID CallerTokenId;
|
|
LUID SuppliedParentTokenId;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
*IsChild = FALSE;
|
|
|
|
// Capture the caller's token and get the token id
|
|
CallerToken = (PTOKEN)PsReferencePrimaryToken(PsGetCurrentProcess());
|
|
|
|
SepAcquireTokenReadLock(CallerToken);
|
|
CallerTokenId = CallerToken->TokenId;
|
|
SepReleaseTokenReadLock(CallerToken);
|
|
|
|
PsDereferencePrimaryToken(CallerToken);
|
|
|
|
// Reference the supplied token and get the parent token id.
|
|
Status = ObReferenceObjectByHandle(
|
|
Token, // Handle
|
|
0, // DesiredAccess
|
|
SepTokenObjectType, // ObjectType
|
|
KeGetPreviousMode(), // AccessMode
|
|
(PVOID *)&SuppliedToken, // Object
|
|
NULL // GrantedAccess
|
|
);
|
|
if (NT_SUCCESS(Status)) {
|
|
SepAcquireTokenReadLock(SuppliedToken);
|
|
SuppliedParentTokenId = SuppliedToken->ParentTokenId;
|
|
SepReleaseTokenReadLock(SuppliedToken);
|
|
|
|
ObDereferenceObject(SuppliedToken);
|
|
|
|
// Check to see if the supplied token's parent ID is the ID
|
|
// of the caller.
|
|
if (RtlEqualLuid(
|
|
&SuppliedParentTokenId,
|
|
&CallerTokenId
|
|
)) {
|
|
*IsChild = TRUE;
|
|
}
|
|
}
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SeIsChildTokenByPointer(
|
|
IN PACCESS_TOKEN Token,
|
|
OUT PBOOLEAN IsChild
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
This routine returns TRUE if the supplied token is a child of the caller's
|
|
token. This is done by comparing the ParentTokenId field of the supplied
|
|
token to the TokenId field of the token from the current subject context.
|
|
Arguments:
|
|
Token - Token to check for childhood
|
|
IsChild - Contains results of comparison.
|
|
TRUE - The supplied token is a child of the caller's token
|
|
FALSE- The supplied token is not a child of the caller's token
|
|
Returns:
|
|
Status codes from any NT services called.
|
|
--*/
|
|
{
|
|
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
|
|
PTOKEN CallerToken;
|
|
PTOKEN SuppliedToken;
|
|
LUID CallerTokenId;
|
|
LUID SuppliedParentTokenId;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
*IsChild = FALSE;
|
|
|
|
// Capture the caller's token and get the token id
|
|
CallerToken = (PTOKEN)PsReferencePrimaryToken(PsGetCurrentProcess());
|
|
|
|
SepAcquireTokenReadLock(CallerToken);
|
|
CallerTokenId = CallerToken->TokenId;
|
|
SepReleaseTokenReadLock(CallerToken);
|
|
|
|
PsDereferencePrimaryToken(CallerToken);
|
|
|
|
SuppliedToken = (PTOKEN)Token;
|
|
|
|
SepAcquireTokenReadLock(SuppliedToken);
|
|
SuppliedParentTokenId = SuppliedToken->ParentTokenId;
|
|
SepReleaseTokenReadLock(SuppliedToken);
|
|
|
|
// Check to see if the supplied token's parent ID is the ID
|
|
// of the caller.
|
|
if (RtlEqualLuid(
|
|
&SuppliedParentTokenId,
|
|
&CallerTokenId
|
|
)) {
|
|
*IsChild = TRUE;
|
|
}
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtImpersonateAnonymousToken(
|
|
IN HANDLE ThreadHandle
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Impersonates the system's anonymous logon token on this thread.
|
|
Arguments:
|
|
ThreadHandle - Handle to the thread to do the impersonation.
|
|
Return Value:
|
|
STATUS_SUCCESS - Indicates the operation was successful.
|
|
STATUS_INVALID_HANDLE - the thread handle is invalid.
|
|
STATUS_ACCESS_DENIED - The thread handle is not open for impersonation access.
|
|
--*/
|
|
{
|
|
PETHREAD CallerThread = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
// Reference the caller's thread to make sure we can impersonate
|
|
Status = ObReferenceObjectByHandle(
|
|
ThreadHandle,
|
|
THREAD_IMPERSONATE,
|
|
PsThreadType,
|
|
KeGetPreviousMode(),
|
|
(PVOID *)&CallerThread,
|
|
NULL
|
|
);
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Reference the impersonation token to make sure we are allowed to
|
|
// impersonate it.
|
|
Status = ObReferenceObjectByPointer(
|
|
SeAnonymousLogonToken,
|
|
TOKEN_IMPERSONATE,
|
|
SepTokenObjectType,
|
|
KeGetPreviousMode()
|
|
);
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
ObDereferenceObject(SeAnonymousLogonToken);
|
|
|
|
// Do the impersonation. We want copy on open so the caller can't
|
|
// actually modify this system's copy of this token.
|
|
Status = PsImpersonateClient(
|
|
CallerThread,
|
|
SeAnonymousLogonToken,
|
|
TRUE, // copy on open
|
|
FALSE, // no effective only
|
|
SecurityImpersonation
|
|
);
|
|
|
|
Cleanup:
|
|
if (CallerThread != NULL) {
|
|
ObDereferenceObject(CallerThread);
|
|
}
|
|
return(Status);
|
|
} |