xref: /reactos/ntoskrnl/se/tokenadj.c (revision 8ee308d7)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:         Access token ajusting Groups/Privileges support routines
5  * COPYRIGHT:       Copyright David Welch <welch@cwcom.net>
6  *                  Copyright 2021-2022 George Bișoc <george.bisoc@reactos.org>
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include <ntoskrnl.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 /* PRIVATE FUNCTIONS *********************************************************/
16 
17 /**
18  * @brief
19  * Removes a certain amount of privileges of a token based upon the request
20  * by the caller.
21  *
22  * @param[in,out] Token
23  * Token handle where the privileges are about to be modified.
24  *
25  * @param[in] DisableAllPrivileges
26  * If set to TRUE, the function disables all the privileges.
27  *
28  * @param[in] NewState
29  * A new list of privileges that the function will use it accordingly to
30  * either disable or enable the said privileges and change them.
31  *
32  * @param[in] NewStateCount
33  * The new total number count of privileges.
34  *
35  * @param[out] PreviousState
36  * If specified, the function will return the previous state list of privileges.
37  *
38  * @param[in] ApplyChanges
39  * If set to TRUE, the function will immediatelly apply the changes onto the
40  * token's privileges.
41  *
42  * @param[out] ChangedPrivileges
43  * The returned count number of changed privileges.
44  *
45  * @param[out] ChangesMade
46  * If TRUE, the function has made changes to the token's privileges. FALSE
47  * otherwise.
48  *
49  * @return
50  * Returns STATUS_SUCCESS if the function has successfully changed the list
51  * of privileges. STATUS_NOT_ALL_ASSIGNED is returned if not every privilege
52  * has been changed.
53  */
54 static
55 NTSTATUS
56 SepAdjustPrivileges(
57     _Inout_ PTOKEN Token,
58     _In_ BOOLEAN DisableAllPrivileges,
59     _In_opt_ PLUID_AND_ATTRIBUTES NewState,
60     _In_ ULONG NewStateCount,
61     _Out_opt_ PTOKEN_PRIVILEGES PreviousState,
62     _In_ BOOLEAN ApplyChanges,
63     _Out_ PULONG ChangedPrivileges,
64     _Out_ PBOOLEAN ChangesMade)
65 {
66     ULONG i, j, PrivilegeCount, ChangeCount, NewAttributes;
67 
68     PAGED_CODE();
69 
70     /* Count the found privileges and those that need to be changed */
71     PrivilegeCount = 0;
72     ChangeCount = 0;
73     *ChangesMade = FALSE;
74 
75     /* Loop all privileges in the token */
76     for (i = 0; i < Token->PrivilegeCount; i++)
77     {
78         /* Shall all of them be disabled? */
79         if (DisableAllPrivileges)
80         {
81             /* The new attributes are the old ones, but disabled */
82             NewAttributes = Token->Privileges[i].Attributes & ~SE_PRIVILEGE_ENABLED;
83         }
84         else
85         {
86             /* Otherwise loop all provided privileges */
87             for (j = 0; j < NewStateCount; j++)
88             {
89                 /* Check if this is the LUID we are looking for */
90                 if (RtlEqualLuid(&Token->Privileges[i].Luid, &NewState[j].Luid))
91                 {
92                     DPRINT("Found privilege\n");
93 
94                     /* Copy SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_REMOVED */
95                     NewAttributes = NewState[j].Attributes;
96                     NewAttributes &= (SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_REMOVED);
97                     NewAttributes |= Token->Privileges[i].Attributes & ~SE_PRIVILEGE_ENABLED;
98 
99                     /* Stop looking */
100                     break;
101                 }
102             }
103 
104             /* Check if we didn't find the privilege */
105             if (j == NewStateCount)
106             {
107                 /* Continue with the token's next privilege */
108                 continue;
109             }
110         }
111 
112         /* We found a privilege, count it */
113         PrivilegeCount++;
114 
115         /* Does the privilege need to be changed? */
116         if (Token->Privileges[i].Attributes != NewAttributes)
117         {
118             /* Does the caller want the old privileges? */
119             if (PreviousState != NULL)
120             {
121                 /* Copy the old privilege */
122                 PreviousState->Privileges[ChangeCount] = Token->Privileges[i];
123             }
124 
125             /* Does the caller want to apply the changes? */
126             if (ApplyChanges)
127             {
128                 /* Shall we remove the privilege? */
129                 if (NewAttributes & SE_PRIVILEGE_REMOVED)
130                 {
131                     /* Set the token as disabled and update flags for it */
132                     Token->Privileges[i].Attributes &= ~SE_PRIVILEGE_ENABLED;
133                     SepUpdateSinglePrivilegeFlagToken(Token, i);
134 
135                     /* Remove the privilege */
136                     SepRemovePrivilegeToken(Token, i);
137 
138                     *ChangesMade = TRUE;
139 
140                     /* Fix the running index and continue with next one */
141                     i--;
142                     continue;
143                 }
144 
145                 /* Set the new attributes and update flags */
146                 Token->Privileges[i].Attributes = NewAttributes;
147                 SepUpdateSinglePrivilegeFlagToken(Token, i);
148                 *ChangesMade = TRUE;
149             }
150 
151             /* Increment the change count */
152             ChangeCount++;
153         }
154     }
155 
156     /* Set the number of saved privileges */
157     if (PreviousState != NULL)
158         PreviousState->PrivilegeCount = ChangeCount;
159 
160     /* Return the number of changed privileges */
161     *ChangedPrivileges = ChangeCount;
162 
163     /* Check if we missed some */
164     if (!DisableAllPrivileges && (PrivilegeCount < NewStateCount))
165     {
166         return STATUS_NOT_ALL_ASSIGNED;
167     }
168 
169     return STATUS_SUCCESS;
170 }
171 
172 /**
173  * @brief
174  * Private routine that iterates over the groups of an
175  * access token to be adjusted as per on request by the
176  * caller, where a group can be enabled or disabled.
177  *
178  * @param[in] Token
179  * Access token where its groups are to be enabled or disabled.
180  *
181  * @param[in] NewState
182  * A list of groups with new state attributes to be assigned to
183  * the token.
184  *
185  * @param[in] NewStateCount
186  * The captured count number of groups in the list.
187  *
188  * @param[in] ApplyChanges
189  * If set to FALSE, the function will only iterate over the token's
190  * groups without performing any kind of modification. If set to TRUE,
191  * the changes will be applied immediately when the function has done
192  * looping the groups.
193  *
194  * @param[in] ResetToDefaultStates
195  * The function will reset the groups in an access token to default
196  * states if set to TRUE. In such scenario the function ignores
197  * NewState outright. Otherwise if set to FALSE, the function will
198  * use NewState to assign the newly attributes to adjust the token's
199  * groups. SE_GROUP_ENABLED_BY_DEFAULT is a flag indicator that is used
200  * for such purpose.
201  *
202  * @param[out] ChangesMade
203  * Returns TRUE if changes to token's groups have been made, otherwise
204  * FALSE is returned. Bear in mind such changes aren't always deterministic.
205  * See remarks for further details.
206  *
207  * @param[out] PreviousGroupsState
208  * If requested by the caller, the function will return the previous state
209  * of groups in an access token prior taking action on adjusting the token.
210  * This is a UM (user mode) pointer and it's prone to raise exceptions
211  * if such pointer address is not valid.
212  *
213  * @param[out] ChangedGroups
214  * Returns the total number of changed groups in an access token. This
215  * argument could also indicate the number of groups to be changed if
216  * the calling thread hasn't chosen to apply the changes yet. A number
217  * of 0 indicates no groups have been or to be changed because the groups'
218  * attributes in a token are the same as the ones from NewState given by
219  * the caller.
220  *
221  * @return
222  * STATUS_SUCCESS is returned if the function has successfully completed
223  * the operation of adjusting groups in a token. STATUS_CANT_DISABLE_MANDATORY
224  * is returned if there was an attempt to disable a mandatory group which is
225  * not possible. STATUS_CANT_ENABLE_DENY_ONLY is returned if there was an attempt
226  * to enable a "use for Deny only" group which is not allowed, that is, a restricted
227  * group. STATUS_NOT_ALL_ASSIGNED is returned if not all the groups are actually
228  * assigned to the token.
229  *
230  * @remarks
231  * Token groups adjusting can be judged to be deterministic or not based on the
232  * NT status code value. That is, STATUS_SUCCESS indicates the function not only
233  * has iterated over the whole groups in a token, it also has applied the changes
234  * thoroughly without impediment and the results perfectly match with the request
235  * desired by the caller. In this situation the condition is deemed deterministic.
236  * In a different situation however, if the status code was STATUS_NOT_ALL_ASSIGNED,
237  * the function would still continue looping the groups in a token and apply the
238  * changes whenever possible where the respective groups actually exist in the
239  * token. This kind of situation is deemed as indeterministic.
240  * For STATUS_CANT_DISABLE_MANDATORY and STATUS_CANT_ENABLE_DENY_ONLY the scenario
241  * is even more indeterministic as the iteration of groups comes to a halt thus
242  * leaving all other possible groups to be adjusted.
243  */
244 static
245 NTSTATUS
246 SepAdjustGroups(
247     _In_ PTOKEN Token,
248     _In_opt_ PSID_AND_ATTRIBUTES NewState,
249     _In_ ULONG NewStateCount,
250     _In_ BOOLEAN ApplyChanges,
251     _In_ BOOLEAN ResetToDefaultStates,
252     _Out_ PBOOLEAN ChangesMade,
253     _Out_opt_ PTOKEN_GROUPS PreviousGroupsState,
254     _Out_ PULONG ChangedGroups)
255 {
256     ULONG GroupsInToken, GroupsInList;
257     ULONG ChangeCount, GroupsCount, NewAttributes;
258 
259     PAGED_CODE();
260 
261     /* Ensure that the token we get is valid */
262     ASSERT(Token);
263 
264     /* Initialize the counters and begin the work */
265     *ChangesMade = FALSE;
266     GroupsCount = 0;
267     ChangeCount = 0;
268 
269     /* Begin looping all the groups in the token */
270     for (GroupsInToken = 0; GroupsInToken < Token->UserAndGroupCount; GroupsInToken++)
271     {
272         /* Does the caller want to reset groups to default states? */
273         if (ResetToDefaultStates)
274         {
275             /*
276              * SE_GROUP_ENABLED_BY_DEFAULT is a special indicator that informs us
277              * if a certain group has been enabled by default or not. In case
278              * a group is enabled by default but it is not currently enabled then
279              * at that point we must enable it back by default. For now just
280              * assign the respective SE_GROUP_ENABLED attribute as we'll do the
281              * eventual work later.
282              */
283             if ((Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_ENABLED_BY_DEFAULT) &&
284                 (Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_ENABLED) == 0)
285             {
286                 NewAttributes = Token->UserAndGroups[GroupsInToken].Attributes |= SE_GROUP_ENABLED;
287             }
288 
289             /*
290              * Unlike the case above, a group that hasn't been enabled by
291              * default but it's currently enabled then we must disable
292              * it back.
293              */
294             if ((Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_ENABLED_BY_DEFAULT) == 0 &&
295                 (Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_ENABLED))
296             {
297                 NewAttributes = Token->UserAndGroups[GroupsInToken].Attributes & ~SE_GROUP_ENABLED;
298             }
299         }
300         else
301         {
302             /* Loop the provided groups in the list then */
303             for (GroupsInList = 0; GroupsInList < NewStateCount; GroupsInList++)
304             {
305                 /* Does this group exist in the token? */
306                 if (RtlEqualSid(&Token->UserAndGroups[GroupsInToken].Sid,
307                                 &NewState[GroupsInList].Sid))
308                 {
309                     /*
310                      * This is the group that we're looking for.
311                      * However, it could be that the group is a
312                      * mandatory group which we are not allowed
313                      * and cannot disable it.
314                      */
315                     if ((Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_MANDATORY) &&
316                         (NewState[GroupsInList].Attributes & SE_GROUP_ENABLED) == 0)
317                     {
318                         /* It is mandatory, forget about this group */
319                         DPRINT1("SepAdjustGroups(): The SID group is mandatory!\n");
320                         return STATUS_CANT_DISABLE_MANDATORY;
321                     }
322 
323                     /*
324                      * We've to ensure that apart the group mustn't be
325                      * mandatory, it mustn't be a restricted group as
326                      * well. That is, the group is marked with
327                      * SE_GROUP_USE_FOR_DENY_ONLY flag and no one
328                      * can enable it because it's for "deny" use only.
329                      */
330                     if ((Token->UserAndGroups[GroupsInToken].Attributes & SE_GROUP_USE_FOR_DENY_ONLY) &&
331                         (NewState[GroupsInList].Attributes & SE_GROUP_ENABLED))
332                     {
333                         /* This group is restricted, forget about it */
334                         DPRINT1("SepAdjustGroups(): The SID group is for use deny only!\n");
335                         return STATUS_CANT_ENABLE_DENY_ONLY;
336                     }
337 
338                     /* Copy the attributes and stop searching */
339                     NewAttributes = NewState[GroupsInList].Attributes;
340                     NewAttributes &= SE_GROUP_ENABLED;
341                     NewAttributes = Token->UserAndGroups[GroupsInToken].Attributes & ~SE_GROUP_ENABLED;
342                     break;
343                 }
344 
345                 /* Did we find the specific group we wanted? */
346                 if (GroupsInList == NewStateCount)
347                 {
348                     /* We didn't, continue with the next token's group */
349                     continue;
350                 }
351             }
352 
353             /* Count the group that we found it */
354             GroupsCount++;
355 
356             /* Does the token have the same attributes as the caller requested them? */
357             if (Token->UserAndGroups[GroupsInToken].Attributes != NewAttributes)
358             {
359                 /*
360                  * No, then it's time to make some adjustment to the
361                  * token's groups. Does the caller want the previous states
362                  * of groups?
363                  */
364                 if (PreviousGroupsState != NULL)
365                 {
366                     PreviousGroupsState->Groups[ChangeCount] = Token->UserAndGroups[GroupsInToken];
367                 }
368 
369                 /* Time to apply the changes now? */
370                 if (ApplyChanges)
371                 {
372                     /* The caller gave us consent, apply and report that we made changes! */
373                     Token->UserAndGroups[GroupsInToken].Attributes = NewAttributes;
374                     *ChangesMade = TRUE;
375                 }
376 
377                 /* Increment the count change */
378                 ChangeCount++;
379             }
380         }
381     }
382 
383     /* Report the number of previous saved groups */
384     if (PreviousGroupsState != NULL)
385     {
386         PreviousGroupsState->GroupCount = ChangeCount;
387     }
388 
389     /* Report the number of changed groups */
390     *ChangedGroups = ChangeCount;
391 
392     /* Did we miss some groups? */
393     if (!ResetToDefaultStates && (GroupsCount < NewStateCount))
394     {
395         /*
396          * If we're at this stage then we are in a situation
397          * where the adjust changes done to token's groups is
398          * not deterministic as the caller might have wanted
399          * as per NewState parameter.
400          */
401         DPRINT1("SepAdjustGroups(): The token hasn't all the groups assigned!\n");
402         return STATUS_NOT_ALL_ASSIGNED;
403     }
404 
405     return STATUS_SUCCESS;
406 }
407 
408 /* SYSTEM CALLS ***************************************************************/
409 
410 /**
411  * @brief
412  * Removes a certain amount of privileges of a token based upon the request
413  * by the caller.
414  *
415  * @param[in,out] Token
416  * Token handle where the privileges are about to be modified.
417  *
418  * @param[in] DisableAllPrivileges
419  * If set to TRUE, the function disables all the privileges.
420  *
421  * @param[in] NewState
422  * A new list of privileges that the function will use it accordingly to
423  * either disable or enable the said privileges and change them.
424  *
425  * @param[in] NewStateCount
426  * The new total number count of privileges.
427  *
428  * @param[out] PreviousState
429  * If specified, the function will return the previous state list of privileges.
430  *
431  * @param[in] ApplyChanges
432  * If set to TRUE, the function will immediatelly apply the changes onto the
433  * token's privileges.
434  *
435  * @param[out] ChangedPrivileges
436  * The returned count number of changed privileges.
437  *
438  * @param[out] ChangesMade
439  * If TRUE, the function has made changes to the token's privileges. FALSE
440  * otherwise.
441  *
442  * @return
443  * Returns STATUS_SUCCESS if the function has successfully changed the list
444  * of privileges. STATUS_NOT_ALL_ASSIGNED is returned if not every privilege
445  * has been changed.
446  */
447 _Must_inspect_result_
448 __kernel_entry
449 NTSTATUS
450 NTAPI
451 NtAdjustPrivilegesToken(
452     _In_ HANDLE TokenHandle,
453     _In_ BOOLEAN DisableAllPrivileges,
454     _In_opt_ PTOKEN_PRIVILEGES NewState,
455     _In_ ULONG BufferLength,
456     _Out_writes_bytes_to_opt_(BufferLength,*ReturnLength)
457         PTOKEN_PRIVILEGES PreviousState,
458     _When_(PreviousState!=NULL, _Out_) PULONG ReturnLength)
459 {
460     NTSTATUS Status;
461     KPROCESSOR_MODE PreviousMode;
462     PTOKEN Token;
463     PLUID_AND_ATTRIBUTES CapturedPrivileges = NULL;
464     ULONG CapturedCount = 0;
465     ULONG CapturedLength = 0;
466     ULONG NewStateSize = 0;
467     ULONG ChangeCount;
468     ULONG RequiredLength;
469     BOOLEAN ChangesMade = FALSE;
470 
471     PAGED_CODE();
472 
473     DPRINT("NtAdjustPrivilegesToken() called\n");
474 
475     /* Fail, if we do not disable all privileges but NewState is NULL */
476     if (DisableAllPrivileges == FALSE && NewState == NULL)
477         return STATUS_INVALID_PARAMETER;
478 
479     PreviousMode = KeGetPreviousMode();
480     if (PreviousMode != KernelMode)
481     {
482         _SEH2_TRY
483         {
484             /* Probe NewState */
485             if (DisableAllPrivileges == FALSE)
486             {
487                 /* First probe the header */
488                 ProbeForRead(NewState, sizeof(TOKEN_PRIVILEGES), sizeof(ULONG));
489 
490                 CapturedCount = NewState->PrivilegeCount;
491                 NewStateSize = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[CapturedCount]);
492 
493                 ProbeForRead(NewState, NewStateSize, sizeof(ULONG));
494             }
495 
496             /* Probe PreviousState and ReturnLength */
497             if (PreviousState != NULL)
498             {
499                 ProbeForWrite(PreviousState, BufferLength, sizeof(ULONG));
500                 ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG));
501             }
502         }
503         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
504         {
505             /* Return the exception code */
506             _SEH2_YIELD(return _SEH2_GetExceptionCode());
507         }
508         _SEH2_END;
509     }
510     else
511     {
512         /* This is kernel mode, we trust the caller */
513         if (DisableAllPrivileges == FALSE)
514             CapturedCount = NewState->PrivilegeCount;
515     }
516 
517     /* Do we need to capture the new state? */
518     if (DisableAllPrivileges == FALSE)
519     {
520         _SEH2_TRY
521         {
522             /* Capture the new state array of privileges */
523             Status = SeCaptureLuidAndAttributesArray(NewState->Privileges,
524                                                      CapturedCount,
525                                                      PreviousMode,
526                                                      NULL,
527                                                      0,
528                                                      PagedPool,
529                                                      TRUE,
530                                                      &CapturedPrivileges,
531                                                      &CapturedLength);
532         }
533         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
534         {
535             /* Return the exception code */
536             Status = _SEH2_GetExceptionCode();
537         }
538         _SEH2_END;
539 
540         if (!NT_SUCCESS(Status))
541             return Status;
542     }
543 
544     /* Reference the token */
545     Status = ObReferenceObjectByHandle(TokenHandle,
546                                        TOKEN_ADJUST_PRIVILEGES | (PreviousState != NULL ? TOKEN_QUERY : 0),
547                                        SeTokenObjectType,
548                                        PreviousMode,
549                                        (PVOID*)&Token,
550                                        NULL);
551     if (!NT_SUCCESS(Status))
552     {
553         DPRINT1("Failed to reference token (Status 0x%lx)\n", Status);
554 
555         /* Release the captured privileges */
556         if (CapturedPrivileges != NULL)
557         {
558             SeReleaseLuidAndAttributesArray(CapturedPrivileges,
559                                             PreviousMode,
560                                             TRUE);
561         }
562 
563         return Status;
564     }
565 
566     /* Lock the token */
567     SepAcquireTokenLockExclusive(Token);
568 
569     /* Count the privileges that need to be changed, do not apply them yet */
570     Status = SepAdjustPrivileges(Token,
571                                  DisableAllPrivileges,
572                                  CapturedPrivileges,
573                                  CapturedCount,
574                                  NULL,
575                                  FALSE,
576                                  &ChangeCount,
577                                  &ChangesMade);
578 
579     /* Check if the caller asked for the previous state */
580     if (PreviousState != NULL)
581     {
582         /* Calculate the required length */
583         RequiredLength = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[ChangeCount]);
584 
585         /* Try to return the required buffer length */
586         _SEH2_TRY
587         {
588             *ReturnLength = RequiredLength;
589         }
590         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
591         {
592             /* Do cleanup and return the exception code */
593             Status = _SEH2_GetExceptionCode();
594             _SEH2_YIELD(goto Cleanup);
595         }
596         _SEH2_END;
597 
598         /* Fail, if the buffer length is smaller than the required length */
599         if (BufferLength < RequiredLength)
600         {
601             Status = STATUS_BUFFER_TOO_SMALL;
602             goto Cleanup;
603         }
604     }
605 
606     /* Now enter SEH, since we might return the old privileges */
607     _SEH2_TRY
608     {
609         /* This time apply the changes */
610         Status = SepAdjustPrivileges(Token,
611                                      DisableAllPrivileges,
612                                      CapturedPrivileges,
613                                      CapturedCount,
614                                      PreviousState,
615                                      TRUE,
616                                      &ChangeCount,
617                                      &ChangesMade);
618     }
619     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
620     {
621         /* Do cleanup and return the exception code */
622         Status = _SEH2_GetExceptionCode();
623         ChangesMade = TRUE; // Force write.
624         _SEH2_YIELD(goto Cleanup);
625     }
626     _SEH2_END;
627 
628 Cleanup:
629     /* Touch the token if we made changes */
630     if (ChangesMade)
631         ExAllocateLocallyUniqueId(&Token->ModifiedId);
632 
633     /* Unlock and dereference the token */
634     SepReleaseTokenLock(Token);
635     ObDereferenceObject(Token);
636 
637     /* Release the captured privileges */
638     if (CapturedPrivileges != NULL)
639     {
640         SeReleaseLuidAndAttributesArray(CapturedPrivileges,
641                                         PreviousMode,
642                                         TRUE);
643     }
644 
645     DPRINT("NtAdjustPrivilegesToken() done\n");
646     return Status;
647 }
648 
649 /**
650  * @brief
651  * Changes the list of groups by enabling or disabling them
652  * in an access token. Unlike NtAdjustPrivilegesToken,
653  * this API routine does not remove groups.
654  *
655  * @param[in] TokenHandle
656  * Token handle where the list of groups SID are to be adjusted.
657  * The access token must have TOKEN_ADJUST_GROUPS access right
658  * in order to change the groups in a token. The token must also
659  * have TOKEN_QUERY access right if the caller requests the previous
660  * states of groups list, that is, PreviousState is not NULL.
661  *
662  * @param[in] ResetToDefault
663  * If set to TRUE, the function resets the list of groups to default
664  * enabled and disabled states. NewState is ignored in this case.
665  * Otherwise if the parameter is set to FALSE, the function expects
666  * a new list of groups from NewState to be adjusted within the token.
667  *
668  * @param[in] NewState
669  * A new list of groups SID that the function will use it accordingly to
670  * modify the current list of groups SID of a token.
671  *
672  * @param[in] BufferLength
673  * The length size of the buffer that is pointed by the NewState parameter
674  * argument, in bytes.
675  *
676  * @param[out] PreviousState
677  * If specified, the function will return to the caller the old list of groups
678  * SID. If this parameter is NULL, ReturnLength must also be NULL.
679  *
680  * @param[out] ReturnLength
681  * If specified, the function will return the total size length of the old list
682  * of groups SIDs, in bytes.
683  *
684  * @return
685  * STATUS_SUCCESS is returned if the function has successfully adjusted the
686  * token's groups. STATUS_INVALID_PARAMETER is returned if the caller has
687  * submitted one or more invalid parameters, that is, the caller didn't want
688  * to reset the groups to default state but no NewState argument list has been
689  * provided. STATUS_BUFFER_TOO_SMALL is returned if the buffer length given
690  * by the caller is smaller than the required length size. A failure NTSTATUS
691  * code is returned otherwise.
692  */
693 NTSTATUS
694 NTAPI
695 NtAdjustGroupsToken(
696     _In_ HANDLE TokenHandle,
697     _In_ BOOLEAN ResetToDefault,
698     _In_ PTOKEN_GROUPS NewState,
699     _In_ ULONG BufferLength,
700     _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength)
701     PTOKEN_GROUPS PreviousState,
702     _When_(PreviousState != NULL, _Out_) PULONG ReturnLength)
703 {
704     PTOKEN Token;
705     NTSTATUS Status;
706     KPROCESSOR_MODE PreviousMode;
707     ULONG ChangeCount, RequiredLength;
708     ULONG CapturedCount = 0;
709     ULONG CapturedLength = 0;
710     ULONG NewStateSize = 0;
711     PSID_AND_ATTRIBUTES CapturedGroups = NULL;
712     BOOLEAN ChangesMade = FALSE;
713 
714     PAGED_CODE();
715 
716     /*
717      * If the caller doesn't want to reset the groups of an
718      * access token to default states then at least we must
719      * expect a list of groups to be adjusted based on NewState
720      * parameter. Otherwise bail out because the caller has
721      * no idea what they're doing.
722      */
723     if (!ResetToDefault && !NewState)
724     {
725         DPRINT1("NtAdjustGroupsToken(): The caller hasn't provided any list of groups to adjust!\n");
726         return STATUS_INVALID_PARAMETER;
727     }
728 
729     PreviousMode = ExGetPreviousMode();
730 
731     if (PreviousMode != KernelMode)
732     {
733         _SEH2_TRY
734         {
735             /* Probe NewState */
736             if (!ResetToDefault)
737             {
738                 /* Probe the header */
739                 ProbeForRead(NewState, sizeof(*NewState), sizeof(ULONG));
740 
741                 CapturedCount = NewState->GroupCount;
742                 NewStateSize = FIELD_OFFSET(TOKEN_GROUPS, Groups[CapturedCount]);
743 
744                 ProbeForRead(NewState, NewStateSize, sizeof(ULONG));
745             }
746 
747             if (PreviousState != NULL)
748             {
749                 ProbeForWrite(PreviousState, BufferLength, sizeof(ULONG));
750                 ProbeForWrite(ReturnLength, sizeof(*ReturnLength), sizeof(ULONG));
751             }
752         }
753         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
754         {
755             /* Return the exception code */
756             _SEH2_YIELD(return _SEH2_GetExceptionCode());
757         }
758         _SEH2_END;
759     }
760     else
761     {
762         /*
763          * We're calling directly from the kernel, just retrieve
764          * the number count of captured groups outright.
765          */
766         if (!ResetToDefault)
767         {
768             CapturedCount = NewState->GroupCount;
769         }
770     }
771 
772     /* Time to capture the NewState list */
773     if (!ResetToDefault)
774     {
775         _SEH2_TRY
776         {
777             Status = SeCaptureSidAndAttributesArray(NewState->Groups,
778                                                     CapturedCount,
779                                                     PreviousMode,
780                                                     NULL,
781                                                     0,
782                                                     PagedPool,
783                                                     TRUE,
784                                                     &CapturedGroups,
785                                                     &CapturedLength);
786         }
787         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
788         {
789             Status = _SEH2_GetExceptionCode();
790         }
791         _SEH2_END;
792 
793         if (!NT_SUCCESS(Status))
794         {
795             DPRINT1("NtAdjustGroupsToken(): Failed to capture the NewState list of groups (Status 0x%lx)\n", Status);
796             return Status;
797         }
798     }
799 
800     /* Time to reference the token */
801     Status = ObReferenceObjectByHandle(TokenHandle,
802                                        TOKEN_ADJUST_GROUPS | (PreviousState != NULL ? TOKEN_QUERY : 0),
803                                        SeTokenObjectType,
804                                        PreviousMode,
805                                        (PVOID*)&Token,
806                                        NULL);
807     if (!NT_SUCCESS(Status))
808     {
809         /* We couldn't reference the access token, bail out */
810         DPRINT1("NtAdjustGroupsToken(): Failed to reference the token (Status 0x%lx)\n", Status);
811 
812         if (CapturedGroups != NULL)
813         {
814             SeReleaseSidAndAttributesArray(CapturedGroups,
815                                            PreviousMode,
816                                            TRUE);
817         }
818 
819         return Status;
820     }
821 
822     /* Lock the token */
823     SepAcquireTokenLockExclusive(Token);
824 
825     /* Count the number of groups to be changed */
826     Status = SepAdjustGroups(Token,
827                              CapturedGroups,
828                              CapturedCount,
829                              FALSE,
830                              ResetToDefault,
831                              &ChangesMade,
832                              NULL,
833                              &ChangeCount);
834 
835     /* Does the caller want the previous state of groups? */
836     if (PreviousState != NULL)
837     {
838         /* Calculate the required length */
839         RequiredLength = FIELD_OFFSET(TOKEN_GROUPS, Groups[ChangeCount]);
840 
841         /* Return the required length to the caller */
842         _SEH2_TRY
843         {
844             *ReturnLength = RequiredLength;
845         }
846         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
847         {
848             /* Bail out and return the exception code */
849             Status = _SEH2_GetExceptionCode();
850             _SEH2_YIELD(goto Quit);
851         }
852         _SEH2_END;
853 
854         /* The buffer length provided is smaller than the required length, bail out */
855         if (BufferLength < RequiredLength)
856         {
857             Status = STATUS_BUFFER_TOO_SMALL;
858             goto Quit;
859         }
860     }
861 
862     /*
863      * Now it's time to apply changes. Wrap the code
864      * in SEH as we are returning the old groups state
865      * list to the caller since PreviousState is a
866      * UM pointer.
867      */
868     _SEH2_TRY
869     {
870         Status = SepAdjustGroups(Token,
871                                  CapturedGroups,
872                                  CapturedCount,
873                                  TRUE,
874                                  ResetToDefault,
875                                  &ChangesMade,
876                                  PreviousState,
877                                  &ChangeCount);
878     }
879     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
880     {
881         /* Bail out and return the exception code */
882         Status = _SEH2_GetExceptionCode();
883 
884         /* Force the write as we touched the token still */
885         ChangesMade = TRUE;
886         _SEH2_YIELD(goto Quit);
887     }
888     _SEH2_END;
889 
890 Quit:
891     /* Allocate a new ID for the token as we made changes */
892     if (ChangesMade)
893         ExAllocateLocallyUniqueId(&Token->ModifiedId);
894 
895     /* Unlock and dereference the token */
896     SepReleaseTokenLock(Token);
897     ObDereferenceObject(Token);
898 
899     /* Release the captured groups */
900     if (CapturedGroups != NULL)
901     {
902         SeReleaseSidAndAttributesArray(CapturedGroups,
903                                        PreviousMode,
904                                        TRUE);
905     }
906 
907     return Status;
908 }
909 
910 /* EOF */
911