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