1 /* 2 * PROJECT: ReactOS Kernel 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Security Reference Monitor Server 5 * COPYRIGHT: Copyright Timo Kreuzer <timo.kreuzer@reactos.org> 6 * Copyright Pierre Schweitzer <pierre@reactos.org> 7 * Copyright 2021 George Bișoc <george.bisoc@reactos.org> 8 */ 9 10 /* INCLUDES *******************************************************************/ 11 12 #include <ntoskrnl.h> 13 #define NDEBUG 14 #include <debug.h> 15 16 /* PRIVATE DEFINITIONS ********************************************************/ 17 18 typedef struct _SEP_LOGON_SESSION_TERMINATED_NOTIFICATION 19 { 20 struct _SEP_LOGON_SESSION_TERMINATED_NOTIFICATION *Next; 21 PSE_LOGON_SESSION_TERMINATED_ROUTINE CallbackRoutine; 22 } SEP_LOGON_SESSION_TERMINATED_NOTIFICATION, *PSEP_LOGON_SESSION_TERMINATED_NOTIFICATION; 23 24 VOID 25 NTAPI 26 SepRmCommandServerThread( 27 _In_ PVOID StartContext); 28 29 static 30 NTSTATUS 31 SepCleanupLUIDDeviceMapDirectory( 32 _In_ PLUID LogonLuid); 33 34 static 35 NTSTATUS 36 SepRmCreateLogonSession( 37 _In_ PLUID LogonLuid); 38 39 40 /* GLOBALS ********************************************************************/ 41 42 extern LUID SeSystemAuthenticationId; 43 extern LUID SeAnonymousAuthenticationId; 44 45 HANDLE SeRmCommandPort; 46 HANDLE SeLsaInitEvent; 47 48 PVOID SepCommandPortViewBase; 49 PVOID SepCommandPortViewRemoteBase; 50 ULONG_PTR SepCommandPortViewBaseOffset; 51 52 static HANDLE SepRmCommandMessagePort; 53 54 BOOLEAN SepAdtAuditingEnabled; 55 ULONG SepAdtMinListLength = 0x2000; 56 ULONG SepAdtMaxListLength = 0x3000; 57 58 #define POLICY_AUDIT_EVENT_TYPE_COUNT 9 // (AuditCategoryAccountLogon - AuditCategorySystem + 1) 59 UCHAR SeAuditingState[POLICY_AUDIT_EVENT_TYPE_COUNT]; 60 61 KGUARDED_MUTEX SepRmDbLock; 62 PSEP_LOGON_SESSION_REFERENCES SepLogonSessions = NULL; 63 PSEP_LOGON_SESSION_TERMINATED_NOTIFICATION SepLogonNotifications = NULL; 64 65 /* PRIVATE FUNCTIONS **********************************************************/ 66 67 /** 68 * @brief 69 * A private registry helper that returns the desired value 70 * data based on the specifics requested by the caller. 71 * 72 * @param[in] KeyName 73 * Name of the key. 74 * 75 * @param[in] ValueName 76 * Name of the registry value. 77 * 78 * @param[in] ValueType 79 * The type of the registry value. 80 * 81 * @param[in] DataLength 82 * The data length, in bytes, representing the size of the registry value. 83 * 84 * @param[out] ValueData 85 * The requested value data provided by the function. 86 * 87 * @return 88 * Returns STATUS_SUCCESS if the operations have completed successfully, 89 * otherwise a failure NTSTATUS code is returned. 90 */ 91 NTSTATUS 92 NTAPI 93 SepRegQueryHelper( 94 _In_ PCWSTR KeyName, 95 _In_ PCWSTR ValueName, 96 _In_ ULONG ValueType, 97 _In_ ULONG DataLength, 98 _Out_ PVOID ValueData) 99 { 100 UNICODE_STRING ValueNameString; 101 UNICODE_STRING KeyNameString; 102 ULONG ResultLength; 103 OBJECT_ATTRIBUTES ObjectAttributes; 104 HANDLE KeyHandle = NULL; 105 struct 106 { 107 KEY_VALUE_PARTIAL_INFORMATION Partial; 108 UCHAR Buffer[64]; 109 } KeyValueInformation; 110 NTSTATUS Status, CloseStatus; 111 PAGED_CODE(); 112 113 RtlInitUnicodeString(&KeyNameString, KeyName); 114 InitializeObjectAttributes(&ObjectAttributes, 115 &KeyNameString, 116 OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, 117 NULL, 118 NULL); 119 120 Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE, &ObjectAttributes); 121 if (!NT_SUCCESS(Status)) 122 { 123 return Status; 124 } 125 126 RtlInitUnicodeString(&ValueNameString, ValueName); 127 Status = ZwQueryValueKey(KeyHandle, 128 &ValueNameString, 129 KeyValuePartialInformation, 130 &KeyValueInformation.Partial, 131 sizeof(KeyValueInformation), 132 &ResultLength); 133 if (!NT_SUCCESS(Status)) 134 { 135 goto Cleanup; 136 } 137 138 if ((KeyValueInformation.Partial.Type != ValueType) || 139 (KeyValueInformation.Partial.DataLength != DataLength)) 140 { 141 Status = STATUS_OBJECT_TYPE_MISMATCH; 142 goto Cleanup; 143 } 144 145 if (ValueType == REG_BINARY) 146 { 147 RtlCopyMemory(ValueData, KeyValueInformation.Partial.Data, DataLength); 148 } 149 else if (ValueType == REG_DWORD) 150 { 151 *(PULONG)ValueData = *(PULONG)KeyValueInformation.Partial.Data; 152 } 153 else 154 { 155 Status = STATUS_INVALID_PARAMETER; 156 } 157 158 Cleanup: 159 CloseStatus = ZwClose(KeyHandle); 160 ASSERT(NT_SUCCESS( CloseStatus )); 161 162 return Status; 163 } 164 165 /** 166 * @brief 167 * Manages the phase 0 initialization of the security reference 168 * monitoring module of the kernel. 169 * 170 * @return 171 * Returns TRUE when phase 0 initialization has completed without 172 * problems, FALSE otherwise. 173 */ 174 BOOLEAN 175 NTAPI 176 SeRmInitPhase0(VOID) 177 { 178 NTSTATUS Status; 179 180 /* Initialize the database lock */ 181 KeInitializeGuardedMutex(&SepRmDbLock); 182 183 /* Create the system logon session */ 184 Status = SepRmCreateLogonSession(&SeSystemAuthenticationId); 185 if (!NT_VERIFY(NT_SUCCESS(Status))) 186 { 187 return FALSE; 188 } 189 190 /* Create the anonymous logon session */ 191 Status = SepRmCreateLogonSession(&SeAnonymousAuthenticationId); 192 if (!NT_VERIFY(NT_SUCCESS(Status))) 193 { 194 return FALSE; 195 } 196 197 return TRUE; 198 } 199 200 /** 201 * @brief 202 * Manages the phase 1 initialization of the security reference 203 * monitoring module of the kernel. 204 * 205 * @return 206 * Returns TRUE when phase 1 initialization has completed without 207 * problems, FALSE otherwise. 208 */ 209 BOOLEAN 210 NTAPI 211 SeRmInitPhase1(VOID) 212 { 213 UNICODE_STRING Name; 214 OBJECT_ATTRIBUTES ObjectAttributes; 215 HANDLE ThreadHandle; 216 NTSTATUS Status; 217 218 /* Create the SeRm command port */ 219 RtlInitUnicodeString(&Name, L"\\SeRmCommandPort"); 220 InitializeObjectAttributes(&ObjectAttributes, &Name, 0, NULL, NULL); 221 Status = ZwCreatePort(&SeRmCommandPort, 222 &ObjectAttributes, 223 sizeof(ULONG), 224 PORT_MAXIMUM_MESSAGE_LENGTH, 225 2 * PAGE_SIZE); 226 if (!NT_SUCCESS(Status)) 227 { 228 DPRINT1("Security: Rm Create Command Port failed 0x%lx\n", Status); 229 return FALSE; 230 } 231 232 /* Create SeLsaInitEvent */ 233 RtlInitUnicodeString(&Name, L"\\SeLsaInitEvent"); 234 InitializeObjectAttributes(&ObjectAttributes, &Name, 0, NULL, NULL); 235 Status = ZwCreateEvent(&SeLsaInitEvent, 236 GENERIC_WRITE, 237 &ObjectAttributes, 238 NotificationEvent, 239 FALSE); 240 if (!NT_VERIFY((NT_SUCCESS(Status)))) 241 { 242 DPRINT1("Security: LSA init event creation failed.0x%xl\n", Status); 243 return FALSE; 244 } 245 246 /* Create the SeRm server thread */ 247 Status = PsCreateSystemThread(&ThreadHandle, 248 THREAD_ALL_ACCESS, 249 NULL, 250 NULL, 251 NULL, 252 SepRmCommandServerThread, 253 NULL); 254 if (!NT_SUCCESS(Status)) 255 { 256 DPRINT1("Security: Rm Server Thread creation failed 0x%lx\n", Status); 257 return FALSE; 258 } 259 260 ObCloseHandle(ThreadHandle, KernelMode); 261 262 return TRUE; 263 } 264 265 /** 266 * @brief 267 * Initializes the local security authority audit bounds. 268 * 269 * @return 270 * Nothing. 271 */ 272 static 273 VOID 274 SepAdtInitializeBounds(VOID) 275 { 276 struct 277 { 278 ULONG MaxLength; 279 ULONG MinLength; 280 } ListBounds; 281 NTSTATUS Status; 282 PAGED_CODE(); 283 284 Status = SepRegQueryHelper(L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Lsa", 285 L"Bounds", 286 REG_BINARY, 287 sizeof(ListBounds), 288 &ListBounds); 289 if (!NT_SUCCESS(Status)) 290 { 291 /* No registry values, so keep hardcoded defaults */ 292 return; 293 } 294 295 /* Check if the bounds are valid */ 296 if ((ListBounds.MaxLength < ListBounds.MinLength) || 297 (ListBounds.MinLength < 16) || 298 (ListBounds.MaxLength - ListBounds.MinLength < 16)) 299 { 300 DPRINT1("ListBounds are invalid: %u, %u\n", 301 ListBounds.MinLength, ListBounds.MaxLength); 302 return; 303 } 304 305 /* Set the new bounds globally */ 306 SepAdtMinListLength = ListBounds.MinLength; 307 SepAdtMaxListLength = ListBounds.MaxLength; 308 } 309 310 /** 311 * @brief 312 * Sets an audit event for future security auditing monitoring. 313 * 314 * @param[in,out] Message 315 * The reference monitoring API message. It is used to determine 316 * if the right API message number is provided, RmAuditSetCommand 317 * in this case. 318 * 319 * @return 320 * Returns STATUS_SUCCESS. 321 */ 322 static 323 NTSTATUS 324 SepRmSetAuditEvent( 325 _Inout_ PSEP_RM_API_MESSAGE Message) 326 { 327 ULONG i; 328 PAGED_CODE(); 329 330 /* First re-initialize the bounds from the registry */ 331 SepAdtInitializeBounds(); 332 333 /* Make sure we have the right message and clear */ 334 ASSERT(Message->ApiNumber == RmAuditSetCommand); 335 Message->ApiNumber = 0; 336 337 /* Store the enable flag in the global variable */ 338 SepAdtAuditingEnabled = Message->u.SetAuditEvent.Enabled; 339 340 /* Loop all audit event types */ 341 for (i = 0; i < POLICY_AUDIT_EVENT_TYPE_COUNT; i++) 342 { 343 /* Save the provided flags in the global array */ 344 SeAuditingState[i] = (UCHAR)Message->u.SetAuditEvent.Flags[i]; 345 } 346 347 return STATUS_SUCCESS; 348 } 349 350 /** 351 * @brief 352 * Inserts a logon session into an access token specified by the 353 * caller. 354 * 355 * @param[in,out] Token 356 * An access token where the logon session is about to be inserted 357 * in. 358 * 359 * @return 360 * STATUS_SUCCESS is returned if the logon session has been inserted into 361 * the token successfully. STATUS_NO_SUCH_LOGON_SESSION is returned when no logon 362 * session has been found with the matching ID of the token and as such 363 * we've failed to add the logon session to the token. STATUS_INSUFFICIENT_RESOURCES 364 * is returned if memory pool allocation for the new session has failed. 365 */ 366 NTSTATUS 367 NTAPI 368 SepRmInsertLogonSessionIntoToken( 369 _Inout_ PTOKEN Token) 370 { 371 PSEP_LOGON_SESSION_REFERENCES LogonSession; 372 PAGED_CODE(); 373 374 /* Ensure that our token is not some plain garbage */ 375 ASSERT(Token); 376 377 /* Acquire the database lock */ 378 KeAcquireGuardedMutex(&SepRmDbLock); 379 380 for (LogonSession = SepLogonSessions; 381 LogonSession != NULL; 382 LogonSession = LogonSession->Next) 383 { 384 /* 385 * The insertion of a logon session into the token has to be done 386 * only IF the authentication ID of the token matches with the ID 387 * of the logon itself. 388 */ 389 if (RtlEqualLuid(&LogonSession->LogonId, &Token->AuthenticationId)) 390 { 391 break; 392 } 393 } 394 395 /* If we reach this then we cannot proceed further */ 396 if (LogonSession == NULL) 397 { 398 DPRINT1("SepRmInsertLogonSessionIntoToken(): Couldn't insert the logon session into the specific access token!\n"); 399 KeReleaseGuardedMutex(&SepRmDbLock); 400 return STATUS_NO_SUCH_LOGON_SESSION; 401 } 402 403 /* 404 * Allocate the session that we are going 405 * to insert it to the token. 406 */ 407 Token->LogonSession = ExAllocatePoolWithTag(PagedPool, 408 sizeof(SEP_LOGON_SESSION_REFERENCES), 409 TAG_LOGON_SESSION); 410 if (Token->LogonSession == NULL) 411 { 412 DPRINT1("SepRmInsertLogonSessionIntoToken(): Couldn't allocate new logon session into the memory pool!\n"); 413 KeReleaseGuardedMutex(&SepRmDbLock); 414 return STATUS_INSUFFICIENT_RESOURCES; 415 } 416 417 /* 418 * Begin copying the logon session references data from the 419 * session whose ID matches with the token authentication ID to 420 * the new session we've allocated blocks of pool memory for it. 421 */ 422 Token->LogonSession->Next = LogonSession->Next; 423 Token->LogonSession->LogonId = LogonSession->LogonId; 424 Token->LogonSession->ReferenceCount = LogonSession->ReferenceCount; 425 Token->LogonSession->Flags = LogonSession->Flags; 426 Token->LogonSession->pDeviceMap = LogonSession->pDeviceMap; 427 InsertHeadList(&LogonSession->TokenList, &Token->LogonSession->TokenList); 428 429 /* Release the database lock and we're done */ 430 KeReleaseGuardedMutex(&SepRmDbLock); 431 return STATUS_SUCCESS; 432 } 433 434 /** 435 * @brief 436 * Removes a logon session from an access token. 437 * 438 * @param[in,out] Token 439 * An access token whose logon session is to be removed from it. 440 * 441 * @return 442 * STATUS_SUCCESS is returned if the logon session has been removed from 443 * the token successfully. STATUS_NO_SUCH_LOGON_SESSION is returned when no logon 444 * session has been found with the matching ID of the token and as such 445 * we've failed to remove the logon session from the token. 446 */ 447 NTSTATUS 448 NTAPI 449 SepRmRemoveLogonSessionFromToken( 450 _Inout_ PTOKEN Token) 451 { 452 PSEP_LOGON_SESSION_REFERENCES LogonSession; 453 PAGED_CODE(); 454 455 /* Ensure that our token is not some plain garbage */ 456 ASSERT(Token); 457 458 /* Acquire the database lock */ 459 KeAcquireGuardedMutex(&SepRmDbLock); 460 461 for (LogonSession = SepLogonSessions; 462 LogonSession != NULL; 463 LogonSession = LogonSession->Next) 464 { 465 /* 466 * Remove the logon session only when the IDs of the token and the 467 * logon match. 468 */ 469 if (RtlEqualLuid(&LogonSession->LogonId, &Token->AuthenticationId)) 470 { 471 break; 472 } 473 } 474 475 /* They don't match */ 476 if (LogonSession == NULL) 477 { 478 DPRINT1("SepRmRemoveLogonSessionFromToken(): Couldn't remove the logon session from the access token!\n"); 479 KeReleaseGuardedMutex(&SepRmDbLock); 480 return STATUS_NO_SUCH_LOGON_SESSION; 481 } 482 483 /* Now it's time to delete the logon session from the token */ 484 RemoveEntryList(&Token->LogonSession->TokenList); 485 ExFreePoolWithTag(Token->LogonSession, TAG_LOGON_SESSION); 486 487 /* Release the database lock and we're done */ 488 KeReleaseGuardedMutex(&SepRmDbLock); 489 return STATUS_SUCCESS; 490 } 491 492 /** 493 * @brief 494 * Creates a logon session. The security reference monitoring (SRM) 495 * module of Executive uses this as an internal kernel data for 496 * respective logon sessions management within the kernel, 497 * as in form of a SEP_LOGON_SESSION_REFERENCES data structure. 498 * 499 * @param[in] LogonLuid 500 * A logon ID represented as a LUID. This LUID is used to create 501 * our logon session and add it to the sessions database. 502 * 503 * @return 504 * Returns STATUS_SUCCESS if the logon has been created successfully. 505 * STATUS_LOGON_SESSION_EXISTS is returned if a logon session with 506 * the pointed logon ID in the call already exists. 507 * STATUS_INSUFFICIENT_RESOURCES is returned if logon session allocation 508 * has failed because of lack of memory pool resources. 509 */ 510 static 511 NTSTATUS 512 SepRmCreateLogonSession( 513 _In_ PLUID LogonLuid) 514 { 515 PSEP_LOGON_SESSION_REFERENCES CurrentSession, NewSession; 516 NTSTATUS Status; 517 PAGED_CODE(); 518 519 DPRINT("SepRmCreateLogonSession(%08lx:%08lx)\n", 520 LogonLuid->HighPart, LogonLuid->LowPart); 521 522 /* Allocate a new session structure */ 523 NewSession = ExAllocatePoolWithTag(PagedPool, 524 sizeof(SEP_LOGON_SESSION_REFERENCES), 525 TAG_LOGON_SESSION); 526 if (NewSession == NULL) 527 { 528 return STATUS_INSUFFICIENT_RESOURCES; 529 } 530 531 /* Initialize it */ 532 NewSession->LogonId = *LogonLuid; 533 NewSession->ReferenceCount = 0; 534 NewSession->Flags = 0; 535 NewSession->pDeviceMap = NULL; 536 InitializeListHead(&NewSession->TokenList); 537 538 /* Acquire the database lock */ 539 KeAcquireGuardedMutex(&SepRmDbLock); 540 541 /* Loop all existing sessions */ 542 for (CurrentSession = SepLogonSessions; 543 CurrentSession != NULL; 544 CurrentSession = CurrentSession->Next) 545 { 546 /* Check if the LUID matches the new one */ 547 if (RtlEqualLuid(&CurrentSession->LogonId, LogonLuid)) 548 { 549 Status = STATUS_LOGON_SESSION_EXISTS; 550 goto Leave; 551 } 552 } 553 554 /* Insert the new session */ 555 NewSession->Next = SepLogonSessions; 556 SepLogonSessions = NewSession; 557 558 Status = STATUS_SUCCESS; 559 560 Leave: 561 /* Release the database lock */ 562 KeReleaseGuardedMutex(&SepRmDbLock); 563 564 if (!NT_SUCCESS(Status)) 565 { 566 ExFreePoolWithTag(NewSession, TAG_LOGON_SESSION); 567 } 568 569 return Status; 570 } 571 572 /** 573 * @brief 574 * Deletes a logon session from the logon sessions database. 575 * 576 * @param[in] LogonLuid 577 * A logon ID represented as a LUID. This LUID is used to point 578 * the exact logon session saved within the database. 579 * 580 * @return 581 * STATUS_SUCCESS is returned if the logon session has been deleted successfully. 582 * STATUS_NO_SUCH_LOGON_SESSION is returned if the logon session with the submitted 583 * LUID doesn't exist. STATUS_BAD_LOGON_SESSION_STATE is returned if the logon session 584 * is still in use and we're not allowed to delete it, or if a system or anonymous session 585 * is submitted and we're not allowed to delete them as they're internal parts of the system. 586 * Otherwise a failure NTSTATUS code is returned. 587 */ 588 static 589 NTSTATUS 590 SepRmDeleteLogonSession( 591 _In_ PLUID LogonLuid) 592 { 593 PSEP_LOGON_SESSION_REFERENCES SessionToDelete; 594 NTSTATUS Status; 595 PAGED_CODE(); 596 597 DPRINT("SepRmDeleteLogonSession(%08lx:%08lx)\n", 598 LogonLuid->HighPart, LogonLuid->LowPart); 599 600 /* Acquire the database lock */ 601 KeAcquireGuardedMutex(&SepRmDbLock); 602 603 /* Loop over the existing logon sessions */ 604 for (SessionToDelete = SepLogonSessions; 605 SessionToDelete != NULL; 606 SessionToDelete = SessionToDelete->Next) 607 { 608 /* 609 * Does the actual logon session exist in the 610 * saved logon sessions database with the LUID 611 * provided? 612 */ 613 if (RtlEqualLuid(&SessionToDelete->LogonId, LogonLuid)) 614 { 615 /* Did the caller supply one of these internal sessions? */ 616 if (RtlEqualLuid(&SessionToDelete->LogonId, &SeSystemAuthenticationId) || 617 RtlEqualLuid(&SessionToDelete->LogonId, &SeAnonymousAuthenticationId)) 618 { 619 /* These logons are critical stuff, we can't delete them */ 620 DPRINT1("SepRmDeleteLogonSession(): We're not allowed to delete anonymous/system sessions!\n"); 621 Status = STATUS_BAD_LOGON_SESSION_STATE; 622 goto Leave; 623 } 624 else 625 { 626 /* We found the logon as exactly as we wanted, break the loop */ 627 break; 628 } 629 } 630 } 631 632 /* 633 * If we reach this then that means we've exhausted all the logon 634 * sessions and couldn't find one with the desired LUID. 635 */ 636 if (SessionToDelete == NULL) 637 { 638 DPRINT1("SepRmDeleteLogonSession(): The logon session with this LUID doesn't exist!\n"); 639 Status = STATUS_NO_SUCH_LOGON_SESSION; 640 goto Leave; 641 } 642 643 /* Is somebody still using this logon session? */ 644 if (SessionToDelete->ReferenceCount != 0) 645 { 646 /* The logon session is still in use, we cannot delete it... */ 647 DPRINT1("SepRmDeleteLogonSession(): The logon session is still in use!\n"); 648 Status = STATUS_BAD_LOGON_SESSION_STATE; 649 goto Leave; 650 } 651 652 /* If we have a LUID device map, clean it */ 653 if (SessionToDelete->pDeviceMap != NULL) 654 { 655 Status = SepCleanupLUIDDeviceMapDirectory(LogonLuid); 656 if (!NT_SUCCESS(Status)) 657 { 658 /* 659 * We had one job on cleaning the device map directory 660 * of the logon session but we failed, quit... 661 */ 662 DPRINT1("SepRmDeleteLogonSession(): Failed to clean the LUID device map directory of the logon (Status: 0x%lx)\n", Status); 663 goto Leave; 664 } 665 666 /* And dereference the device map of the logon */ 667 ObfDereferenceDeviceMap(SessionToDelete->pDeviceMap); 668 } 669 670 /* If we're here then we've deleted the logon session successfully */ 671 DPRINT("SepRmDeleteLogonSession(): Logon session deleted with success!\n"); 672 Status = STATUS_SUCCESS; 673 ExFreePoolWithTag(SessionToDelete, TAG_LOGON_SESSION); 674 675 Leave: 676 /* Release the database lock */ 677 KeReleaseGuardedMutex(&SepRmDbLock); 678 return Status; 679 } 680 681 /** 682 * @brief 683 * References a logon session. 684 * 685 * @param[in] LogonLuid 686 * A valid LUID that points to the logon session in the database that 687 * we're going to reference it. 688 * 689 * @return 690 * Returns STATUS_SUCCESS if the logon has been referenced. 691 * STATUS_NO_SUCH_LOGON_SESSION is returned if the session couldn't be 692 * found otherwise. 693 */ 694 NTSTATUS 695 SepRmReferenceLogonSession( 696 _In_ PLUID LogonLuid) 697 { 698 PSEP_LOGON_SESSION_REFERENCES CurrentSession; 699 700 PAGED_CODE(); 701 702 DPRINT("SepRmReferenceLogonSession(%08lx:%08lx)\n", 703 LogonLuid->HighPart, LogonLuid->LowPart); 704 705 /* Acquire the database lock */ 706 KeAcquireGuardedMutex(&SepRmDbLock); 707 708 /* Loop all existing sessions */ 709 for (CurrentSession = SepLogonSessions; 710 CurrentSession != NULL; 711 CurrentSession = CurrentSession->Next) 712 { 713 /* Check if the LUID matches the new one */ 714 if (RtlEqualLuid(&CurrentSession->LogonId, LogonLuid)) 715 { 716 /* Reference the session */ 717 ++CurrentSession->ReferenceCount; 718 DPRINT("ReferenceCount: %lu\n", CurrentSession->ReferenceCount); 719 720 /* Release the database lock */ 721 KeReleaseGuardedMutex(&SepRmDbLock); 722 723 return STATUS_SUCCESS; 724 } 725 } 726 727 /* Release the database lock */ 728 KeReleaseGuardedMutex(&SepRmDbLock); 729 730 return STATUS_NO_SUCH_LOGON_SESSION; 731 } 732 733 /** 734 * @brief 735 * Cleans the DOS device map directory of a logon 736 * session. 737 * 738 * @param[in] LogonLuid 739 * A logon session ID where its DOS device map directory 740 * is to be cleaned. 741 * 742 * @return 743 * Returns STATUS_SUCCESS if the device map directory has been 744 * successfully cleaned from the logon session. STATUS_INVALID_PARAMETER 745 * is returned if the caller hasn't submitted any logon ID. STATUS_NO_MEMORY 746 * is returned if buffer allocation for links has failed. A failure 747 * NTSTATUS code is returned otherwise. 748 */ 749 static 750 NTSTATUS 751 SepCleanupLUIDDeviceMapDirectory( 752 _In_ PLUID LogonLuid) 753 { 754 BOOLEAN UseCurrentProc; 755 KAPC_STATE ApcState; 756 WCHAR Buffer[63]; 757 UNICODE_STRING DirectoryName; 758 OBJECT_ATTRIBUTES ObjectAttributes; 759 NTSTATUS Status; 760 HANDLE DirectoryHandle, LinkHandle; 761 PHANDLE LinksBuffer; 762 POBJECT_DIRECTORY_INFORMATION DirectoryInfo; 763 ULONG LinksCount, LinksSize, DirInfoLength, ReturnLength, Context, CurrentLinks, i; 764 BOOLEAN RestartScan; 765 766 PAGED_CODE(); 767 768 /* We need a logon LUID */ 769 if (LogonLuid == NULL) 770 { 771 return STATUS_INVALID_PARAMETER; 772 } 773 774 /* Use current process */ 775 UseCurrentProc = ObReferenceObjectSafe(PsGetCurrentProcess()); 776 if (UseCurrentProc) 777 { 778 ObDereferenceObject(PsGetCurrentProcess()); 779 } 780 /* Unless it's gone, then use system process */ 781 else 782 { 783 KeStackAttachProcess(&PsInitialSystemProcess->Pcb, &ApcState); 784 } 785 786 /* Initialize our directory name */ 787 _snwprintf(Buffer, 788 sizeof(Buffer) / sizeof(WCHAR), 789 L"\\Sessions\\0\\DosDevices\\%08x-%08x", 790 LogonLuid->HighPart, 791 LogonLuid->LowPart); 792 RtlInitUnicodeString(&DirectoryName, Buffer); 793 794 /* And open it */ 795 InitializeObjectAttributes(&ObjectAttributes, 796 &DirectoryName, 797 OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, 798 NULL, 799 NULL); 800 Status = ZwOpenDirectoryObject(&DirectoryHandle, 801 DIRECTORY_QUERY, 802 &ObjectAttributes); 803 if (!NT_SUCCESS(Status)) 804 { 805 if (!UseCurrentProc) 806 { 807 KeUnstackDetachProcess(&ApcState); 808 } 809 810 return Status; 811 } 812 813 /* Some initialization needed for browsing all our links... */ 814 Context = 0; 815 DirectoryInfo = NULL; 816 DirInfoLength = 0; 817 /* In our buffer, we'll store at max 100 HANDLE */ 818 LinksCount = 100; 819 CurrentLinks = 0; 820 /* Which gives a certain size */ 821 LinksSize = LinksCount * sizeof(HANDLE); 822 823 /* 824 * This label is hit if we need to store more than a hundred 825 * of links. In that case, we jump here after having cleaned 826 * and deleted previous buffer. 827 * All handles have been already closed 828 */ 829 AllocateLinksAgain: 830 LinksBuffer = ExAllocatePoolWithTag(PagedPool, 831 LinksSize, 832 TAG_SE_HANDLES_TAB); 833 if (LinksBuffer == NULL) 834 { 835 /* 836 * Failure path: no need to clear handles: 837 * already closed and the buffer is already gone 838 */ 839 ZwClose(DirectoryHandle); 840 841 /* 842 * On the first round, DirectoryInfo is NULL, 843 * if we grow LinksBuffer, it has been allocated 844 */ 845 if (DirectoryInfo != NULL) 846 { 847 ExFreePoolWithTag(DirectoryInfo, TAG_SE_DIR_BUFFER); 848 } 849 850 if (!UseCurrentProc) 851 { 852 KeUnstackDetachProcess(&ApcState); 853 } 854 855 return STATUS_NO_MEMORY; 856 } 857 858 /* 859 * We always restart scan, but on the first loop 860 * if we couldn't fit everything in our buffer, 861 * then, we continue scan. 862 * But we restart if link buffer was too small 863 */ 864 for (RestartScan = TRUE; ; RestartScan = FALSE) 865 { 866 /* 867 * Loop until our buffer is big enough to store 868 * one entry 869 */ 870 while (TRUE) 871 { 872 Status = ZwQueryDirectoryObject(DirectoryHandle, 873 DirectoryInfo, 874 DirInfoLength, 875 TRUE, 876 RestartScan, 877 &Context, 878 &ReturnLength); 879 /* Only handle buffer growth in that loop */ 880 if (Status != STATUS_BUFFER_TOO_SMALL) 881 { 882 break; 883 } 884 885 /* Get output length as new length */ 886 DirInfoLength = ReturnLength; 887 /* Delete old buffer if any */ 888 if (DirectoryInfo != NULL) 889 { 890 ExFreePoolWithTag(DirectoryInfo, 'bDeS'); 891 } 892 893 /* And reallocate a bigger one */ 894 DirectoryInfo = ExAllocatePoolWithTag(PagedPool, 895 DirInfoLength, 896 TAG_SE_DIR_BUFFER); 897 /* Fail if we cannot allocate */ 898 if (DirectoryInfo == NULL) 899 { 900 Status = STATUS_INSUFFICIENT_RESOURCES; 901 break; 902 } 903 } 904 905 /* If querying the entry failed, quit */ 906 if (!NT_SUCCESS(Status)) 907 { 908 break; 909 } 910 911 /* We only look for symbolic links, the rest, we ignore */ 912 if (wcscmp(DirectoryInfo->TypeName.Buffer, L"SymbolicLink")) 913 { 914 continue; 915 } 916 917 /* If our link buffer is out of space, reallocate */ 918 if (CurrentLinks >= LinksCount) 919 { 920 /* First, close the links */ 921 for (i = 0; i < CurrentLinks; ++i) 922 { 923 ZwClose(LinksBuffer[i]); 924 } 925 926 /* Allow 20 more HANDLEs */ 927 LinksCount += 20; 928 CurrentLinks = 0; 929 ExFreePoolWithTag(LinksBuffer, TAG_SE_HANDLES_TAB); 930 LinksSize = LinksCount * sizeof(HANDLE); 931 932 /* And reloop again */ 933 goto AllocateLinksAgain; 934 } 935 936 /* Open the found link */ 937 InitializeObjectAttributes(&ObjectAttributes, 938 &DirectoryInfo->Name, 939 OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, 940 DirectoryHandle, 941 NULL); 942 if (NT_SUCCESS(ZwOpenSymbolicLinkObject(&LinkHandle, 943 SYMBOLIC_LINK_ALL_ACCESS, 944 &ObjectAttributes))) 945 { 946 /* If we cannot make it temporary, just close the link handle */ 947 if (!NT_SUCCESS(ZwMakeTemporaryObject(LinkHandle))) 948 { 949 ZwClose(LinkHandle); 950 } 951 /* Otherwise, store it to defer deletion */ 952 else 953 { 954 LinksBuffer[CurrentLinks] = LinkHandle; 955 ++CurrentLinks; 956 } 957 } 958 } 959 960 /* No more entries means we handled all links, that's not a failure */ 961 if (Status == STATUS_NO_MORE_ENTRIES) 962 { 963 Status = STATUS_SUCCESS; 964 } 965 966 /* Close all the links we stored, this will like cause their deletion */ 967 for (i = 0; i < CurrentLinks; ++i) 968 { 969 ZwClose(LinksBuffer[i]); 970 } 971 /* And free our links buffer */ 972 ExFreePoolWithTag(LinksBuffer, TAG_SE_HANDLES_TAB); 973 974 /* Free our directory info buffer - it might be NULL if we failed realloc */ 975 if (DirectoryInfo != NULL) 976 { 977 ExFreePoolWithTag(DirectoryInfo, TAG_SE_DIR_BUFFER); 978 } 979 980 /* Close our session directory */ 981 ZwClose(DirectoryHandle); 982 983 /* And detach from system */ 984 if (!UseCurrentProc) 985 { 986 KeUnstackDetachProcess(&ApcState); 987 } 988 989 return Status; 990 } 991 992 /** 993 * @brief 994 * De-references a logon session. If the session has a reference 995 * count of 0 by the time the function has de-referenced the logon, 996 * that means the session is no longer used and can be safely deleted 997 * from the logon sessions database. 998 * 999 * @param[in] LogonLuid 1000 * A logon session ID to de-reference. 1001 * 1002 * @return 1003 * Returns STATUS_SUCCESS if the logon session has been de-referenced 1004 * without issues. STATUS_NO_SUCH_LOGON_SESSION is returned if no 1005 * such logon exists otherwise. 1006 */ 1007 NTSTATUS 1008 SepRmDereferenceLogonSession( 1009 _In_ PLUID LogonLuid) 1010 { 1011 ULONG RefCount; 1012 PDEVICE_MAP DeviceMap; 1013 PSEP_LOGON_SESSION_REFERENCES CurrentSession; 1014 1015 DPRINT("SepRmDereferenceLogonSession(%08lx:%08lx)\n", 1016 LogonLuid->HighPart, LogonLuid->LowPart); 1017 1018 /* Acquire the database lock */ 1019 KeAcquireGuardedMutex(&SepRmDbLock); 1020 1021 /* Loop all existing sessions */ 1022 for (CurrentSession = SepLogonSessions; 1023 CurrentSession != NULL; 1024 CurrentSession = CurrentSession->Next) 1025 { 1026 /* Check if the LUID matches the new one */ 1027 if (RtlEqualLuid(&CurrentSession->LogonId, LogonLuid)) 1028 { 1029 /* Dereference the session */ 1030 RefCount = --CurrentSession->ReferenceCount; 1031 DPRINT("ReferenceCount: %lu\n", CurrentSession->ReferenceCount); 1032 1033 /* Release the database lock */ 1034 KeReleaseGuardedMutex(&SepRmDbLock); 1035 1036 /* We're done with the session */ 1037 if (RefCount == 0) 1038 { 1039 /* Get rid of the LUID device map */ 1040 DeviceMap = CurrentSession->pDeviceMap; 1041 if (DeviceMap != NULL) 1042 { 1043 CurrentSession->pDeviceMap = NULL; 1044 SepCleanupLUIDDeviceMapDirectory(LogonLuid); 1045 ObfDereferenceDeviceMap(DeviceMap); 1046 } 1047 1048 /* FIXME: Alert LSA and filesystems that a logon is about to be deleted */ 1049 } 1050 1051 return STATUS_SUCCESS; 1052 } 1053 } 1054 1055 /* Release the database lock */ 1056 KeReleaseGuardedMutex(&SepRmDbLock); 1057 1058 return STATUS_NO_SUCH_LOGON_SESSION; 1059 } 1060 1061 /** 1062 * @brief 1063 * Main SRM server thread initialization function. It deals 1064 * with security manager and LSASS port connection, thus 1065 * thereby allowing communication between the kernel side 1066 * (the SRM) and user mode side (the LSASS) of the security 1067 * world of the operating system. 1068 * 1069 * @return 1070 * Returns TRUE if command server connection between SRM 1071 * and LSASS has succeeded, FALSE otherwise. 1072 */ 1073 BOOLEAN 1074 NTAPI 1075 SepRmCommandServerThreadInit(VOID) 1076 { 1077 SECURITY_QUALITY_OF_SERVICE SecurityQos; 1078 SEP_RM_API_MESSAGE Message; 1079 UNICODE_STRING PortName; 1080 REMOTE_PORT_VIEW RemotePortView; 1081 PORT_VIEW PortView; 1082 LARGE_INTEGER SectionSize; 1083 HANDLE SectionHandle; 1084 HANDLE PortHandle; 1085 NTSTATUS Status; 1086 BOOLEAN Result; 1087 1088 SectionHandle = NULL; 1089 PortHandle = NULL; 1090 1091 /* Assume success */ 1092 Result = TRUE; 1093 1094 /* Wait until LSASS is ready */ 1095 Status = ZwWaitForSingleObject(SeLsaInitEvent, FALSE, NULL); 1096 if (!NT_SUCCESS(Status)) 1097 { 1098 DPRINT1("Security Rm Init: Waiting for LSA Init Event failed 0x%lx\n", Status); 1099 goto Cleanup; 1100 } 1101 1102 /* We don't need this event anymore */ 1103 ObCloseHandle(SeLsaInitEvent, KernelMode); 1104 1105 /* Initialize the connection message */ 1106 Message.Header.u1.s1.TotalLength = sizeof(Message); 1107 Message.Header.u1.s1.DataLength = 0; 1108 1109 /* Only LSASS can connect, so handle the connection right now */ 1110 Status = ZwListenPort(SeRmCommandPort, &Message.Header); 1111 if (!NT_SUCCESS(Status)) 1112 { 1113 DPRINT1("Security Rm Init: Listen to Command Port failed 0x%lx\n", Status); 1114 goto Cleanup; 1115 } 1116 1117 /* Set the Port View structure length */ 1118 RemotePortView.Length = sizeof(RemotePortView); 1119 1120 /* Accept the connection */ 1121 Status = ZwAcceptConnectPort(&SepRmCommandMessagePort, 1122 NULL, 1123 &Message.Header, 1124 TRUE, 1125 NULL, 1126 &RemotePortView); 1127 if (!NT_SUCCESS(Status)) 1128 { 1129 DPRINT1("Security Rm Init: Accept Connect to Command Port failed 0x%lx\n", Status); 1130 goto Cleanup; 1131 } 1132 1133 /* Complete the connection */ 1134 Status = ZwCompleteConnectPort(SepRmCommandMessagePort); 1135 if (!NT_SUCCESS(Status)) 1136 { 1137 DPRINT1("Security Rm Init: Complete Connect to Command Port failed 0x%lx\n", Status); 1138 goto Cleanup; 1139 } 1140 1141 /* Create a section for messages */ 1142 SectionSize.QuadPart = PAGE_SIZE; 1143 Status = ZwCreateSection(&SectionHandle, 1144 SECTION_ALL_ACCESS, 1145 NULL, 1146 &SectionSize, 1147 PAGE_READWRITE, 1148 SEC_COMMIT, 1149 NULL); 1150 if (!NT_SUCCESS(Status)) 1151 { 1152 DPRINT1("Security Rm Init: Create Memory Section for LSA port failed: %X\n", Status); 1153 goto Cleanup; 1154 } 1155 1156 /* Setup the PORT_VIEW structure */ 1157 PortView.Length = sizeof(PortView); 1158 PortView.SectionHandle = SectionHandle; 1159 PortView.SectionOffset = 0; 1160 PortView.ViewSize = SectionSize.LowPart; 1161 PortView.ViewBase = NULL; 1162 PortView.ViewRemoteBase = NULL; 1163 1164 /* Setup security QOS */ 1165 SecurityQos.Length = sizeof(SecurityQos); 1166 SecurityQos.ImpersonationLevel = SecurityImpersonation; 1167 SecurityQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; 1168 SecurityQos.EffectiveOnly = TRUE; 1169 1170 /* Connect to LSASS */ 1171 RtlInitUnicodeString(&PortName, L"\\SeLsaCommandPort"); 1172 Status = ZwConnectPort(&PortHandle, 1173 &PortName, 1174 &SecurityQos, 1175 &PortView, 1176 NULL, 1177 0, 1178 0, 1179 0); 1180 if (!NT_SUCCESS(Status)) 1181 { 1182 DPRINT1("Security Rm Init: Connect to LSA Port failed 0x%lx\n", Status); 1183 goto Cleanup; 1184 } 1185 1186 /* Remember section base and view offset */ 1187 SepCommandPortViewBase = PortView.ViewBase; 1188 SepCommandPortViewRemoteBase = PortView.ViewRemoteBase; 1189 SepCommandPortViewBaseOffset = (ULONG_PTR)SepCommandPortViewRemoteBase - 1190 (ULONG_PTR)SepCommandPortViewBase; 1191 1192 DPRINT("SepRmCommandServerThreadInit: done\n"); 1193 1194 Cleanup: 1195 /* Check for failure */ 1196 if (!NT_SUCCESS(Status)) 1197 { 1198 if (PortHandle != NULL) 1199 { 1200 ObCloseHandle(PortHandle, KernelMode); 1201 } 1202 1203 Result = FALSE; 1204 } 1205 1206 /* Did we create a section? */ 1207 if (SectionHandle != NULL) 1208 { 1209 ObCloseHandle(SectionHandle, KernelMode); 1210 } 1211 1212 return Result; 1213 } 1214 1215 /** 1216 * @brief 1217 * Manages the SRM server API commands, that is, receiving such API 1218 * command messages from the user mode side of the security standpoint, 1219 * the LSASS. 1220 * 1221 * @return 1222 * Nothing. 1223 */ 1224 VOID 1225 NTAPI 1226 SepRmCommandServerThread( 1227 _In_ PVOID StartContext) 1228 { 1229 SEP_RM_API_MESSAGE Message; 1230 PPORT_MESSAGE ReplyMessage; 1231 HANDLE DummyPortHandle; 1232 NTSTATUS Status; 1233 1234 /* Initialize the server thread */ 1235 if (!SepRmCommandServerThreadInit()) 1236 { 1237 DPRINT1("Security: Terminating Rm Command Server Thread\n"); 1238 return; 1239 } 1240 1241 /* No reply yet */ 1242 ReplyMessage = NULL; 1243 1244 /* Start looping */ 1245 while (TRUE) 1246 { 1247 /* Wait for a message */ 1248 Status = ZwReplyWaitReceivePort(SepRmCommandMessagePort, 1249 NULL, 1250 ReplyMessage, 1251 &Message.Header); 1252 if (!NT_SUCCESS(Status)) 1253 { 1254 DPRINT1("Failed to get message: 0x%lx", Status); 1255 ReplyMessage = NULL; 1256 continue; 1257 } 1258 1259 /* Check if this is a connection request */ 1260 if (Message.Header.u2.s2.Type == LPC_CONNECTION_REQUEST) 1261 { 1262 /* Reject connection request */ 1263 ZwAcceptConnectPort(&DummyPortHandle, 1264 NULL, 1265 &Message.Header, 1266 FALSE, 1267 NULL, 1268 NULL); 1269 1270 /* Start over */ 1271 ReplyMessage = NULL; 1272 continue; 1273 } 1274 1275 /* Check if the port died */ 1276 if ((Message.Header.u2.s2.Type == LPC_PORT_CLOSED) || 1277 (Message.Header.u2.s2.Type == LPC_CLIENT_DIED)) 1278 { 1279 /* LSASS is dead, so let's quit as well */ 1280 break; 1281 } 1282 1283 /* Check if this is an actual request */ 1284 if (Message.Header.u2.s2.Type != LPC_REQUEST) 1285 { 1286 DPRINT1("SepRmCommandServerThread: unexpected message type: 0x%lx\n", 1287 Message.Header.u2.s2.Type); 1288 1289 /* Restart without replying */ 1290 ReplyMessage = NULL; 1291 continue; 1292 } 1293 1294 ReplyMessage = &Message.Header; 1295 1296 switch (Message.ApiNumber) 1297 { 1298 case RmAuditSetCommand: 1299 Status = SepRmSetAuditEvent(&Message); 1300 break; 1301 1302 case RmCreateLogonSession: 1303 Status = SepRmCreateLogonSession(&Message.u.LogonLuid); 1304 break; 1305 1306 case RmDeleteLogonSession: 1307 Status = SepRmDeleteLogonSession(&Message.u.LogonLuid); 1308 break; 1309 1310 default: 1311 DPRINT1("SepRmDispatchRequest: invalid API number: 0x%lx\n", 1312 Message.ApiNumber); 1313 ReplyMessage = NULL; 1314 } 1315 1316 Message.u.ResultStatus = Status; 1317 } 1318 1319 /* Close the port handles */ 1320 ObCloseHandle(SepRmCommandMessagePort, KernelMode); 1321 ObCloseHandle(SeRmCommandPort, KernelMode); 1322 } 1323 1324 1325 /* PUBLIC FUNCTIONS ***********************************************************/ 1326 1327 /** 1328 * @brief 1329 * Retrieves the DOS device map from a logon session. 1330 * 1331 * @param[in] LogonId 1332 * A valid logon session ID. 1333 * 1334 * @param[out] DeviceMap 1335 * The returned device map buffer from the logon session. 1336 * 1337 * @return 1338 * Returns STATUS_SUCCESS if the device map could be gathered 1339 * from the logon session. STATUS_INVALID_PARAMETER is returned if 1340 * one of the parameters aren't initialized (that is, the caller has 1341 * submitted a NULL pointer variable). STATUS_NO_SUCH_LOGON_SESSION is 1342 * returned if no such session could be found. A failure NTSTATUS code 1343 * is returned otherwise. 1344 */ 1345 NTSTATUS 1346 NTAPI 1347 SeGetLogonIdDeviceMap( 1348 _In_ PLUID LogonId, 1349 _Out_ PDEVICE_MAP *DeviceMap) 1350 { 1351 NTSTATUS Status; 1352 WCHAR Buffer[63]; 1353 PDEVICE_MAP LocalMap; 1354 HANDLE DirectoryHandle, LinkHandle; 1355 OBJECT_ATTRIBUTES ObjectAttributes; 1356 PSEP_LOGON_SESSION_REFERENCES CurrentSession; 1357 UNICODE_STRING DirectoryName, LinkName, TargetName; 1358 1359 PAGED_CODE(); 1360 1361 if (LogonId == NULL || 1362 DeviceMap == NULL) 1363 { 1364 return STATUS_INVALID_PARAMETER; 1365 } 1366 1367 /* Acquire the database lock */ 1368 KeAcquireGuardedMutex(&SepRmDbLock); 1369 1370 /* Loop all existing sessions */ 1371 for (CurrentSession = SepLogonSessions; 1372 CurrentSession != NULL; 1373 CurrentSession = CurrentSession->Next) 1374 { 1375 /* Check if the LUID matches the provided one */ 1376 if (RtlEqualLuid(&CurrentSession->LogonId, LogonId)) 1377 { 1378 break; 1379 } 1380 } 1381 1382 /* No session found, fail */ 1383 if (CurrentSession == NULL) 1384 { 1385 /* Release the database lock */ 1386 KeReleaseGuardedMutex(&SepRmDbLock); 1387 1388 return STATUS_NO_SUCH_LOGON_SESSION; 1389 } 1390 1391 /* The found session has a device map, return it! */ 1392 if (CurrentSession->pDeviceMap != NULL) 1393 { 1394 *DeviceMap = CurrentSession->pDeviceMap; 1395 1396 /* Release the database lock */ 1397 KeReleaseGuardedMutex(&SepRmDbLock); 1398 1399 return STATUS_SUCCESS; 1400 } 1401 1402 /* At that point, we'll setup a new device map for the session */ 1403 LocalMap = NULL; 1404 1405 /* Reference the session so that it doesn't go away */ 1406 CurrentSession->ReferenceCount += 1; 1407 1408 /* Release the database lock */ 1409 KeReleaseGuardedMutex(&SepRmDbLock); 1410 1411 /* Create our object directory given the LUID */ 1412 _snwprintf(Buffer, 1413 sizeof(Buffer) / sizeof(WCHAR), 1414 L"\\Sessions\\0\\DosDevices\\%08x-%08x", 1415 LogonId->HighPart, 1416 LogonId->LowPart); 1417 RtlInitUnicodeString(&DirectoryName, Buffer); 1418 1419 InitializeObjectAttributes(&ObjectAttributes, 1420 &DirectoryName, 1421 OBJ_KERNEL_HANDLE | OBJ_OPENIF | OBJ_CASE_INSENSITIVE, 1422 NULL, 1423 NULL); 1424 Status = ZwCreateDirectoryObject(&DirectoryHandle, 1425 DIRECTORY_ALL_ACCESS, 1426 &ObjectAttributes); 1427 if (NT_SUCCESS(Status)) 1428 { 1429 /* Create the associated device map */ 1430 Status = ObSetDirectoryDeviceMap(&LocalMap, DirectoryHandle); 1431 if (NT_SUCCESS(Status)) 1432 { 1433 /* Make Global point to \Global?? in the directory */ 1434 RtlInitUnicodeString(&LinkName, L"Global"); 1435 RtlInitUnicodeString(&TargetName, L"\\Global??"); 1436 1437 InitializeObjectAttributes(&ObjectAttributes, 1438 &LinkName, 1439 OBJ_KERNEL_HANDLE | OBJ_OPENIF | OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, 1440 DirectoryHandle, 1441 NULL); 1442 Status = ZwCreateSymbolicLinkObject(&LinkHandle, 1443 SYMBOLIC_LINK_ALL_ACCESS, 1444 &ObjectAttributes, 1445 &TargetName); 1446 if (!NT_SUCCESS(Status)) 1447 { 1448 ObfDereferenceDeviceMap(LocalMap); 1449 } 1450 else 1451 { 1452 ZwClose(LinkHandle); 1453 } 1454 } 1455 1456 ZwClose(DirectoryHandle); 1457 } 1458 1459 /* Acquire the database lock */ 1460 KeAcquireGuardedMutex(&SepRmDbLock); 1461 1462 /* If we succeed... */ 1463 if (NT_SUCCESS(Status)) 1464 { 1465 /* The session now has a device map? We raced with someone else */ 1466 if (CurrentSession->pDeviceMap != NULL) 1467 { 1468 /* Give up on our new device map */ 1469 ObfDereferenceDeviceMap(LocalMap); 1470 } 1471 /* Otherwise use our newly allocated device map */ 1472 else 1473 { 1474 CurrentSession->pDeviceMap = LocalMap; 1475 } 1476 1477 /* Return the device map */ 1478 *DeviceMap = CurrentSession->pDeviceMap; 1479 } 1480 /* Zero output */ 1481 else 1482 { 1483 *DeviceMap = NULL; 1484 } 1485 1486 /* Release the database lock */ 1487 KeReleaseGuardedMutex(&SepRmDbLock); 1488 1489 /* We're done with the session */ 1490 SepRmDereferenceLogonSession(&CurrentSession->LogonId); 1491 1492 return Status; 1493 } 1494 1495 /** 1496 * @brief 1497 * Marks a logon session for future termination, given its logon ID. This triggers 1498 * a callout (the registered callback) when the logon is no longer used by anyone, 1499 * that is, no token is still referencing the speciffied logon session. 1500 * 1501 * @param[in] LogonId 1502 * The ID of the logon session. 1503 * 1504 * @return 1505 * STATUS_SUCCESS if the logon session is marked for termination notification successfully, 1506 * STATUS_NOT_FOUND if the logon session couldn't be found otherwise. 1507 */ 1508 NTSTATUS 1509 NTAPI 1510 SeMarkLogonSessionForTerminationNotification( 1511 _In_ PLUID LogonId) 1512 { 1513 PSEP_LOGON_SESSION_REFERENCES SessionToMark; 1514 PAGED_CODE(); 1515 1516 DPRINT("SeMarkLogonSessionForTerminationNotification(%08lx:%08lx)\n", 1517 LogonId->HighPart, LogonId->LowPart); 1518 1519 /* Acquire the database lock */ 1520 KeAcquireGuardedMutex(&SepRmDbLock); 1521 1522 /* Loop over the existing logon sessions */ 1523 for (SessionToMark = SepLogonSessions; 1524 SessionToMark != NULL; 1525 SessionToMark = SessionToMark->Next) 1526 { 1527 /* Does the logon with the given ID exist? */ 1528 if (RtlEqualLuid(&SessionToMark->LogonId, LogonId)) 1529 { 1530 /* We found it */ 1531 break; 1532 } 1533 } 1534 1535 /* 1536 * We've exhausted all the remaining logon sessions and 1537 * couldn't find one with the provided ID. 1538 */ 1539 if (SessionToMark == NULL) 1540 { 1541 DPRINT1("SeMarkLogonSessionForTerminationNotification(): Logon session couldn't be found!\n"); 1542 KeReleaseGuardedMutex(&SepRmDbLock); 1543 return STATUS_NOT_FOUND; 1544 } 1545 1546 /* Mark the logon session for termination */ 1547 SessionToMark->Flags |= SEP_LOGON_SESSION_TERMINATION_NOTIFY; 1548 DPRINT("SeMarkLogonSessionForTerminationNotification(): Logon session marked for termination with success!\n"); 1549 1550 /* Release the database lock */ 1551 KeReleaseGuardedMutex(&SepRmDbLock); 1552 return STATUS_SUCCESS; 1553 } 1554 1555 /** 1556 * @brief 1557 * Registers a callback that will be called once a logon session 1558 * terminates. 1559 * 1560 * @param[in] CallbackRoutine 1561 * Callback routine address. 1562 * 1563 * @return 1564 * Returns STATUS_SUCCESS if the callback routine was registered 1565 * successfully. STATUS_INVALID_PARAMETER is returned if the caller 1566 * did not provide a callback routine. STATUS_INSUFFICIENT_RESOURCES 1567 * is returned if the callback notification data couldn't be allocated 1568 * because of lack of memory pool resources. 1569 */ 1570 NTSTATUS 1571 NTAPI 1572 SeRegisterLogonSessionTerminatedRoutine( 1573 _In_ PSE_LOGON_SESSION_TERMINATED_ROUTINE CallbackRoutine) 1574 { 1575 PSEP_LOGON_SESSION_TERMINATED_NOTIFICATION Notification; 1576 PAGED_CODE(); 1577 1578 /* Fail, if we don not have a callback routine */ 1579 if (CallbackRoutine == NULL) 1580 return STATUS_INVALID_PARAMETER; 1581 1582 /* Allocate a new notification item */ 1583 Notification = ExAllocatePoolWithTag(PagedPool, 1584 sizeof(SEP_LOGON_SESSION_TERMINATED_NOTIFICATION), 1585 TAG_LOGON_NOTIFICATION); 1586 if (Notification == NULL) 1587 return STATUS_INSUFFICIENT_RESOURCES; 1588 1589 /* Acquire the database lock */ 1590 KeAcquireGuardedMutex(&SepRmDbLock); 1591 1592 /* Set the callback routine */ 1593 Notification->CallbackRoutine = CallbackRoutine; 1594 1595 /* Insert the new notification item into the list */ 1596 Notification->Next = SepLogonNotifications; 1597 SepLogonNotifications = Notification; 1598 1599 /* Release the database lock */ 1600 KeReleaseGuardedMutex(&SepRmDbLock); 1601 1602 return STATUS_SUCCESS; 1603 } 1604 1605 /** 1606 * @brief 1607 * Un-registers a callback routine, previously registered by 1608 * SeRegisterLogonSessionTerminatedRoutine function. 1609 * 1610 * @param[in] CallbackRoutine 1611 * Callback routine address to un-register. 1612 * 1613 * @return 1614 * Returns STATUS_SUCCESS if the callback routine was un-registered 1615 * successfully. STATUS_INVALID_PARAMETER is returned if the caller 1616 * did not provide a callback routine. STATUS_NOT_FOUND is returned 1617 * if the callback notification item couldn't be found. 1618 */ 1619 NTSTATUS 1620 NTAPI 1621 SeUnregisterLogonSessionTerminatedRoutine( 1622 _In_ PSE_LOGON_SESSION_TERMINATED_ROUTINE CallbackRoutine) 1623 { 1624 PSEP_LOGON_SESSION_TERMINATED_NOTIFICATION Current, Previous = NULL; 1625 NTSTATUS Status; 1626 PAGED_CODE(); 1627 1628 /* Fail, if we don not have a callback routine */ 1629 if (CallbackRoutine == NULL) 1630 return STATUS_INVALID_PARAMETER; 1631 1632 /* Acquire the database lock */ 1633 KeAcquireGuardedMutex(&SepRmDbLock); 1634 1635 /* Loop all registered notification items */ 1636 for (Current = SepLogonNotifications; 1637 Current != NULL; 1638 Current = Current->Next) 1639 { 1640 /* Check if the callback routine matches the provided one */ 1641 if (Current->CallbackRoutine == CallbackRoutine) 1642 break; 1643 1644 Previous = Current; 1645 } 1646 1647 if (Current == NULL) 1648 { 1649 Status = STATUS_NOT_FOUND; 1650 } 1651 else 1652 { 1653 /* Remove the current notification item from the list */ 1654 if (Previous == NULL) 1655 SepLogonNotifications = Current->Next; 1656 else 1657 Previous->Next = Current->Next; 1658 1659 /* Free the current notification item */ 1660 ExFreePoolWithTag(Current, 1661 TAG_LOGON_NOTIFICATION); 1662 1663 Status = STATUS_SUCCESS; 1664 } 1665 1666 /* Release the database lock */ 1667 KeReleaseGuardedMutex(&SepRmDbLock); 1668 1669 return Status; 1670 } 1671 1672 /* EOF */ 1673