2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Security access state functions support
5 * COPYRIGHT: Copyright Alex Ionescu <alex@relsoft.net>
8 /* INCLUDES *******************************************************************/
14 /* GLOBALS ********************************************************************/
16 ERESOURCE SepSubjectContextLock
;
18 /* PRIVATE FUNCTIONS **********************************************************/
22 * Checks if a SID is present in a token.
25 * A valid token object.
27 * @param[in] PrincipalSelfSid
28 * A principal self SID.
34 * If set to TRUE, the caller expected that a SID in a token
35 * must be a deny-only SID, that is, access checks are performed
36 * only for deny-only ACEs of the said SID.
38 * @param[in] Restricted
39 * If set to TRUE, the caller expects that a SID in a token is
43 * Returns TRUE if the specified SID in the call is present in the token,
49 _In_ PACCESS_TOKEN _Token
,
50 _In_ PSID PrincipalSelfSid
,
53 _In_ BOOLEAN Restricted
)
56 PTOKEN Token
= (PTOKEN
)_Token
;
57 PISID TokenSid
, Sid
= (PISID
)_Sid
;
58 PSID_AND_ATTRIBUTES SidAndAttributes
;
59 ULONG SidCount
, SidLength
;
63 /* Not yet supported */
64 ASSERT(PrincipalSelfSid
== NULL
);
65 ASSERT(Restricted
== FALSE
);
67 /* Check if a principal SID was given, and this is our current SID already */
68 if ((PrincipalSelfSid
) && (RtlEqualSid(SePrincipalSelfSid
, Sid
)))
70 /* Just use the principal SID in this case */
71 Sid
= PrincipalSelfSid
;
74 /* Check if this is a restricted token or not */
77 /* Use the restricted SIDs and count */
78 SidAndAttributes
= Token
->RestrictedSids
;
79 SidCount
= Token
->RestrictedSidCount
;
83 /* Use the normal SIDs and count */
84 SidAndAttributes
= Token
->UserAndGroups
;
85 SidCount
= Token
->UserAndGroupCount
;
88 /* Do checks here by hand instead of the usual 4 function calls */
89 SidLength
= FIELD_OFFSET(SID
,
90 SubAuthority
[Sid
->SubAuthorityCount
]);
91 SidMetadata
= *(PUSHORT
)&Sid
->Revision
;
94 for (i
= 0; i
< SidCount
; i
++)
96 TokenSid
= (PISID
)SidAndAttributes
->Sid
;
98 UNICODE_STRING sidString
;
99 RtlConvertSidToUnicodeString(&sidString
, TokenSid
, TRUE
);
100 DPRINT1("SID in Token: %wZ\n", &sidString
);
101 RtlFreeUnicodeString(&sidString
);
103 /* Check if the SID metadata matches */
104 if (*(PUSHORT
)&TokenSid
->Revision
== SidMetadata
)
106 /* Check if the SID data matches */
107 if (RtlEqualMemory(Sid
, TokenSid
, SidLength
))
109 /* Check if the group is enabled, or used for deny only */
110 if ((!(i
) && !(SidAndAttributes
->Attributes
& SE_GROUP_USE_FOR_DENY_ONLY
)) ||
111 (SidAndAttributes
->Attributes
& SE_GROUP_ENABLED
) ||
112 ((Deny
) && (SidAndAttributes
->Attributes
& SE_GROUP_USE_FOR_DENY_ONLY
)))
119 /* SID is not present */
125 /* Move to the next SID */
129 /* SID is not present */
135 * Checks if a SID is present in a token.
138 * A valid token object.
144 * Returns TRUE if the specified SID in the call is present in the token,
150 _In_ PACCESS_TOKEN _Token
,
153 /* Call extended API */
154 return SepSidInTokenEx(_Token
, NULL
, Sid
, FALSE
, FALSE
);
159 * Checks if a token belongs to the main user, being the owner.
162 * A valid token object.
164 * @param[in] SecurityDescriptor
165 * A security descriptor where the owner is to be found.
167 * @param[in] TokenLocked
168 * If set to TRUE, the token has been already locked and there's
169 * no need to lock it again. Otherwise the function will acquire
173 * Returns TRUE if the token belongs to a owner, FALSE otherwise.
178 _In_ PACCESS_TOKEN _Token
,
179 _In_ PSECURITY_DESCRIPTOR SecurityDescriptor
,
180 _In_ BOOLEAN TokenLocked
)
184 PTOKEN Token
= _Token
;
186 /* Get the owner SID */
187 Sid
= SepGetOwnerFromDescriptor(SecurityDescriptor
);
190 /* Lock the token if needed */
191 if (!TokenLocked
) SepAcquireTokenLockShared(Token
);
193 /* Check if the owner SID is found, handling restricted case as well */
194 Result
= SepSidInToken(Token
, Sid
);
195 if ((Result
) && (Token
->TokenFlags
& TOKEN_IS_RESTRICTED
))
197 Result
= SepSidInTokenEx(Token
, NULL
, Sid
, FALSE
, TRUE
);
200 /* Release the lock if we had acquired it */
201 if (!TokenLocked
) SepReleaseTokenLock(Token
);
203 /* Return the result */
209 * Retrieves token control information.
212 * A valid token object.
214 * @param[out] SecurityDescriptor
215 * The returned token control information.
222 SeGetTokenControlInformation(
223 _In_ PACCESS_TOKEN _Token
,
224 _Out_ PTOKEN_CONTROL TokenControl
)
226 PTOKEN Token
= _Token
;
229 /* Capture the main fields */
230 TokenControl
->AuthenticationId
= Token
->AuthenticationId
;
231 TokenControl
->TokenId
= Token
->TokenId
;
232 TokenControl
->TokenSource
= Token
->TokenSource
;
235 SepAcquireTokenLockShared(Token
);
237 /* Capture the modified ID */
238 TokenControl
->ModifiedId
= Token
->ModifiedId
;
241 SepReleaseTokenLock(Token
);
246 * Creates a client security context based upon an access token.
249 * A valid token object.
251 * @param[in] ClientSecurityQos
252 * The Quality of Service (QoS) of a client security context.
254 * @param[in] ServerIsRemote
255 * If the client is a remote server (TRUE), the function will retrieve the
256 * control information of an access token, that is, we're doing delegation
257 * and that the server isn't local.
259 * @param[in] TokenType
262 * @param[in] ThreadEffectiveOnly
263 * If set to TRUE, the client wants that the current thread wants to modify
264 * (enable or disable) privileges and groups.
266 * @param[in] ImpersonationLevel
267 * Security impersonation level filled in the QoS context.
269 * @param[out] ClientContext
270 * The returned security client context.
273 * Returns STATUS_SUCCESS if client security creation has completed successfully.
274 * STATUS_INVALID_PARAMETER is returned if one or more of the parameters are bogus.
275 * STATUS_BAD_IMPERSONATION_LEVEL is returned if the current impersonation level
276 * within QoS context doesn't meet with the conditions required. A failure
277 * NTSTATUS code is returned otherwise.
281 SepCreateClientSecurity(
282 _In_ PACCESS_TOKEN Token
,
283 _In_ PSECURITY_QUALITY_OF_SERVICE ClientSecurityQos
,
284 _In_ BOOLEAN ServerIsRemote
,
285 _In_ TOKEN_TYPE TokenType
,
286 _In_ BOOLEAN ThreadEffectiveOnly
,
287 _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel
,
288 _Out_ PSECURITY_CLIENT_CONTEXT ClientContext
)
291 PACCESS_TOKEN NewToken
;
294 /* Check for bogus impersonation level */
295 if (!VALID_IMPERSONATION_LEVEL(ClientSecurityQos
->ImpersonationLevel
))
298 return STATUS_INVALID_PARAMETER
;
301 /* Check what kind of token this is */
302 if (TokenType
!= TokenImpersonation
)
304 /* On a primary token, if we do direct access, copy the flag from the QOS */
305 ClientContext
->DirectAccessEffectiveOnly
= ClientSecurityQos
->EffectiveOnly
;
309 /* This is an impersonation token, is the level ok? */
310 if (ClientSecurityQos
->ImpersonationLevel
> ImpersonationLevel
)
313 return STATUS_BAD_IMPERSONATION_LEVEL
;
316 /* Is the level too low, or are we doing something other than delegation remotely */
317 if ((ImpersonationLevel
== SecurityAnonymous
) ||
318 (ImpersonationLevel
== SecurityIdentification
) ||
319 ((ServerIsRemote
) && (ImpersonationLevel
!= SecurityDelegation
)))
322 return STATUS_BAD_IMPERSONATION_LEVEL
;
325 /* Pick either the thread setting or the QOS setting */
326 ClientContext
->DirectAccessEffectiveOnly
=
327 ((ThreadEffectiveOnly
) || (ClientSecurityQos
->EffectiveOnly
)) ? TRUE
: FALSE
;
330 /* Is this static tracking */
331 if (ClientSecurityQos
->ContextTrackingMode
== SECURITY_STATIC_TRACKING
)
333 /* Do not use direct access and make a copy */
334 ClientContext
->DirectlyAccessClientToken
= FALSE
;
335 Status
= SeCopyClientToken(Token
,
336 ClientSecurityQos
->ImpersonationLevel
,
339 if (!NT_SUCCESS(Status
))
344 /* Use direct access and check if this is local */
345 ClientContext
->DirectlyAccessClientToken
= TRUE
;
348 /* We are doing delegation, so make a copy of the control data */
349 SeGetTokenControlInformation(Token
,
350 &ClientContext
->ClientTokenControl
);
353 /* Keep the same token */
357 /* Fill out the context and return success */
358 ClientContext
->SecurityQos
.Length
= sizeof(SECURITY_QUALITY_OF_SERVICE
);
359 ClientContext
->SecurityQos
.ImpersonationLevel
= ClientSecurityQos
->ImpersonationLevel
;
360 ClientContext
->SecurityQos
.ContextTrackingMode
= ClientSecurityQos
->ContextTrackingMode
;
361 ClientContext
->SecurityQos
.EffectiveOnly
= ClientSecurityQos
->EffectiveOnly
;
362 ClientContext
->ServerIsRemote
= ServerIsRemote
;
363 ClientContext
->ClientToken
= NewToken
;
364 return STATUS_SUCCESS
;
367 /* PUBLIC FUNCTIONS ***********************************************************/
371 * An extended function that captures the security subject context based upon
372 * the specified thread and process.
375 * A thread where the calling thread's token is to be referenced for
376 * the security context.
379 * A process where the main process' token is to be referenced for
380 * the security context.
382 * @param[out] SubjectContext
383 * The returned security subject context.
390 SeCaptureSubjectContextEx(
391 _In_ PETHREAD Thread
,
392 _In_ PEPROCESS Process
,
393 _Out_ PSECURITY_SUBJECT_CONTEXT SubjectContext
)
395 BOOLEAN CopyOnOpen
, EffectiveOnly
;
399 /* Save the unique ID */
400 SubjectContext
->ProcessAuditId
= Process
->UniqueProcessId
;
402 /* Check if we have a thread */
405 /* We don't, so no token */
406 SubjectContext
->ClientToken
= NULL
;
410 /* Get the impersonation token */
411 SubjectContext
->ClientToken
= PsReferenceImpersonationToken(Thread
,
414 &SubjectContext
->ImpersonationLevel
);
417 /* Get the primary token */
418 SubjectContext
->PrimaryToken
= PsReferencePrimaryToken(Process
);
423 * Captures the security subject context of the calling thread and calling
426 * @param[out] SubjectContext
427 * The returned security subject context.
434 SeCaptureSubjectContext(
435 _Out_ PSECURITY_SUBJECT_CONTEXT SubjectContext
)
437 /* Call the extended API */
438 SeCaptureSubjectContextEx(PsGetCurrentThread(),
439 PsGetCurrentProcess(),
445 * Locks both the referenced primary and client access tokens of a
446 * security subject context.
448 * @param[in] SubjectContext
449 * A valid security context with both referenced tokens.
456 SeLockSubjectContext(
457 _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext
)
459 PTOKEN PrimaryToken
, ClientToken
;
462 /* Read both tokens */
463 PrimaryToken
= SubjectContext
->PrimaryToken
;
464 ClientToken
= SubjectContext
->ClientToken
;
466 /* Always lock the primary */
467 SepAcquireTokenLockShared(PrimaryToken
);
469 /* Lock the impersonation one if it's there */
470 if (!ClientToken
) return;
471 SepAcquireTokenLockShared(ClientToken
);
476 * Unlocks both the referenced primary and client access tokens of a
477 * security subject context.
479 * @param[in] SubjectContext
480 * A valid security context with both referenced tokens.
487 SeUnlockSubjectContext(
488 _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext
)
490 PTOKEN PrimaryToken
, ClientToken
;
493 /* Read both tokens */
494 PrimaryToken
= SubjectContext
->PrimaryToken
;
495 ClientToken
= SubjectContext
->ClientToken
;
497 /* Unlock the impersonation one if it's there */
500 SepReleaseTokenLock(ClientToken
);
503 /* Always unlock the primary one */
504 SepReleaseTokenLock(PrimaryToken
);
509 * Releases both the primary and client tokens of a security
512 * @param[in] SubjectContext
513 * The captured security context.
520 SeReleaseSubjectContext(
521 _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext
)
525 /* Drop reference on the primary */
526 ObFastDereferenceObject(&PsGetCurrentProcess()->Token
, SubjectContext
->PrimaryToken
);
527 SubjectContext
->PrimaryToken
= NULL
;
529 /* Drop reference on the impersonation, if there was one */
530 PsDereferenceImpersonationToken(SubjectContext
->ClientToken
);
531 SubjectContext
->ClientToken
= NULL
;
536 * An extended function that creates an access state.
539 * Valid thread object where subject context is to be captured.
542 * Valid process object where subject context is to be captured.
544 * @param[in,out] AccessState
545 * An initialized returned parameter to an access state.
548 * Auxiliary security data for access state.
551 * Type of access mask to assign.
553 * @param[in] GenericMapping
554 * Generic mapping for the access state to assign.
557 * Returns STATUS_SUCCESS.
561 SeCreateAccessStateEx(
562 _In_ PETHREAD Thread
,
563 _In_ PEPROCESS Process
,
564 _Inout_ PACCESS_STATE AccessState
,
565 _In_ PAUX_ACCESS_DATA AuxData
,
566 _In_ ACCESS_MASK Access
,
567 _In_ PGENERIC_MAPPING GenericMapping
)
569 ACCESS_MASK AccessMask
= Access
;
573 /* Map the Generic Acess to Specific Access if we have a Mapping */
574 if ((Access
& GENERIC_ACCESS
) && (GenericMapping
))
576 RtlMapGenericMask(&AccessMask
, GenericMapping
);
579 /* Initialize the Access State */
580 RtlZeroMemory(AccessState
, sizeof(ACCESS_STATE
));
581 ASSERT(AccessState
->SecurityDescriptor
== NULL
);
582 ASSERT(AccessState
->PrivilegesAllocated
== FALSE
);
584 /* Initialize and save aux data */
585 RtlZeroMemory(AuxData
, sizeof(AUX_ACCESS_DATA
));
586 AccessState
->AuxData
= AuxData
;
588 /* Capture the Subject Context */
589 SeCaptureSubjectContextEx(Thread
,
591 &AccessState
->SubjectSecurityContext
);
593 /* Set Access State Data */
594 AccessState
->RemainingDesiredAccess
= AccessMask
;
595 AccessState
->OriginalDesiredAccess
= AccessMask
;
596 ExAllocateLocallyUniqueId(&AccessState
->OperationID
);
598 /* Get the Token to use */
599 Token
= SeQuerySubjectContextToken(&AccessState
->SubjectSecurityContext
);
601 /* Check for Travers Privilege */
602 if (Token
->TokenFlags
& TOKEN_HAS_TRAVERSE_PRIVILEGE
)
604 /* Preserve the Traverse Privilege */
605 AccessState
->Flags
= TOKEN_HAS_TRAVERSE_PRIVILEGE
;
608 /* Set the Auxiliary Data */
609 AuxData
->PrivilegeSet
= (PPRIVILEGE_SET
)((ULONG_PTR
)AccessState
+
610 FIELD_OFFSET(ACCESS_STATE
,
612 if (GenericMapping
) AuxData
->GenericMapping
= *GenericMapping
;
615 return STATUS_SUCCESS
;
620 * Creates an access state.
622 * @param[in,out] AccessState
623 * An initialized returned parameter to an access state.
626 * Auxiliary security data for access state.
629 * Type of access mask to assign.
631 * @param[in] GenericMapping
632 * Generic mapping for the access state to assign.
635 * See SeCreateAccessStateEx.
640 _Inout_ PACCESS_STATE AccessState
,
641 _In_ PAUX_ACCESS_DATA AuxData
,
642 _In_ ACCESS_MASK Access
,
643 _In_ PGENERIC_MAPPING GenericMapping
)
647 /* Call the extended API */
648 return SeCreateAccessStateEx(PsGetCurrentThread(),
649 PsGetCurrentProcess(),
658 * Deletes an allocated access state from the memory.
660 * @param[in] AccessState
661 * A valid access state.
669 _In_ PACCESS_STATE AccessState
)
671 PAUX_ACCESS_DATA AuxData
;
674 /* Get the Auxiliary Data */
675 AuxData
= AccessState
->AuxData
;
677 /* Deallocate Privileges */
678 if (AccessState
->PrivilegesAllocated
)
679 ExFreePoolWithTag(AuxData
->PrivilegeSet
, TAG_PRIVILEGE_SET
);
681 /* Deallocate Name and Type Name */
682 if (AccessState
->ObjectName
.Buffer
)
684 ExFreePool(AccessState
->ObjectName
.Buffer
);
687 if (AccessState
->ObjectTypeName
.Buffer
)
689 ExFreePool(AccessState
->ObjectTypeName
.Buffer
);
692 /* Release the Subject Context */
693 SeReleaseSubjectContext(&AccessState
->SubjectSecurityContext
);
698 * Sets a new generic mapping for an allocated access state.
700 * @param[in] AccessState
701 * A valid access state.
703 * @param[in] GenericMapping
704 * New generic mapping to assign.
711 SeSetAccessStateGenericMapping(
712 _In_ PACCESS_STATE AccessState
,
713 _In_ PGENERIC_MAPPING GenericMapping
)
717 /* Set the Generic Mapping */
718 ((PAUX_ACCESS_DATA
)AccessState
->AuxData
)->GenericMapping
= *GenericMapping
;
723 * Creates a client security context.
726 * Thread object of the client where impersonation has to begin.
729 * Quality of service to specify what kind of impersonation to be done.
731 * @param[in] RemoteClient
732 * If set to TRUE, the client that we're going to impersonate is remote.
734 * @param[out] ClientContext
735 * The returned security client context.
738 * See SepCreateClientSecurity.
742 SeCreateClientSecurity(
743 _In_ PETHREAD Thread
,
744 _In_ PSECURITY_QUALITY_OF_SERVICE Qos
,
745 _In_ BOOLEAN RemoteClient
,
746 _Out_ PSECURITY_CLIENT_CONTEXT ClientContext
)
748 TOKEN_TYPE TokenType
;
749 BOOLEAN ThreadEffectiveOnly
;
750 SECURITY_IMPERSONATION_LEVEL ImpersonationLevel
;
755 /* Reference the correct token */
756 Token
= PsReferenceEffectiveToken(Thread
,
758 &ThreadEffectiveOnly
,
759 &ImpersonationLevel
);
761 /* Create client security from it */
762 Status
= SepCreateClientSecurity(Token
,
770 /* Check if we failed or static tracking was used */
771 if (!(NT_SUCCESS(Status
)) || (Qos
->ContextTrackingMode
== SECURITY_STATIC_TRACKING
))
773 /* Dereference our copy since it's not being used */
774 ObDereferenceObject(Token
);
783 * Creates a client security context based upon the captured security
786 * @param[in] SubjectContext
787 * The captured subject context where client security is to be created
790 * @param[in] ClientSecurityQos
791 * Quality of service to specify what kind of impersonation to be done.
793 * @param[in] ServerIsRemote
794 * If set to TRUE, the client that we're going to impersonate is remote.
796 * @param[out] ClientContext
797 * The returned security client context.
800 * See SepCreateClientSecurity.
804 SeCreateClientSecurityFromSubjectContext(
805 _In_ PSECURITY_SUBJECT_CONTEXT SubjectContext
,
806 _In_ PSECURITY_QUALITY_OF_SERVICE ClientSecurityQos
,
807 _In_ BOOLEAN ServerIsRemote
,
808 _Out_ PSECURITY_CLIENT_CONTEXT ClientContext
)
814 /* Get the right token and reference it */
815 Token
= SeQuerySubjectContextToken(SubjectContext
);
816 ObReferenceObject(Token
);
818 /* Create the context */
819 Status
= SepCreateClientSecurity(Token
,
822 SubjectContext
->ClientToken
?
823 TokenImpersonation
: TokenPrimary
,
825 SubjectContext
->ImpersonationLevel
,
828 /* Check if we failed or static tracking was used */
829 if (!(NT_SUCCESS(Status
)) ||
830 (ClientSecurityQos
->ContextTrackingMode
== SECURITY_STATIC_TRACKING
))
832 /* Dereference our copy since it's not being used */
833 ObDereferenceObject(Token
);
842 * Extended function that impersonates a client.
844 * @param[in] ClientContext
845 * A valid client context.
847 * @param[in] ServerThread
848 * The thread where impersonation is to be done.
851 * STATUS_SUCCESS is returned if the calling thread successfully impersonates
852 * the client. A failure NTSTATUS code is returned otherwise.
856 SeImpersonateClientEx(
857 _In_ PSECURITY_CLIENT_CONTEXT ClientContext
,
858 _In_opt_ PETHREAD ServerThread
)
860 BOOLEAN EffectiveOnly
;
863 /* Check if direct access is requested */
864 if (!ClientContext
->DirectlyAccessClientToken
)
866 /* No, so get the flag from QOS */
867 EffectiveOnly
= ClientContext
->SecurityQos
.EffectiveOnly
;
871 /* Yes, so see if direct access should be effective only */
872 EffectiveOnly
= ClientContext
->DirectAccessEffectiveOnly
;
875 /* Use the current thread if one was not passed */
876 if (!ServerThread
) ServerThread
= PsGetCurrentThread();
878 /* Call the lower layer routine */
879 return PsImpersonateClient(ServerThread
,
880 ClientContext
->ClientToken
,
883 ClientContext
->SecurityQos
.ImpersonationLevel
);
888 * Impersonates a client user.
890 * @param[in] ClientContext
891 * A valid client context.
893 * @param[in] ServerThread
894 * The thread where impersonation is to be done.
902 _In_ PSECURITY_CLIENT_CONTEXT ClientContext
,
903 _In_opt_ PETHREAD ServerThread
)
907 /* Call the new API */
908 SeImpersonateClientEx(ClientContext
, ServerThread
);