https://git.reactos.org/?p=reactos.git;a=commitdiff;h=8e6fc7a5f560664c4aa102aea9060960c253cafc

commit 8e6fc7a5f560664c4aa102aea9060960c253cafc
Author:     George Bișoc <[email protected]>
AuthorDate: Sat Sep 18 17:14:27 2021 +0200
Commit:     George Bișoc <[email protected]>
CommitDate: Thu Sep 23 17:38:31 2021 +0200

    [NTOS:SE] Implement token groups adjusting
---
 ntoskrnl/se/token.c       | 485 ++++++++++++++++++++++++++++++++++++++++++++--
 sdk/include/ndk/sefuncs.h |   2 +-
 2 files changed, 474 insertions(+), 13 deletions(-)

diff --git a/ntoskrnl/se/token.c b/ntoskrnl/se/token.c
index b8492238c05..9c8492edfcc 100644
--- a/ntoskrnl/se/token.c
+++ b/ntoskrnl/se/token.c
@@ -4273,18 +4273,262 @@ NtDuplicateToken(
 
 /**
  * @brief
- * Changes the groups list of SIDs of a token.
+ * Private routine that iterates over the groups of an
+ * access token to be adjusted as per on request by the
+ * caller, where a group can be enabled or disabled.
+ *
+ * @param[in] Token
+ * Access token where its groups are to be enabled or disabled.
+ *
+ * @param[in] NewState
+ * A list of groups with new state attributes to be assigned to
+ * the token.
+ *
+ * @param[in] NewStateCount
+ * The captured count number of groups in the list.
+ *
+ * @param[in] ApplyChanges
+ * If set to FALSE, the function will only iterate over the token's
+ * groups without performing any kind of modification. If set to TRUE,
+ * the changes will be applied immediately when the function has done
+ * looping the groups.
+ *
+ * @param[in] ResetToDefaultStates
+ * The function will reset the groups in an access token to default
+ * states if set to TRUE. In such scenario the function ignores
+ * NewState outright. Otherwise if set to FALSE, the function will
+ * use NewState to assign the newly attributes to adjust the token's
+ * groups. SE_GROUP_ENABLED_BY_DEFAULT is a flag indicator that is used
+ * for such purpose.
+ *
+ * @param[out] ChangesMade
+ * Returns TRUE if changes to token's groups have been made, otherwise
+ * FALSE is returned. Bear in mind such changes aren't always deterministic.
+ * See remarks for further details.
+ *
+ * @param[out] PreviousGroupsState
+ * If requested by the caller, the function will return the previous state
+ * of groups in an access token prior taking action on adjusting the token.
+ * This is a UM (user mode) pointer and it's prone to raise exceptions
+ * if such pointer address is not valid.
+ *
+ * @param[out] ChangedGroups
+ * Returns the total number of changed groups in an access token. This
+ * argument could also indicate the number of groups to be changed if
+ * the calling thread hasn't chosen to apply the changes yet. A number
+ * of 0 indicates no groups have been or to be changed because the groups'
+ * attributes in a token are the same as the ones from NewState given by
+ * the caller.
+ *
+ * @return
+ * STATUS_SUCCESS is returned if the function has successfully completed
+ * the operation of adjusting groups in a token. STATUS_CANT_DISABLE_MANDATORY
+ * is returned if there was an attempt to disable a mandatory group which is
+ * not possible. STATUS_CANT_ENABLE_DENY_ONLY is returned if there was an 
attempt
+ * to enable a "use for Deny only" group which is not allowed, that is, a 
restricted
+ * group. STATUS_NOT_ALL_ASSIGNED is returned if not all the groups are 
actually
+ * assigned to the token.
+ *
+ * @remarks
+ * Token groups adjusting can be judged to be deterministic or not based on the
+ * NT status code value. That is, STATUS_SUCCESS indicates the function not 
only
+ * has iterated over the whole groups in a token, it also has applied the 
changes
+ * thoroughly without impediment and the results perfectly match with the 
request
+ * desired by the caller. In this situation the condition is deemed 
deterministic.
+ * In a different situation however, if the status code was 
STATUS_NOT_ALL_ASSIGNED,
+ * the function would still continue looping the groups in a token and apply 
the
+ * changes whenever possible where the respective groups actually exist in the
+ * token. This kind of situation is deemed as indeterministic.
+ * For STATUS_CANT_DISABLE_MANDATORY and STATUS_CANT_ENABLE_DENY_ONLY the 
scenario
+ * is even more indeterministic as the iteration of groups comes to a halt thus
+ * leaving all other possible groups to be adjusted.
+ */
+static
+NTSTATUS
+SepAdjustGroups(
+    _In_ PTOKEN Token,
+    _In_opt_ PSID_AND_ATTRIBUTES NewState,
+    _In_ ULONG NewStateCount,
+    _In_ BOOLEAN ApplyChanges,
+    _In_ BOOLEAN ResetToDefaultStates,
+    _Out_ PBOOLEAN ChangesMade,
+    _Out_opt_ PTOKEN_GROUPS PreviousGroupsState,
+    _Out_ PULONG ChangedGroups)
+{
+    ULONG GroupsInToken, GroupsInList;
+    ULONG ChangeCount, GroupsCount, NewAttributes;
+
+    PAGED_CODE();
+
+    /* Ensure that the token we get is not plain garbage */
+    ASSERT(Token);
+
+    /* Initialize the counters and begin the work */
+    *ChangesMade = FALSE;
+    GroupsCount = 0;
+    ChangeCount = 0;
+
+    /* Begin looping all the groups in the token */
+    for (GroupsInToken = 0; GroupsInToken < Token->UserAndGroupCount; 
GroupsInToken++)
+    {
+        /* Does the caller want to reset groups to default states? */
+        if (ResetToDefaultStates)
+        {
+            /*
+             * SE_GROUP_ENABLED_BY_DEFAULT is a special indicator that informs 
us
+             * if a certain group has been enabled by default or not. In case
+             * a group is enabled by default but it is not currently enabled 
then
+             * at that point we must enable it back by default. For now just
+             * assign the respective SE_GROUP_ENABLED attribute as we'll do the
+             * eventual work later.
+             */
+            if ((Token->UserAndGroups[GroupsInToken].Attributes & 
SE_GROUP_ENABLED_BY_DEFAULT) &&
+                (Token->UserAndGroups[GroupsInToken].Attributes & 
SE_GROUP_ENABLED) == 0)
+            {
+                NewAttributes = Token->UserAndGroups[GroupsInToken].Attributes 
|= SE_GROUP_ENABLED;
+            }
+
+            /*
+             * Unlike the case above, a group that hasn't been enabled by
+             * default but it's currently enabled then we must disable
+             * it back.
+             */
+            if ((Token->UserAndGroups[GroupsInToken].Attributes & 
SE_GROUP_ENABLED_BY_DEFAULT) == 0 &&
+                (Token->UserAndGroups[GroupsInToken].Attributes & 
SE_GROUP_ENABLED))
+            {
+                NewAttributes = Token->UserAndGroups[GroupsInToken].Attributes 
& ~SE_GROUP_ENABLED;
+            }
+        }
+        else
+        {
+            /* Loop the provided groups in the list then */
+            for (GroupsInList = 0; GroupsInList < NewStateCount; 
GroupsInList++)
+            {
+                /* Does this group exist in the token? */
+                if (RtlEqualSid(&Token->UserAndGroups[GroupsInToken].Sid,
+                                &NewState[GroupsInList].Sid))
+                {
+                    /*
+                     * This is the group that we're looking for.
+                     * However, it could be that the group is a
+                     * mandatory group which we are not allowed
+                     * and cannot disable it.
+                     */
+                    if ((Token->UserAndGroups[GroupsInToken].Attributes & 
SE_GROUP_MANDATORY) &&
+                        (NewState[GroupsInList].Attributes & SE_GROUP_ENABLED) 
== 0)
+                    {
+                        /* It is mandatory, forget about this group */
+                        DPRINT1("SepAdjustGroups(): The SID group is 
mandatory!\n");
+                        return STATUS_CANT_DISABLE_MANDATORY;
+                    }
+
+                    /*
+                     * We've to ensure that apart the group mustn't be
+                     * mandatory, it mustn't be a restricted group as
+                     * well. That is, the group is marked with
+                     * SE_GROUP_USE_FOR_DENY_ONLY flag and no one
+                     * can enable it because it's for "deny" use only.
+                     */
+                    if ((Token->UserAndGroups[GroupsInToken].Attributes & 
SE_GROUP_USE_FOR_DENY_ONLY) &&
+                        (NewState[GroupsInList].Attributes & SE_GROUP_ENABLED))
+                    {
+                        /* This group is restricted, forget about it */
+                        DPRINT1("SepAdjustGroups(): The SID group is for use 
deny only!\n");
+                        return STATUS_CANT_ENABLE_DENY_ONLY;
+                    }
+
+                    /* Copy the attributes and stop searching */
+                    NewAttributes = NewState[GroupsInList].Attributes;
+                    NewAttributes &= SE_GROUP_ENABLED;
+                    NewAttributes = 
Token->UserAndGroups[GroupsInToken].Attributes & ~SE_GROUP_ENABLED;
+                    break;
+                }
+
+                /* Did we find the specific group we wanted? */
+                if (GroupsInList == NewStateCount)
+                {
+                    /* We didn't, continue with the next token's group */
+                    continue;
+                }
+            }
+
+            /* Count the group that we found it */
+            GroupsCount++;
+
+            /* Does the token have the same attributes as the caller requested 
them? */
+            if (Token->UserAndGroups[GroupsInToken].Attributes != 
NewAttributes)
+            {
+                /*
+                 * No, then it's time to make some adjustment to the
+                 * token's groups. Does the caller want the previous states
+                 * of groups?
+                 */
+                if (PreviousGroupsState != NULL)
+                {
+                    PreviousGroupsState->Groups[ChangeCount] = 
Token->UserAndGroups[GroupsInToken];
+                }
+
+                /* Time to apply the changes now? */
+                if (ApplyChanges)
+                {
+                    /* The caller gave us consent, apply and report that we 
made changes! */
+                    Token->UserAndGroups[GroupsInToken].Attributes = 
NewAttributes;
+                    *ChangesMade = TRUE;
+                }
+
+                /* Increment the count change */
+                ChangeCount++;
+            }
+        }
+    }
+
+    /* Report the number of previous saved groups */
+    if (PreviousGroupsState != NULL)
+    {
+        PreviousGroupsState->GroupCount = ChangeCount;
+    }
+
+    /* Report the number of changed groups */
+    *ChangedGroups = ChangeCount;
+
+    /* Did we miss some groups? */
+    if (!ResetToDefaultStates && (GroupsCount < NewStateCount))
+    {
+        /*
+         * If we're at this stage then we are in a situation
+         * where the adjust changes done to token's groups is
+         * not deterministic as the caller might have wanted
+         * as per NewState parameter.
+         */
+        DPRINT1("SepAdjustGroups(): The token hasn't all the groups 
assigned!\n");
+        return STATUS_NOT_ALL_ASSIGNED;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+/**
+ * @brief
+ * Changes the list of groups by enabling or disabling them
+ * in an access token. Unlike NtAdjustPrivilegesToken,
+ * this API routine does not remove groups.
  *
  * @param[in] TokenHandle
- * Token handle where the list of groups SIDs are to be adjusted.
+ * Token handle where the list of groups SID are to be adjusted.
+ * The access token must have TOKEN_ADJUST_GROUPS access right
+ * in order to change the groups in a token. The token must also
+ * have TOKEN_QUERY access right if the caller requests the previous
+ * states of groups list, that is, PreviousState is not NULL.
  *
  * @param[in] ResetToDefault
- * If set to TRUE, the function resets the list of groups SIDs to default.
- * All the rest of parameters are ignored.
+ * If set to TRUE, the function resets the list of groups to default
+ * enabled and disabled states. NewState is ignored in this case.
+ * Otherwise if the parameter is set to FALSE, the function expects
+ * a new list of groups from NewState to be adjusted within the token.
  *
  * @param[in] NewState
- * A new list of groups SIDs that the function will use it accordingly to
- * modify the current list of groups SIDs of a token.
+ * A new list of groups SID that the function will use it accordingly to
+ * modify the current list of groups SID of a token.
  *
  * @param[in] BufferLength
  * The length size of the buffer that is pointed by the NewState parameter
@@ -4292,14 +4536,20 @@ NtDuplicateToken(
  *
  * @param[out] PreviousState
  * If specified, the function will return to the caller the old list of groups
- * SIDs.
+ * SID. If this parameter is NULL, ReturnLength must also be NULL.
  *
  * @param[out] ReturnLength
  * If specified, the function will return the total size length of the old list
  * of groups SIDs, in bytes.
  *
  * @return
- * To be added...
+ * STATUS_SUCCESS is returned if the function has successfully adjusted the
+ * token's groups. STATUS_INVALID_PARAMETER is returned if the caller has
+ * submitted one or more invalid parameters, that is, the caller didn't want
+ * to reset the groups to default state but no NewState argument list has been
+ * provided. STATUS_BUFFER_TOO_SMALL is returned if the buffer length given
+ * by the caller is smaller than the required length size. A failure NTSTATUS
+ * code is returned otherwise.
  */
 NTSTATUS
 NTAPI
@@ -4308,11 +4558,222 @@ NtAdjustGroupsToken(
     _In_ BOOLEAN ResetToDefault,
     _In_ PTOKEN_GROUPS NewState,
     _In_ ULONG BufferLength,
-    _Out_opt_ PTOKEN_GROUPS PreviousState,
-    _Out_ PULONG ReturnLength)
+    _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength)
+    PTOKEN_GROUPS PreviousState,
+    _When_(PreviousState != NULL, _Out_) PULONG ReturnLength)
 {
-    UNIMPLEMENTED;
-    return STATUS_NOT_IMPLEMENTED;
+    PTOKEN Token;
+    NTSTATUS Status;
+    KPROCESSOR_MODE PreviousMode;
+    ULONG ChangeCount, RequiredLength;
+    ULONG CapturedCount = 0;
+    ULONG CapturedLength = 0;
+    ULONG NewStateSize = 0;
+    PSID_AND_ATTRIBUTES CapturedGroups = NULL;
+    BOOLEAN ChangesMade = FALSE;
+    BOOLEAN LockAndReferenceAcquired = FALSE;
+
+    PAGED_CODE();
+
+    /*
+     * If the caller doesn't want to reset the groups of an
+     * access token to default states then at least we must
+     * expect a list of groups to be adjusted based on NewState
+     * parameter. Otherwise bail out because the caller has
+     * no idea what they're doing.
+     */
+    if (!ResetToDefault && !NewState)
+    {
+        DPRINT1("NtAdjustGroupsToken(): The caller hasn't provided any list of 
groups to adjust!\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    PreviousMode = ExGetPreviousMode();
+
+    if (PreviousMode != KernelMode)
+    {
+        _SEH2_TRY
+        {
+            /* Probe NewState */
+            if (!ResetToDefault)
+            {
+                /* Probe the header */
+                ProbeForRead(NewState, sizeof(*NewState), sizeof(ULONG));
+
+                CapturedCount = NewState->GroupCount;
+                NewStateSize = FIELD_OFFSET(TOKEN_GROUPS, 
Groups[CapturedCount]);
+
+                ProbeForRead(NewState, NewStateSize, sizeof(ULONG));
+            }
+
+            if (PreviousState != NULL)
+            {
+                ProbeForWrite(PreviousState, BufferLength, sizeof(ULONG));
+                ProbeForWrite(ReturnLength, sizeof(*ReturnLength), 
sizeof(ULONG));
+            }
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+        {
+            /* Return the exception code */
+            _SEH2_YIELD(return _SEH2_GetExceptionCode());
+        }
+        _SEH2_END;
+    }
+    else
+    {
+        /*
+         * We're calling directly from the kernel, just retrieve
+         * the number count of captured groups outright.
+         */
+        if (!ResetToDefault)
+        {
+            CapturedCount = NewState->GroupCount;
+        }
+    }
+
+    /* Time to capture the NewState list */
+    if (!ResetToDefault)
+    {
+        _SEH2_TRY
+        {
+            Status = SeCaptureSidAndAttributesArray(NewState->Groups,
+                                                    CapturedCount,
+                                                    PreviousMode,
+                                                    NULL,
+                                                    0,
+                                                    PagedPool,
+                                                    TRUE,
+                                                    &CapturedGroups,
+                                                    &CapturedLength);
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+        {
+            Status = _SEH2_GetExceptionCode();
+        }
+        _SEH2_END;
+
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("NtAdjustGroupsToken(): Failed to capture the NewState 
list of groups (Status 0x%lx)\n", Status);
+            return Status;
+        }
+    }
+
+    /* Time to reference the token */
+    Status = ObReferenceObjectByHandle(TokenHandle,
+                                       TOKEN_ADJUST_GROUPS | (PreviousState != 
NULL ? TOKEN_QUERY : 0),
+                                       SeTokenObjectType,
+                                       PreviousMode,
+                                       (PVOID*)&Token,
+                                       NULL);
+    if (!NT_SUCCESS(Status))
+    {
+        /* We couldn't reference the access token, bail out */
+        DPRINT1("NtAdjustGroupsToken(): Failed to reference the token (Status 
0x%lx)\n", Status);
+
+        if (CapturedGroups != NULL)
+        {
+            SeReleaseSidAndAttributesArray(CapturedGroups,
+                                           PreviousMode,
+                                           TRUE);
+        }
+
+        goto Quit;
+    }
+
+    /* Lock the token */
+    SepAcquireTokenLockExclusive(Token);
+    LockAndReferenceAcquired = TRUE;
+
+    /* Count the number of groups to be changed */
+    Status = SepAdjustGroups(Token,
+                             CapturedGroups,
+                             CapturedCount,
+                             FALSE,
+                             ResetToDefault,
+                             &ChangesMade,
+                             NULL,
+                             &ChangeCount);
+
+    /* Does the caller want the previous state of groups? */
+    if (PreviousState != NULL)
+    {
+        /* Calculate the required length */
+        RequiredLength = FIELD_OFFSET(TOKEN_GROUPS, Groups[ChangeCount]);
+
+        /* Return the required length to the caller */
+        _SEH2_TRY
+        {
+            *ReturnLength = RequiredLength;
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+        {
+            /* Bail out and return the exception code */
+            Status = _SEH2_GetExceptionCode();
+            _SEH2_YIELD(goto Quit);
+        }
+        _SEH2_END;
+
+        /* The buffer length provided is smaller than the required length, 
bail out */
+        if (BufferLength < RequiredLength)
+        {
+            Status = STATUS_BUFFER_TOO_SMALL;
+            goto Quit;
+        }
+    }
+
+    /*
+     * Now it's time to apply changes. Wrap the code
+     * in SEH as we are returning the old groups state
+     * list to the caller since PreviousState is a
+     * UM pointer.
+     */
+    _SEH2_TRY
+    {
+        Status = SepAdjustGroups(Token,
+                                 CapturedGroups,
+                                 CapturedCount,
+                                 TRUE,
+                                 ResetToDefault,
+                                 &ChangesMade,
+                                 PreviousState,
+                                 &ChangeCount);
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        /* Bail out and return the exception code */
+        Status = _SEH2_GetExceptionCode();
+
+        /* Force the write as we touched the token still */
+        ChangesMade = TRUE;
+        _SEH2_YIELD(goto Quit);
+    }
+    _SEH2_END;
+
+Quit:
+    /* Allocate a new ID for the token as we made changes */
+    if (ChangesMade)
+    {
+        ExAllocateLocallyUniqueId(&Token->ModifiedId);
+    }
+
+    /* Have we successfully acquired the lock and referenced the token before? 
*/
+    if (LockAndReferenceAcquired)
+    {
+        /* Unlock and dereference the token */
+        SepReleaseTokenLock(Token);
+        ObDereferenceObject(Token);
+    }
+
+    /* Release the captured groups */
+    if (CapturedGroups != NULL)
+    {
+        SeReleaseSidAndAttributesArray(CapturedGroups,
+                                       PreviousMode,
+                                       TRUE);
+    }
+
+    return Status;
 }
 
 /**
diff --git a/sdk/include/ndk/sefuncs.h b/sdk/include/ndk/sefuncs.h
index 6fcc96e7fe6..d9a8e58ab83 100644
--- a/sdk/include/ndk/sefuncs.h
+++ b/sdk/include/ndk/sefuncs.h
@@ -150,7 +150,7 @@ NtAdjustGroupsToken(
     _In_opt_ PTOKEN_GROUPS NewState,
     _In_opt_ ULONG BufferLength,
     _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_GROUPS 
PreviousState,
-    _Out_ PULONG ReturnLength);
+    _When_(PreviousState != NULL, _Out_) PULONG ReturnLength);
 
 _Must_inspect_result_
 __kernel_entry

Reply via email to