reactos/ntoskrnl/se/tokencls.c
George Bișoc d0d86ab588
[NTOSKRNL] Force a probe against ReturnLength on query & Misc ICIF stuff
NtQueryInformationToken is by far the only system call in NT where ReturnLength simply cannot be optional. On Windows this parameter is always probed and an argument to NULL directly leads to an access violation exception.
This is due to the fact of how tokens work, as its information contents (token user, owner, primary group, et al) are dynamic and can vary throughout over time in memory.

What happens on current ReactOS master however is that ReturnLength is only probed if the parameter is not NULL. On a NULL case scenario the probing checks succeed and NtQueryInformationToken fails later. For this, just get rid of CompleteProbing
parameter and opt in for a bit mask flag based approach, with ICIF_FORCE_RETURN_LENGTH_PROBE being set on DefaultQueryInfoBufferCheck which NtQueryInformationToken calls it to do sanity checks.

In addition to that...

- Document the ICIF probe helpers
- Annotate the ICIF prope helpers with SAL
- With the riddance of CompleteProbing and adoption of flags based approach, add ICIF_PROBE_READ_WRITE and ICIF_PROBE_READ flags alongside with ICIF_FORCE_RETURN_LENGTH_PROBE
2022-06-12 11:05:05 +02:00

1589 lines
56 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Access token Query/Set information classes implementation
* COPYRIGHT: Copyright David Welch <welch@cwcom.net>
* Copyright 2021-2022 George Bișoc <george.bisoc@reactos.org>
*/
/* INCLUDES *******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
#include <ntlsa.h>
/* INFORMATION CLASSES ********************************************************/
static const INFORMATION_CLASS_INFO SeTokenInformationClass[] = {
/* Class 0 not used, blame MS! */
IQS_NONE,
/* TokenUser */
IQS_SAME(TOKEN_USER, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE),
/* TokenGroups */
IQS_SAME(TOKEN_GROUPS, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE),
/* TokenPrivileges */
IQS_SAME(TOKEN_PRIVILEGES, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE),
/* TokenOwner */
IQS_SAME(TOKEN_OWNER, ULONG, ICIF_QUERY | ICIF_SET | ICIF_SIZE_VARIABLE),
/* TokenPrimaryGroup */
IQS_SAME(TOKEN_PRIMARY_GROUP, ULONG, ICIF_QUERY | ICIF_SET | ICIF_SIZE_VARIABLE),
/* TokenDefaultDacl */
IQS_SAME(TOKEN_DEFAULT_DACL, ULONG, ICIF_QUERY | ICIF_SET | ICIF_SIZE_VARIABLE),
/* TokenSource */
IQS_SAME(TOKEN_SOURCE, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE),
/* TokenType */
IQS_SAME(TOKEN_TYPE, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE),
/* TokenImpersonationLevel */
IQS_SAME(SECURITY_IMPERSONATION_LEVEL, ULONG, ICIF_QUERY),
/* TokenStatistics */
IQS_SAME(TOKEN_STATISTICS, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE),
/* TokenRestrictedSids */
IQS_SAME(TOKEN_GROUPS, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE),
/* TokenSessionId */
IQS_SAME(ULONG, ULONG, ICIF_QUERY | ICIF_SET),
/* TokenGroupsAndPrivileges */
IQS_SAME(TOKEN_GROUPS_AND_PRIVILEGES, ULONG, ICIF_QUERY | ICIF_QUERY_SIZE_VARIABLE),
/* TokenSessionReference */
IQS_SAME(ULONG, ULONG, ICIF_SET),
/* TokenSandBoxInert */
IQS_SAME(ULONG, ULONG, ICIF_QUERY),
/* TokenAuditPolicy */
IQS_SAME(TOKEN_AUDIT_POLICY_INFORMATION, ULONG, ICIF_SET | ICIF_SET_SIZE_VARIABLE),
/* TokenOrigin */
IQS_SAME(TOKEN_ORIGIN, ULONG, ICIF_QUERY | ICIF_SET),
};
/* PUBLIC FUNCTIONS *****************************************************************/
/**
* @brief
* Queries information details about the given token to the call. The difference
* between NtQueryInformationToken and this routine is that the system call has
* user mode buffer data probing and additional protection checks whereas this
* routine doesn't have any of these. The routine is used exclusively in kernel
* mode.
*
* @param[in] AccessToken
* An access token to be given.
*
* @param[in] TokenInformationClass
* Token information class.
*
* @param[out] TokenInformation
* Buffer with retrieved information. Such information is arbitrary, depending
* on the requested information class.
*
* @return
* Returns STATUS_SUCCESS if the operation to query the desired information
* has completed successfully. STATUS_INSUFFICIENT_RESOURCES is returned if
* pool memory allocation has failed to satisfy an operation. Otherwise
* STATUS_INVALID_INFO_CLASS is returned indicating that the information
* class provided is not supported by the routine.
*
* @remarks
* Only certain information classes are not implemented in this function and
* these are TokenOrigin, TokenGroupsAndPrivileges, TokenRestrictedSids and
* TokenSandBoxInert. The following classes are implemented in NtQueryInformationToken
* only.
*/
NTSTATUS
NTAPI
SeQueryInformationToken(
_In_ PACCESS_TOKEN AccessToken,
_In_ TOKEN_INFORMATION_CLASS TokenInformationClass,
_Outptr_result_buffer_(_Inexpressible_(token-dependent)) PVOID *TokenInformation)
{
NTSTATUS Status;
PTOKEN Token = (PTOKEN)AccessToken;
ULONG RequiredLength;
union
{
PSID PSid;
ULONG Ulong;
} Unused;
PAGED_CODE();
/* Lock the token */
SepAcquireTokenLockShared(Token);
switch (TokenInformationClass)
{
case TokenUser:
{
PTOKEN_USER tu;
DPRINT("SeQueryInformationToken(TokenUser)\n");
RequiredLength = sizeof(TOKEN_USER) +
RtlLengthSid(Token->UserAndGroups[0].Sid);
/* Allocate the output buffer */
tu = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (tu == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
Status = RtlCopySidAndAttributesArray(1,
&Token->UserAndGroups[0],
RequiredLength - sizeof(TOKEN_USER),
&tu->User,
(PSID)(tu + 1),
&Unused.PSid,
&Unused.Ulong);
/* Return the structure */
*TokenInformation = tu;
Status = STATUS_SUCCESS;
break;
}
case TokenGroups:
{
PTOKEN_GROUPS tg;
ULONG SidLen;
PSID Sid;
DPRINT("SeQueryInformationToken(TokenGroups)\n");
RequiredLength = sizeof(tg->GroupCount) +
RtlLengthSidAndAttributes(Token->UserAndGroupCount - 1, &Token->UserAndGroups[1]);
SidLen = RequiredLength - sizeof(tg->GroupCount) -
((Token->UserAndGroupCount - 1) * sizeof(SID_AND_ATTRIBUTES));
/* Allocate the output buffer */
tg = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (tg == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
Sid = (PSID)((ULONG_PTR)tg + sizeof(tg->GroupCount) +
((Token->UserAndGroupCount - 1) * sizeof(SID_AND_ATTRIBUTES)));
tg->GroupCount = Token->UserAndGroupCount - 1;
Status = RtlCopySidAndAttributesArray(Token->UserAndGroupCount - 1,
&Token->UserAndGroups[1],
SidLen,
&tg->Groups[0],
Sid,
&Unused.PSid,
&Unused.Ulong);
/* Return the structure */
*TokenInformation = tg;
Status = STATUS_SUCCESS;
break;
}
case TokenPrivileges:
{
PTOKEN_PRIVILEGES tp;
DPRINT("SeQueryInformationToken(TokenPrivileges)\n");
RequiredLength = sizeof(tp->PrivilegeCount) +
(Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
/* Allocate the output buffer */
tp = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (tp == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
tp->PrivilegeCount = Token->PrivilegeCount;
RtlCopyLuidAndAttributesArray(Token->PrivilegeCount,
Token->Privileges,
&tp->Privileges[0]);
/* Return the structure */
*TokenInformation = tp;
Status = STATUS_SUCCESS;
break;
}
case TokenOwner:
{
PTOKEN_OWNER to;
ULONG SidLen;
DPRINT("SeQueryInformationToken(TokenOwner)\n");
SidLen = RtlLengthSid(Token->UserAndGroups[Token->DefaultOwnerIndex].Sid);
RequiredLength = sizeof(TOKEN_OWNER) + SidLen;
/* Allocate the output buffer */
to = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (to == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
to->Owner = (PSID)(to + 1);
Status = RtlCopySid(SidLen,
to->Owner,
Token->UserAndGroups[Token->DefaultOwnerIndex].Sid);
/* Return the structure */
*TokenInformation = to;
Status = STATUS_SUCCESS;
break;
}
case TokenPrimaryGroup:
{
PTOKEN_PRIMARY_GROUP tpg;
ULONG SidLen;
DPRINT("SeQueryInformationToken(TokenPrimaryGroup)\n");
SidLen = RtlLengthSid(Token->PrimaryGroup);
RequiredLength = sizeof(TOKEN_PRIMARY_GROUP) + SidLen;
/* Allocate the output buffer */
tpg = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (tpg == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
tpg->PrimaryGroup = (PSID)(tpg + 1);
Status = RtlCopySid(SidLen,
tpg->PrimaryGroup,
Token->PrimaryGroup);
/* Return the structure */
*TokenInformation = tpg;
Status = STATUS_SUCCESS;
break;
}
case TokenDefaultDacl:
{
PTOKEN_DEFAULT_DACL tdd;
DPRINT("SeQueryInformationToken(TokenDefaultDacl)\n");
RequiredLength = sizeof(TOKEN_DEFAULT_DACL);
if (Token->DefaultDacl != NULL)
RequiredLength += Token->DefaultDacl->AclSize;
/* Allocate the output buffer */
tdd = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (tdd == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
if (Token->DefaultDacl != NULL)
{
tdd->DefaultDacl = (PACL)(tdd + 1);
RtlCopyMemory(tdd->DefaultDacl,
Token->DefaultDacl,
Token->DefaultDacl->AclSize);
}
else
{
tdd->DefaultDacl = NULL;
}
/* Return the structure */
*TokenInformation = tdd;
Status = STATUS_SUCCESS;
break;
}
case TokenSource:
{
PTOKEN_SOURCE ts;
DPRINT("SeQueryInformationToken(TokenSource)\n");
RequiredLength = sizeof(TOKEN_SOURCE);
/* Allocate the output buffer */
ts = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (ts == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
*ts = Token->TokenSource;
/* Return the structure */
*TokenInformation = ts;
Status = STATUS_SUCCESS;
break;
}
case TokenType:
{
PTOKEN_TYPE tt;
DPRINT("SeQueryInformationToken(TokenType)\n");
RequiredLength = sizeof(TOKEN_TYPE);
/* Allocate the output buffer */
tt = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (tt == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
*tt = Token->TokenType;
/* Return the structure */
*TokenInformation = tt;
Status = STATUS_SUCCESS;
break;
}
case TokenImpersonationLevel:
{
PSECURITY_IMPERSONATION_LEVEL sil;
DPRINT("SeQueryInformationToken(TokenImpersonationLevel)\n");
RequiredLength = sizeof(SECURITY_IMPERSONATION_LEVEL);
/* Fail if the token is not an impersonation token */
if (Token->TokenType != TokenImpersonation)
{
Status = STATUS_INVALID_INFO_CLASS;
break;
}
/* Allocate the output buffer */
sil = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (sil == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
*sil = Token->ImpersonationLevel;
/* Return the structure */
*TokenInformation = sil;
Status = STATUS_SUCCESS;
break;
}
case TokenStatistics:
{
PTOKEN_STATISTICS ts;
DPRINT("SeQueryInformationToken(TokenStatistics)\n");
RequiredLength = sizeof(TOKEN_STATISTICS);
/* Allocate the output buffer */
ts = ExAllocatePoolWithTag(PagedPool, RequiredLength, TAG_SE);
if (ts == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
ts->TokenId = Token->TokenId;
ts->AuthenticationId = Token->AuthenticationId;
ts->ExpirationTime = Token->ExpirationTime;
ts->TokenType = Token->TokenType;
ts->ImpersonationLevel = Token->ImpersonationLevel;
ts->DynamicCharged = Token->DynamicCharged;
ts->DynamicAvailable = Token->DynamicAvailable;
ts->GroupCount = Token->UserAndGroupCount - 1;
ts->PrivilegeCount = Token->PrivilegeCount;
ts->ModifiedId = Token->ModifiedId;
/* Return the structure */
*TokenInformation = ts;
Status = STATUS_SUCCESS;
break;
}
case TokenSessionId:
{
DPRINT("SeQueryInformationToken(TokenSessionId)\n");
Status = SeQuerySessionIdToken(Token, (PULONG)TokenInformation);
break;
}
default:
DPRINT1("SeQueryInformationToken(%d) invalid information class\n", TokenInformationClass);
Status = STATUS_INVALID_INFO_CLASS;
break;
}
/* Release the lock of the token */
SepReleaseTokenLock(Token);
return Status;
}
/* SYSTEM CALLS ***************************************************************/
/**
* @brief
* Queries a specific type of information in regard of an access token based upon
* the information class. The calling thread must have specific access rights in order
* to obtain specific information about the token.
*
* @param[in] TokenHandle
* A handle of a token where information is to be gathered.
*
* @param[in] TokenInformationClass
* Token information class.
*
* @param[out] TokenInformation
* A returned output buffer with token information, which information is arbitrarily upon
* the information class chosen.
*
* @param[in] TokenInformationLength
* Length of the token information buffer, in bytes.
*
* @param[out] ReturnLength
* If specified in the call, the function returns the total length size of the token
* information buffer..
*
* @return
* Returns STATUS_SUCCESS if information querying has completed successfully.
* STATUS_BUFFER_TOO_SMALL is returned if the information length that represents
* the token information buffer is not greater than the required length.
* STATUS_INVALID_HANDLE is returned if the token handle is not a valid one.
* STATUS_INVALID_INFO_CLASS is returned if the information class is not a valid
* one (that is, the class doesn't belong to TOKEN_INFORMATION_CLASS). A failure
* NTSTATUS code is returned otherwise.
*/
_Must_inspect_result_
__kernel_entry
NTSTATUS
NTAPI
NtQueryInformationToken(
_In_ HANDLE TokenHandle,
_In_ TOKEN_INFORMATION_CLASS TokenInformationClass,
_Out_writes_bytes_to_opt_(TokenInformationLength, *ReturnLength)
PVOID TokenInformation,
_In_ ULONG TokenInformationLength,
_Out_ PULONG ReturnLength)
{
NTSTATUS Status;
KPROCESSOR_MODE PreviousMode;
PTOKEN Token;
ULONG RequiredLength;
union
{
PSID PSid;
ULONG Ulong;
} Unused;
PAGED_CODE();
PreviousMode = ExGetPreviousMode();
/* Check buffers and class validity */
Status = DefaultQueryInfoBufferCheck(TokenInformationClass,
SeTokenInformationClass,
RTL_NUMBER_OF(SeTokenInformationClass),
ICIF_PROBE_READ_WRITE | ICIF_FORCE_RETURN_LENGTH_PROBE,
TokenInformation,
TokenInformationLength,
ReturnLength,
NULL,
PreviousMode);
if (!NT_SUCCESS(Status))
{
DPRINT("NtQueryInformationToken() failed, Status: 0x%x\n", Status);
return Status;
}
Status = ObReferenceObjectByHandle(TokenHandle,
(TokenInformationClass == TokenSource) ? TOKEN_QUERY_SOURCE : TOKEN_QUERY,
SeTokenObjectType,
PreviousMode,
(PVOID*)&Token,
NULL);
if (NT_SUCCESS(Status))
{
/* Lock the token */
SepAcquireTokenLockShared(Token);
switch (TokenInformationClass)
{
case TokenUser:
{
PTOKEN_USER tu = (PTOKEN_USER)TokenInformation;
DPRINT("NtQueryInformationToken(TokenUser)\n");
RequiredLength = sizeof(TOKEN_USER) +
RtlLengthSid(Token->UserAndGroups[0].Sid);
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
Status = RtlCopySidAndAttributesArray(1,
&Token->UserAndGroups[0],
RequiredLength - sizeof(TOKEN_USER),
&tu->User,
(PSID)(tu + 1),
&Unused.PSid,
&Unused.Ulong);
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenGroups:
{
PTOKEN_GROUPS tg = (PTOKEN_GROUPS)TokenInformation;
DPRINT("NtQueryInformationToken(TokenGroups)\n");
RequiredLength = sizeof(tg->GroupCount) +
RtlLengthSidAndAttributes(Token->UserAndGroupCount - 1, &Token->UserAndGroups[1]);
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
ULONG SidLen = RequiredLength - sizeof(tg->GroupCount) -
((Token->UserAndGroupCount - 1) * sizeof(SID_AND_ATTRIBUTES));
PSID Sid = (PSID_AND_ATTRIBUTES)((ULONG_PTR)tg + sizeof(tg->GroupCount) +
((Token->UserAndGroupCount - 1) * sizeof(SID_AND_ATTRIBUTES)));
tg->GroupCount = Token->UserAndGroupCount - 1;
Status = RtlCopySidAndAttributesArray(Token->UserAndGroupCount - 1,
&Token->UserAndGroups[1],
SidLen,
&tg->Groups[0],
Sid,
&Unused.PSid,
&Unused.Ulong);
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenPrivileges:
{
PTOKEN_PRIVILEGES tp = (PTOKEN_PRIVILEGES)TokenInformation;
DPRINT("NtQueryInformationToken(TokenPrivileges)\n");
RequiredLength = sizeof(tp->PrivilegeCount) +
(Token->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
tp->PrivilegeCount = Token->PrivilegeCount;
RtlCopyLuidAndAttributesArray(Token->PrivilegeCount,
Token->Privileges,
&tp->Privileges[0]);
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenOwner:
{
PTOKEN_OWNER to = (PTOKEN_OWNER)TokenInformation;
ULONG SidLen;
DPRINT("NtQueryInformationToken(TokenOwner)\n");
SidLen = RtlLengthSid(Token->UserAndGroups[Token->DefaultOwnerIndex].Sid);
RequiredLength = sizeof(TOKEN_OWNER) + SidLen;
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
to->Owner = (PSID)(to + 1);
Status = RtlCopySid(SidLen,
to->Owner,
Token->UserAndGroups[Token->DefaultOwnerIndex].Sid);
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenPrimaryGroup:
{
PTOKEN_PRIMARY_GROUP tpg = (PTOKEN_PRIMARY_GROUP)TokenInformation;
ULONG SidLen;
DPRINT("NtQueryInformationToken(TokenPrimaryGroup)\n");
SidLen = RtlLengthSid(Token->PrimaryGroup);
RequiredLength = sizeof(TOKEN_PRIMARY_GROUP) + SidLen;
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
tpg->PrimaryGroup = (PSID)(tpg + 1);
Status = RtlCopySid(SidLen,
tpg->PrimaryGroup,
Token->PrimaryGroup);
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenDefaultDacl:
{
PTOKEN_DEFAULT_DACL tdd = (PTOKEN_DEFAULT_DACL)TokenInformation;
DPRINT("NtQueryInformationToken(TokenDefaultDacl)\n");
RequiredLength = sizeof(TOKEN_DEFAULT_DACL);
if (Token->DefaultDacl != NULL)
RequiredLength += Token->DefaultDacl->AclSize;
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
if (Token->DefaultDacl != NULL)
{
tdd->DefaultDacl = (PACL)(tdd + 1);
RtlCopyMemory(tdd->DefaultDacl,
Token->DefaultDacl,
Token->DefaultDacl->AclSize);
}
else
{
tdd->DefaultDacl = NULL;
}
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenSource:
{
PTOKEN_SOURCE ts = (PTOKEN_SOURCE)TokenInformation;
DPRINT("NtQueryInformationToken(TokenSource)\n");
RequiredLength = sizeof(TOKEN_SOURCE);
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
*ts = Token->TokenSource;
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenType:
{
PTOKEN_TYPE tt = (PTOKEN_TYPE)TokenInformation;
DPRINT("NtQueryInformationToken(TokenType)\n");
RequiredLength = sizeof(TOKEN_TYPE);
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
*tt = Token->TokenType;
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenImpersonationLevel:
{
PSECURITY_IMPERSONATION_LEVEL sil = (PSECURITY_IMPERSONATION_LEVEL)TokenInformation;
DPRINT("NtQueryInformationToken(TokenImpersonationLevel)\n");
/* Fail if the token is not an impersonation token */
if (Token->TokenType != TokenImpersonation)
{
Status = STATUS_INVALID_INFO_CLASS;
break;
}
RequiredLength = sizeof(SECURITY_IMPERSONATION_LEVEL);
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
*sil = Token->ImpersonationLevel;
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenStatistics:
{
PTOKEN_STATISTICS ts = (PTOKEN_STATISTICS)TokenInformation;
DPRINT("NtQueryInformationToken(TokenStatistics)\n");
RequiredLength = sizeof(TOKEN_STATISTICS);
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
ts->TokenId = Token->TokenId;
ts->AuthenticationId = Token->AuthenticationId;
ts->ExpirationTime = Token->ExpirationTime;
ts->TokenType = Token->TokenType;
ts->ImpersonationLevel = Token->ImpersonationLevel;
ts->DynamicCharged = Token->DynamicCharged;
ts->DynamicAvailable = Token->DynamicAvailable;
ts->GroupCount = Token->UserAndGroupCount - 1;
ts->PrivilegeCount = Token->PrivilegeCount;
ts->ModifiedId = Token->ModifiedId;
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenOrigin:
{
PTOKEN_ORIGIN to = (PTOKEN_ORIGIN)TokenInformation;
DPRINT("NtQueryInformationToken(TokenOrigin)\n");
RequiredLength = sizeof(TOKEN_ORIGIN);
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
to->OriginatingLogonSession = Token->AuthenticationId;
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenGroupsAndPrivileges:
DPRINT1("NtQueryInformationToken(TokenGroupsAndPrivileges) not implemented\n");
Status = STATUS_NOT_IMPLEMENTED;
break;
case TokenRestrictedSids:
{
PTOKEN_GROUPS tg = (PTOKEN_GROUPS)TokenInformation;
DPRINT("NtQueryInformationToken(TokenRestrictedSids)\n");
RequiredLength = sizeof(tg->GroupCount) +
RtlLengthSidAndAttributes(Token->RestrictedSidCount, Token->RestrictedSids);
_SEH2_TRY
{
if (TokenInformationLength >= RequiredLength)
{
ULONG SidLen = RequiredLength - sizeof(tg->GroupCount) -
(Token->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES));
PSID Sid = (PSID)((ULONG_PTR)tg + sizeof(tg->GroupCount) +
(Token->RestrictedSidCount * sizeof(SID_AND_ATTRIBUTES)));
tg->GroupCount = Token->RestrictedSidCount;
Status = RtlCopySidAndAttributesArray(Token->RestrictedSidCount,
Token->RestrictedSids,
SidLen,
&tg->Groups[0],
Sid,
&Unused.PSid,
&Unused.Ulong);
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
if (ReturnLength != NULL)
{
*ReturnLength = RequiredLength;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
break;
}
case TokenSandBoxInert:
DPRINT1("NtQueryInformationToken(TokenSandboxInert) not implemented\n");
Status = STATUS_NOT_IMPLEMENTED;
break;
case TokenSessionId:
{
ULONG SessionId = 0;
DPRINT("NtQueryInformationToken(TokenSessionId)\n");
Status = SeQuerySessionIdToken(Token, &SessionId);
if (NT_SUCCESS(Status))
{
_SEH2_TRY
{
/* Buffer size was already verified, no need to check here again */
*(PULONG)TokenInformation = SessionId;
if (ReturnLength != NULL)
{
*ReturnLength = sizeof(ULONG);
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
break;
}
default:
DPRINT1("NtQueryInformationToken(%d) invalid information class\n", TokenInformationClass);
Status = STATUS_INVALID_INFO_CLASS;
break;
}
/* Unlock and dereference the token */
SepReleaseTokenLock(Token);
ObDereferenceObject(Token);
}
return Status;
}
/**
* @unimplemented
* @brief
* Sets (modifies) some specific information in regard of an access token. The
* calling thread must have specific access rights in order to modify token's
* information data.
*
* @param[in] TokenHandle
* A handle of a token where information is to be modified.
*
* @param[in] TokenInformationClass
* Token information class.
*
* @param[in] TokenInformation
* An arbitrary pointer to a buffer with token information to set. Such
* arbitrary buffer depends on the information class chosen that the caller
* wants to modify such information data of a token.
*
* @param[in] TokenInformationLength
* Length of the token information buffer, in bytes.
*
* @return
* Returns STATUS_SUCCESS if information setting has completed successfully.
* STATUS_INFO_LENGTH_MISMATCH is returned if the information length of the
* buffer is less than the required length. STATUS_INSUFFICIENT_RESOURCES is
* returned if memory pool allocation has failed. STATUS_PRIVILEGE_NOT_HELD
* is returned if the calling thread hasn't the required privileges to perform
* the operation in question. A failure NTSTATUS code is returned otherwise.
*
* @remarks
* The function is partly implemented, mainly TokenOrigin and TokenDefaultDacl.
*/
_Must_inspect_result_
__kernel_entry
NTSTATUS
NTAPI
NtSetInformationToken(
_In_ HANDLE TokenHandle,
_In_ TOKEN_INFORMATION_CLASS TokenInformationClass,
_In_reads_bytes_(TokenInformationLength) PVOID TokenInformation,
_In_ ULONG TokenInformationLength)
{
NTSTATUS Status;
PTOKEN Token;
KPROCESSOR_MODE PreviousMode;
ULONG NeededAccess = TOKEN_ADJUST_DEFAULT;
PAGED_CODE();
PreviousMode = ExGetPreviousMode();
Status = DefaultSetInfoBufferCheck(TokenInformationClass,
SeTokenInformationClass,
RTL_NUMBER_OF(SeTokenInformationClass),
TokenInformation,
TokenInformationLength,
PreviousMode);
if (!NT_SUCCESS(Status))
{
/* Invalid buffers */
DPRINT("NtSetInformationToken() failed, Status: 0x%x\n", Status);
return Status;
}
if (TokenInformationClass == TokenSessionId)
{
NeededAccess |= TOKEN_ADJUST_SESSIONID;
}
Status = ObReferenceObjectByHandle(TokenHandle,
NeededAccess,
SeTokenObjectType,
PreviousMode,
(PVOID*)&Token,
NULL);
if (NT_SUCCESS(Status))
{
switch (TokenInformationClass)
{
case TokenOwner:
{
if (TokenInformationLength >= sizeof(TOKEN_OWNER))
{
PTOKEN_OWNER to = (PTOKEN_OWNER)TokenInformation;
PSID InputSid = NULL, CapturedSid;
ULONG DefaultOwnerIndex;
_SEH2_TRY
{
InputSid = to->Owner;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
_SEH2_YIELD(goto Cleanup);
}
_SEH2_END;
Status = SepCaptureSid(InputSid,
PreviousMode,
PagedPool,
FALSE,
&CapturedSid);
if (NT_SUCCESS(Status))
{
/* Lock the token */
SepAcquireTokenLockExclusive(Token);
/* Find the owner amongst the existing token user and groups */
Status = SepFindPrimaryGroupAndDefaultOwner(Token,
NULL,
CapturedSid,
NULL,
&DefaultOwnerIndex);
if (NT_SUCCESS(Status))
{
/* Found it */
Token->DefaultOwnerIndex = DefaultOwnerIndex;
ExAllocateLocallyUniqueId(&Token->ModifiedId);
}
/* Unlock the token */
SepReleaseTokenLock(Token);
SepReleaseSid(CapturedSid,
PreviousMode,
FALSE);
}
}
else
{
Status = STATUS_INFO_LENGTH_MISMATCH;
}
break;
}
case TokenPrimaryGroup:
{
if (TokenInformationLength >= sizeof(TOKEN_PRIMARY_GROUP))
{
PTOKEN_PRIMARY_GROUP tpg = (PTOKEN_PRIMARY_GROUP)TokenInformation;
PSID InputSid = NULL, CapturedSid;
ULONG PrimaryGroupIndex;
_SEH2_TRY
{
InputSid = tpg->PrimaryGroup;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
_SEH2_YIELD(goto Cleanup);
}
_SEH2_END;
Status = SepCaptureSid(InputSid,
PreviousMode,
PagedPool,
FALSE,
&CapturedSid);
if (NT_SUCCESS(Status))
{
/* Lock the token */
SepAcquireTokenLockExclusive(Token);
/* Find the primary group amongst the existing token user and groups */
Status = SepFindPrimaryGroupAndDefaultOwner(Token,
CapturedSid,
NULL,
&PrimaryGroupIndex,
NULL);
if (NT_SUCCESS(Status))
{
/* Found it */
Token->PrimaryGroup = Token->UserAndGroups[PrimaryGroupIndex].Sid;
ExAllocateLocallyUniqueId(&Token->ModifiedId);
}
/* Unlock the token */
SepReleaseTokenLock(Token);
SepReleaseSid(CapturedSid,
PreviousMode,
FALSE);
}
}
else
{
Status = STATUS_INFO_LENGTH_MISMATCH;
}
break;
}
case TokenDefaultDacl:
{
if (TokenInformationLength >= sizeof(TOKEN_DEFAULT_DACL))
{
PTOKEN_DEFAULT_DACL tdd = (PTOKEN_DEFAULT_DACL)TokenInformation;
PACL InputAcl = NULL;
_SEH2_TRY
{
InputAcl = tdd->DefaultDacl;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
_SEH2_YIELD(goto Cleanup);
}
_SEH2_END;
if (InputAcl != NULL)
{
PACL CapturedAcl;
/* Capture, validate, and copy the DACL */
Status = SepCaptureAcl(InputAcl,
PreviousMode,
PagedPool,
TRUE,
&CapturedAcl);
if (NT_SUCCESS(Status))
{
ULONG DynamicLength;
/* Lock the token */
SepAcquireTokenLockExclusive(Token);
//
// NOTE: So far our dynamic area only contains
// the default dacl, so this makes the following
// code pretty simple. The day where it stores
// other data, the code will require adaptations.
//
DynamicLength = Token->DynamicAvailable;
// Add here any other data length present in the dynamic area...
if (Token->DefaultDacl)
DynamicLength += Token->DefaultDacl->AclSize;
/* Reallocate the dynamic area if it is too small */
Status = STATUS_SUCCESS;
if ((DynamicLength < CapturedAcl->AclSize) ||
(Token->DynamicPart == NULL))
{
PVOID NewDynamicPart;
NewDynamicPart = ExAllocatePoolWithTag(PagedPool,
CapturedAcl->AclSize,
TAG_TOKEN_DYNAMIC);
if (NewDynamicPart == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
if (Token->DynamicPart != NULL)
{
// RtlCopyMemory(NewDynamicPart, Token->DynamicPart, DynamicLength);
ExFreePoolWithTag(Token->DynamicPart, TAG_TOKEN_DYNAMIC);
}
Token->DynamicPart = NewDynamicPart;
Token->DynamicAvailable = 0;
}
}
else
{
Token->DynamicAvailable = DynamicLength - CapturedAcl->AclSize;
}
if (NT_SUCCESS(Status))
{
/* Set the new dacl */
Token->DefaultDacl = (PVOID)Token->DynamicPart;
RtlCopyMemory(Token->DefaultDacl,
CapturedAcl,
CapturedAcl->AclSize);
ExAllocateLocallyUniqueId(&Token->ModifiedId);
}
/* Unlock the token */
SepReleaseTokenLock(Token);
ExFreePoolWithTag(CapturedAcl, TAG_ACL);
}
}
else
{
/* Lock the token */
SepAcquireTokenLockExclusive(Token);
/* Clear the default dacl if present */
if (Token->DefaultDacl != NULL)
{
Token->DynamicAvailable += Token->DefaultDacl->AclSize;
RtlZeroMemory(Token->DefaultDacl, Token->DefaultDacl->AclSize);
Token->DefaultDacl = NULL;
ExAllocateLocallyUniqueId(&Token->ModifiedId);
}
/* Unlock the token */
SepReleaseTokenLock(Token);
}
}
else
{
Status = STATUS_INFO_LENGTH_MISMATCH;
}
break;
}
case TokenSessionId:
{
ULONG SessionId = 0;
_SEH2_TRY
{
/* Buffer size was already verified, no need to check here again */
SessionId = *(PULONG)TokenInformation;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
_SEH2_YIELD(goto Cleanup);
}
_SEH2_END;
/* Check for TCB privilege */
if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode))
{
Status = STATUS_PRIVILEGE_NOT_HELD;
break;
}
/* Lock the token */
SepAcquireTokenLockExclusive(Token);
Token->SessionId = SessionId;
ExAllocateLocallyUniqueId(&Token->ModifiedId);
/* Unlock the token */
SepReleaseTokenLock(Token);
break;
}
case TokenSessionReference:
{
ULONG SessionReference;
_SEH2_TRY
{
/* Buffer size was already verified, no need to check here again */
SessionReference = *(PULONG)TokenInformation;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
_SEH2_YIELD(goto Cleanup);
}
_SEH2_END;
/* Check for TCB privilege */
if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode))
{
Status = STATUS_PRIVILEGE_NOT_HELD;
goto Cleanup;
}
/* Check if it is 0 */
if (SessionReference == 0)
{
ULONG OldTokenFlags;
/* Lock the token */
SepAcquireTokenLockExclusive(Token);
/* Atomically set the flag in the token */
OldTokenFlags = RtlInterlockedSetBits(&Token->TokenFlags,
TOKEN_SESSION_NOT_REFERENCED);
/*
* If the flag was already set, do not dereference again
* the logon session. Use SessionReference as an indicator
* to know whether to really dereference the session.
*/
if (OldTokenFlags == Token->TokenFlags)
SessionReference = ULONG_MAX;
/*
* Otherwise if the flag was never set but just for this first time then
* remove the referenced logon session data from the token and dereference
* the logon session when needed.
*/
if (SessionReference == 0)
{
SepRmRemoveLogonSessionFromToken(Token);
SepRmDereferenceLogonSession(&Token->AuthenticationId);
}
/* Unlock the token */
SepReleaseTokenLock(Token);
}
break;
}
case TokenAuditPolicy:
{
PTOKEN_AUDIT_POLICY_INFORMATION PolicyInformation =
(PTOKEN_AUDIT_POLICY_INFORMATION)TokenInformation;
SEP_AUDIT_POLICY AuditPolicy;
ULONG i;
_SEH2_TRY
{
ProbeForRead(PolicyInformation,
FIELD_OFFSET(TOKEN_AUDIT_POLICY_INFORMATION,
Policies[PolicyInformation->PolicyCount]),
sizeof(ULONG));
/* Loop all policies in the structure */
for (i = 0; i < PolicyInformation->PolicyCount; i++)
{
/* Set the corresponding bits in the packed structure */
switch (PolicyInformation->Policies[i].Category)
{
case AuditCategorySystem:
AuditPolicy.PolicyElements.System = PolicyInformation->Policies[i].Value;
break;
case AuditCategoryLogon:
AuditPolicy.PolicyElements.Logon = PolicyInformation->Policies[i].Value;
break;
case AuditCategoryObjectAccess:
AuditPolicy.PolicyElements.ObjectAccess = PolicyInformation->Policies[i].Value;
break;
case AuditCategoryPrivilegeUse:
AuditPolicy.PolicyElements.PrivilegeUse = PolicyInformation->Policies[i].Value;
break;
case AuditCategoryDetailedTracking:
AuditPolicy.PolicyElements.DetailedTracking = PolicyInformation->Policies[i].Value;
break;
case AuditCategoryPolicyChange:
AuditPolicy.PolicyElements.PolicyChange = PolicyInformation->Policies[i].Value;
break;
case AuditCategoryAccountManagement:
AuditPolicy.PolicyElements.AccountManagement = PolicyInformation->Policies[i].Value;
break;
case AuditCategoryDirectoryServiceAccess:
AuditPolicy.PolicyElements.DirectoryServiceAccess = PolicyInformation->Policies[i].Value;
break;
case AuditCategoryAccountLogon:
AuditPolicy.PolicyElements.AccountLogon = PolicyInformation->Policies[i].Value;
break;
}
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
_SEH2_YIELD(goto Cleanup);
}
_SEH2_END;
/* Check for TCB privilege */
if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode))
{
Status = STATUS_PRIVILEGE_NOT_HELD;
break;
}
/* Lock the token */
SepAcquireTokenLockExclusive(Token);
/* Set the new audit policy */
Token->AuditPolicy = AuditPolicy;
ExAllocateLocallyUniqueId(&Token->ModifiedId);
/* Unlock the token */
SepReleaseTokenLock(Token);
break;
}
case TokenOrigin:
{
TOKEN_ORIGIN TokenOrigin;
_SEH2_TRY
{
/* Copy the token origin */
TokenOrigin = *(PTOKEN_ORIGIN)TokenInformation;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
_SEH2_YIELD(goto Cleanup);
}
_SEH2_END;
/* Check for TCB privilege */
if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode))
{
Status = STATUS_PRIVILEGE_NOT_HELD;
break;
}
/* Lock the token */
SepAcquireTokenLockExclusive(Token);
/* Check if there is no token origin set yet */
if (RtlIsZeroLuid(&Token->OriginatingLogonSession))
{
/* Set the token origin */
Token->OriginatingLogonSession =
TokenOrigin.OriginatingLogonSession;
ExAllocateLocallyUniqueId(&Token->ModifiedId);
}
/* Unlock the token */
SepReleaseTokenLock(Token);
break;
}
default:
{
DPRINT1("Invalid TokenInformationClass: 0x%lx\n",
TokenInformationClass);
Status = STATUS_INVALID_INFO_CLASS;
break;
}
}
Cleanup:
ObDereferenceObject(Token);
}
if (!NT_SUCCESS(Status))
{
DPRINT1("NtSetInformationToken failed with Status 0x%lx\n", Status);
}
return Status;
}
/* EOF */