https://git.reactos.org/?p=reactos.git;a=commitdiff;h=51279c3e44459f120dc4d…
commit 51279c3e44459f120dc4db650264d1d0b69821a3
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Fri Jun 2 13:00:28 2023 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Jun 4 11:09:34 2023 +0200
[NTOS:SE] Refactor NtOpenThreadTokenEx
- Wrap most of the code into a new private routine, SepOpenThreadToken.
And properly fail gracefully if we fail to open a thread's token instead of just keeping going.
- Do not use the same thread object that we have referenced in NtOpenThreadTokenEx
to do a copy of the access token in case we can't open it directly.
Instead we must reference a new object with full access, solely used for
the purpose to do our required operations.
- Add debug prints
CORE-18986
---
ntoskrnl/se/token.c | 375 +++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 281 insertions(+), 94 deletions(-)
diff --git a/ntoskrnl/se/token.c b/ntoskrnl/se/token.c
index d5b7555ac07..c048294c868 100644
--- a/ntoskrnl/se/token.c
+++ b/ntoskrnl/se/token.c
@@ -1101,6 +1101,252 @@ SepFindPrimaryGroupAndDefaultOwner(
return STATUS_SUCCESS;
}
+/**
+ * @brief
+ * Internal private function that returns an opened handle
+ * of an access token associated with a thread.
+ *
+ * @param[in] Thread
+ * A pointer to a Executive thread. This parameter is used to
+ * validate that the newly obtained thread in this function
+ * hasn't diverged. This could potentially lead to a scenario
+ * that we might get an access token from a different token
+ * which is not what we want. The validation is performed
+ * if the token has to copied and can't be opened directly.
+ *
+ * @param[in] ThreadHandle
+ * A handle to a thread, of which an access token is to be opened
+ * and given from that thread.
+ *
+ * @param[in] ThreadToken
+ * A pointer to an access token associated with the specific thread.
+ * The function assumes that the token is an impersonation one
+ * prior the calling of this function.
+ *
+ * @param[in] DesiredAccess
+ * The desired access rights for the access token.
+ *
+ * @param[in] HandleAttributes
+ * Handle attributes of which they are used for the newly creation
+ * of the opened thread token. The function assumes that they have
+ * been validated prior the calling of this function.
+ *
+ * @param[in] EffectiveOnly
+ * If set to TRUE, the function will copy a new access token with
+ * privileges and groups that are effectively enabled. Any disabled
+ * privilege or group is removed from the copied token. Otherwise
+ * if set to FALSE, the function retains all the enabled and disabled
+ * privielges and groups.
+ *
+ * @param[in] CopyOnOpen
+ * If set to TRUE, it tells the function that the access token cannot
+ * be directly opened due to the security impersonation info of the
+ * associated thread being enforced. In this case the function will
+ * make a copy of the said token by duplicating it. Otherwise if set
+ * to FALSE, the function will just open the access token directly.
+ *
+ * @param[in] ImpersonationLevel
+ * The security impersonation level, at which it is allowed to
+ * access the token.
+ *
+ * @param[in] PreviousMode
+ * The processor request level mode.
+ *
+ * @param[out] OpenedTokenHandle
+ * A pointer to an opened access token handle associated with the
+ * specific thread, returned to the caller. Initially this parameter
+ * is set to NULL and if the function fails to open the thread's token,
+ * it will stay NULL.
+ *
+ * @return
+ * Returns STATUS_SUCCESS if the function has successfully opened the thread's
+ * token. STATUS_OBJECT_TYPE_MISMATCH is returned if the obtained thread object
+ * no longer matches with the other thread that has been obtained previously.
+ * STATUS_NO_TOKEN is returned if the associated thread's process has no
+ * primary access token. A failure NTSTATUS code is returned otherwise.
+ */
+static
+NTSTATUS
+SepOpenThreadToken(
+ _In_ PETHREAD Thread,
+ _In_ HANDLE ThreadHandle,
+ _In_ PTOKEN ThreadToken,
+ _In_ ACCESS_MASK DesiredAccess,
+ _In_ ULONG HandleAttributes,
+ _In_ BOOLEAN EffectiveOnly,
+ _In_ BOOLEAN CopyOnOpen,
+ _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
+ _In_ KPROCESSOR_MODE PreviousMode,
+ _Out_ PHANDLE OpenedTokenHandle)
+{
+ NTSTATUS Status;
+ HANDLE TokenHandle;
+ PETHREAD Thread2;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ PTOKEN NewToken, PrimaryToken;
+ SECURITY_DESCRIPTOR SecurityDescriptor;
+ PACL Dacl;
+
+ PAGED_CODE();
+
+ /* Assume no opened token handle at first */
+ *OpenedTokenHandle = NULL;
+
+ /* Check if we have to do a copy of the token on open or not */
+ if (!CopyOnOpen)
+ {
+ /* Just open the thread's token directly */
+ Status = ObOpenObjectByPointer(ThreadToken,
+ HandleAttributes,
+ NULL,
+ DesiredAccess,
+ SeTokenObjectType,
+ PreviousMode,
+ &TokenHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to open the thread's token object (Status 0x%lx)\n", Status);
+ return Status;
+ }
+
+ /* Give it to caller */
+ *OpenedTokenHandle = TokenHandle;
+ return STATUS_SUCCESS;
+ }
+
+ /*
+ * The caller asks to do a copy of that token whilst it's opened.
+ * Obtain a thread object again but this time we have to obtain
+ * it in our side, kernel mode, and request all the access needed
+ * to do a copy of the token because the original thread only has
+ * query access needed for access token validation.
+ */
+ Status = ObReferenceObjectByHandle(ThreadHandle,
+ THREAD_ALL_ACCESS,
+ PsThreadType,
+ KernelMode,
+ (PVOID*)&Thread2,
+ NULL);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to reference the object thread (Status 0x%lx)\n", Status);
+ return Status;
+ }
+
+ /* Check that one of the threads hasn't diverged */
+ if (Thread != Thread2)
+ {
+ DPRINT1("One of the threads aren't the same (original thread 0x%p, thread 0x%p)\n", Thread, Thread2);
+ ObDereferenceObject(Thread2);
+ return STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ /* Reference the primary token of the process' thread */
+ PrimaryToken = PsReferencePrimaryToken(Thread2->ThreadsProcess);
+ if (!PrimaryToken)
+ {
+ DPRINT1("Failed to reference the primary token of thread\n");
+ ObDereferenceObject(Thread2);
+ return STATUS_NO_TOKEN;
+ }
+
+ /* Create an impersonation DACL from the tokens we got */
+ Status = SepCreateImpersonationTokenDacl(ThreadToken, PrimaryToken, &Dacl);
+ ObFastDereferenceObject(&Thread2->ThreadsProcess->Token, PrimaryToken);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to create an impersonation token DACL (Status 0x%lx)\n", Status);
+ ObDereferenceObject(Thread2);
+ return Status;
+ }
+
+ /* Create a security descriptor with the DACL we got */
+ Status = RtlCreateSecurityDescriptor(&SecurityDescriptor,
+ SECURITY_DESCRIPTOR_REVISION);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to create a security descriptor (Status 0x%lx)\n", Status);
+ ExFreePoolWithTag(Dacl, TAG_ACL);
+ ObDereferenceObject(Thread2);
+ return Status;
+ }
+
+ /* Attach the DACL to that security descriptor */
+ Status = RtlSetDaclSecurityDescriptor(&SecurityDescriptor,
+ TRUE,
+ Dacl,
+ FALSE);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to set the DACL to the security descriptor (Status 0x%lx)\n", Status);
+ ExFreePoolWithTag(Dacl, TAG_ACL);
+ ObDereferenceObject(Thread2);
+ return Status;
+ }
+
+ /*
+ * Initialize the object attributes for the token we
+ * are going to duplicate.
+ */
+ InitializeObjectAttributes(&ObjectAttributes,
+ NULL,
+ HandleAttributes,
+ NULL,
+ &SecurityDescriptor);
+
+ /* Duplicate (copy) it now */
+ Status = SepDuplicateToken(ThreadToken,
+ &ObjectAttributes,
+ EffectiveOnly,
+ TokenImpersonation,
+ ImpersonationLevel,
+ KernelMode,
+ &NewToken);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to duplicate the token (Status 0x%lx)\n", Status);
+ ExFreePoolWithTag(Dacl, TAG_ACL);
+ ObDereferenceObject(Thread2);
+ return Status;
+ }
+
+ /* Insert that copied token into the handle now */
+ ObReferenceObject(NewToken);
+ Status = ObInsertObject(NewToken,
+ NULL,
+ DesiredAccess,
+ 0,
+ NULL,
+ &TokenHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to insert the token object (Status 0x%lx)\n", Status);
+ ExFreePoolWithTag(Dacl, TAG_ACL);
+ ObDereferenceObject(NewToken);
+ ObDereferenceObject(Thread2);
+ return Status;
+ }
+
+ /* We're almost done, free the DACL if we got one */
+ ExFreePoolWithTag(Dacl, TAG_ACL);
+
+ /* Impersonate the client finally */
+ Status = PsImpersonateClient(Thread, NewToken, FALSE, EffectiveOnly, ImpersonationLevel);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Failed to impersonate the client (Status 0x%lx)\n", Status);
+ ObDereferenceObject(NewToken);
+ ObDereferenceObject(Thread2);
+ return Status;
+ }
+
+ /* Give the newly opened token handle to caller */
+ *OpenedTokenHandle = TokenHandle;
+ ObDereferenceObject(NewToken);
+ ObDereferenceObject(Thread2);
+ return Status;
+}
+
/**
* @brief
* Subtracts a token in exchange of duplicating a new one.
@@ -2085,13 +2331,10 @@ NtOpenThreadTokenEx(
{
PETHREAD Thread;
HANDLE hToken;
- PTOKEN Token, NewToken = NULL, PrimaryToken;
+ PTOKEN Token;
BOOLEAN CopyOnOpen, EffectiveOnly;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
SE_IMPERSONATION_STATE ImpersonationState;
- OBJECT_ATTRIBUTES ObjectAttributes;
- SECURITY_DESCRIPTOR SecurityDescriptor;
- PACL Dacl = NULL;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status;
BOOLEAN RestoreImpersonation = FALSE;
@@ -2100,6 +2343,7 @@ NtOpenThreadTokenEx(
PreviousMode = ExGetPreviousMode();
+ /* Ensure that we can give the handle to the caller */
if (PreviousMode != KernelMode)
{
_SEH2_TRY
@@ -2119,138 +2363,81 @@ NtOpenThreadTokenEx(
/*
* At first open the thread token for information access and verify
- * that the token associated with thread is valid.
+ * that the token associated with the thread is valid.
*/
-
Status = ObReferenceObjectByHandle(ThreadHandle, THREAD_QUERY_INFORMATION,
PsThreadType, PreviousMode, (PVOID*)&Thread,
NULL);
if (!NT_SUCCESS(Status))
{
+ DPRINT1("Failed to reference the object thread (Status 0x%lx)\n", Status);
return Status;
}
+ /* Reference the token from the thread */
Token = PsReferenceImpersonationToken(Thread, &CopyOnOpen, &EffectiveOnly,
&ImpersonationLevel);
if (Token == NULL)
{
+ DPRINT("Failed to reference the thread's impersonation token, thread has no token\n");
ObDereferenceObject(Thread);
return STATUS_NO_TOKEN;
}
+ /* Ensure the token has no anonymous security */
if (ImpersonationLevel == SecurityAnonymous)
{
+ DPRINT1("The thread token has anonymous security, can't open it\n");
PsDereferenceImpersonationToken(Token);
ObDereferenceObject(Thread);
return STATUS_CANT_OPEN_ANONYMOUS;
}
- /*
- * Revert to self if OpenAsSelf is specified.
- */
-
+ /* Revert to self if OpenAsSelf is specified */
if (OpenAsSelf)
{
RestoreImpersonation = PsDisableImpersonation(PsGetCurrentThread(),
&ImpersonationState);
}
- if (CopyOnOpen)
- {
- PrimaryToken = PsReferencePrimaryToken(Thread->ThreadsProcess);
-
- Status = SepCreateImpersonationTokenDacl(Token, PrimaryToken, &Dacl);
-
- ObFastDereferenceObject(&Thread->ThreadsProcess->Token, PrimaryToken);
-
- if (NT_SUCCESS(Status))
- {
- if (Dacl)
- {
- Status = RtlCreateSecurityDescriptor(&SecurityDescriptor,
- SECURITY_DESCRIPTOR_REVISION);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("NtOpenThreadTokenEx(): Failed to create a security descriptor (Status 0x%lx)\n", Status);
- }
-
- Status = RtlSetDaclSecurityDescriptor(&SecurityDescriptor, TRUE, Dacl,
- FALSE);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("NtOpenThreadTokenEx(): Failed to set a DACL to the security descriptor (Status 0x%lx)\n", Status);
- }
- }
-
- InitializeObjectAttributes(&ObjectAttributes, NULL, HandleAttributes,
- NULL, Dacl ? &SecurityDescriptor : NULL);
-
- Status = SepDuplicateToken(Token, &ObjectAttributes, EffectiveOnly,
- TokenImpersonation, ImpersonationLevel,
- KernelMode, &NewToken);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("NtOpenThreadTokenEx(): Failed to duplicate the token (Status 0x%lx)\n", Status);
- }
-
- ObReferenceObject(NewToken);
- Status = ObInsertObject(NewToken, NULL, DesiredAccess, 0, NULL,
- &hToken);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("NtOpenThreadTokenEx(): Failed to insert the token object (Status 0x%lx)\n", Status);
- }
- }
- else
- {
- DPRINT1("NtOpenThreadTokenEx(): Failed to impersonate token from DACL (Status 0x%lx)\n", Status);
- }
- }
- else
- {
- Status = ObOpenObjectByPointer(Token, HandleAttributes,
- NULL, DesiredAccess, SeTokenObjectType,
- PreviousMode, &hToken);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("NtOpenThreadTokenEx(): Failed to open the object (Status 0x%lx)\n", Status);
- }
- }
-
- if (Dacl) ExFreePoolWithTag(Dacl, TAG_ACL);
-
+ /* Call the private function to do the job */
+ Status = SepOpenThreadToken(Thread,
+ ThreadHandle,
+ Token,
+ DesiredAccess,
+ HandleAttributes,
+ EffectiveOnly,
+ CopyOnOpen,
+ ImpersonationLevel,
+ PreviousMode,
+ &hToken);
+
+ /* Restore the impersonation back if needed */
if (RestoreImpersonation)
{
PsRestoreImpersonation(PsGetCurrentThread(), &ImpersonationState);
}
+ /* Dereference the access token and the associated thread */
ObDereferenceObject(Token);
+ ObDereferenceObject(Thread);
- if (NT_SUCCESS(Status) && CopyOnOpen)
+ if (!NT_SUCCESS(Status))
{
- Status = PsImpersonateClient(Thread, NewToken, FALSE, EffectiveOnly, ImpersonationLevel);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("NtOpenThreadTokenEx(): Failed to impersonate the client (Status 0x%lx)\n", Status);
- }
+ DPRINT1("Failed to open the thread's token (Status 0x%lx)\n", Status);
+ return Status;
}
- if (NewToken) ObDereferenceObject(NewToken);
-
- ObDereferenceObject(Thread);
-
- if (NT_SUCCESS(Status))
+ /* Give the opened token handle to the caller */
+ _SEH2_TRY
{
- _SEH2_TRY
- {
- *TokenHandle = hToken;
- }
- _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
- {
- Status = _SEH2_GetExceptionCode();
- }
- _SEH2_END;
+ *TokenHandle = hToken;
+ }
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ Status = _SEH2_GetExceptionCode();
}
+ _SEH2_END;
return Status;
}
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=a389f8aa0ce16db83922e…
commit a389f8aa0ce16db83922eb04af26a546fdcec265
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Fri Jun 2 18:41:12 2023 +0200
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Sun Jun 4 11:09:22 2023 +0200
[NTOS:SE] Make an access token effective after the end of token duplication
Removing any disabled privileges or groups in the middle of token dynamic
part allocation can pose problems. During the operation of making an access
token as effective, we are toying with the privileges and groups arrays
of the token.
After that we are allocating the dynamic part and set EndMem (the end tail
of the memory part) to that dynamic part, previously it was set to the
variable part. As a matter of fact we are making the token effective in
the middle where EndMem still points to VariablePart, thus DynamicPart
will end up with memory pool blocks butchered in the pool list.
Another problem, albeit not related to the DynamicPart corruption, is that
the code starts iterating over the UserAndGroups array from 0, which is
the actual user. One cannot simply remove the user from the array, so we
have to start looping right from the groups.
Move the token effective code part at the end of the SepDuplicateToken
function, which fixes the random pool corruptions caused by the butchered
DynamicPart.
CORE-18986
---
ntoskrnl/se/tokenlif.c | 86 ++++++++++++++++++++++++++------------------------
1 file changed, 45 insertions(+), 41 deletions(-)
diff --git a/ntoskrnl/se/tokenlif.c b/ntoskrnl/se/tokenlif.c
index 44f269ed40b..85a8ee01f78 100644
--- a/ntoskrnl/se/tokenlif.c
+++ b/ntoskrnl/se/tokenlif.c
@@ -665,6 +665,44 @@ SepDuplicateToken(
}
}
+ /* Now allocate the token's dynamic information area and set the data */
+ AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool,
+ DynamicPartSize,
+ TAG_TOKEN_DYNAMIC);
+ if (AccessToken->DynamicPart == NULL)
+ {
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Quit;
+ }
+
+ /* Unused memory in the dynamic area */
+ AccessToken->DynamicAvailable = 0;
+
+ /*
+ * Assign the primary group to the token
+ * and put it in the dynamic part as well.
+ */
+ EndMem = (PVOID)AccessToken->DynamicPart;
+ AccessToken->PrimaryGroup = EndMem;
+ RtlCopySid(RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid),
+ EndMem,
+ AccessToken->UserAndGroups[PrimaryGroupIndex].Sid);
+ AccessToken->DefaultOwnerIndex = Token->DefaultOwnerIndex;
+ EndMem = (PVOID)((ULONG_PTR)EndMem + RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid));
+
+ /*
+ * The existing token has a default DACL only
+ * if it has an allocated dynamic part.
+ */
+ if (Token->DynamicPart && Token->DefaultDacl)
+ {
+ AccessToken->DefaultDacl = EndMem;
+
+ RtlCopyMemory(EndMem,
+ Token->DefaultDacl,
+ Token->DefaultDacl->AclSize);
+ }
+
/*
* Filter the token by removing the disabled privileges
* and groups if the caller wants to duplicate an access
@@ -672,11 +710,15 @@ SepDuplicateToken(
*/
if (EffectiveOnly)
{
- /* Begin querying the groups and search for disabled ones */
- for (GroupsIndex = 0; GroupsIndex < AccessToken->UserAndGroupCount; GroupsIndex++)
+ /*
+ * Begin querying the groups and search for disabled ones. Do not touch the
+ * user which is at the first position because it cannot be disabled, no
+ * matter what attributes it has.
+ */
+ for (GroupsIndex = 1; GroupsIndex < AccessToken->UserAndGroupCount; GroupsIndex++)
{
/*
- * A group or user is considered disabled if its attributes is either
+ * A group is considered disabled if its attributes is either
* 0 or SE_GROUP_ENABLED is not included in the attributes flags list.
* That is because a certain user and/or group can have several attributes
* that bear no influence on whether a user/group is enabled or not
@@ -738,44 +780,6 @@ SepDuplicateToken(
}
}
- /* Now allocate the token's dynamic information area and set the data */
- AccessToken->DynamicPart = ExAllocatePoolWithTag(PagedPool,
- DynamicPartSize,
- TAG_TOKEN_DYNAMIC);
- if (AccessToken->DynamicPart == NULL)
- {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto Quit;
- }
-
- /* Unused memory in the dynamic area */
- AccessToken->DynamicAvailable = 0;
-
- /*
- * Assign the primary group to the token
- * and put it in the dynamic part as well.
- */
- EndMem = (PVOID)AccessToken->DynamicPart;
- AccessToken->PrimaryGroup = EndMem;
- RtlCopySid(RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid),
- EndMem,
- AccessToken->UserAndGroups[PrimaryGroupIndex].Sid);
- AccessToken->DefaultOwnerIndex = Token->DefaultOwnerIndex;
- EndMem = (PVOID)((ULONG_PTR)EndMem + RtlLengthSid(AccessToken->UserAndGroups[PrimaryGroupIndex].Sid));
-
- /*
- * The existing token has a default DACL only
- * if it has an allocated dynamic part.
- */
- if (Token->DynamicPart && Token->DefaultDacl)
- {
- AccessToken->DefaultDacl = EndMem;
-
- RtlCopyMemory(EndMem,
- Token->DefaultDacl,
- Token->DefaultDacl->AclSize);
- }
-
/* Return the token to the caller */
*NewAccessToken = AccessToken;
Status = STATUS_SUCCESS;