xref: /reactos/ntoskrnl/se/srm.c (revision 8b75dce4)
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