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
SepRegQueryHelper(_In_ PCWSTR KeyName,_In_ PCWSTR ValueName,_In_ ULONG ValueType,_In_ ULONG DataLength,_Out_ PVOID ValueData)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
SeRmInitPhase0(VOID)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
SeRmInitPhase1(VOID)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
SepAdtInitializeBounds(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
SepRmSetAuditEvent(_Inout_ PSEP_RM_API_MESSAGE Message)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
SepRmInsertLogonSessionIntoToken(_Inout_ PTOKEN Token)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
SepRmRemoveLogonSessionFromToken(_Inout_ PTOKEN Token)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
SepRmCreateLogonSession(_In_ PLUID LogonLuid)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
SepRmDeleteLogonSession(_In_ PLUID LogonLuid)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
SepRmReferenceLogonSession(_In_ PLUID LogonLuid)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
SepCleanupLUIDDeviceMapDirectory(_In_ PLUID LogonLuid)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
SepRmDereferenceLogonSession(_In_ PLUID LogonLuid)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
SepRmCommandServerThreadInit(VOID)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
SepRmCommandServerThread(_In_ PVOID StartContext)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
SeGetLogonIdDeviceMap(_In_ PLUID LogonId,_Out_ PDEVICE_MAP * DeviceMap)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
SeMarkLogonSessionForTerminationNotification(_In_ PLUID LogonId)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
SeRegisterLogonSessionTerminatedRoutine(_In_ PSE_LOGON_SESSION_TERMINATED_ROUTINE CallbackRoutine)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
SeUnregisterLogonSessionTerminatedRoutine(_In_ PSE_LOGON_SESSION_TERMINATED_ROUTINE CallbackRoutine)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